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