1/*
2 *    Implementation of GPTData class derivative with curses-based text-mode
3 *    interaction
4 *    Copyright (C) 2011-2013 Roderick W. Smith
5 *
6 *    This program is free software; you can redistribute it and/or modify
7 *    it under the terms of the GNU General Public License as published by
8 *    the Free Software Foundation; either version 2 of the License, or
9 *    (at your option) any later version.
10 *
11 *    This program is distributed in the hope that it will be useful,
12 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *    GNU General Public License for more details.
15 *
16 *    You should have received a copy of the GNU General Public License along
17 *    with this program; if not, write to the Free Software Foundation, Inc.,
18 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 *
20 */
21
22#include <iostream>
23#include <string>
24#include <sstream>
25#include <ncurses.h>
26#include "gptcurses.h"
27#include "support.h"
28
29using namespace std;
30
31// # of lines to reserve for general information and headers (RESERVED_TOP)
32// and for options and messages (RESERVED_BOTTOM)
33#define RESERVED_TOP 7
34#define RESERVED_BOTTOM 5
35
36int GPTDataCurses::numInstances = 0;
37
38GPTDataCurses::GPTDataCurses(void) {
39   if (numInstances > 0) {
40      refresh();
41   } else {
42      setlocale( LC_ALL , "" );
43      initscr();
44      cbreak();
45      noecho();
46      intrflush(stdscr, false);
47      keypad(stdscr, true);
48      nonl();
49      numInstances++;
50   } // if/else
51   firstSpace = NULL;
52   lastSpace = NULL;
53   currentSpace = NULL;
54   currentSpaceNum = -1;
55   whichOptions = ""; // current set of options
56   currentKey = 'b'; // currently selected option
57   displayType = USE_CURSES;
58} // GPTDataCurses constructor
59
60GPTDataCurses::~GPTDataCurses(void) {
61   numInstances--;
62   if ((numInstances == 0) && !isendwin())
63      endwin();
64} // GPTDataCurses destructor
65
66/************************************************
67 *                                              *
68 * Functions relating to Spaces data structures *
69 *                                              *
70 ************************************************/
71
72void GPTDataCurses::EmptySpaces(void) {
73   Space *trash;
74
75   while (firstSpace != NULL) {
76      trash = firstSpace;
77      firstSpace = firstSpace->nextSpace;
78      delete trash;
79   } // if
80   numSpaces = 0;
81   lastSpace = NULL;
82} // GPTDataCurses::EmptySpaces()
83
84// Create Spaces from partitions. Does NOT creates Spaces to represent
85// unpartitioned space on the disk.
86// Returns the number of Spaces created.
87int GPTDataCurses::MakeSpacesFromParts(void) {
88   uint i;
89   Space *tempSpace;
90
91   EmptySpaces();
92   for (i = 0; i < numParts; i++) {
93      if (partitions[i].IsUsed()) {
94         tempSpace = new Space;
95         tempSpace->firstLBA = partitions[i].GetFirstLBA();
96         tempSpace->lastLBA = partitions[i].GetLastLBA();
97         tempSpace->origPart = &partitions[i];
98         tempSpace->partNum = (int) i;
99         LinkToEnd(tempSpace);
100      } // if
101   } // for
102   return numSpaces;
103} // GPTDataCurses::MakeSpacesFromParts()
104
105// Add a single empty Space to the current Spaces linked list and sort the result....
106void GPTDataCurses::AddEmptySpace(uint64_t firstLBA, uint64_t lastLBA) {
107   Space *tempSpace;
108
109   tempSpace = new Space;
110   tempSpace->firstLBA = firstLBA;
111   tempSpace->lastLBA = lastLBA;
112   tempSpace->origPart = &emptySpace;
113   tempSpace->partNum = -1;
114   LinkToEnd(tempSpace);
115   SortSpaces();
116} // GPTDataCurses::AddEmptySpace();
117
118// Add Spaces to represent the unallocated parts of the partition table.
119// Returns the number of Spaces added.
120int GPTDataCurses::AddEmptySpaces(void) {
121   int numAdded = 0;
122   Space *current;
123
124   SortSpaces();
125   if (firstSpace == NULL) {
126      AddEmptySpace(GetFirstUsableLBA(), GetLastUsableLBA());
127      numAdded++;
128   } else {
129      current = firstSpace;
130      while ((current != NULL) /* && (current->partNum != -1) */ ) {
131         if ((current == firstSpace) && (current->firstLBA > GetFirstUsableLBA())) {
132            AddEmptySpace(GetFirstUsableLBA(), current->firstLBA - 1);
133            numAdded++;
134         } // if
135         if ((current == lastSpace) && (current->lastLBA < GetLastUsableLBA())) {
136            AddEmptySpace(current->lastLBA + 1, GetLastUsableLBA());
137            numAdded++;
138         } // if
139         if ((current->prevSpace != NULL) && (current->prevSpace->lastLBA < (current->firstLBA - 1))) {
140            AddEmptySpace(current->prevSpace->lastLBA + 1, current->firstLBA - 1);
141            numAdded++;
142         } // if
143         current = current->nextSpace;
144      } // while
145   } // if/else
146   return numAdded;
147} // GPTDataCurses::AddEmptySpaces()
148
149// Remove the specified Space from the linked list and set its previous and
150// next pointers to NULL.
151void GPTDataCurses::UnlinkSpace(Space *theSpace) {
152   if (theSpace != NULL) {
153      if (theSpace->prevSpace != NULL)
154         theSpace->prevSpace->nextSpace = theSpace->nextSpace;
155      if (theSpace->nextSpace != NULL)
156         theSpace->nextSpace->prevSpace = theSpace->prevSpace;
157      if (theSpace == firstSpace)
158         firstSpace = theSpace->nextSpace;
159      if (theSpace == lastSpace)
160         lastSpace = theSpace->prevSpace;
161      theSpace->nextSpace = NULL;
162      theSpace->prevSpace = NULL;
163      numSpaces--;
164   } // if
165} // GPTDataCurses::UnlinkSpace
166
167// Link theSpace to the end of the current linked list.
168void GPTDataCurses::LinkToEnd(Space *theSpace) {
169   if (lastSpace == NULL) {
170      firstSpace = lastSpace = theSpace;
171      theSpace->nextSpace = NULL;
172      theSpace->prevSpace = NULL;
173   } else {
174      theSpace->prevSpace = lastSpace;
175      theSpace->nextSpace = NULL;
176      lastSpace->nextSpace = theSpace;
177      lastSpace = theSpace;
178   } // if/else
179   numSpaces++;
180} // GPTDataCurses::LinkToEnd()
181
182// Sort spaces into ascending order by on-disk position.
183void GPTDataCurses::SortSpaces(void) {
184   Space *oldFirst, *oldLast, *earliest = NULL, *current = NULL;
185
186   oldFirst = firstSpace;
187   oldLast = lastSpace;
188   firstSpace = lastSpace = NULL;
189   while (oldFirst != NULL) {
190      current = earliest = oldFirst;
191      while (current != NULL) {
192         if (current->firstLBA < earliest->firstLBA)
193            earliest = current;
194         current = current->nextSpace;
195      } // while
196      if (oldFirst == earliest)
197         oldFirst = earliest->nextSpace;
198      if (oldLast == earliest)
199         oldLast = earliest->prevSpace;
200      UnlinkSpace(earliest);
201      LinkToEnd(earliest);
202   } // while
203} // GPTDataCurses::SortSpaces()
204
205// Identify the spaces on the disk, a "space" being defined as a partition
206// or an empty gap between, before, or after partitions. The spaces are
207// presented to users in the main menu display.
208void GPTDataCurses::IdentifySpaces(void) {
209   MakeSpacesFromParts();
210   AddEmptySpaces();
211} // GPTDataCurses::IdentifySpaces()
212
213/**************************
214 *                        *
215 * Data display functions *
216 *                        *
217 **************************/
218
219// Display a single Space on line # lineNum.
220// Returns a pointer to the space being displayed
221Space* GPTDataCurses::ShowSpace(int spaceNum, int lineNum) {
222   Space *space;
223   int i = 0;
224#ifdef USE_UTF16
225   char temp[40];
226#endif
227
228   space = firstSpace;
229   while ((space != NULL) && (i < spaceNum)) {
230      space = space->nextSpace;
231      i++;
232   } // while
233   if ((space != NULL) && (lineNum < (LINES - 5))) {
234      ClearLine(lineNum);
235      if (space->partNum == -1) { // space is empty
236         move(lineNum, 12);
237         printw(BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
238         move(lineNum, 24);
239         printw("free space");
240      } else { // space holds a partition
241         move(lineNum, 3);
242         printw("%d", space->partNum + 1);
243         move(lineNum, 12);
244         printw(BytesToIeee((space->lastLBA - space->firstLBA + 1), blockSize).c_str());
245         move(lineNum, 24);
246         printw(space->origPart->GetTypeName().c_str());
247         move(lineNum, 50);
248         #ifdef USE_UTF16
249         space->origPart->GetDescription().extract(0, 39, temp, 39);
250         printw(temp);
251         #else
252         printw(space->origPart->GetDescription().c_str());
253         #endif
254      } // if/else
255   } // if
256   return space;
257} // GPTDataCurses::ShowSpace
258
259// Display the partitions, being sure that the space #selected is displayed
260// and highlighting that space.
261// Returns the number of the space being shown (should be selected, but will
262// be -1 if something weird happens)
263int GPTDataCurses::DisplayParts(int selected) {
264   int lineNum = 5, i = 0, retval = -1, numToShow, pageNum;
265   string theLine;
266
267   move(lineNum++, 0);
268   theLine = "Part. #     Size        Partition Type            Partition Name";
269   printw(theLine.c_str());
270   move(lineNum++, 0);
271   theLine = "----------------------------------------------------------------";
272   printw(theLine.c_str());
273   numToShow = LINES - RESERVED_TOP - RESERVED_BOTTOM;
274   pageNum = selected / numToShow;
275   for (i = pageNum * numToShow; i <= (pageNum + 1) * numToShow - 1; i++) {
276      if (i < numSpaces) { // real space; show it
277         if (i == selected) {
278            currentSpaceNum = i;
279            if (displayType == USE_CURSES) {
280               attron(A_REVERSE);
281               currentSpace = ShowSpace(i, lineNum++);
282               attroff(A_REVERSE);
283            } else {
284               currentSpace = ShowSpace(i, lineNum);
285               move(lineNum++, 0);
286               printw(">");
287            }
288            DisplayOptions(i);
289            retval = selected;
290         } else {
291            ShowSpace(i, lineNum++);
292         }
293      } else { // blank in display
294         ClearLine(lineNum++);
295      } // if/else
296   } // for
297   refresh();
298   return retval;
299} // GPTDataCurses::DisplayParts()
300
301/**********************************************
302 *                                            *
303 * Functions corresponding to main menu items *
304 *                                            *
305 **********************************************/
306
307// Delete the specified partition and re-detect partitions and spaces....
308void GPTDataCurses::DeletePartition(int partNum) {
309   if (!GPTData::DeletePartition(partNum))
310      Report("Could not delete partition!");
311   IdentifySpaces();
312   if (currentSpaceNum >= numSpaces) {
313      currentSpaceNum = numSpaces - 1;
314      currentSpace = lastSpace;
315   } // if
316} // GPTDataCurses::DeletePartition()
317
318// Displays information on the specified partition
319void GPTDataCurses::ShowInfo(int partNum) {
320   uint64_t size;
321#ifdef USE_UTF16
322   char temp[NAME_SIZE + 1];
323#endif
324
325   clear();
326   move(2, (COLS - 29) / 2);
327   printw("Information for partition #%d\n\n", partNum + 1);
328   printw("Partition GUID code: %s (%s)\n", partitions[partNum].GetType().AsString().c_str(),
329          partitions[partNum].GetTypeName().c_str());
330   printw("Partition unique GUID: %s\n", partitions[partNum].GetUniqueGUID().AsString().c_str());
331   printw("First sector: %lld (at %s)\n", partitions[partNum].GetFirstLBA(),
332          BytesToIeee(partitions[partNum].GetFirstLBA(), blockSize).c_str());
333   printw("Last sector: %lld (at %s)\n", partitions[partNum].GetLastLBA(),
334          BytesToIeee(partitions[partNum].GetLastLBA(), blockSize).c_str());
335   size = partitions[partNum].GetLastLBA() - partitions[partNum].GetFirstLBA();
336   printw("Partition size: %lld sectors (%s)\n", size, BytesToIeee(size, blockSize).c_str());
337   printw("Attribute flags: %016x\n", partitions[partNum].GetAttributes().GetAttributes());
338   #ifdef USE_UTF16
339   partitions[partNum].GetDescription().extract(0, NAME_SIZE , temp, NAME_SIZE );
340   printw("Partition name: '%s'\n", temp);
341   #else
342   printw("Partition name: '%s'\n", partitions[partNum].GetDescription().c_str());
343   #endif
344   PromptToContinue();
345} // GPTDataCurses::ShowInfo()
346
347// Prompt for and change a partition's name....
348void GPTDataCurses::ChangeName(int partNum) {
349   char temp[NAME_SIZE + 1];
350
351   if (ValidPartNum(partNum)) {
352      move(LINES - 4, 0);
353      clrtobot();
354      move(LINES - 4, 0);
355      #ifdef USE_UTF16
356      partitions[partNum].GetDescription().extract(0, NAME_SIZE , temp, NAME_SIZE );
357      printw("Current partition name is '%s'\n", temp);
358      #else
359      printw("Current partition name is '%s'\n", partitions[partNum].GetDescription().c_str());
360      #endif
361      printw("Enter new partition name, or <Enter> to use the current name:\n");
362      echo();
363      getnstr(temp, NAME_SIZE );
364      partitions[partNum].SetName((string) temp);
365      noecho();
366   } // if
367} // GPTDataCurses::ChangeName()
368
369// Change the partition's type code....
370void GPTDataCurses::ChangeType(int partNum) {
371   char temp[80] = "L\0";
372   PartType tempType;
373
374   echo();
375   do {
376      move(LINES - 4, 0);
377      clrtobot();
378      move(LINES - 4, 0);
379      printw("Current type is %04x (%s)\n", partitions[partNum].GetType().GetHexType(), partitions[partNum].GetTypeName().c_str());
380      printw("Hex code or GUID (L to show codes, Enter = %04x): ", partitions[partNum].GetType().GetHexType());
381      getnstr(temp, 79);
382      if ((temp[0] == 'L') || (temp[0] == 'l')) {
383         ShowTypes();
384      } else {
385         if (temp[0] == '\0')
386            tempType = partitions[partNum].GetType().GetHexType();
387         tempType = temp;
388         partitions[partNum].SetType(tempType);
389      } // if
390   } while ((temp[0] == 'L') || (temp[0] == 'l') || (partitions[partNum].GetType() == (GUIDData) "0x0000"));
391   noecho();
392} // GPTDataCurses::ChangeType
393
394// Sets the partition alignment value
395void GPTDataCurses::SetAlignment(void) {
396   int alignment;
397
398   move(LINES - 4, 0);
399   clrtobot();
400   printw("Current partition alignment, in sectors, is %d.", GetAlignment());
401   do {
402      move(LINES - 3, 0);
403      printw("Type new alignment value, in sectors: ");
404      echo();
405      scanw("%d", &alignment);
406      noecho();
407   } while ((alignment == 0) || (alignment > MAX_ALIGNMENT));
408   GPTData::SetAlignment(alignment);
409} // GPTDataCurses::SetAlignment()
410
411// Verify the data structures. Note that this function leaves curses mode and
412// relies on the underlying GPTData::Verify() function to report on problems
413void GPTDataCurses::Verify(void) {
414   char junk;
415
416   def_prog_mode();
417   endwin();
418   GPTData::Verify();
419   cout << "\nPress the <Enter> key to continue: ";
420   cin.get(junk);
421   reset_prog_mode();
422   refresh();
423} // GPTDataCurses::Verify()
424
425// Create a new partition in the space pointed to by currentSpace.
426void GPTDataCurses::MakeNewPart(void) {
427   uint64_t size, newFirstLBA = 0, newLastLBA = 0;
428   int partNum;
429   char inLine[80];
430
431   move(LINES - 4, 0);
432   clrtobot();
433   while ((newFirstLBA < currentSpace->firstLBA) || (newFirstLBA > currentSpace->lastLBA)) {
434      newFirstLBA = currentSpace->firstLBA;
435      move(LINES - 4, 0);
436      clrtoeol();
437      newFirstLBA = currentSpace->firstLBA;
438      Align(&newFirstLBA);
439      printw("First sector (%lld-%lld, default = %lld): ", newFirstLBA, currentSpace->lastLBA, newFirstLBA);
440      echo();
441      getnstr(inLine, 79);
442      noecho();
443      newFirstLBA = IeeeToInt(inLine, blockSize, currentSpace->firstLBA, currentSpace->lastLBA, newFirstLBA);
444      Align(&newFirstLBA);
445   } // while
446   size = currentSpace->lastLBA - newFirstLBA + 1;
447   while ((newLastLBA > currentSpace->lastLBA) || (newLastLBA < newFirstLBA)) {
448      move(LINES - 3, 0);
449      clrtoeol();
450      printw("Size in sectors or {KMGTP} (default = %lld): ", size);
451      echo();
452      getnstr(inLine, 79);
453      noecho();
454      newLastLBA = newFirstLBA + IeeeToInt(inLine, blockSize, 1, size, size) - 1;
455   } // while
456   partNum = FindFirstFreePart();
457   if (CreatePartition(partNum, newFirstLBA, newLastLBA)) { // created OK; set type code & name....
458      ChangeType(partNum);
459      ChangeName(partNum);
460   } else {
461      Report("Error creating partition!");
462   } // if/else
463} // GPTDataCurses::MakeNewPart()
464
465// Prompt user for permission to save data and, if it's given, do so!
466void GPTDataCurses::SaveData(void) {
467   string answer = "";
468   char inLine[80];
469
470   move(LINES - 4, 0);
471   clrtobot();
472   move (LINES - 2, 14);
473   printw("Warning!! This may destroy data on your disk!");
474   echo();
475   while ((answer != "yes") && (answer != "no")) {
476      move (LINES - 4, 2);
477      printw("Are you sure you want to write the partition table to disk? (yes or no): ");
478      getnstr(inLine, 79);
479      answer = inLine;
480      if ((answer != "yes") && (answer != "no")) {
481         move(LINES - 2, 0);
482         clrtoeol();
483         move(LINES - 2, 14);
484         printw("Please enter 'yes' or 'no'");
485      } // if
486   } // while()
487   noecho();
488   if (answer == "yes") {
489      if (SaveGPTData(1)) {
490         if (!myDisk.DiskSync())
491            Report("The kernel may be using the old partition table. Reboot to use the new\npartition table!");
492      } else {
493         Report("Problem saving data! Your partition table may be damaged!");
494      }
495   }
496} // GPTDataCurses::SaveData()
497
498// Back up the partition table, prompting user for a filename....
499void GPTDataCurses::Backup(void) {
500   char inLine[80];
501
502   ClearBottom();
503   move(LINES - 3, 0);
504   printw("Enter backup filename to save: ");
505   echo();
506   getnstr(inLine, 79);
507   noecho();
508   SaveGPTBackup(inLine);
509} // GPTDataCurses::Backup()
510
511// Load a GPT backup from a file
512void GPTDataCurses::LoadBackup(void) {
513   char inLine[80];
514
515   ClearBottom();
516   move(LINES - 3, 0);
517   printw("Enter backup filename to load: ");
518   echo();
519   getnstr(inLine, 79);
520   noecho();
521   if (!LoadGPTBackup(inLine))
522      Report("Restoration failed!");
523   IdentifySpaces();
524} // GPTDataCurses::LoadBackup()
525
526// Display some basic help information
527void GPTDataCurses::ShowHelp(void) {
528   int i = 0;
529
530   clear();
531   move(0, (COLS - 22) / 2);
532   printw("Help screen for cgdisk");
533   move(2, 0);
534   printw("This is cgdisk, a curses-based disk partitioning program. You can use it\n");
535   printw("to create, delete, and modify partitions on your hard disk.\n\n");
536   attron(A_BOLD);
537   printw("Use cgdisk only on GUID Partition Table (GPT) disks!\n");
538   attroff(A_BOLD);
539   printw("Use cfdisk on Master Boot Record (MBR) disks.\n\n");
540   printw("Command      Meaning\n");
541   printw("-------      -------\n");
542   while (menuMain[i].key != 0) {
543      printw("   %c         %s\n", menuMain[i].key, menuMain[i].desc.c_str());
544      i++;
545   } // while()
546   PromptToContinue();
547} // GPTDataCurses::ShowHelp()
548
549/************************************
550 *                                  *
551 * User input and menuing functions *
552 *                                  *
553 ************************************/
554
555// Change the currently-selected space....
556void GPTDataCurses::ChangeSpaceSelection(int delta) {
557   if (currentSpace != NULL) {
558      while ((delta > 0) && (currentSpace->nextSpace != NULL)) {
559         currentSpace = currentSpace->nextSpace;
560         delta--;
561         currentSpaceNum++;
562      } // while
563      while ((delta < 0) && (currentSpace->prevSpace != NULL)) {
564         currentSpace = currentSpace->prevSpace;
565         delta++;
566         currentSpaceNum--;
567      } // while
568   } // if
569   // Below will hopefully never be true; bad counting error (bug), so reset to
570   // the first Space as a failsafe....
571   if (DisplayParts(currentSpaceNum) != currentSpaceNum) {
572      currentSpaceNum = 0;
573      currentSpace = firstSpace;
574      DisplayParts(currentSpaceNum);
575   } // if
576} // GPTDataCurses
577
578// Move option selection left or right....
579void GPTDataCurses::MoveSelection(int delta) {
580   int newKeyNum;
581
582   // Begin with a sanity check to ensure a valid key is selected....
583   if (whichOptions.find(currentKey) == string::npos)
584      currentKey = 'n';
585   newKeyNum = whichOptions.find(currentKey);
586   newKeyNum += delta;
587   if (newKeyNum < 0)
588      newKeyNum = whichOptions.length() - 1;
589   newKeyNum %= whichOptions.length();
590   currentKey = whichOptions[newKeyNum];
591   DisplayOptions(currentKey);
592} // GPTDataCurses::MoveSelection()
593
594// Show user's options. Refers to currentSpace to determine which options to show.
595// Highlights the option with the key selectedKey; or a default if that's invalid.
596void GPTDataCurses::DisplayOptions(char selectedKey) {
597   uint i, j = 0, firstLine, numPerLine;
598   string optionName, optionDesc = "";
599
600   if (currentSpace != NULL) {
601      if (currentSpace->partNum == -1) { // empty space is selected
602         whichOptions = EMPTY_SPACE_OPTIONS;
603         if (whichOptions.find(selectedKey) == string::npos)
604            selectedKey = 'n';
605      } else { // a partition is selected
606         whichOptions = PARTITION_OPTIONS;
607         if (whichOptions.find(selectedKey) == string::npos)
608            selectedKey = 't';
609      } // if/else
610
611      firstLine = LINES - 4;
612      numPerLine = (COLS - 8) / 12;
613      ClearBottom();
614      move(firstLine, 0);
615      for (i = 0; i < whichOptions.length(); i++) {
616         optionName = "";
617         for (j = 0; menuMain[j].key; j++) {
618            if (menuMain[j].key == whichOptions[i]) {
619               optionName = menuMain[j].name;
620               if (whichOptions[i] == selectedKey)
621                  optionDesc = menuMain[j].desc;
622            } // if
623         } // for
624         move(firstLine + i / numPerLine, (i % numPerLine) * 12 + 4);
625         if (whichOptions[i] == selectedKey) {
626            attron(A_REVERSE);
627            printw("[ %s ]", optionName.c_str());
628            attroff(A_REVERSE);
629         } else {
630            printw("[ %s ]", optionName.c_str());
631         } // if/else
632      } // for
633      move(LINES - 1, (COLS - optionDesc.length()) / 2);
634      printw(optionDesc.c_str());
635      currentKey = selectedKey;
636   } // if
637} // GPTDataCurses::DisplayOptions()
638
639// Accept user input and process it. Returns when the program should terminate.
640void GPTDataCurses::AcceptInput() {
641   int inputKey, exitNow = 0;
642
643   do {
644      refresh();
645      inputKey = getch();
646      switch (inputKey) {
647         case KEY_UP:
648            ChangeSpaceSelection(-1);
649            break;
650         case KEY_DOWN:
651            ChangeSpaceSelection(+1);
652            break;
653         case 339: // page up key
654            ChangeSpaceSelection(RESERVED_TOP + RESERVED_BOTTOM - LINES);
655            break;
656         case 338: // page down key
657            ChangeSpaceSelection(LINES - RESERVED_TOP - RESERVED_BOTTOM);
658            break;
659         case KEY_LEFT:
660            MoveSelection(-1);
661            break;
662         case KEY_RIGHT:
663            MoveSelection(+1);
664            break;
665         case KEY_ENTER: case 13:
666            exitNow = Dispatch(currentKey);
667            break;
668         case 27: // escape key
669            exitNow = 1;
670            break;
671         default:
672            exitNow = Dispatch(inputKey);
673            break;
674      } // switch()
675   } while (!exitNow);
676} // GPTDataCurses::AcceptInput()
677
678// Operation has been selected, so do it. Returns 1 if the program should
679// terminate on return from this program, 0 otherwise.
680int GPTDataCurses::Dispatch(char operation) {
681   int exitNow = 0;
682
683   switch (operation) {
684      case 'a': case 'A':
685         SetAlignment();
686         break;
687      case 'b': case 'B':
688         Backup();
689         break;
690      case 'd': case 'D':
691         if (ValidPartNum(currentSpace->partNum))
692            DeletePartition(currentSpace->partNum);
693         break;
694      case 'h': case 'H':
695         ShowHelp();
696         break;
697      case 'i': case 'I':
698         if (ValidPartNum(currentSpace->partNum))
699            ShowInfo(currentSpace->partNum);
700         break;
701      case 'l': case 'L':
702         LoadBackup();
703         break;
704      case 'm': case 'M':
705         if (ValidPartNum(currentSpace->partNum))
706            ChangeName(currentSpace->partNum);
707         break;
708      case 'n': case 'N':
709         if (currentSpace->partNum < 0) {
710            MakeNewPart();
711            IdentifySpaces();
712         } // if
713         break;
714      case 'q': case 'Q':
715         exitNow = 1;
716         break;
717      case 't': case 'T':
718         if (ValidPartNum(currentSpace->partNum))
719            ChangeType(currentSpace->partNum);
720         break;
721      case 'v': case 'V':
722         Verify();
723         break;
724      case 'w': case 'W':
725         SaveData();
726         break;
727      default:
728         break;
729   } // switch()
730   DrawMenu();
731   return exitNow;
732} // GPTDataCurses::Dispatch()
733
734// Draws the main menu
735void GPTDataCurses::DrawMenu(void) {
736   string title="cgdisk ";
737   title += GPTFDISK_VERSION;
738   string drive="Disk Drive: ";
739   drive += device;
740   ostringstream size;
741
742   size << "Size: " << diskSize << ", " << BytesToIeee(diskSize, blockSize);
743
744   clear();
745   move(0, (COLS - title.length()) / 2);
746   printw(title.c_str());
747   move(2, (COLS - drive.length()) / 2);
748   printw(drive.c_str());
749   move(3, (COLS - size.str().length()) / 2);
750   printw(size.str().c_str());
751   DisplayParts(currentSpaceNum);
752} // DrawMenu
753
754int GPTDataCurses::MainMenu(void) {
755   if (((LINES - RESERVED_TOP - RESERVED_BOTTOM) < 2) || (COLS < 80)) {
756      Report("Display is too small; it must be at least 80 x 14 characters!");
757   } else {
758      if (GPTData::Verify() > 0)
759         Report("Warning! Problems found on disk! Use the Verify function to learn more.\n"
760                "Using gdisk or some other program may be necessary to repair the problems.");
761      IdentifySpaces();
762      currentSpaceNum = 0;
763      DrawMenu();
764      AcceptInput();
765   } // if/else
766   endwin();
767   return 0;
768} // GPTDataCurses::MainMenu
769
770/***********************************************************
771 *                                                         *
772 * Non-class support functions (mostly related to ncurses) *
773 *                                                         *
774 ***********************************************************/
775
776// Clears the specified line of all data....
777void ClearLine(int lineNum) {
778   move(lineNum, 0);
779   clrtoeol();
780} // ClearLine()
781
782// Clear the last few lines of the display
783void ClearBottom(void) {
784   move(LINES - RESERVED_BOTTOM, 0);
785   clrtobot();
786} // ClearBottom()
787
788void PromptToContinue(void) {
789   ClearBottom();
790   move(LINES - 2, (COLS - 29) / 2);
791   printw("Press any key to continue....");
792   cbreak();
793   getch();
794} // PromptToContinue()
795
796// Display one line of text on the screen and prompt to press any key to continue.
797void Report(string theText) {
798   clear();
799   move(0, 0);
800   printw(theText.c_str());
801   move(LINES - 2, (COLS - 29) / 2);
802   printw("Press any key to continue....");
803   cbreak();
804   getch();
805} // Report()
806
807// Displays all the partition type codes and then prompts to continue....
808// NOTE: This function temporarily exits curses mode as a matter of
809// convenience.
810void ShowTypes(void) {
811   PartType tempType;
812   char junk;
813
814   def_prog_mode();
815   endwin();
816   tempType.ShowAllTypes(LINES - 3);
817   cout << "\nPress the <Enter> key to continue: ";
818   cin.get(junk);
819   reset_prog_mode();
820   refresh();
821} // ShowTypes()
822