Files
wxWidgets/src/msw/toolbar.cpp
Vadim Zeitlin b1c33b0c21 Fix handling of controls in vertical toolbars in wxMSW.
Not adding the controls to vertical toolbar is not enough, we also need to
hide them to prevent them from being shown as independent floating windows.
And we also need to add separators instead of the controls themselves to keep
the indices the same as in the horizontal case.

Closes #11821.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@76099 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2014-03-08 14:34:00 +00:00

2135 lines
69 KiB
C++

/////////////////////////////////////////////////////////////////////////////
// Name: src/msw/toolbar.cpp
// Purpose: wxToolBar
// Author: Julian Smart
// Modified by:
// 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"
#ifdef __BORLANDC__
#pragma hdrstop
#endif
#if wxUSE_TOOLBAR && wxUSE_TOOLBAR_NATIVE && !defined(__SMARTPHONE__)
#include "wx/toolbar.h"
#ifndef WX_PRECOMP
#include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
#include "wx/dynarray.h"
#include "wx/frame.h"
#include "wx/log.h"
#include "wx/intl.h"
#include "wx/settings.h"
#include "wx/bitmap.h"
#include "wx/region.h"
#include "wx/dcmemory.h"
#include "wx/control.h"
#include "wx/app.h" // for GetComCtl32Version
#include "wx/image.h"
#include "wx/stattext.h"
#endif
#include "wx/artprov.h"
#include "wx/sysopt.h"
#include "wx/dcclient.h"
#include "wx/scopedarray.h"
#include "wx/msw/private.h"
#include "wx/msw/dc.h"
#if wxUSE_UXTHEME
#include "wx/msw/uxtheme.h"
#endif
// this define controls whether the code for button colours remapping (only
// useful for 16 or 256 colour images) is active at all, it's always turned off
// for CE where it doesn't compile (and is probably not needed anyhow) and may
// also be turned off for other systems if you always use 24bpp images and so
// never need it
#ifndef __WXWINCE__
#define wxREMAP_BUTTON_COLOURS
#endif // !__WXWINCE__
// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------
// these standard constants are not always defined in compilers headers
// Styles
#ifndef TBSTYLE_FLAT
#define TBSTYLE_LIST 0x1000
#define TBSTYLE_FLAT 0x0800
#endif
#ifndef TBSTYLE_TRANSPARENT
#define TBSTYLE_TRANSPARENT 0x8000
#endif
#ifndef TBSTYLE_TOOLTIPS
#define TBSTYLE_TOOLTIPS 0x0100
#endif
// Messages
#ifndef TB_GETSTYLE
#define TB_SETSTYLE (WM_USER + 56)
#define TB_GETSTYLE (WM_USER + 57)
#endif
#ifndef TB_HITTEST
#define TB_HITTEST (WM_USER + 69)
#endif
#ifndef TB_GETMAXSIZE
#define TB_GETMAXSIZE (WM_USER + 83)
#endif
// ----------------------------------------------------------------------------
// wxWin macros
// ----------------------------------------------------------------------------
IMPLEMENT_DYNAMIC_CLASS(wxToolBar, wxControl)
/*
TOOLBAR PROPERTIES
tool
bitmap
bitmap2
tooltip
longhelp
radio (bool)
toggle (bool)
separator
style ( wxNO_BORDER | wxTB_HORIZONTAL)
bitmapsize
margins
packing
separation
dontattachtoframe
*/
BEGIN_EVENT_TABLE(wxToolBar, wxToolBarBase)
EVT_MOUSE_EVENTS(wxToolBar::OnMouseEvent)
EVT_SYS_COLOUR_CHANGED(wxToolBar::OnSysColourChanged)
EVT_ERASE_BACKGROUND(wxToolBar::OnEraseBackground)
END_EVENT_TABLE()
// ----------------------------------------------------------------------------
// private classes
// ----------------------------------------------------------------------------
class wxToolBarTool : public wxToolBarToolBase
{
public:
wxToolBarTool(wxToolBar *tbar,
int id,
const wxString& label,
const wxBitmap& bmpNormal,
const wxBitmap& bmpDisabled,
wxItemKind kind,
wxObject *clientData,
const wxString& shortHelp,
const wxString& longHelp)
: wxToolBarToolBase(tbar, id, label, bmpNormal, bmpDisabled, kind,
clientData, shortHelp, longHelp)
{
m_nSepCount = 0;
m_staticText = NULL;
}
wxToolBarTool(wxToolBar *tbar, wxControl *control, const wxString& label)
: wxToolBarToolBase(tbar, control, label)
{
if ( IsControl() && !m_label.empty() )
{
// create a control to render the control's label
m_staticText = new wxStaticText
(
m_tbar,
wxID_ANY,
m_label,
wxDefaultPosition,
wxDefaultSize,
wxALIGN_CENTRE | wxST_NO_AUTORESIZE
);
}
else // no label
{
m_staticText = NULL;
}
m_nSepCount = 1;
}
virtual ~wxToolBarTool()
{
delete m_staticText;
}
virtual void SetLabel(const wxString& label)
{
if ( label == m_label )
return;
wxToolBarToolBase::SetLabel(label);
if ( m_staticText )
m_staticText->SetLabel(label);
// we need to update the label shown in the toolbar because it has a
// pointer to the internal buffer of the old label
//
// TODO: use TB_SETBUTTONINFO
}
wxStaticText* GetStaticText()
{
wxASSERT_MSG( IsControl(),
wxT("only makes sense for embedded control tools") );
return m_staticText;
}
// set/get the number of separators which we use to cover the space used by
// a control in the toolbar
void SetSeparatorsCount(size_t count) { m_nSepCount = count; }
size_t GetSeparatorsCount() const { return m_nSepCount; }
// we need ids for the spacers which we want to modify later on, this
// function will allocate a valid/unique id for a spacer if not done yet
void AllocSpacerId()
{
if ( m_id == wxID_SEPARATOR )
m_id = wxWindow::NewControlId();
}
// this method is used for controls only and offsets the control by the
// given amount (in pixels) in horizontal direction
void MoveBy(int offset)
{
wxControl * const control = GetControl();
control->Move(control->GetPosition().x + offset, wxDefaultCoord);
if ( m_staticText )
{
m_staticText->Move(m_staticText->GetPosition().x + offset,
wxDefaultCoord);
}
}
private:
size_t m_nSepCount;
wxStaticText *m_staticText;
wxDECLARE_NO_COPY_CLASS(wxToolBarTool);
};
// ----------------------------------------------------------------------------
// helper functions
// ----------------------------------------------------------------------------
// Return the rectangle of the item at the given index and, if specified, with
// the given id.
//
// Returns an empty (0, 0, 0, 0) rectangle if fails so the caller may compare
// r.right or r.bottom with 0 to check for this.
static RECT wxGetTBItemRect(HWND hwnd, int index, int id = wxID_NONE)
{
RECT r;
// note that we use TB_GETITEMRECT and not TB_GETRECT because the latter
// only appeared in v4.70 of comctl32.dll
if ( !::SendMessage(hwnd, TB_GETITEMRECT, index, (LPARAM)&r) )
{
// This call can return false status even when there is no real error,
// e.g. for a hidden button, so check for this to avoid spurious logs.
const DWORD err = ::GetLastError();
if ( err != ERROR_SUCCESS )
{
bool reportError = true;
if ( id != wxID_NONE )
{
const LRESULT state = ::SendMessage(hwnd, TB_GETSTATE, id, 0);
if ( state != -1 && (state & TBSTATE_HIDDEN) )
{
// There is no real error to report after all.
reportError = false;
}
else // It is not hidden.
{
// So it must have been a real error, report it with the
// original error code and not the one from TB_GETSTATE.
::SetLastError(err);
}
}
if ( reportError )
wxLogLastError(wxT("TB_GETITEMRECT"));
}
::SetRectEmpty(&r);
}
return r;
}
// ============================================================================
// implementation
// ============================================================================
// ----------------------------------------------------------------------------
// wxToolBarTool
// ----------------------------------------------------------------------------
wxToolBarToolBase *wxToolBar::CreateTool(int id,
const wxString& label,
const wxBitmap& bmpNormal,
const wxBitmap& bmpDisabled,
wxItemKind kind,
wxObject *clientData,
const wxString& shortHelp,
const wxString& longHelp)
{
return new wxToolBarTool(this, id, label, bmpNormal, bmpDisabled, kind,
clientData, shortHelp, longHelp);
}
wxToolBarToolBase *
wxToolBar::CreateTool(wxControl *control, const wxString& label)
{
return new wxToolBarTool(this, control, label);
}
// ----------------------------------------------------------------------------
// wxToolBar construction
// ----------------------------------------------------------------------------
void wxToolBar::Init()
{
m_hBitmap = 0;
m_disabledImgList = NULL;
m_nButtons = 0;
m_totalFixedSize = 0;
// even though modern Windows applications typically use 24*24 (or even
// 32*32) size for their bitmaps, the native control itself still uses the
// old 16*15 default size (see TB_SETBITMAPSIZE documentation in MSDN), so
// default to it so that we don't call SetToolBitmapSize() unnecessarily in
// wxToolBarBase::AdjustToolBitmapSize()
m_defaultWidth = 16;
m_defaultHeight = 15;
m_pInTool = NULL;
}
bool wxToolBar::Create(wxWindow *parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size,
long style,
const wxString& name)
{
// common initialisation
if ( !CreateControl(parent, id, pos, size, style, wxDefaultValidator, name) )
return false;
FixupStyle();
// MSW-specific initialisation
if ( !MSWCreateToolbar(pos, size) )
return false;
wxSetCCUnicodeFormat(GetHwnd());
// we always erase our background on WM_PAINT so there is no need to do it
// in WM_ERASEBKGND too (by default this won't be done but if the toolbar
// has a non default background colour, then it would be used in both
// places resulting in flicker)
if (wxApp::GetComCtl32Version() >= 600)
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
}
return true;
}
bool wxToolBar::MSWCreateToolbar(const wxPoint& pos, const wxSize& size)
{
if ( !MSWCreateControl(TOOLBARCLASSNAME, wxEmptyString, pos, size) )
return false;
// toolbar-specific post initialisation
::SendMessage(GetHwnd(), TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
#ifdef TB_SETEXTENDEDSTYLE
if ( wxApp::GetComCtl32Version() >= 471 )
::SendMessage(GetHwnd(), TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_DRAWDDARROWS);
#endif
return true;
}
void wxToolBar::Recreate()
{
const HWND hwndOld = GetHwnd();
if ( !hwndOld )
{
// we haven't been created yet, no need to recreate
return;
}
// get the position and size before unsubclassing the old toolbar
const wxPoint pos = GetPosition();
const wxSize size = GetSize();
UnsubclassWin();
if ( !MSWCreateToolbar(pos, size) )
{
// what can we do?
wxFAIL_MSG( wxT("recreating the toolbar failed") );
return;
}
// reparent all our children under the new toolbar
for ( wxWindowList::compatibility_iterator node = m_children.GetFirst();
node;
node = node->GetNext() )
{
wxWindow *win = node->GetData();
if ( !win->IsTopLevel() )
::SetParent(GetHwndOf(win), GetHwnd());
}
// only destroy the old toolbar now --
// after all the children had been reparented
::DestroyWindow(hwndOld);
// it is for the old bitmap control and can't be used with the new one
if ( m_hBitmap )
{
::DeleteObject((HBITMAP) m_hBitmap);
m_hBitmap = 0;
}
wxDELETE(m_disabledImgList);
Realize();
}
wxToolBar::~wxToolBar()
{
// we must refresh the frame size when the toolbar is deleted but the frame
// is not - otherwise toolbar leaves a hole in the place it used to occupy
SendSizeEventToParent();
if ( m_hBitmap )
::DeleteObject((HBITMAP) m_hBitmap);
delete m_disabledImgList;
}
wxSize wxToolBar::DoGetBestSize() const
{
wxSize sizeBest;
SIZE size;
if ( !::SendMessage(GetHwnd(), TB_GETMAXSIZE, 0, (LPARAM)&size) )
{
// maybe an old (< 0x400) Windows version? try to approximate the
// toolbar size ourselves
sizeBest = GetToolSize();
sizeBest.y += 2 * ::GetSystemMetrics(SM_CYBORDER); // Add borders
sizeBest.x *= GetToolsCount();
// reverse horz and vertical components if necessary
if ( IsVertical() )
{
int t = sizeBest.x;
sizeBest.x = sizeBest.y;
sizeBest.y = t;
}
}
else // TB_GETMAXSIZE succeeded
{
// but it could still return an incorrect result due to what appears to
// be a bug in old comctl32.dll versions which don't handle controls in
// the toolbar correctly, so work around it (see SF patch 1902358)
if ( !IsVertical() && wxApp::GetComCtl32Version() < 600 )
{
// calculate the toolbar width in alternative way
const RECT rcFirst = wxGetTBItemRect(GetHwnd(), 0);
const RECT rcLast = wxGetTBItemRect(GetHwnd(), GetToolsCount() - 1);
const int widthAlt = rcLast.right - rcFirst.left;
if ( widthAlt > size.cx )
size.cx = widthAlt;
}
sizeBest.x = size.cx;
sizeBest.y = size.cy;
}
if ( !IsVertical() )
{
// Without the extra height, DoGetBestSize can report a size that's
// smaller than the actual window, causing windows to overlap slightly
// in some circumstances, leading to missing borders (especially noticeable
// in AUI layouts).
if (!(GetWindowStyle() & wxTB_NODIVIDER))
sizeBest.y += 2;
sizeBest.y ++;
}
CacheBestSize(sizeBest);
return sizeBest;
}
WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const
{
// toolbars never have border, giving one to them results in broken
// appearance
WXDWORD msStyle = wxControl::MSWGetStyle
(
(style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
);
if ( !(style & wxTB_NO_TOOLTIPS) )
msStyle |= TBSTYLE_TOOLTIPS;
if ( style & wxTB_FLAT && wxApp::GetComCtl32Version() > 400 )
msStyle |= TBSTYLE_FLAT;
if ( style & wxTB_HORZ_LAYOUT && wxApp::GetComCtl32Version() >= 470 )
msStyle |= TBSTYLE_LIST;
if ( style & wxTB_NODIVIDER )
msStyle |= CCS_NODIVIDER;
if ( style & wxTB_NOALIGN )
msStyle |= CCS_NOPARENTALIGN;
if ( style & wxTB_VERTICAL )
msStyle |= CCS_VERT;
if( style & wxTB_BOTTOM )
msStyle |= CCS_BOTTOM;
if ( style & wxTB_RIGHT )
msStyle |= CCS_RIGHT;
// always use TBSTYLE_TRANSPARENT because the background is not drawn
// correctly without it in all themes and, for whatever reason, the control
// also flickers horribly when it is resized if this style is not used
//
// note that this is implicitly enabled by the native toolbar itself when
// TBSTYLE_FLAT is used (i.e. it's impossible to use TBSTYLE_FLAT without
// TBSTYLE_TRANSPARENT) but turn it on explicitly in any case
msStyle |= TBSTYLE_TRANSPARENT;
return msStyle;
}
// ----------------------------------------------------------------------------
// adding/removing tools
// ----------------------------------------------------------------------------
bool wxToolBar::DoInsertTool(size_t WXUNUSED(pos),
wxToolBarToolBase * WXUNUSED(tool))
{
// nothing special to do here - we really create the toolbar buttons in
// Realize() later
InvalidateBestSize();
return true;
}
bool wxToolBar::DoDeleteTool(size_t pos, wxToolBarToolBase *tool)
{
// the main difficulty we have here is with the controls in the toolbars:
// as we (sometimes) use several separators to cover up the space used by
// them, the indices are not the same for us and the toolbar
// first determine the position of the first button to delete: it may be
// different from pos if we use several separators to cover the space used
// by a control
wxToolBarToolsList::compatibility_iterator node;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarToolBase *tool2 = node->GetData();
if ( tool2 == tool )
{
// let node point to the next node in the list
node = node->GetNext();
break;
}
if ( tool2->IsControl() )
pos += ((wxToolBarTool *)tool2)->GetSeparatorsCount() - 1;
}
// now determine the number of buttons to delete and the area taken by them
size_t nButtonsToDelete = 1;
// get the size of the button we're going to delete
const RECT r = wxGetTBItemRect(GetHwnd(), pos);
int delta = IsVertical() ? r.bottom - r.top : r.right - r.left;
if ( tool->IsControl() )
{
nButtonsToDelete = ((wxToolBarTool *)tool)->GetSeparatorsCount();
if ( !IsVertical() )
delta *= nButtonsToDelete;
}
m_totalFixedSize -= delta;
// do delete all buttons
m_nButtons -= nButtonsToDelete;
while ( nButtonsToDelete-- > 0 )
{
if ( !::SendMessage(GetHwnd(), TB_DELETEBUTTON, pos, 0) )
{
wxLogLastError(wxT("TB_DELETEBUTTON"));
return false;
}
}
// and finally rearrange the tools
// search for any stretch spacers before the removed tool
bool hasPrecedingStrechables = false;
for ( wxToolBarToolsList::compatibility_iterator nodeStch = m_tools.GetFirst();
nodeStch != node; nodeStch = nodeStch->GetNext() )
{
if ( ((wxToolBarTool*)nodeStch->GetData())->IsStretchable() )
{
hasPrecedingStrechables = true;
break;
}
}
if ( hasPrecedingStrechables )
{
// if the removed tool is preceded by stretch spacers
// just redistribute the space
UpdateStretchableSpacersSize();
}
else
{
// reposition all the controls after this button but before any
// stretch spacer (the toolbar takes care of all normal items)
for ( /* node -> first after deleted */ ; node; node = node->GetNext() )
{
wxToolBarTool *tool2 = (wxToolBarTool*)node->GetData();
if ( tool2->IsControl() )
{
tool2->MoveBy(-delta);
}
// if a stretch spacer is found just redistribute the available space
else if ( tool2->IsStretchable() )
{
UpdateStretchableSpacersSize();
break;
}
}
}
InvalidateBestSize();
return true;
}
void wxToolBar::CreateDisabledImageList()
{
wxDELETE(m_disabledImgList);
// as we can't use disabled image list with older versions of comctl32.dll,
// don't even bother creating it
if ( wxApp::GetComCtl32Version() >= 470 )
{
// search for the first disabled button img in the toolbar, if any
for ( wxToolBarToolsList::compatibility_iterator
node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarToolBase *tool = node->GetData();
wxBitmap bmpDisabled = tool->GetDisabledBitmap();
if ( bmpDisabled.IsOk() )
{
const wxSize sizeBitmap = bmpDisabled.GetSize();
m_disabledImgList = new wxImageList
(
sizeBitmap.x,
sizeBitmap.y,
// Don't use mask if we have alpha
// (wxImageList will fall back to
// mask if alpha not supported)
!bmpDisabled.HasAlpha(),
GetToolsCount()
);
break;
}
}
// we don't have any disabled bitmaps
}
}
bool wxToolBar::Realize()
{
if ( !wxToolBarBase::Realize() )
return false;
const size_t nTools = GetToolsCount();
#ifdef wxREMAP_BUTTON_COLOURS
// don't change the values of these constants, they can be set from the
// user code via wxSystemOptions
enum
{
Remap_None = -1,
Remap_Bg,
Remap_Buttons,
Remap_TransparentBg
};
// the user-specified option overrides anything, but if it wasn't set, only
// remap the buttons on 8bpp displays as otherwise the bitmaps usually look
// much worse after remapping
static const wxChar *remapOption = wxT("msw.remap");
const int remapValue = wxSystemOptions::HasOption(remapOption)
? wxSystemOptions::GetOptionInt(remapOption)
: wxDisplayDepth() <= 8 ? Remap_Buttons
: Remap_None;
#endif // wxREMAP_BUTTON_COLOURS
// delete all old buttons, if any
for ( size_t pos = 0; pos < m_nButtons; pos++ )
{
if ( !::SendMessage(GetHwnd(), TB_DELETEBUTTON, 0, 0) )
{
wxLogDebug(wxT("TB_DELETEBUTTON failed"));
}
}
// First, add the bitmap: we use one bitmap for all toolbar buttons
// ----------------------------------------------------------------
wxToolBarToolsList::compatibility_iterator node;
int bitmapId = 0;
if ( !HasFlag(wxTB_NOICONS) )
{
// if we already have a bitmap, we'll replace the existing one --
// otherwise we'll install a new one
HBITMAP oldToolBarBitmap = (HBITMAP)m_hBitmap;
const wxCoord totalBitmapWidth = m_defaultWidth *
wx_truncate_cast(wxCoord, nTools),
totalBitmapHeight = m_defaultHeight;
// Create a bitmap and copy all the tool bitmaps into it
wxMemoryDC dcAllButtons;
wxBitmap bitmap(totalBitmapWidth, totalBitmapHeight);
dcAllButtons.SelectObject(bitmap);
#ifdef wxREMAP_BUTTON_COLOURS
if ( remapValue != Remap_TransparentBg )
#endif // wxREMAP_BUTTON_COLOURS
{
// VZ: why do we hardcode grey colour for CE?
dcAllButtons.SetBackground(wxBrush(
#ifdef __WXWINCE__
wxColour(0xc0, 0xc0, 0xc0)
#else // !__WXWINCE__
GetBackgroundColour()
#endif // __WXWINCE__/!__WXWINCE__
));
dcAllButtons.Clear();
}
HBITMAP hBitmap = GetHbitmapOf(bitmap);
#ifdef wxREMAP_BUTTON_COLOURS
if ( remapValue == Remap_Bg )
{
dcAllButtons.SelectObject(wxNullBitmap);
// Even if we're not remapping the bitmap
// content, we still have to remap the background.
hBitmap = (HBITMAP)MapBitmap((WXHBITMAP) hBitmap,
totalBitmapWidth, totalBitmapHeight);
dcAllButtons.SelectObject(bitmap);
}
#endif // wxREMAP_BUTTON_COLOURS
// the button position
wxCoord x = 0;
// the number of buttons (not separators)
int nButtons = 0;
CreateDisabledImageList();
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarToolBase *tool = node->GetData();
if ( tool->IsButton() )
{
const wxBitmap& bmp = tool->GetNormalBitmap();
const int w = bmp.GetWidth();
const int h = bmp.GetHeight();
if ( bmp.IsOk() )
{
// By default bitmaps don't have alpha in wxMSW, but if we
// use a bitmap tool with alpha, we should use alpha for
// the combined bitmap as well.
if ( bmp.HasAlpha() )
bitmap.UseAlpha();
int xOffset = wxMax(0, (m_defaultWidth - w)/2);
int yOffset = wxMax(0, (m_defaultHeight - h)/2);
// notice the last parameter: do use mask
dcAllButtons.DrawBitmap(bmp, x + xOffset, yOffset, true);
// Handle of the bitmap could have changed inside
// DrawBitmap() call if it had to convert it from DDB to
// DIB internally, as is necessary if the bitmap being
// drawn had alpha channel.
hBitmap = GetHbitmapOf(bitmap);
}
else
{
wxFAIL_MSG( wxT("invalid tool button bitmap") );
}
// also deal with disabled bitmap if we want to use them
if ( m_disabledImgList )
{
wxBitmap bmpDisabled = tool->GetDisabledBitmap();
#if wxUSE_IMAGE && wxUSE_WXDIB
if ( !bmpDisabled.IsOk() )
{
// no disabled bitmap specified but we still need to
// fill the space in the image list with something, so
// we grey out the normal bitmap
wxImage
imgGreyed = bmp.ConvertToImage().ConvertToGreyscale();
#ifdef wxREMAP_BUTTON_COLOURS
if ( remapValue == Remap_Buttons )
{
// we need to have light grey background colour for
// MapBitmap() to work correctly
for ( int y = 0; y < h; y++ )
{
for ( int x = 0; x < w; x++ )
{
if ( imgGreyed.IsTransparent(x, y) )
imgGreyed.SetRGB(x, y,
wxLIGHT_GREY->Red(),
wxLIGHT_GREY->Green(),
wxLIGHT_GREY->Blue());
}
}
}
#endif // wxREMAP_BUTTON_COLOURS
bmpDisabled = wxBitmap(imgGreyed);
}
#endif // wxUSE_IMAGE
#ifdef wxREMAP_BUTTON_COLOURS
if ( remapValue == Remap_Buttons )
MapBitmap(bmpDisabled.GetHBITMAP(), w, h);
#endif // wxREMAP_BUTTON_COLOURS
m_disabledImgList->Add(bmpDisabled);
}
// still inc width and number of buttons because otherwise the
// subsequent buttons will all be shifted which is rather confusing
// (and like this you'd see immediately which bitmap was bad)
x += m_defaultWidth;
nButtons++;
}
}
dcAllButtons.SelectObject(wxNullBitmap);
// don't delete this HBITMAP!
bitmap.SetHBITMAP(0);
#ifdef wxREMAP_BUTTON_COLOURS
if ( remapValue == Remap_Buttons )
{
// Map to system colours
hBitmap = (HBITMAP)MapBitmap((WXHBITMAP) hBitmap,
totalBitmapWidth, totalBitmapHeight);
}
#endif // wxREMAP_BUTTON_COLOURS
m_hBitmap = hBitmap;
bool addBitmap = true;
if ( oldToolBarBitmap )
{
#ifdef TB_REPLACEBITMAP
if ( wxApp::GetComCtl32Version() >= 400 )
{
TBREPLACEBITMAP replaceBitmap;
replaceBitmap.hInstOld = NULL;
replaceBitmap.hInstNew = NULL;
replaceBitmap.nIDOld = (UINT_PTR)oldToolBarBitmap;
replaceBitmap.nIDNew = (UINT_PTR)hBitmap;
replaceBitmap.nButtons = nButtons;
if ( !::SendMessage(GetHwnd(), TB_REPLACEBITMAP,
0, (LPARAM) &replaceBitmap) )
{
wxFAIL_MSG(wxT("Could not replace the old bitmap"));
}
::DeleteObject(oldToolBarBitmap);
// already done
addBitmap = false;
}
else
#endif // TB_REPLACEBITMAP
{
// we can't replace the old bitmap, so we will add another one
// (awfully inefficient, but what else to do?) and shift the bitmap
// indices accordingly
addBitmap = true;
bitmapId = m_nButtons;
}
}
if ( addBitmap ) // no old bitmap or we can't replace it
{
TBADDBITMAP addBitmap;
addBitmap.hInst = 0;
addBitmap.nID = (UINT_PTR)hBitmap;
if ( ::SendMessage(GetHwnd(), TB_ADDBITMAP,
(WPARAM) nButtons, (LPARAM)&addBitmap) == -1 )
{
wxFAIL_MSG(wxT("Could not add bitmap to toolbar"));
}
}
// disable image lists are only supported in comctl32.dll 4.70+
if ( wxApp::GetComCtl32Version() >= 470 )
{
HIMAGELIST hil = m_disabledImgList
? GetHimagelistOf(m_disabledImgList)
: 0;
// notice that we set the image list even if don't have one right
// now as we could have it before and need to reset it in this case
HIMAGELIST oldImageList = (HIMAGELIST)
::SendMessage(GetHwnd(), TB_SETDISABLEDIMAGELIST, 0, (LPARAM)hil);
// delete previous image list if any
if ( oldImageList )
::DeleteObject(oldImageList);
}
}
// Next add the buttons and separators
// -----------------------------------
wxScopedArray<TBBUTTON> buttons(nTools);
// this array will hold the indices of all controls in the toolbar
wxArrayInt controlIds;
bool lastWasRadio = false;
int i = 0;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarTool *tool = static_cast<wxToolBarTool *>(node->GetData());
// don't add separators to the vertical toolbar with old comctl32.dll
// versions as they didn't handle this properly
if ( IsVertical() && tool->IsSeparator() &&
wxApp::GetComCtl32Version() <= 472 )
{
continue;
}
TBBUTTON& button = buttons[i];
wxZeroMemory(button);
bool isRadio = false;
switch ( tool->GetStyle() )
{
case wxTOOL_STYLE_CONTROL:
if ( wxStaticText *staticText = tool->GetStaticText() )
{
// Display control and its label only if buttons have icons
// and texts as otherwise there is not enough room on the
// toolbar to fit the label.
staticText->
Show(HasFlag(wxTB_TEXT) && !HasFlag(wxTB_NOICONS));
}
// Fall through
case wxTOOL_STYLE_SEPARATOR:
if ( tool->IsStretchableSpace() )
{
// we're going to modify the size of this button later and
// so we need a valid id for it and not wxID_SEPARATOR
// which is used by spacers by default
tool->AllocSpacerId();
// also set the number of separators so that the logic in
// HandlePaint() works correctly
tool->SetSeparatorsCount(1);
}
button.idCommand = tool->GetId();
// We don't embed controls in the vertical toolbar but for
// every control there must exist a corresponding button to
// keep indexes the same as in the horizontal case.
if ( IsVertical() && tool->IsControl() )
button.fsState = TBSTATE_HIDDEN;
else
button.fsState = TBSTATE_ENABLED;
button.fsStyle = TBSTYLE_SEP;
break;
case wxTOOL_STYLE_BUTTON:
if ( !HasFlag(wxTB_NOICONS) )
button.iBitmap = bitmapId;
if ( HasFlag(wxTB_TEXT) )
{
const wxString& label = tool->GetLabel();
if ( !label.empty() )
button.iString = (INT_PTR) wxMSW_CONV_LPCTSTR(label);
}
button.idCommand = tool->GetId();
if ( tool->IsEnabled() )
button.fsState |= TBSTATE_ENABLED;
if ( tool->IsToggled() )
button.fsState |= TBSTATE_CHECKED;
switch ( tool->GetKind() )
{
case wxITEM_RADIO:
button.fsStyle = TBSTYLE_CHECKGROUP;
if ( !lastWasRadio )
{
// the first item in the radio group is checked by
// default to be consistent with wxGTK and the menu
// radio items
button.fsState |= TBSTATE_CHECKED;
if (tool->Toggle(true))
{
DoToggleTool(tool, true);
}
}
else if ( tool->IsToggled() )
{
wxToolBarToolsList::compatibility_iterator nodePrev = node->GetPrevious();
int prevIndex = i - 1;
while ( nodePrev )
{
TBBUTTON& prevButton = buttons[prevIndex];
wxToolBarToolBase *tool = nodePrev->GetData();
if ( !tool->IsButton() || tool->GetKind() != wxITEM_RADIO )
break;
if ( tool->Toggle(false) )
DoToggleTool(tool, false);
prevButton.fsState &= ~TBSTATE_CHECKED;
nodePrev = nodePrev->GetPrevious();
prevIndex--;
}
}
isRadio = true;
break;
case wxITEM_CHECK:
button.fsStyle = TBSTYLE_CHECK;
break;
case wxITEM_NORMAL:
button.fsStyle = TBSTYLE_BUTTON;
break;
case wxITEM_DROPDOWN:
button.fsStyle = TBSTYLE_DROPDOWN;
break;
default:
wxFAIL_MSG( wxT("unexpected toolbar button kind") );
button.fsStyle = TBSTYLE_BUTTON;
break;
}
// Instead of using fixed widths for all buttons, size them
// automatically according to the size of their bitmap and text
// label, if present. This particularly matters for toolbars
// with the wxTB_HORZ_LAYOUT style: they look hideously ugly
// without autosizing when the labels have even slightly
// different lengths.
button.fsStyle |= TBSTYLE_AUTOSIZE;
bitmapId++;
break;
}
lastWasRadio = isRadio;
i++;
}
if ( !::SendMessage(GetHwnd(), TB_ADDBUTTONS, i, (LPARAM)buttons.get()) )
{
wxLogLastError(wxT("TB_ADDBUTTONS"));
}
// Adjust controls and stretchable spaces
// --------------------------------------
// adjust the controls size to fit nicely in the toolbar and compute its
// total size while doing it
m_totalFixedSize = 0;
int toolIndex = 0;
for ( node = m_tools.GetFirst(); node; node = node->GetNext(), toolIndex++ )
{
wxToolBarTool * const tool = (wxToolBarTool*)node->GetData();
const RECT r = wxGetTBItemRect(GetHwnd(), toolIndex, tool->GetId());
if ( !tool->IsControl() )
{
if ( IsVertical() )
m_totalFixedSize += r.bottom - r.top;
else
m_totalFixedSize += r.right - r.left;
continue;
}
wxControl * const control = tool->GetControl();
if ( IsVertical() )
{
// don't embed controls in the vertical toolbar, this doesn't look
// good and wxGTK doesn't do it neither (and the code below can't
// deal with this case)
control->Hide();
continue;
}
control->Show();
wxStaticText * const staticText = tool->GetStaticText();
wxSize size = control->GetSize();
wxSize staticTextSize;
if ( staticText && staticText->IsShown() )
{
staticTextSize = staticText->GetSize();
staticTextSize.y += 3; // margin between control and its label
}
// TB_SETBUTTONINFO message is only supported by comctl32.dll 4.71+
#ifdef TB_SETBUTTONINFO
// available in headers, now check whether it is available now
// (during run-time)
if ( wxApp::GetComCtl32Version() >= 471 )
{
// set the (underlying) separators width to be that of the
// control
TBBUTTONINFO tbbi;
tbbi.cbSize = sizeof(tbbi);
tbbi.dwMask = TBIF_SIZE;
tbbi.cx = (WORD)size.x;
if ( !::SendMessage(GetHwnd(), TB_SETBUTTONINFO,
tool->GetId(), (LPARAM)&tbbi) )
{
// the id is probably invalid?
wxLogLastError(wxT("TB_SETBUTTONINFO"));
}
}
else
#endif // comctl32.dll 4.71
// TB_SETBUTTONINFO unavailable
{
// try adding several separators to fit the controls width
int widthSep = r.right - r.left;
TBBUTTON tbb;
wxZeroMemory(tbb);
tbb.idCommand = 0;
tbb.fsState = TBSTATE_ENABLED;
tbb.fsStyle = TBSTYLE_SEP;
size_t nSeparators = size.x / widthSep;
for ( size_t nSep = 0; nSep < nSeparators; nSep++ )
{
if ( !::SendMessage(GetHwnd(), TB_INSERTBUTTON,
toolIndex, (LPARAM)&tbb) )
{
wxLogLastError(wxT("TB_INSERTBUTTON"));
}
toolIndex++;
}
// remember the number of separators we used - we'd have to
// delete all of them later
tool->SetSeparatorsCount(nSeparators);
// adjust the controls width to exactly cover the separators
size.x = (nSeparators + 1)*widthSep;
control->SetSize(size.x, wxDefaultCoord);
}
// position the control itself correctly vertically centering it on the
// icon area of the toolbar
int height = r.bottom - r.top - staticTextSize.y;
int diff = height - size.y;
if ( diff < 0 || !HasFlag(wxTB_TEXT) )
{
// not enough room for the static text
if ( staticText )
staticText->Hide();
// recalculate height & diff without the staticText control
height = r.bottom - r.top;
diff = height - size.y;
if ( diff < 0 )
{
// the control is too high, resize to fit
control->SetSize(wxDefaultCoord, height - 2);
diff = 2;
}
}
else // enough space for both the control and the label
{
if ( staticText )
staticText->Show();
}
control->Move(r.left, r.top + (diff + 1) / 2);
if ( staticText )
{
staticText->Move(r.left + (size.x - staticTextSize.x)/2,
r.bottom - staticTextSize.y);
}
m_totalFixedSize += size.x;
}
// the max index is the "real" number of buttons - i.e. counting even the
// separators which we added just for aligning the controls
m_nButtons = toolIndex;
if ( !IsVertical() )
{
if ( m_maxRows == 0 )
// if not set yet, only one row
SetRows(1);
}
else if ( m_nButtons > 0 ) // vertical non empty toolbar
{
// if not set yet, have one column
m_maxRows = 1;
SetRows(m_nButtons);
}
InvalidateBestSize();
UpdateSize();
if ( IsVertical() )
{
// For vertical toolbar heights of buttons are incorrect
// unless TB_AUTOSIZE in invoked.
// We need to recalculate fixed elements size again.
m_totalFixedSize = 0;
toolIndex = 0;
for ( node = m_tools.GetFirst(); node; node = node->GetNext(), toolIndex++ )
{
wxToolBarTool * const tool = (wxToolBarTool*)node->GetData();
if ( !tool->IsStretchableSpace() )
{
const RECT r = wxGetTBItemRect(GetHwnd(), toolIndex);
if ( !IsVertical() )
m_totalFixedSize += r.right - r.left;
else if ( !tool->IsControl() )
m_totalFixedSize += r.bottom - r.top;
}
}
// Enforce invoking UpdateStretchableSpacersSize() with correct value of fixed elements size.
UpdateSize();
}
return true;
}
void wxToolBar::UpdateStretchableSpacersSize()
{
// check if we have any stretchable spacers in the first place
unsigned numSpaces = 0;
wxToolBarToolsList::compatibility_iterator node;
for ( node = m_tools.GetFirst(); node; node = node->GetNext() )
{
wxToolBarTool * const tool = (wxToolBarTool*)node->GetData();
if ( tool->IsStretchableSpace() )
numSpaces++;
}
if ( !numSpaces )
return;
// we do, adjust their size: either distribute the extra size among them or
// reduce their size if there is not enough place for all tools
const int totalSize = IsVertical() ? GetClientSize().y : GetClientSize().x;
const int extraSize = totalSize - m_totalFixedSize;
const int sizeSpacer = extraSize > 0 ? extraSize / numSpaces : 1;
// the last spacer should consume all remaining space if we have too much
// of it (which can be greater than sizeSpacer because of the rounding)
const int sizeLastSpacer = extraSize > 0
? extraSize - (numSpaces - 1)*sizeSpacer
: 1;
// cumulated offset by which we need to move all the following controls to
// the right: while the toolbar takes care of the normal items, we must
// move the controls manually ourselves to ensure they remain at the
// correct place
int offset = 0;
int toolIndex = 0;
for ( node = m_tools.GetFirst(); node; node = node->GetNext(), toolIndex++ )
{
wxToolBarTool * const tool = (wxToolBarTool*)node->GetData();
if ( tool->IsControl() && offset )
{
tool->MoveBy(offset);
continue;
}
if ( !tool->IsStretchableSpace() )
continue;
const RECT rcOld = wxGetTBItemRect(GetHwnd(), toolIndex);
const int oldSize = IsVertical()? (rcOld.bottom - rcOld.top): (rcOld.right - rcOld.left);
const int newSize = --numSpaces ? sizeSpacer : sizeLastSpacer;
if ( newSize != oldSize)
{
if ( !::SendMessage(GetHwnd(), TB_DELETEBUTTON, toolIndex, 0) )
{
wxLogLastError(wxT("TB_DELETEBUTTON (separator)"));
}
else
{
TBBUTTON button;
wxZeroMemory(button);
button.idCommand = tool->GetId();
button.iBitmap = newSize; // set separator width/height
button.fsState = TBSTATE_ENABLED;
button.fsStyle = TBSTYLE_SEP;
if ( IsVertical() )
button.fsState |= TBSTATE_WRAP;
if ( !::SendMessage(GetHwnd(), TB_INSERTBUTTON, toolIndex, (LPARAM)&button) )
{
wxLogLastError(wxT("TB_INSERTBUTTON (separator)"));
}
else
{
// We successfully replaced this seprator, move all the controls after it
// by the corresponding amount (may be positive or negative)
offset += newSize - oldSize;
}
}
}
}
}
// ----------------------------------------------------------------------------
// message handlers
// ----------------------------------------------------------------------------
bool wxToolBar::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD id_)
{
// cast to signed is important as we compare this id with (signed) ints in
// FindById() and without the cast we'd get a positive int from a
// "negative" (i.e. > 32767) WORD
const int id = (signed short)id_;
wxToolBarToolBase *tool = FindById(id);
if ( !tool )
return false;
bool toggled = false; // just to suppress warnings
LRESULT state = ::SendMessage(GetHwnd(), TB_GETSTATE, id, 0);
if ( tool->CanBeToggled() )
{
toggled = (state & TBSTATE_CHECKED) != 0;
// ignore the event when a radio button is released, as this doesn't
// seem to happen at all, and is handled otherwise
if ( tool->GetKind() == wxITEM_RADIO && !toggled )
return true;
tool->Toggle(toggled);
UnToggleRadioGroup(tool);
}
// Without the two lines of code below, if the toolbar was repainted during
// OnLeftClick(), then it could end up without the tool bitmap temporarily
// (see http://lists.nongnu.org/archive/html/lmi/2008-10/msg00014.html).
// The Update() call below ensures that this won't happen, by repainting
// invalidated areas of the toolbar immediately.
//
// To complicate matters, the tool would be drawn in depressed state (this
// code is called when mouse button is released, not pressed). That's not
// ideal, having the tool pressed for the duration of OnLeftClick()
// provides the user with useful visual clue that the app is busy reacting
// to the event. So we manually put the tool into pressed state, handle the
// event and then finally restore tool's original state.
::SendMessage(GetHwnd(), TB_SETSTATE, id, MAKELONG(state | TBSTATE_PRESSED, 0));
Update();
bool allowLeftClick = OnLeftClick(id, toggled);
// Restore the unpressed state. Enabled/toggled state might have been
// changed since so take care of it.
if (tool->IsEnabled())
state |= TBSTATE_ENABLED;
else
state &= ~TBSTATE_ENABLED;
if (tool->IsToggled())
state |= TBSTATE_CHECKED;
else
state &= ~TBSTATE_CHECKED;
::SendMessage(GetHwnd(), TB_SETSTATE, id, MAKELONG(state, 0));
// OnLeftClick() can veto the button state change - for buttons which
// may be toggled only, of course.
if ( !allowLeftClick && tool->CanBeToggled() )
{
// revert back
tool->Toggle(!toggled);
::SendMessage(GetHwnd(), TB_CHECKBUTTON, id, MAKELONG(!toggled, 0));
}
return true;
}
bool wxToolBar::MSWOnNotify(int WXUNUSED(idCtrl),
WXLPARAM lParam,
WXLPARAM *WXUNUSED(result))
{
LPNMHDR hdr = (LPNMHDR)lParam;
if ( hdr->code == TBN_DROPDOWN )
{
LPNMTOOLBAR tbhdr = (LPNMTOOLBAR)lParam;
wxCommandEvent evt(wxEVT_TOOL_DROPDOWN, tbhdr->iItem);
if ( HandleWindowEvent(evt) )
{
// Event got handled, don't display default popup menu
return false;
}
const wxToolBarToolBase * const tool = FindById(tbhdr->iItem);
wxCHECK_MSG( tool, false, wxT("drop down message for unknown tool") );
wxMenu * const menu = tool->GetDropdownMenu();
if ( !menu )
return false;
// Display popup menu below button
const RECT r = wxGetTBItemRect(GetHwnd(), GetToolPos(tbhdr->iItem));
if ( r.right )
PopupMenu(menu, r.left, r.bottom);
return true;
}
if( !HasFlag(wxTB_NO_TOOLTIPS) )
{
#if wxUSE_TOOLTIPS
// First check if this applies to us
// the tooltips control created by the toolbar is sometimes Unicode, even
// in an ANSI application - this seems to be a bug in comctl32.dll v5
UINT code = hdr->code;
if ( (code != (UINT) TTN_NEEDTEXTA) && (code != (UINT) TTN_NEEDTEXTW) )
return false;
HWND toolTipWnd = (HWND)::SendMessage(GetHwnd(), TB_GETTOOLTIPS, 0, 0);
if ( toolTipWnd != hdr->hwndFrom )
return false;
LPTOOLTIPTEXT ttText = (LPTOOLTIPTEXT)lParam;
int id = (int)ttText->hdr.idFrom;
wxToolBarToolBase *tool = FindById(id);
if ( tool )
return HandleTooltipNotify(code, lParam, tool->GetShortHelp());
#else
wxUnusedVar(lParam);
#endif
}
return false;
}
// ----------------------------------------------------------------------------
// toolbar geometry
// ----------------------------------------------------------------------------
void wxToolBar::SetToolBitmapSize(const wxSize& size)
{
// Leave the effective size as (0, 0) if we are not showing bitmaps at all.
wxSize effectiveSize;
if ( !HasFlag(wxTB_NOICONS) )
effectiveSize = size;
wxToolBarBase::SetToolBitmapSize(size);
::SendMessage(GetHwnd(), TB_SETBITMAPSIZE, 0, MAKELONG(size.x, size.y));
}
void wxToolBar::SetRows(int nRows)
{
if ( nRows == m_maxRows )
{
// avoid resizing the frame uselessly
return;
}
// TRUE in wParam means to create at least as many rows, FALSE -
// at most as many
RECT rect;
::SendMessage(GetHwnd(), TB_SETROWS,
MAKEWPARAM(nRows, !(GetWindowStyle() & wxTB_VERTICAL)),
(LPARAM) &rect);
m_maxRows = nRows;
UpdateSize();
}
// The button size is bigger than the bitmap size
wxSize wxToolBar::GetToolSize() const
{
// TB_GETBUTTONSIZE is supported from version 4.70
#if defined(_WIN32_IE) && (_WIN32_IE >= 0x300 ) \
&& !( defined(__GNUWIN32__) && !wxCHECK_W32API_VERSION( 1, 0 ) ) \
&& !defined (__DIGITALMARS__)
if ( wxApp::GetComCtl32Version() >= 470 )
{
DWORD dw = ::SendMessage(GetHwnd(), TB_GETBUTTONSIZE, 0, 0);
return wxSize(LOWORD(dw), HIWORD(dw));
}
else
#endif // comctl32.dll 4.70+
{
// defaults
return wxSize(m_defaultWidth + 8, m_defaultHeight + 7);
}
}
static
wxToolBarToolBase *GetItemSkippingDummySpacers(const wxToolBarToolsList& tools,
size_t index )
{
wxToolBarToolsList::compatibility_iterator current = tools.GetFirst();
for ( ; current ; current = current->GetNext() )
{
if ( index == 0 )
return current->GetData();
wxToolBarTool *tool = (wxToolBarTool *)current->GetData();
size_t separators = tool->GetSeparatorsCount();
// if it is a normal button, sepcount == 0, so skip 1 item (the button)
// otherwise, skip as many items as the separator count, plus the
// control itself
index -= separators ? separators + 1 : 1;
}
return 0;
}
wxToolBarToolBase *wxToolBar::FindToolForPosition(wxCoord x, wxCoord y) const
{
POINT pt;
pt.x = x;
pt.y = y;
int index = (int)::SendMessage(GetHwnd(), TB_HITTEST, 0, (LPARAM)&pt);
// MBN: when the point ( x, y ) is close to the toolbar border
// TB_HITTEST returns m_nButtons ( not -1 )
if ( index < 0 || (size_t)index >= m_nButtons )
// it's a separator or there is no tool at all there
return NULL;
// when TB_SETBUTTONINFO is available (both during compile- and run-time),
// we don't use the dummy separators hack
#ifdef TB_SETBUTTONINFO
if ( wxApp::GetComCtl32Version() >= 471 )
{
return m_tools.Item((size_t)index)->GetData();
}
else
#endif // TB_SETBUTTONINFO
{
return GetItemSkippingDummySpacers( m_tools, (size_t) index );
}
}
void wxToolBar::UpdateSize()
{
wxPoint pos = GetPosition();
::SendMessage(GetHwnd(), TB_AUTOSIZE, 0, 0);
if (pos != GetPosition())
Move(pos);
// In case Realize is called after the initial display (IOW the programmer
// may have rebuilt the toolbar) give the frame the option of resizing the
// toolbar to full width again, but only if the parent is a frame and the
// toolbar is managed by the frame. Otherwise assume that some other
// layout mechanism is controlling the toolbar size and leave it alone.
SendSizeEventToParent();
}
// ----------------------------------------------------------------------------
// toolbar styles
// ---------------------------------------------------------------------------
// get the TBSTYLE of the given toolbar window
long wxToolBar::GetMSWToolbarStyle() const
{
return ::SendMessage(GetHwnd(), TB_GETSTYLE, 0, 0L);
}
void wxToolBar::SetWindowStyleFlag(long style)
{
// the style bits whose changes force us to recreate the toolbar
static const long MASK_NEEDS_RECREATE = wxTB_TEXT | wxTB_NOICONS;
const long styleOld = GetWindowStyle();
wxToolBarBase::SetWindowStyleFlag(style);
// don't recreate an empty toolbar: not only this is unnecessary, but it is
// also fatal as we'd then try to recreate the toolbar when it's just being
// created
if ( GetToolsCount() &&
(style & MASK_NEEDS_RECREATE) != (styleOld & MASK_NEEDS_RECREATE) )
{
// to remove the text labels, simply re-realizing the toolbar is enough
// but I don't know of any way to add the text to an existing toolbar
// other than by recreating it entirely
Recreate();
}
}
// ----------------------------------------------------------------------------
// tool state
// ----------------------------------------------------------------------------
void wxToolBar::DoEnableTool(wxToolBarToolBase *tool, bool enable)
{
::SendMessage(GetHwnd(), TB_ENABLEBUTTON,
(WPARAM)tool->GetId(), (LPARAM)MAKELONG(enable, 0));
}
void wxToolBar::DoToggleTool(wxToolBarToolBase *tool, bool toggle)
{
::SendMessage(GetHwnd(), TB_CHECKBUTTON,
(WPARAM)tool->GetId(), (LPARAM)MAKELONG(toggle, 0));
}
void wxToolBar::DoSetToggle(wxToolBarToolBase *WXUNUSED(tool), bool WXUNUSED(toggle))
{
// VZ: AFAIK, the button has to be created either with TBSTYLE_CHECK or
// without, so we really need to delete the button and recreate it here
wxFAIL_MSG( wxT("not implemented") );
}
void wxToolBar::SetToolNormalBitmap( int id, const wxBitmap& bitmap )
{
wxToolBarTool* tool = static_cast<wxToolBarTool*>(FindById(id));
if ( tool )
{
wxCHECK_RET( tool->IsButton(), wxT("Can only set bitmap on button tools."));
tool->SetNormalBitmap(bitmap);
Realize();
}
}
void wxToolBar::SetToolDisabledBitmap( int id, const wxBitmap& bitmap )
{
wxToolBarTool* tool = static_cast<wxToolBarTool*>(FindById(id));
if ( tool )
{
wxCHECK_RET( tool->IsButton(), wxT("Can only set bitmap on button tools."));
tool->SetDisabledBitmap(bitmap);
Realize();
}
}
// ----------------------------------------------------------------------------
// event handlers
// ----------------------------------------------------------------------------
// Responds to colour changes, and passes event on to children.
void wxToolBar::OnSysColourChanged(wxSysColourChangedEvent& event)
{
wxRGBToColour(m_backgroundColour, ::GetSysColor(COLOR_BTNFACE));
// Remap the buttons
Realize();
// Relayout the toolbar
int nrows = m_maxRows;
m_maxRows = 0; // otherwise SetRows() wouldn't do anything
SetRows(nrows);
Refresh();
// let the event propagate further
event.Skip();
}
void wxToolBar::OnMouseEvent(wxMouseEvent& event)
{
if ( event.Leaving() )
{
if ( m_pInTool )
{
OnMouseEnter(wxID_ANY);
m_pInTool = NULL;
}
event.Skip();
return;
}
if ( event.RightDown() )
{
// find the tool under the mouse
wxCoord x = 0, y = 0;
event.GetPosition(&x, &y);
wxToolBarToolBase *tool = FindToolForPosition(x, y);
OnRightClick(tool ? tool->GetId() : -1, x, y);
}
else
{
event.Skip();
}
}
// This handler is needed to fix problems with painting the background of
// toolbar icons with comctl32.dll < 6.0.
void wxToolBar::OnEraseBackground(wxEraseEvent& event)
{
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
MSWDoEraseBackground(event.GetDC()->GetHDC());
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
}
bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
{
// wait until we have some tools
const int toolsCount = GetToolsCount();
if ( toolsCount == 0 )
return false;
// calculate our minor dimension ourselves - we're confusing the standard
// logic (TB_AUTOSIZE) with our horizontal toolbars and other hacks
// Find bounding box for any toolbar item.
RECT r;
::SetRectEmpty(&r);
wxToolBarToolsList::compatibility_iterator node;
int i = 0;
for ( node = m_tools.GetFirst(); node; node = node->GetNext(), i++)
{
wxToolBarTool * const tool = (wxToolBarTool*)node->GetData();
// Separators shouldn't be taken into account as they are sometimes
// reported to have the width of the entire client area by the toolbar.
// And we know that they are not the biggest items in the toolbar in
// any case, so just skip them.
if( !tool->IsSeparator() )
{
RECT ritem = wxGetTBItemRect(GetHwnd(), i);
::OffsetRect(&ritem, -ritem.left, -ritem.top); // Shift origin to (0,0)
::UnionRect(&r, &r, &ritem);
}
}
if ( !r.right )
return false;
int w, h;
if ( IsVertical() )
{
w = r.right - r.left;
if ( m_maxRows )
{
w *= (m_nButtons + m_maxRows - 1)/m_maxRows;
}
h = HIWORD(lParam);
}
else
{
w = LOWORD(lParam);
if (HasFlag( wxTB_FLAT ))
h = r.bottom - r.top - 3;
else
h = r.bottom - r.top;
if ( m_maxRows )
{
// FIXME: hardcoded separator line height...
h += HasFlag(wxTB_NODIVIDER) ? 4 : 6;
h *= m_maxRows;
}
}
if ( MAKELPARAM(w, h) != lParam )
{
// size really changed
SetSize(w, h);
}
UpdateStretchableSpacersSize();
// message processed
return true;
}
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
bool wxToolBar::HandlePaint(WXWPARAM wParam, WXLPARAM lParam)
{
// we must prevent the dummy separators corresponding to controls or
// stretchable spaces from being seen: we used to do it by painting over
// them but this, unsurprisingly, resulted in a lot of flicker so now we
// prevent the toolbar from painting them at all
// compute the region containing all dummy separators which we don't want
// to be seen
wxRegion rgnDummySeps;
const wxRect rectTotal = GetClientRect();
int toolIndex = 0;
for ( wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst();
node;
node = node->GetNext() )
{
wxToolBarTool * const
tool = static_cast<wxToolBarTool *>(node->GetData());
if ( tool->IsControl() || tool->IsStretchableSpace() )
{
const size_t numSeps = tool->GetSeparatorsCount();
for ( size_t n = 0; n < numSeps; n++, toolIndex++ )
{
// for some reason TB_GETITEMRECT returns a rectangle 1 pixel
// shorter than the full window size (at least under Windows 7)
// but we need to erase the full width/height below
RECT rcItem = wxGetTBItemRect(GetHwnd(), toolIndex);
if ( IsVertical() )
{
rcItem.left = 0;
rcItem.right = rectTotal.width;
}
else
{
rcItem.top = 0;
rcItem.bottom = rectTotal.height;
}
// Apparently, regions of height < 3 are not taken into account
// in clipping so we need to extend them for this purpose.
if ( rcItem.bottom - rcItem.top > 0 && rcItem.bottom - rcItem.top < 3 )
rcItem.bottom = rcItem.top + 3;
rgnDummySeps.Union(wxRectFromRECT(rcItem));
}
}
else
{
// normal tools never correspond to more than one native button
toolIndex++;
}
}
if ( rgnDummySeps.IsOk() )
{
// exclude the area occupied by the controls and stretchable spaces
// from the update region to prevent the toolbar from drawing
// separators in it
if ( !::ValidateRgn(GetHwnd(), GetHrgnOf(rgnDummySeps)) )
{
wxLogLastError(wxT("ValidateRgn()"));
}
}
// still let the native control draw everything else normally but set up a
// hook to be able to process the next WM_ERASEBKGND sent to our parent
// because toolbar will ask it to erase its background from its WM_PAINT
// handler (when using TBSTYLE_TRANSPARENT which we do always use)
//
// installing hook is not completely trivial as all kinds of strange
// situations are possible: sometimes we can be called recursively from
// inside the native toolbar WM_PAINT handler so the hook might already be
// installed and sometimes the native toolbar might not send WM_ERASEBKGND
// to the parent at all for whatever reason, so deal with all these cases
wxWindow * const parent = GetParent();
const bool hadHook = parent->MSWHasEraseBgHook();
if ( !hadHook )
GetParent()->MSWSetEraseBgHook(this);
MSWDefWindowProc(WM_PAINT, wParam, lParam);
if ( !hadHook )
GetParent()->MSWSetEraseBgHook(NULL);
if ( rgnDummySeps.IsOk() )
{
// erase the dummy separators region ourselves now as nobody painted
// over them
WindowHDC hdc(GetHwnd());
::SelectClipRgn(hdc, GetHrgnOf(rgnDummySeps));
MSWDoEraseBackground(hdc);
}
return true;
}
WXHBRUSH wxToolBar::MSWGetToolbarBgBrush()
{
// we conservatively use a solid brush here but we could also use a themed
// brush by using DrawThemeBackground() to create a bitmap brush (it'd need
// to be invalidated whenever the toolbar is resized and, also, correctly
// aligned using SetBrushOrgEx() before each use -- there is code for doing
// this in wxNotebook already so it'd need to be refactored into wxWindow)
//
// however inasmuch as there is a default background for the toolbar at all
// (and this is not a trivial question as different applications use very
// different colours), it seems to be a solid one and using REBAR
// background brush as we used to do before doesn't look good at all under
// Windows 7 (and probably Vista too), so for now we just keep it simple
wxColour const
colBg = m_hasBgCol ? GetBackgroundColour()
: wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE);
wxBrush * const
brush = wxTheBrushList->FindOrCreateBrush(colBg);
return brush ? static_cast<WXHBRUSH>(brush->GetResourceHandle()) : 0;
}
WXHBRUSH wxToolBar::MSWGetBgBrushForChild(WXHDC hDC, wxWindowMSW *child)
{
WXHBRUSH hbr = wxToolBarBase::MSWGetBgBrushForChild(hDC, child);
if ( hbr )
return hbr;
// the base class version only returns a brush for erasing children
// background if we have a non-default background colour but as the toolbar
// doesn't erase its own background by default, we need to always do it for
// (semi-)transparent children
if ( child->GetParent() == this && child->HasTransparentBackground() )
return MSWGetToolbarBgBrush();
return 0;
}
void wxToolBar::MSWDoEraseBackground(WXHDC hDC)
{
wxFillRect(GetHwnd(), (HDC)hDC, (HBRUSH)MSWGetToolbarBgBrush());
}
bool wxToolBar::MSWEraseBgHook(WXHDC hDC)
{
// toolbar WM_PAINT handler offsets the DC origin before sending
// WM_ERASEBKGND to the parent but as we handle it in the toolbar itself,
// we need to reset it back
HDC hdc = (HDC)hDC;
POINT ptOldOrg;
if ( !::SetWindowOrgEx(hdc, 0, 0, &ptOldOrg) )
{
wxLogLastError(wxT("SetWindowOrgEx(tbar-bg-hdc)"));
return false;
}
MSWDoEraseBackground(hDC);
::SetWindowOrgEx(hdc, ptOldOrg.x, ptOldOrg.y, NULL);
return true;
}
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
void wxToolBar::HandleMouseMove(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
{
wxCoord x = GET_X_LPARAM(lParam),
y = GET_Y_LPARAM(lParam);
wxToolBarToolBase* tool = FindToolForPosition( x, y );
// has the current tool changed?
if ( tool != m_pInTool )
{
m_pInTool = tool;
OnMouseEnter(tool ? tool->GetId() : wxID_ANY);
}
}
WXLRESULT wxToolBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
switch ( nMsg )
{
case WM_MOUSEMOVE:
// we don't handle mouse moves, so always pass the message to
// wxControl::MSWWindowProc (HandleMouseMove just calls OnMouseEnter)
HandleMouseMove(wParam, lParam);
break;
case WM_SIZE:
if ( HandleSize(wParam, lParam) )
return 0;
break;
#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
case WM_PAINT:
// refreshing the controls in the toolbar inside a composite window
// results in an endless stream of WM_PAINT messages -- and seems
// to be unnecessary anyhow as everything works just fine without
// any special workarounds in this case
if ( !IsDoubleBuffered() && HandlePaint(wParam, lParam) )
return 0;
break;
#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
case WM_PRINTCLIENT:
wxFillRect(GetHwnd(), (HDC)wParam, MSWGetToolbarBgBrush());
return 1;
}
return wxControl::MSWWindowProc(nMsg, wParam, lParam);
}
// ----------------------------------------------------------------------------
// private functions
// ----------------------------------------------------------------------------
#ifdef wxREMAP_BUTTON_COLOURS
WXHBITMAP wxToolBar::MapBitmap(WXHBITMAP bitmap, int width, int height)
{
MemoryHDC hdcMem;
if ( !hdcMem )
{
wxLogLastError(wxT("CreateCompatibleDC"));
return bitmap;
}
SelectInHDC bmpInHDC(hdcMem, (HBITMAP)bitmap);
if ( !bmpInHDC )
{
wxLogLastError(wxT("SelectObject"));
return bitmap;
}
wxCOLORMAP *cmap = wxGetStdColourMap();
for ( int i = 0; i < width; i++ )
{
for ( int j = 0; j < height; j++ )
{
COLORREF pixel = ::GetPixel(hdcMem, i, j);
for ( size_t k = 0; k < wxSTD_COL_MAX; k++ )
{
COLORREF col = cmap[k].from;
if ( abs(GetRValue(pixel) - GetRValue(col)) < 10 &&
abs(GetGValue(pixel) - GetGValue(col)) < 10 &&
abs(GetBValue(pixel) - GetBValue(col)) < 10 )
{
if ( cmap[k].to != pixel )
::SetPixel(hdcMem, i, j, cmap[k].to);
break;
}
}
}
}
return bitmap;
}
#endif // wxREMAP_BUTTON_COLOURS
#endif // wxUSE_TOOLBAR