code:
/*
 * MidiClient.cpp - base-class for MIDI-clients like ALSA-sequencer-client
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 * This file partly contains code from Fluidsynth, Peter Hanappe
 *
 * 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 "MidiClient.h"
#include "MidiPort.h"
#include "Note.h"

MidiClient::MidiClient()
{
}

MidiClient::~MidiClient()
{
	//TODO: noteOffAll(); / clear all ports
	for (MidiPort* port : m_midiPorts)
	{
		port->invalidateCilent();
	}
}

void MidiClient::applyPortMode( MidiPort* )
{
}

void MidiClient::applyPortName( MidiPort* )
{
}

void MidiClient::addPort( MidiPort* port )
{
	m_midiPorts.push_back( port );
}

void MidiClient::removePort( MidiPort* port )
{
	if( ! port )
	{
		return;
	}

	QVector<MidiPort *>::Iterator it =
		std::find( m_midiPorts.begin(), m_midiPorts.end(), port );
	if( it != m_midiPorts.end() )
	{
		m_midiPorts.erase( it );
	}
}

void MidiClient::subscribeReadablePort( MidiPort*, const QString& , bool )
{
}

void MidiClient::subscribeWritablePort( MidiPort* , const QString& , bool )
{
}

MidiClientRaw::MidiClientRaw()
{
}

MidiClientRaw::~MidiClientRaw()
{
}

void MidiClientRaw::parseData( const unsigned char c )
{
	/*********************************************************************/
	/* 'Process' system real-time messages                               */
	/*********************************************************************/
	/* There are not too many real-time messages that are of interest here.
	 * They can occur anywhere, even in the middle of a noteon message!
	 * Real-time range: 0xF8 .. 0xFF
	 * Note: Real-time does not affect (running) status.
	 */
	if( c >= 0xF8 )
	{
		if( c == MidiSystemReset )
		{
			m_midiParseData.m_midiEvent.setType( MidiSystemReset );
			m_midiParseData.m_status = 0;
			processParsedEvent();
		}
		return;
	}

	/*********************************************************************/
	/* 'Process' system common messages (again, just skip them)          */
	/*********************************************************************/
	/* There are no system common messages that are of interest here.
	 * System common range: 0xF0 .. 0xF7
	 */
	if( c > 0xF0 )
	{
	/* MIDI spec say: To ignore a non-real-time message, just discard all
	 * data up to the next status byte.  And our parser will ignore data
	 * that is received without a valid status.
	 * Note: system common cancels running status. */
		m_midiParseData.m_status = 0;
		return;
	}

	/*********************************************************************/
	/* Process voice category messages:                                  */
	/*********************************************************************/
	/* Now that we have handled realtime and system common messages, only
	 * voice messages are left.
	 * Only a status byte has bit # 7 set.
	 * So no matter the status of the parser (in case we have lost sync),
	 * as soon as a byte >= 0x80 comes in, we are dealing with a status byte
	 * and start a new event.
	 */
	if( c & 0x80 )
	{
		m_midiParseData.m_channel = c & 0x0F;
		m_midiParseData.m_status = c & 0xF0;
		/* The event consumes x bytes of data...
					(subtract 1 for the status byte) */
		m_midiParseData.m_bytesTotal = eventLength( m_midiParseData.m_status ) - 1;
		/* of which we have read 0 at this time. */
		m_midiParseData.m_bytes = 0;
		return;
	}

	/*********************************************************************/
	/* Process data                                                      */
	/*********************************************************************/
	/* If we made it this far, then the received char belongs to the data
	 * of the last event. */
	if( m_midiParseData.m_status == 0 )
	{
		/* We are not interested in the event currently received.
							Discard the data. */
		return;
	}

	/* Store the first couple of bytes */
	if( m_midiParseData.m_bytes < RAW_MIDI_PARSE_BUF_SIZE )
	{
		m_midiParseData.m_buffer[m_midiParseData.m_bytes] = c;
	}
	++m_midiParseData.m_bytes;

	/* Do we still need more data to get this event complete? */
	if( m_midiParseData.m_bytes < m_midiParseData.m_bytesTotal )
	{
		return;
	}

	/*********************************************************************/
	/* Send the event                                                    */
	/*********************************************************************/
	/* The event is ready-to-go.  About 'running status':
	 *
	 * The MIDI protocol has a built-in compression mechanism. If several
	 * similar events are sent in-a-row, for example note-ons, then the
	 * event type is only sent once. For this case, the last event type
	 * (status) is remembered.
	 * We simply keep the status as it is, just reset the parameter counter.
	 * If another status byte comes in, it will overwrite the status.
	 */
	m_midiParseData.m_midiEvent.setType( static_cast<MidiEventTypes>( m_midiParseData.m_status ) );
	m_midiParseData.m_midiEvent.setChannel( m_midiParseData.m_channel );
	m_midiParseData.m_bytes = 0; /* Related to running status! */
	switch( m_midiParseData.m_midiEvent.type() )
	{
		case MidiNoteOff:
		case MidiNoteOn:
		case MidiKeyPressure:
		case MidiChannelPressure:
			m_midiParseData.m_midiEvent.setKey( m_midiParseData.m_buffer[0] - KeysPerOctave );
			m_midiParseData.m_midiEvent.setVelocity( m_midiParseData.m_buffer[1] );
			break;

		case MidiProgramChange:
			m_midiParseData.m_midiEvent.setKey( m_midiParseData.m_buffer[0] );
			m_midiParseData.m_midiEvent.setVelocity( m_midiParseData.m_buffer[1] );
			break;

		case MidiControlChange:
			m_midiParseData.m_midiEvent.setControllerNumber( m_midiParseData.m_buffer[0] );
			m_midiParseData.m_midiEvent.setControllerValue(  m_midiParseData.m_buffer[1] );
			break;

		case MidiPitchBend:
			// Pitch-bend is transmitted with 14-bit precision.
			// Note: '|' does here the same as '+' (no common bits),
			// but might be faster
			m_midiParseData.m_midiEvent.setPitchBend( ( m_midiParseData.m_buffer[1] * 128 ) | m_midiParseData.m_buffer[0] );
			break;

		default:
			// Unlikely
			return;
	}

	processParsedEvent();
}

void MidiClientRaw::processParsedEvent()
{
	for( int i = 0; i < m_midiPorts.size(); ++i )
	{
		m_midiPorts[i]->processInEvent( m_midiParseData.m_midiEvent );
	}
}

void MidiClientRaw::processOutEvent( const MidiEvent& event, const TimePos & , const MidiPort* port )
{
	// TODO: also evaluate _time and queue event if necessary
	switch( event.type() )
	{
		case MidiNoteOn:
		case MidiNoteOff:
		case MidiKeyPressure:
			sendByte( event.type() | event.channel() );
			sendByte( event.key() + KeysPerOctave );
			sendByte( event.velocity() );
			break;

		default:
			qWarning( "MidiClientRaw: unhandled MIDI-event %d\n",
							(int) event.type() );
			break;
	}
}

// Taken from Nagano Daisuke's USB-MIDI driver
static const unsigned char REMAINS_F0F6[] =
{
	0,	/* 0xF0 */
	2,	/* 0XF1 */
	3,	/* 0XF2 */
	2,	/* 0XF3 */
	2,	/* 0XF4 (Undefined by MIDI Spec, and subject to change) */
	2,	/* 0XF5 (Undefined by MIDI Spec, and subject to change) */
	1	/* 0XF6 */
} ;

static const unsigned char REMAINS_80E0[] =
{
	3,	/* 0x8X Note Off */
	3,	/* 0x9X Note On */
	3,	/* 0xAX Poly-key pressure */
	3,	/* 0xBX Control Change */
	2,	/* 0xCX Program Change */
	2,	/* 0xDX Channel pressure */
	3 	/* 0xEX PitchBend Change */
} ;

// Returns the length of the MIDI message starting with _event.
// Taken from Nagano Daisuke's USB-MIDI driver
int MidiClientRaw::eventLength( const unsigned char event )
{
	if ( event < 0xF0 )
	{
		return REMAINS_80E0[( ( event - 0x80 ) >> 4 ) & 0x0F];
	}
	else if ( event < 0xF7 )
	{
		return REMAINS_F0F6[event - 0xF0];
	}
	return 1;
}


code:
/*
 * MidiController.cpp - implementation of class midi-controller which handles
 *                      MIDI control change messages
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail.com>
 *
 * 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 <QDomElement>
#include <QObject>

#include "Song.h"
#include "Mixer.h"
#include "MidiClient.h"
#include "MidiController.h"

MidiController::MidiController( Model * _parent ) :
	Controller( Controller::MidiController, _parent, tr( "MIDI Controller" ) ),
	MidiEventProcessor(),
	m_midiPort( tr( "unnamed_midi_controller" ),
			Engine::mixer()->midiClient(), this, this, MidiPort::Input ),
	m_lastValue( 0.0f ),
	m_previousValue( 0.0f )
{
	setSampleExact( true );
	connect( &m_midiPort, SIGNAL( modeChanged() ),
			this, SLOT( updateName() ) );
}

MidiController::~MidiController()
{
}

void MidiController::updateValueBuffer()
{
	if( m_previousValue != m_lastValue )
	{
		m_valueBuffer.interpolate( m_previousValue, m_lastValue );
		m_previousValue = m_lastValue;
	}
	else
	{
		m_valueBuffer.fill( m_lastValue );
	}
	m_bufferLastUpdated = s_periods;
}

void MidiController::updateName()
{
	setName( QString("MIDI ch%1 ctrl%2").
			arg( m_midiPort.inputChannel() ).
			arg( m_midiPort.inputController() ) );
}

void MidiController::processInEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset )
{
	unsigned char controllerNum;
	switch( event.type() )
	{
		case MidiControlChange:
			controllerNum = event.controllerNumber();

			if( m_midiPort.inputController() == controllerNum + 1 &&
					( m_midiPort.inputChannel() == event.channel() + 1 ||
					  m_midiPort.inputChannel() == 0 ) )
			{
				unsigned char val = event.controllerValue();
				m_previousValue = m_lastValue;
				m_lastValue = (float)( val ) / 127.0f;
				emit valueChanged();
			}
			break;

		default:
			// Don't care - maybe add special cases for pitch and mod later
			break;
	}
}

void MidiController::subscribeReadablePorts( const MidiPort::Map & _map )
{
	for( MidiPort::Map::ConstIterator it = _map.constBegin();
						it != _map.constEnd(); ++it )
	{
		m_midiPort.subscribeReadablePort( it.key(), *it );
	}
}

void MidiController::saveSettings( QDomDocument & _doc, QDomElement & _this )
{
	Controller::saveSettings( _doc, _this );
	m_midiPort.saveSettings( _doc, _this );

}

void MidiController::loadSettings( const QDomElement & _this )
{
	Controller::loadSettings( _this );

	m_midiPort.loadSettings( _this );

	updateName();
}

QString MidiController::nodeName() const
{
	return( "Midicontroller" );
}

ControllerDialog * MidiController::createDialog( QWidget * _parent )
{
	return NULL;
}

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * MidiEventToByteSeq.cpp - writeToByteSeq implementation
 *
 * Copyright (c) 2020-2020 Johannes Lorenz <jlsf2013$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 "MidiEventToByteSeq.h"

#include <QtGlobal>

#include "MidiEvent.h"

std::size_t writeToByteSeq(
	const MidiEvent& ev, uint8_t *data, std::size_t bufsize)
{
	Q_ASSERT(bufsize >= 3);

	std::size_t size = 0;
	data[0] = ev.type() | (ev.channel() & 0x0F);

	switch (ev.type())
	{
		case MidiNoteOn:
			if (ev.velocity() > 0)
			{
				if (ev.key() < 0 || ev.key() > MidiMaxKey)
					break;

				data[1] = ev.key();
				data[2] = ev.velocity();
				size    = 3;
				break;
			}
			else
			{
				// Lv2 MIDI specs:
				// "Note On messages with velocity 0 are not allowed.
				// These messages are equivalent to Note Off in standard
				// MIDI streams, but here only proper Note Off messages
				// are allowed."
				data[0] = MidiNoteOff | (ev.channel() & 0x0F);
				// nobreak
			}

		case MidiNoteOff:
			if (ev.key() < 0 || ev.key() > MidiMaxKey)
				break;
			data[1] = ev.key();
			data[2] = ev.velocity(); // release time
			size    = 3;
			break;

		case MidiKeyPressure:
			data[1] = ev.key();
			data[2] = ev.velocity();
			size    = 3;
			break;

		case MidiControlChange:
			data[1] = ev.controllerNumber();
			data[2] = ev.controllerValue();
			size    = 3;
			break;

		case MidiProgramChange:
			data[1] = ev.program();
			size    = 2;
			break;

		case MidiChannelPressure:
			data[1] = ev.channelPressure();
			size    = 2;
			break;

		case MidiPitchBend:
			data[1] = ev.pitchBend() & 0x7f;
			data[2] = ev.pitchBend() >> 7;
			size    = 3;
			break;

		default:
			// unhandled
			break;
	}

	return size;
}


code:
/*
 * MidiJack.cpp - MIDI client for Jack
 *
 * Copyright (c) 2015 Shane Ambler <develop/at/shaneware.biz>
 *
 * 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 "MidiJack.h"

#ifdef LMMS_HAVE_JACK

#include <QCompleter>
#include <QMessageBox>

#include "ConfigManager.h"
#include "gui_templates.h"
#include "GuiApplication.h"
#include "Engine.h"
#include "Mixer.h"
#include "MainWindow.h"

/* callback functions for jack */
static int JackMidiProcessCallback(jack_nframes_t nframes, void *arg)
{
	MidiJack *jmd = (MidiJack *)arg;

	if (nframes <= 0)
		return (0);

	jmd->JackMidiRead(nframes);
	jmd->JackMidiWrite(nframes);

	return (0);
}

static void JackMidiShutdown(void *arg)
{
        //: When JACK(JACK Audio Connection Kit) disconnects, it will show the following message (title)
	QString msg_short = MidiJack::tr("JACK server down");
        //: When JACK(JACK Audio Connection Kit) disconnects, it will show the following message (dialog message)
	QString msg_long = MidiJack::tr("The JACK server seems to be shuted down.");
	QMessageBox::information( gui->mainWindow(), msg_short, msg_long );
}

MidiJack::MidiJack() :
	MidiClientRaw(),
	m_jackClient( nullptr ),
	m_input_port( NULL ),
	m_output_port( NULL ),
	m_quit( false )
{
	// if jack is currently used for audio then we share the connection
	// AudioJack creates and maintains the jack connection
	// and also handles the callback, we pass it our address
	// so that we can also process during the callback

	m_jackAudio = dynamic_cast<AudioJack*>(Engine::mixer()->audioDev());
	if( m_jackAudio )
	{
		// if a jack connection has been created for audio we use that
		m_jackAudio->addMidiClient(this);
	}else{
		m_jackAudio = NULL;
		m_jackClient = jack_client_open(probeDevice().toLatin1().data(),
										JackNoStartServer, NULL);

		if(m_jackClient)
		{
			jack_set_process_callback(m_jackClient,
							JackMidiProcessCallback, this);
			jack_on_shutdown(m_jackClient,
							JackMidiShutdown, 0);
		}
	}

	if(jackClient())
	{
		/* jack midi out not implemented
		   JackMidiWrite and sendByte needs to be functional
		   before enabling this
		   If you enable this, also enable the
		   corresponding jack_port_unregister line below
		m_output_port = jack_port_register(
				jackClient(), "MIDI out", JACK_DEFAULT_MIDI_TYPE,
				JackPortIsOutput, 0);
		*/

		m_input_port = jack_port_register(
				jackClient(), "MIDI in", JACK_DEFAULT_MIDI_TYPE,
				JackPortIsInput, 0);

		if(jack_activate(jackClient()) == 0 )
		{
			// only start thread, if we have an active jack client.
			start( QThread::LowPriority );
		}
	}
}

MidiJack::~MidiJack()
{
	if(jackClient())
	{
		if (m_jackAudio) {
			// remove ourselves first (atomically), so we will not get called again
			m_jackAudio->removeMidiClient();
		}

		if( jack_port_unregister( jackClient(), m_input_port) != 0){
			printf("Failed to unregister jack midi input\n");
		}

		/* Unused yet, see the corresponding jack_port_register call
		if( jack_port_unregister( jackClient(), m_output_port) != 0){
			printf("Failed to unregister jack midi output\n");
		}
		*/

		if(m_jackClient)
		{
			// an m_jackClient means we are handling the jack connection
			if( jack_deactivate(m_jackClient) != 0){
				printf("Failed to deactivate jack midi client\n");
			}

			if( jack_client_close(m_jackClient) != 0){
				printf("Failed close jack midi client\n");
			}
		}
	}
	if( isRunning() )
	{
		m_quit = true;
		wait( 1000 );
		terminate();
	}
}

jack_client_t* MidiJack::jackClient()
{
	if( m_jackAudio == NULL && m_jackClient == NULL)
		return NULL;

	if( m_jackAudio == NULL && m_jackClient )
		return m_jackClient;

	return m_jackAudio->jackClient();
}

QString MidiJack::probeDevice()
{
	QString jid = ConfigManager::inst()->value( "midijack", "lmms" );
	if( jid.isEmpty() )
	{
		return "lmms";
	}
	return jid;
}

// we read data from jack
void MidiJack::JackMidiRead(jack_nframes_t nframes)
{
	unsigned int i,b;
	void* port_buf = jack_port_get_buffer(m_input_port, nframes);
	jack_midi_event_t in_event;
	jack_nframes_t event_index = 0;
	jack_nframes_t event_count = jack_midi_get_event_count(port_buf);

	int rval = jack_midi_event_get(&in_event, port_buf, 0);
	if (rval == 0 /* 0 = success */)
	{
		for(i=0; i<nframes; i++)
		{
			if((in_event.time == i) && (event_index < event_count))
			{
				// lmms is setup to parse bytes coming from a device
				// parse it byte by byte as it expects
				for(b=0;b<in_event.size;b++)
					parseData( *(in_event.buffer + b) );

				event_index++;
				if(event_index < event_count)
					jack_midi_event_get(&in_event, port_buf, event_index);
			}
		}
	}
}

/* jack midi out is not implemented
   sending plain bytes to jack midi outputs doesn't work
   once working the output port needs to be enabled in the constructor
 */

void MidiJack::sendByte( const unsigned char c )
{
	//m_midiDev.putChar( c );
}

// we write data to jack
void MidiJack::JackMidiWrite(jack_nframes_t nframes)
{
	// TODO: write midi data to jack port
}

void MidiJack::run()
{
	while( m_quit == false )
	{
		// we sleep the thread to keep it alive
		// midi processing is handled by jack server callbacks
		sleep(1);
	}
}

#endif // LMMS_HAVE_JACK


code:
/*
 * MidiOss.cpp - OSS raw MIDI client
 *
 * Copyright (c) 2005-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 "MidiOss.h"

#ifdef LMMS_HAVE_OSS

#include "ConfigManager.h"
#include "gui_templates.h"

MidiOss::MidiOss() :
	MidiClientRaw(),
	m_midiDev( probeDevice() ),
	m_quit( false )
{
	// only start thread, if opening of MIDI-device is successful,
	// otherwise isRunning()==false indicates error
	if( m_midiDev.open( QIODevice::ReadWrite ) ||
					m_midiDev.open( QIODevice::ReadOnly ) )
	{
		start( QThread::LowPriority );
	}
}

MidiOss::~MidiOss()
{
	if( isRunning() )
	{
		m_quit = true;
		wait( 1000 );
		terminate();
	}
}

QString MidiOss::probeDevice()
{
	QString dev = ConfigManager::inst()->value( "midioss", "device" );
	if( dev.isEmpty() )
	{
		if( getenv( "MIDIDEV" ) != NULL )
		{
			return getenv( "MIDIDEV" );
		}
#ifdef __NetBSD__
		return "/dev/rmidi0";
#else
		return "/dev/midi";
#endif
	}
	return dev;
}

void MidiOss::sendByte( const unsigned char c )
{
	m_midiDev.putChar( c );
}

void MidiOss::run()
{
	while( m_quit == false && m_midiDev.isOpen() )
	{
		char c;
		if( !m_midiDev.getChar( &c ) )
		{
			continue;
		}
		parseData( c );
	}
}

#endif


code:
/*
 * MidiPort.cpp - abstraction of MIDI-ports which are part of LMMS's MIDI-
 *                sequencing system
 *
 * 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 <QDomElement>

#include "MidiPort.h"
#include "MidiClient.h"
#include "MidiDummy.h"
#include "Note.h"
#include "Song.h"

static MidiDummy s_dummyClient;

MidiPort::MidiPort( const QString& name,
					MidiClient* client,
					MidiEventProcessor* eventProcessor,
					Model* parent,
					Mode mode ) :
	Model( parent ),
	m_readablePortsMenu( NULL ),
	m_writablePortsMenu( NULL ),
	m_midiClient( client ),
	m_midiEventProcessor( eventProcessor ),
	m_mode( mode ),
	m_inputChannelModel( 0, 0, MidiChannelCount, this, tr( "Input channel" ) ),
	m_outputChannelModel( 1, 0, MidiChannelCount, this, tr( "Output channel" ) ),
	m_inputControllerModel( 0, 0, MidiControllerCount, this, tr( "Input controller" ) ),
	m_outputControllerModel( 0, 0, MidiControllerCount, this, tr( "Output controller" ) ),
	m_fixedInputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed input velocity" ) ),
	m_fixedOutputVelocityModel( -1, -1, MidiMaxVelocity, this, tr( "Fixed output velocity" ) ),
	m_fixedOutputNoteModel( -1, -1, MidiMaxKey, this, tr( "Fixed output note" ) ),
	m_outputProgramModel( 1, 1, MidiProgramCount, this, tr( "Output MIDI program" ) ),
	m_baseVelocityModel( MidiMaxVelocity/2, 1, MidiMaxVelocity, this, tr( "Base velocity" ) ),
	m_readableModel( false, this, tr( "Receive MIDI-events" ) ),
	m_writableModel( false, this, tr( "Send MIDI-events" ) )
{
	m_midiClient->addPort( this );

	m_readableModel.setValue( m_mode == Input || m_mode == Duplex );
	m_writableModel.setValue( m_mode == Output || m_mode == Duplex );

	connect( &m_readableModel, SIGNAL( dataChanged() ),
			this, SLOT( updateMidiPortMode() ), Qt::DirectConnection );
	connect( &m_writableModel, SIGNAL( dataChanged() ),
			this, SLOT( updateMidiPortMode() ), Qt::DirectConnection );
	connect( &m_outputProgramModel, SIGNAL( dataChanged() ),
			this, SLOT( updateOutputProgram() ), Qt::DirectConnection );

	// when using with non-raw-clients we can provide buttons showing
	// our port-menus when being clicked
	if( m_midiClient->isRaw() == false )
	{
		updateReadablePorts();
		updateWritablePorts();

		// we want to get informed about port-changes!
		m_midiClient->connectRPChanged( this, SLOT( updateReadablePorts() ) );
		m_midiClient->connectWPChanged( this, SLOT( updateWritablePorts() ) );
	}

	updateMidiPortMode();
}

MidiPort::~MidiPort()
{
	// unsubscribe ports
	m_readableModel.setValue( false );
	m_writableModel.setValue( false );

	// and finally unregister ourself
	m_midiClient->removePort( this );
}

void MidiPort::setName( const QString& name )
{
	setDisplayName( name );
	m_midiClient->applyPortName( this );
}

void MidiPort::setMode( Mode mode )
{
	m_mode = mode;
	m_midiClient->applyPortMode( this );
}

void MidiPort::processInEvent( const MidiEvent& event, const TimePos& time )
{
	// mask event
	if( isInputEnabled() &&
		( inputChannel() == 0 || inputChannel()-1 == event.channel() ) )
	{
		MidiEvent inEvent = event;
		if( event.type() == MidiNoteOn ||
			event.type() == MidiNoteOff ||
			event.type() == MidiKeyPressure )
		{
			if( inEvent.key() < 0 || inEvent.key() >= NumKeys )
			{
				return;
			}

			if( fixedInputVelocity() >= 0 && inEvent.velocity() > 0 )
			{
				inEvent.setVelocity( fixedInputVelocity() );
			}
		}

		m_midiEventProcessor->processInEvent( inEvent, time );
	}
}

void MidiPort::processOutEvent( const MidiEvent& event, const TimePos& time )
{
	// When output is enabled, route midi events if the selected channel matches
	// the event channel or if there's no selected channel (value 0, represented by "--")
	if( isOutputEnabled() && ( outputChannel() == 0 || realOutputChannel() == event.channel() ) )
	{
		MidiEvent outEvent = event;

		if( fixedOutputVelocity() >= 0 && event.velocity() > 0 &&
			( event.type() == MidiNoteOn || event.type() == MidiKeyPressure ) )
		{
			outEvent.setVelocity( fixedOutputVelocity() );
		}

		m_midiClient->processOutEvent( outEvent, time, this );
	}
}

void MidiPort::saveSettings( QDomDocument& doc, QDomElement& thisElement )
{
	m_inputChannelModel.saveSettings( doc, thisElement, "inputchannel" );
	m_outputChannelModel.saveSettings( doc, thisElement, "outputchannel" );
	m_inputControllerModel.saveSettings( doc, thisElement, "inputcontroller" );
	m_outputControllerModel.saveSettings( doc, thisElement, "outputcontroller" );
	m_fixedInputVelocityModel.saveSettings( doc, thisElement, "fixedinputvelocity" );
	m_fixedOutputVelocityModel.saveSettings( doc, thisElement, "fixedoutputvelocity" );
	m_fixedOutputNoteModel.saveSettings( doc, thisElement, "fixedoutputnote" );
	m_outputProgramModel.saveSettings( doc, thisElement, "outputprogram" );
	m_baseVelocityModel.saveSettings( doc, thisElement, "basevelocity" );
	m_readableModel.saveSettings( doc, thisElement, "readable" );
	m_writableModel.saveSettings( doc, thisElement, "writable" );

	if( isInputEnabled() )
	{
		QString rp;
		for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it )
		{
			if( it.value() )
			{
				rp += it.key() + ",";
			}
		}
		// cut off comma
		if( rp.length() > 0 )
		{
			rp.truncate( rp.length() - 1 );
		}
		thisElement.setAttribute( "inports", rp );
	}

	if( isOutputEnabled() )
	{
		QString wp;
		for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it )
		{
			if( it.value() )
			{
				wp += it.key() + ",";
			}
		}
		// cut off comma
		if( wp.length() > 0 )
		{
			wp.truncate( wp.length() - 1 );
		}
		thisElement.setAttribute( "outports", wp );
	}
}

void MidiPort::loadSettings( const QDomElement& thisElement )
{
	m_inputChannelModel.loadSettings( thisElement, "inputchannel" );
	m_outputChannelModel.loadSettings( thisElement, "outputchannel" );
	m_inputControllerModel.loadSettings( thisElement, "inputcontroller" );
	m_outputControllerModel.loadSettings( thisElement, "outputcontroller" );
	m_fixedInputVelocityModel.loadSettings( thisElement, "fixedinputvelocity" );
	m_fixedOutputVelocityModel.loadSettings( thisElement, "fixedoutputvelocity" );
	m_outputProgramModel.loadSettings( thisElement, "outputprogram" );
	m_baseVelocityModel.loadSettings( thisElement, "basevelocity" );
	m_readableModel.loadSettings( thisElement, "readable" );
	m_writableModel.loadSettings( thisElement, "writable" );

	// restore connections

	if( isInputEnabled() )
	{
		QStringList rp = thisElement.attribute( "inports" ).split( ',' );
		for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it )
		{
			if( it.value() != ( rp.indexOf( it.key() ) != -1 ) )
			{
				subscribeReadablePort( it.key() );
			}
		}
		emit readablePortsChanged();
	}

	if( isOutputEnabled() )
	{
		QStringList wp = thisElement.attribute( "outports" ).split( ',' );
		for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it )
		{
			if( it.value() != ( wp.indexOf( it.key() ) != -1 ) )
			{
				subscribeWritablePort( it.key() );
			}
		}
		emit writablePortsChanged();
	}

	if( thisElement.hasAttribute( "basevelocity" ) == false )
	{
		// for projects created by LMMS < 0.9.92 there's no value for the base
		// velocity and for compat reasons we have to stick with maximum velocity
		// which did not allow note volumes > 100%
		m_baseVelocityModel.setValue( MidiMaxVelocity );
	}
}

void MidiPort::subscribeReadablePort( const QString& port, bool subscribe )
{
	m_readablePorts[port] = subscribe;

	// make sure, MIDI-port is configured for input
	if( subscribe == true && !isInputEnabled() )
	{
		m_readableModel.setValue( true );
	}

	m_midiClient->subscribeReadablePort( this, port, subscribe );
}

void MidiPort::subscribeWritablePort( const QString& port, bool subscribe )
{
	m_writablePorts[port] = subscribe;

	// make sure, MIDI-port is configured for output
	if( subscribe == true && !isOutputEnabled() )
	{
		m_writableModel.setValue( true );
	}
	m_midiClient->subscribeWritablePort( this, port, subscribe );
}

void MidiPort::updateMidiPortMode()
{
	// this small lookup-table makes everything easier
	static const Modes modeTable[2][2] =
	{
		{ Disabled, Output },
		{ Input, Duplex }
	} ;
	setMode( modeTable[m_readableModel.value()][m_writableModel.value()] );

	// check whether we have to dis-check items in connection-menu
	if( !isInputEnabled() )
	{
		for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it )
		{
			// subscribed?
			if( it.value() )
			{
				subscribeReadablePort( it.key(), false );
			}
		}
	}

	if( !isOutputEnabled() )
	{
		for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it )
		{
			// subscribed?
			if( it.value() )
			{
				subscribeWritablePort( it.key(), false );
			}
		}
	}

	emit readablePortsChanged();
	emit writablePortsChanged();
	emit modeChanged();

	if( Engine::getSong() )
	{
		Engine::getSong()->setModified();
	}
}

void MidiPort::updateReadablePorts()
{
	// first save all selected ports
	QStringList selectedPorts;
	for( Map::ConstIterator it = m_readablePorts.begin(); it != m_readablePorts.end(); ++it )
	{
		if( it.value() )
		{
			selectedPorts.push_back( it.key() );
		}
	}

	m_readablePorts.clear();
	const QStringList& wp = m_midiClient->readablePorts();
	// now insert new ports and restore selections
	for( QStringList::ConstIterator it = wp.begin(); it != wp.end(); ++it )
	{
		m_readablePorts[*it] = ( selectedPorts.indexOf( *it ) != -1 );
	}

	emit readablePortsChanged();
}

void MidiPort::updateWritablePorts()
{
	// first save all selected ports
	QStringList selectedPorts;
	for( Map::ConstIterator it = m_writablePorts.begin(); it != m_writablePorts.end(); ++it )
	{
		if( it.value() )
		{
			selectedPorts.push_back( it.key() );
		}
	}

	m_writablePorts.clear();
	const QStringList & wp = m_midiClient->writablePorts();
	// now insert new ports and restore selections
	for( QStringList::ConstIterator it = wp.begin(); it != wp.end(); ++it )
	{
		m_writablePorts[*it] = ( selectedPorts.indexOf( *it ) != -1 );
	}

	emit writablePortsChanged();
}

void MidiPort::updateOutputProgram()
{
	processOutEvent( MidiEvent( MidiProgramChange, realOutputChannel(), outputProgram()-1 ) );
}

void MidiPort::invalidateCilent()
{
	m_midiClient = &s_dummyClient;
}


code:
/*
 * MidiSndio.cpp - base-class that implements sndio midi support
 *
 * Copyright (c) 2010-2016 jackmsr@openbsd.net
 * Copyright (c) 2016-2017 David Carlier <devnexen@gmail.com>
 *
 * 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 "MidiSndio.h"

#ifdef LMMS_HAVE_SNDIO

#include <QLabel>
#include <QLineEdit>

#ifdef LMMS_HAVE_STDLIB_H
#include <stdlib.h>
#endif

#include <poll.h>

#include "ConfigManager.h"
#include "gui_templates.h"

MidiSndio::MidiSndio( void ) :
	MidiClientRaw(),
	m_quit( false )
{
	QString dev = probeDevice();

	if (dev == "")
	{
		m_hdl = mio_open( NULL, MIO_IN | MIO_OUT, 0 );
	}
	else
	{
		m_hdl = mio_open( dev.toLatin1().constData(), MIO_IN | MIO_OUT, 0 );
	}

	if( m_hdl == NULL )
	{
		printf( "sndio: failed opening sndio midi device\n" );
		return;
	}

	start( QThread::LowPriority );
}

MidiSndio::~MidiSndio()
{
	if( isRunning() )
	{
		m_quit = true;
		wait( 1000 );
		terminate();
	}
}

QString MidiSndio::probeDevice( void )
{
	QString dev = ConfigManager::inst()->value( "MidiSndio", "device" );

	return dev ;
}

void MidiSndio::sendByte( const unsigned char c )
{
	mio_write( m_hdl, &c, 1 );
}

void MidiSndio::run( void )
{
	struct pollfd pfd;
	nfds_t nfds;
	char buf[0x100], *p;
	size_t n;
	int ret;
	while( m_quit == false && m_hdl )
	{
		nfds = mio_pollfd( m_hdl, &pfd, POLLIN );
		ret = poll( &pfd, nfds, 100 );
		if ( ret < 0 )
			break;
		if ( !ret || !( mio_revents( m_hdl, &pfd ) & POLLIN ) )
			continue;
		n = mio_read( m_hdl, buf, sizeof(buf) );
		if ( !n )
		{
			break;
		}
		for (p = buf; n > 0; n--, p++)
		{
			parseData( *p );
		}
	}
}

#endif	/* LMMS_HAVE_SNDIO */


code:
/*
 * MidiWinMM.cpp - WinMM MIDI client
 *
 * 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 "MidiWinMM.h"
#include "Note.h"

#ifdef LMMS_BUILD_WIN32

MidiWinMM::MidiWinMM() :
	MidiClient(),
	m_inputDevices(),
	m_outputDevices(),
	m_inputSubs(),
	m_outputSubs()
{
	openDevices();
}

MidiWinMM::~MidiWinMM()
{
	closeDevices();
}

void MidiWinMM::processOutEvent( const MidiEvent& event, const TimePos& time, const MidiPort* port )
{
	const DWORD shortMsg = ( event.type() + event.channel() ) +
				( ( event.param( 0 ) & 0xff ) << 8 ) +
				( ( event.param( 1 ) & 0xff ) << 16 );

	QStringList outDevs;
	for( SubMap::ConstIterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
	{
		for( MidiPortList::ConstIterator jt = it.value().begin(); jt != it.value().end(); ++jt )
		{
			if( *jt == port )
			{
				outDevs += it.key();
				break;
			}
		}
	}

	for( QMap<HMIDIOUT, QString>::Iterator it = m_outputDevices.begin(); it != m_outputDevices.end(); ++it )
	{
		if( outDevs.contains( *it ) )
		{
			midiOutShortMsg( it.key(), shortMsg );
		}
	}
}

void MidiWinMM::applyPortMode( MidiPort* port )
{
	// make sure no subscriptions exist which are not possible with
	// current port-mode
	if( !port->isInputEnabled() )
	{
		for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it )
		{
			it.value().removeAll( port );
		}
	}

	if( !port->isOutputEnabled() )
	{
		for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
		{
			it.value().removeAll( port );
		}
	}
}

void MidiWinMM::removePort( MidiPort* port )
{
	for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it )
	{
		it.value().removeAll( port );
	}

	for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
	{
		it.value().removeAll( port );
	}

	MidiClient::removePort( port );
}

QString MidiWinMM::sourcePortName( const MidiEvent& event ) const
{
	if( event.sourcePort() )
	{
		return m_inputDevices.value( *static_cast<const HMIDIIN *>( event.sourcePort() ) );
	}

	return MidiClient::sourcePortName( event );
}

void MidiWinMM::subscribeReadablePort( MidiPort* port, const QString& dest, bool subscribe )
{
	if( subscribe && port->isInputEnabled() == false )
	{
		qWarning( "port %s can't be (un)subscribed!\n", port->displayName().toLatin1().constData() );
		return;
	}

	m_inputSubs[dest].removeAll( port );
	if( subscribe )
	{
		m_inputSubs[dest].push_back( port );
	}
}

void MidiWinMM::subscribeWritablePort( MidiPort* port, const QString& dest, bool subscribe )
{
	if( subscribe && port->isOutputEnabled() == false )
	{
		qWarning( "port %s can't be (un)subscribed!\n", port->displayName().toLatin1().constData() );
		return;
	}

	m_outputSubs[dest].removeAll( port );
	if( subscribe )
	{
		m_outputSubs[dest].push_back( port );
	}
}

void WINAPI CALLBACK MidiWinMM::inputCallback( HMIDIIN hm, UINT msg, DWORD_PTR inst, DWORD_PTR param1, DWORD_PTR param2 )
{
	if( msg == MIM_DATA )
	{
		( (MidiWinMM *) inst )->handleInputEvent( hm, param1 );
	}
}

void MidiWinMM::handleInputEvent( HMIDIIN hm, DWORD ev )
{
	const int cmd = ev & 0xff;
	if( cmd == MidiActiveSensing )
	{
		return;
	}
	const int par1 = ( ev >> 8 ) & 0xff;
	const int par2 = ev >> 16;
	const MidiEventTypes cmdtype = static_cast<MidiEventTypes>( cmd & 0xf0 );
	const int chan = cmd & 0x0f;

	const QString d = m_inputDevices.value( hm );
	if( d.isEmpty() || !m_inputSubs.contains( d ) )
	{
		return;
	}

	const MidiPortList & l = m_inputSubs[d];
	for( MidiPortList::ConstIterator it = l.begin(); it != l.end(); ++it )
	{
		switch( cmdtype )
		{
			case MidiNoteOn:
			case MidiNoteOff:
			case MidiKeyPressure:
				( *it )->processInEvent( MidiEvent( cmdtype, chan, par1 - KeysPerOctave, par2 & 0xff, &hm ) );
				break;

			case MidiControlChange:
			case MidiProgramChange:
			case MidiChannelPressure:
				( *it )->processInEvent( MidiEvent( cmdtype, chan, par1, par2 & 0xff, &hm ) );
				break;

			case MidiPitchBend:
				( *it )->processInEvent( MidiEvent( cmdtype, chan, par1 + par2*128, 0, &hm ) );
				break;

			default:
				qWarning( "MidiWinMM: unhandled input event %d\n", cmdtype );
				break;
		}
	}
}

void MidiWinMM::updateDeviceList()
{
	closeDevices();
	openDevices();

	emit readablePortsChanged();
	emit writablePortsChanged();
}

void MidiWinMM::closeDevices()
{
	m_inputSubs.clear();
	m_outputSubs.clear();

	QMapIterator<HMIDIIN, QString> i( m_inputDevices );

	HMIDIIN hInDev;
	while( i.hasNext() )
	{
		hInDev = i.next().key();
		midiInReset( hInDev );
		midiInClose( hInDev );
	}

	QMapIterator<HMIDIOUT, QString> o( m_outputDevices );
	while( o.hasNext() )
	{
		midiOutClose( o.next().key() );
	}

	m_inputDevices.clear();
	m_outputDevices.clear();
}

void MidiWinMM::openDevices()
{
	m_inputDevices.clear();
	for( unsigned int i = 0; i < midiInGetNumDevs(); ++i )
	{
		MIDIINCAPS c;
		midiInGetDevCaps( i, &c, sizeof( c ) );
		HMIDIIN hm = 0;
		MMRESULT res = midiInOpen( &hm, i, (DWORD_PTR) &inputCallback,
						(DWORD_PTR) this,
							CALLBACK_FUNCTION );
		if( res == MMSYSERR_NOERROR )
		{
			m_inputDevices[hm] = qstrdup( c.szPname );
			midiInStart( hm );
		}
	}

	m_outputDevices.clear();
	for( unsigned int i = 0; i < midiOutGetNumDevs(); ++i )
	{
		MIDIOUTCAPS c;
		midiOutGetDevCaps( i, &c, sizeof( c ) );
		HMIDIOUT hm = 0;
		MMRESULT res = midiOutOpen( &hm, i, 0, 0, CALLBACK_NULL );
		if( res == MMSYSERR_NOERROR )
		{
			m_outputDevices[hm] = qstrdup( c.szPname );
		}
	}
}

#endif


code:
/*
 * AboutDialog.cpp - implementation of about-dialog
 *
 * Copyright (c) 2004-2008 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 "lmmsversion.h"
#include "AboutDialog.h"
#include "embed.h"
#include "versioninfo.h"

AboutDialog::AboutDialog(QWidget* parent) :
	QDialog(parent),
	Ui::AboutDialog()
{
	setupUi( this );

	iconLabel->setPixmap( embed::getIconPixmap( "icon", 64, 64 ) );

	versionLabel->setText( versionLabel->text().
					arg( LMMS_VERSION ).
					arg( PLATFORM ).
					arg( MACHINE ).
					arg( QT_VERSION_STR ).
					arg( COMPILER_VERSION ) );
	versionLabel->setTextInteractionFlags(
					versionLabel->textInteractionFlags() |
					Qt::TextSelectableByMouse );

	copyrightLabel->setText( copyrightLabel->text().
					arg( LMMS_PROJECT_COPYRIGHT ) );

	authorLabel->setPlainText( embed::getText( "AUTHORS" ) );

	licenseLabel->setPlainText( embed::getText( "LICENSE.txt" ) );

	involvedLabel->setPlainText( embed::getText( "CONTRIBUTORS" ) );
}


code:
/*
 * ActionGroup.cpp - wrapper around QActionGroup to provide a more useful triggered(int) slot
 *
 * Copyright (c) 2014 Lukas W <lukaswhl/at/gmail.com>
 *
 * 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 "ActionGroup.h"

ActionGroup::ActionGroup(QObject* parent) : QActionGroup(parent)
{
	connect(this, SIGNAL(triggered(QAction*)), this, SLOT(actionTriggered_(QAction*)));
}

QAction* ActionGroup::addAction(QAction* a)
{
	a->setCheckable(true);

	return QActionGroup::addAction(a);
}

QAction* ActionGroup::addAction(const QString& text)
{
	return addAction(new QAction(text, this));
}

QAction* ActionGroup::addAction(const QIcon& icon, const QString& text)
{
	return addAction(new QAction(icon, text, this));
}

void ActionGroup::actionTriggered_(QAction* action)
{
	Q_ASSERT(action != 0);
	Q_ASSERT(actions().contains(action));

	emit triggered(actions().indexOf(action));
}


code:
/*
 * AudioAlsaSetupWidget.cpp - Implements a setup widget for ALSA-PCM-output
 *
 * Copyright (c) 2004-2015 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 <QComboBox>
#include <QLabel>

#include "AudioAlsaSetupWidget.h"

#ifdef LMMS_HAVE_ALSA

#include "ConfigManager.h"
#include "LcdSpinBox.h"
#include "gui_templates.h"

AudioAlsaSetupWidget::AudioAlsaSetupWidget( QWidget * _parent ) :
	AudioDeviceSetupWidget( AudioAlsa::name(), _parent ),
	m_selectedDevice(-1)
{
	m_deviceInfos = AudioAlsa::getAvailableDevices();

	QString deviceText = ConfigManager::inst()->value( "audioalsa", "device" );

	m_deviceComboBox = new QComboBox(this);
	for (size_t i = 0; i < m_deviceInfos.size(); ++i)
	{
		AudioAlsa::DeviceInfo const & currentDeviceInfo = m_deviceInfos[i];
		QString comboBoxText = currentDeviceInfo.getDeviceName();
		m_deviceComboBox->addItem(comboBoxText, QVariant(static_cast<uint>(i)));

		QString toolTipText = currentDeviceInfo.getDeviceDescription();
		m_deviceComboBox->setItemData(i, toolTipText, Qt::ToolTipRole);

		if (currentDeviceInfo.getDeviceName() == deviceText)
		{
			m_deviceComboBox->setCurrentIndex(static_cast<int>(i));
		}
	}

	m_selectedDevice = m_deviceComboBox->currentIndex();

	m_deviceComboBox->setGeometry( 10, 20, 160, 20 );
	connect(m_deviceComboBox,
			SIGNAL(currentIndexChanged(int)),
			SLOT(onCurrentIndexChanged(int)));

	QLabel * dev_lbl = new QLabel( tr( "DEVICE" ), this );
	dev_lbl->setFont( pointSize<7>( dev_lbl->font() ) );
	dev_lbl->setGeometry( 10, 40, 160, 10 );

	LcdSpinBoxModel * m = new LcdSpinBoxModel( /* this */ );
	m->setRange( DEFAULT_CHANNELS, SURROUND_CHANNELS );
	m->setStep( 2 );
	m->setValue( ConfigManager::inst()->value( "audioalsa",
							"channels" ).toInt() );

	m_channels = new LcdSpinBox( 1, this );
	m_channels->setModel( m );
	m_channels->setLabel( tr( "CHANNELS" ) );
	m_channels->move( 180, 20 );

}

AudioAlsaSetupWidget::~AudioAlsaSetupWidget()
{
	delete m_channels->model();
}

void AudioAlsaSetupWidget::saveSettings()
{
	QString deviceText;

	if (m_selectedDevice != -1)
	{
		AudioAlsa::DeviceInfo const & selectedDevice = m_deviceInfos[m_selectedDevice];
		deviceText = selectedDevice.getDeviceName();
	}

	ConfigManager::inst()->setValue( "audioalsa", "device", deviceText );
	ConfigManager::inst()->setValue( "audioalsa", "channels",
				QString::number( m_channels->value<int>() ) );
}

void AudioAlsaSetupWidget::onCurrentIndexChanged(int index)
{
	m_selectedDevice = index;
}

#endif


code:
/*
 * AudioDeviceSetupWidget.cpp - Base class for audio device setup widgets
 *
 * Copyright (c) 2004-2015 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 "AudioDeviceSetupWidget.h"

AudioDeviceSetupWidget::AudioDeviceSetupWidget(const QString & caption, QWidget * parent) :
	TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent)
{
}

AudioDeviceSetupWidget::~AudioDeviceSetupWidget()
{
}

void AudioDeviceSetupWidget::show()
{
	parentWidget()->show();
	QWidget::show();
}


code:
/*
 * AutomatableModelView.cpp - implementation of AutomatableModelView
 *
 * 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 <QApplication>
#include <QMenu>
#include <QMouseEvent>

#include "AutomatableModelView.h"
#include "AutomationPattern.h"
#include "ControllerConnectionDialog.h"
#include "ControllerConnection.h"
#include "embed.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "StringPairDrag.h"
#include "Clipboard.h"

#include "AutomationEditor.h"

static float floatFromClipboard(bool* ok=nullptr);

AutomatableModelView::AutomatableModelView( ::Model* model, QWidget* _this ) :
	ModelView( model, _this ),
	m_conversionFactor( 1.0 )
{
	widget()->setAcceptDrops( true );
	widget()->setCursor( QCursor( embed::getIconPixmap( "hand" ), 3, 3 ) );
}

void AutomatableModelView::addDefaultActions( QMenu* menu )
{
	AutomatableModel* model = modelUntyped();

	AutomatableModelViewSlots* amvSlots = new AutomatableModelViewSlots( this, menu );

	menu->addAction( embed::getIconPixmap( "reload" ),
						AutomatableModel::tr( "&Reset (%1%2)" ).
							arg( model->initValue<float>() * m_conversionFactor ).
							arg( m_unit ),
						model, SLOT( reset() ) );

	menu->addSeparator();
	menu->addAction( embed::getIconPixmap( "edit_copy" ),
						AutomatableModel::tr( "&Copy value (%1%2)" ).
							arg( model->value<float>() * m_conversionFactor ).
							arg( m_unit ),
						amvSlots, SLOT( copyToClipboard() ) );

	bool canPaste = true;
	const float valueToPaste = floatFromClipboard(&canPaste);
	const QString pasteDesc = canPaste ?
					AutomatableModel::tr( "&Paste value (%1%2)").
						arg( valueToPaste ).
						arg( m_unit )
					: AutomatableModel::tr( "&Paste value");
	QAction* pasteAction = menu->addAction( embed::getIconPixmap( "edit_paste" ),
						pasteDesc, amvSlots, SLOT( pasteFromClipboard() ) );
	pasteAction->setEnabled(canPaste);

	menu->addSeparator();

	menu->addAction( embed::getIconPixmap( "automation" ),
						AutomatableModel::tr( "Edit song-global automation" ),
							amvSlots,
							SLOT( editSongGlobalAutomation() ) );

	menu->addAction( QPixmap(),
						AutomatableModel::tr( "Remove song-global automation" ),
						amvSlots,
						SLOT( removeSongGlobalAutomation() ) );

	menu->addSeparator();

	if( model->hasLinkedModels() )
	{
		menu->addAction( embed::getIconPixmap( "edit-delete" ),
							AutomatableModel::tr( "Remove all linked controls" ),
							amvSlots, SLOT( unlinkAllModels() ) );
		menu->addSeparator();
	}

	QString controllerTxt;
	if( model->controllerConnection() )
	{
		Controller* cont = model->controllerConnection()->getController();
		if( cont )
		{
			controllerTxt = AutomatableModel::tr( "Connected to %1" ).arg( cont->name() );
		}
		else
		{
			controllerTxt = AutomatableModel::tr( "Connected to controller" );
		}

		QMenu* contMenu = menu->addMenu( embed::getIconPixmap( "controller" ), controllerTxt );

		contMenu->addAction( embed::getIconPixmap( "controller" ),
								AutomatableModel::tr("Edit connection..."),
								amvSlots, SLOT( execConnectionDialog() ) );
		contMenu->addAction( embed::getIconPixmap( "cancel" ),
								AutomatableModel::tr("Remove connection"),
								amvSlots, SLOT( removeConnection() ) );
	}
	else
	{
		menu->addAction( embed::getIconPixmap( "controller" ),
							AutomatableModel::tr("Connect to controller..."),
							amvSlots, SLOT( execConnectionDialog() ) );
	}
}

void AutomatableModelView::setModel( Model* model, bool isOldModelValid )
{
	ModelView::setModel( model, isOldModelValid );
}

// Unsets the current model by setting a dummy empty model. The dummy model is marked as
// "defaultConstructed", so the next call to setModel will delete it.
void AutomatableModelView::unsetModel()
{
	if (dynamic_cast<FloatModelView*>(this))
	{
		setModel(new FloatModel(0, 0, 0, 1, nullptr, QString(), true));
	}
	else if (dynamic_cast<IntModelView*>(this))
	{
		setModel(new IntModel(0, 0, 0, nullptr, QString(), true));
	}
	else if (dynamic_cast<BoolModelView*>(this))
	{
		setModel(new BoolModel(false, nullptr, QString(), true));
	}
	else
	{
		ModelView::unsetModel();
	}
}

void AutomatableModelView::mousePressEvent( QMouseEvent* event )
{
	if( event->button() == Qt::LeftButton && event->modifiers() & Qt::ControlModifier )
	{
		new StringPairDrag( "automatable_model", QString::number( modelUntyped()->id() ), QPixmap(), widget() );
		event->accept();
	}
	else if( event->button() == Qt::MidButton )
	{
		modelUntyped()->reset();
	}
}

void AutomatableModelView::setConversionFactor( float factor )
{
	if( factor != 0.0 )
	{
		m_conversionFactor = factor;
	}
}

float AutomatableModelView::getConversionFactor()
{
	return m_conversionFactor;
}

AutomatableModelViewSlots::AutomatableModelViewSlots( AutomatableModelView* amv, QObject* parent ) :
	QObject(),
	m_amv( amv )
{
	connect( parent, SIGNAL( destroyed() ), this, SLOT( deleteLater() ), Qt::QueuedConnection );
}

void AutomatableModelViewSlots::execConnectionDialog()
{
	// TODO[pg]: Display a dialog with list of controllers currently in the song
	// in addition to any system MIDI controllers
	AutomatableModel* m = m_amv->modelUntyped();

	m->displayName();
	ControllerConnectionDialog d( gui->mainWindow(), m );

	if( d.exec() == 1 )
	{
		// Actually chose something
		if( d.chosenController() )
		{
			// Update
			if( m->controllerConnection() )
			{
				m->controllerConnection()->setController( d.chosenController() );
			}
			// New
			else
			{
				ControllerConnection* cc = new ControllerConnection( d.chosenController() );
				m->setControllerConnection( cc );
				//cc->setTargetName( m->displayName() );
			}
		}
		// no controller, so delete existing connection
		else
		{
			removeConnection();
		}
	}
}

void AutomatableModelViewSlots::removeConnection()
{
	AutomatableModel* m = m_amv->modelUntyped();

	if( m->controllerConnection() )
	{
		delete m->controllerConnection();
		m->setControllerConnection( NULL );
	}
}

void AutomatableModelViewSlots::editSongGlobalAutomation()
{
	gui->automationEditor()->open(
				AutomationPattern::globalAutomationPattern(m_amv->modelUntyped())
	);
}

void AutomatableModelViewSlots::removeSongGlobalAutomation()
{
	delete AutomationPattern::globalAutomationPattern( m_amv->modelUntyped() );
}

void AutomatableModelViewSlots::unlinkAllModels()
{
	m_amv->modelUntyped()->unlinkAllModels();
}

void AutomatableModelViewSlots::copyToClipboard()
{
	// For copyString() and MimeType enum class
	using namespace Clipboard;

	copyString( QString::number( m_amv->value<float>() * m_amv->getConversionFactor() ), MimeType::Default );
}

void AutomatableModelViewSlots::pasteFromClipboard()
{
	bool isNumber = false;
	const float number = floatFromClipboard(&isNumber);
	if (isNumber) {
		m_amv->modelUntyped()->setValue(number / m_amv->getConversionFactor());
	}
}

/// Attempt to parse a float from the clipboard
static float floatFromClipboard(bool* ok)
{
	// For getString() and MimeType enum class
	using namespace Clipboard;

	return getString( MimeType::Default ).toFloat(ok);
}


code:
/*
 * AutomationPatternView.cpp - implementation of view for AutomationPattern
 *
 * Copyright (c) 2008-2010 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 "AutomationPatternView.h"

#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QMenu>

#include "AutomationEditor.h"
#include "embed.h"
#include "GuiApplication.h"
#include "gui_templates.h"
#include "ProjectJournal.h"
#include "RenameDialog.h"
#include "StringPairDrag.h"
#include "TextFloat.h"
#include "ToolTip.h"

#include "Engine.h"

QPixmap * AutomationPatternView::s_pat_rec = NULL;

AutomationPatternView::AutomationPatternView( AutomationPattern * _pattern,
						TrackView * _parent ) :
	TrackContentObjectView( _pattern, _parent ),
	m_pat( _pattern ),
	m_paintPixmap()
{
	connect( m_pat, SIGNAL( dataChanged() ),
			this, SLOT( update() ) );
	connect( gui->automationEditor(), SIGNAL( currentPatternChanged() ),
			this, SLOT( update() ) );

	setAttribute( Qt::WA_OpaquePaintEvent, true );

	ToolTip::add(this, m_pat->name());
	setStyle( QApplication::style() );

	if( s_pat_rec == NULL ) { s_pat_rec = new QPixmap( embed::getIconPixmap(
							"pat_rec" ) ); }

	update();
}

AutomationPatternView::~AutomationPatternView()
{
}

void AutomationPatternView::openInAutomationEditor()
{
	if(gui) gui->automationEditor()->open(m_pat);
}

void AutomationPatternView::update()
{
	ToolTip::add(this, m_pat->name());

	TrackContentObjectView::update();
}

void AutomationPatternView::resetName()
{
	m_pat->setName( QString() );
}

void AutomationPatternView::changeName()
{
	QString s = m_pat->name();
	RenameDialog rename_dlg( s );
	rename_dlg.exec();
	m_pat->setName( s );
	update();
}

void AutomationPatternView::disconnectObject( QAction * _a )
{
	JournallingObject * j = Engine::projectJournal()->
				journallingObject( _a->data().toInt() );
	if( j && dynamic_cast<AutomatableModel *>( j ) )
	{
		float oldMin = m_pat->getMin();
		float oldMax = m_pat->getMax();

		m_pat->m_objects.erase( std::find( m_pat->m_objects.begin(),
					m_pat->m_objects.end(),
				dynamic_cast<AutomatableModel *>( j ) ) );
		update();

		//If automation editor is opened, update its display after disconnection
		if( gui->automationEditor() )
		{
			gui->automationEditor()->m_editor->updateAfterPatternChange();
		}

		//if there is no more connection connected to the AutomationPattern
		if( m_pat->m_objects.size() == 0 )
		{
			//scale the points to fit the new min. and max. value
			this->scaleTimemapToFit( oldMin, oldMax );
		}
	}
}

void AutomationPatternView::toggleRecording()
{
	m_pat->setRecording( ! m_pat->isRecording() );
	update();
}

void AutomationPatternView::flipY()
{
	m_pat->flipY( m_pat->getMin(), m_pat->getMax() );
	update();
}

void AutomationPatternView::flipX()
{
	m_pat->flipX( m_pat->length() );
	update();
}

void AutomationPatternView::constructContextMenu( QMenu * _cm )
{
	QAction * a = new QAction( embed::getIconPixmap( "automation" ),
				tr( "Open in Automation editor" ), _cm );
	_cm->insertAction( _cm->actions()[0], a );
	connect(a, SIGNAL(triggered()), this, SLOT(openInAutomationEditor()));
	_cm->insertSeparator( _cm->actions()[1] );

	_cm->addSeparator();

	_cm->addAction( embed::getIconPixmap( "edit_erase" ),
			tr( "Clear" ), m_pat, SLOT( clear() ) );
	_cm->addSeparator();

	_cm->addAction( embed::getIconPixmap( "reload" ), tr( "Reset name" ),
						this, SLOT( resetName() ) );
	_cm->addAction( embed::getIconPixmap( "edit_rename" ),
						tr( "Change name" ),
						this, SLOT( changeName() ) );
	_cm->addAction( embed::getIconPixmap( "record" ),
						tr( "Set/clear record" ),
						this, SLOT( toggleRecording() ) );
	_cm->addAction( embed::getIconPixmap( "flip_y" ),
						tr( "Flip Vertically (Visible)" ),
						this, SLOT( flipY() ) );
	_cm->addAction( embed::getIconPixmap( "flip_x" ),
						tr( "Flip Horizontally (Visible)" ),
						this, SLOT( flipX() ) );
	if( !m_pat->m_objects.isEmpty() )
	{
		_cm->addSeparator();
		QMenu * m = new QMenu( tr( "%1 Connections" ).
				arg( m_pat->m_objects.count() ), _cm );
		for( AutomationPattern::objectVector::iterator it =
						m_pat->m_objects.begin();
					it != m_pat->m_objects.end(); ++it )
		{
			if( *it )
			{
				a = new QAction( tr( "Disconnect \"%1\"" ).
					arg( ( *it )->fullDisplayName() ), m );
				a->setData( ( *it )->id() );
				m->addAction( a );
			}
		}
		connect( m, SIGNAL( triggered( QAction * ) ),
				this, SLOT( disconnectObject( QAction * ) ) );
		_cm->addMenu( m );
	}
}

void AutomationPatternView::mouseDoubleClickEvent( QMouseEvent * me )
{
	if(me->button() != Qt::LeftButton)
	{
		me->ignore();
		return;
	}
	openInAutomationEditor();
}

void AutomationPatternView::paintEvent( QPaintEvent * )
{
	QPainter painter( this );

	if( !needsUpdate() )
	{
		painter.drawPixmap( 0, 0, m_paintPixmap );
		return;
	}

	setNeedsUpdate( false );

	if (m_paintPixmap.isNull() || m_paintPixmap.size() != size())
	{
		m_paintPixmap = QPixmap(size());
	}

	QPainter p( &m_paintPixmap );

	QLinearGradient lingrad( 0, 0, 0, height() );
	QColor c = getColorForDisplay( painter.background().color() );
	bool muted = m_pat->getTrack()->isMuted() || m_pat->isMuted();
	bool current = gui->automationEditor()->currentPattern() == m_pat;

	lingrad.setColorAt( 1, c.darker( 300 ) );
	lingrad.setColorAt( 0, c );

	// paint a black rectangle under the pattern to prevent glitches with transparent backgrounds
	p.fillRect( rect(), QColor( 0, 0, 0 ) );

	if( gradient() )
	{
		p.fillRect( rect(), lingrad );
	}
	else
	{
		p.fillRect( rect(), c );
	}

	const float ppb = fixedTCOs() ?
			( parentWidget()->width() - 2 * TCO_BORDER_WIDTH )
				/ (float) m_pat->timeMapLength().getBar() :
								pixelsPerBar();

	const int x_base = TCO_BORDER_WIDTH;

	const float min = m_pat->firstObject()->minValue<float>();
	const float max = m_pat->firstObject()->maxValue<float>();

	const float y_scale = max - min;
	const float h = ( height() - 2 * TCO_BORDER_WIDTH ) / y_scale;
	const float ppTick  = ppb / TimePos::ticksPerBar();

	p.translate( 0.0f, max * height() / y_scale - TCO_BORDER_WIDTH );
	p.scale( 1.0f, -h );

	QLinearGradient lin2grad( 0, min, 0, max );
	QColor col;

	col = !muted ? painter.pen().brush().color() : mutedColor();

	lin2grad.setColorAt( 1, col.lighter( 150 ) );
	lin2grad.setColorAt( 0.5, col );
	lin2grad.setColorAt( 0, col.darker( 150 ) );

	p.setRenderHints( QPainter::Antialiasing, true );
	for( AutomationPattern::timeMap::const_iterator it =
						m_pat->getTimeMap().begin();
					it != m_pat->getTimeMap().end(); ++it )
	{
		if( it+1 == m_pat->getTimeMap().end() )
		{
			const float x1 = x_base + it.key() * ppTick;
			const float x2 = (float)( width() - TCO_BORDER_WIDTH );
			if( x1 > ( width() - TCO_BORDER_WIDTH ) ) break;
			if( gradient() )
			{
				p.fillRect( QRectF( x1, 0.0f, x2 - x1, it.value() ), lin2grad );
			}
			else
			{
				p.fillRect( QRectF( x1, 0.0f, x2 - x1, it.value() ), col );
			}
			break;
		}

		float *values = m_pat->valuesAfter( it.key() );

		float nextValue;
		if( m_pat->progressionType() == AutomationPattern::DiscreteProgression )
		{
			nextValue = it.value();
		}
		else
		{
			nextValue = ( it + 1 ).value();
		}

		QPainterPath path;
		QPointF origin = QPointF( x_base + it.key() * ppTick, 0.0f );
		path.moveTo( origin );
		path.moveTo( QPointF( x_base + it.key() * ppTick,values[0] ) );
		float x;
		for( int i = it.key() + 1; i < ( it + 1 ).key(); i++ )
		{
			x = x_base + i * ppTick;
			if( x > ( width() - TCO_BORDER_WIDTH ) ) break;
			float value = values[ i - it.key() ];
			path.lineTo( QPointF( x, value ) );

		}
		path.lineTo( x_base + ( ( it + 1 ).key() ) * ppTick, nextValue );
		path.lineTo( x_base + ( ( it + 1 ).key() ) * ppTick, 0.0f );
		path.lineTo( origin );

		if( gradient() )
		{
			p.fillPath( path, lin2grad );
		}
		else
		{
			p.fillPath( path, col );
		}
		delete [] values;
	}

	p.setRenderHints( QPainter::Antialiasing, false );
	p.resetTransform();

	// bar lines
	const int lineSize = 3;
	p.setPen( c.darker( 300 ) );

	for( bar_t t = 1; t < width() - TCO_BORDER_WIDTH; ++t )
	{
		const int tx = x_base + static_cast<int>( ppb * t ) - 2;
		p.drawLine( tx, TCO_BORDER_WIDTH, tx, TCO_BORDER_WIDTH + lineSize );
		p.drawLine( tx,	rect().bottom() - ( lineSize + TCO_BORDER_WIDTH ),
					tx, rect().bottom() - TCO_BORDER_WIDTH );
	}

	// recording icon for when recording automation
	if( m_pat->isRecording() )
	{
		p.drawPixmap( 1, rect().bottom() - s_pat_rec->height(),
		 	*s_pat_rec );
	}

	// pattern name
	paintTextLabel(m_pat->name(), p);

	// inner border
	p.setPen( c.lighter( current ? 160 : 130 ) );
	p.drawRect( 1, 1, rect().right() - TCO_BORDER_WIDTH,
		rect().bottom() - TCO_BORDER_WIDTH );

	// outer border
	p.setPen( current? c.lighter( 130 ) : c.darker( 300 ) );
	p.drawRect( 0, 0, rect().right(), rect().bottom() );

	// draw the 'muted' pixmap only if the pattern was manualy muted
	if( m_pat->isMuted() )
	{
		const int spacing = TCO_BORDER_WIDTH;
		const int size = 14;
		p.drawPixmap( spacing, height() - ( size + spacing ),
			embed::getIconPixmap( "muted", size, size ) );
	}

	p.end();

	painter.drawPixmap( 0, 0, m_paintPixmap );
}

void AutomationPatternView::dragEnterEvent( QDragEnterEvent * _dee )
{
	StringPairDrag::processDragEnterEvent( _dee, "automatable_model" );
	if( !_dee->isAccepted() )
	{
		TrackContentObjectView::dragEnterEvent( _dee );
	}
}

void AutomationPatternView::dropEvent( QDropEvent * _de )
{
	QString type = StringPairDrag::decodeKey( _de );
	QString val = StringPairDrag::decodeValue( _de );
	if( type == "automatable_model" )
	{
		AutomatableModel * mod = dynamic_cast<AutomatableModel *>(
				Engine::projectJournal()->
					journallingObject( val.toInt() ) );
		if( mod != NULL )
		{
			bool added = m_pat->addObject( mod );
			if ( !added )
			{
				TextFloat::displayMessage( mod->displayName(),
							   tr( "Model is already connected "
							   "to this pattern." ),
							   embed::getIconPixmap( "automation" ),
							   2000 );
			}
		}
		update();

		if( gui->automationEditor() &&
			gui->automationEditor()->currentPattern() == m_pat )
		{
			gui->automationEditor()->setCurrentPattern( m_pat );
		}
	}
	else
	{
		TrackContentObjectView::dropEvent( _de );
	}
}

/**
 * @brief Preserves the auto points over different scale
 */
void AutomationPatternView::scaleTimemapToFit( float oldMin, float oldMax )
{
	float newMin = m_pat->getMin();
	float newMax = m_pat->getMax();

	if( oldMin == newMin && oldMax == newMax )
	{
		return;
	}

	for( AutomationPattern::timeMap::iterator it = m_pat->m_timeMap.begin();
		it != m_pat->m_timeMap.end(); ++it )
	{
		if( *it < oldMin )
		{
			*it = oldMin;
		}
		else if( *it > oldMax )
		{
			*it = oldMax;
		}
		*it = (*it-oldMin)*(newMax-newMin)/(oldMax-oldMin)+newMin;
	}

	m_pat->generateTangents();
}


code:
SET(LMMS_SRCS
	${LMMS_SRCS}
	gui/AboutDialog.cpp
	gui/ActionGroup.cpp
	gui/AudioAlsaSetupWidget.cpp
	gui/AudioDeviceSetupWidget.cpp
	gui/AutomatableModelView.cpp
	gui/AutomationPatternView.cpp
	gui/ControllerConnectionDialog.cpp
	gui/ControllerDialog.cpp
	gui/EffectControlDialog.cpp
	gui/EffectSelectDialog.cpp
	gui/embed.cpp
	gui/ExportProjectDialog.cpp
	gui/FileBrowser.cpp
	gui/FxMixerView.cpp
	gui/GuiApplication.cpp
	gui/InstrumentView.cpp
	gui/LfoControllerDialog.cpp
	gui/LmmsPalette.cpp
	gui/LmmsStyle.cpp
	gui/Lv2ViewBase.cpp
	gui/MainApplication.cpp
	gui/MainWindow.cpp
	gui/MidiCCRackView.cpp
	gui/MidiSetupWidget.cpp
	gui/ModelView.cpp
	gui/PeakControllerDialog.cpp
	gui/PianoView.cpp
	gui/PluginBrowser.cpp
	gui/RowTableView.cpp
	gui/SetupDialog.cpp
	gui/StringPairDrag.cpp
	gui/SubWindow.cpp
	gui/TimeLineWidget.cpp
	gui/ToolPluginView.cpp
	gui/TrackContainerView.cpp
	gui/TrackContentObjectView.cpp
	gui/TrackView.cpp

	gui/dialogs/FileDialog.cpp
	gui/dialogs/VersionedSaveDialog.cpp
	gui/dialogs/ColorChooser.cpp

	gui/editors/AutomationEditor.cpp
	gui/editors/BBEditor.cpp
	gui/editors/Editor.cpp
	gui/editors/PianoRoll.cpp
	gui/editors/SongEditor.cpp

	gui/menus/RecentProjectsMenu.cpp
	gui/menus/TemplatesMenu.cpp

	gui/widgets/AutomatableButton.cpp
	gui/widgets/AutomatableSlider.cpp
	gui/widgets/CaptionMenu.cpp
	gui/widgets/ComboBox.cpp
	gui/widgets/ControllerRackView.cpp
	gui/widgets/ControllerView.cpp
	gui/widgets/Controls.cpp
	gui/widgets/CPULoadWidget.cpp
	gui/widgets/CustomTextKnob.cpp
	gui/widgets/EffectRackView.cpp
	gui/widgets/EffectView.cpp
	gui/widgets/EnvelopeAndLfoView.cpp
	gui/widgets/FadeButton.cpp
	gui/widgets/Fader.cpp
	gui/widgets/FxLine.cpp
	gui/widgets/FxLineLcdSpinBox.cpp
	gui/widgets/Graph.cpp
	gui/widgets/GroupBox.cpp
	gui/widgets/InstrumentFunctionViews.cpp
	gui/widgets/InstrumentMidiIOView.cpp
	gui/widgets/InstrumentSoundShapingView.cpp
	gui/widgets/LeftRightNav.cpp
	gui/widgets/Knob.cpp
	gui/widgets/LadspaControlView.cpp
	gui/widgets/LcdSpinBox.cpp
	gui/widgets/LcdWidget.cpp
	gui/widgets/LedCheckbox.cpp
	gui/widgets/ControlLayout.cpp
	gui/widgets/LinkedModelGroupViews.cpp
	gui/widgets/MeterDialog.cpp
	gui/widgets/MidiPortMenu.cpp
	gui/widgets/NStateButton.cpp
	gui/widgets/Oscilloscope.cpp
	gui/widgets/PixmapButton.cpp
	gui/widgets/PositionLine.cpp
	gui/widgets/ProjectNotes.cpp
	gui/widgets/RenameDialog.cpp
	gui/widgets/Rubberband.cpp
	gui/widgets/SendButtonIndicator.cpp
	gui/widgets/SideBar.cpp
	gui/widgets/SideBarWidget.cpp
	gui/widgets/TabBar.cpp
	gui/widgets/TabWidget.cpp
	gui/widgets/TempoSyncKnob.cpp
	gui/widgets/TextFloat.cpp
	gui/widgets/TimeDisplayWidget.cpp
	gui/widgets/ToolButton.cpp
	gui/widgets/ToolTip.cpp
	gui/widgets/TrackLabelButton.cpp
	gui/widgets/TrackContentWidget.cpp
	gui/widgets/TrackOperationsWidget.cpp
	gui/widgets/TrackRenameLineEdit.cpp
	gui/widgets/StepRecorderWidget.cpp

	PARENT_SCOPE
)

set(LMMS_UIS
	${LMMS_UIS}
	gui/dialogs/about_dialog.ui
	gui/dialogs/export_project.ui

	gui/Forms/EffectSelectDialog.ui

	PARENT_SCOPE
)


code:
/*
 * ControllerConnectionDialog.cpp - dialog allowing the user to create and
 *	modify links between controllers and models
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail.com>
 *
 * 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 <QLayout>
#include <QLineEdit>
#include <QPushButton>
#include <QScrollArea>
#include <QMessageBox>

#include "ControllerConnectionDialog.h"
#include "ControllerConnection.h"
#include "MidiController.h"
#include "MidiClient.h"
#include "MidiPortMenu.h"
#include "Mixer.h"
#include "LcdSpinBox.h"
#include "LedCheckbox.h"
#include "ComboBox.h"
#include "GroupBox.h"
#include "Song.h"
#include "ToolButton.h"

#include "gui_templates.h"
#include "embed.h"

class AutoDetectMidiController : public MidiController
{
public:
	AutoDetectMidiController( Model* parent ) :
		MidiController( parent ),
		m_detectedMidiChannel( 0 ),
		m_detectedMidiController( 0 )
	{
		updateName();
	}

	virtual ~AutoDetectMidiController()
	{
	}

	void processInEvent( const MidiEvent& event, const TimePos& time, f_cnt_t offset = 0 ) override
	{
		if( event.type() == MidiControlChange &&
			( m_midiPort.inputChannel() == 0 || m_midiPort.inputChannel() == event.channel() + 1 ) )
		{
			m_detectedMidiChannel = event.channel() + 1;
			m_detectedMidiController = event.controllerNumber() + 1;
			m_detectedMidiPort = Engine::mixer()->midiClient()->sourcePortName( event );

			emit valueChanged();
		}
	}

	// Would be a nice copy ctor, but too hard to add copy ctor because
	// model has none.
	MidiController* copyToMidiController( Model* parent )
	{
		MidiController* c = new MidiController( parent );
		c->m_midiPort.setInputChannel( m_midiPort.inputChannel() );
		c->m_midiPort.setInputController( m_midiPort.inputController() );
		c->subscribeReadablePorts( m_midiPort.readablePorts() );
		c->updateName();

		return c;
	}

	void useDetected()
	{
		m_midiPort.setInputChannel( m_detectedMidiChannel );
		m_midiPort.setInputController( m_detectedMidiController );

		const MidiPort::Map& map = m_midiPort.readablePorts();
		for( MidiPort::Map::ConstIterator it = map.begin(); it != map.end(); ++it )
		{
			m_midiPort.subscribeReadablePort( it.key(),
									m_detectedMidiPort.isEmpty() || ( it.key() == m_detectedMidiPort ) );
		}
	}

public slots:
	void reset()
	{
		m_midiPort.setInputChannel( 0 );
		m_midiPort.setInputController( 0 );
	}

private:
	int m_detectedMidiChannel;
	int m_detectedMidiController;
	QString m_detectedMidiPort;

} ;

ControllerConnectionDialog::ControllerConnectionDialog( QWidget * _parent,
		const AutomatableModel * _target_model ) :
	QDialog( _parent ),
	m_readablePorts( NULL ),
	m_midiAutoDetect( false ),
	m_controller( NULL ),
	m_targetModel( _target_model ),
	m_midiController( NULL )
{
	setWindowIcon( embed::getIconPixmap( "setup_audio" ) );
	setWindowTitle( tr( "Connection Settings" ) );
	setModal( true );

	// Midi stuff
	m_midiGroupBox = new GroupBox( tr( "MIDI CONTROLLER" ), this );
	m_midiGroupBox->setGeometry( 8, 10, 240, 80 );
	connect( m_midiGroupBox->model(), SIGNAL( dataChanged() ),
			this, SLOT( midiToggled() ) );

	m_midiChannelSpinBox = new LcdSpinBox( 2, m_midiGroupBox,
			tr( "Input channel" ) );
	m_midiChannelSpinBox->addTextForValue( 0, "--" );
	m_midiChannelSpinBox->setLabel( tr( "CHANNEL" ) );
	m_midiChannelSpinBox->move( 8, 24 );

	m_midiControllerSpinBox = new LcdSpinBox( 3, m_midiGroupBox,
			tr( "Input controller" ) );
	m_midiControllerSpinBox->addTextForValue( 0, "---" );
	m_midiControllerSpinBox->setLabel( tr( "CONTROLLER" ) );
	m_midiControllerSpinBox->move( 68, 24 );

	m_midiAutoDetectCheckBox =
			new LedCheckBox( tr("Auto Detect"),
				m_midiGroupBox, tr("Auto Detect") );
	m_midiAutoDetectCheckBox->setModel( &m_midiAutoDetect );
	m_midiAutoDetectCheckBox->move( 8, 60 );
	connect( &m_midiAutoDetect, SIGNAL( dataChanged() ),
			this, SLOT( autoDetectToggled() ) );

	// when using with non-raw-clients we can provide buttons showing
	// our port-menus when being clicked
	if( !Engine::mixer()->midiClient()->isRaw() )
	{
		m_readablePorts = new MidiPortMenu( MidiPort::Input );
		connect( m_readablePorts, SIGNAL( triggered( QAction * ) ),
				this, SLOT( enableAutoDetect( QAction * ) ) );
		ToolButton * rp_btn = new ToolButton( m_midiGroupBox );
		rp_btn->setText( tr( "MIDI-devices to receive "
						"MIDI-events from" ) );
		rp_btn->setIcon( embed::getIconPixmap( "piano" ) );
		rp_btn->setGeometry( 160, 24, 32, 32 );
		rp_btn->setMenu( m_readablePorts );
		rp_btn->setPopupMode( QToolButton::InstantPopup );
	}

	// User stuff
	m_userGroupBox = new GroupBox( tr( "USER CONTROLLER" ), this );
	m_userGroupBox->setGeometry( 8, 100, 240, 60 );
	connect( m_userGroupBox->model(), SIGNAL( dataChanged() ),
			this, SLOT( userToggled() ) );

	m_userController = new ComboBox( m_userGroupBox, "Controller" );
	m_userController->setGeometry( 10, 24, 200, ComboBox::DEFAULT_HEIGHT );
	for (Controller * c : Engine::getSong()->controllers())
	{
		m_userController->model()->addItem( c->name() );
	}
	connect( m_userController->model(), SIGNAL( dataUnchanged() ),
			this, SLOT( userSelected() ) );
	connect( m_userController->model(), SIGNAL( dataChanged() ),
			this, SLOT( userSelected() ) );

	// Mapping functions
	m_mappingBox = new TabWidget( tr( "MAPPING FUNCTION" ), this );
	m_mappingBox->setGeometry( 8, 170, 240, 64 );
	m_mappingFunction = new QLineEdit( m_mappingBox );
	m_mappingFunction->setGeometry( 10, 20, 170, 16 );
	m_mappingFunction->setText( "input" );
	m_mappingFunction->setReadOnly( true );

	// Buttons
	QWidget * buttons = new QWidget( this );
	buttons->setGeometry( 8, 240, 240, 32 );

	QHBoxLayout * btn_layout = new QHBoxLayout( buttons );
	btn_layout->setSpacing( 0 );
	btn_layout->setMargin( 0 );

	QPushButton * select_btn = new QPushButton(
					embed::getIconPixmap( "add" ),
					tr( "OK" ), buttons );
	connect( select_btn, SIGNAL( clicked() ),
				this, SLOT( selectController() ) );

	QPushButton * cancel_btn = new QPushButton(
					embed::getIconPixmap( "cancel" ),
					tr( "Cancel" ), buttons );
	connect( cancel_btn, SIGNAL( clicked() ),
				this, SLOT( reject() ) );

	btn_layout->addStretch();
	btn_layout->addSpacing( 10 );
	btn_layout->addWidget( select_btn );
	btn_layout->addSpacing( 10 );
	btn_layout->addWidget( cancel_btn );
	btn_layout->addSpacing( 10 );

	setFixedSize( 256, 280 );

	// Crazy MIDI View stuff

	// TODO, handle by making this a model for the Dialog "view"
	ControllerConnection * cc = NULL;
	if( m_targetModel )
	{
		cc = m_targetModel->controllerConnection();

		if( cc && cc->getController()->type() != Controller::DummyController && Engine::getSong() )
		{
			if ( cc->getController()->type() == Controller::MidiController )
			{
				m_midiGroupBox->model()->setValue( true );
				// ensure controller is created
				midiToggled();

				MidiController * cont = (MidiController*)( cc->getController() );
				m_midiChannelSpinBox->model()->setValue( cont->m_midiPort.inputChannel() );
				m_midiControllerSpinBox->model()->setValue( cont->m_midiPort.inputController() );

				m_midiController->subscribeReadablePorts( static_cast<MidiController*>( cc->getController() )->m_midiPort.readablePorts() );
			}
			else
			{
				int idx = Engine::getSong()->controllers().indexOf( cc->getController() );

				if( idx >= 0 )
				{
					m_userGroupBox->model()->setValue( true );
					m_userController->model()->setValue( idx );
				}
			}
		}
	}

	if( !cc )
	{
		m_midiGroupBox->model()->setValue( true );
	}

	show();
}

ControllerConnectionDialog::~ControllerConnectionDialog()
{
	delete m_readablePorts;

	delete m_midiController;
}

void ControllerConnectionDialog::selectController()
{
	// Midi
	if( m_midiGroupBox->model()->value() > 0 )
	{
		if( m_midiControllerSpinBox->model()->value() > 0 )
		{
			MidiController * mc;
			mc = m_midiController->copyToMidiController( Engine::getSong() );

			/*
			if( m_targetModel->getTrack() &&
					!m_targetModel->getTrack()->displayName().isEmpty() )
			{
				mc->m_midiPort.setName( QString( "%1 (%2)" ).
						arg( m_targetModel->getTrack()->displayName() ).
						arg( m_targetModel->displayName() ) );
			}
			else
			{
				mc->m_midiPort.setName( m_targetModel->displayName() );
			}
			*/
			mc->m_midiPort.setName( m_targetModel->fullDisplayName() );
			m_controller = mc;
		}
	}
	// User
	else
	{
		if( m_userGroupBox->model()->value() > 0 &&
				Engine::getSong()->controllers().size() )
		{
			m_controller = Engine::getSong()->controllers().at(
					m_userController->model()->value() );
		}

		if( m_controller && m_controller->hasModel( m_targetModel ) )
		{
			QMessageBox::warning(this, tr("LMMS"), tr("Cycle Detected."));
			return;
		}

	}

	accept();
}

void ControllerConnectionDialog::midiToggled()
{
	int enabled = m_midiGroupBox->model()->value();
	if( enabled != 0 )
	{
		if( m_userGroupBox->model()->value() != 0 )
		{
			m_userGroupBox->model()->setValue( 0 );
		}

		if( !m_midiController )
		{
			m_midiController = new AutoDetectMidiController( Engine::getSong() );

			MidiPort::Map map = m_midiController->m_midiPort.readablePorts();
			for( MidiPort::Map::Iterator it = map.begin(); it != map.end(); ++it )
			{
				it.value() = true;
			}
			m_midiController->subscribeReadablePorts( map );

			m_midiChannelSpinBox->setModel( &m_midiController->m_midiPort.m_inputChannelModel );
			m_midiControllerSpinBox->setModel( &m_midiController->m_midiPort.m_inputControllerModel );

			if( m_readablePorts )
			{
				m_readablePorts->setModel( &m_midiController->m_midiPort );
			}

			connect( m_midiController, SIGNAL( valueChanged() ), this, SLOT( midiValueChanged() ) );
		}
	}
	m_midiAutoDetect.setValue( enabled );

	m_midiChannelSpinBox->setEnabled( enabled );
	m_midiControllerSpinBox->setEnabled( enabled );
	m_midiAutoDetectCheckBox->setEnabled( enabled );
}

void ControllerConnectionDialog::userToggled()
{
	int enabled = m_userGroupBox->model()->value();
	if( enabled != 0 && m_midiGroupBox->model()->value() != 0 )
	{
		m_midiGroupBox->model()->setValue( 0 );
	}
}

void ControllerConnectionDialog::userSelected()
{
	m_userGroupBox->model()->setValue( 1 );
	userToggled();
}

void ControllerConnectionDialog::autoDetectToggled()
{
	if( m_midiAutoDetect.value() )
	{
		m_midiController->reset();
	}
}

void ControllerConnectionDialog::midiValueChanged()
{
	if( m_midiAutoDetect.value() )
	{
		m_midiController->useDetected();
		if( m_readablePorts )
		{
			m_readablePorts->updateMenu();
		}
	}
}

void ControllerConnectionDialog::enableAutoDetect( QAction * _a )
{
	if( _a->isChecked() )
	{
		m_midiAutoDetectCheckBox->model()->setValue( true );
	}
}


code:
/*
 * ControllerDialog.cpp - per-controller-specific view for changing a
 *                        controller's settings
 *
 * Copyright (c) 2008 Paul Giblock <drfaygo/at/gmail.com>
 *
 * 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 <QCloseEvent>

#include "ControllerDialog.h"
#include "Controller.h"

ControllerDialog::ControllerDialog( Controller * _controller,
							QWidget * _parent ) :
	QWidget( _parent ),
	ModelView( _controller, this )
{
}

ControllerDialog::~ControllerDialog()
{
}

void ControllerDialog::closeEvent( QCloseEvent * _ce )
{
	_ce->ignore();
	emit closed();
}


code:
/*
 * EffectControlDialog.cpp - base-class for effect-dialogs for displaying
 *                           and editing control port values
 *
 * Copyright (c) 2006-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 <QMessageBox>
#include <QCloseEvent>

#include "EffectControlDialog.h"
#include "EffectControls.h"

EffectControlDialog::EffectControlDialog( EffectControls * _controls ) :
	QWidget( NULL ),
	ModelView( _controls, this ),
	m_effectControls( _controls )
{
	setWindowTitle( m_effectControls->effect()->displayName() );
	setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Preferred );
}

EffectControlDialog::~EffectControlDialog()
{
}

void EffectControlDialog::closeEvent( QCloseEvent * _ce )
{
	_ce->ignore();
	emit closed();
}


code:
/*
 * EffectSelectDialog.cpp - dialog to choose effect plugin
 *
 * Copyright (c) 2006-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 "EffectSelectDialog.h"

#include "ui_EffectSelectDialog.h"

#include "gui_templates.h"
#include "DummyEffect.h"
#include "embed.h"
#include "PluginFactory.h"

#include <QLabel>

EffectSelectDialog::EffectSelectDialog( QWidget * _parent ) :
	QDialog( _parent ),
	ui( new Ui::EffectSelectDialog ),
	m_sourceModel(),
	m_model(),
	m_descriptionWidget( NULL )
{
	ui->setupUi( this );

	setWindowIcon( embed::getIconPixmap( "setup_audio" ) );

	// query effects

	EffectKeyList subPluginEffectKeys;

	for (const Plugin::Descriptor* desc: pluginFactory->descriptors(Plugin::Effect))
	{
		if( desc->subPluginFeatures )
		{
			desc->subPluginFeatures->listSubPluginKeys(
							desc,
							subPluginEffectKeys );
		}
		else
		{
			m_effectKeys << EffectKey( desc, desc->name );

		}
	}

	m_effectKeys += subPluginEffectKeys;

	// and fill our source model
	m_sourceModel.setHorizontalHeaderItem( 0, new QStandardItem( tr( "Name" ) ) );
	m_sourceModel.setHorizontalHeaderItem( 1, new QStandardItem( tr( "Type" ) ) );
	int row = 0;
	for( EffectKeyList::ConstIterator it = m_effectKeys.begin();
						it != m_effectKeys.end(); ++it )
	{
		QString name;
		QString type;
		if( it->desc->subPluginFeatures )
		{
			name = it->displayName();
			type = it->desc->displayName;
		}
		else
		{
			name = it->desc->displayName;
			type = "LMMS";
		}
		m_sourceModel.setItem( row, 0, new QStandardItem( name ) );
		m_sourceModel.setItem( row, 1, new QStandardItem( type ) );
		++row;
	}

	// setup filtering
	m_model.setSourceModel( &m_sourceModel );
	m_model.setFilterCaseSensitivity( Qt::CaseInsensitive );

	connect( ui->filterEdit, SIGNAL( textChanged( const QString & ) ),
				&m_model, SLOT( setFilterFixedString( const QString & ) ) );
	connect( ui->filterEdit, SIGNAL( textChanged( const QString & ) ),
					this, SLOT( updateSelection() ) );
	connect( ui->filterEdit, SIGNAL( textChanged( const QString & ) ),
							SLOT( sortAgain() ) );

	ui->pluginList->setModel( &m_model );

	// setup selection model
	QItemSelectionModel * selectionModel = new QItemSelectionModel( &m_model );
	ui->pluginList->setSelectionModel( selectionModel );
	connect( selectionModel, SIGNAL( currentRowChanged( const QModelIndex &,
														const QModelIndex & ) ),
			SLOT( rowChanged( const QModelIndex &, const QModelIndex & ) ) );
	connect( ui->pluginList, SIGNAL( doubleClicked( const QModelIndex & ) ),
				SLOT( acceptSelection() ) );

	// try to accept current selection when pressing "OK"
	connect( ui->buttonBox, SIGNAL( accepted() ),
				this, SLOT( acceptSelection() ) );

	ui->filterEdit->setClearButtonEnabled( true );
	ui->pluginList->verticalHeader()->setSectionResizeMode(
						QHeaderView::ResizeToContents );
	ui->pluginList->verticalHeader()->hide();
	ui->pluginList->horizontalHeader()->setSectionResizeMode( 0,
							QHeaderView::Stretch );
	ui->pluginList->horizontalHeader()->setSectionResizeMode( 1,
						QHeaderView::ResizeToContents );
	ui->pluginList->sortByColumn( 0, Qt::AscendingOrder );

	updateSelection();
	show();
}

EffectSelectDialog::~EffectSelectDialog()
{
	delete ui;
}

Effect * EffectSelectDialog::instantiateSelectedPlugin( EffectChain * _parent )
{
	Effect* result = nullptr;
	if(!m_currentSelection.name.isEmpty() && m_currentSelection.desc)
	{
		result = Effect::instantiate(m_currentSelection.desc->name,
										_parent, &m_currentSelection);
	}
	if(!result)
	{
		result = new DummyEffect(_parent, QDomElement());
	}
	return result;
}

void EffectSelectDialog::acceptSelection()
{
	if( m_currentSelection.isValid() )
	{
		accept();
	}
}

void EffectSelectDialog::rowChanged( const QModelIndex & _idx,
										const QModelIndex & )
{
	delete m_descriptionWidget;
	m_descriptionWidget = NULL;

	if( m_model.mapToSource( _idx ).row() < 0 )
	{
		// invalidate current selection
		m_currentSelection = Plugin::Descriptor::SubPluginFeatures::Key();
	}
	else
	{
		m_currentSelection = m_effectKeys[m_model.mapToSource( _idx ).row()];
	}
	if( m_currentSelection.desc )
	{
		m_descriptionWidget = new QWidget;

		QHBoxLayout *hbox = new QHBoxLayout( m_descriptionWidget );

		Plugin::Descriptor const & descriptor = *( m_currentSelection.desc );

		const PixmapLoader* pixLoa = m_currentSelection.logo();
		if (pixLoa)
		{
			QLabel *logoLabel = new QLabel( m_descriptionWidget );
			logoLabel->setPixmap(pixLoa->pixmap());
			logoLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);

			hbox->addWidget( logoLabel );
			hbox->setAlignment( logoLabel, Qt::AlignTop);
		}

		QWidget *textualInfoWidget = new QWidget( m_descriptionWidget );

		hbox->addWidget(textualInfoWidget);

		QVBoxLayout * textWidgetLayout = new QVBoxLayout( textualInfoWidget);
		textWidgetLayout->setMargin( 4 );
		textWidgetLayout->setSpacing( 0 );

		if ( m_currentSelection.desc->subPluginFeatures )
		{
			QWidget *subWidget = new QWidget(textualInfoWidget);
			QVBoxLayout * subLayout = new QVBoxLayout( subWidget );
			subLayout->setMargin( 4 );
			subLayout->setSpacing( 0 );
			m_currentSelection.desc->subPluginFeatures->
			fillDescriptionWidget( subWidget, &m_currentSelection );
			for( QWidget * w : subWidget->findChildren<QWidget *>() )
			{
				if( w->parent() == subWidget )
				{
					subLayout->addWidget( w );
				}
			}

			textWidgetLayout->addWidget(subWidget);
		}
		else
		{
			QLabel *label = new QLabel(m_descriptionWidget);
			QString labelText = "
" + tr("Name") + ": " + QString::fromUtf8(descriptor.displayName) + "
";
			labelText += "
" + tr("Description") + ": " + qApp->translate( "PluginBrowser", descriptor.description ) + "
";
			labelText += "
" + tr("Author") + ": " + QString::fromUtf8(descriptor.author) + "
";

			label->setText(labelText);
			textWidgetLayout->addWidget(label);
		}

		ui->scrollArea->setWidget( m_descriptionWidget );
		m_descriptionWidget->show();
	}
}

void EffectSelectDialog::sortAgain()
{
	ui->pluginList->setSortingEnabled( ui->pluginList->isSortingEnabled() );
}

void EffectSelectDialog::updateSelection()
{
	// no valid selection anymore due to changed filter?
	if( ui->pluginList->selectionModel()->selection().size() <= 0 )
	{
		// then select our first item
		ui->pluginList->selectionModel()->select( m_model.index( 0, 0 ),
					QItemSelectionModel::ClearAndSelect
					| QItemSelectionModel::Rows );
		rowChanged( m_model.index( 0, 0 ), QModelIndex() );
	}
}