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