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