noteEditor.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. /*
  2. This file is part of QTau
  3. Copyright (C) 2013-2018 Tobias "Tomoko" Platen <tplaten@posteo.de>
  4. Copyright (C) 2013 digited <https://github.com/digited>
  5. Copyright (C) 2010-2013 HAL@ShurabaP <https://github.com/haruneko>
  6. QTau is free software: you can redistribute it and/or modify
  7. it under the terms of the GNU General Public License as published by
  8. the Free Software Foundation, either version 3 of the License, or
  9. (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. You should have received a copy of the GNU General Public License
  15. along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. SPDX-License-Identifier: GPL-3.0+
  17. */
  18. #include "ui/noteEditor.h"
  19. #include "ui/noteEditorHandlers.h"
  20. #include "NoteEvents.h"
  21. #define __devloglevel__ 4
  22. #include <qevent.h>
  23. #include <QTime>
  24. #include <QTimer>
  25. #include <qpainter.h>
  26. #include <QPainterPath>
  27. #include <QMimeData>
  28. #include <QLineEdit>
  29. #include <QDebug>
  30. #include "tempomap.h"
  31. //qtauEvent_Plugin
  32. #include "../Controller.h"
  33. #include "../PluginInterfaces.h"
  34. const int cdef_cache_labels_num = 1000;
  35. const int cdef_cache_line_height = 12;
  36. const int cdef_cache_line_width = 100;
  37. const int cdef_lbl_draw_minwidth = 20; // minimal note width on screen in pixels to draw phoneme in it
  38. const int cdef_scroll_margin = 20; // px of space between edge of widget and target rect
  39. const double F_ROUNDER = 0.1; // because float is floor'ed to int by default, so 3.9999999 becomes 3
  40. qtauNoteEditor::qtauNoteEditor(QWidget *parent) :
  41. QWidget(parent), _bgCache(0), _delayingUpdate(false), _updateCalled(false), _lastCtrl(0)
  42. {
  43. setAttribute(Qt::WA_OpaquePaintEvent);
  44. setAttribute(Qt::WA_NoSystemBackground);
  45. setAutoFillBackground(false);
  46. setMouseTracking(true);
  47. setFocusPolicy(Qt::StrongFocus);
  48. _labelCache = new QPixmap(cdef_cache_line_width, cdef_cache_line_height * cdef_cache_labels_num);
  49. _labelCache->fill(Qt::transparent);
  50. _ctrl = new qtauEdController(*this, _setup, _notes, _state);
  51. _playLine = -1;
  52. //t.start();
  53. }
  54. qtauNoteEditor::~qtauNoteEditor()
  55. {
  56. if (_labelCache)
  57. delete _labelCache;
  58. if (_bgCache)
  59. delete _bgCache;
  60. if (_lastCtrl)
  61. delete _lastCtrl;
  62. if (_ctrl)
  63. delete _ctrl;
  64. }
  65. void qtauNoteEditor::configure(const SNoteSetup &newSetup)
  66. {
  67. _ctrl->reset();
  68. bool gridChanged = newSetup.note != _setup.note || 1;//TODO add hasChanged()
  69. _setup = newSetup;
  70. if (gridChanged)
  71. {
  72. recalcNoteRects();
  73. updateBGCache();
  74. lazyUpdate();
  75. }
  76. }
  77. void qtauNoteEditor::deleteSelected()
  78. {
  79. if (!_notes.selected.isEmpty())
  80. {
  81. _ctrl->reset();
  82. _ctrl->deleteSelected();
  83. }
  84. }
  85. void qtauNoteEditor::onEvent(qtauEvent *e)
  86. {
  87. _ctrl->onEvent(e);
  88. }
  89. void qtauNoteEditor::lazyUpdate()
  90. {
  91. update();
  92. }
  93. //--------------------------------------------------
  94. void qtauNoteEditor::recalcNoteRects()
  95. {
  96. _notes.grid.clear();
  97. //_setup.barWidth = _setup.note.width() * 4;//FIXME
  98. _setup.octHeight = _setup.note.height() * 12;
  99. double pulsesToPixels = (double)_setup.note.width() / c_midi_ppq;
  100. int startBar = 0, endBar = 0;
  101. _playLine = pulsesToPixels*_posPulses;
  102. foreach (quint64 key, _notes.idMap.keys())
  103. {
  104. qne::editorNote &n = _notes.idMap[key];
  105. int nn1 = n.keyNumber+1;
  106. if(n.keyNumber<0 || n.keyNumber>127) {
  107. DEVLOG_ERROR("invalid key number");
  108. return;
  109. }
  110. n.r.setRect((double)n.pulseOffset * pulsesToPixels + F_ROUNDER,
  111. ((_setup.baseOctave + _setup.numOctaves - 1) * 12 - nn1) * _setup.note.height(),
  112. (double)n.pulseLength * pulsesToPixels + F_ROUNDER, _setup.note.height());
  113. // determine bar(s) that have that note fully or partially
  114. startBar = _setup.getBarForScreenOffset(n.r.left());
  115. endBar = _setup.getBarForScreenOffset(n.r.right());
  116. if (endBar >= _notes.grid.size())
  117. _notes.grid.resize(endBar + 10);
  118. _notes.grid[startBar].append(n.id);
  119. if (endBar != startBar)
  120. _notes.grid[endBar].append(n.id);
  121. }
  122. }
  123. void qtauNoteEditor::updateBGCache()
  124. {
  125. //_setup.barWidth = _setup.note.width() * 4;
  126. _setup.octHeight = _setup.note.height() * 12;
  127. int requiredCacheWidth = _setup.barScreenOffsets[_setup.numBars-1] + _setup.note.width() * 8;
  128. int requiredCacheHeight = (geometry().height() / _setup.octHeight + 2) * _setup.octHeight;
  129. if (!_bgCache || (_bgCache->width() < requiredCacheWidth || _bgCache->height() < requiredCacheHeight))
  130. {
  131. if (_bgCache)
  132. delete _bgCache;
  133. _bgCache = new QPixmap(requiredCacheWidth, requiredCacheHeight);
  134. }
  135. // prepare bg/grid data =========================================
  136. QPainterPath blacks; // rects for black keys
  137. QPainterPath outerLines;
  138. QPainterPath innerLines;
  139. // calculating indexes of visible notes of grid ----------------------
  140. int hSt = 0;
  141. int vSt = 0;
  142. int hEnd = requiredCacheWidth - 1;
  143. int vEnd = requiredCacheHeight - 1;
  144. int pxHOff = 0;
  145. int pxVOff = 0;
  146. int bar = 0;
  147. int beat = 0;
  148. int octCounter = 0;
  149. // horizontal pass to calc note & bar vertical delimiter lines
  150. do {
  151. fraction time = _setup.tmap->getTimeSignatureForBar(bar); //1
  152. if (beat==time.numerator)
  153. {
  154. outerLines.moveTo(QPoint(pxHOff, vSt ));
  155. outerLines.lineTo(QPoint(pxHOff, vEnd));
  156. beat = 1;
  157. bar++;
  158. time = _setup.tmap->getTimeSignatureForBar(bar); //2
  159. }
  160. else {
  161. innerLines.moveTo(QPoint(pxHOff, vSt ));
  162. innerLines.lineTo(QPoint(pxHOff, vEnd));
  163. beat++;
  164. }
  165. pxHOff += _setup.note.width() * 4.0/time.denominator;
  166. } while (pxHOff <= hEnd);
  167. QRect noteBG(hSt, 0, hEnd - hSt, _setup.note.height());
  168. // vertical pass to calc note backgrounds, note & octave delimiter lines
  169. do {
  170. if (octCounter == 12)
  171. octCounter = 0;
  172. if (octCounter == 0)
  173. {
  174. outerLines.moveTo(QPoint(hSt, pxVOff));
  175. outerLines.lineTo(QPoint(hEnd, pxVOff));
  176. }
  177. else {
  178. innerLines.moveTo(QPoint(hSt, pxVOff));
  179. innerLines.lineTo(QPoint(hEnd, pxVOff));
  180. }
  181. //--- note bg's --------------
  182. noteBG.moveTop(pxVOff);
  183. if (octCounter == 1 || octCounter == 3 || octCounter == 5 || octCounter == 8 || octCounter == 10)
  184. blacks.addRect(noteBG);
  185. //----------------------------
  186. octCounter++;
  187. pxVOff += _setup.note.height();
  188. } while (pxVOff <= vEnd);
  189. // paint 'em! ======================
  190. _bgCache->fill (Qt::white);
  191. QPainter p (_bgCache);
  192. QBrush brush(p.brush());
  193. p.setPen(Qt::NoPen);
  194. // background -------------
  195. if (!blacks.isEmpty())
  196. {
  197. brush.setStyle(Qt::Dense6Pattern);
  198. brush.setColor(cdef_color_black_noteline_bg);
  199. p.setBrush(brush);
  200. p.drawPath(blacks);
  201. }
  202. p.setPen(Qt::SolidLine);
  203. // lines ------------------
  204. if (!innerLines.isEmpty())
  205. {
  206. p.setPen(QColor(cdef_color_inner_line));
  207. p.drawPath(innerLines);
  208. }
  209. if (!outerLines.isEmpty())
  210. {
  211. p.setPen(QColor(cdef_color_outer_line));
  212. p.drawPath(outerLines);
  213. }
  214. }
  215. void qtauNoteEditor::setVOffset(int voff)
  216. {
  217. if (voff != _state.viewport.y())
  218. {
  219. _ctrl->reset();
  220. _state.viewport.moveTop(voff);
  221. lazyUpdate();
  222. }
  223. }
  224. void qtauNoteEditor::setHOffset(int hoff)
  225. {
  226. if (hoff != _state.viewport.x())
  227. {
  228. _ctrl->reset();
  229. _state.viewport.moveLeft(hoff);
  230. lazyUpdate();
  231. }
  232. }
  233. QPoint qtauNoteEditor::scrollTo(const QRect &r)
  234. {
  235. QPoint result = _state.viewport.topLeft();
  236. if (r.x() < _state.viewport.x())
  237. result.setX(r.x() - cdef_scroll_margin);
  238. else
  239. if (r.x() > _state.viewport.x() + geometry().width() - cdef_cache_line_width - cdef_scroll_margin)
  240. result.setX(r.x() - geometry().width() / 2);
  241. if (r.y() < _state.viewport.y())
  242. result.setY(r.y() - cdef_scroll_margin);
  243. else
  244. if (r.y() > _state.viewport.y() + geometry().height() - cdef_scroll_margin)
  245. result.setY(r.y() - geometry().height() / 2);
  246. if (result != _state.viewport.topLeft())
  247. emit requestsOffset(result);
  248. return result;
  249. }
  250. //----------------------------------------------------
  251. void qtauNoteEditor::paintEvent(QPaintEvent *event)
  252. {
  253. //lastUpdate = t.elapsed();
  254. emit repaintDynDrawer();
  255. // draw bg
  256. QRect r(event->rect());
  257. int hSt = r.x() + _state.viewport.x();
  258. int firstBar = _setup.getBarForScreenOffset(hSt);
  259. int cacheHOffset = hSt;// - firstBar * _setup.barWidth;
  260. int vSt = r.y() + _state.viewport.y();
  261. int firstOct = vSt / _setup.octHeight;
  262. int cacheVOffset = vSt - firstOct * _setup.octHeight;
  263. QRect cacheRect(r);
  264. cacheRect.moveTo(cacheRect.x() + cacheHOffset, cacheRect.y() + cacheVOffset);
  265. QPainter p(this);
  266. p.drawPixmap(r, *_bgCache, cacheRect);
  267. // singing notes with phoneme labels -------
  268. int barSt = firstBar;
  269. int barEnd = _setup.getBarForScreenOffset(hSt + r.width());
  270. QPainterPath noteRects;
  271. QPainterPath selNoteRects;
  272. QPainterPath selNoteRectsRed;
  273. QPainterPath selNoteRectsGreen;
  274. QPainterPath noteRectsRed;
  275. QPainterPath noteRectsGreen;
  276. QMap<quint64, bool> processedIDMap;
  277. QVector<QPainter::PixmapFragment> cachedLabels;
  278. QPainter cacheP(_labelCache);
  279. cacheP.setBrush(Qt::white); // to clear pixmap completely
  280. p.translate(-_state.viewport.topLeft());
  281. if (barSt < _notes.grid.size())
  282. {
  283. if (barEnd >= _notes.grid.size())
  284. barEnd = _notes.grid.size() - 1;
  285. bool hasVisibleNotes = false;
  286. r.moveTo(_state.viewport.topLeft());
  287. for (int i = barSt; i < barEnd + 1; ++i)
  288. for (int k = 0; k < _notes.grid[i].size(); ++k)
  289. {
  290. qne::editorNote &n = _notes.idMap[_notes.grid[i][k]];
  291. if (!processedIDMap.contains(n.id) && r.intersects(n.r))
  292. {
  293. hasVisibleNotes = true;
  294. if (n.selected)
  295. {
  296. if(n.color==EColor::green)
  297. selNoteRectsGreen.addRect(n.r);
  298. else if(n.color==EColor::red)
  299. selNoteRectsRed.addRect(n.r);
  300. else
  301. selNoteRects.addRect(n.r);
  302. }
  303. else
  304. {
  305. if(n.color==EColor::green)
  306. noteRectsGreen.addRect(n.r);
  307. else if(n.color==EColor::red)
  308. noteRectsRed.addRect(n.r);
  309. else
  310. noteRects.addRect(n.r);
  311. }
  312. if (n.r.width() > cdef_lbl_draw_minwidth) // don't draw labels for too narrow rects
  313. {
  314. QRectF fR(0, n.id * cdef_cache_line_height,
  315. cdef_cache_line_width, cdef_cache_line_height);
  316. if (!n.cached)
  317. {
  318. // clearing cache line
  319. cacheP.setCompositionMode(QPainter::CompositionMode_Clear);
  320. cacheP.drawRect(fR);
  321. cacheP.setCompositionMode(QPainter::CompositionMode_SourceOver);
  322. cacheP.drawText(fR, Qt::AlignLeft | Qt::AlignVCenter, n.txt);
  323. n.cached = true;
  324. }
  325. cachedLabels.append(QPainter::PixmapFragment::create(
  326. QPointF(n.r.x() + 55, n.r.y() + 7), fR)); // wtf is with pos?..
  327. }
  328. }
  329. processedIDMap[n.id] = true; // to avoid processing notes that go through 2 or more bars
  330. }
  331. if (hasVisibleNotes)
  332. {
  333. p.setPen (QColor(cdef_color_note_border));
  334. p.setBrush(QColor(cdef_color_note_bg_green));
  335. p.drawPath(noteRectsGreen);
  336. p.setPen (QColor(cdef_color_note_border));
  337. p.setBrush(QColor(cdef_color_note_bg_red));
  338. p.drawPath(noteRectsRed);
  339. p.setPen (QColor(cdef_color_note_border));
  340. p.setBrush(QColor(cdef_color_note_bg));
  341. p.drawPath(noteRects);
  342. p.setBrush(QColor(cdef_color_note_sel_bg));
  343. p.setPen(QColor(cdef_color_note_sel));
  344. p.drawPath(selNoteRects);
  345. p.setBrush(QColor(cdef_color_note_sel_bg_green));
  346. p.setPen(QColor(cdef_color_note_sel));
  347. p.drawPath(selNoteRectsGreen);
  348. p.setBrush(QColor(cdef_color_note_sel_bg_red));
  349. p.setPen(QColor(cdef_color_note_sel));
  350. p.drawPath(selNoteRectsRed);
  351. p.drawPixmapFragments(cachedLabels.data(), cachedLabels.size(), *_labelCache);
  352. }
  353. }
  354. if (_state.selectionRect.x() > -1 && _state.selectionRect.y() > -1)
  355. {
  356. QPen pen = p.pen();
  357. pen.setWidth(1);
  358. pen.setColor(QColor(cdef_color_selection_grey,
  359. cdef_color_selection_grey,
  360. cdef_color_selection_grey,
  361. cdef_color_selection_alpha));
  362. p.setPen(pen);
  363. p.setBrush(QBrush(QColor(cdef_color_selection_bg_grey,
  364. cdef_color_selection_bg_grey,
  365. cdef_color_selection_bg_grey,
  366. cdef_color_selection_bg_alpha)));
  367. p.drawRect(_state.selectionRect);
  368. }
  369. if (_state.snapLine > -1)
  370. {
  371. QPen pen = p.pen();
  372. pen.setWidth(1);
  373. pen.setColor(QColor(cdef_color_snap_line));
  374. p.setPen(pen);
  375. p.drawLine(_state.snapLine, vSt, _state.snapLine, vSt + _state.viewport.height());
  376. }
  377. #if 0
  378. //TODO: refactor this
  379. if (_playLine > -1)
  380. {
  381. QPen pen = p.pen();
  382. pen.setWidth(1);
  383. pen.setColor(QColor(0x00FF0000));
  384. p.setPen(pen);
  385. p.drawLine(_playLine, vSt, _playLine, vSt + _state.viewport.height());
  386. }
  387. #endif
  388. _updateCalled = false;
  389. }
  390. void qtauNoteEditor::resizeEvent(QResizeEvent *event)
  391. {
  392. _state.viewport.setSize(event->size());
  393. updateBGCache();
  394. emit heightChanged(_state.viewport.height());
  395. emit widthChanged (_state.viewport.width ());
  396. }
  397. void qtauNoteEditor::mouseDoubleClickEvent(QMouseEvent *event) { _ctrl->mouseDoubleClickEvent(event); }
  398. void qtauNoteEditor::mouseMoveEvent (QMouseEvent *event) { _ctrl->mouseMoveEvent(event); }
  399. void qtauNoteEditor::mousePressEvent (QMouseEvent *event) { _ctrl->mousePressEvent(event); }
  400. void qtauNoteEditor::mouseReleaseEvent (QMouseEvent *event) { _ctrl->mouseReleaseEvent(event); }
  401. void qtauNoteEditor::wheelEvent(QWheelEvent *event)
  402. {
  403. if (event->modifiers() & Qt::ShiftModifier)
  404. emit hscrolled(event->delta());
  405. else if (event->modifiers() & Qt::ControlModifier)
  406. emit zoomed(event->delta());
  407. else
  408. emit vscrolled(event->delta());
  409. }
  410. void qtauNoteEditor::changeController(qtauEdController *c)
  411. {
  412. if (c)
  413. {
  414. if (_lastCtrl)
  415. delete _lastCtrl;
  416. if (_ctrl)
  417. _ctrl->cleanup(); // since we're not deleting last one (it's dangerous), need to stop it if it isn't
  418. _lastCtrl = _ctrl;
  419. _ctrl = c;
  420. _ctrl->init();
  421. }
  422. }
  423. void qtauNoteEditor::rmbScrollHappened(const QPoint &delta, const QPoint &offset)
  424. {
  425. emit rmbScrolled(delta, offset);
  426. }
  427. void qtauNoteEditor::eventHappened(qtauEvent *e)
  428. {
  429. emit editorEvent(e);
  430. }
  431. void qtauNoteEditor::setPlaybackPosition(int pos)
  432. {
  433. _posPulses = pos;
  434. double pulsesToPixels = (double)_setup.note.width() / c_midi_ppq;
  435. _playLine = pulsesToPixels*_posPulses;
  436. lazyUpdate();
  437. }
  438. inline bool editorNotesComparison(const qne::editorNote *n1, const qne::editorNote *n2)
  439. {
  440. return n1->pulseOffset < n2->pulseOffset;
  441. }
  442. struct selectionRange qtauNoteEditor::getSelectionRange()
  443. {
  444. struct selectionRange sel;
  445. sel.end=-1;
  446. sel.start=-1;
  447. QVector<qne::editorNote*> ednotes;
  448. foreach (quint64 key, _notes.selected)
  449. {
  450. qne::editorNote &n = _notes.idMap[key];
  451. if(!ednotes.contains(&n)) ednotes.insert(0,&n);
  452. }
  453. qStableSort(ednotes.begin(), ednotes.end(), editorNotesComparison);
  454. foreach(qne::editorNote* n,ednotes)
  455. {
  456. if(n->selected) {
  457. if(sel.start==-1) sel.start=n->pulseOffset;
  458. sel.end=n->pulseOffset+n->pulseLength;
  459. }
  460. }
  461. return sel;
  462. }
  463. void qtauNoteEditor::doPhonemeTransformation()
  464. {
  465. ISynth* s = qtauController::instance()->selectedSynth();
  466. if(s==nullptr) return;
  467. QVector<qne::editorNote*> ednotes;
  468. foreach (quint64 key, _notes.selected)
  469. {
  470. qne::editorNote &n = _notes.idMap[key];
  471. if(!ednotes.contains(&n)) ednotes.insert(0,&n);
  472. }
  473. if(ednotes.length()==0) foreach (quint64 key, _notes.idMap.keys())
  474. {
  475. qne::editorNote &n = _notes.idMap[key];
  476. if(!ednotes.contains(&n)) ednotes.insert(0,&n);
  477. }
  478. qStableSort(ednotes.begin(), ednotes.end(), editorNotesComparison);
  479. QStringList lyrics;
  480. foreach(qne::editorNote* n,ednotes)
  481. {
  482. //qDebug() << n->txt;
  483. lyrics.push_back(n->txt);
  484. }
  485. s->doPhonemeTransformation(lyrics);
  486. qtauEvent_NoteText::noteTextVector v;
  487. qtauEvent_NoteText::noteTextData d;
  488. int i=0;
  489. foreach(qne::editorNote* n,ednotes)
  490. {
  491. d.id = n->id;
  492. d.txt = lyrics[i];
  493. n->cached = false;
  494. d.prevTxt = n->txt;
  495. n->txt = d.txt;
  496. v.append(d);
  497. i++;
  498. }
  499. qtauEvent_NoteText *e = new qtauEvent_NoteText(v);
  500. this->eventHappened(e);
  501. }
  502. void qtauNoteEditor::reset()
  503. {
  504. _notes.grid.clear();
  505. _notes.idMap.clear();
  506. }
  507. void qtauNoteEditor::setNoteColor(quint64 id, EColor color)
  508. {
  509. if(_notes.idMap.keys().contains(id))
  510. _notes.idMap[id].color = color;
  511. }
  512. QList<quint64> qtauNoteEditor::getNoteIDs()
  513. {
  514. return _notes.idMap.keys();
  515. }