Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2025 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 : #include "abstracttableviewmodel.h"
23 :
24 : #include <QSet>
25 : #include <QTimer>
26 :
27 : #include "internal/tableviewlistcell.h"
28 :
29 : using namespace muse::uicomponents;
30 :
31 0 : AbstractTableViewModel::AbstractTableViewModel(QObject* parent)
32 : : QAbstractTableModel(parent),
33 0 : m_selectionModel(new ItemMultiSelectionModel(this))
34 : {
35 0 : m_selectionModel->setModel(this);
36 0 : connect(m_selectionModel, &ItemMultiSelectionModel::selectionChanged,
37 0 : this, &AbstractTableViewModel::onSelectionChanged);
38 0 : }
39 :
40 0 : void AbstractTableViewModel::setHorizontalHeaders(const QVector<TableViewHeader*>& headers)
41 : {
42 0 : m_horizontalHeaders = headers;
43 0 : }
44 :
45 0 : void AbstractTableViewModel::insertHorizontalHeader(int column, TableViewHeader* header)
46 : {
47 0 : if (column < 0 || column > m_horizontalHeaders.size()) {
48 0 : return;
49 : }
50 :
51 0 : m_horizontalHeaders.insert(column, header);
52 : }
53 :
54 0 : void AbstractTableViewModel::removeHorizontalHeader(int column)
55 : {
56 0 : if (!isColumnValid(column)) {
57 0 : return;
58 : }
59 :
60 0 : m_horizontalHeaders.removeAt(column);
61 : }
62 :
63 0 : void AbstractTableViewModel::setVerticalHeaders(const QVector<TableViewHeader*>& headers)
64 : {
65 0 : m_verticalHeaders = headers;
66 0 : }
67 :
68 0 : void AbstractTableViewModel::insertVerticalHeader(int row, TableViewHeader* header)
69 : {
70 0 : if (row < 0 || row > m_verticalHeaders.size()) {
71 0 : return;
72 : }
73 :
74 0 : m_verticalHeaders.insert(row, header);
75 : }
76 :
77 0 : void AbstractTableViewModel::removeVerticalHeader(int row)
78 : {
79 0 : if (!isRowValid(row)) {
80 0 : return;
81 : }
82 :
83 0 : m_verticalHeaders.removeAt(row);
84 : }
85 :
86 0 : ItemMultiSelectionModel* AbstractTableViewModel::selectionModel() const
87 : {
88 0 : return m_selectionModel;
89 : }
90 :
91 0 : void AbstractTableViewModel::setTable(const QVector<QVector<TableViewCell*> >& table)
92 : {
93 0 : for (const auto& row : std::as_const(m_table)) {
94 0 : for (TableViewCell* cell : row) {
95 0 : if (cell) {
96 0 : disconnectCellSignals(cell);
97 : }
98 : }
99 : }
100 :
101 0 : beginResetModel();
102 :
103 0 : m_table = table;
104 :
105 0 : for (const auto& row : std::as_const(m_table)) {
106 0 : for (TableViewCell* cell : row) {
107 0 : if (cell) {
108 0 : connectCellSignals(cell);
109 : }
110 : }
111 : }
112 :
113 0 : endResetModel();
114 0 : }
115 :
116 0 : void AbstractTableViewModel::insertRow(int row, const QVector<TableViewCell*>& cells)
117 : {
118 0 : if (row < 0 || row > m_table.size()) {
119 0 : return;
120 : }
121 :
122 0 : beginResetModel();
123 :
124 0 : m_table.insert(row, cells);
125 :
126 0 : for (const auto& cell : std::as_const(m_table.at(row))) {
127 0 : connectCellSignals(cell);
128 : }
129 :
130 0 : endResetModel();
131 :
132 0 : QTimer::singleShot(100, [this, row](){
133 0 : m_selectionModel->select(index(row, 0));
134 0 : });
135 : }
136 :
137 0 : void AbstractTableViewModel::removeRow(int row)
138 : {
139 0 : if (!isRowValid(row)) {
140 0 : return;
141 : }
142 :
143 0 : beginResetModel();
144 :
145 0 : for (const auto& cell : std::as_const(m_table.at(row))) {
146 0 : disconnectCellSignals(cell);
147 : }
148 :
149 0 : m_table.removeAt(row);
150 :
151 0 : endResetModel();
152 :
153 0 : QModelIndex removedIndex = index(row, 0);
154 0 : QItemSelection selectionItem(removedIndex, removedIndex);
155 0 : emit m_selectionModel->selectionChanged(selectionItem, selectionItem);
156 :
157 0 : if (isRowValid(row)) {
158 0 : QTimer::singleShot(100, [this, row](){
159 0 : m_selectionModel->select(index(row, 0));
160 0 : });
161 : }
162 0 : }
163 :
164 0 : int AbstractTableViewModel::rowCount(const QModelIndex& parent) const
165 : {
166 : Q_UNUSED(parent);
167 0 : return m_table.size();
168 : }
169 :
170 0 : int AbstractTableViewModel::columnCount(const QModelIndex& parent) const
171 : {
172 : Q_UNUSED(parent);
173 0 : return m_horizontalHeaders.size();
174 : }
175 :
176 0 : QVariant AbstractTableViewModel::data(const QModelIndex& index, int role) const
177 : {
178 0 : if (!isIndexValid(index.row(), index.column())) {
179 0 : return QVariant();
180 : }
181 :
182 0 : switch (role) {
183 0 : case DisplayRole:
184 0 : return QVariant::fromValue(m_table.at(index.row()).at(index.column()));
185 : }
186 :
187 0 : return QVariant();
188 : }
189 :
190 0 : QVariant AbstractTableViewModel::headerData(int section, Qt::Orientation orientation, int role) const
191 : {
192 0 : if (role != Qt::DisplayRole) {
193 0 : return QVariant();
194 : }
195 :
196 0 : if (orientation == Qt::Horizontal) {
197 0 : if (!m_horizontalHeaders.empty() && isColumnValid(section)) {
198 0 : return QVariant::fromValue(m_horizontalHeaders.at(section));
199 : }
200 : } else {
201 0 : if (!m_verticalHeaders.empty() && isRowValid(section)) {
202 0 : return QVariant::fromValue(m_verticalHeaders.at(section));
203 : }
204 : }
205 :
206 0 : return QVariant();
207 : }
208 :
209 0 : QHash<int, QByteArray> AbstractTableViewModel::roleNames() const
210 : {
211 : static const QHash<int, QByteArray> roles {
212 0 : { DisplayRole, "display" }
213 0 : };
214 :
215 0 : return roles;
216 0 : }
217 :
218 0 : bool AbstractTableViewModel::isIndexValid(int row, int column) const
219 : {
220 0 : return isRowValid(row) && isColumnValid(column);
221 : }
222 :
223 0 : bool AbstractTableViewModel::isRowValid(int row) const
224 : {
225 0 : return row >= 0 && row < m_table.size();
226 : }
227 :
228 0 : bool AbstractTableViewModel::isColumnValid(int column) const
229 : {
230 0 : return column >= 0 && column < m_horizontalHeaders.size();
231 : }
232 :
233 0 : TableViewHeader* AbstractTableViewModel::makeHorizontalHeader(const QString& title, TableViewCellType::Type cellType,
234 : TableViewCellEditMode::Mode cellEditMode, int preferredWidth,
235 : const MenuItemList& availableFormats)
236 : {
237 0 : TableViewHeader* header = new TableViewHeader(this);
238 0 : header->setTitle(title);
239 :
240 0 : header->setCellType(cellType);
241 0 : header->setCellEditMode(cellEditMode);
242 0 : header->setAvailableFormats(availableFormats);
243 :
244 0 : if (preferredWidth != -1) {
245 0 : header->setPreferredWidth(preferredWidth);
246 : }
247 :
248 0 : return header;
249 : }
250 :
251 0 : TableViewCell* AbstractTableViewModel::makeCell(const Val& value, const Val& subValue)
252 : {
253 0 : TableViewCell* cell = nullptr;
254 :
255 0 : if (value.type() == Val::Type::List) {
256 0 : auto listCell = new TableViewListCell(this);
257 0 : listCell->setCurrent(subValue.toQString());
258 0 : cell = std::move(listCell);
259 : } else {
260 0 : cell = new TableViewCell(this);
261 : }
262 :
263 0 : cell->setValue(value);
264 :
265 0 : return cell;
266 : }
267 :
268 0 : TableViewCell* AbstractTableViewModel::findCell(int row, int column) const
269 : {
270 0 : if (!isIndexValid(row, column)) {
271 0 : return nullptr;
272 : }
273 :
274 0 : return m_table.at(row).at(column);
275 : }
276 :
277 0 : TableViewHeader* AbstractTableViewModel::findHorizontalHeader(int column) const
278 : {
279 0 : if (!isColumnValid(column)) {
280 0 : return nullptr;
281 : }
282 :
283 0 : return m_horizontalHeaders.at(column);
284 : }
285 :
286 0 : TableViewHeader* AbstractTableViewModel::findVerticalHeader(int row) const
287 : {
288 0 : if (!isRowValid(row)) {
289 0 : return nullptr;
290 : }
291 :
292 0 : return m_verticalHeaders.at(row);
293 : }
294 :
295 0 : void AbstractTableViewModel::connectCellSignals(TableViewCell* cell)
296 : {
297 0 : if (!cell) {
298 0 : return;
299 : }
300 :
301 0 : connect(cell, &TableViewCell::hoveredChanged, this, &AbstractTableViewModel::onCellHoveredChanged);
302 0 : connect(cell, &TableViewCell::pressedChanged, this, &AbstractTableViewModel::onCellPressedChanged);
303 :
304 0 : cell->setRequestChangeFunction([&](int row, int column, const Val& value)-> bool {
305 0 : return doCellValueChangeRequested(row, column, value);
306 : });
307 : }
308 :
309 0 : void AbstractTableViewModel::disconnectCellSignals(TableViewCell* cell)
310 : {
311 0 : if (!cell) {
312 0 : return;
313 : }
314 :
315 0 : disconnect(cell, &TableViewCell::hoveredChanged, this, &AbstractTableViewModel::onCellHoveredChanged);
316 0 : disconnect(cell, &TableViewCell::pressedChanged, this, &AbstractTableViewModel::onCellPressedChanged);
317 :
318 0 : cell->setRequestChangeFunction(nullptr);
319 : }
320 :
321 0 : void AbstractTableViewModel::onCellHoveredChanged()
322 : {
323 0 : if (m_updatingRowState) {
324 0 : return;
325 : }
326 :
327 0 : TableViewCell* cell = qobject_cast<TableViewCell*>(sender());
328 0 : if (!cell) {
329 0 : return;
330 : }
331 :
332 0 : updateRowHovered(cell, cell->hovered());
333 : }
334 :
335 0 : void AbstractTableViewModel::onCellPressedChanged()
336 : {
337 0 : if (m_updatingRowState) {
338 0 : return;
339 : }
340 :
341 0 : TableViewCell* cell = qobject_cast<TableViewCell*>(sender());
342 0 : if (!cell) {
343 0 : return;
344 : }
345 :
346 0 : updateRowPressed(cell, cell->pressed());
347 : }
348 :
349 0 : bool AbstractTableViewModel::doCellValueChangeRequested(int row, int column, const Val& value)
350 : {
351 : Q_UNUSED(row);
352 : Q_UNUSED(column);
353 : Q_UNUSED(value);
354 :
355 0 : return false;
356 : }
357 :
358 0 : void AbstractTableViewModel::updateRowHovered(TableViewCell* changedCell, bool hovered)
359 : {
360 0 : if (m_updatingRowState) {
361 0 : return;
362 : }
363 :
364 0 : int row = findRowForCell(changedCell);
365 0 : if (row < 0) {
366 0 : return;
367 : }
368 :
369 0 : m_updatingRowState = true;
370 :
371 0 : const QVector<TableViewCell*>& rowCells = m_table.at(row);
372 0 : for (TableViewCell* cell : rowCells) {
373 0 : cell->setHovered(hovered);
374 : }
375 :
376 0 : m_updatingRowState = false;
377 : }
378 :
379 0 : void AbstractTableViewModel::updateRowPressed(TableViewCell* changedCell, bool pressed)
380 : {
381 0 : if (m_updatingRowState) {
382 0 : return;
383 : }
384 :
385 0 : int row = findRowForCell(changedCell);
386 0 : if (row < 0) {
387 0 : return;
388 : }
389 :
390 0 : m_updatingRowState = true;
391 :
392 0 : const QVector<TableViewCell*>& rowCells = m_table.at(row);
393 0 : for (TableViewCell* cell : rowCells) {
394 0 : cell->setPressed(pressed);
395 : }
396 :
397 0 : m_updatingRowState = false;
398 : }
399 :
400 0 : int AbstractTableViewModel::findRowForCell(TableViewCell* cell) const
401 : {
402 0 : for (int row = 0; row < m_table.size(); ++row) {
403 0 : const QVector<TableViewCell*>& rowCells = m_table.at(row);
404 0 : for (TableViewCell* rowCell : rowCells) {
405 0 : if (rowCell == cell) {
406 0 : return row;
407 : }
408 : }
409 : }
410 0 : return -1;
411 : }
412 :
413 0 : int AbstractTableViewModel::findColumnForCell(TableViewCell* cell) const
414 : {
415 0 : int column = -1;
416 0 : for (int row = 0; row < m_table.size(); ++row) {
417 0 : for (int col = 0; col < m_table.size(); ++col) {
418 0 : if (m_table.at(row).at(col) == cell) {
419 0 : column = col;
420 0 : break;
421 : }
422 : }
423 : }
424 :
425 0 : return column;
426 : }
427 :
428 0 : void AbstractTableViewModel::onSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
429 : {
430 0 : if (m_updatingSelection) {
431 0 : return;
432 : }
433 :
434 0 : QSet<int> selectedRows;
435 0 : for (const QModelIndex& index : selected.indexes()) {
436 0 : if (index.isValid()) {
437 0 : selectedRows.insert(index.row());
438 : }
439 0 : }
440 :
441 0 : for (const QModelIndex& index : m_selectionModel->selectedIndexes()) {
442 0 : if (index.isValid()) {
443 0 : selectedRows.insert(index.row());
444 : }
445 0 : }
446 :
447 0 : QSet<int> deselectedRows;
448 0 : for (const QModelIndex& index : deselected.indexes()) {
449 0 : if (index.isValid()) {
450 0 : deselectedRows.insert(index.row());
451 : }
452 0 : }
453 :
454 0 : m_updatingSelection = true;
455 :
456 0 : for (int row : selectedRows) {
457 0 : selectRowCells(row);
458 : }
459 :
460 0 : for (int row : deselectedRows) {
461 0 : deselectRowCells(row);
462 : }
463 :
464 0 : m_updatingSelection = false;
465 0 : }
466 :
467 0 : void AbstractTableViewModel::selectRowCells(int row)
468 : {
469 0 : if (!isRowValid(row)) {
470 0 : return;
471 : }
472 :
473 0 : for (int column = 0; column < columnCount(); ++column) {
474 0 : QModelIndex index = this->index(row, column);
475 0 : if (index.isValid()) {
476 0 : m_selectionModel->select(index, QItemSelectionModel::Select);
477 : }
478 : }
479 : }
480 :
481 0 : void AbstractTableViewModel::deselectRowCells(int row)
482 : {
483 0 : if (!isRowValid(row)) {
484 0 : return;
485 : }
486 :
487 0 : for (int column = 0; column < columnCount(); ++column) {
488 0 : QModelIndex index = this->index(row, column);
489 0 : if (index.isValid()) {
490 0 : m_selectionModel->select(index, QItemSelectionModel::Deselect);
491 : }
492 : }
493 : }
|