1# -*- coding: utf-8 -*- 2# 3# Picard, the next-generation MusicBrainz tagger 4# 5# Copyright (C) 2013 Michael Wiencek 6# Copyright (C) 2014-2015, 2018 Laurent Monin 7# Copyright (C) 2016-2017 Sambhav Kothari 8# Copyright (C) 2018 Philipp Wolfer 9# Copyright (C) 2018 Vishal Choudhary 10# 11# This program is free software; you can redistribute it and/or 12# modify it under the terms of the GNU General Public License 13# as published by the Free Software Foundation; either version 2 14# of the License, or (at your option) any later version. 15# 16# This program is distributed in the hope that it will be useful, 17# but WITHOUT ANY WARRANTY; without even the implied warranty of 18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19# GNU General Public License for more details. 20# 21# You should have received a copy of the GNU General Public License 22# along with this program; if not, write to the Free Software 23# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 24 25 26import locale 27 28from PyQt5 import ( 29 QtCore, 30 QtGui, 31 QtWidgets, 32) 33 34from picard.collection import ( 35 load_user_collections, 36 user_collections, 37) 38 39 40class CollectionMenu(QtWidgets.QMenu): 41 42 def __init__(self, albums, *args): 43 super().__init__(*args) 44 self.ids = set(a.id for a in albums) 45 self._ignore_update = False 46 self.update_collections() 47 48 def update_collections(self): 49 self._ignore_update = True 50 self.clear() 51 self.actions = [] 52 for id_, collection in sorted(user_collections.items(), 53 key=lambda k_v: 54 (locale.strxfrm(str(k_v[1])), k_v[0])): 55 action = QtWidgets.QWidgetAction(self) 56 action.setDefaultWidget(CollectionMenuItem(self, collection)) 57 self.addAction(action) 58 self.actions.append(action) 59 self._ignore_update = False 60 self.addSeparator() 61 self.refresh_action = self.addAction(_("Refresh List")) 62 self.hovered.connect(self.update_highlight) 63 64 def refresh_list(self): 65 self.refresh_action.setEnabled(False) 66 load_user_collections(self.update_collections) 67 68 def mouseReleaseEvent(self, event): 69 # Not using self.refresh_action.triggered because it closes the menu 70 if self.actionAt(event.pos()) == self.refresh_action and self.refresh_action.isEnabled(): 71 self.refresh_list() 72 73 def update_highlight(self, action): 74 if self._ignore_update: 75 return 76 for a in self.actions: 77 a.defaultWidget().set_active(a == action) 78 79 def update_active_action_for_widget(self, widget): 80 if self._ignore_update: 81 return 82 for action in self.actions: 83 action_widget = action.defaultWidget() 84 is_active = action_widget == widget 85 if is_active: 86 self._ignore_hover = True 87 self.setActiveAction(action) 88 self._ignore_hover = False 89 action_widget.set_active(is_active) 90 91 92class CollectionMenuItem(QtWidgets.QWidget): 93 94 def __init__(self, menu, collection): 95 super().__init__() 96 self.menu = menu 97 self.active = False 98 self._setup_layout(menu, collection) 99 self._setup_colors() 100 101 def _setup_layout(self, menu, collection): 102 layout = QtWidgets.QVBoxLayout(self) 103 style = self.style() 104 layout.setContentsMargins( 105 style.pixelMetric(QtWidgets.QStyle.PM_LayoutLeftMargin), 106 style.pixelMetric(QtWidgets.QStyle.PM_FocusFrameVMargin), 107 style.pixelMetric(QtWidgets.QStyle.PM_LayoutRightMargin), 108 style.pixelMetric(QtWidgets.QStyle.PM_FocusFrameVMargin)) 109 self.checkbox = CollectionCheckBox(self, menu, collection) 110 layout.addWidget(self.checkbox) 111 112 def _setup_colors(self): 113 palette = self.palette() 114 self.text_color = palette.text().color() 115 self.highlight_color = palette.highlightedText().color() 116 117 def set_active(self, active): 118 self.active = active 119 palette = self.palette() 120 textcolor = self.highlight_color if active else self.text_color 121 palette.setColor(QtGui.QPalette.WindowText, textcolor) 122 self.checkbox.setPalette(palette) 123 124 def enterEvent(self, e): 125 self.menu.update_active_action_for_widget(self) 126 127 def leaveEvent(self, e): 128 self.set_active(False) 129 130 def paintEvent(self, e): 131 painter = QtWidgets.QStylePainter(self) 132 option = QtWidgets.QStyleOptionMenuItem() 133 option.initFrom(self) 134 option.state = QtWidgets.QStyle.State_None 135 if self.isEnabled(): 136 option.state |= QtWidgets.QStyle.State_Enabled 137 if self.active: 138 option.state |= QtWidgets.QStyle.State_Selected 139 painter.drawControl(QtWidgets.QStyle.CE_MenuItem, option) 140 141 142class CollectionCheckBox(QtWidgets.QCheckBox): 143 144 def __init__(self, parent, menu, collection): 145 self.menu = menu 146 self.collection = collection 147 super().__init__(self.label(), parent) 148 149 releases = collection.releases & menu.ids 150 if len(releases) == len(menu.ids): 151 self.setCheckState(QtCore.Qt.Checked) 152 elif not releases: 153 self.setCheckState(QtCore.Qt.Unchecked) 154 else: 155 self.setCheckState(QtCore.Qt.PartiallyChecked) 156 157 def nextCheckState(self): 158 ids = self.menu.ids 159 if ids & self.collection.pending: 160 return 161 diff = ids - self.collection.releases 162 if diff: 163 self.collection.add_releases(diff, self.updateText) 164 self.setCheckState(QtCore.Qt.Checked) 165 else: 166 self.collection.remove_releases(ids & self.collection.releases, self.updateText) 167 self.setCheckState(QtCore.Qt.Unchecked) 168 169 def updateText(self): 170 self.setText(self.label()) 171 172 def label(self): 173 c = self.collection 174 return ngettext("%s (%i release)", "%s (%i releases)", c.size) % (c.name, c.size) 175