Make column order sortable in book list

This commit is contained in:
Kovid Goyal 2008-11-05 00:09:10 -08:00
parent 3a15dea106
commit a30a10dac8
5 changed files with 488 additions and 170 deletions

View File

@ -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()

View File

@ -2,21 +2,24 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
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)
@ -46,13 +50,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
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)
@ -97,7 +105,74 @@ class ConfigDialog(QDialog, Ui_Dialog):
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()):

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>709</width>
<height>676</height>
<height>840</height>
</rect>
</property>
<property name="windowTitle" >
@ -67,12 +67,6 @@
<property name="viewMode" >
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes" >
<bool>true</bool>
</property>
<property name="currentRow" >
<number>-1</number>
</property>
</widget>
</item>
<item>
@ -433,15 +427,61 @@
<property name="title" >
<string>Select visible &amp;columns in library view</string>
</property>
<layout class="QGridLayout" name="_4" >
<item row="0" column="0" >
<widget class="QListWidget" name="columns" >
<property name="selectionMode" >
<enum>QAbstractItemView::NoSelection</enum>
</property>
</widget>
<layout class="QVBoxLayout" name="verticalLayout_4" >
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3" >
<item>
<widget class="QListWidget" name="columns" >
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionBehavior" >
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3" >
<item>
<widget class="QToolButton" name="column_up" >
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/arrow-up.svg</normaloff>:/images/arrow-up.svg</iconset>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2" >
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="column_down" >
<property name="text" >
<string>...</string>
</property>
<property name="icon" >
<iconset resource="../images.qrc" >
<normaloff>:/images/arrow-down.svg</normaloff>:/images/arrow-down.svg</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item row="1" column="0" >
<item>
<widget class="QGroupBox" name="groupBox_3" >
<property name="title" >
<string>Use internal &amp;viewer for the following formats:</string>
@ -449,6 +489,9 @@
<layout class="QGridLayout" name="gridLayout_4" >
<item row="0" column="0" >
<widget class="QListWidget" name="viewer" >
<property name="alternatingRowColors" >
<bool>true</bool>
</property>
<property name="selectionMode" >
<enum>QAbstractItemView::NoSelection</enum>
</property>
@ -528,6 +571,142 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="page_4" >
<layout class="QVBoxLayout" name="verticalLayout_2" >
<item>
<widget class="QLabel" name="label_9" >
<property name="text" >
<string>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.</string>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_5" >
<item row="0" column="0" >
<widget class="QLabel" name="label_10" >
<property name="text" >
<string>Server &amp;port:</string>
</property>
<property name="buddy" >
<cstring>port</cstring>
</property>
</widget>
</item>
<item row="0" column="1" >
<widget class="QSpinBox" name="port" >
<property name="minimum" >
<number>1025</number>
</property>
<property name="maximum" >
<number>16000</number>
</property>
<property name="value" >
<number>8080</number>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QLabel" name="label_11" >
<property name="text" >
<string>&amp;Username:</string>
</property>
<property name="buddy" >
<cstring>username</cstring>
</property>
</widget>
</item>
<item row="1" column="1" >
<widget class="QLineEdit" name="username" />
</item>
<item row="2" column="0" >
<widget class="QLabel" name="label_12" >
<property name="text" >
<string>&amp;Password:</string>
</property>
<property name="buddy" >
<cstring>password</cstring>
</property>
</widget>
</item>
<item row="2" column="1" >
<widget class="QLineEdit" name="password" >
<property name="toolTip" >
<string>If you leave the password blank, anyone will be able to access your book collection using the web interface.</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QPushButton" name="start" >
<property name="text" >
<string>&amp;Start Server</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="stop" >
<property name="text" >
<string>St&amp;op Server</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="test" >
<property name="text" >
<string>&amp;Test Server</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="auto_launch" >
<property name="text" >
<string>Run server &amp;automatically on startup</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="view_logs" >
<property name="text" >
<string>View &amp;server logs</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer" >
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>

View File

@ -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 <b>edit</b> me<br><br>"))
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 <b>edit</b> me<br><br>"))
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
@ -506,6 +526,22 @@ class BooksView(TableView):
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.<br>'+\
'The new database is stored in the file <b>'+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)
@ -583,6 +600,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):
def __init__(self, parent):
@ -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):

View File

@ -87,6 +87,7 @@ 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)'),
@ -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()
@ -283,6 +284,12 @@ class Main(MainWindow, Ui_MainWindow):
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:
self.library_view.setCurrentIndex(self.library_view.currentIndex())
@ -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):