From 2c12c1aa3db3c5b5a1f8fb2ff0a154c158821405 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 19 May 2013 14:25:09 +0530 Subject: [PATCH] Show the number of currently selected books in the status bar at the bottom of the book list --- src/calibre/gui2/init.py | 64 ++++++++++++++++++++++-------- src/calibre/gui2/library/models.py | 28 +++++++++++-- src/calibre/gui2/ui.py | 6 +++ 3 files changed, 77 insertions(+), 21 deletions(-) diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py index 131adc3216..14f7eec224 100644 --- a/src/calibre/gui2/init.py +++ b/src/calibre/gui2/init.py @@ -27,7 +27,7 @@ def partial(*args, **kwargs): _keep_refs.append(ans) return ans -class LibraryViewMixin(object): # {{{ +class LibraryViewMixin(object): # {{{ def __init__(self, db): self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection) @@ -100,7 +100,7 @@ class LibraryViewMixin(object): # {{{ # }}} -class LibraryWidget(Splitter): # {{{ +class LibraryWidget(Splitter): # {{{ def __init__(self, parent): orientation = Qt.Vertical @@ -119,7 +119,7 @@ class LibraryWidget(Splitter): # {{{ self.addWidget(parent.library_view) # }}} -class Stack(QStackedWidget): # {{{ +class Stack(QStackedWidget): # {{{ def __init__(self, parent): QStackedWidget.__init__(self, parent) @@ -147,7 +147,7 @@ class Stack(QStackedWidget): # {{{ # }}} -class UpdateLabel(QLabel): # {{{ +class UpdateLabel(QLabel): # {{{ def __init__(self, *args, **kwargs): QLabel.__init__(self, *args, **kwargs) @@ -157,22 +157,22 @@ class UpdateLabel(QLabel): # {{{ pass # }}} -class StatusBar(QStatusBar): # {{{ +class StatusBar(QStatusBar): # {{{ def __init__(self, parent=None): QStatusBar.__init__(self, parent) - self.default_message = __appname__ + ' ' + _('version') + ' ' + \ - self.get_version() + ' ' + _('created by Kovid Goyal') self.device_string = '' self.update_label = UpdateLabel('') + self.total = self.current = self.selected = 0 self.addPermanentWidget(self.update_label) self.update_label.setVisible(False) self._font = QFont() self._font.setBold(True) self.setFont(self._font) - self.defmsg = QLabel(self.default_message) + self.defmsg = QLabel('') self.defmsg.setFont(self._font) self.addWidget(self.defmsg) + self.set_label() def initialize(self, systray=None): self.systray = systray @@ -180,18 +180,42 @@ class StatusBar(QStatusBar): # {{{ def device_connected(self, devname): self.device_string = _('Connected ') + devname - self.defmsg.setText(self.default_message + ' ..::.. ' + - self.device_string) + self.set_label() + + def update_state(self, total, current, selected): + self.total, self.current, self.selected = total, current, selected + self.set_label() + + def set_label(self): + try: + self._set_label() + except: + import traceback + traceback.print_exc() + + def _set_label(self): + msg = '%s %s %s' % (__appname__, _('version'), get_version()) + if self.device_string: + msg += ' ..::.. ' + self.device_string + else: + msg += _(' %(created)s %(name)s') % dict(created=_('created by'), name='Kovid Goyal') + + if self.total != self.current: + base = _('%(num)d of %(total)d books') % dict(num=self.current, total=self.total) + else: + base = _('%d books') % self.total + if self.selected > 0: + base = _('%(num)s, %(sel)d selected') % dict(num=base, sel=self.selected) + + self.defmsg.setText('%s [%s]' % (msg, base)) self.clearMessage() def device_disconnected(self): self.device_string = '' + self.set_label() self.defmsg.setText(self.default_message) self.clearMessage() - def get_version(self): - return get_version() - def show_message(self, msg, timeout=0): self.showMessage(msg, timeout) if self.notifier is not None and not config['disable_tray_notification']: @@ -207,11 +231,11 @@ class StatusBar(QStatusBar): # {{{ # }}} -class LayoutMixin(object): # {{{ +class LayoutMixin(object): # {{{ def __init__(self): - if config['gui_layout'] == 'narrow': # narrow {{{ + if config['gui_layout'] == 'narrow': # narrow {{{ self.book_details = BookDetails(False, self) self.stack = Stack(self) self.bd_splitter = Splitter('book_details_splitter', @@ -224,7 +248,7 @@ class LayoutMixin(object): # {{{ self.centralwidget.layout().addWidget(self.bd_splitter) button_order = ('tb', 'bd', 'cb') # }}} - else: # wide {{{ + else: # wide {{{ self.bd_splitter = Splitter('book_details_splitter', _('Book Details'), I('book.png'), initial_side_size=200, orientation=Qt.Horizontal, parent=self, side_index=1, @@ -312,9 +336,15 @@ class LayoutMixin(object): # {{{ def read_layout_settings(self): # View states are restored automatically when set_database is called - for x in ('cb', 'tb', 'bd'): getattr(self, x+'_splitter').restore_state() + def update_status_bar(self, *args): + v = self.current_view() + selected = len(v.selectionModel().selectedRows()) + total, current = v.model().counts() + self.status_bar.update_state(total, current, selected) + # }}} + diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py index 2b0e7d9cc8..86d71dae70 100644 --- a/src/calibre/gui2/library/models.py +++ b/src/calibre/gui2/library/models.py @@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal ' __docformat__ = 'restructuredtext en' import functools, re, os, traceback, errno, time -from collections import defaultdict +from collections import defaultdict, namedtuple from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage, QModelIndex, QVariant, QDateTime, QColor, QPixmap) @@ -29,6 +29,8 @@ from calibre.gui2.library import DEFAULT_SORT from calibre.utils.localization import calibre_langcode_to_name from calibre.library.coloring import color_row_key +Counts = namedtuple('Counts', 'total current') + def human_readable(size, precision=1): """ Convert a size in bytes into megabytes """ return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),) @@ -46,7 +48,7 @@ def default_image(): _default_image = QImage(I('default_cover.png')) return _default_image -class ColumnColor(object): +class ColumnColor(object): # {{{ def __init__(self, formatter, colors): self.mi = None @@ -70,9 +72,9 @@ class ColumnColor(object): return color except: pass +# }}} - -class ColumnIcon(object): +class ColumnIcon(object): # {{{ def __init__(self, formatter): self.mi = None @@ -108,6 +110,7 @@ class ColumnIcon(object): return icon_bitmap except: pass +# }}} class BooksModel(QAbstractTableModel): # {{{ @@ -281,6 +284,13 @@ class BooksModel(QAbstractTableModel): # {{{ self._clear_caches() self.count_changed_signal.emit(self.db.count()) + def counts(self): + if self.db.data.search_restriction_applied(): + total = self.db.data.get_search_restriction_book_count() + else: + total = self.db.count() + return Counts(total, self.count()) + def row_indices(self, index): ''' Return list indices of all cells in index.row()''' return [self.index(index.row(), c) for c in range(self.columnCount(None))] @@ -1195,6 +1205,12 @@ class DeviceBooksModel(BooksModel): # {{{ self.editable = ['title', 'authors', 'collections'] self.book_in_library = None + def counts(self): + return Counts(len(self.db), len(self.map)) + + def count_changed(self, *args): + self.count_changed_signal.emit(len(self.db)) + def mark_for_deletion(self, job, rows, rows_are_ids=False): db_indices = rows if rows_are_ids else self.indices(rows) db_items = [self.db[i] for i in db_indices if -1 < i < len(self.db)] @@ -1234,11 +1250,13 @@ class DeviceBooksModel(BooksModel): # {{{ if not succeeded: indices = self.row_indices(self.index(row, 0)) self.dataChanged.emit(indices[0], indices[-1]) + self.count_changed() def paths_deleted(self, paths): self.map = list(range(0, len(self.db))) self.resort(False) self.research(True) + self.count_changed() def is_row_marked_for_deletion(self, row): try: @@ -1301,6 +1319,7 @@ class DeviceBooksModel(BooksModel): # {{{ self.last_search = text if self.last_search: self.searched.emit(True) + self.count_changed() def research(self, reset=True): self.search(self.last_search, reset) @@ -1370,6 +1389,7 @@ class DeviceBooksModel(BooksModel): # {{{ self.map = list(range(0, len(db))) self.research(reset=False) self.resort() + self.count_changed() def cover(self, row): item = self.db[self.map[row]] diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index aafea4ef2b..c9f07ef6f6 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -325,6 +325,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ if self.library_view.model().rowCount(None) < 3: self.library_view.resizeColumnsToContents() + for view in ('library', 'memory', 'card_a', 'card_b'): + v = getattr(self, '%s_view' % view) + v.selectionModel().selectionChanged.connect(self.update_status_bar) + v.model().count_changed_signal.connect(self.update_status_bar) + self.library_view.model().count_changed() self.bars_manager.database_changed(self.library_view.model().db) self.library_view.model().database_changed.connect(self.bars_manager.database_changed, @@ -661,6 +666,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{ # Reset the view in case something changed while it was invisible self.current_view().reset() self.set_number_of_books_shown() + self.update_status_bar() def job_exception(self, job, dialog_title=_('Conversion Error')): if not hasattr(self, '_modeless_dialogs'):