1 // Copyright (c) Warwick Allison, 1999.
2 // Qt4 conversion copyright (c) Ray Chason, 2012-2014.
3 // NetHack may be freely redistributed.  See license for details.
4 
5 // qt4plsel.cpp -- player selector dialog
6 
7 extern "C" {
8 #include "hack.h"
9 }
10 #undef Invisible
11 #undef Warning
12 #undef index
13 #undef msleep
14 #undef rindex
15 #undef wizard
16 #undef yn
17 #undef min
18 #undef max
19 
20 #include <QtGui/QtGui>
21 #if QT_VERSION >= 0x050000
22 #include <QtWidgets/QtWidgets>
23 #endif
24 #include "qt4plsel.h"
25 #include "qt4plsel.moc"
26 #include "qt4bind.h"
27 #include "qt4glyph.h"
28 #include "qt4set.h"
29 #include "qt4str.h"
30 
31 // Warwick prefers it this way...
32 #define QT_CHOOSE_RACE_FIRST
33 
34 namespace nethack_qt4 {
35 
36 // temporary
37 void centerOnMain( QWidget* w );
38 // end temporary
39 
40 static const char nh_attribution[] = "<center><big>NetHack %1</big>"
41 	"<br><small>by the NetHack DevTeam</small></center>";
42 
43 class NhPSListViewItem : public QTableWidgetItem {
44 public:
NhPSListViewItem(QTableWidget * parent,const QString & name)45     NhPSListViewItem( QTableWidget* parent, const QString& name ) :
46 	QTableWidgetItem(name)
47     {
48     }
49 
setGlyph(int g)50     void setGlyph(int g)
51     {
52 	NetHackQtGlyphs& glyphs = qt_settings->glyphs();
53 	int gw = glyphs.width();
54 	int gh = glyphs.height();
55 	QPixmap pm(gw,gh);
56 	QPainter p(&pm);
57 	glyphs.drawGlyph(p, g, 0, 0);
58 	p.end();
59 	setIcon(QIcon(pm));
60 	//RLC setHeight(std::max(pm.height()+1,height()));
61     }
62 
63 #if 0 //RLC
64     void paintCell( QPainter *p, const QColorGroup &cg,
65 		    int column, int width, int alignment )
66     {
67 	if ( isSelectable() ) {
68 	    QTableWidgetItem::paintCell( p, cg, column, width, alignment );
69 	} else {
70 	    QColorGroup disabled(
71 		cg.foreground().light(),
72 		cg.button().light(),
73 		cg.light(), cg.dark(), cg.mid(),
74 		Qt::gray, cg.base() );
75 	    QTableWidgetItem::paintCell( p, disabled, column, width, alignment );
76 	}
77     }
78 #endif
79 };
80 
81 class NhPSListViewRole : public NhPSListViewItem {
82 public:
NhPSListViewRole(QTableWidget * parent,int id)83     NhPSListViewRole( QTableWidget* parent, int id ) :
84 	NhPSListViewItem(parent,
85 #ifdef QT_CHOOSE_RACE_FIRST // Lowerize - looks better
86 	    QString(roles[id].name.m).toLower()
87 #else
88 	    roles[id].name.m
89 #endif
90 	)
91     {
92 	setGlyph(monnum_to_glyph(roles[id].malenum));
93     }
94 };
95 
96 class NhPSListViewRace : public NhPSListViewItem {
97 public:
NhPSListViewRace(QTableWidget * parent,int id)98     NhPSListViewRace( QTableWidget* parent, int id ) :
99 	NhPSListViewItem(parent,
100 #ifdef QT_CHOOSE_RACE_FIRST // Capitalize - looks better
101 	    str_titlecase(races[id].noun)
102 #else
103 	    races[id].noun
104 #endif
105 	)
106     {
107 	setGlyph(monnum_to_glyph(races[id].malenum));
108     }
109 };
110 
111 class NhPSListView : public QTableWidget {
112 public:
NhPSListView(QWidget * parent)113     NhPSListView( QWidget* parent ) :
114 	QTableWidget(parent)
115     {
116 	setColumnCount(1);
117 	verticalHeader()->hide();
118 #if QT_VERSION >= 0x050000
119 	horizontalHeader()->setSectionsClickable(false);
120 #else
121 	horizontalHeader()->setClickable(false);
122 #endif
123     }
124 
sizePolicy() const125     QSizePolicy sizePolicy() const
126     {
127 	return QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
128     }
129 
minimumSizeHint() const130     QSize minimumSizeHint() const
131     {
132 	return sizeHint();
133     }
134 
sizeHint() const135     QSize sizeHint() const
136     {
137 	return QSize(columnWidth(0), QTableWidget::sizeHint().height());
138     }
139 };
140 
NetHackQtPlayerSelector(NetHackQtKeyBuffer & ks)141 NetHackQtPlayerSelector::NetHackQtPlayerSelector(NetHackQtKeyBuffer& ks) :
142     QDialog(NetHackQtBind::mainWidget()),
143     fully_specified_role(true)
144 {
145     /*
146                0             1             2
147 	  + Name ------------------------------------+
148 	0 |                                          |
149 	  + ---- ------------------------------------+
150 	  + Role ---+   + Race ---+   + Gender ------+
151 	  |         |   |         |   |  * Male      |
152 	1 |         |   |         |   |  * Female    |
153 	  |         |   |         |   +--------------+
154 	  |         |   |         |
155 	  |         |   |         |   + Alignment ---+
156 	2 |         |   |         |   |  * Male      |
157 	  |         |   |         |   |  * Female    |
158 	  |         |   |         |   +--------------+
159 	3 |         |   |         |   ...stretch...
160 	  |         |   |         |
161 	4 |         |   |         |   [ Random ]
162 	5 |         |   |         |   [  Play  ]
163 	6 |         |   |         |   [  Quit  ]
164 	  +---------+   +---------+
165     */
166 
167     QGridLayout *l = new QGridLayout(this);
168     l->setColumnStretch(2, 1);
169     sizePolicy().setHorizontalPolicy(QSizePolicy::Minimum);
170 
171     QGroupBox* namebox = new QGroupBox("Name", this);
172     QVBoxLayout *namelayout = new QVBoxLayout(namebox);
173     QLineEdit* name = new QLineEdit(namebox);
174     namelayout->addWidget(name);
175     name->setMaxLength(sizeof(plname)-1);
176     if ( strncmp(plname,"player",6) && strncmp(plname,"games",5) )
177 	name->setText(plname);
178     connect(name, SIGNAL(textChanged(const QString&)),
179 	    this, SLOT(selectName(const QString&)) );
180     name->setFocus();
181     QGroupBox* genderbox = new QGroupBox("Gender",this);
182     QButtonGroup *gendergroup = new QButtonGroup(this);
183     QGroupBox* alignbox = new QGroupBox("Alignment",this);
184     QButtonGroup *aligngroup = new QButtonGroup(this);
185     QVBoxLayout* vbgb = new QVBoxLayout(genderbox);
186     QVBoxLayout* vbab = new QVBoxLayout(alignbox);
187     char versionbuf[QBUFSZ];
188     QLabel* logo = new QLabel(QString(nh_attribution).arg(version_string(versionbuf)), this);
189 
190     l->addWidget( namebox, 0,0,1,3 );
191 #ifdef QT_CHOOSE_RACE_FIRST
192     race = new NhPSListView(this);
193     role = new NhPSListView(this);
194     l->addWidget( race, 1,0,6,1 );
195     l->addWidget( role, 1,1,6,1 );
196 #else
197     role = new NhPSListView(this);
198     race = new NhPSListView(this);
199     l->addWidget( role, 1,0,6,1 );
200     l->addWidget( race, 1,1,6,1 );
201 #endif
202 
203     l->addWidget( genderbox, 1, 2 );
204     l->addWidget( alignbox, 2, 2 );
205     l->addWidget( logo, 3, 2, Qt::AlignCenter );
206     l->setRowStretch( 3, 6 );
207 
208     int i;
209     int nrole;
210 
211     chosen_gend = flags.initgend;
212     chosen_align = flags.initalign;
213 
214     // XXX QListView unsorted goes in rev.
215     for (nrole=0; roles[nrole].name.m; nrole++)
216 	;
217     role->setRowCount(nrole);
218     for (i=0; roles[i].name.m; i++) {
219 	QTableWidgetItem *item = new QTableWidgetItem(
220 		QIcon(qt_settings->glyphs().glyph(roles[i].malenum)),
221 		roles[i].name.m);
222 	item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
223 	role->setItem(i, 0, item);
224     }
225     connect( role, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(selectRole(int, int, int, int)) );
226     role->setHorizontalHeaderLabels(QStringList("Role"));
227     role->resizeColumnToContents(0);
228 
229     int nrace;
230     for (nrace=0; races[nrace].noun; nrace++)
231 	;
232     race->setRowCount(nrace);
233     for (i=0; races[i].noun; i++) {
234 	QTableWidgetItem *item = new QTableWidgetItem(
235 		QIcon(qt_settings->glyphs().glyph(races[i].malenum)),
236 		races[i].noun);
237 	item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
238 	race->setItem(i, 0, item);
239     }
240     connect( race, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(selectRace(int, int, int, int)) );
241     race->setHorizontalHeaderLabels(QStringList("Race"));
242     race->resizeColumnToContents(0);
243 
244     gender = new QRadioButton*[ROLE_GENDERS];
245     for (i=0; i<ROLE_GENDERS; i++) {
246 	gender[i] = new QRadioButton( genders[i].adj, genderbox );
247 	genderbox->layout()->addWidget(gender[i]);
248 	gendergroup->addButton(gender[i], i);
249     }
250     connect( gendergroup, SIGNAL(buttonPressed(int)), this, SLOT(selectGender(int)) );
251 
252     alignment = new QRadioButton*[ROLE_ALIGNS];
253     for (i=0; i<ROLE_ALIGNS; i++) {
254 	alignment[i] = new QRadioButton( aligns[i].adj, alignbox );
255 	alignbox->layout()->addWidget(alignment[i]);
256 	aligngroup->addButton(alignment[i], i);
257     }
258     connect( aligngroup, SIGNAL(buttonPressed(int)), this, SLOT(selectAlignment(int)) );
259 
260     QPushButton* rnd = new QPushButton("Random",this);
261     l->addWidget( rnd, 4, 2 );
262     rnd->setDefault(false);
263     connect( rnd, SIGNAL(clicked()), this, SLOT(Randomize()) );
264 
265     QPushButton* ok = new QPushButton("Play",this);
266     l->addWidget( ok, 5, 2 );
267     ok->setDefault(true);
268     connect( ok, SIGNAL(clicked()), this, SLOT(accept()) );
269 
270     QPushButton* cancel = new QPushButton("Quit",this);
271     l->addWidget( cancel, 6, 2 );
272     connect( cancel, SIGNAL(clicked()), this, SLOT(reject()) );
273 
274     Randomize();
275 }
276 
Randomize()277 void NetHackQtPlayerSelector::Randomize()
278 {
279     int nrole = role->rowCount();
280     int nrace = race->rowCount();
281 
282     boolean picksomething = (flags.initrole == ROLE_NONE
283                              || flags.initrace == ROLE_NONE
284                              || flags.initgend == ROLE_NONE
285                              || flags.initalign == ROLE_NONE);
286 
287     if (flags.randomall && picksomething) {
288         if (flags.initrole == ROLE_NONE)
289             flags.initrole = ROLE_RANDOM;
290         if (flags.initrace == ROLE_NONE)
291             flags.initrace = ROLE_RANDOM;
292         if (flags.initgend == ROLE_NONE)
293             flags.initgend = ROLE_RANDOM;
294         if (flags.initalign == ROLE_NONE)
295             flags.initalign = ROLE_RANDOM;
296     }
297 
298     rigid_role_checks();
299 
300     // Randomize race and role, unless specified in config
301     int ro = flags.initrole;
302     if (ro == ROLE_NONE || ro == ROLE_RANDOM) {
303 	ro = rn2(nrole);
304 	if (flags.initrole != ROLE_RANDOM) {
305 	    fully_specified_role = false;
306 	}
307     }
308     int ra = flags.initrace;
309     if (ra == ROLE_NONE || ra == ROLE_RANDOM) {
310 	ra = rn2(nrace);
311 	if (flags.initrace != ROLE_RANDOM) {
312 	    fully_specified_role = false;
313 	}
314     }
315 
316     // make sure we have a valid combination, honoring
317     // the users request if possible.
318     bool choose_race_first;
319 #ifdef QT_CHOOSE_RACE_FIRST
320     choose_race_first = true;
321     if (flags.initrole >= 0 && flags.initrace < 0) {
322 	choose_race_first = false;
323     }
324 #else
325     choose_race_first = false;
326     if (flags.initrace >= 0 && flags.initrole < 0) {
327 	choose_race_first = true;
328     }
329 #endif
330     while (!validrace(ro,ra)) {
331 	if (choose_race_first) {
332 	    ro = rn2(nrole);
333 	    if (flags.initrole != ROLE_RANDOM) {
334 	        fully_specified_role = false;
335 	    }
336 	} else {
337 	    ra = rn2(nrace);
338 	    if (flags.initrace != ROLE_RANDOM) {
339 	        fully_specified_role = false;
340 	    }
341 	}
342     }
343 
344     int g = flags.initgend;
345     if (g == -1) {
346 	g = rn2(ROLE_GENDERS);
347 	fully_specified_role = false;
348     }
349     while (!validgend(ro,ra,g)) {
350 	g = rn2(ROLE_GENDERS);
351     }
352     gender[g]->setChecked(true);
353     selectGender(g);
354 
355     int a = flags.initalign;
356     if (a == -1) {
357 	a = rn2(ROLE_ALIGNS);
358 	fully_specified_role = false;
359     }
360     while (!validalign(ro,ra,a)) {
361 	a = rn2(ROLE_ALIGNS);
362     }
363     alignment[a]->setChecked(true);
364     selectAlignment(a);
365 
366     role->setCurrentCell(ro, 0);
367 
368     race->setCurrentCell(ra, 0);
369 }
370 
selectName(const QString & n)371 void NetHackQtPlayerSelector::selectName(const QString& n)
372 {
373     str_copy(plname,n.toLatin1().constData(),SIZE(plname));
374 }
375 
selectRole(int crow,int ccol,int prow,int pcol)376 void NetHackQtPlayerSelector::selectRole(int crow, int ccol, int prow, int pcol)
377 {
378     int ra = race->currentRow();
379     int ro = role->currentRow();
380     if (ra == -1 || ro == -1) return;
381     QTableWidgetItem* item;
382     item = role->item(prow, 0);
383     if (item != NULL)
384        item->setSelected(false);
385 
386 #ifdef QT_CHOOSE_RACE_FIRST
387     selectRace(crow, ccol, prow, pcol);
388 #else
389     QTableWidgetItem* i=role->currentItem();
390     QTableWidgetItem* valid=0;
391     int j;
392     for (j=0; roles[j].name.m; j++) {
393 	bool v = validrace(j,ra);
394 	item = role->item(j, 0);
395 	item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
396 	if ( !valid && v ) valid = item;
397     }
398     if ( !validrace(role->currentRow(),ra) )
399 	i = valid;
400     role->setCurrentItem(i, 0);
401     for (j=0; roles[j].name.m; j++) {
402 	item = role->item(j, 0);
403 	item->setSelected(item == i);
404 	bool v = validrace(j,ra);
405 	item->setFlags(
406 		v ? Qt::ItemIsEnabled|Qt::ItemIsSelectable
407 		  : Qt::NoItemFlags);
408     }
409 #endif
410 
411     //flags.initrole = role->currentRow();
412     setupOthers();
413 }
414 
selectRace(int crow,int ccol,int prow,int pcol)415 void NetHackQtPlayerSelector::selectRace(int crow, int ccol, int prow, int pcol)
416 {
417     int ra = race->currentRow();
418     int ro = role->currentRow();
419     if (ra == -1 || ro == -1) return;
420     QTableWidgetItem* item;
421     item = race->item(prow, 0);
422     if (item != NULL)
423        item->setSelected(false);
424 
425 #ifndef QT_CHOOSE_RACE_FIRST
426     selectRole(crow, ccol, prow, pcol);
427 #else
428     QTableWidgetItem* i=race->currentItem();
429     QTableWidgetItem* valid=0;
430     int j;
431     for (j=0; races[j].noun; j++) {
432 	bool v = validrace(ro,j);
433 	item = race->item(j, 0);
434 	item->setFlags(Qt::ItemIsEnabled|Qt::ItemIsSelectable);
435 	if ( !valid && v ) valid = item;
436     }
437     if ( !validrace(ro,race->currentRow()) )
438 	i = valid;
439     for (j=0; races[j].noun; j++) {
440 	item = race->item(j, 0);
441 	item->setSelected(item == i);
442 	bool v = validrace(ro,j);
443 	item->setFlags(
444 		v ? Qt::ItemIsEnabled|Qt::ItemIsSelectable
445 		  : Qt::NoItemFlags);
446     }
447 #endif
448 
449     //flags.initrace = race->currentRow();
450     setupOthers();
451 }
452 
setupOthers()453 void NetHackQtPlayerSelector::setupOthers()
454 {
455     int ro = role->currentRow();
456     int ra = race->currentRow();
457     int valid=-1;
458     int c=0;
459     int j;
460     for (j=0; j<ROLE_GENDERS; j++) {
461 	bool v = validgend(ro,ra,j);
462 	if ( gender[j]->isChecked() )
463 	    c = j;
464 	gender[j]->setEnabled(v);
465 	if ( valid<0 && v ) valid = j;
466     }
467     if ( !validgend(ro,ra,c) )
468 	c = valid;
469     int k;
470     for (k=0; k<ROLE_GENDERS; k++) {
471 	gender[k]->setChecked(c==k);
472     }
473     selectGender(c);
474 
475     valid=-1;
476     for (j=0; j<ROLE_ALIGNS; j++) {
477 	bool v = validalign(ro,ra,j);
478 	if ( alignment[j]->isChecked() )
479 	    c = j;
480 	alignment[j]->setEnabled(v);
481 	if ( valid<0 && v ) valid = j;
482     }
483     if ( !validalign(ro,ra,c) )
484 	c = valid;
485     for (k=0; k<ROLE_ALIGNS; k++) {
486 	alignment[k]->setChecked(c==k);
487     }
488     selectAlignment(c);
489 }
490 
selectGender(int i)491 void NetHackQtPlayerSelector::selectGender(int i)
492 {
493     chosen_gend = i;
494 }
495 
selectAlignment(int i)496 void NetHackQtPlayerSelector::selectAlignment(int i)
497 {
498     chosen_align = i;
499 }
500 
Quit()501 void NetHackQtPlayerSelector::Quit()
502 {
503     done(R_Quit);
504 }
505 
Random()506 void NetHackQtPlayerSelector::Random()
507 {
508     done(R_Rand);
509 }
510 
Choose()511 bool NetHackQtPlayerSelector::Choose()
512 {
513     if (fully_specified_role) return true;
514 
515 #if defined(QWS) // probably safe with Qt 3, too (where show!=exec in QDialog).
516     if ( qt_compact_mode ) {
517 	showMaximized();
518     } else
519 #endif
520     {
521 	adjustSize();
522 	centerOnMain(this);
523     }
524 
525     if ( exec() ) {
526         flags.initrace = race->currentRow();
527         flags.initrole = role->currentRow();
528         flags.initgend = chosen_gend;
529         flags.initalign = chosen_align;
530 	return true;
531     } else {
532 	return false;
533     }
534 }
535 
536 } // namespace nethack_qt4
537