1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 TrackInfo.cpp
6
7 Paul Licameli split from TrackPanel.cpp
8
9 ********************************************************************//*!
10
11 \namespace TrackInfo
12 \brief
13 Functions for drawing the track control panel, which is shown to the side
14 of a track
15 It has the menus, pan and gain controls displayed in it.
16 So "Info" is somewhat a misnomer. Should possibly be "TrackControls".
17
18 It maintains global slider widget instances that are reparented and
19 repositioned as needed for drawing and interaction with the user,
20 interoperating with the custom panel subdivision implemented in CellularPanel
21 and avoiding wxWidgets sizers
22
23 If we'd instead coded it as a wxWindow, we would have an instance
24 of this class for each track displayed.
25
26 **********************************************************************/
27
28
29 #include "TrackInfo.h"
30
31 #include <wx/app.h>
32 #include <wx/dc.h>
33 #include <wx/frame.h>
34
35 #include "AColor.h"
36 #include "AllThemeResources.h"
37 #include "Prefs.h"
38 #include "Project.h"
39 #include "Theme.h"
40 #include "Track.h"
41 #include "TrackPanelDrawingContext.h"
42 #include "ViewInfo.h"
43 #include "prefs/TracksBehaviorsPrefs.h"
44 #include "tracks/ui/TrackView.h"
45
46 // Subscribe to preference changes to update static variables
47 struct Settings : PrefsListener {
48 wxString gSoloPref;
49 wxFont gFont;
50
51 bool mInitialized{ false };
52
UpdatePrefsSettings53 void UpdatePrefs() override
54 {
55 gSoloPref = TracksBehaviorsSolo.Read();
56
57 // Calculation of best font size depends on language, so it should be redone in case
58 // the language preference changed.
59
60 // wxWidgets seems to need a window to do this portably.
61 if ( !wxTheApp )
62 return;
63 auto window = wxTheApp->GetTopWindow();
64 if ( !window )
65 return;
66
67 int fontSize = 10;
68 gFont.Create(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
69
70 int allowableWidth =
71 // PRL: was it correct to include the margin?
72 ( kTrackInfoWidth + kLeftMargin )
73 - 2; // 2 to allow for left/right borders
74 int textWidth;
75 do {
76 gFont.SetPointSize(fontSize);
77 window->GetTextExtent(_("Stereo, 999999Hz"),
78 &textWidth, nullptr, nullptr, nullptr, &gFont);
79 fontSize--;
80 } while (textWidth >= allowableWidth);
81
82 mInitialized = true;
83 }
84 };
85
settings()86 static Settings &settings()
87 {
88 static Settings theSettings;
89 if ( !theSettings.mInitialized )
90 theSettings.UpdatePrefs();
91 return theSettings;
92 }
93
HasSoloButton()94 bool TrackInfo::HasSoloButton()
95 {
96 return settings().gSoloPref != wxT("None");
97 }
98
99 #define RANGE(array) (array), (array) + sizeof(array)/sizeof(*(array))
100 using TCPLine = TrackInfo::TCPLine;
101 using TCPLines = TrackInfo::TCPLines;
102
commonTrackTCPLines()103 static const TCPLines &commonTrackTCPLines()
104 {
105 static const TCPLines theLines{
106 #ifdef EXPERIMENTAL_DA
107
108 { TCPLine::kItemBarButtons, kTrackInfoBtnSize, 4,
109 &TrackInfo::CloseTitleDrawFunction },
110
111 #else
112
113 { TCPLine::kItemBarButtons, kTrackInfoBtnSize, 0,
114 &TrackInfo::CloseTitleDrawFunction },
115
116 #endif
117 };
118 return theLines;
119 }
120
121 #include "tracks/ui/CommonTrackControls.h"
StaticTCPLines()122 const TCPLines &CommonTrackControls::StaticTCPLines()
123 {
124 return commonTrackTCPLines();
125 }
126
127 namespace {
128
totalTCPLines(const TCPLines & lines,bool omitLastExtra)129 int totalTCPLines( const TCPLines &lines, bool omitLastExtra )
130 {
131 int total = 0;
132 int lastExtra = 0;
133 for ( const auto line : lines ) {
134 lastExtra = line.extraSpace;
135 total += line.height + lastExtra;
136 }
137 if (omitLastExtra)
138 total -= lastExtra;
139 return total;
140 }
141 }
142
143 // return y value and height
144 std::pair< int, int >
CalcItemY(const TCPLines & lines,unsigned iItem)145 TrackInfo::CalcItemY( const TCPLines &lines, unsigned iItem )
146 {
147 int y = 0;
148 auto pLines = lines.begin();
149 while ( pLines != lines.end() &&
150 0 == (pLines->items & iItem) ) {
151 y += pLines->height + pLines->extraSpace;
152 ++pLines;
153 }
154 int height = 0;
155 if ( pLines != lines.end() )
156 height = pLines->height;
157 return { y, height };
158 }
159
160 namespace {
161
162 // Items for the bottom of the panel, listed bottom-upwards
163 // As also with the top items, the extra space is below the item
164 const TrackInfo::TCPLine defaultCommonTrackTCPBottomLines[] = {
165 // The '0' avoids impinging on bottom line of TCP
166 // Use -1 if you do want to do so.
167 { TCPLine::kItemSyncLock | TCPLine::kItemMinimize, kTrackInfoBtnSize, 0,
168 &TrackInfo::MinimizeSyncLockDrawFunction },
169 };
170 TCPLines commonTrackTCPBottomLines{ RANGE(defaultCommonTrackTCPBottomLines) };
171
172 // return y value and height
CalcBottomItemY(const TCPLines & lines,unsigned iItem,int height)173 std::pair< int, int > CalcBottomItemY
174 ( const TCPLines &lines, unsigned iItem, int height )
175 {
176 int y = height;
177 auto pLines = lines.begin();
178 while ( pLines != lines.end() &&
179 0 == (pLines->items & iItem) ) {
180 y -= pLines->height + pLines->extraSpace;
181 ++pLines;
182 }
183 if (pLines != lines.end())
184 y -= (pLines->height + pLines->extraSpace );
185 return { y, pLines->height };
186 }
187
188 }
189
GetTCPLines() const190 const TCPLines &CommonTrackControls::GetTCPLines() const
191 {
192 return commonTrackTCPLines();
193 }
194
MinimumTrackHeight()195 unsigned TrackInfo::MinimumTrackHeight()
196 {
197 unsigned height = 0;
198 if (!commonTrackTCPLines().empty())
199 height += commonTrackTCPLines().front().height;
200 if (!commonTrackTCPBottomLines.empty())
201 height += commonTrackTCPBottomLines.front().height;
202 // + 1 prevents the top item from disappearing for want of enough space,
203 // according to the rules in HideTopItem.
204 return height + kVerticalPadding + 1;
205 }
206
HideTopItem(const wxRect & rect,const wxRect & subRect,int allowance)207 bool TrackInfo::HideTopItem( const wxRect &rect, const wxRect &subRect,
208 int allowance ) {
209 auto limit = CalcBottomItemY
210 ( commonTrackTCPBottomLines, TCPLine::kHighestBottomItem, rect.height).first;
211 // Return true if the rectangle is even touching the limit
212 // without an overlap. That was the behavior as of 2.1.3.
213 return subRect.y + subRect.height - allowance >= rect.y + limit;
214 }
215
DrawItems(TrackPanelDrawingContext & context,const wxRect & rect,const Track & track)216 void TrackInfo::DrawItems
217 ( TrackPanelDrawingContext &context,
218 const wxRect &rect, const Track &track )
219 {
220 auto &trackControl = static_cast<const CommonTrackControls&>(
221 TrackControls::Get( track ) );
222 const auto &topLines = trackControl.GetTCPLines();
223 const auto &bottomLines = commonTrackTCPBottomLines;
224 DrawItems
225 ( context, rect, &track, topLines, bottomLines );
226 }
227
DrawItems(TrackPanelDrawingContext & context,const wxRect & rect,const Track * pTrack,const std::vector<TCPLine> & topLines,const std::vector<TCPLine> & bottomLines)228 void TrackInfo::DrawItems
229 ( TrackPanelDrawingContext &context,
230 const wxRect &rect, const Track *pTrack,
231 const std::vector<TCPLine> &topLines, const std::vector<TCPLine> &bottomLines )
232 {
233 auto dc = &context.dc;
234 TrackInfo::SetTrackInfoFont(dc);
235 dc->SetTextForeground(theTheme.Colour(clrTrackPanelText));
236
237 {
238 int yy = 0;
239 for ( const auto &line : topLines ) {
240 wxRect itemRect{
241 rect.x, rect.y + yy,
242 rect.width, line.height
243 };
244 if ( !TrackInfo::HideTopItem( rect, itemRect ) &&
245 line.drawFunction )
246 line.drawFunction( context, itemRect, pTrack );
247 yy += line.height + line.extraSpace;
248 }
249 }
250 {
251 int yy = rect.height;
252 for ( const auto &line : bottomLines ) {
253 yy -= line.height + line.extraSpace;
254 if ( line.drawFunction ) {
255 wxRect itemRect{
256 rect.x, rect.y + yy,
257 rect.width, line.height
258 };
259 line.drawFunction( context, itemRect, pTrack );
260 }
261 }
262 }
263 }
264
265 #include "tracks/ui/TrackButtonHandles.h"
DrawCloseButton(TrackPanelDrawingContext & context,const wxRect & bev,const Track * pTrack,ButtonHandle * target)266 void TrackInfo::DrawCloseButton(
267 TrackPanelDrawingContext &context, const wxRect &bev,
268 const Track *pTrack, ButtonHandle *target )
269 {
270 auto dc = &context.dc;
271 bool selected = pTrack ? pTrack->GetSelected() : true;
272 bool hit = target && target->GetTrack().get() == pTrack;
273 bool captured = hit && target->IsClicked();
274 bool down = captured && bev.Contains( context.lastState.GetPosition());
275 AColor::Bevel2(*dc, !down, bev, selected, hit );
276
277 #ifdef EXPERIMENTAL_THEMING
278 wxPen pen( theTheme.Colour( clrTrackPanelText ));
279 dc->SetPen( pen );
280 #else
281 dc->SetPen(*wxBLACK_PEN);
282 #endif
283 bev.Inflate( -1, -1 );
284 // Draw the "X"
285 const int s = 6;
286
287 int ls = bev.x + ((bev.width - s) / 2);
288 int ts = bev.y + ((bev.height - s) / 2);
289 int rs = ls + s;
290 int bs = ts + s;
291
292 AColor::Line(*dc, ls, ts, rs, bs);
293 AColor::Line(*dc, ls + 1, ts, rs + 1, bs);
294 AColor::Line(*dc, rs, ts, ls, bs);
295 AColor::Line(*dc, rs + 1, ts, ls + 1, bs);
296 // bev.Inflate(-1, -1);
297 }
298
CloseTitleDrawFunction(TrackPanelDrawingContext & context,const wxRect & rect,const Track * pTrack)299 void TrackInfo::CloseTitleDrawFunction
300 ( TrackPanelDrawingContext &context,
301 const wxRect &rect, const Track *pTrack )
302 {
303 auto dc = &context.dc;
304 bool selected = pTrack ? pTrack->GetSelected() : true;
305 {
306 wxRect bev = rect;
307 GetCloseBoxHorizontalBounds( rect, bev );
308 auto target = dynamic_cast<CloseButtonHandle*>( context.target.get() );
309 DrawCloseButton( context, bev, pTrack, target );
310 }
311
312 {
313 wxRect bev = rect;
314 GetTitleBarHorizontalBounds( rect, bev );
315 auto target = dynamic_cast<MenuButtonHandle*>( context.target.get() );
316 bool hit = target && target->GetTrack().get() == pTrack;
317 bool captured = hit && target->IsClicked();
318 bool down = captured && bev.Contains( context.lastState.GetPosition());
319 wxString titleStr =
320 pTrack ? pTrack->GetName() : _("Name");
321
322 //bev.Inflate(-1, -1);
323 AColor::Bevel2(*dc, !down, bev, selected, hit);
324
325 // Draw title text
326 SetTrackInfoFont(dc);
327
328 // Bug 1660 The 'k' of 'Audio Track' was being truncated.
329 // Constant of 32 found by counting pixels on a windows machine.
330 // I believe it's the size of the X close button + the size of the
331 // drop down arrow.
332 int allowableWidth = rect.width - 32;
333
334 wxCoord textWidth, textHeight;
335 dc->GetTextExtent(titleStr, &textWidth, &textHeight);
336 while (textWidth > allowableWidth) {
337 titleStr = titleStr.Left(titleStr.length() - 1);
338 dc->GetTextExtent(titleStr, &textWidth, &textHeight);
339 }
340
341 // Pop-up triangle
342 #ifdef EXPERIMENTAL_THEMING
343 wxColour c = theTheme.Colour( clrTrackPanelText );
344 #else
345 wxColour c = *wxBLACK;
346 #endif
347
348 // wxGTK leaves little scraps (antialiasing?) of the
349 // characters if they are repeatedly drawn. This
350 // happens when holding down mouse button and moving
351 // in and out of the title bar. So clear it first.
352 // AColor::MediumTrackInfo(dc, t->GetSelected());
353 // dc->DrawRectangle(bev);
354
355 dc->SetTextForeground( c );
356 dc->SetTextBackground( wxTRANSPARENT );
357 dc->DrawText(titleStr, bev.x + 2, bev.y + (bev.height - textHeight) / 2);
358
359
360
361 dc->SetPen(c);
362 dc->SetBrush(c);
363
364 int s = 10; // Width of dropdown arrow...height is half of width
365 AColor::Arrow(*dc,
366 bev.GetRight() - s - 3, // 3 to offset from right border
367 bev.y + ((bev.height - (s / 2)) / 2),
368 s);
369
370 }
371 }
372
MinimizeSyncLockDrawFunction(TrackPanelDrawingContext & context,const wxRect & rect,const Track * pTrack)373 void TrackInfo::MinimizeSyncLockDrawFunction
374 ( TrackPanelDrawingContext &context,
375 const wxRect &rect, const Track *pTrack )
376 {
377 auto dc = &context.dc;
378 bool selected = pTrack ? pTrack->GetSelected() : true;
379 bool syncLockSelected = pTrack ? pTrack->IsSyncLockSelected() : true;
380 bool minimized =
381 pTrack ? TrackView::Get( *pTrack ).GetMinimized() : false;
382 {
383 wxRect bev = rect;
384 GetMinimizeHorizontalBounds(rect, bev);
385 auto target = dynamic_cast<MinimizeButtonHandle*>( context.target.get() );
386 bool hit = target && target->GetTrack().get() == pTrack;
387 bool captured = hit && target->IsClicked();
388 bool down = captured && bev.Contains( context.lastState.GetPosition());
389
390 // Clear background to get rid of previous arrow
391 //AColor::MediumTrackInfo(dc, t->GetSelected());
392 //dc->DrawRectangle(bev);
393
394 AColor::Bevel2(*dc, !down, bev, selected, hit);
395
396 #ifdef EXPERIMENTAL_THEMING
397 wxColour c = theTheme.Colour(clrTrackPanelText);
398 dc->SetBrush(c);
399 dc->SetPen(c);
400 #else
401 AColor::Dark(dc, selected);
402 #endif
403
404 AColor::Arrow(*dc,
405 bev.x - 5 + bev.width / 2,
406 bev.y - 2 + bev.height / 2,
407 10,
408 minimized);
409 }
410
411 {
412 wxRect bev = rect;
413 GetSelectButtonHorizontalBounds(rect, bev);
414 auto target = dynamic_cast<SelectButtonHandle*>( context.target.get() );
415 bool hit = target && target->GetTrack().get() == pTrack;
416 bool captured = hit && target->IsClicked();
417 bool down = captured && bev.Contains( context.lastState.GetPosition());
418
419 AColor::Bevel2(*dc, !down, bev, selected, hit);
420
421 #ifdef EXPERIMENTAL_THEMING
422 wxColour c = theTheme.Colour(clrTrackPanelText);
423 dc->SetBrush(c);
424 dc->SetPen(c);
425 #else
426 AColor::Dark(dc, selected);
427 #endif
428
429 wxString str = _("Select");
430 wxCoord textWidth;
431 wxCoord textHeight;
432 SetTrackInfoFont(dc);
433 dc->GetTextExtent(str, &textWidth, &textHeight);
434
435 dc->SetTextForeground( c );
436 dc->SetTextBackground( wxTRANSPARENT );
437 dc->DrawText(str, bev.x + 2 + (bev.width-textWidth)/2, bev.y + (bev.height - textHeight) / 2);
438 }
439
440
441 // Draw the sync-lock indicator if this track is in a sync-lock selected group.
442 if (syncLockSelected)
443 {
444 wxRect syncLockIconRect = rect;
445
446 GetSyncLockHorizontalBounds( rect, syncLockIconRect );
447 wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockIcon));
448 // Icon is 12x12 and syncLockIconRect is 16x16.
449 dc->DrawBitmap(syncLockBitmap,
450 syncLockIconRect.x + 3,
451 syncLockIconRect.y + 2,
452 true);
453 }
454 }
455
GetCloseBoxHorizontalBounds(const wxRect & rect,wxRect & dest)456 void TrackInfo::GetCloseBoxHorizontalBounds( const wxRect & rect, wxRect &dest )
457 {
458 dest.x = rect.x;
459 dest.width = kTrackInfoBtnSize;
460 }
461
GetCloseBoxRect(const wxRect & rect,wxRect & dest)462 void TrackInfo::GetCloseBoxRect(const wxRect & rect, wxRect & dest)
463 {
464 GetCloseBoxHorizontalBounds( rect, dest );
465 auto results = CalcItemY( commonTrackTCPLines(), TCPLine::kItemBarButtons );
466 dest.y = rect.y + results.first;
467 dest.height = results.second;
468 }
469
GetTitleBarHorizontalBounds(const wxRect & rect,wxRect & dest)470 void TrackInfo::GetTitleBarHorizontalBounds( const wxRect & rect, wxRect &dest )
471 {
472 // to right of CloseBoxRect, plus a little more
473 wxRect closeRect;
474 GetCloseBoxHorizontalBounds( rect, closeRect );
475 dest.x = rect.x + closeRect.width + 1;
476 dest.width = rect.x + rect.width - dest.x + TitleSoloBorderOverlap;
477 }
478
GetTitleBarRect(const wxRect & rect,wxRect & dest)479 void TrackInfo::GetTitleBarRect(const wxRect & rect, wxRect & dest)
480 {
481 GetTitleBarHorizontalBounds( rect, dest );
482 auto results = CalcItemY( commonTrackTCPLines(), TCPLine::kItemBarButtons );
483 dest.y = rect.y + results.first;
484 dest.height = results.second;
485 }
486
GetSliderHorizontalBounds(const wxPoint & topleft,wxRect & dest)487 void TrackInfo::GetSliderHorizontalBounds( const wxPoint &topleft, wxRect &dest )
488 {
489 dest.x = topleft.x + 6;
490 dest.width = kTrackInfoSliderWidth;
491 }
492
GetMinimizeHorizontalBounds(const wxRect & rect,wxRect & dest)493 void TrackInfo::GetMinimizeHorizontalBounds( const wxRect &rect, wxRect &dest )
494 {
495 const int space = 0;// was 3.
496 dest.x = rect.x + space;
497
498 wxRect syncLockRect;
499 GetSyncLockHorizontalBounds( rect, syncLockRect );
500
501 // Width is rect.width less space on left for track select
502 // and on right for sync-lock icon.
503 dest.width = kTrackInfoBtnSize;
504 // rect.width - (space + syncLockRect.width);
505 }
506
GetMinimizeRect(const wxRect & rect,wxRect & dest)507 void TrackInfo::GetMinimizeRect(const wxRect & rect, wxRect &dest)
508 {
509 GetMinimizeHorizontalBounds( rect, dest );
510 auto results = CalcBottomItemY
511 ( commonTrackTCPBottomLines, TCPLine::kItemMinimize, rect.height);
512 dest.y = rect.y + results.first;
513 dest.height = results.second;
514 }
515
GetSelectButtonHorizontalBounds(const wxRect & rect,wxRect & dest)516 void TrackInfo::GetSelectButtonHorizontalBounds( const wxRect &rect, wxRect &dest )
517 {
518 const int space = 0;// was 3.
519 dest.x = rect.x + space;
520
521 wxRect syncLockRect;
522 GetSyncLockHorizontalBounds( rect, syncLockRect );
523 wxRect minimizeRect;
524 GetMinimizeHorizontalBounds( rect, minimizeRect );
525
526 dest.x = dest.x + space + minimizeRect.width;
527 // Width is rect.width less space on left for track select
528 // and on right for sync-lock icon.
529 dest.width = rect.width - (space + syncLockRect.width) - (space + minimizeRect.width);
530 }
531
532
GetSelectButtonRect(const wxRect & rect,wxRect & dest)533 void TrackInfo::GetSelectButtonRect(const wxRect & rect, wxRect &dest)
534 {
535 GetSelectButtonHorizontalBounds( rect, dest );
536 auto results = CalcBottomItemY
537 ( commonTrackTCPBottomLines, TCPLine::kItemMinimize, rect.height);
538 dest.y = rect.y + results.first;
539 dest.height = results.second;
540 }
541
GetSyncLockHorizontalBounds(const wxRect & rect,wxRect & dest)542 void TrackInfo::GetSyncLockHorizontalBounds( const wxRect &rect, wxRect &dest )
543 {
544 dest.width = kTrackInfoBtnSize;
545 dest.x = rect.x + rect.width - dest.width;
546 }
547
GetSyncLockIconRect(const wxRect & rect,wxRect & dest)548 void TrackInfo::GetSyncLockIconRect(const wxRect & rect, wxRect &dest)
549 {
550 GetSyncLockHorizontalBounds( rect, dest );
551 auto results = CalcBottomItemY
552 ( commonTrackTCPBottomLines, TCPLine::kItemSyncLock, rect.height);
553 dest.y = rect.y + results.first;
554 dest.height = results.second;
555 }
556
557 /// \todo Probably should move to 'Utils.cpp'.
SetTrackInfoFont(wxDC * dc)558 void TrackInfo::SetTrackInfoFont(wxDC * dc)
559 {
560 dc->SetFont(settings().gFont);
561 }
562
563 //#define USE_BEVELS
564
DefaultTrackHeight(const TCPLines & topLines)565 unsigned TrackInfo::DefaultTrackHeight( const TCPLines &topLines )
566 {
567 int needed =
568 kVerticalPadding +
569 totalTCPLines( topLines, true ) +
570 totalTCPLines( commonTrackTCPBottomLines, false ) + 1;
571 return (unsigned) std::max( needed, (int) TrackView::DefaultHeight );
572 }
573