3131)
3232from PySide6 .QtWidgets import (
3333 QApplication ,
34+ QComboBox ,
3435 QFileDialog ,
3536 QFrame ,
3637 QGraphicsItem ,
8081PDF_DETECT_DPI = 300
8182PDF_DISPLAY_DPI = 150
8283
84+ TEMPLATES : List [Tuple [str , str ]] = [
85+ ("Read Barcodes (default)" , EnumPresetTemplate .PT_READ_BARCODES .value ),
86+ ("Speed First" , EnumPresetTemplate .PT_READ_BARCODES_SPEED_FIRST .value ),
87+ ("Read Rate First" , EnumPresetTemplate .PT_READ_BARCODES_READ_RATE_FIRST .value ),
88+ ("Single Barcode" , EnumPresetTemplate .PT_READ_SINGLE_BARCODE .value ),
89+ ]
90+ DEFAULT_TEMPLATE_INDEX = 0 # Read Barcodes (default)
91+
8392OVERLAY_COLOR = QColor (0 , 200 , 80 )
8493OVERLAY_FILL = QColor (0 , 200 , 80 , 60 )
8594OVERLAY_TEXT_BG = QColor (0 , 0 , 0 , 180 )
@@ -175,15 +184,82 @@ class ScannerSignals(QObject):
175184
176185
177186class ScannerThread (QThread ):
178- def __init__ (self , files : List [str ], parent = None ) -> None :
187+ def __init__ (
188+ self ,
189+ files : List [str ],
190+ template : str ,
191+ cached_pages : Optional [dict ] = None ,
192+ parent = None ,
193+ ) -> None :
179194 super ().__init__ (parent )
180195 self .files = list (files )
196+ self .template = template
197+ # {(file_path, page_idx) -> PageData} - rendered pages we can reuse
198+ # so a template-switch re-decode does not re-render PDFs/TIFFs.
199+ self .cached_pages = cached_pages or {}
181200 self .signals = ScannerSignals ()
182201 self ._stop = False
183202
184203 def stop (self ) -> None :
185204 self ._stop = True
186205
206+ def _prepare_pages (self , file_path : str ) -> List [PageData ]:
207+ """Render pages or reuse cached renders. Emits fileStarted (only when
208+ the file was not previously loaded) and a placeholder pageReady for
209+ every page. Returns the list of PageData."""
210+ cached_keys = sorted (k for k in self .cached_pages if k [0 ] == file_path )
211+ if cached_keys :
212+ n_total = cached_keys [- 1 ][1 ] + 1
213+ if all ((file_path , i ) in self .cached_pages for i in range (n_total )):
214+ records : List [PageData ] = []
215+ for i in range (n_total ):
216+ old = self .cached_pages [(file_path , i )]
217+ records .append (
218+ PageData (
219+ file_path = file_path ,
220+ page_index = i ,
221+ total_pages = n_total ,
222+ image = old .image ,
223+ detect_width = old .detect_width ,
224+ detect_height = old .detect_height ,
225+ barcodes = [],
226+ error = None ,
227+ )
228+ )
229+ # No fileStarted - the tree item already exists.
230+ for rec in records :
231+ self .signals .pageReady .emit (rec )
232+ return records
233+
234+ try :
235+ rendered = render_pages (file_path )
236+ except Exception as exc :
237+ self .signals .error .emit (
238+ file_path , f"Failed to render: { exc } \n { traceback .format_exc ()} "
239+ )
240+ return []
241+
242+ total_pages = len (rendered )
243+ if total_pages == 0 :
244+ self .signals .error .emit (file_path , "No pages rendered." )
245+ return []
246+
247+ self .signals .fileStarted .emit (file_path , total_pages )
248+
249+ records = []
250+ for idx , (img , dw , dh ) in enumerate (rendered ):
251+ rec = PageData (
252+ file_path = file_path ,
253+ page_index = idx ,
254+ total_pages = total_pages ,
255+ image = img ,
256+ detect_width = dw ,
257+ detect_height = dh ,
258+ )
259+ records .append (rec )
260+ self .signals .pageReady .emit (rec )
261+ return records
262+
187263 def run (self ) -> None :
188264 try :
189265 cvr = CaptureVisionRouter ()
@@ -192,40 +268,16 @@ def run(self) -> None:
192268 self .signals .allFinished .emit ()
193269 return
194270
195- template = EnumPresetTemplate . PT_READ_BARCODES_READ_RATE_FIRST . value
271+ template = self . template
196272
197273 for file_path in self .files :
198274 if self ._stop :
199275 break
200- try :
201- rendered = render_pages (file_path )
202- except Exception as exc :
203- self .signals .error .emit (
204- file_path , f"Failed to render: { exc } \n { traceback .format_exc ()} "
205- )
206- continue
207276
208- total_pages = len (rendered )
209- if total_pages == 0 :
210- self .signals .error .emit (file_path , "No pages rendered." )
277+ page_records = self ._prepare_pages (file_path )
278+ if not page_records :
211279 continue
212-
213- self .signals .fileStarted .emit (file_path , total_pages )
214-
215- # Emit placeholder pages first so the UI is responsive while the
216- # scan runs; barcodes will be filled in after decode finishes.
217- page_records : List [PageData ] = []
218- for idx , (img , dw , dh ) in enumerate (rendered ):
219- rec = PageData (
220- file_path = file_path ,
221- page_index = idx ,
222- total_pages = total_pages ,
223- image = img ,
224- detect_width = dw ,
225- detect_height = dh ,
226- )
227- page_records .append (rec )
228- self .signals .pageReady .emit (rec )
280+ total_pages = len (page_records )
229281
230282 if self ._stop :
231283 break
@@ -472,6 +524,7 @@ def __init__(self) -> None:
472524 self ._license_ok = False
473525 self ._barcode_total = 0
474526 self ._auto_select_target : Optional [str ] = None
527+ self ._current_template : str = TEMPLATES [DEFAULT_TEMPLATE_INDEX ][1 ]
475528
476529 self ._build_ui ()
477530 self ._init_license ()
@@ -520,6 +573,20 @@ def _build_ui(self) -> None:
520573 self ._fit_act .triggered .connect (lambda : self .viewer .fit_to_window ())
521574 toolbar .addAction (self ._fit_act )
522575
576+ toolbar .addSeparator ()
577+ toolbar .addWidget (QLabel (" Template: " ))
578+ self ._template_combo = QComboBox ()
579+ for label , _value in TEMPLATES :
580+ self ._template_combo .addItem (label )
581+ self ._template_combo .setCurrentIndex (DEFAULT_TEMPLATE_INDEX )
582+ self ._template_combo .setMinimumWidth (180 )
583+ self ._template_combo .setToolTip (
584+ "Capture Vision preset template. Changing it re-decodes the "
585+ "currently selected file."
586+ )
587+ self ._template_combo .currentIndexChanged .connect (self ._on_template_changed )
588+ toolbar .addWidget (self ._template_combo )
589+
523590 toolbar .addSeparator ()
524591 spacer = QWidget ()
525592 spacer .setSizePolicy (QSizePolicy .Policy .Expanding , QSizePolicy .Policy .Preferred )
@@ -669,6 +736,50 @@ def _on_open_folder(self) -> None:
669736 if folder :
670737 self ._enqueue_paths ([folder ])
671738
739+ def _on_template_changed (self , index : int ) -> None :
740+ if index < 0 or index >= len (TEMPLATES ):
741+ return
742+ label , value = TEMPLATES [index ]
743+ self ._current_template = value
744+
745+ file_path = self ._current_file_path ()
746+ if not file_path :
747+ self ._status_label .setText (f"Template -> { label } " )
748+ return
749+
750+ # Re-decode the currently displayed file with the new template.
751+ # Cached page renders are reused so PDFs/TIFFs don't have to be
752+ # rasterised again.
753+ cached = {
754+ key : page for key , page in self ._pages .items () if key [0 ] == file_path
755+ }
756+ if self ._scanner and self ._scanner .isRunning ():
757+ self ._scanner .stop ()
758+ self ._scanner .wait (2000 )
759+
760+ self ._auto_select_target = None # keep the current selection
761+ self ._progress .setVisible (True )
762+ self ._status_label .setText (
763+ f"Re-decoding { os .path .basename (file_path )} with '{ label } '..."
764+ )
765+
766+ self ._scanner = ScannerThread (
767+ [file_path ], self ._current_template , cached_pages = cached , parent = self
768+ )
769+ self ._scanner .signals .fileStarted .connect (self ._on_file_started )
770+ self ._scanner .signals .pageReady .connect (self ._on_page_ready )
771+ self ._scanner .signals .fileFinished .connect (self ._on_file_finished )
772+ self ._scanner .signals .allFinished .connect (self ._on_all_finished )
773+ self ._scanner .signals .error .connect (self ._on_scan_error )
774+ self ._scanner .start ()
775+
776+ def _current_file_path (self ) -> Optional [str ]:
777+ item = self .tree .currentItem ()
778+ if item is None :
779+ return None
780+ path = item .data (0 , self .FILE_ROLE )
781+ return path if path else None
782+
672783 def _on_clear (self ) -> None :
673784 if self ._scanner and self ._scanner .isRunning ():
674785 self ._scanner .stop ()
@@ -713,7 +824,7 @@ def _enqueue_paths(self, paths: List[str]) -> None:
713824 self ._progress .setVisible (True )
714825 self ._status_label .setText (f"Scanning { len (new_files )} file(s)..." )
715826
716- self ._scanner = ScannerThread (new_files , self )
827+ self ._scanner = ScannerThread (new_files , self . _current_template , parent = self )
717828 self ._scanner .signals .fileStarted .connect (self ._on_file_started )
718829 self ._scanner .signals .pageReady .connect (self ._on_page_ready )
719830 self ._scanner .signals .fileFinished .connect (self ._on_file_finished )
0 commit comments