1: /*
     2:  *  Guitar-ZyX(tm)::MasterControlProgram - portable guitar F/X controller
     3:  *  Copyright (C) 2009  Douglas McClendon
     4:  *
     5:  *  This program is free software: you can redistribute it and/or modify
     6:  *  it under the terms of the GNU General Public License as published by
     7:  *  the Free Software Foundation, version 3 of the License.
     8:  *
     9:  *  This program is distributed in the hope that it will be useful,
    10:  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
    11:  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12:  *  GNU General Public License for more details.
    13:  *
    14:  *  You should have received a copy of the GNU General Public License
    15:  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16: */
    17: /*
    18: #############################################################################
    19: #############################################################################
    20: ## 
    21: ## gzmcpc::mode__main_menu: the main menu(/command-tree) system
    22: ##
    23: #############################################################################
    24: ##
    25: ## Copyright 2008-2009 Douglas McClendon <dmc AT filteredperception DOT org>
    26: ##
    27: #############################################################################
    28: #############################################################################
    29: #
    30: */
    31: 
    32: 
    33: 
    34: 
    35: #include <nds.h> 
    36: 
    37: 
    38: #include <nds/bios.h> 
    39: 
    40: #include <stdio.h> 
    41: 
    42: #include <string.h> 
    43: 
    44: #include <dirent.h> 
    45: 
    46: 
    47: #include "cloader/arm9_loader.h" 
    48: 
    49: #include "dmc.h" 
    50: 
    51: #include "debug.h" 
    52: 
    53: #include "graphics.h" 
    54: 
    55: #include "input.h" 
    56: 
    57: #include "main.h" 
    58: 
    59: #include "mcp.h" 
    60: 
    61: #include "modes.h" 
    62: 
    63: #include "mode__intro__main.h" 
    64: 
    65: #include "mode__get_update.h" 
    66: 
    67: #include "mode__main_menu.h" 
    68: 
    69: #include "network.h" 
    70: 
    71: #include "sound.h" 
    72: 
    73: 
    74: #include "sounds.h" 
    75: 
    76: #include "sounds_bin.h" 
    77: 
    78: #include "resources/bitmaps/guitar-zyx.splash.main.h" 
    79: 
    80: #include "resources/bitmaps/dlava.h" 
    81: 
    82: #include "resources/bitmaps/mcpfont.h" 
    83: 
    84: 
    85: 
    86: 
    87: 
    88: gzmcp_menu_node *mm_list = NULL;
    89: gzmcp_menu_node *mm_hist = NULL;
    90: 
    91: 
    92: 
    93: ConsoleFont mcpfont;
    94: 
    95: char pwd[FS_PATH_MAXLEN + 1];
    96: char t_scratch_path[FS_PATH_MAXLEN + 1];
    97: 
    98: int selection_index = 0;
    99: int num_selections = 0;
   100: 
   101: int hilite_offset = 0;
   102: 
   103: int mm_doublemode = 0;
   104: int mm_dm_height_lines = 0;
   105: int mm_dm_scroll_pad = 0;
   106: int mm_dm_repeat_rate = 0;
   107: int mm_dm_width = 0;
   108: int mm_dm_xscl = 0;
   109: int mm_dm_yscl = 0;
   110: 
   111: 
   112: int mm_text_offset_target = 0;
   113: int mm_text_offset_actual = 0;
   114: int mm_hilite_offset_relative = 0;
   115: 
   116: u16 mm_txt_fontcolor = MM_TXT_DEFAULTCOLOR;
   117: u16 mm_hilitecolor = MM_HILITE_DEFAULTCOLOR;
   118: 
   119: int mm_txt_firstfade_done;
   120: int mm_txt_render_hilite;
   121: 
   122: 
   123: 
   124: 
   125: 
   126: void mode__main_menu___init(void) {
   127: 
   128: 
   129:   // initialize fading system
   130:   mcp_fade_init();
   131: 
   132:   // initialize main screen
   133:   videoSetMode(MODE_5_2D);
   134: 
   135:   // map main screen background fourth (128k) region to vram bank A
   136:   vramSetBankA(VRAM_A_MAIN_BG_0x06060000);
   137:   
   138:   // set the secondary/sub screen for text and a background
   139:   videoSetModeSub(MODE_5_2D);
   140: 
   141:   // map sub screen background (only? 1/4?) to vram bank C
   142:   vramSetBankC(VRAM_C_SUB_BG);
   143: 
   144:   // unfaded
   145:   mcp_set_blend(MCP_MAIN_SCREEN,
   146: 		MCP_MAX_BLEND_LEVEL);
   147:   mcp_set_blend(MCP_SUB_SCREEN,
   148: 		MCP_MAX_BLEND_LEVEL);
   149: 
   150:   // fade the mainscreen background to/from black, layer 3
   151:   REG_BLDCNT = BLEND_FADE_BLACK | BLEND_SRC_BG3;
   152:   // fade the lava background to/from black, layer 2
   153:   REG_BLDCNT_SUB = BLEND_FADE_BLACK | BLEND_SRC_BG2;
   154: 
   155:   // visible fonts
   156:   BG_PALETTE[255] = RGB15(0, 0, 0);
   157:   BG_PALETTE_SUB[255] = RGB15(0, 0, 0);
   158: 
   159:   // init subscreen layer/background 3 
   160:   // the mapbase offset of 24 here means 24*16k which means utilizing
   161:   // the 4th of the possible main background memory regions that vram
   162:   // bank A can be mapped to.  I.e. above we mapped to the 4th.  Had
   163:   // we mapped to the 1st, we would have used offset 0.  
   164:   // note: vram bank A is 128k, i.e. 8 * 16k.
   165:   // note: *16k is because of bitmap type, else would be *2k
   166:   //  bg3 = bgInit(3, BgType_Bmp16, BgSize_B16_256x256, 24, 0);
   167:   bg3 = mcp_bg_init(MCP_MAIN_SCREEN,
   168: 		    3, 
   169: 		    MCP_BG_HIDE,
   170: 		    BgType_Bmp16, 
   171: 		    BgSize_B16_256x256, 
   172: 		    24, 
   173: 		    0);
   174:   // its initial priority, lowest (to emphasize lack of other enabled layers)
   175:   // priorities 0..3, 0 highest priority
   176:   bgSetPriority(bg3, 3);
   177: 
   178:   // load main splash screen into screen/background memory (bgs3)
   179:   // note: if I wanted to do this quickly/perfectly/doublebufferred somehow
   180:   //       I'm not sure if I'd need to tweak the bgInitSub offset or pointer
   181:   //       here or some such.  As it is, the fade to black ensures no tear
   182:   //       as this new image gets loaded into display memory.
   183:   decompress(guitar_zyx_splash_mainBitmap, 
   184: 	     (u16*)bgGetGfxPtr(bg3),  
   185: 	     LZ77Vram);
   186:   
   187:   bgShow(bg3);
   188: 
   189:   // note: using offset=4, because 4 will be 64k offset, where 31 above is 62k
   190:   // (thus above using only 2k? seems plausible with tiles for console text chars
   191:   //  bgs2 = bgInitSub(2, BgType_Bmp8, BgSize_B8_256x256, 4, 0);
   192:   bgs2 = mcp_bg_init(MCP_SUB_SCREEN,
   193: 		     2,
   194: 		     MCP_BG_HIDE,
   195: 		     BgType_Bmp8, 
   196: 		     BgSize_B8_256x256, 
   197: 		     4, 
   198: 		     0);
   199:   // its initial priority, lowest (to emphasize lack of other enabled layers)
   200:   // priorities 0..3, 0 highest priority
   201:   bgSetPriority(bgs2, 2);
   202: 
   203:   // as per libnds doc on dma
   204:   DC_FlushRange(MM_BG_BMP, 256*256);
   205:   dmaCopy(MM_BG_BMP, bgGetGfxPtr(bgs2), 256*256);
   206:   DC_FlushRange(MM_BG_PAL, 256*2);
   207:   dmaCopy(MM_BG_PAL, BG_PALETTE_SUB, 256*2);
   208:   
   209:   bgShow(bgs2);
   210: 
   211:   // edunote: consoleInit/mcp_console_init overwrite completely 
   212:   //          the PrintConsole arg
   213: 
   214:   // was 31, 0 for normal non 8bpp font, 
   215:   // 20, 0 for 8bpp font
   216:   // ,1 is to to avoid corruption
   217: 
   218:   // note: no need to load graphics
   219:   mcp_console_init(&bottom_screen,
   220: 		   MCP_SUB_SCREEN,
   221: 		   3,
   222: 		   1,
   223: 		   1,
   224: 		   BgType_ExRotation,
   225: 		   BgSize_ER_256x256,
   226: 		   31, 
   227: 		   1);
   228: 
   229:   // set printf sink
   230:   consoleSelect(&bottom_screen);
   231: 
   232:   // custom 8bpp font
   233:   mcpfont.asciiOffset = 32;
   234:   mcpfont.bpp = 8;
   235:   mcpfont.convertSingleColor = false;
   236:   mcpfont.gfx = (u16*)mcpfontTiles;
   237:   mcpfont.numChars = 95;
   238:   mcpfont.numColors =  mcpfontPalLen / 2;
   239:   mcpfont.pal = (u16*)mcpfontPal;
   240:   consoleSetFont(&bottom_screen, &mcpfont);
   241: 
   242:   // set console background layer to top priority
   243:   bgSetPriority(bottom_screen.bgId, 0);
   244: 
   245:   // this triggers the initial mode fade in
   246:   mm_txt_firstfade_done = 0;
   247:   mm_txt_render_hilite = 0;
   248: 
   249:   // initialize to black text
   250:   BG_PALETTE_SUB[MM_FONT_COLOR_INDEX] = RGB15(0, 0, 0);
   251: 
   252:   // initialize pwd
   253:   // just using sn out of habit
   254:   snprintf(pwd, FS_PATH_MAXLEN, "/");
   255: 
   256:   //
   257:   // initialize the menu
   258:   //
   259:   //  mm_hist_push("WhammyPad!");
   260:   //  mm_create_root();
   261:   mm_create_last();
   262: 
   263: }
   264: 
   265: 
   266: void mode__main_menu___top_renderer(void) {
   267: 
   268:   int t_blend;
   269: 
   270:   // 
   271:   // initialize to unfaded values
   272:   // 
   273:   t_blend = 0;
   274: 
   275: 
   276:   // 
   277:   // do top background fade-in
   278:   // 
   279:   if (mode_ms < MAIN_MENU__TOP_BG_FADE_IN_START_MS) {
   280:     t_blend = 31;
   281:   } else if (mode_ms < (MAIN_MENU__TOP_BG_FADE_IN_START_MS + 
   282: 			MAIN_MENU__TOP_BG_FADE_IN_DURATION_MS)) {
   283:     // main screen bg fade
   284:     t_blend = 31 - ((mode_ms - MAIN_MENU__TOP_BG_FADE_IN_START_MS) * 31 / 
   285: 		     MAIN_MENU__TOP_BG_FADE_IN_DURATION_MS);
   286: 
   287:   } 
   288: 
   289:   //
   290:   // handle fadeout
   291:   //
   292:   if (mode != next_mode) {
   293:     if ((mode_ms - exit_mode_ms) < MAIN_MENU__TOP_BG_FADE_OUT_DURATION_MS) {
   294:       t_blend = ((mode_ms - exit_mode_ms) * 
   295: 		 31 / MAIN_MENU__TOP_BG_FADE_OUT_DURATION_MS);
   296:     } else {
   297:       t_blend = 31;
   298:     }
   299:     // fadeouts should only be fading out
   300:     // i.e. this code may be executing right after an aborted fadein
   301:     //    t_blend = MAX(REG_BLDY, t_blend);
   302:     t_blend = MAX(t_blend, mcp_get_blend(MCP_MAIN_SCREEN));
   303:   } // end handle fadeout
   304: 
   305:   // actually set the blend register value
   306:   mcp_set_blend(MCP_MAIN_SCREEN,
   307: 		t_blend);
   308: 
   309: }
   310: 
   311: 
   312: void mode__main_menu___bot_renderer(void) {
   313: 
   314:   int t_blend;
   315: 
   316:   if (mode_ms < MAIN_MENU__BOT_TXT_FADE_IN_START_MS) {
   317:   } else {
   318:     if (!mm_txt_firstfade_done) {
   319:       mm_txt_render_hilite = 1;
   320:       BG_PALETTE_SUB[MM_FONT_COLOR_INDEX] = RGB15(0, 0, 0);
   321:       mcp_fade_set_level(MCP_SUB_SCREEN, 3, 0);
   322:       mcp_bg_show(MCP_SUB_SCREEN, 3);
   323:       mcp_fade_start(MCP_SUB_SCREEN, 
   324: 		     3, 
   325: 		     1,
   326: 		     MAIN_MENU__BOT_TXT_FADE_IN_DURATION_MS,
   327: 		     idle_rate,
   328: 		     NULL);
   329:       mm_txt_firstfade_done = 1;
   330:     }
   331:   }
   332: 
   333:   // clear the console text
   334:   consoleClear();
   335:   // and hilite
   336:   render_hilite(bgs2, 0, 0, 0, 0, 0, 1);
   337: 
   338:   // set console variable parameters
   339:   // note: -128, -128 is doubling
   340:   bgSetRotateScale(bottom_screen.bgId, 
   341: 		   0,
   342: 		   intToFixed(1,8) + mm_dm_xscl,
   343: 		   intToFixed(1,8) + mm_dm_yscl);
   344: 
   345:   // TODO: verbosely document this ugly crap
   346:   // the -4 is down half an 8 pixel char (that has scaled to 16pix)
   347:   // the other term is another optional same amount
   348:   bgScroll(bottom_screen.bgId, 
   349:   	   0, 
   350: 	   -4 - (mm_text_offset_target & 15));
   351:   bgUpdate();
   352: 
   353: 
   354:   // 
   355:   // initialize to unfaded values
   356:   // 
   357:   t_blend = 0;
   358: 
   359:   // 
   360:   // do bg fade-in
   361:   // 
   362:   if (mode_ms < MAIN_MENU__BOT_BG_FADE_IN_START_MS) {
   363:     // pre fade-in
   364:     t_blend = 31;
   365:   } else if (mode_ms < (MAIN_MENU__BOT_BG_FADE_IN_START_MS + 
   366: 			MAIN_MENU__BOT_BG_FADE_IN_DURATION_MS)) {
   367:     // main screen bg fade-in
   368:     t_blend = 31 - ((mode_ms - MAIN_MENU__BOT_BG_FADE_IN_START_MS) * 31 / 
   369: 		    MAIN_MENU__BOT_BG_FADE_IN_DURATION_MS);
   370: 
   371:   } 
   372: 
   373: 
   374:   //
   375:   // do fadeout, possibly overriding above
   376:   //
   377:   if (mode != next_mode) {
   378:     // start a fade-out if a fade isn't in progress, or is fading in,
   379:     if (!mcp_fade_get_enabled(MCP_SUB_SCREEN, 3) ||
   380: 	mcp_fade_get_fwd(MCP_SUB_SCREEN, 3)) {
   381:       mcp_fade_start(MCP_SUB_SCREEN, 
   382: 		     3, 
   383: 		     0,
   384: 		     MAIN_MENU__BOT_TXT_FADE_OUT_DURATION_MS,
   385: 		     idle_rate,
   386: 		     NULL);
   387:     }
   388: 
   389:     // fade-out
   390:     if ((mode_ms - exit_mode_ms) < MAIN_MENU__BOT_BG_FADE_OUT_DURATION_MS) {
   391:       t_blend = ((mode_ms - exit_mode_ms) * 
   392: 		      31 / MAIN_MENU__BOT_BG_FADE_OUT_DURATION_MS);
   393:     } else {
   394:       t_blend = 31;
   395:     }
   396:     // only fadeout
   397:     t_blend = MAX(t_blend, mcp_get_blend(MCP_SUB_SCREEN));
   398: 
   399:   }
   400: 
   401:   mcp_set_blend(MCP_SUB_SCREEN,
   402: 		t_blend);
   403: 
   404:   /*
   405:   // clear the console text
   406:   //  consoleClear();
   407:   // show the entire font
   408:   int i, x, y;
   409:   // 32 + 95, now 60 + 9
   410:   for (i = 32; i < (32 + 95); i++) {
   411:     y = (i - 32) / 12;
   412:     x = (i - 32) % 12;
   413:     printf("\x1b[%02d;%02dH%c", 
   414: 	   y, x, i);
   415:   }
   416:   */
   417: 
   418:   // print/render the main menu labels
   419:   mm_print();
   420: 
   421:   if (mm_txt_render_hilite) {
   422:     render_hilite(bgs2,
   423: 		  RGB15(RGB15_TO_R5(mm_hilitecolor) * mcp_fade_get_level(MCP_SUB_SCREEN, 3) / MCP_FADE_MAXLEVEL,
   424: 			RGB15_TO_G5(mm_hilitecolor) * mcp_fade_get_level(MCP_SUB_SCREEN, 3) / MCP_FADE_MAXLEVEL,
   425: 			RGB15_TO_B5(mm_hilitecolor) * mcp_fade_get_level(MCP_SUB_SCREEN, 3) / MCP_FADE_MAXLEVEL),
   426: 		  hilite_offset,
   427: 		  SCREEN_WIDTH * mm_dm_width / 32,
   428: 		  SCREEN_HEIGHT * 3 / 2 / mm_dm_height_lines,
   429: 		  SCREEN_HEIGHT / mm_dm_height_lines / 2,
   430: 		  0);
   431:   }
   432: 
   433:   // set the fade by changing the font's palette entry
   434:   BG_PALETTE_SUB[MM_FONT_COLOR_INDEX] = 
   435:     RGB15(RGB15_TO_R5(mm_txt_fontcolor) * mcp_fade_get_level(MCP_SUB_SCREEN, 3) / MCP_FADE_MAXLEVEL,
   436: 	  RGB15_TO_G5(mm_txt_fontcolor) * mcp_fade_get_level(MCP_SUB_SCREEN, 3) / MCP_FADE_MAXLEVEL,
   437: 	  RGB15_TO_B5(mm_txt_fontcolor) * mcp_fade_get_level(MCP_SUB_SCREEN, 3) / MCP_FADE_MAXLEVEL);
   438: 
   439: }
   440: 
   441: 
   442: void mode__main_menu___input_handler(void) {
   443: 
   444:   //
   445:   // prepare input, i.e. transmogrify special held situations 
   446:   // into downkeys
   447:   //
   448: 
   449:   // only bother if sunset has expired
   450:   if (time_val_compare(num_ticks, heldover_sunset) < 0) {
   451:     
   452:     if ((heldkeys & KEY_L) && (heldkeys & KEY_R)) {
   453:       //
   454:       // held(L)+held(R)+something
   455:       //
   456:       
   457:       if (heldkeys & KEY_LEFT) {
   458: 	downkeys |= KEY_LEFT;
   459:       }
   460:       
   461:       if (heldkeys & KEY_RIGHT) {
   462: 	downkeys |= KEY_RIGHT;
   463:       }
   464:       
   465:       if (heldkeys & KEY_UP) {
   466: 	downkeys |= KEY_UP;
   467:       }
   468:       
   469:       if (heldkeys & KEY_DOWN) {
   470: 	downkeys |= KEY_DOWN;
   471:       }
   472:       
   473:     } else if (heldkeys & KEY_L) {
   474:       
   475:       if (heldkeys & KEY_A) {
   476: 	downkeys |= KEY_A;
   477:       }
   478:       
   479:       if (heldkeys & KEY_B) {
   480: 	downkeys |= KEY_B;
   481:       }
   482:       
   483:       if (heldkeys & KEY_X) {
   484: 	downkeys |= KEY_X;
   485:       }
   486:       
   487:       if (heldkeys & KEY_Y) {
   488: 	downkeys |= KEY_Y;
   489:       }
   490:       
   491:       if (heldkeys & KEY_LEFT) {
   492: 	downkeys |= KEY_LEFT;
   493:       }
   494:       
   495:       if (heldkeys & KEY_RIGHT) {
   496: 	downkeys |= KEY_RIGHT;
   497:       }
   498:       
   499:       if (heldkeys & KEY_UP) {
   500: 	downkeys |= KEY_UP;
   501:       }
   502:       
   503:       if (heldkeys & KEY_DOWN) {
   504: 	downkeys |= KEY_DOWN;
   505:       }
   506:       
   507:     } else if (heldkeys & KEY_R) {
   508:       
   509:       if (heldkeys & KEY_A) {
   510: 	downkeys |= KEY_A;
   511:       }
   512:       
   513:       if (heldkeys & KEY_B) {
   514: 	downkeys |= KEY_B;
   515:       }
   516:       
   517:       if (heldkeys & KEY_X) {
   518: 	downkeys |= KEY_X;
   519:       }
   520:       
   521:       if (heldkeys & KEY_Y) {
   522: 	downkeys |= KEY_Y;
   523:       }
   524: 
   525:       if (heldkeys & KEY_LEFT) {
   526: 	downkeys |= KEY_LEFT;
   527:       }
   528:       
   529:       if (heldkeys & KEY_RIGHT) {
   530: 	downkeys |= KEY_RIGHT;
   531:       }
   532:       
   533:       if (heldkeys & KEY_UP) {
   534: 	downkeys |= KEY_UP;
   535:       }
   536:       
   537:       if (heldkeys & KEY_DOWN) {
   538: 	downkeys |= KEY_DOWN;
   539:       }
   540:       
   541:     } else  {
   542:       
   543:       if (heldkeys & KEY_LEFT) {
   544: 	downkeys |= KEY_LEFT;
   545:       }
   546:       
   547:       if (heldkeys & KEY_RIGHT) {
   548: 	downkeys |= KEY_RIGHT;
   549:       }
   550:       
   551:       if (heldkeys & KEY_UP) {
   552: 	downkeys |= KEY_UP;
   553:       }
   554:       
   555:       if (heldkeys & KEY_DOWN) {
   556: 	downkeys |= KEY_DOWN;
   557:       }
   558:       
   559:     }
   560:     
   561:   } // end if heldover_sunset expired
   562: 
   563:   //
   564:   // handle input
   565:   //
   566:   if ((heldkeys & KEY_L) && (heldkeys & KEY_R)) {
   567:     //
   568:     // held(L)+held(R)+something
   569:     //
   570: 
   571:   } else if (heldkeys & KEY_L) {
   572:     //
   573:     // held(L)+something
   574:     //
   575: 
   576:     if (downkeys & KEY_X) {
   577:     }
   578: 
   579:     if (downkeys & KEY_Y) {
   580:     }
   581: 
   582:     if (downkeys & KEY_A) {
   583:     }
   584: 
   585:     if (downkeys & KEY_B) {
   586:     }
   587: 
   588:     if (downkeys & KEY_UP) {
   589:     }
   590: 
   591:     if (downkeys & KEY_DOWN) {
   592:     }
   593: 
   594:     if (downkeys & KEY_START) {
   595:     }
   596: 
   597:   } else if (heldkeys & KEY_R) {
   598:     //
   599:     // held(R)+something
   600:     //
   601: 
   602:     if (downkeys & KEY_UP) {
   603:     }
   604: 
   605:     if (downkeys & KEY_DOWN) {
   606:     }
   607: 
   608:     if (downkeys & KEY_LEFT) {
   609:     }
   610: 
   611:     if (downkeys & KEY_RIGHT) {
   612:     }
   613: 
   614:   } else {
   615:     //
   616:     // no interesting modifer keys held
   617:     //
   618: 
   619:     if (downkeys & KEY_START) {
   620:       if (last_mode == MODE_NUM_MODES) {
   621: 	system_xmode_new(MODE_MAIN_MENU);
   622:       } else {
   623: 	system_xmode_new(last_mode);
   624:       }
   625:     }
   626: 
   627:     if (downkeys & KEY_SELECT) {
   628:     }
   629: 
   630:     if (downkeys & KEY_UP) {
   631:       mm_set_selection_by_index(selection_index - 1);
   632:       heldover_sunset = time_val_add_ms(num_ticks, mm_dm_repeat_rate);
   633:     }
   634: 
   635:     if (downkeys & KEY_DOWN) {
   636:       mm_set_selection_by_index(selection_index + 1);
   637:       heldover_sunset = time_val_add_ms(num_ticks, mm_dm_repeat_rate);
   638:     }
   639: 
   640:     if (downkeys & KEY_LEFT) {
   641:     }
   642: 
   643:     if (downkeys & KEY_RIGHT) {
   644:     }
   645: 
   646:     if (downkeys & KEY_X) {
   647:     }
   648: 
   649:     if (downkeys & KEY_Y) {
   650:     }
   651: 
   652:     if (downkeys & KEY_A) {
   653:       // run selected select_cb
   654:       if (mm_get_node_by_index(selection_index) != NULL) {
   655: 	// wow this is pretty gross
   656: 	// don't add *(go back)* to the stack, as our selection of it
   657: 	// always means we'd prefer whatever is already on the 
   658: 	// top of the stack.
   659: 	if (strstr(mm_get_node_by_index(selection_index)->label,
   660: 		   "(go back)") == NULL) {
   661: 	  mm_hist_push(mm_get_node_by_index(selection_index)->label);
   662: 	}
   663: 	if (mm_get_node_by_index(selection_index)->select_cb != NULL) {
   664: 	  mcp_snd_click();
   665: 	  (*((mm_get_node_by_index(selection_index))->select_cb))();
   666: 	}
   667:       }
   668:     }
   669: 
   670:     if (downkeys & KEY_B) {
   671:     }
   672: 
   673:   }
   674: 
   675: 
   676:   // touchpad handling is independent of modifiers (at the moment)
   677:   if ((downkeys & KEY_TOUCH) || (heldkeys & KEY_TOUCH)) {
   678: 
   679:   }
   680: 
   681: }
   682: 
   683: void mode__main_menu___idle(void) {
   684:   //
   685:   // handle fadeout
   686:   //
   687:   if (mode != next_mode) {
   688:     if (((mode_ms - exit_mode_ms) > MAIN_MENU__TOP_BG_FADE_OUT_DURATION_MS) &&
   689: 	((mode_ms - exit_mode_ms) > MAIN_MENU__BOT_BG_FADE_OUT_DURATION_MS) &&
   690: 	((mode_ms - exit_mode_ms) > MAIN_MENU__BOT_TXT_FADE_OUT_DURATION_MS)) {
   691:       system_xmode_real();
   692:     }
   693:   }
   694: }
   695: 
   696: 
   697: void mode__main_menu___exit(void) {
   698: 
   699:   mm_free();
   700: 
   701: }
   702: 
   703: 
   704: void mm_set_doublemode(int mode) {
   705: 
   706:   mm_doublemode = mode;
   707:   if (mode) {
   708:     mm_dm_width = 24;
   709:     mm_dm_height_lines = 12;
   710:     mm_dm_scroll_pad = 4;
   711:     mm_dm_repeat_rate = 200;
   712:     mm_dm_xscl = -128;
   713:     mm_dm_yscl = -128;
   714:   } else {
   715:     mm_dm_width = 30;
   716:     mm_dm_height_lines = 24;
   717:     mm_dm_scroll_pad = 8;
   718:     mm_dm_repeat_rate = 125;
   719:     mm_dm_xscl = 0;
   720:     mm_dm_yscl = 0;
   721:   }
   722: 
   723:   // XXX if this works, make this a function shared with other copy
   724:   // clear the console text
   725:   consoleClear();
   726:   // and the hilite
   727:   render_hilite(bgs2, 0, 0, 0, 0, 0, 1);
   728: 
   729:   // set console variable parameters
   730:   // note: -128, -128 is doubling
   731:   bgSetRotateScale(bottom_screen.bgId, 
   732: 		   0,
   733: 		   intToFixed(1,8) + mm_dm_xscl,
   734: 		   intToFixed(1,8) + mm_dm_yscl);
   735: 
   736:   // TODO: verbosely document this ugly crap
   737:   // the -4 is down half an 8 pixel char (that has scaled to 16pix)
   738:   // the other term is another optional same amount
   739:   bgScroll(bottom_screen.bgId, 
   740:   	   0, 
   741: 	   -4 - (mm_text_offset_target & 15));
   742:   bgUpdate();
   743: 
   744: 
   745: }
   746: 
   747: 
   748: void render_hilite(int bgid,
   749: 		   int color,
   750: 		   int y_offset,
   751: 		   int width,
   752: 		   int height,
   753: 		   int roundness,
   754: 		   int unrender) {
   755: 
   756:   // ooh, ahh, static local variables put to good use
   757:   // edunote: in case I forget, static local variables in functions, which
   758:   //          clearly I rarely use, are roughly equivalent to private globals,
   759:   //          i.e. initialized once, and retaining value across multiple 
   760:   //          invocations of the function
   761:   static int y_offset_prev = 0;
   762:   // note: the dmaCopy doesn't behave as I'd expect with a length of 0
   763:   static int height_prev = 1;
   764:   static int dirty = 0;
   765: 
   766:   int i;
   767:   int rounded_width;
   768: 
   769:   
   770:   // note: bgGetGfxPtr returns u16*, but this is 8bpp data
   771: 
   772:   // edunote: brief net search suggests there is no advantage to using
   773:   //          bitshifts instead of *2^n, as compiler will optimize while
   774:   //          the code will remain more readable.
   775: 
   776:   if (unrender || dirty) {
   777:     // first, restore possible background damage from previous invocation
   778:     DC_FlushRange(((const void*)MM_BG_BMP) + (y_offset_prev * SCREEN_WIDTH), 
   779: 		  height_prev * SCREEN_WIDTH);
   780:     dmaCopy(((const void*)MM_BG_BMP) + (y_offset_prev * SCREEN_WIDTH), 
   781: 	    (void *)(bgGetGfxPtr(bgid)) + (y_offset_prev * SCREEN_WIDTH), 
   782: 	    height_prev * SCREEN_WIDTH);
   783:     dirty = 0;
   784:     // unrender means that is all that should be done
   785:     if (unrender) return;
   786:   }
   787: 
   788:   // set the hilite color 
   789:   BG_PALETTE_SUB[MM_HILITE_COLOR_INDEX] = color;
   790: 
   791:   // render/copy the top rounded portion of the highlite
   792:   for (i = y_offset ; i < (y_offset + roundness) ; i++) {
   793:     rounded_width = width - ((roundness - (i - y_offset)) * 2);
   794:     dmaFillHalfWords((MM_HILITE_COLOR_INDEX * SCREEN_WIDTH) + MM_HILITE_COLOR_INDEX, 
   795: 		     ((void *)bgGetGfxPtr(bgid)) + 
   796: 		     (i * SCREEN_WIDTH) + ((SCREEN_WIDTH - rounded_width) / 2),
   797: 		     rounded_width);
   798:   }
   799: 
   800:   // render/copy the middle unrounded portion of the highlite
   801:   for (i = (y_offset + roundness) ; 
   802:        i < (y_offset + height - roundness) ; 
   803:        i++) {
   804:     dmaFillHalfWords((MM_HILITE_COLOR_INDEX * SCREEN_WIDTH) + MM_HILITE_COLOR_INDEX, 
   805: 		     ((void *)bgGetGfxPtr(bgid)) + 
   806: 		     (i * SCREEN_WIDTH) + ((SCREEN_WIDTH - width) / 2),
   807: 		     width);
   808:   }
   809: 
   810:   // render/copy the bottom rounded portion of the highlite
   811:   for (i = (y_offset + height - roundness) ; 
   812:        i < (y_offset + height) ; 
   813:        i++) {
   814:     rounded_width = width - ((roundness - ((y_offset + height) - i)) * 2);
   815:     dmaFillHalfWords((MM_HILITE_COLOR_INDEX * SCREEN_WIDTH) + MM_HILITE_COLOR_INDEX, 
   816: 		     ((void *)bgGetGfxPtr(bgid)) + 
   817: 		     (i * SCREEN_WIDTH) + ((SCREEN_WIDTH - rounded_width) / 2),
   818: 		     rounded_width);
   819:   }
   820: 
   821:   // save the information about the part of the screen we just hilited,
   822:   // so that it can be restored next time.
   823:   y_offset_prev = y_offset;
   824:   height_prev = height;
   825: 
   826: }
   827: 
   828: void mm_add_node(const char *label, 
   829: 		 int selectable,
   830: 		 void(*select_cb)(void)) {
   831: 
   832:   gzmcp_menu_node **p = &mm_list;
   833: 
   834: 
   835:   // get a pointer to final NULL pointer in the llist
   836:   while (*p != NULL) p = &((*p)->next);
   837: 
   838:   // allocate memory for a new node
   839:   *p = malloc(sizeof(gzmcp_menu_node));
   840:   if (*p == NULL) {
   841:     die();
   842:   }
   843: 
   844:   strncpy((*p)->label, label, MENU_LABEL_MAXLEN);
   845:   (*p)->select_cb = select_cb;
   846:   (*p)->selectable = selectable;
   847:   (*p)->next = NULL;
   848: 
   849:   num_selections++;
   850: }
   851: 
   852: 
   853: gzmcp_menu_node *mm_get_node_by_index(int index) {
   854: 
   855:   int i;
   856:   gzmcp_menu_node *result;
   857: 
   858:   result = mm_list;
   859: 
   860:   for (i = 1; i < index; i++) {
   861:     if (result->next == NULL) {
   862:       return NULL;
   863:     } else {
   864:       result = result->next;
   865:     }
   866:   }
   867: 
   868:   return result;
   869: 
   870: }
   871: 
   872: 
   873: int mm_get_index_by_label(const char *label) {
   874: 
   875:   int i;
   876:   int done = 0;
   877:   gzmcp_menu_node *node;
   878: 
   879:   node = mm_list;
   880:   i=1;
   881: 
   882:   while (!done) {
   883:     if (strncmp(node->label, label, MENU_LABEL_MAXLEN) == 0) return i;
   884:     i++;
   885:     node = node->next;
   886:     if (node == NULL) done = 1;
   887:   }
   888: 
   889:   return -1;
   890: }
   891: 
   892: 
   893: void mm_alphasort(gzmcp_menu_node *head, 
   894: 		  int from_index, 
   895: 		  int to_index) {
   896: 
   897:   gzmcp_menu_node *t_mm_node_lower_parent;
   898:   gzmcp_menu_node *t_mm_node_lower;
   899:   gzmcp_menu_node *t_mm_node_upper_parent;
   900:   gzmcp_menu_node *t_mm_node_upper;
   901:   gzmcp_menu_node *t_mm_node_ptr;
   902:   int t_cnt;
   903:   int i, j, k;
   904: 
   905: 
   906:   // current logic cannot change the first entry, which
   907:   // by convention is '(go back)' anyway
   908:   if (from_index <= 1) return;
   909: 
   910:   // note: for now, ignore selection, assuming that
   911:   //       alphasort will be called after menu creation,
   912:   //       but before selection initialization
   913:   
   914:   t_cnt = from_index;
   915: 
   916:   for (i = from_index ; i <= to_index ; i++) {
   917: 
   918:     for (j = from_index ; j < to_index  ; j++) {
   919: 
   920:       t_mm_node_lower_parent = mm_get_node_by_index(j - 1);
   921:       t_mm_node_lower = mm_get_node_by_index(j);
   922:       // these two are mainly to silence - possible uninitialized use warnings
   923:       t_mm_node_upper_parent = mm_get_node_by_index(j);
   924:       t_mm_node_upper = mm_get_node_by_index(j + 1);
   925: 
   926:       if (!(t_mm_node_lower->selectable)) continue;
   927:       if (t_mm_node_lower->next == NULL) continue;
   928: 
   929:       for (k = (j + 1); k <= to_index ; k++) {
   930: 	t_mm_node_upper = mm_get_node_by_index(k);
   931: 	if (t_mm_node_upper->selectable) break;
   932:       }
   933: 
   934:       if (t_mm_node_upper->selectable) {
   935: 	t_mm_node_upper_parent = mm_get_node_by_index(k - 1);
   936: 	// compare the node's labels
   937: 	if (strncmp(t_mm_node_lower->label, 
   938: 		    t_mm_node_upper->label, 
   939: 		    MENU_LABEL_MAXLEN) > 0) {
   940: 	  // swap
   941: 	  t_mm_node_ptr = t_mm_node_lower->next;
   942: 	  t_mm_node_lower->next = t_mm_node_upper->next;
   943: 	  t_mm_node_upper->next = t_mm_node_ptr;
   944: 
   945: 	  t_mm_node_ptr = t_mm_node_lower_parent->next;
   946: 	  t_mm_node_lower_parent->next = t_mm_node_upper_parent->next;
   947: 	  t_mm_node_upper_parent->next = t_mm_node_ptr;
   948: 
   949: 	} // end if need to actually do a bubble swap
   950: 
   951:       } // end if we have a selectable node to possibly bubble swap
   952: 
   953:     } // end of a single pass of bubbling iteration
   954: 
   955:   } // end outtermost bubble iteration
   956: 
   957: }
   958: 
   959: 
   960: void mm_free(void) {
   961: 
   962:   gzmcp_menu_node *t_mm_node;
   963: 
   964: 
   965:   // free the list entry by entry
   966:   while (mm_list != NULL) {
   967:     t_mm_node = mm_list->next;
   968:     free(mm_list);
   969:     mm_list = t_mm_node;
   970:     num_selections--;
   971:   }
   972: 
   973:   // initialize offsets
   974:   mm_text_offset_target = (0 << 4) | 0;
   975:   mm_text_offset_actual = (0 << 4) | 0;
   976:   mm_hilite_offset_relative = (0 << 4) | 0;
   977: 
   978:   // and selection_index
   979:   selection_index = 0;
   980: 
   981: }
   982: 
   983: 
   984: void mm_set_selection_by_index(int index) {
   985:   
   986:   // for finding selectable index
   987:   int found = 0;
   988:   // for recording previous selected index
   989:   int prev_index;
   990:   // stack variables could be removed, but code would be less readable
   991:   int offset_lines_fraction;
   992:   int offset_lines;
   993:   
   994: 
   995:   // record prior selection for rendering offset calculation below
   996:   prev_index = selection_index;
   997: 
   998:   // do nothing if there is nothing to do
   999:   if (num_selections < 1) return;
  1000: 
  1001:   // can't go out of bounds
  1002:   // assert candidate
  1003:   if ((index < 1) || (index > num_selections)) return;
  1004: 
  1005: #define PRV_OFFSET (mm_text_offset_target >> 4) 
  1006: #define VIS_ENTRIES_MAX (mm_dm_height_lines - 1) 
  1007: #define CUR_INNER_REGION_MAX PRV_OFFSET + VIS_ENTRIES_MAX - mm_dm_scroll_pad 
  1008: #define CUR_INNER_REGION_MIN PRV_OFFSET + mm_dm_scroll_pad + 1 
  1009: 
  1010:   //
  1011:   // set new index (skipping unselectable entries)
  1012:   //
  1013:   selection_index = index;
  1014:   if (selection_index < prev_index) {
  1015:     while (!found) {
  1016:       if (mm_get_node_by_index(selection_index)->selectable) {
  1017: 	found = 1;
  1018:       } else if (selection_index == 1) {
  1019: 	  // give up, no selectable entry found, use previous
  1020: 	  selection_index = prev_index;
  1021: 	  found = 1;
  1022:       } else {
  1023: 	// try the next entry above
  1024: 	selection_index--;
  1025:       }
  1026:     } // end while (!found)
  1027:   } else if (selection_index > prev_index) {
  1028:     while (!found) {
  1029:       if (mm_get_node_by_index(selection_index)->selectable) {
  1030: 	found = 1;
  1031:       } else if (selection_index == num_selections) {
  1032: 	  // give up, no selectable entry found, use previous
  1033: 	  selection_index = prev_index;
  1034: 	  found = 1;
  1035:       } else {
  1036: 	// try the next entry above
  1037: 	selection_index++;
  1038:       }
  1039:     } // end while (!found)
  1040:   } else {
  1041:     // no change in selection
  1042:     return;
  1043:   }
  1044: 
  1045:   //
  1046:   // handle many possible scenarios, the union of which is all possibilities
  1047:   //
  1048: 
  1049: 
  1050:   if (num_selections < VIS_ENTRIES_MAX) {
  1051:     // if the number of selections need not be scrolled, 
  1052:     // adjust so that the offset vertically centers the entries
  1053:     offset_lines_fraction = (num_selections % 2) * 8;
  1054:     // this logic needs to be rewritten and better understood
  1055:     offset_lines = ((VIS_ENTRIES_MAX - num_selections - 1) / -2);
  1056:   } else if ((selection_index <= CUR_INNER_REGION_MAX) &&
  1057: 	     (selection_index >= CUR_INNER_REGION_MIN)) {
  1058:     // if the new index is in the existing visible region,
  1059:     // no adjustment is necessary
  1060:     offset_lines_fraction = mm_text_offset_target & 15;
  1061:     offset_lines = mm_text_offset_target >> 4;
  1062:   } else if (selection_index <= mm_dm_scroll_pad) {
  1063:     // handle case where scroll should be at the top
  1064:     offset_lines_fraction = 0;
  1065:     offset_lines = 0;
  1066:   } else if (selection_index >= (num_selections - mm_dm_scroll_pad)) {
  1067:     // handle case where scroll should be at the bottom
  1068:     offset_lines_fraction = 0;
  1069:     offset_lines = num_selections - VIS_ENTRIES_MAX;
  1070:   } else if (selection_index > prev_index) {
  1071:     // scroll down
  1072:     offset_lines_fraction = 0;
  1073:     offset_lines = selection_index -VIS_ENTRIES_MAX + mm_dm_scroll_pad;
  1074:   } else if (selection_index < prev_index) {
  1075:     // scroll up
  1076:     offset_lines_fraction = 0;
  1077:     offset_lines = selection_index - mm_dm_scroll_pad - 1;
  1078:   } else {
  1079:     // should be an assertion 
  1080:     offset_lines_fraction = 0;
  1081:     offset_lines = -42;
  1082:   }
  1083: 
  1084:   // set the new compact mm_text_offset_target from calculated values
  1085:   mm_text_offset_target = (offset_lines << 4) | offset_lines_fraction;
  1086: 
  1087:   //  int mm_text_offset_actual = (0 << 4) | 0;
  1088:   //  int mm_hilite_offset_relative = (0 << 4) | 0;
  1089: 
  1090:   hilite_offset = ((selection_index - offset_lines - 1) * 
  1091: 		   ((SCREEN_HEIGHT) / mm_dm_height_lines)) + (SCREEN_HEIGHT / (mm_dm_height_lines * 4));
  1092:   hilite_offset += (offset_lines_fraction * ((SCREEN_HEIGHT / mm_dm_height_lines) / 8));
  1093: 
  1094:   // user audio feedback / click (quarter volume)
  1095:   if (prev_index && (selection_index != prev_index)) mcp_snd_click();
  1096: 
  1097: }
  1098: 
  1099: 
  1100: void mm_set_selection_by_label(const char *label) {
  1101: 
  1102:   // TODO: catch error/-1 from get_index_by_label
  1103:   if (mm_get_index_by_label(label) == -1) {
  1104:     mm_set_selection_by_index(1);
  1105:   } else {
  1106:     mm_set_selection_by_index(mm_get_index_by_label(label));
  1107:   }
  1108:     
  1109: }
  1110: 
  1111: 
  1112: void mm_set_selection_by_hist(void) {
  1113: 
  1114:   while (mm_get_index_by_label(mm_hist_peek()) == -1) {
  1115:     mm_hist_pop();
  1116:     if (mm_hist == NULL) break;
  1117:   }
  1118: 
  1119:   if (mm_hist == NULL) {
  1120:     mm_set_selection_by_index(1);
  1121:   } else {
  1122:     selection_index = 0;
  1123:     mm_set_selection_by_index(mm_get_index_by_label(mm_hist_peek()));
  1124:     mm_hist_pop();
  1125:   }
  1126:     
  1127: }
  1128: 
  1129: 
  1130: void mm_print(void) {
  1131: 
  1132:   gzmcp_menu_node *t_mm_node;
  1133:   int skip;
  1134:   int line_number = 0;
  1135: 
  1136: 
  1137:   t_mm_node = mm_list;
  1138:   skip = mm_text_offset_target >> 4;
  1139: 
  1140:   // first skip however lines mm_text_offset_target tells us to
  1141:   // note: this is for negative skip, positive skip is handled below
  1142:   while (skip < 0) {
  1143:     line_number++;
  1144:     skip++;
  1145:   }
  1146: 
  1147:   // iterate over the linked list of entry nodes, possibly rendering
  1148:   while (t_mm_node != NULL) {
  1149:     if (skip > 0) {
  1150:       skip--;
  1151:     } else {
  1152:       if (line_number < (mm_dm_height_lines - 1)) {
  1153: 	// only print nonblank lines as not to overwrite a wrapped line
  1154: 	if (strncmp(t_mm_node->label, "", 1) != 0) {
  1155: 	  printf("\x1b[%02d;0H   %s", 
  1156: 		 line_number,
  1157: 		 t_mm_node->label);
  1158: 	}
  1159: 	line_number++;
  1160:       }
  1161:     }
  1162:     t_mm_node = t_mm_node->next;
  1163:   }
  1164: 
  1165: }
  1166: 
  1167: 
  1168: void mm_hist_push(const char *label) {
  1169: 
  1170:   gzmcp_menu_node *new_node;
  1171: 
  1172:   // allocate memory for a new node
  1173:   new_node = malloc(sizeof(gzmcp_menu_node));
  1174:   if (new_node == NULL) {
  1175:     die();
  1176:   }
  1177: 
  1178:   strncpy(new_node->label,
  1179: 	  label,
  1180: 	  MENU_LABEL_MAXLEN);
  1181:   // not used
  1182:   new_node->select_cb = NULL;
  1183:   // not used
  1184:   new_node->selectable = 0;
  1185:   new_node->next = mm_hist;
  1186: 
  1187:   mm_hist = new_node;
  1188: 
  1189: }
  1190: 
  1191: 
  1192: char *mm_hist_peek(void) {
  1193:   if (mm_hist == NULL) return NULL;
  1194:   return (mm_hist->label);
  1195: }
  1196: 
  1197: 
  1198: void mm_hist_pop(void) {
  1199: 
  1200:   gzmcp_menu_node *t_mm_node;
  1201: 
  1202:   if (mm_hist == NULL) return;
  1203: 
  1204:   t_mm_node = mm_hist->next;
  1205:   free(mm_hist);
  1206:   mm_hist = t_mm_node;
  1207:   
  1208: }
  1209: 
  1210: void mm_go_whammypad(void) {
  1211: 
  1212:   system_xmode_new(MODE_TPW__JAM);
  1213: 
  1214: }
  1215: 
  1216: 
  1217: void mm_go_settings(void) {
  1218: 
  1219:   // XXX: unimplemented
  1220: 
  1221: }
  1222: 
  1223: 
  1224: void mm_go_files(void) {
  1225: 
  1226:   mcp_snd_click();
  1227:   mm_hist_push("(go back)");
  1228:   mcp_fade_start(MCP_SUB_SCREEN, 
  1229: 		 3, 
  1230: 		 0,
  1231: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1232: 		 idle_rate,
  1233: 		 &mm_create_files);
  1234: 
  1235: }
  1236: 
  1237: 
  1238: void mm_go_usermanual(void) {
  1239: 
  1240:   // XXX: unimplemented
  1241: 
  1242: }
  1243: 
  1244: 
  1245: void mm_go_misc(void) {
  1246: 
  1247:   mcp_snd_click();
  1248:   mm_hist_push("(go back)");
  1249:   mcp_fade_start(MCP_SUB_SCREEN, 
  1250: 		 3, 
  1251: 		 0,
  1252: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1253: 		 idle_rate,
  1254: 		 &mm_create_misc);
  1255: 
  1256: }
  1257: 
  1258: 
  1259: void mm_go_shutdown(void) {
  1260: 
  1261:   // this isn't enough, netsearching seems to suggest it isn't possible,
  1262:   // or at least widely known how to accomplish this.
  1263: 
  1264:   // this gets to sleep mode, but thats it
  1265:   netmode_go_offline();
  1266:   powerOff(POWER_ALL | PM_SYSTEM_PWR);
  1267:   swiSetHaltCR(0x80);
  1268: 
  1269: }
  1270: 
  1271: 
  1272: void mm_go_get_update(void) {
  1273: 
  1274:   system_xmode_new(MODE_GET_UPDATE);
  1275: 
  1276: }
  1277: 
  1278: 
  1279: void mm_go_get_and_burn_update(void) {
  1280: 
  1281:   overwrite_self_with_update = 1;
  1282:   system_xmode_new(MODE_GET_UPDATE);
  1283: 
  1284: }
  1285: 
  1286: 
  1287: void mm_go_ssid_input(void) {
  1288: 
  1289:   mm_hist->select_cb = &mm_create_misc;
  1290:   system_xmode_new(MODE_SSID__INPUT);
  1291: 
  1292: }
  1293: 
  1294: 
  1295: void mm_go_show_credits(void) {
  1296: 
  1297:   mm_hist->select_cb = &mm_create_misc;
  1298:   system_xmode_new(MODE_INTRO__CREDITS);
  1299: 
  1300: }
  1301: 
  1302: 
  1303: void mm_go_show_ssid_list(void) {
  1304: 
  1305:   Wifi_AccessPoint ap;
  1306:   char ssidbuf[MENU_LABEL_MAXLEN + 1];
  1307:   int i, nn;
  1308: 
  1309:   mm_free();
  1310: 
  1311:   mm_set_doublemode(0);
  1312:   
  1313:   mm_add_node("/========================\\", 0, NULL);
  1314:   mm_add_node("|                        |", 0, NULL);
  1315:   mm_add_node("| Scanned WiFi Networks  |", 0, NULL);
  1316:   mm_add_node("|                        |", 0, NULL);
  1317:   mm_add_node("\\========================/", 0, NULL);
  1318:   mm_add_node("", 0, NULL);
  1319:   mm_add_node("(go back)",
  1320: 	      1,
  1321: 	      mm_go_back);
  1322:   mm_add_node("", 0, NULL);
  1323: 
  1324:   nn = Wifi_GetNumAP();
  1325: 
  1326:   for (i = 0 ; i < nn ; i++) {
  1327: 
  1328:     if (WIFI_RETURN_OK == Wifi_GetAPData(i,&ap)) {
  1329: 
  1330:       if (strncmp(ap.ssid, "", 2) != 0) {
  1331: 
  1332: 	if (mm_get_index_by_label(ap.ssid) == -1) {
  1333: 
  1334: 	  strncpy(ssidbuf, ap.ssid, MENU_LABEL_MAXLEN);
  1335: 	  
  1336: 	  mm_add_node("", 0, NULL);
  1337: 	  mm_add_node(ssidbuf,
  1338: 		      1,
  1339: 		      NULL);
  1340: 
  1341: 	} // end if not already in the list
  1342: 
  1343:       } // end if not an empty string
  1344: 
  1345:     } // end got data for ap       
  1346:                 
  1347:   } // end iteration over networks 
  1348: 
  1349:   mm_alphasort(mm_list, 9, num_selections);
  1350: 
  1351:   mm_set_selection_by_hist();
  1352: 
  1353:   mcp_fade_start(MCP_SUB_SCREEN, 
  1354: 		 3, 
  1355: 		 1,
  1356: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1357: 		 idle_rate,
  1358: 		 NULL);
  1359: 
  1360: }
  1361: 
  1362: 
  1363: void mm_go_back(void) {
  1364: 
  1365:   mcp_snd_click();
  1366:   mcp_fade_start(MCP_SUB_SCREEN, 
  1367: 		 3, 
  1368: 		 0,
  1369: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1370: 		 idle_rate,
  1371: 		 &mm_create_last);
  1372: 
  1373: }
  1374: 
  1375: 
  1376: void mm_go_misc_back(void) {
  1377: 
  1378:   mcp_snd_click();
  1379:   mcp_fade_start(MCP_SUB_SCREEN, 
  1380: 		 3, 
  1381: 		 0,
  1382: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1383: 		 idle_rate,
  1384: 		 &mm_create_root);
  1385: 
  1386: }
  1387: 
  1388: 
  1389: void mm_go_files_back(void) {
  1390: 
  1391:   // note: this made a big difference, I.e. I think was causing
  1392:   //       stack corruption of the 16k stack
  1393:   //  char tmpstring_dir[FS_PATH_MAXLEN];
  1394:   //  char tmpstring_base[FS_PATH_MAXLEN];
  1395:   char *tmpstring_dir;
  1396: 
  1397: 
  1398:   if ((tmpstring_dir = malloc(FS_PATH_MAXLEN)) == NULL ) die();
  1399: 
  1400:   if (strncmp(pwd, "/", FS_PATH_MAXLEN) == 0) {
  1401:     // if pwd is /, go back
  1402:     mcp_snd_click();
  1403:     mcp_fade_start(MCP_SUB_SCREEN, 
  1404: 		   3, 
  1405: 		   0,
  1406: 		   MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1407: 		   idle_rate,
  1408: 		   &mm_create_root);
  1409:   } else {
  1410:     // else cd .. and regenerate list
  1411:     // note: dirname manpage on linux.com, but not f10, says not to free
  1412:     //       the returned pointer.  Also warnings about passed string being
  1413:     //       modified.
  1414:     //    strncpy(tmpstring_dir, dirname(pwd), FS_PATH_MAXLEN);
  1415:     // ... but my dirname implementation is different at the moment
  1416:     mcp_dirname(tmpstring_dir, pwd);
  1417: 
  1418:     strncpy(pwd, tmpstring_dir, FS_PATH_MAXLEN);
  1419: 
  1420:     mcp_fade_start(MCP_SUB_SCREEN, 
  1421: 		   3, 
  1422: 		   0,
  1423: 		   MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1424: 		   idle_rate,
  1425: 		   &mm_create_files);
  1426:   }
  1427: 
  1428:   free(tmpstring_dir);
  1429: 
  1430:   return;
  1431: }
  1432: 
  1433: 
  1434: void mm_go_files_entry(void) {
  1435: 
  1436:   // stack->heap fodder
  1437:   struct stat t_statbuf;
  1438: 
  1439:   // use selection_index to retrieve entry string/filename
  1440:   // and then use pwd to craft full path
  1441:   if (strncmp(pwd, "/", 2) == 0) {
  1442:     snprintf(t_scratch_path, 
  1443: 	     FS_PATH_MAXLEN, 
  1444: 	     "/%s",
  1445: 	     (mm_get_node_by_index(selection_index))->label);
  1446:   } else {
  1447:     snprintf(t_scratch_path, 
  1448: 	     FS_PATH_MAXLEN, 
  1449: 	     "%s/%s",
  1450: 	     pwd,
  1451: 	     (mm_get_node_by_index(selection_index))->label);
  1452:   }
  1453: 
  1454:   // TODO: catch error
  1455:   stat(t_scratch_path, &t_statbuf);
  1456: 
  1457:   if (S_ISDIR(t_statbuf.st_mode)) {
  1458:     // set new pwd
  1459:     strncpy(pwd, t_scratch_path, FS_PATH_MAXLEN);
  1460:     // lose a trailing slash if present
  1461:     // note: I really think I tried the same with t_scratch_path above,
  1462:     //       and it didn't make its way through to pwd??
  1463:     if (pwd[strlen(pwd) - 1] == '/') {
  1464:       pwd[strlen(pwd) - 1] = '\0';
  1465:     }
  1466:     mm_hist_push("../ (go back)");
  1467:     mcp_snd_click();
  1468:     mcp_fade_start(MCP_SUB_SCREEN, 
  1469: 		   3, 
  1470: 		   0,
  1471: 		   MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1472: 		   idle_rate,
  1473: 		   &mm_create_files);
  1474:   } else {
  1475:     execz(t_scratch_path, 0, NULL);
  1476:   }
  1477: 
  1478:   // TODO: add handlers for ogg, preverification of valid .nds, etc...
  1479: 
  1480: }
  1481: 
  1482: 
  1483: void mm_create(void (*create_func)(void)) {
  1484:   
  1485:   if (mm_hist != NULL) {
  1486:     mm_hist->select_cb = create_func;
  1487:   }
  1488: 
  1489:   (*create_func)();
  1490: 
  1491: }
  1492: 
  1493: void mm_create_last(void) {
  1494: 
  1495:   if (mm_hist != NULL) {
  1496:     if (mm_hist->select_cb != NULL) {
  1497:       (*(mm_hist->select_cb))();
  1498:       return;
  1499:     } else if (mm_hist->next != NULL) {
  1500:       if (mm_hist->next->select_cb != NULL) {
  1501: 	(*(mm_hist->next->select_cb))();
  1502: 	return;
  1503:       }
  1504:     }
  1505:   }
  1506: 
  1507:   // fallback to root
  1508:   mm_create_root();
  1509: }
  1510: 
  1511: 
  1512: void mm_create_root(void) {
  1513: 
  1514:   mm_free();
  1515: 
  1516:   mm_set_doublemode(1);
  1517:   
  1518:   mm_add_node("WhammyPad!",
  1519: 	      1,
  1520: 	      mm_go_whammypad);
  1521:   mm_add_node("", 0, NULL);
  1522:   mm_add_node(" settings",
  1523: 	      1,
  1524: 	      mm_go_settings);
  1525:   mm_add_node("", 0, NULL);
  1526:   mm_add_node("   help",
  1527: 	      1,
  1528:   	      mm_go_usermanual);
  1529:   mm_add_node("", 0, NULL);
  1530:   mm_add_node(" advanced",
  1531: 	      1,
  1532: 	      mm_go_misc);
  1533:   mm_add_node("", 0, NULL);
  1534:   mm_add_node("filesystem",
  1535: 	      1,
  1536: 	      mm_go_files);
  1537: 
  1538:   mm_set_selection_by_hist();
  1539: 
  1540:   mcp_fade_start(MCP_SUB_SCREEN, 
  1541: 		 3, 
  1542: 		 1,
  1543: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1544: 		 idle_rate,
  1545: 		 NULL);
  1546: 
  1547: }
  1548: 
  1549: 
  1550: void mm_create_misc(void) {
  1551:   
  1552:   mm_free();
  1553: 
  1554:   mm_set_doublemode(0);
  1555: 
  1556:   mm_add_node("/========================\\", 0, NULL);
  1557:   mm_add_node("|                        |", 0, NULL);
  1558:   mm_add_node("| MCP:: Advanced Options |", 0, NULL);
  1559:   mm_add_node("|                        |", 0, NULL);
  1560:   mm_add_node("\\========================/", 0, NULL);
  1561:   mm_add_node("", 0, NULL);
  1562:   mm_add_node("(go back)",
  1563: 	      1,
  1564: 	      mm_go_misc_back);
  1565:   mm_add_node("", 0, NULL);
  1566:   mm_add_node("get update",
  1567: 	      1,
  1568: 	      mm_go_get_update);
  1569:   mm_add_node("", 0, NULL);
  1570:   mm_add_node("get and burn-in update",
  1571: 	      1,
  1572: 	      mm_go_get_and_burn_update);
  1573:   mm_add_node("", 0, NULL);
  1574:   mm_add_node("show credits",
  1575: 	      1,
  1576: 	      mm_go_show_credits);
  1577:   mm_add_node("", 0, NULL);
  1578:   mm_add_node("set custom ssid",
  1579: 	      1,
  1580: 	      mm_go_ssid_input);
  1581:   mm_add_node("", 0, NULL);
  1582:   mm_add_node("show ssid list",
  1583: 	      1,
  1584: 	      mm_go_show_ssid_list);
  1585: 
  1586:   /*
  1587:   // debugging
  1588:   char label[16];
  1589:   int i;
  1590:   for (i = 3; i <= 46; i++) {
  1591:     mm_add_node("", 0, NULL);
  1592:     snprintf(label, 16, "adv-test-%02d", i);
  1593:     mm_add_node(label,
  1594: 		i % 2,
  1595: 		NULL);
  1596:   }
  1597:   mm_alphasort(mm_list, 8, num_selections);
  1598:   // /debugging
  1599:   */
  1600: 
  1601:   mm_set_selection_by_hist();
  1602: 
  1603:   mcp_fade_start(MCP_SUB_SCREEN, 
  1604: 		 3, 
  1605: 		 1,
  1606: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1607: 		 idle_rate,
  1608: 		 NULL);
  1609: }
  1610: 
  1611: 
  1612: void mm_create_files(void) {
  1613:   
  1614:   // the directory to open and scan (will be whatever pwd[] is)
  1615:   DIR *dir;
  1616:   // note: if the 16k stack space becomes really tight, this is a prime
  1617:   //       candidate for something to malloc on the heap
  1618:   // TODO: detect directory and add trailing / to label
  1619:   //         - and hope further trailing / handline is unneeded
  1620:   //  struct stat t_statbuf;
  1621:   // hint: 42
  1622:   struct dirent *arthur;
  1623: 
  1624:   // stack->heap fodder
  1625:   struct stat t_statbuf;
  1626: 
  1627: 
  1628:   mm_free();
  1629: 
  1630:   mm_set_doublemode(0);
  1631: 
  1632:   mm_add_node("/========================\\", 0, NULL);
  1633:   mm_add_node("|                        |", 0, NULL);
  1634:   mm_add_node("| Contents of directory- |", 0, NULL);
  1635:   mm_add_node("|                        |", 0, NULL);
  1636:   if (strncmp(pwd, "/", 2) == 0) {
  1637:     snprintf(t_scratch_path, 
  1638: 	     MENU_LABEL_MAXLEN,
  1639: 	     "| / (root)               |");
  1640:     
  1641:   } else {
  1642:     snprintf(t_scratch_path, 
  1643: 	     MENU_LABEL_MAXLEN,
  1644: 	     "| %-23s|",
  1645: 	     pwd);
  1646:   }
  1647:   mm_add_node(t_scratch_path,
  1648: 	      0,
  1649: 	      NULL);
  1650:   // allow wrappage
  1651:   mm_add_node("|                        |", 0, NULL);
  1652:   mm_add_node("\\========================/", 0, NULL);
  1653:   mm_add_node("", 0, NULL);
  1654: 
  1655:   if (strncmp(pwd, "/", 2) == 0) {
  1656:     mm_add_node("(go back)",
  1657: 		1,
  1658: 		mm_go_files_back);
  1659:   } else {
  1660:     mm_add_node("../ (go back)",
  1661: 		1,
  1662: 		mm_go_files_back);
  1663:   }
  1664: 
  1665:   // open directory
  1666:   dir = opendir(pwd);
  1667: 
  1668:   // catch possible error
  1669:   if (dir == NULL) {
  1670:     mm_add_node("", 0, NULL);
  1671:     mm_add_node("ERROR: opendir failed",
  1672: 		0,
  1673: 		NULL);
  1674: 
  1675:     mm_set_selection_by_index(1);
  1676: 
  1677:     return;
  1678:   }
  1679: 
  1680:   // readdir returns struct dirent *, which has d_name[NAME_MAX] entry name string, and NULL on end of dir
  1681:   while ((arthur = readdir(dir)) != NULL) {
  1682:     // leave out . and .. (for .. we have back already)
  1683:     if ((strncmp(arthur->d_name, ".", 2) != 0) &&
  1684: 	(strncmp(arthur->d_name, "..", 3) != 0)) {
  1685:       
  1686:       if (strncmp(pwd, "/", 2) == 0) {
  1687: 	snprintf(t_scratch_path, 
  1688: 		 FS_PATH_MAXLEN, 
  1689: 		 "/%s",
  1690: 		 arthur->d_name);
  1691:       } else {
  1692: 	snprintf(t_scratch_path, 
  1693: 		 FS_PATH_MAXLEN, 
  1694: 		 "%s/%s",
  1695: 		 pwd,
  1696: 		 arthur->d_name);
  1697:       }
  1698:       // TODO: catch error
  1699:       retval = stat(t_scratch_path, &t_statbuf);
  1700:       
  1701:       if (retval == -1) {
  1702: 	//	die("stat error");
  1703: 	die();
  1704:       } else if (S_ISDIR(t_statbuf.st_mode)) {
  1705: 	snprintf(t_scratch_path, 
  1706: 		 MENU_LABEL_MAXLEN,
  1707: 		 "%s/",
  1708: 		 arthur->d_name);
  1709:       } else {
  1710: 	snprintf(t_scratch_path, 
  1711: 		 MENU_LABEL_MAXLEN,
  1712: 		 "%s",
  1713: 		 arthur->d_name);
  1714:       }
  1715: 
  1716:       mm_add_node("", 0, NULL);
  1717:       mm_add_node(t_scratch_path,
  1718: 		  1,
  1719: 		  mm_go_files_entry);
  1720:     }
  1721:   }
  1722: 
  1723:   // TODO: catch error / rv=-1
  1724:   closedir(dir);
  1725: 
  1726:   // 9 is dependent on header formatting above (and doublespacing)
  1727:   mm_alphasort(mm_list, 9, num_selections);
  1728: 
  1729:   mm_set_selection_by_label(mm_hist_peek());
  1730:   mm_hist_pop();
  1731: 
  1732:   mcp_fade_start(MCP_SUB_SCREEN, 
  1733: 		 3, 
  1734: 		 1,
  1735: 		 MAIN_MENU__BOT_TXT_INTRAMODE_FADE_DURATION_MS,
  1736: 		 idle_rate,
  1737: 		 NULL);
  1738: }
  1739: