From a74d0546eb35071466fa2cc3ab628e7e750ab80f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 3 Aug 2017 11:25:45 +0530 Subject: [PATCH] Copy to library: Add a action to show a dialog that allows for easy selection of libraries for copy/move. Useful when there are a large number of libraries to choose from. Fixes #1706198 [[Enhancement] Provide users the ability to remove/hide popup menu option copy to library, leaving only Delete After Copy option visible](https://bugs.launchpad.net/calibre/+bug/1706198) --- src/calibre/gui2/actions/copy_to_library.py | 91 ++++++++++++++------- 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py index 3b66d44eee..0b9c4de68f 100644 --- a/src/calibre/gui2/actions/copy_to_library.py +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -12,8 +12,9 @@ from contextlib import closing from collections import defaultdict from PyQt5.Qt import ( - QToolButton, QDialog, QGridLayout, QIcon, QLabel, QDialogButtonBox, QApplication, - QFormLayout, QCheckBox, QWidget, QScrollArea, QVBoxLayout, Qt, QListWidgetItem, QListWidget) + QToolButton, QDialog, QGridLayout, QIcon, QLabel, QDialogButtonBox, + QApplication, QLineEdit, QHBoxLayout, QFormLayout, QCheckBox, QWidget, + QScrollArea, QVBoxLayout, Qt, QListWidgetItem, QListWidget, QSize) from calibre import as_unicode from calibre.constants import isosx @@ -22,10 +23,10 @@ from calibre.gui2.actions import InterfaceAction from calibre.gui2 import (error_dialog, Dispatcher, warning_dialog, gprefs, info_dialog, choose_dir) from calibre.gui2.dialogs.progress import ProgressDialog -from calibre.gui2.widgets import HistoryLineEdit +from calibre.gui2.widgets2 import Dialog from calibre.utils.config import prefs, tweaks from calibre.utils.date import now -from calibre.utils.icu import sort_key +from calibre.utils.icu import sort_key, numeric_sort_key def ask_about_cc_mismatch(gui, db, newdb, missing_cols, incompatible_cols): # {{{ @@ -257,28 +258,52 @@ class Worker(Thread): # {{{ # }}} -class ChooseLibrary(QDialog): # {{{ +class ChooseLibrary(Dialog): # {{{ - def __init__(self, parent): - super(ChooseLibrary, self).__init__(parent) - d = self - d.l = l = QGridLayout() - d.setLayout(l) - d.setWindowTitle(_('Choose library')) - la = d.la = QLabel(_('Library &path:')) - l.addWidget(la, 0, 0) - le = d.le = HistoryLineEdit(d) - le.initialize('choose_library_for_copy') - l.addWidget(le, 0, 1) + def __init__(self, parent, locations): + self.locations = locations + Dialog.__init__(self, _('Choose library'), 'copy_to_choose_library_dialog', parent) + self.resort() + self.current_changed() + + def resort(self): + if self.sort_alphabetically.isChecked(): + sorted_locations = sorted(self.locations, key=lambda (name, loc): numeric_sort_key(name)) + else: + sorted_locations = self.locations + self.items.clear() + for name, loc in sorted_locations: + i = QListWidgetItem(name, self.items) + i.setData(Qt.UserRole, loc) + self.items.setCurrentRow(0) + + def setup_ui(self): + self.l = l = QGridLayout(self) + self.items = i = QListWidget(self) + i.setSelectionMode(i.SingleSelection) + i.currentItemChanged.connect(self.current_changed) + l.addWidget(i) + self.v = v = QVBoxLayout() + l.addLayout(v, 0, 1) + self.sort_alphabetically = sa = QCheckBox(_('&Sort libraries alphabetically')) + v.addWidget(sa) + sa.setChecked(bool(gprefs.get('copy_to_library_choose_library_sort_alphabetically', True))) + sa.stateChanged.connect(self.resort) + sa.stateChanged.connect(lambda: gprefs.set('copy_to_library_choose_library_sort_alphabetically', bool(self.sort_alphabetically.isChecked()))) + la = self.la = QLabel(_('Library &path:')) + v.addWidget(la) + le = self.le = QLineEdit(self) la.setBuddy(le) - b = d.b = QToolButton(d) + b = self.b = QToolButton(self) b.setIcon(QIcon(I('document_open.png'))) b.setToolTip(_('Browse for library')) b.clicked.connect(self.browse) - l.addWidget(b, 0, 2) - self.bb = bb = QDialogButtonBox(QDialogButtonBox.Cancel) - bb.accepted.connect(self.accept) - bb.rejected.connect(self.reject) + h = QHBoxLayout() + h.addWidget(le), h.addWidget(b) + v.addLayout(h) + v.addStretch(10) + bb = self.bb + bb.setStandardButtons(QDialogButtonBox.Cancel) self.delete_after_copy = False b = bb.addButton(_('&Copy'), bb.AcceptRole) b.setIcon(QIcon(I('edit-copy.png'))) @@ -288,9 +313,16 @@ class ChooseLibrary(QDialog): # {{{ b2.setIcon(QIcon(I('edit-cut.png'))) b2.setToolTip(_('Copy to the specified library and delete from the current library')) b.setDefault(True) - l.addWidget(bb, 1, 0, 1, 3) - le.setMinimumWidth(350) - self.resize(self.sizeHint()) + l.addWidget(bb, 1, 0, 1, 2) + self.items.setFocus(Qt.OtherFocusReason) + + def sizeHint(self): + return QSize(800, 550) + + def current_changed(self): + i = self.items.currentItem() or self.items.item(0) + loc = i.data(Qt.UserRole) + self.le.setText(loc) def browse(self): d = choose_dir(self, 'choose_library_for_copy', @@ -396,9 +428,8 @@ class CopyToLibraryAction(InterfaceAction): return db = self.gui.library_view.model().db locations = list(self.stats.locations(db)) - if len(locations) > 50: - self.menu.addAction(_('Choose library by path...'), self.choose_library) - self.menu.addSeparator() + self.menu.addAction(_('Choose library...'), self.choose_library) + self.menu.addSeparator() for name, loc in locations: name = name.replace('&', '&&') self.menu.addAction(name, partial(self.copy_to_library, @@ -407,15 +438,15 @@ class CopyToLibraryAction(InterfaceAction): partial(self.copy_to_library, loc, delete_after=True)) self.menu.addSeparator() - if len(locations) <= 50: - self.menu.addAction(_('Choose library by path...'), self.choose_library) self.qaction.setVisible(bool(locations)) if isosx: # The cloned action has to have its menu updated self.qaction.changed.emit() def choose_library(self): - d = ChooseLibrary(self.gui) + db = self.gui.library_view.model().db + locations = list(self.stats.locations(db)) + d = ChooseLibrary(self.gui, locations) if d.exec_() == d.Accepted: path, delete_after = d.args if not path: