|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- /*
- * Pixmap Keyboard, a custom Qt4 widget
- * Copyright (C) 2011-2013 Filipe Coelho <falktx@falktx.com>
- *
- * 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 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.
- *
- * For a full copy of the GNU General Public License see the GPL.txt file
- */
-
- #include "pixmapkeyboard.hpp"
-
- #include <QtCore/QTimer>
- #include <QtGui/QKeyEvent>
- #include <QtGui/QMouseEvent>
- #include <QtGui/QPainter>
-
- static std::map<int, QRectF> kMidiKey2RectMapHorizontal = {
- {0, QRectF(0, 0, 18, 64)}, // C
- {1, QRectF(13, 0, 11, 42)}, // C#
- {2, QRectF(18, 0, 25, 64)}, // D
- {3, QRectF(37, 0, 11, 42)}, // D#
- {4, QRectF(42, 0, 18, 64)}, // E
- {5, QRectF(60, 0, 18, 64)}, // F
- {6, QRectF(73, 0, 11, 42)}, // F#
- {7, QRectF(78, 0, 25, 64)}, // G
- {8, QRectF(97, 0, 11, 42)}, // G#
- {9, QRectF(102, 0, 25, 64)}, // A
- {10, QRectF(121, 0, 11, 42)}, // A#
- {11, QRectF(126, 0, 18, 64)} // B
- };
-
- static std::map<int, QRectF> kMidiKey2RectMapVertical = {
- {11, QRectF(0, 0, 64, 18)}, // B
- {10, QRectF(0, 14, 42, 7)}, // A#
- {9, QRectF(0, 18, 64, 24)}, // A
- {8, QRectF(0, 38, 42, 7)}, // G#
- {7, QRectF(0, 42, 64, 24)}, // G
- {6, QRectF(0, 62, 42, 7)}, // F#
- {5, QRectF(0, 66, 64, 18)}, // F
- {4, QRectF(0, 84, 64, 18)}, // E
- {3, QRectF(0, 98, 42, 7)}, // D#
- {2, QRectF(0, 102, 64, 24)}, // D
- {1, QRectF(0, 122, 42, 7)}, // C#
- {0, QRectF(0, 126, 64, 18)} // C
- };
-
- static const std::map<int, int> kMidiKeyboard2KeyMap = {
- // 3th octave
- {Qt::Key_Z, 48},
- {Qt::Key_S, 49},
- {Qt::Key_X, 50},
- {Qt::Key_D, 51},
- {Qt::Key_C, 52},
- {Qt::Key_V, 53},
- {Qt::Key_G, 54},
- {Qt::Key_B, 55},
- {Qt::Key_H, 56},
- {Qt::Key_N, 57},
- {Qt::Key_J, 58},
- {Qt::Key_M, 59},
- // 4th octave
- {Qt::Key_Q, 60},
- {Qt::Key_2, 61},
- {Qt::Key_W, 62},
- {Qt::Key_3, 63},
- {Qt::Key_E, 64},
- {Qt::Key_R, 65},
- {Qt::Key_5, 66},
- {Qt::Key_T, 67},
- {Qt::Key_6, 68},
- {Qt::Key_Y, 69},
- {Qt::Key_7, 70},
- {Qt::Key_U, 71}
- };
-
- static const QVector<int> kBlackNotes = {1, 3, 6, 8, 10};
-
- PixmapKeyboard::PixmapKeyboard(QWidget* parent)
- : QWidget(parent),
- fPixmap(""),
- fPixmapMode(HORIZONTAL),
- fColorStr("orange"),
- fFont("Monospace", 8, QFont::Normal),
- fOctaves(6),
- fLastMouseNote(-1),
- fWidth(0),
- fHeight(0),
- fNeedsUpdate(false),
- fMidiMap(kMidiKey2RectMapHorizontal)
- {
- setCursor(Qt::PointingHandCursor);
- setMode(HORIZONTAL);
- }
-
- void PixmapKeyboard::allNotesOff()
- {
- fEnabledKeys.clear();
- fNeedsUpdate = true;
-
- emit notesOff();
- QTimer::singleShot(0, this, SLOT(updateOnce()));
- }
-
- void PixmapKeyboard::sendNoteOn(int note, bool sendSignal)
- {
- if (0 <= note && note <= 127 && ! fEnabledKeys.contains(note))
- {
- fEnabledKeys.append(note);
-
- if (sendSignal)
- emit noteOn(note);
-
- fNeedsUpdate = true;
- QTimer::singleShot(0, this, SLOT(updateOnce()));
- }
-
- if (fEnabledKeys.count() == 1)
- emit notesOn();
- }
-
- void PixmapKeyboard::sendNoteOff(int note, bool sendSignal)
- {
- if (note >= 0 && note <= 127 && fEnabledKeys.contains(note))
- {
- fEnabledKeys.removeOne(note);
-
- if (sendSignal)
- emit noteOff(note);
-
- fNeedsUpdate = true;
- QTimer::singleShot(0, this, SLOT(updateOnce()));
- }
-
- if (fEnabledKeys.count() == 0)
- emit notesOff();
- }
-
- void PixmapKeyboard::setMode(Orientation mode, Color color)
- {
- if (color == COLOR_CLASSIC)
- {
- fColorStr = "classic";
- }
- else if (color == COLOR_ORANGE)
- {
- fColorStr = "orange";
- }
- else
- {
- qCritical("PixmapKeyboard::setMode(%i, %i) - invalid color", mode, color);
- return setMode(mode);
- }
-
- if (mode == HORIZONTAL)
- {
- fMidiMap = kMidiKey2RectMapHorizontal;
- fPixmap.load(QString(":/bitmaps/kbd_h_%1.png").arg(fColorStr));
- fPixmapMode = HORIZONTAL;
- fWidth = fPixmap.width();
- fHeight = fPixmap.height() / 2;
- }
- else if (mode == VERTICAL)
- {
- fMidiMap = kMidiKey2RectMapVertical;
- fPixmap.load(QString(":/bitmaps/kbd_v_%1.png").arg(fColorStr));
- fPixmapMode = VERTICAL;
- fWidth = fPixmap.width() / 2;
- fHeight = fPixmap.height();
- }
- else
- {
- qCritical("PixmapKeyboard::setMode(%i, %i) - invalid mode", mode, color);
- return setMode(HORIZONTAL);
- }
-
- setOctaves(fOctaves);
- }
-
- void PixmapKeyboard::setOctaves(int octaves)
- {
- Q_ASSERT(octaves >= 1 && octaves <= 8);
-
- if (octaves < 1)
- octaves = 1;
- else if (octaves > 8)
- octaves = 8;
-
- fOctaves = octaves;
-
- if (fPixmapMode == HORIZONTAL)
- {
- setMinimumSize(fWidth * fOctaves, fHeight);
- setMaximumSize(fWidth * fOctaves, fHeight);
- }
- else if (fPixmapMode == VERTICAL)
- {
- setMinimumSize(fWidth, fHeight * fOctaves);
- setMaximumSize(fWidth, fHeight * fOctaves);
- }
-
- update();
- }
-
- void PixmapKeyboard::handleMousePos(const QPoint& pos)
- {
- int note;
- int octave;
- QPointF keyPos;
-
- if (fPixmapMode == HORIZONTAL)
- {
- if (pos.x() < 0 or pos.x() > fOctaves * 144)
- return;
- int posX = pos.x() - 1;
- octave = posX / fWidth;
- keyPos = QPointF(posX % fWidth, pos.y());
- }
- else if (fPixmapMode == VERTICAL)
- {
- if (pos.y() < 0 or pos.y() > fOctaves * 144)
- return;
- int posY = pos.y() - 1;
- octave = fOctaves - posY / fHeight;
- keyPos = QPointF(pos.x(), posY % fHeight);
- }
- else
- return;
-
- octave += 3;
-
- if (fMidiMap[1].contains(keyPos)) // C#
- note = 1;
- else if (fMidiMap[3].contains(keyPos)) // D#
- note = 3;
- else if (fMidiMap[6].contains(keyPos)) // F#
- note = 6;
- else if (fMidiMap[8].contains(keyPos)) // G#
- note = 8;
- else if (fMidiMap[10].contains(keyPos))// A#
- note = 10;
- else if (fMidiMap[0].contains(keyPos)) // C
- note = 0;
- else if (fMidiMap[2].contains(keyPos)) // D
- note = 2;
- else if (fMidiMap[4].contains(keyPos)) // E
- note = 4;
- else if (fMidiMap[5].contains(keyPos)) // F
- note = 5;
- else if (fMidiMap[7].contains(keyPos)) // G
- note = 7;
- else if (fMidiMap[9].contains(keyPos)) // A
- note = 9;
- else if (fMidiMap[11].contains(keyPos))// B
- note = 11;
- else
- note = -1;
-
- if (note != -1)
- {
- note += octave * 12;
-
- if (fLastMouseNote != note)
- {
- sendNoteOff(fLastMouseNote);
- sendNoteOn(note);
- }
- }
- else if (fLastMouseNote != -1)
- sendNoteOff(fLastMouseNote);
-
- fLastMouseNote = note;
- }
-
- void PixmapKeyboard::keyPressEvent(QKeyEvent* event)
- {
- if (! event->isAutoRepeat())
- {
- int qKey = event->key();
- std::map<int, int>::const_iterator it = kMidiKeyboard2KeyMap.find(qKey);
- if (it != kMidiKeyboard2KeyMap.end())
- sendNoteOn(it->second);
- }
- QWidget::keyPressEvent(event);
- }
-
- void PixmapKeyboard::keyReleaseEvent(QKeyEvent* event)
- {
- if (! event->isAutoRepeat())
- {
- int qKey = event->key();
- std::map<int, int>::const_iterator it = kMidiKeyboard2KeyMap.find(qKey);
- if (it != kMidiKeyboard2KeyMap.end())
- sendNoteOff(it->second);
- }
- QWidget::keyReleaseEvent(event);
- }
-
- void PixmapKeyboard::mousePressEvent(QMouseEvent* event)
- {
- fLastMouseNote = -1;
- handleMousePos(event->pos());
- setFocus();
- QWidget::mousePressEvent(event);
- }
-
- void PixmapKeyboard::mouseMoveEvent(QMouseEvent* event)
- {
- handleMousePos(event->pos());
- QWidget::mousePressEvent(event);
- }
-
- void PixmapKeyboard::mouseReleaseEvent(QMouseEvent* event)
- {
- if (fLastMouseNote != -1)
- {
- sendNoteOff(fLastMouseNote);
- fLastMouseNote = -1;
- }
- QWidget::mouseReleaseEvent(event);
- }
-
- void PixmapKeyboard::paintEvent(QPaintEvent* event)
- {
- QPainter painter(this);
- event->accept();
-
- // -------------------------------------------------------------
- // Paint clean keys (as background)
-
- for (int octave=0; octave < fOctaves; octave++)
- {
- QRectF target;
-
- if (fPixmapMode == HORIZONTAL)
- target = QRectF(fWidth * octave, 0, fWidth, fHeight);
- else if (fPixmapMode == VERTICAL)
- target = QRectF(0, fHeight * octave, fWidth, fHeight);
- else
- return;
-
- QRectF source = QRectF(0, 0, fWidth, fHeight);
- painter.drawPixmap(target, fPixmap, source);
- }
-
- // -------------------------------------------------------------
- // Paint (white) pressed keys
-
- bool paintedWhite = false;
-
- for (int i=0, count=fEnabledKeys.count(); i < count; i++)
- {
- int octave, note = fEnabledKeys[i];
- const QRectF& pos(_getRectFromMidiNote(note));
-
- if (_isNoteBlack(note))
- continue;
-
- if (note < 36)
- // cannot paint this note
- continue;
- else if (note < 48)
- octave = 0;
- else if (note < 60)
- octave = 1;
- else if (note < 72)
- octave = 2;
- else if (note < 84)
- octave = 3;
- else if (note < 96)
- octave = 4;
- else if (note < 108)
- octave = 5;
- else if (note < 120)
- octave = 6;
- else if (note < 132)
- octave = 7;
- else
- // cannot paint this note either
- continue;
-
- if (fPixmapMode == VERTICAL)
- octave = fOctaves - octave - 1;
-
- QRectF target, source;
-
- if (fPixmapMode == HORIZONTAL)
- {
- target = QRectF(pos.x() + (fWidth * octave), 0, pos.width(), pos.height());
- source = QRectF(pos.x(), fHeight, pos.width(), pos.height());
- }
- else if (fPixmapMode == VERTICAL)
- {
- target = QRectF(pos.x(), pos.y() + (fHeight * octave), pos.width(), pos.height());
- source = QRectF(fWidth, pos.y(), pos.width(), pos.height());
- }
- else
- return;
-
- paintedWhite = true;
- painter.drawPixmap(target, fPixmap, source);
- }
-
- // -------------------------------------------------------------
- // Clear white keys border
-
- if (paintedWhite)
- {
- for (int octave=0; octave < fOctaves; octave++)
- {
- foreach (int note, kBlackNotes)
- {
- QRectF target, source;
- const QRectF& pos(_getRectFromMidiNote(note));
-
- if (fPixmapMode == HORIZONTAL)
- {
- target = QRectF(pos.x() + (fWidth * octave), 0, pos.width(), pos.height());
- source = QRectF(pos.x(), 0, pos.width(), pos.height());
- }
- else if (fPixmapMode == VERTICAL)
- {
- target = QRectF(pos.x(), pos.y() + (fHeight * octave), pos.width(), pos.height());
- source = QRectF(0, pos.y(), pos.width(), pos.height());
- }
- else
- return;
-
- painter.drawPixmap(target, fPixmap, source);
- }
- }
- }
-
- // -------------------------------------------------------------
- // Paint (black) pressed keys
-
- for (int i=0, count=fEnabledKeys.count(); i < count; i++)
- {
- int octave, note = fEnabledKeys[i];
- const QRectF& pos(_getRectFromMidiNote(note));
-
- if (! _isNoteBlack(note))
- continue;
-
- if (note < 36)
- // cannot paint this note
- continue;
- else if (note < 48)
- octave = 0;
- else if (note < 60)
- octave = 1;
- else if (note < 72)
- octave = 2;
- else if (note < 84)
- octave = 3;
- else if (note < 96)
- octave = 4;
- else if (note < 108)
- octave = 5;
- else if (note < 120)
- octave = 6;
- else if (note < 132)
- octave = 7;
- else
- // cannot paint this note either
- continue;
-
- if (fPixmapMode == VERTICAL)
- octave = fOctaves - octave - 1;
-
- QRectF target, source;
-
- if (fPixmapMode == HORIZONTAL)
- {
- target = QRectF(pos.x() + (fWidth * octave), 0, pos.width(), pos.height());
- source = QRectF(pos.x(), fHeight, pos.width(), pos.height());
- }
- else if (fPixmapMode == VERTICAL)
- {
- target = QRectF(pos.x(), pos.y() + (fHeight * octave), pos.width(), pos.height());
- source = QRectF(fWidth, pos.y(), pos.width(), pos.height());
- }
- else
- return;
-
- painter.drawPixmap(target, fPixmap, source);
- }
-
- // Paint C-number note info
- painter.setFont(fFont);
- painter.setPen(Qt::black);
-
- for (int i=0; i < fOctaves; i++)
- {
- if (fPixmapMode == HORIZONTAL)
- painter.drawText(i * 144, 48, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2));
- else if (fPixmapMode == VERTICAL)
- painter.drawText(45, (fOctaves * 144) - (i * 144) - 16, 18, 18, Qt::AlignCenter, QString("C%1").arg(i + 2));
- }
- }
-
- void PixmapKeyboard::updateOnce()
- {
- if (fNeedsUpdate)
- {
- update();
- fNeedsUpdate = false;
- }
- }
-
- bool PixmapKeyboard::_isNoteBlack(int note) const
- {
- int baseNote = note % 12;
- return kBlackNotes.contains(baseNote);
- }
-
- const QRectF& PixmapKeyboard::_getRectFromMidiNote(int note) const
- {
- return fMidiMap[note % 12];
- }
|