1 /***************************************************************************
2 **                                                                        **
3 **  Polyphone, a soundfont editor                                         **
4 **  Copyright (C) 2013-2020 Davy Triponney                                **
5 **                                                                        **
6 **  This program is free software: you can redistribute it and/or modify  **
7 **  it under the terms of the GNU General Public License as published by  **
8 **  the Free Software Foundation, either version 3 of the License, or     **
9 **  (at your option) any later version.                                   **
10 **                                                                        **
11 **  This program is distributed in the hope that it will be useful,       **
12 **  but WITHOUT ANY WARRANTY; without even the implied warranty of        **
13 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          **
14 **  GNU General Public License for more details.                          **
15 **                                                                        **
16 **  You should have received a copy of the GNU General Public License     **
17 **  along with this program. If not, see http://www.gnu.org/licenses/.    **
18 **                                                                        **
19 ****************************************************************************
20 **           Author: Davy Triponney                                       **
21 **  Website/Contact: https://www.polyphone-soundfonts.com                 **
22 **             Date: 01.01.2013                                           **
23 ***************************************************************************/
24 
25 #include "graphicsviewrange.h"
26 #include "contextmanager.h"
27 #include "graphicssimpletextitem.h"
28 #include "graphicsrectangleitem.h"
29 #include "graphicslegenditem.h"
30 #include "graphicslegenditem2.h"
31 #include "graphicszoomline.h"
32 #include "graphicskey.h"
33 #include <QScrollBar>
34 #include <QMouseEvent>
35 #include <QApplication>
36 
37 const double GraphicsViewRange::WIDTH = 128.0;
38 const double GraphicsViewRange::MARGIN = 0.5;
39 const double GraphicsViewRange::OFFSET = -0.5;
40 
41 // Z values (all GraphicsItem being at the top level)
42 //   0: grid
43 //  50: non selected rectangles
44 //  51: selected rectangles
45 //  80: play marker
46 // 100: axis values
47 // 120: legends
48 // 150: zoom line
49 
GraphicsViewRange(QWidget * parent)50 GraphicsViewRange::GraphicsViewRange(QWidget *parent) : QGraphicsView(parent),
51     _sf2(SoundfontManager::getInstance()),
52     _scene(new QGraphicsScene(OFFSET, OFFSET, WIDTH, WIDTH)),
53     _legendItem(nullptr),
54     _legendItem2(nullptr),
55     _zoomLine(nullptr),
56     _dontRememberScroll(false),
57     _keyTriggered(-1),
58     _keepIndexOnRelease(false),
59     _editing(false),
60     _buttonPressed(Qt::NoButton),
61     _moveOccured(false),
62     _zoomX(1.),
63     _zoomY(1.),
64     _posX(0.5),
65     _posY(0.5),
66     _displayedRect(OFFSET, OFFSET, WIDTH, WIDTH),
67     _shiftRectangles(2, nullptr)
68 {
69     // Colors
70     QColor color = this->palette().color(QPalette::Text);
71     color.setAlpha(180);
72     _textColor = color;
73     color.setAlpha(40);
74     _lineColor = color;
75 
76     // Configuration
77     this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
78     this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
79     this->setRenderHint(QPainter::Antialiasing, true);
80     this->setMouseTracking(true);
81 
82     // Preparation of the graphics
83     this->setScene(_scene);
84     this->initItems();
85 }
86 
~GraphicsViewRange()87 GraphicsViewRange::~GraphicsViewRange()
88 {
89     while (!_rectangles.isEmpty())
90         delete _rectangles.takeFirst();
91     while (!_leftLabels.isEmpty())
92         delete _leftLabels.takeFirst();
93     while (!_bottomLabels.isEmpty())
94         delete _bottomLabels.takeFirst();
95     delete _legendItem;
96     delete _legendItem2;
97     delete _zoomLine;
98     while (!_keyLines.isEmpty())
99         delete _keyLines.takeFirst();
100     while (!_mapGraphicsKeys.isEmpty())
101         delete _mapGraphicsKeys.take(_mapGraphicsKeys.keys().first());
102     delete _scene;
103 }
104 
initItems()105 void GraphicsViewRange::initItems()
106 {
107     // Vertical lines
108     QPen penVerticalLines(_lineColor, 1);
109     penVerticalLines.setCosmetic(true);
110     for (quint32 note = 12; note <= 120; note += 12)
111     {
112         QGraphicsLineItem * line = new QGraphicsLineItem(note, OFFSET - MARGIN, note, OFFSET + WIDTH + MARGIN);
113         _scene->addItem(line);
114         line->setPen(penVerticalLines);
115         line->setZValue(0);
116         GraphicsSimpleTextItem * text = new GraphicsSimpleTextItem(Qt::AlignHCenter + Qt::AlignBottom);
117         _scene->addItem(text);
118         text->setZValue(100);
119         text->setBrush(_textColor);
120         text->setText(ContextManager::keyName()->getKeyName(note));
121         text->setPos(note, OFFSET + WIDTH);
122         _bottomLabels << text;
123         _keyLines << line;
124     }
125 
126     // Horizontal lines
127     QPen penHorizontalLines(_lineColor, 1, Qt::DotLine);
128     penHorizontalLines.setCosmetic(true);
129     for (int vel = 10; vel <= 120; vel += 10)
130     {
131         QGraphicsLineItem * line = new QGraphicsLineItem(OFFSET - MARGIN, 127 - vel, OFFSET + WIDTH + MARGIN, 127 - vel);
132         _scene->addItem(line);
133         line->setPen(penHorizontalLines);
134         line->setZValue(0);
135         GraphicsSimpleTextItem * text = new GraphicsSimpleTextItem(Qt::AlignLeft + Qt::AlignVCenter);
136         _scene->addItem(text);
137         text->setZValue(100);
138         text->setBrush(_textColor);
139         text->setText(QString::number(vel));
140         text->setPos(OFFSET, OFFSET + WIDTH - vel);
141         _leftLabels << text;
142         _keyLines << line;
143     }
144 
145     // Legends
146     _legendItem = new GraphicsLegendItem(this->font().family());
147     _scene->addItem(_legendItem);
148     _legendItem->setZValue(120);
149     _legendItem2 = new GraphicsLegendItem2(this->font().family());
150     _scene->addItem(_legendItem2);
151     _legendItem2->setZValue(120);
152 
153     // Zoomline
154     _zoomLine = new GraphicsZoomLine();
155     _scene->addItem(_zoomLine);
156     _zoomLine->setZValue(150);
157 }
158 
updateLabelPosition()159 void GraphicsViewRange::updateLabelPosition()
160 {
161     // Current rect
162     QRectF rect = getCurrentRect();
163 
164     // Update the position of the axis labels (they stay to the left and bottom)
165     foreach (GraphicsSimpleTextItem * label, _leftLabels)
166         label->setX(qMax(OFFSET, rect.x()));
167     foreach (GraphicsSimpleTextItem * label, _bottomLabels)
168         label->setY(qMin(WIDTH + OFFSET, rect.y() + rect.height()));
169 
170     // Update the position of the legend (it stays in a corner)
171     if (_legendItem->isLeft())
172     {
173         double posX = 35. * rect.width() / this->width() + qMax(OFFSET, rect.x());
174         _legendItem->setX(posX);
175         _legendItem2->setX(posX);
176     }
177     else
178     {
179         double posX = qMin(WIDTH + OFFSET, rect.x() + rect.width()) - 5. * rect.width() / this->width();
180         _legendItem->setX(posX);
181         _legendItem2->setX(posX);
182     }
183     _legendItem->setY(5. * rect.height() / this->height() + qMax(OFFSET, rect.y()));
184     _legendItem2->setY(13. * rect.height() / this->height() + qMax(OFFSET, rect.y()));
185 }
186 
updateHover(QPoint mousePos)187 void GraphicsViewRange::updateHover(QPoint mousePos)
188 {
189     // Rectangles under the mouse
190     QList<QList<GraphicsRectangleItem *> > pairs = getRectanglesUnderMouse(mousePos);
191 
192     // Highlighted rectangles below the mouse, priority for the selected rectangles
193     QList<GraphicsRectangleItem*> hoveredRectangles;
194     int selectionNumber = pairs.count();
195     int selectionIndex = 0;
196     if (selectionNumber > 0)
197     {
198         for (int i = 0; i < pairs.count(); i++)
199         {
200             bool ok = !pairs[i].isEmpty();
201             foreach (GraphicsRectangleItem * item, pairs[i])
202                 ok &= item->isSelected();
203             if (ok)
204             {
205                 selectionIndex = i;
206                 break;
207             }
208         }
209         hoveredRectangles = pairs[selectionIndex];
210     }
211 
212     // Update the hover, get the type of editing in the same time
213     GraphicsRectangleItem::EditingMode editingMode = GraphicsRectangleItem::NONE;
214     foreach (GraphicsRectangleItem * item, _rectangles)
215     {
216         if (hoveredRectangles.contains(item))
217             editingMode = item->setHover(true, mousePos);
218         else
219             item->setHover(false);
220     }
221 
222     // Adapt the cursor depending on the way we can edit the rectangle
223     switch (editingMode)
224     {
225     case GraphicsRectangleItem::NONE:
226         this->setCursor(Qt::ArrowCursor);
227         break;
228     case GraphicsRectangleItem::MOVE_ALL:
229         this->setCursor(Qt::SizeAllCursor);
230         break;
231     case GraphicsRectangleItem::MOVE_RIGHT: case GraphicsRectangleItem::MOVE_LEFT:
232         this->setCursor(Qt::SizeHorCursor);
233         break;
234     case GraphicsRectangleItem::MOVE_TOP: case GraphicsRectangleItem::MOVE_BOTTOM:
235         this->setCursor(Qt::SizeVerCursor);
236         break;
237     }
238 
239     // Update the content of the first legend
240     QList<EltID> ids;
241     QList<int> selectedIds;
242     int count = 0;
243     for (int i = 0; i < pairs.count(); i++)
244     {
245         foreach  (GraphicsRectangleItem * item, pairs[i])
246         {
247             ids << item->getID();
248             if (i == selectionIndex)
249                 selectedIds << count;
250             count++;
251         }
252     }
253     _legendItem->setIds(ids, selectedIds, selectionIndex, pairs.count());
254 
255     // Offset and location of the second legend
256     _legendItem2->setOffsetY(_legendItem->boundingRect().bottom());
257 }
258 
display(IdList ids,bool justSelection)259 void GraphicsViewRange::display(IdList ids, bool justSelection)
260 {
261     if (!justSelection)
262     {
263         // Clear previous rectangles
264         while (!_rectangles.isEmpty())
265         {
266             _scene->removeItem(_rectangles.first());
267             delete _rectangles.takeFirst();
268         }
269 
270         // Add new ones
271         _defaultID = ids[0];
272         EltID idDiv = ids[0];
273         switch (_defaultID.typeElement)
274         {
275         case elementInst: case elementInstSmpl:
276             _defaultID.typeElement = elementInst;
277             idDiv.typeElement = elementInstSmpl;
278             break;
279         case elementPrst: case elementPrstInst:
280             _defaultID.typeElement = elementPrst;
281             idDiv.typeElement = elementPrstInst;
282             break;
283         default:
284             return;
285         }
286 
287         foreach (int i, _sf2->getSiblings(idDiv))
288         {
289             idDiv.indexElt2 = i;
290             GraphicsRectangleItem * item = new GraphicsRectangleItem(idDiv);
291             item->setZValue(50);
292             _scene->addItem(item);
293             _rectangles << item;
294         }
295 
296         // Reset the shiftpoints
297         _shiftRectangles.fill(nullptr, 2);
298 
299         updateLabelPosition();
300     }
301 
302     // Selection
303     foreach (GraphicsRectangleItem * item, _rectangles)
304     {
305         if (ids.contains(item->getID()))
306         {
307             item->setSelected(true);
308             _shiftRectangles[1] = item;
309         }
310         else {
311             item->setSelected(false);
312         }
313     }
314 
315     viewport()->update();
316 }
317 
resizeEvent(QResizeEvent * event)318 void GraphicsViewRange::resizeEvent(QResizeEvent * event)
319 {
320     _dontRememberScroll = true;
321     QGraphicsView::resizeEvent(event);
322     fitInView(_displayedRect);
323     _dontRememberScroll = false;
324 }
325 
mousePressEvent(QMouseEvent * event)326 void GraphicsViewRange::mousePressEvent(QMouseEvent *event)
327 {
328     // Return immediately if a button is already pressed
329     if (_buttonPressed != Qt::NoButton)
330         return;
331 
332     if (event->button() == Qt::MiddleButton)
333     {
334         QPointF p = this->mapToScene(event->pos());
335         int key = qRound(p.x());
336         int velocity = 127 - qRound(p.y());
337         if (velocity > 0)
338         {
339             ContextManager::midi()->processKeyOn(key, velocity, true);
340             _keyTriggered = key;
341         }
342     }
343     else if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton)
344     {
345         // Update current position
346         double deltaX = WIDTH - _displayedRect.width();
347         if (deltaX < 0.5)
348             _posX = 0.5;
349         else
350             _posX = (_displayedRect.left() - OFFSET) / deltaX;
351         double deltaY = WIDTH - _displayedRect.height();
352         if (deltaY < 0.5)
353             _posY = 0.5;
354         else
355             _posY = (_displayedRect.top() - OFFSET) / deltaY;
356 
357         // Remember situation
358         _xInit = normalizeX(event->x());
359         _yInit = normalizeY(event->y());
360         _zoomXinit = _zoomX;
361         _zoomYinit = _zoomY;
362         _posXinit = _posX;
363         _posYinit = _posY;
364 
365         // Rectangles below the mouse?
366         QList<QList<GraphicsRectangleItem*> > pairs = getRectanglesUnderMouse(event->pos());
367         if (event->button() == Qt::LeftButton && !pairs.empty())
368         {
369             _editing = true;
370             GraphicsRectangleItem::syncHover(true);
371 
372             // Store the highlighted rectangle for the shift selection
373             _shiftRectangles[0] = _shiftRectangles[1];
374             _shiftRectangles[1] = pairs[0][0]; // By default
375             for (int i = 0; i < pairs.count(); i++)
376                 foreach (GraphicsRectangleItem * item, pairs[i])
377                     if (item->isHovered())
378                         _shiftRectangles[1] = item;
379 
380             // Other properties
381             Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
382             bool hasSelection = false;
383             for (int i = 0; i < pairs.count(); i++)
384                 foreach (GraphicsRectangleItem * item, pairs[i])
385                     hasSelection |= item->isSelected();
386 
387             _keepIndexOnRelease = !hasSelection;
388             if (modifiers == Qt::ShiftModifier)
389             {
390                 // Select all rectangles between the previous rectangle and the new one
391                 if (_shiftRectangles[0] != nullptr && _shiftRectangles[1] != nullptr)
392                 {
393                     QRectF shiftZone = _shiftRectangles[0]->rect().united(_shiftRectangles[1]->rect());
394                     foreach (GraphicsRectangleItem * item, _rectangles)
395                         item->setSelected(shiftZone.intersects(item->rect()));
396                 }
397                 else if (!hasSelection)
398                 {
399                     // Select the first pair
400                     foreach (GraphicsRectangleItem * item, pairs[0])
401                         item->setSelected(true);
402                 }
403             }
404             else if (modifiers == Qt::ControlModifier)
405             {
406                 if (!hasSelection)
407                 {
408                     // Select the first pair
409                     foreach (GraphicsRectangleItem * item, pairs[0])
410                         item->setSelected(true);
411                 }
412             }
413             else // No modifiers
414             {
415                 if (!hasSelection)
416                 {
417                     // Deselect everything
418                     foreach (GraphicsRectangleItem * item, _rectangles)
419                         item->setSelected(false);
420 
421                     // Select the first pair
422                     foreach (GraphicsRectangleItem * item, pairs[0])
423                         item->setSelected(true);
424                 }
425             }
426 
427             // Update the selection outside the range editor
428             triggerDivisionSelected();
429         }
430     }
431 
432     _moveOccured = false;
433     _buttonPressed = event->button();
434     updateHover(event->pos());
435     viewport()->update();
436 }
437 
mouseReleaseEvent(QMouseEvent * event)438 void GraphicsViewRange::mouseReleaseEvent(QMouseEvent *event)
439 {
440     // Only continue if the button released if the one that initiated the move
441     if (_buttonPressed != event->button())
442         return;
443 
444     if (event->button() == Qt::MiddleButton)
445     {
446         if (_keyTriggered != -1)
447         {
448             ContextManager::midi()->processKeyOff(_keyTriggered, true);
449             _keyTriggered = -1;
450         }
451     }
452     else if (event->button() == Qt::RightButton)
453     {
454         // Stop zooming
455         this->setZoomLine(-1, 0, 0, 0);
456     }
457     else if (event->button() == Qt::LeftButton)
458     {
459         if (_moveOccured)
460         {
461             // Save the changes
462             bool withChanges = false;
463             foreach (GraphicsRectangleItem * item, _rectangles)
464                 if (item->isSelected())
465                     withChanges |= item->saveChanges();
466 
467             if (withChanges)
468             {
469                 _sf2->endEditing("rangeEditor");
470                 updateKeyboard();
471             }
472         }
473         else
474         {
475             QList<QList<GraphicsRectangleItem*> > pairs = getRectanglesUnderMouse(event->pos());
476             if (pairs.empty())
477             {
478                 if (QApplication::keyboardModifiers() != Qt::ControlModifier)
479                 {
480                     // Remove the selection
481                     foreach (GraphicsRectangleItem * item, _rectangles)
482                         item->setSelected(false);
483                 }
484             }
485             else
486             {
487                 if (QApplication::keyboardModifiers() == Qt::ControlModifier && !_keepIndexOnRelease)
488                 {
489                     // Something more to select?
490                     int indexToSelect = -1;
491                     for (int i = 0; i < pairs.count(); i++)
492                     {
493                         for (int j = 0; j < pairs[i].count(); j++)
494                         {
495                             if (!pairs[i][j]->isSelected())
496                             {
497                                 pairs[i][j]->setSelected(false);
498                                 indexToSelect = i;
499                                 break;
500                             }
501                         }
502                         if (indexToSelect != -1)
503                             break;
504                     }
505 
506                     if (indexToSelect != -1)
507                     {
508                         foreach (GraphicsRectangleItem * item, pairs[indexToSelect])
509                             item->setSelected(true);
510                     }
511                     else
512                     {
513                         // Deselect everything below the mouse
514                         for (int i = 0; i < pairs.count(); i++)
515                             foreach (GraphicsRectangleItem * item, pairs[i])
516                                 item->setSelected(false);
517                     }
518                 }
519                 else if (QApplication::keyboardModifiers() == Qt::NoModifier)
520                 {
521                     // Current index of the selected pair (maximum index)
522                     int index = -1;
523                     for (int i = 0; i < pairs.count(); i++)
524                     {
525                         for (int j = 0; j < pairs[i].count(); j++)
526                         {
527                             if (pairs[i][j]->isSelected())
528                             {
529                                 index = i;
530                                 break;
531                             }
532                         }
533                     }
534 
535                     if (index == -1)
536                     {
537                         // Deselect everything
538                         foreach (GraphicsRectangleItem * item, _rectangles)
539                             item->setSelected(false);
540 
541                         // Select the first pair if possible
542                         if (!pairs.empty())
543                             for (int j = 0; j < pairs[0].count(); j++)
544                                 pairs[0][j]->setSelected(true);
545                     }
546                     else if (!_keepIndexOnRelease)
547                     {
548                         // Deselect everything
549                         foreach (GraphicsRectangleItem * item, _rectangles)
550                             item->setSelected(false);
551 
552                         // Next index is the new selection
553                         index = (index + 1) % pairs.count();
554                         foreach (GraphicsRectangleItem * item, pairs[index])
555                             item->setSelected(true);
556                     }
557                 }
558             }
559 
560             // Update the selection outside the range editor
561             triggerDivisionSelected();
562         }
563     }
564 
565     GraphicsRectangleItem::syncHover(false);
566     _legendItem2->setNewValues(-1, 0, 0, 0);
567     _editing = false;
568     _buttonPressed = Qt::NoButton;
569     updateHover(event->pos());
570     viewport()->update();
571 }
572 
mouseMoveEvent(QMouseEvent * event)573 void GraphicsViewRange::mouseMoveEvent(QMouseEvent *event)
574 {
575     _moveOccured = true;
576 
577     switch (_buttonPressed)
578     {
579     case Qt::LeftButton: {
580         this->setCursor(Qt::ClosedHandCursor);
581         if (_editing)
582         {
583             // Try to move rectangles
584             GraphicsRectangleItem * highlightedRectangle = nullptr;
585             foreach (GraphicsRectangleItem * item, _rectangles)
586             {
587                 if (item->isSelected())
588                 {
589                     QPointF pointInit = this->mapToScene(
590                                 static_cast<int>(_xInit * this->width()), static_cast<int>(_yInit * this->height()));
591                     QPointF pointFinal = this->mapToScene(event->pos());
592                     item->computeNewRange(pointInit, pointFinal);
593 
594                     if (item->isHovered())
595                         highlightedRectangle = item;
596                 }
597             }
598 
599             if (highlightedRectangle != nullptr)
600             {
601                 // Update the modification legend
602                 _legendItem2->setNewValues(highlightedRectangle->currentMinKey(), highlightedRectangle->currentMaxKey(),
603                         highlightedRectangle->currentMinVel(), highlightedRectangle->currentMaxVel());
604             }
605         }
606         else
607         {
608             // Drag
609             this->drag(event->pos());
610         }
611     } break;
612     case Qt::RightButton:
613         this->setCursor(Qt::SizeAllCursor);
614         this->setZoomLine(_xInit, _yInit, normalizeX(event->x()), normalizeY(event->y()));
615         this->zoom(event->pos());
616         break;
617     case Qt::NoButton: default: {
618         // Section of the legend
619         bool isLeft = (event->pos().x() > this->width() / 2);
620         if (_legendItem->isLeft() != isLeft)
621         {
622             _legendItem->setLeft(isLeft);
623             _legendItem2->setLeft(isLeft);
624             updateLabelPosition();
625         }
626 
627         // Update legend content
628         updateHover(event->pos());
629     }
630     }
631 
632     viewport()->update();
633 }
634 
wheelEvent(QWheelEvent * event)635 void GraphicsViewRange::wheelEvent(QWheelEvent * event)
636 {
637     if (_buttonPressed == Qt::NoButton)
638         QGraphicsView::wheelEvent(event);
639 }
640 
scrollContentsBy(int dx,int dy)641 void GraphicsViewRange::scrollContentsBy(int dx, int dy)
642 {
643     QGraphicsView::scrollContentsBy(dx, dy);
644     if (_dontRememberScroll)
645         return;
646 
647     // Update the displayed rect
648     _displayedRect = getCurrentRect();
649 
650     // Limits
651     if (_displayedRect.left() < OFFSET)
652         _displayedRect.setLeft(OFFSET);
653     if (_displayedRect.right() > WIDTH + OFFSET)
654         _displayedRect.setRight(WIDTH + OFFSET);
655     if (_displayedRect.top() < OFFSET)
656         _displayedRect.setTop(OFFSET);
657     if (_displayedRect.bottom() > WIDTH + OFFSET)
658         _displayedRect.setBottom(WIDTH + OFFSET);
659 
660     updateLabelPosition();
661 }
662 
drag(QPoint point)663 void GraphicsViewRange::drag(QPoint point)
664 {
665     // Décalage
666     double decX = normalizeX(point.x()) - _xInit;
667     double decY = normalizeY(point.y()) - _yInit;
668 
669     // Modification posX et posY
670     if (_zoomXinit > 1)
671         _posX = _posXinit - decX / (_zoomXinit - 1);
672     if (_zoomYinit > 1)
673         _posY = _posYinit - decY / (_zoomYinit - 1);
674 
675     // Mise à jour
676     this->zoomDrag();
677 }
678 
zoom(QPoint point)679 void GraphicsViewRange::zoom(QPoint point)
680 {
681     // Décalage
682     double decX = normalizeX(point.x()) - _xInit;
683     double decY = _yInit - normalizeY(point.y());
684 
685     // Modification zoom & drag
686     _zoomX = _zoomXinit * pow(2, 10.0 * decX);
687     _zoomY = _zoomYinit * pow(2, 10.0 * decY);
688 
689     // Ajustement posX et posY
690     if (_zoomX > 1)
691         _posX = (_zoomX * _posXinit * (_zoomXinit - 1) +
692                  _xInit * (_zoomX - _zoomXinit)) / (_zoomXinit * (_zoomX - 1));
693     if (_zoomY > 1)
694         _posY = (_zoomY * _posYinit * (_zoomYinit - 1) +
695                  _yInit * (_zoomY - _zoomYinit)) / (_zoomYinit * (_zoomY - 1));
696 
697     // Mise à jour
698     this->zoomDrag();
699 }
700 
zoomDrag()701 void GraphicsViewRange::zoomDrag()
702 {
703     // Bornes des paramètres d'affichage
704     if (_zoomX < 1)
705         _zoomX = 1;
706     else if (_zoomX > 8)
707         _zoomX = 8;
708     if (_zoomY < 1)
709         _zoomY = 1;
710     else if (_zoomY > 8)
711         _zoomY = 8;
712     if (_posX < 0)
713         _posX = 0;
714     else if (_posX > 1)
715         _posX = 1;
716     if (_posY < 0)
717         _posY = 0;
718     else if (_posY > 1)
719         _posY = 1;
720 
721     // Application du drag et zoom
722     double etendueX = WIDTH / _zoomX;
723     double offsetX = (WIDTH - etendueX) * _posX + OFFSET;
724     double etendueY = WIDTH / _zoomY;
725     double offsetY = (WIDTH - etendueY) * _posY + OFFSET;
726     _displayedRect.setRect(offsetX, offsetY, etendueX, etendueY);
727 
728     // Mise à jour
729     _dontRememberScroll = true;
730     this->fitInView(_displayedRect);
731     _dontRememberScroll = false;
732     updateLabelPosition();
733 }
734 
setZoomLine(double x1Norm,double y1Norm,double x2Norm,double y2Norm)735 void GraphicsViewRange::setZoomLine(double x1Norm, double y1Norm, double x2Norm, double y2Norm)
736 {
737     if (x1Norm < 0)
738         _zoomLine->setSize(0, 0);
739     else
740     {
741         QRectF rect = getCurrentRect();
742 
743         // Initial position
744         _zoomLine->setPos(rect.left() + x1Norm * rect.width(),
745                           rect.top() + y1Norm * rect.height());
746 
747         // Size
748         _zoomLine->setSize((x2Norm - x1Norm) * this->width(),
749                            (y2Norm - y1Norm) * this->height());
750     }
751 }
752 
getRectanglesUnderMouse(QPoint mousePos)753 QList<QList<GraphicsRectangleItem*> > GraphicsViewRange::getRectanglesUnderMouse(QPoint mousePos)
754 {
755     // Rectangles under the mouse
756     QList<GraphicsRectangleItem *> rectanglesUnderMouse;
757     foreach (GraphicsRectangleItem * item, _rectangles)
758         if (item->contains(this->mapToScene(mousePos)))
759             rectanglesUnderMouse << item;
760 
761     // Sort them by pairs
762     QList<QList<GraphicsRectangleItem*> > pairs;
763     while (!rectanglesUnderMouse.isEmpty())
764     {
765         QList<GraphicsRectangleItem*> listTmp;
766         listTmp << rectanglesUnderMouse.takeFirst();
767         EltID idBrother = listTmp[0]->findBrother();
768         foreach (GraphicsRectangleItem* item, rectanglesUnderMouse)
769         {
770             if (*item == idBrother)
771             {
772                 listTmp << item;
773                 rectanglesUnderMouse.removeOne(item);
774                 break;
775             }
776         }
777         pairs << listTmp;
778     }
779 
780     return pairs;
781 }
782 
normalizeX(int xPixel)783 double GraphicsViewRange::normalizeX(int xPixel)
784 {
785     return static_cast<double>(xPixel) / this->width();
786 }
787 
normalizeY(int yPixel)788 double GraphicsViewRange::normalizeY(int yPixel)
789 {
790     return static_cast<double>(yPixel) / this->height();
791 }
792 
getCurrentRect()793 QRectF GraphicsViewRange::getCurrentRect()
794 {
795     QPointF tl(horizontalScrollBar()->value(), verticalScrollBar()->value());
796     QPointF br = tl + viewport()->rect().bottomRight();
797     QMatrix mat = matrix().inverted();
798     return mat.mapRect(QRectF(tl,br));
799 }
800 
playKey(int key,int velocity)801 void GraphicsViewRange::playKey(int key, int velocity)
802 {
803     if (velocity == 0 && _mapGraphicsKeys[key] != nullptr)
804     {
805         // A key is removed
806         delete _mapGraphicsKeys.take(key);
807     }
808     else if (velocity > 0 && _mapGraphicsKeys[key] == nullptr)
809     {
810         // A key is added
811         _mapGraphicsKeys[key] = new GraphicsKey();
812         _scene->addItem(_mapGraphicsKeys[key]);
813         _mapGraphicsKeys[key]->setPos(QPoint(key, 127 - velocity));
814         _mapGraphicsKeys[key]->setZValue(80);
815     }
816 }
817 
triggerDivisionSelected()818 void GraphicsViewRange::triggerDivisionSelected()
819 {
820     IdList ids;
821     foreach (GraphicsRectangleItem * item, _rectangles)
822         if (item->isSelected())
823             ids << item->getID();
824     if (ids.empty())
825         ids << _defaultID;
826     divisionsSelected(ids);
827     updateKeyboard();
828 }
829