1 // For license of this file, see <project-root-folder>/LICENSE.md.
2 
3 #include "gui/reusable/labelsmenu.h"
4 
5 #include "3rd-party/boolinq/boolinq.h"
6 #include "database/databasequeries.h"
7 #include "miscellaneous/application.h"
8 #include "miscellaneous/iconfactory.h"
9 
10 #include <QCheckBox>
11 #include <QKeyEvent>
12 #include <QPainter>
13 
LabelsMenu(const QList<Message> & messages,const QList<Label * > & labels,QWidget * parent)14 LabelsMenu::LabelsMenu(const QList<Message>& messages, const QList<Label*>& labels, QWidget* parent)
15   : NonClosableMenu(tr("Labels"), parent), m_messages(messages) {
16   setIcon(qApp->icons()->fromTheme(QSL("tag-folder")));
17 
18   if (labels.isEmpty()) {
19     QAction* act_not_labels = new QAction(tr("No labels found"));
20 
21     act_not_labels->setEnabled(false);
22     addAction(act_not_labels);
23   }
24   else {
25     QSqlDatabase db = qApp->database()->driver()->connection(QSL("LabelsMenu"));
26 
27     for (Label* label: boolinq::from(labels).orderBy([](const Label* label) {
28       return label->title().toLower();
29     }).toStdList()) {
30 
31       auto count = boolinq::from(messages).count([&db, label](const Message& msg) {
32         return DatabaseQueries::isLabelAssignedToMessage(db, label, msg);
33       });
34 
35       Qt::CheckState state = Qt::CheckState::Unchecked;
36 
37       if (count == messages.size()) {
38         state = Qt::CheckState::Checked;
39       }
40       else if (count > 0) {
41         state = Qt::CheckState::PartiallyChecked;
42       }
43 
44       addLabelAction(label, state);
45     }
46   }
47 }
48 
keyPressEvent(QKeyEvent * event)49 void LabelsMenu::keyPressEvent(QKeyEvent* event) {
50   LabelAction* act = qobject_cast<LabelAction*>(activeAction());
51 
52   if (act != nullptr && event->key() == Qt::Key::Key_Space) {
53     act->toggleCheckState();
54   }
55 
56   NonClosableMenu::keyPressEvent(event);
57 }
58 
mousePressEvent(QMouseEvent * event)59 void LabelsMenu::mousePressEvent(QMouseEvent* event) {
60   LabelAction* act = qobject_cast<LabelAction*>(activeAction());
61 
62   if (act != nullptr) {
63     act->toggleCheckState();
64   }
65   else {
66     NonClosableMenu::mousePressEvent(event);
67   }
68 }
69 
changeLabelAssignment(Qt::CheckState state)70 void LabelsMenu::changeLabelAssignment(Qt::CheckState state) {
71   LabelAction* origin = qobject_cast<LabelAction*>(sender());
72 
73   if (origin != nullptr) {
74     if (state == Qt::CheckState::Checked) {
75       // Assign this label to selected messages.
76       for (const auto& msg : qAsConst(m_messages)) {
77         origin->label()->assignToMessage(msg);
78       }
79     }
80     else if (state == Qt::CheckState::Unchecked) {
81       // Remove label from selected messages.
82       for (const auto& msg : qAsConst(m_messages)) {
83         origin->label()->deassignFromMessage(msg);
84       }
85     }
86   }
87 
88   emit labelsChanged();
89 }
90 
addLabelAction(Label * label,Qt::CheckState state)91 void LabelsMenu::addLabelAction(Label* label, Qt::CheckState state) {
92   auto* act = new LabelAction(label, this, this);
93 
94   act->setCheckState(state);
95   addAction(act);
96 
97   connect(act, &LabelAction::checkStateChanged, this, &LabelsMenu::changeLabelAssignment);
98 }
99 
LabelAction(Label * label,QWidget * parent_widget,QObject * parent)100 LabelAction::LabelAction(Label* label, QWidget* parent_widget, QObject* parent)
101   : QAction(parent), m_label(label), m_parentWidget(parent_widget), m_checkState(Qt::CheckState::Unchecked) {
102   setText(label->title());
103   setIconVisibleInMenu(true);
104   setIcon(label->icon());
105 
106   connect(this, &LabelAction::checkStateChanged, this, &LabelAction::updateActionForState);
107   updateActionForState();
108 }
109 
checkState() const110 Qt::CheckState LabelAction::checkState() const {
111   return m_checkState;
112 }
113 
setCheckState(Qt::CheckState state)114 void LabelAction::setCheckState(Qt::CheckState state) {
115   if (state != m_checkState) {
116     m_checkState = state;
117     emit checkStateChanged(m_checkState);
118   }
119 }
120 
label() const121 Label* LabelAction::label() const {
122   return m_label;
123 }
124 
toggleCheckState()125 void LabelAction::toggleCheckState() {
126   if (m_checkState == Qt::CheckState::Unchecked) {
127     setCheckState(Qt::CheckState::Checked);
128   }
129   else {
130     setCheckState(Qt::CheckState::Unchecked);
131   }
132 }
133 
updateActionForState()134 void LabelAction::updateActionForState() {
135   QColor highlight;
136 
137   switch (m_checkState) {
138     case Qt::CheckState::Checked:
139       highlight = Qt::GlobalColor::green;
140       break;
141 
142     case Qt::CheckState::PartiallyChecked:
143       highlight = QColor(100, 50, 0);
144       break;
145 
146     case Qt::CheckState::Unchecked:
147     default:
148       highlight = Qt::GlobalColor::transparent;
149       break;
150   }
151 
152   QPixmap copy_icon(m_label->icon().pixmap(48, 48));
153 
154   if (m_checkState != Qt::CheckState::Unchecked) {
155     QPainter paint(&copy_icon);
156 
157     paint.setPen(QPen(Qt::GlobalColor::black, 4.0f));
158     paint.setBrush(highlight);
159     paint.drawRect(0, 0, 22, 22);
160   }
161 
162   setIcon(copy_icon);
163 }
164