summaryrefslogtreecommitdiff
path: root/rapid/preferencesdialog.py
diff options
context:
space:
mode:
Diffstat (limited to 'rapid/preferencesdialog.py')
-rw-r--r--rapid/preferencesdialog.py1963
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)