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