OSDN Git Service

shrink mine
[nethackexpress/trunk.git] / win / Qt / qt_win.cpp
1 //      SCCS Id: @(#)qt_win.cpp 3.4     1999/11/19
2 // Copyright (c) Warwick Allison, 1999.
3 // NetHack may be freely redistributed.  See license for details.
4
5 // Qt Binding for NetHack 3.4
6 //
7 // Copyright (C) 1996-2001 by Warwick W. Allison (warwick@troll.no)
8 // 
9 // Contributors:
10 //    Michael Hohmuth <hohmuth@inf.tu-dresden.de>
11 //       - Userid control
12 //    Svante Gerhard <svante@algonet.se>
13 //       - .nethackrc tile and font size settings
14 //    Dirk Schoenberger <schoenberger@signsoft.com>
15 //       - KDE support
16 //       - SlashEm support
17 //    and many others for bug reports.
18 // 
19 // Unfortunately, this doesn't use Qt as well as I would like,
20 // primarily because NetHack is fundamentally a getkey-type program
21 // rather than being event driven (hence the ugly key and click buffer)
22 // and also because this is my first major application of Qt.  
23 // 
24 // The problem of NetHack's getkey requirement is solved by intercepting
25 // key events by overiding QApplicion::notify(...), and putting them in
26 // a buffer.  Mouse clicks on the map window are treated with a similar
27 // buffer.  When the NetHack engine calls for a key, one is taken from
28 // the buffer, or if that is empty, QApplication::enter_loop() is called.
29 // Whenever keys or clicks go into the buffer, QApplication::exit_loop()
30 // is called.
31 //
32 // Another problem is that some NetHack players are decade-long players who
33 // demand complete keyboard control (while Qt and X11 conspire to make this
34 // difficult by having widget-based focus rather than application based -
35 // a good thing in general).  This problem is solved by again using the key
36 // event buffer.
37 //
38 // Out of all this hackery comes a silver lining however, as macros for
39 // the super-expert and menus for the ultra-newbie are also made possible
40 // by the key event buffer.
41 //
42
43 extern "C" {
44
45 // This includes all the definitions we need from the NetHack main
46 // engine.  We pretend MSC is a STDC compiler, because C++ is close
47 // enough, and we undefine NetHack macros which conflict with Qt
48 // identifiers.
49
50 #define alloc hide_alloc // avoid treading on STL symbol
51 #define lock hide_lock // avoid treading on STL symbol
52 #ifdef _MSC_VER
53 #define NHSTDC
54 #endif
55 #include "hack.h"
56 #include "func_tab.h"
57 #include "dlb.h"
58 #include "patchlevel.h"
59 #include "tile2x11.h"
60 #undef Invisible
61 #undef Warning
62 #undef red
63 #undef green
64 #undef blue
65 #undef Black
66 #undef curs
67 #undef TRUE
68 #undef FALSE
69 #undef min
70 #undef max
71 #undef alloc
72 #undef lock
73 #undef yn
74
75 }
76
77 #include "qt_win.h"
78 #include <qregexp.h>
79 #include <qpainter.h>
80 #include <qdir.h>
81 #include <qbitmap.h>
82 #include <qkeycode.h>
83 #include <qmenubar.h>
84 #include <qpopupmenu.h>
85 #include <qlayout.h>
86 #include <qheader.h>
87 #include <qradiobutton.h>
88 #include <qtoolbar.h>
89 #include <qtoolbutton.h>
90 #include <qcombobox.h>
91 #include <qvbox.h>
92 #include <qdragobject.h>
93 #include <qtextbrowser.h>
94 #include <qhbox.h>
95 #include <qsignalmapper.h>
96 //#include <qgrid.h>
97 //#include <qlabelled.h>
98
99 #include <ctype.h>
100
101 #include "qt_clust.h"
102 #include "qt_xpms.h"
103
104 #include <dirent.h>
105 #ifdef Q_WS_MACX
106 #  include <sys/malloc.h>
107 #else
108 #  include <malloc.h>
109 #endif
110
111 #ifdef _WS_X11_
112 // For userid control
113 #include <unistd.h>
114 #endif
115
116 // Some distributors released Qt 2.1.0beta4
117 #if QT_VERSION < 220
118 # define nh_WX11BypassWM 0x01000000
119 #else
120 # define nh_WX11BypassWM WX11BypassWM
121 #endif
122
123 #ifdef USER_SOUNDS
124 # if QT_VERSION < 220
125 #  undef USER_SOUNDS
126 # else
127 #  include <qsound.h>
128 # endif
129 #endif
130
131
132 #ifdef USER_SOUNDS
133 extern "C" void play_sound_for_message(const char* str);
134 #endif
135
136 // Warwick prefers it this way...
137 #define QT_CHOOSE_RACE_FIRST
138
139 static const char nh_attribution[] = "<center><big>NetHack</big>"
140         "<br><small>by the NetHack DevTeam</small></center>";
141
142 static QString
143 aboutMsg()
144 {
145     QString msg;
146     msg.sprintf(
147     "Qt NetHack is a version of NetHack built\n"
148 #ifdef KDE
149     "using KDE and the Qt GUI toolkit.\n"
150 #else
151     "using the Qt GUI toolkit.\n"
152 #endif
153     "This is version %d.%d.%d\n\n"
154     "Homepage:\n     http://trolls.troll.no/warwick/nethack/\n\n"
155 #ifdef KDE
156           "KDE:\n     http://www.kde.org\n"
157 #endif
158           "Qt:\n     http://www.troll.no",
159         VERSION_MAJOR,
160         VERSION_MINOR,
161         PATCHLEVEL);
162     return msg;
163 }
164
165 static void
166 centerOnMain( QWidget* w )
167 {
168     QWidget* m = qApp->mainWidget();
169     if (!m) m = qApp->desktop();
170     QPoint p = m->mapToGlobal(QPoint(0,0));
171     w->move( p.x() + m->width()/2  - w->width()/2,
172               p.y() + m->height()/2 - w->height()/2 );
173 }
174
175 NetHackQtLineEdit::NetHackQtLineEdit() :
176     QLineEdit(0)
177 {
178 }
179
180 NetHackQtLineEdit::NetHackQtLineEdit(QWidget* parent, const char* name) :
181     QLineEdit(parent,name)
182 {
183 }
184
185 void NetHackQtLineEdit::fakeEvent(int key, int ascii, int state)
186 {
187     QKeyEvent fake(QEvent::KeyPress,key,ascii,state);
188     keyPressEvent(&fake);
189 }
190
191 extern "C" {
192 /* Used by tile/font-size patch below and in ../../src/files.c */
193 char *qt_tilewidth=NULL;
194 char *qt_tileheight=NULL;
195 char *qt_fontsize=NULL;
196 #if defined(QWS)
197 int qt_compact_mode = 1;
198 #else
199 int qt_compact_mode = 0;
200 #endif
201 extern const char *enc_stat[]; /* from botl.c */
202 extern const char *hu_stat[]; /* from eat.c */
203 extern const char *killed_by_prefix[];
204 extern int total_tiles_used; // from tile.c
205 extern short glyph2tile[]; // from tile.c
206 }
207
208 static int tilefile_tile_W=16;
209 static int tilefile_tile_H=16;
210
211 #define TILEWMIN 1
212 #define TILEHMIN 1
213
214
215 /* XPM */
216 static const char * nh_icon[] = {
217 "40 40 6 1",
218 "       s None c none",
219 ".      c #ffffff",
220 "X      c #dadab6",
221 "o      c #6c91b6",
222 "O      c #476c6c",
223 "+      c #000000",
224 "                                        ",
225 "                                        ",
226 "                                        ",
227 "        .      .X..XX.XX      X         ",
228 "        ..   .....X.XXXXXX   XX         ",
229 "        ... ....X..XX.XXXXX XXX         ",
230 "   ..   ..........X.XXXXXXXXXXX   XX    ",
231 "   .... ........X..XX.XXXXXXXXX XXXX    ",
232 "   .... ..........X.XXXXXXXXXXX XXXX    ",
233 "   ooOOO..ooooooOooOOoOOOOOOOXX+++OO++  ",
234 "   ooOOO..ooooooooOoOOOOOOOOOXX+++OO++  ",
235 "   ....O..ooooooOooOOoOOOOOOOXX+XXXX++  ",
236 "   ....O..ooooooooOoOOOOOOOOOXX+XXXX++  ",
237 "   ..OOO..ooooooOooOOoOOOOOOOXX+++XX++  ",
238 "    ++++..ooooooooOoOOOOOOOOOXX+++ +++  ",
239 "     +++..ooooooOooOOoOOOOOOOXX+++  +   ",
240 "      ++..ooooooooOoOOOOOOOOOXX+++      ",
241 "        ..ooooooOooOOoOOOOOOOXX+++      ",
242 "        ..ooooooooOoOOOOOOOOOXX+++      ",
243 "        ..ooooooOooOOoOOOOOOOXX+++      ",
244 "        ..ooooooooOoOOOOOOOOOXX+++      ",
245 "         ..oooooOooOOoOOOOOOXX+++       ",
246 "         ..oooooooOoOOOOOOOOXX+++       ",
247 "          ..ooooOooOOoOOOOOXX+++        ",
248 "          ..ooooooOoOOOOOOOXX++++       ",
249 "        ..o..oooOooOOoOOOOXX+XX+++      ",
250 "       ...o..oooooOoOOOOOXX++XXX++      ",
251 "      ....OO..ooOooOOoOOXX+++XXXX++     ",
252 "     ...oo..+..oooOoOOOXX++XXooXXX++    ",
253 "    ...ooo..++..OooOOoXX+++XXooOXXX+    ",
254 "   ..oooOOXX+++....XXXX++++XXOOoOOXX+   ",
255 "   ..oooOOXX+++ ...XXX+++++XXOOooOXX++  ",
256 "   ..oooOXXX+++  ..XX+++  +XXOOooOXX++  ",
257 "   .....XXX++++             XXXXXXX++   ",
258 "    ....XX++++              XXXXXXX+    ",
259 "     ...XX+++                XXXXX++    ",
260 "                                        ",
261 "                                        ",
262 "                                        ",
263 "                                        "};
264 /* XPM */
265 static const char * nh_icon_small[] = {
266 /* width height ncolors chars_per_pixel */
267 "16 16 16 1",
268 /* colors */
269 "  c #587070",
270 ". c #D1D5C9",
271 "X c #8B8C84",
272 "o c #2A2A28",
273 "O c #9AABA9",
274 "+ c #6A8FB2",
275 "@ c #C4CAC4",
276 "# c #B6BEB6",
277 "$ c None",
278 "% c #54564E",
279 "& c #476C6C",
280 "* c #ADB2AB",
281 "= c #ABABA2",
282 "- c #5E8295",
283 "; c #8B988F",
284 ": c #E8EAE7",
285 /* pixels */
286 "$$$$$$$$$$$$$$$$",
287 "$$$.$#::.#==*$$$",
288 "$.*:::::....#*=$",
289 "$@#:..@#*==#;XX;",
290 "$@O:+++- &&; X%X",
291 "$#%.+++- &&;% oX",
292 "$$o.++-- &&;%%X$",
293 "$$$:++-- &&;%%$$",
294 "$$$.O++- &&=o $$",
295 "$$$=:++- & XoX$$",
296 "$$*:@O--  ;%Xo$$",
297 "$*:O#$+--;oOOX $",
298 "$:+ =o::=oo=-;%X",
299 "$::.%o$*;X;##@%$",
300 "$$@# ;$$$$$=*;X$",
301 "$$$$$$$$$$$$$$$$"
302 };
303
304 /* XPM */
305 static const char * map_xpm[] = {
306 "12 13 4 1",
307 ".      c None",
308 "       c #000000000000",
309 "X      c #0000B6DAFFFF",
310 "o      c #69A69248B6DA",
311 "           .",
312 " XXXXX ooo  ",
313 " XoooX o    ",
314 " XoooX o o  ",
315 " XoooX ooo  ",
316 " XXoXX o    ",
317 "  oooooXXX  ",
318 " oo o oooX  ",
319 "    o XooX  ",
320 " oooo XooX  ",
321 " o  o XXXX  ",
322 "            ",
323 ".           "};
324 /* XPM */
325 static const char * msg_xpm[] = {
326 "12 13 4 1",
327 ".      c None",
328 "       c #FFFFFFFFFFFF",
329 "X      c #69A69248B6DA",
330 "o      c #000000000000",
331 "           .",
332 " XXX XXX X o",
333 "           o",
334 " XXXXX XX  o",
335 "           o",
336 " XX XXXXX  o",
337 "           o",
338 " XXXXXX    o",
339 "           o",
340 " XX XXX XX o",
341 "           o",
342 "           o",
343 ".ooooooooooo"};
344 /* XPM */
345 static const char * stat_xpm[] = {
346 "12 13 5 1",
347 "  c None",
348 ".      c #FFFF00000000",
349 "X      c #000000000000",
350 "o      c #FFFFFFFF0000",
351 "O      c #69A6FFFF0000",
352 "            ",
353 "            ",
354 "...         ",
355 "...X        ",
356 "...X    ... ",
357 "oooX    oooX",
358 "oooXooo oooX",
359 "OOOXOOOXOOOX",
360 "OOOXOOOXOOOX",
361 "OOOXOOOXOOOX",
362 "OOOXOOOXOOOX",
363 "OOOXOOOXOOOX",
364 " XXXXXXXXXXX"};
365 /* XPM */
366 static const char * info_xpm[] = {
367 "12 13 4 1",
368 "  c None",
369 ".      c #00000000FFFF",
370 "X      c #FFFFFFFFFFFF",
371 "o      c #000000000000",
372 "    ...     ",
373 "  .......   ",
374 " ...XXX...  ",
375 " .........o ",
376 "...XXXX.... ",
377 "....XXX....o",
378 "....XXX....o",
379 "....XXX....o",
380 " ...XXX...oo",
381 " ..XXXXX..o ",
382 "  .......oo ",
383 "   o...ooo  ",
384 "     ooo    "};
385
386
387 /* XPM */
388 static const char * again_xpm[] = {
389 "12 13 2 1",
390 "       c None",
391 ".      c #000000000000",
392 "    ..      ",
393 "     ..     ",
394 "   .....    ",
395 " .......    ",
396 "...  ..  .. ",
397 "..  ..   .. ",
398 "..        ..",
399 "..        ..",
400 "..        ..",
401 " ..      .. ",
402 " .......... ",
403 "   ......   ",
404 "            "};
405 /* XPM */
406 static const char * kick_xpm[] = {
407 "12 13 3 1",
408 "       c None",
409 ".      c #000000000000",
410 "X      c #FFFF6DB60000",
411 "            ",
412 "            ",
413 "   .  .  .  ",
414 "  ...  .  . ",
415 "   ...  .   ",
416 "    ...  .  ",
417 "     ...    ",
418 "XXX   ...   ",
419 "XXX.  ...   ",
420 "XXX. ...    ",
421 "XXX. ..     ",
422 " ...        ",
423 "            "};
424 /* XPM */
425 static const char * throw_xpm[] = {
426 "12 13 3 1",
427 "       c None",
428 ".      c #FFFF6DB60000",
429 "X      c #000000000000",
430 "            ",
431 "            ",
432 "            ",
433 "            ",
434 "....     X  ",
435 "....X     X ",
436 "....X XXXXXX",
437 "....X     X ",
438 " XXXX    X  ",
439 "            ",
440 "            ",
441 "            ",
442 "            "};
443 /* XPM */
444 static const char * fire_xpm[] = {
445 "12 13 5 1",
446 "       c None",
447 ".      c #B6DA45140000",
448 "X      c #FFFFB6DA9658",
449 "o      c #000000000000",
450 "O      c #FFFF6DB60000",
451 " .          ",
452 " X.         ",
453 " X .        ",
454 " X .o       ",
455 " X  .    o  ",
456 " X  .o    o ",
457 "OOOOOOOOoooo",
458 " X  .o    o ",
459 " X . o   o  ",
460 " X .o       ",
461 " X. o       ",
462 " . o        ",
463 "  o         "};
464 /* XPM */
465 static const char * get_xpm[] = {
466 "12 13 3 1",
467 "       c None",
468 ".      c #000000000000",
469 "X      c #FFFF6DB60000",
470 "            ",
471 "     .      ",
472 "    ...     ",
473 "   . . .    ",
474 "     .      ",
475 "     .      ",
476 "            ",
477 "   XXXXX    ",
478 "   XXXXX.   ",
479 "   XXXXX.   ",
480 "   XXXXX.   ",
481 "    .....   ",
482 "            "};
483 /* XPM */
484 static const char * drop_xpm[] = {
485 "12 13 3 1",
486 "       c None",
487 ".      c #FFFF6DB60000",
488 "X      c #000000000000",
489 "            ",
490 "   .....    ",
491 "   .....X   ",
492 "   .....X   ",
493 "   .....X   ",
494 "    XXXXX   ",
495 "            ",
496 "      X     ",
497 "      X     ",
498 "    X X X   ",
499 "     XXX    ",
500 "      X     ",
501 "            "};
502 /* XPM */
503 static const char * eat_xpm[] = {
504 "12 13 4 1",
505 "       c None",
506 ".      c #000000000000",
507 "X      c #FFFFB6DA9658",
508 "o      c #FFFF6DB60000",
509 "  .X.  ..   ",
510 "  .X.  ..   ",
511 "  .X.  ..   ",
512 "  .X.  ..   ",
513 "  ...  ..   ",
514 "   ..  ..   ",
515 "   ..  ..   ",
516 "   oo  oo   ",
517 "   oo  oo   ",
518 "   oo  oo   ",
519 "   oo  oo   ",
520 "   oo  oo   ",
521 "   oo  oo   "};
522 /* XPM */
523 static const char * rest_xpm[] = {
524 "12 13 2 1",
525 "       c None",
526 ".      c #000000000000",
527 "  .....     ",
528 "     .      ",
529 "    .       ",
530 "   .    ....",
531 "  .....   . ",
532 "         .  ",
533 "        ....",
534 "            ",
535 "     ....   ",
536 "       .    ",
537 "      .     ",
538 "     ....   ",
539 "            "};
540 /* XPM */
541 static const char * cast_a_xpm[] = {
542 "12 13 3 1",
543 "       c None",
544 ".      c #FFFF6DB60000",
545 "X      c #000000000000",
546 "    .       ",
547 "    .       ",
548 "   ..       ",
549 "   ..       ",
550 "  ..  .     ",
551 "  ..  .     ",
552 " ......     ",
553 " .. ..  XX  ",
554 "    .. X  X ",
555 "   ..  X  X ",
556 "   ..  XXXX ",
557 "   .   X  X ",
558 "   .   X  X "};
559 /* XPM */
560 static const char * cast_b_xpm[] = {
561 "12 13 3 1",
562 "       c None",
563 ".      c #FFFF6DB60000",
564 "X      c #000000000000",
565 "    .       ",
566 "    .       ",
567 "   ..       ",
568 "   ..       ",
569 "  ..  .     ",
570 "  ..  .     ",
571 " ......     ",
572 " .. .. XXX  ",
573 "    .. X  X ",
574 "   ..  XXX  ",
575 "   ..  X  X ",
576 "   .   X  X ",
577 "   .   XXX  "};
578 /* XPM */
579 static const char * cast_c_xpm[] = {
580 "12 13 3 1",
581 "       c None",
582 ".      c #FFFF6DB60000",
583 "X      c #000000000000",
584 "    .       ",
585 "    .       ",
586 "   ..       ",
587 "   ..       ",
588 "  ..  .     ",
589 "  ..  .     ",
590 " ......     ",
591 " .. ..  XX  ",
592 "    .. X  X ",
593 "   ..  X    ",
594 "   ..  X    ",
595 "   .   X  X ",
596 "   .    XX  "};
597
598 NetHackQtSettings::NetHackQtSettings(int w, int h) :
599     tilewidth(TILEWMIN,64,1,this),
600     tileheight(TILEHMIN,64,1,this),
601     widthlbl(&tilewidth,"&Width:",this),
602     heightlbl(&tileheight,"&Height:",this),
603     whichsize("&Zoomed",this),
604     fontsize(this),
605     normal("times"),
606 #ifdef WS_WIN
607     normalfixed("courier new"),
608 #else
609     normalfixed("fixed"),
610 #endif
611     large("times"),
612     theglyphs(0)
613
614 {
615     int default_fontsize;
616
617     if (w<=300) {
618         // ~240x320
619         default_fontsize=4;
620         tilewidth.setValue(8);
621         tileheight.setValue(12);
622     } else if (w<=700) {
623         // ~640x480
624         default_fontsize=3;
625         tilewidth.setValue(8);
626         tileheight.setValue(14);
627     } else if (w<=900) {
628         // ~800x600
629         default_fontsize=3;
630         tilewidth.setValue(10);
631         tileheight.setValue(17);
632     } else if (w<=1100) {
633         // ~1024x768
634         default_fontsize=2;
635         tilewidth.setValue(12);
636         tileheight.setValue(22);
637     } else if (w<=1200) {
638         // ~1152x900
639         default_fontsize=1;
640         tilewidth.setValue(14);
641         tileheight.setValue(26);
642     } else {
643         // ~1280x1024 and larger
644         default_fontsize=0;
645         tilewidth.setValue(16);
646         tileheight.setValue(30);
647     }
648
649     // Tile/font sizes read from .nethackrc
650     if (qt_tilewidth != NULL) {
651         tilewidth.setValue(atoi(qt_tilewidth));
652         free(qt_tilewidth);
653     }
654     if (qt_tileheight != NULL) {
655         tileheight.setValue(atoi(qt_tileheight));
656         free(qt_tileheight);
657     }
658     if (qt_fontsize != NULL) {
659         switch (tolower(qt_fontsize[0])) {
660           case 'h': default_fontsize = 0; break;
661           case 'l': default_fontsize = 1; break;
662           case 'm': default_fontsize = 2; break;
663           case 's': default_fontsize = 3; break;
664           case 't': default_fontsize = 4; break;
665         }
666         free(qt_fontsize); 
667     }
668
669     theglyphs=new NetHackQtGlyphs();
670     resizeTiles();
671
672     connect(&tilewidth,SIGNAL(valueChanged(int)),this,SLOT(resizeTiles()));
673     connect(&tileheight,SIGNAL(valueChanged(int)),this,SLOT(resizeTiles()));
674     connect(&whichsize,SIGNAL(toggled(bool)),this,SLOT(setGlyphSize(bool)));
675
676     fontsize.insertItem("Huge");
677     fontsize.insertItem("Large");
678     fontsize.insertItem("Medium");
679     fontsize.insertItem("Small");
680     fontsize.insertItem("Tiny");
681     fontsize.setCurrentItem(default_fontsize);
682     connect(&fontsize,SIGNAL(activated(int)),this,SIGNAL(fontChanged()));
683
684     QGridLayout* grid = new QGridLayout(this, 5, 2, 8);
685     grid->addMultiCellWidget(&whichsize, 0, 0, 0, 1);
686     grid->addWidget(&tilewidth, 1, 1);  grid->addWidget(&widthlbl, 1, 0);
687     grid->addWidget(&tileheight, 2, 1); grid->addWidget(&heightlbl, 2, 0);
688     QLabel* flabel=new QLabel(&fontsize, "&Font:",this);
689     grid->addWidget(flabel, 3, 0); grid->addWidget(&fontsize, 3, 1);
690     QPushButton* dismiss=new QPushButton("Dismiss",this);
691     dismiss->setDefault(TRUE);
692     grid->addMultiCellWidget(dismiss, 4, 4, 0, 1);
693     grid->setRowStretch(4,0);
694     grid->setColStretch(1,1);
695     grid->setColStretch(2,2);
696     grid->activate();
697
698     connect(dismiss,SIGNAL(clicked()),this,SLOT(accept()));
699     resize(150,140);
700 }
701
702 NetHackQtGlyphs& NetHackQtSettings::glyphs()
703 {
704     return *theglyphs;
705 }
706
707 void NetHackQtSettings::resizeTiles()
708 {
709     int w = tilewidth.value();
710     int h = tileheight.value();
711
712     theglyphs->setSize(w,h);
713     emit tilesChanged();
714 }
715
716 void NetHackQtSettings::toggleGlyphSize()
717 {
718     whichsize.toggle();
719 }
720
721 void NetHackQtSettings::setGlyphSize(bool which)
722 {
723     QSize n = QSize(tilewidth.value(),tileheight.value());
724     if ( othersize.isValid() ) {
725         tilewidth.blockSignals(TRUE);
726         tileheight.blockSignals(TRUE);
727         tilewidth.setValue(othersize.width());
728         tileheight.setValue(othersize.height());
729         tileheight.blockSignals(FALSE);
730         tilewidth.blockSignals(FALSE);
731         resizeTiles();
732     }
733     othersize = n;
734 }
735
736 const QFont& NetHackQtSettings::normalFont()
737 {
738     static int size[]={ 18, 14, 12, 10, 8 };
739     normal.setPointSize(size[fontsize.currentItem()]);
740     return normal;
741 }
742
743 const QFont& NetHackQtSettings::normalFixedFont()
744 {
745     static int size[]={ 18, 14, 13, 10, 8 };
746     normalfixed.setPointSize(size[fontsize.currentItem()]);
747     return normalfixed;
748 }
749
750 const QFont& NetHackQtSettings::largeFont()
751 {
752     static int size[]={ 24, 18, 14, 12, 10 };
753     large.setPointSize(size[fontsize.currentItem()]);
754     return large;
755 }
756
757 bool NetHackQtSettings::ynInMessages()
758 {
759     return !qt_compact_mode;
760 }
761
762
763 NetHackQtSettings* qt_settings;
764
765
766
767 NetHackQtKeyBuffer::NetHackQtKeyBuffer() :
768     in(0), out(0)
769 {
770 }
771
772 bool NetHackQtKeyBuffer::Empty() const { return in==out; }
773 bool NetHackQtKeyBuffer::Full() const { return (in+1)%maxkey==out; }
774
775 void NetHackQtKeyBuffer::Put(int k, int a, int state)
776 {
777     if ( Full() ) return;       // Safety
778     key[in]=k;
779     ascii[in]=a;
780     in=(in+1)%maxkey;
781 }
782
783 void NetHackQtKeyBuffer::Put(char a)
784 {
785     Put(0,a,0);
786 }
787
788 void NetHackQtKeyBuffer::Put(const char* str)
789 {
790     while (*str) Put(*str++);
791 }
792
793 int NetHackQtKeyBuffer::GetKey()
794 {
795     if ( Empty() ) return 0;
796     int r=TopKey();
797     out=(out+1)%maxkey;
798     return r;
799 }
800
801 int NetHackQtKeyBuffer::GetAscii()
802 {
803     if ( Empty() ) return 0; // Safety
804     int r=TopAscii();
805     out=(out+1)%maxkey;
806     return r;
807 }
808
809 int NetHackQtKeyBuffer::GetState()
810 {
811     if ( Empty() ) return 0;
812     int r=TopState();
813     out=(out+1)%maxkey;
814     return r;
815 }
816
817 int NetHackQtKeyBuffer::TopKey() const
818 {
819     if ( Empty() ) return 0;
820     return key[out];
821 }
822
823 int NetHackQtKeyBuffer::TopAscii() const
824 {
825     if ( Empty() ) return 0;
826     return ascii[out];
827 }
828
829 int NetHackQtKeyBuffer::TopState() const
830 {
831     if ( Empty() ) return 0;
832     return state[out];
833 }
834
835
836 NetHackQtClickBuffer::NetHackQtClickBuffer() :
837     in(0), out(0)
838 {
839 }
840
841 bool NetHackQtClickBuffer::Empty() const { return in==out; }
842 bool NetHackQtClickBuffer::Full() const { return (in+1)%maxclick==out; }
843
844 void NetHackQtClickBuffer::Put(int x, int y, int mod)
845 {
846     click[in].x=x;
847     click[in].y=y;
848     click[in].mod=mod;
849     in=(in+1)%maxclick;
850 }
851
852 int NetHackQtClickBuffer::NextX() const { return click[out].x; }
853 int NetHackQtClickBuffer::NextY() const { return click[out].y; }
854 int NetHackQtClickBuffer::NextMod() const { return click[out].mod; }
855
856 void NetHackQtClickBuffer::Get()
857 {
858     out=(out+1)%maxclick;
859 }
860
861 class NhPSListViewItem : public QListViewItem {
862 public:
863     NhPSListViewItem( QListView* parent, const QString& name ) :
864         QListViewItem(parent, name)
865     {
866     }
867
868     void setGlyph(int g)
869     {
870         NetHackQtGlyphs& glyphs = qt_settings->glyphs();
871         int gw = glyphs.width();
872         int gh = glyphs.height();
873         QPixmap pm(gw,gh);
874         QPainter p(&pm);
875         glyphs.drawGlyph(p, g, 0, 0);
876         p.end();
877         setPixmap(0,pm);
878         setHeight(QMAX(pm.height()+1,height()));
879     }
880
881     void paintCell( QPainter *p, const QColorGroup &cg,
882                     int column, int width, int alignment )
883     {
884         if ( isSelectable() ) {
885             QListViewItem::paintCell( p, cg, column, width, alignment );
886         } else {
887             QColorGroup disabled(
888                 cg.foreground().light(),
889                 cg.button().light(),
890                 cg.light(), cg.dark(), cg.mid(),
891                 gray, cg.base() );
892             QListViewItem::paintCell( p, disabled, column, width, alignment );
893         }
894     }
895 };
896
897 class NhPSListViewRole : public NhPSListViewItem {
898 public:
899     NhPSListViewRole( QListView* parent, int id ) :
900         NhPSListViewItem(parent,
901 #ifdef QT_CHOOSE_RACE_FIRST // Lowerize - looks better
902             QString(QChar(roles[id].name.m[0])).lower()+QString(roles[id].name.m+1)
903 #else
904             roles[id].name.m
905 #endif
906         )
907     {
908         setGlyph(monnum_to_glyph(roles[id].malenum));
909     }
910 };
911
912 class NhPSListViewRace : public NhPSListViewItem {
913 public:
914     NhPSListViewRace( QListView* parent, int id ) :
915         NhPSListViewItem(parent,
916 #ifdef QT_CHOOSE_RACE_FIRST // Capitalize - looks better
917             QString(QChar(races[id].noun[0])).upper()+QString(races[id].noun+1)
918 #else
919             QString(QChar(races[id].noun[0])+QString(races[id].noun+1))
920 #endif
921         )
922     {
923         setGlyph(monnum_to_glyph(races[id].malenum));
924     }
925 };
926
927 class NhPSListView : public QListView {
928 public:
929     NhPSListView( QWidget* parent ) :
930         QListView(parent)
931     {
932         setSorting(-1); // order is identity
933         header()->setClickEnabled(FALSE);
934     }
935
936     QSizePolicy sizePolicy() const
937     {
938         return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
939     }
940
941     QSize minimumSizeHint() const
942     {
943         return sizeHint();
944     }
945
946     QSize sizeHint() const
947     {
948         QListView::sizeHint();
949         QSize sz = header()->sizeHint();
950         int h=0;
951         QListViewItem* c=firstChild();
952         while (c) h+=c->height(),c = c->nextSibling();
953         sz += QSize(frameWidth()*2, h+frameWidth()*2);
954         return sz;
955     }
956
957     int selectedItemNumber() const
958     {
959         int i=0;
960         QListViewItem* c = firstChild();
961         while (c) {
962             if (c == selectedItem()) {
963                 return i;
964             }
965             i++;
966             c = c->nextSibling();
967         }
968         return -1;
969     }
970
971     void setSelectedItemNumber(int i)
972     {
973         QListViewItem* c=firstChild();
974         while (i--)
975             c = c->nextSibling();
976         c->setSelected(TRUE);
977     }
978 };
979
980 NetHackQtPlayerSelector::NetHackQtPlayerSelector(NetHackQtKeyBuffer& ks) :
981     QDialog(qApp->mainWidget(),"plsel",TRUE),
982     keysource(ks),
983     fully_specified_role(TRUE)
984 {
985     /*
986                0             1             2
987           + Name ------------------------------------+
988         0 |                                          |
989           + ---- ------------------------------------+
990           + Role ---+   + Race ---+   + Gender ------+
991           |         |   |         |   |  * Male      |
992         1 |         |   |         |   |  * Female    |
993           |         |   |         |   +--------------+
994           |         |   |         |   
995           |         |   |         |   + Alignment ---+
996         2 |         |   |         |   |  * Male      |
997           |         |   |         |   |  * Female    |
998           |         |   |         |   +--------------+
999         3 |         |   |         |   ...stretch...
1000           |         |   |         |   
1001         4 |         |   |         |   [  Play  ]
1002         5 |         |   |         |   [  Quit  ]
1003           +---------+   +---------+   
1004     */
1005
1006     int marg=4;
1007     QGridLayout *l = new QGridLayout(this,6,3,marg,marg);
1008
1009     QButtonGroup* namebox = new QButtonGroup(1,Horizontal,"Name",this);
1010     QLineEdit* name = new QLineEdit(namebox);
1011     name->setMaxLength(sizeof(plname)-1);
1012     if ( strncmp(plname,"player",6) && strncmp(plname,"games",5) )
1013         name->setText(plname);
1014     connect(name, SIGNAL(textChanged(const QString&)),
1015             this, SLOT(selectName(const QString&)) );
1016     name->setFocus();
1017     QButtonGroup* genderbox = new QButtonGroup("Sex",this);
1018     QButtonGroup* alignbox = new QButtonGroup("Alignment",this);
1019     QVBoxLayout* vbgb = new QVBoxLayout(genderbox,3,1);
1020     vbgb->setAutoAdd(TRUE);
1021     vbgb->addSpacing(fontMetrics().height()*3/4);
1022     QVBoxLayout* vbab = new QVBoxLayout(alignbox,3,1);
1023     vbab->setAutoAdd(TRUE);
1024     vbab->addSpacing(fontMetrics().height());
1025     QLabel* logo = new QLabel(nh_attribution, this);
1026
1027     l->addMultiCellWidget( namebox, 0,0,0,2 );
1028 #ifdef QT_CHOOSE_RACE_FIRST
1029     race = new NhPSListView(this);
1030     role = new NhPSListView(this);
1031     l->addMultiCellWidget( race, 1,5,0,0 );
1032     l->addMultiCellWidget( role, 1,5,1,1 );
1033 #else
1034     role = new NhPSListView(this);
1035     race = new NhPSListView(this);
1036     l->addMultiCellWidget( role, 1,5,0,0 );
1037     l->addMultiCellWidget( race, 1,5,1,1 );
1038 #endif
1039     role->addColumn("Role");
1040     race->addColumn("Race");
1041
1042     l->addWidget( genderbox, 1, 2 );
1043     l->addWidget( alignbox, 2, 2 );
1044     l->addWidget( logo, 3, 2, AlignCenter );
1045     l->setRowStretch( 3, 5 );
1046
1047     int i;
1048     int nrole;
1049
1050     for (nrole=0; roles[nrole].name.m; nrole++)
1051         ;
1052     for (i=nrole-1; i>=0; i--) { // XXX QListView unsorted goes in rev.
1053         new NhPSListViewRole( role, i );
1054     }
1055     connect( role, SIGNAL(selectionChanged()), this, SLOT(selectRole()) );
1056
1057     int nrace;
1058     for (nrace=0; races[nrace].noun; nrace++)
1059         ;
1060     for (i=nrace-1; i>=0; i--) {
1061         new NhPSListViewRace( race, i );
1062     }
1063     connect( race, SIGNAL(selectionChanged()), this, SLOT(selectRace()) );
1064
1065     gender = new QRadioButton*[ROLE_GENDERS];
1066     for (i=0; i<ROLE_GENDERS; i++) {
1067         gender[i] = new QRadioButton( genders[i].adj, genderbox );
1068     }
1069     connect( genderbox, SIGNAL(clicked(int)), this, SLOT(selectGender(int)) );
1070
1071     alignment = new QRadioButton*[ROLE_ALIGNS];
1072     for (i=0; i<ROLE_ALIGNS; i++) {
1073         alignment[i] = new QRadioButton( aligns[i].adj, alignbox );
1074     }
1075     connect( alignbox, SIGNAL(clicked(int)), this, SLOT(selectAlignment(int)) );
1076
1077     QPushButton* ok = new QPushButton("Play",this);
1078     l->addWidget( ok, 4, 2 );
1079     ok->setDefault(TRUE);
1080     connect( ok, SIGNAL(clicked()), this, SLOT(accept()) );
1081
1082     QPushButton* cancel = new QPushButton("Quit",this);
1083     l->addWidget( cancel, 5, 2 );
1084     connect( cancel, SIGNAL(clicked()), this, SLOT(reject()) );
1085
1086     // Randomize race and role, unless specified in config
1087     int ro = flags.initrole;
1088     if (ro == ROLE_NONE || ro == ROLE_RANDOM) {
1089         ro = rn2(nrole);
1090         if (flags.initrole != ROLE_RANDOM) {
1091             fully_specified_role = FALSE;
1092         }
1093     }
1094     int ra = flags.initrace;
1095     if (ra == ROLE_NONE || ra == ROLE_RANDOM) {
1096         ra = rn2(nrace);
1097         if (flags.initrace != ROLE_RANDOM) {
1098             fully_specified_role = FALSE;
1099         }
1100     }
1101
1102     // make sure we have a valid combination, honoring 
1103     // the users request if possible.
1104     bool choose_race_first;
1105 #ifdef QT_CHOOSE_RACE_FIRST
1106     choose_race_first = TRUE;
1107     if (flags.initrole >= 0 && flags.initrace < 0) {
1108         choose_race_first = FALSE;
1109     }
1110 #else
1111     choose_race_first = FALSE;
1112     if (flags.initrace >= 0 && flags.initrole < 0) {
1113         choose_race_first = TRUE;
1114     }
1115 #endif
1116     while (!validrace(ro,ra)) {
1117         if (choose_race_first) {
1118             ro = rn2(nrole);
1119             if (flags.initrole != ROLE_RANDOM) {
1120                 fully_specified_role = FALSE;
1121             }
1122         } else {
1123             ra = rn2(nrace);
1124             if (flags.initrace != ROLE_RANDOM) {
1125                 fully_specified_role = FALSE;
1126             }
1127         }
1128     }
1129
1130     int g = flags.initgend;
1131     if (g == -1) {
1132         g = rn2(ROLE_GENDERS);
1133         fully_specified_role = FALSE;
1134     }
1135     while (!validgend(ro,ra,g)) {
1136         g = rn2(ROLE_GENDERS);
1137     }
1138     gender[g]->setChecked(TRUE);
1139     selectGender(g);
1140
1141     int a = flags.initalign;
1142     if (a == -1) {
1143         a = rn2(ROLE_ALIGNS);
1144         fully_specified_role = FALSE;
1145     }
1146     while (!validalign(ro,ra,a)) {
1147         a = rn2(ROLE_ALIGNS);
1148     }
1149     alignment[a]->setChecked(TRUE);
1150     selectAlignment(a);
1151
1152     QListViewItem* li;
1153
1154     li = role->firstChild();
1155     while (ro--) li=li->nextSibling();
1156     role->setSelected(li,TRUE);
1157
1158     li = race->firstChild();
1159     while (ra--) li=li->nextSibling();
1160     race->setSelected(li,TRUE);
1161
1162     flags.initrace = race->selectedItemNumber();
1163     flags.initrole = role->selectedItemNumber();
1164 }
1165
1166
1167 void NetHackQtPlayerSelector::selectName(const QString& n)
1168 {
1169     strncpy(plname,n.latin1(),sizeof(plname)-1);
1170 }
1171
1172 void NetHackQtPlayerSelector::selectRole()
1173 {
1174     int ra = race->selectedItemNumber();
1175     int ro = role->selectedItemNumber();
1176     if (ra == -1 || ro == -1) return;
1177
1178 #ifdef QT_CHOOSE_RACE_FIRST
1179     selectRace();
1180 #else
1181     QListViewItem* i=role->currentItem();
1182     QListViewItem* valid=0;
1183     int j;
1184     NhPSListViewItem* item;
1185     item = (NhPSListViewItem*)role->firstChild();
1186     for (j=0; roles[j].name.m; j++) {
1187         bool v = validrace(j,ra);
1188         item->setSelectable(TRUE);
1189         if ( !valid && v ) valid = item;
1190         item=(NhPSListViewItem*)item->nextSibling();
1191     }
1192     if ( !validrace(role->selectedItemNumber(),ra) )
1193         i = valid;
1194     role->setSelected(i,TRUE);
1195     item = (NhPSListViewItem*)role->firstChild();
1196     for (j=0; roles[j].name.m; j++) {
1197         bool v = validrace(j,ra);
1198         item->setSelectable(v);
1199         item->repaint();
1200         item=(NhPSListViewItem*)item->nextSibling();
1201     }
1202 #endif
1203
1204     flags.initrole = role->selectedItemNumber();
1205     setupOthers();
1206 }
1207
1208 void NetHackQtPlayerSelector::selectRace()
1209 {
1210     int ra = race->selectedItemNumber();
1211     int ro = role->selectedItemNumber();
1212     if (ra == -1 || ro == -1) return;
1213
1214 #ifndef QT_CHOOSE_RACE_FIRST
1215     selectRole();
1216 #else
1217     QListViewItem* i=race->currentItem();
1218     QListViewItem* valid=0;
1219     int j;
1220     NhPSListViewItem* item;
1221     item = (NhPSListViewItem*)race->firstChild();
1222     for (j=0; races[j].noun; j++) {
1223         bool v = validrace(ro,j);
1224         item->setSelectable(TRUE);
1225         if ( !valid && v ) valid = item;
1226         item=(NhPSListViewItem*)item->nextSibling();
1227     }
1228     if ( !validrace(ro,race->selectedItemNumber()) )
1229         i = valid;
1230     race->setSelected(i,TRUE);
1231     item = (NhPSListViewItem*)race->firstChild();
1232     for (j=0; races[j].noun; j++) {
1233         bool v = validrace(ro,j);
1234         item->setSelectable(v);
1235         item->repaint();
1236         item=(NhPSListViewItem*)item->nextSibling();
1237     }
1238 #endif
1239
1240     flags.initrace = race->selectedItemNumber();
1241     setupOthers();
1242 }
1243
1244 void NetHackQtPlayerSelector::setupOthers()
1245 {
1246     int ro = role->selectedItemNumber();
1247     int ra = race->selectedItemNumber();
1248     int valid=-1;
1249     int c=0;
1250     int j;
1251     for (j=0; j<ROLE_GENDERS; j++) {
1252         bool v = validgend(ro,ra,j);
1253         if ( gender[j]->isChecked() )
1254             c = j;
1255         gender[j]->setEnabled(v);
1256         if ( valid<0 && v ) valid = j;
1257     }
1258     if ( !validgend(ro,ra,c) )
1259         c = valid;
1260     int k;
1261     for (k=0; k<ROLE_GENDERS; k++) {
1262         gender[k]->setChecked(c==k);
1263     }
1264     selectGender(c);
1265
1266     valid=-1;
1267     for (j=0; j<ROLE_ALIGNS; j++) {
1268         bool v = validalign(ro,ra,j);
1269         if ( alignment[j]->isChecked() )
1270             c = j;
1271         alignment[j]->setEnabled(v);
1272         if ( valid<0 && v ) valid = j;
1273     }
1274     if ( !validalign(ro,ra,c) )
1275         c = valid;
1276     for (k=0; k<ROLE_ALIGNS; k++) {
1277         alignment[k]->setChecked(c==k);
1278     }
1279     selectAlignment(c);
1280 }
1281
1282 void NetHackQtPlayerSelector::selectGender(int i)
1283 {
1284     flags.initgend = i;
1285 }
1286
1287 void NetHackQtPlayerSelector::selectAlignment(int i)
1288 {
1289     flags.initalign = i;
1290 }
1291
1292
1293 void NetHackQtPlayerSelector::done(int i)
1294 {
1295     setResult(i);
1296     qApp->exit_loop();
1297 }
1298
1299 void NetHackQtPlayerSelector::Quit()
1300 {
1301     done(R_Quit);
1302     qApp->exit_loop();
1303 }
1304
1305 void NetHackQtPlayerSelector::Random()
1306 {
1307     done(R_Rand);
1308     qApp->exit_loop();
1309 }
1310
1311 bool NetHackQtPlayerSelector::Choose()
1312 {
1313     if (fully_specified_role) return TRUE;
1314
1315 #if defined(QWS) // probably safe with Qt 3, too (where show!=exec in QDialog).
1316     if ( qt_compact_mode ) {
1317         showMaximized();
1318     } else
1319 #endif
1320     {
1321         adjustSize();
1322         centerOnMain(this);
1323     }
1324
1325     if ( exec() ) {
1326         return TRUE;
1327     } else {
1328         return FALSE;
1329     }
1330 }
1331
1332
1333 NetHackQtStringRequestor::NetHackQtStringRequestor(NetHackQtKeyBuffer& ks, const char* p, const char* cancelstr) :
1334     QDialog(qApp->mainWidget(),"string",FALSE),
1335     prompt(p,this,"prompt"),
1336     input(this,"input"),
1337     keysource(ks)
1338 {
1339     cancel=new QPushButton(cancelstr,this);
1340     connect(cancel,SIGNAL(clicked()),this,SLOT(reject()));
1341
1342     okay=new QPushButton("Okay",this);
1343     connect(okay,SIGNAL(clicked()),this,SLOT(accept()));
1344     connect(&input,SIGNAL(returnPressed()),this,SLOT(accept()));
1345     okay->setDefault(TRUE);
1346
1347     setFocusPolicy(StrongFocus);
1348 }
1349
1350 void NetHackQtStringRequestor::resizeEvent(QResizeEvent*)
1351 {
1352     const int margin=5;
1353     const int gutter=5;
1354
1355     int h=(height()-margin*2-gutter);
1356
1357     if (strlen(prompt.text()) > 16) {
1358         h/=3;
1359         prompt.setGeometry(margin,margin,width()-margin*2,h);
1360         input.setGeometry(width()*1/5,margin+h+gutter,
1361             (width()-margin-2-gutter)*4/5,h);
1362     } else {
1363         h/=2;
1364         prompt.setGeometry(margin,margin,(width()-margin*2-gutter)*2/5,h);
1365         input.setGeometry(prompt.geometry().right()+gutter,margin,
1366             (width()-margin-2-gutter)*3/5,h);
1367     }
1368
1369     cancel->setGeometry(margin,input.geometry().bottom()+gutter,
1370         (width()-margin*2-gutter)/2,h);
1371     okay->setGeometry(cancel->geometry().right()+gutter,cancel->geometry().y(),
1372         cancel->width(),h);
1373 }
1374
1375 void NetHackQtStringRequestor::SetDefault(const char* d)
1376 {
1377     input.setText(d);
1378 }
1379 bool NetHackQtStringRequestor::Get(char* buffer, int maxchar)
1380 {
1381     input.setMaxLength(maxchar);
1382     if (strlen(prompt.text()) > 16) {
1383         resize(fontMetrics().width(prompt.text())+50,fontMetrics().height()*6);
1384     } else {
1385         resize(fontMetrics().width(prompt.text())*2+50,fontMetrics().height()*4);
1386     }
1387
1388     centerOnMain(this);
1389     show();
1390     input.setFocus();
1391     setResult(-1);
1392     while (result()==-1) {
1393         // Put keys in buffer (eg. from macros, from out-of-focus input)
1394         if (!keysource.Empty()) {
1395             while (!keysource.Empty()) {
1396                 int key=keysource.TopKey();
1397                 int ascii=keysource.TopAscii();
1398                 int state=keysource.GetState();
1399                 if (ascii=='\r' || ascii=='\n') {
1400                     // CR or LF in buffer causes confirmation
1401                     strcpy(buffer,input.text());
1402                     return TRUE;
1403                 } else if (ascii=='\033') {
1404                     return FALSE;
1405                 } else {
1406                     input.fakeEvent(key,ascii,state);
1407                 }
1408             }
1409         }
1410         qApp->enter_loop();
1411     }
1412     // XXX Get rid of extra keys, since we couldn't get focus!
1413     while (!keysource.Empty()) keysource.GetKey();
1414
1415     if (result()) {
1416         strcpy(buffer,input.text());
1417         return TRUE;
1418     } else {
1419         return FALSE;
1420     }
1421 }
1422 void NetHackQtStringRequestor::done(int i)
1423 {
1424     setResult(i);
1425     qApp->exit_loop();
1426 }
1427
1428
1429 NetHackQtWindow::NetHackQtWindow()
1430 {
1431 }
1432 NetHackQtWindow::~NetHackQtWindow()
1433 {
1434 }
1435
1436 // XXX Use "expected ..." for now, abort or default later.
1437 //
1438 void NetHackQtWindow::Clear() { puts("unexpected Clear"); }
1439 void NetHackQtWindow::Display(bool block) { puts("unexpected Display"); }
1440 bool NetHackQtWindow::Destroy() { return TRUE; }
1441 void NetHackQtWindow::CursorTo(int x,int y) { puts("unexpected CursorTo"); }
1442 void NetHackQtWindow::PutStr(int attr, const char* text) { puts("unexpected PutStr"); }
1443 void NetHackQtWindow::StartMenu() { puts("unexpected StartMenu"); }
1444 void NetHackQtWindow::AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr,
1445     const char* str, bool presel) { puts("unexpected AddMenu"); }
1446 void NetHackQtWindow::EndMenu(const char* prompt) { puts("unexpected EndMenu"); }
1447 int NetHackQtWindow::SelectMenu(int how, MENU_ITEM_P **menu_list) { puts("unexpected SelectMenu"); return 0; }
1448 void NetHackQtWindow::ClipAround(int x,int y) { puts("unexpected ClipAround"); }
1449 void NetHackQtWindow::PrintGlyph(int x,int y,int glyph) { puts("unexpected PrintGlyph"); }
1450 //void NetHackQtWindow::PrintGlyphCompose(int x,int y,int,int) { puts("unexpected PrintGlyphCompose"); }
1451 void NetHackQtWindow::UseRIP(int how) { puts("unexpected UseRIP"); }
1452
1453
1454
1455 // XXX Hmmm... crash after saving bones file if Map window is
1456 // XXX deleted.  Strange bug somewhere.
1457 bool NetHackQtMapWindow::Destroy() { return FALSE; }
1458
1459 NetHackQtMapWindow::NetHackQtMapWindow(NetHackQtClickBuffer& click_sink) :
1460     clicksink(click_sink),
1461     change(10),
1462     rogue_font(0)
1463 {
1464     viewport.addChild(this);
1465
1466     setBackgroundColor(black);
1467     viewport.setBackgroundColor(black);
1468
1469     pet_annotation = QPixmap(qt_compact_mode ? pet_mark_small_xpm : pet_mark_xpm);
1470
1471     cursor.setX(0);
1472     cursor.setY(0);
1473     Clear();
1474
1475     connect(qt_settings,SIGNAL(tilesChanged()),this,SLOT(updateTiles()));
1476     connect(&viewport, SIGNAL(contentsMoving(int,int)), this,
1477                 SLOT(moveMessages(int,int)));
1478
1479     updateTiles();
1480     //setFocusPolicy(StrongFocus);
1481 }
1482
1483 void NetHackQtMapWindow::moveMessages(int x, int y)
1484 {
1485     QRect u = messages_rect;
1486     messages_rect.moveTopLeft(QPoint(x,y));
1487     u |= messages_rect;
1488     update(u);
1489 }
1490
1491 void NetHackQtMapWindow::clearMessages()
1492 {
1493     messages = "";
1494     update(messages_rect);
1495     messages_rect = QRect();
1496 }
1497
1498 void NetHackQtMapWindow::putMessage(int attr, const char* text)
1499 {
1500     if ( !messages.isEmpty() )
1501         messages += "\n";
1502     messages += text;
1503     QFontMetrics fm = fontMetrics();
1504     messages_rect = fm.boundingRect(viewport.contentsX(),viewport.contentsY(),viewport.width(),0, WordBreak|AlignTop|AlignLeft|DontClip, messages);
1505     update(messages_rect);
1506 }
1507
1508 void NetHackQtMapWindow::updateTiles()
1509 {
1510     NetHackQtGlyphs& glyphs = qt_settings->glyphs();
1511     int gw = glyphs.width();
1512     int gh = glyphs.height();
1513     // Be exactly the size we want to be - full map...
1514     resize(COLNO*gw,ROWNO*gh);
1515
1516     viewport.verticalScrollBar()->setSteps(gh,gh);
1517     viewport.horizontalScrollBar()->setSteps(gw,gw);
1518     /*
1519     viewport.setMaximumSize(
1520         gw*COLNO + viewport.verticalScrollBar()->width(),
1521         gh*ROWNO + viewport.horizontalScrollBar()->height()
1522     );
1523     */
1524     viewport.updateScrollBars();
1525
1526     change.clear();
1527     change.add(0,0,COLNO,ROWNO);
1528     delete rogue_font; rogue_font = 0;
1529     Display(FALSE);
1530
1531     emit resized();
1532 }
1533
1534 NetHackQtMapWindow::~NetHackQtMapWindow()
1535 {
1536     // Remove from viewport porthole, since that is a destructible member.
1537     viewport.removeChild(this);
1538     recreate(0,0,QPoint(0,0));
1539 }
1540
1541 QWidget* NetHackQtMapWindow::Widget()
1542 {
1543     return &viewport;
1544 }
1545
1546 void NetHackQtMapWindow::Scroll(int dx, int dy)
1547 {
1548     if (viewport.horizontalScrollBar()->isVisible()) {
1549         while (dx<0) { viewport.horizontalScrollBar()->subtractPage(); dx++; }
1550         while (dx>0) { viewport.horizontalScrollBar()->addPage(); dx--; }
1551     }
1552     if (viewport.verticalScrollBar()->isVisible()) {
1553         while (dy<0) { viewport.verticalScrollBar()->subtractPage(); dy++; }
1554         while (dy>0) { viewport.verticalScrollBar()->addPage(); dy--; }
1555     }
1556 }
1557
1558 void NetHackQtMapWindow::Clear()
1559 {
1560     unsigned short stone=cmap_to_glyph(S_stone);
1561
1562     for (int j=0; j<ROWNO; j++) {
1563         for (int i=0; i<COLNO; i++) {
1564             Glyph(i,j)=stone;
1565         }
1566     }
1567
1568     change.clear();
1569     change.add(0,0,COLNO,ROWNO);
1570 }
1571
1572 void NetHackQtMapWindow::clickCursor()
1573 {
1574     clicksink.Put(cursor.x(),cursor.y(),CLICK_1);
1575     qApp->exit_loop();
1576 }
1577
1578 void NetHackQtMapWindow::mousePressEvent(QMouseEvent* event)
1579 {
1580     clicksink.Put(
1581         event->pos().x()/qt_settings->glyphs().width(),
1582         event->pos().y()/qt_settings->glyphs().height(),
1583         event->button()==LeftButton ? CLICK_1 : CLICK_2
1584     );
1585     qApp->exit_loop();
1586 }
1587
1588 #ifdef TEXTCOLOR
1589 static
1590 const QPen& nhcolor_to_pen(int c)
1591 {
1592     static QPen* pen=0;
1593     if ( !pen ) {
1594         pen = new QPen[17];
1595         pen[0] = Qt::black;
1596         pen[1] = Qt::red;
1597         pen[2] = QColor(0,191,0);
1598         pen[3] = QColor(127,127,0);
1599         pen[4] = Qt::blue;
1600         pen[5] = Qt::magenta;
1601         pen[6] = Qt::cyan;
1602         pen[7] = Qt::gray;
1603         pen[8] = Qt::white; // no color
1604         pen[9] = QColor(255,127,0);
1605         pen[10] = QColor(127,255,127);
1606         pen[11] = Qt::yellow;
1607         pen[12] = QColor(127,127,255);
1608         pen[13] = QColor(255,127,255);
1609         pen[14] = QColor(127,255,255);
1610         pen[15] = Qt::white;
1611         pen[16] = Qt::black;
1612     }
1613
1614     return pen[c];
1615 }
1616 #endif
1617
1618 void NetHackQtMapWindow::paintEvent(QPaintEvent* event)
1619 {
1620     QRect area=event->rect();
1621     QRect garea;
1622     garea.setCoords(
1623         QMAX(0,area.left()/qt_settings->glyphs().width()),
1624         QMAX(0,area.top()/qt_settings->glyphs().height()),
1625         QMIN(COLNO-1,area.right()/qt_settings->glyphs().width()),
1626         QMIN(ROWNO-1,area.bottom()/qt_settings->glyphs().height())
1627     );
1628
1629     QPainter painter;
1630
1631     painter.begin(this);
1632
1633     if (
1634 #ifdef REINCARNATION
1635         Is_rogue_level(&u.uz) ||
1636 #endif
1637         iflags.wc_ascii_map
1638     )
1639     {
1640         // You enter a VERY primitive world!
1641
1642         painter.setClipRect( event->rect() ); // (normally we don't clip)
1643         painter.fillRect( event->rect(), black );
1644
1645         if ( !rogue_font ) {
1646             // Find font...
1647             int pts = 5;
1648             QString fontfamily = iflags.wc_font_map
1649                 ? iflags.wc_font_map : "Courier";
1650             bool bold = FALSE;
1651             if ( fontfamily.right(5).lower() == "-bold" ) {
1652                 fontfamily.truncate(fontfamily.length()-5);
1653                 bold = TRUE;
1654             }
1655             while ( pts < 32 ) {
1656                 QFont f(fontfamily, pts, bold ? QFont::Bold : QFont::Normal);
1657                 painter.setFont(QFont(fontfamily, pts));
1658                 QFontMetrics fm = painter.fontMetrics();
1659                 if ( fm.width("M") > qt_settings->glyphs().width() )
1660                     break;
1661                 if ( fm.height() > qt_settings->glyphs().height() )
1662                     break;
1663                 pts++;
1664             }
1665             rogue_font = new QFont(fontfamily,pts-1);
1666         }
1667         painter.setFont(*rogue_font);
1668
1669         for (int j=garea.top(); j<=garea.bottom(); j++) {
1670             for (int i=garea.left(); i<=garea.right(); i++) {
1671                 unsigned short g=Glyph(i,j);
1672                 uchar ch;
1673                 int color, och;
1674                 unsigned special;
1675
1676                 painter.setPen( green );
1677                 /* map glyph to character and color */
1678                 mapglyph(g, &och, &color, &special, i, j);
1679                 ch = (uchar)och;
1680 #ifdef TEXTCOLOR
1681                 painter.setPen( nhcolor_to_pen(color) );
1682 #endif
1683                 painter.drawText(
1684                     i*qt_settings->glyphs().width(),
1685                     j*qt_settings->glyphs().height(),
1686                     qt_settings->glyphs().width(),
1687                     qt_settings->glyphs().height(),
1688                     AlignCenter,
1689                     (const char*)&ch, 1
1690                 );
1691                 if (glyph_is_pet(g)
1692 #ifdef TEXTCOLOR
1693                     && ::iflags.hilite_pet
1694 #endif
1695                 ) {
1696                     painter.drawPixmap(QPoint(i*qt_settings->glyphs().width(), j*qt_settings->glyphs().height()), pet_annotation);
1697                 }
1698             }
1699         }
1700
1701         painter.setFont(font());
1702     } else {
1703         for (int j=garea.top(); j<=garea.bottom(); j++) {
1704             for (int i=garea.left(); i<=garea.right(); i++) {
1705                 unsigned short g=Glyph(i,j);
1706                 qt_settings->glyphs().drawCell(painter, g, i, j);
1707                 if (glyph_is_pet(g)
1708 #ifdef TEXTCOLOR
1709                     && ::iflags.hilite_pet
1710 #endif
1711                 ) {
1712                     painter.drawPixmap(QPoint(i*qt_settings->glyphs().width(), j*qt_settings->glyphs().height()), pet_annotation);
1713                 }
1714             }
1715         }
1716     }
1717
1718     if (garea.contains(cursor)) {
1719 #ifdef REINCARNATION
1720         if (Is_rogue_level(&u.uz)) {
1721 #ifdef TEXTCOLOR
1722             painter.setPen( white );
1723 #else
1724             painter.setPen( green ); // REALLY primitive
1725 #endif
1726         } else
1727 #endif
1728         {
1729             int hp100;
1730             if (u.mtimedone) {
1731                 hp100=u.mhmax ? u.mh*100/u.mhmax : 100;
1732             } else {
1733                 hp100=u.uhpmax ? u.uhp*100/u.uhpmax : 100;
1734             }
1735
1736             if (hp100 > 75) painter.setPen(white);
1737             else if (hp100 > 50) painter.setPen(yellow);
1738             else if (hp100 > 25) painter.setPen(QColor(0xff,0xbf,0x00)); // orange
1739             else if (hp100 > 10) painter.setPen(red);
1740             else painter.setPen(magenta);
1741         }
1742
1743         painter.drawRect(
1744             cursor.x()*qt_settings->glyphs().width(),cursor.y()*qt_settings->glyphs().height(),
1745             qt_settings->glyphs().width(),qt_settings->glyphs().height());
1746     }
1747
1748     if (area.intersects(messages_rect)) {
1749         painter.setPen(black);
1750         painter.drawText(viewport.contentsX()+1,viewport.contentsY()+1,
1751             viewport.width(),0, WordBreak|AlignTop|AlignLeft|DontClip, messages);
1752         painter.setPen(white);
1753         painter.drawText(viewport.contentsX(),viewport.contentsY(),
1754             viewport.width(),0, WordBreak|AlignTop|AlignLeft|DontClip, messages);
1755     }
1756
1757     painter.end();
1758 }
1759
1760 void NetHackQtMapWindow::Display(bool block)
1761 {
1762     for (int i=0; i<change.clusters(); i++) {
1763         const QRect& ch=change[i];
1764         repaint(
1765             ch.x()*qt_settings->glyphs().width(),
1766             ch.y()*qt_settings->glyphs().height(),
1767             ch.width()*qt_settings->glyphs().width(),
1768             ch.height()*qt_settings->glyphs().height(),
1769             FALSE
1770         );
1771     }
1772
1773     change.clear();
1774
1775     if (block) {
1776         yn_function("Press a key when done viewing",0,'\0');
1777     }
1778 }
1779
1780 void NetHackQtMapWindow::CursorTo(int x,int y)
1781 {
1782     Changed(cursor.x(),cursor.y());
1783     cursor.setX(x);
1784     cursor.setY(y);
1785     Changed(cursor.x(),cursor.y());
1786 }
1787
1788 void NetHackQtMapWindow::PutStr(int attr, const char* text)
1789 {
1790     puts("unexpected PutStr in MapWindow");
1791 }
1792
1793 void NetHackQtMapWindow::ClipAround(int x,int y)
1794 {
1795     // Convert to pixel of center of tile
1796     x=x*qt_settings->glyphs().width()+qt_settings->glyphs().width()/2;
1797     y=y*qt_settings->glyphs().height()+qt_settings->glyphs().height()/2;
1798
1799     // Then ensure that pixel is visible
1800     viewport.center(x,y,0.45,0.45);
1801 }
1802
1803 void NetHackQtMapWindow::PrintGlyph(int x,int y,int glyph)
1804 {
1805     Glyph(x,y)=glyph;
1806     Changed(x,y);
1807 }
1808
1809 //void NetHackQtMapWindow::PrintGlyphCompose(int x,int y,int glyph1, int glyph2)
1810 //{
1811     // TODO: composed graphics
1812 //}
1813
1814 void NetHackQtMapWindow::Changed(int x, int y)
1815 {
1816     change.add(x,y);
1817 }
1818
1819
1820 class NetHackQtScrollText : public QTableView {
1821     struct UData {
1822         UData() : text(0), attr(0) { }
1823         ~UData() { if (text) free(text); }
1824
1825         char* text;
1826         int attr;
1827     };
1828 public:
1829     int uncleared;
1830
1831     NetHackQtScrollText(int maxlength) :
1832         uncleared(0),
1833         maxitems(maxlength),
1834         first(0),
1835         count(0),
1836         item_cycle(maxlength)
1837     {
1838         setNumCols(1);
1839         setCellWidth(200);
1840         setCellHeight(fontMetrics().height());
1841         setBackgroundColor(white);
1842         setTableFlags(Tbl_vScrollBar
1843             |Tbl_autoHScrollBar
1844             |Tbl_clipCellPainting
1845             |Tbl_smoothScrolling);
1846     }
1847
1848     ~NetHackQtScrollText()
1849     {
1850     }
1851
1852     void Scroll(int dx, int dy)
1853     {
1854         setXOffset(xOffset()+dx*viewWidth());
1855         setYOffset(yOffset()+dy*viewHeight());
1856     }
1857
1858     void insertItem(int attr, const char* text)
1859     {
1860         setTopCell(count);
1861
1862         setAutoUpdate(FALSE);
1863
1864         int i;
1865         if (count<maxitems) {
1866             i=count++;
1867             setNumRows(count);
1868         } else {
1869             i=count-1;
1870             first=(first+1)%maxitems;
1871         }
1872         item(i).attr=attr;
1873         item(i).text=strdup(text);
1874         int w=datumWidth(item(i));
1875
1876         if (w > cellWidth()) {
1877             // Get wider.
1878             setCellWidth(w);
1879         }
1880         setTopCell(count);
1881
1882         setAutoUpdate(TRUE);
1883
1884         if (viewHeight() >= totalHeight()-cellHeight()) {
1885             repaint();
1886         } else {
1887             scroll(0,cellHeight());
1888         }
1889     }
1890
1891     virtual void setFont(const QFont& font)
1892     {
1893         QTableView::setFont(font);
1894         setCellHeight(fontMetrics().height());
1895     }
1896
1897 protected:
1898
1899     UData& item(int i)
1900     {
1901         return item_cycle[(first+i)%maxitems];
1902     }
1903
1904     const int maxitems;
1905     int first, count;
1906     QArray<UData> item_cycle;
1907
1908     int datumWidth(const UData& uitem)
1909     {
1910         if (uitem.text) {
1911             int width=fontMetrics().width(uitem.text)+3;
1912             if (uitem.attr) {
1913                 // XXX Too expensive to do properly, because
1914                 // XXX we have to set the font of the widget
1915                 // XXX just to get the font metrics information!
1916                 // XXX Could hold a fake widget for that
1917                 // XXX purpose, but this hack is less ugly.
1918                 width+=width/10;
1919             }
1920             return width;
1921         } else {
1922             return 0;
1923         }
1924     }
1925
1926     virtual void setupPainter(QPainter *p)
1927     {
1928         // XXX This shouldn't be needed - we set the bg in the constructor.
1929         p->setBackgroundColor(white);
1930     }
1931
1932     virtual void paintCell(QPainter *p, int row, int col)
1933     {
1934         bool sel=FALSE;
1935         UData& uitem=item(row);
1936
1937         if (!sel && row < count-uncleared) {
1938             p->setPen(darkGray);
1939         } else {
1940             p->setPen(black);
1941         }
1942
1943         if (uitem.attr) {
1944             // XXX only bold
1945             QFont bold(font().family(),font().pointSize(),QFont::Bold);
1946             p->setFont(bold);
1947         }
1948
1949         p->drawText(3, 0, cellWidth(), cellHeight(),
1950                 AlignLeft|AlignVCenter, uitem.text);
1951
1952         if (uitem.attr) {
1953             p->setFont(font());
1954         }
1955     }
1956 };
1957
1958 NetHackQtMessageWindow::NetHackQtMessageWindow() :
1959     list(new NetHackQtScrollText(::iflags.msg_history))
1960 {
1961     ::iflags.window_inited = 1;
1962     map = 0;
1963     connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(updateFont()));
1964     updateFont();
1965 }
1966
1967 NetHackQtMessageWindow::~NetHackQtMessageWindow()
1968 {
1969     ::iflags.window_inited = 0;
1970     delete list;
1971 }
1972
1973 QWidget* NetHackQtMessageWindow::Widget() { return list; }
1974
1975 void NetHackQtMessageWindow::setMap(NetHackQtMapWindow* m)
1976 {
1977     map = m;
1978     updateFont();
1979 }
1980
1981 void NetHackQtMessageWindow::updateFont()
1982 {
1983     list->setFont(qt_settings->normalFont());
1984     if ( map )
1985         map->setFont(qt_settings->normalFont());
1986 }
1987
1988 void NetHackQtMessageWindow::Scroll(int dx, int dy)
1989 {
1990     list->Scroll(dx,dy);
1991 }
1992
1993 void NetHackQtMessageWindow::Clear()
1994 {
1995     if ( map )
1996         map->clearMessages();
1997     if (list->uncleared) {
1998         list->uncleared=0;
1999         changed=TRUE;
2000         Display(FALSE);
2001     }
2002 }
2003
2004 void NetHackQtMessageWindow::Display(bool block)
2005 {
2006     if (changed) {
2007         list->repaint();
2008         changed=FALSE;
2009     }
2010 }
2011
2012 void NetHackQtMessageWindow::PutStr(int attr, const char* text)
2013 {
2014 #ifdef USER_SOUNDS
2015     play_sound_for_message(text);
2016 #endif
2017
2018     changed=TRUE;
2019     list->uncleared++;
2020     list->insertItem(attr,text);
2021
2022     // Force scrollbar to bottom
2023     // XXX list->setTopItem(list->count());
2024
2025     if ( map )
2026         map->putMessage(attr, text);
2027 }
2028
2029
2030
2031 NetHackQtLabelledIcon::NetHackQtLabelledIcon(QWidget* parent, const char* l) :
2032     QWidget(parent),
2033     low_is_good(FALSE),
2034     prev_value(-123),
2035     turn_count(-1),
2036     label(new QLabel(l,this)),
2037     icon(0)
2038 {
2039     initHighlight();
2040 }
2041 NetHackQtLabelledIcon::NetHackQtLabelledIcon(QWidget* parent, const char* l, const QPixmap& i) :
2042     QWidget(parent),
2043     low_is_good(FALSE),
2044     prev_value(-123),
2045     turn_count(-1),
2046     label(new QLabel(l,this)),
2047     icon(new QLabel(this))
2048 {
2049     setIcon(i);
2050     initHighlight();
2051 }
2052 void NetHackQtLabelledIcon::initHighlight()
2053 {
2054     const QPalette& pal=palette();
2055     const QColorGroup& pa=pal.normal();
2056     //QColorGroup good(white,darkGreen,pa.light(),pa.dark(),pa.mid(),white,pa.base());
2057     QColorGroup good(black,green,pa.light(),pa.dark(),pa.mid(),black,pa.base());
2058     QColorGroup bad(white,red,pa.light(),pa.dark(),pa.mid(),white,pa.base());
2059     hl_good=pal.copy();
2060     hl_good.setNormal(good);
2061     hl_good.setActive(good);
2062     hl_bad=pal.copy();
2063     hl_bad.setNormal(bad);
2064     hl_bad.setActive(bad);
2065 }
2066
2067 void NetHackQtLabelledIcon::setLabel(const char* t, bool lower)
2068 {
2069     if (!label) {
2070         label=new QLabel(this);
2071         label->setFont(font());
2072         resizeEvent(0);
2073     }
2074     if (0!=strcmp(label->text(),t)) {
2075         label->setText(t);
2076         highlight(lower==low_is_good ? hl_good : hl_bad);
2077     }
2078 }
2079 void NetHackQtLabelledIcon::setLabel(const char* t, long v, long cv, const char* tail)
2080 {
2081     char buf[BUFSZ];
2082     if (v==NoNum) {
2083         Sprintf(buf,"%s%s",t,tail);
2084     } else {
2085         Sprintf(buf,"%s%ld%s",t,v,tail);
2086     }
2087     setLabel(buf,cv<prev_value);
2088     prev_value=cv;
2089 }
2090 void NetHackQtLabelledIcon::setLabel(const char* t, long v, const char* tail)
2091 {
2092     setLabel(t,v,v,tail);
2093 }
2094 void NetHackQtLabelledIcon::setIcon(const QPixmap& i)
2095 {
2096     if (icon) icon->setPixmap(i);
2097     else { icon=new QLabel(this); icon->setPixmap(i); resizeEvent(0); }
2098     icon->resize(i.width(),i.height());
2099 }
2100 void NetHackQtLabelledIcon::setFont(const QFont& f)
2101 {
2102     QWidget::setFont(f);
2103     if (label) label->setFont(f);
2104 }
2105 void NetHackQtLabelledIcon::show()
2106 {
2107 #if QT_VERSION >= 300
2108     if (isHidden())
2109 #else
2110     if (!isVisible())
2111 #endif
2112         highlight(hl_bad);
2113     QWidget::show();
2114 }
2115 void NetHackQtLabelledIcon::highlightWhenChanging()
2116 {
2117     turn_count=0;
2118 }
2119 void NetHackQtLabelledIcon::lowIsGood()
2120 {
2121     low_is_good=TRUE;
2122 }
2123 void NetHackQtLabelledIcon::dissipateHighlight()
2124 {
2125     if (turn_count>0) {
2126         turn_count--;
2127         if (!turn_count)
2128             unhighlight();
2129     }
2130 }
2131 void NetHackQtLabelledIcon::highlight(const QPalette& hl)
2132 {
2133     if (label) { // Surely it is?!
2134         if (turn_count>=0) {
2135             label->setPalette(hl);
2136             turn_count=4;
2137             // `4' includes this turn, so dissipates after
2138             // 3 more keypresses.
2139         } else {
2140             label->setPalette(palette());
2141         }
2142     }
2143 }
2144 void NetHackQtLabelledIcon::unhighlight()
2145 {
2146     if (label) { // Surely it is?!
2147         label->setPalette(palette());
2148     }
2149 }
2150 void NetHackQtLabelledIcon::resizeEvent(QResizeEvent*)
2151 {
2152     setAlignments();
2153
2154     //int labw=label ? label->fontMetrics().width(label->text()) : 0;
2155     int labh=label ? label->fontMetrics().height() : 0;
2156     int icoh=icon ? icon->height() : 0;
2157     int h=icoh+labh;
2158     int icoy=(h>height() ? height()-labh-icoh : height()/2-h/2);
2159     int laby=icoy+icoh;
2160     if (icon) {
2161         icon->setGeometry(0,icoy,width(),icoh);
2162     }
2163     if (label) {
2164         label->setGeometry(0,laby,width(),labh);
2165     }
2166 }
2167
2168 void NetHackQtLabelledIcon::setAlignments()
2169 {
2170     if (label) label->setAlignment(AlignHCenter|AlignVCenter);
2171     if (icon) icon->setAlignment(AlignHCenter|AlignVCenter);
2172 }
2173
2174 static void
2175 tryload(QPixmap& pm, const char* fn)
2176 {
2177     if (!pm.load(fn)) {
2178         QString msg;
2179         msg.sprintf("Cannot load \"%s\"", fn);
2180         QMessageBox::warning(qApp->mainWidget(), "IO Error", msg);
2181     }
2182 }
2183
2184 NetHackQtStatusWindow::NetHackQtStatusWindow() :
2185     // Notes:
2186     //  Alignment needs -2 init value, because -1 is an alignment.
2187     //  Armor Class is an schar, so 256 is out of range.
2188     //  Blank value is 0 and should never change.
2189     name(this,"(name)"),
2190     dlevel(this,"(dlevel)"),
2191     str(this,"STR"),
2192     dex(this,"DEX"),
2193     con(this,"CON"),
2194     intel(this,"INT"),
2195     wis(this,"WIS"),
2196     cha(this,"CHA"),
2197     gold(this,"Gold"),
2198     hp(this,"Hit Points"),
2199     power(this,"Power"),
2200     ac(this,"Armour Class"),
2201     level(this,"Level"),
2202     exp(this,"Experience"),
2203     align(this,"Alignment"),
2204     time(this,"Time"),
2205     score(this,"Score"),
2206     hunger(this,""),
2207     confused(this,"Confused"),
2208     sick_fp(this,"Sick"),
2209     sick_il(this,"Ill"),
2210     blind(this,"Blind"),
2211     stunned(this,"Stunned"),
2212     hallu(this,"Hallu"),
2213     encumber(this,""),
2214     hline1(this),
2215     hline2(this),
2216     hline3(this),
2217     first_set(TRUE)
2218 {
2219     p_str = QPixmap(str_xpm);
2220     p_str = QPixmap(str_xpm);
2221     p_dex = QPixmap(dex_xpm);
2222     p_con = QPixmap(cns_xpm);
2223     p_int = QPixmap(int_xpm);
2224     p_wis = QPixmap(wis_xpm);
2225     p_cha = QPixmap(cha_xpm);
2226
2227     p_chaotic = QPixmap(chaotic_xpm);
2228     p_neutral = QPixmap(neutral_xpm);
2229     p_lawful = QPixmap(lawful_xpm);
2230
2231     p_satiated = QPixmap(satiated_xpm);
2232     p_hungry = QPixmap(hungry_xpm);
2233
2234     p_confused = QPixmap(confused_xpm);
2235     p_sick_fp = QPixmap(sick_fp_xpm);
2236     p_sick_il = QPixmap(sick_il_xpm);
2237     p_blind = QPixmap(blind_xpm);
2238     p_stunned = QPixmap(stunned_xpm);
2239     p_hallu = QPixmap(hallu_xpm);
2240
2241     p_encumber[0] = QPixmap(slt_enc_xpm);
2242     p_encumber[1] = QPixmap(mod_enc_xpm);
2243     p_encumber[2] = QPixmap(hvy_enc_xpm);
2244     p_encumber[3] = QPixmap(ext_enc_xpm);
2245     p_encumber[4] = QPixmap(ovr_enc_xpm);
2246
2247     str.setIcon(p_str);
2248     dex.setIcon(p_dex);
2249     con.setIcon(p_con);
2250     intel.setIcon(p_int);
2251     wis.setIcon(p_wis);
2252     cha.setIcon(p_cha);
2253
2254     align.setIcon(p_neutral);
2255     hunger.setIcon(p_hungry);
2256
2257     confused.setIcon(p_confused);
2258     sick_fp.setIcon(p_sick_fp);
2259     sick_il.setIcon(p_sick_il);
2260     blind.setIcon(p_blind);
2261     stunned.setIcon(p_stunned);
2262     hallu.setIcon(p_hallu);
2263
2264     encumber.setIcon(p_encumber[0]);
2265
2266     hline1.setFrameStyle(QFrame::HLine|QFrame::Sunken);
2267     hline2.setFrameStyle(QFrame::HLine|QFrame::Sunken);
2268     hline3.setFrameStyle(QFrame::HLine|QFrame::Sunken);
2269     hline1.setLineWidth(1);
2270     hline2.setLineWidth(1);
2271     hline3.setLineWidth(1);
2272
2273     connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(doUpdate()));
2274     doUpdate();
2275 }
2276
2277 void NetHackQtStatusWindow::doUpdate()
2278 {
2279     const QFont& large=qt_settings->largeFont();
2280     name.setFont(large);
2281     dlevel.setFont(large);
2282
2283     const QFont& normal=qt_settings->normalFont();
2284     str.setFont(normal);
2285     dex.setFont(normal);
2286     con.setFont(normal);
2287     intel.setFont(normal);
2288     wis.setFont(normal);
2289     cha.setFont(normal);
2290     gold.setFont(normal);
2291     hp.setFont(normal);
2292     power.setFont(normal);
2293     ac.setFont(normal);
2294     level.setFont(normal);
2295     exp.setFont(normal);
2296     align.setFont(normal);
2297     time.setFont(normal);
2298     score.setFont(normal);
2299     hunger.setFont(normal);
2300     confused.setFont(normal);
2301     sick_fp.setFont(normal);
2302     sick_il.setFont(normal);
2303     blind.setFont(normal);
2304     stunned.setFont(normal);
2305     hallu.setFont(normal);
2306     encumber.setFont(normal);
2307
2308     updateStats();
2309 }
2310
2311 QWidget* NetHackQtStatusWindow::Widget() { return this; }
2312
2313 void NetHackQtStatusWindow::Clear()
2314 {
2315 }
2316 void NetHackQtStatusWindow::Display(bool block)
2317 {
2318 }
2319 void NetHackQtStatusWindow::CursorTo(int,int y)
2320 {
2321     cursy=y;
2322 }
2323 void NetHackQtStatusWindow::PutStr(int attr, const char* text)
2324 {
2325     // do a complete update when line 0 is done (as per X11 fancy status)
2326     if (cursy==0) updateStats();
2327 }
2328
2329 void NetHackQtStatusWindow::resizeEvent(QResizeEvent*)
2330 {
2331     const float SP_name=0.13; //     <Name> the <Class> (large)
2332     const float SP_dlev=0.13; //   Level 3 in The Dungeons of Doom (large)
2333     const float SP_atr1=0.25; //  STR   DEX   CON   INT   WIS   CHA
2334     const float SP_hln1=0.02; // ---
2335     const float SP_atr2=0.09; //  Au    HP    PW    AC    LVL   EXP
2336     const float SP_hln2=0.02; // ---
2337     const float SP_time=0.09; //      time    score
2338     const float SP_hln3=0.02; // ---
2339     const float SP_stat=0.25; // Alignment, Poisoned, Hungry, Sick, etc.
2340
2341     int h=height();
2342     int x=0,y=0;
2343
2344     int iw; // Width of an item across line
2345     int lh; // Height of a line of values
2346
2347     lh=int(h*SP_name);
2348     name.setGeometry(0,0,width(),lh); y+=lh;
2349     lh=int(h*SP_dlev);
2350     dlevel.setGeometry(0,y,width(),lh); y+=lh;
2351
2352     lh=int(h*SP_hln1);
2353     hline1.setGeometry(0,y,width(),lh); y+=lh;
2354
2355     lh=int(h*SP_atr1);
2356     iw=width()/6;
2357     str.setGeometry(x,y,iw,lh); x+=iw;
2358     dex.setGeometry(x,y,iw,lh); x+=iw;
2359     con.setGeometry(x,y,iw,lh); x+=iw;
2360     intel.setGeometry(x,y,iw,lh); x+=iw;
2361     wis.setGeometry(x,y,iw,lh); x+=iw;
2362     cha.setGeometry(x,y,iw,lh); x+=iw;
2363     x=0; y+=lh;
2364
2365     lh=int(h*SP_hln2);
2366     hline2.setGeometry(0,y,width(),lh); y+=lh;
2367
2368     lh=int(h*SP_atr2);
2369     iw=width()/6;
2370     gold.setGeometry(x,y,iw,lh); x+=iw;
2371     hp.setGeometry(x,y,iw,lh); x+=iw;
2372     power.setGeometry(x,y,iw,lh); x+=iw;
2373     ac.setGeometry(x,y,iw,lh); x+=iw;
2374     level.setGeometry(x,y,iw,lh); x+=iw;
2375     exp.setGeometry(x,y,iw,lh); x+=iw;
2376     x=0; y+=lh;
2377
2378     lh=int(h*SP_hln3);
2379     hline3.setGeometry(0,y,width(),lh); y+=lh;
2380
2381     lh=int(h*SP_time);
2382     iw=width()/3; x+=iw/2;
2383     time.setGeometry(x,y,iw,lh); x+=iw;
2384     score.setGeometry(x,y,iw,lh); x+=iw;
2385     x=0; y+=lh;
2386
2387     lh=int(h*SP_stat);
2388     iw=width()/9;
2389     align.setGeometry(x,y,iw,lh); x+=iw;
2390     hunger.setGeometry(x,y,iw,lh); x+=iw;
2391     confused.setGeometry(x,y,iw,lh); x+=iw;
2392     sick_fp.setGeometry(x,y,iw,lh); x+=iw;
2393     sick_il.setGeometry(x,y,iw,lh); x+=iw;
2394     blind.setGeometry(x,y,iw,lh); x+=iw;
2395     stunned.setGeometry(x,y,iw,lh); x+=iw;
2396     hallu.setGeometry(x,y,iw,lh); x+=iw;
2397     encumber.setGeometry(x,y,iw,lh); x+=iw;
2398     x=0; y+=lh;
2399 }
2400
2401
2402 /*
2403  * Set all widget values to a null string.  This is used after all spacings
2404  * have been calculated so that when the window is popped up we don't get all
2405  * kinds of funny values being displayed.
2406  */
2407 void NetHackQtStatusWindow::nullOut()
2408 {
2409 }
2410
2411 void NetHackQtStatusWindow::fadeHighlighting()
2412 {
2413     name.dissipateHighlight();
2414     dlevel.dissipateHighlight();
2415
2416     str.dissipateHighlight();
2417     dex.dissipateHighlight();
2418     con.dissipateHighlight();
2419     intel.dissipateHighlight();
2420     wis.dissipateHighlight();
2421     cha.dissipateHighlight();
2422
2423     gold.dissipateHighlight();
2424     hp.dissipateHighlight();
2425     power.dissipateHighlight();
2426     ac.dissipateHighlight();
2427     level.dissipateHighlight();
2428     exp.dissipateHighlight();
2429     align.dissipateHighlight();
2430
2431     time.dissipateHighlight();
2432     score.dissipateHighlight();
2433
2434     hunger.dissipateHighlight();
2435     confused.dissipateHighlight();
2436     sick_fp.dissipateHighlight();
2437     sick_il.dissipateHighlight();
2438     blind.dissipateHighlight();
2439     stunned.dissipateHighlight();
2440     hallu.dissipateHighlight();
2441     encumber.dissipateHighlight();
2442 }
2443
2444 /*
2445  * Update the displayed status.  The current code in botl.c updates
2446  * two lines of information.  Both lines are always updated one after
2447  * the other.  So only do our update when we update the second line.
2448  *
2449  * Information on the first line:
2450  *    name, attributes, alignment, score
2451  *
2452  * Information on the second line:
2453  *    dlvl, gold, hp, power, ac, {level & exp or HD **}
2454  *    status (hunger, conf, halu, stun, sick, blind), time, encumbrance
2455  *
2456  * [**] HD is shown instead of level and exp if mtimedone is non-zero.
2457  */
2458 void NetHackQtStatusWindow::updateStats()
2459 {
2460     if (!parentWidget()) return;
2461
2462     char buf[BUFSZ];
2463
2464     if (cursy != 0) return;    /* do a complete update when line 0 is done */
2465
2466     if (ACURR(A_STR) > 118) {
2467         Sprintf(buf,"STR:%d",ACURR(A_STR)-100);
2468     } else if (ACURR(A_STR)==118) {
2469         Sprintf(buf,"STR:18/**");
2470     } else if(ACURR(A_STR) > 18) {
2471         Sprintf(buf,"STR:18/%02d",ACURR(A_STR)-18);
2472     } else {
2473         Sprintf(buf,"STR:%d",ACURR(A_STR));
2474     }
2475     str.setLabel(buf,NetHackQtLabelledIcon::NoNum,ACURR(A_STR));
2476
2477     dex.setLabel("DEX:",(long)ACURR(A_DEX));
2478     con.setLabel("CON:",(long)ACURR(A_CON));
2479     intel.setLabel("INT:",(long)ACURR(A_INT));
2480     wis.setLabel("WIS:",(long)ACURR(A_WIS));
2481     cha.setLabel("CHA:",(long)ACURR(A_CHA));
2482     const char* hung=hu_stat[u.uhs];
2483     if (hung[0]==' ') {
2484         hunger.hide();
2485     } else {
2486         hunger.setIcon(u.uhs ? p_hungry : p_satiated);
2487         hunger.setLabel(hung);
2488         hunger.show();
2489     }
2490     if (Confusion) confused.show(); else confused.hide();
2491     if (Sick) {
2492         if (u.usick_type & SICK_VOMITABLE) {
2493             sick_fp.show();
2494         } else {
2495             sick_fp.hide();
2496         }
2497         if (u.usick_type & SICK_NONVOMITABLE) {
2498             sick_il.show();
2499         } else {
2500             sick_il.hide();
2501         }
2502     } else {
2503         sick_fp.hide();
2504         sick_il.hide();
2505     }
2506     if (Blind) blind.show(); else blind.hide();
2507     if (Stunned) stunned.show(); else stunned.hide();
2508     if (Hallucination) hallu.show(); else hallu.hide();
2509     const char* enc=enc_stat[near_capacity()];
2510     if (enc[0]==' ' || !enc[0]) {
2511         encumber.hide();
2512     } else {
2513         encumber.setIcon(p_encumber[near_capacity()-1]);
2514         encumber.setLabel(enc);
2515         encumber.show();
2516     }
2517     Strcpy(buf, plname);
2518     if ('a' <= buf[0] && buf[0] <= 'z') buf[0] += 'A'-'a';
2519     Strcat(buf, " the ");
2520     if (u.mtimedone) {
2521         char mname[BUFSZ];
2522         int k = 0;
2523
2524         Strcpy(mname, mons[u.umonnum].mname);
2525         while(mname[k] != 0) {
2526             if ((k == 0 || (k > 0 && mname[k-1] == ' '))
2527              && 'a' <= mname[k] && mname[k] <= 'z')
2528             {
2529                 mname[k] += 'A' - 'a';
2530             }
2531             k++;
2532         }
2533         Strcat(buf, mname);
2534     } else {
2535         Strcat(buf, rank_of(u.ulevel, pl_character[0], ::flags.female));
2536     }
2537     name.setLabel(buf,NetHackQtLabelledIcon::NoNum,u.ulevel);
2538
2539     if (describe_level(buf)) {
2540         dlevel.setLabel(buf,(bool)TRUE);
2541     } else {
2542         Sprintf(buf, "%s, level ", dungeons[u.uz.dnum].dname);
2543         dlevel.setLabel(buf,(long)depth(&u.uz));
2544     }
2545
2546 #ifndef GOLDOBJ
2547     gold.setLabel("Au:", u.ugold);
2548 #else
2549     gold.setLabel("Au:", money_cnt(invent));
2550 #endif
2551     if (u.mtimedone) {
2552         // You're a monster!
2553
2554         Sprintf(buf, "/%d", u.mhmax);
2555         hp.setLabel("HP:",u.mh  > 0 ? u.mh  : 0,buf);
2556         level.setLabel("HD:",(long)mons[u.umonnum].mlevel);
2557     } else {
2558         // You're normal.
2559
2560         Sprintf(buf, "/%d", u.uhpmax);
2561         hp.setLabel("HP:",u.uhp > 0 ? u.uhp : 0,buf);
2562         level.setLabel("Level:",(long)u.ulevel);
2563     }
2564     Sprintf(buf, "/%d", u.uenmax);
2565     power.setLabel("Pow:",u.uen,buf);
2566     ac.setLabel("AC:",(long)u.uac);
2567 #ifdef EXP_ON_BOTL
2568     if (::flags.showexp) {
2569         exp.setLabel("Exp:",(long)u.uexp);
2570     } else
2571 #endif
2572     {
2573         exp.setLabel("");
2574     }
2575     if (u.ualign.type==A_CHAOTIC) {
2576         align.setIcon(p_chaotic);
2577         align.setLabel("Chaotic");
2578     } else if (u.ualign.type==A_NEUTRAL) {
2579         align.setIcon(p_neutral);
2580         align.setLabel("Neutral");
2581     } else {
2582         align.setIcon(p_lawful);
2583         align.setLabel("Lawful");
2584     }
2585
2586     if (::flags.time) time.setLabel("Time:",(long)moves);
2587     else time.setLabel("");
2588 #ifdef SCORE_ON_BOTL
2589     if (::flags.showscore) {
2590         score.setLabel("Score:",(long)botl_score());
2591     } else
2592 #endif
2593     {
2594         score.setLabel("");
2595     }
2596
2597     if (first_set)
2598     {
2599         first_set=FALSE;
2600
2601         name.highlightWhenChanging();
2602         dlevel.highlightWhenChanging();
2603
2604         str.highlightWhenChanging();
2605         dex.highlightWhenChanging();
2606         con.highlightWhenChanging();
2607         intel.highlightWhenChanging();
2608         wis.highlightWhenChanging();
2609         cha.highlightWhenChanging();
2610
2611         gold.highlightWhenChanging();
2612         hp.highlightWhenChanging();
2613         power.highlightWhenChanging();
2614         ac.highlightWhenChanging(); ac.lowIsGood();
2615         level.highlightWhenChanging();
2616         exp.highlightWhenChanging();
2617         align.highlightWhenChanging();
2618
2619         //time.highlightWhenChanging();
2620         score.highlightWhenChanging();
2621
2622         hunger.highlightWhenChanging();
2623         confused.highlightWhenChanging();
2624         sick_fp.highlightWhenChanging();
2625         sick_il.highlightWhenChanging();
2626         blind.highlightWhenChanging();
2627         stunned.highlightWhenChanging();
2628         hallu.highlightWhenChanging();
2629         encumber.highlightWhenChanging();
2630     }
2631 }
2632
2633 /*
2634  * Turn off hilighted status values after a certain amount of turns.
2635  */
2636 void NetHackQtStatusWindow::checkTurnEvents()
2637 {
2638 }
2639
2640
2641
2642 NetHackQtMenuDialog::NetHackQtMenuDialog() :
2643     QDialog(qApp->mainWidget(),0,FALSE)
2644 {
2645 }
2646
2647 void NetHackQtMenuDialog::resizeEvent(QResizeEvent*)
2648 {
2649     emit Resized();
2650 }
2651
2652 void NetHackQtMenuDialog::Accept()
2653 {
2654     accept();
2655 }
2656
2657 void NetHackQtMenuDialog::Reject()
2658 {
2659     reject();
2660 }
2661
2662 void NetHackQtMenuDialog::SetResult(int r)
2663 {
2664     setResult(r);
2665 }
2666
2667 void NetHackQtMenuDialog::done(int i)
2668 {
2669     setResult(i);
2670     qApp->exit_loop();
2671 }
2672
2673 // Table view columns:
2674 // 
2675 // [pick-count] [accel] [glyph] [string]
2676 // 
2677 // Maybe accel should be near string.  We'll see.
2678 // pick-count normally blank.
2679 //   double-clicking or click-on-count gives pop-up entry
2680 // string is green when selected
2681 //
2682 NetHackQtMenuWindow::NetHackQtMenuWindow(NetHackQtKeyBuffer& ks) :
2683     QTableView(),
2684     keysource(ks),
2685     dialog(new NetHackQtMenuDialog()),
2686     prompt(0),
2687     pressed(-1)
2688 {
2689     setNumCols(4);
2690     setCellHeight(QMAX(qt_settings->glyphs().height()+1,fontMetrics().height()));
2691     setBackgroundColor(lightGray);
2692     setFrameStyle(Panel|Sunken);
2693     setLineWidth(2);
2694
2695     ok=new QPushButton("Ok",dialog);
2696     connect(ok,SIGNAL(clicked()),dialog,SLOT(accept()));
2697
2698     cancel=new QPushButton("Cancel",dialog);
2699     connect(cancel,SIGNAL(clicked()),dialog,SLOT(reject()));
2700
2701     all=new QPushButton("All",dialog);
2702     connect(all,SIGNAL(clicked()),this,SLOT(All()));
2703
2704     none=new QPushButton("None",dialog);
2705     connect(none,SIGNAL(clicked()),this,SLOT(ChooseNone()));
2706
2707     invert=new QPushButton("Invert",dialog);
2708     connect(invert,SIGNAL(clicked()),this,SLOT(Invert()));
2709
2710     search=new QPushButton("Search",dialog);
2711     connect(search,SIGNAL(clicked()),this,SLOT(Search()));
2712
2713     QPoint pos(0,ok->height());
2714     recreate(dialog,0,pos);
2715     prompt.recreate(dialog,0,pos);
2716
2717     setBackgroundColor(lightGray);
2718
2719     connect(dialog,SIGNAL(Resized()),this,SLOT(Layout()));
2720
2721     setTableFlags(Tbl_autoHScrollBar|Tbl_autoVScrollBar
2722             |Tbl_smoothScrolling|Tbl_clipCellPainting);
2723     setFocusPolicy(StrongFocus);
2724 }
2725
2726 NetHackQtMenuWindow::~NetHackQtMenuWindow()
2727 {
2728     // Remove from dialog before we destruct it
2729     recreate(0,0,QPoint(0,0));
2730     delete dialog;
2731 }
2732
2733 void NetHackQtMenuWindow::focusInEvent(QFocusEvent *)
2734 {
2735     // Don't repaint at all, since nothing is using the focus colour
2736 }
2737 void NetHackQtMenuWindow::focusOutEvent(QFocusEvent *)
2738 {
2739     // Don't repaint at all, since nothing is using the focus colour
2740 }
2741
2742 int NetHackQtMenuWindow::cellWidth(int col)
2743 {
2744     switch (col) {
2745      case 0:
2746         return fontMetrics().width("All ");
2747     break; case 1:
2748         return fontMetrics().width(" m ");
2749     break; case 2:
2750         return qt_settings->glyphs().width();
2751     break; case 3:
2752         return str_width;
2753     }
2754     impossible("Extra column (#%d) in MenuWindow",col);
2755     return 0;
2756 }
2757
2758 QWidget* NetHackQtMenuWindow::Widget() { return dialog; }
2759
2760 void NetHackQtMenuWindow::StartMenu()
2761 {
2762     setNumRows((itemcount=0));
2763     str_width=200;
2764     str_fixed=FALSE;
2765     next_accel=0;
2766     has_glyphs=FALSE;
2767 }
2768
2769 NetHackQtMenuWindow::MenuItem::MenuItem() :
2770     str(0)
2771 {
2772 }
2773
2774 NetHackQtMenuWindow::MenuItem::~MenuItem()
2775 {
2776     if (str) free((void*)str);
2777 }
2778
2779 #define STR_MARGIN 4
2780
2781 void NetHackQtMenuWindow::AddMenu(int glyph, const ANY_P* identifier,
2782         char ch, char gch, int attr, const char* str, bool presel)
2783 {
2784     if (!ch && identifier->a_void!=0) {
2785         // Supply a keyboard accelerator.  Limited supply.
2786         static char accel[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
2787         if (accel[next_accel]) {
2788             ch=accel[next_accel++];
2789         }
2790     }
2791
2792     if ((int)item.size() < itemcount+1) {
2793         item.resize(itemcount*4+10);
2794     }
2795     item[itemcount].glyph=glyph;
2796     item[itemcount].identifier=*identifier;
2797     item[itemcount].ch=ch;
2798     item[itemcount].attr=attr;
2799     item[itemcount].str=strdup(str);
2800     item[itemcount].selected=presel;
2801     item[itemcount].count=-1;
2802     ++itemcount;
2803
2804     str_fixed=str_fixed || strstr(str,"     ");
2805     if (glyph!=NO_GLYPH) has_glyphs=TRUE;
2806 }
2807 void NetHackQtMenuWindow::EndMenu(const char* p)
2808 {
2809     prompt.setText(p ? p : "");
2810 }
2811 void NetHackQtMenuWindow::Layout()
2812 {
2813     int butw=totalWidth()/6; // 6 buttons
2814     int buth=fontMetrics().height()+8; // 8 for spacing & mitres
2815     int prompth=(prompt.text().isNull() ? 0 : buth);
2816
2817     prompt.setGeometry(6,buth,dialog->width()-6,prompth);
2818     int h=dialog->height()-buth-prompth;
2819     setGeometry(0,buth+prompth, dialog->width(), h);
2820
2821     // Below, we take care to use up full width
2822     int x=0;
2823     ok->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/5;
2824     cancel->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/4;
2825     all->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/3;
2826     none->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/2;
2827     invert->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/1;
2828     search->setGeometry(x,0,butw,buth);
2829 }
2830 int NetHackQtMenuWindow::SelectMenu(int h, MENU_ITEM_P **menu_list)
2831 {
2832     setFont(str_fixed ?
2833         qt_settings->normalFixedFont() : qt_settings->normalFont());
2834
2835     for (int i=0; i<itemcount; i++) {
2836         str_width=QMAX(str_width,
2837             STR_MARGIN+fontMetrics().width(item[i].str));
2838     }
2839
2840     setCellHeight(QMAX(qt_settings->glyphs().height()+1,fontMetrics().height()));
2841     setNumRows(itemcount);
2842
2843     int buth=fontMetrics().height()+8; // 8 for spacing & mitres
2844
2845     how=h;
2846
2847     ok->setEnabled(how!=PICK_ONE);ok->setDefault(how!=PICK_ONE);
2848     cancel->setEnabled(how!=PICK_NONE);
2849     all->setEnabled(how==PICK_ANY);
2850     none->setEnabled(how==PICK_ANY);
2851     invert->setEnabled(how==PICK_ANY);
2852     search->setEnabled(how!=PICK_NONE);
2853
2854     dialog->SetResult(-1);
2855
2856     // 20 allows for scrollbar or spacing
2857     // 4 for frame borders
2858     int mh = QApplication::desktop()->height()*3/5;
2859     if ( qt_compact_mode && totalHeight() > mh ) {
2860         // big, so make it fill
2861         dialog->showMaximized();
2862     } else {
2863         dialog->resize(totalWidth()+20,
2864             QMIN(totalHeight(), mh)+buth+4+(prompt.text().isNull() ? 0 : buth));
2865         if ( dialog->width() > QApplication::desktop()->width() )
2866             dialog->resize(QApplication::desktop()->width(),dialog->height()+16);
2867         centerOnMain(dialog);
2868         dialog->show();
2869     }
2870
2871     setFocus();
2872     while (dialog->result()<0) {
2873         // changed the defaults below to the values in wintype.h 000119 - azy
2874         if (!keysource.Empty()) {
2875             char k=keysource.GetAscii();
2876             k=map_menu_cmd(k); /* added 000119 - azy */
2877             if (k=='\033')
2878                 dialog->Reject();
2879             else if (k=='\r' || k=='\n' || k==' ')
2880                 dialog->Accept();
2881             else if (k==MENU_SEARCH)
2882                 Search();
2883             else if (k==MENU_SELECT_ALL)
2884                 All();
2885             else if (k==MENU_INVERT_ALL)
2886                 Invert();
2887             else if (k==MENU_UNSELECT_ALL)
2888                 ChooseNone();
2889             else {
2890                 for (int i=0; i<itemcount; i++) {
2891                     if (item[i].ch==k)
2892                         ToggleSelect(i);
2893                 }
2894             }
2895         }
2896         if (dialog->result()<0)
2897             qApp->enter_loop();
2898     }
2899     //if ( (nhid != WIN_INVEN || !flags.perm_invent) ) // doesn't work yet
2900     {
2901         dialog->hide();
2902     }
2903     int result=dialog->result();
2904
2905     // Consume ^M (which QDialog steals for default button)
2906     while (!keysource.Empty() &&
2907         (keysource.TopAscii()=='\n' || keysource.TopAscii()=='\r'))
2908         keysource.GetAscii();
2909
2910     *menu_list=0;
2911     if (result>0 && how!=PICK_NONE) {
2912         if (how==PICK_ONE) {
2913             int i;
2914             for (i=0; i<itemcount && !item[i].selected; i++)
2915                 ;
2916             if (i<itemcount) {
2917                 *menu_list=(MENU_ITEM_P*)malloc(sizeof(MENU_ITEM_P));
2918                 (*menu_list)[0].item=item[i].identifier;
2919                 (*menu_list)[0].count=item[i].count;
2920                 return 1;
2921             } else {
2922                 return 0;
2923             }
2924         } else {
2925             int count=0;
2926             for (int i=0; i<itemcount; i++)
2927                 if (item[i].selected) count++;
2928             if (count) {
2929                 *menu_list=(MENU_ITEM_P*)malloc(count*sizeof(MENU_ITEM_P));
2930                 int j=0;
2931                 for (int i=0; i<itemcount; i++) {
2932                     if (item[i].selected) {
2933                         (*menu_list)[j].item=item[i].identifier;
2934                         (*menu_list)[j].count=item[i].count;
2935                         j++;
2936                     }
2937                 }
2938                 return count;
2939             } else {
2940                 return 0;
2941             }
2942         }
2943     } else {
2944         return -1;
2945     }
2946 }
2947 void NetHackQtMenuWindow::keyPressEvent(QKeyEvent* event)
2948 {
2949     if (viewHeight() < totalHeight() && !(event->state()&ShiftButton)) {
2950         if (event->key()==Key_Prior) {
2951             setYOffset(yOffset()-viewHeight());
2952         } else if (event->key()==Key_Next) {
2953             setYOffset(yOffset()+viewHeight());
2954         } else {
2955             event->ignore();
2956         }
2957     } else {
2958         event->ignore();
2959     }
2960 }
2961
2962 void NetHackQtMenuWindow::All()
2963 {
2964     for (int i=0; i<itemcount; i++) {
2965         if (!item[i].selected) ToggleSelect(i);
2966     }
2967 }
2968 void NetHackQtMenuWindow::ChooseNone()
2969 {
2970     for (int i=0; i<itemcount; i++) {
2971         if (item[i].selected) ToggleSelect(i);
2972     }
2973 }
2974 void NetHackQtMenuWindow::Invert()
2975 {
2976     for (int i=0; i<itemcount; i++) {
2977         ToggleSelect(i);
2978     }
2979 }
2980 void NetHackQtMenuWindow::Search()
2981 {
2982     NetHackQtStringRequestor requestor(keysource,"Search for:");
2983     char line[256];
2984     if (requestor.Get(line)) {
2985         for (int i=0; i<itemcount; i++) {
2986             if (strstr(item[i].str,line))
2987                 ToggleSelect(i);
2988         }
2989     }
2990 }
2991 void NetHackQtMenuWindow::ToggleSelect(int i)
2992 {
2993     if (item[i].Selectable()) {
2994         item[i].selected = !item[i].selected;
2995         if ( !item[i].selected )
2996             item[i].count=-1;
2997         updateCell(i,3);
2998         if (how==PICK_ONE) {
2999             dialog->Accept();
3000         }
3001     }
3002 }
3003
3004
3005 void NetHackQtMenuWindow::paintCell(QPainter* painter, int row, int col)
3006 {
3007     // [pick-count] [accel] [glyph] [string]
3008
3009     MenuItem& i = item[row];
3010
3011     painter->setPen(black);
3012     painter->setFont(font());
3013
3014     if (i.selected) {
3015         painter->setPen(darkGreen);
3016     }
3017
3018     switch (col) {
3019      case 0:
3020         if ( i.ch || i.attr!=ATR_INVERSE ) {
3021             QString text;
3022             if ( i.selected && i.count == -1 ) {
3023                 if ( i.str[0]>='0' && i.str[0]<='9' )
3024                     text = "All";
3025                 else
3026                     text = "*";
3027             } else if ( i.count<0 ) {
3028                 text = "-";
3029             } else {
3030                 text.sprintf("%d",i.count);
3031             }
3032             painter->drawText(0,0,cellWidth(col),cellHeight(),
3033             AlignHCenter|AlignVCenter,text);
3034         }
3035     break; case 1:
3036         if ((signed char)i.ch >= 0) {
3037             char text[2]={i.ch,0};
3038             painter->drawText(0,0,cellWidth(col),cellHeight(),
3039                 AlignHCenter|AlignVCenter,text);
3040         }
3041     break; case 2:
3042         if (i.glyph!=NO_GLYPH) {
3043             // Centered in height
3044             int y=(cellHeight()-qt_settings->glyphs().height())/2;
3045             if (y<0) y=0;
3046             qt_settings->glyphs().drawGlyph(*painter, i.glyph, 0, y);
3047         }
3048     break; case 3:
3049         // XXX should qt_settings have ALL the various fonts
3050         QFont newfont=font();
3051
3052         if (i.attr) {
3053             switch(i.attr) {
3054              case ATR_ULINE:
3055                 newfont.setUnderline(TRUE);
3056             break; case ATR_BOLD:
3057                 painter->setPen(red);
3058             break; case ATR_BLINK:
3059                 newfont.setItalic(TRUE);
3060             break; case ATR_INVERSE:
3061                 newfont=qt_settings->largeFont();
3062                 newfont.setWeight(QFont::Bold);
3063
3064                 if (i.selected) {
3065                     painter->setPen(blue);
3066                 } else {
3067                     painter->setPen(darkBlue);
3068                 }
3069             }
3070         }
3071         painter->setFont(newfont);
3072
3073         painter->drawText(STR_MARGIN,0,cellWidth(col),cellHeight(),
3074             AlignLeft|AlignVCenter,i.str);
3075     }
3076 }
3077
3078 void NetHackQtMenuWindow::mousePressEvent(QMouseEvent* event)
3079 {
3080     int col=findCol(event->pos().x());
3081     int row=findRow(event->pos().y());
3082
3083     if (col<0 || row<0 || !item[row].Selectable()) return;
3084
3085     if (how!=PICK_NONE) {
3086         if (col==0) {
3087             // Changing count.
3088             NetHackQtStringRequestor requestor(keysource,"Count:");
3089             char buf[BUFSZ];
3090
3091             if (item[row].count>0)
3092                 Sprintf(buf,"%d", item[row].count);
3093             else
3094                 Strcpy(buf, "");
3095
3096             requestor.SetDefault(buf);
3097             if (requestor.Get(buf)) {
3098                 item[row].count=atoi(buf);
3099                 if (item[row].count==0) {
3100                     item[row].count=-1;
3101                     if (item[row].selected) ToggleSelect(row);
3102                 } else {
3103                     if (!item[row].selected) ToggleSelect(row);
3104                 }
3105                 updateCell(row,0);
3106             }
3107         } else {
3108             pressed=row;
3109             was_sel=item[row].selected;
3110             ToggleSelect(row);
3111             updateCell(row,0);
3112         }
3113     }
3114 }
3115 void NetHackQtMenuWindow::mouseReleaseEvent(QMouseEvent* event)
3116 {
3117     if (pressed>=0) {
3118         int p=pressed;
3119         pressed=-1;
3120         updateCell(p,3);
3121     }
3122 }
3123 void NetHackQtMenuWindow::mouseMoveEvent(QMouseEvent* event)
3124 {
3125     if (pressed>=0) {
3126         int col=findCol(event->pos().x());
3127         int row=findRow(event->pos().y());
3128
3129         if (row>=0 && col>=0) {
3130             if (pressed!=row) {
3131                 // reset to initial state
3132                 if (item[pressed].selected!=was_sel)
3133                     ToggleSelect(pressed);
3134             } else {
3135                 // reset to new state
3136                 if (item[pressed].selected==was_sel)
3137                     ToggleSelect(pressed);
3138             }
3139         }
3140     }
3141 }
3142
3143
3144 class NetHackQtTextListBox : public QListBox {
3145 public:
3146     NetHackQtTextListBox(QWidget* parent) : QListBox(parent) { }
3147
3148     int TotalWidth()
3149     {
3150         doLayout();
3151         return contentsWidth();
3152     }
3153     int TotalHeight()
3154     {
3155         doLayout();
3156         return contentsHeight();
3157     }
3158
3159     virtual void setFont(const QFont &font)
3160     {
3161         QListBox::setFont(font);
3162     }
3163     void keyPressEvent(QKeyEvent* e)
3164     {
3165         QListBox::keyPressEvent(e);
3166     }
3167 };
3168
3169
3170 QPixmap* NetHackQtRIP::pixmap=0;
3171
3172 NetHackQtRIP::NetHackQtRIP(QWidget* parent) :
3173     QWidget(parent)
3174 {
3175     if (!pixmap) {
3176         pixmap=new QPixmap;
3177         tryload(*pixmap, "rip.xpm");
3178     }
3179     riplines=0;
3180     resize(pixmap->width(),pixmap->height());
3181     setFont(QFont("times",12)); // XXX may need to be configurable
3182 }
3183
3184 void NetHackQtRIP::setLines(char** l, int n)
3185 {
3186     line=l;
3187     riplines=n;
3188 }
3189
3190 QSize NetHackQtRIP::sizeHint() const
3191 {
3192     return pixmap->size();
3193 }
3194
3195 void NetHackQtRIP::paintEvent(QPaintEvent* event)
3196 {
3197     if ( riplines ) {
3198         int pix_x=(width()-pixmap->width())/2;
3199         int pix_y=(height()-pixmap->height())/2;
3200
3201         // XXX positions based on RIP image
3202         int rip_text_x=pix_x+156;
3203         int rip_text_y=pix_y+67;
3204         int rip_text_h=94/riplines;
3205
3206         QPainter painter;
3207         painter.begin(this);
3208         painter.drawPixmap(pix_x,pix_y,*pixmap);
3209         for (int i=0; i<riplines; i++) {
3210             painter.drawText(rip_text_x-i/2,rip_text_y+i*rip_text_h,
3211                 1,1,DontClip|AlignHCenter,line[i]);
3212         }
3213         painter.end();
3214     }
3215 }
3216
3217 NetHackQtTextWindow::NetHackQtTextWindow(NetHackQtKeyBuffer& ks) :
3218     QDialog(qApp->mainWidget(),0,FALSE),
3219     keysource(ks),
3220     use_rip(FALSE),
3221     str_fixed(FALSE),
3222     ok("Dismiss",this),
3223     search("Search",this),
3224     lines(new NetHackQtTextListBox(this)),
3225     rip(this)
3226 {
3227     ok.setDefault(TRUE);
3228     connect(&ok,SIGNAL(clicked()),this,SLOT(accept()));
3229     connect(&search,SIGNAL(clicked()),this,SLOT(Search()));
3230     connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(doUpdate()));
3231
3232     QVBoxLayout* vb = new QVBoxLayout(this);
3233     vb->addWidget(&rip);
3234     QHBoxLayout* hb = new QHBoxLayout(vb);
3235     hb->addWidget(&ok);
3236     hb->addWidget(&search);
3237     vb->addWidget(lines);
3238 }
3239
3240 void NetHackQtTextWindow::doUpdate()
3241 {
3242     update();
3243 }
3244
3245
3246 NetHackQtTextWindow::~NetHackQtTextWindow()
3247 {
3248
3249 }
3250
3251 QWidget* NetHackQtTextWindow::Widget()
3252 {
3253     return this;
3254 }
3255
3256 bool NetHackQtTextWindow::Destroy()
3257 {
3258     return !isVisible();
3259 }
3260
3261 void NetHackQtTextWindow::UseRIP(int how)
3262 {
3263 // Code from X11 windowport
3264 #define STONE_LINE_LEN 16    /* # chars that fit on one line */
3265 #define NAME_LINE 0     /* line # for player name */
3266 #define GOLD_LINE 1     /* line # for amount of gold */
3267 #define DEATH_LINE 2    /* line # for death description */
3268 #define YEAR_LINE 6     /* line # for year */
3269
3270 static char** rip_line=0;
3271     if (!rip_line) {
3272         rip_line=new char*[YEAR_LINE+1];
3273         for (int i=0; i<YEAR_LINE+1; i++) {
3274             rip_line[i]=new char[STONE_LINE_LEN+1];
3275         }
3276     }
3277
3278     /* Follows same algorithm as genl_outrip() */
3279
3280     char buf[BUFSZ];
3281     char *dpx;
3282     int line;
3283
3284     /* Put name on stone */
3285     Sprintf(rip_line[NAME_LINE], "%s", plname);
3286
3287     /* Put $ on stone */
3288 #ifndef GOLDOBJ
3289     Sprintf(rip_line[GOLD_LINE], "%ld Au", u.ugold);
3290 #else
3291     Sprintf(rip_line[GOLD_LINE], "%ld Au", done_money);
3292 #endif
3293
3294     /* Put together death description */
3295     switch (killer_format) {
3296         default: impossible("bad killer format?");
3297         case KILLED_BY_AN:
3298             Strcpy(buf, killed_by_prefix[how]);
3299             Strcat(buf, an(killer));
3300             break;
3301         case KILLED_BY:
3302             Strcpy(buf, killed_by_prefix[how]);
3303             Strcat(buf, killer);
3304             break;
3305         case NO_KILLER_PREFIX:
3306             Strcpy(buf, killer);
3307             break;
3308     }
3309
3310     /* Put death type on stone */
3311     for (line=DEATH_LINE, dpx = buf; line<YEAR_LINE; line++) {
3312         register int i,i0;
3313         char tmpchar;
3314
3315         if ( (i0=strlen(dpx)) > STONE_LINE_LEN) {
3316             for(i = STONE_LINE_LEN;
3317                 ((i0 > STONE_LINE_LEN) && i); i--)
3318                 if(dpx[i] == ' ') i0 = i;
3319             if(!i) i0 = STONE_LINE_LEN;
3320         }
3321         tmpchar = dpx[i0];
3322         dpx[i0] = 0;
3323         strcpy(rip_line[line], dpx);
3324         if (tmpchar != ' ') {
3325             dpx[i0] = tmpchar;
3326             dpx= &dpx[i0];
3327         } else  dpx= &dpx[i0+1];
3328     }
3329
3330     /* Put year on stone */
3331     Sprintf(rip_line[YEAR_LINE], "%4d", getyear());
3332
3333     rip.setLines(rip_line,YEAR_LINE+1);
3334
3335     use_rip=TRUE;
3336 }
3337
3338 void NetHackQtTextWindow::Clear()
3339 {
3340     lines->clear();
3341     use_rip=FALSE;
3342     str_fixed=FALSE;
3343 }
3344
3345 void NetHackQtTextWindow::Display(bool block)
3346 {
3347     if (str_fixed) {
3348         lines->setFont(qt_settings->normalFixedFont());
3349     } else {
3350         lines->setFont(qt_settings->normalFont());
3351     }
3352
3353     int h=0;
3354     if (use_rip) {
3355         h+=rip.height();
3356         ok.hide();
3357         search.hide();
3358         rip.show();
3359     } else {
3360         h+=ok.height()*2;
3361         ok.show();
3362         search.show();
3363         rip.hide();
3364     }
3365     int mh = QApplication::desktop()->height()*3/5;
3366     if ( qt_compact_mode && lines->TotalHeight() > mh || use_rip ) {
3367         // big, so make it fill
3368         showMaximized();
3369     } else {
3370         resize(QMAX(use_rip ? rip.width() : 200,
3371                 lines->TotalWidth()+24),
3372             QMIN(mh, lines->TotalHeight()+h));
3373         centerOnMain(this);
3374         show();
3375     }
3376     if (block) {
3377         setResult(-1);
3378         while (result()==-1) {
3379             qApp->enter_loop();
3380             if (result()==-1 && !keysource.Empty()) {
3381                 char k=keysource.GetAscii();
3382                 if (k=='\033' || k==' ' || k=='\r' || k=='\n') {
3383                     accept();
3384                 } else if (k=='/') {
3385                     Search();
3386                 }
3387             }
3388         }
3389     }
3390 }
3391
3392 void NetHackQtTextWindow::PutStr(int attr, const char* text)
3393 {
3394     str_fixed=str_fixed || strstr(text,"    ");
3395     lines->insertItem(text);
3396 }
3397
3398 void NetHackQtTextWindow::done(int i)
3399 {
3400     setResult(i+1000);
3401     hide();
3402     qApp->exit_loop();
3403 }
3404
3405 void NetHackQtTextWindow::keyPressEvent(QKeyEvent* e)
3406 {
3407     if ( e->ascii() != '\r' && e->ascii() != '\n' && e->ascii() != '\033' )
3408         lines->keyPressEvent(e);
3409     else
3410         QDialog::keyPressEvent(e);
3411 }
3412
3413 void NetHackQtTextWindow::Search()
3414 {
3415     NetHackQtStringRequestor requestor(keysource,"Search for:");
3416     static char line[256]="";
3417     requestor.SetDefault(line);
3418     if (requestor.Get(line)) {
3419         int current=lines->currentItem();
3420         for (uint i=1; i<lines->count(); i++) {
3421             int lnum=(i+current)%lines->count();
3422             QString str=lines->text(lnum);
3423             if (str.contains(line)) {
3424                 lines->setCurrentItem(lnum);
3425                 lines->centerCurrentItem();
3426                 return;
3427             }
3428         }
3429         lines->setCurrentItem(-1);
3430     }
3431 }
3432
3433
3434 NetHackQtDelay::NetHackQtDelay(int ms) :
3435     msec(ms)
3436 {
3437 }
3438
3439 void NetHackQtDelay::wait()
3440 {
3441     startTimer(msec);
3442     qApp->enter_loop();
3443 }
3444
3445 void NetHackQtDelay::timerEvent(QTimerEvent* timer)
3446 {
3447     qApp->exit_loop();
3448     killTimers();
3449 }
3450
3451 NetHackQtInvUsageWindow::NetHackQtInvUsageWindow(QWidget* parent) :
3452     QWidget(parent)
3453 {
3454 }
3455
3456 void NetHackQtInvUsageWindow::drawWorn(QPainter& painter, obj* nhobj, int x, int y, bool canbe)
3457 {
3458     short int glyph;
3459     if (nhobj)
3460         glyph=obj_to_glyph(nhobj);
3461     else if (canbe)
3462         glyph=cmap_to_glyph(S_room);
3463     else
3464         glyph=cmap_to_glyph(S_stone);
3465
3466     qt_settings->glyphs().drawCell(painter,glyph,x,y);
3467 }
3468
3469 void NetHackQtInvUsageWindow::paintEvent(QPaintEvent*)
3470 {
3471     //  012
3472     //
3473     //0 WhB   
3474     //1 s"w   
3475     //2 gCg   
3476     //3 =A=   
3477     //4  T    
3478     //5  S    
3479
3480     QPainter painter;
3481     painter.begin(this);
3482
3483     // Blanks
3484     drawWorn(painter,0,0,4,FALSE);
3485     drawWorn(painter,0,0,5,FALSE);
3486     drawWorn(painter,0,2,4,FALSE);
3487     drawWorn(painter,0,2,5,FALSE);
3488
3489     drawWorn(painter,uarm,1,3); // Armour
3490     drawWorn(painter,uarmc,1,2); // Cloak
3491     drawWorn(painter,uarmh,1,0); // Helmet
3492     drawWorn(painter,uarms,0,1); // Shield
3493     drawWorn(painter,uarmg,0,2); // Gloves - repeated
3494     drawWorn(painter,uarmg,2,2); // Gloves - repeated
3495 #ifdef TOURIST
3496     drawWorn(painter,uarmf,1,5); // Shoes (feet)
3497     drawWorn(painter,uarmu,1,4); // Undershirt
3498 #else
3499     drawWorn(painter,0    ,1,5,FALSE);
3500     drawWorn(painter,uarmf,1,4); // Shoes (feet)
3501 #endif
3502     drawWorn(painter,uleft,0,3); // RingL
3503     drawWorn(painter,uright,2,3); // RingR
3504
3505     drawWorn(painter,uwep,2,1); // Weapon
3506     drawWorn(painter,uswapwep,0,0); // Secondary weapon
3507     drawWorn(painter,uamul,1,1); // Amulet
3508     drawWorn(painter,ublindf,2,0); // Blindfold
3509
3510     painter.end();
3511 }
3512
3513 class SmallToolButton : public QToolButton {
3514 public:
3515     SmallToolButton(const QPixmap & pm, const QString &textLabel,
3516                  const QString& grouptext,
3517                  QObject * receiver, const char* slot,
3518                  QToolBar * parent) :
3519         QToolButton(pm, textLabel,
3520 #if QT_VERSION < 210
3521                 QString::null,
3522 #else
3523                 grouptext,
3524 #endif
3525                     receiver, slot, parent)
3526     {
3527     }
3528
3529     QSize sizeHint() const
3530     {
3531         // get just a couple more pixels for the map
3532         return QToolButton::sizeHint()-QSize(0,2);
3533     }
3534 };
3535
3536
3537 NetHackQtMainWindow::NetHackQtMainWindow(NetHackQtKeyBuffer& ks) :
3538     message(0), map(0), status(0), invusage(0),
3539     keysink(ks), dirkey(0)
3540 {
3541     QToolBar* toolbar = new QToolBar(this);
3542 #if QT_VERSION >= 210
3543     setToolBarsMovable(FALSE);
3544     toolbar->setHorizontalStretchable(TRUE);
3545     toolbar->setVerticalStretchable(TRUE);
3546 #endif
3547     addToolBar(toolbar);
3548     menubar = menuBar();
3549
3550     setCaption("Qt NetHack");
3551     if ( qt_compact_mode )
3552         setIcon(QPixmap(nh_icon_small));
3553     else
3554         setIcon(QPixmap(nh_icon));
3555
3556     QPopupMenu* game=new QPopupMenu;
3557     QPopupMenu* apparel=new QPopupMenu;
3558     QPopupMenu* act1=new QPopupMenu;
3559     QPopupMenu* act2 = qt_compact_mode ? new QPopupMenu : act1;
3560     QPopupMenu* magic=new QPopupMenu;
3561     QPopupMenu* info=new QPopupMenu;
3562
3563     QPopupMenu *help;
3564
3565 #ifdef KDE
3566     help = kapp->getHelpMenu( TRUE, "" );
3567     help->insertSeparator();
3568 #else
3569     help = qt_compact_mode ? info : new QPopupMenu;
3570 #endif
3571
3572     enum { OnDesktop=1, OnHandhelds=2 };
3573     struct Macro {
3574         QPopupMenu* menu;
3575         const char* name;
3576         const char* action;
3577         int flags;
3578     } item[] = {
3579         { game,         0, 0, 3},
3580         { game,         "Version\tv",           "v", 3},
3581         { game,         "Compilation\tAlt+V",     "\366", 3},
3582         { game,         "History\tShift+V",           "V", 3},
3583         { game,         "Redraw\tCtrl+R",          "\022", 0}, // useless
3584         { game,         "Options\tShift+O",           "O", 3},
3585         { game,         "Explore mode\tShift+X",      "X", 3},
3586         { game,         0, 0, 3},
3587         { game,         "Save\tSy",              "Sy", 3},
3588         { game,         "Quit\tAlt+Q",                "\361", 3},
3589
3590         { apparel,      "Apparel off\tShift+A",       "A", 2},
3591         { apparel,      "Remove many\tShift+A",       "A", 1},
3592         { apparel,      0, 0, 3},
3593         { apparel,      "Wield weapon\tw",      "w", 3},
3594         { apparel,      "Exchange weapons\tx",      "x", 3},
3595         { apparel,      "Two weapon combat\t#two",      "#tw", 3},
3596         { apparel,      "Load quiver\tShift+Q",       "Q", 3},
3597         { apparel,      0, 0, 3},
3598         { apparel,      "Wear armour\tShift+W",       "W", 3},
3599         { apparel,      "Take off armour\tShift+T",   "T", 3},
3600         { apparel,      0, 0, 3},
3601         { apparel,      "Put on non-armour\tShift+P", "P", 3},
3602         { apparel,      "Remove non-armour\tShift+R", "R", 3},
3603
3604         { act1, "Again\tCtrl+A",           "\001", 2},
3605         { act1, 0, 0, 3},
3606         { act1, "Apply\ta?",            "a?", 3},
3607         { act1, "Chat\tAlt+C",            "\343", 3},
3608         { act1, "Close door\tc",        "c", 3},
3609         { act1, "Down\t>",              ">", 3},
3610         { act1, "Drop many\tShift+D",         "D", 2},
3611         { act1, "Drop\td?",             "d?", 2},
3612         { act1, "Eat\te?",              "e?", 2},
3613         { act1, "Engrave\tShift+E",           "E", 3},
3614         { act1, "Fight\tShift+F",             "F", 3},
3615         { act1, "Fire from quiver\tf",  "f", 2},
3616         { act1, "Force\tAlt+F",           "\346", 3},
3617         { act1, "Get\t,",               ",", 2},
3618         { act1, "Jump\tAlt+J",            "\352", 3},
3619         { act2, "Kick\tCtrl+D",              "\004", 2},
3620         { act2, "Loot\tAlt+L",            "\354", 3},
3621         { act2, "Open door\to",         "o", 3},
3622         { act2, "Pay\tp",               "p", 3},
3623         { act2, "Rest\t.",              ".", 2},
3624         { act2, "Ride\t#ri",            "#ri", 3},
3625         { act2, "Search\ts",            "s", 3},
3626         { act2, "Sit\tAlt+S",             "\363", 3},
3627         { act2, "Throw\tt",             "t", 2},
3628         { act2, "Untrap\t#u",             "#u", 3},
3629         { act2, "Up\t<",                "<", 3},
3630         { act2, "Wipe face\tAlt+W",       "\367", 3},
3631
3632         { magic,        "Quaff potion\tq?",     "q?", 3},
3633         { magic,        "Read scroll/book\tr?", "r?", 3},
3634         { magic,        "Zap wand\tz?",         "z?", 3},
3635         { magic,        "Zap spell\tShift+Z",        "Z", 3},
3636         { magic,        "Dip\tAlt+D",             "\344", 3},
3637         { magic,        "Rub\tAlt+R",             "\362", 3},
3638         { magic,        "Invoke\tAlt+I",          "\351", 3},
3639         { magic,        0, 0, 3},
3640         { magic,        "Offer\tAlt+O",           "\357", 3},
3641         { magic,        "Pray\tAlt+P",            "\360", 3},
3642         { magic,        0, 0, 3},
3643         { magic,        "Teleport\tCtrl+T",        "\024", 3},
3644         { magic,        "Monster action\tAlt+M",  "\355", 3},
3645         { magic,        "Turn undead\tAlt+T",     "\364", 3},
3646
3647         { help,         "Help\t?",              "?", 3},
3648         { help,         0, 0, 3},
3649         { help,         "What is here\t:",      ":", 3},
3650         { help,         "What is there\t;",      ";", 3},
3651         { help,         "What is...\t/y",        "/y", 2},
3652         { help,         0, 0, 1},
3653
3654         { info,         "Inventory\ti",         "i", 3},
3655 #ifdef SLASHEM
3656         { info,         "Angbandish inventory\t*",    "*", 3},
3657 #endif
3658         { info,         "Conduct\t#co",         "#co", 3},
3659         { info,         "Discoveries\t\\",      "\\", 3},
3660         { info,         "List/reorder spells\t+",     "+", 3},
3661         { info,         "Adjust letters\tAlt+A",  "\341", 2},
3662         { info,         0, 0, 3},
3663         { info,         "Name object\tAlt+N",    "\356y?", 3},
3664         { info,         "Name object type\tAlt+N",    "\356n?", 3},
3665         { info,         "Name creature\tShift+C",      "C", 3},
3666         { info,         0, 0, 3},
3667         { info,         "Qualifications\tAlt+E",  "\345", 3},
3668
3669         { 0, 0, 0, 0 }
3670     };
3671
3672     int i;
3673     int count=0;
3674     for (i=0; item[i].menu; i++)
3675         if (item[i].name) count++;
3676
3677     macro=new const char* [count];
3678
3679     game->insertItem("Qt settings...",1000);
3680     help->insertItem("About Qt NetHack...",2000);
3681     //help->insertItem("NetHack Guidebook...",3000);
3682     help->insertSeparator();
3683
3684     count=0;
3685     for (i=0; item[i].menu; i++) {
3686         if ( item[i].flags & (qt_compact_mode ? 1 : 2) ) {
3687             if (item[i].name) {
3688                 QString name = item[i].name;
3689                 if ( qt_compact_mode ) // accelerators aren't
3690                     name.replace(QRegExp("\t.*"),"");
3691                 item[i].menu->insertItem(name,count);
3692                 macro[count++]=item[i].action;
3693             } else {
3694                 item[i].menu->insertSeparator();
3695             }
3696         }
3697     }
3698
3699     menubar->insertItem("Game",game);
3700     menubar->insertItem("Gear",apparel);
3701
3702     if ( qt_compact_mode ) {
3703         menubar->insertItem("A-J",act1);
3704         menubar->insertItem("K-Z",act2);
3705         menubar->insertItem("Magic",magic);
3706         menubar->insertItem(QPixmap(info_xpm),info);
3707         menubar->insertItem(QPixmap(map_xpm), this, SLOT(raiseMap()));
3708         menubar->insertItem(QPixmap(msg_xpm), this, SLOT(raiseMessages()));
3709         menubar->insertItem(QPixmap(stat_xpm), this, SLOT(raiseStatus()));
3710     } else {
3711         menubar->insertItem("Action",act1);
3712         menubar->insertItem("Magic",magic);
3713         menubar->insertItem("Info",info);
3714         menubar->insertSeparator();
3715         menubar->insertItem("Help",help);
3716     }
3717
3718     QSignalMapper* sm = new QSignalMapper(this);
3719     connect(sm, SIGNAL(mapped(const QString&)), this, SLOT(doKeys(const QString&)));
3720     QToolButton* tb;
3721     tb = new SmallToolButton( QPixmap(again_xpm),"Again","Action", sm, SLOT(map()), toolbar );
3722     sm->setMapping(tb, "\001" );
3723     tb = new SmallToolButton( QPixmap(get_xpm),"Get","Action", sm, SLOT(map()), toolbar );
3724     sm->setMapping(tb, "," );
3725     tb = new SmallToolButton( QPixmap(kick_xpm),"Kick","Action", sm, SLOT(map()), toolbar );
3726     sm->setMapping(tb, "\004" );
3727     tb = new SmallToolButton( QPixmap(throw_xpm),"Throw","Action", sm, SLOT(map()), toolbar );
3728     sm->setMapping(tb, "t" );
3729     tb = new SmallToolButton( QPixmap(fire_xpm),"Fire","Action", sm, SLOT(map()), toolbar );
3730     sm->setMapping(tb, "f" );
3731     tb = new SmallToolButton( QPixmap(drop_xpm),"Drop","Action", sm, SLOT(map()), toolbar );
3732     sm->setMapping(tb, "D" );
3733     tb = new SmallToolButton( QPixmap(eat_xpm),"Eat","Action", sm, SLOT(map()), toolbar );
3734     sm->setMapping(tb, "e" );
3735     tb = new SmallToolButton( QPixmap(rest_xpm),"Rest","Action", sm, SLOT(map()), toolbar );
3736     sm->setMapping(tb, "." );
3737     tb = new SmallToolButton( QPixmap(cast_a_xpm),"Cast A","Magic", sm, SLOT(map()), toolbar );
3738     sm->setMapping(tb, "Za" );
3739     tb = new SmallToolButton( QPixmap(cast_b_xpm),"Cast B","Magic", sm, SLOT(map()), toolbar );
3740     sm->setMapping(tb, "Zb" );
3741     tb = new SmallToolButton( QPixmap(cast_c_xpm),"Cast C","Magic", sm, SLOT(map()), toolbar );
3742     sm->setMapping(tb, "Zc" );
3743     if ( !qt_compact_mode ) {
3744         QWidget* filler = new QWidget(toolbar);
3745         filler->setBackgroundMode(PaletteButton);
3746         toolbar->setStretchableWidget(filler);
3747     }
3748
3749     connect(menubar,SIGNAL(activated(int)),this,SLOT(doMenuItem(int)));
3750
3751 #ifdef KDE
3752     setMenu (menubar);
3753 #endif
3754
3755     int x=0,y=0;
3756     int w=QApplication::desktop()->width()-10; // XXX arbitrary extra space for frame
3757     int h=QApplication::desktop()->height()-50;
3758
3759     int maxwn;
3760     int maxhn;
3761     if (qt_tilewidth != NULL) {
3762         maxwn = atoi(qt_tilewidth) * COLNO + 10;
3763     } else {
3764         maxwn = 1400;
3765     }
3766     if (qt_tileheight != NULL) {
3767         maxhn = atoi(qt_tileheight) * ROWNO * 6/4;
3768     } else {
3769         maxhn = 1024;
3770     }
3771
3772     // Be exactly the size we want to be - full map...
3773     if (w>maxwn) {
3774         x+=(w-maxwn)/2;
3775         w=maxwn; // Doesn't need to be any wider
3776     }
3777     if (h>maxhn) {
3778         y+=(h-maxhn)/2;
3779         h=maxhn; // Doesn't need to be any taller
3780     }
3781
3782     setGeometry(x,y,w,h);
3783
3784     if ( qt_compact_mode ) {
3785         stack = new QWidgetStack(this);
3786         setCentralWidget(stack);
3787     } else {
3788         setCentralWidget(new QWidget(this));
3789         invusage = new NetHackQtInvUsageWindow(centralWidget());
3790     }
3791 }
3792
3793 void NetHackQtMainWindow::zoomMap()
3794 {
3795     qt_settings->toggleGlyphSize();
3796 }
3797
3798 void NetHackQtMainWindow::raiseMap()
3799 {
3800     if ( stack->id(stack->visibleWidget()) == 0 ) {
3801         zoomMap();
3802     } else {
3803         stack->raiseWidget(0);
3804     }
3805 }
3806
3807 void NetHackQtMainWindow::raiseMessages()
3808 {
3809     stack->raiseWidget(1);
3810 }
3811
3812 void NetHackQtMainWindow::raiseStatus()
3813 {
3814     stack->raiseWidget(2);
3815 }
3816
3817 class NetHackMimeSourceFactory : public QMimeSourceFactory {
3818 public:
3819     const QMimeSource* data(const QString& abs_name) const
3820     {
3821         const QMimeSource* r = 0;
3822         if ( (NetHackMimeSourceFactory*)this == QMimeSourceFactory::defaultFactory() )
3823             r = QMimeSourceFactory::data(abs_name);
3824         else
3825             r = QMimeSourceFactory::defaultFactory()->data(abs_name);
3826         if ( !r ) {
3827             int sl = abs_name.length();
3828             do {
3829                 sl = abs_name.findRev('/',sl-1);
3830                 QString name = sl>=0 ? abs_name.mid(sl+1) : abs_name;
3831                 int dot = name.findRev('.');
3832                 if ( dot >= 0 )
3833                     name = name.left(dot);
3834                 if ( name == "map" )
3835                     r = new QImageDrag(QImage(map_xpm));
3836                 else if ( name == "msg" )
3837                     r = new QImageDrag(QImage(msg_xpm));
3838                 else if ( name == "stat" )
3839                     r = new QImageDrag(QImage(stat_xpm));
3840             } while (!r && sl>0);
3841         }
3842         return r;
3843     }
3844 };
3845
3846 void NetHackQtMainWindow::doMenuItem(int id)
3847 {
3848     switch (id) {
3849       case 1000:
3850         centerOnMain(qt_settings);
3851         qt_settings->show();
3852         break;
3853       case 2000:
3854         QMessageBox::about(this,  "About Qt NetHack", aboutMsg());
3855         break;
3856       case 3000: {
3857             QDialog dlg(this,0,TRUE);
3858             (new QVBoxLayout(&dlg))->setAutoAdd(TRUE);
3859             QTextBrowser browser(&dlg);
3860             NetHackMimeSourceFactory ms;
3861             browser.setMimeSourceFactory(&ms);
3862             browser.setSource(QDir::currentDirPath()+"/Guidebook.html");
3863             if ( qt_compact_mode )
3864                 dlg.showMaximized();
3865             dlg.exec();
3866         }
3867         break;
3868       default:
3869         if ( id >= 0 )
3870             doKeys(macro[id]);
3871     }
3872 }
3873
3874 void NetHackQtMainWindow::doKeys(const QString& k)
3875 {
3876     keysink.Put(k);
3877     qApp->exit_loop();
3878 }
3879
3880 void NetHackQtMainWindow::AddMessageWindow(NetHackQtMessageWindow* window)
3881 {
3882     message=window;
3883     ShowIfReady();
3884 }
3885
3886 void NetHackQtMainWindow::AddMapWindow(NetHackQtMapWindow* window)
3887 {
3888     map=window;
3889     ShowIfReady();
3890     connect(map,SIGNAL(resized()),this,SLOT(layout()));
3891 }
3892
3893 void NetHackQtMainWindow::AddStatusWindow(NetHackQtStatusWindow* window)
3894 {
3895     status=window;
3896     ShowIfReady();
3897 }
3898
3899 void NetHackQtMainWindow::RemoveWindow(NetHackQtWindow* window)
3900 {
3901     if (window==status) {
3902         status=0;
3903         ShowIfReady();
3904     } else if (window==map) {
3905         map=0;
3906         ShowIfReady();
3907     } else if (window==message) {
3908         message=0;
3909         ShowIfReady();
3910     }
3911 }
3912
3913 void NetHackQtMainWindow::updateInventory()
3914 {
3915     if ( invusage )
3916         invusage->repaint(FALSE);
3917 }
3918
3919 void NetHackQtMainWindow::fadeHighlighting()
3920 {
3921     if (status) {
3922         status->fadeHighlighting();
3923     }
3924 }
3925
3926 void NetHackQtMainWindow::layout()
3927 {
3928     if ( qt_compact_mode )
3929         return;
3930     if (message && map && status) {
3931         QSize maxs=map->Widget()->maximumSize();
3932         int maph=QMIN(height()*2/3,maxs.height());
3933
3934         QWidget* c = centralWidget();
3935         int h=c->height();
3936         int toph=h-maph;
3937         int iuw=3*qt_settings->glyphs().width();
3938         int topw=(c->width()-iuw)/2;
3939
3940         message->Widget()->setGeometry(0,0,topw,toph);
3941         invusage->setGeometry(topw,0,iuw,toph);
3942         status->Widget()->setGeometry(topw+iuw,0,topw,toph);
3943         map->Widget()->setGeometry(QMAX(0,(c->width()-maxs.width())/2),
3944                                    toph,c->width(),maph);
3945     }
3946 }
3947
3948 void NetHackQtMainWindow::resizeEvent(QResizeEvent*)
3949 {
3950     layout();
3951 #ifdef KDE
3952     updateRects();
3953 #endif         
3954 }
3955
3956 void NetHackQtMainWindow::keyReleaseEvent(QKeyEvent* event)
3957 {
3958     if ( dirkey ) {
3959         doKeys(QString(QChar(dirkey)));
3960         if ( !event->isAutoRepeat() )
3961             dirkey = 0;
3962     }
3963 }
3964
3965 void NetHackQtMainWindow::keyPressEvent(QKeyEvent* event)
3966 {
3967     // Global key controls
3968
3969     // For desktop, arrow keys scroll map, since we don't want players
3970     // to think that's the way to move. For handhelds, the normal way is to
3971     // click-to-travel, so we allow the cursor keys for fine movements.
3972
3973     //  321
3974     //  4 0
3975     //  567
3976
3977     if ( event->isAutoRepeat() &&
3978         event->key() >= Key_Left && event->key() <= Key_Down )
3979         return;
3980
3981     const char* d = iflags.num_pad ? ndir : sdir; 
3982     switch (event->key()) {
3983      case Key_Up:
3984         if ( dirkey == d[0] )
3985             dirkey = d[1];
3986         else if ( dirkey == d[4] )
3987             dirkey = d[3];
3988         else
3989             dirkey = d[2];
3990     break; case Key_Down:
3991         if ( dirkey == d[0] )
3992             dirkey = d[7];
3993         else if ( dirkey == d[4] )
3994             dirkey = d[5];
3995         else
3996             dirkey = d[6];
3997     break; case Key_Left:
3998         if ( dirkey == d[2] )
3999             dirkey = d[1];
4000         else if ( dirkey == d[6] )
4001             dirkey = d[7];
4002         else
4003             dirkey = d[0];
4004     break; case Key_Right:
4005         if ( dirkey == d[2] )
4006             dirkey = d[3];
4007         else if ( dirkey == d[6] )
4008             dirkey = d[5];
4009         else
4010             dirkey = d[4];
4011     break; case Key_Prior:
4012         dirkey = 0;
4013         if (message) message->Scroll(0,-1);
4014     break; case Key_Next:
4015         dirkey = 0;
4016         if (message) message->Scroll(0,+1);
4017     break; case Key_Space:
4018         if ( flags.rest_on_space ) {
4019             event->ignore();
4020             return;
4021         }
4022         case Key_Enter:
4023         if ( map )
4024             map->clickCursor();
4025     break; default:
4026         dirkey = 0;
4027         event->ignore();
4028     }
4029 }
4030
4031 void NetHackQtMainWindow::closeEvent(QCloseEvent* e)
4032 {
4033     if ( program_state.something_worth_saving ) {
4034         switch ( QMessageBox::information( this, "NetHack",
4035             "This will end your NetHack session",
4036             "&Save", "&Cancel", 0, 1 ) )
4037         {
4038             case 0:
4039                 // See dosave() function
4040                 if (dosave0()) {
4041                     u.uhp = -1;
4042                     NetHackQtBind::qt_exit_nhwindows(0);
4043                     terminate(EXIT_SUCCESS);
4044                 }
4045                 break;
4046             case 1:
4047                 break; // ignore the event
4048         }
4049     } else {
4050         e->accept();
4051     }
4052 }
4053
4054 void NetHackQtMainWindow::ShowIfReady()
4055 {
4056     if (message && map && status) {
4057         QPoint pos(0,0);
4058         QWidget* p = qt_compact_mode ? stack : centralWidget();
4059         message->Widget()->recreate(p,0,pos);
4060         map->Widget()->recreate(p,0,pos);
4061         status->Widget()->recreate(p,0,pos);
4062         if ( qt_compact_mode ) {
4063             message->setMap(map);
4064             stack->addWidget(map->Widget(), 0);
4065             stack->addWidget(message->Widget(), 1);
4066             stack->addWidget(status->Widget(), 2);
4067             raiseMap();
4068         } else {
4069             layout();
4070         }
4071         showMaximized();
4072     } else if (isVisible()) {
4073         hide();
4074     }
4075 }
4076
4077
4078 NetHackQtYnDialog::NetHackQtYnDialog(NetHackQtKeyBuffer& keysrc,const char* q,const char* ch,char df) :
4079     QDialog(qApp->mainWidget(),0,FALSE),
4080     question(q), choices(ch), def(df),
4081     keysource(keysrc)
4082 {
4083     setCaption("NetHack: Question");
4084 }
4085
4086 char NetHackQtYnDialog::Exec()
4087 {
4088     QString ch(choices);
4089     int ch_per_line=6;
4090     QString qlabel;
4091     QString enable;
4092     if ( qt_compact_mode && !choices ) {
4093         // expand choices from prompt
4094         // ##### why isn't choices set properly???
4095         const char* c=question;
4096         while ( *c && *c != '[' )
4097             c++;
4098         qlabel = QString(question).left(c-question);
4099         if ( *c ) {
4100             c++;
4101             if ( *c == '-' )
4102                 ch.append(*c++);
4103             char from=0;
4104             while ( *c && *c != ']' && *c != ' ' ) {
4105                 if ( *c == '-' ) {
4106                     from = c[-1];
4107                 } else if ( from ) {
4108                     for (char f=from+1; f<=*c; f++)
4109                         ch.append(f);
4110                     from = 0;
4111                 } else {
4112                     ch.append(*c);
4113                     from = 0;
4114                 }
4115                 c++;
4116             }
4117             if ( *c == ' ' ) {
4118                 while ( *c && *c != ']' ) {
4119                     if ( *c == '*' || *c == '?' )
4120                         ch.append(*c);
4121                     c++;
4122                 }
4123             }
4124         }
4125         if ( strstr(question, "what direction") ) {
4126             // We replace this regardless, since sometimes you get choices.
4127             const char* d = iflags.num_pad ? ndir : sdir; 
4128             enable=ch;
4129             ch="";
4130             ch.append(d[1]);
4131             ch.append(d[2]);
4132             ch.append(d[3]);
4133             ch.append(d[0]);
4134             ch.append('.');
4135             ch.append(d[4]);
4136             ch.append(d[7]);
4137             ch.append(d[6]);
4138             ch.append(d[5]);
4139             ch.append(d[8]);
4140             ch.append(d[9]);
4141             ch_per_line = 3;
4142             def = ' ';
4143         } else {
4144             // Hmm... they'll have to use a virtual keyboard
4145         }
4146     } else {
4147         qlabel = question;
4148     }
4149     if (!ch.isNull()) {
4150         QVBoxLayout vb(this);
4151         vb.setAutoAdd(TRUE);
4152         bool bigq = qlabel.length()>40;
4153         if ( bigq ) {
4154             QLabel* q = new QLabel(qlabel,this);
4155             q->setAlignment(AlignLeft|WordBreak);
4156             q->setMargin(4);
4157         }
4158         QButtonGroup group(ch_per_line, Horizontal,
4159             bigq ? QString::null : qlabel, this);
4160
4161         int nchoices=ch.length();
4162
4163         bool allow_count=ch.contains('#');
4164
4165         const int margin=8;
4166         const int gutter=8;
4167         const int extra=fontMetrics().height(); // Extra for group
4168         int x=margin, y=extra+margin;
4169         int butsize=fontMetrics().height()*2+5;
4170
4171         QPushButton* button;
4172         for (int i=0; i<nchoices && ch[i]!='\033'; i++) {
4173             button=new QPushButton(QString(ch[i]),&group);
4174             if ( !enable.isNull() ) {
4175                 if ( !enable.contains(ch[i]) )
4176                     button->setEnabled(FALSE);
4177             }
4178             button->setFixedSize(butsize,butsize); // Square
4179             if (ch[i]==def) button->setDefault(TRUE);
4180             if (i%10==9) {
4181                 // last in row
4182                 x=margin;
4183                 y+=butsize+gutter;
4184             } else {
4185                 x+=butsize+gutter;
4186             }
4187         }
4188
4189         connect(&group,SIGNAL(clicked(int)),this,SLOT(doneItem(int)));
4190
4191         QLabel* lb=0;
4192         QLineEdit* le=0;
4193
4194         if (allow_count) {
4195             QHBox *hb = new QHBox(this);
4196             lb=new QLabel("Count: ",hb);
4197             le=new QLineEdit(hb);
4198         }
4199
4200         adjustSize();
4201         centerOnMain(this);
4202         show();
4203         char choice=0;
4204         char ch_esc=0;
4205         for (uint i=0; i<ch.length(); i++) {
4206             if (ch[i].latin1()=='q') ch_esc='q';
4207             else if (!ch_esc && ch[i].latin1()=='n') ch_esc='n';
4208         }
4209         setResult(-1);
4210         while (!choice) {
4211             if (!keysource.Empty()) {
4212                 char k=keysource.GetAscii();
4213                 char ch_esc=0;
4214                 for (uint i=0; i<ch.length(); i++)
4215                     if (ch[i].latin1()==k)
4216                         choice=k;
4217                 if (!choice) {
4218                     if (k=='\033' && ch_esc)
4219                         choice=ch_esc;
4220                     else if (k==' ' || k=='\r' || k=='\n')
4221                         choice=def;
4222                     // else choice remains 0
4223                 }
4224             } else if ( result() == 0 ) {
4225                 choice = ch_esc ? ch_esc : def ? def : ' ';
4226             } else if ( result() == 1 ) {
4227                 choice = def ? def : ch_esc ? ch_esc : ' ';
4228             } else if ( result() >= 1000 ) {
4229                 choice = ch[result() - 1000].latin1();
4230             }
4231             if ( !choice )
4232                 qApp->enter_loop();
4233         }
4234         hide();
4235         if (allow_count && !le->text().isEmpty()) {
4236             yn_number=atoi(le->text());
4237             choice='#';
4238         }
4239         return choice;
4240     } else {
4241         QLabel label(qlabel,this);
4242         QPushButton cancel("Dismiss",this);
4243         label.setFrameStyle(QFrame::Box|QFrame::Sunken);
4244         label.setAlignment(AlignCenter);
4245         label.resize(fontMetrics().width(qlabel)+60,30+fontMetrics().height());
4246         cancel.move(width()/2-cancel.width()/2,label.geometry().bottom()+8);
4247         connect(&cancel,SIGNAL(clicked()),this,SLOT(reject()));
4248         centerOnMain(this);
4249         setResult(-1);
4250         show();
4251         while (result()<0 && keysource.Empty()) {
4252             qApp->enter_loop();
4253         }
4254         hide();
4255         if (keysource.Empty()) {
4256             return '\033';
4257         } else {
4258             return keysource.GetAscii();
4259         }
4260     }
4261 }
4262 void NetHackQtYnDialog::keyPressEvent(QKeyEvent* event)
4263 {
4264     // Don't want QDialog's Return/Esc behaviour
4265     event->ignore();
4266 }
4267
4268 void NetHackQtYnDialog::doneItem(int i)
4269 {
4270     done(i+1000);
4271 }
4272
4273 void NetHackQtYnDialog::done(int i)
4274 {
4275     setResult(i);
4276     qApp->exit_loop();
4277 }
4278
4279 NetHackQtGlyphs::NetHackQtGlyphs()
4280 {
4281     const char* tile_file = "nhtiles.bmp";
4282     if ( iflags.wc_tile_file )
4283         tile_file = iflags.wc_tile_file;
4284
4285     if (!img.load(tile_file)) {
4286         tile_file = "x11tiles";
4287         if (!img.load(tile_file)) {
4288             QString msg;
4289             msg.sprintf("Cannot load x11tiles or nhtiles.bmp");
4290             QMessageBox::warning(0, "IO Error", msg);
4291         } else {
4292             tiles_per_row = TILES_PER_ROW;
4293             if (img.width()%tiles_per_row) {
4294                 impossible("Tile file \"%s\" has %d columns, not multiple of row count (%d)",
4295                    tile_file, img.width(), tiles_per_row);
4296             }
4297         }
4298     } else {
4299         tiles_per_row = 40;
4300     }
4301
4302     if ( iflags.wc_tile_width )
4303         tilefile_tile_W = iflags.wc_tile_width;
4304     else
4305         tilefile_tile_W = img.width() / tiles_per_row;
4306     if ( iflags.wc_tile_height )
4307         tilefile_tile_H = iflags.wc_tile_height;
4308     else
4309         tilefile_tile_H = tilefile_tile_W;
4310
4311     setSize(tilefile_tile_W, tilefile_tile_H);
4312 }
4313
4314 void NetHackQtGlyphs::drawGlyph(QPainter& painter, int glyph, int x, int y)
4315 {
4316     int tile = glyph2tile[glyph];
4317     int px = (tile%tiles_per_row)*width();
4318     int py = tile/tiles_per_row*height();
4319
4320     painter.drawPixmap(
4321         x,
4322         y,
4323         pm,
4324         px,py,
4325         width(),height()
4326     );
4327 }
4328 void NetHackQtGlyphs::drawCell(QPainter& painter, int glyph, int cellx, int celly)
4329 {
4330     drawGlyph(painter,glyph,cellx*width(),celly*height());
4331 }
4332 void NetHackQtGlyphs::setSize(int w, int h)
4333 {
4334     if ( size == QSize(w,h) )
4335         return;
4336
4337     bool was1 = size == pm1.size();
4338     size = QSize(w,h);
4339     if (!w || !h)
4340         return; // Still not decided
4341
4342     if ( size == pm1.size() ) {
4343         pm = pm1;
4344         return;
4345     }
4346     if ( size == pm2.size() ) {
4347         pm = pm2;
4348         return;
4349     }
4350
4351     if (w==tilefile_tile_W && h==tilefile_tile_H) {
4352         pm.convertFromImage(img);
4353     } else {
4354         QApplication::setOverrideCursor( Qt::waitCursor );
4355         QImage scaled = img.smoothScale(
4356             w*img.width()/tilefile_tile_W,
4357             h*img.height()/tilefile_tile_H
4358         );
4359         pm.convertFromImage(scaled,Qt::ThresholdDither|Qt::PreferDither);
4360         QApplication::restoreOverrideCursor();
4361     }
4362     (was1 ? pm2 : pm1) = pm;
4363 }
4364
4365
4366 //////////////////////////////////////////////////////////////
4367 //
4368 //  The ugly C binding classes...
4369 //
4370 //////////////////////////////////////////////////////////////
4371
4372
4373 NetHackQtMenuOrTextWindow::NetHackQtMenuOrTextWindow(NetHackQtKeyBuffer& ks) :
4374     actual(0),
4375     keysource(ks)
4376 {
4377 }
4378
4379 QWidget* NetHackQtMenuOrTextWindow::Widget()
4380 {
4381     if (!actual) impossible("Widget called before we know if Menu or Text");
4382     return actual->Widget();
4383 }
4384
4385 // Text
4386 void NetHackQtMenuOrTextWindow::Clear()
4387 {
4388     if (!actual) impossible("Clear called before we know if Menu or Text");
4389     actual->Clear();
4390 }
4391 void NetHackQtMenuOrTextWindow::Display(bool block)
4392 {
4393     if (!actual) impossible("Display called before we know if Menu or Text");
4394     actual->Display(block);
4395 }
4396 bool NetHackQtMenuOrTextWindow::Destroy()
4397 {
4398     if (!actual) impossible("Destroy called before we know if Menu or Text");
4399     return actual->Destroy();
4400 }
4401
4402 void NetHackQtMenuOrTextWindow::PutStr(int attr, const char* text)
4403 {
4404     if (!actual) actual=new NetHackQtTextWindow(keysource);
4405     actual->PutStr(attr,text);
4406 }
4407
4408 // Menu
4409 void NetHackQtMenuOrTextWindow::StartMenu()
4410 {
4411     if (!actual) actual=new NetHackQtMenuWindow(keysource);
4412     actual->StartMenu();
4413 }
4414 void NetHackQtMenuOrTextWindow::AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr,
4415         const char* str, bool presel)
4416 {
4417     if (!actual) impossible("AddMenu called before we know if Menu or Text");
4418     actual->AddMenu(glyph,identifier,ch,gch,attr,str,presel);
4419 }
4420 void NetHackQtMenuOrTextWindow::EndMenu(const char* prompt)
4421 {
4422     if (!actual) impossible("EndMenu called before we know if Menu or Text");
4423     actual->EndMenu(prompt);
4424 }
4425 int NetHackQtMenuOrTextWindow::SelectMenu(int how, MENU_ITEM_P **menu_list)
4426 {
4427     if (!actual) impossible("SelectMenu called before we know if Menu or Text");
4428     return actual->SelectMenu(how,menu_list);
4429 }
4430
4431
4432 // XXX Should be from Options
4433 //
4434 // XXX Hmm.  Tricky part is that perhaps some macros should only be active
4435 // XXX       when a key is about to be gotten.  For example, the user could
4436 // XXX       define "-" to do "E-yyyyyyyy\r", but would still need "-" for
4437 // XXX       other purposes.  Maybe just too bad.
4438 //
4439 struct {
4440     int key;
4441     int state;
4442     const char* macro;
4443 } key_macro[]={
4444     { Qt::Key_F1, 0, "n100." }, // Rest (x100)
4445     { Qt::Key_F2, 0, "n20s" },  // Search (x20)
4446     { Qt::Key_F3, 0, "o8o4o6o2o8o4o6o2o8o4o6o2" }, // Open all doors (x3)
4447     { Qt::Key_Tab, 0, "\001" },
4448     { 0, 0, 0 }
4449 };
4450
4451
4452 NetHackQtBind::NetHackQtBind(int& argc, char** argv) :
4453 #ifdef KDE
4454     KApplication(argc,argv)
4455 #elif defined(QWS) // not quite the right condition
4456     QPEApplication(argc,argv)
4457 #else
4458     QApplication(argc,argv)
4459 #endif
4460 {
4461     QPixmap pm("nhsplash.xpm");
4462     if ( iflags.wc_splash_screen && !pm.isNull() ) {
4463         QVBox *vb = new QVBox(0,0,
4464             WStyle_Customize | WStyle_NoBorder | nh_WX11BypassWM | WStyle_StaysOnTop );
4465         splash = vb;
4466         QLabel *lsplash = new QLabel(vb);
4467         lsplash->setAlignment(AlignCenter);
4468         lsplash->setPixmap(pm);
4469         QLabel* capt = new QLabel("Loading...",vb);
4470         capt->setAlignment(AlignCenter);
4471         if ( pm.mask() ) {
4472             lsplash->setFixedSize(pm.size());
4473             lsplash->setMask(*pm.mask());
4474         }
4475         splash->move((QApplication::desktop()->width()-pm.width())/2,
4476                       (QApplication::desktop()->height()-pm.height())/2);
4477         //splash->setGeometry(0,0,100,100);
4478         if ( qt_compact_mode ) {
4479             splash->showMaximized();
4480         } else {
4481             vb->setFrameStyle(QFrame::WinPanel|QFrame::Raised);
4482             vb->setMargin(10);
4483             splash->adjustSize();
4484             splash->show();
4485         }
4486
4487         // force content refresh outside event loop
4488         splash->repaint(FALSE);
4489         lsplash->repaint(FALSE);
4490         capt->repaint(FALSE);
4491         qApp->flushX();
4492
4493     } else {
4494         splash = 0;
4495     }
4496     main = new NetHackQtMainWindow(keybuffer);
4497 #if defined(QWS) // not quite the right condition
4498     showMainWidget(main);
4499 #else
4500     setMainWidget(main);
4501 #endif
4502     qt_settings=new NetHackQtSettings(main->width(),main->height());
4503 }
4504
4505 void NetHackQtBind::qt_init_nhwindows(int* argc, char** argv)
4506 {
4507 #ifdef UNIX
4508 // Userid control
4509 //
4510 // Michael Hohmuth <hohmuth@inf.tu-dresden.de>...
4511 //
4512 // As the game runs setuid games, it must seteuid(getuid()) before
4513 // calling XOpenDisplay(), and reset the euid afterwards.
4514 // Otherwise, it can't read the $HOME/.Xauthority file and whines about
4515 // not being able to open the X display (if a magic-cookie
4516 // authorization mechanism is being used). 
4517
4518     uid_t gamesuid=geteuid();
4519     seteuid(getuid());
4520 #endif
4521
4522     QApplication::setColorSpec(ManyColor);
4523     instance=new NetHackQtBind(*argc,argv);
4524
4525 #ifdef UNIX
4526     seteuid(gamesuid);
4527 #endif
4528
4529 #ifdef _WS_WIN_
4530     // This nethack engine feature should be moved into windowport API
4531     nt_kbhit = NetHackQtBind::qt_kbhit;
4532 #endif
4533 }
4534
4535 int NetHackQtBind::qt_kbhit()
4536 {
4537     return !keybuffer.Empty();
4538 }
4539
4540 static bool have_asked = FALSE;
4541
4542 void NetHackQtBind::qt_player_selection()
4543 {
4544     if ( !have_asked )
4545         qt_askname();
4546 }
4547
4548 NetHackQtSavedGameSelector::NetHackQtSavedGameSelector(const char** saved) :
4549     QDialog(qApp->mainWidget(),"sgsel",TRUE)
4550 {
4551     QVBoxLayout *vbl = new QVBoxLayout(this,6);
4552     QHBox* hb;
4553
4554     QLabel* logo = new QLabel(this); vbl->addWidget(logo);
4555     logo->setAlignment(AlignCenter);
4556     logo->setPixmap(QPixmap("nhsplash.xpm"));
4557     QLabel* attr = new QLabel("by the NetHack DevTeam",this);
4558     attr->setAlignment(AlignCenter);
4559     vbl->addWidget(attr);
4560     vbl->addStretch(2);
4561     /*
4562     QLabel* logo = new QLabel(hb);
4563     hb = new QHBox(this);
4564     vbl->addWidget(hb, AlignCenter);
4565     logo->setPixmap(QPixmap(nh_icon));
4566     logo->setAlignment(AlignRight|AlignVCenter);
4567     new QLabel(nh_attribution,hb);
4568     */
4569
4570     hb = new QHBox(this);
4571     vbl->addWidget(hb, AlignCenter);
4572     QPushButton* q = new QPushButton("Quit",hb);
4573     connect(q, SIGNAL(clicked()), this, SLOT(reject()));
4574     QPushButton* c = new QPushButton("New Game",hb);
4575     connect(c, SIGNAL(clicked()), this, SLOT(accept()));
4576     c->setDefault(TRUE);
4577
4578     QButtonGroup* bg = new QButtonGroup(3, Horizontal, "Saved Characters",this);
4579     vbl->addWidget(bg);
4580     connect(bg, SIGNAL(clicked(int)), this, SLOT(done(int)));
4581     for (int i=0; saved[i]; i++) {
4582         QPushButton* b = new QPushButton(saved[i],bg);
4583         bg->insert(b, i+2);
4584     }
4585 }
4586
4587 int NetHackQtSavedGameSelector::choose()
4588 {
4589 #if defined(QWS) // probably safe with Qt 3, too (where show!=exec in QDialog).
4590     if ( qt_compact_mode )
4591         showMaximized();
4592 #endif
4593     return exec()-2;
4594 }
4595
4596 void NetHackQtBind::qt_askname()
4597 {
4598     have_asked = TRUE;
4599
4600     // We do it all here, and nothing in askname
4601
4602     char** saved = get_saved_games();
4603     int ch = -1;
4604     if ( saved && *saved ) {
4605         if ( splash ) splash->hide();
4606         NetHackQtSavedGameSelector sgsel((const char**)saved);
4607         ch = sgsel.choose();
4608         if ( ch >= 0 )
4609             strcpy(plname,saved[ch]);
4610     }
4611     free_saved_games(saved);
4612
4613     switch (ch) {
4614       case -1:
4615         if ( splash ) splash->hide();
4616         if (NetHackQtPlayerSelector(keybuffer).Choose())
4617             return;
4618       case -2:
4619         break;
4620       default:
4621         return;
4622     }
4623
4624     // Quit
4625     clearlocks();
4626     qt_exit_nhwindows(0);
4627     terminate(0);
4628 }
4629
4630 void NetHackQtBind::qt_get_nh_event()
4631 {
4632 }
4633
4634 #if defined(QWS)
4635 // Kludge to access lastWindowClosed() signal.
4636 class TApp : public QApplication {
4637 public:
4638     TApp(int& c, char**v) : QApplication(c,v) {}
4639     void lwc() { emit lastWindowClosed(); }
4640 };
4641 #endif
4642  
4643 void NetHackQtBind::qt_exit_nhwindows(const char *)
4644 {
4645 #if defined(QWS)
4646     // Avoids bug in SHARP SL5500
4647     ((TApp*)qApp)->lwc();
4648     qApp->quit();
4649 #endif
4650  
4651     delete instance; // ie. qApp
4652 }
4653
4654 void NetHackQtBind::qt_suspend_nhwindows(const char *)
4655 {
4656 }
4657
4658 void NetHackQtBind::qt_resume_nhwindows()
4659 {
4660 }
4661
4662 static QArray<NetHackQtWindow*> id_to_window;
4663
4664 winid NetHackQtBind::qt_create_nhwindow(int type)
4665 {
4666     winid id;
4667     for (id = 0; id < (winid) id_to_window.size(); id++) {
4668         if ( !id_to_window[id] )
4669             break;
4670     }
4671     if ( id == (winid) id_to_window.size() )
4672         id_to_window.resize(id+1);
4673
4674     NetHackQtWindow* window=0;
4675
4676     switch (type) {
4677      case NHW_MAP: {
4678         NetHackQtMapWindow* w=new NetHackQtMapWindow(clickbuffer);
4679         main->AddMapWindow(w);
4680         window=w;
4681     } break; case NHW_MESSAGE: {
4682         NetHackQtMessageWindow* w=new NetHackQtMessageWindow;
4683         main->AddMessageWindow(w);
4684         window=w;
4685     } break; case NHW_STATUS: {
4686         NetHackQtStatusWindow* w=new NetHackQtStatusWindow;
4687         main->AddStatusWindow(w);
4688         window=w;
4689     } break; case NHW_MENU:
4690         window=new NetHackQtMenuOrTextWindow(keybuffer);
4691     break; case NHW_TEXT:
4692         window=new NetHackQtTextWindow(keybuffer);
4693     }
4694
4695     window->nhid = id;
4696
4697     // Note: use of isHidden does not work with Qt 2.1
4698     if ( splash 
4699 #if QT_VERSION >= 300
4700         && !main->isHidden()
4701 #else
4702         && main->isVisible()
4703 #endif
4704         )
4705     {
4706         delete splash;
4707         splash = 0;
4708     }
4709
4710     id_to_window[id] = window;
4711     return id;
4712 }
4713
4714 void NetHackQtBind::qt_clear_nhwindow(winid wid)
4715 {
4716     NetHackQtWindow* window=id_to_window[wid];
4717     window->Clear();
4718 }
4719
4720 void NetHackQtBind::qt_display_nhwindow(winid wid, BOOLEAN_P block)
4721 {
4722     NetHackQtWindow* window=id_to_window[wid];
4723     window->Display(block);
4724 }
4725
4726 void NetHackQtBind::qt_destroy_nhwindow(winid wid)
4727 {
4728     NetHackQtWindow* window=id_to_window[wid];
4729     main->RemoveWindow(window);
4730     if (window->Destroy())
4731         delete window;
4732     id_to_window[wid] = 0;
4733 }
4734
4735 void NetHackQtBind::qt_curs(winid wid, int x, int y)
4736 {
4737     NetHackQtWindow* window=id_to_window[wid];
4738     window->CursorTo(x,y);
4739 }
4740
4741 void NetHackQtBind::qt_putstr(winid wid, int attr, const char *text)
4742 {
4743     NetHackQtWindow* window=id_to_window[wid];
4744     window->PutStr(attr,text);
4745 }
4746
4747 void NetHackQtBind::qt_display_file(const char *filename, BOOLEAN_P must_exist)
4748 {
4749     NetHackQtTextWindow* window=new NetHackQtTextWindow(keybuffer);
4750     bool complain = FALSE;
4751
4752 #ifdef DLB
4753     {
4754         dlb *f;
4755         char buf[BUFSZ];
4756         char *cr;
4757
4758         window->Clear();
4759         f = dlb_fopen(filename, "r");
4760         if (!f) {
4761             complain = must_exist;
4762         } else {
4763             while (dlb_fgets(buf, BUFSZ, f)) {
4764                 if ((cr = index(buf, '\n')) != 0) *cr = 0;
4765 #ifdef MSDOS
4766                 if ((cr = index(buf, '\r')) != 0) *cr = 0;
4767 #endif
4768                 if (index(buf, '\t') != 0) (void) tabexpand(buf);
4769                 window->PutStr(ATR_NONE, buf);
4770             }
4771             window->Display(FALSE);
4772             (void) dlb_fclose(f);
4773         }
4774     }
4775 #else
4776     QFile file(filename);
4777
4778     if (file.open(IO_ReadOnly)) {
4779         char line[128];
4780         while (file.readLine(line,127) >= 0) {
4781             line[strlen(line)-1]=0;// remove newline
4782             window->PutStr(ATR_NONE,line);
4783         }
4784         window->Display(FALSE);
4785     } else {
4786         complain = must_exist;
4787     }
4788 #endif
4789
4790     if (complain) {
4791         QString message;
4792         message.sprintf("File not found: %s\n",filename);
4793         QMessageBox::message("File Error", (const char*)message, "Ignore");
4794     }
4795 }
4796
4797 void NetHackQtBind::qt_start_menu(winid wid)
4798 {
4799     NetHackQtWindow* window=id_to_window[wid];
4800     window->StartMenu();
4801 }
4802
4803 void NetHackQtBind::qt_add_menu(winid wid, int glyph,
4804     const ANY_P * identifier, CHAR_P ch, CHAR_P gch, int attr,
4805     const char *str, BOOLEAN_P presel)
4806 {
4807     NetHackQtWindow* window=id_to_window[wid];
4808     window->AddMenu(glyph, identifier, ch, gch, attr, str, presel);
4809 }
4810
4811 void NetHackQtBind::qt_end_menu(winid wid, const char *prompt)
4812 {
4813     NetHackQtWindow* window=id_to_window[wid];
4814     window->EndMenu(prompt);
4815 }
4816
4817 int NetHackQtBind::qt_select_menu(winid wid, int how, MENU_ITEM_P **menu_list)
4818 {
4819     NetHackQtWindow* window=id_to_window[wid];
4820     return window->SelectMenu(how,menu_list);
4821 }
4822
4823 void NetHackQtBind::qt_update_inventory()
4824 {
4825     if (main)
4826         main->updateInventory();
4827     /* doesn't work yet
4828     if (program_state.something_worth_saving && flags.perm_invent)
4829         display_inventory(NULL, FALSE);
4830     */
4831 }
4832
4833 void NetHackQtBind::qt_mark_synch()
4834 {
4835 }
4836
4837 void NetHackQtBind::qt_wait_synch()
4838 {
4839 }
4840
4841 void NetHackQtBind::qt_cliparound(int x, int y)
4842 {
4843     // XXXNH - winid should be a parameter!
4844     qt_cliparound_window(WIN_MAP,x,y);
4845 }
4846
4847 void NetHackQtBind::qt_cliparound_window(winid wid, int x, int y)
4848 {
4849     NetHackQtWindow* window=id_to_window[wid];
4850     window->ClipAround(x,y);
4851 }
4852 void NetHackQtBind::qt_print_glyph(winid wid,XCHAR_P x,XCHAR_P y,int glyph)
4853 {
4854     NetHackQtWindow* window=id_to_window[wid];
4855     window->PrintGlyph(x,y,glyph);
4856 }
4857 //void NetHackQtBind::qt_print_glyph_compose(winid wid,XCHAR_P x,XCHAR_P y,int glyph1, int glyph2)
4858 //{
4859     //NetHackQtWindow* window=id_to_window[wid];
4860     //window->PrintGlyphCompose(x,y,glyph1,glyph2);
4861 //}
4862
4863 void NetHackQtBind::qt_raw_print(const char *str)
4864 {
4865     puts(str);
4866 }
4867
4868 void NetHackQtBind::qt_raw_print_bold(const char *str)
4869 {
4870     puts(str);
4871 }
4872
4873 int NetHackQtBind::qt_nhgetch()
4874 {
4875     if (main)
4876         main->fadeHighlighting();
4877
4878     // Process events until a key arrives.
4879     //
4880     while (keybuffer.Empty()) {
4881         qApp->enter_loop();
4882     }
4883
4884     return keybuffer.GetAscii();
4885 }
4886
4887 int NetHackQtBind::qt_nh_poskey(int *x, int *y, int *mod)
4888 {
4889     if (main)
4890         main->fadeHighlighting();
4891
4892     // Process events until a key or map-click arrives.
4893     //
4894     while (keybuffer.Empty() && clickbuffer.Empty()) {
4895         qApp->enter_loop();
4896     }
4897     if (!keybuffer.Empty()) {
4898         return keybuffer.GetAscii();
4899     } else {
4900         *x=clickbuffer.NextX();
4901         *y=clickbuffer.NextY();
4902         *mod=clickbuffer.NextMod();
4903         clickbuffer.Get();
4904         return 0;
4905     }
4906 }
4907
4908 void NetHackQtBind::qt_nhbell()
4909 {
4910     QApplication::beep();
4911 }
4912
4913 int NetHackQtBind::qt_doprev_message()
4914 {
4915     // Don't need it - uses scrollbar
4916     // XXX but could make this a shortcut
4917     return 0;
4918 }
4919
4920 char NetHackQtBind::qt_yn_function(const char *question, const char *choices, CHAR_P def)
4921 {
4922     if (qt_settings->ynInMessages() && WIN_MESSAGE!=WIN_ERR) {
4923         // Similar to X11 windowport `slow' feature.
4924
4925         char message[BUFSZ];
4926         char yn_esc_map='\033';
4927
4928         if (choices) {
4929             char *cb, choicebuf[QBUFSZ];
4930             Strcpy(choicebuf, choices);
4931             if ((cb = index(choicebuf, '\033')) != 0) {
4932                 // anything beyond <esc> is hidden
4933                 *cb = '\0';
4934             }
4935             Sprintf(message, "%s [%s] ", question, choicebuf);
4936             if (def) Sprintf(eos(message), "(%c) ", def);
4937             // escape maps to 'q' or 'n' or default, in that order
4938             yn_esc_map = (index(choices, 'q') ? 'q' :
4939                      (index(choices, 'n') ? 'n' : def));
4940         } else {
4941             Strcpy(message, question);
4942         }
4943
4944 #ifdef USE_POPUPS
4945         // Improve some special-cases (DIRKS 08/02/23)
4946         if (strcmp (choices,"ynq") == 0) {
4947             switch (QMessageBox::information (qApp->mainWidget(),"NetHack",question,"&Yes","&No","&Quit",0,2))
4948             {
4949               case 0: return 'y'; 
4950               case 1: return 'n'; 
4951               case 2: return 'q'; 
4952             }
4953         }
4954
4955         if (strcmp (choices,"yn") == 0) {
4956             switch (QMessageBox::information(qApp->mainWidget(),"NetHack",question,"&Yes", "&No",0,1))
4957             {
4958               case 0: return 'y';
4959               case 1: return 'n'; 
4960             }
4961         }
4962 #endif
4963
4964         NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message);
4965
4966         int result=-1;
4967         while (result<0) {
4968             char ch=NetHackQtBind::qt_nhgetch();
4969             if (ch=='\033') {
4970                 result=yn_esc_map;
4971             } else if (choices && !index(choices,ch)) {
4972                 if (def && (ch==' ' || ch=='\r' || ch=='\n')) {
4973                     result=def;
4974                 } else {
4975                     NetHackQtBind::qt_nhbell();
4976                     // and try again...
4977                 }
4978             } else {
4979                 result=ch;
4980             }
4981         }
4982
4983         NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);
4984
4985         return result;
4986     } else {
4987         NetHackQtYnDialog dialog(keybuffer,question,choices,def);
4988         return dialog.Exec();
4989     }
4990 }
4991
4992 void NetHackQtBind::qt_getlin(const char *prompt, char *line)
4993 {
4994     NetHackQtStringRequestor requestor(keybuffer,prompt);
4995     if (!requestor.Get(line)) {
4996         line[0]=0;
4997     }
4998 }
4999
5000 NetHackQtExtCmdRequestor::NetHackQtExtCmdRequestor(NetHackQtKeyBuffer& ks) :
5001     QDialog(qApp->mainWidget(), "ext-cmd", FALSE),
5002     keysource(ks)
5003 {
5004     int marg=4;
5005     QVBoxLayout *l = new QVBoxLayout(this,marg,marg);
5006
5007     QPushButton* can = new QPushButton("Cancel", this);
5008     can->setDefault(TRUE);
5009     can->setMinimumSize(can->sizeHint());
5010     l->addWidget(can);
5011
5012     QButtonGroup *group=new QButtonGroup("",0);
5013     QGroupBox *grid=new QGroupBox("Extended commands",this);
5014     l->addWidget(grid);
5015
5016     int i;
5017     int butw=50;
5018     QFontMetrics fm = fontMetrics();
5019     for (i=0; extcmdlist[i].ef_txt; i++) {
5020         butw = QMAX(butw,30+fm.width(extcmdlist[i].ef_txt));
5021     }
5022     int ncols=4;
5023     int nrows=(i+ncols-1)/ncols;
5024
5025     QVBoxLayout* bl = new QVBoxLayout(grid,marg);
5026     bl->addSpacing(fm.height());
5027     QGridLayout* gl = new QGridLayout(nrows,ncols,marg);
5028     bl->addLayout(gl);
5029     for (i=0; extcmdlist[i].ef_txt; i++) {
5030         QPushButton* pb=new QPushButton(extcmdlist[i].ef_txt, grid);
5031         pb->setMinimumSize(butw,pb->sizeHint().height());
5032         group->insert(pb);
5033         gl->addWidget(pb,i/ncols,i%ncols);
5034     }
5035     connect(group,SIGNAL(clicked(int)),this,SLOT(done(int)));
5036
5037     bl->activate();
5038     l->activate();
5039     resize(1,1);
5040
5041     connect(can,SIGNAL(clicked()),this,SLOT(cancel()));
5042 }
5043
5044 void NetHackQtExtCmdRequestor::cancel()
5045 {
5046     setResult(-1);
5047     qApp->exit_loop();
5048 }
5049
5050 void NetHackQtExtCmdRequestor::done(int i)
5051 {
5052     setResult(i);
5053     qApp->exit_loop();
5054 }
5055
5056 int NetHackQtExtCmdRequestor::get()
5057 {
5058     const int none = -10;
5059     char str[32];
5060     int cursor=0;
5061     resize(1,1); // pack
5062     centerOnMain(this);
5063     show();
5064     setResult(none);
5065     while (result()==none) {
5066         while (result()==none && !keysource.Empty()) {
5067             char k=keysource.GetAscii();
5068             if (k=='\r' || k=='\n' || k==' ' || k=='\033') {
5069                 setResult(-1);
5070             } else {
5071                 str[cursor++] = k;
5072                 int r=-1;
5073                 for (int i=0; extcmdlist[i].ef_txt; i++) {
5074                     if (qstrnicmp(str, extcmdlist[i].ef_txt, cursor)==0) {
5075                         if ( r == -1 )
5076                             r = i;
5077                         else
5078                             r = -2;
5079                     }
5080                 }
5081                 if ( r == -1 ) { // no match!
5082                     QApplication::beep();
5083                     cursor=0;
5084                 } else if ( r != -2 ) { // only one match
5085                     setResult(r);
5086                 }
5087             }
5088         }
5089         if (result()==none)
5090             qApp->enter_loop();
5091     }
5092     hide();
5093     return result();
5094 }
5095
5096
5097 int NetHackQtBind::qt_get_ext_cmd()
5098 {
5099     NetHackQtExtCmdRequestor requestor(keybuffer);
5100     return requestor.get();
5101 }
5102
5103 void NetHackQtBind::qt_number_pad(int)
5104 {
5105     // Ignore.
5106 }
5107
5108 void NetHackQtBind::qt_delay_output()
5109 {
5110     NetHackQtDelay delay(15);
5111     delay.wait();
5112 }
5113
5114 void NetHackQtBind::qt_start_screen()
5115 {
5116     // Ignore.
5117 }
5118
5119 void NetHackQtBind::qt_end_screen()
5120 {
5121     // Ignore.
5122 }
5123
5124 void NetHackQtBind::qt_outrip(winid wid, int how)
5125 {
5126     NetHackQtWindow* window=id_to_window[wid];
5127
5128     window->UseRIP(how);
5129 }
5130
5131 bool NetHackQtBind::notify(QObject *receiver, QEvent *event)
5132 {
5133     // Ignore Alt-key navigation to menubar, it's annoying when you
5134     // use Alt-Direction to move around.
5135     if ( main && event->type()==QEvent::KeyRelease && main==receiver
5136             && ((QKeyEvent*)event)->key() == Key_Alt )
5137         return TRUE;
5138
5139     bool result=QApplication::notify(receiver,event);
5140     if (event->type()==QEvent::KeyPress) {
5141         QKeyEvent* key_event=(QKeyEvent*)event;
5142
5143         if (!key_event->isAccepted()) {
5144             const int k=key_event->key();
5145             bool macro=FALSE;
5146             for (int i=0; !macro && key_macro[i].key; i++) {
5147                 if (key_macro[i].key==k
5148                  && ((key_macro[i].state&key_event->state())==key_macro[i].state))
5149                 {
5150                     keybuffer.Put(key_macro[i].macro);
5151                     macro=TRUE;
5152                 }
5153             }
5154             char ch=key_event->ascii();
5155             if ( !ch && (key_event->state() & Qt::ControlButton) ) {
5156                 // On Mac, ascii control codes are not sent, force them.
5157                 if ( k>=Qt::Key_A && k<=Qt::Key_Z )
5158                     ch = k - Qt::Key_A + 1;
5159             }
5160             if (!macro && ch) {
5161                 bool alt = (key_event->state()&AltButton) ||
5162                    (k >= Key_0 && k <= Key_9 && (key_event->state()&ControlButton));
5163                 keybuffer.Put(key_event->key(),ch + (alt ? 128 : 0),
5164                     key_event->state());
5165                 key_event->accept();
5166                 result=TRUE;
5167             }
5168
5169             if (ch || macro) {
5170                 qApp->exit_loop();
5171             }
5172         }
5173     }
5174     return result;
5175 }
5176
5177 NetHackQtBind* NetHackQtBind::instance=0;
5178 NetHackQtKeyBuffer NetHackQtBind::keybuffer;
5179 NetHackQtClickBuffer NetHackQtBind::clickbuffer;
5180 NetHackQtMainWindow* NetHackQtBind::main=0;
5181 QWidget* NetHackQtBind::splash=0;
5182
5183
5184 extern "C" struct window_procs Qt_procs;
5185
5186 struct window_procs Qt_procs = {
5187     "Qt",
5188     WC_COLOR|WC_HILITE_PET|
5189         WC_ASCII_MAP|WC_TILED_MAP|
5190         WC_FONT_MAP|WC_TILE_FILE|WC_TILE_WIDTH|WC_TILE_HEIGHT|
5191         WC_PLAYER_SELECTION|WC_SPLASH_SCREEN,
5192     0L,
5193     NetHackQtBind::qt_init_nhwindows,
5194     NetHackQtBind::qt_player_selection,
5195     NetHackQtBind::qt_askname,
5196     NetHackQtBind::qt_get_nh_event,
5197     NetHackQtBind::qt_exit_nhwindows,
5198     NetHackQtBind::qt_suspend_nhwindows,
5199     NetHackQtBind::qt_resume_nhwindows,
5200     NetHackQtBind::qt_create_nhwindow,
5201     NetHackQtBind::qt_clear_nhwindow,
5202     NetHackQtBind::qt_display_nhwindow,
5203     NetHackQtBind::qt_destroy_nhwindow,
5204     NetHackQtBind::qt_curs,
5205     NetHackQtBind::qt_putstr,
5206     NetHackQtBind::qt_display_file,
5207     NetHackQtBind::qt_start_menu,
5208     NetHackQtBind::qt_add_menu,
5209     NetHackQtBind::qt_end_menu,
5210     NetHackQtBind::qt_select_menu,
5211     genl_message_menu,      /* no need for X-specific handling */
5212     NetHackQtBind::qt_update_inventory,
5213     NetHackQtBind::qt_mark_synch,
5214     NetHackQtBind::qt_wait_synch,
5215 #ifdef CLIPPING
5216     NetHackQtBind::qt_cliparound,
5217 #endif
5218 #ifdef POSITIONBAR
5219     donull,
5220 #endif
5221     NetHackQtBind::qt_print_glyph,
5222     //NetHackQtBind::qt_print_glyph_compose,
5223     NetHackQtBind::qt_raw_print,
5224     NetHackQtBind::qt_raw_print_bold,
5225     NetHackQtBind::qt_nhgetch,
5226     NetHackQtBind::qt_nh_poskey,
5227     NetHackQtBind::qt_nhbell,
5228     NetHackQtBind::qt_doprev_message,
5229     NetHackQtBind::qt_yn_function,
5230     NetHackQtBind::qt_getlin,
5231     NetHackQtBind::qt_get_ext_cmd,
5232     NetHackQtBind::qt_number_pad,
5233     NetHackQtBind::qt_delay_output,
5234 #ifdef CHANGE_COLOR     /* only a Mac option currently */
5235     donull,
5236     donull,
5237 #endif
5238     /* other defs that really should go away (they're tty specific) */
5239     NetHackQtBind::qt_start_screen,
5240     NetHackQtBind::qt_end_screen,
5241 #ifdef GRAPHIC_TOMBSTONE
5242     NetHackQtBind::qt_outrip,
5243 #else
5244     genl_outrip,
5245 #endif
5246     genl_preference_update,
5247 };
5248
5249 extern "C" void play_usersound(const char* filename, int volume)
5250 {
5251 #ifdef USER_SOUNDS
5252 #ifndef QT_NO_SOUND
5253     QSound::play(filename);
5254 #endif
5255 #endif
5256 }
5257
5258 #include "qt_win.moc"
5259 #ifndef KDE
5260 #include "qt_kde0.moc"
5261 #endif
5262 #if QT_VERSION >= 300
5263 #include "qttableview.moc"
5264 #endif