Audacity 3.2.0
TrackArt.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 @file TrackArt.cpp
6
7 Paul Licameli split from TrackArtist.cpp
8
9**********************************************************************/
10
11#include "TrackArt.h"
12
13#include "AColor.h"
14#include "AllThemeResources.h"
15#include "SelectedRegion.h"
16#include "SyncLock.h"
17#include "Theme.h"
18#include "Track.h"
19#include "TrackArtist.h"
21#include "ZoomInfo.h"
22#include <wx/app.h>
23#include <wx/dc.h>
24
25static constexpr int ClipSelectionStrokeSize{ 1 };//px
26
27namespace
28{
29 //Helper function that takes affordance rectangle as
30 //argument and returns rectangle to be used for title
31 //drawings
32 wxRect GetAffordanceTitleRect(const wxRect& rect)
33 {
34 constexpr int FrameThickness{ 1 };
35 return wxRect(
36 rect.GetLeft() + TrackArt::ClipFrameRadius,
37 rect.GetTop() + ClipSelectionStrokeSize + FrameThickness,
38 rect.GetWidth() - TrackArt::ClipFrameRadius * 2,
39 rect.GetHeight() - ClipSelectionStrokeSize - FrameThickness);
40 }
41
42}
43
49int GetWaveYPos(float value, float min, float max,
50 int height, bool dB, bool outer,
51 float dBr, bool clip)
52{
53 if (dB) {
54 if (height == 0) {
55 return 0;
56 }
57
58 float sign = (value >= 0 ? 1 : -1);
59
60 if (value != 0.) {
61 float db = LINEAR_TO_DB(fabs(value));
62 value = (db + dBr) / dBr;
63 if (!outer) {
64 value -= 0.5;
65 }
66 if (value < 0.0) {
67 value = 0.0;
68 }
69 value *= sign;
70 }
71 }
72 else {
73 if (!outer) {
74 if (value >= 0.0) {
75 value -= 0.5;
76 }
77 else {
78 value += 0.5;
79 }
80 }
81 }
82
83 if (clip) {
84 if (value < min) {
85 value = min;
86 }
87 if (value > max) {
88 value = max;
89 }
90 }
91
92 value = (max - value) / (max - min);
93 return (int) (value * (height - 1) + 0.5);
94}
95
96float FromDB(float value, double dBRange)
97{
98 if (value == 0)
99 return 0;
100
101 double sign = (value >= 0 ? 1 : -1);
102 return DB_TO_LINEAR((fabs(value) * dBRange) - dBRange) * sign;
103}
104
105float ValueOfPixel(int yy, int height, bool offset,
106 bool dB, double dBRange, float zoomMin, float zoomMax)
107{
108 wxASSERT(height > 0);
109 // Map 0 to max and height - 1 (not height) to min
110 float v =
111 height == 1 ? (zoomMin + zoomMax) / 2 :
112 zoomMax - (yy / (float)(height - 1)) * (zoomMax - zoomMin);
113 if (offset) {
114 if (v > 0.0)
115 v += .5;
116 else
117 v -= .5;
118 }
119
120 if (dB)
121 v = FromDB(v, dBRange);
122
123 return v;
124}
125
127 TrackPanelDrawingContext &context, const wxRect &rect )
128{
129 auto &dc = context.dc;
130
131 // Draws two black arrows on the left side of the track to
132 // indicate the user that the track has been time-shifted
133 // to the left beyond t=0.0.
134
135 dc.SetPen(*wxBLACK_PEN);
136 AColor::Line(dc,
137 rect.x + 2, rect.y + 6,
138 rect.x + 8, rect.y + 6);
139 AColor::Line(dc,
140 rect.x + 2, rect.y + 6,
141 rect.x + 6, rect.y + 2);
142 AColor::Line(dc,
143 rect.x + 2, rect.y + 6,
144 rect.x + 6, rect.y + 10);
145 AColor::Line(dc,
146 rect.x + 2, rect.y + rect.height - 8,
147 rect.x + 8, rect.y + rect.height - 8);
148 AColor::Line(dc,
149 rect.x + 2, rect.y + rect.height - 8,
150 rect.x + 6, rect.y + rect.height - 4);
151 AColor::Line(dc,
152 rect.x + 2, rect.y + rect.height - 8,
153 rect.x + 6, rect.y + rect.height - 12);
154}
155
156wxString TrackArt::TruncateText(wxDC& dc, const wxString& text, const int maxWidth)
157{
158 static const wxString ellipsis = "\u2026";
159
160 if (dc.GetTextExtent(text).GetWidth() <= maxWidth)
161 return text;
162
163 auto left = 0;
164 //no need to check text + '...'
165 auto right = static_cast<int>(text.Length() - 2);
166
167 while (left <= right)
168 {
169 auto middle = (left + right) / 2;
170 auto str = text.SubString(0, middle).Trim() + ellipsis;
171 auto strWidth = dc.GetTextExtent(str).GetWidth();
172 if (strWidth < maxWidth)
173 //if left == right (== middle), then exit loop
174 //with right equals to the last known index for which
175 //strWidth < maxWidth
176 left = middle + 1;
177 else if (strWidth > maxWidth)
178 //if right == left (== middle), then exit loop with
179 //right equals to (left - 1), which is the last known
180 //index for which (strWidth < maxWidth) or -1
181 right = middle - 1;
182 else
183 return str;
184 }
185 if (right >= 0)
186 return text.SubString(0, right).Trim() + ellipsis;
187
188 return wxEmptyString;
189}
190
191wxRect TrackArt::DrawClipAffordance(wxDC& dc, const wxRect& rect, const wxString& title, bool highlight, bool selected)
192{
193 //To make sure that roundings do not overlap each other
194 auto clipFrameRadius = std::min(ClipFrameRadius, rect.width / 2);
195
196 wxRect clipRect;
197 bool hasClipRect = dc.GetClippingBox(clipRect);
198 //Fix #1689: visual glitches appear on attempt to draw a rectangle
199 //larger than 0x7FFFFFF pixels wide (value was discovered
200 //by manual testing, and maybe depends on OS being used), but
201 //it's very unlikely that such huge rectangle will be ever fully visible
202 //on the screen, so we can safely reduce its size to be slightly larger than
203 //clipping rectangle, and avoid that problem
204 auto drawingRect = rect;
205 if (hasClipRect)
206 {
207 //to make sure that rounding happends outside the clipping rectangle
208 drawingRect.SetLeft(std::max(rect.GetLeft(), clipRect.GetLeft() - clipFrameRadius - 1));
209 drawingRect.SetRight(std::min(rect.GetRight(), clipRect.GetRight() + clipFrameRadius + 1));
210 }
211
212 if (selected)
213 {
214 wxRect strokeRect{
215 drawingRect.x - ClipSelectionStrokeSize,
216 drawingRect.y,
217 drawingRect.width + ClipSelectionStrokeSize * 2,
218 drawingRect.height + clipFrameRadius };
219 dc.SetBrush(*wxTRANSPARENT_BRUSH);
220 AColor::UseThemeColour(&dc, clrClipAffordanceStroke, clrClipAffordanceStroke);
221 dc.DrawRoundedRectangle(strokeRect, clipFrameRadius);
222 }
223
224 AColor::UseThemeColour(&dc, highlight ? clrClipAffordanceActiveBrush : clrClipAffordanceInactiveBrush, clrClipAffordanceOutlinePen);
225 dc.DrawRoundedRectangle(
226 wxRect(
227 drawingRect.x,
228 drawingRect.y + ClipSelectionStrokeSize,
229 drawingRect.width,
230 drawingRect.height + clipFrameRadius
231 ), clipFrameRadius
232 );
233
234 auto titleRect = hasClipRect ?
235 //avoid drawing text outside the clipping rectangle if possible
236 GetAffordanceTitleRect(rect.Intersect(clipRect)) :
238
239 if (!title.empty())
240 {
241 auto truncatedTitle = TrackArt::TruncateText(dc, title, titleRect.GetWidth());
242 if (!truncatedTitle.empty())
243 {
244 auto hAlign = wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft ? wxALIGN_RIGHT : wxALIGN_LEFT;
245 dc.DrawLabel(truncatedTitle, titleRect, hAlign | wxALIGN_CENTER_VERTICAL);
246 }
247 else
248 return { };
249 }
250 return titleRect;
251}
252
253void TrackArt::DrawClipEdges(wxDC& dc, const wxRect& clipRect, bool selected)
254{
255 dc.SetBrush(*wxTRANSPARENT_BRUSH);
256 {
257 AColor::UseThemeColour(&dc, -1, clrClipAffordanceOutlinePen);
258 AColor::Line(dc,
259 clipRect.GetLeft(), clipRect.GetTop(),
260 clipRect.GetLeft(), clipRect.GetBottom());
261 AColor::Line(dc,
262 clipRect.GetRight(), clipRect.GetTop(),
263 clipRect.GetRight(), clipRect.GetBottom());
264 }
265 if(selected)
266 {
267 if constexpr (ClipSelectionStrokeSize == 1)
268 {
269 AColor::UseThemeColour(&dc, -1, clrClipAffordanceStroke);
270 AColor::Line(dc,
271 clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetTop(),
272 clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetBottom());
273 AColor::Line(dc,
274 clipRect.GetRight() + ClipSelectionStrokeSize, clipRect.GetTop(),
275 clipRect.GetRight() + ClipSelectionStrokeSize, clipRect.GetBottom());
276 }
277 else if constexpr (ClipSelectionStrokeSize > 1)
278 {
279 AColor::UseThemeColour(&dc, clrClipAffordanceStroke, clrClipAffordanceStroke);
280 dc.DrawRectangle(wxRect(
281 clipRect.GetLeft() - ClipSelectionStrokeSize, clipRect.GetTop(),
282 ClipSelectionStrokeSize, clipRect.GetHeight()));
283 dc.DrawRectangle(wxRect(
284 clipRect.GetRight() + 1, clipRect.GetTop(),
285 ClipSelectionStrokeSize, clipRect.GetHeight()));
286 }
287 }
288}
289
290void TrackArt::DrawClipFolded(wxDC& dc, const wxRect& rect)
291{
292 AColor::UseThemeColour(&dc, clrClipAffordanceOutlinePen);
293 dc.DrawRectangle(rect);
294}
295
296// Draws the sync-lock bitmap, tiled; always draws stationary relative to the DC
297//
298// AWD: now that the tiles don't link together, we're drawing a tilted grid, at
299// two steps down for every one across. This creates a pattern that repeats in
300// 5-step by 5-step boxes. Because we're only drawing in 5/25 possible positions
301// we have a grid spacing somewhat smaller than the image dimensions. Thus we
302// achieve lower density than with a square grid and eliminate edge cases where
303// no tiles are displayed.
304//
305// The pattern draws in tiles at (0,0), (2,1), (4,2), (1,3), and (3,4) in each
306// 5x5 box.
307//
308// There may be a better way to do this, or a more appealing pattern.
310 TrackPanelDrawingContext &context, const wxRect &rect )
311{
312 const auto dc = &context.dc;
313
314 wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockSelTile));
315
316 // Grid spacing is a bit smaller than actual image size
317 int gridW = syncLockBitmap.GetWidth() - 6;
318 int gridH = syncLockBitmap.GetHeight() - 8;
319
320 // Horizontal position within the grid, modulo its period
321 int blockX = (rect.x / gridW) % 5;
322
323 // Amount to offset drawing of first column
324 int xOffset = rect.x % gridW;
325 if (xOffset < 0) xOffset += gridW;
326
327 // Check if we're missing an extra column to the left (this can happen
328 // because the tiles are bigger than the grid spacing)
329 bool extraCol = false;
330 if (syncLockBitmap.GetWidth() - gridW > xOffset) {
331 extraCol = true;
332 xOffset += gridW;
333 blockX = (blockX - 1) % 5;
334 }
335 // Make sure blockX is non-negative
336 if (blockX < 0) blockX += 5;
337
338 int xx = 0;
339 while (xx < rect.width) {
340 int width = syncLockBitmap.GetWidth() - xOffset;
341 if (xx + width > rect.width)
342 width = rect.width - xx;
343
344 //
345 // Draw each row in this column
346 //
347
348 // Vertical position in the grid, modulo its period
349 int blockY = (rect.y / gridH) % 5;
350
351 // Amount to offset drawing of first row
352 int yOffset = rect.y % gridH;
353 if (yOffset < 0) yOffset += gridH;
354
355 // Check if we're missing an extra row on top (this can happen because
356 // the tiles are bigger than the grid spacing)
357 bool extraRow = false;
358 if (syncLockBitmap.GetHeight() - gridH > yOffset) {
359 extraRow = true;
360 yOffset += gridH;
361 blockY = (blockY - 1) % 5;
362 }
363 // Make sure blockY is non-negative
364 if (blockY < 0) blockY += 5;
365
366 int yy = 0;
367 while (yy < rect.height)
368 {
369 int height = syncLockBitmap.GetHeight() - yOffset;
370 if (yy + height > rect.height)
371 height = rect.height - yy;
372
373 // AWD: draw blocks according to our pattern
374 if ((blockX == 0 && blockY == 0) || (blockX == 2 && blockY == 1) ||
375 (blockX == 4 && blockY == 2) || (blockX == 1 && blockY == 3) ||
376 (blockX == 3 && blockY == 4))
377 {
378
379 // Do we need to get a sub-bitmap?
380 if (width != syncLockBitmap.GetWidth() || height != syncLockBitmap.GetHeight()) {
381 wxBitmap subSyncLockBitmap =
382 syncLockBitmap.GetSubBitmap(wxRect(xOffset, yOffset, width, height));
383 dc->DrawBitmap(subSyncLockBitmap, rect.x + xx, rect.y + yy, true);
384 }
385 else {
386 dc->DrawBitmap(syncLockBitmap, rect.x + xx, rect.y + yy, true);
387 }
388 }
389
390 // Updates for next row
391 if (extraRow) {
392 // Second offset row, still at y = 0; no more extra rows
393 yOffset -= gridH;
394 extraRow = false;
395 }
396 else {
397 // Move on in y, no more offset rows
398 yy += gridH - yOffset;
399 yOffset = 0;
400 }
401 blockY = (blockY + 1) % 5;
402 }
403
404 // Updates for next column
405 if (extraCol) {
406 // Second offset column, still at x = 0; no more extra columns
407 xOffset -= gridW;
408 extraCol = false;
409 }
410 else {
411 // Move on in x, no more offset rows
412 xx += gridW - xOffset;
413 xOffset = 0;
414 }
415 blockX = (blockX + 1) % 5;
416 }
417}
418
420 TrackPanelDrawingContext &context, const wxRect &rect,
421 const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush,
422 bool useSelection)
423{
424 const auto dc = &context.dc;
425 const auto artist = TrackArtist::Get( context );
426 const auto &selectedRegion = *artist->pSelectedRegion;
427 const auto &zoomInfo = *artist->pZoomInfo;
428
429 //MM: Draw background. We should optimize that a bit more.
430 const double sel0 = useSelection ? selectedRegion.t0() : 0.0;
431 const double sel1 = useSelection ? selectedRegion.t1() : 0.0;
432
433 dc->SetPen(*wxTRANSPARENT_PEN);
435 {
436 // Rectangles before, within, after the selection
437 wxRect before = rect;
438 wxRect within = rect;
439 wxRect after = rect;
440
441 before.width = (int)(zoomInfo.TimeToPosition(sel0) );
442 if (before.GetRight() > rect.GetRight()) {
443 before.width = rect.width;
444 }
445
446 if (before.width > 0) {
447 dc->SetBrush(unselBrush);
448 dc->DrawRectangle(before);
449
450 within.x = 1 + before.GetRight();
451 }
452 within.width = rect.x + (int)(zoomInfo.TimeToPosition(sel1) ) - within.x -1;
453
454 if (within.GetRight() > rect.GetRight()) {
455 within.width = 1 + rect.GetRight() - within.x;
456 }
457
458 // Bug 2389 - Selection can disappear
459 // This handles case where no waveform is visible.
460 if (within.width < 1)
461 {
462 within.width = 1;
463 }
464
465 if (within.width > 0) {
466 if (track->GetSelected()) {
467 dc->SetBrush(selBrush);
468 dc->DrawRectangle(within);
469 }
470 else {
471 // Per condition above, track must be sync-lock selected
472 dc->SetBrush(unselBrush);
473 dc->DrawRectangle(within);
474 DrawSyncLockTiles( context, within );
475 }
476
477 after.x = 1 + within.GetRight();
478 }
479 else {
480 // `within` not drawn; start where it would have gone
481 after.x = within.x;
482 }
483
484 after.width = 1 + rect.GetRight() - after.x;
485 if (after.width > 0) {
486 dc->SetBrush(unselBrush);
487 dc->DrawRectangle(after);
488 }
489 }
490 else
491 {
492 // Track not selected; just draw background
493 dc->SetBrush(unselBrush);
494 dc->DrawRectangle(rect);
495 }
496}
497
499 const wxRect& rect, const Track* track)
500{
501 const auto dc = &context.dc;
502 const auto artist = TrackArtist::Get(context);
503 const auto& selectedRegion = *artist->pSelectedRegion;
504
505 if (selectedRegion.isPoint())
506 {
507 const auto& zoomInfo = *artist->pZoomInfo;
508 auto x = static_cast<int>(zoomInfo.TimeToPosition(selectedRegion.t0(), rect.x));
509 if (x >= rect.GetLeft() && x <= rect.GetRight())
510 {
512 AColor::Line(*dc, x, rect.GetTop(), x, rect.GetBottom());
513 }
514 }
515}
static const wxPoint2DDouble outer[]
Definition: ASlider.cpp:402
int min(int a, int b)
#define str(a)
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:544
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:543
static const auto title
THEME_API Theme theTheme
Definition: Theme.cpp:82
declares abstract base class Track, TrackList, and iterators over TrackList
int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip)
Definition: TrackArt.cpp:49
float FromDB(float value, double dBRange)
Definition: TrackArt.cpp:96
float ValueOfPixel(int yy, int height, bool offset, bool dB, double dBRange, float zoomMin, float zoomMax)
Definition: TrackArt.cpp:105
static constexpr int ClipSelectionStrokeSize
Definition: TrackArt.cpp:25
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:166
static void Line(wxDC &dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2)
Definition: AColor.cpp:187
static void CursorColor(wxDC *dc)
Definition: AColor.cpp:463
static void UseThemeColour(wxDC *dc, int iBrush, int iPen=-1, int alpha=255)
Definition: AColor.cpp:372
static bool IsSelectedOrSyncLockSelected(const Track *pTrack)
Definition: SyncLock.cpp:73
wxImage & Image(int iIndex)
static TrackArtist * Get(TrackPanelDrawingContext &)
Definition: TrackArtist.cpp:69
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
bool GetSelected() const
Definition: Track.h:469
AUDACITY_DLL_API void DrawSyncLockTiles(TrackPanelDrawingContext &context, const wxRect &rect)
Definition: TrackArt.cpp:309
AUDACITY_DLL_API wxString TruncateText(wxDC &dc, const wxString &text, const int maxWidth)
Definition: TrackArt.cpp:156
AUDACITY_DLL_API void DrawClipFolded(wxDC &dc, const wxRect &rect)
Definition: TrackArt.cpp:290
AUDACITY_DLL_API void DrawClipEdges(wxDC &dc, const wxRect &clipRect, bool selected=false)
Definition: TrackArt.cpp:253
AUDACITY_DLL_API void DrawBackgroundWithSelection(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track, const wxBrush &selBrush, const wxBrush &unselBrush, bool useSelection=true)
Definition: TrackArt.cpp:419
AUDACITY_DLL_API void DrawCursor(TrackPanelDrawingContext &context, const wxRect &rect, const Track *track)
Definition: TrackArt.cpp:498
AUDACITY_DLL_API void DrawNegativeOffsetTrackArrows(TrackPanelDrawingContext &context, const wxRect &rect)
Definition: TrackArt.cpp:126
AUDACITY_DLL_API wxRect DrawClipAffordance(wxDC &dc, const wxRect &affordanceRect, const wxString &title, bool highlight=false, bool selected=false)
Definition: TrackArt.cpp:191
static constexpr int ClipFrameRadius
Definition: TrackArt.h:22
wxRect GetAffordanceTitleRect(const wxRect &rect)
Definition: TrackArt.cpp:32