supervertaler 1.9.185__py3-none-any.whl → 1.9.196__py3-none-any.whl

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.

Potentially problematic release.


This version of supervertaler might be problematic. Click here for more details.

Supervertaler.py CHANGED
@@ -32,9 +32,9 @@ License: MIT
32
32
  """
33
33
 
34
34
  # Version Information.
35
- __version__ = "1.9.185"
35
+ __version__ = "1.9.196"
36
36
  __phase__ = "0.9"
37
- __release_date__ = "2026-02-01"
37
+ __release_date__ = "2026-02-02"
38
38
  __edition__ = "Qt"
39
39
 
40
40
  import sys
@@ -2298,10 +2298,15 @@ class ReadOnlyGridTextEditor(QTextEdit):
2298
2298
 
2299
2299
  # Superlookup search action
2300
2300
  if self.textCursor().hasSelection():
2301
- superlookup_action = QAction("🔍 Search in Superlookup (Ctrl+K)", self)
2301
+ superlookup_action = QAction("🔍 Search in SuperLookup (Ctrl+K)", self)
2302
2302
  superlookup_action.triggered.connect(self._handle_superlookup_search)
2303
2303
  menu.addAction(superlookup_action)
2304
- menu.addSeparator()
2304
+
2305
+ # MT Quick Lookup action
2306
+ mt_lookup_action = QAction("⚡ QuickTrans (Ctrl+M)", self)
2307
+ mt_lookup_action.triggered.connect(self._handle_mt_quick_lookup)
2308
+ menu.addAction(mt_lookup_action)
2309
+ menu.addSeparator()
2305
2310
 
2306
2311
  # QuickMenu (prompt-based actions)
2307
2312
  try:
@@ -2355,7 +2360,7 @@ class ReadOnlyGridTextEditor(QTextEdit):
2355
2360
  """Handle Ctrl+Alt+N: Add selected text to active non-translatable list(s)"""
2356
2361
  # Get selected text
2357
2362
  selected_text = self.textCursor().selectedText().strip()
2358
-
2363
+
2359
2364
  if not selected_text:
2360
2365
  from PyQt6.QtWidgets import QMessageBox
2361
2366
  QMessageBox.warning(
@@ -2364,14 +2369,14 @@ class ReadOnlyGridTextEditor(QTextEdit):
2364
2369
  "Please select text in the Source cell before adding to non-translatables."
2365
2370
  )
2366
2371
  return
2367
-
2372
+
2368
2373
  # Find main window and call add_to_nt method
2369
2374
  table = self.table_ref if hasattr(self, 'table_ref') else self.parent()
2370
2375
  if table:
2371
2376
  main_window = table.parent()
2372
2377
  while main_window and not hasattr(main_window, 'add_text_to_non_translatables'):
2373
2378
  main_window = main_window.parent()
2374
-
2379
+
2375
2380
  if main_window and hasattr(main_window, 'add_text_to_non_translatables'):
2376
2381
  main_window.add_text_to_non_translatables(selected_text)
2377
2382
  else:
@@ -2382,6 +2387,16 @@ class ReadOnlyGridTextEditor(QTextEdit):
2382
2387
  "Non-translatables functionality not available."
2383
2388
  )
2384
2389
 
2390
+ def _handle_mt_quick_lookup(self):
2391
+ """Handle right-click: Open MT Quick Lookup popup for selected text or full source"""
2392
+ # Get selected text (if any)
2393
+ selected_text = self.textCursor().selectedText().strip() if self.textCursor().hasSelection() else None
2394
+
2395
+ # Find main window and call show_mt_quick_popup
2396
+ main_window = self._get_main_window()
2397
+ if main_window and hasattr(main_window, 'show_mt_quick_popup'):
2398
+ main_window.show_mt_quick_popup(text_override=selected_text)
2399
+
2385
2400
  def set_background_color(self, color: str):
2386
2401
  """Set the background color for this text editor (for alternating row colors)"""
2387
2402
  self.setStyleSheet(f"""
@@ -2991,10 +3006,15 @@ class EditableGridTextEditor(QTextEdit):
2991
3006
 
2992
3007
  # Superlookup search action
2993
3008
  if self.textCursor().hasSelection():
2994
- superlookup_action = QAction("🔍 Search in Superlookup (Ctrl+K)", self)
3009
+ superlookup_action = QAction("🔍 Search in SuperLookup (Ctrl+K)", self)
2995
3010
  superlookup_action.triggered.connect(self._handle_superlookup_search)
2996
3011
  menu.addAction(superlookup_action)
2997
- menu.addSeparator()
3012
+
3013
+ # MT Quick Lookup action
3014
+ mt_lookup_action = QAction("⚡ QuickTrans (Ctrl+M)", self)
3015
+ mt_lookup_action.triggered.connect(self._handle_mt_quick_lookup)
3016
+ menu.addAction(mt_lookup_action)
3017
+ menu.addSeparator()
2998
3018
 
2999
3019
  # QuickMenu (prompt-based actions)
3000
3020
  try:
@@ -3507,6 +3527,24 @@ class EditableGridTextEditor(QTextEdit):
3507
3527
  "Non-translatables functionality not available."
3508
3528
  )
3509
3529
 
3530
+ def _handle_mt_quick_lookup(self):
3531
+ """Handle right-click: Open MT Quick Lookup popup for selected text or full source"""
3532
+ # Get selected text (if any) - prefer from target, then try source
3533
+ selected_text = self.textCursor().selectedText().strip() if self.textCursor().hasSelection() else None
3534
+
3535
+ if not selected_text and self.table and self.row >= 0:
3536
+ # Try getting selected text from source cell
3537
+ source_widget = self.table.cellWidget(self.row, 2)
3538
+ if source_widget and hasattr(source_widget, 'textCursor'):
3539
+ cursor = source_widget.textCursor()
3540
+ if cursor.hasSelection():
3541
+ selected_text = cursor.selectedText().strip()
3542
+
3543
+ # Find main window and call show_mt_quick_popup
3544
+ main_window = self._get_main_window()
3545
+ if main_window and hasattr(main_window, 'show_mt_quick_popup'):
3546
+ main_window.show_mt_quick_popup(text_override=selected_text)
3547
+
3510
3548
  def _insert_next_tag_or_wrap_selection(self):
3511
3549
  """
3512
3550
  Insert the next memoQ tag, HTML tag, or CafeTran pipe symbol from source, or wrap selection.
@@ -6435,7 +6473,7 @@ class SupervertalerQt(QMainWindow):
6435
6473
 
6436
6474
  # Restore Termview under grid visibility state
6437
6475
  if hasattr(self, 'bottom_tabs'):
6438
- termview_visible = general_settings.get('termview_under_grid_visible', True)
6476
+ termview_visible = general_settings.get('termview_under_grid_visible', False)
6439
6477
  self.bottom_tabs.setVisible(termview_visible)
6440
6478
  if hasattr(self, 'termview_visible_action'):
6441
6479
  self.termview_visible_action.setChecked(termview_visible)
@@ -7283,7 +7321,12 @@ class SupervertalerQt(QMainWindow):
7283
7321
 
7284
7322
  # Alt+K - Open QuickMenu directly
7285
7323
  create_shortcut("editor_open_quickmenu", "Alt+K", self.open_quickmenu)
7286
-
7324
+
7325
+ # Ctrl+Shift+Q - MT Quick Lookup (GT4T-style popup)
7326
+ mt_quick_shortcut = create_shortcut("mt_quick_lookup", "Ctrl+Shift+Q", self.show_mt_quick_popup)
7327
+ # Use ApplicationShortcut context so it works even when focus is in QTextEdit widgets
7328
+ mt_quick_shortcut.setContext(Qt.ShortcutContext.ApplicationShortcut)
7329
+
7287
7330
  def focus_segment_notes(self):
7288
7331
  """Switch to Segment Note tab and focus the notes editor so user can start typing immediately"""
7289
7332
  if not hasattr(self, 'right_tabs'):
@@ -7355,7 +7398,105 @@ class SupervertalerQt(QMainWindow):
7355
7398
 
7356
7399
  except Exception as e:
7357
7400
  self.log(f"❌ Error opening QuickMenu: {e}")
7358
-
7401
+
7402
+ def show_mt_quick_popup(self, text_override: str = None):
7403
+ """Show GT4T-style MT Quick Lookup popup with translations from all enabled MT engines.
7404
+
7405
+ Triggered by Ctrl+Shift+Q or right-click menu. Shows machine translation
7406
+ suggestions from all configured and enabled MT providers in a popup window.
7407
+
7408
+ Args:
7409
+ text_override: Optional text to translate. If None, uses selected text
7410
+ in the current cell, or falls back to the full source text.
7411
+
7412
+ Features:
7413
+ - Displays source text at top
7414
+ - Shows numbered list of MT suggestions from each provider
7415
+ - Press 1-9 to quickly insert a translation
7416
+ - Arrow keys to navigate, Enter to insert selected
7417
+ - Escape to dismiss
7418
+ """
7419
+ try:
7420
+ # Get current segment
7421
+ current_row = self.table.currentRow()
7422
+ if current_row < 0:
7423
+ self.log("⚠️ No segment selected")
7424
+ return
7425
+
7426
+ # Determine what text to translate
7427
+ text_to_translate = text_override
7428
+
7429
+ if not text_to_translate:
7430
+ # Check for selected text in the currently focused widget
7431
+ focus_widget = QApplication.focusWidget()
7432
+ if focus_widget and hasattr(focus_widget, 'textCursor'):
7433
+ cursor = focus_widget.textCursor()
7434
+ if cursor.hasSelection():
7435
+ text_to_translate = cursor.selectedText().strip()
7436
+
7437
+ if not text_to_translate:
7438
+ # Fall back to full source text
7439
+ source_widget = self.table.cellWidget(current_row, 2)
7440
+ if not source_widget or not hasattr(source_widget, 'toPlainText'):
7441
+ self.log("⚠️ Could not get source text")
7442
+ return
7443
+ text_to_translate = source_widget.toPlainText().strip()
7444
+
7445
+ if not text_to_translate:
7446
+ self.log("⚠️ No text to translate")
7447
+ return
7448
+
7449
+ # Import and create the popup
7450
+ from modules.quicktrans import MTQuickPopup
7451
+
7452
+ # Create popup
7453
+ popup = MTQuickPopup(
7454
+ parent_app=self,
7455
+ source_text=text_to_translate,
7456
+ source_lang=getattr(self, 'source_language', 'en'),
7457
+ target_lang=getattr(self, 'target_language', 'nl'),
7458
+ parent=self
7459
+ )
7460
+
7461
+ # Connect signal to insert translation into target cell
7462
+ def insert_translation(translation: str):
7463
+ # Get the target widget
7464
+ target_widget = self.table.cellWidget(current_row, 3)
7465
+ if not target_widget or not hasattr(target_widget, 'toPlainText'):
7466
+ return
7467
+
7468
+ # Check if there was a selection in the target - replace just that
7469
+ focus_widget = QApplication.focusWidget()
7470
+ if focus_widget == target_widget and hasattr(focus_widget, 'textCursor'):
7471
+ cursor = focus_widget.textCursor()
7472
+ if cursor.hasSelection():
7473
+ # Replace selection only
7474
+ cursor.insertText(translation)
7475
+ self.log(f"✅ Replaced selection with MT translation")
7476
+ else:
7477
+ # No selection in target, replace entire target
7478
+ target_widget.setPlainText(translation)
7479
+ self.log(f"✅ Inserted MT translation")
7480
+ else:
7481
+ # Focus was elsewhere, replace entire target
7482
+ target_widget.setPlainText(translation)
7483
+ self.log(f"✅ Inserted MT translation")
7484
+
7485
+ # Mark segment as modified
7486
+ if hasattr(self, 'segments') and current_row < len(self.segments):
7487
+ self.segments[current_row].target = target_widget.toPlainText()
7488
+ self.mark_segment_modified(current_row)
7489
+
7490
+ popup.translation_selected.connect(insert_translation)
7491
+
7492
+ # Show the popup (it positions itself near cursor)
7493
+ popup.show()
7494
+
7495
+ except ImportError as e:
7496
+ self.log(f"❌ MT Quick Popup module not found: {e}")
7497
+ except Exception as e:
7498
+ self.log(f"❌ Error showing MT Quick Popup: {e}")
7499
+
7359
7500
  def refresh_shortcut_enabled_states(self):
7360
7501
  """Refresh enabled/disabled states and key bindings of all global shortcuts from shortcut manager.
7361
7502
 
@@ -7991,7 +8132,7 @@ class SupervertalerQt(QMainWindow):
7991
8132
  edit_menu.addSeparator()
7992
8133
 
7993
8134
  # Superlookup
7994
- superlookup_action = QAction("🔍 &Superlookup...", self)
8135
+ superlookup_action = QAction("🔍 &SuperLookup...", self)
7995
8136
  superlookup_action.setShortcut("Ctrl+Alt+L")
7996
8137
  # Tab indices: Grid=0, Project resources=1, Tools=2, Settings=3
7997
8138
  superlookup_action.triggered.connect(lambda: self._go_to_superlookup() if hasattr(self, 'main_tabs') else None) # Navigate to Superlookup
@@ -8098,7 +8239,7 @@ class SupervertalerQt(QMainWindow):
8098
8239
  # Termview visibility toggle
8099
8240
  self.termview_visible_action = QAction("🔍 &Termview Under Grid", self)
8100
8241
  self.termview_visible_action.setCheckable(True)
8101
- self.termview_visible_action.setChecked(True) # Default: visible
8242
+ self.termview_visible_action.setChecked(False) # Default: hidden (restored from settings if enabled)
8102
8243
  self.termview_visible_action.triggered.connect(self.toggle_termview_under_grid)
8103
8244
  self.termview_visible_action.setToolTip("Show/hide the Termview panel under the grid")
8104
8245
  view_menu.addAction(self.termview_visible_action)
@@ -10138,7 +10279,7 @@ class SupervertalerQt(QMainWindow):
10138
10279
 
10139
10280
  # Create detached window
10140
10281
  self.lookup_detached_window = QDialog(self)
10141
- self.lookup_detached_window.setWindowTitle("🔍 Superlookup - Supervertaler")
10282
+ self.lookup_detached_window.setWindowTitle("🔍 SuperLookup - Supervertaler")
10142
10283
  self.lookup_detached_window.setMinimumSize(600, 700)
10143
10284
  self.lookup_detached_window.resize(700, 800)
10144
10285
 
@@ -10192,7 +10333,7 @@ class SupervertalerQt(QMainWindow):
10192
10333
  # Header with reattach button
10193
10334
  header_layout = QVBoxLayout()
10194
10335
 
10195
- header_title = QLabel("🔍 Superlookup")
10336
+ header_title = QLabel("🔍 SuperLookup")
10196
10337
  header_title.setStyleSheet("font-size: 16px; font-weight: bold; color: #333;")
10197
10338
  header_layout.addWidget(header_title)
10198
10339
 
@@ -10346,7 +10487,7 @@ class SupervertalerQt(QMainWindow):
10346
10487
 
10347
10488
  lookup_tab = SuperlookupTab(self, user_data_path=self.user_data_path)
10348
10489
  self.lookup_tab = lookup_tab # Store reference for later use
10349
- modules_tabs.addTab(lookup_tab, "🔍 Superlookup")
10490
+ modules_tabs.addTab(lookup_tab, "🔍 SuperLookup")
10350
10491
 
10351
10492
  # Supervoice - Voice Commands & Dictation
10352
10493
  supervoice_tab = self._create_voice_dictation_settings_tab()
@@ -14995,7 +15136,7 @@ class SupervertalerQt(QMainWindow):
14995
15136
 
14996
15137
  # Delete selected term button
14997
15138
  delete_term_btn = QPushButton("🗑️ Delete Selected Term")
14998
- delete_term_btn.setStyleSheet("background-color: #f44336; color: white; font-weight: bold;")
15139
+ delete_term_btn.setStyleSheet("background-color: #f44336; color: white; font-weight: bold; padding: 3px 5px;")
14999
15140
  def delete_selected_term():
15000
15141
  selected_row = terms_table.currentRow()
15001
15142
  if selected_row < 0:
@@ -15669,8 +15810,13 @@ class SupervertalerQt(QMainWindow):
15669
15810
  # ===== TAB 4: MT Settings =====
15670
15811
  mt_tab = self._create_mt_settings_tab()
15671
15812
  settings_tabs.addTab(scroll_area_wrapper(mt_tab), "🌐 MT Settings")
15672
-
15673
- # ===== TAB 5: View Settings =====
15813
+
15814
+ # ===== TAB 5: MT Quick Lookup Settings =====
15815
+ mt_quick_tab = self._create_mt_quick_lookup_settings_tab()
15816
+ settings_tabs.addTab(scroll_area_wrapper(mt_quick_tab), "⚡ QuickTrans")
15817
+ self.mt_quick_lookup_tab_index = settings_tabs.count() - 1 # Store index for opening
15818
+
15819
+ # ===== TAB 6: View Settings =====
15674
15820
  view_tab = self._create_view_settings_tab()
15675
15821
  settings_tabs.addTab(scroll_area_wrapper(view_tab), "🔍 View Settings")
15676
15822
 
@@ -16478,9 +16624,199 @@ class SupervertalerQt(QMainWindow):
16478
16624
  layout.addWidget(save_btn)
16479
16625
 
16480
16626
  layout.addStretch()
16481
-
16627
+
16482
16628
  return tab
16483
-
16629
+
16630
+ def _create_mt_quick_lookup_settings_tab(self):
16631
+ """Create MT Quick Lookup settings tab content"""
16632
+ from PyQt6.QtWidgets import QCheckBox, QGroupBox, QPushButton, QComboBox
16633
+
16634
+ tab = QWidget()
16635
+ layout = QVBoxLayout(tab)
16636
+ layout.setContentsMargins(20, 20, 20, 20)
16637
+ layout.setSpacing(15)
16638
+
16639
+ # Load current settings
16640
+ general_settings = self.load_general_settings()
16641
+ mt_quick_settings = general_settings.get('mt_quick_lookup', {})
16642
+ api_keys = self.load_api_keys()
16643
+ enabled_providers = self.load_provider_enabled_states()
16644
+
16645
+ # Header info
16646
+ header_info = QLabel(
16647
+ "⚡ <b>QuickTrans</b> - Configure which providers appear in the QuickTrans popup (Ctrl+M / Ctrl+Alt+M).<br>"
16648
+ "Enable MT engines and/or LLMs to get instant translation suggestions."
16649
+ )
16650
+ header_info.setTextFormat(Qt.TextFormat.RichText)
16651
+ header_info.setStyleSheet("font-size: 9pt; color: #444; padding: 10px; background-color: #E3F2FD; border-radius: 4px;")
16652
+ header_info.setWordWrap(True)
16653
+ layout.addWidget(header_info)
16654
+
16655
+ # ===== MT Providers Group =====
16656
+ mt_group = QGroupBox("🌐 Machine Translation Providers")
16657
+ mt_layout = QVBoxLayout()
16658
+
16659
+ mt_info = QLabel("Select which MT engines to query. Only enabled providers with valid API keys are shown.")
16660
+ mt_info.setWordWrap(True)
16661
+ mt_info.setStyleSheet("font-size: 8pt; color: #666; padding-bottom: 8px;")
16662
+ mt_layout.addWidget(mt_info)
16663
+
16664
+ # MT provider checkboxes
16665
+ self._mtql_checkboxes = {}
16666
+
16667
+ mt_providers = [
16668
+ ("gt", "Google Translate", "mt_google_translate", "google_translate"),
16669
+ ("dl", "DeepL", "mt_deepl", "deepl"),
16670
+ ("ms", "Microsoft Translator", "mt_microsoft", "microsoft_translate"),
16671
+ ("at", "Amazon Translate", "mt_amazon", "amazon_translate"),
16672
+ ("mmt", "ModernMT", "mt_modernmt", "modernmt"),
16673
+ ("mm", "MyMemory (Free)", "mt_mymemory", None),
16674
+ ]
16675
+
16676
+ for code, name, enabled_key, api_key_name in mt_providers:
16677
+ # Check if provider is available (has API key or doesn't need one)
16678
+ has_key = api_key_name is None or bool(api_keys.get(api_key_name))
16679
+ is_enabled_globally = enabled_providers.get(enabled_key, True)
16680
+
16681
+ checkbox = CheckmarkCheckBox(name)
16682
+ # Default: use global MT enabled state
16683
+ checkbox.setChecked(mt_quick_settings.get(f"mtql_{code}", is_enabled_globally and has_key))
16684
+ checkbox.setEnabled(has_key)
16685
+
16686
+ if not has_key:
16687
+ checkbox.setToolTip(f"API key not configured for {name}")
16688
+ checkbox.setStyleSheet("color: #999;")
16689
+ else:
16690
+ checkbox.setToolTip(f"Include {name} in QuickTrans results")
16691
+
16692
+ self._mtql_checkboxes[f"mtql_{code}"] = checkbox
16693
+ mt_layout.addWidget(checkbox)
16694
+
16695
+ mt_group.setLayout(mt_layout)
16696
+ layout.addWidget(mt_group)
16697
+
16698
+ # ===== LLM Providers Group =====
16699
+ llm_group = QGroupBox("🤖 AI/LLM Providers")
16700
+ llm_layout = QVBoxLayout()
16701
+
16702
+ llm_info = QLabel(
16703
+ "Enable AI models for translation suggestions. LLMs may provide more context-aware translations but are slower.<br>"
16704
+ "<b>Note:</b> LLM calls cost more than MT APIs. Use sparingly for quick lookups."
16705
+ )
16706
+ llm_info.setTextFormat(Qt.TextFormat.RichText)
16707
+ llm_info.setWordWrap(True)
16708
+ llm_info.setStyleSheet("font-size: 8pt; color: #666; padding-bottom: 8px;")
16709
+ llm_layout.addWidget(llm_info)
16710
+
16711
+ # LLM provider checkboxes with model selection
16712
+ self._mtql_llm_combos = {}
16713
+
16714
+ llm_providers = [
16715
+ ("claude", "Claude", "claude", [
16716
+ ("claude-sonnet-4-5-20250929", "Claude Sonnet 4.5 (Recommended)"),
16717
+ ("claude-haiku-4-5-20251001", "Claude Haiku 4.5 (Fast)"),
16718
+ ("claude-opus-4-1-20250924", "Claude Opus 4.1 (Premium)"),
16719
+ ]),
16720
+ ("openai", "OpenAI", "openai", [
16721
+ ("gpt-4o", "GPT-4o (Recommended)"),
16722
+ ("gpt-4o-mini", "GPT-4o Mini (Fast)"),
16723
+ ("gpt-4-turbo", "GPT-4 Turbo"),
16724
+ ("o1", "o1 (Reasoning)"),
16725
+ ]),
16726
+ ("gemini", "Gemini", "gemini", [
16727
+ ("gemini-2.5-flash", "Gemini 2.5 Flash (Recommended)"),
16728
+ ("gemini-2.5-pro", "Gemini 2.5 Pro"),
16729
+ ("gemini-2.0-flash", "Gemini 2.0 Flash"),
16730
+ ]),
16731
+ ]
16732
+
16733
+ for code, name, api_key_name, models in llm_providers:
16734
+ has_key = bool(api_keys.get(api_key_name))
16735
+
16736
+ # Container for checkbox and model combo
16737
+ llm_row = QHBoxLayout()
16738
+
16739
+ checkbox = CheckmarkCheckBox(name)
16740
+ # Default: disabled (LLMs are opt-in)
16741
+ checkbox.setChecked(mt_quick_settings.get(f"mtql_{code}", False))
16742
+ checkbox.setEnabled(has_key)
16743
+
16744
+ if not has_key:
16745
+ checkbox.setToolTip(f"API key not configured for {name}. Add it in AI Settings.")
16746
+ checkbox.setStyleSheet("color: #999;")
16747
+ else:
16748
+ checkbox.setToolTip(f"Include {name} translations in QuickTrans")
16749
+
16750
+ self._mtql_checkboxes[f"mtql_{code}"] = checkbox
16751
+ llm_row.addWidget(checkbox)
16752
+
16753
+ # Model selection combo
16754
+ model_combo = QComboBox()
16755
+ model_combo.setMinimumWidth(200)
16756
+ for model_id, model_name in models:
16757
+ model_combo.addItem(model_name, model_id)
16758
+
16759
+ # Restore saved model selection
16760
+ saved_model = mt_quick_settings.get(f"mtql_{code}_model")
16761
+ if saved_model:
16762
+ idx = model_combo.findData(saved_model)
16763
+ if idx >= 0:
16764
+ model_combo.setCurrentIndex(idx)
16765
+
16766
+ model_combo.setEnabled(has_key)
16767
+ self._mtql_llm_combos[f"mtql_{code}_model"] = model_combo
16768
+ llm_row.addWidget(model_combo)
16769
+
16770
+ llm_row.addStretch()
16771
+ llm_layout.addLayout(llm_row)
16772
+
16773
+ llm_group.setLayout(llm_layout)
16774
+ layout.addWidget(llm_group)
16775
+
16776
+ # Save button
16777
+ save_btn = QPushButton("💾 Save QuickTrans Settings")
16778
+ save_btn.setStyleSheet("font-weight: bold; padding: 8px;")
16779
+ save_btn.clicked.connect(self._save_mt_quick_lookup_settings)
16780
+ layout.addWidget(save_btn)
16781
+
16782
+ layout.addStretch()
16783
+
16784
+ return tab
16785
+
16786
+ def _save_mt_quick_lookup_settings(self):
16787
+ """Save MT Quick Lookup settings"""
16788
+ general_settings = self.load_general_settings()
16789
+
16790
+ mt_quick_settings = {}
16791
+
16792
+ # Save MT provider states
16793
+ for key, checkbox in self._mtql_checkboxes.items():
16794
+ mt_quick_settings[key] = checkbox.isChecked()
16795
+
16796
+ # Save LLM model selections
16797
+ for key, combo in self._mtql_llm_combos.items():
16798
+ mt_quick_settings[key] = combo.currentData()
16799
+
16800
+ general_settings['mt_quick_lookup'] = mt_quick_settings
16801
+ self.save_general_settings(general_settings)
16802
+
16803
+ self.log("✓ QuickTrans settings saved")
16804
+ QMessageBox.information(self, "Settings Saved", "QuickTrans settings have been saved.")
16805
+
16806
+ def open_mt_quick_lookup_settings(self):
16807
+ """Open Settings and navigate to MT Quick Lookup tab"""
16808
+ # Switch to Settings tab
16809
+ if hasattr(self, 'main_tabs'):
16810
+ # Find Settings tab index
16811
+ for i in range(self.main_tabs.count()):
16812
+ if "Settings" in self.main_tabs.tabText(i):
16813
+ self.main_tabs.setCurrentIndex(i)
16814
+ break
16815
+
16816
+ # Navigate to MT Quick Lookup sub-tab
16817
+ if hasattr(self, 'settings_tabs') and hasattr(self, 'mt_quick_lookup_tab_index'):
16818
+ self.settings_tabs.setCurrentIndex(self.mt_quick_lookup_tab_index)
16819
+
16484
16820
  def _find_autohotkey_for_settings(self):
16485
16821
  """Find AutoHotkey executable for settings display (doesn't modify state)"""
16486
16822
  # Standard installation paths
@@ -17971,14 +18307,15 @@ class SupervertalerQt(QMainWindow):
17971
18307
 
17972
18308
  def _create_voice_dictation_settings_tab(self):
17973
18309
  """Create Supervoice Settings tab content with Voice Commands"""
17974
- from PyQt6.QtWidgets import (QGroupBox, QPushButton, QComboBox, QSpinBox,
18310
+ from PyQt6.QtWidgets import (QGroupBox, QPushButton, QComboBox, QSpinBox,
17975
18311
  QTableWidget, QTableWidgetItem, QHeaderView,
17976
- QAbstractItemView, QCheckBox)
18312
+ QAbstractItemView, QCheckBox, QSplitter)
18313
+ from modules.keyboard_shortcuts_widget import CheckmarkCheckBox
17977
18314
 
17978
18315
  tab = QWidget()
17979
- layout = QVBoxLayout(tab)
17980
- layout.setContentsMargins(20, 20, 20, 20)
17981
- layout.setSpacing(15)
18316
+ main_layout = QVBoxLayout(tab)
18317
+ main_layout.setContentsMargins(20, 20, 20, 20)
18318
+ main_layout.setSpacing(15)
17982
18319
 
17983
18320
  # Load current dictation settings
17984
18321
  dictation_settings = self.load_dictation_settings()
@@ -17991,19 +18328,33 @@ class SupervertalerQt(QMainWindow):
17991
18328
  header_info.setTextFormat(Qt.TextFormat.RichText)
17992
18329
  header_info.setStyleSheet("font-size: 9pt; color: #444; padding: 10px; background-color: #E3F2FD; border-radius: 4px;")
17993
18330
  header_info.setWordWrap(True)
17994
- layout.addWidget(header_info)
18331
+ main_layout.addWidget(header_info)
17995
18332
 
17996
- # ===== Voice Commands Section =====
17997
- commands_group = QGroupBox("🗣️ Voice Commands (Talon-style)")
17998
- commands_layout = QVBoxLayout()
18333
+ # ===== Two-column layout: Left = Settings, Right = Voice Commands Table =====
18334
+ columns_layout = QHBoxLayout()
18335
+ columns_layout.setSpacing(15)
17999
18336
 
18000
- # Enable voice commands checkbox
18001
- voice_cmd_enabled = QCheckBox("Enable voice commands (spoken phrases trigger actions)")
18337
+ # --- LEFT COLUMN: Settings ---
18338
+ left_column = QVBoxLayout()
18339
+ left_column.setSpacing(15)
18340
+
18341
+ # Enable voice commands checkbox (green checkmark style)
18342
+ voice_cmd_enabled = CheckmarkCheckBox("Enable voice commands (spoken phrases trigger actions)")
18002
18343
  voice_cmd_enabled.setChecked(dictation_settings.get('voice_commands_enabled', True))
18003
18344
  voice_cmd_enabled.setToolTip("When enabled, spoken phrases like 'confirm' or 'next segment' will execute commands instead of being inserted as text")
18004
- commands_layout.addWidget(voice_cmd_enabled)
18345
+ left_column.addWidget(voice_cmd_enabled)
18005
18346
  self.voice_commands_enabled_checkbox = voice_cmd_enabled
18006
18347
 
18348
+ # Create a layout variable for settings sections to be added below
18349
+ layout = left_column
18350
+
18351
+ # --- RIGHT COLUMN: Voice Commands Table ---
18352
+ right_column = QVBoxLayout()
18353
+ right_column.setSpacing(10)
18354
+
18355
+ commands_group = QGroupBox("🗣️ Voice Commands (Talon-style)")
18356
+ commands_layout = QVBoxLayout()
18357
+
18007
18358
  commands_info = QLabel(
18008
18359
  "Voice commands let you control Supervertaler by voice. Say a phrase to execute an action.\n"
18009
18360
  "If no command matches, the spoken text is inserted as dictation."
@@ -18022,38 +18373,47 @@ class SupervertalerQt(QMainWindow):
18022
18373
  self.voice_commands_table.horizontalHeader().setSectionResizeMode(3, QHeaderView.ResizeMode.ResizeToContents)
18023
18374
  self.voice_commands_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
18024
18375
  self.voice_commands_table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
18025
- self.voice_commands_table.setMinimumHeight(200)
18026
- self.voice_commands_table.setMaximumHeight(300)
18027
-
18376
+ self.voice_commands_table.setMinimumHeight(350)
18377
+
18028
18378
  # Populate table with current commands
18029
18379
  self._populate_voice_commands_table()
18030
18380
  commands_layout.addWidget(self.voice_commands_table)
18031
18381
 
18032
18382
  # Command buttons
18033
18383
  cmd_btn_layout = QHBoxLayout()
18034
-
18035
- add_cmd_btn = QPushButton("➕ Add Command")
18384
+
18385
+ add_cmd_btn = QPushButton("➕ Add")
18036
18386
  add_cmd_btn.clicked.connect(self._add_voice_command)
18037
18387
  cmd_btn_layout.addWidget(add_cmd_btn)
18038
-
18039
- edit_cmd_btn = QPushButton("✏️ Edit Command")
18388
+
18389
+ edit_cmd_btn = QPushButton("✏️ Edit")
18040
18390
  edit_cmd_btn.clicked.connect(self._edit_voice_command)
18041
18391
  cmd_btn_layout.addWidget(edit_cmd_btn)
18042
-
18043
- remove_cmd_btn = QPushButton("🗑️ Remove Command")
18392
+
18393
+ remove_cmd_btn = QPushButton("🗑️ Remove")
18044
18394
  remove_cmd_btn.clicked.connect(self._remove_voice_command)
18045
18395
  cmd_btn_layout.addWidget(remove_cmd_btn)
18046
-
18396
+
18047
18397
  cmd_btn_layout.addStretch()
18048
-
18049
- reset_cmd_btn = QPushButton("🔄 Reset to Defaults")
18398
+
18399
+ reset_cmd_btn = QPushButton("🔄 Reset")
18050
18400
  reset_cmd_btn.clicked.connect(self._reset_voice_commands)
18051
18401
  cmd_btn_layout.addWidget(reset_cmd_btn)
18052
-
18402
+
18053
18403
  commands_layout.addLayout(cmd_btn_layout)
18054
18404
 
18055
18405
  commands_group.setLayout(commands_layout)
18056
- layout.addWidget(commands_group)
18406
+ right_column.addWidget(commands_group)
18407
+ right_column.addStretch()
18408
+
18409
+ # Add columns to the two-column layout
18410
+ left_widget = QWidget()
18411
+ left_widget.setLayout(left_column)
18412
+ right_widget = QWidget()
18413
+ right_widget.setLayout(right_column)
18414
+
18415
+ columns_layout.addWidget(left_widget, stretch=1)
18416
+ columns_layout.addWidget(right_widget, stretch=1)
18057
18417
 
18058
18418
  # ===== Always-On Mode Section =====
18059
18419
  alwayson_group = QGroupBox("🎧 Always-On Listening Mode")
@@ -18216,7 +18576,13 @@ class SupervertalerQt(QMainWindow):
18216
18576
  ahk_group.setLayout(ahk_layout)
18217
18577
  layout.addWidget(ahk_group)
18218
18578
 
18219
- # Save button
18579
+ # Add stretch to left column to push content up
18580
+ layout.addStretch()
18581
+
18582
+ # Add two-column layout to main layout
18583
+ main_layout.addLayout(columns_layout, stretch=1)
18584
+
18585
+ # Save button (full width, below the two columns)
18220
18586
  save_btn = QPushButton("💾 Save Supervoice Settings")
18221
18587
  save_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 10px; border: none; outline: none;")
18222
18588
  save_btn.clicked.connect(lambda: self._save_voice_settings(
@@ -18225,15 +18591,13 @@ class SupervertalerQt(QMainWindow):
18225
18591
  lang_combo.currentText(),
18226
18592
  voice_cmd_enabled.isChecked()
18227
18593
  ))
18228
- layout.addWidget(save_btn)
18594
+ main_layout.addWidget(save_btn)
18229
18595
 
18230
18596
  # Store references
18231
18597
  self.dictation_model_combo = model_combo
18232
18598
  self.dictation_duration_spin = duration_spin
18233
18599
  self.dictation_lang_combo = lang_combo
18234
18600
 
18235
- layout.addStretch()
18236
-
18237
18601
  return tab
18238
18602
 
18239
18603
  def _populate_voice_commands_table(self):
@@ -18487,7 +18851,7 @@ class SupervertalerQt(QMainWindow):
18487
18851
  background-color: #2E7D32;
18488
18852
  color: white;
18489
18853
  font-weight: bold;
18490
- padding: 4px 8px;
18854
+ padding: 3px 5px;
18491
18855
  border-radius: 3px;
18492
18856
  }
18493
18857
  QPushButton:checked {
@@ -18501,7 +18865,7 @@ class SupervertalerQt(QMainWindow):
18501
18865
  background-color: #C62828;
18502
18866
  color: white;
18503
18867
  font-weight: bold;
18504
- padding: 4px 8px;
18868
+ padding: 3px 5px;
18505
18869
  border-radius: 3px;
18506
18870
  }
18507
18871
  QPushButton:checked {
@@ -18515,7 +18879,7 @@ class SupervertalerQt(QMainWindow):
18515
18879
  background-color: #F57C00;
18516
18880
  color: white;
18517
18881
  font-weight: bold;
18518
- padding: 4px 8px;
18882
+ padding: 3px 5px;
18519
18883
  border-radius: 3px;
18520
18884
  }
18521
18885
  QPushButton:checked {
@@ -18530,7 +18894,7 @@ class SupervertalerQt(QMainWindow):
18530
18894
  background-color: #757575;
18531
18895
  color: white;
18532
18896
  font-weight: bold;
18533
- padding: 4px 8px;
18897
+ padding: 3px 5px;
18534
18898
  border-radius: 3px;
18535
18899
  }
18536
18900
  QPushButton:checked {
@@ -19864,6 +20228,27 @@ class SupervertalerQt(QMainWindow):
19864
20228
  grid_font_family_combo=None, termview_font_family_combo=None, termview_font_spin=None, termview_bold_check=None,
19865
20229
  border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None):
19866
20230
  """Save view settings from UI"""
20231
+ # CRITICAL: Suppress TM saves during view settings update
20232
+ # Grid operations (setStyleSheet, rehighlight, etc.) can trigger textChanged events
20233
+ # which would cause mass TM saves for all confirmed segments
20234
+ previous_suppression = getattr(self, '_suppress_target_change_handlers', False)
20235
+ self._suppress_target_change_handlers = True
20236
+
20237
+ try:
20238
+ self._save_view_settings_from_ui_impl(
20239
+ grid_spin, match_spin, compare_spin, show_tags_check, tag_color_btn,
20240
+ alt_colors_check, even_color_btn, odd_color_btn, invisible_char_color_btn,
20241
+ grid_font_family_combo, termview_font_family_combo, termview_font_spin, termview_bold_check,
20242
+ border_color_btn, border_thickness_spin, badge_text_color_btn, tabs_above_check
20243
+ )
20244
+ finally:
20245
+ self._suppress_target_change_handlers = previous_suppression
20246
+
20247
+ def _save_view_settings_from_ui_impl(self, grid_spin, match_spin, compare_spin, show_tags_check=None, tag_color_btn=None,
20248
+ alt_colors_check=None, even_color_btn=None, odd_color_btn=None, invisible_char_color_btn=None,
20249
+ grid_font_family_combo=None, termview_font_family_combo=None, termview_font_spin=None, termview_bold_check=None,
20250
+ border_color_btn=None, border_thickness_spin=None, badge_text_color_btn=None, tabs_above_check=None):
20251
+ """Implementation of save view settings (called with TM saves suppressed)"""
19867
20252
  general_settings = {
19868
20253
  'restore_last_project': self.load_general_settings().get('restore_last_project', False),
19869
20254
  'auto_propagate_exact_matches': self.auto_propagate_exact_matches, # Keep existing value
@@ -20034,8 +20419,10 @@ class SupervertalerQt(QMainWindow):
20034
20419
  if invisible_char_color_btn and hasattr(self, 'table') and self.table is not None:
20035
20420
  invisible_char_color = invisible_char_color_btn.property('selected_color')
20036
20421
  if invisible_char_color:
20037
- # Update all cell highlighters
20422
+ # Update all cell highlighters (with processEvents to keep UI responsive)
20038
20423
  for row in range(self.table.rowCount()):
20424
+ if row % 50 == 0:
20425
+ QApplication.processEvents()
20039
20426
  for col in [2, 3]: # Source and target columns
20040
20427
  widget = self.table.cellWidget(row, col)
20041
20428
  if widget and hasattr(widget, 'highlighter'):
@@ -20047,8 +20434,10 @@ class SupervertalerQt(QMainWindow):
20047
20434
  border_color = EditableGridTextEditor.focus_border_color
20048
20435
  border_thickness = EditableGridTextEditor.focus_border_thickness
20049
20436
  self.log(f"Applying focus border: color={border_color}, thickness={border_thickness}px")
20050
-
20437
+
20051
20438
  for row in range(self.table.rowCount()):
20439
+ if row % 50 == 0:
20440
+ QApplication.processEvents()
20052
20441
  widget = self.table.cellWidget(row, 3) # Target column
20053
20442
  if widget and isinstance(widget, EditableGridTextEditor):
20054
20443
  # Update the stylesheet with new border settings
@@ -20154,7 +20543,7 @@ class SupervertalerQt(QMainWindow):
20154
20543
  filter_layout.setSpacing(10)
20155
20544
 
20156
20545
  # Source filter
20157
- source_filter_label = QLabel("Filter Source:")
20546
+ source_filter_label = QLabel("Source:")
20158
20547
  self.source_filter = self._ensure_shared_filter(
20159
20548
  'source_filter',
20160
20549
  "Type to filter source segments...",
@@ -20163,7 +20552,7 @@ class SupervertalerQt(QMainWindow):
20163
20552
  )
20164
20553
 
20165
20554
  # Target filter
20166
- target_filter_label = QLabel("Filter Target:")
20555
+ target_filter_label = QLabel("Target:")
20167
20556
  self.target_filter = self._ensure_shared_filter(
20168
20557
  'target_filter',
20169
20558
  "Type to filter target segments...",
@@ -20175,11 +20564,12 @@ class SupervertalerQt(QMainWindow):
20175
20564
  clear_filters_btn = QPushButton("Clear Filters")
20176
20565
  clear_filters_btn.clicked.connect(self.clear_filters)
20177
20566
  clear_filters_btn.setMaximumWidth(100)
20567
+ clear_filters_btn.setStyleSheet("padding: 3px 5px;")
20178
20568
 
20179
20569
  # Show Invisibles button with dropdown menu
20180
20570
  show_invisibles_btn = QPushButton("¶ Show Invisibles")
20181
20571
  show_invisibles_btn.setMaximumWidth(140)
20182
- show_invisibles_btn.setStyleSheet("background-color: #607D8B; color: white; font-weight: bold;")
20572
+ show_invisibles_btn.setStyleSheet("background-color: #607D8B; color: white; font-weight: bold; padding: 3px 5px;")
20183
20573
  show_invisibles_menu = QMenu(show_invisibles_btn)
20184
20574
 
20185
20575
  # Create checkable actions for each invisible character type
@@ -20304,7 +20694,7 @@ class SupervertalerQt(QMainWindow):
20304
20694
  filter_layout.setSpacing(10)
20305
20695
 
20306
20696
  # Source filter
20307
- source_filter_label = QLabel("Filter Source:")
20697
+ source_filter_label = QLabel("Source:")
20308
20698
  self.source_filter = self._ensure_shared_filter(
20309
20699
  'source_filter',
20310
20700
  "Type to filter source segments... (Press Enter or click Filter)",
@@ -20312,7 +20702,7 @@ class SupervertalerQt(QMainWindow):
20312
20702
  )
20313
20703
 
20314
20704
  # Target filter
20315
- target_filter_label = QLabel("Filter Target:")
20705
+ target_filter_label = QLabel("Target:")
20316
20706
  self.target_filter = self._ensure_shared_filter(
20317
20707
  'target_filter',
20318
20708
  "Type to filter target segments... (Press Enter or click Filter)",
@@ -20323,17 +20713,18 @@ class SupervertalerQt(QMainWindow):
20323
20713
  apply_filter_btn = QPushButton("Filter")
20324
20714
  apply_filter_btn.clicked.connect(self.apply_filters)
20325
20715
  apply_filter_btn.setMaximumWidth(80)
20326
- apply_filter_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
20716
+ apply_filter_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 3px 5px;")
20327
20717
 
20328
20718
  # Clear filters button
20329
20719
  clear_filters_btn = QPushButton("Clear Filters")
20330
20720
  clear_filters_btn.clicked.connect(self.clear_filters)
20331
20721
  clear_filters_btn.setMaximumWidth(100)
20722
+ clear_filters_btn.setStyleSheet("padding: 3px 5px;")
20332
20723
 
20333
20724
  # Quick Filters dropdown menu
20334
20725
  quick_filter_btn = QPushButton("⚡ Quick Filters")
20335
20726
  quick_filter_btn.setMaximumWidth(130)
20336
- quick_filter_btn.setStyleSheet("background-color: #D84315; color: white; font-weight: bold;")
20727
+ quick_filter_btn.setStyleSheet("background-color: #D84315; color: white; font-weight: bold; padding: 3px 5px;")
20337
20728
  quick_filter_menu = QMenu(self)
20338
20729
  quick_filter_menu.addAction("🔍 Empty segments", lambda: self.apply_quick_filter("empty"))
20339
20730
  quick_filter_menu.addAction("❌ Not translated", lambda: self.apply_quick_filter("not_translated"))
@@ -20347,8 +20738,69 @@ class SupervertalerQt(QMainWindow):
20347
20738
  advanced_filter_btn = QPushButton("⚙️ Advanced Filters")
20348
20739
  advanced_filter_btn.clicked.connect(self.show_advanced_filters_dialog)
20349
20740
  advanced_filter_btn.setMaximumWidth(160)
20350
- advanced_filter_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold;")
20351
-
20741
+ advanced_filter_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 3px 5px;")
20742
+
20743
+ # Sort dropdown button (similar to memoQ)
20744
+ sort_btn = QPushButton("⇅ Sort")
20745
+ sort_btn.setMaximumWidth(100)
20746
+ sort_btn.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold; padding: 3px 5px;")
20747
+ sort_menu = QMenu(self)
20748
+
20749
+ # Initialize sort state if not exists
20750
+ if not hasattr(self, 'current_sort'):
20751
+ self.current_sort = None # None = document order
20752
+
20753
+ # Sort by source text
20754
+ sort_menu.addAction("📝 Source A → Z", lambda: self.apply_sort('source_asc'))
20755
+ sort_menu.addAction("📝 Source Z → A", lambda: self.apply_sort('source_desc'))
20756
+
20757
+ sort_menu.addSeparator()
20758
+
20759
+ # Sort by target text
20760
+ sort_menu.addAction("📄 Target A → Z", lambda: self.apply_sort('target_asc'))
20761
+ sort_menu.addAction("📄 Target Z → A", lambda: self.apply_sort('target_desc'))
20762
+
20763
+ sort_menu.addSeparator()
20764
+
20765
+ # Sort by text length
20766
+ sort_menu.addAction("📏 Source (longer first)", lambda: self.apply_sort('source_length_desc'))
20767
+ sort_menu.addAction("📏 Source (shorter first)", lambda: self.apply_sort('source_length_asc'))
20768
+ sort_menu.addAction("📏 Target (longer first)", lambda: self.apply_sort('target_length_desc'))
20769
+ sort_menu.addAction("📏 Target (shorter first)", lambda: self.apply_sort('target_length_asc'))
20770
+
20771
+ sort_menu.addSeparator()
20772
+
20773
+ # Sort by match rate
20774
+ sort_menu.addAction("🎯 Match Rate (higher first)", lambda: self.apply_sort('match_desc'))
20775
+ sort_menu.addAction("🎯 Match Rate (lower first)", lambda: self.apply_sort('match_asc'))
20776
+
20777
+ sort_menu.addSeparator()
20778
+
20779
+ # Sort by frequency
20780
+ sort_menu.addAction("📊 Source Frequency (higher first)", lambda: self.apply_sort('source_freq_desc'))
20781
+ sort_menu.addAction("📊 Source Frequency (lower first)", lambda: self.apply_sort('source_freq_asc'))
20782
+ sort_menu.addAction("📊 Target Frequency (higher first)", lambda: self.apply_sort('target_freq_desc'))
20783
+ sort_menu.addAction("📊 Target Frequency (lower first)", lambda: self.apply_sort('target_freq_asc'))
20784
+
20785
+ sort_menu.addSeparator()
20786
+
20787
+ # Sort by last changed
20788
+ sort_menu.addAction("🕒 Last Changed (newest first)", lambda: self.apply_sort('modified_desc'))
20789
+ sort_menu.addAction("🕒 Last Changed (oldest first)", lambda: self.apply_sort('modified_asc'))
20790
+
20791
+ sort_menu.addSeparator()
20792
+
20793
+ # Sort by row status
20794
+ sort_menu.addAction("🚦 Row Status", lambda: self.apply_sort('status'))
20795
+
20796
+ sort_menu.addSeparator()
20797
+
20798
+ # Reset to document order
20799
+ sort_menu.addAction("↩️ Document Order (default)", lambda: self.apply_sort(None))
20800
+
20801
+ sort_btn.setMenu(sort_menu)
20802
+ sort_btn.setToolTip("Sort segments by various criteria")
20803
+
20352
20804
  # File filter dropdown (for multi-file projects)
20353
20805
  self.file_filter_combo = QComboBox()
20354
20806
  self.file_filter_combo.setMinimumWidth(150)
@@ -20361,7 +20813,7 @@ class SupervertalerQt(QMainWindow):
20361
20813
  # Show Invisibles button with dropdown menu
20362
20814
  show_invisibles_btn_home = QPushButton("¶ Show Invisibles")
20363
20815
  show_invisibles_btn_home.setMaximumWidth(140)
20364
- show_invisibles_btn_home.setStyleSheet("background-color: #607D8B; color: white; font-weight: bold;")
20816
+ show_invisibles_btn_home.setStyleSheet("background-color: #607D8B; color: white; font-weight: bold; padding: 3px 5px;")
20365
20817
  show_invisibles_menu_home = QMenu(show_invisibles_btn_home)
20366
20818
 
20367
20819
  # Use the same actions (they're stored as instance variables)
@@ -20442,6 +20894,7 @@ class SupervertalerQt(QMainWindow):
20442
20894
  filter_layout.addWidget(clear_filters_btn)
20443
20895
  filter_layout.addWidget(quick_filter_btn)
20444
20896
  filter_layout.addWidget(advanced_filter_btn)
20897
+ filter_layout.addWidget(sort_btn) # Sort dropdown
20445
20898
  filter_layout.addWidget(self.file_filter_combo) # File filter for multi-file projects
20446
20899
  filter_layout.addWidget(show_invisibles_btn_home)
20447
20900
  filter_layout.addWidget(self.spellcheck_btn)
@@ -20565,30 +21018,80 @@ class SupervertalerQt(QMainWindow):
20565
21018
  tab_seg_info.setStyleSheet("font-weight: bold;")
20566
21019
  toolbar_layout.addWidget(tab_seg_info)
20567
21020
 
20568
- # Tag View toggle button
20569
- tag_view_btn = QPushButton("🏷️ Tags OFF")
20570
- tag_view_btn.setCheckable(True)
20571
- tag_view_btn.setChecked(False) # Default: WYSIWYG mode (tags hidden)
20572
- tag_view_btn.setStyleSheet("""
21021
+ # View mode segmented control (WYSIWYG / Tags)
21022
+ from PyQt6.QtWidgets import QButtonGroup
21023
+
21024
+ view_mode_container = QWidget()
21025
+ view_mode_layout = QHBoxLayout(view_mode_container)
21026
+ view_mode_layout.setContentsMargins(0, 0, 0, 0)
21027
+ view_mode_layout.setSpacing(0)
21028
+
21029
+ # Create button group to ensure only one is checked
21030
+ view_mode_group = QButtonGroup(self)
21031
+ view_mode_group.setExclusive(True)
21032
+
21033
+ # WYSIWYG button (left)
21034
+ wysiwyg_btn = QPushButton("WYSIWYG")
21035
+ wysiwyg_btn.setCheckable(True)
21036
+ wysiwyg_btn.setChecked(False)
21037
+ wysiwyg_btn.setToolTip("WYSIWYG View (Ctrl+Alt+T)\nShows formatted text without raw tags")
21038
+ wysiwyg_btn.setStyleSheet("""
20573
21039
  QPushButton {
20574
21040
  background-color: #757575;
20575
21041
  color: white;
20576
21042
  font-weight: bold;
20577
- padding: 4px 8px;
20578
- border-radius: 3px;
21043
+ padding: 4px 12px;
21044
+ border: none;
21045
+ border-top-left-radius: 3px;
21046
+ border-bottom-left-radius: 3px;
21047
+ }
21048
+ QPushButton:checked {
21049
+ background-color: #9C27B0;
21050
+ }
21051
+ QPushButton:hover:!checked {
21052
+ background-color: #858585;
21053
+ }
21054
+ """)
21055
+ wysiwyg_btn.clicked.connect(lambda: self.toggle_tag_view(False, None))
21056
+ view_mode_group.addButton(wysiwyg_btn, 0)
21057
+ view_mode_layout.addWidget(wysiwyg_btn)
21058
+
21059
+ # Tags button (right)
21060
+ tags_btn = QPushButton("Tags")
21061
+ tags_btn.setCheckable(True)
21062
+ tags_btn.setChecked(True) # Default: Tags mode
21063
+ tags_btn.setToolTip("Tag View (Ctrl+Alt+T)\nShows raw tags like <b>bold</b>")
21064
+ tags_btn.setStyleSheet("""
21065
+ QPushButton {
21066
+ background-color: #757575;
21067
+ color: white;
21068
+ font-weight: bold;
21069
+ padding: 4px 12px;
21070
+ border: none;
21071
+ border-top-right-radius: 3px;
21072
+ border-bottom-right-radius: 3px;
20579
21073
  }
20580
21074
  QPushButton:checked {
20581
21075
  background-color: #9C27B0;
20582
21076
  }
21077
+ QPushButton:hover:!checked {
21078
+ background-color: #858585;
21079
+ }
20583
21080
  """)
20584
- tag_view_btn.setToolTip("Toggle Tag View (Ctrl+Alt+T)\n\nTags OFF: Shows formatted text (WYSIWYG)\nTags ON: Shows raw tags like <b>bold</b>")
20585
- tag_view_btn.clicked.connect(lambda checked: self.toggle_tag_view(checked, tag_view_btn))
20586
- toolbar_layout.addWidget(tag_view_btn)
20587
- self.tag_view_btn = tag_view_btn # Store reference
21081
+ tags_btn.clicked.connect(lambda: self.toggle_tag_view(True, None))
21082
+ view_mode_group.addButton(tags_btn, 1)
21083
+ view_mode_layout.addWidget(tags_btn)
21084
+
21085
+ toolbar_layout.addWidget(view_mode_container)
21086
+
21087
+ # Store references for keyboard shortcut and programmatic access
21088
+ self.wysiwyg_btn = wysiwyg_btn
21089
+ self.tags_btn = tags_btn
21090
+ self.view_mode_group = view_mode_group
20588
21091
 
20589
21092
  # Initialize tag view state
20590
21093
  if not hasattr(self, 'show_tags'):
20591
- self.show_tags = False
21094
+ self.show_tags = True # Default: show tags
20592
21095
 
20593
21096
  # Status selector
20594
21097
  from modules.statuses import get_status, STATUSES
@@ -20606,12 +21109,12 @@ class SupervertalerQt(QMainWindow):
20606
21109
 
20607
21110
  preview_prompt_btn = QPushButton("🧪 Preview Prompts")
20608
21111
  preview_prompt_btn.setToolTip("Preview the complete assembled prompt\n(System Prompt + Custom Prompts + current segment)")
20609
- preview_prompt_btn.setStyleSheet("background-color: #9C27B0; color: white; font-weight: bold; padding: 4px 8px; border: none; outline: none;")
21112
+ preview_prompt_btn.setStyleSheet("background-color: #9C27B0; color: white; font-weight: bold; padding: 3px 5px; border: none; outline: none;")
20610
21113
  preview_prompt_btn.clicked.connect(self._preview_combined_prompt_from_grid)
20611
21114
  toolbar_layout.addWidget(preview_prompt_btn)
20612
21115
 
20613
21116
  dictate_btn = QPushButton("🎤 Dictation")
20614
- dictate_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 4px 8px; border: none; outline: none;")
21117
+ dictate_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 3px 5px; border: none; outline: none;")
20615
21118
  dictate_btn.clicked.connect(self.start_voice_dictation)
20616
21119
  dictate_btn.setToolTip("Start/stop voice dictation (F9)")
20617
21120
  toolbar_layout.addWidget(dictate_btn)
@@ -20625,7 +21128,7 @@ class SupervertalerQt(QMainWindow):
20625
21128
  background-color: #757575;
20626
21129
  color: white;
20627
21130
  font-weight: bold;
20628
- padding: 4px 8px;
21131
+ padding: 3px 5px;
20629
21132
  border-radius: 3px;
20630
21133
  }
20631
21134
  QPushButton:checked {
@@ -20640,7 +21143,7 @@ class SupervertalerQt(QMainWindow):
20640
21143
  toolbar_layout.addStretch()
20641
21144
 
20642
21145
  save_next_btn = QPushButton("✓ Confirm && Next")
20643
- save_next_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 4px 8px; border: none; outline: none;")
21146
+ save_next_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 3px 5px; border: none; outline: none;")
20644
21147
  save_next_btn.clicked.connect(self.confirm_selected_or_next)
20645
21148
  save_next_btn.setToolTip("Confirm current segment and go to next unconfirmed (Ctrl+Enter)")
20646
21149
  toolbar_layout.addWidget(save_next_btn)
@@ -21249,9 +21752,21 @@ class SupervertalerQt(QMainWindow):
21249
21752
  # Configure columns
21250
21753
  self.table.setColumnCount(5)
21251
21754
  self.table.setHorizontalHeaderLabels(["#", "Type", "Source", "Target", "Status"])
21252
-
21253
- # Column widths - Source and Target columns stretch to fill space, others are interactive
21755
+
21756
+ # Explicitly set header font to normal weight (not bold)
21254
21757
  header = self.table.horizontalHeader()
21758
+ header_font = QFont(self.default_font_family, self.default_font_size, QFont.Weight.Normal)
21759
+ header.setFont(header_font)
21760
+
21761
+ # Also set font on individual header items through the model (extra insurance)
21762
+ model = self.table.model()
21763
+ if model:
21764
+ for col in range(5):
21765
+ item = model.headerData(col, Qt.Orientation.Horizontal, Qt.ItemDataRole.DisplayRole)
21766
+ model.setHeaderData(col, Qt.Orientation.Horizontal, item, Qt.ItemDataRole.DisplayRole)
21767
+ model.setHeaderData(col, Qt.Orientation.Horizontal, header_font, Qt.ItemDataRole.FontRole)
21768
+
21769
+ # Column widths - Source and Target columns stretch to fill space, others are interactive
21255
21770
  header.setSectionResizeMode(0, QHeaderView.ResizeMode.Interactive) # ID
21256
21771
  header.setSectionResizeMode(1, QHeaderView.ResizeMode.Interactive) # Type
21257
21772
  header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) # Source - stretch to fill space
@@ -21265,7 +21780,7 @@ class SupervertalerQt(QMainWindow):
21265
21780
  self.table.setColumnWidth(1, 40) # Type - narrower
21266
21781
  self.table.setColumnWidth(2, 400) # Source
21267
21782
  self.table.setColumnWidth(3, 400) # Target
21268
- self.table.setColumnWidth(4, 60) # Status - compact
21783
+ self.table.setColumnWidth(4, 50) # Status - compact width
21269
21784
 
21270
21785
  # Enable word wrap in cells (both display and edit mode)
21271
21786
  self.table.setWordWrap(True)
@@ -21295,6 +21810,51 @@ class SupervertalerQt(QMainWindow):
21295
21810
  QTableWidget::item:last-child {
21296
21811
  border-right: none;
21297
21812
  }
21813
+
21814
+ /* Narrower scrollbar with visible arrow buttons */
21815
+ QScrollBar:vertical {
21816
+ border: none;
21817
+ background: #F0F0F0;
21818
+ width: 12px;
21819
+ margin: 12px 0 12px 0;
21820
+ }
21821
+ QScrollBar::handle:vertical {
21822
+ background: #C0C0C0;
21823
+ min-height: 20px;
21824
+ border-radius: 2px;
21825
+ }
21826
+ QScrollBar::handle:vertical:hover {
21827
+ background: #A0A0A0;
21828
+ }
21829
+ QScrollBar::add-line:vertical {
21830
+ height: 12px;
21831
+ background: #E0E0E0;
21832
+ subcontrol-position: bottom;
21833
+ subcontrol-origin: margin;
21834
+ }
21835
+ QScrollBar::add-line:vertical:hover {
21836
+ background: #2196F3;
21837
+ }
21838
+ QScrollBar::sub-line:vertical {
21839
+ height: 12px;
21840
+ background: #E0E0E0;
21841
+ subcontrol-position: top;
21842
+ subcontrol-origin: margin;
21843
+ }
21844
+ QScrollBar::sub-line:vertical:hover {
21845
+ background: #2196F3;
21846
+ }
21847
+ /* Arrow images */
21848
+ QScrollBar::up-arrow:vertical {
21849
+ image: url(assets/scrollbar_up.png);
21850
+ width: 8px;
21851
+ height: 8px;
21852
+ }
21853
+ QScrollBar::down-arrow:vertical {
21854
+ image: url(assets/scrollbar_down.png);
21855
+ width: 8px;
21856
+ height: 8px;
21857
+ }
21298
21858
  """)
21299
21859
 
21300
21860
  # Simplified editing: Double-click only (no F2 key) - companion tool philosophy
@@ -21317,8 +21877,8 @@ class SupervertalerQt(QMainWindow):
21317
21877
  # Debug: Confirm signal connections
21318
21878
  self.log("🔌 Table signals connected: currentCellChanged, itemClicked, cellDoubleClicked, itemSelectionChanged")
21319
21879
 
21320
- # Add precision scroll buttons (memoQ-style)
21321
- self.add_precision_scroll_buttons()
21880
+ # Precision scroll buttons removed (user preference)
21881
+ # self.add_precision_scroll_buttons()
21322
21882
 
21323
21883
  def add_precision_scroll_buttons(self):
21324
21884
  """Add precision scroll buttons at top/bottom of scrollbar (memoQ-style)"""
@@ -21544,10 +22104,10 @@ class SupervertalerQt(QMainWindow):
21544
22104
  editor_widget.dictate_btn = dictate_btn
21545
22105
 
21546
22106
  save_btn = QPushButton("💾 Save")
21547
- save_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
22107
+ save_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 3px 5px;")
21548
22108
  save_btn.clicked.connect(self.save_tab_segment)
21549
22109
  save_next_btn = QPushButton("✓ Confirm && Next (Ctrl+Enter)")
21550
- save_next_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold;")
22110
+ save_next_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 3px 5px;")
21551
22111
  save_next_btn.clicked.connect(self.confirm_selected_or_next)
21552
22112
 
21553
22113
  button_layout.addWidget(copy_btn)
@@ -21891,6 +22451,10 @@ class SupervertalerQt(QMainWindow):
21891
22451
  # Update UI
21892
22452
  self.project_file_path = None
21893
22453
  self.project_modified = True # Mark as modified since it hasn't been saved
22454
+
22455
+ # Store original segment order for "Document Order" sort reset
22456
+ self._original_segment_order = self.current_project.segments.copy()
22457
+
21894
22458
  self.update_window_title()
21895
22459
  self.load_segments_to_grid()
21896
22460
  self.initialize_tm_database() # Initialize TM for this project
@@ -21919,16 +22483,16 @@ class SupervertalerQt(QMainWindow):
21919
22483
  if hasattr(self.lookup_tab, '_ahk') and self.lookup_tab._ahk:
21920
22484
  try:
21921
22485
  self.lookup_tab._ahk.stop_hotkeys()
21922
- print("[Superlookup] ahk library hotkeys stopped")
22486
+ print("[SuperLookup] ahk library hotkeys stopped")
21923
22487
  except Exception as e:
21924
- print(f"[Superlookup] Error stopping ahk library: {e}")
22488
+ print(f"[SuperLookup] Error stopping ahk library: {e}")
21925
22489
 
21926
22490
  # Terminate external AutoHotkey process if running (fallback method)
21927
22491
  if hasattr(self, 'lookup_tab') and hasattr(self.lookup_tab, 'ahk_process') and self.lookup_tab.ahk_process:
21928
22492
  try:
21929
22493
  self.lookup_tab.ahk_process.terminate()
21930
22494
  self.lookup_tab.ahk_process.wait(timeout=2)
21931
- print("[Superlookup] AHK process terminated")
22495
+ print("[SuperLookup] AHK process terminated")
21932
22496
  except:
21933
22497
  # Force kill if terminate doesn't work
21934
22498
  try:
@@ -21936,7 +22500,7 @@ class SupervertalerQt(QMainWindow):
21936
22500
  except:
21937
22501
  pass
21938
22502
  except Exception as e:
21939
- print(f"[Superlookup] Error terminating AHK: {e}")
22503
+ print(f"[SuperLookup] Error terminating AHK: {e}")
21940
22504
 
21941
22505
  # Accept the close event
21942
22506
  event.accept()
@@ -21972,6 +22536,12 @@ class SupervertalerQt(QMainWindow):
21972
22536
  self.project_file_path = file_path
21973
22537
  self.project_modified = False
21974
22538
 
22539
+ # Store original segment order for "Document Order" sort reset
22540
+ self._original_segment_order = self.current_project.segments.copy()
22541
+
22542
+ # Always reset sort state when loading - project should open in document order
22543
+ self.current_sort = None
22544
+
21975
22545
  # Sync global language settings with project languages
21976
22546
  if self.current_project.source_lang:
21977
22547
  self.source_language = self.current_project.source_lang
@@ -23323,9 +23893,21 @@ class SupervertalerQt(QMainWindow):
23323
23893
  original_path = getattr(self, 'original_docx', None) or getattr(self, 'current_document_path', None)
23324
23894
  if original_path and os.path.exists(original_path):
23325
23895
  self.current_project.original_docx_path = original_path
23326
-
23896
+
23897
+ # IMPORTANT: Always save segments in original document order, not sorted order
23898
+ # Store current sort state and temporarily restore original order
23899
+ current_sort_state = getattr(self, 'current_sort', None)
23900
+ current_segments = self.current_project.segments.copy() # Save current (possibly sorted) order
23901
+
23902
+ # Restore original order for saving
23903
+ if hasattr(self, '_original_segment_order') and self._original_segment_order:
23904
+ self.current_project.segments = self._original_segment_order.copy()
23905
+
23327
23906
  with open(file_path, 'w', encoding='utf-8') as f:
23328
23907
  json.dump(self.current_project.to_dict(), f, indent=2, ensure_ascii=False)
23908
+
23909
+ # Restore the current (sorted) order after saving
23910
+ self.current_project.segments = current_segments
23329
23911
 
23330
23912
  self.project_modified = False
23331
23913
  self.update_window_title()
@@ -29040,7 +29622,11 @@ class SupervertalerQt(QMainWindow):
29040
29622
  def load_segments_to_grid(self):
29041
29623
  """Load segments into the grid with termbase highlighting"""
29042
29624
  self.log(f"🔄🔄🔄 load_segments_to_grid CALLED - this will RELOAD grid from segment data!")
29043
-
29625
+
29626
+ # Ensure original segment order is stored (for Document Order sort)
29627
+ if self.current_project and not hasattr(self, '_original_segment_order'):
29628
+ self._original_segment_order = self.current_project.segments.copy()
29629
+
29044
29630
  # Clear row color settings cache to ensure fresh settings are loaded
29045
29631
  if hasattr(self, '_row_color_settings_cached'):
29046
29632
  delattr(self, '_row_color_settings_cached')
@@ -29117,7 +29703,7 @@ class SupervertalerQt(QMainWindow):
29117
29703
  id_item.setForeground(QColor(segment_num_color))
29118
29704
  id_item.setBackground(QColor()) # Default background from theme
29119
29705
  # Smaller font for segment numbers
29120
- seg_num_font = QFont(self.default_font_family, max(8, self.default_font_size - 2))
29706
+ seg_num_font = QFont(self.default_font_family, max(9, self.default_font_size - 1))
29121
29707
  id_item.setFont(seg_num_font)
29122
29708
  self.table.setItem(row, 0, id_item)
29123
29709
 
@@ -29209,7 +29795,7 @@ class SupervertalerQt(QMainWindow):
29209
29795
  type_item.setForeground(QColor("#388E3C")) # Green for list items (works in both themes)
29210
29796
 
29211
29797
  # Smaller font for type symbols
29212
- type_font = QFont(self.default_font_family, max(8, self.default_font_size - 2))
29798
+ type_font = QFont(self.default_font_family, max(9, self.default_font_size - 1))
29213
29799
  type_item.setFont(type_font)
29214
29800
 
29215
29801
  self.table.setItem(row, 1, type_item)
@@ -29659,7 +30245,10 @@ class SupervertalerQt(QMainWindow):
29659
30245
  if shortcut_badge_tooltip:
29660
30246
  badge_label.setToolTip(f"Press {shortcut_badge_tooltip} to insert")
29661
30247
  header_layout.addWidget(badge_label)
29662
-
30248
+
30249
+ # Add stretch to push navigation controls to the right (aligned with scrollbar)
30250
+ header_layout.addStretch()
30251
+
29663
30252
  nav_label = None
29664
30253
  nav_buttons = None
29665
30254
 
@@ -29725,8 +30314,7 @@ class SupervertalerQt(QMainWindow):
29725
30314
  self.theme_aware_arrows.extend([prev_btn, next_btn])
29726
30315
 
29727
30316
  nav_buttons = [prev_btn, next_btn]
29728
-
29729
- header_layout.addStretch()
30317
+
29730
30318
  main_layout.addLayout(header_layout)
29731
30319
 
29732
30320
  # Text area
@@ -30649,19 +31237,24 @@ class SupervertalerQt(QMainWindow):
30649
31237
  widget.setToolTip(f"Notes: {segment.notes.strip()}")
30650
31238
 
30651
31239
  status_def = get_status(segment.status)
30652
- status_label = QLabel(status_def.icon)
31240
+
31241
+ # Status icons: ✔ (green) and ❌ (naturally red emoji)
31242
+ icon_text = status_def.icon
31243
+ if segment.status == "confirmed":
31244
+ icon_html = f'<font color="#2e7d32" size="2">{icon_text}</font>' # Green checkmark
31245
+ else:
31246
+ icon_html = f'<font size="2">{icon_text}</font>' # Other icons (including ❌ emoji)
31247
+
31248
+ status_label = QLabel(icon_html)
31249
+ status_label.setTextFormat(Qt.TextFormat.RichText) # Enable HTML rendering
30653
31250
  status_label.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft)
31251
+ status_label.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) # Expand vertically to match row height
30654
31252
  status_label.setToolTip(status_def.label)
30655
- # Smaller red X for "not_started" to match green checkmark visual size
30656
- font_size = "8px" if segment.status == "not_started" else "13px"
30657
- # Make confirmed checkmark green
30658
- color = "color: #2e7d32;" if segment.status == "confirmed" else ""
30659
-
30660
- # Apply orange background highlight to status icon if segment has notes
31253
+
31254
+ # Add orange background if segment has notes (using stylesheet for background only)
30661
31255
  if has_notes:
30662
- status_label.setStyleSheet(f"font-size: {font_size}; {color} padding: 2px 4px; background-color: rgba(255, 152, 0, 0.35); border-radius: 3px;")
30663
- else:
30664
- status_label.setStyleSheet(f"font-size: {font_size}; {color} padding-right: 4px;")
31256
+ status_label.setStyleSheet("padding: 2px 4px; background-color: rgba(255, 152, 0, 0.35); border-radius: 3px;")
31257
+ # Note: No stylesheet for non-notes case to avoid interfering with HTML color
30665
31258
  layout.addWidget(status_label)
30666
31259
 
30667
31260
  # Only add match label if there's a match percentage
@@ -30773,14 +31366,17 @@ class SupervertalerQt(QMainWindow):
30773
31366
  """Auto-resize all rows to fit content - Compact version"""
30774
31367
  if not hasattr(self, 'table') or not self.table:
30775
31368
  return
30776
-
31369
+
30777
31370
  # Reduce width slightly to account for padding and prevent text cut-off
30778
31371
  width_reduction = 8
30779
-
31372
+
30780
31373
  # Manually calculate and set row heights for compact display
30781
31374
  for row in range(self.table.rowCount()):
31375
+ # Keep UI responsive during large grid updates
31376
+ if row % 50 == 0:
31377
+ QApplication.processEvents()
30782
31378
  self._auto_resize_single_row(row, width_reduction)
30783
-
31379
+
30784
31380
  self.log("✓ Auto-resized rows to fit content (compact)")
30785
31381
  self._enforce_status_row_heights()
30786
31382
 
@@ -30827,26 +31423,30 @@ class SupervertalerQt(QMainWindow):
30827
31423
  def apply_font_to_grid(self):
30828
31424
  """Apply selected font to all grid cells"""
30829
31425
  font = QFont(self.default_font_family, self.default_font_size)
30830
-
31426
+
30831
31427
  self.table.setFont(font)
30832
-
30833
- # Also update header font - same size as grid content, just bold
30834
- header_font = QFont(self.default_font_family, self.default_font_size, QFont.Weight.Bold)
31428
+
31429
+ # Also update header font - same size as grid content, normal weight
31430
+ header_font = QFont(self.default_font_family, self.default_font_size, QFont.Weight.Normal)
30835
31431
  self.table.horizontalHeader().setFont(header_font)
30836
-
31432
+
30837
31433
  # Update fonts in QTextEdit widgets (source and target columns)
30838
31434
  if hasattr(self, 'table') and self.table:
30839
31435
  for row in range(self.table.rowCount()):
31436
+ # Keep UI responsive during large grid updates
31437
+ if row % 50 == 0:
31438
+ QApplication.processEvents()
31439
+
30840
31440
  # Source column (2) - ReadOnlyGridTextEditor
30841
31441
  source_widget = self.table.cellWidget(row, 2)
30842
31442
  if source_widget and isinstance(source_widget, ReadOnlyGridTextEditor):
30843
31443
  source_widget.setFont(font)
30844
-
31444
+
30845
31445
  # Target column (3) - EditableGridTextEditor
30846
31446
  target_widget = self.table.cellWidget(row, 3)
30847
31447
  if target_widget and isinstance(target_widget, EditableGridTextEditor):
30848
31448
  target_widget.setFont(font)
30849
-
31449
+
30850
31450
  # Adjust segment number column width based on font size
30851
31451
  self._update_segment_column_width()
30852
31452
 
@@ -30870,12 +31470,12 @@ class SupervertalerQt(QMainWindow):
30870
31470
  # Measure the width of the largest number (as string)
30871
31471
  text_width = fm.horizontalAdvance(str(max_segment))
30872
31472
 
30873
- # Add padding (10px on each side = 20px total)
30874
- new_width = text_width + 20
30875
-
30876
- # Ensure minimum width for very small numbers
30877
- new_width = max(30, new_width)
30878
-
31473
+ # Add padding (6px on each side = 12px total)
31474
+ new_width = text_width + 12
31475
+
31476
+ # Ensure minimum width for very small numbers, cap at width for ~1000
31477
+ new_width = max(30, min(new_width, 55))
31478
+
30879
31479
  self.table.setColumnWidth(0, new_width)
30880
31480
 
30881
31481
  def set_font_family(self, family_name: str):
@@ -30907,14 +31507,18 @@ class SupervertalerQt(QMainWindow):
30907
31507
  """Refresh tag highlight colors in all grid cells"""
30908
31508
  if not hasattr(self, 'table') or not self.table:
30909
31509
  return
30910
-
31510
+
30911
31511
  for row in range(self.table.rowCount()):
31512
+ # Keep UI responsive during large grid updates
31513
+ if row % 50 == 0:
31514
+ QApplication.processEvents()
31515
+
30912
31516
  # Source column (2) - ReadOnlyGridTextEditor
30913
31517
  source_widget = self.table.cellWidget(row, 2)
30914
31518
  if source_widget and isinstance(source_widget, ReadOnlyGridTextEditor):
30915
31519
  if hasattr(source_widget, 'highlighter'):
30916
31520
  source_widget.highlighter.set_tag_color(EditableGridTextEditor.tag_highlight_color)
30917
-
31521
+
30918
31522
  # Target column (3) - EditableGridTextEditor
30919
31523
  target_widget = self.table.cellWidget(row, 3)
30920
31524
  if target_widget and isinstance(target_widget, EditableGridTextEditor):
@@ -30979,18 +31583,22 @@ class SupervertalerQt(QMainWindow):
30979
31583
  """Apply alternating row colors to all source and target cells in the grid"""
30980
31584
  if not hasattr(self, 'table') or not self.table:
30981
31585
  return
30982
-
31586
+
30983
31587
  # Clear cached settings to force reload
30984
31588
  if hasattr(self, '_row_color_settings_cached'):
30985
31589
  delattr(self, '_row_color_settings_cached')
30986
-
31590
+
30987
31591
  for row in range(self.table.rowCount()):
31592
+ # Keep UI responsive during large grid updates
31593
+ if row % 50 == 0:
31594
+ QApplication.processEvents()
31595
+
30988
31596
  source_widget = self.table.cellWidget(row, 2)
30989
31597
  target_widget = self.table.cellWidget(row, 3)
30990
-
31598
+
30991
31599
  if source_widget and target_widget:
30992
31600
  self._apply_row_color(row, source_widget, target_widget)
30993
-
31601
+
30994
31602
  self.log("✓ Alternating row colors applied")
30995
31603
 
30996
31604
  def on_font_changed(self):
@@ -36864,9 +37472,9 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
36864
37472
  """Update spellcheck button style based on enabled state"""
36865
37473
  if hasattr(self, 'spellcheck_btn'):
36866
37474
  if self.spellcheck_enabled:
36867
- self.spellcheck_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold;")
37475
+ self.spellcheck_btn.setStyleSheet("background-color: #4CAF50; color: white; font-weight: bold; padding: 3px 5px;")
36868
37476
  else:
36869
- self.spellcheck_btn.setStyleSheet("background-color: #9E9E9E; color: white; font-weight: bold;")
37477
+ self.spellcheck_btn.setStyleSheet("background-color: #9E9E9E; color: white; font-weight: bold; padding: 3px 5px;")
36870
37478
 
36871
37479
  def _toggle_spellcheck(self, checked=None):
36872
37480
  """Toggle spellcheck on/off"""
@@ -37566,7 +38174,154 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37566
38174
  self.table.setUpdatesEnabled(True)
37567
38175
 
37568
38176
  self.log(f"🔍 Advanced filters: showing {visible_count} of {len(self.current_project.segments)} segments")
37569
-
38177
+
38178
+ def apply_sort(self, sort_type: str = None):
38179
+ """Sort segments by various criteria (similar to memoQ)"""
38180
+ if not self.current_project or not hasattr(self, 'table') or self.table is None:
38181
+ return
38182
+
38183
+ if not self.current_project.segments:
38184
+ return
38185
+
38186
+ # Show progress dialog during sorting
38187
+ from PyQt6.QtWidgets import QProgressDialog
38188
+ from PyQt6.QtCore import Qt
38189
+
38190
+ progress = QProgressDialog("Sorting segments, please wait...", None, 0, 0, self)
38191
+ progress.setWindowTitle("Sorting")
38192
+ progress.setWindowModality(Qt.WindowModality.WindowModal)
38193
+ progress.setMinimumDuration(0) # Show immediately
38194
+ progress.show()
38195
+ QApplication.processEvents() # Force UI update
38196
+
38197
+ try:
38198
+ # Store original document order if not already stored
38199
+ if not hasattr(self, '_original_segment_order'):
38200
+ self._original_segment_order = self.current_project.segments.copy()
38201
+
38202
+ # Update current sort state
38203
+ self.current_sort = sort_type
38204
+
38205
+ # If sort_type is None, restore document order
38206
+ if sort_type is None:
38207
+ # Restore document order by sorting by segment ID (original position)
38208
+ # This works even if the stored original order is wrong
38209
+ self.current_project.segments.sort(key=lambda seg: int(seg.id))
38210
+
38211
+ # Update stored original order to this correct order
38212
+ self._original_segment_order = self.current_project.segments.copy()
38213
+
38214
+ # Set pagination to "All" to show all segments
38215
+ if hasattr(self, 'page_size_combo') and self._widget_is_alive(self.page_size_combo):
38216
+ self.page_size_combo.blockSignals(True)
38217
+ self.page_size_combo.setCurrentText("All")
38218
+ self.page_size_combo.blockSignals(False)
38219
+ # Update the internal page size variable
38220
+ if hasattr(self, 'grid_page_size'):
38221
+ self.grid_page_size = 999999
38222
+
38223
+ self.load_segments_to_grid()
38224
+ self.log("↩️ Restored document order (showing all segments)")
38225
+ return
38226
+
38227
+ # Helper function to get text without tags for more accurate sorting
38228
+ def strip_tags(text: str) -> str:
38229
+ """Remove HTML/XML tags from text for sorting"""
38230
+ import re
38231
+ return re.sub(r'<[^>]+>', '', text).strip()
38232
+
38233
+ # Calculate frequency maps if needed
38234
+ frequency_cache = {}
38235
+ if 'freq' in sort_type:
38236
+ from collections import Counter
38237
+ if 'source' in sort_type:
38238
+ counter = Counter(strip_tags(seg.source).lower() for seg in self.current_project.segments)
38239
+ frequency_cache = {strip_tags(seg.source).lower(): counter[strip_tags(seg.source).lower()]
38240
+ for seg in self.current_project.segments}
38241
+ else: # target frequency
38242
+ counter = Counter(strip_tags(seg.target).lower() for seg in self.current_project.segments if seg.target)
38243
+ frequency_cache = {strip_tags(seg.target).lower(): counter[strip_tags(seg.target).lower()]
38244
+ for seg in self.current_project.segments if seg.target}
38245
+
38246
+ # Sort based on selected criterion
38247
+ if sort_type == 'source_asc':
38248
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.source).lower())
38249
+ sort_name = "Source A → Z"
38250
+ elif sort_type == 'source_desc':
38251
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.source).lower(), reverse=True)
38252
+ sort_name = "Source Z → A"
38253
+ elif sort_type == 'target_asc':
38254
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.target).lower() if s.target else "")
38255
+ sort_name = "Target A → Z"
38256
+ elif sort_type == 'target_desc':
38257
+ self.current_project.segments.sort(key=lambda s: strip_tags(s.target).lower() if s.target else "", reverse=True)
38258
+ sort_name = "Target Z → A"
38259
+ elif sort_type == 'source_length_asc':
38260
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.source)))
38261
+ sort_name = "Source (shorter first)"
38262
+ elif sort_type == 'source_length_desc':
38263
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.source)), reverse=True)
38264
+ sort_name = "Source (longer first)"
38265
+ elif sort_type == 'target_length_asc':
38266
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.target)) if s.target else 0)
38267
+ sort_name = "Target (shorter first)"
38268
+ elif sort_type == 'target_length_desc':
38269
+ self.current_project.segments.sort(key=lambda s: len(strip_tags(s.target)) if s.target else 0, reverse=True)
38270
+ sort_name = "Target (longer first)"
38271
+ elif sort_type == 'match_asc':
38272
+ self.current_project.segments.sort(key=lambda s: getattr(s, 'match_percent', 0) or 0)
38273
+ sort_name = "Match Rate (lower first)"
38274
+ elif sort_type == 'match_desc':
38275
+ self.current_project.segments.sort(key=lambda s: getattr(s, 'match_percent', 0) or 0, reverse=True)
38276
+ sort_name = "Match Rate (higher first)"
38277
+ elif sort_type == 'source_freq_asc':
38278
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.source).lower(), 0))
38279
+ sort_name = "Source Frequency (lower first)"
38280
+ elif sort_type == 'source_freq_desc':
38281
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.source).lower(), 0), reverse=True)
38282
+ sort_name = "Source Frequency (higher first)"
38283
+ elif sort_type == 'target_freq_asc':
38284
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.target).lower(), 0) if s.target else 0)
38285
+ sort_name = "Target Frequency (lower first)"
38286
+ elif sort_type == 'target_freq_desc':
38287
+ self.current_project.segments.sort(key=lambda s: frequency_cache.get(strip_tags(s.target).lower(), 0) if s.target else 0, reverse=True)
38288
+ sort_name = "Target Frequency (higher first)"
38289
+ elif sort_type == 'modified_asc':
38290
+ self.current_project.segments.sort(key=lambda s: s.modified_at if s.modified_at else "")
38291
+ sort_name = "Last Changed (oldest first)"
38292
+ elif sort_type == 'modified_desc':
38293
+ self.current_project.segments.sort(key=lambda s: s.modified_at if s.modified_at else "", reverse=True)
38294
+ sort_name = "Last Changed (newest first)"
38295
+ elif sort_type == 'status':
38296
+ # Sort by status in a logical order: not_started, draft, translated, confirmed
38297
+ status_order = {'not_started': 0, 'draft': 1, 'translated': 2, 'confirmed': 3}
38298
+ self.current_project.segments.sort(key=lambda s: status_order.get(s.status, 99))
38299
+ sort_name = "Row Status"
38300
+ else:
38301
+ self.log(f"⚠️ Unknown sort type: {sort_type}")
38302
+ return
38303
+
38304
+ # Set pagination to "All" to show all sorted segments
38305
+ if hasattr(self, 'page_size_combo') and self._widget_is_alive(self.page_size_combo):
38306
+ self.page_size_combo.blockSignals(True)
38307
+ self.page_size_combo.setCurrentText("All")
38308
+ self.page_size_combo.blockSignals(False)
38309
+ # Update the internal page size variable
38310
+ if hasattr(self, 'grid_page_size'):
38311
+ self.grid_page_size = 999999
38312
+
38313
+ # Reload grid to reflect new order
38314
+ self.load_segments_to_grid()
38315
+ self.log(f"⇅ Sorted by: {sort_name} (showing all segments)")
38316
+
38317
+ except Exception as e:
38318
+ self.log(f"❌ Error sorting segments: {e}")
38319
+ import traceback
38320
+ traceback.print_exc()
38321
+ finally:
38322
+ # Close progress dialog
38323
+ progress.close()
38324
+
37570
38325
  # ========================================================================
37571
38326
  # TABBED SEGMENT EDITOR METHODS (for Grid view)
37572
38327
  # ========================================================================
@@ -39560,32 +40315,30 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
39560
40315
 
39561
40316
  def _toggle_tag_view_via_shortcut(self):
39562
40317
  """Toggle tag view using keyboard shortcut (Ctrl+Alt+T)"""
39563
- if hasattr(self, 'tag_view_btn'):
39564
- # Toggle the button state (which triggers toggle_tag_view)
39565
- new_state = not self.tag_view_btn.isChecked()
39566
- self.tag_view_btn.setChecked(new_state)
39567
- self.toggle_tag_view(new_state, self.tag_view_btn)
40318
+ if hasattr(self, 'wysiwyg_btn') and hasattr(self, 'tags_btn'):
40319
+ # Toggle between the two modes
40320
+ new_state = not self.show_tags
40321
+ self.toggle_tag_view(new_state, None)
39568
40322
 
39569
40323
  def _enable_tag_view_after_import(self):
39570
40324
  """Auto-enable Tag View after importing a document with formatting tags"""
39571
- if hasattr(self, 'tag_view_btn'):
39572
- self.tag_view_btn.setChecked(True)
39573
- self.toggle_tag_view(True, self.tag_view_btn)
40325
+ if hasattr(self, 'tags_btn'):
40326
+ self.toggle_tag_view(True, None)
39574
40327
  self.log("🏷️ Tag View auto-enabled (formatting tags detected in import)")
39575
40328
 
39576
40329
  def toggle_tag_view(self, checked: bool, button: QPushButton = None):
39577
40330
  """Toggle between Tag View (showing raw tags) and WYSIWYG View (formatted display)"""
39578
40331
  self.show_tags = checked
39579
-
39580
- # Update button text
39581
- if button:
40332
+
40333
+ # Update segmented control buttons if they exist
40334
+ if hasattr(self, 'wysiwyg_btn') and hasattr(self, 'tags_btn'):
39582
40335
  if checked:
39583
- button.setText("🏷️ Tags ON")
40336
+ self.tags_btn.setChecked(True)
39584
40337
  else:
39585
- button.setText("🏷️ Tags OFF")
39586
-
40338
+ self.wysiwyg_btn.setChecked(True)
40339
+
39587
40340
  self.log(f"{'🏷️ Tag View ENABLED - showing raw tags' if checked else '✨ WYSIWYG View ENABLED - showing formatted text'}")
39588
-
40341
+
39589
40342
  # Refresh the grid to update display
39590
40343
  if hasattr(self, 'table') and self.current_project:
39591
40344
  self._refresh_grid_display_mode()
@@ -40958,45 +41711,12 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
40958
41711
  event.accept()
40959
41712
 
40960
41713
  def _cleanup_web_views(self):
40961
- """Clean up WebEngine views to prevent 'Release of profile requested' warning"""
40962
- try:
40963
- # Close any SuperBrowser tabs
40964
- if hasattr(self, 'superbrowser_tabs'):
40965
- for tab in self.superbrowser_tabs.values():
40966
- try:
40967
- if hasattr(tab, 'web_view'):
40968
- # Stop any loading/rendering
40969
- tab.web_view.stop()
40970
- # Clear page to release resources
40971
- tab.web_view.setPage(None)
40972
- tab.web_view.setUrl(QUrl('about:blank'))
40973
- tab.web_view.deleteLater()
40974
- except:
40975
- pass
40976
-
40977
- # Close Superlookup web views (from Web Resources tab)
40978
- if hasattr(self, 'web_views'):
40979
- for resource_id, web_view in list(self.web_views.items()):
40980
- try:
40981
- web_view.stop()
40982
- web_view.setPage(None)
40983
- web_view.setUrl(QUrl('about:blank'))
40984
- web_view.deleteLater()
40985
- except:
40986
- pass
40987
- self.web_views.clear()
40988
-
40989
- # Process events multiple times to ensure cleanup completes
40990
- from PyQt6.QtWidgets import QApplication
40991
- from PyQt6.QtCore import QUrl
40992
- for _ in range(3):
40993
- QApplication.processEvents()
40994
-
40995
- # Small delay to allow Qt to finish cleanup
40996
- import time
40997
- time.sleep(0.1)
40998
- except:
40999
- pass
41714
+ """Clean up WebEngine views - DISABLED to prevent crash"""
41715
+ # WebEngine cleanup has been disabled because it was causing Python crashes
41716
+ # on program exit. Qt will handle WebEngine cleanup automatically, though
41717
+ # you may see a "Release of profile requested" warning which is harmless.
41718
+ print("[WebEngine Cleanup] Skipping manual cleanup - letting Qt handle it")
41719
+ pass
41000
41720
 
41001
41721
  def _close_detached_log_windows(self):
41002
41722
  """Close all detached log windows when main window closes"""
@@ -43541,23 +44261,58 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
43541
44261
  """Call DeepL API"""
43542
44262
  try:
43543
44263
  import deepl
43544
-
44264
+
43545
44265
  if not api_key:
43546
44266
  api_keys = self.load_api_keys()
43547
44267
  api_key = api_keys.get("deepl")
43548
-
44268
+
43549
44269
  if not api_key:
43550
44270
  return "[DeepL requires API key]"
43551
-
44271
+
43552
44272
  translator = deepl.Translator(api_key)
43553
-
43554
- # Convert language codes (DeepL uses uppercase)
44273
+
44274
+ # Convert source language code (DeepL uses uppercase, no variant needed for source)
43555
44275
  src_code = source_lang.split('-')[0].split('_')[0].upper()
43556
- tgt_code = target_lang.split('-')[0].split('_')[0].upper()
43557
-
44276
+
44277
+ # Convert target language code - DeepL requires variants for some languages
44278
+ # Handle full codes like "en-US", "en-GB", "pt-BR", "pt-PT"
44279
+ tgt_upper = target_lang.upper().replace('_', '-')
44280
+
44281
+ # DeepL target language mapping - some require specific variants
44282
+ deepl_target_map = {
44283
+ # English variants (EN alone is deprecated)
44284
+ 'EN': 'EN-US', # Default to US English
44285
+ 'EN-US': 'EN-US',
44286
+ 'EN-GB': 'EN-GB',
44287
+ 'EN-AU': 'EN-GB', # Map Australian to British
44288
+ 'EN-CA': 'EN-US', # Map Canadian to US
44289
+ # Portuguese variants
44290
+ 'PT': 'PT-PT', # Default to European Portuguese
44291
+ 'PT-PT': 'PT-PT',
44292
+ 'PT-BR': 'PT-BR',
44293
+ # Chinese variants
44294
+ 'ZH': 'ZH-HANS', # Default to Simplified
44295
+ 'ZH-CN': 'ZH-HANS',
44296
+ 'ZH-TW': 'ZH-HANT',
44297
+ 'ZH-HANS': 'ZH-HANS',
44298
+ 'ZH-HANT': 'ZH-HANT',
44299
+ }
44300
+
44301
+ # Check if full code matches first, then base code
44302
+ if tgt_upper in deepl_target_map:
44303
+ tgt_code = deepl_target_map[tgt_upper]
44304
+ else:
44305
+ # Extract base code and check
44306
+ base_code = tgt_upper.split('-')[0]
44307
+ if base_code in deepl_target_map:
44308
+ tgt_code = deepl_target_map[base_code]
44309
+ else:
44310
+ # Use base code as-is for other languages
44311
+ tgt_code = base_code
44312
+
43558
44313
  result = translator.translate_text(text, source_lang=src_code, target_lang=tgt_code)
43559
44314
  return result.text
43560
-
44315
+
43561
44316
  except ImportError:
43562
44317
  return "[DeepL requires: pip install deepl]"
43563
44318
  except Exception as e:
@@ -44003,8 +44758,11 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
44003
44758
  # Determine segment number color based on theme
44004
44759
  is_dark_theme = theme.name == "Dark"
44005
44760
  segment_num_color = theme.text if is_dark_theme else "black"
44006
-
44761
+
44007
44762
  for row in range(self.table.rowCount()):
44763
+ # Keep UI responsive during large grid updates
44764
+ if row % 50 == 0:
44765
+ QApplication.processEvents()
44008
44766
  id_item = self.table.item(row, 0)
44009
44767
  if id_item:
44010
44768
  # Don't change currently highlighted row (orange background)
@@ -45093,7 +45851,7 @@ class SuperlookupTab(QWidget):
45093
45851
  self.main_window = parent # Store reference to main window for database access
45094
45852
  self.user_data_path = user_data_path # Store user data path for web cache
45095
45853
 
45096
- print("[Superlookup] SuperlookupTab.__init__ called")
45854
+ print("[SuperLookup] SuperlookupTab.__init__ called")
45097
45855
 
45098
45856
  # Get theme manager from main window (try parent first, then parent's parent for dialogs)
45099
45857
  self.theme_manager = getattr(parent, 'theme_manager', None)
@@ -45102,16 +45860,16 @@ class SuperlookupTab(QWidget):
45102
45860
  parent_parent = getattr(parent, 'parent', lambda: None)()
45103
45861
  if parent_parent:
45104
45862
  self.theme_manager = getattr(parent_parent, 'theme_manager', None)
45105
- print(f"[Superlookup] theme_manager: {self.theme_manager is not None}")
45863
+ print(f"[SuperLookup] theme_manager: {self.theme_manager is not None}")
45106
45864
 
45107
45865
  # Import lookup engine
45108
45866
  try:
45109
45867
  from modules.superlookup import SuperlookupEngine, LookupResult
45110
45868
  self.SuperlookupEngine = SuperlookupEngine
45111
45869
  self.LookupResult = LookupResult
45112
- print("[Superlookup] Successfully imported SuperlookupEngine")
45870
+ print("[SuperLookup] Successfully imported SuperlookupEngine")
45113
45871
  except ImportError as e:
45114
- print(f"[Superlookup] IMPORT ERROR: {e}")
45872
+ print(f"[SuperLookup] IMPORT ERROR: {e}")
45115
45873
  QMessageBox.critical(
45116
45874
  self,
45117
45875
  "Missing Module",
@@ -45193,7 +45951,7 @@ class SuperlookupTab(QWidget):
45193
45951
  if self.db_manager or self.termbase_mgr:
45194
45952
  self.populate_language_dropdowns()
45195
45953
  self._languages_populated = True
45196
- print("[Superlookup] Languages populated on first show")
45954
+ print("[SuperLookup] Languages populated on first show")
45197
45955
 
45198
45956
  def init_ui(self):
45199
45957
  """Initialize the UI"""
@@ -45202,7 +45960,7 @@ class SuperlookupTab(QWidget):
45202
45960
  layout.setSpacing(5) # Reduced from 10 to 5 for consistency
45203
45961
 
45204
45962
  # Header
45205
- header = QLabel("🔍 Superlookup")
45963
+ header = QLabel("🔍 SuperLookup")
45206
45964
  header.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
45207
45965
  layout.addWidget(header, 0) # 0 = no stretch, stays compact
45208
45966
 
@@ -45696,7 +46454,7 @@ class SuperlookupTab(QWidget):
45696
46454
  })
45697
46455
 
45698
46456
  except Exception as e:
45699
- print(f"[Superlookup] MT error ({provider_name}): {e}")
46457
+ print(f"[SuperLookup] MT error ({provider_name}): {e}")
45700
46458
  results.append({
45701
46459
  'provider': provider_name,
45702
46460
  'translation': f"[Error: {str(e)}]",
@@ -45787,9 +46545,9 @@ class SuperlookupTab(QWidget):
45787
46545
  self.web_profile = QWebEngineProfile("SuperlookupProfile", self)
45788
46546
  self.web_profile.setPersistentStoragePath(storage_path)
45789
46547
  self.web_profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.ForcePersistentCookies)
45790
- print(f"[Superlookup] QWebEngineView available - embedded browser enabled with persistent storage at {storage_path}")
46548
+ print(f"[SuperLookup] QWebEngineView available - embedded browser enabled with persistent storage at {storage_path}")
45791
46549
  except ImportError:
45792
- print("[Superlookup] QWebEngineView not available - external browser only")
46550
+ print("[SuperLookup] QWebEngineView not available - external browser only")
45793
46551
  self.QWebEngineView = None
45794
46552
  self.QWebEngineProfile = None
45795
46553
 
@@ -46241,7 +46999,7 @@ class SuperlookupTab(QWidget):
46241
46999
  self.web_view_stack.addWidget(web_view)
46242
47000
  self.web_views[resource['id']] = web_view
46243
47001
 
46244
- print(f"[Superlookup] Created web view for {resource['name']} (lazy load)")
47002
+ print(f"[SuperLookup] Created web view for {resource['name']} (lazy load)")
46245
47003
 
46246
47004
  def _get_web_view_index(self, resource_id):
46247
47005
  """Get the stack index for a web view by resource ID"""
@@ -46258,7 +47016,7 @@ class SuperlookupTab(QWidget):
46258
47016
 
46259
47017
  self._update_web_view_for_mode()
46260
47018
  self._show_web_welcome_message()
46261
- print(f"[Superlookup] Web browser mode changed to: {self.web_browser_mode}")
47019
+ print(f"[SuperLookup] Web browser mode changed to: {self.web_browser_mode}")
46262
47020
 
46263
47021
  def _update_web_view_for_mode(self):
46264
47022
  """Update the view stack based on current mode"""
@@ -46891,11 +47649,11 @@ class SuperlookupTab(QWidget):
46891
47649
  if index == 2:
46892
47650
  # Initialize Supermemory when tab is first viewed
46893
47651
  if not self.supermemory_engine:
46894
- print("[Superlookup] Supermemory tab viewed - initializing engine")
47652
+ print("[SuperLookup] Supermemory tab viewed - initializing engine")
46895
47653
  self.init_supermemory()
46896
47654
  # Settings tab is at index 5
46897
47655
  elif index == 5:
46898
- print("[Superlookup] Settings tab viewed - refreshing resource lists")
47656
+ print("[SuperLookup] Settings tab viewed - refreshing resource lists")
46899
47657
  self.refresh_tm_list()
46900
47658
  self.refresh_termbase_list()
46901
47659
  self.populate_language_dropdowns()
@@ -46903,19 +47661,19 @@ class SuperlookupTab(QWidget):
46903
47661
  def on_tm_search_toggled(self, state):
46904
47662
  """Handle TM search checkbox toggle"""
46905
47663
  self.search_tm_enabled = (state == Qt.CheckState.Checked.value)
46906
- print(f"[Superlookup] TM search {'enabled' if self.search_tm_enabled else 'disabled'}")
47664
+ print(f"[SuperLookup] TM search {'enabled' if self.search_tm_enabled else 'disabled'}")
46907
47665
 
46908
47666
  def on_termbase_search_toggled(self, state):
46909
47667
  """Handle termbase search checkbox toggle"""
46910
47668
  self.search_termbase_enabled = (state == Qt.CheckState.Checked.value)
46911
- print(f"[Superlookup] Termbase search {'enabled' if self.search_termbase_enabled else 'disabled'}")
47669
+ print(f"[SuperLookup] Termbase search {'enabled' if self.search_termbase_enabled else 'disabled'}")
46912
47670
 
46913
47671
  def _on_web_resource_checkbox_changed(self, index: int, state: int):
46914
47672
  """Handle web resource checkbox change - show/hide corresponding sidebar button"""
46915
47673
  is_checked = (state == Qt.CheckState.Checked.value)
46916
47674
  if hasattr(self, 'web_resource_buttons') and index < len(self.web_resource_buttons):
46917
47675
  self.web_resource_buttons[index].setVisible(is_checked)
46918
- print(f"[Superlookup] Web resource {index} {'shown' if is_checked else 'hidden'}")
47676
+ print(f"[SuperLookup] Web resource {index} {'shown' if is_checked else 'hidden'}")
46919
47677
 
46920
47678
  # If the hidden resource was selected, select the first visible one
46921
47679
  if not is_checked and hasattr(self, 'current_web_resource_index') and self.current_web_resource_index == index:
@@ -46933,18 +47691,18 @@ class SuperlookupTab(QWidget):
46933
47691
  checkbox.deleteLater()
46934
47692
  self.tm_checkboxes.clear()
46935
47693
 
46936
- print(f"[Superlookup] refresh_tm_list called")
46937
- print(f"[Superlookup] main_window exists: {self.main_window is not None}")
47694
+ print(f"[SuperLookup] refresh_tm_list called")
47695
+ print(f"[SuperLookup] main_window exists: {self.main_window is not None}")
46938
47696
 
46939
47697
  # Get TMs from main window's database
46940
47698
  if self.main_window and hasattr(self.main_window, 'db_manager') and self.main_window.db_manager:
46941
47699
  try:
46942
- print(f"[Superlookup] db_manager found, querying TMs...")
47700
+ print(f"[SuperLookup] db_manager found, querying TMs...")
46943
47701
  cursor = self.main_window.db_manager.cursor
46944
47702
  cursor.execute("SELECT id, name, tm_id FROM translation_memories ORDER BY name")
46945
47703
  tms = cursor.fetchall()
46946
47704
 
46947
- print(f"[Superlookup] Query returned {len(tms)} TMs")
47705
+ print(f"[SuperLookup] Query returned {len(tms)} TMs")
46948
47706
 
46949
47707
  for db_id, tm_name, tm_id_str in tms:
46950
47708
  checkbox = CheckmarkCheckBox(f"{tm_name} (ID: {db_id})")
@@ -46955,13 +47713,13 @@ class SuperlookupTab(QWidget):
46955
47713
  # Insert before the stretch at the end
46956
47714
  self.tm_scroll_layout.insertWidget(len(self.tm_checkboxes) - 1, checkbox)
46957
47715
 
46958
- print(f"[Superlookup] ✓ Loaded {len(tms)} TMs")
47716
+ print(f"[SuperLookup] ✓ Loaded {len(tms)} TMs")
46959
47717
  except Exception as e:
46960
- print(f"[Superlookup] ✗ Error loading TMs: {e}")
47718
+ print(f"[SuperLookup] ✗ Error loading TMs: {e}")
46961
47719
  import traceback
46962
47720
  traceback.print_exc()
46963
47721
  else:
46964
- print(f"[Superlookup] db_manager not available")
47722
+ print(f"[SuperLookup] db_manager not available")
46965
47723
  # Add placeholder label
46966
47724
  placeholder = QLabel("No database connection - TMs unavailable")
46967
47725
  placeholder.setStyleSheet("color: #999; font-style: italic;")
@@ -46975,16 +47733,16 @@ class SuperlookupTab(QWidget):
46975
47733
  checkbox.deleteLater()
46976
47734
  self.tb_checkboxes.clear()
46977
47735
 
46978
- print(f"[Superlookup] refresh_termbase_list called")
46979
- print(f"[Superlookup] main_window exists: {self.main_window is not None}")
47736
+ print(f"[SuperLookup] refresh_termbase_list called")
47737
+ print(f"[SuperLookup] main_window exists: {self.main_window is not None}")
46980
47738
 
46981
47739
  # Try termbase_mgr first (preferred method)
46982
47740
  if self.main_window and hasattr(self.main_window, 'termbase_mgr') and self.main_window.termbase_mgr:
46983
47741
  try:
46984
- print(f"[Superlookup] termbase_mgr found, querying termbases...")
47742
+ print(f"[SuperLookup] termbase_mgr found, querying termbases...")
46985
47743
  termbases = self.main_window.termbase_mgr.get_all_termbases()
46986
47744
 
46987
- print(f"[Superlookup] get_all_termbases() returned {len(termbases)} termbases")
47745
+ print(f"[SuperLookup] get_all_termbases() returned {len(termbases)} termbases")
46988
47746
 
46989
47747
  for tb in termbases:
46990
47748
  tb_id = tb.get('id')
@@ -46996,22 +47754,22 @@ class SuperlookupTab(QWidget):
46996
47754
  # Insert before the stretch at the end
46997
47755
  self.tb_scroll_layout.insertWidget(len(self.tb_checkboxes) - 1, checkbox)
46998
47756
 
46999
- print(f"[Superlookup] ✓ Loaded {len(termbases)} termbases via termbase_mgr")
47757
+ print(f"[SuperLookup] ✓ Loaded {len(termbases)} termbases via termbase_mgr")
47000
47758
  return
47001
47759
  except Exception as e:
47002
- print(f"[Superlookup] ✗ Error loading termbases via termbase_mgr: {e}")
47760
+ print(f"[SuperLookup] ✗ Error loading termbases via termbase_mgr: {e}")
47003
47761
  import traceback
47004
47762
  traceback.print_exc()
47005
47763
 
47006
47764
  # Fallback to direct database query
47007
47765
  if self.main_window and hasattr(self.main_window, 'db_manager') and self.main_window.db_manager:
47008
47766
  try:
47009
- print(f"[Superlookup] db_manager found, querying termbases...")
47767
+ print(f"[SuperLookup] db_manager found, querying termbases...")
47010
47768
  cursor = self.main_window.db_manager.cursor
47011
47769
  cursor.execute("SELECT id, name FROM termbases ORDER BY name")
47012
47770
  termbases = cursor.fetchall()
47013
47771
 
47014
- print(f"[Superlookup] Query returned {len(termbases)} termbases")
47772
+ print(f"[SuperLookup] Query returned {len(termbases)} termbases")
47015
47773
 
47016
47774
  for tb_id, tb_name in termbases:
47017
47775
  checkbox = CheckmarkCheckBox(f"{tb_name} (ID: {tb_id})")
@@ -47021,13 +47779,13 @@ class SuperlookupTab(QWidget):
47021
47779
  # Insert before the stretch at the end
47022
47780
  self.tb_scroll_layout.insertWidget(len(self.tb_checkboxes) - 1, checkbox)
47023
47781
 
47024
- print(f"[Superlookup] ✓ Loaded {len(termbases)} termbases via db_manager")
47782
+ print(f"[SuperLookup] ✓ Loaded {len(termbases)} termbases via db_manager")
47025
47783
  except Exception as e:
47026
- print(f"[Superlookup] ✗ Error loading termbases via db_manager: {e}")
47784
+ print(f"[SuperLookup] ✗ Error loading termbases via db_manager: {e}")
47027
47785
  import traceback
47028
47786
  traceback.print_exc()
47029
47787
  else:
47030
- print(f"[Superlookup] Neither termbase_mgr nor db_manager available")
47788
+ print(f"[SuperLookup] Neither termbase_mgr nor db_manager available")
47031
47789
  # Add placeholder label
47032
47790
  placeholder = QLabel("No database connection - Glossaries unavailable")
47033
47791
  placeholder.setStyleSheet("color: #999; font-style: italic;")
@@ -47838,7 +48596,7 @@ class SuperlookupTab(QWidget):
47838
48596
  self.status_label.setText(f"Navigated to glossary entry: {source_term}")
47839
48597
 
47840
48598
  except Exception as e:
47841
- print(f"[Superlookup] Error navigating to glossary: {e}")
48599
+ print(f"[SuperLookup] Error navigating to glossary: {e}")
47842
48600
  self.status_label.setText(f"Error navigating to glossary: {e}")
47843
48601
 
47844
48602
  def _select_first_term_in_table(self, main):
@@ -47852,7 +48610,7 @@ class SuperlookupTab(QWidget):
47852
48610
  if item:
47853
48611
  table.scrollToItem(item)
47854
48612
  except Exception as e:
47855
- print(f"[Superlookup] Error selecting term: {e}")
48613
+ print(f"[SuperLookup] Error selecting term: {e}")
47856
48614
 
47857
48615
  def display_mt_results(self, results):
47858
48616
  """Display MT results in the table"""
@@ -48387,7 +49145,7 @@ class SuperlookupTab(QWidget):
48387
49145
  if self.main_window and hasattr(self.main_window, 'general_settings'):
48388
49146
  saved_path = self.main_window.general_settings.get('autohotkey_path', '')
48389
49147
  if saved_path and os.path.exists(saved_path):
48390
- print(f"[Superlookup] Using saved AutoHotkey path: {saved_path}")
49148
+ print(f"[SuperLookup] Using saved AutoHotkey path: {saved_path}")
48391
49149
  return saved_path, 'saved'
48392
49150
 
48393
49151
  # Standard installation paths
@@ -48405,7 +49163,7 @@ class SuperlookupTab(QWidget):
48405
49163
 
48406
49164
  for path in ahk_paths:
48407
49165
  if os.path.exists(path):
48408
- print(f"[Superlookup] Detected AutoHotkey at: {path}")
49166
+ print(f"[SuperLookup] Detected AutoHotkey at: {path}")
48409
49167
  return path, 'detected'
48410
49168
 
48411
49169
  return None, None
@@ -48532,7 +49290,7 @@ class SuperlookupTab(QWidget):
48532
49290
  if self.main_window and hasattr(self.main_window, 'general_settings'):
48533
49291
  self.main_window.general_settings['autohotkey_path'] = file_path
48534
49292
  self.main_window.save_general_settings()
48535
- print(f"[Superlookup] Saved AutoHotkey path: {file_path}")
49293
+ print(f"[SuperLookup] Saved AutoHotkey path: {file_path}")
48536
49294
 
48537
49295
  self._ahk_setup_status.setText(f"✓ Saved: {file_path}\n\nRestart Supervertaler to use this path.")
48538
49296
 
@@ -48553,11 +49311,11 @@ class SuperlookupTab(QWidget):
48553
49311
  Note: AutoHotkey is Windows-only, so skip on Linux/Mac.
48554
49312
  """
48555
49313
  global _ahk_process
48556
- print("[Superlookup] register_global_hotkey called")
49314
+ print("[SuperLookup] register_global_hotkey called")
48557
49315
 
48558
49316
  # AutoHotkey is Windows-only - skip on other platforms
48559
49317
  if sys.platform != 'win32':
48560
- print("[Superlookup] Skipping AutoHotkey registration (not Windows)")
49318
+ print("[SuperLookup] Skipping AutoHotkey registration (not Windows)")
48561
49319
  self.hotkey_registered = False
48562
49320
  return
48563
49321
 
@@ -48571,24 +49329,24 @@ class SuperlookupTab(QWidget):
48571
49329
  """
48572
49330
  try:
48573
49331
  from ahk import AHK
48574
- print("[Superlookup] ahk library available, attempting to use it...")
49332
+ print("[SuperLookup] ahk library available, attempting to use it...")
48575
49333
 
48576
49334
  # Find AutoHotkey executable using shared function
48577
49335
  ahk_exe, source = self._find_autohotkey_executable()
48578
49336
 
48579
49337
  # Create AHK instance (with executable path if found)
48580
49338
  if ahk_exe:
48581
- print(f"[Superlookup] Using AutoHotkey at: {ahk_exe} (source: {source})")
49339
+ print(f"[SuperLookup] Using AutoHotkey at: {ahk_exe} (source: {source})")
48582
49340
  self._ahk = AHK(executable_path=ahk_exe)
48583
49341
  else:
48584
49342
  # Let it try to find AHK on PATH (may fail)
48585
49343
  self._ahk = AHK()
48586
- print(f"[Superlookup] AHK instance created: {self._ahk}")
49344
+ print(f"[SuperLookup] AHK instance created: {self._ahk}")
48587
49345
 
48588
49346
  # Define hotkey callback
48589
49347
  def on_hotkey():
48590
49348
  """Called when Ctrl+Alt+L is pressed"""
48591
- print("[Superlookup] Hotkey triggered via ahk library!")
49349
+ print("[SuperLookup] Hotkey triggered via ahk library!")
48592
49350
  try:
48593
49351
  # Copy selection to clipboard
48594
49352
  self._ahk.send('^c') # Ctrl+C
@@ -48601,7 +49359,7 @@ class SuperlookupTab(QWidget):
48601
49359
  try:
48602
49360
  self._ahk.win_activate('Supervertaler')
48603
49361
  except Exception as e:
48604
- print(f"[Superlookup] win_activate error (non-critical): {e}")
49362
+ print(f"[SuperLookup] win_activate error (non-critical): {e}")
48605
49363
 
48606
49364
  # Trigger lookup in main thread
48607
49365
  if text:
@@ -48609,22 +49367,22 @@ class SuperlookupTab(QWidget):
48609
49367
  QTimer.singleShot(0, lambda: self.on_ahk_capture(text))
48610
49368
 
48611
49369
  except Exception as e:
48612
- print(f"[Superlookup] Error in hotkey callback: {e}")
49370
+ print(f"[SuperLookup] Error in hotkey callback: {e}")
48613
49371
 
48614
49372
  # Register the hotkey
48615
49373
  self._ahk.add_hotkey('^!l', callback=on_hotkey) # Ctrl+Alt+L
48616
49374
  self._ahk.start_hotkeys()
48617
49375
 
48618
- print("[Superlookup] ✓ Hotkey registered via ahk library: Ctrl+Alt+L")
49376
+ print("[SuperLookup] ✓ Hotkey registered via ahk library: Ctrl+Alt+L")
48619
49377
  self.hotkey_registered = True
48620
49378
  self._using_ahk_library = True
48621
49379
  return True
48622
49380
 
48623
49381
  except ImportError:
48624
- print("[Superlookup] ahk library not installed (pip install ahk)")
49382
+ print("[SuperLookup] ahk library not installed (pip install ahk)")
48625
49383
  return False
48626
49384
  except Exception as e:
48627
- print(f"[Superlookup] ahk library method failed: {e}")
49385
+ print(f"[SuperLookup] ahk library method failed: {e}")
48628
49386
  # Clean up on failure
48629
49387
  if hasattr(self, '_ahk'):
48630
49388
  try:
@@ -48705,11 +49463,13 @@ class SuperlookupTab(QWidget):
48705
49463
  self.hotkey_registered = False
48706
49464
 
48707
49465
  def start_file_watcher(self):
48708
- """Watch for signal file from AHK"""
49466
+ """Watch for signal files from AHK (Superlookup and MT Quick Lookup)"""
48709
49467
  self.signal_file = Path(__file__).parent / "lookup_signal.txt"
48710
49468
  self.capture_file = Path(__file__).parent / "temp_capture.txt"
48711
-
48712
- print(f"[Superlookup] File watcher started, watching: {self.signal_file}")
49469
+ self.mt_lookup_signal_file = Path(__file__).parent / "mt_lookup_signal.txt"
49470
+
49471
+ print(f"[SuperLookup] File watcher started, watching: {self.signal_file}")
49472
+ print(f"[QuickTrans] File watcher started, watching: {self.mt_lookup_signal_file}")
48713
49473
 
48714
49474
  # Create timer to check for signal file
48715
49475
  self.file_check_timer = QTimer()
@@ -48718,27 +49478,57 @@ class SuperlookupTab(QWidget):
48718
49478
 
48719
49479
  def check_for_signal(self):
48720
49480
  """Check if AHK wrote a signal file"""
49481
+ # Check for Superlookup signal
48721
49482
  if self.signal_file.exists():
48722
- print(f"[Superlookup] Signal file detected!")
49483
+ print(f"[SuperLookup] Signal file detected!")
48723
49484
  try:
48724
49485
  # Delete signal file
48725
49486
  self.signal_file.unlink()
48726
- print(f"[Superlookup] Signal file deleted")
48727
-
49487
+ print(f"[SuperLookup] Signal file deleted")
49488
+
48728
49489
  # Get text from clipboard (AHK already copied it)
48729
49490
  time.sleep(0.1) # Give clipboard a moment
48730
49491
  text = pyperclip.paste()
48731
-
49492
+
48732
49493
  # Trigger lookup
48733
49494
  if text:
48734
49495
  self.on_ahk_capture(text)
48735
49496
  except Exception as e:
48736
- print(f"[Superlookup] Error reading capture: {e}")
49497
+ print(f"[SuperLookup] Error reading capture: {e}")
49498
+
49499
+ # Check for MT Quick Lookup signal
49500
+ if hasattr(self, 'mt_lookup_signal_file') and self.mt_lookup_signal_file.exists():
49501
+ print(f"[QuickTrans] Signal file detected!")
49502
+ try:
49503
+ # Small delay to let AHK finish writing/close the file
49504
+ time.sleep(0.05)
49505
+
49506
+ # Delete signal file with retry for file lock
49507
+ for attempt in range(3):
49508
+ try:
49509
+ self.mt_lookup_signal_file.unlink()
49510
+ print(f"[QuickTrans] Signal file deleted")
49511
+ break
49512
+ except PermissionError:
49513
+ if attempt < 2:
49514
+ time.sleep(0.05)
49515
+ else:
49516
+ raise
49517
+
49518
+ # Get text from clipboard (AHK already copied it)
49519
+ time.sleep(0.1) # Give clipboard a moment
49520
+ text = pyperclip.paste()
49521
+
49522
+ # Trigger MT Quick Lookup
49523
+ if text:
49524
+ self.on_ahk_mt_lookup_capture(text)
49525
+ except Exception as e:
49526
+ print(f"[QuickTrans] Error reading capture: {e}")
48737
49527
 
48738
49528
  def on_ahk_capture(self, text):
48739
49529
  """Handle text captured by AHK"""
48740
49530
  try:
48741
- print(f"[Superlookup] on_ahk_capture called with text: {text[:50]}...")
49531
+ print(f"[SuperLookup] on_ahk_capture called with text: {text[:50]}...")
48742
49532
 
48743
49533
  # Bring Supervertaler to foreground
48744
49534
  main_window = self.window()
@@ -48768,39 +49558,96 @@ class SuperlookupTab(QWidget):
48768
49558
  QTimer.singleShot(250, lambda: self.show_superlookup(text))
48769
49559
 
48770
49560
  except Exception as e:
48771
- print(f"[Superlookup] Error handling capture: {e}")
48772
-
49561
+ print(f"[SuperLookup] Error handling capture: {e}")
49562
+
49563
+ def on_ahk_mt_lookup_capture(self, text):
49564
+ """Handle MT Quick Lookup text captured by AHK (Ctrl+Alt+M)"""
49565
+ try:
49566
+ print(f"[QuickTrans] on_ahk_mt_lookup_capture called with text: {text[:50]}...")
49567
+
49568
+ # Show popup directly without bringing Supervertaler to foreground
49569
+ # The popup has WindowStaysOnTopHint so it will appear over any app
49570
+ QTimer.singleShot(100, lambda: self.show_mt_quick_lookup_from_ahk(text))
49571
+
49572
+ except Exception as e:
49573
+ print(f"[QuickTrans] Error handling capture: {e}")
49574
+
49575
+ def show_mt_quick_lookup_from_ahk(self, text):
49576
+ """Show MT Quick Lookup popup with text from AHK capture"""
49577
+ try:
49578
+ print(f"[QuickTrans] show_mt_quick_lookup_from_ahk called with text: {text[:50]}...")
49579
+
49580
+ # Get main window reference for settings access
49581
+ main_window = self.main_window
49582
+ if not main_window:
49583
+ main_window = self.window()
49584
+
49585
+ if not main_window:
49586
+ print("[QuickTrans] ERROR: Could not find main window")
49587
+ return
49588
+
49589
+ # Get language settings
49590
+ source_lang = getattr(main_window, 'source_language', 'English')
49591
+ target_lang = getattr(main_window, 'target_language', 'Dutch')
49592
+
49593
+ # Import and show MT Quick Lookup popup
49594
+ from modules.quicktrans import MTQuickPopup
49595
+
49596
+ # Create popup without Qt parent so it can appear independently over any app
49597
+ # Pass main_window as parent_app for API access
49598
+ popup = MTQuickPopup(
49599
+ parent_app=main_window,
49600
+ source_text=text,
49601
+ source_lang=source_lang,
49602
+ target_lang=target_lang,
49603
+ parent=None # No Qt parent - allows independent window
49604
+ )
49605
+
49606
+ # Store reference to prevent garbage collection
49607
+ self._ahk_mt_popup = popup
49608
+
49609
+ # Show and ensure it's on top
49610
+ popup.show()
49611
+ popup.raise_()
49612
+ popup.activateWindow()
49613
+ print(f"[QuickTrans] Popup shown for text: {text[:30]}...")
49614
+
49615
+ except Exception as e:
49616
+ print(f"[QuickTrans] Error showing popup: {e}")
49617
+ import traceback
49618
+ traceback.print_exc()
49619
+
48773
49620
  def show_superlookup(self, text):
48774
49621
  """Show Superlookup with pre-filled text"""
48775
49622
  try:
48776
- print(f"[Superlookup] show_superlookup called with text: {text[:50]}...")
49623
+ print(f"[SuperLookup] show_superlookup called with text: {text[:50]}...")
48777
49624
 
48778
49625
  # Get main window reference
48779
49626
  main_window = self.main_window
48780
49627
  if not main_window:
48781
49628
  main_window = self.window()
48782
49629
 
48783
- print(f"[Superlookup] Main window found: {main_window is not None}")
48784
- print(f"[Superlookup] Main window type: {type(main_window).__name__}")
48785
- print(f"[Superlookup] Has main_tabs: {hasattr(main_window, 'main_tabs')}")
49630
+ print(f"[SuperLookup] Main window found: {main_window is not None}")
49631
+ print(f"[SuperLookup] Main window type: {type(main_window).__name__}")
49632
+ print(f"[SuperLookup] Has main_tabs: {hasattr(main_window, 'main_tabs')}")
48786
49633
 
48787
49634
  # Switch to Tools tab (main_tabs index 3)
48788
49635
  # Tab structure: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
48789
49636
  if hasattr(main_window, 'main_tabs'):
48790
- print(f"[Superlookup] Current main_tab index: {main_window.main_tabs.currentIndex()}")
49637
+ print(f"[SuperLookup] Current main_tab index: {main_window.main_tabs.currentIndex()}")
48791
49638
  main_window.main_tabs.setCurrentIndex(3) # Tools tab
48792
- print(f"[Superlookup] Switched to Tools tab (index 2)")
49639
+ print(f"[SuperLookup] Switched to Tools tab (index 2)")
48793
49640
  QApplication.processEvents() # Force GUI update
48794
49641
  else:
48795
- print(f"[Superlookup] WARNING: Main window has no main_tabs attribute!")
49642
+ print(f"[SuperLookup] WARNING: Main window has no main_tabs attribute!")
48796
49643
 
48797
49644
  # Switch to Superlookup within modules_tabs
48798
49645
  if hasattr(main_window, 'modules_tabs'):
48799
- print(f"[Superlookup] Current modules_tab index: {main_window.modules_tabs.currentIndex()}")
49646
+ print(f"[SuperLookup] Current modules_tab index: {main_window.modules_tabs.currentIndex()}")
48800
49647
  for i in range(main_window.modules_tabs.count()):
48801
49648
  if "Superlookup" in main_window.modules_tabs.tabText(i):
48802
49649
  main_window.modules_tabs.setCurrentIndex(i)
48803
- print(f"[Superlookup] Switched to Superlookup tab (index {i})")
49650
+ print(f"[SuperLookup] Switched to Superlookup tab (index {i})")
48804
49651
  QApplication.processEvents() # Force GUI update
48805
49652
  break
48806
49653
 
@@ -48808,23 +49655,23 @@ class SuperlookupTab(QWidget):
48808
49655
  QTimer.singleShot(100, lambda: self._fill_and_search(text))
48809
49656
 
48810
49657
  except Exception as e:
48811
- print(f"[Superlookup] Error showing lookup: {e}")
49658
+ print(f"[SuperLookup] Error showing lookup: {e}")
48812
49659
 
48813
49660
  def _fill_and_search(self, text):
48814
49661
  """Fill in text and trigger search (called after tab switching completes)"""
48815
49662
  try:
48816
- print(f"[Superlookup] _fill_and_search called")
49663
+ print(f"[SuperLookup] _fill_and_search called")
48817
49664
  # Fill in text and trigger lookup
48818
49665
  if hasattr(self, 'source_text'):
48819
49666
  self.source_text.setCurrentText(text)
48820
- print(f"[Superlookup] Text filled in source_text field")
49667
+ print(f"[SuperLookup] Text filled in source_text field")
48821
49668
  # Trigger lookup by calling perform_lookup directly
48822
49669
  self.perform_lookup()
48823
- print(f"[Superlookup] perform_lookup() called")
49670
+ print(f"[SuperLookup] perform_lookup() called")
48824
49671
  else:
48825
- print(f"[Superlookup] ERROR: source_text widget not found!")
49672
+ print(f"[SuperLookup] ERROR: source_text widget not found!")
48826
49673
  except Exception as e:
48827
- print(f"[Superlookup] Error in _fill_and_search: {e}")
49674
+ print(f"[SuperLookup] Error in _fill_and_search: {e}")
48828
49675
  import traceback
48829
49676
  traceback.print_exc()
48830
49677