1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Rosegarden
5 A MIDI and audio sequencer and musical notation editor.
6 Copyright 2000-2021 the Rosegarden development team.
7
8 Other copyrights also apply to some parts of this work. Please
9 see the AUTHORS file and individual file headers for details.
10
11 This program is free software; you can redistribute it and/or
12 modify it under the terms of the GNU General Public License as
13 published by the Free Software Foundation; either version 2 of the
14 License, or (at your option) any later version. See the file
15 COPYING included with this distribution for more information.
16 */
17
18
19 #include "SegmentPencil.h"
20
21 #include "misc/Debug.h"
22 #include "misc/Strings.h"
23 #include "gui/general/ClefIndex.h"
24 #include "base/NotationTypes.h"
25 #include "base/Segment.h"
26 #include "base/SnapGrid.h"
27 #include "base/Track.h"
28 #include "commands/segment/SegmentInsertCommand.h"
29 #include "CompositionView.h"
30 #include "document/RosegardenDocument.h"
31 #include "gui/general/BaseTool.h"
32 #include "gui/general/GUIPalette.h"
33 #include "gui/general/RosegardenScrollView.h"
34 #include "SegmentTool.h"
35 #include "document/Command.h"
36 #include "document/CommandHistory.h"
37
38 #include <QCursor>
39 #include <QEvent>
40 #include <QPoint>
41 #include <QRect>
42 #include <QString>
43 #include <QMouseEvent>
44 #include <QKeyEvent>
45
46 namespace Rosegarden
47 {
48
ToolName()49 QString SegmentPencil::ToolName() { return "segmentpencil"; }
50
SegmentPencil(CompositionView * c,RosegardenDocument * d)51 SegmentPencil::SegmentPencil(CompositionView *c, RosegardenDocument *d)
52 : SegmentTool(c, d),
53 m_newRect(false),
54 m_pressX(0),
55 m_lastMousePos(),
56 m_startTime(0),
57 m_endTime(0)
58 {
59 //RG_DEBUG << "SegmentPencil()\n";
60 }
61
ready()62 void SegmentPencil::ready()
63 {
64 m_canvas->viewport()->setCursor(Qt::IBeamCursor);
65 setContextHelpFor(QPoint(0, 0));
66 }
67
stow()68 void SegmentPencil::stow()
69 {
70 }
71
mousePressEvent(QMouseEvent * e)72 void SegmentPencil::mousePressEvent(QMouseEvent *e)
73 {
74 // Let the baseclass have a go.
75 SegmentTool::mousePressEvent(e);
76
77 // We only care about the left and middle mouse buttons.
78 // SegmentSelector might send us a middle press.
79 if (e->button() != Qt::LeftButton &&
80 e->button() != Qt::MiddleButton)
81 return;
82
83 // No need to propagate.
84 e->accept();
85
86 // is user holding Ctrl+Alt? (ugly, but we are running short on available
87 // modifiers; Alt is grabbed by the window manager, and right clicking, my
88 // (dmm) original idea, is grabbed by the context menu, so let's see how
89 // this goes over
90 // ??? Why not just set this to true? The use case is starting a
91 // pencil click/drag on top of an existing segment. If the
92 // user wants to draw a segment on top of a segment, just let
93 // them. Maybe this was an issue when segments could overlap?
94 bool pencilAnyway = ((e->modifiers() & Qt::AltModifier) != 0 &&
95 (e->modifiers() & Qt::ControlModifier) != 0);
96
97 m_newRect = false;
98
99 QPoint pos = m_canvas->viewportToContents(e->pos());
100
101 // Check if mouse click was on a rect
102 //
103 ChangingSegmentPtr item = m_canvas->getModel()->getSegmentAt(pos);
104
105 // If user clicked a rect, and pencilAnyway is false, then there's nothing
106 // left to do here
107 if (item) {
108 if (!pencilAnyway) return ;
109 }
110
111 // make new item
112
113 SnapGrid &snapGrid = m_canvas->grid();
114
115 setSnapTime(e, SnapGrid::SnapToBar);
116
117 int trackPosition = snapGrid.getYBin(pos.y());
118
119 // Don't do anything if the user clicked beyond the track buttons
120 //
121 if (trackPosition >= (int)m_doc->getComposition().getNbTracks())
122 return ;
123
124 Track *t = m_doc->getComposition().getTrackByPosition(trackPosition);
125 if (!t)
126 return ;
127
128 TrackId trackId = t->getId();
129
130 // Save the mouse X as the original Press point
131 m_pressX = pos.x();
132
133 m_startTime = snapGrid.snapX(m_pressX, SnapGrid::SnapLeft);
134 m_endTime = snapGrid.snapX(m_pressX, SnapGrid::SnapRight);
135
136 // Don't allow a length smaller than the smallest note
137 if (m_endTime - m_startTime < Note(Note::Shortest).getDuration())
138 m_endTime = m_startTime + Note(Note::Shortest).getDuration();
139
140 int multiple =
141 m_doc->getComposition().getMaxContemporaneousSegmentsOnTrack(trackId);
142 if (multiple < 1)
143 multiple = 1;
144
145 QRect tmpRect;
146 tmpRect.setLeft(lround(snapGrid.getRulerScale()->
147 getXForTime(m_startTime)));
148 tmpRect.setRight(lround(snapGrid.getRulerScale()->
149 getXForTime(m_endTime)));
150 tmpRect.setY(snapGrid.getYBinCoordinate(trackPosition) + 1);
151 tmpRect.setHeight(snapGrid.getYSnap() * multiple - 2);
152
153 m_canvas->setNewSegmentColor(
154 m_doc->getComposition().getSegmentColourMap().
155 getColour(t->getColor()));
156 m_canvas->drawNewSegment(tmpRect);
157
158 m_newRect = true;
159
160 setContextHelpFor(pos, e->modifiers());
161
162 m_canvas->update(tmpRect);
163 }
164
mouseReleaseEvent(QMouseEvent * e)165 void SegmentPencil::mouseReleaseEvent(QMouseEvent *e)
166 {
167 // Have to allow middle button for SegmentSelector's middle
168 // button feature to work.
169 if (e->button() != Qt::LeftButton &&
170 e->button() != Qt::MiddleButton)
171 return;
172
173 // No need to propagate.
174 e->accept();
175
176 QPoint pos = m_canvas->viewportToContents(e->pos());
177
178 if (m_newRect) {
179
180 QRect tmpRect = m_canvas->getNewSegmentRect();
181
182 int trackPosition = m_canvas->grid().getYBin(tmpRect.y());
183 Track *track =
184 m_doc->getComposition().getTrackByPosition(trackPosition);
185
186 SegmentInsertCommand *command =
187 new SegmentInsertCommand(m_doc, track->getId(),
188 m_startTime, m_endTime);
189
190 m_newRect = false;
191
192 CommandHistory::getInstance()->addCommand(command);
193
194 // add the SegmentItem by hand, instead of allowing the usual
195 // update mechanism to spot it. This way we can select the
196 // segment as we add it; otherwise we'd have no way to know
197 // that the segment was created by this tool rather than by
198 // e.g. a simple file load
199
200 Segment *segment = command->getSegment();
201
202 // add a clef to the start of the segment (tracks initialize to a
203 // default of 0 for this property, so treble will be the default if it
204 // is not specified elsewhere)
205 segment->insert(clefIndexToClef(track->getClef()).getAsEvent
206 (segment->getStartTime()));
207
208 //!!! Should not a default key be inserted here equally in order to
209 // have the "hide redundant clefs and keys" mechanism working
210 // on the segments using the default key ?
211
212 segment->setTranspose(track->getTranspose());
213 segment->setColourIndex(track->getColor());
214 segment->setLowestPlayable(track->getLowestPlayable());
215 segment->setHighestPlayable(track->getHighestPlayable());
216
217 std::string label = track->getPresetLabel();
218 if (label != "") {
219 segment->setLabel( track->getPresetLabel().c_str() );
220 }
221
222 m_canvas->getModel()->clearSelected();
223 m_canvas->getModel()->setSelected(segment);
224 m_canvas->getModel()->selectionHasChanged();
225
226 m_canvas->hideNewSegment();
227 m_canvas->slotUpdateAll();
228 }
229
230 setContextHelpFor(pos, e->modifiers());
231 }
232
mouseMoveEvent(QMouseEvent * e)233 int SegmentPencil::mouseMoveEvent(QMouseEvent *e)
234 {
235 // No need to propagate.
236 e->accept();
237
238 QPoint pos = m_canvas->viewportToContents(e->pos());
239 m_lastMousePos = pos;
240
241 if (!m_newRect) {
242 setContextHelpFor(pos);
243 return NO_FOLLOW;
244 }
245
246 // Display help for the Shift key.
247 setContextHelpFor(pos, e->modifiers());
248
249 QRect tmpRect = m_canvas->getNewSegmentRect();
250
251 SnapGrid &snapGrid = m_canvas->grid();
252
253 setSnapTime(e, SnapGrid::SnapToBar);
254
255 int mouseX = pos.x();
256
257 // if mouse X is to the right of the original Press point
258 if (mouseX >= m_pressX) {
259 m_startTime = snapGrid.snapX(m_pressX, SnapGrid::SnapLeft);
260 m_endTime = snapGrid.snapX(mouseX, SnapGrid::SnapRight);
261
262 // Make sure the segment is never smaller than the smallest note.
263 if (m_endTime - m_startTime < Note(Note::Shortest).getDuration())
264 m_endTime = m_startTime + Note(Note::Shortest).getDuration();
265 } else { // we are to the left of the original Press point
266 m_startTime = snapGrid.snapX(mouseX, SnapGrid::SnapLeft);
267 m_endTime = snapGrid.snapX(m_pressX, SnapGrid::SnapRight);
268
269 // Make sure the segment is never smaller than the smallest note.
270 if (m_endTime - m_startTime < Note(Note::Shortest).getDuration())
271 m_startTime = m_endTime - Note(Note::Shortest).getDuration();
272 }
273
274 int leftX = snapGrid.getRulerScale()->getXForTime(m_startTime);
275 int rightX = snapGrid.getRulerScale()->getXForTime(m_endTime);
276
277 // Adjust the rectangle to go from leftX to rightX
278 tmpRect.setLeft(leftX);
279 tmpRect.setRight(rightX);
280
281 m_canvas->drawNewSegment(tmpRect);
282 return FOLLOW_HORIZONTAL;
283 }
284
keyPressEvent(QKeyEvent * e)285 void SegmentPencil::keyPressEvent(QKeyEvent *e)
286 {
287 // In case shift was pressed, update the context help.
288 setContextHelpFor(m_lastMousePos, e->modifiers());
289 }
290
keyReleaseEvent(QKeyEvent * e)291 void SegmentPencil::keyReleaseEvent(QKeyEvent *e)
292 {
293 // In case shift was released, update the context help.
294 setContextHelpFor(m_lastMousePos, e->modifiers());
295 }
296
setContextHelpFor(QPoint pos,Qt::KeyboardModifiers modifiers)297 void SegmentPencil::setContextHelpFor(QPoint pos,
298 Qt::KeyboardModifiers modifiers)
299 {
300 // if we're drawing a segment...
301 if (m_newRect)
302 {
303 const bool shift = ((modifiers & Qt::ShiftModifier) != 0);
304
305 // If shift isn't being held down
306 if (!shift)
307 setContextHelp(tr("Hold Shift to avoid snapping to bar lines"));
308 else
309 clearContextHelp();
310
311 return;
312 }
313
314 // Mouse is hovering.
315
316 // Audio Track
317
318 int trackPosition = m_canvas->grid().getYBin(pos.y());
319
320 if (trackPosition < (int)m_doc->getComposition().getNbTracks()) {
321 Track *track = m_doc->getComposition().getTrackByPosition(trackPosition);
322 if (track) {
323 InstrumentId id = track->getInstrument();
324 // If this is an audio instrument
325 if (id >= AudioInstrumentBase && id < MidiInstrumentBase) {
326 setContextHelp(tr("Record or drop audio here"));
327 return;
328 }
329 }
330 }
331
332 // MIDI Track
333
334 setContextHelp(tr("Click and drag to draw an empty segment. Control+Alt click and drag to draw in overlap mode."));
335 }
336
337 }
338