From 45d20f7d7c7d20dfc585182c0630b6b2f2810689 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 11:19:35 -0600 Subject: [PATCH 01/18] Show current library in title bar --- src/calibre/gui2/actions/__init__.py | 18 ++++++++++++++++-- src/calibre/gui2/actions/choose_library.py | 13 ++++++++++++- src/calibre/gui2/actions/fetch_news.py | 7 ++++++- src/calibre/gui2/ui.py | 14 ++++++++++---- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index c4d755aaea..bce1f283aa 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -26,8 +26,8 @@ class InterfaceAction(QObject): If two :class:`InterfaceAction` objects have the same name, the one with higher priority takes precedence. - Sub-classes should implement the :meth:`genesis` and - :meth:`location_selected` methods. + Sub-classes should implement the :meth:`genesis`, :meth:`library_moved`, + :meth:`location_selected` and :meth:`initialization_complete` methods. Once initialized, this plugin has access to the main calibre GUI via the :attr:`gui` member. You can access other plugins by name, for example:: @@ -108,3 +108,17 @@ class InterfaceAction(QObject): ''' pass + def library_changed(self, db): + ''' + Called whenever the current library is changed. + + :param db: The LibraryDatabase corresponding to the current library. + ''' + pass + + def initialization_complete(self): + ''' + Called once per action when the initialization of the main GUI is + completed. + ''' + pass diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index 52485c91b0..7bdbcf748a 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -97,10 +97,21 @@ class ChooseLibraryAction(InterfaceAction): ac.triggered.connect(partial(self.qs_requested, i)) self.choose_menu.addAction(ac) - def library_used(self, db): + def library_name(self): + db = self.gui.library_view.model().db + path = db.library_path + if isbytestring(path): + path = path.decode(filesystem_encoding) + path = path.replace(os.sep, '/') + return self.stats.pretty(path) + + def library_changed(self, db): self.stats.library_used(db) self.build_menus() + def initialization_complete(self): + self.library_changed(self.gui.library_view.model().db) + def build_menus(self): db = self.gui.library_view.model().db locations = list(self.stats.locations(db)) diff --git a/src/calibre/gui2/actions/fetch_news.py b/src/calibre/gui2/actions/fetch_news.py index bf44f5ab0e..b2893e0834 100644 --- a/src/calibre/gui2/actions/fetch_news.py +++ b/src/calibre/gui2/actions/fetch_news.py @@ -31,7 +31,12 @@ class FetchNewsAction(InterfaceAction): self.qaction.setMenu(self.scheduler.news_menu) self.qaction.triggered.connect( self.scheduler.show_dialog) - self.database_changed = self.scheduler.database_changed + + def library_changed(self, db): + self.scheduler.database_changed(db) + + def initialization_complete(self): + self.connect_scheduler() def connect_scheduler(self): self.scheduler.delete_old_news.connect( diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 820656d0e2..01e9d959f9 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -249,9 +249,10 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ self.read_settings() self.finalize_layout() self.donate_button.start_animation() + self.set_window_title() - self.iactions['Fetch News'].connect_scheduler() - self.iactions['Choose Library'].library_used(self.library_view.model().db) + for ac in self.iactions.values(): + ac.initialization_complete() def start_content_server(self): from calibre.library.server.main import start_threaded_server @@ -367,9 +368,14 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ self.saved_search.clear_to_help() self.book_details.reset_info() self.library_view.model().count_changed() - self.iactions['Fetch News'].database_changed(db) prefs['library_path'] = self.library_path - self.iactions['Choose Library'].library_used(self.library_view.model().db) + db = self.library_view.model().db + for action in self.iactions.values(): + action.library_changed(db) + self.set_window_title() + + def set_window_title(self): + self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name()) def location_selected(self, location): From 51c56af0797a967b8b426b0e4f1135cb3e19bbff Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 11:25:19 -0600 Subject: [PATCH 02/18] Add hooks to GUI plugins for the shutdown process --- src/calibre/gui2/actions/__init__.py | 14 +++++++++++++- src/calibre/gui2/ui.py | 6 +++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/actions/__init__.py b/src/calibre/gui2/actions/__init__.py index bce1f283aa..9c20f2adef 100644 --- a/src/calibre/gui2/actions/__init__.py +++ b/src/calibre/gui2/actions/__init__.py @@ -27,7 +27,8 @@ class InterfaceAction(QObject): priority takes precedence. Sub-classes should implement the :meth:`genesis`, :meth:`library_moved`, - :meth:`location_selected` and :meth:`initialization_complete` methods. + :meth:`location_selected` :meth:`shutting_down` + and :meth:`initialization_complete` methods. Once initialized, this plugin has access to the main calibre GUI via the :attr:`gui` member. You can access other plugins by name, for example:: @@ -122,3 +123,14 @@ class InterfaceAction(QObject): completed. ''' pass + + def shutting_down(self): + ''' + Called once per plugin when the main GUI is in the process of shutting + down. Release any used resources, but try not to block the shutdown for + long periods of time. + + :return: False to halt the shutdown. You are responsible for telling + the user why the shutdown was halted. + ''' + return True diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 01e9d959f9..1b918e679e 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -352,8 +352,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ def booklists(self): return self.memory_view.model().db, self.card_a_view.model().db, self.card_b_view.model().db - - def library_moved(self, newloc): if newloc is None: return db = LibraryDatabase2(newloc) @@ -377,7 +375,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ def set_window_title(self): self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name()) - def location_selected(self, location): ''' Called when a location icon is clicked (e.g. Library) @@ -517,6 +514,9 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ def shutdown(self, write_settings=True): + for action in self.iactions.values(): + if not action.shutting_down(): + return if write_settings: self.write_settings() self.check_messages_timer.stop() From c1bf0ebd13005f6bbefd1ce3119e5e4d75217764 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 12:39:12 -0600 Subject: [PATCH 03/18] Futurismic by DM. Fixes #6514 (New recipe for SF blog Futurismic) --- resources/images/news/futurismic.png | Bin 0 -> 479 bytes resources/recipes/futurismic.recipe | 38 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 resources/images/news/futurismic.png create mode 100644 resources/recipes/futurismic.recipe diff --git a/resources/images/news/futurismic.png b/resources/images/news/futurismic.png new file mode 100644 index 0000000000000000000000000000000000000000..1f5870e8479f21142926285f22dd14b287804a93 GIT binary patch literal 479 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Y)RhkE)4%caKYZ?lYt_f1s;*b zK-vS0-A-oPfdtD69Mgd`SU*F|v9*U87#KM`T^vI!PQRVB*XxLbNbCNYXINsyTYVol zm2oGnUctZM2a{d%t)EhlxPP!uvpdigHf`HQr`#h>95-HcEjlsl?D?(SD%RZ_zn45W z_*}U2`-;}H&I^?U7s@Z^X!>Zf;6tyQW@v3!VePlIiMbmOzwvP}xTqlh@PWybr+;6U zJJ@(c&YGQ;p1hRBcIS?j>}-DT81>_$;e3_TCtc<=Eabdr_v=mR;h=5zc`G}rzA`8|%~`Tk$1iBpo@Xo! zyNdGHiu7^yu>EoU&v0FmG5gd;Q#s`-lRGZiHt!aP_nlkU_2%72$@^7)2l$gkBNv=) z)i? Date: Sun, 15 Aug 2010 14:30:01 -0600 Subject: [PATCH 04/18] Fix #6509 (v0.7.14 - Broken link in book description) --- src/calibre/gui2/dialogs/book_info.py | 6 +++--- src/calibre/gui2/dialogs/book_info.ui | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/dialogs/book_info.py b/src/calibre/gui2/dialogs/book_info.py index 9770ef864f..e085dcc3e2 100644 --- a/src/calibre/gui2/dialogs/book_info.py +++ b/src/calibre/gui2/dialogs/book_info.py @@ -48,11 +48,11 @@ class BookInfo(QDialog, Ui_BookInfo): self.refresh(row) def open_book_path(self, path): - if os.sep in unicode(path): + path = unicode(path) + if os.sep in path: open_local_file(path) else: - format = unicode(path) - path = self.view.model().db.format_abspath(self.current_row, format) + path = self.view.model().db.format_abspath(self.current_row, path) if path is not None: open_local_file(path) diff --git a/src/calibre/gui2/dialogs/book_info.ui b/src/calibre/gui2/dialogs/book_info.ui index 02dae12281..39f9abf3c5 100644 --- a/src/calibre/gui2/dialogs/book_info.ui +++ b/src/calibre/gui2/dialogs/book_info.ui @@ -57,7 +57,7 @@ - Fit &cover to view + Fit &cover within view From 388d8e34dcc16fa1d3410d3671c1c6440e6cef23 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 14:56:17 -0600 Subject: [PATCH 05/18] Fix #6505 (Flesh out window settings and keyboard shortcuts.) --- src/calibre/gui2/cover_flow.py | 1 + src/calibre/gui2/jobs.py | 21 +++++++++++++++++++-- src/calibre/gui2/ui.py | 2 +- src/calibre/gui2/widgets.py | 9 ++++++--- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py index b0252210e3..88bbae6c41 100644 --- a/src/calibre/gui2/cover_flow.py +++ b/src/calibre/gui2/cover_flow.py @@ -196,6 +196,7 @@ class CoverFlowMixin(object): def show_cover_browser(self): d = CBDialog(self, self.cover_flow) + d.addAction(self.cb_splitter.action_toggle) self.cover_flow.setVisible(True) self.cover_flow.setFocus(Qt.OtherFocusReason) d.show() diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index 4b00d39877..e92a502daa 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -14,7 +14,8 @@ from Queue import Empty, Queue from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \ QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, \ QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, \ - QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication + QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication, QAction, \ + QByteArray from calibre.utils.ipc.server import Server from calibre.utils.ipc.job import ParallelJob @@ -281,6 +282,7 @@ class JobsButton(QFrame): self.pi = ProgressIndicator(self, size) self._jobs = QLabel(''+_('Jobs:')+' 0') self._jobs.mouseReleaseEvent = self.mouseReleaseEvent + self.shortcut = _('Shift+Alt+J') if horizontal: self.setLayout(QHBoxLayout()) @@ -297,15 +299,24 @@ class JobsButton(QFrame): self.layout().setMargin(0) self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.setCursor(Qt.PointingHandCursor) - self.setToolTip(_('Click to see list of active jobs.')) + b = _('Click to see list of jobs') + self.setToolTip(b + u' (%s)'%self.shortcut) + self.action_toggle = QAction(b, parent) + parent.addAction(self.action_toggle) + self.action_toggle.setShortcut(self.shortcut) + self.action_toggle.triggered.connect(self.toggle) def initialize(self, jobs_dialog, job_manager): self.jobs_dialog = jobs_dialog job_manager.job_added.connect(self.job_added) job_manager.job_done.connect(self.job_done) + self.jobs_dialog.addAction(self.action_toggle) def mouseReleaseEvent(self, event): + self.toggle() + + def toggle(self, *args): if self.jobs_dialog.isVisible(): self.jobs_dialog.hide() else: @@ -372,6 +383,10 @@ class JobsDialog(QDialog, Ui_JobsDialog): except: pass + geom = gprefs.get('jobs_dialog_geometry', bytearray('')) + self.restoreGeometry(QByteArray(geom)) + + def show_job_details(self, index): row = index.row() job = self.jobs_view.model().row_to_job(row) @@ -397,6 +412,8 @@ class JobsDialog(QDialog, Ui_JobsDialog): try: state = bytearray(self.jobs_view.horizontalHeader().saveState()) gprefs['jobs view column layout'] = state + geom = bytearray(self.saveGeometry()) + gprefs['jobs_dialog_geometry'] = geom except: pass e.accept() diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 1b918e679e..95cccb72a5 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -132,7 +132,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ # Jobs Button {{{ self.job_manager = JobManager() self.jobs_dialog = JobsDialog(self, self.job_manager) - self.jobs_button = JobsButton(horizontal=True) + self.jobs_button = JobsButton(horizontal=True, parent=self) self.jobs_button.initialize(self.jobs_dialog, self.job_manager) # }}} diff --git a/src/calibre/gui2/widgets.py b/src/calibre/gui2/widgets.py index 54cb526750..dbb3947d4c 100644 --- a/src/calibre/gui2/widgets.py +++ b/src/calibre/gui2/widgets.py @@ -871,14 +871,14 @@ class LayoutButton(QToolButton): def set_state_to_show(self, *args): self.setChecked(False) label =_('Show') - self.setText(label + ' ' + self.label + ' ' + self.shortcut) + self.setText(label + ' ' + self.label + u' (%s)'%self.shortcut) self.setToolTip(self.text()) self.setStatusTip(self.text()) def set_state_to_hide(self, *args): self.setChecked(True) label = _('Hide') - self.setText(label + ' ' + self.label+ ' ' + self.shortcut) + self.setText(label + ' ' + self.label+ u' (%s)'%self.shortcut) self.setToolTip(self.text()) self.setStatusTip(self.text()) @@ -941,7 +941,10 @@ class Splitter(QSplitter): @property def is_side_index_hidden(self): sizes = list(self.sizes()) - return sizes[self.side_index] == 0 + try: + return sizes[self.side_index] == 0 + except IndexError: + return True @property def save_name(self): From 4677b9acc129bb92e3134efa9443871014b99836 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 15:02:24 -0600 Subject: [PATCH 06/18] ... --- src/calibre/gui2/jobs.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index e92a502daa..fed101d278 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -376,15 +376,19 @@ class JobsDialog(QDialog, Ui_JobsDialog): self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate) self.jobs_view.doubleClicked.connect(self.show_job_details) self.jobs_view.horizontalHeader().setMovable(True) - state = gprefs.get('jobs view column layout', None) - if state is not None: - try: - self.jobs_view.horizontalHeader().restoreState(bytes(state)) - except: - pass + self.restore_state() + def restore_state(self): geom = gprefs.get('jobs_dialog_geometry', bytearray('')) self.restoreGeometry(QByteArray(geom)) + state = gprefs.get('jobs view column layout', bytearray('')) + self.jobs_view.horizontalHeader().restoreState(QByteArray(state)) + + def save_state(self): + state = bytearray(self.jobs_view.horizontalHeader().saveState()) + gprefs['jobs view column layout'] = state + geom = bytearray(self.saveGeometry()) + gprefs['jobs_dialog_geometry'] = geom def show_job_details(self, index): @@ -409,11 +413,13 @@ class JobsDialog(QDialog, Ui_JobsDialog): self.model.kill_all_jobs() def closeEvent(self, e): - try: - state = bytearray(self.jobs_view.horizontalHeader().saveState()) - gprefs['jobs view column layout'] = state - geom = bytearray(self.saveGeometry()) - gprefs['jobs_dialog_geometry'] = geom - except: - pass - e.accept() + self.save_state() + return QDialog.closeEvent(self, e) + + def show(self, *args): + self.restore_state() + return QDialog.show(self, *args) + + def hide(self, *args): + self.save_state() + return QDialog.hide(self, *args) From aa371fed65f5e2a748bcbe656aed762bc7169b13 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 15:02:54 -0600 Subject: [PATCH 07/18] ... --- src/calibre/gui2/jobs.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py index fed101d278..fd23234c8a 100644 --- a/src/calibre/gui2/jobs.py +++ b/src/calibre/gui2/jobs.py @@ -379,16 +379,22 @@ class JobsDialog(QDialog, Ui_JobsDialog): self.restore_state() def restore_state(self): - geom = gprefs.get('jobs_dialog_geometry', bytearray('')) - self.restoreGeometry(QByteArray(geom)) - state = gprefs.get('jobs view column layout', bytearray('')) - self.jobs_view.horizontalHeader().restoreState(QByteArray(state)) + try: + geom = gprefs.get('jobs_dialog_geometry', bytearray('')) + self.restoreGeometry(QByteArray(geom)) + state = gprefs.get('jobs view column layout', bytearray('')) + self.jobs_view.horizontalHeader().restoreState(QByteArray(state)) + except: + pass def save_state(self): - state = bytearray(self.jobs_view.horizontalHeader().saveState()) - gprefs['jobs view column layout'] = state - geom = bytearray(self.saveGeometry()) - gprefs['jobs_dialog_geometry'] = geom + try: + state = bytearray(self.jobs_view.horizontalHeader().saveState()) + gprefs['jobs view column layout'] = state + geom = bytearray(self.saveGeometry()) + gprefs['jobs_dialog_geometry'] = geom + except: + pass def show_job_details(self, index): From 61f0cbe23c57abfdfb1449982ce4e8cde2c913cb Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 16:05:00 -0600 Subject: [PATCH 08/18] Fix #6511 (Tag browser problem after switching libraries) --- src/calibre/gui2/tag_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 1565520ca1..239ee6a35a 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -78,6 +78,7 @@ class TagsView(QTreeView): # {{{ self.setAnimated(True) self.setHeaderHidden(True) self.setItemDelegate(TagDelegate(self)) + self.clicked.connect(self.toggle) def set_database(self, db, tag_match, sort_by): self.hidden_categories = config['tag_browser_hidden_categories'] @@ -90,7 +91,6 @@ class TagsView(QTreeView): # {{{ self.search_restriction = None self.setModel(self._model) self.setContextMenuPolicy(Qt.CustomContextMenu) - self.clicked.connect(self.toggle) self.customContextMenuRequested.connect(self.show_context_menu) pop = config['sort_tags_by'] self.sort_by.setCurrentIndex(self.db.CATEGORY_SORTS.index(pop)) From b4c94461579f0db41afb79e9eeb90727a422c702 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 15 Aug 2010 16:58:24 -0600 Subject: [PATCH 09/18] Only make connections between tag browser components once when switching libraries --- src/calibre/gui2/tag_view.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 239ee6a35a..7d2333f58b 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -78,7 +78,7 @@ class TagsView(QTreeView): # {{{ self.setAnimated(True) self.setHeaderHidden(True) self.setItemDelegate(TagDelegate(self)) - self.clicked.connect(self.toggle) + self.made_connections = False def set_database(self, db, tag_match, sort_by): self.hidden_categories = config['tag_browser_hidden_categories'] @@ -91,11 +91,14 @@ class TagsView(QTreeView): # {{{ self.search_restriction = None self.setModel(self._model) self.setContextMenuPolicy(Qt.CustomContextMenu) - self.customContextMenuRequested.connect(self.show_context_menu) pop = config['sort_tags_by'] self.sort_by.setCurrentIndex(self.db.CATEGORY_SORTS.index(pop)) - self.sort_by.currentIndexChanged.connect(self.sort_changed) - self.refresh_required.connect(self.recount, type=Qt.QueuedConnection) + if not self.made_connections: + self.clicked.connect(self.toggle) + self.customContextMenuRequested.connect(self.show_context_menu) + self.refresh_required.connect(self.recount, type=Qt.QueuedConnection) + self.sort_by.currentIndexChanged.connect(self.sort_changed) + self.made_connections = True db.add_listener(self.database_changed) def database_changed(self, event, ids): From 0497085c65164edf50000450b302e087653df743 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 16 Aug 2010 11:18:59 +0100 Subject: [PATCH 10/18] Improve bulk metadata performance when no changes to in_multiple columns --- src/calibre/gui2/custom_column_widgets.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 96232fe85f..1c72511630 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -407,7 +407,9 @@ class BulkBase(Base): if self.process_each_book(): for book_id in book_ids: val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) - self.db.set_custom(book_id, self.getter(val), num=self.col_id, notify=notify) + new_val = self.getter(val) + if set(val) != new_val or True: + self.db.set_custom(book_id, new_val, num=self.col_id, notify=notify) else: val = self.getter() val = self.normalize_ui_val(val) @@ -544,8 +546,9 @@ class BulkText(BulkBase): ans = set(original_value) ans -= set([v.strip() for v in unicode(self.removing_widget.tags_box.text()).split(',')]) - ans |= set([v.strip() for v in - unicode(self.adding_widget.text()).split(',')]) + txt = unicode(self.adding_widget.text()) + if txt: + ans |= set([v.strip() for v in txt.split(',')]) return ans # returning a set instead of a list works, for now at least. val = unicode(self.widgets[1].currentText()).strip() if not val: From c917f95c2c723d81946fa417941edc4bf3bde687 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Mon, 16 Aug 2010 11:24:34 +0100 Subject: [PATCH 11/18] Take out debug test. --- src/calibre/gui2/custom_column_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 1c72511630..0b15fcb633 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -408,7 +408,7 @@ class BulkBase(Base): for book_id in book_ids: val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) new_val = self.getter(val) - if set(val) != new_val or True: + if set(val) != new_val: self.db.set_custom(book_id, new_val, num=self.col_id, notify=notify) else: val = self.getter() From d13d0342ba0d42934739e3af48c1c2ffc7369e35 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Aug 2010 09:33:59 -0600 Subject: [PATCH 12/18] ... --- src/calibre/customize/builtins.py | 7 ++++++- src/calibre/gui2/actions/copy_to_library.py | 21 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 src/calibre/gui2/actions/copy_to_library.py diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index cd2896f232..35d7c846d0 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -657,9 +657,14 @@ class ActionEditCollections(InterfaceActionBase): name = 'Edit Collections' actual_plugin = 'calibre.gui2.actions.edit_collections:EditCollectionsAction' +class ActionCopyToLibrary(InterfaceActionBase): + name = 'Copy To Library' + actual_plugin = 'calibre.gui2.actions.copy_to_library:CopyToLibraryAction' + plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog, ActionConvert, ActionDelete, ActionEditMetadata, ActionView, ActionFetchNews, ActionSaveToDisk, ActionShowBookDetails, ActionRestart, ActionOpenFolder, ActionConnectShare, ActionSendToDevice, ActionHelp, ActionPreferences, ActionSimilarBooks, - ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary] + ActionAddToLibrary, ActionEditCollections, ActionChooseLibrary, + ActionCopyToLibrary] diff --git a/src/calibre/gui2/actions/copy_to_library.py b/src/calibre/gui2/actions/copy_to_library.py new file mode 100644 index 0000000000..5b9e885cec --- /dev/null +++ b/src/calibre/gui2/actions/copy_to_library.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from PyQt4.Qt import QMenu + +from calibre.gui2.actions import InterfaceAction + +class CopyToLibraryAction(InterfaceAction): + + name = 'Copy To Library' + action_spec = (_('Copy to library'), 'lt.png', + _('Copy selected books to the specified library'), None) + + def genesis(self): + self.menu = QMenu(self.gui) + + From 3b52187aeedc91659f61b3b118a3db7de4a4a83b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Aug 2010 09:50:25 -0600 Subject: [PATCH 13/18] Implement #6523 (New value for existing tweak) --- resources/default_tweaks.py | 1 + src/calibre/ebooks/metadata/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py index 80456234b9..d6f134f724 100644 --- a/resources/default_tweaks.py +++ b/resources/default_tweaks.py @@ -24,6 +24,7 @@ series_index_auto_increment = 'next' # invert: use "fn ln" -> "ln, fn" (the original algorithm) # copy : copy author to author_sort without modification # comma : use 'copy' if there is a ',' in the name, otherwise use 'invert' +# nocomma : "fn ln" -> "ln fn" (without the comma) author_sort_copy_method = 'invert' diff --git a/src/calibre/ebooks/metadata/__init__.py b/src/calibre/ebooks/metadata/__init__.py index 1e62cffd0f..8ce81c73d5 100644 --- a/src/calibre/ebooks/metadata/__init__.py +++ b/src/calibre/ebooks/metadata/__init__.py @@ -38,7 +38,7 @@ def author_to_author_sort(author): author = _bracket_pat.sub('', author).strip() tokens = author.split() tokens = tokens[-1:] + tokens[:-1] - if len(tokens) > 1: + if len(tokens) > 1 and method != 'nocomma': tokens[0] += ',' return ' '.join(tokens) From 56d5f9f2a9cdee2c2f1c61b4bee9d1625a49b1be Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Aug 2010 16:34:51 -0600 Subject: [PATCH 14/18] Fix #6530 (Add Support for Samsung Captivate) --- src/calibre/devices/android/driver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py index 951ed66e72..969fdfbd19 100644 --- a/src/calibre/devices/android/driver.py +++ b/src/calibre/devices/android/driver.py @@ -55,9 +55,9 @@ class ANDROID(USBMS): VENDOR_NAME = ['HTC', 'MOTOROLA', 'GOOGLE_', 'ANDROID', 'ACER', 'GT-I5700', 'SAMSUNG', 'DELL', 'LINUX'] WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE', - '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', + '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID'] - WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', + WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID'] OSX_MAIN_MEM = 'HTC Android Phone Media' From 6bbc5c3e2b67b9ea082b7ec4422c58530cbde17a Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Aug 2010 16:35:33 -0600 Subject: [PATCH 15/18] ... --- src/calibre/devices/hanlin/driver.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/calibre/devices/hanlin/driver.py b/src/calibre/devices/hanlin/driver.py index f19135a7e7..10dd333b8d 100644 --- a/src/calibre/devices/hanlin/driver.py +++ b/src/calibre/devices/hanlin/driver.py @@ -80,6 +80,13 @@ class HANLINV3(USBMS): drives['carda'] = main return drives +class SPECTRA(HANLINV3): + + name = 'Spectra' + gui_name = 'Spectra' + PRODUCT_ID = [0xa4a5] + + FORMATS = ['epub', 'mobi', 'fb2', 'lit', 'prc', 'djvu', 'pdf', 'rtf', 'txt'] class HANLINV5(HANLINV3): name = 'Hanlin V5 driver' From 8b40b9f22c81e90060c8d09334cad3dfeed4ab74 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Aug 2010 19:13:16 -0600 Subject: [PATCH 16/18] DB: Store temporary tables in memory. Fix #6472 (Add/change tags for large number of eBooks is s l o w) --- src/calibre/gui2/dialogs/metadata_bulk.py | 18 +++-- src/calibre/library/database2.py | 86 +++++++++++++++++++++-- src/calibre/library/sqlite.py | 18 +++++ 3 files changed, 106 insertions(+), 16 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 9fcfe13253..05c4f48cf3 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -27,8 +27,9 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.changed = False QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync) - self.tags.update_tags_cache(self.db.all_tags()) - self.remove_tags.update_tags_cache(self.db.all_tags()) + all_tags = self.db.all_tags() + self.tags.update_tags_cache(all_tags) + self.remove_tags.update_tags_cache(all_tags) self.initialize_combos() @@ -103,6 +104,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.remove_tags.update_tags_cache(self.db.all_tags()) def sync(self): + remove = unicode(self.remove_tags.text()).strip().split(',') + add = unicode(self.tags.text()).strip().split(',') + self.db.bulk_modify_tags(self.ids, add=add, remove=remove) + + for id in self.ids: au = unicode(self.authors.text()) if au: @@ -120,14 +126,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): pub = unicode(self.publisher.text()) if pub: self.db.set_publisher(id, pub, notify=False) - remove_tags = unicode(self.remove_tags.text()).strip() - if remove_tags: - remove_tags = [i.strip() for i in remove_tags.split(',')] - self.db.unapply_tags(id, remove_tags, notify=False) - tags = unicode(self.tags.text()).strip() - if tags: - tags = map(lambda x: x.strip(), tags.split(',')) - self.db.set_tags(id, tags, append=True, notify=False) if self.write_series: series = unicode(self.series.currentText()).strip() next = self.db.get_next_series_num_for(series) diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 36a31b78a2..b8ac065760 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -26,7 +26,7 @@ from calibre.ebooks.metadata.meta import get_metadata, metadata_from_formats from calibre.constants import preferred_encoding, iswindows, isosx, filesystem_encoding from calibre.ptempfile import PersistentTemporaryFile from calibre.customize.ui import run_plugins_on_import - +from calibre import isbytestring from calibre.utils.filenames import ascii_filename from calibre.utils.date import utcnow, now as nowf, utcfromtimestamp from calibre.utils.config import prefs, tweaks @@ -116,6 +116,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # so that various code taht connects directly will not complain about # missing functions self.books_list_filter = self.conn.create_dynamic_filter('books_list_filter') + # Store temporary tables in memory + self.conn.execute('pragma temp_store=2') + self.conn.commit() @classmethod def exists_at(cls, path): @@ -1369,6 +1372,80 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): return set([]) return set([r[0] for r in result]) + @classmethod + def cleanup_tags(cls, tags): + tags = [x.strip() for x in tags if x.strip()] + tags = [x.decode(preferred_encoding, 'replace') \ + if isbytestring(x) else x for x in tags] + tags = [u' '.join(x.split()) for x in tags] + ans, seen = [], set([]) + for tag in tags: + if tag.lower() not in seen: + seen.add(tag.lower()) + ans.append(tag) + return ans + + def bulk_modify_tags(self, ids, add=[], remove=[], notify=False): + add = self.cleanup_tags(add) + remove = self.cleanup_tags(remove) + remove = set(remove) - set(add) + if not ids or (not add and not remove): + return + + # Add tags that do not already exist into the tag table + all_tags = self.all_tags() + lt = [t.lower() for t in all_tags] + new_tags = [t for t in add if t.lower() not in lt] + if new_tags: + self.conn.executemany('INSERT INTO tags(name) VALUES (?)', [(x,) for x in + new_tags]) + + # Create the temporary tables to store the ids for books and tags + # to be operated on + tables = ('temp_bulk_tag_edit_books', 'temp_bulk_tag_edit_add', + 'temp_bulk_tag_edit_remove') + drops = '\n'.join(['DROP TABLE IF EXISTS %s;'%t for t in tables]) + creates = '\n'.join(['CREATE TEMP TABLE %s(id INTEGER PRIMARY KEY);'%t + for t in tables]) + self.conn.executescript(drops + creates) + + # Populate the books temp table + self.conn.executemany( + 'INSERT INTO temp_bulk_tag_edit_books VALUES (?)', + [(x,) for x in ids]) + + # Populate the add/remove tags temp tables + for table, tags in enumerate([add, remove]): + if not tags: + continue + table = tables[table+1] + insert = ('INSERT INTO %s(id) SELECT tags.id FROM tags WHERE name=?' + ' COLLATE PYNOCASE LIMIT 1') + self.conn.executemany(insert%table, [(x,) for x in tags]) + + if remove: + self.conn.execute( + '''DELETE FROM books_tags_link WHERE + book IN (SELECT id FROM %s) AND + tag IN (SELECT id FROM %s)''' + % (tables[0], tables[2])) + + if add: + self.conn.execute( + ''' + INSERT INTO books_tags_link(book, tag) SELECT {0}.id, {1}.id FROM + {0}, {1} + '''.format(tables[0], tables[1]) + ) + self.conn.executescript(drops) + self.conn.commit() + + for x in ids: + tags = u','.join(self.get_tags(x)) + self.data.set(x, self.FIELD_MAP['tags'], tags, row_is_id=True) + if notify: + self.notify('metadata', ids) + def set_tags(self, id, tags, append=False, notify=True): ''' @param tags: list of strings @@ -1378,10 +1455,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.execute('DELETE FROM books_tags_link WHERE book=?', (id,)) self.conn.execute('DELETE FROM tags WHERE (SELECT COUNT(id) FROM books_tags_link WHERE tag=tags.id) < 1') otags = self.get_tags(id) - tags = [x.strip() for x in tags if x.strip()] - tags = [x.decode(preferred_encoding, 'replace') if not isinstance(x, - unicode) else x for x in tags] - tags = [u' '.join(x.split()) for x in tags] + tags = self.cleanup_tags(tags) for tag in (set(tags)-otags): tag = tag.strip() if not tag: @@ -1407,7 +1481,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): self.conn.execute('INSERT INTO books_tags_link(book, tag) VALUES (?,?)', (id, tid)) self.conn.commit() - tags = ','.join(self.get_tags(id)) + tags = u','.join(self.get_tags(id)) self.data.set(id, self.FIELD_MAP['tags'], tags, row_is_id=True) if notify: self.notify('metadata', [id]) diff --git a/src/calibre/library/sqlite.py b/src/calibre/library/sqlite.py index 85954f6e0f..1242d0bf7b 100644 --- a/src/calibre/library/sqlite.py +++ b/src/calibre/library/sqlite.py @@ -13,10 +13,12 @@ from threading import Thread from Queue import Queue from threading import RLock from datetime import datetime +from functools import partial from calibre.ebooks.metadata import title_sort, author_to_author_sort from calibre.utils.config import tweaks from calibre.utils.date import parse_date, isoformat +from calibre import isbytestring global_lock = RLock() @@ -98,6 +100,19 @@ def _author_to_author_sort(x): if not x: return '' return author_to_author_sort(x.replace('|', ',')) +def pynocase(one, two, encoding='utf-8'): + if isbytestring(one): + try: + one = one.decode(encoding, 'replace') + except: + pass + if isbytestring(two): + try: + two = two.decode(encoding, 'replace') + except: + pass + return cmp(one.lower(), two.lower()) + class DBThread(Thread): CLOSE = '-------close---------' @@ -115,10 +130,13 @@ class DBThread(Thread): def connect(self): self.conn = sqlite.connect(self.path, factory=Connection, detect_types=sqlite.PARSE_DECLTYPES|sqlite.PARSE_COLNAMES) + encoding = self.conn.execute('pragma encoding').fetchone()[0] self.conn.row_factory = sqlite.Row if self.row_factory else lambda cursor, row : list(row) self.conn.create_aggregate('concat', 1, Concatenate) self.conn.create_aggregate('sortconcat', 2, SortedConcatenate) self.conn.create_aggregate('sort_concat', 2, SafeSortedConcatenate) + self.conn.create_collation('PYNOCASE', partial(pynocase, + encoding=encoding)) if tweaks['title_series_sorting'] == 'strictly_alphabetic': self.conn.create_function('title_sort', 1, lambda x:x) else: From 8d9a4164b48dad95a8bda9180e0d5488edf5f9d4 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 16 Aug 2010 23:19:23 -0600 Subject: [PATCH 17/18] Show progress dialog to give user feedback that something is happening when applying bulk metadata edit --- resources/recipes/science_aas.recipe | 2 +- src/calibre/gui2/actions/edit_metadata.py | 10 +- src/calibre/gui2/custom_column_widgets.py | 6 +- src/calibre/gui2/dialogs/metadata_bulk.py | 133 +++++++++++++++------- 4 files changed, 103 insertions(+), 48 deletions(-) diff --git a/resources/recipes/science_aas.recipe b/resources/recipes/science_aas.recipe index 092db8665e..d5f95c0b83 100644 --- a/resources/recipes/science_aas.recipe +++ b/resources/recipes/science_aas.recipe @@ -22,7 +22,7 @@ class ScienceAAS(BasicNewsRecipe): timefmt = ' [%A, %d %B, %Y]' needs_subscription = True LOGIN = 'http://www.sciencemag.org/cgi/login?uri=%2Findex.dtl' - + def get_browser(self): br = BasicNewsRecipe.get_browser() if self.username is not None and self.password is not None: diff --git a/src/calibre/gui2/actions/edit_metadata.py b/src/calibre/gui2/actions/edit_metadata.py index 05b4bdf7fc..9d1066ee40 100644 --- a/src/calibre/gui2/actions/edit_metadata.py +++ b/src/calibre/gui2/actions/edit_metadata.py @@ -174,8 +174,14 @@ class EditMetadataAction(InterfaceAction): _('No books selected')) d.exec_() return - if MetadataBulkDialog(self.gui, rows, - self.gui.library_view.model().db).changed: + # Prevent the TagView from updating due to signals from the database + self.gui.tags_view.blockSignals(True) + try: + changed = MetadataBulkDialog(self.gui, rows, + self.gui.library_view.model().db).changed + finally: + self.gui.tags_view.blockSignals(False) + if changed: self.gui.library_view.model().resort(reset=False) self.gui.library_view.model().research() self.gui.tags_view.recount() diff --git a/src/calibre/gui2/custom_column_widgets.py b/src/calibre/gui2/custom_column_widgets.py index 0b15fcb633..3ed7d0c4ad 100644 --- a/src/calibre/gui2/custom_column_widgets.py +++ b/src/calibre/gui2/custom_column_widgets.py @@ -11,7 +11,7 @@ from functools import partial from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateEdit, \ QDate, QGroupBox, QVBoxLayout, QPlainTextEdit, QSizePolicy, \ QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \ - QPushButton + QPushButton, QCoreApplication from calibre.utils.date import qt_to_dt, now from calibre.gui2.widgets import TagsLineEdit, EnComboBox @@ -406,6 +406,7 @@ class BulkBase(Base): def commit(self, book_ids, notify=False): if self.process_each_book(): for book_id in book_ids: + QCoreApplication.processEvents() val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) new_val = self.getter(val) if set(val) != new_val: @@ -415,6 +416,7 @@ class BulkBase(Base): val = self.normalize_ui_val(val) if val != self.initial_val: for book_id in book_ids: + QCoreApplication.processEvents() self.db.set_custom(book_id, val, num=self.col_id, notify=notify) class BulkBool(BulkBase, Bool): @@ -433,6 +435,7 @@ class BulkDateTime(BulkBase, DateTime): pass class BulkSeries(BulkBase): + def setup_ui(self, parent): values = self.all_values = list(self.db.all_custom(num=self.col_id)) values.sort(cmp = lambda x,y: cmp(x.lower(), y.lower())) @@ -458,6 +461,7 @@ class BulkSeries(BulkBase): update_indices = self.idx_widget.checkState() if val != '': for book_id in book_ids: + QCoreApplication.processEvents() if update_indices: if tweaks['series_index_auto_increment'] == 'next': s_index = self.db.get_next_cc_series_num_for\ diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index 05c4f48cf3..dac3e3f477 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -3,8 +3,8 @@ __copyright__ = '2008, Kovid Goyal ' '''Dialog to edit metadata in bulk''' -from PyQt4.QtCore import SIGNAL, QObject -from PyQt4.QtGui import QDialog, QGridLayout +from PyQt4.Qt import SIGNAL, QObject, QDialog, QGridLayout, \ + QProgressDialog, QCoreApplication, QString from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor @@ -25,7 +25,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): len(rows)) self.write_series = False self.changed = False - QObject.connect(self.button_box, SIGNAL("accepted()"), self.sync) all_tags = self.db.all_tags() self.tags.update_tags_cache(all_tags) @@ -103,56 +102,102 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.tags.update_tags_cache(self.db.all_tags()) self.remove_tags.update_tags_cache(self.db.all_tags()) - def sync(self): - remove = unicode(self.remove_tags.text()).strip().split(',') - add = unicode(self.tags.text()).strip().split(',') - self.db.bulk_modify_tags(self.ids, add=add, remove=remove) + def accept(self): + if len(self.ids) < 1: + return QDialog.accept(self) + pd = QProgressDialog( + _('Applying changes to %d books. This may take a while.')%len(self.ids), + QString(), 0, 0, self) + pd.setModal(True) + pd.show() + pd.setValue(0) + def upd(): + QCoreApplication.processEvents() - for id in self.ids: + try: + remove = unicode(self.remove_tags.text()).strip().split(',') + add = unicode(self.tags.text()).strip().split(',') au = unicode(self.authors.text()) - if au: - au = string_to_authors(au) - self.db.set_authors(id, au, notify=False) - if self.auto_author_sort.isChecked(): - x = self.db.author_sort_from_book(id, index_is_id=True) - if x: - self.db.set_author_sort(id, x, notify=False) aus = unicode(self.author_sort.text()) - if aus and self.author_sort.isEnabled(): - self.db.set_author_sort(id, aus, notify=False) - if self.rating.value() != -1: - self.db.set_rating(id, 2*self.rating.value(), notify=False) + do_aus = self.author_sort.isEnabled() + rating = self.rating.value() pub = unicode(self.publisher.text()) - if pub: - self.db.set_publisher(id, pub, notify=False) - if self.write_series: - series = unicode(self.series.currentText()).strip() - next = self.db.get_next_series_num_for(series) - self.db.set_series(id, series, notify=False) - num = next if self.autonumber_series.isChecked() and series else 1.0 - self.db.set_series_index(id, num, notify=False) + do_series = self.write_series + series = unicode(self.series.currentText()).strip() + do_autonumber = self.autonumber_series.isChecked() + do_remove_format = self.remove_format.currentIndex() > -1 + remove_format = unicode(self.remove_format.currentText()) + do_swap_ta = self.swap_title_and_author.isChecked() + do_remove_conv = self.remove_conversion_settings.isChecked() + do_auto_author = self.auto_author_sort.isChecked() - if self.remove_format.currentIndex() > -1: - self.db.remove_format(id, unicode(self.remove_format.currentText()), index_is_id=True, notify=False) + upd() + self.changed = bool(self.ids) + for id in self.ids: + upd() + if do_swap_ta: + title = self.db.title(id, index_is_id=True) + aum = self.db.authors(id, index_is_id=True) + if aum: + aum = [a.strip().replace('|', ',') for a in aum.split(',')] + new_title = authors_to_string(aum) + self.db.set_title(id, new_title, notify=False) + if title: + new_authors = string_to_authors(title) + self.db.set_authors(id, new_authors, notify=False) + upd() - if self.swap_title_and_author.isChecked(): - title = self.db.title(id, index_is_id=True) - aum = self.db.authors(id, index_is_id=True) - if aum: - aum = [a.strip().replace('|', ',') for a in aum.split(',')] - new_title = authors_to_string(aum) - self.db.set_title(id, new_title, notify=False) - if title: - new_authors = string_to_authors(title) - self.db.set_authors(id, new_authors, notify=False) + if au: + self.db.set_authors(id, string_to_authors(au), notify=False) + upd() - if self.remove_conversion_settings.isChecked(): - self.db.delete_conversion_options(id, 'PIPE') + if do_auto_author: + x = self.db.author_sort_from_book(id, index_is_id=True) + if x: + self.db.set_author_sort(id, x, notify=False) + upd() - self.changed = True - for w in getattr(self, 'custom_column_widgets', []): - w.commit(self.ids) + if aus and do_aus: + self.db.set_author_sort(id, aus, notify=False) + upd() + + if rating != -1: + self.db.set_rating(id, 2*rating, notify=False) + upd() + + if pub: + self.db.set_publisher(id, pub, notify=False) + upd() + + if do_series: + next = self.db.get_next_series_num_for(series) + self.db.set_series(id, series, notify=False) + num = next if do_autonumber and series else 1.0 + self.db.set_series_index(id, num, notify=False) + upd() + + if do_remove_format: + self.db.remove_format(id, remove_format, index_is_id=True, notify=False) + upd() + + + if do_remove_conv: + self.db.delete_conversion_options(id, 'PIPE') + upd() + + upd() + for w in getattr(self, 'custom_column_widgets', []): + w.commit(self.ids) + self.db.bulk_modify_tags(self.ids, add=add, remove=remove, + notify=False) + upd() + + + finally: + pd.cancel() + + return QDialog.accept(self) def series_changed(self): From 817bde27aa5151a8b8c0f1620b111c2e76252fe5 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 17 Aug 2010 00:03:01 -0600 Subject: [PATCH 18/18] ... --- src/calibre/gui2/dialogs/metadata_bulk.py | 13 +++++-------- src/calibre/gui2/dialogs/progress.py | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/calibre/gui2/dialogs/metadata_bulk.py b/src/calibre/gui2/dialogs/metadata_bulk.py index dac3e3f477..29ba22a5ac 100644 --- a/src/calibre/gui2/dialogs/metadata_bulk.py +++ b/src/calibre/gui2/dialogs/metadata_bulk.py @@ -4,13 +4,14 @@ __copyright__ = '2008, Kovid Goyal ' '''Dialog to edit metadata in bulk''' from PyQt4.Qt import SIGNAL, QObject, QDialog, QGridLayout, \ - QProgressDialog, QCoreApplication, QString + QCoreApplication from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.ebooks.metadata import string_to_authors, \ authors_to_string from calibre.gui2.custom_column_widgets import populate_metadata_page +from calibre.gui2.dialogs.progress import ProgressDialog class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): @@ -106,12 +107,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): if len(self.ids) < 1: return QDialog.accept(self) - pd = QProgressDialog( + pd = ProgressDialog(_('Working'), _('Applying changes to %d books. This may take a while.')%len(self.ids), - QString(), 0, 0, self) + 0, 0, self, cancelable=False) pd.setModal(True) pd.show() - pd.setValue(0) def upd(): QCoreApplication.processEvents() @@ -164,7 +164,6 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): if rating != -1: self.db.set_rating(id, 2*rating, notify=False) - upd() if pub: self.db.set_publisher(id, pub, notify=False) @@ -181,10 +180,8 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): self.db.remove_format(id, remove_format, index_is_id=True, notify=False) upd() - if do_remove_conv: self.db.delete_conversion_options(id, 'PIPE') - upd() upd() for w in getattr(self, 'custom_column_widgets', []): @@ -195,7 +192,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog): finally: - pd.cancel() + pd.hide() return QDialog.accept(self) diff --git a/src/calibre/gui2/dialogs/progress.py b/src/calibre/gui2/dialogs/progress.py index 40404050ec..3afa8dd633 100644 --- a/src/calibre/gui2/dialogs/progress.py +++ b/src/calibre/gui2/dialogs/progress.py @@ -13,7 +13,8 @@ class ProgressDialog(QDialog, Ui_Dialog): canceled_signal = pyqtSignal() - def __init__(self, title, msg='', min=0, max=99, parent=None): + def __init__(self, title, msg='', min=0, max=99, parent=None, + cancelable=True): QDialog.__init__(self, parent) self.setupUi(self) self.setWindowTitle(title) @@ -26,6 +27,9 @@ class ProgressDialog(QDialog, Ui_Dialog): self.canceled = False self.button_box.rejected.connect(self._canceled) + if not cancelable: + self.button_box.setVisible(False) + self.cancelable = cancelable def set_msg(self, msg=''): self.message.setText(msg) @@ -54,8 +58,14 @@ class ProgressDialog(QDialog, Ui_Dialog): self.title.setText(_('Aborting...')) self.canceled_signal.emit() + def reject(self): + if not self.cancelable: + return + QDialog.reject(self) + def keyPressEvent(self, ev): if ev.key() == Qt.Key_Escape: - self._canceled() + if self.cancelable: + self._canceled() else: QDialog.keyPressEvent(self, ev)