1 // Copyright (c) Warwick Allison, 1999.
2 // Qt4 conversion copyright (c) Ray Chason, 2012-2014.
3 // NetHack may be freely redistributed. See license for details.
5 // qt4main.cpp -- the main window
10 #include "patchlevel.h"
21 #include <QtGui/QtGui>
22 #if QT_VERSION >= 0x050000
23 #include <QtWidgets/QtWidgets>
26 #include "qt4main.moc"
38 #include "qt4kde0.moc"
42 extern char *qt_tilewidth;
43 extern char *qt_tileheight;
44 extern int qt_compact_mode;
47 namespace nethack_qt4 {
50 void centerOnMain( QWidget* w );
54 static const char * nh_icon[] = {
66 " .. .....X.XXXXXX XX ",
67 " ... ....X..XX.XXXXX XXX ",
68 " .. ..........X.XXXXXXXXXXX XX ",
69 " .... ........X..XX.XXXXXXXXX XXXX ",
70 " .... ..........X.XXXXXXXXXXX XXXX ",
71 " ooOOO..ooooooOooOOoOOOOOOOXX+++OO++ ",
72 " ooOOO..ooooooooOoOOOOOOOOOXX+++OO++ ",
73 " ....O..ooooooOooOOoOOOOOOOXX+XXXX++ ",
74 " ....O..ooooooooOoOOOOOOOOOXX+XXXX++ ",
75 " ..OOO..ooooooOooOOoOOOOOOOXX+++XX++ ",
76 " ++++..ooooooooOoOOOOOOOOOXX+++ +++ ",
77 " +++..ooooooOooOOoOOOOOOOXX+++ + ",
78 " ++..ooooooooOoOOOOOOOOOXX+++ ",
79 " ..ooooooOooOOoOOOOOOOXX+++ ",
80 " ..ooooooooOoOOOOOOOOOXX+++ ",
81 " ..ooooooOooOOoOOOOOOOXX+++ ",
82 " ..ooooooooOoOOOOOOOOOXX+++ ",
83 " ..oooooOooOOoOOOOOOXX+++ ",
84 " ..oooooooOoOOOOOOOOXX+++ ",
85 " ..ooooOooOOoOOOOOXX+++ ",
86 " ..ooooooOoOOOOOOOXX++++ ",
87 " ..o..oooOooOOoOOOOXX+XX+++ ",
88 " ...o..oooooOoOOOOOXX++XXX++ ",
89 " ....OO..ooOooOOoOOXX+++XXXX++ ",
90 " ...oo..+..oooOoOOOXX++XXooXXX++ ",
91 " ...ooo..++..OooOOoXX+++XXooOXXX+ ",
92 " ..oooOOXX+++....XXXX++++XXOOoOOXX+ ",
93 " ..oooOOXX+++ ...XXX+++++XXOOooOXX++ ",
94 " ..oooOXXX+++ ..XX+++ +XXOOooOXX++ ",
95 " .....XXX++++ XXXXXXX++ ",
96 " ....XX++++ XXXXXXX+ ",
103 static const char * nh_icon_small[] = {
104 /* width height ncolors chars_per_pixel */
144 static const char * map_xpm[] = {
164 static const char * msg_xpm[] = {
184 static const char * stat_xpm[] = {
206 static const char * info_xpm[] = {
228 static const char * again_xpm[] = {
246 static const char * kick_xpm[] = {
265 static const char * throw_xpm[] = {
284 static const char * fire_xpm[] = {
305 static const char * get_xpm[] = {
324 static const char * drop_xpm[] = {
343 static const char * eat_xpm[] = {
363 static const char * rest_xpm[] = {
381 static const char * cast_a_xpm[] = {
400 static const char * cast_b_xpm[] = {
419 static const char * cast_c_xpm[] = {
443 "Qt NetHack is a version of NetHack built\n"
445 "using KDE and the Qt GUI toolkit.\n"
447 "using the Qt GUI toolkit.\n"
449 "This is version %d.%d.%d\n\n"
450 "Homepage:\n http://trolls.troll.no/warwick/nethack/\n\n"
452 "KDE:\n http://www.kde.org\n"
454 "Qt:\n http://www.troll.no",
461 class SmallToolButton : public QToolButton {
463 SmallToolButton(const QPixmap & pm, const QString &textLabel,
464 const QString& grouptext,
465 QObject * receiver, const char* slot,
470 setToolTip(textLabel);
471 setStatusTip(grouptext);
472 connect(this, SIGNAL(clicked(bool)), receiver, slot);
475 QSize sizeHint() const
477 // get just a couple more pixels for the map
478 return QToolButton::sizeHint()-QSize(0,2);
482 NetHackQtMainWindow::NetHackQtMainWindow(NetHackQtKeyBuffer& ks) :
483 message(0), map(0), status(0), invusage(0),
484 hsplitter(0), vsplitter(0),
485 keysink(ks), dirkey(0)
487 QToolBar* toolbar = new QToolBar(this);
488 toolbar->setMovable(false);
489 toolbar->setFocusPolicy(Qt::NoFocus);
493 QCoreApplication::setOrganizationName("The NetHack DevTeam");
494 QCoreApplication::setOrganizationDomain("nethack.org");
495 QCoreApplication::setApplicationName("NetHack");
497 setWindowTitle("Qt NetHack");
498 if ( qt_compact_mode )
499 setWindowIcon(QIcon(QPixmap(nh_icon_small)));
501 setWindowIcon(QIcon(QPixmap(nh_icon)));
503 QMenu* game=new QMenu;
504 QMenu* apparel=new QMenu;
505 QMenu* act1=new QMenu;
506 QMenu* act2 = qt_compact_mode ? new QMenu : act1;
507 QMenu* magic=new QMenu;
508 QMenu* info=new QMenu;
513 help = kapp->getHelpMenu( true, "" );
514 help->addSeparator();
516 help = qt_compact_mode ? info : new QMenu;
519 enum { OnDesktop=1, OnHandhelds=2 };
527 { game, "Version", 3, doversion},
528 { game, "Compilation", 3, doextversion},
529 { game, "History", 3, dohistory},
530 { game, "Redraw", 0, doredraw}, // useless
531 { game, "Options", 3, doset},
532 { game, "Explore mode", 3, enter_explore_mode},
534 { game, "Save", 3, dosave},
535 { game, "Quit", 3, done2},
537 { apparel, "Apparel off", 2, doddoremarm},
538 { apparel, "Remove many", 1, doddoremarm},
540 { apparel, "Wield weapon", 3, dowield},
541 { apparel, "Exchange weapons", 3, doswapweapon},
542 { apparel, "Two weapon combat", 3, dotwoweapon},
543 { apparel, "Load quiver", 3, dowieldquiver},
545 { apparel, "Wear armour", 3, dowear},
546 { apparel, "Take off armour", 3, dotakeoff},
548 { apparel, "Put on non-armour", 3, doputon},
549 { apparel, "Remove non-armour", 3, doremring},
551 /* { act1, "Again\tCtrl+A", "\001", 2},
553 { act1, "Apply", 3, doapply},
554 { act1, "Chat", 3, dotalk},
555 { act1, "Close door", 3, doclose},
556 { act1, "Down", 3, dodown},
557 { act1, "Drop many", 2, doddrop},
558 { act1, "Drop", 2, dodrop},
559 { act1, "Eat", 2, doeat},
560 { act1, "Engrave", 3, doengrave},
561 /* { act1, "Fight\tShift+F", "F", 3}, */
562 { act1, "Fire from quiver", 2, dofire},
563 { act1, "Force", 3, doforce},
564 { act1, "Get", 2, dopickup},
565 { act1, "Jump", 3, dojump},
566 { act2, "Kick", 2, dokick},
567 { act2, "Loot", 3, doloot},
568 { act2, "Open door", 3, doopen},
569 { act2, "Pay", 3, dopay},
570 { act2, "Rest", 2, donull},
571 { act2, "Ride", 3, doride},
572 { act2, "Search", 3, dosearch},
573 { act2, "Sit", 3, dosit},
574 { act2, "Throw", 2, dothrow},
575 { act2, "Untrap", 3, dountrap},
576 { act2, "Up", 3, doup},
577 { act2, "Wipe face", 3, dowipe},
579 { magic, "Quaff potion", 3, dodrink},
580 { magic, "Read scroll/book", 3, doread},
581 { magic, "Zap wand", 3, dozap},
582 { magic, "Zap spell", 3, docast},
583 { magic, "Dip", 3, dodip},
584 { magic, "Rub", 3, dorub},
585 { magic, "Invoke", 3, doinvoke},
587 { magic, "Offer", 3, dosacrifice},
588 { magic, "Pray", 3, dopray},
590 { magic, "Teleport", 3, dotelecmd},
591 { magic, "Monster action", 3, domonability},
592 { magic, "Turn undead", 3, doturn},
594 { help, "Help", 3, dohelp},
596 { help, "What is here", 3, dolook},
597 { help, "What is there", 3, doquickwhatis},
598 { help, "What is...", 2, dowhatis},
601 { info, "Inventory", 3, ddoinv},
602 { info, "Conduct", 3, doconduct},
603 { info, "Discoveries", 3, dodiscovered},
604 { info, "List/reorder spells", 3, dovspell},
605 { info, "Adjust letters", 2, doorganize},
607 { info, "Name object or creature", 3, docallcmd},
609 { info, "Skills", 3, enhance_weapon_skill},
616 game->addAction("Qt settings...",this,SLOT(doQtSettings(bool)));
617 help->addAction("About Qt NetHack...",this,SLOT(doAbout(bool)));
618 //help->addAction("NetHack Guidebook...",this,SLOT(doGuidebook(bool)));
619 help->addSeparator();
621 for (i=0; item[i].menu; i++) {
622 if ( item[i].flags & (qt_compact_mode ? 1 : 2) ) {
625 char menuitem[BUFSZ];
628 actchar[0] = cmd_from_func(item[i].funct);
631 if (actchar[0] && !qt_compact_mode)
632 Sprintf(menuitem, "%s\t%s", item[i].name,
633 visctrl(actchar[0]));
635 Sprintf(menuitem, "%s", item[i].name);
637 QString name = menuitem;
638 QAction *action = item[i].menu->addAction(name);
639 action->setData(actchar);
642 item[i].menu->addSeparator();
647 game->setTitle("Game");
648 menubar->addMenu(game);
649 apparel->setTitle("Gear");
650 menubar->addMenu(apparel);
652 if ( qt_compact_mode ) {
653 act1->setTitle("A-J");
654 menubar->addMenu(act1);
655 act2->setTitle("K-Z");
656 menubar->addMenu(act2);
657 magic->setTitle("Magic");
658 menubar->addMenu(magic);
659 info->setIcon(QIcon(QPixmap(info_xpm)));
660 info->setTitle("Info");
661 menubar->addMenu(info);
662 //menubar->insertItem(QPixmap(map_xpm), this, SLOT(raiseMap()));
663 //menubar->insertItem(QPixmap(msg_xpm), this, SLOT(raiseMessages()));
664 //menubar->insertItem(QPixmap(stat_xpm), this, SLOT(raiseStatus()));
665 info->addSeparator();
666 info->addAction("Map", this, SLOT(raiseMap()));
667 info->addAction("Messages", this, SLOT(raiseMessages()));
668 info->addAction("Status", this, SLOT(raiseStatus()));
670 act1->setTitle("Action");
671 menubar->addMenu(act1);
672 magic->setTitle("Magic");
673 menubar->addMenu(magic);
674 info->setTitle("Info");
675 menubar->addMenu(info);
676 menubar->addSeparator();
677 help->setTitle("Help");
678 menubar->addMenu(help);
681 QSignalMapper* sm = new QSignalMapper(this);
682 connect(sm, SIGNAL(mapped(const QString&)), this, SLOT(doKeys(const QString&)));
685 tb = new SmallToolButton( QPixmap(again_xpm),"Again","Action", sm, SLOT(map()), toolbar );
686 Sprintf(actchar, "%c", Cmd.spkeys[NHKF_DOAGAIN]);
687 sm->setMapping(tb, actchar );
688 toolbar->addWidget(tb);
689 tb = new SmallToolButton( QPixmap(get_xpm),"Get","Action", sm, SLOT(map()), toolbar );
690 Sprintf(actchar, "%c", cmd_from_func(dopickup));
691 sm->setMapping(tb, actchar );
692 toolbar->addWidget(tb);
693 tb = new SmallToolButton( QPixmap(kick_xpm),"Kick","Action", sm, SLOT(map()), toolbar );
694 Sprintf(actchar, "%c", cmd_from_func(dokick));
695 sm->setMapping(tb, actchar );
696 toolbar->addWidget(tb);
697 tb = new SmallToolButton( QPixmap(throw_xpm),"Throw","Action", sm, SLOT(map()), toolbar );
698 Sprintf(actchar, "%c", cmd_from_func(dothrow));
699 sm->setMapping(tb, actchar );
700 toolbar->addWidget(tb);
701 tb = new SmallToolButton( QPixmap(fire_xpm),"Fire","Action", sm, SLOT(map()), toolbar );
702 Sprintf(actchar, "%c", cmd_from_func(dofire));
703 sm->setMapping(tb, actchar );
704 toolbar->addWidget(tb);
705 tb = new SmallToolButton( QPixmap(drop_xpm),"Drop","Action", sm, SLOT(map()), toolbar );
706 Sprintf(actchar, "%c", cmd_from_func(doddrop));
707 sm->setMapping(tb, actchar );
708 toolbar->addWidget(tb);
709 tb = new SmallToolButton( QPixmap(eat_xpm),"Eat","Action", sm, SLOT(map()), toolbar );
710 Sprintf(actchar, "%c", cmd_from_func(doeat));
711 sm->setMapping(tb, actchar );
712 toolbar->addWidget(tb);
713 tb = new SmallToolButton( QPixmap(rest_xpm),"Rest","Action", sm, SLOT(map()), toolbar );
714 Sprintf(actchar, "%c", cmd_from_func(donull));
715 sm->setMapping(tb, actchar );
716 toolbar->addWidget(tb);
718 connect(game,SIGNAL(triggered(QAction *)),this,SLOT(doMenuItem(QAction *)));
719 connect(apparel,SIGNAL(triggered(QAction *)),this,SLOT(doMenuItem(QAction *)));
720 connect(act1,SIGNAL(triggered(QAction *)),this,SLOT(doMenuItem(QAction *)));
722 connect(act2,SIGNAL(triggered(QAction *)),this,SLOT(doMenuItem(QAction *)));
723 connect(magic,SIGNAL(triggered(QAction *)),this,SLOT(doMenuItem(QAction *)));
724 connect(info,SIGNAL(triggered(QAction *)),this,SLOT(doMenuItem(QAction *)));
725 connect(help,SIGNAL(triggered(QAction *)),this,SLOT(doMenuItem(QAction *)));
732 int w=QApplication::desktop()->width()-10; // XXX arbitrary extra space for frame
733 int h=QApplication::desktop()->height()-50;
737 if (qt_tilewidth != NULL) {
738 maxwn = atoi(qt_tilewidth) * COLNO + 10;
742 if (qt_tileheight != NULL) {
743 maxhn = atoi(qt_tileheight) * ROWNO * 6/4;
748 // Be exactly the size we want to be - full map...
751 w=maxwn; // Doesn't need to be any wider
755 h=maxhn; // Doesn't need to be any taller
758 setGeometry(x,y,w,h);
760 if ( qt_compact_mode ) {
761 stack = new QStackedWidget(this);
762 setCentralWidget(stack);
764 vsplitter = new QSplitter(Qt::Vertical);
765 setCentralWidget(vsplitter);
766 hsplitter = new QSplitter(Qt::Horizontal);
767 invusage = new NetHackQtInvUsageWindow(hsplitter);
768 vsplitter->insertWidget(0, hsplitter);
769 hsplitter->insertWidget(1, invusage);
773 void NetHackQtMainWindow::zoomMap()
775 qt_settings->toggleGlyphSize();
778 void NetHackQtMainWindow::raiseMap()
780 if ( stack->currentWidget() == map->Widget() ) {
783 stack->setCurrentWidget(map->Widget());
787 void NetHackQtMainWindow::raiseMessages()
789 stack->setCurrentWidget(message->Widget());
792 void NetHackQtMainWindow::raiseStatus()
794 stack->setCurrentWidget(status->Widget());
797 #if 0 // RLC this isn't used
798 class NetHackMimeSourceFactory : public Q3MimeSourceFactory {
800 const QMimeSource* data(const QString& abs_name) const
802 const QMimeSource* r = 0;
803 if ( (NetHackMimeSourceFactory*)this == Q3MimeSourceFactory::defaultFactory() )
804 r = Q3MimeSourceFactory::data(abs_name);
806 r = Q3MimeSourceFactory::defaultFactory()->data(abs_name);
808 int sl = abs_name.length();
810 sl = abs_name.lastIndexOf('/',sl-1);
811 QString name = sl>=0 ? abs_name.mid(sl+1) : abs_name;
812 int dot = name.lastIndexOf('.');
814 name = name.left(dot);
816 r = new Q3ImageDrag(QImage(map_xpm));
817 else if ( name == "msg" )
818 r = new Q3ImageDrag(QImage(msg_xpm));
819 else if ( name == "stat" )
820 r = new Q3ImageDrag(QImage(stat_xpm));
821 } while (!r && sl>0);
828 void NetHackQtMainWindow::doMenuItem(QAction *action)
830 doKeys(action->data().toString());
833 void NetHackQtMainWindow::doQtSettings(bool)
835 centerOnMain(qt_settings);
839 void NetHackQtMainWindow::doAbout(bool)
841 QMessageBox::about(this, "About Qt NetHack", aboutMsg());
844 #if 0 // RLC this isn't used
845 void NetHackQtMainWindow::doGuidebook(bool)
848 new QVBoxLayout(&dlg);
849 Q3TextBrowser browser(&dlg);
850 NetHackMimeSourceFactory ms;
851 browser.setMimeSourceFactory(&ms);
852 browser.setSource(QDir::currentPath()+"/Guidebook.html");
853 if ( qt_compact_mode )
859 void NetHackQtMainWindow::doKeys(const QString& k)
861 keysink.Put(k.toLatin1().constData());
865 void NetHackQtMainWindow::AddMessageWindow(NetHackQtMessageWindow* window)
868 if (!qt_compact_mode)
869 hsplitter->insertWidget(0, message->Widget());
873 NetHackQtMessageWindow * NetHackQtMainWindow::GetMessageWindow()
878 void NetHackQtMainWindow::AddMapWindow(NetHackQtMapWindow2* window)
882 if (!qt_compact_mode)
883 vsplitter->insertWidget(1, map->Widget());
885 connect(map,SIGNAL(resized()),this,SLOT(layout()));
888 void NetHackQtMainWindow::AddStatusWindow(NetHackQtStatusWindow* window)
891 if (!qt_compact_mode)
892 hsplitter->insertWidget(2, status->Widget());
896 void NetHackQtMainWindow::RemoveWindow(NetHackQtWindow* window)
898 if (window==status) {
901 } else if (window==map) {
904 } else if (window==message) {
910 void NetHackQtMainWindow::updateInventory()
916 void NetHackQtMainWindow::fadeHighlighting()
919 status->fadeHighlighting();
923 void NetHackQtMainWindow::layout()
926 if ( qt_compact_mode )
928 if (message && map && status) {
929 QSize maxs=map->Widget()->maximumSize();
930 int maph=std::min(height()*2/3,maxs.height());
932 QWidget* c = centralWidget();
935 int iuw=3*qt_settings->glyphs().width();
936 int topw=(c->width()-iuw)/2;
938 message->Widget()->setGeometry(0,0,topw,toph);
939 invusage->setGeometry(topw,0,iuw,toph);
940 status->Widget()->setGeometry(topw+iuw,0,topw,toph);
941 map->Widget()->setGeometry(std::max(0,(c->width()-maxs.width())/2),
942 toph,c->width(),maph);
947 void NetHackQtMainWindow::resizeEvent(QResizeEvent*)
955 void NetHackQtMainWindow::keyReleaseEvent(QKeyEvent* event)
958 doKeys(QString(QChar(dirkey)));
959 if ( !event->isAutoRepeat() )
964 void NetHackQtMainWindow::keyPressEvent(QKeyEvent* event)
966 // Global key controls
968 // For desktop, arrow keys scroll map, since we don't want players
969 // to think that's the way to move. For handhelds, the normal way is to
970 // click-to-travel, so we allow the cursor keys for fine movements.
976 if ( event->isAutoRepeat() &&
977 event->key() >= Qt::Key_Left && event->key() <= Qt::Key_Down )
980 const char* d = Cmd.dirchars;
981 switch (event->key()) {
983 if ( dirkey == d[0] )
985 else if ( dirkey == d[4] )
989 break; case Qt::Key_Down:
990 if ( dirkey == d[0] )
992 else if ( dirkey == d[4] )
996 break; case Qt::Key_Left:
997 if ( dirkey == d[2] )
999 else if ( dirkey == d[6] )
1003 break; case Qt::Key_Right:
1004 if ( dirkey == d[2] )
1006 else if ( dirkey == d[6] )
1010 break; case Qt::Key_PageUp:
1012 if (message) message->Scroll(0,-1);
1013 break; case Qt::Key_PageDown:
1015 if (message) message->Scroll(0,+1);
1016 break; case Qt::Key_Space:
1017 if ( flags.rest_on_space ) {
1030 void NetHackQtMainWindow::closeEvent(QCloseEvent* e)
1032 if ( program_state.something_worth_saving ) {
1033 switch ( QMessageBox::information( this, "NetHack",
1034 "This will end your NetHack session",
1035 "&Save", "&Cancel", 0, 1 ) )
1038 // See dosave() function
1041 NetHackQtBind::qt_exit_nhwindows(0);
1042 nh_terminate(EXIT_SUCCESS);
1046 break; // ignore the event
1053 void NetHackQtMainWindow::ShowIfReady()
1055 if (message && map && status) {
1056 QWidget* hp = qt_compact_mode ? static_cast<QWidget *>(stack) : static_cast<QWidget *>(hsplitter);
1057 QWidget* vp = qt_compact_mode ? static_cast<QWidget *>(stack) : static_cast<QWidget *>(vsplitter);
1058 message->Widget()->setParent(hp);
1059 map->Widget()->setParent(vp);
1060 status->Widget()->setParent(hp);
1061 if ( qt_compact_mode ) {
1062 message->setMap(map);
1063 stack->addWidget(map->Widget());
1064 stack->addWidget(message->Widget());
1065 stack->addWidget(status->Widget());
1071 } else if (isVisible()) {
1076 } // namespace nethack_qt4