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