mirror of
git://github.com/kovidgoyal/calibre.git
synced 2026-05-09 02:43:34 +02:00
Merge from main branch
This commit is contained in:
commit
2c054f7505
98 changed files with 40024 additions and 34704 deletions
|
|
@ -19,6 +19,65 @@
|
|||
# new recipes:
|
||||
# - 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
|
||||
date: 2011-04-01
|
||||
|
||||
|
|
|
|||
62
recipes/al_ahram.recipe
Normal file
62
recipes/al_ahram.recipe
Normal 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'),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = 'calibre'
|
||||
__version__ = '0.7.53'
|
||||
__version__ = '0.7.54'
|
||||
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
import re, importlib
|
||||
|
|
|
|||
|
|
@ -97,6 +97,10 @@ class CSSSelector(etree.XPath):
|
|||
|
||||
def __init__(self, css, namespaces=XPNSMAP):
|
||||
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:
|
||||
path = css_to_xpath(css)
|
||||
except UnicodeEncodeError: # Bug in css_to_xpath
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@
|
|||
# Setup gprefs {{{
|
||||
gprefs = JSONConfig('gui')
|
||||
|
||||
gprefs.defaults['action-layout-menubar'] = ()
|
||||
|
||||
gprefs.defaults['action-layout-menubar-device'] = ()
|
||||
|
||||
gprefs.defaults['action-layout-toolbar'] = (
|
||||
'Add Books', 'Edit Metadata', None, 'Convert Books', 'View', None,
|
||||
'Choose Library', 'Donate', None, 'Fetch News', 'Save To Disk',
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class InterfaceAction(QObject):
|
|||
dont_remove_from = frozenset([])
|
||||
|
||||
all_locations = frozenset(['toolbar', 'toolbar-device', 'context-menu',
|
||||
'context-menu-device', 'toolbar-child'])
|
||||
'context-menu-device', 'toolbar-child', 'menubar', 'menubar-device'])
|
||||
|
||||
#: Type of action
|
||||
#: 'current' means acts on the current view
|
||||
|
|
@ -145,11 +145,10 @@ def load_resources(self, names):
|
|||
ans[candidate] = zf.read(candidate)
|
||||
return ans
|
||||
|
||||
|
||||
def genesis(self):
|
||||
'''
|
||||
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``.
|
||||
'''
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class AddToLibraryAction(InterfaceAction):
|
|||
name = 'Add To Library'
|
||||
action_spec = (_('Add books to library'), 'add_book.png',
|
||||
_('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'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ class FetchAnnotationsAction(InterfaceAction):
|
|||
|
||||
name = 'Fetch Annotations'
|
||||
action_spec = (_('Fetch annotations (experimental)'), None, None, None)
|
||||
dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class GenerateCatalogAction(InterfaceAction):
|
|||
|
||||
name = 'Generate Catalog'
|
||||
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):
|
||||
rows = self.gui.library_view.selectionModel().selectedRows()
|
||||
|
|
@ -31,10 +31,10 @@ def generate_catalog(self):
|
|||
_('No books selected for catalog generation'),
|
||||
show=True)
|
||||
|
||||
db = self.gui.library_view.model().db
|
||||
dbspec = {}
|
||||
for id in ids:
|
||||
dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)}
|
||||
db = self.gui.library_view.model().db
|
||||
dbspec = {}
|
||||
for id in ids:
|
||||
dbspec[id] = {'ondevice': db.ondevice(id, index_is_id=True)}
|
||||
|
||||
# Calling gui2.tools:generate_catalog()
|
||||
ret = generate_catalog(self.gui, dbspec, ids, self.gui.device_manager,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import os, shutil
|
||||
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.constants import filesystem_encoding
|
||||
|
|
@ -80,7 +80,7 @@ class ChooseLibraryAction(InterfaceAction):
|
|||
name = 'Choose Library'
|
||||
action_spec = (_('%d books'), 'lt.png',
|
||||
_('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):
|
||||
self.count_changed(0)
|
||||
|
|
@ -88,6 +88,9 @@ def genesis(self):
|
|||
type=Qt.QueuedConnection)
|
||||
|
||||
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,
|
||||
None), attr='action_choose')
|
||||
self.action_choose.triggered.connect(self.choose_library,
|
||||
|
|
@ -123,6 +126,7 @@ def genesis(self):
|
|||
type=Qt.QueuedConnection)
|
||||
self.choose_menu.addAction(ac)
|
||||
|
||||
|
||||
self.rename_separator = self.choose_menu.addSeparator()
|
||||
|
||||
self.maintenance_menu = QMenu(_('Library Maintenance'))
|
||||
|
|
@ -172,6 +176,7 @@ def build_menus(self):
|
|||
return
|
||||
db = self.gui.library_view.model().db
|
||||
locations = list(self.stats.locations(db))
|
||||
|
||||
for ac in self.switch_actions:
|
||||
ac.setVisible(False)
|
||||
self.quick_menu.clear()
|
||||
|
|
@ -205,7 +210,6 @@ def build_menus(self):
|
|||
rename_actions, delete_actions, qs_actions,
|
||||
self.action_choose)
|
||||
|
||||
|
||||
def location_selected(self, loc):
|
||||
enabled = loc == 'library'
|
||||
self.qaction.setEnabled(enabled)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ class ConvertAction(InterfaceAction):
|
|||
|
||||
name = 'Convert Books'
|
||||
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'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class ShareConnMenu(QMenu): # {{{
|
|||
|
||||
config_email = 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):
|
||||
QMenu.__init__(self, parent)
|
||||
|
|
@ -121,8 +121,7 @@ class SendToDeviceAction(InterfaceAction):
|
|||
|
||||
name = 'Send To Device'
|
||||
action_spec = (_('Send to device'), 'sync.png', None, _('D'))
|
||||
dont_remove_from = frozenset(['toolbar-device'])
|
||||
dont_add_to = frozenset(['toolbar', 'context-menu', 'toolbar-child'])
|
||||
dont_add_to = frozenset(['menubar', 'toolbar', 'context-menu', 'toolbar-child'])
|
||||
|
||||
def genesis(self):
|
||||
self.qaction.triggered.connect(self.do_sync)
|
||||
|
|
@ -169,7 +168,7 @@ def content_server_state_changed(self, running):
|
|||
|
||||
def toggle_content_server(self):
|
||||
if self.gui.content_server is None:
|
||||
self.gui.start_content_server()
|
||||
self.gui.start_content_server()
|
||||
else:
|
||||
self.gui.content_server.threaded_exit()
|
||||
self.stopping_msg = info_dialog(self.gui, _('Stopping'),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class EditCollectionsAction(InterfaceAction):
|
|||
name = 'Edit Collections'
|
||||
action_spec = (_('Manage collections'), 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'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -141,15 +141,18 @@ def edit_metadata(self, checked, bulk=None):
|
|||
list(range(self.gui.library_view.model().rowCount(QModelIndex())))
|
||||
current_row = row_list.index(cr)
|
||||
|
||||
if test_eight_code:
|
||||
changed = self.do_edit_metadata(row_list, current_row)
|
||||
else:
|
||||
changed = self.do_edit_metadata_old(row_list, current_row)
|
||||
func = (self.do_edit_metadata if test_eight_code else
|
||||
self.do_edit_metadata_old)
|
||||
changed, rows_to_refresh = func(row_list, current_row)
|
||||
|
||||
m = self.gui.library_view.model()
|
||||
|
||||
if rows_to_refresh:
|
||||
m.refresh_rows(rows_to_refresh)
|
||||
|
||||
if changed:
|
||||
self.gui.library_view.model().refresh_ids(list(changed))
|
||||
m.refresh_ids(list(changed))
|
||||
current = self.gui.library_view.currentIndex()
|
||||
m = self.gui.library_view.model()
|
||||
if self.gui.cover_flow:
|
||||
self.gui.cover_flow.dataChanged()
|
||||
m.current_changed(current, previous)
|
||||
|
|
@ -183,6 +186,7 @@ def do_edit_metadata_old(self, row_list, current_row):
|
|||
current_row += d.row_delta
|
||||
self.gui.library_view.set_current_row(current_row)
|
||||
self.gui.library_view.scroll_to_row(current_row)
|
||||
return changed, set()
|
||||
|
||||
def do_edit_metadata(self, row_list, current_row):
|
||||
from calibre.gui2.metadata.single import edit_metadata
|
||||
|
|
@ -190,7 +194,7 @@ def do_edit_metadata(self, row_list, current_row):
|
|||
changed, rows_to_refresh = edit_metadata(db, row_list, current_row,
|
||||
parent=self.gui, view_slot=self.view_format_callback,
|
||||
set_current_callback=self.set_current_callback)
|
||||
return changed
|
||||
return changed, rows_to_refresh
|
||||
|
||||
def set_current_callback(self, id_):
|
||||
db = self.gui.library_view.model().db
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ class NextMatchAction(InterfaceAction):
|
|||
name = 'Move to next highlighted book'
|
||||
action_spec = (_('Move to next match'), 'arrow-down.png',
|
||||
_('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'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class OpenFolderAction(InterfaceAction):
|
|||
name = 'Open Folder'
|
||||
action_spec = (_('Open containing folder'), 'document_open.png', None,
|
||||
_('O'))
|
||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class PreferencesAction(InterfaceAction):
|
|||
|
||||
name = 'Preferences'
|
||||
action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P'))
|
||||
dont_remove_from = frozenset(['toolbar'])
|
||||
|
||||
def genesis(self):
|
||||
pm = QMenu()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class ShowBookDetailsAction(InterfaceAction):
|
|||
name = 'Show Book Details'
|
||||
action_spec = (_('Show book details'), 'dialog_information.png', None,
|
||||
_('I'))
|
||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ class TweakEpubAction(InterfaceAction):
|
|||
action_spec = (_('Tweak ePub'), 'trim.png',
|
||||
_('Make small changes to ePub format books'),
|
||||
_('T'))
|
||||
dont_add_to = frozenset(['toolbar-device', 'context-menu-device'])
|
||||
dont_add_to = frozenset(['menubar-device', 'toolbar-device', 'context-menu-device'])
|
||||
action_type = 'current'
|
||||
|
||||
def genesis(self):
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ def setup_ui(self, parent):
|
|||
w = self.widgets[1]
|
||||
items = [_('Yes'), _('No'), _('Undefined')]
|
||||
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]
|
||||
icons = icons[:-1]
|
||||
for icon, text in zip(icons, items):
|
||||
|
|
@ -70,7 +70,7 @@ def setup_ui(self, parent):
|
|||
|
||||
def setter(self, 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
|
||||
self.widgets[1].setCurrentIndex(val)
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ def get_initial_value(self, book_ids):
|
|||
value = None
|
||||
for book_id in book_ids:
|
||||
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
|
||||
if value is not None and value != val:
|
||||
return None
|
||||
|
|
@ -559,7 +559,7 @@ def get_initial_value(self, book_ids):
|
|||
def setup_ui(self, parent):
|
||||
self.make_widgets(parent, QComboBox)
|
||||
items = [_('Yes'), _('No')]
|
||||
if tweaks['bool_custom_columns_are_tristate'] == 'no':
|
||||
if not self.db.prefs.get('bools_are_tristate'):
|
||||
items.append('')
|
||||
else:
|
||||
items.append(_('Undefined'))
|
||||
|
|
@ -571,7 +571,7 @@ def setup_ui(self, parent):
|
|||
|
||||
def getter(self):
|
||||
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]
|
||||
else:
|
||||
return {2: None, 1: False, 0: True}[val]
|
||||
|
|
@ -586,13 +586,13 @@ def commit(self, book_ids, notify=False):
|
|||
return
|
||||
val = self.gui_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
|
||||
self.db.set_custom_bulk(book_ids, val, num=self.col_id, notify=notify)
|
||||
|
||||
def a_c_checkbox_changed(self):
|
||||
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.a_c_checkbox.setChecked(False)
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@
|
|||
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QIcon, Qt, QWidget, QToolBar, QSize, \
|
||||
pyqtSignal, QToolButton, QMenu, \
|
||||
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup
|
||||
from PyQt4.Qt import (QIcon, Qt, QWidget, QToolBar, QSize,
|
||||
pyqtSignal, QToolButton, QMenu, QMenuBar, QAction,
|
||||
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.throbber import ThrobbingButton
|
||||
from calibre.gui2 import gprefs
|
||||
|
|
@ -238,6 +238,80 @@ def __init__(self, parent):
|
|||
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): # {{{
|
||||
|
||||
def __init__(self, donate, location_manager, child_bar, parent):
|
||||
|
|
@ -284,6 +358,8 @@ def build_bar(self):
|
|||
mactions = gprefs['action-layout-toolbar'+mactions]
|
||||
cactions = gprefs['action-layout-toolbar-child']
|
||||
|
||||
show_main = len(mactions) > 0
|
||||
self.setVisible(show_main)
|
||||
show_child = len(cactions) > 0
|
||||
self.child_bar.setVisible(show_child)
|
||||
|
||||
|
|
@ -306,11 +382,16 @@ def build_bar(self):
|
|||
bar.added_actions.append(ac)
|
||||
bar.setup_tool_button(bar, ac, QToolButton.MenuButtonPopup)
|
||||
elif what == 'Donate':
|
||||
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
|
||||
if isosx:
|
||||
bar.addAction(self.gui.donate_action)
|
||||
ch = self.setup_tool_button(bar, self.gui.donate_action)
|
||||
ch.setText(_('Donate'))
|
||||
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:
|
||||
action = self.gui.iactions[what]
|
||||
bar.addAction(action.qaction)
|
||||
|
|
@ -325,17 +406,20 @@ def setup_tool_button(self, bar, ac, menu_mode=None):
|
|||
ch.setAutoRaise(True)
|
||||
if ac.menu() is not None and menu_mode is not None:
|
||||
ch.setPopupMode(menu_mode)
|
||||
return ch
|
||||
|
||||
def resizeEvent(self, ev):
|
||||
QToolBar.resizeEvent(self, ev)
|
||||
style = Qt.ToolButtonTextUnderIcon
|
||||
p = gprefs['toolbar_text']
|
||||
if p == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
s = gprefs['toolbar_icon_size']
|
||||
if s != 'off':
|
||||
p = gprefs['toolbar_text']
|
||||
if p == 'never':
|
||||
style = Qt.ToolButtonIconOnly
|
||||
|
||||
if p == 'auto' and self.preferred_width > self.width()+35 and \
|
||||
not gprefs['action-layout-toolbar-child']:
|
||||
style = Qt.ToolButtonIconOnly
|
||||
if p == 'auto' and self.preferred_width > self.width()+35 and \
|
||||
not gprefs['action-layout-toolbar-child']:
|
||||
style = Qt.ToolButtonIconOnly
|
||||
|
||||
self.setToolButtonStyle(style)
|
||||
|
||||
|
|
@ -421,6 +505,9 @@ def __init__(self, db):
|
|||
self.location_manager, self.child_bar, self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.tool_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.addWidget(self.search_bar)
|
||||
|
|
|
|||
|
|
@ -353,7 +353,7 @@ def createEditor(self, parent, option, index):
|
|||
editor = DelegateCB(parent)
|
||||
items = [_('Y'), _('N'), ' ']
|
||||
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]
|
||||
icons = icons[:-1]
|
||||
for icon, text in zip(icons, items):
|
||||
|
|
@ -367,7 +367,7 @@ def setModelData(self, editor, model, index):
|
|||
def setEditorData(self, editor, index):
|
||||
m = index.model()
|
||||
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
|
||||
else:
|
||||
val = 2 if val is None else 1 if not val else 0
|
||||
|
|
|
|||
|
|
@ -700,7 +700,7 @@ def number_type(r, idx=-1):
|
|||
self.dc_decorator[col] = functools.partial(
|
||||
bool_type_decorator, idx=idx,
|
||||
bool_cols_are_tristate=
|
||||
tweaks['bool_custom_columns_are_tristate'] != 'no')
|
||||
self.db.prefs.get('bools_are_tristate'))
|
||||
elif datatype in ('int', 'float'):
|
||||
self.dc[col] = functools.partial(number_type, idx=idx)
|
||||
elif datatype == 'datetime':
|
||||
|
|
@ -710,7 +710,7 @@ def number_type(r, idx=-1):
|
|||
self.dc_decorator[col] = functools.partial(
|
||||
bool_type_decorator, idx=idx,
|
||||
bool_cols_are_tristate=
|
||||
tweaks['bool_custom_columns_are_tristate'] != 'no')
|
||||
self.db.prefs.get('bools_are_tristate'))
|
||||
elif datatype == 'rating':
|
||||
self.dc[col] = functools.partial(rating_type, idx=idx)
|
||||
elif datatype == 'series':
|
||||
|
|
|
|||
|
|
@ -521,7 +521,7 @@ def create_row2(row, widget, button=None):
|
|||
|
||||
# }}}
|
||||
|
||||
class MetadataSingleDialogAlt(MetadataSingleDialogBase): # {{{
|
||||
class MetadataSingleDialogAlt1(MetadataSingleDialogBase): # {{{
|
||||
|
||||
cc_two_column = False
|
||||
one_line_comments_toolbar = True
|
||||
|
|
@ -654,10 +654,14 @@ def create_row(row, widget, tab_to, button=None, icon=None, span=1):
|
|||
|
||||
# }}}
|
||||
|
||||
editors = {'default': MetadataSingleDialog, 'alt1': MetadataSingleDialogAlt1}
|
||||
|
||||
def edit_metadata(db, row_list, current_row, parent=None, view_slot=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,
|
||||
set_current_callback=set_current_callback)
|
||||
return d.changed, d.rows_to_refresh
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from threading import Thread, Event
|
||||
from operator import attrgetter
|
||||
|
||||
from PyQt4.Qt import (QStyledItemDelegate, QTextDocument, QRectF, QIcon, Qt,
|
||||
QStyle, QApplication, QDialog, QVBoxLayout, QLabel, QDialogButtonBox,
|
||||
|
|
@ -22,7 +23,7 @@
|
|||
from calibre.ebooks.metadata.book.base import Metadata
|
||||
from calibre.gui2 import error_dialog, NONE
|
||||
from calibre.utils.date import utcnow, fromordinal, format_date
|
||||
|
||||
from calibre.library.comments import comments_to_html
|
||||
|
||||
class RichTextDelegate(QStyledItemDelegate): # {{{
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ def paint(self, painter, option, index):
|
|||
painter.restore()
|
||||
# }}}
|
||||
|
||||
class ResultsModel(QAbstractTableModel):
|
||||
class ResultsModel(QAbstractTableModel): # {{{
|
||||
|
||||
COLUMNS = (
|
||||
'#', _('Title'), _('Published'), _('Has cover'), _('Has summary')
|
||||
|
|
@ -71,15 +72,12 @@ def columnCount(self, parent=None):
|
|||
return len(self.COLUMNS)
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role != Qt.DisplayRole:
|
||||
return NONE
|
||||
if orientation == Qt.Horizontal:
|
||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
||||
try:
|
||||
return QVariant(self.COLUMNS[section])
|
||||
except:
|
||||
return NONE
|
||||
else:
|
||||
return QVariant(unicode(section+1))
|
||||
return NONE
|
||||
|
||||
def data_as_text(self, book, col):
|
||||
if col == 0:
|
||||
|
|
@ -110,10 +108,33 @@ def data(self, index, role):
|
|||
return self.yes_icon
|
||||
if col == 4 and book.comments:
|
||||
return self.yes_icon
|
||||
elif role == Qt.UserRole:
|
||||
return book
|
||||
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): # {{{
|
||||
|
||||
show_details_signal = pyqtSignal(object)
|
||||
book_selected = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QTableView.__init__(self, parent)
|
||||
self.rt_delegate = RichTextDelegate(self)
|
||||
|
|
@ -121,6 +142,9 @@ def __init__(self, parent=None):
|
|||
self.setAlternatingRowColors(True)
|
||||
self.setSelectionBehavior(self.SelectRows)
|
||||
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):
|
||||
self._model = ResultsModel(results, self)
|
||||
|
|
@ -129,6 +153,38 @@ def show_results(self, results):
|
|||
self.setItemDelegateForColumn(i, self.rt_delegate)
|
||||
self.resizeRowsToContents()
|
||||
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 @@ def run(self):
|
|||
class IdentifyWidget(QWidget): # {{{
|
||||
|
||||
rejected = pyqtSignal()
|
||||
results_found = pyqtSignal()
|
||||
book_selected = pyqtSignal(object)
|
||||
|
||||
def __init__(self, log, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
|
|
@ -244,11 +302,15 @@ def __init__(self, log, parent=None):
|
|||
l.addWidget(self.top, 0, 0)
|
||||
|
||||
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)
|
||||
|
||||
self.comments_view = Comments(self)
|
||||
l.addWidget(self.comments_view, 1, 1)
|
||||
|
||||
self.results_view.show_details_signal.connect(self.comments_view.show_data)
|
||||
|
||||
self.query = QLabel('download starting...')
|
||||
f = self.query.font()
|
||||
f.setPointSize(f.pointSize()-2)
|
||||
|
|
@ -324,6 +386,13 @@ def process_results(self):
|
|||
|
||||
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):
|
||||
self.abort.set()
|
||||
|
|
@ -343,23 +412,46 @@ def __init__(self, log, parent=None):
|
|||
self.setLayout(l)
|
||||
l.addWidget(self.stack)
|
||||
|
||||
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel)
|
||||
self.bb = QDialogButtonBox(QDialogButtonBox.Cancel|QDialogButtonBox.Ok)
|
||||
l.addWidget(self.bb)
|
||||
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.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.resize(850, 500)
|
||||
|
||||
def book_selected(self, book):
|
||||
print (book)
|
||||
self.next_button.setVisible(False)
|
||||
self.ok_button.setVisible(True)
|
||||
|
||||
def accept(self):
|
||||
# Prevent pressing Enter from closing the dialog
|
||||
# Prevent the usual dialog accept mechanisms from working
|
||||
pass
|
||||
|
||||
def reject(self):
|
||||
self.identify_widget.cancel()
|
||||
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={}):
|
||||
self.identify_widget.start(title=title, authors=authors,
|
||||
identifiers=identifiers)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
from calibre.gui2.preferences.columns_ui import Ui_Form
|
||||
from calibre.gui2.preferences.create_custom_column import CreateCustomColumn
|
||||
from calibre.gui2 import error_dialog, question_dialog, ALL_COLUMNS
|
||||
from calibre.utils.config import test_eight_code
|
||||
|
||||
class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
||||
|
||||
|
|
@ -33,6 +34,14 @@ def genesis(self, gui):
|
|||
signal = getattr(self.opt_columns, 'item'+signal)
|
||||
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):
|
||||
ConfigWidgetBase.initialize(self)
|
||||
self.init_columns()
|
||||
|
|
@ -169,6 +178,10 @@ def col_pos(x, y):
|
|||
must_restart = True
|
||||
return must_restart
|
||||
|
||||
def refresh_gui(self, gui):
|
||||
gui.library_view.reset()
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import QApplication
|
||||
|
|
|
|||
|
|
@ -197,6 +197,67 @@
|
|||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<resources>
|
||||
|
|
|
|||
|
|
@ -360,6 +360,7 @@ def closeEvent(self, *args):
|
|||
self.gui.create_device_menu()
|
||||
self.gui.set_device_menu_items_state(bool(self.gui.device_connected))
|
||||
self.gui.tool_bar.build_bar()
|
||||
self.gui.menu_bar.build_bar()
|
||||
self.gui.build_context_menus()
|
||||
self.gui.tool_bar.apply_settings()
|
||||
|
||||
|
|
|
|||
|
|
@ -34,9 +34,12 @@ def name_to_action(self, name, gui):
|
|||
if name == 'Location Manager':
|
||||
return FakeAction(name, None,
|
||||
_('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:
|
||||
return FakeAction('--- '+_('Separator')+' ---', None)
|
||||
return FakeAction('--- '+_('Separator')+' ---', None,
|
||||
dont_add_to=frozenset(['menubar', 'menubar-device']))
|
||||
try:
|
||||
return gui.iactions[name]
|
||||
except:
|
||||
|
|
@ -89,7 +92,7 @@ def __init__(self, key, gui):
|
|||
self._data = self.get_all_actions(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 = [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]
|
||||
|
|
@ -208,12 +211,14 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
|
|||
|
||||
LOCATIONS = [
|
||||
('toolbar', _('The main toolbar')),
|
||||
('toolbar-child', _('The optional second toolbar')),
|
||||
('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 '
|
||||
'calibre library')),
|
||||
('context-menu-device', _('The context menu for the books on '
|
||||
'the device'))
|
||||
'the device')),
|
||||
]
|
||||
|
||||
def genesis(self, gui):
|
||||
|
|
@ -284,6 +289,21 @@ def move(self, delta, *args):
|
|||
self.changed_signal.emit()
|
||||
|
||||
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():
|
||||
cm.commit()
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -153,6 +153,7 @@ def initialize(self, library_path, db, listener, actions, show_gui=True):
|
|||
|
||||
for ac in self.iactions.values():
|
||||
ac.do_genesis()
|
||||
self.donate_action = QAction(QIcon(I('donate.png')), _('&Donate to support calibre'), self)
|
||||
MainWindowMixin.__init__(self, db)
|
||||
|
||||
# Jobs Button {{{
|
||||
|
|
@ -186,8 +187,7 @@ def initialize(self, library_path, db, listener, actions, show_gui=True):
|
|||
self.system_tray_menu = QMenu(self)
|
||||
self.restore_action = self.system_tray_menu.addAction(
|
||||
QIcon(I('page.png')), _('&Restore'))
|
||||
self.donate_action = self.system_tray_menu.addAction(
|
||||
QIcon(I('donate.png')), _('&Donate to support calibre'))
|
||||
self.system_tray_menu.addAction(self.donate_action)
|
||||
self.donate_button.setDefaultAction(self.donate_action)
|
||||
self.donate_button.setStatusTip(self.donate_button.toolTip())
|
||||
self.eject_action = self.system_tray_menu.addAction(
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ def _matchkind(self, query):
|
|||
return matchkind, query
|
||||
|
||||
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']
|
||||
matches = set()
|
||||
query = icu_lower(query)
|
||||
|
|
@ -947,7 +947,7 @@ def multisort(self, fields=[], subsort=False):
|
|||
if not fields:
|
||||
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)
|
||||
|
||||
tmap = list(itertools.repeat(False, len(self._data)))
|
||||
|
|
@ -970,9 +970,10 @@ def __cmp__(self, other):
|
|||
|
||||
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
|
||||
self.field_metadata = field_metadata
|
||||
self.db_prefs = db_prefs
|
||||
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.library_order = tweaks['title_series_sorting'] == 'library_order'
|
||||
|
|
@ -1032,7 +1033,7 @@ def itervals(self, record):
|
|||
val = self.string_sort_key(val)
|
||||
|
||||
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)
|
||||
else:
|
||||
val = {True: 1, False: 2, None: 3}.get(val, 3)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@
|
|||
from calibre.utils.magick.draw import save_cover_data_to
|
||||
from calibre.utils.recycle_bin import delete_file, delete_tree
|
||||
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
|
||||
|
||||
|
|
@ -213,6 +213,12 @@ def initialize_dynamic(self):
|
|||
defs = self.prefs.defaults
|
||||
defs['gui_restriction'] = defs['cs_restriction'] = ''
|
||||
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
|
||||
def migrate_preference(key, default):
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
|
||||
class CSSortKeyGenerator(SortKeyGenerator):
|
||||
|
||||
def __init__(self, fields, fm):
|
||||
SortKeyGenerator.__init__(self, fields, fm, None)
|
||||
def __init__(self, fields, fm, db_prefs):
|
||||
SortKeyGenerator.__init__(self, fields, fm, None, db_prefs)
|
||||
|
||||
def __call__(self, record):
|
||||
return self.itervals(record).next()
|
||||
|
|
@ -56,7 +56,8 @@ def sort(self, items, field, order):
|
|||
field = self.db.data.sanitize_sort_field_name(field)
|
||||
if field not in self.db.field_metadata.sortable_field_keys():
|
||||
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)
|
||||
|
||||
# }}}
|
||||
|
|
|
|||
|
|
@ -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 *
|
||||
|
||||
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
|
||||
----------------------
|
||||
|
|
|
|||
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
|
|
@ -785,6 +785,9 @@ def write_tweaks(raw):
|
|||
|
||||
tweaks = read_tweaks()
|
||||
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():
|
||||
if hasattr(os, 'geteuid') and os.geteuid() == 0:
|
||||
|
|
|
|||
Loading…
Reference in a new issue