Merge lp:~fhscholl/4eturntracker/4eturntracker into lp:4eturntracker

Proposed by Frank Scholl
Status: Approved
Approved by: Paul McCullagh
Approved revision: 196
Proposed branch: lp:~fhscholl/4eturntracker/4eturntracker
Merge into: lp:4eturntracker
Diff against target: 945 lines (+517/-95)
9 files modified
resources/4eTurnTracker.qrc (+1/-0)
src/creature.cpp (+37/-44)
src/encounters.cpp (+3/-19)
src/global.cpp (+234/-1)
src/global.h (+45/-2)
src/mainwindow.cpp (+38/-0)
src/mainwindow.h (+5/-0)
src/playerswindow.cpp (+135/-22)
src/playerswindow.h (+19/-7)
To merge this branch: bzr merge lp:~fhscholl/4eturntracker/4eturntracker
Reviewer Review Type Date Requested Status
Frank Scholl (community) Approve
Paul McCullagh Pending
Review via email: mp+188763@code.launchpad.net

This proposal supersedes a proposal from 2013-09-25.

Description of the change

Resolves KI57 by ensuring the Portraits folders exists at load time.
Resolves KI55.
Moved splitID from encounter to global for reuse in creature.
Rewrote creature::compareName to utilize splitID.
Changed creature::compareName to a case sensitive sort for more expected behavior when using more than 26 monsters identified by letters ("a" will come after "Z" instead of before "A").
Rebuilt Player View context menu to use check boxes.
Added option to highlight the row with the current turn. (resolves to-do 108)
Added option to rotate the players view list keeping the current turn on top. (partially resolves to-do 046)

Corrected issues with previous merge proposal.

Added option to change the highlight color in the player's window via the context menu. Options are currently yellow, light yellow, dark gray, and light green. If the highlighting settign is turned on and the color isn't saved for some reason, it defaults to dark gray.

Added RollExpression. This class takes a string and interprets it into a dice roll. e.g. "3d6+7" or "2d8-1d6+6"
Added toolbar to do on the fly rolling using RollExpression.

To post a comment you must log in.
Revision history for this message
Frank Scholl (fhscholl) wrote : Posted in a previous version of this proposal

Typo: That should be KI57.

Revision history for this message
Paul McCullagh (paul-mccullagh) wrote : Posted in a previous version of this proposal

Hi Frank,

Well done on these additions. I am impressed with your changes to the players window, they are not trivial!

A couple of things before I can commit this.

- In order to be consistent with the current code, please use TABs (4-space tabs) in place of spaces for indentation.

- The code added: //Verify Portraits folder exists

Would be best added after

if (new_root.isEmpty()) {
 QFileDialog dialog;
 QStringList fileNames;

 ...
 new_pretty_root = new_root;
}

And the code must return a boolean. Although the result is not evaluated, you should do the following:

if (r != DIALOG_ACCEPTED)
 return false;

And at the end, remove:

return DIALOG_ACCEPTED;

Because you want to continue after creating the directory.

- With regard to highlighting the current.

What looks good on my computer is the following:

 else if (role == Qt::ForegroundRole) {
  if (gray)
   return Qt::gray;
  if (isCurrentIndex(drawRow) && index.column() != HP_COLUMN && highlightCurrent) {
   if (index.column() == NAME_COLUMN && !creature->isMonster())
    return QColor(238, 243, 254);
   return Qt::white;
  }
  if (index.column() == NAME_COLUMN && !creature->isMonster())
   return Qt::blue;
 }

...

    else if (role == Qt::BackgroundColorRole) {
  if (isCurrentIndex(drawRow) && index.column() != HP_COLUMN && highlightCurrent)
   return Qt::darkGray;
 }

Then the highlight is white (light blue for characters) on dark gray. I think this fits better with the current color schema.

- I noticed that after turning off "Keep Current Turn on Top", this was not saved after a restart.

review: Needs Fixing
Revision history for this message
Frank Scholl (fhscholl) wrote : Posted in a previous version of this proposal

See latest revision.

review: Needs Resubmitting
196. By Frank Scholl

Previous commit was shortsighted.
Added new class RollLineEdit inherited from QLineEdit. It contains a RollExpression, has a faded d6 background image for easy recognition, and performs self validation or the entered expression.
Replaced the toolbar implementation with one based on RollLineEdit.

Revision history for this message
Frank Scholl (fhscholl) :
review: Approve
Revision history for this message
Paul McCullagh (paul-mccullagh) wrote :

Hi Frank,

Well done with this changes! I am impressed with the dice roller!

Revision history for this message
Frank Scholl (fhscholl) wrote :

Thanks. Honestly, so am I. I would never have even remotely considered
trying any of the fancy decoration on the LineEdit if it wasn't for Qt. I'm
quickly falling in love with the whole Qt framework.

Of course you now realize that since the dice roller is a simple drop in UI
element, I'm going to start replacing other boxes with it. Namely I'll put
it in the power damage instead of the 3 fields with the drop down and the
power healing in place of the SpinBox. The only hesitation I have is the
option for DM override. I think there should be the option for the DM to
change the value before it is applied. I'm just not sure where to put it so
it doesn't look redundant or become more annoying than useful.
 On Oct 3, 2013 5:06 AM, "Paul McCullagh" <email address hidden> wrote:

> Hi Frank,
>
> Well done with this changes! I am impressed with the dice roller!
> --
>
> https://code.launchpad.net/~fhscholl/4eturntracker/4eturntracker/+merge/188763
> You are the owner of lp:~fhscholl/4eturntracker/4eturntracker.
>

Revision history for this message
Paul McCullagh (paul-mccullagh) wrote :

On Oct 3, 2013, at 1:18 PM, Frank Scholl wrote:

> Thanks. Honestly, so am I. I would never have even remotely considered
> trying any of the fancy decoration on the LineEdit if it wasn't for Qt. I'm
> quickly falling in love with the whole Qt framework.

Yes! Qt really is a fantastic framework. I makes programming C++ a breeze. :)

And one of the greatest features is that it is completely cross-platform. Really cool.

> Of course you now realize that since the dice roller is a simple drop in UI
> element, I'm going to start replacing other boxes with it. Namely I'll put
> it in the power damage instead of the 3 fields with the drop down and the
> power healing in place of the SpinBox.

Yes. The dice roller is much more flexible that the 3 fields.

And, we could pre-fill the text area by parsing it out the power descriptions (for monsters). Should not be too difficult.

> The only hesitation I have is the
> option for DM override. I think there should be the option for the DM to
> change the value before it is applied. I'm just not sure where to put it so
> it doesn't look redundant or become more annoying than useful.

We will probably need 2 fields. One for the dice text, and one for the result, so that the DM can change the result, if required.

Unmerged revisions

196. By Frank Scholl

Previous commit was shortsighted.
Added new class RollLineEdit inherited from QLineEdit. It contains a RollExpression, has a faded d6 background image for easy recognition, and performs self validation or the entered expression.
Replaced the toolbar implementation with one based on RollLineEdit.

195. By Frank Scholl

Added RollExpression. This class takes a string and interprets it into a dice roll. e.g. "3d6+7" or "2d8-1d6+6"
Added toolbar to do on the fly rolling using RollExpression.

194. By Frank Scholl

Added options to change the highlight color in the player's window via the context menu. Options are currently yellow, light yellow, dark gray, and light green. If the highlighting settign is turned on and the color isn't saved for some reason, it defaults to dark gray.

193. By Frank Scholl

> - In order to be consistent with the current code, please use TABs (4-space tabs) in place of spaces for indentation.

FIXED (silly default editor template)

> - The code added: //Verify Portraits folder exists
>
> Would be best added after

Moved. I actually had it here originally but moved it to the loading of content.

> And the code must return a boolean. Although the result is not evaluated, you should do the following...

FIXED. I actually stole this from elsewhere in your code. I just forgot to change the retrn behavior.

> - With regard to highlighting the current.

Changed colors. I thought the yellow made it easier to read from across the room, but it's your call.

> - I noticed that after turning off "Keep Current Turn on Top", this was not saved after a restart.

I cannot reproduce this. I've tried a dozen times in different ways and it always saves for me. I'm using Windows and verified the value is stored in the registry. The code is identical to the highlight and showDamageAndHeals.

192. By Frank Scholl

Rebuilt Player View context menu to use check boxes.
Added option to highlight the row with the current turn. (resolves to-do 108)
Added option to rotate the players view list keeping the current turn on top. (partially resolves to-do 046)

191. By Frank Scholl

Resolves KI55.
Moved splitID from encounter to global for reuse in creature.
Rewrote creature::compareName to utilize splitID.
Changed creature::compareName to a case sensitive sort for more expected behavior when using more than 26 monsters identified by letters ("a" will come after "Z" instead of before "A").

190. By Frank Scholl

Resolves K157

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'resources/4eTurnTracker.qrc'
2--- resources/4eTurnTracker.qrc 2012-06-11 18:30:40 +0000
3+++ resources/4eTurnTracker.qrc 2013-10-03 04:04:27 +0000
4@@ -86,5 +86,6 @@
5 <file>images/multi-open24.png</file>
6 <file>images/close14.png</file>
7 <file>images/open14.png</file>
8+ <file>images/dice16.png</file>
9 </qresource>
10 </RCC>
11
12=== added file 'resources/images/dice16.png'
13Binary files resources/images/dice16.png 1970-01-01 00:00:00 +0000 and resources/images/dice16.png 2013-10-03 04:04:27 +0000 differ
14=== modified file 'src/creature.cpp'
15--- src/creature.cpp 2013-09-16 12:20:24 +0000
16+++ src/creature.cpp 2013-10-03 04:04:27 +0000
17@@ -581,32 +581,32 @@
18 }
19 else {
20 if (level) {
21- if (role.isEmpty()) {
22- if (heroClass.isEmpty())
23- levelClassRole = tr("Level %1").arg(level);
24- else
25- levelClassRole = tr("Level %1 %2").arg(level).arg(heroClass);
26- }
27- else {
28- if (heroClass.isEmpty())
29- levelClassRole = tr("Level %1 %2").arg(level).arg(role);
30- else
31- levelClassRole = tr("Level %1 %2 (%3)").arg(level).arg(heroClass).arg(role);
32- }
33- }
34+ if (role.isEmpty()) {
35+ if (heroClass.isEmpty())
36+ levelClassRole = tr("Level %1").arg(level);
37+ else
38+ levelClassRole = tr("Level %1 %2").arg(level).arg(heroClass);
39+ }
40+ else {
41+ if (heroClass.isEmpty())
42+ levelClassRole = tr("Level %1 %2").arg(level).arg(role);
43+ else
44+ levelClassRole = tr("Level %1 %2 (%3)").arg(level).arg(heroClass).arg(role);
45+ }
46+ }
47 else {
48- if (role.isEmpty()) {
49- if (heroClass.isEmpty())
50- levelClassRole = ASC("");
51- else
52- levelClassRole = heroClass;
53- }
54- else {
55- if (heroClass.isEmpty())
56- levelClassRole = role;
57- else
58- levelClassRole = tr("%1 (%2)").arg(heroClass).arg(role);
59- }
60+ if (role.isEmpty()) {
61+ if (heroClass.isEmpty())
62+ levelClassRole = ASC("");
63+ else
64+ levelClassRole = heroClass;
65+ }
66+ else {
67+ if (heroClass.isEmpty())
68+ levelClassRole = role;
69+ else
70+ levelClassRole = tr("%1 (%2)").arg(heroClass).arg(role);
71+ }
72 }
73 }
74 return levelClassRole;
75@@ -621,12 +621,12 @@
76 str = tr("Level %1 ").arg(level);
77 if (!race.isEmpty())
78 str += ASCS("%1 ").arg(race);
79- if (!heroClass.isEmpty() && !role.isEmpty())
80- str += ASCS("%1 (%2) ").arg(heroClass).arg(role);
81- else if (!heroClass.isEmpty())
82- str += ASCS("%1 ").arg(heroClass);
83- else if (!role.isEmpty())
84- str += ASCS("%1 ").arg(role);
85+ if (!heroClass.isEmpty() && !role.isEmpty())
86+ str += ASCS("%1 (%2) ").arg(heroClass).arg(role);
87+ else if (!heroClass.isEmpty())
88+ str += ASCS("%1 ").arg(heroClass);
89+ else if (!role.isEmpty())
90+ str += ASCS("%1 ").arg(role);
91 str.chop(1);
92 }
93 return str;
94@@ -1934,27 +1934,20 @@
95
96 int Creature::compareNames(QString n1, QString n2)
97 {
98- int p1, p2;
99+ QString p1, p2, vs1, vs2;
100 int v1, v2;
101
102- p1 = n1.size()-1;
103- while (p1 > 0 && n1[p1].isDigit())
104- p1--;
105- p1++;
106-
107- p2 = n2.size()-1;
108- while (p2 > 0 && n2[p2].isDigit())
109- p2--;
110- p2++;
111+ p1 = G_::splitID(n1, vs1);
112+ p2 = G_::splitID(n2, vs2);
113
114 // Compare the preceeding text values
115- int r = n1.left(p1).compare(n2.left(p2), Qt::CaseInsensitive);
116+ int r = p1.compare(p2);
117 if (r != 0)
118 return r;
119
120 // Compare the trialing integer values
121- v1 = n1.right(n1.size() - p1).toInt();
122- v2 = n2.right(n2.size() - p2).toInt();
123+ v1 = vs1.toInt();
124+ v2 = vs2.toInt();
125 return v1 - v2;
126 }
127
128
129=== modified file 'src/encounters.cpp'
130--- src/encounters.cpp 2012-09-27 22:13:16 +0000
131+++ src/encounters.cpp 2013-10-03 04:04:27 +0000
132@@ -443,22 +443,6 @@
133 return i+1;
134 }
135
136-static QString splitID(QString id, QString& ret_number)
137-{
138- QString number;
139-
140- int k = id.size()-1;
141- while (k >= 0 && id[k].isDigit()) {
142- number.prepend(id[k]);
143- k--;
144- }
145-
146- ret_number = number;
147- if (!number.isEmpty())
148- return G_::left(id, number);
149- return id;
150-}
151-
152 QString EncounterData::getNewUniqueName(QString group, QString default_id, int& selected_numbering, bool unique)
153 {
154 QString name;
155@@ -478,7 +462,7 @@
156 if (!last_id.isEmpty()) {
157 QString number;
158
159- prefix = splitID(last_id, number);
160+ prefix = G_::splitID(last_id, number);
161 if (!number.isEmpty()) {
162 i = number.toInt() + 1;
163 if (prefix.isEmpty())
164@@ -529,7 +513,7 @@
165
166 QString number;
167 for (int j=0; j<sortedList.size(); j++) {
168- if (splitID(sortedList[j].monsterID, number) == prefix) {
169+ if (G_::splitID(sortedList[j].monsterID, number) == prefix) {
170 goto cont_1;
171 }
172 }
173@@ -995,7 +979,7 @@
174 delete data;
175 progress->setValue(progress->value() + 1);
176 if (progress->wasCanceled())
177- return DIALOG_REJECTED;
178+ return DIALOG_REJECTED;
179 }
180
181 if (encounterDataList.size() == 0) {
182
183=== modified file 'src/global.cpp'
184--- src/global.cpp 2013-06-15 10:50:51 +0000
185+++ src/global.cpp 2013-10-03 04:04:27 +0000
186@@ -1095,6 +1095,17 @@
187 new_pretty_root = new_root;
188 }
189
190+ //Verify Portraits folder exists
191+ QDir root(new_root);
192+ if (!root.cd(ASC("Portraits"))) {
193+ int r = G_::badRootAlert(tr("Directory not Found"),
194+ tr("Directory <b>Portraits</b> not found in %1.").arg(new_pretty_root), tr("Create"));
195+ if (r != DIALOG_ACCEPTED)
196+ return false;
197+ QDir dir = QDir(new_root);
198+ dir.mkdir(ASC("Portraits"));
199+ }
200+
201 ccount = new_characters.getFileCount(new_root);
202 mcount = new_monsters.getFileCount(new_root);
203 ecount = new_encounters.getFileCount(new_root);
204@@ -1229,6 +1240,21 @@
205 return nativeRootPath;
206 }
207
208+QString Global::splitID(QString id, QString& ret_number)
209+{
210+ QString number;
211+
212+ int k = id.size()-1;
213+ while (k >= 0 && id[k].isDigit()) {
214+ number.prepend(id[k]);
215+ k--;
216+ }
217+
218+ ret_number = number;
219+ if (!number.isEmpty())
220+ return G_::left(id, number);
221+ return id;
222+}
223
224 // WindowsAndDialogs -------------------------
225
226@@ -1987,7 +2013,7 @@
227 // FlatDialog -------------------------
228
229 FlatDialog::FlatDialog(QWidget *parent) :
230- QWidget(parent)
231+ QWidget(parent)
232 {
233 CONNECT_TO(&G_::switchBoard, doCancelFlatDialog(), this, onCancel());
234 }
235@@ -2221,3 +2247,210 @@
236 return size;
237 }
238
239+// RollExpression ---------------------------------------------------
240+
241+int RollExpression::roll()
242+{
243+ if (!valid)
244+ return 0;
245+
246+ rolls.clear();
247+
248+ for (int i = 0; i < dice.length(); i++ ) {
249+ int r = (rand() % dice[i].first) + 1;
250+ if (!dice[i].second)
251+ r = -r;
252+ rolls.append(r);
253+ }
254+
255+ return this->getResult();
256+}
257+
258+void RollExpression::setExp(QString rollExp)
259+{
260+ valid = false;
261+ modifier = 0;
262+ dice.clear();
263+ rolls.clear();
264+
265+ original = rollExp.toLower();
266+ this->process();
267+}
268+
269+QString RollExpression::getNormalized()
270+{
271+ if (!valid)
272+ return ASC("");
273+ QString norm = ASC("");
274+ QString pos = ASC("");
275+ QString neg = ASC("");
276+
277+ if (dice.length() > 0) {
278+
279+ int cd = dice[0].first;
280+ bool cs = dice[0].second;
281+ int count = 1;
282+
283+ for (int i = 1; i < dice.length(); i++ ) {
284+ if (cd != dice[i].first || cs != dice[i].second) {
285+ if (cs) {
286+ if (pos.length() > 0)
287+ pos += ASC(" + ");
288+ pos += QString::number(count);
289+ pos += ASC("d");
290+ pos += QString::number(cd);
291+ }
292+ else {
293+ neg += ASC(" - ");
294+ neg += QString::number(count);
295+ neg += ASC("d");
296+ neg += QString::number(cd);
297+ }
298+ cd = dice[i].first;
299+ cs = dice[i].second;
300+ count = 0;
301+ }
302+ count++;
303+ }
304+ //process the last die group
305+ if (cs) {
306+ if (pos.length() > 0)
307+ pos += ASC(" + ");
308+ pos += QString::number(count);
309+ pos += ASC("d");
310+ pos += QString::number(cd);
311+ }
312+ else {
313+ neg += ASC(" - ");
314+ neg += QString::number(count);
315+ neg += ASC("d");
316+ neg += QString::number(cd);
317+ }
318+ }
319+ if (pos.length() == 0 && modifier > 0) {
320+ norm += QString::number(modifier);
321+ norm += neg;
322+ }
323+ else {
324+ norm += pos;
325+ norm += neg;
326+ if (modifier < 0)
327+ norm += ASC(" - ");
328+ else if (modifier > 0)
329+ norm += ASC(" + ");
330+ if (modifier != 0)
331+ norm += QString::number(abs(modifier));
332+ }
333+ return norm;
334+}
335+
336+int RollExpression::getResult()
337+{
338+ if (!valid)
339+ return 0;
340+
341+ if (rolls.length() != dice.length()) //we haven't rolled yet
342+ return 0;
343+
344+ int total = modifier;
345+
346+ for (int i = 0; i < rolls.length(); i++)
347+ total += rolls[i];
348+
349+ return total;
350+}
351+
352+void RollExpression::process()
353+{
354+ QString copy = original;
355+ bool ok;
356+
357+ valid = false;
358+ modifier = 0;
359+
360+ copy.replace(ASC(" "),ASC(""));
361+
362+ //the only valid characters are numbers, +, -, and d
363+ copy.replace(ASC("+"),ASC(""));
364+ copy.replace(ASC("-"),ASC(""));
365+ copy.replace(ASC("d"),ASC(""));
366+ copy.toInt(&ok);
367+ if (!ok) return; //if there is anything but digits left, this isn't vaild
368+
369+ QStringList parts;
370+
371+ //try to make sense of this string
372+ //the interpretation will be a little loose to make it less complex
373+ copy = original;
374+ copy.replace(ASC(" "),ASC(""));
375+ copy.replace(ASC("-"),ASC("+-"));
376+ parts = copy.split(ASC("+"),QString::SkipEmptyParts);
377+ for (int i = 0; i < parts.length(); i++) {
378+ if (parts[i].contains(ASC("d"))) {
379+ QStringList die = parts[i].split(ASC("d"),QString::SkipEmptyParts);
380+ QString first = ASC("");
381+ QString second = ASC("");
382+ int c, d;
383+
384+ if (die.length() == 1 && parts[i].left(1) == ASC("d")) { //"d6"
385+ first = ASC("1");
386+ second = die[0];
387+ }
388+ else if (die.length() == 2 && die[0] == ASC("-")) { //"-d6"
389+ first = ASC("-1");
390+ second = die[1];
391+ }
392+ else if (die.length() == 2) {
393+ first = die[0];
394+ second = die[1];
395+ }
396+
397+ if (first == ASC("") || second == ASC("")) return;
398+
399+ c = first.toInt(&ok);
400+ if (!ok) return;
401+ d = second.toInt(&ok);
402+ if (!ok) return;
403+ if (d < 1) return;
404+
405+ for (int j = 0; j < abs(c); j++)
406+ dice.append(QPair<int,bool> (d,(c > 0)));
407+ }
408+ else {
409+ parts[i].toInt(&ok);
410+ if (!ok) return;
411+ modifier += parts[i].toInt();
412+ }
413+ }
414+ //at this point the string was successfully processed
415+ valid = true;
416+}
417+
418+//RollLineEdit ------------------------------------------------------
419+
420+RollLineEdit::RollLineEdit() {
421+ setStatusTip(tr("Enter a valid die expression."));
422+ setStyleSheet(ASC("QLineEdit {"
423+ "background-image: url(:/images/dice16.png);"
424+ "background-repeat: no-repeat;"
425+ "background-position:center center;"
426+ "}"));
427+ connect(this, SIGNAL(textChanged(QString)), this, SLOT(onValueChanged()));
428+}
429+
430+void RollLineEdit::onValueChanged() {
431+ roller.setExp(text());
432+ if (!isValid() && text() != ASC(""))
433+ setStyleSheet(ASC("QLineEdit {"
434+ "background-image: url(:/images/dice16.png);"
435+ "background-color: #ffe7e7;"
436+ "background-repeat: no-repeat;"
437+ "background-position:center center;"
438+ "}"));
439+ else
440+ setStyleSheet(ASC("QLineEdit {"
441+ "background-image: url(:/images/dice16.png);"
442+ "background-repeat: no-repeat;"
443+ "background-position:center center;"
444+ "}"));
445+}
446
447=== modified file 'src/global.h'
448--- src/global.h 2012-12-02 15:58:52 +0000
449+++ src/global.h 2013-10-03 04:04:27 +0000
450@@ -177,6 +177,7 @@
451 static QByteArray toStandardLineEnding(QByteArray& from);
452 static QByteArray toWindowsLineEndings(QByteArray& from);
453 static QString addDirChar(QString path, QChar ch = QDir::separator());
454+ static QString splitID(QString id, QString& ret_number);
455
456 static int badRootAlert(QString title, QString message, QString ok_text);
457 static void alert(QString title, QString message);
458@@ -410,7 +411,7 @@
459
460 class StretchyWidget : public QWidget
461 {
462- Q_OBJECT
463+ Q_OBJECT
464 public:
465 StretchyWidget(QWidget *parent = 0) :
466 QWidget(parent) {
467@@ -537,7 +538,7 @@
468 {
469 Q_OBJECT
470 public:
471- explicit FlatDialog(QWidget *parent = 0);
472+ explicit FlatDialog(QWidget *parent = 0);
473 virtual ~FlatDialog();
474
475 virtual void setupDialogTitle(QLabel* label);
476@@ -639,4 +640,46 @@
477 virtual QSize sizeHint() const;
478 };
479
480+class RollExpression
481+{
482+public:
483+ RollExpression() : valid(false) {}
484+
485+ int roll();
486+ void setExp(QString rollExp);
487+ bool isValid() { return valid; }
488+ QString getOriginal() { return original; }
489+ QString getNormalized();
490+ int getResult();
491+
492+private:
493+ void process();
494+
495+private:
496+ QList< QPair<int, bool> > dice; //die size/positive
497+ QList<int> rolls;
498+ int modifier;
499+ QString original;
500+ bool valid;
501+};
502+
503+class RollLineEdit : public QLineEdit
504+{
505+public:
506+ RollLineEdit();
507+
508+ int roll() { return roller.roll(); }
509+ bool isValid() { return roller.isValid(); }
510+ QString getOriginal() { return roller.getOriginal(); }
511+ QString getNormalized() { return roller.getNormalized(); }
512+ int getResult() { return roller.getResult(); }
513+
514+private slots:
515+ void onValueChanged();
516+
517+private:
518+ RollExpression roller;
519+ Q_OBJECT
520+};
521+
522 #endif // GLOBAL_H
523
524=== modified file 'src/mainwindow.cpp'
525--- src/mainwindow.cpp 2012-12-02 15:58:52 +0000
526+++ src/mainwindow.cpp 2013-10-03 04:04:27 +0000
527@@ -249,6 +249,13 @@
528 toolBar->addAction(showPlayersWindowAction);
529 toolBar->addAction(activateManagerWindowAction);
530 toolBar->addAction(activateCharacterWindowAction);
531+
532+ toolBar = addToolBar(tr("Die Roller"));
533+ toolBar->setObjectName(ASC("dieRollerToolBar"));
534+ toolBar->setMovable(true);
535+ toolBar->addWidget(rollExpEdit);
536+ toolBar->addAction(rollButton);
537+ toolBar->addWidget(rollResultLabel);
538 }
539
540 void MainWindow::createStatusBar()
541@@ -475,6 +482,21 @@
542
543 setTempHPButton = createAction(ASC(":/images/temporary-hp32.png"), tr("Set Temporary HP"), ASC("Ctrl+P"), tr("Set Temporary HP to amount in the HP box, if larger than current value"));
544 connect(setTempHPButton, SIGNAL(triggered()), this, SLOT(onSetTempHP()));
545+
546+ rollExpEdit = new RollLineEdit();
547+ rollExpEdit->setMaximumWidth(100);
548+ connect(rollExpEdit, SIGNAL(textChanged(QString)), this, SLOT(onRollExpValueChanged()));
549+ connect(rollExpEdit, SIGNAL(returnPressed()), this, SLOT(onRoll()));
550+
551+ rollButton = createAction(ASC(":/images/dice24.png"), tr("Roll"), ASC("Ctrl+Shift+R"), tr("Rolls the entered die expression and displays the results"));
552+ rollButton->setEnabled(false);
553+ connect(rollButton, SIGNAL(triggered()), this, SLOT(onRoll()));
554+
555+ rollResultLabel = new QLabel();
556+ rollResultLabel->setMinimumWidth(75);
557+ rollResultLabel->setMaximumWidth(75);
558+ rollResultLabel->setAlignment(Qt::AlignCenter);
559+ rollResultLabel->setStatusTip(tr("Displays the result of the roll"));
560 }
561
562 QSize TestFrame::sizeHint() const {
563@@ -2023,6 +2045,22 @@
564 toggleSelectionButtons();
565 }
566
567+void MainWindow::onRollExpValueChanged()
568+{
569+ rollButton->setEnabled(rollExpEdit->isValid());
570+}
571+
572+void MainWindow::onRoll()
573+{
574+ QString text;
575+
576+ text = ASC("<b><font size=6>");
577+ if (rollExpEdit->isValid())
578+ text += QString::number(rollExpEdit->roll());
579+ text += ASC("</font></b>");
580+ rollResultLabel->setText(text);
581+}
582+
583 QAction* MainWindow::createAction(QString icon, QString text, QString scut, QString tip)
584 {
585 QAction* action;
586
587=== modified file 'src/mainwindow.h'
588--- src/mainwindow.h 2012-10-28 21:56:42 +0000
589+++ src/mainwindow.h 2013-10-03 04:04:27 +0000
590@@ -173,6 +173,8 @@
591 void onSurge();
592 void onSetTempHP();
593 void onCreatureSelectionChanged();
594+ void onRollExpValueChanged();
595+ void onRoll();
596
597 private:
598 QAction* createAction(QString icon, QString text, QString scut, QString tip);
599@@ -242,6 +244,9 @@
600 QAction *healButton;
601 QAction *surgeButton;
602 QAction *setTempHPButton;
603+ RollLineEdit *rollExpEdit;
604+ QAction *rollButton;
605+ QLabel *rollResultLabel;
606
607 QAction *activateMainWindowAction;
608 QAction *activateManagerWindowAction;
609
610=== modified file 'src/playerswindow.cpp'
611--- src/playerswindow.cpp 2012-10-28 21:56:42 +0000
612+++ src/playerswindow.cpp 2013-10-03 04:04:27 +0000
613@@ -23,6 +23,7 @@
614 */
615
616 #include <QtGui>
617+#include <math.h>
618
619 #include "playerswindow.h"
620 #include "global.h"
621@@ -72,7 +73,7 @@
622
623 QVariant PlayersListModel::data(const QModelIndex &index, int role) const
624 {
625- int idx;
626+ int idx, drawRow;
627 Creature *creature;
628 bool gray = false;
629
630@@ -82,7 +83,14 @@
631 if (index.row() >= playersView.visibleCreatures.size() || index.column() >= columnCount())
632 return QVariant();
633
634- idx = playersView.visibleCreatures[index.row()];
635+ drawRow = index.row();
636+ if (rotatePlayerInitList) {
637+ drawRow = index.row() + (getCurrentIndex() < 0 ? 0 : getCurrentIndex());
638+ if (drawRow >= playersView.visibleCreatures.size())
639+ drawRow -= playersView.visibleCreatures.size();
640+ }
641+
642+ idx = playersView.visibleCreatures[drawRow];
643 creature = G_::encounter->getCreature(idx);
644 if (!creature)
645 return QVariant();
646@@ -121,13 +129,19 @@
647 }
648 else if (role == Qt::ForegroundRole) {
649 if (gray)
650- return ASC("gray");
651+ return Qt::gray;
652+ if (isCurrentIndex(drawRow) && index.column() != HP_COLUMN &&
653+ highlightCurrent && getHighlightCurrentColorBrightness() < 140) {
654+ if (index.column() == NAME_COLUMN && !creature->isMonster())
655+ return QColor(238,243,254);
656+ return Qt::white;
657+ }
658 if (index.column() == NAME_COLUMN && !creature->isMonster())
659- return ASC("blue");
660+ return Qt::blue;
661 }
662 else if (role == Qt::DecorationRole) {
663 if (index.column() == NAME_COLUMN) {
664- if (isCurrentIndex(index.row()))
665+ if (isCurrentIndex(drawRow))
666 return currentIcon;
667 else {
668 if (creature->isDelayed())
669@@ -137,10 +151,12 @@
670 else if (creature->isHidden())
671 return hiddenIcon;
672 return emptyIcon;
673-
674 }
675 }
676 }
677+ else if (role == Qt::BackgroundColorRole)
678+ if (isCurrentIndex(drawRow) && index.column() != HP_COLUMN && highlightCurrent)
679+ return highlightCurrentColor;
680
681 return QVariant();
682 }
683@@ -237,6 +253,38 @@
684 return showDamageAndHeals;
685 }
686
687+void PlayersListModel::setHighlightCurrent(bool hc)
688+{
689+ if (highlightCurrent != hc) {
690+ highlightCurrent = hc;
691+ emit layoutChanged();
692+ }
693+}
694+
695+void PlayersListModel::setHighlightCurrentColor(QColor color)
696+{
697+ if (highlightCurrentColor != color) {
698+ highlightCurrentColor = color;
699+ emit layoutChanged();
700+ }
701+}
702+
703+int PlayersListModel::getHighlightCurrentColorBrightness() const
704+{
705+ int r, g, b, bright;
706+ highlightCurrentColor.getRgb(&r, &g, &b);
707+ bright = round(sqrt(.299 * r * r + .587 * g * g + .144 * b * b));
708+ return bright;
709+}
710+
711+void PlayersListModel::setRotatePlayerInitList(bool rl)
712+{
713+ if (rotatePlayerInitList != rl) {
714+ rotatePlayerInitList = rl;
715+ emit layoutChanged();
716+ }
717+}
718+
719 bool PlayersListModel::isCurrentIndex(int row) const
720 {
721 EncounterState state = G_::encounter->getState();
722@@ -268,7 +316,7 @@
723 return is_current;
724 }
725
726-int PlayersListModel::getCurrentIndex()
727+int PlayersListModel::getCurrentIndex() const
728 {
729 for (int i=0; i<playersView.visibleCreatures.size(); i++) {
730 if (isCurrentIndex(i)) {
731@@ -451,6 +499,12 @@
732 playersView->setFontSize(fsize);
733 bool dh = settings.value(ASC("playersWindow/showDamageAndHeals"), true).toBool();
734 playersList->setShowDamageAndHeals(dh);
735+ bool hc = settings.value(ASC("playersWindow/highlightCurrent"), true).toBool();
736+ playersList->setHighlightCurrent(hc);
737+ QString hcc = settings.value(ASC("playersWindow/highlightCurrentColor"), QColor(Qt::darkGray).name()).toString();
738+ playersList->setHighlightCurrentColor(QColor(hcc));
739+ bool rl = settings.value(ASC("playersWindow/rotatePlayerInitList"), false).toBool();
740+ playersList->setRotatePlayerInitList(rl);
741 }
742
743 PlayersWindow::~PlayersWindow()
744@@ -461,6 +515,9 @@
745 settings.setValue(ASC("playersWindow/state"), saveState());
746 settings.setValue(ASC("playersWindow/fontSize"), playersView->getFontSize());
747 settings.setValue(ASC("playersWindow/showDamageAndHeals"), playersList->getShowDamageAndHeals());
748+ settings.setValue(ASC("playersWindow/highlightCurrent"), playersList->getHighlightCurrent());
749+ settings.setValue(ASC("playersWindow/highlightCurrentColor"), playersList->getHighlightCurrentColor().name());
750+ settings.setValue(ASC("playersWindow/rotatePlayerInitList"), playersList->getRotatePlayerInitList());
751 if (this->isVisible())
752 hideEvent(NULL);
753 }
754@@ -514,31 +571,87 @@
755 // for QAbstractScrollArea and derived classes you would use:
756 // QPoint globalPos = myWidget->viewport()->mapToGlobal(pos);
757
758- QMenu myMenu;
759+ bool hlOn = playersList->getHighlightCurrent();
760+ QString hlColor = playersList->getHighlightCurrentColor().name();
761+
762+ QMenu myMenu, *highlightMenu;
763+ highlightMenu = new QMenu(ASC("Highlight Current Turn"));
764+
765 myMenu.addAction(showYourTurnAction);
766 myMenu.addAction(showLastKillAction);
767 myMenu.addAction(showTickerAction);
768
769+ myMenu.addSeparator();
770+
771 QAction *damage_heals_action;
772- bool dh;
773- if ((dh = playersList->getShowDamageAndHeals()))
774- damage_heals_action = myMenu.addAction(tr("Hide Damage and Heals"));
775- else
776- damage_heals_action = myMenu.addAction(tr("Show Damage and Heals"));
777+ damage_heals_action = myMenu.addAction(tr("Show Damage and Heals"));
778+ damage_heals_action->setCheckable(true);
779+ damage_heals_action->setChecked(playersList->getShowDamageAndHeals());
780
781 QAction *font_size_action;
782- int fsize;
783- if ((fsize = playersView->getFontSize()))
784- font_size_action = myMenu.addAction(tr("Scale Font Size"));
785- else
786- font_size_action = myMenu.addAction(tr("Fix Font Size)"));
787- // ...
788-
789+ font_size_action = myMenu.addAction(tr("Fix Font Size"));
790+ font_size_action->setCheckable(true);
791+ font_size_action->setChecked(playersView->getFontSize());
792+
793+ myMenu.addMenu(highlightMenu);
794+
795+ QAction *highlight_disable;
796+ highlight_disable = highlightMenu->addAction(tr("Off"));
797+ highlight_disable->setCheckable(true);
798+ highlight_disable->setChecked(!hlOn);
799+
800+ highlightMenu->addSeparator();
801+
802+ QAction *highlight_darkgray;
803+ highlight_darkgray = highlightMenu->addAction(tr("Dark Gray"));
804+ highlight_darkgray->setCheckable(true);
805+ highlight_darkgray->setChecked(hlOn && hlColor == QColor(Qt::darkGray).name());
806+
807+ QAction *highlight_lightgreen;
808+ highlight_lightgreen = highlightMenu->addAction(tr("Light Green"));
809+ highlight_lightgreen->setCheckable(true);
810+ highlight_lightgreen->setChecked(hlOn && hlColor == ASC("#aaffaa"));
811+
812+ QAction *highlight_lightyellow;
813+ highlight_lightyellow = highlightMenu->addAction(tr("Light Yellow"));
814+ highlight_lightyellow->setCheckable(true);
815+ highlight_lightyellow->setChecked(hlOn && hlColor == ASC("#ffff80"));
816+
817+ QAction *highlight_yellow;
818+ highlight_yellow = highlightMenu->addAction(tr("Yellow"));
819+ highlight_yellow->setCheckable(true);
820+ highlight_yellow->setChecked(hlOn && hlColor == QColor(Qt::yellow).name());
821+
822+ QAction *rotate_init_action;
823+ rotate_init_action = myMenu.addAction(tr("Keep Current Turn on Top"));
824+ rotate_init_action->setCheckable(true);
825+ rotate_init_action->setChecked(playersList->getRotatePlayerInitList());
826+
827 QAction* selected_item = myMenu.exec(globalPos);
828 if (selected_item == font_size_action)
829- playersView->setFontSize(fsize ? 0 : 1);
830+ playersView->setFontSize(playersView->getFontSize() ? 0 : 1);
831 else if (selected_item == damage_heals_action)
832- playersList->setShowDamageAndHeals(!dh);
833+ playersList->setShowDamageAndHeals(!(playersList->getShowDamageAndHeals()));
834+ else if (selected_item == highlight_disable)
835+ playersList->setHighlightCurrent(false);
836+ else if (selected_item == highlight_darkgray) {
837+ playersList->setHighlightCurrent(true);
838+ playersList->setHighlightCurrentColor(QColor(Qt::darkGray));
839+ }
840+ else if (selected_item == highlight_lightgreen) {
841+ playersList->setHighlightCurrent(true);
842+ playersList->setHighlightCurrentColor(QColor("#aaffaa"));
843+ }
844+ else if (selected_item == highlight_lightyellow) {
845+ playersList->setHighlightCurrent(true);
846+ playersList->setHighlightCurrentColor(QColor("#ffff80"));
847+ }
848+ else if (selected_item == highlight_yellow) {
849+ playersList->setHighlightCurrent(true);
850+ playersList->setHighlightCurrentColor(QColor(Qt::yellow));
851+ }
852+ else if (selected_item == rotate_init_action)
853+ playersList->toggleRotatePlayerInitList();
854 }
855
856 void PlayersWindow::drawYourTurnCreature(Creature *creature, QString state)
857
858=== modified file 'src/playerswindow.h'
859--- src/playerswindow.h 2012-10-28 21:56:42 +0000
860+++ src/playerswindow.h 2013-10-03 04:04:27 +0000
861@@ -72,10 +72,19 @@
862 QMap<int, Creature *> getDeadMonsters();
863 void setShowDamageAndHeals(bool dh);
864 bool getShowDamageAndHeals();
865+ void setHighlightCurrent(bool hc);
866+ void toggleHighlightCurrent() { setHighlightCurrent(!highlightCurrent); }
867+ bool getHighlightCurrent() { return highlightCurrent; }
868+ void setHighlightCurrentColor(QColor color);
869+ QColor getHighlightCurrentColor() { return highlightCurrentColor; }
870+ int getHighlightCurrentColorBrightness() const;
871+ void setRotatePlayerInitList(bool rl);
872+ void toggleRotatePlayerInitList() { setRotatePlayerInitList(!rotatePlayerInitList); }
873+ bool getRotatePlayerInitList() { return rotatePlayerInitList; }
874
875 private:
876 bool isCurrentIndex(int idx) const;
877- int getCurrentIndex();
878+ int getCurrentIndex() const;
879 void refreshList();
880
881 PlayersView *myView;
882@@ -83,6 +92,9 @@
883 PlayersCreatureList playersView;
884 Creature *playerCurrentCreature;
885 bool showDamageAndHeals;
886+ bool highlightCurrent;
887+ QColor highlightCurrentColor;
888+ bool rotatePlayerInitList;
889
890 QIcon currentIcon;
891 QIcon emptyIcon;
892@@ -95,7 +107,7 @@
893
894 class PlayersView : public QTableView
895 {
896- Q_OBJECT
897+ Q_OBJECT
898 public:
899 PlayersView(QWidget *parent = 0) :
900 QTableView(parent),
901@@ -136,7 +148,7 @@
902
903 class PlayersWindow : public QMainWindow, public WindowsAndDialogs
904 {
905- Q_OBJECT
906+ Q_OBJECT
907 public:
908 PlayersWindow(QWidget *parent = 0);
909 virtual ~PlayersWindow();
910@@ -169,7 +181,7 @@
911
912 class PlayersWidget : public QWidget
913 {
914- Q_OBJECT
915+ Q_OBJECT
916 public:
917 PlayersWidget(QWidget *parent = 0);
918 virtual ~PlayersWidget() { }
919@@ -181,7 +193,7 @@
920 };
921
922 class YourTurnWidget : public PlayersWidget, public WindowsAndDialogs {
923- Q_OBJECT
924+ Q_OBJECT
925 public:
926 YourTurnWidget(PlayersListModel *model, QWidget *parent = 0);
927 virtual ~YourTurnWidget();
928@@ -209,7 +221,7 @@
929 };
930
931 class TickerWidget : public PlayersWidget, public WindowsAndDialogs {
932- Q_OBJECT
933+ Q_OBJECT
934 public:
935 TickerWidget(QWidget *parent = 0);
936 virtual ~TickerWidget();
937@@ -229,7 +241,7 @@
938 };
939
940 class LastKillWidget : public PlayersWidget, public WindowsAndDialogs {
941- Q_OBJECT
942+ Q_OBJECT
943 public:
944 LastKillWidget(PlayersListModel *model, QWidget *parent = 0);
945 virtual ~LastKillWidget();

Subscribers

People subscribed via source and target branches