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 "graphicsrectangleitem.h"
26 #include <QGraphicsScene>
27 #include <QApplication>
28 #include <QPainter>
29 #include <QGraphicsView>
30 #include "contextmanager.h"
31 #include "soundfontmanager.h"
32 
33 GraphicsRectangleItem::EditingMode GraphicsRectangleItem::s_globalEditingMode = GraphicsRectangleItem::NONE;
34 bool GraphicsRectangleItem::s_synchronizeEditingMode = false;
35 
GraphicsRectangleItem(EltID id,QGraphicsItem * parent)36 GraphicsRectangleItem::GraphicsRectangleItem(EltID id, QGraphicsItem *parent) : QGraphicsRectItem(parent),
37     _id(id),
38     _editingMode(NONE),
39     _isSelected(false)
40 {
41     // Initialize pens and brushes
42     QColor color = QApplication::palette().color(QPalette::Highlight);
43     color.setAlpha(180);
44     _penBorderThin = QPen(color, 1);
45     color.setAlpha(255);
46     _penBorderFat = QPen(color, 3);
47     color.setAlpha(60);
48     _brushRectangle = QBrush(color);
49     color.setAlpha(140);
50     _brushRectangleSelected = QBrush(color, Qt::DiagCrossPattern);
51     _brushRectangleSelected.setMatrix(QMatrix(1,0,1,0,0,0)); // Don't scale the pattern
52 
53     initialize(id);
54     _penBorderThin.setCosmetic(true);
55     _penBorderFat.setCosmetic(true);
56 
57     this->setRect(getRectF());
58 }
59 
initialize(EltID id)60 void GraphicsRectangleItem::initialize(EltID id)
61 {
62     // Default values
63     _minKey = 0;
64     _maxKey = 127;
65     _minVel = 0;
66     _maxVel = 127;
67 
68     // Global division
69     EltID idGlobal = id;
70     if (id.typeElement == elementInstSmpl)
71         idGlobal.typeElement = elementInst;
72     else
73         idGlobal.typeElement = elementPrst;
74 
75     // Key range
76     SoundfontManager * sm = SoundfontManager::getInstance();
77     if (sm->isSet(id, champ_keyRange))
78     {
79         RangesType range = sm->get(id, champ_keyRange).rValue;
80         _minKey = range.byLo;
81         _maxKey = range.byHi;
82     }
83     else if (sm->isSet(idGlobal, champ_keyRange))
84     {
85         RangesType range = sm->get(idGlobal, champ_keyRange).rValue;
86         _minKey = range.byLo;
87         _maxKey = range.byHi;
88     }
89 
90     // Velocity range
91     if (sm->isSet(id, champ_velRange))
92     {
93         RangesType range = sm->get(id, champ_velRange).rValue;
94         _minVel = range.byLo;
95         _maxVel = range.byHi;
96     }
97     else if (sm->isSet(idGlobal, champ_velRange))
98     {
99         RangesType range = sm->get(idGlobal, champ_velRange).rValue;
100         _minVel = range.byLo;
101         _maxVel = range.byHi;
102     }
103 
104     _minKeyInit = _minKey;
105     _maxKeyInit = _maxKey;
106     _minVelInit = _minVel;
107     _maxVelInit = _maxVel;
108 }
109 
getRectF() const110 QRectF GraphicsRectangleItem::getRectF() const
111 {
112     return QRectF(-0.5 + _minKey, 126.5 - _maxVel, 1.0 + _maxKey - _minKey, 1.0 + _maxVel - _minVel);
113 }
114 
contains(const QPointF & point) const115 bool GraphicsRectangleItem::contains(const QPointF &point) const
116 {
117     QRectF rectF = getRectF();
118     return rectF.left() <= point.x() && point.x() < rectF.right() &&
119             rectF.top() < point.y() && point.y() < rectF.bottom();
120 }
121 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)122 void GraphicsRectangleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
123 {
124     // Draw base rectangle with a background color and a thin border
125     this->setBrush(_isSelected ? _brushRectangleSelected : _brushRectangle);
126     this->setPen(_penBorderThin);
127     QGraphicsRectItem::paint(painter, option, widget);
128 
129     QRectF rectF = this->rect();
130     switch (_isSelected && s_synchronizeEditingMode ? s_globalEditingMode : _editingMode)
131     {
132     case NONE:
133         // Nothing else
134         break;
135     case MOVE_ALL:
136         painter->setPen(_penBorderFat);
137         painter->drawLine(rectF.topRight(), rectF.bottomRight());
138         painter->drawLine(rectF.topLeft(), rectF.bottomLeft());
139         painter->drawLine(rectF.topLeft(), rectF.topRight());
140         painter->drawLine(rectF.bottomLeft(), rectF.bottomRight());
141         break;
142     case MOVE_RIGHT:
143         painter->setPen(_penBorderFat);
144         painter->drawLine(rectF.topRight(), rectF.bottomRight());
145         break;
146     case MOVE_LEFT:
147         painter->setPen(_penBorderFat);
148         painter->drawLine(rectF.topLeft(), rectF.bottomLeft());
149         break;
150     case MOVE_TOP:
151         painter->setPen(_penBorderFat);
152         painter->drawLine(rectF.topLeft(), rectF.topRight());
153         break;
154     case MOVE_BOTTOM:
155         painter->setPen(_penBorderFat);
156         painter->drawLine(rectF.bottomLeft(), rectF.bottomRight());
157         break;
158     }
159 }
160 
getEditingMode(const QPoint & point)161 GraphicsRectangleItem::EditingMode GraphicsRectangleItem::getEditingMode(const QPoint &point)
162 {
163     /*    _____________
164      *    |\         /|     The central area is for a global move
165      *    | \ _____ / |
166      *    |  |     |  |     The other areas have a constant width and only move a side of the rectangle
167      *    |  |     |  |     Width = 30% of the minimal size (width or length), maximum 10 pixels
168      *    |  |     |  |
169      *    |  |     |  |     Top or bottom have the priority in case of a conflict with right or left
170      *    |  |     |  |
171      *    |  |     |  |
172      *    |  |_____|  |
173      *    | /       \ |
174      *    |/_________\|
175      */
176 
177     // Size in pixels
178     QRectF rectF = getRectF();
179     QGraphicsView *view = scene()->views().first();
180     QPoint topLeftPx = view->mapFromScene(rectF.topLeft());
181     QPoint bottomRightPx = view->mapFromScene(rectF.bottomRight());
182 
183     // Width of the external areas
184     int width = static_cast<int>(qMin(0.3 * qMin(bottomRightPx.x() - topLeftPx.x(),
185                                                  bottomRightPx.y() - topLeftPx.y()), 10.));
186 
187     // Minimal distance from a border
188     EditingMode mode = MOVE_TOP;
189     int minDist = point.y() - topLeftPx.y();
190     if (bottomRightPx.y() - point.y() < minDist)
191     {
192         minDist = bottomRightPx.y() - point.y();
193         mode = MOVE_BOTTOM;
194     }
195     if (point.x() - topLeftPx.x() < minDist)
196     {
197         minDist = point.x() - topLeftPx.x();
198         mode = MOVE_LEFT;
199     }
200     if (bottomRightPx.x() - point.x() < minDist)
201     {
202         minDist = bottomRightPx.x() - point.x();
203         mode = MOVE_RIGHT;
204     }
205 
206     if (minDist < 0)
207         mode = NONE;
208     else if (minDist > width)
209         mode = MOVE_ALL;
210 
211     return mode;
212 }
213 
setHover(bool isHovered,const QPoint & point)214 GraphicsRectangleItem::EditingMode GraphicsRectangleItem::setHover(bool isHovered, const QPoint &point)
215 {
216     if (isHovered)
217     {
218         _editingMode = getEditingMode(point);
219         s_globalEditingMode = _editingMode;
220         this->setZValue(51);
221     }
222     else
223     {
224         _editingMode = NONE;
225         this->setZValue(50);
226     }
227 
228     return _editingMode;
229 }
230 
findBrother()231 EltID GraphicsRectangleItem::findBrother()
232 {
233     if (ContextManager::configuration()->getValue(ConfManager::SECTION_NONE, "stereo_modification", false).toBool() &&
234             _id.typeElement == elementInstSmpl)
235     {
236         // Sample linked to the division
237         SoundfontManager * sm = SoundfontManager::getInstance();
238         EltID idSmpl = _id;
239         idSmpl.typeElement = elementSmpl;
240         idSmpl.indexElt = sm->get(_id, champ_sampleID).wValue;
241 
242         // Sample linked to another one?
243         SFSampleLink typeLink = sm->get(idSmpl, champ_sfSampleType).sfLinkValue;
244         if (typeLink == rightSample || typeLink == leftSample || typeLink == linkedSample ||
245                 typeLink == RomRightSample || typeLink == RomLeftSample || typeLink == RomLinkedSample)
246         {
247             // Characteristics to find in the other divisions
248             int numSmpl2 = sm->get(idSmpl, champ_wSampleLink).wValue;
249             RangesType keyRange = sm->get(_id, champ_keyRange).rValue;
250             RangesType velRange = sm->get(_id, champ_velRange).rValue;
251 
252             // Search the brother
253             int numBrothers = 0;
254             EltID idBrother = _id;
255             EltID id2 = _id;
256             foreach (int i, sm->getSiblings(_id))
257             {
258                 id2.indexElt2 = i;
259                 if (i != _id.indexElt2)
260                 {
261                     RangesType keyRange2 = sm->get(id2, champ_keyRange).rValue;
262                     RangesType velRange2 = sm->get(id2, champ_velRange).rValue;
263                     if (keyRange2.byLo == keyRange.byLo && keyRange2.byHi == keyRange.byHi &&
264                             velRange2.byLo == velRange.byLo && velRange2.byHi == velRange.byHi)
265                     {
266                         int iTmp = sm->get(id2, champ_sampleID).wValue;
267                         if (iTmp == idSmpl.indexElt)
268                             numBrothers = 2; // ambiguity, another sample is like id
269                         else if (iTmp == numSmpl2)
270                         {
271                             numBrothers++;
272                             idBrother.indexElt2 = i;
273                         }
274                     }
275                 }
276             }
277 
278             // Return the brother if there is only one (no ambiguity)
279             if (numBrothers == 1)
280                 return idBrother;
281             else
282                 return EltID(elementUnknown, 0, 0, 0, 0);
283         }
284         else
285             return EltID(elementUnknown, 0, 0, 0, 0);
286     }
287     else
288         return EltID(elementUnknown, 0, 0, 0, 0);
289 }
290 
computeNewRange(const QPointF & pointInit,const QPointF & pointFinal)291 void GraphicsRectangleItem::computeNewRange(const QPointF &pointInit, const QPointF &pointFinal)
292 {
293     // Move
294     int nKey = qRound(pointFinal.x() - pointInit.x());
295     int nVel = qRound(pointInit.y() - pointFinal.y());
296 
297     switch (s_globalEditingMode)
298     {
299     case MOVE_ALL:
300         _minKey = limit(_minKeyInit + nKey, 0, 127);
301         _maxKey = limit(_maxKeyInit + nKey, 0, 127);
302         _minVel = limit(_minVelInit + nVel, 0, 127);
303         _maxVel = limit(_maxVelInit + nVel, 0, 127);
304         break;
305     case MOVE_RIGHT:
306         _maxKey = limit(_maxKeyInit + nKey, _minKey, 127);
307         break;
308     case MOVE_LEFT:
309         _minKey = limit(_minKeyInit + nKey, 0, _maxKey);
310         break;
311     case MOVE_TOP:
312         _maxVel = limit(_maxVelInit + nVel, _minVel, 127);
313         break;
314     case MOVE_BOTTOM:
315         _minVel = limit(_minVelInit + nVel, 0, _maxVel);
316         break;
317     default:
318         break;
319     }
320 
321     this->setRect(getRectF());
322 }
323 
saveChanges()324 bool GraphicsRectangleItem::saveChanges()
325 {
326     EltID idGlobal = _id;
327     if (_id.typeElement == elementInstSmpl)
328         idGlobal.typeElement = elementInst;
329     else
330         idGlobal.typeElement = elementPrst;
331 
332     // Store the key range
333     SoundfontManager * sm = SoundfontManager::getInstance();
334     bool changes = false;
335     if (_minKey != _minKeyInit || _maxKey != _maxKeyInit)
336     {
337         changes = true;
338         if (_minKey == 0 && _maxKey == 127 && !sm->isSet(idGlobal, champ_keyRange))
339             sm->reset(_id, champ_keyRange);
340         else
341         {
342             AttributeValue value;
343             value.rValue.byLo = static_cast<unsigned char>(_minKey);
344             value.rValue.byHi = static_cast<unsigned char>(_maxKey);
345             sm->set(_id, champ_keyRange, value);
346         }
347     }
348 
349     // Store the velocity range
350     if (_minVel != _minVelInit || _maxVel != _maxVelInit)
351     {
352         changes = true;
353         if (_minVel == 0 && _maxVel == 127 && !sm->isSet(idGlobal, champ_velRange))
354             sm->reset(_id, champ_velRange);
355         else
356         {
357             AttributeValue value;
358             value.rValue.byLo = static_cast<unsigned char>(_minVel);
359             value.rValue.byHi = static_cast<unsigned char>(_maxVel);
360             sm->set(_id, champ_velRange, value);
361         }
362     }
363 
364     _minKeyInit = _minKey;
365     _maxKeyInit = _maxKey;
366     _minVelInit = _minVel;
367     _maxVelInit = _maxVel;
368 
369     return changes;
370 }
371 
372 
limit(int value,int min,int max)373 int GraphicsRectangleItem::limit(int value, int min, int max)
374 {
375     if (value < min)
376         return min;
377     else if (value > max)
378         return max;
379     else
380         return value;
381 }
382