summaryrefslogtreecommitdiff
path: root/raphodo/renamepanel.py
diff options
context:
space:
mode:
Diffstat (limited to 'raphodo/renamepanel.py')
-rw-r--r--raphodo/renamepanel.py489
1 files changed, 489 insertions, 0 deletions
diff --git a/raphodo/renamepanel.py b/raphodo/renamepanel.py
new file mode 100644
index 0000000..b60a2fc
--- /dev/null
+++ b/raphodo/renamepanel.py
@@ -0,0 +1,489 @@
+# Copyright (C) 2016 Damon Lynch <damonlynch@gmail.com>
+
+# This file is part of Rapid Photo Downloader.
+#
+# Rapid Photo Downloader is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Rapid Photo Downloader is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Rapid Photo Downloader. If not,
+# see <http://www.gnu.org/licenses/>.
+
+"""
+Display file renaming preferences, including sequence numbers
+"""
+
+__author__ = 'Damon Lynch'
+__copyright__ = "Copyright 2016, Damon Lynch"
+
+from typing import Optional, Dict, Tuple, Union
+import logging
+from gettext import gettext as _
+
+
+from PyQt5.QtCore import (Qt, pyqtSlot, QTime)
+from PyQt5.QtWidgets import (QWidget, QSizePolicy, QComboBox, QFormLayout,
+ QVBoxLayout, QLabel, QSpinBox, QTimeEdit, QCheckBox, QGroupBox,
+ QScrollArea, QFrame, QGridLayout)
+from PyQt5.QtGui import (QColor, QPalette)
+
+
+from raphodo.constants import (PresetPrefType, NameGenerationType,
+ ThumbnailBackgroundName, PresetClass)
+from raphodo.utilities import platform_c_maxint
+from raphodo.rpdfile import FileType, Photo, Video
+from raphodo.nameeditor import PrefDialog, make_sample_rpd_file, PresetComboBox
+import raphodo.exiftool as exiftool
+import raphodo.generatename as gn
+from raphodo.generatenameconfig import *
+from raphodo.viewutils import QFramedWidget
+from raphodo.panelview import QPanelView
+from raphodo.preferences import Preferences, DownloadsTodayTracker
+
+
+class RenameWidget(QFramedWidget):
+ """
+ Display combo boxes for file renaming and file extension case handling, and
+ an example file name
+ """
+
+ def __init__(self, preset_type: PresetPrefType,
+ prefs: Preferences,
+ exiftool_process: exiftool.ExifTool,
+ parent) -> None:
+ super().__init__(parent)
+ self.setBackgroundRole(QPalette.Base)
+ self.setAutoFillBackground(True)
+ self.exiftool_process = exiftool_process
+ self.prefs = prefs
+ self.preset_type = preset_type
+ if preset_type == PresetPrefType.preset_photo_rename:
+ self.file_type = FileType.photo
+ self.pref_defn = DICT_IMAGE_RENAME_L0
+ self.generation_type = NameGenerationType.photo_name
+ self.index_lookup = self.prefs.photo_rename_index
+ self.pref_conv = PHOTO_RENAME_MENU_DEFAULTS_CONV
+ self.generation_type = NameGenerationType.photo_name
+ else:
+ self.file_type = FileType.video
+ self.pref_defn = DICT_VIDEO_RENAME_L0
+ self.generation_type = NameGenerationType.video_name
+ self.index_lookup = self.prefs.video_rename_index
+ self.pref_conv = VIDEO_RENAME_MENU_DEFAULTS_CONV
+ self.generation_type = NameGenerationType.video_name
+
+ self.sample_rpd_file = make_sample_rpd_file(
+ sample_job_code=self.prefs.most_recent_job_code(missing=_('Job Code')),
+ prefs=self.prefs,
+ generation_type=self.generation_type)
+
+ layout = QFormLayout()
+ self.setLayout(layout)
+
+ self.getCustomPresets()
+
+ self.renameCombo = PresetComboBox(prefs=self.prefs, preset_names=self.preset_names,
+ preset_type=preset_type, parent=self, edit_mode=False)
+ self.setRenameComboIndex()
+ self.renameCombo.activated.connect(self.renameComboItemActivated)
+
+ # File extensions
+ self.extensionCombo = QComboBox()
+ self.extensionCombo.addItem(_(ORIGINAL_CASE), ORIGINAL_CASE)
+ self.extensionCombo.addItem(_(UPPERCASE), UPPERCASE)
+ self.extensionCombo.addItem(_(LOWERCASE), LOWERCASE)
+ if preset_type == PresetPrefType.preset_photo_rename:
+ pref_value = self.prefs.photo_extension
+ else:
+ pref_value = self.prefs.video_extension
+ try:
+ index = [ORIGINAL_CASE, UPPERCASE, LOWERCASE].index(pref_value)
+ except ValueError:
+ if preset_type == PresetPrefType.preset_photo_rename:
+ t = 'Photo'
+ else:
+ t = 'Video'
+ logging.error('%s extension case value is invalid. Resetting to lower case.', t)
+ index = 2
+ self.extensionCombo.setCurrentIndex(index)
+ self.extensionCombo.currentIndexChanged.connect(self.extensionChanged)
+
+ self.example = QLabel()
+ self.updateExampleFilename()
+
+ layout.addRow(_('Preset:'), self.renameCombo)
+ layout.addRow(_('Extension:'), self.extensionCombo)
+ layout.addRow(_('Example:'), self.example)
+
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
+
+ def setRenameComboIndex(self) -> None:
+ """
+ Set the value being displayed in the combobox to reflect the
+ current renaming preference.
+
+ Takes into account built-in renaming presets and custom presets.
+ """
+
+ index = self.index_lookup(self.preset_pref_lists)
+ if index == -1:
+ # Set to the "Custom..." value
+ cb_index = self.renameCombo.count() - 1
+ else:
+ # Set to appropriate combobox idex, allowing for possible separator
+ cb_index = self.renameCombo.getComboBoxIndex(index)
+ logging.debug("Setting %s combobox chosen value to %s", self.file_type.name,
+ self.renameCombo.itemText(cb_index))
+ self.renameCombo.setCurrentIndex(cb_index)
+
+ def pref_list(self) -> List[str]:
+ """
+ :return: the user's file naming preference according to whether
+ this widget is handling photos or videos
+ """
+ if self.preset_type == PresetPrefType.preset_photo_rename:
+ return self.prefs.photo_rename
+ else:
+ return self.prefs.video_rename
+
+ @pyqtSlot(int)
+ def renameComboItemActivated(self, index: int) -> None:
+ """
+ Respond to user activating the Rename preset combo box.
+
+ :param index: index of the item activated
+ """
+
+ user_pref_list = None
+
+ preset_class = self.renameCombo.currentData()
+ if preset_class == PresetClass.start_editor:
+
+
+ prefDialog = PrefDialog(self.pref_defn, self.pref_list(), self.generation_type,
+ self.prefs, self.sample_rpd_file)
+
+ if prefDialog.exec():
+ user_pref_list = prefDialog.getPrefList()
+ if not user_pref_list:
+ user_pref_list = None
+
+ # Regardless of whether the user clicked OK or cancel, refresh the rename combo
+ # box entries
+ self.getCustomPresets()
+ self.renameCombo.resetEntries(self.preset_names)
+ self.setUserPrefList(user_pref_list=user_pref_list)
+ self.setRenameComboIndex()
+ else:
+ assert preset_class == PresetClass.custom or preset_class == PresetClass.builtin
+ index = self.renameCombo.getPresetIndex(self.renameCombo.currentIndex())
+ user_pref_list = self.combined_pref_lists[index]
+ self.setUserPrefList(user_pref_list=user_pref_list)
+
+ self.updateExampleFilename()
+
+ def getCustomPresets(self) -> None:
+ """
+ Get the custom presets from the user preferences and store them in lists
+ """
+
+ self.preset_names, self.preset_pref_lists = self.prefs.get_preset(
+ preset_type=self.preset_type)
+ self.combined_pref_lists = self.pref_conv + tuple(self.preset_pref_lists)
+
+ def setUserPrefList(self, user_pref_list: List[str]) -> None:
+ """
+ Update the user preferences with a new preference value
+ :param user_pref_list: the photo or video rename preference list
+ """
+
+ if user_pref_list is not None:
+ logging.debug("Setting new %s rename preference value", self.file_type.name)
+ if self.preset_type == PresetPrefType.preset_photo_rename:
+ self.prefs.photo_rename = user_pref_list
+ else:
+ self.prefs.video_rename = user_pref_list
+
+ def updateExampleFilename(self, downloads_today: Optional[List[str]]=None,
+ stored_sequence_no: Optional[int]=None) -> None:
+ """
+ Update filename shown to user that serves as an example of the
+ renaming rule in practice on sample data.
+
+ :param downloads_today: if specified, update the downloads today value
+ :param stored_sequence_no: if specified, update the stored sequence value
+ """
+
+ if downloads_today:
+ self.sample_rpd_file.sequences.downloads_today_tracker.downloads_today = downloads_today
+ if stored_sequence_no is not None:
+ self.sample_rpd_file.sequences.stored_sequence_no = stored_sequence_no
+
+ if self.preset_type == PresetPrefType.preset_photo_rename:
+ self.name_generator = gn.PhotoName(self.prefs.photo_rename)
+ logging.debug("Updating example photo name in rename panel")
+ else:
+ self.name_generator = gn.VideoName(self.prefs.video_rename)
+ logging.debug("Updating example video name in rename panel")
+
+ self.example.setText(self.name_generator.generate_name(self.sample_rpd_file))
+
+ def updateSampleFile(self, sample_rpd_file: Union[Photo, Video]) -> None:
+ self.sample_rpd_file = make_sample_rpd_file(
+ sample_rpd_file=sample_rpd_file,
+ sample_job_code=self.prefs.most_recent_job_code(missing=_('Job Code')),
+ prefs=self.prefs,
+ generation_type=self.generation_type)
+ self.updateExampleFilename()
+
+ @pyqtSlot(int)
+ def extensionChanged(self, index: int) -> None:
+ """
+ Respond to user changing the case of file extensions in file name generation.
+
+ Save new preference value, and update example file name.
+ """
+
+ value = self.extensionCombo.currentData()
+ if self.preset_type == PresetPrefType.preset_photo_rename:
+ self.prefs.photo_extension = value
+ else:
+ self.prefs.video_extension = value
+ self.sample_rpd_file.generate_extension_case = value
+ self.updateExampleFilename()
+
+
+class RenameOptionsWidget(QFramedWidget):
+ """
+ Display and allow editing of preference values for Downloads today
+ and Stored Sequence Number and associated options, as well as
+ the strip incompatible characters option.
+ """
+
+ def __init__(self, prefs: Preferences,
+ photoRenameWidget: RenameWidget,
+ videoRenameWidget: RenameWidget,
+ parent) -> None:
+ super().__init__(parent)
+
+ self.prefs = prefs
+ self.photoRenameWidget = photoRenameWidget
+ self.videoRenameWidget = videoRenameWidget
+
+ self.setBackgroundRole(QPalette.Base)
+ self.setAutoFillBackground(True)
+
+ compatibilityLayout = QVBoxLayout()
+ layout = QVBoxLayout()
+ self.setLayout(layout)
+
+ # QSpinBox cannot display values greater than this value
+ c_maxint = platform_c_maxint()
+
+ tip = _('A counter for how many downloads occur on each day')
+ self.downloadsTodayLabel = QLabel(_('Downloads today:'))
+ self.downloadsToday = QSpinBox()
+ self.downloadsToday.setMinimum(0)
+ # QSpinBox defaults to a maximum of 99
+ self.downloadsToday.setMaximum(c_maxint)
+ self.downloadsToday.setToolTip(tip)
+
+ # This instance of the downloads today tracker is secondary to the
+ # instance in the rename files process. That process automatically
+ # updates the value and then once a download is complete, the
+ # downloads today value here is overwritten.
+ self.downloads_today_tracker = DownloadsTodayTracker(
+ day_start=self.prefs.day_start,
+ downloads_today=self.prefs.downloads_today)
+
+ downloads_today = self.downloads_today_tracker.get_or_reset_downloads_today()
+ if self.prefs.downloads_today != self.downloads_today_tracker.downloads_today:
+ self.prefs.downloads_today = self.downloads_today_tracker.downloads_today
+
+ self.downloadsToday.setValue(downloads_today)
+ self.downloadsToday.valueChanged.connect(self.downloadsTodayChanged)
+
+ tip = _('A counter that is remembered each time the program is run ')
+ self.storedNumberLabel = QLabel(_('Stored number:'))
+ self.storedNumberLabel.setToolTip(tip)
+ self.storedNumber = QSpinBox()
+ self.storedNumber.setMinimum(0)
+ self.storedNumber.setMaximum(c_maxint)
+ self.storedNumber.setToolTip(tip)
+ try:
+ stored_value = int(self.prefs.stored_sequence_no)
+ assert stored_value >= 0 and stored_value <= c_maxint
+ except (ValueError, AssertionError):
+ stored_value = 0
+ logging.error("Resetting invalid stored sequence number to 0")
+ self.prefs.stored_sequence_no = stored_value
+
+ self.storedNumber.setValue(stored_value)
+ self.storedNumber.valueChanged.connect(self.storedNumberChanged)
+
+ tip = _('The time at which the <i>Downloads today</i> sequence number should be reset')
+ self.dayStartLabel = QLabel(_('Day start:'))
+ self.dayStartLabel.setToolTip(tip)
+
+ self.dayStart = QTimeEdit()
+ self.dayStart.setToolTip(tip)
+ self.dayStart.setTime(self.prefs.get_day_start_qtime())
+ self.dayStart.timeChanged.connect(self.timeChanged)
+ # 24 hour format, if wanted in a future release:
+ # self.dayStart.setDisplayFormat('HH:mm:ss')
+
+ self.sync = QCheckBox(_('Synchronize RAW + JPEG'))
+ self.sync.setChecked(self.prefs.synchronize_raw_jpg)
+ self.sync.stateChanged.connect(self.syncChanged)
+ tip = _('Synchronize sequence numbers for matching RAW and JPEG pairs.\n\n'
+ 'See the online documentation for more details.')
+ self.sync.setToolTip(tip)
+
+ self.sequences = QGroupBox(_('Sequence Numbers'))
+
+ sequencesLayout = QFormLayout()
+
+ sequencesLayout.addRow(self.storedNumberLabel, self.storedNumber)
+ sequencesLayout.addRow(self.downloadsTodayLabel, self.downloadsToday)
+ sequencesLayout.addRow(self.dayStartLabel, self.dayStart)
+ sequencesLayout.addRow(self.sync)
+
+ self.sequences.setLayout(sequencesLayout)
+
+ self.stripCharacters = QCheckBox(_('Strip incompatible characters'))
+ self.stripCharacters.setChecked(self.prefs.strip_characters)
+ self.stripCharacters.stateChanged.connect(self.stripCharactersChanged)
+ self.stripCharacters.setToolTip(_('Whether photo, video and folder names should have any '
+ 'characters removed that are not allowed by other operating systems'))
+ self.compatibility = QGroupBox(_('Compatibility'))
+ self.compatibility.setLayout(compatibilityLayout)
+ compatibilityLayout.addWidget(self.stripCharacters)
+
+ layout.addWidget(self.sequences)
+ layout.addWidget(self.compatibility)
+ layout.addStretch()
+ layout.setSpacing(18)
+
+ self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
+
+ @pyqtSlot(QTime)
+ def timeChanged(self, time: QTime) -> None:
+ hour = time.hour()
+ minute = time.minute()
+ self.prefs.day_start = '{}:{}'.format(hour, minute)
+ logging.debug("Setting day start to %s", self.prefs.day_start)
+ self.downloads_today_tracker.set_day_start(hour=hour, minute=minute)
+
+ @pyqtSlot(int)
+ def downloadsTodayChanged(self, value: int) -> None:
+ self.downloads_today_tracker.reset_downloads_today(value=value)
+ dt = self.downloads_today_tracker.downloads_today
+ logging.debug("Setting downloads today value to %s %s", dt[0], dt[1])
+ self.prefs.downloads_today = dt
+ if self.prefs.photo_rename_pref_uses_downloads_today():
+ self.photoRenameWidget.updateExampleFilename(downloads_today=dt)
+ if self.prefs.video_rename_pref_uses_downloads_today():
+ self.videoRenameWidget.updateExampleFilename(downloads_today=dt)
+
+ @pyqtSlot(int)
+ def storedNumberChanged(self, value: int) -> None:
+ logging.debug("Setting stored sequence no to %d", value)
+ self.prefs.stored_sequence_no = value
+ if self.prefs.photo_rename_pref_uses_stored_sequence_no():
+ self.photoRenameWidget.updateExampleFilename(stored_sequence_no=value)
+ if self.prefs.video_rename_pref_uses_stored_sequence_no():
+ self.videoRenameWidget.updateExampleFilename(stored_sequence_no=value)
+
+ @pyqtSlot(int)
+ def syncChanged(self, state: int) -> None:
+ sync = state == Qt.Checked
+ logging.debug("Setting synchronize RAW + JPEG sequence values to %s", sync)
+ self.prefs.synchronize_raw_jpg = sync
+
+ @pyqtSlot(int)
+ def stripCharactersChanged(self, state: int) -> None:
+ strip = state == Qt.Checked
+ logging.debug("Setting strip incompatible characers to %s", strip)
+ self.prefs.strip_characters = strip
+
+
+class RenamePanel(QScrollArea):
+ """
+ Renaming preferences widget, for photos, videos, and general
+ renaming options.
+ """
+
+ def __init__(self, parent) -> None:
+ super().__init__(parent)
+ if parent is not None:
+ self.rapidApp = parent
+ self.prefs = self.rapidApp.prefs
+ else:
+ self.prefs = None
+
+ self.setFrameShape(QFrame.NoFrame)
+
+ self.photoRenamePanel = QPanelView(label=_('Photo Renaming'),
+ headerColor=QColor(ThumbnailBackgroundName),
+ headerFontColor=QColor(Qt.white))
+ self.videoRenamePanel = QPanelView(label=_('Video Renaming'),
+ headerColor=QColor(ThumbnailBackgroundName),
+ headerFontColor=QColor(Qt.white))
+ self.renameOptionsPanel = QPanelView(label=_('Renaming Options'),
+ headerColor=QColor(ThumbnailBackgroundName),
+ headerFontColor=QColor(Qt.white))
+
+ self.photoRenameWidget = RenameWidget(preset_type=PresetPrefType.preset_photo_rename,
+ prefs=self.prefs, parent=self,
+ exiftool_process=self.rapidApp.exiftool_process)
+ self.photoRenamePanel.addWidget(self.photoRenameWidget)
+
+ self.videoRenameWidget = RenameWidget(preset_type=PresetPrefType.preset_video_rename,
+ prefs=self.prefs, parent=self,
+ exiftool_process=self.rapidApp.exiftool_process)
+ self.videoRenamePanel.addWidget(self.videoRenameWidget)
+
+ self.renameOptions = RenameOptionsWidget(prefs=self.prefs, parent=self,
+ photoRenameWidget=self.photoRenameWidget,
+ videoRenameWidget=self.videoRenameWidget)
+ self.renameOptionsPanel.addWidget(self.renameOptions)
+
+ widget = QWidget()
+ layout = QVBoxLayout()
+ layout.setContentsMargins(0, 0, 0, 0)
+ widget.setLayout(layout)
+ layout.addWidget(self.photoRenamePanel)
+ layout.addWidget(self.videoRenamePanel)
+ layout.addWidget(self.renameOptionsPanel)
+ self.setWidget(widget)
+ self.setWidgetResizable(True)
+ self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
+
+ def updateSequences(self, downloads_today: List[str], stored_sequence_no: int) -> None:
+ """
+ Update the value displayed in the display to reflect any values changed after
+ the completion of a download.
+
+ :param downloads_today: new downloads today value
+ :param stored_sequence_no: new stored sequence number value
+ """
+
+ self.renameOptions.downloadsToday.setValue(int(downloads_today[1]))
+ self.renameOptions.downloads_today_tracker.downloads_today = downloads_today
+ self.renameOptions.storedNumber.setValue(stored_sequence_no)
+
+ def setSamplePhoto(self, sample_photo: Photo) -> None:
+ self.photoRenameWidget.updateSampleFile(sample_rpd_file=sample_photo)
+
+ def setSampleVideo(self, sample_video: Video) -> None:
+ self.videoRenameWidget.updateSampleFile(sample_rpd_file=sample_video)
+
+
+