diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index e09a54501c..b993b15e37 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -74,6 +74,8 @@ def _config(): c.add_opt('search_as_you_type', default=True, help='Start searching as you type. If this is disabled then search will ' 'only take place when the Enter or Return key is pressed.') + c.add_opt('save_to_disk_template_history', default=[], + help='Previously used Save to Disk templates') return ConfigProxy(c) config = _config() diff --git a/src/calibre/gui2/dialogs/add_save.py b/src/calibre/gui2/dialogs/add_save.py new file mode 100644 index 0000000000..7bd6273b4a --- /dev/null +++ b/src/calibre/gui2/dialogs/add_save.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import textwrap + +from PyQt4.Qt import QTabWidget + +from calibre.gui2.dialogs.add_save_ui import Ui_TabWidget +from calibre.library.save_to_disk import config, FORMAT_ARG_DESCS + + +class AddSave(QTabWidget, Ui_TabWidget): + + def __init__(self, parent=None): + QTabWidget.__init__(self, parent) + self.setupUi(self) + c = config() + opts = c.parse() + for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'): + g = getattr(self, 'opt_'+x) + g.setChecked(getattr(opts, x)) + help = '\n'.join(textwrap.wrap(c.get_option(x).help, 75)) + g.setToolTip(help) + g.setWhatsThis(help) + + for x in ('formats', 'timefmt'): + g = getattr(self, 'opt_'+x) + g.setText(getattr(opts, x)) + help = '\n'.join(textwrap.wrap(c.get_option(x).help, 75)) + g.setToolTip(help) + g.setWhatsThis(help) + + help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75)) + self.opt_template.initialize('save_to_disk_template_history', + opts.template, help=help) + + variables = sorted(FORMAT_ARG_DESCS.keys()) + rows = [] + for var in variables: + rows.append(u'%s%s'% + (var, FORMAT_ARG_DESCS[var])) + table = u'%s
'%(u'\n'.join(rows)) + self.template_variables.setText(table) + + def save_settings(self): + c = config() + for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'): + c.set(x, getattr(self, 'opt_'+x).isChecked()) + for x in ('formats', 'template', 'timefmt'): + c.set(x, unicode(getattr(self, 'opt_'+x).text()).strip()) + + + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + app=QApplication([]) + a = AddSave() + a.show() + app.exec_() + a.save_settings() + diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config/__init__.py similarity index 98% rename from src/calibre/gui2/dialogs/config.py rename to src/calibre/gui2/dialogs/config/__init__.py index 04b5650188..feab592467 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config/__init__.py @@ -10,12 +10,11 @@ from PyQt4.Qt import QDialog, QMessageBox, QListWidgetItem, QIcon, \ QDialogButtonBox, QTabWidget, QBrush, QLineEdit from calibre.constants import islinux, iswindows -from calibre.gui2.dialogs.config_ui import Ui_Dialog +from calibre.gui2.dialogs.config.config_ui import Ui_Dialog from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \ ALL_COLUMNS, NONE, info_dialog, choose_files, \ warning_dialog from calibre.utils.config import prefs -from calibre.gui2.widgets import FilenamePattern from calibre.gui2.library import BooksModel from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.oeb.iterator import is_supported @@ -193,12 +192,12 @@ class CategoryModel(QStringListModel): def __init__(self, *args): QStringListModel.__init__(self, *args) self.setStringList([_('General'), _('Interface'), _('Conversion'), - _('Email\nDelivery'), + _('Email\nDelivery'), _('Add/Save'), _('Advanced'), _('Content\nServer'), _('Plugins')]) self.icons = list(map(QVariant, map(QIcon, [':/images/dialog_information.svg', ':/images/lookfeel.svg', ':/images/convert.svg', - ':/images/mail.svg', ':/images/view.svg', + ':/images/mail.svg', ':/images/save.svg', ':/images/view.svg', ':/images/network-server.svg', ':/images/plugins.svg']))) def data(self, index, role): @@ -373,9 +372,6 @@ class ConfigDialog(QDialog, Ui_Dialog): self.connect(self.column_up, SIGNAL('clicked()'), self.up_column) self.connect(self.column_down, SIGNAL('clicked()'), self.down_column) - self.filename_pattern = FilenamePattern(self) - self.metadata_box.layout().insertWidget(0, self.filename_pattern) - icons = config['toolbar_icon_size'] self.toolbar_button_size.setCurrentIndex(0 if icons == self.ICON_SIZES[0] else 1 if icons == self.ICON_SIZES[1] else 2) self.show_toolbar_text.setChecked(config['show_text_in_toolbar']) @@ -408,7 +404,6 @@ class ConfigDialog(QDialog, Ui_Dialog): for item in items: self.language.addItem(item[1], QVariant(item[0])) - self.pdf_metadata.setChecked(prefs['read_file_metadata']) exts = set([]) for ext in BOOK_EXTENSIONS: @@ -439,7 +434,6 @@ class ConfigDialog(QDialog, Ui_Dialog): self.password.setText(opts.password if opts.password else '') self.auto_launch.setChecked(config['autolaunch_server']) self.systray_icon.setChecked(config['systray_icon']) - self.search_as_you_type.setChecked(config['search_as_you_type']) self.sync_news.setChecked(config['upload_news_to_device']) self.delete_news.setChecked(config['delete_news_from_library_on_upload']) p = {'normal':0, 'high':1, 'low':2}[prefs['worker_process_priority']] @@ -683,6 +677,8 @@ class ConfigDialog(QDialog, Ui_Dialog): return if not self.conversion_options.commit(): return + if not self.add_save.save_settings(): + return config['use_roman_numerals_for_series_number'] = bool(self.roman_numerals.isChecked()) config['new_version_notification'] = bool(self.new_version_notification.isChecked()) prefs['network_timeout'] = int(self.timeout.value()) @@ -697,11 +693,8 @@ class ConfigDialog(QDialog, Ui_Dialog): config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked()) config['separate_cover_flow'] = bool(self.separate_cover_flow.isChecked()) config['disable_tray_notification'] = not self.systray_notifications.isChecked() - pattern = self.filename_pattern.commit() - prefs['filename_pattern'] = pattern p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()] prefs['worker_process_priority'] = p - prefs['read_file_metadata'] = bool(self.pdf_metadata.isChecked()) prefs['output_format'] = unicode(self.output_format.currentText()).upper() config['cover_flow_queue_length'] = self.cover_browse.value() prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString()) diff --git a/src/calibre/gui2/dialogs/config/add_save.py b/src/calibre/gui2/dialogs/config/add_save.py new file mode 100644 index 0000000000..ba4289ddb9 --- /dev/null +++ b/src/calibre/gui2/dialogs/config/add_save.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +import textwrap + +from PyQt4.Qt import QTabWidget + +from calibre.gui2.dialogs.config.add_save_ui import Ui_TabWidget +from calibre.library.save_to_disk import config, FORMAT_ARG_DESCS, \ + preprocess_template +from calibre.gui2 import error_dialog +from calibre.utils.config import prefs +from calibre.gui2.widgets import FilenamePattern + +class AddSave(QTabWidget, Ui_TabWidget): + + def __init__(self, parent=None): + QTabWidget.__init__(self, parent) + self.setupUi(self) + while self.count() > 2: + self.removeTab(2) + c = config() + opts = c.parse() + for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'): + g = getattr(self, 'opt_'+x) + g.setChecked(getattr(opts, x)) + help = '\n'.join(textwrap.wrap(c.get_option(x).help, 75)) + g.setToolTip(help) + g.setWhatsThis(help) + + for x in ('formats', 'timefmt'): + g = getattr(self, 'opt_'+x) + g.setText(getattr(opts, x)) + help = '\n'.join(textwrap.wrap(c.get_option(x).help, 75)) + g.setToolTip(help) + g.setWhatsThis(help) + + help = '\n'.join(textwrap.wrap(c.get_option('template').help, 75)) + self.opt_template.initialize('save_to_disk_template_history', + opts.template, help) + + variables = sorted(FORMAT_ARG_DESCS.keys()) + rows = [] + for var in variables: + rows.append(u'%s%s'% + (var, FORMAT_ARG_DESCS[var])) + table = u'%s
'%(u'\n'.join(rows)) + self.template_variables.setText(table) + + self.opt_read_metadata_from_filename.setChecked(prefs['read_file_metadata']) + self.metadata_box.setEnabled(self.opt_read_metadata_from_filename.isChecked()) + self.filename_pattern = FilenamePattern(self) + self.metadata_box.layout().insertWidget(0, self.filename_pattern) + + + + def validate(self): + tmpl = preprocess_template(self.opt_template.text()) + fa = {} + for x in FORMAT_ARG_DESCS.keys(): + fa[x]='' + try: + tmpl.format(**fa) + except Exception, err: + error_dialog(self, _('Invalid template'), + '

'+_('The template %s is invalid:')%tmpl + \ + '
'+str(err), show=True) + return False + return True + + def save_settings(self): + if not self.validate(): + return False + c = config() + for x in ('asciiize', 'update_metadata', 'save_cover', 'write_opf'): + c.set(x, getattr(self, 'opt_'+x).isChecked()) + for x in ('formats', 'template', 'timefmt'): + c.set(x, unicode(getattr(self, 'opt_'+x).text()).strip()) + self.opt_template.save_history('save_to_disk_template_history') + prefs['read_file_metadata'] = bool(self.opt_read_metadata_from_filename.isChecked()) + pattern = self.filename_pattern.commit() + prefs['filename_pattern'] = pattern + + return True + + + +if __name__ == '__main__': + from PyQt4.Qt import QApplication + app=QApplication([]) + a = AddSave() + a.show() + app.exec_() + a.save_settings() + diff --git a/src/calibre/gui2/dialogs/config/add_save.ui b/src/calibre/gui2/dialogs/config/add_save.ui new file mode 100644 index 0000000000..e61dc102ee --- /dev/null +++ b/src/calibre/gui2/dialogs/config/add_save.ui @@ -0,0 +1,195 @@ + + + TabWidget + + + + 0 + 0 + 645 + 516 + + + + TabWidget + + + 0 + + + + &Adding books + + + + + + Here you can control how calibre will read metadata from the files you add to it. calibre can either read metadata from the contents of the file, or from the filename. + + + true + + + + + + + Read metadata from &file name + + + + + + + &Configure metadata from file name + + + + + + Qt::Vertical + + + + 20 + 363 + + + + + + + + + + + + &Saving books + + + + + + Here you can control how calibre will save your books when you click the Save to Disk button: + + + true + + + + + + + Save &cover separately + + + + + + + Update &metadata in saved copies + + + + + + + Save metadata in &OPF file + + + + + + + Convert non-English characters to &English equivalents + + + + + + + Format &dates as: + + + opt_timefmt + + + + + + + + + + File &formats to save: + + + opt_formats + + + + + + + + + + Save &template + + + + + + By adjusting the template below, you can control what folders the files are saved in and what filenames they are given. You can use the / character to indicate sub-folders. Available metadata variables are described below. If a particular book does not have some metadata, the variable will be replaced by the empty string. + + + true + + + + + + + Available variables: + + + + + + + + + + + + + + + + + + HistoryBox + QComboBox +

calibre/gui2/dialogs/config/history.h
+ + + + + + opt_read_metadata_from_filename + toggled(bool) + metadata_box + setEnabled(bool) + + + 159 + 81 + + + 178 + 122 + + + + + diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config/config.ui similarity index 95% rename from src/calibre/gui2/dialogs/config.ui rename to src/calibre/gui2/dialogs/config/config.ui index be213ba7bc..e38b16cdf5 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config/config.ui @@ -15,7 +15,7 @@ Preferences - + :/images/config.svg:/images/config.svg @@ -115,7 +115,7 @@ ... - + :/images/mimetypes/dir.svg:/images/mimetypes/dir.svg @@ -131,19 +131,6 @@ - - - - If you disable this setting, metadata is guessed from the filename instead. This can be configured in the Advanced section. - - - Read &metadata from files - - - true - - - @@ -258,7 +245,7 @@ ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -282,7 +269,7 @@ ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -339,7 +326,7 @@ ... - + :/images/plus.svg:/images/plus.svg @@ -366,7 +353,7 @@ ... - + :/images/list_remove.svg:/images/list_remove.svg @@ -543,7 +530,7 @@ ... - + :/images/arrow-up.svg:/images/arrow-up.svg @@ -567,7 +554,7 @@ ... - + :/images/arrow-down.svg:/images/arrow-down.svg @@ -627,7 +614,7 @@ &Add email - + :/images/plus.svg:/images/plus.svg @@ -654,7 +641,7 @@ &Remove email - + :/images/minus.svg:/images/minus.svg @@ -687,6 +674,14 @@ + + + + + + + + @@ -729,28 +724,6 @@ - - - - &Metadata from file name - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - @@ -1020,7 +993,7 @@ ... - + :/images/document_open.svg:/images/document_open.svg @@ -1079,9 +1052,15 @@
calibre/gui2/wizard/send_email.h
1 + + AddSave + QTabWidget +
calibre/gui2/dialogs/config/add_save.h
+ 1 +
- + diff --git a/src/calibre/gui2/dialogs/config/history.py b/src/calibre/gui2/dialogs/config/history.py new file mode 100644 index 0000000000..74b88a4380 --- /dev/null +++ b/src/calibre/gui2/dialogs/config/history.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QComboBox, QStringList, Qt + +from calibre.gui2 import config as gui_conf + +class HistoryBox(QComboBox): + + def __init__(self, parent=None): + QComboBox.__init__(self, parent) + self.setEditable(True) + + def initialize(self, opt_name, default, help=None): + history = gui_conf[opt_name] + if default not in history: + history.append(default) + self.addItems(QStringList(history)) + self.setCurrentIndex(self.findText(default, Qt.MatchFixedString)) + if help is not None: + self.setToolTip(help) + self.setWhatsThis(help) + + def save_history(self, opt_name): + history = [unicode(self.itemText(i)) for i in range(self.count())] + ct = self.text() + if ct not in history: + history = [ct] + history + gui_conf[opt_name] = history[:10] + + def text(self): + return unicode(self.currentText()).strip() + + + diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 149e92ddf9..283de1c2f7 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -1488,8 +1488,9 @@ class LibraryDatabase2(LibraryDatabase): yield record def all_ids(self): + x = FIELD_MAP['id'] for i in iter(self): - yield i['id'] + yield i[x] def get_data_as_dict(self, prefix=None, authors_as_string=False): ''' diff --git a/src/calibre/library/save_to_disk.py b/src/calibre/library/save_to_disk.py index 3df5bc8ab1..a274efe5ee 100644 --- a/src/calibre/library/save_to_disk.py +++ b/src/calibre/library/save_to_disk.py @@ -6,32 +6,37 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import os, traceback, sys, cStringIO +import os, traceback, cStringIO from calibre.utils.config import Config, StringConfig from calibre.utils.filenames import shorten_components_to, supports_long_names, \ ascii_filename, sanitize_file_name from calibre.ebooks.metadata.opf2 import metadata_to_opf from calibre.ebooks.metadata.meta import set_metadata +from calibre.constants import preferred_encoding, filesystem_encoding from calibre import strftime DEFAULT_TEMPLATE = '{author_sort}/{title} - {authors}' -FORMAT_ARGS = dict( - title='', - authors='', - author_sort='', - tags='', - series='', - series_index='', - rating='', - isbn='', - publisher='', - timestamp='', - pubdate='', - id='' +FORMAT_ARG_DESCS = dict( + title=_('The title'), + authors=_('The authors'), + author_sort=_('The author sort string'), + tags=_('The tags'), + series=_('The series'), + series_index=_('The series number'), + rating=_('The rating'), + isbn=_('The ISBN'), + publisher=_('The publisher'), + timestamp=_('The date'), + pubdate=_('The published date'), + id=_('The calibre internal id') ) +FORMAT_ARGS = {} +for x in FORMAT_ARG_DESCS: + FORMAT_ARGS[x] = '' + def config(defaults=None): if defaults is None: @@ -72,6 +77,8 @@ def preprocess_template(template): template = template.replace('//', '/') template = template.replace('{author}', '{authors}') template = template.replace('{tag}', '{tags}') + if not isinstance(template, unicode): + template = template.decode(preferred_encoding, 'replace') return template def get_components(template, mi, id, timefmt='%b %Y', length=250, sanitize_func=ascii_filename): @@ -104,6 +111,8 @@ def get_components(template, mi, id, timefmt='%b %Y', length=250, sanitize_func= components = [sanitize_func(x) for x in components if x] if not components: components = [str(id)] + components = [x.encode(filesystem_encoding, 'replace') if isinstance(x, + unicode) else x for x in components] return shorten_components_to(length, components) @@ -187,7 +196,7 @@ def save_to_disk(db, ids, root, opts=None, callback=None): if opts is None: opts = config().parse() if isinstance(root, unicode): - root = root.encode(sys.getfilesystemencoding()) + root = root.encode(filesystem_encoding) root = os.path.abspath(root) opts.template = preprocess_template(opts.template)