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 : 67 : CheckableFolderCombo::CheckableFolderCombo(QWidget *parent)
11 [ + - ]: 67 : : QComboBox(parent), m_emptyText(tr("All folders")) {
12 [ + - + - : 67 : m_model = new QStandardItemModel(this);
- + - - ]
13 [ + - ]: 67 : 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 [ + - ]: 67 : setEditable(true);
19 [ + - + - ]: 67 : lineEdit()->setReadOnly(true);
20 [ + - + - ]: 67 : lineEdit()->setFocusPolicy(Qt::NoFocus);
21 [ + - + - ]: 67 : lineEdit()->setContextMenuPolicy(Qt::NoContextMenu);
22 [ + - ]: 67 : setInsertPolicy(QComboBox::NoInsert);
23 [ + - ]: 67 : setCompleter(nullptr);
24 : :
25 : : // Toggle checkboxes on click and keep the popup open for multi-selection.
26 [ + - + - : 67 : 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 : 67 : connect(m_model, &QStandardItemModel::itemChanged, this,
31 [ + - ]: 67 : [this](QStandardItem *) {
32 : 4 : updateSummaryText();
33 [ - + ]: 4 : if (!m_suppressSignal)
34 : 0 : emit selectionChanged();
35 : 4 : });
36 : :
37 [ + - ]: 67 : updateSummaryText();
38 : 67 : }
39 : :
40 : 37 : void CheckableFolderCombo::setFolders(const QStringList &paths) {
41 : 37 : m_suppressSignal = true;
42 : 37 : m_model->clear();
43 [ + + ]: 158 : for (const QString &path : paths) {
44 [ + - + - : 121 : auto *item = new QStandardItem(path);
- + - - ]
45 [ + - ]: 121 : item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
46 [ + - ]: 121 : item->setData(Qt::Unchecked, Qt::CheckStateRole);
47 [ + - ]: 121 : m_model->appendRow(item);
48 : : }
49 : 37 : m_suppressSignal = false;
50 : 37 : updateSummaryText();
51 : 37 : }
52 : :
53 : 349 : QStringList CheckableFolderCombo::checkedFolders() const {
54 : 349 : QStringList out;
55 [ + - + + ]: 869 : for (int i = 0; i < m_model->rowCount(); ++i) {
56 [ + - ]: 520 : QStandardItem *item = m_model->item(i);
57 [ + - + - : 520 : if (item && item->checkState() == Qt::Checked)
+ + + + ]
58 [ + - + - ]: 13 : out.append(item->text());
59 : : }
60 : 349 : return out;
61 : 0 : }
62 : :
63 : 100 : void CheckableFolderCombo::setCheckedFolders(const QStringList &paths) {
64 : 100 : m_suppressSignal = true;
65 : : // Track which requested paths already exist so we can append the rest.
66 : 100 : QStringList present;
67 [ + - + + ]: 341 : for (int i = 0; i < m_model->rowCount(); ++i) {
68 [ + - ]: 241 : QStandardItem *item = m_model->item(i);
69 [ - + ]: 241 : if (!item)
70 : 0 : continue;
71 [ + - ]: 241 : const bool on = paths.contains(item->text());
72 [ - + + - ]: 241 : item->setCheckState(on ? Qt::Checked : Qt::Unchecked);
73 [ - + ]: 241 : 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 [ + + ]: 108 : for (const QString &p : paths) {
80 [ - + ]: 8 : if (present.contains(p))
81 : 0 : continue;
82 [ + - + - : 8 : auto *item = new QStandardItem(p);
- + - - ]
83 [ + - ]: 8 : item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
84 [ + - ]: 8 : item->setData(Qt::Checked, Qt::CheckStateRole);
85 [ + - ]: 8 : m_model->appendRow(item);
86 : : }
87 : 100 : m_suppressSignal = false;
88 [ + - ]: 100 : updateSummaryText();
89 : 100 : }
90 : :
91 : 222 : void CheckableFolderCombo::updateSummaryText() {
92 [ + - ]: 222 : const QStringList checked = checkedFolders();
93 : 222 : QString summary;
94 [ + + ]: 222 : if (checked.isEmpty())
95 : 215 : summary = m_emptyText;
96 [ + + ]: 7 : else if (checked.size() == 1)
97 : 6 : summary = checked.first();
98 : : else
99 [ + - + - ]: 2 : summary = tr("%1 folders").arg(checked.size());
100 [ + - + - : 222 : if (lineEdit() && lineEdit()->text() != summary)
+ - + - +
+ + - + +
- - ]
101 [ + - + - ]: 119 : lineEdit()->setText(summary);
102 : 222 : }
103 : :
104 : 1091 : bool CheckableFolderCombo::eventFilter(QObject *obj, QEvent *event) {
105 [ + - - + : 2182 : if (obj == view()->viewport() &&
- + ]
106 : 1091 : 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 : 1091 : return QComboBox::eventFilter(obj, event);
120 : : }
121 : :
122 : : // T-76.B3: Runtime language switching
123 : 1767 : void CheckableFolderCombo::changeEvent(QEvent *event) {
124 [ + + ]: 1767 : if (event->type() == QEvent::LanguageChange)
125 : 14 : retranslateUi();
126 : 1767 : QComboBox::changeEvent(event);
127 : 1767 : }
128 : :
129 : 14 : void CheckableFolderCombo::retranslateUi() {
130 [ + - ]: 14 : m_emptyText = tr("All folders");
131 : 14 : updateSummaryText();
132 : 14 : }
|