diff --git a/win/Qt/qt_menu.cpp b/win/Qt/qt_menu.cpp index a90cb7d9b..d14f19338 100644 --- a/win/Qt/qt_menu.cpp +++ b/win/Qt/qt_menu.cpp @@ -4,6 +4,20 @@ // qt_menu.cpp -- a menu or text-list widget +// +// TODO: +// search behaves weirdly unless you click in the line-edit dialog box +// after clicking on the [search] button to pop that up; +// implement next_page, prev_page, first_page, and last_page to work +// like they do for X11: scroll menu window as if it were paginated; +// entering a count that uses more digits than the previous biggest count +// widens count field but fails to widen the whole menu so a horizontal +// scrollbar might appear; +// create and use custom check-box icons to visually distinguish +// pre-selected entries and numeric subset entries from ordinary select +// and unselected. +// + extern "C" { #include "hack.h" } @@ -25,6 +39,7 @@ extern "C" { extern "C" int qt_compact_mode; // end temporary +/* for documentation rather than necessity; hack.h -> decl.h declares this */ extern "C" struct menucoloring *menu_colorings; namespace nethack_qt_ { @@ -67,20 +82,20 @@ QSize NetHackQtMenuListBox::sizeHint() const return QSize(TotalWidth()+hsize, TotalHeight()+hsize); } -// Table view columns: +// Table view columns (0..4): // -// [pick-count] [accel] [glyph] [string] +// [pick-count] [check-box] [glyph] [accel] [string] // -// Maybe accel should be near string. We'll see. -// pick-count normally blank. -// double-clicking or click-on-count gives pop-up entry -// string is green when selected +// pick-count is normally empty and gets wider as needed. // NetHackQtMenuWindow::NetHackQtMenuWindow(QWidget *parent) : QDialog(parent), table(new NetHackQtMenuListBox()), prompt(0), - counting(false) + biggestcount(0L), // largest subset amount that user has entered + countdigits(0), // number of digits needed by biggestcount + counting(false), // user has typed a digit and more might follow + searching(false) { // setFont() was in SelectMenu(), in time to be rendered but too late // when measuring the width and height that will be needed @@ -90,7 +105,7 @@ NetHackQtMenuWindow::NetHackQtMenuWindow(QWidget *parent) : QGridLayout *grid = new QGridLayout(); table->setColumnCount(5); table->setFrameStyle(QFrame::Panel|QFrame::Sunken); - table->setLineWidth(2); + table->setLineWidth(2); // note: this is not row spacing table->setShowGrid(false); table->horizontalHeader()->hide(); table->verticalHeader()->hide(); @@ -131,7 +146,7 @@ NetHackQtMenuWindow::NetHackQtMenuWindow(QWidget *parent) : setFocusPolicy(Qt::StrongFocus); table->setFocusPolicy(Qt::NoFocus); connect(table, SIGNAL(cellClicked(int,int)), - this, SLOT(cellToggleSelect(int,int))); + this, SLOT(TableCellClicked(int,int))); setLayout(grid); } @@ -158,13 +173,15 @@ NetHackQtMenuWindow::MenuItem::~MenuItem() { } -void NetHackQtMenuWindow::AddMenu(int glyph, const ANY_P* identifier, - char ch, char gch, int attr, const QString& str, unsigned itemflags) +void NetHackQtMenuWindow::AddMenu(int glyph, const ANY_P *identifier, + char ch, char gch, int attr, + const QString& str, unsigned itemflags) { bool presel = (itemflags & MENU_ITEMFLAGS_SELECTED) != 0; if (!ch && identifier->a_void!=0) { // Supply a keyboard accelerator. Limited supply. - static char accel[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + static char accel[] + = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; if (accel[next_accel]) { ch=accel[next_accel++]; } @@ -173,15 +190,15 @@ void NetHackQtMenuWindow::AddMenu(int glyph, const ANY_P* identifier, if ((int)itemlist.size() < itemcount+1) { itemlist.resize(itemcount*4+10); } - itemlist[itemcount].glyph=glyph; - itemlist[itemcount].identifier=*identifier; - itemlist[itemcount].ch=ch; - itemlist[itemcount].gch=gch; - itemlist[itemcount].attr=attr; - itemlist[itemcount].str=str; - itemlist[itemcount].selected=presel; - itemlist[itemcount].itemflags=itemflags; - itemlist[itemcount].count=-1; + itemlist[itemcount].glyph = glyph; + itemlist[itemcount].identifier = *identifier; + itemlist[itemcount].ch = ch; + itemlist[itemcount].gch = gch; + itemlist[itemcount].attr = attr; + itemlist[itemcount].str = str; + itemlist[itemcount].selected = itemlist[itemcount].preselected = presel; + itemlist[itemcount].itemflags = itemflags; + itemlist[itemcount].count = -1L; itemlist[itemcount].color = -1; // Display the boulder symbol correctly if (str.left(8) == "boulder\t") { @@ -193,14 +210,15 @@ void NetHackQtMenuWindow::AddMenu(int glyph, const ANY_P* identifier, } } int mcolor, mattr; - if (attr == 0 + if (attr == 0 && ::iflags.use_menu_color && get_menu_coloring(str.toLatin1().constData(), &mcolor, &mattr)) { itemlist[itemcount].attr = mattr; itemlist[itemcount].color = mcolor; } ++itemcount; - if (glyph!=NO_GLYPH) has_glyphs=true; + if (glyph != NO_GLYPH) + has_glyphs = true; } void NetHackQtMenuWindow::EndMenu(const QString& p) @@ -215,136 +233,247 @@ int NetHackQtMenuWindow::SelectMenu(int h, MENU_ITEM_P **menu_list) how=h; - ok->setEnabled(how!=PICK_ONE); ok->setDefault(how!=PICK_ONE); + int presel_ct = 0; + for (int i = 0; i < itemcount; ++i) + if (itemlist[i].preselected) + ++presel_ct; + bool ok_ok = (how != PICK_ONE || presel_ct == 1); + ok->setEnabled(ok_ok); ok->setDefault(ok_ok); cancel->setEnabled(true); - all->setEnabled(how==PICK_ANY); - none->setEnabled(how==PICK_ANY); - invert->setEnabled(how==PICK_ANY); - search->setEnabled(how!=PICK_NONE); + all->setEnabled(how == PICK_ANY || (how == PICK_ONE && itemcount == 1)); + none->setEnabled(how == PICK_ANY || (how == PICK_ONE && presel_ct == 1)); + invert->setEnabled(how == PICK_ANY || (how == PICK_ONE && itemcount == 1)); + search->setEnabled(how != PICK_NONE); setResult(-1); // Set contents of table QFontMetrics fm(table->font()); - for (int i = 0; i < 5; i++) { - table->setColumnWidth(i, 0); + for (int col = 0; col < 5; ++col) { + table->setColumnWidth(col, 0); } - for (int i = 0; i < itemcount; i++) { - AddRow(i, itemlist[i]); + // default height is way too big; rows end up being double-spaced with it + int rowheight = fm.height() * 3 / 5; + for (int row = 0; row < itemcount; ++row) { + AddRow(row, itemlist[row]); + table->setRowHeight(row, rowheight); } + PadMenuColumns(::iflags.menu_tab_sep ? true : false); -#define MENU_WIDTH_SLOP 10 /* this should not be necessary */ - // Determine column widths - std::vector col_widths; - for (std::size_t i = 0; i < (size_t) itemlist.size(); ++i) { - QStringList columns = itemlist[i].str.split("\t"); - if (!itemlist[i].Selectable() && columns.size() == 1) { - // Nonselectable line with no column dividers. - // Assume this is a section header - // or ordinary text (^X feedback, for instance) rendered in - // a menu because tty's paginated menus can be used to go - // backward, unlike text windows which can only go forward. - QTableWidgetItem *twi = table->item(i, 4); - if (twi) { - QString text = twi->text(); - WidenColumn(4, fm.width(text)); - } - continue; - } - for (std::size_t j = 0U; j < (size_t) columns.size(); ++j) { - int w = fm.width(columns[j] + " \t"); - if (j >= col_widths.size()) { - col_widths.push_back(w); - } else if (col_widths[j] < w) { - col_widths[j] = w; - } - } - } - - // Pad each column to its column width - for (std::size_t i = 0U; i < (size_t) itemlist.size(); ++i) { - QTableWidgetItem *twi = table->item(i, 4); - if (twi == NULL) { continue; } - QString text = twi->text(); - QStringList columns = text.split("\t"); - for (std::size_t j = 0U; j+1U < (size_t) columns.size(); ++j) { - columns[j] += "\t"; - int width = col_widths[j]; - while (fm.width(columns[j]) < width) { - columns[j] += "\t"; - } - } - text = columns.join(""); - twi->setText(text); - WidenColumn(4, fm.width(text) + MENU_WIDTH_SLOP); - } - - // FIXME: size for compact mode + //old FIXME: size for compact mode //resize(this->width(), parent()->height()*7/8); move(0, 0); adjustSize(); centerOnMain(this); - exec(); - int result=this->result(); - *menu_list=0; - if (result>0 && how!=PICK_NONE) { - if (how==PICK_ONE) { - int i; - for (i=0; iresult(); + + *menu_list = (MENU_ITEM_P *) 0; + if (result > 0 && how != PICK_NONE) { + int n = 0; + for (int i = 0; i < itemcount; ++i) + if (itemlist[i].selected) + ++n; + if (n) { + *menu_list = (MENU_ITEM_P *) alloc(n * sizeof(MENU_ITEM_P)); + n = 0; + for (int i = 0; i < itemcount; ++i) { + if (itemlist[i].selected) { + (*menu_list)[n].item = itemlist[i].identifier; + (*menu_list)[n].count = count(i); + ++n; + } + } + } + return n; } else { return -1; } } +// pad the menu columns so that they all line up +void NetHackQtMenuWindow::PadMenuColumns(bool split_descr) +{ + QFontMetrics fm(table->font()); + QString col0width_str = ""; + if (biggestcount > 0L) + col0width_str.sprintf("%*ld", std::max(countdigits, 1), biggestcount); + int col0width_int = (int) fm.width(col0width_str) + MENU_WIDTH_SLOP; + if (col0width_int > table->columnWidth(0)) + WidenColumn(0, col0width_int); + + // If the description (column 4) is a tab separated list, split + // it into fields and figure out how wide each field should be. + // Needs to be done at most once for any given menu instantiation. + std::vector col_widths(1, 0); // start with 1 element init'd to 0 + if (split_descr) { + for (int row = 0; row < itemcount; ++row) { + QTableWidgetItem *twi = table->item(row, 4); // description + if (twi == NULL) + continue; + // determine column widths of sub-fields within description + QStringList columns = itemlist[row].str.split("\t"); + for (int fld = 0; fld < (int) columns.size(); ++fld) { + bool lastcol = (fld == (int) columns.size() - 1); + int w = fm.width(columns[fld] + (lastcol ? "" : " ")); + if (fld >= (int) col_widths.size()) { + col_widths.push_back(w); // add another element + } else if (col_widths[fld] < w) { + col_widths[fld] = w; + } + } + } + } // split_descr + + // Reformat all counts so that they line up right justified and + // pad each field within description to fill that field's width. + int widest4 = 0; + for (int row = 0; row < (int) itemlist.size(); ++row) { + // column 0 (subset count); format as right-justified number + QTableWidgetItem *cnt = table->item(row, 0); + if (cnt != NULL) { + QString Amt = ""; + long amt = count(row); // fetch item(i,0) as a number + if (amt > 0L) + Amt.sprintf("%*ld", countdigits, amt); + cnt->setText(Amt); + } + + // column 4 (item description) + QTableWidgetItem *twi = table->item(row, 4); + if (twi == NULL) + continue; + QString text = twi->text(); + if (split_descr) { + QStringList columns = text.split("\t"); + for (int fld = 0; fld < (int) columns.size() - 1; ++fld) { + //columns[j] += "\t"; + int width = col_widths[fld]; + while (fm.width(columns[fld]) < width) + columns[fld] += " "; //"\t"; + } + text = columns.join(""); + twi->setText(text); + } + // TODO? if description needs to wrap, increase the height of this row + int wid = fm.width(text) + MENU_WIDTH_SLOP; + if (wid > widest4) + widest4 = wid; + } + if (widest4 > table->columnWidth(4)) + WidenColumn(4, widest4); +} + +// got a bigger count than previously; might need to widen column 0 +// (or possibly had all counts removed and need to shrink column 0) +void NetHackQtMenuWindow::UpdateCountColumn(long newcount) +{ + if (newcount < 0L) { + // this will happen if user clicks on [all],[none],[invert] buttons; + // they clear all pending counts while selecting or unselecting + biggestcount = 0L; + countdigits = 0; + table->setColumnWidth(0, 0); + WidenColumn(0, MENU_WIDTH_SLOP); + } else { + biggestcount = std::max(biggestcount, newcount); + QString num; + num.sprintf("%*ld", std::max(countdigits, 1), biggestcount); + int numlen = (int) num.length(); + if (numlen % 2) + ++numlen; + if (numlen <= countdigits) + return; + countdigits = numlen; + // FIXME: neither adjusting the table size (below) nor the + // menu widget size (also below) is making the menu widget + // bigger after the count column has been expanded + } + + PadMenuColumns(false); + + // Temporary? workaround for scrolling becoming wedged if using + // all/none/invert removes all counts so we narrow a non-empty + // count column to empty. [That can take away the horizontal + // scroll bar but should not be affecting the vertical one, yet + // is (Qt 5.11.3).] Typing any digit restored normal scrolling + // and the only significant thing about that is that it updates + // the prompt line which is outside the table of menu items where + // scrolling takes place. Oddly, both prompt changes are needed + // (possibly the unnecessary space in the first is being optimized + // away but the second call to remove it isn't aware of that). + prompt.setText(promptstr + " "); + prompt.setText(promptstr); + + // when this was just 'adjustSize()', our sizeHints() was not + // being called so explicitly indicate the table widget + table->adjustSize(); + this->adjustSize(); + table->repaint(); +} + +struct qcolor { + QColor q; + const char *nm; +}; +// these match the tty colors, or better versions of same; +// [0] is used for black, and [8] (the first white) corresponds to "no color" +static const struct qcolor colors[] = { + { QColor(64, 64, 64), "64,64,64" }, // black + { QColor(Qt::red), "red" }, + { QColor(0, 191, 0), "0,191,0" }, // green + { QColor(127, 127, 0), "127,127,0" }, // brownish + { QColor(Qt::blue), "blue" }, + { QColor(Qt::magenta), "magenta" }, + { QColor(Qt::cyan), "cyan" }, + { QColor(Qt::gray), "gray" }, + // on tty, the "light" variations are "bright" instead; here they're paler + { QColor(Qt::white), "white" }, // no-color, so not rendered + { QColor(255, 127, 0), "255,127,0" }, // orange + { QColor(127, 255, 127), "127,255,127" }, // light green + { QColor(Qt::yellow), "yellow" }, + { QColor(127, 127, 255), "127,127,255" }, // light blue + { QColor(255, 127, 255), "255,127,255" }, // light magenta + { QColor(127, 255, 255), "127,255,255" }, // light cyan + { QColor(Qt::white), "white" }, +}; + +#if 0 /* available for debugging */ +static const char *color_name(const QColor q) +{ + for (int i = 0; i < SIZE(colors); ++i) + if (q == colors[i].q) + return colors[i].nm; + // these are all the enum GlobalColor values ; + // black and white have been moved in front of color0 and color1 here + const char *nm = (q == Qt::black) ? "black" + : (q == Qt::white) ? "white" + : (q == Qt::color0) ? "color0" // doesn't duplicate white? + : (q == Qt::color1) ? "color1" // does duplicate black + : (q == Qt::darkGray) ? "darkGray" + : (q == Qt::gray) ? "gray" + : (q == Qt::lightGray) ? "lightGray" + : (q == Qt::red) ? "red" + : (q == Qt::green) ? "green" + : (q == Qt::blue) ? "blue" + : (q == Qt::cyan) ? "cyan" + : (q == Qt::magenta) ? "magenta" + : (q == Qt::yellow) ? "yellow" + : (q == Qt::darkRed) ? "darkRed" + : (q == Qt::darkGreen) ? "darkGreen" + : (q == Qt::darkBlue) ? "darkBlue" + : (q == Qt::darkCyan) ? "darkCyan" + : (q == Qt::darkMagenta) ? "darkMagenta" + : (q == Qt::darkYellow) ? "darkYellow" + : (q == Qt::transparent) ? "transparent" + : "other"; + return nm; +} +#endif + void NetHackQtMenuWindow::AddRow(int row, const MenuItem& mi) { - static const QColor colors[] = { - QColor(64, 64, 64), - QColor(Qt::red), - QColor(0, 191, 0), - QColor(127, 127, 0), - QColor(Qt::blue), - QColor(Qt::magenta), - QColor(Qt::cyan), - QColor(Qt::gray), - QColor(Qt::white), - QColor(255, 127, 0), - QColor(127, 255, 127), - QColor(Qt::yellow), - QColor(127, 127, 255), - QColor(255, 127, 255), - QColor(127, 255, 255), - QColor(Qt::white) - }; QFontMetrics fm(table->font()); QTableWidgetItem *twi; @@ -353,13 +482,19 @@ void NetHackQtMenuWindow::AddRow(int row, const MenuItem& mi) twi = new QTableWidgetItem(""); table->setItem(row, 0, twi); twi->setFlags(Qt::ItemIsEnabled); - WidenColumn(0, fm.width("999999")); - // Check box, set if selected +#if 0 // active count field now widened as needed rather than preset + WidenColumn(0, fm.width("999999") + MENU_WIDTH_SLOP); +#else + WidenColumn(0, MENU_WIDTH_SLOP); +#endif + + // Check box, set if pre-selected QCheckBox *cb = new QCheckBox(); - cb->setChecked(mi.selected); + cb->setChecked(mi.preselected ? Qt::Checked : Qt::Unchecked); cb->setFocusPolicy(Qt::NoFocus); - if (how == PICK_ONE) - connect(cb, SIGNAL(clicked(bool)), this, SLOT(DoSelection(bool))); + // CheckboxClicked() will call ToggleSelect() for item whose checkbox + // gets clicked upon + connect(cb, SIGNAL(clicked(bool)), this, SLOT(CheckboxClicked(bool))); table->setCellWidget(row, 1, cb); WidenColumn(1, cb->width()); } @@ -371,12 +506,12 @@ void NetHackQtMenuWindow::AddRow(int row, const MenuItem& mi) twi->setFlags(Qt::ItemIsEnabled); WidenColumn(2, pm.width()); } + QString letter, text(mi.str); if (mi.ch != 0) { // Letter specified letter = QString(mi.ch) + " - "; - } - else { + } else { // Letter is left blank, except for skills display when # and * are // presented if (text.startsWith(" ")) { @@ -393,47 +528,54 @@ void NetHackQtMenuWindow::AddRow(int row, const MenuItem& mi) table->setItem(row, 3, twi); table->item(row, 3)->setFlags(Qt::ItemIsEnabled); WidenColumn(3, fm.width(letter)); + twi = new QTableWidgetItem(text); table->setItem(row, 4, twi); table->item(row, 4)->setFlags(Qt::ItemIsEnabled); WidenColumn(4, fm.width(text)); if ((int) mi.color != -1) { - twi->setForeground(colors[mi.color]); + twi->setForeground(colors[mi.color].q); } - QFont itemfont(table->font()); - switch (mi.attr) { - case ATR_BOLD: - itemfont.setWeight(QFont::Bold); - twi->setFont(itemfont); - break; - - case ATR_DIM: - twi->setFlags(Qt::NoItemFlags); - break; - - case ATR_ULINE: - itemfont.setUnderline(true); - twi->setFont(itemfont); - break; - - case ATR_INVERSE: - { - QBrush fg = twi->foreground(); - QBrush bg = twi->background(); - if (fg == bg) { - // default foreground and background come up the same for - // some unknown reason - twi->setForeground(Qt::white); - twi->setBackground(Qt::black); - } else { - twi->setForeground(bg); - twi->setBackground(fg); - } - } - break; - } + if (mi.attr != ATR_NONE) { + QFont itemfont(table->font()); + switch (mi.attr) { + case ATR_BOLD: + itemfont.setWeight(QFont::Bold); + twi->setFont(itemfont); + break; + case ATR_DIM: + twi->setFlags(Qt::NoItemFlags); + break; + case ATR_ULINE: + itemfont.setUnderline(true); + twi->setFont(itemfont); + break; + case ATR_INVERSE: { + QBrush fg = twi->foreground(); + QBrush bg = twi->background(); + if (fg.color() == bg.color()) { + // default foreground and background come up the same for + // some unknown reason + //[pr: both are set to 'Qt::color1' which has same RGB + // value as 'Qt::black'; X11 on OSX behaves similarly] + if (fg.color() == Qt::color1) { + fg = Qt::black; + bg = Qt::white; + } else { + fg = (bg.color() == Qt::white) ? Qt::black : Qt::white; + } + } + twi->setForeground(bg); + twi->setBackground(fg); + break; + } + case ATR_BLINK: + // not supported + break; + } /* switch */ + } /* if mi.attr != ATR_NONE */ } void NetHackQtMenuWindow::WidenColumn(int column, int width) @@ -447,18 +589,14 @@ void NetHackQtMenuWindow::WidenColumn(int column, int width) void NetHackQtMenuWindow::InputCount(char key) { - if (key == '\b') - { - if (counting) - { + if (key == '\b' || key == '\177') { + if (counting) { if (countstr.isEmpty()) ClearCount(); else countstr = countstr.mid(0, countstr.size() - 1); } - } - else - { + } else { counting = true; countstr += QChar(key); } @@ -473,162 +611,260 @@ void NetHackQtMenuWindow::ClearCount(void) countstr = ""; } -void NetHackQtMenuWindow::keyPressEvent(QKeyEvent* event) +void NetHackQtMenuWindow::keyPressEvent(QKeyEvent *key_event) { - QString text = event->text(); + // key_event manipulation derived from NetHackQtBind::notify() + const int k = key_event->key(); + Qt::KeyboardModifiers mod = key_event->modifiers(); + QChar ch = !key_event->text().isEmpty() ? key_event->text().at(0) : 0; + if (ch > 128) + ch = 0; + // on OSX, ascii control codes are not sent, force them + if (ch == 0 && (mod & Qt::ControlModifier) != 0) { + if (k >= Qt::Key_A && k <= Qt::Key_Underscore) + ch = (QChar) (k - (Qt::Key_A - 1)); + } + uchar key = (char) ch.cell(); - const QChar *uni = text.unicode(); - for (unsigned k = 0; uni[k] != 0; k++) { - unsigned key = uni[k].unicode(); - if (key=='\033') { - if (counting) - ClearCount(); - else - reject(); - } else if (key=='\r' || key=='\n' || key==' ') - accept(); - else if (key==MENU_SEARCH) - Search(); - else if (key==MENU_SELECT_ALL || key==MENU_SELECT_PAGE) - All(); - else if (key==MENU_INVERT_ALL || key==MENU_INVERT_PAGE) - Invert(); - else if (key==MENU_UNSELECT_ALL || key==MENU_UNSELECT_PAGE) - ChooseNone(); - else if (('0' <= key && key <= '9') || key == '\b') - InputCount(key); - else { - for (int i=0; iitem(i, 0); - if (count != NULL) count->setText(""); + if (counting) + ClearCount(); // discard any pending count + for (int row = 0; row < itemcount; ++row) { + itemlist[row].selected = itemlist[row].preselected = false; + if (!itemlist[row].Selectable()) + continue; + itemlist[row].selected = true; - QCheckBox *cb = dynamic_cast(table->cellWidget(i, 1)); - if (cb != NULL) { - cb->setChecked(true); - didcheck = true; + QTableWidgetItem *count = table->item(row, 0); + if (count != NULL) { + count->setText(""); + } + QCheckBox *cb = dynamic_cast (table->cellWidget(row, 1)); + if (cb != NULL) { + cb->setChecked(Qt::Checked); } } - if (didcheck) + if (biggestcount > 0L) { // counts got cleared when entries became checked + UpdateCountColumn(-1L); + } else { table->repaint(); + } } + void NetHackQtMenuWindow::ChooseNone() { - if (how != PICK_ANY) + if (how == PICK_NONE) return; - bool diduncheck = false; - for (int i=0; iitem(i, 0); - if (count != NULL) count->setText(""); - - QCheckBox *cb = dynamic_cast(table->cellWidget(i, 1)); - if (cb != NULL) { - cb->setChecked(false); - diduncheck = true; - } - } - if (diduncheck) - table->repaint(); -} -void NetHackQtMenuWindow::Invert() -{ - if (how != PICK_ANY) - return; - - boolean didtoggle = false; - for (int i=0; iitem(i, 0); - if (count != NULL) count->setText(""); - - QCheckBox *cb = dynamic_cast(table->cellWidget(i, 1)); - if (cb != NULL) { - cb->setChecked(cb->checkState() == Qt::Unchecked); - didtoggle = true; + QTableWidgetItem *count = table->item(row, 0); + if (count != NULL) { + count->setText(""); + } + QCheckBox *cb = dynamic_cast (table->cellWidget(row, 1)); + if (cb != NULL) { + cb->setChecked(Qt::Unchecked); } } - if (didtoggle) + if (biggestcount > 0L) { // all counts have been removed + UpdateCountColumn(-1L); + } else { table->repaint(); + } } + +void NetHackQtMenuWindow::Invert() +{ + if (how == PICK_NONE) + return; + + if (counting) + ClearCount(); // discard any pending count + for (int row = 0; row < itemcount; ++row) { + if (!menuitem_invert_test(0, itemlist[row].itemflags, + itemlist[row].preselected)) + continue; + ToggleSelect(row, false); + } + if (biggestcount > 0L) { // all entries with counts are now unchecked + UpdateCountColumn(-1L); + } else { + table->repaint(); + } +} + void NetHackQtMenuWindow::Search() { if (how == PICK_NONE) return; + searching = true; NetHackQtStringRequestor requestor(this, "Search for:"); char line[256]; line[0] = '\0'; /* for EDIT_GETLIN */ if (requestor.Get(line)) { for (int i=0; i(table->cellWidget(i, 1)); - if (cb == NULL) return; + searching = false; +} - cb->setChecked((counting && !countstr.isEmpty()) - || cb->checkState() == Qt::Unchecked); +void NetHackQtMenuWindow::ToggleSelect(int row, bool already_checked) +{ + if (itemlist[row].Selectable()) { + QCheckBox *cb = dynamic_cast (table->cellWidget(row, 1)); + if (cb == NULL) + return; - QTableWidgetItem *count = table->item(i, 0); - if (count != NULL) count->setText(countstr); - - ClearCount(); - - if (how==PICK_ONE) { - accept(); + if (how == PICK_ONE) { + // explicitly picking a preselected item in a pick-one menu + // chooses that item rather than toggling preselection off; + // by clearing whole menu, the code below will select item #i + ChooseNone(); + already_checked = false; + // FIXME? this won't handle a pending count properly; + // are there any pick-one menus with preselected choice + // where a count is useful? } else { + itemlist[row].preselected = false; // flag is now out-of-date + } + + QTableWidgetItem *countfield = table->item(row, 0); + if (!counting) { + if (!already_checked) + cb->setChecked((cb->checkState() == Qt::Unchecked) // toggle + ? Qt::Checked : Qt::Unchecked); + itemlist[row].selected = (cb->checkState() != Qt::Unchecked); + if (countfield != NULL) + countfield->setText(""); + } else { + long amt = 1L; + if (countfield != NULL) { + countfield->setText(countstr); // store in item(row,0) + amt = count(row); // fetch item(row,0) as a number + QString Amt = ""; + if (amt > 0L) + Amt.sprintf("%*ld", countdigits, amt); + countfield->setText(Amt); // store right-justified value + } + ClearCount(); // blank out 'countstr' and reset 'counting' + + // TODO: use a custom icon for check mark, like tty's '#' vs '+' + // [maybe not necessary since unlike tty menus, count is visible] + + // uncheck if count is explicitly 0, otherwise check + cb->setChecked((amt > 0L) ? Qt::Checked : Qt::Unchecked); + itemlist[row].selected = (cb->checkState() != Qt::Unchecked); + + // if this count is larger than the biggest we've seen + // so far, it might need more digits to render; if so, + // all pending counts should be reformatted with new width + if (amt > biggestcount) + UpdateCountColumn(amt); + } + + if (how == PICK_ONE && isSelected(row)) + accept(); + else table->repaint(); + } +} + +// table cell click handler for cells (*,col) where col != 1 +void NetHackQtMenuWindow::TableCellClicked(int row, int col UNUSED) +{ + ToggleSelect(row, false); +} + +// checkbox click handler for table cells (*,1); +// presence of a checkbox in the clicked cell prevents the table cell click +// handler above from being called even if this handler doesn't get set up +void NetHackQtMenuWindow::CheckboxClicked(bool on_off UNUSED) +{ + // find which checkbox just got toggled by looking for one whose + // check state doesn't match corresponding itemlist[].selected flag + // [there's got to be a more direct way of achieving this...] + for (int row = 0; row < itemcount; ++row) { + // for pick-one menu, ToggleSelect() will return to menu's caller + if (itemlist[row].Selectable()) { + if (!isSelected(row) ^ !itemlist[row].selected) { + // signal dispatcher has already checked or unchecked this box + ToggleSelect(row, true); + return; + } } } -} - -void NetHackQtMenuWindow::cellToggleSelect(int i, int j UNUSED) -{ - ToggleSelect(i); -} - -void NetHackQtMenuWindow::DoSelection(bool) -{ - if (how == PICK_ONE) { - accept(); - } + // didn't find any changed checkbox; should never happen; what to do? } bool NetHackQtMenuWindow::isSelected(int row) { - QCheckBox *cb = dynamic_cast(table->cellWidget(row, 1)); - return cb != NULL && cb->checkState() != Qt::Unchecked; + QCheckBox *cb = dynamic_cast (table->cellWidget(row, 1)); + return cb ? (cb->checkState() != Qt::Unchecked) : false; } -int NetHackQtMenuWindow::count(int row) +// convert text count to numeric for final result +long NetHackQtMenuWindow::count(int row) { QTableWidgetItem *count = table->item(row, 0); - if (count == NULL) return -1; + if (count == NULL) + return -1L; QString cstr = count->text(); - return cstr.isEmpty() ? -1 : cstr.toInt(); + return cstr.isEmpty() ? -1L : cstr.toLong(); } NetHackQtTextWindow::NetHackQtTextWindow(QWidget *parent) : diff --git a/win/Qt/qt_menu.h b/win/Qt/qt_menu.h index 7aa5c2b8d..a6aadf51c 100644 --- a/win/Qt/qt_menu.h +++ b/win/Qt/qt_menu.h @@ -10,6 +10,9 @@ #include "qt_win.h" #include "qt_rip.h" +// some menu fields aren't wide enough even though sized for measured text +#define MENU_WIDTH_SLOP 10 /* this should not be necessary */ + namespace nethack_qt_ { class NetHackQtTextListBox : public QListWidget { @@ -54,8 +57,9 @@ public: virtual QWidget* Widget(); virtual void StartMenu(); - virtual void AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr, - const QString& str, unsigned itemflags); + virtual void AddMenu(int glyph, const ANY_P *identifier, + char ch, char gch, int attr, + const QString& str, unsigned itemflags); virtual void EndMenu(const QString& prompt); virtual int SelectMenu(int how, MENU_ITEM_P **menu_list); @@ -65,9 +69,9 @@ public slots: void Invert(); void Search(); - void ToggleSelect(int); - void cellToggleSelect(int, int); - void DoSelection(bool); + void ToggleSelect(int row, bool alyready_checked); + void TableCellClicked(int row, int col); + void CheckboxClicked(bool on_off); protected: virtual void keyPressEvent(QKeyEvent*); @@ -81,10 +85,11 @@ private: ANY_P identifier; int attr; QString str; - int count; + long count; char ch; char gch; - bool selected; + bool selected; // True if checkbox is set + bool preselected; // True if caller told us to set checkbox unsigned itemflags; unsigned color; @@ -108,19 +113,25 @@ private: // Count replaces prompt while it is being input QString promptstr; QString countstr; - bool counting; + long biggestcount; // determines width of field #0 + int countdigits; // number of digits to format biggestcount + bool counting; // in midst of entering a count + bool searching; // in midst of entering a search string void InputCount(char key); void ClearCount(void); - int how; - - bool has_glyphs; + int how; // pick-none, pick-one, pick-any + bool has_glyphs; // at least one item specified a glyph bool isSelected(int row); - int count(int row); + long count(int row); void AddRow(int row, const MenuItem& mi); void WidenColumn(int column, int width); + void PadMenuColumns(bool split_descr); + void UpdateCountColumn(long newcount); + + void ClearSearch(); }; class NetHackQtTextWindow : public QDialog, public NetHackQtWindow { @@ -172,8 +183,9 @@ public: // Menu virtual void StartMenu(); - virtual void AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr, - const QString& str, unsigned itemflags); + virtual void AddMenu(int glyph, const ANY_P *identifier, + char ch, char gch, int attr, + const QString& str, unsigned itemflags); virtual void EndMenu(const QString& prompt); virtual int SelectMenu(int how, MENU_ITEM_P **menu_list);