1 //=========================================================
2 // MusE
3 // Linux Music Editor
4 // $Id: dcanvas.cpp,v 1.16.2.10 2009/10/15 22:45:50 terminator356 Exp $
5 // (C) Copyright 1999 Werner Schweer (ws@seh.de)
6 // (C) Copyright 2016 Tim E. Real (terminator356 on users dot sourceforge dot net)
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License
10 // as published by the Free Software Foundation; version 2 of
11 // the License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21 //
22 //=========================================================
23
24 #include <QPainter>
25 #include <QApplication>
26 #include <QMessageBox>
27 #include <QClipboard>
28 #include <QDrag>
29 #include <QDragLeaveEvent>
30 #include <QPolygon>
31 #include <QDragEnterEvent>
32 #include <QDragMoveEvent>
33 #include <QDropEvent>
34 #include <QResizeEvent>
35 #include <QList>
36 #include <QPair>
37 #include <QToolTip>
38
39 #include <stdio.h>
40 #include <limits.h>
41 #include <errno.h>
42
43 #include "dcanvas.h"
44 #include "midieditor.h"
45 #include "drumedit.h"
46 #include "drummap.h"
47 #include "drum_ordering.h"
48 #include "event.h"
49 #include "mpevent.h"
50 #include "xml.h"
51 #include "globals.h"
52 #include "midiport.h"
53 #include "audio.h"
54 #include "midi_consts.h"
55 #include "shortcuts.h"
56 #include "icons.h"
57 #include "functions.h"
58 #include "helper.h"
59 #include "operations.h"
60 #include "gconfig.h"
61 #include "app.h"
62
63 #define CARET 10
64 #define CARET2 5
65
66 using MusEGlobal::debugMsg;
67 using MusEGlobal::heavyDebugMsg;
68 using MusECore::Track;
69 using MusECore::MidiTrack;
70
71 namespace MusEGui {
72
73 //---------------------------------------------------------
74 // DEvent
75 //---------------------------------------------------------
76
DEvent(MusECore::Event e,MusECore::Part * p,int instr)77 DEvent::DEvent(MusECore::Event e, MusECore::Part* p, int instr)
78 : EItem(e, p)
79 {
80 int y = instr * TH + TH/2;
81 int tick = e.tick() + p->tick();
82 setPos(QPoint(tick, y));
83 setBBox(QRect(-CARET2, -CARET2, CARET, CARET));
84 // Give the moving point an initial value.
85 setMp(pos());
86 }
87
88 //---------------------------------------------------------
89 // addItem
90 //---------------------------------------------------------
91
addItem(MusECore::Part * part,const MusECore::Event & event)92 CItem* DrumCanvas::addItem(MusECore::Part* part, const MusECore::Event& event)
93 {
94 if (signed(event.tick())<0) {
95 printf("ERROR: trying to add event before current part!\n");
96 return nullptr;
97 }
98
99 int instr=pitch_and_track_to_instrument(event.pitch(), part->track());
100 if (instr<0)
101 {
102 if (heavyDebugMsg) printf("trying to add event which is hidden or not in any part known to me\n");
103 return nullptr;
104 }
105
106 DEvent* ev = new DEvent(event, part, instr);
107 items.add(ev);
108
109 return ev;
110 }
111
112 //---------------------------------------------------------
113 // DrumCanvas
114 //---------------------------------------------------------
115
DrumCanvas(MidiEditor * pr,QWidget * parent,int sx,int sy,const char * name)116 DrumCanvas::DrumCanvas(MidiEditor* pr, QWidget* parent, int sx,
117 int sy, const char* name)
118 : EventCanvas(pr, parent, sx, sy, name)
119 {
120 setObjectName("DrumCanvas");
121
122 drumEditor=static_cast<DrumEdit*>(pr);
123
124 setStatusTip(tr("Drum canvas: Use Pencil tool to create and edit events, Pointer tool to select, Cursor tool for special keyboard entry mode (arrow keys, V, B, N, M, Del). Press F1 for help."));
125
126 ourDrumMap=nullptr;
127 rebuildOurDrumMap();
128
129 setVirt(false);
130 cursorPos= QPoint(0,0);
131 _stepSize=1;
132
133 steprec=new MusECore::StepRec(nullptr);
134
135 songChanged(SC_TRACK_INSERTED);
136 connect(MusEGlobal::song, SIGNAL(midiNote(int, int)), SLOT(midiNote(int,int)));
137 }
138
~DrumCanvas()139 DrumCanvas::~DrumCanvas()
140 {
141 if (must_delete_our_drum_map && ourDrumMap!=nullptr)
142 delete [] ourDrumMap;
143
144 delete steprec;
145 }
146
147 //---------------------------------------------------------
148 // index2Note
149 // Return false if invalid index
150 //---------------------------------------------------------
151
index2Note(int index,int * port,int * channel,int * note)152 bool DrumCanvas::index2Note(int index, int* port, int* channel, int* note)
153 {
154 if ((index<0) || (index>=getOurDrumMapSize()))
155 return false;
156
157 int mport, ch;
158 // Default to track port if -1 and track channel if -1.
159 MusECore::Track* track = nullptr;
160 MusECore::MidiTrack* mt = nullptr;
161 if(ourDrumMap[index].port == -1)
162 {
163 track = *instrument_map[index].tracks.begin();
164 if(!track->isMidiTrack())
165 return false;
166 mt = static_cast<MusECore::MidiTrack*>(track);
167 mport = mt->outPort();
168 }
169 else
170 mport = ourDrumMap[index].port;
171
172 if(ourDrumMap[index].channel == -1)
173 {
174 if(!track)
175 {
176 track = *instrument_map[index].tracks.begin();
177 if(!track->isMidiTrack())
178 return false;
179 mt = static_cast<MusECore::MidiTrack*>(track);
180 }
181 ch = mt->outChannel();
182 }
183 else
184 ch = ourDrumMap[index].channel;
185
186 if(port)
187 *port = mport;
188 if(channel)
189 *channel = ch;
190 if(note)
191 //*note = old_style_drummap_mode ? ourDrumMap[index].anote : instrument_map[index].pitch;
192 *note = ourDrumMap[index].anote;
193
194 return true;
195 }
196
197 //---------------------------------------------------------
198 // moveCanvasItems
199 //---------------------------------------------------------
200
moveCanvasItems(CItemMap & items,int dp,int dx,DragType dtype,bool rasterize)201 MusECore::Undo DrumCanvas::moveCanvasItems(CItemMap& items, int dp, int dx, DragType dtype, bool rasterize)
202 {
203
204 if(editor->parts()->empty())
205 return MusECore::Undo(); //return empty list
206
207 MusECore::PartsToChangeMap parts2change;
208 MusECore::Undo operations;
209
210 for(MusECore::iPart ip = editor->parts()->begin(); ip != editor->parts()->end(); ++ip)
211 {
212 MusECore::Part* part = ip->second;
213 if(!part)
214 continue;
215
216 int npartoffset = 0;
217 for(iCItem ici = items.begin(); ici != items.end(); ++ici)
218 {
219 CItem* ci = ici->second;
220 ci->setMoving(false);
221
222 if(ci->part() != part)
223 continue;
224
225 int x = ci->pos().x() + dx;
226 int y = pitch2y(y2pitch(ci->pos().y()) + dp);
227 QPoint newpos = QPoint(x, y);
228 if(rasterize)
229 newpos = raster(newpos);
230
231 // Test moving the item...
232 DEvent* nevent = (DEvent*) ci;
233 MusECore::Event event = nevent->event();
234 x = newpos.x();
235 if(x < 0)
236 x = 0;
237 int ntick = (rasterize ? editor->rasterVal(x) : x) - part->tick();
238 if(ntick < 0)
239 ntick = 0;
240 int diff = ntick + event.lenTick() - part->lenTick();
241
242 // If moving the item would require a new part size...
243 if(diff > npartoffset)
244 npartoffset = diff;
245 }
246
247 if(npartoffset > 0)
248 {
249 MusECore::iPartToChange ip2c = parts2change.find(part);
250 if(ip2c == parts2change.end())
251 {
252 MusECore::PartToChange p2c = {nullptr, npartoffset};
253 parts2change.insert(std::pair<MusECore::Part*, MusECore::PartToChange> (part, p2c));
254 }
255 else
256 ip2c->second.xdiff = npartoffset;
257 }
258 }
259
260 bool forbidden=false;
261 for(MusECore::iPartToChange ip2c = parts2change.begin(); ip2c != parts2change.end(); ++ip2c)
262 {
263 MusECore::Part* opart = ip2c->first;
264 if (opart->hasHiddenEvents() & MusECore::Part::RightEventsHidden)
265 {
266 forbidden=true;
267 break;
268 }
269 }
270
271
272 if (!forbidden)
273 {
274 std::vector< CItem* > doneList;
275 typedef std::vector< CItem* >::iterator iDoneList;
276
277 for(iCItem ici = items.begin(); ici != items.end(); ++ici)
278 {
279 CItem* ci = ici->second;
280
281 const QPoint oldpos(ci->pos());
282 int x = ci->pos().x();
283 int y = ci->pos().y();
284 int nx = x + dx;
285 int ny = pitch2y(y2pitch(y) + dp);
286 QPoint newpos = QPoint(nx, ny);
287 if(rasterize)
288 newpos = raster(newpos);
289 selectItem(ci, true);
290
291 iDoneList idl;
292 for(idl = doneList.begin(); idl != doneList.end(); ++idl)
293 // This compares EventBase pointers to see if they're the same...
294 if((*idl)->event() == ci->event())
295 break;
296
297 // Do not process if the event has already been processed (meaning it's an event in a clone part)...
298 if (idl == doneList.end())
299 {
300 if (moveItem(operations, ci, newpos, dtype, rasterize) == false) //error?
301 {
302 QMessageBox::warning(this, tr("Moving items failed"), tr("The selection couldn't be moved, because at least one note would be moved into a track which is different from both the original track and the current part's track.\nChanging the current part with ALT+LEFT/RIGHT may help."));
303 return MusECore::Undo(); //return empty list
304 }
305 doneList.push_back(ci);
306 }
307 ci->move(newpos);
308
309 itemReleased(ci, oldpos);
310
311 if(dtype == MOVE_COPY || dtype == MOVE_CLONE)
312 selectItem(ci, false);
313 }
314
315 itemsReleased();
316
317 for(MusECore::iPartToChange ip2c = parts2change.begin(); ip2c != parts2change.end(); ++ip2c)
318 {
319 MusECore::Part* opart = ip2c->first;
320 int diff = ip2c->second.xdiff;
321
322 schedule_resize_all_same_len_clone_parts(opart, opart->lenTick() + diff, operations);
323 }
324
325 return operations;
326 }
327 else
328 {
329 return MusECore::Undo(); //return empty list
330 }
331 }
332
333 //---------------------------------------------------------
334 // moveItem
335 //---------------------------------------------------------
336
moveItem(MusECore::Undo & operations,CItem * item,const QPoint & pos,DragType dtype,bool rasterize)337 bool DrumCanvas::moveItem(MusECore::Undo& operations, CItem* item, const QPoint& pos, DragType dtype, bool rasterize)
338 {
339 DEvent* nevent = (DEvent*) item;
340
341 MusECore::MidiPart* part = (MusECore::MidiPart*)nevent->part();
342 MusECore::MidiPart* dest_part = part;
343
344 int instrument = y2pitch(pos.y());
345 if (instrument<0) instrument=0;
346 if (instrument>=getOurDrumMapSize()) instrument=getOurDrumMapSize()-1;
347 MusECore::Event event = nevent->event();
348 if (!instrument_map[instrument].tracks.contains(dest_part->track()))
349 {
350 if (debugMsg)
351 printf("trying to move an event into a different track. checking if curPart is set correctly...\n");
352
353 if (!instrument_map[instrument].tracks.contains(curPart->track()))
354 {
355 printf ("ERROR: tried to move an event into a track which is different from both the initial part's and the curPart's track! ignoring this one...\n");
356 return false;
357 }
358 else
359 dest_part=(MusECore::MidiPart*)curPart;
360 }
361
362 int x = pos.x();
363 if (x < 0)
364 x = 0;
365 int ntick = (rasterize ? editor->rasterVal(x) : x) - dest_part->tick();
366 if (ntick < 0)
367 ntick = 0;
368
369 event.setSelected(false);
370 MusECore::Event newEvent = event.clone();
371 newEvent.setSelected(true);
372
373 int ev_pitch = instrument_map[instrument].pitch;
374 newEvent.setPitch(ev_pitch);
375 newEvent.setTick(ntick);
376
377 // don't check, whether the new event is within the part
378 // at this place. with operation groups, the part isn't
379 // resized yet. (flo93)
380
381 if (dtype == MOVE_COPY || dtype == MOVE_CLONE)
382 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::AddEvent, newEvent, dest_part, false, false));
383 else
384 {
385 if (dest_part == part)
386 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::ModifyEvent, newEvent, event, part, false, false));
387 else
388 {
389 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::DeleteEvent, event, part, false, false));
390 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::AddEvent, newEvent, dest_part, false, false));
391 }
392 }
393 return true;
394 }
395
396 //---------------------------------------------------------
397 // newItem
398 //---------------------------------------------------------
399
newItem(const QPoint & p,int state)400 CItem* DrumCanvas::newItem(const QPoint& p, int state)
401 {
402 int instr = y2pitch(p.y());
403 if ((instr<0) || (instr>=getOurDrumMapSize()))
404 return nullptr;
405
406 int k4 = (Qt::MetaModifier | Qt::AltModifier);
407 //int nk4 = Qt::ControlModifier;
408
409 int k2 = Qt::MetaModifier;
410 int nk2 = (Qt::ControlModifier | Qt::AltModifier);
411
412 int k1 = (Qt::ControlModifier | Qt::MetaModifier);
413 int nk1 = Qt::AltModifier;
414
415 int velo = ourDrumMap[instr].lv3;
416 if ((state & k4) == k4) // && !(state & nk4))
417 velo = ourDrumMap[instr].lv4;
418 else if ((state & k2) == k2 && !(state & nk2))
419 velo = ourDrumMap[instr].lv2;
420 else if ((state & k1) == k1 && !(state & nk1))
421 velo = ourDrumMap[instr].lv1;
422 int tick = p.x();
423 if(tick < 0)
424 tick = 0;
425 if(!(state & Qt::ShiftModifier))
426 tick = editor->rasterVal(tick);
427 return newItem(tick, instr, velo);
428 }
429
430 //---------------------------------------------------------
431 // newItem
432 //---------------------------------------------------------
433
newItem(int tick,int instrument,int velocity)434 CItem* DrumCanvas::newItem(int tick, int instrument, int velocity)
435 {
436 if ((instrument<0) || (instrument>=getOurDrumMapSize()))
437 return nullptr;
438
439 if (!instrument_map[instrument].tracks.contains(curPart->track()))
440 {
441 if (debugMsg)
442 printf("tried to create a new Item which cannot be inside the current track. looking for destination part...\n");
443
444 QSet<MusECore::Part*> parts = parts_at_tick(tick, instrument_map[instrument].tracks);
445
446 if (parts.count() != 1)
447 {
448 QMessageBox::warning(this, tr("Creating event failed"), tr("Couldn't create the event, because the currently selected part isn't the same track, and the selected instrument could be either on no or on multiple parts, which is ambiguous.\nSelect the destination part, then try again."));
449 return nullptr;
450 }
451 else
452 {
453 setCurrentPart(*parts.begin());
454 }
455 }
456 // else or if we found an alternative part (which has now been set as curPart)
457
458 tick -= curPart->tick();
459 if (tick < 0)
460 return nullptr;
461 MusECore::Event e(MusECore::Note);
462 e.setTick(tick);
463 e.setPitch(instrument_map[instrument].pitch);
464 e.setVelo(velocity);
465 e.setLenTick(ourDrumMap[instrument].len);
466 if(_playEvents)
467 {
468 int pitch, port, channel;
469 if(index2Note(instrument, &port, &channel, &pitch))
470 startPlayEvent(pitch, e.velo(), port, channel);
471 }
472 return new DEvent(e, curPart, instrument);
473 }
474
475 //---------------------------------------------------------
476 // newItem
477 //---------------------------------------------------------
newItem(CItem * item,bool noSnap)478 void DrumCanvas::newItem(CItem* item, bool noSnap) {
479 newItem(item, noSnap,true);
480 }
481
newItem(CItem * item,bool noSnap,bool replace)482 void DrumCanvas::newItem(CItem* item, bool noSnap, bool replace)
483 {
484 if(!item)
485 {
486 printf("THIS SHOULD NEVER HAPPEN: DrumCanvas::newItem called with nullptr item!\n");
487 return;
488 }
489
490 DEvent* nevent = (DEvent*) item;
491 MusECore::Event event = nevent->event();
492 MusECore::Part* part = nevent->part();
493 int ptick = part->tick();
494 int x = item->x();
495 if (x<ptick)
496 x=ptick;
497 if (!noSnap)
498 x = editor->rasterVal(x);
499 if (x<ptick)
500 x=ptick;
501 event.setTick(x - ptick);
502 int npitch = y2pitch(item->y());
503 if ((npitch<0) || (npitch>=getOurDrumMapSize()))
504 return;
505 npitch = instrument_map[npitch].pitch;
506 event.setPitch(npitch);
507 event.setSelected(true);
508 // check for existing event
509 // if found change command semantic from insert to delete
510 MusECore::Undo operations;
511 std::pair<MusECore::ciEvent,MusECore::ciEvent> range =
512 part->events().equal_range(event.type() == MusECore::Wave ? event.frame() : event.tick());
513 MusECore::Event oev;
514 bool found = false;
515 for(MusECore::ciEvent i = range.first; i != range.second; ++i)
516 {
517 oev = i->second;
518 if(!oev.isNote())
519 continue;
520 if(oev.pitch() == npitch)
521 {
522 found = true;
523 break;
524 }
525 }
526
527 // Indicate do undo, and do not do port controller values and clone parts.
528 int diff = event.endTick()-part->lenTick();
529 if (! ((diff > 0) && (part->hasHiddenEvents() & MusECore::Part::RightEventsHidden)) ) //operation is allowed
530 {
531 if(found)
532 {
533 if(replace)
534 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::ModifyEvent,event, oev, part, false, false));
535 else
536 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::DeleteEvent,oev, part, false, false));
537 }
538 else
539 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::AddEvent,event, part, false, false));
540
541 if (diff > 0) // part must be extended?
542 {
543 schedule_resize_all_same_len_clone_parts(part, event.endTick(), operations);
544 printf("newItem: extending\n");
545 }
546 }
547 else // forbid action by not applying it
548 {
549 if(found)
550 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::DeleteEvent,oev, part, false, false));
551 }
552
553 if(!operations.empty())
554 MusEGlobal::song->applyOperationGroup(operations);
555 else
556 songChanged(SC_EVENT_INSERTED); //this forces an update of the itemlist, which is necessary
557 //to remove "forbidden" events from the list again
558 }
559
560 //---------------------------------------------------------
561 // deleteItem
562 //---------------------------------------------------------
563
deleteItem(CItem * item)564 bool DrumCanvas::deleteItem(CItem* item)
565 {
566 MusECore::Event ev = ((DEvent*)item)->event();
567 // Indicate do undo, and do not do port controller values and clone parts.
568 MusEGlobal::song->applyOperation(MusECore::UndoOp(MusECore::UndoOp::DeleteEvent,
569 ev, ((DEvent*)item)->part(), false, false));
570 return false;
571 }
572
573 //---------------------------------------------------------
574 // itemPressed
575 //---------------------------------------------------------
576
itemPressed(const CItem * item)577 void DrumCanvas::itemPressed(const CItem* item)
578 {
579 if (!_playEvents)
580 return;
581 MusECore::Event e = ((DEvent*)item)->event();
582 int index = e.pitch();
583 // play note:
584
585 for (int i = 0; i < instrument_map.size(); ++i) {
586 if (instrument_map.at(i).pitch == index) {
587 index = i;
588 break;
589 }
590 }
591 int pitch, port, channel;
592 if(index2Note(index, &port, &channel, &pitch))
593 startPlayEvent(pitch, e.velo(), port, channel);
594 }
595
596 //---------------------------------------------------------
597 // itemReleased
598 //---------------------------------------------------------
599
itemReleased(const CItem * item,const QPoint &)600 void DrumCanvas::itemReleased(const CItem* item, const QPoint&)
601 {
602 const int oindex = y2pitch(item->mp().y());
603 // for (int i = 0; i < instrument_map.size(); ++i) {
604 // if (instrument_map.at(i).pitch == index) {
605 // index = i;
606 // break;
607 // }
608 // }
609
610 int opitch, oport, ochannel;
611 if(!index2Note(oindex, &oport, &ochannel, &opitch))
612 {
613 // Stop any playing notes:
614 stopPlayEvents();
615 return;
616 }
617
618 // stop note:
619 stopStuckNote(oport, ochannel, opitch);
620 }
621
622
623 //---------------------------------------------------------
624 // itemMoving
625 //---------------------------------------------------------
626
itemMoving(const CItem * item,const QPoint & newMP)627 void DrumCanvas::itemMoving(const CItem* item, const QPoint& newMP)
628 {
629 const int oindex = y2pitch(item->mp().y());
630 const int index = y2pitch(newMP.y());
631 int opitch, oport, ochannel, pitch, port, channel;
632 if(!index2Note(oindex, &oport, &ochannel, &opitch))
633 {
634 // Stop any playing notes:
635 stopPlayEvents();
636 return;
637 }
638 if(!index2Note(index, &port, &channel, &pitch))
639 {
640 // Stop any playing notes:
641 stopPlayEvents();
642 return;
643 }
644
645 if(port == oport && channel == ochannel && pitch == opitch)
646 return;
647
648 // Stop any playing note:
649 stopStuckNote(port, channel, opitch);
650 }
651
652 //---------------------------------------------------------
653 // itemMoved
654 //---------------------------------------------------------
655
itemMoved(const CItem * item,const QPoint & oldMP)656 void DrumCanvas::itemMoved(const CItem* item, const QPoint& oldMP)
657 {
658 const int oindex = y2pitch(oldMP.y());
659 const int index = y2pitch(item->mp().y());
660 int opitch, oport, ochannel, pitch, port, channel;
661 if(!index2Note(oindex, &oport, &ochannel, &opitch))
662 {
663 // Stop any playing notes:
664 stopPlayEvents();
665 return;
666 }
667 if(!index2Note(index, &port, &channel, &pitch))
668 {
669 // Stop any playing notes:
670 stopPlayEvents();
671 return;
672 }
673
674 if(port == oport && channel == ochannel && pitch == opitch)
675 return;
676
677 if(_playEvents)
678 {
679 // Unlike with PianoRoll::itemMoved(), don't play multiple notes (no concept of chords).
680 if(item == curItem)
681 {
682 const MusECore::Event e = ((DEvent*)item)->event();
683 // play note:
684 startPlayEvent(pitch, e.velo(), port, channel);
685 }
686 }
687 }
688
689 //---------------------------------------------------------
690 // drawItem
691 //---------------------------------------------------------
692
drawItem(QPainter & p,const CItem * item,const QRect & mr,const QRegion &)693 void DrumCanvas::drawItem(QPainter&p, const CItem*item, const QRect& mr, const QRegion&)
694 {
695 DEvent* e = (DEvent*) item;
696 int mx = 0, my = 0;
697 mx = mapx(item->pos().x());
698 my = mapy(item->pos().y());
699
700 QPolygon pa(4);
701 pa.setPoint(0, mx - CARET2, my);
702 pa.setPoint(1, mx, my - CARET2);
703 pa.setPoint(2, mx + CARET2, my);
704 pa.setPoint(3, mx, my + CARET2);
705 QRect r(pa.boundingRect());
706 r = r.intersected(mr);
707 if(!r.isValid())
708 return;
709
710 QPen pen;
711 pen.setCosmetic(true);
712 pen.setColor(Qt::black);
713 p.setPen(pen);
714
715 if (e->part() != curPart)
716 {
717 if(item->isMoving())
718 p.setBrush(Qt::gray);
719 else if(item->isSelected())
720 p.setBrush(Qt::black);
721 else
722 p.setBrush(Qt::lightGray);
723 }
724 else if (item->isMoving()) {
725 p.setBrush(Qt::gray);
726 }
727 else if (item->isSelected())
728 {
729 p.setBrush(MusEGlobal::config.midiItemSelectedColor);
730 // p.setBrush(Qt::black);
731 }
732 else
733 {
734 int velo = e->event().velo();
735 MusECore::DrumMap* dm = &ourDrumMap[y2pitch(my)]; //Get the drum item
736 QColor color;
737 if (velo < dm->lv1)
738 color.setRgb(240, 240, 255);
739 else if (velo < dm->lv2)
740 color.setRgb(200, 200, 255);
741 else if (velo < dm->lv3)
742 color.setRgb(170, 170, 255);
743 else
744 color = MusEGlobal::config.midiItemColor;
745 // color.setRgb(0, 0, 255);
746 p.setBrush(color);
747 }
748
749 p.drawPolygon(pa);
750 }
751
752 //---------------------------------------------------------
753 // drawMoving
754 // draws moving items
755 //---------------------------------------------------------
756
drawMoving(QPainter & p,const CItem * item,const QRect & rect,const QRegion &)757 void DrumCanvas::drawMoving(QPainter& p, const CItem* item, const QRect& rect, const QRegion&)
758 {
759 QPolygon pa(4);
760 QPoint pt = map(item->mp());
761 int x = pt.x();
762 int y = pt.y();
763 pa.setPoint(0, x-CARET2, y + TH/2);
764 pa.setPoint(1, x, y + TH/2+CARET2);
765 pa.setPoint(2, x+CARET2, y + TH/2);
766 pa.setPoint(3, x, y + (TH-CARET)/2);
767 QRect mr(pa.boundingRect());
768 mr = mr.intersected(rect);
769 if(!mr.isValid())
770 return;
771 QPen pen;
772 pen.setCosmetic(true);
773 pen.setColor(Qt::black);
774 p.setPen(pen);
775 p.setBrush(Qt::black);
776 p.drawPolygon(pa);
777 }
778
779 //---------------------------------------------------------
780 // drawCanvas
781 //---------------------------------------------------------
782
drawCanvas(QPainter & p,const QRect & mr,const QRegion & rg)783 void DrumCanvas::drawCanvas(QPainter& p, const QRect& mr, const QRegion& rg)
784 {
785 const QRect ur = mapDev(mr);
786
787 int ux = ur.x();
788 if(ux < 0)
789 ux = 0;
790 const int uy = ur.y();
791 const int uw = ur.width();
792 const int uh = ur.height();
793 const int ux_2 = ux + uw;
794 const int uy_2 = uy + uh;
795
796 QPen pen;
797 pen.setCosmetic(true);
798 pen.setColor(MusEGlobal::config.midiDividerColor);
799 p.setPen(pen);
800
801 if (MusEGlobal::config.canvasShowGrid || MusEGlobal::config.canvasShowGridHorizontalAlways)
802 {
803 //---------------------------------------------------
804 // horizontal lines
805 //---------------------------------------------------
806
807 int uyy = ((uy-1) / TH) * TH + TH;
808
809 // For testing...
810 // fprintf(stderr, "DrumCanvas::drawCanvas ux:%d uy:%d uw:%d uh:%d uyy:%d\n", ux, uy, uw, uh, uyy);
811
812 for (; uyy < uy_2; uyy += TH) {
813 p.drawLine(ux, uyy, ux_2, uyy);
814 }
815 }
816
817 if (MusEGlobal::config.canvasShowGrid)
818 {
819 //---------------------------------------------------
820 // vertical lines
821 //---------------------------------------------------
822
823 if (MusEGlobal::config.canvasShowGrid)
824 drawTickRaster(p, mr, rg, editor->raster(), false, false, false,
825 MusEGlobal::config.midiCanvasBeatColor,
826 MusEGlobal::config.midiCanvasBeatColor,
827 MusEGlobal::config.midiCanvasFineColor,
828 MusEGlobal::config.midiCanvasBarColor);
829 }
830 }
831
832 //---------------------------------------------------------
833 // drawTopItem
834 //---------------------------------------------------------
835
drawTopItem(QPainter & p,const QRect &,const QRegion &)836 void DrumCanvas::drawTopItem(QPainter& p, const QRect&, const QRegion&)
837 {
838 // draw cursor
839 if (_tool == CursorTool) {
840 QPen pen;
841 pen.setCosmetic(true);
842 pen.setColor(Qt::black);
843 p.setPen(pen);
844
845 int y = mapy(TH * cursorPos.y());
846
847 // p.drawPixmap(mapx(cursorPos.x())-TH/2,y,TH,TH, *cursorIconSVG);
848 cursorIconSVG->paint(&p, mapx(cursorPos.x())-TH/2, y, TH, TH);
849 // need to figure out a coordinate system for the cursor, complicated stuff.
850 }
851
852 }
853 //---------------------------------------------------------
854 // y2pitch
855 //---------------------------------------------------------
856
y2pitch(int y) const857 int DrumCanvas::y2pitch(int y) const
858 {
859 int pitch = y/TH;
860 if (pitch >= instrument_map.size())
861 pitch = instrument_map.size()-1;
862 else if (pitch<0)
863 pitch = 0;
864 return pitch;
865 }
866
867 //---------------------------------------------------------
868 // pitch2y
869 //---------------------------------------------------------
870
pitch2y(int pitch) const871 int DrumCanvas::pitch2y(int pitch) const
872 {
873 return pitch * TH;
874 }
875
876 //---------------------------------------------------------
877 // cmd
878 //---------------------------------------------------------
879
cmd(int cmd)880 void DrumCanvas::cmd(int cmd)
881 {
882 switch (cmd) {
883 case CMD_SELECT_ALL: // select all
884 for (iCItem k = items.begin(); k != items.end(); ++k) {
885 if (!k->second->isSelected())
886 selectItem(k->second, true);
887 }
888 break;
889 case CMD_SELECT_NONE: // select none
890 deselectAll();
891 break;
892 case CMD_SELECT_INVERT: // invert selection
893 for (iCItem k = items.begin(); k != items.end(); ++k) {
894 selectItem(k->second, !k->second->isSelected());
895 }
896 break;
897 case CMD_SELECT_ILOOP: // select inside loop
898 for (iCItem k = items.begin(); k != items.end(); ++k) {
899 DEvent* nevent =(DEvent*)(k->second);
900 MusECore::Part* part = nevent->part();
901 MusECore::Event event = nevent->event();
902 unsigned tick = event.tick() + part->tick();
903 if (tick < MusEGlobal::song->lpos() || tick >= MusEGlobal::song->rpos())
904 selectItem(k->second, false);
905 else
906 selectItem(k->second, true);
907 }
908 break;
909 case CMD_SELECT_OLOOP: // select outside loop
910 for (iCItem k = items.begin(); k != items.end(); ++k) {
911 DEvent* nevent = (DEvent*)(k->second);
912 MusECore::Part* part = nevent->part();
913 MusECore::Event event = nevent->event();
914 unsigned tick = event.tick() + part->tick();
915 if (tick < MusEGlobal::song->lpos() || tick >= MusEGlobal::song->rpos())
916 selectItem(k->second, true);
917 else
918 selectItem(k->second, false);
919 }
920 break;
921 case CMD_RANGE_TO_SELECTION:
922 setRangeToSelection();
923 break;
924 case CMD_SELECT_PREV_PART: // select previous part
925 {
926 MusECore::Part* pt = editor->curCanvasPart();
927 MusECore::Part* newpt = pt;
928 MusECore::PartList* pl = editor->parts();
929 for(MusECore::iPart ip = pl->begin(); ip != pl->end(); ++ip)
930 if(ip->second == pt)
931 {
932 if(ip == pl->begin())
933 ip = pl->end();
934 --ip;
935 newpt = ip->second;
936 break;
937 }
938 if(newpt != pt)
939 editor->setCurCanvasPart(newpt);
940 }
941 break;
942 case CMD_SELECT_NEXT_PART: // select next part
943 {
944 MusECore::Part* pt = editor->curCanvasPart();
945 MusECore::Part* newpt = pt;
946 MusECore::PartList* pl = editor->parts();
947 for(MusECore::iPart ip = pl->begin(); ip != pl->end(); ++ip)
948 if(ip->second == pt)
949 {
950 ++ip;
951 if(ip == pl->end())
952 ip = pl->begin();
953 newpt = ip->second;
954 break;
955 }
956 if(newpt != pt)
957 editor->setCurCanvasPart(newpt);
958 }
959 break;
960
961 case CMD_FIXED_LEN: //Set notes to the length specified in the drummap
962 if (!selectionSize())
963 break;
964 MusEGlobal::song->startUndo();
965 for (iCItem k = items.begin(); k != items.end(); ++k) {
966 if (k->second->isSelected()) {
967 DEvent* devent = (DEvent*)(k->second);
968 MusECore::Event event = devent->event();
969 MusECore::Event newEvent = event.clone();
970 // newEvent.setLenTick(drumMap[event.pitch()].len);
971 newEvent.setLenTick(ourDrumMap[y2pitch(devent->y())].len);
972 // Operation is undoable but do not start/end undo.
973 // Indicate do not do port controller values and clone parts.
974 MusEGlobal::song->applyOperation(MusECore::UndoOp(MusECore::UndoOp::ModifyEvent,
975 newEvent, event, devent->part(), false, false), MusECore::Song::OperationUndoable);
976 }
977 }
978 MusEGlobal::song->endUndo(SC_EVENT_MODIFIED);
979 break;
980 case CMD_LEFT:
981 {
982 int spos = pos[0];
983 if(spos > 0)
984 {
985 spos -= 1; // Nudge by -1, then snap down with raster1.
986 spos = MusEGlobal::sigmap.raster1(spos, editor->rasterStep(pos[0]));
987 }
988 if(spos < 0)
989 spos = 0;
990 MusECore::Pos p(spos,true);
991 MusEGlobal::song->setPos(MusECore::Song::CPOS, p, true, true, true);
992 }
993 break;
994 case CMD_RIGHT:
995 {
996 int spos = MusEGlobal::sigmap.raster2(pos[0] + 1, editor->rasterStep(pos[0])); // Nudge by +1, then snap up with raster2.
997 MusECore::Pos p(spos,true);
998 MusEGlobal::song->setPos(MusECore::Song::CPOS, p, true, true, true);
999 }
1000 break;
1001 case CMD_LEFT_NOSNAP:
1002 {
1003 printf("left no snap\n");
1004 int spos = pos[0] - editor->rasterStep(pos[0]);
1005 if (spos < 0)
1006 spos = 0;
1007 MusECore::Pos p(spos,true);
1008 MusEGlobal::song->setPos(MusECore::Song::CPOS, p, true, true, true); //CDW
1009 }
1010 break;
1011 case CMD_RIGHT_NOSNAP:
1012 {
1013 MusECore::Pos p(pos[0] + editor->rasterStep(pos[0]), true);
1014 MusEGlobal::song->setPos(MusECore::Song::CPOS, p, true, true, true); //CDW
1015 }
1016 break;
1017 }
1018 itemSelectionsChanged();
1019 redraw();
1020 }
1021
1022
1023 //---------------------------------------------------------
1024 // startDrag
1025 //---------------------------------------------------------
1026
startDrag(CItem *,DragType t)1027 void DrumCanvas::startDrag(CItem* /* item*/, DragType t)
1028 {
1029 QMimeData* md = selected_events_to_mime(partlist_to_set(editor->parts()), 1);
1030
1031 if (md)
1032 {
1033 // "Note that setMimeData() assigns ownership of the QMimeData object to the QDrag object.
1034 // The QDrag must be constructed on the heap with a parent QWidget to ensure that Qt can
1035 // clean up after the drag and drop operation has been completed. "
1036 QDrag* drag = new QDrag(this);
1037 drag->setMimeData(md);
1038
1039 if (t == MOVE_COPY || t == MOVE_CLONE)
1040 drag->exec(Qt::CopyAction);
1041 else
1042 drag->exec(Qt::MoveAction);
1043 }
1044 }
1045
1046 //---------------------------------------------------------
1047 // dragEnterEvent
1048 //---------------------------------------------------------
1049
dragEnterEvent(QDragEnterEvent * event)1050 void DrumCanvas::dragEnterEvent(QDragEnterEvent* event)
1051 {
1052 event->acceptProposedAction(); // TODO CHECK Tim.
1053 }
1054
1055 //---------------------------------------------------------
1056 // dragMoveEvent
1057 //---------------------------------------------------------
1058
dragMoveEvent(QDragMoveEvent *)1059 void DrumCanvas::dragMoveEvent(QDragMoveEvent*)
1060 {
1061 }
1062
1063 //---------------------------------------------------------
1064 // dragLeaveEvent
1065 //---------------------------------------------------------
1066
dragLeaveEvent(QDragLeaveEvent *)1067 void DrumCanvas::dragLeaveEvent(QDragLeaveEvent*)
1068 {
1069 }
1070
1071
1072 //---------------------------------------------------------
1073 // keyPressed - called from DList
1074 //---------------------------------------------------------
1075
keyPressed(int index,int velocity)1076 void DrumCanvas::keyPressed(int index, int velocity)
1077 {
1078 if(velocity > 127)
1079 velocity = 127;
1080 else if(velocity <= 0)
1081 velocity = 1;
1082
1083 // Stop all notes.
1084 stopPlayEvents();
1085
1086 if ((index<0) || (index>=getOurDrumMapSize()))
1087 return;
1088 // called from DList - play event
1089 // play note:
1090 if(_playEvents)
1091 {
1092 int pitch, port, channel;
1093 if(index2Note(index, &port, &channel, &pitch))
1094 startPlayEvent(pitch, velocity, port, channel);
1095 }
1096
1097 if (_steprec) /* && pos[0] >= start_tick && pos[0] < end_tick [removed by flo93: this is handled in steprec->record] */
1098 {
1099 if ( curPart && instrument_map[index].tracks.contains(curPart->track()) )
1100 steprec->record(curPart,instrument_map[index].pitch,ourDrumMap[index].len,editor->raster(),velocity,MusEGlobal::globalKeyState&Qt::ControlModifier,MusEGlobal::globalKeyState&Qt::ShiftModifier, -1 /* invalid pitch as "really played" -> the "insert rest" feature is never triggered */);
1101 else
1102 {
1103 QSet<MusECore::Part*> parts = parts_at_tick(pos[0], instrument_map[index].tracks);
1104
1105 if (parts.count() != 1)
1106 QMessageBox::warning(this, tr("Recording event failed"), tr("Couldn't record the event, because the currently selected part isn't the same track, and the instrument to be recorded could be either on no or on multiple parts, which is ambiguous.\nSelect the destination part, then try again."));
1107 else
1108 steprec->record(*parts.begin(), instrument_map[index].pitch,ourDrumMap[index].len,editor->raster(),velocity,MusEGlobal::globalKeyState&Qt::ControlModifier,MusEGlobal::globalKeyState&Qt::ShiftModifier, -1 /* invalid pitch as "really played" -> the "insert rest" feature is never triggered */);
1109
1110 }
1111 }
1112 }
1113
1114 //---------------------------------------------------------
1115 // keyReleased
1116 //---------------------------------------------------------
1117
keyReleased(int,bool)1118 void DrumCanvas::keyReleased(int, bool)
1119 {
1120 // release note:
1121 if(_playEvents)
1122 stopPlayEvents();
1123 }
1124
1125 //---------------------------------------------------------
1126 // mapChanged
1127 //---------------------------------------------------------
1128
mapChanged(int spitch,int dpitch)1129 void DrumCanvas::mapChanged(int spitch, int dpitch)
1130 {
1131 // spitch may be the same as dpitch! and something in here must be executed
1132 // even if they're same (i assume it's song->update(SC_DRUMMAP)) (flo93)
1133
1134 if (dpitch!=spitch)
1135 {
1136 using MusEGlobal::global_drum_ordering_t;
1137 using MusEGlobal::global_drum_ordering;
1138
1139 for (QSet<MusECore::Track*>::iterator it=instrument_map[spitch].tracks.begin();
1140 it!=instrument_map[spitch].tracks.end(); it++)
1141 {
1142 if (dynamic_cast<MusECore::MidiTrack*>(*it))
1143 dynamic_cast<MusECore::MidiTrack*>(*it)->set_drummap_ordering_tied_to_patch(false);
1144 }
1145 for (QSet<MusECore::Track*>::iterator it=instrument_map[dpitch].tracks.begin();
1146 it!=instrument_map[dpitch].tracks.end(); it++)
1147 {
1148 if (dynamic_cast<MusECore::MidiTrack*>(*it))
1149 dynamic_cast<MusECore::MidiTrack*>(*it)->set_drummap_ordering_tied_to_patch(false);
1150 }
1151
1152 MusECore::DrumMap dm_temp = ourDrumMap[spitch];
1153 instrument_number_mapping_t im_temp = instrument_map[spitch];
1154
1155 global_drum_ordering_t order_temp;
1156 for (global_drum_ordering_t::iterator it=global_drum_ordering.begin(); it!=global_drum_ordering.end();)
1157 {
1158 if (im_temp.pitch==it->second && im_temp.tracks.contains(it->first))
1159 {
1160 order_temp.push_back(*it);
1161 it=global_drum_ordering.erase(it);
1162 }
1163 else
1164 it++;
1165 }
1166
1167 // the instrument represented by instrument_map[dpitch] is always the instrument
1168 // which will be immediately AFTER our dragged instrument. or it's invalid
1169 if (dpitch < getOurDrumMapSize())
1170 {
1171 for (global_drum_ordering_t::iterator it=global_drum_ordering.begin(); it!=global_drum_ordering.end(); it++)
1172 if (instrument_map[dpitch].pitch==it->second && instrument_map[dpitch].tracks.contains(it->first))
1173 {
1174 while (!order_temp.empty())
1175 it=global_drum_ordering.insert(it, order_temp.takeLast());
1176
1177 break;
1178 }
1179 }
1180 else
1181 {
1182 global_drum_ordering_t::iterator it=global_drum_ordering.end();
1183 while (!order_temp.empty())
1184 it=global_drum_ordering.insert(it, order_temp.takeLast());
1185 }
1186
1187
1188
1189
1190
1191 if (dpitch > spitch)
1192 {
1193 for (int i=spitch; i<dpitch-1; i++)
1194 {
1195 ourDrumMap[i]=ourDrumMap[i+1];
1196 instrument_map[i]=instrument_map[i+1];
1197 }
1198
1199 ourDrumMap[dpitch-1] = dm_temp;
1200 instrument_map[dpitch-1] = im_temp;
1201 }
1202 else if (spitch > dpitch)
1203 {
1204 for (int i=spitch; i>dpitch; i--)
1205 {
1206 ourDrumMap[i]=ourDrumMap[i-1];
1207 instrument_map[i]=instrument_map[i-1];
1208 }
1209
1210 ourDrumMap[dpitch] = dm_temp;
1211 instrument_map[dpitch] = im_temp;
1212 }
1213 }
1214
1215
1216 MusEGlobal::song->update(SC_DRUMMAP); // this causes a complete rebuild of ourDrumMap
1217 // which also handles the changed order in all
1218 // other drum editors
1219 }
1220
1221 //---------------------------------------------------------
1222 // resizeEvent
1223 //---------------------------------------------------------
1224
resizeEvent(QResizeEvent * ev)1225 void DrumCanvas::resizeEvent(QResizeEvent* ev)
1226 {
1227 if (ev->size().width() != ev->oldSize().width())
1228 emit newWidth(ev->size().width());
1229 EventCanvas::resizeEvent(ev);
1230 }
1231
1232
1233 //---------------------------------------------------------
1234 // modifySelected
1235 //---------------------------------------------------------
1236
modifySelected(NoteInfo::ValType type,int val,bool delta_mode)1237 void DrumCanvas::modifySelected(NoteInfo::ValType type, int val, bool delta_mode)
1238 {
1239 QList< QPair<int,MusECore::Event> > already_done;
1240 MusECore::Undo operations;
1241 for (iCItem i = items.begin(); i != items.end(); ++i) {
1242 if (!(i->second->isSelected()))
1243 continue;
1244 DEvent* e = (DEvent*)(i->second);
1245 MusECore::Event event = e->event();
1246 if (event.type() != MusECore::Note)
1247 continue;
1248
1249 MusECore::MidiPart* part = (MusECore::MidiPart*)(e->part());
1250
1251 if (already_done.contains(QPair<int,MusECore::Event>(part->clonemaster_sn(), event)))
1252 continue;
1253
1254 MusECore::Event newEvent = event.clone();
1255
1256 switch (type) {
1257 case NoteInfo::VAL_TIME:
1258 {
1259 int newTime = val;
1260 if(delta_mode)
1261 newTime += event.tick();
1262 else
1263 newTime -= part->tick();
1264 if (newTime < 0)
1265 newTime = 0;
1266 newEvent.setTick(newTime);
1267 }
1268 break;
1269 case NoteInfo::VAL_LEN:
1270 {
1271 int len = val;
1272 if(delta_mode)
1273 len += event.lenTick();
1274 if (len < 1)
1275 len = 1;
1276 newEvent.setLenTick(len);
1277 }
1278 break;
1279 case NoteInfo::VAL_VELON:
1280 {
1281 int velo = val;
1282 if(delta_mode)
1283 velo += event.velo();
1284 if (velo > 127)
1285 velo = 127;
1286 else if (velo < 0)
1287 // REMOVE Tim. Noteoff. Changed. Zero note on vel is not allowed now.
1288 // velo = 0;
1289 velo = 1;
1290 newEvent.setVelo(velo);
1291 }
1292 break;
1293 case NoteInfo::VAL_VELOFF:
1294 {
1295 int velo = val;
1296 if(delta_mode)
1297 velo += event.veloOff();
1298 if (velo > 127)
1299 velo = 127;
1300 else if (velo < 0)
1301 velo = 0;
1302 newEvent.setVeloOff(velo);
1303 }
1304 break;
1305 case NoteInfo::VAL_PITCH:
1306 {
1307 int direction = -val;
1308 for (int i = 0; i < instrument_map.size(); ++i) {
1309 if (instrument_map.at(i).pitch == event.pitch()) {
1310 int nextPos = i + direction;
1311 if (nextPos> -1 && nextPos < instrument_map.size())
1312 newEvent.setPitch(instrument_map.at(nextPos).pitch);
1313 break;
1314 }
1315 }
1316 }
1317 break;
1318 }
1319
1320 operations.push_back(MusECore::UndoOp(MusECore::UndoOp::ModifyEvent, newEvent, event, part, false, false));
1321
1322 already_done.append(QPair<int,MusECore::Event>(part->clonemaster_sn(), event));
1323 }
1324 MusEGlobal::song->applyOperationGroup(operations);
1325 }
1326
1327 //---------------------------------------------------------
1328 // curPartChanged
1329 //---------------------------------------------------------
1330
curPartChanged()1331 void DrumCanvas::curPartChanged()
1332 {
1333 EventCanvas::curPartChanged();
1334 editor->setWindowTitle(getCaption());
1335 }
1336
1337 //---------------------------------------------------------
1338 // getNextStep - gets next tick in the chosen direction
1339 // when raster and stepSize are taken into account
1340 //---------------------------------------------------------
getNextStep(unsigned int currentPos,int basicStep,int stepSize)1341 int DrumCanvas::getNextStep(unsigned int currentPos, int basicStep, int stepSize)
1342 {
1343 int newPos = currentPos; // newPos will be updated for every loop
1344 for (int i = 0; i < stepSize; i++)
1345 {
1346 if (basicStep > 0) // moving right
1347 {
1348 // Nudge by +1, then snap up with raster2.
1349 newPos = MusEGlobal::sigmap.raster2(newPos + basicStep, editor->rasterStep(newPos));
1350 if (newPos > signed(curPart->endTick() - editor->rasterStep(curPart->endTick())))
1351 {
1352 newPos = curPart->tick();
1353 }
1354 }
1355 else // moving left
1356 {
1357 // Nudge by -1, then snap up with raster1.
1358 newPos = MusEGlobal::sigmap.raster1(newPos + basicStep, editor->rasterStep(newPos));
1359
1360 if (newPos < signed(curPart->tick()))
1361 {
1362 newPos = MusEGlobal::sigmap.raster1(curPart->endTick()-1, editor->rasterStep(curPart->endTick()));
1363 }
1364 }
1365 }
1366 return newPos;
1367 }
1368
1369 //---------------------------------------------------------
1370 // keyPress
1371 //---------------------------------------------------------
1372
keyPress(QKeyEvent * event)1373 void DrumCanvas::keyPress(QKeyEvent* event)
1374 {
1375 if (_tool == CursorTool) {
1376
1377 int key = event->key();
1378 if (((QInputEvent*)event)->modifiers() & Qt::ShiftModifier)
1379 key += Qt::SHIFT;
1380 if (((QInputEvent*)event)->modifiers() & Qt::AltModifier)
1381 key += Qt::ALT;
1382 if (((QInputEvent*)event)->modifiers() & Qt::ControlModifier)
1383 key+= Qt::CTRL;
1384
1385 // Select items by key (PianoRoll & DrumEditor)
1386 if (key == shortcuts[SHRT_SEL_RIGHT].key) {
1387 cursorPos.setX(getNextStep(cursorPos.x(),1));
1388
1389 selectCursorEvent(getEventAtCursorPos());
1390 if (mapx(cursorPos.x()) < 0 || mapx(cursorPos.x()) > width())
1391 emit followEvent(cursorPos.x());
1392 update();
1393 return;
1394 }
1395 else if (key == shortcuts[SHRT_SEL_LEFT].key) {
1396 cursorPos.setX(getNextStep(cursorPos.x(),-1));
1397
1398 selectCursorEvent(getEventAtCursorPos());
1399 if (mapx(cursorPos.x()) < 0 || mapx(cursorPos.x()) > width())
1400 emit followEvent(cursorPos.x());
1401 update();
1402 return;
1403 }
1404 // NOTE: The inner NewItem may play the note. But let us not stop the note so shortly after playing it.
1405 // So it is up to the corresponding keyRelease() to stop the note.
1406 else if ( key == shortcuts[SHRT_ADDNOTE_1].key ||
1407 key == shortcuts[SHRT_ADDNOTE_2].key ||
1408 key == shortcuts[SHRT_ADDNOTE_3].key ||
1409 key == shortcuts[SHRT_ADDNOTE_4].key)
1410 {
1411 if (key ==shortcuts[SHRT_ADDNOTE_1].key) {
1412 newItem(newItem(cursorPos.x(), cursorPos.y(), ourDrumMap[cursorPos.y()].lv1),false,true);
1413 }
1414 else if(key ==shortcuts[SHRT_ADDNOTE_2].key) {
1415 newItem(newItem(cursorPos.x(), cursorPos.y(), ourDrumMap[cursorPos.y()].lv2),false,true);
1416 }
1417 else if(key ==shortcuts[SHRT_ADDNOTE_3].key) {
1418 newItem(newItem(cursorPos.x(), cursorPos.y(), ourDrumMap[cursorPos.y()].lv3),false,true);
1419 }
1420 else if(key ==shortcuts[SHRT_ADDNOTE_4].key) {
1421 newItem(newItem(cursorPos.x(), cursorPos.y(), ourDrumMap[cursorPos.y()].lv4),false,true);
1422 }
1423
1424 cursorPos.setX(getNextStep(cursorPos.x(),1, _stepSize));
1425 selectCursorEvent(getEventAtCursorPos());
1426 if (mapx(cursorPos.x()) < 0 || mapx(cursorPos.x()) > width())
1427 emit followEvent(cursorPos.x());
1428 return;
1429 }
1430 }
1431 EventCanvas::keyPress(event);
1432 }
1433
1434 //---------------------------------------------------------
1435 // keyRelease
1436 //---------------------------------------------------------
1437
keyRelease(QKeyEvent * event)1438 void DrumCanvas::keyRelease(QKeyEvent* event)
1439 {
1440 if (_tool == CursorTool)
1441 {
1442 if (_playEvents)
1443 {
1444 int key = event->key();
1445 if (((QInputEvent*)event)->modifiers() & Qt::ShiftModifier)
1446 key += Qt::SHIFT;
1447 if (((QInputEvent*)event)->modifiers() & Qt::AltModifier)
1448 key += Qt::ALT;
1449 if (((QInputEvent*)event)->modifiers() & Qt::ControlModifier)
1450 key+= Qt::CTRL;
1451 if (key == shortcuts[SHRT_ADDNOTE_1].key ||
1452 key == shortcuts[SHRT_ADDNOTE_2].key ||
1453 key == shortcuts[SHRT_ADDNOTE_3].key ||
1454 key == shortcuts[SHRT_ADDNOTE_4].key)
1455 {
1456 // Must stop note that was played.
1457 stopPlayEvents();
1458 return;
1459 }
1460 }
1461 }
1462 EventCanvas::keyRelease(event);
1463 }
1464
1465 //---------------------------------------------------------
1466 // setTool2
1467 //---------------------------------------------------------
setTool2(int)1468 void DrumCanvas::setTool2(int)
1469 {
1470 if (_tool == CursorTool)
1471 deselectAll();
1472 if (unsigned(cursorPos.x()) < curPart->tick())
1473 cursorPos.setX(curPart->tick());
1474 update();
1475 }
1476 //---------------------------------------------------------
1477 // setCurDrumInstrument
1478 //---------------------------------------------------------
setCurDrumInstrument(int i)1479 void DrumCanvas::setCurDrumInstrument(int i)
1480 {
1481 cursorPos.setY(i);
1482 update();
1483 }
1484
1485 //---------------------------------------------------------
1486 // setStep
1487 //---------------------------------------------------------
setStep(int v)1488 void DrumCanvas::setStep(int v)
1489 {
1490 _stepSize=v;
1491 }
1492
1493 //---------------------------------------------------------
1494 // getEventAtCursorPos
1495 //---------------------------------------------------------
getEventAtCursorPos()1496 const MusECore::Event* DrumCanvas::getEventAtCursorPos()
1497 {
1498 if (_tool != CursorTool)
1499 return 0;
1500 if (instrument_map[cursorPos.y()].tracks.contains(curPart->track()))
1501 {
1502 MusECore::ciEvent lower = curPart->events().lower_bound(cursorPos.x()-curPart->tick());
1503 MusECore::ciEvent upper = curPart->events().upper_bound(cursorPos.x()-curPart->tick());
1504 int curPitch = instrument_map[cursorPos.y()].pitch;
1505 for (MusECore::ciEvent i = lower; i != upper; ++i) {
1506 const MusECore::Event& ev = i->second;
1507 if (ev.isNote() && ev.pitch() == curPitch)
1508 return &ev;
1509 }
1510 }
1511 // else or if the for loop didn't find anything
1512 return 0;
1513 }
1514 //---------------------------------------------------------
1515 // selectCursorEvent
1516 //---------------------------------------------------------
selectCursorEvent(const MusECore::Event * ev)1517 void DrumCanvas::selectCursorEvent(const MusECore::Event* ev)
1518 {
1519 for (iCItem i = items.begin(); i != items.end(); ++i)
1520 {
1521 MusECore::Event e = i->second->event();
1522
1523 if (ev && ev->tick() == e.tick() && ev->pitch() == e.pitch() && e.isNote())
1524 i->second->setSelected(true);
1525 else
1526 i->second->setSelected(false);
1527
1528 }
1529 itemSelectionsChanged();
1530 }
1531
1532 //---------------------------------------------------------
1533 // midiNote
1534 //---------------------------------------------------------
midiNote(int pitch,int velo)1535 void DrumCanvas::midiNote(int pitch, int velo)
1536 {
1537 using MusECore::Track;
1538 using MusECore::Part;
1539
1540 if (debugMsg) printf("DrumCanvas::midiNote: pitch=%i, velo=%i\n", pitch, velo);
1541
1542 if (_midiin && _steprec && !MusEGlobal::audio->isPlaying() && velo && !(MusEGlobal::globalKeyState & Qt::AltModifier) /* && pos[0] >= start_tick && pos[0] < end_tick [removed by flo93: this is handled in steprec->record()] */ )
1543 {
1544 if (pitch == MusEGlobal::rcSteprecNote) // skip the fancy code below, simply record a rest
1545 {
1546 if (curPart)
1547 steprec->record(curPart,0xdead,0xbeef,editor->raster(),velo,MusEGlobal::globalKeyState&Qt::ControlModifier,MusEGlobal::globalKeyState&Qt::ShiftModifier, pitch);
1548 }
1549 else
1550 {
1551 QSet<Track*> possible_dest_tracks;
1552 Part* rec_part=nullptr;
1553 int rec_index=-1;
1554
1555 int ourDrumMapSize=getOurDrumMapSize();
1556 int i;
1557 for (i=0;i<ourDrumMapSize;i++)
1558 {
1559 if ( instrument_map[i].tracks.contains(curPart->track()) && ourDrumMap[i].enote==pitch)
1560 {
1561 rec_part=curPart;
1562 rec_index=i;
1563 break;
1564 }
1565 else if (ourDrumMap[i].enote==pitch)
1566 possible_dest_tracks.unite(instrument_map[i].tracks);
1567 }
1568
1569 if (rec_part == nullptr) // if recording to curPart isn't possible
1570 {
1571 QSet<Part*> possible_dest_parts = parts_at_tick(pos[0], possible_dest_tracks);
1572
1573 if (possible_dest_parts.count() != 1)
1574 QMessageBox::warning(this, tr("Recording event failed"), tr("Couldn't record the event, because the currently selected part isn't the same track, and the instrument to be recorded could be either on no or on multiple parts, which is ambiguous.\nSelect the destination part, then try again."));
1575 else
1576 {
1577 rec_part = *possible_dest_parts.begin();
1578 Track* dest_track=rec_part->track();
1579
1580 for (i=0;i<ourDrumMapSize;i++)
1581 if ( instrument_map[i].tracks.contains(dest_track) && ourDrumMap[i].enote==pitch)
1582 {
1583 rec_index=i;
1584 break;
1585 }
1586
1587 if (rec_index==-1)
1588 {
1589 printf("ERROR: THIS SHOULD NEVER HAPPEN: i found a destination part for step recording, but now i can't find the instrument any more in DrumCanvas::midiNote()?!\n");
1590 QMessageBox::critical(this, tr("Internal error"), tr("Wtf, some nasty internal error which is actually impossible occurred. Check console output. Nothing recorded."));
1591 rec_part=nullptr;
1592 }
1593 }
1594 }
1595
1596 if (rec_part != nullptr)
1597 steprec->record(rec_part,instrument_map[rec_index].pitch,ourDrumMap[rec_index].len,editor->raster(),velo,MusEGlobal::globalKeyState&Qt::ControlModifier,MusEGlobal::globalKeyState&Qt::ShiftModifier, pitch);
1598 }
1599 }
1600 }
1601
1602
pitch_and_track_to_instrument(int pitch,MusECore::Track * track)1603 int DrumCanvas::pitch_and_track_to_instrument(int pitch, MusECore::Track* track)
1604 {
1605 for (int i=0; i<instrument_map.size(); i++)
1606 if (instrument_map[i].tracks.contains(track) && instrument_map[i].pitch==pitch)
1607 return i;
1608
1609 if (heavyDebugMsg) printf("DrumCanvas::pitch_and_track_to_instrument() called with invalid arguments.\n");
1610 return -1;
1611 }
1612
propagate_drummap_change(int instrument,int fields,bool isReset,bool includeDefault,bool isInstrumentMod,bool doWholeMap)1613 void DrumCanvas::propagate_drummap_change(int instrument, int fields, bool isReset, bool includeDefault, bool isInstrumentMod, bool doWholeMap)
1614 {
1615 //fprintf(stderr, "DrumCanvas::propagate_track_drummap_change instrument:%d fields:%x isReset:%d isInstrumentMod:%d\n",
1616 // instrument, fields, isReset, isInstrumentMod);
1617 const QSet<MusECore::Track*>& tracks=instrument_map[instrument].tracks;
1618 int index=instrument_map[instrument].pitch;
1619
1620 MusECore::DrumMapTrackOperation* dmop = new MusECore::DrumMapTrackOperation;
1621 dmop->_isReset = isReset;
1622 dmop->_includeDefault = includeDefault;
1623 dmop->_doWholeMap = doWholeMap;
1624 dmop->_isInstrumentMod = isInstrumentMod;
1625
1626 MusECore::PendingOperationList operations;
1627 MusECore::Track* t;
1628 for(QSet<MusECore::Track*>::const_iterator it = tracks.begin(); it != tracks.end(); it++)
1629 {
1630 t = *it;
1631 if(!t->isDrumTrack())
1632 continue;
1633 MusECore::MidiTrack* mt = static_cast<MusECore::MidiTrack*>(t);
1634 dmop->_tracks.push_back(mt);
1635 }
1636
1637 if(isReset)
1638 dmop->_workingItemList.add(index, MusECore::WorkingDrumMapEntry(MusECore::DrumMap(), fields)); // Fixme: Dummy map. Should just be fields.
1639 else
1640 dmop->_workingItemList.add(index, MusECore::WorkingDrumMapEntry(ourDrumMap[instrument], fields));
1641
1642 operations.add(MusECore::PendingOperationItem(dmop, MusECore::PendingOperationItem::ModifyTrackDrumMapItem));
1643 MusEGlobal::audio->msgExecutePendingOperations(operations, true);
1644 }
1645
isWorkingMapInstrument(int instr,int fields) const1646 int DrumCanvas::isWorkingMapInstrument(int instr, int fields) const
1647 {
1648 const QSet<MusECore::Track*>& tracks=instrument_map[instr].tracks;
1649 int index=instrument_map[instr].pitch;
1650
1651 MusECore::Track* t;
1652 MusECore::MidiTrack* mt;
1653 int ret = MusECore::WorkingDrumMapEntry::NoOverride;
1654 for (QSet<MusECore::Track*>::const_iterator it = tracks.begin(); it != tracks.end(); it++)
1655 {
1656 t = *it;
1657 if(!t->isDrumTrack())
1658 continue;
1659 mt = static_cast<MusECore::MidiTrack*>(t);
1660 // Don't pass a patch - ask it to take care of patch number for us.
1661 ret |= mt->isWorkingMapItem(index, fields);
1662 }
1663 return ret;
1664 }
1665
hasOverrides(int instr) const1666 bool DrumCanvas::hasOverrides(int instr) const
1667 {
1668 const QSet<MusECore::Track*>& tracks=instrument_map[instr].tracks;
1669 MusECore::Track* t;
1670 MusECore::MidiTrack* mt;
1671 for (QSet<MusECore::Track*>::const_iterator it = tracks.begin(); it != tracks.end(); it++)
1672 {
1673 t = *it;
1674 if(!t->isDrumTrack())
1675 continue;
1676 mt = static_cast<MusECore::MidiTrack*>(t);
1677 if(!mt->workingDrumMap()->empty())
1678 return true;
1679 }
1680 return false;
1681 }
1682
resetOverridesForAllPatches(int instr)1683 void DrumCanvas::resetOverridesForAllPatches(int instr)
1684 {
1685 if(QMessageBox::warning(this, tr("Drum map"),
1686 tr("Reset the track's drum map with instrument defaults?"),
1687 QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) == QMessageBox::Ok)
1688 {
1689 MusECore::PendingOperationList operations;
1690 const QSet<MusECore::Track*>& tracks=instrument_map[instr].tracks;
1691 MusECore::Track* t;
1692 MusECore::MidiTrack* mt;
1693 MusECore::WorkingDrumMapPatchList* new_wdmpl;
1694 MusECore::DrumMapTrackPatchReplaceOperation* dmop;
1695 for (QSet<MusECore::Track*>::const_iterator it = tracks.begin(); it != tracks.end(); it++)
1696 {
1697 t = *it;
1698 if(!t->isDrumTrack())
1699 continue;
1700 mt = static_cast<MusECore::MidiTrack*>(t);
1701 if(!mt->workingDrumMap()->empty())
1702 {
1703 // Completely blank replacement list.
1704 new_wdmpl = new MusECore::WorkingDrumMapPatchList();
1705 // The allocated WorkingDrumMapPatchList wdmpl will become the new list and the
1706 // original lists will be deleted, in the operation following.
1707 dmop = new MusECore::DrumMapTrackPatchReplaceOperation;
1708 dmop->_isInstrumentMod = false; // Not instrument operation.
1709 dmop->_workingItemPatchList = new_wdmpl;
1710 dmop->_track = static_cast<MusECore::MidiTrack*>(t);
1711 operations.add(MusECore::PendingOperationItem(dmop, MusECore::PendingOperationItem::ReplaceTrackDrumMapPatchList));
1712 }
1713 }
1714 if(!operations.empty())
1715 MusEGlobal::audio->msgExecutePendingOperations(operations, true);
1716 }
1717 }
1718
rebuildOurDrumMap()1719 void DrumCanvas::rebuildOurDrumMap()
1720 {
1721 using MusECore::drummaps_almost_equal;
1722 using MusECore::Track;
1723 using MusECore::MidiTrack;
1724 using MusECore::TrackList;
1725 using MusECore::ciTrack;
1726 using MusECore::ciPart;
1727 using MusECore::DrumMap;
1728 using MusEGlobal::global_drum_ordering_t;
1729 using MusEGlobal::global_drum_ordering;
1730
1731 //fprintf(stderr, "DrumCanvas::rebuildOurDrumMap\n");
1732 bool need_update = false;
1733
1734 TrackList* tl=MusEGlobal::song->tracks();
1735 QList< QSet<Track*> > track_groups;
1736 QVector<instrument_number_mapping_t> old_instrument_map = instrument_map;
1737
1738 instrument_map.clear();
1739
1740 for (ciTrack track = tl->begin(); track!=tl->end(); track++)
1741 {
1742 ciPart p_it;
1743 for (p_it=drumEditor->parts()->begin(); p_it!=drumEditor->parts()->end(); p_it++)
1744 if (p_it->second->track() == *track)
1745 break;
1746
1747 if (p_it!=drumEditor->parts()->end()) // if *track is represented by some part in this editor
1748 {
1749 bool inserted=false;
1750
1751 switch (drumEditor->group_mode())
1752 {
1753 case DrumEdit::GROUP_SAME_CHANNEL:
1754 for (QList< QSet<Track*> >::iterator group=track_groups.begin(); group!=track_groups.end(); group++)
1755 if ( ((MidiTrack*)*group->begin())->outChannel() == ((MidiTrack*)*track)->outChannel() &&
1756 ((MidiTrack*)*group->begin())->outPort() == ((MidiTrack*)*track)->outPort() &&
1757 (drummaps_almost_equal(((MidiTrack*)*group->begin())->drummap(), ((MidiTrack*)*track)->drummap())) )
1758 {
1759 group->insert(*track);
1760 inserted=true;
1761 break;
1762 }
1763 break;
1764
1765 case DrumEdit::GROUP_MAX:
1766 for (QList< QSet<Track*> >::iterator group=track_groups.begin(); group!=track_groups.end(); group++)
1767 if (drummaps_almost_equal(((MidiTrack*)*group->begin())->drummap(), ((MidiTrack*)*track)->drummap()))
1768 {
1769 group->insert(*track);
1770 inserted=true;
1771 break;
1772 }
1773 break;
1774
1775 case DrumEdit::DONT_GROUP:
1776 inserted=false;
1777 break;
1778
1779 default:
1780 printf("THIS SHOULD NEVER HAPPEN: group_mode() is invalid!\n");
1781 inserted=false;
1782 }
1783
1784 if (!inserted)
1785 {
1786 QSet<Track*> temp;
1787 temp.insert(*track);
1788 track_groups.push_back(temp);
1789 }
1790 }
1791 }
1792
1793 // from now, we assume that every track_group's entry only groups tracks with identical
1794 // drum maps, but not necessarily identical hide-lists together.
1795 QVector< std::pair<MidiTrack*,int> > ignore_order_entries;
1796 for (global_drum_ordering_t::iterator order_it=global_drum_ordering.begin(); order_it!=global_drum_ordering.end(); order_it++)
1797 {
1798 // if this entry should be ignored, ignore it.
1799 if (ignore_order_entries.contains(*order_it))
1800 continue;
1801
1802 // look if we have order_it->first (the MidiTrack*) in any of our track groups
1803 QList< QSet<Track*> >::iterator group;
1804 for (group=track_groups.begin(); group!=track_groups.end(); group++)
1805 if (group->contains(order_it->first))
1806 break;
1807
1808 if (group!=track_groups.end()) // we have
1809 {
1810 int pitch=order_it->second;
1811
1812 bool mute=true;
1813 bool hidden=true;
1814
1815 if (drumEditor->ignore_hide()) hidden=false;
1816
1817 for (QSet<Track*>::iterator track=group->begin(); track!=group->end() && (mute || hidden); track++)
1818 {
1819 if (dynamic_cast<MidiTrack*>(*track)->drummap()[pitch].mute == false)
1820 mute=false;
1821 if (dynamic_cast<MidiTrack*>(*track)->drummap()[pitch].hide == false)
1822 hidden=false;
1823 }
1824
1825 if (!hidden)
1826 {
1827 for (QSet<Track*>::iterator track=group->begin(); track!=group->end(); track++)
1828 {
1829 DrumMap* dm = &dynamic_cast<MidiTrack*>(*track)->drummap()[pitch];
1830 if (dm->mute != mute)
1831 {
1832 dm->mute=mute;
1833 need_update = true;
1834 }
1835 }
1836 instrument_map.append(instrument_number_mapping_t(*group, pitch));
1837 }
1838
1839 for (QSet<Track*>::iterator track=group->begin(); track!=group->end(); track++)
1840 ignore_order_entries.append(std::pair<MidiTrack*,int>(dynamic_cast<MidiTrack*>(*track), pitch));
1841 }
1842 // else ignore it
1843 }
1844
1845
1846 // maybe delete and then populate ourDrumMap
1847
1848 if (must_delete_our_drum_map && ourDrumMap!=nullptr)
1849 delete [] ourDrumMap;
1850
1851 int size = instrument_map.size();
1852 ourDrumMap=new DrumMap[size];
1853 must_delete_our_drum_map=true;
1854
1855 Track* t;
1856 MidiTrack* mt;
1857 int index;
1858 for (int i=0;i<size;i++)
1859 {
1860 t = *instrument_map[i].tracks.begin();
1861 if(!t->isMidiTrack())
1862 continue;
1863 mt = static_cast<MidiTrack*>(t);
1864 index = instrument_map[i].pitch;
1865 ourDrumMap[i] = mt->drummap()[index];
1866 }
1867
1868 if (instrument_map!=old_instrument_map)
1869 {
1870 if (debugMsg) printf("rebuilt drummap and instrument map, size is now %i\n",size);
1871
1872 songChanged(SC_EVENT_INSERTED); // force an update of the itemlist
1873 emit ourDrumMapChanged(true);
1874 }
1875 else
1876 emit ourDrumMapChanged(false);
1877
1878 if (need_update)
1879 MusEGlobal::song->update(SC_DRUMMAP, true); // i know, this causes a recursion, which possibly
1880 // isn't the most elegant solution here. but it will
1881 // never be an infinite recursion
1882 }
1883
mouseMove(QMouseEvent * event)1884 void DrumCanvas::mouseMove(QMouseEvent* event) {
1885
1886 EventCanvas::mouseMove(event);
1887
1888 if (MusEGlobal::config.showNoteTooltips)
1889 showNoteTooltip(event);
1890
1891 if (MusEGlobal::config.showStatusBar)
1892 showStatusTip(event);
1893 }
1894
showNoteTooltip(QMouseEvent * event)1895 void DrumCanvas::showNoteTooltip(QMouseEvent* event) {
1896
1897 static CItem* hoverItem = nullptr;
1898
1899 if (!(_tool & (MusEGui::PointerTool | MusEGui::PencilTool | MusEGui::RubberTool | MusEGui::CursorTool)))
1900 return;
1901
1902 CItem* item = findCurrentItem(event->pos());
1903 if (item && hoverItem == item)
1904 return;
1905
1906 // careful, drum list can be hidden/reduced by various display options
1907 // int pitch = drumEditor->get_instrument_map()[y2pitch(event->pos().y())].pitch;
1908 const auto& imap = drumEditor->get_instrument_map();
1909 if (imap.isEmpty())
1910 return;
1911
1912 const int ipitch = y2pitch(event->pos().y());
1913 if (ipitch < 0 || ipitch >= imap.size())
1914 return;
1915
1916 const int pitch = imap.at(ipitch).pitch;
1917
1918 QString str;
1919 if (track()->drummap()[pitch].name.isEmpty())
1920 str = MusECore::pitch2string(pitch) + " (" + QString::number(pitch) + ")";
1921 else
1922 str = track()->drummap()[pitch].name + " (" + MusECore::pitch2string(pitch)
1923 + "/" + QString::number(pitch) + ")";
1924
1925 if (item) {
1926 hoverItem = item;
1927
1928 MusECore::Pos start(item->event().tick() + item->part()->tick());
1929
1930 int bar, beat, tick, hour, min, sec, msec;
1931
1932 start.mbt(&bar, &beat, &tick);
1933 QString str_bar = QString("%1.%2.%3")
1934 .arg(bar + 1, 4, 10, QLatin1Char('0'))
1935 .arg(beat + 1, 2, 10, QLatin1Char('0'))
1936 .arg(tick, 3, 10, QLatin1Char('0'));
1937
1938 start.msmu(&hour, &min, &sec, &msec, nullptr);
1939 QString str_time = QString("%1:%2:%3.%4")
1940 .arg(hour, 2, 10, QLatin1Char('0'))
1941 .arg(min, 2, 10, QLatin1Char('0'))
1942 .arg(sec, 2, 10, QLatin1Char('0'))
1943 .arg(msec, 3, 10, QLatin1Char('0'));
1944
1945 str = tr("Note: ") + str + "\n"
1946 + tr("Velocity: ") + QString::number(item->event().velo()) + "\n"
1947 + tr("Start (bar): ") + str_bar + "\n"
1948 + tr("Start (time): ") + str_time;
1949
1950 } else {
1951 hoverItem = nullptr;
1952 }
1953
1954 QToolTip::showText(QPoint(event->globalX(), event->globalY() + 10), str);
1955 }
1956
showStatusTip(QMouseEvent * event)1957 void DrumCanvas::showStatusTip(QMouseEvent* event) {
1958
1959 static CItem* hoverItem = nullptr;
1960 static Tool localTool;
1961
1962 CItem* item = findCurrentItem(event->pos());
1963 if (item) {
1964 if (hoverItem == item && localTool == _tool)
1965 return;
1966
1967 hoverItem = item;
1968 localTool = _tool;
1969
1970 QString s;
1971 if (_tool & (MusEGui::PointerTool ))
1972 s = tr("LMB: Select/Move | CTRL+LMB: Multi select/Move© | SHIFT+LMB: Select pitch | MMB: Delete");
1973 else if (_tool & (MusEGui::PencilTool))
1974 s = tr("LMB: Select | CTRL+LMB: Multi select | SHIFT+LMB: Select pitch | CTRL+SHIFT+LMB: Multi pitch select | MMB: Delete");
1975 else if (_tool & (MusEGui::RubberTool))
1976 s = tr("LMB: Delete");
1977 else if (_tool & (MusEGui::CursorTool))
1978 s = tr("Arrow keys to move cursor, V,B,N,M keys to create events with increasing velocity, Del to delete.");
1979
1980 if (!s.isEmpty())
1981 MusEGlobal::muse->setStatusBarText(s);
1982 } else {
1983 if (hoverItem != nullptr) {
1984 MusEGlobal::muse->clearStatusBarText();
1985 hoverItem = nullptr;
1986 }
1987 }
1988 }
1989
setCursor()1990 void DrumCanvas::setCursor()
1991 {
1992 // Avoid duplication, just do it below.
1993 //showCursor();
1994
1995 switch (drag) {
1996
1997 case DRAGX_MOVE:
1998 case DRAGX_COPY:
1999 case DRAGX_CLONE:
2000 // Make sure to do this.
2001 showCursor();
2002 QWidget::setCursor(*pencilMoveHorizCursor);
2003 break;
2004
2005 case DRAGY_MOVE:
2006 case DRAGY_COPY:
2007 case DRAGY_CLONE:
2008 // Make sure to do this.
2009 showCursor();
2010 QWidget::setCursor(*pencilMoveVertCursor);
2011 break;
2012
2013 case DRAG_MOVE:
2014 case DRAG_COPY:
2015 case DRAG_CLONE:
2016 // Make sure to do this.
2017 showCursor();
2018 QWidget::setCursor(*pencilMove4WayCursor);
2019 break;
2020
2021 case DRAG_RESIZE:
2022 // Make sure to do this.
2023 showCursor();
2024 QWidget::setCursor(*pencilMoveHorizCursor);
2025 break;
2026
2027 default:
2028 // Let the Canvas handle it, and call showCursor().
2029 Canvas::setCursor();
2030 break;
2031 }
2032 }
2033
2034 //---------------------------------------------------------
2035 // setMouseOverItemCursor
2036 //---------------------------------------------------------
2037
setMouseOverItemCursor()2038 void DrumCanvas::setMouseOverItemCursor()
2039 {
2040 //showCursor();
2041 QWidget::setCursor(*pencilMove4WayCursor);
2042 }
2043
2044
2045 } // namespace MusEGui
2046