From a50db2e5ed47c1d1a01805b21e41bebe68b7ea70 Mon Sep 17 00:00:00 2001 From: jaroslawjanas Date: Wed, 3 Dec 2025 15:38:29 +0100 Subject: [PATCH] Add bulk volume editing with rich tooltip * Add checkbox next to Volume field for enabling bulk volume editing (only in bulk mode) * Support single number (5), range (1-10), or comma list (1, 3, 5) input formats * Sort files alphabetically before volume assignment * Validate that volume count matches file count * Add rich HTML tooltip explaining the feature --- kindlecomicconverter/KCC_gui.py | 100 +++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index e227f4b..0465f77 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -22,7 +22,7 @@ import itertools from pathlib import Path from PySide6.QtCore import (QSize, QUrl, Qt, Signal, QIODeviceBase, QEvent, QThread, QSettings) from PySide6.QtGui import (QColor, QIcon, QPixmap, QDesktopServices) -from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog) +from PySide6.QtWidgets import (QApplication, QLabel, QListWidgetItem, QMainWindow, QSystemTrayIcon, QFileDialog, QMessageBox, QDialog, QCheckBox) from PySide6.QtNetwork import (QLocalSocket, QLocalServer) import os @@ -1422,6 +1422,9 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): self.files = files if isinstance(files, list) else [files] self.bulkMode = len(self.files) > 1 + # Sort files by name for consistent volume assignment + self.files.sort() + if self.bulkMode: firstFile = self.files[0] self.parser = metadata.MetadataParser(firstFile) @@ -1429,6 +1432,10 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): self.okButton.setEnabled(True) self.statusLabel.setText(f'Editing {len(self.files)} files.') + # Show bulk volume checkbox + self.bulkVolumeCheck.setVisible(True) + self.bulkVolumeCheck.setChecked(False) + for field in (self.volumeLine, self.numberLine, self.titleLine): field.setEnabled(False) field.setText('') @@ -1447,6 +1454,9 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): file = self.files[0] self.parser = metadata.MetadataParser(file) + # Hide bulk volume checkbox in single file mode + self.bulkVolumeCheck.setVisible(False) + for field in (self.volumeLine, self.numberLine, self.titleLine, self.seriesLine, self.writerLine, self.pencillerLine, self.inkerLine, self.coloristLine): field.setEnabled(True) @@ -1486,7 +1496,16 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): if tmpData: bulkData[fieldName] = tmpData - if not bulkData: + # Handle bulk volume editing + volumes = None + if self.bulkVolumeCheck.isChecked(): + volumeText = self.volumeLine.text() + volumes, error = self.parseVolumeInput(volumeText, len(self.files)) + if error: + self.statusLabel.setText(error) + return + + if not bulkData and volumes is None: self.statusLabel.setText('No changes to apply.') return @@ -1506,6 +1525,9 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): continue for key, value in bulkData.items(): parser.data[key] = value + # Set volume if bulk volume editing is enabled + if volumes is not None: + parser.data['Volume'] = str(volumes[i - 1]) parser.saveXML() except Exception as err: errors.append(f'{os.path.basename(file)}: {str(err)}') @@ -1548,6 +1570,60 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): def cleanData(self, s): return escape(s.strip()) + def parseVolumeInput(self, text, fileCount): + """Parse volume input and return list of volume numbers. + Supports: single number (5), range (5-10), comma list (1,3,5) + Returns (volumes_list, error_message) tuple. + """ + text = text.strip() + if not text: + return None, None + + volumes = [] + + # Check if it's a range (e.g., "5-10") + if '-' in text and ',' not in text: + parts = text.split('-') + if len(parts) == 2: + try: + start = int(parts[0].strip()) + end = int(parts[1].strip()) + if start <= end: + volumes = list(range(start, end + 1)) + else: + return None, 'Invalid range: start > end' + except ValueError: + return None, 'Invalid range format' + # Check if it's a comma-separated list (e.g., "1,3,5") + elif ',' in text: + try: + volumes = [int(v.strip()) for v in text.split(',') if v.strip()] + except ValueError: + return None, 'Invalid list format' + # Single number - generate sequence starting from that number + else: + try: + start = int(text) + volumes = list(range(start, start + fileCount)) + except ValueError: + return None, 'Invalid number' + + # Validate count + if volumes and len(volumes) != fileCount: + return None, f'Volume count ({len(volumes)}) ≠ file count ({fileCount})' + + return volumes, None + + def toggleBulkVolume(self, checked): + """Toggle volume field enabled state based on checkbox.""" + self.volumeLine.setEnabled(checked) + if checked: + self.volumeLine.setText('') + self.volumeLine.setPlaceholderText('e.g., 5 or 1-10 or 1,3,5') + else: + self.volumeLine.setText('') + self.volumeLine.setPlaceholderText('(multiple files)') + def __init__(self): self.ui = QDialog() self.parser = None @@ -1555,6 +1631,26 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): self.bulkMode = False self.setupUi(self.ui) self.ui.setWindowFlags(self.ui.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint) + + # Create bulk volume editing widgets + self.bulkVolumeCheck = QCheckBox() + self.bulkVolumeCheck.setToolTip( + 'Bulk Volume Editing
' + 'Check this box to assign volume numbers to multiple files.

' + 'Input formats:
' + '5 → sequence starting from 5 (5, 6, 7...)
' + '1-10 → range from 1 to 10
' + '1, 3, 5 → specific values

' + 'Note: Files are sorted alphabetically before assignment.' + ) + self.bulkVolumeCheck.stateChanged.connect(self.toggleBulkVolume) + + # Add widget to the grid layout at row 1 (Volume row), column 2 + self.gridLayout.addWidget(self.bulkVolumeCheck, 1, 2, 1, 1) + + # Hide by default (only shown in bulk mode) + self.bulkVolumeCheck.setVisible(False) + self.okButton.clicked.connect(self.saveData) self.cancelButton.clicked.connect(self.ui.close) if sys.platform.startswith('linux'):