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