Add wxTimePickerCtrl class.
Implement wxTimePickerCtrl natively for MSW and add a generic implementation (very loosely based on the original class by Paul Breen) for the other platforms. Also update the calendar sample to show the new control. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@69224 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
672
src/generic/timectrl.cpp
Normal file
672
src/generic/timectrl.cpp
Normal file
@@ -0,0 +1,672 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Name: src/generic/timectrl.cpp
|
||||
// Purpose: Generic implementation of wxTimePickerCtrl.
|
||||
// Author: Paul Breen, Vadim Zeitlin
|
||||
// Created: 2011-09-22
|
||||
// RCS-ID: $Id: wxhead.cpp,v 1.11 2010-04-22 12:44:51 zeitlin Exp $
|
||||
// Copyright: (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
|
||||
// Licence: wxWindows licence
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// ============================================================================
|
||||
// declarations
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// headers
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// for compilers that support precompilation, includes "wx.h".
|
||||
#include "wx/wxprec.h"
|
||||
|
||||
#ifdef __BORLANDC__
|
||||
#pragma hdrstop
|
||||
#endif
|
||||
|
||||
#if wxUSE_TIMEPICKCTRL
|
||||
|
||||
#ifndef WX_PRECOMP
|
||||
#include "wx/textctrl.h"
|
||||
#endif // WX_PRECOMP
|
||||
|
||||
#include "wx/timectrl.h"
|
||||
|
||||
// This class is only compiled if there is no native version or if we
|
||||
// explicitly want to use both the native and generic one (this is useful for
|
||||
// testing but not much otherwise and so by default we don't use the generic
|
||||
// implementation if a native one is available).
|
||||
#if !defined(wxHAS_NATIVE_TIMEPICKERCTRL) || wxUSE_TIMEPICKCTRL_GENERIC
|
||||
|
||||
#include "wx/generic/timectrl.h"
|
||||
|
||||
#include "wx/dateevt.h"
|
||||
#include "wx/spinbutt.h"
|
||||
|
||||
#ifndef wxHAS_NATIVE_TIMEPICKERCTRL
|
||||
IMPLEMENT_DYNAMIC_CLASS(wxTimePickerCtrl, wxControl)
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Constants
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
enum
|
||||
{
|
||||
// Horizontal margin between the text and spin control.
|
||||
HMARGIN_TEXT_SPIN = 2
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxTimePickerGenericImpl: used to implement wxTimePickerCtrlGeneric
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class wxTimePickerGenericImpl : public wxEvtHandler
|
||||
{
|
||||
public:
|
||||
wxTimePickerGenericImpl(wxTimePickerCtrlGeneric* ctrl)
|
||||
{
|
||||
m_text = new wxTextCtrl(ctrl, wxID_ANY, wxString());
|
||||
|
||||
// As this text can't be edited, don't use the standard cursor for it
|
||||
// to avoid misleading the user. Ideally we'd also hide the caret but
|
||||
// this is not currently supported by wxTextCtrl.
|
||||
m_text->SetCursor(wxCURSOR_ARROW);
|
||||
|
||||
m_btn = new wxSpinButton(ctrl);
|
||||
|
||||
m_currentField = Field_Hour;
|
||||
m_isFirstDigit = true;
|
||||
|
||||
// We don't support arbitrary formats currently as this requires
|
||||
// significantly more work both here and also in wxLocale::GetInfo().
|
||||
//
|
||||
// For now just use either "%H:%M:%S" or "%I:%M:%S %p". It would be
|
||||
// nice to add support to "%k" and "%l" (hours with leading blanks
|
||||
// instead of zeros) too as this is the most common unsupported case in
|
||||
// practice.
|
||||
m_useAMPM = wxLocale::GetInfo(wxLOCALE_TIME_FMT).Contains("%p");
|
||||
|
||||
m_text->Connect
|
||||
(
|
||||
wxEVT_SET_FOCUS,
|
||||
wxFocusEventHandler(wxTimePickerGenericImpl::OnTextSetFocus),
|
||||
NULL,
|
||||
this
|
||||
);
|
||||
m_text->Connect
|
||||
(
|
||||
wxEVT_KEY_DOWN,
|
||||
wxKeyEventHandler(wxTimePickerGenericImpl::OnTextKeyDown),
|
||||
NULL,
|
||||
this
|
||||
);
|
||||
m_text->Connect
|
||||
(
|
||||
wxEVT_LEFT_DOWN,
|
||||
wxMouseEventHandler(wxTimePickerGenericImpl::OnTextClick),
|
||||
NULL,
|
||||
this
|
||||
);
|
||||
|
||||
m_btn->Connect
|
||||
(
|
||||
wxEVT_SPIN_UP,
|
||||
wxSpinEventHandler(wxTimePickerGenericImpl::OnArrowUp),
|
||||
NULL,
|
||||
this
|
||||
);
|
||||
m_btn->Connect
|
||||
(
|
||||
wxEVT_SPIN_DOWN,
|
||||
wxSpinEventHandler(wxTimePickerGenericImpl::OnArrowDown),
|
||||
NULL,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
// Set the new value.
|
||||
void SetValue(const wxDateTime& time)
|
||||
{
|
||||
m_time = time.IsValid() ? time : wxDateTime::Now();
|
||||
|
||||
UpdateTextWithoutEvent();
|
||||
}
|
||||
|
||||
|
||||
// The text part of the control.
|
||||
wxTextCtrl* m_text;
|
||||
|
||||
// The spin button used to change the text fields.
|
||||
wxSpinButton* m_btn;
|
||||
|
||||
// The current time (date part is ignored).
|
||||
wxDateTime m_time;
|
||||
|
||||
private:
|
||||
// The logical fields of the text control (AM/PM one may not be present).
|
||||
enum Field
|
||||
{
|
||||
Field_Hour,
|
||||
Field_Min,
|
||||
Field_Sec,
|
||||
Field_AMPM,
|
||||
Field_Max
|
||||
};
|
||||
|
||||
// Direction of change of time fields.
|
||||
enum Direction
|
||||
{
|
||||
// Notice that the enum elements values matter.
|
||||
Dir_Down = -1,
|
||||
Dir_Up = +1
|
||||
};
|
||||
|
||||
// A range of character positions ("from" is inclusive, "to" -- exclusive).
|
||||
struct CharRange
|
||||
{
|
||||
int from,
|
||||
to;
|
||||
};
|
||||
|
||||
// Event handlers for various events in our controls.
|
||||
void OnTextSetFocus(wxFocusEvent& event)
|
||||
{
|
||||
HighlightCurrentField();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
// Keyboard interface here is modelled over MSW native control and may need
|
||||
// adjustments for other platforms.
|
||||
void OnTextKeyDown(wxKeyEvent& event)
|
||||
{
|
||||
const int key = event.GetKeyCode();
|
||||
|
||||
switch ( key )
|
||||
{
|
||||
case WXK_DOWN:
|
||||
ChangeCurrentFieldBy1(Dir_Down);
|
||||
break;
|
||||
|
||||
case WXK_UP:
|
||||
ChangeCurrentFieldBy1(Dir_Up);
|
||||
break;
|
||||
|
||||
case WXK_LEFT:
|
||||
CycleCurrentField(Dir_Down);
|
||||
break;
|
||||
|
||||
case WXK_RIGHT:
|
||||
CycleCurrentField(Dir_Up);
|
||||
break;
|
||||
|
||||
case WXK_HOME:
|
||||
ResetCurrentField(Dir_Down);
|
||||
break;
|
||||
|
||||
case WXK_END:
|
||||
ResetCurrentField(Dir_Up);
|
||||
break;
|
||||
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
// The digits work in all keys except AM/PM.
|
||||
if ( m_currentField != Field_AMPM )
|
||||
{
|
||||
AppendDigitToCurrentField(key - '0');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'A':
|
||||
case 'P':
|
||||
// These keys only work to toggle AM/PM field.
|
||||
if ( m_currentField == Field_AMPM )
|
||||
{
|
||||
unsigned hour = m_time.GetHour();
|
||||
if ( key == 'A' )
|
||||
{
|
||||
if ( hour >= 12 )
|
||||
hour -= 12;
|
||||
}
|
||||
else // PM
|
||||
{
|
||||
if ( hour < 12 )
|
||||
hour += 12;
|
||||
}
|
||||
|
||||
if ( hour != m_time.GetHour() )
|
||||
{
|
||||
m_time.SetHour(hour);
|
||||
UpdateText();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Do not skip the other events, just consume them to prevent the
|
||||
// user from editing the text directly.
|
||||
}
|
||||
}
|
||||
|
||||
void OnTextClick(wxMouseEvent& event)
|
||||
{
|
||||
Field field wxDUMMY_INITIALIZE(Field_Max);
|
||||
long pos;
|
||||
switch ( m_text->HitTest(event.GetPosition(), &pos) )
|
||||
{
|
||||
case wxTE_HT_UNKNOWN:
|
||||
// Don't do anything, it's better than doing something wrong.
|
||||
return;
|
||||
|
||||
case wxTE_HT_BEFORE:
|
||||
// Select the first field.
|
||||
field = Field_Hour;
|
||||
break;
|
||||
|
||||
case wxTE_HT_ON_TEXT:
|
||||
// Find the field containing this position.
|
||||
for ( field = Field_Hour; field <= GetLastField(); )
|
||||
{
|
||||
const CharRange range = GetFieldRange(field);
|
||||
|
||||
// Normally the "to" end is exclusive but we want to give
|
||||
// focus to some field when the user clicks between them so
|
||||
// count it as part of the preceding field here.
|
||||
if ( range.from <= pos && pos <= range.to )
|
||||
break;
|
||||
|
||||
field = static_cast<Field>(field + 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case wxTE_HT_BELOW:
|
||||
// This shouldn't happen for single line control.
|
||||
wxFAIL_MSG( "Unreachable" );
|
||||
// fall through
|
||||
|
||||
case wxTE_HT_BEYOND:
|
||||
// Select the last field.
|
||||
field = GetLastField();
|
||||
break;
|
||||
}
|
||||
|
||||
ChangeCurrentField(field);
|
||||
|
||||
// As we don't skip the event, we also prevent the system from setting
|
||||
// focus to this control as it does by default, so do it manually.
|
||||
m_text->SetFocus();
|
||||
}
|
||||
|
||||
void OnArrowUp(wxSpinEvent& WXUNUSED(event))
|
||||
{
|
||||
ChangeCurrentFieldBy1(Dir_Up);
|
||||
|
||||
m_text->SetFocus();
|
||||
}
|
||||
|
||||
void OnArrowDown(wxSpinEvent& WXUNUSED(event))
|
||||
{
|
||||
ChangeCurrentFieldBy1(Dir_Down);
|
||||
|
||||
m_text->SetFocus();
|
||||
}
|
||||
|
||||
|
||||
// Get the range of the given field in character positions ("from" is
|
||||
// inclusive, "to" exclusive).
|
||||
static CharRange GetFieldRange(Field field)
|
||||
{
|
||||
// Currently we can just hard code the ranges as they are the same for
|
||||
// both supported formats, if we want to support arbitrary formats in
|
||||
// the future, we'd need to determine them dynamically by examining the
|
||||
// format here.
|
||||
static const CharRange ranges[] =
|
||||
{
|
||||
{ 0, 2 },
|
||||
{ 3, 5 },
|
||||
{ 6, 8 },
|
||||
{ 9, 11},
|
||||
};
|
||||
|
||||
wxCOMPILE_TIME_ASSERT( WXSIZEOF(ranges) == Field_Max,
|
||||
FieldRangesMismatch );
|
||||
|
||||
return ranges[field];
|
||||
}
|
||||
|
||||
// Get the last field used depending on m_useAMPM.
|
||||
Field GetLastField() const
|
||||
{
|
||||
return m_useAMPM ? Field_AMPM : Field_Sec;
|
||||
}
|
||||
|
||||
// Change the current field. For convenience, accept int field here as this
|
||||
// allows us to use arithmetic operations in the caller.
|
||||
void ChangeCurrentField(int field)
|
||||
{
|
||||
if ( field == m_currentField )
|
||||
return;
|
||||
|
||||
wxCHECK_RET( field <= GetLastField(), "Invalid field" );
|
||||
|
||||
m_currentField = static_cast<Field>(field);
|
||||
m_isFirstDigit = true;
|
||||
|
||||
HighlightCurrentField();
|
||||
}
|
||||
|
||||
// Go to the next (Dir_Up) or previous (Dir_Down) field, wrapping if
|
||||
// necessary.
|
||||
void CycleCurrentField(Direction dir)
|
||||
{
|
||||
const unsigned numFields = GetLastField() + 1;
|
||||
|
||||
ChangeCurrentField((m_currentField + numFields + dir) % numFields);
|
||||
}
|
||||
|
||||
// Select the currently actively field.
|
||||
void HighlightCurrentField()
|
||||
{
|
||||
const CharRange range = GetFieldRange(m_currentField);
|
||||
|
||||
m_text->SetSelection(range.from, range.to);
|
||||
}
|
||||
|
||||
// Decrement or increment the value of the current field (wrapping if
|
||||
// necessary).
|
||||
void ChangeCurrentFieldBy1(Direction dir)
|
||||
{
|
||||
switch ( m_currentField )
|
||||
{
|
||||
case Field_Hour:
|
||||
m_time.SetHour((m_time.GetHour() + 24 + dir) % 24);
|
||||
break;
|
||||
|
||||
case Field_Min:
|
||||
m_time.SetMinute((m_time.GetMinute() + 60 + dir) % 60);
|
||||
break;
|
||||
|
||||
case Field_Sec:
|
||||
m_time.SetSecond((m_time.GetSecond() + 60 + dir) % 60);
|
||||
break;
|
||||
|
||||
case Field_AMPM:
|
||||
m_time.SetHour((m_time.GetHour() + 12) % 24);
|
||||
break;
|
||||
|
||||
case Field_Max:
|
||||
wxFAIL_MSG( "Invalid field" );
|
||||
}
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
// Set the current field to its minimal or maximal value.
|
||||
void ResetCurrentField(Direction dir)
|
||||
{
|
||||
switch ( m_currentField )
|
||||
{
|
||||
case Field_Hour:
|
||||
case Field_AMPM:
|
||||
// In 12-hour mode setting the hour to the minimal value
|
||||
// also changes the suffix to AM and, correspondingly,
|
||||
// setting it to the maximal one changes the suffix to PM.
|
||||
// And, for consistency with the native MSW behaviour, we
|
||||
// also do the same thing when changing AM/PM field itself,
|
||||
// so change hours in any case.
|
||||
m_time.SetHour(dir == Dir_Down ? 0 : 23);
|
||||
break;
|
||||
|
||||
case Field_Min:
|
||||
m_time.SetMinute(dir == Dir_Down ? 0 : 59);
|
||||
break;
|
||||
|
||||
case Field_Sec:
|
||||
m_time.SetSecond(dir == Dir_Down ? 0 : 59);
|
||||
break;
|
||||
|
||||
case Field_Max:
|
||||
wxFAIL_MSG( "Invalid field" );
|
||||
}
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
// Append the given digit (from 0 to 9) to the current value of the current
|
||||
// field.
|
||||
void AppendDigitToCurrentField(int n)
|
||||
{
|
||||
bool moveToNextField = false;
|
||||
|
||||
if ( !m_isFirstDigit )
|
||||
{
|
||||
// The first digit simply replaces the existing field contents,
|
||||
// but the second one should be combined with the previous one,
|
||||
// otherwise entering 2-digit numbers would be impossible.
|
||||
int currentValue wxDUMMY_INITIALIZE(0),
|
||||
maxValue wxDUMMY_INITIALIZE(0);
|
||||
|
||||
switch ( m_currentField )
|
||||
{
|
||||
case Field_Hour:
|
||||
currentValue = m_time.GetHour();
|
||||
maxValue = 23;
|
||||
break;
|
||||
|
||||
case Field_Min:
|
||||
currentValue = m_time.GetMinute();
|
||||
maxValue = 59;
|
||||
break;
|
||||
|
||||
case Field_Sec:
|
||||
currentValue = m_time.GetSecond();
|
||||
maxValue = 59;
|
||||
break;
|
||||
|
||||
case Field_AMPM:
|
||||
case Field_Max:
|
||||
wxFAIL_MSG( "Invalid field" );
|
||||
}
|
||||
|
||||
// Check if the new value is acceptable. If not, we just handle
|
||||
// this digit as if it were the first one.
|
||||
int newValue = currentValue*10 + n;
|
||||
if ( newValue < maxValue )
|
||||
{
|
||||
n = newValue;
|
||||
|
||||
// If we're not on the seconds field, advance to the next one.
|
||||
// This makes it more convenient to enter times as you can just
|
||||
// press all digits one after one without touching the cursor
|
||||
// arrow keys at all.
|
||||
//
|
||||
// Notice that MSW native control doesn't do this but it seems
|
||||
// so useful that we intentionally diverge from it here.
|
||||
moveToNextField = true;
|
||||
|
||||
// We entered both digits so the next one will be "first" again.
|
||||
m_isFirstDigit = true;
|
||||
}
|
||||
}
|
||||
else // First digit entered.
|
||||
{
|
||||
// The next one won't be first any more.
|
||||
m_isFirstDigit = false;
|
||||
}
|
||||
|
||||
switch ( m_currentField )
|
||||
{
|
||||
case Field_Hour:
|
||||
m_time.SetHour(n);
|
||||
break;
|
||||
|
||||
case Field_Min:
|
||||
m_time.SetMinute(n);
|
||||
break;
|
||||
|
||||
case Field_Sec:
|
||||
m_time.SetSecond(n);
|
||||
break;
|
||||
|
||||
case Field_AMPM:
|
||||
case Field_Max:
|
||||
wxFAIL_MSG( "Invalid field" );
|
||||
}
|
||||
|
||||
if ( moveToNextField && m_currentField < Field_Sec )
|
||||
CycleCurrentField(Dir_Up);
|
||||
|
||||
UpdateText();
|
||||
}
|
||||
|
||||
// Update the text value to correspond to the current time. By default also
|
||||
// generate an event but this can be avoided by calling the "WithoutEvent"
|
||||
// variant.
|
||||
void UpdateText()
|
||||
{
|
||||
UpdateTextWithoutEvent();
|
||||
|
||||
wxWindow* const ctrl = m_text->GetParent();
|
||||
|
||||
wxDateEvent event(ctrl, m_time, wxEVT_TIME_CHANGED);
|
||||
ctrl->HandleWindowEvent(event);
|
||||
}
|
||||
|
||||
void UpdateTextWithoutEvent()
|
||||
{
|
||||
m_text->SetValue(m_time.Format(m_useAMPM ? "%I:%M:%S %p" : "%H:%M:%S"));
|
||||
|
||||
HighlightCurrentField();
|
||||
}
|
||||
|
||||
|
||||
// The current field of the text control: this is the one affected by
|
||||
// pressing arrow keys or spin button.
|
||||
Field m_currentField;
|
||||
|
||||
// Flag indicating whether we use AM/PM indicator or not.
|
||||
bool m_useAMPM;
|
||||
|
||||
// Flag indicating whether the next digit pressed by user will be the first
|
||||
// digit of the current field or the second one. This is necessary because
|
||||
// the first digit replaces the current field contents while the second one
|
||||
// is appended to it (if possible, e.g. pressing '7' in a field already
|
||||
// containing '8' will still replace it as "78" would be invalid).
|
||||
bool m_isFirstDigit;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS(wxTimePickerGenericImpl);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// wxTimePickerCtrlGeneric implementation
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxTimePickerCtrlGeneric creation
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void wxTimePickerCtrlGeneric::Init()
|
||||
{
|
||||
m_impl = NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
wxTimePickerCtrlGeneric::Create(wxWindow *parent,
|
||||
wxWindowID id,
|
||||
const wxDateTime& date,
|
||||
const wxPoint& pos,
|
||||
const wxSize& size,
|
||||
long style,
|
||||
const wxValidator& validator,
|
||||
const wxString& name)
|
||||
{
|
||||
// The text control we use already has a border, so we don't need one
|
||||
// ourselves.
|
||||
style &= ~wxBORDER_MASK;
|
||||
style |= wxBORDER_NONE;
|
||||
|
||||
if ( !Base::Create(parent, id, pos, size, style, validator, name) )
|
||||
return false;
|
||||
|
||||
m_impl = new wxTimePickerGenericImpl(this);
|
||||
m_impl->SetValue(date);
|
||||
|
||||
InvalidateBestSize();
|
||||
SetInitialSize(size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxTimePickerCtrlGeneric::~wxTimePickerCtrlGeneric()
|
||||
{
|
||||
delete m_impl;
|
||||
}
|
||||
|
||||
wxWindowList wxTimePickerCtrlGeneric::GetCompositeWindowParts() const
|
||||
{
|
||||
wxWindowList parts;
|
||||
if ( m_impl )
|
||||
{
|
||||
parts.push_back(m_impl->m_text);
|
||||
parts.push_back(m_impl->m_btn);
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxTimePickerCtrlGeneric value
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void wxTimePickerCtrlGeneric::SetValue(const wxDateTime& date)
|
||||
{
|
||||
wxCHECK_RET( m_impl, "Must create first" );
|
||||
|
||||
m_impl->SetValue(date);
|
||||
}
|
||||
|
||||
wxDateTime wxTimePickerCtrlGeneric::GetValue() const
|
||||
{
|
||||
wxCHECK_MSG( m_impl, wxDateTime(), "Must create first" );
|
||||
|
||||
return m_impl->m_time;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxTimePickerCtrlGeneric geometry
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
void wxTimePickerCtrlGeneric::DoMoveWindow(int x, int y, int width, int height)
|
||||
{
|
||||
Base::DoMoveWindow(x, y, width, height);
|
||||
|
||||
if ( !m_impl )
|
||||
return;
|
||||
|
||||
const int widthBtn = m_impl->m_btn->GetSize().x;
|
||||
const int widthText = width - widthBtn - HMARGIN_TEXT_SPIN;
|
||||
|
||||
m_impl->m_text->SetSize(0, 0, widthText, height);
|
||||
m_impl->m_btn->SetSize(widthText + HMARGIN_TEXT_SPIN, 0, widthBtn, height);
|
||||
}
|
||||
|
||||
wxSize wxTimePickerCtrlGeneric::DoGetBestSize() const
|
||||
{
|
||||
if ( !m_impl )
|
||||
return Base::DoGetBestSize();
|
||||
|
||||
wxSize size = m_impl->m_text->GetBestSize();
|
||||
size.x += m_impl->m_btn->GetBestSize().x + HMARGIN_TEXT_SPIN;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
#endif // !wxHAS_NATIVE_TIMEPICKERCTRL || wxUSE_TIMEPICKCTRL_GENERIC
|
||||
|
||||
#endif // wxUSE_TIMEPICKCTRL
|
||||
Reference in New Issue
Block a user