1 /***************************************************************************
2 ** **
3 ** Polyphone, a soundfont editor **
4 ** Copyright (C) 2013-2020 Davy Triponney **
5 ** 2014 Andrea Celani **
6 ** **
7 ** This program is free software: you can redistribute it and/or modify **
8 ** it under the terms of the GNU General Public License as published by **
9 ** the Free Software Foundation, either version 3 of the License, or **
10 ** (at your option) any later version. **
11 ** **
12 ** This program is distributed in the hope that it will be useful, **
13 ** but WITHOUT ANY WARRANTY; without even the implied warranty of **
14 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
15 ** GNU General Public License for more details. **
16 ** **
17 ** You should have received a copy of the GNU General Public License **
18 ** along with this program. If not, see http://www.gnu.org/licenses/. **
19 ** **
20 ****************************************************************************
21 ** Author: Davy Triponney **
22 ** Website/Contact: https://www.polyphone-soundfonts.com **
23 ** Date: 01.01.2013 **
24 ***************************************************************************/
25
26 #include "tablewidget.h"
27 #include "tabledelegate.h"
28 #include "contextmanager.h"
29 #include <QApplication>
30 #include <QKeyEvent>
31 #include <QClipboard>
32 #include <QPainter>
33 #include <QHeaderView>
34 #include <QScrollBar>
35 #include "tableheaderview.h"
36 #include "tableheaderviewv.h"
37
TableWidget(QWidget * parent)38 TableWidget::TableWidget(QWidget *parent) : QTableWidget(parent)
39 {
40 _tableDelegate = new TableDelegate(this);
41 setItemDelegate(_tableDelegate);
42 setHorizontalHeader(new TableHeaderView(this));
43 setVerticalHeader(new TableHeaderViewV(this));
44
45 _timer = new QTimer(this);
46 connect(_timer, SIGNAL(timeout()), this, SLOT(updateColors()));
47 connect(this->horizontalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(onSectionDoubleClicked(int)));
48 connect(this, SIGNAL(itemSelectionChanged()), this, SLOT(onItemSelectionChanged()));
49 }
50
clear()51 void TableWidget::clear()
52 {
53 for (int i = 0; i < this->columnCount(); i++)
54 for (int j = 0; j < this->rowCount(); j++)
55 delete this->item(j, i);
56 this->setColumnCount(0);
57 _columnIds.clear();
58 }
59
addColumn(int column,QString title,EltID id)60 void TableWidget::addColumn(int column, QString title, EltID id)
61 {
62 _columnIds.insert(column, EltID());
63 this->insertColumn(column);
64 for (int i = 0; i < this->rowCount(); i++)
65 this->setItem(i, column, new QTableWidgetItem());
66 this->setHorizontalHeaderItem(column, new QTableWidgetItem(title));
67
68 // Add a colored element
69 QColor color = this->palette().color(QPalette::Text);
70 _listColors.insert(column, color);
71 this->horizontalHeaderItem(column)->setForeground(color);
72
73 // Keep the id somewhere
74 if (id.typeElement == elementInstGen)
75 id.typeElement = elementInst;
76 if (id.typeElement == elementInstSmplGen)
77 id.typeElement = elementInstSmpl;
78 if (id.typeElement == elementPrstGen)
79 id.typeElement = elementPrst;
80 if (id.typeElement == elementPrstInstGen)
81 id.typeElement = elementPrstInst;
82 _columnIds[column] = id;
83 this->model()->setHeaderData(column, Qt::Horizontal, QVariant::fromValue(id), Qt::UserRole);
84 }
85
getID(int column)86 EltID TableWidget::getID(int column)
87 {
88 return _columnIds.count() > column ? _columnIds[column] : EltID();
89 }
90
setEnlighted(int colonne,bool isEnlighted)91 void TableWidget::setEnlighted(int colonne, bool isEnlighted)
92 {
93 if (colonne >= this->columnCount())
94 return;
95
96 QPalette p = this->palette();
97 if (isEnlighted)
98 _listColors[colonne] = p.color(QPalette::Highlight);
99 else
100 _listColors[colonne] = p.color(QPalette::Text);
101
102 _timer->start(30);
103 }
104
updateColors()105 void TableWidget::updateColors()
106 {
107 int minChange = 40;
108 bool toutPareil = true;
109 for (int i = 0; i < this->columnCount(); i++)
110 {
111 if (this->horizontalHeaderItem(i))
112 {
113 QColor couleur1 = this->horizontalHeaderItem(i)->foreground().color();
114 QColor couleur2 = _listColors.at(i);
115 if (couleur1 != couleur2)
116 {
117 int deltaRouge = qMax(-minChange, qMin(minChange, couleur2.red() - couleur1.red()));
118 int deltaVert = qMax(-minChange, qMin(minChange, couleur2.green() - couleur1.green()));
119 int deltaBleu = qMax(-minChange, qMin(minChange, couleur2.blue() - couleur1.blue()));
120 couleur1.setRed(couleur1.red() + deltaRouge);
121 couleur1.setGreen(couleur1.green() + deltaVert);
122 couleur1.setBlue(couleur1.blue() + deltaBleu);
123 this->horizontalHeaderItem(i)->setForeground(couleur1);
124 }
125 if (couleur1 != couleur2)
126 toutPareil = false;
127 }
128 }
129 if (toutPareil)
130 _timer->stop();
131 }
132
setColumnCount(int columns)133 void TableWidget::setColumnCount(int columns)
134 {
135 QTableWidget::setColumnCount(columns);
136 _columnIds.clear();
137 for (int i = 0; i < columns; i++)
138 _columnIds.append(EltID());
139 _listColors.clear();
140 QPalette p = this->palette();
141 QColor color = p.color(QPalette::Text);
142 for (int i = 0; i < columns; i++)
143 _listColors << color;
144 }
145
removeColumn(int column)146 void TableWidget::removeColumn(int column)
147 {
148 QTableWidget::removeColumn(column);
149 _listColors.removeAt(column);
150 _columnIds.removeAt(column);
151 }
152
keyPressEvent(QKeyEvent * event)153 void TableWidget::keyPressEvent(QKeyEvent *event)
154 {
155 if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier))
156 {
157 copy();
158 }
159 else if (event->key() == Qt::Key_V && (event->modifiers() & Qt::ControlModifier))
160 {
161 if (!selectedItems().isEmpty())
162 paste();
163 }
164 else if (event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete)
165 {
166 // Delete the cell(s)
167 if (!selectedItems().isEmpty())
168 deleteCells();
169 }
170 else if (event->key() == Qt::Key_Space || event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
171 {
172 // Key enter or space: enter the cell
173 if (this->state() != QAbstractItemView::EditingState && !selectedItems().isEmpty())
174 {
175 QTableWidgetItem * item = selectedItems()[0];
176 this->edit(this->model()->index(item->row(), item->column()));
177 }
178 }
179 else if (event->key() == Qt::Key_X && (event->modifiers() & Qt::ControlModifier))
180 {
181 copy();
182 deleteCells();
183 }
184 else if (event->key() == Qt::Key_Tab)
185 {
186 QModelIndex index = this->currentIndex();
187 bool editing = _tableDelegate->isEditing();
188
189 QTableWidget::keyPressEvent(event);
190
191 if (editing)
192 {
193 // Otherwise the next cell is selected
194 this->setCurrentIndex(index);
195
196 // Otherwise we lose the focus if we use the mouse in the combobox
197 this->setFocus();
198 }
199 }
200 else
201 {
202 QTableWidget::keyPressEvent(event);
203 }
204 }
205
wheelEvent(QWheelEvent * event)206 void TableWidget::wheelEvent(QWheelEvent * event)
207 {
208 if (event->modifiers() == Qt::ShiftModifier)
209 {
210 event->setModifiers(Qt::NoModifier); // Otherwise it moves too fast
211 QApplication::sendEvent(this->horizontalScrollBar(), event);
212 }
213 else
214 QTableWidget::wheelEvent(event);
215 }
216
commitData(QWidget * editor)217 void TableWidget::commitData(QWidget *editor)
218 {
219 emit(actionBegin());
220 QTableWidget::commitData(editor);
221
222 QVariant value = model()->data(currentIndex(), Qt::EditRole).toString().replace(",", ".");
223 int curRow = currentIndex().row();
224 int curCol = currentIndex().column();
225 QItemSelectionRange isr;
226
227 foreach (isr, selectionModel()->selection())
228 {
229 for (int rows = isr.top(); rows <= isr.bottom(); rows++)
230 {
231 for (int cols = isr.left(); cols <= isr.right(); cols++)
232 {
233 if (!(curRow == rows && curCol == cols))
234 {
235 const QModelIndex idx = model()->index(rows, cols);
236
237 if (this->rowCount() == 50 && curRow == 5)
238 {
239 // Copy the data in DecorationRole and UserRole
240 model()->setData(idx, model()->data(currentIndex(), Qt::DecorationRole), Qt::DecorationRole);
241 model()->setData(idx, model()->data(currentIndex(), Qt::UserRole), Qt::UserRole);
242 }
243 else
244 {
245 // Copy the data in EditRole
246 model()->setData(idx, value, Qt::EditRole);
247 }
248 }
249 }
250 }
251 }
252 emit(actionFinished());
253 }
254
setLoopModeImage(int row,int column,int loopModeValue)255 void TableWidget::setLoopModeImage(int row, int column, int loopModeValue)
256 {
257 bool isDark = ContextManager::theme()->isDark(ThemeManager::LIST_BACKGROUND, ThemeManager::LIST_TEXT);
258 switch (loopModeValue)
259 {
260 case 0:
261 // no loop
262 if (isDark)
263 this->item(row, column)->setData(Qt::DecorationRole, QImage(":/icons/loop_off_w.png"));
264 else
265 this->item(row, column)->setData(Qt::DecorationRole, QImage(":/icons/loop_off.png"));
266 this->item(row, column)->setData(Qt::UserRole, 0);
267 break;
268 case 1:
269 // loop
270 if (isDark)
271 this->item(row, column)->setData(Qt::DecorationRole, QImage(":/icons/loop_on_w.png"));
272 else
273 this->item(row, column)->setData(Qt::DecorationRole, QImage(":/icons/loop_on.png"));
274 this->item(row, column)->setData(Qt::UserRole, 1);
275 break;
276 case 3:
277 // loop + end
278 if (isDark)
279 this->item(row, column)->setData(Qt::DecorationRole, QImage(":/icons/loop_on_end_w.png"));
280 else
281 this->item(row, column)->setData(Qt::DecorationRole, QImage(":/icons/loop_on_end.png"));
282 this->item(row, column)->setData(Qt::UserRole, 3);
283 break;
284 default:
285 // no image
286 this->item(row, column)->setData(Qt::DecorationRole, QImage());
287 this->item(row, column)->setData(Qt::UserRole, QVariant());
288 break;
289 }
290 }
291
copy()292 void TableWidget::copy()
293 {
294 if (selectedItems().isEmpty())
295 {
296 // no items selected
297 QApplication::clipboard()->setText("");
298 }
299 else
300 {
301 // Selected items
302 // They may not be contiguous!
303 QModelIndexList indexes = selectionModel()->selectedIndexes();
304
305 // Row 0 is for selection
306 int indexNumber = indexes.size();
307 for (int i = indexNumber - 1; i >= 0; i--)
308 if (indexes.at(i).row() == 0)
309 indexes.removeAt(i);
310
311 QMap<int, QMap<int, QString> > mapText;
312 foreach (QModelIndex index, indexes)
313 {
314 QString text = model()->data(index).toString();
315
316 // Maybe data comes from the UserRole (loopmode?)
317 if (!model()->data(index, Qt::UserRole).isNull())
318 text = QString::number(model()->data(index, Qt::UserRole).toInt());
319
320 // "!" stands for "erase", empty cell in the selection
321 if (text.isEmpty())
322 text = "!";
323 mapText[index.row()][index.column()] = text;
324 }
325
326 int minRow = mapText.keys().first();
327 int maxRow = mapText.keys().last();
328
329 int minCol = -1;
330 int maxCol = -1;
331 QMap<int, QString> mapTmp;
332 foreach (mapTmp, mapText.values())
333 {
334 int minColTmp = mapTmp.keys().first();
335 int maxColTmp = mapTmp.keys().last();
336 if (minCol == -1)
337 minCol = minColTmp;
338 else
339 minCol = qMin(minCol, minColTmp);
340 if (maxCol == -1)
341 maxCol = maxColTmp;
342 else
343 maxCol = qMax(maxCol, maxColTmp);
344 }
345
346 QString selected_text;
347 for (int numRow = minRow; numRow <= maxRow; numRow++)
348 {
349 if (numRow != minRow)
350 selected_text += "\n";
351
352 for (int numCol = minCol; numCol <= maxCol; numCol++)
353 {
354 if (numCol != minCol)
355 selected_text += "\t";
356
357 QString text = mapText[numRow][numCol];
358 // "?" stands for "no change", this cell was not present in the selection
359 if (text.isEmpty())
360 text = "?";
361 selected_text += text;
362 }
363 }
364
365 QApplication::clipboard()->setText(selected_text);
366 }
367 }
368
paste()369 void TableWidget::paste()
370 {
371 emit(actionBegin());
372 QString selected_text = qApp->clipboard()->text();
373 QStringList cells = selected_text.split(QRegExp("[\n\t]"));
374
375 int cellrows = selected_text.count('\n') + 1;
376 int cellcols = cells.size() / cellrows;
377 if (cells.size() != cellrows * cellcols)
378 {
379 // error, uneven number of columns, probably bad data
380 //QMessageBox::critical(this, "Error", "Invalid clipboard data, unable to perform paste operation.");
381 return;
382 }
383
384 // Paste from the top left corner of the selected items
385 QModelIndexList indexes = selectionModel()->selectedIndexes();
386 int minRow = -1;
387 int minCol = -1;
388 foreach (QModelIndex modelIndex, indexes)
389 {
390 if (minRow == -1)
391 minRow = modelIndex.row();
392 else
393 minRow = qMin(minRow, modelIndex.row());
394 if (minCol == -1)
395 minCol = modelIndex.column();
396 else
397 minCol = qMin(minCol, modelIndex.column());
398 }
399
400 // First row is for selection, no paste here
401 if (minRow < 1)
402 minRow = 1;
403
404 for (int indRow = 0; indRow < cellrows; indRow++)
405 {
406 for (int indCol = 0; indCol < cellcols; indCol++)
407 {
408 const QModelIndex idx = model()->index(indRow + minRow, indCol + minCol);
409 QString text = cells.takeFirst();
410 if (text != "?")
411 {
412 if (text == "!")
413 text = "";
414
415 if (this->rowCount() == 50 && indRow + minRow == 5)
416 {
417 bool ok;
418 int val = text.toInt(&ok);
419 if (ok && (val == -1 || val == 0 || val == 1 || val == 3))
420 setLoopModeImage(indRow + minRow, indCol + minCol, val);
421 }
422 else
423 model()->setData(idx, text.replace(",", "."), Qt::EditRole);
424 }
425 }
426 }
427 emit(actionFinished());
428 }
429
deleteCells()430 void TableWidget::deleteCells()
431 {
432 emit(actionBegin());
433 QList<QTableWidgetItem*> listItems = selectedItems();
434 foreach (QTableWidgetItem * item, listItems)
435 {
436 item->setText("");
437 item->setData(Qt::DecorationRole, QImage());
438 item->setData(Qt::UserRole, QVariant());
439 }
440 emit(actionFinished());
441 }
442
onSectionDoubleClicked(int index)443 void TableWidget::onSectionDoubleClicked(int index)
444 {
445 if (index >= 0) // Avoid a double click in the empty area
446 {
447 EltID id = getID(index);
448 openElement(id);
449 }
450 }
451
onItemSelectionChanged()452 void TableWidget::onItemSelectionChanged()
453 {
454 // Problematic case: background color when the first loopmode cell is selected
455 // The highlight color must be displayed along with the hatching pattern
456 // Only if ONE instrument is displayed
457 if (_columnIds.empty())
458 return;
459 if (_columnIds[0].typeElement != elementInst)
460 return;
461 if (_columnIds.count() > 1 && _columnIds[1].typeElement != elementInstSmpl)
462 return;
463
464 if (this->item(5, 0)->isSelected())
465 this->item(5, 0)->setBackground(this->palette().color(QPalette::Highlight));
466 else if (this->item(5, 0)->background() == this->palette().color(QPalette::Highlight))
467 {
468 QColor color = this->palette().color(QPalette::Base);
469 QColor alternateColor = this->palette().color(QPalette::AlternateBase);
470 QBrush brush(TableWidget::getPixMap(color, alternateColor));
471 this->item(5, 0)->setBackground(brush);
472 }
473 }
474
getPixMap(QColor backgroundColor,QColor dotColor)475 QPixmap TableWidget::getPixMap(QColor backgroundColor, QColor dotColor)
476 {
477 QPixmap pix(4, 4);
478 QPainter painter(&pix);
479 painter.fillRect(0, 0, 4, 4, backgroundColor);
480 painter.setPen(dotColor);
481 painter.drawPoint(3, 0);
482 painter.drawPoint(2, 1);
483 painter.drawPoint(1, 2);
484 painter.drawPoint(0, 3);
485 return pix;
486 }
487
resetModDisplay()488 void TableWidget::resetModDisplay()
489 {
490 _tableDelegate->resetModDisplay();
491 }
492
updateModDisplay(int column,QList<int> rows)493 void TableWidget::updateModDisplay(int column, QList<int> rows)
494 {
495 _tableDelegate->updateModDisplay(column, rows);
496 }
497
selectCells(EltID id,QList<AttributeType> attributes)498 void TableWidget::selectCells(EltID id, QList<AttributeType> attributes)
499 {
500 int numCol = _columnIds.indexOf(id);
501 if (numCol < 0 && numCol >= this->columnCount())
502 return;
503
504 int selectionNumber = 0;
505 this->blockSignals(true);
506 foreach (AttributeType attribute, attributes)
507 {
508 if (attribute == champ_startAddrsCoarseOffset)
509 attribute = champ_startAddrsOffset;
510 else if (attribute == champ_endAddrsCoarseOffset)
511 attribute = champ_endAddrsOffset;
512 else if (attribute == champ_startloopAddrsCoarseOffset)
513 attribute = champ_startloopAddrsOffset;
514 else if (attribute == champ_endloopAddrsCoarseOffset)
515 attribute = champ_endloopAddrsOffset;
516
517 int numRow = getRow(attribute);
518 if (numRow >= 0 && numRow < this->rowCount())
519 {
520 this->selectionModel()->select(this->model()->index(numRow, numCol),
521 selectionNumber == 0 ? QItemSelectionModel::ClearAndSelect : QItemSelectionModel::Select);
522 selectionNumber++;
523 }
524 }
525 this->blockSignals(false);
526 }
527