Merge branch 'master' of ssh://skeezixgit@git.openpandora.org/srv/git/pandora-libraries
[pandora-libraries.git] / minimenu / mmui.c
1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <limits.h>
5 #include <time.h>
6 #include "SDL.h"
7 #include "SDL_audio.h"
8 #include "SDL_image.h"
9 #include "SDL_ttf.h"
10 #include "SDL_gfxPrimitives.h"
11 #include "SDL_rotozoom.h"
12
13 #include "pnd_conf.h"
14 #include "pnd_logger.h"
15 #include "pnd_pxml.h"
16 #include "pnd_container.h"
17 #include "pnd_discovery.h"
18 #include "pnd_apps.h"
19 #include "pnd_device.h"
20
21 #include "mmenu.h"
22 #include "mmcat.h"
23 #include "mmcache.h"
24 #include "mmui.h"
25 #include "mmwrapcmd.h"
26
27 /* SDL
28  */
29 SDL_Surface *sdl_realscreen = NULL;
30 unsigned int sdl_ticks = 0;
31
32 /* app state
33  */
34 unsigned short int g_scale = 1; // 1 == noscale
35
36 SDL_Surface *g_imgcache [ IMG_MAX ];
37
38 TTF_Font *g_big_font = NULL;
39 TTF_Font *g_grid_font = NULL;
40 TTF_Font *g_detailtext_font = NULL;
41 TTF_Font *g_tab_font = NULL;
42
43 extern pnd_conf_handle g_conf;
44
45 /* current display state
46  */
47 int ui_rows_scrolled_down = 0;          // number of rows that should be missing from top of the display
48 mm_appref_t *ui_selected = NULL;
49 unsigned char ui_category = 0;          // current category
50 unsigned char ui_catshift = 0;          // how many cats are offscreen to the left
51
52 extern mm_category_t g_categories [ MAX_CATS ];
53 extern unsigned char g_categorycount;
54
55 static SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ); // height -1 means ignore
56 static int ui_selected_index ( void );
57
58 unsigned char ui_setup ( void ) {
59
60   /* set up SDL
61    */
62
63   SDL_Init ( SDL_INIT_EVERYTHING | SDL_INIT_NOPARACHUTE );
64
65   SDL_JoystickOpen ( 0 ); // turn on joy-0
66
67   SDL_WM_SetCaption ( "mmenu", "mmenu" );
68
69   // hide the mouse cursor if we can
70   if ( SDL_ShowCursor ( -1 ) == 1 ) {
71     SDL_ShowCursor ( 0 );
72   }
73
74   atexit ( SDL_Quit );
75
76   // open up a surface
77   unsigned int svm = SDL_SWSURFACE /*| SDL_FULLSCREEN*/ /* 0*/;
78   if ( pnd_conf_get_as_int_d ( g_conf, "display.fullscreen", 0 ) > 0 ) {
79     svm |= SDL_FULLSCREEN;
80   }
81
82   sdl_realscreen =
83     SDL_SetVideoMode ( 800 * g_scale, 480 * g_scale, 16 /*bpp*/, svm );
84
85   if ( ! sdl_realscreen ) {
86     pnd_log ( pndn_error, "ERROR: Couldn't open SDL real screen; dieing." );
87     return ( 0 );
88   }
89
90   pnd_log ( pndn_debug, "Pixel format:" );
91   pnd_log ( pndn_debug, "bpp b: %u\n", sdl_realscreen -> format -> BitsPerPixel );
92   pnd_log ( pndn_debug, "bpp B: %u\n", sdl_realscreen -> format -> BytesPerPixel );
93   pnd_log ( pndn_debug, "R mask: %08x\n", sdl_realscreen -> format -> Rmask );
94   pnd_log ( pndn_debug, "G mask: %08x\n", sdl_realscreen -> format -> Gmask );
95   pnd_log ( pndn_debug, "B mask: %08x\n", sdl_realscreen -> format -> Bmask );
96
97 #if 0 // audio
98   {
99     SDL_AudioSpec fmt;
100
101     /* Set 16-bit stereo audio at 22Khz */
102     fmt.freq = 44100; //22050;
103     fmt.format = AUDIO_S16; //AUDIO_S16;
104     fmt.channels = 1;
105     fmt.samples = 2048;        /* A good value for games */
106     fmt.callback = mixaudio;
107     fmt.userdata = NULL;
108
109     /* Open the audio device and start playing sound! */
110     if ( SDL_OpenAudio ( &fmt, NULL ) < 0 ) {
111       zotlog ( "Unable to open audio: %s\n", SDL_GetError() );
112       exit ( 1 );
113     }
114
115     SDL_PauseAudio ( 0 );
116   }
117 #endif
118
119   // images
120   //IMG_Init ( IMG_INIT_JPG | IMG_INIT_PNG );
121
122   /* fonts
123    */
124
125   // init
126   if ( TTF_Init() == -1 ) {
127     pnd_log ( pndn_error, "ERROR: Couldn't set up SDL TTF lib\n" );
128     return ( 0 ); // couldn't set up SDL TTF
129   }
130
131   char fullpath [ PATH_MAX ];
132   // big font
133   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "minimenu.font" ) );
134   g_big_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
135   if ( ! g_big_font ) {
136     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
137               pnd_conf_get_as_char ( g_conf, "minimenu.font" ), pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
138     return ( 0 ); // couldn't set up SDL TTF
139   }
140
141   // grid font
142   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, MMENU_GRID_FONT ) );
143   g_grid_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, MMENU_GRID_FONTSIZE, 10 ) );
144   if ( ! g_grid_font ) {
145     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
146               pnd_conf_get_as_char ( g_conf, MMENU_GRID_FONT ), pnd_conf_get_as_int_d ( g_conf, MMENU_GRID_FONTSIZE, 10 ) );
147     return ( 0 ); // couldn't set up SDL TTF
148   }
149
150   // detailtext font
151   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "detailtext.font" ) );
152   g_detailtext_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
153   if ( ! g_detailtext_font ) {
154     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
155               pnd_conf_get_as_char ( g_conf, "detailtext.font" ), pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
156     return ( 0 ); // couldn't set up SDL TTF
157   }
158
159   // tab font
160   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "tabs.font" ) );
161   g_tab_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
162   if ( ! g_tab_font ) {
163     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
164               pnd_conf_get_as_char ( g_conf, "tabs.font" ), pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
165     return ( 0 ); // couldn't set up SDL TTF
166   }
167
168   return ( 1 );
169 }
170
171 mm_imgcache_t g_imagecache [ IMG_TRUEMAX ] = {
172   { IMG_BACKGROUND_800480,    "graphics.IMG_BACKGROUND_800480" },
173   { IMG_BACKGROUND_TABMASK,   "graphics.IMG_BACKGROUND_TABMASK" },
174   { IMG_DETAIL_PANEL,         "graphics.IMG_DETAIL_PANEL" },
175   { IMG_DETAIL_BG,            "graphics.IMG_DETAIL_BG" },
176   { IMG_SELECTED_ALPHAMASK,   "graphics.IMG_SELECTED_ALPHAMASK" },
177   { IMG_TAB_SEL,              "graphics.IMG_TAB_SEL" },
178   { IMG_TAB_UNSEL,            "graphics.IMG_TAB_UNSEL" },
179   { IMG_ICON_MISSING,         "graphics.IMG_ICON_MISSING" },
180   { IMG_SELECTED_HILITE,      "graphics.IMG_SELECTED_HILITE" },
181   { IMG_PREVIEW_MISSING,      "graphics.IMG_PREVIEW_MISSING" },
182   { IMG_ARROW_UP,             "graphics.IMG_ARROW_UP", },
183   { IMG_ARROW_DOWN,           "graphics.IMG_ARROW_DOWN", },
184   { IMG_ARROW_SCROLLBAR,      "graphics.IMG_ARROW_SCROLLBAR", },
185   { IMG_MAX,                  NULL },
186 };
187
188 unsigned char ui_imagecache ( char *basepath ) {
189   unsigned int i;
190   char fullpath [ PATH_MAX ];
191
192   // loaded
193
194   for ( i = 0; i < IMG_MAX; i++ ) {
195
196     if ( g_imagecache [ i ].id != i ) {
197       pnd_log ( pndn_error, "ERROR: Internal table mismatch during caching [%u]\n", i );
198       exit ( -1 );
199     }
200
201     char *filename = pnd_conf_get_as_char ( g_conf, g_imagecache [ i ].confname );
202
203     if ( ! filename ) {
204       pnd_log ( pndn_error, "ERROR: Missing filename in conf for key: %s\n", g_imagecache [ i ].confname );
205       return ( 0 );
206     }
207
208     sprintf ( fullpath, "%s/%s", basepath, filename );
209
210     if ( ! ( g_imagecache [ i ].i = IMG_Load ( fullpath ) ) ) {
211       pnd_log ( pndn_error, "ERROR: Couldn't load static cache image: %s\n", fullpath );
212       return ( 0 );
213     }
214
215   } // for
216
217   // generated
218   //g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = SDL_CreateRGBSurface ( SDL_SWSURFACE, 60, 60, 32, 0xFF0000, 0x00FF00, 0xFF, 0xFF000000 );
219   //boxRGBA ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, 0, 0, 60, 60, 100, 100, 100, 250 );
220
221   // post processing
222   //
223
224   // scale icons
225   g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = ui_scale_image ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ), -1 );
226   g_imagecache [ IMG_ICON_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_ICON_MISSING ].i, pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 ), -1 );
227   // scale text hilight
228   g_imagecache [ IMG_SELECTED_HILITE ].i = ui_scale_image ( g_imagecache [ IMG_SELECTED_HILITE ].i, pnd_conf_get_as_int_d ( g_conf, "grid.text_width", 50 ), -1 );
229   // scale preview no-pic
230   g_imagecache [ IMG_PREVIEW_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_PREVIEW_MISSING ].i,
231                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ),
232                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 50 ) );
233
234   // set alpha on detail panel
235   SDL_SetAlpha ( g_imagecache [ IMG_DETAIL_BG ].i, SDL_SRCALPHA, pnd_conf_get_as_int_d ( g_conf, "display.detail_bg_alpha", 50 ) );
236
237   return ( 1 );
238 } // ui_imagecache
239
240 void ui_render ( unsigned int render_mask ) {
241
242   // 800x480:
243   // divide width: 550 / 250
244   // divide left side: 5 columns == 110px width
245   //   20px buffer either side == 70px wide icon + 20 + 20?
246
247   unsigned int icon_rows;
248
249 #define MAXRECTS 200
250   SDL_Rect rects [ MAXRECTS ], src;
251   SDL_Rect *dest = rects;
252   bzero ( dest, sizeof(SDL_Rect)*MAXRECTS );
253
254   unsigned int row, displayrow, col;
255   mm_appref_t *appiter;
256
257   unsigned int screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
258
259   unsigned char row_max = pnd_conf_get_as_int_d ( g_conf, MMENU_DISP_ROWMAX, 4 );
260   unsigned char col_max = pnd_conf_get_as_int_d ( g_conf, MMENU_DISP_COLMAX, 5 );
261
262   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
263   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
264   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
265   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
266
267   unsigned int grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
268   unsigned int grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
269
270   unsigned int icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
271   unsigned int icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
272   unsigned int icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
273
274   unsigned int text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
275   unsigned int text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
276   unsigned int text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
277   unsigned int text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
278
279   unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
280   unsigned int cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
281
282   // how many total rows do we need?
283   icon_rows = g_categories [ ui_category ].refcount / col_max;
284   if ( g_categories [ ui_category ].refcount % col_max > 0 ) {
285     icon_rows++;
286   }
287
288   // if no selected app yet, select the first one
289 #if 0
290   if ( ! ui_selected ) {
291     ui_selected = g_categories [ ui_category ].refs;
292   }
293 #endif
294
295   // ensure selection is visible
296   if ( ui_selected ) {
297
298     int index = ui_selected_index();
299     int topleft = col_max * ui_rows_scrolled_down;
300     int botright = ( col_max * ( ui_rows_scrolled_down + row_max ) - 1 );
301
302     //pnd_log ( PND_LOG_DEFAULT, "index %u tl %u br %u\n", index, topleft, botright );
303
304     if ( index < topleft ) {
305       ui_rows_scrolled_down -= pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
306     } else if ( index > botright ) {
307       ui_rows_scrolled_down += pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
308     }
309
310     if ( ui_rows_scrolled_down < 0 ) {
311       ui_rows_scrolled_down = 0;
312     } else if ( ui_rows_scrolled_down > icon_rows ) {
313       ui_rows_scrolled_down = icon_rows;
314     }
315
316   } // ensire visible
317
318   // render background
319   if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
320     dest -> x = 0;
321     dest -> y = 0;
322     dest -> w = sdl_realscreen -> w;
323     dest -> h = sdl_realscreen -> h;
324     SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
325     //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
326     dest++;
327   }
328
329   // tabmask
330   if ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i ) {
331     dest -> x = 0;
332     dest -> y = 0;
333     dest -> w = sdl_realscreen -> w;
334     dest -> h = sdl_realscreen -> h;
335     SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
336     //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
337     dest++;
338   }
339
340   // tabs
341   if ( g_imagecache [ IMG_TAB_SEL ].i && g_imagecache [ IMG_TAB_UNSEL ].i ) {
342     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
343     unsigned int tab_height = pnd_conf_get_as_int ( g_conf, "tabs.tab_height" );
344     unsigned int tab_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_x" );
345     unsigned int tab_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_y" );
346     unsigned int text_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_x" );
347     unsigned int text_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_y" );
348     unsigned int text_width = pnd_conf_get_as_int ( g_conf, "tabs.text_width" );
349
350     for ( col = ui_catshift;
351           col < ( 
352                    ( screen_width / tab_width ) < g_categorycount ? ( screen_width / tab_width ) + ui_catshift : g_categorycount + ui_catshift
353                 );
354           col++ )
355     {
356
357       SDL_Surface *s;
358       if ( col == ui_category ) {
359         s = g_imagecache [ IMG_TAB_SEL ].i;
360       } else {
361         s = g_imagecache [ IMG_TAB_UNSEL ].i;
362       }
363
364       // draw tab
365       src.x = 0;
366       src.y = 0;
367       src.w = tab_width;
368       src.h = tab_height;
369       dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
370       dest -> y = tab_offset_y;
371       //pnd_log ( pndn_debug, "tab %u at %ux%u\n", col, dest.x, dest.y );
372       SDL_BlitSurface ( s, &src, sdl_realscreen, dest );
373       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
374       dest++;
375
376       // draw text
377       SDL_Surface *rtext;
378       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
379       rtext = TTF_RenderText_Blended ( g_tab_font, g_categories [ col ].catname, tmpfontcolor );
380       src.x = 0;
381       src.y = 0;
382       src.w = rtext -> w < text_width ? rtext -> w : text_width;
383       src.h = rtext -> h;
384       dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width ) + text_offset_x;
385       dest -> y = tab_offset_y + text_offset_y;
386       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
387       SDL_FreeSurface ( rtext );
388       dest++;
389
390     } // for
391
392   } // tabs
393
394   // scroll bars and arrows
395   {
396     unsigned char show_bar = 0;
397
398     // up?
399     if ( ui_rows_scrolled_down && g_imagecache [ IMG_ARROW_UP ].i ) {
400       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
401       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
402       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_UP ].i, NULL /* whole image */, sdl_realscreen, dest );
403       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
404       dest++;
405
406       show_bar = 1;
407     }
408
409     // down?
410     if ( ui_rows_scrolled_down + row_max < icon_rows && g_imagecache [ IMG_ARROW_DOWN ].i ) {
411       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
412       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
413       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_DOWN ].i, NULL /* whole image */, sdl_realscreen, dest );
414       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
415       dest++;
416
417       show_bar = 1;
418     }
419
420     if ( show_bar ) {
421       // show scrollbar as well
422       src.x = 0;
423       src.y = 0;
424       src.w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
425       src.h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
426       dest -> x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
427       dest -> y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
428       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_SCROLLBAR ].i, &src /* whole image */, sdl_realscreen, dest );
429       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
430       dest++;
431     } // bar
432
433   } // scroll bars
434
435   // render detail pane bg
436   if ( pnd_conf_get_as_int_d ( g_conf, "detailpane.show", 1 ) ) {
437
438     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
439       src.x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
440       src.y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
441       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
442       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
443       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
444       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
445       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
446       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
447       dest++;
448     }
449
450     // render detail pane
451     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
452       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
453       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
454       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
455       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
456       dest++;
457     }
458
459   } // detailpane frame/bg
460
461   // anything to render?
462   if ( g_categories [ ui_category ].refs ) {
463
464     appiter = g_categories [ ui_category ].refs;
465     row = 0;
466     displayrow = 0;
467
468     // until we run out of apps, or run out of space
469     while ( appiter != NULL ) {
470
471       for ( col = 0; col < col_max && appiter != NULL; col++ ) {
472
473         // do we even need to render it? or are we suppressing it due to rows scrolled off the top?
474         if ( row >= ui_rows_scrolled_down ) {
475
476           // selected? show hilights
477           if ( appiter == ui_selected ) {
478             // icon
479             dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x;
480             dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y;
481             SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, NULL /* all */, sdl_realscreen, dest );
482             //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
483             dest++;
484             // text
485             dest -> x = grid_offset_x + ( col * cell_width ) + text_clip_x;
486             dest -> y = grid_offset_y + ( displayrow * cell_height ) + pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
487             SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_HILITE ].i, NULL /* all */, sdl_realscreen, dest );
488             //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
489             dest++;
490           } // selected?
491
492           // show icon
493           mm_cache_t *ic = cache_query_icon ( appiter -> ref -> unique_id );
494           SDL_Surface *iconsurface;
495           if ( ic ) {
496             iconsurface = ic -> i;
497           } else {
498             //pnd_log ( pndn_warning, "WARNING: TBD: Need Missin-icon icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
499             iconsurface = g_imagecache [ IMG_ICON_MISSING ].i;
500           }
501           if ( iconsurface ) {
502             //pnd_log ( pndn_debug, "Got an icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
503
504             src.x = 0;
505             src.y = 0;
506             src.w = 60;
507             src.h = 60;
508             dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x + (( icon_max_width - iconsurface -> w ) / 2);
509             dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y;
510
511             SDL_BlitSurface ( iconsurface, &src, sdl_realscreen, dest );
512             //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
513             dest++;
514
515           }
516
517           // show text
518           if ( appiter -> ref -> title_en ) {
519             SDL_Surface *rtext;
520             SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
521             rtext = TTF_RenderText_Blended ( g_grid_font, appiter -> ref -> title_en, tmpfontcolor );
522             src.x = 0;
523             src.y = 0;
524             src.w = text_width < rtext -> w ? text_width : rtext -> w;
525             src.h = rtext -> h;
526             if ( rtext -> w > text_width ) {
527               dest -> x = grid_offset_x + ( col * cell_width ) + text_clip_x;
528             } else {
529               dest -> x = grid_offset_x + ( col * cell_width ) + text_offset_x - ( rtext -> w / 2 );
530             }
531             dest -> y = grid_offset_y + ( displayrow * cell_height ) + text_offset_y;
532             SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
533             SDL_FreeSurface ( rtext );
534             dest++;
535           }
536
537         } // display now? or scrolled away..
538
539         // next
540         appiter = appiter -> next;
541
542       } // for column 1...X
543
544       if ( row >= ui_rows_scrolled_down ) {
545         displayrow++;
546       }
547
548       row ++;
549
550       // are we done displaying rows?
551       if ( displayrow >= row_max ) {
552         break;
553       }
554
555     } // while
556
557   } else {
558     // no apps to render?
559     pnd_log ( pndn_rem, "No applications to render?\n" );
560   } // apps to renser?
561
562   // detail panel
563   if ( ui_selected ) {
564
565     unsigned int cell_offset_x = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_x" );
566     unsigned int cell_offset_y = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_y" );
567     unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "detailtext.cell_width" );
568
569     unsigned int desty = cell_offset_y;
570
571     char buffer [ 256 ];
572
573     // full name
574     if ( ui_selected -> ref -> title_en ) {
575       SDL_Surface *rtext;
576       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
577       rtext = TTF_RenderText_Blended ( g_detailtext_font, ui_selected -> ref -> title_en, tmpfontcolor );
578       src.x = 0;
579       src.y = 0;
580       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
581       src.h = rtext -> h;
582       dest -> x = cell_offset_x;
583       dest -> y = desty;
584       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
585       SDL_FreeSurface ( rtext );
586       dest++;
587       desty += src.h;
588     }
589
590     // category
591     if ( ui_selected -> ref -> main_category ) {
592
593       sprintf ( buffer, "Category: %s", ui_selected -> ref -> main_category );
594
595       SDL_Surface *rtext;
596       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
597       rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, tmpfontcolor );
598       src.x = 0;
599       src.y = 0;
600       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
601       src.h = rtext -> h;
602       dest -> x = cell_offset_x;
603       dest -> y = desty;
604       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
605       SDL_FreeSurface ( rtext );
606       dest++;
607       desty += src.h;
608     }
609
610     // clock
611     if ( ui_selected -> ref -> clockspeed ) {
612
613       sprintf ( buffer, "CPU Clock: %s", ui_selected -> ref -> clockspeed );
614
615       SDL_Surface *rtext;
616       SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
617       rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, tmpfontcolor );
618       src.x = 0;
619       src.y = 0;
620       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
621       src.h = rtext -> h;
622       dest -> x = cell_offset_x;
623       dest -> y = desty;
624       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
625       SDL_FreeSurface ( rtext );
626       dest++;
627       desty += src.h;
628     }
629
630     // preview pic
631     mm_cache_t *ic = cache_query_preview ( ui_selected -> ref -> unique_id );
632     SDL_Surface *previewpic;
633
634     if ( ic ) {
635       previewpic = ic -> i;
636     } else {
637       previewpic = g_imagecache [ IMG_PREVIEW_MISSING ].i;
638     }
639
640     if ( previewpic ) {
641       dest -> x = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_x", 50 ) +
642         ( ( pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ) - previewpic -> w ) / 2 );
643       dest -> y = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_y", 50 );
644       SDL_BlitSurface ( previewpic, NULL /* whole image */, sdl_realscreen, dest );
645       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
646       dest++;
647     }
648
649   } // selected?
650
651   // extras
652   //
653
654   // battery
655   if ( 1 ) {
656     unsigned char batterylevel = pnd_device_get_battery_gauge_perc();
657     char buffer [ 100 ];
658
659     sprintf ( buffer, "Battery: %u%%", batterylevel );
660
661     SDL_Surface *rtext;
662     SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
663     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, tmpfontcolor );
664     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.battery_x", 20 );
665     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.battery_y", 450 );
666     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
667     SDL_FreeSurface ( rtext );
668     dest++;
669   }
670
671   // hints
672   if ( pnd_conf_get_as_char ( g_conf, "display.hintline" ) ) {
673     char *buffer = pnd_conf_get_as_char ( g_conf, "display.hintline" );
674     SDL_Surface *rtext;
675     SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
676     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, tmpfontcolor );
677     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.hint_x", 40 );
678     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.hint_y", 450 );
679     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
680     SDL_FreeSurface ( rtext );
681     dest++;
682   }
683
684   // hints
685   if ( pnd_conf_get_as_int_d ( g_conf, "display.clock_x", -1 ) != -1 ) {
686     char buffer [ 50 ];
687
688     time_t t = time ( NULL );
689     struct tm *tm = localtime ( &t );
690     strftime ( buffer, 50, "%a %H:%M %F", tm );
691
692     SDL_Surface *rtext;
693     SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
694     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, tmpfontcolor );
695     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.clock_x", 700 );
696     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.clock_y", 450 );
697     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
698     SDL_FreeSurface ( rtext );
699     dest++;
700   }
701
702   // update all the rects and send it all to sdl
703   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
704
705 } // ui_render
706
707 void ui_process_input ( unsigned char block_p ) {
708   SDL_Event event;
709
710   unsigned char ui_event = 0; // if we get a ui event, flip to 1 and break
711   static ui_sdl_button_e ui_mask = uisb_none; // current buttons down
712
713   while ( ! ui_event &&
714           block_p ? SDL_WaitEvent ( &event ) : SDL_PollEvent ( &event ) )
715   {
716
717     switch ( event.type ) {
718
719     case SDL_USEREVENT:
720       // update something
721
722       if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
723
724         pnd_log ( pndn_debug, "Deferred preview pic load ----------\n" );
725
726         // load the preview pics now!
727         pnd_disco_t *iter = ui_selected -> ref;
728
729         if ( iter -> preview_pic1 &&
730              ! cache_preview ( iter, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ), pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) ) )
731         {
732           pnd_log ( pndn_debug, "  Couldn't load preview pic: '%s' -> '%s'\n", IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
733         }
734
735         pnd_log ( pndn_debug, "Deferred preview pic load finish ---\n" );
736
737         ui_event++;
738       }
739
740       break;
741
742 #if 0 // joystick motion
743     case SDL_JOYAXISMOTION:
744
745       pnd_log ( PND_LOG_DEFAULT, "joystick axis\n" );
746
747         if ( event.jaxis.axis == 0 ) {
748           // horiz
749           if ( event.jaxis.value < 0 ) {
750             ui_push_left ( 0 );
751             pnd_log ( PND_LOG_DEFAULT, "joystick axis - LEFT\n" );
752           } else if ( event.jaxis.value > 0 ) {
753             ui_push_right ( 0 );
754             pnd_log ( PND_LOG_DEFAULT, "joystick axis - RIGHT\n" );
755           }
756         } else if ( event.jaxis.axis == 1 ) {
757           // vert
758           if ( event.jaxis.value < 0 ) {
759             ui_push_up();
760           } else if ( event.jaxis.value > 0 ) {
761             ui_push_down();
762           }
763         }
764
765         ui_event++;
766
767         break;
768 #endif
769
770 #if 0 // joystick buttons
771     case SDL_JOYBUTTONDOWN:
772
773       pnd_log ( PND_LOG_DEFAULT, "joystick button down %u\n", event.jbutton.button );
774
775       if ( event.jbutton.button == 0 ) { // B
776         ui_mask |= uisb_b;
777       } else if ( event.jbutton.button == 1 ) { // Y
778         ui_mask |= uisb_y;
779       } else if ( event.jbutton.button == 2 ) { // X
780         ui_mask |= uisb_x;
781       } else if ( event.jbutton.button == 3 ) { // A
782         ui_mask |= uisb_a;
783
784       } else if ( event.jbutton.button == 4 ) { // Select
785         ui_mask |= uisb_select;
786       } else if ( event.jbutton.button == 5 ) { // Start
787         ui_mask |= uisb_start;
788
789       } else if ( event.jbutton.button == 7 ) { // L
790         ui_mask |= uisb_l;
791       } else if ( event.jbutton.button == 8 ) { // R
792         ui_mask |= uisb_r;
793
794       }
795
796       ui_event++;
797
798       break;
799
800     case SDL_JOYBUTTONUP:
801
802       pnd_log ( PND_LOG_DEFAULT, "joystick button up %u\n", event.jbutton.button );
803
804       if ( event.jbutton.button == 0 ) { // B
805         ui_mask &= ~uisb_b;
806         ui_push_exec();
807       } else if ( event.jbutton.button == 1 ) { // Y
808         ui_mask &= ~uisb_y;
809       } else if ( event.jbutton.button == 2 ) { // X
810         ui_mask &= ~uisb_x;
811       } else if ( event.jbutton.button == 3 ) { // A
812         ui_mask &= ~uisb_a;
813
814       } else if ( event.jbutton.button == 4 ) { // Select
815         ui_mask &= ~uisb_select;
816       } else if ( event.jbutton.button == 5 ) { // Start
817         ui_mask &= ~uisb_start;
818
819       } else if ( event.jbutton.button == 7 ) { // L
820         ui_mask &= ~uisb_l;
821         ui_push_ltrigger();
822       } else if ( event.jbutton.button == 8 ) { // R
823         ui_mask &= ~uisb_r;
824         ui_push_rtrigger();
825
826       }
827
828       ui_event++;
829
830       break;
831 #endif
832
833 #if 1 // keyboard events
834     case SDL_KEYUP:
835
836       //pnd_log ( pndn_debug, "key up %u\n", event.key.keysym.sym );
837
838       // SDLK_LALT -> Start
839
840       // directional
841       if ( event.key.keysym.sym == SDLK_RIGHT ) {
842         ui_push_right ( 0 );
843         ui_event++;
844       } else if ( event.key.keysym.sym == SDLK_LEFT ) {
845         ui_push_left ( 0 );
846         ui_event++;
847       } else if ( event.key.keysym.sym == SDLK_UP ) {
848         ui_push_up();
849         ui_event++;
850       } else if ( event.key.keysym.sym == SDLK_DOWN ) {
851         ui_push_down();
852         ui_event++;
853       } else if ( event.key.keysym.sym == SDLK_SPACE || event.key.keysym.sym == SDLK_END ) {
854         ui_push_exec();
855         ui_event++;
856       } else if ( event.key.keysym.sym == SDLK_z || event.key.keysym.sym == SDLK_RSHIFT ) {
857         ui_push_ltrigger();
858         ui_event++;
859       } else if ( event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_RCTRL ) {
860         ui_push_rtrigger();
861         ui_event++;
862
863       } else if ( event.key.keysym.sym == SDLK_LALT ) { // start button
864         char *opts [ 20 ] = {
865           "Return to Minimenu",
866           "Shutdown Pandora",
867           "Rescan for Applications",
868           "Run xfce4 from Minimenu",
869           "Set to full desktop and reboot",
870           "Set to pmenu and reboot",
871           "Quit (<- beware)",
872           "About Minimenu"
873         };
874         int sel = ui_modal_single_menu ( opts, 8, "Minimenu", "Enter to select; other to return." );
875
876         char buffer [ 100 ];
877         if ( sel == 0 ) {
878           // do nothing
879         } else if ( sel == 1 ) {
880           // shutdown
881           sprintf ( buffer, "sudo poweroff" );
882           system ( buffer );
883         } else if ( sel == 2 ) {
884           // rescan apps
885         } else if ( sel == 3 ) {
886           // run xfce
887           char buffer [ PATH_MAX ];
888           sprintf ( buffer, "%s %s\n", MM_RUN, "/usr/bin/startxfce4" );
889           emit_and_quit ( buffer );
890         } else if ( sel == 4 ) {
891           // set env to xfce
892           sprintf ( buffer, "echo startxfce4 > /tmp/gui.load" );
893           system ( buffer );
894           sprintf ( buffer, "sudo poweroff" );
895           system ( buffer );
896         } else if ( sel == 5 ) {
897           // set env to pmenu
898           sprintf ( buffer, "echo pmenu > /tmp/gui.load" );
899           system ( buffer );
900           sprintf ( buffer, "sudo poweroff" );
901           system ( buffer );
902         } else if ( sel == 6 ) {
903           emit_and_quit ( MM_QUIT );
904         } else if ( sel == 7 ) {
905           // about
906         }
907
908         ui_event++;
909       }
910
911       // extras
912       if ( event.key.keysym.sym == SDLK_q ) {
913         emit_and_quit ( MM_QUIT );
914       }
915
916       break;
917 #endif
918
919 #if 0 // mouse / touchscreen
920     case SDL_MOUSEBUTTONDOWN:
921       if ( event.button.button == SDL_BUTTON_LEFT ) {
922         cb_pointer_press ( gc, event.button.x / g_scale, event.button.y / g_scale );
923         ui_event++;
924       }
925       break;
926
927     case SDL_MOUSEBUTTONUP:
928       if ( event.button.button == SDL_BUTTON_LEFT ) {
929         cb_pointer_release ( gc, event.button.x / g_scale, event.button.y / g_scale );
930         retval |= STAT_pen;
931         ui_event++;
932       }
933       break;
934 #endif
935
936     case SDL_QUIT:
937       exit ( 0 );
938       break;
939
940     default:
941       break;
942
943     } // switch event type
944
945   } // while poll
946
947   return;
948 }
949
950 void ui_push_left ( unsigned char forcecoil ) {
951
952   if ( ! ui_selected ) {
953     ui_push_right ( 0 );
954     return;
955   }
956
957   // what column we in?
958   unsigned int col = ui_determine_screen_col ( ui_selected );
959
960   // are we alreadt at first item?
961   if ( forcecoil == 0 &&
962        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
963        col == 0 )
964   {
965     unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
966     while ( i ) {
967       ui_push_right ( 0 );
968       i--;
969     }
970
971   } else if ( g_categories [ ui_category ].refs == ui_selected ) {
972     // can't go any more left, we're at the head
973
974   } else {
975     // figure out the previous item; yay for singly linked list :/
976     mm_appref_t *i = g_categories [ ui_category ].refs;
977     while ( i ) {
978       if ( i -> next == ui_selected ) {
979         ui_selected = i;
980         break;
981       }
982       i = i -> next;
983     }
984   }
985
986   ui_set_selected ( ui_selected );
987
988   return;
989 }
990
991 void ui_push_right ( unsigned char forcecoil ) {
992
993   if ( ui_selected ) {
994
995     // what column we in?
996     unsigned int col = ui_determine_screen_col ( ui_selected );
997
998     // wrap same or no?
999     if ( forcecoil == 0 &&
1000          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1001          col == pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1 )
1002     {
1003       // same wrap
1004       unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
1005       while ( i ) {
1006         ui_push_left ( 0 );
1007         i--;
1008       }
1009
1010     } else {
1011       // just go to the next
1012
1013       if ( ui_selected -> next ) {
1014         ui_selected = ui_selected -> next;
1015       }
1016
1017     }
1018
1019   } else {
1020     ui_selected = g_categories [ ui_category ].refs;
1021   }
1022
1023   ui_set_selected ( ui_selected );
1024
1025   return;
1026 }
1027
1028 void ui_push_up ( void ) {
1029   unsigned char col_max = pnd_conf_get_as_int ( g_conf, MMENU_DISP_COLMAX );
1030
1031   // what row we in?
1032   unsigned int row = ui_determine_row ( ui_selected );
1033
1034   if ( row == 0 &&
1035        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 1 ) == 0 )
1036   {
1037     // wrap around instead
1038
1039     unsigned int col = ui_determine_screen_col ( ui_selected );
1040
1041     // go to end
1042     ui_selected = g_categories [ ui_category ].refs;
1043     while ( ui_selected -> next ) {
1044       ui_selected = ui_selected -> next;
1045     }
1046
1047     // try to move to same column
1048     unsigned int newcol = ui_determine_screen_col ( ui_selected );
1049     if ( newcol > col ) {
1050       col = newcol - col;
1051       while ( col ) {
1052         ui_push_left ( 0 );
1053         col--;
1054       }
1055     }
1056
1057     // scroll down to show it
1058     int r = ui_determine_row ( ui_selected ) - 1;
1059     if ( r - pnd_conf_get_as_int ( g_conf, MMENU_DISP_ROWMAX ) > 0 ) {
1060       ui_rows_scrolled_down = (unsigned int) r;
1061     }
1062
1063   } else {
1064     // stop at top/bottom
1065
1066     while ( col_max ) {
1067       ui_push_left ( 1 );
1068       col_max--;
1069     }
1070
1071   }
1072
1073   return;
1074 }
1075
1076 void ui_push_down ( void ) {
1077   unsigned char col_max = pnd_conf_get_as_int ( g_conf, MMENU_DISP_COLMAX );
1078
1079   if ( ui_selected ) {
1080
1081     // what row we in?
1082     unsigned int row = ui_determine_row ( ui_selected );
1083
1084     // max rows?
1085     unsigned int icon_rows = g_categories [ ui_category ].refcount / col_max;
1086     if ( g_categories [ ui_category ].refcount % col_max > 0 ) {
1087       icon_rows++;
1088     }
1089
1090     // we at the end?
1091     if ( row == ( icon_rows - 1 ) &&
1092          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 1 ) == 0 )
1093     {
1094
1095       unsigned char col = ui_determine_screen_col ( ui_selected );
1096
1097       ui_selected = g_categories [ ui_category ].refs;
1098
1099       while ( col ) {
1100         ui_selected = ui_selected -> next;
1101         col--;
1102       }
1103
1104       ui_rows_scrolled_down = 0;
1105
1106     } else {
1107
1108       while ( col_max ) {
1109         ui_push_right ( 1 );
1110         col_max--;
1111       }
1112
1113     }
1114
1115   } else {
1116     ui_push_right ( 0 );
1117   }
1118
1119   return;
1120 }
1121
1122 void ui_push_exec ( void ) {
1123
1124   if ( ui_selected ) {
1125     char buffer [ PATH_MAX ];
1126     sprintf ( buffer, "%s/%s", ui_selected -> ref -> object_path, ui_selected -> ref -> object_filename );
1127     pnd_apps_exec ( pnd_run_script,
1128                     buffer,
1129                     ui_selected -> ref -> unique_id,
1130                     ui_selected -> ref -> exec,
1131                     ui_selected -> ref -> startdir,
1132                     ui_selected -> ref -> execargs,
1133                     atoi ( ui_selected -> ref -> clockspeed ),
1134                     PND_EXEC_OPTION_NORUN );
1135     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1136     emit_and_quit ( buffer );
1137   }
1138
1139   return;
1140 }
1141
1142 void ui_push_ltrigger ( void ) {
1143   unsigned char oldcat = ui_category;
1144
1145   if ( ui_category > 0 ) {
1146     ui_category--;
1147   } else {
1148     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1149       ui_category = g_categorycount - 1;
1150     }
1151   }
1152
1153   if ( oldcat != ui_category ) {
1154     ui_selected = NULL;
1155     ui_set_selected ( ui_selected );
1156   }
1157
1158   // make tab visible?
1159   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
1160     ui_catshift--;
1161   }
1162
1163   // unscroll
1164   ui_rows_scrolled_down = 0;
1165
1166   return;
1167 }
1168
1169 void ui_push_rtrigger ( void ) {
1170   unsigned char oldcat = ui_category;
1171
1172   unsigned int screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
1173   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1174
1175   if ( ui_category < ( g_categorycount - 1 ) ) {
1176     ui_category++;
1177   } else {
1178     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1179       ui_category = 0;
1180     }
1181   }
1182
1183   if ( oldcat != ui_category ) {
1184     ui_selected = NULL;
1185     ui_set_selected ( ui_selected );
1186   }
1187
1188   // make tab visible?
1189   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
1190     ui_catshift++;
1191   }
1192
1193   // unscroll
1194   ui_rows_scrolled_down = 0;
1195
1196   return;
1197 }
1198
1199 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
1200   double scale = 1000000.0;
1201   double scalex = 1000000.0;
1202   double scaley = 1000000.0;
1203   SDL_Surface *scaled;
1204
1205   scalex = (double)maxwidth / (double)s -> w;
1206
1207   if ( maxheight == -1 ) {
1208     scale = scalex;
1209   } else {
1210     scaley = (double)maxheight / (double)s -> h;
1211
1212     if ( scaley < scalex ) {
1213       scale = scaley;
1214     } else {
1215       scale = scalex;
1216     }
1217
1218   }
1219
1220   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
1221   SDL_FreeSurface ( s );
1222   s = scaled;
1223
1224   return ( s );
1225 }
1226
1227 void ui_loadscreen ( void ) {
1228
1229   SDL_Rect dest;
1230
1231   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1232   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1233   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1234   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1235
1236   // clear the screen
1237   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1238
1239   // render text
1240   SDL_Surface *rtext;
1241   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1242   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", tmpfontcolor );
1243   dest.x = 20;
1244   dest.y = 20;
1245   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1246   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1247   SDL_FreeSurface ( rtext );
1248
1249   return;
1250 }
1251
1252 void ui_discoverscreen ( unsigned char clearscreen ) {
1253
1254   SDL_Rect dest;
1255
1256   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1257   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1258   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1259   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1260
1261   // clear the screen
1262   if ( clearscreen ) {
1263     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1264
1265     // render background
1266     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1267       dest.x = 0;
1268       dest.y = 0;
1269       dest.w = sdl_realscreen -> w;
1270       dest.h = sdl_realscreen -> h;
1271       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1272       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1273     }
1274
1275   }
1276
1277   // render text
1278   SDL_Surface *rtext;
1279   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1280   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", tmpfontcolor );
1281   if ( clearscreen ) {
1282     dest.x = 20;
1283     dest.y = 20;
1284   } else {
1285     dest.x = 20;
1286     dest.y = 40;
1287   }
1288   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1289   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1290   SDL_FreeSurface ( rtext );
1291
1292   // render icon
1293   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1294     dest.x = rtext -> w + 30;
1295     dest.y = 20;
1296     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
1297     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1298   }
1299
1300   return;
1301 }
1302
1303 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
1304
1305   SDL_Rect rects [ 4 ];
1306   SDL_Rect *dest = rects;
1307   bzero ( dest, sizeof(SDL_Rect)* 4 );
1308
1309   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1310   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1311   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1312   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1313
1314   static unsigned int stepx = 0;
1315
1316   // clear the screen
1317   if ( clearscreen ) {
1318     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1319
1320     // render background
1321     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1322       dest -> x = 0;
1323       dest -> y = 0;
1324       dest -> w = sdl_realscreen -> w;
1325       dest -> h = sdl_realscreen -> h;
1326       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1327       dest++;
1328     }
1329
1330   }
1331
1332   // render text
1333   SDL_Surface *rtext;
1334   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1335   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
1336   if ( clearscreen ) {
1337     dest -> x = 20;
1338     dest -> y = 20;
1339   } else {
1340     dest -> x = 20;
1341     dest -> y = 40;
1342   }
1343   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1344   SDL_FreeSurface ( rtext );
1345   dest++;
1346
1347   // render icon
1348   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1349     dest -> x = rtext -> w + 30 + stepx;
1350     dest -> y = 20;
1351     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
1352     dest++;
1353   }
1354
1355   // filename
1356   if ( filename ) {
1357     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
1358     dest -> x = 20;
1359     dest -> y = 50;
1360     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1361     SDL_FreeSurface ( rtext );
1362     dest++;
1363   }
1364
1365   // move across
1366   stepx += 20;
1367
1368   if ( stepx > 350 ) {
1369     stepx = 0;
1370   }
1371
1372   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1373
1374   return;
1375 }
1376
1377 int ui_selected_index ( void ) {
1378
1379   if ( ! ui_selected ) {
1380     return ( -1 ); // no index
1381   }
1382
1383   mm_appref_t *r = g_categories [ ui_category ].refs;
1384   int counter = 0;
1385   while ( r ) {
1386     if ( r == ui_selected ) {
1387       return ( counter );
1388     }
1389     r = r -> next;
1390     counter++;
1391   }
1392
1393   return ( -1 );
1394 }
1395
1396 static mm_appref_t *timer_ref = NULL;
1397 void ui_set_selected ( mm_appref_t *r ) {
1398
1399   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
1400     return; // no desire to defer anything
1401   }
1402
1403   if ( ! r ) {
1404     // cancel timer
1405     SDL_SetTimer ( 0, NULL );
1406     timer_ref = NULL;
1407     return;
1408   }
1409
1410   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
1411   timer_ref = r;
1412
1413   return;
1414 }
1415
1416 unsigned int ui_callback_f ( unsigned int t ) {
1417
1418   if ( ui_selected != timer_ref ) {
1419     return ( 0 ); // user has moved it, who cares
1420   }
1421
1422   SDL_Event e;
1423   e.type = SDL_USEREVENT;
1424   SDL_PushEvent ( &e );
1425
1426   return ( 0 );
1427 }
1428
1429 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
1430   SDL_Rect rects [ 40 ];
1431   SDL_Rect *dest = rects;
1432   SDL_Rect src;
1433   SDL_Surface *rtext;
1434
1435   bzero ( rects, sizeof(SDL_Rect) * 40 );
1436
1437   unsigned int sel = 0;
1438
1439   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1440   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1441   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1442   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1443
1444   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1445
1446   SDL_Color selfontcolor = { 0/*font_rgba_r*/, font_rgba_g, font_rgba_b, font_rgba_a };
1447
1448   unsigned int i;
1449   SDL_Event event;
1450
1451   while ( 1 ) {
1452
1453     // clear
1454     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1455     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1456     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
1457     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
1458     SDL_FillRect( sdl_realscreen, dest, 0 );
1459
1460     // show dialog background
1461     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
1462       src.x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1463       src.y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1464       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
1465       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
1466       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1467       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1468       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
1469       // repeat for darken?
1470       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
1471       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
1472       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1473       dest++;
1474     }
1475
1476     // show dialog frame
1477     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
1478       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
1479       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
1480       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
1481       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1482       dest++;
1483     }
1484
1485     // show header
1486     if ( title ) {
1487       rtext = TTF_RenderText_Blended ( g_tab_font, title, tmpfontcolor );
1488       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
1489       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
1490       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1491       SDL_FreeSurface ( rtext );
1492       dest++;
1493     }
1494
1495     // show footer
1496     if ( footer ) {
1497       rtext = TTF_RenderText_Blended ( g_tab_font, footer, tmpfontcolor );
1498       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
1499       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
1500         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
1501         - 60;
1502       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1503       SDL_FreeSurface ( rtext );
1504       dest++;
1505     }
1506
1507     // show options
1508     for ( i = 0; i < argc; i++ ) {
1509
1510       // show options
1511       if ( sel == i ) {
1512         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
1513       } else {
1514         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], tmpfontcolor );
1515       }
1516       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
1517       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 ) );
1518       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1519       SDL_FreeSurface ( rtext );
1520       dest++;
1521
1522     } // for
1523
1524     // update all the rects and send it all to sdl
1525     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1526     dest = rects;
1527
1528     // check for input
1529     while ( SDL_WaitEvent ( &event ) ) {
1530
1531       switch ( event.type ) {
1532
1533       case SDL_KEYUP:
1534
1535         if ( event.key.keysym.sym == SDLK_UP ) {
1536           if ( sel ) {
1537             sel--;
1538           }
1539         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
1540           if ( sel < argc - 1 ) {
1541             sel++;
1542           }
1543
1544         } else if ( event.key.keysym.sym == SDLK_RETURN ) {
1545           return ( sel );
1546
1547         } else if ( event.key.keysym.sym == SDLK_q ) {
1548           exit ( 0 );
1549
1550         } else {
1551           return ( -1 ); // nada
1552         }
1553
1554         break;
1555
1556       } // switch
1557
1558       break;
1559     } // while
1560
1561   } // while
1562
1563   return ( -1 );
1564 }
1565
1566 unsigned char ui_determine_row ( mm_appref_t *a ) {
1567   unsigned int row = 0;
1568
1569   mm_appref_t *i = g_categories [ ui_category ].refs;
1570   while ( i != a ) {
1571     i = i -> next;
1572     row++;
1573   } // while
1574   row /= pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
1575
1576   return ( row );
1577 }
1578
1579 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
1580   return ( ui_determine_row ( a ) % pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 5 ) );
1581 }
1582
1583 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
1584   unsigned int col = 0;
1585
1586   mm_appref_t *i = g_categories [ ui_category ].refs;
1587   while ( i != a ) {
1588     i = i -> next;
1589     col++;
1590   } // while
1591   col %= pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
1592
1593   return ( col );
1594 }