Home | History | Annotate | Download | only in gptfdisk
      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 
     29 using 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 
     36 int GPTDataCurses::numInstances = 0;
     37 
     38 GPTDataCurses::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 
     60 GPTDataCurses::~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 
     72 void 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.
     87 int 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....
    106 void 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.
    120 int 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.
    151 void 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.
    168 void 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.
    183 void 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.
    208 void 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
    221 Space* 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)
    263 int 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....
    308 void 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
    319 void 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....
    348 void 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....
    370 void 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
    395 void 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
    413 void 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.
    426 void 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!
    466 void 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....
    499 void 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
    512 void 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
    527 void 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....
    556 void 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....
    579 void 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.
    596 void 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.
    640 void 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.
    680 int 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
    735 void 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 
    754 int 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....
    777 void ClearLine(int lineNum) {
    778    move(lineNum, 0);
    779    clrtoeol();
    780 } // ClearLine()
    781 
    782 // Clear the last few lines of the display
    783 void ClearBottom(void) {
    784    move(LINES - RESERVED_BOTTOM, 0);
    785    clrtobot();
    786 } // ClearBottom()
    787 
    788 void 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.
    797 void 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.
    810 void 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