1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-17 16:40:07 +02:00
audacity/src/widgets/ASlider.cpp
BusinessmanProgrammerSteve b51db1586b Support mice with high-precision scroll wheels
fixes P2: Ctrl+Mouse Wheel causes Crash (hang)
2010-01-25 22:44:48 +00:00

1738 lines
42 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ASlider.cpp
Dominic Mazzoni
*******************************************************************//**
\class ASlider
\brief ASlider is a custom slider, allowing for a slicker look and
feel.
It allows you to use images for the slider background and
the thumb.
*//****************************************************************//**
\class LWSlider
\brief Lightweight version of ASlider. In other words it does not
have a window permanently associated with it.
*//****************************************************************//**
\class SliderDialog
\brief Pop up dialog used with an LWSlider.
*//****************************************************************//**
\class TipPanel
\brief A wxPanel or a wxPopupWindow used to give the numerical value
of an LWSlider or ASlider.
*//*******************************************************************/
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
// Include your minimal set of headers here, or wx.h
#include <wx/window.h>
#endif
#include "../Audacity.h"
#include <math.h>
#include <wx/defs.h>
#include <wx/dcclient.h>
#include <wx/dcmemory.h>
#include <wx/image.h>
#include <wx/msgdlg.h>
#include <wx/panel.h>
#include <wx/tooltip.h>
#include <wx/debug.h>
#include <wx/textctrl.h>
#include <wx/valtext.h>
#include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/statline.h>
#include <wx/sizer.h>
#if defined(__WXMSW__) && !defined(__CYGWIN__)
#define USE_POPUPWIN 1
#endif
#if USE_POPUPWIN
#include <wx/popupwin.h>
#endif
#if defined(__WXMAC__)
#include <wx/sysopt.h>
#endif
#include "ASlider.h"
#include "../Experimental.h"
#include "../AColor.h"
#include "../ImageManipulation.h"
#include "../Project.h"
#include "../ShuttleGui.h"
#include "../Theme.h"
#include "../AllThemeResources.h"
//#include "../../images/SliderThumb.xpm"
#include "../../images/SliderThumbAlpha.xpm"
#include "../../images/SliderThumbDisabled.xpm"
#include "../../images/SliderThumb_Vertical.xpm"
#include "../../images/SliderThumb_VerticalAlpha.xpm"
#include "../../images/SliderThumb_VerticalDisabled.xpm"
#if defined __WXMSW__
const int sliderFontSize = 10;
#else
const int sliderFontSize = 12;
#endif
//
// TipPanel
//
#if USE_POPUPWIN
class TipPanel : public wxPopupWindow
#elif defined(__WXMAC__)
// On the Mac, we use a wxFrame as a wxPanel will appear to fall behind
// whatever window it is hovering above if that window is refreshed. A frame
// does not have this problem. One unfortunate side effect is that a shadow
// now appears below the tip panel.
class TipPanel : public wxFrame
#else
class TipPanel : public wxPanel
#endif
{
public:
TipPanel(wxWindow * parent, wxWindowID id,
wxString label,
const wxPoint &pos);
void SetPos(const wxPoint &pos);
void OnPaint(wxPaintEvent & event);
#if defined(__WXMAC__)
bool Show(bool show);
#endif
wxString label;
wxString origLabel;
wxWindow *mParent;
DECLARE_EVENT_TABLE()
};
#if USE_POPUPWIN
BEGIN_EVENT_TABLE(TipPanel, wxPopupWindow)
EVT_PAINT(TipPanel::OnPaint)
END_EVENT_TABLE()
TipPanel::TipPanel(wxWindow *parent, wxWindowID id,
wxString label, const wxPoint &pos):
wxPopupWindow(parent)
{
this->label = label;
this->origLabel = label;
mParent = parent;
SetPos(pos);
}
void TipPanel::SetPos(const wxPoint& pos)
{
int x = pos.x;
int y = pos.y;
if (mParent)
mParent->ClientToScreen(&x,&y);
wxClientDC dc(this);
wxFont labelFont(sliderFontSize, wxSWISS, wxNORMAL, wxNORMAL);
dc.SetFont(labelFont);
int width, height;
dc.GetTextExtent(origLabel, &width, &height);
height += 4;
SetSize(x - width/2, y, width, height);
}
#else
#if defined(__WXMAC__)
BEGIN_EVENT_TABLE(TipPanel, wxFrame)
EVT_PAINT(TipPanel::OnPaint)
END_EVENT_TABLE()
TipPanel::TipPanel(wxWindow *parent, wxWindowID id,
wxString label,
const wxPoint &pos):
wxFrame(parent, id, wxEmptyString, wxDefaultPosition, wxDefaultSize,
wxNO_BORDER | wxFRAME_NO_TASKBAR)
#else
BEGIN_EVENT_TABLE(TipPanel, wxPanel)
EVT_PAINT(TipPanel::OnPaint)
END_EVENT_TABLE()
TipPanel::TipPanel(wxWindow *parent, wxWindowID id,
wxString label,
const wxPoint &pos):
wxPanel(parent, id)
#endif
{
this->label = label;
this->origLabel = label;
SetPos(pos);
}
void TipPanel::SetPos(const wxPoint& pos)
{
wxClientDC dc(this);
wxFont labelFont(sliderFontSize, wxSWISS, wxNORMAL, wxNORMAL);
dc.SetFont(labelFont);
int width, height;
dc.GetTextExtent(origLabel, &width, &height);
width += 4;
height += 4;
int left = pos.x - width/2;
if (left < 0)
left = 0;
SetSize(left, pos.y, width, height);
Raise();
}
#if defined(__WXMAC__)
bool TipPanel::Show(bool show)
{
// Save original transition
int trans = wxSystemOptions::GetOptionInt( wxMAC_WINDOW_PLAIN_TRANSITION );
// Disable window animation
wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, 1 );
// Show/Hide the window
bool shown = wxFrame::Show(show);
// Disable window animation
wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, trans );
return shown;
}
#endif
#endif
void TipPanel::OnPaint(wxPaintEvent& event)
{
wxPaintDC dc(this);
int width, height, textWidth, textHeight;
wxFont labelFont(sliderFontSize, wxSWISS, wxNORMAL, wxNORMAL);
dc.SetFont(labelFont);
GetClientSize(&width, &height);
#if defined(__WXMAC__)
dc.SetPen(AColor::tooltipBrush.GetColour());
#else
dc.SetPen(*wxBLACK_PEN);
#endif
dc.SetBrush(AColor::tooltipBrush);
dc.DrawRectangle(0, 0, width, height);
dc.GetTextExtent(label, &textWidth, &textHeight);
dc.DrawText(label, (width-textWidth)/2, (height-textHeight)/2);
}
//
// SliderDialog
//
BEGIN_EVENT_TABLE(SliderDialog, wxDialog)
EVT_SLIDER(wxID_ANY,SliderDialog::OnSlider)
END_EVENT_TABLE();
SliderDialog::SliderDialog(wxWindow * parent, wxWindowID id,
const wxString & title,
wxPoint position,
wxSize size,
int style,
float value,
float line,
float page):
wxDialog(parent,id,title,position),
mStyle(style)
{
ShuttleGui S(this, eIsCreating);
S.StartVerticalLay();
{
mTextCtrl = S.AddTextBox(wxEmptyString,
wxEmptyString,
15);
mTextCtrl->SetValidator(wxTextValidator(wxFILTER_NUMERIC));
mSlider = new ASlider(this,
wxID_ANY,
title,
wxDefaultPosition,
size,
style,
false);
mSlider->SetScroll(line, page);
S.AddWindow(mSlider, wxEXPAND);
}
S.EndVerticalLay();
S.AddStandardButtons(eOkButton | eCancelButton);
Fit();
mSlider->Set(value);
}
SliderDialog::~SliderDialog()
{
}
bool SliderDialog::TransferDataToWindow()
{
mTextCtrl->SetValue(wxString::Format(wxT("%g"), mSlider->Get(false)));
mTextCtrl->SetSelection(-1, -1);
return true;
}
bool SliderDialog::TransferDataFromWindow()
{
double value;
mTextCtrl->GetValue().ToDouble(&value);
if (mStyle == DB_SLIDER)
value = pow(10.0, value / 20.0);
mSlider->Set(value);
return true;
}
void SliderDialog::OnSlider(wxCommandEvent & event)
{
TransferDataToWindow();
event.Skip(false);
}
float SliderDialog::Get()
{
return mSlider->Get(false);
}
//
// LWSlider
//
// Construct customizable slider
LWSlider::LWSlider(wxWindow * parent,
wxString name,
const wxPoint &pos,
const wxSize &size,
float minValue,
float maxValue,
float stepValue,
bool canUseShift,
int style,
bool heavyweight /* = false */,
bool popup /* = true */,
int orientation /* = wxHORIZONTAL */) // wxHORIZONTAL or wxVERTICAL. wxVERTICAL is currently only for DB_SLIDER.
{
Init(parent, name, pos, size, minValue, maxValue,
stepValue, canUseShift, style, heavyweight, popup, 1.0, orientation);
}
// Construct predefined slider
LWSlider::LWSlider(wxWindow *parent,
wxString name,
const wxPoint &pos,
const wxSize &size,
int style,
bool heavyweight /* = false */,
bool popup /* = true */,
int orientation /* = wxHORIZONTAL */) // wxHORIZONTAL or wxVERTICAL. wxVERTICAL is currently only for DB_SLIDER.
{
wxString leftLabel, rightLabel;
float minValue, maxValue, stepValue;
float speed = 1.0;
switch(style)
{
case PAN_SLIDER:
minValue = -1.0f;
maxValue = +1.0f;
stepValue = 0.1f;
orientation = wxHORIZONTAL; //v Vertical PAN_SLIDER currently not handled, forced to horizontal.
break;
case DB_SLIDER:
minValue = -36.0f;
if (orientation == wxHORIZONTAL)
maxValue = 36.0f;
else
maxValue = 36.0f; // for MixerBoard //vvv Previously was 6dB for MixerBoard, but identical for now.
stepValue = 1.0f;
speed = 0.5;
break;
case FRAC_SLIDER:
minValue = 0.0f;
maxValue = 1.0f;
stepValue = STEP_CONTINUOUS;
break;
case SPEED_SLIDER:
minValue = 0.01f;
maxValue = 3.0f;
stepValue = STEP_CONTINUOUS;
break;
default:
minValue = 0.0f;
maxValue = 1.0f;
stepValue = 0.0f;
wxASSERT(false); // undefined style
}
Init(parent, name, pos, size, minValue, maxValue, stepValue,
true, style, heavyweight, popup, speed, orientation);
}
void LWSlider::Init(wxWindow * parent,
wxString name,
const wxPoint &pos,
const wxSize &size,
float minValue,
float maxValue,
float stepValue,
bool canUseShift,
int style,
bool heavyweight,
bool popup,
float speed,
int orientation /* = wxHORIZONTAL */) // wxHORIZONTAL or wxVERTICAL. wxVERTICAL is currently only for DB_SLIDER.
{
mEnabled = true;
mName = name;
mStyle = style;
mOrientation = orientation;
mIsDragging = false;
mWidth = size.x;
mHeight = size.y;
mParent = parent;
mHW = heavyweight;
mPopup = popup;
mSpeed = speed;
mID = wxID_ANY;
mMinValue = minValue;
mMaxValue = maxValue;
mStepValue = stepValue;
mCanUseShift = canUseShift;
mCurrentValue = 0.0f;
mDefaultValue = 0.0f;
mDefaultShortcut = false;
mBitmap = NULL;
mScrollLine = 1.0f;
mScrollPage = 5.0f;
mpRuler = NULL; // Do this and Move() before Draw().
Move(pos);
Draw();
mPopWin = NULL;
CreatePopWin();
}
LWSlider::~LWSlider()
{
delete mBitmap;
if (mOrientation == wxVERTICAL && mThumbBitmap) {
delete mThumbBitmap;
}
delete mPopWin;
delete mpRuler;
}
wxWindowID LWSlider::GetId()
{
return mID;
}
void LWSlider::SetId(wxWindowID id)
{
mID = id;
}
void LWSlider::SetDefaultValue(float value)
{
SetDefaultShortcut(true);
mDefaultValue = value;
}
void LWSlider::SetDefaultShortcut(bool value)
{
mDefaultShortcut = value;
}
void LWSlider::GetScroll(float & line, float & page)
{
line = mScrollLine;
page = mScrollPage;
}
void LWSlider::SetScroll(float line, float page)
{
mScrollLine = line;
mScrollPage = page;
}
wxWindow* LWSlider::GetToolTipParent() const
{
wxWindow *top = mParent;
while(top && top->GetParent()) {
top = top->GetParent();
}
return top;
}
void LWSlider::CreatePopWin()
{
if (mPopWin)
return;
wxString maxStr = mName + wxT(": 000000");
if (mStyle == PAN_SLIDER || mStyle == DB_SLIDER || mStyle == SPEED_SLIDER)
maxStr += wxT("000");
mPopWin = new TipPanel(GetToolTipParent(), -1, maxStr, wxDefaultPosition);
mPopWin->Hide();
}
void LWSlider::SetPopWinPosition()
{
wxPoint pt;
if (mOrientation == wxHORIZONTAL)
pt = wxPoint(mWidth/2 + mLeft, mHeight + mTop + 1);
else
pt = wxPoint(mWidth + mLeft + 1, mHeight/2 + mTop);
pt = mParent->ClientToScreen(pt);
#if !defined(__WXMAC__)
if (GetToolTipParent())
pt = GetToolTipParent()->ScreenToClient(pt);
#endif
if (mPopWin)
((TipPanel *)mPopWin)->SetPos(pt);
}
void LWSlider::Move(const wxPoint &newpos)
{
mLeft = newpos.x;
mTop = newpos.y;
}
void LWSlider::RecreateTipWin()
{
delete mPopWin;
mPopWin = NULL;
CreatePopWin();
}
void LWSlider::OnPaint(wxDC &dc, bool selected)
{
//thumbPos should be in pixels
int thumbPos = ValueToPosition(mCurrentValue);
int thumbOrtho; // position in axis orthogonal to mOrientation
if (mOrientation == wxHORIZONTAL)
thumbOrtho = mCenterY - (mThumbHeight/2);
else
thumbOrtho = mCenterX - (mThumbWidth/2);
#if defined(__WXMSW__)
if( mHW )
{
dc.Clear();
}
#endif
dc.DrawBitmap(*mBitmap, mLeft, mTop, true);
if (mOrientation == wxHORIZONTAL)
dc.DrawBitmap(*mThumbBitmap, mLeft+thumbPos, mTop+thumbOrtho, true);
else
dc.DrawBitmap(*mThumbBitmap, mLeft+thumbOrtho, mTop+thumbPos, true);
if (mPopWin)
mPopWin->Refresh();
}
void LWSlider::OnSize( wxSizeEvent & event )
{
mWidth = event.GetSize().GetX();
mHeight = event.GetSize().GetY();
Draw();
Refresh();
}
void LWSlider::Draw()
{
//
// Get the thumb slider bitmap
//
if (mEnabled && mOrientation == wxHORIZONTAL)
mThumbBitmap = &theTheme.Bitmap( bmpSliderThumb );
//vvv \todo Convert this to an image in AllThemeResources, as bmpSliderThumb. Make an alpha also, as for horizontal slider thumb?
else if (mOrientation == wxHORIZONTAL)
{
wxImage thumbImage(wxBitmap(SliderThumbDisabled).ConvertToImage());
mThumbBitmap = new wxBitmap(thumbImage);
mThumbBitmap->SetMask(new wxMask(wxBitmap(SliderThumbAlpha), *wxBLACK));
}
else if (mEnabled)
{
wxImage thumbImage(wxBitmap(SliderThumb_Vertical).ConvertToImage());
mThumbBitmap = new wxBitmap(thumbImage);
mThumbBitmap->SetMask(
new wxMask(wxBitmap(SliderThumb_VerticalAlpha), *wxBLACK));
}
else
{
wxImage thumbImage(wxBitmap(
SliderThumb_VerticalDisabled).ConvertToImage());
mThumbBitmap = new wxBitmap(thumbImage);
mThumbBitmap->SetMask(
new wxMask(wxBitmap(SliderThumb_VerticalAlpha), *wxBLACK));
}
// mThumbBitmap = new wxBitmap( SliderThumb );
// mThumbBitmap->SetMask( new wxMask( wxBitmap( SliderThumbAlpha ), *wxBLACK ) );
//
// Now the background bitmap
//
if( mBitmap )
{
delete mBitmap;
mBitmap = NULL;
}
if (mOrientation == wxHORIZONTAL)
mCenterY = mHeight - 9;
else
mCenterX = mWidth - 9;
mThumbWidth = mThumbBitmap->GetWidth();
mThumbHeight = mThumbBitmap->GetHeight();
if (mOrientation == wxHORIZONTAL)
{
mLeftX = mThumbWidth/2;
mRightX = mWidth - mThumbWidth/2 - 1;
mWidthX = mRightX - mLeftX;
}
else
{
mTopY = mThumbWidth/2;
mBottomY = mHeight - mThumbWidth/2 - 1;
mHeightY = mBottomY - mTopY;
}
wxMemoryDC *dc = new wxMemoryDC();
mBitmap = new wxBitmap(mWidth, mHeight);
// Set background to an unused color. This allows for easy
// mask creation. And garbage can be displayed if it isn't
// cleared.
dc->SelectObject(*mBitmap);
wxColour TransparentColour = wxColour( 255, 254, 255 );
// DO-THEME Mask colour!! JC-Aug-2007
// Needed with experimental theming!
// ... because windows blends against this colour.
#ifdef EXPERIMENTAL_THEMING
TransparentColour = theTheme.Colour( clrTrackInfo );
#endif
dc->SetBackground( wxBrush( TransparentColour ) );
dc->Clear();
// Draw the line along which the thumb moves.
if (mEnabled)
AColor::Dark(dc, false);
else
AColor::Medium(dc, false);
if (mOrientation == wxHORIZONTAL)
AColor::Line(*dc, mLeftX, mCenterY+1, mRightX+2, mCenterY+1);
else //vvvvv if (mStyle != DB_SLIDER) // Let the ruler do it for vertical DB_SLIDER.
AColor::Line(*dc, mCenterX+1, mTopY, mCenterX+1, mBottomY+2);
// Draw +/- or L/R first. We need to draw these before the tick marks.
if (mStyle == PAN_SLIDER)
{
//v Vertical PAN_SLIDER currently not handled, forced to horizontal.
// sliderFontSize is for the tooltip.
// we need something smaller here...
wxFont labelFont(sliderFontSize-3, wxSWISS, wxNORMAL, wxNORMAL);
dc->SetFont(labelFont);
#ifdef EXPERIMENTAL_THEMING
dc->SetTextForeground( theTheme.Colour( clrTrackPanelText ));
// TransparentColour should be same as clrTrackInfo.
dc->SetTextBackground( theTheme.Colour( clrTrackInfo ) );
dc->SetBackground( theTheme.Colour( clrTrackInfo ) );
// HAVE to use solid and not transparent here,
// otherwise windows will do it's clever font optimisation trick,
// but against a default colour of white, which is not OK on a dark
// background.
dc->SetBackgroundMode( wxSOLID );
#else
dc->SetTextForeground( wxColour( 0,0,0) );
dc->SetTextBackground( wxColour( 255,255,255));
#endif
/* i18n-hint: One-letter abbreviation for Left, in the Pan slider */
dc->DrawText(_("L"), mLeftX, 0);
/* i18n-hint: One-letter abbreviation for Right, in the Pan slider */
dc->DrawText(_("R"), mRightX-6,0);
} else
{
// draw the '-' and the '+'
#ifdef EXPERIMENTAL_THEMING
wxPen pen( theTheme.Colour( clrTrackPanelText ));
dc->SetPen( pen );
#else
dc->SetPen(*wxBLACK_PEN);
#endif
if (mOrientation == wxHORIZONTAL)
{
AColor::Line(*dc, mLeftX, mCenterY-10, mLeftX+4, mCenterY-10);
AColor::Line(*dc, mRightX-5, mCenterY-10, mRightX-1, mCenterY-10);
AColor::Line(*dc, mRightX-3, mCenterY-12, mRightX-3, mCenterY-8);
}
else
{
// Vertical DB_SLIDER is for gain slider in MixerBoard.
// We use a Ruler instead of marks & ticks.
// Draw '+' and '-' only for other vertical sliders.
if (mStyle != DB_SLIDER)
{
AColor::Line(*dc, mCenterX-12, mBottomY-3, mCenterX-8, mBottomY-3);
AColor::Line(*dc, mCenterX-12, mTopY+3, mCenterX-8, mTopY+3);
AColor::Line(*dc, mCenterX-10, mTopY, mCenterX-10, mTopY+5);
}
}
}
//vvvvv 20090820: Ruler doesn't align with slider correctly -- yet.
//if ((mOrientation == wxVERTICAL) && (mStyle == DB_SLIDER))
//{
// if (!mpRuler)
// {
// mpRuler = new Ruler();
// mpRuler->mbTicksOnly = false;
// mpRuler->mbTicksAtExtremes = true;
// #ifdef __WXMSW__
// const int kFontSize = 8;
// #else
// const int kFontSize = 10;
// #endif
// wxFont rulerFont(kFontSize, wxSWISS, wxNORMAL, wxNORMAL);
// mpRuler->SetFonts(rulerFont, rulerFont, rulerFont);
// mpRuler->SetFlip(false);
// mpRuler->SetLabelEdges(true);
// mpRuler->SetOrientation(wxVERTICAL);
// mpRuler->SetRange(mMaxValue, mMinValue);
// mpRuler->SetFormat(Ruler::LinearDBFormat);
// }
// mpRuler->SetBounds(mLeft, mTop, mWidth, mHeightY); //vvvvv Why the magic number reqd on height to get it to line up? + 9);
// mpRuler->Draw(*dc);
//}
//else
{
// tick marks
int divs = 10;
double upp;
if (mOrientation == wxHORIZONTAL)
upp = divs / (double)(mWidthX-1);
else
{
if (mStyle == DB_SLIDER)
divs = mMaxValue - mMinValue + 1;
upp = divs / (double)(mHeightY-1);
}
double d = 0.0;
int int_d = -1;
const int kMax = (mOrientation == wxHORIZONTAL) ? mWidthX : mHeightY;
for(int p = 0; p <= kMax; p++) {
if (((int)d) > int_d) {
int_d = (int)d;
int tickLength = ((int_d == 0) || (int_d == divs)) ? 5: 3; // longer ticks at extremes
AColor::Light(dc, false);
if (mOrientation == wxHORIZONTAL)
AColor::Line(*dc, mLeftX+p, mCenterY-tickLength, mLeftX+p, mCenterY-1); // ticks above
else
AColor::Line(*dc, mCenterX-tickLength, mTopY+p, mCenterX-1, mTopY+p); // ticks at left
if (mEnabled)
AColor::Dark(dc, false);
else
AColor::Medium(dc, false);
if (mOrientation == wxHORIZONTAL)
AColor::Line(*dc, mLeftX+p+1, mCenterY-tickLength+1, mLeftX+p+1, mCenterY-1); // ticks above
else
AColor::Line(*dc, mCenterX-tickLength+1, mTopY+p+1, mCenterX-1, mTopY+p+1); // ticks at left
}
d += upp;
}
}
// Must preceed creating the mask as that will attempt to
// select the bitmap into another DC.
delete dc;
mBitmap->SetMask( new wxMask( *mBitmap, TransparentColour ) );
}
void LWSlider::FormatPopWin()
{
wxString label;
wxString valstr;
switch(mStyle) {
case FRAC_SLIDER:
label.Printf(wxT("%s: %.1f"), mName.c_str(), mCurrentValue);
break;
case DB_SLIDER:
valstr.Printf(wxT("%.1f"), mCurrentValue);
if (valstr.Right(1) == wxT("0"))
valstr = valstr.Left(valstr.Length() - 2);
if (mCurrentValue > 0)
valstr = wxT("+") + valstr;
label.Printf(wxT("%s: %s dB"), mName.c_str(), valstr.c_str());
break;
case PAN_SLIDER:
if (mCurrentValue == 0.0)
label.Printf(wxT("%s: %s"), mName.c_str(),
_("Center"));
else {
if (mCurrentValue < 0.0)
label.Printf(wxT("%s: %.0f%% %s"), mName.c_str(),
-mCurrentValue * 100.0f, _("Left"));
else /* if (val > 0.0) */
label.Printf(wxT("%s: %.0f%% %s"), mName.c_str(),
mCurrentValue * 100.0f, _("Right"));
}
break;
case SPEED_SLIDER:
label.Printf(wxT("%s: %.2fx"), mName.c_str(), mCurrentValue);
break;
}
((TipPanel *)mPopWin)->label = label;
}
bool LWSlider::ShowDialog()
{
return DoShowDialog( mParent->ClientToScreen(wxPoint( mLeft, mTop ) ) );
}
bool LWSlider::ShowDialog(wxPoint pos)
{
return DoShowDialog( pos );
}
bool LWSlider::DoShowDialog(wxPoint pos)
{
float value;
bool changed = false;
SliderDialog dlg( NULL,
wxID_ANY,
mName,
pos,
wxSize( mWidth, mHeight ),
mStyle,
Get(),
mScrollLine,
mScrollPage);
if (pos == wxPoint(-1, -1)) {
dlg.Center();
}
if( dlg.ShowModal() == wxID_OK )
{
value = dlg.Get();
if( value != mCurrentValue )
{
mCurrentValue = value;
changed = true;
}
}
return changed;
}
void LWSlider::OnMouseEvent(wxMouseEvent & event)
{
if (event.Entering()) {
#if wxUSE_TOOLTIPS // Not available in wxX11
// Display the tooltip in the status bar
if (mParent->GetToolTip())
{
wxString tip = mParent->GetToolTip()->GetTip();
GetActiveProject()->TP_DisplayStatusMessage(tip);
Refresh();
}
#endif
}
else if (event.Leaving())
{
GetActiveProject()->TP_DisplayStatusMessage(wxT(""));
Refresh();
}
// Events other than mouse-overs are ignored when we are disabled
if (!mEnabled)
return;
float prevValue = mCurrentValue;
// Figure out the thumb position
wxRect r;
if (mOrientation == wxHORIZONTAL)
{
r.x = mLeft + ValueToPosition(mCurrentValue);
r.y = mTop + (mCenterY - (mThumbHeight / 2));
}
else
{
r.x = mLeft + (mCenterX - (mThumbWidth / 2));
r.y = mTop + ValueToPosition(mCurrentValue);
}
r.width = mThumbWidth;
r.height = mThumbHeight;
wxRect tolerantThumbRect = r;
tolerantThumbRect.Inflate(3, 3);
// Should probably use a right click instead/also
if( event.ButtonDClick() && mPopup )
{
//On a double-click, we should pop up a dialog.
DoShowDialog(mParent->ClientToScreen(wxPoint(event.m_x,event.m_y)));
}
else if( event.ButtonDown() )
{
if( mDefaultShortcut && event.CmdDown() )
{
mCurrentValue = mDefaultValue;
}
if( event.RightDown() ) {
mParent->SetFocus();
}
// Thumb clicked?
//
// Do not change position until first drag. This helps
// with unintended value changes.
if( tolerantThumbRect.Contains( event.GetPosition() ) )
{
// Remember mouse position and current value
mClickPos = (mOrientation == wxHORIZONTAL) ? event.m_x : event.m_y;
mClickValue = mCurrentValue;
mIsDragging = true;
}
// Clicked to set location?
else
{
mCurrentValue =
ClickPositionToValue(
(mOrientation == wxHORIZONTAL) ? event.m_x : event.m_y,
event.ShiftDown());
}
mParent->CaptureMouse();
// wxSetCursor(wxCURSOR_BLANK);
FormatPopWin();
SetPopWinPosition();
mPopWin->Show();
}
else if( event.ButtonUp() )
{
mIsDragging = false;
if (mParent->HasCapture())
mParent->ReleaseMouse();
mPopWin->Hide();
((TipPanel *)mPopWin)->SetPos(wxPoint(-32000, -32000));
// wxSetCursor(wxNullCursor);
}
else if (event.Dragging() && mIsDragging)
{
if (mOrientation == wxHORIZONTAL)
{
if (event.m_y < (r.y - 2 * r.height) ||
event.m_y > (r.y + 3 * r.height)) {
// If the mouse y coordinate is relatively far from the slider,
// snap back to the original position
mCurrentValue = mClickValue;
}
else {
// Otherwise, move the slider to the right position based
// on the mouse position
mCurrentValue = DragPositionToValue(event.m_x, event.ShiftDown());
}
}
else // (mOrientation == wxVERTICAL)
{
if (event.m_x < (r.x - 2 * r.width) ||
event.m_x > (r.x + 3 * r.width)) {
// If the mouse x coordinate is relatively far from the slider,
// snap back to the original position
mCurrentValue = mClickValue;
}
else {
// Otherwise, move the slider to the right position based
// on the mouse position
mCurrentValue = DragPositionToValue(event.m_y, event.ShiftDown());
}
}
}
else if( event.m_wheelRotation != 0 )
{
//Calculate the number of steps in a given direction this event
//represents (allows for two or more clicks on a single event.)
double steps = event.m_wheelRotation /
(event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0);
if( steps < 0.0 )
{
Decrease( (float)-steps );
}
else
{
Increase( (float)steps );
}
SendUpdate( mCurrentValue );
}
if( prevValue != mCurrentValue )
SendUpdate( mCurrentValue );
}
void LWSlider::OnKeyEvent(wxKeyEvent & event)
{
if (mEnabled)
{
switch( event.GetKeyCode() )
{
case WXK_RIGHT:
case WXK_UP:
Increase( mScrollLine );
SendUpdate( mCurrentValue );
break;
case WXK_LEFT:
case WXK_DOWN:
Decrease( mScrollLine );
SendUpdate( mCurrentValue );
break;
case WXK_PAGEUP:
#if !wxCHECK_VERSION(2,7,0)
case WXK_PRIOR:
#endif
Increase( mScrollPage );
SendUpdate( mCurrentValue );
break;
case WXK_PAGEDOWN:
#if !wxCHECK_VERSION(2,7,0)
case WXK_NEXT:
#endif
Decrease( mScrollPage );
SendUpdate( mCurrentValue );
break;
case WXK_HOME:
SendUpdate( mMinValue );
break;
case WXK_END:
SendUpdate( mMaxValue );
break;
case WXK_TAB:
{
wxNavigationKeyEvent nevent;
nevent.SetWindowChange( event.ControlDown() );
nevent.SetDirection( !event.ShiftDown() );
nevent.SetEventObject( mParent );
nevent.SetCurrentFocus( mParent );
mParent->GetParent()->ProcessEvent( nevent );
}
break;
case WXK_RETURN:
case WXK_NUMPAD_ENTER:
{
wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(mParent), wxTopLevelWindow);
wxWindow *def = tlw->GetDefaultItem();
if (def && def->IsEnabled()) {
wxCommandEvent cevent(wxEVT_COMMAND_BUTTON_CLICKED,
def->GetId());
mParent->ProcessEvent(cevent);
}
}
default:
// Allow it to propagate
event.Skip();
break;
}
}
event.Skip();
}
void LWSlider::SendUpdate( float newValue )
{
mCurrentValue = newValue;
FormatPopWin();
mPopWin->Refresh();
Refresh();
wxCommandEvent e( wxEVT_COMMAND_SLIDER_UPDATED, mID );
int intValue = (int)( ( mCurrentValue - mMinValue ) * 1000.0f /
( mMaxValue - mMinValue ) );
e.SetInt( intValue );
mParent->ProcessEvent( e );
}
int LWSlider::ValueToPosition(float val)
{
float fRange = mMaxValue - mMinValue;
if (mOrientation == wxHORIZONTAL)
return (int)rint((val - mMinValue) * mWidthX / fRange);
else
// low values at bottom
return (int)rint((mMaxValue - val) * mHeightY / fRange);
}
void LWSlider::SetSpeed(float speed)
{
mSpeed = speed;
}
// Given the mouse slider coordinate in fromPos, compute the new value
// of the slider when clicking to set a new position.
float LWSlider::ClickPositionToValue(int fromPos, bool shiftDown)
{
int nSpan;
int pos;
if (mOrientation == wxHORIZONTAL)
{
pos = (fromPos - mLeft - (mThumbWidth / 2));
nSpan = mWidthX;
}
else
{
// wxVERTICAL => Low values at bottom.
pos = mBottomY - fromPos;
nSpan = mHeightY;
}
// MM: Special cases: If position is at the very left or the
// very right (or top/bottom for wxVERTICAL), set minimum/maximum value without other checks
if (pos <= 0)
return mMinValue;
if (pos >= nSpan)
return mMaxValue;
float val = (pos / (float)nSpan)
* (mMaxValue - mMinValue) + mMinValue;
if (val < mMinValue)
val = mMinValue;
if (val > mMaxValue)
val = mMaxValue;
if (!(mCanUseShift && shiftDown) && mStepValue != STEP_CONTINUOUS)
{
// MM: If shift is not down, or we don't allow usage
// of shift key at all, trim value to steps of
// provided size.
val = (int)(val / mStepValue + 0.5 * (val>0?1.0f:-1.0f)) * mStepValue;
}
return val;
}
// Given the mouse slider coordinate in fromPos, compute the new value
// of the slider during a drag.
float LWSlider::DragPositionToValue(int fromPos, bool shiftDown)
{
int delta = (fromPos - mClickPos);
float speed = mSpeed;
// Precision enhancement for Shift drags
if (mCanUseShift && shiftDown)
speed *= 0.4f;
// wxVERTICAL => Low values at bottom, so throw in the minus sign here with -mHeightY.
float denominator = (mOrientation == wxHORIZONTAL) ? mWidthX : -mHeightY;
float val = mClickValue +
speed * (delta / denominator) * (mMaxValue - mMinValue);
if (val < mMinValue)
val = mMinValue;
if (val > mMaxValue)
val = mMaxValue;
if (!(mCanUseShift && shiftDown) && mStepValue != STEP_CONTINUOUS)
{
// MM: If shift is not down, or we don't allow usage
// of shift key at all, and the slider has not continuous values,
// trim value to steps of provided size.
val = (int)(val / mStepValue + 0.5 * (val>0?1.0f:-1.0f)) * mStepValue;
}
return val;
}
float LWSlider::Get( bool convert )
{
if (mStyle == DB_SLIDER)
return ( convert ? pow(10.0f, mCurrentValue / 20.0f) : mCurrentValue );
else
return mCurrentValue;
}
void LWSlider::Set(float value)
{
if (mIsDragging)
return;
if (mStyle == DB_SLIDER)
mCurrentValue = 20.0f*log10(value);
else
mCurrentValue = value;
if (mCurrentValue < mMinValue)
mCurrentValue = mMinValue;
if (mCurrentValue > mMaxValue)
mCurrentValue = mMaxValue;
Refresh();
}
void LWSlider::Increase(float steps)
{
float stepValue = mStepValue;
if ( stepValue == 0.0 )
{
stepValue = ( mMaxValue - mMinValue ) / 10.0;
}
mCurrentValue += ( steps * stepValue );
if ( mCurrentValue < mMinValue )
{
mCurrentValue = mMinValue;
}
else if ( mCurrentValue > mMaxValue )
{
mCurrentValue = mMaxValue;
}
Refresh();
}
void LWSlider::Decrease(float steps)
{
float stepValue = mStepValue;
if ( stepValue == 0.0 )
{
stepValue = ( mMaxValue - mMinValue ) / 10.0;
}
mCurrentValue -= ( steps * stepValue );
if ( mCurrentValue < mMinValue )
{
mCurrentValue = mMinValue;
}
else if ( mCurrentValue > mMaxValue )
{
mCurrentValue = mMaxValue;
}
Refresh();
}
void LWSlider::Refresh()
{
if (mHW)
mParent->Refresh(false);
}
bool LWSlider::GetEnabled()
{
return mEnabled;
}
void LWSlider::SetEnabled(bool enabled)
{
mEnabled = enabled;
Draw();
Refresh();
}
//
// ASlider
//
BEGIN_EVENT_TABLE(ASlider, wxWindow)
EVT_CHAR(ASlider::OnKeyEvent)
EVT_MOUSE_EVENTS(ASlider::OnMouseEvent)
EVT_MOUSE_CAPTURE_LOST(ASlider::OnCaptureLost)
EVT_PAINT(ASlider::OnPaint)
EVT_SIZE(ASlider::OnSize)
EVT_ERASE_BACKGROUND(ASlider::OnErase)
EVT_SLIDER(wxID_ANY, ASlider::OnSlider)
EVT_SET_FOCUS(ASlider::OnSetFocus)
EVT_KILL_FOCUS(ASlider::OnKillFocus)
END_EVENT_TABLE()
ASlider::ASlider( wxWindow * parent,
wxWindowID id,
wxString name,
const wxPoint & pos,
const wxSize & size,
int style,
bool popup,
bool canUseShift,
float stepValue,
int orientation /*= wxHORIZONTAL*/)
: wxPanel( parent, id, pos, size, wxWANTS_CHARS )
{
mLWSlider = new LWSlider( this,
name,
wxPoint(0,0),
size,
style,
canUseShift,
popup,
orientation);
mLWSlider->mStepValue = stepValue;
mLWSlider->SetId( id );
SetName( name );
mSliderIsFocused = false;
mStyle = style;
#if wxUSE_ACCESSIBILITY
SetAccessible( new ASliderAx( this ) );
#endif
}
ASlider::~ASlider()
{
delete mLWSlider;
}
void ASlider::OnSlider(wxCommandEvent &event)
{
if ( event.GetId() == mLWSlider->GetId() )
{
#if wxUSE_ACCESSIBILITY
GetAccessible()->NotifyEvent( wxACC_EVENT_OBJECT_VALUECHANGE,
this,
wxOBJID_CLIENT,
wxACC_SELF );
#endif
}
event.Skip();
}
void ASlider::OnSize(wxSizeEvent &event)
{
mLWSlider->OnSize( event );
}
void ASlider::OnErase(wxEraseEvent &event)
{
// Ignore it to prevent flashing
}
void ASlider::OnPaint(wxPaintEvent &event)
{
wxPaintDC dc(this);
#ifdef EXPERIMENTAL_THEMING
wxColour Col(GetParent()->GetBackgroundColour());
this->SetBackgroundColour( Col );
#endif
mLWSlider->OnPaint(dc, false);
if ( mSliderIsFocused )
{
wxRect r( 0, 0, mLWSlider->mWidth, mLWSlider->mHeight );
r.Deflate( 1, 1 );
AColor::DrawFocus( dc, r );
}
}
void ASlider::OnMouseEvent(wxMouseEvent &event)
{
mLWSlider->OnMouseEvent(event);
}
void ASlider::OnCaptureLost(wxMouseCaptureLostEvent &event)
{
wxMouseEvent e(wxEVT_LEFT_UP);
mLWSlider->OnMouseEvent(e);
}
void ASlider::OnKeyEvent(wxKeyEvent &event)
{
mLWSlider->OnKeyEvent(event);
}
void ASlider::OnSetFocus(wxFocusEvent & event)
{
mSliderIsFocused = true;
Refresh();
}
void ASlider::OnKillFocus(wxFocusEvent & event)
{
mSliderIsFocused = false;
Refresh();
}
void ASlider::RecreateTipWin()
{
mLWSlider->RecreateTipWin();
}
void ASlider::GetScroll(float & line, float & page)
{
mLWSlider->GetScroll(line, page);
}
void ASlider::SetScroll(float line, float page)
{
mLWSlider->SetScroll(line, page);
}
float ASlider::Get( bool convert )
{
return mLWSlider->Get( convert );
}
void ASlider::Set(float value)
{
mLWSlider->Set(value);
}
void ASlider::Increase(float steps)
{
mLWSlider->Increase(steps);
}
void ASlider::Decrease(float steps)
{
mLWSlider->Decrease(steps);
}
bool ASlider::ShowDialog(wxPoint pos)
{
return mLWSlider->ShowDialog(pos);
}
void ASlider::SetSpeed(float speed)
{
mLWSlider->SetSpeed(speed);
}
bool ASlider::Enable(bool enable)
{
if (mLWSlider->GetEnabled() == enable)
return false;
mLWSlider->SetEnabled(enable);
return true;
}
bool ASlider::IsEnabled() const
{
return mLWSlider->GetEnabled();
}
#if wxUSE_ACCESSIBILITY
ASliderAx::ASliderAx( wxWindow * window ) :
wxWindowAccessible( window )
{
}
ASliderAx::~ASliderAx()
{
}
// Retrieves the address of an IDispatch interface for the specified child.
// All objects must support this property.
wxAccStatus ASliderAx::GetChild( int childId, wxAccessible** child )
{
if ( childId == wxACC_SELF )
{
*child = this;
}
else
{
*child = NULL;
}
return wxACC_OK;
}
// Gets the number of children.
wxAccStatus ASliderAx::GetChildCount(int* childCount)
{
*childCount = 3;
return wxACC_OK;
}
// Gets the default action for this object (0) or > 0 (the action for a child).
// Return wxACC_OK even if there is no action. actionName is the action, or the empty
// string if there is no action.
// The retrieved string describes the action that is performed on an object,
// not what the object does as a result. For example, a toolbar button that prints
// a document has a default action of "Press" rather than "Prints the current document."
wxAccStatus ASliderAx::GetDefaultAction( int childId, wxString *actionName )
{
actionName->Clear();
return wxACC_OK;
}
// Returns the description for this object or a child.
wxAccStatus ASliderAx::GetDescription( int childId, wxString *description )
{
description->Clear();
return wxACC_OK;
}
// Gets the window with the keyboard focus.
// If childId is 0 and child is NULL, no object in
// this subhierarchy has the focus.
// If this object has the focus, child should be 'this'.
wxAccStatus ASliderAx::GetFocus(int* childId, wxAccessible** child)
{
*childId = 0;
*child = this;
return wxACC_OK;
}
// Returns help text for this object or a child, similar to tooltip text.
wxAccStatus ASliderAx::GetHelpText( int childId, wxString *helpText )
{
helpText->Clear();
return wxACC_OK;
}
// Returns the keyboard shortcut for this object or child.
// Return e.g. ALT+K
wxAccStatus ASliderAx::GetKeyboardShortcut( int childId, wxString *shortcut )
{
shortcut->Clear();
return wxACC_OK;
}
// Returns the rectangle for this object (id = 0) or a child element (id > 0).
// rect is in screen coordinates.
wxAccStatus ASliderAx::GetLocation( wxRect& rect, int elementId )
{
ASlider *as = wxDynamicCast( GetWindow(), ASlider );
rect = as->GetRect();
rect.SetPosition( as->GetParent()->ClientToScreen( rect.GetPosition() ) );
return wxACC_OK;
}
// Gets the name of the specified object.
wxAccStatus ASliderAx::GetName(int childId, wxString* name)
{
ASlider *as = wxDynamicCast( GetWindow(), ASlider );
*name = as->GetName();
return wxACC_OK;
}
// Returns a role constant.
wxAccStatus ASliderAx::GetRole(int childId, wxAccRole* role)
{
switch( childId )
{
case 0:
*role = wxROLE_SYSTEM_SLIDER;
break;
case 1:
case 3:
*role = wxROLE_SYSTEM_PUSHBUTTON;
break;
case 2:
*role = wxROLE_SYSTEM_INDICATOR;
break;
}
return wxACC_OK;
}
// Gets a variant representing the selected children
// of this object.
// Acceptable values:
// - a null variant (IsNull() returns TRUE)
// - a list variant (GetType() == wxT("list"))
// - an integer representing the selected child element,
// or 0 if this object is selected (GetType() == wxT("long"))
// - a "void*" pointer to a wxAccessible child object
wxAccStatus ASliderAx::GetSelections( wxVariant *selections )
{
return wxACC_NOT_IMPLEMENTED;
}
// Returns a state constant.
wxAccStatus ASliderAx::GetState(int childId, long* state)
{
ASlider *as = wxDynamicCast( GetWindow(), ASlider );
switch( childId )
{
case 0:
*state = wxACC_STATE_SYSTEM_FOCUSABLE;
break;
case 1:
if ( as->mLWSlider->mCurrentValue == as->mLWSlider->mMinValue )
{
*state = wxACC_STATE_SYSTEM_INVISIBLE;
}
break;
case 3:
if ( as->mLWSlider->mCurrentValue == as->mLWSlider->mMaxValue )
{
*state = wxACC_STATE_SYSTEM_INVISIBLE;
}
break;
}
// Do not use mSliderIsFocused is not set until after this method
// is called.
*state |= ( as == wxWindow::FindFocus() ? wxACC_STATE_SYSTEM_FOCUSED : 0 );
return wxACC_OK;
}
// Returns a localized string representing the value for the object
// or child.
wxAccStatus ASliderAx::GetValue(int childId, wxString* strValue)
{
ASlider *as = wxDynamicCast( GetWindow(), ASlider );
if ( childId == 0 )
{
switch( as->mLWSlider->mStyle )
{
case FRAC_SLIDER:
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue * 100 );
break;
case DB_SLIDER:
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue );
break;
case PAN_SLIDER:
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue * 100 );
break;
case SPEED_SLIDER:
strValue->Printf( wxT("%.0f"), as->mLWSlider->mCurrentValue * 100 );
break;
}
return wxACC_OK;
}
return wxACC_NOT_SUPPORTED;
}
#endif
// Indentation settings for Vim and Emacs and unique identifier for Arch, a
// version control system. Please do not modify past this point.
//
// Local Variables:
// c-basic-offset: 3
// indent-tabs-mode: nil
// End:
//
// vim: et sts=3 sw=3
// arch-tag: 0030c06f-7a62-40eb-b833-0a9fb96d1f55