supervertaler 1.9.202__py3-none-any.whl → 1.9.204__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.198"
35
+ __version__ = "1.9.204"
36
36
  __phase__ = "0.9"
37
- __release_date__ = "2026-02-02"
37
+ __release_date__ = "2026-02-03"
38
38
  __edition__ = "Qt"
39
39
 
40
40
  import sys
@@ -7945,19 +7945,36 @@ class SupervertalerQt(QMainWindow):
7945
7945
  import_menu.addAction(import_review_table_action)
7946
7946
 
7947
7947
  export_menu = file_menu.addMenu("&Export")
7948
-
7948
+
7949
+ # --- Monolingual (target-only) exports at top ---
7950
+ export_target_docx_action = QAction("&Target Only (DOCX)...", self)
7951
+ export_target_docx_action.triggered.connect(self.export_target_only_docx)
7952
+ export_menu.addAction(export_target_docx_action)
7953
+
7954
+ export_txt_action = QAction("Simple &Text File - Translated (TXT)...", self)
7955
+ export_txt_action.triggered.connect(self.export_simple_txt)
7956
+ export_menu.addAction(export_txt_action)
7957
+
7958
+ export_ai_action = QAction("📄 &AI-Readable Markdown (.md)...", self)
7959
+ export_ai_action.triggered.connect(self.export_bilingual_table_markdown)
7960
+ export_ai_action.setToolTip("Export segments in [SEGMENT] format for AI translation/review")
7961
+ export_menu.addAction(export_ai_action)
7962
+
7963
+ export_menu.addSeparator()
7964
+
7965
+ # --- Bilingual CAT tool exports ---
7949
7966
  export_memoq_action = QAction("memoQ &Bilingual Table - Translated (DOCX)...", self)
7950
7967
  export_memoq_action.triggered.connect(self.export_memoq_bilingual)
7951
7968
  export_menu.addAction(export_memoq_action)
7952
-
7969
+
7953
7970
  export_memoq_xliff_action = QAction("memoQ &XLIFF - Translated (.mqxliff)...", self)
7954
7971
  export_memoq_xliff_action.triggered.connect(self.export_memoq_xliff)
7955
7972
  export_menu.addAction(export_memoq_xliff_action)
7956
-
7973
+
7957
7974
  export_cafetran_action = QAction("&CafeTran Bilingual Table - Translated (DOCX)...", self)
7958
7975
  export_cafetran_action.triggered.connect(self.export_cafetran_bilingual)
7959
7976
  export_menu.addAction(export_cafetran_action)
7960
-
7977
+
7961
7978
  # Trados submenu - group all Trados exports together
7962
7979
  trados_export_submenu = export_menu.addMenu("&Trados Studio")
7963
7980
 
@@ -7978,20 +7995,7 @@ class SupervertalerQt(QMainWindow):
7978
7995
  export_dejavu_action = QAction("&Déjà Vu X3 Bilingual - Translated (RTF)...", self)
7979
7996
  export_dejavu_action.triggered.connect(self.export_dejavu_bilingual)
7980
7997
  export_menu.addAction(export_dejavu_action)
7981
-
7982
- export_target_docx_action = QAction("&Target Only (DOCX)...", self)
7983
- export_target_docx_action.triggered.connect(self.export_target_only_docx)
7984
- export_menu.addAction(export_target_docx_action)
7985
-
7986
- export_txt_action = QAction("Simple &Text File - Translated (TXT)...", self)
7987
- export_txt_action.triggered.connect(self.export_simple_txt)
7988
- export_menu.addAction(export_txt_action)
7989
-
7990
- export_ai_action = QAction("📄 &AI-Readable Markdown (.md)...", self)
7991
- export_ai_action.triggered.connect(self.export_bilingual_table_markdown)
7992
- export_ai_action.setToolTip("Export segments in [SEGMENT] format for AI translation/review")
7993
- export_menu.addAction(export_ai_action)
7994
-
7998
+
7995
7999
  export_menu.addSeparator()
7996
8000
 
7997
8001
  # Multi-file folder export
@@ -8267,30 +8271,7 @@ class SupervertalerQt(QMainWindow):
8267
8271
  grid_font_family_menu.addAction(font_action)
8268
8272
 
8269
8273
  view_menu.addSeparator()
8270
-
8271
- # Translation Results Pane section
8272
- results_zoom_menu = view_menu.addMenu("📋 Translation &Results Pane")
8273
-
8274
- results_zoom_in_action = QAction("Results Zoom &In", self)
8275
- results_zoom_in_action.setShortcut("Ctrl+Shift+=")
8276
- results_zoom_in_action.triggered.connect(self.results_pane_zoom_in)
8277
- results_zoom_menu.addAction(results_zoom_in_action)
8278
-
8279
- results_zoom_out_action = QAction("Results Zoom &Out", self)
8280
- results_zoom_out_action.setShortcut("Ctrl+Shift+-")
8281
- results_zoom_out_action.triggered.connect(self.results_pane_zoom_out)
8282
- results_zoom_menu.addAction(results_zoom_out_action)
8283
-
8284
- results_zoom_reset_action = QAction("Results Zoom &Reset", self)
8285
- results_zoom_reset_action.triggered.connect(self.results_pane_zoom_reset)
8286
- results_zoom_menu.addAction(results_zoom_reset_action)
8287
-
8288
- results_zoom_menu.addSeparator()
8289
-
8290
- results_note = QAction("(Includes match list + compare boxes)", self)
8291
- results_note.setEnabled(False)
8292
- results_zoom_menu.addAction(results_note)
8293
-
8274
+
8294
8275
  # Match Panel zoom section
8295
8276
  match_panel_zoom_menu = view_menu.addMenu("🔍 &Match Panel")
8296
8277
 
@@ -11280,8 +11261,10 @@ class SupervertalerQt(QMainWindow):
11280
11261
  text = re.sub(r'</?li>', '', text) # <li>, </li>
11281
11262
  text = re.sub(r'</?[biu]>', '', text) # <b>, </b>, <i>, </i>, <u>, </u>
11282
11263
  text = re.sub(r'</?bi>', '', text) # <bi>, </bi>
11264
+ text = re.sub(r'</?sub>', '', text) # <sub>, </sub>
11265
+ text = re.sub(r'</?sup>', '', text) # <sup>, </sup>
11283
11266
  return text.strip()
11284
-
11267
+
11285
11268
  def clean_special_chars(text):
11286
11269
  """Remove problematic Unicode characters like object replacement char"""
11287
11270
  # Remove Unicode Object Replacement Character (U+FFFC) and similar
@@ -11295,34 +11278,48 @@ class SupervertalerQt(QMainWindow):
11295
11278
  """
11296
11279
  Replace paragraph text with tagged text, applying bold/italic/underline formatting.
11297
11280
  Parses tags like <b>, <i>, <u>, <bi> and creates appropriate runs.
11281
+ Preserves original font name and size from the first run.
11298
11282
  """
11299
11283
  # Clean special characters first
11300
11284
  text = clean_special_chars(tagged_text)
11301
-
11285
+
11302
11286
  # Strip list tags - they don't affect formatting
11303
11287
  text = re.sub(r'</?li-[bo]>', '', text)
11304
11288
  text = re.sub(r'</?li>', '', text)
11305
-
11289
+
11290
+ # Capture original font properties BEFORE clearing runs
11291
+ original_font_name = None
11292
+ original_font_size = None
11293
+ original_all_caps = None
11294
+ if para.runs:
11295
+ first_run = para.runs[0]
11296
+ if first_run.font:
11297
+ original_font_name = first_run.font.name
11298
+ original_font_size = first_run.font.size
11299
+ original_all_caps = first_run.font.all_caps
11300
+
11306
11301
  # Clear existing runs
11307
11302
  for run in para.runs:
11308
11303
  run.clear()
11309
11304
  # Remove the cleared runs
11310
11305
  for run in list(para.runs):
11311
11306
  run._element.getparent().remove(run._element)
11312
-
11307
+
11313
11308
  # Parse tags and create runs with formatting
11314
11309
  # Pattern matches tags or text between tags
11315
- tag_pattern = re.compile(r'(</?(?:b|i|u|bi)>)')
11310
+ tag_pattern = re.compile(r'(</?(?:b|i|u|bi|sub|sup)>)')
11316
11311
  parts = tag_pattern.split(text)
11317
-
11312
+
11318
11313
  is_bold = False
11319
11314
  is_italic = False
11320
11315
  is_underline = False
11321
-
11316
+ is_subscript = False
11317
+ is_superscript = False
11318
+
11322
11319
  for part in parts:
11323
11320
  if not part:
11324
11321
  continue
11325
-
11322
+
11326
11323
  # Check if this is a tag
11327
11324
  if part == '<b>':
11328
11325
  is_bold = True
@@ -11342,6 +11339,14 @@ class SupervertalerQt(QMainWindow):
11342
11339
  elif part == '</bi>':
11343
11340
  is_bold = False
11344
11341
  is_italic = False
11342
+ elif part == '<sub>':
11343
+ is_subscript = True
11344
+ elif part == '</sub>':
11345
+ is_subscript = False
11346
+ elif part == '<sup>':
11347
+ is_superscript = True
11348
+ elif part == '</sup>':
11349
+ is_superscript = False
11345
11350
  else:
11346
11351
  # This is text content - create a run with current formatting
11347
11352
  if part.strip() or part: # Include whitespace
@@ -11349,6 +11354,17 @@ class SupervertalerQt(QMainWindow):
11349
11354
  run.bold = is_bold
11350
11355
  run.italic = is_italic
11351
11356
  run.underline = is_underline
11357
+ if is_subscript:
11358
+ run.font.subscript = True
11359
+ if is_superscript:
11360
+ run.font.superscript = True
11361
+ # Restore original font properties
11362
+ if original_font_name:
11363
+ run.font.name = original_font_name
11364
+ if original_font_size:
11365
+ run.font.size = original_font_size
11366
+ if original_all_caps:
11367
+ run.font.all_caps = original_all_caps
11352
11368
 
11353
11369
  # Build a mapping of source text (without tags) to raw target text (with tags)
11354
11370
  text_map = {}
@@ -11361,16 +11377,14 @@ class SupervertalerQt(QMainWindow):
11361
11377
  if source_clean and target_raw:
11362
11378
  text_map[source_clean] = target_raw
11363
11379
 
11364
- def replace_segments_in_text(original_text, text_map):
11365
- """Replace all matching segments in text, handling partial matches."""
11380
+ def replace_segments_in_text_with_tags(original_text, text_map):
11381
+ """Replace all matching segments in text, preserving formatting tags."""
11366
11382
  result = original_text
11367
11383
  # Sort by length (longest first) to avoid partial replacement issues
11368
11384
  for source_clean, target_raw in sorted(text_map.items(), key=lambda x: len(x[0]), reverse=True):
11369
11385
  if source_clean in result:
11370
- # Get clean target (no tags) for text replacement
11371
- target_clean = strip_all_tags(target_raw)
11372
- target_clean = clean_special_chars(target_clean)
11373
- result = result.replace(source_clean, target_clean)
11386
+ # Keep target WITH tags for formatting preservation
11387
+ result = result.replace(source_clean, target_raw)
11374
11388
  return result
11375
11389
 
11376
11390
  replaced_count = 0
@@ -11386,17 +11400,13 @@ class SupervertalerQt(QMainWindow):
11386
11400
  replaced_count += 1
11387
11401
  else:
11388
11402
  # Try partial replacement (paragraph contains multiple segments)
11389
- new_text = replace_segments_in_text(para_text, text_map)
11403
+ new_text = replace_segments_in_text_with_tags(para_text, text_map)
11390
11404
  if new_text != para_text:
11391
- # Text was changed - update paragraph
11392
- # For partial replacements, we lose formatting tags but at least translate
11393
- for run in para.runs:
11394
- run.clear()
11395
- for run in list(para.runs):
11396
- run._element.getparent().remove(run._element)
11397
- para.add_run(new_text)
11405
+ # Text was changed - use apply_formatted_text_to_paragraph
11406
+ # to preserve inline formatting tags (bold, italic, sub, sup, etc.)
11407
+ apply_formatted_text_to_paragraph(para, new_text)
11398
11408
  replaced_count += 1
11399
-
11409
+
11400
11410
  # Replace text in tables
11401
11411
  for table in doc.tables:
11402
11412
  for row in table.rows:
@@ -11411,15 +11421,12 @@ class SupervertalerQt(QMainWindow):
11411
11421
  replaced_count += 1
11412
11422
  else:
11413
11423
  # Try partial replacement
11414
- new_text = replace_segments_in_text(para_text, text_map)
11424
+ new_text = replace_segments_in_text_with_tags(para_text, text_map)
11415
11425
  if new_text != para_text:
11416
- for run in para.runs:
11417
- run.clear()
11418
- for run in list(para.runs):
11419
- run._element.getparent().remove(run._element)
11420
- para.add_run(new_text)
11426
+ # Use apply_formatted_text_to_paragraph to preserve formatting
11427
+ apply_formatted_text_to_paragraph(para, new_text)
11421
11428
  replaced_count += 1
11422
-
11429
+
11423
11430
  doc.save(file_path)
11424
11431
  self.log(f"✓ Replaced {replaced_count} text segments in original document structure")
11425
11432
 
@@ -11435,8 +11442,10 @@ class SupervertalerQt(QMainWindow):
11435
11442
  text = re.sub(r'</?li>', '', text) # <li>, </li>
11436
11443
  text = re.sub(r'</?[biu]>', '', text) # <b>, </b>, <i>, </i>, <u>, </u>
11437
11444
  text = re.sub(r'</?bi>', '', text) # <bi>, </bi>
11445
+ text = re.sub(r'</?sub>', '', text) # <sub>, </sub>
11446
+ text = re.sub(r'</?sup>', '', text) # <sup>, </sup>
11438
11447
  return text.strip()
11439
-
11448
+
11440
11449
  def clean_special_chars(text):
11441
11450
  """Remove problematic Unicode characters"""
11442
11451
  text = text.replace('\ufffc', '') # Object Replacement Character
@@ -11455,13 +11464,15 @@ class SupervertalerQt(QMainWindow):
11455
11464
  text = re.sub(r'</?li>', '', text)
11456
11465
 
11457
11466
  # Parse and apply formatting
11458
- tag_pattern = re.compile(r'(</?(?:b|i|u|bi)>)')
11467
+ tag_pattern = re.compile(r'(</?(?:b|i|u|bi|sub|sup)>)')
11459
11468
  parts = tag_pattern.split(text)
11460
-
11469
+
11461
11470
  is_bold = False
11462
11471
  is_italic = False
11463
11472
  is_underline = False
11464
-
11473
+ is_subscript = False
11474
+ is_superscript = False
11475
+
11465
11476
  for part in parts:
11466
11477
  if not part:
11467
11478
  continue
@@ -11483,13 +11494,25 @@ class SupervertalerQt(QMainWindow):
11483
11494
  elif part == '</bi>':
11484
11495
  is_bold = False
11485
11496
  is_italic = False
11497
+ elif part == '<sub>':
11498
+ is_subscript = True
11499
+ elif part == '</sub>':
11500
+ is_subscript = False
11501
+ elif part == '<sup>':
11502
+ is_superscript = True
11503
+ elif part == '</sup>':
11504
+ is_superscript = False
11486
11505
  else:
11487
11506
  if part:
11488
11507
  run = para.add_run(part)
11489
11508
  run.bold = is_bold
11490
11509
  run.italic = is_italic
11491
11510
  run.underline = is_underline
11492
-
11511
+ if is_subscript:
11512
+ run.font.subscript = True
11513
+ if is_superscript:
11514
+ run.font.superscript = True
11515
+
11493
11516
  doc = Document()
11494
11517
 
11495
11518
  for seg in segments:
@@ -21387,70 +21410,46 @@ class SupervertalerQt(QMainWindow):
21387
21410
 
21388
21411
  # Add left side to main horizontal splitter
21389
21412
  main_horizontal_splitter.addWidget(left_container)
21390
-
21391
- # Right side: Translation Results panel with Preview tab
21392
- from modules.translation_results_panel import TranslationResultsPanel
21393
-
21413
+
21394
21414
  # Create tabbed container for right panel
21395
21415
  right_tabs = QTabWidget()
21396
21416
  right_tabs.tabBar().setFocusPolicy(Qt.FocusPolicy.NoFocus)
21397
21417
  right_tabs.tabBar().setDrawBase(False)
21398
21418
  right_tabs.setStyleSheet("QTabBar::tab { outline: 0; } QTabBar::tab:focus { outline: none; } QTabBar::tab:selected { border-bottom: 1px solid #2196F3; background-color: rgba(33, 150, 243, 0.08); }")
21399
-
21400
- # Tab 1: Translation Results
21401
- self.translation_results_panel = TranslationResultsPanel(right_tabs, parent_app=self)
21402
-
21403
- # Connect signals for match selection/insertion
21404
- self.translation_results_panel.match_selected.connect(self.on_match_selected)
21405
- self.translation_results_panel.match_inserted.connect(self.on_match_inserted)
21406
-
21407
- # Connect notes editing to save segment notes
21408
- if hasattr(self.translation_results_panel, 'notes_edit') and self.translation_results_panel.notes_edit:
21409
- self.translation_results_panel.notes_edit.textChanged.connect(self._on_results_panel_notes_changed)
21410
-
21411
- # Register this panel so it receives updates when segments are selected
21419
+
21420
+ # NOTE: Translation Results panel is deprecated - MT/LLM is now only via QuickTrans (Ctrl+M)
21421
+ # Panel is no longer created to save resources. Set to None for compatibility checks.
21422
+ self.translation_results_panel = None
21412
21423
  if not hasattr(self, 'results_panels'):
21413
21424
  self.results_panels = []
21414
- self.results_panels.append(self.translation_results_panel)
21415
-
21425
+
21416
21426
  # Track tab indices for visibility-aware default selection
21417
21427
  tab_index = 0
21418
- results_tab_index = -1
21419
- compare_tab_index = -1
21420
21428
  preview_tab_index = -1
21421
-
21422
- # Tab 1: Translation Results (conditionally added)
21423
- if self.show_translation_results_pane:
21424
- right_tabs.addTab(self.translation_results_panel, "🔍 Translation Results")
21425
- results_tab_index = tab_index
21426
- tab_index += 1
21427
- else:
21428
- # Hide the panel if not added as tab (it still exists as child widget)
21429
- self.translation_results_panel.hide()
21430
-
21431
- # Tab 2: Match Panel (Termview + TM Source/Target) - shown first by default
21429
+
21430
+ # Tab 1: Match Panel (Termview + TM Source/Target) - primary TM display
21432
21431
  match_panel_widget = self._create_match_panel()
21433
21432
  self.match_panel_widget = match_panel_widget # Store reference for mode detection
21434
21433
  right_tabs.addTab(match_panel_widget, "🎯 Match Panel")
21435
21434
  match_panel_tab_index = tab_index
21436
21435
  tab_index += 1
21437
21436
 
21438
- # Tab 4: Document Preview (always added)
21437
+ # Tab 2: Document Preview
21439
21438
  preview_widget = self._create_preview_tab()
21440
21439
  right_tabs.addTab(preview_widget, "📄 Preview")
21441
21440
  preview_tab_index = tab_index
21442
21441
  self._preview_tab_index = preview_tab_index # Store for visibility checks
21443
21442
  tab_index += 1
21444
-
21445
- # Tab 5: Segment Note (moved from bottom panel)
21443
+
21444
+ # Tab 3: Segment Note
21446
21445
  right_tabs.addTab(self._notes_widget_for_right_panel, "📝 Segment note")
21447
21446
  tab_index += 1
21448
-
21449
- # Tab 6: Session Log (moved from bottom panel)
21447
+
21448
+ # Tab 4: Session Log
21450
21449
  right_tabs.addTab(self._session_log_widget_for_right_panel, "📋 Session Log")
21451
21450
  tab_index += 1
21452
-
21453
- # Tab 7: Scratchpad (private translator notes for the whole project)
21451
+
21452
+ # Tab 5: Scratchpad (private translator notes for the whole project)
21454
21453
  right_tabs.addTab(self._scratchpad_widget_for_right_panel, "📝 Scratchpad")
21455
21454
  tab_index += 1
21456
21455
 
@@ -21597,7 +21596,13 @@ class SupervertalerQt(QMainWindow):
21597
21596
  self.table.setRowHidden(row, not in_page)
21598
21597
  finally:
21599
21598
  self.table.setUpdatesEnabled(True)
21600
-
21599
+
21600
+ # Recalculate heights for visible rows to prevent layout corruption.
21601
+ # Call synchronously first, then schedule a deferred resize to catch any
21602
+ # layout issues that Qt processes asynchronously.
21603
+ self._resize_visible_rows()
21604
+ QTimer.singleShot(50, self._resize_visible_rows)
21605
+
21601
21606
  # Update pagination UI
21602
21607
  self._update_pagination_ui()
21603
21608
 
@@ -21889,7 +21894,10 @@ class SupervertalerQt(QMainWindow):
21889
21894
  header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch) # Target - stretch to fill space
21890
21895
  header.setSectionResizeMode(4, QHeaderView.ResizeMode.Interactive) # Status - allow resizing
21891
21896
  header.setStretchLastSection(False) # Don't auto-stretch last section (we use Stretch mode for Source/Target)
21892
-
21897
+
21898
+ # Recalculate row heights when columns are resized (text reflows to new width)
21899
+ header.sectionResized.connect(self._on_column_resized)
21900
+
21893
21901
  # Set initial column widths - give Source and Target equal space
21894
21902
  # ID column width will be auto-adjusted by _update_segment_column_width() after segments load
21895
21903
  self.table.setColumnWidth(0, 35) # ID - temporary, auto-adjusts to fit content
@@ -30118,10 +30126,15 @@ class SupervertalerQt(QMainWindow):
30118
30126
 
30119
30127
  # Update progress stats in status bar
30120
30128
  self.update_progress_stats()
30121
-
30129
+
30122
30130
  # Refresh document preview
30123
30131
  self.refresh_preview()
30124
30132
 
30133
+ # Deferred row resize: columns use Stretch mode, so their final widths
30134
+ # are only known after Qt processes the layout. Schedule a resize for
30135
+ # after all pending events are processed.
30136
+ QTimer.singleShot(0, self._resize_visible_rows)
30137
+
30125
30138
  # =========================================================================
30126
30139
  # COMPARE PANEL TAB
30127
30140
  # =========================================================================
@@ -31481,9 +31494,11 @@ class SupervertalerQt(QMainWindow):
31481
31494
  def _refresh_segment_status(self, segment: Segment):
31482
31495
  if not self.current_project:
31483
31496
  return
31497
+ updated_row = None
31484
31498
  for row, seg in enumerate(self.current_project.segments):
31485
31499
  if seg.id == segment.id:
31486
31500
  self._update_status_cell(row, seg)
31501
+ updated_row = row
31487
31502
  break
31488
31503
 
31489
31504
  # Update list view entry in place (if visible)
@@ -31505,7 +31520,10 @@ class SupervertalerQt(QMainWindow):
31505
31520
  status_tooltip += f"\nComment: {segment.notes.strip()}"
31506
31521
  item.setToolTip(2, status_tooltip)
31507
31522
  item.setBackground(2, QColor(status_def.color))
31508
- self._enforce_status_row_heights()
31523
+
31524
+ # Recalculate row height after status cell update to prevent layout corruption
31525
+ if updated_row is not None:
31526
+ self._auto_resize_single_row(updated_row)
31509
31527
 
31510
31528
  def _refresh_segment_status_by_id(self, segment_id: int):
31511
31529
  """Refresh the status display for a segment by its ID."""
@@ -31568,40 +31586,79 @@ class SupervertalerQt(QMainWindow):
31568
31586
  return
31569
31587
  if row < 0 or row >= self.table.rowCount():
31570
31588
  return
31571
-
31589
+
31590
+ # Minimum column width threshold - if columns are narrower than this,
31591
+ # layout isn't ready yet and we should skip resizing
31592
+ MIN_COL_WIDTH = 100
31593
+
31572
31594
  max_height = 1
31573
-
31595
+
31574
31596
  # Check source cell (column 2)
31575
31597
  source_widget = self.table.cellWidget(row, 2)
31576
31598
  if source_widget and isinstance(source_widget, ReadOnlyGridTextEditor):
31577
31599
  doc = source_widget.document()
31578
31600
  if doc:
31579
31601
  col_width = self.table.columnWidth(2) - width_reduction
31580
- if col_width > 0:
31602
+ if col_width >= MIN_COL_WIDTH:
31581
31603
  doc.setTextWidth(col_width)
31582
- size = doc.size()
31583
- if size.isValid():
31584
- height = int(size.height())
31585
- max_height = max(max_height, height)
31586
-
31604
+ # Use documentLayout().documentSize() to force fresh calculation
31605
+ layout = doc.documentLayout()
31606
+ if layout:
31607
+ size = layout.documentSize()
31608
+ if size.isValid():
31609
+ height = int(size.height())
31610
+ max_height = max(max_height, height)
31611
+
31587
31612
  # Check target cell (column 3)
31588
31613
  target_widget = self.table.cellWidget(row, 3)
31589
31614
  if target_widget and isinstance(target_widget, EditableGridTextEditor):
31590
31615
  doc = target_widget.document()
31591
31616
  if doc:
31592
31617
  col_width = self.table.columnWidth(3) - width_reduction
31593
- if col_width > 0:
31618
+ if col_width >= MIN_COL_WIDTH:
31594
31619
  doc.setTextWidth(col_width)
31595
- size = doc.size()
31596
- if size.isValid():
31597
- height = int(size.height())
31598
- max_height = max(max_height, height)
31620
+ # Use documentLayout().documentSize() to force fresh calculation
31621
+ layout = doc.documentLayout()
31622
+ if layout:
31623
+ size = layout.documentSize()
31624
+ if size.isValid():
31625
+ height = int(size.height())
31626
+ max_height = max(max_height, height)
31599
31627
 
31600
31628
  # Set row height with minimal padding
31601
31629
  # Minimum 32px to accommodate status icons (16px) + match text + padding without any cutoff
31602
31630
  compact_height = max(max_height + 2, 32)
31603
31631
  self.table.setRowHeight(row, compact_height)
31604
-
31632
+
31633
+ def _resize_visible_rows(self):
31634
+ """Resize only visible (non-hidden) rows for efficiency after pagination/filter changes."""
31635
+ if not hasattr(self, 'table') or not self.table:
31636
+ return
31637
+ row_count = self.table.rowCount()
31638
+ for row in range(row_count):
31639
+ if not self.table.isRowHidden(row):
31640
+ self._auto_resize_single_row(row)
31641
+
31642
+ def _on_column_resized(self, logical_index: int, old_size: int, new_size: int):
31643
+ """Handle column resize - recalculate row heights for text reflow.
31644
+
31645
+ Only reacts to Source (2) or Target (3) column changes since those
31646
+ contain wrapped text. Uses debouncing to avoid excessive recalculations
31647
+ during continuous dragging.
31648
+ """
31649
+ # Only care about Source or Target columns (they contain wrapped text)
31650
+ if logical_index not in (2, 3):
31651
+ return
31652
+
31653
+ # Debounce: cancel previous timer and start a new one
31654
+ if hasattr(self, '_column_resize_timer') and self._column_resize_timer is not None:
31655
+ self._column_resize_timer.stop()
31656
+
31657
+ self._column_resize_timer = QTimer()
31658
+ self._column_resize_timer.setSingleShot(True)
31659
+ self._column_resize_timer.timeout.connect(self._resize_visible_rows)
31660
+ self._column_resize_timer.start(150) # 150ms debounce
31661
+
31605
31662
  def apply_font_to_grid(self):
31606
31663
  """Apply selected font to all grid cells"""
31607
31664
  font = QFont(self.default_font_family, self.default_font_size)
@@ -37438,10 +37495,10 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
37438
37495
  # Clear filtering flag to re-enable auto-center
37439
37496
  self.filtering_active = False
37440
37497
 
37441
- # Re-apply pagination after clearing filters
37498
+ # Re-apply pagination after clearing filters (also resizes visible rows)
37442
37499
  if hasattr(self, '_apply_pagination_to_grid'):
37443
37500
  self._apply_pagination_to_grid()
37444
-
37501
+
37445
37502
  # Restore selection to the previously selected segment
37446
37503
  if selected_segment_id is not None:
37447
37504
  for row, segment in enumerate(self.current_project.segments):
@@ -38709,7 +38766,12 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
38709
38766
  break
38710
38767
 
38711
38768
  def _on_results_panel_notes_changed(self):
38712
- """Handle notes change in Translation Results panel - saves to current segment"""
38769
+ """Handle notes change in Translation Results panel - saves to current segment.
38770
+ NOTE: This function is deprecated - Translation Results panel has been removed.
38771
+ """
38772
+ # Translation Results panel is deprecated - this should never be called
38773
+ if not self.translation_results_panel:
38774
+ return
38713
38775
  if not self.current_project:
38714
38776
  return
38715
38777
 
@@ -45647,9 +45709,19 @@ OUTPUT ONLY THE SEGMENT MARKERS. DO NOT ADD EXPLANATIONS BEFORE OR AFTER."""
45647
45709
  self.log(f"⚠ Error auto-inserting TM match: {e}")
45648
45710
 
45649
45711
  def _add_mt_and_llm_matches_progressive(self, segment, source_lang, target_lang, source_lang_code, target_lang_code):
45650
- """Add MT and LLM matches progressively - show each as it completes"""
45712
+ """DEPRECATED: Translation Results panel has been removed.
45713
+
45714
+ MT/LLM translations are now only available via QuickTrans (Ctrl+M).
45715
+ This function is kept as a stub for backwards compatibility.
45716
+ """
45717
+ pass # Translation Results panel removed - use QuickTrans (Ctrl+M) for MT/LLM
45718
+
45719
+ def _REMOVED_add_mt_and_llm_matches_progressive(self, segment, source_lang, target_lang, source_lang_code, target_lang_code):
45720
+ """REMOVED - kept for reference only, will be deleted in future cleanup."""
45721
+ return # Dead code below - keeping temporarily for reference
45722
+
45651
45723
  from modules.translation_results_panel import TranslationMatch
45652
-
45724
+
45653
45725
  # MT matches (usually fast ~0.5s each)
45654
45726
  if self.enable_mt_matching:
45655
45727
  api_keys = self.load_api_keys()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supervertaler
3
- Version: 1.9.202
3
+ Version: 1.9.204
4
4
  Summary: Professional AI-enhanced translation workbench with multi-LLM support, glossary system, TM, spellcheck, voice commands, and PyQt6 interface. Batteries included (core).
5
5
  Home-page: https://supervertaler.com
6
6
  Author: Michael Beijer
@@ -1,4 +1,4 @@
1
- Supervertaler.py,sha256=yYljYTFunua83fL9AjIEWd5e4lO56lbwR6T3X9ZYcmU,2395306
1
+ Supervertaler.py,sha256=ZCV8QknOZhHpUGJ_t4ONW_wq7zS1L_0nlHAuw8x0ybo,2398568
2
2
  modules/__init__.py,sha256=G58XleS-EJ2sX4Kehm-3N2m618_W2Es0Kg8CW_eBG7g,327
3
3
  modules/ai_actions.py,sha256=i5MJcM-7Y6CAvKUwxmxrVHeoZAVtAP7aRDdWM5KLkO0,33877
4
4
  modules/ai_attachment_manager.py,sha256=juZlrW3UPkIkcnj0SREgOQkQROLf0fcu3ShZcKXMxsI,11361
@@ -80,9 +80,9 @@ modules/unified_prompt_manager_qt.py,sha256=y7xAIM9X7f1afXGE1tOcAc5EY7BKFy53Rgqi
80
80
  modules/voice_commands.py,sha256=iBb-gjWxRMLhFH7-InSRjYJz1EIDBNA2Pog8V7TtJaY,38516
81
81
  modules/voice_dictation.py,sha256=QmitXfkG-vRt5hIQATjphHdhXfqmwhzcQcbXB6aRzIg,16386
82
82
  modules/voice_dictation_lite.py,sha256=jorY0BmWE-8VczbtGrWwt1zbnOctMoSlWOsQrcufBcc,9423
83
- supervertaler-1.9.202.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
84
- supervertaler-1.9.202.dist-info/METADATA,sha256=rxqzXM85xvLcu_qHvaiCKvLx1Nh3WJE28KdNaNuP-ro,5725
85
- supervertaler-1.9.202.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
86
- supervertaler-1.9.202.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
87
- supervertaler-1.9.202.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
88
- supervertaler-1.9.202.dist-info/RECORD,,
83
+ supervertaler-1.9.204.dist-info/licenses/LICENSE,sha256=m28u-4qL5nXIWnJ6xlQVw__H30rWFtRK3pCOais2OuY,1092
84
+ supervertaler-1.9.204.dist-info/METADATA,sha256=zcv2fcgWU60pLclQn-I23GvrLuXf-z9vVFzElk6hIXw,5725
85
+ supervertaler-1.9.204.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
86
+ supervertaler-1.9.204.dist-info/entry_points.txt,sha256=NP4hiCvx-_30YYKqgr-jfJYQvHr1qTYBMfoVmKIXSM8,53
87
+ supervertaler-1.9.204.dist-info/top_level.txt,sha256=9tUHBYUSfaE4S2E4W3eavJsDyYymkwLfeWAHHAPT6Dk,22
88
+ supervertaler-1.9.204.dist-info/RECORD,,