... This seems the more portabe way to do it across platforms. We are not relying on a passed-in TrackPanel, for the sake of keeping TrackInfo independent of TrackPanel. We just get the active window from the application, which should exists in all contexts in which the Settings are demanded, which are drawing functions.
565 lines
17 KiB
C++
565 lines
17 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
TrackInfo.cpp
|
|
|
|
Paul Licameli split from TrackPanel.cpp
|
|
|
|
********************************************************************//*!
|
|
|
|
\namespace TrackInfo
|
|
\brief
|
|
Functions for drawing the track control panel, which is shown to the side
|
|
of a track
|
|
It has the menus, pan and gain controls displayed in it.
|
|
So "Info" is somewhat a misnomer. Should possibly be "TrackControls".
|
|
|
|
It maintains global slider widget instances that are reparented and
|
|
repositioned as needed for drawing and interaction with the user,
|
|
interoperating with the custom panel subdivision implemented in CellularPanel
|
|
and avoiding wxWidgets sizers
|
|
|
|
If we'd instead coded it as a wxWindow, we would have an instance
|
|
of this class for each track displayed.
|
|
|
|
**********************************************************************/
|
|
|
|
#include "Audacity.h"
|
|
#include "TrackInfo.h"
|
|
|
|
#include "Experimental.h"
|
|
|
|
#include <wx/dc.h>
|
|
#include <wx/frame.h>
|
|
|
|
#include "AColor.h"
|
|
#include "AllThemeResources.h"
|
|
#include "Prefs.h"
|
|
#include "Project.h"
|
|
#include "Track.h"
|
|
#include "TrackPanelDrawingContext.h"
|
|
#include "ViewInfo.h"
|
|
#include "tracks/ui/TrackView.h"
|
|
|
|
|
|
// Subscribe to preference changes to update static variables
|
|
struct Settings : PrefsListener {
|
|
wxString gSoloPref;
|
|
wxFont gFont;
|
|
|
|
bool mInitialized{ false };
|
|
|
|
void UpdatePrefs() override
|
|
{
|
|
gPrefs->Read(wxT("/GUI/Solo"), &gSoloPref, wxT("Simple"));
|
|
|
|
// Calculation of best font size depends on language, so it should be redone in case
|
|
// the language preference changed.
|
|
|
|
// wxWidgets seems to need a window to do this portably.
|
|
if ( !wxTheApp )
|
|
return;
|
|
auto window = wxTheApp->GetTopWindow();
|
|
if ( !window )
|
|
return;
|
|
|
|
int fontSize = 10;
|
|
gFont.Create(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
|
|
|
|
int allowableWidth =
|
|
// PRL: was it correct to include the margin?
|
|
( kTrackInfoWidth + kLeftMargin )
|
|
- 2; // 2 to allow for left/right borders
|
|
int textWidth;
|
|
do {
|
|
gFont.SetPointSize(fontSize);
|
|
window->GetTextExtent(_("Stereo, 999999Hz"),
|
|
&textWidth, nullptr, nullptr, nullptr, &gFont);
|
|
fontSize--;
|
|
} while (textWidth >= allowableWidth);
|
|
|
|
mInitialized = true;
|
|
}
|
|
};
|
|
|
|
static Settings &settings()
|
|
{
|
|
static Settings theSettings;
|
|
if ( !theSettings.mInitialized )
|
|
theSettings.UpdatePrefs();
|
|
return theSettings;
|
|
}
|
|
|
|
bool TrackInfo::HasSoloButton()
|
|
{
|
|
return settings().gSoloPref != wxT("None");
|
|
}
|
|
|
|
#define RANGE(array) (array), (array) + sizeof(array)/sizeof(*(array))
|
|
using TCPLine = TrackInfo::TCPLine;
|
|
using TCPLines = TrackInfo::TCPLines;
|
|
|
|
static const TCPLines &commonTrackTCPLines()
|
|
{
|
|
static const TCPLines theLines{
|
|
#ifdef EXPERIMENTAL_DA
|
|
|
|
{ TCPLine::kItemBarButtons, kTrackInfoBtnSize, 4,
|
|
&TrackInfo::CloseTitleDrawFunction },
|
|
|
|
#else
|
|
|
|
{ TCPLine::kItemBarButtons, kTrackInfoBtnSize, 0,
|
|
&TrackInfo::CloseTitleDrawFunction },
|
|
|
|
#endif
|
|
};
|
|
return theLines;
|
|
}
|
|
|
|
#include "tracks/ui/CommonTrackControls.h"
|
|
const TCPLines &CommonTrackControls::StaticTCPLines()
|
|
{
|
|
return commonTrackTCPLines();
|
|
}
|
|
|
|
namespace {
|
|
|
|
int totalTCPLines( const TCPLines &lines, bool omitLastExtra )
|
|
{
|
|
int total = 0;
|
|
int lastExtra = 0;
|
|
for ( const auto line : lines ) {
|
|
lastExtra = line.extraSpace;
|
|
total += line.height + lastExtra;
|
|
}
|
|
if (omitLastExtra)
|
|
total -= lastExtra;
|
|
return total;
|
|
}
|
|
}
|
|
|
|
// return y value and height
|
|
std::pair< int, int >
|
|
TrackInfo::CalcItemY( const TCPLines &lines, unsigned iItem )
|
|
{
|
|
int y = 0;
|
|
auto pLines = lines.begin();
|
|
while ( pLines != lines.end() &&
|
|
0 == (pLines->items & iItem) ) {
|
|
y += pLines->height + pLines->extraSpace;
|
|
++pLines;
|
|
}
|
|
int height = 0;
|
|
if ( pLines != lines.end() )
|
|
height = pLines->height;
|
|
return { y, height };
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Items for the bottom of the panel, listed bottom-upwards
|
|
// As also with the top items, the extra space is below the item
|
|
const TrackInfo::TCPLine defaultCommonTrackTCPBottomLines[] = {
|
|
// The '0' avoids impinging on bottom line of TCP
|
|
// Use -1 if you do want to do so.
|
|
{ TCPLine::kItemSyncLock | TCPLine::kItemMinimize, kTrackInfoBtnSize, 0,
|
|
&TrackInfo::MinimizeSyncLockDrawFunction },
|
|
};
|
|
TCPLines commonTrackTCPBottomLines{ RANGE(defaultCommonTrackTCPBottomLines) };
|
|
|
|
// return y value and height
|
|
std::pair< int, int > CalcBottomItemY
|
|
( const TCPLines &lines, unsigned iItem, int height )
|
|
{
|
|
int y = height;
|
|
auto pLines = lines.begin();
|
|
while ( pLines != lines.end() &&
|
|
0 == (pLines->items & iItem) ) {
|
|
y -= pLines->height + pLines->extraSpace;
|
|
++pLines;
|
|
}
|
|
if (pLines != lines.end())
|
|
y -= (pLines->height + pLines->extraSpace );
|
|
return { y, pLines->height };
|
|
}
|
|
|
|
}
|
|
|
|
const TCPLines &CommonTrackControls::GetTCPLines() const
|
|
{
|
|
return commonTrackTCPLines();
|
|
}
|
|
|
|
unsigned TrackInfo::MinimumTrackHeight()
|
|
{
|
|
unsigned height = 0;
|
|
if (!commonTrackTCPLines().empty())
|
|
height += commonTrackTCPLines().front().height;
|
|
if (!commonTrackTCPBottomLines.empty())
|
|
height += commonTrackTCPBottomLines.front().height;
|
|
// + 1 prevents the top item from disappearing for want of enough space,
|
|
// according to the rules in HideTopItem.
|
|
return height + kTopMargin + kBottomMargin + 1;
|
|
}
|
|
|
|
bool TrackInfo::HideTopItem( const wxRect &rect, const wxRect &subRect,
|
|
int allowance ) {
|
|
auto limit = CalcBottomItemY
|
|
( commonTrackTCPBottomLines, TCPLine::kHighestBottomItem, rect.height).first;
|
|
// Return true if the rectangle is even touching the limit
|
|
// without an overlap. That was the behavior as of 2.1.3.
|
|
return subRect.y + subRect.height - allowance >= rect.y + limit;
|
|
}
|
|
|
|
void TrackInfo::DrawItems
|
|
( TrackPanelDrawingContext &context,
|
|
const wxRect &rect, const Track &track )
|
|
{
|
|
auto &trackControl = static_cast<const CommonTrackControls&>(
|
|
TrackControls::Get( track ) );
|
|
const auto &topLines = trackControl.GetTCPLines();
|
|
const auto &bottomLines = commonTrackTCPBottomLines;
|
|
DrawItems
|
|
( context, rect, &track, topLines, bottomLines );
|
|
}
|
|
|
|
void TrackInfo::DrawItems
|
|
( TrackPanelDrawingContext &context,
|
|
const wxRect &rect, const Track *pTrack,
|
|
const std::vector<TCPLine> &topLines, const std::vector<TCPLine> &bottomLines )
|
|
{
|
|
auto dc = &context.dc;
|
|
TrackInfo::SetTrackInfoFont(dc);
|
|
dc->SetTextForeground(theTheme.Colour(clrTrackPanelText));
|
|
|
|
{
|
|
int yy = 0;
|
|
for ( const auto &line : topLines ) {
|
|
wxRect itemRect{
|
|
rect.x, rect.y + yy,
|
|
rect.width, line.height
|
|
};
|
|
if ( !TrackInfo::HideTopItem( rect, itemRect ) &&
|
|
line.drawFunction )
|
|
line.drawFunction( context, itemRect, pTrack );
|
|
yy += line.height + line.extraSpace;
|
|
}
|
|
}
|
|
{
|
|
int yy = rect.height;
|
|
for ( const auto &line : bottomLines ) {
|
|
yy -= line.height + line.extraSpace;
|
|
if ( line.drawFunction ) {
|
|
wxRect itemRect{
|
|
rect.x, rect.y + yy,
|
|
rect.width, line.height
|
|
};
|
|
line.drawFunction( context, itemRect, pTrack );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#include "tracks/ui/TrackButtonHandles.h"
|
|
void TrackInfo::CloseTitleDrawFunction
|
|
( TrackPanelDrawingContext &context,
|
|
const wxRect &rect, const Track *pTrack )
|
|
{
|
|
auto dc = &context.dc;
|
|
bool selected = pTrack ? pTrack->GetSelected() : true;
|
|
{
|
|
wxRect bev = rect;
|
|
GetCloseBoxHorizontalBounds( rect, bev );
|
|
auto target = dynamic_cast<CloseButtonHandle*>( context.target.get() );
|
|
bool hit = target && target->GetTrack().get() == pTrack;
|
|
bool captured = hit && target->IsClicked();
|
|
bool down = captured && bev.Contains( context.lastState.GetPosition());
|
|
AColor::Bevel2(*dc, !down, bev, selected, hit );
|
|
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
wxPen pen( theTheme.Colour( clrTrackPanelText ));
|
|
dc->SetPen( pen );
|
|
#else
|
|
dc->SetPen(*wxBLACK_PEN);
|
|
#endif
|
|
bev.Inflate( -1, -1 );
|
|
// Draw the "X"
|
|
const int s = 6;
|
|
|
|
int ls = bev.x + ((bev.width - s) / 2);
|
|
int ts = bev.y + ((bev.height - s) / 2);
|
|
int rs = ls + s;
|
|
int bs = ts + s;
|
|
|
|
AColor::Line(*dc, ls, ts, rs, bs);
|
|
AColor::Line(*dc, ls + 1, ts, rs + 1, bs);
|
|
AColor::Line(*dc, rs, ts, ls, bs);
|
|
AColor::Line(*dc, rs + 1, ts, ls + 1, bs);
|
|
|
|
// bev.Inflate(-1, -1);
|
|
}
|
|
|
|
{
|
|
wxRect bev = rect;
|
|
GetTitleBarHorizontalBounds( rect, bev );
|
|
auto target = dynamic_cast<MenuButtonHandle*>( context.target.get() );
|
|
bool hit = target && target->GetTrack().get() == pTrack;
|
|
bool captured = hit && target->IsClicked();
|
|
bool down = captured && bev.Contains( context.lastState.GetPosition());
|
|
wxString titleStr =
|
|
pTrack ? pTrack->GetName() : _("Name");
|
|
|
|
//bev.Inflate(-1, -1);
|
|
AColor::Bevel2(*dc, !down, bev, selected, hit);
|
|
|
|
// Draw title text
|
|
SetTrackInfoFont(dc);
|
|
|
|
// Bug 1660 The 'k' of 'Audio Track' was being truncated.
|
|
// Constant of 32 found by counting pixels on a windows machine.
|
|
// I believe it's the size of the X close button + the size of the
|
|
// drop down arrow.
|
|
int allowableWidth = rect.width - 32;
|
|
|
|
wxCoord textWidth, textHeight;
|
|
dc->GetTextExtent(titleStr, &textWidth, &textHeight);
|
|
while (textWidth > allowableWidth) {
|
|
titleStr = titleStr.Left(titleStr.length() - 1);
|
|
dc->GetTextExtent(titleStr, &textWidth, &textHeight);
|
|
}
|
|
|
|
// Pop-up triangle
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
wxColour c = theTheme.Colour( clrTrackPanelText );
|
|
#else
|
|
wxColour c = *wxBLACK;
|
|
#endif
|
|
|
|
// wxGTK leaves little scraps (antialiasing?) of the
|
|
// characters if they are repeatedly drawn. This
|
|
// happens when holding down mouse button and moving
|
|
// in and out of the title bar. So clear it first.
|
|
// AColor::MediumTrackInfo(dc, t->GetSelected());
|
|
// dc->DrawRectangle(bev);
|
|
|
|
dc->SetTextForeground( c );
|
|
dc->SetTextBackground( wxTRANSPARENT );
|
|
dc->DrawText(titleStr, bev.x + 2, bev.y + (bev.height - textHeight) / 2);
|
|
|
|
|
|
|
|
dc->SetPen(c);
|
|
dc->SetBrush(c);
|
|
|
|
int s = 10; // Width of dropdown arrow...height is half of width
|
|
AColor::Arrow(*dc,
|
|
bev.GetRight() - s - 3, // 3 to offset from right border
|
|
bev.y + ((bev.height - (s / 2)) / 2),
|
|
s);
|
|
|
|
}
|
|
}
|
|
|
|
void TrackInfo::MinimizeSyncLockDrawFunction
|
|
( TrackPanelDrawingContext &context,
|
|
const wxRect &rect, const Track *pTrack )
|
|
{
|
|
auto dc = &context.dc;
|
|
bool selected = pTrack ? pTrack->GetSelected() : true;
|
|
bool syncLockSelected = pTrack ? pTrack->IsSyncLockSelected() : true;
|
|
bool minimized =
|
|
pTrack ? TrackView::Get( *pTrack ).GetMinimized() : false;
|
|
{
|
|
wxRect bev = rect;
|
|
GetMinimizeHorizontalBounds(rect, bev);
|
|
auto target = dynamic_cast<MinimizeButtonHandle*>( context.target.get() );
|
|
bool hit = target && target->GetTrack().get() == pTrack;
|
|
bool captured = hit && target->IsClicked();
|
|
bool down = captured && bev.Contains( context.lastState.GetPosition());
|
|
|
|
// Clear background to get rid of previous arrow
|
|
//AColor::MediumTrackInfo(dc, t->GetSelected());
|
|
//dc->DrawRectangle(bev);
|
|
|
|
AColor::Bevel2(*dc, !down, bev, selected, hit);
|
|
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
wxColour c = theTheme.Colour(clrTrackPanelText);
|
|
dc->SetBrush(c);
|
|
dc->SetPen(c);
|
|
#else
|
|
AColor::Dark(dc, selected);
|
|
#endif
|
|
|
|
AColor::Arrow(*dc,
|
|
bev.x - 5 + bev.width / 2,
|
|
bev.y - 2 + bev.height / 2,
|
|
10,
|
|
minimized);
|
|
}
|
|
|
|
{
|
|
wxRect bev = rect;
|
|
GetSelectButtonHorizontalBounds(rect, bev);
|
|
auto target = dynamic_cast<SelectButtonHandle*>( context.target.get() );
|
|
bool hit = target && target->GetTrack().get() == pTrack;
|
|
bool captured = hit && target->IsClicked();
|
|
bool down = captured && bev.Contains( context.lastState.GetPosition());
|
|
|
|
AColor::Bevel2(*dc, !down, bev, selected, hit);
|
|
|
|
#ifdef EXPERIMENTAL_THEMING
|
|
wxColour c = theTheme.Colour(clrTrackPanelText);
|
|
dc->SetBrush(c);
|
|
dc->SetPen(c);
|
|
#else
|
|
AColor::Dark(dc, selected);
|
|
#endif
|
|
|
|
wxString str = _("Select");
|
|
wxCoord textWidth;
|
|
wxCoord textHeight;
|
|
SetTrackInfoFont(dc);
|
|
dc->GetTextExtent(str, &textWidth, &textHeight);
|
|
|
|
dc->SetTextForeground( c );
|
|
dc->SetTextBackground( wxTRANSPARENT );
|
|
dc->DrawText(str, bev.x + 2 + (bev.width-textWidth)/2, bev.y + (bev.height - textHeight) / 2);
|
|
}
|
|
|
|
|
|
// Draw the sync-lock indicator if this track is in a sync-lock selected group.
|
|
if (syncLockSelected)
|
|
{
|
|
wxRect syncLockIconRect = rect;
|
|
|
|
GetSyncLockHorizontalBounds( rect, syncLockIconRect );
|
|
wxBitmap syncLockBitmap(theTheme.Image(bmpSyncLockIcon));
|
|
// Icon is 12x12 and syncLockIconRect is 16x16.
|
|
dc->DrawBitmap(syncLockBitmap,
|
|
syncLockIconRect.x + 3,
|
|
syncLockIconRect.y + 2,
|
|
true);
|
|
}
|
|
}
|
|
|
|
void TrackInfo::GetCloseBoxHorizontalBounds( const wxRect & rect, wxRect &dest )
|
|
{
|
|
dest.x = rect.x;
|
|
dest.width = kTrackInfoBtnSize;
|
|
}
|
|
|
|
void TrackInfo::GetCloseBoxRect(const wxRect & rect, wxRect & dest)
|
|
{
|
|
GetCloseBoxHorizontalBounds( rect, dest );
|
|
auto results = CalcItemY( commonTrackTCPLines(), TCPLine::kItemBarButtons );
|
|
dest.y = rect.y + results.first;
|
|
dest.height = results.second;
|
|
}
|
|
|
|
void TrackInfo::GetTitleBarHorizontalBounds( const wxRect & rect, wxRect &dest )
|
|
{
|
|
// to right of CloseBoxRect, plus a little more
|
|
wxRect closeRect;
|
|
GetCloseBoxHorizontalBounds( rect, closeRect );
|
|
dest.x = rect.x + closeRect.width + 1;
|
|
dest.width = rect.x + rect.width - dest.x + TitleSoloBorderOverlap;
|
|
}
|
|
|
|
void TrackInfo::GetTitleBarRect(const wxRect & rect, wxRect & dest)
|
|
{
|
|
GetTitleBarHorizontalBounds( rect, dest );
|
|
auto results = CalcItemY( commonTrackTCPLines(), TCPLine::kItemBarButtons );
|
|
dest.y = rect.y + results.first;
|
|
dest.height = results.second;
|
|
}
|
|
|
|
void TrackInfo::GetSliderHorizontalBounds( const wxPoint &topleft, wxRect &dest )
|
|
{
|
|
dest.x = topleft.x + 6;
|
|
dest.width = kTrackInfoSliderWidth;
|
|
}
|
|
|
|
void TrackInfo::GetMinimizeHorizontalBounds( const wxRect &rect, wxRect &dest )
|
|
{
|
|
const int space = 0;// was 3.
|
|
dest.x = rect.x + space;
|
|
|
|
wxRect syncLockRect;
|
|
GetSyncLockHorizontalBounds( rect, syncLockRect );
|
|
|
|
// Width is rect.width less space on left for track select
|
|
// and on right for sync-lock icon.
|
|
dest.width = kTrackInfoBtnSize;
|
|
// rect.width - (space + syncLockRect.width);
|
|
}
|
|
|
|
void TrackInfo::GetMinimizeRect(const wxRect & rect, wxRect &dest)
|
|
{
|
|
GetMinimizeHorizontalBounds( rect, dest );
|
|
auto results = CalcBottomItemY
|
|
( commonTrackTCPBottomLines, TCPLine::kItemMinimize, rect.height);
|
|
dest.y = rect.y + results.first;
|
|
dest.height = results.second;
|
|
}
|
|
|
|
void TrackInfo::GetSelectButtonHorizontalBounds( const wxRect &rect, wxRect &dest )
|
|
{
|
|
const int space = 0;// was 3.
|
|
dest.x = rect.x + space;
|
|
|
|
wxRect syncLockRect;
|
|
GetSyncLockHorizontalBounds( rect, syncLockRect );
|
|
wxRect minimizeRect;
|
|
GetMinimizeHorizontalBounds( rect, minimizeRect );
|
|
|
|
dest.x = dest.x + space + minimizeRect.width;
|
|
// Width is rect.width less space on left for track select
|
|
// and on right for sync-lock icon.
|
|
dest.width = rect.width - (space + syncLockRect.width) - (space + minimizeRect.width);
|
|
}
|
|
|
|
|
|
void TrackInfo::GetSelectButtonRect(const wxRect & rect, wxRect &dest)
|
|
{
|
|
GetSelectButtonHorizontalBounds( rect, dest );
|
|
auto results = CalcBottomItemY
|
|
( commonTrackTCPBottomLines, TCPLine::kItemMinimize, rect.height);
|
|
dest.y = rect.y + results.first;
|
|
dest.height = results.second;
|
|
}
|
|
|
|
void TrackInfo::GetSyncLockHorizontalBounds( const wxRect &rect, wxRect &dest )
|
|
{
|
|
dest.width = kTrackInfoBtnSize;
|
|
dest.x = rect.x + rect.width - dest.width;
|
|
}
|
|
|
|
void TrackInfo::GetSyncLockIconRect(const wxRect & rect, wxRect &dest)
|
|
{
|
|
GetSyncLockHorizontalBounds( rect, dest );
|
|
auto results = CalcBottomItemY
|
|
( commonTrackTCPBottomLines, TCPLine::kItemSyncLock, rect.height);
|
|
dest.y = rect.y + results.first;
|
|
dest.height = results.second;
|
|
}
|
|
|
|
/// \todo Probably should move to 'Utils.cpp'.
|
|
void TrackInfo::SetTrackInfoFont(wxDC * dc)
|
|
{
|
|
dc->SetFont(settings().gFont);
|
|
}
|
|
|
|
//#define USE_BEVELS
|
|
|
|
unsigned TrackInfo::DefaultTrackHeight( const TCPLines &topLines )
|
|
{
|
|
int needed =
|
|
kTopMargin + kBottomMargin +
|
|
totalTCPLines( topLines, true ) +
|
|
totalTCPLines( commonTrackTCPBottomLines, false ) + 1;
|
|
return (unsigned) std::max( needed, (int) TrackView::DefaultHeight );
|
|
}
|