]>
git.saurik.com Git - wxWidgets.git/blob - src/generic/timectrlg.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/generic/timectrl.cpp
3 // Purpose: Generic implementation of wxTimePickerCtrl.
4 // Author: Paul Breen, Vadim Zeitlin
6 // RCS-ID: $Id: wxhead.cpp,v 1.11 2010-04-22 12:44:51 zeitlin Exp $
7 // Copyright: (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
11 // ============================================================================
13 // ============================================================================
15 // ----------------------------------------------------------------------------
17 // ----------------------------------------------------------------------------
19 // for compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
26 #if wxUSE_TIMEPICKCTRL
29 #include "wx/textctrl.h"
32 #include "wx/timectrl.h"
34 // This class is only compiled if there is no native version or if we
35 // explicitly want to use both the native and generic one (this is useful for
36 // testing but not much otherwise and so by default we don't use the generic
37 // implementation if a native one is available).
38 #if !defined(wxHAS_NATIVE_TIMEPICKERCTRL) || wxUSE_TIMEPICKCTRL_GENERIC
40 #include "wx/generic/timectrl.h"
42 #include "wx/dateevt.h"
43 #include "wx/spinbutt.h"
45 #ifndef wxHAS_NATIVE_TIMEPICKERCTRL
46 IMPLEMENT_DYNAMIC_CLASS(wxTimePickerCtrl
, wxControl
)
49 // ----------------------------------------------------------------------------
51 // ----------------------------------------------------------------------------
55 // Horizontal margin between the text and spin control.
59 // ----------------------------------------------------------------------------
60 // wxTimePickerGenericImpl: used to implement wxTimePickerCtrlGeneric
61 // ----------------------------------------------------------------------------
63 class wxTimePickerGenericImpl
: public wxEvtHandler
66 wxTimePickerGenericImpl(wxTimePickerCtrlGeneric
* ctrl
)
68 m_text
= new wxTextCtrl(ctrl
, wxID_ANY
, wxString());
70 // As this text can't be edited, don't use the standard cursor for it
71 // to avoid misleading the user. Ideally we'd also hide the caret but
72 // this is not currently supported by wxTextCtrl.
73 m_text
->SetCursor(wxCURSOR_ARROW
);
75 m_btn
= new wxSpinButton(ctrl
, wxID_ANY
,
76 wxDefaultPosition
, wxDefaultSize
,
77 wxSP_VERTICAL
| wxSP_WRAP
);
79 m_currentField
= Field_Hour
;
80 m_isFirstDigit
= true;
82 // We don't support arbitrary formats currently as this requires
83 // significantly more work both here and also in wxLocale::GetInfo().
85 // For now just use either "%H:%M:%S" or "%I:%M:%S %p". It would be
86 // nice to add support to "%k" and "%l" (hours with leading blanks
87 // instead of zeros) too as this is the most common unsupported case in
89 m_useAMPM
= wxLocale::GetInfo(wxLOCALE_TIME_FMT
).Contains("%p");
94 wxFocusEventHandler(wxTimePickerGenericImpl::OnTextSetFocus
),
101 wxKeyEventHandler(wxTimePickerGenericImpl::OnTextKeyDown
),
108 wxMouseEventHandler(wxTimePickerGenericImpl::OnTextClick
),
116 wxSpinEventHandler(wxTimePickerGenericImpl::OnArrowUp
),
123 wxSpinEventHandler(wxTimePickerGenericImpl::OnArrowDown
),
129 // Set the new value.
130 void SetValue(const wxDateTime
& time
)
132 m_time
= time
.IsValid() ? time
: wxDateTime::Now();
134 // Ensure that the date part doesn't correspond to a DST change date as
135 // time is discontinuous then resulting in many problems, e.g. it's
136 // impossible to even enter 2:00:00 at the beginning of summer time
137 // date as this time doesn't exist. By using Jan 1, on which nobody
138 // changes DST, we avoid all such problems.
139 wxDateTime::Tm tm
= m_time
.GetTm();
142 tm
.mon
= wxDateTime::Jan
;
145 UpdateTextWithoutEvent();
149 // The text part of the control.
152 // The spin button used to change the text fields.
155 // The current time (date part is ignored).
159 // The logical fields of the text control (AM/PM one may not be present).
169 // Direction of change of time fields.
172 // Notice that the enum elements values matter.
177 // A range of character positions ("from" is inclusive, "to" -- exclusive).
184 // Event handlers for various events in our controls.
185 void OnTextSetFocus(wxFocusEvent
& event
)
187 HighlightCurrentField();
192 // Keyboard interface here is modelled over MSW native control and may need
193 // adjustments for other platforms.
194 void OnTextKeyDown(wxKeyEvent
& event
)
196 const int key
= event
.GetKeyCode();
201 ChangeCurrentFieldBy1(Dir_Down
);
205 ChangeCurrentFieldBy1(Dir_Up
);
209 CycleCurrentField(Dir_Down
);
213 CycleCurrentField(Dir_Up
);
217 ResetCurrentField(Dir_Down
);
221 ResetCurrentField(Dir_Up
);
234 // The digits work in all keys except AM/PM.
235 if ( m_currentField
!= Field_AMPM
)
237 AppendDigitToCurrentField(key
- '0');
243 // These keys only work to toggle AM/PM field.
244 if ( m_currentField
== Field_AMPM
)
246 unsigned hour
= m_time
.GetHour();
258 if ( hour
!= m_time
.GetHour() )
260 m_time
.SetHour(hour
);
266 // Do not skip the other events, just consume them to prevent the
267 // user from editing the text directly.
271 void OnTextClick(wxMouseEvent
& event
)
273 Field field
= Field_Max
; // Initialize just to suppress warnings.
275 switch ( m_text
->HitTest(event
.GetPosition(), &pos
) )
277 case wxTE_HT_UNKNOWN
:
278 // Don't do anything, it's better than doing something wrong.
282 // Select the first field.
286 case wxTE_HT_ON_TEXT
:
287 // Find the field containing this position.
288 for ( field
= Field_Hour
; field
<= GetLastField(); )
290 const CharRange range
= GetFieldRange(field
);
292 // Normally the "to" end is exclusive but we want to give
293 // focus to some field when the user clicks between them so
294 // count it as part of the preceding field here.
295 if ( range
.from
<= pos
&& pos
<= range
.to
)
298 field
= static_cast<Field
>(field
+ 1);
303 // This shouldn't happen for single line control.
304 wxFAIL_MSG( "Unreachable" );
308 // Select the last field.
309 field
= GetLastField();
313 ChangeCurrentField(field
);
316 void OnArrowUp(wxSpinEvent
& WXUNUSED(event
))
318 ChangeCurrentFieldBy1(Dir_Up
);
321 void OnArrowDown(wxSpinEvent
& WXUNUSED(event
))
323 ChangeCurrentFieldBy1(Dir_Down
);
327 // Get the range of the given field in character positions ("from" is
328 // inclusive, "to" exclusive).
329 static CharRange
GetFieldRange(Field field
)
331 // Currently we can just hard code the ranges as they are the same for
332 // both supported formats, if we want to support arbitrary formats in
333 // the future, we'd need to determine them dynamically by examining the
335 static const CharRange ranges
[] =
343 wxCOMPILE_TIME_ASSERT( WXSIZEOF(ranges
) == Field_Max
,
344 FieldRangesMismatch
);
346 return ranges
[field
];
349 // Get the last field used depending on m_useAMPM.
350 Field
GetLastField() const
352 return m_useAMPM
? Field_AMPM
: Field_Sec
;
355 // Change the current field. For convenience, accept int field here as this
356 // allows us to use arithmetic operations in the caller.
357 void ChangeCurrentField(int field
)
359 if ( field
== m_currentField
)
362 wxCHECK_RET( field
<= GetLastField(), "Invalid field" );
364 m_currentField
= static_cast<Field
>(field
);
365 m_isFirstDigit
= true;
367 HighlightCurrentField();
370 // Go to the next (Dir_Up) or previous (Dir_Down) field, wrapping if
372 void CycleCurrentField(Direction dir
)
374 const unsigned numFields
= GetLastField() + 1;
376 ChangeCurrentField((m_currentField
+ numFields
+ dir
) % numFields
);
379 // Select the currently actively field.
380 void HighlightCurrentField()
384 const CharRange range
= GetFieldRange(m_currentField
);
386 m_text
->SetSelection(range
.from
, range
.to
);
389 // Decrement or increment the value of the current field (wrapping if
391 void ChangeCurrentFieldBy1(Direction dir
)
393 switch ( m_currentField
)
396 m_time
.SetHour((m_time
.GetHour() + 24 + dir
) % 24);
400 m_time
.SetMinute((m_time
.GetMinute() + 60 + dir
) % 60);
404 m_time
.SetSecond((m_time
.GetSecond() + 60 + dir
) % 60);
408 m_time
.SetHour((m_time
.GetHour() + 12) % 24);
412 wxFAIL_MSG( "Invalid field" );
418 // Set the current field to its minimal or maximal value.
419 void ResetCurrentField(Direction dir
)
421 switch ( m_currentField
)
425 // In 12-hour mode setting the hour to the minimal value
426 // also changes the suffix to AM and, correspondingly,
427 // setting it to the maximal one changes the suffix to PM.
428 // And, for consistency with the native MSW behaviour, we
429 // also do the same thing when changing AM/PM field itself,
430 // so change hours in any case.
431 m_time
.SetHour(dir
== Dir_Down
? 0 : 23);
435 m_time
.SetMinute(dir
== Dir_Down
? 0 : 59);
439 m_time
.SetSecond(dir
== Dir_Down
? 0 : 59);
443 wxFAIL_MSG( "Invalid field" );
449 // Append the given digit (from 0 to 9) to the current value of the current
451 void AppendDigitToCurrentField(int n
)
453 bool moveToNextField
= false;
455 if ( !m_isFirstDigit
)
457 // The first digit simply replaces the existing field contents,
458 // but the second one should be combined with the previous one,
459 // otherwise entering 2-digit numbers would be impossible.
460 int currentValue
wxDUMMY_INITIALIZE(0),
461 maxValue
wxDUMMY_INITIALIZE(0);
463 switch ( m_currentField
)
466 currentValue
= m_time
.GetHour();
471 currentValue
= m_time
.GetMinute();
476 currentValue
= m_time
.GetSecond();
482 wxFAIL_MSG( "Invalid field" );
485 // Check if the new value is acceptable. If not, we just handle
486 // this digit as if it were the first one.
487 int newValue
= currentValue
*10 + n
;
488 if ( newValue
< maxValue
)
492 // If we're not on the seconds field, advance to the next one.
493 // This makes it more convenient to enter times as you can just
494 // press all digits one after one without touching the cursor
495 // arrow keys at all.
497 // Notice that MSW native control doesn't do this but it seems
498 // so useful that we intentionally diverge from it here.
499 moveToNextField
= true;
501 // We entered both digits so the next one will be "first" again.
502 m_isFirstDigit
= true;
505 else // First digit entered.
507 // The next one won't be first any more.
508 m_isFirstDigit
= false;
511 switch ( m_currentField
)
527 wxFAIL_MSG( "Invalid field" );
530 if ( moveToNextField
&& m_currentField
< Field_Sec
)
531 CycleCurrentField(Dir_Up
);
536 // Update the text value to correspond to the current time. By default also
537 // generate an event but this can be avoided by calling the "WithoutEvent"
541 UpdateTextWithoutEvent();
543 wxWindow
* const ctrl
= m_text
->GetParent();
545 wxDateEvent
event(ctrl
, m_time
, wxEVT_TIME_CHANGED
);
546 ctrl
->HandleWindowEvent(event
);
549 void UpdateTextWithoutEvent()
551 m_text
->SetValue(m_time
.Format(m_useAMPM
? "%I:%M:%S %p" : "%H:%M:%S"));
553 HighlightCurrentField();
557 // The current field of the text control: this is the one affected by
558 // pressing arrow keys or spin button.
559 Field m_currentField
;
561 // Flag indicating whether we use AM/PM indicator or not.
564 // Flag indicating whether the next digit pressed by user will be the first
565 // digit of the current field or the second one. This is necessary because
566 // the first digit replaces the current field contents while the second one
567 // is appended to it (if possible, e.g. pressing '7' in a field already
568 // containing '8' will still replace it as "78" would be invalid).
571 wxDECLARE_NO_COPY_CLASS(wxTimePickerGenericImpl
);
574 // ============================================================================
575 // wxTimePickerCtrlGeneric implementation
576 // ============================================================================
578 // ----------------------------------------------------------------------------
579 // wxTimePickerCtrlGeneric creation
580 // ----------------------------------------------------------------------------
582 void wxTimePickerCtrlGeneric::Init()
588 wxTimePickerCtrlGeneric::Create(wxWindow
*parent
,
590 const wxDateTime
& date
,
594 const wxValidator
& validator
,
595 const wxString
& name
)
597 // The text control we use already has a border, so we don't need one
599 style
&= ~wxBORDER_MASK
;
600 style
|= wxBORDER_NONE
;
602 if ( !Base::Create(parent
, id
, pos
, size
, style
, validator
, name
) )
605 m_impl
= new wxTimePickerGenericImpl(this);
606 m_impl
->SetValue(date
);
608 InvalidateBestSize();
609 SetInitialSize(size
);
614 wxTimePickerCtrlGeneric::~wxTimePickerCtrlGeneric()
619 wxWindowList
wxTimePickerCtrlGeneric::GetCompositeWindowParts() const
624 parts
.push_back(m_impl
->m_text
);
625 parts
.push_back(m_impl
->m_btn
);
630 // ----------------------------------------------------------------------------
631 // wxTimePickerCtrlGeneric value
632 // ----------------------------------------------------------------------------
634 void wxTimePickerCtrlGeneric::SetValue(const wxDateTime
& date
)
636 wxCHECK_RET( m_impl
, "Must create first" );
638 m_impl
->SetValue(date
);
641 wxDateTime
wxTimePickerCtrlGeneric::GetValue() const
643 wxCHECK_MSG( m_impl
, wxDateTime(), "Must create first" );
645 return m_impl
->m_time
;
648 // ----------------------------------------------------------------------------
649 // wxTimePickerCtrlGeneric geometry
650 // ----------------------------------------------------------------------------
652 void wxTimePickerCtrlGeneric::DoMoveWindow(int x
, int y
, int width
, int height
)
654 Base::DoMoveWindow(x
, y
, width
, height
);
659 const int widthBtn
= m_impl
->m_btn
->GetSize().x
;
660 const int widthText
= width
- widthBtn
- HMARGIN_TEXT_SPIN
;
662 m_impl
->m_text
->SetSize(0, 0, widthText
, height
);
663 m_impl
->m_btn
->SetSize(widthText
+ HMARGIN_TEXT_SPIN
, 0, widthBtn
, height
);
666 wxSize
wxTimePickerCtrlGeneric::DoGetBestSize() const
669 return Base::DoGetBestSize();
671 wxSize size
= m_impl
->m_text
->GetBestSize();
672 size
.x
+= m_impl
->m_btn
->GetBestSize().x
+ HMARGIN_TEXT_SPIN
;
677 #endif // !wxHAS_NATIVE_TIMEPICKERCTRL || wxUSE_TIMEPICKCTRL_GENERIC
679 #endif // wxUSE_TIMEPICKCTRL