0.9.21+ - KG revisions for dnd support

This commit is contained in:
GRiker 2013-03-04 02:44:55 -08:00
commit 627f895c47
24 changed files with 594 additions and 344 deletions

View File

@ -672,6 +672,7 @@ Some limitations of PDF input are:
* Links and Tables of Contents are not supported * Links and Tables of Contents are not supported
* PDFs that use embedded non-unicode fonts to represent non-English characters will result in garbled output for those characters * PDFs that use embedded non-unicode fonts to represent non-English characters will result in garbled output for those characters
* Some PDFs are made up of photographs of the page with OCRed text behind them. In such cases |app| uses the OCRed text, which can be very different from what you see when you view the PDF file * Some PDFs are made up of photographs of the page with OCRed text behind them. In such cases |app| uses the OCRed text, which can be very different from what you see when you view the PDF file
* PDFs that are used to display complex text, like right to left languages and math typesetting will not convert correctly
To re-iterate **PDF is a really, really bad** format to use as input. If you absolutely must use PDF, then be prepared for an To re-iterate **PDF is a really, really bad** format to use as input. If you absolutely must use PDF, then be prepared for an
output ranging anywhere from decent to unusable, depending on the input PDF. output ranging anywhere from decent to unusable, depending on the input PDF.

View File

@ -30,11 +30,6 @@ class tvn24(BasicNewsRecipe):
feeds = [(u'Najnowsze', u'http://www.tvn24.pl/najnowsze.xml'), ] feeds = [(u'Najnowsze', u'http://www.tvn24.pl/najnowsze.xml'), ]
#(u'Polska', u'www.tvn24.pl/polska.xml'), (u'\u015awiat', u'http://www.tvn24.pl/swiat.xml'), (u'Sport', u'http://www.tvn24.pl/sport.xml'), (u'Biznes', u'http://www.tvn24.pl/biznes.xml'), (u'Meteo', u'http://www.tvn24.pl/meteo.xml'), (u'Micha\u0142ki', u'http://www.tvn24.pl/michalki.xml'), (u'Kultura', u'http://www.tvn24.pl/kultura.xml')] #(u'Polska', u'www.tvn24.pl/polska.xml'), (u'\u015awiat', u'http://www.tvn24.pl/swiat.xml'), (u'Sport', u'http://www.tvn24.pl/sport.xml'), (u'Biznes', u'http://www.tvn24.pl/biznes.xml'), (u'Meteo', u'http://www.tvn24.pl/meteo.xml'), (u'Micha\u0142ki', u'http://www.tvn24.pl/michalki.xml'), (u'Kultura', u'http://www.tvn24.pl/kultura.xml')]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup
def preprocess_html(self, soup): def preprocess_html(self, soup):
for alink in soup.findAll('a'): for alink in soup.findAll('a'):
if alink.string is not None: if alink.string is not None:

View File

@ -101,9 +101,9 @@ class InterfaceAction(QObject):
#: on calibre as a whole #: on calibre as a whole
action_type = 'global' action_type = 'global'
#: If True, the action may inspect the event at accept_enter_event() and #: If True, then this InterfaceAction will have the opportunity to interact
#: accept_drag_move_event(), returning True or False if it wants to handle the event. #: with drag and drop events. See the methods, :meth:`accept_enter_event`,
#: drop_event() will be called in the subclass from calibre.gui2.bars #: :meth`:accept_drag_move_event`, :meth:`drop_event` for details.
accepts_drops = False accepts_drops = False
def __init__(self, parent, site_customization): def __init__(self, parent, site_customization):
@ -113,14 +113,26 @@ class InterfaceAction(QObject):
self.site_customization = site_customization self.site_customization = site_customization
self.interface_action_base_plugin = None self.interface_action_base_plugin = None
def accept_enter_event(self, mime_data): def accept_enter_event(self, event, mime_data):
''' This method should return True iff this interface action is capable
of handling the drag event. Do not call accept/ignore on the event,
that will be taken care of by the calibre UI.'''
return False return False
def accept_drag_move_event(self, mime_data): def accept_drag_move_event(self, event, mime_data):
''' This method should return True iff this interface action is capable
of handling the drag event. Do not call accept/ignore on the event,
that will be taken care of by the calibre UI.'''
return False return False
def drop_event(self, event): def drop_event(self, event, mime_data):
pass ''' This method should perform some useful action and return True
iff this interface action is capable of handling the drop event. Do not
call accept/ignore on the event, that will be taken care of by the
calibre UI. You should not perform blocking/long operations in this
function. Instead emit a signal or use QTimer.singleShot and return
quickly. See the builtin actions for examples.'''
return False
def do_genesis(self): def do_genesis(self):
self.Dispatcher = partial(Dispatcher, parent=self) self.Dispatcher = partial(Dispatcher, parent=self)
@ -145,7 +157,6 @@ class InterfaceAction(QObject):
else: else:
action = QAction(text, self.gui) action = QAction(text, self.gui)
if attr == 'qaction': if attr == 'qaction':
action.associated_interface_action = self
mt = (action.text() if self.action_menu_clone_qaction is True else mt = (action.text() if self.action_menu_clone_qaction is True else
unicode(self.action_menu_clone_qaction)) unicode(self.action_menu_clone_qaction))
self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui) self.menuless_qaction = ma = QAction(action.icon(), mt, self.gui)

View File

@ -18,7 +18,8 @@ from calibre import sanitize_file_name_unicode
class GenerateCatalogAction(InterfaceAction): class GenerateCatalogAction(InterfaceAction):
name = 'Generate Catalog' name = 'Generate Catalog'
action_spec = (_('Create catalog'), 'catalog.png', 'Catalog builder', ()) action_spec = (_('Create catalog'), 'catalog.png',
_('Create a catalog of the books in your calibre library in different formats'), ())
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
def genesis(self): def genesis(self):

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os import os
from functools import partial from functools import partial
from PyQt4.Qt import QModelIndex from PyQt4.Qt import QModelIndex, QTimer
from calibre.gui2 import error_dialog, Dispatcher from calibre.gui2 import error_dialog, Dispatcher
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook
@ -19,11 +19,36 @@ from calibre.customize.ui import plugin_for_input_format
class ConvertAction(InterfaceAction): class ConvertAction(InterfaceAction):
name = 'Convert Books' name = 'Convert Books'
action_spec = (_('Convert books'), 'convert.png', None, _('C')) action_spec = (_('Convert books'), 'convert.png', _('Convert books between different ebook formats'), _('C'))
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
action_type = 'current' action_type = 'current'
action_add_menu = True action_add_menu = True
accepts_drops = True
def accept_enter_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def accept_drag_move_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def drop_event(self, event, mime_data):
mime = 'application/calibre+from_library'
if mime_data.hasFormat(mime):
self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split()))
QTimer.singleShot(1, self.do_drop)
return True
return False
def do_drop(self):
book_ids = self.dropped_ids
del self.dropped_ids
self.do_convert(book_ids)
def genesis(self): def genesis(self):
m = self.convert_menu = self.qaction.menu() m = self.convert_menu = self.qaction.menu()
cm = partial(self.create_menu_action, self.convert_menu) cm = partial(self.create_menu_action, self.convert_menu)
@ -112,6 +137,9 @@ class ConvertAction(InterfaceAction):
def convert_ebook(self, checked, bulk=None): def convert_ebook(self, checked, bulk=None):
book_ids = self.get_books_for_conversion() book_ids = self.get_books_for_conversion()
if book_ids is None: return if book_ids is None: return
self.do_convert(book_ids, bulk=bulk)
def do_convert(self, book_ids, bulk=None):
previous = self.gui.library_view.currentIndex() previous = self.gui.library_view.currentIndex()
rows = [x.row() for x in \ rows = [x.row() for x in \
self.gui.library_view.selectionModel().selectedRows()] self.gui.library_view.selectionModel().selectedRows()]

View File

@ -83,11 +83,37 @@ class MultiDeleter(QObject): # {{{
class DeleteAction(InterfaceAction): class DeleteAction(InterfaceAction):
name = 'Remove Books' name = 'Remove Books'
action_spec = (_('Remove books'), 'trash.png', None, 'Del') action_spec = (_('Remove books'), 'trash.png', _('Delete books'), 'Del')
action_type = 'current' action_type = 'current'
action_add_menu = True action_add_menu = True
action_menu_clone_qaction = _('Remove selected books') action_menu_clone_qaction = _('Remove selected books')
accepts_drops = True
def accept_enter_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def accept_drag_move_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def drop_event(self, event, mime_data):
mime = 'application/calibre+from_library'
if mime_data.hasFormat(mime):
self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split()))
QTimer.singleShot(1, self.do_drop)
return True
return False
def do_drop(self):
book_ids = self.dropped_ids
del self.dropped_ids
if book_ids:
self.do_library_delete(book_ids)
def genesis(self): def genesis(self):
self.qaction.triggered.connect(self.delete_books) self.qaction.triggered.connect(self.delete_books)
self.delete_menu = self.qaction.menu() self.delete_menu = self.qaction.menu()
@ -296,17 +322,8 @@ class DeleteAction(InterfaceAction):
current_row = rmap.get(next_id, None) current_row = rmap.get(next_id, None)
self.library_ids_deleted(ids_deleted, current_row=current_row) self.library_ids_deleted(ids_deleted, current_row=current_row)
def delete_books(self, *args): def do_library_delete(self, to_delete_ids):
'''
Delete selected books from device or library.
'''
view = self.gui.current_view() view = self.gui.current_view()
rows = view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
return
# Library view is visible.
if self.gui.stack.currentIndex() == 0:
to_delete_ids = [view.model().id(r) for r in rows]
# Ask the user if they want to delete the book from the library or device if it is in both. # Ask the user if they want to delete the book from the library or device if it is in both.
if self.gui.device_manager.is_device_connected: if self.gui.device_manager.is_device_connected:
on_device = False on_device = False
@ -336,12 +353,25 @@ class DeleteAction(InterfaceAction):
+'</p>', 'library_delete_books', self.gui): +'</p>', 'library_delete_books', self.gui):
return return
next_id = view.next_id next_id = view.next_id
if len(rows) < 5: if len(to_delete_ids) < 5:
view.model().delete_books_by_id(to_delete_ids) view.model().delete_books_by_id(to_delete_ids)
self.library_ids_deleted2(to_delete_ids, next_id=next_id) self.library_ids_deleted2(to_delete_ids, next_id=next_id)
else: else:
self.__md = MultiDeleter(self.gui, to_delete_ids, self.__md = MultiDeleter(self.gui, to_delete_ids,
partial(self.library_ids_deleted2, next_id=next_id)) partial(self.library_ids_deleted2, next_id=next_id))
def delete_books(self, *args):
'''
Delete selected books from device or library.
'''
view = self.gui.current_view()
rows = view.selectionModel().selectedRows()
if not rows or len(rows) == 0:
return
# Library view is visible.
if self.gui.stack.currentIndex() == 0:
to_delete_ids = [view.model().id(r) for r in rows]
self.do_library_delete(to_delete_ids)
# Device view is visible. # Device view is visible.
else: else:
if self.gui.stack.currentIndex() == 1: if self.gui.stack.currentIndex() == 1:

View File

@ -177,7 +177,8 @@ class SendToDeviceAction(InterfaceAction):
class ConnectShareAction(InterfaceAction): class ConnectShareAction(InterfaceAction):
name = 'Connect Share' name = 'Connect Share'
action_spec = (_('Connect/share'), 'connect_share.png', None, None) action_spec = (_('Connect/share'), 'connect_share.png',
_('Share books using a web server or email. Connect to special devices, etc.'), None)
popup_type = QToolButton.InstantPopup popup_type = QToolButton.InstantPopup
def genesis(self): def genesis(self):

View File

@ -23,10 +23,38 @@ from calibre.db.errors import NoSuchFormat
class EditMetadataAction(InterfaceAction): class EditMetadataAction(InterfaceAction):
name = 'Edit Metadata' name = 'Edit Metadata'
action_spec = (_('Edit metadata'), 'edit_input.png', None, _('E')) action_spec = (_('Edit metadata'), 'edit_input.png', _('Change the title/author/cover etc. of books'), _('E'))
action_type = 'current' action_type = 'current'
action_add_menu = True action_add_menu = True
accepts_drops = True
def accept_enter_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def accept_drag_move_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def drop_event(self, event, mime_data):
mime = 'application/calibre+from_library'
if mime_data.hasFormat(mime):
self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split()))
QTimer.singleShot(1, self.do_drop)
return True
return False
def do_drop(self):
book_ids = self.dropped_ids
del self.dropped_ids
if book_ids:
db = self.gui.library_view.model().db
rows = [db.row(i) for i in book_ids]
self.edit_metadata_for(rows, book_ids)
def genesis(self): def genesis(self):
md = self.qaction.menu() md = self.qaction.menu()
cm = partial(self.create_menu_action, md) cm = partial(self.create_menu_action, md)
@ -186,18 +214,23 @@ class EditMetadataAction(InterfaceAction):
Edit metadata of selected books in library. Edit metadata of selected books in library.
''' '''
rows = self.gui.library_view.selectionModel().selectedRows() rows = self.gui.library_view.selectionModel().selectedRows()
previous = self.gui.library_view.currentIndex()
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
d = error_dialog(self.gui, _('Cannot edit metadata'), d = error_dialog(self.gui, _('Cannot edit metadata'),
_('No books selected')) _('No books selected'))
d.exec_() d.exec_()
return return
if bulk or (bulk is None and len(rows) > 1):
return self.edit_bulk_metadata(checked)
row_list = [r.row() for r in rows] row_list = [r.row() for r in rows]
m = self.gui.library_view.model()
ids = [m.id(r) for r in rows]
self.edit_metadata_for(row_list, ids, bulk=bulk)
def edit_metadata_for(self, rows, book_ids, bulk=None):
previous = self.gui.library_view.currentIndex()
if bulk or (bulk is None and len(rows) > 1):
return self.do_edit_bulk_metadata(rows, book_ids)
current_row = 0 current_row = 0
row_list = rows
if len(row_list) == 1: if len(row_list) == 1:
cr = row_list[0] cr = row_list[0]
@ -242,7 +275,6 @@ class EditMetadataAction(InterfaceAction):
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
view.view_format(db.row(id_), fmt) view.view_format(db.row(id_), fmt)
def edit_bulk_metadata(self, checked): def edit_bulk_metadata(self, checked):
''' '''
Edit metadata of selected books in library in bulk. Edit metadata of selected books in library in bulk.
@ -256,6 +288,9 @@ class EditMetadataAction(InterfaceAction):
_('No books selected')) _('No books selected'))
d.exec_() d.exec_()
return return
self.do_edit_bulk_metadata(rows, ids)
def do_edit_bulk_metadata(self, rows, book_ids):
# Prevent the TagView from updating due to signals from the database # Prevent the TagView from updating due to signals from the database
self.gui.tags_view.blockSignals(True) self.gui.tags_view.blockSignals(True)
changed = False changed = False
@ -278,7 +313,7 @@ class EditMetadataAction(InterfaceAction):
self.gui.tags_view.recount() self.gui.tags_view.recount()
if self.gui.cover_flow: if self.gui.cover_flow:
self.gui.cover_flow.dataChanged() self.gui.cover_flow.dataChanged()
self.gui.library_view.select_rows(ids) self.gui.library_view.select_rows(book_ids)
# Merge books {{{ # Merge books {{{
def merge_books(self, safe_merge=False, merge_only_formats=False): def merge_books(self, safe_merge=False, merge_only_formats=False):

View File

@ -16,7 +16,7 @@ from calibre.gui2.actions import InterfaceAction
class FetchNewsAction(InterfaceAction): class FetchNewsAction(InterfaceAction):
name = 'Fetch News' name = 'Fetch News'
action_spec = (_('Fetch news'), 'news.png', None, _('F')) action_spec = (_('Fetch news'), 'news.png', _('Download news in ebook form from various websites all over the world'), _('F'))
def location_selected(self, loc): def location_selected(self, loc):
enabled = loc == 'library' enabled = loc == 'library'

View File

@ -11,8 +11,8 @@ from calibre.gui2.actions import InterfaceAction
class OpenFolderAction(InterfaceAction): 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',
_('O')) _('Open the folder containing the current book\'s files'), _('O'))
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
action_type = 'current' action_type = 'current'

View File

@ -15,7 +15,7 @@ from calibre.gui2.dialogs.plugin_updater import (PluginUpdaterDialog,
class PluginUpdaterAction(InterfaceAction): class PluginUpdaterAction(InterfaceAction):
name = 'Plugin Updater' name = 'Plugin Updater'
action_spec = (_('Plugin Updater'), None, None, ()) action_spec = (_('Plugin Updater'), None, _('Update any plugins you have installed in calibre'), ())
action_type = 'current' action_type = 'current'
def genesis(self): def genesis(self):

View File

@ -10,6 +10,7 @@ __docformat__ = 'restructuredtext en'
import os, weakref, shutil, textwrap import os, weakref, shutil, textwrap
from collections import OrderedDict from collections import OrderedDict
from functools import partial from functools import partial
from future_builtins import map
from PyQt4.Qt import (QDialog, QGridLayout, QIcon, QCheckBox, QLabel, QFrame, from PyQt4.Qt import (QDialog, QGridLayout, QIcon, QCheckBox, QLabel, QFrame,
QApplication, QDialogButtonBox, Qt, QSize, QSpacerItem, QApplication, QDialogButtonBox, Qt, QSize, QSpacerItem,
@ -364,9 +365,35 @@ class Report(QDialog): # {{{
class PolishAction(InterfaceAction): class PolishAction(InterfaceAction):
name = 'Polish Books' name = 'Polish Books'
action_spec = (_('Polish books'), 'polish.png', None, _('P')) action_spec = (_('Polish books'), 'polish.png',
_('Apply the shine of perfection to your books'), _('P'))
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
action_type = 'current' action_type = 'current'
accepts_drops = True
def accept_enter_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def accept_drag_move_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def drop_event(self, event, mime_data):
mime = 'application/calibre+from_library'
if mime_data.hasFormat(mime):
self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split()))
QTimer.singleShot(1, self.do_drop)
return True
return False
def do_drop(self):
book_id_map = self.get_supported_books(self.dropped_ids)
del self.dropped_ids
if book_id_map:
self.do_polish(book_id_map)
def genesis(self): def genesis(self):
self.qaction.triggered.connect(self.polish_books) self.qaction.triggered.connect(self.polish_books)
@ -377,7 +404,6 @@ class PolishAction(InterfaceAction):
self.qaction.setEnabled(enabled) self.qaction.setEnabled(enabled)
def get_books_for_polishing(self): def get_books_for_polishing(self):
from calibre.ebooks.oeb.polish.main import SUPPORTED
rows = [r.row() for r in rows = [r.row() for r in
self.gui.library_view.selectionModel().selectedRows()] self.gui.library_view.selectionModel().selectedRows()]
if not rows or len(rows) == 0: if not rows or len(rows) == 0:
@ -387,11 +413,16 @@ class PolishAction(InterfaceAction):
return None return None
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
ans = (db.id(r) for r in rows) ans = (db.id(r) for r in rows)
return self.get_supported_books(ans)
def get_supported_books(self, book_ids):
from calibre.ebooks.oeb.polish.main import SUPPORTED
db = self.gui.library_view.model().db
supported = set(SUPPORTED) supported = set(SUPPORTED)
for x in SUPPORTED: for x in SUPPORTED:
supported.add('ORIGINAL_'+x) supported.add('ORIGINAL_'+x)
ans = [(x, set( (db.formats(x, index_is_id=True) or '').split(',') ) ans = [(x, set( (db.formats(x, index_is_id=True) or '').split(',') )
.intersection(supported)) for x in ans] .intersection(supported)) for x in book_ids]
ans = [x for x in ans if x[1]] ans = [x for x in ans if x[1]]
if not ans: if not ans:
error_dialog(self.gui, _('Cannot polish'), error_dialog(self.gui, _('Cannot polish'),
@ -409,6 +440,9 @@ class PolishAction(InterfaceAction):
book_id_map = self.get_books_for_polishing() book_id_map = self.get_books_for_polishing()
if not book_id_map: if not book_id_map:
return return
self.do_polish(book_id_map)
def do_polish(self, book_id_map):
d = Polish(self.gui.library_view.model().db, book_id_map, parent=self.gui) d = Polish(self.gui.library_view.model().db, book_id_map, parent=self.gui)
if d.exec_() == d.Accepted and d.jobs: if d.exec_() == d.Accepted and d.jobs:
show_reports = bool(d.show_reports.isChecked()) show_reports = bool(d.show_reports.isChecked())

View File

@ -17,7 +17,7 @@ from calibre.constants import DEBUG, isosx
class PreferencesAction(InterfaceAction): class PreferencesAction(InterfaceAction):
name = 'Preferences' name = 'Preferences'
action_spec = (_('Preferences'), 'config.png', None, _('Ctrl+P')) action_spec = (_('Preferences'), 'config.png', _('Configure calibre'), _('Ctrl+P'))
action_add_menu = True action_add_menu = True
action_menu_clone_qaction = _('Change calibre behavior') action_menu_clone_qaction = _('Change calibre behavior')

View File

@ -11,7 +11,7 @@ from calibre.gui2.actions import InterfaceAction
class RestartAction(InterfaceAction): class RestartAction(InterfaceAction):
name = 'Restart' name = 'Restart'
action_spec = (_('Restart'), None, None, _('Ctrl+R')) action_spec = (_('Restart'), None, _('Restart calibre'), _('Ctrl+R'))
def genesis(self): def genesis(self):
self.qaction.triggered.connect(self.restart) self.qaction.triggered.connect(self.restart)

View File

@ -17,7 +17,8 @@ from calibre.gui2.actions import InterfaceAction
class SaveToDiskAction(InterfaceAction): class SaveToDiskAction(InterfaceAction):
name = "Save To Disk" name = "Save To Disk"
action_spec = (_('Save to disk'), 'save.png', None, _('S')) action_spec = (_('Save to disk'), 'save.png',
_('Export ebook files from the calibre library'), _('S'))
action_type = 'current' action_type = 'current'
action_add_menu = True action_add_menu = True
action_menu_clone_qaction = True action_menu_clone_qaction = True

View File

@ -13,8 +13,8 @@ from calibre.gui2 import error_dialog
class ShowBookDetailsAction(InterfaceAction): 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',
_('I')) _('Show the detailed metadata for the current book in a separate window'), _('I'))
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
action_type = 'current' action_type = 'current'

View File

@ -14,7 +14,7 @@ from calibre.gui2.actions import InterfaceAction
class SimilarBooksAction(InterfaceAction): class SimilarBooksAction(InterfaceAction):
name = 'Similar Books' name = 'Similar Books'
action_spec = (_('Similar books...'), None, None, None) action_spec = (_('Similar books...'), None, _('Show books similar to the current book'), None)
popup_type = QToolButton.InstantPopup popup_type = QToolButton.InstantPopup
action_type = 'current' action_type = 'current'
action_add_menu = True action_add_menu = True

View File

@ -17,7 +17,7 @@ from calibre.gui2.dialogs.confirm_delete import confirm
class StoreAction(InterfaceAction): class StoreAction(InterfaceAction):
name = 'Store' name = 'Store'
action_spec = (_('Get books'), 'store.png', None, _('G')) action_spec = (_('Get books'), 'store.png', _('Search dozens of online ebook retailers for the cheapest books'), _('G'))
action_add_menu = True action_add_menu = True
action_menu_clone_qaction = _('Search for ebooks') action_menu_clone_qaction = _('Search for ebooks')

View File

@ -64,7 +64,7 @@ class TweakBook(QDialog):
self.fmt_choice_box = QGroupBox(_('Choose the format to tweak:'), self) self.fmt_choice_box = QGroupBox(_('Choose the format to tweak:'), self)
self._fl = fl = QHBoxLayout() self._fl = fl = QHBoxLayout()
self.fmt_choice_box.setLayout(self._fl) self.fmt_choice_box.setLayout(self._fl)
self.fmt_choice_buttons = [QRadioButton(x, self) for x in fmts] self.fmt_choice_buttons = [QRadioButton(y, self) for y in fmts]
for x in self.fmt_choice_buttons: for x in self.fmt_choice_buttons:
fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else fl.addWidget(x, stretch=10 if x is self.fmt_choice_buttons[-1] else
0) 0)
@ -291,6 +291,32 @@ class TweakEpubAction(InterfaceAction):
dont_add_to = frozenset(['context-menu-device']) dont_add_to = frozenset(['context-menu-device'])
action_type = 'current' action_type = 'current'
accepts_drops = True
def accept_enter_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def accept_drag_move_event(self, event, mime_data):
if mime_data.hasFormat("application/calibre+from_library"):
return True
return False
def drop_event(self, event, mime_data):
mime = 'application/calibre+from_library'
if mime_data.hasFormat(mime):
self.dropped_ids = tuple(map(int, str(mime_data.data(mime)).split()))
QTimer.singleShot(1, self.do_drop)
return True
return False
def do_drop(self):
book_ids = self.dropped_ids
del self.dropped_ids
if book_ids:
self.do_tweak(book_ids[0])
def genesis(self): def genesis(self):
self.qaction.triggered.connect(self.tweak_book) self.qaction.triggered.connect(self.tweak_book)
@ -301,6 +327,9 @@ class TweakEpubAction(InterfaceAction):
_('No book selected'), show=True) _('No book selected'), show=True)
book_id = self.gui.library_view.model().id(row) book_id = self.gui.library_view.model().id(row)
self.do_tweak(book_id)
def do_tweak(self, book_id):
db = self.gui.library_view.model().db db = self.gui.library_view.model().db
fmts = db.formats(book_id, index_is_id=True) or '' fmts = db.formats(book_id, index_is_id=True) or ''
fmts = [x.lower().strip() for x in fmts.split(',')] fmts = [x.lower().strip() for x in fmts.split(',')]

View File

@ -34,7 +34,7 @@ class HistoryAction(QAction):
class ViewAction(InterfaceAction): class ViewAction(InterfaceAction):
name = 'View' name = 'View'
action_spec = (_('View'), 'view.png', None, _('V')) action_spec = (_('View'), 'view.png', _('Read books'), _('V'))
action_type = 'current' action_type = 'current'
action_add_menu = True action_add_menu = True
action_menu_clone_qaction = True action_menu_clone_qaction = True

View File

@ -117,21 +117,30 @@ class ToolBar(QToolBar): # {{{
return ch return ch
# support drag&drop from/to library, from/to reader/card, enabled plugins # support drag&drop from/to library, from/to reader/card, enabled plugins
def check_iactions_for_drag(self, event, md, func):
if self.added_actions:
pos = event.pos()
for iac in self.gui.iactions.itervalues():
if iac.accepts_drops:
aa = iac.qaction
w = self.widgetForAction(aa)
m = aa.menu()
if (( (w is not None and w.geometry().contains(pos)) or
(m is not None and m.isVisible() and m.geometry().contains(pos)) ) and
getattr(iac, func)(event, md)):
return True
return False
def dragEnterEvent(self, event): def dragEnterEvent(self, event):
md = event.mimeData() md = event.mimeData()
if md.hasFormat("application/calibre+from_library") or \ if md.hasFormat("application/calibre+from_library") or \
md.hasFormat("application/calibre+from_device"): md.hasFormat("application/calibre+from_device"):
event.setDropAction(Qt.CopyAction) event.setDropAction(Qt.CopyAction)
event.accept() event.accept()
elif self.added_actions: return
for aa in self.added_actions:
if (getattr(aa.associated_interface_action, 'accepts_drops', False) and if self.check_iactions_for_drag(event, md, 'accept_enter_event'):
aa.menu().geometry().contains(event.pos())):
if aa.associated_interface_action.accept_enter_event(md):
event.accept() event.accept()
break
else:
event.ignore()
else: else:
event.ignore() event.ignore()
@ -152,15 +161,8 @@ class ToolBar(QToolBar): # {{{
event.acceptProposedAction() event.acceptProposedAction()
return return
if self.added_actions: if self.check_iactions_for_drag(event, md, 'accept_drag_move_event'):
for aa in self.added_actions:
if (getattr(aa.associated_interface_action, 'accepts_drops', False) and
aa.menu().geometry().contains(event.pos())):
if aa.associated_interface_action.accept_drag_move_event(md):
event.acceptProposedAction() event.acceptProposedAction()
break
else:
event.ignore()
else: else:
event.ignore() event.ignore()
@ -191,10 +193,7 @@ class ToolBar(QToolBar): # {{{
return return
# Give added_actions an opportunity to process the drag&drop event # Give added_actions an opportunity to process the drag&drop event
for aa in self.added_actions: if self.check_iactions_for_drag(event, data, 'drop_event'):
if (getattr(aa.associated_interface_action, 'accepts_drops', False) and
aa.menu().geometry().contains(event.pos())):
aa.associated_interface_action.drop_event(event)
event.accept() event.accept()
else: else:
event.ignore() event.ignore()

View File

@ -28,9 +28,10 @@ class BaseModel(QAbstractListModel):
def name_to_action(self, name, gui): def name_to_action(self, name, gui):
if name == 'Donate': if name == 'Donate':
return FakeAction('Donate', _('Donate'), 'donate.png', return FakeAction(
dont_add_to=frozenset(['context-menu', 'Donate', _('Donate'), 'donate.png', tooltip=
'context-menu-device'])) _('Donate to support the development of calibre'),
dont_add_to=frozenset(['context-menu', 'context-menu-device']))
if name == 'Location Manager': if name == 'Location Manager':
return FakeAction('Location Manager', _('Location Manager'), 'reader.png', return FakeAction('Location Manager', _('Location Manager'), 'reader.png',
_('Switch between library and device views'), _('Switch between library and device views'),
@ -247,6 +248,18 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.remove_action_button.clicked.connect(self.remove_action) self.remove_action_button.clicked.connect(self.remove_action)
self.action_up_button.clicked.connect(partial(self.move, -1)) self.action_up_button.clicked.connect(partial(self.move, -1))
self.action_down_button.clicked.connect(partial(self.move, 1)) self.action_down_button.clicked.connect(partial(self.move, 1))
self.all_actions.setMouseTracking(True)
self.current_actions.setMouseTracking(True)
self.all_actions.entered.connect(self.all_entered)
self.current_actions.entered.connect(self.current_entered)
def all_entered(self, index):
tt = self.all_actions.model().data(index, Qt.ToolTipRole).toString()
self.help_text.setText(tt)
def current_entered(self, index):
tt = self.current_actions.model().data(index, Qt.ToolTipRole).toString()
self.help_text.setText(tt)
def what_changed(self, idx): def what_changed(self, idx):
key = unicode(self.what.itemData(idx).toString()) key = unicode(self.what.itemData(idx).toString())
@ -264,7 +277,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
names = self.all_actions.model().names(x) names = self.all_actions.model().names(x)
if names: if names:
not_added = self.current_actions.model().add(names) not_added = self.current_actions.model().add(names)
ns = set([x.name for x in not_added]) ns = set([y.name for y in not_added])
added = set(names) - ns added = set(names) - ns
self.all_actions.model().remove(x, added) self.all_actions.model().remove(x, added)
if not_added: if not_added:
@ -283,7 +296,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
names = self.current_actions.model().names(x) names = self.current_actions.model().names(x)
if names: if names:
not_removed = self.current_actions.model().remove(x) not_removed = self.current_actions.model().remove(x)
ns = set([x.name for x in not_removed]) ns = set([y.name for y in not_removed])
removed = set(names) - ns removed = set(names) - ns
self.all_actions.model().add(removed) self.all_actions.model().add(removed)
if not_removed: if not_removed:

View File

@ -234,6 +234,13 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="help_text">
<property name="text">
<string/>
</property>
</widget>
</item>
<item> <item>
<widget class="QWidget" name="spacer_widget" native="true"> <widget class="QWidget" name="spacer_widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">

File diff suppressed because it is too large Load Diff