mmenu: introduced artificial delay after SD event (ins/eject); mmenu is racing agains...
[pandora-libraries.git] / minimenu / mmui.c
1
2 #include <stdio.h> /* for FILE etc */
3 #include <stdlib.h> /* for malloc */
4 #include <unistd.h> /* for unlink */
5 #include <limits.h> /* for PATH_MAX */
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #define __USE_GNU /* for strcasestr */
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   scalex = (double)maxwidth / (double)s -> w;
2476
2477   if ( maxheight == -1 ) {
2478     scale = scalex;
2479   } else {
2480     scaley = (double)maxheight / (double)s -> h;
2481
2482     if ( scaley < scalex ) {
2483       scale = scaley;
2484     } else {
2485       scale = scalex;
2486     }
2487
2488   }
2489
2490   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
2491   SDL_FreeSurface ( s );
2492   s = scaled;
2493
2494   return ( s );
2495 }
2496
2497 void ui_loadscreen ( void ) {
2498
2499   SDL_Rect dest;
2500
2501   // clear the screen
2502   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2503
2504   // render text
2505   SDL_Surface *rtext;
2506   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", ui_display_context.fontcolor );
2507   dest.x = 20;
2508   dest.y = 20;
2509   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2510   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2511   SDL_FreeSurface ( rtext );
2512
2513   return;
2514 }
2515
2516 void ui_discoverscreen ( unsigned char clearscreen ) {
2517
2518   SDL_Rect dest;
2519
2520   // clear the screen
2521   if ( clearscreen ) {
2522     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2523
2524     // render background
2525     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2526       dest.x = 0;
2527       dest.y = 0;
2528       dest.w = sdl_realscreen -> w;
2529       dest.h = sdl_realscreen -> h;
2530       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2531       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2532     }
2533
2534   }
2535
2536   // render text
2537   SDL_Surface *rtext;
2538   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", ui_display_context.fontcolor );
2539   if ( clearscreen ) {
2540     dest.x = 20;
2541     dest.y = 20;
2542   } else {
2543     dest.x = 20;
2544     dest.y = 40;
2545   }
2546   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
2547   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2548   SDL_FreeSurface ( rtext );
2549
2550   // render icon
2551   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2552     dest.x = rtext -> w + 30;
2553     dest.y = 20;
2554     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
2555     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2556   }
2557
2558   return;
2559 }
2560
2561 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
2562
2563   SDL_Rect rects [ 4 ];
2564   SDL_Rect *dest = rects;
2565   SDL_Rect src;
2566   bzero ( dest, sizeof(SDL_Rect)* 4 );
2567
2568   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
2569   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
2570   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
2571   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
2572
2573   static unsigned int stepx = 0;
2574
2575   // clear the screen
2576   if ( clearscreen ) {
2577     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
2578
2579     // render background
2580     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2581       dest -> x = 0;
2582       dest -> y = 0;
2583       dest -> w = sdl_realscreen -> w;
2584       dest -> h = sdl_realscreen -> h;
2585       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
2586       dest++;
2587     }
2588
2589   } else {
2590
2591     // render background
2592     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
2593       src.x = 0;
2594       src.y = 0;
2595       src.w = sdl_realscreen -> w;
2596       src.h = 100;
2597       dest -> x = 0;
2598       dest -> y = 0;
2599       dest -> w = sdl_realscreen -> w;
2600       dest -> h = sdl_realscreen -> h;
2601       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
2602       dest++;
2603     }
2604
2605   } // clear it
2606
2607   // render text
2608   SDL_Surface *rtext;
2609   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
2610   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
2611   dest -> x = 20;
2612   dest -> y = 20;
2613   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2614   SDL_FreeSurface ( rtext );
2615   dest++;
2616
2617   // render icon
2618   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
2619     dest -> x = rtext -> w + 30 + stepx;
2620     dest -> y = 20;
2621     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
2622     dest++;
2623   }
2624
2625   // filename
2626   if ( filename ) {
2627     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
2628     dest -> x = 20;
2629     dest -> y = 50;
2630     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2631     SDL_FreeSurface ( rtext );
2632     dest++;
2633   }
2634
2635   // move across
2636   stepx += 20;
2637
2638   if ( stepx > 350 ) {
2639     stepx = 0;
2640   }
2641
2642   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2643
2644   return;
2645 }
2646
2647 int ui_selected_index ( void ) {
2648
2649   if ( ! ui_selected ) {
2650     return ( -1 ); // no index
2651   }
2652
2653   mm_appref_t *r = g_categories [ ui_category ] -> refs;
2654   int counter = 0;
2655   while ( r ) {
2656     if ( r == ui_selected ) {
2657       return ( counter );
2658     }
2659     r = r -> next;
2660     counter++;
2661   }
2662
2663   return ( -1 );
2664 }
2665
2666 static mm_appref_t *timer_ref = NULL;
2667 void ui_set_selected ( mm_appref_t *r ) {
2668
2669   render_mask |= CHANGED_SELECTION;
2670
2671   // preview pic stuff
2672   //
2673
2674   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
2675     return; // no desire to defer anything
2676   }
2677
2678   if ( ! r ) {
2679     // cancel timer
2680     SDL_SetTimer ( 0, NULL );
2681     timer_ref = NULL;
2682     return;
2683   }
2684
2685   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
2686   timer_ref = r;
2687
2688   return;
2689 }
2690
2691 unsigned int ui_callback_f ( unsigned int t ) {
2692
2693   if ( ui_selected != timer_ref ) {
2694     return ( 0 ); // user has moved it, who cares
2695   }
2696
2697   SDL_Event e;
2698   bzero ( &e, sizeof(SDL_Event) );
2699   e.type = SDL_USEREVENT;
2700   e.user.code = sdl_user_ticker;
2701   SDL_PushEvent ( &e );
2702
2703   return ( 0 );
2704 }
2705
2706 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
2707   SDL_Rect rects [ 40 ];
2708   SDL_Rect *dest = rects;
2709   SDL_Rect src;
2710   SDL_Surface *rtext;
2711   unsigned char max_visible = pnd_conf_get_as_int_d ( g_conf, "detailtext.max_visible" , 11 );
2712   unsigned char first_visible = 0;
2713
2714   bzero ( rects, sizeof(SDL_Rect) * 40 );
2715
2716   unsigned int sel = 0;
2717
2718   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 };
2719
2720   unsigned int i;
2721   SDL_Event event;
2722
2723   while ( 1 ) {
2724
2725     // clear
2726     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2727     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2728     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
2729     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
2730     SDL_FillRect( sdl_realscreen, dest, 0 );
2731
2732     // show dialog background
2733     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
2734       src.x = 0;
2735       src.y = 0;
2736       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
2737       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
2738       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2739       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2740       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2741       // repeat for darken?
2742       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2743       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2744       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2745       dest++;
2746     }
2747
2748     // show dialog frame
2749     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
2750       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2751       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2752       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
2753       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2754       dest++;
2755     }
2756
2757     // show header
2758     if ( title ) {
2759       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
2760       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2761       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
2762       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2763       SDL_FreeSurface ( rtext );
2764       dest++;
2765     }
2766
2767     // show footer
2768     if ( footer ) {
2769       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
2770       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2771       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
2772         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
2773         - 60;
2774       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2775       SDL_FreeSurface ( rtext );
2776       dest++;
2777     }
2778
2779     // show options
2780     for ( i = first_visible; i < first_visible + max_visible && i < argc; i++ ) {
2781
2782       // show options
2783       if ( sel == i ) {
2784         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
2785       } else {
2786         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], ui_display_context.fontcolor );
2787       }
2788       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2789       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 - first_visible ) );
2790       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2791       SDL_FreeSurface ( rtext );
2792       dest++;
2793
2794     } // for
2795
2796     // update all the rects and send it all to sdl
2797     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2798     dest = rects;
2799
2800     // check for input
2801     while ( SDL_WaitEvent ( &event ) ) {
2802
2803       switch ( event.type ) {
2804
2805       //case SDL_KEYUP:
2806       case SDL_KEYDOWN:
2807
2808         if ( event.key.keysym.sym == SDLK_UP ) {
2809           if ( sel ) {
2810             sel--;
2811
2812             if ( sel < first_visible ) {
2813               first_visible--;
2814             }
2815
2816           }
2817         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
2818
2819           if ( sel < argc - 1 ) {
2820             sel++;
2821
2822             // ensure visibility
2823             if ( sel >= first_visible + max_visible ) {
2824               first_visible++;
2825             }
2826
2827           }
2828
2829         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
2830           return ( sel );
2831
2832 #if 0
2833         } else if ( event.key.keysym.sym == SDLK_q ) {
2834           exit ( 0 );
2835 #endif
2836
2837         } else {
2838           return ( -1 ); // nada
2839         }
2840
2841         break;
2842
2843       } // switch
2844
2845       break;
2846     } // while
2847
2848   } // while
2849
2850   return ( -1 );
2851 }
2852
2853 unsigned char ui_determine_row ( mm_appref_t *a ) {
2854   unsigned int row = 0;
2855
2856   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2857   while ( i != a ) {
2858     i = i -> next;
2859     row++;
2860   } // while
2861   row /= ui_display_context.col_max;
2862
2863   return ( row );
2864 }
2865
2866 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
2867   return ( ui_determine_row ( a ) % ui_display_context.row_max );
2868 }
2869
2870 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
2871   unsigned int col = 0;
2872
2873   mm_appref_t *i = g_categories [ ui_category ] -> refs;
2874   while ( i != a ) {
2875     i = i -> next;
2876     col++;
2877   } // while
2878   col %= ui_display_context.col_max;
2879
2880   return ( col );
2881 }
2882
2883 unsigned char ui_show_info ( char *pndrun, pnd_disco_t *p ) {
2884   char *viewer, *searchpath;
2885   pnd_conf_handle desktoph;
2886
2887   // viewer
2888   searchpath = pnd_conf_query_searchpath();
2889
2890   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
2891
2892   if ( ! desktoph ) {
2893     return ( 0 );
2894   }
2895
2896   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
2897
2898   if ( ! viewer ) {
2899     return ( 0 ); // no way to view the file
2900   }
2901
2902   // etc
2903   if ( ! p -> unique_id ) {
2904     return ( 0 );
2905   }
2906
2907   if ( ! p -> info_filename ) {
2908     return ( 0 );
2909   }
2910
2911   if ( ! p -> info_name ) {
2912     return ( 0 );
2913   }
2914
2915   if ( ! pndrun ) {
2916     return ( 0 );
2917   }
2918
2919   // exec line
2920   char args [ 1001 ];
2921   char *pargs = args;
2922   if ( pnd_conf_get_as_char ( desktoph, "info.viewer_args" ) ) {
2923     snprintf ( pargs, 1001, "%s %s",
2924                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
2925   } else {
2926     pargs = NULL;
2927   }
2928
2929   char pndfile [ 1024 ];
2930   if ( p -> object_type == pnd_object_type_directory ) {
2931     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
2932     strncpy ( pndfile, p -> object_path, 1000 );
2933   } else if ( p -> object_type == pnd_object_type_pnd ) {
2934     // pnd_run.sh wants the full path and filename for the .pnd file
2935     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
2936   }
2937
2938   if ( ! pnd_apps_exec ( pndrun, pndfile, p -> unique_id, viewer, p -> startdir, pargs,
2939                          p -> clockspeed ? atoi ( p -> clockspeed ) : 0, PND_EXEC_OPTION_NORUN ) )
2940   {
2941     return ( 0 );
2942   }
2943
2944   pnd_log ( pndn_debug, "Info Exec=%s\n", pnd_apps_exec_runline() );
2945
2946   // try running it
2947   int x;
2948   if ( ( x = fork() ) < 0 ) {
2949     pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
2950     return ( 0 );
2951   }
2952
2953   if ( x == 0 ) {
2954     execl ( "/bin/sh", "/bin/sh", "-c", pnd_apps_exec_runline(), (char*)NULL );
2955     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", pnd_apps_exec_runline() );
2956     return ( 0 );
2957   }
2958
2959   return ( 1 );
2960 }
2961
2962 typedef struct {
2963   SDL_Rect r;
2964   int catnum;
2965   mm_appref_t *ref;
2966 } ui_touch_t;
2967 #define MAXTOUCH 100
2968 ui_touch_t ui_touchrects [ MAXTOUCH ];
2969 unsigned char ui_touchrect_count = 0;
2970
2971 void ui_register_reset ( void ) {
2972   bzero ( ui_touchrects, sizeof(ui_touch_t)*MAXTOUCH );
2973   ui_touchrect_count = 0;
2974   return;
2975 }
2976
2977 void ui_register_tab ( unsigned char catnum, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2978
2979   if ( ui_touchrect_count == MAXTOUCH ) {
2980     return;
2981   }
2982
2983   ui_touchrects [ ui_touchrect_count ].r.x = x;
2984   ui_touchrects [ ui_touchrect_count ].r.y = y;
2985   ui_touchrects [ ui_touchrect_count ].r.w = w;
2986   ui_touchrects [ ui_touchrect_count ].r.h = h;
2987   ui_touchrects [ ui_touchrect_count ].catnum = catnum;
2988   ui_touchrect_count++;
2989
2990   return;
2991 }
2992
2993 void ui_register_app ( mm_appref_t *app, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2994
2995   if ( ui_touchrect_count == MAXTOUCH ) {
2996     return;
2997   }
2998
2999   ui_touchrects [ ui_touchrect_count ].r.x = x;
3000   ui_touchrects [ ui_touchrect_count ].r.y = y;
3001   ui_touchrects [ ui_touchrect_count ].r.w = w;
3002   ui_touchrects [ ui_touchrect_count ].r.h = h;
3003   ui_touchrects [ ui_touchrect_count ].ref = app;
3004   ui_touchrect_count++;
3005
3006   return;
3007 }
3008
3009 void ui_touch_act ( unsigned int x, unsigned int y ) {
3010
3011   unsigned char i;
3012   ui_touch_t *t;
3013
3014   for ( i = 0; i < ui_touchrect_count; i++ ) {
3015     t = &(ui_touchrects [ i ]);
3016
3017     if ( x >= t -> r.x &&
3018          x <= t -> r.x + t -> r.w &&
3019          y >= t -> r.y &&
3020          y <= t -> r.y + t -> r.h
3021        )
3022     {
3023
3024       if ( t -> ref ) {
3025         ui_selected = t -> ref;
3026         ui_push_exec();
3027       } else {
3028         if ( ui_category != t -> catnum ) {
3029           ui_selected = NULL;
3030         }
3031         ui_category = t -> catnum;
3032         render_mask |= CHANGED_CATEGORY;
3033         // rescan the dir
3034         category_fs_restock ( g_categories [ ui_category ] );
3035         ui_start_defered_icon_thread();
3036       }
3037
3038       break;
3039     }
3040
3041   } // for
3042
3043   return;
3044 }
3045
3046 unsigned char ui_forkexec ( char *argv[] ) {
3047   char *fooby = argv[0];
3048   int x;
3049
3050   if ( ( x = fork() ) < 0 ) {
3051     pnd_log ( pndn_error, "ERROR: Couldn't fork() for '%s'\n", fooby );
3052     return ( 0 );
3053   }
3054
3055   if ( x == 0 ) { // child
3056     execv ( fooby, argv );
3057     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", fooby );
3058     return ( 0 );
3059   }
3060
3061   // parent, success
3062   return ( 1 );
3063 }
3064
3065 unsigned char ui_threaded_timer_create ( void ) {
3066
3067   g_timer_thread = SDL_CreateThread ( (void*)ui_threaded_timer, NULL );
3068
3069   if ( ! g_timer_thread ) {
3070     pnd_log ( pndn_error, "ERROR: Couldn't create timer thread\n" );
3071     return ( 0 );
3072   }
3073
3074   return ( 1 );
3075 }
3076
3077 int ui_threaded_timer ( pnd_disco_t *p ) {
3078
3079   // this timer's job is to ..
3080   // - do nothing for quite awhile
3081   // - on wake, post event to SDL event queue, so that main thread will check if SD insert/eject occurred
3082   // - goto 10
3083
3084   unsigned int delay_s = 2; // seconds
3085
3086   while ( 1 ) {
3087
3088     // pause...
3089     //sleep ( delay_s );
3090     SDL_Delay ( delay_s * 1000 );
3091
3092     // .. trigger SD check
3093     SDL_Event e;
3094     bzero ( &e, sizeof(SDL_Event) );
3095     e.type = SDL_USEREVENT;
3096     e.user.code = sdl_user_checksd;
3097     SDL_PushEvent ( &e );
3098
3099   } // while
3100
3101   return ( 0 );
3102 }
3103
3104 unsigned char ui_threaded_defered_preview ( pnd_disco_t *p ) {
3105
3106   if ( ! cache_preview ( p, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
3107                          pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
3108      )
3109   {
3110     pnd_log ( pndn_debug, "THREAD: Couldn't load preview pic: '%s' -> '%s'\n",
3111               IFNULL(p->title_en,"No Name"), p -> preview_pic1 );
3112   }
3113
3114   // trigger that we completed
3115   SDL_Event e;
3116   bzero ( &e, sizeof(SDL_Event) );
3117   e.type = SDL_USEREVENT;
3118   e.user.code = sdl_user_finishedpreview;
3119   e.user.data1 = p;
3120   SDL_PushEvent ( &e );
3121
3122   return ( 0 );
3123 }
3124
3125 void ui_post_scan ( void ) {
3126
3127   // reset view
3128   ui_selected = NULL;
3129   ui_rows_scrolled_down = 0;
3130   // set back to first tab, to be safe
3131   ui_category = 0;
3132   ui_catshift = 0;
3133
3134   // do we have a preferred category to jump to? or a last known one?
3135   char *dc = pnd_conf_get_as_char ( g_conf, "categories.default_cat" );
3136   char *lastcat = pnd_conf_get_as_char ( g_conf, "minimenu.last_known_catname" );
3137   if ( ( dc ) ||
3138        ( pnd_conf_get_as_int_d ( g_conf, "minimenu.start_selected", 0 ) && lastcat ) )
3139   {
3140     char *catpick = NULL;
3141
3142     if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.start_selected", 0 ) && lastcat ) {
3143       catpick = lastcat;
3144
3145       // if this is a subcat, we have some doctoring to do :/ the hackishness is really
3146       // starting to show..
3147       if ( pnd_conf_get_as_char ( g_conf, "minimenu.last_known_parentcatname" ) ) {
3148         // in subcat view, we only have one cat
3149         ui_category = 0;
3150         ui_catshift = 0;
3151         // the cat name to search for is Child*Parent
3152         char key [ 512 ];
3153         sprintf ( key, "%s*%s",
3154                   pnd_conf_get_as_char ( g_conf, "minimenu.last_known_catname" )
3155                   , pnd_conf_get_as_char ( g_conf, "minimenu.last_known_parentcatname" ) );
3156         category_publish ( CFBYNAME, key );
3157         // since we forced it by hand, no need to do a cat-scan below
3158         catpick = NULL;
3159       }
3160
3161     } else if ( dc ) {
3162       catpick = dc;
3163     }
3164
3165     // attempt to find default cat; if we do find it, select it; otherwise
3166     // default behaviour will pick first cat (ie: usually All)
3167     if ( catpick ) {
3168       unsigned int i;
3169
3170       for ( i = 0; i < g_categorycount; i++ ) {
3171         if ( strcasecmp ( g_categories [ i ] -> catname, catpick ) == 0 ) {
3172           ui_category = i;
3173           // ensure visibility
3174           unsigned int screen_width = ui_display_context.screen_width;
3175           unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
3176           if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
3177             ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
3178           }
3179           break;
3180         }
3181       }
3182
3183       if ( i == g_categorycount ) {
3184         pnd_log ( pndn_warning, "  User defined default category or last known cat '%s' but not found, so using default behaviour\n", catpick );
3185       }
3186
3187     } // cat change?
3188
3189   } // default cat
3190
3191   // if we're sent right to a dirbrowser tab, restock it now (normally we restock on entry)
3192   if ( g_categories [ ui_category ] && g_categories [ ui_category ] -> fspath ) {
3193     printf ( "Restock on start: '%s'\n", g_categories [ ui_category ] -> fspath );
3194     category_fs_restock ( g_categories [ ui_category ] );
3195   }
3196
3197   // redraw all
3198   render_mask |= CHANGED_EVERYTHING;
3199
3200   // if deferred icon load, kick off the thread now
3201   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) == 1 ) {
3202     ui_start_defered_icon_thread();
3203   } // deferred icon load
3204
3205   return;
3206 }
3207
3208 unsigned char ui_threaded_defered_icon ( void *p ) {
3209   extern pnd_box_handle g_active_apps;
3210
3211   unsigned char maxwidth, maxheight;
3212   maxwidth = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 );
3213   maxheight = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 );
3214
3215   pnd_disco_t *iter;
3216
3217   g_icon_thread_busy = 1;
3218
3219   // work at it in order within current category
3220
3221   mm_appref_t *refiter = g_categories [ ui_category ] ? g_categories [ ui_category ] -> refs : NULL;
3222   while ( refiter && ! g_icon_thread_stop ) {
3223     iter = refiter -> ref;
3224
3225     // has an icon that is not already cached?
3226     if ( ( iter -> pnd_icon_pos ) ||
3227          ( iter -> icon && iter -> object_flags & PND_DISCO_LIBPND_DD )
3228        )
3229     {
3230   
3231       // try to cache it?
3232       if ( ! cache_icon ( iter, maxwidth, maxheight ) ) {
3233         //pnd_log ( pndn_warning, "  Couldn't load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
3234
3235       } else {
3236
3237         // trigger that we completed
3238         SDL_Event e;
3239         bzero ( &e, sizeof(SDL_Event) );
3240         e.type = SDL_USEREVENT;
3241         e.user.code = sdl_user_finishedicon;
3242         SDL_PushEvent ( &e );
3243
3244         //pnd_log ( pndn_warning, "  Finished deferred load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
3245         //usleep ( pnd_conf_get_as_int_d ( g_conf, "minimenu.defer_icon_us", 50000 ) );
3246
3247       }
3248
3249       // avoid churn
3250       iter -> pnd_icon_pos = 0;
3251       if ( iter -> icon ) {
3252         free ( iter -> icon );
3253         iter -> icon = NULL;
3254       }
3255
3256       // let user do something..
3257       SDL_Delay ( 200 );
3258
3259     } // has icon
3260
3261     refiter = refiter -> next;
3262   }
3263
3264   // mark as done
3265   g_icon_thread_busy = 0;
3266
3267   return ( 0 );
3268 }
3269
3270 void ui_show_hourglass ( unsigned char updaterect ) {
3271
3272   SDL_Rect dest;
3273   SDL_Surface *s = g_imagecache [ IMG_HOURGLASS ].i;
3274
3275   dest.x = ( 800 - s -> w ) / 2;
3276   dest.y = ( 480 - s -> h ) / 2;
3277
3278   SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, &dest );
3279
3280   if ( updaterect ) {
3281     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
3282   }
3283
3284   return;
3285 }
3286
3287 unsigned char ui_pick_skin ( void ) {
3288 #define MAXSKINS 10
3289   char *skins [ MAXSKINS ];
3290   unsigned char iter;
3291
3292   char *searchpath = pnd_conf_get_as_char ( g_conf, "minimenu.skin_searchpath" );
3293   char tempname [ 100 ];
3294
3295   iter = 0;
3296
3297   skins [ iter++ ] = "No skin change";
3298
3299   SEARCHPATH_PRE
3300   {
3301     DIR *d = opendir ( buffer );
3302
3303     if ( d ) {
3304       struct dirent *dd;
3305
3306       while ( ( dd = readdir ( d ) ) ) {
3307
3308         if ( dd -> d_name [ 0 ] == '.' ) {
3309           // ignore
3310         } else if ( ( dd -> d_type == DT_DIR || dd -> d_type == DT_UNKNOWN ) &&
3311                     iter < MAXSKINS )
3312         {
3313           snprintf ( tempname, 100, "Skin: %s", dd -> d_name );
3314           skins [ iter++ ] = strdup ( tempname );
3315         }
3316
3317       }
3318
3319       closedir ( d );
3320     }
3321
3322   }
3323   SEARCHPATH_POST
3324
3325   int sel = ui_modal_single_menu ( skins, iter, "Skins", "Enter to select; other to return." );
3326
3327   // did they pick one?
3328   if ( sel > 0 ) {
3329     FILE *f;
3330
3331     char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
3332     s = pnd_expand_tilde ( s );
3333
3334     f = fopen ( s, "w" );
3335
3336     free ( s );
3337
3338     if ( f ) {
3339       fprintf ( f, "%s\n", skins [ sel ] + 6 );
3340       fclose ( f );
3341     }
3342
3343     return ( 1 );
3344   }
3345
3346   return ( 0 );
3347 }
3348
3349 void ui_aboutscreen ( char *textpath ) {
3350 #define PIXELW 7
3351 #define PIXELH 7
3352 #define MARGINW 3
3353 #define MARGINH 3
3354 #define SCRW 800
3355 #define SCRH 480
3356 #define ROWS SCRH / ( PIXELH + MARGINH )
3357 #define COLS SCRW / ( PIXELW + MARGINW )
3358
3359   unsigned char pixelboard [ ROWS * COLS ]; // pixel heat
3360   bzero ( pixelboard, ROWS * COLS );
3361
3362   SDL_Surface *rtext;
3363   SDL_Rect r;
3364
3365   SDL_Color rtextc = { 200, 200, 200, 100 };
3366
3367   // pixel scroller
3368   char *textloop [ 500 ];
3369   unsigned int textmax = 0;
3370   bzero ( textloop, 500 * sizeof(char*) );
3371
3372   // cursor scroller
3373   char cbuffer [ 50000 ];
3374   bzero ( cbuffer, 50000 );
3375   unsigned int crevealed = 0;
3376
3377   FILE *f = fopen ( textpath, "r" );
3378
3379   if ( ! f ) {
3380     pnd_log ( pndn_error, "ERROR: Couldn't open about text: %s\n", textpath );
3381     return;
3382   }
3383
3384   char textbuf [ 100 ];
3385   while ( fgets ( textbuf, 100, f ) ) {
3386
3387     // add to full buffer
3388     strncat ( cbuffer, textbuf, 50000 );
3389
3390     // chomp
3391     if ( strchr ( textbuf, '\n' ) ) {
3392       * strchr ( textbuf, '\n' ) = '\0';
3393     }
3394
3395     // add to pixel loop
3396     if ( 1||textbuf [ 0 ] ) {
3397       textloop [ textmax ] = strdup ( textbuf );
3398       textmax++;
3399     }
3400
3401   } // while fgets
3402
3403   fclose ( f );
3404
3405   unsigned int textiter = 0;
3406   while ( textiter < textmax ) {
3407     char *text = textloop [ textiter ];
3408
3409     rtext = NULL;
3410     if ( text [ 0 ] ) {
3411       // render to surface
3412       rtext = TTF_RenderText_Blended ( g_grid_font, text, rtextc );
3413
3414       // render font to pixelboard
3415       unsigned int px, py;
3416       unsigned char *ph;
3417       unsigned int *pixels = rtext -> pixels;
3418       unsigned char cr, cg, cb, ca;
3419       for ( py = 0; py < rtext -> h; py ++ ) {
3420         for ( px = 0; px < ( rtext -> w > COLS ? COLS : rtext -> w ); px++ ) {
3421
3422           SDL_GetRGBA ( pixels [ ( py * rtext -> pitch / 4 ) + px ],
3423                         rtext -> format, &cr, &cg, &cb, &ca );
3424
3425           if ( ca != 0 ) {
3426
3427             ph = pixelboard + ( /*y offset */ 30 * COLS ) + ( py * COLS ) + px /* / 2 */;
3428
3429             if ( *ph < 100 ) {
3430               *ph = 100;
3431             }
3432
3433             ca /= 5;
3434             if ( *ph + ca < 250 ) {
3435               *ph += ca;
3436             }
3437
3438           } // got a pixel?
3439
3440         } // x
3441       } // y
3442
3443     } // got text?
3444
3445     unsigned int runcount = 10;
3446     while ( runcount-- ) {
3447
3448       // clear display
3449       SDL_FillRect( sdl_realscreen, NULL /* whole */, 0 );
3450
3451       // render pixelboard
3452       unsigned int x, y;
3453       unsigned int c;
3454       for ( y = 0; y < ROWS; y++ ) {
3455         for ( x = 0; x < COLS; x++ ) {
3456
3457           if ( 1||pixelboard [ ( y * COLS ) + x ] ) {
3458
3459             // position
3460             r.x = x * ( PIXELW + MARGINW );
3461             r.y = y * ( PIXELH + MARGINH );
3462             r.w = PIXELW;
3463             r.h = PIXELH;
3464             // heat -> colour
3465             c = SDL_MapRGB ( sdl_realscreen -> format, 100 /* r */, 0 /* g */, pixelboard [ ( y * COLS ) + x ] );
3466             // render
3467             SDL_FillRect( sdl_realscreen, &r /* whole */, c );
3468
3469           }
3470
3471         } // x
3472       } // y
3473
3474       // cool pixels
3475       unsigned char *pc = pixelboard;
3476       for ( y = 0; y < ROWS; y++ ) {
3477         for ( x = 0; x < COLS; x++ ) {
3478
3479           if ( *pc > 10 ) {
3480             (*pc) -= 3;
3481           }
3482
3483           pc++;
3484         } // x
3485       } // y
3486
3487       // slide pixels upwards
3488       memmove ( pixelboard, pixelboard + COLS, ( COLS * ROWS ) - COLS );
3489
3490       // render actual readable text
3491       {
3492
3493         // display up to cursor
3494         SDL_Rect dest;
3495         unsigned int cdraw = 0;
3496         SDL_Surface *cs;
3497         char ctb [ 2 ];
3498
3499         if ( crevealed > 200 ) {
3500           cdraw = crevealed - 200;
3501         }
3502
3503         dest.x = 400;
3504         dest.y = 20;
3505
3506         for ( ; cdraw < crevealed; cdraw++ ) {
3507           ctb [ 0 ] = cbuffer [ cdraw ];
3508           ctb [ 1 ] = '\0';
3509           // move over or down
3510           if ( cbuffer [ cdraw ] == '\n' ) {
3511             // EOL
3512             dest.x = 400;
3513             dest.y += 14;
3514
3515             if ( dest.y > 450 ) {
3516               dest.y = 450;
3517             }
3518
3519           } else {
3520             // draw the char
3521             cs = TTF_RenderText_Blended ( g_tab_font, ctb, rtextc );
3522             if ( cs ) {
3523               SDL_BlitSurface ( cs, NULL /* all */, sdl_realscreen, &dest );
3524               SDL_FreeSurface ( cs );
3525               // over
3526               dest.x += cs -> w;
3527             }
3528           }
3529
3530         }
3531
3532         dest.w = 10;
3533         dest.h = 20;
3534         SDL_FillRect ( sdl_realscreen, &dest /* whole */, 220 );
3535
3536         // increment cursor to next character
3537         if ( cbuffer [ crevealed ] != '\0' ) {
3538           crevealed++;
3539         }
3540
3541       } // draw cursor text
3542
3543       // reveal
3544       //
3545       SDL_UpdateRect ( sdl_realscreen, 0, 0, 0, 0 ); // whole screen
3546
3547       usleep ( 50000 );
3548
3549       // any button? if so, about
3550       {
3551         SDL_PumpEvents();
3552
3553         SDL_Event e;
3554
3555         if ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_EVENTMASK(/*SDL_KEYUP|*/SDL_KEYDOWN) ) > 0 ) {
3556           return;
3557         }
3558
3559       }
3560
3561     } // while cooling
3562
3563     if ( rtext ) {
3564       SDL_FreeSurface ( rtext );
3565     }
3566
3567     textiter++;
3568   } // while more text
3569
3570   // free up
3571   unsigned int i;
3572   for ( i = 0; i < textmax; i++ ) {
3573     if ( textloop [ i ] ) {
3574       free ( textloop [ i ] );
3575       textloop [ i ] = 0;
3576     }
3577   }
3578
3579   return;
3580 }
3581
3582 void ui_revealscreen ( void ) {
3583   char *labels [ 500 ];
3584   unsigned int labelmax = 0;
3585   unsigned int i;
3586   char fulllabel [ 200 ];
3587
3588   if ( ! category_count ( CFHIDDEN ) ) {
3589     return; // nothing to do
3590   }
3591
3592   // switch to hidden categories
3593   category_publish ( CFHIDDEN, NULL );
3594
3595   // build up labels to show in menu
3596   for ( i = 0; i < g_categorycount; i++ ) {
3597
3598     if ( g_categories [ i ] -> parent_catname ) {
3599       sprintf ( fulllabel, "%s [%s]", g_categories [ i ] -> catname, g_categories [ i ] -> parent_catname );
3600     } else {
3601       sprintf ( fulllabel, "%s", g_categories [ i ] -> catname );
3602     }
3603
3604     labels [ labelmax++ ] = strdup ( fulllabel );
3605   }
3606
3607   // show menu
3608   int sel = ui_modal_single_menu ( labels, labelmax, "Temporary Category Reveal",
3609                                    "Enter to select; other to return." );
3610
3611   // if selected, try to set this guy to visible
3612   if ( sel >= 0 ) {
3613
3614     // fix up category name, if its been hacked
3615 #if 0 // prepending and .. wtf crap is this
3616     if ( strchr ( g_categories [ sel ] -> catname, '.' ) ) {
3617       char *t = g_categories [ sel ] -> catname;
3618       g_categories [ sel ] -> catname = strdup ( strchr ( g_categories [ sel ] -> catname, '.' ) + 1 );
3619       free ( t );
3620     }
3621 #endif
3622
3623     // reflag this guy to be visible
3624     g_categories [ sel ] -> catflags = CFNORMAL;
3625
3626     // switch to the new category.. cache name.
3627     char *switch_to_name = g_categories [ sel ] -> catname;
3628
3629     // republish categories
3630     category_publish ( CFNORMAL, NULL );
3631
3632     // switch to the new category.. with the cached name!
3633     ui_category = category_index ( switch_to_name );
3634
3635     // ensure visibility
3636     unsigned int screen_width = ui_display_context.screen_width;
3637     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
3638     if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
3639       ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
3640     }
3641
3642   }
3643
3644   // republish categories
3645   category_publish ( CFNORMAL, NULL );
3646
3647   // redraw tabs
3648   render_mask |= CHANGED_CATEGORY;
3649   ui_start_defered_icon_thread();
3650
3651   return;
3652 }
3653
3654 void ui_recache_context ( ui_context_t *c ) {
3655
3656   c -> screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
3657
3658   c -> font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
3659   c -> font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
3660   c -> font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
3661   c -> font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
3662
3663   c -> grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
3664   c -> grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
3665
3666   c -> icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
3667   c -> icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
3668   c -> icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
3669   c -> icon_max_height = pnd_conf_get_as_int ( g_conf, "grid.icon_max_height" );
3670   c -> sel_icon_offset_x = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_x", 0 );
3671   c -> sel_icon_offset_y = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_y", 0 );
3672
3673   c -> text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
3674   c -> text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
3675   c -> text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
3676   c -> text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
3677   c -> text_hilite_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
3678
3679   c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 4 );
3680   c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
3681
3682   c -> cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
3683   c -> cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
3684
3685   c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
3686   c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
3687   c -> arrow_bar_clip_w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
3688   c -> arrow_bar_clip_h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
3689   c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
3690   c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
3691   c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
3692   c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
3693
3694   // font colour
3695   SDL_Color tmp = { c -> font_rgba_r, c -> font_rgba_g, c -> font_rgba_b, c -> font_rgba_a };
3696   c -> fontcolor = tmp;
3697
3698   // determine font height
3699   if ( g_grid_font ) {
3700     SDL_Surface *rtext;
3701     rtext = TTF_RenderText_Blended ( g_grid_font, "M", c -> fontcolor );
3702     c -> text_height = rtext -> h;
3703   } else {
3704     c -> text_height = 10;
3705   }
3706
3707   if ( g_tab_font ) {
3708     SDL_Surface *rtext;
3709     rtext = TTF_RenderText_Blended ( g_tab_font, "M", c -> fontcolor );
3710     c -> text_height_tab = rtext -> h;
3711   } else {
3712     c -> text_height_tab = 15;
3713   }
3714
3715   // now that we've got 'normal' (detail pane shown) param's, lets check if detail pane
3716   // is hidden; if so, override some values with those alternate skin values where possible.
3717   if ( ui_detail_hidden ) {
3718     // if detail panel is hidden, and theme cannot support it, unhide the bloody thing. (This may help
3719     // when someone is amid theme hacking or changing.)
3720     if ( ! ui_is_detail_hideable() ) {
3721       ui_detail_hidden = 0;
3722     }
3723
3724     // still hidden?
3725     if ( ui_detail_hidden ) {
3726
3727       c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max_w", c -> row_max );
3728       c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max_w", c -> col_max );
3729
3730       c -> cell_width = pnd_conf_get_as_int_d ( g_conf, "grid.cell_width_w", c -> cell_width );
3731       c -> cell_height = pnd_conf_get_as_int_d ( g_conf, "grid.cell_height_w", c -> cell_height );
3732
3733       c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x_w", 450 );
3734       c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y_w", 100 );
3735       c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x_w", 450 );
3736       c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y_w", 80 );
3737       c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x_w", 450 );
3738       c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y_w", 80 );
3739
3740     } // if detail hidden.. still.
3741
3742   } // if detail hidden
3743
3744   return;
3745 }
3746
3747 unsigned char ui_is_detail_hideable ( void ) {
3748
3749   // if skin has a bit of info for wide-mode, we assume wide-mode is available
3750   if ( pnd_conf_get_as_char ( g_conf, "grid.row_max_w" ) != NULL ) {
3751     return ( 1 );
3752   }
3753
3754   // else not!
3755   return ( 0 );
3756 }
3757
3758 void ui_toggle_detail_pane ( void ) {
3759
3760   // no bitmask trickery here; I like it to be stand-out obvious at 3am.
3761
3762   if ( ui_detail_hidden ) {
3763     ui_detail_hidden = 0;
3764   } else {
3765     ui_detail_hidden = 1;
3766   }
3767
3768   // repull skin config
3769   ui_recache_context ( &ui_display_context );
3770
3771   // redraw
3772   render_mask |= CHANGED_EVERYTHING;
3773
3774   return;
3775 }
3776
3777 void ui_menu_context ( mm_appref_t *a ) {
3778
3779   unsigned char rescan_apps = 0;
3780   unsigned char context_alive = 1;
3781
3782   enum {
3783     context_done = 0,
3784     context_file_info,
3785     context_file_delete,
3786     context_app_info,
3787     context_app_hide,
3788     context_app_recategorize,
3789     context_app_recategorize_sub,
3790     context_app_rename,
3791     context_app_cpuspeed,
3792     context_app_run,
3793     context_app_notes1,
3794     context_app_notes2,
3795     context_app_notes3,
3796     context_menu_max
3797   };
3798
3799   char *verbiage[] = {
3800     "Done (return to grid)",      // context_done
3801     "Get info about file/dir",    // context_file_info
3802     "Delete file/dir",            // context_file_delete
3803     "Get info",                   // context_app_info
3804     "Hide application",           //             hide
3805     "Recategorize",               //             recategorize
3806     "Recategorize subcategory",   //             recategorize
3807     "Change displayed title",     //             rename
3808     "Set CPU speed for launch",   //             cpuspeed
3809     "Run application",            //             run
3810     "Edit notes line 1",          //             notes1
3811     "Edit notes line 2",          //             notes2
3812     "Edit notes line 3",          //             notes3
3813   };
3814
3815   unsigned short int menu [ context_menu_max ];
3816   char *menustring [ context_menu_max ];
3817   unsigned char menumax = 0;
3818
3819   // ++ done
3820   menu [ menumax ] = context_done; menustring [ menumax++ ] = verbiage [ context_done ];
3821
3822   // hook up appropriate menu options based on tab-type and object-type
3823   if ( g_categories [ ui_category ] -> fspath ) {
3824     return; // TBD
3825     menu [ menumax ] = context_file_info; menustring [ menumax++ ] = verbiage [ context_file_info ];
3826     menu [ menumax ] = context_file_delete; menustring [ menumax++ ] = verbiage [ context_file_delete ];
3827   } else {
3828
3829     if ( a -> ref -> object_type == pnd_object_type_directory ) {
3830       return; // don't do anything if the guy is a subcat-as-folder
3831     }
3832
3833     //menu [ menumax ] = context_app_info; menustring [ menumax++ ] = verbiage [ context_app_info ];
3834     menu [ menumax ] = context_app_run; menustring [ menumax++ ] = verbiage [ context_app_run ];
3835     menu [ menumax ] = context_app_hide; menustring [ menumax++ ] = verbiage [ context_app_hide ];
3836     menu [ menumax ] = context_app_recategorize; menustring [ menumax++ ] = verbiage [ context_app_recategorize ];
3837     menu [ menumax ] = context_app_recategorize_sub; menustring [ menumax++ ] = verbiage [ context_app_recategorize_sub ];
3838     menu [ menumax ] = context_app_rename; menustring [ menumax++ ] = verbiage [ context_app_rename ];
3839     menu [ menumax ] = context_app_cpuspeed; menustring [ menumax++ ] = verbiage [ context_app_cpuspeed ];
3840     menu [ menumax ] = context_app_notes1; menustring [ menumax++ ] = verbiage [ context_app_notes1 ];
3841     menu [ menumax ] = context_app_notes2; menustring [ menumax++ ] = verbiage [ context_app_notes2 ];
3842     menu [ menumax ] = context_app_notes3; menustring [ menumax++ ] = verbiage [ context_app_notes3 ];
3843   }
3844
3845   // operate the menu
3846   while ( context_alive ) {
3847
3848     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 */ );
3849
3850     if ( sel < 0 ) {
3851       context_alive = 0;
3852
3853     } else {
3854
3855       switch ( menu [ sel ] ) {
3856
3857       case context_done:
3858         context_alive = 0;
3859         break;
3860
3861       case context_file_info:
3862         break;
3863
3864       case context_file_delete:
3865         //ui_menu_twoby ( "Delete - Are you sure?", "B/enter; other to cancel", "Delete", "Do not delete" );
3866         break;
3867
3868       case context_app_info:
3869         break;
3870
3871       case context_app_hide:
3872         {
3873           // determine key
3874           char confkey [ 1000 ];
3875           snprintf ( confkey, 999, "%s.%s", "appshow", a -> ref -> unique_id );
3876
3877           // turn app 'off'
3878           pnd_conf_set_char ( g_conf, confkey, "0" );
3879
3880           // write conf, so it will take next time
3881           conf_write ( g_conf, conf_determine_location ( g_conf ) );
3882           conf_write ( g_conf, CONF_PREF_TEMPPATH );
3883
3884           // can we just 'hide' this guy without reloading all apps? (this is for you, EvilDragon)
3885           if ( 0 ) {
3886             //
3887             // DOESN'T WORK YET; other parts of app are still hanging onto some values and blow up
3888             //
3889             char *uid = strdup ( a -> ref -> unique_id );
3890             unsigned int i;
3891             for ( i = 0; i < g_categorycount; i++ ) {
3892               mm_appref_t *p = g_categories [ i ] -> refs;
3893               mm_appref_t *n;
3894               while ( p ) {
3895                 n = p -> next;
3896
3897                 if ( strcmp ( p -> ref -> unique_id, uid ) == 0 ) {
3898                   free ( p );
3899                   if ( g_categories [ i ] -> refcount ) {
3900                     g_categories [ i ] -> refcount--;
3901                   }
3902                 }
3903
3904                 p = n;
3905               } // while for each appref
3906             } // for each cat/tab
3907
3908             free ( uid );
3909
3910           } else {
3911             // request rescan and wrap up
3912             rescan_apps++;
3913           }
3914
3915           context_alive = 0; // nolonger visible, so lets just get out
3916
3917         }
3918
3919         break;
3920
3921       case context_app_recategorize:
3922         {
3923           char *opts [ 250 ];
3924           unsigned char optmax = 0;
3925           unsigned char i;
3926
3927           // show custom categories
3928           if ( mmcustom_setup() ) {
3929
3930             for ( i = 0; i < mmcustom_count; i++ ) {
3931               if ( mmcustom_complete [ i ].parent_cat == NULL ) {
3932                 opts [ optmax++ ] = mmcustom_complete [ i ].cat;
3933               }
3934             }
3935
3936           }
3937
3938           // show FD categories
3939           i = 2; // skip first two - Other and NoParentCategory
3940           while ( 1 ) {
3941
3942             if ( ! freedesktop_complete [ i ].cat ) {
3943               break;
3944             }
3945
3946             if ( ! freedesktop_complete [ i ].parent_cat ) {
3947               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
3948             }
3949
3950             i++;
3951           } // while
3952
3953           // picker
3954           char prompt [ 101 ];
3955           snprintf ( prompt, 100, "Pick category [%s]", a -> ref -> main_category ? a -> ref -> main_category : "NoParentCategory" );
3956
3957           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select parent category"*/, "Enter to select; other to skip." );
3958
3959           if ( sel >= 0 ) {
3960             char confirm [ 1001 ];
3961             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
3962
3963             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm categorization", "Do not set category" ) == 1 ) {
3964               ovr_replace_or_add ( a, "maincategory", opts [ sel ] );
3965               rescan_apps++;
3966               // when changing main cat, reset subcat, otherwise you go from Game/Emu to Network/Emu and get sent to Other right away
3967               ovr_replace_or_add ( a, "maincategorysub1", freedesktop_complete [ 2 ].cat );
3968             }
3969
3970           }
3971
3972           if ( mmcustom_is_ready() ) {
3973             mmcustom_shutdown();
3974           }
3975
3976         }
3977         break;
3978
3979       case context_app_recategorize_sub:
3980         {
3981           char *opts [ 250 ];
3982           unsigned char optmax = 0;
3983           unsigned char i = 0;
3984
3985           char *whichparentarewe;
3986           if ( g_categories [ ui_category ] -> parent_catname ) {
3987             whichparentarewe = g_categories [ ui_category ] -> parent_catname;
3988           } else {
3989             whichparentarewe = g_categories [ ui_category ] -> catname;
3990           }
3991
3992           // add NoSubcategory magic one
3993           opts [ optmax++ ] = freedesktop_complete [ 2 ].cat;
3994
3995           // add custom categories
3996           if ( mmcustom_setup() ) {
3997
3998             for ( i = 0; i < mmcustom_count; i++ ) {
3999               if ( mmcustom_complete [ i ].parent_cat && strcmp ( mmcustom_complete [ i ].parent_cat, whichparentarewe ) == 0  ) {
4000                 opts [ optmax++ ] = mmcustom_complete [ i ].cat;
4001               }
4002             }
4003
4004           }
4005
4006           // add FD categories
4007           while ( 1 ) {
4008
4009             if ( ! freedesktop_complete [ i ].cat ) {
4010               break;
4011             }
4012
4013             if ( ( freedesktop_complete [ i ].parent_cat ) &&
4014                  ( strcasecmp ( freedesktop_complete [ i ].parent_cat, whichparentarewe ) == 0 )
4015                )
4016             {
4017               opts [ optmax++ ] = freedesktop_complete [ i ].cat;
4018             }
4019
4020             i++;
4021           } // while
4022
4023           char prompt [ 101 ];
4024           //snprintf ( prompt, 100, "Currently: %s", a -> ref -> main_category1 ? a -> ref -> main_category1 : "NoSubcategory" );
4025           snprintf ( prompt, 100, "%s [%s]", a -> ref -> main_category1 ? a -> ref -> main_category1 : "NoSubcategory", whichparentarewe );
4026
4027           int sel = ui_modal_single_menu ( opts, optmax, prompt /*"Select subcategory"*/, "Enter to select; other to skip." );
4028
4029           if ( sel >= 0 ) {
4030             char confirm [ 1001 ];
4031             snprintf ( confirm, 1000, "Confirm: %s", opts [ sel ] );
4032
4033             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm sub-categorization", "Do not set sub-category" ) == 1 ) {
4034               ovr_replace_or_add ( a, "maincategorysub1", opts [ sel ] );
4035               rescan_apps++;
4036             }
4037
4038           }
4039
4040           if ( mmcustom_is_ready() ) {
4041             mmcustom_shutdown();
4042           }
4043
4044         }
4045         break;
4046
4047       case context_app_rename:
4048         {
4049           char namebuf [ 101 ];
4050           unsigned char changed;
4051
4052           changed = ui_menu_get_text_line ( "Rename application", "Use keyboard; Enter when done.",
4053                                             a -> ref -> title_en ? a -> ref -> title_en : "blank", namebuf, 30, 0 /* alphanumeric */ );
4054
4055           if ( changed ) {
4056             char confirm [ 1001 ];
4057             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
4058
4059             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm rename", "Do not rename" ) == 1 ) {
4060               ovr_replace_or_add ( a, "title", namebuf );
4061               rescan_apps++;
4062             }
4063
4064           }
4065
4066         }
4067
4068         break;
4069
4070       case context_app_cpuspeed:
4071         {
4072           char namebuf [ 101 ];
4073           unsigned char changed;
4074
4075           changed = ui_menu_get_text_line ( "Specify runspeed", "Use keyboard; Enter when done.",
4076                                             a -> ref -> clockspeed ? a -> ref -> clockspeed : "500", namebuf, 6, 1 /* numeric */ );
4077
4078           if ( changed ) {
4079             char confirm [ 1001 ];
4080             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
4081
4082             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm clockspeed", "Do not set" ) == 1 ) {
4083               ovr_replace_or_add ( a, "clockspeed", namebuf );
4084               rescan_apps++;
4085             }
4086
4087           }
4088
4089         }
4090
4091         break;
4092
4093       case context_app_run:
4094         ui_push_exec();
4095         break;
4096
4097       case context_app_notes1:
4098       case context_app_notes2:
4099       case context_app_notes3:
4100         {
4101           char namebuf [ 101 ] = "";
4102
4103           char key [ 501 ];
4104           unsigned char notenum;
4105
4106           // which note line?
4107           if ( menu [ sel ] == context_app_notes1 ) {
4108             notenum = 1;
4109           } else if ( menu [ sel ] == context_app_notes2 ) {
4110             notenum = 2;
4111           } else if ( menu [ sel ] == context_app_notes3 ) {
4112             notenum = 3;
4113           }
4114
4115           // figure out key for looking up existing, and for storing replacement
4116           snprintf ( key, 500, "Application-%u.note-%u", a -> ref -> subapp_number, notenum );
4117
4118           // do we have existing value?
4119           if ( a -> ovrh ) {
4120             char *existing = pnd_conf_get_as_char ( a -> ovrh, key );
4121             if ( existing ) {
4122               strncpy ( namebuf, existing, 100 );
4123             }
4124           }
4125
4126           unsigned char changed;
4127
4128           changed = ui_menu_get_text_line ( "Enter replacement note", "Use keyboard; Enter when done.",
4129                                             namebuf, namebuf, 30, 0 /* not-numeric-forced */ );
4130
4131           if ( changed ) {
4132             ovr_replace_or_add ( a, strchr ( key, '.' ) + 1, namebuf );
4133             rescan_apps++;
4134           }
4135
4136         }
4137         break;
4138
4139       default:
4140         return;
4141
4142       } // switch
4143
4144     } // if useful return
4145
4146   } // menu is alive?
4147
4148   // rescan apps?
4149   if ( rescan_apps ) {
4150     applications_free();
4151     applications_scan();
4152   }
4153
4154   return;
4155 }
4156
4157 unsigned char ui_menu_oneby ( char *title, char *footer, char *one ) {
4158   char *opts [ 2 ];
4159   opts [ 0 ] = one;
4160   int sel = ui_modal_single_menu ( opts, 1, title, footer );
4161   if ( sel < 0 ) {
4162     return ( 0 );
4163   }
4164   return ( sel + 1 );
4165 }
4166
4167 unsigned char ui_menu_twoby ( char *title, char *footer, char *one, char *two ) {
4168   char *opts [ 3 ];
4169   opts [ 0 ] = one;
4170   opts [ 1 ] = two;
4171   int sel = ui_modal_single_menu ( opts, 2, title, footer );
4172   if ( sel < 0 ) {
4173     return ( 0 );
4174   }
4175   return ( sel + 1 );
4176 }
4177
4178 unsigned char ui_menu_get_text_line ( char *title, char *footer, char *initialvalue,
4179                                       char *r_buffer, unsigned char maxlen, unsigned char numbersonlyp )
4180 {
4181   SDL_Rect rects [ 40 ];
4182   SDL_Rect *dest = rects;
4183   SDL_Rect src;
4184   SDL_Surface *rtext;
4185
4186   char hacktext [ 1024 ];
4187   unsigned char shifted = 0;
4188
4189   bzero ( rects, sizeof(SDL_Rect) * 40 );
4190
4191   if ( initialvalue ) {
4192     if ( initialvalue == r_buffer ) {
4193       // already good to go
4194     } else {
4195       strncpy ( r_buffer, initialvalue, maxlen );
4196     }
4197   } else {
4198     bzero ( r_buffer, maxlen );
4199   }
4200
4201   while ( 1 ) {
4202
4203     // clear
4204     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
4205     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
4206     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
4207     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
4208     SDL_FillRect( sdl_realscreen, dest, 0 );
4209
4210     // show dialog background
4211     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
4212       src.x = 0;
4213       src.y = 0;
4214       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
4215       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
4216       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
4217       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
4218       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
4219       dest++;
4220     }
4221
4222     // show dialog frame
4223     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
4224       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
4225       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
4226       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
4227       dest++;
4228     }
4229
4230     // show header
4231     if ( title ) {
4232       rtext = TTF_RenderText_Blended ( g_tab_font, title, ui_display_context.fontcolor );
4233       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
4234       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
4235       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
4236       SDL_FreeSurface ( rtext );
4237       dest++;
4238     }
4239
4240     // show footer
4241     if ( footer ) {
4242       rtext = TTF_RenderText_Blended ( g_tab_font, footer, ui_display_context.fontcolor );
4243       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
4244       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
4245         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
4246         - 60;
4247       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
4248       SDL_FreeSurface ( rtext );
4249       dest++;
4250     }
4251
4252     // show text line - and embed cursor
4253     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
4254     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( 0/*i*/ + 1 - 0/*first_visible*/ ) );
4255
4256     strncpy ( hacktext, r_buffer, 1000 );
4257     strncat ( hacktext, "\n", 1000 ); // add [] in most fonts
4258
4259     rtext = TTF_RenderText_Blended ( g_tab_font, hacktext, ui_display_context.fontcolor );
4260     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
4261     SDL_FreeSurface ( rtext );
4262     dest++;
4263
4264     // update all the rects and send it all to sdl
4265     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
4266     dest = rects;
4267
4268     // check for input
4269     SDL_Event event;
4270     while ( SDL_WaitEvent ( &event ) ) {
4271
4272       switch ( event.type ) {
4273
4274       case SDL_KEYUP:
4275         if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
4276           shifted = 0;
4277         }
4278         break;
4279
4280       case SDL_KEYDOWN:
4281
4282         if ( event.key.keysym.sym == SDLK_LEFT || event.key.keysym.sym == SDLK_BACKSPACE ) {
4283           if ( strlen ( r_buffer ) > 0 ) {
4284             char *eol = strchr ( r_buffer, '\0' );
4285             *( eol - 1 ) = '\0';
4286           }
4287
4288         } else if ( event.key.keysym.sym == SDLK_UP ) {
4289           r_buffer [ 0 ] = '\0'; // truncate!
4290
4291         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
4292           // on Enter/Return or B, if the buffer has 1 or more chars, we return it as valid.. otherwise, invalid.
4293           if ( strlen ( r_buffer ) > 0 ) {
4294             return ( 1 );
4295           }
4296           return ( 0 );
4297
4298         } else if ( event.key.keysym.sym == SDLK_LSHIFT || event.key.keysym.sym == SDLK_RSHIFT ) {
4299           shifted = 1;
4300
4301         } else if ( event.key.keysym.sym == SDLK_ESCAPE ||
4302                     event.key.keysym.sym == SDLK_PAGEUP ||
4303                     event.key.keysym.sym == SDLK_PAGEDOWN ||
4304                     event.key.keysym.sym == SDLK_HOME
4305                   )
4306         {
4307           return ( 0 );
4308
4309         } else {
4310
4311           if ( isprint(event.key.keysym.sym) ) {
4312
4313             unsigned char good = 1;
4314
4315             if ( numbersonlyp && ( ! isdigit(event.key.keysym.sym) ) ) {
4316               good = 0;
4317             }
4318
4319             if ( maxlen && strlen(r_buffer) >= maxlen ) {
4320               good = 0;
4321             }
4322
4323             if ( good ) {
4324               char b [ 2 ] = { '\0', '\0' };
4325               if ( shifted ) {
4326                 b [ 0 ] = toupper ( event.key.keysym.sym );
4327               } else {
4328                 b [ 0 ] = event.key.keysym.sym;
4329               }
4330               strncat ( r_buffer, b, maxlen );
4331             } // good?
4332
4333           } // printable?
4334
4335         }
4336
4337         break;
4338
4339       } // switch
4340
4341       break;
4342
4343     } // while waiting for input
4344
4345   } // while
4346
4347   return ( 0 );
4348 }
4349
4350 unsigned char ovr_replace_or_add ( mm_appref_t *a, char *keybase, char *newvalue ) {
4351   //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 );
4352
4353   char fullpath [ PATH_MAX ];
4354
4355   sprintf ( fullpath, "%s/%s", a -> ref -> object_path, a -> ref -> object_filename );
4356   char *dot = strrchr ( fullpath, '.' );
4357   if ( dot ) {
4358     sprintf ( dot, PXML_SAMEPATH_OVERRIDE_FILEEXT );
4359   } else {
4360     pnd_log ( pndn_error, "ERROR: Bad pnd-path in disco_t! %s\n", fullpath );
4361     return ( 0 );
4362   }
4363
4364   struct stat statbuf;
4365
4366   if ( stat ( fullpath, &statbuf ) == 0 ) {
4367     // file exists
4368     pnd_conf_handle h;
4369
4370     h = pnd_conf_fetch_by_path ( fullpath );
4371
4372     if ( ! h ) {
4373       return ( 0 ); // fail!
4374     }
4375
4376     char key [ 101 ];
4377     snprintf ( key, 100, "Application-%u.%s", a -> ref -> subapp_number, keybase );
4378
4379     pnd_conf_set_char ( h, key, newvalue );
4380
4381     return ( pnd_conf_write ( h, fullpath ) );
4382
4383   } else {
4384     // file needs to be created - easy!
4385
4386     FILE *f = fopen ( fullpath, "w" );
4387
4388     if ( f ) {
4389       fprintf ( f, "Application-%u.%s\t%s\n", a -> ref -> subapp_number, keybase, newvalue );
4390       fclose ( f );
4391
4392     } else {
4393       return ( 0 ); // fail!
4394     }
4395
4396   } // new or used?
4397
4398   return ( 1 );
4399 }
4400
4401 void ui_manage_categories ( void ) {
4402   unsigned char require_app_scan = 0;
4403
4404   if ( ! mmcustom_setup() ) {
4405     return; // error
4406   }
4407
4408   char *opts [ 20 ] = {
4409     "List custom categories",
4410     "List custom subcategories",
4411     "Register custom category",
4412     "Register custom subcategory",
4413     "Unregister custom category",
4414     "Unregister custom subcategory",
4415     "Done"
4416   };
4417
4418   while ( 1 ) {
4419
4420     int sel = ui_modal_single_menu ( opts, 7, "Custom Categories", "B to select; other to cancel." );
4421
4422     switch ( sel ) {
4423
4424     case 0: // list custom
4425       ui_pick_custom_category ( 0 );
4426       break;
4427
4428     case 1: // list custom sub
4429       if ( mmcustom_count ) {
4430
4431         char *maincat = ui_pick_custom_category ( 2 );
4432
4433         if ( maincat ) {
4434           unsigned int subcount = mmcustom_count_subcats ( maincat );
4435           char titlebuf [ 201 ];
4436
4437           snprintf ( titlebuf, 200, "Category: %s", maincat );
4438
4439           if ( subcount == 0 ) {
4440             ui_menu_oneby ( titlebuf, "B/Enter to accept", "Category has no subcategories." );
4441           } else {
4442
4443             char **list = malloc ( subcount * sizeof(char*) );
4444             int i;
4445             unsigned int counter = 0;
4446
4447             for ( i = 0; i < mmcustom_count; i++ ) {
4448               if ( mmcustom_complete [ i ].parent_cat && strcasecmp ( mmcustom_complete [ i ].parent_cat, maincat ) == 0 ) {
4449                 list [ counter++ ] = mmcustom_complete [ i ].cat;
4450               }
4451             }
4452
4453             ui_modal_single_menu ( list, counter, titlebuf, "Any button to exit." );
4454
4455             free ( list );
4456
4457           } // more than 0 subcats?
4458
4459         } // user picked a main cat?
4460
4461       } else {
4462         ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4463       }
4464       break;
4465
4466     case 2: // register custom
4467       {
4468         unsigned char changed;
4469         char namebuf [ 101 ] = "";
4470
4471         changed = ui_menu_get_text_line ( "Enter unique category name", "Use keyboard; Enter when done.",
4472                                           "Pandora", namebuf, 30, 0 /* alphanumeric */ );
4473
4474         // did the user enter something?
4475         if ( changed ) {
4476
4477           // for now, force use of '*' into something else as we use * internally :/ (FIXME)
4478           {
4479             char *fixme;
4480             while ( ( fixme = strchr ( namebuf, '*' ) ) ) {
4481               *fixme = '_';
4482             }
4483           }
4484
4485           // and if so, is it existant already or not?
4486           if ( mmcustom_query ( namebuf, NULL ) ) {
4487             ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a registered category." );
4488           } else if ( freedesktop_category_query ( namebuf, NULL ) ) {
4489             ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a Standard category." );
4490           } else {
4491
4492             char confirm [ 1001 ];
4493             snprintf ( confirm, 1000, "Confirm: %s", namebuf );
4494
4495             if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm new category", "Do not register" ) == 1 ) {
4496               // register, save, recycle the current list
4497               mmcustom_register ( namebuf, NULL );
4498               mmcustom_write ( NULL );
4499               mmcustom_shutdown();
4500               mmcustom_setup();
4501             }
4502
4503           } // dupe?
4504
4505         } // entered something?
4506
4507       }
4508       break;
4509
4510     case 3: // register custom sub
4511       if ( 1 /*mmcustom_count -- we allow FD cats now, so this isn't applicable error */ ) {
4512
4513         char *maincat = ui_pick_custom_category ( 1 /* include FD */ );
4514
4515         if ( maincat ) {
4516           char titlebuf [ 201 ];
4517
4518           snprintf ( titlebuf, 200, "Subcat of: %s", maincat );
4519
4520           unsigned char changed;
4521           char namebuf [ 101 ] = "";
4522
4523           changed = ui_menu_get_text_line ( titlebuf, "Use keyboard; Enter when done.", "Submarine", namebuf, 30, 0 /* alphanumeric */ );
4524
4525           // did the user enter something?
4526           if ( changed ) {
4527
4528             // for now, force use of '*' into something else as we use * internally :/ (FIXME)
4529             {
4530               char *fixme;
4531               while ( ( fixme = strchr ( namebuf, '*' ) ) ) {
4532                 *fixme = '_';
4533               }
4534             }
4535
4536             // and if so, is it existant already or not?
4537             if ( mmcustom_query ( namebuf, maincat ) ) {
4538               ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a subcategory." );
4539             } else if ( freedesktop_category_query ( namebuf, maincat ) ) {
4540               ui_menu_oneby ( "Warning", "B/Enter to accept", "Already a Standard subcategory." );
4541             } else {
4542
4543               char confirm [ 1001 ];
4544               snprintf ( confirm, 1000, "Confirm: %s [%s]", namebuf, maincat );
4545
4546               if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm new category", "Do not register" ) == 1 ) {
4547                 // register, save, recycle the current list
4548                 mmcustom_register ( namebuf, maincat );
4549                 mmcustom_write ( NULL );
4550                 mmcustom_shutdown();
4551                 mmcustom_setup();
4552               }
4553
4554             } // dupe?
4555
4556           } // entered something?
4557
4558         } // selected parent cat?
4559
4560       } else {
4561         ui_menu_oneby ( "Warning", "B/Enter to accept", "No categories registered." );
4562       }
4563       break;
4564
4565     case 4: // unreg custom
4566       if ( mmcustom_count ) {
4567         char *maincat = ui_pick_custom_category ( 0 );
4568
4569         if ( maincat ) {
4570           char confirm [ 1001 ];
4571           snprintf ( confirm, 1000, "Confirm remove: %s", maincat );
4572
4573           if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm unregister", "Do not unregister" ) == 1 ) {
4574             // register, save, recycle the current list
4575             mmcustom_unregister ( maincat, NULL );
4576             mmcustom_write ( NULL );
4577             mmcustom_shutdown();
4578             mmcustom_setup();
4579           }
4580
4581         } // picked?
4582
4583       } else {
4584         ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4585       }
4586       break;
4587
4588     case 5: // unreg custom sub
4589       if ( mmcustom_count ) {
4590         char *maincat = ui_pick_custom_category ( 2 );
4591
4592         if ( maincat ) {
4593           unsigned int subcount = mmcustom_count_subcats ( maincat );
4594           char titlebuf [ 201 ];
4595
4596           snprintf ( titlebuf, 200, "Category: %s", maincat );
4597
4598           if ( subcount == 0 ) {
4599             ui_menu_oneby ( titlebuf, "B/Enter to accept", "Category has no subcategories." );
4600           } else {
4601
4602             char **list = malloc ( subcount * sizeof(char*) );
4603             int i;
4604             unsigned int counter = 0;
4605
4606             for ( i = 0; i < mmcustom_count; i++ ) {
4607               if ( mmcustom_complete [ i ].parent_cat && strcasecmp ( mmcustom_complete [ i ].parent_cat, maincat ) == 0 ) {
4608                 list [ counter++ ] = mmcustom_complete [ i ].cat;
4609               }
4610             }
4611
4612             int sel = ui_modal_single_menu ( list, counter, titlebuf, "B to selct; other to exit." );
4613
4614             if ( sel >= 0 ) {
4615               char confirm [ 1001 ];
4616               snprintf ( confirm, 1000, "Confirm remove: %s", list [ sel ] );
4617
4618               if ( ui_menu_twoby ( confirm, "B/enter; other to cancel", "Confirm unregister", "Do not unregister" ) == 1 ) {
4619                 // register, save, recycle the current list
4620                 mmcustom_unregister ( list [ sel ], maincat );
4621                 mmcustom_write ( NULL );
4622                 mmcustom_shutdown();
4623                 mmcustom_setup();
4624               }
4625
4626             } // confirm kill?
4627
4628             free ( list );
4629
4630           } // more than 0 subcats?
4631
4632         } // user picked a main cat?
4633
4634       } else {
4635         ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4636       }
4637       break;
4638
4639     } // switch
4640
4641     // exeunt
4642     if ( sel < 0 || sel > 5 ) {
4643       break;
4644     }
4645
4646   } // while running the menu
4647
4648   // shut down custom cats
4649   mmcustom_shutdown();
4650
4651   // reload apps?
4652   if ( require_app_scan ) {
4653     applications_free();
4654     applications_scan();
4655   }
4656
4657   // redraw
4658   render_mask |= CHANGED_EVERYTHING;
4659
4660   return;
4661 }
4662
4663 // mode 0 == custom main only; 1 == custom main + FD main; 2 == custom main + FD mains-with-custom-subs
4664 char *ui_pick_custom_category ( unsigned char mode ) {
4665   char **list;
4666   int i;
4667   unsigned int counter = 0;
4668
4669   // alloc space for list, depending on scope
4670   if ( mode > 0 ) {
4671     list = malloc ( (mmcustom_count+freedesktop_count()) * sizeof(char*) );
4672   } else {
4673     list = malloc ( mmcustom_count * sizeof(char*) );
4674   }
4675
4676   // add custom mains
4677   for ( i = 0; i < mmcustom_count; i++ ) {
4678     if ( mmcustom_complete [ i ].parent_cat == NULL ) {
4679       list [ counter++ ] = mmcustom_complete [ i ].cat;
4680     }
4681   }
4682
4683   // add FD if needed
4684   if ( mode > 0 ) {
4685     i = 3;
4686
4687     while ( 1 ) {
4688
4689       if ( ! freedesktop_complete [ i ].cat ) {
4690         break;
4691       }
4692
4693       // if FD main cat
4694       if ( freedesktop_complete [ i ].parent_cat == NULL ) {
4695
4696         // mode 1 == include them all
4697         // mode 2 == include them if they have a custom subcat
4698         if ( ( mode == 1 ) ||
4699              ( mmcustom_subcount ( freedesktop_complete [ i ].cat ) ) )
4700         {
4701           list [ counter++ ] = freedesktop_complete [ i ].cat;
4702         }
4703
4704       } // if parent cat
4705
4706       i++;
4707     } // while
4708
4709   } // if
4710
4711   // we actually showing anything?
4712   if ( ! counter ) {
4713     ui_menu_oneby ( "Warning", "B/Enter to accept", "There are none registered." );
4714     return ( NULL );
4715   }
4716
4717   // do it
4718   int sel = ui_modal_single_menu ( list, counter, "Custom Categories", "Any button to exit." );
4719
4720   if ( sel < 0 ) {
4721     free ( list );
4722     return ( NULL );
4723   }
4724
4725   char *foo = list [ sel ];
4726   free ( list );
4727
4728   return ( foo );
4729 }
4730
4731 void ui_start_defered_icon_thread ( void ) {
4732
4733   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) != 1 ) {
4734     return;
4735   }
4736
4737   if ( g_icon_thread_busy ) {
4738     //fprintf ( stderr, "REM: Waiting for thread to stop..\n" );
4739     ui_stop_defered_icon_thread();
4740   }
4741
4742   //fprintf ( stderr, "WARN: Starting new icon caching thread..\n" );
4743   g_icon_thread = SDL_CreateThread ( (void*)ui_threaded_defered_icon, NULL );
4744
4745   if ( ! g_icon_thread ) {
4746     pnd_log ( pndn_error, "ERROR: Couldn't create icon thread\n" );
4747   }
4748
4749   return;
4750 }
4751
4752 void ui_stop_defered_icon_thread ( void ) {
4753   time_t started = time ( NULL );
4754
4755   // ask thread to stop, then wait for it (if two run at same time, or if we change
4756   // category content under neath it, could be bad..)
4757   g_icon_thread_stop = 1;
4758   while ( g_icon_thread_busy ) {
4759     time ( NULL ); // spin
4760   }
4761   g_icon_thread_stop = 0;
4762
4763   fprintf ( stderr, "REM: Thread stoppage took %u seconds.\n", (int) ( time ( NULL ) - started ) );
4764
4765   return;
4766 }