mmenu; fix for minor bug -- when 'do not show app in main cat, if it has subcats...
[pandora-libraries.git] / minimenu / mmconf.c
1
2 #include <stdio.h>
3 #include <limits.h> /* for PATH_MAX */
4 #define __USE_GNU /* for strndup */
5 #include <string.h> /* for strdup */
6
7 #include "SDL.h"
8 #include "SDL_image.h"
9 #include "SDL_ttf.h"
10
11 #include "pnd_container.h"
12 #include "pnd_conf.h"
13 #include "pnd_discovery.h"
14 #include "pnd_notify.h"
15 #include "pnd_dbusnotify.h"
16
17 #include "mmenu.h"
18 #include "mmconf.h"
19 #include "mmcat.h"
20 #include "mmui.h"
21 #include "mmwrapcmd.h"
22
23 static unsigned char conf_render_text ( TTF_Font *f, char *buffer, SDL_Rect *dest, unsigned int x, unsigned int y, unsigned char selected );
24 static unsigned char conf_render_line ( SDL_Rect *dest, unsigned int y );
25 static char *conf_format_int ( int v, change_type_e c );
26
27 confitem_t page_general[] = {
28   { "Default tab to show",           "On startup, Minimenu will try to switch to this tab",     NULL /* default */, "categories.default_cat",  ct_visible_tab_list },
29 #if 0 // and also in mmenu.c -- something crashes during image caching
30   { "Set CPU speed within Minimenu", "Whether the next setting is applied or not",              "0",                "minimenu.use_mm_speed",   ct_boolean },
31   { "CPU speed within Minimenu",     "Set low; speed to run Minimenu at",                       "400",              "minimenu.mm_speed",       ct_cpu_speed },
32 #endif
33   { "Show 'All' tab",                "Whethor an All tab is shown or not",                      "1",                "categories.do_all_cat",   ct_boolean },
34   { "Show directory browser tabs",   "Show a tab for each SD card?",                            "0",                "filesystem.do_browser",   ct_boolean },
35   { "Detail panel on start?",        "Show or hide the detail panel when menu starts",          "1",                "display.show_detail_pane", ct_boolean },
36   { "Sub-categories as folders?",    "If no, uses tabs instead of folders within tabs.",        "1",                "tabs.subcat_as_folders",  ct_boolean },
37   { "Sub-category apps in their main cat?","If no, apps with subcategories are only in subcategories","1",          "tabs.subcat_to_parent",   ct_boolean },
38   { "Start with app selected",       "Whethor selection is placed by default or not",           "0",                "minimenu.start_selected", ct_boolean },
39   { "Auto discover pnd apps?",       "If no, turn on diectory browser to manually find apps",   "1",                "filesystem.do_pnd_disco", ct_boolean },
40   // dropped option -- we now strictly enforce free desktop categories (or user defined, but no more bogus PXML categories)
41   //    { "Keep bad categories in Other?", "Lazy dev! Put broken categories into Other to keep clean", "1",               "categories.good_cats_only", ct_boolean },
42   { "Set CPU speed when leaving",    "Whether the next setting is applied or not",              "0",                "minimenu.use_run_speed",  ct_boolean },
43   { "CPU speed when leaving",        "Before running app, set this speed; app may override.",   "500",              "minimenu.run_speed",      ct_cpu_speed },
44   { "Wrap tab change",               "Changing tab left or right, does it wrap around?",        "0",                "tabs.wraparound",         ct_boolean },
45   { "Grid stop vertical",            "Changing selection up or down, does it stop or wrap?",    "0",                "grid.wrap_vert_stop",     ct_boolean },
46   { "Live (not exit) on app run?",   "Normally menu exits (to save ram) on app run",            "0",                "minimenu.live_on_run",    ct_boolean },
47   { "Force wallpaper with..",        "You can force an override over themes background",        "/pandora/appdata/mmenu/wallpaper.png", "minimenu.force_wallpaper",  ct_filename },
48   { "",                              "",                                                        NULL,               NULL,                      ct_nil },
49   { "^- Back up to main",            "Go back to top level of configuration",                   NULL,               NULL,                      ct_switch_page, NULL },
50   { NULL }
51 };
52
53 confitem_t page_appshowhide [ CONF_MAX_LISTLENGTH ] = {
54   { "^- Back up to main",        "Go back to top level of configuration",                   NULL,               NULL,                      ct_switch_page, NULL },
55   { CONF_APPLIST_TAG,            "Show or hide this application",                           "1",                "appshow",                 ct_nil },
56   { NULL }
57 };
58
59 confitem_t page_tabshowhide [ CONF_MAX_LISTLENGTH ] = {
60   { "^- Back up to main",        "Go back to top level of configuration",                   NULL,               NULL,                      ct_switch_page, NULL },
61   { CONF_TABLIST_TAG,            "Show or hide or reorder this tab",                        "1",                "tabshow",                 ct_nil },
62   { NULL }
63 };
64
65 confitem_t pages[] = {
66   { "General Options",           "Miscellaneous handy options",                             NULL /* default */, NULL,                      ct_switch_page, page_general },
67   { "Show/Hide Applications",    "Each application can be hidden/revealed",                 NULL /* default */, NULL,                      ct_switch_page, page_appshowhide },
68   { "Show/Hide/Order Tabs",      "Each tab can be hidden/revealed or re-ordered",           NULL /* default */, NULL,                      ct_switch_page, page_tabshowhide },
69   { "",                          "",                                                        NULL,               NULL,                      ct_nil },
70   { "Exit configuration",        "Quit and save configuration",                             NULL /* default */, NULL,                      ct_exit },
71   { "",                          "",                                                        NULL,               NULL,                      ct_nil },
72   { "Reset to defaults",         "Remove any custom options set in this UI",                NULL /* default */, NULL,                      ct_reset },
73   { NULL }
74 };
75
76 extern pnd_conf_handle g_conf;
77 extern SDL_Surface *sdl_realscreen;
78 extern mm_imgcache_t g_imagecache [ IMG_TRUEMAX ];
79 extern pnd_box_handle g_active_apps;
80
81 unsigned char conf_run_menu ( confitem_t *toplevel ) {
82   confitem_t *page = toplevel;
83   unsigned int sel = 0;
84   unsigned int first_visible = 0;
85   unsigned char max_visible = 12;
86   SDL_Event event;
87   confitem_t *lastpage = NULL;
88
89   while ( 1 ) {
90
91     if ( ! page ) {
92       page = pages;
93       sel = 0;
94       first_visible = 0;
95     }
96
97     if ( lastpage != page ) {
98       conf_prepare_page ( page );
99       lastpage = page;
100     }
101
102     conf_display_page ( page, sel, first_visible, max_visible );
103
104     // check for input
105     while ( SDL_WaitEvent ( &event ) ) {
106
107       switch ( event.type ) {
108
109       //case SDL_KEYUP:
110       case SDL_KEYDOWN:
111
112         if ( event.key.keysym.sym == SDLK_UP ) {
113
114           do {
115
116             if ( sel ) {
117               sel--;
118
119               if ( sel < first_visible ) {
120                 first_visible--;
121               }
122
123             }
124
125           } while ( page [ sel ].type == ct_nil );
126
127         } else if ( event.key.keysym.sym == SDLK_DOWN ) {
128
129           do {
130
131             if ( page [ sel + 1 ].text ) {
132               sel++;
133
134               // ensure visibility
135               if ( sel >= first_visible + max_visible ) {
136                 first_visible++;
137               }
138
139             }
140
141           } while ( page [ sel ].type == ct_nil );
142
143         } else if ( event.key.keysym.sym == SDLK_PAGEUP ) {
144           page = NULL;
145
146         } else if ( event.key.keysym.sym == SDLK_LEFT || event.key.keysym.sym == SDLK_RIGHT ) {
147
148           unsigned char left = 0;
149           if ( event.key.keysym.sym == SDLK_LEFT ) {
150             left = 1;
151           }
152
153           switch ( page [ sel ].type ) {
154
155           case ct_cpu_speed:
156             {
157               int v = pnd_conf_get_as_int ( g_conf, page [ sel ].key );
158               if ( v == PND_CONF_BADNUM ) {
159                 v = 500;
160               }
161
162               if ( left ) {
163                 if ( v > 30 ) {
164                   v -= 10;
165                 }
166               } else {
167                 v += 10;
168               }
169
170               char buffer [ 20 ];
171               sprintf ( buffer, "%u", v );
172               pnd_conf_set_char ( g_conf, page [ sel ].key, buffer );
173
174             }
175             break;
176
177           case ct_visible_tab_list:
178             {
179               if ( g_categorycount ) {
180                 char *v = pnd_conf_get_as_char ( g_conf, page [ sel ].key );
181                 if ( v ) {
182                   unsigned char n = 0;
183                   for ( n = 0; n < g_categorycount; n++ ) {
184                     if ( strcmp ( v, g_categories [ n ] -> catname ) == 0 ) {
185                       break;
186                     }
187                   }
188                   if ( n < g_categorycount ) {
189                     if ( left && n ) {
190                       pnd_conf_set_char ( g_conf, page [ sel ].key, g_categories [ n - 1 ] -> catname );
191                     } else if ( ! left && n + 1 < g_categorycount ) {
192                       pnd_conf_set_char ( g_conf, page [ sel ].key, g_categories [ n + 1 ] -> catname );
193                     }
194                   } else {
195                     pnd_conf_set_char ( g_conf, page [ sel ].key, g_categories [ 0 ] -> catname );
196                   }
197                 } else {
198                   pnd_conf_set_char ( g_conf, page [ sel ].key, g_categories [ 0 ] -> catname );
199                 }
200               } // if category count
201             }
202             break;
203
204           case ct_boolean:
205             {
206               int v = pnd_conf_get_as_int ( g_conf, page [ sel ].key );
207               if ( v == PND_CONF_BADNUM ) {
208                 v = 0;
209               }
210               if ( v ) {
211                 v = 0;
212               } else {
213                 v = 1;
214               }
215
216               char buffer [ 20 ];
217               sprintf ( buffer, "%u", v );
218
219               pnd_conf_set_char ( g_conf, page [ sel ].key, buffer );
220             }
221             break;
222
223           case ct_filename:
224             break;
225
226           case ct_nil:
227           case ct_switch_page:
228           case ct_reset:
229           case ct_exit:
230             break;
231
232           } // switch
233
234         } else if ( event.key.keysym.sym == SDLK_ESCAPE ) {
235           emit_and_quit ( MM_QUIT );
236
237         } else if ( event.key.keysym.sym == SDLK_RETURN || event.key.keysym.sym == SDLK_END ) { // return, or "B"
238
239           switch ( page [ sel ].type ) {
240           case ct_exit:
241             {
242               return ( 1 /* always cause restart for now */ );
243             }
244             break;
245           case ct_reset:
246             {
247               conf_reset_to_default ( g_conf );
248               page = NULL;
249               sel = 0;
250             }
251             break;
252           case ct_switch_page:
253             page = page [ sel ].newhead;
254             sel = 0; // should use a stack..
255             break;
256           case ct_filename:
257             break;
258           case ct_nil:
259           case ct_visible_tab_list:
260           case ct_cpu_speed:
261           case ct_boolean:
262             break;
263           } // switch
264
265         } else {
266           // nada
267         }
268
269         break; // case sdl_key_up
270
271       } // switch what SDL event
272
273       break; // get out of sdl-wait-event loop
274     } // while events
275
276   } // while forever
277
278   return ( 1 /* always cause restart for now */ );
279 }
280
281 void conf_display_page ( confitem_t *page, unsigned int selitem, unsigned int first_visible, unsigned int max_visible ) {
282   extern TTF_Font *g_big_font;
283   extern TTF_Font *g_tab_font;
284
285 #define MAXRECTS 200
286   SDL_Rect rects [ MAXRECTS ];
287   SDL_Rect *dest = rects;
288   bzero ( dest, sizeof(SDL_Rect)*MAXRECTS );
289
290   unsigned short int tx, ty;
291
292   // background
293   if ( g_imagecache [ IMG_BACKGROUND_800480 ].i ) {
294     dest -> x = 0;
295     dest -> y = 0;
296     dest -> w = sdl_realscreen -> w;
297     dest -> h = sdl_realscreen -> h;
298     SDL_BlitSurface ( g_imagecache [ IMG_BACKGROUND_800480 ].i, NULL /* whole image */, sdl_realscreen, dest /* 0,0 */ );
299     dest++;
300   }
301
302   // title
303   //
304   // <items>
305   // description
306   // default
307   //
308   // controls help
309
310   // title
311   dest += conf_render_text ( g_big_font, "Minimenu Configuration", dest, 10, 10, CONF_UNSELECTED );
312   dest += conf_render_line ( dest, 45 );
313
314   // scrollable hints
315   {
316
317     // up
318     if ( first_visible > 0 ) {
319       dest -> x = 10;
320       dest -> y = 65;
321       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_UP ].i, NULL /* whole image */, sdl_realscreen, dest );
322       dest++;
323     } // scroll arrow up
324
325     // down
326     if ( first_visible + max_visible < conf_determine_pagelength ( page ) ) {
327       dest -> x = 10;
328       dest -> y = 345;
329       SDL_BlitSurface ( g_imagecache [ IMG_ARROW_DOWN ].i, NULL /* whole image */, sdl_realscreen, dest );
330       dest++;
331     } // scroll arrow up
332
333   } // scrollbar
334
335   // items
336   tx = 50; ty = 70;
337   unsigned char counter = first_visible;
338   while ( page [ counter ].text ) {
339
340     // item
341     conf_render_text ( g_tab_font, page [ counter ].text, dest, tx, ty, counter == selitem ? CONF_SELECTED : CONF_UNSELECTED );
342
343     // value
344     switch ( page [ counter ].type ) {
345     case ct_switch_page:
346       break;
347     case ct_reset:
348       break;
349     case ct_visible_tab_list:
350       {
351         char *v = pnd_conf_get_as_char ( g_conf, page [ counter ].key );
352         if ( v ) {
353           conf_render_text ( g_tab_font, v, dest, tx + 400, ty, counter == selitem ? CONF_SELECTED : CONF_UNSELECTED );
354         } else {
355           conf_render_text ( g_tab_font, "Not specified", dest, tx + 400, ty, counter == selitem ? CONF_SELECTED : CONF_UNSELECTED );
356         }
357       }
358       break;
359     case ct_cpu_speed:
360       {
361         int v = pnd_conf_get_as_int ( g_conf, page [ counter ].key );
362         conf_render_text ( g_tab_font, conf_format_int ( v, page [ counter ].type ), dest, tx + 400, ty, counter == selitem ? CONF_SELECTED : CONF_UNSELECTED );
363       }
364       break;
365     case ct_boolean:
366       {
367         int v = pnd_conf_get_as_int ( g_conf, page [ counter ].key );
368         conf_render_text ( g_tab_font, conf_format_int ( v, page [ counter ].type ), dest, tx + 400, ty, counter == selitem ? CONF_SELECTED : CONF_UNSELECTED );
369       }
370       break;
371     case ct_filename:
372       conf_render_text ( g_tab_font, page [ counter ].def, dest, tx + 400, ty, counter == selitem ? CONF_SELECTED : CONF_UNSELECTED );
373       break;
374     case ct_exit:
375       break;
376     case ct_nil:
377       break;
378     } // switch
379
380     // far enough?
381     if ( counter - first_visible >= max_visible - 1 ) {
382       break;
383     }
384
385     // next line
386     ty += 25;
387     counter++;
388   } // while
389
390   // description and default
391   if ( page [ selitem ].desc ) {
392     dest += conf_render_text ( g_tab_font, page [ selitem ].desc, dest, 380, 400, CONF_UNSELECTED );
393   }
394   if ( page [ selitem ].def ) {
395     char buffer [ 100 ] = "Default: ";
396
397     switch ( page [ selitem ].type ) {
398     case ct_boolean:
399       sprintf ( buffer + strlen ( buffer ), "%s", conf_format_int ( atoi ( page [ selitem ].def ), ct_boolean ) );
400       break;
401     case ct_cpu_speed:
402       sprintf ( buffer + strlen ( buffer ), "%s", conf_format_int ( atoi ( page [ selitem ].def ), ct_cpu_speed ) );
403       break;
404     case ct_filename:
405       sprintf ( buffer + strlen ( buffer ), "%s", page [ selitem ].def );
406       break;
407     case ct_nil:
408     case ct_switch_page:
409     case ct_reset:
410     case ct_exit:
411     case ct_visible_tab_list:
412       break;
413     } // switch
414
415     dest += conf_render_text ( g_tab_font, buffer, dest, 380, 420, CONF_UNSELECTED );
416   } else {
417     dest += conf_render_text ( g_tab_font, "No default value", dest, 380, 420, CONF_UNSELECTED );
418   }
419
420   // cursor's conf item count number - not for top level, just the sublevels
421   if ( page != pages ) {
422     char buffer [ 40 ];
423     sprintf ( buffer, "Config item %d of %d", selitem + 1, conf_determine_pagelength ( page ) );
424     /*dest += */conf_render_text ( g_tab_font, buffer, dest, 380, 440, CONF_UNSELECTED );
425   }
426
427   // help
428   dest += conf_render_line ( dest, 380 );
429   dest += conf_render_text ( g_tab_font, "D-pad Up/down; Y return to index", dest, 10, 400, CONF_UNSELECTED );
430   dest += conf_render_text ( g_tab_font, "Left and right to alter selected item", dest, 10, 420, CONF_UNSELECTED );
431   dest += conf_render_text ( g_tab_font, "B or Enter to activate an option", dest, 10, 440, CONF_UNSELECTED );
432
433   // update all the rects and send it all to sdl
434   // - at this point, we could probably just do 1 rect, of the
435   //   whole screen, and be faster :/
436   SDL_UpdateRects ( sdl_realscreen, dest - rects, rects );
437
438   return;
439 }
440
441 unsigned char conf_render_text ( TTF_Font *f, char *buffer, SDL_Rect *dest, unsigned int x, unsigned int y, unsigned char selected ) {
442   unsigned int font_rgba_r = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_r", 200 );
443   unsigned int font_rgba_g = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_g", 200 );
444   unsigned int font_rgba_b = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_b", 200 );
445   unsigned int font_rgba_a = pnd_conf_get_as_int_d ( g_conf, "display.font_rgba_a", 100 );
446
447   SDL_Color tmpfontcolor = { font_rgba_r, font_rgba_g, font_rgba_b, font_rgba_a };
448   SDL_Color selfontcolor = { 0/*font_rgba_r*/, font_rgba_g, font_rgba_b, font_rgba_a };
449
450   SDL_Surface *rtext = TTF_RenderText_Blended ( f, buffer, selected ? selfontcolor : tmpfontcolor );
451   dest -> x = x;
452   dest -> y = y;
453   SDL_BlitSurface ( rtext, NULL /* all */, sdl_realscreen, dest );
454   SDL_FreeSurface ( rtext );
455
456   return ( 1 );
457 }
458
459 unsigned char conf_render_line ( SDL_Rect *dest, unsigned int y ) {
460
461   dest -> x = 0;
462   dest -> y = y;
463
464   SDL_Surface *i = g_imagecache [ IMG_TAB_LINE ].i;
465
466   while ( dest -> x + i -> w < 800 ) {
467     SDL_BlitSurface ( i, NULL, sdl_realscreen, dest );
468     dest -> x += i -> w;
469   }
470
471   dest -> x = 0;
472   dest -> w = 480 - 10;
473   dest -> h = i -> h;
474
475   return ( 1 );
476 }
477
478 char *conf_format_int ( int v, change_type_e c ) {
479   static char buffer [ 50 ];
480   buffer [ 0 ] = '\0';
481
482   switch ( c ) {
483
484     case ct_cpu_speed:
485       {
486         if ( v == PND_CONF_BADNUM ) {
487           strcpy ( buffer, "Leave Alone" );
488         } else {
489           sprintf ( buffer, "%u", v );
490         }
491       }
492       break;
493
494     case ct_boolean:
495       {
496         if ( v == PND_CONF_BADNUM ) {
497           strcpy ( buffer, "FUBAR" );
498         } else if ( v == 0 ) {
499           strcpy ( buffer, "no" );
500         } else if ( v == 1 ) {
501           strcpy ( buffer, "yes" );
502         } else {
503           strcpy ( buffer, "FUBAR 2" );
504         }
505       }
506       break;
507
508   case ct_filename:
509     break;
510
511   case ct_exit:
512   case ct_reset:
513   case ct_switch_page:
514   case ct_visible_tab_list:
515   case ct_nil:
516     break;
517
518   } // switch
519
520   return ( buffer );
521 }
522
523 unsigned char conf_prepare_page ( confitem_t *page ) {
524   char buffer [ 100 ];
525
526   confitem_t *p = page;
527   confitem_t *template = NULL;
528   while ( p -> text != NULL ) {
529
530     if ( strcmp ( p -> text, CONF_APPLIST_TAG ) == 0 ) {
531       // rewrite this and subsequent items to be the listing
532       template = p;
533       p++;
534
535       template -> text = ""; // so it won't get repopulated again later
536
537       // for each app..
538       pnd_disco_t *iter = pnd_box_get_head ( g_active_apps );
539
540       while ( p - page < CONF_MAX_LISTLENGTH && iter ) {
541
542         p -> text = strndup ( iter -> title_en ? iter -> title_en : "Unnamed", 40 );
543         p -> desc = strdup ( iter -> unique_id );
544         p -> def = NULL;
545
546         sprintf ( buffer, "%s.%s", template -> key, iter -> unique_id );
547         p -> key = strdup ( buffer );
548         p -> type = ct_boolean;
549         p -> newhead = NULL;
550
551         // create to positive if not existant
552         if ( ! pnd_conf_get_as_char ( g_conf, buffer ) ) {
553           pnd_conf_set_char ( g_conf, buffer, "1" );
554         }
555
556         iter = pnd_box_get_next ( iter );
557         p++;
558       } // while not run off end of buffer
559
560       break;
561
562     } else if ( strcmp ( p -> text, CONF_TABLIST_TAG ) == 0 ) {
563       // rewrite this and subsequent items to be the listing
564       template = p;
565       p++;
566
567       template -> text = ""; // so it won't get repopulated again later
568
569       // switch categories being published
570       category_publish ( CFALL, NULL );
571
572       // for each tab
573       unsigned int i;
574       char catname [ 512 ];
575       char *actual_catname;
576       char finalbuf [ 101 ];
577
578       for ( i = 0;  i < g_categorycount; i++ ) {
579
580         // if this is an invisi-guy, it has parent-cat prepended; we want the real cat name.
581         strncpy ( catname, g_categories [ i ] -> catname, 500 );
582
583         if ( ( actual_catname = strchr ( catname, '.' ) ) ) {
584           actual_catname++; // skip the period
585         } else {
586           actual_catname = catname;
587         }
588         //fprintf ( stderr, "conf ui; got '%s' but showing '%s'\n", cc [ i ].catname, actual_catname );
589
590         if ( strncmp ( g_categories [ i ] -> catname, "All ", 4 ) == 0 ) {
591           // skip All tab, since it is generated, and managed by another config item
592           continue;
593         }
594
595         if ( g_categories [ i ] -> parent_catname ) {
596           snprintf ( finalbuf, 100, "%s [%s]", actual_catname, g_categories [ i ] -> parent_catname );
597         } else {
598           strncpy ( finalbuf, actual_catname, 100 );
599         }
600
601         p -> text = strndup ( finalbuf, 40 );
602         p -> desc = NULL;
603         p -> def = NULL;
604
605         sprintf ( buffer, "%s.%s", template -> key, actual_catname );
606         p -> key = strdup ( buffer );
607         p -> type = ct_boolean;
608         p -> newhead = NULL;
609
610         // create to positive if not existant
611         if ( ! pnd_conf_get_as_char ( g_conf, buffer ) ) {
612           pnd_conf_set_char ( g_conf, buffer, "1" );
613         }
614
615         //fprintf ( stderr, "Created tabshow entry '%s'\n", cc [ i ].catname );
616
617         p++;
618       } // for
619
620       // switch categories being published
621       category_publish ( CFNORMAL, NULL );
622
623       break;
624     }
625
626     p++;
627   }
628
629   return ( 1 );
630 }
631
632 unsigned char conf_write ( pnd_conf_handle h, char *fullpath ) {
633
634   // cherry pick the named keys from the conf-ui-array
635   // spill out the apps ands tabs 'broups' of conf keys
636
637   FILE *f = fopen ( fullpath, "w" );
638
639   if ( ! f ) {
640     return ( 0 );
641   }
642
643   fprintf ( f, "# Machine written; do not edit.\n" );
644   fprintf ( f, "# If you do edit, its KEY<tab>VALUE<newline>, nothing extra.\n" );
645   fprintf ( f, "\n" );
646
647   // deal with general keys
648   confitem_t *ci = page_general;
649   while ( ci -> text ) {
650     // does this item have a key? a value? if so, try to emit it.
651     if ( ci -> key ) {
652       char *v = pnd_conf_get_as_char ( h, ci -> key );
653       if ( v ) {
654         fprintf ( f, "%s\t%s\n", ci -> key, v );
655       }
656     }
657     ci++;
658   } // while
659
660   // deal with apps and tabs
661   char *v = pnd_box_get_head ( g_conf );
662   while ( v ) {
663
664     // does item begin with app or tab tag?
665     char *k = pnd_box_get_key ( v );
666
667     if ( k && 
668          ( strncasecmp ( k, "appshow.", 8 ) == 0 ||
669            strncasecmp ( k, "tabshow.", 8 ) == 0 )
670        )
671     {
672       fprintf ( f, "%s\t%s\n", k, v );
673     }
674
675     v = pnd_box_get_next ( v );
676   } // while
677
678   fclose ( f );
679
680   return ( 1 );
681 }
682
683 void conf_merge_into ( char *fullpath, pnd_conf_handle h ) {
684   FILE *f;
685   char buffer [ 1024 ];
686   char *s;
687
688   f = fopen ( fullpath, "r" );
689
690   if ( ! f ) {
691     return;
692   }
693
694   while ( fgets ( buffer, 1000, f ) ) {
695 #if 0
696     // trim trailing spaces
697     s = strchr ( buffer, ' ' );
698     if ( s ) {
699       *s = '\0';
700     }
701 #endif
702     // and #...
703     s = strchr ( buffer, '#' );
704     if ( s ) {
705       *s = '\0';
706     }
707     // and newline...
708     s = strchr ( buffer, '\n' );
709     if ( s ) {
710       *s = '\0';
711     }
712
713     // if theres anything left..
714     if ( buffer [ 0 ] != '\0' ) {
715
716       // assume FOO<tab>BAR<newline> since this really should be machine written, not human screwed with; or if human
717       // edited, assume they know what to do :) I even put in some 'docs' in the conf file.
718       char *s = strchr ( buffer, '\t' );
719
720       if ( s ) {
721         *s = '\0';
722
723         // set it; libpnd conf code already handles 'existant' or 'new'
724         pnd_conf_set_char ( h, buffer, s + 1 );
725
726       } // found a <tab>?
727
728     } // anything left?
729
730   } // while
731
732   fclose ( f );
733
734   return;
735 }
736
737 char *conf_determine_location ( pnd_conf_handle h ) {
738   static char path [ PATH_MAX ];
739
740   bzero ( path, PATH_MAX );
741
742   if ( ! getenv ( "HOME" ) ) {
743     return ( "." ); // wtf?
744   }
745
746   snprintf ( path, PATH_MAX - strlen(CONF_PREF_FILENAME) - 1, "%s/%s", getenv ( "HOME" ), CONF_PREF_FILENAME );
747
748   return ( path );
749 }
750
751 void conf_setup_missing ( pnd_conf_handle h ) {
752
753   confitem_t *ci = page_general;
754
755   while ( ci -> text ) {
756
757     // does this item have a default value?
758     if ( ci -> def ) {
759
760       // it does, so lets see if we can pull a current value in; if not, set one
761       char *v = pnd_conf_get_as_char ( h, ci -> key );
762
763       if ( ! v ) {
764         fprintf ( stderr, "pref conf: no value present in config, better set to default; key is '%s' def '%s'\n", ci -> key, ci -> def );
765         pnd_conf_set_char ( h, ci -> key, ci -> def );
766       }
767
768     } // has def?
769
770     ci++;
771   } // while
772
773   return;
774 }
775
776 void conf_reset_to_default ( pnd_conf_handle h ) {
777
778   // reset all keys to default value - if present
779   // reset all apps to show
780   // reset all tabs to show
781
782   // deal with general keys
783   confitem_t *ci = page_general;
784   while ( ci -> text ) {
785
786     // does this item have a default value? if so, set it
787     if ( ci -> key && ci -> def ) {
788       pnd_conf_set_char ( h, ci -> key, ci -> def );
789     }
790
791     ci++;
792   } // while
793
794   // deal with apps and tabs
795   char *v = pnd_box_get_head ( g_conf );
796   char *next;
797   while ( v ) {
798     next = pnd_box_get_next ( v );
799
800     // does item begin with app or tab tag?
801     char *k = pnd_box_get_key ( v );
802
803     if ( k && 
804          ( strncasecmp ( k, "appshow.", 8 ) == 0 ||
805            strncasecmp ( k, "tabshow.", 8 ) == 0 )
806        )
807     {
808       pnd_conf_set_char ( g_conf, k, "1" );
809     }
810
811     v = next;
812   } // while
813
814   return;
815 }
816
817 unsigned int conf_determine_pagelength ( confitem_t *page ) {
818   confitem_t *p = page;
819   while ( p -> text ) {
820     p++;
821   }
822   return ( p - page );
823 }