Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
Loading...
Searching...
No Matches
mainwindow.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2020 Twilight
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15*/
16
23#include <QQmlEngine>
24#include <QQmlContext>
25#include <QGuiApplication>
26#include <QScreen>
27#include <QElapsedTimer>
28#include <QDebug>
29#include <QMessageBox>
30#include "mainwindow.h"
31
34
35#include <pse-common/types.h>
44
45#include <pse-db/db.h>
46#include <pse-db/fontsdb.h>
48
51
52MainWindow::MainWindow(QWidget *parent) :
53 QMainWindow(parent)
54{
55 QElapsedTimer t;
56 t.start();
57
58 // First setup UI
59 ui.setupUi(this);
60 qDebug() << "[MainWindow] setupUi —" << t.elapsed() << "ms";
61
62 // Save global class instance
63 MainWindow::instance = this;
64
65 // Create the file management class which kickstarts all the data classes and
66 // data management, etc... Basically a whole thing here lol
67 file = new FileManagement();
68 qDebug() << "[MainWindow] FileManagement —" << t.elapsed() << "ms";
69
70 // Inject several C++ class instances into QML
71 injectIntoQML();
72 qDebug() << "[MainWindow] injectIntoQML —" << t.elapsed() << "ms";
73
74 // Setup providers to QML
75 setupProviders();
76 qDebug() << "[MainWindow] setupProviders —" << t.elapsed() << "ms";
77
78 // Report QML load errors. In debug builds this shows a message box so
79 // problems are immediately visible during development; release builds stay
80 // silent (the app continues running as gracefully as it can).
81 connect(ui.app, &QQuickWidget::statusChanged, this, [this](QQuickWidget::Status status) {
82 if (status == QQuickWidget::Error) {
83 QString msg;
84 for (const auto& err : ui.app->errors())
85 msg += err.toString() + "\n";
86 qCritical() << "[QML]" << msg;
87#ifdef QT_DEBUG
88 QMessageBox::critical(this, "QML Error", msg);
89#endif
90 }
91 });
92
93 // Now load the QML page, has to be done after setup and injection
94 ui.app->setSource(QUrl(QStringLiteral("qrc:/ui/app/App.qml")));
95 qDebug() << "[MainWindow] setSource —" << t.elapsed() << "ms";
96
97 // Setup global shortcuts
98 setupShortcuts();
99
100 // Link up Signal & Slots
101 ssConnect();
102
103 // Initial setup
104 reUpdateRecentFiles(file->getRecentFiles());
105 onPathChanged(file->getPath());
106 loadState();
107 qDebug() << "[MainWindow] constructor done —" << t.elapsed() << "ms";
108}
109
111{
112 file->deleteLater();
113
114 for(var8 i = 0; i < MAX_RECENT_FILES; i++)
115 recentFileShortcuts[i]->deleteLater();
116
117 for(auto tmp : otherShortcuts)
118 tmp->deleteLater();
119}
120
121MainWindow* MainWindow::instance{nullptr};
122Bridge* MainWindow::bridge = nullptr;
123QQmlEngine* MainWindow::engine = nullptr;
124
126{
127 return MainWindow::instance;
128}
129
130void MainWindow::reUpdateRecentFiles(QList<QString> files)
131{
132 // Disable all recent files shortcuts
133 for(var8 i{0}; i < MAX_RECENT_FILES; i++) {
134 recentFileShortcuts[i]->setEnabled(false);
135 }
136
137 // Re-enable them based on how many recent files
138 for(var8 i{0}; i < MAX_RECENT_FILES && i < files.size(); i++) {
139 QString file{files.at(i)};
140 if(file == "")
141 continue;
142
143 recentFileShortcuts[i]->setEnabled(true);
144 }
145}
146
147void MainWindow::onRecentFileClick()
148{
149 QShortcut* shortcut{qobject_cast<QShortcut*>(sender())};
150 var8 index{static_cast<var8>(shortcut->property("index").toInt())};
151 file->openFileRecent(index);
152}
153
154void MainWindow::onPathChanged(QString path)
155{
156 if(path == "")
157 this->setWindowTitle("Pokered Save Editor - New File");
158 else
159 this->setWindowTitle("Pokered Save Editor - " + path);
160}
161
162void MainWindow::closeEvent(QCloseEvent *event)
163{
164 this->saveState();
165 event->accept();
166}
167
168void MainWindow::saveState()
169{
170 settings.beginGroup("WindowState");
171 settings.setValue("size", this->size());
172 settings.setValue("pos", this->pos());
173 settings.endGroup();
174}
175
176void MainWindow::loadState()
177{
178 settings.beginGroup("WindowState");
179 QSize savedSize = settings.value("size", QSize(1130, 740)).toSize();
180 QPoint savedPos = settings.value("pos", QPoint(200, 200)).toPoint();
181 settings.endGroup();
182
183 this->resize(savedSize);
184
185 // Guard against off-screen positions (e.g. disconnected monitor).
186 // Accept the saved position only if the title bar area is on some screen.
187 bool onScreen = false;
188 const QPoint titleBarPt = savedPos + QPoint(savedSize.width() / 2, 10);
189 for (const QScreen* screen : QGuiApplication::screens()) {
190 if (screen->availableGeometry().contains(titleBarPt)) {
191 onScreen = true;
192 break;
193 }
194 }
195 this->move(onScreen ? savedPos : QPoint(200, 200));
196}
197
198void MainWindow::setupShortcuts()
199{
200 // Create recent files shortcut (Ctrl+Shift+0..4 -> open recent file 0..Max).
201 for(var8 i = 0; i < MAX_RECENT_FILES; i++) {
202
203 // Create and link up a shortcut
204 // Ensure it's disabled, assign a shortcut, and assign the index to it
205 recentFileShortcuts[i] = new QShortcut(this);
206 recentFileShortcuts[i]->setEnabled(false);
208 recentFileShortcuts[i]->setProperty("index", i);
209 connect(recentFileShortcuts[i], &QShortcut::activated, this, &MainWindow::onRecentFileClick);
210 }
211
212 // Create and link up other shortcuts. Key sequences come from the shared
213 // pse::shortcutKeyMap() (single source of truth, asserted by tst_shortcuts);
214 // the action→verb wiring stays here. NB: write into the `otherShortcuts` MEMBER
215 // directly -- the previous `auto os = otherShortcuts;` copied it, so the member
216 // was left empty (the shortcuts only survived via their QObject parent).
217 auto& os = otherShortcuts;
218 const auto keymap = pse::shortcutKeyMap();
219 for (auto it = keymap.constBegin(); it != keymap.constEnd(); ++it)
220 os.insert(it.key(), new QShortcut(it.value(), this));
221
222 // Wire each shortcut to its verb via the shared pse::shortcutActions() map (the
223 // single source of truth tst_shortcuts fires against). exit/exit2 close the window.
224 const auto actions = pse::shortcutActions(file, [this]{ close(); });
225 for (auto it = actions.constBegin(); it != actions.constEnd(); ++it) {
226 QShortcut* sc = os.value(it.key(), nullptr);
227 if (!sc) continue;
228 const std::function<void()> verb = it.value();
229 connect(sc, &QShortcut::activated, this, [verb]{ verb(); });
230 }
231}
232
233void MainWindow::setupProviders()
234{
235 auto engine = ui.app->engine();
236 engine->addImageProvider("tileset", new TilesetProvider);
237 engine->addImageProvider("font", new FontPreviewProvider(file->data->dataExpanded));
238}
239
240void MainWindow::injectIntoQML()
241{
242 auto* context = ui.app->rootContext();
243 bridge = new Bridge(file);
244 context->setContextProperty("brg", bridge);
245 MainWindow::engine = ui.app->engine();
246
247 // Protect every DB entry from QML's garbage collector (s13f). DB::qmlProtect
248 // cascades CppOwnership to all sub-DB entries; without it QML GCs the shared,
249 // parentless FontDBEntry (and other) objects mid-session — fonts blank out,
250 // picker pills go red/empty, and names stop saving until an app reboot.
252}
253
254void MainWindow::ssConnect()
255{
256 connect(file, &FileManagement::pathChanged, this, &MainWindow::onPathChanged);
257 connect(file, &FileManagement::recentFilesChanged, this, &MainWindow::reUpdateRecentFiles);
258}
259
The single QML<->C++ doorway – everything the UI touches hangs off here.
Definition bridge.h:71
void qmlProtect(const QQmlEngine *const engine) const
Pin the DB aggregate (and every sub-DB) to C++ ownership so QML never GCs them.
Definition db.cpp:192
static DB * inst()
< Raw parsed JSON assets behind every DB.
Definition db.cpp:33
Owns the on-disk side of a save: the current path, the recent-files list, and the live SaveFile.
protected::void pathChanged(QString newPath, QString oldPath)
The active path changed.
void recentFilesChanged(QList< QString > files)
The recent-files list changed.
The top-level window – a QMainWindow hosting the QML UI in a QQuickWidget.
Definition mainwindow.h:51
static QQmlEngine * engine
The QML engine behind the hosted QQuickWidget.
Definition mainwindow.h:62
QShortcut * recentFileShortcuts[5]
Ctrl+1..5 open-recent shortcuts.
Definition mainwindow.h:67
QHash< QString, QShortcut * > otherShortcuts
Other global keyboard shortcuts by name.
Definition mainwindow.h:68
FileManagement * file
Definition mainwindow.h:64
MainWindow(QWidget *parent=nullptr)
< The live save controller.
static MainWindow * getInstance()
The single MainWindow instance.
virtual ~MainWindow()
static Bridge * bridge
The brg aggregate (created here, injected into QML).
Definition mainwindow.h:61
Project-wide fixed-width integer aliases (var8, var16, ...).
var8e var8
Everyday 8-bit alias. Exact (not "fastest") to dodge the pointer-width bug noted above.
Definition types.h:124
constexpr var8 MAX_RECENT_FILES
How many recent paths to remember.
QHash< QString, QKeySequence > shortcutKeyMap()
Named global shortcuts: action id -> key sequence.
QHash< QString, std::function< void()> > shortcutActions(FileManagement *file, std::function< void()> onExit)
What each named shortcut DOES, action id -> callable, over a live FileManagement.
QKeySequence recentFileShortcutKey(int i)
Recent-file shortcut i (0..MAX_RECENT_FILES-1) == Ctrl+Shift+(0+i), i.e.
Single source of truth for the global keyboard shortcut KEY SEQUENCES.