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