// TODO:
//   + Add command-line options for tile/font size and ynInMessages
//   + Optionally have pop-ups appear in the Messages list instead (yn_func)
//   + Honor attributes in text windows (for, eg. topten)
//   + Show menu of exist games for userid (look at save/<uid>*.gz)
//   q Font size changes don't work - Qt bug.
//   Q Bug with menu items (bottom item disappears) - prob. Qt bug.
//   Q Dialogs can steal keys as they disappear - maybe Qt or X11 bug.
//   ? KDE toolbar, menu KDE style guide like
//   + META Cmd should give a menu for available commands
//   + player selector with icons?
//   + figure with worn/wielded items as image
//   ? internationalization, Session management, HTML help
//   ? write KDE config files for KDE
//   + better icons 
//
// ?=maybe  *=bug  -=misfeature  +=feature  Q=Qt problem (q=worked around)
//


// Qt Binding for NetHack 3.2
//
// Copyright (C) 1996 by Warwick W. Allison (warwick@cs.uq.edu.au)
// 
// 
// Unfortunately, this doesn't use Qt as well as I would like,
// primarily because NetHack is fundamentally a getkey-type program
// rather than being event driven (hence the ugly key and click buffer)
// and also because this is my first major application of Qt.  
// 
// The problem of NetHack's getkey requirement is solved by intercepting
// key events by overiding QApplicion::notify(...), and putting them in
// a buffer.  Mouse clicks on the map window are treated with a similar
// buffer.  When the NetHack engine calls for a key, one is taken from
// the buffer, or if that is empty, QApplication::enter_loop() is called.
// Whenever keys or clicks go into the buffer, QApplication::exit_loop()
// is called.
//
// Another problem is that some NetHack players are decade-long players who
// demand complete keyboard control (while Qt and X11 conspire to make this
// difficult by having widget-based focus rather than application based -
// a good thing in general).  This problem is solved by again using the key
// event buffer.
//
// Out of all this hackery comes a silver lining however, as macros for
// the super-expert and menus for the ultra-newbie are also made possible
// by the key event buffer.
//

extern "C" {
#include "hack.h"
// Undefine NetHack macros which conflict with Qt identifiers.
//
#undef Warning
#undef red
#undef green
#undef blue
#undef Black
#undef curs
#undef TRUE
#undef FALSE
}

#define ROW_TILES 32

// #define TINY

#include "qt_win.h"
#include <qpainter.h>
#include <qbitmap.h>
#include <qkeycode.h>
#include <qmenubar.h>
#include <qpopmenu.h>
#include <qcombo.h>

#include <ctype.h>

#ifdef KDE
#include <ktopwidget.h>
#endif

#include "cluster.h"

#include <unistd.h>
#include "qviewp.h"
#ifdef USER_SOUNDS
#include "qaudio.h"
#endif


#ifdef USER_SOUNDS
extern "C" void play_sound_for_message(const char* str);
#endif


NetHackQtLineEdit::NetHackQtLineEdit()
{
}

NetHackQtLineEdit::NetHackQtLineEdit(QWidget* parent, const char* name) :
	QLineEdit(parent,name)
{
}

void NetHackQtLineEdit::fakeEvent(int key, int ascii, int state)
{
	QKeyEvent fake(Event_KeyPress,key,ascii,state);
	keyPressEvent(&fake);
}


NetHackQtSettings::NetHackQtSettings(int w, int h) :
	tilesize(this),
	fontsize(this),
	normal("times"),
//	normalfixed("fixed"),
        normalfixed("courier"),

	large("times"),
	theglyphs(0)

{
	int default_tilesize;
	int default_fontsize;

	if (w<=700) {
		// ~640x480
		default_tilesize=3;
		default_fontsize=3;
	} else if (w<=1100) {
		// ~1024x768
		default_tilesize=1;
		default_fontsize=2;
	} else if (w<=1200) {
		// ~1152x900
		default_tilesize=1;
		default_fontsize=1;
	} else {
		// ~1280x1024 and larger
		default_tilesize=0;
		default_fontsize=0;
	}

	// XXX Don't provide these for editing yet.
	petDataFile.setText("pet_mark.xpm");
	petMaskFile.setText("pet_mark.pbm");

	tilesize.insertItem("Huge");
	tilesize.insertItem("Large");
	tilesize.insertItem("Medium");
	tilesize.insertItem("Small");
	tilesize.setCurrentItem(default_tilesize);
	connect(&tilesize,SIGNAL(activated(int)),this,SLOT(loadTiles()));

	fontsize.insertItem("Huge");
	fontsize.insertItem("Large");
	fontsize.insertItem("Medium");
	fontsize.insertItem("Small");
	fontsize.setCurrentItem(default_fontsize);
	connect(&fontsize,SIGNAL(activated(int)),this,SIGNAL(fontChanged()));

	// Geometry - fixed
	const int margin=8;
	const int gutter=8;
	int x=margin,y=margin;
	int w=0;
	QLabel* tlabel=new QLabel("Tiles:",this);
	tlabel->setGeometry(x,y,tlabel->width(),tilesize.height()); x+=tlabel->width()+gutter;
	tlabel->setAlignment(AlignRight|AlignVCenter);
	tilesize.move(x,y);
		w=max(w,x+margin+tilesize.width());
		y+=tilesize.height()+gutter;
	x=margin;
	QLabel* flabel=new QLabel("Font:",this);
	flabel->setAlignment(AlignRight|AlignVCenter);
	flabel->setGeometry(x,y,flabel->width(),fontsize.height()); x+=tlabel->width()+gutter;
	fontsize.move(x,y);
		w=max(w,x+margin+fontsize.width());
		y+=tilesize.height()+gutter;
	x=margin;
	QPushButton* dismiss=new QPushButton("Dismiss",this);
	dismiss->move(w/2-dismiss->width()/2,y);
		y+=dismiss->height()+gutter;
	connect(dismiss,SIGNAL(clicked()),this,SLOT(accept()));
	resize(w,y);

	loadTiles();
}

const NetHackQtGlyphs& NetHackQtSettings::glyphs()
{
	return *theglyphs;
}

void NetHackQtSettings::loadTiles()
{
	static const char* file[]={
			"qt_tiles_3.bmp",
			"qt_tiles_2.bmp",
			"qt_tiles_1.bmp",
			"qt_tiles_0.bmp",
		};
	delete theglyphs;
	theglyphs=new NetHackQtGlyphs(file[tilesize.currentItem()]);

	emit tilesChanged();
}

const char* NetHackQtSettings::petMarkDataFile() const
{
	return petDataFile.text();
}

const char* NetHackQtSettings::petMarkMaskFile() const
{
	return petMaskFile.text();
}

const QFont& NetHackQtSettings::normalFont()
{
	static int size[]={ 18, 14, 12, 10 };
	normal.setPointSize(size[fontsize.currentItem()]);
	return normal;
}

const QFont& NetHackQtSettings::normalFixedFont()
{
	static int size[]={ 20, 14, 13, 10 };
	normalfixed.setPointSize(size[fontsize.currentItem()]);
	return normalfixed;
}

const QFont& NetHackQtSettings::largeFont()
{
	static int size[]={ 24, 18, 14, 12 };
	large.setPointSize(size[fontsize.currentItem()]);
	return large;
}

bool NetHackQtSettings::ynInMessages()
{
	// XXX Two out of two users prefer True.
	return true;
}


NetHackQtSettings* qt_settings;



NetHackQtKeyBuffer::NetHackQtKeyBuffer() :
	in(0), out(0)
{
}

bool NetHackQtKeyBuffer::Empty() const { return in==out; }
bool NetHackQtKeyBuffer::Full() const { return (in+1)%maxkey==out; }

void NetHackQtKeyBuffer::Put(int k, int a, int state)
{
	key[in]=k;
	ascii[in]=a;
	in=(in+1)%maxkey;
}

void NetHackQtKeyBuffer::Put(const char* str)
{
	while (*str) Put(0,*str++,0);
}

int NetHackQtKeyBuffer::GetKey()
{
	int r=TopKey();
	out=(out+1)%maxkey;
	return r;
}

int NetHackQtKeyBuffer::GetAscii()
{
	int r=TopAscii();
	out=(out+1)%maxkey;
	return r;
}

int NetHackQtKeyBuffer::GetState()
{
	int r=TopState();
	out=(out+1)%maxkey;
	return r;
}

int NetHackQtKeyBuffer::TopKey() const
{
	return key[out];
}

int NetHackQtKeyBuffer::TopAscii() const
{
	return ascii[out];
}

int NetHackQtKeyBuffer::TopState() const
{
	return state[out];
}


NetHackQtClickBuffer::NetHackQtClickBuffer() :
	in(0), out(0)
{
}

bool NetHackQtClickBuffer::Empty() const { return in==out; }
bool NetHackQtClickBuffer::Full() const { return (in+1)%maxclick==out; }

void NetHackQtClickBuffer::Put(int x, int y, int mod)
{
	click[in].x=x;
	click[in].y=y;
	click[in].mod=mod;
	in=(in+1)%maxclick;
}

int NetHackQtClickBuffer::NextX() const { return click[out].x; }
int NetHackQtClickBuffer::NextY() const { return click[out].y; }
int NetHackQtClickBuffer::NextMod() const { return click[out].mod; }

void NetHackQtClickBuffer::Get()
{
	out=(out+1)%maxclick;
}



NetHackQtPlayerSelector::NetHackQtPlayerSelector(NetHackQtKeyBuffer& ks, Role roles[]) :
	QDialog(0,"plsel",false),
	keysource(ks)
{
	qbutton = new QPushButton("Quit",this);
	connect(qbutton,SIGNAL(clicked()),this,SLOT(Quit()));

	rbutton = new QPushButton("Random",this);
	connect(rbutton,SIGNAL(clicked()),this,SLOT(Random()));
	rbutton->setDefault(true);

	group=new QButtonGroup("Role",this);

	for (role_count=0; roles[role_count].name.m; role_count++)
		;

	rolebutton=new QPushButton*[role_count];

	for (int i=0; roles[i].name.m; i++) {
		rolebutton[i] = new QPushButton(roles[i].name.m,group);
		connect(group,SIGNAL(clicked(int)),this,SLOT(done(int)));
	}

	resize(fontMetrics().width("x")*20,fontMetrics().height()*role_count*2+20);
}

void NetHackQtPlayerSelector::done(int i)
{
	setResult(i);
	qApp->exit_loop();
}

void NetHackQtPlayerSelector::resizeEvent(QResizeEvent*)
{
	int h=height();

	const int margin=8;
	const int gutter=6;
	int butw=(width()-margin*2-gutter)/2;
	int buth=fontMetrics().height()*2;

	int x=margin,y=margin;

	qbutton->setGeometry(x,y,butw,buth);

	x+=butw+gutter;
	rbutton->setGeometry(x,y,butw,buth);
	x=0; y+=buth+gutter*2;

	h-=y;

	const int extra=fontMetrics().height(); // Extra gap for title
	butw=width()-margin*4;
	buth=(h-margin*2-extra)/role_count-gutter;

	x=margin;

	group->setGeometry(x,y,
		width()-margin*2,
		buth*role_count+extra+margin*2+gutter*(role_count-1));

	y=margin+extra;

	for (int i=0; i < role_count; i++) {
		rolebutton[i]->setGeometry(x,y,butw,buth);
		y+=buth+gutter;
	}
}

void NetHackQtPlayerSelector::Quit()
{
	done(R_Quit);
	qApp->exit_loop();
}

void NetHackQtPlayerSelector::Random()
{
	done(R_Rand);
	qApp->exit_loop();
}

bool NetHackQtPlayerSelector::ChooseRole(int& role)
{
	show();
	setResult(R_None);
	while (result()==R_None) {
		qApp->enter_loop();
		if (result()==R_None && !keysource.Empty()) {
			char k=keysource.GetAscii();
			if (k=='\r' || k=='\n' || k==' ') {
				setResult(R_Rand);
			} else if (k=='\033') {
				setResult(R_Quit);
			} else {
				for (int i=0; i<role_count; i++) {
					if (tolower(rolebutton[i]->text()[0])==tolower(k)) {
						setResult(i);
					}
				}
			}
		}
	}
	hide();
	int i=result();

	if (i==R_Quit) return false;

	if (i==R_Rand) {
		i = rn2(role_count);

		char buf[BUFSZ];
		Sprintf(buf,"This game you will be a %s", rolebutton[i]->text());
		yn_function(buf,0,0);
	}

	role=i;
	return true;
}


NetHackQtStringRequestor::NetHackQtStringRequestor(NetHackQtKeyBuffer& ks, const char* p, const char* cancelstr) :
	QDialog(0,"string",false),
	prompt(p,this,"prompt"),
	input(this,"input"),
	keysource(ks)
{
	cancel=new QPushButton(cancelstr,this);
	connect(cancel,SIGNAL(clicked()),this,SLOT(reject()));

	okay=new QPushButton("Okay",this);
	connect(okay,SIGNAL(clicked()),this,SLOT(accept()));
	connect(&input,SIGNAL(returnPressed()),this,SLOT(accept()));

	setAcceptFocus(true);
}

void NetHackQtStringRequestor::resizeEvent(QResizeEvent*)
{
	const int margin=5;
	const int gutter=5;

	int h=(height()-margin*2-gutter);

	if (strlen(prompt.text()) > 16) {
		h/=3;
		prompt.setGeometry(margin,margin,width()-margin*2,h);
		input.setGeometry(width()*1/5,margin+h+gutter,
			(width()-margin-2-gutter)*4/5,h);
	} else {
		h/=2;
		prompt.setGeometry(margin,margin,(width()-margin*2-gutter)*2/5,h);
		input.setGeometry(prompt.geometry().right()+gutter,margin,
			(width()-margin-2-gutter)*3/5,h);
	}

	cancel->setGeometry(margin,input.geometry().bottom()+gutter,
		(width()-margin*2-gutter)/2,h);
	okay->setGeometry(cancel->geometry().right()+gutter,cancel->geometry().y(),
		cancel->width(),h);
}

void NetHackQtStringRequestor::SetDefault(const char* d)
{
	input.setText(d);
}
bool NetHackQtStringRequestor::Get(char* buffer, int maxchar)
{
	input.setMaxLength(maxchar);
	if (strlen(prompt.text()) > 16) {
		resize(fontMetrics().width(prompt.text())+50,fontMetrics().height()*6);
	} else {
		resize(fontMetrics().width(prompt.text())*2+50,fontMetrics().height()*4);
	}

	show();
	//setActiveWindow(); // XXX Crashes!
	input.setFocus();
	setResult(-1);
	while (result()==-1) {
		// Put keys in buffer (eg. from macros, from out-of-focus input)
		if (!keysource.Empty()) {
			while (!keysource.Empty()) {
				int key=keysource.TopKey();
				int ascii=keysource.TopAscii();
				int state=keysource.GetState();
				if (ascii=='\r' || ascii=='\n') {
					// CR or LF in buffer causes confirmation
					strcpy(buffer,input.text());
					return true;
				} else if (ascii=='\033') {
					return false;
				} else {
					input.fakeEvent(key,ascii,state);
				}
			}
		}
		qApp->enter_loop();
	}
	// XXX Get rid of extra keys, since we couldn't get focus!
	while (!keysource.Empty()) keysource.GetKey();

	if (result()) {
		strcpy(buffer,input.text());
		return true;
	} else {
		return false;
	}
}
void NetHackQtStringRequestor::done(int i)
{
	setResult(i);
	qApp->exit_loop();
}


NetHackQtWindow::NetHackQtWindow()
{
}
NetHackQtWindow::~NetHackQtWindow()
{
}

// XXX Use "expected ..." for now, abort or default later.
//
void NetHackQtWindow::Clear() { puts("unexpected Clear"); }
void NetHackQtWindow::Display(bool block) { puts("unexpected Display"); }
bool NetHackQtWindow::Destroy() { return true; }
void NetHackQtWindow::CursorTo(int x,int y) { puts("unexpected CursorTo"); }
void NetHackQtWindow::PutStr(int attr, const char* text) { puts("unexpected PutStr"); }
void NetHackQtWindow::StartMenu() { puts("unexpected StartMenu"); }
void NetHackQtWindow::AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr,
	const char* str, bool presel) { puts("unexpected AddMenu"); }
void NetHackQtWindow::EndMenu(const char* prompt) { puts("unexpected EndMenu"); }
int NetHackQtWindow::SelectMenu(int how, MENU_ITEM_P **menu_list) { puts("unexpected SelectMenu"); return 0; }
void NetHackQtWindow::ClipAround(int x,int y) { puts("unexpected ClipAround"); }
void NetHackQtWindow::PrintGlyph(int x,int y,int glyph) { puts("unexpected PrintGlyph"); }
void NetHackQtWindow::UseRIP(int how) { puts("unexpected UseRIP"); }



// XXX Hmmm... crash after saving bones file if Map window is
// XXX deleted.  Strange bug somewhere.
bool NetHackQtMapWindow::Destroy() { return false; }

NetHackQtMapWindow::NetHackQtMapWindow(NetHackQtClickBuffer& click_sink) :
	clicksink(click_sink),
	change(10)
{
	// ... but put self in porthole of viewport
	recreate(viewport.portHole(),0,QPoint(0,0));

	setBackgroundColor(black);

	pet_annotation.load(qt_settings->petMarkDataFile());
	QBitmap mask;
	mask.load(qt_settings->petMarkMaskFile());

// we try to load a xpm ith mask...
//	pet_annotation.setMask(mask);

	cursor.setX(0);
	cursor.setY(0);
	Clear();

	connect(qt_settings,SIGNAL(tilesChanged()),this,SLOT(updateTiles()));

	updateTiles();
}

void NetHackQtMapWindow::updateTiles()
{
	// Be exactly the size we want to be - full map...
	resize(COLNO*qt_settings->glyphs().width(),ROWNO*qt_settings->glyphs().height());

	viewport.setMaximumSize(
		qt_settings->glyphs().width()*COLNO+viewport.scrollBarWidth(),
		qt_settings->glyphs().height()*ROWNO+viewport.scrollBarWidth()
	);

	viewport.resizeScrollBars();

	change.clear();
	change.add(0,0,COLNO,ROWNO);
	Display(false);

	emit resized();
}

NetHackQtMapWindow::~NetHackQtMapWindow()
{
	// Remove from viewport porthole, since that is a destructible member.
	recreate(0,0,QPoint(0,0));
}

QWidget* NetHackQtMapWindow::Widget()
{
	return &viewport;
}

void NetHackQtMapWindow::Scroll(int dx, int dy)
{
	while (dx<0) { viewport.horizontalScrollBar().subtractPage(); dx++; }
	while (dx>0) { viewport.horizontalScrollBar().addPage(); dx--; }
	while (dy<0) { viewport.verticalScrollBar().subtractPage(); dy++; }
	while (dy>0) { viewport.verticalScrollBar().addPage(); dy--; }
}

void NetHackQtMapWindow::Clear()
{
	unsigned short stone=cmap_to_glyph(S_stone);

	for (int j=0; j<ROWNO; j++) {
		for (int i=0; i<COLNO; i++) {
			Glyph(i,j)=stone;
		}
	}

	change.clear();
	change.add(0,0,COLNO,ROWNO);
}

void NetHackQtMapWindow::mousePressEvent(QMouseEvent* event)
{
	clicksink.Put(
		event->pos().x()/qt_settings->glyphs().width(),
		event->pos().y()/qt_settings->glyphs().height(),
		event->button()==LeftButton ? CLICK_1 : CLICK_2
	);
	qApp->exit_loop();
}

void NetHackQtMapWindow::paintEvent(QPaintEvent* event)
{
	QRect area=event->rect();
	QRect garea;
	garea.setCoords(
		max(0,area.left()/qt_settings->glyphs().width()),
		max(0,area.top()/qt_settings->glyphs().height()),
		min(COLNO-1,area.right()/qt_settings->glyphs().width()),
		min(ROWNO-1,area.bottom()/qt_settings->glyphs().height())
	);

	QPainter painter;

       extern short glyph2tile[];
       int r, c;

	painter.begin(this);
	for (int j=garea.top(); j<=garea.bottom(); j++) {
//                fprintf (stderr, "\n");
		for (int i=garea.left(); i<=garea.right(); i++) {
			unsigned short g=Glyph(i,j);

 //                       r = glyph2tile[g] / ROW_TILES;
//                        c = glyph2tile[g] % ROW_TILES; 

//                        fprintf (stderr, "(x %d y %d) ",
//                          qt_settings->glyphs().xFor(g), 
//                          qt_settings->glyphs().yFor(g));
			painter.drawPixmap(
				i*qt_settings->glyphs().width(),
				j*qt_settings->glyphs().height(),
				qt_settings->glyphs(),
				qt_settings->glyphs().xFor(g),qt_settings->glyphs().yFor(g),
				qt_settings->glyphs().width(),qt_settings->glyphs().height()
			);
			if (glyph_is_pet(g)
#ifdef TEXTCOLOR
				&& ::flags.hilite_pet
#endif
			) {
				painter.drawPixmap(QPoint(i*qt_settings->glyphs().width(), j*qt_settings->glyphs().height()), pet_annotation);
			}
		}
	}

//        fprintf (stderr,"\n\n\n");

	if (garea.contains(cursor)) {
		int hp100;
		if (u.mtimedone) {
			hp100=u.mhmax ? u.mh*100/u.mhmax : 100;
		} else {
			hp100=u.uhpmax ? u.uhp*100/u.uhpmax : 100;
		}

		if (hp100 > 75) painter.setPen(white);
		else if (hp100 > 50) painter.setPen(yellow);
		else if (hp100 > 25) painter.setPen(QColor(0xff,0xbf,0x00)); // orange
		else if (hp100 > 10) painter.setPen(red);
		else painter.setPen(magenta);

		painter.drawRect(
			cursor.x()*qt_settings->glyphs().width(),cursor.y()*qt_settings->glyphs().height(),
			qt_settings->glyphs().width(),qt_settings->glyphs().height());
	}

	painter.end();
}

void NetHackQtMapWindow::Display(bool block)
{
	for (int i=0; i<change.clusters(); i++) {
		const QRect& ch=change[i];
		repaint(
			ch.x()*qt_settings->glyphs().width(),
			ch.y()*qt_settings->glyphs().height(),
			ch.width()*qt_settings->glyphs().width(),
			ch.height()*qt_settings->glyphs().height(),
			false
		);
	}

	change.clear();

	if (block) {
		yn_function("Press a key when done viewing",0,'\0');
	}
}

void NetHackQtMapWindow::CursorTo(int x,int y)
{
	Changed(cursor.x(),cursor.y());
	cursor.setX(x);
	cursor.setY(y);
	Changed(cursor.x(),cursor.y());
}

void NetHackQtMapWindow::PutStr(int attr, const char* text)
{
	puts("unexpected PutStr in MapWindow");
}

void NetHackQtMapWindow::ClipAround(int x,int y)
{
	// Convert to pixel of center of tile
	x=x*qt_settings->glyphs().width()+qt_settings->glyphs().width()/2;
	y=y*qt_settings->glyphs().height()+qt_settings->glyphs().height()/2;

	// Then ensure that pixel is visible
	viewport.centralize(x,y);
}

void NetHackQtMapWindow::PrintGlyph(int x,int y,int glyph)
{
	Glyph(x,y)=glyph;
	Changed(x,y);
}

void NetHackQtMapWindow::Changed(int x, int y)
{
	change.add(x,y);
}



class NetHackQtListBox : public QTableView {
	struct UData {
		UData() : text(0), attr(0) { }
		~UData() { if (text) free(text); }

		char* text;
		int attr;
	};
public:
	int uncleared;

	NetHackQtListBox(int maxlength) :
		maxitems(maxlength),
		first(0),
		count(0),
		uncleared(0),
		item_cycle(maxlength)
	{
		setNumCols(1);
		setCellWidth(200);
		setCellHeight(fontMetrics().height());
		setBackgroundColor(white);
		setTableFlags(Tbl_vScrollBar
			|Tbl_autoHScrollBar
			|Tbl_clipCellPainting
			|Tbl_smoothScrolling);
	}

	~NetHackQtListBox()
	{
	}

	void Scroll(int dx, int dy)
	{
		setXOffset(xOffset()+dx*viewWidth());
		setYOffset(yOffset()+dy*viewHeight());
	}

	void insertItem(int attr, const char* text)
	{
		setTopCell(count);

		setAutoUpdate(false);

		int i;
		if (count<maxitems) {
			i=count++;
			setNumRows(count);
		} else {
			i=count-1;
			first=(first+1)%maxitems;
		}
		item(i).attr=attr;
		item(i).text=strdup(text);
		int w=datumWidth(item(i));

		if (w > cellWidth()) {
			// Get wider.
			setCellWidth(w);
		}
		setTopCell(count);

		setAutoUpdate(true);

		if (viewHeight() >= totalHeight()-cellHeight()) {
			repaint();
		} else {
			scroll(0,cellHeight());
		}
	}

	virtual void setFont(const QFont& font)
	{
		QTableView::setFont(font);
		setCellHeight(fontMetrics().height());
	}

protected:

	UData& item(int i)
	{
		return item_cycle[(first+i)%maxitems];
	}

	const int maxitems;
	int first, count;
	QArray<UData> item_cycle;

	int datumWidth(const UData& uitem)
	{
		if (uitem.text) {
			int width=fontMetrics().width(uitem.text)+3;
			if (uitem.attr) {
				// XXX Too expensive to do properly, because
				// XXX we have to set the font of the widget
				// XXX just to get the font metrics information!
				// XXX Could hold a fake widget for that
				// XXX purpose, but this hack is less ugly.
				width+=width/10;
			}
			return width;
		} else {
			return 0;
		}
	}

	virtual void setupPainter(QPainter *p)
	{
		// XXX This shouldn't be needed - we set the bg in the constructor.
		p->setBackgroundColor(white);
	}

	virtual void paintCell(QPainter *p, int row, int col)
	{
		bool sel=false;
		UData& uitem=item(row);

		if (!sel && row < count-uncleared) {
			p->setPen(darkGray);
		} else {
			p->setPen(black);
		}

		if (uitem.attr) {
			// XXX only bold
			QFont bold(font().family(),font().pointSize(),QFont::Bold);
			p->setFont(bold);
		}

		p->drawText(3,0,width(),cellHeight(),AlignLeft|AlignVCenter,uitem.text);

		if (uitem.attr) {
			p->setFont(font());
		}
	}
};

NetHackQtMessageWindow::NetHackQtMessageWindow() :
	list(new NetHackQtListBox(::iflags.msg_history))
{
	::iflags.window_inited = 1;
	connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(updateFont()));
	updateFont();
}

NetHackQtMessageWindow::~NetHackQtMessageWindow()
{
	::iflags.window_inited = 0;
	delete list;
}

QWidget* NetHackQtMessageWindow::Widget() { return list; }

void NetHackQtMessageWindow::updateFont()
{
	list->setFont(qt_settings->normalFont());
}

void NetHackQtMessageWindow::Scroll(int dx, int dy)
{
	list->Scroll(dx,dy);
}

void NetHackQtMessageWindow::Clear()
{
	if (list->uncleared) {
		list->uncleared=0;
		changed=true;
		Display(false);
	}
}

void NetHackQtMessageWindow::Display(bool block)
{
	if (changed) {
		list->repaint();
		changed=false;
	}
}

void NetHackQtMessageWindow::PutStr(int attr, const char* text)
{
#ifdef USER_SOUNDS
	play_sound_for_message(text);
#endif

	changed=true;
	list->uncleared++;
	list->insertItem(attr,text);

	// Force scrollbar to bottom
	// XXX list->setTopItem(list->count());
}



NetHackQtLabelledIcon::NetHackQtLabelledIcon(QWidget* parent, const char* l) :
	QWidget(parent),
	turn_count(-1),
	label(new QLabel(l,this)),
	icon(0),
	low_is_good(false),
	prev_value(-123)
{
	initHighlight();
}
NetHackQtLabelledIcon::NetHackQtLabelledIcon(QWidget* parent, const char* l, const QPixmap& i) :
	QWidget(parent),
	low_is_good(false),
	turn_count(-1),
	label(new QLabel(l,this)),
	icon(new QLabel(this)),
	prev_value(-123)
{
	setIcon(i);
	initHighlight();
}
void NetHackQtLabelledIcon::initHighlight()
{
	const QPalette& pal=palette();
	const QColorGroup& pa=pal.normal();
	//QColorGroup good(white,darkGreen,pa.light(),pa.dark(),pa.mid(),white,pa.base());
	QColorGroup good(black,green,pa.light(),pa.dark(),pa.mid(),black,pa.base());
	QColorGroup bad(white,red,pa.light(),pa.dark(),pa.mid(),white,pa.base());
	hl_good=pal.copy();
	hl_good.setNormal(good);
	hl_good.setActive(good);
	hl_bad=pal.copy();
	hl_bad.setNormal(bad);
	hl_bad.setActive(bad);
}

void NetHackQtLabelledIcon::setLabel(const char* t, bool lower)
{
	if (!label) {
		label=new QLabel(this);
		label->setFont(font());
		resizeEvent(0);
	}
	if (0!=strcmp(label->text(),t)) {
		label->setText(t);
		highlight(lower==low_is_good ? hl_good : hl_bad);
	}
}
void NetHackQtLabelledIcon::setLabel(const char* t, long v, long cv, const char* tail="")
{
	char buf[BUFSZ];
	if (v==NoNum) {
		Sprintf(buf,"%s%s",t,tail);
	} else {
		Sprintf(buf,"%s%ld%s",t,v,tail);
	}
	setLabel(buf,cv<prev_value);
	prev_value=cv;
}
void NetHackQtLabelledIcon::setLabel(const char* t, long v, const char* tail="")
{
	setLabel(t,v,v,tail);
}
void NetHackQtLabelledIcon::setIcon(const QPixmap& i)
{
	if (icon) icon->setPixmap(i);
	else { icon=new QLabel(this); icon->setPixmap(i); resizeEvent(0); }
	icon->resize(i.width(),i.height());
}
void NetHackQtLabelledIcon::setFont(const QFont& f)
{
	QWidget::setFont(f);
	if (label) label->setFont(f);
}
void NetHackQtLabelledIcon::show()
{
	if (!isVisible()) highlight(hl_bad);
	QWidget::show();
}
void NetHackQtLabelledIcon::highlightWhenChanging()
{
	turn_count=0;
}
void NetHackQtLabelledIcon::lowIsGood()
{
	low_is_good=true;
}
void NetHackQtLabelledIcon::dissipateHighlight()
{
	if (turn_count>0) {
		turn_count--;
		if (!turn_count)
			unhighlight();
	}
}
void NetHackQtLabelledIcon::highlight(const QPalette& hl)
{
	if (label) { // Surely it is?!
		if (turn_count>=0) {
			label->setPalette(hl);
			turn_count=4;
			// `4' includes this turn, so dissipates after
			// 3 more keypresses.
		} else {
			label->setPalette(palette());
		}
	}
}
void NetHackQtLabelledIcon::unhighlight()
{
	if (label) { // Surely it is?!
		label->setPalette(palette());
	}
}
void NetHackQtLabelledIcon::resizeEvent(QResizeEvent*)
{
	setAlignments();

	//int labw=label ? label->fontMetrics().width(label->text()) : 0;
	int labh=label ? label->fontMetrics().height() : 0;
	int icoh=icon ? icon->height() : 0;
	int h=icoh+labh;
	int icoy=(h>height() ? height()-labh-icoh : height()/2-h/2);
	int laby=icoy+icoh;
	if (icon) { icon->setGeometry(0,icoy,width(),icoh); }
	if (label) { label->setGeometry(0,laby,width(),labh); }
}

void NetHackQtLabelledIcon::setAlignments()
{
	if (label) label->setAlignment(AlignHCenter|AlignVCenter);
	if (icon) icon->setAlignment(AlignHCenter|AlignVCenter);
}

NetHackQtStatusWindow::NetHackQtStatusWindow() :
	// Notes:
	//  Alignment needs -2 init value, because -1 is an alignment.
	//  Armor Class is an schar, so 256 is out of range.
	//  Blank value is 0 and should never change.
	str(this,"STR"),
	dex(this,"DEX"),
	con(this,"CON"),
	intel(this,"INT"),
	wis(this,"WIS"),
	cha(this,"CHA"),
	name(this,"(name)"),
	dlevel(this,"(dlevel)"),
	gold(this,"Gold"),
	hp(this,"Hit Points"),
	power(this,"Power"),
	ac(this,"Armour Class"),
	level(this,"Level"),
	exp(this,"Experience"),
	align(this,"Alignment"),
	time(this,"Time"),
	score(this,"Score"),
	hunger(this,""),
	confused(this,"Confused"),
	sick_fp(this,"Sick"),
	sick_il(this,"Ill"),
	blind(this,"Blind"),
	stunned(this,"Stunned"),
	hallu(this,"Hallu"),
	encumber(this,""),
	hline1(this),
	hline2(this),
	hline3(this),
	first_set(true)
{

#ifdef TINY
	p_str.load("str_tny.xpm");
	p_dex.load("dex_tny.xpm");
	p_con.load("con_tny.xpm");
	p_int.load("int_tny.xpm");
	p_wis.load("wis_tny.xpm");
	p_cha.load("cha_tny.xpm");

	p_chaotic.load("chaotic_tny.xpm");
	p_neutral.load("neutral_tny.xpm");
	p_lawful.load("lawful_tny.xpm");

	p_satiated.load("satiated_tny.xpm");
	p_hungry.load("hungry_tny.xpm");

	p_confused.load("confused_tny.xpm");
	p_sick_fp.load("sick_fp_tny.xpm");
	p_sick_il.load("sick_il_tny.xpm");
	p_blind.load("blind_tny.xpm");
	p_stunned.load("stunned_tny.xpm");
	p_hallu.load("hallu_tny.xpm");

	p_encumber[0].load("slt_encumber_tny.xpm");
	p_encumber[1].load("mod_encumber_tny.xpm");
	p_encumber[2].load("hvy_encumber_tny.xpm");
	p_encumber[3].load("ext_encumber_tny.xpm");
	p_encumber[4].load("ovr_encumber_tny.xpm");
#else
	p_str.load("str.xpm");
	p_dex.load("dex.xpm");
	p_con.load("con.xpm");
	p_int.load("int.xpm");
	p_wis.load("wis.xpm");
	p_cha.load("cha.xpm");

	p_chaotic.load("chaotic.xpm");
	p_neutral.load("neutral.xpm");
	p_lawful.load("lawful.xpm");

	p_satiated.load("satiated.xpm");
	p_hungry.load("hungry.xpm");

	p_confused.load("confused.xpm");
	p_sick_fp.load("sick_fp.xpm");
	p_sick_il.load("sick_il.xpm");
	p_blind.load("blind.xpm");
	p_stunned.load("stunned.xpm");
	p_hallu.load("hallu.xpm");

	p_encumber[0].load("slt_encumber.xpm");
	p_encumber[1].load("mod_encumber.xpm");
	p_encumber[2].load("hvy_encumber.xpm");
	p_encumber[3].load("ext_encumber.xpm");
	p_encumber[4].load("ovr_encumber.xpm");
#endif

	str.setIcon(p_str);
	dex.setIcon(p_dex);
	con.setIcon(p_con);
	intel.setIcon(p_int);
	wis.setIcon(p_wis);
	cha.setIcon(p_cha);

	align.setIcon(p_neutral);
	hunger.setIcon(p_hungry);

	confused.setIcon(p_confused);
	sick_fp.setIcon(p_sick_fp);
	sick_il.setIcon(p_sick_il);
	blind.setIcon(p_blind);
	stunned.setIcon(p_stunned);
	hallu.setIcon(p_hallu);

	encumber.setIcon(p_encumber[0]);

	hline1.setFrameStyle(QFrame::HLine|QFrame::Sunken);
	hline2.setFrameStyle(QFrame::HLine|QFrame::Sunken);
	hline3.setFrameStyle(QFrame::HLine|QFrame::Sunken);
	hline1.setLineWidth(1);
	hline2.setLineWidth(1);
	hline3.setLineWidth(1);

	connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(doUpdate()));
	doUpdate();
}

void NetHackQtStatusWindow::doUpdate()
{
	const QFont& large=qt_settings->largeFont();
	name.setFont(large);
	dlevel.setFont(large);

	const QFont& normal=qt_settings->normalFont();
	str.setFont(normal);
	dex.setFont(normal);
	con.setFont(normal);
	intel.setFont(normal);
	wis.setFont(normal);
	cha.setFont(normal);
	gold.setFont(normal);
	hp.setFont(normal);
	power.setFont(normal);
	ac.setFont(normal);
	level.setFont(normal);
	exp.setFont(normal);
	align.setFont(normal);
	time.setFont(normal);
	score.setFont(normal);
	hunger.setFont(normal);
	confused.setFont(normal);
	sick_fp.setFont(normal);
	sick_il.setFont(normal);
	blind.setFont(normal);
	stunned.setFont(normal);
	hallu.setFont(normal);
	encumber.setFont(normal);

	updateStats();
}

QWidget* NetHackQtStatusWindow::Widget() { return this; }

void NetHackQtStatusWindow::Clear()
{
}
void NetHackQtStatusWindow::Display(bool block)
{
}
void NetHackQtStatusWindow::CursorTo(int,int y)
{
	cursy=y;
}
void NetHackQtStatusWindow::PutStr(int attr, const char* text)
{
	// do a complete update when line 0 is done (as per X11 fancy status)
	if (cursy==0) updateStats();
}

void NetHackQtStatusWindow::resizeEvent(QResizeEvent*)
{
	const float SP_name=0.13; //         <Name> the <Class> (large)
	const float SP_dlev=0.13; //   Level 3 in The Dungeons of Doom (large)
	const float SP_atr1=0.25; //  STR   DEX   CON   INT   WIS   CHA
	const float SP_hln1=0.02; // ---
	const float SP_atr2=0.09; //  Au    HP    PW    AC    LVL   EXP
	const float SP_hln2=0.02; // ---
	const float SP_time=0.09; //          time        score
	const float SP_hln3=0.02; // ---
	const float SP_stat=0.25; // Alignment, Poisoned, Hungry, Sick, etc.

	int h=height();
	int x=0,y=0;
	double space=1.0;

	int iw; // Width of an item across line
	int lh; // Height of a line of values

	lh=int(h*SP_name);
	name.setGeometry(0,0,width(),lh); y+=lh;
	lh=int(h*SP_dlev);
	dlevel.setGeometry(0,y,width(),lh); y+=lh;

	lh=int(h*SP_hln1);
	hline1.setGeometry(0,y,width(),lh); y+=lh;

	lh=int(h*SP_atr1);
	iw=width()/6;
	str.setGeometry(x,y,iw,lh); x+=iw;
	dex.setGeometry(x,y,iw,lh); x+=iw;
	con.setGeometry(x,y,iw,lh); x+=iw;
	intel.setGeometry(x,y,iw,lh); x+=iw;
	wis.setGeometry(x,y,iw,lh); x+=iw;
	cha.setGeometry(x,y,iw,lh); x+=iw;
	x=0; y+=lh;

	lh=int(h*SP_hln2);
	hline2.setGeometry(0,y,width(),lh); y+=lh;

	lh=int(h*SP_atr2);
	iw=width()/6;
	gold.setGeometry(x,y,iw,lh); x+=iw;
	hp.setGeometry(x,y,iw,lh); x+=iw;
	power.setGeometry(x,y,iw,lh); x+=iw;
	ac.setGeometry(x,y,iw,lh); x+=iw;
	level.setGeometry(x,y,iw,lh); x+=iw;
	exp.setGeometry(x,y,iw,lh); x+=iw;
	x=0; y+=lh;

	lh=int(h*SP_hln3);
	hline3.setGeometry(0,y,width(),lh); y+=lh;

	lh=int(h*SP_time);
	iw=width()/3; x+=iw/2;
	time.setGeometry(x,y,iw,lh); x+=iw;
	score.setGeometry(x,y,iw,lh); x+=iw;
	x=0; y+=lh;

	lh=int(h*SP_stat);
	iw=width()/9;
	align.setGeometry(x,y,iw,lh); x+=iw;
	hunger.setGeometry(x,y,iw,lh); x+=iw;
	confused.setGeometry(x,y,iw,lh); x+=iw;
	sick_fp.setGeometry(x,y,iw,lh); x+=iw;
	sick_il.setGeometry(x,y,iw,lh); x+=iw;
	blind.setGeometry(x,y,iw,lh); x+=iw;
	stunned.setGeometry(x,y,iw,lh); x+=iw;
	hallu.setGeometry(x,y,iw,lh); x+=iw;
	encumber.setGeometry(x,y,iw,lh); x+=iw;
	x=0; y+=lh;
}


/*
 * Set all widget values to a null string.  This is used after all spacings
 * have been calculated so that when the window is popped up we don't get all
 * kinds of funny values being displayed.
 */
void NetHackQtStatusWindow::nullOut()
{
}

void NetHackQtStatusWindow::fadeHighlighting()
{
	name.dissipateHighlight();
	dlevel.dissipateHighlight();

	str.dissipateHighlight();
	dex.dissipateHighlight();
	con.dissipateHighlight();
	intel.dissipateHighlight();
	wis.dissipateHighlight();
	cha.dissipateHighlight();

	gold.dissipateHighlight();
	hp.dissipateHighlight();
	power.dissipateHighlight();
	ac.dissipateHighlight();
	level.dissipateHighlight();
	exp.dissipateHighlight();
	align.dissipateHighlight();

	time.dissipateHighlight();
	score.dissipateHighlight();

	hunger.dissipateHighlight();
	confused.dissipateHighlight();
	sick_fp.dissipateHighlight();
	sick_il.dissipateHighlight();
	blind.dissipateHighlight();
	stunned.dissipateHighlight();
	hallu.dissipateHighlight();
	encumber.dissipateHighlight();
}

/*
 * Update the displayed status.  The current code in botl.c updates
 * two lines of information.  Both lines are always updated one after
 * the other.  So only do our update when we update the second line.
 *
 * Information on the first line:
 *	name, attributes, alignment, score
 *
 * Information on the second line:
 *	dlvl, gold, hp, power, ac, {level & exp or HD **}
 *	status (hunger, conf, halu, stun, sick, blind), time, encumbrance
 *
 * [**] HD is shown instead of level and exp if mtimedone is non-zero.
 */
void NetHackQtStatusWindow::updateStats()
{
	char buf[BUFSZ];

	if (cursy != 0) return;	/* do a complete update when line 0 is done */

	if (ACURR(A_STR) > 118) {
		Sprintf(buf,"STR:%d",ACURR(A_STR)-100);
	} else if (ACURR(A_STR)==118) {
		Sprintf(buf,"STR:18/**");
	} else if(ACURR(A_STR) > 18) {
		Sprintf(buf,"STR:18/%02d",ACURR(A_STR)-18);
	} else {
		Sprintf(buf,"STR:%d",ACURR(A_STR));
	}
	str.setLabel(buf,NetHackQtLabelledIcon::NoNum,ACURR(A_STR));

	dex.setLabel("DEX:",(long)ACURR(A_DEX));
	con.setLabel("CON:",(long)ACURR(A_CON));
	intel.setLabel("INT:",(long)ACURR(A_INT));
	wis.setLabel("WIS:",(long)ACURR(A_WIS));
	cha.setLabel("CHA:",(long)ACURR(A_CHA));
	extern const char *hu_stat[]; /* from eat.c */
	const char* hung=hu_stat[u.uhs];
	if (hung[0]==' ') {
		hunger.hide();
	} else {
		hunger.setIcon(u.uhs ? p_hungry : p_satiated);
		hunger.setLabel(hung);
		hunger.show();
	}
	if (Confusion) confused.show(); else confused.hide();
	if (Sick) {
		if (u.usick_type & SICK_VOMITABLE) {
			sick_fp.show();
		} else {
			sick_fp.hide();
		}
		if (u.usick_type & SICK_NONVOMITABLE) {
			sick_il.show();
		} else {
			sick_il.hide();
		}
	} else {
		sick_fp.hide();
		sick_il.hide();
	}
	if (Blind) blind.show(); else blind.hide();
	if (Stunned) stunned.show(); else stunned.hide();
	if (Hallucination) hallu.show(); else hallu.hide();
	extern const char *enc_stat[]; /* from botl.c */
	const char* enc=enc_stat[near_capacity()];
	if (enc[0]==' ' || !enc[0]) {
		encumber.hide();
	} else {
		encumber.setIcon(p_encumber[near_capacity()-1]);
		encumber.setLabel(enc);
		encumber.show();
	}
	Strcpy(buf, plname);
	if ('a' <= buf[0] && buf[0] <= 'z') buf[0] += 'A'-'a';
	Strcat(buf, " the ");
	if (u.mtimedone) {
		char mname[BUFSZ];
		int k = 0;

		Strcpy(mname, mons[u.umonnum].mname);
		while(mname[k] != 0) {
			if ((k == 0 || (k > 0 && mname[k-1] == ' '))
			 && 'a' <= mname[k] && mname[k] <= 'z')
			{
				mname[k] += 'A' - 'a';
			}
			k++;
		}
		Strcat(buf, mname);
	} else {
		Strcat(buf, rank_of(u.ulevel, pl_character[0], ::flags.female));
	}
	name.setLabel(buf,NetHackQtLabelledIcon::NoNum,u.ulevel);

	if (In_endgame(&u.uz)) {
		Strcpy(buf, (Is_astralevel(&u.uz) ? "Astral Plane":"End Game"));
		dlevel.setLabel(buf,true);
	} else {
		Strcpy(buf, dungeons[u.uz.dnum].dname);
		Sprintf(eos(buf), ", level ");
		dlevel.setLabel(buf,(long)depth(&u.uz));
	}

	gold.setLabel("Au:",(long)u.ugold);
	if (u.mtimedone) {
		// You're a monster!

		Sprintf(buf, "/%d", u.mhmax);
		hp.setLabel("HP:",u.mh  > 0 ? u.mh  : 0,buf);
		level.setLabel("HD:",(long)mons[u.umonnum].mlevel);
	} else {
		// You're normal.

		Sprintf(buf, "/%d", u.uhpmax);
		hp.setLabel("HP:",u.uhp > 0 ? u.uhp : 0,buf);
		level.setLabel("Level:",(long)u.ulevel);
	}
	Sprintf(buf, "/%d", u.uenmax);
	power.setLabel("Pow:",u.uen,buf);
	ac.setLabel("AC:",(long)u.uac);
#ifdef EXP_ON_BOTL
	if (::flags.showexp) {
		exp.setLabel("Exp:",(long)u.uexp);
	} else
#endif
	{
		exp.setLabel("");
	}
	if (u.ualign.type==A_CHAOTIC) {
		align.setIcon(p_chaotic);
		align.setLabel("Chaotic");
	} else if (u.ualign.type==A_NEUTRAL) {
		align.setIcon(p_neutral);
		align.setLabel("Neutral");
	} else {
		align.setIcon(p_lawful);
		align.setLabel("Lawful");
	}

	if (::flags.time) time.setLabel("Time:",(long)moves);
	else time.setLabel("");
#ifdef SCORE_ON_BOTL
	if (::flags.showscore) {
		score.setLabel("Score:",(long)botl_score());
	} else
#else
	{
		score.setLabel("");
	}
#endif

	if (first_set)
	{
		first_set=false;

		name.highlightWhenChanging();
		dlevel.highlightWhenChanging();

		str.highlightWhenChanging();
		dex.highlightWhenChanging();
		con.highlightWhenChanging();
		intel.highlightWhenChanging();
		wis.highlightWhenChanging();
		cha.highlightWhenChanging();

		gold.highlightWhenChanging();
		hp.highlightWhenChanging();
		power.highlightWhenChanging();
		ac.highlightWhenChanging(); ac.lowIsGood();
		level.highlightWhenChanging();
		exp.highlightWhenChanging();
		align.highlightWhenChanging();

		//time.highlightWhenChanging();
		score.highlightWhenChanging();

		hunger.highlightWhenChanging();
		confused.highlightWhenChanging();
		sick_fp.highlightWhenChanging();
		sick_il.highlightWhenChanging();
		blind.highlightWhenChanging();
		stunned.highlightWhenChanging();
		hallu.highlightWhenChanging();
		encumber.highlightWhenChanging();
	}
}

/*
 * Turn off hilighted status values after a certain amount of turns.
 */
void NetHackQtStatusWindow::checkTurnEvents()
{
}



NetHackQtMenuDialog::NetHackQtMenuDialog() :
	QDialog(0,0,false)
{
}

void NetHackQtMenuDialog::resizeEvent(QResizeEvent*)
{
	emit Resized();
}

void NetHackQtMenuDialog::Accept()
{
	accept();
}

void NetHackQtMenuDialog::Reject()
{
	reject();
}

void NetHackQtMenuDialog::SetResult(int r)
{
	setResult(r);
}

void NetHackQtMenuDialog::done(int i)
{
	setResult(i);
	qApp->exit_loop();
}

// Table view columns:
// 
// [pick-count] [accel] [glyph] [string]
// 
// Maybe accel should be near string.  We'll see.
// pick-count normally blank.
//   double-clicking or click-on-count gives pop-up entry
// string is green when selected
//
NetHackQtMenuWindow::NetHackQtMenuWindow(NetHackQtKeyBuffer& ks) :
	QTableView(),
	keysource(ks),
	dialog(new NetHackQtMenuDialog()),
	pressed(-1)
{
	setNumCols(4);
	setCellHeight(max(qt_settings->glyphs().height()+1,fontMetrics().height()));

#ifndef KDE
	setBackgroundColor(lightGray);
#endif

	setFrameStyle(Panel|Sunken);
	setLineWidth(2);

	QPushButton* button;
	int x=0;

	ok=new QPushButton("Ok",dialog);
	connect(ok,SIGNAL(clicked()),dialog,SLOT(accept()));

	cancel=new QPushButton("Cancel",dialog);
	connect(cancel,SIGNAL(clicked()),dialog,SLOT(reject()));

	all=new QPushButton("All",dialog);
	connect(all,SIGNAL(clicked()),this,SLOT(All()));

	none=new QPushButton("None",dialog);
	connect(none,SIGNAL(clicked()),this,SLOT(None_()));

	invert=new QPushButton("Invert",dialog);
	connect(invert,SIGNAL(clicked()),this,SLOT(Invert()));

	search=new QPushButton("Search",dialog);
	connect(search,SIGNAL(clicked()),this,SLOT(Search()));

	QPoint pos(0,ok->height());
	recreate(dialog,0,pos);
	prompt.recreate(dialog,0,pos);

	connect(dialog,SIGNAL(Resized()),this,SLOT(Layout()));

	setTableFlags(Tbl_autoVScrollBar|Tbl_smoothScrolling
			|Tbl_clipCellPainting);
	setAcceptFocus(true);
}

NetHackQtMenuWindow::~NetHackQtMenuWindow()
{
	// Remove from dialog porthole before we destruct it
	recreate(0,0,QPoint(0,0));
	delete dialog;
}

void NetHackQtMenuWindow::focusInEvent(QFocusEvent *)
{
	// Don't repaint at all, since nothing is using the focus colour
}
void NetHackQtMenuWindow::focusOutEvent(QFocusEvent *)
{
	// Don't repaint at all, since nothing is using the focus colour
}

int NetHackQtMenuWindow::cellWidth(int col)
{
	switch (col) {
	 case 0:
		return 20;
	break; case 1:
		return 16;
	break; case 2:
		return qt_settings->glyphs().width();
	break; case 3:
		return str_width;
	}
	impossible("Extra column (#%d) in MenuWindow",col);
	return 0;
}

QWidget* NetHackQtMenuWindow::Widget() { return dialog; }

void NetHackQtMenuWindow::StartMenu()
{
	setNumRows((itemcount=0));
	str_width=200;
	str_fixed=false;
	next_accel=0;
	has_glyphs=false;
}

NetHackQtMenuWindow::MenuItem::MenuItem() :
	str(0)
{
}

NetHackQtMenuWindow::MenuItem::~MenuItem()
{
	if (str) free((void*)str);
}

#define STR_MARGIN 4

void NetHackQtMenuWindow::AddMenu(int glyph, const ANY_P* identifier,
		char ch, char gch, int attr, const char* str, bool presel)
{
	if (!ch && identifier->a_void!=0) {
		// Supply a keyboard accelerator.  Limited supply.
		static char accel[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
		if (accel[next_accel]) {
			ch=accel[next_accel++];
		}
	}

	if (item.size()<itemcount+1) {
		item.resize(itemcount*4+10);
	}
	item[itemcount].glyph=glyph;
	item[itemcount].identifier=*identifier;
	item[itemcount].ch=ch;
	item[itemcount].attr=attr;
	item[itemcount].str=strdup(str);
	item[itemcount].selected=presel;
	item[itemcount].count=-1;
	++itemcount;

	str_fixed=str_fixed || strstr(str,"     ");
	if (glyph!=NO_GLYPH) has_glyphs=true;
}
void NetHackQtMenuWindow::EndMenu(const char* p)
{
	prompt.setText(p ? p : "");
}
void NetHackQtMenuWindow::Layout()
{
	int butw=totalWidth()/6; // 6 buttons
	int buth=fontMetrics().height()+8; // 8 for spacing & mitres
	int prompth=(*prompt.text() ? buth : 0);

	prompt.setGeometry(6,buth,dialog->width()-6,prompth);
	int h=dialog->height()-buth-prompth;

/* changed Dirk 
	if (h<totalHeight()) {
		// Leave whatever room there is free for the scrollbar
		setGeometry(0,buth+prompth, dialog->width(), h);
	} else {
		setGeometry(0,buth+prompth, totalWidth(), h);
	}
*/
	setGeometry(0,buth+prompth, dialog->width(), h);


	// Below, we take care to use up full width
	int x=0;
	ok->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/5;
	cancel->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/4;
	all->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/3;
	none->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/2;
	invert->setGeometry(x,0,butw,buth); x+=butw; butw=(dialog->width()-x)/1;
	search->setGeometry(x,0,butw,buth);
}
int NetHackQtMenuWindow::SelectMenu(int h, MENU_ITEM_P **menu_list)
{
	setFont(str_fixed ?
		qt_settings->normalFixedFont() : qt_settings->normalFont());

	for (int i=0; i<itemcount; i++) {
		str_width=max(str_width,
			STR_MARGIN+fontMetrics().width(item[i].str));
	}

	setCellHeight(max(qt_settings->glyphs().height()+1,fontMetrics().height()));
	setNumRows(itemcount);

	int buth=fontMetrics().height()+8; // 8 for spacing & mitres

	how=h;

	ok->setEnabled(how!=PICK_ONE);ok->setDefault(how!=PICK_ONE);
	cancel->setEnabled(how!=PICK_NONE);
	all->setEnabled(how==PICK_ANY);
	none->setEnabled(how==PICK_ANY);
	invert->setEnabled(how==PICK_ANY);
	search->setEnabled(how!=PICK_NONE);

	// 600 should be relative to root window height
	// 20 allows for scrollbar or spacing
	// 4 for frame borders
	dialog->resize(totalWidth()+20,
		min(totalHeight(),600)+buth+4+(*prompt.text() ? buth : 0));

	dialog->SetResult(-1);
	dialog->show();
	setFocus();
	while (dialog->result()<0) {
		qApp->enter_loop();
		if (dialog->result()<0 && !keysource.Empty()) {
			char k=keysource.GetAscii();
			if (k=='\033')
				dialog->Reject();
			else if (k=='\r' || k=='\n' || k==' ')
				dialog->Accept();
			else if (k=='/')
				Search();
			else if (k=='*')
				All();
			else if (k=='@')
				Invert();
			else if (k=='%')
				None_();
			else {
				for (int i=0; i<itemcount; i++) {
					if (item[i].ch==k)
						ToggleSelect(i);
				}
			}
		}
	}
	dialog->hide();
	int result=dialog->result();

	if (result>0 && how!=PICK_NONE) {
		if (how==PICK_ONE) {
			int i;
			for (i=0; i<itemcount && !item[i].selected; i++)
				;
			if (i<itemcount) {
				*menu_list=(MENU_ITEM_P*)malloc(sizeof(MENU_ITEM_P));
				(*menu_list)[0].item=item[i].identifier;
				(*menu_list)[0].count=item[i].count;
				return 1;
			} else {
				return 0;
			}
		} else {
			int count=0;
			for (int i=0; i<itemcount; i++)
				if (item[i].selected) count++;
			if (count) {
				*menu_list=(MENU_ITEM_P*)malloc(count*sizeof(MENU_ITEM_P));
				int j=0;
				for (int i=0; i<itemcount; i++) {
					if (item[i].selected) {
						(*menu_list)[j].item=item[i].identifier;
						(*menu_list)[j].count=item[i].count;
						j++;
					}
				}
				return count;
			} else {
				*menu_list=0;
				return 0;
			}
		}
	} else {
		return -1;
	}
}
void NetHackQtMenuWindow::keyPressEvent(QKeyEvent* event)
{
	if (viewHeight() < totalHeight() && !(event->state()&ShiftButton)) {
		if (event->key()==Key_Prior) {
			setYOffset(yOffset()-viewHeight());
		} else if (event->key()==Key_Next) {
			setYOffset(yOffset()+viewHeight());
		} else {
			event->ignore();
		}
	} else {
		event->ignore();
	}
}

void NetHackQtMenuWindow::All()
{
	for (int i=0; i<itemcount; i++) {
		if (!item[i].selected) ToggleSelect(i);
	}
}
void NetHackQtMenuWindow::None_()
{
	for (int i=0; i<itemcount; i++) {
		if (item[i].selected) ToggleSelect(i);
	}
}
void NetHackQtMenuWindow::Invert()
{
	for (int i=0; i<itemcount; i++) {
		ToggleSelect(i);
	}
}
void NetHackQtMenuWindow::Search()
{
	NetHackQtStringRequestor requestor(keysource,"Search for:");
	char line[256];
	if (requestor.Get(line)) {
		for (int i=0; i<itemcount; i++) {
			if (strstr(item[i].str,line))
				ToggleSelect(i);
		}
	}
}
void NetHackQtMenuWindow::ToggleSelect(int i)
{
	if (item[i].Selectable()) {
		item[i].selected = !item[i].selected;
		updateCell(i,3);
		if (how==PICK_ONE) {
			dialog->Accept();
		}
	}
}


void NetHackQtMenuWindow::paintCell(QPainter* painter, int row, int col)
{
	// [pick-count] [accel] [glyph] [string]

	MenuItem& i(item[row]);

	painter->setPen(black);
	painter->setFont(font());

	if (i.selected) {
		painter->setPen(darkGreen);
	}

	switch (col) {
	 case 0:
		if (i.count>=0) {
			char text[16];
			sprintf(text,"%d",i.count);
			painter->drawText(0,0,cellWidth(col),cellHeight(),
				AlignHCenter|AlignVCenter,text);
		}
	break; case 1:
		if (i.ch>=0) {
			char text[2]={i.ch,0};
			painter->drawText(0,0,cellWidth(col),cellHeight(),
				AlignHCenter|AlignVCenter,text);
		}
	break; case 2:
		if (i.glyph!=NO_GLYPH) {
			// Centered in height
			int y=(cellHeight()-qt_settings->glyphs().height())/2;
			if (y<0) y=0;

painter->drawPixmap(0,y,qt_settings->glyphs(),qt_settings->glyphs().xFor(i.glyph),qt_settings->glyphs().yFor(i.glyph),
				qt_settings->glyphs().width(),qt_settings->glyphs().height());
		}
	break; case 3:
		// XXX should qt_settings have ALL the various fonts
		QFont newfont=font();

		if (i.attr) {
			switch(i.attr) {
			 case ATR_ULINE:
				newfont.setUnderline(true);
			break; case ATR_BOLD:
				painter->setPen(red);
			break; case ATR_BLINK:
				newfont.setItalic(true);
			break; case ATR_INVERSE:
				newfont=qt_settings->largeFont();
				newfont.setWeight(QFont::Bold);

				if (i.selected) {
					painter->setPen(blue);
				} else {
					painter->setPen(darkBlue);
				}
			}
		}
		painter->setFont(newfont);

		painter->drawText(STR_MARGIN,0,cellWidth(col),cellHeight(),
			AlignLeft|AlignVCenter,i.str);
	}
}

void NetHackQtMenuWindow::mousePressEvent(QMouseEvent* event)
{
	int col=findCol(event->pos().x());
	int row=findRow(event->pos().y());

	if (col<0 || row<0 || !item[row].Selectable()) return;

	if (how!=PICK_NONE) {
		if (col==0) {
			// Changing count.
			NetHackQtStringRequestor requestor(keysource,"Count:");
			char buf[BUFSZ];

			if (item[row].count>0)
				Sprintf(buf,"%d", item[row].count);
			else
				Sprintf(buf,"");

			requestor.SetDefault(buf);
			if (requestor.Get(buf)) {
				item[row].count=atoi(buf);
				if (item[row].count==0) {
					item[row].count=-1;
					if (item[row].selected) ToggleSelect(row);
				} else {
					if (!item[row].selected) ToggleSelect(row);
				}
				updateCell(row,0);
			}
		} else {
			pressed=row;
			was_sel=item[row].selected;
			ToggleSelect(row);
		}
	}
}
void NetHackQtMenuWindow::mouseReleaseEvent(QMouseEvent* event)
{
	if (pressed>=0) {
		int p=pressed;
		pressed=-1;
		updateCell(p,3);
	}
}
void NetHackQtMenuWindow::mouseMoveEvent(QMouseEvent* event)
{
	if (pressed>=0) {
		int col=findCol(event->pos().x());
		int row=findRow(event->pos().y());

		if (row>=0 && col>=0) {
			if (pressed!=row) {
				// reset to initial state
				if (item[pressed].selected!=was_sel)
					ToggleSelect(pressed);
			} else {
				// reset to new state
				if (item[pressed].selected==was_sel)
					ToggleSelect(pressed);
			}
		}
	}
}


class NetHackQtTextListBox : public QListBox {
public:
	NetHackQtTextListBox(QWidget* parent) : QListBox(parent) { }

	// XXX Why does Qt keep these protected?
	// XXX And why aren't they const?
	//
	int TotalWidth() { return totalWidth(); }
	int TotalHeight() { return totalHeight(); }

	virtual void setFont(const QFont &font)
	{
		QListBox::setFont(font);
		updateCellWidth();
	}
};


QPixmap* NetHackQtRIP::pixmap=0;

NetHackQtRIP::NetHackQtRIP(QWidget* parent) :
	QWidget(parent)
{
	if (!pixmap) {
		pixmap=new QPixmap;
		pixmap->load("rip.ppm"); // XXX configurable?
	}
	resize(pixmap->width(),pixmap->height());
	setFont(QFont("times",12)); // XXX may need to be configurable
}

void NetHackQtRIP::setLines(char** l, int n)
{
	line=l;
	riplines=n;
}

void NetHackQtRIP::paintEvent(QPaintEvent* event)
{
	int pix_x=(width()-pixmap->width())/2;
	int pix_y=(height()-pixmap->height())/2;

	// XXX positions based on RIP image
	int rip_text_x=pix_x+156;
	int rip_text_y=pix_y+67;
	int rip_text_h=94/riplines;

	QPainter painter;
	painter.begin(this);
	painter.drawPixmap(pix_x,pix_y,*pixmap);
	for (int i=0; i<riplines; i++) {
		painter.drawText(rip_text_x-i/2,rip_text_y+i*rip_text_h,
			1,1,DontClip|AlignHCenter,line[i]);
	}
	painter.end();
}

NetHackQtTextWindow::NetHackQtTextWindow(NetHackQtKeyBuffer& ks) :
	QDialog(0,0,false),
	keysource(ks),
	rip(this),
	ok("Dismiss",this),
	search("Search",this),
	lines(new NetHackQtTextListBox(this))
{
	ok.setDefault(true);
	connect(&ok,SIGNAL(clicked()),this,SLOT(accept()));
	connect(&search,SIGNAL(clicked()),this,SLOT(Search()));
	use_rip=false;
	str_fixed=false;

	connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(doUpdate()));
}

void NetHackQtTextWindow::doUpdate()
{
	update();
}


NetHackQtTextWindow::~NetHackQtTextWindow()
{

}

QWidget* NetHackQtTextWindow::Widget()
{
	return this;
}

bool NetHackQtTextWindow::Destroy()
{
	return !isVisible();
}

void NetHackQtTextWindow::UseRIP(int how)
{
// Code from X11 windowport
#define STONE_LINE_LEN 16	/* # chars that fit on one line */
#define NAME_LINE 0		/* line # for player name */
#define GOLD_LINE 1		/* line # for amount of gold */
#define DEATH_LINE 2		/* line # for death description */
#define YEAR_LINE 6		/* line # for year */

static char** rip_line=0;
	if (!rip_line) {
		rip_line=new char*[YEAR_LINE+1];
		for (int i=0; i<YEAR_LINE+1; i++) {
			rip_line[i]=new char[STONE_LINE_LEN+1];
		}
	}

extern const char *killed_by_prefix[];

	/* Follows same algorithm as genl_outrip() */

	char buf[BUFSZ];
	char *dpx;
	int line;

	/* Put name on stone */
	Sprintf(rip_line[NAME_LINE], "%s", plname);

	/* Put $ on stone */
	Sprintf(rip_line[GOLD_LINE], "%ld Au", u.ugold);

	/* Put together death description */
	switch (killer_format) {
		default: impossible("bad killer format?");
		case KILLED_BY_AN:
			Strcpy(buf, killed_by_prefix[how]);
			Strcat(buf, an(killer));
			break;
		case KILLED_BY:
			Strcpy(buf, killed_by_prefix[how]);
			Strcat(buf, killer);
			break;
		case NO_KILLER_PREFIX:
			Strcpy(buf, killer);
			break;
	}

	/* Put death type on stone */
	for (line=DEATH_LINE, dpx = buf; line<YEAR_LINE; line++) {
		register int i,i0;
		char tmpchar;

		if ( (i0=strlen(dpx)) > STONE_LINE_LEN) {
			for(i = STONE_LINE_LEN;
			    ((i0 > STONE_LINE_LEN) && i); i--)
				if(dpx[i] == ' ') i0 = i;
			if(!i) i0 = STONE_LINE_LEN;
		}
		tmpchar = dpx[i0];
		dpx[i0] = 0;
		strcpy(rip_line[line], dpx);
		if (tmpchar != ' ') {
			dpx[i0] = tmpchar;
			dpx= &dpx[i0];
		} else  dpx= &dpx[i0+1];
	}

	/* Put year on stone */
	Sprintf(rip_line[YEAR_LINE], "%4d", getyear());

	rip.setLines(rip_line,YEAR_LINE+1);

	use_rip=true;
}

void NetHackQtTextWindow::Clear()
{
	lines->clear();
	use_rip=false;
	str_fixed=false;
}

void NetHackQtTextWindow::Display(bool block)
{
	if (str_fixed) {
		lines->setFont(qt_settings->normalFixedFont());
	}

	int h=ok.height()*2;
	if (use_rip) {
		h+=rip.height();
		rip.show();
	} else {
		rip.hide();
	}
	// 600 should be relative to root window height
	resize(max(use_rip ? rip.width() : 200,
			lines->TotalWidth()+24),
		min(600,lines->TotalHeight()+h));
	show();
	if (block) {
		setResult(-1);
		while (result()==-1) {
			qApp->enter_loop();
			if (result()==-1 && !keysource.Empty()) {
				char k=keysource.GetAscii();
				if (k=='\033' || k==' ' || k=='\r' || k=='\n') {
					accept();
				} else if (k=='/') {
					Search();
				}
			}
		}
	}
}

void NetHackQtTextWindow::PutStr(int attr, const char* text)
{
	str_fixed=str_fixed || strstr(text,"    ");
	lines->insertItem(text);
}

void NetHackQtTextWindow::done(int i)
{
	setResult(i+1000);
	hide();
	qApp->exit_loop();
}

void NetHackQtTextWindow::resizeEvent(QResizeEvent*)
{
	const int margin=8;
	const int gutter=8;
	const int buth=fontMetrics().height()*2;
	int y=0;
	if (use_rip) {
		rip.setGeometry(0,0,width(),rip.height());
		y+=rip.height();
	}
	y+=margin;
	ok.setGeometry(margin,y,width()/2-margin*2-gutter,buth);
	search.setGeometry(ok.width()+gutter,y,width()-ok.width()-margin-gutter,buth);
	y+=buth+margin;
	lines->setGeometry(0,y,width(),height()-y);
}

void NetHackQtTextWindow::Search()
{
	NetHackQtStringRequestor requestor(keysource,"Search for:");
	static char line[256]="";
	requestor.SetDefault(line);
	if (requestor.Get(line)) {
		int current=lines->currentItem();
		for (int i=1; i<lines->count(); i++) {
			int lnum=(i+current)%lines->count();
			const char* str=lines->text(lnum);
			if (strstr(str,line)) {
				lines->setCurrentItem(lnum);
				lines->centerCurrentItem();
				return;
			}
		}
		lines->setCurrentItem(-1);
	}
}


NetHackQtDelay::NetHackQtDelay(int ms) :
	msec(ms)
{
}

void NetHackQtDelay::wait()
{
	startTimer(msec);
	qApp->enter_loop();
}

void NetHackQtDelay::timerEvent(QTimerEvent* timer)
{
	qApp->exit_loop();
	killTimers();
}

NetHackQtInvUsageWindow::NetHackQtInvUsageWindow(QWidget* parent) :
	QWidget(parent)
{
}

void NetHackQtInvUsageWindow::drawWorn(QPainter& painter, obj* nhobj, int x, int y, bool canbe)
{
	short int glyph;
	if (nhobj)
		glyph=obj_to_glyph(nhobj);
	else if (canbe)
		glyph=cmap_to_glyph(S_room);
	else
		glyph=cmap_to_glyph(S_stone);

	painter.drawPixmap(
		x*qt_settings->glyphs().width(),
		y*qt_settings->glyphs().height(),
		qt_settings->glyphs(),
		qt_settings->glyphs().xFor(glyph),qt_settings->glyphs().yFor(glyph),
		qt_settings->glyphs().width(),qt_settings->glyphs().height()
	);
}

void NetHackQtInvUsageWindow::paintEvent(QPaintEvent*)
{
	//  012
	//
	//0  hB   
	//1 s"w   
	//2 gCg   
	//3 =A=   
	//4  T    
	//5  S    

	QPainter painter;
	painter.begin(this);

	// Blanks
	drawWorn(painter,0,0,0,false);
	drawWorn(painter,0,0,4,false);
	drawWorn(painter,0,0,5,false);
	drawWorn(painter,0,2,4,false);
	drawWorn(painter,0,2,5,false);

	drawWorn(painter,uarm,1,3); // Armour
	drawWorn(painter,uarmc,1,2); // Cloak
        drawWorn(painter,uarmh,1,0); // Helmet
        drawWorn(painter,uarms,0,1); // Shield
        drawWorn(painter,uarmg,0,2); // Gloves - repeated
        drawWorn(painter,uarmg,2,2); // Gloves - repeated
#ifdef TOURIST
        drawWorn(painter,uarmf,1,5); // Shoes (feet)
        drawWorn(painter,uarmu,1,4); // Undershirt
#else
	drawWorn(painter,0    ,1,5,false);
        drawWorn(painter,uarmf,1,4); // Shoes (feet)
#endif
        drawWorn(painter,uleft,0,3); // RingL
        drawWorn(painter,uright,2,3); // RingR

        drawWorn(painter,uwep,2,1); // Weapon
        drawWorn(painter,uamul,1,1); // Amulet
        drawWorn(painter,ublindf,2,0); // Blindfold

	painter.end();
}



NetHackQtMainWindow::NetHackQtMainWindow(NetHackQtKeyBuffer& ks) :
	message(0), map(0), status(0), invusage(this),
#ifdef KDE
        menubar(new KMenuBar(this)),
#else
	menubar(new QMenuBar(this)),
#endif
	keysink(ks)
{


	QPopupMenu* game=new QPopupMenu;
	QPopupMenu* apparel=new QPopupMenu;
	QPopupMenu* action=new QPopupMenu;
	QPopupMenu* magic=new QPopupMenu;
	QPopupMenu* nonaction=new QPopupMenu;

#ifdef KDE
	QPopupMenu *help = kapp->getHelpMenu(true, "KSLASHEM\n\nKDE version by Dirk Schoenberger (schoenberger@signsoft.com");
        help->insertSeparator();
#else
	QPopupMenu* help=new QPopupMenu;
#endif

	struct Macro {
		QPopupMenu* menu;
		const char* name;
		const char* action;
	} item[] = {
/*		{ game, 0, 0 },
		{ game, "Version         v",	"v" },
		{ game, "Compilation   A-v",	"\366" },
		{ game, "History         V",	"V" },
		{ game, "Redraw        C-r",	"\022" },
		{ game, "Options         O",	"O" },
		{ game, "Explore mode    X",	"X" },
		{ game, 0, 0 },
		{ game, "Save            S",	"S" },
		{ game, "Quit          A-Q",	"\361" },
		{ apparel, "Apparel off         A",	"A" },
		{ apparel, 0, 0 },
		{ apparel, "Wield weapon        w",	"w" },
		{ apparel, 0, 0 },
		{ apparel, "Wear armour         W",	"W" },
		{ apparel, "Take off armour     T",	"T" },
		{ apparel, 0, 0 },
		{ apparel, "Put on non-armour   P",	"P" },
		{ apparel, "Remove non-armour   R",	"R" },
		{ action, "Again      C-a",	"\001" },
		{ action, 0, 0 },
		{ action, "Get          ,",	"," },
		{ action, "Loot       A-l",	"\354" },
		{ action, "Sit        A-s",	"\363" },
		{ action, "Force      A-f",	"\346" },
		{ action, "Kick         d",	"\004" },
		{ action, "Jump       A-j",	"\352" },
		{ action, "Wipe face  A-w",	"\367" },
		{ action, "Throw        t",	"t" },
		{ action, "Open door    o",	"o" },
		{ action, "Close door   c",	"c" },
		{ action, "Drop        d?",	"d?" },
		{ action, "Drop many    D",	"D" },
		{ action, "Eat         e?",	"e?" },
		{ action, "Engrave      E",	"E" },
		{ action, "Apply       a?",	"a?" },
		{ action, 0, 0 },
		{ action, "Up           <",		"<" },
		{ action, "Down         >",	">" },
		{ action, "Rest         .",	"." },
		{ action, "Search       s",	"s" },
		{ action, 0, 0 },
		{ action, "Chat       A-c",	"\343" },
		{ action, "Pay        A-p",	"p" },
		{ magic, "Quaff potion       q",	"q?" },
		{ magic, "Read scroll/book  r?",	"r?" },
		{ magic, "Zap wand          z?",	"z?" },
		{ magic, "Zap spell         Z?",	"Z?" },
		{ magic, "Dip              A-d",	"\204" },
		{ magic, "Rub              A-r",	"\222" },
		{ magic, "Invoke           A-i",	"\351" },
		{ magic, 0, 0 },
		{ magic, "Offer            A-o",	"\357" },
		{ magic, "Pray             A-p",	"\360" },
		{ magic, 0, 0 },
		{ magic, "Teleport         C-t",	"\024" },
		{ magic, "Monster action   A-m",	"\355" },
		{ magic, "Turn undead      A-t",	"\364" },
		{ nonaction, "Inventory         i",	"i" },
		{ nonaction, "Discoveries       \\",	"\\" },
		{ nonaction, "Spells            x",	"x" },
		{ nonaction, "Adjust letters  A-a",	"\341" },
		{ nonaction, 0, 0 },
		{ nonaction, "Name objects    A-n",	"\356" },
		{ nonaction, "Call monster      C",	"C" },
		{ nonaction, 0, 0 },
		{ nonaction, "Qualifications  A-q",	"\361" },
		{ nonaction, "Enhance Quals.  A-e",	"\345" },
		{ help, "Help           ?",		"?" },
		{ help, 0, 0 },
		{ help, "What is here   :",	":" },
		{ help, "What is that   ;",	";" },
		{ help, "What is...     /",	"/" }, */
 
                /* DIRKS 98/02/23 */
		{ game, 0, 0 },
		{ game, "Version\tv",	"v" },
		{ game, "Compilation\tA-v",	"\366" }, // A-v
		{ game, "History\tV",	"V" },
		{ game, "Options\tO",	"O" },
		{ game, "Explore mode\tA-x",	"\370" }, // A-x
		{ game, 0, 0 },
		{ game, "Save\tS",	"S" },
		{ game, "Quit\tA-q",	"\361" },  // A-q
		{ apparel, "Wield weapon\tw",	"w?" },
		{ apparel, 0, 0 },
		{ apparel, "Wear armour\tW",	"W?" },
		{ apparel, "Take off armour\tT",	"T" },
		{ apparel, 0, 0 },
		{ apparel, "Put on non-armour\tP",	"P?" },
		{ apparel, "Remove non-armour\tR",	"R" },
		{ action, "Again\tC-a",	"\001" }, // C-a
		{ action, 0, 0 },
		{ action, "Get",	"," },
		{ action, "Loot\tA-l",	"\354" }, // A-l
		{ action, "Sit\tA-s",	"\363" }, // A-s
		{ action, "Force\tA-f",	"\346" }, // A-f
		{ action, "Kick",	"\004" }, // d?
		{ action, "Jump\tA-j",	"\352" }, // A-j
		{ action, "Wipe face\tA-w",	"\367" }, // A-w
		{ action, "Throw\tt",	"t?" },
		{ action, "Open door\to",	"o" },
		{ action, "Close door\tc",	"c" },
		{ action, "Drop\td",	"d?" },
		{ action, "Drop many\tD",	"D" },
		{ action, "Eat\te",	"e?" },
		{ action, "Engrave\tE",	"E" },
		{ action, "Apply\ta",	"a?" },
		{ action, 0, 0 },
		{ action, "Up\t>",		"<" },
		{ action, "Down\t<",	">" },
		{ action, "Rest",	"." },
		{ action, "Search\ts",	"s" },
		{ action, 0, 0 },
		{ action, "Chat\tA-c",	"\343" }, // A-c
		{ action, "Pay\tp",	"p" }, 
		{ magic, "Quaff potion\tq",	"q?" },
		{ magic, "Read scroll/book\tr",	"r?" },
		{ magic, "Zap wand\tz",	"z?" },
		{ magic, "Zap spell\tZ",	"Z?" },
		{ magic, "Dip\tA-d",	"\204" }, // A-d
		{ magic, "Rub\tA-r",	"\222" }, // A-r
		{ magic, "Invoke\tA-i",	"\351?" }, // A-i
		{ magic, 0, 0 },
		{ magic, "Offer\tA-o",	"\357" }, // A-o
		{ magic, "Pray\tA-p",	"\360" }, // A-p
		{ magic, 0, 0 },
		{ magic, "Teleport\tC-t",	"\024" }, // C-t
		{ magic, "Monster action\tA-m",	"\355" }, // A-m
		{ magic, "Turn undead\tA-t",	"\364" }, // A-t
		{ nonaction, "Inventory\ti",	"i" },
		{ nonaction, "Angbandish inventory",	"*" },
		{ nonaction, "Discoveries",	"\\" },
		{ nonaction, "Spells\tX",	"X" },
		{ nonaction, "Adjust letters\tA-a",	"\341" }, // A-a
		{ nonaction, 0, 0 },
		{ nonaction, "Name objects\tA-n",	"\356" }, // A-n
		{ nonaction, "Call monster\tC",	"C" },
		{ nonaction, 0, 0 },
		{ nonaction, "Qualifications\tA-k",	"\353" }, // A-k
		{ nonaction, "Enhance Quals.\tA-e",	"\345" }, // A-e
		{ help, "Help",		"?" },
		{ help, 0, 0 },
		{ help, "What is here",	":" },
		{ help, "What is that",	";" },
		{ help, "What is...",	"/y" }, 
		{ 0, 0, 0 }
	};

	int i;
	int count=0;
	for (i=0; item[i].menu; i++)
		if (item[i].name) count++;

	macro=new (const char*)[count];

	game->insertItem("Qt settings...",1000);

	count=0;
	for (i=0; item[i].menu; i++) {
		if (item[i].name) {
			item[i].menu->insertItem(item[i].name,count);
			macro[count++]=item[i].action;
		} else {
			item[i].menu->insertSeparator();
		}
	}

	menubar->insertItem("Game",game);
	menubar->insertItem("Apparel",apparel);
	menubar->insertItem("Action",action);
	menubar->insertItem("Magic",magic);
	menubar->insertItem("Non-action",nonaction);

#ifdef KDE
	menubar->insertSeparator();
#endif

	menubar->insertItem("Help",help);

	connect(menubar,SIGNAL(activated(int)),this,SLOT(doMenuItem(int)));

#ifdef KDE
        setMenu (menubar);
#endif

	int x=0,y=0;
	int w=QApplication::desktop()->width()-10; // XXX arbitrary extra space for frame
	int h=QApplication::desktop()->height()-20;

	if (w>1400) {
		x+=(1400-w)/2;
		w=1400; // Doesn't need to be any wider
	}
	if (h>1024) {
		y+=(1024-h)/2;
		h=1024; // Doesn't need to be any taller
	}

	setGeometry(x,y,w,h);
}
void NetHackQtMainWindow::doMenuItem(int id)
{
	if (id==1000) {
		qt_settings->show();
	} else {
		keysink.Put(macro[id]);
		qApp->exit_loop();
	}
}

void NetHackQtMainWindow::AddMessageWindow(NetHackQtMessageWindow* window)
{
	message=window;
	ShowIfReady();
}

void NetHackQtMainWindow::AddMapWindow(NetHackQtMapWindow* window)
{
	map=window;
	ShowIfReady();
	connect(map,SIGNAL(resized()),this,SLOT(layout()));
}

void NetHackQtMainWindow::AddStatusWindow(NetHackQtStatusWindow* window)
{
	status=window;
	ShowIfReady();
}

void NetHackQtMainWindow::RemoveWindow(NetHackQtWindow* window)
{
	if (window==status) {
		status=0;
		ShowIfReady();
	} else if (window==map) {
		map=0;
		ShowIfReady();
	} else if (window==message) {
		message=0;
		ShowIfReady();
	}
}

void NetHackQtMainWindow::updateInventory()
{
	invusage.repaint(false);
}

void NetHackQtMainWindow::fadeHighlighting()
{
	if (status) {
		status->fadeHighlighting();
	}
}

void NetHackQtMainWindow::layout()
{
	if (message && map && status) {
		QSize maxs=map->Widget()->maximumSize();
		int maph=min(height()*2/3,maxs.height());

		int y=menubar->height();
		int h=height()-y;
		int toph=h-maph;
		int iuw=3*qt_settings->glyphs().width();
		int topw=(width()-iuw)/2;

		message->Widget()->setGeometry(0,y,topw,toph);
		invusage.setGeometry(topw,y,iuw,toph);
		status->Widget()->setGeometry(topw+iuw,y,topw,toph);
		map->Widget()->setGeometry(0,y+toph,width(),maph);
	}
}

void NetHackQtMainWindow::resizeEvent(QResizeEvent*)
{
	layout();

#ifdef KDE
        updateRects();
#endif         

}

void NetHackQtMainWindow::keyPressEvent(QKeyEvent* event)
{
	// Global key controls

	switch (event->key()) {
	 case Key_Up:
		if (map) map->Scroll(0,-1);
	break; case Key_Down:
		if (map) map->Scroll(0,+1);
	break; case Key_Left:
		if (map) map->Scroll(-1,0);
	break; case Key_Right:
		if (map) map->Scroll(+1,0);
	break; case Key_Prior:
		if (message) message->Scroll(0,-1);
	break; case Key_Next:
		if (message) message->Scroll(0,+1);
	break; default:
		event->ignore();
	}
}

void NetHackQtMainWindow::ShowIfReady()
{
	if (message && map && status) {
		QPoint pos(0,0);
		message->Widget()->recreate(this,0,pos);
		map->Widget()->recreate(this,0,pos);
		status->Widget()->recreate(this,0,pos);

		layout();
		show();
	} else if (isVisible()) {
		hide();
	}
}


NetHackQtYnDialog::NetHackQtYnDialog(NetHackQtKeyBuffer& keysrc,const char* q,const char* ch,char df) :
	QDialog(0,0,false),
	keysource(keysrc),
	question(q), choices(ch), def(df)
{
	setCaption("NetHack: Question");
}

char NetHackQtYnDialog::Exec()
{
	if (choices) {
		QButtonGroup group(question, this);

		int nchoices=strlen(choices);

		bool allow_count=strchr(choices,'#')!=0;

		const int margin=8;
		const int gutter=8;
		const int extra=fontMetrics().height(); // Extra for group
		int row=0;
		int x=margin, y=extra+margin;
		int butsize=fontMetrics().height()*2+5;

		QPushButton* button;
		for (int i=0; i<nchoices && choices[i]!='\033'; i++) {
			char text[2]={choices[i],0};
			button=new QPushButton(text,&group);
			button->setGeometry(x,y,butsize,butsize); // Square
			if (choices[i]==def) button->setDefault(true);
			if (i%10==9) {
				// last in row
				x=margin;
				y+=butsize+gutter;
			} else {
				x+=butsize+gutter;
			}
		}

		group.resize(margin*2+(gutter+button->width())*10,
			extra+margin*2+(gutter+button->height())*(nchoices/10+1));
		connect(&group,SIGNAL(clicked(int)),this,SLOT(done(int)));

		QLabel* lb=0;
		QLineEdit* le=0;

		if (allow_count) {
			lb=new QLabel("Count: ",this);
			le=new QLineEdit(this);
			lb->setGeometry(margin,group.height()+margin,group.width()*1/3,le->height());
			le->setGeometry(lb->geometry().right()+margin,lb->geometry().top(),
				group.width()*2/3,le->height());
		}

		show();
		char choice=0;
		while (!choice) {
			qApp->enter_loop();
			if (!result()) {
				if (!keysource.Empty()) {
					char k=keysource.GetAscii();
					char ch_esc=0;
					for (int i=0; choices[i]; i++) {
						if (choices[i]==k)
							choice=k;
						if (choices[i]=='q') ch_esc='q';
						else if (!ch_esc && choices[i]=='n') ch_esc='n';
					}
					if (!choice) {
						if (k=='\033' && ch_esc)
							choice=ch_esc;
						else if (k==' ' || k=='\r' || k=='\n')
							choice=def;
						// else choice remains 0
					}
				}
			} else {
				choice=choices[result()-1000];
			}
		}
		hide();
		if (allow_count && le->text()[0]) {
			yn_number=atoi(le->text());
			choice='#';
		}
		return choice;
	} else {
		QLabel label(question,this);
		QPushButton cancel("Dismiss",this);
		label.setFrameStyle(QFrame::Box|QFrame::Sunken);
		label.setAlignment(AlignCenter);
		label.resize(fontMetrics().width(question)+60,30+fontMetrics().height());
		cancel.move(width()/2-cancel.width()/2,label.geometry().bottom()+8);
		connect(&cancel,SIGNAL(clicked()),this,SLOT(reject()));
		show();
		while (!result() && keysource.Empty()) {
			qApp->enter_loop();
		}
		hide();
		if (keysource.Empty()) {
			return '\033';
		} else {
			return keysource.GetAscii();
		}
	}
}
void NetHackQtYnDialog::keyPressEvent(QKeyEvent* event)
{
	// Don't want QDialog's Return/Esc behaviour
	event->ignore();
}

void NetHackQtYnDialog::done(int i)
{
	setResult(i+1000);
	qApp->exit_loop();
}

NetHackQtGlyphs::NetHackQtGlyphs(const char* tile_file)
{
	load(tile_file);

	extern int total_tiles_used; // from tile.c

        int lines;

/*	if (QPixmap::height()%total_tiles_used) {
		impossible("Tile file \"%s\" has %d lines, not multiple of glyph count (%d)",
			tile_file, QPixmap::height(), total_tiles_used);
	}

	size.setWidth(QPixmap::width());
	size.setHeight(QPixmap::height()/total_tiles_used); */

        size.setWidth (QPixmap::width()/ROW_TILES);

        lines = total_tiles_used / ROW_TILES;
        if ((total_tiles_used % ROW_TILES)!=0) {
         lines++;  
        }

//       fprintf (stderr, "lines %d\n",lines);


        size.setHeight (QPixmap::height() / lines);

//        fprintf (stderr,"tile load width: %d height: %d\n",size.width(),
//                 size.height());
}
int NetHackQtGlyphs::yFor(int glyph) const
{
	extern short glyph2tile[]; // from tile.c
	return glyph2tile[glyph] / ROW_TILES * size.height();
}
int NetHackQtGlyphs::xFor(int glyph) const
{
	extern short glyph2tile[]; // from tile.c
	return glyph2tile[glyph] % ROW_TILES * size.width();
}


//////////////////////////////////////////////////////////////
//
//  The ugly C binding classes...
//
//////////////////////////////////////////////////////////////


NetHackQtMenuOrTextWindow::NetHackQtMenuOrTextWindow(NetHackQtKeyBuffer& ks) :
	keysource(ks),
	actual(0)
{
}

QWidget* NetHackQtMenuOrTextWindow::Widget()
{
	if (!actual) impossible("Widget called before we know if Menu or Text");
	return actual->Widget();
}

// Text
void NetHackQtMenuOrTextWindow::Clear()
{
	if (!actual) impossible("Clear called before we know if Menu or Text");
	actual->Clear();
}
void NetHackQtMenuOrTextWindow::Display(bool block)
{
	if (!actual) impossible("Display called before we know if Menu or Text");
	actual->Display(block);
}
bool NetHackQtMenuOrTextWindow::Destroy()
{
	if (!actual) impossible("Destroy called before we know if Menu or Text");
	return actual->Destroy();
}

void NetHackQtMenuOrTextWindow::PutStr(int attr, const char* text)
{
	if (!actual) actual=new NetHackQtTextWindow(keysource);
	actual->PutStr(attr,text);
}

// Menu
void NetHackQtMenuOrTextWindow::StartMenu()
{
	if (!actual) actual=new NetHackQtMenuWindow(keysource);
	actual->StartMenu();
}
void NetHackQtMenuOrTextWindow::AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr,
		const char* str, bool presel)
{
	if (!actual) impossible("AddMenu called before we know if Menu or Text");
	actual->AddMenu(glyph,identifier,ch,gch,attr,str,presel);
}
void NetHackQtMenuOrTextWindow::EndMenu(const char* prompt)
{
	if (!actual) impossible("EndMenu called before we know if Menu or Text");
	actual->EndMenu(prompt);
}
int NetHackQtMenuOrTextWindow::SelectMenu(int how, MENU_ITEM_P **menu_list)
{
	if (!actual) impossible("SelectMenu called before we know if Menu or Text");
	return actual->SelectMenu(how,menu_list);
}


// XXX Should be from Options
//
// XXX Hmm.  Tricky part is that perhaps some macros should only be active
// XXX       when a key is about to be gotten.  For example, the user could
// XXX       define "-" to do "E-yyyyyyyy\r", but would still need "-" for
// XXX       other purposes.  Maybe just too bad.
//
struct {
	int key;
	int state;
	const char* macro;
} key_macro[]={
	{ Key_F1, 0, "n100." }, // Rest (x100)
	{ Key_F2, 0, "n20s" },  // Search (x20)
	{ Key_F3, 0, "o8o4o6o2o8o4o6o2o8o4o6o2" }, // Open all doors (x3)
	{ Key_Tab, 0, "\001" },
	{ 0, 0, 0 }
};


NetHackQtBind::NetHackQtBind(int& argc, char** argv) :
#ifdef KDE
	KApplication(argc,argv)
#else
	QApplication(argc,argv)
#endif
{
	main = new NetHackQtMainWindow(keybuffer);
	setMainWidget(main);
	qt_settings=new NetHackQtSettings(main->width(),main->height());
}

void NetHackQtBind::qt_init_nhwindows(int* argc, char** argv)
{
// Michael Hohmuth <hohmuth@inf.tu-dresden.de>...
//
// As the game runs setuid games, it must seteuid(getuid()) before
// calling XOpenDisplay(), and reset the euid afterwards.
// Otherwise, it can't read the $HOME/.Xauthority file and whines about
// not being able to open the X display (if a magic-cookie
// authorization mechanism is being used). 

	uid_t gamesuid=geteuid();
	seteuid(getuid());
	instance=new NetHackQtBind(*argc,argv);
	seteuid(gamesuid);
}

void NetHackQtBind::qt_player_selection()
{
	char pc=highc(pl_character[0]);

	if (pc != 0) {
		if (!index(pl_classes, pc)) {
			pl_character[0] = pc = 0;
		}
	}

//	extern const char *roles[];     /* from u_init.c */
	extern const struct Role roles[];

	if (!pc) {
		NetHackQtPlayerSelector selector(keybuffer,roles);

		int role;
	
		if (selector.ChooseRole(role)) {
			pc=pl_classes[role];
		} else {
			clearlocks();
			qt_exit_nhwindows(0);
			terminate(0);
		}
	}

	pl_character[0]=pc;
}

void NetHackQtBind::qt_askname()
{
	NetHackQtStringRequestor requestor(keybuffer,"What is your name?","Quit");
	if (!requestor.Get(plname,sizeof(plname)-1)) {
		terminate(0);
	}
}

void NetHackQtBind::qt_get_nh_event()
{
}

void NetHackQtBind::qt_exit_nhwindows(const char *)
{
}

void NetHackQtBind::qt_suspend_nhwindows(const char *)
{
}

void NetHackQtBind::qt_resume_nhwindows()
{
}

winid NetHackQtBind::qt_create_nhwindow(int type)
{
	NetHackQtWindow* window=0;

	switch (type) {
	 case NHW_MAP: {
		NetHackQtMapWindow* w=new NetHackQtMapWindow(clickbuffer);
		main->AddMapWindow(w);
		window=w;
	} break; case NHW_MESSAGE: {
		NetHackQtMessageWindow* w=new NetHackQtMessageWindow;
		main->AddMessageWindow(w);
		window=w;
	} break; case NHW_STATUS: {
		NetHackQtStatusWindow* w=new NetHackQtStatusWindow;
		main->AddStatusWindow(w);
		window=w;
	} break; case NHW_MENU:
		window=new NetHackQtMenuOrTextWindow(keybuffer);
	break; case NHW_TEXT:
		window=new NetHackQtTextWindow(keybuffer);
	}

	return (winid)window;
}

void NetHackQtBind::qt_clear_nhwindow(winid wid)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->Clear();
}

void NetHackQtBind::qt_display_nhwindow(winid wid, BOOLEAN_P block)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->Display(block);
}

void NetHackQtBind::qt_destroy_nhwindow(winid wid)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	main->RemoveWindow(window);
	if (window->Destroy())
		delete window;
}

void NetHackQtBind::qt_curs(winid wid, int x, int y)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->CursorTo(x,y);
}

void NetHackQtBind::qt_putstr(winid wid, int attr, const char *text)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->PutStr(attr,text);
}

void NetHackQtBind::qt_display_file(const char *filename, BOOLEAN_P must_exist)
{
	NetHackQtTextWindow* window=new NetHackQtTextWindow(keybuffer);
	QFile file(filename);

	if (file.open(IO_ReadOnly)) {
		char line[128];
		while (file.readLine(line,127)) {
			line[strlen(line)-1]=0;// remove newline
			window->PutStr(ATR_NONE,line);
		}
		window->Display(false);
	} else {
		if (must_exist) {
			char message[strlen(filename)+50];
			sprintf(message,"File not found: %s\n",filename);
			QMessageBox::message("File Error", message, "Ignore");
		}
	}
}

void NetHackQtBind::qt_start_menu(winid wid)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->StartMenu();
}

void NetHackQtBind::qt_add_menu(winid wid, int glyph,
	const ANY_P * identifier, CHAR_P ch, CHAR_P gch, int attr,
	const char *str, BOOLEAN_P presel)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->AddMenu(glyph, identifier, ch, gch, attr, str, presel);
}

void NetHackQtBind::qt_end_menu(winid wid, const char *prompt)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->EndMenu(prompt);
}

int NetHackQtBind::qt_select_menu(winid wid, int how, MENU_ITEM_P **menu_list)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	return window->SelectMenu(how,menu_list);
}

void NetHackQtBind::qt_update_inventory()
{
	if (main)
		main->updateInventory();
}

void NetHackQtBind::qt_mark_synch()
{
}

void NetHackQtBind::qt_wait_synch()
{
}

void NetHackQtBind::qt_cliparound(int x, int y)
{
	// XXXNH - winid should be a parameter!
	qt_cliparound(WIN_MAP,x,y);
}

void NetHackQtBind::qt_cliparound(winid wid, int x, int y)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->ClipAround(x,y);
}
void NetHackQtBind::qt_print_glyph(winid wid,XCHAR_P x,XCHAR_P y,int glyph)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;
	window->PrintGlyph(x,y,glyph);
}

void NetHackQtBind::qt_raw_print(const char *str)
{
	puts(str);
}

void NetHackQtBind::qt_raw_print_bold(const char *str)
{
	puts(str);
}

int NetHackQtBind::qt_nhgetch()
{
	if (main)
		main->fadeHighlighting();

	// Process events until a key arrives.
	//
	while (keybuffer.Empty()) {
		qApp->enter_loop();
	}

	return keybuffer.GetAscii();
}

int NetHackQtBind::qt_nh_poskey(int *x, int *y, int *mod)
{
	if (main)
		main->fadeHighlighting();

	// Process events until a key or map-click arrives.
	//
	while (keybuffer.Empty() && clickbuffer.Empty()) {
		qApp->enter_loop();
	}
	if (!keybuffer.Empty()) {
		return keybuffer.GetAscii();
	} else {
		*x=clickbuffer.NextX();
		*y=clickbuffer.NextY();
		*mod=clickbuffer.NextMod();
		clickbuffer.Get();
		return 0;
	}
}

void NetHackQtBind::qt_nhbell()
{
	QApplication::beep();
}

int NetHackQtBind::qt_doprev_message()
{
	// Don't need it - uses scrollbar
	// XXX but could make this a shortcut
	return 0;
}

char NetHackQtBind::qt_yn_function(const char *question, const char *choices, CHAR_P def)
{
	if (qt_settings->ynInMessages() && WIN_MESSAGE!=WIN_ERR) {
		// Similar to X11 windowport `slow' feature.

		char message[BUFSZ];
		char yn_esc_map='\033';

		if (choices) {
			char *cb, choicebuf[QBUFSZ];
			Strcpy(choicebuf, choices);
			if ((cb = index(choicebuf, '\033')) != 0) {
				// anything beyond <esc> is hidden
				*cb = '\0';
			}
			Sprintf(message, "%s [%s] ", question, choicebuf);
			if (def) Sprintf(eos(message), "(%c) ", def);
			// escape maps to 'q' or 'n' or default, in that order
			yn_esc_map = (index(choices, 'q') ? 'q' :
				     (index(choices, 'n') ? 'n' : def));
		} else {
			Strcpy(message, question);
		}

                /* DIRKS 08/02/23 */
                if (strcmp (choices,"ynq") == 0) {
                  int result;
                  switch (QMessageBox::information (0,"Slashem",question,"&Yes","&No","&Quit",0,2))
                  {
                    case 0: return 'y'; 
                    case 1: return 'n'; 
                    case 2: return 'q'; 
                  }
                }

                if (strcmp (choices,"yn") == 0) {
                  switch (QMessageBox::information(0,"Slashem",question,"&Yes","&No",0,1))
                  {
                    case 0: return 'y';
                    case 1: return 'n'; 
                  }
                }

		NetHackQtBind::qt_putstr(WIN_MESSAGE, ATR_BOLD, message);


		int result=-1;
		while (result<0) {
			char ch=NetHackQtBind::qt_nhgetch();
			if (ch=='\033') {
				result=yn_esc_map;
			} else if (choices && !index(choices,ch)) {
				if (def && (ch==' ' || ch=='\r' || ch=='\n')) {
					result=def;
				} else {
					NetHackQtBind::qt_nhbell();
					// and try again...
				}
			} else {
				result=ch;
			}
		}

		NetHackQtBind::qt_clear_nhwindow(WIN_MESSAGE);

		return result;
	} else {
		NetHackQtYnDialog dialog(keybuffer,question,choices,def);
		return dialog.Exec();
	}
}

void NetHackQtBind::qt_getlin(const char *prompt, char *line)
{
	NetHackQtStringRequestor requestor(keybuffer,prompt);
	if (!requestor.Get(line)) {
		line[0]=0;
	}
}

int NetHackQtBind::qt_get_ext_cmd()
{
	// XXX Not implemented
	return 0;
}

void NetHackQtBind::qt_number_pad(int)
{
	// Ignore.
}

void NetHackQtBind::qt_delay_output()
{
	NetHackQtDelay delay(15);
	delay.wait();
}

void NetHackQtBind::qt_start_screen()
{
	// Ignore.
}

void NetHackQtBind::qt_end_screen()
{
	// Ignore.
}

void NetHackQtBind::qt_outrip(winid wid, int how)
{
	NetHackQtWindow* window=(NetHackQtWindow*)wid;

	window->UseRIP(how);
}

bool NetHackQtBind::notify(QObject *receiver, QEvent *event)
{
	bool result=QApplication::notify(receiver,event);
	if (event->type()==Event_KeyPress) {
		QKeyEvent* key_event=(QKeyEvent*)event;

		if (!key_event->isAccepted()) {
			const int k=key_event->key();
			bool macro=false;
			for (int i=0; !macro && key_macro[i].key; i++) {
				if (key_macro[i].key==k
				 && ((key_macro[i].state&key_event->state())==key_macro[i].state))
				{
					keybuffer.Put(key_macro[i].macro);
					macro=true;
				}
			}
			char ch=key_event->ascii();
			if (!macro && ch) {
				keybuffer.Put(key_event->key(),ch
					+ ((key_event->state()&AltButton) ? 128 : 0),
					key_event->state());
				result=true;
			}

			if (ch || macro) qApp->exit_loop();
		}
	}
	return result;
}

NetHackQtBind* NetHackQtBind::instance=0;
NetHackQtKeyBuffer NetHackQtBind::keybuffer;
NetHackQtClickBuffer NetHackQtBind::clickbuffer;
NetHackQtMainWindow* NetHackQtBind::main=0;


struct window_procs Qt_procs = {
    "Qt",
    NetHackQtBind::qt_init_nhwindows,
    NetHackQtBind::qt_player_selection,
    NetHackQtBind::qt_askname,
    NetHackQtBind::qt_get_nh_event,
    NetHackQtBind::qt_exit_nhwindows,
    NetHackQtBind::qt_suspend_nhwindows,
    NetHackQtBind::qt_resume_nhwindows,
    NetHackQtBind::qt_create_nhwindow,
    NetHackQtBind::qt_clear_nhwindow,
    NetHackQtBind::qt_display_nhwindow,
    NetHackQtBind::qt_destroy_nhwindow,
    NetHackQtBind::qt_curs,
    NetHackQtBind::qt_putstr,
    NetHackQtBind::qt_display_file,
    NetHackQtBind::qt_start_menu,
    NetHackQtBind::qt_add_menu,
    NetHackQtBind::qt_end_menu,
    NetHackQtBind::qt_select_menu,
    genl_message_menu,          /* no need for X-specific handling */
    NetHackQtBind::qt_update_inventory,
    NetHackQtBind::qt_mark_synch,
    NetHackQtBind::qt_wait_synch,
#ifdef CLIPPING
    NetHackQtBind::qt_cliparound,
#endif
#ifdef POSITIONBAR
    donull,
#endif
    NetHackQtBind::qt_print_glyph,
    NetHackQtBind::qt_raw_print,
    NetHackQtBind::qt_raw_print_bold,
    NetHackQtBind::qt_nhgetch,
    NetHackQtBind::qt_nh_poskey,
    NetHackQtBind::qt_nhbell,
    NetHackQtBind::qt_doprev_message,
    NetHackQtBind::qt_yn_function,
    NetHackQtBind::qt_getlin,
    NetHackQtBind::qt_get_ext_cmd,
    NetHackQtBind::qt_number_pad,
    NetHackQtBind::qt_delay_output,
#ifdef CHANGE_COLOR     /* only a Mac option currently */
    donull,
    donull,
#endif
    /* other defs that really should go away (they're tty specific) */
    NetHackQtBind::qt_start_screen,
    NetHackQtBind::qt_end_screen,
#ifdef GRAPHIC_TOMBSTONE
    NetHackQtBind::qt_outrip,
#else
    genl_outrip,
#endif
};

extern "C" void play_usersound(const char* filename, int volume)
{
#undef USER_SOUNDS
#ifdef USER_SOUNDS
	QAudio::play(filename,volume);
#endif
}

#include "qt_win.moc"

#ifndef KDE
#include "qtdummy.moc"
#endif
