mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Nicer unified toolbar
This commit is contained in:
parent
93ece30686
commit
0ad6ab164f
@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
import os, sys
|
||||
from threading import RLock
|
||||
|
||||
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, QSize, \
|
||||
from PyQt4.Qt import QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt, \
|
||||
QByteArray, QTranslator, QCoreApplication, QThread, \
|
||||
QEvent, QTimer, pyqtSignal, QDate, QDesktopServices, \
|
||||
QFileDialog, QMessageBox, QPixmap, QFileIconProvider, \
|
||||
@ -33,10 +33,6 @@ def _config():
|
||||
help=_('Send file to storage card instead of main memory by default'))
|
||||
c.add_opt('confirm_delete', default=False,
|
||||
help=_('Confirm before deleting'))
|
||||
c.add_opt('toolbar_icon_size', default=QSize(48, 48),
|
||||
help=_('Toolbar icon size')) # value QVariant.toSize
|
||||
c.add_opt('show_text_in_toolbar', default=True,
|
||||
help=_('Show button labels in the toolbar'))
|
||||
c.add_opt('main_window_geometry', default=None,
|
||||
help=_('Main window geometry')) # value QVariant.toByteArray
|
||||
c.add_opt('new_version_notification', default=True,
|
||||
|
@ -638,7 +638,6 @@ class DeviceMixin(object): # {{{
|
||||
self.device_error_dialog = error_dialog(self, _('Error'),
|
||||
_('Error communicating with device'), ' ')
|
||||
self.device_error_dialog.setModal(Qt.NonModal)
|
||||
self.device_connected = None
|
||||
self.emailer = Emailer()
|
||||
self.emailer.start()
|
||||
self.device_manager = DeviceManager(Dispatcher(self.device_detected),
|
||||
@ -755,17 +754,14 @@ class DeviceMixin(object): # {{{
|
||||
self.device_manager.device.__class__.get_gui_name()+\
|
||||
_(' detected.'), 3000)
|
||||
self.device_connected = device_kind
|
||||
self.location_view.model().device_connected(self.device_manager.device)
|
||||
self.refresh_ondevice_info (device_connected = True, reset_only = True)
|
||||
else:
|
||||
self.device_connected = None
|
||||
self.status_bar.device_disconnected()
|
||||
self.location_view.model().update_devices()
|
||||
if self.current_view() != self.library_view:
|
||||
self.book_details.reset_info()
|
||||
self.location_view.setCurrentIndex(self.location_view.model().index(0))
|
||||
self.refresh_ondevice_info (device_connected = False)
|
||||
self.tool_bar.device_status_changed(bool(connected))
|
||||
self.location_manager.update_devices()
|
||||
self.refresh_ondevice_info(device_connected=False)
|
||||
|
||||
def info_read(self, job):
|
||||
'''
|
||||
@ -774,7 +770,8 @@ class DeviceMixin(object): # {{{
|
||||
if job.failed:
|
||||
return self.device_job_exception(job)
|
||||
info, cp, fs = job.result
|
||||
self.location_view.model().update_devices(cp, fs)
|
||||
self.location_manager.update_devices(cp, fs,
|
||||
self.device_manager.device.icon)
|
||||
self.status_bar.device_connected(info[0])
|
||||
self.device_manager.books(Dispatcher(self.metadata_downloaded))
|
||||
|
||||
@ -1076,9 +1073,9 @@ class DeviceMixin(object): # {{{
|
||||
dynamic.set('catalogs_to_be_synced', set([]))
|
||||
if files:
|
||||
remove = []
|
||||
space = { self.location_view.model().free[0] : None,
|
||||
self.location_view.model().free[1] : 'carda',
|
||||
self.location_view.model().free[2] : 'cardb' }
|
||||
space = { self.location_manager.free[0] : None,
|
||||
self.location_manager.free[1] : 'carda',
|
||||
self.location_manager.free[2] : 'cardb' }
|
||||
on_card = space.get(sorted(space.keys(), reverse=True)[0], None)
|
||||
self.upload_books(files, names, metadata,
|
||||
on_card=on_card,
|
||||
@ -1140,9 +1137,9 @@ class DeviceMixin(object): # {{{
|
||||
dynamic.set('news_to_be_synced', set([]))
|
||||
if config['upload_news_to_device'] and files:
|
||||
remove = ids if del_on_upload else []
|
||||
space = { self.location_view.model().free[0] : None,
|
||||
self.location_view.model().free[1] : 'carda',
|
||||
self.location_view.model().free[2] : 'cardb' }
|
||||
space = { self.location_manager.free[0] : None,
|
||||
self.location_manager.free[1] : 'carda',
|
||||
self.location_manager.free[2] : 'cardb' }
|
||||
on_card = space.get(sorted(space.keys(), reverse=True)[0], None)
|
||||
self.upload_books(files, names, metadata,
|
||||
on_card=on_card,
|
||||
@ -1263,7 +1260,8 @@ class DeviceMixin(object): # {{{
|
||||
self.device_job_exception(job)
|
||||
return
|
||||
cp, fs = job.result
|
||||
self.location_view.model().update_devices(cp, fs)
|
||||
self.location_manager.update_devices(cp, fs,
|
||||
self.device_manager.device.icon)
|
||||
# reset the views so that up-to-date info is shown. These need to be
|
||||
# here because the sony driver updates collections in sync_booklists
|
||||
self.memory_view.reset()
|
||||
|
@ -7,14 +7,13 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import functools, sys, os
|
||||
|
||||
from PyQt4.Qt import QMenu, Qt, pyqtSignal, QIcon, QStackedWidget, \
|
||||
QSize, QSizePolicy, QStatusBar, QUrl, QLabel, QFont
|
||||
from PyQt4.Qt import QMenu, Qt, QStackedWidget, \
|
||||
QSize, QSizePolicy, QStatusBar, QLabel, QFont
|
||||
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.constants import isosx, __appname__, preferred_encoding, \
|
||||
__version__
|
||||
from calibre.gui2 import config, is_widescreen, open_url
|
||||
from calibre.gui2 import config, is_widescreen
|
||||
from calibre.gui2.library.views import BooksView, DeviceBooksView
|
||||
from calibre.gui2.widgets import Splitter
|
||||
from calibre.gui2.tag_view import TagBrowserWidget
|
||||
@ -28,157 +27,6 @@ def partial(*args, **kwargs):
|
||||
_keep_refs.append(ans)
|
||||
return ans
|
||||
|
||||
class SaveMenu(QMenu): # {{{
|
||||
|
||||
save_fmt = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
||||
for ext in sorted(BOOK_EXTENSIONS):
|
||||
action = self.addAction(ext.upper())
|
||||
setattr(self, 'do_'+ext, partial(self.do, ext))
|
||||
action.triggered.connect(
|
||||
getattr(self, 'do_'+ext))
|
||||
|
||||
def do(self, ext, *args):
|
||||
self.save_fmt.emit(ext)
|
||||
|
||||
# }}}
|
||||
|
||||
class ToolbarMixin(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
self.action_help.triggered.connect(self.show_help)
|
||||
md = QMenu()
|
||||
md.addAction(_('Edit metadata individually'),
|
||||
partial(self.edit_metadata, False, bulk=False))
|
||||
md.addSeparator()
|
||||
md.addAction(_('Edit metadata in bulk'),
|
||||
partial(self.edit_metadata, False, bulk=True))
|
||||
md.addSeparator()
|
||||
md.addAction(_('Download metadata and covers'),
|
||||
partial(self.download_metadata, False, covers=True),
|
||||
Qt.ControlModifier+Qt.Key_D)
|
||||
md.addAction(_('Download only metadata'),
|
||||
partial(self.download_metadata, False, covers=False))
|
||||
md.addAction(_('Download only covers'),
|
||||
partial(self.download_metadata, False, covers=True,
|
||||
set_metadata=False, set_social_metadata=False))
|
||||
md.addAction(_('Download only social metadata'),
|
||||
partial(self.download_metadata, False, covers=False,
|
||||
set_metadata=False, set_social_metadata=True))
|
||||
self.metadata_menu = md
|
||||
|
||||
mb = QMenu()
|
||||
mb.addAction(_('Merge into first selected book - delete others'),
|
||||
self.merge_books)
|
||||
mb.addSeparator()
|
||||
mb.addAction(_('Merge into first selected book - keep others'),
|
||||
partial(self.merge_books, safe_merge=True))
|
||||
self.merge_menu = mb
|
||||
self.action_merge.setMenu(mb)
|
||||
md.addSeparator()
|
||||
md.addAction(self.action_merge)
|
||||
|
||||
self.add_menu = QMenu()
|
||||
self.add_menu.addAction(_('Add books from a single directory'),
|
||||
self.add_books)
|
||||
self.add_menu.addAction(_('Add books from directories, including '
|
||||
'sub-directories (One book per directory, assumes every ebook '
|
||||
'file is the same book in a different format)'),
|
||||
self.add_recursive_single)
|
||||
self.add_menu.addAction(_('Add books from directories, including '
|
||||
'sub directories (Multiple books per directory, assumes every '
|
||||
'ebook file is a different book)'), self.add_recursive_multiple)
|
||||
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
||||
'formats)'), self.add_empty)
|
||||
self.action_add.setMenu(self.add_menu)
|
||||
self.action_add.triggered.connect(self.add_books)
|
||||
self.action_del.triggered.connect(self.delete_books)
|
||||
self.action_edit.triggered.connect(self.edit_metadata)
|
||||
self.action_merge.triggered.connect(self.merge_books)
|
||||
|
||||
self.action_save.triggered.connect(self.save_to_disk)
|
||||
self.save_menu = QMenu()
|
||||
self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk,
|
||||
False))
|
||||
self.save_menu.addAction(_('Save to disk in a single directory'),
|
||||
partial(self.save_to_single_dir, False))
|
||||
self.save_menu.addAction(_('Save only %s format to disk')%
|
||||
prefs['output_format'].upper(),
|
||||
partial(self.save_single_format_to_disk, False))
|
||||
self.save_menu.addAction(
|
||||
_('Save only %s format to disk in a single directory')%
|
||||
prefs['output_format'].upper(),
|
||||
partial(self.save_single_fmt_to_single_dir, False))
|
||||
self.save_sub_menu = SaveMenu(self)
|
||||
self.save_menu.addMenu(self.save_sub_menu)
|
||||
self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)
|
||||
|
||||
self.action_view.triggered.connect(self.view_book)
|
||||
self.view_menu = QMenu()
|
||||
self.view_menu.addAction(_('View'), partial(self.view_book, False))
|
||||
ac = self.view_menu.addAction(_('View specific format'))
|
||||
ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
|
||||
self.action_view.setMenu(self.view_menu)
|
||||
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
|
||||
|
||||
self.delete_menu = QMenu()
|
||||
self.delete_menu.addAction(_('Remove selected books'), self.delete_books)
|
||||
self.delete_menu.addAction(
|
||||
_('Remove files of a specific format from selected books..'),
|
||||
self.delete_selected_formats)
|
||||
self.delete_menu.addAction(
|
||||
_('Remove all formats from selected books, except...'),
|
||||
self.delete_all_but_selected_formats)
|
||||
self.delete_menu.addAction(
|
||||
_('Remove covers from selected books'), self.delete_covers)
|
||||
self.delete_menu.addSeparator()
|
||||
self.delete_menu.addAction(
|
||||
_('Remove matching books from device'),
|
||||
self.remove_matching_books_from_device)
|
||||
self.action_del.setMenu(self.delete_menu)
|
||||
|
||||
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
||||
self.addAction(self.action_open_containing_folder)
|
||||
self.action_open_containing_folder.triggered.connect(self.view_folder)
|
||||
self.action_sync.setShortcut(Qt.Key_D)
|
||||
self.action_sync.setEnabled(True)
|
||||
self.create_device_menu()
|
||||
self.action_sync.triggered.connect(
|
||||
self._sync_action_triggered)
|
||||
|
||||
self.action_edit.setMenu(md)
|
||||
self.action_save.setMenu(self.save_menu)
|
||||
|
||||
cm = QMenu()
|
||||
cm.addAction(_('Convert individually'), partial(self.convert_ebook,
|
||||
False, bulk=False))
|
||||
cm.addAction(_('Bulk convert'),
|
||||
partial(self.convert_ebook, False, bulk=True))
|
||||
cm.addSeparator()
|
||||
ac = cm.addAction(
|
||||
_('Create catalog of books in your calibre library'))
|
||||
ac.triggered.connect(self.generate_catalog)
|
||||
self.action_convert.setMenu(cm)
|
||||
self.action_convert.triggered.connect(self.convert_ebook)
|
||||
self.convert_menu = cm
|
||||
|
||||
pm = QMenu()
|
||||
pm.addAction(QIcon(I('config.svg')), _('Preferences'), self.do_config)
|
||||
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
|
||||
self.run_wizard)
|
||||
self.action_preferences.setMenu(pm)
|
||||
self.preferences_menu = pm
|
||||
for x in (self.preferences_action, self.action_preferences):
|
||||
x.triggered.connect(self.do_config)
|
||||
|
||||
def show_help(self, *args):
|
||||
open_url(QUrl('http://calibre-ebook.com/user_manual'))
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
class LibraryViewMixin(object): # {{{
|
||||
|
||||
def __init__(self, db):
|
||||
|
@ -6,108 +6,105 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
from operator import attrgetter
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, QVariant, \
|
||||
QAbstractListModel, QFont, QApplication, QPalette, pyqtSignal, QToolButton, \
|
||||
QModelIndex, QListView, QAbstractButton, QPainter, QPixmap, QColor, \
|
||||
QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout
|
||||
from PyQt4.Qt import QIcon, Qt, QWidget, QAction, QToolBar, QSize, \
|
||||
pyqtSignal, QToolButton, \
|
||||
QObject, QVBoxLayout, QSizePolicy, QLabel, QHBoxLayout, QActionGroup, \
|
||||
QMenu, QUrl
|
||||
|
||||
from calibre.constants import __appname__, filesystem_encoding
|
||||
from calibre.constants import __appname__, isosx
|
||||
from calibre.gui2.search_box import SearchBox2, SavedSearchBox
|
||||
from calibre.gui2.throbber import ThrobbingButton
|
||||
from calibre.gui2 import NONE, config
|
||||
from calibre.gui2 import config, open_url
|
||||
from calibre.gui2.widgets import ComboBoxWithHelp
|
||||
from calibre import human_readable
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
|
||||
ICON_SIZE = 48
|
||||
|
||||
# Location View {{{
|
||||
class SaveMenu(QMenu): # {{{
|
||||
|
||||
class LocationModel(QAbstractListModel): # {{{
|
||||
|
||||
devicesChanged = pyqtSignal()
|
||||
save_fmt = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
QAbstractListModel.__init__(self, parent)
|
||||
self.icons = [QVariant(QIcon(I('library.png'))),
|
||||
QVariant(QIcon(I('reader.svg'))),
|
||||
QVariant(QIcon(I('sd.svg'))),
|
||||
QVariant(QIcon(I('sd.svg')))]
|
||||
self.text = [_('Library\n%d books'),
|
||||
_('Reader\n%s'),
|
||||
_('Card A\n%s'),
|
||||
_('Card B\n%s')]
|
||||
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
||||
for ext in sorted(BOOK_EXTENSIONS):
|
||||
action = self.addAction(ext.upper())
|
||||
setattr(self, 'do_'+ext, partial(self.do, ext))
|
||||
action.triggered.connect(
|
||||
getattr(self, 'do_'+ext))
|
||||
|
||||
def do(self, ext, *args):
|
||||
self.save_fmt.emit(ext)
|
||||
|
||||
# }}}
|
||||
|
||||
class LocationManager(QObject): # {{{
|
||||
|
||||
locations_changed = pyqtSignal()
|
||||
unmount_device = pyqtSignal()
|
||||
location_selected = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QObject.__init__(self, parent)
|
||||
self.free = [-1, -1, -1]
|
||||
self.count = 0
|
||||
self.highlight_row = 0
|
||||
self.library_tooltip = _('Click to see the books available on your computer')
|
||||
self.tooltips = [
|
||||
self.library_tooltip,
|
||||
_('Click to see the books in the main memory of your reader'),
|
||||
_('Click to see the books on storage card A in your reader'),
|
||||
_('Click to see the books on storage card B in your reader')
|
||||
]
|
||||
self.location_actions = QActionGroup(self)
|
||||
self.location_actions.setExclusive(True)
|
||||
self.current_location = 'library'
|
||||
self._mem = []
|
||||
self.tooltips = {}
|
||||
|
||||
def database_changed(self, db):
|
||||
lp = db.library_path
|
||||
if not isinstance(lp, unicode):
|
||||
lp = lp.decode(filesystem_encoding, 'replace')
|
||||
self.tooltips[0] = self.library_tooltip + '\n\n' + \
|
||||
_('Books located at') + ' ' + lp
|
||||
self.dataChanged.emit(self.index(0), self.index(0))
|
||||
def ac(name, text, icon, tooltip):
|
||||
icon = QIcon(I(icon))
|
||||
ac = self.location_actions.addAction(icon, text)
|
||||
setattr(self, 'location_'+name, ac)
|
||||
ac.setAutoRepeat(False)
|
||||
ac.setCheckable(True)
|
||||
receiver = partial(self._location_selected, name)
|
||||
ac.triggered.connect(receiver)
|
||||
self.tooltips[name] = tooltip
|
||||
if name != 'library':
|
||||
m = QMenu(parent)
|
||||
self._mem.append(m)
|
||||
a = m.addAction(icon, tooltip)
|
||||
a.triggered.connect(receiver)
|
||||
self._mem.append(a)
|
||||
a = m.addAction(QIcon(I('eject.svg')), _('Eject this device'))
|
||||
a.triggered.connect(self._eject_requested)
|
||||
ac.setMenu(m)
|
||||
self._mem.append(a)
|
||||
else:
|
||||
ac.setToolTip(tooltip)
|
||||
|
||||
def rowCount(self, *args):
|
||||
return 1 + len([i for i in self.free if i >= 0])
|
||||
return ac
|
||||
|
||||
def get_device_row(self, row):
|
||||
if row == 2 and self.free[1] == -1 and self.free[2] > -1:
|
||||
row = 3
|
||||
return row
|
||||
ac('library', _('Library'), 'lt.png',
|
||||
_('Show books in calibre library'))
|
||||
ac('main', _('Main'), 'reader.svg',
|
||||
_('Show books in the main memory of the device'))
|
||||
ac('carda', _('Card A'), 'sd.svg',
|
||||
_('Show books in storage card A'))
|
||||
ac('cardb', _('Card B'), 'sd.svg',
|
||||
_('Show books in storage card B'))
|
||||
|
||||
def get_tooltip(self, row, drow):
|
||||
ans = self.tooltips[row]
|
||||
if row > 0:
|
||||
fs = self.free[drow-1]
|
||||
if fs > -1:
|
||||
ans += '\n\n%s '%(human_readable(fs)) + _('free')
|
||||
return ans
|
||||
def _location_selected(self, location, *args):
|
||||
if location != self.current_location and hasattr(self,
|
||||
'location_'+location):
|
||||
self.current_location = location
|
||||
self.location_selected.emit(location)
|
||||
getattr(self, 'location_'+location).setChecked(True)
|
||||
|
||||
def data(self, index, role):
|
||||
row = index.row()
|
||||
drow = self.get_device_row(row)
|
||||
data = NONE
|
||||
if role == Qt.DisplayRole:
|
||||
text = self.text[drow]%(human_readable(self.free[drow-1])) if row > 0 \
|
||||
else self.text[drow]%self.count
|
||||
data = QVariant(text)
|
||||
elif role == Qt.DecorationRole:
|
||||
data = self.icons[drow]
|
||||
elif role in (Qt.ToolTipRole, Qt.StatusTipRole):
|
||||
ans = self.get_tooltip(row, drow)
|
||||
data = QVariant(ans)
|
||||
elif role == Qt.SizeHintRole:
|
||||
data = QVariant(QSize(155, 90))
|
||||
elif role == Qt.FontRole:
|
||||
font = QFont('monospace')
|
||||
font.setBold(row == self.highlight_row)
|
||||
data = QVariant(font)
|
||||
elif role == Qt.ForegroundRole and row == self.highlight_row:
|
||||
return QVariant(QApplication.palette().brush(
|
||||
QPalette.HighlightedText))
|
||||
elif role == Qt.BackgroundRole and row == self.highlight_row:
|
||||
return QVariant(QApplication.palette().brush(
|
||||
QPalette.Highlight))
|
||||
def _eject_requested(self, *args):
|
||||
self.unmount_device.emit()
|
||||
|
||||
return data
|
||||
|
||||
def device_connected(self, dev):
|
||||
self.icons[1] = QIcon(dev.icon)
|
||||
self.dataChanged.emit(self.index(1), self.index(1))
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
return NONE
|
||||
|
||||
def update_devices(self, cp=(None, None), fs=[-1, -1, -1]):
|
||||
def update_devices(self, cp=(None, None), fs=[-1, -1, -1], icon=None):
|
||||
if icon is None:
|
||||
icon = I('reader.svg')
|
||||
self.location_main.setIcon(QIcon(icon))
|
||||
had_device = self.has_device
|
||||
if cp is None:
|
||||
cp = (None, None)
|
||||
if isinstance(cp, (str, unicode)):
|
||||
@ -120,137 +117,34 @@ class LocationModel(QAbstractListModel): # {{{
|
||||
cpa, cpb = cp
|
||||
self.free[1] = fs[1] if fs[1] is not None and cpa is not None else -1
|
||||
self.free[2] = fs[2] if fs[2] is not None and cpb is not None else -1
|
||||
self.reset()
|
||||
self.devicesChanged.emit()
|
||||
self.update_tooltips()
|
||||
if self.has_device != had_device:
|
||||
self.locations_changed.emit()
|
||||
if not self.has_device:
|
||||
self.location_library.trigger()
|
||||
|
||||
def location_changed(self, row):
|
||||
self.highlight_row = row
|
||||
self.dataChanged.emit(
|
||||
self.index(0), self.index(self.rowCount(QModelIndex())-1))
|
||||
def update_tooltips(self):
|
||||
for i, loc in enumerate(('main', 'carda', 'cardb')):
|
||||
t = self.tooltips[loc]
|
||||
if self.free[i] > -1:
|
||||
t += u'\n\n%s '%human_readable(self.free[i]) + _('available')
|
||||
ac = getattr(self, 'location_'+loc)
|
||||
ac.setToolTip(t)
|
||||
ac.setWhatsThis(t)
|
||||
ac.setStatusTip(t)
|
||||
|
||||
def location_for_row(self, row):
|
||||
if row == 0: return 'library'
|
||||
if row == 1: return 'main'
|
||||
if row == 3: return 'cardb'
|
||||
return 'carda' if self.free[1] > -1 else 'cardb'
|
||||
|
||||
# }}}
|
||||
|
||||
class LocationView(QListView):
|
||||
|
||||
umount_device = pyqtSignal()
|
||||
location_selected = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
QListView.__init__(self, parent)
|
||||
self.setModel(LocationModel(self))
|
||||
self.reset()
|
||||
self.currentChanged = self.current_changed
|
||||
|
||||
self.eject_button = EjectButton(self)
|
||||
self.eject_button.hide()
|
||||
|
||||
self.entered.connect(self.item_entered)
|
||||
self.viewportEntered.connect(self.viewport_entered)
|
||||
self.eject_button.clicked.connect(self.eject_clicked)
|
||||
self.model().devicesChanged.connect(self.eject_button.hide)
|
||||
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
|
||||
QSizePolicy.Expanding))
|
||||
self.setMouseTracking(True)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setEditTriggers(self.NoEditTriggers)
|
||||
self.setTabKeyNavigation(True)
|
||||
self.setProperty("showDropIndicator", True)
|
||||
self.setSelectionMode(self.SingleSelection)
|
||||
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
self.setMovement(self.Static)
|
||||
self.setFlow(self.LeftToRight)
|
||||
self.setGridSize(QSize(175, ICON_SIZE))
|
||||
self.setViewMode(self.ListMode)
|
||||
self.setWordWrap(True)
|
||||
self.setObjectName("location_view")
|
||||
self.setMaximumSize(QSize(600, ICON_SIZE+16))
|
||||
self.setMinimumWidth(400)
|
||||
|
||||
def eject_clicked(self, *args):
|
||||
self.umount_device.emit()
|
||||
|
||||
def count_changed(self, new_count):
|
||||
self.model().count = new_count
|
||||
self.model().reset()
|
||||
|
||||
@property
|
||||
def book_count(self):
|
||||
return self.model().count
|
||||
|
||||
def current_changed(self, current, previous):
|
||||
if current.isValid():
|
||||
i = current.row()
|
||||
location = self.model().location_for_row(i)
|
||||
self.location_selected.emit(location)
|
||||
self.model().location_changed(i)
|
||||
|
||||
def location_changed(self, row):
|
||||
if 0 <= row and row <= 3:
|
||||
self.model().location_changed(row)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.unsetCursor()
|
||||
self.eject_button.hide()
|
||||
|
||||
def item_entered(self, location):
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
self.eject_button.hide()
|
||||
|
||||
if location.row() == 1:
|
||||
rect = self.visualRect(location)
|
||||
|
||||
self.eject_button.resize(rect.height()/2, rect.height()/2)
|
||||
|
||||
x, y = rect.left(), rect.top()
|
||||
x = x + (rect.width() - self.eject_button.width() - 2)
|
||||
y += 6
|
||||
|
||||
self.eject_button.move(x, y)
|
||||
self.eject_button.show()
|
||||
|
||||
def viewport_entered(self):
|
||||
self.unsetCursor()
|
||||
self.eject_button.hide()
|
||||
|
||||
|
||||
class EjectButton(QAbstractButton):
|
||||
|
||||
def __init__(self, parent):
|
||||
QAbstractButton.__init__(self, parent)
|
||||
self.mouse_over = False
|
||||
self.setMouseTracking(True)
|
||||
|
||||
def enterEvent(self, event):
|
||||
self.mouse_over = True
|
||||
QAbstractButton.enterEvent(self, event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.mouse_over = False
|
||||
QAbstractButton.leaveEvent(self, event)
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QPainter(self)
|
||||
painter.setClipRect(event.rect())
|
||||
image = QPixmap(I('eject')).scaledToHeight(event.rect().height(),
|
||||
Qt.SmoothTransformation)
|
||||
|
||||
if not self.mouse_over:
|
||||
alpha_mask = QPixmap(image.width(), image.height())
|
||||
color = QColor(128, 128, 128)
|
||||
alpha_mask.fill(color)
|
||||
image.setAlphaChannel(alpha_mask)
|
||||
|
||||
painter.drawPixmap(0, 0, image)
|
||||
|
||||
|
||||
def has_device(self):
|
||||
return max(self.free) > -1
|
||||
|
||||
@property
|
||||
def available_actions(self):
|
||||
ans = [self.location_library]
|
||||
for i, loc in enumerate(('main', 'carda', 'cardb')):
|
||||
if self.free[i] > -1:
|
||||
ans.append(getattr(self, 'location_'+loc))
|
||||
return ans
|
||||
|
||||
# }}}
|
||||
|
||||
@ -326,7 +220,7 @@ class SearchBar(QWidget): # {{{
|
||||
|
||||
class ToolBar(QToolBar): # {{{
|
||||
|
||||
def __init__(self, actions, donate, location_view, parent=None):
|
||||
def __init__(self, actions, donate, location_manager, parent=None):
|
||||
QToolBar.__init__(self, parent)
|
||||
self.setContextMenuPolicy(Qt.PreventContextMenu)
|
||||
self.setMovable(False)
|
||||
@ -335,11 +229,12 @@ class ToolBar(QToolBar): # {{{
|
||||
self.setAllowedAreas(Qt.TopToolBarArea|Qt.BottomToolBarArea)
|
||||
self.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
|
||||
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
self.setStyleSheet('QToolButton:checked { font-weight: bold }')
|
||||
|
||||
self.showing_device = False
|
||||
self.all_actions = actions
|
||||
self.donate = donate
|
||||
self.location_view = location_view
|
||||
self.location_manager = location_manager
|
||||
self.location_manager.locations_changed.connect(self.build_bar)
|
||||
self.d_widget = QWidget()
|
||||
self.d_widget.setLayout(QVBoxLayout())
|
||||
self.d_widget.layout().addWidget(donate)
|
||||
@ -350,40 +245,45 @@ class ToolBar(QToolBar): # {{{
|
||||
def contextMenuEvent(self, *args):
|
||||
pass
|
||||
|
||||
def device_status_changed(self, connected):
|
||||
self.showing_device = connected
|
||||
self.build_bar()
|
||||
|
||||
def build_bar(self):
|
||||
order_field = 'device' if self.showing_device else 'normal'
|
||||
showing_device = self.location_manager.has_device
|
||||
order_field = 'device' if showing_device else 'normal'
|
||||
o = attrgetter(order_field+'_order')
|
||||
sepvals = [2] if self.showing_device else [1]
|
||||
sepvals = [2] if showing_device else [1]
|
||||
sepvals += [3]
|
||||
actions = [x for x in self.all_actions if o(x) > -1]
|
||||
actions.sort(cmp=lambda x,y : cmp(o(x), o(y)))
|
||||
self.clear()
|
||||
for x in actions:
|
||||
self.addAction(x)
|
||||
ch = self.widgetForAction(x)
|
||||
|
||||
|
||||
def setup_tool_button(ac):
|
||||
ch = self.widgetForAction(ac)
|
||||
ch.setCursor(Qt.PointingHandCursor)
|
||||
ch.setAutoRaise(True)
|
||||
|
||||
if x.action_name == 'choose_library':
|
||||
self.location_action = self.addWidget(self.location_view)
|
||||
self.choose_action = x
|
||||
if config['show_donate_button']:
|
||||
self.addWidget(self.d_widget)
|
||||
if x.action_name not in ('choose_library', 'help'):
|
||||
if ac.menu() is not None:
|
||||
ch.setPopupMode(ch.MenuButtonPopup)
|
||||
|
||||
for x in actions:
|
||||
self.addAction(x)
|
||||
setup_tool_button(x)
|
||||
|
||||
if x.action_name == 'choose_library':
|
||||
self.choose_action = x
|
||||
if showing_device:
|
||||
self.addSeparator()
|
||||
for ac in self.location_manager.available_actions:
|
||||
self.addAction(ac)
|
||||
setup_tool_button(ac)
|
||||
self.addSeparator()
|
||||
self.location_manager.location_library.trigger()
|
||||
elif config['show_donate_button']:
|
||||
self.addWidget(self.d_widget)
|
||||
|
||||
for x in actions:
|
||||
if x.separator_before in sepvals:
|
||||
self.insertSeparator(x)
|
||||
|
||||
|
||||
self.location_action.setVisible(self.showing_device)
|
||||
self.choose_action.setVisible(not self.showing_device)
|
||||
self.choose_action.setVisible(not showing_device)
|
||||
|
||||
def count_changed(self, new_count):
|
||||
text = _('%d books')%new_count
|
||||
@ -397,6 +297,9 @@ class ToolBar(QToolBar): # {{{
|
||||
self.setToolButtonStyle(style)
|
||||
QToolBar.resizeEvent(self, ev)
|
||||
|
||||
def database_changed(self, db):
|
||||
pass
|
||||
|
||||
# }}}
|
||||
|
||||
class Action(QAction):
|
||||
@ -405,6 +308,7 @@ class Action(QAction):
|
||||
class MainWindowMixin(object):
|
||||
|
||||
def __init__(self):
|
||||
self.device_connected = None
|
||||
self.setObjectName('MainWindow')
|
||||
self.setWindowIcon(QIcon(I('library.png')))
|
||||
self.setWindowTitle(__appname__)
|
||||
@ -417,9 +321,23 @@ class MainWindowMixin(object):
|
||||
self.resize(1012, 740)
|
||||
self.donate_button = ThrobbingButton(self.centralwidget)
|
||||
self.donate_button.set_normal_icon_size(ICON_SIZE, ICON_SIZE)
|
||||
self.location_manager = LocationManager(self)
|
||||
|
||||
# Actions {{{
|
||||
all_actions = self.setup_actions()
|
||||
|
||||
self.search_bar = SearchBar(self)
|
||||
self.tool_bar = ToolBar(all_actions, self.donate_button,
|
||||
self.location_manager, self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
||||
|
||||
l = self.centralwidget.layout()
|
||||
l.addWidget(self.search_bar)
|
||||
|
||||
|
||||
def read_toolbar_settings(self):
|
||||
pass
|
||||
|
||||
def setup_actions(self): # {{{
|
||||
all_actions = []
|
||||
|
||||
def ac(normal_order, device_order, separator_before,
|
||||
@ -467,17 +385,135 @@ class MainWindowMixin(object):
|
||||
ac(-1, -1, 0, 'books_with_the_same_tags', _('Books with the same tags'),
|
||||
'tags.svg')
|
||||
|
||||
# }}}
|
||||
self.action_help.triggered.connect(self.show_help)
|
||||
md = QMenu()
|
||||
md.addAction(_('Edit metadata individually'),
|
||||
partial(self.edit_metadata, False, bulk=False))
|
||||
md.addSeparator()
|
||||
md.addAction(_('Edit metadata in bulk'),
|
||||
partial(self.edit_metadata, False, bulk=True))
|
||||
md.addSeparator()
|
||||
md.addAction(_('Download metadata and covers'),
|
||||
partial(self.download_metadata, False, covers=True),
|
||||
Qt.ControlModifier+Qt.Key_D)
|
||||
md.addAction(_('Download only metadata'),
|
||||
partial(self.download_metadata, False, covers=False))
|
||||
md.addAction(_('Download only covers'),
|
||||
partial(self.download_metadata, False, covers=True,
|
||||
set_metadata=False, set_social_metadata=False))
|
||||
md.addAction(_('Download only social metadata'),
|
||||
partial(self.download_metadata, False, covers=False,
|
||||
set_metadata=False, set_social_metadata=True))
|
||||
self.metadata_menu = md
|
||||
|
||||
self.location_view = LocationView(self.centralwidget)
|
||||
self.search_bar = SearchBar(self)
|
||||
self.tool_bar = ToolBar(all_actions, self.donate_button, self.location_view, self)
|
||||
self.addToolBar(Qt.TopToolBarArea, self.tool_bar)
|
||||
mb = QMenu()
|
||||
mb.addAction(_('Merge into first selected book - delete others'),
|
||||
self.merge_books)
|
||||
mb.addSeparator()
|
||||
mb.addAction(_('Merge into first selected book - keep others'),
|
||||
partial(self.merge_books, safe_merge=True))
|
||||
self.merge_menu = mb
|
||||
self.action_merge.setMenu(mb)
|
||||
md.addSeparator()
|
||||
md.addAction(self.action_merge)
|
||||
|
||||
l = self.centralwidget.layout()
|
||||
l.addWidget(self.search_bar)
|
||||
self.add_menu = QMenu()
|
||||
self.add_menu.addAction(_('Add books from a single directory'),
|
||||
self.add_books)
|
||||
self.add_menu.addAction(_('Add books from directories, including '
|
||||
'sub-directories (One book per directory, assumes every ebook '
|
||||
'file is the same book in a different format)'),
|
||||
self.add_recursive_single)
|
||||
self.add_menu.addAction(_('Add books from directories, including '
|
||||
'sub directories (Multiple books per directory, assumes every '
|
||||
'ebook file is a different book)'), self.add_recursive_multiple)
|
||||
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
||||
'formats)'), self.add_empty)
|
||||
self.action_add.setMenu(self.add_menu)
|
||||
self.action_add.triggered.connect(self.add_books)
|
||||
self.action_del.triggered.connect(self.delete_books)
|
||||
self.action_edit.triggered.connect(self.edit_metadata)
|
||||
self.action_merge.triggered.connect(self.merge_books)
|
||||
|
||||
self.action_save.triggered.connect(self.save_to_disk)
|
||||
self.save_menu = QMenu()
|
||||
self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk,
|
||||
False))
|
||||
self.save_menu.addAction(_('Save to disk in a single directory'),
|
||||
partial(self.save_to_single_dir, False))
|
||||
self.save_menu.addAction(_('Save only %s format to disk')%
|
||||
prefs['output_format'].upper(),
|
||||
partial(self.save_single_format_to_disk, False))
|
||||
self.save_menu.addAction(
|
||||
_('Save only %s format to disk in a single directory')%
|
||||
prefs['output_format'].upper(),
|
||||
partial(self.save_single_fmt_to_single_dir, False))
|
||||
self.save_sub_menu = SaveMenu(self)
|
||||
self.save_menu.addMenu(self.save_sub_menu)
|
||||
self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)
|
||||
|
||||
self.action_view.triggered.connect(self.view_book)
|
||||
self.view_menu = QMenu()
|
||||
self.view_menu.addAction(_('View'), partial(self.view_book, False))
|
||||
ac = self.view_menu.addAction(_('View specific format'))
|
||||
ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
|
||||
self.action_view.setMenu(self.view_menu)
|
||||
ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
|
||||
|
||||
self.delete_menu = QMenu()
|
||||
self.delete_menu.addAction(_('Remove selected books'), self.delete_books)
|
||||
self.delete_menu.addAction(
|
||||
_('Remove files of a specific format from selected books..'),
|
||||
self.delete_selected_formats)
|
||||
self.delete_menu.addAction(
|
||||
_('Remove all formats from selected books, except...'),
|
||||
self.delete_all_but_selected_formats)
|
||||
self.delete_menu.addAction(
|
||||
_('Remove covers from selected books'), self.delete_covers)
|
||||
self.delete_menu.addSeparator()
|
||||
self.delete_menu.addAction(
|
||||
_('Remove matching books from device'),
|
||||
self.remove_matching_books_from_device)
|
||||
self.action_del.setMenu(self.delete_menu)
|
||||
|
||||
self.action_open_containing_folder.setShortcut(Qt.Key_O)
|
||||
self.addAction(self.action_open_containing_folder)
|
||||
self.action_open_containing_folder.triggered.connect(self.view_folder)
|
||||
self.action_sync.setShortcut(Qt.Key_D)
|
||||
self.action_sync.setEnabled(True)
|
||||
self.create_device_menu()
|
||||
self.action_sync.triggered.connect(
|
||||
self._sync_action_triggered)
|
||||
|
||||
self.action_edit.setMenu(md)
|
||||
self.action_save.setMenu(self.save_menu)
|
||||
|
||||
cm = QMenu()
|
||||
cm.addAction(_('Convert individually'), partial(self.convert_ebook,
|
||||
False, bulk=False))
|
||||
cm.addAction(_('Bulk convert'),
|
||||
partial(self.convert_ebook, False, bulk=True))
|
||||
cm.addSeparator()
|
||||
ac = cm.addAction(
|
||||
_('Create catalog of books in your calibre library'))
|
||||
ac.triggered.connect(self.generate_catalog)
|
||||
self.action_convert.setMenu(cm)
|
||||
self.action_convert.triggered.connect(self.convert_ebook)
|
||||
self.convert_menu = cm
|
||||
|
||||
pm = QMenu()
|
||||
pm.addAction(QIcon(I('config.svg')), _('Preferences'), self.do_config)
|
||||
pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
|
||||
self.run_wizard)
|
||||
self.action_preferences.setMenu(pm)
|
||||
self.preferences_menu = pm
|
||||
for x in (self.preferences_action, self.action_preferences):
|
||||
x.triggered.connect(self.do_config)
|
||||
|
||||
return all_actions
|
||||
# }}}
|
||||
|
||||
def show_help(self, *args):
|
||||
open_url(QUrl('http://calibre-ebook.com/user_manual'))
|
||||
|
||||
|
||||
def read_toolbar_settings(self):
|
||||
pass
|
||||
|
||||
|
@ -12,7 +12,7 @@ __docformat__ = 'restructuredtext en'
|
||||
import collections, os, sys, textwrap, time
|
||||
from Queue import Queue, Empty
|
||||
from threading import Thread
|
||||
from PyQt4.Qt import Qt, SIGNAL, QObject, QTimer, \
|
||||
from PyQt4.Qt import Qt, SIGNAL, QTimer, \
|
||||
QPixmap, QMenu, QIcon, pyqtSignal, \
|
||||
QDialog, \
|
||||
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
|
||||
@ -38,7 +38,7 @@ from calibre.gui2.dialogs.config import ConfigDialog
|
||||
|
||||
from calibre.gui2.dialogs.book_info import BookInfo
|
||||
from calibre.library.database2 import LibraryDatabase2
|
||||
from calibre.gui2.init import ToolbarMixin, LibraryViewMixin, LayoutMixin
|
||||
from calibre.gui2.init import LibraryViewMixin, LayoutMixin
|
||||
from calibre.gui2.search_box import SearchBoxMixin, SavedSearchBoxMixin
|
||||
from calibre.gui2.search_restriction_mixin import SearchRestrictionMixin
|
||||
from calibre.gui2.tag_view import TagBrowserMixin
|
||||
@ -91,7 +91,7 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{
|
||||
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin, SearchBoxMixin,
|
||||
SavedSearchBoxMixin, SearchRestrictionMixin, LayoutMixin, UpdateMixin,
|
||||
AnnotationsAction, AddAction, DeleteAction,
|
||||
@ -192,21 +192,14 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
####################### Start spare job server ########################
|
||||
QTimer.singleShot(1000, self.add_spare_server)
|
||||
|
||||
####################### Location View ########################
|
||||
QObject.connect(self.location_view,
|
||||
SIGNAL('location_selected(PyQt_PyObject)'),
|
||||
self.location_selected)
|
||||
QObject.connect(self.location_view,
|
||||
SIGNAL('umount_device()'),
|
||||
self.device_manager.umount_device)
|
||||
####################### Location Manager ########################
|
||||
self.location_manager.location_selected.connect(self.location_selected)
|
||||
self.location_manager.unmount_device.connect(self.device_manager.umount_device)
|
||||
self.eject_action.triggered.connect(self.device_manager.umount_device)
|
||||
|
||||
#################### Update notification ###################
|
||||
UpdateMixin.__init__(self, opts)
|
||||
|
||||
####################### Setup Toolbar #####################
|
||||
ToolbarMixin.__init__(self)
|
||||
|
||||
####################### Search boxes ########################
|
||||
SavedSearchBoxMixin.__init__(self)
|
||||
SearchBoxMixin.__init__(self)
|
||||
@ -218,7 +211,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
|
||||
if self.system_tray_icon.isVisible() and opts.start_in_tray:
|
||||
self.hide_windows()
|
||||
for t in (self.location_view, self.tool_bar):
|
||||
for t in (self.tool_bar, ):
|
||||
self.library_view.model().count_changed_signal.connect \
|
||||
(t.count_changed)
|
||||
if not gprefs.get('quick_start_guide_added', False):
|
||||
@ -235,8 +228,8 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, ToolbarMixin, # {{{
|
||||
self.db_images.reset()
|
||||
|
||||
self.library_view.model().count_changed()
|
||||
self.location_view.model().database_changed(self.library_view.model().db)
|
||||
self.library_view.model().database_changed.connect(self.location_view.model().database_changed,
|
||||
self.tool_bar.database_changed(self.library_view.model().db)
|
||||
self.library_view.model().database_changed.connect(self.tool_bar.database_changed,
|
||||
type=Qt.QueuedConnection)
|
||||
|
||||
########################### Tags Browser ##############################
|
||||
|
Loading…
x
Reference in New Issue
Block a user