Add an "Add/Save" category to Preferences to control the "Add books" and "Save to disk" options

This commit is contained in:
Kovid Goyal 2009-08-17 21:12:03 -06:00
parent e810b58f40
commit 6973d80602
9 changed files with 460 additions and 75 deletions

View File

@ -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()

View File

@ -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 <kovid@kovidgoyal.net>'
__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'<tr><td>%s</td><td>%s</td></tr>'%
(var, FORMAT_ARG_DESCS[var]))
table = u'<table>%s</table>'%(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()

View File

@ -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())

View File

@ -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 <kovid@kovidgoyal.net>'
__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'<tr><td>%s</td><td>%s</td></tr>'%
(var, FORMAT_ARG_DESCS[var]))
table = u'<table>%s</table>'%(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'),
'<p>'+_('The template %s is invalid:')%tmpl + \
'<br>'+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()

View File

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TabWidget</class>
<widget class="QTabWidget" name="TabWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>645</width>
<height>516</height>
</rect>
</property>
<property name="windowTitle">
<string>TabWidget</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>&amp;Adding books</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="opt_read_metadata_from_filename">
<property name="text">
<string>Read metadata from &amp;file name</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="metadata_box">
<property name="title">
<string>&amp;Configure metadata from file name</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>363</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>&amp;Saving books</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Here you can control how calibre will save your books when you click the Save to Disk button:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<widget class="QCheckBox" name="opt_save_cover">
<property name="text">
<string>Save &amp;cover separately</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="opt_update_metadata">
<property name="text">
<string>Update &amp;metadata in saved copies</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="opt_write_opf">
<property name="text">
<string>Save metadata in &amp;OPF file</string>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QCheckBox" name="opt_asciiize">
<property name="text">
<string>Convert non-English characters to &amp;English equivalents</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Format &amp;dates as:</string>
</property>
<property name="buddy">
<cstring>opt_timefmt</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="opt_timefmt"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>File &amp;formats to save:</string>
</property>
<property name="buddy">
<cstring>opt_formats</cstring>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="opt_formats"/>
</item>
<item row="7" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Save &amp;template</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Available variables:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QTextBrowser" name="template_variables"/>
</item>
<item row="1" column="0">
<widget class="HistoryBox" name="opt_template"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>HistoryBox</class>
<extends>QComboBox</extends>
<header>calibre/gui2/dialogs/config/history.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>opt_read_metadata_from_filename</sender>
<signal>toggled(bool)</signal>
<receiver>metadata_box</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>159</x>
<y>81</y>
</hint>
<hint type="destinationlabel">
<x>178</x>
<y>122</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -15,7 +15,7 @@
<string>Preferences</string>
</property>
<property name="windowIcon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/config.svg</normaloff>:/images/config.svg</iconset>
</property>
<layout class="QGridLayout">
@ -115,7 +115,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/mimetypes/dir.svg</normaloff>:/images/mimetypes/dir.svg</iconset>
</property>
</widget>
@ -131,19 +131,6 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="pdf_metadata">
<property name="toolTip">
<string>If you disable this setting, metadata is guessed from the filename instead. This can be configured in the Advanced section.</string>
</property>
<property name="text">
<string>Read &amp;metadata from files</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
@ -258,7 +245,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property>
</widget>
@ -282,7 +269,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property>
</widget>
@ -339,7 +326,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
</property>
</widget>
@ -366,7 +353,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/list_remove.svg</normaloff>:/images/list_remove.svg</iconset>
</property>
</widget>
@ -543,7 +530,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property>
</widget>
@ -567,7 +554,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property>
</widget>
@ -627,7 +614,7 @@
<string>&amp;Add email</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/plus.svg</normaloff>:/images/plus.svg</iconset>
</property>
<property name="iconSize">
@ -654,7 +641,7 @@
<string>&amp;Remove email</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/minus.svg</normaloff>:/images/minus.svg</iconset>
</property>
<property name="iconSize">
@ -687,6 +674,14 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_7">
<layout class="QVBoxLayout" name="verticalLayout_9">
<item>
<widget class="AddSave" name="add_save">
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_2">
<layout class="QVBoxLayout">
<item>
@ -729,28 +724,6 @@
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="metadata_box">
<property name="title">
<string>&amp;Metadata from file name</string>
</property>
<layout class="QVBoxLayout">
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4">
@ -1020,7 +993,7 @@
<string>...</string>
</property>
<property name="icon">
<iconset resource="../images.qrc">
<iconset resource="../../images.qrc">
<normaloff>:/images/document_open.svg</normaloff>:/images/document_open.svg</iconset>
</property>
</widget>
@ -1079,9 +1052,15 @@
<header>calibre/gui2/wizard/send_email.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>AddSave</class>
<extends>QTabWidget</extends>
<header>calibre/gui2/dialogs/config/add_save.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources>
<include location="../images.qrc"/>
<include location="../../images.qrc"/>
</resources>
<connections>
<connection>

View File

@ -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 <kovid@kovidgoyal.net>'
__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()

View File

@ -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):
'''

View File

@ -6,32 +6,37 @@ __license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__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)