pndnotifyd: fix some crashes
[pandora-libraries.git] / minimenu / mmui.c
1
2 #define __USE_GNU /* for strcasestr */
3 #include <stdio.h> /* for FILE etc */
4 #include <stdlib.h> /* for malloc */
5 #include <unistd.h> /* for unlink */
6 #include <limits.h> /* for PATH_MAX */
7 #include <sys/types.h>
8 #include <sys/stat.h>
9 #include <string.h> /* for making ftw.h happy */
10 #include <time.h>
11 #include <ftw.h>
12 #include <ctype.h>
13
14 #include "SDL.h"
15 #include "SDL_audio.h"
16 #include "SDL_image.h"
17 #include "SDL_ttf.h"
18 #include "SDL_gfxPrimitives.h"
19 #include "SDL_rotozoom.h"
20 #include "SDL_thread.h"
21 #include <dirent.h>
22
23 #include "pnd_conf.h"
24 #include "pnd_logger.h"
25 #include "pnd_pxml.h"
26 #include "pnd_container.h"
27 #include "pnd_discovery.h"
28 #include "pnd_apps.h"
29 #include "pnd_device.h"
30 #include "../lib/pnd_pathiter.h"
31 #include "pnd_utility.h"
32 #include "pnd_pndfiles.h"
33 #include "pnd_notify.h"
34 #include "pnd_dbusnotify.h"
35
36 #include "mmenu.h"
37 #include "mmcat.h"
38 #include "mmcache.h"
39 #include "mmui.h"
40 #include "mmwrapcmd.h"
41 #include "mmconf.h"
42 #include "mmui_context.h"
43 #include "freedesktop_cats.h"
44 #include "mmcustom_cats.h"
45
46 #define CHANGED_NOTHING     (0)
47 #define CHANGED_CATEGORY    (1<<0)  /* changed to different category */
48 #define CHANGED_SELECTION   (1<<1)  /* changed app selection */
49 #define CHANGED_DATAUPDATE  (1<<2)  /* deferred preview pic or icon came in */
50 #define CHANGED_APPRELOAD   (1<<3)  /* new set of applications entirely */
51 #define CHANGED_EVERYTHING  (1<<4)  /* redraw it all! */
52 unsigned int render_mask = CHANGED_EVERYTHING;
53
54 SDL_Thread *g_icon_thread = NULL;
55 unsigned char g_icon_thread_stop = 0; /* if !0 means thread should stop and maim app may block until it goes back to 0.. */
56 unsigned char g_icon_thread_busy = 0; /* if !0 means thread is running right now */
57
58 #define MIMETYPE_EXE "/usr/bin/file"     /* check for file type prior to invocation */
59
60 /* SDL
61  */
62 SDL_Surface *sdl_realscreen = NULL;
63 unsigned int sdl_ticks = 0;
64 SDL_Thread *g_preview_thread = NULL;
65 SDL_Thread *g_timer_thread = NULL;
66
67 enum { sdl_user_ticker = 0,
68        sdl_user_finishedpreview = 1,
69        sdl_user_finishedicon = 2,
70        sdl_user_checksd = 3,
71 };
72
73 /* app state
74  */
75 unsigned short int g_scale = 1; // 1 == noscale
76
77 SDL_Surface *g_imgcache [ IMG_MAX ];
78
79 TTF_Font *g_big_font = NULL;
80 TTF_Font *g_grid_font = NULL;
81 TTF_Font *g_detailtext_font = NULL;
82 TTF_Font *g_tab_font = NULL;
83
84 extern pnd_conf_handle g_conf;
85
86 /* current display state
87  */
88 int ui_rows_scrolled_down = 0;          // number of rows that should be missing from top of the display
89 mm_appref_t *ui_selected = NULL;
90 unsigned char ui_category = 0;          // current category
91 unsigned char ui_catshift = 0;          // how many cats are offscreen to the left
92 ui_viewmode_e ui_viewmode = uiv_icons;  // default to traditional icon view; why or why is viewstate not per-viewmode :/
93 ui_context_t ui_display_context;        // display paramaters: see mmui_context.h
94 unsigned char ui_detail_hidden = 0;     // if >0, detail panel is hidden
95 // FUTURE: If multiple panels can be shown/hidden, convert ui_detail_hidden to a bitmask
96
97 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ); // height -1 means ignore
98
99 static int ui_selected_index ( void );
100 static void ui_start_defered_icon_thread ( void );
101 static void ui_stop_defered_icon_thread ( void );
102
103 unsigned char ui_setup ( void ) {
104
105   /* set up SDL
106    */
107
108   SDL_Init ( SDL_INIT_EVERYTHING | SDL_INIT_NOPARACHUTE );
109
110   SDL_JoystickOpen ( 0 ); // turn on joy-0
111
112   SDL_WM_SetCaption ( "mmenu", "mmenu" );
113
114   // hide the mouse cursor if we can
115   if ( SDL_ShowCursor ( -1 ) == 1 ) {
116     SDL_ShowCursor ( 0 );
117   }
118
119   atexit ( SDL_Quit );
120
121   // open up a surface
122   unsigned int svm = SDL_SWSURFACE /*| SDL_FULLSCREEN*/ /* 0*/;
123   if ( pnd_conf_get_as_int_d ( g_conf, "display.fullscreen", 0 ) > 0 ) {
124     svm |= SDL_FULLSCREEN;
125   }
126
127   sdl_realscreen =
128     SDL_SetVideoMode ( 800 * g_scale, 480 * g_scale, 16 /*bpp*/, svm );
129
130   if ( ! sdl_realscreen ) {
131     pnd_log ( pndn_error, "ERROR: Couldn't open SDL real screen; dieing." );
132     return ( 0 );
133   }
134
135   pnd_log ( pndn_debug, "Pixel format:" );
136   pnd_log ( pndn_debug, "bpp b: %u\n", sdl_realscreen -> format -> BitsPerPixel );
137   pnd_log ( pndn_debug, "bpp B: %u\n", sdl_realscreen -> format -> BytesPerPixel );
138   pnd_log ( pndn_debug, "R mask: %08x\n", sdl_realscreen -> format -> Rmask );
139   pnd_log ( pndn_debug, "G mask: %08x\n", sdl_realscreen -> format -> Gmask );
140   pnd_log ( pndn_debug, "B mask: %08x\n", sdl_realscreen -> format -> Bmask );
141
142 #if 0 // audio
143   {
144     SDL_AudioSpec fmt;
145
146     /* Set 16-bit stereo audio at 22Khz */
147     fmt.freq = 44100; //22050;
148     fmt.format = AUDIO_S16; //AUDIO_S16;
149     fmt.channels = 1;
150     fmt.samples = 2048;        /* A good value for games */
151     fmt.callback = mixaudio;
152     fmt.userdata = NULL;
153
154     /* Open the audio device and start playing sound! */
155     if ( SDL_OpenAudio ( &fmt, NULL ) < 0 ) {
156       zotlog ( "Unable to open audio: %s\n", SDL_GetError() );
157       exit ( 1 );
158     }
159
160     SDL_PauseAudio ( 0 );
161   }
162 #endif
163
164   // key repeat
165   SDL_EnableKeyRepeat ( 500, 125 /* 150 */ );
166
167   // images
168   //IMG_Init ( IMG_INIT_JPG | IMG_INIT_PNG );
169
170   /* fonts
171    */
172
173   // init
174   if ( TTF_Init() == -1 ) {
175     pnd_log ( pndn_error, "ERROR: Couldn't set up SDL TTF lib\n" );
176     return ( 0 ); // couldn't set up SDL TTF
177   }
178
179   char fullpath [ PATH_MAX ];
180   // big font
181   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "minimenu.font" ) );
182   g_big_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
183   if ( ! g_big_font ) {
184     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
185               pnd_conf_get_as_char ( g_conf, "minimenu.font" ), pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
186     return ( 0 ); // couldn't set up SDL TTF
187   }
188
189   // grid font
190   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "grid.font" ) );
191   g_grid_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
192   if ( ! g_grid_font ) {
193     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
194               pnd_conf_get_as_char ( g_conf, "grid.font" ), pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
195     return ( 0 ); // couldn't set up SDL TTF
196   }
197
198   // detailtext font
199   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "detailtext.font" ) );
200   g_detailtext_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
201   if ( ! g_detailtext_font ) {
202     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
203               pnd_conf_get_as_char ( g_conf, "detailtext.font" ), pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
204     return ( 0 ); // couldn't set up SDL TTF
205   }
206
207   // tab font
208   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "tabs.font" ) );
209   g_tab_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
210   if ( ! g_tab_font ) {
211     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
212               pnd_conf_get_as_char ( g_conf, "tabs.font" ), pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
213     return ( 0 ); // couldn't set up SDL TTF
214   }
215
216   // determine display context
217   if ( pnd_conf_get_as_int_d ( g_conf, "display.show_detail_pane", 1 ) > 0 ) {
218     ui_detail_hidden = 0;
219   } else {
220     ui_detail_hidden = 1;
221   }
222
223   // determine default viewmode
224   if ( pnd_conf_get_as_int_d ( g_conf, "display.viewmode_list", -1 ) != -1 ) {
225     int i = pnd_conf_get_as_int_d ( g_conf, "display.viewmode_list", 0 );
226     if ( i >= uiv_max ) {
227       ui_viewmode = uiv_icons;
228     } else {
229       ui_viewmode = i;
230     }
231   }
232
233   // update display context
234   ui_recache_context ( &ui_display_context );
235
236   return ( 1 );
237 }
238
239 mm_imgcache_t g_imagecache [ IMG_TRUEMAX ] = {
240   { IMG_BACKGROUND_800480,    "graphics.IMG_BACKGROUND_800480" },
241   { IMG_BACKGROUND_TABMASK,   "graphics.IMG_BACKGROUND_TABMASK" },
242   { IMG_DETAIL_PANEL,         "graphics.IMG_DETAIL_PANEL" },
243   { IMG_DETAIL_BG,            "graphics.IMG_DETAIL_BG" },
244   { IMG_SELECTED_ALPHAMASK,   "graphics.IMG_SELECTED_ALPHAMASK" },
245   { IMG_TAB_SEL,              "graphics.IMG_TAB_SEL" },
246   { IMG_TAB_SEL_L,            "graphics.IMG_TAB_SEL_L" },
247   { IMG_TAB_SEL_R,            "graphics.IMG_TAB_SEL_R" },
248   { IMG_TAB_UNSEL,            "graphics.IMG_TAB_UNSEL" },
249   { IMG_TAB_UNSEL_L,          "graphics.IMG_TAB_UNSEL_L" },
250   { IMG_TAB_UNSEL_R,          "graphics.IMG_TAB_UNSEL_R" },
251   { IMG_TAB_LINE,             "graphics.IMG_TAB_LINE" },
252   { IMG_TAB_LINEL,            "graphics.IMG_TAB_LINEL" },
253   { IMG_TAB_LINER,            "graphics.IMG_TAB_LINER" },
254   { IMG_ICON_MISSING,         "graphics.IMG_ICON_MISSING" },
255   { IMG_SELECTED_HILITE,      "graphics.IMG_SELECTED_HILITE" },
256   { IMG_PREVIEW_MISSING,      "graphics.IMG_PREVIEW_MISSING" },
257   { IMG_ARROW_UP,             "graphics.IMG_ARROW_UP", },
258   { IMG_ARROW_DOWN,           "graphics.IMG_ARROW_DOWN", },
259   { IMG_ARROW_SCROLLBAR,      "graphics.IMG_ARROW_SCROLLBAR", },
260   { IMG_HOURGLASS,            "graphics.IMG_HOURGLASS", },
261   { IMG_FOLDER,               "graphics.IMG_FOLDER", },
262   { IMG_EXECBIN,              "graphics.IMG_EXECBIN", },
263   { IMG_SUBCATFOLDER,         "graphics.IMG_SUBCATFOLDER", "graphics.IMG_FOLDER", },
264   { IMG_DOTDOTFOLDER,         "graphics.IMG_DOTDOTFOLDER", "graphics.IMG_FOLDER", },
265   { IMG_MAX,                  NULL },
266   { IMG_LIST_ALPHAMASK,       NULL }, // generated
267   { IMG_LIST_ALPHAMASK_W,     NULL }, // generated
268 };
269
270 unsigned char ui_imagecache ( char *basepath ) {
271   unsigned int i;
272   char fullpath [ PATH_MAX ];
273   unsigned char try;
274
275   // loaded
276
277   for ( i = 0; i < IMG_MAX; i++ ) {
278
279     if ( g_imagecache [ i ].id != i ) {
280       pnd_log ( pndn_error, "ERROR: Internal table mismatch during caching [%u]\n", i );
281       exit ( -1 );
282     }
283
284     for ( try = 0; try < 2; try++ ) {
285
286       char *filename;
287
288       if ( try == 0 ) {
289         filename = pnd_conf_get_as_char ( g_conf, g_imagecache [ i ].confname );
290       } else {
291         if ( g_imagecache [ i ].alt_confname ) {
292           filename = pnd_conf_get_as_char ( g_conf, g_imagecache [ i ].alt_confname );
293         } else {
294           return ( 0 );
295         }
296       }
297
298       if ( ! filename ) {
299         pnd_log ( pndn_error, "ERROR: (Try %u) Missing filename in conf for key: %s\n", try + 1, g_imagecache [ i ].confname );
300         if ( try == 0 ) { continue; } else { return ( 0 ); }
301       }
302
303       if ( filename [ 0 ] == '/' ) {
304         strncpy ( fullpath, filename, PATH_MAX );
305       } else {
306         sprintf ( fullpath, "%s/%s", basepath, filename );
307       }
308
309       if ( ( g_imagecache [ i ].i = IMG_Load ( fullpath ) ) ) {
310
311         // also make a 'tiny' version..
312         SDL_Surface *s = g_imagecache [ i ].i;
313         SDL_Surface *scaled_tiny = SDL_ConvertSurface ( s, s -> format, s -> flags ); // duplicate
314         extern ui_context_t ui_display_context;
315         scaled_tiny = ui_scale_image ( scaled_tiny, -1 , ui_display_context.text_height_tab ); // resize
316         g_imagecache [ i ].itiny = scaled_tiny;
317
318         break; // no retry needed
319       } else {
320         pnd_log ( pndn_error, "ERROR: (Try %u) Couldn't load static cache image: %s\n", try + 1, fullpath );
321         if ( try == 0 ) { continue; } else { return ( 0 ); }
322       }
323
324     } // try twice
325
326   } // for
327
328   // generated
329   //
330
331   //g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = SDL_CreateRGBSurface ( SDL_SWSURFACE, 60, 60, 32, 0xFF0000, 0x00FF00, 0xFF, 0xFF000000 );
332   //boxRGBA ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, 0, 0, 60, 60, 100, 100, 100, 250 );
333
334   SDL_Surface *p = g_imagecache [ IMG_SELECTED_ALPHAMASK ].i;
335   g_imagecache [ IMG_LIST_ALPHAMASK ].i = SDL_ConvertSurface ( p, p -> format, p -> flags );
336   g_imagecache [ IMG_LIST_ALPHAMASK_W ].i = SDL_ConvertSurface ( p, p -> format, p -> flags );
337
338   g_imagecache [ IMG_LIST_ALPHAMASK ].i =
339     ui_scale_image ( g_imagecache [ IMG_LIST_ALPHAMASK ].i,
340                      pnd_conf_get_as_int ( g_conf, "grid.col_max" ) * pnd_conf_get_as_int ( g_conf, "grid.cell_width" ) , -1 );
341
342   g_imagecache [ IMG_LIST_ALPHAMASK_W ].i =
343     ui_scale_image ( g_imagecache [ IMG_LIST_ALPHAMASK_W ].i,
344                      pnd_conf_get_as_int ( g_conf, "grid.col_max_w" ) * pnd_conf_get_as_int ( g_conf, "grid.cell_width_w" ) , -1 );
345
346   // post processing
347   //
348
349   // scale icons
350   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 );
351   // scale text hilight
352   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 );
353   // scale preview no-pic
354   g_imagecache [ IMG_PREVIEW_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_PREVIEW_MISSING ].i,
355                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ),
356                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 50 ) );
357
358   // set alpha on detail panel
359   //SDL_SetAlpha ( g_imagecache [ IMG_DETAIL_BG ].i, SDL_SRCALPHA, pnd_conf_get_as_int_d ( g_conf, "display.detail_bg_alpha", 50 ) );
360
361   return ( 1 );
362 } // ui_imagecache
363
364 void ui_render ( void ) {
365
366   // 800x480:
367   // divide width: 550 / 250
368   // divide left side: 5 columns == 110px width
369   //   20px buffer either side == 70px wide icon + 20 + 20?
370
371   // what jobs to do during render?
372   //
373
374 #define R_BG     (1<<0)
375 #define R_TABS   (1<<1)
376 #define R_DETAIL (1<<2)
377 #define R_GRID   (1<<3)
378
379 #define R_ALL (R_BG|R_TABS|R_DETAIL|R_GRID)
380
381   unsigned int render_jobs_b = 0;
382
383   if ( render_mask & CHANGED_EVERYTHING ) {
384     render_jobs_b |= R_ALL;
385   }
386
387   if ( render_mask & CHANGED_SELECTION ) {
388     render_jobs_b |= R_GRID;
389     render_jobs_b |= R_DETAIL;
390   }
391
392   if ( render_mask & CHANGED_CATEGORY ) {
393     render_jobs_b |= R_ALL;
394   }
395
396   render_mask = CHANGED_NOTHING;
397
398   // render everything
399   //
400   unsigned int icon_rows;
401   unsigned int icon_visible_rows = 0; // list view, max number of visible rows
402
403 #define MAXRECTS 200
404   SDL_Rect rects [ MAXRECTS ], src;
405   SDL_Rect *dest = rects;
406   bzero ( dest, sizeof(SDL_Rect)*MAXRECTS );
407
408   unsigned int row, displayrow, col;
409   mm_appref_t *appiter;
410
411   ui_context_t *c = &ui_display_context; // for convenience and shorthand
412
413   // on demand icon loading
414   static int load_visible = -1;
415   if ( load_visible == -1 ) {
416     load_visible = pnd_conf_get_as_int_d ( g_conf, "minimenu.load_visible_icons", 0 );
417   }
418
419 #if 1
420   // if no selected app yet, select the first one
421   if ( ! ui_selected && pnd_conf_get_as_int_d ( g_conf, "minimenu.start_selected", 0 ) ) {
422
423     // pick first visible app
424     ui_selected = g_categories [ ui_category ] -> refs;
425
426     // change.. so we pick first visible if option is set .. but now we also try to restore
427     // selection to the last app selected in previous session (if there is one.)
428     char *previous_unique_id = pnd_conf_get_as_char ( g_conf, "minimenu.last_known_app_uid" );
429
430     if ( previous_unique_id ) {
431
432       // 1) we should already be in the right category, since its set in ui_post_scan to minimenu.last_known_catname
433       // 2) so we just pick the app in question..
434       mm_appref_t *previter = g_categories [ ui_category ] -> refs;
435       while ( previter ) {
436         if ( strcmp ( previter -> ref -> unique_id, previous_unique_id ) == 0 ) {
437           break;
438         }
439         previter = previter -> next;
440       }
441
442       if ( previter ) {
443         ui_selected = previter;
444       }
445
446     } // last known app?
447
448   }
449 #endif
450
451   // how many total rows do we need?
452   if ( g_categorycount ) {
453
454     if ( ui_viewmode == uiv_list ) {
455       // in list view, dimension of grid area is ..
456       // grid height == cell-height * row-max
457       // font height == display_context -> text_height
458       // padding == icon_offset_y
459       // max visible --> row-max == grid height / ( font-height + padding )
460
461       icon_rows = g_categories [ ui_category ] -> refcount; // one app per row
462       icon_visible_rows = ( c -> cell_height * c -> row_max ) / ( c -> text_height_tab + c -> icon_offset_y );
463
464     } else {
465
466       icon_rows = g_categories [ ui_category ] -> refcount / c -> col_max;
467       if ( g_categories [ ui_category ] -> refcount % c -> col_max > 0 ) {
468         icon_rows++;
469       }
470
471     }
472
473   } else {
474     icon_rows = 0;
475     icon_visible_rows = 0;
476   }
477
478   // reset touchscreen regions
479   if ( render_jobs_b ) {
480     ui_register_reset();
481   }
482
483   // ensure selection is visible
484   if ( ui_selected ) {
485
486     unsigned char autoscrolled = 1;
487     while ( autoscrolled ) {
488       autoscrolled = 0;
489
490       int index = ui_selected_index();
491
492       if ( ui_viewmode == uiv_list ) {
493
494         if ( index >= ui_rows_scrolled_down + icon_visible_rows ) {
495           ui_rows_scrolled_down += 1;
496           autoscrolled = 1;
497           render_jobs_b |= R_ALL;
498         } else if ( index < ui_rows_scrolled_down ) {
499           ui_rows_scrolled_down -= 1;
500           autoscrolled = 1;
501           render_jobs_b |= R_ALL;
502         }
503
504       } else {
505         // icons
506
507         int topleft = c -> col_max * ui_rows_scrolled_down;
508         int botright = ( c -> col_max * ( ui_rows_scrolled_down + c -> row_max ) - 1 );
509
510         if ( index < topleft ) {
511           ui_rows_scrolled_down -= pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
512           render_jobs_b |= R_ALL;
513           autoscrolled = 1;
514         } else if ( index > botright ) {
515           ui_rows_scrolled_down += pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
516           render_jobs_b |= R_ALL;
517           autoscrolled = 1;
518         }
519
520       } // viewmode
521
522     } // while autoscrolling
523
524     if ( ui_rows_scrolled_down < 0 ) {
525       ui_rows_scrolled_down = 0;
526     } else if ( ui_rows_scrolled_down > icon_rows ) {
527       ui_rows_scrolled_down = icon_rows;
528     }
529
530   } // ensure visible
531
532   // render background
533   if ( render_jobs_b & R_BG ) {
534
535     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
536       dest -> x = 0;
537       dest -> y = 0;
538       dest -> w = sdl_realscreen -> w;
539       dest -> h = sdl_realscreen -> h;
540       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
541       dest++;
542     }
543
544     // tabmask
545     if ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i ) {
546       dest -> x = 0;
547       dest -> y = 0;
548       dest -> w = sdl_realscreen -> w;
549       dest -> h = sdl_realscreen -> h;
550       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
551       dest++;
552     }
553
554   } // r_bg
555
556   // tabs
557   if ( g_imagecache [ IMG_TAB_SEL ].i && g_imagecache [ IMG_TAB_UNSEL ].i ) {
558     static unsigned int tab_width;
559     static unsigned int tab_height;
560     static unsigned int tab_offset_x;
561     static unsigned int tab_offset_y;
562     static unsigned int text_offset_x;
563     static unsigned int text_offset_y;
564     static unsigned int text_width;
565
566     static unsigned char tab_first_run = 1;
567     if ( tab_first_run ) {
568       tab_first_run = 0;
569       tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
570       tab_height = pnd_conf_get_as_int ( g_conf, "tabs.tab_height" );
571       tab_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_x" );
572       tab_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_y" );
573       text_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_x" );
574       text_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_y" );
575       text_width = pnd_conf_get_as_int ( g_conf, "tabs.text_width" );
576     }
577
578     unsigned int maxtab = ( c -> screen_width / tab_width ) < g_categorycount ? ( c -> screen_width / tab_width ) + ui_catshift : g_categorycount + ui_catshift;
579     unsigned int maxtabspot = ( c -> screen_width / tab_width );
580
581     if ( g_categorycount > 0 ) {
582
583       // draw tabs with categories
584       for ( col = ui_catshift;
585             col < maxtab;
586             col++ )
587       {
588
589         SDL_Surface *s;
590
591         // if this is the first (leftmost) tab, we use different artwork
592         // than if the other tabs (so skinner can link lines up nicely.)
593         if ( col == ui_catshift ) {
594           // leftmost tab, special case
595
596           if ( col == ui_category ) {
597             s = g_imagecache [ IMG_TAB_SEL_L ].i;
598           } else {
599             s = g_imagecache [ IMG_TAB_UNSEL_L ].i;
600           }
601
602         } else if ( col == maxtab - 1 ) {
603           // rightmost tab, special case
604
605           if ( col == ui_category ) {
606             s = g_imagecache [ IMG_TAB_SEL_R ].i;
607           } else {
608             s = g_imagecache [ IMG_TAB_UNSEL_R ].i;
609           }
610
611         } else {
612           // normal (not leftmost) tab
613
614           if ( col == ui_category ) {
615             s = g_imagecache [ IMG_TAB_SEL ].i;
616           } else {
617             s = g_imagecache [ IMG_TAB_UNSEL ].i;
618           }
619
620         } // first col, or not first col?
621
622         // draw tab
623         src.x = 0;
624         src.y = 0;
625 #if 0
626         src.w = tab_width;
627         if ( col == ui_category ) {
628           src.h = tab_selheight;
629         } else {
630           src.h = tab_height;
631         }
632 #else
633         src.w = s -> w;
634         src.h = s -> h;
635 #endif
636         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
637         dest -> y = tab_offset_y;
638
639         // store touch info
640         ui_register_tab ( col, dest -> x, dest -> y, tab_width, tab_height );
641
642         if ( render_jobs_b & R_TABS ) {
643           //pnd_log ( pndn_debug, "tab %u at %ux%u\n", col, dest.x, dest.y );
644           SDL_BlitSurface ( s, &src, sdl_realscreen, dest );
645           dest++;
646
647           // draw tab line
648           if ( col == ui_category ) {
649             // no line for selected tab
650           } else {
651             if ( col - ui_catshift == 0 ) {
652               s = g_imagecache [ IMG_TAB_LINEL ].i;
653             } else if ( col - ui_catshift == maxtabspot - 1 ) {
654               s = g_imagecache [ IMG_TAB_LINER ].i;
655             } else {
656               s = g_imagecache [ IMG_TAB_LINE ].i;
657             }
658             dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
659             dest -> y = tab_offset_y + tab_height;
660             SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
661             dest++;
662           }
663
664           // draw text
665           SDL_Surface *rtext;
666           rtext = TTF_RenderText_Blended ( g_tab_font, g_categories [ col ] -> catname, c -> fontcolor );
667           src.x = 0;
668           src.y = 0;
669           src.w = rtext -> w < text_width ? rtext -> w : text_width;
670           src.h = rtext -> h;
671           dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width ) + text_offset_x;
672           dest -> y = tab_offset_y + text_offset_y;
673           SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
674           SDL_FreeSurface ( rtext );
675           dest++;
676
677         } // r_tabs
678
679       } // for
680
681     } // if we got categories
682
683     if ( render_jobs_b & R_TABS ) {
684
685       // draw tab lines under where tabs would be if we had categories
686       for ( /* foo */; col < maxtabspot; col++ ) {
687         SDL_Surface *s;
688
689         if ( col - ui_catshift == 0 ) {
690           s = g_imagecache [ IMG_TAB_LINEL ].i;
691         } else if ( col - ui_catshift == maxtabspot - 1 ) {
692           s = g_imagecache [ IMG_TAB_LINER ].i;
693         } else {
694           s = g_imagecache [ IMG_TAB_LINE ].i;
695         }
696         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
697         dest -> y = tab_offset_y + tab_height;
698         SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
699         dest++;
700
701       } // for
702
703     } // r_tabs
704
705   } // tabs
706
707   // scroll bars and arrows
708   if ( render_jobs_b & R_BG ) {
709     unsigned char show_bar = 0;
710
711     // up?
712     if ( ui_rows_scrolled_down && g_imagecache [ IMG_ARROW_UP ].i ) {
713       dest -> x = c -> arrow_up_x;
714       dest -> y = c -> arrow_up_y;
715       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_UP ].i, NULL /* whole image */, sdl_realscreen, dest );
716       dest++;
717
718       show_bar = 1;
719     }
720
721     // down?
722     if ( ui_rows_scrolled_down + c -> row_max < icon_rows && g_imagecache [ IMG_ARROW_DOWN ].i ) {
723       dest -> x = c -> arrow_down_x;
724       dest -> y = c -> arrow_down_y;
725       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_DOWN ].i, NULL /* whole image */, sdl_realscreen, dest );
726       dest++;
727
728       show_bar = 1;
729     }
730
731     if ( show_bar ) {
732       // show scrollbar as well
733       src.x = 0;
734       src.y = 0;
735       src.w = c -> arrow_bar_clip_w;
736       src.h = c -> arrow_bar_clip_h;
737       dest -> x = c -> arrow_bar_x;
738       dest -> y = c -> arrow_bar_y;
739       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_SCROLLBAR ].i, &src /* whole image */, sdl_realscreen, dest );
740       dest++;
741     } // bar
742
743   } // r_bg, scroll bars
744
745   // render detail pane bg
746   if ( render_jobs_b & R_DETAIL && ui_detail_hidden == 0 ) {
747
748     if ( pnd_conf_get_as_int_d ( g_conf, "detailpane.show", 1 ) ) {
749
750       // render detail bg
751       if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
752         src.x = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
753         src.y = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
754         src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
755         src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
756         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
757         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
758         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
759         dest++;
760       }
761
762       // render detail pane
763       if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
764         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
765         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
766         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
767         dest++;
768       }
769
770     } // detailpane frame/bg
771
772   } // r_details
773
774   // anything to render?
775   if ( render_jobs_b & R_GRID && g_categorycount ) {
776
777     // if just rendering grid, and nothing else, better clear it first
778     if ( ! ( render_jobs_b & R_BG ) ) {
779       if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
780         src.x = c -> grid_offset_x;
781         src.y = c -> grid_offset_y + c -> sel_icon_offset_y;
782         src.w = c -> col_max * c -> cell_width;
783         src.h = c -> row_max * c -> cell_height;
784
785         dest -> x = c -> grid_offset_x;
786         dest -> y = c -> grid_offset_y + c -> sel_icon_offset_y;
787
788         SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
789         dest++;
790
791       }
792     }
793
794     // any apps to render at all?
795     if ( g_categories [ ui_category ] -> refs ) {
796
797       // render grid differently based on view mode..
798       //
799       if ( ui_viewmode == uiv_list ) {
800
801         appiter = g_categories [ ui_category ] -> refs;
802         row = 0; // row under consideration (ie: could be scrolled off)
803         displayrow = 0; // rows displayed
804
805         while ( appiter != NULL ) {
806
807           // do we even need to render it? or are we suppressing it due to rows scrolled off the top?
808           if ( row >= ui_rows_scrolled_down ) {
809
810             // background hilight
811             SDL_Surface *hilight = ui_detail_hidden ? g_imagecache [ IMG_LIST_ALPHAMASK_W ].i : g_imagecache [ IMG_LIST_ALPHAMASK ].i;
812             if ( appiter == ui_selected ) {
813               src.x = 0;
814               src.y = 0;
815               src.w = hilight -> w;
816               src.h = c -> text_height_tab + c -> icon_offset_y;
817               dest -> x = c -> grid_offset_x + c -> text_clip_x;
818               dest -> y = c -> grid_offset_y + ( displayrow * ( c -> text_height_tab + c -> icon_offset_y ) ) - ( c -> icon_offset_y / 2 );
819               SDL_BlitSurface ( hilight, &src, sdl_realscreen, dest );
820               dest++;
821             }
822
823             // show icon
824             mm_cache_t *ic = cache_query_icon ( appiter -> ref -> unique_id );
825             SDL_Surface *iconsurface = NULL;
826
827             // if icon not in cache, and its a pnd-file source, perhaps try to load it right now..
828             if ( ( ! ic ) &&
829                  ( load_visible ) &&
830                  ( ! ( appiter -> ref -> object_flags & PND_DISCO_GENERATED ) )
831                )
832             {
833               // try to load any icons that..
834               // - are not yet loaded
835               // - did not fail a previous load attempt
836               // this way user can upfront load all icons, or defer all icons, or even defer all icons
837               // and still try to load visible ones 'just before needed'; so not at mmenu load time, but
838               // as needed (only the ones needed.)
839
840               if ( ( appiter -> ref -> pnd_icon_pos ) ||
841                    ( appiter -> ref -> icon && appiter -> ref -> object_flags & PND_DISCO_LIBPND_DD )
842                  )
843               {
844   
845                 // try to cache it?
846                 if ( ! cache_icon ( appiter -> ref, ui_display_context.icon_max_width, ui_display_context.icon_max_width ) ) {
847                   // erm..
848                 }
849
850                 // avoid churn
851                 appiter -> ref -> pnd_icon_pos = 0;
852                 if ( appiter -> ref -> icon ) {
853                   free ( appiter -> ref -> icon );
854                   appiter -> ref -> icon = NULL;
855                 }
856
857                 // pick up as if nothing happened..
858                 ic = cache_query_icon ( appiter -> ref -> unique_id );
859
860               }
861
862             } // load icon during rendering?
863
864             if ( ic ) {
865               iconsurface = ic -> itiny;
866             } else {
867               //pnd_log ( pndn_warning, "WARNING: TBD: Need Missin-icon icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
868
869               // no icon override; was this a pnd-file (show the unknown icon then), or was this generated from
870               // filesystem (file or directory icon)
871               if ( appiter -> ref -> object_flags & PND_DISCO_GENERATED ) {
872                 if ( appiter -> ref -> object_type == pnd_object_type_directory ) {
873
874                   // is this a subcat, a .., or a filesystem folder?
875                   //iconsurface = g_imagecache [ IMG_FOLDER ].i;
876                   if ( g_categories [ ui_category ] -> fspath ) {
877                     iconsurface = g_imagecache [ IMG_FOLDER ].itiny;
878                   } else if ( strcmp ( appiter -> ref -> title_en, ".." ) == 0 ) {
879                     iconsurface = g_imagecache [ IMG_DOTDOTFOLDER ].itiny;
880                   } else {
881                     iconsurface = g_imagecache [ IMG_SUBCATFOLDER ].itiny;
882                   }
883
884                 } else {
885                   iconsurface = g_imagecache [ IMG_EXECBIN ].itiny;
886                 }
887               } else {
888                 iconsurface = g_imagecache [ IMG_ICON_MISSING ].itiny;
889               }
890
891             }
892
893             if ( iconsurface ) {
894               dest -> x = c -> grid_offset_x + c -> text_clip_x;
895               dest -> y = c -> grid_offset_y + ( displayrow * ( c -> text_height_tab + c -> icon_offset_y ) ) - ( c -> icon_offset_y / 2 );
896               SDL_BlitSurface ( iconsurface, NULL, sdl_realscreen, dest );
897             }
898
899             // show title text
900             if ( appiter -> ref -> title_en ) {
901               SDL_Surface *rtext;
902               rtext = TTF_RenderText_Blended ( g_tab_font, appiter -> ref -> title_en, c -> fontcolor );
903               src.x = 0;
904               src.y = 0;
905               src.w = hilight -> w; //c -> text_width < rtext -> w ? c -> text_width : rtext -> w;
906               src.h = rtext -> h;
907
908               dest -> x = c -> grid_offset_x + c -> text_clip_x;
909               dest -> x += 30; // so all title-text line up, regardless of icon presence
910 #if 0
911               if ( ic ) {
912                 dest -> x += 30; //((SDL_Surface*)ic -> i) -> w;
913               }
914 #endif
915
916               dest -> y = c -> grid_offset_y + ( displayrow * ( c -> text_height_tab + c -> icon_offset_y ) );
917               SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
918               SDL_FreeSurface ( rtext );
919               dest++;
920             }
921
922           } // visible or scrolled?
923
924           if ( row >= ui_rows_scrolled_down ) {
925             displayrow++;
926           }
927
928           // are we done displaying rows?
929           if ( displayrow >= icon_visible_rows ) {
930             break;
931           }
932
933           appiter = appiter -> next;
934           row++;
935
936         } // while
937
938       } else {
939
940         appiter = g_categories [ ui_category ] -> refs;
941         row = 0;
942         displayrow = 0;
943
944         // until we run out of apps, or run out of space
945         while ( appiter != NULL ) {
946
947           for ( col = 0; col < c -> col_max && appiter != NULL; col++ ) {
948
949             // do we even need to render it? or are we suppressing it due to rows scrolled off the top?
950             if ( row >= ui_rows_scrolled_down ) {
951
952               // selected? show hilights
953               if ( appiter == ui_selected ) {
954                 SDL_Surface *s = g_imagecache [ IMG_SELECTED_ALPHAMASK ].i;
955                 // icon
956                 //dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x + ( ( icon_max_width - s -> w ) / 2 );
957                 dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> icon_offset_x + c -> sel_icon_offset_x;
958                 //dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y + ( ( icon_max_height - s -> h ) / 2 );
959                 dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> icon_offset_y + c -> sel_icon_offset_y;
960                 SDL_BlitSurface ( s, NULL /* all */, sdl_realscreen, dest );
961                 dest++;
962                 // text
963                 dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_clip_x;
964                 dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> text_hilite_offset_y;
965                 SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_HILITE ].i, NULL /* all */, sdl_realscreen, dest );
966                 dest++;
967               } // selected?
968
969               // show icon
970               mm_cache_t *ic = cache_query_icon ( appiter -> ref -> unique_id );
971               SDL_Surface *iconsurface;
972
973               // if icon not in cache, and its a pnd-file source, perhaps try to load it right now..
974               if ( ( ! ic ) &&
975                    ( load_visible ) &&
976                    ( ! ( appiter -> ref -> object_flags & PND_DISCO_GENERATED ) )
977                  )
978               {
979                 // try to load any icons that..
980                 // - are not yet loaded
981                 // - did not fail a previous load attempt
982                 // this way user can upfront load all icons, or defer all icons, or even defer all icons
983                 // and still try to load visible ones 'just before needed'; so not at mmenu load time, but
984                 // as needed (only the ones needed.)
985
986                 if ( ( appiter -> ref -> pnd_icon_pos ) ||
987                      ( appiter -> ref -> icon && appiter -> ref -> object_flags & PND_DISCO_LIBPND_DD )
988                    )
989                 {
990   
991                   // try to cache it?
992                   if ( ! cache_icon ( appiter -> ref, ui_display_context.icon_max_width, ui_display_context.icon_max_width ) ) {
993                     // erm..
994                   }
995
996                   // avoid churn
997                   appiter -> ref -> pnd_icon_pos = 0;
998                   if ( appiter -> ref -> icon ) {
999                     free ( appiter -> ref -> icon );
1000                     appiter -> ref -> icon = NULL;
1001                   }
1002
1003                   // pick up as if nothing happened..
1004                   ic = cache_query_icon ( appiter -> ref -> unique_id );
1005
1006                 }
1007
1008               } // load icon during rendering?
1009
1010               if ( ic ) {
1011                 iconsurface = ic -> i;
1012               } else {
1013                 //pnd_log ( pndn_warning, "WARNING: TBD: Need Missin-icon icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
1014
1015                 // no icon override; was this a pnd-file (show the unknown icon then), or was this generated from
1016                 // filesystem (file or directory icon)
1017                 if ( appiter -> ref -> object_flags & PND_DISCO_GENERATED ) {
1018                   if ( appiter -> ref -> object_type == pnd_object_type_directory ) {
1019
1020                     // is this a subcat, a .., or a filesystem folder?
1021                     //iconsurface = g_imagecache [ IMG_FOLDER ].i;
1022                     if ( g_categories [ ui_category ] -> fspath ) {
1023                       iconsurface = g_imagecache [ IMG_FOLDER ].i;
1024                     } else if ( strcmp ( appiter -> ref -> title_en, ".." ) == 0 ) {
1025                       iconsurface = g_imagecache [ IMG_DOTDOTFOLDER ].i;
1026                     } else {
1027                       iconsurface = g_imagecache [ IMG_SUBCATFOLDER ].i;
1028                     }
1029
1030                   } else {
1031                     iconsurface = g_imagecache [ IMG_EXECBIN ].i;
1032                   }
1033                 } else {
1034                   iconsurface = g_imagecache [ IMG_ICON_MISSING ].i;
1035                 }
1036
1037               }
1038
1039               // got an icon I hope?
1040               if ( iconsurface ) {
1041                 //pnd_log ( pndn_debug, "Got an icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
1042
1043                 src.x = 0;
1044                 src.y = 0;
1045                 src.w = 60;
1046                 src.h = 60;
1047                 dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> icon_offset_x + (( c -> icon_max_width - iconsurface -> w ) / 2);
1048                 dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> icon_offset_y + (( c -> icon_max_height - iconsurface -> h ) / 2);
1049
1050                 SDL_BlitSurface ( iconsurface, &src, sdl_realscreen, dest );
1051
1052                 // store touch info
1053                 ui_register_app ( appiter, dest -> x, dest -> y, src.w, src.h );
1054
1055                 dest++;
1056
1057               }
1058
1059               // show text
1060               if ( appiter -> ref -> title_en ) {
1061                 SDL_Surface *rtext;
1062                 rtext = TTF_RenderText_Blended ( g_grid_font, appiter -> ref -> title_en, c -> fontcolor );
1063                 src.x = 0;
1064                 src.y = 0;
1065                 src.w = c -> text_width < rtext -> w ? c -> text_width : rtext -> w;
1066                 src.h = rtext -> h;
1067                 if ( rtext -> w > c -> text_width ) {
1068                   dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_clip_x;
1069                 } else {
1070                   dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_offset_x - ( rtext -> w / 2 );
1071                 }
1072                 dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> text_offset_y;
1073                 SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1074                 SDL_FreeSurface ( rtext );
1075                 dest++;
1076               }
1077
1078             } // display now? or scrolled away..
1079
1080             // next
1081             appiter = appiter -> next;
1082
1083           } // for column 1...X
1084
1085           if ( row >= ui_rows_scrolled_down ) {
1086             displayrow++;
1087           }
1088
1089           row ++;
1090
1091           // are we done displaying rows?
1092           if ( displayrow >= c -> row_max ) {
1093             break;
1094           }
1095
1096         } // while
1097
1098       } // icon view
1099
1100     } else {
1101       // no apps to render?
1102       pnd_log ( pndn_rem, "No applications to render?\n" );
1103     } // apps to renser?
1104
1105   } // r_grid
1106
1107   // detail panel - show app details or blank-message
1108   if ( render_jobs_b & R_DETAIL && ui_detail_hidden == 0 ) {
1109
1110     unsigned int cell_offset_x = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_x" );
1111     unsigned int cell_offset_y = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_y" );
1112     unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "detailtext.cell_width" );
1113
1114     unsigned int desty = cell_offset_y;
1115
1116     if ( ui_selected ) {
1117
1118       char buffer [ 256 ];
1119
1120       // full name
1121       if ( ui_selected -> ref -> title_en ) {
1122         SDL_Surface *rtext;
1123         rtext = TTF_RenderText_Blended ( g_detailtext_font, ui_selected -> ref -> title_en, c -> fontcolor );
1124         src.x = 0;
1125         src.y = 0;
1126         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
1127         src.h = rtext -> h;
1128         dest -> x = cell_offset_x;
1129         dest -> y = desty;
1130         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1131         SDL_FreeSurface ( rtext );
1132         dest++;
1133         desty += src.h;
1134       }
1135
1136       // category
1137 #if 0
1138       if ( ui_selected -> ref -> main_category ) {
1139
1140         sprintf ( buffer, "Category: %s", ui_selected -> ref -> main_category );
1141
1142         SDL_Surface *rtext;
1143         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
1144         src.x = 0;
1145         src.y = 0;
1146         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
1147         src.h = rtext -> h;
1148         dest -> x = cell_offset_x;
1149         dest -> y = desty;
1150         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1151         SDL_FreeSurface ( rtext );
1152         dest++;
1153         desty += src.h;
1154       }
1155 #endif
1156
1157       // clock
1158       if ( ui_selected -> ref -> clockspeed ) {
1159
1160         sprintf ( buffer, "CPU Clock: %s", ui_selected -> ref -> clockspeed );
1161
1162         SDL_Surface *rtext;
1163         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
1164         src.x = 0;
1165         src.y = 0;
1166         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
1167         src.h = rtext -> h;
1168         dest -> x = cell_offset_x;
1169         dest -> y = desty;
1170         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1171         SDL_FreeSurface ( rtext );
1172         dest++;
1173         desty += src.h;
1174       }
1175
1176       // show sub-app# on right side of cpu clock?
1177       //if ( ui_selected -> ref -> subapp_number )
1178       {
1179         sprintf ( buffer, "(app#%u)", ui_selected -> ref -> subapp_number );
1180
1181         SDL_Surface *rtext;
1182         rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1183         dest -> x = cell_offset_x + cell_width - rtext -> w;
1184         dest -> y = desty - src.h;
1185         SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1186         SDL_FreeSurface ( rtext );
1187         dest++;
1188       }
1189
1190       // info hint
1191 #if 0 // merged into hint-line
1192       if ( ui_selected -> ref -> info_filename ) {
1193
1194         sprintf ( buffer, "Documentation - hit Y" );
1195
1196         SDL_Surface *rtext;
1197         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
1198         src.x = 0;
1199         src.y = 0;
1200         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
1201         src.h = rtext -> h;
1202         dest -> x = cell_offset_x;
1203         dest -> y = desty;
1204         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1205         SDL_FreeSurface ( rtext );
1206         dest++;
1207         desty += src.h;
1208       }
1209 #endif
1210
1211       // notes
1212       if ( ui_selected -> ovrh ) {
1213         char *n;
1214         unsigned char i;
1215         char buffer [ 50 ];
1216
1217         desty += 5; // a touch of spacing can't hurt
1218
1219         for ( i = 1; i < 4; i++ ) {
1220           sprintf ( buffer, "Application-%u.note-%u", ui_selected -> ref -> subapp_number, i );
1221           n = pnd_conf_get_as_char ( ui_selected -> ovrh, buffer );
1222
1223           if ( n ) {
1224             SDL_Surface *rtext;
1225             rtext = TTF_RenderText_Blended ( g_detailtext_font, n, c -> fontcolor );
1226             src.x = 0;
1227             src.y = 0;
1228             src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
1229             src.h = rtext -> h;
1230             dest -> x = cell_offset_x;
1231             dest -> y = desty;
1232             SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1233             SDL_FreeSurface ( rtext );
1234             dest++;
1235             desty += rtext -> h;
1236           }
1237         } // for
1238
1239       } // r_detail -> notes
1240
1241       // preview pic
1242       mm_cache_t *ic = cache_query_preview ( ui_selected -> ref -> unique_id );
1243       SDL_Surface *previewpic;
1244
1245       if ( ic ) {
1246         previewpic = ic -> i;
1247       } else {
1248         previewpic = g_imagecache [ IMG_PREVIEW_MISSING ].i;
1249       }
1250
1251       if ( previewpic ) {
1252         dest -> x = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_x", 50 ) +
1253           ( ( pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ) - previewpic -> w ) / 2 );
1254         dest -> y = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_y", 50 );
1255         SDL_BlitSurface ( previewpic, NULL /* whole image */, sdl_realscreen, dest );
1256         dest++;
1257       }
1258
1259     } else {
1260
1261       char *empty_message = "Press SELECT for menu";
1262
1263       SDL_Surface *rtext;
1264
1265       rtext = TTF_RenderText_Blended ( g_detailtext_font, empty_message, c -> fontcolor );
1266
1267       src.x = 0;
1268       src.y = 0;
1269       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
1270       src.h = rtext -> h;
1271       dest -> x = cell_offset_x;
1272       dest -> y = desty;
1273       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1274       SDL_FreeSurface ( rtext );
1275       dest++;
1276
1277       desty += src.h;
1278
1279       rtext = TTF_RenderText_Blended ( g_detailtext_font, "START or B to run app", c -> fontcolor );
1280       dest -> x = cell_offset_x;
1281       dest -> y = desty;
1282       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1283       SDL_FreeSurface ( rtext );
1284       dest++;
1285       desty += src.h;
1286
1287       rtext = TTF_RenderText_Blended ( g_detailtext_font, "SPACE for app menu", c -> fontcolor );
1288       dest -> x = cell_offset_x;
1289       dest -> y = desty;
1290       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1291       SDL_FreeSurface ( rtext );
1292       dest++;
1293       desty += src.h;
1294
1295       rtext = TTF_RenderText_Blended ( g_detailtext_font, "A to toggle details", c -> fontcolor );
1296       dest -> x = cell_offset_x;
1297       dest -> y = desty;
1298       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
1299       SDL_FreeSurface ( rtext );
1300       dest++;
1301       desty += src.h;
1302
1303     } // r_detail && selected?
1304
1305   } // r_detail
1306
1307   // extras
1308   //
1309
1310   // battery
1311   if ( render_jobs_b & R_BG ) {
1312     static int last_battlevel = 0;
1313     static unsigned char batterylevel = 0;
1314     char buffer [ 100 ];
1315
1316     if ( time ( NULL ) - last_battlevel > 60 ) {
1317       batterylevel = pnd_device_get_battery_gauge_perc();
1318       last_battlevel = time ( NULL );
1319     }
1320
1321     sprintf ( buffer, "Battery: %u%%", batterylevel );
1322
1323     SDL_Surface *rtext;
1324     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1325     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.battery_x", 20 );
1326     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.battery_y", 450 );
1327     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1328     SDL_FreeSurface ( rtext );
1329     dest++;
1330   }
1331
1332   // hints
1333   if ( pnd_conf_get_as_char ( g_conf, "display.hintline" ) ) {
1334     char *buffer;
1335     unsigned int hintx, hinty;
1336     hintx = pnd_conf_get_as_int_d ( g_conf, "display.hint_x", 40 );
1337     hinty = pnd_conf_get_as_int_d ( g_conf, "display.hint_y", 450 );
1338     static unsigned int lastwidth = 3000;
1339
1340     if ( ui_selected && ui_selected -> ref -> info_filename ) {
1341       buffer = "Documentation - hit Y";
1342     } else {
1343       buffer = pnd_conf_get_as_char ( g_conf, "display.hintline" );
1344     }
1345
1346     SDL_Surface *rtext;
1347     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1348
1349     // clear bg
1350     if ( ! ( render_jobs_b & R_BG ) ) {
1351       src.x = hintx;
1352       src.y = hinty;
1353       src.w = lastwidth;
1354       src.h = rtext -> h;
1355       dest -> x = hintx;
1356       dest -> y = hinty;
1357       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, &src, sdl_realscreen, dest );
1358       dest++;
1359       lastwidth = rtext -> w;
1360     }
1361
1362     // now render text
1363     dest -> x = hintx;
1364     dest -> y = hinty;
1365     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1366     SDL_FreeSurface ( rtext );
1367     dest++;
1368   }
1369
1370   // clock time
1371   if ( render_jobs_b & R_BG &&
1372        pnd_conf_get_as_int_d ( g_conf, "display.clock_x", -1 ) != -1 )
1373   {
1374     char buffer [ 50 ];
1375
1376     time_t t = time ( NULL );
1377     struct tm *tm = localtime ( &t );
1378     strftime ( buffer, 50, "%a %H:%M %F", tm );
1379
1380     SDL_Surface *rtext;
1381     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1382     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.clock_x", 700 );
1383     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.clock_y", 450 );
1384     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1385     SDL_FreeSurface ( rtext );
1386     dest++;
1387   }
1388
1389   // update all the rects and send it all to sdl
1390   // - at this point, we could probably just do 1 rect, of the
1391   //   whole screen, and be faster :/
1392   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1393
1394 } // ui_render
1395
1396 void ui_process_input ( pnd_dbusnotify_handle dbh, pnd_notify_handle nh ) {
1397   SDL_Event event;
1398
1399   unsigned char ui_event = 0; // if we get a ui event, flip to 1 and break
1400   //static ui_sdl_button_e ui_mask = uisb_none; // current buttons down
1401
1402   while ( ( ! ui_event ) &&
1403           /*block_p ?*/ SDL_WaitEvent ( &event ) /*: SDL_PollEvent ( &event )*/ )
1404   {
1405
1406     switch ( event.type ) {
1407
1408     case SDL_USEREVENT:
1409       // update something
1410
1411       // the user-defined SDL events are all for threaded/delayed previews (and icons, which
1412       // generally are not used); if we're in wide mode, we can skip previews
1413       // to avoid slowing things down when they're not shown.
1414
1415       if ( event.user.code == sdl_user_ticker ) {
1416
1417         if ( ui_detail_hidden ) {
1418           break; // skip building previews when not showing them
1419         }
1420
1421         // timer went off, time to load something
1422         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
1423
1424           if ( ! ui_selected ) {
1425             break;
1426           }
1427
1428           // load the preview pics now!
1429           pnd_disco_t *iter = ui_selected -> ref;
1430
1431           if ( iter -> preview_pic1 ) {
1432
1433             if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.threaded_preview", 0 ) ) {
1434               // load in bg thread, make user experience chuggy
1435
1436               g_preview_thread = SDL_CreateThread ( (void*)ui_threaded_defered_preview, iter );
1437
1438               if ( ! g_preview_thread ) {
1439                 pnd_log ( pndn_error, "ERROR: Couldn't create preview thread\n" );
1440               }
1441
1442             } else {
1443               // load it now, make user wait
1444
1445               if ( ! cache_preview ( iter, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
1446                                      pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
1447                  )
1448               {
1449                 pnd_log ( pndn_debug, "  Couldn't load preview pic: '%s' -> '%s'\n",
1450                           IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1451               }
1452
1453             } // threaded?
1454
1455           } // got a preview at all?
1456
1457           ui_event++;
1458         }
1459
1460       } else if ( event.user.code == sdl_user_finishedpreview ) {
1461
1462         // if we just finished the one we happen to be looking at, better redraw now; otherwise, if
1463         // we finished another, no big woop
1464         if ( ui_selected && event.user.data1 == ui_selected -> ref ) {
1465           ui_event++;
1466         }
1467
1468       } else if ( event.user.code == sdl_user_finishedicon ) {
1469         // redraw, so we can show the newly loaded icon
1470         ui_event++;
1471
1472       } else if ( event.user.code == sdl_user_checksd ) {
1473         // check if any inotify-type events occured, forcing us to rescan/re-disco the SDs
1474
1475         unsigned char watch_dbus = 0;
1476         unsigned char watch_inotify = 0;
1477
1478         if ( dbh ) {
1479           watch_dbus = pnd_dbusnotify_rediscover_p ( dbh );
1480         }
1481
1482         if ( nh ) {
1483           watch_inotify = pnd_notify_rediscover_p ( nh );
1484         }
1485
1486         if ( watch_dbus || watch_inotify ) {
1487
1488           pnd_log ( pndn_debug, "dbusnotify detected SD event\n" );
1489
1490           // sometimes mmenu notices the SD event before pndnotifyd does, since pndnotifyd
1491           // is coded not to react too quickly (in the event of multiple spammed events or
1492           // other weird stuff that was seen when it was being written.) As such, lets wait
1493           // a second to give pndnotifyd a chance to have awoken..
1494           SDL_Delay ( 1500 ); // in 1/1000th of seconds
1495
1496           // kick off a rescan
1497           applications_free();
1498           applications_scan();
1499
1500           ui_event++;
1501         }
1502
1503       } // SDL user event
1504
1505       render_mask |= CHANGED_EVERYTHING;
1506
1507       break;
1508
1509 #if 0 // joystick motion
1510     case SDL_JOYAXISMOTION:
1511
1512       pnd_log ( PND_LOG_DEFAULT, "joystick axis\n" );
1513
1514         if ( event.jaxis.axis == 0 ) {
1515           // horiz
1516           if ( event.jaxis.value < 0 ) {
1517             ui_push_left ( 0 );
1518             pnd_log ( PND_LOG_DEFAULT, "joystick axis - LEFT\n" );
1519           } else if ( event.jaxis.value > 0 ) {
1520             ui_push_right ( 0 );
1521             pnd_log ( PND_LOG_DEFAULT, "joystick axis - RIGHT\n" );
1522           }
1523         } else if ( event.jaxis.axis == 1 ) {
1524           // vert
1525           if ( event.jaxis.value < 0 ) {
1526             ui_push_up();
1527           } else if ( event.jaxis.value > 0 ) {
1528             ui_push_down();
1529           }
1530         }
1531
1532         ui_event++;
1533
1534         break;
1535 #endif
1536
1537 #if 0 // joystick buttons
1538     case SDL_JOYBUTTONDOWN:
1539
1540       pnd_log ( PND_LOG_DEFAULT, "joystick button down %u\n", event.jbutton.button );
1541
1542       if ( event.jbutton.button == 0 ) { // B
1543         ui_mask |= uisb_b;
1544       } else if ( event.jbutton.button == 1 ) { // Y
1545         ui_mask |= uisb_y;
1546       } else if ( event.jbutton.button == 2 ) { // X
1547         ui_mask |= uisb_x;
1548       } else if ( event.jbutton.button == 3 ) { // A
1549         ui_mask |= uisb_a;
1550
1551       } else if ( event.jbutton.button == 4 ) { // Select
1552         ui_mask |= uisb_select;
1553       } else if ( event.jbutton.button == 5 ) { // Start
1554         ui_mask |= uisb_start;
1555
1556       } else if ( event.jbutton.button == 7 ) { // L
1557         ui_mask |= uisb_l;
1558       } else if ( event.jbutton.button == 8 ) { // R
1559         ui_mask |= uisb_r;
1560
1561       }
1562
1563       ui_event++;
1564
1565       break;
1566
1567     case SDL_JOYBUTTONUP:
1568
1569       pnd_log ( PND_LOG_DEFAULT, "joystick button up %u\n", event.jbutton.button );
1570
1571       if ( event.jbutton.button == 0 ) { // B
1572         ui_mask &= ~uisb_b;
1573         ui_push_exec();
1574       } else if ( event.jbutton.button == 1 ) { // Y
1575         ui_mask &= ~uisb_y;
1576       } else if ( event.jbutton.button == 2 ) { // X
1577         ui_mask &= ~uisb_x;
1578       } else if ( event.jbutton.button == 3 ) { // A
1579         ui_mask &= ~uisb_a;
1580
1581       } else if ( event.jbutton.button == 4 ) { // Select
1582         ui_mask &= ~uisb_select;
1583       } else if ( event.jbutton.button == 5 ) { // Start
1584         ui_mask &= ~uisb_start;
1585
1586       } else if ( event.jbutton.button == 7 ) { // L
1587         ui_mask &= ~uisb_l;
1588         ui_push_ltrigger();
1589       } else if ( event.jbutton.button == 8 ) { // R
1590         ui_mask &= ~uisb_r;
1591         ui_push_rtrigger();
1592
1593       }
1594
1595       ui_event++;
1596
1597       break;
1598 #endif
1599
1600 #if 1 // keyboard events
1601     //case SDL_KEYUP:
1602     case SDL_KEYDOWN:
1603
1604       //pnd_log ( pndn_debug, "key up %u\n", event.key.keysym.sym );
1605
1606       // SDLK_LALT -> Start
1607       // page up/down for y/x
1608       // home/end for a and b
1609
1610       // directional
1611       if ( event.key.keysym.sym == SDLK_RIGHT ) {
1612         ui_push_right ( 0 );
1613         ui_event++;
1614       } else if ( event.key.keysym.sym == SDLK_LEFT ) {
1615         ui_push_left ( 0 );
1616         ui_event++;
1617       } else if ( event.key.keysym.sym == SDLK_UP ) {
1618         ui_push_up();
1619         ui_event++;
1620       } else if ( event.key.keysym.sym == SDLK_DOWN ) {
1621         ui_push_down();
1622         ui_event++;
1623       } else if ( event.key.keysym.sym == SDLK_END ) { // B
1624         ui_push_exec();
1625         ui_event++;
1626       } else if ( event.key.keysym.sym == SDLK_SPACE ) { // space
1627         if ( ui_selected ) {
1628           ui_menu_context ( ui_selected );
1629         } else {
1630           // TBD: post an error?
1631         }
1632         render_mask |= CHANGED_EVERYTHING;
1633         ui_event++;
1634       } else if ( event.key.keysym.sym == SDLK_TAB || event.key.keysym.sym == SDLK_HOME ) { // tab or A
1635         // if detail panel is togglable, then toggle it
1636         // if not, make sure its ruddy well shown!
1637         if ( ui_is_detail_hideable() ) {
1638           ui_toggle_detail_pane();
1639         } else {
1640           ui_detail_hidden = 0;
1641         }
1642         ui_event++;
1643       } else if ( event.key.keysym.sym == SDLK_RSHIFT || event.key.keysym.sym == SDLK_COMMA ) { // left trigger or comma
1644         ui_push_ltrigger();
1645         ui_event++;
1646       } else if ( event.key.keysym.sym == SDLK_RCTRL || event.key.keysym.sym == SDLK_PERIOD ) { // right trigger or period
1647         ui_push_rtrigger();
1648         ui_event++;
1649
1650       } else if ( event.key.keysym.sym == SDLK_PAGEUP ) { // Y
1651         // info
1652         if ( ui_selected ) {
1653           ui_show_info ( pnd_run_script, ui_selected -> ref );
1654           ui_event++;
1655         }
1656       } else if ( event.key.keysym.sym == SDLK_PAGEDOWN ) { // X
1657         ui_push_backup();
1658
1659         // forget the selection, nolonger applies
1660         ui_selected = NULL;
1661         ui_set_selected ( ui_selected );
1662         // rescan the dir
1663         if ( g_categories [ ui_category ] -> fspath ) {
1664           category_fs_restock ( g_categories [ ui_category ] );
1665         }
1666         // redraw the grid
1667         render_mask |= CHANGED_EVERYTHING;
1668         ui_event++;
1669
1670       } else if ( event.key.keysym.sym == SDLK_LALT ) { // start button
1671         ui_push_exec();
1672         ui_event++;
1673
1674       } else if ( event.key.keysym.sym == SDLK_LCTRL /*LALT*/ ) { // select button
1675         char *opts [ 20 ] = {
1676           "Toggle view icons<->list",
1677           "Reveal hidden category",
1678           "Shutdown Pandora",
1679           "Configure Minimenu",
1680           "Manage custom app categories",
1681           "Rescan for applications",
1682           "Cache previews to SD now",
1683           "Run a terminal/console",
1684           "Run another GUI (xfce, etc)",
1685           "Quit (<- beware)",
1686           "Select a Minimenu skin",
1687           "About Minimenu"
1688         };
1689         int sel = ui_modal_single_menu ( opts, 12, "Minimenu", "Enter to select; other to return." );
1690
1691         char buffer [ 100 ];
1692         if ( sel == 0 ) {
1693           if ( ui_viewmode == uiv_list ) {
1694             ui_viewmode = uiv_icons;
1695           } else {
1696             ui_viewmode = uiv_list;
1697           }
1698         } else if ( sel == 1 ) {
1699           // do nothing
1700           ui_revealscreen();
1701         } else if ( sel == 2 ) {
1702           // store conf on exit, so that last app/cat can be cached.. for ED :)
1703           conf_write ( g_conf, conf_determine_location ( g_conf ) );
1704           // shutdown
1705           sprintf ( buffer, "sudo poweroff" );
1706           system ( buffer );
1707         } else if ( sel == 3 ) {
1708           // configure mm
1709           unsigned char restart = conf_run_menu ( NULL );
1710           conf_write ( g_conf, conf_determine_location ( g_conf ) );
1711           conf_write ( g_conf, CONF_PREF_TEMPPATH );
1712           if ( restart ) {
1713             emit_and_quit ( MM_RESTART );
1714           }
1715         } else if ( sel == 4 ) {
1716           // manage custom categories
1717           ui_manage_categories();
1718         } else if ( sel == 5 ) {
1719           // rescan apps
1720           pnd_log ( pndn_debug, "Freeing up applications\n" );
1721           applications_free();
1722           pnd_log ( pndn_debug, "Rescanning applications\n" );
1723           applications_scan();
1724         } else if ( sel == 6 ) {
1725           // cache preview to SD now
1726           extern pnd_box_handle g_active_apps;
1727           pnd_box_handle h = g_active_apps;
1728
1729           unsigned int maxwidth, maxheight;
1730           maxwidth = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 );
1731           maxheight = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 );
1732
1733           pnd_disco_t *iter = pnd_box_get_head ( h );
1734
1735           while ( iter ) {
1736
1737             // cache it
1738             if ( ! cache_preview ( iter, maxwidth, maxheight ) ) {
1739               pnd_log ( pndn_debug, "Force cache: Couldn't load preview pic: '%s' -> '%s'\n",
1740                         IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1741             }
1742
1743             // next
1744             iter = pnd_box_get_next ( iter );
1745           } // while
1746
1747         } else if ( sel == 7 ) {
1748           // run terminal
1749           char *argv[5];
1750           argv [ 0 ] = pnd_conf_get_as_char ( g_conf, "utility.terminal" );
1751           argv [ 1 ] = NULL;
1752
1753           if ( argv [ 0 ] ) {
1754             ui_forkexec ( argv );
1755           }
1756
1757         } else if ( sel == 8 ) {
1758           char buffer [ PATH_MAX ];
1759           sprintf ( buffer, "%s %s\n", MM_RUN, "/usr/pandora/scripts/op_switchgui.sh" );
1760           emit_and_quit ( buffer );
1761         } else if ( sel == 9 ) {
1762           emit_and_quit ( MM_QUIT );
1763         } else if ( sel == 10 ) {
1764           // select skin
1765           if ( ui_pick_skin() ) {
1766             emit_and_quit ( MM_RESTART );
1767           }
1768         } else if ( sel == 11 ) {
1769           // about
1770           char buffer [ PATH_MAX ];
1771           sprintf ( buffer, "%s/about.txt", g_skinpath );
1772           ui_aboutscreen ( buffer );
1773         }
1774
1775         ui_event++;
1776         render_mask |= CHANGED_EVERYTHING;
1777
1778       } else {
1779         // unknown SDLK_ keycode?
1780
1781         // many SDLK_keycodes map to ASCII ("a" is ascii(a)), so try to jump to a filename of that name, in this category?
1782         // and if already there, try to jump to next, maybe?
1783         // future: look for sequence typing? ie: user types 'm' then 'a', look for 'ma*' instead of 'm' then 'a' matching
1784         if ( isalpha ( event.key.keysym.sym ) && g_categories [ ui_category ] -> refcount > 0 ) {
1785           mm_appref_t *app = g_categories [ ui_category ] -> refs;
1786
1787           //fprintf ( stderr, "sel %s next %s\n", ui_selected -> ref -> title_en, ui_selected -> next -> ref -> title_en );
1788
1789           // are we already matching the same char? and next item is also same char?
1790           if ( app && ui_selected && ui_selected -> next &&
1791                ui_selected -> ref -> title_en && ui_selected -> next -> ref -> title_en &&
1792                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( ui_selected -> next -> ref -> title_en [ 0 ] ) &&
1793                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym )
1794              )
1795           {
1796             // just skip down one
1797             app = ui_selected -> next;
1798           } else {
1799
1800             // walk the category, looking for a first-char match
1801             while ( app ) {
1802               if ( app -> ref -> title_en && toupper ( app -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym ) ) {
1803                 break;
1804               }
1805               app = app -> next;
1806             }
1807
1808           } // same start letter, or new run?
1809
1810           // found something, or no?
1811           if ( app ) {
1812             // looks like we found a potential match; try switching it to visible selection
1813             ui_selected = app;
1814             ui_set_selected ( ui_selected );
1815             ui_event++;
1816           }
1817
1818
1819         } // SDLK_alphanumeric?
1820
1821       } // SDLK_....
1822
1823       // extras
1824 #if 1
1825       if ( event.key.keysym.sym == SDLK_ESCAPE ) {
1826         emit_and_quit ( MM_QUIT );
1827       }
1828 #endif
1829
1830       break;
1831 #endif
1832
1833 #if 1 // mouse / touchscreen
1834 #if 0
1835     case SDL_MOUSEBUTTONDOWN:
1836       if ( event.button.button == SDL_BUTTON_LEFT ) {
1837         cb_pointer_press ( gc, event.button.x / g_scale, event.button.y / g_scale );
1838         ui_event++;
1839       }
1840       break;
1841 #endif
1842
1843     case SDL_MOUSEBUTTONUP:
1844       if ( event.button.button == SDL_BUTTON_LEFT ) {
1845         ui_touch_act ( event.button.x, event.button.y );
1846         ui_event++;
1847       }
1848       break;
1849 #endif
1850
1851     case SDL_QUIT:
1852       exit ( 0 );
1853       break;
1854
1855     default:
1856       break;
1857
1858     } // switch event type
1859
1860   } // while poll
1861
1862   return;
1863 }
1864
1865 void ui_push_left ( unsigned char forcecoil ) {
1866
1867   if ( ui_viewmode == uiv_list ) {
1868     ui_context_t *c = &ui_display_context;
1869     int i;
1870     int imax = ( c -> cell_height * c -> row_max ) / ( c -> text_height_tab + c -> icon_offset_y ); // visible rows
1871     for ( i = 0; i < imax; i++ ) {
1872       ui_push_up();
1873     }
1874     return;
1875   }
1876
1877   if ( ! ui_selected ) {
1878     ui_push_right ( 0 );
1879     return;
1880   }
1881
1882   // what column we in?
1883   unsigned int col = ui_determine_screen_col ( ui_selected );
1884
1885   // are we already at first item?
1886   if ( forcecoil == 0 &&
1887        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1888        col == 0 )
1889   {
1890     unsigned int i = ui_display_context.col_max - 1;
1891     while ( i && ui_selected -> next ) {
1892       ui_push_right ( 0 );
1893       i--;
1894     }
1895
1896   } else if ( g_categories [ ui_category ] -> refs == ui_selected ) {
1897     // can't go any more left, we're at the head
1898
1899   } else {
1900     // figure out the previous item; yay for singly linked list :/
1901     mm_appref_t *i = g_categories [ ui_category ] -> refs;
1902     while ( i ) {
1903       if ( i -> next == ui_selected ) {
1904         ui_selected = i;
1905         break;
1906       }
1907       i = i -> next;
1908     }
1909   }
1910
1911   ui_set_selected ( ui_selected );
1912
1913   return;
1914 }
1915
1916 void ui_push_right ( unsigned char forcecoil ) {
1917
1918   if ( ui_viewmode == uiv_list ) {
1919     ui_context_t *c = &ui_display_context;
1920     int i;
1921     int imax = ( c -> cell_height * c -> row_max ) / ( c -> text_height_tab + c -> icon_offset_y ); // visible rows
1922     for ( i = 0; i < imax; i++ ) {
1923       ui_push_down();
1924     }
1925     return;
1926   }
1927
1928   if ( ui_selected ) {
1929
1930     // what column we in?
1931     unsigned int col = ui_determine_screen_col ( ui_selected );
1932
1933     // wrap same or no?
1934     if ( forcecoil == 0 &&
1935          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1936          // and selected is far-right, or last icon in category (might not be far right)
1937          ( ( col == ui_display_context.col_max - 1 ) ||
1938            ( ui_selected -> next == NULL ) )
1939        )
1940     {
1941       // same wrap
1942       //unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
1943       while ( col /*i*/ ) {
1944         ui_push_left ( 0 );
1945         col--; //i--;
1946       }
1947
1948     } else {
1949       // just go to the next
1950
1951       if ( ui_selected -> next ) {
1952         ui_selected = ui_selected -> next;
1953       }
1954
1955     }
1956
1957   } else {
1958     ui_selected = g_categories [ ui_category ] -> refs;
1959   }
1960
1961   ui_set_selected ( ui_selected );
1962
1963   return;
1964 }
1965
1966 void ui_push_up ( void ) {
1967
1968   unsigned char col_max = ui_display_context.col_max;
1969
1970   // not selected? well, no where to go..
1971   if ( ! ui_selected ) {
1972     return;
1973   }
1974
1975   if ( ui_viewmode == uiv_list ) {
1976
1977     if ( g_categories [ ui_category ] -> refs == ui_selected ) {
1978       // can't go any more up, we're at the head
1979
1980     } else {
1981       // figure out the previous item; yay for singly linked list :/
1982       mm_appref_t *i = g_categories [ ui_category ] -> refs;
1983       while ( i ) {
1984         if ( i -> next == ui_selected ) {
1985           ui_selected = i;
1986           break;
1987         }
1988         i = i -> next;
1989       }
1990
1991     }
1992
1993     // for list view.. we're done
1994     ui_set_selected ( ui_selected );
1995     return;
1996   }
1997
1998   // what row we in?
1999   unsigned int row = ui_determine_row ( ui_selected );
2000
2001   if ( row == 0 &&
2002        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
2003   {
2004     // wrap around instead
2005
2006     unsigned int col = ui_determine_screen_col ( ui_selected );
2007
2008     // go to end
2009     ui_selected = g_categories [ ui_category ] -> refs;
2010     while ( ui_selected -> next ) {
2011       ui_selected = ui_selected -> next;
2012     }
2013
2014     // try to move to same column
2015     unsigned int newcol = ui_determine_screen_col ( ui_selected );
2016     if ( newcol > col ) {
2017       col = newcol - col;
2018       while ( col ) {
2019         ui_push_left ( 0 );
2020         col--;
2021       }
2022     }
2023
2024     // scroll down to show it
2025     int r = ui_determine_row ( ui_selected ) - 1;
2026     if ( r - ui_display_context.row_max > 0 ) {
2027       ui_rows_scrolled_down = (unsigned int) r;
2028     }
2029
2030   } else {
2031     // stop at top/bottom
2032
2033     while ( col_max ) {
2034       ui_push_left ( 1 );
2035       col_max--;
2036     }
2037
2038   }
2039
2040   return;
2041 }
2042
2043 void ui_push_down ( void ) {
2044
2045   if ( ui_viewmode == uiv_list ) {
2046
2047     if ( ui_selected ) {
2048
2049       if ( ui_selected -> next ) {
2050         ui_selected = ui_selected -> next;
2051       }
2052
2053     } else {
2054       ui_selected = g_categories [ ui_category ] -> refs; // frist!
2055     }
2056
2057     // list view? we're done.
2058     ui_set_selected ( ui_selected );
2059     return;
2060   }
2061
2062   unsigned char col_max = ui_display_context.col_max;
2063
2064   if ( ui_selected ) {
2065
2066     // what row we in?
2067     unsigned int row = ui_determine_row ( ui_selected );
2068
2069     // max rows?
2070     unsigned int icon_rows = g_categories [ ui_category ] -> refcount / col_max;
2071     if ( g_categories [ ui_category ] -> refcount % col_max > 0 ) {
2072       icon_rows++;
2073     }
2074
2075     // we at the end?
2076     if ( row == ( icon_rows - 1 ) &&
2077          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
2078     {
2079
2080       unsigned char col = ui_determine_screen_col ( ui_selected );
2081
2082       ui_selected = g_categories [ ui_category ] -> refs;
2083
2084       while ( col ) {
2085         ui_selected = ui_selected -> next;
2086         col--;
2087       }
2088
2089       ui_rows_scrolled_down = 0;
2090
2091       render_mask |= CHANGED_EVERYTHING;
2092
2093     } else {
2094
2095       while ( col_max ) {
2096         ui_push_right ( 1 );
2097         col_max--;
2098       }
2099
2100     }
2101
2102   } else {
2103     ui_push_right ( 0 );
2104   }
2105
2106   return;
2107 }
2108
2109 // 'backup' is currently 'X', for going back up in a folder/subcat without having to hit exec on the '..' entry
2110 void ui_push_backup ( void ) {
2111
2112   // a subcat-as-dir, or a dir browser?
2113   if ( g_categories [ ui_category] -> fspath ) {
2114     // dir browser, just climb our way back up
2115
2116     // go up
2117     char *c;
2118
2119     // lop off last word; if the thing ends with /, lop that one, then the next word.
2120     while ( ( c = strrchr ( g_categories [ ui_category] -> fspath, '/' ) ) ) {
2121       *c = '\0'; // lop off the last hunk
2122       if ( *(c+1) != '\0' ) {
2123         break;
2124       }
2125     } // while
2126
2127     // nothing left?
2128     if ( g_categories [ ui_category] -> fspath [ 0 ] == '\0' ) {
2129       free ( g_categories [ ui_category] -> fspath );
2130       g_categories [ ui_category] -> fspath = strdup ( "/" );
2131     }
2132
2133   } else {
2134     // a pnd subcat .. are we in one, or at the 'top'?
2135     char *pcatname = g_categories [ ui_category ] -> parent_catname;
2136
2137     if ( ! pcatname ) {
2138       return; // we're at the 'top' already
2139     }
2140
2141     // set to first cat!
2142     ui_category = 0;
2143
2144     // republish cats .. shoudl just be the one
2145     category_publish ( CFNORMAL, NULL );
2146
2147     if ( pcatname ) {
2148       ui_category = category_index ( pcatname );
2149
2150       // ensure tab visible?
2151       unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2152       if ( ui_category > ui_catshift + ( ui_display_context.screen_width / tab_width ) - 1 ) {
2153         ui_catshift = ui_category - ( ui_display_context.screen_width / tab_width ) + 1;
2154       }
2155
2156     }
2157
2158   } // dir or subcat?
2159
2160   return;
2161 }
2162
2163 void ui_push_exec ( void ) {
2164
2165   if ( ! ui_selected ) {
2166     return;
2167   }
2168
2169   // cache the category/app so we can come back to it another time
2170   if ( ui_selected ) {
2171     pnd_conf_set_char ( g_conf, "minimenu.last_known_app_uid", ui_selected -> ref -> unique_id );
2172   }
2173   if ( g_categories [ 0 ] ) {
2174     pnd_conf_set_char ( g_conf, "minimenu.last_known_catname", g_categories [ ui_category ] -> catname );
2175
2176     // and also the parent cat..
2177     if ( g_categories [ ui_category ] -> parent_catname ) {
2178       pnd_conf_set_char ( g_conf, "minimenu.last_known_parentcatname", g_categories [ ui_category ] -> parent_catname );
2179     } else {
2180       char *kv = pnd_box_find_by_key ( g_conf, "minimenu.last_known_parentcatname" );
2181       if ( kv ) {
2182         pnd_box_delete_node ( g_conf, kv );
2183       }
2184
2185     }
2186   }
2187
2188   // cache last known cat/app to /tmp, so we can use it again later
2189   conf_write ( g_conf, CONF_PREF_TEMPPATH );
2190
2191   // was this icon generated from filesystem, or from pnd-file?
2192   if ( ui_selected -> ref -> object_flags & PND_DISCO_GENERATED ) {
2193
2194     if ( ! ui_selected -> ref -> title_en ) {
2195       return; // no filename
2196     }
2197
2198     if ( ui_selected -> ref -> object_type == pnd_object_type_directory ) {
2199
2200       // check if this guy is a dir-browser tab, or is a directory on a pnd tab
2201       if ( ! g_categories [ ui_category] -> fspath ) {
2202         // pnd subcat as dir
2203
2204         // are we already in a subcat? if so, go back to parent; there is no grandparenting or deeper
2205         if ( g_categories [ ui_category ] -> parent_catname ) {
2206           // go back up
2207           ui_push_backup();
2208
2209         } else {
2210           // delve into subcat
2211
2212           // set to first cat!
2213           ui_category = 0;
2214           ui_catshift = 0;
2215
2216           // republish cats .. shoudl just be the one
2217           category_publish ( CFBYNAME, ui_selected -> ref -> object_path );
2218
2219         }
2220
2221         // forget the selection, nolonger applies
2222         ui_selected = NULL;
2223         ui_set_selected ( ui_selected );
2224         // redraw the grid
2225         render_mask |= CHANGED_EVERYTHING;
2226
2227       } else {
2228
2229         // delve up/down the dir tree
2230         if ( strcmp ( ui_selected -> ref -> title_en, ".." ) == 0 ) {
2231           ui_push_backup();
2232
2233         } else {
2234           // go down
2235           char *temp = malloc ( strlen ( g_categories [ ui_category] -> fspath ) + strlen ( ui_selected -> ref -> title_en ) + 1 + 1 );
2236           sprintf ( temp, "%s/%s", g_categories [ ui_category] -> fspath, ui_selected -> ref -> title_en );
2237           free ( g_categories [ ui_category] -> fspath );
2238           g_categories [ ui_category] -> fspath = temp;
2239         }
2240
2241         pnd_log ( pndn_debug, "Cat %s is now in path %s\n", g_categories [ ui_category ] -> catname, g_categories [ ui_category ]-> fspath );
2242
2243         // forget the selection, nolonger applies
2244         ui_selected = NULL;
2245         ui_set_selected ( ui_selected );
2246         // rescan the dir
2247         category_fs_restock ( g_categories [ ui_category ] );
2248         // redraw the grid
2249         render_mask |= CHANGED_SELECTION;
2250
2251       } // directory browser or pnd subcat?
2252
2253     } else {
2254       // just run it arbitrarily?
2255
2256       // if this a pnd-file, or just some executable?
2257       if ( strcasestr ( ui_selected -> ref -> object_filename, PND_PACKAGE_FILEEXT ) ) {
2258         // looks like a pnd, now what do we do..
2259         pnd_box_handle h = pnd_disco_file ( ui_selected -> ref -> object_path, ui_selected -> ref -> object_filename );
2260
2261         if ( h ) {
2262           pnd_disco_t *d = pnd_box_get_head ( h );
2263           pnd_apps_exec_disco ( pnd_run_script, d, PND_EXEC_OPTION_NORUN, NULL );
2264           char buffer [ PATH_MAX ];
2265           sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
2266           if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
2267             emit_and_quit ( buffer );
2268           } else {
2269             emit_and_run ( buffer );
2270           }
2271         }
2272
2273       } else {
2274         // random bin file
2275
2276         // is it even executable? if we don't have handlers for non-executables yet (Jan 2011 we don't),
2277         // then don't even try to run things not-flagged as executable.. but wait most people are on
2278         // FAT filesystems, what a drag, we can't tell at the fs level.
2279         // ... but we can still invoke 'file' and grep out the good bits, at least.
2280         //
2281         // open a stream reading 'file /path/to/file' and check output for 'executable'
2282         // -- not checking for "ARM" so it can pick up x86 (or whatever native) executables in build environment
2283         unsigned char is_executable = 0;
2284
2285         // popen test
2286         {
2287           char popenbuf [ FILENAME_MAX ];
2288           snprintf ( popenbuf, FILENAME_MAX, "%s %s/%s", MIMETYPE_EXE, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
2289
2290           FILE *marceau;
2291           if ( ! ( marceau = popen ( popenbuf, "r" ) ) ) {
2292             return; // error, we need some useful error handling and dialog boxes here
2293           }
2294
2295           if ( fgets ( popenbuf, FILENAME_MAX, marceau ) ) {
2296             //printf ( "File test returns: %s\n", popenbuf );
2297             if ( strstr ( popenbuf, "executable" ) != NULL ) {
2298               is_executable = 1;
2299             }
2300           }
2301
2302           pclose ( marceau );
2303
2304         } // popen test
2305
2306         if ( ! is_executable ) {
2307           fprintf ( stderr, "ERROR: File to invoke is not executable, skipping. (%s)\n", ui_selected -> ref -> title_en );
2308           return; // need some error handling here
2309         }
2310
2311 #if 0 // eat up any pending SDL events and toss 'em?
2312         {
2313           SDL_PumpEvents();
2314           SDL_Event e;
2315           while ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_ALLEVENTS ) > 0 ) {
2316             // spin
2317           }
2318         }
2319 #endif
2320
2321 #if 1
2322         // just exec it
2323         //
2324
2325         // get CWD so we can restore it on return
2326         char cwd [ PATH_MAX ];
2327         getcwd ( cwd, PATH_MAX );
2328
2329         // full path to executable so we don't rely on implicit "./"
2330         char execbuf [ FILENAME_MAX ];
2331         snprintf ( execbuf, FILENAME_MAX, "%s/%s", g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
2332
2333         // do it!
2334         chdir ( g_categories [ ui_category ] -> fspath );
2335         exec_raw_binary ( execbuf /*ui_selected -> ref -> title_en*/ );
2336         chdir ( cwd );
2337 #else
2338         // DEPRECATED / NOT TESTED
2339         // get mmwrapper to run it
2340         char buffer [ PATH_MAX ];
2341         sprintf ( buffer, "%s %s/%s\n", MM_RUN, g_categories [ ui_category ] -> fspath, ui_selected -> ref -> title_en );
2342         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
2343           emit_and_quit ( buffer );
2344         } else {
2345           emit_and_run ( buffer );
2346         }
2347 #endif
2348       } // pnd or bin?
2349
2350     } // dir or file?
2351
2352   } else {
2353
2354     // set app-run speed
2355     int use_run_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.use_run_speed", 0 );
2356     if ( use_run_speed > 0 ) {
2357       int mm_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.run_speed", -1 );
2358       if ( mm_speed > -1 ) {
2359         char buffer [ 512 ];
2360         snprintf ( buffer, 500, "sudo /usr/pandora/scripts/op_cpuspeed.sh %d", mm_speed );
2361         system ( buffer );
2362       }
2363     } // do speed change?
2364
2365     // request app to run and quit mmenu
2366     pnd_apps_exec_disco ( pnd_run_script, ui_selected -> ref, PND_EXEC_OPTION_NORUN, NULL );
2367     char buffer [ PATH_MAX ];
2368     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
2369     if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
2370       emit_and_quit ( buffer );
2371     } else {
2372       emit_and_run ( buffer );
2373     }
2374   }
2375
2376   return;
2377 }
2378
2379 void ui_push_ltrigger ( void ) {
2380   unsigned char oldcat = ui_category;
2381   unsigned int screen_width = ui_display_context.screen_width;
2382   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2383
2384   if ( g_categorycount == 0 ) {
2385     return;
2386   }
2387
2388   if ( g_icon_thread_busy ) {
2389     ui_stop_defered_icon_thread();
2390   }
2391
2392   if ( ui_category > 0 ) {
2393     ui_category--;
2394     category_fs_restock ( g_categories [ ui_category ] );
2395   } else {
2396     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
2397       ui_category = g_categorycount - 1;
2398       ui_catshift = 0;
2399       if ( ui_category >= ( screen_width / tab_width ) ) {
2400         ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
2401       }
2402       category_fs_restock ( g_categories [ ui_category ] );
2403     }
2404   }
2405
2406   if ( oldcat != ui_category ) {
2407     ui_selected = NULL;
2408     ui_set_selected ( ui_selected );
2409   }
2410
2411   // make tab visible?
2412   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
2413     ui_catshift--;
2414   }
2415
2416   // unscroll
2417   ui_rows_scrolled_down = 0;
2418
2419   render_mask |= CHANGED_CATEGORY;
2420   ui_start_defered_icon_thread();
2421
2422   return;
2423 }
2424
2425 void ui_push_rtrigger ( void ) {
2426   unsigned char oldcat = ui_category;
2427
2428   if ( g_categorycount == 0 ) {
2429     return;
2430   }
2431
2432   if ( g_icon_thread_busy ) {
2433     ui_stop_defered_icon_thread();
2434   }
2435
2436   unsigned int screen_width = ui_display_context.screen_width;
2437   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2438
2439   if ( ui_category < ( g_categorycount - 1 ) ) {
2440     ui_category++;
2441     category_fs_restock ( g_categories [ ui_category ] );
2442   } else {
2443     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
2444       ui_category = 0;
2445       ui_catshift = 0;
2446       category_fs_restock ( g_categories [ ui_category ] );
2447     }
2448   }
2449
2450   if ( oldcat != ui_category ) {
2451     ui_selected = NULL;
2452     ui_set_selected ( ui_selected );
2453   }
2454
2455   // make tab visible?
2456   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
2457     ui_catshift++;
2458   }
2459
2460   // unscroll
2461   ui_rows_scrolled_down = 0;
2462
2463   render_mask |= CHANGED_CATEGORY;
2464   ui_start_defered_icon_thread();
2465
2466   return;
2467 }
2468
2469 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
2470   double scale = 1000000.0;
2471   double scalex = 1000000.0;
2472   double scaley = 1000000.0;
2473   SDL_Surface *scaled;
2474
2475   if ( maxwidth != -1 && maxwidth > ui_display_context.screen_width )
2476     // probably bad config
2477     return ( s );
2478
2479   scalex = (double)maxwidth / (double)s -> w;
2480
2481   if ( maxheight == -1 ) {
2482     scale = scalex;
2483   } else {
2484     scaley = (double)maxheight / (double)s -> h;
2485
2486     if ( scaley < scalex ) {
2487       scale = scaley;
2488     } else {
2489       scale = scalex;
2490     }
2491
2492   }
2493
2494   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
2495   SDL_FreeSurface ( s );
2496   s = scaled;
2497
2498   return ( s );
2499 }
2500
2501 void ui_loadscreen ( void ) {
2502
2503   SDL_Rect dest;
2504
2505   // clear the screen
2506   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2507
2508   // render text
2509   SDL_Surface *rtext;
2510   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", ui_display_context.fontcolor );
2511   dest.x = 20;
2512   dest.y = 20;
2513   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2514   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2515   SDL_FreeSurface ( rtext );
2516
2517   return;
2518 }
2519
2520 void ui_discoverscreen ( unsigned char clearscreen ) {
2521
2522   SDL_Rect dest;
2523
2524   // clear the screen
2525   if ( clearscreen ) {
2526     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2527
2528     // render background
2529     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2530       dest.x = 0;
2531       dest.y = 0;
2532       dest.w = sdl_realscreen -> w;
2533       dest.h = sdl_realscreen -> h;
2534       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2535       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2536     }
2537
2538   }
2539
2540   // render text
2541   SDL_Surface *rtext;
2542   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", ui_display_context.fontcolor );
2543   if ( clearscreen ) {
2544     dest.x = 20;
2545     dest.y = 20;
2546   } else {
2547     dest.x = 20;
2548     dest.y = 40;
2549   }
2550   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2551   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2552   SDL_FreeSurface ( rtext );
2553
2554   // render icon
2555   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2556     dest.x = rtext -> w + 30;
2557     dest.y = 20;
2558     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
2559     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2560   }
2561
2562   return;
2563 }
2564
2565 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
2566
2567   SDL_Rect rects [ 4 ];
2568   SDL_Rect *dest = rects;
2569   SDL_Rect src;
2570   bzero ( dest, sizeof(SDL_Rect)* 4 );
2571
2572   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
2573   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
2574   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
2575   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
2576
2577   static unsigned int stepx = 0;
2578
2579   // clear the screen
2580   if ( clearscreen ) {
2581     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2582
2583     // render background
2584     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2585       dest -> x = 0;
2586       dest -> y = 0;
2587       dest -> w = sdl_realscreen -> w;
2588       dest -> h = sdl_realscreen -> h;
2589       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2590       dest++;
2591     }
2592
2593   } else {
2594
2595     // render background
2596     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2597       src.x = 0;
2598       src.y = 0;
2599       src.w = sdl_realscreen -> w;
2600       src.h = 100;
2601       dest -> x = 0;
2602       dest -> y = 0;
2603       dest -> w = sdl_realscreen -> w;
2604       dest -> h = sdl_realscreen -> h;
2605       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
2606       dest++;
2607     }
2608
2609   } // clear it
2610
2611   // render text
2612   SDL_Surface *rtext;
2613   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
2614   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
2615   dest -> x = 20;
2616   dest -> y = 20;
2617   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2618   SDL_FreeSurface ( rtext );
2619   dest++;
2620
2621   // render icon
2622   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2623     dest -> x = rtext -> w + 30 + stepx;
2624     dest -> y = 20;
2625     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
2626     dest++;
2627   }
2628
2629   // filename
2630   if ( filename ) {
2631     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
2632     dest -> x = 20;
2633     dest -> y = 50;
2634     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2635     SDL_FreeSurface ( rtext );
2636     dest++;
2637   }
2638
2639   // move across
2640   stepx += 20;
2641
2642   if ( stepx > 350 ) {
2643     stepx = 0;
2644   }
2645
2646   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2647
2648   return;
2649 }
2650
2651 int ui_selected_index ( void ) {
2652
2653   if ( ! ui_selected ) {
2654     return ( -1 ); // no index
2655   }
2656
2657   mm_appref_t *r = g_categories [ ui_category ] -> refs;
2658   int counter = 0;
2659   while ( r ) {
2660     if ( r == ui_selected ) {
2661       return ( counter );
2662     }
2663     r = r -> next;
2664     counter++;
2665   }
2666
2667   return ( -1 );
2668 }
2669
2670 static mm_appref_t *timer_ref = NULL;
2671 void ui_set_selected ( mm_appref_t *r ) {
2672
2673   render_mask |= CHANGED_SELECTION;
2674
2675   // preview pic stuff
2676   //
2677
2678   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
2679     return; // no desire to defer anything
2680   }
2681
2682   if ( ! r ) {
2683     // cancel timer
2684     SDL_SetTimer ( 0, NULL );
2685     timer_ref = NULL;
2686     return;
2687   }
2688
2689   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
2690   timer_ref = r;
2691
2692   return;
2693 }
2694
2695 unsigned int ui_callback_f ( unsigned int t ) {
2696
2697   if ( ui_selected != timer_ref ) {
2698     return ( 0 ); // user has moved it, who cares
2699   }
2700
2701   SDL_Event e;
2702   bzero ( &e, sizeof(SDL_Event) );
2703   e.type = SDL_USEREVENT;
2704   e.user.code = sdl_user_ticker;
2705   SDL_PushEvent ( &e );
2706
2707   return ( 0 );
2708 }
2709
2710 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
2711   SDL_Rect rects [ 40 ];
2712   SDL_Rect *dest = rects;
2713   SDL_Rect src;
2714   SDL_Surface *rtext;
2715   unsigned char max_visible = pnd_conf_get_as_int_d ( g_conf, "detailtext.max_visible" , 11 );
2716   unsigned char first_visible = 0;
2717
2718   bzero ( rects, sizeof(SDL_Rect) * 40 );
2719
2720   unsigned int sel = 0;
2721
2722   SDL_Color selfontcolor = { 0/*font_rgba_r*/, ui_display_context.font_rgba_g, ui_display_context.font_rgba_b, ui_display_context.font_rgba_a };
2723
2724   unsigned int i;
2725   SDL_Event event;
2726
2727   while ( 1 ) {
2728
2729     // clear
2730     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2731     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2732     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
2733     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
2734     SDL_FillRect( sdl_realscreen, dest, 0 );
2735
2736     // show dialog background
2737     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
2738       src.x = 0;
2739       src.y = 0;
2740       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
2741       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
2742       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2743       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2744       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2745       // repeat for darken?
2746       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2747       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2748       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2749       dest++;
2750     }
2751
2752     // show dialog frame
2753     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
2754       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2755       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2756       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
2757       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2758       dest++;
2759     }
2760
2761     // show header
2762     if ( title ) {
2763       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
2764       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2765       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
2766       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2767       SDL_FreeSurface ( rtext );
2768       dest++;
2769     }
2770
2771     // show footer
2772     if ( footer ) {
2773       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
2774       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2775       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
2776         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
2777         - 60;
2778       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2779       SDL_FreeSurface ( rtext );
2780       dest++;
2781     }
2782
2783     // show options
2784     for ( i = first_visible; i < first_visible + max_visible && i < argc; i++ ) {
2785
2786       // show options
2787       if ( sel == i ) {
2788         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
2789       } else {
2790         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], ui_display_context.fontcolor );
2791       }
2792       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2793       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 - first_visible ) );
2794       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2795       SDL_FreeSurface ( rtext );
2796       dest++;
2797
2798     } // for
2799
2800     // update all the rects and send it all to sdl
2801     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2802     dest = rects;
2803
2804     // check for input
2805     while ( SDL_WaitEvent ( &event ) ) {
2806
2807       switch ( event.type ) {
2808
2809       //case SDL_KEYUP:
2810       case SDL_KEYDOWN:
2811
2812         if ( event.key.keysym.sym == SDLK_UP ) {
2813           if ( sel ) {
2814             sel--;
2815
2816             if ( sel < first_visible ) {
2817               first_visible--;
2818             }
2819
2820           }
2821         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
2822
2823           if ( sel < argc - 1 ) {
2824             sel++;
2825
2826             // ensure visibility
2827             if ( sel >= first_visible + max_visible ) {
2828               first_visible++;
2829             }
2830
2831           }
2832
2833         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
2834           return ( sel );
2835
2836 #if 0
2837         } else if ( event.key.keysym.sym == SDLK_q ) {
2838           exit ( 0 );
2839 #endif
2840
2841         } else {
2842           return ( -1 ); // nada
2843         }
2844
2845         break;
2846
2847       } // switch
2848
2849       break;
2850     } // while
2851
2852   } // while
2853
2854   return ( -1 );
2855 }
2856
2857 unsigned char ui_determine_row ( mm_appref_t *a ) {
2858   unsigned int row = 0;
2859
2860   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2861   while ( i != a ) {
2862     i = i -> next;
2863     row++;
2864   } // while
2865   row /= ui_display_context.col_max;
2866
2867   return ( row );
2868 }
2869
2870 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
2871   return ( ui_determine_row ( a ) % ui_display_context.row_max );
2872 }
2873
2874 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
2875   unsigned int col = 0;
2876
2877   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2878   while ( i != a ) {
2879     i = i -> next;
2880     col++;
2881   } // while
2882   col %= ui_display_context.col_max;
2883
2884   return ( col );
2885 }
2886
2887 unsigned char ui_show_info ( char *pndrun, pnd_disco_t *p ) {
2888   char *viewer, *searchpath;
2889   pnd_conf_handle desktoph;
2890
2891   // viewer
2892   searchpath = pnd_conf_query_searchpath();
2893
2894   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
2895
2896   if ( ! desktoph ) {
2897     return ( 0 );
2898   }
2899
2900   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
2901
2902   if ( ! viewer ) {
2903     return ( 0 ); // no way to view the file
2904   }
2905
2906   // etc
2907   if ( ! p -> unique_id ) {
2908     return ( 0 );
2909   }
2910
2911   if ( ! p -> info_filename ) {
2912     return ( 0 );
2913   }
2914
2915   if ( ! p -> info_name ) {
2916     return ( 0 );
2917   }
2918
2919   if ( ! pndrun ) {
2920     return ( 0 );
2921   }
2922
2923   // exec line
2924   char args [ 1001 ];
2925   char *pargs = args;
2926   if ( pnd_conf_get_as_char ( desktoph, "info.viewer_args" ) ) {
2927     snprintf ( pargs, 1001, "%s %s",
2928                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
2929   } else {
2930     pargs = NULL;
2931   }
2932
2933   char pndfile [ 1024 ];
2934   if ( p -> object_type == pnd_object_type_directory ) {
2935     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
2936     strncpy ( pndfile, p -> object_path, 1000 );
2937   } else if ( p -> object_type == pnd_object_type_pnd ) {
2938     // pnd_run.sh wants the full path and filename for the .pnd file
2939     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
2940   }
2941
2942   if ( ! pnd_apps_exec ( pndrun, pndfile, p -> unique_id, viewer, p -> startdir, pargs,
2943                          p -> clockspeed ? atoi ( p -> clockspeed ) : 0, PND_EXEC_OPTION_NORUN ) )
2944   {
2945     return ( 0 );
2946   }
2947
2948   pnd_log ( pndn_debug, "Info Exec=%s\n", pnd_apps_exec_runline() );
2949
2950   // try running it
2951   int x;
2952   if ( ( x = fork() ) < 0 ) {
2953     pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
2954     return ( 0 );
2955   }
2956
2957   if ( x == 0 ) {
2958     execl ( "/bin/sh", "/bin/sh", "-c", pnd_apps_exec_runline(), (char*)NULL );
2959     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", pnd_apps_exec_runline() );
2960     return ( 0 );
2961   }
2962
2963   return ( 1 );
2964 }
2965
2966 typedef struct {
2967   SDL_Rect r;
2968   int catnum;
2969   mm_appref_t *ref;
2970 } ui_touch_t;
2971 #define MAXTOUCH 100
2972 ui_touch_t ui_touchrects [ MAXTOUCH ];
2973 unsigned char ui_touchrect_count = 0;
2974
2975 void ui_register_reset ( void ) {
2976   bzero ( ui_touchrects, sizeof(ui_touch_t)*MAXTOUCH );
2977   ui_touchrect_count = 0;
2978   return;
2979 }
2980
2981 void ui_register_tab ( unsigned char catnum, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2982
2983   if ( ui_touchrect_count == MAXTOUCH ) {
2984     return;
2985   }
2986
2987   ui_touchrects [ ui_touchrect_count ].r.x = x;
2988   ui_touchrects [ ui_touchrect_count ].r.y = y;
2989   ui_touchrects [ ui_touchrect_count ].r.w = w;
2990   ui_touchrects [ ui_touchrect_count ].r.h = h;
2991   ui_touchrects [ ui_touchrect_count ].catnum = catnum;
2992   ui_touchrect_count++;
2993
2994   return;
2995 }
2996
2997 void ui_register_app ( mm_appref_t *app, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2998
2999   if ( ui_touchrect_count == MAXTOUCH ) {
3000     return;
3001   }
3002
3003   ui_touchrects [ ui_touchrect_count ].r.x = x;
3004   ui_touchrects [ ui_touchrect_count ].r.y = y;
3005   ui_touchrects [ ui_touchrect_count ].r.w = w;
3006   ui_touchrects [ ui_touchrect_count ].r.h = h;
3007   ui_touchrects [ ui_touchrect_count ].ref = app;
3008   ui_touchrect_count++;
3009
3010   return;
3011 }
3012
3013 void ui_touch_act ( unsigned int x, unsigned int y ) {
3014
3015   unsigned char i;
3016   ui_touch_t *t;
3017
3018   for ( i = 0; i < ui_touchrect_count; i++ ) {
3019     t = &(ui_touchrects [ i ]);
3020
3021     if ( x >= t -> r.x &&
3022          x <= t -> r.x + t -> r.w &&
3023          y >= t -> r.y &&
3024          y <= t -> r.y + t -> r.h
3025        )
3026     {
3027
3028       if ( t -> ref ) {
3029         ui_selected = t -> ref;
3030         ui_push_exec();
3031       } else {
3032         if ( ui_category != t -> catnum ) {
3033           ui_selected = NULL;
3034         }
3035         ui_category = t -> catnum;
3036         render_mask |= CHANGED_CATEGORY;
3037         // rescan the dir
3038         category_fs_restock ( g_categories [ ui_category ] );
3039         ui_start_defered_icon_thread();
3040       }
3041
3042       break;
3043     }
3044
3045   } // for
3046
3047   return;
3048 }
3049
3050 unsigned char ui_forkexec ( char *argv[] ) {
3051   char *fooby = argv[0];
3052   int x;
3053
3054   if ( ( x = fork() ) < 0 ) {
3055     pnd_log ( pndn_error, "ERROR: Couldn't fork() for '%s'\n", fooby );
3056     return ( 0 );
3057   }
3058
3059   if ( x == 0 ) { // child
3060     execv ( fooby, argv );
3061     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", fooby );
3062     return ( 0 );
3063   }
3064
3065   // parent, success
3066   return ( 1 );
3067 }
3068
3069 unsigned char ui_threaded_timer_create ( void ) {
3070
3071   g_timer_thread = SDL_CreateThread ( (void*)ui_threaded_timer, NULL );
3072
3073   if ( ! g_timer_thread ) {
3074     pnd_log ( pndn_error, "ERROR: Couldn't create timer thread\n" );
3075     return ( 0 );
3076   }
3077
3078   return ( 1 );
3079 }
3080
3081 int ui_threaded_timer ( pnd_disco_t *p ) {
3082
3083   // this timer's job is to ..
3084   // - do nothing for quite awhile
3085   // - on wake, post event to SDL event queue, so that main thread will check if SD insert/eject occurred
3086   // - goto 10
3087
3088   unsigned int delay_s = 2; // seconds
3089
3090   while ( 1 ) {
3091
3092     // pause...
3093     //sleep ( delay_s );
3094     SDL_Delay ( delay_s * 1000 );
3095
3096     // .. trigger SD check
3097     SDL_Event e;
3098     bzero ( &e, sizeof(SDL_Event) );
3099     e.type = SDL_USEREVENT;
3100     e.user.code = sdl_user_checksd;
3101     SDL_PushEvent ( &e );
3102
3103   } // while
3104
3105   return ( 0 );
3106 }
3107
3108 unsigned char ui_threaded_defered_preview ( pnd_disco_t *p ) {
3109
3110   if ( ! cache_preview ( p, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
3111                          pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
3112      )
3113   {
3114     pnd_log ( pndn_debug, "THREAD: Couldn't load preview pic: '%s' -> '%s'\n",
3115               IFNULL(p->title_en,"No Name"), p -> preview_pic1 );
3116   }
3117
3118   // trigger that we completed
3119   SDL_Event e;
3120   bzero ( &e, sizeof(SDL_Event) );
3121   e.type = SDL_USEREVENT;
3122   e.user.code = sdl_user_finishedpreview;
3123   e.user.data1 = p;
3124   SDL_PushEvent ( &e );
3125
3126   return ( 0 );
3127 }
3128
3129 void ui_post_scan ( void ) {
3130
3131   // reset view
3132   ui_selected = NULL;
3133   ui_rows_scrolled_down = 0;
3134   // set back to first tab, to be safe
3135   ui_category = 0;
3136   ui_catshift = 0;
3137
3138   // do we have a preferred category to jump to? or a last known one?
3139   char *dc = pnd_conf_get_as_char ( g_conf, "categories.default_cat" );
3140   char *lastcat = pnd_conf_get_as_char ( g_conf, "minimenu.last_known_catname" );
3141   if ( ( dc ) ||
3142        ( pnd_conf_get_as_int_d ( g_conf, "minimenu.start_selected", 0 ) && lastcat ) )
3143   {
3144     char *catpick = NULL;
3145
3146     if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.start_selected", 0 ) && lastcat ) {
3147       catpick = lastcat;
3148
3149       // if this is a subcat, we have some doctoring to do :/ the hackishness is really
3150       // starting to show..
3151       if ( pnd_conf_get_as_char ( g_conf, "minimenu.last_known_parentcatname" ) ) {
3152         // in subcat view, we only have one cat
3153         ui_category = 0;
3154         ui_catshift = 0;
3155         // the cat name to search for is Child*Parent
3156         char key [ 512 ];
3157         sprintf ( key, "%s*%s",
3158                   pnd_conf_get_as_char ( g_conf, "minimenu.last_known_catname" )
3159                   , pnd_conf_get_as_char ( g_conf, "minimenu.last_known_parentcatname" ) );
3160         category_publish ( CFBYNAME, key );
3161         // since we forced it by hand, no need to do a cat-scan below
3162         catpick = NULL;
3163       }
3164
3165     } else if ( dc ) {
3166       catpick = dc;
3167     }
3168
3169     // attempt to find default cat; if we do find it, select it; otherwise
3170     // default behaviour will pick first cat (ie: usually All)
3171     if ( catpick ) {
3172       unsigned int i;
3173
3174       for ( i = 0; i < g_categorycount; i++ ) {
3175         if ( strcasecmp ( g_categories [ i ] -> catname, catpick ) == 0 ) {
3176           ui_category = i;
3177           // ensure visibility
3178           unsigned int screen_width = ui_display_context.screen_width;
3179           unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
3180           if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
3181             ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
3182           }
3183           break;
3184         }
3185       }
3186
3187       if ( i == g_categorycount ) {
3188         pnd_log ( pndn_warning, "  User defined default category or last known cat '%s' but not found, so using default behaviour\n", catpick );
3189       }
3190
3191     } // cat change?
3192
3193   } // default cat
3194
3195   // if we're sent right to a dirbrowser tab, restock it now (normally we restock on entry)
3196   if ( g_categories [ ui_category ] && g_categories [ ui_category ] -> fspath ) {
3197     printf ( "Restock on start: '%s'\n", g_categories [ ui_category ] -> fspath );
3198     category_fs_restock ( g_categories [ ui_category ] );
3199   }
3200
3201   // redraw all
3202   render_mask |= CHANGED_EVERYTHING;
3203
3204   // if deferred icon load, kick off the thread now
3205   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) == 1 ) {
3206     ui_start_defered_icon_thread();
3207   } // deferred icon load
3208
3209   return;
3210 }
3211
3212 unsigned char ui_threaded_defered_icon ( void *p ) {
3213   extern pnd_box_handle g_active_apps;
3214
3215   unsigned char maxwidth, maxheight;
3216   maxwidth = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 );
3217   maxheight = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 );
3218
3219   pnd_disco_t *iter;
3220
3221   g_icon_thread_busy = 1;
3222
3223   // work at it in order within current category
3224
3225   mm_appref_t *refiter = g_categories [ ui_category ] ? g_categories [ ui_category ] -> refs : NULL;
3226   while ( refiter && ! g_icon_thread_stop ) {
3227     iter = refiter -> ref;
3228
3229     // has an icon that is not already cached?
3230     if ( ( iter -> pnd_icon_pos ) ||
3231          ( iter -> icon && iter -> object_flags & PND_DISCO_LIBPND_DD )
3232        )
3233     {
3234   
3235       // try to cache it?
3236       if ( ! cache_icon ( iter, maxwidth, maxheight ) ) {
3237         //pnd_log ( pndn_warning, "  Couldn't load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
3238
3239       } else {
3240
3241         // trigger that we completed
3242         SDL_Event e;
3243         bzero ( &e, sizeof(SDL_Event) );
3244         e.type = SDL_USEREVENT;
3245         e.user.code = sdl_user_finishedicon;
3246         SDL_PushEvent ( &e );
3247
3248         //pnd_log ( pndn_warning, "  Finished deferred load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
3249         //usleep ( pnd_conf_get_as_int_d ( g_conf, "minimenu.defer_icon_us", 50000 ) );
3250
3251       }
3252
3253       // avoid churn
3254       iter -> pnd_icon_pos = 0;
3255       if ( iter -> icon ) {
3256         free ( iter -> icon );
3257         iter -> icon = NULL;
3258       }
3259
3260       // let user do something..
3261       SDL_Delay ( 200 );
3262
3263     } // has icon
3264
3265     refiter = refiter -> next;
3266   }
3267
3268   // mark as done
3269   g_icon_thread_busy = 0;
3270
3271   return ( 0 );
3272 }
3273
3274 void ui_show_hourglass ( unsigned char updaterect ) {
3275
3276   SDL_Rect dest;
3277   SDL_Surface *s = g_imagecache [ IMG_HOURGLASS ].i;
3278
3279   dest.x = ( 800 - s -> w ) / 2;
3280   dest.y = ( 480 - s -> h ) / 2;
3281
3282   SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, &dest );
3283
3284   if ( updaterect ) {
3285     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
3286   }
3287
3288   return;
3289 }
3290
3291 unsigned char ui_pick_skin ( void ) {
3292 #define MAXSKINS 10
3293   char *skins [ MAXSKINS ];
3294   unsigned char iter;
3295
3296   char *searchpath = pnd_conf_get_as_char ( g_conf, "minimenu.skin_searchpath" );
3297   char tempname [ 100 ];
3298
3299   iter = 0;
3300
3301   skins [ iter++ ] = "No skin change";
3302
3303   SEARCHPATH_PRE
3304   {
3305     DIR *d = opendir ( buffer );
3306
3307     if ( d ) {
3308       struct dirent *dd;
3309
3310       while ( ( dd = readdir ( d ) ) ) {
3311
3312         if ( dd -> d_name [ 0 ] == '.' ) {
3313           // ignore
3314         } else if ( ( dd -> d_type == DT_DIR || dd -> d_type == DT_UNKNOWN ) &&
3315                     iter < MAXSKINS )
3316         {
3317           snprintf ( tempname, 100, "Skin: %s", dd -> d_name );
3318           skins [ iter++ ] = strdup ( tempname );
3319         }
3320
3321       }
3322
3323       closedir ( d );
3324     }
3325
3326   }
3327   SEARCHPATH_POST
3328
3329   int sel = ui_modal_single_menu ( skins, iter, "Skins", "Enter to select; other to return." );
3330
3331   // did they pick one?
3332   if ( sel > 0 ) {
3333     FILE *f;
3334
3335     char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
3336     s = pnd_expand_tilde ( s );
3337
3338     f = fopen ( s, "w" );
3339
3340     free ( s );
3341
3342     if ( f ) {
3343       fprintf ( f, "%s\n", skins [ sel ] + 6 );
3344       fclose ( f );
3345     }
3346
3347     return ( 1 );
3348   }
3349
3350   return ( 0 );
3351 }
3352
3353 void ui_aboutscreen ( char *textpath ) {
3354 #define PIXELW 7
3355 #define PIXELH 7
3356 #define MARGINW 3
3357 #define MARGINH 3
3358 #define SCRW 800
3359 #define SCRH 480
3360 #define ROWS SCRH / ( PIXELH + MARGINH )
3361 #define COLS SCRW / ( PIXELW + MARGINW )
3362
3363   unsigned char pixelboard [ ROWS * COLS ]; // pixel heat
3364   bzero ( pixelboard, ROWS * COLS );
3365
3366   SDL_Surface *rtext;
3367   SDL_Rect r;
3368
3369   SDL_Color rtextc = { 200, 200, 200, 100 };
3370
3371   // pixel scroller
3372   char *textloop [ 500 ];
3373   unsigned int textmax = 0;
3374   bzero ( textloop, 500 * sizeof(char*) );
3375
3376   // cursor scroller
3377   char cbuffer [ 50000 ];
3378   bzero ( cbuffer, 50000 );
3379   unsigned int crevealed = 0;
3380
3381   FILE *f = fopen ( textpath, "r" );
3382
3383   if ( ! f ) {
3384     pnd_log ( pndn_error, "ERROR: Couldn't open about text: %s\n", textpath );
3385     return;
3386   }
3387
3388   char textbuf [ 100 ];
3389   while ( fgets ( textbuf, 100, f ) ) {
3390
3391     // add to full buffer
3392     strncat ( cbuffer, textbuf, 50000 );
3393
3394     // chomp
3395     if ( strchr ( textbuf, '\n' ) ) {
3396       * strchr ( textbuf, '\n' ) = '\0';
3397     }
3398
3399     // add to pixel loop
3400     if ( 1||textbuf [ 0 ] ) {
3401       textloop [ textmax ] = strdup ( textbuf );
3402       textmax++;
3403     }
3404
3405   } // while fgets
3406
3407   fclose ( f );
3408
3409   unsigned int textiter = 0;
3410   while ( textiter < textmax ) {
3411     char *text = textloop [ textiter ];
3412
3413     rtext = NULL;
3414     if ( text [ 0 ] ) {
3415       // render to surface
3416       rtext = TTF_RenderText_Blended ( g_grid_font, text, rtextc );
3417
3418       // render font to pixelboard
3419       unsigned int px, py;
3420       unsigned char *ph;
3421       unsigned int *pixels = rtext -> pixels;
3422       unsigned char cr, cg, cb, ca;
3423       for ( py = 0; py < rtext -> h; py ++ ) {
3424         for ( px = 0; px < ( rtext -> w > COLS ? COLS : rtext -> w ); px++ ) {
3425
3426           SDL_GetRGBA ( pixels [ ( py * rtext -> pitch / 4 ) + px ],
3427                         rtext -> format, &cr, &cg, &cb, &ca );
3428
3429           if ( ca != 0 ) {
3430
3431             ph = pixelboard + ( /*y offset */ 30 * COLS ) + ( py * COLS ) + px /* / 2 */;
3432
3433             if ( *ph < 100 ) {
3434               *ph = 100;
3435             }
3436
3437             ca /= 5;
3438             if ( *ph + ca < 250 ) {
3439               *ph += ca;
3440             }
3441
3442           } // got a pixel?
3443
3444         } // x
3445       } // y
3446
3447     } // got text?
3448
3449     unsigned int runcount = 10;
3450     while ( runcount-- ) {
3451
3452       // clear display
3453       SDL_FillRect( sdl_realscreen, NULL /* whole */, 0 );
3454
3455       // render pixelboard
3456       unsigned int x, y;
3457       unsigned int c;
3458       for ( y = 0; y < ROWS; y++ ) {
3459         for ( x = 0; x < COLS; x++ ) {
3460
3461           if ( 1||pixelboard [ ( y * COLS ) + x ] ) {
3462
3463             // position
3464             r.x = x * ( PIXELW + MARGINW );
3465             r.y = y * ( PIXELH + MARGINH );
3466             r.w = PIXELW;
3467             r.h = PIXELH;
3468             // heat -> colour
3469             c = SDL_MapRGB ( sdl_realscreen -> format, 100 /* r */, 0 /* g */, pixelboard [ ( y * COLS ) + x ] );
3470             // render
3471             SDL_FillRect( sdl_realscreen, &r /* whole */, c );
3472
3473           }
3474
3475         } // x
3476       } // y
3477
3478       // cool pixels
3479       unsigned char *pc = pixelboard;
3480       for ( y = 0; y < ROWS; y++ ) {
3481         for ( x = 0; x < COLS; x++ ) {
3482
3483           if ( *pc > 10 ) {
3484             (*pc) -= 3;
3485           }
3486
3487           pc++;
3488         } // x
3489       } // y
3490
3491       // slide pixels upwards
3492       memmove ( pixelboard, pixelboard + COLS, ( COLS * ROWS ) - COLS );
3493
3494       // render actual readable text
3495       {
3496
3497         // display up to cursor
3498         SDL_Rect dest;
3499         unsigned int cdraw = 0;
3500         SDL_Surface *cs;
3501         char ctb [ 2 ];
3502
3503         if ( crevealed > 200 ) {
3504           cdraw = crevealed - 200;
3505         }
3506
3507         dest.x = 400;
3508         dest.y = 20;
3509
3510         for ( ; cdraw < crevealed; cdraw++ ) {
3511           ctb [ 0 ] = cbuffer [ cdraw ];
3512           ctb [ 1 ] = '\0';
3513           // move over or down
3514           if ( cbuffer [ cdraw ] == '\n' ) {
3515             // EOL
3516             dest.x = 400;
3517             dest.y += 14;
3518
3519             if ( dest.y > 450 ) {
3520               dest.y = 450;
3521             }
3522
3523           } else {
3524             // draw the char
3525             cs = TTF_RenderText_Blended ( g_tab_font, ctb, rtextc );
3526             if ( cs ) {
3527               SDL_BlitSurface ( cs, NULL /* all */, sdl_realscreen, &dest );
3528               SDL_FreeSurface ( cs );
3529               // over
3530               dest.x += cs -> w;
3531             }
3532           }
3533
3534         }
3535
3536         dest.w = 10;
3537         dest.h = 20;
3538         SDL_FillRect ( sdl_realscreen, &dest /* whole */, 220 );
3539
3540         // increment cursor to next character
3541         if ( cbuffer [ crevealed ] != '\0' ) {
3542           crevealed++;
3543         }
3544
3545       } // draw cursor text
3546
3547       // reveal
3548       //
3549       SDL_UpdateRect ( sdl_realscreen, 0, 0, 0, 0 ); // whole screen
3550
3551       usleep ( 50000 );
3552
3553       // any button? if so, about
3554       {
3555         SDL_PumpEvents();
3556
3557         SDL_Event e;
3558
3559         if ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_EVENTMASK(/*SDL_KEYUP|*/SDL_KEYDOWN) ) > 0 ) {
3560           return;
3561         }
3562
3563       }
3564
3565     } // while cooling
3566
3567     if ( rtext ) {
3568       SDL_FreeSurface ( rtext );
3569     }
3570
3571     textiter++;
3572   } // while more text
3573
3574   // free up
3575   unsigned int i;
3576   for ( i = 0; i < textmax; i++ ) {
3577     if ( textloop [ i ] ) {
3578       free ( textloop [ i ] );
3579       textloop [ i ] = 0;
3580     }
3581   }
3582
3583   return;
3584 }
3585
3586 void ui_revealscreen ( void ) {
3587   char *labels [ 500 ];
3588   unsigned int labelmax = 0;
3589   unsigned int i;
3590   char fulllabel [ 200 ];
3591
3592   if ( ! category_count ( CFHIDDEN ) ) {
3593     return; // nothing to do
3594   }
3595
3596   // switch to hidden categories
3597   category_publish ( CFHIDDEN, NULL );
3598
3599   // build up labels to show in menu
3600   for ( i = 0; i < g_categorycount; i++ ) {
3601
3602     if ( g_categories [ i ] -> parent_catname ) {
3603       sprintf ( fulllabel, "%s [%s]", g_categories [ i ] -> catname, g_categories [ i ] -> parent_catname );
3604     } else {
3605       sprintf ( fulllabel, "%s", g_categories [ i ] -> catname );
3606     }
3607
3608     labels [ labelmax++ ] = strdup ( fulllabel );
3609   }
3610
3611   // show menu
3612   int sel = ui_modal_single_menu ( labels, labelmax, "Temporary Category Reveal",
3613                                    "Enter to select; other to return." );
3614
3615   // if selected, try to set this guy to visible
3616   if ( sel >= 0 ) {
3617
3618     // fix up category name, if its been hacked
3619 #if 0 // prepending and .. wtf crap is this
3620     if ( strchr ( g_categories [ sel ] -> catname, '.' ) ) {
3621       char *t = g_categories [ sel ] -> catname;
3622       g_categories [ sel ] -> catname = strdup ( strchr ( g_categories [ sel ] -> catname, '.' ) + 1 );
3623       free ( t );
3624     }
3625 #endif
3626
3627     // reflag this guy to be visible
3628     g_categories [ sel ] -> catflags = CFNORMAL;
3629
3630     // switch to the new category.. cache name.
3631     char *switch_to_name = g_categories [ sel ] -> catname;
3632
3633     // republish categories
3634     category_publish ( CFNORMAL, NULL );
3635
3636     // switch to the new category.. with the cached name!
3637     ui_category = category_index ( switch_to_name );
3638
3639     // ensure visibility
3640     unsigned int screen_width = ui_display_context.screen_width;
3641     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
3642     if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
3643       ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
3644     }
3645
3646   }
3647
3648   // republish categories
3649   category_publish ( CFNORMAL, NULL );
3650
3651   // redraw tabs
3652   render_mask |= CHANGED_CATEGORY;
3653   ui_start_defered_icon_thread();
3654
3655   return;
3656 }
3657
3658 void ui_recache_context ( ui_context_t *c ) {
3659
3660   c -> screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
3661
3662   c -> font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
3663   c -> font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
3664   c -> font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
3665   c -> font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
3666
3667   c -> grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
3668   c -> grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
3669
3670   c -> icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
3671   c -> icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
3672   c -> icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
3673   c -> icon_max_height = pnd_conf_get_as_int ( g_conf, "grid.icon_max_height" );
3674   c -> sel_icon_offset_x = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_x", 0 );
3675   c -> sel_icon_offset_y = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_y", 0 );
3676
3677   c -> text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
3678   c -> text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
3679   c -> text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
3680   c -> text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
3681   c -> text_hilite_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
3682
3683   c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 4 );
3684   c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
3685
3686   c -> cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
3687   c -> cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
3688
3689   c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
3690   c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
3691   c -> arrow_bar_clip_w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
3692   c -> arrow_bar_clip_h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
3693   c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
3694   c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
3695   c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
3696   c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
3697
3698   // font colour
3699   SDL_Color tmp = { c -> font_rgba_r, c -> font_rgba_g, c -> font_rgba_b, c -> font_rgba_a };
3700   c -> fontcolor = tmp;
3701
3702   // determine font height
3703   if ( g_grid_font ) {
3704     SDL_Surface *rtext;
3705     rtext = TTF_RenderText_Blended ( g_grid_font, "M", c -> fontcolor );
3706     c -> text_height = rtext -> h;
3707   } else {
3708     c -> text_height = 10;
3709   }
3710
3711   if ( g_tab_font ) {
3712     SDL_Surface *rtext;
3713     rtext = TTF_RenderText_Blended ( g_tab_font, "M", c -> fontcolor );
3714     c -> text_height_tab = rtext -> h;
3715   } else {
3716     c -> text_height_tab = 15;
3717   }
3718
3719   // now that we've got 'normal' (detail pane shown) param's, lets check if detail pane
3720   // is hidden; if so, override some values with those alternate skin values where possible.
3721   if ( ui_detail_hidden ) {
3722     // if detail panel is hidden, and theme cannot support it, unhide the bloody thing. (This may help
3723     // when someone is amid theme hacking or changing.)
3724     if ( ! ui_is_detail_hideable() ) {
3725       ui_detail_hidden = 0;
3726     }
3727
3728     // still hidden?
3729     if ( ui_detail_hidden ) {
3730
3731       c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max_w", c -> row_max );
3732       c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max_w", c -> col_max );
3733
3734       c -> cell_width = pnd_conf_get_as_int_d ( g_conf, "grid.cell_width_w", c -> cell_width );
3735       c -> cell_height = pnd_conf_get_as_int_d ( g_conf, "grid.cell_height_w", c -> cell_height );
3736
3737       c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x_w", 450 );
3738       c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y_w", 100 );
3739       c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x_w", 450 );
3740       c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y_w", 80 );
3741       c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x_w", 450 );
3742       c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y_w", 80 );
3743
3744     } // if detail hidden.. still.
3745
3746   } // if detail hidden
3747
3748   return;
3749 }
3750
3751 unsigned char ui_is_detail_hideable ( void ) {
3752
3753   // if skin has a bit of info for wide-mode, we assume wide-mode is available
3754   if ( pnd_conf_get_as_char ( g_conf, "grid.row_max_w" ) != NULL ) {
3755     return ( 1 );
3756   }
3757
3758   // else not!
3759   return ( 0 );
3760 }
3761
3762 void ui_toggle_detail_pane ( void ) {
3763
3764   // no bitmask trickery here; I like it to be stand-out obvious at 3am.
3765
3766   if ( ui_detail_hidden ) {
3767     ui_detail_hidden = 0;
3768   } else {
3769     ui_detail_hidden = 1;
3770   }
3771
3772   // repull skin config
3773   ui_recache_context ( &ui_display_context );
3774
3775   // redraw
3776   render_mask |= CHANGED_EVERYTHING;
3777
3778   return;
3779 }
3780
3781 void ui_menu_context ( mm_appref_t *a ) {
3782
3783   unsigned char rescan_apps = 0;
3784   unsigned char context_alive = 1;
3785
3786   enum {
3787     context_done = 0,
3788     context_file_info,
3789     context_file_delete,
3790     context_app_info,
3791     context_app_hide,
3792     context_app_recategorize,
3793     context_app_recategorize_sub,
3794     context_app_rename,
3795     context_app_cpuspeed,
3796     context_app_run,
3797     context_app_notes1,
3798     context_app_notes2,
3799     context_app_notes3,
3800     context_menu_max
3801   };
3802
3803   char *verbiage[] = {
3804     "Done (return to grid)",      // context_done
3805     "Get info about file/dir",    // context_file_info
3806     "Delete file/dir",            // context_file_delete
3807     "Get info",                   // context_app_info
3808     "Hide application",           //             hide
3809     "Recategorize",               //             recategorize
3810     "Recategorize subcategory",   //             recategorize
3811     "Change displayed title",     //             rename
3812     "Set CPU speed for launch",   //             cpuspeed
3813     "Run application",            //             run
3814     "Edit notes line 1",          //             notes1
3815     "Edit notes line 2",          //             notes2
3816     "Edit notes line 3",          //             notes3
3817   };
3818
3819   unsigned short int menu [ context_menu_max ];
3820   char *menustring [ context_menu_max ];
3821   unsigned char menumax = 0;
3822
3823   // ++ done
3824   menu [ menumax ] = context_done; menustring [ menumax++ ] = verbiage [ context_done ];
3825
3826   // hook up appropriate menu options based on tab-type and object-type
3827   if ( g_categories [ ui_category ] -> fspath ) {
3828     return; // TBD
3829     menu [ menumax ] = context_file_info; menustring [ menumax++ ] = verbiage [ context_file_info ];
3830     menu [ menumax ] = context_file_delete; menustring [ menumax++ ] = verbiage [ context_file_delete ];
3831   } else {
3832
3833     if ( a -> ref -> object_type == pnd_object_type_directory ) {
3834       return; // don't do anything if the guy is a subcat-as-folder
3835     }
3836
3837     //menu [ menumax ] = context_app_info; menustring [ menumax++ ] = verbiage [ context_app_info ];
3838     menu [ menumax ] = context_app_run; menustring [ menumax++ ] = verbiage [ context_app_run ];
3839     menu [ menumax ] = context_app_hide; menustring [ menumax++ ] = verbiage [ context_app_hide ];
3840     menu [ menumax ] = context_app_recategorize; menustring [ menumax++ ] = verbiage [ context_app_recategorize ];
3841     menu [ menumax ] = context_app_recategorize_sub; menustring [ menumax++ ] = verbiage [ context_app_recategorize_sub ];
3842     menu [ menumax ] = context_app_rename; menustring [ menumax++ ] = verbiage [ context_app_rename ];
3843     menu [ menumax ] = context_app_cpuspeed; menustring [ menumax++ ] = verbiage [ context_app_cpuspeed ];
3844     menu [ menumax ] = context_app_notes1; menustring [ menumax++ ] = verbiage [ context_app_notes1 ];
3845     menu [ menumax ] = context_app_notes2; menustring [ menumax++ ] = verbiage [ context_app_notes2 ];
3846     menu [ menumax ] = context_app_notes3; menustring [ menumax++ ] = verbiage [ context_app_notes3 ];
3847   }
3848
3849   // operate the menu
3850   while ( context_alive ) {
3851
3852     int sel = ui_modal_single_menu ( menustring, menumax, a -> ref -> title_en ? a -> ref -> title_en : "Quickpick Menu" /* title */, "B or Enter; other to cancel." /* footer */ );
3853
3854     if ( sel < 0 ) {
3855       context_alive = 0;
3856
3857     } else {
3858
3859       switch ( menu [ sel ] ) {
3860
3861       case context_done:
3862         context_alive = 0;
3863         break;
3864
3865       case context_file_info:
3866         break;
3867
3868       case context_file_delete:
3869         //ui_menu_twoby ( "Delete - Are you sure?", "B/enter; other to cancel", "Delete", "Do not delete" );
3870         break;
3871
3872       case context_app_info:
3873         break;
3874
3875       case context_app_hide:
3876         {
3877           // determine key
3878           char confkey [ 1000 ];
3879           snprintf ( confkey, 999, "%s.%s", "appshow", a -> ref -> unique_id );
3880
3881           // turn app 'off'
3882           pnd_conf_set_char ( g_conf, confkey, "0" );
3883
3884           // write conf, so it will take next time
3885           conf_write ( g_conf, conf_determine_location ( g_conf ) );
3886           conf_write ( g_conf, CONF_PREF_TEMPPATH );
3887
3888           // can we just 'hide' this guy without reloading all apps? (this is for you, EvilDragon)
3889           if ( 0 ) {
3890             //
3891             // DOESN'T WORK YET; other parts of app are still hanging onto some values and blow up
3892             //
3893             char *uid = strdup ( a -> ref -> unique_id );
3894             unsigned int i;
3895             for ( i = 0; i < g_categorycount; i++ ) {
3896               mm_appref_t *p = g_categories [ i ] -> refs;
3897               mm_appref_t *n;
3898               while ( p ) {
3899                 n = p -> next;
3900
3901                 if ( strcmp ( p -> ref -> unique_id, uid ) == 0 ) {
3902                   free ( p );
3903                   if ( g_categories [ i ] -> refcount ) {
3904                     g_categories [ i ] -> refcount--;
3905                   }
3906                 }
3907
3908                 p = n;
3909               } // while for each appref
3910             } // for each cat/tab
3911
3912             free ( uid );
3913
3914           } else {
3915             // request rescan and wrap up
3916             rescan_apps++;
3917           }
3918
3919           context_alive = 0; // nolonger visible, so lets just get out
3920
3921         }
3922
3923         break;
3924
3925       case context_app_recategorize:
3926         {
3927           char *opts [ 250 ];
3928           unsigned char optmax = 0;
3929           unsigned char i;
3930
3931           // show custom categories
3932           if ( mmcustom_setup() ) {
3933
3934             for ( i = 0; i < mmcustom_count; i++ ) {
3935               if ( mmcustom_complete [ i ].parent_cat == NULL ) {
3936                 opts [ optmax++ ] = mmcustom_complete [ i ].cat;
3937               }
3938             }
3939
3940           }
3941
3942           // show FD categories
3943           i = 2; // skip first two - Other and NoParentCategory
3944           while ( 1 ) {
3945
3946             if ( ! freedesktop_complete [ i ].cat ) {
3947               break;
3948             }
3949
3950             if ( ! freedesktop_complete [ i ].parent_cat ) {
3951               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3952             }
3953
3954             i++;
3955           } // while
3956
3957           // picker
3958           char prompt [ 101 ];
3959           snprintf ( prompt, 100, "Pick category [%s]", a -> ref -> main_category ? a -> ref -> main_category : "NoParentCategory" );
3960
3961           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select parent category"*/, "Enter to select; other to skip." );
3962
3963           if ( sel >= 0 ) {
3964             char confirm [ 1001 ];
3965             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3966
3967             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm categorization", "Do not set category" ) == 1 ) {
3968               ovr_replace_or_add ( a, "maincategory", opts [ sel ] );
3969               rescan_apps++;
3970               // when changing main cat, reset subcat, otherwise you go from Game/Emu to Network/Emu and get sent to Other right away
3971               ovr_replace_or_add ( a, "maincategorysub1", freedesktop_complete [ 2 ].cat );
3972             }
3973
3974           }
3975
3976           if ( mmcustom_is_ready() ) {
3977             mmcustom_shutdown();
3978           }
3979
3980         }
3981         break;
3982
3983       case context_app_recategorize_sub:
3984         {
3985           char *opts [ 250 ];
3986           unsigned char optmax = 0;
3987           unsigned char i = 0;
3988
3989           char *whichparentarewe;
3990           if ( g_categories [ ui_category ] -> parent_catname ) {
3991             whichparentarewe = g_categories [ ui_category ] -> parent_catname;
3992           } else {
3993             whichparentarewe = g_categories [ ui_category ] -> catname;
3994           }
3995
3996           // add NoSubcategory magic one
3997           opts [ optmax++ ] = freedesktop_complete [ 2 ].cat;
3998
3999           // add custom categories
4000           if ( mmcustom_setup() ) {
4001
4002             for ( i = 0; i < mmcustom_count; i++ ) {
4003               if ( mmcustom_complete [ i ].parent_cat && strcmp ( mmcustom_complete [ i ].parent_cat, whichparentarewe ) == 0  ) {
4004                 opts [ optmax++ ] = mmcustom_complete [ i ].cat;
4005               }
4006             }
4007
4008           }
4009
4010           // add FD categories
4011           while ( 1 ) {
4012
4013             if ( ! freedesktop_complete [ i ].cat ) {
4014               break;
4015             }
4016
4017             if ( ( freedesktop_complete [ i ].parent_cat ) &&
4018                  ( strcasecmp ( freedesktop_complete [ i ].parent_cat, whichparentarewe ) == 0 )
4019                )
4020             {
4021               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
4022             }
4023
4024             i++;
4025           } // while
4026
4027           char prompt [ 101 ];
4028           //snprintf ( prompt, 100, "Currently: %s", a -> ref -> main_category1 ? a -> ref -> main_category1 : "NoSubcategory" );
4029           snprintf ( prompt, 100, "%s [%s]", a -> ref -> main_category1 ? a -> ref -> main_category1 : "NoSubcategory", whichparentarewe );
4030
4031           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select subcategory"*/, "Enter to select; other to skip." );
4032
4033           if ( sel >= 0 ) {
4034             char confirm [ 1001 ];
4035             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
4036
4037             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm sub-categorization", "Do not set sub-category" ) == 1 ) {
4038               ovr_replace_or_add ( a, "maincategorysub1", opts [ sel ] );
4039               rescan_apps++;
4040             }
4041
4042           }
4043
4044           if ( mmcustom_is_ready() ) {
4045             mmcustom_shutdown();
4046           }
4047
4048         }
4049         break;
4050
4051       case context_app_rename:
4052         {
4053           char namebuf [ 101 ];
4054           unsigned char changed;
4055
4056           changed = ui_menu_get_text_line ( "Rename application", "Use keyboard; Enter when done.",
4057                                             a -> ref -> title_en ? a -> ref -> title_en : "blank", namebuf, 30, 0 /* alphanumeric */ );
4058
4059           if ( changed ) {
4060             char confirm [ 1001 ];
4061             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
4062
4063             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm rename", "Do not rename" ) == 1 ) {
4064               ovr_replace_or_add ( a, "title", namebuf );
4065               rescan_apps++;
4066             }
4067
4068           }
4069
4070         }
4071
4072         break;
4073
4074       case context_app_cpuspeed:
4075         {
4076           char namebuf [ 101 ];
4077           unsigned char changed;
4078
4079           changed = ui_menu_get_text_line ( "Specify runspeed", "Use keyboard; Enter when done.",
4080                                             a -> ref -> clockspeed ? a -> ref -> clockspeed : "500", namebuf, 6, 1 /* numeric */ );
4081
4082           if ( changed ) {
4083             char confirm [ 1001 ];
4084             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
4085
4086             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm clockspeed", "Do not set" ) == 1 ) {
4087               ovr_replace_or_add ( a, "clockspeed", namebuf );
4088               rescan_apps++;
4089             }
4090
4091           }
4092
4093         }
4094
4095         break;
4096
4097       case context_app_run:
4098         ui_push_exec();
4099         break;
4100
4101       case context_app_notes1:
4102       case context_app_notes2:
4103       case context_app_notes3:
4104         {
4105           char namebuf [ 101 ] = "";
4106
4107           char key [ 501 ];
4108           unsigned char notenum;
4109
4110           // which note line?
4111           if ( menu [ sel ] == context_app_notes1 ) {
4112             notenum = 1;
4113           } else if ( menu [ sel ] == context_app_notes2 ) {
4114             notenum = 2;
4115           } else if ( menu [ sel ] == context_app_notes3 ) {
4116             notenum = 3;
4117           }
4118
4119           // figure out key for looking up existing, and for storing replacement
4120           snprintf ( key, 500, "Application-%u.note-%u", a -> ref -> subapp_number, notenum );
4121
4122           // do we have existing value?
4123           if ( a -> ovrh ) {
4124             char *existing = pnd_conf_get_as_char ( a -> ovrh, key );
4125             if ( existing ) {
4126               strncpy ( namebuf, existing, 100 );
4127             }
4128           }
4129
4130           unsigned char changed;
4131
4132           changed = ui_menu_get_text_line ( "Enter replacement note", "Use keyboard; Enter when done.",
4133                                             namebuf, namebuf, 30, 0 /* not-numeric-forced */ );
4134
4135           if ( changed ) {
4136             ovr_replace_or_add ( a, strchr ( key, '.' ) + 1, namebuf );
4137             rescan_apps++;
4138           }
4139
4140         }
4141         break;
4142
4143       default:
4144         return;
4145
4146       } // switch
4147
4148     } // if useful return
4149
4150   } // menu is alive?
4151
4152   // rescan apps?
4153   if ( rescan_apps ) {
4154     applications_free();
4155     applications_scan();
4156   }
4157
4158   return;
4159 }
4160
4161 unsigned char ui_menu_oneby ( char *title, char *footer, char *one ) {
4162   char *opts [ 2 ];
4163   opts [ 0 ] = one;
4164   int sel = ui_modal_single_menu ( opts, 1, title, footer );
4165   if ( sel < 0 ) {
4166     return ( 0 );
4167   }
4168   return ( sel + 1 );
4169 }
4170
4171 unsigned char ui_menu_twoby ( char *title, char *footer, char *one, char *two ) {
4172   char *opts [ 3 ];
4173   opts [ 0 ] = one;
4174   opts [ 1 ] = two;
4175   int sel = ui_modal_single_menu ( opts, 2, title, footer );
4176   if ( sel < 0 ) {
4177     return ( 0 );
4178   }
4179   return ( sel + 1 );
4180 }
4181
4182 unsigned char ui_menu_get_text_line ( char *title, char *footer, char *initialvalue,
4183                                       char *r_buffer, unsigned char maxlen, unsigned char numbersonlyp )
4184 {
4185   SDL_Rect rects [ 40 ];
4186   SDL_Rect *dest = rects;
4187   SDL_Rect src;
4188   SDL_Surface *rtext;
4189
4190   char hacktext [ 1024 ];
4191   unsigned char shifted = 0;
4192
4193   bzero ( rects, sizeof(SDL_Rect) * 40 );
4194
4195   if ( initialvalue ) {
4196     if ( initialvalue == r_buffer ) {
4197       // already good to go
4198     } else {
4199       strncpy ( r_buffer, initialvalue, maxlen );
4200     }
4201   } else {
4202     bzero ( r_buffer, maxlen );
4203   }
4204
4205   while ( 1 ) {
4206
4207     // clear
4208     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
4209     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
4210     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
4211     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
4212     SDL_FillRect( sdl_realscreen, dest, 0 );
4213
4214     // show dialog background
4215     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
4216       src.x = 0;
4217       src.y = 0;
4218       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
4219       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
4220       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
4221       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
4222       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
4223       dest++;
4224     }
4225
4226     // show dialog frame
4227     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
4228       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
4229       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
4230       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
4231       dest++;
4232     }
4233
4234     // show header
4235     if ( title ) {
4236       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
4237       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
4238       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
4239       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
4240       SDL_FreeSurface ( rtext );
4241       dest++;
4242     }
4243
4244     // show footer
4245     if ( footer ) {
4246       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
4247       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
4248       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
4249         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
4250         - 60;
4251       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
4252       SDL_FreeSurface ( rtext );
4253       dest++;
4254     }
4255
4256     // show text line - and embed cursor
4257     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
4258     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( 0/*i*/ + 1 - 0/*first_visible*/ ) );
4259
4260     strncpy ( hacktext, r_buffer, 1000 );
4261     strncat ( hacktext, "\n", 1000 ); // add [] in most fonts
4262
4263     rtext = TTF_RenderText_Blended ( g_tab_font, hacktext, ui_display_context.fontcolor );
4264     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
4265     SDL_FreeSurface ( rtext );
4266     dest++;
4267
4268     // update all the rects and send it all to sdl
4269     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
4270     dest = rects;
4271
4272     // check for input
4273     SDL_Event event;
4274     while ( SDL_WaitEvent ( &event ) ) {
4275
4276       switch ( event.type ) {
4277
4278       case SDL_KEYUP:
4279         if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
4280           shifted = 0;
4281         }
4282         break;
4283
4284       case SDL_KEYDOWN:
4285
4286         if ( event.key.keysym.sym == SDLK_LEFT || event.key.keysym.sym == SDLK_BACKSPACE ) {
4287           if ( strlen ( r_buffer ) > 0 ) {
4288             char *eol = strchr ( r_buffer, '\0' );
4289             *( eol - 1 ) = '\0';
4290           }
4291
4292         } else if ( event.key.keysym.sym == SDLK_UP ) {
4293           r_buffer [ 0 ] = '\0'; // truncate!
4294
4295         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
4296           // on Enter/Return or B, if the buffer has 1 or more chars, we return it as valid.. otherwise, invalid.
4297           if ( strlen ( r_buffer ) > 0 ) {
4298             return ( 1 );
4299           }
4300           return ( 0 );
4301
4302         } else if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
4303           shifted = 1;
4304
4305         } else if ( event.key.keysym.sym == SDLK_ESCAPE ||
4306                     event.key.keysym.sym == SDLK_PAGEUP ||
4307                     event.key.keysym.sym == SDLK_PAGEDOWN ||
4308                     event.key.keysym.sym == SDLK_HOME
4309                   )
4310         {
4311           return ( 0 );
4312
4313         } else {
4314
4315           if ( isprint(event.key.keysym.sym) ) {
4316
4317             unsigned char good = 1;
4318
4319             if ( numbersonlyp && ( ! isdigit(event.key.keysym.sym) ) ) {
4320               good = 0;
4321             }
4322
4323             if ( maxlen && strlen(r_buffer) >= maxlen ) {
4324               good = 0;
4325             }
4326
4327             if ( good ) {
4328               char b [ 2 ] = { '\0', '\0' };
4329               if ( shifted ) {
4330                 b [ 0 ] = toupper ( event.key.keysym.sym );
4331               } else {
4332                 b [ 0 ] = event.key.keysym.sym;
4333               }
4334               strncat ( r_buffer, b, maxlen );
4335             } // good?
4336
4337           } // printable?
4338
4339         }
4340
4341         break;
4342
4343       } // switch
4344
4345       break;
4346
4347     } // while waiting for input
4348
4349   } // while
4350
4351   return ( 0 );
4352 }
4353
4354 unsigned char ovr_replace_or_add ( mm_appref_t *a, char *keybase, char *newvalue ) {
4355   //printf ( "setting %s:%u - '%s' to '%s' - %s/%s\n", a -> ref -> title_en, a -> ref -> subapp_number, keybase, newvalue, a -> ref -> object_path, a -> ref -> object_filename );
4356
4357   char fullpath [ PATH_MAX ];
4358
4359   sprintf ( fullpath, "%s/%s", a -> ref -> object_path, a -> ref -> object_filename );
4360   char *dot = strrchr ( fullpath, '.' );
4361   if ( dot ) {
4362     sprintf ( dot, PXML_SAMEPATH_OVERRIDE_FILEEXT );
4363   } else {
4364     pnd_log ( pndn_error, "ERROR: Bad pnd-path in disco_t! %s\n", fullpath );
4365     return ( 0 );
4366   }
4367
4368   struct stat statbuf;
4369
4370   if ( stat ( fullpath, &statbuf ) == 0 ) {
4371     // file exists
4372     pnd_conf_handle h;
4373
4374     h = pnd_conf_fetch_by_path ( fullpath );
4375
4376     if ( ! h ) {
4377       return ( 0 ); // fail!
4378     }
4379
4380     char key [ 101 ];
4381     snprintf ( key, 100, "Application-%u.%s", a -> ref -> subapp_number, keybase );
4382
4383     pnd_conf_set_char ( h, key, newvalue );
4384
4385     return ( pnd_conf_write ( h, fullpath ) );
4386
4387   } else {
4388     // file needs to be created - easy!
4389
4390     FILE *f = fopen ( fullpath, "w" );
4391
4392     if ( f ) {
4393       fprintf ( f, "Application-%u.%s\t%s\n", a -> ref -> subapp_number, keybase, newvalue );
4394       fclose ( f );
4395
4396     } else {
4397       return ( 0 ); // fail!
4398     }
4399
4400   } // new or used?
4401
4402   return ( 1 );
4403 }
4404
4405 void ui_manage_categories ( void ) {
4406   unsigned char require_app_scan = 0;
4407
4408   if ( ! mmcustom_setup() ) {
4409     return; // error
4410   }
4411
4412   char *opts [ 20 ] = {
4413     "List custom categories",
4414     "List custom subcategories",
4415     "Register custom category",
4416     "Register custom subcategory",
4417     "Unregister custom category",
4418     "Unregister custom subcategory",
4419     "Done"
4420   };
4421
4422   while ( 1 ) {
4423
4424     int sel = ui_modal_single_menu ( opts, 7, "Custom Categories", "B to select; other to cancel." );
4425
4426     switch ( sel ) {
4427
4428     case 0: // list custom
4429       ui_pick_custom_category ( 0 );
4430       break;
4431
4432     case 1: // list custom sub
4433       if ( mmcustom_count ) {
4434
4435         char *maincat = ui_pick_custom_category ( 2 );
4436
4437         if ( maincat ) {
4438           unsigned int subcount = mmcustom_count_subcats ( maincat );
4439           char titlebuf [ 201 ];
4440
4441           snprintf ( titlebuf, 200, "Category: %s", maincat );
4442
4443           if ( subcount == 0 ) {
4444             ui_menu_oneby ( titlebuf, "B/Enter to accept", "Category has no subcategories." );
4445           } else {
4446
4447             char **list = malloc ( subcount * sizeof(char*) );
4448             int i;
4449             unsigned int counter = 0;
4450
4451             for ( i = 0; i < mmcustom_count; i++ ) {
4452               if ( mmcustom_complete [ i ].parent_cat && strcasecmp ( mmcustom_complete [ i ].parent_cat, maincat ) == 0 ) {
4453                 list [ counter++ ] = mmcustom_complete [ i ].cat;
4454               }
4455             }
4456
4457             ui_modal_single_menu ( list, counter, titlebuf, "Any button to exit." );
4458
4459             free ( list );
4460
4461           } // more than 0 subcats?
4462
4463         } // user picked a main cat?
4464
4465       } else {
4466         ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4467       }
4468       break;
4469
4470     case 2: // register custom
4471       {
4472         unsigned char changed;
4473         char namebuf [ 101 ] = "";
4474
4475         changed = ui_menu_get_text_line ( "Enter unique category name", "Use keyboard; Enter when done.",
4476                                           "Pandora", namebuf, 30, 0 /* alphanumeric */ );
4477
4478         // did the user enter something?
4479         if ( changed ) {
4480
4481           // for now, force use of '*' into something else as we use * internally :/ (FIXME)
4482           {
4483             char *fixme;
4484             while ( ( fixme = strchr ( namebuf, '*' ) ) ) {
4485               *fixme = '_';
4486             }
4487           }
4488
4489           // and if so, is it existant already or not?
4490           if ( mmcustom_query ( namebuf, NULL ) ) {
4491             ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a registered category." );
4492           } else if ( freedesktop_category_query ( namebuf, NULL ) ) {
4493             ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a Standard category." );
4494           } else {
4495
4496             char confirm [ 1001 ];
4497             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
4498
4499             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm new category", "Do not register" ) == 1 ) {
4500               // register, save, recycle the current list
4501               mmcustom_register ( namebuf, NULL );
4502               mmcustom_write ( NULL );
4503               mmcustom_shutdown();
4504               mmcustom_setup();
4505             }
4506
4507           } // dupe?
4508
4509         } // entered something?
4510
4511       }
4512       break;
4513
4514     case 3: // register custom sub
4515       if ( 1 /*mmcustom_count -- we allow FD cats now, so this isn't applicable error */ ) {
4516
4517         char *maincat = ui_pick_custom_category ( 1 /* include FD */ );
4518
4519         if ( maincat ) {
4520           char titlebuf [ 201 ];
4521
4522           snprintf ( titlebuf, 200, "Subcat of: %s", maincat );
4523
4524           unsigned char changed;
4525           char namebuf [ 101 ] = "";
4526
4527           changed = ui_menu_get_text_line ( titlebuf, "Use keyboard; Enter when done.", "Submarine", namebuf, 30, 0 /* alphanumeric */ );
4528
4529           // did the user enter something?
4530           if ( changed ) {
4531
4532             // for now, force use of '*' into something else as we use * internally :/ (FIXME)
4533             {
4534               char *fixme;
4535               while ( ( fixme = strchr ( namebuf, '*' ) ) ) {
4536                 *fixme = '_';
4537               }
4538             }
4539
4540             // and if so, is it existant already or not?
4541             if ( mmcustom_query ( namebuf, maincat ) ) {
4542               ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a subcategory." );
4543             } else if ( freedesktop_category_query ( namebuf, maincat ) ) {
4544               ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a Standard subcategory." );
4545             } else {
4546
4547               char confirm [ 1001 ];
4548               snprintf ( confirm, 1000, "Confirm: %s [%s]", namebuf, maincat );
4549
4550               if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm new category", "Do not register" ) == 1 ) {
4551                 // register, save, recycle the current list
4552                 mmcustom_register ( namebuf, maincat );
4553                 mmcustom_write ( NULL );
4554                 mmcustom_shutdown();
4555                 mmcustom_setup();
4556               }
4557
4558             } // dupe?
4559
4560           } // entered something?
4561
4562         } // selected parent cat?
4563
4564       } else {
4565         ui_menu_oneby ( "Warning", "B/Enter to accept", "No categories registered." );
4566       }
4567       break;
4568
4569     case 4: // unreg custom
4570       if ( mmcustom_count ) {
4571         char *maincat = ui_pick_custom_category ( 0 );
4572
4573         if ( maincat ) {
4574           char confirm [ 1001 ];
4575           snprintf ( confirm, 1000, "Confirm remove: %s", maincat );
4576
4577           if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm unregister", "Do not unregister" ) == 1 ) {
4578             // register, save, recycle the current list
4579             mmcustom_unregister ( maincat, NULL );
4580             mmcustom_write ( NULL );
4581             mmcustom_shutdown();
4582             mmcustom_setup();
4583           }
4584
4585         } // picked?
4586
4587       } else {
4588         ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4589       }
4590       break;
4591
4592     case 5: // unreg custom sub
4593       if ( mmcustom_count ) {
4594         char *maincat = ui_pick_custom_category ( 2 );
4595
4596         if ( maincat ) {
4597           unsigned int subcount = mmcustom_count_subcats ( maincat );
4598           char titlebuf [ 201 ];
4599
4600           snprintf ( titlebuf, 200, "Category: %s", maincat );
4601
4602           if ( subcount == 0 ) {
4603             ui_menu_oneby ( titlebuf, "B/Enter to accept", "Category has no subcategories." );
4604           } else {
4605
4606             char **list = malloc ( subcount * sizeof(char*) );
4607             int i;
4608             unsigned int counter = 0;
4609
4610             for ( i = 0; i < mmcustom_count; i++ ) {
4611               if ( mmcustom_complete [ i ].parent_cat && strcasecmp ( mmcustom_complete [ i ].parent_cat, maincat ) == 0 ) {
4612                 list [ counter++ ] = mmcustom_complete [ i ].cat;
4613               }
4614             }
4615
4616             int sel = ui_modal_single_menu ( list, counter, titlebuf, "B to selct; other to exit." );
4617
4618             if ( sel >= 0 ) {
4619               char confirm [ 1001 ];
4620               snprintf ( confirm, 1000, "Confirm remove: %s", list [ sel ] );
4621
4622               if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm unregister", "Do not unregister" ) == 1 ) {
4623                 // register, save, recycle the current list
4624                 mmcustom_unregister ( list [ sel ], maincat );
4625                 mmcustom_write ( NULL );
4626                 mmcustom_shutdown();
4627                 mmcustom_setup();
4628               }
4629
4630             } // confirm kill?
4631
4632             free ( list );
4633
4634           } // more than 0 subcats?
4635
4636         } // user picked a main cat?
4637
4638       } else {
4639         ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4640       }
4641       break;
4642
4643     } // switch
4644
4645     // exeunt
4646     if ( sel < 0 || sel > 5 ) {
4647       break;
4648     }
4649
4650   } // while running the menu
4651
4652   // shut down custom cats
4653   mmcustom_shutdown();
4654
4655   // reload apps?
4656   if ( require_app_scan ) {
4657     applications_free();
4658     applications_scan();
4659   }
4660
4661   // redraw
4662   render_mask |= CHANGED_EVERYTHING;
4663
4664   return;
4665 }
4666
4667 // mode 0 == custom main only; 1 == custom main + FD main; 2 == custom main + FD mains-with-custom-subs
4668 char *ui_pick_custom_category ( unsigned char mode ) {
4669   char **list;
4670   int i;
4671   unsigned int counter = 0;
4672
4673   // alloc space for list, depending on scope
4674   if ( mode > 0 ) {
4675     list = malloc ( (mmcustom_count+freedesktop_count()) * sizeof(char*) );
4676   } else {
4677     list = malloc ( mmcustom_count * sizeof(char*) );
4678   }
4679
4680   // add custom mains
4681   for ( i = 0; i < mmcustom_count; i++ ) {
4682     if ( mmcustom_complete [ i ].parent_cat == NULL ) {
4683       list [ counter++ ] = mmcustom_complete [ i ].cat;
4684     }
4685   }
4686
4687   // add FD if needed
4688   if ( mode > 0 ) {
4689     i = 3;
4690
4691     while ( 1 ) {
4692
4693       if ( ! freedesktop_complete [ i ].cat ) {
4694         break;
4695       }
4696
4697       // if FD main cat
4698       if ( freedesktop_complete [ i ].parent_cat == NULL ) {
4699
4700         // mode 1 == include them all
4701         // mode 2 == include them if they have a custom subcat
4702         if ( ( mode == 1 ) ||
4703              ( mmcustom_subcount ( freedesktop_complete [ i ].cat ) ) )
4704         {
4705           list [ counter++ ] = freedesktop_complete [ i ].cat;
4706         }
4707
4708       } // if parent cat
4709
4710       i++;
4711     } // while
4712
4713   } // if
4714
4715   // we actually showing anything?
4716   if ( ! counter ) {
4717     ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4718     return ( NULL );
4719   }
4720
4721   // do it
4722   int sel = ui_modal_single_menu ( list, counter, "Custom Categories", "Any button to exit." );
4723
4724   if ( sel < 0 ) {
4725     free ( list );
4726     return ( NULL );
4727   }
4728
4729   char *foo = list [ sel ];
4730   free ( list );
4731
4732   return ( foo );
4733 }
4734
4735 void ui_start_defered_icon_thread ( void ) {
4736
4737   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) != 1 ) {
4738     return;
4739   }
4740
4741   if ( g_icon_thread_busy ) {
4742     //fprintf ( stderr, "REM: Waiting for thread to stop..\n" );
4743     ui_stop_defered_icon_thread();
4744   }
4745
4746   //fprintf ( stderr, "WARN: Starting new icon caching thread..\n" );
4747   g_icon_thread = SDL_CreateThread ( (void*)ui_threaded_defered_icon, NULL );
4748
4749   if ( ! g_icon_thread ) {
4750     pnd_log ( pndn_error, "ERROR: Couldn't create icon thread\n" );
4751   }
4752
4753   return;
4754 }
4755
4756 void ui_stop_defered_icon_thread ( void ) {
4757   time_t started = time ( NULL );
4758
4759   // ask thread to stop, then wait for it (if two run at same time, or if we change
4760   // category content under neath it, could be bad..)
4761   g_icon_thread_stop = 1;
4762   while ( g_icon_thread_busy ) {
4763     time ( NULL ); // spin
4764   }
4765   g_icon_thread_stop = 0;
4766
4767   fprintf ( stderr, "REM: Thread stoppage took %u seconds.\n", (int) ( time ( NULL ) - started ) );
4768
4769   return;
4770 }