08d8dac61983d677ae8a42116631670802b0cc6b
[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 "SDL.h"
13 #include "SDL_audio.h"
14 #include "SDL_image.h"
15 #include "SDL_ttf.h"
16 #include "SDL_gfxPrimitives.h"
17 #include "SDL_rotozoom.h"
18 #include "SDL_thread.h"
19 #include <dirent.h>
20
21 #include "pnd_conf.h"
22 #include "pnd_logger.h"
23 #include "pnd_pxml.h"
24 #include "pnd_container.h"
25 #include "pnd_discovery.h"
26 #include "pnd_apps.h"
27 #include "pnd_device.h"
28 #include "../lib/pnd_pathiter.h"
29 #include "pnd_utility.h"
30 #include "pnd_pndfiles.h"
31
32 #include "mmenu.h"
33 #include "mmcat.h"
34 #include "mmcache.h"
35 #include "mmui.h"
36 #include "mmwrapcmd.h"
37 #include "mmconf.h"
38 #include "mmui_context.h"
39
40 #define CHANGED_NOTHING     (0)
41 #define CHANGED_CATEGORY    (1<<0)  /* changed to different category */
42 #define CHANGED_SELECTION   (1<<1)  /* changed app selection */
43 #define CHANGED_DATAUPDATE  (1<<2)  /* deferred preview pic or icon came in */
44 #define CHANGED_APPRELOAD   (1<<3)  /* new set of applications entirely */
45 #define CHANGED_EVERYTHING  (1<<4)  /* redraw it all! */
46 unsigned int render_mask = CHANGED_EVERYTHING;
47
48 /* SDL
49  */
50 SDL_Surface *sdl_realscreen = NULL;
51 unsigned int sdl_ticks = 0;
52 SDL_Thread *g_preview_thread = NULL;
53
54 enum { sdl_user_ticker = 0,
55        sdl_user_finishedpreview = 1,
56        sdl_user_finishedicon = 2,
57 };
58
59 /* app state
60  */
61 unsigned short int g_scale = 1; // 1 == noscale
62
63 SDL_Surface *g_imgcache [ IMG_MAX ];
64
65 TTF_Font *g_big_font = NULL;
66 TTF_Font *g_grid_font = NULL;
67 TTF_Font *g_detailtext_font = NULL;
68 TTF_Font *g_tab_font = NULL;
69
70 extern pnd_conf_handle g_conf;
71
72 /* current display state
73  */
74 int ui_rows_scrolled_down = 0;          // number of rows that should be missing from top of the display
75 mm_appref_t *ui_selected = NULL;
76 unsigned char ui_category = 0;          // current category
77 unsigned char ui_catshift = 0;          // how many cats are offscreen to the left
78 ui_context_t ui_display_context;        // display paramaters: see mmui_context.h
79 unsigned char ui_detail_hidden = 0;     // if >0, detail panel is hidden
80 // FUTURE: If multiple panels can be shown/hidden, convert ui_detail_hidden to a bitmask
81
82 extern mm_category_t *g_categories;
83 extern unsigned char g_categorycount;
84 extern mm_category_t _categories_invis [ MAX_CATS ];
85 extern unsigned char _categories_inviscount;
86
87 static SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ); // height -1 means ignore
88 static int ui_selected_index ( void );
89
90 unsigned char ui_setup ( void ) {
91
92   /* set up SDL
93    */
94
95   SDL_Init ( SDL_INIT_EVERYTHING | SDL_INIT_NOPARACHUTE );
96
97   SDL_JoystickOpen ( 0 ); // turn on joy-0
98
99   SDL_WM_SetCaption ( "mmenu", "mmenu" );
100
101   // hide the mouse cursor if we can
102   if ( SDL_ShowCursor ( -1 ) == 1 ) {
103     SDL_ShowCursor ( 0 );
104   }
105
106   atexit ( SDL_Quit );
107
108   // open up a surface
109   unsigned int svm = SDL_SWSURFACE /*| SDL_FULLSCREEN*/ /* 0*/;
110   if ( pnd_conf_get_as_int_d ( g_conf, "display.fullscreen", 0 ) > 0 ) {
111     svm |= SDL_FULLSCREEN;
112   }
113
114   sdl_realscreen =
115     SDL_SetVideoMode ( 800 * g_scale, 480 * g_scale, 16 /*bpp*/, svm );
116
117   if ( ! sdl_realscreen ) {
118     pnd_log ( pndn_error, "ERROR: Couldn't open SDL real screen; dieing." );
119     return ( 0 );
120   }
121
122   pnd_log ( pndn_debug, "Pixel format:" );
123   pnd_log ( pndn_debug, "bpp b: %u\n", sdl_realscreen -> format -> BitsPerPixel );
124   pnd_log ( pndn_debug, "bpp B: %u\n", sdl_realscreen -> format -> BytesPerPixel );
125   pnd_log ( pndn_debug, "R mask: %08x\n", sdl_realscreen -> format -> Rmask );
126   pnd_log ( pndn_debug, "G mask: %08x\n", sdl_realscreen -> format -> Gmask );
127   pnd_log ( pndn_debug, "B mask: %08x\n", sdl_realscreen -> format -> Bmask );
128
129 #if 0 // audio
130   {
131     SDL_AudioSpec fmt;
132
133     /* Set 16-bit stereo audio at 22Khz */
134     fmt.freq = 44100; //22050;
135     fmt.format = AUDIO_S16; //AUDIO_S16;
136     fmt.channels = 1;
137     fmt.samples = 2048;        /* A good value for games */
138     fmt.callback = mixaudio;
139     fmt.userdata = NULL;
140
141     /* Open the audio device and start playing sound! */
142     if ( SDL_OpenAudio ( &fmt, NULL ) < 0 ) {
143       zotlog ( "Unable to open audio: %s\n", SDL_GetError() );
144       exit ( 1 );
145     }
146
147     SDL_PauseAudio ( 0 );
148   }
149 #endif
150
151   // key repeat
152   SDL_EnableKeyRepeat ( 500, 150 );
153
154   // images
155   //IMG_Init ( IMG_INIT_JPG | IMG_INIT_PNG );
156
157   /* fonts
158    */
159
160   // init
161   if ( TTF_Init() == -1 ) {
162     pnd_log ( pndn_error, "ERROR: Couldn't set up SDL TTF lib\n" );
163     return ( 0 ); // couldn't set up SDL TTF
164   }
165
166   char fullpath [ PATH_MAX ];
167   // big font
168   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "minimenu.font" ) );
169   g_big_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
170   if ( ! g_big_font ) {
171     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
172               pnd_conf_get_as_char ( g_conf, "minimenu.font" ), pnd_conf_get_as_int_d ( g_conf, "minimenu.font_ptsize", 24 ) );
173     return ( 0 ); // couldn't set up SDL TTF
174   }
175
176   // grid font
177   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "grid.font" ) );
178   g_grid_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
179   if ( ! g_grid_font ) {
180     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
181               pnd_conf_get_as_char ( g_conf, "grid.font" ), pnd_conf_get_as_int_d ( g_conf, "grid.font_ptsize", 10 ) );
182     return ( 0 ); // couldn't set up SDL TTF
183   }
184
185   // detailtext font
186   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "detailtext.font" ) );
187   g_detailtext_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
188   if ( ! g_detailtext_font ) {
189     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
190               pnd_conf_get_as_char ( g_conf, "detailtext.font" ), pnd_conf_get_as_int_d ( g_conf, "detailtext.font_ptsize", 10 ) );
191     return ( 0 ); // couldn't set up SDL TTF
192   }
193
194   // tab font
195   sprintf ( fullpath, "%s/%s", g_skinpath, pnd_conf_get_as_char ( g_conf, "tabs.font" ) );
196   g_tab_font = TTF_OpenFont ( fullpath, pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
197   if ( ! g_tab_font ) {
198     pnd_log ( pndn_error, "ERROR: Couldn't load font '%s' for size %u\n",
199               pnd_conf_get_as_char ( g_conf, "tabs.font" ), pnd_conf_get_as_int_d ( g_conf, "tabs.font_ptsize", 10 ) );
200     return ( 0 ); // couldn't set up SDL TTF
201   }
202
203   // determine display context
204   if ( pnd_conf_get_as_int_d ( g_conf, "display.show_detail_pane", 1 ) > 0 ) {
205     ui_detail_hidden = 0;
206   } else {
207     ui_detail_hidden = 1;
208   }
209   ui_recache_context ( &ui_display_context );
210
211   return ( 1 );
212 }
213
214 mm_imgcache_t g_imagecache [ IMG_TRUEMAX ] = {
215   { IMG_BACKGROUND_800480,    "graphics.IMG_BACKGROUND_800480" },
216   { IMG_BACKGROUND_TABMASK,   "graphics.IMG_BACKGROUND_TABMASK" },
217   { IMG_DETAIL_PANEL,         "graphics.IMG_DETAIL_PANEL" },
218   { IMG_DETAIL_BG,            "graphics.IMG_DETAIL_BG" },
219   { IMG_SELECTED_ALPHAMASK,   "graphics.IMG_SELECTED_ALPHAMASK" },
220   { IMG_TAB_SEL,              "graphics.IMG_TAB_SEL" },
221   { IMG_TAB_SEL_L,            "graphics.IMG_TAB_SEL_L" },
222   { IMG_TAB_SEL_R,            "graphics.IMG_TAB_SEL_R" },
223   { IMG_TAB_UNSEL,            "graphics.IMG_TAB_UNSEL" },
224   { IMG_TAB_UNSEL_L,          "graphics.IMG_TAB_UNSEL_L" },
225   { IMG_TAB_UNSEL_R,          "graphics.IMG_TAB_UNSEL_R" },
226   { IMG_TAB_LINE,             "graphics.IMG_TAB_LINE" },
227   { IMG_TAB_LINEL,            "graphics.IMG_TAB_LINEL" },
228   { IMG_TAB_LINER,            "graphics.IMG_TAB_LINER" },
229   { IMG_ICON_MISSING,         "graphics.IMG_ICON_MISSING" },
230   { IMG_SELECTED_HILITE,      "graphics.IMG_SELECTED_HILITE" },
231   { IMG_PREVIEW_MISSING,      "graphics.IMG_PREVIEW_MISSING" },
232   { IMG_ARROW_UP,             "graphics.IMG_ARROW_UP", },
233   { IMG_ARROW_DOWN,           "graphics.IMG_ARROW_DOWN", },
234   { IMG_ARROW_SCROLLBAR,      "graphics.IMG_ARROW_SCROLLBAR", },
235   { IMG_HOURGLASS,            "graphics.IMG_HOURGLASS", },
236   { IMG_FOLDER,               "graphics.IMG_FOLDER", },
237   { IMG_EXECBIN,              "graphics.IMG_EXECBIN", },
238   { IMG_MAX,                  NULL },
239 };
240
241 unsigned char ui_imagecache ( char *basepath ) {
242   unsigned int i;
243   char fullpath [ PATH_MAX ];
244
245   // loaded
246
247   for ( i = 0; i < IMG_MAX; i++ ) {
248
249     if ( g_imagecache [ i ].id != i ) {
250       pnd_log ( pndn_error, "ERROR: Internal table mismatch during caching [%u]\n", i );
251       exit ( -1 );
252     }
253
254     char *filename = pnd_conf_get_as_char ( g_conf, g_imagecache [ i ].confname );
255
256     if ( ! filename ) {
257       pnd_log ( pndn_error, "ERROR: Missing filename in conf for key: %s\n", g_imagecache [ i ].confname );
258       return ( 0 );
259     }
260
261     if ( filename [ 0 ] == '/' ) {
262       strncpy ( fullpath, filename, PATH_MAX );
263     } else {
264       sprintf ( fullpath, "%s/%s", basepath, filename );
265     }
266
267     if ( ! ( g_imagecache [ i ].i = IMG_Load ( fullpath ) ) ) {
268       pnd_log ( pndn_error, "ERROR: Couldn't load static cache image: %s\n", fullpath );
269       return ( 0 );
270     }
271
272   } // for
273
274   // generated
275   //g_imagecache [ IMG_SELECTED_ALPHAMASK ].i = SDL_CreateRGBSurface ( SDL_SWSURFACE, 60, 60, 32, 0xFF0000, 0x00FF00, 0xFF, 0xFF000000 );
276   //boxRGBA ( g_imagecache [ IMG_SELECTED_ALPHAMASK ].i, 0, 0, 60, 60, 100, 100, 100, 250 );
277
278   // post processing
279   //
280
281   // scale icons
282   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 );
283   // scale text hilight
284   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 );
285   // scale preview no-pic
286   g_imagecache [ IMG_PREVIEW_MISSING ].i = ui_scale_image ( g_imagecache [ IMG_PREVIEW_MISSING ].i,
287                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ),
288                                                             pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 50 ) );
289
290   // set alpha on detail panel
291   //SDL_SetAlpha ( g_imagecache [ IMG_DETAIL_BG ].i, SDL_SRCALPHA, pnd_conf_get_as_int_d ( g_conf, "display.detail_bg_alpha", 50 ) );
292
293   return ( 1 );
294 } // ui_imagecache
295
296 void ui_render ( void ) {
297
298   // 800x480:
299   // divide width: 550 / 250
300   // divide left side: 5 columns == 110px width
301   //   20px buffer either side == 70px wide icon + 20 + 20?
302
303   // what jobs to do during render?
304   //
305
306 #define R_BG     (1<<0)
307 #define R_TABS   (1<<1)
308 #define R_DETAIL (1<<2)
309 #define R_GRID   (1<<3)
310
311 #define R_ALL (R_BG|R_TABS|R_DETAIL|R_GRID)
312
313   unsigned int render_jobs_b = 0;
314
315   if ( render_mask & CHANGED_EVERYTHING ) {
316     render_jobs_b |= R_ALL;
317   }
318
319   if ( render_mask & CHANGED_SELECTION ) {
320     render_jobs_b |= R_GRID;
321     render_jobs_b |= R_DETAIL;
322   }
323
324   if ( render_mask & CHANGED_CATEGORY ) {
325     render_jobs_b |= R_ALL;
326   }
327
328   render_mask = CHANGED_NOTHING;
329
330   // render everything
331   //
332   unsigned int icon_rows;
333
334 #define MAXRECTS 200
335   SDL_Rect rects [ MAXRECTS ], src;
336   SDL_Rect *dest = rects;
337   bzero ( dest, sizeof(SDL_Rect)*MAXRECTS );
338
339   unsigned int row, displayrow, col;
340   mm_appref_t *appiter;
341
342   ui_context_t *c = &ui_display_context; // for convenience and shorthand
343
344   // how many total rows do we need?
345   icon_rows = g_categories [ ui_category ].refcount / c -> col_max;
346   if ( g_categories [ ui_category ].refcount % c -> col_max > 0 ) {
347     icon_rows++;
348   }
349
350 #if 1
351   // if no selected app yet, select the first one
352   if ( ! ui_selected && pnd_conf_get_as_int_d ( g_conf, "minimenu.start_selected", 0 ) ) {
353     ui_selected = g_categories [ ui_category ].refs;
354   }
355 #endif
356
357   // reset touchscreen regions
358   if ( render_jobs_b ) {
359     ui_register_reset();
360   }
361
362   // ensure selection is visible
363   if ( ui_selected ) {
364
365     int index = ui_selected_index();
366     int topleft = c -> col_max * ui_rows_scrolled_down;
367     int botright = ( c -> col_max * ( ui_rows_scrolled_down + c -> row_max ) - 1 );
368
369     if ( index < topleft ) {
370       ui_rows_scrolled_down -= pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
371       render_jobs_b |= R_ALL;
372     } else if ( index > botright ) {
373       ui_rows_scrolled_down += pnd_conf_get_as_int_d ( g_conf, "grid.scroll_increment", 1 );
374       render_jobs_b |= R_ALL;
375     }
376
377     if ( ui_rows_scrolled_down < 0 ) {
378       ui_rows_scrolled_down = 0;
379     } else if ( ui_rows_scrolled_down > icon_rows ) {
380       ui_rows_scrolled_down = icon_rows;
381     }
382
383   } // ensire visible
384
385   // render background
386   if ( render_jobs_b & R_BG ) {
387
388     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
389       dest -> x = 0;
390       dest -> y = 0;
391       dest -> w = sdl_realscreen -> w;
392       dest -> h = sdl_realscreen -> h;
393       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
394       dest++;
395     }
396
397     // tabmask
398     if ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i ) {
399       dest -> x = 0;
400       dest -> y = 0;
401       dest -> w = sdl_realscreen -> w;
402       dest -> h = sdl_realscreen -> h;
403       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
404       dest++;
405     }
406
407   } // r_bg
408
409   // tabs
410   if ( g_imagecache [ IMG_TAB_SEL ].i && g_imagecache [ IMG_TAB_UNSEL ].i ) {
411     static unsigned int tab_width;
412     static unsigned int tab_height;
413     static unsigned int tab_offset_x;
414     static unsigned int tab_offset_y;
415     static unsigned int text_offset_x;
416     static unsigned int text_offset_y;
417     static unsigned int text_width;
418
419     static unsigned char tab_first_run = 1;
420     if ( tab_first_run ) {
421       tab_first_run = 0;
422       tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
423       tab_height = pnd_conf_get_as_int ( g_conf, "tabs.tab_height" );
424       tab_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_x" );
425       tab_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.tab_offset_y" );
426       text_offset_x = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_x" );
427       text_offset_y = pnd_conf_get_as_int ( g_conf, "tabs.text_offset_y" );
428       text_width = pnd_conf_get_as_int ( g_conf, "tabs.text_width" );
429     }
430
431     unsigned int maxtab = ( c -> screen_width / tab_width ) < g_categorycount ? ( c -> screen_width / tab_width ) + ui_catshift : g_categorycount + ui_catshift;
432     unsigned int maxtabspot = ( c -> screen_width / tab_width );
433
434     if ( g_categorycount > 0 ) {
435
436       // draw tabs with categories
437       for ( col = ui_catshift;
438             col < maxtab;
439             col++ )
440       {
441
442         SDL_Surface *s;
443
444         // if this is the first (leftmost) tab, we use different artwork
445         // than if the other tabs (so skinner can link lines up nicely.)
446         if ( col == ui_catshift ) {
447           // leftmost tab, special case
448
449           if ( col == ui_category ) {
450             s = g_imagecache [ IMG_TAB_SEL_L ].i;
451           } else {
452             s = g_imagecache [ IMG_TAB_UNSEL_L ].i;
453           }
454
455         } else if ( col == maxtab - 1 ) {
456           // rightmost tab, special case
457
458           if ( col == ui_category ) {
459             s = g_imagecache [ IMG_TAB_SEL_R ].i;
460           } else {
461             s = g_imagecache [ IMG_TAB_UNSEL_R ].i;
462           }
463
464         } else {
465           // normal (not leftmost) tab
466
467           if ( col == ui_category ) {
468             s = g_imagecache [ IMG_TAB_SEL ].i;
469           } else {
470             s = g_imagecache [ IMG_TAB_UNSEL ].i;
471           }
472
473         } // first col, or not first col?
474
475         // draw tab
476         src.x = 0;
477         src.y = 0;
478 #if 0
479         src.w = tab_width;
480         if ( col == ui_category ) {
481           src.h = tab_selheight;
482         } else {
483           src.h = tab_height;
484         }
485 #else
486         src.w = s -> w;
487         src.h = s -> h;
488 #endif
489         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
490         dest -> y = tab_offset_y;
491
492         // store touch info
493         ui_register_tab ( col, dest -> x, dest -> y, tab_width, tab_height );
494
495         if ( render_jobs_b & R_TABS ) {
496           //pnd_log ( pndn_debug, "tab %u at %ux%u\n", col, dest.x, dest.y );
497           SDL_BlitSurface ( s, &src, sdl_realscreen, dest );
498           dest++;
499
500           // draw tab line
501           if ( col == ui_category ) {
502             // no line for selected tab
503           } else {
504             if ( col - ui_catshift == 0 ) {
505               s = g_imagecache [ IMG_TAB_LINEL ].i;
506             } else if ( col - ui_catshift == maxtabspot - 1 ) {
507               s = g_imagecache [ IMG_TAB_LINER ].i;
508             } else {
509               s = g_imagecache [ IMG_TAB_LINE ].i;
510             }
511             dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
512             dest -> y = tab_offset_y + tab_height;
513             SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
514             dest++;
515           }
516
517           // draw text
518           SDL_Surface *rtext;
519           rtext = TTF_RenderText_Blended ( g_tab_font, g_categories [ col ].catname, c -> fontcolor );
520           src.x = 0;
521           src.y = 0;
522           src.w = rtext -> w < text_width ? rtext -> w : text_width;
523           src.h = rtext -> h;
524           dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width ) + text_offset_x;
525           dest -> y = tab_offset_y + text_offset_y;
526           SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
527           SDL_FreeSurface ( rtext );
528           dest++;
529
530         } // r_tabs
531
532       } // for
533
534     } // if we got categories
535
536     if ( render_jobs_b & R_TABS ) {
537
538       // draw tab lines under where tabs would be if we had categories
539       for ( /* foo */; col < maxtabspot; col++ ) {
540         SDL_Surface *s;
541
542         if ( col - ui_catshift == 0 ) {
543           s = g_imagecache [ IMG_TAB_LINEL ].i;
544         } else if ( col - ui_catshift == maxtabspot - 1 ) {
545           s = g_imagecache [ IMG_TAB_LINER ].i;
546         } else {
547           s = g_imagecache [ IMG_TAB_LINE ].i;
548         }
549         dest -> x = tab_offset_x + ( (col-ui_catshift) * tab_width );
550         dest -> y = tab_offset_y + tab_height;
551         SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, dest );
552         dest++;
553
554       } // for
555
556     } // r_tabs
557
558   } // tabs
559
560   // scroll bars and arrows
561   if ( render_jobs_b & R_BG ) {
562     unsigned char show_bar = 0;
563
564     // up?
565     if ( ui_rows_scrolled_down && g_imagecache [ IMG_ARROW_UP ].i ) {
566       dest -> x = c -> arrow_up_x;
567       dest -> y = c -> arrow_up_y;
568       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_UP ].i, NULL /* whole image */, sdl_realscreen, dest );
569       dest++;
570
571       show_bar = 1;
572     }
573
574     // down?
575     if ( ui_rows_scrolled_down + c -> row_max < icon_rows && g_imagecache [ IMG_ARROW_DOWN ].i ) {
576       dest -> x = c -> arrow_down_x;
577       dest -> y = c -> arrow_down_y;
578       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_DOWN ].i, NULL /* whole image */, sdl_realscreen, dest );
579       dest++;
580
581       show_bar = 1;
582     }
583
584     if ( show_bar ) {
585       // show scrollbar as well
586       src.x = 0;
587       src.y = 0;
588       src.w = c -> arrow_bar_clip_w;
589       src.h = c -> arrow_bar_clip_h;
590       dest -> x = c -> arrow_bar_x;
591       dest -> y = c -> arrow_bar_y;
592       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_SCROLLBAR ].i, &src /* whole image */, sdl_realscreen, dest );
593       dest++;
594     } // bar
595
596   } // r_bg, scroll bars
597
598   // render detail pane bg
599   if ( render_jobs_b & R_DETAIL && ui_detail_hidden == 0 ) {
600
601     if ( pnd_conf_get_as_int_d ( g_conf, "detailpane.show", 1 ) ) {
602
603       // render detail bg
604       if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
605         src.x = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
606         src.y = 0; // pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
607         src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> w;
608         src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_PANEL ].i)) -> h;
609         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
610         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
611         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
612         dest++;
613       }
614
615       // render detail pane
616       if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
617         dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
618         dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
619         SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
620         dest++;
621       }
622
623     } // detailpane frame/bg
624
625   } // r_details
626
627   // anything to render?
628   if ( render_jobs_b & R_GRID ) {
629
630     // if just rendering grid, and nothing else, better clear it first
631     if ( ! ( render_jobs_b & R_BG ) ) {
632       if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
633         src.x = c -> grid_offset_x;
634         src.y = c -> grid_offset_y + c -> sel_icon_offset_y;
635         src.w = c -> col_max * c -> cell_width;
636         src.h = c -> row_max * c -> cell_height;
637
638         dest -> x = c -> grid_offset_x;
639         dest -> y = c -> grid_offset_y + c -> sel_icon_offset_y;
640
641         SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
642         dest++;
643
644       }
645     }
646
647     if ( g_categories [ ui_category ].refs ) {
648
649       appiter = g_categories [ ui_category ].refs;
650       row = 0;
651       displayrow = 0;
652
653       // until we run out of apps, or run out of space
654       while ( appiter != NULL ) {
655
656         for ( col = 0; col < c -> col_max && appiter != NULL; col++ ) {
657
658           // do we even need to render it? or are we suppressing it due to rows scrolled off the top?
659           if ( row >= ui_rows_scrolled_down ) {
660
661             // selected? show hilights
662             if ( appiter == ui_selected ) {
663               SDL_Surface *s = g_imagecache [ IMG_SELECTED_ALPHAMASK ].i;
664               // icon
665               //dest -> x = grid_offset_x + ( col * cell_width ) + icon_offset_x + ( ( icon_max_width - s -> w ) / 2 );
666               dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> icon_offset_x + c -> sel_icon_offset_x;
667               //dest -> y = grid_offset_y + ( displayrow * cell_height ) + icon_offset_y + ( ( icon_max_height - s -> h ) / 2 );
668               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> icon_offset_y + c -> sel_icon_offset_y;
669               SDL_BlitSurface ( s, NULL /* all */, sdl_realscreen, dest );
670               dest++;
671               // text
672               dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_clip_x;
673               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> text_hilite_offset_y;
674               SDL_BlitSurface ( g_imagecache [ IMG_SELECTED_HILITE ].i, NULL /* all */, sdl_realscreen, dest );
675               dest++;
676             } // selected?
677
678             // show icon
679             mm_cache_t *ic = cache_query_icon ( appiter -> ref -> unique_id );
680             SDL_Surface *iconsurface;
681             if ( ic ) {
682               iconsurface = ic -> i;
683             } else {
684               //pnd_log ( pndn_warning, "WARNING: TBD: Need Missin-icon icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
685
686               // no icon override; was this a pnd-file (show the unknown icon then), or was this generated from
687               // filesystem (file or directory icon)
688               if ( appiter -> ref -> object_flags & PND_DISCO_GENERATED ) {
689                 if ( appiter -> ref -> object_type == pnd_object_type_directory ) {
690                   iconsurface = g_imagecache [ IMG_FOLDER ].i;
691                 } else {
692                   iconsurface = g_imagecache [ IMG_EXECBIN ].i;
693                 }
694               } else {
695                 iconsurface = g_imagecache [ IMG_ICON_MISSING ].i;
696               }
697
698             }
699
700             // got an icon I hope?
701             if ( iconsurface ) {
702               //pnd_log ( pndn_debug, "Got an icon for '%s'\n", IFNULL(appiter -> ref -> title_en,"No Name") );
703
704               src.x = 0;
705               src.y = 0;
706               src.w = 60;
707               src.h = 60;
708               dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> icon_offset_x + (( c -> icon_max_width - iconsurface -> w ) / 2);
709               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> icon_offset_y + (( c -> icon_max_height - iconsurface -> h ) / 2);
710
711               SDL_BlitSurface ( iconsurface, &src, sdl_realscreen, dest );
712
713               // store touch info
714               ui_register_app ( appiter, dest -> x, dest -> y, src.w, src.h );
715
716               dest++;
717
718             }
719
720             // show text
721             if ( appiter -> ref -> title_en ) {
722               SDL_Surface *rtext;
723               rtext = TTF_RenderText_Blended ( g_grid_font, appiter -> ref -> title_en, c -> fontcolor );
724               src.x = 0;
725               src.y = 0;
726               src.w = c -> text_width < rtext -> w ? c -> text_width : rtext -> w;
727               src.h = rtext -> h;
728               if ( rtext -> w > c -> text_width ) {
729                 dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_clip_x;
730               } else {
731                 dest -> x = c -> grid_offset_x + ( col * c -> cell_width ) + c -> text_offset_x - ( rtext -> w / 2 );
732               }
733               dest -> y = c -> grid_offset_y + ( displayrow * c -> cell_height ) + c -> text_offset_y;
734               SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
735               SDL_FreeSurface ( rtext );
736               dest++;
737             }
738
739           } // display now? or scrolled away..
740
741           // next
742           appiter = appiter -> next;
743
744         } // for column 1...X
745
746         if ( row >= ui_rows_scrolled_down ) {
747           displayrow++;
748         }
749
750         row ++;
751
752         // are we done displaying rows?
753         if ( displayrow >= c -> row_max ) {
754           break;
755         }
756
757       } // while
758
759     } else {
760       // no apps to render?
761       pnd_log ( pndn_rem, "No applications to render?\n" );
762     } // apps to renser?
763
764   } // r_grid
765
766   // detail panel - show app details or blank-message
767   if ( render_jobs_b & R_DETAIL && ui_detail_hidden == 0 ) {
768
769     unsigned int cell_offset_x = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_x" );
770     unsigned int cell_offset_y = pnd_conf_get_as_int ( g_conf, "detailtext.cell_offset_y" );
771     unsigned int cell_width = pnd_conf_get_as_int ( g_conf, "detailtext.cell_width" );
772
773     unsigned int desty = cell_offset_y;
774
775     if ( ui_selected ) {
776
777       char buffer [ 256 ];
778
779       // full name
780       if ( ui_selected -> ref -> title_en ) {
781         SDL_Surface *rtext;
782         rtext = TTF_RenderText_Blended ( g_detailtext_font, ui_selected -> ref -> title_en, c -> fontcolor );
783         src.x = 0;
784         src.y = 0;
785         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
786         src.h = rtext -> h;
787         dest -> x = cell_offset_x;
788         dest -> y = desty;
789         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
790         SDL_FreeSurface ( rtext );
791         dest++;
792         desty += src.h;
793       }
794
795       // category
796 #if 0
797       if ( ui_selected -> ref -> main_category ) {
798
799         sprintf ( buffer, "Category: %s", ui_selected -> ref -> main_category );
800
801         SDL_Surface *rtext;
802         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
803         src.x = 0;
804         src.y = 0;
805         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
806         src.h = rtext -> h;
807         dest -> x = cell_offset_x;
808         dest -> y = desty;
809         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
810         SDL_FreeSurface ( rtext );
811         dest++;
812         desty += src.h;
813       }
814 #endif
815
816       // clock
817       if ( ui_selected -> ref -> clockspeed ) {
818
819         sprintf ( buffer, "CPU Clock: %s", ui_selected -> ref -> clockspeed );
820
821         SDL_Surface *rtext;
822         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
823         src.x = 0;
824         src.y = 0;
825         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
826         src.h = rtext -> h;
827         dest -> x = cell_offset_x;
828         dest -> y = desty;
829         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
830         SDL_FreeSurface ( rtext );
831         dest++;
832         desty += src.h;
833       }
834
835       // show sub-app# on right side of cpu clock?
836       //if ( ui_selected -> ref -> subapp_number )
837       {
838         sprintf ( buffer, "(app#%u)", ui_selected -> ref -> subapp_number );
839
840         SDL_Surface *rtext;
841         rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
842         dest -> x = cell_offset_x + cell_width - rtext -> w;
843         dest -> y = desty - src.h;
844         SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
845         SDL_FreeSurface ( rtext );
846         dest++;
847       }
848
849       // info hint
850 #if 0 // merged into hint-line
851       if ( ui_selected -> ref -> info_filename ) {
852
853         sprintf ( buffer, "Documentation - hit Y" );
854
855         SDL_Surface *rtext;
856         rtext = TTF_RenderText_Blended ( g_detailtext_font, buffer, c -> fontcolor );
857         src.x = 0;
858         src.y = 0;
859         src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
860         src.h = rtext -> h;
861         dest -> x = cell_offset_x;
862         dest -> y = desty;
863         SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
864         SDL_FreeSurface ( rtext );
865         dest++;
866         desty += src.h;
867       }
868 #endif
869
870       // notes
871       if ( ui_selected -> ovrh ) {
872         char *n;
873         unsigned char i;
874         char buffer [ 50 ];
875
876         desty += 5; // a touch of spacing can't hurt
877
878         for ( i = 1; i < 4; i++ ) {
879           sprintf ( buffer, "Application-%u.note-%u", ui_selected -> ref -> subapp_number, i );
880           n = pnd_conf_get_as_char ( ui_selected -> ovrh, buffer );
881
882           if ( n ) {
883             SDL_Surface *rtext;
884             rtext = TTF_RenderText_Blended ( g_detailtext_font, n, c -> fontcolor );
885             src.x = 0;
886             src.y = 0;
887             src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
888             src.h = rtext -> h;
889             dest -> x = cell_offset_x;
890             dest -> y = desty;
891             SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
892             SDL_FreeSurface ( rtext );
893             dest++;
894             desty += rtext -> h;
895           }
896         } // for
897
898       } // r_detail -> notes
899
900       // preview pic
901       mm_cache_t *ic = cache_query_preview ( ui_selected -> ref -> unique_id );
902       SDL_Surface *previewpic;
903
904       if ( ic ) {
905         previewpic = ic -> i;
906       } else {
907         previewpic = g_imagecache [ IMG_PREVIEW_MISSING ].i;
908       }
909
910       if ( previewpic ) {
911         dest -> x = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_x", 50 ) +
912           ( ( pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 50 ) - previewpic -> w ) / 2 );
913         dest -> y = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_offset_y", 50 );
914         SDL_BlitSurface ( previewpic, NULL /* whole image */, sdl_realscreen, dest );
915         dest++;
916       }
917
918     } else {
919
920       char *empty_message = "Press SELECT for menu";
921
922       SDL_Surface *rtext;
923
924       rtext = TTF_RenderText_Blended ( g_detailtext_font, empty_message, c -> fontcolor );
925
926       src.x = 0;
927       src.y = 0;
928       src.w = rtext -> w < cell_width ? rtext -> w : cell_width;
929       src.h = rtext -> h;
930       dest -> x = cell_offset_x;
931       dest -> y = desty;
932       SDL_BlitSurface ( rtext, &src, sdl_realscreen, dest );
933       SDL_FreeSurface ( rtext );
934       dest++;
935
936       desty += src.h;
937
938     } // r_detail && selected?
939
940   } // r_detail
941
942   // extras
943   //
944
945   // battery
946   if ( render_jobs_b & R_BG ) {
947     static int last_battlevel = 0;
948     static unsigned char batterylevel = 0;
949     char buffer [ 100 ];
950
951     if ( time ( NULL ) - last_battlevel > 60 ) {
952       batterylevel = pnd_device_get_battery_gauge_perc();
953       last_battlevel = time ( NULL );
954     }
955
956     sprintf ( buffer, "Battery: %u%%", batterylevel );
957
958     SDL_Surface *rtext;
959     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
960     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.battery_x", 20 );
961     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.battery_y", 450 );
962     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
963     SDL_FreeSurface ( rtext );
964     dest++;
965   }
966
967   // hints
968   if ( pnd_conf_get_as_char ( g_conf, "display.hintline" ) ) {
969     char *buffer;
970     unsigned int hintx, hinty;
971     hintx = pnd_conf_get_as_int_d ( g_conf, "display.hint_x", 40 );
972     hinty = pnd_conf_get_as_int_d ( g_conf, "display.hint_y", 450 );
973     static unsigned int lastwidth = 3000;
974
975     if ( ui_selected && ui_selected -> ref -> info_filename ) {
976       buffer = "Documentation - hit Y";
977     } else {
978       buffer = pnd_conf_get_as_char ( g_conf, "display.hintline" );
979     }
980
981     SDL_Surface *rtext;
982     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
983
984     // clear bg
985     if ( ! ( render_jobs_b & R_BG ) ) {
986       src.x = hintx;
987       src.y = hinty;
988       src.w = lastwidth;
989       src.h = rtext -> h;
990       dest -> x = hintx;
991       dest -> y = hinty;
992       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_TABMASK ].i, &src, sdl_realscreen, dest );
993       dest++;
994       lastwidth = rtext -> w;
995     }
996
997     // now render text
998     dest -> x = hintx;
999     dest -> y = hinty;
1000     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1001     SDL_FreeSurface ( rtext );
1002     dest++;
1003   }
1004
1005   // clock time
1006   if ( render_jobs_b & R_BG &&
1007        pnd_conf_get_as_int_d ( g_conf, "display.clock_x", -1 ) != -1 )
1008   {
1009     char buffer [ 50 ];
1010
1011     time_t t = time ( NULL );
1012     struct tm *tm = localtime ( &t );
1013     strftime ( buffer, 50, "%a %H:%M %F", tm );
1014
1015     SDL_Surface *rtext;
1016     rtext = TTF_RenderText_Blended ( g_grid_font, buffer, c -> fontcolor );
1017     dest -> x = pnd_conf_get_as_int_d ( g_conf, "display.clock_x", 700 );
1018     dest -> y = pnd_conf_get_as_int_d ( g_conf, "display.clock_y", 450 );
1019     SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
1020     SDL_FreeSurface ( rtext );
1021     dest++;
1022   }
1023
1024   // update all the rects and send it all to sdl
1025   // - at this point, we could probably just do 1 rect, of the
1026   //   whole screen, and be faster :/
1027   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1028
1029 } // ui_render
1030
1031 void ui_process_input ( unsigned char block_p ) {
1032   SDL_Event event;
1033
1034   unsigned char ui_event = 0; // if we get a ui event, flip to 1 and break
1035   //static ui_sdl_button_e ui_mask = uisb_none; // current buttons down
1036
1037   while ( ! ui_event &&
1038           block_p ? SDL_WaitEvent ( &event ) : SDL_PollEvent ( &event ) )
1039   {
1040
1041     switch ( event.type ) {
1042
1043     case SDL_USEREVENT:
1044       // update something
1045
1046       if ( event.user.code == sdl_user_ticker ) {
1047
1048         // timer went off, time to load something
1049         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
1050
1051           // load the preview pics now!
1052           pnd_disco_t *iter = ui_selected -> ref;
1053
1054           if ( iter -> preview_pic1 ) {
1055
1056             if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.threaded_preview", 0 ) ) {
1057               // load in bg thread, make user experience chuggy
1058
1059               g_preview_thread = SDL_CreateThread ( (void*)ui_threaded_defered_preview, iter );
1060
1061               if ( ! g_preview_thread ) {
1062                 pnd_log ( pndn_error, "ERROR: Couldn't create preview thread\n" );
1063               }
1064
1065             } else {
1066               // load it now, make user wait
1067
1068               if ( ! cache_preview ( iter, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
1069                                      pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
1070                  )
1071               {
1072                 pnd_log ( pndn_debug, "  Couldn't load preview pic: '%s' -> '%s'\n",
1073                           IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1074               }
1075
1076             } // threaded?
1077
1078           } // got a preview at all?
1079
1080           ui_event++;
1081         }
1082
1083       } else if ( event.user.code == sdl_user_finishedpreview ) {
1084
1085         // if we just finished the one we happen to be looking at, better redraw now; otherwise, if
1086         // we finished another, no big woop
1087         if ( ui_selected && event.user.data1 == ui_selected -> ref ) {
1088           ui_event++;
1089         }
1090
1091       } else if ( event.user.code == sdl_user_finishedicon ) {
1092         // redraw, so we can show the newly loaded icon
1093         ui_event++;
1094
1095       }
1096
1097       render_mask |= CHANGED_EVERYTHING;
1098
1099       break;
1100
1101 #if 0 // joystick motion
1102     case SDL_JOYAXISMOTION:
1103
1104       pnd_log ( PND_LOG_DEFAULT, "joystick axis\n" );
1105
1106         if ( event.jaxis.axis == 0 ) {
1107           // horiz
1108           if ( event.jaxis.value < 0 ) {
1109             ui_push_left ( 0 );
1110             pnd_log ( PND_LOG_DEFAULT, "joystick axis - LEFT\n" );
1111           } else if ( event.jaxis.value > 0 ) {
1112             ui_push_right ( 0 );
1113             pnd_log ( PND_LOG_DEFAULT, "joystick axis - RIGHT\n" );
1114           }
1115         } else if ( event.jaxis.axis == 1 ) {
1116           // vert
1117           if ( event.jaxis.value < 0 ) {
1118             ui_push_up();
1119           } else if ( event.jaxis.value > 0 ) {
1120             ui_push_down();
1121           }
1122         }
1123
1124         ui_event++;
1125
1126         break;
1127 #endif
1128
1129 #if 0 // joystick buttons
1130     case SDL_JOYBUTTONDOWN:
1131
1132       pnd_log ( PND_LOG_DEFAULT, "joystick button down %u\n", event.jbutton.button );
1133
1134       if ( event.jbutton.button == 0 ) { // B
1135         ui_mask |= uisb_b;
1136       } else if ( event.jbutton.button == 1 ) { // Y
1137         ui_mask |= uisb_y;
1138       } else if ( event.jbutton.button == 2 ) { // X
1139         ui_mask |= uisb_x;
1140       } else if ( event.jbutton.button == 3 ) { // A
1141         ui_mask |= uisb_a;
1142
1143       } else if ( event.jbutton.button == 4 ) { // Select
1144         ui_mask |= uisb_select;
1145       } else if ( event.jbutton.button == 5 ) { // Start
1146         ui_mask |= uisb_start;
1147
1148       } else if ( event.jbutton.button == 7 ) { // L
1149         ui_mask |= uisb_l;
1150       } else if ( event.jbutton.button == 8 ) { // R
1151         ui_mask |= uisb_r;
1152
1153       }
1154
1155       ui_event++;
1156
1157       break;
1158
1159     case SDL_JOYBUTTONUP:
1160
1161       pnd_log ( PND_LOG_DEFAULT, "joystick button up %u\n", event.jbutton.button );
1162
1163       if ( event.jbutton.button == 0 ) { // B
1164         ui_mask &= ~uisb_b;
1165         ui_push_exec();
1166       } else if ( event.jbutton.button == 1 ) { // Y
1167         ui_mask &= ~uisb_y;
1168       } else if ( event.jbutton.button == 2 ) { // X
1169         ui_mask &= ~uisb_x;
1170       } else if ( event.jbutton.button == 3 ) { // A
1171         ui_mask &= ~uisb_a;
1172
1173       } else if ( event.jbutton.button == 4 ) { // Select
1174         ui_mask &= ~uisb_select;
1175       } else if ( event.jbutton.button == 5 ) { // Start
1176         ui_mask &= ~uisb_start;
1177
1178       } else if ( event.jbutton.button == 7 ) { // L
1179         ui_mask &= ~uisb_l;
1180         ui_push_ltrigger();
1181       } else if ( event.jbutton.button == 8 ) { // R
1182         ui_mask &= ~uisb_r;
1183         ui_push_rtrigger();
1184
1185       }
1186
1187       ui_event++;
1188
1189       break;
1190 #endif
1191
1192 #if 1 // keyboard events
1193     //case SDL_KEYUP:
1194     case SDL_KEYDOWN:
1195
1196       //pnd_log ( pndn_debug, "key up %u\n", event.key.keysym.sym );
1197
1198       // SDLK_LALT -> Start
1199       // page up/down for y/x
1200       // home/end for a and b
1201
1202       // directional
1203       if ( event.key.keysym.sym == SDLK_RIGHT ) {
1204         ui_push_right ( 0 );
1205         ui_event++;
1206       } else if ( event.key.keysym.sym == SDLK_LEFT ) {
1207         ui_push_left ( 0 );
1208         ui_event++;
1209       } else if ( event.key.keysym.sym == SDLK_UP ) {
1210         ui_push_up();
1211         ui_event++;
1212       } else if ( event.key.keysym.sym == SDLK_DOWN ) {
1213         ui_push_down();
1214         ui_event++;
1215       } else if ( event.key.keysym.sym == SDLK_SPACE || event.key.keysym.sym == SDLK_END ) { // space or B
1216         ui_push_exec();
1217         ui_event++;
1218       } else if ( event.key.keysym.sym == SDLK_TAB || event.key.keysym.sym == SDLK_HOME ) { // tab or A
1219         // if detail panel is togglable, then toggle it
1220         // if not, make sure its ruddy well shown!
1221         if ( ui_is_detail_hideable() ) {
1222           ui_toggle_detail_pane();
1223         } else {
1224           ui_detail_hidden = 0;
1225         }
1226         ui_event++;
1227       } else if ( event.key.keysym.sym == SDLK_RSHIFT || event.key.keysym.sym == SDLK_COMMA ) { // left trigger or comma
1228         ui_push_ltrigger();
1229         ui_event++;
1230       } else if ( event.key.keysym.sym == SDLK_RCTRL || event.key.keysym.sym == SDLK_PERIOD ) { // right trigger or period
1231         ui_push_rtrigger();
1232         ui_event++;
1233       } else if ( event.key.keysym.sym == SDLK_PAGEUP ) { // Y
1234         // info
1235         if ( ui_selected ) {
1236           ui_show_info ( pnd_run_script, ui_selected -> ref );
1237           ui_event++;
1238         }
1239
1240       } else if ( event.key.keysym.sym == SDLK_LALT ) { // start button
1241         ui_push_exec();
1242         ui_event++;
1243
1244       } else if ( event.key.keysym.sym == SDLK_LCTRL /*LALT*/ ) { // select button
1245         char *opts [ 20 ] = {
1246           "Reveal hidden category",
1247           "Shutdown Pandora",
1248           "Configure Minimenu",
1249           "Rescan for applications",
1250           "Cache previews to SD now",
1251           "Run a terminal/console",
1252           "Run another GUI (xfce, etc)",
1253           "Quit (<- beware)",
1254           "Select a Minimenu skin",
1255           "About Minimenu"
1256         };
1257         int sel = ui_modal_single_menu ( opts, 10, "Minimenu", "Enter to select; other to return." );
1258
1259         char buffer [ 100 ];
1260         if ( sel == 0 ) {
1261           // do nothing
1262           ui_revealscreen();
1263         } else if ( sel == 1 ) {
1264           // shutdown
1265           sprintf ( buffer, "sudo poweroff" );
1266           system ( buffer );
1267         } else if ( sel == 2 ) {
1268           // configure mm
1269           unsigned char restart = conf_run_menu ( NULL );
1270           conf_write ( g_conf, conf_determine_location ( g_conf ) );
1271           if ( restart ) {
1272             emit_and_quit ( MM_RESTART );
1273           }
1274         } else if ( sel == 3 ) {
1275           // rescan apps
1276           pnd_log ( pndn_debug, "Freeing up applications\n" );
1277           applications_free();
1278           pnd_log ( pndn_debug, "Rescanning applications\n" );
1279           applications_scan();
1280         } else if ( sel == 4 ) {
1281           // cache preview to SD now
1282           extern pnd_box_handle g_active_apps;
1283           pnd_box_handle h = g_active_apps;
1284
1285           unsigned int maxwidth, maxheight;
1286           maxwidth = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 );
1287           maxheight = pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 );
1288
1289           pnd_disco_t *iter = pnd_box_get_head ( h );
1290
1291           while ( iter ) {
1292
1293             // cache it
1294             if ( ! cache_preview ( iter, maxwidth, maxheight ) ) {
1295               pnd_log ( pndn_debug, "Force cache: Couldn't load preview pic: '%s' -> '%s'\n",
1296                         IFNULL(iter->title_en,"No Name"), iter -> preview_pic1 );
1297             }
1298
1299             // next
1300             iter = pnd_box_get_next ( iter );
1301           } // while
1302
1303         } else if ( sel == 5 ) {
1304           // run terminal
1305           char *argv[5];
1306           argv [ 0 ] = pnd_conf_get_as_char ( g_conf, "utility.terminal" );
1307           argv [ 1 ] = NULL;
1308
1309           if ( argv [ 0 ] ) {
1310             ui_forkexec ( argv );
1311           }
1312
1313         } else if ( sel == 6 ) {
1314           char buffer [ PATH_MAX ];
1315           sprintf ( buffer, "%s %s\n", MM_RUN, "/usr/pandora/scripts/op_switchgui.sh" );
1316           emit_and_quit ( buffer );
1317         } else if ( sel == 7 ) {
1318           emit_and_quit ( MM_QUIT );
1319         } else if ( sel == 8 ) {
1320           // select skin
1321           if ( ui_pick_skin() ) {
1322             emit_and_quit ( MM_RESTART );
1323           }
1324         } else if ( sel == 9 ) {
1325           // about
1326           char buffer [ PATH_MAX ];
1327           sprintf ( buffer, "%s/about.txt", g_skinpath );
1328           ui_aboutscreen ( buffer );
1329         }
1330
1331         ui_event++;
1332         render_mask |= CHANGED_EVERYTHING;
1333
1334       } else {
1335         // unknown SDLK_ keycode?
1336
1337         // many SDLK_keycodes map to ASCII ("a" is ascii(a)), so try to jump to a filename of that name, in this category?
1338         // and if already there, try to jump to next, maybe?
1339         // future: look for sequence typing? ie: user types 'm' then 'a', look for 'ma*' instead of 'm' then 'a' matching
1340         if ( isalpha ( event.key.keysym.sym ) && g_categories [ ui_category ].refcount > 0 ) {
1341           mm_appref_t *app = g_categories [ ui_category ].refs;
1342
1343           //fprintf ( stderr, "sel %s next %s\n", ui_selected -> ref -> title_en, ui_selected -> next -> ref -> title_en );
1344
1345           // are we already matching the same char? and next item is also same char?
1346           if ( app && ui_selected &&
1347                ui_selected -> ref -> title_en && ui_selected -> next -> ref -> title_en &&
1348                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( ui_selected -> next -> ref -> title_en [ 0 ] ) &&
1349                toupper ( ui_selected -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym )
1350              )
1351           {
1352             // just skip down one
1353             app = ui_selected -> next;
1354           } else {
1355
1356             // walk the category, looking for a first-char match
1357             while ( app ) {
1358               if ( app -> ref -> title_en && toupper ( app -> ref -> title_en [ 0 ] ) == toupper ( event.key.keysym.sym ) ) {
1359                 break;
1360               }
1361               app = app -> next;
1362             }
1363
1364           } // same start letter, or new run?
1365
1366           // found something, or no?
1367           if ( app ) {
1368             // looks like we found a potential match; try switching it to visible selection
1369             ui_selected = app;
1370             ui_set_selected ( ui_selected );
1371           }
1372
1373
1374
1375
1376
1377         } // SDLK_alphanumeric?
1378
1379       } // SDLK_....
1380
1381       // extras
1382 #if 1
1383       if ( event.key.keysym.sym == SDLK_ESCAPE ) {
1384         emit_and_quit ( MM_QUIT );
1385       }
1386 #endif
1387
1388       break;
1389 #endif
1390
1391 #if 1 // mouse / touchscreen
1392 #if 0
1393     case SDL_MOUSEBUTTONDOWN:
1394       if ( event.button.button == SDL_BUTTON_LEFT ) {
1395         cb_pointer_press ( gc, event.button.x / g_scale, event.button.y / g_scale );
1396         ui_event++;
1397       }
1398       break;
1399 #endif
1400
1401     case SDL_MOUSEBUTTONUP:
1402       if ( event.button.button == SDL_BUTTON_LEFT ) {
1403         ui_touch_act ( event.button.x, event.button.y );
1404         ui_event++;
1405       }
1406       break;
1407 #endif
1408
1409     case SDL_QUIT:
1410       exit ( 0 );
1411       break;
1412
1413     default:
1414       break;
1415
1416     } // switch event type
1417
1418   } // while poll
1419
1420   return;
1421 }
1422
1423 void ui_push_left ( unsigned char forcecoil ) {
1424
1425   if ( ! ui_selected ) {
1426     ui_push_right ( 0 );
1427     return;
1428   }
1429
1430   // what column we in?
1431   unsigned int col = ui_determine_screen_col ( ui_selected );
1432
1433   // are we already at first item?
1434   if ( forcecoil == 0 &&
1435        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1436        col == 0 )
1437   {
1438     unsigned int i = ui_display_context.col_max - 1;
1439     while ( i && ui_selected -> next ) {
1440       ui_push_right ( 0 );
1441       i--;
1442     }
1443
1444   } else if ( g_categories [ ui_category ].refs == ui_selected ) {
1445     // can't go any more left, we're at the head
1446
1447   } else {
1448     // figure out the previous item; yay for singly linked list :/
1449     mm_appref_t *i = g_categories [ ui_category ].refs;
1450     while ( i ) {
1451       if ( i -> next == ui_selected ) {
1452         ui_selected = i;
1453         break;
1454       }
1455       i = i -> next;
1456     }
1457   }
1458
1459   ui_set_selected ( ui_selected );
1460
1461   return;
1462 }
1463
1464 void ui_push_right ( unsigned char forcecoil ) {
1465
1466   if ( ui_selected ) {
1467
1468     // what column we in?
1469     unsigned int col = ui_determine_screen_col ( ui_selected );
1470
1471     // wrap same or no?
1472     if ( forcecoil == 0 &&
1473          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_horiz_samerow", 0 ) &&
1474          // and selected is far-right, or last icon in category (might not be far right)
1475          ( ( col == ui_display_context.col_max - 1 ) ||
1476            ( ui_selected -> next == NULL ) )
1477        )
1478     {
1479       // same wrap
1480       //unsigned int i = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 ) - 1;
1481       while ( col /*i*/ ) {
1482         ui_push_left ( 0 );
1483         col--; //i--;
1484       }
1485
1486     } else {
1487       // just go to the next
1488
1489       if ( ui_selected -> next ) {
1490         ui_selected = ui_selected -> next;
1491       }
1492
1493     }
1494
1495   } else {
1496     ui_selected = g_categories [ ui_category ].refs;
1497   }
1498
1499   ui_set_selected ( ui_selected );
1500
1501   return;
1502 }
1503
1504 void ui_push_up ( void ) {
1505   unsigned char col_max = ui_display_context.col_max;
1506
1507   if ( ! ui_selected ) {
1508     return;
1509   }
1510
1511   // what row we in?
1512   unsigned int row = ui_determine_row ( ui_selected );
1513
1514   if ( row == 0 &&
1515        pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
1516   {
1517     // wrap around instead
1518
1519     unsigned int col = ui_determine_screen_col ( ui_selected );
1520
1521     // go to end
1522     ui_selected = g_categories [ ui_category ].refs;
1523     while ( ui_selected -> next ) {
1524       ui_selected = ui_selected -> next;
1525     }
1526
1527     // try to move to same column
1528     unsigned int newcol = ui_determine_screen_col ( ui_selected );
1529     if ( newcol > col ) {
1530       col = newcol - col;
1531       while ( col ) {
1532         ui_push_left ( 0 );
1533         col--;
1534       }
1535     }
1536
1537     // scroll down to show it
1538     int r = ui_determine_row ( ui_selected ) - 1;
1539     if ( r - ui_display_context.row_max > 0 ) {
1540       ui_rows_scrolled_down = (unsigned int) r;
1541     }
1542
1543   } else {
1544     // stop at top/bottom
1545
1546     while ( col_max ) {
1547       ui_push_left ( 1 );
1548       col_max--;
1549     }
1550
1551   }
1552
1553   return;
1554 }
1555
1556 void ui_push_down ( void ) {
1557   unsigned char col_max = ui_display_context.col_max;
1558
1559   if ( ui_selected ) {
1560
1561     // what row we in?
1562     unsigned int row = ui_determine_row ( ui_selected );
1563
1564     // max rows?
1565     unsigned int icon_rows = g_categories [ ui_category ].refcount / col_max;
1566     if ( g_categories [ ui_category ].refcount % col_max > 0 ) {
1567       icon_rows++;
1568     }
1569
1570     // we at the end?
1571     if ( row == ( icon_rows - 1 ) &&
1572          pnd_conf_get_as_int_d ( g_conf, "grid.wrap_vert_stop", 0 ) == 0 )
1573     {
1574
1575       unsigned char col = ui_determine_screen_col ( ui_selected );
1576
1577       ui_selected = g_categories [ ui_category ].refs;
1578
1579       while ( col ) {
1580         ui_selected = ui_selected -> next;
1581         col--;
1582       }
1583
1584       ui_rows_scrolled_down = 0;
1585
1586       render_mask |= CHANGED_EVERYTHING;
1587
1588     } else {
1589
1590       while ( col_max ) {
1591         ui_push_right ( 1 );
1592         col_max--;
1593       }
1594
1595     }
1596
1597   } else {
1598     ui_push_right ( 0 );
1599   }
1600
1601   return;
1602 }
1603
1604 void ui_push_exec ( void ) {
1605
1606   if ( ! ui_selected ) {
1607     return;
1608   }
1609
1610   // was this icon generated from filesystem, or from pnd-file?
1611   if ( ui_selected -> ref -> object_flags & PND_DISCO_GENERATED ) {
1612
1613     if ( ! ui_selected -> ref -> title_en ) {
1614       return; // no filename
1615     }
1616
1617     if ( ui_selected -> ref -> object_type == pnd_object_type_directory ) {
1618       // delve up/down the dir tree
1619
1620       if ( strcmp ( ui_selected -> ref -> title_en, ".." ) == 0 ) {
1621         // go up
1622         char *c;
1623
1624         // lop off last word; if the thing ends with /, lop that one, then the next word.
1625         while ( ( c = strrchr ( g_categories [ ui_category].fspath, '/' ) ) ) {
1626           *c = '\0'; // lop off the last hunk
1627           if ( *(c+1) != '\0' ) {
1628             break;
1629           }
1630         } // while
1631
1632         // nothing left?
1633         if ( g_categories [ ui_category].fspath [ 0 ] == '\0' ) {
1634           strcpy ( g_categories [ ui_category].fspath, "/" );
1635         }
1636
1637       } else {
1638         // go down
1639         strcat ( g_categories [ ui_category].fspath, "/" );
1640         strcat ( g_categories [ ui_category].fspath, ui_selected -> ref -> title_en );
1641       }
1642
1643       pnd_log ( pndn_debug, "Cat %s is now in path %s\n", g_categories [ ui_category ].catname, g_categories [ ui_category ].fspath );
1644
1645       // rescan the dir
1646       category_fs_restock ( &(g_categories [ ui_category ]) );
1647       // forget the selection, nolonger applies
1648       ui_selected = NULL;
1649       ui_set_selected ( ui_selected );
1650       // redraw the grid
1651       render_mask |= CHANGED_SELECTION;
1652
1653     } else {
1654       // just run it arbitrarily?
1655
1656       // if this a pnd-file, or just some executable?
1657       if ( strcasestr ( ui_selected -> ref -> object_filename, PND_PACKAGE_FILEEXT ) ) {
1658         // looks like a pnd, now what do we do..
1659         pnd_box_handle h = pnd_disco_file ( ui_selected -> ref -> object_path, ui_selected -> ref -> object_filename );
1660
1661         if ( h ) {
1662           pnd_disco_t *d = pnd_box_get_head ( h );
1663           pnd_apps_exec_disco ( pnd_run_script, d, PND_EXEC_OPTION_NORUN, NULL );
1664           char buffer [ PATH_MAX ];
1665           sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1666           if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1667             emit_and_quit ( buffer );
1668           } else {
1669             emit_and_run ( buffer );
1670           }
1671         }
1672
1673       } else {
1674         // random bin file
1675
1676         // is it even executable? if we don't have handlers for non-executables yet (Jan 2011 we don't),
1677         // then don't even try to run things not-flagged as executable.. but wait most people are on
1678         // FAT filesystems, what a drag, we can't tell.
1679
1680 #if 0 // eat up any pending SDL events and toss 'em?
1681         {
1682           SDL_PumpEvents();
1683           SDL_Event e;
1684           while ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_ALLEVENTS ) > 0 ) {
1685             // spin
1686           }
1687         }
1688 #endif
1689
1690 #if 1
1691         // just exec it
1692         char cwd [ PATH_MAX ];
1693         getcwd ( cwd, PATH_MAX );
1694
1695         chdir ( g_categories [ ui_category ].fspath );
1696         pnd_exec_no_wait_1 ( ui_selected -> ref -> title_en, NULL );
1697         chdir ( cwd );
1698 #else
1699         // get mmwrapper to run it
1700         char buffer [ PATH_MAX ];
1701         sprintf ( buffer, "%s %s/%s\n", MM_RUN, g_categories [ ui_category ].fspath, ui_selected -> ref -> title_en );
1702         if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1703           emit_and_quit ( buffer );
1704         } else {
1705           emit_and_run ( buffer );
1706         }
1707 #endif
1708       } // pnd or bin?
1709
1710     } // dir or file?
1711
1712   } else {
1713
1714     // set app-run speed
1715     int use_run_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.use_run_speed", 0 );
1716     if ( use_run_speed > 0 ) {
1717       int mm_speed = pnd_conf_get_as_int_d ( g_conf, "minimenu.run_speed", -1 );
1718       if ( mm_speed > -1 ) {
1719         char buffer [ 512 ];
1720         snprintf ( buffer, 500, "sudo /usr/pandora/scripts/op_cpuspeed.sh %d", mm_speed );
1721         system ( buffer );
1722       }
1723     } // do speed change?
1724
1725     // request app to run and quit mmenu
1726     pnd_apps_exec_disco ( pnd_run_script, ui_selected -> ref, PND_EXEC_OPTION_NORUN, NULL );
1727     char buffer [ PATH_MAX ];
1728     sprintf ( buffer, "%s %s\n", MM_RUN, pnd_apps_exec_runline() );
1729     if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.live_on_run", 0 ) == 0 ) {
1730       emit_and_quit ( buffer );
1731     } else {
1732       emit_and_run ( buffer );
1733     }
1734   }
1735
1736   return;
1737 }
1738
1739 void ui_push_ltrigger ( void ) {
1740   unsigned char oldcat = ui_category;
1741   unsigned int screen_width = ui_display_context.screen_width;
1742   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1743
1744   if ( g_categorycount == 0 ) {
1745     return;
1746   }
1747
1748   if ( ui_category > 0 ) {
1749     ui_category--;
1750     category_fs_restock ( &(g_categories [ ui_category ]) );
1751   } else {
1752     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1753       ui_category = g_categorycount - 1;
1754       ui_catshift = 0;
1755       if ( ui_category >= ( screen_width / tab_width ) ) {
1756         ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
1757       }
1758       category_fs_restock ( &(g_categories [ ui_category ]) );
1759     }
1760   }
1761
1762   if ( oldcat != ui_category ) {
1763     ui_selected = NULL;
1764     ui_set_selected ( ui_selected );
1765   }
1766
1767   // make tab visible?
1768   if ( ui_catshift > 0 && ui_category == ui_catshift - 1 ) {
1769     ui_catshift--;
1770   }
1771
1772   // unscroll
1773   ui_rows_scrolled_down = 0;
1774
1775   render_mask |= CHANGED_CATEGORY;
1776
1777   return;
1778 }
1779
1780 void ui_push_rtrigger ( void ) {
1781   unsigned char oldcat = ui_category;
1782
1783   if ( g_categorycount == 0 ) {
1784     return;
1785   }
1786
1787   unsigned int screen_width = ui_display_context.screen_width;
1788   unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
1789
1790   if ( ui_category < ( g_categorycount - 1 ) ) {
1791     ui_category++;
1792     category_fs_restock ( &(g_categories [ ui_category ]) );
1793   } else {
1794     if ( pnd_conf_get_as_int_d ( g_conf, "tabs.wraparound", 0 ) > 0 ) {
1795       ui_category = 0;
1796       ui_catshift = 0;
1797       category_fs_restock ( &(g_categories [ ui_category ]) );
1798     }
1799   }
1800
1801   if ( oldcat != ui_category ) {
1802     ui_selected = NULL;
1803     ui_set_selected ( ui_selected );
1804   }
1805
1806   // make tab visible?
1807   if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
1808     ui_catshift++;
1809   }
1810
1811   // unscroll
1812   ui_rows_scrolled_down = 0;
1813
1814   render_mask |= CHANGED_CATEGORY;
1815
1816   return;
1817 }
1818
1819 SDL_Surface *ui_scale_image ( SDL_Surface *s, unsigned int maxwidth, int maxheight ) {
1820   double scale = 1000000.0;
1821   double scalex = 1000000.0;
1822   double scaley = 1000000.0;
1823   SDL_Surface *scaled;
1824
1825   scalex = (double)maxwidth / (double)s -> w;
1826
1827   if ( maxheight == -1 ) {
1828     scale = scalex;
1829   } else {
1830     scaley = (double)maxheight / (double)s -> h;
1831
1832     if ( scaley < scalex ) {
1833       scale = scaley;
1834     } else {
1835       scale = scalex;
1836     }
1837
1838   }
1839
1840   scaled = rotozoomSurface ( s, 0 /* angle*/, scale /* scale */, 1 /* smooth==1*/ );
1841   SDL_FreeSurface ( s );
1842   s = scaled;
1843
1844   return ( s );
1845 }
1846
1847 void ui_loadscreen ( void ) {
1848
1849   SDL_Rect dest;
1850
1851   // clear the screen
1852   SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1853
1854   // render text
1855   SDL_Surface *rtext;
1856   rtext = TTF_RenderText_Blended ( g_big_font, "Setting up menu...", ui_display_context.fontcolor );
1857   dest.x = 20;
1858   dest.y = 20;
1859   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1860   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1861   SDL_FreeSurface ( rtext );
1862
1863   return;
1864 }
1865
1866 void ui_discoverscreen ( unsigned char clearscreen ) {
1867
1868   SDL_Rect dest;
1869
1870   // clear the screen
1871   if ( clearscreen ) {
1872     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1873
1874     // render background
1875     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1876       dest.x = 0;
1877       dest.y = 0;
1878       dest.w = sdl_realscreen -> w;
1879       dest.h = sdl_realscreen -> h;
1880       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1881       SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1882     }
1883
1884   }
1885
1886   // render text
1887   SDL_Surface *rtext;
1888   rtext = TTF_RenderText_Blended ( g_big_font, "Looking for applications...", ui_display_context.fontcolor );
1889   if ( clearscreen ) {
1890     dest.x = 20;
1891     dest.y = 20;
1892   } else {
1893     dest.x = 20;
1894     dest.y = 40;
1895   }
1896   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, &dest );
1897   SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1898   SDL_FreeSurface ( rtext );
1899
1900   // render icon
1901   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1902     dest.x = rtext -> w + 30;
1903     dest.y = 20;
1904     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, &dest );
1905     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
1906   }
1907
1908   return;
1909 }
1910
1911 void ui_cachescreen ( unsigned char clearscreen, char *filename ) {
1912
1913   SDL_Rect rects [ 4 ];
1914   SDL_Rect *dest = rects;
1915   SDL_Rect src;
1916   bzero ( dest, sizeof(SDL_Rect)* 4 );
1917
1918   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
1919   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
1920   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
1921   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
1922
1923   static unsigned int stepx = 0;
1924
1925   // clear the screen
1926   if ( clearscreen ) {
1927     SDL_FillRect( SDL_GetVideoSurface(), NULL, 0 );
1928
1929     // render background
1930     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1931       dest -> x = 0;
1932       dest -> y = 0;
1933       dest -> w = sdl_realscreen -> w;
1934       dest -> h = sdl_realscreen -> h;
1935       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, NULL /* 0,0 */ );
1936       dest++;
1937     }
1938
1939   } else {
1940
1941     // render background
1942     if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
1943       src.x = 0;
1944       src.y = 0;
1945       src.w = sdl_realscreen -> w;
1946       src.h = 100;
1947       dest -> x = 0;
1948       dest -> y = 0;
1949       dest -> w = sdl_realscreen -> w;
1950       dest -> h = sdl_realscreen -> h;
1951       SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, &src, sdl_realscreen, dest );
1952       dest++;
1953     }
1954
1955   } // clear it
1956
1957   // render text
1958   SDL_Surface *rtext;
1959   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
1960   rtext = TTF_RenderText_Blended ( g_big_font, "Caching applications artwork...", tmpfontcolor );
1961   dest -> x = 20;
1962   dest -> y = 20;
1963   SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1964   SDL_FreeSurface ( rtext );
1965   dest++;
1966
1967   // render icon
1968   if ( g_imagecache [ IMG_ICON_MISSING ].i ) {
1969     dest -> x = rtext -> w + 30 + stepx;
1970     dest -> y = 20;
1971     SDL_BlitSurface ( g_imagecache [ IMG_ICON_MISSING ].i, NULL, sdl_realscreen, dest );
1972     dest++;
1973   }
1974
1975   // filename
1976   if ( filename ) {
1977     rtext = TTF_RenderText_Blended ( g_tab_font, filename, tmpfontcolor );
1978     dest -> x = 20;
1979     dest -> y = 50;
1980     SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
1981     SDL_FreeSurface ( rtext );
1982     dest++;
1983   }
1984
1985   // move across
1986   stepx += 20;
1987
1988   if ( stepx > 350 ) {
1989     stepx = 0;
1990   }
1991
1992   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
1993
1994   return;
1995 }
1996
1997 int ui_selected_index ( void ) {
1998
1999   if ( ! ui_selected ) {
2000     return ( -1 ); // no index
2001   }
2002
2003   mm_appref_t *r = g_categories [ ui_category ].refs;
2004   int counter = 0;
2005   while ( r ) {
2006     if ( r == ui_selected ) {
2007       return ( counter );
2008     }
2009     r = r -> next;
2010     counter++;
2011   }
2012
2013   return ( -1 );
2014 }
2015
2016 static mm_appref_t *timer_ref = NULL;
2017 void ui_set_selected ( mm_appref_t *r ) {
2018
2019   render_mask |= CHANGED_SELECTION;
2020
2021   if ( ! pnd_conf_get_as_int_d ( g_conf, "minimenu.load_previews_later", 0 ) ) {
2022     return; // no desire to defer anything
2023   }
2024
2025   if ( ! r ) {
2026     // cancel timer
2027     SDL_SetTimer ( 0, NULL );
2028     timer_ref = NULL;
2029     return;
2030   }
2031
2032   SDL_SetTimer ( pnd_conf_get_as_int_d ( g_conf, "previewpic.defer_timer_ms", 1000 ), ui_callback_f );
2033   timer_ref = r;
2034
2035   return;
2036 }
2037
2038 unsigned int ui_callback_f ( unsigned int t ) {
2039
2040   if ( ui_selected != timer_ref ) {
2041     return ( 0 ); // user has moved it, who cares
2042   }
2043
2044   SDL_Event e;
2045   bzero ( &e, sizeof(SDL_Event) );
2046   e.type = SDL_USEREVENT;
2047   e.user.code = sdl_user_ticker;
2048   SDL_PushEvent ( &e );
2049
2050   return ( 0 );
2051 }
2052
2053 int ui_modal_single_menu ( char *argv[], unsigned int argc, char *title, char *footer ) {
2054   SDL_Rect rects [ 40 ];
2055   SDL_Rect *dest = rects;
2056   SDL_Rect src;
2057   SDL_Surface *rtext;
2058   unsigned char max_visible = pnd_conf_get_as_int_d ( g_conf, "detailtext.max_visible" , 11 );
2059   unsigned char first_visible = 0;
2060
2061   bzero ( rects, sizeof(SDL_Rect) * 40 );
2062
2063   unsigned int sel = 0;
2064
2065   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
2066   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
2067   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
2068   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
2069
2070   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
2071
2072   SDL_Color selfontcolor = { 0/*font_rgba_r*/, font_rgba_g, font_rgba_b, font_rgba_a };
2073
2074   unsigned int i;
2075   SDL_Event event;
2076
2077   while ( 1 ) {
2078
2079     // clear
2080     dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2081     dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2082     dest -> w = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> w;
2083     dest -> h = ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h;
2084     SDL_FillRect( sdl_realscreen, dest, 0 );
2085
2086     // show dialog background
2087     if ( g_imagecache [ IMG_DETAIL_BG ].i ) {
2088       src.x = 0;
2089       src.y = 0;
2090       src.w = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> w;
2091       src.h = ((SDL_Surface*)(g_imagecache [ IMG_DETAIL_BG ].i)) -> h;
2092       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2093       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2094       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2095       // repeat for darken?
2096       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2097       //SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_BG ].i, &src, sdl_realscreen, dest );
2098       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2099       dest++;
2100     }
2101
2102     // show dialog frame
2103     if ( g_imagecache [ IMG_DETAIL_PANEL ].i ) {
2104       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 );
2105       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 );
2106       SDL_BlitSurface ( g_imagecache [ IMG_DETAIL_PANEL ].i, NULL /* whole image */, sdl_realscreen, dest );
2107       //SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2108       dest++;
2109     }
2110
2111     // show header
2112     if ( title ) {
2113       rtext = TTF_RenderText_Blended ( g_tab_font, title, tmpfontcolor );
2114       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2115       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 20;
2116       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2117       SDL_FreeSurface ( rtext );
2118       dest++;
2119     }
2120
2121     // show footer
2122     if ( footer ) {
2123       rtext = TTF_RenderText_Blended ( g_tab_font, footer, tmpfontcolor );
2124       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2125       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) +
2126         ((SDL_Surface*) g_imagecache [ IMG_DETAIL_PANEL ].i) -> h
2127         - 60;
2128       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2129       SDL_FreeSurface ( rtext );
2130       dest++;
2131     }
2132
2133     // show options
2134     for ( i = first_visible; i < first_visible + max_visible && i < argc; i++ ) {
2135
2136       // show options
2137       if ( sel == i ) {
2138         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], selfontcolor );
2139       } else {
2140         rtext = TTF_RenderText_Blended ( g_tab_font, argv [ i ], tmpfontcolor );
2141       }
2142       dest -> x = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_x", 460 ) + 20;
2143       dest -> y = pnd_conf_get_as_int_d ( g_conf, "detailpane.pane_offset_y", 60 ) + 40 + ( 20 * ( i + 1 - first_visible ) );
2144       SDL_BlitSurface ( rtext, NULL /* full src */, sdl_realscreen, dest );
2145       SDL_FreeSurface ( rtext );
2146       dest++;
2147
2148     } // for
2149
2150     // update all the rects and send it all to sdl
2151     SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
2152     dest = rects;
2153
2154     // check for input
2155     while ( SDL_WaitEvent ( &event ) ) {
2156
2157       switch ( event.type ) {
2158
2159       //case SDL_KEYUP:
2160       case SDL_KEYDOWN:
2161
2162         if ( event.key.keysym.sym == SDLK_UP ) {
2163           if ( sel ) {
2164             sel--;
2165
2166             if ( sel < first_visible ) {
2167               first_visible--;
2168             }
2169
2170           }
2171         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
2172
2173           if ( sel < argc - 1 ) {
2174             sel++;
2175
2176             // ensure visibility
2177             if ( sel >= first_visible + max_visible ) {
2178               first_visible++;
2179             }
2180
2181           }
2182
2183         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
2184           return ( sel );
2185
2186 #if 0
2187         } else if ( event.key.keysym.sym == SDLK_q ) {
2188           exit ( 0 );
2189 #endif
2190
2191         } else {
2192           return ( -1 ); // nada
2193         }
2194
2195         break;
2196
2197       } // switch
2198
2199       break;
2200     } // while
2201
2202   } // while
2203
2204   return ( -1 );
2205 }
2206
2207 unsigned char ui_determine_row ( mm_appref_t *a ) {
2208   unsigned int row = 0;
2209
2210   mm_appref_t *i = g_categories [ ui_category ].refs;
2211   while ( i != a ) {
2212     i = i -> next;
2213     row++;
2214   } // while
2215   row /= ui_display_context.col_max;
2216
2217   return ( row );
2218 }
2219
2220 unsigned char ui_determine_screen_row ( mm_appref_t *a ) {
2221   return ( ui_determine_row ( a ) % ui_display_context.row_max );
2222 }
2223
2224 unsigned char ui_determine_screen_col ( mm_appref_t *a ) {
2225   unsigned int col = 0;
2226
2227   mm_appref_t *i = g_categories [ ui_category ].refs;
2228   while ( i != a ) {
2229     i = i -> next;
2230     col++;
2231   } // while
2232   col %= ui_display_context.col_max;
2233
2234   return ( col );
2235 }
2236
2237 unsigned char ui_show_info ( char *pndrun, pnd_disco_t *p ) {
2238   char *viewer, *searchpath;
2239   pnd_conf_handle desktoph;
2240
2241   // viewer
2242   searchpath = pnd_conf_query_searchpath();
2243
2244   desktoph = pnd_conf_fetch_by_id ( pnd_conf_desktop, searchpath );
2245
2246   if ( ! desktoph ) {
2247     return ( 0 );
2248   }
2249
2250   viewer = pnd_conf_get_as_char ( desktoph, "info.viewer" );
2251
2252   if ( ! viewer ) {
2253     return ( 0 ); // no way to view the file
2254   }
2255
2256   // etc
2257   if ( ! p -> unique_id ) {
2258     return ( 0 );
2259   }
2260
2261   if ( ! p -> info_filename ) {
2262     return ( 0 );
2263   }
2264
2265   if ( ! p -> info_name ) {
2266     return ( 0 );
2267   }
2268
2269   if ( ! pndrun ) {
2270     return ( 0 );
2271   }
2272
2273   // exec line
2274   char args [ 1001 ];
2275   char *pargs = args;
2276   if ( pnd_conf_get_as_char ( desktoph, "info.viewer_args" ) ) {
2277     snprintf ( pargs, 1001, "%s %s",
2278                pnd_conf_get_as_char ( desktoph, "info.viewer_args" ), p -> info_filename );
2279   } else {
2280     pargs = NULL;
2281   }
2282
2283   char pndfile [ 1024 ];
2284   if ( p -> object_type == pnd_object_type_directory ) {
2285     // for PXML-app-dir, pnd_run.sh doesn't want the PXML.xml.. it just wants the dir-name
2286     strncpy ( pndfile, p -> object_path, 1000 );
2287   } else if ( p -> object_type == pnd_object_type_pnd ) {
2288     // pnd_run.sh wants the full path and filename for the .pnd file
2289     snprintf ( pndfile, 1020, "%s/%s", p -> object_path, p -> object_filename );
2290   }
2291
2292   if ( ! pnd_apps_exec ( pndrun, pndfile, p -> unique_id, viewer, p -> startdir, pargs,
2293                          p -> clockspeed ? atoi ( p -> clockspeed ) : 0, PND_EXEC_OPTION_NORUN ) )
2294   {
2295     return ( 0 );
2296   }
2297
2298   pnd_log ( pndn_debug, "Info Exec=%s\n", pnd_apps_exec_runline() );
2299
2300   // try running it
2301   int x;
2302   if ( ( x = fork() ) < 0 ) {
2303     pnd_log ( pndn_error, "ERROR: Couldn't fork()\n" );
2304     return ( 0 );
2305   }
2306
2307   if ( x == 0 ) {
2308     execl ( "/bin/sh", "/bin/sh", "-c", pnd_apps_exec_runline(), (char*)NULL );
2309     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", pnd_apps_exec_runline() );
2310     return ( 0 );
2311   }
2312
2313   return ( 1 );
2314 }
2315
2316 typedef struct {
2317   SDL_Rect r;
2318   int catnum;
2319   mm_appref_t *ref;
2320 } ui_touch_t;
2321 #define MAXTOUCH 100
2322 ui_touch_t ui_touchrects [ MAXTOUCH ];
2323 unsigned char ui_touchrect_count = 0;
2324
2325 void ui_register_reset ( void ) {
2326   bzero ( ui_touchrects, sizeof(ui_touch_t)*MAXTOUCH );
2327   ui_touchrect_count = 0;
2328   return;
2329 }
2330
2331 void ui_register_tab ( unsigned char catnum, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2332
2333   if ( ui_touchrect_count == MAXTOUCH ) {
2334     return;
2335   }
2336
2337   ui_touchrects [ ui_touchrect_count ].r.x = x;
2338   ui_touchrects [ ui_touchrect_count ].r.y = y;
2339   ui_touchrects [ ui_touchrect_count ].r.w = w;
2340   ui_touchrects [ ui_touchrect_count ].r.h = h;
2341   ui_touchrects [ ui_touchrect_count ].catnum = catnum;
2342   ui_touchrect_count++;
2343
2344   return;
2345 }
2346
2347 void ui_register_app ( mm_appref_t *app, unsigned int x, unsigned int y, unsigned int w, unsigned int h ) {
2348
2349   if ( ui_touchrect_count == MAXTOUCH ) {
2350     return;
2351   }
2352
2353   ui_touchrects [ ui_touchrect_count ].r.x = x;
2354   ui_touchrects [ ui_touchrect_count ].r.y = y;
2355   ui_touchrects [ ui_touchrect_count ].r.w = w;
2356   ui_touchrects [ ui_touchrect_count ].r.h = h;
2357   ui_touchrects [ ui_touchrect_count ].ref = app;
2358   ui_touchrect_count++;
2359
2360   return;
2361 }
2362
2363 void ui_touch_act ( unsigned int x, unsigned int y ) {
2364
2365   unsigned char i;
2366   ui_touch_t *t;
2367
2368   for ( i = 0; i < ui_touchrect_count; i++ ) {
2369     t = &(ui_touchrects [ i ]);
2370
2371     if ( x >= t -> r.x &&
2372          x <= t -> r.x + t -> r.w &&
2373          y >= t -> r.y &&
2374          y <= t -> r.y + t -> r.h
2375        )
2376     {
2377
2378       if ( t -> ref ) {
2379         ui_selected = t -> ref;
2380         ui_push_exec();
2381       } else {
2382         if ( ui_category != t -> catnum ) {
2383           ui_selected = NULL;
2384         }
2385         ui_category = t -> catnum;
2386         render_mask |= CHANGED_CATEGORY;
2387         // rescan the dir
2388         category_fs_restock ( &(g_categories [ ui_category ]) );
2389       }
2390
2391       break;
2392     }
2393
2394   } // for
2395
2396   return;
2397 }
2398
2399 unsigned char ui_forkexec ( char *argv[] ) {
2400   char *fooby = argv[0];
2401   int x;
2402
2403   if ( ( x = fork() ) < 0 ) {
2404     pnd_log ( pndn_error, "ERROR: Couldn't fork() for '%s'\n", fooby );
2405     return ( 0 );
2406   }
2407
2408   if ( x == 0 ) { // child
2409     execv ( fooby, argv );
2410     pnd_log ( pndn_error, "ERROR: Couldn't exec(%s)\n", fooby );
2411     return ( 0 );
2412   }
2413
2414   // parent, success
2415   return ( 1 );
2416 }
2417
2418 unsigned char ui_threaded_defered_preview ( pnd_disco_t *p ) {
2419
2420   if ( ! cache_preview ( p, pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_width", 200 ),
2421                          pnd_conf_get_as_int_d ( g_conf, "previewpic.cell_height", 180 ) )
2422      )
2423   {
2424     pnd_log ( pndn_debug, "THREAD: Couldn't load preview pic: '%s' -> '%s'\n",
2425               IFNULL(p->title_en,"No Name"), p -> preview_pic1 );
2426   }
2427
2428   // trigger that we completed
2429   SDL_Event e;
2430   bzero ( &e, sizeof(SDL_Event) );
2431   e.type = SDL_USEREVENT;
2432   e.user.code = sdl_user_finishedpreview;
2433   e.user.data1 = p;
2434   SDL_PushEvent ( &e );
2435
2436   return ( 0 );
2437 }
2438
2439 SDL_Thread *g_icon_thread = NULL;
2440 void ui_post_scan ( void ) {
2441
2442   // if deferred icon load, kick off the thread now
2443   if ( pnd_conf_get_as_int_d ( g_conf, "minimenu.load_icons_later", 0 ) == 1 ) {
2444
2445     g_icon_thread = SDL_CreateThread ( (void*)ui_threaded_defered_icon, NULL );
2446
2447     if ( ! g_icon_thread ) {
2448       pnd_log ( pndn_error, "ERROR: Couldn't create icon thread\n" );
2449     }
2450
2451   } // deferred icon load
2452
2453   // reset view
2454   ui_selected = NULL;
2455   ui_rows_scrolled_down = 0;
2456   // set back to first tab, to be safe
2457   ui_category = 0;
2458   ui_catshift = 0;
2459
2460   // do we have a preferred category to jump to?
2461   char *dc = pnd_conf_get_as_char ( g_conf, "categories.default_cat" );
2462   if ( dc ) {
2463
2464     // attempt to find default cat; if we do find it, select it; otherwise
2465     // default behaviour will pick first cat (ie: usually All)
2466     unsigned int i;
2467     for ( i = 0; i < g_categorycount; i++ ) {
2468       if ( strcasecmp ( g_categories [ i ].catname, dc ) == 0 ) {
2469         ui_category = i;
2470         // ensure visibility
2471         unsigned int screen_width = ui_display_context.screen_width;
2472         unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2473         if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
2474           ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
2475         }
2476         break;
2477       }
2478     }
2479
2480     if ( i == g_categorycount ) {
2481       pnd_log ( pndn_warning, "  User defined default category '%s' but not found, so using default behaviour\n", dc );
2482     }
2483
2484   } // default cat
2485
2486   // redraw all
2487   render_mask |= CHANGED_EVERYTHING;
2488
2489   return;
2490 }
2491
2492 unsigned char ui_threaded_defered_icon ( void *p ) {
2493   extern pnd_box_handle g_active_apps;
2494   pnd_box_handle h = g_active_apps;
2495
2496   unsigned char maxwidth, maxheight;
2497   maxwidth = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_width", 50 );
2498   maxheight = pnd_conf_get_as_int_d ( g_conf, "grid.icon_max_height", 50 );
2499
2500   pnd_disco_t *iter = pnd_box_get_head ( h );
2501
2502   while ( iter ) {
2503
2504     // cache it
2505     if ( iter -> pnd_icon_pos &&
2506          ! cache_icon ( iter, maxwidth, maxheight ) )
2507     {
2508       pnd_log ( pndn_warning, "  Couldn't load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2509     } else {
2510
2511       // trigger that we completed
2512       SDL_Event e;
2513       bzero ( &e, sizeof(SDL_Event) );
2514       e.type = SDL_USEREVENT;
2515       e.user.code = sdl_user_finishedicon;
2516       SDL_PushEvent ( &e );
2517
2518       //pnd_log ( pndn_warning, "  Finished deferred load icon: '%s'\n", IFNULL(iter->title_en,"No Name") );
2519       usleep ( pnd_conf_get_as_int_d ( g_conf, "minimenu.defer_icon_us", 50000 ) );
2520
2521     }
2522
2523     // next
2524     iter = pnd_box_get_next ( iter );
2525   } // while
2526
2527   return ( 0 );
2528 }
2529
2530 void ui_show_hourglass ( unsigned char updaterect ) {
2531
2532   SDL_Rect dest;
2533   SDL_Surface *s = g_imagecache [ IMG_HOURGLASS ].i;
2534
2535   dest.x = ( 800 - s -> w ) / 2;
2536   dest.y = ( 480 - s -> h ) / 2;
2537
2538   SDL_BlitSurface ( s, NULL /* whole image */, sdl_realscreen, &dest );
2539
2540   if ( updaterect ) {
2541     SDL_UpdateRects ( sdl_realscreen, 1, &dest );
2542   }
2543
2544   return;
2545 }
2546
2547 unsigned char ui_pick_skin ( void ) {
2548 #define MAXSKINS 10
2549   char *skins [ MAXSKINS ];
2550   unsigned char iter;
2551
2552   char *searchpath = pnd_conf_get_as_char ( g_conf, "minimenu.skin_searchpath" );
2553   char tempname [ 100 ];
2554
2555   iter = 0;
2556
2557   skins [ iter++ ] = "No skin change";
2558
2559   SEARCHPATH_PRE
2560   {
2561     DIR *d = opendir ( buffer );
2562
2563     if ( d ) {
2564       struct dirent *dd;
2565
2566       while ( ( dd = readdir ( d ) ) ) {
2567
2568         if ( dd -> d_name [ 0 ] == '.' ) {
2569           // ignore
2570         } else if ( ( dd -> d_type == DT_DIR || dd -> d_type == DT_UNKNOWN ) &&
2571                     iter < MAXSKINS )
2572         {
2573           snprintf ( tempname, 100, "Skin: %s", dd -> d_name );
2574           skins [ iter++ ] = strdup ( tempname );
2575         }
2576
2577       }
2578
2579       closedir ( d );
2580     }
2581
2582   }
2583   SEARCHPATH_POST
2584
2585   int sel = ui_modal_single_menu ( skins, iter, "Skins", "Enter to select; other to return." );
2586
2587   // did they pick one?
2588   if ( sel > 0 ) {
2589     FILE *f;
2590
2591     char *s = strdup ( pnd_conf_get_as_char ( g_conf, "minimenu.skin_selected" ) );
2592     s = pnd_expand_tilde ( s );
2593
2594     f = fopen ( s, "w" );
2595
2596     free ( s );
2597
2598     if ( f ) {
2599       fprintf ( f, "%s\n", skins [ sel ] + 6 );
2600       fclose ( f );
2601     }
2602
2603     return ( 1 );
2604   }
2605
2606   return ( 0 );
2607 }
2608
2609 void ui_aboutscreen ( char *textpath ) {
2610 #define PIXELW 7
2611 #define PIXELH 7
2612 #define MARGINW 3
2613 #define MARGINH 3
2614 #define SCRW 800
2615 #define SCRH 480
2616 #define ROWS SCRH / ( PIXELH + MARGINH )
2617 #define COLS SCRW / ( PIXELW + MARGINW )
2618
2619   unsigned char pixelboard [ ROWS * COLS ]; // pixel heat
2620   bzero ( pixelboard, ROWS * COLS );
2621
2622   SDL_Surface *rtext;
2623   SDL_Rect r;
2624
2625   SDL_Color rtextc = { 200, 200, 200, 100 };
2626
2627   // pixel scroller
2628   char *textloop [ 500 ];
2629   unsigned int textmax = 0;
2630   bzero ( textloop, 500 * sizeof(char*) );
2631
2632   // cursor scroller
2633   char cbuffer [ 50000 ];
2634   bzero ( cbuffer, 50000 );
2635   unsigned int crevealed = 0;
2636
2637   FILE *f = fopen ( textpath, "r" );
2638
2639   if ( ! f ) {
2640     pnd_log ( pndn_error, "ERROR: Couldn't open about text: %s\n", textpath );
2641     return;
2642   }
2643
2644   char textbuf [ 100 ];
2645   while ( fgets ( textbuf, 100, f ) ) {
2646
2647     // add to full buffer
2648     strncat ( cbuffer, textbuf, 50000 );
2649
2650     // chomp
2651     if ( strchr ( textbuf, '\n' ) ) {
2652       * strchr ( textbuf, '\n' ) = '\0';
2653     }
2654
2655     // add to pixel loop
2656     if ( 1||textbuf [ 0 ] ) {
2657       textloop [ textmax ] = strdup ( textbuf );
2658       textmax++;
2659     }
2660
2661   } // while fgets
2662
2663   fclose ( f );
2664
2665   unsigned int textiter = 0;
2666   while ( textiter < textmax ) {
2667     char *text = textloop [ textiter ];
2668
2669     rtext = NULL;
2670     if ( text [ 0 ] ) {
2671       // render to surface
2672       rtext = TTF_RenderText_Blended ( g_grid_font, text, rtextc );
2673
2674       // render font to pixelboard
2675       unsigned int px, py;
2676       unsigned char *ph;
2677       unsigned int *pixels = rtext -> pixels;
2678       unsigned char cr, cg, cb, ca;
2679       for ( py = 0; py < rtext -> h; py ++ ) {
2680         for ( px = 0; px < ( rtext -> w > COLS ? COLS : rtext -> w ); px++ ) {
2681
2682           SDL_GetRGBA ( pixels [ ( py * rtext -> pitch / 4 ) + px ],
2683                         rtext -> format, &cr, &cg, &cb, &ca );
2684
2685           if ( ca != 0 ) {
2686
2687             ph = pixelboard + ( /*y offset */ 30 * COLS ) + ( py * COLS ) + px /* / 2 */;
2688
2689             if ( *ph < 100 ) {
2690               *ph = 100;
2691             }
2692
2693             ca /= 5;
2694             if ( *ph + ca < 250 ) {
2695               *ph += ca;
2696             }
2697
2698           } // got a pixel?
2699
2700         } // x
2701       } // y
2702
2703     } // got text?
2704
2705     unsigned int runcount = 10;
2706     while ( runcount-- ) {
2707
2708       // clear display
2709       SDL_FillRect( sdl_realscreen, NULL /* whole */, 0 );
2710
2711       // render pixelboard
2712       unsigned int x, y;
2713       unsigned int c;
2714       for ( y = 0; y < ROWS; y++ ) {
2715         for ( x = 0; x < COLS; x++ ) {
2716
2717           if ( 1||pixelboard [ ( y * COLS ) + x ] ) {
2718
2719             // position
2720             r.x = x * ( PIXELW + MARGINW );
2721             r.y = y * ( PIXELH + MARGINH );
2722             r.w = PIXELW;
2723             r.h = PIXELH;
2724             // heat -> colour
2725             c = SDL_MapRGB ( sdl_realscreen -> format, 100 /* r */, 0 /* g */, pixelboard [ ( y * COLS ) + x ] );
2726             // render
2727             SDL_FillRect( sdl_realscreen, &r /* whole */, c );
2728
2729           }
2730
2731         } // x
2732       } // y
2733
2734       // cool pixels
2735       unsigned char *pc = pixelboard;
2736       for ( y = 0; y < ROWS; y++ ) {
2737         for ( x = 0; x < COLS; x++ ) {
2738
2739           if ( *pc > 10 ) {
2740             (*pc) -= 3;
2741           }
2742
2743           pc++;
2744         } // x
2745       } // y
2746
2747       // slide pixels upwards
2748       memmove ( pixelboard, pixelboard + COLS, ( COLS * ROWS ) - COLS );
2749
2750       // render actual readable text
2751       {
2752
2753         // display up to cursor
2754         SDL_Rect dest;
2755         unsigned int cdraw = 0;
2756         SDL_Surface *cs;
2757         char ctb [ 2 ];
2758
2759         if ( crevealed > 200 ) {
2760           cdraw = crevealed - 200;
2761         }
2762
2763         dest.x = 400;
2764         dest.y = 20;
2765
2766         for ( ; cdraw < crevealed; cdraw++ ) {
2767           ctb [ 0 ] = cbuffer [ cdraw ];
2768           ctb [ 1 ] = '\0';
2769           // move over or down
2770           if ( cbuffer [ cdraw ] == '\n' ) {
2771             // EOL
2772             dest.x = 400;
2773             dest.y += 14;
2774
2775             if ( dest.y > 450 ) {
2776               dest.y = 450;
2777             }
2778
2779           } else {
2780             // draw the char
2781             cs = TTF_RenderText_Blended ( g_tab_font, ctb, rtextc );
2782             if ( cs ) {
2783               SDL_BlitSurface ( cs, NULL /* all */, sdl_realscreen, &dest );
2784               SDL_FreeSurface ( cs );
2785               // over
2786               dest.x += cs -> w;
2787             }
2788           }
2789
2790         }
2791
2792         dest.w = 10;
2793         dest.h = 20;
2794         SDL_FillRect ( sdl_realscreen, &dest /* whole */, 220 );
2795
2796         // increment cursor to next character
2797         if ( cbuffer [ crevealed ] != '\0' ) {
2798           crevealed++;
2799         }
2800
2801       } // draw cursor text
2802
2803       // reveal
2804       //
2805       SDL_UpdateRect ( sdl_realscreen, 0, 0, 0, 0 ); // whole screen
2806
2807       usleep ( 50000 );
2808
2809       // any button? if so, about
2810       {
2811         SDL_PumpEvents();
2812
2813         SDL_Event e;
2814
2815         if ( SDL_PeepEvents ( &e, 1, SDL_GETEVENT, SDL_EVENTMASK(/*SDL_KEYUP|*/SDL_KEYDOWN) ) > 0 ) {
2816           return;
2817         }
2818
2819       }
2820
2821     } // while cooling
2822
2823     if ( rtext ) {
2824       SDL_FreeSurface ( rtext );
2825     }
2826
2827     textiter++;
2828   } // while more text
2829
2830   // free up
2831   unsigned int i;
2832   for ( i = 0; i < textmax; i++ ) {
2833     if ( textloop [ i ] ) {
2834       free ( textloop [ i ] );
2835       textloop [ i ] = 0;
2836     }
2837   }
2838
2839   return;
2840 }
2841
2842 void ui_revealscreen ( void ) {
2843   char *labels [ 500 ];
2844   unsigned int labelmax = 0;
2845   unsigned int i;
2846
2847   if ( ! _categories_inviscount ) {
2848     return; // nothing to do
2849   }
2850
2851   for ( i = 0; i < _categories_inviscount; i++ ) {
2852     labels [ labelmax++ ] = _categories_invis [ i ].catname;
2853   }
2854
2855   int sel = ui_modal_single_menu ( labels, labelmax, "Temporary Category Reveal",
2856                                    "Enter to select; other to return." );
2857
2858   if ( sel >= 0 ) {
2859
2860     if ( category_query ( _categories_invis [ sel ].catname ) ) {
2861       // already present
2862       return;
2863     }
2864
2865     // fix up category name, if its been hacked
2866     if ( strchr ( _categories_invis [ sel ].catname, '.' ) ) {
2867       char *t = _categories_invis [ sel ].catname;
2868       _categories_invis [ sel ].catname = strdup ( strchr ( _categories_invis [ sel ].catname, '.' ) + 1 );
2869       free ( t );
2870     }
2871     // copy invisi-cat into live-cat
2872     memmove ( &(g_categories [ g_categorycount ]), &(_categories_invis [ sel ]), sizeof(mm_category_t) );
2873     g_categorycount++;
2874     // move subsequent invisi-cats up, so the selected invisi-cat is nolonger existing in invisi-list at
2875     // all (avoid double-free() later)
2876     memmove ( &(_categories_invis [ sel ]), &(_categories_invis [ sel + 1 ]), sizeof(mm_category_t) * ( _categories_inviscount - sel - 1 ) );
2877     _categories_inviscount--;
2878
2879     // switch to the new category
2880     ui_category = g_categorycount - 1;
2881
2882     // ensure visibility
2883     unsigned int screen_width = ui_display_context.screen_width;
2884     unsigned int tab_width = pnd_conf_get_as_int ( g_conf, "tabs.tab_width" );
2885     if ( ui_category > ui_catshift + ( screen_width / tab_width ) - 1 ) {
2886       ui_catshift = ui_category - ( screen_width / tab_width ) + 1;
2887     }
2888
2889     // redraw tabs
2890     render_mask |= CHANGED_CATEGORY;
2891   }
2892
2893   return;
2894 }
2895
2896 void ui_recache_context ( ui_context_t *c ) {
2897
2898   c -> screen_width = pnd_conf_get_as_int_d ( g_conf, "display.screen_width", 800 );
2899
2900   c -> font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
2901   c -> font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
2902   c -> font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
2903   c -> font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
2904
2905   c -> grid_offset_x = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_x" );
2906   c -> grid_offset_y = pnd_conf_get_as_int ( g_conf, "grid.grid_offset_y" );
2907
2908   c -> icon_offset_x = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_x" );
2909   c -> icon_offset_y = pnd_conf_get_as_int ( g_conf, "grid.icon_offset_y" );
2910   c -> icon_max_width = pnd_conf_get_as_int ( g_conf, "grid.icon_max_width" );
2911   c -> icon_max_height = pnd_conf_get_as_int ( g_conf, "grid.icon_max_height" );
2912   c -> sel_icon_offset_x = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_x", 0 );
2913   c -> sel_icon_offset_y = pnd_conf_get_as_int_d ( g_conf, "grid.sel_offoffset_y", 0 );
2914
2915   c -> text_width = pnd_conf_get_as_int ( g_conf, "grid.text_width" );
2916   c -> text_clip_x = pnd_conf_get_as_int ( g_conf, "grid.text_clip_x" );
2917   c -> text_offset_x = pnd_conf_get_as_int ( g_conf, "grid.text_offset_x" );
2918   c -> text_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_offset_y" );
2919   c -> text_hilite_offset_y = pnd_conf_get_as_int ( g_conf, "grid.text_hilite_offset_y" );
2920
2921   c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max", 4 );
2922   c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max", 5 );
2923
2924   c -> cell_width = pnd_conf_get_as_int ( g_conf, "grid.cell_width" );
2925   c -> cell_height = pnd_conf_get_as_int ( g_conf, "grid.cell_height" );
2926
2927   c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x", 450 );
2928   c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y", 100 );
2929   c -> arrow_bar_clip_w = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_w", 10 );
2930   c -> arrow_bar_clip_h = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_clip_h", 100 );
2931   c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x", 450 );
2932   c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y", 80 );
2933   c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x", 450 );
2934   c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y", 80 );
2935
2936   // font colour
2937   SDL_Color tmp = { c -> font_rgba_r, c -> font_rgba_g, c -> font_rgba_b, c -> font_rgba_a };
2938   c -> fontcolor = tmp;
2939
2940   // now that we've got 'normal' (detail pane shown) param's, lets check if detail pane
2941   // is hidden; if so, override some values with those alternate skin values where possible.
2942   if ( ui_detail_hidden ) {
2943     // if detail panel is hidden, and theme cannot support it, unhide the bloody thing. (This may help
2944     // when someone is amid theme hacking or changing.)
2945     if ( ! ui_is_detail_hideable() ) {
2946       ui_detail_hidden = 0;
2947     }
2948
2949     // still hidden?
2950     if ( ui_detail_hidden ) {
2951
2952       c -> row_max = pnd_conf_get_as_int_d ( g_conf, "grid.row_max_w", c -> row_max );
2953       c -> col_max = pnd_conf_get_as_int_d ( g_conf, "grid.col_max_w", c -> col_max );
2954
2955       c -> cell_width = pnd_conf_get_as_int_d ( g_conf, "grid.cell_width_w", c -> cell_width );
2956       c -> cell_height = pnd_conf_get_as_int_d ( g_conf, "grid.cell_height_w", c -> cell_height );
2957
2958       c -> arrow_bar_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_x_w", 450 );
2959       c -> arrow_bar_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_bar_y_w", 100 );
2960       c -> arrow_up_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_x_w", 450 );
2961       c -> arrow_up_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_up_y_w", 80 );
2962       c -> arrow_down_x = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_x_w", 450 );
2963       c -> arrow_down_y = pnd_conf_get_as_int_d ( g_conf, "grid.arrow_down_y_w", 80 );
2964
2965     } // if detail hidden.. still.
2966
2967   } // if detail hidden
2968
2969   return;
2970 }
2971
2972 unsigned char ui_is_detail_hideable ( void ) {
2973
2974   // if skin has a bit of info for wide-mode, we assume wide-mode is available
2975   if ( pnd_conf_get_as_char ( g_conf, "grid.row_max_w" ) != NULL ) {
2976     return ( 1 );
2977   }
2978
2979   // else not!
2980   return ( 0 );
2981 }
2982
2983 void ui_toggle_detail_pane ( void ) {
2984
2985   // no bitmask trickery here; I like it to be stand-out obvious at 3am.
2986
2987   if ( ui_detail_hidden ) {
2988     ui_detail_hidden = 0;
2989   } else {
2990     ui_detail_hidden = 1;
2991   }
2992
2993   // repull skin config
2994   ui_recache_context ( &ui_display_context );
2995
2996   // redraw
2997   render_mask |= CHANGED_EVERYTHING;
2998
2999   return;
3000 }