// Copyright (c) Warwick Allison, 1999. // Qt4 conversion copyright (c) Ray Chason, 2012-2014. // NetHack may be freely redistributed. See license for details. // qt_menu.cpp -- a menu or text-list widget extern "C" { #include "hack.h" } #undef Invisible #undef Warning #undef index #undef msleep #undef rindex #undef wizard #undef yn #undef min #undef max #include "qt_pre.h" #include #if QT_VERSION >= 0x050000 #include #endif #include "qt_post.h" #include "qt_menu.h" #include "qt_menu.moc" #include "qt_glyph.h" #include "qt_set.h" #include "qt_streq.h" #include "qt_str.h" // temporary extern "C" int qt_compact_mode; // end temporary extern "C" struct menucoloring *menu_colorings; namespace nethack_qt_ { // temporary void centerOnMain( QWidget* w ); // end temporary QSize NetHackQtTextListBox::sizeHint() const { QScrollBar *hscroll = horizontalScrollBar(); int hsize = hscroll ? hscroll->height() : 0; return QSize(TotalWidth()+hsize, TotalHeight()+hsize); } int NetHackQtMenuListBox::TotalWidth() const { int width = 0; for (int col = 0; col < columnCount(); ++col) { width += columnWidth(col); } return width; } int NetHackQtMenuListBox::TotalHeight() const { int height = 0; for (int row = 0; row < rowCount(); ++row) { height += rowHeight(row); } return height; } QSize NetHackQtMenuListBox::sizeHint() const { QScrollBar *hscroll = horizontalScrollBar(); int hsize = hscroll ? hscroll->height() : 0; return QSize(TotalWidth()+hsize, TotalHeight()+hsize); } // Table view columns: // // [pick-count] [accel] [glyph] [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 // NetHackQtMenuWindow::NetHackQtMenuWindow(QWidget *parent) : QDialog(parent), table(new NetHackQtMenuListBox()), prompt(0), counting(false) { QGridLayout *grid = new QGridLayout(); table->setColumnCount(5); table->setFrameStyle(QFrame::Panel|QFrame::Sunken); table->setLineWidth(2); table->setShowGrid(false); table->horizontalHeader()->hide(); table->verticalHeader()->hide(); ok=new QPushButton("Ok"); connect(ok,SIGNAL(clicked()),this,SLOT(accept())); cancel=new QPushButton("Cancel"); connect(cancel,SIGNAL(clicked()),this,SLOT(reject())); all=new QPushButton("All"); connect(all,SIGNAL(clicked()),this,SLOT(All())); none=new QPushButton("None"); connect(none,SIGNAL(clicked()),this,SLOT(ChooseNone())); invert=new QPushButton("Invert"); connect(invert,SIGNAL(clicked()),this,SLOT(Invert())); search=new QPushButton("Search"); connect(search,SIGNAL(clicked()),this,SLOT(Search())); QPoint pos(0,ok->height()); move(pos); prompt.setParent(this,0); prompt.move(pos); grid->addWidget(ok, 0, 0); grid->addWidget(cancel, 0, 1); grid->addWidget(all, 0, 2); grid->addWidget(none, 0, 3); grid->addWidget(invert, 0, 4); grid->addWidget(search, 0, 5); grid->addWidget(&prompt, 1, 0, 1, 7); grid->addWidget(table, 2, 0, 1, 7); grid->setColumnStretch(6, 1); grid->setRowStretch(2, 1); setFocusPolicy(Qt::StrongFocus); table->setFocusPolicy(Qt::NoFocus); connect(table, SIGNAL(cellClicked(int,int)), this, SLOT(cellToggleSelect(int,int))); setLayout(grid); } NetHackQtMenuWindow::~NetHackQtMenuWindow() { } QWidget* NetHackQtMenuWindow::Widget() { return this; } void NetHackQtMenuWindow::StartMenu() { table->setRowCount((itemcount=0)); next_accel=0; has_glyphs=false; } NetHackQtMenuWindow::MenuItem::MenuItem() : str("") { } NetHackQtMenuWindow::MenuItem::~MenuItem() { } 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"; 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].color = -1; // Display the boulder symbol correctly if (str.left(8) == "boulder\t") { int bracket = str.indexOf('['); if (bracket != -1) { itemlist[itemcount].str = str.left(bracket+1) + QChar(cp437(str.at(bracket+1).unicode())) + str.mid(bracket+2); } } int mcolor, mattr; if (attr == 0 && get_menu_coloring(str.toLatin1().constData(), &mcolor, &mattr)) { itemlist[itemcount].attr = mattr; itemlist[itemcount].color = mcolor; } ++itemcount; if (glyph!=NO_GLYPH) has_glyphs=true; } void NetHackQtMenuWindow::EndMenu(const QString& p) { prompt.setText(p); promptstr = p; } int NetHackQtMenuWindow::SelectMenu(int h, MENU_ITEM_P **menu_list) { QFont tablefont(qt_settings->normalFixedFont()); table->setFont(tablefont); table->setRowCount(itemcount); how=h; ok->setEnabled(how!=PICK_ONE);ok->setDefault(how!=PICK_ONE); cancel->setEnabled(true); all->setEnabled(how==PICK_ANY); none->setEnabled(how==PICK_ANY); invert->setEnabled(how==PICK_ANY); 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]); } // 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 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)); } // 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; ifont()); QTableWidgetItem *twi; if (mi.Selectable() && how != PICK_NONE) { // Count twi = new QTableWidgetItem(""); table->setItem(row, 0, twi); twi->setFlags(Qt::ItemIsEnabled); WidenColumn(0, fm.width("999999")); // Check box, set if selected QCheckBox *cb = new QCheckBox(); cb->setChecked(mi.selected); cb->setFocusPolicy(Qt::NoFocus); if (how == PICK_ONE) connect(cb, SIGNAL(clicked(bool)), this, SLOT(DoSelection(bool))); table->setCellWidget(row, 1, cb); WidenColumn(1, cb->width()); } if (mi.glyph != NO_GLYPH) { // Icon QPixmap pm(qt_settings->glyphs().glyph(mi.glyph)); twi = new QTableWidgetItem(QIcon(pm), ""); table->setItem(row, 2, twi); twi->setFlags(Qt::ItemIsEnabled); WidenColumn(2, pm.width()); } QString letter, text(mi.str); if (mi.ch != 0) { // Letter specified letter = QString(mi.ch) + " - "; } else { // Letter is left blank, except for skills display when # and * are // presented if (text.startsWith(" ")) { // If mi.str starts with " ", it's meant to line up with lines // that have a letter; we don't want that here text = text.mid(4); } else if (text.startsWith(" #") || text.startsWith(" *")) { // Put the * or # in the letter column letter = text.left(4); text = text.mid(4); } } twi = new QTableWidgetItem(letter); 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; } } void NetHackQtMenuWindow::WidenColumn(int column, int width) { // need to add a bit so the whole column displays width += 7; if (table->columnWidth(column) < width) { table->setColumnWidth(column, width); } } void NetHackQtMenuWindow::InputCount(char key) { if (key == '\b') { if (counting) { if (countstr.isEmpty()) ClearCount(); else countstr = countstr.mid(0, countstr.size() - 1); } } else { counting = true; countstr += QChar(key); } if (counting) prompt.setText("Count: " + countstr); } void NetHackQtMenuWindow::ClearCount(void) { counting = false; prompt.setText(promptstr); countstr = ""; } void NetHackQtMenuWindow::keyPressEvent(QKeyEvent* event) { QString text = event->text(); 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(""); QCheckBox *cb = dynamic_cast(table->cellWidget(i, 1)); if (cb != NULL) cb->setChecked(true); } } void NetHackQtMenuWindow::ChooseNone() { if (how != PICK_ANY) return; 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); } } void NetHackQtMenuWindow::Invert() { if (how != PICK_ANY) return; 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); } } void NetHackQtMenuWindow::Search() { if (how == PICK_NONE) return; NetHackQtStringRequestor requestor(this, "Search for:"); char line[256]; if (requestor.Get(line)) { for (int i=0; i(table->cellWidget(i, 1)); if (cb == NULL) return; cb->setChecked((counting && !countstr.isEmpty()) || cb->checkState() == Qt::Unchecked); QTableWidgetItem *count = table->item(i, 0); if (count != NULL) count->setText(countstr); ClearCount(); if (how==PICK_ONE) { accept(); } } } void NetHackQtMenuWindow::cellToggleSelect(int i, int j UNUSED) { ToggleSelect(i); } void NetHackQtMenuWindow::DoSelection(bool) { if (how == PICK_ONE) { accept(); } } bool NetHackQtMenuWindow::isSelected(int row) { QCheckBox *cb = dynamic_cast(table->cellWidget(row, 1)); return cb != NULL && cb->checkState() != Qt::Unchecked; } int NetHackQtMenuWindow::count(int row) { QTableWidgetItem *count = table->item(row, 0); if (count == NULL) return -1; QString cstr = count->text(); return cstr.isEmpty() ? -1 : cstr.toInt(); } NetHackQtTextWindow::NetHackQtTextWindow(QWidget *parent) : QDialog(parent), use_rip(false), str_fixed(false), ok("Dismiss",this), search("Search",this), lines(new NetHackQtTextListBox(this)), rip(this) { ok.setDefault(true); connect(&ok,SIGNAL(clicked()),this,SLOT(accept())); connect(&search,SIGNAL(clicked()),this,SLOT(Search())); connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(doUpdate())); QVBoxLayout* vb = new QVBoxLayout(this); vb->addWidget(&rip); QHBoxLayout* hb = new QHBoxLayout(); vb->addLayout(hb); hb->addWidget(&ok); hb->addWidget(&search); vb->addWidget(lines); } void NetHackQtTextWindow::doUpdate() { update(); } NetHackQtTextWindow::~NetHackQtTextWindow() { } QWidget* NetHackQtTextWindow::Widget() { return this; } bool NetHackQtTextWindow::Destroy() { return !isVisible(); } void NetHackQtTextWindow::UseRIP(int how, time_t when UNUSED) { // Code from X11 windowport #define STONE_LINE_LEN 16 /* # chars that fit on one line */ #define NAME_LINE 0 /* line # for player name */ #define GOLD_LINE 1 /* line # for amount of gold */ #define DEATH_LINE 2 /* line # for death description */ #define YEAR_LINE 6 /* line # for year */ static char** rip_line=0; if (!rip_line) { rip_line=new char*[YEAR_LINE+1]; for (int i=0; i STONE_LINE_LEN) { for(i = STONE_LINE_LEN; ((i0 > STONE_LINE_LEN) && i); i--) if(dpx[i] == ' ') i0 = i; if(!i) i0 = STONE_LINE_LEN; } tmpchar = dpx[i0]; dpx[i0] = 0; str_copy(rip_line[line], dpx, STONE_LINE_LEN+1); if (tmpchar != ' ') { dpx[i0] = tmpchar; dpx= &dpx[i0]; } else dpx= &dpx[i0+1]; } /* Put year on stone */ snprintf(rip_line[YEAR_LINE], STONE_LINE_LEN+1, "%4d", getyear()); rip.setLines(rip_line,YEAR_LINE+1); use_rip=true; } void NetHackQtTextWindow::Clear() { lines->clear(); use_rip=false; str_fixed=false; } void NetHackQtTextWindow::Display(bool block UNUSED) { if (str_fixed) { lines->setFont(qt_settings->normalFixedFont()); } else { lines->setFont(qt_settings->normalFont()); } int h=0; if (use_rip) { h+=rip.height(); ok.hide(); search.hide(); rip.show(); } else { h+=ok.height()*2 + 7; ok.show(); search.show(); rip.hide(); } int mh = QApplication::desktop()->height()*3/5; if ( (qt_compact_mode && lines->TotalHeight() > mh) || use_rip ) { // big, so make it fill showMaximized(); } else { move(0, 0); adjustSize(); centerOnMain(this); show(); } exec(); } void NetHackQtTextWindow::PutStr(int attr UNUSED, const QString& text) { str_fixed=str_fixed || text.contains(" "); lines->addItem(text); } void NetHackQtTextWindow::Search() { NetHackQtStringRequestor requestor(this, "Search for:"); static char line[256]=""; requestor.SetDefault(line); if (requestor.Get(line)) { int current=lines->currentRow(); for (int i=1; icount(); i++) { int lnum=(i+current)%lines->count(); QString str=lines->item(lnum)->text(); if (str.contains(line)) { lines->setCurrentRow(lnum); return; } } lines->setCurrentItem(NULL); } } NetHackQtMenuOrTextWindow::NetHackQtMenuOrTextWindow(QWidget *parent_) : actual(0), parent(parent_) { } QWidget* NetHackQtMenuOrTextWindow::Widget() { if (!actual) impossible("Widget called before we know if Menu or Text"); return actual->Widget(); } // Text void NetHackQtMenuOrTextWindow::Clear() { if (!actual) impossible("Clear called before we know if Menu or Text"); actual->Clear(); } void NetHackQtMenuOrTextWindow::Display(bool block) { if (!actual) impossible("Display called before we know if Menu or Text"); actual->Display(block); } bool NetHackQtMenuOrTextWindow::Destroy() { if (!actual) impossible("Destroy called before we know if Menu or Text"); return actual->Destroy(); } void NetHackQtMenuOrTextWindow::PutStr(int attr, const QString& text) { if (!actual) actual=new NetHackQtTextWindow(parent); actual->PutStr(attr,text); } // Menu void NetHackQtMenuOrTextWindow::StartMenu() { if (!actual) actual=new NetHackQtMenuWindow(parent); actual->StartMenu(); } void NetHackQtMenuOrTextWindow::AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr, const QString& str, unsigned itemflags) { if (!actual) impossible("AddMenu called before we know if Menu or Text"); actual->AddMenu(glyph,identifier,ch,gch,attr,str,itemflags); } void NetHackQtMenuOrTextWindow::EndMenu(const QString& prompt) { if (!actual) impossible("EndMenu called before we know if Menu or Text"); actual->EndMenu(prompt); } int NetHackQtMenuOrTextWindow::SelectMenu(int how, MENU_ITEM_P **menu_list) { if (!actual) impossible("SelectMenu called before we know if Menu or Text"); return actual->SelectMenu(how,menu_list); } } // namespace nethack_qt_