123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- /*
- This file is part of QTau
- Copyright (C) 2013-2018 Tobias "Tomoko" Platen <tplaten@posteo.de>
- Copyright (C) 2013 digited <https://github.com/digited>
- Copyright (C) 2010-2013 HAL@ShurabaP <https://github.com/haruneko>
- QTau 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 3 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. If not, see <http://www.gnu.org/licenses/>.
- SPDX-License-Identifier: GPL-3.0+
- */
- #include "ui/noteEditor.h"
- #include "ui/noteEditorHandlers.h"
- #include "NoteEvents.h"
- #define __devloglevel__ 4
- #include <qevent.h>
- #include <QTime>
- #include <QTimer>
- #include <qpainter.h>
- #include <QPainterPath>
- #include <QMimeData>
- #include <QLineEdit>
- #include <QDebug>
- #include "tempomap.h"
- //qtauEvent_Plugin
- #include "../Controller.h"
- #include "../PluginInterfaces.h"
- const int cdef_cache_labels_num = 1000;
- const int cdef_cache_line_height = 12;
- const int cdef_cache_line_width = 100;
- const int cdef_lbl_draw_minwidth = 20; // minimal note width on screen in pixels to draw phoneme in it
- const int cdef_scroll_margin = 20; // px of space between edge of widget and target rect
- const double F_ROUNDER = 0.1; // because float is floor'ed to int by default, so 3.9999999 becomes 3
- qtauNoteEditor::qtauNoteEditor(QWidget *parent) :
- QWidget(parent), _bgCache(0), _delayingUpdate(false), _updateCalled(false), _lastCtrl(0)
- {
- setAttribute(Qt::WA_OpaquePaintEvent);
- setAttribute(Qt::WA_NoSystemBackground);
- setAutoFillBackground(false);
- setMouseTracking(true);
- setFocusPolicy(Qt::StrongFocus);
- _labelCache = new QPixmap(cdef_cache_line_width, cdef_cache_line_height * cdef_cache_labels_num);
- _labelCache->fill(Qt::transparent);
- _ctrl = new qtauEdController(*this, _setup, _notes, _state);
- _playLine = -1;
- //t.start();
- }
- qtauNoteEditor::~qtauNoteEditor()
- {
- if (_labelCache)
- delete _labelCache;
- if (_bgCache)
- delete _bgCache;
- if (_lastCtrl)
- delete _lastCtrl;
- if (_ctrl)
- delete _ctrl;
- }
- void qtauNoteEditor::configure(const SNoteSetup &newSetup)
- {
- _ctrl->reset();
- bool gridChanged = newSetup.note != _setup.note || 1;//TODO add hasChanged()
- _setup = newSetup;
- if (gridChanged)
- {
- recalcNoteRects();
- updateBGCache();
- lazyUpdate();
- }
- }
- void qtauNoteEditor::deleteSelected()
- {
- if (!_notes.selected.isEmpty())
- {
- _ctrl->reset();
- _ctrl->deleteSelected();
- }
- }
- void qtauNoteEditor::onEvent(qtauEvent *e)
- {
- _ctrl->onEvent(e);
- }
- void qtauNoteEditor::lazyUpdate()
- {
- update();
- }
- //--------------------------------------------------
- void qtauNoteEditor::recalcNoteRects()
- {
- _notes.grid.clear();
- //_setup.barWidth = _setup.note.width() * 4;//FIXME
- _setup.octHeight = _setup.note.height() * 12;
- double pulsesToPixels = (double)_setup.note.width() / c_midi_ppq;
- int startBar = 0, endBar = 0;
- _playLine = pulsesToPixels*_posPulses;
- foreach (quint64 key, _notes.idMap.keys())
- {
- qne::editorNote &n = _notes.idMap[key];
- int nn1 = n.keyNumber+1;
- if(n.keyNumber<0 || n.keyNumber>127) {
- DEVLOG_ERROR("invalid key number");
- return;
- }
- n.r.setRect((double)n.pulseOffset * pulsesToPixels + F_ROUNDER,
- ((_setup.baseOctave + _setup.numOctaves - 1) * 12 - nn1) * _setup.note.height(),
- (double)n.pulseLength * pulsesToPixels + F_ROUNDER, _setup.note.height());
- // determine bar(s) that have that note fully or partially
- startBar = _setup.getBarForScreenOffset(n.r.left());
- endBar = _setup.getBarForScreenOffset(n.r.right());
- if (endBar >= _notes.grid.size())
- _notes.grid.resize(endBar + 10);
- _notes.grid[startBar].append(n.id);
- if (endBar != startBar)
- _notes.grid[endBar].append(n.id);
- }
- }
- void qtauNoteEditor::updateBGCache()
- {
- //_setup.barWidth = _setup.note.width() * 4;
- _setup.octHeight = _setup.note.height() * 12;
- int requiredCacheWidth = _setup.barScreenOffsets[_setup.numBars-1] + _setup.note.width() * 8;
- int requiredCacheHeight = (geometry().height() / _setup.octHeight + 2) * _setup.octHeight;
- if (!_bgCache || (_bgCache->width() < requiredCacheWidth || _bgCache->height() < requiredCacheHeight))
- {
- if (_bgCache)
- delete _bgCache;
- _bgCache = new QPixmap(requiredCacheWidth, requiredCacheHeight);
- }
- // prepare bg/grid data =========================================
- QPainterPath blacks; // rects for black keys
- QPainterPath outerLines;
- QPainterPath innerLines;
- // calculating indexes of visible notes of grid ----------------------
- int hSt = 0;
- int vSt = 0;
- int hEnd = requiredCacheWidth - 1;
- int vEnd = requiredCacheHeight - 1;
- int pxHOff = 0;
- int pxVOff = 0;
- int bar = 0;
- int beat = 0;
- int octCounter = 0;
- // horizontal pass to calc note & bar vertical delimiter lines
- do {
- fraction time = _setup.tmap->getTimeSignatureForBar(bar); //1
- if (beat==time.numerator)
- {
- outerLines.moveTo(QPoint(pxHOff, vSt ));
- outerLines.lineTo(QPoint(pxHOff, vEnd));
- beat = 1;
- bar++;
- time = _setup.tmap->getTimeSignatureForBar(bar); //2
- }
- else {
- innerLines.moveTo(QPoint(pxHOff, vSt ));
- innerLines.lineTo(QPoint(pxHOff, vEnd));
- beat++;
- }
- pxHOff += _setup.note.width() * 4.0/time.denominator;
- } while (pxHOff <= hEnd);
- QRect noteBG(hSt, 0, hEnd - hSt, _setup.note.height());
- // vertical pass to calc note backgrounds, note & octave delimiter lines
- do {
- if (octCounter == 12)
- octCounter = 0;
- if (octCounter == 0)
- {
- outerLines.moveTo(QPoint(hSt, pxVOff));
- outerLines.lineTo(QPoint(hEnd, pxVOff));
- }
- else {
- innerLines.moveTo(QPoint(hSt, pxVOff));
- innerLines.lineTo(QPoint(hEnd, pxVOff));
- }
- //--- note bg's --------------
- noteBG.moveTop(pxVOff);
- if (octCounter == 1 || octCounter == 3 || octCounter == 5 || octCounter == 8 || octCounter == 10)
- blacks.addRect(noteBG);
- //----------------------------
- octCounter++;
- pxVOff += _setup.note.height();
- } while (pxVOff <= vEnd);
- // paint 'em! ======================
- _bgCache->fill (Qt::white);
- QPainter p (_bgCache);
- QBrush brush(p.brush());
- p.setPen(Qt::NoPen);
- // background -------------
- if (!blacks.isEmpty())
- {
- brush.setStyle(Qt::Dense6Pattern);
- brush.setColor(cdef_color_black_noteline_bg);
- p.setBrush(brush);
- p.drawPath(blacks);
- }
- p.setPen(Qt::SolidLine);
- // lines ------------------
- if (!innerLines.isEmpty())
- {
- p.setPen(QColor(cdef_color_inner_line));
- p.drawPath(innerLines);
- }
- if (!outerLines.isEmpty())
- {
- p.setPen(QColor(cdef_color_outer_line));
- p.drawPath(outerLines);
- }
- }
- void qtauNoteEditor::setVOffset(int voff)
- {
- if (voff != _state.viewport.y())
- {
- _ctrl->reset();
- _state.viewport.moveTop(voff);
- lazyUpdate();
- }
- }
- void qtauNoteEditor::setHOffset(int hoff)
- {
- if (hoff != _state.viewport.x())
- {
- _ctrl->reset();
- _state.viewport.moveLeft(hoff);
- lazyUpdate();
- }
- }
- QPoint qtauNoteEditor::scrollTo(const QRect &r)
- {
- QPoint result = _state.viewport.topLeft();
- if (r.x() < _state.viewport.x())
- result.setX(r.x() - cdef_scroll_margin);
- else
- if (r.x() > _state.viewport.x() + geometry().width() - cdef_cache_line_width - cdef_scroll_margin)
- result.setX(r.x() - geometry().width() / 2);
- if (r.y() < _state.viewport.y())
- result.setY(r.y() - cdef_scroll_margin);
- else
- if (r.y() > _state.viewport.y() + geometry().height() - cdef_scroll_margin)
- result.setY(r.y() - geometry().height() / 2);
- if (result != _state.viewport.topLeft())
- emit requestsOffset(result);
- return result;
- }
- //----------------------------------------------------
- void qtauNoteEditor::paintEvent(QPaintEvent *event)
- {
- //lastUpdate = t.elapsed();
- emit repaintDynDrawer();
- // draw bg
- QRect r(event->rect());
- int hSt = r.x() + _state.viewport.x();
- int firstBar = _setup.getBarForScreenOffset(hSt);
- int cacheHOffset = hSt;// - firstBar * _setup.barWidth;
- int vSt = r.y() + _state.viewport.y();
- int firstOct = vSt / _setup.octHeight;
- int cacheVOffset = vSt - firstOct * _setup.octHeight;
- QRect cacheRect(r);
- cacheRect.moveTo(cacheRect.x() + cacheHOffset, cacheRect.y() + cacheVOffset);
- QPainter p(this);
- p.drawPixmap(r, *_bgCache, cacheRect);
- // singing notes with phoneme labels -------
- int barSt = firstBar;
- int barEnd = _setup.getBarForScreenOffset(hSt + r.width());
- QPainterPath noteRects;
- QPainterPath selNoteRects;
- QPainterPath selNoteRectsRed;
- QPainterPath selNoteRectsGreen;
- QPainterPath noteRectsRed;
- QPainterPath noteRectsGreen;
- QMap<quint64, bool> processedIDMap;
- QVector<QPainter::PixmapFragment> cachedLabels;
- QPainter cacheP(_labelCache);
- cacheP.setBrush(Qt::white); // to clear pixmap completely
- p.translate(-_state.viewport.topLeft());
- if (barSt < _notes.grid.size())
- {
- if (barEnd >= _notes.grid.size())
- barEnd = _notes.grid.size() - 1;
- bool hasVisibleNotes = false;
- r.moveTo(_state.viewport.topLeft());
- for (int i = barSt; i < barEnd + 1; ++i)
- for (int k = 0; k < _notes.grid[i].size(); ++k)
- {
- qne::editorNote &n = _notes.idMap[_notes.grid[i][k]];
- if (!processedIDMap.contains(n.id) && r.intersects(n.r))
- {
- hasVisibleNotes = true;
- if (n.selected)
- {
- if(n.color==EColor::green)
- selNoteRectsGreen.addRect(n.r);
- else if(n.color==EColor::red)
- selNoteRectsRed.addRect(n.r);
- else
- selNoteRects.addRect(n.r);
- }
- else
- {
- if(n.color==EColor::green)
- noteRectsGreen.addRect(n.r);
- else if(n.color==EColor::red)
- noteRectsRed.addRect(n.r);
- else
- noteRects.addRect(n.r);
- }
- if (n.r.width() > cdef_lbl_draw_minwidth) // don't draw labels for too narrow rects
- {
- QRectF fR(0, n.id * cdef_cache_line_height,
- cdef_cache_line_width, cdef_cache_line_height);
- if (!n.cached)
- {
- // clearing cache line
- cacheP.setCompositionMode(QPainter::CompositionMode_Clear);
- cacheP.drawRect(fR);
- cacheP.setCompositionMode(QPainter::CompositionMode_SourceOver);
- cacheP.drawText(fR, Qt::AlignLeft | Qt::AlignVCenter, n.txt);
- n.cached = true;
- }
- cachedLabels.append(QPainter::PixmapFragment::create(
- QPointF(n.r.x() + 55, n.r.y() + 7), fR)); // wtf is with pos?..
- }
- }
- processedIDMap[n.id] = true; // to avoid processing notes that go through 2 or more bars
- }
- if (hasVisibleNotes)
- {
- p.setPen (QColor(cdef_color_note_border));
- p.setBrush(QColor(cdef_color_note_bg_green));
- p.drawPath(noteRectsGreen);
- p.setPen (QColor(cdef_color_note_border));
- p.setBrush(QColor(cdef_color_note_bg_red));
- p.drawPath(noteRectsRed);
- p.setPen (QColor(cdef_color_note_border));
- p.setBrush(QColor(cdef_color_note_bg));
- p.drawPath(noteRects);
- p.setBrush(QColor(cdef_color_note_sel_bg));
- p.setPen(QColor(cdef_color_note_sel));
- p.drawPath(selNoteRects);
- p.setBrush(QColor(cdef_color_note_sel_bg_green));
- p.setPen(QColor(cdef_color_note_sel));
- p.drawPath(selNoteRectsGreen);
- p.setBrush(QColor(cdef_color_note_sel_bg_red));
- p.setPen(QColor(cdef_color_note_sel));
- p.drawPath(selNoteRectsRed);
- p.drawPixmapFragments(cachedLabels.data(), cachedLabels.size(), *_labelCache);
- }
- }
- if (_state.selectionRect.x() > -1 && _state.selectionRect.y() > -1)
- {
- QPen pen = p.pen();
- pen.setWidth(1);
- pen.setColor(QColor(cdef_color_selection_grey,
- cdef_color_selection_grey,
- cdef_color_selection_grey,
- cdef_color_selection_alpha));
- p.setPen(pen);
- p.setBrush(QBrush(QColor(cdef_color_selection_bg_grey,
- cdef_color_selection_bg_grey,
- cdef_color_selection_bg_grey,
- cdef_color_selection_bg_alpha)));
- p.drawRect(_state.selectionRect);
- }
- if (_state.snapLine > -1)
- {
- QPen pen = p.pen();
- pen.setWidth(1);
- pen.setColor(QColor(cdef_color_snap_line));
- p.setPen(pen);
- p.drawLine(_state.snapLine, vSt, _state.snapLine, vSt + _state.viewport.height());
- }
- #if 0
- //TODO: refactor this
- if (_playLine > -1)
- {
- QPen pen = p.pen();
- pen.setWidth(1);
- pen.setColor(QColor(0x00FF0000));
- p.setPen(pen);
- p.drawLine(_playLine, vSt, _playLine, vSt + _state.viewport.height());
- }
- #endif
- _updateCalled = false;
- }
- void qtauNoteEditor::resizeEvent(QResizeEvent *event)
- {
- _state.viewport.setSize(event->size());
- updateBGCache();
- emit heightChanged(_state.viewport.height());
- emit widthChanged (_state.viewport.width ());
- }
- void qtauNoteEditor::mouseDoubleClickEvent(QMouseEvent *event) { _ctrl->mouseDoubleClickEvent(event); }
- void qtauNoteEditor::mouseMoveEvent (QMouseEvent *event) { _ctrl->mouseMoveEvent(event); }
- void qtauNoteEditor::mousePressEvent (QMouseEvent *event) { _ctrl->mousePressEvent(event); }
- void qtauNoteEditor::mouseReleaseEvent (QMouseEvent *event) { _ctrl->mouseReleaseEvent(event); }
- void qtauNoteEditor::wheelEvent(QWheelEvent *event)
- {
- if (event->modifiers() & Qt::ShiftModifier)
- emit hscrolled(event->delta());
- else if (event->modifiers() & Qt::ControlModifier)
- emit zoomed(event->delta());
- else
- emit vscrolled(event->delta());
- }
- void qtauNoteEditor::changeController(qtauEdController *c)
- {
- if (c)
- {
- if (_lastCtrl)
- delete _lastCtrl;
- if (_ctrl)
- _ctrl->cleanup(); // since we're not deleting last one (it's dangerous), need to stop it if it isn't
- _lastCtrl = _ctrl;
- _ctrl = c;
- _ctrl->init();
- }
- }
- void qtauNoteEditor::rmbScrollHappened(const QPoint &delta, const QPoint &offset)
- {
- emit rmbScrolled(delta, offset);
- }
- void qtauNoteEditor::eventHappened(qtauEvent *e)
- {
- emit editorEvent(e);
- }
- void qtauNoteEditor::setPlaybackPosition(int pos)
- {
- _posPulses = pos;
- double pulsesToPixels = (double)_setup.note.width() / c_midi_ppq;
- _playLine = pulsesToPixels*_posPulses;
- lazyUpdate();
- }
- inline bool editorNotesComparison(const qne::editorNote *n1, const qne::editorNote *n2)
- {
- return n1->pulseOffset < n2->pulseOffset;
- }
- struct selectionRange qtauNoteEditor::getSelectionRange()
- {
- struct selectionRange sel;
- sel.end=-1;
- sel.start=-1;
- QVector<qne::editorNote*> ednotes;
- foreach (quint64 key, _notes.selected)
- {
- qne::editorNote &n = _notes.idMap[key];
- if(!ednotes.contains(&n)) ednotes.insert(0,&n);
- }
- qStableSort(ednotes.begin(), ednotes.end(), editorNotesComparison);
- foreach(qne::editorNote* n,ednotes)
- {
- if(n->selected) {
- if(sel.start==-1) sel.start=n->pulseOffset;
- sel.end=n->pulseOffset+n->pulseLength;
- }
- }
- return sel;
- }
- void qtauNoteEditor::doPhonemeTransformation()
- {
- ISynth* s = qtauController::instance()->selectedSynth();
- if(s==nullptr) return;
- QVector<qne::editorNote*> ednotes;
- foreach (quint64 key, _notes.selected)
- {
- qne::editorNote &n = _notes.idMap[key];
- if(!ednotes.contains(&n)) ednotes.insert(0,&n);
- }
- if(ednotes.length()==0) foreach (quint64 key, _notes.idMap.keys())
- {
- qne::editorNote &n = _notes.idMap[key];
- if(!ednotes.contains(&n)) ednotes.insert(0,&n);
- }
- qStableSort(ednotes.begin(), ednotes.end(), editorNotesComparison);
- QStringList lyrics;
- foreach(qne::editorNote* n,ednotes)
- {
- //qDebug() << n->txt;
- lyrics.push_back(n->txt);
- }
- s->doPhonemeTransformation(lyrics);
- qtauEvent_NoteText::noteTextVector v;
- qtauEvent_NoteText::noteTextData d;
- int i=0;
- foreach(qne::editorNote* n,ednotes)
- {
- d.id = n->id;
- d.txt = lyrics[i];
- n->cached = false;
- d.prevTxt = n->txt;
- n->txt = d.txt;
- v.append(d);
- i++;
- }
- qtauEvent_NoteText *e = new qtauEvent_NoteText(v);
- this->eventHappened(e);
- }
- void qtauNoteEditor::reset()
- {
- _notes.grid.clear();
- _notes.idMap.clear();
- }
- void qtauNoteEditor::setNoteColor(quint64 id, EColor color)
- {
- if(_notes.idMap.keys().contains(id))
- _notes.idMap[id].color = color;
- }
- QList<quint64> qtauNoteEditor::getNoteIDs()
- {
- return _notes.idMap.keys();
- }
|