1 //=========================================================
2 //  MusE
3 //  Linux Music Editor
4 //    $Id: scrollscale.cpp,v 1.2.2.2 2009/11/04 17:43:25 lunar_shuttle Exp $
5 //  (C) Copyright 1999 Werner Schweer (ws@seh.de)
6 //
7 //  This program is free software; you can redistribute it and/or
8 //  modify it under the terms of the GNU General Public License
9 //  as published by the Free Software Foundation; version 2 of
10 //  the License, or (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, write to the Free Software
19 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 //
21 //=========================================================
22 
23 #include <stdio.h>
24 #include "muse_math.h"
25 
26 #include <QBoxLayout>
27 #include <QLabel>
28 #include <QResizeEvent>
29 #include <QScrollBar>
30 #include <QSlider>
31 #include <QToolButton>
32 #include <QToolTip>
33 #include <QStyle>
34 
35 #include "scrollscale.h"
36 #include "icons.h"
37 
38 namespace MusEGui {
39 
40 
41 //---------------------------------------------------------
42 //   stepScale
43 //    increase/decrease scale by single step
44 //---------------------------------------------------------
stepScale(bool up)45 void ScrollScale::stepScale ( bool up )
46 {
47     setMag(scale2mag(up ? scaleVal + 1 : scaleVal - 1));
48 }
49 
50 //---------------------------------------------------------
51 //   setScale
52 //    "val" - slider value in range 0-convertQuickZoomLevelToMag(zoomLevels-1)
53 //---------------------------------------------------------
54 
setScale(int val,int pos_offset)55 void ScrollScale::setScale ( int val, int pos_offset )
56 {
57 	int off = offset();
58 	int old_scale_val = scaleVal;
59 
60 	scaleVal = mag2scale(val);
61 
62 	//fprintf(stderr, "scaleMin %d scaleMax %d val=%d emit scaleVal=%d\n", scaleMin, scaleMax, val, scaleVal);
63 	emit scaleChanged ( scaleVal );
64 	if ( !noScale )
65 		setRange ( minVal, maxVal );
66 
67 	int i = ( scroll->orientation() == Qt::Horizontal ) ? width() : height();
68 	int pos, pmax;
69 	if ( scaleVal < 1 )
70 	{
71 		pos = ( off-scaleVal/2 ) / ( -scaleVal );
72 		pmax = ( maxVal-scaleVal-1 ) / ( -scaleVal ) - i;
73 	}
74 	else
75 	{
76 		pos = off * scaleVal;
77 		pmax = maxVal * scaleVal - i;
78 	}
79 
80 	// Zoom at cursor support...
81 	if(pos_offset != 0)
82 	{
83 		double oscale = old_scale_val;
84 		double nscale = scaleVal;
85 		if(old_scale_val < 1)
86 			oscale = 1.0 / -old_scale_val;
87 		if(scaleVal < 1)
88 			nscale = 1.0 / -scaleVal;
89 		double scale_fact = nscale / oscale;
90 		int pos_diff = (int)((double)pos_offset * scale_fact - (double)pos_offset + 0.5);  // 0.5 for round-off
91 		pos += pos_diff;
92 	}
93 
94 	if(pos > pmax)
95 		pos = pmax;
96 	setPos(pos);
97 }
98 
99 //---------------------------------------------------------
100 //   setMag
101 //---------------------------------------------------------
102 
setMag(int cs,int pos_offset)103 void ScrollScale::setMag ( int cs, int pos_offset )
104 {
105 	scale->blockSignals(true);
106 	scale->setValue ( cs );
107 	scale->blockSignals(false);
108 	setScale ( cs, pos_offset );
109 }
110 
111 //---------------------------------------------------------
112 //   setRange
113 //    min,max  ticks
114 //---------------------------------------------------------
115 
setRange(int min,int max)116 void ScrollScale::setRange ( int min, int max )
117 {
118 //      if ((min != minVal) && (max != maxVal))
119 //            return;
120 	minVal = min;
121 	maxVal = max;
122 	int i = ( scroll->orientation() == Qt::Horizontal ) ? width() : height();
123 
124 	if ( !noScale )
125 	{
126 		if ( scaleVal < 1 )
127 		{
128 			min = minVal / ( -scaleVal );
129 			max = ( maxVal-scaleVal-1 ) / ( -scaleVal ) - i;
130 		}
131 		else
132 		{
133 			min = minVal * scaleVal;
134 			max = maxVal * scaleVal - i;
135 		}
136 	}
137 	else
138 		max -= i;
139 	if ( max < 0 )
140 		max = 0;
141 	if ( min < 0 )
142 		min = 0;
143 	if ( min > max )
144 		max = min;
145 
146 	scroll->setRange ( min, max );
147 
148 	// qt doesn't check this...
149 	if ( scroll->value() < min )
150 		scroll->setValue ( min );
151 	if ( scroll->value() > max )
152 		scroll->setValue ( max );
153 	scroll->setSingleStep(20);
154 	scroll->setPageStep(i);
155 }
156 
157 //---------------------------------------------------------
158 //   setPos
159 //    pos in pixel
160 //---------------------------------------------------------
161 
setPos(unsigned pos)162 void ScrollScale::setPos ( unsigned pos )
163 {
164 	scroll->setValue ( pos );
165 }
166 
167 //---------------------------------------------------------
168 //   setPosNoLimit
169 //    pos in pixel
170 //---------------------------------------------------------
171 
setPosNoLimit(unsigned pos)172 void ScrollScale::setPosNoLimit ( unsigned pos )
173 {
174 	//printf ( "ScrollScale::setPosNoLimit pos:%d scaleVal:%d offset ticks:%d\n", pos, scaleVal, pos2offset ( pos ) );
175 
176 	if((int)pos > scroll->maximum())
177 		scroll->setMaximum(pos);
178 	scroll->setValue(pos);
179 }
180 
181 //---------------------------------------------------------
182 //   resizeEvent
183 //---------------------------------------------------------
184 
resizeEvent(QResizeEvent * ev)185 void ScrollScale::resizeEvent ( QResizeEvent* ev)
186 {
187 	QWidget::resizeEvent(ev);
188 	emit scaleChanged ( scaleVal );
189 	if ( !noScale )
190 		setRange ( minVal, maxVal );
191 }
192 
193 //---------------------------------------------------------
194 //   ScrollScale
195 //---------------------------------------------------------
196 
ScrollScale(int s1,int s2,int cs,int max_,Qt::Orientation o,QWidget * parent,int min_,bool inv,double bas)197 ScrollScale::ScrollScale ( int s1, int s2, int cs, int max_, Qt::Orientation o,
198                            QWidget* parent, int min_, bool inv, double bas )
199 		: QWidget ( parent )
200 {
201 	noScale     = false;
202 	_page        = 0;
203 	_pages       = 1;
204     pageButtons = false;
205 	showMagFlag = true;
206 	scaleMin    = s1;
207 	scaleMax    = s2;
208 	minVal      = min_;
209 	maxVal      = max_;
210     up          = nullptr;
211     down        = nullptr;
212 	logbase     = bas;
213 	invers      = inv;
214 	scaleVal    = 0;
215 
216 	scaleVal = cs;
217 	const int cur = scale2mag(cs);
218 
219 	//fprintf(stderr, "ScrollScale: cs:%d cur:%f\n", cs, cur);
220 	scale  = new QSlider (o);
221     scale->setObjectName("ScrollScaleZoomSlider");
222 	// Added by Tim. For some reason focus was on.
223 	// It messes up tabbing, and really should have a shortcut instead.
224 	scale->setFocusPolicy(Qt::NoFocus);
225 	scale->setMinimum(0);
226 	scale->setMaximum(convertQuickZoomLevelToMag(zoomLevels-1));
227 	scale->setPageStep(1);
228 	scale->setValue(cur);
229 
230 	scroll = new QScrollBar ( o );
231 	//scroll->setFocusPolicy(Qt::NoFocus);  // Tim.
232 
233 	emit scaleChanged ( scaleVal );
234 	if ( !noScale )
235 		setRange ( minVal, maxVal );
236 
237 	if ( o == Qt::Horizontal )
238 	{
239 		box = new QBoxLayout ( QBoxLayout::LeftToRight);
240 		scale->setMaximumWidth ( 70 );
241 		scroll->setMinimumWidth ( 50 );
242 	}
243 	else
244 	{
245 		box = new QBoxLayout ( QBoxLayout::TopToBottom);
246 		scroll->setMinimumHeight ( 50 );
247 		scale->setMaximumHeight ( 70 );
248 	}
249     box->setContentsMargins(0, 0, 0, 0);
250     box->setSpacing(0);
251     box->addWidget ( scroll, 10 );
252 
253     int w = style()->pixelMetric(QStyle::PM_ScrollBarExtent);;
254     scaleUp = new QToolButton;
255     scaleUp->setObjectName("ScrollScaleZoomButton");
256     scaleUp->setFocusPolicy(Qt::NoFocus);
257     scaleUp->setMaximumSize(w, w);
258     scaleUp->setIcon (*plusSVGIcon);
259     scaleUp->setToolTip(tr("Increase zoom level"));
260     connect(scaleUp, &QToolButton::clicked, this, [this](){ stepScale(true); });
261     scaleDown = new QToolButton;
262     scaleDown->setFocusPolicy(Qt::NoFocus);
263     scaleDown->setObjectName("ScrollScaleZoomButton");
264     scaleDown->setMaximumSize(w, w);
265     scaleDown->setIcon (*minusSVGIcon);
266     scaleDown->setToolTip(tr("Decrease zoom level"));
267     connect(scaleDown, &QToolButton::clicked, this, [this](){ stepScale(false); });
268 
269     box->addSpacing(4);
270     box->addWidget(scaleDown);
271 	box->addWidget ( scale, 5 );
272     box->addWidget(scaleUp);
273 
274     setLayout(box);
275 	connect ( scale, SIGNAL ( valueChanged ( int ) ), SLOT ( setScale ( int ) ) );
276 	connect ( scroll, SIGNAL ( valueChanged ( int ) ), SIGNAL ( scrollChanged ( int ) ) );
277 }
278 
279 //---------------------------------------------------------
280 //   setPageButtons
281 //---------------------------------------------------------
282 
setPageButtons(bool flag)283 void ScrollScale::setPageButtons ( bool flag )
284 {
285 	if ( flag == pageButtons )
286 		return;
287 
288 	if ( flag )
289 	{
290 		if ( up == nullptr )
291 		{
292 			up = new QToolButton;
293 			up->setIcon ( QIcon(":/svg/up_vee.svg") );
294 			down = new QToolButton;
295 			down->setIcon ( QIcon(":/svg/down_vee.svg") );
296 			pageNo = new QLabel;
297 			QString s;
298 			s.setNum ( _page+1 );
299 			pageNo->setText ( s );
300 			down->setToolTip(tr ( "next page" ) );
301 			up->setToolTip(tr ( "previous page" ) );
302 			pageNo->setToolTip(tr ( "current page number" ) );
303 			box->insertWidget ( 1, up );
304 			box->insertWidget ( 2, down );
305 			box->insertSpacing ( 3, 5 );
306 			box->insertWidget ( 4, pageNo );
307 			box->insertSpacing ( 5, 5 );
308 			connect ( up, SIGNAL ( clicked() ), SLOT ( pageUp() ) );
309 			connect ( down, SIGNAL ( clicked() ), SLOT ( pageDown() ) );
310 		}
311 		up->show();
312 		down->show();
313 		pageNo->show();
314 		if ( _page == ( _pages-1 ) )
315 			down->setEnabled ( false );
316 		if ( _page == 0 )
317 			up->setEnabled ( false );
318 	}
319 	else
320 	{
321 		up->hide();
322 		down->hide();
323 	}
324 	pageButtons = flag;
325 }
326 
327 //---------------------------------------------------------
328 //   showMag
329 //---------------------------------------------------------
330 
showMag(bool flag)331 void ScrollScale::showMag ( bool flag )
332 {
333 	showMagFlag = flag;
334 	if ( flag )
335 		scale->show();
336 	else
337 		scale->hide();
338 	box->activate();
339 }
340 
341 //---------------------------------------------------------
342 //   offset
343 //---------------------------------------------------------
offset() const344 int ScrollScale::offset() const
345 {
346 	return pos2offset ( scroll->value() );
347 }
348 
349 //---------------------------------------------------------
350 //   pos2offset
351 //---------------------------------------------------------
pos2offset(int pos) const352 int ScrollScale::pos2offset ( int pos ) const
353 {
354 	if ( scaleVal < 1 )
355 		return pos * ( -scaleVal ) + scaleVal/2;
356 	else
357 		return pos / scaleVal;
358 }
359 
360 //---------------------------------------------------------
361 //   offset2pos
362 //---------------------------------------------------------
363 
offset2pos(int off) const364 int ScrollScale::offset2pos ( int off ) const
365 {
366 	if ( scaleVal < 1 )
367 		return ( off-scaleVal/2 ) / ( -scaleVal );
368 	else
369 		return off * scaleVal;
370 }
371 
372 //---------------------------------------------------------
373 //   mag2scale
374 //---------------------------------------------------------
375 
mag2scale(int mag) const376 int ScrollScale::mag2scale(int mag) const
377 {
378 	int mag_max = convertQuickZoomLevelToMag(zoomLevels-1);
379 	if(mag < 0)
380 	  mag = 0;
381 	else if(mag > mag_max)
382 	  mag = mag_max;
383 	if ( invers )
384 		mag = mag_max - mag;
385 	double min, max;
386 	if ( scaleMin < 0 )
387 		min = 1.0/ ( -scaleMin );
388 	else
389 		min = double ( scaleMin );
390 
391 	if ( scaleMax < 0 )
392 		max = 1.0/ ( -scaleMax );
393 	else
394 		max = double ( scaleMax );
395 
396 	double diff = max-min;
397 	double fkt  = double ( mag ) /double(mag_max);
398 	double v = ( pow ( logbase, fkt )-1 ) / ( logbase-1 );
399 	double scale;
400 	if ( invers )
401 		scale = max - v * diff;
402 	else
403 		scale = min + v * diff;
404 
405 #if 0
406 	if ( scaleMax > scaleMin )
407 	{
408 		if ( scale < scaleMin )
409 			scale = scaleMin;
410 		else if ( scale > scaleMax )
411 			scale = scaleMax;
412 	}
413 	else
414 	{
415 		if ( scale < scaleMax )
416 			scale = scaleMax;
417 		else if ( scale > scaleMin )
418 			scale = scaleMin;
419 	}
420 #endif
421 
422 	int scale_val;
423 	if ( scale < 1.0 )
424 		// Floor, rather than simply casting 1.0/scale as a negative int,
425 		//  was required here due to the unique nature of our negative numbers,
426 		//  so that the reciprocal scale2mag() matches this mag2scale().
427 		// Tested OK so far, loading and saving, with newly opened windows as well. Tim.
428 		scale_val = floor ( 1.0 / ( -scale ) );
429 	else
430 		scale_val = int ( scale );
431 	if ( scale_val == -1 )   // nur so
432 		scale_val = 1;
433 	return scale_val;
434 }
435 
436 //---------------------------------------------------------
437 //   scale2mag
438 //---------------------------------------------------------
439 
scale2mag(int scale) const440 int ScrollScale::scale2mag(int scale) const
441 {
442 	double min, max;
443 	if ( scaleMin < 0 )
444 		min = 1.0/ ( -scaleMin );
445 	else
446 		min = double ( scaleMin );
447 
448 	if ( scaleMax < 0 )
449 		max = 1.0/ ( -scaleMax );
450 	else
451 		max = double ( scaleMax );
452 
453 	double cmag = ( scale < 0 ) ? ( 1.0/ ( -scale ) ) : double ( scale );
454 	double diff = max-min;
455 
456 	const int mag_max = convertQuickZoomLevelToMag(zoomLevels-1);
457 
458 	// Do a log in the given logbase (see Change of Base Formula).
459 	// Choice of 'log()' base (10, 2 natural etc.) is not supposed to matter.
460 	const double fkt = log10( (cmag - min) * (logbase - 1) / diff + 1 ) / log10(logbase);
461 // 	const double fkt = log( (cmag - min) * (logbase - 1) / diff + 1 ) / log(logbase);
462 	// Round up so that the reciprocal function scale2mag() matches.
463 	const double cur = ceil( fkt * mag_max );
464 
465 	return cur;
466 }
467 
468 //---------------------------------------------------------
469 //   setOffset
470 //    val in tick
471 //---------------------------------------------------------
472 
setOffset(int val)473 void ScrollScale::setOffset ( int val )
474 {
475 	int i = ( scroll->orientation() == Qt::Horizontal ) ? width() : height();
476 	int pos, max;
477 
478 	if ( scaleVal < 1 )
479 	{
480 		pos = ( val-scaleVal/2 ) / ( -scaleVal );
481 		max = ( maxVal-scaleVal-1 ) / ( -scaleVal ) - i;
482 	}
483 	else
484 	{
485 		pos = val * scaleVal;
486 		max = maxVal * scaleVal - i;
487 	}
488 	if ( pos > max )
489 	{
490 		int min;
491 		if ( scaleVal < 1 )
492 		{
493 			maxVal  = ( pos + width() ) * ( -scaleVal );
494 			min = ( minVal-scaleVal/2 ) / ( -scaleVal );
495 			max     = ( maxVal-scaleVal/2 ) / ( -scaleVal ) - i;
496 		}
497 		else
498 		{
499 			maxVal  = ( pos + width() + scaleVal/2 ) /scaleVal;
500 			min = minVal * scaleVal;
501 			max     = maxVal * scaleVal - i;
502 		}
503 
504 		if ( max < 0 )
505 			max = 0;
506 		if ( min < 0 )
507 			min = 0;
508 		if ( min > max )
509 			max = min;
510 		scroll->setRange ( min, max );
511 	}
512 
513 	setPos ( pos );
514 }
515 
516 //---------------------------------------------------------
517 //   pageUp
518 //    goto previous page
519 //---------------------------------------------------------
520 
pageUp()521 void ScrollScale::pageUp()
522 {
523 	if ( _page )
524 	{
525 		--_page;
526 		emit newPage ( _page );
527 		QString s;
528 		s.setNum ( _page+1 );
529 		pageNo->setText ( s );
530 		if ( _page == 0 )
531 			up->setEnabled ( false );
532 		if ( _page == ( _pages-2 ) )
533 			down->setEnabled ( true );
534 	}
535 }
536 
537 //---------------------------------------------------------
538 //   pageDown
539 //    goto next page
540 //---------------------------------------------------------
541 
pageDown()542 void ScrollScale::pageDown()
543 {
544 	if ( _page + 1 < _pages )
545 	{
546 		++_page;
547 		emit newPage ( _page );
548 		QString s;
549 		s.setNum ( _page+1 );
550 		pageNo->setText ( s );
551 		if ( _page == ( _pages-1 ) )
552 			down->setEnabled ( false );
553 		if ( _page == 1 )
554 			up->setEnabled ( true );
555 	}
556 }
557 
558 //---------------------------------------------------------
559 //   setPages
560 //---------------------------------------------------------
561 
setPages(int n)562 void ScrollScale::setPages ( int n )
563 {
564 	_pages = n;
565 	if ( _page >= _pages )
566 	{
567 		_page = _pages-1;
568 		emit newPage ( _page );
569 		QString s;
570 		s.setNum ( _page+1 );
571 		pageNo->setText ( s );
572 	}
573 	up->setEnabled ( _page );
574 	down->setEnabled ( _page < ( _pages-1 ) );
575 }
576 
pos() const577 int ScrollScale::pos() const
578 {
579 	return scroll->value();
580 }
581 
mag() const582 int ScrollScale::mag() const
583 {
584 	return scale->value();
585 }
586 
587 /**
588  * Hardcoded hackish function that corresponds to the values used for the scrollscales in PianoRoll and DrumEditor
589  * since I couldn't easily create any inverse function from the [0,1024]-range to detect where a zoom actually occurs
590  * (mg)
591  */
getQuickZoomLevel(int mag)592 int ScrollScale::getQuickZoomLevel(int mag)
593 {
594 	if (mag == 0)
595 		return 0;
596 
597 	for (int i=0; i<zoomLevels-1; i++) {
598 		int val1 = ScrollScale::convertQuickZoomLevelToMag(i);
599 		int val2 = ScrollScale::convertQuickZoomLevelToMag(i + 1);
600 		if (mag > val1 && mag <= val2)
601 			return i + 1;
602 	}
603 
604 	return -1;
605 }
606 
607 /**
608  * Function returning the boundary values for a zoom change, hardcoded corresponding to the values used in PianoRoll
609  * and DrumEditor
610  */
convertQuickZoomLevelToMag(int zoomlevel)611 int ScrollScale::convertQuickZoomLevelToMag(int zoomlevel)
612 {
613 	int vals[] = {
614 		0,   1,   15,  30,  46,  62,  80,  99,  119, 140,
615 		163, 187, 214, 242, 274, 308, 346, 388, 436, 491,
616 		555, 631, 726, 849, 1024, 1200, 1300, 1400, 1500, 1600, 1700, 1800,
617 		1900, 2100, 2200, 2300, 2400, 2500 };
618 
619 	return vals[zoomlevel];
620 }
621 
scaleMinimum() const622 int ScrollScale::scaleMinimum() const { return scaleMin; }
scaleMaximum() const623 int ScrollScale::scaleMaximum() const { return scaleMax; }
624 
setScaleMinimum(int min)625 void ScrollScale::setScaleMinimum(int min)
626 {
627   if(scaleMin == min)
628     return;
629   scaleMin = min;
630 
631   if(scaleVal < scaleMin)
632   {
633     scaleVal = scaleMin;
634     emit scaleChanged ( scaleVal );
635     if ( !noScale )
636       setRange ( minVal, maxVal );
637   }
638   repaint();
639 }
640 
setScaleMaximum(int max)641 void ScrollScale::setScaleMaximum(int max)
642 {
643   if(scaleMax == max)
644     return;
645   scaleMax = max;
646 
647   if(scaleVal > scaleMax)
648   {
649     scaleVal = scaleMax;
650     emit scaleChanged ( scaleVal );
651     if ( !noScale )
652       setRange ( minVal, maxVal );
653   }
654   repaint();
655 }
656 
setScaleRange(int min,int max)657 void ScrollScale::setScaleRange(int min, int max)
658 {
659   if(scaleMin == min && scaleMax == max)
660     return;
661   scaleMin = min;
662   scaleMax = max;
663 
664   if(scaleVal < scaleMin || scaleVal > scaleMax)
665   {
666     if(scaleVal < scaleMin)
667       scaleVal = scaleMin;
668     else if(scaleVal > scaleMax)
669       scaleVal = scaleMax;
670     emit scaleChanged ( scaleVal );
671     if ( !noScale )
672       setRange ( minVal, maxVal );
673   }
674   repaint();
675 }
676 
677 } // namespace MusEGui
678