A Virtual Piano Keyboard GUI application.
 
 
 
 
#include <QDir>
#include <QFileInfo>
#include <QFontDialog>
#include <QInputDialog>
#include <QMessageBox>
#include <QActionGroup>
#if defined(Q_OS_MACOS)
#include <CoreFoundation/CoreFoundation.h>
#endif
 
 
#include "connections.h"
#include "preferences.h"
#include "vpiano.h"
#include "vpianoabout.h"
#include "vpianosettings.h"
 
 
VPiano::VPiano(QWidget *parent, Qt::WindowFlags flags)
    : QMainWindow(parent, flags)
    , m_midiIn{nullptr}
    , m_midiOut{nullptr}
{
    ui.setupUi(this);
 
    connect(ui.pianokeybd, &PianoKeybd::noteOn, this, QOverload<int,int>::of(&VPiano::slotNoteOn));
    connect(ui.pianokeybd, &PianoKeybd::noteOff, this, QOverload<int,int>::of(&VPiano::slotNoteOff));
    connect(ui.pianokeybd, &PianoKeybd::signalName, this, &VPiano::slotNoteName);
    connect(ui.actionExit, &QAction::triggered, this,  &VPiano::close);
    connect(ui.actionAbout, &QAction::triggered, this,  &VPiano::slotAbout);
    connect(ui.actionAbout_Qt, &QAction::triggered, qApp, &QApplication::aboutQt);
    connect(ui.actionConnections, &QAction::triggered, this,  &VPiano::slotConnections);
    connect(ui.actionPreferences, &QAction::triggered, this,  &VPiano::slotPreferences);
    connect(ui.actionNames_Font, &QAction::triggered, this, &VPiano::slotChangeFont);
    connect(ui.actionInverted_Keys_Color, &QAction::triggered, this, &VPiano::slotInvertedColors);
    connect(ui.actionRaw_Computer_Keyboard, &QAction::triggered, this, &VPiano::slotRawKeyboard);
    connect(ui.actionComputer_Keyboard_Input, &QAction::triggered, this, &VPiano::slotKeyboardInput);
    connect(ui.actionMouse_Input, &QAction::triggered, this, &VPiano::slotMouseInput);
    connect(ui.actionTouch_Screen_Input, &QAction::triggered, this, &VPiano::slotTouchScreenInput);
    connect(ui.actionOctave_Subscript_Designation, &QAction::triggered, this, &VPiano::slotOctaveSubscript);
 
    QActionGroup* nameGroup = new QActionGroup(this);
    nameGroup->setExclusive(true);
    nameGroup->addAction(ui.actionStandard);
    nameGroup->addAction(ui.actionCustom_Sharps);
    nameGroup->addAction(ui.actionCustom_Flats);
    connect(ui.actionStandard,      &QAction::triggered, this, &VPiano::slotStandardNames);
    connect(ui.actionCustom_Sharps, &QAction::triggered, this, [=]{ slotCustomNames(true); });
    connect(ui.actionCustom_Flats,  &QAction::triggered, this, [=]{ slotCustomNames(false); });
 
    QActionGroup* nameVisibilityGroup = new QActionGroup(this);
    nameVisibilityGroup->setExclusive(true);
    nameVisibilityGroup->addAction(ui.actionNever);
    nameVisibilityGroup->addAction(ui.actionMinimal);
    nameVisibilityGroup->addAction(ui.actionWhen_Activated);
    nameVisibilityGroup->addAction(ui.actionAlways);
    connect(nameVisibilityGroup, &QActionGroup::triggered, this, &VPiano::slotNameVisibility);
 
    QActionGroup* blackKeysGroup = new QActionGroup(this);
    blackKeysGroup->setExclusive(true);
    blackKeysGroup->addAction(ui.actionFlats);
    blackKeysGroup->addAction(ui.actionSharps);
    blackKeysGroup->addAction(ui.actionNothing);
    connect(blackKeysGroup, &QActionGroup::triggered, this, &VPiano::slotNameVariant);
 
    QActionGroup* orientationGroup = new QActionGroup(this);
    orientationGroup->setExclusive(true);
    orientationGroup->addAction(ui.actionHorizontal);
    orientationGroup->addAction(ui.actionVertical);
    orientationGroup->addAction(ui.actionAutomatic);
    connect(orientationGroup, &QActionGroup::triggered, this, &VPiano::slotNameOrientation);
 
    QActionGroup* centralOctaveGroup = new QActionGroup(this);
    centralOctaveGroup->setExclusive(true);
    centralOctaveGroup->addAction(ui.actionNoOctaves);
    centralOctaveGroup->addAction(ui.actionC3);
    centralOctaveGroup->addAction(ui.actionC4);
    centralOctaveGroup->addAction(ui.actionC5);
    connect(centralOctaveGroup, &QActionGroup::triggered, this, &VPiano::slotCentralOctave);
 
    ui.statusBar->hide();
}
 
VPiano::~VPiano()
{
    m_midiIn->close();
    m_midiOut->close();
    delete m_manager;
}
 
void VPiano::initialize()
{
    readSettings();
 
    m_manager->refresh(VPianoSettings::instance()->settingsMap());
    m_inputs = m_manager->availableInputs();
    m_outputs = m_manager->availableOutputs();
 
    m_midiIn = m_manager->findInput(VPianoSettings::instance()->lastInputBackend());
    if (m_midiIn == nullptr) {
        qFatal("Unable to find a suitable input backend.");
    }
 
    m_midiOut = m_manager->findOutput(VPianoSettings::instance()->lastOutputBackend());
    if (m_midiOut == nullptr) {
        qFatal("Unable to find a suitable output backend. You may need to set the DRUMSTICKRT environment variable.");
    }
 
    if (m_midiIn != nullptr) {
        connect(m_midiIn, &MIDIInput::midiNoteOn,
                this, QOverload<int,int,int>::of(&VPiano::slotNoteOn),
                Qt::QueuedConnection);
        connect(m_midiIn, &MIDIInput::midiNoteOff,
                this, QOverload<int,int,int>::of(&VPiano::slotNoteOff),
                Qt::QueuedConnection);
 
        if (m_midiIn != nullptr) {
            auto conin = m_midiIn->connections(VPianoSettings::instance()->advanced());
            auto lastIn = VPianoSettings::instance()->lastInputConnection();
            auto itr = std::find_if(conin.constBegin(), conin.constEnd(), [lastIn](
const MIDIConnection& s) { return s.first == lastIn; });
 
            if(itr == conin.constEnd()) {
                if (!conin.isEmpty()) {
                    conn = conin.first();
                }
            } else {
                conn = (*itr);
            }
            m_midiIn->open(conn);
            auto metaObj = m_midiIn->metaObject();
            if ((metaObj->indexOfProperty("status") != -1) &&
                (metaObj->indexOfProperty("diagnostics") != -1)) {
                auto status = m_midiIn->property("status");
                if (status.isValid() && !status.toBool()) {
                    auto diagnostics = m_midiIn->property("diagnostics");
                    if (diagnostics.isValid()) {
                        auto text = diagnostics.toStringList().join(QChar::LineFeed).trimmed();
                        qWarning() << "MIDI Input" << text;
                    }
                }
            }
        }
    }
 
    if (m_midiOut != nullptr) {
        auto connOut = m_midiOut->connections(VPianoSettings::instance()->advanced());
        auto lastOut = VPianoSettings::instance()->lastOutputConnection();
        auto itr = std::find_if(connOut.constBegin(), connOut.constEnd(), [lastOut](
const MIDIConnection& s) { return s.first == lastOut; });
 
        if(itr == connOut.constEnd()) {
            if (!connOut.isEmpty()) {
                conn = connOut.first();
            }
        } else {
            conn = (*itr);
        }
        m_midiOut->open(conn);
        auto metaObj = m_midiOut->metaObject();
        if ((metaObj->indexOfProperty("status") != -1) &&
            (metaObj->indexOfProperty("diagnostics") != -1)) {
            auto status = m_midiOut->property("status");
            if (status.isValid() && !status.toBool()) {
                auto diagnostics = m_midiOut->property("diagnostics");
                if (diagnostics.isValid()) {
                    auto text = diagnostics.toStringList().join(QChar::LineFeed).trimmed();
                    qWarning() << "MIDI Output" << text;
                }
            }
        }
        if (m_midiIn != nullptr) {
            m_midiIn->setMIDIThruDevice(m_midiOut);
            m_midiIn->enableMIDIThru(VPianoSettings::instance()->midiThru());
        }
    }
}
 
void VPiano::showEvent(QShowEvent *event)
{
    initialize();
    event->accept();
}
 
void VPiano::closeEvent(QCloseEvent *event)
{
    writeSettings();
    event->accept();
}
 
void VPiano::slotNoteOn(const int midiNote, const int vel)
{
    int chan = VPianoSettings::instance()->outChannel();
    m_midiOut->sendNoteOn(chan, midiNote, vel);
}
 
void VPiano::slotNoteOff(const int midiNote, const int vel)
{
    int chan = VPianoSettings::instance()->outChannel();
    m_midiOut->sendNoteOff(chan, midiNote, vel);
}
 
void VPiano::slotNoteOn(const int chan, const int note, const int vel)
{
    if (VPianoSettings::instance()->inChannel() == chan) {
        if (vel > 0) {
            ui.pianokeybd->showNoteOn(note, vel);
        } else {
            ui.pianokeybd->showNoteOff(note);
        }
    }
}
 
void VPiano::slotNoteOff(const int chan, const int note, const int vel)
{
    Q_UNUSED(vel)
    if (VPianoSettings::instance()->inChannel() == chan) {
        ui.pianokeybd->showNoteOff(note);
    }
}
 
void VPiano::slotAbout()
{
    About dlgAbout(this);
    dlgAbout.exec();
}
 
void VPiano::slotConnections()
{
    Connections dlgConnections(this);
    dlgConnections.setInputs(m_inputs);
    dlgConnections.setOutputs(m_outputs);
    dlgConnections.setInput(m_midiIn);
    dlgConnections.setOutput(m_midiOut);
    dlgConnections.refresh();
    if (dlgConnections.exec() == QDialog::Accepted) {
        if (m_midiIn != nullptr) {
            m_midiIn->disconnect();
        }
        if (m_midiOut != nullptr) {
            m_midiOut->disconnect();
        }
        m_midiIn = dlgConnections.getInput();
        m_midiOut = dlgConnections.getOutput();
        if (m_midiIn != nullptr) {
            connect(m_midiIn, &MIDIInput::midiNoteOn, this,
                    QOverload<int,int,int>::of(&VPiano::slotNoteOn), Qt::QueuedConnection);
            connect(m_midiIn, &MIDIInput::midiNoteOff, this,
                    QOverload<int,int,int>::of(&VPiano::slotNoteOff), Qt::QueuedConnection);
        }
    }
}
 
void VPiano::slotPreferences()
{
    Preferences dlgPreferences(this);
    if (dlgPreferences.exec() == QDialog::Accepted) {
        if (ui.pianokeybd->baseOctave() != VPianoSettings::instance()->baseOctave()) {
            ui.pianokeybd->setBaseOctave(VPianoSettings::instance()->baseOctave());
        }
        if (ui.pianokeybd->numKeys() != VPianoSettings::instance()->numKeys() ||
            ui.pianokeybd->startKey() != VPianoSettings::instance()->startingKey()) {
            ui.pianokeybd->setNumKeys(VPianoSettings::instance()->numKeys(), VPianoSettings::instance()->startingKey());
        }
        ui.pianokeybd->setChannel(VPianoSettings::instance()->outChannel());
        ui.pianokeybd->setVelocity(VPianoSettings::instance()->velocity());
    }
}
 
void VPiano::writeSettings()
{
    VPianoSettings::instance()->setGeometry(saveGeometry());
    VPianoSettings::instance()->setState(saveState());
    VPianoSettings::instance()->SaveSettings();
}
 
void VPiano::readSettings()
{
    VPianoSettings::instance()->ReadSettings();
    restoreGeometry(VPianoSettings::instance()->geometry());
    restoreState(VPianoSettings::instance()->state());
 
    ui.pianokeybd->setFont(VPianoSettings::instance()->namesFont());
 
    LabelNaming namingPolicy = VPianoSettings::instance()->namingPolicy();
 
    switch(namingPolicy) {
        ui.actionStandard->setChecked(true);
        ui.pianokeybd->useStandardNoteNames();
        break;
        ui.actionCustom_Sharps->setChecked(true);
        ui.pianokeybd->useCustomNoteNames(VPianoSettings::instance()->names_sharps());
        break;
        ui.actionCustom_Flats->setChecked(true);
        ui.pianokeybd->useCustomNoteNames(VPianoSettings::instance()->names_flats());
        break;
    }
 
    ui.pianokeybd->setLabelOrientation(nOrientation);
    switch(nOrientation) {
        ui.actionHorizontal->setChecked(true);
        break;
        ui.actionVertical->setChecked(true);
        break;
        ui.actionAutomatic->setChecked(true);
        break;
    default:
        break;
    }
 
    ui.pianokeybd->setLabelAlterations(alteration);
    switch(alteration) {
        ui.actionSharps->setChecked(true);
        break;
        ui.actionFlats->setChecked(true);
        break;
        ui.actionNothing->setChecked(true);
        break;
    default:
        break;
    }
 
    LabelVisibility visibility = VPianoSettings::instance()->namesVisibility();
 
    ui.pianokeybd->setShowLabels(visibility);
    switch(visibility) {
        ui.actionNever->setChecked(true);
        break;
        ui.actionMinimal->setChecked(true);
        break;
        ui.actionWhen_Activated->setChecked(true);
        break;
        ui.actionAlways->setChecked(true);
        break;
    default:
        break;
    }
 
    ui.pianokeybd->setLabelOctave(nOctave);
    switch(nOctave) {
        ui.actionNoOctaves->setChecked(true);
        break;
        ui.actionC3->setChecked(true);
        break;
        ui.actionC4->setChecked(true);
        break;
        ui.actionC5->setChecked(true);
        break;
    default:
        break;
    }
 
    bool octaveSubscript = VPianoSettings::instance()->octaveSubscript();
    ui.pianokeybd->setOctaveSubscript(octaveSubscript);
    ui.actionOctave_Subscript_Designation->setChecked(octaveSubscript);
 
    ui.statusBar->show();
 
    ui.pianokeybd->setBaseOctave(VPianoSettings::instance()->baseOctave());
    ui.pianokeybd->setNumKeys(VPianoSettings::instance()->numKeys(), VPianoSettings::instance()->startingKey());
 
    
    ui.pianokeybd->setKeyPressedColor(Qt::red);
    ui.pianokeybd->setVelocityTint(false);
 
    ui.actionInverted_Keys_Color->setChecked(VPianoSettings::instance()->invertedKeys());
    slotInvertedColors(ui.actionInverted_Keys_Color->isChecked());
 
    ui.actionRaw_Computer_Keyboard->setChecked(VPianoSettings::instance()->rawKeyboard());
    slotRawKeyboard(ui.actionRaw_Computer_Keyboard->isChecked());
 
    ui.actionComputer_Keyboard_Input->setChecked(VPianoSettings::instance()->keyboardInput());
    slotKeyboardInput(ui.actionComputer_Keyboard_Input->isChecked());
 
    ui.actionMouse_Input->setChecked(VPianoSettings::instance()->mouseInput());
    slotMouseInput(ui.actionMouse_Input->isChecked());
 
    ui.actionTouch_Screen_Input->setChecked(VPianoSettings::instance()->touchScreenInput());
    slotTouchScreenInput(ui.actionTouch_Screen_Input->isChecked());
}
 
void VPiano::setPortableConfig(const QString fileName)
{
    if (fileName.isEmpty()) {
        QFileInfo appInfo(QCoreApplication::applicationFilePath());
#if defined(Q_OS_MACOS)
        CFURLRef url = static_cast<CFURLRef>(CFAutorelease(static_cast<CFURLRef>(CFBundleCopyBundleURL(CFBundleGetMainBundle()))));
        QString path = QUrl::fromCFURL(url).path() + "../";
        QFileInfo cfgInfo(path, appInfo.baseName() + ".conf");
#else
        QFileInfo cfgInfo(appInfo.absoluteDir(), appInfo.baseName() + ".conf");
#endif
    } else {
    }
}
 
void VPiano::useCustomNoteNames()
{
    if (ui.pianokeybd->labelAlterations() == ShowFlats) {
        ui.pianokeybd->useCustomNoteNames(VPianoSettings::instance()->names_flats());
    } else {
        ui.pianokeybd->useCustomNoteNames(VPianoSettings::instance()->names_sharps());
    }
}
 
void VPiano::slotChangeFont()
{
    bool ok;
    QFont font = QFontDialog::getFont(&ok,
                    VPianoSettings::instance()->namesFont(),
                    this, tr("Font to display note names"),
                    QFontDialog::DontUseNativeDialog | QFontDialog::ScalableFonts);
    if (ok) {
        VPianoSettings::instance()->setNamesFont(font);
        ui.pianokeybd->setFont(font);
    }
}
 
void VPiano::slotNameOrientation(QAction* action)
{
    if(action == ui.actionHorizontal) {
        VPianoSettings::instance()->setNamesOrientation(HorizontalOrientation);
    } else if(action == ui.actionVertical) {
        VPianoSettings::instance()->setNamesOrientation(VerticalOrientation);
    } else if(action == ui.actionAutomatic) {
        VPianoSettings::instance()->setNamesOrientation(AutomaticOrientation);
    }
    ui.pianokeybd->setLabelOrientation(VPianoSettings::instance()->namesOrientation());
}
 
void VPiano::slotNameVisibility(QAction* action)
{
    if(action == ui.actionNever) {
        VPianoSettings::instance()->setNamesVisibility(ShowNever);
    } else if(action == ui.actionMinimal) {
        VPianoSettings::instance()->setNamesVisibility(ShowMinimum);
    } else if(action == ui.actionWhen_Activated) {
        VPianoSettings::instance()->setNamesVisibility(ShowActivated);
    } else if(action == ui.actionAlways) {
        VPianoSettings::instance()->setNamesVisibility(ShowAlways);
    }
    ui.pianokeybd->setShowLabels(VPianoSettings::instance()->namesVisibility());
}
 
void VPiano::slotNameVariant(QAction* action)
{
    if(action == ui.actionSharps) {
        VPianoSettings::instance()->setNamesAlterations(ShowSharps);
    } else if(action == ui.actionFlats) {
        VPianoSettings::instance()->setNamesAlterations(ShowFlats);
    } else if(action == ui.actionNothing) {
        VPianoSettings::instance()->setNamesAlterations(ShowNothing);
    }
    ui.pianokeybd->setLabelAlterations(VPianoSettings::instance()->alterations());
}
 
void VPiano::slotCentralOctave(QAction *action)
{
    if (action == ui.actionNoOctaves) {
        VPianoSettings::instance()->setNamesOctave(OctaveNothing);
    } else if (action == ui.actionC3) {
        VPianoSettings::instance()->setNamesOctave(OctaveC3);
    } else if(action == ui.actionC4) {
        VPianoSettings::instance()->setNamesOctave(OctaveC4);
    } else if(action == ui.actionC5) {
        VPianoSettings::instance()->setNamesOctave(OctaveC5);
    }
    ui.pianokeybd->setLabelOctave(VPianoSettings::instance()->namesOctave());
}
 
void VPiano::slotStandardNames()
{
    VPianoSettings::instance()->setNamingPolicy(StandardNames);
    ui.pianokeybd->useStandardNoteNames();
    ui.actionStandard->setChecked(true);
}
 
void VPiano::slotCustomNames(bool sharps)
{
    bool ok;
    QString names;
    if ( sharps ) {
        names = VPianoSettings::instance()->names_sharps().join('\n');
    } else {
        names = VPianoSettings::instance()->names_flats().join('\n');
    }
    QString text = QInputDialog::getMultiLineText(this,tr("Custom Note Names"),tr("Names:"),
                                                  names, &ok);
    if (ok && !text.isEmpty()) {
        QStringList customNames = text.split('\n');
        if (sharps) {
            VPianoSettings::instance()->setNames_sharps(customNames);
            VPianoSettings::instance()->setNamingPolicy(CustomNamesWithSharps);
        } else {
            VPianoSettings::instance()->setNames_flats(customNames);
            VPianoSettings::instance()->setNamingPolicy(CustomNamesWithFlats);
        }
        ui.pianokeybd->useCustomNoteNames(customNames);
    } else {
        slotStandardNames();
    }
}
 
void VPiano::slotNoteName(const QString& name)
{
    if (name.isEmpty()) {
        ui.statusBar->clearMessage();
    } else {
        ui.statusBar->showMessage(name);
    }
}
 
void VPiano::slotInvertedColors(bool checked)
{
    if (checked) {
        bgpal.setColor(0, Qt::black);
        bgpal.setColor(1, Qt::white);
 
        fpal.setColor(0, Qt::white);
        fpal.setColor(1, Qt::black);
        fpal.setColor(2, Qt::white);
        fpal.setColor(3, Qt::white);
    } else {
        bgpal.setColor(0, QColor("ivory"));
        bgpal.setColor(1, QColor(0x40,0x10,0x10));
 
        fpal.setColor(0, Qt::black);
        fpal.setColor(1, Qt::white);
        fpal.setColor(2, Qt::white);
        fpal.setColor(3, Qt::white);
    }
    ui.pianokeybd->setBackgroundPalette(bgpal);
    ui.pianokeybd->setForegroundPalette(fpal);
    VPianoSettings::instance()->setInvertedKeys(checked);
}
 
void VPiano::slotRawKeyboard(bool checked)
{
    
    if (checked) {
        ui.pianokeybd->resetRawKeyboardMap();
    } else {
        ui.pianokeybd->resetKeyboardMap();
    }
    ui.pianokeybd->setRawKeyboardMode(checked);
    VPianoSettings::instance()->setRawKeyboard(checked);
}
 
void VPiano::slotKeyboardInput(bool checked)
{
    
    ui.pianokeybd->setKeyboardEnabled(checked);
    VPianoSettings::instance()->setKeyboardInput(checked);
}
 
void VPiano::slotMouseInput(bool checked)
{
    
    ui.pianokeybd->setMouseEnabled(checked);
    VPianoSettings::instance()->setMouseInput(checked);
}
 
void VPiano::slotTouchScreenInput(bool checked)
{
    
    ui.pianokeybd->setTouchEnabled(checked);
    VPianoSettings::instance()->setTouchScreenInput(checked);
}
 
void VPiano::slotOctaveSubscript(bool checked)
{
    ui.pianokeybd->setOctaveSubscript(checked);
    VPianoSettings::instance()->setOctaveSubscript(checked);
}
QPair< QString, QVariant > MIDIConnection
MIDIConnection represents a connection identifier.
Drumstick Real-Time library.
SettingsFactory class declaration.