// Copyright (c) Warwick Allison, 1999. // Qt4 conversion copyright (c) Ray Chason, 2012-2014. // NetHack may be freely redistributed. See license for details. // qt_main.cpp -- the main window extern "C" { #include "hack.h" #define CTRL(c) (0x1f & (c)) // substitute for C() from global.h, for ^V hack } #include "qt_pre.h" #include #if QT_VERSION >= 0x060000 #include #elif QT_VERSION >= 0x050000 #include #endif #if QT_VERSION >= 0x050000 #include #endif #include "qt_post.h" #include "qt_main.h" #include "qt_main.moc" #include "qt_bind.h" #include "qt_glyph.h" #include "qt_inv.h" #include "qt_key.h" #include "qt_map.h" #include "qt_msg.h" #include "qt_set.h" #include "qt_stat.h" #include "qt_str.h" #ifndef KDE #include "qt_kde0.moc" #endif // temporary extern char *qt_tilewidth; extern char *qt_tileheight; extern int qt_compact_mode; // end temporary namespace nethack_qt_ { // temporary void centerOnMain( QWidget* w ); // end temporary /* XPM */ static const char * nh_icon[] = { "40 40 6 1", " s None c none", ". c #ffffff", "X c #dadab6", "o c #6c91b6", "O c #476c6c", "+ c #000000", " ", " ", " ", " . .X..XX.XX X ", " .. .....X.XXXXXX XX ", " ... ....X..XX.XXXXX XXX ", " .. ..........X.XXXXXXXXXXX XX ", " .... ........X..XX.XXXXXXXXX XXXX ", " .... ..........X.XXXXXXXXXXX XXXX ", " ooOOO..ooooooOooOOoOOOOOOOXX+++OO++ ", " ooOOO..ooooooooOoOOOOOOOOOXX+++OO++ ", " ....O..ooooooOooOOoOOOOOOOXX+XXXX++ ", " ....O..ooooooooOoOOOOOOOOOXX+XXXX++ ", " ..OOO..ooooooOooOOoOOOOOOOXX+++XX++ ", " ++++..ooooooooOoOOOOOOOOOXX+++ +++ ", " +++..ooooooOooOOoOOOOOOOXX+++ + ", " ++..ooooooooOoOOOOOOOOOXX+++ ", " ..ooooooOooOOoOOOOOOOXX+++ ", " ..ooooooooOoOOOOOOOOOXX+++ ", " ..ooooooOooOOoOOOOOOOXX+++ ", " ..ooooooooOoOOOOOOOOOXX+++ ", " ..oooooOooOOoOOOOOOXX+++ ", " ..oooooooOoOOOOOOOOXX+++ ", " ..ooooOooOOoOOOOOXX+++ ", " ..ooooooOoOOOOOOOXX++++ ", " ..o..oooOooOOoOOOOXX+XX+++ ", " ...o..oooooOoOOOOOXX++XXX++ ", " ....OO..ooOooOOoOOXX+++XXXX++ ", " ...oo..+..oooOoOOOXX++XXooXXX++ ", " ...ooo..++..OooOOoXX+++XXooOXXX+ ", " ..oooOOXX+++....XXXX++++XXOOoOOXX+ ", " ..oooOOXX+++ ...XXX+++++XXOOooOXX++ ", " ..oooOXXX+++ ..XX+++ +XXOOooOXX++ ", " .....XXX++++ XXXXXXX++ ", " ....XX++++ XXXXXXX+ ", " ...XX+++ XXXXX++ ", " ", " ", " ", " "}; /* XPM */ static const char * nh_icon_small[] = { /* width height ncolors chars_per_pixel */ "16 16 16 1", /* colors */ " c #587070", ". c #D1D5C9", "X c #8B8C84", "o c #2A2A28", "O c #9AABA9", "+ c #6A8FB2", "@ c #C4CAC4", "# c #B6BEB6", "$ c None", "% c #54564E", "& c #476C6C", "* c #ADB2AB", "= c #ABABA2", "- c #5E8295", "; c #8B988F", ": c #E8EAE7", /* pixels */ "$$$$$$$$$$$$$$$$", "$$$.$#::.#==*$$$", "$.*:::::....#*=$", "$@#:..@#*==#;XX;", "$@O:+++- &&; X%X", "$#%.+++- &&;% oX", "$$o.++-- &&;%%X$", "$$$:++-- &&;%%$$", "$$$.O++- &&=o $$", "$$$=:++- & XoX$$", "$$*:@O-- ;%Xo$$", "$*:O#$+--;oOOX $", "$:+ =o::=oo=-;%X", "$::.%o$*;X;##@%$", "$$@# ;$$$$$=*;X$", "$$$$$$$$$$$$$$$$" }; #if 0 // RLC /* XPM */ static const char * map_xpm[] = { "12 13 4 1", ". c None", " c #000000000000", "X c #0000B6DAFFFF", "o c #69A69248B6DA", " .", " XXXXX ooo ", " XoooX o ", " XoooX o o ", " XoooX ooo ", " XXoXX o ", " oooooXXX ", " oo o oooX ", " o XooX ", " oooo XooX ", " o o XXXX ", " ", ". "}; /* XPM */ static const char * msg_xpm[] = { "12 13 4 1", ". c None", " c #FFFFFFFFFFFF", "X c #69A69248B6DA", "o c #000000000000", " .", " XXX XXX X o", " o", " XXXXX XX o", " o", " XX XXXXX o", " o", " XXXXXX o", " o", " XX XXX XX o", " o", " o", ".ooooooooooo"}; /* XPM */ static const char * stat_xpm[] = { "12 13 5 1", " c None", ". c #FFFF00000000", "X c #000000000000", "o c #FFFFFFFF0000", "O c #69A6FFFF0000", " ", " ", "... ", "...X ", "...X ... ", "oooX oooX", "oooXooo oooX", "OOOXOOOXOOOX", "OOOXOOOXOOOX", "OOOXOOOXOOOX", "OOOXOOOXOOOX", "OOOXOOOXOOOX", " XXXXXXXXXXX"}; #endif /* XPM */ static const char * info_xpm[] = { "12 13 4 1", " c None", ". c #00000000FFFF", "X c #FFFFFFFFFFFF", "o c #000000000000", " ... ", " ....... ", " ...XXX... ", " .........o ", "...XXXX.... ", "....XXX....o", "....XXX....o", "....XXX....o", " ...XXX...oo", " ..XXXXX..o ", " .......oo ", " o...ooo ", " ooo "}; /* XPM */ static const char * again_xpm[] = { "12 13 2 1", " c None", ". c #000000000000", " .. ", " .. ", " ..... ", " ....... ", "... .. .. ", ".. .. .. ", ".. ..", ".. ..", ".. ..", " .. .. ", " .......... ", " ...... ", " "}; /* XPM */ static const char * kick_xpm[] = { "12 13 3 1", " c None", ". c #000000000000", "X c #FFFF6DB60000", " ", " ", " . . . ", " ... . . ", " ... . ", " ... . ", " ... ", "XXX ... ", "XXX. ... ", "XXX. ... ", "XXX. .. ", " ... ", " "}; /* XPM */ static const char * throw_xpm[] = { "12 13 3 1", " c None", ". c #FFFF6DB60000", "X c #000000000000", " ", " ", " ", " ", ".... X ", "....X X ", "....X XXXXXX", "....X X ", " XXXX X ", " ", " ", " ", " "}; /* XPM */ static const char * fire_xpm[] = { "12 13 5 1", " c None", ". c #B6DA45140000", "X c #FFFFB6DA9658", "o c #000000000000", "O c #FFFF6DB60000", " . ", " X. ", " X . ", " X .o ", " X . o ", " X .o o ", "OOOOOOOOoooo", " X .o o ", " X . o o ", " X .o ", " X. o ", " . o ", " o "}; /* XPM */ static const char * pickup_xpm[] = { "12 13 3 1", " c None", ". c #000000000000", "X c #FFFF6DB60000", " ", " . ", " ... ", " . . . ", " . ", " . ", " ", " XXXXX ", " XXXXX. ", " XXXXX. ", " XXXXX. ", " ..... ", " "}; /* XPM */ static const char * drop_xpm[] = { "12 13 3 1", " c None", ". c #FFFF6DB60000", "X c #000000000000", " ", " ..... ", " .....X ", " .....X ", " .....X ", " XXXXX ", " ", " X ", " X ", " X X X ", " XXX ", " X ", " "}; /* XPM */ static const char * eat_xpm[] = { "12 13 4 1", " c None", ". c #000000000000", "X c #FFFFB6DA9658", "o c #FFFF6DB60000", " .X. .. ", " .X. .. ", " .X. .. ", " .X. .. ", " ... .. ", " .. .. ", " .. .. ", " oo oo ", " oo oo ", " oo oo ", " oo oo ", " oo oo ", " oo oo "}; /* XPM */ static const char * search_xpm[] = { "12 13 3 1", " c None", ". c #FFFFFFFF0000", "X c #7F0000000000", " ", " XXXXX ", " X ... X ", " X.....X ", " X.....X ", " X ... X ", " XXXXX ", " X ", " X ", " X ", " X ", " X ", " "}; /* XPM */ static const char * rest_xpm[] = { "12 13 2 1", " c None", ". c #000000000000", " ..... ", " . ", " . ", " . ....", " ..... . ", " . ", " ....", " ", " .... ", " . ", " . ", " .... ", " "}; /* XPM */ static const char * cast_a_xpm[] UNUSED = { "12 13 3 1", " c None", ". c #FFFF6DB60000", "X c #000000000000", " . ", " . ", " .. ", " .. ", " .. . ", " .. . ", " ...... ", " .. .. XX ", " .. X X ", " .. X X ", " .. XXXX ", " . X X ", " . X X "}; /* XPM */ static const char * cast_b_xpm[] UNUSED = { "12 13 3 1", " c None", ". c #FFFF6DB60000", "X c #000000000000", " . ", " . ", " .. ", " .. ", " .. . ", " .. . ", " ...... ", " .. .. XXX ", " .. X X ", " .. XXX ", " .. X X ", " . X X ", " . XXX "}; /* XPM */ static const char * cast_c_xpm[] UNUSED = { "12 13 3 1", " c None", ". c #FFFF6DB60000", "X c #000000000000", " . ", " . ", " .. ", " .. ", " .. . ", " .. . ", " ...... ", " .. .. XX ", " .. X X ", " .. X ", " .. X ", " . X X ", " . XX "}; static QString aboutMsg() { char *p, vbuf[BUFSZ]; /* nethack's getversionstring() includes a final period but we're using it mid-sentence so strip period off */ if ((p = strrchr(::getversionstring(vbuf, sizeof vbuf), '.')) != 0 && *(p + 1) == '\0') *p = '\0'; /* it's also long; break it into two pieces */ (void) strsubst(vbuf, " - ", "\n- "); QString msg = nh_qsprintf( // format "NetHack-Qt is a version of NetHack built using" // no newline #ifdef KDE " KDE and" // ditto #endif " the Qt %d GUI toolkit.\n" // short Qt version "\n" "This is %s%s and Lua %s.\n" // long nethack version, Qt & Lua versions "\n" "NetHack's Qt interface originally developed by Warwick Allison.\n" "\n" #if 0 "Homepage:\n http://trolls.troll.no/warwick/nethack/\n" //obsolete #endif #ifdef KDE "KDE:\n https://kde.org/\n" #endif #if 1 "Qt:\n https://qt.io/\n" #else "Qt:\n http://www.troll.no/\n" // obsolete #endif "Lua:\n https://lua.org/\n" "NetHack:\n %s\n", // DEVTEAM_URL // arguments #ifdef QT_VERSION_MAJOR QT_VERSION_MAJOR, #else 5, // Qt version macro should exist; if not, assume Qt5 #endif vbuf, // nethack version #ifdef QT_VERSION_STR " with Qt " QT_VERSION_STR, #else "", #endif ::get_lua_version(), DEVTEAM_URL); return msg; } class SmallToolButton : public QToolButton { public: SmallToolButton(const QPixmap &pm, const QString &textLabel, const QString &grouptext, QObject *receiver, const char *slot, QWidget *parent) : QToolButton(parent) { setIcon(QIcon(pm)); setToolTip(textLabel); setStatusTip(grouptext); connect(this, SIGNAL(clicked(bool)), receiver, slot); } QSize sizeHint() const { // get just a couple more pixels for the map return QToolButton::sizeHint()-QSize(0,2); } }; NetHackQtMainWindow::NetHackQtMainWindow(NetHackQtKeyBuffer& ks) : message(0), map(0), status(0), invusage(0), hsplitter(0), vsplitter(0), keysink(ks), dirkey(0) { QToolBar* toolbar = new QToolBar(this); toolbar->setMovable(false); toolbar->setFocusPolicy(Qt::NoFocus); addToolBar(toolbar); menubar = menuBar(); setWindowTitle("NetHack-Qt"); setWindowIcon(QIcon(QPixmap(qt_compact_mode ? nh_icon_small : nh_icon))); #ifdef MACOS /* * MacOS Note: * The toolbar on MacOS starts with a system menu labeled with the * Apple logo and an application menu labeled with the application's * name (taken from Info.plist if present, otherwise the base name * of the running program). After that, application-specific menus * (in our case "game",...,"help") follow. Several menu entry * names ("About", "Quit"/"Exit", "Preferences"/"Options"/ * "Settings"/"Setup"/"Config") get hijacked and placed in the * application menu (and renamed in the process) even if the code * here tries to put them in another menu. * See QtWidgets/doc/qmenubar.html for slightly more information. * setMenuRole() can be used to override this behavior. */ #endif #ifdef CTRL_V_HACK // NetHackQtBind::notify() sees all control characters except for ^V QShortcut *c_V = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_V), this); connect(c_V, &QShortcut::activated, this, &NetHackQtMainWindow::CtrlV); #endif QMenu* game=new QMenu; QMenu* apparel=new QMenu; QMenu* act1=new QMenu; QMenu* act2 = qt_compact_mode ? new QMenu : act1; QMenu* magic=new QMenu; QMenu* info=new QMenu; QMenu *help; #ifdef KDE help = kapp->getHelpMenu( true, "" ); help->addSeparator(); #else help = qt_compact_mode ? info : new QMenu; #endif enum { OnDesktop=1, OnHandhelds=2 }; struct Macro { QMenu *menu; const char *name; int flags; // 1 desktop, 2 handheld, 3 either/both int (*funct)(void); } item[] = { { game, 0, 3, (int (*)(void)) 0}, { game, "Extended-commands", 3, doextcmd }, { game, 0, 3, (int (*)(void)) 0}, { game, "Version", 3, doversion}, { game, "Compilation", 3, doextversion}, { game, "History", 3, dohistory}, { game, "Redraw", 0, doredraw}, // useless { game, #ifdef MACOS /* Qt on OSX would rename "Options" to "Preferences..." and move it from intended destination to the application menu; the ampersand produces &O which makes Alt+O into a keyboard shortcut--except those are disabled by default by Qt on OSX */ "Run-time &" // rely on adjacent string concatenation #endif "Options", 3, doset}, { game, "Explore mode", 3, enter_explore_mode}, { game, 0, 3, (int (*)(void)) 0}, { game, "Save-and-exit", 3, dosave}, { game, #ifdef MACOS /* need something to prevent matching leading "quit" so that it isn't hijacked for the application menu; the ampersand is to make &Q be a keyboard shortcut (but see Options above) */ "\177&" #endif "Quit-without-saving", 3, done2}, { apparel, "Apparel off", 2, doddoremarm}, { apparel, "Remove many", 1, doddoremarm}, { apparel, 0, 3, (int (*)(void)) 0}, { apparel, "Wield weapon", 3, dowield}, { apparel, "Exchange weapons", 3, doswapweapon}, { apparel, "Two weapon combat", 3, dotwoweapon}, { apparel, "Load quiver", 3, dowieldquiver}, { apparel, 0, 3, (int (*)(void)) 0}, { apparel, "Wear armor", 3, dowear}, { apparel, "Take off armor", 3, dotakeoff}, { apparel, 0, 3, (int (*)(void)) 0}, { apparel, "Put on accessories", 3, doputon}, { apparel, "Remove accessories", 3, doremring}, /* { act1, "Again\tCtrl+A", "\001", 2}, { act1, 0, 0, 3}, */ { act1, "Apply", 3, doapply}, { act1, "Chat", 3, dotalk}, { act1, "Close door", 3, doclose}, { act1, "Down", 3, dodown}, { act1, "Drop many", 2, doddrop}, { act1, "Drop", 2, dodrop}, { act1, "Eat", 2, doeat}, { act1, "Engrave", 3, doengrave}, /* { act1, "Fight\tShift+F", "F", 3}, */ { act1, "Fire from quiver", 2, dofire}, { act1, "Force", 3, doforce}, { act1, "Jump", 3, dojump}, { act2, "Kick", 2, dokick}, { act2, "Loot", 3, doloot}, { act2, "Open door", 3, doopen}, { act2, "Pay", 3, dopay}, // calling this "Get" was confusing to experienced players { act1, "Pick up (was Get)", 3, dopickup}, { act2, "Rest", 2, donull}, { act2, "Ride", 3, doride}, { act2, "Search", 3, dosearch}, { act2, "Sit", 3, dosit}, { act2, "Throw", 2, dothrow}, { act2, "Untrap", 3, dountrap}, { act2, "Up", 3, doup}, { act2, "Wipe face", 3, dowipe}, { magic, "Quaff potion", 3, dodrink}, { magic, "Read scroll/book", 3, doread}, { magic, "Zap wand", 3, dozap}, { magic, "Zap spell", 3, docast}, { magic, "Dip", 3, dodip}, { magic, "Rub", 3, dorub}, { magic, "Invoke", 3, doinvoke}, { magic, 0, 3, (int (*)(void)) 0}, { magic, "Offer", 3, dosacrifice}, { magic, "Pray", 3, dopray}, { magic, 0, 3, (int (*)(void)) 0}, { magic, "Teleport", 3, dotelecmd}, { magic, "Monster action", 3, domonability}, { magic, "Turn undead", 3, doturn}, { help, "Help", 3, dohelp}, { help, 0, 3, (int (*)(void)) 0}, { help, "What is here", 3, dolook}, { help, "What is there", 3, doquickwhatis}, { help, "What is...", 2, dowhatis}, { help, 0, 1, (int (*)(void)) 0}, { info, "Inventory", 3, ddoinv}, { info, "Attributes (extended status)", 3, doattributes }, { info, "Overview", 3, dooverview }, { info, "Conduct", 3, doconduct}, { info, "Discoveries", 3, dodiscovered}, { info, "List/reorder spells", 3, dovspell}, { info, "Adjust inventory letters", 3, doorganize }, { info, 0, 3, (int (*)(void)) 0}, { info, "Name object or creature", 3, docallcmd}, { info, "Annotate level", 3, donamelevel }, { info, 0, 3, (int (*)(void)) 0}, { info, "Skills", 3, enhance_weapon_skill}, { 0, 0, 0, (int (*)(void)) 0 } }; QAction *actn; #ifndef MACOS (void) game->addAction("Qt settings...", this, SLOT(doQtSettings(bool))); #else /* on OSX, put this in the application menu instead of the game menu; Qt would change the action name behind our backs; do it explicitly */ actn = game->addAction("Preferences...", this, SLOT(doQtSettings(bool))); actn->setMenuRole(QWidgetAction::PreferencesRole); /* we also want a "Quit NetHack" entry in the application menu; when "_Quit-without-saving" was called "Quit" it got intercepted for that, but now this needs to be added separately; we'll use a handy menu and let the interception put it in the intended place; unlike About, it is not a duplicate; _Quit-without-saving runs nethack's #quit command with "really quit?" prompt, this quit--with Command+q as shortcut--pops up a dialog to choose between quit or cancel-and-resume-playing */ actn = game->addAction("Quit NetHack-Qt", this, SLOT(doQuit(bool))); actn->setMenuRole(QWidgetAction::QuitRole); #endif actn = help->addAction("About NetHack-Qt", this, SLOT(doAbout(bool))); #ifdef MACOS actn->setMenuRole(QWidgetAction::AboutRole); /* for OSX, the preceding "About" went into the application menu; now add another duplicate one to the Help dropdown menu */ actn = help->addAction("About NetHack-Qt", this, SLOT(doAbout(bool))); actn->setMenuRole(QWidgetAction::NoRole); #else nhUse(actn); #endif help->addSeparator(); //help->addAction("NetHack Guidebook", this, SLOT(doGuidebook(bool))); //help->addSeparator(); for (int i = 0; item[i].menu; ++i) { if ( item[i].flags & (qt_compact_mode ? 1 : 2) ) { if (item[i].name) { char actchar[32]; char menuitem[BUFSZ]; actchar[0] = actchar[1] = '\0'; if (item[i].funct) { actchar[0] = cmd_from_func(item[i].funct); if (actchar[0] /* M-c won't work; translation between character sets by the QString class can classify such characters as erroneous and change them to '?' */ && ((actchar[0] & 0x7f) != actchar[0] /* the vi movement keys won't work reliably because toggling number_pad affects them but doesn't redo these menus */ || strchr("hjklyubnHJKLYUBN", actchar[0]) || strchr("hjklyubn", (actchar[0] | 0x60)))) actchar[0] = '\0'; } if (actchar[0] && !qt_compact_mode) Sprintf(menuitem, "%.50s\t%.9s", item[i].name, visctrl(actchar[0])); else Sprintf(menuitem, "%s", item[i].name); if (item[i].funct && !actchar[0]) { actchar[0] = '#'; (void) cmdname_from_func(item[i].funct, &actchar[1], FALSE); } if (actchar[0]) { QString name = menuitem; QAction *action = item[i].menu->addAction(name); #if QT_VERSION < 0x060000 action->setData(actchar); #else action->setData(QString(actchar)); #endif } } else { item[i].menu->addSeparator(); } } } game->setTitle("Game"); menubar->addMenu(game); apparel->setTitle("Gear"); menubar->addMenu(apparel); if ( qt_compact_mode ) { act1->setTitle("A-J"); menubar->addMenu(act1); act2->setTitle("K-Z"); menubar->addMenu(act2); magic->setTitle("Magic"); menubar->addMenu(magic); info->setIcon(QIcon(QPixmap(info_xpm))); info->setTitle("Info"); menubar->addMenu(info); //menubar->insertItem(QPixmap(map_xpm), this, SLOT(raiseMap())); //menubar->insertItem(QPixmap(msg_xpm), this, SLOT(raiseMessages())); //menubar->insertItem(QPixmap(stat_xpm), this, SLOT(raiseStatus())); info->addSeparator(); info->addAction("Map", this, SLOT(raiseMap())); info->addAction("Messages", this, SLOT(raiseMessages())); info->addAction("Status", this, SLOT(raiseStatus())); } else { act1->setTitle("Action"); menubar->addMenu(act1); magic->setTitle("Magic"); menubar->addMenu(magic); info->setTitle("Info"); menubar->addMenu(info); menubar->addSeparator(); #ifndef MACOS help->setTitle("Help"); #else // On OSX, an entry in the menubar called "Help" will get an // extra action, "Search [______]", inserted as the first entry. // We have no control over what it does and don't want it. // // Using actions() to fetch a list of all entries doesn't find it, // so we don't have its widget pointer to pass to removeAction(). // // Altering the name with an invisible character to inhibit // string matching is ludicrous but keeps the unwanted action // from getting inserted into the "Help" menu behind our back. // Underscore works too and is more robust but unless we prepend // it to every entry, "_Help" would stand out as strange. help->setTitle("\177Help"); // (Renaming back to "Help" after the fact does reset the menu's // name but it also results in the Search action being added. // Perhaps a custom context menu that changes its name to "Help" // as it is being shown--and possibly changes back afterward-- // would work but the name mangling hack is much simpler.) #endif menubar->addMenu(help); } // order changed: was Again, Get, Kick, Throw, Fire, Drop, Eat, Rest // now Again, PickUp, Drop, Kick, Throw, Fire, Eat, Rest QSignalMapper* sm = new QSignalMapper(this); #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) connect(sm, SIGNAL(mappedString(const QString&)), this, SLOT(doKeys(const QString&))); #else connect(sm, SIGNAL(mapped(const QString&)), this, SLOT(doKeys(const QString&))); #endif AddToolButton(toolbar, sm, "Again", do_repeat, QPixmap(again_xpm)); // this used to be called "Get" which is confusing to experienced players AddToolButton(toolbar, sm, "Pick up", dopickup, QPixmap(pickup_xpm)); AddToolButton(toolbar, sm, "Drop", doddrop, QPixmap(drop_xpm)); AddToolButton(toolbar, sm, "Kick", dokick, QPixmap(kick_xpm)); AddToolButton(toolbar, sm, "Throw", dothrow, QPixmap(throw_xpm)); AddToolButton(toolbar, sm, "Fire", dofire, QPixmap(fire_xpm)); AddToolButton(toolbar, sm, "Eat", doeat, QPixmap(eat_xpm)); AddToolButton(toolbar, sm, "Search", dosearch, QPixmap(search_xpm)); AddToolButton(toolbar, sm, "Rest", donull, QPixmap(rest_xpm)); connect(game, SIGNAL(triggered(QAction *)), this, SLOT(doMenuItem(QAction *))); connect(apparel, SIGNAL(triggered(QAction *)), this, SLOT(doMenuItem(QAction *))); connect(act1, SIGNAL(triggered(QAction *)), this, SLOT(doMenuItem(QAction *))); if (act2 != act1) connect(act2, SIGNAL(triggered(QAction *)), this, SLOT(doMenuItem(QAction *))); connect(magic, SIGNAL(triggered(QAction *)), this, SLOT(doMenuItem(QAction *))); connect(info, SIGNAL(triggered(QAction *)), this, SLOT(doMenuItem(QAction *))); connect(help, SIGNAL(triggered(QAction *)), this, SLOT(doMenuItem(QAction *))); #ifdef KDE setMenu (menubar); #endif #if QT_VERSION < 0x060000 QSize screensize = QApplication::desktop()->size(); #else QSize screensize = screen()->size(); #endif int x=0,y=0; int w=screensize.width()-10; // XXX arbitrary extra space for frame int h=screensize.height()-50; int maxwn = 1400; int maxhn = 1024; if (qt_settings != NULL) { auto glyphs = &qt_settings->glyphs(); if (glyphs != NULL) { maxwn = glyphs->width() * (COLNO + 1); maxhn = glyphs->height() * ROWNO * 6/4 + glyphs->height() * 10; } } // Be exactly the size we want to be - full map... if (w>maxwn) { x+=(w-maxwn)/2; w=maxwn; // Doesn't need to be any wider } if (h>maxhn) { y+=(h-maxhn)/2; h=maxhn; // Doesn't need to be any taller } setGeometry(x,y,w,h); if ( qt_compact_mode ) { stack = new QStackedWidget(this); setCentralWidget(stack); } else { vsplitter = new QSplitter(Qt::Vertical); setCentralWidget(vsplitter); hsplitter = new QSplitter(Qt::Horizontal); invusage = new NetHackQtInvUsageWindow(hsplitter); vsplitter->insertWidget(0, hsplitter); hsplitter->insertWidget(1, invusage); } } #ifdef CTRL_V_HACK // called when ^V is typed while the main window has keyboard focus; // all other control characters go through NetHackQtBind::notify() void NetHackQtMainWindow::CtrlV() { static const char cV[] = { CTRL('V'), '\0' }; doKeys(cV); } #endif // add a toolbar button to invoke command 'name' via function '(*func)()' void NetHackQtMainWindow::AddToolButton(QToolBar *toolbar, QSignalMapper *sm, const char *name, int (*func)(void), QPixmap xpm) { char actchar[2]; uchar key; key = (uchar) cmd_from_func(func); // if key is valid, add a button for it; otherwise omit the command // (won't work as intended if a different command is bound to same key) if (key) { QToolButton *tb = new SmallToolButton(xpm, QString(name), "Action", sm, SLOT(map()), toolbar); actchar[0] = '\0'; sm->setMapping(tb, strkitten(actchar, (char) key)); toolbar->addWidget(tb); } } void NetHackQtMainWindow::zoomMap() { qt_settings->toggleGlyphSize(); } void NetHackQtMainWindow::raiseMap() { if ( stack->currentWidget() == map->Widget() ) { zoomMap(); } else { stack->setCurrentWidget(map->Widget()); } } void NetHackQtMainWindow::raiseMessages() { stack->setCurrentWidget(message->Widget()); } void NetHackQtMainWindow::raiseStatus() { stack->setCurrentWidget(status->Widget()); } #if 0 // RLC this isn't used class NetHackMimeSourceFactory : public Q3MimeSourceFactory { public: const QMimeSource* data(const QString& abs_name) const { const QMimeSource* r = 0; if ( (NetHackMimeSourceFactory *) this == Q3MimeSourceFactory::defaultFactory() ) r = Q3MimeSourceFactory::data(abs_name); else r = Q3MimeSourceFactory::defaultFactory()->data(abs_name); if ( !r ) { int sl = abs_name.length(); do { sl = abs_name.lastIndexOf('/',sl-1); QString name = sl>=0 ? abs_name.mid(sl+1) : abs_name; int dot = name.lastIndexOf('.'); if ( dot >= 0 ) name = name.left(dot); if ( name == "map" ) r = new Q3ImageDrag(QImage(map_xpm)); else if ( name == "msg" ) r = new Q3ImageDrag(QImage(msg_xpm)); else if ( name == "stat" ) r = new Q3ImageDrag(QImage(stat_xpm)); } while (!r && sl>0); } return r; } }; #endif /* used by doMenuItem() and for the toolbar buttons */ bool NetHackQtMainWindow::ok_for_command() { /* * If the core expects text to be entered (perhaps typing in a wish, * assigning a name to something, or answering a y/n prompt), or a * map position or a direction is being picked, don't accept commands * from the toolbar. * * FIXME: it would be much better to gray-out inapplicable entries * when popping up a command menu instead of needing this. */ if (::program_state.input_state != commandInp) { NetHackQtBind::qt_nhbell(); // possibly call doKeys("\033"); here? return false; } return true; } void NetHackQtMainWindow::doMenuItem(QAction *action) { if (!ok_for_command()) return; /* this converts meta characters to '?'; menu processing has been changed to send multi-character "#abc" instead (already needed for commands that didn't have either a regular keystroke or a meta shortcut); it must send just enough to disambiguate from other extended command names, otherwise the remainder would be left in the queue for subsequent handling as additional commands */ doKeys(action->data().toString()); } void NetHackQtMainWindow::doQtSettings(bool) { centerOnMain(qt_settings); qt_settings->show(); } void NetHackQtMainWindow::doAbout(bool) { QMessageBox::about(this, "About NetHack-Qt", aboutMsg()); } // on OSX, "quit nethack" has been selected in the application menu or // "Command+Q" has been typed -- user is asking to quit the application; // unlike with the window's Close button, user has a chance to back out void NetHackQtMainWindow::doQuit(bool) { // there is a separate Quit-without-saving menu entry in the game menu // that leads to nethack's "Really quit?" prompt; OSX players can use // either one, other implementations only have that other one (plus // nethack's #quit command itself) but this routine is unconditional // in case someone wants to change that #ifdef MACOS QString info = nh_qsprintf("This will end your NetHack session.%s", !program_state.something_worth_saving ? "" : "\n(Cancel quitting and use the Save command" "\nto save your current game.)"); /* this is similar to closeEvent but the details are different; first choice (Cancel) is the default action for most arbitrary keys; the second choice (Quit) is the action for or ; leaves the popup waiting for some other response; the & settings for Alt+ shortcuts don't work on OSX */ int act = QMessageBox::information(this, "NetHack", info, "&Cancel and return to game", "&Quit without saving", 0, 1); switch (act) { case 0: // cancel break; // return to game case 1: // quit -- bypass the prompting preformed by done2() program_state.stopprint++; ::done(QUIT); /*NOTREACHED*/ break; } #endif return; } #if 0 // RLC this isn't used void NetHackQtMainWindow::doGuidebook(bool) { QDialog dlg(this); new QVBoxLayout(&dlg); Q3TextBrowser browser(&dlg); NetHackMimeSourceFactory ms; browser.setMimeSourceFactory(&ms); browser.setSource(QDir::currentPath()+"/Guidebook.html"); if ( qt_compact_mode ) dlg.showMaximized(); dlg.exec(); } #endif void NetHackQtMainWindow::doKeys(const char *cmds) { keysink.Put(cmds); qApp->exit(); } void NetHackQtMainWindow::doKeys(const QString& k) { /* [this should probably be using toLocal8Bit(); toAscii() is not offered as an alternative...] */ doKeys(k.toLatin1().constData()); } // queue up the command name for a function, as if user had typed it void NetHackQtMainWindow::FuncAsCommand(int (*func)(void)) { char cmdbuf[32]; Strcpy(cmdbuf, "#"); (void) cmdname_from_func(func, &cmdbuf[1], FALSE); doKeys(cmdbuf); } void NetHackQtMainWindow::AddMessageWindow(NetHackQtMessageWindow* window) { message=window; if (!qt_compact_mode) hsplitter->insertWidget(0, message->Widget()); ShowIfReady(); } NetHackQtMessageWindow * NetHackQtMainWindow::GetMessageWindow() { return message; } void NetHackQtMainWindow::AddMapWindow(NetHackQtMapWindow2* window) { map=window; if (!qt_compact_mode) vsplitter->insertWidget(1, map->Widget()); ShowIfReady(); connect(map,SIGNAL(resized()),this,SLOT(layout())); } void NetHackQtMainWindow::AddStatusWindow(NetHackQtStatusWindow* window) { status=window; if (!qt_compact_mode) hsplitter->insertWidget(2, status->Widget()); ShowIfReady(); } void NetHackQtMainWindow::RemoveWindow(NetHackQtWindow* window) { if (window==status) { status=0; ShowIfReady(); } else if (window==map) { map=0; ShowIfReady(); } else if (window==message) { message=0; ShowIfReady(); } } void NetHackQtMainWindow::updateInventory() { if (invusage) { invusage->repaint(); } } void NetHackQtMainWindow::fadeHighlighting(bool before_key) { if (before_key) { // status highlighting fades at start of turn if (status) status->fadeHighlighting(); } else { // message highlighting fades after user has given input if (message && message->hilit_mesgs()) message->unhighlight_mesgs(); } } void NetHackQtMainWindow::layout() { #if 0 if ( qt_compact_mode ) return; if (message && map && status) { QSize maxs=map->Widget()->maximumSize(); int maph=std::min(height()*2/3,maxs.height()); QWidget* c = centralWidget(); int h=c->height(); int toph=h-maph; int iuw=3*qt_settings->glyphs().width(); int topw=(c->width()-iuw)/2; message->Widget()->setGeometry(0,0,topw,toph); invusage->setGeometry(topw,0,iuw,toph); status->Widget()->setGeometry(topw+iuw,0,topw,toph); map->Widget()->setGeometry(std::max(0,(c->width()-maxs.width())/2), toph,c->width(),maph); } #endif if (qt_settings && !qt_compact_mode && map && message && status && invusage) { // For the initial PaperDoll sizing, message window // and/or status window might still be empty; // widen them before changing PaperDoll to use saved settings. QList splittersizes = hsplitter->sizes(); #define MIN_WIN_WIDTH 400 if (splittersizes[0] < MIN_WIN_WIDTH || splittersizes[2] < MIN_WIN_WIDTH) { if (splittersizes[0] < MIN_WIN_WIDTH) splittersizes[0] = MIN_WIN_WIDTH; #ifndef ENHANCED_PAPERDOLL if (splittersizes[1] < 6) // TILEWMIN splittersizes[1] = 16; // 16x16 #endif if (splittersizes[2] < MIN_WIN_WIDTH) splittersizes[2] = MIN_WIN_WIDTH; hsplitter->setSizes(splittersizes); } #ifdef ENHANCED_PAPERDOLL // call resizePaperDoll() indirectly... qt_settings->resizeDoll(); #endif // reset widths int w = width(); /* of main window */ int d = invusage->width(); splittersizes[2] = w / 2 - (d * 1 / 4); // status splittersizes[1] = d; // invusage splittersizes[0] = w / 2 - (d * 3 / 4); // messages hsplitter->setSizes(splittersizes); } } #ifdef DYNAMIC_STATUSLINES // called when 'statuslines' changes from 2 to 3 or vice versa; simpler to // destroy and recreate the status window than to adjust existing fields NetHackQtWindow *NetHackQtMainWindow::redoStatus() { NetHackQtStatusWindow *oldstatus = this->status; if (!oldstatus) return NULL; // not ready yet? this->status = new NetHackQtStatusWindow; if (!qt_compact_mode) hsplitter->replaceWidget(2, this->status->Widget()); delete oldstatus; ShowIfReady(); return (NetHackQtWindow *) this->status; } #endif void NetHackQtMainWindow::resizePaperDoll(bool showdoll) { #ifdef ENHANCED_PAPERDOLL // this is absurd... NetHackQtInvUsageWindow *w = static_cast (NetHackQtBind::mainWidget())->invusage; QList hsplittersizes = hsplitter->sizes(), vsplittersizes = vsplitter->sizes(); w->resize(w->sizeHint()); int oldwidth = hsplittersizes[1], newwidth = w->width(); if (newwidth != oldwidth) { if (oldwidth > newwidth) hsplittersizes[0] += (oldwidth - newwidth); else hsplittersizes[2] += (newwidth - oldwidth); hsplittersizes[1] = newwidth; hsplitter->setSizes(hsplittersizes); } // Height limit is 48+2 pixels per doll cell plus 1 pixel margin at top; // values greater than 44+2 need taller window which pushes the map down // (when font size 'Large' is used for messages and status; threshold // may vary by 1 or 2 for other sizes). // FIXME: this doesn't shrink the window back if size is reduced from 45+ int oldheight = vsplittersizes[0], newheight = w->height(); if (newheight > oldheight && oldheight > 0 && vsplittersizes[1] > 0) { vsplittersizes[0] = newheight; vsplitter->setSizes(vsplittersizes); } if (showdoll) { if (w->isHidden()) w->show(); else w->repaint(); } else { if (w->isVisible()) w->hide(); } #else nhUse(showdoll); #endif /* ENHANCED_PAPERDOLL */ } void NetHackQtMainWindow::resizeEvent(QResizeEvent*) { layout(); #ifdef KDE updateRects(); #endif } void NetHackQtMainWindow::keyReleaseEvent(QKeyEvent* event) { if ( dirkey ) { doKeys(QString(QChar(dirkey))); if ( !event->isAutoRepeat() ) dirkey = 0; } } void NetHackQtMainWindow::keyPressEvent(QKeyEvent* event) { // Global key controls // For desktop, arrow keys scroll map, since we don't want players // to think that's the way to move. For handhelds, the normal way is to // click-to-travel, so we allow the cursor keys for fine movements. // 321 // 4 0 // 567 if ( event->isAutoRepeat() && event->key() >= Qt::Key_Left && event->key() <= Qt::Key_Down ) return; const char* d = gc.Cmd.dirchars; switch (event->key()) { case Qt::Key_Up: if ( dirkey == d[0] ) dirkey = d[1]; else if ( dirkey == d[4] ) dirkey = d[3]; else dirkey = d[2]; break; case Qt::Key_Down: if ( dirkey == d[0] ) dirkey = d[7]; else if ( dirkey == d[4] ) dirkey = d[5]; else dirkey = d[6]; break; case Qt::Key_Left: if ( dirkey == d[2] ) dirkey = d[1]; else if ( dirkey == d[6] ) dirkey = d[7]; else dirkey = d[0]; break; case Qt::Key_Right: if ( dirkey == d[2] ) dirkey = d[3]; else if ( dirkey == d[6] ) dirkey = d[5]; else dirkey = d[4]; break; case Qt::Key_PageUp: dirkey = 0; if (message) message->Scroll(0,-1); break; case Qt::Key_PageDown: dirkey = 0; if (message) message->Scroll(0,+1); break; case Qt::Key_Space: //if (flags.rest_on_space) { event->ignore(); // punt to NetHackQtBind::notify() return; //} case Qt::Key_Enter: if ( map ) map->clickCursor(); break; default: dirkey = 0; event->ignore(); break; } } // game window's Close button has been activated void NetHackQtMainWindow::closeEvent(QCloseEvent *e UNUSED) { int ok = 0; if ( program_state.something_worth_saving ) { /* this used to offer "Save" and "Cancel" but cancel (ignoring the close attempt) won't work if user has clicked on the window's Close button */ int act = QMessageBox::information(this, "NetHack", "This will end your NetHack session.", "&Save and exit", "&Quit without saving", 0, 1); switch (act) { case 0: // save portion of save-and-exit ok = dosave0(); break; case 1: // quit -- bypass the prompting preformed by done2() ok = 1; program_state.stopprint++; ::done(QUIT); /*NOTREACHED*/ break; } } else { /* nothing worth saving; just close/quit */ ok = 1; } /* if !ok, we should try to continue, but we don't... */ nhUse(ok); u.uhp = -1; NetHackQtBind::qt_exit_nhwindows(0); nh_terminate(EXIT_SUCCESS); } void NetHackQtMainWindow::ShowIfReady() { if (message && map && status) { QWidget* hp = qt_compact_mode ? static_cast(stack) : static_cast(hsplitter); QWidget* vp = qt_compact_mode ? static_cast(stack) : static_cast(vsplitter); message->Widget()->setParent(hp); map->Widget()->setParent(vp); status->Widget()->setParent(hp); if ( qt_compact_mode ) { message->setMap(map); stack->addWidget(map->Widget()); stack->addWidget(message->Widget()); stack->addWidget(status->Widget()); raiseMap(); } else { layout(); } showNormal(); } else if (isVisible()) { hide(); } } } // namespace nethack_qt_