//========================================================= // MusE // Linux Music Editor // $Id: ctrlcanvas.cpp,v 1.15.2.10 2009/11/14 03:37:48 terminator356 Exp $ // (C) Copyright 1999 Werner Schweer (ws@seh.de) // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; version 2 of // the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // //========================================================= #include #include #include #include #include #include "app.h" #include "globals.h" #include "ctrledit.h" #include "midieditor.h" #include "icons.h" #include "midiport.h" #include "song.h" #include "midictrl.h" #include "audio.h" #include "gconfig.h" #include "ctrlpanel.h" #include "midiedit/drummap.h" #include "drumedit.h" #include "drummap.h" static MusECore::MidiCtrlValList veloList(MusECore::CTRL_VELOCITY); // dummy namespace MusEGui { //--------------------------------------------------------- // computeVal //--------------------------------------------------------- static int computeVal(MusECore::MidiController* mc, int y, int height) { int min; int max; if(mc->num() == MusECore::CTRL_PROGRAM) { min = 1; max = 128; } else { min = mc->minVal(); max = mc->maxVal(); } int val = max - (y * (max-min) / height); if (val < min) val = min; if (val > max) val = max; if(mc->num() != MusECore::CTRL_PROGRAM) val += mc->bias(); return val; } //--------------------------------------------------------- // computeY //--------------------------------------------------------- static int computeY(const MusECore::MidiController* mc, int val, int height) { int min; int max; if(mc->num() == MusECore::CTRL_PROGRAM) { min = 1; max = 128; } else { min = mc->minVal(); max = mc->maxVal(); } //printf("computeY #1 min:%d max:%d val:%d bias:%d height:%d\n", min, max, val, mc->bias(), height); if(mc->num() != MusECore::CTRL_PROGRAM) val -= mc->bias(); if (val < min) val = min; if (val > max) val = max; int y = min == max ? 0 : (max-val)*height/(max-min); //printf("computeY #2 y:%d min:%d max:%d val:%d bias:%d height:%d\n", y, min, max, val, mc->bias(), height); return y; } //--------------------------------------------------------- // CEvent //--------------------------------------------------------- CEvent::CEvent(MusECore::Event e, MusECore::MidiPart* pt, int v) { _event = e; _part = pt; _val = v; ex = !e.empty() ? e.tick() : 0; } //--------------------------------------------------------- // intersects //--------------------------------------------------------- bool CEvent::intersects(const MusECore::MidiController* mc, const QRect& r, const int tickstep, const int wh) const { if(_event.empty()) return false; int y1 = computeY(mc, _val, wh); int tick1 = _event.tick() + _part->tick(); if(ex == -1) return tick1 < (r.x() + r.width()) && y1 < (r.y() + r.height()); int tick2 = ex + _part->tick(); // Velocities don't use EX (set equal to event tick, giving zero width here), // and velocities are drawn three pixels wide, so adjust the width now. // Remember, each drawn pixel represents one tickstep which varies with zoom. // So that's 3 x tickstep for each velocity line. // Hmm, actually, for better pin-point accuracy just use one tickstep for now. if(MusECore::midiControllerType(mc->num()) == MusECore::MidiController::Velo) tick2 += tickstep; QRect er(tick1, y1, tick2 - tick1, wh - y1); return r.intersects(er); } //--------------------------------------------------------- // contains //--------------------------------------------------------- bool CEvent::contains(int x1, int x2) const { int tick1 = !_event.empty() ? _event.tick() + _part->tick() : 0; if(ex == -1) return (tick1 < x2); int tick2 = ex + _part->tick(); return ((tick1 >= x1 && tick1 < x2) //|| (tick2 >= x1 && tick2 < x2) DELETETHIS? || (tick2 > x1 && tick2 < x2) || (tick1 < x1 && tick2 >= x2)); } //--------------------------------------------------------- // clearDelete //--------------------------------------------------------- void CEventList::clearDelete() { for(ciCEvent i = begin(); i != end(); ++i) { CEvent* ce = *i; if(ce) delete ce; } clear(); } //--------------------------------------------------------- // CtrlCanvas //--------------------------------------------------------- CtrlCanvas::CtrlCanvas(MidiEditor* e, QWidget* parent, int xmag, const char* name, CtrlPanel* pnl) : View(parent, xmag, 1, name) { setBg(MusEGlobal::config.midiControllerViewBg); setFont(MusEGlobal::config.fonts[3]); editor = e; _panel = pnl; drag = DRAG_OFF; tool = MusEGui::PointerTool; pos[0] = 0; pos[1] = 0; pos[2] = 0; noEvents=false; _perNoteVeloMode = MusEGlobal::config.velocityPerNote; if(_panel) _panel->setVeloPerNoteMode(_perNoteVeloMode); if (dynamic_cast(editor) && dynamic_cast(editor)->old_style_drummap_mode()==false) filterTrack=true; else filterTrack=false; ctrl = &veloList; _controller = &MusECore::veloCtrl; _cnum = MusECore::CTRL_VELOCITY; _dnum = MusECore::CTRL_VELOCITY; _didx = MusECore::CTRL_VELOCITY; connect(MusEGlobal::song, SIGNAL(posChanged(int, unsigned, bool)), this, SLOT(setPos(int, unsigned, bool))); setMouseTracking(true); curPart = 0; curTrack = 0; if (!editor->parts()->empty()) setCurTrackAndPart(); connect(MusEGlobal::song, SIGNAL(songChanged(MusECore::SongChangedFlags_t)), SLOT(songChanged(MusECore::SongChangedFlags_t))); connect(MusEGlobal::muse, SIGNAL(configChanged()), SLOT(configChanged())); setCurDrumPitch(editor->curDrumInstrument()); //printf("CtrlCanvas::CtrlCanvas curDrumPitch:%d\n", curDrumPitch); connect(editor, SIGNAL(curDrumInstrumentChanged(int)), SLOT(setCurDrumPitch(int))); updateItems(); } CtrlCanvas::~CtrlCanvas() { items.clearDelete(); } //--------------------------------------------------------- // setPanel //--------------------------------------------------------- void CtrlCanvas::setPanel(CtrlPanel* pnl) { _panel = pnl; if(_panel) _panel->setVeloPerNoteMode(_perNoteVeloMode); } //--------------------------------------------------------- // setPos // set one of three markers // idx - 0-cpos 1-lpos 2-rpos // flag - emit followEvent() //--------------------------------------------------------- void CtrlCanvas::setPos(int idx, unsigned val, bool adjustScrollbar) { if (pos[idx] == val) return; int opos = mapx(pos[idx]); int npos = mapx(val); if (adjustScrollbar && idx == 0) { switch (MusEGlobal::song->follow()) { case MusECore::Song::NO: break; case MusECore::Song::JUMP: if (npos >= width()) { int ppos = val - rmapxDev(width()/4); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } else if (npos < 0) { int ppos = val - rmapxDev(width()*3/4); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } break; case MusECore::Song::CONTINUOUS: if (npos > (width()*5)/8) { int ppos = pos[idx] - rmapxDev(width()*5/8); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } else if (npos < (width()*3)/8) { int ppos = pos[idx] - rmapxDev(width()*3/8); if (ppos < 0) ppos = 0; emit followEvent(ppos); opos = mapx(pos[idx]); npos = mapx(val); } break; } } int x; int w = 1; if (opos > npos) { w += opos - npos; x = npos; } else { w += npos - opos; x = opos; } pos[idx] = val; redraw(QRect(x, 0, w, height())); } //--------------------------------------------------------- // setMidiController //--------------------------------------------------------- void CtrlCanvas::setMidiController(int num) { _cnum = num; partControllers(curPart, _cnum, &_dnum, &_didx, &_controller, &ctrl); if(_panel) { if(_cnum == MusECore::CTRL_VELOCITY) _panel->setHWController(curTrack, &MusECore::veloCtrl); else _panel->setHWController(curTrack, _controller); } } //--------------------------------------------------------- // leaveEvent //--------------------------------------------------------- void CtrlCanvas::leaveEvent(QEvent*) { emit xposChanged(INT_MAX); emit yposChanged(-1); } //--------------------------------------------------------- // raster //--------------------------------------------------------- QPoint CtrlCanvas::raster(const QPoint& p) const { return p; } //--------------------------------------------------------- // deselectAll //--------------------------------------------------------- void CtrlCanvas::deselectAll() { for(iCEvent i = selection.begin(); i != selection.end(); ++i) (*i)->setSelected(false); selection.clear(); } //--------------------------------------------------------- // selectItem //--------------------------------------------------------- void CtrlCanvas::selectItem(CEvent* e) { e->setSelected(true); selection.push_back(e); } //--------------------------------------------------------- // deselectItem //--------------------------------------------------------- void CtrlCanvas::deselectItem(CEvent* e) { e->setSelected(false); for (iCEvent i = selection.begin(); i != selection.end(); ++i) { if (*i == e) { selection.erase(i); break; } } } //--------------------------------------------------------- // setController //--------------------------------------------------------- void CtrlCanvas::setController(int num) { setMidiController(num); updateItems(); } //--------------------------------------------------------- // setCurTrackAndPart //--------------------------------------------------------- bool CtrlCanvas::setCurTrackAndPart() { bool changed = false; MusECore::MidiPart* part = 0; MusECore::MidiTrack* track = 0; if(!editor->parts()->empty()) { MusECore::Part* pt = editor->curCanvasPart(); if(pt && pt->track()) { if(pt->track()->isMidiTrack()) { part = (MusECore::MidiPart*)pt; track = part->track(); } } } if(part != curPart) { curPart = part; changed = true; } if(track != curTrack) { curTrack = track; changed = true; } return changed; } //--------------------------------------------------------- // configChanged //--------------------------------------------------------- void CtrlCanvas::configChanged() { songChanged(SC_CONFIG); } //--------------------------------------------------------- // songChanged // all marked parts are added to the internal event list //--------------------------------------------------------- void CtrlCanvas::songChanged(MusECore::SongChangedFlags_t type) { if(editor->deleting()) // Ignore while while deleting to prevent crash. return; //printf("CtrlCanvas::songChanged type:%x\n", type); // Is it simply a midi controller value adjustment? Forget it. if(type == SC_MIDI_CONTROLLER) return; if(type & SC_CONFIG) setFont(MusEGlobal::config.fonts[3]); bool changed = false; if(type & (SC_CONFIG | SC_PART_MODIFIED | SC_SELECTION)) changed = setCurTrackAndPart(); // Although changing the instrument/device in the // config window generates a type of -1, we can eliminate // some other useless calls using SC_CONFIG, which was not used // anywhere else in muse before now, except song header. if((type & (SC_CONFIG | SC_DRUMMAP)) || ((type & (SC_PART_MODIFIED | SC_SELECTION)) && changed)) setMidiController(_cnum); if(!curPart) return; if(type & (SC_CONFIG | SC_DRUMMAP | SC_PART_MODIFIED | SC_EVENT_INSERTED | SC_EVENT_REMOVED | SC_EVENT_MODIFIED)) updateItems(); else if(type & SC_SELECTION) updateSelections(); } //--------------------------------------------------------- // partControllers // num is the controller number, in 'MidiController terms' (lo-byte = 0xff for per-note controllers). //--------------------------------------------------------- void CtrlCanvas::partControllers(const MusECore::MidiPart* part, int num, int* dnum, int* didx, MusECore::MidiController** mc, MusECore::MidiCtrlValList** mcvl) { if(num == MusECore::CTRL_VELOCITY) // special case { if(mcvl) *mcvl = &veloList; if(mc) *mc = &MusECore::veloCtrl; if(dnum) *dnum = num; if(didx) *didx = num; } else { if(!part) { if(mcvl) *mcvl = 0; if(mc) *mc = 0; if(dnum) *dnum = 0; if(didx) *didx = 0; return; } MusECore::MidiTrack* mt = part->track(); MusECore::MidiPort* mp; int di; int n; if((curDrumPitch >= 0) && ((num & 0xff) == 0xff)) { di = (num & ~0xff) | curDrumPitch; if((mt->type() == MusECore::Track::DRUM)) { n = (num & ~0xff) | MusEGlobal::drumMap[curDrumPitch].anote; // Default to track port if -1 and track channel if -1. int mport = MusEGlobal::drumMap[curDrumPitch].port; if(mport == -1) mport = mt->outPort(); mp = &MusEGlobal::midiPorts[mport]; } if(mt->type() == MusECore::Track::NEW_DRUM) { n = di; // Simple one-to-one correspondence. mp = &MusEGlobal::midiPorts[mt->outPort()]; } else if(mt->type() == MusECore::Track::MIDI) { n = di; // Simple one-to-one correspondence. There is no 'mapping' for piano roll midi - yet. mp = &MusEGlobal::midiPorts[mt->outPort()]; } } else { di = num; n = num; mp = &MusEGlobal::midiPorts[mt->outPort()]; } if(dnum) *dnum = n; if(didx) *didx = di; if(mc) *mc = mp->midiController(n); if(mcvl) { MusECore::MidiCtrlValList* tmcvl = 0; MusECore::MidiCtrlValListList* cvll = mp->controller(); for(MusECore::iMidiCtrlValList i = cvll->begin(); i != cvll->end(); ++i) { if(i->second->num() == n) { tmcvl = i->second; break; } } *mcvl = tmcvl; } } } //--------------------------------------------------------- // updateItems //--------------------------------------------------------- void CtrlCanvas::updateItems() { selection.clear(); items.clearDelete(); if(!editor->parts()->empty()) { CEvent *newev = 0; for (MusECore::iPart p = editor->parts()->begin(); p != editor->parts()->end(); ++p) { MusECore::Event last; CEvent* lastce = 0; MusECore::MidiPart* part = (MusECore::MidiPart*)(p->second); if (filterTrack && part->track() != curTrack) continue; MusECore::MidiCtrlValList* mcvl; partControllers(part, _cnum, 0, 0, 0, &mcvl); unsigned len = part->lenTick(); for (MusECore::ciEvent i = part->events().begin(); i != part->events().end(); ++i) { const MusECore::Event& e = i->second; // Do not add events which are past the end of the part. if(e.tick() >= len) break; if(_cnum == MusECore::CTRL_VELOCITY && e.type() == MusECore::Note) { newev = 0; if (curDrumPitch == -1 || !_perNoteVeloMode) // and NOT >=0 items.add(newev = new CEvent(e, part, e.velo())); else if (e.dataA() == curDrumPitch) //same note. if curDrumPitch==-2, this never is true items.add(newev = new CEvent(e, part, e.velo())); if(newev && e.selected()) selection.push_back(newev); } else if (e.type() == MusECore::Controller) { int ctl = e.dataA(); if(part->track() && part->track()->type() == MusECore::Track::DRUM && (_cnum & 0xff) == 0xff) { if(curDrumPitch < 0) continue; // Default to track port if -1 and track channel if -1. int port = MusEGlobal::drumMap[ctl & 0x7f].port; if(port == -1) port = part->track()->outPort(); int chan = MusEGlobal::drumMap[ctl & 0x7f].channel; if(chan == -1) chan = part->track()->outChannel(); int cur_port = MusEGlobal::drumMap[curDrumPitch].port; if(cur_port == -1) cur_port = part->track()->outPort(); int cur_chan = MusEGlobal::drumMap[curDrumPitch].channel; if(cur_chan == -1) cur_chan = part->track()->outChannel(); if((port != cur_port) || (chan != cur_chan)) continue; ctl = (ctl & ~0xff) | MusEGlobal::drumMap[ctl & 0x7f].anote; } if(ctl == _dnum) { if(mcvl && last.empty()) { lastce = new CEvent(MusECore::Event(), part, mcvl->value(part->tick())); items.add(lastce); } if (lastce) lastce->setEX(e.tick()); lastce = new CEvent(e, part, e.dataB()); lastce->setEX(-1); items.add(lastce); if(e.selected()) selection.push_back(lastce); last = e; } } } } } redraw(); } //--------------------------------------------------------- // updateSelections //--------------------------------------------------------- void CtrlCanvas::updateSelections() { selection.clear(); for(ciCEvent i = items.begin(); i != items.end(); ++i) { CEvent* e = *i; //if(e->part() != part) // continue; if(e->selected()) selection.push_back(e); } redraw(); } //--------------------------------------------------------- // viewMousePressEvent //--------------------------------------------------------- void CtrlCanvas::viewMousePressEvent(QMouseEvent* event) { if(!_controller || curDrumPitch==-2) return; start = event->pos(); MusEGui::Tool activeTool = tool; bool ctrlKey = event->modifiers() & Qt::ControlModifier; int xpos = start.x(); int ypos = start.y(); MusECore::MidiController::ControllerType type = MusECore::midiControllerType(_controller->num()); switch (activeTool) { case MusEGui::PointerTool: if(curPart) { drag = DRAG_LASSO_START; bool do_redraw = false; if (!ctrlKey) { deselectAll(); do_redraw = true; } int h = height(); int tickstep = rmapxDev(1); QRect r(xpos, ypos, tickstep, rmapyDev(1)); int endTick = xpos + tickstep; int partTick = curPart->tick(); for (iCEvent i = items.begin(); i != items.end(); ++i) { CEvent* ev = *i; if(ev->part() != curPart) continue; MusECore::Event event = ev->event(); if(event.empty()) continue; int ax = event.tick() + partTick; //if (ax < xpos) // continue; if (ax >= endTick) break; if (ev->intersects(_controller, r, tickstep, h)) { if (ctrlKey && ev->selected()) deselectItem(ev); else selectItem(ev); do_redraw = true; //break; } } if(do_redraw) redraw(); // Let songChanged handle the redraw upon SC_SELECTION. } break; case MusEGui::PencilTool: if ((!ctrlKey) && (type != MusECore::MidiController::Velo)) { drag = DRAG_NEW; MusEGlobal::song->startUndo(); newVal(xpos, ypos); } else { drag = DRAG_RESIZE; MusEGlobal::song->startUndo(); changeVal(xpos, xpos, ypos); } break; case MusEGui::RubberTool: if (type != MusECore::MidiController::Velo) { drag = DRAG_DELETE; MusEGlobal::song->startUndo(); deleteVal(xpos, xpos, ypos); } break; case MusEGui::DrawTool: if (drawLineMode) { line2x = xpos; line2y = ypos; if ((!ctrlKey) && (type != MusECore::MidiController::Velo)) newValRamp(line1x, line1y, line2x, line2y); else changeValRamp(line1x, line1y, line2x, line2y); drawLineMode = false; } else { line2x = line1x = xpos; line2y = line1y = ypos; drawLineMode = true; } redraw(); break; default: break; } } //--------------------------------------------------------- // viewMouseMoveEvent //--------------------------------------------------------- void CtrlCanvas::viewMouseMoveEvent(QMouseEvent* event) { if(!_controller || curDrumPitch==-2) return; QPoint pos = event->pos(); QPoint dist = pos - start; bool moving = dist.y() >= 3 || dist.y() <= 3 || dist.x() >= 3 || dist.x() <= 3; switch (drag) { case DRAG_LASSO_START: if (!moving) break; drag = DRAG_LASSO; // fallthrough case DRAG_LASSO: lasso.setRect(start.x(), start.y(), dist.x(), dist.y()); redraw(); break; case DRAG_RESIZE: changeVal(start.x(), pos.x(), pos.y()); start = pos; break; case DRAG_NEW: newVal(start.x(), start.y(), pos.x(), pos.y()); start = pos; break; case DRAG_DELETE: deleteVal(start.x(), pos.x(), pos.y()); start = pos; break; default: break; } if (tool == MusEGui::DrawTool && drawLineMode) { line2x = pos.x(); line2y = pos.y(); redraw(); } emit xposChanged(pos.x()); int val = computeVal(_controller, pos.y(), height()); emit yposChanged(val); } //--------------------------------------------------------- // viewMouseReleaseEvent //--------------------------------------------------------- void CtrlCanvas::viewMouseReleaseEvent(QMouseEvent* event) { bool ctrlKey = event->modifiers() & Qt::ControlModifier; switch (drag) { case DRAG_RESIZE: MusEGlobal::song->endUndo(SC_EVENT_MODIFIED); break; case DRAG_NEW: MusEGlobal::song->endUndo(SC_EVENT_MODIFIED | SC_EVENT_INSERTED | SC_EVENT_REMOVED); break; case DRAG_DELETE: MusEGlobal::song->endUndo(SC_EVENT_REMOVED); break; case DRAG_LASSO_START: lasso.setRect(-1, -1, -1, -1); //fallthrough case DRAG_LASSO: if(_controller) { lasso = lasso.normalized(); int h = height(); int tickstep = rmapxDev(1); for (iCEvent i = items.begin(); i != items.end(); ++i) { if((*i)->part() != curPart) continue; if ((*i)->intersects(_controller, lasso, tickstep, h)) { if (ctrlKey && (*i)->selected()) (*i)->setSelected(false); else (*i)->setSelected(true); } } drag = DRAG_OFF; // Let songChanged handle the redraw upon SC_SELECTION. MusEGlobal::song->update(SC_SELECTION); } break; default: break; } drag = DRAG_OFF; } //--------------------------------------------------------- // wheelEvent //--------------------------------------------------------- void CtrlCanvas::wheelEvent(QWheelEvent* ev) { emit redirectWheelEvent(ev); } //--------------------------------------------------------- // newValRamp //--------------------------------------------------------- void CtrlCanvas::newValRamp(int x1, int y1, int x2, int y2) { if(!curPart || !_controller) return; if(x2 - x1 < 0) { int a = x1; x1 = x2; x2 = a; a = y1; y1 = y2; y2 = a; } int xx1 = editor->rasterVal1(x1); int xx2 = editor->rasterVal2(x2); // If x1 and x2 happen to lie directly on the same raster, xx1 will equal xx2, // which is not good - there should always be a spread. Nudge by +1 and recompute. if(xx1 == xx2) xx2 = editor->rasterVal2(x2 + 1); int type = _controller->num(); bool useRaster = false; int raster = editor->raster(); if (raster == 1) // set reasonable raster { raster = MusEGlobal::config.division/16; // Let's use 64th notes, for a bit finer resolution. Tim. useRaster = true; } MusECore::Undo operations; // delete existing events unsigned curPartTick = curPart->tick(); int lastpv = MusECore::CTRL_VAL_UNKNOWN; for (ciCEvent i = items.begin(); i != items.end(); ++i) { CEvent* ev = *i; if (ev->part() != curPart) continue; MusECore::Event event = ev->event(); if (event.empty()) continue; int x = event.tick() + curPartTick; if (x < xx1) continue; if (x >= xx2) break; // Do port controller values and clone parts. operations.push_back(MusECore::UndoOp(MusECore::UndoOp::DeleteEvent, event, curPart, true, true)); } if (ctrl) lastpv = ctrl->hwVal(); unsigned curPartLen = curPart->lenTick(); // insert new events for (int x = xx1, step; x < xx2 ; x += step ) { step = useRaster ? raster : editor->rasterVal2(x + 1) - x; int y = x + step >= xx2 || x2 == x1 ? y2 : (x == xx1 ? y1 : (((y2 - y1) * (x + step/2 - x1)) / (x2 - x1)) + y1); int nval = computeVal(_controller, y, height()); unsigned tick = x - curPartTick; // Do not add events which are past the end of the part. if (tick >= curPartLen) break; MusECore::Event event(MusECore::Controller); event.setTick(tick); event.setA(_didx); if (type == MusECore::CTRL_PROGRAM) { if (lastpv == MusECore::CTRL_VAL_UNKNOWN) event.setB(nval - 1); else event.setB((lastpv & 0xffff00) | (nval - 1)); } else event.setB(nval); // Do port controller values and clone parts. operations.push_back(MusECore::UndoOp(MusECore::UndoOp::AddEvent, event, curPart, true, true)); } MusEGlobal::song->applyOperationGroup(operations); } //--------------------------------------------------------- // changeValRamp //--------------------------------------------------------- void CtrlCanvas::changeValRamp(int x1, int y1, int x2, int y2) { if(!curPart || !_controller) return; int h = height(); int type = _controller->num(); MusECore::Undo operations; for (ciCEvent i = items.begin(); i != items.end(); ++i) { if ((*i)->contains(x1, x2)) { CEvent* ev = *i; if (ev->part() != curPart) continue; MusECore::Event event = ev->event(); if (event.empty()) continue; int x = event.tick() + curPart->tick(); int y = (x2==x1) ? y1 : (((y2-y1)*(x-x1))/(x2-x1))+y1; int nval = computeVal(_controller, y, h); if (type == MusECore::CTRL_PROGRAM) { if (event.dataB() == MusECore::CTRL_VAL_UNKNOWN) --nval; else nval = (event.dataB() & 0xffff00) | (nval - 1); } ev->setVal(nval); if (type == MusECore::CTRL_VELOCITY) { if ((event.velo() != nval)) { MusECore::Event newEvent = event.clone(); newEvent.setVelo(nval); ev->setEvent(newEvent); // Do not do port controller values and clone parts. operations.push_back(MusECore::UndoOp(MusECore::UndoOp::ModifyEvent, newEvent, event, curPart, false, false)); } } else { if (!event.empty()) { if ((event.dataB() != nval)) { MusECore::Event newEvent = event.clone(); newEvent.setB(nval); ev->setEvent(newEvent); // Do port controller values and clone parts. operations.push_back(MusECore::UndoOp(MusECore::UndoOp::ModifyEvent, newEvent, event, curPart, true, true)); } } } } } MusEGlobal::song->applyOperationGroup(operations); } //--------------------------------------------------------- // changeVal //--------------------------------------------------------- void CtrlCanvas::changeVal(int x1, int x2, int y) { if(!curPart || !_controller) return; bool changed = false; int newval = computeVal(_controller, y, height()); int type = _controller->num(); for (ciCEvent i = items.begin(); i != items.end(); ++i) { if (!(*i)->contains(x1, x2)) continue; CEvent* ev = *i; if(ev->part() != curPart) continue; MusECore::Event event = ev->event(); if (type == MusECore::CTRL_VELOCITY) { if ((event.velo() != newval)) { ev->setVal(newval); MusECore::Event newEvent = event.clone(); newEvent.setVelo(newval); ev->setEvent(newEvent); // Indicate no undo, and do not do port controller values and clone parts. MusEGlobal::audio->msgChangeEvent(event, newEvent, curPart, false, false, false); changed = true; } } else { if (!event.empty()) { int nval = newval; if(type == MusECore::CTRL_PROGRAM) { if(event.dataB() == MusECore::CTRL_VAL_UNKNOWN) --nval; else nval = (event.dataB() & 0xffff00) | (nval - 1); } ev->setVal(nval); if ((event.dataB() != nval)) { MusECore::Event newEvent = event.clone(); newEvent.setB(nval); ev->setEvent(newEvent); // Indicate no undo, and do port controller values and clone parts. MusEGlobal::audio->msgChangeEvent(event, newEvent, curPart, false, true, true); changed = true; } } } } if (changed) redraw(); } //--------------------------------------------------------- // newVal //--------------------------------------------------------- void CtrlCanvas::newVal(int x1, int y) { if(!curPart || !_controller) return; int xx1 = editor->rasterVal1(x1); int xx2 = editor->rasterVal2(x1); // If x1 happens to lie directly on a raster, xx1 will equal xx2, // which is not good - there should always be a spread. Nudge by +1 and recompute. if(xx1 == xx2) xx2 = editor->rasterVal2(x1 + 1); int newval = computeVal(_controller, y, height()); int type = _controller->num(); bool found = false; bool do_redraw = false; iCEvent ice_tmp; iCEvent prev_ev = items.end(); // End is OK with multi-part, this is just an end 'invalid' marker at first. iCEvent insertPoint = items.end(); // Similar case here. bool curPartFound = false; int lastpv = MusECore::CTRL_VAL_UNKNOWN; if(ctrl) lastpv = ctrl->hwVal(); int partTick = curPart->tick(); for (iCEvent i = items.begin(); i != items.end() ; ) { CEvent* ev = *i; if(ev->part() != curPart) { if(curPartFound) { // Make sure insertPoint doesn't lie beyond next part's first event. if(insertPoint == items.end()) insertPoint = i; // Speed-up: If the current part was already processed, we are done. break; } ++i; continue; } curPartFound = true; MusECore::Event event = ev->event(); if (event.empty()) { prev_ev = i; ++i; continue; } int ax = event.tick() + partTick; if (ax < xx1) { prev_ev = i; ++i; continue; } if (ax >= xx2) { insertPoint = i; break; } int nval = newval; if(type == MusECore::CTRL_PROGRAM) { if(event.dataB() == MusECore::CTRL_VAL_UNKNOWN) { if(lastpv == MusECore::CTRL_VAL_UNKNOWN) --nval; else nval = (lastpv & 0xffff00) | (nval - 1); } else nval = (event.dataB() & 0xffff00) | (nval - 1); } if (ax == xx1) { // change event found = true; ev->setVal(nval); if ((event.dataB() != nval)) { MusECore::Event newEvent = event.clone(); newEvent.setB(nval); ev->setEvent(newEvent); // Indicate no undo, and do port controller values and clone parts. MusEGlobal::audio->msgChangeEvent(event, newEvent, curPart, false, true, true); do_redraw = true; } prev_ev = i; ++i; } else if (ax < xx2) { // delete event deselectItem(ev); // Indicate no undo, and do port controller values and clone parts. MusEGlobal::audio->msgDeleteEvent(event, curPart, false, true, true); delete (ev); i = items.erase(i); ev = *i; // Is there a previous item? if(prev_ev != items.end()) { // Have we reached the end of the list, or the end of the current part's items within the item list? // Then it's current part's last drawn item. EX is 'infinity'. (As far as the part knows. Another part may overlay later.) // Else EX is current item's tick. (By now, ((*i)->event() should be valid - it must be not empty to call tick()). (*prev_ev)->setEX(i == items.end() || ev->part() != curPart ? -1 : ev->event().tick()); } do_redraw = true; // Let songChanged handle the redraw upon SC_SELECTION. prev_ev = i; } } if (!found) { // new event int tick = xx1 - curPart->tick(); // Do not add events which are past the end of the part. if((unsigned)tick < curPart->lenTick()) { MusECore::Event event(MusECore::Controller); event.setTick(tick); event.setA(_didx); if(type == MusECore::CTRL_PROGRAM) { if(lastpv == MusECore::CTRL_VAL_UNKNOWN) event.setB(newval - 1); else event.setB((lastpv & 0xffff00) | (newval - 1)); } else event.setB(newval); // Indicate no undo, and do port controller values and clone parts. MusEGlobal::audio->msgAddEvent(event, curPart, false, true, true); CEvent* newev = new CEvent(event, curPart, event.dataB()); insertPoint = items.insert(insertPoint, newev); //selectItem(newev); // TODO: There are advantages, but do we really want new items to be selected? if(insertPoint != items.begin()) { ice_tmp = insertPoint; --ice_tmp; (*ice_tmp)->setEX(tick); } ice_tmp = insertPoint; ++ice_tmp; (*insertPoint)->setEX(ice_tmp == items.end() || (*ice_tmp)->part() != curPart ? -1 : (*ice_tmp)->event().tick()); do_redraw = true; } } if(do_redraw) // Let songChanged handle the redraw upon SC_SELECTION. redraw(); } //--------------------------------------------------------- // newVal //--------------------------------------------------------- void CtrlCanvas::newVal(int x1, int y1, int x2, int y2) { if(!curPart || !_controller) return; if(x2 - x1 < 0) { int a = x1; x1 = x2; x2 = a; a = y1; y1 = y2; y2 = a; } // I wanted to avoid redundantly drawing the first x position since it is already done via mouseDown. // But with the raster 'off' there were some problems not catching some items, and jagged drawing. // Possibly there are slight differences between rasterVal1() and rasterVal2() - not reciprocals of each other? // Also the loops below were getting complicated to ignore that first position. // So I draw from the first x. (TODO The idea may work now since I wrote this - more work was done.) p4.0.18 Tim. int xx1 = editor->rasterVal1(x1); int xx2 = editor->rasterVal2(x2); // If x1 and x2 happen to lie directly on the same raster, xx1 will equal xx2, // which is not good - there should always be a spread. Nudge by +1 and recompute. if(xx1 == xx2) xx2 = editor->rasterVal2(x2 + 1); int type = _controller->num(); bool useRaster = false; int raster = editor->raster(); if (raster == 1) // set reasonable raster { raster = MusEGlobal::config.division/16; // Let's use 64th notes, for a bit finer resolution. Tim. useRaster = true; } bool do_redraw = false; // delete existing events iCEvent prev_ev = items.end(); // End is OK with multi-part, this is just an end 'invalid' marker at first. iCEvent insertPoint = items.end(); // Similar case here. iCEvent ice_tmp; bool curPartFound = false; int lastpv = MusECore::CTRL_VAL_UNKNOWN; int partTick = curPart->tick(); for (iCEvent i = items.begin(); i != items.end() ; ) { CEvent* ev = *i; if(ev->part() != curPart) { if(curPartFound) { // Make sure insertPoint doesn't lie beyond next part's first event. if(insertPoint == items.end()) insertPoint = i; // Speed-up: If the current part was already processed, we are done. break; } ++i; continue; } curPartFound = true; MusECore::Event event = ev->event(); if (event.empty()) { prev_ev = i; ++i; continue; } int x = event.tick() + partTick; if (x < xx1) { prev_ev = i; ++i; continue; } if (x >= xx2) { insertPoint = i; break; } deselectItem(ev); // Indicate no undo, and do port controller values and clone parts. MusEGlobal::audio->msgDeleteEvent(event, curPart, false, true, true); delete (ev); i = items.erase(i); ev = *i; // Is there a previous item? if(prev_ev != items.end()) { // Have we reached the end of the list, or the end of the current part's items within the item list? // Then it's current part's last drawn item. EX is 'infinity'. (As far as the part knows. Another part may overlay later.) // Else EX is current item's tick. (By now, ((*i)->event() should be valid - it must be not empty to call tick()). (*prev_ev)->setEX(i == items.end() || ev->part() != curPart ? -1 : ev->event().tick()); } do_redraw = true; // Let songChanged handle the redraw upon SC_SELECTION. prev_ev = i; } if(ctrl) lastpv = ctrl->hwVal(); // insert new events for (int x = xx1, step; x < xx2 ; x += step ) { step = useRaster ? raster : editor->rasterVal2(x + 1) - x; int y = x + step >= xx2 || x2 == x1 ? y2 : (x == xx1 ? y1 : (((y2 - y1) * (x + step/2 - x1)) / (x2 - x1)) + y1); int nval = computeVal(_controller, y, height()); int tick = x - partTick; // Do not add events which are past the end of the part. if((unsigned)tick >= curPart->lenTick()) break; MusECore::Event event(MusECore::Controller); event.setTick(tick); event.setA(_didx); if(type == MusECore::CTRL_PROGRAM) { if(lastpv == MusECore::CTRL_VAL_UNKNOWN) event.setB(nval - 1); else event.setB((lastpv & 0xffff00) | (nval - 1)); } else event.setB(nval); // Indicate no undo, and do port controller values and clone parts. MusEGlobal::audio->msgAddEvent(event, curPart, false, true, true); CEvent* newev = new CEvent(event, curPart, event.dataB()); insertPoint = items.insert(insertPoint, newev); //selectItem(newev); // TODO: There are advantages, but do we really want new items to be selected? if(insertPoint != items.begin()) { ice_tmp = insertPoint; --ice_tmp; (*ice_tmp)->setEX(tick); } ice_tmp = insertPoint; ++ice_tmp; (*insertPoint)->setEX(ice_tmp == items.end() || (*ice_tmp)->part() != curPart ? -1 : (*ice_tmp)->event().tick()); ++insertPoint; do_redraw = true; } if(do_redraw) // redraw(); } //--------------------------------------------------------- // deleteVal //--------------------------------------------------------- void CtrlCanvas::deleteVal(int x1, int x2, int) { if(!curPart) return; if(x2 - x1 < 0) { int a = x1; x1 = x2; x2 = a; } int xx1 = editor->rasterVal1(x1); int xx2 = editor->rasterVal2(x2); // If x1 and x2 happen to lie directly on the same raster, xx1 will equal xx2, // which is not good - there should always be a spread. Nudge by +1 and recompute. if(xx1 == xx2) xx2 = editor->rasterVal2(x2 + 1); int partTick = curPart->tick(); xx1 -= partTick; xx2 -= partTick; iCEvent prev_ev = items.end(); bool curPartFound = false; bool do_redraw = false; for (iCEvent i = items.begin(); i != items.end() ;) { CEvent* ev = *i; if(ev->part() != curPart) { // Speed-up: If the current part was already processed, we are done. if(curPartFound) break; ++i; continue; } curPartFound = true; MusECore::Event event = ev->event(); if (event.empty()) { prev_ev = i; ++i; continue; } int x = event.tick(); if (x < xx1) { prev_ev = i; ++i; continue; } if (x >= xx2) break; deselectItem(ev); // Indicate no undo, and do port controller values and clone parts. MusEGlobal::audio->msgDeleteEvent(event, curPart, false, true, true); delete (ev); i = items.erase(i); ev = *i; // Is there a previous item? if(prev_ev != items.end()) { // Have we reached the end of the list, or the end of the current part's items within the item list? // Then it's current part's last drawn item. EX is 'infinity'. (As far as the part knows. Another part may overlay later.) // Else EX is current item's tick. (By now, ((*i)->event() should be valid - it must be not empty to call tick()). (*prev_ev)->setEX(i == items.end() || ev->part() != curPart ? -1 : ev->event().tick()); } do_redraw = true; prev_ev = i; } if(do_redraw) redraw(); // Let songChanged handle the redraw upon SC_SELECTION. } //--------------------------------------------------------- // setTool //--------------------------------------------------------- void CtrlCanvas::setTool(int t) { if (tool == MusEGui::Tool(t)) return; tool = MusEGui::Tool(t); switch(tool) { case MusEGui::PencilTool: setCursor(QCursor(*pencilIcon, 4, 15)); break; case MusEGui::DrawTool: drawLineMode = false; break; default: setCursor(QCursor(Qt::ArrowCursor)); break; } } //--------------------------------------------------------- // pdrawItems //--------------------------------------------------------- void CtrlCanvas::pdrawItems(QPainter& p, const QRect& rect, const MusECore::MidiPart* part, bool velo, bool fg) { int x = rect.x() - 1; // compensate for 3 pixel line width int w = rect.width() + 2; int wh = height(); noEvents=true; if(velo) { noEvents=false; for(iCEvent i = items.begin(); i != items.end(); ++i) { CEvent* e = *i; // Draw selected part velocity events on top of unselected part events. if(e->part() != part) continue; MusECore::Event event = e->event(); int tick = mapx(event.tick() + e->part()->tick()); if (tick <= x) continue; if (tick > x+w) break; int y1 = wh - (e->val() * wh / 128); // fg means 'draw selected parts'. if(fg) { if(e->selected()) p.setPen(QPen(Qt::blue, 3)); else p.setPen(QPen(MusEGlobal::config.ctrlGraphFg, 3)); } else p.setPen(QPen(Qt::darkGray, 3)); p.drawLine(tick, wh, tick, y1); } } else { if(!part) return; MusECore::MidiTrack* mt = part->track(); MusECore::MidiPort* mp; int cnum = _cnum; bool is_drum_ctl = (mt->type() == MusECore::Track::DRUM) && (curDrumPitch >= 0) && ((_cnum & 0xff) == 0xff); if(is_drum_ctl) { // Default to track port if -1 and track channel if -1. int mport = MusEGlobal::drumMap[curDrumPitch].port; if(mport == -1) mport = mt->outPort(); mp = &MusEGlobal::midiPorts[mport]; cnum = (_cnum & ~0xff) | MusEGlobal::drumMap[curDrumPitch].anote; } else mp = &MusEGlobal::midiPorts[mt->outPort()]; MusECore::MidiController* mc = mp->midiController(cnum); int min; int max; int bias; if(cnum == MusECore::CTRL_PROGRAM) { min = 1; max = 128; bias = 0; } else { min = mc->minVal(); max = mc->maxVal(); bias = mc->bias(); } int x1 = rect.x(); int lval = MusECore::CTRL_VAL_UNKNOWN; bool selected = false; for (iCEvent i = items.begin(); i != items.end(); ++i) { noEvents=false; CEvent* e = *i; // Draw unselected part controller events (lines) on top of selected part events (bars). if(e->part() != part) continue; MusECore::Event ev = e->event(); // Draw drum controllers from another drum on top of ones from this drum. if(is_drum_ctl && ev.type() == MusECore::Controller && ev.dataA() != _didx) continue; int tick = mapx(!ev.empty() ? ev.tick() + e->part()->tick() : 0); int val = e->val(); int pval = val; if(cnum == MusECore::CTRL_PROGRAM) { if((val & 0xff) == 0xff) // What to do here? prog = 0xff should not be allowed, but may still be encountered. pval = 1; else pval = (val & 0x7f) + 1; } if (tick <= x) { if (val == MusECore::CTRL_VAL_UNKNOWN) lval = MusECore::CTRL_VAL_UNKNOWN; else { if(cnum == MusECore::CTRL_PROGRAM) lval = wh - ((pval - min - bias) * wh / (max - min)); else lval = wh - ((val - min - bias) * wh / (max - min)); } selected = e->selected(); continue; } if (tick > x+w) break; if (lval == MusECore::CTRL_VAL_UNKNOWN) { // fg means 'draw unselected parts'. if(!fg) p.fillRect(x1, 0, tick - x1, wh, Qt::darkGray); } else { if(fg) { p.setPen(Qt::gray); p.drawLine(x1, lval, tick, lval); } else p.fillRect(x1, lval, tick - x1, wh - lval, selected ? Qt::blue : MusEGlobal::config.ctrlGraphFg); } x1 = tick; if (val == MusECore::CTRL_VAL_UNKNOWN) lval = MusECore::CTRL_VAL_UNKNOWN; else { if(cnum == MusECore::CTRL_PROGRAM) lval = wh - ((pval - min - bias) * wh / (max - min)); else lval = wh - ((val - min - bias) * wh / (max - min)); } selected = e->selected(); } if (lval == MusECore::CTRL_VAL_UNKNOWN) { if(!fg) { p.fillRect(x1, 0, (x+w) - x1, wh, Qt::darkGray); } } else { if(fg) { p.setPen(Qt::gray); p.drawLine(x1, lval, x + w, lval); } else p.fillRect(x1, lval, (x+w) - x1, wh - lval, selected ? Qt::blue : MusEGlobal::config.ctrlGraphFg); } } } //--------------------------------------------------------- // pdrawExtraDrumCtrlItems //--------------------------------------------------------- void CtrlCanvas::pdrawExtraDrumCtrlItems(QPainter& p, const QRect& rect, const MusECore::MidiPart* part, int drum_ctl) { int x = rect.x() - 1; // compensate for 3 pixel line width int w = rect.width() + 2; int wh = height(); noEvents=true; { if(!part) return; MusECore::MidiTrack* mt = part->track(); MusECore::MidiPort* mp; int cnum = _cnum; bool is_drum_ctl = (mt->type() == MusECore::Track::DRUM) && (curDrumPitch >= 0) && ((_cnum & 0xff) == 0xff); if(is_drum_ctl) { // Default to track port if -1 and track channel if -1. int mport = MusEGlobal::drumMap[curDrumPitch].port; if(mport == -1) mport = mt->outPort(); mp = &MusEGlobal::midiPorts[mport]; cnum = (_cnum & ~0xff) | MusEGlobal::drumMap[curDrumPitch].anote; } else mp = &MusEGlobal::midiPorts[mt->outPort()]; MusECore::MidiController* mc = mp->midiController(cnum); int min; int max; int bias; if(cnum == MusECore::CTRL_PROGRAM) { min = 1; max = 128; bias = 0; } else { min = mc->minVal(); max = mc->maxVal(); bias = mc->bias(); } int x1 = rect.x(); int lval = MusECore::CTRL_VAL_UNKNOWN; //bool selected = false; for (iCEvent i = items.begin(); i != items.end(); ++i) { noEvents=false; CEvent* e = *i; // Draw unselected part controller events (lines) on top of selected part events (bars). if(e->part() != part) continue; MusECore::Event ev = e->event(); // Draw drum controllers from another drum on top of ones from this drum. // FIXME TODO Finish this off, not correct yet. if(drum_ctl == -1 && is_drum_ctl && ev.type() == MusECore::Controller && ev.dataA() != _didx) continue; if(drum_ctl != -1 && (!is_drum_ctl || (ev.type() == MusECore::Controller && ev.dataA() == _didx))) continue; int tick = mapx(!ev.empty() ? ev.tick() + e->part()->tick() : 0); int val = e->val(); int pval = val; if(cnum == MusECore::CTRL_PROGRAM) { if((val & 0xff) == 0xff) // What to do here? prog = 0xff should not be allowed, but may still be encountered. pval = 1; else pval = (val & 0x7f) + 1; } if (tick <= x) { if (val == MusECore::CTRL_VAL_UNKNOWN) lval = MusECore::CTRL_VAL_UNKNOWN; else { if(cnum == MusECore::CTRL_PROGRAM) lval = wh - ((pval - min - bias) * wh / (max - min)); else lval = wh - ((val - min - bias) * wh / (max - min)); } //selected = e->selected(); continue; } if (tick > x+w) break; if (lval != MusECore::CTRL_VAL_UNKNOWN) { p.setPen(Qt::gray); p.drawLine(x1, lval, tick, lval); } x1 = tick; if (val == MusECore::CTRL_VAL_UNKNOWN) lval = MusECore::CTRL_VAL_UNKNOWN; else { if(cnum == MusECore::CTRL_PROGRAM) lval = wh - ((pval - min - bias) * wh / (max - min)); else lval = wh - ((val - min - bias) * wh / (max - min)); } //selected = e->selected(); } if (lval != MusECore::CTRL_VAL_UNKNOWN) { p.setPen(Qt::gray); p.drawLine(x1, lval, x + w, lval); } } } //--------------------------------------------------------- // pdraw //--------------------------------------------------------- void CtrlCanvas::pdraw(QPainter& p, const QRect& rect) { if(!_controller) return; int x = rect.x() - 1; // compensate for 3 pixel line width int y = rect.y(); int w = rect.width() + 2; int h = rect.height(); //--------------------------------------------------- // draw Canvas Items //--------------------------------------------------- bool velo = (MusECore::midiControllerType(_controller->num()) == MusECore::MidiController::Velo); if(velo) { //--------------------------------------------------- // draw the grid and markers now - before velocity items //--------------------------------------------------- p.save(); View::pdraw(p, rect); p.restore(); int xp = mapx(pos[0]); if (xp >= x && xp < x+w) { p.setPen(Qt::red); p.drawLine(xp, y, xp, y+h); } xp = mapx(pos[1]); if (xp >= x && xp < x+w) { p.setPen(Qt::blue); p.drawLine(xp, y, xp, y+h); } xp = mapx(pos[2]); if (xp >= x && xp < x+w) { p.setPen(Qt::blue); p.drawLine(xp, y, xp, y+h); } } else // Draw non-fg non-velocity items for the current part pdrawItems(p, rect, curPart, false, false); for(MusECore::iPart ip = editor->parts()->begin(); ip != editor->parts()->end(); ++ip) { MusECore::MidiPart* part = (MusECore::MidiPart*)(ip->second); if(part == curPart || (filterTrack && part->track() != curTrack)) continue; // Draw items for all parts - other than current part pdrawItems(p, rect, part, velo, !velo); } // Special: Draw fg drum controller items for non-current selected drum, for the current part // FIXME TODO Finish this off, not correct yet. if(curPart && curPart->track() && curPart->track()->type() == MusECore::Track::DRUM && curDrumPitch >= 0 && ((_cnum & 0xff) == 0xff)) { // Default to track port if -1 and track channel if -1. int port = MusEGlobal::drumMap[curDrumPitch].port; if(port == -1) port = curPart->track()->outPort(); int anote = MusEGlobal::drumMap[curDrumPitch].anote; for(int i = 0; i < DRUM_MAPSIZE; ++i) { int iport = MusEGlobal::drumMap[i].port; if(iport == -1) iport = curPart->track()->outPort(); if(i != curDrumPitch && iport == port && MusEGlobal::drumMap[i].anote == anote) pdrawExtraDrumCtrlItems(p, rect, curPart, anote); } } if(velo) { // Draw fg velocity items for the current part pdrawItems(p, rect, curPart, true, true); } else { //--------------------------------------------------- // draw the grid and markers now - after non-velocity items //--------------------------------------------------- p.save(); View::pdraw(p, rect); p.restore(); int xp = mapx(pos[0]); if (xp >= x && xp < x+w) { p.setPen(Qt::red); p.drawLine(xp, y, xp, y+h); } xp = mapx(pos[1]); if (xp >= x && xp < x+w) { p.setPen(Qt::blue); p.drawLine(xp, y, xp, y+h); } xp = mapx(pos[2]); if (xp >= x && xp < x+w) { p.setPen(Qt::blue); p.drawLine(xp, y, xp, y+h); } } //--------------------------------------------------- // draw lasso //--------------------------------------------------- if (drag == DRAG_LASSO) { setPainter(p); p.setPen(Qt::blue); p.setBrush(Qt::NoBrush); p.drawRect(lasso); } } //--------------------------------------------------------- // drawOverlay //--------------------------------------------------------- void CtrlCanvas::drawOverlay(QPainter& p) { QString s(_controller ? _controller->name() : QString("")); //p.setFont(MusEGlobal::config.fonts[3]); // Use widget font instead. p.setFont(font()); p.setPen(Qt::black); //QFontMetrics fm(MusEGlobal::config.fonts[3]); // Use widget font metrics instead. //int y = fm.lineSpacing() + 2; int y = fontMetrics().lineSpacing() + 2; p.drawText(2, y, s); if (curDrumPitch==-2) { p.drawText(2 , y * 2, tr("Make the current part's track match the selected drumlist entry")); } else if (noEvents) p.drawText(2 , y * 2, tr("Drawing hint: Hold Ctrl to affect only existing events")); } //--------------------------------------------------------- // overlayRect // returns geometry of overlay rectangle //--------------------------------------------------------- QRect CtrlCanvas::overlayRect() const { //QFontMetrics fm(MusEGlobal::config.fonts[3]); // Use widget font metrics instead (and set a widget font) !!! QFontMetrics fm(fontMetrics()); QRect r(fm.boundingRect(_controller ? _controller->name() : QString(""))); int y = fm.lineSpacing() + 2; r.translate(2, y); if (curDrumPitch==-2) { QRect r2(fm.boundingRect(QString(tr("Make the current part's track match the selected drumlist entry")))); //r2.translate(width()/2-100, height()/2-10); r2.translate(2, y * 2); r |= r2; } else if (noEvents) { QRect r2(fm.boundingRect(QString(tr("Use pencil or line tool to draw new events")))); //r2.translate(width()/2-100, height()/2-10); r2.translate(2, y * 2); r |= r2; } return r; } //--------------------------------------------------------- // draw //--------------------------------------------------------- void CtrlCanvas::draw(QPainter& p, const QRect& rect) { drawTickRaster(p, rect.x(), rect.y(), rect.width(), rect.height(), editor->raster()); //--------------------------------------------------- // draw line tool //--------------------------------------------------- if ((tool == MusEGui::DrawTool) && drawLineMode) { p.setPen(Qt::black); p.drawLine(line1x, line1y, line2x, line2y); } } //--------------------------------------------------------- // setCurDrumPitch //--------------------------------------------------------- void CtrlCanvas::setCurDrumPitch(int instrument) { DrumEdit* drumedit = dynamic_cast(editor); if (drumedit == NULL || drumedit->old_style_drummap_mode()) curDrumPitch = instrument; else // new style drummap mode { // Crash protection by Tim. // FIXME: Still, drum list is blank, editor can't edit. Other values of instrument or curDrumPitch just crash too. // Seems only with drum tracks that were created by importing a midi file (then changed to use fluidsynth device?). if(instrument == -1) curDrumPitch = -1; else if (drumedit->get_instrument_map()[instrument].tracks.contains(curTrack)) curDrumPitch = drumedit->get_instrument_map()[instrument].pitch; else curDrumPitch = -2; // this means "invalid", but not "unused" } } void CtrlCanvas::curPartHasChanged(MusECore::Part*) { setCurTrackAndPart(); setCurDrumPitch(editor->curDrumInstrument()); songChanged(SC_EVENT_MODIFIED); } void CtrlCanvas::setPerNoteVeloMode(bool v) { if(v == _perNoteVeloMode) return; _perNoteVeloMode = v; if(_cnum == MusECore::CTRL_VELOCITY) updateItems(); } } // namespace MusEGui