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