code:
/*
 * MainApplication.cpp - Main QApplication handler
 *
 * Copyright (c) 2017-2017 Tres Finocchiaro <tres.finocchiaro/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 "MainApplication.h"

#include <QDebug>
#include <QFileOpenEvent>

#include "Engine.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "Song.h"

MainApplication::MainApplication(int& argc, char** argv) :
	QApplication(argc, argv),
	m_queuedFile()
{
#if defined(LMMS_BUILD_WIN32)
	installNativeEventFilter(this);
#endif
}

bool MainApplication::event(QEvent* event)
{
	switch(event->type())
	{
		case QEvent::FileOpen:
		{
			QFileOpenEvent * fileEvent = static_cast<QFileOpenEvent *>(event);
			// Handle the project file
			m_queuedFile = fileEvent->file();
			if(Engine::getSong())
			{
				if(gui->mainWindow()->mayChangeProject(true))
				{
					qDebug() << "Loading file " << m_queuedFile;
					Engine::getSong()->loadProject(m_queuedFile);
				}
			}
			else
			{
				qDebug() << "Queuing file " << m_queuedFile;
			}
			return true;
		}
		default:
			return QApplication::event(event);
	}
}

#ifdef LMMS_BUILD_WIN32
// This can be moved into nativeEventFilter once Qt4 support has been dropped
bool MainApplication::winEventFilter(MSG* msg, long* result)
{
	switch(msg->message)
	{
		case WM_STYLECHANGING:
			if(msg->wParam == GWL_EXSTYLE)
			{
				// Prevent plugins making the main window transparent
				STYLESTRUCT * style = reinterpret_cast<STYLESTRUCT *>(msg->lParam);
				if(!(style->styleOld & WS_EX_LAYERED))
				{
					style->styleNew &= ~WS_EX_LAYERED;
				}
				*result = 0;
				return true;
			}
			return false;
		default:
			return false;
	}
}

bool MainApplication::nativeEventFilter(const QByteArray& eventType,
					void* message, long* result)
{
	if(eventType == "windows_generic_MSG")
	{
		return winEventFilter(static_cast<MSG *>(message), result);
	}
	return false;
}
#endif


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

#include "MainWindow.h"

#include <QApplication>
#include <QCloseEvent>
#include <QDesktopServices>
#include <QDomElement>
#include <QFileInfo>
#include <QMdiArea>
#include <QMenuBar>
#include <QMessageBox>
#include <QScrollBar>
#include <QShortcut>
#include <QLibrary>
#include <QSplitter>
#include <QUrl>

#include "AboutDialog.h"
#include "AudioDummy.h"
#include "AutomationEditor.h"
#include "BBEditor.h"
#include "ControllerRackView.h"
#include "embed.h"
#include "Engine.h"
#include "ExportProjectDialog.h"
#include "FileBrowser.h"
#include "FileDialog.h"
#include "FxMixerView.h"
#include "GuiApplication.h"
#include "ImportFilter.h"
#include "InstrumentTrack.h"
#include "PianoRoll.h"
#include "PluginBrowser.h"
#include "PluginFactory.h"
#include "PluginView.h"
#include "ProjectJournal.h"
#include "ProjectNotes.h"
#include "ProjectRenderer.h"
#include "RecentProjectsMenu.h"
#include "RemotePlugin.h"
#include "SetupDialog.h"
#include "SideBar.h"
#include "SongEditor.h"
#include "TemplatesMenu.h"
#include "TextFloat.h"
#include "TimeLineWidget.h"
#include "ToolButton.h"
#include "ToolPlugin.h"
#include "VersionedSaveDialog.h"

#include "lmmsversion.h"

#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_HAIKU)
//Work around an issue on KDE5 as per https://bugs.kde.org/show_bug.cgi?id=337491#c21
void disableAutoKeyAccelerators(QWidget* mainWindow)
{
	using DisablerFunc = void(*)(QWidget*);
	QLibrary kf5WidgetsAddon("KF5WidgetsAddons", 5);
	DisablerFunc setNoAccelerators =
			reinterpret_cast<DisablerFunc>(kf5WidgetsAddon.resolve("_ZN19KAcceleratorManager10setNoAccelEP7QWidget"));
	if(setNoAccelerators)
	{
		setNoAccelerators(mainWindow);
	}
	kf5WidgetsAddon.unload();
}
#endif

MainWindow::MainWindow() :
	m_workspace( NULL ),
	m_toolsMenu( NULL ),
	m_autoSaveTimer( this ),
	m_viewMenu( NULL ),
	m_metronomeToggle( 0 ),
	m_session( Normal )
{
#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_APPLE) && !defined(LMMS_BUILD_HAIKU)
	disableAutoKeyAccelerators(this);
#endif
	setAttribute( Qt::WA_DeleteOnClose );

	QWidget * main_widget = new QWidget( this );
	QVBoxLayout * vbox = new QVBoxLayout( main_widget );
	vbox->setSpacing( 0 );
	vbox->setMargin( 0 );

	QWidget * w = new QWidget( main_widget );
	QHBoxLayout * hbox = new QHBoxLayout( w );
	hbox->setSpacing( 0 );
	hbox->setMargin( 0 );

	SideBar * sideBar = new SideBar( Qt::Vertical, w );

	QSplitter * splitter = new QSplitter( Qt::Horizontal, w );
	splitter->setChildrenCollapsible( false );

	ConfigManager* confMgr = ConfigManager::inst();
	bool sideBarOnRight = confMgr->value("ui", "sidebaronright").toInt();

	emit initProgress(tr("Preparing plugin browser"));
	sideBar->appendTab( new PluginBrowser( splitter ) );
	emit initProgress(tr("Preparing file browsers"));
	sideBar->appendTab( new FileBrowser(
				confMgr->userProjectsDir() + "*" +
				confMgr->factoryProjectsDir(),
					"*.mmp *.mmpz *.xml *.mid",
							tr( "My Projects" ),
					embed::getIconPixmap( "project_file" ).transformed( QTransform().rotate( 90 ) ),
							splitter, false, true,
				confMgr->userProjectsDir(),
				confMgr->factoryProjectsDir()));
	sideBar->appendTab( new FileBrowser(
				confMgr->userSamplesDir() + "*" +
				confMgr->factorySamplesDir(),
					"*", tr( "My Samples" ),
					embed::getIconPixmap( "sample_file" ).transformed( QTransform().rotate( 90 ) ),
							splitter, false, true,
					confMgr->userSamplesDir(),
					confMgr->factorySamplesDir()));
	sideBar->appendTab( new FileBrowser(
				confMgr->userPresetsDir() + "*" +
				confMgr->factoryPresetsDir(),
					"*.xpf *.cs.xml *.xiz *.lv2",
					tr( "My Presets" ),
					embed::getIconPixmap( "preset_file" ).transformed( QTransform().rotate( 90 ) ),
							splitter , false, true,
				confMgr->userPresetsDir(),
				confMgr->factoryPresetsDir()));
	sideBar->appendTab( new FileBrowser( QDir::homePath(), "*",
							tr( "My Home" ),
					embed::getIconPixmap( "home" ).transformed( QTransform().rotate( 90 ) ),
							splitter, false, false ) );

	QStringList root_paths;
	QString title = tr( "Root directory" );
	bool dirs_as_items = false;

#ifdef LMMS_BUILD_APPLE
	title = tr( "Volumes" );
	root_paths += "/Volumes";
#elif defined(LMMS_BUILD_WIN32)
	title = tr( "My Computer" );
	dirs_as_items = true;
#endif

#if ! defined(LMMS_BUILD_APPLE)
	QFileInfoList drives = QDir::drives();
	for( const QFileInfo & drive : drives )
	{
		root_paths += drive.absolutePath();
	}
#endif

	sideBar->appendTab( new FileBrowser( root_paths.join( "*" ), "*", title,
					embed::getIconPixmap( "computer" ).transformed( QTransform().rotate( 90 ) ),
							splitter, dirs_as_items) );

	m_workspace = new QMdiArea(splitter);

	// Load background
	emit initProgress(tr("Loading background picture"));
	QString backgroundPicFile = ConfigManager::inst()->backgroundPicFile();
	QImage backgroundPic;
	if( !backgroundPicFile.isEmpty() )
	{
		backgroundPic = QImage( backgroundPicFile );
	}
	if( !backgroundPicFile.isNull() )
	{
		m_workspace->setBackground( backgroundPic );
	}
	else
	{
		m_workspace->setBackground( Qt::NoBrush );
	}

	m_workspace->setOption( QMdiArea::DontMaximizeSubWindowOnActivation );
	m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
	m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );

	hbox->addWidget(sideBar);
	hbox->addWidget(splitter);
	// If the user wants the sidebar on the right, we move the workspace and
	// the splitter to the "left" side, or the first widgets in their list
	if (sideBarOnRight)
	{
		splitter->insertWidget(0, m_workspace);
		hbox->insertWidget(0, splitter);
	}

	// create global-toolbar at the top of our window
	m_toolBar = new QWidget( main_widget );
	m_toolBar->setObjectName( "mainToolbar" );
	m_toolBar->setFixedHeight( 64 );
	m_toolBar->move( 0, 0 );

	// add layout for organizing quite complex toolbar-layouting
	m_toolBarLayout = new QGridLayout( m_toolBar/*, 2, 1*/ );
	m_toolBarLayout->setMargin( 0 );
	m_toolBarLayout->setSpacing( 0 );

	vbox->addWidget( m_toolBar );
	vbox->addWidget( w );
	setCentralWidget( main_widget );

	m_updateTimer.start( 1000 / 60, this );  // 60 fps

	if( ConfigManager::inst()->value( "ui", "enableautosave" ).toInt() )
	{
		// connect auto save
		connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSave()));
		m_autoSaveInterval = ConfigManager::inst()->value(
					"ui", "saveinterval" ).toInt() < 1 ?
						DEFAULT_AUTO_SAVE_INTERVAL :
				ConfigManager::inst()->value(
					"ui", "saveinterval" ).toInt();

		// The auto save function mustn't run until there is a project
		// to save or it will run over recover.mmp if you hesitate at the
		// recover messagebox for a minute. It is now started in main.
		// See autoSaveTimerReset() in MainWindow.h
	}

	connect( Engine::getSong(), SIGNAL( playbackStateChanged() ),
				this, SLOT( updatePlayPauseIcons() ) );

	connect(Engine::getSong(), SIGNAL(stopped()), SLOT(onSongStopped()));

	connect(Engine::getSong(), SIGNAL(modified()), SLOT(onSongModified()));
	connect(Engine::getSong(), SIGNAL(projectFileNameChanged()), SLOT(onProjectFileNameChanged()));

	maximized = isMaximized();
	new QShortcut(QKeySequence(Qt::Key_F11), this, SLOT(toggleFullscreen()));
}

MainWindow::~MainWindow()
{
	for( PluginView *view : m_tools )
	{
		delete view->model();
		delete view;
	}
	// TODO: Close tools
	// dependencies are such that the editors must be destroyed BEFORE Song is deletect in Engine::destroy
	//   see issue #2015 on github
	delete gui->automationEditor();
	delete gui->pianoRoll();
	delete gui->songEditor();
	// destroy engine which will do further cleanups etc.
	Engine::destroy();
}

void MainWindow::finalize()
{
	resetWindowTitle();
	setWindowIcon( embed::getIconPixmap( "icon_small" ) );

	// project-popup-menu
	QMenu * project_menu = new QMenu( this );
	menuBar()->addMenu( project_menu )->setText( tr( "&File" ) );
	project_menu->addAction( embed::getIconPixmap( "project_new" ),
					tr( "&New" ),
					this, SLOT( createNewProject() ),
					QKeySequence::New );

	auto templates_menu = new TemplatesMenu( this );
	project_menu->addMenu(templates_menu);

	project_menu->addAction( embed::getIconPixmap( "project_open" ),
					tr( "&Open..." ),
					this, SLOT( openProject() ),
					QKeySequence::Open );

	project_menu->addMenu(new RecentProjectsMenu(this));

	project_menu->addAction( embed::getIconPixmap( "project_save" ),
					tr( "&Save" ),
					this, SLOT( saveProject() ),
					QKeySequence::Save );
	project_menu->addAction( embed::getIconPixmap( "project_save" ),
					tr( "Save &As..." ),
					this, SLOT( saveProjectAs() ),
					Qt::CTRL + Qt::SHIFT + Qt::Key_S );
	project_menu->addAction( embed::getIconPixmap( "project_save" ),
					tr( "Save as New &Version" ),
					this, SLOT( saveProjectAsNewVersion() ),
					Qt::CTRL + Qt::ALT + Qt::Key_S );

	project_menu->addAction( embed::getIconPixmap( "project_save" ),
					tr( "Save as default template" ),
					this, SLOT( saveProjectAsDefaultTemplate() ) );

	project_menu->addSeparator();
	project_menu->addAction( embed::getIconPixmap( "project_import" ),
					tr( "Import..." ),
					this,
					SLOT( onImportProject() ) );
	project_menu->addAction( embed::getIconPixmap( "project_export" ),
					tr( "E&xport..." ),
					this,
					SLOT( onExportProject() ),
					Qt::CTRL + Qt::Key_E );
	project_menu->addAction( embed::getIconPixmap( "project_export" ),
					tr( "E&xport Tracks..." ),
					this,
					SLOT( onExportProjectTracks() ),
					Qt::CTRL + Qt::SHIFT + Qt::Key_E );

	project_menu->addAction( embed::getIconPixmap( "midi_file" ),
					tr( "Export &MIDI..." ),
					this,
					SLOT( onExportProjectMidi() ),
					Qt::CTRL + Qt::Key_M );

// Prevent dangling separator at end of menu per https://bugreports.qt.io/browse/QTBUG-40071
#if !(defined(LMMS_BUILD_APPLE) && (QT_VERSION < 0x050600))
	project_menu->addSeparator();
#endif
	project_menu->addAction( embed::getIconPixmap( "exit" ), tr( "&Quit" ),
					qApp, SLOT( closeAllWindows() ),
					Qt::CTRL + Qt::Key_Q );

	QMenu * edit_menu = new QMenu( this );
	menuBar()->addMenu( edit_menu )->setText( tr( "&Edit" ) );
	m_undoAction = edit_menu->addAction( embed::getIconPixmap( "edit_undo" ),
					tr( "Undo" ),
					this, SLOT( undo() ),
					QKeySequence::Undo );
	m_redoAction = edit_menu->addAction( embed::getIconPixmap( "edit_redo" ),
					tr( "Redo" ),
					this, SLOT( redo() ),
					QKeySequence::Redo );
	// Ensure that both (Ctrl+Y) and (Ctrl+Shift+Z) activate redo shortcut regardless of OS defaults
	if (QKeySequence(QKeySequence::Redo) != QKeySequence(Qt::CTRL + Qt::Key_Y))
	{
		new QShortcut( QKeySequence( Qt::CTRL + Qt::Key_Y ), this, SLOT(redo()) );
	}
	if (QKeySequence(QKeySequence::Redo) != QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z ))
	{
		new QShortcut( QKeySequence( Qt::CTRL + Qt::SHIFT + Qt::Key_Z ), this, SLOT(redo()) );
	}

	edit_menu->addSeparator();
	edit_menu->addAction( embed::getIconPixmap( "setup_general" ),
					tr( "Settings" ),
					this, SLOT( showSettingsDialog() ) );
	connect( edit_menu, SIGNAL(aboutToShow()), this, SLOT(updateUndoRedoButtons()) );

	m_viewMenu = new QMenu( this );
	menuBar()->addMenu( m_viewMenu )->setText( tr( "&View" ) );
	connect( m_viewMenu, SIGNAL( aboutToShow() ),
		 this, SLOT( updateViewMenu() ) );
	connect( m_viewMenu, SIGNAL(triggered(QAction*)), this,
		SLOT(updateConfig(QAction*)));

	m_toolsMenu = new QMenu( this );
	for( const Plugin::Descriptor* desc : pluginFactory->descriptors(Plugin::Tool) )
	{
		m_toolsMenu->addAction( desc->logo->pixmap(), desc->displayName );
		m_tools.push_back( ToolPlugin::instantiate( desc->name, /*this*/NULL )
						   ->createView(this) );
	}
	if( !m_toolsMenu->isEmpty() )
	{
		menuBar()->addMenu( m_toolsMenu )->setText( tr( "&Tools" ) );
		connect( m_toolsMenu, SIGNAL( triggered( QAction * ) ),
					this, SLOT( showTool( QAction * ) ) );
	}

	// help-popup-menu
	QMenu * help_menu = new QMenu( this );
	menuBar()->addMenu( help_menu )->setText( tr( "&Help" ) );
	// May use offline help
	if( true )
	{
		help_menu->addAction( embed::getIconPixmap( "help" ),
						tr( "Online Help" ),
						this, SLOT( browseHelp() ) );
	}
	else
	{
		help_menu->addAction( embed::getIconPixmap( "help" ),
							tr( "Help" ),
							this, SLOT( help() ) );
	}

// Prevent dangling separator at end of menu per https://bugreports.qt.io/browse/QTBUG-40071
#if !(defined(LMMS_BUILD_APPLE) && (QT_VERSION < 0x050600))
	help_menu->addSeparator();
#endif
	help_menu->addAction( embed::getIconPixmap( "icon_small" ), tr( "About" ),
				  this, SLOT( aboutLMMS() ) );

	// create tool-buttons
	ToolButton * project_new = new ToolButton(
					embed::getIconPixmap( "project_new" ),
					tr( "Create new project" ),
					this, SLOT( createNewProject() ),
							m_toolBar );

	ToolButton * project_new_from_template = new ToolButton(
			embed::getIconPixmap( "project_new_from_template" ),
				tr( "Create new project from template" ),
					this, SLOT( emptySlot() ),
							m_toolBar );
	project_new_from_template->setMenu( templates_menu );
	project_new_from_template->setPopupMode( ToolButton::InstantPopup );

	ToolButton * project_open = new ToolButton(
					embed::getIconPixmap( "project_open" ),
					tr( "Open existing project" ),
					this, SLOT( openProject() ),
								m_toolBar );

	ToolButton * project_open_recent = new ToolButton(
				embed::getIconPixmap( "project_open_recent" ),
					tr( "Recently opened projects" ),
					this, SLOT( emptySlot() ), m_toolBar );
	project_open_recent->setMenu( new RecentProjectsMenu(this) );
	project_open_recent->setPopupMode( ToolButton::InstantPopup );

	ToolButton * project_save = new ToolButton(
					embed::getIconPixmap( "project_save" ),
					tr( "Save current project" ),
					this, SLOT( saveProject() ),
								m_toolBar );

	ToolButton * project_export = new ToolButton(
				embed::getIconPixmap( "project_export" ),
					tr( "Export current project" ),
					this,
							SLOT( onExportProject() ),
								m_toolBar );

	m_metronomeToggle = new ToolButton(
				embed::getIconPixmap( "metronome" ),
				tr( "Metronome" ),
				this, SLOT( onToggleMetronome() ),
							m_toolBar );
	m_metronomeToggle->setCheckable(true);
	m_metronomeToggle->setChecked(Engine::mixer()->isMetronomeActive());

	m_toolBarLayout->setColumnMinimumWidth( 0, 5 );
	m_toolBarLayout->addWidget( project_new, 0, 1 );
	m_toolBarLayout->addWidget( project_new_from_template, 0, 2 );
	m_toolBarLayout->addWidget( project_open, 0, 3 );
	m_toolBarLayout->addWidget( project_open_recent, 0, 4 );
	m_toolBarLayout->addWidget( project_save, 0, 5 );
	m_toolBarLayout->addWidget( project_export, 0, 6 );
	m_toolBarLayout->addWidget( m_metronomeToggle, 0, 7 );

	// window-toolbar
	ToolButton * song_editor_window = new ToolButton(
					embed::getIconPixmap( "songeditor" ),
					tr( "Song Editor" ) + " (Ctrl+1)",
					this, SLOT( toggleSongEditorWin() ),
								m_toolBar );
	song_editor_window->setShortcut( Qt::CTRL + Qt::Key_1 );

	ToolButton * bb_editor_window = new ToolButton(
					embed::getIconPixmap( "bb_track_btn" ),
					tr( "Beat+Bassline Editor" ) +
									" (Ctrl+2)",
					this, SLOT( toggleBBEditorWin() ),
								m_toolBar );
	bb_editor_window->setShortcut( Qt::CTRL + Qt::Key_2 );

	ToolButton * piano_roll_window = new ToolButton(
						embed::getIconPixmap( "piano" ),
						tr( "Piano Roll" ) +
									" (Ctrl+3)",
					this, SLOT( togglePianoRollWin() ),
								m_toolBar );
	piano_roll_window->setShortcut( Qt::CTRL + Qt::Key_3 );

	ToolButton * automation_editor_window = new ToolButton(
					embed::getIconPixmap( "automation" ),
					tr( "Automation Editor" ) +
									" (Ctrl+4)",
					this,
					SLOT( toggleAutomationEditorWin() ),
					m_toolBar );
	automation_editor_window->setShortcut( Qt::CTRL + Qt::Key_4 );

	ToolButton * fx_mixer_window = new ToolButton(
					embed::getIconPixmap( "fx_mixer" ),
					tr( "FX Mixer" ) + " (Ctrl+5)",
					this, SLOT( toggleFxMixerWin() ),
					m_toolBar );
	fx_mixer_window->setShortcut( Qt::CTRL + Qt::Key_5 );

	ToolButton * controllers_window = new ToolButton(
					embed::getIconPixmap( "controller" ),
					tr( "Show/hide controller rack" ) +
								" (Ctrl+6)",
					this, SLOT( toggleControllerRack() ),
								m_toolBar );
	controllers_window->setShortcut( Qt::CTRL + Qt::Key_6 );

	ToolButton * project_notes_window = new ToolButton(
					embed::getIconPixmap( "project_notes" ),
					tr( "Show/hide project notes" ) +
								" (Ctrl+7)",
					this, SLOT( toggleProjectNotesWin() ),
								m_toolBar );
	project_notes_window->setShortcut( Qt::CTRL + Qt::Key_7 );

	m_toolBarLayout->addWidget( song_editor_window, 1, 1 );
	m_toolBarLayout->addWidget( bb_editor_window, 1, 2 );
	m_toolBarLayout->addWidget( piano_roll_window, 1, 3 );
	m_toolBarLayout->addWidget( automation_editor_window, 1, 4 );
	m_toolBarLayout->addWidget( fx_mixer_window, 1, 5 );
	m_toolBarLayout->addWidget( controllers_window, 1, 6 );
	m_toolBarLayout->addWidget( project_notes_window, 1, 7 );
	m_toolBarLayout->setColumnStretch( 100, 1 );

	// setup-dialog opened before?
	if( !ConfigManager::inst()->value( "app", "configured" ).toInt() )
	{
		ConfigManager::inst()->setValue( "app", "configured", "1" );
		// no, so show it that user can setup everything
		SetupDialog sd;
		sd.exec();
	}
	// look whether mixer failed to start the audio device selected by the
	// user and is using AudioDummy as a fallback
	// or the audio device is set to invalid one
	else if( Engine::mixer()->audioDevStartFailed() || !Mixer::isAudioDevNameValid(
		ConfigManager::inst()->value( "mixer", "audiodev" ) ) )
	{
		// if so, offer the audio settings section of the setup dialog
		SetupDialog sd( SetupDialog::AudioSettings );
		sd.exec();
	}

	// Add editor subwindows
	for (QWidget* widget :  std::list<QWidget*>{
			gui->automationEditor(),
			gui->getBBEditor(),
			gui->pianoRoll(),
			gui->songEditor()
	})
	{
		QMdiSubWindow* window = addWindowedWidget(widget);
		window->setWindowIcon(widget->windowIcon());
		window->setAttribute(Qt::WA_DeleteOnClose, false);
		window->resize(widget->sizeHint());
	}

	gui->automationEditor()->parentWidget()->hide();
	gui->getBBEditor()->parentWidget()->move( 610, 5 );
	gui->getBBEditor()->parentWidget()->hide();
	gui->pianoRoll()->parentWidget()->move(5, 5);
	gui->pianoRoll()->parentWidget()->hide();
	gui->songEditor()->parentWidget()->move(5, 5);
	gui->songEditor()->parentWidget()->show();

	// reset window title every time we change the state of a subwindow to show the correct title
	for( const QMdiSubWindow * subWindow : workspace()->subWindowList() )
	{
		connect( subWindow, SIGNAL( windowStateChanged(Qt::WindowStates,Qt::WindowStates) ), this, SLOT( resetWindowTitle() ) );
	}
}

int MainWindow::addWidgetToToolBar( QWidget * _w, int _row, int _col )
{
	int col = ( _col == -1 ) ? m_toolBarLayout->columnCount() + 7 : _col;
	if( _w->height() > 32 || _row == -1 )
	{
		m_toolBarLayout->addWidget( _w, 0, col, 2, 1 );
	}
	else
	{
		m_toolBarLayout->addWidget( _w, _row, col );
	}
	return( col );
}

void MainWindow::addSpacingToToolBar( int _size )
{
	m_toolBarLayout->setColumnMinimumWidth( m_toolBarLayout->columnCount() +
								7, _size );
}

SubWindow* MainWindow::addWindowedWidget(QWidget *w, Qt::WindowFlags windowFlags)
{
	// wrap the widget in our own *custom* window that patches some errors in QMdiSubWindow
	SubWindow *win = new SubWindow(m_workspace->viewport(), windowFlags);
	win->setAttribute(Qt::WA_DeleteOnClose);
	win->setWidget(w);
	if (w && w->sizeHint().isValid()) {win->resize(w->sizeHint());}
	m_workspace->addSubWindow(win);
	return win;
}

void MainWindow::resetWindowTitle()
{
	QString title(tr( "Untitled" ));

	if( Engine::getSong()->projectFileName() != "" )
	{
		title = QFileInfo( Engine::getSong()->projectFileName()
							).completeBaseName();
	}

	if( Engine::getSong()->isModified() )
	{
		title += '*';
	}

	if( getSession() == Recover )
	{
		title += " - " + tr( "Recover session. Please save your work!" );
	}

	setWindowTitle( title + " - " + tr( "LMMS %1" ).arg( LMMS_VERSION ) );
}

bool MainWindow::mayChangeProject(bool stopPlayback)
{
	if( stopPlayback )
	{
		Engine::getSong()->stop();
	}

	if( !Engine::getSong()->isModified() && getSession() != Recover )
	{
		return( true );
	}

	// Separate message strings for modified and recovered files
	QString messageTitleRecovered = tr( "Recovered project not saved" );
	QString messageRecovered = tr( "This project was recovered from the "
					"previous session. It is currently "
					"unsaved and will be lost if you don't "
					"save it. Do you want to save it now?" );

	QString messageTitleUnsaved = tr( "Project not saved" );
	QString messageUnsaved = tr( "The current project was modified since "
					"last saving. Do you want to save it "
								"now?" );

	QMessageBox mb( ( getSession() == Recover ?
				messageTitleRecovered : messageTitleUnsaved ),
			( getSession() == Recover ?
					messageRecovered : messageUnsaved ),
				QMessageBox::Question,
				QMessageBox::Save,
				QMessageBox::Discard,
				QMessageBox::Cancel,
				this );
	int answer = mb.exec();

	if( answer == QMessageBox::Save )
	{
		return( saveProject() );
	}
	else if( answer == QMessageBox::Discard )
	{
		if( getSession() == Recover )
		{
			sessionCleanup();
		}
		return( true );
	}

	return( false );
}

void MainWindow::clearKeyModifiers()
{
	m_keyMods.m_ctrl = false;
	m_keyMods.m_shift = false;
	m_keyMods.m_alt = false;
}

void MainWindow::saveWidgetState( QWidget * _w, QDomElement & _de )
{
	// If our widget is the main content of a window (e.g. piano roll, FxMixer, etc),
	// we really care about the position of the *window* - not the position of the widget within its window
	if( _w->parentWidget() != NULL &&
			_w->parentWidget()->inherits( "QMdiSubWindow" ) )
	{
		_w = _w->parentWidget();
	}

	// If the widget is a SubWindow, then we can make use of the getTrueNormalGeometry() method that
	// performs the same as normalGeometry, but isn't broken on X11 ( see https://bugreports.qt.io/browse/QTBUG-256 )
	SubWindow *asSubWindow = qobject_cast<SubWindow*>(_w);
	QRect normalGeom = asSubWindow != nullptr ? asSubWindow->getTrueNormalGeometry() : _w->normalGeometry();

	bool visible = _w->isVisible();
	_de.setAttribute( "visible", visible );
	_de.setAttribute( "minimized", _w->isMinimized() );
	_de.setAttribute( "maximized", _w->isMaximized() );

	_de.setAttribute( "x", normalGeom.x() );
	_de.setAttribute( "y", normalGeom.y() );

	QSize sizeToStore = normalGeom.size();
	_de.setAttribute( "width", sizeToStore.width() );
	_de.setAttribute( "height", sizeToStore.height() );
}

void MainWindow::restoreWidgetState( QWidget * _w, const QDomElement & _de )
{
	QRect r( qMax( 1, _de.attribute( "x" ).toInt() ),
			qMax( 1, _de.attribute( "y" ).toInt() ),
			qMax( _w->sizeHint().width(), _de.attribute( "width" ).toInt() ),
			qMax( _w->minimumHeight(), _de.attribute( "height" ).toInt() ) );
	if( _de.hasAttribute( "visible" ) && !r.isNull() )
	{
		// If our widget is the main content of a window (e.g. piano roll, FxMixer, etc),
		// we really care about the position of the *window* - not the position of the widget within its window
		if ( _w->parentWidget() != NULL &&
			_w->parentWidget()->inherits( "QMdiSubWindow" ) )
		{
			_w = _w->parentWidget();
		}
		// first restore the window, as attempting to resize a maximized window causes graphics glitching
		_w->setWindowState( _w->windowState() & ~(Qt::WindowMaximized | Qt::WindowMinimized) );

		// Check isEmpty() to work around corrupt project files with empty size
		if ( ! r.size().isEmpty() ) {
			_w->resize( r.size() );
		}
		_w->move( r.topLeft() );

		// set the window to its correct minimized/maximized/restored state
		Qt::WindowStates flags = _w->windowState();
		flags = _de.attribute( "minimized" ).toInt() ?
				( flags | Qt::WindowMinimized ) :
				( flags & ~Qt::WindowMinimized );
		flags = _de.attribute( "maximized" ).toInt() ?
				( flags | Qt::WindowMaximized ) :
				( flags & ~Qt::WindowMaximized );
		_w->setWindowState( flags );

		_w->setVisible( _de.attribute( "visible" ).toInt() );
	}
}

void MainWindow::emptySlot()
{
}

void MainWindow::createNewProject()
{
	if( mayChangeProject(true) )
	{
		Engine::getSong()->createNewProject();
	}
}

void MainWindow::openProject()
{
	if( mayChangeProject(false) )
	{
		FileDialog ofd( this, tr( "Open Project" ), "", tr( "LMMS (*.mmp *.mmpz)" ) );

		ofd.setDirectory( ConfigManager::inst()->userProjectsDir() );
		ofd.setFileMode( FileDialog::ExistingFiles );
		if( ofd.exec () == QDialog::Accepted &&
						!ofd.selectedFiles().isEmpty() )
		{
			Song *song = Engine::getSong();

			song->stop();
			setCursor( Qt::WaitCursor );
			song->loadProject( ofd.selectedFiles()[0] );
			setCursor( Qt::ArrowCursor );
		}
	}
}

bool MainWindow::saveProject()
{
	if( Engine::getSong()->projectFileName() == "" )
	{
		return( saveProjectAs() );
	}
	else if( this->guiSaveProject() )
	{
		if( getSession() == Recover )
		{
			sessionCleanup();
		}
		return true;
	}
	return false;
}

bool MainWindow::saveProjectAs()
{
	auto optionsWidget = new SaveOptionsWidget(Engine::getSong()->getSaveOptions());
	VersionedSaveDialog sfd( this, optionsWidget, tr( "Save Project" ), "",
			tr( "LMMS Project" ) + " (*.mmpz *.mmp);;" +
				tr( "LMMS Project Template" ) + " (*.mpt)" );
	QString f = Engine::getSong()->projectFileName();
	if( f != "" )
	{
		sfd.setDirectory( QFileInfo( f ).absolutePath() );
		sfd.selectFile( QFileInfo( f ).fileName() );
	}
	else
	{
		sfd.setDirectory( ConfigManager::inst()->userProjectsDir() );
	}

	// Don't write over file with suffix if no suffix is provided.
	QString suffix = ConfigManager::inst()->value( "app",
							"nommpz" ).toInt() == 0
						? "mmpz"
						: "mmp" ;
	sfd.setDefaultSuffix( suffix );

	if( sfd.exec () == FileDialog::Accepted &&
		!sfd.selectedFiles().isEmpty() && sfd.selectedFiles()[0] != "" )
	{
		QString fname = sfd.selectedFiles()[0] ;
		if( sfd.selectedNameFilter().contains( "(*.mpt)" ) )
		{
			// Remove the default suffix
			fname.remove( "." + suffix );
			if( !sfd.selectedFiles()[0].endsWith( ".mpt" ) )
			{
				if( VersionedSaveDialog::fileExistsQuery( fname + ".mpt",
						tr( "Save project template" ) ) )
				{
					fname += ".mpt";
				}
			}
		}
		if( this->guiSaveProjectAs( fname ) )
		{
			if( getSession() == Recover )
			{
				sessionCleanup();
			}
			return true;
		}
	}
	return false;
}

bool MainWindow::saveProjectAsNewVersion()
{
	QString fileName = Engine::getSong()->projectFileName();
	if( fileName == "" )
	{
		return saveProjectAs();
	}
	else
	{
		do 		VersionedSaveDialog::changeFileNameVersion( fileName, true );
		while 	( QFile( fileName ).exists() );

		return this->guiSaveProjectAs( fileName );
	}
}

void MainWindow::saveProjectAsDefaultTemplate()
{
	QString defaultTemplate = ConfigManager::inst()->userTemplateDir() + "default.mpt";

	QFileInfo fileInfo(defaultTemplate);
	if (fileInfo.exists())
	{
		if (QMessageBox::warning(this,
					 tr("Overwrite default template?"),
					 tr("This will overwrite your current default template."),
					 QMessageBox::Ok,
					 QMessageBox::Cancel) != QMessageBox::Ok)
		{
			return;
		}
	}

	Engine::getSong()->saveProjectFile( defaultTemplate );
}

void MainWindow::showSettingsDialog()
{
	SetupDialog sd;
	sd.exec();
}

void MainWindow::aboutLMMS()
{
	AboutDialog(this).exec();
}

void MainWindow::help()
{
	QMessageBox::information( this, tr( "Help not available" ),
				  tr( "Currently there's no help "
						  "available in LMMS.\n"
						  "Please visit "
						  "http://lmms.sf.net/wiki "
						  "for documentation on LMMS." ),
				  QMessageBox::Ok );
}

void MainWindow::toggleWindow( QWidget *window, bool forceShow )
{
	QWidget *parent = window->parentWidget();

	if( forceShow ||
		m_workspace->activeSubWindow() != parent ||
		parent->isHidden() )
	{
		parent->show();
		window->show();
		window->setFocus();
	}
	else
	{
		parent->hide();
		refocus();
	}

	// Workaround for Qt Bug #260116
	m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
	m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
	m_workspace->setHorizontalScrollBarPolicy( Qt::ScrollBarAsNeeded );
	m_workspace->setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded );
}

void MainWindow::toggleFullscreen()
{
	if ( !isFullScreen() )
	{
		maximized = isMaximized();
		showFullScreen();
	}
	else
	{
		maximized ? showMaximized() : showNormal();
	}
}

/*
 * When an editor window with focus is toggled off, attempt to set focus
 * to the next visible editor window, or if none are visible, set focus
 * to the parent window.
 */
void MainWindow::refocus()
{
	QList<QWidget*> editors;
	editors
		<< gui->songEditor()->parentWidget()
		<< gui->getBBEditor()->parentWidget()
		<< gui->pianoRoll()->parentWidget()
		<< gui->automationEditor()->parentWidget();

	bool found = false;
	QList<QWidget*>::Iterator editor;
	for( editor = editors.begin(); editor != editors.end(); ++editor )
	{
		if( ! (*editor)->isHidden() ) {
			(*editor)->setFocus();
			found = true;
			break;
		}
	}

	if( ! found )
		this->setFocus();
}

void MainWindow::toggleBBEditorWin( bool forceShow )
{
	toggleWindow( gui->getBBEditor(), forceShow );
}

void MainWindow::toggleSongEditorWin()
{
	toggleWindow( gui->songEditor() );
}

void MainWindow::toggleProjectNotesWin()
{
	toggleWindow( gui->getProjectNotes() );
}

void MainWindow::togglePianoRollWin()
{
	toggleWindow( gui->pianoRoll() );
}

void MainWindow::toggleAutomationEditorWin()
{
	toggleWindow( gui->automationEditor() );
}

void MainWindow::toggleFxMixerWin()
{
	toggleWindow( gui->fxMixerView() );
}

void MainWindow::updateViewMenu()
{
	m_viewMenu->clear();
	// TODO: get current visibility for these and indicate in menu?
	// Not that it's straight visible <-> invisible, more like
	// not on top -> top <-> invisible
	m_viewMenu->addAction(embed::getIconPixmap( "songeditor" ),
			      tr( "Song Editor" ) + "\tCtrl+1",
			      this, SLOT( toggleSongEditorWin() )
		);
	m_viewMenu->addAction(embed::getIconPixmap( "bb_track" ),
					tr( "Beat+Bassline Editor" ) + "\tCtrl+2",
					this, SLOT( toggleBBEditorWin() )
		);
	m_viewMenu->addAction(embed::getIconPixmap( "piano" ),
			      tr( "Piano Roll" ) + "\tCtrl+3",
			      this, SLOT( togglePianoRollWin() )
		);
	m_viewMenu->addAction(embed::getIconPixmap( "automation" ),
			      tr( "Automation Editor" ) + "\tCtrl+4",
			      this,
			      SLOT( toggleAutomationEditorWin())
		);
	m_viewMenu->addAction(embed::getIconPixmap( "fx_mixer" ),
			      tr( "FX Mixer" ) + "\tCtrl+5",
			      this, SLOT( toggleFxMixerWin() )
		);
	m_viewMenu->addAction(embed::getIconPixmap( "controller" ),
			      tr( "Controller Rack" ) + "\tCtrl+6",
			      this, SLOT( toggleControllerRack() )
		);
	m_viewMenu->addAction(embed::getIconPixmap( "project_notes" ),
			      tr( "Project Notes" ) + "\tCtrl+7",
			      this, SLOT( toggleProjectNotesWin() )
		);

	m_viewMenu->addSeparator();

	m_viewMenu->addAction(embed::getIconPixmap( "fullscreen" ),
				tr( "Fullscreen" ) + "\tF11",
				this, SLOT( toggleFullscreen() )
		);

	m_viewMenu->addSeparator();

	// Here we should put all look&feel -stuff from configmanager
	// that is safe to change on the fly. There is probably some
	// more elegant way to do this.
	QAction *qa;
	qa = new QAction(tr( "Volume as dBFS" ), this);
	qa->setData("displaydbfs");
	qa->setCheckable( true );
	qa->setChecked( ConfigManager::inst()->value( "app", "displaydbfs" ).toInt() );
	m_viewMenu->addAction(qa);

	// Maybe this is impossible?
	/* qa = new QAction(tr( "Tooltips" ), this);
	qa->setData("tooltips");
	qa->setCheckable( true );
	qa->setChecked( !ConfigManager::inst()->value( "tooltips", "disabled" ).toInt() );
	m_viewMenu->addAction(qa);
	*/

	qa = new QAction(tr( "Smooth scroll" ), this);
	qa->setData("smoothscroll");
	qa->setCheckable( true );
	qa->setChecked( ConfigManager::inst()->value( "ui", "smoothscroll" ).toInt() );
	m_viewMenu->addAction(qa);

	// Not yet.
	/* qa = new QAction(tr( "One instrument track window" ), this);
	qa->setData("oneinstrument");
	qa->setCheckable( true );
	qa->setChecked( ConfigManager::inst()->value( "ui", "oneinstrumenttrackwindow" ).toInt() );
	m_viewMenu->addAction(qa);
	*/

	qa = new QAction(tr( "Enable note labels in piano roll" ), this);
	qa->setData("printnotelabels");
	qa->setCheckable( true );
	qa->setChecked( ConfigManager::inst()->value( "ui", "printnotelabels" ).toInt() );
	m_viewMenu->addAction(qa);

}

void MainWindow::updateConfig( QAction * _who )
{
	QString tag = _who->data().toString();
	bool checked = _who->isChecked();

	if( tag == "displaydbfs" )
	{
		ConfigManager::inst()->setValue( "app", "displaydbfs",
						 QString::number(checked) );
	}
	else if ( tag == "tooltips" )
	{
		ConfigManager::inst()->setValue( "tooltips", "disabled",
						 QString::number(!checked) );
	}
	else if ( tag == "smoothscroll" )
	{
		ConfigManager::inst()->setValue( "ui", "smoothscroll",
						 QString::number(checked) );
	}
	else if ( tag == "oneinstrument" )
	{
		ConfigManager::inst()->setValue( "ui", "oneinstrumenttrackwindow",
						 QString::number(checked) );
	}
	else if ( tag == "printnotelabels" )
	{
		ConfigManager::inst()->setValue( "ui", "printnotelabels",
						 QString::number(checked) );
	}
}

void MainWindow::onToggleMetronome()
{
	Mixer * mixer = Engine::mixer();

	mixer->setMetronomeActive( m_metronomeToggle->isChecked() );
}

void MainWindow::toggleControllerRack()
{
	toggleWindow( gui->getControllerRackView() );
}

void MainWindow::updatePlayPauseIcons()
{
	gui->songEditor()->setPauseIcon( false );
	gui->automationEditor()->setPauseIcon( false );
	gui->getBBEditor()->setPauseIcon( false );
	gui->pianoRoll()->setPauseIcon( false );

	if( Engine::getSong()->isPlaying() )
	{
		switch( Engine::getSong()->playMode() )
		{
			case Song::Mode_PlaySong:
				gui->songEditor()->setPauseIcon( true );
				break;

			case Song::Mode_PlayAutomationPattern:
				gui->automationEditor()->setPauseIcon( true );
				break;

			case Song::Mode_PlayBB:
				gui->getBBEditor()->setPauseIcon( true );
				break;

			case Song::Mode_PlayPattern:
				gui->pianoRoll()->setPauseIcon( true );
				break;

			default:
				break;
		}
	}
}

void MainWindow::updateUndoRedoButtons()
{
	// when the edit menu is shown, grey out the undo/redo buttons if there's nothing to undo/redo
	// else, un-grey them
	m_undoAction->setEnabled(Engine::projectJournal()->canUndo());
	m_redoAction->setEnabled(Engine::projectJournal()->canRedo());
}

void MainWindow::undo()
{
	Engine::projectJournal()->undo();
}

void MainWindow::redo()
{
	Engine::projectJournal()->redo();
}

void MainWindow::closeEvent( QCloseEvent * _ce )
{
	if( mayChangeProject(true) )
	{
		// delete recovery file
		if( ConfigManager::inst()->
				value( "ui", "enableautosave" ).toInt() )
		{
			sessionCleanup();
			_ce->accept();
		}
	}
	else
	{
		_ce->ignore();
	}
}

void MainWindow::sessionCleanup()
{
	// delete recover session files
	QFile::remove( ConfigManager::inst()->recoveryFile() );
	setSession( Normal );
}

void MainWindow::focusOutEvent( QFocusEvent * _fe )
{
	// TODO Remove this function, since it is apparently never actually called!
	// when loosing focus we do not receive key-(release!)-events anymore,
	// so we might miss release-events of one the modifiers we're watching!
	clearKeyModifiers();
	QMainWindow::leaveEvent( _fe );
}

void MainWindow::keyPressEvent( QKeyEvent * _ke )
{
	switch( _ke->key() )
	{
		case Qt::Key_Control: m_keyMods.m_ctrl = true; break;
		case Qt::Key_Shift: m_keyMods.m_shift = true; break;
		case Qt::Key_Alt: m_keyMods.m_alt = true; break;
		default:
		{
			InstrumentTrackWindow * w =
						InstrumentTrackView::topLevelInstrumentTrackWindow();
			if( w )
			{
				w->pianoView()->keyPressEvent( _ke );
			}
			if( !_ke->isAccepted() )
			{
				QMainWindow::keyPressEvent( _ke );
			}
		}
	}
}

void MainWindow::keyReleaseEvent( QKeyEvent * _ke )
{
	switch( _ke->key() )
	{
		case Qt::Key_Control: m_keyMods.m_ctrl = false; break;
		case Qt::Key_Shift: m_keyMods.m_shift = false; break;
		case Qt::Key_Alt: m_keyMods.m_alt = false; break;
		default:
			if( InstrumentTrackView::topLevelInstrumentTrackWindow() )
			{
				InstrumentTrackView::topLevelInstrumentTrackWindow()->
					pianoView()->keyReleaseEvent( _ke );
			}
			if( !_ke->isAccepted() )
			{
				QMainWindow::keyReleaseEvent( _ke );
			}
	}
}

void MainWindow::timerEvent( QTimerEvent * _te)
{
	emit periodicUpdate();
}

void MainWindow::showTool( QAction * _idx )
{
	PluginView * p = m_tools[m_toolsMenu->actions().indexOf( _idx )];
	p->show();
	p->parentWidget()->show();
	p->setFocus();
}

void MainWindow::browseHelp()
{
	// file:// alternative for offline help
	QString url = "https://lmms.io/documentation/";
	QDesktopServices::openUrl( url );
	// TODO: Handle error
}

void MainWindow::autoSave()
{
	if( !Engine::getSong()->isExporting() &&
		!Engine::getSong()->isLoadingProject() &&
		!RemotePluginBase::isMainThreadWaiting() &&
		!QApplication::mouseButtons() &&
		( ConfigManager::inst()->value( "ui",
				"enablerunningautosave" ).toInt() ||
			! Engine::getSong()->isPlaying() ) )
	{
		Engine::getSong()->saveProjectFile(ConfigManager::inst()->recoveryFile());
		autoSaveTimerReset();  // Reset timer
	}
	else
	{
		// try again in 10 seconds
		if( getAutoSaveTimerInterval() != m_autoSaveShortTime )
		{
			autoSaveTimerReset( m_autoSaveShortTime );
		}
	}
}

void MainWindow::onExportProjectMidi()
{
	FileDialog efd( this );

	efd.setFileMode( FileDialog::AnyFile );

	QStringList types;
	types << tr("MIDI File (*.mid)");
	efd.setNameFilters( types );
	QString base_filename;
	QString const & projectFileName = Engine::getSong()->projectFileName();
	if( !projectFileName.isEmpty() )
	{
		efd.setDirectory( QFileInfo( projectFileName ).absolutePath() );
		base_filename = QFileInfo( projectFileName ).completeBaseName();
	}
	else
	{
		efd.setDirectory( ConfigManager::inst()->userProjectsDir() );
		base_filename = tr( "untitled" );
	}
	efd.selectFile( base_filename + ".mid" );
	efd.setDefaultSuffix( "mid");
	efd.setWindowTitle( tr( "Select file for project-export..." ) );

	efd.setAcceptMode( FileDialog::AcceptSave );

	if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() && !efd.selectedFiles()[0].isEmpty() )
	{
		const QString suffix = ".mid";

		QString export_filename = efd.selectedFiles()[0];
		if (!export_filename.endsWith(suffix)) export_filename += suffix;

		Engine::getSong()->exportProjectMidi(export_filename);
	}
}

void MainWindow::exportProject(bool multiExport)
{
	QString const & projectFileName = Engine::getSong()->projectFileName();

	FileDialog efd( gui->mainWindow() );

	if ( multiExport )
	{
		efd.setFileMode( FileDialog::Directory);
		efd.setWindowTitle( tr( "Select directory for writing exported tracks..." ) );
		if( !projectFileName.isEmpty() )
		{
			efd.setDirectory( QFileInfo( projectFileName ).absolutePath() );
		}
	}
	else
	{
		efd.setFileMode( FileDialog::AnyFile );
		int idx = 0;
		QStringList types;
		while( ProjectRenderer::fileEncodeDevices[idx].m_fileFormat != ProjectRenderer::NumFileFormats)
		{
			if(ProjectRenderer::fileEncodeDevices[idx].isAvailable()) {
				types << tr(ProjectRenderer::fileEncodeDevices[idx].m_description);
			}
			++idx;
		}
		efd.setNameFilters( types );
		QString baseFilename;
		if( !projectFileName.isEmpty() )
		{
			efd.setDirectory( QFileInfo( projectFileName ).absolutePath() );
			baseFilename = QFileInfo( projectFileName ).completeBaseName();
		}
		else
		{
			efd.setDirectory( ConfigManager::inst()->userProjectsDir() );
			baseFilename = tr( "untitled" );
		}
		efd.selectFile( baseFilename + ProjectRenderer::fileEncodeDevices[0].m_extension );
		efd.setWindowTitle( tr( "Select file for project-export..." ) );
	}

	QString suffix = "wav";
	efd.setDefaultSuffix( suffix );
	efd.setAcceptMode( FileDialog::AcceptSave );

	if( efd.exec() == QDialog::Accepted && !efd.selectedFiles().isEmpty() &&
					 !efd.selectedFiles()[0].isEmpty() )
	{

		QString exportFileName = efd.selectedFiles()[0];
		if ( !multiExport )
		{
			int stx = efd.selectedNameFilter().indexOf( "(*." );
			int etx = efd.selectedNameFilter().indexOf( ")" );

			if ( stx > 0 && etx > stx )
			{
				// Get first extension from selected dropdown.
				// i.e. ".wav" from "WAV-File (*.wav), Dummy-File (*.dum)"
				suffix = efd.selectedNameFilter().mid( stx + 2, etx - stx - 2 ).split( " " )[0].trimmed();

				Qt::CaseSensitivity cs = Qt::CaseSensitive;
#if defined(LMMS_BUILD_APPLE) || defined(LMMS_BUILD_WIN32)
				cs = Qt::CaseInsensitive;
#endif
				exportFileName.remove( "." + suffix, cs );
				if ( efd.selectedFiles()[0].endsWith( suffix ) )
				{
					if( VersionedSaveDialog::fileExistsQuery( exportFileName + suffix,
							tr( "Save project" ) ) )
					{
						exportFileName += suffix;
					}
				}
			}
		}

		ExportProjectDialog epd( exportFileName, gui->mainWindow(), multiExport );
		epd.exec();
	}
}

void MainWindow::handleSaveResult(QString const & filename, bool songSavedSuccessfully)
{
	if (songSavedSuccessfully)
	{
		TextFloat::displayMessage( tr( "Project saved" ), tr( "The project %1 is now saved.").arg( filename ),
				embed::getIconPixmap( "project_save", 24, 24 ), 2000 );
		ConfigManager::inst()->addRecentlyOpenedProject(filename);
		resetWindowTitle();
	}
	else
	{
		TextFloat::displayMessage( tr( "Project NOT saved." ), tr( "The project %1 was not saved!" ).arg(filename),
				embed::getIconPixmap( "error" ), 4000 );
	}
}

bool MainWindow::guiSaveProject()
{
	Song * song = Engine::getSong();
	bool const songSaveResult = song->guiSaveProject();
	handleSaveResult(song->projectFileName(), songSaveResult);

	return songSaveResult;
}

bool MainWindow::guiSaveProjectAs( const QString & filename )
{
	Song * song = Engine::getSong();
	bool const songSaveResult = song->guiSaveProjectAs(filename);
	handleSaveResult(filename, songSaveResult);

	return songSaveResult;
}

void MainWindow::onExportProject()
{
	this->exportProject();
}

void MainWindow::onExportProjectTracks()
{
	this->exportProject(true);
}

void MainWindow::onImportProject()
{
	Song * song = Engine::getSong();

	if (song)
	{
		FileDialog ofd( nullptr, tr( "Import file" ),
				ConfigManager::inst()->userProjectsDir(),
				tr("MIDI sequences") +
				" (*.mid *.midi *.rmi);;" +
				tr("Hydrogen projects") +
				" (*.h2song);;" +
				tr("All file types") +
				" (*.*)");

		ofd.setFileMode( FileDialog::ExistingFiles );
		if( ofd.exec () == QDialog::Accepted && !ofd.selectedFiles().isEmpty() )
		{
			ImportFilter::import( ofd.selectedFiles()[0], song );
		}

		song->setLoadOnLaunch(false);
	}
}

void MainWindow::onSongStopped()
{
	Song * song = Engine::getSong();
	Song::PlayPos const & playPos = song->getPlayPos();

	TimeLineWidget * tl = playPos.m_timeLine;

	if( tl )
	{
		SongEditorWindow* songEditor = gui->songEditor();
		switch( tl->behaviourAtStop() )
		{
			case TimeLineWidget::BackToZero:
				if( songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollEnabled ) )
				{
					songEditor->m_editor->updatePosition(0);
				}
				break;

			case TimeLineWidget::BackToStart:
				if( tl->savedPos() >= 0 )
				{
					if(songEditor && ( tl->autoScroll() == TimeLineWidget::AutoScrollEnabled ) )
					{
						songEditor->m_editor->updatePosition( TimePos(tl->savedPos().getTicks() ) );
					}
					tl->savePos( -1 );
				}
				break;

			case TimeLineWidget::KeepStopPosition:
				break;
		}
	}
}

void MainWindow::onSongModified()
{
	// Only update the window title if the code is executed from the GUI main thread.
	// The assumption seems to be that the Song can also be set as modified from other
	// threads. This is not a good design! Copied from the original implementation of
	// Song::setModified.
	if(QThread::currentThread() == this->thread())
	{
		this->resetWindowTitle();
	}
}

void MainWindow::onProjectFileNameChanged()
{
	this->resetWindowTitle();
}

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * MidiCCRackView.cpp - implementation of the MIDI CC rack widget
 *
 * Copyright (c) 2020 Ian Caio <iancaio_dev/at/hotmail.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 "MidiCCRackView.h"

#include <QGridLayout>
#include <QMdiSubWindow>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>

#include "embed.h"
#include "GroupBox.h"
#include "GuiApplication.h"
#include "InstrumentTrack.h"
#include "Knob.h"
#include "MainWindow.h"
#include "Track.h"

MidiCCRackView::MidiCCRackView(InstrumentTrack * track) :
	QWidget(),
	m_track(track)
{
	setWindowIcon(embed::getIconPixmap("midi_cc_rack"));
	setWindowTitle(tr("MIDI CC Rack - %1").arg(m_track->name()));

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

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

	// Adjust window attributes, sizing and position
	subWin->setAttribute(Qt::WA_DeleteOnClose, false);
	subWin->resize(350, 300);
	subWin->setFixedWidth(350);
	subWin->setMinimumHeight(300);
	subWin->hide();

	// Main window layout
	QVBoxLayout *mainLayout = new QVBoxLayout(this);

	// Knobs GroupBox - Here we have the MIDI CC controller knobs for the selected track
	m_midiCCGroupBox = new GroupBox(tr("MIDI CC Knobs:"));

	// Layout to keep scrollable area under the GroupBox header
	QVBoxLayout *knobsGroupBoxLayout = new QVBoxLayout();
	knobsGroupBoxLayout->setContentsMargins(5, 16, 5, 5);

	m_midiCCGroupBox->setLayout(knobsGroupBoxLayout);

	// Scrollable area + widget + its layout that will have all the knobs
	QScrollArea *knobsScrollArea = new QScrollArea();
	QWidget *knobsArea = new QWidget();
	QGridLayout *knobsAreaLayout = new QGridLayout();

	knobsArea->setLayout(knobsAreaLayout);
	knobsScrollArea->setWidget(knobsArea);
	knobsScrollArea->setWidgetResizable(true);

	knobsGroupBoxLayout->addWidget(knobsScrollArea);

	// Adds the controller knobs
	for (int i = 0; i < MidiControllerCount; ++i)
	{
		m_controllerKnob[i] = new Knob(knobBright_26);
		m_controllerKnob[i]->setLabel(tr("CC %1").arg(i));
		knobsAreaLayout->addWidget(m_controllerKnob[i], i/4, i%4);
	}

	// Set all the models
	// Set the LED button to enable/disable the track midi cc
	m_midiCCGroupBox->setModel(m_track->m_midiCCEnable.get());

	// Set the model for each Knob
	for (int i = 0; i < MidiControllerCount; ++i)
	{
		m_controllerKnob[i]->setModel(m_track->m_midiCCModel[i].get());
	}

	// Connection to update the name of the track on the label
	connect(m_track, SIGNAL(nameChanged()),
		this, SLOT(renameWindow()));

	// Adding everything to the main layout
	mainLayout->addWidget(m_midiCCGroupBox);
}

MidiCCRackView::~MidiCCRackView()
{
	if(parentWidget())
	{
		parentWidget()->hide();
		parentWidget()->deleteLater();
	}
}

void MidiCCRackView::renameWindow()
{
	setWindowTitle(tr("MIDI CC Rack - %1").arg(m_track->name()));
}

void MidiCCRackView::saveSettings(QDomDocument & doc, QDomElement & parent)
{
}

void MidiCCRackView::loadSettings(const QDomElement &)
{
}


code:
/*
 * MidiSetupWidget - class for configuring midi sources in the settings window
 *
 * 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 "MidiSetupWidget.h"

#include <QLineEdit>

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

MidiSetupWidget::MidiSetupWidget(const QString & caption, const QString & configSection,
	const QString & devName, QWidget * parent) :
	TabWidget(TabWidget::tr("Settings for %1").arg(tr(caption.toUtf8())), parent),
	m_configSection(configSection),
	m_device(nullptr)
{
	// supply devName=QString() (distinct from QString(""))
	// to indicate that there is no editable device field
	if (!devName.isNull())
	{
		m_device = new QLineEdit(devName, this);
		m_device->setGeometry(10, 20, 160, 20);

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

void MidiSetupWidget::saveSettings()
{
	if (!m_configSection.isEmpty() && m_device)
	{
		ConfigManager::inst()->setValue(m_configSection, "device",
				m_device->text());
	}
}

void MidiSetupWidget::show()
{
	// the setup widget should only be visible if the device has some configurable attributes
	bool visible = !m_configSection.isEmpty();
	parentWidget()->setVisible(visible);
	QWidget::setVisible(visible);
}


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

#include <QWidget>

#include "ModelView.h"

ModelView::ModelView( Model* model, QWidget* widget ) :
	m_widget( widget ),
	m_model( model )
{
}

ModelView::~ModelView()
{
	if( m_model != NULL && m_model->isDefaultConstructed() )
	{
		delete m_model;
	}
}

void ModelView::setModel( Model* model, bool isOldModelValid )
{
	if( isOldModelValid && m_model != NULL )
	{
		if( m_model->isDefaultConstructed() )
		{
			delete m_model;
		}
		else
		{
			m_model->disconnect( widget() );
		}
	}

	m_model = model;

	doConnections();

	widget()->update();

	modelChanged();
}

// 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 ModelView::unsetModel()
{
	setModel(new Model(nullptr, QString(), true));
}

void ModelView::doConnections()
{
	if( m_model != NULL )
	{
		QObject::connect( m_model, SIGNAL( dataChanged() ), widget(), SLOT( update() ) );
		QObject::connect( m_model, SIGNAL( propertiesChanged() ), widget(), SLOT( update() ) );
	}
}


code:
/*
 * PeakControllerDialog.cpp - per-controller-specific view for changing a
 * controller's settings
 *
 * Copyright (c) 2008-2009 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 <QLabel>
#include <QPushButton>
#include <QMdiArea>
#include <QPainter>

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

#include "PeakController.h"
#include "Knob.h"
#include "TempoSyncKnob.h"

PeakControllerDialog::PeakControllerDialog( Controller * _model, QWidget * _parent ) :
	ControllerDialog( _model, _parent )
{
	setWindowTitle( tr( "PEAK" ) );
	setWindowIcon( embed::getIconPixmap( "controller" ) );
	setFixedSize( 256, 64 );

	ToolTip::add( this, tr( "LFO Controller" ) );

	QLabel * l = new QLabel( this );
	l->setText( "Use FX's controls" );
	l->move(10, 10);

	setModel( _model );
}

PeakControllerDialog::~PeakControllerDialog()
{
}

/*
void effectView::closeEffects()
{
	m_subWindow->hide();
	m_show = true;
}
*/

void PeakControllerDialog::contextMenuEvent( QContextMenuEvent * )
{
}

void PeakControllerDialog::paintEvent( QPaintEvent * )
{
	QPainter p( this );
}

void PeakControllerDialog::modelChanged()
{
	m_peakController = castModel<PeakController>();
}


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

/** \file piano.cpp
 *  \brief A piano keyboard to play notes on in the instrument plugin window.
 */

/*
 * \mainpage Instrument plugin keyboard display classes
 *
 * \section introduction Introduction
 *
 * \todo fill this out
 * \todo write isWhite inline function and replace throughout
 */

#include <cmath>

#include <QCursor>
#include <QKeyEvent>
#include <QPainter>
#include <QVBoxLayout>

#include "PianoView.h"
#include "Piano.h"
#include "CaptionMenu.h"
#include "embed.h"
#include "gui_templates.h"
#include "InstrumentTrack.h"
#include "Knob.h"
#include "StringPairDrag.h"
#include "MainWindow.h"

/*! The scale of C Major - white keys only.
 */
Keys WhiteKeys[] =
{
	Key_C, Key_D, Key_E, Key_F, Key_G, Key_A, Key_H
} ;

QPixmap * PianoView::s_whiteKeyPm = NULL;           /*!< A white key released */
QPixmap * PianoView::s_blackKeyPm = NULL;           /*!< A black key released */
QPixmap * PianoView::s_whiteKeyPressedPm = NULL;    /*!< A white key pressed */
QPixmap * PianoView::s_blackKeyPressedPm = NULL;    /*!< A black key pressed */

const int PIANO_BASE = 11;          /*!< The height of the root note display */
const int PW_WHITE_KEY_WIDTH = 10;  /*!< The width of a white key */
const int PW_BLACK_KEY_WIDTH = 8;   /*!< The width of a black key */
const int PW_WHITE_KEY_HEIGHT = 57; /*!< The height of a white key */
const int PW_BLACK_KEY_HEIGHT = 38; /*!< The height of a black key */
const int LABEL_TEXT_SIZE = 7;      /*!< The height of the key label text */

/*! \brief Create a new keyboard display view
 *
 *  \param _parent the parent instrument plugin window
 *  \todo are the descriptions of the m_startkey and m_lastkey properties correct?
 */
PianoView::PianoView( QWidget * _parent ) :
	QWidget( _parent ),             /*!< Our parent */
	ModelView( NULL, this ),        /*!< Our view Model */
	m_piano( NULL ),                /*!< Our piano Model */
	m_startKey( Key_C + Octave_3*KeysPerOctave ), /*!< The first key displayed? */
	m_lastKey( -1 )                 /*!< The last key displayed? */
{
	if( s_whiteKeyPm == NULL )
	{
		s_whiteKeyPm = new QPixmap( embed::getIconPixmap( "white_key" ) );
	}
	if( s_blackKeyPm == NULL )
	{
		s_blackKeyPm = new QPixmap( embed::getIconPixmap( "black_key" ) );
	}
	if( s_whiteKeyPressedPm == NULL )
	{
		s_whiteKeyPressedPm = new QPixmap( embed::getIconPixmap( "white_key_pressed" ) );
	}
	if ( s_blackKeyPressedPm == NULL )
	{
		s_blackKeyPressedPm = new QPixmap( embed::getIconPixmap( "black_key_pressed" ) );
	}

	setAttribute( Qt::WA_OpaquePaintEvent, true );
	setFocusPolicy( Qt::StrongFocus );
	setMaximumWidth( WhiteKeysPerOctave * NumOctaves * PW_WHITE_KEY_WIDTH );

	// create scrollbar at the bottom
	m_pianoScroll = new QScrollBar( Qt::Horizontal, this );
	m_pianoScroll->setSingleStep( 1 );
	m_pianoScroll->setPageStep( 20 );
	m_pianoScroll->setValue( Octave_3 * WhiteKeysPerOctave );

	// and connect it to this widget
	connect( m_pianoScroll, SIGNAL( valueChanged( int ) ),
			this, SLOT( pianoScrolled( int ) ) );

	// create a layout for ourselves
	QVBoxLayout * layout = new QVBoxLayout( this );
	layout->setSpacing( 0 );
	layout->setMargin( 0 );
	layout->addSpacing( PIANO_BASE+PW_WHITE_KEY_HEIGHT );
	layout->addWidget( m_pianoScroll );

}

/*! \brief Map a keyboard key being pressed to a note in our keyboard view
 *
 *  \param _k The keyboard scan code of the key being pressed.
 *  \todo check the scan codes for ',' = c, 'L' = c#, '.' = d, ':' = d#,
 *     '/' = d, '[' = f', '=' = f'#, ']' = g' - Paul's additions
 */
int PianoView::getKeyFromKeyEvent( QKeyEvent * _ke )
{
#ifdef LMMS_BUILD_APPLE
	const int k = _ke->nativeVirtualKey();
#else
	const int k = _ke->nativeScanCode();
#endif

#ifdef LMMS_BUILD_WIN32
	switch( k )
	{
		case 44: return 0; // Z  = C
		case 31: return 1; // S  = C#
		case 45: return 2; // X  = D
		case 32: return 3; // D  = D#
		case 46: return 4; // C  = E
		case 47: return 5; // V  = F
		case 34: return 6; // G  = F#
		case 48: return 7; // B  = G
		case 35: return 8; // H  = G#
		case 49: return 9; // N  = A
		case 36: return 10; // J = A#
		case 50: return 11; // M = B
		case 51: return 12; // , = c
		case 38: return 13; // L = c#
		case 52: return 14; // . = d
		case 39: return 15; // ; = d#
		//case 86: return 16; // / = e
		case 53: return 16; // / = e
		case 16: return 12; // Q = c
		case 3: return 13; // 2 = c#
		case 17: return 14; // W = d
		case 4: return 15; // 3 = d#
		case 18: return 16; // E = e
		case 19: return 17; // R = f
		case 6: return 18; // 5 = f#
		case 20: return 19; // T = g
		case 7: return 20; // 6 = g#
		case 21: return 21; // Y = a
		case 8: return 22; // 7 = a#
		case 22: return 23; // U = b
		case 23: return 24; // I = c'
		case 10: return 25; // 9 = c'#
		case 24: return 26; // O = d'
		case 11: return 27; // 0 = d'#
		case 25: return 28; // P = e'
		case 26: return 29; // [
		case 13: return 30; // =
		case 27: return 31; // ]
	}
#endif
#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_OPENBSD) || defined(LMMS_BUILD_FREEBSD)
	switch( k )
	{
		case 52: return 0; // Z  = C
		case 39: return 1; // S  = C#
		case 53: return 2; // X  = D
		case 40: return 3; // D  = D#
		case 54: return 4; // C  = E
		case 55: return 5; // V  = F
		case 42: return 6; // G  = F#
		case 56: return 7; // B  = G
		case 43: return 8; // H  = G#
		case 57: return 9; // N  = A
		case 44: return 10; // J = A#
		case 58: return 11; // M = B
		case 59: return 12; // , = c
		case 46: return 13; // L = c#
		case 60: return 14; // . = d
		case 47: return 15; // ; = d#
		case 61: return 16; // / = e
		case 24: return 12; // Q = c
		case 11: return 13; // 2 = c#
		case 25: return 14; // W = d
		case 12: return 15; // 3 = d#
		case 26: return 16; // E = e
		case 27: return 17; // R = f
		case 14: return 18; // 5 = f#
		case 28: return 19; // T = g
		case 15: return 20; // 6 = g#
		case 29: return 21; // Y = a
		case 16: return 22; // 7 = a#
		case 30: return 23; // U = b
		case 31: return 24; // I = c'
		case 18: return 25; // 9 = c'#
		case 32: return 26; // O = d'
		case 19: return 27; // 0 = d'#
		case 33: return 28; // P = e'
		case 34: return 29; // [
		case 21: return 30; // =
		case 35: return 31; // ]
	}
#endif
#ifdef LMMS_BUILD_APPLE
	switch( k )
	{
		case 6: return 0; // Z  = C
		case 1: return 1; // S  = C#
		case 7: return 2; // X  = D
		case 2: return 3; // D  = D#
		case 8: return 4; // C  = E
		case 9: return 5; // V  = F
		case 5: return 6; // G  = F#
		case 11: return 7; // B  = G
		case 4: return 8; // H  = G#
		case 45: return 9; // N  = A
		case 38: return 10; // J = A#
		case 46: return 11; // M = B
		case 43: return 12; // , = c
		case 37: return 13; // L = c#
		case 47: return 14; // . = d
		case 41: return 15; // ; = d#
		case 44: return 16; // / = e
		case 12: return 12; // Q = c
		case 19: return 13; // 2 = c#
		case 13: return 14; // W = d
		case 20: return 15; // 3 = d#
		case 14: return 16; // E = e
		case 15: return 17; // R = f
		case 23: return 18; // 5 = f#
		case 17: return 19; // T = g
		case 22: return 20; // 6 = g#
		case 16: return 21; // Y = a
		case 26: return 22; // 7 = a#
		case 32: return 23; // U = b
		case 34: return 24; // I = c'
		case 25: return 25; // 9 = c'#
		case 31: return 26; // O = d'
		case 29: return 27; // 0 = d'#
		case 35: return 28; // P = e'
	}
#endif

	return -100;
}

/*! \brief Register a change to this piano display view
 *
 */
void PianoView::modelChanged()
{
	m_piano = castModel<Piano>();
	if( m_piano != NULL )
	{
		connect( m_piano->instrumentTrack()->baseNoteModel(), SIGNAL( dataChanged() ),
					this, SLOT( update() ) );
	}

}

// gets the key from the given mouse-position
/*! \brief Get the key from the mouse position in the piano display
 *
 *  First we determine it roughly by the position of the point given in
 *  white key widths from our start.  We then add in any black keys that
 *  might have been skipped over (they take a key number, but no 'white
 *  key' space).  We then add in our starting key number.
 *
 *  We then determine whether it was a black key that was pressed by
 *  checking whether it was within the vertical range of black keys.
 *  Black keys sit exactly between white keys on this keyboard, so
 *  we then shift the note down or up if we were in the left or right
 *  half of the white note.  We only do this, of course, if the white
 *  note has a black key on that side, so to speak.
 *
 *  This function returns const because there is a linear mapping from
 *  the point given to the key returned that never changes.
 *
 *  \param _p The point that the mouse was pressed.
 */
int PianoView::getKeyFromMouse( const QPoint & _p ) const
{
	int offset = _p.x() % PW_WHITE_KEY_WIDTH;
	if( offset < 0 ) offset += PW_WHITE_KEY_WIDTH;
	int key_num = ( _p.x() - offset) / PW_WHITE_KEY_WIDTH;

	for( int i = 0; i <= key_num; ++i )
	{
		if ( Piano::isBlackKey( m_startKey+i ) )
		{
			++key_num;
		}
	}
	for( int i = 0; i >= key_num; --i )
	{
		if ( Piano::isBlackKey( m_startKey+i ) )
		{
			--key_num;
		}
	}

	key_num += m_startKey;

	// is it a black key?
	if( _p.y() < PIANO_BASE + PW_BLACK_KEY_HEIGHT )
	{
		// then do extra checking whether the mouse-cursor is over
		// a black key
		if( key_num > 0 && Piano::isBlackKey( key_num-1 ) &&
			offset <= ( PW_WHITE_KEY_WIDTH / 2 ) -
					( PW_BLACK_KEY_WIDTH / 2 ) )
		{
			--key_num;
		}
		if( key_num < NumKeys - 1 && Piano::isBlackKey( key_num+1 ) &&
			offset >= ( PW_WHITE_KEY_WIDTH -
					PW_BLACK_KEY_WIDTH / 2 ) )
		{
			++key_num;
		}
	}

	// some range-checking-stuff
	return qBound( 0, key_num, NumKeys - 1 );
}

// handler for scrolling-event
/*! \brief Handle the scrolling on the piano display view
 *
 *  We need to update our start key position based on the new position.
 *
 *  \param _new_pos the new key position.
 */
void PianoView::pianoScrolled( int _new_pos )
{
	m_startKey = WhiteKeys[_new_pos % WhiteKeysPerOctave]+
			( _new_pos / WhiteKeysPerOctave ) * KeysPerOctave;

	update();
}

/*! \brief Handle a context menu selection on the piano display view
 *
 *  \param _me the ContextMenuEvent to handle.
 *  \todo Is this right, or does this create the context menu?
 */
void PianoView::contextMenuEvent( QContextMenuEvent * _me )
{
	if( _me->pos().y() > PIANO_BASE || m_piano == NULL )
	{
		QWidget::contextMenuEvent( _me );
		return;
	}

	CaptionMenu contextMenu( tr( "Base note" ) );
	AutomatableModelView amv( m_piano->instrumentTrack()->baseNoteModel(), &contextMenu );
	amv.addDefaultActions( &contextMenu );
	contextMenu.exec( QCursor::pos() );
}

// handler for mouse-click-event
/*! \brief Handle a mouse click on this piano display view
 *
 *  We first determine the key number using the getKeyFromMouse() method.
 *
 *  If we're below the 'root key selection' area,
 *  we set the volume of the note to be proportional to the vertical
 *  position on the keyboard - lower down the key is louder, within the
 *  boundaries of the (white or black) key pressed.  We then tell the
 *  instrument to play that note, scaling for MIDI max loudness = 127.
 *
 *  If we're in the 'root key selection' area, of course, we set the
 *  root key to be that key.
 *
 *  We finally update ourselves to show the key press
 *
 *  \param _me the mouse click to handle.
 */
void PianoView::mousePressEvent( QMouseEvent * _me )
{
	if( _me->button() == Qt::LeftButton && m_piano != NULL )
	{
		// get pressed key
		int key_num = getKeyFromMouse( _me->pos() );
		if( _me->pos().y() > PIANO_BASE )
		{
			int y_diff = _me->pos().y() - PIANO_BASE;
			int velocity = (int)( ( float ) y_diff /
				( Piano::isWhiteKey( key_num )  ?
				PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) *
						(float) m_piano->instrumentTrack()->midiPort()->baseVelocity() );
			if( y_diff < 0 )
			{
				velocity = 0;
			}
			else if( y_diff >
				( Piano::isWhiteKey( key_num ) ?
				PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) )
			{
				velocity = m_piano->instrumentTrack()->midiPort()->baseVelocity();
			}
			// set note on
			m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOn, -1, key_num, velocity ) );
			m_piano->setKeyState( key_num, true );
			m_lastKey = key_num;

			emit keyPressed( key_num );
		}
		else
		{
			if( _me->modifiers() & Qt::ControlModifier )
			{
				new StringPairDrag( "automatable_model",
					QString::number( m_piano->instrumentTrack()->baseNoteModel()->id() ),
					QPixmap(), this );
				_me->accept();
			}
			else
			{
				m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num );

				emit baseNoteChanged();
			}
		}

		// and let the user see that he pressed a key... :)
		update();
	}
}

// handler for mouse-release-event
/*! \brief Handle a mouse release event on the piano display view
 *
 *  If a key was pressed by the in the mousePressEvent() function, we
 *  turn the note off.
 *
 *  \param _me the mousePressEvent to handle.
 */
void PianoView::mouseReleaseEvent( QMouseEvent * )
{
	if( m_lastKey != -1 )
	{
		if( m_piano != NULL )
		{
			m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, m_lastKey, 0 ) );
			m_piano->setKeyState( m_lastKey, false );
		}

		// and let the user see that he released a key... :)
		update();

		m_lastKey = -1;
	}
}

// handler for mouse-move-event
/*! \brief Handle a mouse move event on the piano display view
 *
 *  This handles the user dragging the mouse across the keys.  It uses
 *  code from mousePressEvent() and mouseReleaseEvent(), also correcting
 *  for if the mouse movement has stayed within one key and if the mouse
 *  has moved outside the vertical area of the keyboard (which is still
 *  allowed but won't make the volume go up to 11).
 *
 *  \param _me the ContextMenuEvent to handle.
 *  \todo Paul Wayper thinks that this code should be refactored to
 *  reduce or remove the duplication between this, the mousePressEvent()
 *  and mouseReleaseEvent() methods.
 */
void PianoView::mouseMoveEvent( QMouseEvent * _me )
{
	if( m_piano == NULL )
	{
		return;
	}

	int key_num = getKeyFromMouse( _me->pos() );
	int y_diff = _me->pos().y() - PIANO_BASE;
	int velocity = (int)( (float) y_diff /
		( Piano::isWhiteKey( key_num ) ?
			PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) *
						(float) m_piano->instrumentTrack()->midiPort()->baseVelocity() );
	// maybe the user moved the mouse-cursor above or under the
	// piano-widget while holding left button so check that and
	// correct volume if necessary
	if( y_diff < 0 )
	{
		velocity = 0;
	}
	else if( y_diff >
		( Piano::isWhiteKey( key_num ) ?
				PW_WHITE_KEY_HEIGHT : PW_BLACK_KEY_HEIGHT ) )
	{
		velocity = m_piano->instrumentTrack()->midiPort()->baseVelocity();
	}

	// is the calculated key different from current key? (could be the
	// user just moved the cursor one pixel left but on the same key)
	if( key_num != m_lastKey )
	{
		if( m_lastKey != -1 )
		{
			m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, m_lastKey, 0 ) );
			m_piano->setKeyState( m_lastKey, false );
			m_lastKey = -1;
		}
		if( _me->buttons() & Qt::LeftButton )
		{
			if( _me->pos().y() > PIANO_BASE )
			{
				m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOn, -1, key_num, velocity ) );
				m_piano->setKeyState( key_num, true );
				m_lastKey = key_num;
			}
			else
			{
				m_piano->instrumentTrack()->baseNoteModel()->setInitValue( (float) key_num );
			}
		}
		// and let the user see that he pressed a key... :)
		update();
	}
	else if( m_piano->isKeyPressed( key_num ) )
	{
		m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiKeyPressure, -1, key_num, velocity ) );
	}

}

/*! \brief Handle a key press event on the piano display view
 *
 *  We determine our key number from the getKeyFromKeyEvent() method,
 *  and pass the event on to the piano's handleKeyPress() method if
 *  auto-repeat is off.
 *
 *  \param _ke the KeyEvent to handle.
 */
void PianoView::keyPressEvent( QKeyEvent * _ke )
{
	const int key_num = getKeyFromKeyEvent( _ke ) +
				( DefaultOctave - 1 ) * KeysPerOctave;

	if( _ke->isAutoRepeat() == false && key_num > -1 )
	{
		if( m_piano != NULL )
		{
			m_piano->handleKeyPress( key_num );
			_ke->accept();
			update();
		}
	}
	else
	{
		_ke->ignore();
	}
}

/*! \brief Handle a key release event on the piano display view
 *
 *  The same logic as the keyPressEvent() method.
 *
 *  \param _ke the KeyEvent to handle.
 */
void PianoView::keyReleaseEvent( QKeyEvent * _ke )
{
	const int key_num = getKeyFromKeyEvent( _ke ) +
				( DefaultOctave - 1 ) * KeysPerOctave;
	if( _ke->isAutoRepeat() == false && key_num > -1 )
	{
		if( m_piano != NULL )
		{
			m_piano->handleKeyRelease( key_num );
			_ke->accept();
			update();
		}
	}
	else
	{
		_ke->ignore();
	}
}

/*! \brief Handle the focus leaving the piano display view
 *
 *  Turn off all notes if we lose focus.
 *
 *  \todo Is there supposed to be a parameter given here?
 */
void PianoView::focusOutEvent( QFocusEvent * )
{
	if( m_piano == NULL )
	{
		return;
	}

	// focus just switched to another control inside the instrument track
	// window we live in?
	if( parentWidget()->parentWidget()->focusWidget() != this &&
		parentWidget()->parentWidget()->focusWidget() != NULL &&
		!(parentWidget()->parentWidget()->
				focusWidget()->inherits( "QLineEdit" ) ||
		parentWidget()->parentWidget()->
				focusWidget()->inherits( "QPlainTextEdit" ) ))
	{
		// then reclaim keyboard focus!
		setFocus();
		return;
	}

	// if we loose focus, we HAVE to note off all running notes because
	// we don't receive key-release-events anymore and so the notes would
	// hang otherwise
	for( int i = 0; i < NumKeys; ++i )
	{
		m_piano->midiEventProcessor()->processInEvent( MidiEvent( MidiNoteOff, -1, i, 0 ) );
		m_piano->setKeyState( i, false );
	}

	update();
}

void PianoView::focusInEvent( QFocusEvent * )
{
	m_piano->instrumentTrack()->autoAssignMidiDevice(true);
}

/*! \brief update scrollbar range after resize
 *
 *  After resizing we need to adjust range of scrollbar for not allowing
 *  to scroll too far to the right.
 *
 *  \param _event resize-event object (unused)
 */
void PianoView::resizeEvent( QResizeEvent * _event )
{
	QWidget::resizeEvent( _event );
	m_pianoScroll->setRange( 0, WhiteKeysPerOctave * NumOctaves -
					(int) ceil( (float) width() /
							PW_WHITE_KEY_WIDTH ) );
}

/*! \brief Convert a key number to an X coordinate in the piano display view
 *
 *  We can immediately discard the trivial case of when the key number is
 *  less than our starting key.  We then iterate through the keys from the
 *  start key to this key, adding the width of each key as we go.  For
 *  black keys, and the first white key if there is no black key between
 *  two white keys, we add half a white key width; for that second white
 *  key, we add a whole width.  That takes us to the boundary of a white
 *  key - subtract half a width to get to the middle.
 *
 *  \param _key_num the keyboard key to translate
 *  \todo is this description of what the method does correct?
 *  \todo replace the final subtract with initialising x to width/2.
 */
int PianoView::getKeyX( int _key_num ) const
{
	int k = m_startKey;
	if( _key_num < m_startKey )
	{
		return ( _key_num - k ) * PW_WHITE_KEY_WIDTH / 2;
	}

	int x = 0;
	int white_cnt = 0;

	while( k <= _key_num )
	{
		if( Piano::isWhiteKey( k ) )
		{
			++white_cnt;
			if( white_cnt > 1 )
			{
				x += PW_WHITE_KEY_WIDTH;
			}
			else
			{
				x += PW_WHITE_KEY_WIDTH/2;
			}
		}
		else
		{
			white_cnt = 0;
			x += PW_WHITE_KEY_WIDTH/2;
		}
		++k;
	}

	x -= PW_WHITE_KEY_WIDTH / 2;

	return x;

}

/*! \brief Paint the piano display view in response to an event
 *
 *  This method draws the piano and the 'root note' base.  It draws
 *  the base first, then all the white keys, then all the black keys.
 *
 *  \todo Is there supposed to be a parameter given here?
 */
void PianoView::paintEvent( QPaintEvent * )
{
	QPainter p( this );

	// set smaller font for printing number of every octave
	p.setFont( pointSize<LABEL_TEXT_SIZE>( p.font() ) );

	// draw bar above the keyboard (there will be the labels
	// for all C's)
	p.fillRect( QRect( 0, 1, width(), PIANO_BASE-2 ), p.background() );

	// draw the line above the keyboard
	p.setPen( Qt::black );
	p.drawLine( 0, 0, width(), 0 );
	p.drawLine( 0, PIANO_BASE-1, width(), PIANO_BASE-1 );

	p.setPen( Qt::white );

	const int base_key = ( m_piano != NULL ) ?
		m_piano->instrumentTrack()->baseNoteModel()->value() : 0;

	QColor baseKeyColor = QApplication::palette().color( QPalette::Active,
							QPalette::BrightText );
	if( Piano::isWhiteKey( base_key ) )
	{
		p.fillRect( QRect( getKeyX( base_key ), 1, PW_WHITE_KEY_WIDTH-1,
							PIANO_BASE-2 ), baseKeyColor );
	}
	else
	{
		p.fillRect( QRect( getKeyX( base_key ) + 1, 1,
				PW_BLACK_KEY_WIDTH - 1, PIANO_BASE - 2 ), baseKeyColor);
	}

	int cur_key = m_startKey;

	// draw all white keys...
	for( int x = 0; x < width(); )
	{
		while( Piano::isBlackKey( cur_key ) )
		{
			++cur_key;
		}

		// draw pressed or not pressed key, depending on state of
		// current key
		if( m_piano && m_piano->isKeyPressed( cur_key ) )
		{
			p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPressedPm );
		}
		else
		{
			p.drawPixmap( x, PIANO_BASE, *s_whiteKeyPm );
		}

		x += PW_WHITE_KEY_WIDTH;

		if( (Keys) (cur_key%KeysPerOctave) == Key_C )
		{
			// label key of note C with "C" and number of current
			// octave
			p.drawText( x - PW_WHITE_KEY_WIDTH, LABEL_TEXT_SIZE + 2,
					QString( "C" ) + QString::number(
					cur_key / KeysPerOctave, 10 ) );
		}
		++cur_key;
	}

	// reset all values, because now we're going to draw all black keys
	cur_key = m_startKey;
	int white_cnt = 0;

	int startKey = m_startKey;
	if( startKey > 0 && Piano::isBlackKey( (Keys)(--startKey) ) )
	{
		if( m_piano && m_piano->isKeyPressed( startKey ) )
		{
			p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm );
		}
		else
		{
			p.drawPixmap( 0 - PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm );
		}
	}

	// now draw all black keys...
	for( int x = 0; x < width(); )
	{
		if( Piano::isBlackKey( cur_key ) )
		{
			// draw pressed or not pressed key, depending on
			// state of current key
			if( m_piano && m_piano->isKeyPressed( cur_key ) )
			{
				p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPressedPm );
			}
			else
			{
				p.drawPixmap( x + PW_WHITE_KEY_WIDTH / 2, PIANO_BASE, *s_blackKeyPm );
			}
			x += PW_WHITE_KEY_WIDTH;
			white_cnt = 0;
		}
		else
		{
			// simple workaround for increasing x if there were two
			// white keys (e.g. between E and F)
			++white_cnt;
			if( white_cnt > 1 )
			{
				x += PW_WHITE_KEY_WIDTH;
			}
		}
		++cur_key;
	}
}


code:
/*
 * PluginBrowser.cpp - implementation of the plugin-browser
 *
 * 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 "PluginBrowser.h"

#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QMouseEvent>
#include <QPainter>
#include <QStyleOption>
#include <QTreeWidget>

#include "embed.h"
#include "Engine.h"
#include "gui_templates.h"
#include "StringPairDrag.h"
#include "PluginFactory.h"

PluginBrowser::PluginBrowser( QWidget * _parent ) :
	SideBarWidget( tr( "Instrument Plugins" ),
				embed::getIconPixmap( "plugins" ).transformed( QTransform().rotate( 90 ) ), _parent )
{
	setWindowTitle( tr( "Instrument browser" ) );
	m_view = new QWidget( contentParent() );
	//m_view->setFrameShape( QFrame::NoFrame );

	addContentWidget( m_view );

	QVBoxLayout * view_layout = new QVBoxLayout( m_view );
	view_layout->setMargin( 5 );
	view_layout->setSpacing( 5 );

	auto hint = new QLabel( tr( "Drag an instrument "
					"into either the Song-Editor, the "
					"Beat+Bassline Editor or into an "
					"existing instrument track." ),
								m_view );
	hint->setWordWrap( true );

	QLineEdit * searchBar = new QLineEdit( m_view );
	searchBar->setPlaceholderText( "Search" );
	searchBar->setMaxLength( 64 );
	searchBar->setClearButtonEnabled( true );

	m_descTree = new QTreeWidget( m_view );
	m_descTree->setColumnCount( 1 );
	m_descTree->header()->setVisible( false );
	m_descTree->setIndentation( 10 );
	m_descTree->setSelectionMode( QAbstractItemView::NoSelection );

	connect( searchBar, SIGNAL( textEdited( const QString & ) ),
			this, SLOT( onFilterChanged( const QString & ) ) );

	view_layout->addWidget( hint );
	view_layout->addWidget( searchBar );
	view_layout->addWidget( m_descTree );

	// Add plugins to the tree
	addPlugins();

	// Resize
	m_descTree->header()->setSectionResizeMode( QHeaderView::ResizeToContents );

	// Hide empty roots
	updateRootVisibilities();
}

void PluginBrowser::updateRootVisibility( int rootIndex )
{
	QTreeWidgetItem * root = m_descTree->topLevelItem( rootIndex );
	root->setHidden( !root->childCount() );
}

void PluginBrowser::updateRootVisibilities()
{
	int rootCount = m_descTree->topLevelItemCount();
	for (int rootIndex = 0; rootIndex < rootCount; ++rootIndex)
	{
		updateRootVisibility( rootIndex );
	}
}

void PluginBrowser::onFilterChanged( const QString & filter )
{
	int rootCount = m_descTree->topLevelItemCount();
	for (int rootIndex = 0; rootIndex < rootCount; ++rootIndex)
	{
		QTreeWidgetItem * root = m_descTree->topLevelItem( rootIndex );

		int itemCount = root->childCount();
		for (int itemIndex = 0; itemIndex < itemCount; ++itemIndex)
		{
			QTreeWidgetItem * item = root->child( itemIndex );
			PluginDescWidget * descWidget = static_cast<PluginDescWidget *>
							(m_descTree->itemWidget( item, 0));
			if (descWidget->name().contains(filter, Qt::CaseInsensitive))
			{
				item->setHidden( false );
			}
			else
			{
				item->setHidden( true );
			}
		}
	}
}

void PluginBrowser::addPlugins()
{
	// Add a root node to the plugin tree with the specified `label` and return it
	const auto addRoot = [this](auto label)
	{
		const auto root = new QTreeWidgetItem();
		root->setText(0, label);
		m_descTree->addTopLevelItem(root);
		return root;
	};

	// Add the plugin identified by `key` to the tree under the root node `root`
	const auto addPlugin = [this](const auto& key, auto root)
	{
		const auto item = new QTreeWidgetItem();
		root->addChild(item);
		m_descTree->setItemWidget(item, 0, new PluginDescWidget(key, m_descTree));
	};

	// Remove any existing plugins from the tree
	m_descTree->clear();

	// Fetch and sort all instrument plugin descriptors
	auto descs = pluginFactory->descriptors(Plugin::Instrument);
	std::sort(descs.begin(), descs.end(),
		[](auto d1, auto d2)
		{
			return qstricmp(d1->displayName, d2->displayName) < 0;
		}
	);

	// Add a root node to the tree for native LMMS plugins
	const auto lmmsRoot = addRoot("LMMS");
	lmmsRoot->setExpanded(true);

	// Add all of the descriptors to the tree
	for (const auto desc : descs)
	{
		if (desc->subPluginFeatures)
		{
			// Fetch and sort all subplugins for this plugin descriptor
			auto subPluginKeys = Plugin::Descriptor::SubPluginFeatures::KeyList{};
			desc->subPluginFeatures->listSubPluginKeys(desc, subPluginKeys);
			std::sort(subPluginKeys.begin(), subPluginKeys.end(),
				[](const auto& l, const auto& r)
				{
					return QString::compare(l.displayName(), r.displayName(), Qt::CaseInsensitive) < 0;
				}
			);

			// Create a root node for this plugin and add the subplugins under it
			const auto root = addRoot(desc->displayName);
			for (const auto& key : subPluginKeys) { addPlugin(key, root); }
		}
		else
		{
			addPlugin(Plugin::Descriptor::SubPluginFeatures::Key(desc, desc->name), lmmsRoot);
		}
	}
}

PluginDescWidget::PluginDescWidget(const PluginKey &_pk,
							QWidget * _parent ) :
	QWidget( _parent ),
	m_pluginKey( _pk ),
	m_logo( _pk.logo()->pixmap() ),
	m_mouseOver( false )
{
	setFixedHeight( DEFAULT_HEIGHT );
	setMouseTracking( true );
	setCursor( Qt::PointingHandCursor );
	setToolTip(_pk.desc->subPluginFeatures
		? _pk.description()
		: tr(_pk.desc->description));
}

QString PluginDescWidget::name() const
{
	return m_pluginKey.displayName();
}

void PluginDescWidget::paintEvent( QPaintEvent * )
{

	QPainter p( this );

	// Paint everything according to the style sheet
	QStyleOption o;
	o.initFrom( this );
	style()->drawPrimitive( QStyle::PE_Widget, &o, &p, this );

	// Draw the rest
	const int s = 16 + ( 32 * ( qBound( 24, height(), 60 ) - 24 ) ) /
								( 60 - 24 );
	const QSize logo_size( s, s );
	QPixmap logo = m_logo.scaled( logo_size, Qt::KeepAspectRatio,
						Qt::SmoothTransformation );
	p.drawPixmap( 4, 4, logo );

	QFont f = p.font();
	if ( m_mouseOver )
	{
		f.setBold( true );
	}

	p.setFont( f );
	p.drawText( 10 + logo_size.width(), 15, m_pluginKey.displayName());
}

void PluginDescWidget::enterEvent( QEvent * _e )
{
	m_mouseOver = true;

	QWidget::enterEvent( _e );
}

void PluginDescWidget::leaveEvent( QEvent * _e )
{
	m_mouseOver = false;

	QWidget::leaveEvent( _e );
}

void PluginDescWidget::mousePressEvent( QMouseEvent * _me )
{
	if ( _me->button() == Qt::LeftButton )
	{
		Engine::setDndPluginKey(&m_pluginKey);
		new StringPairDrag("instrument",
			QString::fromUtf8(m_pluginKey.desc->name), m_logo, this);
		leaveEvent( _me );
	}
}


code:
/*
 * RowTableView.cpp - table with rows that act like single cells
 *
 * Copyright (c) 2016 Javier Serrano Polo <javier@jasp.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 "RowTableView.h"

#include <QKeyEvent>
#include <QPainter>
#include <QStyledItemDelegate>

class RowDelegate : public QStyledItemDelegate
{
public:
	RowDelegate( QAbstractItemView * table, QObject * parent = 0 ) :
		QStyledItemDelegate( parent ),
		m_table( table )
		{
		}
	virtual void paint( QPainter * painter,
					const QStyleOptionViewItem & option,
					const QModelIndex & index ) const override;

protected:
	virtual void initStyleOption( QStyleOptionViewItem * option,
					const QModelIndex & index ) const override;

private:
	QAbstractItemView * m_table;

} ;

void RowDelegate::initStyleOption( QStyleOptionViewItem * option,
					const QModelIndex & index ) const
{
	QStyledItemDelegate::initStyleOption( option, index );
	option->state &= ~QStyle::State_HasFocus;
}

void RowDelegate::paint( QPainter * painter,
	const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
	QStyledItemDelegate::paint( painter, option, index );
	if ( index.row() == m_table->currentIndex().row() )
	{
		const QRect rect( option.rect );
		painter->drawLine( rect.topLeft(), rect.topRight() );
		painter->drawLine( rect.bottomLeft(), rect.bottomRight() );
		if ( index.column() == 0 )
		{
			painter->drawLine( rect.topLeft(), rect.bottomLeft() );
		}
		if ( index.column() == index.model()->columnCount() - 1 )
		{
			painter->drawLine( rect.topRight(),
							rect.bottomRight() );
		}
	}
}

RowTableView::RowTableView( QWidget * parent ) :
	QTableView( parent )
{
	m_rowDelegate = new RowDelegate( this, this );
}

RowTableView::~RowTableView()
{
	delete m_rowDelegate;
}

void RowTableView::setModel( QAbstractItemModel * model )
{
	QTableView::setModel( model );
	for ( int i = 0; i < model->rowCount(); i++ )
	{
		setItemDelegateForRow( i, m_rowDelegate );
	}

}

void RowTableView::keyPressEvent( QKeyEvent * event )
{
	switch( event->key() )
	{
		case Qt::Key_Tab:
		case Qt::Key_Backtab:
			for( int i = 0; i < model()->columnCount() - 1; i++ )
			{
				QTableView::keyPressEvent( event );
			}
		default:
			QTableView::keyPressEvent( event );
	}
}