Files
wxWidgets/src/common/tbarbase.cpp
Vadim Zeitlin 6ec36a900c Separate the requested and effective size of toolbar bitmaps
Add wxToolBarBase::DoSetToolBitmapSize() which does everything that
SetToolBitmapSize() used to do and make SetToolBitmapSize() itself also
remember the size passed to it in a new field, so that we could check
that we don't decrease the bitmap size below the size requested by the
application, while still being able to decrease it if necessary due to a
DPI change.

This allows SetToolBitmapSize() to continue working as before, i.e.
scale the bitmaps up if necessary without ever scaling them down, but
also allows them to resize in both directions when DPI changes.

Closes #22105.
2022-02-10 00:12:14 +00:00

840 lines
22 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()
{
// the list owns the pointers
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)
{
m_requestedBitmapSize = 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() )
{
wxSize sizePreferred = wxBitmapBundle::GetConsensusSizeFor
(
this,
bundles,
sizeOrig
);
// Don't do anything if it doesn't change, our current size is supposed
// to satisfy any constraints we might have anyhow.
if ( sizePreferred == sizeOrig )
return;
// This size is supposed to be in logical units for the platforms where
// they differ from physical ones, so convert it.
//
// 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.
sizePreferred /= GetContentScaleFactor();
// Don't decrease the bitmap below the size requested by the application
// as using larger bitmaps shouldn't shrink them to the small default
// size.
sizePreferred.IncTo(m_requestedBitmapSize);
// 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(sizePreferred);
}
}
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