wxwidgets/src/common/tbarbase.cpp
Vadim Zeitlin e13b4f8833 Don't force fractional scale when toolbar bitmap size is given
The old code in wxToolBarBase::AdjustToolBitmapSize() forced the use of
the exact value of the requested bitmap size multiplied by the current
scale factor, which resulted in ugly bitmaps whenever fractional scaling
factor was used. It also used not immediately clear IncTo() call.

Simplify and improve it by handling the cases when we have a requested
bitmap size and we don't have it differently: if we do have it, just use
it directly, but only with an integer scale factor. And if we don't,
then simply use the bitmap size suitable for the current scale factor.

This seems to result in the most expected behaviour and, notably,
doesn't break the toolbar sample where the bitmap size can still be
toggled between small and big bitmaps on both normal and high DPI
monitors.

Also update the documentation: still recommend not to use
SetToolBitmapSize() at all, but don't claim that it forces fractional
scaling, as this is not the case any longer.
2022-06-05 03:23:03 +02:00

850 lines
23 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/common/tbarbase.cpp
// Purpose: wxToolBarBase implementation
// Author: Julian Smart
// Modified by: VZ at 11.12.99 (wxScrollableToolBar split off)
// Created: 04/01/98
// Copyright: (c) Julian Smart
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// ============================================================================
// declarations
// ============================================================================
// ----------------------------------------------------------------------------
// headers
// ----------------------------------------------------------------------------
// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
#if wxUSE_TOOLBAR
#include "wx/toolbar.h"
#ifndef WX_PRECOMP
#include "wx/control.h"
#include "wx/frame.h"
#include "wx/settings.h"
#if WXWIN_COMPATIBILITY_2_8
#include "wx/image.h"
#endif // WXWIN_COMPATIBILITY_2_8
#include "wx/menu.h"
#include "wx/vector.h"
#endif
extern WXDLLEXPORT_DATA(const char) wxToolBarNameStr[] = "toolbar";
// ----------------------------------------------------------------------------
// wxWidgets macros
// ----------------------------------------------------------------------------
wxBEGIN_EVENT_TABLE(wxToolBarBase, wxControl)
wxEND_EVENT_TABLE()
#include "wx/listimpl.cpp"
WX_DEFINE_LIST(wxToolBarToolsList)
// ============================================================================
// implementation
// ============================================================================
// ----------------------------------------------------------------------------
// wxToolBarToolBase
// ----------------------------------------------------------------------------
wxIMPLEMENT_DYNAMIC_CLASS(wxToolBarToolBase, wxObject);
wxToolBarToolBase::~wxToolBarToolBase()
{
#if wxUSE_MENUS
delete m_dropdownMenu;
#endif
if ( IsControl() )
GetControl()->Destroy();
}
bool wxToolBarToolBase::Enable(bool enable)
{
if ( m_enabled == enable )
return false;
m_enabled = enable;
return true;
}
bool wxToolBarToolBase::Toggle(bool toggle)
{
wxASSERT_MSG( CanBeToggled(), wxT("can't toggle this tool") );
if ( m_toggled == toggle )
return false;
m_toggled = toggle;
return true;
}
bool wxToolBarToolBase::SetToggle(bool toggle)
{
wxItemKind kind = toggle ? wxITEM_CHECK : wxITEM_NORMAL;
if ( m_kind == kind )
return false;
m_kind = kind;
return true;
}
bool wxToolBarToolBase::SetShortHelp(const wxString& help)
{
if ( m_shortHelpString == help )
return false;
m_shortHelpString = help;
return true;
}
bool wxToolBarToolBase::SetLongHelp(const wxString& help)
{
if ( m_longHelpString == help )
return false;
m_longHelpString = help;
return true;
}
#if wxUSE_MENUS
void wxToolBarToolBase::SetDropdownMenu(wxMenu* menu)
{
delete m_dropdownMenu;
m_dropdownMenu = menu;
}
#endif
// ----------------------------------------------------------------------------
// wxToolBarBase adding/deleting items
// ----------------------------------------------------------------------------
wxToolBarBase::wxToolBarBase()
{
m_xMargin = m_yMargin = 0;
m_maxRows = m_maxCols = 0;
m_toolPacking = m_toolSeparation = 0;
m_defaultWidth = 16;
m_defaultHeight = 15;
}
void wxToolBarBase::FixupStyle()
{
if ( !HasFlag(wxTB_TOP | wxTB_LEFT | wxTB_RIGHT | wxTB_BOTTOM) )
{
// this is the default
m_windowStyle |= wxTB_TOP;
}
}
wxToolBarToolBase *wxToolBarBase::DoAddTool(int toolid,
const wxString& label,
const wxBitmapBundle& bitmap,
const wxBitmapBundle& bmpDisabled,
wxItemKind kind,
const wxString& shortHelp,
const wxString& longHelp,
wxObject *clientData,
wxCoord WXUNUSED(xPos),
wxCoord WXUNUSED(yPos))
{
InvalidateBestSize();
return InsertTool(GetToolsCount(), toolid, label, bitmap, bmpDisabled,
kind, shortHelp, longHelp, clientData);
}
wxToolBarToolBase *wxToolBarBase::InsertTool(size_t pos,
int toolid,
const wxString& label,
const wxBitmapBundle& bitmap,
const wxBitmapBundle& bmpDisabled,
wxItemKind kind,
const wxString& shortHelp,
const wxString& longHelp,
wxObject *clientData)
{
wxCHECK_MSG( pos <= GetToolsCount(), NULL,
wxT("invalid position in wxToolBar::InsertTool()") );
return DoInsertNewTool(pos, CreateTool(toolid, label, bitmap, bmpDisabled, kind,
clientData, shortHelp, longHelp));
}
wxToolBarToolBase *wxToolBarBase::AddTool(wxToolBarToolBase *tool)
{
return InsertTool(GetToolsCount(), tool);
}
wxToolBarToolBase *
wxToolBarBase::InsertTool(size_t pos, wxToolBarToolBase *tool)
{
wxCHECK_MSG( pos <= GetToolsCount(), NULL,
wxT("invalid position in wxToolBar::InsertTool()") );
if ( !tool || !DoInsertTool(pos, tool) )
{
return NULL;
}
m_tools.Insert(pos, tool);
tool->Attach(this);
return tool;
}
wxToolBarToolBase *
wxToolBarBase::AddControl(wxControl *control, const wxString& label)
{
return InsertControl(GetToolsCount(), control, label);
}
wxToolBarToolBase *
wxToolBarBase::InsertControl(size_t pos,
wxControl *control,
const wxString& label)
{
wxCHECK_MSG( control, NULL,
wxT("toolbar: can't insert NULL control") );
wxCHECK_MSG( control->GetParent() == this, NULL,
wxT("control must have toolbar as parent") );
return DoInsertNewTool(pos, CreateTool(control, label));
}
wxControl *wxToolBarBase::FindControl( int toolid )
{
for ( wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
node;
node = node->GetNext() )
{
const wxToolBarToolBase * const tool = node->GetData();
if ( tool->IsControl() )
{
wxControl * const control = tool->GetControl();
if ( !control )
{
wxFAIL_MSG( wxT("NULL control in toolbar?") );
}
else if ( control->GetId() == toolid )
{
// found
return control;
}
}
}
return NULL;
}
wxToolBarToolBase *wxToolBarBase::AddSeparator()
{
return InsertSeparator(GetToolsCount());
}
wxToolBarToolBase *wxToolBarBase::InsertSeparator(size_t pos)
{
return DoInsertNewTool(pos, CreateSeparator());
}
wxToolBarToolBase *wxToolBarBase::AddStretchableSpace()
{
return InsertStretchableSpace(GetToolsCount());
}
wxToolBarToolBase *wxToolBarBase::InsertStretchableSpace(size_t pos)
{
wxToolBarToolBase * const tool = CreateSeparator();
if ( tool )
{
// this is a hack but we know that all the current implementations
// don't really use the tool when it's created, they will do it
// InsertTool() at earliest and maybe even in Realize() much later
//
// so we can create the tool as a plain separator and mark it as being
// a stretchable space later
tool->MakeStretchable();
}
return DoInsertNewTool(pos, tool);
}
wxToolBarToolBase *wxToolBarBase::RemoveTool(int toolid)
{
size_t pos = 0;
wxToolBarToolsList::compatibility_iterator node;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
if ( node->GetData()->GetId() == toolid )
break;
pos++;
}
if ( !node )
{
// don't give any error messages - sometimes we might call RemoveTool()
// without knowing whether the tool is or not in the toolbar
return NULL;
}
wxToolBarToolBase *tool = node->GetData();
wxCHECK_MSG( tool, NULL, "NULL tool in the tools list?" );
if ( !DoDeleteTool(pos, tool) )
return NULL;
m_tools.Erase(node);
tool->Detach();
return tool;
}
bool wxToolBarBase::DeleteToolByPos(size_t pos)
{
wxCHECK_MSG( pos < GetToolsCount(), false,
wxT("invalid position in wxToolBar::DeleteToolByPos()") );
wxToolBarToolsList::compatibility_iterator node = m_tools.Item(pos);
if ( !DoDeleteTool(pos, node->GetData()) )
{
return false;
}
delete node->GetData();
m_tools.Erase(node);
return true;
}
bool wxToolBarBase::DeleteTool(int toolid)
{
size_t pos = 0;
wxToolBarToolsList::compatibility_iterator node;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
if ( node->GetData()->GetId() == toolid )
break;
pos++;
}
if ( !node || !DoDeleteTool(pos, node->GetData()) )
{
return false;
}
delete node->GetData();
m_tools.Erase(node);
return true;
}
wxToolBarToolBase *wxToolBarBase::FindById(int toolid) const
{
wxToolBarToolBase *tool = NULL;
for ( wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
node;
node = node->GetNext() )
{
tool = node->GetData();
if ( tool->GetId() == toolid )
{
// found
break;
}
tool = NULL;
}
return tool;
}
void wxToolBarBase::UnToggleRadioGroup(wxToolBarToolBase *tool)
{
wxCHECK_RET( tool, wxT("NULL tool in wxToolBarTool::UnToggleRadioGroup") );
if ( !tool->IsButton() || tool->GetKind() != wxITEM_RADIO )
return;
wxToolBarToolsList::compatibility_iterator node = m_tools.Find(tool);
wxCHECK_RET( node, wxT("invalid tool in wxToolBarTool::UnToggleRadioGroup") );
wxToolBarToolsList::compatibility_iterator nodeNext = node->GetNext();
while ( nodeNext )
{
wxToolBarToolBase *toolNext = nodeNext->GetData();
if ( !toolNext->IsButton() || toolNext->GetKind() != wxITEM_RADIO )
break;
if ( toolNext->Toggle(false) )
{
DoToggleTool(toolNext, false);
}
nodeNext = nodeNext->GetNext();
}
wxToolBarToolsList::compatibility_iterator nodePrev = node->GetPrevious();
while ( nodePrev )
{
wxToolBarToolBase *toolNext = nodePrev->GetData();
if ( !toolNext->IsButton() || toolNext->GetKind() != wxITEM_RADIO )
break;
if ( toolNext->Toggle(false) )
{
DoToggleTool(toolNext, false);
}
nodePrev = nodePrev->GetPrevious();
}
}
void wxToolBarBase::ClearTools()
{
while ( GetToolsCount() )
{
DeleteToolByPos(0);
}
}
void wxToolBarBase::DoSetToolBitmapSize(const wxSize& size)
{
m_defaultWidth = size.x;
m_defaultHeight = size.y;
}
void wxToolBarBase::SetToolBitmapSize(const wxSize& size)
{
// We store this value in DIPs to avoid having to update it when the DPI
// changes.
m_requestedBitmapSize = ToDIP(size);
DoSetToolBitmapSize(size);
}
wxSize wxToolBarBase::GetToolBitmapSize() const
{
return wxSize(m_defaultWidth, m_defaultHeight);
}
void wxToolBarBase::AdjustToolBitmapSize()
{
if ( HasFlag(wxTB_NOICONS) )
{
DoSetToolBitmapSize(wxSize(0, 0));
return;
}
const wxSize sizeOrig(m_defaultWidth, m_defaultHeight);
// Check if we should be using a different size because we have bitmaps
// that shouldn't be scaled to the size we use right now.
wxVector<wxBitmapBundle> bundles;
for ( wxToolBarToolsList::const_iterator i = m_tools.begin();
i != m_tools.end();
++i )
{
const wxBitmapBundle& bmp = (*i)->GetNormalBitmapBundle();
if ( bmp.IsOk() )
bundles.push_back(bmp);
}
if ( bundles.empty() )
return;
wxSize sizeNeeded;
if ( m_requestedBitmapSize != wxSize(0, 0) )
{
// If we have a fixed requested bitmap size, use it, but scale it by
// integer factor only, as otherwise we'd force fractional (and hence
// ugly looking) scaling here whenever fractional DPI scaling is used.
// We want to round 1.5 down to 1, but 1.75 up to 2.
int scaleFactorRoundedDown =
static_cast<int>(ceil(2*GetDPIScaleFactor())) / 2;
sizeNeeded = m_requestedBitmapSize*scaleFactorRoundedDown;
}
else // Determine the best size to use from the bitmaps we have.
{
wxSize sizePreferred = wxBitmapBundle::GetConsensusSizeFor
(
this,
bundles,
ToDIP(sizeOrig)
);
// GetConsensusSizeFor() returns physical size, but we want to operate
// with logical pixels as everything else is expressed in them.
//
// Note that this could introduce rounding problems but, in fact,
// neither wxGTK nor wxOSX (that are the only ports where contents
// scale factor may be different from 1) use this size at all
// currently, so it shouldn't matter. But if/when they are modified to
// use the size computed here, this would need to be revisited.
sizeNeeded = FromPhys(sizePreferred);
}
// No need to change the bitmaps size if it doesn't really change.
if ( sizeNeeded != sizeOrig )
{
// Call DoSetToolBitmapSize() and not SetToolBitmapSize() to avoid
// changing the requested bitmap size: if we set our own adjusted size
// as the preferred one, we wouldn't decrease it later even if we ought
// to, as when moving from a monitor with higher DPI to a lower-DPI one.
DoSetToolBitmapSize(sizeNeeded);
}
}
bool wxToolBarBase::Realize()
{
// check if we have anything to do
if ( m_tools.empty() )
return false;
// make sure tool size is large enough for all bitmaps to fit in
AdjustToolBitmapSize();
return true;
}
wxToolBarBase::~wxToolBarBase()
{
WX_CLEAR_LIST(wxToolBarToolsList, m_tools);
// notify the frame that it doesn't have a tool bar any longer to avoid
// dangling pointers
wxFrame *frame = wxDynamicCast(GetParent(), wxFrame);
if ( frame && frame->GetToolBar() == this )
{
frame->SetToolBar(NULL);
}
}
// ----------------------------------------------------------------------------
// wxToolBarBase tools state
// ----------------------------------------------------------------------------
void wxToolBarBase::EnableTool(int toolid, bool enable)
{
wxToolBarToolBase *tool = FindById(toolid);
if ( tool )
{
if ( tool->Enable(enable) )
{
DoEnableTool(tool, enable);
}
}
}
void wxToolBarBase::ToggleTool(int toolid, bool toggle)
{
wxToolBarToolBase *tool = FindById(toolid);
if ( tool && tool->CanBeToggled() )
{
if ( tool->Toggle(toggle) )
{
UnToggleRadioGroup(tool);
DoToggleTool(tool, toggle);
}
}
}
void wxToolBarBase::SetToggle(int toolid, bool toggle)
{
wxToolBarToolBase *tool = FindById(toolid);
if ( tool )
{
if ( tool->SetToggle(toggle) )
{
DoSetToggle(tool, toggle);
}
}
}
void wxToolBarBase::SetToolShortHelp(int toolid, const wxString& help)
{
wxToolBarToolBase *tool = FindById(toolid);
if ( tool )
{
(void)tool->SetShortHelp(help);
}
}
void wxToolBarBase::SetToolLongHelp(int toolid, const wxString& help)
{
wxToolBarToolBase *tool = FindById(toolid);
if ( tool )
{
(void)tool->SetLongHelp(help);
}
}
wxObject *wxToolBarBase::GetToolClientData(int toolid) const
{
wxToolBarToolBase *tool = FindById(toolid);
return tool ? tool->GetClientData() : NULL;
}
void wxToolBarBase::SetToolClientData(int toolid, wxObject *clientData)
{
wxToolBarToolBase *tool = FindById(toolid);
wxCHECK_RET( tool, wxT("no such tool in wxToolBar::SetToolClientData") );
tool->SetClientData(clientData);
}
int wxToolBarBase::GetToolPos(int toolid) const
{
size_t pos = 0;
wxToolBarToolsList::compatibility_iterator node;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
if ( node->GetData()->GetId() == toolid )
return pos;
pos++;
}
return wxNOT_FOUND;
}
bool wxToolBarBase::GetToolState(int toolid) const
{
wxToolBarToolBase *tool = FindById(toolid);
wxCHECK_MSG( tool, false, wxT("no such tool") );
return tool->IsToggled();
}
bool wxToolBarBase::GetToolEnabled(int toolid) const
{
wxToolBarToolBase *tool = FindById(toolid);
wxCHECK_MSG( tool, false, wxT("no such tool") );
return tool->IsEnabled();
}
wxString wxToolBarBase::GetToolShortHelp(int toolid) const
{
wxToolBarToolBase *tool = FindById(toolid);
wxCHECK_MSG( tool, wxEmptyString, wxT("no such tool") );
return tool->GetShortHelp();
}
wxString wxToolBarBase::GetToolLongHelp(int toolid) const
{
wxToolBarToolBase *tool = FindById(toolid);
wxCHECK_MSG( tool, wxEmptyString, wxT("no such tool") );
return tool->GetLongHelp();
}
// ----------------------------------------------------------------------------
// wxToolBarBase geometry
// ----------------------------------------------------------------------------
void wxToolBarBase::SetMargins(int x, int y)
{
m_xMargin = x;
m_yMargin = y;
}
void wxToolBarBase::SetRows(int WXUNUSED(nRows))
{
// nothing
}
bool wxToolBarBase::IsVertical() const
{
return HasFlag(wxTB_LEFT | wxTB_RIGHT);
}
// wxTB_HORIZONTAL is same as wxTB_TOP and wxTB_VERTICAL is same as wxTB_LEFT,
// so a toolbar created with wxTB_HORIZONTAL | wxTB_BOTTOM style can have set both
// wxTB_TOP and wxTB_BOTTOM, similarly wxTB_VERTICAL | wxTB_RIGHT == wxTB_LEFT | wxTB_RIGHT.
// GetDirection() makes things less confusing and returns just one of wxTB_TOP, wxTB_BOTTOM,
// wxTB_LEFT, wxTB_RIGHT indicating where the toolbar is placed in the associated frame.
int wxToolBarBase::GetDirection() const
{
if ( HasFlag(wxTB_BOTTOM) )
return wxTB_BOTTOM;
if ( HasFlag(wxTB_RIGHT) )
return wxTB_RIGHT;
if ( HasFlag(wxTB_LEFT) )
return wxTB_LEFT;
return wxTB_TOP;
}
// ----------------------------------------------------------------------------
// event processing
// ----------------------------------------------------------------------------
// Only allow toggle if returns true
bool wxToolBarBase::OnLeftClick(int toolid, bool toggleDown)
{
wxCommandEvent event(wxEVT_TOOL, toolid);
event.SetEventObject(this);
// we use SetInt() to make wxCommandEvent::IsChecked() return toggleDown
event.SetInt((int)toggleDown);
// and SetExtraLong() for backwards compatibility
event.SetExtraLong((long)toggleDown);
// Send events to this toolbar instead (and thence up the window hierarchy)
HandleWindowEvent(event);
return true;
}
// Call when right button down.
void wxToolBarBase::OnRightClick(int toolid,
long WXUNUSED(x),
long WXUNUSED(y))
{
wxCommandEvent event(wxEVT_TOOL_RCLICKED, toolid);
event.SetEventObject(this);
event.SetInt(toolid);
GetEventHandler()->ProcessEvent(event);
}
// Called when the mouse cursor enters a tool bitmap (no button pressed).
// Argument is wxID_ANY if mouse is exiting the toolbar.
// Note that for this event, the toolid of the window is used,
// and the integer parameter of wxCommandEvent is used to retrieve
// the tool toolid.
void wxToolBarBase::OnMouseEnter(int toolid)
{
wxCommandEvent event(wxEVT_TOOL_ENTER, GetId());
event.SetEventObject(this);
event.SetInt(toolid);
wxFrame *frame = wxDynamicCast(wxGetTopLevelParent(this), wxFrame);
if ( frame )
{
wxString help;
if ( toolid != wxID_ANY )
{
const wxToolBarToolBase * const tool = FindById(toolid);
if ( tool )
help = tool->GetLongHelp();
}
// call DoGiveHelp() even if help string is empty to avoid showing the
// help for the previously selected tool when another one is selected
frame->DoGiveHelp(help, toolid != wxID_ANY);
}
(void)GetEventHandler()->ProcessEvent(event);
}
// ----------------------------------------------------------------------------
// UI updates
// ----------------------------------------------------------------------------
// Do the toolbar button updates (check for EVT_UPDATE_UI handlers)
void wxToolBarBase::UpdateWindowUI(long flags)
{
wxWindowBase::UpdateWindowUI(flags);
// don't waste time updating state of tools in a hidden toolbar
if ( !IsShown() )
return;
wxEvtHandler* evtHandler = GetEventHandler() ;
for ( wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
node;
node = node->GetNext() )
{
wxToolBarToolBase * const tool = node->GetData();
if ( tool->IsSeparator() )
continue;
int toolid = tool->GetId();
wxUpdateUIEvent event(toolid);
event.SetEventObject(this);
if ( !tool->CanBeToggled() )
event.DisallowCheck();
if ( evtHandler->ProcessEvent(event) )
{
if ( event.GetSetEnabled() )
EnableTool(toolid, event.GetEnabled());
if ( event.GetSetChecked() )
ToggleTool(toolid, event.GetChecked());
#if 0
if ( event.GetSetText() )
// Set tooltip?
#endif // 0
}
}
}
#if wxUSE_MENUS
bool wxToolBarBase::SetDropdownMenu(int toolid, wxMenu* menu)
{
wxToolBarToolBase * const tool = FindById(toolid);
wxCHECK_MSG( tool, false, wxT("invalid tool toolid") );
wxCHECK_MSG( tool->GetKind() == wxITEM_DROPDOWN, false,
wxT("menu can be only associated with drop down tools") );
tool->SetDropdownMenu(menu);
return true;
}
#endif
#if WXWIN_COMPATIBILITY_2_8
bool wxCreateGreyedImage(const wxImage& in, wxImage& out)
{
#if wxUSE_IMAGE
out = in.ConvertToGreyscale();
if ( out.IsOk() )
return true;
#endif // wxUSE_IMAGE
return false;
}
#endif // WXWIN_COMPATIBILITY_2_8
#endif // wxUSE_TOOLBAR