// 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"
}
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_ {
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
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();
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);
}
{
}
-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++];
}
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") {
}
}
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)
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 i = 0; i < itemcount; i++) {
- AddRow(i, itemlist[i]);
- }
-
-#define MENU_WIDTH_SLOP 10 /* this should not be necessary */
- // Determine column widths
- std::vector<int> 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;
- }
- }
+ for (int col = 0; col < 5; ++col) {
+ table->setColumnWidth(col, 0);
}
-
- // 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);
+ // 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);
- // 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; i<itemcount && !isSelected(i); i++)
- ;
- if (i<itemcount) {
- *menu_list=(MENU_ITEM_P *)alloc(sizeof(MENU_ITEM_P)*1);
- (*menu_list)[0].item=itemlist[i].identifier;
- (*menu_list)[0].count=count(i);
- return 1;
- } else {
- return 0;
- }
- } else {
- int selcount=0;
- for (int i=0; i<itemcount; i++)
- if (isSelected(i)) selcount++;
- if (selcount) {
- *menu_list=(MENU_ITEM_P *)alloc(sizeof(MENU_ITEM_P)*selcount);
- int j=0;
- for (int i=0; i<itemcount; i++) {
- if (isSelected(i)) {
- (*menu_list)[j].item=itemlist[i].identifier;
- (*menu_list)[j].count=count(i);
- j++;
- }
- }
- return selcount;
- } else {
- return 0;
- }
- }
+ int result = this->result();
+
+ *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<int> 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 <qt5/QtCore/qnamespace.h>;
+ // 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;
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());
}
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(" ")) {
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]);
- }
-
- 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;
+ twi->setForeground(colors[mi.color].q);
}
+
+ 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)
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);
}
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();
+
+ // only one possible match for key==ch, and if one occurs it takes
+ // precedence over any other match (for instance, some menus might
+ // contain '-' to represent bare hands vs '-' for menu_unselect_page,
+ // or ':' to look into a container vs ':' for menu_search)
+ for (int row = 0; row < itemcount; ++row)
+ if (key == (uchar) itemlist[row].ch) {
+ ToggleSelect(row, false);
+ return;
+ }
- 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; i<itemcount; i++) {
- if ((unsigned int) itemlist[i].ch == key
- || (unsigned int) itemlist[i].gch == key)
- ToggleSelect(i);
- }
- }
+ if (key == '\033') {
+ if (counting)
+ ClearCount();
+ else if (searching)
+ ClearSearch();
+ else
+ reject();
+ } else if (key == '\r' || key == '\n' || key == ' ') {
+ accept();
+ } else if (('0' <= key && key <= '9') || key == '\b' || key == '\177') {
+ InputCount(key);
+ } 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 (key == MENU_SEARCH) {
+ Search();
+ } else {
+ // multiple matches are possible with gch
+ for (int row = 0; row < itemcount; ++row)
+ if (key == (uchar) itemlist[row].gch)
+ ToggleSelect(row, false);
}
}
void NetHackQtMenuWindow::All()
{
- if (how != PICK_ANY)
+ if (how != PICK_ANY && (how != PICK_ONE || itemcount != 1))
return;
- bool didcheck = false;
- for (int i=0; i<itemcount; i++) {
- QTableWidgetItem *count = table->item(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<QCheckBox *>(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<QCheckBox *> (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; i<itemcount; i++) {
- QTableWidgetItem *count = table->item(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;
- QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(i, 1));
- if (cb != NULL) {
- cb->setChecked(false);
- diduncheck = true;
+ QTableWidgetItem *count = table->item(row, 0);
+ if (count != NULL) {
+ count->setText("");
+ }
+ QCheckBox *cb = dynamic_cast<QCheckBox *> (table->cellWidget(row, 1));
+ if (cb != NULL) {
+ cb->setChecked(Qt::Unchecked);
}
}
- if (diduncheck)
+ if (biggestcount > 0L) { // all counts have been removed
+ UpdateCountColumn(-1L);
+ } else {
table->repaint();
+ }
}
+
void NetHackQtMenuWindow::Invert()
{
- if (how != PICK_ANY)
+ if (how == PICK_NONE)
return;
- boolean didtoggle = false;
- for (int i=0; i<itemcount; i++) {
- if (!menuitem_invert_test(0, itemlist[i].itemflags,
- itemlist[i].selected))
+ 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;
-
- QTableWidgetItem *count = table->item(i, 0);
- if (count != NULL) count->setText("");
-
- QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(i, 1));
- if (cb != NULL) {
- cb->setChecked(cb->checkState() == Qt::Unchecked);
- didtoggle = true;
- }
+ ToggleSelect(row, false);
}
- if (didtoggle)
+ 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<itemcount; i++) {
if (itemlist[i].str.contains(line))
- ToggleSelect(i);
+ ToggleSelect(i, false);
}
}
+ searching = false;
+}
+
+void NetHackQtMenuWindow::ClearSearch()
+{
+ searching = false;
}
-void NetHackQtMenuWindow::ToggleSelect(int i)
+
+void NetHackQtMenuWindow::ToggleSelect(int row, bool already_checked)
{
- if (itemlist[i].Selectable()) {
- QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(i, 1));
- if (cb == NULL) return;
+ if (itemlist[row].Selectable()) {
+ QCheckBox *cb = dynamic_cast<QCheckBox *> (table->cellWidget(row, 1));
+ if (cb == NULL)
+ return;
+
+ 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'
- cb->setChecked((counting && !countstr.isEmpty())
- || cb->checkState() == Qt::Unchecked);
+ // TODO: use a custom icon for check mark, like tty's '#' vs '+'
+ // [maybe not necessary since unlike tty menus, count is visible]
- QTableWidgetItem *count = table->item(i, 0);
- if (count != NULL) count->setText(countstr);
+ // uncheck if count is explicitly 0, otherwise check
+ cb->setChecked((amt > 0L) ? Qt::Checked : Qt::Unchecked);
+ itemlist[row].selected = (cb->checkState() != Qt::Unchecked);
- ClearCount();
+ // 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) {
- accept();
- } else {
+ if (how == PICK_ONE && isSelected(row))
+ accept();
+ else
table->repaint();
- }
}
}
-void NetHackQtMenuWindow::cellToggleSelect(int i, int j UNUSED)
+// table cell click handler for cells (*,col) where col != 1
+void NetHackQtMenuWindow::TableCellClicked(int row, int col UNUSED)
{
- ToggleSelect(i);
+ ToggleSelect(row, false);
}
-void NetHackQtMenuWindow::DoSelection(bool)
+// 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)
{
- if (how == PICK_ONE) {
- accept();
+ // 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;
+ }
+ }
}
+ // didn't find any changed checkbox; should never happen; what to do?
}
bool NetHackQtMenuWindow::isSelected(int row)
{
- QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(row, 1));
- return cb != NULL && cb->checkState() != Qt::Unchecked;
+ QCheckBox *cb = dynamic_cast<QCheckBox *> (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) :