From a30a10dac811c68de46fe41c3957b7351d819a31 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 5 Nov 2008 00:09:10 -0800 Subject: [PATCH] Make column order sortable in book list --- src/calibre/gui2/__init__.py | 6 + src/calibre/gui2/dialogs/config.py | 113 +++++++++-- src/calibre/gui2/dialogs/config.ui | 209 ++++++++++++++++++-- src/calibre/gui2/library.py | 304 ++++++++++++++++------------- src/calibre/gui2/main.py | 26 ++- 5 files changed, 488 insertions(+), 170 deletions(-) diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py index 60b7174c26..3d60ffc871 100644 --- a/src/calibre/gui2/__init__.py +++ b/src/calibre/gui2/__init__.py @@ -17,6 +17,8 @@ import calibre.resources as resources NONE = QVariant() #: Null value to return from the data function of item models +ALL_COLUMNS = ['title', 'authors', 'size', 'timestamp', 'rating', 'publisher', 'tags', 'series'] + def _config(): c = Config('gui', 'preferences for the calibre GUI') c.add_opt('frequently_used_directories', default=[], @@ -47,6 +49,10 @@ def _config(): help=_('Options for the LRF ebook viewer')) c.add_opt('internally_viewed_formats', default=['LRF', 'EPUB', 'LIT', 'MOBI', 'PRC', 'HTML', 'FB2'], help=_('Formats that are viewed using the internal viewer')) + c.add_opt('column_map', default=ALL_COLUMNS, + help=_('Columns to be displayed in the book list')) + c.add_opt('autolaunch_server', default=False, help=_('Automatically launch content server on application startup')) + return ConfigProxy(c) config = _config() diff --git a/src/calibre/gui2/dialogs/config.py b/src/calibre/gui2/dialogs/config.py index 48bb742d5a..7c84854513 100644 --- a/src/calibre/gui2/dialogs/config.py +++ b/src/calibre/gui2/dialogs/config.py @@ -2,21 +2,24 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' import os, re -from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon -from PyQt4.QtCore import SIGNAL, QTimer, Qt, QSize, QVariant +from PyQt4.QtGui import QDialog, QMessageBox, QListWidgetItem, QIcon, \ + QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit +from PyQt4.QtCore import SIGNAL, QTimer, Qt, QSize, QVariant, QUrl from calibre import islinux from calibre.gui2.dialogs.config_ui import Ui_Dialog -from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, warning_dialog +from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \ + warning_dialog, ALL_COLUMNS from calibre.utils.config import prefs from calibre.gui2.widgets import FilenamePattern +from calibre.gui2.library import BooksModel from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks.epub.iterator import is_supported - +from calibre.library import server_config class ConfigDialog(QDialog, Ui_Dialog): - def __init__(self, window, db, columns): + def __init__(self, window, db, server=None): QDialog.__init__(self, window) Ui_Dialog.__init__(self) self.ICON_SIZES = {0:QSize(48, 48), 1:QSize(32,32), 2:QSize(24,24)} @@ -24,8 +27,9 @@ class ConfigDialog(QDialog, Ui_Dialog): self.item1 = QListWidgetItem(QIcon(':/images/metadata.svg'), _('General'), self.category_list) self.item2 = QListWidgetItem(QIcon(':/images/lookfeel.svg'), _('Interface'), self.category_list) self.item3 = QListWidgetItem(QIcon(':/images/view.svg'), _('Advanced'), self.category_list) + self.item4 = QListWidgetItem(QIcon(':/images/network-server.svg'), _('Content\nServer'), self.category_list) self.db = db - self.current_cols = columns + self.server = None path = prefs['library_path'] self.location.setText(path if path else '') self.connect(self.browse_button, SIGNAL('clicked(bool)'), self.browse) @@ -45,14 +49,18 @@ class ConfigDialog(QDialog, Ui_Dialog): self.priority.addItem('Idle') if not islinux: self.dirs_box.setVisible(False) - - for hidden, hdr in self.current_cols: - item = QListWidgetItem(hdr, self.columns) - item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable) - if hidden: - item.setCheckState(Qt.Unchecked) - else: + + column_map = config['column_map'] + for col in column_map + [i for i in ALL_COLUMNS if i not in column_map]: + item = QListWidgetItem(BooksModel.headers[col], self.columns) + item.setData(Qt.UserRole, QVariant(col)) + item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable) + if col in column_map: item.setCheckState(Qt.Checked) + else: + item.setCheckState(Qt.Unchecked) + self.connect(self.column_up, SIGNAL('clicked()'), self.up_column) + self.connect(self.column_down, SIGNAL('clicked()'), self.down_column) self.filename_pattern = FilenamePattern(self) self.metadata_box.layout().insertWidget(0, self.filename_pattern) @@ -96,8 +104,75 @@ class ConfigDialog(QDialog, Ui_Dialog): self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked) added_html = ext == 'html' self.viewer.sortItems() - + self.start.setEnabled(not getattr(self.server, 'is_running', False)) + self.test.setEnabled(not self.start.isEnabled()) + self.stop.setDisabled(self.start.isEnabled()) + self.connect(self.start, SIGNAL('clicked()'), self.start_server) + self.connect(self.view_logs, SIGNAL('clicked()'), self.view_server_logs) + self.connect(self.stop, SIGNAL('clicked()'), self.stop_server) + self.connect(self.test, SIGNAL('clicked()'), self.test_server) + opts = server_config().parse() + self.port.setValue(opts.port) + self.username.setText(opts.username) + self.password.setText(opts.password if opts.password else '') + self.auto_launch.setChecked(config['autolaunch_server']) + + def up_column(self): + idx = self.columns.currentRow() + if idx > 0: + self.columns.insertItem(idx-1, self.columns.takeItem(idx)) + self.columns.setCurrentRow(idx-1) + + def down_column(self): + idx = self.columns.currentRow() + if idx < self.columns.count()-1: + self.columns.insertItem(idx+1, self.columns.takeItem(idx)) + self.columns.setCurrentRow(idx+1) + + def view_server_logs(self): + from calibre.library.server import log_access_file, log_error_file + d = QDialog(self) + d.resize(QSize(800, 600)) + layout = QVBoxLayout() + d.setLayout(layout) + layout.addWidget(QLabel(_('Error log:'))) + el = QPlainTextEdit(d) + layout.addWidget(el) + el.setPlainText(open(log_error_file, 'rb').read().decode('utf8', 'replace')) + layout.addWidget(QLabel(_('Access log:'))) + al = QPlainTextEdit(d) + layout.addWidget(al) + al.setPlainText(open(log_access_file, 'rb').read().decode('utf8', 'replace')) + d.show() + + def set_server_options(self): + c = server_config() + c.set('port', self.port.value()) + c.set('username', unicode(self.username.text()).strip()) + p = unicode(self.password.text()).strip() + if not p: + p = None + c.set('password', p) + + def start_server(self): + self.set_server_options() + from calibre.library.server import start_threaded_server + self.server = start_threaded_server(self.db, server_config().parse()) + self.start.setEnabled(False) + self.test.setEnabled(True) + self.stop.setEnabled(True) + + def stop_server(self): + from calibre.library.server import stop_threaded_server + stop_threaded_server(self.server) + self.server = None + self.start.setEnabled(True) + self.test.setEnabled(False) + self.stop.setEnabled(False) + + def test_server(self): + QDesktopServices.openUrl(QUrl('http://127.0.0.1:'+str(self.port.value()))) def compact(self, toggled): d = Vacuum(self, self.db) @@ -123,7 +198,10 @@ class ConfigDialog(QDialog, Ui_Dialog): config['new_version_notification'] = bool(self.new_version_notification.isChecked()) prefs['network_timeout'] = int(self.timeout.value()) path = qstring_to_unicode(self.location.text()) - self.final_columns = [self.columns.item(i).checkState() == Qt.Checked for i in range(self.columns.count())] + cols = [] + for i in range(self.columns.count()): + cols.append(unicode(self.columns.item(i).data(Qt.UserRole).toString())) + config['column_map'] = cols config['toolbar_icon_size'] = self.ICON_SIZES[self.toolbar_button_size.currentIndex()] config['show_text_in_toolbar'] = bool(self.show_toolbar_text.isChecked()) config['confirm_delete'] = bool(self.confirm_delete.isChecked()) @@ -133,6 +211,11 @@ class ConfigDialog(QDialog, Ui_Dialog): config['save_to_disk_single_format'] = BOOK_EXTENSIONS[self.single_format.currentIndex()] config['cover_flow_queue_length'] = self.cover_browse.value() prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString()) + config['autolaunch_server'] = self.auto_launch.isChecked() + sc = server_config() + sc.set('username', unicode(self.username.text()).strip()) + sc.set('password', unicode(self.password.text()).strip()) + sc.set('port', self.port.value()) of = str(self.output_format.currentText()) fmts = [] for i in range(self.viewer.count()): diff --git a/src/calibre/gui2/dialogs/config.ui b/src/calibre/gui2/dialogs/config.ui index 63f3ddd170..2699a92d37 100644 --- a/src/calibre/gui2/dialogs/config.ui +++ b/src/calibre/gui2/dialogs/config.ui @@ -7,7 +7,7 @@ 0 0 709 - 676 + 840 @@ -67,12 +67,6 @@ QListView::IconMode - - true - - - -1 - @@ -433,15 +427,61 @@ Select visible &columns in library view - - - - - QAbstractItemView::NoSelection - - + + + + + + + true + + + QAbstractItemView::SelectRows + + + + + + + + + ... + + + + :/images/arrow-up.svg:/images/arrow-up.svg + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + ... + + + + :/images/arrow-down.svg:/images/arrow-down.svg + + + + + + - + Use internal &viewer for the following formats: @@ -449,6 +489,9 @@ + + true + QAbstractItemView::NoSelection @@ -528,6 +571,142 @@ + + + + + + calibre contains a network server that allows you to access your book collection using a browser from anywhere in the world. Any changes to the settings will only take effect after a server restart. + + + true + + + + + + + + + Server &port: + + + port + + + + + + + 1025 + + + 16000 + + + 8080 + + + + + + + &Username: + + + username + + + + + + + + + + &Password: + + + password + + + + + + + If you leave the password blank, anyone will be able to access your book collection using the web interface. + + + + + + + + + + + &Start Server + + + + + + + St&op Server + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Test Server + + + + + + + + + Run server &automatically on startup + + + + + + + View &server logs + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + diff --git a/src/calibre/gui2/library.py b/src/calibre/gui2/library.py index eb0b7298b8..cda1898866 100644 --- a/src/calibre/gui2/library.py +++ b/src/calibre/gui2/library.py @@ -5,18 +5,18 @@ from datetime import timedelta, datetime from operator import attrgetter from math import cos, sin, pi -from itertools import repeat -from PyQt4.QtGui import QTableView, QProgressDialog, QAbstractItemView, QColor, \ +from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \ QItemDelegate, QPainterPath, QLinearGradient, QBrush, \ QPen, QStyle, QPainter, QLineEdit, \ QPalette, QImage, QApplication from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, QString, \ - QCoreApplication, SIGNAL, QObject, QSize, QModelIndex + SIGNAL, QObject, QSize, QModelIndex from calibre import strftime from calibre.ptempfile import PersistentTemporaryFile -from calibre.library.database import LibraryDatabase, text_to_tokens +from calibre.library.database2 import FIELD_MAP from calibre.gui2 import NONE, TableView, qstring_to_unicode, config +from calibre.utils.search_query_parser import SearchQueryParser class LibraryDelegate(QItemDelegate): COLOR = QColor("blue") @@ -85,6 +85,18 @@ class BooksModel(QAbstractTableModel): [1000,900,500,400,100,90,50,40,10,9,5,4,1], ["M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"] ) + + headers = { + 'title' : _("Title"), + 'authors' : _("Author(s)"), + 'size' : _("Size (MB)"), + 'timestamp' : _("Date"), + 'rating' : _('Rating'), + 'publisher' : _("Publisher"), + 'tags' : _("Tags"), + 'series' : _("Series"), + } + @classmethod def roman(cls, num): if num <= 0 or num >= 4000 or int(num) != num: @@ -99,10 +111,10 @@ class BooksModel(QAbstractTableModel): def __init__(self, parent=None, buffer=40): QAbstractTableModel.__init__(self, parent) self.db = None - self.cols = ['title', 'authors', 'size', 'date', 'rating', 'publisher', 'tags', 'series'] - self.editable_cols = [0, 1, 4, 5, 6, 7] + self.column_map = config['column_map'] + self.editable_cols = ['title', 'authors', 'rating', 'publisher', 'tags', 'series'] self.default_image = QImage(':/images/book.svg') - self.sorted_on = (3, Qt.AscendingOrder) + self.sorted_on = ('timestamp', Qt.AscendingOrder) self.last_search = '' # The last search performed on this model self.read_config() self.buffer_size = buffer @@ -114,14 +126,15 @@ class BooksModel(QAbstractTableModel): def read_config(self): self.use_roman_numbers = config['use_roman_numerals_for_series_number'] + cols = config['column_map'] + if cols != self.column_map: + self.column_map = cols + self.reset() def set_database(self, db): - if isinstance(db, (QString, basestring)): - if isinstance(db, QString): - db = qstring_to_unicode(db) - db = LibraryDatabase(os.path.expanduser(db)) self.db = db + self.build_data_convertors() def refresh_ids(self, ids, current_row=-1): rows = self.db.refresh_ids(ids) @@ -151,7 +164,8 @@ class BooksModel(QAbstractTableModel): def save_to_disk(self, rows, path, single_dir=False, single_format=None): rows = [row.row() for row in rows] if single_format is None: - return self.db.export_to_dir(path, rows, self.sorted_on[0] == 1, single_dir=single_dir) + return self.db.export_to_dir(path, rows, self.sorted_on[0] == 'authors', + single_dir=single_dir) else: return self.db.export_single_format_to_dir(path, rows, single_format) @@ -166,9 +180,6 @@ class BooksModel(QAbstractTableModel): self.clear_caches() self.reset() - def search_tokens(self, text): - return text_to_tokens(text) - def books_added(self, num): if num > 0: self.beginInsertRows(QModelIndex(), 0, num-1) @@ -185,29 +196,27 @@ class BooksModel(QAbstractTableModel): if not self.db: return ascending = order == Qt.AscendingOrder - self.db.sort(self.cols[col], ascending) - self.research() + self.db.sort(self.column_map[col], ascending) + self.research(reset=False) if reset: self.clear_caches() self.reset() - self.sorted_on = (col, order) + self.sorted_on = (self.column_map[col], order) def resort(self, reset=True): - self.sort(*self.sorted_on, **dict(reset=reset)) + try: + col = self.column_map.index(self.sorted_on[0]) + except: + col = 0 + self.sort(col, self.sorted_on[1], reset=reset) def research(self, reset=True): self.search(self.last_search, False, reset=reset) - def database_needs_migration(self): - path = os.path.expanduser('~/library.db') - return self.db.is_empty() and \ - os.path.exists(path) and\ - LibraryDatabase.sizeof_old_database(path) > 0 - def columnCount(self, parent): if parent and parent.isValid(): return 0 - return len(self.cols) + return len(self.column_map) def rowCount(self, parent): if parent and parent.isValid(): @@ -374,65 +383,77 @@ class BooksModel(QAbstractTableModel): img = self.default_image return img + def build_data_convertors(self): + + tidx = FIELD_MAP['title'] + aidx = FIELD_MAP['authors'] + sidx = FIELD_MAP['size'] + ridx = FIELD_MAP['rating'] + pidx = FIELD_MAP['publisher'] + tmdx = FIELD_MAP['timestamp'] + srdx = FIELD_MAP['series'] + tgdx = FIELD_MAP['tags'] + siix = FIELD_MAP['series_index'] + + def authors(r): + au = self.db.data[r][aidx] + if au: + au = [a.strip().replace('|', ',') for a in au.split(',')] + return '\n'.join(au) + + def timestamp(r): + dt = self.db.data[r][tmdx] + if dt: + dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) + return strftime(BooksView.TIME_FMT, dt.timetuple()) + + def rating(r): + r = self.db.data[r][ridx] + r = r/2 if r else 0 + return r + + def publisher(r): + pub = self.db.data[r][pidx] + if pub: + return pub + + def tags(r): + tags = self.db.data[r][tgdx] + if tags: + return ', '.join(tags.split(',')) + + def series(r): + series = self.db.data[r][srdx] + if series: + return series + ' [%d]'%self.db.data[r][siix] + + self.dc = { + 'title' : lambda r : self.db.data[r][tidx], + 'authors' : authors, + 'size' : lambda r : self.db.data[r][sidx], + 'timestamp': timestamp, + 'rating' : rating, + 'publisher': publisher, + 'tags' : tags, + 'series' : series, + } + def data(self, index, role): - if role == Qt.DisplayRole or role == Qt.EditRole: - row, col = index.row(), index.column() - if col == 0: - text = self.db.title(row) - if text: - return QVariant(text) - elif col == 1: - au = self.db.authors(row) - if au: - au = [a.strip().replace('|', ',') for a in au.split(',')] - return QVariant("\n".join(au)) - elif col == 2: - size = self.db.max_size(row) - if size: - return QVariant(BooksView.human_readable(size)) - elif col == 3: - dt = self.db.timestamp(row) - if dt: - dt = dt - timedelta(seconds=time.timezone) + timedelta(hours=time.daylight) - return QVariant(strftime(BooksView.TIME_FMT, dt.timetuple())) - elif col == 4: - r = self.db.rating(row) - r = r/2 if r else 0 - return QVariant(r) - elif col == 5: - pub = self.db.publisher(row) - if pub: - return QVariant(pub) - elif col == 6: - tags = self.db.tags(row) - if tags: - return QVariant(', '.join(tags.split(','))) - elif col == 7: - series = self.db.series(row) - if series: - return QVariant(series + ' [%d]'%self.db.series_index(row)) - return NONE - elif role == Qt.TextAlignmentRole and index.column() in [2, 3, 4]: - return QVariant(Qt.AlignRight | Qt.AlignVCenter) - elif role == Qt.ToolTipRole and index.isValid(): - if index.column() in self.editable_cols: - return QVariant(_("Double click to edit me

")) + if role in (Qt.DisplayRole, Qt.EditRole): + ans = self.dc[self.column_map[index.column()]](index.row()) + return NONE if ans is None else QVariant(ans) + elif role == Qt.TextAlignmentRole and self.column_map[index.column()] in ('size', 'timestamp', 'rating'): + return QVariant(Qt.AlignCenter | Qt.AlignVCenter) + #elif role == Qt.ToolTipRole and index.isValid(): + # if self.column_map[index.column()] in self.editable_cols: + # return QVariant(_("Double click to edit me

")) return NONE def headerData(self, section, orientation, role): if role != Qt.DisplayRole: return NONE - text = "" if orientation == Qt.Horizontal: - if section == 0: text = _("Title") - elif section == 1: text = _("Author(s)") - elif section == 2: text = _("Size (MB)") - elif section == 3: text = _("Date") - elif section == 4: text = _("Rating") - elif section == 5: text = _("Publisher") - elif section == 6: text = _("Tags") - elif section == 7: text = _("Series") - return QVariant(text) + return QVariant(self.headers[self.column_map[section]]) else: return QVariant(section+1) @@ -447,14 +468,15 @@ class BooksModel(QAbstractTableModel): done = False if role == Qt.EditRole: row, col = index.row(), index.column() - if col not in self.editable_cols: + column = self.column_map[col] + if column not in self.editable_cols: return False - val = unicode(value.toString().toUtf8(), 'utf-8').strip() if col != 4 else \ + val = unicode(value.toString().toUtf8(), 'utf-8').strip() if column != 'rating' else \ int(value.toInt()[0]) - if col == 4: + if col == 'rating': val = 0 if val < 0 else 5 if val > 5 else val val *= 2 - if col == 7: + if col == 'series': pat = re.compile(r'\[(\d+)\]') match = pat.search(val) id = self.db.id(row) @@ -465,12 +487,11 @@ class BooksModel(QAbstractTableModel): if val: self.db.set_series(id, val) else: - column = self.cols[col] self.db.set(row, column, val) self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), \ index, index) - if col == self.sorted_on[0]: - self.sort(col, self.sorted_on[1]) + if column == self.sorted_on[0]: + self.resort() done = True return done @@ -495,8 +516,7 @@ class BooksView(TableView): self.setModel(self._model) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setSortingEnabled(True) - if self.__class__.__name__ == 'BooksView': # Subclasses may not have rating as col 4 - self.setItemDelegateForColumn(4, LibraryDelegate(self)) + self.columns_sorted() QObject.connect(self.selectionModel(), SIGNAL('currentRowChanged(QModelIndex, QModelIndex)'), self._model.current_changed) # Adding and removing rows should resize rows to contents @@ -505,7 +525,23 @@ class BooksView(TableView): # Resetting the model should resize rows (model is reset after search and sort operations) QObject.connect(self.model(), SIGNAL('modelReset()'), self.resizeRowsToContents) self.set_visible_columns() - + + def columns_sorted(self): + if self.__class__.__name__ == 'BooksView': + try: + idx = self._model.column_map.index('rating') + self.setItemDelegateForColumn(idx, LibraryDelegate(self)) + except ValueError: + pass + + + def sortByColumn(self, colname, order): + try: + idx = self._model.column_map.index(colname) + except ValueError: + idx = 0 + TableView.sortByColumn(self, idx, order) + @classmethod def paths_from_event(cls, event): ''' @@ -541,25 +577,6 @@ class BooksView(TableView): def close(self): self._model.close() - def migrate_database(self): - if self.model().database_needs_migration(): - print 'Migrating database from pre 0.4.0 version' - path = os.path.abspath(os.path.expanduser('~/library.db')) - progress = QProgressDialog('Upgrading database from pre 0.4.0 version.
'+\ - 'The new database is stored in the file '+self._model.db.dbpath, - QString(), 0, LibraryDatabase.sizeof_old_database(path), - self) - progress.setModal(True) - progress.setValue(0) - app = QCoreApplication.instance() - - def meter(count): - progress.setValue(count) - app.processEvents() - progress.setWindowTitle('Upgrading database') - progress.show() - LibraryDatabase.import_old_database(path, self._model.db.conn, meter) - def connect_to_search_box(self, sb): QObject.connect(sb, SIGNAL('search(PyQt_PyObject, PyQt_PyObject)'), self._model.search) @@ -582,6 +599,40 @@ class DeviceBooksView(BooksView): def connect_dirtied_signal(self, slot): QObject.connect(self._model, SIGNAL('booklist_dirtied()'), slot) + + def sortByColumn(self, col, order): + TableView.sortByColumn(self, col, order) + +class OnDeviceSearch(SearchQueryParser): + + def __init__(self, model): + SearchQueryParser.__init__(self) + self.model = model + + def universal_set(self): + return set(range(0, len(self.model.db))) + + def get_matches(self, location, query): + location = location.lower().strip() + query = query.lower().strip() + if location not in ('title', 'authors', 'tags', 'all'): + return set([]) + matches = set([]) + locations = ['title', 'authors', 'tags'] if location == 'all' else [location] + q = { + 'title' : lambda x : getattr(x, 'title').lower(), + 'authors': lambda x: getattr(x, 'authors').lower(), + 'tags':lambda x: ','.join(getattr(x, 'tags')).lower() + } + for i, v in enumerate(locations): + locations[i] = q[v] + for i, r in enumerate(self.model.db): + for loc in locations: + if query in loc(r): + matches.add(i) + break + return matches + class DeviceBooksModel(BooksModel): @@ -592,6 +643,7 @@ class DeviceBooksModel(BooksModel): self.sorted_map = [] self.unknown = str(self.trUtf8('Unknown')) self.marked_for_deletion = {} + self.search_engine = OnDeviceSearch(self) def mark_for_deletion(self, job, rows): @@ -632,34 +684,22 @@ class DeviceBooksModel(BooksModel): def search(self, text, refinement, reset=True): - tokens, OR = self.search_tokens(text) - base = self.map if refinement else self.sorted_map - result = [] - for i in base: - q = ['', self.db[i].title, self.db[i].authors, '', ', '.join(self.db[i].tags)] + list(repeat('', 10)) - if OR: - add = False - for token in tokens: - if token.match(q): - add = True - break - if add: - result.append(i) - else: - add = True - for token in tokens: - if not token.match(q): - add = False - break - if add: - result.append(i) - - self.map = result - + if not text: + self.map = list(range(len(self.db))) + else: + matches = self.search_engine.parse(text) + self.map = [] + for i in range(len(self.db)): + if i in matches: + self.map.append(i) + self.resort(reset=False) if reset: self.reset() self.last_search = text - + + def resort(self, reset): + self.sort(self.sorted_on[0], self.sorted_on[1], reset=reset) + def sort(self, col, order, reset=True): descending = order != Qt.AscendingOrder def strcmp(attr): diff --git a/src/calibre/gui2/main.py b/src/calibre/gui2/main.py index 7ffc1e1148..e1a43d7663 100644 --- a/src/calibre/gui2/main.py +++ b/src/calibre/gui2/main.py @@ -87,7 +87,8 @@ class Main(MainWindow, Ui_MainWindow): self.tb_wrapper = textwrap.TextWrapper(width=40) self.device_connected = False self.viewers = collections.deque() - + self.content_server = None + ####################### Location View ######################## QObject.connect(self.location_view, SIGNAL('location_selected(PyQt_PyObject)'), self.location_selected) @@ -239,7 +240,7 @@ class Main(MainWindow, Ui_MainWindow): os.remove(self.olddb.dbpath) self.olddb = None prefs['library_path'] = self.library_path - self.library_view.sortByColumn(*dynamic.get('sort_column', (3, Qt.DescendingOrder))) + self.library_view.sortByColumn(*dynamic.get('sort_column', ('timestamp', Qt.DescendingOrder))) if not self.library_view.restore_column_widths(): self.library_view.resizeColumnsToContents() self.library_view.resizeRowsToContents() @@ -282,6 +283,12 @@ class Main(MainWindow, Ui_MainWindow): self.device_manager.start() self.news_menu.set_custom_feeds(self.library_view.model().db.get_feeds()) + + if config['autolaunch_server']: + from calibre.library.server import start_threaded_server + from calibre.library import server_config + self.server = start_threaded_server(db, server_config().parse()) + def toggle_cover_flow(self, show): if show: @@ -1004,13 +1011,10 @@ class Main(MainWindow, Ui_MainWindow): d = error_dialog(self, _('Cannot configure'), _('Cannot configure while there are running jobs.')) d.exec_() return - columns = [(self.library_view.isColumnHidden(i), \ - self.library_view.model().headerData(i, Qt.Horizontal, Qt.DisplayRole).toString())\ - for i in range(self.library_view.model().columnCount(None))] - d = ConfigDialog(self, self.library_view.model().db, columns) + d = ConfigDialog(self, self.library_view.model().db, self.content_server) d.exec_() + self.content_server = d.server if d.result() == d.Accepted: - self.library_view.set_visible_columns(d.final_columns) self.tool_bar.setIconSize(config['toolbar_icon_size']) self.tool_bar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon if config['show_text_in_toolbar'] else Qt.ToolButtonIconOnly) self.save_menu.actions()[2].setText(_('Save only %s format to disk')%config.get('save_to_disk_single_format').upper()) @@ -1055,6 +1059,7 @@ class Main(MainWindow, Ui_MainWindow): if hasattr(d, 'directories'): set_sidebar_directories(d.directories) self.library_view.model().read_config() + self.library_view.columns_sorted() ############################################################################ @@ -1215,8 +1220,13 @@ in which you want to store your books files. Any existing books will be automati self.device_manager.keep_going = False self.cover_cache.stop() self.hide() - time.sleep(2) self.cover_cache.terminate() + try: + if self.server is not None: + self.server.exit() + except: + pass + time.sleep(2) e.accept() def update_found(self, version):