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