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