noteEditorHandlers.cpp 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070
  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/noteEditorHandlers.h"
  19. #include "NoteEvents.h"
  20. #include "ui/noteEditor.h"
  21. #include <qevent.h>
  22. #include <QKeyEvent>
  23. #include <QLineEdit>
  24. #include "../Controller.h"
  25. #include "../PluginInterfaces.h"
  26. //FIXME -- new develog system
  27. #define __devloglevel__ 0
  28. const int CONST_NOTE_RESIZE_CURSOR_MARGIN = 6;
  29. inline int snap(int value, int unit, int baseValue = 0) {
  30. int baseOffset = baseValue % unit;
  31. value -= baseOffset;
  32. int prev = value / unit;
  33. float percent = (float)(value % unit) / (float)unit;
  34. return ((percent >= 0.5) ? (prev + 1) * unit : prev * unit) + baseOffset;
  35. }
  36. void qtauEdController::changeController(qtauEdController *c) {
  37. _owner->changeController(c);
  38. }
  39. void qtauEdController::eventHappened(qtauEvent *e) { _owner->eventHappened(e); }
  40. void qtauEdController::recalcNoteRects() { _owner->recalcNoteRects(); }
  41. void qtauEdController::lazyUpdate() { _owner->lazyUpdate(); }
  42. void qtauEdController::onEvent(qtauEvent *e) {
  43. reset();
  44. switch (e->type()) {
  45. case ENoteEvents::add:
  46. onNoteAdd(static_cast<qtauEvent_NoteAddition *>(e));
  47. break;
  48. case ENoteEvents::text:
  49. onNoteText(static_cast<qtauEvent_NoteText *>(e));
  50. break;
  51. case ENoteEvents::move:
  52. onNoteMove(static_cast<qtauEvent_NoteMove *>(e));
  53. break;
  54. case ENoteEvents::resize:
  55. onNoteResize(static_cast<qtauEvent_NoteResize *>(e));
  56. break;
  57. //case ENoteEvents::effect:
  58. //onNoteEffect(static_cast<qtauEvent_NoteEffect *>(e));
  59. //break;
  60. default: {}
  61. DEVLOG_ERROR(QString("Editor controller got event of unknown type %1")
  62. .arg(e->type()));
  63. }
  64. }
  65. void qtauEdController::deleteSelected() {
  66. if (!_notes->selected.isEmpty()) {
  67. _pointedNote = 0;
  68. qtauEvent_NoteAddition::noteAddVector v;
  69. foreach (const quint64 &id, _notes->selected) {
  70. const qne::editorNote &n = _notes->idMap[id];
  71. qtauEvent_NoteAddition::noteAddData d;
  72. d.id = id;
  73. d.lyrics = n.txt;
  74. d.pulseLength = n.pulseLength;
  75. d.pulseOffset = n.pulseOffset;
  76. d.keyNumber = n.keyNumber;
  77. v.append(d);
  78. removeFromGrid(n.r.left(), n.id);
  79. removeFromGrid(n.r.right(), n.id);
  80. _notes->idMap.remove(id);
  81. }
  82. _notes->selected.clear();
  83. lazyUpdate();
  84. qtauEvent_NoteAddition *event = new qtauEvent_NoteAddition(v, true, true);
  85. eventHappened(event);
  86. }
  87. }
  88. void qtauEdController::mouseDoubleClickEvent(QMouseEvent *event) {
  89. unselectAll();
  90. if (event->button() == Qt::LeftButton && _pointedNote &&
  91. _state->editingEnabled) {
  92. _notes->selected.append(_pointedNote->id);
  93. _pointedNote->selected = true;
  94. QRect r(_pointedNote->r);
  95. r.setSize(QSize(textInputWidth, _setup->note.height()));
  96. if (!_state->viewport.contains(r)) _owner->scrollTo(r);
  97. _owner->lazyUpdate();
  98. _owner->changeController(new qtauEd_TextInput(this));
  99. }
  100. }
  101. void qtauEdController::mousePressEvent(QMouseEvent *event) {
  102. if (_state->rmbScrollEnabled && event->button() == Qt::RightButton) {
  103. _rmbScrollStartPos = event->pos();
  104. _rmbStartVpOffset = _state->viewport.topLeft();
  105. _rmbDragging = true;
  106. _owner->setCursor(Qt::ClosedHandCursor);
  107. } else if (event->button() == Qt::LeftButton) {
  108. _absFirstClickPos = event->pos() + _state->viewport.topLeft();
  109. if (_state->editingEnabled &&
  110. _owner->cursor().shape() == Qt::SplitHCursor && _pointedNote) {
  111. int msPosInNote =
  112. event->pos().x() + _state->viewport.x() - _pointedNote->r.x();
  113. bool left = (float)msPosInNote / _pointedNote->r.width() < 0.5;
  114. _owner->changeController(new qtauEd_ResizeNote(this, left));
  115. }
  116. }
  117. }
  118. void qtauEdController::mouseMoveEvent(QMouseEvent *event) {
  119. if (_rmbDragging)
  120. _owner->rmbScrollHappened(event->pos() - _rmbScrollStartPos,
  121. _rmbStartVpOffset);
  122. else {
  123. QPoint absPos(event->pos() + _state->viewport.topLeft());
  124. QPoint delta = absPos - _absFirstClickPos;
  125. _pointedNote = noteInPoint(absPos);
  126. if (_pointedNote) {
  127. if (event->buttons() & Qt::LeftButton) {
  128. if (abs(delta.x()) > 3 ||
  129. abs(delta.y()) >
  130. 3) // mousepress exceeded accidental slide click margin
  131. {
  132. if (!_pointedNote->selected) {
  133. unselectAll();
  134. _pointedNote->selected = true;
  135. _notes->selected.append(_pointedNote->id);
  136. }
  137. _owner->changeController(new qtauEd_DragNotes(this));
  138. }
  139. } else if (_state->editingEnabled) // no mousepress, just moving cursor
  140. {
  141. // if above edge, show resize cursor
  142. if (absPos.x() - _pointedNote->r.left() <=
  143. CONST_NOTE_RESIZE_CURSOR_MARGIN ||
  144. _pointedNote->r.right() - absPos.x() <=
  145. CONST_NOTE_RESIZE_CURSOR_MARGIN)
  146. _owner->setCursor(Qt::SplitHCursor);
  147. else
  148. _owner->setCursor(Qt::ArrowCursor);
  149. } else // no button pressed, outside any notes
  150. if (_owner->cursor().shape() != Qt::ArrowCursor)
  151. _owner->setCursor(Qt::ArrowCursor);
  152. } else {
  153. if (_owner->cursor().shape() != Qt::ArrowCursor)
  154. _owner->setCursor(Qt::ArrowCursor);
  155. if (!noteInPoint(_absFirstClickPos) &&
  156. (event->buttons() & Qt::LeftButton &&
  157. (abs(delta.x()) > 3 || abs(delta.y()) > 3))) {
  158. if (_state->editingEnabled)
  159. _owner->changeController(new qtauEd_AddNote(this));
  160. else
  161. _owner->changeController(new qtauEd_SelectRect(this));
  162. }
  163. }
  164. }
  165. }
  166. void qtauEdController::mouseReleaseEvent(QMouseEvent *event) {
  167. if (_rmbDragging) {
  168. _rmbDragging = false;
  169. _owner->setCursor(Qt::ArrowCursor);
  170. } else if (_pointedNote) {
  171. if (event->modifiers() & Qt::ControlModifier)
  172. {
  173. // control toggles selection
  174. if (_pointedNote->selected) // unselect
  175. _notes->selected.remove(_notes->selected.indexOf(_pointedNote->id));
  176. else // select
  177. _notes->selected.prepend(_pointedNote->id);
  178. _pointedNote->selected = !_pointedNote->selected;
  179. _owner->selectionChanged();
  180. } else if (event->modifiers() &
  181. Qt::ShiftModifier)
  182. {
  183. // just add it to selection, if not already
  184. if (!_pointedNote->selected) {
  185. _pointedNote->selected = true;
  186. _notes->selected.prepend(_pointedNote->id);
  187. _owner->selectionChanged();
  188. }
  189. } else // unselect everything except this note
  190. {
  191. unselectAll();
  192. _notes->selected.append(_pointedNote->id);
  193. _pointedNote->selected = true;
  194. _owner->selectionChanged();
  195. }
  196. } else
  197. {
  198. unselectAll();
  199. _owner->selectionChanged();
  200. }
  201. _absFirstClickPos = QPoint(-1, -1);
  202. _owner->lazyUpdate();
  203. }
  204. void qtauEdController::onNoteAdd(qtauEvent_NoteAddition *event) {
  205. unselectAll();
  206. foreach (const qtauEvent_NoteAddition::noteAddData &d, event->getAdded()) {
  207. bool reallyForward = (event->isForward() && !event->isDeleteEvent()) ||
  208. (!event->isForward() && event->isDeleteEvent());
  209. if (reallyForward) {
  210. qne::editorNote n;
  211. n.id = d.id;
  212. n.txt = d.lyrics;
  213. n.pulseOffset = d.pulseOffset;
  214. n.pulseLength = d.pulseLength;
  215. n.keyNumber = d.keyNumber;
  216. n.velocity = n.velocity;
  217. _idOffset = qMax(_idOffset, d.id);
  218. _notes->idMap[d.id] = n;
  219. } else {
  220. qne::editorNote &n = _notes->idMap[d.id];
  221. removeFromGrid(n.r.left(), n.id);
  222. removeFromGrid(n.r.right(), n.id);
  223. _notes->idMap.remove(n.id);
  224. }
  225. }
  226. _owner->recalcNoteRects();
  227. _owner->lazyUpdate();
  228. }
  229. void qtauEdController::onNoteResize(qtauEvent_NoteResize *event) {
  230. foreach (const qtauEvent_NoteResize::noteResizeData &d, event->getResized()) {
  231. qne::editorNote &n = _notes->idMap[d.id];
  232. removeFromGrid(n.r.left(), n.id);
  233. removeFromGrid(n.r.right(), n.id);
  234. if (event->isForward()) {
  235. n.pulseOffset = d.offset;
  236. n.pulseLength = d.length;
  237. } else {
  238. n.pulseOffset = d.prevOffset;
  239. n.pulseLength = d.prevLength;
  240. }
  241. addToGrid(n.r.left(), n.id);
  242. addToGrid(n.r.right(), n.id);
  243. }
  244. recalcNoteRects();
  245. _owner->lazyUpdate();
  246. }
  247. void qtauEdController::onNoteMove(qtauEvent_NoteMove *event) {
  248. foreach (const qtauEvent_NoteMove::noteMoveData &d, event->getMoved()) {
  249. qne::editorNote &n = _notes->idMap[d.id];
  250. removeFromGrid(n.r.left(), n.id);
  251. removeFromGrid(n.r.right(), n.id);
  252. if (event->isForward()) {
  253. n.pulseOffset += d.pulseOffDelta;
  254. n.keyNumber = d.keyNumber;
  255. } else {
  256. n.pulseOffset -= d.pulseOffDelta;
  257. n.keyNumber = d.prevKeyNumber;
  258. }
  259. addToGrid(n.r.left(), n.id);
  260. addToGrid(n.r.right(), n.id);
  261. }
  262. recalcNoteRects();
  263. _owner->lazyUpdate();
  264. }
  265. void qtauEdController::onNoteText(qtauEvent_NoteText *event) {
  266. foreach (const qtauEvent_NoteText::noteTextData &d, event->getText()) {
  267. qne::editorNote &n = _notes->idMap[d.id];
  268. if (event->isForward())
  269. n.txt = d.txt;
  270. else
  271. n.txt = d.prevTxt;
  272. n.cached = false;
  273. }
  274. _owner->lazyUpdate();
  275. }
  276. #if 0
  277. void qtauEdController::onNoteEffect(qtauEvent_NoteEffect *) {
  278. // FIXME: does nothing -- remove if not needed
  279. }
  280. #endif
  281. qne::editorNote *qtauEdController::noteInPoint(const QPoint &p) {
  282. qne::editorNote *result = 0;
  283. int barUnderCursor = _setup->getBarForScreenOffset(p.x());
  284. if (barUnderCursor == -1) return nullptr;
  285. if (barUnderCursor < _notes->grid.size() && barUnderCursor >= 0) {
  286. for (int i = 0; i < _notes->grid[barUnderCursor].size(); ++i) {
  287. qne::editorNote &n = _notes->idMap[_notes->grid[barUnderCursor][i]];
  288. if (n.r.contains(p)) {
  289. result = &n;
  290. break;
  291. }
  292. }
  293. }
  294. return result;
  295. }
  296. //========================================================================
  297. qtauEd_TextInput::qtauEd_TextInput(qtauNoteEditor &ne, SNoteSetup &ns,
  298. qne::editorNotes &nts, qne::editorState &st)
  299. : qtauEdController(ne, ns, nts, st),
  300. _managedOnEdited(false),
  301. _editingNote(false) {}
  302. qtauEd_TextInput::qtauEd_TextInput(qtauEdController *c) : qtauEdController(c) {}
  303. qtauEd_TextInput::~qtauEd_TextInput() {}
  304. void qtauEd_TextInput::cleanup() {
  305. if (_editingNote) {
  306. _editingNote = false;
  307. disconnect(_edit, SIGNAL(editingFinished()), this, SLOT(onEdited()));
  308. disconnect(_edit, SIGNAL(returnPressed()), this, SLOT(unfocus()));
  309. _edit->removeEventFilter(this);
  310. _owner->setFocus();
  311. _edit->setVisible(false);
  312. }
  313. }
  314. void qtauEd_TextInput::init() {
  315. _editingNote = !_notes->selected.isEmpty();
  316. if (_editingNote) {
  317. if (!_edit) _edit = new QLineEdit(_owner);
  318. connect(_edit, SIGNAL(editingFinished()), SLOT(onEdited()));
  319. connect(_edit, SIGNAL(returnPressed()), SLOT(unfocus()));
  320. _editedNote = _pointedNote; // may change when clicked outside editbox, so
  321. // need to store
  322. QRect r(_pointedNote->r);
  323. r.moveTo(r.topLeft() - _state->viewport.topLeft());
  324. r.setRight(r.right() + 1);
  325. r.setBottom(r.bottom() + 1);
  326. _edit->setGeometry(r);
  327. _edit->setVisible(true);
  328. _edit->setText(_editedNote->txt);
  329. _edit->setFocus();
  330. _edit->selectAll();
  331. _edit->installEventFilter(this);
  332. }
  333. }
  334. bool qtauEd_TextInput::getEditingNote() const { return _editingNote; }
  335. void qtauEd_TextInput::setEditingNote(bool value) { _editingNote = value; }
  336. void qtauEd_TextInput::unfocus() { _owner->setFocus(); }
  337. void qtauEd_TextInput::reset() {
  338. cleanup();
  339. changeController(new qtauEdController(this));
  340. }
  341. void qtauEd_TextInput::onEdited() {
  342. if (_editingNote && _editedNote) {
  343. _editingNote = false;
  344. QString txt = _edit->text();
  345. disconnect(_edit, SIGNAL(editingFinished()), this, SLOT(onEdited()));
  346. _edit->setVisible(false);
  347. if (txt != _editedNote->txt) {
  348. qtauEvent_NoteText::noteTextData d;
  349. if (txt.endsWith(" ")) {
  350. ISynth *s = qtauController::instance()->selectedSynth();
  351. if (s) txt = s->getTranscription(txt);
  352. }
  353. d.id = _editedNote->id;
  354. d.txt = txt;
  355. d.prevTxt = _editedNote->txt;
  356. qtauEvent_NoteText::noteTextVector v;
  357. v.append(d);
  358. qtauEvent_NoteText *add = new qtauEvent_NoteText(v);
  359. _editedNote->txt = txt;
  360. _editedNote->cached = false;
  361. _editedNote = 0;
  362. eventHappened(add);
  363. }
  364. lazyUpdate();
  365. if (!_managedOnEdited) changeController(new qtauEdController(this));
  366. }
  367. }
  368. bool qtauEd_TextInput::eventFilter(QObject * /*obj*/, QEvent *event) {
  369. bool result = false; // we don't handle the event by default, passing it to
  370. // be handled by qlineedit itself
  371. if (_editingNote && (event->type() == QEvent::KeyPress ||
  372. event->type() == QEvent::Shortcut)) {
  373. QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
  374. if (keyEvent->key() == Qt::Key_Tab ||
  375. (keyEvent->key() == Qt::Key_Backtab)) {
  376. int targetOffset = _editedNote->r.x();
  377. int stBar = _setup->getBarForScreenOffset(targetOffset);
  378. if (stBar == -1) return false;
  379. qne::editorNote *nextNote = 0;
  380. if (stBar < _notes->grid.size()) {
  381. if (keyEvent->key() ==
  382. Qt::Key_Backtab) // move editing focus to previous note ----------
  383. {
  384. if (targetOffset >
  385. 0) // no point in shift-tabbing if current note is first already
  386. {
  387. for (int bar = stBar; bar >= 0; --bar) {
  388. const QVector<quint64> &gridBar = _notes->grid[bar];
  389. for (int n = 0; n < gridBar.size(); ++n) {
  390. if (gridBar[n] != _editedNote->id) {
  391. qne::editorNote *note = &_notes->idMap[gridBar[n]];
  392. if (note->r.x() < targetOffset)
  393. if (!nextNote || nextNote->r.x() < note->r.x())
  394. nextNote = note;
  395. }
  396. } // looping though all notes in a bar without break to find
  397. // closest one, since they're unordered
  398. if (nextNote) break;
  399. } // end looping bar grid
  400. }
  401. } else // ------------------------------------------ move editing focus
  402. // to next note --------------
  403. {
  404. int endBar = _notes->grid.size();
  405. for (int bar = stBar; bar < endBar; ++bar) {
  406. const QVector<quint64> &gridBar = _notes->grid[bar];
  407. for (int n = 0; n < gridBar.size(); ++n) {
  408. if (gridBar[n] != _editedNote->id) {
  409. qne::editorNote *note = &_notes->idMap[gridBar[n]];
  410. if (note->r.x() > targetOffset)
  411. if (!nextNote || nextNote->r.x() > note->r.x())
  412. nextNote = note;
  413. }
  414. } // looping though all notes in a bar without break to find
  415. // closest one, since they're unordered
  416. if (nextNote) break;
  417. } // end looping bar grid
  418. }
  419. } else
  420. DEVLOG_ERROR(
  421. "Currently edited note isn't in notes grid. How could this "
  422. "happen?..");
  423. _managedOnEdited = nextNote != 0;
  424. onEdited(); // shouldn't delete this if managedOnEdited
  425. if (nextNote) {
  426. result = true; // flag qlineedit that the event was handled
  427. _pointedNote = nextNote;
  428. changeController(new qtauEd_TextInput(this));
  429. }
  430. }
  431. }
  432. return result;
  433. }
  434. //========================================================================
  435. qtauEd_SelectRect::qtauEd_SelectRect(qtauNoteEditor &ne, SNoteSetup &ns,
  436. qne::editorNotes &nts,
  437. qne::editorState &st)
  438. : qtauEdController(ne, ns, nts, st) {}
  439. qtauEd_SelectRect::qtauEd_SelectRect(qtauEdController *c)
  440. : qtauEdController(c) {}
  441. qtauEd_SelectRect::~qtauEd_SelectRect() {}
  442. void qtauEd_SelectRect::reset() {
  443. // rect resetting should just deselect all notes and remove rect itself
  444. unselectAll();
  445. _state->selectionRect.setRect(-1, -1, 0, 0);
  446. _pointedNote = 0;
  447. lazyUpdate();
  448. changeController(new qtauEdController(this));
  449. }
  450. void qtauEd_SelectRect::mouseMoveEvent(QMouseEvent *event) {
  451. QMap<quint64, bool> usedIDs;
  452. QVector<quint64> selected;
  453. int X = _setup->barScreenOffsets[_setup->numBars - 1];
  454. int Y = _setup->octHeight * _setup->numOctaves;
  455. QPoint pos = event->pos() + _state->viewport.topLeft();
  456. QPoint tl(qMax(qMin(pos.x(), _absFirstClickPos.x()), 0),
  457. qMax(qMin(pos.y(), _absFirstClickPos.y()), 0));
  458. QPoint br(qMin(qMax(pos.x(), _absFirstClickPos.x()), X),
  459. qMin(qMax(pos.y(), _absFirstClickPos.y()), Y));
  460. _state->selectionRect.setTopLeft(tl);
  461. _state->selectionRect.setBottomRight(br);
  462. foreach (const quint64 &id, _notes->selected) {
  463. usedIDs[id] = true;
  464. qne::editorNote &n = _notes->idMap[id];
  465. bool intersects = _state->selectionRect.intersects(n.r);
  466. if (intersects)
  467. selected.append(n.id);
  468. else
  469. n.selected = false;
  470. }
  471. int stBar = _setup->getBarForScreenOffset(tl.x());
  472. int endBar = _setup->getBarForScreenOffset(br.x());
  473. if (stBar == -1 || endBar == -1) return;
  474. // check all notes in bars from first that has left edge of sel rect to last
  475. // that has right edge
  476. if (stBar < _notes->grid.size()) {
  477. if (endBar >= _notes->grid.size()) endBar = _notes->grid.size() - 1;
  478. if (stBar == -1 || endBar == -1) return;
  479. for (int i = stBar; i <= endBar; ++i)
  480. for (int k = 0; k < _notes->grid[i].size(); ++k) {
  481. quint64 id = _notes->grid[i][k];
  482. if (!usedIDs.contains(_notes->grid[i][k])) {
  483. qne::editorNote &n = _notes->idMap[id];
  484. if (_state->selectionRect.intersects(n.r)) {
  485. selected.append(id);
  486. n.selected = true;
  487. }
  488. }
  489. }
  490. }
  491. _notes->selected = selected;
  492. lazyUpdate();
  493. }
  494. void qtauEd_SelectRect::mouseReleaseEvent(QMouseEvent *) {
  495. _state->selectionRect = QRect(-1, -1, 0, 0); // disable
  496. lazyUpdate();
  497. changeController(new qtauEdController(this));
  498. }
  499. //========================================================================
  500. qtauEd_DragNotes::qtauEd_DragNotes(qtauNoteEditor &ne, SNoteSetup &ns,
  501. qne::editorNotes &nts, qne::editorState &st)
  502. : qtauEdController(ne, ns, nts, st) {}
  503. qtauEd_DragNotes::qtauEd_DragNotes(qtauEdController *c) : qtauEdController(c) {}
  504. qtauEd_DragNotes::~qtauEd_DragNotes() {}
  505. void qtauEd_DragNotes::reset() {
  506. // drag reset should move all selected notes back to original position
  507. foreach (const quint64 id, _notes->selected) {
  508. qne::editorNote &n = _notes->idMap[id];
  509. removeFromGrid(n.r.left(), n.id);
  510. removeFromGrid(n.r.right(), n.id);
  511. n.r.moveTo(n.dragSt);
  512. addToGrid(n.r.left(), n.id);
  513. addToGrid(n.r.right(), n.id);
  514. }
  515. _pointedNote = 0;
  516. _state->snapLine = -1;
  517. lazyUpdate();
  518. changeController(new qtauEdController(this));
  519. }
  520. void qtauEd_DragNotes::init() {
  521. if (!_pointedNote) {
  522. DEVLOG_ERROR("Can't drag notes without note under cursor");
  523. changeController(new qtauEdController(this));
  524. return;
  525. }
  526. _mainMovedNote = _pointedNote;
  527. _selBounds = _mainMovedNote->r;
  528. _selRects.clear(); // just to be sure
  529. if (!_mainMovedNote->selected) {
  530. _mainMovedNote->selected = true;
  531. _notes->selected.append(_mainMovedNote->id);
  532. }
  533. foreach (const quint64 &id, _notes->selected) {
  534. qne::editorNote &n = _notes->idMap[id];
  535. n.dragSt = n.r.topLeft();
  536. _selRects.append(n.r);
  537. _selBounds.setLeft(qMin(_selBounds.left(), n.r.left()));
  538. _selBounds.setTop(qMin(_selBounds.top(), n.r.top()));
  539. _selBounds.setRight(qMax(_selBounds.right(), n.r.right()));
  540. _selBounds.setBottom(qMax(_selBounds.bottom(), n.r.bottom()));
  541. }
  542. }
  543. void qtauEd_DragNotes::mouseMoveEvent(QMouseEvent *event) {
  544. QPoint dynDelta =
  545. (event->pos() + _state->viewport.topLeft()) - _absFirstClickPos;
  546. QPoint desiredPos = _mainMovedNote->dragSt + dynDelta;
  547. int minOffset = (_setup->note.width() * 4) / _setup->quantize;
  548. int maxOffset = _setup->barScreenOffsets[_setup->numBars - 1];
  549. if (_state->gridSnapEnabled)
  550. desiredPos.setX(qMax(0, qMin(snap(desiredPos.x(), minOffset), maxOffset)));
  551. desiredPos.setY(qMax(
  552. 0, qMin(snap(desiredPos.y(), _setup->note.height()),
  553. _setup->octHeight *
  554. _setup->numOctaves))); // always snapping Y to pitch grid
  555. QPoint snappedDelta = desiredPos - _mainMovedNote->dragSt;
  556. // collision detection ---------------------------------------------------
  557. QRect workspaceZone(0, 0, maxOffset, _setup->numOctaves * _setup->octHeight);
  558. QRect newSelBounds(_selBounds);
  559. newSelBounds.moveTo(_selBounds.topLeft() + snappedDelta);
  560. bool noCollision = workspaceZone.contains(newSelBounds);
  561. if (noCollision) {
  562. QVector<QRect> newSelRects;
  563. for (int i = 0; i < _selRects.size(); ++i) {
  564. QRect r(_selRects[i]);
  565. r.moveTo(r.topLeft() + snappedDelta);
  566. newSelRects.append(r);
  567. }
  568. int firstBar = _setup->getBarForScreenOffset(newSelBounds.left());
  569. int lastBar = _setup->getBarForScreenOffset(newSelBounds.right());
  570. if (firstBar == -1 || lastBar == -1) return;
  571. if (firstBar < _notes->grid.size()) {
  572. if (lastBar >= _notes->grid.size()) lastBar = _notes->grid.size() - 1;
  573. for (int i = firstBar; i <= lastBar; ++i) {
  574. for (int k = 0; k < _notes->grid[i].size(); ++k) {
  575. const qne::editorNote &n = _notes->idMap[_notes->grid[i][k]];
  576. if (!n.selected &&
  577. n.r.intersects(
  578. newSelBounds)) // skip selected and those out of grouprect
  579. {
  580. for (int m = 0; m < newSelRects.size(); ++m)
  581. if (n.r.intersects(newSelRects[m])) {
  582. noCollision = false;
  583. break;
  584. }
  585. if (!noCollision) break;
  586. }
  587. }
  588. if (!noCollision) break;
  589. }
  590. }
  591. }
  592. //------------------------------------------------------------------------
  593. if (noCollision) {
  594. foreach (const quint64 &id, _notes->selected) {
  595. qne::editorNote &n = _notes->idMap[id];
  596. removeFromGrid(n.r.left(), n.id);
  597. removeFromGrid(n.r.right(), n.id);
  598. n.r.moveTo(n.dragSt + snappedDelta);
  599. updateModelData(n);
  600. addToGrid(n.r.left(), n.id);
  601. addToGrid(n.r.right(), n.id);
  602. }
  603. _state->snapLine = desiredPos.x();
  604. lazyUpdate();
  605. }
  606. }
  607. void qtauEd_DragNotes::mouseReleaseEvent(QMouseEvent *) {
  608. _state->snapLine = -1;
  609. QPoint pSt = _mainMovedNote->r.topLeft();
  610. QPoint pEnd = _mainMovedNote->dragSt;
  611. if (pSt.x() != pEnd.x() || pSt.y() != pEnd.y()) {
  612. // some movement was applied, so generate move event
  613. qtauEvent_NoteMove::noteMoveVector v;
  614. float pixelsToPulses = 480.0 / _setup->note.width();
  615. int totalKeys = (_setup->baseOctave + _setup->numOctaves - 1) * 12;
  616. foreach (const quint64 &id, _notes->selected) {
  617. qne::editorNote &n = _notes->idMap[id];
  618. int pxDelta = n.r.x() - n.dragSt.x();
  619. float rounder = (pxDelta < 0) ? -0.001 : 0.001;
  620. qtauEvent_NoteMove::noteMoveData d;
  621. d.id = n.id;
  622. d.pulseOffDelta = (float)pxDelta * pixelsToPulses + rounder;
  623. d.keyNumber = n.keyNumber;
  624. d.prevKeyNumber = (totalKeys - n.dragSt.y() / _setup->note.height()) - 1;
  625. v.append(d);
  626. }
  627. qtauEvent_NoteMove *event = new qtauEvent_NoteMove(v);
  628. eventHappened(event);
  629. }
  630. lazyUpdate(); // need to remove that snap line
  631. changeController(new qtauEdController(this));
  632. }
  633. //========================================================================
  634. qtauEd_ResizeNote::qtauEd_ResizeNote(qtauNoteEditor &ne, SNoteSetup &ns,
  635. qne::editorNotes &nts,
  636. qne::editorState &st, bool left)
  637. : qtauEdController(ne, ns, nts, st), _toLeft(left) {}
  638. qtauEd_ResizeNote::qtauEd_ResizeNote(qtauEdController *c, bool left)
  639. : qtauEdController(c), _toLeft(left) {}
  640. qtauEd_ResizeNote::~qtauEd_ResizeNote() {}
  641. void qtauEd_ResizeNote::reset() {
  642. // reset() should just revert resized note to its original size, since resize
  643. // event may mess with
  644. // undo/redo stack if reset is called because of event
  645. _pointedNote->r = _originalRect;
  646. _pointedNote = 0;
  647. _state->snapLine = -1;
  648. lazyUpdate();
  649. changeController(new qtauEdController(this));
  650. }
  651. void qtauEd_ResizeNote::init() {
  652. if (!_pointedNote) {
  653. DEVLOG_ERROR("Note resize controller is created without note to resize");
  654. changeController(new qtauEdController(this));
  655. return;
  656. }
  657. // min note width is 1/64 -> 1 px at max zoom, when 1/4 note width == 16 px
  658. _minNoteWidth = (_setup->note.width() * 4) / _setup->length;
  659. _editedNote = _pointedNote;
  660. _originalRect = _pointedNote->r;
  661. }
  662. void qtauEd_ResizeNote::mouseMoveEvent(QMouseEvent *event) {
  663. float pixelsToPulses = 480.0 / (float)_setup->note.width();
  664. int maxOffset = _setup->barScreenOffsets[_setup->numBars - 1];
  665. QRect newNoteRect(_editedNote->r);
  666. int cursorHPos = event->pos().x() + _state->viewport.x();
  667. if (_state->gridSnapEnabled)
  668. cursorHPos =
  669. snap(cursorHPos, _minNoteWidth,
  670. (_toLeft ? _editedNote->r.right() + 1 : _editedNote->r.left()));
  671. if (_toLeft)
  672. // calc new left coord, with magical +1's, because no code can work properly
  673. // without a bit of magic
  674. newNoteRect.setLeft(
  675. qMax(0, qMin(cursorHPos, _editedNote->r.right() - _minNoteWidth + 1)));
  676. else
  677. newNoteRect.setRight(
  678. qMin(maxOffset,
  679. qMax(cursorHPos - 1, _editedNote->r.left() + _minNoteWidth - 1)));
  680. int barSt = _setup->getBarForScreenOffset(newNoteRect.left());
  681. int barEnd = _setup->getBarForScreenOffset(newNoteRect.right());
  682. if (barSt == -1 || barEnd == -1) return;
  683. bool noCollision = true;
  684. if (barSt < _notes->grid.size()) {
  685. if (barEnd >= _notes->grid.size()) barEnd = _notes->grid.size() - 1;
  686. int i = barSt;
  687. while (i <= barEnd && noCollision) {
  688. for (int k = 0; k < _notes->grid[i].size(); ++k) {
  689. qne::editorNote &n = _notes->idMap[_notes->grid[i][k]];
  690. if (n.id != _editedNote->id && n.r.intersects(newNoteRect)) {
  691. noCollision = false;
  692. break;
  693. }
  694. }
  695. ++i;
  696. }
  697. } else
  698. _notes->grid.resize(barEnd +
  699. 10); // that shouldn't really happen, but whatever
  700. if (noCollision && (_editedNote->r.x() != newNoteRect.x() ||
  701. _editedNote->r.width() != newNoteRect.width())) {
  702. removeFromGrid(_editedNote->r.left(), _editedNote->id);
  703. removeFromGrid(_editedNote->r.right(), _editedNote->id);
  704. if (_toLeft) {
  705. _state->snapLine = newNoteRect.left();
  706. _editedNote->r.setLeft(_state->snapLine);
  707. _editedNote->pulseOffset = _editedNote->r.x() * pixelsToPulses + 0.001;
  708. } else {
  709. _state->snapLine = newNoteRect.right() + 1;
  710. _editedNote->r.setRight(newNoteRect.right());
  711. }
  712. addToGrid(_editedNote->r.left(), _editedNote->id);
  713. addToGrid(_editedNote->r.right(), _editedNote->id);
  714. _editedNote->pulseLength = _editedNote->r.width() * pixelsToPulses + 0.001;
  715. lazyUpdate();
  716. }
  717. }
  718. void qtauEd_ResizeNote::mouseReleaseEvent(QMouseEvent *) {
  719. _state->snapLine = -1;
  720. _owner->setCursor(Qt::ArrowCursor);
  721. float pixelsToPulses = 480.0 / _setup->note.width();
  722. if (_editedNote->r.width() != _originalRect.width()) {
  723. qtauEvent_NoteResize::noteResizeData d;
  724. d.id = _editedNote->id;
  725. d.offset = _editedNote->r.x() * pixelsToPulses + 0.001;
  726. d.length = _editedNote->r.width() * pixelsToPulses + 0.001;
  727. d.prevOffset = _originalRect.x() * pixelsToPulses + 0.001;
  728. d.prevLength = _originalRect.width() * pixelsToPulses + 0.001;
  729. qtauEvent_NoteResize::noteResizeVector v;
  730. v.append(d);
  731. qtauEvent_NoteResize *noteResize = new qtauEvent_NoteResize(v);
  732. eventHappened(noteResize);
  733. }
  734. lazyUpdate();
  735. changeController(new qtauEdController(this));
  736. }
  737. //========================================================================
  738. qtauEd_AddNote::qtauEd_AddNote(qtauNoteEditor &ne, SNoteSetup &ns,
  739. qne::editorNotes &nts, qne::editorState &st)
  740. : qtauEdController(ne, ns, nts, st) {}
  741. qtauEd_AddNote::qtauEd_AddNote(qtauEdController *c) : qtauEdController(c) {}
  742. qtauEd_AddNote::~qtauEd_AddNote() {}
  743. void qtauEd_AddNote::reset() {
  744. // resetting adding should remove still dragged new note completely, since its
  745. // add event on mouseUp
  746. // may mess with undo/redo stack if reason for reset is new event
  747. removeFromGrid(_editedNote->r.left(), _editedNote->id);
  748. removeFromGrid(_editedNote->r.right(), _editedNote->id);
  749. if (_editedNote->selected) {
  750. int i = _notes->selected.indexOf(_editedNote->id);
  751. if (i >= 0) _notes->selected.remove(i);
  752. }
  753. _notes->idMap.remove(_editedNote->id);
  754. _pointedNote = 0;
  755. _state->snapLine = -1;
  756. lazyUpdate();
  757. changeController(new qtauEdController(this));
  758. }
  759. // won't overload resizenote's init() because using virtual funcs from
  760. // constructors in C++ is a bad idea
  761. void qtauEd_AddNote::init() {
  762. _minOffset = (_setup->note.width() * 4) /
  763. _setup->quantize; // using quantizing to place note
  764. int hUnits = _absFirstClickPos.y() / _setup->note.height();
  765. int vOff = hUnits * _setup->note.height(); // floor to prev note offset
  766. int units = _absFirstClickPos.x() / _minOffset;
  767. float percent =
  768. (float)(_absFirstClickPos.x() % _minOffset) / (float)_minOffset;
  769. int hOff = (percent >= 0.5) ? (units + 1) * _minOffset : units * _minOffset;
  770. _minOffset =
  771. (_setup->note.width() * 4) /
  772. _setup->length; // using musical note length (1/64..1/4) for size
  773. qne::editorNote n;
  774. n.r.setRect(hOff, vOff, _minOffset, _setup->note.height());
  775. n.id = ++_idOffset;
  776. n.txt = "a";
  777. updateModelData(n);
  778. addToGrid(n.r.left(), n.id);
  779. _notes->idMap[n.id] = n;
  780. _editedNote = &_notes->idMap[n.id];
  781. _pointedNote = _editedNote;
  782. }
  783. void qtauEd_AddNote::mouseMoveEvent(QMouseEvent *event) {
  784. int cursorHPos = event->pos().x() + _state->viewport.x();
  785. if (_state->gridSnapEnabled)
  786. cursorHPos = snap(cursorHPos, _minOffset, _editedNote->r.x());
  787. int lastBar = _setup->barScreenOffsets[_setup->numBars - 1];
  788. int desiredRight = qMin(
  789. lastBar, qMax(cursorHPos - 1, _editedNote->r.left() + _minOffset - 1));
  790. QRect newNoteRect(_editedNote->r);
  791. newNoteRect.setRight(desiredRight);
  792. int barSt = _setup->getBarForScreenOffset(newNoteRect.left());
  793. int barEnd = _setup->getBarForScreenOffset(newNoteRect.right());
  794. if (barSt == -1 || barEnd == -1) return;
  795. bool noCollision = true;
  796. if (barSt < _notes->grid.size()) {
  797. if (barEnd >= _notes->grid.size()) barEnd = _notes->grid.size() - 1;
  798. int i = barSt;
  799. while (i <= barEnd && noCollision) {
  800. for (int k = 0; k < _notes->grid[i].size(); ++k) {
  801. qne::editorNote &n = _notes->idMap[_notes->grid[i][k]];
  802. if (n.id != _editedNote->id && n.r.intersects(newNoteRect)) {
  803. noCollision = false;
  804. break;
  805. }
  806. }
  807. ++i;
  808. }
  809. } else
  810. _notes->grid.resize(barEnd +
  811. 10); // that shouldn't really happen, but whatever
  812. bool snap = (_editedNote->r.width() != newNoteRect.width());
  813. if (noCollision && snap) {
  814. // remove right coord from grid
  815. removeFromGrid(_editedNote->r.left(), _editedNote->id);
  816. removeFromGrid(_editedNote->r.right(), _editedNote->id);
  817. // calc new right coord
  818. _state->snapLine = cursorHPos;
  819. _editedNote->r.setRight(cursorHPos - 1);
  820. updateModelData(*_editedNote);
  821. addToGrid(_editedNote->r.left(), _editedNote->id);
  822. addToGrid(_editedNote->r.right(), _editedNote->id);
  823. lazyUpdate();
  824. }
  825. }
  826. void qtauEd_AddNote::mouseReleaseEvent(QMouseEvent *) {
  827. _state->snapLine = -1;
  828. qtauEvent_NoteAddition::noteAddData d;
  829. d.id = _editedNote->id;
  830. d.lyrics = _editedNote->txt;
  831. d.pulseOffset = _editedNote->pulseOffset;
  832. d.pulseLength = _editedNote->pulseLength;
  833. d.keyNumber = _editedNote->keyNumber;
  834. qtauEvent_NoteAddition::noteAddVector v;
  835. v.append(d);
  836. qtauEvent_NoteAddition *noteAdd = new qtauEvent_NoteAddition(v);
  837. eventHappened(noteAdd);
  838. lazyUpdate();
  839. changeController(new qtauEdController(this));
  840. }