1 /*
2 SPDX-FileCopyrightText: 2009 Ian Wadham <iandw.au@gmail.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "kgrdebug.h"
8 #include "kgreditor.h"
9 #include "kgrscene.h"
10 #include "kgrview.h"
11 #include "kgrselector.h"
12 #include "kgrdialog.h"
13 #include "kgrgameio.h"
14 #include <KLocalizedString>
15 #include <ctype.h>
16 #include <QTimer>
17
18 #include "kgoldrunner_debug.h"
19
KGrEditor(KGrView * theView,const QString & theSystemDir,const QString & theUserDir,QList<KGrGameData * > & pGameList)20 KGrEditor::KGrEditor (KGrView * theView,
21 const QString & theSystemDir,
22 const QString & theUserDir,
23 QList<KGrGameData *> & pGameList)
24 :
25 QObject (theView), // Destroy Editor if view closes.
26 view (theView),
27 scene (view->gameScene()),
28 io (new KGrGameIO (view)),
29 systemDataDir (theSystemDir),
30 userDataDir (theUserDir),
31 gameList (pGameList),
32 editObj (BRICK), // Default edit-object.
33 shouldSave (false),
34 mouseDisabled (true)
35 {
36 levelData.width = FIELDWIDTH; // Default values for a brand new game.
37 levelData.height = FIELDHEIGHT;
38
39 // Connect and start the timer.
40 timer = new QTimer (this);
41 connect(timer, &QTimer::timeout, this, &KGrEditor::tick);
42 timer->start (TickTime); // TickTime def in kgrglobals.h.
43
44 // Connect edit-mode slots to signals from "view".
45 connect(view, &KGrView::mouseClick, this, &KGrEditor::doEdit);
46 connect(view, &KGrView::mouseLetGo, this, &KGrEditor::endEdit);
47 connect(this, &KGrEditor::getMousePos, scene, &KGrScene::getMousePos);
48 }
49
~KGrEditor()50 KGrEditor::~KGrEditor()
51 {
52 }
53
setEditObj(char newEditObj)54 void KGrEditor::setEditObj (char newEditObj)
55 {
56 editObj = newEditObj;
57 }
58
createLevel(int pGameIndex)59 bool KGrEditor::createLevel (int pGameIndex)
60 {
61 if (! saveOK ()) { // Check unsaved work.
62 return false;
63 }
64
65 if (! ownerOK (USER)) {
66 KGrMessage::information (view, i18n ("Create Level"),
67 i18n ("You cannot create and save a level "
68 "until you have created a game to hold "
69 "it. Try menu item \"Create Game\"."));
70 return false;
71 }
72
73 int i, j;
74 gameIndex = pGameIndex;
75 editLevel = 0;
76
77 // Ignore player input from the mouse while the screen is set up.
78 mouseDisabled = true;
79
80 levelData.level = 0;
81 levelData.name = "";
82 levelData.hint = "";
83 initEdit();
84
85 // Clear the playfield.
86 levelData.layout.resize (levelData.width * levelData.height);
87 for (i = 1; i <= levelData.width; ++i) {
88 for (j = 1; j <= levelData.height; ++j) {
89 insertEditObj (i, j, FREE);
90 }
91 }
92
93 // Add a hero.
94 insertEditObj (1, 1, HERO);
95
96 savedLevelData.layout = levelData.layout; // Copy for "saveOK()".
97 savedLevelData.name = levelData.name;
98 savedLevelData.hint = levelData.hint;
99
100 view->update(); // Show the level name.
101
102 // Re-enable player input.
103 mouseDisabled = false;
104 return true;
105 }
106
updateLevel(int pGameIndex,int level)107 bool KGrEditor::updateLevel (int pGameIndex, int level)
108 {
109
110 if (! saveOK ()) { // Check unsaved work.
111 return false;
112 }
113
114 if (! ownerOK (USER)) {
115 KGrMessage::information (view, i18n ("Edit Level"),
116 i18n ("You cannot edit and save a level until you "
117 "have created a game and a level. Try menu item \"Create Game\"."));
118 return false;
119 }
120
121 gameIndex = pGameIndex;
122
123 // Ignore player input from the mouse while the screen is set up.
124 mouseDisabled = true;
125
126 if (level < 0)
127 level = 0;
128 int selectedLevel = selectLevel (SL_UPDATE, level, gameIndex);
129 qCDebug(KGOLDRUNNER_LOG) << "Selected" << gameList.at(gameIndex)->name
130 << "level" << selectedLevel;
131 if (selectedLevel == 0) {
132 mouseDisabled = false;
133 return false;
134 }
135
136 if (gameList.at(gameIndex)->owner == SYSTEM) {
137 KGrMessage::information (view, i18n ("Edit Level"),
138 i18n ("It is OK to edit a system level, but you MUST save "
139 "the level in one of your own games. You are not just "
140 "taking a peek at the hidden ladders "
141 "and fall-through bricks, are you? :-)"));
142 }
143
144 loadEditLevel (selectedLevel);
145 mouseDisabled = false;
146 return true;
147 }
148
loadEditLevel(int lev)149 void KGrEditor::loadEditLevel (int lev)
150 {
151 KGrLevelData d;
152
153 qCDebug(KGOLDRUNNER_LOG) << "gameIndex" << gameIndex;
154 // If system game or ENDE screen, choose system dir, else choose user dir.
155 const QString dir = ((gameList.at(gameIndex)->owner == SYSTEM) ||
156 (lev == 0)) ? systemDataDir : userDataDir;
157 // Read the level data.
158 if (! io->readLevelData (dir, gameList.at(gameIndex)->prefix, lev, d)) {
159 return; // If I/O failed, no load.
160 }
161
162 editLevel = lev;
163 initEdit();
164
165 int i, j;
166 char obj;
167
168 // Load the level.
169 for (i = 1; i <= levelData.width; ++i) {
170 for (j = 1; j <= levelData.height; ++j) {
171 obj = d.layout [(j-1) * d.width + (i-1)];
172 insertEditObj (i, j, obj);
173 }
174 }
175 savedLevelData.layout = d.layout; // Copy for "saveOK()".
176
177 // Retain the original language of the name and hint when editing,
178 // but convert non-ASCII, UTF-8 substrings to Unicode (eg. ü to ü).
179 levelName = (d.name.size() > 0) ?
180 QString::fromUtf8 (d.name) : QString();
181 levelHint = (d.hint.size() > 0) ?
182 QString::fromUtf8 (d.hint) : QString();
183
184 scene->setTitle (getTitle()); // Show the level name.
185 }
186
editNameAndHint()187 void KGrEditor::editNameAndHint()
188 {
189 // Run a dialog box to create/edit the level name and hint.
190 KGrNHDialog * nh = new KGrNHDialog (levelName, levelHint, view);
191
192 if (nh->exec() == QDialog::Accepted) {
193 levelName = nh->getName();
194 levelHint = nh->getHint();
195 shouldSave = true;
196 }
197
198 delete nh;
199 }
200
saveLevelFile()201 bool KGrEditor::saveLevelFile()
202 {
203 bool isNew;
204 int action;
205
206 int i, j;
207 QString filePath;
208
209 // Save the current game index.
210 int N = gameIndex;
211
212 if (editLevel == 0) {
213 // New level: choose a number.
214 action = SL_CREATE;
215 }
216 else {
217 // Existing level: confirm the number or choose a new number.
218 action = SL_SAVE;
219 }
220
221 // Pop up dialog box, which could change the game or level or both.
222 int selectedLevel = selectLevel (action, editLevel, gameIndex);
223 if (selectedLevel == 0) {
224 return false;
225 }
226
227 // Get the new game (if changed).
228 int n = gameIndex;
229
230 // Set the name of the output file.
231 filePath = getLevelFilePath (gameList.at(n), selectedLevel);
232 QFile levelFile (filePath);
233
234 if ((action == SL_SAVE) && (n == N) && (selectedLevel == editLevel)) {
235 // This is a normal edit: the old file is to be re-written.
236 isNew = false;
237 }
238 else {
239 isNew = true;
240 // Check if the file is to be inserted in or appended to the game.
241 if (levelFile.exists()) {
242 switch (KGrMessage::warning (view, i18n ("Save Level"),
243 i18n ("Do you want to insert a level and "
244 "move existing levels up by one?"),
245 i18n ("&Insert Level"), i18n ("&Cancel"))) {
246
247 case 0: if (! reNumberLevels (n, selectedLevel,
248 gameList.at (n)->nLevels, +1)) {
249 return false;
250 }
251 break;
252 case 1: return false;
253 break;
254 }
255 }
256 }
257
258 // Open the output file.
259 if (! levelFile.open (QIODevice::WriteOnly)) {
260 KGrMessage::information (view, i18n ("Save Level"),
261 i18n ("Cannot open file '%1' for output.", filePath));
262 return false;
263 }
264
265 // Save the level - row by row.
266 for (j = 1; j <= levelData.height; ++j) {
267 for (i = 1; i <= levelData.width; ++i) {
268 levelFile.putChar (editableCell (i, j));
269 }
270 }
271 savedLevelData.layout = levelData.layout; // Copy for "saveOK()".
272 levelFile.putChar ('\n');
273
274 // Save the level name, changing non-ASCII chars to UTF-8 (eg. ü to ü).
275 QByteArray levelNameC = levelName.toUtf8();
276 int len1 = levelNameC.length();
277 if (len1 > 0) {
278 for (i = 0; i < len1; ++i)
279 levelFile.putChar (levelNameC[i]);
280 levelFile.putChar ('\n'); // Add a newline.
281 }
282
283 // Save the level hint, changing non-ASCII chars to UTF-8 (eg. ü to ü).
284 QByteArray levelHintC = levelHint.toUtf8();
285 int len2 = levelHintC.length();
286 char ch = '\0';
287
288 if (len2 > 0) {
289 if (len1 <= 0)
290 levelFile.putChar ('\n'); // Leave blank line for name.
291 for (i = 0; i < len2; ++i) {
292 ch = levelHintC[i];
293 levelFile.putChar (ch); // Copy the character.
294 }
295 if (ch != '\n')
296 levelFile.putChar ('\n'); // Add a newline character.
297 }
298
299 levelFile.close();
300 shouldSave = false;
301
302 if (isNew) {
303 gameList.at (n)->nLevels++;
304 saveGameData (USER);
305 }
306
307 editLevel = selectedLevel;
308 scene->setLevel (editLevel); // Choose a background picture.
309 scene->setTitle (getTitle()); // Display new title.
310 return true;
311 }
312
moveLevelFile(int pGameIndex,int level)313 bool KGrEditor::moveLevelFile (int pGameIndex, int level)
314 {
315 if (level <= 0) {
316 KGrMessage::information (view, i18n ("Move Level"),
317 i18n ("You must first load a level to be moved. Use "
318 "the \"%1\" or \"%2\" menu.",
319 i18n ("Game"), i18n ("Editor")));
320 return false;
321 }
322 gameIndex = pGameIndex;
323
324 int action = SL_MOVE;
325
326 int fromC = gameIndex;
327 int fromL = level;
328 int toC = fromC;
329 int toL = fromL;
330
331 if (! ownerOK (USER)) {
332 KGrMessage::information (view, i18n ("Move Level"),
333 i18n ("You cannot move a level until you "
334 "have created a game and at least two levels. Try "
335 "menu item \"Create Game\"."));
336 return false;
337 }
338
339 if (gameList.at (fromC)->owner != USER) {
340 KGrMessage::information (view, i18n ("Move Level"),
341 i18n ("Sorry, you cannot move a system level."));
342 return false;
343 }
344
345 // Pop up dialog box to get the game and level number to move to.
346 while ((toC == fromC) && (toL == fromL)) {
347 toL = selectLevel (action, toL, gameIndex);
348 if (toL == 0) {
349 return false;
350 }
351
352 toC = gameIndex;
353
354 if ((toC == fromC) && (toL == fromL)) {
355 KGrMessage::information (view, i18n ("Move Level"),
356 i18n ("You must change the level or the game or both."));
357 }
358 }
359
360 QString filePath1;
361 QString filePath2;
362
363 // Save the "fromN" file under a temporary name.
364 filePath1 = getLevelFilePath (gameList.at (fromC), fromL);
365 filePath2 = filePath1;
366 filePath2.append (QStringLiteral(".tmp"));
367 if (! KGrGameIO::safeRename (view, filePath1, filePath2)) {
368 return false;
369 }
370
371 if (toC == fromC) { // Same game.
372 if (toL < fromL) { // Decrease level.
373 // Move "toL" to "fromL - 1" up by 1.
374 if (! reNumberLevels (toC, toL, fromL-1, +1)) {
375 return false;
376 }
377 }
378 else { // Increase level.
379 // Move "fromL + 1" to "toL" down by 1.
380 if (! reNumberLevels (toC, fromL+1, toL, -1)) {
381 return false;
382 }
383 }
384 }
385 else { // Different game.
386 // In "fromC", move "fromL + 1" to "nLevels" down and update "nLevels".
387 if (! reNumberLevels (fromC, fromL + 1,
388 gameList.at (fromC)->nLevels, -1)) {
389 return false;
390 }
391 gameList.at (fromC)->nLevels--;
392
393 // In "toC", move "toL + 1" to "nLevels" up and update "nLevels".
394 if (! reNumberLevels (toC, toL, gameList.at (toC)->nLevels, +1)) {
395 return false;
396 }
397 gameList.at (toC)->nLevels++;
398
399 saveGameData (USER);
400 }
401
402 // Rename the saved "fromL" file to become "toL".
403 filePath1 = getLevelFilePath (gameList.at (toC), toL);
404 KGrGameIO::safeRename (view, filePath2, filePath1);
405
406 editLevel = toL;
407 scene->setLevel (editLevel); // Choose a background picture.
408 scene->setTitle (getTitle()); // Re-write title.
409 return true;
410 }
411
deleteLevelFile(int pGameIndex,int level)412 bool KGrEditor::deleteLevelFile (int pGameIndex, int level)
413 {
414 int action = SL_DELETE;
415 gameIndex = pGameIndex;
416
417 if (! ownerOK (USER)) {
418 KGrMessage::information (view, i18n ("Delete Level"),
419 i18n ("You cannot delete a level until you "
420 "have created a game and a level. Try "
421 "menu item \"Create Game\"."));
422 return false;
423 }
424
425 // Pop up dialog box to get the game and level number.
426 int selectedLevel = selectLevel (action, level, gameIndex);
427 if (selectedLevel == 0) {
428 return false;
429 }
430
431 QString filePath;
432
433 // Set the name of the file to be deleted.
434 int n = gameIndex;
435 filePath = getLevelFilePath (gameList.at (n), selectedLevel);
436 QFile levelFile (filePath);
437
438 // Delete the file for the selected game and level.
439 if (levelFile.exists()) {
440 if (selectedLevel < gameList.at (n)->nLevels) {
441 switch (KGrMessage::warning (view, i18n ("Delete Level"),
442 i18n ("Do you want to delete a level and "
443 "move higher levels down by one?"),
444 i18n ("&Delete Level"), i18n ("&Cancel"))) {
445 case 0: break;
446 case 1: return false; break;
447 }
448 levelFile.remove();
449 if (! reNumberLevels (n, selectedLevel + 1,
450 gameList.at(n)->nLevels, -1)) {
451 return false;
452 }
453 }
454 else {
455 levelFile.remove();
456 }
457 }
458 else {
459 KGrMessage::information (view, i18n ("Delete Level"),
460 i18n ("Cannot find file '%1' to be deleted.", filePath));
461 return false;
462 }
463
464 gameList.at (n)->nLevels--;
465 saveGameData (USER);
466 if (selectedLevel <= gameList.at (n)->nLevels) {
467 editLevel = selectedLevel;
468 }
469 else {
470 editLevel = gameList.at (n)->nLevels;
471 }
472
473 // Repaint the screen with the level that now has the selected number.
474 if (level > 0) {
475 loadEditLevel (editLevel); // Load level in edit mode.
476 }
477 else {
478 createLevel (gameIndex); // No levels left in game.
479 }
480 return true;
481 }
482
editGame(int pGameIndex)483 bool KGrEditor::editGame (int pGameIndex)
484 {
485 int n = -1;
486 int action = (pGameIndex < 0) ? SL_CR_GAME : SL_UPD_GAME;
487 gameIndex = pGameIndex;
488
489 // If editing, choose a game.
490 if (gameIndex >= 0) {
491 int selectedLevel = selectLevel (SL_UPD_GAME, editLevel, gameIndex);
492 if (selectedLevel == 0) {
493 return false;
494 }
495 editLevel = selectedLevel;
496 n = gameIndex;
497 }
498
499 bool result = false;
500 KGrECDialog * ec = new KGrECDialog (action, n, gameList, view);
501
502 while (ec->exec() == QDialog::Accepted) { // Loop until valid.
503
504 // Validate the game details.
505 QString ecName = ec->getName();
506 int len = ecName.length();
507 if (len == 0) {
508 KGrMessage::information (view, i18n ("Save Game Info"),
509 i18n ("You must enter a name for the game."));
510 continue;
511 }
512
513 QString ecPrefix = ec->getPrefix();
514 if ((action == SL_CR_GAME) || (gameList.at (n)->nLevels <= 0)) {
515 // The filename prefix could have been entered, so validate it.
516 len = ecPrefix.length();
517 if (len == 0) {
518 KGrMessage::information (view, i18n ("Save Game Info"),
519 i18n ("You must enter a filename prefix for the game."));
520 continue;
521 }
522 if (len > 5) {
523 KGrMessage::information (view, i18n ("Save Game Info"),
524 i18n ("The filename prefix should not "
525 "be more than 5 characters."));
526 continue;
527 }
528
529 bool allAlpha = true;
530 for (int i = 0; i < len; i++) {
531 if (! isalpha (ecPrefix.at (i).toLatin1())) {
532 allAlpha = false;
533 break;
534 }
535 }
536 if (! allAlpha) {
537 KGrMessage::information (view, i18n ("Save Game Info"),
538 i18n ("The filename prefix should be "
539 "all alphabetic characters."));
540 continue;
541 }
542
543 bool duplicatePrefix = false;
544 KGrGameData * c;
545 int imax = gameList.count();
546 for (int i = 0; i < imax; i++) {
547 c = gameList.at (i);
548 if ((c->prefix == ecPrefix) && (i != n)) {
549 duplicatePrefix = true;
550 break;
551 }
552 }
553
554 if (duplicatePrefix) {
555 KGrMessage::information (view, i18n ("Save Game Info"),
556 i18n ("The filename prefix '%1' is already in use.",
557 ecPrefix));
558 continue;
559 }
560 }
561
562 // Save the game details.
563 char rules = 'K';
564 if (ec->isTrad()) {
565 rules = 'T';
566 }
567
568 KGrGameData * gameData = nullptr;
569 if (action == SL_CR_GAME) {
570 // Create empty game data and add it to the main list in KGrGame.
571 gameData = new KGrGameData();
572 gameList.append (gameData);
573 gameIndex = gameList.count() - 1;
574 editLevel = 1;
575
576 // Set the initial values for a new game.
577 gameData->owner = USER;
578 gameData->nLevels = 0;
579 gameData->skill = 'N';
580 gameData->width = FIELDWIDTH;
581 gameData->height = FIELDHEIGHT;
582 }
583 else {
584 // Point to existing game data.
585 gameData = gameList.at (gameIndex);
586 }
587
588 // Create or update the editable values.
589 gameData->rules = rules;
590 gameData->prefix = ecPrefix;
591 gameData->name = ecName;
592 gameData->about = ec->getAboutText().toUtf8();
593
594 saveGameData (USER);
595 result = true; // Successful create/edit.
596 break; // All done now.
597 }
598
599 delete ec;
600 return result;
601 }
602
603 /******************************************************************************/
604 /************************ LEVEL SELECTION DIALOG ************************/
605 /******************************************************************************/
606
selectLevel(int action,int requestedLevel,int & requestedGame)607 int KGrEditor::selectLevel (int action, int requestedLevel, int & requestedGame)
608 {
609 int selectedLevel = 0; // 0 = no selection (Cancel) or invalid.
610 int selectedGame = requestedGame;
611
612 // Create and run a modal dialog box to select a game and level.
613 KGrSLDialog * sl = new KGrSLDialog (action, requestedLevel, requestedGame,
614 gameList, systemDataDir, userDataDir,
615 view);
616 connect(sl, &KGrSLDialog::editNameAndHint, this, &KGrEditor::editNameAndHint);
617 bool selected = sl->selectLevel (selectedGame, selectedLevel);
618 delete sl;
619
620 if (selected) {
621 requestedGame = selectedGame;
622 return (selectedLevel);
623 }
624 else {
625 return (0); // 0 = cancelled or invalid.
626 }
627 }
628
629 /******************************************************************************/
630 /********************* SUPPORTING GAME EDITOR FUNCTIONS *********************/
631 /******************************************************************************/
632
saveOK()633 bool KGrEditor::saveOK ()
634 {
635 bool result = true;
636
637 if ((shouldSave) || (levelData.layout != savedLevelData.layout)) {
638 // If shouldSave == true, level name or hint was edited.
639 switch (KGrMessage::warning (view, i18n ("Editor"),
640 i18n ("You have not saved your work. Do "
641 "you want to save it now?"),
642 i18n ("&Save"), i18n ("&Do Not Save"),
643 i18n ("&Go on editing")))
644 {
645 case 0:
646 result = saveLevelFile(); // Save, do next action: or more edits.
647 break;
648 case 1:
649 shouldSave = false; // Do not save, but do next action.
650 break;
651 case 2:
652 result = false; // Go back to editing.
653 break;
654 }
655 }
656
657 return (result);
658 }
659
initEdit()660 void KGrEditor::initEdit()
661 {
662 paintEditObj = false;
663 paintAltObj = false;
664
665 // Set the default object and button.
666 editObj = BRICK;
667
668 oldI = 0;
669 oldJ = 0;
670 heroCount = 0;
671
672 scene->setLevel (editLevel); // Choose a background picture.
673 scene->setTitle (getTitle()); // Show title of level.
674
675 shouldSave = false; // Used to flag editing of name or hint.
676 }
677
insertEditObj(int i,int j,char obj)678 void KGrEditor::insertEditObj (int i, int j, char obj)
679 {
680 dbk2 << i << j << obj;
681 if ((i < 1) || (j < 1) || (i > levelData.width) || (j > levelData.height)) {
682 return; // Do nothing: mouse pointer is out of playfield.
683 }
684
685 if (editableCell (i, j) == HERO) {
686 // The hero is in this cell: remove him.
687 setEditableCell (i, j, FREE);
688 heroCount = 0;
689 }
690
691 if (obj == HERO) {
692 if (heroCount > 0) {
693 // Can only have one hero: remove him from his previous position.
694 for (int m = 1; m <= levelData.width; m++) {
695 for (int n = 1; n <= levelData.height; n++) {
696 if (editableCell (m, n) == HERO) {
697 setEditableCell (m, n, FREE);
698 }
699 }
700 }
701 }
702 heroCount = 1;
703 }
704
705 setEditableCell (i, j, obj);
706 }
707
editableCell(int i,int j)708 char KGrEditor::editableCell (int i, int j)
709 {
710 return (levelData.layout [(i - 1) + (j - 1) * levelData.width]);
711 }
712
setEditableCell(int i,int j,char type)713 void KGrEditor::setEditableCell (int i, int j, char type)
714 {
715 levelData.layout [(i - 1) + (j - 1) * levelData.width] = type;
716 scene->paintCell (i, j, type);
717 }
718
reNumberLevels(int cIndex,int first,int last,int inc)719 bool KGrEditor::reNumberLevels (int cIndex, int first, int last, int inc)
720 {
721 int i, n, step;
722 QString file1, file2;
723
724 if (inc > 0) {
725 i = last;
726 n = first - 1;
727 step = -1;
728 }
729 else {
730 i = first;
731 n = last + 1;
732 step = +1;
733 }
734
735 while (i != n) {
736 file1 = getLevelFilePath (gameList.at (cIndex), i);
737 file2 = getLevelFilePath (gameList.at (cIndex), i - step);
738 if (! KGrGameIO::safeRename (view, file1, file2)) {
739 return (false);
740 }
741 i = i + step;
742 }
743
744 return (true);
745 }
746
ownerOK(Owner o)747 bool KGrEditor::ownerOK (Owner o)
748 {
749 // Check that this owner has at least one set of game data.
750 bool OK = false;
751
752 for (KGrGameData * d : std::as_const(gameList)) {
753 if (d->owner == o) {
754 OK = true;
755 break;
756 }
757 }
758
759 return (OK);
760 }
761
saveGameData(Owner o)762 bool KGrEditor::saveGameData (Owner o)
763 {
764 QString filePath;
765
766 if (o != USER) {
767 KGrMessage::information (view, i18n ("Save Game Info"),
768 i18n ("You can only modify user games."));
769 return (false);
770 }
771
772 filePath = userDataDir + QStringLiteral("games.dat");
773
774 QFile c (filePath);
775
776 // Open the output file.
777 if (! c.open (QIODevice::WriteOnly)) {
778 KGrMessage::information (view, i18n ("Save Game Info"),
779 i18n ("Cannot open file '%1' for output.", filePath));
780 return (false);
781 }
782
783 // Save the game-data objects.
784 QString line;
785 QByteArray lineC;
786 int i, len;
787 char ch;
788
789 for (KGrGameData * gData : std::as_const(gameList)) {
790 if (gData->owner == o) {
791 line = QStringLiteral ("%1 %2 %3 %4\n")
792 .arg (gData->nLevels, 3, 10, QLatin1Char('0')) // int 00n
793 .arg (gData->rules) // char
794 .arg (gData->prefix) // QString
795 .arg (gData->name); // QString
796 lineC = line.toUtf8();
797 len = lineC.length();
798 for (i = 0; i < len; ++i) {
799 c.putChar (lineC.at (i));
800 }
801
802 len = gData->about.length();
803 if (len > 0) {
804 QByteArray aboutC = gData->about;
805 len = aboutC.length(); // Might be longer now.
806 for (i = 0; i < len; ++i) {
807 ch = aboutC[i];
808 if (ch != '\n') {
809 c.putChar (ch); // Copy the character.
810 }
811 else {
812 c.putChar ('\\'); // Change newline to \ and n.
813 c.putChar ('n');
814 }
815 }
816 c.putChar ('\n'); // Add a real newline.
817 }
818 }
819 }
820
821 c.close();
822 return (true);
823 }
824
getTitle()825 QString KGrEditor::getTitle()
826 {
827 if (editLevel <= 0) {
828 // Generate a special title for a new level.
829 return (i18n ("New Level"));
830 }
831
832 // Set title string to "Game-name - NNN" or "Game-name - NNN - Level-name".
833 KGrGameData * gameData = gameList.at(gameIndex);
834 QString levelNumber = QString::number(editLevel).rightJustified(3, QLatin1Char('0'));
835
836 QString levelTitle = (levelName.length() <= 0)
837 ?
838 i18nc ("Game name - level number.",
839 "%1 - %2",
840 gameData->name, levelNumber)
841 :
842 i18nc ("Game name - level number - level name.",
843 "%1 - %2 - %3",
844 gameData->name, levelNumber, levelName);
845 return (levelTitle);
846 }
847
getLevelFilePath(KGrGameData * gameData,int lev)848 QString KGrEditor::getLevelFilePath (KGrGameData * gameData, int lev)
849 {
850 QString filePath = userDataDir + QLatin1String("levels/") + gameData->prefix +
851 QString::number(lev).rightJustified(3, QLatin1Char('0')) + QStringLiteral(".grl");
852 return (filePath);
853 }
854
855 /******************************************************************************/
856 /********************* EDIT ACTION SLOTS **********************************/
857 /******************************************************************************/
858
doEdit(int button)859 void KGrEditor::doEdit (int button)
860 {
861 if (mouseDisabled) {
862 return;
863 }
864
865 // Mouse button down: start making changes.
866 int i, j;
867 Q_EMIT getMousePos (i, j);
868 qCDebug(KGOLDRUNNER_LOG) << "Button" << button << "at" << i << j;
869
870 switch (button) {
871 case Qt::LeftButton:
872 paintEditObj = true;
873 insertEditObj (i, j, editObj);
874 oldI = i;
875 oldJ = j;
876 break;
877 case Qt::RightButton:
878 paintAltObj = true;
879 insertEditObj (i, j, FREE);
880 oldI = i;
881 oldJ = j;
882 break;
883 default:
884 break;
885 }
886 }
887
tick()888 void KGrEditor::tick()
889 {
890 if (mouseDisabled) {
891 return;
892 }
893
894 // Check if a mouse-button is down: left = paint, right = erase.
895 if (paintEditObj || paintAltObj) {
896
897 int i, j;
898 Q_EMIT getMousePos (i, j);
899
900 // Check if the mouse has moved.
901 if ((i != oldI) || (j != oldJ)) {
902 // If so, paint or erase a cell.
903 insertEditObj (i, j, (paintEditObj) ? editObj : FREE);
904 oldI = i;
905 oldJ = j;
906 }
907 }
908 }
909
endEdit(int button)910 void KGrEditor::endEdit (int button)
911 {
912 if (mouseDisabled) {
913 return;
914 }
915
916 // Mouse button released: finish making changes.
917 int i, j;
918 Q_EMIT getMousePos (i, j);
919
920 switch (button) {
921 case Qt::LeftButton:
922 paintEditObj = false;
923 if ((i != oldI) || (j != oldJ)) {
924 insertEditObj (i, j, editObj);
925 }
926 break;
927 case Qt::RightButton:
928 paintAltObj = false;
929 if ((i != oldI) || (j != oldJ)) {
930 insertEditObj (i, j, FREE);
931 }
932 break;
933 default:
934 break;
935 }
936 }
937
938
939