aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-30 19:46:12 +0100
committerDanilo M. <danix@danix.xyz>2026-10-30 19:46:12 +0100
commit1953399f9bf0cf18ba54961583632d703eeaa07e (patch)
treec25283c05705137b2926d4ad466922d53f8d8f12
parent27dc1b71ff62dd4e8d91de239bc5ad70c1bc7428 (diff)
downloadupscale-qt-1953399f9bf0cf18ba54961583632d703eeaa07e.tar.gz
upscale-qt-1953399f9bf0cf18ba54961583632d703eeaa07e.zip
Add Ctrl+Q and ask before quitting with an unsaved upscale
Quit and window close share closeEvent, so both ask. The unsaved check moves into discard_ok, used by Clear as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
-rw-r--r--README.md1
-rw-r--r--test_upscale_qt.py19
-rwxr-xr-xupscale-qt21
3 files changed, 37 insertions, 4 deletions
diff --git a/README.md b/README.md
index cd73c5e..71ae4a0 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ Small PyQt6 app to upscale an image with an ESRGAN model through `sd-cli` (stabl
- Save As (Ctrl+Shift+S) writes a new file. Overwrite replaces the original after a confirmation, in the same format. EXIF metadata is not kept.
- Clear (Ctrl+W) empties the view and deletes the temp files. It asks first if the upscaled image was not saved.
- Settings holds the upscalers folder.
+- Ctrl+Q quits. Quitting or closing the window asks first if the upscaled image was not saved.
### Model descriptions
diff --git a/test_upscale_qt.py b/test_upscale_qt.py
index 66423e8..7175e92 100644
--- a/test_upscale_qt.py
+++ b/test_upscale_qt.py
@@ -114,6 +114,25 @@ def test_main_window():
assert not w.a_clear.isEnabled() and not w.a_up.isEnabled()
+def test_quit_asks_when_unsaved():
+ from PyQt6.QtWidgets import QMessageBox
+ w = uq.Main()
+ assert w.a_quit.shortcut().toString() == 'Ctrl+Q'
+ path = os.path.join(TMP, 'q.png')
+ QImage(4, 4, QImage.Format.Format_RGB32).save(path)
+ w.load(path)
+ w.jobs[0].result = QImage(16, 16, QImage.Format.Format_RGB32)
+ answers = []
+ real = QMessageBox.question
+ try:
+ QMessageBox.question = lambda *a: answers.append(a) or QMessageBox.StandardButton.No
+ assert not w.close() and len(answers) == 1 # declined, stays open
+ w.jobs[0].saved = True
+ assert w.close() and len(answers) == 1 # saved, no question
+ finally:
+ QMessageBox.question = real
+
+
if __name__ == '__main__':
for name, fn in list(globals().items()):
if name.startswith('test_'):
diff --git a/upscale-qt b/upscale-qt
index 5f22a5c..3515a1b 100755
--- a/upscale-qt
+++ b/upscale-qt
@@ -249,6 +249,10 @@ class Main(QMainWindow):
self.a_over = act('Overwrite', 'document-save', self.overwrite)
self.a_clear = act('Clear', 'edit-clear', self.clear, 'Ctrl+W')
act('Settings', 'preferences-system', self.settings)
+ self.a_quit = QAction('Quit', self) # window-wide, not on the toolbar
+ self.a_quit.setShortcut(QKeySequence.StandardKey.Quit)
+ self.a_quit.triggered.connect(self.close)
+ self.addAction(self.a_quit)
self.proc = QProcess(self)
self.proc.finished.connect(self.proc_done)
@@ -394,12 +398,18 @@ class Main(QMainWindow):
else:
QMessageBox.warning(self, 'upscale-qt', f'Cannot write {job.path}, original untouched')
- def clear(self):
+ def discard_ok(self, title):
+ """True unless an unsaved upscale exists and the user keeps it."""
job = self.job()
- if job.result is not None and not job.saved and QMessageBox.question(
- self, 'Clear', 'The upscaled image is not saved. Discard it?'
- ) != QMessageBox.StandardButton.Yes:
+ return (job is None or job.result is None or job.saved
+ or QMessageBox.question(
+ self, title, 'The upscaled image is not saved. Discard it?'
+ ) == QMessageBox.StandardButton.Yes)
+
+ def clear(self):
+ if not self.discard_ok('Clear'):
return
+ job = self.job()
for f in (job.input, job.out):
if f and os.path.exists(f):
os.remove(f) # 4x PNGs run to hundreds of MB
@@ -414,6 +424,9 @@ class Main(QMainWindow):
self.reload_models()
def closeEvent(self, e):
+ if not self.discard_ok('Quit'):
+ e.ignore()
+ return
if self.proc.state() != QProcess.ProcessState.NotRunning:
self.proc.kill()
self.proc.waitForFinished(2000)