supervertaler 1.9.189__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.189"
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)
18336
+
18337
+ # --- LEFT COLUMN: Settings ---
18338
+ left_column = QVBoxLayout()
18339
+ left_column.setSpacing(15)
17999
18340
 
18000
- # Enable voice commands checkbox
18001
- voice_cmd_enabled = QCheckBox("Enable voice commands (spoken phrases trigger actions)")
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,12 +20738,12 @@ 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;")
20741
+ advanced_filter_btn.setStyleSheet("background-color: #2196F3; color: white; font-weight: bold; padding: 3px 5px;")
20351
20742
 
20352
20743
  # Sort dropdown button (similar to memoQ)
20353
20744
  sort_btn = QPushButton("⇅ Sort")
20354
20745
  sort_btn.setMaximumWidth(100)
20355
- sort_btn.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold;")
20746
+ sort_btn.setStyleSheet("background-color: #FF9800; color: white; font-weight: bold; padding: 3px 5px;")
20356
20747
  sort_menu = QMenu(self)
20357
20748
 
20358
20749
  # Initialize sort state if not exists
@@ -20422,7 +20813,7 @@ class SupervertalerQt(QMainWindow):
20422
20813
  # Show Invisibles button with dropdown menu
20423
20814
  show_invisibles_btn_home = QPushButton("¶ Show Invisibles")
20424
20815
  show_invisibles_btn_home.setMaximumWidth(140)
20425
- 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;")
20426
20817
  show_invisibles_menu_home = QMenu(show_invisibles_btn_home)
20427
20818
 
20428
20819
  # Use the same actions (they're stored as instance variables)
@@ -20718,12 +21109,12 @@ class SupervertalerQt(QMainWindow):
20718
21109
 
20719
21110
  preview_prompt_btn = QPushButton("🧪 Preview Prompts")
20720
21111
  preview_prompt_btn.setToolTip("Preview the complete assembled prompt\n(System Prompt + Custom Prompts + current segment)")
20721
- 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;")
20722
21113
  preview_prompt_btn.clicked.connect(self._preview_combined_prompt_from_grid)
20723
21114
  toolbar_layout.addWidget(preview_prompt_btn)
20724
21115
 
20725
21116
  dictate_btn = QPushButton("🎤 Dictation")
20726
- 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;")
20727
21118
  dictate_btn.clicked.connect(self.start_voice_dictation)
20728
21119
  dictate_btn.setToolTip("Start/stop voice dictation (F9)")
20729
21120
  toolbar_layout.addWidget(dictate_btn)
@@ -20737,7 +21128,7 @@ class SupervertalerQt(QMainWindow):
20737
21128
  background-color: #757575;
20738
21129
  color: white;
20739
21130
  font-weight: bold;
20740
- padding: 4px 8px;
21131
+ padding: 3px 5px;
20741
21132
  border-radius: 3px;
20742
21133
  }
20743
21134
  QPushButton:checked {
@@ -20752,7 +21143,7 @@ class SupervertalerQt(QMainWindow):
20752
21143
  toolbar_layout.addStretch()
20753
21144
 
20754
21145
  save_next_btn = QPushButton("✓ Confirm && Next")
20755
- 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;")
20756
21147
  save_next_btn.clicked.connect(self.confirm_selected_or_next)
20757
21148
  save_next_btn.setToolTip("Confirm current segment and go to next unconfirmed (Ctrl+Enter)")
20758
21149
  toolbar_layout.addWidget(save_next_btn)
@@ -21361,9 +21752,21 @@ class SupervertalerQt(QMainWindow):
21361
21752
  # Configure columns
21362
21753
  self.table.setColumnCount(5)
21363
21754
  self.table.setHorizontalHeaderLabels(["#", "Type", "Source", "Target", "Status"])
21364
-
21365
- # Column widths - Source and Target columns stretch to fill space, others are interactive
21755
+
21756
+ # Explicitly set header font to normal weight (not bold)
21366
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
21367
21770
  header.setSectionResizeMode(0, QHeaderView.ResizeMode.Interactive) # ID
21368
21771
  header.setSectionResizeMode(1, QHeaderView.ResizeMode.Interactive) # Type
21369
21772
  header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) # Source - stretch to fill space
@@ -21377,7 +21780,7 @@ class SupervertalerQt(QMainWindow):
21377
21780
  self.table.setColumnWidth(1, 40) # Type - narrower
21378
21781
  self.table.setColumnWidth(2, 400) # Source
21379
21782
  self.table.setColumnWidth(3, 400) # Target
21380
- self.table.setColumnWidth(4, 60) # Status - compact
21783
+ self.table.setColumnWidth(4, 50) # Status - compact width
21381
21784
 
21382
21785
  # Enable word wrap in cells (both display and edit mode)
21383
21786
  self.table.setWordWrap(True)
@@ -21407,6 +21810,51 @@ class SupervertalerQt(QMainWindow):
21407
21810
  QTableWidget::item:last-child {
21408
21811
  border-right: none;
21409
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
+ }
21410
21858
  """)
21411
21859
 
21412
21860
  # Simplified editing: Double-click only (no F2 key) - companion tool philosophy
@@ -21429,8 +21877,8 @@ class SupervertalerQt(QMainWindow):
21429
21877
  # Debug: Confirm signal connections
21430
21878
  self.log("🔌 Table signals connected: currentCellChanged, itemClicked, cellDoubleClicked, itemSelectionChanged")
21431
21879
 
21432
- # Add precision scroll buttons (memoQ-style)
21433
- self.add_precision_scroll_buttons()
21880
+ # Precision scroll buttons removed (user preference)
21881
+ # self.add_precision_scroll_buttons()
21434
21882
 
21435
21883
  def add_precision_scroll_buttons(self):
21436
21884
  """Add precision scroll buttons at top/bottom of scrollbar (memoQ-style)"""
@@ -21656,10 +22104,10 @@ class SupervertalerQt(QMainWindow):
21656
22104
  editor_widget.dictate_btn = dictate_btn
21657
22105
 
21658
22106
  save_btn = QPushButton("💾 Save")
21659
- 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;")
21660
22108
  save_btn.clicked.connect(self.save_tab_segment)
21661
22109
  save_next_btn = QPushButton("✓ Confirm && Next (Ctrl+Enter)")
21662
- 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;")
21663
22111
  save_next_btn.clicked.connect(self.confirm_selected_or_next)
21664
22112
 
21665
22113
  button_layout.addWidget(copy_btn)
@@ -22003,6 +22451,10 @@ class SupervertalerQt(QMainWindow):
22003
22451
  # Update UI
22004
22452
  self.project_file_path = None
22005
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
+
22006
22458
  self.update_window_title()
22007
22459
  self.load_segments_to_grid()
22008
22460
  self.initialize_tm_database() # Initialize TM for this project
@@ -22031,16 +22483,16 @@ class SupervertalerQt(QMainWindow):
22031
22483
  if hasattr(self.lookup_tab, '_ahk') and self.lookup_tab._ahk:
22032
22484
  try:
22033
22485
  self.lookup_tab._ahk.stop_hotkeys()
22034
- print("[Superlookup] ahk library hotkeys stopped")
22486
+ print("[SuperLookup] ahk library hotkeys stopped")
22035
22487
  except Exception as e:
22036
- print(f"[Superlookup] Error stopping ahk library: {e}")
22488
+ print(f"[SuperLookup] Error stopping ahk library: {e}")
22037
22489
 
22038
22490
  # Terminate external AutoHotkey process if running (fallback method)
22039
22491
  if hasattr(self, 'lookup_tab') and hasattr(self.lookup_tab, 'ahk_process') and self.lookup_tab.ahk_process:
22040
22492
  try:
22041
22493
  self.lookup_tab.ahk_process.terminate()
22042
22494
  self.lookup_tab.ahk_process.wait(timeout=2)
22043
- print("[Superlookup] AHK process terminated")
22495
+ print("[SuperLookup] AHK process terminated")
22044
22496
  except:
22045
22497
  # Force kill if terminate doesn't work
22046
22498
  try:
@@ -22048,7 +22500,7 @@ class SupervertalerQt(QMainWindow):
22048
22500
  except:
22049
22501
  pass
22050
22502
  except Exception as e:
22051
- print(f"[Superlookup] Error terminating AHK: {e}")
22503
+ print(f"[SuperLookup] Error terminating AHK: {e}")
22052
22504
 
22053
22505
  # Accept the close event
22054
22506
  event.accept()
@@ -22084,6 +22536,12 @@ class SupervertalerQt(QMainWindow):
22084
22536
  self.project_file_path = file_path
22085
22537
  self.project_modified = False
22086
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
+
22087
22545
  # Sync global language settings with project languages
22088
22546
  if self.current_project.source_lang:
22089
22547
  self.source_language = self.current_project.source_lang
@@ -23435,9 +23893,21 @@ class SupervertalerQt(QMainWindow):
23435
23893
  original_path = getattr(self, 'original_docx', None) or getattr(self, 'current_document_path', None)
23436
23894
  if original_path and os.path.exists(original_path):
23437
23895
  self.current_project.original_docx_path = original_path
23438
-
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
+
23439
23906
  with open(file_path, 'w', encoding='utf-8') as f:
23440
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
23441
23911
 
23442
23912
  self.project_modified = False
23443
23913
  self.update_window_title()
@@ -29152,7 +29622,11 @@ class SupervertalerQt(QMainWindow):
29152
29622
  def load_segments_to_grid(self):
29153
29623
  """Load segments into the grid with termbase highlighting"""
29154
29624
  self.log(f"🔄🔄🔄 load_segments_to_grid CALLED - this will RELOAD grid from segment data!")
29155
-
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
+
29156
29630
  # Clear row color settings cache to ensure fresh settings are loaded
29157
29631
  if hasattr(self, '_row_color_settings_cached'):
29158
29632
  delattr(self, '_row_color_settings_cached')
@@ -29229,7 +29703,7 @@ class SupervertalerQt(QMainWindow):
29229
29703
  id_item.setForeground(QColor(segment_num_color))
29230
29704
  id_item.setBackground(QColor()) # Default background from theme
29231
29705
  # Smaller font for segment numbers
29232
- 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))
29233
29707
  id_item.setFont(seg_num_font)
29234
29708
  self.table.setItem(row, 0, id_item)
29235
29709
 
@@ -29321,7 +29795,7 @@ class SupervertalerQt(QMainWindow):
29321
29795
  type_item.setForeground(QColor("#388E3C")) # Green for list items (works in both themes)
29322
29796
 
29323
29797
  # Smaller font for type symbols
29324
- 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))
29325
29799
  type_item.setFont(type_font)
29326
29800
 
29327
29801
  self.table.setItem(row, 1, type_item)
@@ -29771,7 +30245,10 @@ class SupervertalerQt(QMainWindow):
29771
30245
  if shortcut_badge_tooltip:
29772
30246
  badge_label.setToolTip(f"Press {shortcut_badge_tooltip} to insert")
29773
30247
  header_layout.addWidget(badge_label)
29774
-
30248
+
30249
+ # Add stretch to push navigation controls to the right (aligned with scrollbar)
30250
+ header_layout.addStretch()
30251
+
29775
30252
  nav_label = None
29776
30253
  nav_buttons = None
29777
30254
 
@@ -29837,8 +30314,7 @@ class SupervertalerQt(QMainWindow):
29837
30314
  self.theme_aware_arrows.extend([prev_btn, next_btn])
29838
30315
 
29839
30316
  nav_buttons = [prev_btn, next_btn]
29840
-
29841
- header_layout.addStretch()
30317
+
29842
30318
  main_layout.addLayout(header_layout)
29843
30319
 
29844
30320
  # Text area
@@ -30761,19 +31237,24 @@ class SupervertalerQt(QMainWindow):
30761
31237
  widget.setToolTip(f"Notes: {segment.notes.strip()}")
30762
31238
 
30763
31239
  status_def = get_status(segment.status)
30764
- 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
30765
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
30766
31252
  status_label.setToolTip(status_def.label)
30767
- # Smaller red X for "not_started" to match green checkmark visual size
30768
- font_size = "8px" if segment.status == "not_started" else "13px"
30769
- # Make confirmed checkmark green
30770
- color = "color: #2e7d32;" if segment.status == "confirmed" else ""
30771
-
30772
- # 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)
30773
31255
  if has_notes:
30774
- status_label.setStyleSheet(f"font-size: {font_size}; {color} padding: 2px 4px; background-color: rgba(255, 152, 0, 0.35); border-radius: 3px;")
30775
- else:
30776
- 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
30777
31258
  layout.addWidget(status_label)
30778
31259
 
30779
31260
  # Only add match label if there's a match percentage
@@ -30885,14 +31366,17 @@ class SupervertalerQt(QMainWindow):
30885
31366
  """Auto-resize all rows to fit content - Compact version"""
30886
31367
  if not hasattr(self, 'table') or not self.table:
30887
31368
  return
30888
-
31369
+
30889
31370
  # Reduce width slightly to account for padding and prevent text cut-off
30890
31371
  width_reduction = 8
30891
-
31372
+
30892
31373
  # Manually calculate and set row heights for compact display
30893
31374
  for row in range(self.table.rowCount()):
31375
+ # Keep UI responsive during large grid updates
31376
+ if row % 50 == 0:
31377
+ QApplication.processEvents()
30894
31378
  self._auto_resize_single_row(row, width_reduction)
30895
-
31379
+
30896
31380
  self.log("✓ Auto-resized rows to fit content (compact)")
30897
31381
  self._enforce_status_row_heights()
30898
31382
 
@@ -30939,26 +31423,30 @@ class SupervertalerQt(QMainWindow):
30939
31423
  def apply_font_to_grid(self):
30940
31424
  """Apply selected font to all grid cells"""
30941
31425
  font = QFont(self.default_font_family, self.default_font_size)
30942
-
31426
+
30943
31427
  self.table.setFont(font)
30944
-
30945
- # Also update header font - same size as grid content, just bold
30946
- 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)
30947
31431
  self.table.horizontalHeader().setFont(header_font)
30948
-
31432
+
30949
31433
  # Update fonts in QTextEdit widgets (source and target columns)
30950
31434
  if hasattr(self, 'table') and self.table:
30951
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
+
30952
31440
  # Source column (2) - ReadOnlyGridTextEditor
30953
31441
  source_widget = self.table.cellWidget(row, 2)
30954
31442
  if source_widget and isinstance(source_widget, ReadOnlyGridTextEditor):
30955
31443
  source_widget.setFont(font)
30956
-
31444
+
30957
31445
  # Target column (3) - EditableGridTextEditor
30958
31446
  target_widget = self.table.cellWidget(row, 3)
30959
31447
  if target_widget and isinstance(target_widget, EditableGridTextEditor):
30960
31448
  target_widget.setFont(font)
30961
-
31449
+
30962
31450
  # Adjust segment number column width based on font size
30963
31451
  self._update_segment_column_width()
30964
31452
 
@@ -30982,12 +31470,12 @@ class SupervertalerQt(QMainWindow):
30982
31470
  # Measure the width of the largest number (as string)
30983
31471
  text_width = fm.horizontalAdvance(str(max_segment))
30984
31472
 
30985
- # Add padding (10px on each side = 20px total)
30986
- new_width = text_width + 20
30987
-
30988
- # Ensure minimum width for very small numbers
30989
- new_width = max(30, new_width)
30990
-
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
+
30991
31479
  self.table.setColumnWidth(0, new_width)
30992
31480
 
30993
31481
  def set_font_family(self, family_name: str):
@@ -31019,14 +31507,18 @@ class SupervertalerQt(QMainWindow):
31019
31507
  """Refresh tag highlight colors in all grid cells"""
31020
31508
  if not hasattr(self, 'table') or not self.table:
31021
31509
  return
31022
-
31510
+
31023
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
+
31024
31516
  # Source column (2) - ReadOnlyGridTextEditor
31025
31517
  source_widget = self.table.cellWidget(row, 2)
31026
31518
  if source_widget and isinstance(source_widget, ReadOnlyGridTextEditor):
31027
31519
  if hasattr(source_widget, 'highlighter'):
31028
31520
  source_widget.highlighter.set_tag_color(EditableGridTextEditor.tag_highlight_color)
31029
-
31521
+
31030
31522
  # Target column (3) - EditableGridTextEditor
31031
31523
  target_widget = self.table.cellWidget(row, 3)
31032
31524
  if target_widget and isinstance(target_widget, EditableGridTextEditor):
@@ -31091,18 +31583,22 @@ class SupervertalerQt(QMainWindow):
31091
31583
  """Apply alternating row colors to all source and target cells in the grid"""
31092
31584
  if not hasattr(self, 'table') or not self.table:
31093
31585
  return
31094
-
31586
+
31095
31587
  # Clear cached settings to force reload
31096
31588
  if hasattr(self, '_row_color_settings_cached'):
31097
31589
  delattr(self, '_row_color_settings_cached')
31098
-
31590
+
31099
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
+
31100
31596
  source_widget = self.table.cellWidget(row, 2)
31101
31597
  target_widget = self.table.cellWidget(row, 3)
31102
-
31598
+
31103
31599
  if source_widget and target_widget:
31104
31600
  self._apply_row_color(row, source_widget, target_widget)
31105
-
31601
+
31106
31602
  self.log("✓ Alternating row colors applied")
31107
31603
 
31108
31604
  def on_font_changed(self):
@@ -36976,9 +37472,9 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
36976
37472
  """Update spellcheck button style based on enabled state"""
36977
37473
  if hasattr(self, 'spellcheck_btn'):
36978
37474
  if self.spellcheck_enabled:
36979
- 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;")
36980
37476
  else:
36981
- 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;")
36982
37478
 
36983
37479
  def _toggle_spellcheck(self, checked=None):
36984
37480
  """Toggle spellcheck on/off"""
@@ -37687,16 +38183,16 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37687
38183
  if not self.current_project.segments:
37688
38184
  return
37689
38185
 
37690
- # Show progress dialog for large projects
38186
+ # Show progress dialog during sorting
37691
38187
  from PyQt6.QtWidgets import QProgressDialog
37692
38188
  from PyQt6.QtCore import Qt
37693
38189
 
37694
38190
  progress = QProgressDialog("Sorting segments, please wait...", None, 0, 0, self)
37695
38191
  progress.setWindowTitle("Sorting")
37696
38192
  progress.setWindowModality(Qt.WindowModality.WindowModal)
37697
- progress.setMinimumDuration(500) # Only show if operation takes > 500ms
37698
- progress.setValue(0)
37699
- QApplication.processEvents() # Show dialog immediately
38193
+ progress.setMinimumDuration(0) # Show immediately
38194
+ progress.show()
38195
+ QApplication.processEvents() # Force UI update
37700
38196
 
37701
38197
  try:
37702
38198
  # Store original document order if not already stored
@@ -37708,7 +38204,12 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37708
38204
 
37709
38205
  # If sort_type is None, restore document order
37710
38206
  if sort_type is None:
37711
- self.current_project.segments = self._original_segment_order.copy()
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()
37712
38213
 
37713
38214
  # Set pagination to "All" to show all segments
37714
38215
  if hasattr(self, 'page_size_combo') and self._widget_is_alive(self.page_size_combo):
@@ -43760,23 +44261,58 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
43760
44261
  """Call DeepL API"""
43761
44262
  try:
43762
44263
  import deepl
43763
-
44264
+
43764
44265
  if not api_key:
43765
44266
  api_keys = self.load_api_keys()
43766
44267
  api_key = api_keys.get("deepl")
43767
-
44268
+
43768
44269
  if not api_key:
43769
44270
  return "[DeepL requires API key]"
43770
-
44271
+
43771
44272
  translator = deepl.Translator(api_key)
43772
-
43773
- # Convert language codes (DeepL uses uppercase)
44273
+
44274
+ # Convert source language code (DeepL uses uppercase, no variant needed for source)
43774
44275
  src_code = source_lang.split('-')[0].split('_')[0].upper()
43775
- tgt_code = target_lang.split('-')[0].split('_')[0].upper()
43776
-
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
+
43777
44313
  result = translator.translate_text(text, source_lang=src_code, target_lang=tgt_code)
43778
44314
  return result.text
43779
-
44315
+
43780
44316
  except ImportError:
43781
44317
  return "[DeepL requires: pip install deepl]"
43782
44318
  except Exception as e:
@@ -44222,8 +44758,11 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
44222
44758
  # Determine segment number color based on theme
44223
44759
  is_dark_theme = theme.name == "Dark"
44224
44760
  segment_num_color = theme.text if is_dark_theme else "black"
44225
-
44761
+
44226
44762
  for row in range(self.table.rowCount()):
44763
+ # Keep UI responsive during large grid updates
44764
+ if row % 50 == 0:
44765
+ QApplication.processEvents()
44227
44766
  id_item = self.table.item(row, 0)
44228
44767
  if id_item:
44229
44768
  # Don't change currently highlighted row (orange background)
@@ -45312,7 +45851,7 @@ class SuperlookupTab(QWidget):
45312
45851
  self.main_window = parent # Store reference to main window for database access
45313
45852
  self.user_data_path = user_data_path # Store user data path for web cache
45314
45853
 
45315
- print("[Superlookup] SuperlookupTab.__init__ called")
45854
+ print("[SuperLookup] SuperlookupTab.__init__ called")
45316
45855
 
45317
45856
  # Get theme manager from main window (try parent first, then parent's parent for dialogs)
45318
45857
  self.theme_manager = getattr(parent, 'theme_manager', None)
@@ -45321,16 +45860,16 @@ class SuperlookupTab(QWidget):
45321
45860
  parent_parent = getattr(parent, 'parent', lambda: None)()
45322
45861
  if parent_parent:
45323
45862
  self.theme_manager = getattr(parent_parent, 'theme_manager', None)
45324
- print(f"[Superlookup] theme_manager: {self.theme_manager is not None}")
45863
+ print(f"[SuperLookup] theme_manager: {self.theme_manager is not None}")
45325
45864
 
45326
45865
  # Import lookup engine
45327
45866
  try:
45328
45867
  from modules.superlookup import SuperlookupEngine, LookupResult
45329
45868
  self.SuperlookupEngine = SuperlookupEngine
45330
45869
  self.LookupResult = LookupResult
45331
- print("[Superlookup] Successfully imported SuperlookupEngine")
45870
+ print("[SuperLookup] Successfully imported SuperlookupEngine")
45332
45871
  except ImportError as e:
45333
- print(f"[Superlookup] IMPORT ERROR: {e}")
45872
+ print(f"[SuperLookup] IMPORT ERROR: {e}")
45334
45873
  QMessageBox.critical(
45335
45874
  self,
45336
45875
  "Missing Module",
@@ -45412,7 +45951,7 @@ class SuperlookupTab(QWidget):
45412
45951
  if self.db_manager or self.termbase_mgr:
45413
45952
  self.populate_language_dropdowns()
45414
45953
  self._languages_populated = True
45415
- print("[Superlookup] Languages populated on first show")
45954
+ print("[SuperLookup] Languages populated on first show")
45416
45955
 
45417
45956
  def init_ui(self):
45418
45957
  """Initialize the UI"""
@@ -45421,7 +45960,7 @@ class SuperlookupTab(QWidget):
45421
45960
  layout.setSpacing(5) # Reduced from 10 to 5 for consistency
45422
45961
 
45423
45962
  # Header
45424
- header = QLabel("🔍 Superlookup")
45963
+ header = QLabel("🔍 SuperLookup")
45425
45964
  header.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1976D2;")
45426
45965
  layout.addWidget(header, 0) # 0 = no stretch, stays compact
45427
45966
 
@@ -45915,7 +46454,7 @@ class SuperlookupTab(QWidget):
45915
46454
  })
45916
46455
 
45917
46456
  except Exception as e:
45918
- print(f"[Superlookup] MT error ({provider_name}): {e}")
46457
+ print(f"[SuperLookup] MT error ({provider_name}): {e}")
45919
46458
  results.append({
45920
46459
  'provider': provider_name,
45921
46460
  'translation': f"[Error: {str(e)}]",
@@ -46006,9 +46545,9 @@ class SuperlookupTab(QWidget):
46006
46545
  self.web_profile = QWebEngineProfile("SuperlookupProfile", self)
46007
46546
  self.web_profile.setPersistentStoragePath(storage_path)
46008
46547
  self.web_profile.setPersistentCookiesPolicy(QWebEngineProfile.PersistentCookiesPolicy.ForcePersistentCookies)
46009
- 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}")
46010
46549
  except ImportError:
46011
- print("[Superlookup] QWebEngineView not available - external browser only")
46550
+ print("[SuperLookup] QWebEngineView not available - external browser only")
46012
46551
  self.QWebEngineView = None
46013
46552
  self.QWebEngineProfile = None
46014
46553
 
@@ -46460,7 +46999,7 @@ class SuperlookupTab(QWidget):
46460
46999
  self.web_view_stack.addWidget(web_view)
46461
47000
  self.web_views[resource['id']] = web_view
46462
47001
 
46463
- print(f"[Superlookup] Created web view for {resource['name']} (lazy load)")
47002
+ print(f"[SuperLookup] Created web view for {resource['name']} (lazy load)")
46464
47003
 
46465
47004
  def _get_web_view_index(self, resource_id):
46466
47005
  """Get the stack index for a web view by resource ID"""
@@ -46477,7 +47016,7 @@ class SuperlookupTab(QWidget):
46477
47016
 
46478
47017
  self._update_web_view_for_mode()
46479
47018
  self._show_web_welcome_message()
46480
- 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}")
46481
47020
 
46482
47021
  def _update_web_view_for_mode(self):
46483
47022
  """Update the view stack based on current mode"""
@@ -47110,11 +47649,11 @@ class SuperlookupTab(QWidget):
47110
47649
  if index == 2:
47111
47650
  # Initialize Supermemory when tab is first viewed
47112
47651
  if not self.supermemory_engine:
47113
- print("[Superlookup] Supermemory tab viewed - initializing engine")
47652
+ print("[SuperLookup] Supermemory tab viewed - initializing engine")
47114
47653
  self.init_supermemory()
47115
47654
  # Settings tab is at index 5
47116
47655
  elif index == 5:
47117
- print("[Superlookup] Settings tab viewed - refreshing resource lists")
47656
+ print("[SuperLookup] Settings tab viewed - refreshing resource lists")
47118
47657
  self.refresh_tm_list()
47119
47658
  self.refresh_termbase_list()
47120
47659
  self.populate_language_dropdowns()
@@ -47122,19 +47661,19 @@ class SuperlookupTab(QWidget):
47122
47661
  def on_tm_search_toggled(self, state):
47123
47662
  """Handle TM search checkbox toggle"""
47124
47663
  self.search_tm_enabled = (state == Qt.CheckState.Checked.value)
47125
- 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'}")
47126
47665
 
47127
47666
  def on_termbase_search_toggled(self, state):
47128
47667
  """Handle termbase search checkbox toggle"""
47129
47668
  self.search_termbase_enabled = (state == Qt.CheckState.Checked.value)
47130
- 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'}")
47131
47670
 
47132
47671
  def _on_web_resource_checkbox_changed(self, index: int, state: int):
47133
47672
  """Handle web resource checkbox change - show/hide corresponding sidebar button"""
47134
47673
  is_checked = (state == Qt.CheckState.Checked.value)
47135
47674
  if hasattr(self, 'web_resource_buttons') and index < len(self.web_resource_buttons):
47136
47675
  self.web_resource_buttons[index].setVisible(is_checked)
47137
- 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'}")
47138
47677
 
47139
47678
  # If the hidden resource was selected, select the first visible one
47140
47679
  if not is_checked and hasattr(self, 'current_web_resource_index') and self.current_web_resource_index == index:
@@ -47152,18 +47691,18 @@ class SuperlookupTab(QWidget):
47152
47691
  checkbox.deleteLater()
47153
47692
  self.tm_checkboxes.clear()
47154
47693
 
47155
- print(f"[Superlookup] refresh_tm_list called")
47156
- 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}")
47157
47696
 
47158
47697
  # Get TMs from main window's database
47159
47698
  if self.main_window and hasattr(self.main_window, 'db_manager') and self.main_window.db_manager:
47160
47699
  try:
47161
- print(f"[Superlookup] db_manager found, querying TMs...")
47700
+ print(f"[SuperLookup] db_manager found, querying TMs...")
47162
47701
  cursor = self.main_window.db_manager.cursor
47163
47702
  cursor.execute("SELECT id, name, tm_id FROM translation_memories ORDER BY name")
47164
47703
  tms = cursor.fetchall()
47165
47704
 
47166
- print(f"[Superlookup] Query returned {len(tms)} TMs")
47705
+ print(f"[SuperLookup] Query returned {len(tms)} TMs")
47167
47706
 
47168
47707
  for db_id, tm_name, tm_id_str in tms:
47169
47708
  checkbox = CheckmarkCheckBox(f"{tm_name} (ID: {db_id})")
@@ -47174,13 +47713,13 @@ class SuperlookupTab(QWidget):
47174
47713
  # Insert before the stretch at the end
47175
47714
  self.tm_scroll_layout.insertWidget(len(self.tm_checkboxes) - 1, checkbox)
47176
47715
 
47177
- print(f"[Superlookup] ✓ Loaded {len(tms)} TMs")
47716
+ print(f"[SuperLookup] ✓ Loaded {len(tms)} TMs")
47178
47717
  except Exception as e:
47179
- print(f"[Superlookup] ✗ Error loading TMs: {e}")
47718
+ print(f"[SuperLookup] ✗ Error loading TMs: {e}")
47180
47719
  import traceback
47181
47720
  traceback.print_exc()
47182
47721
  else:
47183
- print(f"[Superlookup] db_manager not available")
47722
+ print(f"[SuperLookup] db_manager not available")
47184
47723
  # Add placeholder label
47185
47724
  placeholder = QLabel("No database connection - TMs unavailable")
47186
47725
  placeholder.setStyleSheet("color: #999; font-style: italic;")
@@ -47194,16 +47733,16 @@ class SuperlookupTab(QWidget):
47194
47733
  checkbox.deleteLater()
47195
47734
  self.tb_checkboxes.clear()
47196
47735
 
47197
- print(f"[Superlookup] refresh_termbase_list called")
47198
- 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}")
47199
47738
 
47200
47739
  # Try termbase_mgr first (preferred method)
47201
47740
  if self.main_window and hasattr(self.main_window, 'termbase_mgr') and self.main_window.termbase_mgr:
47202
47741
  try:
47203
- print(f"[Superlookup] termbase_mgr found, querying termbases...")
47742
+ print(f"[SuperLookup] termbase_mgr found, querying termbases...")
47204
47743
  termbases = self.main_window.termbase_mgr.get_all_termbases()
47205
47744
 
47206
- print(f"[Superlookup] get_all_termbases() returned {len(termbases)} termbases")
47745
+ print(f"[SuperLookup] get_all_termbases() returned {len(termbases)} termbases")
47207
47746
 
47208
47747
  for tb in termbases:
47209
47748
  tb_id = tb.get('id')
@@ -47215,22 +47754,22 @@ class SuperlookupTab(QWidget):
47215
47754
  # Insert before the stretch at the end
47216
47755
  self.tb_scroll_layout.insertWidget(len(self.tb_checkboxes) - 1, checkbox)
47217
47756
 
47218
- print(f"[Superlookup] ✓ Loaded {len(termbases)} termbases via termbase_mgr")
47757
+ print(f"[SuperLookup] ✓ Loaded {len(termbases)} termbases via termbase_mgr")
47219
47758
  return
47220
47759
  except Exception as e:
47221
- print(f"[Superlookup] ✗ Error loading termbases via termbase_mgr: {e}")
47760
+ print(f"[SuperLookup] ✗ Error loading termbases via termbase_mgr: {e}")
47222
47761
  import traceback
47223
47762
  traceback.print_exc()
47224
47763
 
47225
47764
  # Fallback to direct database query
47226
47765
  if self.main_window and hasattr(self.main_window, 'db_manager') and self.main_window.db_manager:
47227
47766
  try:
47228
- print(f"[Superlookup] db_manager found, querying termbases...")
47767
+ print(f"[SuperLookup] db_manager found, querying termbases...")
47229
47768
  cursor = self.main_window.db_manager.cursor
47230
47769
  cursor.execute("SELECT id, name FROM termbases ORDER BY name")
47231
47770
  termbases = cursor.fetchall()
47232
47771
 
47233
- print(f"[Superlookup] Query returned {len(termbases)} termbases")
47772
+ print(f"[SuperLookup] Query returned {len(termbases)} termbases")
47234
47773
 
47235
47774
  for tb_id, tb_name in termbases:
47236
47775
  checkbox = CheckmarkCheckBox(f"{tb_name} (ID: {tb_id})")
@@ -47240,13 +47779,13 @@ class SuperlookupTab(QWidget):
47240
47779
  # Insert before the stretch at the end
47241
47780
  self.tb_scroll_layout.insertWidget(len(self.tb_checkboxes) - 1, checkbox)
47242
47781
 
47243
- print(f"[Superlookup] ✓ Loaded {len(termbases)} termbases via db_manager")
47782
+ print(f"[SuperLookup] ✓ Loaded {len(termbases)} termbases via db_manager")
47244
47783
  except Exception as e:
47245
- print(f"[Superlookup] ✗ Error loading termbases via db_manager: {e}")
47784
+ print(f"[SuperLookup] ✗ Error loading termbases via db_manager: {e}")
47246
47785
  import traceback
47247
47786
  traceback.print_exc()
47248
47787
  else:
47249
- print(f"[Superlookup] Neither termbase_mgr nor db_manager available")
47788
+ print(f"[SuperLookup] Neither termbase_mgr nor db_manager available")
47250
47789
  # Add placeholder label
47251
47790
  placeholder = QLabel("No database connection - Glossaries unavailable")
47252
47791
  placeholder.setStyleSheet("color: #999; font-style: italic;")
@@ -48057,7 +48596,7 @@ class SuperlookupTab(QWidget):
48057
48596
  self.status_label.setText(f"Navigated to glossary entry: {source_term}")
48058
48597
 
48059
48598
  except Exception as e:
48060
- print(f"[Superlookup] Error navigating to glossary: {e}")
48599
+ print(f"[SuperLookup] Error navigating to glossary: {e}")
48061
48600
  self.status_label.setText(f"Error navigating to glossary: {e}")
48062
48601
 
48063
48602
  def _select_first_term_in_table(self, main):
@@ -48071,7 +48610,7 @@ class SuperlookupTab(QWidget):
48071
48610
  if item:
48072
48611
  table.scrollToItem(item)
48073
48612
  except Exception as e:
48074
- print(f"[Superlookup] Error selecting term: {e}")
48613
+ print(f"[SuperLookup] Error selecting term: {e}")
48075
48614
 
48076
48615
  def display_mt_results(self, results):
48077
48616
  """Display MT results in the table"""
@@ -48606,7 +49145,7 @@ class SuperlookupTab(QWidget):
48606
49145
  if self.main_window and hasattr(self.main_window, 'general_settings'):
48607
49146
  saved_path = self.main_window.general_settings.get('autohotkey_path', '')
48608
49147
  if saved_path and os.path.exists(saved_path):
48609
- print(f"[Superlookup] Using saved AutoHotkey path: {saved_path}")
49148
+ print(f"[SuperLookup] Using saved AutoHotkey path: {saved_path}")
48610
49149
  return saved_path, 'saved'
48611
49150
 
48612
49151
  # Standard installation paths
@@ -48624,7 +49163,7 @@ class SuperlookupTab(QWidget):
48624
49163
 
48625
49164
  for path in ahk_paths:
48626
49165
  if os.path.exists(path):
48627
- print(f"[Superlookup] Detected AutoHotkey at: {path}")
49166
+ print(f"[SuperLookup] Detected AutoHotkey at: {path}")
48628
49167
  return path, 'detected'
48629
49168
 
48630
49169
  return None, None
@@ -48751,7 +49290,7 @@ class SuperlookupTab(QWidget):
48751
49290
  if self.main_window and hasattr(self.main_window, 'general_settings'):
48752
49291
  self.main_window.general_settings['autohotkey_path'] = file_path
48753
49292
  self.main_window.save_general_settings()
48754
- print(f"[Superlookup] Saved AutoHotkey path: {file_path}")
49293
+ print(f"[SuperLookup] Saved AutoHotkey path: {file_path}")
48755
49294
 
48756
49295
  self._ahk_setup_status.setText(f"✓ Saved: {file_path}\n\nRestart Supervertaler to use this path.")
48757
49296
 
@@ -48772,11 +49311,11 @@ class SuperlookupTab(QWidget):
48772
49311
  Note: AutoHotkey is Windows-only, so skip on Linux/Mac.
48773
49312
  """
48774
49313
  global _ahk_process
48775
- print("[Superlookup] register_global_hotkey called")
49314
+ print("[SuperLookup] register_global_hotkey called")
48776
49315
 
48777
49316
  # AutoHotkey is Windows-only - skip on other platforms
48778
49317
  if sys.platform != 'win32':
48779
- print("[Superlookup] Skipping AutoHotkey registration (not Windows)")
49318
+ print("[SuperLookup] Skipping AutoHotkey registration (not Windows)")
48780
49319
  self.hotkey_registered = False
48781
49320
  return
48782
49321
 
@@ -48790,24 +49329,24 @@ class SuperlookupTab(QWidget):
48790
49329
  """
48791
49330
  try:
48792
49331
  from ahk import AHK
48793
- print("[Superlookup] ahk library available, attempting to use it...")
49332
+ print("[SuperLookup] ahk library available, attempting to use it...")
48794
49333
 
48795
49334
  # Find AutoHotkey executable using shared function
48796
49335
  ahk_exe, source = self._find_autohotkey_executable()
48797
49336
 
48798
49337
  # Create AHK instance (with executable path if found)
48799
49338
  if ahk_exe:
48800
- print(f"[Superlookup] Using AutoHotkey at: {ahk_exe} (source: {source})")
49339
+ print(f"[SuperLookup] Using AutoHotkey at: {ahk_exe} (source: {source})")
48801
49340
  self._ahk = AHK(executable_path=ahk_exe)
48802
49341
  else:
48803
49342
  # Let it try to find AHK on PATH (may fail)
48804
49343
  self._ahk = AHK()
48805
- print(f"[Superlookup] AHK instance created: {self._ahk}")
49344
+ print(f"[SuperLookup] AHK instance created: {self._ahk}")
48806
49345
 
48807
49346
  # Define hotkey callback
48808
49347
  def on_hotkey():
48809
49348
  """Called when Ctrl+Alt+L is pressed"""
48810
- print("[Superlookup] Hotkey triggered via ahk library!")
49349
+ print("[SuperLookup] Hotkey triggered via ahk library!")
48811
49350
  try:
48812
49351
  # Copy selection to clipboard
48813
49352
  self._ahk.send('^c') # Ctrl+C
@@ -48820,7 +49359,7 @@ class SuperlookupTab(QWidget):
48820
49359
  try:
48821
49360
  self._ahk.win_activate('Supervertaler')
48822
49361
  except Exception as e:
48823
- print(f"[Superlookup] win_activate error (non-critical): {e}")
49362
+ print(f"[SuperLookup] win_activate error (non-critical): {e}")
48824
49363
 
48825
49364
  # Trigger lookup in main thread
48826
49365
  if text:
@@ -48828,22 +49367,22 @@ class SuperlookupTab(QWidget):
48828
49367
  QTimer.singleShot(0, lambda: self.on_ahk_capture(text))
48829
49368
 
48830
49369
  except Exception as e:
48831
- print(f"[Superlookup] Error in hotkey callback: {e}")
49370
+ print(f"[SuperLookup] Error in hotkey callback: {e}")
48832
49371
 
48833
49372
  # Register the hotkey
48834
49373
  self._ahk.add_hotkey('^!l', callback=on_hotkey) # Ctrl+Alt+L
48835
49374
  self._ahk.start_hotkeys()
48836
49375
 
48837
- print("[Superlookup] ✓ Hotkey registered via ahk library: Ctrl+Alt+L")
49376
+ print("[SuperLookup] ✓ Hotkey registered via ahk library: Ctrl+Alt+L")
48838
49377
  self.hotkey_registered = True
48839
49378
  self._using_ahk_library = True
48840
49379
  return True
48841
49380
 
48842
49381
  except ImportError:
48843
- print("[Superlookup] ahk library not installed (pip install ahk)")
49382
+ print("[SuperLookup] ahk library not installed (pip install ahk)")
48844
49383
  return False
48845
49384
  except Exception as e:
48846
- print(f"[Superlookup] ahk library method failed: {e}")
49385
+ print(f"[SuperLookup] ahk library method failed: {e}")
48847
49386
  # Clean up on failure
48848
49387
  if hasattr(self, '_ahk'):
48849
49388
  try:
@@ -48924,11 +49463,13 @@ class SuperlookupTab(QWidget):
48924
49463
  self.hotkey_registered = False
48925
49464
 
48926
49465
  def start_file_watcher(self):
48927
- """Watch for signal file from AHK"""
49466
+ """Watch for signal files from AHK (Superlookup and MT Quick Lookup)"""
48928
49467
  self.signal_file = Path(__file__).parent / "lookup_signal.txt"
48929
49468
  self.capture_file = Path(__file__).parent / "temp_capture.txt"
48930
-
48931
- 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}")
48932
49473
 
48933
49474
  # Create timer to check for signal file
48934
49475
  self.file_check_timer = QTimer()
@@ -48937,27 +49478,57 @@ class SuperlookupTab(QWidget):
48937
49478
 
48938
49479
  def check_for_signal(self):
48939
49480
  """Check if AHK wrote a signal file"""
49481
+ # Check for Superlookup signal
48940
49482
  if self.signal_file.exists():
48941
- print(f"[Superlookup] Signal file detected!")
49483
+ print(f"[SuperLookup] Signal file detected!")
48942
49484
  try:
48943
49485
  # Delete signal file
48944
49486
  self.signal_file.unlink()
48945
- print(f"[Superlookup] Signal file deleted")
48946
-
49487
+ print(f"[SuperLookup] Signal file deleted")
49488
+
48947
49489
  # Get text from clipboard (AHK already copied it)
48948
49490
  time.sleep(0.1) # Give clipboard a moment
48949
49491
  text = pyperclip.paste()
48950
-
49492
+
48951
49493
  # Trigger lookup
48952
49494
  if text:
48953
49495
  self.on_ahk_capture(text)
48954
49496
  except Exception as e:
48955
- 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}")
48956
49527
 
48957
49528
  def on_ahk_capture(self, text):
48958
49529
  """Handle text captured by AHK"""
48959
49530
  try:
48960
- print(f"[Superlookup] on_ahk_capture called with text: {text[:50]}...")
49531
+ print(f"[SuperLookup] on_ahk_capture called with text: {text[:50]}...")
48961
49532
 
48962
49533
  # Bring Supervertaler to foreground
48963
49534
  main_window = self.window()
@@ -48987,39 +49558,96 @@ class SuperlookupTab(QWidget):
48987
49558
  QTimer.singleShot(250, lambda: self.show_superlookup(text))
48988
49559
 
48989
49560
  except Exception as e:
48990
- print(f"[Superlookup] Error handling capture: {e}")
48991
-
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
+
48992
49620
  def show_superlookup(self, text):
48993
49621
  """Show Superlookup with pre-filled text"""
48994
49622
  try:
48995
- print(f"[Superlookup] show_superlookup called with text: {text[:50]}...")
49623
+ print(f"[SuperLookup] show_superlookup called with text: {text[:50]}...")
48996
49624
 
48997
49625
  # Get main window reference
48998
49626
  main_window = self.main_window
48999
49627
  if not main_window:
49000
49628
  main_window = self.window()
49001
49629
 
49002
- print(f"[Superlookup] Main window found: {main_window is not None}")
49003
- print(f"[Superlookup] Main window type: {type(main_window).__name__}")
49004
- 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')}")
49005
49633
 
49006
49634
  # Switch to Tools tab (main_tabs index 3)
49007
49635
  # Tab structure: Grid=0, Resources=1, QuickMenu=2, Tools=3, Settings=4
49008
49636
  if hasattr(main_window, 'main_tabs'):
49009
- 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()}")
49010
49638
  main_window.main_tabs.setCurrentIndex(3) # Tools tab
49011
- print(f"[Superlookup] Switched to Tools tab (index 2)")
49639
+ print(f"[SuperLookup] Switched to Tools tab (index 2)")
49012
49640
  QApplication.processEvents() # Force GUI update
49013
49641
  else:
49014
- print(f"[Superlookup] WARNING: Main window has no main_tabs attribute!")
49642
+ print(f"[SuperLookup] WARNING: Main window has no main_tabs attribute!")
49015
49643
 
49016
49644
  # Switch to Superlookup within modules_tabs
49017
49645
  if hasattr(main_window, 'modules_tabs'):
49018
- 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()}")
49019
49647
  for i in range(main_window.modules_tabs.count()):
49020
49648
  if "Superlookup" in main_window.modules_tabs.tabText(i):
49021
49649
  main_window.modules_tabs.setCurrentIndex(i)
49022
- print(f"[Superlookup] Switched to Superlookup tab (index {i})")
49650
+ print(f"[SuperLookup] Switched to Superlookup tab (index {i})")
49023
49651
  QApplication.processEvents() # Force GUI update
49024
49652
  break
49025
49653
 
@@ -49027,23 +49655,23 @@ class SuperlookupTab(QWidget):
49027
49655
  QTimer.singleShot(100, lambda: self._fill_and_search(text))
49028
49656
 
49029
49657
  except Exception as e:
49030
- print(f"[Superlookup] Error showing lookup: {e}")
49658
+ print(f"[SuperLookup] Error showing lookup: {e}")
49031
49659
 
49032
49660
  def _fill_and_search(self, text):
49033
49661
  """Fill in text and trigger search (called after tab switching completes)"""
49034
49662
  try:
49035
- print(f"[Superlookup] _fill_and_search called")
49663
+ print(f"[SuperLookup] _fill_and_search called")
49036
49664
  # Fill in text and trigger lookup
49037
49665
  if hasattr(self, 'source_text'):
49038
49666
  self.source_text.setCurrentText(text)
49039
- print(f"[Superlookup] Text filled in source_text field")
49667
+ print(f"[SuperLookup] Text filled in source_text field")
49040
49668
  # Trigger lookup by calling perform_lookup directly
49041
49669
  self.perform_lookup()
49042
- print(f"[Superlookup] perform_lookup() called")
49670
+ print(f"[SuperLookup] perform_lookup() called")
49043
49671
  else:
49044
- print(f"[Superlookup] ERROR: source_text widget not found!")
49672
+ print(f"[SuperLookup] ERROR: source_text widget not found!")
49045
49673
  except Exception as e:
49046
- print(f"[Superlookup] Error in _fill_and_search: {e}")
49674
+ print(f"[SuperLookup] Error in _fill_and_search: {e}")
49047
49675
  import traceback
49048
49676
  traceback.print_exc()
49049
49677