Branch data Line data Source code
1 : : #include "CheckableFolderCombo.h"
2 : :
3 : : #include <QAbstractItemView>
4 : : #include <QEvent>
5 : : #include <QLineEdit>
6 : : #include <QMouseEvent>
7 : : #include <QStandardItem>
8 : : #include <QStandardItemModel>
9 : :
10 : 66 : CheckableFolderCombo::CheckableFolderCombo(QWidget *parent)
11 [ + - ]: 66 : : QComboBox(parent), m_emptyText(tr("All folders")) {
12 [ + - + - : 66 : m_model = new QStandardItemModel(this);
- + - - ]
13 [ + - ]: 66 : setModel(m_model);
14 : :
15 : : // Editable + read-only line edit lets us paint a custom summary string in the
16 : : // closed combo while the popup stays a plain checkable list. The user can
17 : : // never type into it.
18 [ + - ]: 66 : setEditable(true);
19 [ + - + - ]: 66 : lineEdit()->setReadOnly(true);
20 [ + - + - ]: 66 : lineEdit()->setFocusPolicy(Qt::NoFocus);
21 [ + - + - ]: 66 : lineEdit()->setContextMenuPolicy(Qt::NoContextMenu);
22 [ + - ]: 66 : setInsertPolicy(QComboBox::NoInsert);
23 [ + - ]: 66 : setCompleter(nullptr);
24 : :
25 : : // Toggle checkboxes on click and keep the popup open for multi-selection.
26 [ + - + - : 66 : view()->viewport()->installEventFilter(this);
+ - ]
27 : :
28 : : // A check toggled in the model → refresh the summary and (unless the change
29 : : // was programmatic) notify listeners.
30 : 66 : connect(m_model, &QStandardItemModel::itemChanged, this,
31 [ + - ]: 66 : [this](QStandardItem *) {
32 : 1 : updateSummaryText();
33 [ - + ]: 1 : if (!m_suppressSignal)
34 : 0 : emit selectionChanged();
35 : 1 : });
36 : :
37 [ + - ]: 66 : updateSummaryText();
38 : 66 : }
39 : :
40 : 36 : void CheckableFolderCombo::setFolders(const QStringList &paths) {
41 : 36 : m_suppressSignal = true;
42 : 36 : m_model->clear();
43 [ + + ]: 173 : for (const QString &path : paths) {
44 [ + - + - : 137 : auto *item = new QStandardItem(path);
- + - - ]
45 [ + - ]: 137 : item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
46 [ + - ]: 137 : item->setData(Qt::Unchecked, Qt::CheckStateRole);
47 [ + - ]: 137 : m_model->appendRow(item);
48 : : }
49 : 36 : m_suppressSignal = false;
50 : 36 : updateSummaryText();
51 : 36 : }
52 : :
53 : 504 : QStringList CheckableFolderCombo::checkedFolders() const {
54 : 504 : QStringList out;
55 [ + - + + ]: 1069 : for (int i = 0; i < m_model->rowCount(); ++i) {
56 [ + - ]: 565 : QStandardItem *item = m_model->item(i);
57 [ + - + - : 565 : if (item && item->checkState() == Qt::Checked)
+ + + + ]
58 [ + - + - ]: 10 : out.append(item->text());
59 : : }
60 : 504 : return out;
61 : 0 : }
62 : :
63 : 96 : void CheckableFolderCombo::setCheckedFolders(const QStringList &paths) {
64 : 96 : m_suppressSignal = true;
65 : : // Track which requested paths already exist so we can append the rest.
66 : 96 : QStringList present;
67 [ + - + + ]: 350 : for (int i = 0; i < m_model->rowCount(); ++i) {
68 [ + - ]: 254 : QStandardItem *item = m_model->item(i);
69 [ - + ]: 254 : if (!item)
70 : 0 : continue;
71 [ + - ]: 254 : const bool on = paths.contains(item->text());
72 [ - + + - ]: 254 : item->setCheckState(on ? Qt::Checked : Qt::Unchecked);
73 [ - + ]: 254 : if (on)
74 [ # # # # ]: 0 : present.append(item->text());
75 : : }
76 : : // A checked path that is not (yet) in the known folder list — e.g. restored
77 : : // from a "folder:" query before the folder list loaded — is added as a
78 : : // checked item so the selection survives the round-trip and stays visible.
79 [ + + ]: 101 : for (const QString &p : paths) {
80 [ - + ]: 5 : if (present.contains(p))
81 : 0 : continue;
82 [ + - + - : 5 : auto *item = new QStandardItem(p);
- + - - ]
83 [ + - ]: 5 : item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
84 [ + - ]: 5 : item->setData(Qt::Checked, Qt::CheckStateRole);
85 [ + - ]: 5 : m_model->appendRow(item);
86 : : }
87 : 96 : m_suppressSignal = false;
88 [ + - ]: 96 : updateSummaryText();
89 : 96 : }
90 : :
91 : 369 : void CheckableFolderCombo::updateSummaryText() {
92 [ + - ]: 369 : const QStringList checked = checkedFolders();
93 : 369 : QString summary;
94 [ + + ]: 369 : if (checked.isEmpty())
95 : 365 : summary = m_emptyText;
96 [ + + ]: 4 : else if (checked.size() == 1)
97 : 3 : summary = checked.first();
98 : : else
99 [ + - + - ]: 2 : summary = tr("%1 folders").arg(checked.size());
100 [ + - + - : 369 : if (lineEdit() && lineEdit()->text() != summary)
+ - + - +
+ + - + +
- - ]
101 [ + - + - ]: 189 : lineEdit()->setText(summary);
102 : 369 : }
103 : :
104 : 1335 : bool CheckableFolderCombo::eventFilter(QObject *obj, QEvent *event) {
105 [ + - - + : 2670 : if (obj == view()->viewport() &&
- + ]
106 : 1335 : event->type() == QEvent::MouseButtonRelease) {
107 : 0 : auto *me = static_cast<QMouseEvent *>(event);
108 [ # # # # : 0 : const QModelIndex index = view()->indexAt(me->pos());
# # ]
109 [ # # ]: 0 : if (index.isValid()) {
110 [ # # ]: 0 : QStandardItem *item = m_model->itemFromIndex(index);
111 [ # # ]: 0 : if (item) {
112 [ # # # # : 0 : item->setCheckState(item->checkState() == Qt::Checked ? Qt::Unchecked
# # ]
113 : : : Qt::Checked);
114 : : }
115 : : }
116 : : // Consume the event so the popup does NOT close — multi-select continues.
117 : 0 : return true;
118 : : }
119 : 1335 : return QComboBox::eventFilter(obj, event);
120 : : }
121 : :
122 : : // T-76.B3: Runtime language switching
123 : 1923 : void CheckableFolderCombo::changeEvent(QEvent *event) {
124 [ + + ]: 1923 : if (event->type() == QEvent::LanguageChange)
125 : 170 : retranslateUi();
126 : 1923 : QComboBox::changeEvent(event);
127 : 1923 : }
128 : :
129 : 170 : void CheckableFolderCombo::retranslateUi() {
130 [ + - ]: 170 : m_emptyText = tr("All folders");
131 : 170 : updateSummaryText();
132 : 170 : }
|