home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_1316 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  28.0 KB  |  794 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. __license__ = 'GPL v3'
  5. __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
  6. __docformat__ = 'restructuredtext en'
  7. from itertools import izip
  8. from functools import partial
  9. from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, QFont, QSize, QIcon, QPoint, QVBoxLayout, QComboBox, QAbstractItemModel, QVariant, QModelIndex, QMenu, QPushButton, QWidget, QItemDelegate
  10. from calibre.ebooks.metadata import title_sort
  11. from calibre.gui2 import config, NONE
  12. from calibre.library.field_metadata import TagsIcons
  13. from calibre.utils.search_query_parser import saved_searches
  14. from calibre.gui2 import error_dialog
  15. from calibre.gui2.dialogs.tag_categories import TagCategories
  16. from calibre.gui2.dialogs.tag_list_editor import TagListEditor
  17. from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog
  18.  
  19. class TagDelegate(QItemDelegate):
  20.     
  21.     def paint(self, painter, option, index):
  22.         item = index.internalPointer()
  23.         if item.type != TagTreeItem.TAG:
  24.             QItemDelegate.paint(self, painter, option, index)
  25.             return None
  26.         r = option.rect
  27.         model = self.parent().model()
  28.         icon = model.data(index, Qt.DecorationRole).toPyObject()
  29.         painter.save()
  30.         if item.tag.state != 0 and not config['show_avg_rating'] or item.tag.avg_rating is None:
  31.             icon.paint(painter, r, Qt.AlignLeft)
  32.         else:
  33.             painter.setOpacity(0.3)
  34.             icon.paint(painter, r, Qt.AlignLeft)
  35.             painter.setOpacity(1)
  36.             rating = item.tag.avg_rating
  37.             painter.setClipRect(r.left(), r.bottom() - int(r.height() * (rating / 5)), r.width(), r.height())
  38.             icon.paint(painter, r, Qt.AlignLeft)
  39.             painter.setClipRect(r)
  40.         r.setLeft(r.left() + r.height() + 3)
  41.         painter.drawText(r, Qt.AlignLeft | Qt.AlignVCenter, model.data(index, Qt.DisplayRole).toString())
  42.         painter.restore()
  43.  
  44.  
  45.  
  46. class TagsView(QTreeView):
  47.     refresh_required = pyqtSignal()
  48.     tags_marked = pyqtSignal(object, object)
  49.     user_category_edit = pyqtSignal(object)
  50.     tag_list_edit = pyqtSignal(object, object)
  51.     saved_search_edit = pyqtSignal(object)
  52.     author_sort_edit = pyqtSignal(object, object)
  53.     tag_item_renamed = pyqtSignal()
  54.     search_item_renamed = pyqtSignal()
  55.     
  56.     def __init__(self, parent = None):
  57.         QTreeView.__init__(self, parent = None)
  58.         self.tag_match = None
  59.         self.setUniformRowHeights(True)
  60.         self.setCursor(Qt.PointingHandCursor)
  61.         self.setIconSize(QSize(30, 30))
  62.         self.setTabKeyNavigation(True)
  63.         self.setAlternatingRowColors(True)
  64.         self.setAnimated(True)
  65.         self.setHeaderHidden(True)
  66.         self.setItemDelegate(TagDelegate(self))
  67.  
  68.     
  69.     def set_database(self, db, tag_match, sort_by):
  70.         self.hidden_categories = config['tag_browser_hidden_categories']
  71.         self._model = TagsModel(db, parent = self, hidden_categories = self.hidden_categories, search_restriction = None)
  72.         self.sort_by = sort_by
  73.         self.tag_match = tag_match
  74.         self.db = db
  75.         self.search_restriction = None
  76.         self.setModel(self._model)
  77.         self.setContextMenuPolicy(Qt.CustomContextMenu)
  78.         self.clicked.connect(self.toggle)
  79.         self.customContextMenuRequested.connect(self.show_context_menu)
  80.         pop = config['sort_tags_by']
  81.         self.sort_by.setCurrentIndex(self.db.CATEGORY_SORTS.index(pop))
  82.         self.sort_by.currentIndexChanged.connect(self.sort_changed)
  83.         self.refresh_required.connect(self.recount, type = Qt.QueuedConnection)
  84.         db.add_listener(self.database_changed)
  85.  
  86.     
  87.     def database_changed(self, event, ids):
  88.         self.refresh_required.emit()
  89.  
  90.     
  91.     def match_all(self):
  92.         if self.tag_match:
  93.             pass
  94.         return self.tag_match.currentIndex() > 0
  95.  
  96.     match_all = property(match_all)
  97.     
  98.     def sort_changed(self, pop):
  99.         config.set('sort_tags_by', self.db.CATEGORY_SORTS[pop])
  100.         self.recount()
  101.  
  102.     
  103.     def set_search_restriction(self, s):
  104.         if s:
  105.             self.search_restriction = s
  106.         else:
  107.             self.search_restriction = None
  108.         self.set_new_model()
  109.  
  110.     
  111.     def mouseReleaseEvent(self, event):
  112.         if event.button() == Qt.LeftButton:
  113.             QTreeView.mouseReleaseEvent(self, event)
  114.         
  115.  
  116.     
  117.     def mouseDoubleClickEvent(self, event):
  118.         pass
  119.  
  120.     
  121.     def toggle(self, index):
  122.         modifiers = int(QApplication.keyboardModifiers())
  123.         exclusive = modifiers not in (Qt.CTRL, Qt.SHIFT)
  124.         if self._model.toggle(index, exclusive):
  125.             self.tags_marked.emit(self._model.tokens(), self.match_all)
  126.         
  127.  
  128.     
  129.     def context_menu_handler(self, action = None, category = None, key = None, index = None):
  130.         if not action:
  131.             return None
  132.         
  133.         try:
  134.             if action == 'edit_item':
  135.                 self.edit(index)
  136.                 return None
  137.             if action == 'open_editor':
  138.                 self.tag_list_edit.emit(category, key)
  139.                 return None
  140.             if action == 'manage_categories':
  141.                 self.user_category_edit.emit(category)
  142.                 return None
  143.             if action == 'manage_searches':
  144.                 self.saved_search_edit.emit(category)
  145.                 return None
  146.             if action == 'edit_author_sort':
  147.                 self.author_sort_edit.emit(self, index)
  148.                 return None
  149.             if action == 'hide':
  150.                 self.hidden_categories.add(category)
  151.             elif action == 'show':
  152.                 self.hidden_categories.discard(category)
  153.             elif action == 'defaults':
  154.                 self.hidden_categories.clear()
  155.             
  156.             config.set('tag_browser_hidden_categories', self.hidden_categories)
  157.             self.set_new_model()
  158.         except:
  159.             action
  160.             return None
  161.  
  162.  
  163.     
  164.     def show_context_menu(self, point):
  165.         index = self.indexAt(point)
  166.         if not index.isValid():
  167.             return False
  168.         item = index.internalPointer()
  169.         tag_name = ''
  170.         if item.type == TagTreeItem.TAG:
  171.             tag_item = item
  172.             tag_name = item.tag.name
  173.             tag_id = item.tag.id
  174.             item = item.parent
  175.         
  176.         if item.type == TagTreeItem.CATEGORY:
  177.             category = unicode(item.name.toString())
  178.             key = item.category_key
  179.             if key not in self.db.field_metadata:
  180.                 return True
  181.             self.context_menu = QMenu(self)
  182.             if tag_name:
  183.                 pass
  184.             self.context_menu.addAction(_('Hide category %s') % category, partial(self.context_menu_handler, action = 'hide', category = category))
  185.             if self.hidden_categories:
  186.                 m = self.context_menu.addMenu(_('Show category'))
  187.                 for col in sorted(self.hidden_categories, cmp = (lambda x, y: cmp(x.lower(), y.lower()))):
  188.                     m.addAction(col, partial(self.context_menu_handler, action = 'show', category = col))
  189.                 
  190.                 self.context_menu.addAction(_('Show all categories'), partial(self.context_menu_handler, action = 'defaults'))
  191.             
  192.             self.context_menu.addSeparator()
  193.             if key in ('tags', 'publisher', 'series') or self.db.field_metadata[key]['is_custom']:
  194.                 self.context_menu.addAction(_('Manage %s') % category, partial(self.context_menu_handler, action = 'open_editor', category = tag_name, key = key))
  195.             elif key == 'authors':
  196.                 self.context_menu.addAction(_('Manage %s') % category, partial(self.context_menu_handler, action = 'edit_author_sort'))
  197.             elif key == 'search':
  198.                 self.context_menu.addAction(_('Manage Saved Searches'), partial(self.context_menu_handler, action = 'manage_searches', category = tag_name))
  199.             
  200.             self.context_menu.addSeparator()
  201.             if category in self.db.prefs.get('user_categories', { }).keys():
  202.                 self.context_menu.addAction(_('Manage User Categories'), partial(self.context_menu_handler, action = 'manage_categories', category = category))
  203.             else:
  204.                 self.context_menu.addAction(_('Manage User Categories'), partial(self.context_menu_handler, action = 'manage_categories', category = None))
  205.             self.context_menu.popup(self.mapToGlobal(point))
  206.         
  207.         return True
  208.  
  209.     
  210.     def clear(self):
  211.         if self.model():
  212.             self.model().clear_state()
  213.         
  214.  
  215.     
  216.     def is_visible(self, idx):
  217.         item = idx.internalPointer()
  218.         if getattr(item, 'type', None) == TagTreeItem.TAG:
  219.             idx = idx.parent()
  220.         
  221.         return self.isExpanded(idx)
  222.  
  223.     
  224.     def recount(self, *args):
  225.         ci = self.currentIndex()
  226.         if not ci.isValid():
  227.             ci = self.indexAt(QPoint(10, 10))
  228.         
  229.         path = None if self.is_visible(ci) else None
  230.         
  231.         try:
  232.             if not self.model().refresh():
  233.                 self.set_new_model()
  234.                 path = None
  235.         except:
  236.             pass
  237.  
  238.         if path:
  239.             idx = self.model().index_for_path(path)
  240.             if idx.isValid():
  241.                 self.setCurrentIndex(idx)
  242.                 self.scrollTo(idx, QTreeView.PositionAtCenter)
  243.             
  244.         
  245.  
  246.     
  247.     def set_new_model(self):
  248.         
  249.         try:
  250.             self._model = TagsModel(self.db, parent = self, hidden_categories = self.hidden_categories, search_restriction = self.search_restriction)
  251.             self.setModel(self._model)
  252.         except:
  253.             self._model = None
  254.             self.setModel(None)
  255.  
  256.  
  257.  
  258.  
  259. class TagTreeItem(object):
  260.     CATEGORY = 0
  261.     TAG = 1
  262.     ROOT = 2
  263.     
  264.     def __init__(self, data = None, category_icon = None, icon_map = None, parent = None, tooltip = None, category_key = None):
  265.         self.parent = parent
  266.         self.children = []
  267.         if self.parent is not None:
  268.             self.parent.append(self)
  269.         
  270.         if data is None:
  271.             self.type = self.ROOT
  272.         elif category_icon is None:
  273.             pass
  274.         
  275.         self.type = self.CATEGORY
  276.         if self.type == self.CATEGORY:
  277.             (self.name, self.icon) = map(QVariant, (data, category_icon))
  278.             self.py_name = data
  279.             self.bold_font = QFont()
  280.             self.bold_font.setBold(True)
  281.             self.bold_font = QVariant(self.bold_font)
  282.             self.category_key = category_key
  283.         elif self.type == self.TAG:
  284.             icon_map[0] = data.icon
  285.             self.tag = data
  286.             self.icon_state_map = list(map(QVariant, icon_map))
  287.         
  288.         self.tooltip = tooltip
  289.  
  290.     
  291.     def __str__(self):
  292.         if self.type == self.ROOT:
  293.             return 'ROOT'
  294.         if self.type == self.CATEGORY:
  295.             return 'CATEGORY:' + str(QVariant.toString(self.name)) + ':%d' % len(self.children)
  296.         return 'TAG:' + self.tag.name
  297.  
  298.     
  299.     def row(self):
  300.         if self.parent is not None:
  301.             return self.parent.children.index(self)
  302.         return 0
  303.  
  304.     
  305.     def append(self, child):
  306.         child.parent = self
  307.         self.children.append(child)
  308.  
  309.     
  310.     def data(self, role):
  311.         if self.type == self.TAG:
  312.             return self.tag_data(role)
  313.         if self.type == self.CATEGORY:
  314.             return self.category_data(role)
  315.         return NONE
  316.  
  317.     
  318.     def category_data(self, role):
  319.         if role == Qt.DisplayRole:
  320.             return QVariant(self.py_name + ' [%d]' % len(self.children))
  321.         if role == Qt.DecorationRole:
  322.             return self.icon
  323.         if role == Qt.FontRole:
  324.             return self.bold_font
  325.         if role == Qt.ToolTipRole and self.tooltip is not None:
  326.             return QVariant(self.tooltip)
  327.         return NONE
  328.  
  329.     
  330.     def tag_data(self, role):
  331.         if role == Qt.DisplayRole:
  332.             if self.tag.count == 0:
  333.                 return QVariant('%s' % self.tag.name)
  334.             return QVariant('[%d] %s' % (self.tag.count, self.tag.name))
  335.         role == Qt.DisplayRole
  336.         if role == Qt.EditRole:
  337.             return QVariant(self.tag.name)
  338.         if role == Qt.DecorationRole:
  339.             return self.icon_state_map[self.tag.state]
  340.         if role == Qt.ToolTipRole and self.tag.tooltip is not None:
  341.             return QVariant(self.tag.tooltip)
  342.         return NONE
  343.  
  344.     
  345.     def toggle(self):
  346.         if self.type == self.TAG:
  347.             self.tag.state = (self.tag.state + 1) % 3
  348.         
  349.  
  350.  
  351.  
  352. class TagsModel(QAbstractItemModel):
  353.     
  354.     def __init__(self, db, parent, hidden_categories = None, search_restriction = None):
  355.         QAbstractItemModel.__init__(self, parent)
  356.         self.category_icon_map = TagsIcons({
  357.             'authors': QIcon(I('user_profile.svg')),
  358.             'series': QIcon(I('series.svg')),
  359.             'formats': QIcon(I('book.svg')),
  360.             'publisher': QIcon(I('publisher.png')),
  361.             'rating': QIcon(I('star.png')),
  362.             'news': QIcon(I('news.svg')),
  363.             'tags': QIcon(I('tags.svg')),
  364.             ':custom': QIcon(I('column.svg')),
  365.             ':user': QIcon(I('drawer.svg')),
  366.             'search': QIcon(I('search.svg')) })
  367.         self.categories_with_ratings = [
  368.             'authors',
  369.             'series',
  370.             'publisher',
  371.             'tags']
  372.         self.icon_state_map = [
  373.             None,
  374.             QIcon(I('plus.svg')),
  375.             QIcon(I('minus.svg'))]
  376.         self.db = db
  377.         self.tags_view = parent
  378.         self.hidden_categories = hidden_categories
  379.         self.search_restriction = search_restriction
  380.         self.row_map = []
  381.         data = self.get_node_tree(config['sort_tags_by'])
  382.         self.root_item = TagTreeItem()
  383.         for i, r in enumerate(self.row_map):
  384.             if self.hidden_categories and self.categories[i] in self.hidden_categories:
  385.                 continue
  386.             
  387.             if self.db.field_metadata[r]['kind'] != 'user':
  388.                 tt = _('The lookup/search name is "{0}"').format(r)
  389.             else:
  390.                 tt = ''
  391.             c = TagTreeItem(parent = self.root_item, data = self.categories[i], category_icon = self.category_icon_map[r], tooltip = tt, category_key = r)
  392.             for tag in data[r]:
  393.                 if r not in self.categories_with_ratings and not self.db.field_metadata[r]['is_custom'] and not (self.db.field_metadata[r]['kind'] == 'user'):
  394.                     tag.avg_rating = None
  395.                 
  396.                 TagTreeItem(parent = c, data = tag, icon_map = self.icon_state_map)
  397.             
  398.         
  399.  
  400.     
  401.     def set_search_restriction(self, s):
  402.         self.search_restriction = s
  403.  
  404.     
  405.     def get_node_tree(self, sort):
  406.         old_row_map = self.row_map[:]
  407.         self.row_map = []
  408.         self.categories = []
  409.         tb_cats = self.db.field_metadata
  410.         for k in tb_cats.keys():
  411.             if tb_cats[k]['kind'] in ('user', 'search'):
  412.                 del tb_cats[k]
  413.                 continue
  414.         
  415.         for user_cat in sorted(self.db.prefs.get('user_categories', { }).keys()):
  416.             cat_name = user_cat + ':'
  417.             tb_cats.add_user_category(label = cat_name, name = user_cat)
  418.         
  419.         if len(saved_searches().names()):
  420.             tb_cats.add_search_category(label = 'search', name = _('Searches'))
  421.         
  422.         if self.search_restriction:
  423.             data = self.db.get_categories(sort = sort, icon_map = self.category_icon_map, ids = self.db.search('', return_matches = True))
  424.         else:
  425.             data = self.db.get_categories(sort = sort, icon_map = self.category_icon_map)
  426.         tb_categories = self.db.field_metadata
  427.         for category in tb_categories:
  428.             if category in data:
  429.                 self.row_map.append(category)
  430.                 self.categories.append(tb_categories[category]['name'])
  431.                 continue
  432.         
  433.         if len(old_row_map) != 0 and len(old_row_map) != len(self.row_map):
  434.             return None
  435.         return data
  436.  
  437.     
  438.     def refresh(self):
  439.         data = self.get_node_tree(config['sort_tags_by'])
  440.         if data is None:
  441.             return False
  442.         row_index = -1
  443.         for i, r in enumerate(self.row_map):
  444.             if self.hidden_categories and self.categories[i] in self.hidden_categories:
  445.                 continue
  446.             
  447.             row_index += 1
  448.             category = self.root_item.children[row_index]
  449.             names = [ t.tag.name for t in category.children ]
  450.             states = [ t.tag.state for t in category.children ]
  451.             state_map = dict(izip(names, states))
  452.             category_index = self.index(row_index, 0, QModelIndex())
  453.             if len(data[r]) > 0:
  454.                 self.beginInsertRows(category_index, 0, len(data[r]) - 1)
  455.                 for tag in data[r]:
  456.                     tag.state = state_map.get(tag.name, 0)
  457.                     t = TagTreeItem(parent = category, data = tag, icon_map = self.icon_state_map)
  458.                 
  459.                 self.endInsertRows()
  460.                 continue
  461.             None if r not in self.categories_with_ratings and not self.db.field_metadata[r]['is_custom'] and not (self.db.field_metadata[r]['kind'] == 'user') else []
  462.         
  463.         return True
  464.  
  465.     
  466.     def columnCount(self, parent):
  467.         return 1
  468.  
  469.     
  470.     def data(self, index, role):
  471.         if not index.isValid():
  472.             return NONE
  473.         item = index.internalPointer()
  474.         return item.data(role)
  475.  
  476.     
  477.     def setData(self, index, value, role = Qt.EditRole):
  478.         if not index.isValid():
  479.             return NONE
  480.         path = self.path_for_index(self.parent(index))
  481.         val = unicode(value.toString())
  482.         if not val:
  483.             error_dialog(self.tags_view, _('Item is blank'), _('An item cannot be set to nothing. Delete it instead.')).exec_()
  484.             return False
  485.         item = index.internalPointer()
  486.         key = item.parent.category_key
  487.         if key not in self.db.field_metadata:
  488.             return None
  489.         if key == 'search':
  490.             if val in saved_searches().names():
  491.                 error_dialog(self.tags_view, _('Duplicate search name'), _('The saved search name %s is already used.') % val).exec_()
  492.                 return False
  493.             saved_searches().rename(unicode(item.data(role).toString()), val)
  494.             self.tags_view.search_item_renamed.emit()
  495.         elif key == 'series':
  496.             self.db.rename_series(item.tag.id, val)
  497.         elif key == 'publisher':
  498.             self.db.rename_publisher(item.tag.id, val)
  499.         elif key == 'tags':
  500.             self.db.rename_tag(item.tag.id, val)
  501.         elif key == 'authors':
  502.             self.db.rename_author(item.tag.id, val)
  503.         elif self.db.field_metadata[key]['is_custom']:
  504.             self.db.rename_custom_item(item.tag.id, val, label = self.db.field_metadata[key]['label'])
  505.         
  506.         self.tags_view.tag_item_renamed.emit()
  507.         item.tag.name = val
  508.         self.refresh()
  509.         if path:
  510.             idx = self.index_for_path(path)
  511.             if idx.isValid():
  512.                 self.tags_view.setCurrentIndex(idx)
  513.                 self.tags_view.scrollTo(idx, QTreeView.PositionAtCenter)
  514.             
  515.         
  516.         return True
  517.  
  518.     
  519.     def headerData(self, *args):
  520.         return NONE
  521.  
  522.     
  523.     def flags(self, *args):
  524.         return Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsEditable
  525.  
  526.     
  527.     def path_for_index(self, index):
  528.         ans = []
  529.         while index.isValid():
  530.             ans.append(index.row())
  531.             index = self.parent(index)
  532.         ans.reverse()
  533.         return ans
  534.  
  535.     
  536.     def index_for_path(self, path):
  537.         parent = QModelIndex()
  538.         for i in path:
  539.             parent = self.index(i, 0, parent)
  540.             if not parent.isValid():
  541.                 return QModelIndex()
  542.         
  543.         return parent
  544.  
  545.     
  546.     def index(self, row, column, parent):
  547.         if not self.hasIndex(row, column, parent):
  548.             return QModelIndex()
  549.         if not parent.isValid():
  550.             parent_item = self.root_item
  551.         else:
  552.             parent_item = parent.internalPointer()
  553.         
  554.         try:
  555.             child_item = parent_item.children[row]
  556.         except IndexError:
  557.             return QModelIndex()
  558.  
  559.         ans = self.createIndex(row, column, child_item)
  560.         return ans
  561.  
  562.     
  563.     def parent(self, index):
  564.         if not index.isValid():
  565.             return QModelIndex()
  566.         child_item = index.internalPointer()
  567.         parent_item = getattr(child_item, 'parent', None)
  568.         if parent_item is self.root_item or parent_item is None:
  569.             return QModelIndex()
  570.         ans = self.createIndex(parent_item.row(), 0, parent_item)
  571.         return ans
  572.  
  573.     
  574.     def rowCount(self, parent):
  575.         if parent.column() > 0:
  576.             return 0
  577.         if not parent.isValid():
  578.             parent_item = self.root_item
  579.         else:
  580.             parent_item = parent.internalPointer()
  581.         return len(parent_item.children)
  582.  
  583.     
  584.     def reset_all_states(self, except_ = None):
  585.         update_list = []
  586.         for i in xrange(self.rowCount(QModelIndex())):
  587.             category_index = self.index(i, 0, QModelIndex())
  588.             for j in xrange(self.rowCount(category_index)):
  589.                 tag_index = self.index(j, 0, category_index)
  590.                 tag_item = tag_index.internalPointer()
  591.                 tag = tag_item.tag
  592.                 if tag is except_:
  593.                     self.dataChanged.emit(tag_index, tag_index)
  594.                     continue
  595.                 
  596.                 if tag.state != 0 or tag in update_list:
  597.                     tag.state = 0
  598.                     update_list.append(tag)
  599.                     self.dataChanged.emit(tag_index, tag_index)
  600.                     continue
  601.             
  602.         
  603.  
  604.     
  605.     def clear_state(self):
  606.         self.reset_all_states()
  607.  
  608.     
  609.     def toggle(self, index, exclusive):
  610.         if not index.isValid():
  611.             return False
  612.         item = index.internalPointer()
  613.         if item.type == TagTreeItem.TAG:
  614.             item.toggle()
  615.             if exclusive:
  616.                 self.reset_all_states(except_ = item.tag)
  617.             
  618.             self.dataChanged.emit(index, index)
  619.             return True
  620.         return False
  621.  
  622.     
  623.     def tokens(self):
  624.         ans = []
  625.         tags_seen = set()
  626.         row_index = -1
  627.         for i, key in enumerate(self.row_map):
  628.             if self.hidden_categories and self.categories[i] in self.hidden_categories:
  629.                 continue
  630.             
  631.             row_index += 1
  632.             if key.endswith(':'):
  633.                 continue
  634.             
  635.             category_item = self.root_item.children[row_index]
  636.             for tag_item in category_item.children:
  637.                 tag = tag_item.tag
  638.                 if tag.state > 0:
  639.                     prefix = None if tag.state == 2 else ''
  640.                     category = None if key != 'news' else 'tag'
  641.                     if tag.name and tag.name[0] == u'Γÿà':
  642.                         ans.append('%s%s:%s' % (prefix, category, len(tag.name)))
  643.                     elif category == 'tags':
  644.                         if tag.name in tags_seen:
  645.                             continue
  646.                         
  647.                         tags_seen.add(tag.name)
  648.                     
  649.                     ans.append('%s%s:"=%s"' % (prefix, category, tag.name))
  650.                     continue
  651.             
  652.         
  653.         return ans
  654.  
  655.  
  656.  
  657. class TagBrowserMixin(object):
  658.     
  659.     def __init__(self, db):
  660.         self.library_view.model().count_changed_signal.connect(self.tags_view.recount)
  661.         self.tags_view.set_database(self.library_view.model().db, self.tag_match, self.sort_by)
  662.         self.tags_view.tags_marked.connect(self.search.search_from_tags)
  663.         self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
  664.         self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
  665.         self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
  666.         self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
  667.         self.tags_view.author_sort_edit.connect(self.do_author_sort_edit)
  668.         self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
  669.         self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
  670.         (self.edit_categories.clicked.connect,)((lambda x: self.do_user_categories_edit()))
  671.  
  672.     
  673.     def do_user_categories_edit(self, on_category = None):
  674.         d = TagCategories(self, self.library_view.model().db, on_category)
  675.         d.exec_()
  676.         if d.result() == d.Accepted:
  677.             self.tags_view.set_new_model()
  678.             self.tags_view.recount()
  679.         
  680.  
  681.     
  682.     def do_tags_list_edit(self, tag, category):
  683.         db = self.library_view.model().db
  684.         if category == 'tags':
  685.             result = db.get_tags_with_ids()
  686.             
  687.             compare = lambda x, y: cmp(x.lower(), y.lower())
  688.         elif category == 'series':
  689.             result = db.get_series_with_ids()
  690.             
  691.             compare = lambda x, y: cmp(title_sort(x).lower(), title_sort(y).lower())
  692.         elif category == 'publisher':
  693.             result = db.get_publishers_with_ids()
  694.             
  695.             compare = lambda x, y: cmp(x.lower(), y.lower())
  696.         else:
  697.             cc_label = None
  698.             if category in db.field_metadata:
  699.                 cc_label = db.field_metadata[category]['label']
  700.                 result = self.db.get_custom_items_with_ids(label = cc_label)
  701.             else:
  702.                 result = []
  703.             
  704.             compare = lambda x, y: cmp(x.lower(), y.lower())
  705.         d = TagListEditor(self, tag_to_match = tag, data = result, compare = compare)
  706.         d.exec_()
  707.         if d.result() == d.Accepted:
  708.             to_rename = d.to_rename
  709.             to_delete = d.to_delete
  710.             rename_func = None
  711.             if category == 'tags':
  712.                 rename_func = db.rename_tag
  713.                 delete_func = db.delete_tag_using_id
  714.             elif category == 'series':
  715.                 rename_func = db.rename_series
  716.                 delete_func = db.delete_series_using_id
  717.             elif category == 'publisher':
  718.                 rename_func = db.rename_publisher
  719.                 delete_func = db.delete_publisher_using_id
  720.             else:
  721.                 rename_func = partial(db.rename_custom_item, label = cc_label)
  722.                 delete_func = partial(db.delete_custom_item_using_id, label = cc_label)
  723.             if rename_func:
  724.                 for text in to_rename:
  725.                     for old_id in to_rename[text]:
  726.                         rename_func(old_id, new_name = unicode(text))
  727.                     
  728.                 
  729.                 for item in to_delete:
  730.                     delete_func(item)
  731.                 
  732.             
  733.             self.library_view.model().refresh()
  734.             self.tags_view.set_new_model()
  735.             self.tags_view.recount()
  736.             self.saved_search.clear_to_help()
  737.             self.search.clear_to_help()
  738.         
  739.  
  740.     
  741.     def do_tag_item_renamed(self):
  742.         self.library_view.model().refresh()
  743.         self.saved_search.clear_to_help()
  744.         self.search.clear_to_help()
  745.  
  746.     
  747.     def do_author_sort_edit(self, parent, id):
  748.         db = self.library_view.model().db
  749.         editor = EditAuthorsDialog(parent, db, id)
  750.         d = editor.exec_()
  751.         if d:
  752.             for id, old_author, new_author, new_sort in editor.result:
  753.                 if old_author != new_author:
  754.                     id = db.rename_author(id, new_author)
  755.                 
  756.                 db.set_sort_field_for_author(id, unicode(new_sort))
  757.             
  758.             self.library_view.model().refresh()
  759.             self.tags_view.recount()
  760.         
  761.  
  762.  
  763.  
  764. class TagBrowserWidget(QWidget):
  765.     
  766.     def __init__(self, parent):
  767.         QWidget.__init__(self, parent)
  768.         self._layout = QVBoxLayout()
  769.         self.setLayout(self._layout)
  770.         parent.tags_view = TagsView(parent)
  771.         self._layout.addWidget(parent.tags_view)
  772.         parent.sort_by = QComboBox(parent)
  773.         for x in (_('Sort by name'), _('Sort by popularity'), _('Sort by average rating')):
  774.             parent.sort_by.addItem(x)
  775.         
  776.         parent.sort_by.setToolTip(_('Set the sort order for entries in the Tag Browser'))
  777.         parent.sort_by.setStatusTip(parent.sort_by.toolTip())
  778.         parent.sort_by.setCurrentIndex(0)
  779.         self._layout.addWidget(parent.sort_by)
  780.         parent.tag_match = QComboBox(parent)
  781.         for x in (_('Match any'), _('Match all')):
  782.             parent.tag_match.addItem(x)
  783.         
  784.         parent.tag_match.setCurrentIndex(0)
  785.         self._layout.addWidget(parent.tag_match)
  786.         parent.tag_match.setToolTip(_('When selecting multiple entries in the Tag Browser match any or all of them'))
  787.         parent.tag_match.setStatusTip(parent.tag_match.toolTip())
  788.         parent.edit_categories = QPushButton(_('Manage &user categories'), parent)
  789.         self._layout.addWidget(parent.edit_categories)
  790.         parent.edit_categories.setToolTip(_('Add your own categories to the Tag Browser'))
  791.         parent.edit_categories.setStatusTip(parent.edit_categories.toolTip())
  792.  
  793.  
  794.