Merge from main branch

This commit is contained in:
Tom Scholl 2011-04-08 23:00:32 +00:00
commit 2c054f7505
98 changed files with 40024 additions and 34704 deletions

View File

@ -19,6 +19,65 @@
# new recipes: # new recipes:
# - title: # - title:
- version: 0.7.54
date: 2011-04-08
new features:
- title: "New output format, HTMLZ which is a single HTML file with its associated images/stylesheets in a zipped up file"
description: "Useful when you want to convert your ebook into a single HTML file for easy editing. Note that this output plugin is still new and needs testing"
- title: "When dealing with ZIP/RAR archives, use the file header rather than the file extension to detrmine the file type, when possible. This fixes the common case of CBZ files being actually cbr files and vice versa"
- title: "Support for the Motorola Atrix"
- title: "Allow the icons in the toolbar to be turned off completely via Preferences->Look & Feel"
- title: "When downloading metadata use the gzip transfer encoding when possible for a speedup."
tickets: [749304]
bug fixes:
- title: "Conversion pipeline: Workaround for bug in lxml that causes a massive mem leak on windows and OS X when the input document contains non ASCII CSS selectors."
tickets: [754555]
- title: "Conversion pipeline: Handle inline <style> tags that put all the actual CSS inside an XML comment."
tickets: [750063]
- title: "The 'Choose Library' button now shows its popup menu when you already have more than one library instead of the dialog to create a new library"
tickets: [754154]
- title: "Apply all content server setting when clicking the Start Server button in Preferences->Sharing over the net"
tickets: [753122]
- title: "Fix content server breaking if its restriction is set to a saved search that was deleted"
tickets: [751950]
- title: "Fix detection of PocketBook with 2.0.6 firmware on windows"
tickets: [750336]
- title: "ODT Input: Fix handling of the <text:s> element."
tickets: [749655]
- title: "MOBI Output: Don't use self closed tags"
- title: "Fix book details popup becoming too tall if there is a lot of metadata"
- title: "Fix new PDF engine crashing on PDF files with embedded fonts with null names"
improved recipes:
- Kommersant
- Perfil
- Times of India
- IHT
- Guardian
new recipes:
- title: "Al Ahram"
authors: Hassan Williamson
- title: "F-Secure and developpez.com"
authors: louhike
- version: 0.7.53 - version: 0.7.53
date: 2011-04-01 date: 2011-04-01

62
recipes/al_ahram.recipe Normal file
View File

@ -0,0 +1,62 @@
# coding=utf-8
__license__ = 'GPL v3'
__copyright__ = '2011, Hassan Williamson <haz at hazrpg.co.uk>'
'''
ahram.org.eg
'''
from calibre.web.feeds.recipes import BasicNewsRecipe
class AlAhram(BasicNewsRecipe):
title = 'Al-Ahram'
__author__ = 'Hassan Williamson'
description = 'News from Egypt in Arabic.'
oldest_article = 7
max_articles_per_feed = 100
no_stylesheets = True
#delay = 1
use_embedded_content = False
encoding = 'utf8'
publisher = 'Al-Ahram'
category = 'News'
language = 'ar'
publication_type = 'newsportal'
extra_css = ' body{ font-family: Verdana,Helvetica,Arial,sans-serif; direction: rtl; } .txtTitle{ font-weight: bold; } '
keep_only_tags = [
dict(name='div', attrs={'class':['bbcolright']})
]
remove_tags = [
dict(name='div', attrs={'class':['bbnav', 'bbsp']}),
dict(name='div', attrs={'id':['AddThisButton']})
]
remove_attributes = [
'width','height'
]
feeds = [
(u'الأولى', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=25'),
(u'مصر', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=27'),
(u'المحافظات', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=29'),
(u'الوطن العربي', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=31'),
(u'العالم', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=26'),
(u'تقارير المراسلين', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=2'),
(u'تحقيقات', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=3'),
(u'قضايا واراء', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=4'),
(u'اقتصاد', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=5'),
(u'رياضة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=6'),
(u'حوادث', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=38'),
(u'دنيا الثقافة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=7'),
(u'المراة والطفل', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=8'),
(u'يوم جديد', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=9'),
(u'الكتاب', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=10'),
(u'الاعمدة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=11'),
(u'أراء حرة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=59'),
(u'ملفات الاهرام', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=12'),
(u'بريد الاهرام', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=15'),
(u'الاخيرة', 'http://www.ahram.org.eg/RssXml.aspx?CategoryID=16'),
]

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.7.53' __version__ = '0.7.54'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re, importlib import re, importlib

View File

@ -97,6 +97,10 @@ class CSSSelector(etree.XPath):
def __init__(self, css, namespaces=XPNSMAP): def __init__(self, css, namespaces=XPNSMAP):
css = self.MIN_SPACE_RE.sub(r'\1', css) css = self.MIN_SPACE_RE.sub(r'\1', css)
if isinstance(css, unicode):
# Workaround for bug in lxml on windows/OS X that causes a massive
# memory leak with non ASCII selectors
css = css.encode('ascii', 'ignore').decode('ascii')
try: try:
path = css_to_xpath(css) path = css_to_xpath(css)
except UnicodeEncodeError: # Bug in css_to_xpath except UnicodeEncodeError: # Bug in css_to_xpath

View File

@ -23,6 +23,10 @@ from calibre.utils.date import UNDEFINED_DATE
# Setup gprefs {{{ # Setup gprefs {{{
gprefs = JSONConfig('gui') gprefs = JSONConfig('gui')
gprefs.defaults['action-layout-menubar'] = ()
gprefs.defaults['action-layout-menubar-device'] = ()
gprefs.defaults['action-layout-toolbar'] = ( gprefs.defaults['action-layout-toolbar'] = (
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None, 'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None,
'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk', 'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk',

View File

@ -75,7 +75,7 @@ class InterfaceAction(QObject):
dont_remove_from = frozenset([]) dont_remove_from = frozenset([])
all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu', all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu',
'context-menu-device', 'toolbar-child']) 'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device'])
#: Type of action #: Type of action
#: 'current' means acts on the current view #: 'current' means acts on the current view
@ -145,11 +145,10 @@ class InterfaceAction(QObject):
ans[candidate] = zf.read(candidate) ans[candidate] = zf.read(candidate)
return ans return ans
def genesis(self): def genesis(self):
''' '''
Setup this plugin. Only called once during initialization. self.gui is Setup this plugin. Only called once during initialization. self.gui is
available. The action secified by :attr:`action_spec` is available as available. The action specified by :attr:`action_spec` is available as
``self.qaction``. ``self.qaction``.
''' '''
pass pass

View File

@ -12,7 +12,7 @@ class AddToLibraryAction(InterfaceAction):
name = 'Add To Library' name = 'Add To Library'
action_spec = (_('Add books to library'), 'add_book.png', action_spec = (_('Add books to library'), 'add_book.png',
_('Add books to your calibre library from the connected device'), None) _('Add books to your calibre library from the connected device'), None)
dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -18,6 +18,7 @@ class FetchAnnotationsAction(InterfaceAction):
name = 'Fetch Annotations' name = 'Fetch Annotations'
action_spec = (_('Fetch annotations (experimental)'), None, None, None) action_spec = (_('Fetch annotations (experimental)'), None, None, None)
dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -18,7 +18,7 @@ class GenerateCatalogAction(InterfaceAction):
name = 'Generate Catalog' name = 'Generate Catalog'
action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None) action_spec = (_('Create a catalog of the books in your calibre library'), None, None, None)
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
def generate_catalog(self): def generate_catalog(self):
rows = self.gui.library_view.selectionModel().selectedRows() rows = self.gui.library_view.selectionModel().selectedRows()
@ -31,10 +31,10 @@ class GenerateCatalogAction(InterfaceAction):
_('No books selected for catalog generation'), _('No books selected for catalog generation'),
show=True) show=True)
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
dbspec = {} dbspec = {}
for id in ids: for id in ids:
dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)} dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)}
# Calling gui2.tools:generate_catalog() # Calling gui2.tools:generate_catalog()
ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager, ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager,

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, shutil import os, shutil
from functools import partial from functools import partial
from PyQt4.Qt import QMenu, Qt, QInputDialog from PyQt4.Qt import QMenu, Qt, QInputDialog, QToolButton
from calibre import isbytestring from calibre import isbytestring
from calibre.constants import filesystem_encoding from calibre.constants import filesystem_encoding
@ -80,7 +80,7 @@ class ChooseLibraryAction(InterfaceAction):
name = 'Choose Library' name = 'Choose Library'
action_spec = (_('%d books'), 'lt.png', action_spec = (_('%d books'), 'lt.png',
_('Choose calibre library to work with'), None) _('Choose calibre library to work with'), None)
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
def genesis(self): def genesis(self):
self.count_changed(0) self.count_changed(0)
@ -88,6 +88,9 @@ class ChooseLibraryAction(InterfaceAction):
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self.stats = LibraryUsageStats() self.stats = LibraryUsageStats()
self.popup_type = (QToolButton.InstantPopup if len(self.stats.stats) > 1 else
QToolButton.MenuButtonPopup)
self.create_action(spec=(_('Switch/create library...'), 'lt.png', None, self.create_action(spec=(_('Switch/create library...'), 'lt.png', None,
None), attr='action_choose') None), attr='action_choose')
self.action_choose.triggered.connect(self.choose_library, self.action_choose.triggered.connect(self.choose_library,
@ -123,6 +126,7 @@ class ChooseLibraryAction(InterfaceAction):
type=Qt.QueuedConnection) type=Qt.QueuedConnection)
self.choose_menu.addAction(ac) self.choose_menu.addAction(ac)
self.rename_separator = self.choose_menu.addSeparator() self.rename_separator = self.choose_menu.addSeparator()
self.maintenance_menu = QMenu(_('Library Maintenance')) self.maintenance_menu = QMenu(_('Library Maintenance'))
@ -172,6 +176,7 @@ class ChooseLibraryAction(InterfaceAction):
return return
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
locations = list(self.stats.locations(db)) locations = list(self.stats.locations(db))
for ac in self.switch_actions: for ac in self.switch_actions:
ac.setVisible(False) ac.setVisible(False)
self.quick_menu.clear() self.quick_menu.clear()
@ -205,7 +210,6 @@ class ChooseLibraryAction(InterfaceAction):
rename_actions, delete_actions, qs_actions, rename_actions, delete_actions, qs_actions,
self.action_choose) self.action_choose)
def location_selected(self, loc): def location_selected(self, loc):
enabled = loc == 'library' enabled = loc == 'library'
self.qaction.setEnabled(enabled) self.qaction.setEnabled(enabled)

View File

@ -20,7 +20,7 @@ class ConvertAction(InterfaceAction):
name = 'Convert Books' name = 'Convert Books'
action_spec = (_('Convert books'), 'convert.png', None, _('C')) action_spec = (_('Convert books'), 'convert.png', None, _('C'))
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -24,7 +24,7 @@ class ShareConnMenu(QMenu): # {{{
config_email = pyqtSignal() config_email = pyqtSignal()
toggle_server = pyqtSignal() toggle_server = pyqtSignal()
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
def __init__(self, parent=None): def __init__(self, parent=None):
QMenu.__init__(self, parent) QMenu.__init__(self, parent)
@ -121,8 +121,7 @@ class SendToDeviceAction(InterfaceAction):
name = 'Send To Device' name = 'Send To Device'
action_spec = (_('Send to device'), 'sync.png', None, _('D')) action_spec = (_('Send to device'), 'sync.png', None, _('D'))
dont_remove_from = frozenset(['toolbar-device']) dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child'])
def genesis(self): def genesis(self):
self.qaction.triggered.connect(self.do_sync) self.qaction.triggered.connect(self.do_sync)
@ -169,7 +168,7 @@ class ConnectShareAction(InterfaceAction):
def toggle_content_server(self): def toggle_content_server(self):
if self.gui.content_server is None: if self.gui.content_server is None:
self.gui.start_content_server() self.gui.start_content_server()
else: else:
self.gui.content_server.threaded_exit() self.gui.content_server.threaded_exit()
self.stopping_msg = info_dialog(self.gui, _('Stopping'), self.stopping_msg = info_dialog(self.gui, _('Stopping'),

View File

@ -12,7 +12,7 @@ class EditCollectionsAction(InterfaceAction):
name = 'Edit Collections' name = 'Edit Collections'
action_spec = (_('Manage collections'), None, action_spec = (_('Manage collections'), None,
_('Manage the collections on this device'), None) _('Manage the collections on this device'), None)
dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child']) dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -141,15 +141,18 @@ class EditMetadataAction(InterfaceAction):
list(range(self.gui.library_view.model().rowCount(QModelIndex()))) list(range(self.gui.library_view.model().rowCount(QModelIndex())))
current_row = row_list.index(cr) current_row = row_list.index(cr)
if test_eight_code: func = (self.do_edit_metadata if test_eight_code else
changed = self.do_edit_metadata(row_list, current_row) self.do_edit_metadata_old)
else: changed, rows_to_refresh = func(row_list, current_row)
changed = self.do_edit_metadata_old(row_list, current_row)
m = self.gui.library_view.model()
if rows_to_refresh:
m.refresh_rows(rows_to_refresh)
if changed: if changed:
self.gui.library_view.model().refresh_ids(list(changed)) m.refresh_ids(list(changed))
current = self.gui.library_view.currentIndex() current = self.gui.library_view.currentIndex()
m = self.gui.library_view.model()
if self.gui.cover_flow: if self.gui.cover_flow:
self.gui.cover_flow.dataChanged() self.gui.cover_flow.dataChanged()
m.current_changed(current, previous) m.current_changed(current, previous)
@ -183,6 +186,7 @@ class EditMetadataAction(InterfaceAction):
current_row += d.row_delta current_row += d.row_delta
self.gui.library_view.set_current_row(current_row) self.gui.library_view.set_current_row(current_row)
self.gui.library_view.scroll_to_row(current_row) self.gui.library_view.scroll_to_row(current_row)
return changed, set()
def do_edit_metadata(self, row_list, current_row): def do_edit_metadata(self, row_list, current_row):
from calibre.gui2.metadata.single import edit_metadata from calibre.gui2.metadata.single import edit_metadata
@ -190,7 +194,7 @@ class EditMetadataAction(InterfaceAction):
changed, rows_to_refresh = edit_metadata(db, row_list, current_row, changed, rows_to_refresh = edit_metadata(db, row_list, current_row,
parent=self.gui, view_slot=self.view_format_callback, parent=self.gui, view_slot=self.view_format_callback,
set_current_callback=self.set_current_callback) set_current_callback=self.set_current_callback)
return changed return changed, rows_to_refresh
def set_current_callback(self, id_): def set_current_callback(self, id_):
db = self.gui.library_view.model().db db = self.gui.library_view.model().db

View File

@ -11,7 +11,7 @@ class NextMatchAction(InterfaceAction):
name = 'Move to next highlighted book' name = 'Move to next highlighted book'
action_spec = (_('Move to next match'), 'arrow-down.png', action_spec = (_('Move to next match'), 'arrow-down.png',
_('Move to next highlighted match'), [_('N'), _('F3')]) _('Move to next highlighted match'), [_('N'), _('F3')])
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -13,7 +13,7 @@ class OpenFolderAction(InterfaceAction):
name = 'Open Folder' name = 'Open Folder'
action_spec = (_('Open containing folder'), 'document_open.png', None, action_spec = (_('Open containing folder'), 'document_open.png', None,
_('O')) _('O'))
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -16,7 +16,6 @@ class PreferencesAction(InterfaceAction):
name = 'Preferences' name = 'Preferences'
action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P')) action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P'))
dont_remove_from = frozenset(['toolbar'])
def genesis(self): def genesis(self):
pm = QMenu() pm = QMenu()

View File

@ -15,7 +15,7 @@ class ShowBookDetailsAction(InterfaceAction):
name = 'Show Book Details' name = 'Show Book Details'
action_spec = (_('Show book details'), 'dialog_information.png', None, action_spec = (_('Show book details'), 'dialog_information.png', None,
_('I')) _('I'))
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -15,7 +15,7 @@ class TweakEpubAction(InterfaceAction):
action_spec = (_('Tweak ePub'), 'trim.png', action_spec = (_('Tweak ePub'), 'trim.png',
_('Make small changes to ePub format books'), _('Make small changes to ePub format books'),
_('T')) _('T'))
dont_add_to = frozenset(['toolbar-device', 'context-menu-device']) dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -62,7 +62,7 @@ class Bool(Base):
w = self.widgets[1] w = self.widgets[1]
items = [_('Yes'), _('No'), _('Undefined')] items = [_('Yes'), _('No'), _('Undefined')]
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
if tweaks['bool_custom_columns_are_tristate'] == 'no': if not self.db.prefs.get('bools_are_tristate'):
items = items[:-1] items = items[:-1]
icons = icons[:-1] icons = icons[:-1]
for icon, text in zip(icons, items): for icon, text in zip(icons, items):
@ -70,7 +70,7 @@ class Bool(Base):
def setter(self, val): def setter(self, val):
val = {None: 2, False: 1, True: 0}[val] val = {None: 2, False: 1, True: 0}[val]
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val == 2: if not self.db.prefs.get('bools_are_tristate') and val == 2:
val = 1 val = 1
self.widgets[1].setCurrentIndex(val) self.widgets[1].setCurrentIndex(val)
@ -549,7 +549,7 @@ class BulkBool(BulkBase, Bool):
value = None value = None
for book_id in book_ids: for book_id in book_ids:
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: if not self.db.prefs.get('bools_are_tristate') and val is None:
val = False val = False
if value is not None and value != val: if value is not None and value != val:
return None return None
@ -559,7 +559,7 @@ class BulkBool(BulkBase, Bool):
def setup_ui(self, parent): def setup_ui(self, parent):
self.make_widgets(parent, QComboBox) self.make_widgets(parent, QComboBox)
items = [_('Yes'), _('No')] items = [_('Yes'), _('No')]
if tweaks['bool_custom_columns_are_tristate'] == 'no': if not self.db.prefs.get('bools_are_tristate'):
items.append('') items.append('')
else: else:
items.append(_('Undefined')) items.append(_('Undefined'))
@ -571,7 +571,7 @@ class BulkBool(BulkBase, Bool):
def getter(self): def getter(self):
val = self.main_widget.currentIndex() val = self.main_widget.currentIndex()
if tweaks['bool_custom_columns_are_tristate'] == 'no': if not self.db.prefs.get('bools_are_tristate'):
return {2: False, 1: False, 0: True}[val] return {2: False, 1: False, 0: True}[val]
else: else:
return {2: None, 1: False, 0: True}[val] return {2: None, 1: False, 0: True}[val]
@ -586,13 +586,13 @@ class BulkBool(BulkBase, Bool):
return return
val = self.gui_val val = self.gui_val
val = self.normalize_ui_val(val) val = self.normalize_ui_val(val)
if tweaks['bool_custom_columns_are_tristate'] == 'no' and val is None: if not self.db.prefs.get('bools_are_tristate') and val is None:
val = False val = False
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify) self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
def a_c_checkbox_changed(self): def a_c_checkbox_changed(self):
if not self.ignore_change_signals: if not self.ignore_change_signals:
if tweaks['bool_custom_columns_are_tristate'] == 'no' and \ if not self.db.prefs.get('bools_are_tristate') and \
self.main_widget.currentIndex() == 2: self.main_widget.currentIndex() == 2:
self.a_c_checkbox.setChecked(False) self.a_c_checkbox.setChecked(False)
else: else:

View File

@ -7,12 +7,12 @@ __docformat__ = 'restructuredtext en'
from functools import partial from functools import partial
from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, \ from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize,
pyqtSignal, QToolButton, QMenu, \ pyqtSignal, QToolButton, QMenu, QMenuBar, QAction,
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup)
from calibre.constants import __appname__ from calibre.constants import __appname__, isosx
from calibre.gui2.search_box import SearchBox2, SavedSearchBox from calibre.gui2.search_box import SearchBox2, SavedSearchBox
from calibre.gui2.throbber import ThrobbingButton from calibre.gui2.throbber import ThrobbingButton
from calibre.gui2 import gprefs from calibre.gui2 import gprefs
@ -238,6 +238,80 @@ class Spacer(QWidget): # {{{
self.l.addStretch(10) self.l.addStretch(10)
# }}} # }}}
class MenuAction(QAction):
def __init__(self, clone, parent):
QAction.__init__(self, clone.text(), parent)
self.clone = clone
clone.changed.connect(self.clone_changed)
def clone_changed(self):
self.setText(self.clone.text())
class MenuBar(QMenuBar): # {{{
def __init__(self, location_manager, parent):
QMenuBar.__init__(self, parent)
self.gui = parent
self.setNativeMenuBar(True)
self.location_manager = location_manager
self.location_manager.locations_changed.connect(self.build_bar)
self.added_actions = []
self.donate_action = QAction(_('Donate'), self)
self.donate_menu = QMenu()
self.donate_menu.addAction(self.gui.donate_action)
self.donate_action.setMenu(self.donate_menu)
self.build_bar()
def build_bar(self, changed_action=None):
showing_device = self.location_manager.has_device
actions = '-device' if showing_device else ''
actions = gprefs['action-layout-menubar'+actions]
show_main = len(actions) > 0
self.setVisible(show_main)
for ac in self.added_actions:
m = ac.menu()
if m is not None:
m.setVisible(False)
self.clear()
self.added_actions = []
self.action_map = {}
for what in actions:
if what is None:
continue
elif what == 'Location Manager':
for ac in self.location_manager.available_actions:
ac = self.build_menu(ac)
self.addAction(ac)
self.added_actions.append(ac)
elif what == 'Donate':
self.addAction(self.donate_action)
elif what in self.gui.iactions:
action = self.gui.iactions[what]
ac = self.build_menu(action.qaction)
self.addAction(ac)
self.added_actions.append(ac)
def build_menu(self, action):
m = action.menu()
ac = MenuAction(action, self)
if m is None:
m = QMenu()
m.addAction(action)
ac.setMenu(m)
return ac
# }}}
class ToolBar(QToolBar): # {{{ class ToolBar(QToolBar): # {{{
def __init__(self, donate, location_manager, child_bar, parent): def __init__(self, donate, location_manager, child_bar, parent):
@ -284,6 +358,8 @@ class ToolBar(QToolBar): # {{{
mactions = gprefs['action-layout-toolbar'+mactions] mactions = gprefs['action-layout-toolbar'+mactions]
cactions = gprefs['action-layout-toolbar-child'] cactions = gprefs['action-layout-toolbar-child']
show_main = len(mactions) > 0
self.setVisible(show_main)
show_child = len(cactions) > 0 show_child = len(cactions) > 0
self.child_bar.setVisible(show_child) self.child_bar.setVisible(show_child)
@ -306,11 +382,16 @@ class ToolBar(QToolBar): # {{{
bar.added_actions.append(ac) bar.added_actions.append(ac)
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup) bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
elif what == 'Donate': elif what == 'Donate':
self.d_widget = QWidget() if isosx:
self.d_widget.setLayout(QVBoxLayout()) bar.addAction(self.gui.donate_action)
self.d_widget.layout().addWidget(self.donate_button) ch = self.setup_tool_button(bar, self.gui.donate_action)
bar.addWidget(self.d_widget) ch.setText(_('Donate'))
self.showing_donate = True else:
self.d_widget = QWidget()
self.d_widget.setLayout(QVBoxLayout())
self.d_widget.layout().addWidget(self.donate_button)
bar.addWidget(self.d_widget)
self.showing_donate = True
elif what in self.gui.iactions: elif what in self.gui.iactions:
action = self.gui.iactions[what] action = self.gui.iactions[what]
bar.addAction(action.qaction) bar.addAction(action.qaction)
@ -325,17 +406,20 @@ class ToolBar(QToolBar): # {{{
ch.setAutoRaise(True) ch.setAutoRaise(True)
if ac.menu() is not None and menu_mode is not None: if ac.menu() is not None and menu_mode is not None:
ch.setPopupMode(menu_mode) ch.setPopupMode(menu_mode)
return ch
def resizeEvent(self, ev): def resizeEvent(self, ev):
QToolBar.resizeEvent(self, ev) QToolBar.resizeEvent(self, ev)
style = Qt.ToolButtonTextUnderIcon style = Qt.ToolButtonTextUnderIcon
p = gprefs['toolbar_text'] s = gprefs['toolbar_icon_size']
if p == 'never': if s != 'off':
style = Qt.ToolButtonIconOnly p = gprefs['toolbar_text']
if p == 'never':
style = Qt.ToolButtonIconOnly
if p == 'auto' and self.preferred_width > self.width()+35 and \ if p == 'auto' and self.preferred_width > self.width()+35 and \
not gprefs['action-layout-toolbar-child']: not gprefs['action-layout-toolbar-child']:
style = Qt.ToolButtonIconOnly style = Qt.ToolButtonIconOnly
self.setToolButtonStyle(style) self.setToolButtonStyle(style)
@ -421,6 +505,9 @@ class MainWindowMixin(object): # {{{
self.location_manager, self.child_bar, self) self.location_manager, self.child_bar, self)
self.addToolBar(Qt.TopToolBarArea, self.tool_bar) self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
self.addToolBar(Qt.BottomToolBarArea, self.child_bar) self.addToolBar(Qt.BottomToolBarArea, self.child_bar)
self.menu_bar = MenuBar(self.location_manager, self)
self.setMenuBar(self.menu_bar)
self.setUnifiedTitleAndToolBarOnMac(True)
l = self.centralwidget.layout() l = self.centralwidget.layout()
l.addWidget(self.search_bar) l.addWidget(self.search_bar)

View File

@ -353,7 +353,7 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{
editor = DelegateCB(parent) editor = DelegateCB(parent)
items = [_('Y'), _('N'), ' '] items = [_('Y'), _('N'), ' ']
icons = [I('ok.png'), I('list_remove.png'), I('blank.png')] icons = [I('ok.png'), I('list_remove.png'), I('blank.png')]
if tweaks['bool_custom_columns_are_tristate'] == 'no': if not index.model().db.prefs.get('bools_are_tristate'):
items = items[:-1] items = items[:-1]
icons = icons[:-1] icons = icons[:-1]
for icon, text in zip(icons, items): for icon, text in zip(icons, items):
@ -367,7 +367,7 @@ class CcBoolDelegate(QStyledItemDelegate): # {{{
def setEditorData(self, editor, index): def setEditorData(self, editor, index):
m = index.model() m = index.model()
val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']] val = m.db.data[index.row()][m.custom_columns[m.column_map[index.column()]]['rec_index']]
if tweaks['bool_custom_columns_are_tristate'] == 'no': if not m.db.prefs.get('bools_are_tristate'):
val = 1 if not val else 0 val = 1 if not val else 0
else: else:
val = 2 if val is None else 1 if not val else 0 val = 2 if val is None else 1 if not val else 0

View File

@ -700,7 +700,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.dc_decorator[col] = functools.partial( self.dc_decorator[col] = functools.partial(
bool_type_decorator, idx=idx, bool_type_decorator, idx=idx,
bool_cols_are_tristate= bool_cols_are_tristate=
tweaks['bool_custom_columns_are_tristate'] != 'no') self.db.prefs.get('bools_are_tristate'))
elif datatype in ('int', 'float'): elif datatype in ('int', 'float'):
self.dc[col] = functools.partial(number_type, idx=idx) self.dc[col] = functools.partial(number_type, idx=idx)
elif datatype == 'datetime': elif datatype == 'datetime':
@ -710,7 +710,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.dc_decorator[col] = functools.partial( self.dc_decorator[col] = functools.partial(
bool_type_decorator, idx=idx, bool_type_decorator, idx=idx,
bool_cols_are_tristate= bool_cols_are_tristate=
tweaks['bool_custom_columns_are_tristate'] != 'no') self.db.prefs.get('bools_are_tristate'))
elif datatype == 'rating': elif datatype == 'rating':
self.dc[col] = functools.partial(rating_type, idx=idx) self.dc[col] = functools.partial(rating_type, idx=idx)
elif datatype == 'series': elif datatype == 'series':

View File

@ -521,7 +521,7 @@ class MetadataSingleDialog(MetadataSingleDialogBase): # {{{
# }}} # }}}
class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{ class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
cc_two_column = False cc_two_column = False
one_line_comments_toolbar = True one_line_comments_toolbar = True
@ -654,10 +654,14 @@ class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{
# }}} # }}}
editors = {'default': MetadataSingleDialog, 'alt1': MetadataSingleDialogAlt1}
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None, def edit_metadata(db, row_list, current_row, parent=None, view_slot=None,
set_current_callback=None): set_current_callback=None):
d = MetadataSingleDialog(db, parent) cls = db.prefs.get('edit_metadata_single_layout', '')
if cls not in editors:
cls = 'default'
d = editors[cls](db, parent)
d.start(row_list, current_row, view_slot=view_slot, d.start(row_list, current_row, view_slot=view_slot,
set_current_callback=set_current_callback) set_current_callback=set_current_callback)
return d.changed, d.rows_to_refresh return d.changed, d.rows_to_refresh

View File

@ -8,6 +8,7 @@ __copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from threading import Thread, Event from threading import Thread, Event
from operator import attrgetter
from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt, from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt,
QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox, QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox,
@ -22,7 +23,7 @@ from calibre.ebooks.metadata.sources.identify import identify
from calibre.ebooks.metadata.book.base import Metadata from calibre.ebooks.metadata.book.base import Metadata
from calibre.gui2 import error_dialog, NONE from calibre.gui2 import error_dialog, NONE
from calibre.utils.date import utcnow, fromordinal, format_date from calibre.utils.date import utcnow, fromordinal, format_date
from calibre.library.comments import comments_to_html
class RichTextDelegate(QStyledItemDelegate): # {{{ class RichTextDelegate(QStyledItemDelegate): # {{{
@ -51,7 +52,7 @@ class RichTextDelegate(QStyledItemDelegate): # {{{
painter.restore() painter.restore()
# }}} # }}}
class ResultsModel(QAbstractTableModel): class ResultsModel(QAbstractTableModel): # {{{
COLUMNS = ( COLUMNS = (
'#', _('Title'), _('Published'), _('Has cover'), _('Has summary') '#', _('Title'), _('Published'), _('Has cover'), _('Has summary')
@ -71,15 +72,12 @@ class ResultsModel(QAbstractTableModel):
return len(self.COLUMNS) return len(self.COLUMNS)
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if role != Qt.DisplayRole: if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return NONE
if orientation == Qt.Horizontal:
try: try:
return QVariant(self.COLUMNS[section]) return QVariant(self.COLUMNS[section])
except: except:
return NONE return NONE
else: return NONE
return QVariant(unicode(section+1))
def data_as_text(self, book, col): def data_as_text(self, book, col):
if col == 0: if col == 0:
@ -110,10 +108,33 @@ class ResultsModel(QAbstractTableModel):
return self.yes_icon return self.yes_icon
if col == 4 and book.comments: if col == 4 and book.comments:
return self.yes_icon return self.yes_icon
elif role == Qt.UserRole:
return book
return NONE return NONE
def sort(self, col, order=Qt.AscendingOrder):
key = lambda x: x
if col == 0:
key = attrgetter('gui_rank')
elif col == 1:
key = attrgetter('title')
elif col == 2:
key = attrgetter('authors')
elif col == 3:
key = attrgetter('has_cached_cover_url')
elif key == 4:
key = lambda x: bool(x.comments)
self.results.sort(key=key, reverse=order==Qt.AscendingOrder)
self.reset()
# }}}
class ResultsView(QTableView): # {{{ class ResultsView(QTableView): # {{{
show_details_signal = pyqtSignal(object)
book_selected = pyqtSignal(object)
def __init__(self, parent=None): def __init__(self, parent=None):
QTableView.__init__(self, parent) QTableView.__init__(self, parent)
self.rt_delegate = RichTextDelegate(self) self.rt_delegate = RichTextDelegate(self)
@ -121,6 +142,9 @@ class ResultsView(QTableView): # {{{
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
self.setSelectionBehavior(self.SelectRows) self.setSelectionBehavior(self.SelectRows)
self.setIconSize(QSize(24, 24)) self.setIconSize(QSize(24, 24))
self.clicked.connect(self.show_details)
self.doubleClicked.connect(self.select_index)
self.setSortingEnabled(True)
def show_results(self, results): def show_results(self, results):
self._model = ResultsModel(results, self) self._model = ResultsModel(results, self)
@ -129,6 +153,38 @@ class ResultsView(QTableView): # {{{
self.setItemDelegateForColumn(i, self.rt_delegate) self.setItemDelegateForColumn(i, self.rt_delegate)
self.resizeRowsToContents() self.resizeRowsToContents()
self.resizeColumnsToContents() self.resizeColumnsToContents()
self.setFocus(Qt.OtherFocusReason)
def currentChanged(self, current, previous):
ret = QTableView.currentChanged(self, current, previous)
self.show_details(current)
return ret
def show_details(self, index):
book = self.model().data(index, Qt.UserRole)
parts = [
'<center>',
'<h2>%s</h2>'%book.title,
'<div><i>%s</i></div>'%authors_to_string(book.authors),
]
if not book.is_null('rating'):
parts.append('<div>%s</div>'%('\u2605'*int(book.rating)))
parts.append('</center>')
if book.tags:
parts.append('<div>%s</div><div>\u00a0</div>'%', '.join(book.tags))
if book.comments:
parts.append(comments_to_html(book.comments))
self.show_details_signal.emit(''.join(parts))
def select_index(self, index):
if not index.isValid():
index = self.model().index(0, 0)
book = self.model().data(index, Qt.UserRole)
self.book_selected.emit(book)
def get_result(self):
self.select_index(self.currentIndex())
# }}} # }}}
@ -227,6 +283,8 @@ class IdentifyWorker(Thread): # {{{
class IdentifyWidget(QWidget): # {{{ class IdentifyWidget(QWidget): # {{{
rejected = pyqtSignal() rejected = pyqtSignal()
results_found = pyqtSignal()
book_selected = pyqtSignal(object)
def __init__(self, log, parent=None): def __init__(self, log, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -244,11 +302,15 @@ class IdentifyWidget(QWidget): # {{{
l.addWidget(self.top, 0, 0) l.addWidget(self.top, 0, 0)
self.results_view = ResultsView(self) self.results_view = ResultsView(self)
self.results_view.book_selected.connect(self.book_selected.emit)
self.get_result = self.results_view.get_result
l.addWidget(self.results_view, 1, 0) l.addWidget(self.results_view, 1, 0)
self.comments_view = Comments(self) self.comments_view = Comments(self)
l.addWidget(self.comments_view, 1, 1) l.addWidget(self.comments_view, 1, 1)
self.results_view.show_details_signal.connect(self.comments_view.show_data)
self.query = QLabel('download starting...') self.query = QLabel('download starting...')
f = self.query.font() f = self.query.font()
f.setPointSize(f.pointSize()-2) f.setPointSize(f.pointSize()-2)
@ -324,6 +386,13 @@ class IdentifyWidget(QWidget): # {{{
self.results_view.show_results(self.worker.results) self.results_view.show_results(self.worker.results)
self.comments_view.show_data('''
<div style="margin-bottom:2ex">Found <b>%d</b> results</div>
<div>To see <b>details</b>, click on any result</div>''' %
len(self.worker.results))
self.results_found.emit()
def cancel(self): def cancel(self):
self.abort.set() self.abort.set()
@ -343,23 +412,46 @@ class FullFetch(QDialog): # {{{
self.setLayout(l) self.setLayout(l)
l.addWidget(self.stack) l.addWidget(self.stack)
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel) self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
l.addWidget(self.bb) l.addWidget(self.bb)
self.bb.rejected.connect(self.reject) self.bb.rejected.connect(self.reject)
self.next_button = self.bb.addButton(_('Next'), self.bb.AcceptRole)
self.next_button.setDefault(True)
self.next_button.setEnabled(False)
self.next_button.clicked.connect(self.next_clicked)
self.ok_button = self.bb.button(self.bb.Ok)
self.ok_button.setVisible(False)
self.ok_button.clicked.connect(self.ok_clicked)
self.identify_widget = IdentifyWidget(log, self) self.identify_widget = IdentifyWidget(log, self)
self.identify_widget.rejected.connect(self.reject) self.identify_widget.rejected.connect(self.reject)
self.identify_widget.results_found.connect(self.identify_results_found)
self.identify_widget.book_selected.connect(self.book_selected)
self.stack.addWidget(self.identify_widget) self.stack.addWidget(self.identify_widget)
self.resize(850, 500) self.resize(850, 500)
def book_selected(self, book):
print (book)
self.next_button.setVisible(False)
self.ok_button.setVisible(True)
def accept(self): def accept(self):
# Prevent pressing Enter from closing the dialog # Prevent the usual dialog accept mechanisms from working
pass pass
def reject(self): def reject(self):
self.identify_widget.cancel() self.identify_widget.cancel()
return QDialog.reject(self) return QDialog.reject(self)
def identify_results_found(self):
self.next_button.setEnabled(True)
def next_clicked(self, *args):
self.identify_widget.get_result()
def ok_clicked(self, *args):
pass
def start(self, title=None, authors=None, identifiers={}): def start(self, title=None, authors=None, identifiers={}):
self.identify_widget.start(title=title, authors=authors, self.identify_widget.start(title=title, authors=authors,
identifiers=identifiers) identifiers=identifiers)

View File

@ -13,6 +13,7 @@ from calibre.gui2.preferences import ConfigWidgetBase, test_widget
from calibre.gui2.preferences.columns_ui import Ui_Form from calibre.gui2.preferences.columns_ui import Ui_Form
from calibre.gui2.preferences.create_custom_column import CreateCustomColumn from calibre.gui2.preferences.create_custom_column import CreateCustomColumn
from calibre.gui2 import error_dialog, question_dialog, ALL_COLUMNS from calibre.gui2 import error_dialog, question_dialog, ALL_COLUMNS
from calibre.utils.config import test_eight_code
class ConfigWidget(ConfigWidgetBase, Ui_Form): class ConfigWidget(ConfigWidgetBase, Ui_Form):
@ -33,6 +34,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
signal = getattr(self.opt_columns, 'item'+signal) signal = getattr(self.opt_columns, 'item'+signal)
signal.connect(self.columns_changed) signal.connect(self.columns_changed)
if test_eight_code:
r = self.register
choices = [(_('Default'), 'default'), (_('Compact Metadata'), 'alt1')]
r('edit_metadata_single_layout', db.prefs, choices=choices)
r('bools_are_tristate', db.prefs, restart_required=True)
else:
self.items_in_v_eight.setVisible(False)
def initialize(self): def initialize(self):
ConfigWidgetBase.initialize(self) ConfigWidgetBase.initialize(self)
self.init_columns() self.init_columns()
@ -169,6 +178,10 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
must_restart = True must_restart = True
return must_restart return must_restart
def refresh_gui(self, gui):
gui.library_view.reset()
if __name__ == '__main__': if __name__ == '__main__':
from PyQt4.Qt import QApplication from PyQt4.Qt import QApplication

View File

@ -197,6 +197,67 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="2">
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="items_in_v_eight">
<property name="title">
<string>Related Options</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel">
<property name="text">
<string>Edit metadata layout:</string>
</property>
<property name="buddy">
<cstring>opt_edit_metadata_single_layout</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="opt_edit_metadata_single_layout">
<property name="toolTip">
<string>Choose a different layout for the Edit Metadata dialog. Alternate layouts make it easier to edit custom columns.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel">
<property name="text">
<string>Boolean columns are tristate:</string>
</property>
<property name="buddy">
<cstring>opt_bools_are_tristate</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="opt_bools_are_tristate">
<property name="toolTip">
<string>If checked, boolean columns values can be Yes, No, and Unknown.
If not checked, the values can be Yes and No.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_5">
<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>
</item>
</layout> </layout>
</widget> </widget>
<resources> <resources>

View File

@ -360,6 +360,7 @@ class Preferences(QMainWindow):
self.gui.create_device_menu() self.gui.create_device_menu()
self.gui.set_device_menu_items_state(bool(self.gui.device_connected)) self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
self.gui.tool_bar.build_bar() self.gui.tool_bar.build_bar()
self.gui.menu_bar.build_bar()
self.gui.build_context_menus() self.gui.build_context_menus()
self.gui.tool_bar.apply_settings() self.gui.tool_bar.apply_settings()

View File

@ -34,9 +34,12 @@ class BaseModel(QAbstractListModel):
if name == 'Location Manager': if name == 'Location Manager':
return FakeAction(name, None, return FakeAction(name, None,
_('Switch between library and device views'), _('Switch between library and device views'),
dont_remove_from=set(['toolbar-device'])) dont_add_to=frozenset(['menubar', 'toolbar',
'toolbar-child', 'context-menu',
'context-menu-device']))
if name is None: if name is None:
return FakeAction('--- '+_('Separator')+' ---', None) return FakeAction('--- '+_('Separator')+' ---', None,
dont_add_to=frozenset(['menubar', 'menubar-device']))
try: try:
return gui.iactions[name] return gui.iactions[name]
except: except:
@ -89,7 +92,7 @@ class AllModel(BaseModel):
self._data = self.get_all_actions(current) self._data = self.get_all_actions(current)
def get_all_actions(self, current): def get_all_actions(self, current):
all = list(self.gui.iactions.keys()) + ['Donate'] all = list(self.gui.iactions.keys()) + ['Donate', 'Location Manager']
all = [x for x in all if x not in current] + [None] all = [x for x in all if x not in current] + [None]
all = [self.name_to_action(x, self.gui) for x in all] all = [self.name_to_action(x, self.gui) for x in all]
all = [x for x in all if self.key not in x.dont_add_to] all = [x for x in all if self.key not in x.dont_add_to]
@ -208,12 +211,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
LOCATIONS = [ LOCATIONS = [
('toolbar', _('The main toolbar')), ('toolbar', _('The main toolbar')),
('toolbar-child', _('The optional second toolbar')),
('toolbar-device', _('The main toolbar when a device is connected')), ('toolbar-device', _('The main toolbar when a device is connected')),
('toolbar-child', _('The optional second toolbar')),
('menubar', _('The menubar')),
('menubar-device', _('The menubar when a device is connected')),
('context-menu', _('The context menu for the books in the ' ('context-menu', _('The context menu for the books in the '
'calibre library')), 'calibre library')),
('context-menu-device', _('The context menu for the books on ' ('context-menu-device', _('The context menu for the books on '
'the device')) 'the device')),
] ]
def genesis(self, gui): def genesis(self, gui):
@ -284,6 +289,21 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.changed_signal.emit() self.changed_signal.emit()
def commit(self): def commit(self):
# Ensure preferences are showing in either the toolbar or
# the menubar.
pref_in_toolbar = lm_in_toolbar = False
cm = self.models['toolbar']
for x in cm[1]._data:
if x.name == 'Preferences':
pref_in_toolbar = True
if x.name == 'Location Manager':
lm_in_toolbar = True
if not pref_in_toolbar:
self.models['menubar'][1].add(['Preferences'])
if not lm_in_toolbar:
self.models['menubar-device'][1].add(['Location Manager'])
# Save data.
for am, cm in self.models.values(): for am, cm in self.models.values():
cm.commit() cm.commit()
return False return False

View File

@ -153,6 +153,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
for ac in self.iactions.values(): for ac in self.iactions.values():
ac.do_genesis() ac.do_genesis()
self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self)
MainWindowMixin.__init__(self, db) MainWindowMixin.__init__(self, db)
# Jobs Button {{{ # Jobs Button {{{
@ -186,8 +187,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.system_tray_menu = QMenu(self) self.system_tray_menu = QMenu(self)
self.restore_action = self.system_tray_menu.addAction( self.restore_action = self.system_tray_menu.addAction(
QIcon(I('page.png')), _('&Restore')) QIcon(I('page.png')), _('&Restore'))
self.donate_action = self.system_tray_menu.addAction( self.system_tray_menu.addAction(self.donate_action)
QIcon(I('donate.png')), _('&Donate to support calibre'))
self.donate_button.setDefaultAction(self.donate_action) self.donate_button.setDefaultAction(self.donate_action)
self.donate_button.setStatusTip(self.donate_button.toolTip()) self.donate_button.setStatusTip(self.donate_button.toolTip())
self.eject_action = self.system_tray_menu.addAction( self.eject_action = self.system_tray_menu.addAction(

View File

@ -547,7 +547,7 @@ class ResultCache(SearchQueryParser): # {{{
return matchkind, query return matchkind, query
def get_bool_matches(self, location, query, candidates): def get_bool_matches(self, location, query, candidates):
bools_are_tristate = tweaks['bool_custom_columns_are_tristate'] != 'no' bools_are_tristate = not self.db_prefs.get('bools_are_tristate')
loc = self.field_metadata[location]['rec_index'] loc = self.field_metadata[location]['rec_index']
matches = set() matches = set()
query = icu_lower(query) query = icu_lower(query)
@ -947,7 +947,7 @@ class ResultCache(SearchQueryParser): # {{{
if not fields: if not fields:
fields = [('timestamp', False)] fields = [('timestamp', False)]
keyg = SortKeyGenerator(fields, self.field_metadata, self._data) keyg = SortKeyGenerator(fields, self.field_metadata, self._data, self.db_prefs)
self._map.sort(key=keyg) self._map.sort(key=keyg)
tmap = list(itertools.repeat(False, len(self._data))) tmap = list(itertools.repeat(False, len(self._data)))
@ -970,9 +970,10 @@ class SortKey(object):
class SortKeyGenerator(object): class SortKeyGenerator(object):
def __init__(self, fields, field_metadata, data): def __init__(self, fields, field_metadata, data, db_prefs):
from calibre.utils.icu import sort_key from calibre.utils.icu import sort_key
self.field_metadata = field_metadata self.field_metadata = field_metadata
self.db_prefs = db_prefs
self.orders = [1 if x[1] else -1 for x in fields] self.orders = [1 if x[1] else -1 for x in fields]
self.entries = [(x[0], field_metadata[x[0]]) for x in fields] self.entries = [(x[0], field_metadata[x[0]]) for x in fields]
self.library_order = tweaks['title_series_sorting'] == 'library_order' self.library_order = tweaks['title_series_sorting'] == 'library_order'
@ -1032,7 +1033,7 @@ class SortKeyGenerator(object):
val = self.string_sort_key(val) val = self.string_sort_key(val)
elif dt == 'bool': elif dt == 'bool':
if tweaks['bool_custom_columns_are_tristate'] == 'no': if not self.db_prefs.get('bools_are_tristate'):
val = {True: 1, False: 2, None: 2}.get(val, 2) val = {True: 1, False: 2, None: 2}.get(val, 2)
else: else:
val = {True: 1, False: 2, None: 3}.get(val, 3) val = {True: 1, False: 2, None: 3}.get(val, 3)

View File

@ -40,7 +40,7 @@ from calibre.ebooks import BOOK_EXTENSIONS, check_ebook_format
from calibre.utils.magick.draw import save_cover_data_to from calibre.utils.magick.draw import save_cover_data_to
from calibre.utils.recycle_bin import delete_file, delete_tree from calibre.utils.recycle_bin import delete_file, delete_tree
from calibre.utils.formatter_functions import load_user_template_functions from calibre.utils.formatter_functions import load_user_template_functions
from calibre.utils.config import test_eight_code
copyfile = os.link if hasattr(os, 'link') else shutil.copyfile copyfile = os.link if hasattr(os, 'link') else shutil.copyfile
@ -213,6 +213,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
defs = self.prefs.defaults defs = self.prefs.defaults
defs['gui_restriction'] = defs['cs_restriction'] = '' defs['gui_restriction'] = defs['cs_restriction'] = ''
defs['categories_using_hierarchy'] = [] defs['categories_using_hierarchy'] = []
defs['edit_metadata_single_layout'] = 'default'
defs['bools_are_tristate'] = \
tweaks.get('bool_custom_columns_are_tristate', 'yes') == 'yes'
if self.prefs.get('bools_are_tristate') is None or not test_eight_code:
self.prefs.set('bools_are_tristate', defs['bools_are_tristate'])
# Migrate saved search and user categories to db preference scheme # Migrate saved search and user categories to db preference scheme
def migrate_preference(key, default): def migrate_preference(key, default):

View File

@ -17,8 +17,8 @@ from calibre.utils.magick.draw import save_cover_data_to, Image, \
class CSSortKeyGenerator(SortKeyGenerator): class CSSortKeyGenerator(SortKeyGenerator):
def __init__(self, fields, fm): def __init__(self, fields, fm, db_prefs):
SortKeyGenerator.__init__(self, fields, fm, None) SortKeyGenerator.__init__(self, fields, fm, None, db_prefs)
def __call__(self, record): def __call__(self, record):
return self.itervals(record).next() return self.itervals(record).next()
@ -56,7 +56,8 @@ class ContentServer(object):
field = self.db.data.sanitize_sort_field_name(field) field = self.db.data.sanitize_sort_field_name(field)
if field not in self.db.field_metadata.sortable_field_keys(): if field not in self.db.field_metadata.sortable_field_keys():
raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field) raise cherrypy.HTTPError(400, '%s is not a valid sort field'%field)
keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata) keyg = CSSortKeyGenerator([(field, order)], self.db.field_metadata,
self.db.prefs)
items.sort(key=keyg, reverse=not order) items.sort(key=keyg, reverse=not order)
# }}} # }}}

View File

@ -197,7 +197,7 @@ Once you've located the zip file of your plugin you can then directly update it
zip -R /path/to/plugin/zip/file.zip * zip -R /path/to/plugin/zip/file.zip *
This will automatically update all changed files. It relies on the freely available zip command line tool. This will update all changed files. It relies on the freely available zip command line tool. Note that you should quit calibre before running this command.
More plugin examples More plugin examples
---------------------- ----------------------

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -785,6 +785,9 @@ def write_tweaks(raw):
tweaks = read_tweaks() tweaks = read_tweaks()
test_eight_code = tweaks.get('test_eight_code', False) test_eight_code = tweaks.get('test_eight_code', False)
# test_eight_code notes
# Change documentation of bool columns are tristate to indicate that it can be
# overridden on a per library basis via Preferences->Custom columns
def migrate(): def migrate():
if hasattr(os, 'geteuid') and os.geteuid() == 0: if hasattr(os, 'geteuid') and os.geteuid() == 0: