code:
/*
 * SongEditor.cpp - basic window for song-editing
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "SongEditor.h"

#include <QAction>
#include <QKeyEvent>
#include <QLabel>
#include <QLayout>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QPainter>
#include <QScrollBar>
#include <QTimeLine>

#include "AudioDevice.h"
#include "AutomatableSlider.h"
#include "ComboBox.h"
#include "ConfigManager.h"
#include "CPULoadWidget.h"
#include "DeprecationHelper.h"
#include "embed.h"
#include "GuiApplication.h"
#include "LcdSpinBox.h"
#include "MainWindow.h"
#include "MeterDialog.h"
#include "Mixer.h"
#include "Oscilloscope.h"
#include "PianoRoll.h"
#include "TextFloat.h"
#include "TimeDisplayWidget.h"
#include "TimeLineWidget.h"
#include "ToolTip.h"
#include "Track.h"

const QVector<double> SongEditor::m_zoomLevels =
		{ 0.125f, 0.25f, 0.5f, 1.0f, 2.0f, 4.0f, 8.0f, 16.0f };

SongEditor::SongEditor( Song * song ) :
	TrackContainerView( song ),
	m_song( song ),
	m_zoomingModel(new ComboBoxModel()),
	m_snappingModel(new ComboBoxModel()),
	m_proportionalSnap( false ),
	m_scrollBack( false ),
	m_smoothScroll( ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt() ),
	m_mode(DrawMode),
	m_origin(),
	m_scrollPos(),
	m_mousePos(),
	m_rubberBandStartTrackview(0),
	m_rubberbandStartTimePos(0),
	m_currentZoomingValue(m_zoomingModel->value()),
	m_trackHeadWidth(ConfigManager::inst()->value("ui", "compacttrackbuttons").toInt()==1
					 ? DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT + TRACK_OP_WIDTH_COMPACT
					 : DEFAULT_SETTINGS_WIDGET_WIDTH + TRACK_OP_WIDTH),
	m_selectRegion(false)
{
	m_zoomingModel->setParent(this);
	m_snappingModel->setParent(this);
	m_timeLine = new TimeLineWidget( m_trackHeadWidth, 32,
					pixelsPerBar(),
					m_song->m_playPos[Song::Mode_PlaySong],
					m_currentPosition,
					Song::Mode_PlaySong, this );
	connect( this, SIGNAL( positionChanged( const TimePos & ) ),
				m_song->m_playPos[Song::Mode_PlaySong].m_timeLine,
			SLOT( updatePosition( const TimePos & ) ) );
	connect( m_timeLine, SIGNAL( positionChanged( const TimePos & ) ),
			this, SLOT( updatePosition( const TimePos & ) ) );
	connect( m_timeLine, SIGNAL( regionSelectedFromPixels( int, int ) ),
			this, SLOT( selectRegionFromPixels( int, int ) ) );
	connect( m_timeLine, SIGNAL( selectionFinished() ),
			 this, SLOT( stopRubberBand() ) );

	m_positionLine = new PositionLine(this);
	static_cast<QVBoxLayout *>( layout() )->insertWidget( 1, m_timeLine );

	connect( m_song, SIGNAL( playbackStateChanged() ),
			 m_positionLine, SLOT( update() ) );
	connect( this, SIGNAL( zoomingValueChanged( double ) ),
			 m_positionLine, SLOT( zoomChange( double ) ) );

	// add some essential widgets to global tool-bar
	QWidget * tb = gui->mainWindow()->toolBar();

	gui->mainWindow()->addSpacingToToolBar( 40 );

	m_tempoSpinBox = new LcdSpinBox( 3, tb, tr( "Tempo" ) );
	m_tempoSpinBox->setModel( &m_song->m_tempoModel );
	m_tempoSpinBox->setLabel( tr( "TEMPO" ) );
	ToolTip::add( m_tempoSpinBox, tr( "Tempo in BPM" ) );

	int tempoSpinBoxCol = gui->mainWindow()->addWidgetToToolBar( m_tempoSpinBox, 0 );

#if 0
	toolButton * hq_btn = new toolButton( embed::getIconPixmap( "hq_mode" ),
						tr( "High quality mode" ),
						NULL, NULL, tb );
	hq_btn->setCheckable( true );
	connect( hq_btn, SIGNAL( toggled( bool ) ),
			this, SLOT( setHighQuality( bool ) ) );
	hq_btn->setFixedWidth( 42 );
	gui->mainWindow()->addWidgetToToolBar( hq_btn, 1, col );
#endif

	gui->mainWindow()->addWidgetToToolBar( new TimeDisplayWidget, 1, tempoSpinBoxCol );

	gui->mainWindow()->addSpacingToToolBar( 10 );

	m_timeSigDisplay = new MeterDialog( this, true );
	m_timeSigDisplay->setModel( &m_song->m_timeSigModel );
	gui->mainWindow()->addWidgetToToolBar( m_timeSigDisplay );

	gui->mainWindow()->addSpacingToToolBar( 10 );

	QLabel * master_vol_lbl = new QLabel( tb );
	master_vol_lbl->setPixmap( embed::getIconPixmap( "master_volume" ) );

	m_masterVolumeSlider = new AutomatableSlider( tb,
							tr( "Master volume" ) );
	m_masterVolumeSlider->setModel( &m_song->m_masterVolumeModel );
	m_masterVolumeSlider->setOrientation( Qt::Vertical );
	m_masterVolumeSlider->setPageStep( 1 );
	m_masterVolumeSlider->setTickPosition( QSlider::TicksLeft );
	m_masterVolumeSlider->setFixedSize( 26, 60 );
	m_masterVolumeSlider->setTickInterval( 50 );
	ToolTip::add( m_masterVolumeSlider, tr( "Master volume" ) );

	connect( m_masterVolumeSlider, SIGNAL( logicValueChanged( int ) ), this,
			SLOT( setMasterVolume( int ) ) );
	connect( m_masterVolumeSlider, SIGNAL( sliderPressed() ), this,
			SLOT( showMasterVolumeFloat()) );
	connect( m_masterVolumeSlider, SIGNAL( logicSliderMoved( int ) ), this,
			SLOT( updateMasterVolumeFloat( int ) ) );
	connect( m_masterVolumeSlider, SIGNAL( sliderReleased() ), this,
			SLOT( hideMasterVolumeFloat() ) );

	m_mvsStatus = new TextFloat;
	m_mvsStatus->setTitle( tr( "Master volume" ) );
	m_mvsStatus->setPixmap( embed::getIconPixmap( "master_volume" ) );

	gui->mainWindow()->addWidgetToToolBar( master_vol_lbl );
	gui->mainWindow()->addWidgetToToolBar( m_masterVolumeSlider );

	gui->mainWindow()->addSpacingToToolBar( 10 );

	QLabel * master_pitch_lbl = new QLabel( tb );
	master_pitch_lbl->setPixmap( embed::getIconPixmap( "master_pitch" ) );
	master_pitch_lbl->setFixedHeight( 64 );

	m_masterPitchSlider = new AutomatableSlider( tb, tr( "Master pitch" ) );
	m_masterPitchSlider->setModel( &m_song->m_masterPitchModel );
	m_masterPitchSlider->setOrientation( Qt::Vertical );
	m_masterPitchSlider->setPageStep( 1 );
	m_masterPitchSlider->setTickPosition( QSlider::TicksLeft );
	m_masterPitchSlider->setFixedSize( 26, 60 );
	m_masterPitchSlider->setTickInterval( 12 );
	ToolTip::add( m_masterPitchSlider, tr( "Master pitch" ) );
	connect( m_masterPitchSlider, SIGNAL( logicValueChanged( int ) ), this,
			SLOT( setMasterPitch( int ) ) );
	connect( m_masterPitchSlider, SIGNAL( sliderPressed() ), this,
			SLOT( showMasterPitchFloat() ) );
	connect( m_masterPitchSlider, SIGNAL( logicSliderMoved( int ) ), this,
			SLOT( updateMasterPitchFloat( int ) ) );
	connect( m_masterPitchSlider, SIGNAL( sliderReleased() ), this,
			SLOT( hideMasterPitchFloat() ) );

	m_mpsStatus = new TextFloat;
	m_mpsStatus->setTitle( tr( "Master pitch" ) );
	m_mpsStatus->setPixmap( embed::getIconPixmap( "master_pitch" ) );

	gui->mainWindow()->addWidgetToToolBar( master_pitch_lbl );
	gui->mainWindow()->addWidgetToToolBar( m_masterPitchSlider );

	gui->mainWindow()->addSpacingToToolBar( 10 );

	// create widget for oscilloscope- and cpu-load-widget
	QWidget * vc_w = new QWidget( tb );
	QVBoxLayout * vcw_layout = new QVBoxLayout( vc_w );
	vcw_layout->setMargin( 0 );
	vcw_layout->setSpacing( 0 );

	vcw_layout->addStretch();
	vcw_layout->addWidget( new Oscilloscope( vc_w ) );

	vcw_layout->addWidget( new CPULoadWidget( vc_w ) );
	vcw_layout->addStretch();

	gui->mainWindow()->addWidgetToToolBar( vc_w );

	static_cast<QVBoxLayout *>( layout() )->insertWidget( 0, m_timeLine );

	m_leftRightScroll = new QScrollBar( Qt::Horizontal, this );
	m_leftRightScroll->setMinimum( 0 );
	m_leftRightScroll->setMaximum( 0 );
	m_leftRightScroll->setSingleStep( 1 );
	m_leftRightScroll->setPageStep( 20 );
	static_cast<QVBoxLayout *>( layout() )->addWidget( m_leftRightScroll );
	connect( m_leftRightScroll, SIGNAL( valueChanged( int ) ),
					this, SLOT( scrolled( int ) ) );
	connect( m_song, SIGNAL( lengthChanged( int ) ),
			this, SLOT( updateScrollBar( int ) ) );
	connect(m_leftRightScroll, SIGNAL(valueChanged(int)),this, SLOT(updateRubberband()));
	connect(contentWidget()->verticalScrollBar(), SIGNAL(valueChanged(int)),this, SLOT(updateRubberband()));
	connect(m_timeLine, SIGNAL(selectionFinished()), this, SLOT(stopSelectRegion()));

	//Set up zooming model
	for( float const & zoomLevel : m_zoomLevels )
	{
		m_zoomingModel->addItem( QString( "%1\%" ).arg( zoomLevel * 100 ) );
	}
	m_zoomingModel->setInitValue(
			m_zoomingModel->findText( "100%" ) );
	connect( m_zoomingModel, SIGNAL( dataChanged() ),
					this, SLOT( zoomingChanged() ) );
	connect( m_zoomingModel, SIGNAL( dataChanged() ),
					m_positionLine, SLOT( update() ) );

	//Set up snapping model, 2^i
	for ( int i = 3; i >= -4; i-- )
	{
		if ( i > 0 )
		{
			m_snappingModel->addItem( QString( "%1 Bars").arg( 1 << i ) );
		}
		else if ( i == 0 )
		{
			m_snappingModel->addItem( "1 Bar" );
		}
		else
		{
			m_snappingModel->addItem( QString( "1/%1 Bar" ).arg( 1 << (-i) ) );
		}
	}
	m_snappingModel->setInitValue( m_snappingModel->findText( "1 Bar" ) );

	setFocusPolicy( Qt::StrongFocus );
	setFocus();
}

SongEditor::~SongEditor()
{
}

void SongEditor::saveSettings( QDomDocument& doc, QDomElement& element )
{
	MainWindow::saveWidgetState( parentWidget(), element );
}

void SongEditor::loadSettings( const QDomElement& element )
{
	MainWindow::restoreWidgetState(parentWidget(), element);
}

float SongEditor::getSnapSize() const
{
	// 1 Bar is the third value in the snapping dropdown
	int val = -m_snappingModel->value() + 3;
	// If proportional snap is on, we snap to finer values when zoomed in
	if (m_proportionalSnap)
	{
		val = val - m_zoomingModel->value() + 3;
	}
	val = std::max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing.

	if ( val >= 0 ){
		return 1 << val;
	}
	else {
		return 1.0 / ( 1 << -val );
	}
}

QString SongEditor::getSnapSizeString() const
{
	int val = -m_snappingModel->value() + 3;
	val = val - m_zoomingModel->value() + 3;
	val = std::max(val, -6); // -6 gives 1/64th bar snapping. Lower values cause crashing.

	if ( val >= 0 ){
		int bars = 1 << val;
		if ( bars == 1 ) { return QString("1 Bar"); }
		else
		{
			return QString( "%1 Bars" ).arg(bars);
		}
	}
	else {
		int div = ( 1 << -val );
		return QString( "1/%1 Bar" ).arg(div);
	}
}

void SongEditor::setHighQuality( bool hq )
{
	Engine::mixer()->changeQuality( Mixer::qualitySettings(
			hq ? Mixer::qualitySettings::Mode_HighQuality :
				Mixer::qualitySettings::Mode_Draft ) );
}

void SongEditor::scrolled( int new_pos )
{
	update();
	emit positionChanged( m_currentPosition = TimePos( new_pos, 0 ) );
}

void SongEditor::selectRegionFromPixels(int xStart, int xEnd)
{
	if (!m_selectRegion)
	{
		m_selectRegion = true;

		//deselect all tcos
		for (auto &it : findChildren<selectableObject *>()) { it->setSelected(false); }

		rubberBand()->setEnabled(true);
		rubberBand()->show();

		//we save the position of scrollbars, mouse position and zooming level
		m_origin = QPoint(xStart, 0);
		m_scrollPos = QPoint(m_leftRightScroll->value(), contentWidget()->verticalScrollBar()->value());
		m_currentZoomingValue = zoomingModel()->value();

		//calculate the song position where the mouse was clicked
		m_rubberbandStartTimePos = TimePos((xStart - m_trackHeadWidth)
											/ pixelsPerBar() * TimePos::ticksPerBar())
											+ m_currentPosition;
		m_rubberBandStartTrackview = 0;
	}
	//the current mouse position within the borders of song editor
	m_mousePos = QPoint(qMax(m_trackHeadWidth, qMin(xEnd, width()))
						, std::numeric_limits<int>::max());
	updateRubberband();
}

void SongEditor::stopSelectRegion()
{
	m_selectRegion = false;
}

void SongEditor::updateRubberband()
{
	if (rubberBandActive())
	{
		int originX = m_origin.x();

		//take care of the zooming
		if (m_currentZoomingValue != m_zoomingModel->value())
		{
			originX = m_trackHeadWidth + (originX - m_trackHeadWidth)
					* m_zoomLevels[m_zoomingModel->value()] / m_zoomLevels[m_currentZoomingValue];
		}

		//take care of the scrollbar position
		int hs = (m_leftRightScroll->value() - m_scrollPos.x()) * pixelsPerBar();
		int vs = contentWidget()->verticalScrollBar()->value() - m_scrollPos.y();

		//the adjusted origin point
		QPoint origin = QPoint(qMax(originX - hs, m_trackHeadWidth), m_origin.y() - vs);

		//paint the rubber band rect
		rubberBand()->setGeometry(QRect(origin,
										contentWidget()->mapFromParent(QPoint(m_mousePos.x(), m_mousePos.y()))
										).normalized());

		//the index of the TrackView the mouse is hover
		int rubberBandTrackview = trackIndexFromSelectionPoint(m_mousePos.y());

		//the time position the mouse is hover
		TimePos rubberbandTimePos = TimePos((qMin(m_mousePos.x(), width()) - m_trackHeadWidth)
											  / pixelsPerBar() * TimePos::ticksPerBar())
											  + m_currentPosition;

		//are tcos in the rect of selection?
		for (auto &it : findChildren<selectableObject *>())
		{
			TrackContentObjectView * tco = dynamic_cast<TrackContentObjectView*>(it);
			if (tco)
			{
				auto indexOfTrackView = trackViews().indexOf(tco->getTrackView());
				bool isBeetweenRubberbandViews = indexOfTrackView >= qMin(m_rubberBandStartTrackview, rubberBandTrackview)
											  && indexOfTrackView <= qMax(m_rubberBandStartTrackview, rubberBandTrackview);
				bool isBeetweenRubberbandTimePos = tco->getTrackContentObject()->endPosition() >= qMin(m_rubberbandStartTimePos, rubberbandTimePos)
											  && tco->getTrackContentObject()->startPosition() <= qMax(m_rubberbandStartTimePos, rubberbandTimePos);
				it->setSelected(isBeetweenRubberbandViews && isBeetweenRubberbandTimePos);
			}
		}
	}
}

void SongEditor::setEditMode( EditMode mode )
{
	m_mode = mode;
}

void SongEditor::setEditModeDraw()
{
	setEditMode(DrawMode);
}

void SongEditor::setEditModeSelect()
{
	setEditMode(SelectMode);
}

void SongEditor::toggleProportionalSnap()
{
	m_proportionalSnap = !m_proportionalSnap;
}

void SongEditor::keyPressEvent( QKeyEvent * ke )
{
	bool isShiftPressed = ke->modifiers() & Qt::ShiftModifier;
	if( isShiftPressed &&
						( ke->key() == Qt::Key_Insert || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return ) )
	{
		m_song->insertBar();
	}
	else if( isShiftPressed &&
						( ke->key() == Qt::Key_Delete || ke->key() == Qt::Key_Backspace ) )
	{
		m_song->removeBar();
	}
	else if( ke->key() == Qt::Key_Left )
	{
		tick_t t = m_song->currentTick() - TimePos::ticksPerBar();
		if( t >= 0 )
		{
			m_song->setPlayPos( t, Song::Mode_PlaySong );
		}
	}
	else if( ke->key() == Qt::Key_Right )
	{
		tick_t t = m_song->currentTick() + TimePos::ticksPerBar();
		if( t < MaxSongLength )
		{
			m_song->setPlayPos( t, Song::Mode_PlaySong );
		}
	}
	else if( ke->key() == Qt::Key_Home )
	{
		m_song->setPlayPos( 0, Song::Mode_PlaySong );
	}
	else if( ke->key() == Qt::Key_Delete || ke->key() == Qt::Key_Backspace )
	{
		QVector<selectableObject *> so = selectedObjects();
		for( QVector<selectableObject *>::iterator it = so.begin();
				it != so.end(); ++it )
		{
			TrackContentObjectView * tcov =
				dynamic_cast<TrackContentObjectView *>( *it );
			tcov->remove();
		}
	}
	else if( ke->key() == Qt::Key_A && ke->modifiers() & Qt::ControlModifier )
	{
		selectAllTcos( !isShiftPressed );
	}
	else if( ke->key() == Qt::Key_Escape )
	{
		selectAllTcos( false );
	}
	else
	{
		QWidget::keyPressEvent( ke );
	}
}

void SongEditor::wheelEvent( QWheelEvent * we )
{
	if( we->modifiers() & Qt::ControlModifier )
	{
		int z = m_zoomingModel->value();

		if(we->angleDelta().y() > 0)
		{
			z++;
		}
		else if(we->angleDelta().y() < 0)
		{
			z--;
		}
		z = qBound( 0, z, m_zoomingModel->size() - 1 );

		int x = position(we).x() - m_trackHeadWidth;
		// bar based on the mouse x-position where the scroll wheel was used
		int bar = x / pixelsPerBar();
		// what would be the bar in the new zoom level on the very same mouse x
		int newBar = x / DEFAULT_PIXELS_PER_BAR / m_zoomLevels[z];
		// scroll so the bar "selected" by the mouse x doesn't move on the screen
		m_leftRightScroll->setValue(m_leftRightScroll->value() + bar - newBar);

		// update combobox with zooming-factor
		m_zoomingModel->setValue( z );

		// update timeline
		m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->
					setPixelsPerBar( pixelsPerBar() );
		// and make sure, all TCO's are resized and relocated
		realignTracks();
	}

	// FIXME: Reconsider if determining orientation is necessary in Qt6.
	else if(abs(we->angleDelta().x()) > abs(we->angleDelta().y())) // scrolling is horizontal
	{
		m_leftRightScroll->setValue(m_leftRightScroll->value() -
							we->angleDelta().x() /30);
	}
	else if(we->modifiers() & Qt::ShiftModifier)
	{
		m_leftRightScroll->setValue(m_leftRightScroll->value() -
							we->angleDelta().y() / 30);
	}
	else
	{
		we->ignore();
		return;
	}
	we->accept();
}

void SongEditor::closeEvent( QCloseEvent * ce )
{
	if( parentWidget() )
	{
		parentWidget()->hide();
	}
	else
	{
		hide();
	}
	ce->ignore();
}

void SongEditor::mousePressEvent(QMouseEvent *me)
{
	if (allowRubberband())
	{
		//we save the position of scrollbars, mouse position and zooming level
		m_scrollPos = QPoint(m_leftRightScroll->value(), contentWidget()->verticalScrollBar()->value());
		m_origin = contentWidget()->mapFromParent(QPoint(me->pos().x(), me->pos().y()));
		m_currentZoomingValue = zoomingModel()->value();

		//paint the rubberband
		rubberBand()->setEnabled(true);
		rubberBand()->setGeometry(QRect(m_origin, QSize()));
		rubberBand()->show();

		//the trackView(index) and the time position where the mouse was clicked
		m_rubberBandStartTrackview = trackIndexFromSelectionPoint(me->y());
		m_rubberbandStartTimePos = TimePos((me->x() - m_trackHeadWidth)
											/ pixelsPerBar() * TimePos::ticksPerBar())
											+ m_currentPosition;
	}
	QWidget::mousePressEvent(me);
}

void SongEditor::mouseMoveEvent(QMouseEvent *me)
{
	m_mousePos = me->pos();
	updateRubberband();
	QWidget::mouseMoveEvent(me);
}

void SongEditor::mouseReleaseEvent(QMouseEvent *me)
{
	rubberBand()->hide();
	rubberBand()->setEnabled(false);
	QWidget::mouseReleaseEvent(me);
}

void SongEditor::setMasterVolume( int new_val )
{
	updateMasterVolumeFloat( new_val );

	if( !m_mvsStatus->isVisible() && !m_song->m_loadingProject
					&& m_masterVolumeSlider->showStatus() )
	{
		m_mvsStatus->moveGlobal( m_masterVolumeSlider,
			QPoint( m_masterVolumeSlider->width() + 2, -2 ) );
		m_mvsStatus->setVisibilityTimeOut( 1000 );
	}
	Engine::mixer()->setMasterGain( new_val / 100.0f );
}

void SongEditor::showMasterVolumeFloat( void )
{
	m_mvsStatus->moveGlobal( m_masterVolumeSlider,
			QPoint( m_masterVolumeSlider->width() + 2, -2 ) );
	m_mvsStatus->show();
	updateMasterVolumeFloat( m_song->m_masterVolumeModel.value() );
}

void SongEditor::updateMasterVolumeFloat( int new_val )
{
	m_mvsStatus->setText( tr( "Value: %1%" ).arg( new_val ) );
}

void SongEditor::hideMasterVolumeFloat( void )
{
	m_mvsStatus->hide();
}

void SongEditor::setMasterPitch( int new_val )
{
	updateMasterPitchFloat( new_val );
	if( m_mpsStatus->isVisible() == false && m_song->m_loadingProject == false
					&& m_masterPitchSlider->showStatus() )
	{
		m_mpsStatus->moveGlobal( m_masterPitchSlider,
			QPoint( m_masterPitchSlider->width() + 2, -2 ) );
		m_mpsStatus->setVisibilityTimeOut( 1000 );
	}
}

void SongEditor::showMasterPitchFloat( void )
{
	m_mpsStatus->moveGlobal( m_masterPitchSlider,
			QPoint( m_masterPitchSlider->width() + 2, -2 ) );
	m_mpsStatus->show();
	updateMasterPitchFloat( m_song->m_masterPitchModel.value() );
}

void SongEditor::updateMasterPitchFloat( int new_val )
{
	m_mpsStatus->setText( tr( "Value: %1 semitones").arg( new_val ) );

}

void SongEditor::hideMasterPitchFloat( void )
{
	m_mpsStatus->hide();
}

void SongEditor::updateScrollBar( int len )
{
	m_leftRightScroll->setMaximum( len );
}

static inline void animateScroll( QScrollBar *scrollBar, int newVal, bool smoothScroll )
{
	if( smoothScroll == false )
	{
		scrollBar->setValue( newVal );
	}
	else
	{
		// do smooth scroll animation using QTimeLine
		QTimeLine *t = scrollBar->findChild<QTimeLine *>();
		if( t == NULL )
		{
			t = new QTimeLine( 600, scrollBar );
			t->setFrameRange( scrollBar->value(), newVal );
			t->connect( t, SIGNAL( finished() ), SLOT( deleteLater() ) );

			scrollBar->connect( t, SIGNAL( frameChanged( int ) ), SLOT( setValue( int ) ) );

			t->start();
		}
		else
		{
			// smooth scrolling is still active, therefore just update the end frame
			t->setEndFrame( newVal );
		}
	}
}

void SongEditor::updatePosition( const TimePos & t )
{
	int widgetWidth, trackOpWidth;
	if( ConfigManager::inst()->value( "ui", "compacttrackbuttons" ).toInt() )
	{
		widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH_COMPACT;
		trackOpWidth = TRACK_OP_WIDTH_COMPACT;
	}
	else
	{
		widgetWidth = DEFAULT_SETTINGS_WIDGET_WIDTH;
		trackOpWidth = TRACK_OP_WIDTH;
	}

	if( ( m_song->isPlaying() && m_song->m_playMode == Song::Mode_PlaySong
		  && m_timeLine->autoScroll() == TimeLineWidget::AutoScrollEnabled) ||
							m_scrollBack == true )
	{
		m_smoothScroll = ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt();
		const int w = width() - widgetWidth
							- trackOpWidth
							- contentWidget()->verticalScrollBar()->width(); // width of right scrollbar
		if( t > m_currentPosition + w * TimePos::ticksPerBar() /
							pixelsPerBar() )
		{
			animateScroll( m_leftRightScroll, t.getBar(), m_smoothScroll );
		}
		else if( t < m_currentPosition )
		{
			animateScroll( m_leftRightScroll, t.getBar(), m_smoothScroll );
		}
		m_scrollBack = false;
	}

	const int x = m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->
							markerX( t ) + 8;
	if( x >= trackOpWidth + widgetWidth -1 )
	{
		m_positionLine->show();
		m_positionLine->move( x-( m_positionLine->width() - 1 ), m_timeLine->height() );
	}
	else
	{
		m_positionLine->hide();
	}

	m_positionLine->setFixedHeight( height() );
}

void SongEditor::updatePositionLine()
{
	m_positionLine->setFixedHeight( height() );
}

void SongEditor::zoomingChanged()
{
	setPixelsPerBar( m_zoomLevels[m_zoomingModel->value()] * DEFAULT_PIXELS_PER_BAR );

	m_song->m_playPos[Song::Mode_PlaySong].m_timeLine->
					setPixelsPerBar( pixelsPerBar() );
	realignTracks();
	updateRubberband();

	emit zoomingValueChanged( m_zoomLevels[m_zoomingModel->value()] );
}

void SongEditor::selectAllTcos( bool select )
{
	QVector<selectableObject *> so = select ? rubberBand()->selectableObjects() : rubberBand()->selectedObjects();
	for( int i = 0; i < so.count(); ++i )
	{
		so.at(i)->setSelected( select );
	}
}

bool SongEditor::allowRubberband() const
{
	return m_mode == SelectMode;
}

int SongEditor::trackIndexFromSelectionPoint(int yPos)
{
	const TrackView * tv = trackViewAt(yPos - m_timeLine->height());
	return tv ? indexOfTrackView(tv)
			  : yPos < m_timeLine->height() ? 0
											: trackViews().count();
}

int SongEditor::indexOfTrackView(const TrackView *tv)
{
	return static_cast<int>(std::distance(trackViews().begin(),
										  std::find(trackViews().begin(), trackViews().end(), tv)));
}

ComboBoxModel *SongEditor::zoomingModel() const
{
	return m_zoomingModel;
}

ComboBoxModel *SongEditor::snappingModel() const
{
	return m_snappingModel;
}

SongEditorWindow::SongEditorWindow(Song* song) :
	Editor(Engine::mixer()->audioDev()->supportsCapture(), false),
	m_editor(new SongEditor(song)),
	m_crtlAction( NULL ),
	m_snapSizeLabel( new QLabel( m_toolBar ) )
{
	setWindowTitle( tr( "Song-Editor" ) );
	setWindowIcon( embed::getIconPixmap( "songeditor" ) );

	setCentralWidget(m_editor);
	setAcceptDrops(true);
	m_toolBar->setAcceptDrops(true);
	connect(m_toolBar, SIGNAL(dragEntered(QDragEnterEvent*)), m_editor, SLOT(dragEnterEvent(QDragEnterEvent*)));
	connect(m_toolBar, SIGNAL(dropped(QDropEvent*)), m_editor, SLOT(dropEvent(QDropEvent*)));

	// Set up buttons
	m_playAction->setToolTip(tr("Play song (Space)"));
	m_recordAction->setToolTip(tr("Record samples from Audio-device"));
	m_recordAccompanyAction->setToolTip(tr( "Record samples from Audio-device while playing song or BB track"));
	m_stopAction->setToolTip(tr( "Stop song (Space)" ));

	// Track actions
	DropToolBar *trackActionsToolBar = addDropToolBarToTop(tr("Track actions"));

	m_addBBTrackAction = new QAction(embed::getIconPixmap("add_bb_track"),
									 tr("Add beat/bassline"), this);

	m_addSampleTrackAction = new QAction(embed::getIconPixmap("add_sample_track"),
										 tr("Add sample-track"), this);

	m_addAutomationTrackAction = new QAction(embed::getIconPixmap("add_automation"),
											 tr("Add automation-track"), this);

	connect(m_addBBTrackAction, SIGNAL(triggered()), m_editor->m_song, SLOT(addBBTrack()));
	connect(m_addSampleTrackAction, SIGNAL(triggered()), m_editor->m_song, SLOT(addSampleTrack()));
	connect(m_addAutomationTrackAction, SIGNAL(triggered()), m_editor->m_song, SLOT(addAutomationTrack()));

	trackActionsToolBar->addAction( m_addBBTrackAction );
	trackActionsToolBar->addAction( m_addSampleTrackAction );
	trackActionsToolBar->addAction( m_addAutomationTrackAction );

	// Edit actions
	DropToolBar *editActionsToolBar = addDropToolBarToTop(tr("Edit actions"));

	m_editModeGroup = new ActionGroup(this);
	m_drawModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_draw"), tr("Draw mode"));
	m_selectModeAction = m_editModeGroup->addAction(embed::getIconPixmap("edit_select"), tr("Edit mode (select and move)"));
	m_drawModeAction->setChecked(true);

	connect(m_drawModeAction, SIGNAL(triggered()), m_editor, SLOT(setEditModeDraw()));
	connect(m_selectModeAction, SIGNAL(triggered()), m_editor, SLOT(setEditModeSelect()));

	editActionsToolBar->addAction( m_drawModeAction );
	editActionsToolBar->addAction( m_selectModeAction );

	DropToolBar *timeLineToolBar = addDropToolBarToTop(tr("Timeline controls"));
	m_editor->m_timeLine->addToolButtons(timeLineToolBar);

	DropToolBar *insertActionsToolBar = addDropToolBarToTop(tr("Bar insert controls"));
	m_insertBarAction = new QAction(embed::getIconPixmap("insert_bar"), tr("Insert bar"), this);
	m_removeBarAction = new QAction(embed::getIconPixmap("remove_bar"), tr("Remove bar"), this);
	insertActionsToolBar->addAction( m_insertBarAction );
	insertActionsToolBar->addAction( m_removeBarAction );
	connect(m_insertBarAction, SIGNAL(triggered()), song, SLOT(insertBar()));
	connect(m_removeBarAction, SIGNAL(triggered()), song, SLOT(removeBar()));

	DropToolBar *zoomToolBar = addDropToolBarToTop(tr("Zoom controls"));

	QLabel * zoom_lbl = new QLabel( m_toolBar );
	zoom_lbl->setPixmap( embed::getIconPixmap( "zoom" ) );

	//Set up zooming-stuff
	m_zoomingComboBox = new ComboBox( m_toolBar );
	m_zoomingComboBox->setFixedSize( 80, ComboBox::DEFAULT_HEIGHT );
	m_zoomingComboBox->move( 580, 4 );
	m_zoomingComboBox->setModel(m_editor->m_zoomingModel);
	m_zoomingComboBox->setToolTip(tr("Horizontal zooming"));
	connect(m_editor->zoomingModel(), SIGNAL(dataChanged()), this, SLOT(updateSnapLabel()));

	zoomToolBar->addWidget( zoom_lbl );
	zoomToolBar->addWidget( m_zoomingComboBox );

	DropToolBar *snapToolBar = addDropToolBarToTop(tr("Snap controls"));
	QLabel * snap_lbl = new QLabel( m_toolBar );
	snap_lbl->setPixmap( embed::getIconPixmap( "quantize" ) );

	//Set up quantization/snapping selector
	m_snappingComboBox = new ComboBox( m_toolBar );
	m_snappingComboBox->setFixedSize( 80, ComboBox::DEFAULT_HEIGHT );
	m_snappingComboBox->setModel(m_editor->m_snappingModel);
	m_snappingComboBox->setToolTip(tr("Clip snapping size"));
	connect(m_editor->snappingModel(), SIGNAL(dataChanged()), this, SLOT(updateSnapLabel()));

	m_setProportionalSnapAction = new QAction(embed::getIconPixmap("proportional_snap"),
											 tr("Toggle proportional snap on/off"), this);
	m_setProportionalSnapAction->setCheckable(true);
	m_setProportionalSnapAction->setChecked(false);
	connect(m_setProportionalSnapAction, SIGNAL(triggered()), m_editor, SLOT(toggleProportionalSnap()));
	connect(m_setProportionalSnapAction, SIGNAL(triggered()), this, SLOT(updateSnapLabel()) );

	snapToolBar->addWidget( snap_lbl );
	snapToolBar->addWidget( m_snappingComboBox );
	snapToolBar->addSeparator();
	snapToolBar->addAction( m_setProportionalSnapAction );

	snapToolBar->addSeparator();
	snapToolBar->addWidget( m_snapSizeLabel );

	connect(song, SIGNAL(projectLoaded()), this, SLOT(adjustUiAfterProjectLoad()));
	connect(this, SIGNAL(resized()), m_editor, SLOT(updatePositionLine()));
}

QSize SongEditorWindow::sizeHint() const
{
	return {720, 300};
}

void SongEditorWindow::updateSnapLabel(){
	if (m_setProportionalSnapAction->isChecked())
	{
		m_snapSizeLabel->setText(QString("Snap: ") + m_editor->getSnapSizeString());
		m_snappingComboBox->setToolTip(tr("Base snapping size"));
	}
	else
	{
		m_snappingComboBox->setToolTip(tr("Clip snapping size"));
		m_snapSizeLabel->clear();
	}
}

void SongEditorWindow::resizeEvent(QResizeEvent *event)
{
	emit resized();
}

void SongEditorWindow::changeEvent(QEvent *event)
{
	QWidget::changeEvent(event);
	if (event->type() == QEvent::WindowStateChange)
	{
		m_editor->realignTracks();
	}
}

void SongEditorWindow::play()
{
	emit playTriggered();
	if( Engine::getSong()->playMode() != Song::Mode_PlaySong )
	{
		Engine::getSong()->playSong();
	}
	else
	{
		Engine::getSong()->togglePause();
	}
}

void SongEditorWindow::record()
{
	m_editor->m_song->record();
}

void SongEditorWindow::recordAccompany()
{
	m_editor->m_song->playAndRecord();
}

void SongEditorWindow::stop()
{
	m_editor->m_song->stop();
	gui->pianoRoll()->stopRecording();
}

void SongEditorWindow::lostFocus()
{
	if( m_crtlAction )
	{
		m_crtlAction->setChecked( true );
		m_crtlAction->trigger();
	}
}

void SongEditorWindow::adjustUiAfterProjectLoad()
{
	// make sure to bring us to front as the song editor is the central
	// widget in a song and when just opening a song in order to listen to
	// it, it's very annyoing to manually bring up the song editor each time
	gui->mainWindow()->workspace()->setActiveSubWindow(
			qobject_cast<QMdiSubWindow *>( parentWidget() ) );
	connect( qobject_cast<SubWindow *>( parentWidget() ), SIGNAL( focusLost() ), this, SLOT( lostFocus() ) );
	m_editor->scrolled(0);
}

void SongEditorWindow::keyPressEvent( QKeyEvent *ke )
{
	if( ke->key() == Qt::Key_Control )
	{
		m_crtlAction = m_editModeGroup->checkedAction();
		m_selectModeAction->setChecked( true );
		m_selectModeAction->trigger();
	}
}

void SongEditorWindow::keyReleaseEvent( QKeyEvent *ke )
{
	if( ke->key() == Qt::Key_Control )
	{
		if( m_crtlAction )
		{
			m_crtlAction->setChecked( true );
			m_crtlAction->trigger();
		}
	}
}

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * embed.cpp - misc stuff for using embedded resources (linked into binary)
 *
 * Copyright (c) 2004-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QDebug>
#include <QImageReader>
#include <QPixmapCache>
#include <QResource>
#include "embed.h"

namespace embed
{

QPixmap getIconPixmap(const QString& pixmapName,
	int width, int height, const char** xpm )
{
	QString cacheName;
	if (width > 0 && height > 0)
	{
		cacheName = QString("%1_%2_%3").arg(pixmapName, width, height);
	}
	else
	{
		cacheName = pixmapName;
	}

	// Return cached pixmap
	QPixmap pixmap;
	if( QPixmapCache::find(cacheName, &pixmap) )
	{
		return pixmap;
	}

	if(xpm)
	{
		pixmap = QPixmap(xpm);
	}
	else
	{
		QImageReader reader(QString("artwork:%1").arg(pixmapName));

		if (width > 0 && height > 0)
		{
			reader.setScaledSize(QSize(width, height));
		}

		pixmap = QPixmap::fromImageReader(&reader);

		if (pixmap.isNull())
		{
			qWarning().nospace() << "Error loading icon pixmap " << pixmapName << ": " <<
									reader.errorString().toLocal8Bit().data();
			return QPixmap(1,1);
		}
	}

	// Save to cache and return
	QPixmapCache::insert(cacheName, pixmap);
	return pixmap;
}

QString getText( const char * name )
{
	return QString::fromUtf8( (const char*) QResource(QString(":/%1").arg(name)).data());
}

}


code:
#include "RecentProjectsMenu.h"

#include <QFileInfo>

#include "ConfigManager.h"
#include "Engine.h"
#include "Song.h"

#include "embed.h"
#include "GuiApplication.h"
#include "MainWindow.h"

RecentProjectsMenu::RecentProjectsMenu(QWidget *parent) :
	QMenu(tr( "&Recently Opened Projects" ), parent)
{
	setIcon(embed::getIconPixmap( "project_open_recent" ));

	connect( this, SIGNAL( aboutToShow() ),
			 this, SLOT(fillMenu() ) );
	connect( this, SIGNAL( triggered( QAction * ) ),
			 this, SLOT(openProject(QAction * ) ) );
}

void RecentProjectsMenu::fillMenu()
{
	clear();
	QStringList rup = ConfigManager::inst()->recentlyOpenedProjects();

	auto projectFileIcon = embed::getIconPixmap( "project_file" );

	//	The file history goes 50 deep but we only show the 15
	//	most recent ones that we can open and omit .mpt files.
	int shownInMenu = 0;
	for(QString& fileName : rup)
	{
		QFileInfo recentFile(fileName);
		if (!recentFile.exists() ||
			fileName == ConfigManager::inst()->recoveryFile() )
		{
			continue;
		}

		if( recentFile.suffix().toLower() == "mpt" )
		{
			continue;
		}

		addAction(projectFileIcon, fileName.replace("&", "&&") );
#ifdef LMMS_BUILD_APPLE
		actions().last()->setIconVisibleInMenu(false); // QTBUG-44565 workaround
		actions().last()->setIconVisibleInMenu(true);
#endif

		shownInMenu++;
		if( shownInMenu >= 15 )
		{
			break;
		}
	}
}

void RecentProjectsMenu::openProject(QAction * _action )
{
	auto mainWindow = gui->mainWindow();
	if (mainWindow->mayChangeProject(true))
	{
		const QString f = _action->text().replace("&&", "&");
		mainWindow->setCursor( Qt::WaitCursor );
		Engine::getSong()->loadProject( f );
		mainWindow->setCursor( Qt::ArrowCursor );
	}
}


code:
#include "TemplatesMenu.h"

#include "ConfigManager.h"
#include "Engine.h"
#include "Song.h"

#include "embed.h"
#include "GuiApplication.h"
#include "MainWindow.h"

TemplatesMenu::TemplatesMenu(QWidget *parent) :
	QMenu(tr("New from template"), parent)
{
	setIcon(embed::getIconPixmap("project_new"));

	connect( this, SIGNAL( aboutToShow() ), SLOT( fillTemplatesMenu() ) );
	connect( this, SIGNAL( triggered( QAction * ) ),
		SLOT( createNewProjectFromTemplate( QAction * ) ) );
}

void TemplatesMenu::createNewProjectFromTemplate(QAction * _action)
{
	if( gui->mainWindow()->mayChangeProject(true) )
	{
		const QString& templateFilePath = _action->data().toString();
		Engine::getSong()->createNewProjectFromTemplate(templateFilePath);
	}
}

void TemplatesMenu::fillTemplatesMenu()
{
	clear();

	addTemplatesFromDir(ConfigManager::inst()->userTemplateDir());
	addTemplatesFromDir(ConfigManager::inst()->factoryProjectsDir() + "templates");
}

void TemplatesMenu::addTemplatesFromDir( const QDir& dir ) {
	QFileInfoList templates = dir.entryInfoList( QStringList( "*.mpt" ),
		QDir::Files | QDir::Readable );

	if (!templates.empty() && !actions().isEmpty())
	{
		addSeparator();
	}

	auto projectFileIcon = embed::getIconPixmap( "project_file" );

	for(const QFileInfo& templateFile : templates)
	{
		auto action = addAction(projectFileIcon,
			templateFile.completeBaseName().replace("&", "&&"));
		action->setData(templateFile.absoluteFilePath());
#ifdef LMMS_BUILD_APPLE
		action->setIconVisibleInMenu(false); // QTBUG-44565 workaround
		action->setIconVisibleInMenu(true);
#endif
	}
}


code:
/*
 * AutomatableButton.cpp - implementation of class automatableButton and
 *                          automatableButtonGroup
 *
 * Copyright (c) 2006-2011 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AutomatableButton.h"

#include <QMouseEvent>

#include "CaptionMenu.h"
#include "MainWindow.h"
#include "StringPairDrag.h"

AutomatableButton::AutomatableButton( QWidget * _parent,
						const QString & _name ) :
	QPushButton( _parent ),
	BoolModelView( new BoolModel( false, NULL, _name, true ), this ),
	m_group( NULL )
{
	setWindowTitle( _name );
	doConnections();
	setFocusPolicy( Qt::NoFocus );
}

AutomatableButton::~AutomatableButton()
{
	if( m_group != NULL )
	{
		m_group->removeButton( this );
	}
}

void AutomatableButton::modelChanged()
{
	if( QPushButton::isChecked() != model()->value() )
	{
		QPushButton::setChecked( model()->value() );
	}
}

void AutomatableButton::update()
{
	if( QPushButton::isChecked() != model()->value() )
	{
		QPushButton::setChecked( model()->value() );
	}
	QPushButton::update();
}

void AutomatableButton::contextMenuEvent( QContextMenuEvent * _me )
{
	// for the case, the user clicked right while pressing left mouse-
	// button, the context-menu appears while mouse-cursor is still hidden
	// and it isn't shown again until user does something which causes
	// an QApplication::restoreOverrideCursor()-call...
	mouseReleaseEvent( NULL );

	if ( m_group != NULL )
	{
		CaptionMenu contextMenu( m_group->model()->displayName() );
		m_group->addDefaultActions( &contextMenu );
		contextMenu.exec( QCursor::pos() );
	}
	else
	{
		CaptionMenu contextMenu( model()->displayName() );
		addDefaultActions( &contextMenu );
		contextMenu.exec( QCursor::pos() );
	}

}

void AutomatableButton::mousePressEvent( QMouseEvent * _me )
{
	if( _me->button() == Qt::LeftButton &&
			! ( _me->modifiers() & Qt::ControlModifier ) )
	{
        // User simply clicked, toggle if needed
		if( isCheckable() )
		{
			toggle();
		}
		_me->accept();
	}
	else
	{
		// Ctrl-clicked, need to prepare drag-drop
		if( m_group )
		{
			// A group, we must get process it instead
			AutomatableModelView* groupView = (AutomatableModelView*)m_group;
			new StringPairDrag( "automatable_model",
					QString::number( groupView->modelUntyped()->id() ),
					QPixmap(), widget() );
			// TODO: ^^ Maybe use a predefined icon instead of the button they happened to select
			_me->accept();
		}
		else
		{
			// Otherwise, drag the standalone button
			AutomatableModelView::mousePressEvent( _me );
			QPushButton::mousePressEvent( _me );
		}
	}
}

void AutomatableButton::mouseReleaseEvent( QMouseEvent * _me )
{
	if( _me && _me->button() == Qt::LeftButton )
	{
		emit clicked();
	}
}

void AutomatableButton::toggle()
{
	if( isCheckable() && m_group != NULL )
	{
		if( model()->value() == false )
		{
			m_group->activateButton( this );
		}
	}
	else
	{
		model()->setValue( !model()->value() );
	}
}

automatableButtonGroup::automatableButtonGroup( QWidget * _parent,
						const QString & _name ) :
	QWidget( _parent ),
	IntModelView( new IntModel( 0, 0, 0, NULL, _name, true ), this )
{
	hide();
	setWindowTitle( _name );
}

automatableButtonGroup::~automatableButtonGroup()
{
	for( QList<AutomatableButton *>::iterator it = m_buttons.begin();
					it != m_buttons.end(); ++it )
	{
		( *it )->m_group = NULL;
	}
}

void automatableButtonGroup::addButton( AutomatableButton * _btn )
{
	_btn->m_group = this;
	_btn->setCheckable( true );
	_btn->model()->setValue( false );
	// disable journalling as we're recording changes of states of
	// button-group members on our own
	_btn->model()->setJournalling( false );

	m_buttons.push_back( _btn );
	model()->setRange( 0, m_buttons.size() - 1 );
	updateButtons();
}

void automatableButtonGroup::removeButton( AutomatableButton * _btn )
{
	m_buttons.erase( std::find( m_buttons.begin(), m_buttons.end(), _btn ) );
	_btn->m_group = NULL;
}

void automatableButtonGroup::activateButton( AutomatableButton * _btn )
{
	if( _btn != m_buttons[model()->value()] &&
					m_buttons.indexOf( _btn ) != -1 )
	{
		model()->setValue( m_buttons.indexOf( _btn ) );
		for( AutomatableButton * btn : m_buttons )
		{
			btn->update();
		}
	}
}

void automatableButtonGroup::modelChanged()
{
	connect( model(), SIGNAL( dataChanged() ),
			this, SLOT( updateButtons() ) );
	IntModelView::modelChanged();
	updateButtons();
}

void automatableButtonGroup::updateButtons()
{
	model()->setRange( 0, m_buttons.size() - 1 );
	int i = 0;
	for( AutomatableButton * btn : m_buttons )
	{
		btn->model()->setValue( i == model()->value() );
		++i;
	}
}


code:
/*
 * AutomatableSlider.cpp - implementation of class AutomatableSlider
 *
 * Copyright (c) 2006-2007 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
 * Copyright (c) 2007-2009 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "AutomatableSlider.h"

#include <QMouseEvent>

#include "CaptionMenu.h"
#include "MainWindow.h"

AutomatableSlider::AutomatableSlider( QWidget * _parent,
						const QString & _name ) :
	QSlider( _parent ),
	IntModelView( new IntModel( 0, 0, 0, NULL, _name, true ), this ),
	m_showStatus( false )
{
	setWindowTitle( _name );

	connect( this, SIGNAL( valueChanged( int ) ),
					this, SLOT( changeValue( int ) ) );
	connect( this, SIGNAL( sliderMoved( int ) ),
					this, SLOT( moveSlider( int ) ) );
}

AutomatableSlider::~AutomatableSlider()
{
}

void AutomatableSlider::contextMenuEvent( QContextMenuEvent * _me )
{
	CaptionMenu contextMenu( model()->displayName() );
	addDefaultActions( &contextMenu );
	contextMenu.exec( QCursor::pos() );
}

void AutomatableSlider::mousePressEvent( QMouseEvent * _me )
{
	if( _me->button() == Qt::LeftButton &&
	   ! ( _me->modifiers() & Qt::ControlModifier ) )
	{
		m_showStatus = true;
		QSlider::mousePressEvent( _me );
	}
	else
	{
		IntModelView::mousePressEvent( _me );
	}
}

void AutomatableSlider::mouseReleaseEvent( QMouseEvent * _me )
{
	m_showStatus = false;
	QSlider::mouseReleaseEvent( _me );
}

void AutomatableSlider::wheelEvent( QWheelEvent * _me )
{
	bool old_status = m_showStatus;
	m_showStatus = true;
	QSlider::wheelEvent( _me );
	m_showStatus = old_status;
}

void AutomatableSlider::modelChanged()
{
	QSlider::setRange( model()->minValue(), model()->maxValue() );
	updateSlider();
	connect( model(), SIGNAL( dataChanged() ),
				this, SLOT( updateSlider() ) );
}

void AutomatableSlider::changeValue( int _value )
{
	model()->setValue( _value );
	emit logicValueChanged( model()->value() );
}

void AutomatableSlider::moveSlider( int _value )
{
	model()->setValue( _value );
	emit logicSliderMoved( model()->value() );
}

void AutomatableSlider::updateSlider()
{
	QSlider::setValue( model()->value() );
}


code:
/*
 * CPULoadWidget.cpp - widget for displaying CPU-load (partly based on
 *                      Hydrogen's CPU-load-widget)
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QPainter>

#include "CPULoadWidget.h"
#include "embed.h"
#include "Engine.h"
#include "Mixer.h"

CPULoadWidget::CPULoadWidget( QWidget * _parent ) :
	QWidget( _parent ),
	m_currentLoad( 0 ),
	m_temp(),
	m_background( embed::getIconPixmap( "cpuload_bg" ) ),
	m_leds( embed::getIconPixmap( "cpuload_leds" ) ),
	m_changed( true ),
	m_updateTimer()
{
	setAttribute( Qt::WA_OpaquePaintEvent, true );
	setFixedSize( m_background.width(), m_background.height() );

	m_temp = QPixmap( width(), height() );

	connect( &m_updateTimer, SIGNAL( timeout() ),
					this, SLOT( updateCpuLoad() ) );
	m_updateTimer.start( 100 );	// update cpu-load at 10 fps
}

CPULoadWidget::~CPULoadWidget()
{
}

void CPULoadWidget::paintEvent( QPaintEvent *  )
{
	if( m_changed == true )
	{
		m_changed = false;

		m_temp.fill( QColor(0,0,0,0) );
		QPainter p( &m_temp );
		p.drawPixmap( 0, 0, m_background );

		// as load-indicator consists of small 2-pixel wide leds with
		// 1 pixel spacing, we have to make sure, only whole leds are
		// shown which we achieve by the following formula
		int w = ( m_leds.width() * m_currentLoad / 300 ) * 3;
		if( w > 0 )
		{
			p.drawPixmap( 23, 3, m_leds, 0, 0, w,
							m_leds.height() );
		}
	}
	QPainter p( this );
	p.drawPixmap( 0, 0, m_temp );
}

void CPULoadWidget::updateCpuLoad()
{
	// smooth load-values a bit
	int new_load = ( m_currentLoad + Engine::mixer()->cpuLoad() ) / 2;
	if( new_load != m_currentLoad )
	{
		m_currentLoad = new_load;
		m_changed = true;
		update();
	}
}


code:
/*
 * CaptionMenu.cpp - context menu with a caption
 *
 * Copyright (c) 2007 Javier Serrano Polo <jasp00/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "CaptionMenu.h"
#include "embed.h"

CaptionMenu::CaptionMenu( const QString & _title, QWidget * _parent ) :
	QMenu( _title, _parent )
{
	QAction * caption = addAction( _title );
	caption->setEnabled( false );
}

CaptionMenu::~CaptionMenu()
{
}


code:
/*
 * ComboBox.cpp - implementation of LMMS combobox
 *
 * Copyright (c) 2006-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 * Copyright (c) 2008-2009 Paul Giblock <pgib/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "ComboBox.h"

#include <QApplication>
#include <QDesktopWidget>
#include <QMouseEvent>
#include <QPainter>
#include <QStyleOptionFrame>

#include "CaptionMenu.h"
#include "embed.h"
#include "gui_templates.h"
#include "MainWindow.h"

QPixmap * ComboBox::s_background = NULL;
QPixmap * ComboBox::s_arrow = NULL;
QPixmap * ComboBox::s_arrowSelected = NULL;

const int CB_ARROW_BTN_WIDTH = 18;

ComboBox::ComboBox( QWidget * _parent, const QString & _name ) :
	QWidget( _parent ),
	IntModelView( new ComboBoxModel( NULL, QString(), true ), this ),
	m_menu( this ),
	m_pressed( false )
{
	setFixedHeight( ComboBox::DEFAULT_HEIGHT );

	if( s_background == NULL )
	{
		s_background = new QPixmap( embed::getIconPixmap( "combobox_bg" ) );
	}

	if( s_arrow == NULL )
	{
		s_arrow = new QPixmap( embed::getIconPixmap( "combobox_arrow" ) );
	}

	if( s_arrowSelected == NULL )
	{
		s_arrowSelected = new QPixmap( embed::getIconPixmap( "combobox_arrow_selected" ) );
	}

	setFont( pointSize<9>( font() ) );
	m_menu.setFont( pointSize<8>( m_menu.font() ) );

	connect( &m_menu, SIGNAL( triggered( QAction * ) ),
				this, SLOT( setItem( QAction * ) ) );

	setWindowTitle( _name );
	doConnections();
}

ComboBox::~ComboBox()
{
}

void ComboBox::selectNext()
{
	model()->setInitValue( model()->value() + 1 );
}

void ComboBox::selectPrevious()
{
	model()->setInitValue( model()->value() - 1 );
}

void ComboBox::contextMenuEvent( QContextMenuEvent * event )
{
	if( model() == NULL || event->x() <= width() - CB_ARROW_BTN_WIDTH )
	{
		QWidget::contextMenuEvent( event );
		return;
	}

	CaptionMenu contextMenu( model()->displayName() );
	addDefaultActions( &contextMenu );
	contextMenu.exec( QCursor::pos() );
}

void ComboBox::mousePressEvent( QMouseEvent* event )
{
	if( model() == NULL )
	{
		return;
	}

	if( event->button() == Qt::LeftButton && ! ( event->modifiers() & Qt::ControlModifier ) )
	{
		if( event->x() > width() - CB_ARROW_BTN_WIDTH )
		{
			m_pressed = true;
			update();

			m_menu.clear();
			for( int i = 0; i < model()->size(); ++i )
			{
				QAction * a = m_menu.addAction( model()->itemPixmap( i ) ? model()->itemPixmap( i )->pixmap() : QPixmap(),
													model()->itemText( i ) );
				a->setData( i );
			}

			QPoint gpos = mapToGlobal( QPoint( 0, height() ) );
			if( gpos.y() + m_menu.sizeHint().height() < qApp->desktop()->height() )
			{
				m_menu.exec( gpos );
			}
			else
			{
				m_menu.exec( mapToGlobal( QPoint( width(), 0 ) ) );
			}
			m_pressed = false;
			update();
		}
		else if( event->button() == Qt::LeftButton )
		{
			selectNext();
			update();
		}
	}
	else if( event->button() == Qt::RightButton )
	{
		selectPrevious();
		update();
	}
	else
	{
		IntModelView::mousePressEvent( event );
	}
}

void ComboBox::paintEvent( QPaintEvent * _pe )
{
	QPainter p( this );

	p.fillRect( 2, 2, width()-2, height()-4, *s_background );

	QColor shadow = palette().shadow().color();
	QColor highlight = palette().highlight().color();

	shadow.setAlpha( 124 );
	highlight.setAlpha( 124 );

	// button-separator
	p.setPen( shadow );
	p.drawLine( width() - CB_ARROW_BTN_WIDTH - 1, 1, width() - CB_ARROW_BTN_WIDTH - 1, height() - 3 );

	p.setPen( highlight );
	p.drawLine( width() - CB_ARROW_BTN_WIDTH, 1, width() - CB_ARROW_BTN_WIDTH, height() - 3 );

	// Border
	QStyleOptionFrame opt;
	opt.initFrom( this );
	opt.state = QStyle::StateFlag::State_None;

	style()->drawPrimitive( QStyle::PE_Frame, &opt, &p, this );

	QPixmap * arrow = m_pressed ? s_arrowSelected : s_arrow;

	p.drawPixmap( width() - CB_ARROW_BTN_WIDTH + 3, 4, *arrow );

	if( model() && model()->size() > 0 )
	{
		p.setFont( font() );
		p.setClipRect( QRect( 4, 2, width() - CB_ARROW_BTN_WIDTH - 8, height() - 2 ) );
		QPixmap pm = model()->currentData() ?  model()->currentData()->pixmap() : QPixmap();
		int tx = 5;
		if( !pm.isNull() )
		{
			if( pm.height() > 16 )
			{
				pm = pm.scaledToHeight( 16, Qt::SmoothTransformation );
			}
			p.drawPixmap( tx, 3, pm );
			tx += pm.width() + 3;
		}
		const int y = ( height()+p.fontMetrics().height() ) /2;
		p.setPen( QColor( 64, 64, 64 ) );
		p.drawText( tx+1, y-3, model()->currentText() );
		p.setPen( QColor( 224, 224, 224 ) );
		p.drawText( tx, y-4, model()->currentText() );
	}
}

void ComboBox::wheelEvent( QWheelEvent* event )
{
	if( model() )
	{
		model()->setInitValue(model()->value() + ((event->angleDelta().y() < 0) ? 1 : -1));
		update();
		event->accept();
	}
}

void ComboBox::setItem( QAction* item )
{
	if( model() )
	{
		model()->setInitValue( item->data().toInt() );
	}
}


code:
/*
 * ControlLayout.cpp - implementation for ControlLayout.h
 *
 * Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "ControlLayout.h"

#include "stdshims.h"

#include <QWidget>
#include <QLayoutItem>
#include <QLineEdit>
#include <QRect>
#include <QString>

constexpr const int ControlLayout::m_minWidth;

ControlLayout::ControlLayout(QWidget *parent, int margin, int hSpacing, int vSpacing)
	: QLayout(parent), m_hSpace(hSpacing), m_vSpace(vSpacing),
	  m_searchBar(new QLineEdit(parent))
{
	setContentsMargins(margin, margin, margin, margin);
	m_searchBar->setPlaceholderText("filter");
	m_searchBar->setObjectName(s_searchBarName);
	connect(m_searchBar, SIGNAL(textChanged(const QString&)),
		this, SLOT(onTextChanged(const QString& )));
	addWidget(m_searchBar);
	m_searchBar->setHidden(true); // nothing to filter yet
}

ControlLayout::~ControlLayout()
{
	QLayoutItem *item;
	while ((item = takeAt(0))) { delete item; }
}

void ControlLayout::onTextChanged(const QString&)
{
	invalidate();
	update();
}

void ControlLayout::addItem(QLayoutItem *item)
{
	QWidget* widget = item->widget();
	const QString str = widget ? widget->objectName() : QString("unnamed");
	m_itemMap.insert(str, item);
	invalidate();
}

int ControlLayout::horizontalSpacing() const
{
	if (m_hSpace >= 0) { return m_hSpace; }
	else
	{
		return smartSpacing(QStyle::PM_LayoutHorizontalSpacing);
	}
}

int ControlLayout::verticalSpacing() const
{
	if (m_vSpace >= 0) { return m_vSpace; }
	else
	{
		return smartSpacing(QStyle::PM_LayoutVerticalSpacing);
	}
}

int ControlLayout::count() const
{
	return m_itemMap.size() - 1;
}

QMap<QString, QLayoutItem*>::const_iterator
ControlLayout::pairAt(int index) const
{
	if (index < 0) { return m_itemMap.cend(); }

	auto skip = [&](QLayoutItem* item) -> bool
	{
		return item->widget()->objectName() == s_searchBarName;
	};

	QMap<QString, QLayoutItem*>::const_iterator itr = m_itemMap.cbegin();
	for (; itr != m_itemMap.cend() && (index > 0 || skip(itr.value())); ++itr)
	{
		if(!skip(itr.value())) { index--; }
	}
	return itr;
}

// linear time :-(
QLayoutItem *ControlLayout::itemAt(int index) const
{
	auto itr = pairAt(index);
	return (itr == m_itemMap.end()) ? nullptr : itr.value();
}

QLayoutItem *ControlLayout::itemByString(const QString &key) const
{
	auto itr = m_itemMap.find(key);
	return (itr == m_itemMap.end()) ? nullptr : *itr;
}

// linear time :-(
QLayoutItem *ControlLayout::takeAt(int index)
{
	auto itr = pairAt(index);
	return (itr == m_itemMap.end()) ? nullptr : m_itemMap.take(itr.key());
}

void ControlLayout::removeFocusFromSearchBar()
{
	m_searchBar->clearFocus();
}

Qt::Orientations ControlLayout::expandingDirections() const
{
	return Qt::Orientations();
}

bool ControlLayout::hasHeightForWidth() const
{
	return true;
}

int ControlLayout::heightForWidth(int width) const
{
	int height = doLayout(QRect(0, 0, width, 0), true);
	return height;
}

void ControlLayout::setGeometry(const QRect &rect)
{
	QLayout::setGeometry(rect);
	doLayout(rect, false);
}

QSize ControlLayout::sizeHint() const
{
	return minimumSize();
}

QSize ControlLayout::minimumSize() const
{
	// original formula from Qt's FlowLayout example:
	// get maximum height and width for all children.
	// as Qt will later call heightForWidth, only the width here really matters
	QSize size;
	for (const QLayoutItem *item : as_const(m_itemMap))
	{
		size = size.expandedTo(item->minimumSize());
	}
	const QMargins margins = contentsMargins();
	size += QSize(margins.left() + margins.right(), margins.top() + margins.bottom());

	// the original formula would leed to ~1 widget per row
	// bash it at least to 400 so we have ~4 knobs per row
	size.setWidth(qMax(size.width(), m_minWidth));
	return size;
}

int ControlLayout::doLayout(const QRect &rect, bool testOnly) const
{
	int left, top, right, bottom;
	getContentsMargins(&left, &top, &right, &bottom);
	QRect effectiveRect = rect.adjusted(+left, +top, -right, -bottom);
	int x = effectiveRect.x();
	int y = effectiveRect.y();
	int lineHeight = 0;

	const QString filterText = m_searchBar->text();
	bool first = true;

	QMapIterator<QString, QLayoutItem*> itr(m_itemMap);
	while (itr.hasNext())
	{
		itr.next();
		QLayoutItem* item = itr.value();
		QWidget *wid = item->widget();
		if (wid)
		{
			if (	first || // do not filter search bar
				filterText.isEmpty() || // no filter - pass all
				itr.key().contains(filterText, Qt::CaseInsensitive))
			{
				if (first)
				{
					// for the search bar, only show it if there are at least
					// two control widgets (i.e. at least 3 widgets)
					if (m_itemMap.size() > 2) { wid->show(); }
					else { wid->hide(); }
				}
				else { wid->show(); }

				int spaceX = horizontalSpacing();
				if (spaceX == -1)
				{
					spaceX = wid->style()->layoutSpacing(
						QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Horizontal);
				}
				int spaceY = verticalSpacing();
				if (spaceY == -1)
				{
					spaceY = wid->style()->layoutSpacing(
						QSizePolicy::PushButton, QSizePolicy::PushButton, Qt::Vertical);
				}
				int nextX = x + item->sizeHint().width() + spaceX;
				if (nextX - spaceX > effectiveRect.right() && lineHeight > 0)
				{
					x = effectiveRect.x();
					y = y + lineHeight + spaceY;
					nextX = x + item->sizeHint().width() + spaceX;
					lineHeight = 0;
				}

				if (!testOnly)
				{
					item->setGeometry(QRect(QPoint(x, y), item->sizeHint()));
				}

				x = nextX;
				lineHeight = qMax(lineHeight, item->sizeHint().height());
				first = false;
			}
			else
			{
				wid->hide();
			}
		}
	}
	return y + lineHeight - rect.y() + bottom;
}

int ControlLayout::smartSpacing(QStyle::PixelMetric pm) const
{
	QObject *parent = this->parent();
	if (!parent) { return -1; }
	else if (parent->isWidgetType())
	{
		QWidget *pw = static_cast<QWidget *>(parent);
		return pw->style()->pixelMetric(pm, nullptr, pw);
	}
	else { return static_cast<QLayout *>(parent)->spacing(); }
}


code:
/*
 * ControllerRackView.cpp - view for song's controllers
 *
 * Copyright (c) 2008-2009 Paul Giblock <drfaygo/at/gmail.com>
 * Copyright (c) 2010-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QApplication>
#include <QLayout>
#include <QMdiSubWindow>
#include <QPushButton>
#include <QScrollArea>
#include <QMdiArea>
#include <QMessageBox>

#include "Song.h"
#include "embed.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "GroupBox.h"
#include "ControllerRackView.h"
#include "ControllerView.h"
#include "LfoController.h"

ControllerRackView::ControllerRackView( ) :
	QWidget(),
	m_nextIndex(0)
{
	setWindowIcon( embed::getIconPixmap( "controller" ) );
	setWindowTitle( tr( "Controller Rack" ) );

	m_scrollArea = new QScrollArea( this );
	m_scrollArea->setPalette( QApplication::palette( m_scrollArea ) );
	m_scrollArea->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );

	QWidget * scrollAreaWidget = new QWidget( m_scrollArea );
	m_scrollAreaLayout = new QVBoxLayout( scrollAreaWidget );
	m_scrollAreaLayout->addStretch();
	scrollAreaWidget->setLayout( m_scrollAreaLayout );

	m_scrollArea->setWidget( scrollAreaWidget );
	m_scrollArea->setWidgetResizable( true );

	m_addButton = new QPushButton( this );
	m_addButton->setText( tr( "Add" ) );

	connect( m_addButton, SIGNAL( clicked() ),
			this, SLOT( addController() ) );

	Song * song = Engine::getSong();
	connect( song, SIGNAL( controllerAdded( Controller* ) ), SLOT( onControllerAdded( Controller* ) ) );
	connect( song, SIGNAL( controllerRemoved( Controller* ) ), SLOT( onControllerRemoved( Controller* ) ) );

	QVBoxLayout * layout = new QVBoxLayout();
	layout->addWidget( m_scrollArea );
	layout->addWidget( m_addButton );
	this->setLayout( layout );

	QMdiSubWindow * subWin = gui->mainWindow()->addWindowedWidget( this );

	// No maximize button
	Qt::WindowFlags flags = subWin->windowFlags();
	flags &= ~Qt::WindowMaximizeButtonHint;
	subWin->setWindowFlags( flags );

	subWin->setAttribute( Qt::WA_DeleteOnClose, false );
	subWin->move( 680, 310 );
	subWin->resize( 350, 200 );
	subWin->setFixedWidth( 350 );
	subWin->setMinimumHeight( 200 );
}

ControllerRackView::~ControllerRackView()
{
}

void ControllerRackView::saveSettings( QDomDocument & _doc,
							QDomElement & _this )
{
	MainWindow::saveWidgetState( this, _this );
}

void ControllerRackView::loadSettings( const QDomElement & _this )
{
	MainWindow::restoreWidgetState( this, _this );
}

void ControllerRackView::deleteController( ControllerView * _view )
{
	Controller * c = _view->getController();

	if( c->connectionCount() > 0 )
	{
		QMessageBox msgBox;
		msgBox.setIcon( QMessageBox::Question );
		msgBox.setWindowTitle( tr("Confirm Delete") );
		msgBox.setText( tr("Confirm delete? There are existing connection(s) "
				"associated with this controller. There is no way to undo.") );
		msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
		if( msgBox.exec() != QMessageBox::Ok )
		{
			return;
		}
	}

	Song * song = Engine::getSong();
	song->removeController( c );
}

void ControllerRackView::onControllerAdded( Controller * controller )
{
	QWidget * scrollAreaWidget = m_scrollArea->widget();

	ControllerView * controllerView = new ControllerView( controller, scrollAreaWidget );

	connect( controllerView, SIGNAL( deleteController( ControllerView * ) ),
		 this, SLOT( deleteController( ControllerView * ) ), Qt::QueuedConnection );

	m_controllerViews.append( controllerView );
	m_scrollAreaLayout->insertWidget( m_nextIndex, controllerView );

	++m_nextIndex;
}

void ControllerRackView::onControllerRemoved( Controller * removedController )
{
	ControllerView * viewOfRemovedController = 0;

	QVector<ControllerView *>::const_iterator end = m_controllerViews.end();
	for ( QVector<ControllerView *>::const_iterator it = m_controllerViews.begin(); it != end; ++it)
	{
		ControllerView *currentControllerView = *it;
		if ( currentControllerView->getController() == removedController )
		{
			viewOfRemovedController = currentControllerView;
			break;
		}
	}

	if (viewOfRemovedController )
	{
		m_controllerViews.erase( std::find( m_controllerViews.begin(),
					m_controllerViews.end(), viewOfRemovedController ) );

		delete viewOfRemovedController;
		--m_nextIndex;
	}
}

void ControllerRackView::addController()
{
	// TODO: Eventually let the user pick from available controller types

	Engine::getSong()->addController( new LfoController( Engine::getSong() ) );

	// fix bug which always made ControllerRackView loose focus when adding
	// new controller
	setFocus();
}

void ControllerRackView::closeEvent( QCloseEvent * _ce )
 {
	if( parentWidget() )
	{
		parentWidget()->hide();
	}
	else
	{
		hide();
	}
	_ce->ignore();
 }


code:
/*
 * ControllerView.cpp - view-component for an controller
 *
 * Copyright (c) 2008-2009 Paul Giblock <drfaygo/at/gmail.com>
 * Copyright (c) 2011-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QLabel>
#include <QPushButton>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QPainter>
#include <QInputDialog>
#include <QLayout>

#include "ControllerView.h"

#include "CaptionMenu.h"
#include "ControllerDialog.h"
#include "gui_templates.h"
#include "embed.h"
#include "GuiApplication.h"
#include "LedCheckbox.h"
#include "MainWindow.h"
#include "ToolTip.h"

ControllerView::ControllerView( Controller * _model, QWidget * _parent ) :
	QFrame( _parent ),
	ModelView( _model, this ),
	m_subWindow( NULL ),
	m_controllerDlg( NULL ),
	m_show( true )
{
	this->setFrameStyle( QFrame::StyledPanel );
	this->setFrameShadow( QFrame::Raised );

	QVBoxLayout *vBoxLayout = new QVBoxLayout(this);

	QHBoxLayout *hBox = new QHBoxLayout();
	vBoxLayout->addLayout(hBox);

	QLabel *label = new QLabel( "<b>" + _model->displayName() + "</b>", this);
	QSizePolicy sizePolicy = label->sizePolicy();
	sizePolicy.setHorizontalStretch(1);
	label->setSizePolicy(sizePolicy);

	hBox->addWidget(label);

	QPushButton * controlsButton = new QPushButton( tr( "Controls" ), this );
	connect( controlsButton, SIGNAL( clicked() ), SLOT( editControls() ) );

	hBox->addWidget(controlsButton);

	m_nameLabel = new QLabel(_model->name(), this);
	vBoxLayout->addWidget(m_nameLabel);

	m_controllerDlg = getController()->createDialog( gui->mainWindow()->workspace() );

	m_subWindow = gui->mainWindow()->addWindowedWidget( m_controllerDlg );

	Qt::WindowFlags flags = m_subWindow->windowFlags();
	flags &= ~Qt::WindowMaximizeButtonHint;
	m_subWindow->setWindowFlags( flags );
	m_subWindow->setFixedSize( m_subWindow->size() );

	m_subWindow->setWindowIcon( m_controllerDlg->windowIcon() );

	connect( m_controllerDlg, SIGNAL( closed() ),
		this, SLOT( closeControls() ) );

	m_subWindow->hide();

	setModel( _model );
}

ControllerView::~ControllerView()
{
	if (m_subWindow)
	{
		delete m_subWindow;
	}
}

void ControllerView::editControls()
{
	if( m_show )
	{
		m_subWindow->show();
		m_subWindow->raise();
		m_show = false;
	}
	else
	{
		m_subWindow->hide();
		m_show = true;
	}
}

void ControllerView::closeControls()
{
	m_subWindow->hide();
	m_show = true;
}

void ControllerView::deleteController()
{
	emit( deleteController( this ) );
}

void ControllerView::renameController()
{
	bool ok;
	Controller * c = castModel<Controller>();
	QString new_name = QInputDialog::getText( this,
			tr( "Rename controller" ),
			tr( "Enter the new name for this controller" ),
			QLineEdit::Normal, c->name() , &ok );
	if( ok && !new_name.isEmpty() )
	{
		c->setName( new_name );
		if( getController()->type() == Controller::LfoController )
		{
			m_controllerDlg->setWindowTitle( tr( "LFO" ) + " (" + new_name + ")" );
		}
		m_nameLabel->setText( new_name );
	}
}

void ControllerView::mouseDoubleClickEvent( QMouseEvent * event )
{
	renameController();
}

void ControllerView::modelChanged()
{
}

void ControllerView::contextMenuEvent( QContextMenuEvent * )
{
	QPointer<CaptionMenu> contextMenu = new CaptionMenu( model()->displayName(), this );
	contextMenu->addAction( embed::getIconPixmap( "cancel" ),
						tr( "&Remove this controller" ),
						this, SLOT( deleteController() ) );
	contextMenu->addAction( tr("Re&name this controller"), this, SLOT( renameController() ));
	contextMenu->addSeparator();
	contextMenu->exec( QCursor::pos() );
	delete contextMenu;
}


code:
/*
 * Controls.cpp - labeled control widgets
 *
 * Copyright (c) 2019-2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Controls.h"

#include <QLabel>
#include <QString>
#include <QVBoxLayout>

#include "ComboBox.h"
#include "LcdSpinBox.h"
#include "LedCheckbox.h"
#include "Knob.h"

Control::~Control() {}

void KnobControl::setText(const QString &text) { m_knob->setLabel(text); }

QWidget *KnobControl::topWidget() { return m_knob; }

void KnobControl::setModel(AutomatableModel *model)
{
	m_knob->setModel(model->dynamicCast<FloatModel>(true));
}

FloatModel *KnobControl::model() { return m_knob->model(); }

AutomatableModelView* KnobControl::modelView() { return m_knob; }

KnobControl::KnobControl(QWidget *parent) :
	m_knob(new Knob(parent)) {}

KnobControl::~KnobControl() {}

void ComboControl::setText(const QString &text) { m_label->setText(text); }

void ComboControl::setModel(AutomatableModel *model)
{
	m_combo->setModel(model->dynamicCast<ComboBoxModel>(true));
}

ComboBoxModel *ComboControl::model() { return m_combo->model(); }

AutomatableModelView* ComboControl::modelView() { return m_combo; }

ComboControl::ComboControl(QWidget *parent) :
	m_widget(new QWidget(parent)),
	m_combo(new ComboBox(nullptr)),
	m_label(new QLabel(m_widget))
{
	m_combo->setFixedSize(64, ComboBox::DEFAULT_HEIGHT);
	QVBoxLayout* vbox = new QVBoxLayout(m_widget);
	vbox->addWidget(m_combo);
	vbox->addWidget(m_label);
	m_combo->repaint();
}

ComboControl::~ComboControl() {}

void CheckControl::setText(const QString &text) { m_label->setText(text); }

QWidget *CheckControl::topWidget() { return m_widget; }

void CheckControl::setModel(AutomatableModel *model)
{
	m_checkBox->setModel(model->dynamicCast<BoolModel>(true));
}

BoolModel *CheckControl::model() { return m_checkBox->model(); }

AutomatableModelView* CheckControl::modelView() { return m_checkBox; }

CheckControl::CheckControl(QWidget *parent) :
	m_widget(new QWidget(parent)),
	m_checkBox(new LedCheckBox(nullptr, QString(), LedCheckBox::Green)),
	m_label(new QLabel(m_widget))
{
	QVBoxLayout* vbox = new QVBoxLayout(m_widget);
	vbox->addWidget(m_checkBox);
	vbox->addWidget(m_label);
}

CheckControl::~CheckControl() {}

void LcdControl::setText(const QString &text) { m_lcd->setLabel(text); }

QWidget *LcdControl::topWidget() { return m_lcd; }

void LcdControl::setModel(AutomatableModel *model)
{
	m_lcd->setModel(model->dynamicCast<IntModel>(true));
}

IntModel *LcdControl::model() { return m_lcd->model(); }

AutomatableModelView* LcdControl::modelView() { return m_lcd; }

LcdControl::LcdControl(int numDigits, QWidget *parent) :
	m_lcd(new LcdSpinBox(numDigits, parent))
{
}

LcdControl::~LcdControl() {}


code:
#include "CustomTextKnob.h"

CustomTextKnob::CustomTextKnob( knobTypes _knob_num, QWidget * _parent, const QString & _name, const QString & _value_text ) :
	Knob( _knob_num, _parent, _name ),
	m_value_text( _value_text ) {}

CustomTextKnob::CustomTextKnob( QWidget * _parent, const QString & _name, const QString & _value_text ) : //!< default ctor
	Knob( _parent, _name ),
	m_value_text( _value_text ) {}

QString CustomTextKnob::displayValue() const
{
	return m_description.trimmed() + m_value_text;
}


code:
/*
 * EffectRackView.cpp - view for effectChain model
 *
 * Copyright (c) 2006-2007 Danny McRae <khjklujn@netscape.net>
 * Copyright (c) 2008-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QApplication>
#include <QLayout>
#include <QPushButton>
#include <QScrollArea>

#include "EffectRackView.h"
#include "EffectSelectDialog.h"
#include "EffectView.h"
#include "GroupBox.h"

EffectRackView::EffectRackView( EffectChain* model, QWidget* parent ) :
	QWidget( parent ),
	ModelView( NULL, this )
{
	QVBoxLayout* mainLayout = new QVBoxLayout( this );
	mainLayout->setMargin( 5 );

	m_effectsGroupBox = new GroupBox( tr( "EFFECTS CHAIN" ) );
	mainLayout->addWidget( m_effectsGroupBox );

	QVBoxLayout* effectsLayout = new QVBoxLayout( m_effectsGroupBox );
	effectsLayout->setSpacing( 0 );
	effectsLayout->setContentsMargins( 2, m_effectsGroupBox->titleBarHeight() + 2, 2, 2 );

	m_scrollArea = new QScrollArea;
	m_scrollArea->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOn );
	m_scrollArea->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
	m_scrollArea->setPalette( QApplication::palette( m_scrollArea ) );
	m_scrollArea->setFrameStyle( QFrame::NoFrame );
	m_scrollArea->setWidget( new QWidget );

	effectsLayout->addWidget( m_scrollArea );

	QPushButton* addButton = new QPushButton;
	addButton->setText( tr( "Add effect" ) );

	effectsLayout->addWidget( addButton );

	connect( addButton, SIGNAL( clicked() ), this, SLOT( addEffect() ) );

	m_lastY = 0;

	setModel( model );
}

EffectRackView::~EffectRackView()
{
	clearViews();
}

void EffectRackView::clearViews()
{
	while( m_effectViews.size() )
	{
		EffectView * e = m_effectViews[m_effectViews.size() - 1];
		m_effectViews.pop_back();
		delete e;
	}
}

void EffectRackView::moveUp( EffectView* view )
{
	fxChain()->moveUp( view->effect() );
	if( view != m_effectViews.first() )
	{
		int i = 0;
		for( QVector<EffectView *>::Iterator it = m_effectViews.begin();
					it != m_effectViews.end(); it++, i++ )
		{
			if( *it == view )
			{
				break;
			}
		}

		EffectView * temp = m_effectViews[ i - 1 ];

		m_effectViews[i - 1] = view;
		m_effectViews[i] = temp;

		update();
	}
}

void EffectRackView::moveDown( EffectView* view )
{
	if( view != m_effectViews.last() )
	{
		// moving next effect up is the same
		moveUp( *( std::find( m_effectViews.begin(), m_effectViews.end(), view ) + 1 ) );
	}
}

void EffectRackView::deletePlugin( EffectView* view )
{
	Effect * e = view->effect();
	m_effectViews.erase( std::find( m_effectViews.begin(), m_effectViews.end(), view ) );
	delete view;
	fxChain()->removeEffect( e );
	e->deleteLater();
	update();
}

void EffectRackView::update()
{
	QWidget * w = m_scrollArea->widget();
	QVector<bool> view_map( qMax<int>( fxChain()->m_effects.size(),
						m_effectViews.size() ), false );

	for( QVector<Effect *>::Iterator it = fxChain()->m_effects.begin();
					it != fxChain()->m_effects.end(); ++it )
	{
		int i = 0;
		for( QVector<EffectView *>::Iterator vit = m_effectViews.begin();
				vit != m_effectViews.end(); ++vit, ++i )
		{
			if( ( *vit )->model() == *it )
			{
				view_map[i] = true;
				break;
			}
		}
		if( i >= m_effectViews.size() )
		{
			EffectView * view = new EffectView( *it, w );
			connect( view, SIGNAL( moveUp( EffectView * ) ),
					this, SLOT( moveUp( EffectView * ) ) );
			connect( view, SIGNAL( moveDown( EffectView * ) ),
				this, SLOT( moveDown( EffectView * ) ) );
			connect( view, SIGNAL( deletePlugin( EffectView * ) ),
				this, SLOT( deletePlugin( EffectView * ) ),
							Qt::QueuedConnection );
			view->show();
			m_effectViews.append( view );
			if( i < view_map.size() )
			{
				view_map[i] = true;
			}
			else
			{
				view_map.append( true );
			}

		}
	}

	int i = 0, nView = 0;

	const int EffectViewMargin = 3;
	m_lastY = EffectViewMargin;

	for( QVector<EffectView *>::Iterator it = m_effectViews.begin();
					it != m_effectViews.end(); i++ )
	{
		if( i < view_map.size() && view_map[i] == false )
		{
			delete m_effectViews[nView];
			it = m_effectViews.erase( it );
		}
		else
		{
			( *it )->move( EffectViewMargin, m_lastY );
			m_lastY += ( *it )->height();
			++nView;
			++it;
		}
	}

	w->setFixedSize( EffectView::DEFAULT_WIDTH + 2*EffectViewMargin, m_lastY);

	QWidget::update();
}

void EffectRackView::addEffect()
{
	EffectSelectDialog esd( this );
	esd.exec();

	if( esd.result() == QDialog::Rejected )
	{
		return;
	}

	Effect * fx = esd.instantiateSelectedPlugin( fxChain() );

	fxChain()->appendEffect( fx );
	update();

	// Find the effectView, and show the controls
	for( QVector<EffectView *>::Iterator vit = m_effectViews.begin();
					vit != m_effectViews.end(); ++vit )
	{
		if( ( *vit )->effect() == fx )
		{
			( *vit )->editControls();

			break;
		}
	}

}

void EffectRackView::modelChanged()
{
	//clearViews();
	m_effectsGroupBox->setModel( &fxChain()->m_enabledModel );
	connect( fxChain(), SIGNAL( aboutToClear() ), this, SLOT( clearViews() ) );
	update();
}


code:
/*
 * EffectView.cpp - view-component for an effect
 *
 * Copyright (c) 2006-2007 Danny McRae <khjklujn/at/users.sourceforge.net>
 * Copyright (c) 2007-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include <QLabel>
#include <QPushButton>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QPainter>
#include <QLayout>

#include "EffectView.h"
#include "DummyEffect.h"
#include "CaptionMenu.h"
#include "embed.h"
#include "GuiApplication.h"
#include "gui_templates.h"
#include "Knob.h"
#include "LedCheckbox.h"
#include "MainWindow.h"
#include "TempoSyncKnob.h"
#include "ToolTip.h"

EffectView::EffectView( Effect * _model, QWidget * _parent ) :
	PluginView( _model, _parent ),
	m_bg( embed::getIconPixmap( "effect_plugin" ) ),
	m_subWindow( NULL ),
	m_controlView( NULL )
{
	setFixedSize( EffectView::DEFAULT_WIDTH, 60 );

	// Disable effects that are of type "DummyEffect"
	bool isEnabled = !dynamic_cast<DummyEffect *>( effect() );
	m_bypass = new LedCheckBox( this, "", isEnabled ? LedCheckBox::Green : LedCheckBox::Red );
	m_bypass->move( 3, 3 );
	m_bypass->setEnabled( isEnabled );

	ToolTip::add( m_bypass, tr( "On/Off" ) );

	m_wetDry = new Knob( knobBright_26, this );
	m_wetDry->setLabel( tr( "W/D" ) );
	m_wetDry->move( 40 - m_wetDry->width() / 2, 5 );
	m_wetDry->setEnabled( isEnabled );
	m_wetDry->setHintText( tr( "Wet Level:" ), "" );

	m_autoQuit = new TempoSyncKnob( knobBright_26, this );
	m_autoQuit->setLabel( tr( "DECAY" ) );
	m_autoQuit->move( 78 - m_autoQuit->width() / 2, 5 );
	m_autoQuit->setEnabled( isEnabled && !effect()->m_autoQuitDisabled );
	m_autoQuit->setHintText( tr( "Time:" ), "ms" );

	m_gate = new Knob( knobBright_26, this );
	m_gate->setLabel( tr( "GATE" ) );
	m_gate->move( 116 - m_gate->width() / 2, 5 );
	m_gate->setEnabled( isEnabled && !effect()->m_autoQuitDisabled );
	m_gate->setHintText( tr( "Gate:" ), "" );

	setModel( _model );

	if( effect()->controls()->controlCount() > 0 )
	{
		QPushButton * ctls_btn = new QPushButton( tr( "Controls" ),
									this );
		QFont f = ctls_btn->font();
		ctls_btn->setFont( pointSize<8>( f ) );
		ctls_btn->setGeometry( 150, 14, 50, 20 );
		connect( ctls_btn, SIGNAL( clicked() ),
					this, SLOT( editControls() ) );

		m_controlView = effect()->controls()->createView();
		if( m_controlView )
		{
			m_subWindow = gui->mainWindow()->addWindowedWidget( m_controlView );

			if ( !m_controlView->isResizable() )
			{
				m_subWindow->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
				if (m_subWindow->layout())
				{
					m_subWindow->layout()->setSizeConstraint(QLayout::SetFixedSize);
				}
			}

			Qt::WindowFlags flags = m_subWindow->windowFlags();
			flags &= ~Qt::WindowMaximizeButtonHint;
			m_subWindow->setWindowFlags( flags );

			connect( m_controlView, SIGNAL( closed() ),
					this, SLOT( closeEffects() ) );

			m_subWindow->hide();
		}
	}

	//move above vst effect view creation
	//setModel( _model );
}

EffectView::~EffectView()
{
	delete m_subWindow;
}

void EffectView::editControls()
{
	if( m_subWindow )
	{
		if( !m_subWindow->isVisible() )
		{
			m_subWindow->show();
			m_subWindow->raise();
			effect()->controls()->setViewVisible( true );
		}
		else
		{
			m_subWindow->hide();
			effect()->controls()->setViewVisible( false );
		}
	}
}

void EffectView::moveUp()
{
	emit moveUp( this );
}

void EffectView::moveDown()
{
	emit moveDown( this );
}

void EffectView::deletePlugin()
{
	emit deletePlugin( this );
}

void EffectView::closeEffects()
{
	if( m_subWindow )
	{
		m_subWindow->hide();
	}
	effect()->controls()->setViewVisible( false );
}

void EffectView::contextMenuEvent( QContextMenuEvent * )
{
	QPointer<CaptionMenu> contextMenu = new CaptionMenu( model()->displayName(), this );
	contextMenu->addAction( embed::getIconPixmap( "arp_up" ),
						tr( "Move &up" ),
						this, SLOT( moveUp() ) );
	contextMenu->addAction( embed::getIconPixmap( "arp_down" ),
						tr( "Move &down" ),
						this, SLOT( moveDown() ) );
	contextMenu->addSeparator();
	contextMenu->addAction( embed::getIconPixmap( "cancel" ),
						tr( "&Remove this plugin" ),
						this, SLOT( deletePlugin() ) );
	contextMenu->addSeparator();
	contextMenu->exec( QCursor::pos() );
	delete contextMenu;
}

void EffectView::paintEvent( QPaintEvent * )
{
	QPainter p( this );
	p.drawPixmap( 0, 0, m_bg );

	QFont f = pointSizeF( font(), 7.5f );
	f.setBold( true );
	p.setFont( f );

	QString elidedText = p.fontMetrics().elidedText( model()->displayName(), Qt::ElideRight, width() - 22 );

	p.setPen( palette().shadow().color() );
	p.drawText( 6, 55, elidedText );
	p.setPen( palette().text().color() );
	p.drawText( 5, 54, elidedText );
}

void EffectView::modelChanged()
{
	m_bypass->setModel( &effect()->m_enabledModel );
	m_wetDry->setModel( &effect()->m_wetDryModel );
	m_autoQuit->setModel( &effect()->m_autoQuitModel );
	m_gate->setModel( &effect()->m_gateModel );
}