diff options
Diffstat (limited to 'rapid/preferencesdialog.py')
-rw-r--r-- | rapid/preferencesdialog.py | 1963 |
1 files changed, 0 insertions, 1963 deletions
diff --git a/rapid/preferencesdialog.py b/rapid/preferencesdialog.py deleted file mode 100644 index a0767af..0000000 --- a/rapid/preferencesdialog.py +++ /dev/null @@ -1,1963 +0,0 @@ -#!/usr/bin/python -# -*- coding: latin1 -*- - -### Copyright (C) 2007-2014 Damon Lynch <damonlynch@gmail.com> - -### This program 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 2 of the License, or -### (at your option) any later version. - -### This program 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 this program; if not, write to the Free Software -### Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 -### USA - - -import datetime, re - -import gtk - -import datetime -import multiprocessing -import logging -logger = multiprocessing.get_logger() - -import webbrowser - -import ValidatedEntry -import misc - -import config -import paths -import rpdfile -import higdefaults as hd -import metadataphoto -import metadatavideo - -import tableplusminus as tpm - -import utilities - -import generatename as gn -from generatenameconfig import * -import problemnotification as pn - -from prefsrapid import format_pref_list_for_pretty_print, DownloadsTodayTracker - -from gettext import gettext as _ - -class PrefError(Exception): - """ base class """ - def unpackList(self, l): - """ - Make the preferences presentable to the user - """ - - s = '' - for i in l: - if i <> ORDER_KEY: - s += "'" + i + "', " - return s[:-2] - - def __str__(self): - return self.msg - -class PrefKeyError(PrefError): - def __init__(self, error): - value = error[0] - expectedValues = self.unpackList(error[1]) - self.msg = "Preference key '%(key)s' is invalid.\nExpected one of %(value)s" % { - 'key': value, 'value': expectedValues} - - -class PrefValueInvalidError(PrefKeyError): - def __init__(self, error): - value = error[0] - self.msg = "Preference value '%(value)s' is invalid" % {'value': value} - -class PrefLengthError(PrefError): - def __init__(self, error): - self.msg = "These preferences are not well formed:" + "\n %s" % self.unpackList(error) - -class PrefValueKeyComboError(PrefError): - def __init__(self, error): - self.msg = error - - -def check_pref_valid(pref_defn, prefs, modulo=3): - """ - Checks to see if prefs are valid according to definition. - - prefs is a list of preferences. - pref_defn is a Dict specifying what is valid. - modulo is how many list elements are equivalent to one line of preferences. - - Returns True if prefs match with pref_defn, - else raises appropriate error. - """ - - if (len(prefs) % modulo <> 0) or not prefs: - raise PrefLengthError(prefs) - else: - for i in range(0, len(prefs), modulo): - _check_pref_valid(pref_defn, prefs[i:i+modulo]) - - return True - -def _check_pref_valid(pref_defn, prefs): - - key = prefs[0] - value = prefs[1] - - - if pref_defn.has_key(key): - - next_pref_defn = pref_defn[key] - - if value == None: - # value should never be None, at any time - raise PrefValueInvalidError((None, next_pref_defn)) - - if next_pref_defn and not value: - raise gn.PrefValueInvalidError((value, next_pref_defn)) - - if type(next_pref_defn) == type({}): - return _check_pref_valid(next_pref_defn, prefs[1:]) - else: - if type(next_pref_defn) == type([]): - result = value in next_pref_defn - if not result: - raise gn.PrefValueInvalidError((value, next_pref_defn)) - return True - elif not next_pref_defn: - return True - else: - result = next_pref_defn == value - if not result: - raise gn.PrefKeyValue((value, next_pref_defn)) - return True - else: - raise PrefKeyError((key, pref_defn[ORDER_KEY])) - - -def filter_subfolder_prefs(pref_list): - """ - Filters out extraneous preference choices - """ - prefs_changed = False - continue_check = True - while continue_check and pref_list: - continue_check = False - if pref_list[0] == SEPARATOR: - # subfolder preferences should not start with a / - pref_list = pref_list[3:] - prefs_changed = True - continue_check = True - elif pref_list[-3] == SEPARATOR: - # subfolder preferences should not end with a / - pref_list = pref_list[:-3] - continue_check = True - prefs_changed = True - else: - for i in range(0, len(pref_list) - 3, 3): - if pref_list[i] == SEPARATOR and pref_list[i+3] == SEPARATOR: - # subfolder preferences should not contain two /s side by side - continue_check = True - prefs_changed = True - # note we are messing with the contents of the pref list, - # must exit loop and try again - pref_list = pref_list[:i] + pref_list[i+3:] - break - - return (prefs_changed, pref_list) - -class Comboi18n(gtk.ComboBox): - """ very simple i18n version of the venerable combo box - with one column displayed to the user. - - This combo box has two columns: - 1. the first contains the actual value and is invisible - 2. the second contains the translation of the first column, and this is what - the users sees - """ - def __init__(self): - liststore = gtk.ListStore(str, str) - gtk.ComboBox.__init__(self, liststore) - cell = gtk.CellRendererText() - self.pack_start(cell, True) - self.add_attribute(cell, 'text', 1) - # must name the combo box on pygtk used in Ubuntu 11.04, Fedora 15, etc. - self.set_name('GtkComboBox') - - def append_text(self, text): - model = self.get_model() - model.append((text, _(text))) - - def get_active_text(self): - model = self.get_model() - active = self.get_active() - if active < 0: - return None - return model[active][0] - -class PreferenceWidgets: - - def __init__(self, default_row, default_prefs, pref_defn_L0, pref_list): - self.default_row = default_row - self.default_prefs = default_prefs - self.pref_defn_L0 = pref_defn_L0 - self.pref_list = pref_list - - def _create_combo(self, choices): - combobox = Comboi18n() - for text in choices: - combobox.append_text(text) - return combobox - - def get_default_row(self): - """ - returns a list of default widgets - """ - return self.get_widgets_based_on_user_selection(self.default_row) - - def _get_pref_widgets(self, pref_definition, prefs, widgets): - key = prefs[0] - value = prefs[1] - - # supply a default value if the user has not yet chosen a value! - if not key: - key = pref_definition[ORDER_KEY][0] - - if not key in pref_definition: - raise gn.PrefKeyError((key, pref_definition.keys())) - - - list0 = pref_definition[ORDER_KEY] - - # the first widget will always be a combo box - widget0 = self._create_combo(list0) - widget0.set_active(list0.index(key)) - - widgets.append(widget0) - - if key == TEXT: - widget1 = gtk.Entry() - widget1.set_text(value) - - widgets.append(widget1) - widgets.append(None) - return - elif key in [SEPARATOR, JOB_CODE]: - widgets.append(None) - widgets.append(None) - return - else: - next_pref_definition = pref_definition[key] - if type(next_pref_definition) == type({}): - return self._get_pref_widgets(next_pref_definition, - prefs[1:], - widgets) - else: - if type(next_pref_definition) == type([]): - widget1 = self._create_combo(next_pref_definition) - if not value: - value = next_pref_definition[0] - try: - widget1.set_active(next_pref_definition.index(value)) - except: - raise gn.PrefValueInvalidError((value, next_pref_definition)) - - widgets.append(widget1) - else: - widgets.append(None) - - def _get_values_from_list(self): - for i in range(0, len(self.pref_list), 3): - yield (self.pref_list[i], self.pref_list[i+1], self.pref_list[i+2]) - - def get_widgets_based_on_prefs(self): - """ - Yields a list of widgets and their callbacks based on the users preferences. - - This list is equivalent to one row of preferences when presented to the - user in the Plus Minus Table. - """ - - for L0, L1, L2 in self._get_values_from_list(): - prefs = [L0, L1, L2] - widgets = [] - self._get_pref_widgets(self.pref_defn_L0, prefs, widgets) - yield widgets - - - def get_widgets_based_on_user_selection(self, selection): - """ - Returns a list of widgets and their callbacks based on what the user has selected. - - Selection is the values the user has chosen thus far in comboboxes. - It determines the contents of the widgets returned. - It should be a list of three values, with None for values not chosen. - For values which are None, the first value in the preferences - definition is chosen. - - """ - widgets = [] - - self._get_pref_widgets(self.pref_defn_L0, selection, widgets) - return widgets - - def check_prefs_for_validity(self): - """ - Checks preferences validity - """ - - return check_pref_valid(self.pref_defn_L0, self.pref_list) - -class PhotoNamePrefs(PreferenceWidgets): - def __init__(self, pref_list): - PreferenceWidgets.__init__(self, - default_row = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE], - default_prefs = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE], - pref_defn_L0 = DICT_IMAGE_RENAME_L0, - pref_list = pref_list) - -class VideoNamePrefs(PreferenceWidgets): - def __init__(self, pref_list): - PreferenceWidgets.__init__(self, - default_row = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE], - default_prefs = [FILENAME, NAME_EXTENSION, ORIGINAL_CASE], - pref_defn_L0 = DICT_VIDEO_RENAME_L0, - pref_list = pref_list) - - -class PhotoSubfolderPrefs(PreferenceWidgets): - def __init__(self, pref_list): - - PreferenceWidgets.__init__(self, - default_row = [DATE_TIME, IMAGE_DATE, LIST_DATE_TIME_L2[0]], - default_prefs = DEFAULT_SUBFOLDER_PREFS, - pref_defn_L0 = DICT_SUBFOLDER_L0, - pref_list = pref_list) - - def filter_preferences(self): - filtered, pref_list = filter_subfolder_prefs(self.pref_list) - if filtered: - self.pref_list = pref_list - - def check_prefs_for_validity(self): - """ - Checks subfolder preferences validity above and beyond image name checks. - - See parent method for full description. - - Subfolders have additional requirments to that of file names. - """ - v = PreferenceWidgets.check_prefs_for_validity(self) - if v: - # peform additional checks: - # 1. do not start with a separator - # 2. do not end with a separator - # 3. do not have two separators in a row - # these three rules will ensure something else other than a - # separator is specified - L1s = [] - for i in range(0, len(self.pref_list), 3): - L1s.append(self.pref_list[i]) - - if L1s[0] == SEPARATOR: - raise PrefValueKeyComboError(_("Subfolder preferences should not start with a %s") % os.sep) - elif L1s[-1] == SEPARATOR: - raise PrefValueKeyComboError(_("Subfolder preferences should not end with a %s") % os.sep) - else: - for i in range(len(L1s) - 1): - if L1s[i] == SEPARATOR and L1s[i+1] == SEPARATOR: - raise PrefValueKeyComboError(_("Subfolder preferences should not contain two %s one after the other") % os.sep) - return v - -class VideoSubfolderPrefs(PhotoSubfolderPrefs): - def __init__(self, pref_list): - PreferenceWidgets.__init__(self, - default_row = [DATE_TIME, VIDEO_DATE, LIST_DATE_TIME_L2[0]], - default_prefs = DEFAULT_VIDEO_SUBFOLDER_PREFS, - pref_defn_L0 = DICT_VIDEO_SUBFOLDER_L0, - pref_list = pref_list) - -class QuestionDialog(gtk.Dialog): - def __init__(self, parent_window, title, question, use_markup=False, - default_to_yes=True, post_choice_callback=None): - gtk.Dialog.__init__(self, title, None, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_NO, gtk.RESPONSE_CANCEL, - gtk.STOCK_YES, gtk.RESPONSE_OK)) - - self.post_choice_callback = post_choice_callback - self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg')) - - prompt_hbox = gtk.HBox() - - icontheme = gtk.icon_theme_get_default() - icon = icontheme.load_icon('dialog-question', 36, gtk.ICON_LOOKUP_USE_BUILTIN) - if icon: - image = gtk.Image() - image.set_from_pixbuf(icon) - prompt_hbox.pack_start(image, False, False, padding = 6) - - prompt_label = gtk.Label(question) - prompt_label.set_use_markup(use_markup) - prompt_label.set_line_wrap(True) - prompt_hbox.pack_start(prompt_label, False, False, padding=6) - - self.vbox.pack_start(prompt_hbox, padding=6) - - self.set_border_width(6) - self.set_has_separator(False) - - if default_to_yes: - self.set_default_response(gtk.RESPONSE_OK) - else: - self.set_default_response(gtk.RESPONSE_CANCEL) - - self.set_transient_for(parent_window) - self.show_all() - - if post_choice_callback: - self.connect('response', self.on_response) - - def on_response(self, device_dialog, response): - user_selected = response == gtk.RESPONSE_OK - self.post_choice_callback(self, user_selected) - -class RemoveAllJobCodeDialog(QuestionDialog): - def __init__(self, parent_window, post_choice_callback): - QuestionDialog.__init__(self, parent_window, - _('Remove all Job Codes?'), - _('Should all Job Codes be removed?'), - post_choice_callback=post_choice_callback) - -class RemoveAllRemeberedDevicesDialog(QuestionDialog): - def __init__(self, parent_window, post_choice_callback): - QuestionDialog.__init__(self, parent_window, - _('Remove all Remembered Paths?'), - _('Should all remembered paths be removed?'), - post_choice_callback=post_choice_callback) - -class RemoveAllIgnoredPathsDialog(QuestionDialog): - def __init__(self, parent_window, post_choice_callback): - QuestionDialog.__init__(self, parent_window, - _('Remove all Ignored Paths?'), - _('Should all ignored paths be removed?'), - post_choice_callback=post_choice_callback) - -class PhotoRenameTable(tpm.TablePlusMinus): - - def __init__(self, preferencesdialog, adjust_scroll_window): - - tpm.TablePlusMinus.__init__(self, 1, 3) - self.preferencesdialog = preferencesdialog - self.adjust_scroll_window = adjust_scroll_window - if not hasattr(self, "error_title"): - self.error_title = _("Error in Photo Rename preferences") - - self.table_type = self.error_title[len("Error in "):] - self.i = 0 - - if adjust_scroll_window: - self.scroll_bar = self.adjust_scroll_window.get_vscrollbar() - #this next line does not work on early versions of pygtk :( - self.scroll_bar.connect('visibility-notify-event', self.scrollbar_visibility_change) - self.connect("size-request", self.size_adjustment) - self.connect("add", self.size_adjustment) - self.connect("remove", self.size_adjustment) - - self.bump = 16 - self.have_vertical_scrollbar = False - - - self.get_preferencesdialog_prefs() - self.setup_prefs_factory() - - try: - self.prefs_factory.check_prefs_for_validity() - - except (PrefValueInvalidError, PrefLengthError, - PrefValueKeyComboError, PrefKeyError), e: - - logger.error(self.error_title) - logger.error("Sorry, these preferences contain an error:") - logger.error(format_pref_list_for_pretty_print(self.prefs_factory.pref_list)) - - # the preferences were invalid - # reset them to their default - - self.pref_list = self.prefs_factory.default_prefs - self.setup_prefs_factory() - self.update_parentapp_prefs() - - msg = "%s.\n" % e - msg += "Resetting to default values." - logger.error(msg) - - - misc.run_dialog(self.error_title, msg, - preferencesdialog, - gtk.MESSAGE_ERROR) - - for row in self.prefs_factory.get_widgets_based_on_prefs(): - self.append(row) - - def update_preferences(self): - pref_list = [] - for row in self.pm_rows: - for col in range(self.pm_no_columns): - widget = row[col] - if widget: - name = widget.get_name() - if name == 'GtkComboBox': - value = widget.get_active_text() - elif name == 'GtkEntry': - value = widget.get_text() - else: - logger.critical("Program error: Unknown preference widget!") - value = '' - else: - value = '' - pref_list.append(value) - - self.pref_list = pref_list - self.update_parentapp_prefs() - self.prefs_factory.pref_list = pref_list - self.update_example() - - - def scrollbar_visibility_change(self, widget, event): - if event.state == gtk.gdk.VISIBILITY_UNOBSCURED: - self.have_vertical_scrollbar = True - self.adjust_scroll_window.set_size_request(self.adjust_scroll_window.allocation.width + self.bump, -1) - - - def size_adjustment(self, widget, arg2): - """ - Adjust scrolledwindow width in preferences dialog to reflect width of image rename table - - The algorithm is complicated by the need to take into account the presence of a vertical scrollbar, - which might be added as the user adds more rows - - The pygtk code behaves inconsistently depending on the pygtk version - """ - - if self.adjust_scroll_window: - self.have_vertical_scrollbar = self.scroll_bar.allocation.width > 1 or self.have_vertical_scrollbar - if not self.have_vertical_scrollbar: - if self.allocation.width > self.adjust_scroll_window.allocation.width: - self.adjust_scroll_window.set_size_request(self.allocation.width, -1) - else: - if self.allocation.width > self.adjust_scroll_window.allocation.width - self.bump: - self.adjust_scroll_window.set_size_request(self.allocation.width + self.bump, -1) - self.bump = 0 - - def get_preferencesdialog_prefs(self): - self.pref_list = self.preferencesdialog.prefs.image_rename - - - def setup_prefs_factory(self): - self.prefs_factory = PhotoNamePrefs(self.pref_list) - - def update_parentapp_prefs(self): - self.preferencesdialog.prefs.image_rename = self.pref_list - - def update_example_job_code(self): - job_code = self.preferencesdialog.prefs.get_sample_job_code() - if not job_code: - job_code = _('Job code') - #~ self.prefs_factory.setJobCode(job_code) - - def update_example(self): - self.preferencesdialog.update_photo_rename_example() - - def get_default_row(self): - return self.prefs_factory.get_default_row() - - def on_combobox_changed(self, widget, row_position): - - for col in range(self.pm_no_columns): - if self.pm_rows[row_position][col] == widget: - break - selection = [] - for i in range(col + 1): - # ensure it is a combo box we are getting the value from - w = self.pm_rows[row_position][i] - name = w.get_name() - if name == 'GtkComboBox': - selection.append(w.get_active_text()) - else: - selection.append(w.get_text()) - - for i in range(col + 1, self.pm_no_columns): - selection.append('') - - if col <> (self.pm_no_columns - 1): - widgets = self.prefs_factory.get_widgets_based_on_user_selection(selection) - - for i in range(col + 1, self.pm_no_columns): - old_widget = self.pm_rows[row_position][i] - if old_widget: - self.remove(old_widget) - if old_widget in self.pm_callbacks: - del self.pm_callbacks[old_widget] - new_widget = widgets[i] - self.pm_rows[row_position][i] = new_widget - if new_widget: - self._create_callback(new_widget, row_position) - self.attach(new_widget, i, i+1, row_position, row_position + 1) - new_widget.show() - self.update_preferences() - - - def on_entry_changed(self, widget, row_position): - self.update_preferences() - - def on_row_added(self, row_position): - """ - Update preferences, as a row has been added - """ - self.update_preferences() - - # if this was the last row or 2nd to last row, and another has just been added, move vertical scrollbar down - if row_position in range(self.pm_no_rows - 3, self.pm_no_rows - 2): - adjustment = self.preferencesdialog.rename_scrolledwindow.get_vadjustment() - adjustment.set_value(adjustment.upper) - - - def on_row_deleted(self, row_position): - """ - Update preferences, as a row has been deleted - """ - self.update_preferences() - -class VideoRenameTable(PhotoRenameTable): - def __init__(self, preferencesdialog, adjust_scroll_window): - self.error_title = _("Error in Video Rename preferences") - PhotoRenameTable.__init__(self, preferencesdialog, adjust_scroll_window) - - def get_preferencesdialog_prefs(self): - self.pref_list = self.preferencesdialog.prefs.video_rename - - def setup_prefs_factory(self): - self.prefs_factory = VideoNamePrefs(self.pref_list) - - def update_parentapp_prefs(self): - self.preferencesdialog.prefs.video_rename = self.pref_list - - def update_example(self): - self.preferencesdialog.update_video_rename_example() - -class SubfolderTable(PhotoRenameTable): - """ - Table to display photo download subfolder preferences as part of preferences - dialog window. - """ - def __init__(self, preferencesdialog, adjust_scroll_window): - self.error_title = _("Error in Photo Download Subfolders preferences") - PhotoRenameTable.__init__(self, preferencesdialog, adjust_scroll_window) - - def get_preferencesdialog_prefs(self): - self.pref_list = self.preferencesdialog.prefs.subfolder - - def setup_prefs_factory(self): - self.prefs_factory = PhotoSubfolderPrefs(self.pref_list) - - def update_parentapp_prefs(self): - self.preferencesdialog.prefs.subfolder = self.pref_list - - def update_example(self): - self.preferencesdialog.update_photo_download_folder_example() - -class VideoSubfolderTable(PhotoRenameTable): - def __init__(self, preferencesdialog, adjust_scroll_window): - self.error_title = _("Error in Video Download Subfolders preferences") - PhotoRenameTable.__init__(self, preferencesdialog, adjust_scroll_window) - - def get_preferencesdialog_prefs(self): - self.pref_list = self.preferencesdialog.prefs.video_subfolder - - def setup_prefs_factory(self): - self.prefs_factory = VideoSubfolderPrefs(self.pref_list) - - def update_parentapp_prefs(self): - self.preferencesdialog.prefs.video_subfolder = self.pref_list - - def update_example(self): - self.preferencesdialog.update_video_download_folder_example() - -class RemoveAllJobCodeDialog(gtk.Dialog): - def __init__(self, parent_window, post_choice_callback): - gtk.Dialog.__init__(self, _('Remove all Job Codes?'), None, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_NO, gtk.RESPONSE_CANCEL, - gtk.STOCK_YES, gtk.RESPONSE_OK)) - - self.post_choice_callback = post_choice_callback - self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg')) - - prompt_hbox = gtk.HBox() - - icontheme = gtk.icon_theme_get_default() - icon = icontheme.load_icon('dialog-question', 36, gtk.ICON_LOOKUP_USE_BUILTIN) - if icon: - image = gtk.Image() - image.set_from_pixbuf(icon) - prompt_hbox.pack_start(image, False, False, padding = 6) - - prompt_label = gtk.Label(_('Should all Job Codes be removed?')) - prompt_label.set_line_wrap(True) - prompt_hbox.pack_start(prompt_label, False, False, padding=6) - - self.vbox.pack_start(prompt_hbox, padding=6) - - self.set_border_width(6) - self.set_has_separator(False) - - self.set_default_response(gtk.RESPONSE_OK) - - self.set_transient_for(parent_window) - self.show_all() - - self.connect('response', self.on_response) - - def on_response(self, device_dialog, response): - user_selected = response == gtk.RESPONSE_OK - self.post_choice_callback(self, user_selected) - -class JobCodeDialog(gtk.Dialog): - """ Dialog prompting for a job code""" - - def __init__(self, parent_window, job_codes, default_job_code, post_job_code_entry_callback, entry_only): - # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode - gtk.Dialog.__init__(self, _('Enter a Job Code'), None, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK)) - - self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg')) - self.post_job_code_entry_callback = post_job_code_entry_callback - - self.combobox = gtk.combo_box_entry_new_text() - for text in job_codes: - self.combobox.append_text(text) - - self.job_code_hbox = gtk.HBox(homogeneous = False) - - if len(job_codes) and not entry_only: - # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode - task_label = gtk.Label(_('Enter a new Job Code, or select a previous one')) - else: - # Translators: for an explanation of what this means, see http://damonlynch.net/rapid/documentation/index.html#jobcode - task_label = gtk.Label(_('Enter a new Job Code')) - task_label.set_line_wrap(True) - task_hbox = gtk.HBox() - task_hbox.pack_start(task_label, False, False, padding=6) - - label = gtk.Label(_('Job Code:')) - self.job_code_hbox.pack_start(label, False, False, padding=6) - self.job_code_hbox.pack_start(self.combobox, True, True, padding=6) - - self.set_border_width(6) - self.set_has_separator(False) - - # make entry box have entry completion - self.entry = self.combobox.child - - completion = gtk.EntryCompletion() - completion.set_match_func(self.match_func) - completion.connect("match-selected", - self.on_completion_match) - completion.set_model(self.combobox.get_model()) - completion.set_text_column(0) - self.entry.set_completion(completion) - - # when user hits enter, close the dialog window - self.set_default_response(gtk.RESPONSE_OK) - self.entry.set_activates_default(True) - - if default_job_code: - self.entry.set_text(default_job_code) - - self.vbox.pack_start(task_hbox, False, False, padding = 6) - self.vbox.pack_start(self.job_code_hbox, False, False, padding=12) - - self.set_transient_for(parent_window) - self.show_all() - self.connect('response', self.on_job_code_resp) - - def match_func(self, completion, key, iter): - model = completion.get_model() - return model[iter][0].lower().startswith(self.entry.get_text().lower()) - - def on_completion_match(self, completion, model, iter): - self.entry.set_text(model[iter][0]) - self.entry.set_position(-1) - - def get_job_code(self): - return self.combobox.child.get_text() - - def on_job_code_resp(self, jc_dialog, response): - user_chose_code = False - if response == gtk.RESPONSE_OK: - user_chose_code = True - logger.debug("Job Code entered") - else: - logger.debug("Job Code not entered") - self.post_job_code_entry_callback(self, user_chose_code, self.get_job_code()) - -class IgnorePathDialog(gtk.Dialog): - """ Dialog prompting for a path to ignore when scanning devices""" - - def __init__(self, parent_window, post_entry_callback): - gtk.Dialog.__init__(self, _('Enter a Path to Ignore'), None, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, - gtk.STOCK_OK, gtk.RESPONSE_OK)) - - self.set_icon_from_file(paths.share_dir('glade3/rapid-photo-downloader.svg')) - self.post_entry_callback = post_entry_callback - - self.path_entry = gtk.Entry(max=0) - - self.ignored_path_hbox = gtk.HBox(homogeneous = False) - - task_label = gtk.Label(_('Specify a path that will never be scanned for photos or videos')) - task_label.set_line_wrap(True) - task_hbox = gtk.HBox() - task_hbox.pack_start(task_label, False, False, padding=6) - - label = gtk.Label(_('Path:')) - self.ignored_path_hbox.pack_start(label, False, False, padding=6) - self.ignored_path_hbox.pack_start(self.path_entry, True, True, padding=6) - - self.set_border_width(6) - self.set_has_separator(False) - - # when user hits enter, close the dialog window - self.set_default_response(gtk.RESPONSE_OK) - self.path_entry.set_activates_default(True) - - self.vbox.pack_start(task_hbox, False, False, padding = 6) - self.vbox.pack_start(self.ignored_path_hbox, False, False, padding=12) - - self.set_transient_for(parent_window) - self.show_all() - self.connect('response', self.on_ignored_path_resp) - - def on_ignored_path_resp(self, ignored_path_dialog, response): - user_chose_path = False - if response == gtk.RESPONSE_OK: - user_chose_path = True - logger.debug("Ignored Path entered") - else: - logger.debug("Ignored Path not entered") - self.post_entry_callback(self, user_chose_path, self.path_entry.get_text()) - - -class PreferencesDialog(): - """ - Dialog window to show Rapid Photo Downloader preferences. - - Is tightly integrated into main Rapid Photo Downloader window, i.e. - directly access members in class RapidApp. - """ - - def __init__(self, rapidapp): - - self.builder = gtk.Builder() - self.builder.set_translation_domain(config.APP_NAME) - self.builder.add_from_file(paths.share_dir("glade3/prefs.ui")) - self.builder.connect_signals(self) - - self.dialog = self.preferencesdialog - self.widget = self.dialog - self.dialog.set_transient_for(rapidapp.rapidapp) - self.prefs = rapidapp.prefs - - rapidapp.preferences_dialog_displayed = True - - self.pref_dialog_startup = True - - self.rapidapp = rapidapp - - self._setup_tab_selector() - - self._setup_control_spacing() - - self.file_types = metadatavideo.file_types_to_download() - - self._setup_sample_names() - - # setup tabs - self._setup_photo_download_folder_tab() - self._setup_image_rename_tab() - self._setup_video_download_folder_tab() - self._setup_video_rename_tab() - self._setup_rename_options_tab() - self._setup_job_code_tab() - self._setup_device_tab() - self._setup_device_options_tab() - self._setup_backup_tab() - self._setup_miscellaneous_tab() - self._setup_error_tab() - - if not metadatavideo.DOWNLOAD_VIDEO: - self.disable_video_controls() - - self.dialog.realize() - - #set the width of the left column for selecting values - #note: this must be called after self.dialog.realize(), or else the width calculation will fail - width_of_widest_sel_row = self.treeview.get_background_area(1, self.treeview_column)[2] - self.scrolled_window.set_size_request(width_of_widest_sel_row + 2, -1) - - #set the minimum width of the scolled window holding the photo rename table - if self.rename_scrolledwindow.get_vscrollbar(): - extra = self.rename_scrolledwindow.get_vscrollbar().allocation.width + 10 - else: - extra = 10 - self.rename_scrolledwindow.set_size_request(self.rename_table.allocation.width + extra, -1) - - self.dialog.show() - - self.pref_dialog_startup = False - - def __getattr__(self, key): - """Allow builder widgets to be accessed as self.widgetname - """ - widget = self.builder.get_object(key) - if widget: # cache lookups - setattr(self, key, widget) - return widget - raise AttributeError(key) - - def on_preferencesdialog_destroy(self, widget): - """ Delete variables from memory that cause a file descriptor to be created on a mounted media""" - logger.debug("Preference window closing") - - def _setup_tab_selector(self): - self.notebook.set_show_tabs(0) - self.model = gtk.ListStore(type("")) - column = gtk.TreeViewColumn() - rentext = gtk.CellRendererText() - column.pack_start(rentext, expand=0) - column.set_attributes(rentext, text=0) - self.treeview_column = column - self.treeview.append_column(column) - self.treeview.props.model = self.model - for c in self.notebook.get_children(): - label = self.notebook.get_tab_label(c).get_text() - if not label.startswith("_"): - self.model.append( (label,) ) - - - # select the first value in the list store - self.treeview.set_cursor(0,column) - - def on_download_folder_filechooser_button_selection_changed(self, widget): - path = misc.get_folder_selection(widget) - if path: - self.prefs.download_folder = path - self.update_photo_download_folder_example() - - def on_video_download_folder_filechooser_button_selection_changed(self, widget): - path = misc.get_folder_selection(widget) - if path: - self.prefs.video_download_folder = path - self.update_video_download_folder_example() - - def on_backup_folder_filechooser_button_selection_changed(self, widget): - path = misc.get_folder_selection(widget) - if path: - self.prefs.backup_location = path - self.update_backup_example() - - def on_backup_video_folder_filechooser_button_selection_changed(self, widget): - path = misc.get_folder_selection(widget) - if path: - self.prefs.backup_video_location = path - self.update_backup_example() - - def on_device_location_filechooser_button_selection_changed(self, widget): - path = misc.get_folder_selection(widget) - if path: - self.prefs.device_location = path - - def on_add_ignored_path_button_clicked(self, widget): - i = IgnorePathDialog(parent_window = self.dialog, - post_entry_callback = self.add_ignored_path) - - def add_ignored_path(self, dialog, user_chose_path, path): - dialog.destroy() - if user_chose_path: - if path and path not in self.prefs.ignored_paths: - self.ignored_paths_liststore.prepend((path, )) - self.update_ignored_paths() - selection = self.ignored_paths_treeview.get_selection() - selection.unselect_all() - selection.select_path((0, )) - #scroll to the top - adjustment = self.ignored_paths_scrolledwindow.get_vadjustment() - adjustment.set_value(adjustment.lower) - - def on_ignored_paths_use_re_checkbutton_toggled(self, checkbutton): - self.prefs.use_re_ignored_paths = checkbutton.get_active() - if self.prefs.use_re_ignored_paths and not self.pref_dialog_startup: - # check for invalid regular expressions - self.update_ignored_paths() - - def on_remove_ignored_path_button_clicked(self, button): - self._remove_from_treeview(self.ignored_paths_treeview) - self.update_ignored_paths() - - def on_remove_all_ignored_paths_button_clicked(self, button): - i = RemoveAllIgnoredPathsDialog(self.dialog, self.remove_all_ignored_paths) - - def remove_all_ignored_paths(self, dialog, user_selected): - dialog.destroy() - if user_selected: - self.ignored_paths_liststore.clear() - self.update_ignored_paths() - - def on_remove_remembered_device_button_clicked(self, button): - """ - uses remembered devices treeview to delete any removed items from the - device_whitelist and device_blacklist prefs - """ - blacklist = [i for i in self.prefs.device_blacklist if i] - whitelist = [i for i in self.prefs.device_whitelist if i] - selection = self.remembered_devices_treeview.get_selection() - model, selected = selection.get_selected_rows() - iters = [model.get_iter(path) for path in selected] - # only delete if a value is selected - if iters: - no = len(iters) - path = None - for i in range(0, no): - iter = iters[i] - if i == no - 1: - path = model.get_path(iter) - v = self.remembered_devices_liststore.get_value(iter, 0) - if v in blacklist: - blacklist.remove(v) - elif v in whitelist: - whitelist.remove(v) - else: - logger.debug("Unknown remembered device %s", v) - model.remove(iter) - - # now that we removed the selection, play nice with - # the user and select the next item - selection.select_path(path) - - # if there was no selection that meant the user - # removed the last entry, so we try to select the - # last item - if not selection.path_is_selected(path): - row = path[0]-1 - # test case for empty lists - if row >= 0: - selection.select_path((row,)) - - self.prefs.device_blacklist = blacklist - self.prefs.device_whitelist = whitelist - - def on_remove_all_remembered_device_button_clicked(self, button): - r = RemoveAllRemeberedDevicesDialog(self.dialog, self.remove_all_remembered_devices) - - def remove_all_remembered_devices(self, dialog, user_selected): - dialog.destroy() - if user_selected: - self.remembered_devices_liststore.clear() - self.prefs.device_blacklist = [] - self.prefs.device_whitelist = [] - - def _setup_sample_names(self, use_dummy_data = False): - """ - If use_dummy_data is True, then samples will not attempt to get - data from actual download files - """ - job_code = self.prefs.most_recent_job_code() - if job_code is None: - job_code = _("Job Code") - self.downloads_today_tracker = DownloadsTodayTracker( - day_start = self.prefs.day_start, - downloads_today = self.prefs.downloads_today[1], - downloads_today_date = self.prefs.downloads_today[0]) - self.sequences = gn.Sequences(self.downloads_today_tracker, - self.prefs.stored_sequence_no) - - # get example photo and video data - if use_dummy_data: - self.sample_photo = None - else: - self.sample_photo = self.rapidapp.thumbnails.get_sample_file(rpdfile.FILE_TYPE_PHOTO) - if self.sample_photo is not None: - # try to load metadata from the file returned - # if it fails, give up with this sample file - if not self.sample_photo.load_metadata(): - self.sample_photo = None - else: - self.sample_photo.sequences = self.sequences - self.sample_photo.download_start_time = datetime.datetime.now() - - if self.sample_photo is None: - self.sample_photo = rpdfile.SamplePhoto(sequences=self.sequences) - - self.sample_photo.job_code = job_code - - self.sample_video = None - if metadatavideo.DOWNLOAD_VIDEO: - if not use_dummy_data: - self.sample_video = self.rapidapp.thumbnails.get_sample_file(rpdfile.FILE_TYPE_VIDEO) - if self.sample_video is not None: - self.sample_video.load_metadata() - self.sample_video.sequences = self.sequences - self.sample_video.download_start_time = datetime.datetime.now() - if self.sample_video is None: - self.sample_video = rpdfile.SampleVideo(sequences=self.sequences) - self.sample_video.job_code = job_code - - - - def _setup_control_spacing(self): - """ - set spacing of some but not all controls - """ - - self._setup_table_spacing(self.download_folder_table) - self._setup_table_spacing(self.video_download_folder_table) - self.download_folder_table.set_row_spacing(2, - hd.VERTICAL_CONTROL_SPACE) - self.video_download_folder_table.set_row_spacing(2, - hd.VERTICAL_CONTROL_SPACE) - self._setup_table_spacing(self.rename_example_table) - self._setup_table_spacing(self.video_rename_example_table) - self.devices_table.set_col_spacing(0, hd.NESTED_CONTROLS_SPACE) - self.automation_table.set_col_spacing(0, hd.NESTED_CONTROLS_SPACE) - - self._setup_table_spacing(self.backup_table) - self.backup_table.set_col_spacing(1, hd.NESTED_CONTROLS_SPACE) - self.backup_table.set_col_spacing(2, hd.CONTROL_LABEL_SPACE) - self._setup_table_spacing(self.compatibility_table) - self.compatibility_table.set_row_spacing(0, - hd.VERTICAL_CONTROL_LABEL_SPACE) - self._setup_table_spacing(self.error_table) - - - def _setup_table_spacing(self, table): - table.set_col_spacing(0, hd.NESTED_CONTROLS_SPACE) - table.set_col_spacing(1, hd.CONTROL_LABEL_SPACE) - - def _setup_subfolder_table(self): - self.subfolder_table = SubfolderTable(self, None) - self.subfolder_vbox.pack_start(self.subfolder_table) - self.subfolder_table.show_all() - - def _setup_video_subfolder_table(self): - self.video_subfolder_table = VideoSubfolderTable(self, None) - self.video_subfolder_vbox.pack_start(self.video_subfolder_table) - self.video_subfolder_table.show_all() - - def _setup_photo_download_folder_tab(self): - self.download_folder_filechooser_button = gtk.FileChooserButton( - _("Select a folder to download photos to")) - self.download_folder_filechooser_button.set_current_folder( - self.prefs.download_folder) - self.download_folder_filechooser_button.set_action( - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - self.download_folder_filechooser_button.connect("selection-changed", - self.on_download_folder_filechooser_button_selection_changed) - - self.download_folder_table.attach( - self.download_folder_filechooser_button, - 2, 3, 2, 3, yoptions = gtk.SHRINK) - self.download_folder_filechooser_button.show() - - self._setup_subfolder_table() - self.update_photo_download_folder_example() - - def _setup_video_download_folder_tab(self): - self.video_download_folder_filechooser_button = gtk.FileChooserButton( - _("Select a folder to download videos to")) - self.video_download_folder_filechooser_button.set_current_folder( - self.prefs.video_download_folder) - self.video_download_folder_filechooser_button.set_action( - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - self.video_download_folder_filechooser_button.connect("selection-changed", - self.on_video_download_folder_filechooser_button_selection_changed) - - self.video_download_folder_table.attach( - self.video_download_folder_filechooser_button, - 2, 3, 2, 3, yoptions = gtk.SHRINK) - self.video_download_folder_filechooser_button.show() - self._setup_video_subfolder_table() - self.update_video_download_folder_example() - - def _setup_image_rename_tab(self): - - self.rename_table = PhotoRenameTable(self, self.rename_scrolledwindow) - self.rename_table_vbox.pack_start(self.rename_table) - self.rename_table.show_all() - self._setup_photo_original_name() - self.update_photo_rename_example() - - def _setup_photo_original_name(self): - self.original_name_label.set_markup("<i>%s</i>" % self.sample_photo.display_name) - - def _setup_video_rename_tab(self): - - self.video_rename_table = VideoRenameTable(self, self.video_rename_scrolledwindow) - self.video_rename_table_vbox.pack_start(self.video_rename_table) - self.video_rename_table.show_all() - self._setup_video_original_name() - self.update_video_rename_example() - - def _setup_video_original_name(self): - if self.sample_video is not None: - self.video_original_name_label.set_markup("<i>%s</i>" % self.sample_video.display_name) - else: - self.video_original_name_label.set_markup("") - - def _setup_rename_options_tab(self): - - # sequence numbers - self.downloads_today_entry = ValidatedEntry.ValidatedEntry(ValidatedEntry.bounded(ValidatedEntry.v_int, int, 0)) - self.stored_number_entry = ValidatedEntry.ValidatedEntry(ValidatedEntry.bounded(ValidatedEntry.v_int, int, 1)) - self.downloads_today_entry.connect('changed', self.on_downloads_today_entry_changed) - self.stored_number_entry.connect('changed', self.on_stored_number_entry_changed) - v = self.rapidapp.downloads_today_tracker.get_and_maybe_reset_downloads_today() - self.downloads_today_entry.set_text(str(v)) - # make the displayed value of stored sequence no 1 more than actual value - # so as not to confuse the user - self.stored_number_entry.set_text(str(self.prefs.stored_sequence_no+1)) - self.sequence_vbox.pack_start(self.downloads_today_entry, expand=True, fill=True) - self.sequence_vbox.pack_start(self.stored_number_entry, expand=False) - self.downloads_today_entry.show() - self.stored_number_entry.show() - hour, minute = self.rapidapp.downloads_today_tracker.get_day_start() - self.hour_spinbutton.set_value(float(hour)) - self.minute_spinbutton.set_value(float(minute)) - - self.synchronize_raw_jpg_checkbutton.set_active( - self.prefs.synchronize_raw_jpg) - - #compatibility - self.strip_characters_checkbutton.set_active( - self.prefs.strip_characters) - - def _setup_job_code_tab(self): - self.job_code_liststore = gtk.ListStore(str) - column = gtk.TreeViewColumn() - rentext = gtk.CellRendererText() - rentext.connect('edited', self.on_job_code_edited) - rentext.set_property('editable', True) - - column.pack_start(rentext, expand=0) - column.set_attributes(rentext, text=0) - self.job_code_treeview_column = column - self.job_code_treeview.append_column(column) - self.job_code_treeview.props.model = self.job_code_liststore - for code in self.prefs.job_codes: - self.job_code_liststore.append((code, )) - - # set multiple selections - self.job_code_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - self.remove_all_job_code_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_CLEAR, - gtk.ICON_SIZE_BUTTON)) - def _setup_device_options_tab(self): - """ - Setup ignored paths and remembered devices tab in prefs dialog - """ - - self.ignored_paths_use_re_checkbutton.set_active( - self.prefs.use_re_ignored_paths) - - self.ignored_paths_liststore = gtk.ListStore(str) - column = gtk.TreeViewColumn() - rentext = gtk.CellRendererText() - rentext.connect('edited', self.on_ignored_path_edited) - rentext.set_property('editable', True) - - column.pack_start(rentext, expand=0) - column.set_attributes(rentext, text=0) - self.ignored_paths_treeview_column = column - self.ignored_paths_treeview.append_column(column) - self.ignored_paths_treeview.props.model = self.ignored_paths_liststore - for path in self.prefs.ignored_paths: - self.ignored_paths_liststore.append((path, )) - - self.ignored_paths_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - self.remove_all_ignored_paths_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_CLEAR, - gtk.ICON_SIZE_BUTTON)) - - # Remembered devices are a little different in that they cannot be - # edited, and they can only added when the user is prompted by the - # program. Moreover, the list the user sees is a combination of two - # lists: device_whitelist and device_blacklist - - self.remembered_devices_liststore = gtk.ListStore(str) - column = gtk.TreeViewColumn() - rentext = gtk.CellRendererText() - rentext.set_property('editable', False) - - column.pack_start(rentext, expand=0) - column.set_attributes(rentext, text=0) - self.remembered_devices_treeview_column = column - self.remembered_devices_treeview.append_column(column) - self.remembered_devices_treeview.props.model = self.remembered_devices_liststore - for device in self.prefs.device_whitelist: - if device: - self.remembered_devices_liststore.append((device, )) - for device in self.prefs.device_blacklist: - if device: - self.remembered_devices_liststore.append((device, )) - - self.remembered_devices_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - self.remove_all_remembered_device_button.set_image(gtk.image_new_from_stock( - gtk.STOCK_CLEAR, - gtk.ICON_SIZE_BUTTON)) - - def _setup_device_tab(self): - - self.device_location_filechooser_button = gtk.FileChooserButton( - _("Select a folder containing %(file_types)s") % {'file_types':self.file_types}) - self.device_location_filechooser_button.set_current_folder( - self.prefs.device_location) - self.device_location_filechooser_button.set_action( - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - - self.device_location_filechooser_button.connect("selection-changed", - self.on_device_location_filechooser_button_selection_changed) - - self.devices2_table.attach(self.device_location_filechooser_button, - 1, 2, 1, 2, xoptions = gtk.EXPAND|gtk.FILL, yoptions = gtk.SHRINK) - self.device_location_filechooser_button.show() - self.autodetect_device_checkbutton.set_active( - self.prefs.device_autodetection) - self.autodetect_psd_checkbutton.set_active( - self.prefs.device_autodetection_psd) - - self.update_device_controls() - - - def _setup_backup_tab(self): - """ - Setup and configure backup tab - """ - #Manual backup location for photos file chooser - self.backup_folder_filechooser_button = gtk.FileChooserButton( - _("Select a folder in which to backup photos")) - self.backup_folder_filechooser_button.set_current_folder( - self.prefs.backup_location) - self.backup_folder_filechooser_button.set_action( - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - self.backup_folder_filechooser_button.connect("selection-changed", - self.on_backup_folder_filechooser_button_selection_changed) - self.backup_table.attach(self.backup_folder_filechooser_button, - 3, 4, 8, 9, yoptions = gtk.SHRINK) - self.backup_folder_filechooser_button.show() - - #Manual backup location for videos file chooser - self.backup_video_folder_filechooser_button = gtk.FileChooserButton( - _("Select a folder in which to backup videos")) - self.backup_video_folder_filechooser_button.set_current_folder( - self.prefs.backup_video_location) - self.backup_video_folder_filechooser_button.set_action( - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER) - self.backup_video_folder_filechooser_button.connect("selection-changed", - self.on_backup_video_folder_filechooser_button_selection_changed) - self.backup_table.attach(self.backup_video_folder_filechooser_button, - 3, 4, 9, 10, yoptions = gtk.SHRINK) - self.backup_video_folder_filechooser_button.show() - - self.backup_identifier_entry.set_text(self.prefs.backup_identifier) - self.video_backup_identifier_entry.set_text(self.prefs.video_backup_identifier) - - #setup controls for manipulating sensitivity - self._backup_controls0 = [self.auto_detect_backup_checkbutton] - self._backup_controls1 = [self.backup_identifier_explanation_label, - self.backup_identifier_label, - self.backup_identifier_entry, - self.example_backup_path_label, - self.backup_example_label,] - self._backup_controls2 = [self.backup_location_label, - self.backup_folder_filechooser_button, - self.backup_location_explanation_label] - - if metadatavideo.DOWNLOAD_VIDEO: - self._backup_controls2 += [self.backup_video_folder_filechooser_button, - self.backup_video_location_label] - else: - self.backup_video_folder_filechooser_button.set_sensitive(False) - self.backup_video_location_label.set_sensitive(False) - - self._backup_controls = self._backup_controls0 + self._backup_controls1 + \ - self._backup_controls2 - - self._backup_video_controls = [self.video_backup_identifier_label, - self.video_backup_identifier_entry] - - #assign values to checkbuttons only when other controls - #have been setup, because their toggle signal is activated - #when a value is assigned - - self.backup_checkbutton.set_active(self.prefs.backup_images) - self.auto_detect_backup_checkbutton.set_active( - self.prefs.backup_device_autodetection) - self.update_backup_controls() - self.update_backup_example() - - def _setup_miscellaneous_tab(self): - self.auto_startup_checkbutton.set_active( - self.prefs.auto_download_at_startup) - self.auto_insertion_checkbutton.set_active( - self.prefs.auto_download_upon_device_insertion) - self.auto_unmount_checkbutton.set_active( - self.prefs.auto_unmount) - self.auto_exit_checkbutton.set_active( - self.prefs.auto_exit) - self.auto_exit_force_checkbutton.set_active( - self.prefs.auto_exit_force) - self.generate_thumbnails_checkbutton.set_active( - self.prefs.generate_thumbnails) - self.auto_rotate_checkbutton.set_active( - self.prefs.auto_rotate_jpeg) - self.verify_file_checkbutton.set_active( - self.prefs.verify_file) - - self.update_misc_controls() - - - def _setup_error_tab(self): - if self.prefs.download_conflict_resolution == config.SKIP_DOWNLOAD: - self.skip_download_radiobutton.set_active(True) - else: - self.add_identifier_radiobutton.set_active(True) - - if self.prefs.backup_duplicate_overwrite: - self.backup_duplicate_overwrite_radiobutton.set_active(True) - else: - self.backup_duplicate_skip_radiobutton.set_active(True) - - - def update_example_file_name(self, display_table, rename_table, sample_rpd_file, generator, example_label): - if hasattr(self, display_table) and sample_rpd_file is not None: - sample_rpd_file.download_folder = self.prefs.get_download_folder_for_file_type(sample_rpd_file.file_type) - sample_rpd_file.strip_characters = self.prefs.strip_characters - sample_rpd_file.initialize_problem() - name = generator.generate_name(sample_rpd_file) - else: - name = '' - - # since this is markup, escape it - text = "<i>%s</i>" % utilities.escape(name) - - if sample_rpd_file is not None: - if sample_rpd_file.has_problem(): - text += "\n" - # Translators: please do not modify or leave out html formatting tags like <i> and <b>. These are used to format the text the users sees - text += _("<i><b>Warning:</b> There is insufficient metadata to fully generate the name. Please use other renaming options.</i>") - - example_label.set_markup(text) - - def update_photo_rename_example(self): - """ - Displays example image name to the user - """ - generator = gn.PhotoName(self.prefs.image_rename) - self.update_example_file_name('rename_table', self.rename_table, - self.sample_photo, generator, - self.new_name_label) - - - def update_video_rename_example(self): - """ - Displays example video name to the user - """ - if self.sample_video is not None: - generator = gn.VideoName(self.prefs.video_rename) - else: - generator = None - self.update_example_file_name('video_rename_table', - self.video_rename_table, - self.sample_video, generator, - self.video_new_name_label) - - def update_download_folder_example(self, display_table, subfolder_table, - download_folder, sample_rpd_file, - generator, - example_download_path_label, - subfolder_warning_label): - """ - Displays example subfolder name(s) to the user - """ - - if hasattr(self, display_table) and sample_rpd_file is not None: - #~ subfolder_table.update_example_job_code() - sample_rpd_file.strip_characters = self.prefs.strip_characters - sample_rpd_file.initialize_problem() - path = generator.generate_name(sample_rpd_file) - else: - path = '' - - text = os.path.join(download_folder, path) - # since this is markup, escape it - path = utilities.escape(text) - - warning = "" - if sample_rpd_file is not None: - if sample_rpd_file.has_problem(): - warning = _("<i><b>Warning:</b> There is insufficient metadata to fully generate subfolders. Please use other subfolder naming options.</i>" ) - - # Translators: you should not modify or leave out the %s. This is a code used by the programming language python to insert a value that thes user will see - example_download_path_label.set_markup(_("<i>Example: %s</i>") % text) - subfolder_warning_label.set_markup(warning) - - def update_photo_download_folder_example(self): - if hasattr(self, 'subfolder_table'): - generator = gn.PhotoSubfolder(self.prefs.subfolder) - self.update_download_folder_example('subfolder_table', - self.subfolder_table, self.prefs.download_folder, - self.sample_photo, generator, - self.example_photo_download_path_label, - self.photo_subfolder_warning_label) - - def update_video_download_folder_example(self): - if hasattr(self, 'video_subfolder_table'): - if self.sample_video is not None: - generator = gn.VideoSubfolder(self.prefs.video_subfolder) - else: - generator = None - self.update_download_folder_example('video_subfolder_table', - self.video_subfolder_table, - self.prefs.video_download_folder, - self.sample_video, generator, - self.example_video_download_path_label, - self.video_subfolder_warning_label) - - def on_hour_spinbutton_value_changed(self, spinbutton): - hour = spinbutton.get_value_as_int() - minute = self.minute_spinbutton.get_value_as_int() - self.rapidapp.downloads_today_tracker.set_day_start(hour, minute) - self.on_downloads_today_entry_changed(self.downloads_today_entry) - - def on_minute_spinbutton_value_changed(self, spinbutton): - hour = self.hour_spinbutton.get_value_as_int() - minute = spinbutton.get_value_as_int() - self.rapidapp.downloads_today_tracker.set_day_start(hour, minute) - self.on_downloads_today_entry_changed(self.downloads_today_entry) - - def on_downloads_today_entry_changed(self, entry): - # do not update value if a download is occurring - it will mess it up! - if self.rapidapp.download_is_occurring(): - logger.info("Downloads today value not updated, as a download is currently occurring") - else: - v = entry.get_text() - try: - v = int(v) - except: - v = 0 - if v < 0: - v = 0 - self.rapidapp.downloads_today_tracker.reset_downloads_today(v) - self.rapidapp.refresh_downloads_today = True - self.update_photo_rename_example() - - def on_stored_number_entry_changed(self, entry): - # do not update value if a download is occurring - it will mess it up! - if self.rapidapp.download_is_occurring(): - logger.info("Stored number value not updated, as a download is currently occurring") - else: - v = entry.get_text() - try: - # the displayed value of stored sequence no 1 more than actual value - # so as not to confuse the user - v = int(v) - 1 - except: - v = 0 - if v < 0: - v = 0 - self.prefs.stored_sequence_no = v - self.update_photo_rename_example() - - def _update_subfolder_pref_on_error(self, new_pref_list): - self.prefs.subfolder = new_pref_list - - def _update_video_subfolder_pref_on_error(self, new_pref_list): - self.prefs.video_subfolder = new_pref_list - - - def check_subfolder_values_valid_on_exit(self, users_pref_list, update_pref_function, filetype, default_pref_list): - """ - Checks that the user has not entered in any inappropriate values - - If they have, filters out bad values and warns the user - """ - filtered, pref_list = filter_subfolder_prefs(users_pref_list) - if filtered: - logger.info("The %(filetype)s subfolder preferences had some unnecessary values removed.", {'filetype': filetype}) - if pref_list: - update_pref_function(pref_list) - else: - #Preferences list is now empty - msg = _("The %(filetype)s subfolder preferences entered are invalid and cannot be used.\nThey will be reset to their default values.") % {'filetype': filetype} - sys.stderr.write(msg + "\n") - misc.run_dialog(PROGRAM_NAME, msg) - update_pref_function(self.prefs.get_default(default_pref_list)) - - def on_preferencesdialog_response(self, dialog, arg): - if arg == gtk.RESPONSE_HELP: - webbrowser.open("http://www.damonlynch.net/rapid/documentation") - else: - # arg==gtk.RESPONSE_CLOSE, or the user hit the 'x' to close the window - self.prefs.backup_identifier = self.backup_identifier_entry.get_property("text") - self.prefs.video_backup_identifier = self.video_backup_identifier_entry.get_property("text") - - #check subfolder preferences for bad values - self.check_subfolder_values_valid_on_exit(self.prefs.subfolder, self._update_subfolder_pref_on_error, _("photo"), "subfolder") - self.check_subfolder_values_valid_on_exit(self.prefs.video_subfolder, self._update_video_subfolder_pref_on_error, _("video"), "video_subfolder") - - self.dialog.destroy() - self.rapidapp.preferences_dialog_displayed = False - self.rapidapp.post_preference_change() - - - - - def on_add_job_code_button_clicked(self, button): - j = JobCodeDialog(parent_window = self.dialog, - job_codes = self.prefs.job_codes, - default_job_code = None, - post_job_code_entry_callback=self.add_job_code, - entry_only = True) - - def add_job_code(self, dialog, user_chose_code, job_code): - dialog.destroy() - if user_chose_code: - if job_code and job_code not in self.prefs.job_codes: - self.job_code_liststore.prepend((job_code, )) - self.update_job_codes() - selection = self.job_code_treeview.get_selection() - selection.unselect_all() - selection.select_path((0, )) - #scroll to the top - adjustment = self.job_code_scrolledwindow.get_vadjustment() - adjustment.set_value(adjustment.lower) - - def _remove_from_treeview(self, treeview): - """ - Removes selected items from a treeview, allowing multiple selections - """ - selection = treeview.get_selection() - model, selected = selection.get_selected_rows() - iters = [model.get_iter(path) for path in selected] - # only delete if a value is selected - if iters: - no = len(iters) - path = None - for i in range(0, no): - iter = iters[i] - if i == no - 1: - path = model.get_path(iter) - model.remove(iter) - - # now that we removed the selection, play nice with - # the user and select the next item - selection.select_path(path) - - # if there was no selection that meant the user - # removed the last entry, so we try to select the - # last item - if not selection.path_is_selected(path): - row = path[0]-1 - # test case for empty lists - if row >= 0: - selection.select_path((row,)) - - - def on_remove_job_code_button_clicked(self, button): - """ remove selected job codes (can be multiple selection)""" - - self._remove_from_treeview(self.job_code_treeview) - self.update_job_codes() - self.update_photo_rename_example() - self.update_video_rename_example() - self.update_photo_download_folder_example() - self.update_video_download_folder_example() - - def on_remove_all_job_code_button_clicked(self, button): - j = RemoveAllJobCodeDialog(self.dialog, self.remove_all_job_code) - - def remove_all_job_code(self, dialog, user_selected): - dialog.destroy() - if user_selected: - self.job_code_liststore.clear() - self.update_job_codes() - self.update_photo_rename_example() - self.update_video_rename_example() - self.update_photo_download_folder_example() - self.update_video_download_folder_example() - - def on_job_code_edited(self, widget, path, new_text): - iter = self.job_code_liststore.get_iter(path) - self.job_code_liststore.set_value(iter, 0, new_text) - self.update_job_codes() - self.update_photo_rename_example() - self.update_video_rename_example() - self.update_photo_download_folder_example() - self.update_video_download_folder_example() - - def _update_prefs_list(self, liststore): - replacement_list = [] - for row in liststore: - replacement_list.append(row[0]) - return replacement_list - - def update_job_codes(self): - """ update preferences with list of job codes""" - self.prefs.job_codes = self._update_prefs_list(self.job_code_liststore) - - def on_ignored_path_edited(self, widget, path, new_text): - iter = self.ignored_paths_liststore.get_iter(path) - self.ignored_paths_liststore.set_value(iter, 0, new_text) - self.update_ignored_paths() - - def update_ignored_paths(self): - ignored_paths = self._update_prefs_list(self.ignored_paths_liststore) - - # remove any trailing slashes - ignored_paths = [path.rstrip('/') for path in ignored_paths if path] - # remove any blank values from ignored_paths - ignored_paths = [path for path in ignored_paths if path] - - if self.prefs.use_re_ignored_paths: - ip = [] - bad_paths = '' - for path in ignored_paths: - # check for validity - try: - re.match(path, '') - ip.append(path) - except: - logger.error("Ignoring invalid regular expression: %s", path) - bad_paths += path + '\n' - ignored_paths = ip - if bad_paths: - bad_paths = bad_paths[:-1] - if bad_paths.find('\n') >= 0: - msg = _("The following regular expressions are invalid, and will be removed unless you correct them:\n %s") % bad_paths - else: - msg = _("This regular expression is invalid, and will be removed unless you correct it:\n %s") % bad_paths - misc.run_dialog(_("Invalid regular expression"), msg, self) - - self.prefs.ignored_paths = ignored_paths - - def on_auto_startup_checkbutton_toggled(self, checkbutton): - self.prefs.auto_download_at_startup = checkbutton.get_active() - - def on_auto_insertion_checkbutton_toggled(self, checkbutton): - self.prefs.auto_download_upon_device_insertion = checkbutton.get_active() - - def on_auto_unmount_checkbutton_toggled(self, checkbutton): - self.prefs.auto_unmount = checkbutton.get_active() - - def on_auto_rotate_checkbutton_toggled(self, checkbutton): - self.prefs.auto_rotate_jpeg = checkbutton.get_active() - - def on_verify_file_checkbutton_toggled(self, checkbutton): - self.prefs.verify_file = checkbutton.get_active() - - def on_auto_exit_checkbutton_toggled(self, checkbutton): - active = checkbutton.get_active() - self.prefs.auto_exit = active - if not active: - self.prefs.auto_exit_force = False - self.auto_exit_force_checkbutton.set_active(False) - self.update_misc_controls() - - def on_auto_exit_force_checkbutton_toggled(self, checkbutton): - self.prefs.auto_exit_force = checkbutton.get_active() - - def on_autodetect_device_checkbutton_toggled(self, checkbutton): - self.prefs.device_autodetection = checkbutton.get_active() - self.update_device_controls() - - def on_autodetect_psd_checkbutton_toggled(self, checkbutton): - self.prefs.device_autodetection_psd = checkbutton.get_active() - - def on_generate_thumbnails_checkbutton_toggled(self, checkbutton): - self.prefs.generate_thumbnails = checkbutton.get_active() - - def on_backup_duplicate_overwrite_radiobutton_toggled(self, widget): - self.prefs.backup_duplicate_overwrite = widget.get_active() - - def on_backup_duplicate_skip_radiobutton_toggled(self, widget): - self.prefs.backup_duplicate_overwrite = not widget.get_active() - - def on_treeview_cursor_changed(self, tree): - path, column = tree.get_cursor() - self.notebook.set_current_page(path[0]) - - def on_synchronize_raw_jpg_checkbutton_toggled(self, check_button): - self.prefs.synchronize_raw_jpg = check_button.get_active() - - def on_strip_characters_checkbutton_toggled(self, check_button): - self.prefs.strip_characters = check_button.get_active() - self.update_photo_rename_example() - self.update_photo_download_folder_example() - self.update_video_download_folder_example() - - def on_add_identifier_radiobutton_toggled(self, widget): - if widget.get_active(): - self.prefs.download_conflict_resolution = config.ADD_UNIQUE_IDENTIFIER - else: - self.prefs.download_conflict_resolution = config.SKIP_DOWNLOAD - - - def update_device_controls(self): - """ - Sets sensitivity of image device controls - """ - - controls = [self.device_location_explanation_label, - self.device_location_label, - self.device_location_filechooser_button] - - if self.prefs.device_autodetection: - for c in controls: - c.set_sensitive(False) - self.autodetect_psd_checkbutton.set_sensitive(True) - self.autodetect_image_devices_label.set_sensitive(True) - else: - for c in controls: - c.set_sensitive(True) - self.autodetect_psd_checkbutton.set_sensitive(False) - self.autodetect_image_devices_label.set_sensitive(False) - - if not self.pref_dialog_startup: - logger.debug("Resetting sample file photo and video files") - self._setup_sample_names(use_dummy_data = True) - self._setup_photo_original_name() - self.update_photo_download_folder_example() - self.update_photo_rename_example() - self.update_video_download_folder_example() - self._setup_video_original_name() - self.update_video_rename_example() - - def update_misc_controls(self): - """ - Sets sensitivity of miscillaneous controls - """ - - self.auto_exit_force_checkbutton.set_sensitive(self.prefs.auto_exit) - - - def update_backup_controls(self): - """ - Sets sensitivity of backup related widgets - """ - - if not self.backup_checkbutton.get_active(): - for c in self._backup_controls + self._backup_video_controls: - c.set_sensitive(False) - - else: - for c in self._backup_controls0: - c.set_sensitive(True) - self.update_backup_controls_auto() - - def update_backup_controls_auto(self): - """ - Sets sensitivity of subset of backup related widgets - """ - - if self.auto_detect_backup_checkbutton.get_active(): - for c in self._backup_controls1: - c.set_sensitive(True) - for c in self._backup_controls2: - c.set_sensitive(False) - for c in self._backup_video_controls: - c.set_sensitive(False) - if metadatavideo.DOWNLOAD_VIDEO: - for c in self._backup_video_controls: - c.set_sensitive(True) - else: - for c in self._backup_controls1: - c.set_sensitive(False) - for c in self._backup_controls2: - c.set_sensitive(True) - if metadatavideo.DOWNLOAD_VIDEO: - for c in self._backup_video_controls: - c.set_sensitive(False) - - def disable_video_controls(self): - """ - Disables video preferences if video downloading is disabled - (probably because the appropriate libraries to enable - video metadata extraction are not installed) - """ - controls = [self.example_video_filename_label, - self.original_video_filename_label, - self.new_video_filename_label, - self.video_new_name_label, - self.video_original_name_label, - self.video_rename_scrolledwindow, - self.video_folders_hbox, - self.video_backup_identifier_label, - self.video_backup_identifier_entry - ] - for c in controls: - c.set_sensitive(False) - - self.videos_cannot_be_downloaded_label.show() - self.folder_videos_cannot_be_downloaded_label.show() - self.folder_videos_cannot_be_downloaded_hbox.show() - - def on_auto_detect_backup_checkbutton_toggled(self, widget): - self.prefs.backup_device_autodetection = widget.get_active() - self.update_backup_controls_auto() - - def on_backup_checkbutton_toggled(self, widget): - self.prefs.backup_images = self.backup_checkbutton.get_active() - self.update_backup_controls() - - def on_backup_identifier_entry_changed(self, widget): - self.update_backup_example() - - def on_video_backup_identifier_entry_changed(self, widget): - self.update_backup_example() - - def on_backup_scan_folder_on_entry_changed(self, widget): - self.update_backup_example() - - def update_backup_example(self): - # Translators: this value is used as an example device when automatic backup device detection is enabled. You should translate this. - drive1 = os.path.join(config.MEDIA_LOCATION, _("externaldrive1")) - # Translators: this value is used as an example device when automatic backup device detection is enabled. You should translate this. - drive2 = os.path.join(config.MEDIA_LOCATION, _("externaldrive2")) - - path = os.path.join(drive1, self.backup_identifier_entry.get_text()) - path2 = os.path.join(drive2, self.backup_identifier_entry.get_text()) - path3 = os.path.join(drive2, self.video_backup_identifier_entry.get_text()) - path = utilities.escape(path) - path2 = utilities.escape(path2) - path3 = utilities.escape(path3) - if metadatavideo.DOWNLOAD_VIDEO: - example = "<i>%s</i>\n<i>%s</i>\n<i>%s</i>" % (path, path2, path3) - else: - example = "<i>%s</i>\n<i>%s</i>" % (path, path2) - self.example_backup_path_label.set_markup(example) |