hydrogen 1.2.3
WidgetWithInput.cpp
Go to the documentation of this file.
1/*
2 * Hydrogen
3 * Copyright(c) 2008-2024 The hydrogen development team [hydrogen-devel@lists.sourceforge.net]
4 *
5 * http://www.hydrogen-music.org
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 2 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, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
21 */
22#include "WidgetWithInput.h"
23#include "../CommonStrings.h"
24#include "../HydrogenApp.h"
25#include "MidiSenseWidget.h"
26
27#include <core/Hydrogen.h>
28#include <core/MidiMap.h>
29
30#ifdef WIN32
31# include "core/Timehelper.h"
32#else
33# include <unistd.h>
34# include <sys/time.h>
35#endif
36
37WidgetWithInput::WidgetWithInput( QWidget* parent, bool bUseIntSteps, QString sBaseTooltip, int nScrollSpeed, int nScrollSpeedFast, float fMin, float fMax, bool bModifyOnChange )
38 : QWidget( parent )
39 , m_bUseIntSteps( bUseIntSteps )
40 , m_sBaseTooltip( sBaseTooltip )
41 , m_nScrollSpeed( nScrollSpeed )
42 , m_nScrollSpeedFast( nScrollSpeedFast )
43 , m_fMin( fMin )
44 , m_fMax( fMax )
45 , m_fDefaultValue( fMin )
46 , m_fValue( fMin )
47 , m_fMousePressValue( 0.0 )
48 , m_fMousePressY( 0.0 )
49 , m_bIgnoreMouseMove( false )
50 , m_bEntered( false )
51 , m_bIsActive( true )
52 , m_nWidgetHeight( 20 )
53 , m_nWidgetWidth( 20 )
54 , m_sInputBuffer( "" )
55 , m_inputBufferTimeout( 2.0 )
56 , m_bModifyOnChange( bModifyOnChange ) {
57
58 setAttribute( Qt::WA_Hover );
59 setFocusPolicy( Qt::ClickFocus );
61
63}
64
66
68
69 auto pCommonStrings = HydrogenApp::get_instance()->getCommonStrings();
70
71 QString sTip = QString("%1: %2\n\n%3: [%4, %5]" ).arg( m_sBaseTooltip ).arg( m_fValue, 0, 'f', 2 )
72 .arg( pCommonStrings->getRangeTooltip() )
73 .arg( m_fMin, 0, 'f', 2 ).arg( m_fMax, 0, 'f', 2 );
74
75 // Add the associated MIDI action.
76 if ( m_pAction != nullptr ) {
77 sTip.append( QString( "\n%1: %2 " ).arg( pCommonStrings->getMidiTooltipHeading() )
78 .arg( m_pAction->getType() ) );
79 if ( m_registeredMidiEvents.size() > 0 ) {
80 for ( const auto& [event, nnParam] : m_registeredMidiEvents ) {
83 sTip.append( QString( "\n%1 [%2 : %3]" )
84 .arg( pCommonStrings->getMidiTooltipBound() )
86 .arg( nnParam ) );
87 }
88 else {
89 // PC and MMC_x do not have a parameter.
90 sTip.append( QString( "\n%1 [%2]" )
91 .arg( pCommonStrings->getMidiTooltipBound() )
92 .arg( H2Core::MidiMessage::EventToQString( event ) ) );
93 }
94 }
95
96 }
97 else {
98 sTip.append( QString( "%1" ).arg( pCommonStrings->getMidiTooltipUnbound() ) );
99 }
100 }
101
102 setToolTip( sTip );
103}
104
105void WidgetWithInput::setIsActive( bool bIsActive ) {
106 m_bIsActive = bIsActive;
107 update();
108}
109
110void WidgetWithInput::setValue( float fValue, bool bTriggeredByUserInteraction )
111{
112 if ( ! m_bIsActive ) {
113 return;
114 }
115
116 if ( m_bUseIntSteps ) {
117 fValue = std::round( fValue );
118 } else {
119 if ( std::abs( fValue ) < 1E-6 ) {
120 // The calculation of the increment when altering the
121 // value via drag or mouse wheel - (m_fMax - m_fMin)/100.0
122 // - introduces rounding errors. These become dominant
123 // when trying to reset the widget's value to zero and
124 // cause a mismatch.
125 fValue = 0.0;
126 }
127 }
128
129
130 if ( fValue == m_fValue ) {
131 return;
132 }
133
134 if ( fValue < m_fMin ) {
135 fValue = m_fMin;
136 }
137 else if ( fValue > m_fMax ) {
138 fValue = m_fMax;
139 }
140
141 if ( fValue != m_fValue ) {
142 m_fValue = fValue;
143 emit valueChanged( this );
145 update();
146
147 if ( m_bModifyOnChange && bTriggeredByUserInteraction ) {
149 }
150 }
151}
152
153
155{
156 if ( ! m_bIsActive ) {
157 return;
158 }
159
160 if ( ev->button() == Qt::LeftButton && ev->modifiers() == Qt::ControlModifier ) {
162 m_bIgnoreMouseMove = true;
163 }
164 else if ( ev->button() == Qt::LeftButton && ev->modifiers() == Qt::ShiftModifier ) {
165 MidiSenseWidget midiSense( this, true, this->m_pAction );
166 midiSense.exec();
167
168 m_bIgnoreMouseMove = true;
169 }
170 else {
171 setCursor( QCursor( Qt::SizeVerCursor ) );
172
174 m_fMousePressY = ev->y();
175 }
176
177 QToolTip::showText( ev->globalPos(), QString( "%1" ).arg( m_fValue, 0, 'f', 2 ) , this );
178}
179
181{
182 UNUSED( ev );
183
184 if ( ! m_bIsActive ) {
185 return;
186 }
187
188 setCursor( QCursor( Qt::ArrowCursor ) );
189
190 m_bIgnoreMouseMove = false;
191}
192
193void WidgetWithInput::wheelEvent ( QWheelEvent *ev )
194{
195 ev->accept();
196
197 if ( ! m_bIsActive ) {
198 return;
199 }
200
201 float fStepFactor;
202 float fDelta = 1.0;
203
204 if ( ev->modifiers() == Qt::ControlModifier ) {
205 fStepFactor = m_nScrollSpeedFast;
206 } else {
207 fStepFactor = m_nScrollSpeed;
208 }
209
210 if ( !m_bUseIntSteps ) {
211 float fRange = m_fMax - m_fMin;
212 fDelta = fRange / 100.0;
213 }
214 if ( ev->angleDelta().y() < 0 ) {
215 fDelta *= -1.;
216 }
217
218 setValue( getValue() + ( fDelta * fStepFactor ), true );
219
220 QToolTip::showText(
221#if QT_VERSION >= QT_VERSION_CHECK( 5, 14, 0 )
222 ev->globalPosition().toPoint(),
223#else
224 ev->globalPos(),
225#endif
226 QString( "%1" ).arg( m_fValue, 0, 'f', 2 ) , this );
227}
228
229
230
231void WidgetWithInput::mouseMoveEvent( QMouseEvent *ev )
232{
233
234 if ( ! m_bIsActive || m_bIgnoreMouseMove ) {
235 return;
236 }
237
238 float fStepFactor;
239
240 if ( ev->modifiers() == Qt::ControlModifier ) {
241 fStepFactor = m_nScrollSpeedFast;
242 } else {
243 fStepFactor = m_nScrollSpeed;
244 }
245
246 float fRange = m_fMax - m_fMin;
247
248 float fDeltaY = ev->y() - m_fMousePressY;
249 float fNewValue = ( m_fMousePressValue - fStepFactor * ( fDeltaY / 100.0 * fRange ) );
250
251 setValue( fNewValue, true );
252
253 QToolTip::showText( ev->globalPos(), QString( "%1" ).arg( m_fValue, 0, 'f', 2 ) , this );
254}
255
256void WidgetWithInput::enterEvent( QEvent *ev ) {
257 UNUSED( ev );
258 m_bEntered = true;
259 update();
260}
261
262void WidgetWithInput::leaveEvent( QEvent *ev ) {
263 UNUSED( ev );
264 m_bEntered = false;
265 update();
266}
267
268void WidgetWithInput::keyPressEvent( QKeyEvent *ev ) {
269
270 if ( ! m_bIsActive ) {
271 return;
272 }
273
274 float fIncrement;
275 if ( !m_bUseIntSteps ) {
276 fIncrement = ( m_fMax - m_fMin ) / 100.0;
277 } else {
278 fIncrement = 1.0;
279 }
280
281 if ( ev->key() == Qt::Key_Right || ev->key() == Qt::Key_Up ) {
282 if ( ev->modifiers() == Qt::ControlModifier ) {
283 fIncrement *= m_nScrollSpeedFast;
284 } else {
285 fIncrement *= m_nScrollSpeed;
286 }
287 setValue( m_fValue + fIncrement, true );
288 } else if ( ev->key() == Qt::Key_PageUp ) {
289 setValue( m_fValue + fIncrement * m_nScrollSpeedFast, true );
290 } else if ( ev->key() == Qt::Key_Home ) {
291 setValue( m_fMax, true );
292 } else if ( ev->key() == Qt::Key_Left || ev->key() == Qt::Key_Down ) {
293 if ( ev->modifiers() == Qt::ControlModifier ) {
294 fIncrement *= m_nScrollSpeedFast;
295 } else {
296 fIncrement *= m_nScrollSpeed;
297 }
298 setValue( m_fValue - fIncrement, true );
299 } else if ( ev->key() == Qt::Key_PageDown ) {
300 setValue( m_fValue - fIncrement * m_nScrollSpeedFast, true );
301 } else if ( ev->key() == Qt::Key_Home ) {
302 setValue( m_fMin, true );
303 } else if ( ( ev->key() >= Qt::Key_0 && ev->key() <= Qt::Key_9 ) || ev->key() == Qt::Key_Minus || ev->key() == Qt::Key_Period ) {
304
305 timeval now;
306 gettimeofday( &now, nullptr );
307 // Flush the input buffer if there was no user input for X
308 // seconds
309 if ( ( static_cast<double>( now.tv_sec ) +
310 static_cast<double>( now.tv_usec * US_DIVIDER )) -
311 ( static_cast<double>( m_inputBufferTimeval.tv_sec ) +
312 static_cast<double>( m_inputBufferTimeval.tv_usec * US_DIVIDER ) ) >
314 m_sInputBuffer.clear();
315 }
317
318 if ( ev->key() == Qt::Key_Period ) {
319 m_sInputBuffer += ".";
320 } else if ( ev->key() == Qt::Key_Minus ) {
321 m_sInputBuffer += "-";
322 } else {
323 m_sInputBuffer += QString::number( ev->key() - 48 );
324 }
325
326 bool bOk;
327 float fNewValue = m_sInputBuffer.toFloat( &bOk );
328 if ( ! bOk ) {
329 return;
330 }
331 setValue( fNewValue, true );
332 update();
333 } else if ( ev->key() == Qt::Key_Escape ) {
334 // reset the input buffer
335 m_sInputBuffer.clear();
336 QToolTip::hideText();
337 return;
338 } else {
339 // return without showing a tooltop
340 return;
341 }
342
343 QPoint p( mapToGlobal( QPoint( 0,0 ) ) );
344 QToolTip::showText( QPoint( p.x() + width(), p.y() ), QString( "%1" ).arg( m_fValue, 0, 'f', 2 ), this, geometry(), m_inputBufferTimeout * 1000 );
345}
346
347void WidgetWithInput::setMin( float fMin )
348{
349 if ( fMin == m_fMin ) {
350 return;
351 }
352 if ( m_bUseIntSteps && std::fmod( fMin, 1.0 ) != 0.0 && ! std::isinf( fMin ) ) {
353 ___WARNINGLOG( QString( "As widget is set to use integer values only the supply minimal value [%1] will be rounded to [%2] " )
354 .arg( fMin )
355 .arg( std::round( fMin ) ) );
356 fMin = std::round( fMin );
357 }
358
359 if ( fMin >= m_fMax ) {
360 ___ERRORLOG( QString( "Supplied value [%1] must be smaller than maximal one [%2]" )
361 .arg( fMin ).arg( m_fMax ) );
362 return;
363 }
364
365 if ( fMin != m_fMin ) {
366 m_fMin = fMin;
367
368 if ( m_fValue < fMin ) {
369 setValue( fMin, false );
370 }
371 update();
372 }
373}
374
375void WidgetWithInput::setMax( float fMax )
376{
377 if ( fMax == m_fMax ) {
378 return;
379 }
380 if ( m_bUseIntSteps && std::fmod( fMax, 1.0 ) != 0.0 && ! std::isinf( fMax ) ) {
381 ___WARNINGLOG( QString( "As widget is set to use integer values only the supply maximal value [%1] will be rounded to [%2] " )
382 .arg( fMax )
383 .arg( std::round( fMax ) ) );
384 fMax = std::round( fMax );
385 }
386
387 if ( fMax >= m_fMin ) {
388 ___ERRORLOG( QString( "Supplied value [%1] must be bigger than the minimal one [%2]" )
389 .arg( fMax ).arg( m_fMin ) );
390 return;
391 }
392
393 if ( fMax != m_fMax ) {
394 m_fMax = fMax;
395
396 if ( m_fValue > fMax ) {
397 setValue( fMax, false );
398 }
399 update();
400 }
401}
402
403
404void WidgetWithInput::setDefaultValue( float fDefaultValue )
405{
406 if ( fDefaultValue == m_fDefaultValue ) {
407 return;
408 }
409
410 if ( m_bUseIntSteps && std::fmod( fDefaultValue, 1.0 ) != 0.0 && ! std::isinf( fDefaultValue ) ) {
411 ___WARNINGLOG( QString( "As widget is set to use integer values only the supply default value [%1] will be rounded to [%2] " )
412 .arg( fDefaultValue )
413 .arg( std::round( fDefaultValue ) ) );
414 fDefaultValue = std::round( fDefaultValue );
415 }
416
417 if ( fDefaultValue < m_fMin ) {
418 fDefaultValue = m_fMin;
419 }
420 else if ( fDefaultValue > m_fMax ) {
421 fDefaultValue = m_fMax;
422 }
423
424 if ( fDefaultValue != m_fDefaultValue ) {
425 m_fDefaultValue = fDefaultValue;
426 }
427}
428
#define ___WARNINGLOG(x)
Definition Object.h:256
#define ___ERRORLOG(x)
Definition Object.h:257
int gettimeofday(struct timeval *tv, struct timezone *tz)
QString getType() const
Definition MidiAction.h:76
static Hydrogen * get_instance()
Returns the current Hydrogen instance __instance.
Definition Hydrogen.h:83
void setIsModified(bool bIsModified)
Wrapper around Song::setIsModified() that checks whether a song is set.
static QString EventToQString(Event event)
static HydrogenApp * get_instance()
Returns the instance of HydrogenApp class.
std::shared_ptr< CommonStrings > getCommonStrings()
std::vector< std::pair< H2Core::MidiMessage::Event, int > > m_registeredMidiEvents
Stores all MIDI events mapped to m_pAction.
std::shared_ptr< Action > m_pAction
virtual void wheelEvent(QWheelEvent *ev)
virtual void enterEvent(QEvent *ev)
virtual void mouseReleaseEvent(QMouseEvent *ev)
int m_nScrollSpeedFast
Fast version used when the Control modifier is pressed.
virtual void setValue(float fValue, bool bTriggeredByUserInteraction=false)
QString m_sInputBuffer
All key input will be appended to this string.
virtual void leaveEvent(QEvent *ev)
void valueChanged(WidgetWithInput *ref)
void updateTooltip() override
Indicates child class to recalculate its tool tip in case m_registeredMidiEvents changed.
virtual void keyPressEvent(QKeyEvent *ev)
timeval m_inputBufferTimeval
bool m_bModifyOnChange
Whether Hydrogen::setIsModified() is invoked with true as soon as the value of the widget does change...
void setMin(float fMin)
WidgetWithInput(QWidget *parent, bool bUseIntSteps, QString sBaseTooltip, int nScrollSpeed, int nScrollSpeedFast, float fMin, float fMax, bool bModifyOnChange)
void setDefaultValue(float fDefaultValue)
double m_inputBufferTimeout
Number of seconds before m_sInputBuffer will be flushed (happens asynchronically whenever the next ke...
virtual void mousePressEvent(QMouseEvent *ev)
void setIsActive(bool bIsActive)
float getValue() const
void setMax(float fMax)
virtual void mouseMoveEvent(QMouseEvent *ev)
#define UNUSED(v)
Definition Globals.h:42
#define US_DIVIDER
Definition Globals.h:46