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 // qt4bind.cpp -- bindings between the Qt 4 interface and the main code
20 #include <QtGui/QtGui>
21 #include <QStringList>
22 #if QT_VERSION >= 0x050000
23 #include <QtWidgets/QtWidgets>
24 #include <QtMultimedia/QSound>
26 #include <QtGui/QSound>
51 extern int qt_compact_mode;
54 namespace nethack_qt4 {
56 // XXX Should be from Options
58 // XXX Hmm. Tricky part is that perhaps some macros should only be active
59 // XXX when a key is about to be gotten. For example, the user could
60 // XXX define "-" to do "E-yyyyyyyy\r", but would still need "-" for
61 // XXX other purposes. Maybe just too bad.
63 static struct key_macro_rec {
68 { Qt::Key_F1, 0, "n100." }, // Rest (x100)
69 { Qt::Key_F2, 0, "n20s" }, // Search (x20)
70 { Qt::Key_Tab, 0, "\001" },
74 NetHackQtBind::NetHackQtBind(int& argc, char** argv) :
76 KApplication(argc,argv)
77 #elif defined(QWS) // not quite the right condition
78 QPEApplication(argc,argv)
80 QApplication(argc,argv)
83 QPixmap pm("nhsplash.xpm");
84 if ( iflags.wc_splash_screen && !pm.isNull() ) {
85 splash = new QFrame(NULL,
86 Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint );
87 QVBoxLayout *vb = new QVBoxLayout(splash);
88 QLabel *lsplash = new QLabel(splash);
89 vb->addWidget(lsplash);
90 lsplash->setAlignment(Qt::AlignCenter);
91 lsplash->setPixmap(pm);
92 QLabel* capt = new QLabel("Loading...",splash);
94 capt->setAlignment(Qt::AlignCenter);
96 lsplash->setFixedSize(pm.size());
99 splash->move((QApplication::desktop()->width()-pm.width())/2,
100 (QApplication::desktop()->height()-pm.height())/2);
101 //splash->setGeometry(0,0,100,100);
102 if ( qt_compact_mode ) {
103 splash->showMaximized();
105 splash->setFrameStyle(QFrame::WinPanel|QFrame::Raised);
106 splash->setLineWidth(10);
107 splash->adjustSize();
111 // force content refresh outside event loop
120 main = new NetHackQtMainWindow(keybuffer);
121 connect(qApp, SIGNAL(lastWindowClosed()), qApp, SLOT(quit()));
122 qt_settings=new NetHackQtSettings(main->width(),main->height());
123 msgs_strings = new QStringList();
128 void NetHackQtBind::qt_init_nhwindows(int* argc, char** argv)
130 iflags.menu_tab_sep = true;
135 // Michael Hohmuth <hohmuth@inf.tu-dresden.de>...
137 // As the game runs setuid games, it must seteuid(getuid()) before
138 // calling XOpenDisplay(), and reset the euid afterwards.
139 // Otherwise, it can't read the $HOME/.Xauthority file and whines about
140 // not being able to open the X display (if a magic-cookie
141 // authorization mechanism is being used).
143 uid_t gamesuid=geteuid();
147 QApplication::setColorSpec(ManyColor);
148 instance=new NetHackQtBind(*argc,argv);
155 // This nethack engine feature should be moved into windowport API
156 nt_kbhit = NetHackQtBind::qt_kbhit;
160 int NetHackQtBind::qt_kbhit()
162 return !keybuffer.Empty();
166 static bool have_asked = false;
168 void NetHackQtBind::qt_player_selection()
174 void NetHackQtBind::qt_askname()
178 // We do it all here, and nothing in askname
180 char** saved = get_saved_games();
182 if ( saved && *saved ) {
183 if ( splash ) splash->hide();
184 NetHackQtSavedGameSelector sgsel((const char**)saved);
187 str_copy(plname, saved[ch], SIZE(plname));
189 free_saved_games(saved);
193 if ( splash ) splash->hide();
194 if (NetHackQtPlayerSelector(keybuffer).Choose())
204 qt_exit_nhwindows(0);
208 void NetHackQtBind::qt_get_nh_event()
213 // Kludge to access lastWindowClosed() signal.
214 class TApp : public QApplication {
216 TApp(int& c, char**v) : QApplication(c,v) {}
217 void lwc() { emit lastWindowClosed(); }
221 void NetHackQtBind::qt_exit_nhwindows(const char *)
224 // Avoids bug in SHARP SL5500
225 ((TApp*)qApp)->lwc();
229 delete instance; // ie. qApp
232 void NetHackQtBind::qt_suspend_nhwindows(const char *)
236 void NetHackQtBind::qt_resume_nhwindows()
240 static QVector<NetHackQtWindow*> id_to_window;
242 winid NetHackQtBind::qt_create_nhwindow(int type)
245 for (id = 0; id < (winid) id_to_window.size(); id++) {
246 if ( !id_to_window[(int)id] )
249 if ( id == (winid) id_to_window.size() )
250 id_to_window.resize(id+1);
252 NetHackQtWindow* window=0;
256 NetHackQtMapWindow2* w=new NetHackQtMapWindow2(clickbuffer);
257 main->AddMapWindow(w);
259 } break; case NHW_MESSAGE: {
260 NetHackQtMessageWindow* w=new NetHackQtMessageWindow;
261 main->AddMessageWindow(w);
263 } break; case NHW_STATUS: {
264 NetHackQtStatusWindow* w=new NetHackQtStatusWindow;
265 main->AddStatusWindow(w);
267 } break; case NHW_MENU:
268 window=new NetHackQtMenuOrTextWindow(mainWidget());
269 break; case NHW_TEXT:
270 window=new NetHackQtTextWindow(mainWidget());
275 // Note: use of isHidden does not work with Qt 2.1
277 #if QT_VERSION >= 300
288 id_to_window[(int)id] = window;
292 void NetHackQtBind::qt_clear_nhwindow(winid wid)
294 NetHackQtWindow* window=id_to_window[(int)wid];
298 void NetHackQtBind::qt_display_nhwindow(winid wid, BOOLEAN_P block)
300 NetHackQtWindow* window=id_to_window[(int)wid];
301 window->Display(block);
304 void NetHackQtBind::qt_destroy_nhwindow(winid wid)
306 NetHackQtWindow* window=id_to_window[(int)wid];
307 main->RemoveWindow(window);
308 if (window->Destroy())
310 id_to_window[(int)wid] = 0;
313 void NetHackQtBind::qt_curs(winid wid, int x, int y)
315 NetHackQtWindow* window=id_to_window[(int)wid];
316 window->CursorTo(x,y);
319 void NetHackQtBind::qt_putstr(winid wid, int attr, const char *text)
321 NetHackQtWindow* window=id_to_window[(int)wid];
322 window->PutStr(attr,QString::fromLatin1(text));
325 void NetHackQtBind::qt_putstr(winid wid, int attr, const std::string& text)
327 NetHackQtWindow* window=id_to_window[(int)wid];
328 window->PutStr(attr,QString::fromLatin1(text.c_str(), text.size()));
331 void NetHackQtBind::qt_putstr(winid wid, int attr, const QString& text)
333 NetHackQtWindow* window=id_to_window[(int)wid];
334 window->PutStr(attr,text);
337 void NetHackQtBind::qt_display_file(const char *filename, BOOLEAN_P must_exist)
339 NetHackQtTextWindow* window=new NetHackQtTextWindow(mainWidget());
340 bool complain = false;
348 f = dlb_fopen(filename, "r");
350 complain = must_exist;
352 while (dlb_fgets(buf, BUFSZ, f)) {
353 if ((cr = index(buf, '\n')) != 0) *cr = 0;
355 if ((cr = index(buf, '\r')) != 0) *cr = 0;
357 window->PutStr(ATR_NONE, tabexpand(buf));
359 window->Display(false);
360 (void) dlb_fclose(f);
366 message.sprintf("File not found: %s\n",filename);
367 QMessageBox::warning(NULL, "File Error", message, QMessageBox::Ignore);
371 void NetHackQtBind::qt_start_menu(winid wid)
373 NetHackQtWindow* window=id_to_window[(int)wid];
377 void NetHackQtBind::qt_add_menu(winid wid, int glyph,
378 const ANY_P * identifier, CHAR_P ch, CHAR_P gch, int attr,
379 const char *str, BOOLEAN_P presel)
381 NetHackQtWindow* window=id_to_window[(int)wid];
382 window->AddMenu(glyph, identifier, ch, gch, attr,
383 QString::fromLatin1(str),
387 void NetHackQtBind::qt_end_menu(winid wid, const char *prompt)
389 NetHackQtWindow* window=id_to_window[(int)wid];
390 window->EndMenu(prompt);
393 int NetHackQtBind::qt_select_menu(winid wid, int how, MENU_ITEM_P **menu_list)
395 NetHackQtWindow* window=id_to_window[(int)wid];
396 return window->SelectMenu(how,menu_list);
399 void NetHackQtBind::qt_update_inventory()
402 main->updateInventory();
404 if (program_state.something_worth_saving && flags.perm_invent)
405 display_inventory(NULL, false);
409 void NetHackQtBind::qt_mark_synch()
413 void NetHackQtBind::qt_wait_synch()
417 void NetHackQtBind::qt_cliparound(int x, int y)
419 // XXXNH - winid should be a parameter!
420 qt_cliparound_window(WIN_MAP,x,y);
423 void NetHackQtBind::qt_cliparound_window(winid wid, int x, int y)
425 NetHackQtWindow* window=id_to_window[(int)wid];
426 window->ClipAround(x,y);
428 void NetHackQtBind::qt_print_glyph(winid wid,XCHAR_P x,XCHAR_P y,int glyph,int bkglyph)
431 NetHackQtWindow* window=id_to_window[(int)wid];
432 window->PrintGlyph(x,y,glyph);
434 //void NetHackQtBind::qt_print_glyph_compose(winid wid,xchar x,xchar y,int glyph1, int glyph2)
436 //NetHackQtWindow* window=id_to_window[(int)wid];
437 //window->PrintGlyphCompose(x,y,glyph1,glyph2);
440 void NetHackQtBind::qt_raw_print(const char *str)
445 void NetHackQtBind::qt_raw_print_bold(const char *str)
450 int NetHackQtBind::qt_nhgetch()
453 main->fadeHighlighting();
455 // Process events until a key arrives.
457 while (keybuffer.Empty()) {
461 return keybuffer.GetAscii();
464 int NetHackQtBind::qt_nh_poskey(int *x, int *y, int *mod)
467 main->fadeHighlighting();
469 // Process events until a key or map-click arrives.
471 while (keybuffer.Empty() && clickbuffer.Empty()) {
474 if (!keybuffer.Empty()) {
475 return keybuffer.GetAscii();
477 *x=clickbuffer.NextX();
478 *y=clickbuffer.NextY();
479 *mod=clickbuffer.NextMod();
485 void NetHackQtBind::qt_nhbell()
487 QApplication::beep();
490 int NetHackQtBind::qt_doprev_message()
492 // Don't need it - uses scrollbar
493 // XXX but could make this a shortcut
497 char NetHackQtBind::qt_yn_function(const char *question_, const char *choices, CHAR_P def)
499 QString question(QString::fromLatin1(question_));
501 char yn_esc_map='\033';
504 // anything beyond <esc> is hidden>
505 QString choicebuf = choices;
506 size_t cb = choicebuf.indexOf('\033');
507 choicebuf = choicebuf.mid(0U, cb);
508 message = QString("%1 [%2] ").arg(question, choicebuf);
509 if (def) message += QString("(%1) ").arg(QChar(def));
510 // escape maps to 'q' or 'n' or default, in that order
511 yn_esc_map = (index(choices, 'q') ? 'q' :
512 (index(choices, 'n') ? 'n' : def));
517 if (qt_settings->ynInMessages() && WIN_MESSAGE!=WIN_ERR) {
518 // Similar to X11 windowport `slow' feature.
524 if (!strcmp(choices,"ynq"))
525 result = QMessageBox::information (NetHackQtBind::mainWidget(),"NetHack",question,"&Yes","&No","&Quit",0,2);
526 else if (!strcmp(choices,"yn"))
527 result = QMessageBox::information(NetHackQtBind::mainWidget(),"NetHack",question,"&Yes", "&No",0,1);
528 else if (!strcmp(choices, "rl"))
529 result = QMessageBox::information(NetHackQtBind::mainWidget(),"NetHack",question,"&Right", "&Left",0,1);
531 if (result >= 0 && result < strlen(choices)) {
532 char yn_resp = choices[result];
533 message += QString(" %1").arg(yn_resp);
539 NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message);
542 char ch=NetHackQtBind::qt_nhgetch();
545 } else if (choices && !index(choices,ch)) {
546 if (def && (ch==' ' || ch=='\r' || ch=='\n')) {
549 NetHackQtBind::qt_nhbell();
557 NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);
561 NetHackQtYnDialog dialog(mainWidget(),question,choices,def);
562 char ret = dialog.Exec();
563 if (!(ret == '\0' || ret == '\033') && choices)
564 message += QString(" %1").arg(ret);
566 message += QString(" %1").arg(def);
567 NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message);
571 void NetHackQtBind::qt_getlin(const char *prompt, char *line)
573 NetHackQtStringRequestor requestor(mainWidget(),prompt);
574 if (!requestor.Get(line)) {
579 int NetHackQtBind::qt_get_ext_cmd()
581 NetHackQtExtCmdRequestor requestor(mainWidget());
582 return requestor.get();
585 void NetHackQtBind::qt_number_pad(int)
590 void NetHackQtBind::qt_delay_output()
593 NetHackQtDelay delay(50);
598 void NetHackQtBind::qt_start_screen()
603 void NetHackQtBind::qt_end_screen()
608 void NetHackQtBind::qt_outrip(winid wid, int how, time_t when)
610 NetHackQtWindow* window=id_to_window[(int)wid];
611 window->UseRIP(how, when);
614 char * NetHackQtBind::qt_getmsghistory(BOOLEAN_P init)
616 NetHackQtMessageWindow* window = main->GetMessageWindow();
618 return (char *)window->GetStr(init);
622 void NetHackQtBind::qt_putmsghistory(const char *msg, BOOLEAN_P is_restoring)
624 NetHackQtMessageWindow* window = main->GetMessageWindow();
628 if (is_restoring && !msgs_initd) {
629 /* we're restoring history from the previous session, but new
630 messages have already been issued this session */
634 while ((str = window->GetStr((i == 0)))) {
635 msgs_strings->append(str);
639 msgs_saved = (i > 0);
640 window->ClearMessages();
644 //raw_printf("msg='%s'", msg);
645 window->PutStr(ATR_NONE, QString::fromLatin1(msg));
649 } else if (msgs_saved) {
650 /* restore strings */
652 for (i = 0; i < msgs_strings->size(); i++) {
653 window->PutStr(ATR_NONE, msgs_strings->at((i)));
655 dumplogmsg(msgs_strings->at(i).toLatin1().constData());
663 bool NetHackQtBind::notify(QObject *receiver, QEvent *event)
665 // Ignore Alt-key navigation to menubar, it's annoying when you
666 // use Alt-Direction to move around.
667 if ( main && event->type()==QEvent::KeyRelease && main==receiver
668 && ((QKeyEvent*)event)->key() == Qt::Key_Alt )
671 bool result=QApplication::notify(receiver,event);
672 if (event->type()==QEvent::KeyPress) {
673 QKeyEvent* key_event=(QKeyEvent*)event;
675 if (!key_event->isAccepted()) {
676 const int k=key_event->key();
678 for (int i=0; !macro && key_macro[i].key; i++) {
679 if (key_macro[i].key==k
680 && ((key_macro[i].state&key_event->modifiers())==key_macro[i].state))
682 keybuffer.Put(key_macro[i].macro);
686 QString key=key_event->text();
687 QChar ch = !key.isEmpty() ? key.at(0) : 0;
688 if (ch > 128) ch = 0;
689 if ( ch == 0 && (key_event->modifiers() & Qt::ControlModifier) ) {
690 // On Mac, ascii control codes are not sent, force them.
691 if ( k>=Qt::Key_A && k<=Qt::Key_Z )
692 ch = k - Qt::Key_A + 1;
694 if (!macro && ch != 0) {
695 bool alt = (key_event->modifiers()&Qt::AltModifier) ||
696 (k >= Qt::Key_0 && k <= Qt::Key_9 && (key_event->modifiers()&Qt::ControlModifier));
697 keybuffer.Put(key_event->key(),ch.cell() + (alt ? 128 : 0),
698 key_event->modifiers());
703 if (ch != 0 || macro) {
711 NetHackQtBind* NetHackQtBind::instance=0;
712 NetHackQtKeyBuffer NetHackQtBind::keybuffer;
713 NetHackQtClickBuffer NetHackQtBind::clickbuffer;
714 NetHackQtMainWindow* NetHackQtBind::main=0;
715 QFrame* NetHackQtBind::splash=0;
716 QStringList *NetHackQtBind::msgs_strings;
717 boolean NetHackQtBind::msgs_saved = false;
718 boolean NetHackQtBind::msgs_initd = false;
720 static void Qt_positionbar(char *) {}
722 } // namespace nethack_qt4
724 struct window_procs Qt_procs = {
726 WC_COLOR | WC_HILITE_PET
727 | WC_ASCII_MAP | WC_TILED_MAP
728 | WC_FONT_MAP | WC_TILE_FILE | WC_TILE_WIDTH | WC_TILE_HEIGHT
729 | WC_PLAYER_SELECTION | WC_SPLASH_SCREEN,
731 nethack_qt4::NetHackQtBind::qt_init_nhwindows,
732 nethack_qt4::NetHackQtBind::qt_player_selection,
733 nethack_qt4::NetHackQtBind::qt_askname,
734 nethack_qt4::NetHackQtBind::qt_get_nh_event,
735 nethack_qt4::NetHackQtBind::qt_exit_nhwindows,
736 nethack_qt4::NetHackQtBind::qt_suspend_nhwindows,
737 nethack_qt4::NetHackQtBind::qt_resume_nhwindows,
738 nethack_qt4::NetHackQtBind::qt_create_nhwindow,
739 nethack_qt4::NetHackQtBind::qt_clear_nhwindow,
740 nethack_qt4::NetHackQtBind::qt_display_nhwindow,
741 nethack_qt4::NetHackQtBind::qt_destroy_nhwindow,
742 nethack_qt4::NetHackQtBind::qt_curs,
743 nethack_qt4::NetHackQtBind::qt_putstr,
745 nethack_qt4::NetHackQtBind::qt_display_file,
746 nethack_qt4::NetHackQtBind::qt_start_menu,
747 nethack_qt4::NetHackQtBind::qt_add_menu,
748 nethack_qt4::NetHackQtBind::qt_end_menu,
749 nethack_qt4::NetHackQtBind::qt_select_menu,
750 genl_message_menu, /* no need for X-specific handling */
751 nethack_qt4::NetHackQtBind::qt_update_inventory,
752 nethack_qt4::NetHackQtBind::qt_mark_synch,
753 nethack_qt4::NetHackQtBind::qt_wait_synch,
755 nethack_qt4::NetHackQtBind::qt_cliparound,
758 nethack_qt4::Qt_positionbar,
760 nethack_qt4::NetHackQtBind::qt_print_glyph,
761 //NetHackQtBind::qt_print_glyph_compose,
762 nethack_qt4::NetHackQtBind::qt_raw_print,
763 nethack_qt4::NetHackQtBind::qt_raw_print_bold,
764 nethack_qt4::NetHackQtBind::qt_nhgetch,
765 nethack_qt4::NetHackQtBind::qt_nh_poskey,
766 nethack_qt4::NetHackQtBind::qt_nhbell,
767 nethack_qt4::NetHackQtBind::qt_doprev_message,
768 nethack_qt4::NetHackQtBind::qt_yn_function,
769 nethack_qt4::NetHackQtBind::qt_getlin,
770 nethack_qt4::NetHackQtBind::qt_get_ext_cmd,
771 nethack_qt4::NetHackQtBind::qt_number_pad,
772 nethack_qt4::NetHackQtBind::qt_delay_output,
773 #ifdef CHANGE_COLOR /* only a Mac option currently */
779 /* other defs that really should go away (they're tty specific) */
780 nethack_qt4::NetHackQtBind::qt_start_screen,
781 nethack_qt4::NetHackQtBind::qt_end_screen,
782 #ifdef GRAPHIC_TOMBSTONE
783 nethack_qt4::NetHackQtBind::qt_outrip,
787 genl_preference_update,
789 nethack_qt4::NetHackQtBind::qt_getmsghistory,
790 nethack_qt4::NetHackQtBind::qt_putmsghistory,
792 genl_status_finish, genl_status_enablefield,
793 #ifdef STATUS_HILITES
798 genl_can_suspend_yes,
801 extern "C" void play_usersound(const char* filename, int volume)
805 QSound::play(filename);