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