mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Fix #2573 (Crash when "Click to browse books by Tags")
This commit is contained in:
commit
859fd352f1
@ -696,7 +696,7 @@
|
|||||||
<customwidget>
|
<customwidget>
|
||||||
<class>TagsView</class>
|
<class>TagsView</class>
|
||||||
<extends>QTreeView</extends>
|
<extends>QTreeView</extends>
|
||||||
<header>tags.h</header>
|
<header>calibre/gui2/tag_view.h</header>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
<resources>
|
<resources>
|
||||||
|
151
src/calibre/gui2/tag_view.py
Normal file
151
src/calibre/gui2/tag_view.py
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
'''
|
||||||
|
Browsing book collection by tags.
|
||||||
|
'''
|
||||||
|
from PyQt4.Qt import QStandardItemModel, Qt, QTreeView, QStandardItem, \
|
||||||
|
QFont, SIGNAL, QSize, QIcon, QPoint, QPixmap
|
||||||
|
from calibre.gui2 import config
|
||||||
|
|
||||||
|
class TagsView(QTreeView):
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
QTreeView.__init__(self, *args)
|
||||||
|
self.setUniformRowHeights(True)
|
||||||
|
self.setCursor(Qt.PointingHandCursor)
|
||||||
|
self.setIconSize(QSize(30, 30))
|
||||||
|
|
||||||
|
def set_database(self, db, match_all, popularity):
|
||||||
|
self._model = TagsModel(db)
|
||||||
|
self.popularity = popularity
|
||||||
|
self.match_all = match_all
|
||||||
|
self.setModel(self._model)
|
||||||
|
self.connect(self, SIGNAL('clicked(QModelIndex)'), self.toggle)
|
||||||
|
self.popularity.setChecked(config['sort_by_popularity'])
|
||||||
|
self.connect(self.popularity, SIGNAL('stateChanged(int)'), self.sort_changed)
|
||||||
|
|
||||||
|
def sort_changed(self, state):
|
||||||
|
config.set('sort_by_popularity', state == Qt.Checked)
|
||||||
|
self.model().refresh()
|
||||||
|
|
||||||
|
def toggle(self, index):
|
||||||
|
if self._model.toggle(index):
|
||||||
|
self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
|
||||||
|
self._model.tokens(), self.match_all.isChecked())
|
||||||
|
|
||||||
|
def recount(self, *args):
|
||||||
|
ci = self.currentIndex()
|
||||||
|
if not ci.isValid():
|
||||||
|
ci = self.indexAt(QPoint(10, 10))
|
||||||
|
self.model().refresh()
|
||||||
|
if ci.isValid():
|
||||||
|
self.scrollTo(ci, QTreeView.PositionAtTop)
|
||||||
|
|
||||||
|
class CategoryItem(QStandardItem):
|
||||||
|
|
||||||
|
def __init__(self, category, display_text, tags, icon, font, icon_map):
|
||||||
|
self.category = category
|
||||||
|
self.tags = tags
|
||||||
|
QStandardItem.__init__(self, icon, display_text)
|
||||||
|
self.setFont(font)
|
||||||
|
self.setSelectable(False)
|
||||||
|
self.setSizeHint(QSize(100, 40))
|
||||||
|
self.setEditable(False)
|
||||||
|
for tag in tags:
|
||||||
|
self.appendRow(TagItem(tag, icon_map))
|
||||||
|
|
||||||
|
class TagItem(QStandardItem):
|
||||||
|
|
||||||
|
def __init__(self, tag, icon_map):
|
||||||
|
self.icon_map = icon_map
|
||||||
|
self.tag = tag
|
||||||
|
QStandardItem.__init__(self, tag.as_string())
|
||||||
|
self.set_icon()
|
||||||
|
self.setEditable(False)
|
||||||
|
self.setSelectable(False)
|
||||||
|
|
||||||
|
def toggle(self):
|
||||||
|
self.tag.state = (self.tag.state + 1)%3
|
||||||
|
self.set_icon()
|
||||||
|
|
||||||
|
def set_icon(self):
|
||||||
|
self.setIcon(self.icon_map[self.tag.state])
|
||||||
|
|
||||||
|
|
||||||
|
class TagsModel(QStandardItemModel):
|
||||||
|
|
||||||
|
categories = [_('Authors'), _('Series'), _('Formats'), _('Publishers'), _('News'), _('Tags')]
|
||||||
|
row_map = ['author', 'series', 'format', 'publisher', 'news', 'tag']
|
||||||
|
|
||||||
|
def __init__(self, db):
|
||||||
|
self.cmap = tuple(map(QIcon, [':/images/user_profile.svg',
|
||||||
|
':/images/series.svg', ':/images/book.svg', ':/images/publisher.png',
|
||||||
|
':/images/news.svg', ':/images/tags.svg']))
|
||||||
|
p = QPixmap(30, 30)
|
||||||
|
p.fill(Qt.transparent)
|
||||||
|
self.icon_map = [QIcon(p), QIcon(':/images/plus.svg'),
|
||||||
|
QIcon(':/images/minus.svg')]
|
||||||
|
QStandardItemModel.__init__(self)
|
||||||
|
self.db = db
|
||||||
|
self.ignore_next_search = False
|
||||||
|
self._data = {}
|
||||||
|
self.bold_font = QFont()
|
||||||
|
self.bold_font.setBold(True)
|
||||||
|
self.refresh()
|
||||||
|
self.db.add_listener(self.database_changed)
|
||||||
|
|
||||||
|
def database_changed(self, event, ids):
|
||||||
|
self.refresh()
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
old_data = self._data
|
||||||
|
self._data = self.db.get_categories(config['sort_by_popularity'])
|
||||||
|
for key in old_data.keys():
|
||||||
|
for tag in old_data[key]:
|
||||||
|
try:
|
||||||
|
index = self._data[key].index(tag)
|
||||||
|
if index > -1:
|
||||||
|
self._data[key][index].state = tag.state
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
self.clear()
|
||||||
|
root = self.invisibleRootItem()
|
||||||
|
for r, category in enumerate(self.row_map):
|
||||||
|
tags = self._data.get(category, [])
|
||||||
|
root.appendRow(CategoryItem(category, self.categories[r],
|
||||||
|
self._data[category], self.cmap[r], self.bold_font, self.icon_map))
|
||||||
|
#self.reset()
|
||||||
|
|
||||||
|
|
||||||
|
def reinit(self, *args, **kwargs):
|
||||||
|
if not self.ignore_next_search:
|
||||||
|
for category in self._data.values():
|
||||||
|
for tag in category:
|
||||||
|
tag.state = 0
|
||||||
|
self.reset()
|
||||||
|
self.ignore_next_search = False
|
||||||
|
|
||||||
|
def toggle(self, index):
|
||||||
|
if index.parent().isValid():
|
||||||
|
category = self.row_map[index.parent().row()]
|
||||||
|
tag = self._data[category][index.row()]
|
||||||
|
self.invisibleRootItem().child(index.parent().row()).child(index.row()).toggle()
|
||||||
|
self.ignore_next_search = True
|
||||||
|
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'), index, index)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def tokens(self):
|
||||||
|
ans = []
|
||||||
|
for key in self.row_map:
|
||||||
|
for tag in self._data[key]:
|
||||||
|
category = key if key != 'news' else 'tag'
|
||||||
|
if tag.state > 0:
|
||||||
|
prefix = ' not ' if tag.state == 2 else ''
|
||||||
|
ans.append('%s%s:"%s"'%(prefix, category, tag))
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
__license__ = 'GPL v3'
|
|
||||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
|
||||||
__docformat__ = 'restructuredtext en'
|
|
||||||
|
|
||||||
'''
|
|
||||||
Browsing book collection by tags.
|
|
||||||
'''
|
|
||||||
from PyQt4.Qt import QAbstractItemModel, Qt, QVariant, QTreeView, QModelIndex, \
|
|
||||||
QFont, SIGNAL, QSize, QColor, QIcon, QPoint
|
|
||||||
from calibre.gui2 import config
|
|
||||||
NONE = QVariant()
|
|
||||||
|
|
||||||
class TagsView(QTreeView):
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
QTreeView.__init__(self, *args)
|
|
||||||
self.setUniformRowHeights(True)
|
|
||||||
self.setCursor(Qt.PointingHandCursor)
|
|
||||||
self.setIconSize(QSize(30, 30))
|
|
||||||
|
|
||||||
def set_database(self, db, match_all, popularity):
|
|
||||||
self._model = TagsModel(db)
|
|
||||||
self.popularity = popularity
|
|
||||||
self.match_all = match_all
|
|
||||||
self.setModel(self._model)
|
|
||||||
self.connect(self, SIGNAL('clicked(QModelIndex)'), self.toggle)
|
|
||||||
self.popularity.setChecked(config['sort_by_popularity'])
|
|
||||||
self.connect(self.popularity, SIGNAL('stateChanged(int)'), self.sort_changed)
|
|
||||||
|
|
||||||
def sort_changed(self, state):
|
|
||||||
config.set('sort_by_popularity', state == Qt.Checked)
|
|
||||||
self.model().refresh()
|
|
||||||
|
|
||||||
def toggle(self, index):
|
|
||||||
if self._model.toggle(index):
|
|
||||||
self.emit(SIGNAL('tags_marked(PyQt_PyObject, PyQt_PyObject)'),
|
|
||||||
self._model.tokens(), self.match_all.isChecked())
|
|
||||||
|
|
||||||
def recount(self, *args):
|
|
||||||
ci = self.currentIndex()
|
|
||||||
if not ci.isValid():
|
|
||||||
ci = self.indexAt(QPoint(10, 10))
|
|
||||||
self.model().refresh()
|
|
||||||
if ci.isValid():
|
|
||||||
self.scrollTo(ci, QTreeView.PositionAtTop)
|
|
||||||
|
|
||||||
class TagsModel(QAbstractItemModel):
|
|
||||||
|
|
||||||
categories = [_('Authors'), _('Series'), _('Formats'), _('Publishers'), _('News'), _('Tags')]
|
|
||||||
row_map = {0: 'author', 1:'series', 2:'format', 3:'publisher', 4:'news', 5:'tag'}
|
|
||||||
|
|
||||||
def __init__(self, db):
|
|
||||||
QAbstractItemModel.__init__(self)
|
|
||||||
self.db = db
|
|
||||||
self.ignore_next_search = False
|
|
||||||
self._data = {}
|
|
||||||
self.refresh()
|
|
||||||
self.bold_font = QFont()
|
|
||||||
self.bold_font.setBold(True)
|
|
||||||
self.bold_font = QVariant(self.bold_font)
|
|
||||||
self.status_map = [QColor(200,200,200, 0), QIcon(':/images/plus.svg'), QIcon(':/images/minus.svg')]
|
|
||||||
self.status_map = list(map(QVariant, self.status_map))
|
|
||||||
self.cmap = [QIcon(':/images/user_profile.svg'), QIcon(':/images/series.svg'), QIcon(':/images/book.svg'), QIcon(':/images/publisher.png'), QIcon(':/images/news.svg'), QIcon(':/images/tags.svg')]
|
|
||||||
self.cmap = list(map(QVariant, self.cmap))
|
|
||||||
self.db.add_listener(self.database_changed)
|
|
||||||
|
|
||||||
def database_changed(self, event, ids):
|
|
||||||
self.refresh()
|
|
||||||
|
|
||||||
def refresh(self):
|
|
||||||
old_data = self._data
|
|
||||||
self._data = self.db.get_categories(config['sort_by_popularity'])
|
|
||||||
for key in old_data.keys():
|
|
||||||
for tag in old_data[key]:
|
|
||||||
try:
|
|
||||||
index = self._data[key].index(tag)
|
|
||||||
if index > -1:
|
|
||||||
self._data[key][index].state = tag.state
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def reinit(self, *args, **kwargs):
|
|
||||||
if not self.ignore_next_search:
|
|
||||||
for category in self._data.values():
|
|
||||||
for tag in category:
|
|
||||||
tag.state = 0
|
|
||||||
self.reset()
|
|
||||||
self.ignore_next_search = False
|
|
||||||
|
|
||||||
def toggle(self, index):
|
|
||||||
if index.parent().isValid():
|
|
||||||
category = self.row_map[index.parent().row()]
|
|
||||||
tag = self._data[category][index.row()]
|
|
||||||
tag.state = (tag.state + 1)%3
|
|
||||||
self.ignore_next_search = True
|
|
||||||
self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'), index, index)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def tokens(self):
|
|
||||||
ans = []
|
|
||||||
for key in self.row_map.values():
|
|
||||||
for tag in self._data[key]:
|
|
||||||
category = key if key != 'news' else 'tag'
|
|
||||||
if tag.state > 0:
|
|
||||||
prefix = ' not ' if tag.state == 2 else ''
|
|
||||||
ans.append('%s%s:"%s"'%(prefix, category, tag))
|
|
||||||
return ans
|
|
||||||
|
|
||||||
def index(self, row, col, parent=QModelIndex()):
|
|
||||||
if parent.isValid():
|
|
||||||
if parent.parent().isValid(): # parent is a tag
|
|
||||||
return QModelIndex()
|
|
||||||
try:
|
|
||||||
category = self.row_map[parent.row()]
|
|
||||||
except KeyError:
|
|
||||||
return QModelIndex()
|
|
||||||
if col == 0 and row < len(self._data[category]):
|
|
||||||
return self.createIndex(row, col, parent.row())
|
|
||||||
return QModelIndex()
|
|
||||||
if col == 0 and row < len(self.categories):
|
|
||||||
return self.createIndex(row, col, -1)
|
|
||||||
return QModelIndex()
|
|
||||||
|
|
||||||
def parent(self, index):
|
|
||||||
if not index.isValid() or index.internalId() < 0:
|
|
||||||
return QModelIndex()
|
|
||||||
return self.createIndex(index.internalId(), 0, -1)
|
|
||||||
|
|
||||||
def rowCount(self, parent):
|
|
||||||
if not parent or not parent.isValid():
|
|
||||||
return len(self.categories)
|
|
||||||
if not parent.parent().isValid():
|
|
||||||
return len(self._data[self.row_map[parent.row()]])
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def columnCount(self, parent):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def flags(self, index):
|
|
||||||
if not index.isValid():
|
|
||||||
return Qt.NoItemFlags
|
|
||||||
return Qt.ItemIsEnabled
|
|
||||||
|
|
||||||
def category_data(self, index, role):
|
|
||||||
if role == Qt.DisplayRole:
|
|
||||||
row = index.row()
|
|
||||||
return QVariant(self.categories[row])
|
|
||||||
if role == Qt.FontRole:
|
|
||||||
return self.bold_font
|
|
||||||
if role == Qt.SizeHintRole:
|
|
||||||
return QVariant(QSize(100, 40))
|
|
||||||
if role == Qt.DecorationRole:
|
|
||||||
return self.cmap[index.row()]
|
|
||||||
return NONE
|
|
||||||
|
|
||||||
def tag_data(self, index, role):
|
|
||||||
category = self.row_map[index.parent().row()]
|
|
||||||
if role == Qt.DisplayRole:
|
|
||||||
return QVariant(self._data[category][index.row()].as_string())
|
|
||||||
if role == Qt.DecorationRole:
|
|
||||||
return self.status_map[self._data[category][index.row()].state]
|
|
||||||
return NONE
|
|
||||||
|
|
||||||
|
|
||||||
def data(self, index, role):
|
|
||||||
if not index.parent().isValid():
|
|
||||||
return self.category_data(index, role)
|
|
||||||
if not index.parent().parent().isValid():
|
|
||||||
return self.tag_data(index, role)
|
|
||||||
return NONE
|
|
@ -334,7 +334,9 @@ def post_install():
|
|||||||
os.chdir(config_dir)
|
os.chdir(config_dir)
|
||||||
for f in os.listdir('.'):
|
for f in os.listdir('.'):
|
||||||
if os.stat(f).st_uid == 0:
|
if os.stat(f).st_uid == 0:
|
||||||
os.unlink(f)
|
os.rmdir(f) if os.path.isdir(f) else os.unlink(f)
|
||||||
|
if os.stat(config_dir).st_uid == 0:
|
||||||
|
os.rmdir(config_dir)
|
||||||
|
|
||||||
def binary_install():
|
def binary_install():
|
||||||
manifest = os.path.join(getattr(sys, 'frozen_path'), 'manifest')
|
manifest = os.path.join(getattr(sys, 'frozen_path'), 'manifest')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user