1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 LabelTextHandle.cpp
6 
7 Paul Licameli split from TrackPanel.cpp
8 
9 **********************************************************************/
10 
11 
12 #include "LabelTextHandle.h"
13 
14 #include "LabelTrackView.h"
15 
16 #include "../../../HitTestResult.h"
17 #include "../../../LabelTrack.h"
18 #include "../../../ProjectAudioIO.h"
19 #include "../../../ProjectHistory.h"
20 #include "../../../RefreshCode.h"
21 #include "../../../SelectionState.h"
22 #include "../../../TrackPanelMouseEvent.h"
23 #include "ViewInfo.h"
24 #include "../../../../images/Cursors.h"
25 
26 #include <wx/clipbrd.h>
27 
LabelTextHandle(const std::shared_ptr<LabelTrack> & pLT,int labelNum)28 LabelTextHandle::LabelTextHandle
29 ( const std::shared_ptr<LabelTrack> &pLT, int labelNum )
30    : mpLT{ pLT }
31    , mLabelNum{ labelNum }
32 {
33 }
34 
Enter(bool,AudacityProject *)35 void LabelTextHandle::Enter(bool, AudacityProject *)
36 {
37 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
38    mChangeHighlight = RefreshCode::RefreshCell;
39 #endif
40 }
41 
HitPreview()42 HitTestPreview LabelTextHandle::HitPreview()
43 {
44    static auto ibeamCursor =
45       ::MakeCursor(wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16);
46    return {
47       XO("Click to edit label text"),
48       ibeamCursor.get()
49    };
50 }
51 
HitTest(std::weak_ptr<LabelTextHandle> & holder,const wxMouseState & state,const std::shared_ptr<LabelTrack> & pLT)52 UIHandlePtr LabelTextHandle::HitTest
53 (std::weak_ptr<LabelTextHandle> &holder,
54  const wxMouseState &state, const std::shared_ptr<LabelTrack> &pLT)
55 {
56    // If Control is down, let the select handle be hit instead
57    int labelNum;
58    if (!state.ControlDown() &&
59        (labelNum =
60           LabelTrackView::OverATextBox(*pLT, state.m_x, state.m_y) ) >= 0) {
61       auto result = std::make_shared<LabelTextHandle>( pLT, labelNum );
62       result = AssignUIHandlePtr(holder, result);
63       return result;
64    }
65 
66    return {};
67 }
68 
~LabelTextHandle()69 LabelTextHandle::~LabelTextHandle()
70 {
71 }
72 
HandleTextClick(AudacityProject & project,const wxMouseEvent & evt)73 void LabelTextHandle::HandleTextClick(AudacityProject &project, const wxMouseEvent & evt)
74 {
75    auto pTrack = mpLT.lock();
76    if (!pTrack)
77       return;
78 
79    auto &view = LabelTrackView::Get( *pTrack );
80    if (evt.ButtonDown())
81    {
82       const auto selIndex = LabelTrackView::OverATextBox( *pTrack, evt.m_x, evt.m_y );
83       if ( selIndex != -1 ) {
84          if (evt.LeftDown()) {
85             mRightDragging = false;
86             // Find the NEW drag end
87             auto position = view.FindCursorPosition(selIndex, evt.m_x );
88 
89             // Anchor shift-drag at the farther end of the previous highlight
90             // that is farther from the click, on Mac, for consistency with
91             // its text editors, but on the others, re-use the previous
92             // anchor.
93             auto initial = view.GetInitialCursorPosition();
94             if (evt.ShiftDown()) {
95 #ifdef __WXMAC__
96                // Set the drag anchor at the end of the previous selection
97                // that is farther from the NEW drag end
98                const auto current = view.GetCurrentCursorPosition();
99                if ( abs( position - current ) > abs( position - initial ) )
100                   initial = current;
101 #else
102                // initial position remains as before
103 #endif
104             }
105             else
106                initial = position;
107 
108             view.SetTextSelection(selIndex, initial, position );
109          }
110          else
111          {
112             if (!view.IsTextSelected(project))
113             {
114                auto position = view.FindCursorPosition(selIndex, evt.m_x);
115                view.SetTextSelection(selIndex, position, position);
116             }
117             // Actually this might be right or middle down
118             mRightDragging = true;
119          }
120          // Middle click on GTK: paste from primary selection
121 #if defined(__WXGTK__) && (HAVE_GTK)
122          if (evt.MiddleDown()) {
123             // Check for a click outside of the selected label's text box; in this
124             // case PasteSelectedText() will start a NEW label at the click
125             // location
126             if (!LabelTrackView::OverTextBox(&labelStruct, evt.m_x, evt.m_y))
127                view.ResetTextSelection();
128             double t = zoomInfo.PositionToTime(evt.m_x, r.x);
129             newSel = SelectedRegion(t, t);
130          }
131 #endif
132       }
133 #if defined(__WXGTK__) && (HAVE_GTK)
134       if (evt.MiddleDown()) {
135          // Paste text, making a NEW label if none is selected.
136          wxTheClipboard->UsePrimarySelection(true);
137          view.PasteSelectedText(project, newSel.t0(), newSel.t1());
138          wxTheClipboard->UsePrimarySelection(false);
139       }
140 #endif
141    }
142 }
143 
HandlesRightClick()144 bool LabelTextHandle::HandlesRightClick()
145 {
146    return true;
147 }
148 
Click(const TrackPanelMouseEvent & evt,AudacityProject * pProject)149 UIHandle::Result LabelTextHandle::Click
150 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
151 {
152    auto pLT = mpLT.lock();
153    if (!pLT)
154       return RefreshCode::Cancelled;
155 
156    auto result = LabelDefaultClickHandle::Click( evt, pProject );
157 
158    const wxMouseEvent &event = evt.event;
159    auto &viewInfo = ViewInfo::Get( *pProject );
160 
161    HandleTextClick(*pProject, event);
162 
163    return result | RefreshCode::RefreshCell;
164 }
165 
HandleTextDragRelease(AudacityProject & project,const wxMouseEvent & evt)166 void LabelTextHandle::HandleTextDragRelease(
167    AudacityProject &project, const wxMouseEvent & evt)
168 {
169    auto pTrack = mpLT.lock();
170    if (!pTrack)
171       return;
172    auto &view = LabelTrackView::Get( *pTrack );
173 
174    if(evt.LeftUp())
175    {
176 #if 0
177       // AWD: Due to wxWidgets bug #7491 (fix not ported to 2.8 branch) we
178       // should never write the primary selection. We can enable this block
179       // when we move to the 3.0 branch (or if a fixed 2.8 version is released
180       // and we can do a runtime version check)
181 #if defined (__WXGTK__) && defined (HAVE_GTK)
182       // On GTK, if we just dragged out a text selection, set the primary
183       // selection
184       if (mInitialCursorPos != mCurrentCursorPos) {
185          wxTheClipboard->UsePrimarySelection(true);
186          CopySelectedText();
187          wxTheClipboard->UsePrimarySelection(false);
188       }
189 #endif
190 #endif
191 
192       return;
193    }
194 
195    if(evt.Dragging())
196    {
197       auto index = view.GetTextEditIndex(project);
198       if (!mRightDragging && index != -1)
199          // Update drag end
200          view.SetCurrentCursorPosition(view.FindCursorPosition(index, evt.m_x ));
201       return;
202    }
203 
204    if (evt.RightUp())
205    {
206       auto index = view.GetTextEditIndex(project);
207       if(index != -1 &&
208          LabelTrackView::OverTextBox(pTrack->GetLabel(index), evt.m_x, evt.m_y))
209       {
210          // popup menu for editing
211          // TODO: handle context menus via CellularPanel?
212          view.ShowContextMenu( project );
213       }
214    }
215 
216    return;
217 }
218 
Drag(const TrackPanelMouseEvent & evt,AudacityProject * pProject)219 UIHandle::Result LabelTextHandle::Drag
220 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
221 {
222    auto &project = *pProject;
223    using namespace RefreshCode;
224    auto result = LabelDefaultClickHandle::Drag( evt, pProject );
225 
226    const wxMouseEvent &event = evt.event;
227    auto pLT = TrackList::Get( *pProject ).Lock(mpLT);
228    if(pLT)
229       HandleTextDragRelease( project, event );
230 
231    // locate the initial mouse position
232    if (event.LeftIsDown()) {
233       if (mLabelTrackStartXPos == -1) {
234          mLabelTrackStartXPos = event.m_x;
235          mLabelTrackStartYPos = event.m_y;
236 
237          auto pView = pLT ? &LabelTrackView::Get( *pLT ) : nullptr;
238          if (pLT && (pView->GetTextEditIndex( project ) != -1) &&
239              LabelTrackView::OverTextBox(
240                pLT->GetLabel(pView->GetTextEditIndex( project )),
241                mLabelTrackStartXPos,
242                mLabelTrackStartYPos))
243             mLabelTrackStartYPos = -1;
244       }
245       // if initial mouse position in the text box
246       // then only drag text
247       if (mLabelTrackStartYPos == -1)
248          result |= RefreshCell;
249    }
250 
251    return result;
252 }
253 
Preview(const TrackPanelMouseState &,AudacityProject *)254 HitTestPreview LabelTextHandle::Preview
255 (const TrackPanelMouseState &, AudacityProject *)
256 {
257    return HitPreview();
258 }
259 
Release(const TrackPanelMouseEvent & evt,AudacityProject * pProject,wxWindow * pParent)260 UIHandle::Result LabelTextHandle::Release
261 (const TrackPanelMouseEvent &evt, AudacityProject *pProject,
262  wxWindow *pParent)
263 {
264    auto result = LabelDefaultClickHandle::Release( evt, pProject, pParent );
265 
266    // Only selected a part of a text string and changed track selectedness.
267    // No undoable effects.
268 
269    const wxMouseEvent &event = evt.event;
270    auto pLT = TrackList::Get( *pProject ).Lock(mpLT);
271    if (pLT)
272       HandleTextDragRelease( *pProject, event );
273 
274    // handle mouse left button up
275    if (event.LeftUp())
276       mLabelTrackStartXPos = -1;
277 
278    return result | RefreshCode::RefreshNone;
279 }
280 
Cancel(AudacityProject * pProject)281 UIHandle::Result LabelTextHandle::Cancel( AudacityProject *pProject )
282 {
283    auto result = LabelDefaultClickHandle::Cancel( pProject );
284    return result | RefreshCode::RefreshAll;
285 }
286