sqlbench 0.1.59__tar.gz → 0.1.61__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {sqlbench-0.1.59 → sqlbench-0.1.61}/PKG-INFO +1 -1
  2. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/main_window.py +43 -25
  3. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tabs/sql_tab.py +143 -46
  4. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/version.py +1 -1
  5. {sqlbench-0.1.59 → sqlbench-0.1.61}/.github/workflows/pypi-publish.yml +0 -0
  6. {sqlbench-0.1.59 → sqlbench-0.1.61}/.gitignore +0 -0
  7. {sqlbench-0.1.59 → sqlbench-0.1.61}/CLAUDE.md +0 -0
  8. {sqlbench-0.1.59 → sqlbench-0.1.61}/Makefile +0 -0
  9. {sqlbench-0.1.59 → sqlbench-0.1.61}/PYQT_MIGRATION_FEATURES.md +0 -0
  10. {sqlbench-0.1.59 → sqlbench-0.1.61}/README.md +0 -0
  11. {sqlbench-0.1.59 → sqlbench-0.1.61}/pyproject.toml +0 -0
  12. {sqlbench-0.1.59 → sqlbench-0.1.61}/requirements.txt +0 -0
  13. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/__init__.py +0 -0
  14. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/__main__.py +0 -0
  15. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/adapters.py +0 -0
  16. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/app.py +0 -0
  17. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/database.py +0 -0
  18. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/dialogs/__init__.py +0 -0
  19. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/dialogs/connection_dialog.py +0 -0
  20. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/dialogs/regex_builder_dialog.py +0 -0
  21. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/launcher.py +0 -0
  22. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/__init__.py +0 -0
  23. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/connection_tree.py +0 -0
  24. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/__init__.py +0 -0
  25. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/connection_dialog.py +0 -0
  26. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/query_manager_dialog.py +0 -0
  27. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/record_viewer_dialog.py +0 -0
  28. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/regex_builder_dialog.py +0 -0
  29. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/dialogs/settings_dialog.py +0 -0
  30. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/icons.py +0 -0
  31. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/syntax.py +0 -0
  32. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tab_widget.py +0 -0
  33. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tabs/__init__.py +0 -0
  34. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/tabs/spool_tab.py +0 -0
  35. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/qt/theme.py +0 -0
  36. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_ibmi.png +0 -0
  37. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_mysql.png +0 -0
  38. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_postgresql.png +0 -0
  39. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/db_unknown.png +0 -0
  40. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/resources/sqlbench.png +0 -0
  41. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/tabs/__init__.py +0 -0
  42. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/tabs/spool_tab.py +0 -0
  43. {sqlbench-0.1.59 → sqlbench-0.1.61}/sqlbench/tabs/sql_tab.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlbench
3
- Version: 0.1.59
3
+ Version: 0.1.61
4
4
  Summary: A multi-database SQL workbench with support for IBM i, MySQL, and PostgreSQL
5
5
  Project-URL: Homepage, https://github.com/jpsteil/sqlbench
6
6
  Project-URL: Repository, https://github.com/jpsteil/sqlbench
@@ -8,7 +8,7 @@ and tabbed query/spool interface.
8
8
  import subprocess
9
9
  import threading
10
10
  from typing import Optional, Dict, Any
11
- from PyQt6.QtCore import Qt, QSettings, QTimer, pyqtSignal
11
+ from PyQt6.QtCore import Qt, QTimer, pyqtSignal
12
12
  from PyQt6.QtGui import QAction, QIcon, QKeySequence, QCloseEvent
13
13
  from PyQt6.QtWidgets import (
14
14
  QMainWindow,
@@ -141,25 +141,28 @@ class MainWindow(QMainWindow):
141
141
  self.status_bar.showMessage("Ready")
142
142
 
143
143
  def _restore_state(self) -> None:
144
- """Restore window geometry and state."""
145
- settings = QSettings("SQLBench", "SQLBench")
144
+ """Restore window size (position is compositor-controlled on Wayland)."""
145
+ w = get_setting("window_width")
146
+ h = get_setting("window_height")
146
147
 
147
- # Restore geometry
148
- geometry = settings.value("geometry")
149
- if geometry:
150
- self.restoreGeometry(geometry)
148
+ if w and h:
149
+ try:
150
+ w, h = int(w), int(h)
151
+ w = max(w, self.minimumWidth())
152
+ h = max(h, self.minimumHeight())
153
+ # Clamp to fit on screen
154
+ screen = QApplication.primaryScreen().availableGeometry()
155
+ w = min(w, screen.width())
156
+ h = min(h, screen.height())
157
+ self.resize(w, h)
158
+ except (ValueError, TypeError):
159
+ self.resize(1400, 900)
151
160
  else:
152
- # Default size
153
161
  self.resize(1400, 900)
154
- # Center on screen
155
- screen = QApplication.primaryScreen().geometry()
156
- self.move(
157
- (screen.width() - self.width()) // 2,
158
- (screen.height() - self.height()) // 2
159
- )
160
162
 
161
163
  # Restore main splitter from ratio
162
164
  ratio_str = get_setting("layout_main_ratio")
165
+
163
166
  if ratio_str:
164
167
  try:
165
168
  ratio = float(ratio_str)
@@ -209,9 +212,9 @@ class MainWindow(QMainWindow):
209
212
  self._connect(last_conn)
210
213
 
211
214
  def _save_state(self) -> None:
212
- """Save window geometry and state."""
213
- settings = QSettings("SQLBench", "SQLBench")
214
- settings.setValue("geometry", self.saveGeometry())
215
+ """Save window size and state."""
216
+ set_setting("window_width", str(self.width()))
217
+ set_setting("window_height", str(self.height()))
215
218
 
216
219
  # Save main splitter as ratio
217
220
  sizes = self.splitter.sizes()
@@ -332,13 +335,6 @@ class MainWindow(QMainWindow):
332
335
  self.splitter.setSizes([250, 1000])
333
336
  self.resize(1400, 900)
334
337
 
335
- # Center on screen
336
- screen = QApplication.primaryScreen().geometry()
337
- self.move(
338
- (screen.width() - self.width()) // 2,
339
- (screen.height() - self.height()) // 2
340
- )
341
-
342
338
  set_setting("font_size", "13")
343
339
  self._apply_font_size()
344
340
  self.status_bar.showMessage("Layout reset to defaults", 3000)
@@ -633,7 +629,12 @@ class MainWindow(QMainWindow):
633
629
  success, message = result
634
630
  self.status_bar.showMessage("Upgrade complete" if success else "Upgrade failed", 3000)
635
631
  if success:
636
- QMessageBox.information(self, "Upgrade Complete", message)
632
+ result = QMessageBox.question(
633
+ self, "Upgrade Complete",
634
+ "SQLBench has been upgraded.\n\nWould you like to restart now?",
635
+ QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
636
+ if result == QMessageBox.StandardButton.Yes:
637
+ self._restart_app()
637
638
  else:
638
639
  QMessageBox.warning(self, "Upgrade Failed", message)
639
640
 
@@ -642,6 +643,23 @@ class MainWindow(QMainWindow):
642
643
  thread.start()
643
644
  QTimer.singleShot(2000, poll_result)
644
645
 
646
+ def _restart_app(self) -> None:
647
+ """Restart the application."""
648
+ import sys
649
+ import os
650
+
651
+ # Save window state
652
+ self._save_state()
653
+
654
+ # Start new process before closing
655
+ if sys.argv[0].endswith('sqlbench') or 'sqlbench' in sys.argv[0]:
656
+ subprocess.Popen([sys.argv[0]])
657
+ else:
658
+ subprocess.Popen([sys.executable, '-m', 'sqlbench'])
659
+
660
+ self.close()
661
+ os._exit(0)
662
+
645
663
  def set_status(self, message: str, timeout: int = 0) -> None:
646
664
  """Set status bar message."""
647
665
  self.status_bar.showMessage(message, timeout)
@@ -281,6 +281,8 @@ class ScriptWorker(QThread):
281
281
  "row_count": 0,
282
282
  "success": True,
283
283
  "error": None,
284
+ "rows": None,
285
+ "description": None,
284
286
  }
285
287
 
286
288
  try:
@@ -293,6 +295,8 @@ class ScriptWorker(QThread):
293
295
  rows = cursor.fetchall()
294
296
  result["row_count"] = len(rows)
295
297
  result["status"] = f"{len(rows)} row(s) returned"
298
+ result["description"] = cursor.description
299
+ result["rows"] = rows[:10000]
296
300
  else:
297
301
  rc = cursor.rowcount if cursor.rowcount >= 0 else 0
298
302
  result["row_count"] = rc
@@ -370,22 +374,31 @@ class SQLEditor(QPlainTextEdit):
370
374
  # Shortcuts
371
375
  self._setup_shortcuts()
372
376
 
377
+ def _add_menu_action(self, menu, text, slot, shortcut=None) -> QAction:
378
+ """Add action to menu with proper PyQt6 overload handling."""
379
+ action = QAction(text, menu)
380
+ if shortcut:
381
+ action.setShortcut(shortcut)
382
+ action.triggered.connect(slot)
383
+ menu.addAction(action)
384
+ return action
385
+
373
386
  def _show_context_menu(self, pos) -> None:
374
387
  """Show context menu."""
375
388
  menu = QMenu(self)
376
389
 
377
- undo_action = menu.addAction("Undo", self.undo, QKeySequence("Ctrl+Z"))
378
- undo_action.setEnabled(self.document().isUndoAvailable())
379
- redo_action = menu.addAction("Redo", self.redo, QKeySequence("Ctrl+Y"))
380
- redo_action.setEnabled(self.document().isRedoAvailable())
390
+ undo = self._add_menu_action(menu, "Undo", self.undo, QKeySequence("Ctrl+Z"))
391
+ undo.setEnabled(self.document().isUndoAvailable())
392
+ redo = self._add_menu_action(menu, "Redo", self.redo, QKeySequence("Ctrl+Y"))
393
+ redo.setEnabled(self.document().isRedoAvailable())
381
394
  menu.addSeparator()
382
- menu.addAction("Cut", self.cut, QKeySequence("Ctrl+X"))
383
- menu.addAction("Copy", self.copy, QKeySequence("Ctrl+C"))
384
- menu.addAction("Paste", self.paste, QKeySequence("Ctrl+V"))
395
+ self._add_menu_action(menu, "Cut", self.cut, QKeySequence("Ctrl+X"))
396
+ self._add_menu_action(menu, "Copy", self.copy, QKeySequence("Ctrl+C"))
397
+ self._add_menu_action(menu, "Paste", self.paste, QKeySequence("Ctrl+V"))
385
398
  menu.addSeparator()
386
- menu.addAction("Select All", self.selectAll, QKeySequence("Ctrl+A"))
399
+ self._add_menu_action(menu, "Select All", self.selectAll, QKeySequence("Ctrl+A"))
387
400
  menu.addSeparator()
388
- menu.addAction("Find...", self.find_requested.emit, QKeySequence("Ctrl+F"))
401
+ self._add_menu_action(menu, "Find...", self.find_requested.emit, QKeySequence("Ctrl+F"))
389
402
 
390
403
  menu.exec(self.mapToGlobal(pos))
391
404
 
@@ -399,16 +412,6 @@ class SQLEditor(QPlainTextEdit):
399
412
  shortcut_exec_all = QShortcut(QKeySequence("Ctrl+F5"), self)
400
413
  shortcut_exec_all.activated.connect(self.execute_all_requested.emit)
401
414
 
402
- # Ctrl+Z - Undo
403
- shortcut_undo = QShortcut(QKeySequence("Ctrl+Z"), self)
404
- shortcut_undo.activated.connect(self.undo)
405
-
406
- # Ctrl+Y or Ctrl+Shift+Z - Redo
407
- shortcut_redo = QShortcut(QKeySequence("Ctrl+Y"), self)
408
- shortcut_redo.activated.connect(self.redo)
409
- shortcut_redo2 = QShortcut(QKeySequence("Ctrl+Shift+Z"), self)
410
- shortcut_redo2.activated.connect(self.redo)
411
-
412
415
  def update_theme(self) -> None:
413
416
  """Update colors when theme changes."""
414
417
  self.highlighter.update_theme()
@@ -532,10 +535,20 @@ class ResultsTable(QTableWidget):
532
535
  def _show_context_menu(self, pos) -> None:
533
536
  """Show context menu."""
534
537
  menu = QMenu(self)
535
- menu.addAction("Copy", self._copy_selection, QKeySequence("Ctrl+C"))
538
+
539
+ copy_act = QAction("Copy", menu)
540
+ copy_act.setShortcut(QKeySequence("Ctrl+C"))
541
+ copy_act.triggered.connect(self._copy_selection)
542
+ menu.addAction(copy_act)
543
+
536
544
  menu.addAction("Copy with Headers", self._copy_with_headers)
537
545
  menu.addSeparator()
538
- menu.addAction("Select All", self.selectAll, QKeySequence("Ctrl+A"))
546
+
547
+ sel_act = QAction("Select All", menu)
548
+ sel_act.setShortcut(QKeySequence("Ctrl+A"))
549
+ sel_act.triggered.connect(self.selectAll)
550
+ menu.addAction(sel_act)
551
+
539
552
  menu.exec(self.mapToGlobal(pos))
540
553
 
541
554
  def _copy_selection(self) -> None:
@@ -693,6 +706,8 @@ class SQLTab(QWidget):
693
706
  self._original_values: dict = {} # row_index -> original row tuple
694
707
  self._modified_cells: dict = {} # row_index -> {col_index: new_value}
695
708
  self._loading_results = False # guard flag to ignore cellChanged during loads
709
+ self._in_script_mode = False
710
+ self._script_results = [] # result dicts for sub-tab field lookups
696
711
 
697
712
  self._setup_ui()
698
713
  self._connect_signals()
@@ -841,13 +856,19 @@ class SQLTab(QWidget):
841
856
  results_layout.setSpacing(0)
842
857
 
843
858
  # Results controls
844
- controls = self._create_results_controls()
845
- results_layout.addWidget(controls)
859
+ self._results_controls = self._create_results_controls()
860
+ results_layout.addWidget(self._results_controls)
846
861
 
847
862
  # Results table
848
863
  self.results_table = ResultsTable()
849
864
  results_layout.addWidget(self.results_table)
850
865
 
866
+ # Script sub-tabs (hidden by default, shown in script mode)
867
+ self._script_sub_tabs = QTabWidget()
868
+ self._script_sub_tabs.setDocumentMode(True)
869
+ self._script_sub_tabs.hide()
870
+ results_layout.addWidget(self._script_sub_tabs)
871
+
851
872
  # Status label
852
873
  self.results_status = QLabel("No results")
853
874
  self.results_status.setProperty("subheading", True)
@@ -1178,6 +1199,14 @@ class SQLTab(QWidget):
1178
1199
  self.results_table.verticalHeader().setDefaultSectionSize(row_height)
1179
1200
  self.fields_table.verticalHeader().setDefaultSectionSize(row_height)
1180
1201
 
1202
+ # Scale any visible script sub-tab tables
1203
+ if self._in_script_mode:
1204
+ for i in range(self._script_sub_tabs.count()):
1205
+ w = self._script_sub_tabs.widget(i)
1206
+ if isinstance(w, ResultsTable):
1207
+ w.scale_columns(scale)
1208
+ w.verticalHeader().setDefaultSectionSize(row_height)
1209
+
1181
1210
  # Update stats font
1182
1211
  font = self.stats_text.font()
1183
1212
  font.setPointSize(size)
@@ -1219,6 +1248,93 @@ class SQLTab(QWidget):
1219
1248
  self._script_worker.error.connect(self._on_query_error)
1220
1249
  self._script_worker.start()
1221
1250
 
1251
+ def _enter_script_mode(self, results: List) -> None:
1252
+ """Switch to script sub-tab layout."""
1253
+ self._in_script_mode = True
1254
+ self._script_results = results
1255
+
1256
+ # Hide single-query controls and table
1257
+ self._results_controls.hide()
1258
+ self.results_table.hide()
1259
+
1260
+ # Clear previous sub-tabs
1261
+ try:
1262
+ self._script_sub_tabs.currentChanged.disconnect(self._on_script_sub_tab_changed)
1263
+ except TypeError:
1264
+ pass
1265
+ while self._script_sub_tabs.count():
1266
+ w = self._script_sub_tabs.widget(0)
1267
+ self._script_sub_tabs.removeTab(0)
1268
+ w.deleteLater()
1269
+
1270
+ # Summary tab
1271
+ summary_table = ResultsTable()
1272
+ summary_table.setSortingEnabled(False)
1273
+ summary_table.setColumnCount(4)
1274
+ summary_table.setHorizontalHeaderLabels(["#", "SQL", "Result", "Time"])
1275
+ summary_table.setRowCount(len(results))
1276
+ for i, r in enumerate(results):
1277
+ summary_table.setItem(i, 0, QTableWidgetItem(str(r["stmt"])))
1278
+ summary_table.setItem(i, 1, QTableWidgetItem(r["sql"]))
1279
+ status_item = QTableWidgetItem(r["status"])
1280
+ if not r["success"]:
1281
+ status_item.setForeground(QColor(255, 80, 80))
1282
+ summary_table.setItem(i, 2, status_item)
1283
+ summary_table.setItem(i, 3, QTableWidgetItem(f"{r['time']:.3f}s"))
1284
+ summary_table.resizeColumnsToContents()
1285
+ for col in range(summary_table.columnCount()):
1286
+ if summary_table.columnWidth(col) > 400:
1287
+ summary_table.setColumnWidth(col, 400)
1288
+ summary_table.setSortingEnabled(True)
1289
+ self._script_sub_tabs.addTab(summary_table, "Summary")
1290
+
1291
+ # Per-SELECT result tabs
1292
+ for r in results:
1293
+ if r["rows"] is not None and r["description"] is not None:
1294
+ table = ResultsTable()
1295
+ table.load_results(r["rows"], r["description"])
1296
+ label = f"#{r['stmt']} {r['sql'][:40]}"
1297
+ self._script_sub_tabs.addTab(table, label)
1298
+
1299
+ # Connect tab-changed signal for Fields updates
1300
+ self._script_sub_tabs.currentChanged.connect(self._on_script_sub_tab_changed)
1301
+
1302
+ self._script_sub_tabs.show()
1303
+
1304
+ def _exit_script_mode(self) -> None:
1305
+ """Restore single-query layout."""
1306
+ if not self._in_script_mode:
1307
+ return
1308
+ self._in_script_mode = False
1309
+ self._script_results = []
1310
+
1311
+ # Clear and hide sub-tabs
1312
+ self._script_sub_tabs.hide()
1313
+ while self._script_sub_tabs.count():
1314
+ w = self._script_sub_tabs.widget(0)
1315
+ self._script_sub_tabs.removeTab(0)
1316
+ w.deleteLater()
1317
+
1318
+ # Restore single-query widgets
1319
+ self._results_controls.show()
1320
+ self.results_table.show()
1321
+
1322
+ def _on_script_sub_tab_changed(self, index: int) -> None:
1323
+ """Update Fields tab when a script sub-tab is selected."""
1324
+ if index == 0:
1325
+ # Summary tab — clear fields
1326
+ self._update_fields(None)
1327
+ return
1328
+ # Map sub-tab index to the result that has rows
1329
+ select_idx = 0
1330
+ for r in self._script_results:
1331
+ if r["rows"] is not None and r["description"] is not None:
1332
+ select_idx += 1
1333
+ if select_idx == index:
1334
+ self._update_fields(r["description"])
1335
+ return
1336
+ self._update_fields(None)
1337
+
1222
1338
  def _on_script_finished(self, results: List, total_time: float) -> None:
1223
1339
  """Handle script execution completion."""
1224
1340
  self._reset_buttons()
@@ -1237,29 +1353,8 @@ class SQLTab(QWidget):
1237
1353
  except Exception:
1238
1354
  pass
1239
1355
 
1240
- # Display results in table with script columns
1241
- self._loading_results = True
1242
- self.results_table.clear()
1243
- self.results_table.setSortingEnabled(False)
1244
- self.results_table.setColumnCount(4)
1245
- self.results_table.setHorizontalHeaderLabels(["#", "SQL", "Result", "Time"])
1246
- self.results_table.setRowCount(len(results))
1247
-
1248
- for i, r in enumerate(results):
1249
- self.results_table.setItem(i, 0, QTableWidgetItem(str(r["stmt"])))
1250
- self.results_table.setItem(i, 1, QTableWidgetItem(r["sql"]))
1251
- status_item = QTableWidgetItem(r["status"])
1252
- if not r["success"]:
1253
- status_item.setForeground(QColor(255, 80, 80))
1254
- self.results_table.setItem(i, 2, status_item)
1255
- self.results_table.setItem(i, 3, QTableWidgetItem(f"{r['time']:.3f}s"))
1256
-
1257
- self.results_table.resizeColumnsToContents()
1258
- for col in range(self.results_table.columnCount()):
1259
- if self.results_table.columnWidth(col) > 400:
1260
- self.results_table.setColumnWidth(col, 400)
1261
- self.results_table.setSortingEnabled(True)
1262
- self._loading_results = False
1356
+ # Build script sub-tabs (summary + per-SELECT result tabs)
1357
+ self._enter_script_mode(results)
1263
1358
 
1264
1359
  # Update statistics
1265
1360
  success = sum(1 for r in results if r["success"])
@@ -1370,6 +1465,7 @@ class SQLTab(QWidget):
1370
1465
  exec_time: float, fetch_time: float,
1371
1466
  total_rows: int = 0) -> None:
1372
1467
  """Handle query completion."""
1468
+ self._exit_script_mode()
1373
1469
  self._reset_buttons()
1374
1470
 
1375
1471
  # Log to database
@@ -1561,6 +1657,7 @@ class SQLTab(QWidget):
1561
1657
  exec_time: float, fetch_time: float,
1562
1658
  total_rows: int = 0) -> None:
1563
1659
  """Handle page load completion."""
1660
+ self._exit_script_mode()
1564
1661
  self._reset_buttons()
1565
1662
 
1566
1663
  self._loading_results = True
@@ -4,7 +4,7 @@ import threading
4
4
  import urllib.request
5
5
  import json
6
6
 
7
- __version__ = "0.1.59"
7
+ __version__ = "0.1.61"
8
8
 
9
9
 
10
10
  def get_installed_version():
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes