X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/d4cf50c8b2574caec6dc13f7dfb0f3ee2b92842e..c28a4e5fa598ac3eabb82ed507a8010137fca170:/src/generic/timectrl.cpp?ds=inline diff --git a/src/generic/timectrl.cpp b/src/generic/timectrl.cpp deleted file mode 100644 index b3d0e5acaf..0000000000 --- a/src/generic/timectrl.cpp +++ /dev/null @@ -1,668 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// 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 -// 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, wxID_ANY, - wxDefaultPosition, wxDefaultSize, - wxSP_VERTICAL | wxSP_WRAP); - - 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 + 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); - } - - void OnArrowUp(wxSpinEvent& WXUNUSED(event)) - { - ChangeCurrentFieldBy1(Dir_Up); - } - - void OnArrowDown(wxSpinEvent& WXUNUSED(event)) - { - ChangeCurrentFieldBy1(Dir_Down); - } - - - // 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); - 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() - { - m_text->SetFocus(); - - 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