1 /////////////////////////////////////////////////////////////////////////////
2 // Name: generic/datectlg.cpp
3 // Purpose: generic wxDatePickerCtrlGeneric implementation
4 // Author: Andreas Pflug
8 // Copyright: (c) 2005 Andreas Pflug <pgadmin@pse-consulting.de>
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 // ============================================================================
14 // ============================================================================
16 // ----------------------------------------------------------------------------
18 // ----------------------------------------------------------------------------
20 #include "wx/wxprec.h"
26 #if wxUSE_DATEPICKCTRL
28 #include "wx/datectrl.h"
30 // use this version if we're explicitly requested to do it or if it's the only
32 #if !defined(wxHAS_NATIVE_DATEPICKCTRL) || \
33 (defined(wxUSE_DATEPICKCTRL_GENERIC) && wxUSE_DATEPICKCTRL_GENERIC)
36 #include "wx/bmpbuttn.h"
37 #include "wx/dialog.h"
38 #include "wx/dcmemory.h"
40 #include "wx/textctrl.h"
41 #include "wx/valtext.h"
44 #ifdef wxHAS_NATIVE_DATEPICKCTRL
45 // this header is not included from wx/datectrl.h if we have a native
46 // version, but we do need it here
47 #include "wx/generic/datectrl.h"
49 // we need to define _WX_DEFINE_DATE_EVENTS_ before including wx/dateevt.h to
50 // define the event types we use if we're the only date picker control version
51 // being compiled -- otherwise it's defined in the native version implementation
52 #define _WX_DEFINE_DATE_EVENTS_
55 #include "wx/dateevt.h"
57 #include "wx/calctrl.h"
58 #include "wx/renderer.h"
60 // ----------------------------------------------------------------------------
62 // ----------------------------------------------------------------------------
72 #ifndef DEFAULT_ITEM_WIDTH
73 #define DEFAULT_ITEM_WIDTH 100
76 #if defined(__WXMSW__)
78 #define wxUSE_POPUPWIN 0 // Popup not working
79 #define TXTCTRL_FLAGS wxNO_BORDER
82 #elif defined(__WXGTK__)
83 #define TXTCTRL_FLAGS 0
87 #define TXTCTRL_FLAGS 0
92 // ----------------------------------------------------------------------------
94 // ----------------------------------------------------------------------------
96 // this should have been a flag in wxDatePickerCtrlGeneric itself but adding it
97 // there now would break backwards compatibility, so put it here as a global:
98 // this shouldn't be a big problem as only one (GUI) thread normally can call
99 // wxDatePickerCtrlGeneric::SetValue() and so it can be only ever used for one
102 // if the value is not NULL, it points to the control which is inside SetValue()
103 static wxDatePickerCtrlGeneric
*gs_inSetValue
= NULL
;
105 // ----------------------------------------------------------------------------
107 // ----------------------------------------------------------------------------
109 // This flag indicates that combo box style drop button is to be created
110 #define wxBU_COMBO 0x0400
113 class wxDropdownButton
: public wxBitmapButton
116 wxDropdownButton() { Init(); }
117 wxDropdownButton(wxWindow
*parent
,
119 const wxPoint
& pos
= wxDefaultPosition
,
120 const wxSize
& size
= wxDefaultSize
,
122 const wxValidator
& validator
= wxDefaultValidator
);
124 bool Create(wxWindow
*parent
,
126 const wxPoint
& pos
= wxDefaultPosition
,
127 const wxSize
& size
= wxDefaultSize
,
129 const wxValidator
& validator
= wxDefaultValidator
);
131 virtual void DoMoveWindow(int x
, int y
, int w
, int h
);
135 void OnSize(wxSizeEvent
& event
);
136 void OnMouseEnter(wxMouseEvent
& event
);
137 void OnMouseLeave(wxMouseEvent
& event
);
139 void RecreateBitmaps(int w
, int h
);
141 wxBitmap m_bmpNormal
;
144 int m_borderX
, m_borderY
;
146 // True if DrawDropArrow should be used instead of DrawComboBoxDropButton
157 DECLARE_EVENT_TABLE()
158 DECLARE_DYNAMIC_CLASS_NO_COPY(wxDropdownButton
)
162 // Below, macro DROPBUT_USEDROPARROW should return false when
163 // DrawComboBoxDropButton is to be used to render the entire button.
164 // COMBOST is non-zero if wxBU_COMBO was set.
166 #if defined(__WXMSW__)
168 #define DROPBUT_USEDROPARROW(COMBOST) (COMBOST?false:true)
169 #define DROPBUT_DEFAULT_WIDTH 17
171 #elif defined(__WXGTK__)
173 #define DROPBUT_USEDROPARROW(COMBOST) true
174 #define DROPBUT_DEFAULT_WIDTH 19
178 #define DROPBUT_USEDROPARROW(COMBOST) true
179 #define DROPBUT_DEFAULT_WIDTH 17
184 IMPLEMENT_DYNAMIC_CLASS(wxDropdownButton
, wxBitmapButton
)
187 BEGIN_EVENT_TABLE(wxDropdownButton
,wxBitmapButton
)
188 EVT_ENTER_WINDOW(wxDropdownButton::OnMouseEnter
)
189 EVT_LEAVE_WINDOW(wxDropdownButton::OnMouseLeave
)
190 EVT_SIZE(wxDropdownButton::OnSize
)
194 wxDropdownButton::wxDropdownButton(wxWindow
*parent
,
199 const wxValidator
& validator
)
202 Create(parent
, id
, pos
, size
, style
, validator
);
206 bool wxDropdownButton::Create(wxWindow
*parent
,
211 const wxValidator
& validator
)
216 m_useDropArrow
= DROPBUT_USEDROPARROW(style
& wxBU_COMBO
);
218 wxBitmap
chkBmp(15,15); // arbitrary
219 if ( !wxBitmapButton::Create(parent
, id
, chkBmp
,
221 style
| (m_useDropArrow
? wxBU_AUTODRAW
: wxNO_BORDER
),
225 const wxSize sz
= GetSize();
226 int w
= chkBmp
.GetWidth(),
227 h
= chkBmp
.GetHeight();
228 m_borderX
= sz
.x
- m_marginX
- w
;
229 m_borderY
= sz
.y
- m_marginY
- h
;
231 DoMoveWindow(pos
.x
, pos
.y
, size
.x
, size
.y
);
237 void wxDropdownButton::RecreateBitmaps(int w
, int h
)
241 int borderX
= m_marginX
+ m_borderX
;
242 int borderY
= m_marginY
+ m_borderY
;
243 int bw
= w
- borderX
;
244 int bh
= h
- borderY
;
246 wxBitmap
bmp(bw
, bh
);
247 wxBitmap
bmpSel(bw
, bh
);
250 wxRendererNative
& renderer
= wxRendererNative::Get();
252 dc
.SelectObject(bmp
);
254 if ( m_useDropArrow
)
256 // Use DrawDropArrow on transparent background.
258 wxColour
magic(255,0,255);
259 wxBrush
magicBrush(magic
);
263 dc
.SetBrush( magicBrush
);
264 dc
.SetPen( *wxTRANSPARENT_PEN
);
265 dc
.DrawRectangle(0,0,bw
,bh
);
266 renderer
.DrawDropArrow(this, dc
, r
);
267 dc
.SelectObject( wxNullBitmap
);
268 wxMask
*mask
= new wxMask( bmp
, magic
);
271 dc
.SelectObject(bmpSel
);
273 dc
.SetBrush( magicBrush
);
274 dc
.SetPen( *wxTRANSPARENT_PEN
);
275 dc
.DrawRectangle(0,0,bw
,bh
);
276 renderer
.DrawDropArrow(this, dc
, r
, wxCONTROL_PRESSED
);
277 dc
.SelectObject( wxNullBitmap
);
278 mask
= new wxMask( bmpSel
, magic
);
279 bmpSel
.SetMask( mask
);
283 // Use DrawComboBoxDropButton for the entire button
284 // (also render extra "hot" button state).
286 renderer
.DrawComboBoxDropButton(this, dc
, r
);
288 dc
.SelectObject(bmpSel
);
290 renderer
.DrawComboBoxDropButton(this, dc
, r
, wxCONTROL_PRESSED
);
292 wxBitmap
bmpHot(bw
,bh
);
293 dc
.SelectObject(bmpHot
);
294 renderer
.DrawComboBoxDropButton(this, dc
, r
, wxCONTROL_CURRENT
);
301 SetBitmapSelected(bmpSel
);
305 void wxDropdownButton::DoMoveWindow(int x
, int y
, int w
, int h
)
308 w
= DROPBUT_DEFAULT_WIDTH
;
310 wxBitmapButton::DoMoveWindow(x
, y
, w
, h
);
314 void wxDropdownButton::OnSize(wxSizeEvent
& event
)
316 if ( m_borderX
>= 0 && m_borderY
>= 0 )
319 GetClientSize(&w
,&h
);
321 if ( w
> 1 && h
> 1 )
322 RecreateBitmaps(w
,h
);
328 void wxDropdownButton::OnMouseEnter(wxMouseEvent
& event
)
330 if ( !m_useDropArrow
)
331 SetBitmapLabel(m_bmpHot
);
337 void wxDropdownButton::OnMouseLeave(wxMouseEvent
& event
)
339 if ( !m_useDropArrow
)
340 SetBitmapLabel(m_bmpNormal
);
348 #include "wx/popupwin.h"
350 class wxDatePopupInternal
: public wxPopupTransientWindow
353 wxDatePopupInternal(wxWindow
*parent
) : wxPopupTransientWindow(parent
) { }
355 void ShowAt(int x
, int y
)
357 Position(wxPoint(x
, y
), wxSize(0, 0));
367 #else // !wxUSE_POPUPWIN
369 class wxDatePopupInternal
: public wxDialog
372 wxDatePopupInternal(wxWindow
*parent
)
382 void ShowAt(int x
, int y
)
394 #endif // wxUSE_POPUPWIN/!wxUSE_POPUPWIN
396 // ============================================================================
397 // wxDatePickerCtrlGeneric implementation
398 // ============================================================================
400 BEGIN_EVENT_TABLE(wxDatePickerCtrlGeneric
, wxDatePickerCtrlBase
)
401 EVT_BUTTON(CTRLID_BTN
, wxDatePickerCtrlGeneric::OnClick
)
402 EVT_TEXT(CTRLID_TXT
, wxDatePickerCtrlGeneric::OnText
)
403 EVT_CHILD_FOCUS(wxDatePickerCtrlGeneric::OnChildSetFocus
)
404 EVT_SIZE(wxDatePickerCtrlGeneric::OnSize
)
407 #ifndef wxHAS_NATIVE_DATEPICKCTRL
408 IMPLEMENT_DYNAMIC_CLASS(wxDatePickerCtrl
, wxControl
)
411 // ----------------------------------------------------------------------------
413 // ----------------------------------------------------------------------------
415 bool wxDatePickerCtrlGeneric::Create(wxWindow
*parent
,
417 const wxDateTime
& date
,
421 const wxValidator
& validator
,
422 const wxString
& name
)
424 wxASSERT_MSG( !(style
& wxDP_SPIN
),
425 _T("wxDP_SPIN style not supported, use wxDP_DEFAULT") );
427 if ( !wxControl::Create(parent
, id
, pos
, size
,
428 style
| wxCLIP_CHILDREN
| wxWANTS_CHARS
,
437 m_txt
= new wxTextCtrl(this, CTRLID_TXT
, wxEmptyString
, wxDefaultPosition
, wxDefaultSize
, TXTCTRL_FLAGS
);
439 m_txt
->Connect(wxEVT_KEY_DOWN
,
440 wxKeyEventHandler(wxDatePickerCtrlGeneric::OnEditKey
),
442 m_txt
->Connect(wxEVT_KILL_FOCUS
,
443 wxFocusEventHandler(wxDatePickerCtrlGeneric::OnKillFocus
),
446 m_btn
= new wxDropdownButton(this, CTRLID_BTN
, wxDefaultPosition
, wxDefaultSize
, wxBU_COMBO
);
448 m_popup
= new wxDatePopupInternal(this);
449 m_popup
->SetFont(GetFont());
451 wxPanel
*panel
=new wxPanel(m_popup
, CTRLID_PAN
,
452 wxPoint(0, 0), wxDefaultSize
,
454 m_cal
= new wxCalendarCtrl(panel
, CTRLID_CAL
, wxDefaultDateTime
,
455 wxPoint(0, 0), wxDefaultSize
,
456 wxCAL_SHOW_HOLIDAYS
| wxSUNKEN_BORDER
);
457 m_cal
->Connect(wxEVT_CALENDAR_SEL_CHANGED
,
458 wxCalendarEventHandler(wxDatePickerCtrlGeneric::OnSelChange
),
460 m_cal
->Connect(wxEVT_KEY_DOWN
,
461 wxKeyEventHandler(wxDatePickerCtrlGeneric::OnCalKey
),
463 m_cal
->Connect(wxEVT_CALENDAR_DOUBLECLICKED
,
464 wxCalendarEventHandler(wxDatePickerCtrlGeneric::OnSelChange
),
466 m_cal
->Connect(wxEVT_CALENDAR_DAY_CHANGED
,
467 wxCalendarEventHandler(wxDatePickerCtrlGeneric::OnSelChange
),
469 m_cal
->Connect(wxEVT_CALENDAR_MONTH_CHANGED
,
470 wxCalendarEventHandler(wxDatePickerCtrlGeneric::OnSelChange
),
472 m_cal
->Connect(wxEVT_CALENDAR_YEAR_CHANGED
,
473 wxCalendarEventHandler(wxDatePickerCtrlGeneric::OnSelChange
),
476 wxWindow
*yearControl
= m_cal
->GetYearControl();
478 Connect(wxEVT_SET_FOCUS
,
479 wxFocusEventHandler(wxDatePickerCtrlGeneric::OnSetFocus
));
481 wxClientDC
dc(yearControl
);
482 dc
.SetFont(yearControl
->GetFont());
483 wxCoord width
, dummy
;
484 dc
.GetTextExtent(wxT("2000"), &width
, &dummy
);
485 width
+= ConvertDialogToPixels(wxSize(20, 0)).x
;
487 wxSize calSize
= m_cal
->GetBestSize();
488 wxSize yearSize
= yearControl
->GetSize();
491 wxPoint yearPosition
= yearControl
->GetPosition();
493 SetFormat(wxT("%x"));
495 width
= yearPosition
.x
+ yearSize
.x
+2+CALBORDER
/2;
496 if (width
< calSize
.x
-4)
499 int calPos
= (width
-calSize
.x
)/2;
505 m_cal
->SetSize(calPos
, 0, calSize
.x
, calSize
.y
);
506 yearControl
->SetSize(width
-yearSize
.x
-CALBORDER
/2, yearPosition
.y
,
507 yearSize
.x
, yearSize
.y
);
508 m_cal
->GetMonthControl()->Move(0, 0);
512 panel
->SetClientSize(width
+CALBORDER
/2, calSize
.y
-2+CALBORDER
);
513 m_popup
->SetClientSize(panel
->GetSize());
516 SetValue(date
.IsValid() ? date
: wxDateTime::Today());
518 SetBestFittingSize(size
);
520 SetBackgroundColour(m_txt
->GetBackgroundColour());
526 void wxDatePickerCtrlGeneric::Init()
534 m_ignoreDrop
= false;
537 wxDatePickerCtrlGeneric::~wxDatePickerCtrlGeneric()
545 bool wxDatePickerCtrlGeneric::Destroy()
561 return wxControl::Destroy();
564 // ----------------------------------------------------------------------------
565 // overridden base class methods
566 // ----------------------------------------------------------------------------
568 void wxDatePickerCtrlGeneric::DoMoveWindow(int x
, int y
, int w
, int h
)
570 wxControl::DoMoveWindow(x
, y
, w
, h
);
576 wxSize
wxDatePickerCtrlGeneric::DoGetBestSize() const
578 int bh
=m_btn
->GetBestSize().y
;
579 int eh
=m_txt
->GetBestSize().y
;
580 return wxSize(DEFAULT_ITEM_WIDTH
, bh
> eh
? bh
: eh
);
584 bool wxDatePickerCtrlGeneric::Show(bool show
)
586 if ( !wxControl::Show(show
) )
604 bool wxDatePickerCtrlGeneric::Enable(bool enable
)
606 if ( !wxControl::Enable(enable
) )
618 m_btn
->Enable(enable
);
623 // ----------------------------------------------------------------------------
624 // wxDatePickerCtrlGeneric API
625 // ----------------------------------------------------------------------------
628 wxDatePickerCtrlGeneric::SetDateRange(const wxDateTime
& lowerdate
,
629 const wxDateTime
& upperdate
)
631 return m_cal
->SetDateRange(lowerdate
, upperdate
);
634 bool wxDatePickerCtrlGeneric::SetFormat(const wxChar
*fmt
)
637 dt
.ParseFormat(wxT("2003-10-13"), wxT("%Y-%m-%d"));
638 wxString str
=dt
.Format(fmt
);
639 wxChar
*p
=(wxChar
*)str
.c_str();
641 m_format
=wxEmptyString
;
646 if (n
== dt
.GetDay())
648 m_format
.Append(wxT("%d"));
651 else if (n
== (int)dt
.GetMonth()+1)
653 m_format
.Append(wxT("%m"));
656 else if (n
== dt
.GetYear())
658 m_format
.Append(wxT("%Y"));
661 else if (n
== (dt
.GetYear() % 100))
663 if (GetWindowStyle() & wxDP_SHOWCENTURY
)
664 m_format
.Append(wxT("%Y"));
666 m_format
.Append(wxT("%y"));
670 m_format
.Append(*p
++);
675 wxArrayString allowedChars
;
676 for ( wxChar c
= _T('0'); c
<= _T('9'); c
++ )
677 allowedChars
.Add(wxString(c
, 1));
679 const wxChar
*p
= m_format
.c_str();
685 allowedChars
.Add(wxString(*p
++, 1));
689 wxTextValidator
tv(wxFILTER_INCLUDE_CHAR_LIST
);
690 tv
.SetIncludes(allowedChars
);
691 m_txt
->SetValidator(tv
);
694 if (m_currentDate
.IsValid())
695 m_txt
->SetValue(m_currentDate
.Format(m_format
));
702 wxDateTime
wxDatePickerCtrlGeneric::GetValue() const
704 return m_currentDate
;
708 void wxDatePickerCtrlGeneric::SetValue(const wxDateTime
& date
)
713 // we need to suppress the event sent from wxTextCtrl as calling our
714 // SetValue() should not result in an event being sent (wxTextCtrl is
715 // an exception to this rule)
716 gs_inSetValue
= this;
718 if ( date
.IsValid() )
720 m_txt
->SetValue(date
.Format(m_format
));
724 wxASSERT_MSG( HasFlag(wxDP_ALLOWNONE
),
725 _T("this control must have a valid date") );
727 m_txt
->SetValue(wxEmptyString
);
730 gs_inSetValue
= NULL
;
732 m_currentDate
= date
;
736 bool wxDatePickerCtrlGeneric::GetRange(wxDateTime
*dt1
, wxDateTime
*dt2
) const
739 *dt1
= m_cal
->GetLowerDateLimit();
741 *dt2
= m_cal
->GetUpperDateLimit();
747 wxDatePickerCtrlGeneric::SetRange(const wxDateTime
&dt1
, const wxDateTime
&dt2
)
749 m_cal
->SetDateRange(dt1
, dt2
);
752 // ----------------------------------------------------------------------------
754 // ----------------------------------------------------------------------------
756 void wxDatePickerCtrlGeneric::DropDown(bool down
)
763 if (!m_txt
->GetValue().empty())
764 dt
.ParseFormat(m_txt
->GetValue(), m_format
);
769 m_cal
->SetDate(wxDateTime::Today());
771 wxPoint pos
=GetParent()->ClientToScreen(GetPosition());
772 m_popup
->ShowAt(pos
.x
, pos
.y
+ GetSize().y
);
786 void wxDatePickerCtrlGeneric::OnSize(wxSizeEvent
& event
)
790 wxSize sz
= GetClientSize();
792 wxSize bs
=m_btn
->GetSize();
793 int eh
=m_txt
->GetBestSize().y
;
795 m_txt
->SetSize(0, TXTPOSY
, sz
.x
-bs
.x
, sz
.y
> eh
? eh
-TXTPOSY
: sz
.y
-TXTPOSY
);
796 m_btn
->SetSize(sz
.x
- bs
.x
, 0, bs
.x
, sz
.y
);
803 void wxDatePickerCtrlGeneric::OnChildSetFocus(wxChildFocusEvent
&ev
)
806 m_ignoreDrop
= false;
808 wxWindow
*w
=(wxWindow
*)ev
.GetEventObject();
819 if (::wxFindWindowAtPoint(::wxGetMousePosition()) == m_btn
)
825 void wxDatePickerCtrlGeneric::OnClick(wxCommandEvent
& WXUNUSED(event
))
829 m_ignoreDrop
= false;
840 void wxDatePickerCtrlGeneric::OnSetFocus(wxFocusEvent
& WXUNUSED(ev
))
845 m_txt
->SetSelection(-1, -1); // select everything
850 void wxDatePickerCtrlGeneric::OnKillFocus(wxFocusEvent
&ev
)
858 dt
.ParseFormat(m_txt
->GetValue(), m_format
);
861 if ( !HasFlag(wxDP_ALLOWNONE
) )
866 m_txt
->SetValue(dt
.Format(m_format
));
868 m_txt
->SetValue(wxEmptyString
);
870 // notify that we had to change the date after validation
871 if ( (dt
.IsValid() && (!m_currentDate
.IsValid() || m_currentDate
!= dt
)) ||
872 (!dt
.IsValid() && m_currentDate
.IsValid()) )
875 wxDateEvent
event(this, dt
, wxEVT_DATE_CHANGED
);
876 GetEventHandler()->ProcessEvent(event
);
881 void wxDatePickerCtrlGeneric::OnSelChange(wxCalendarEvent
&ev
)
885 m_currentDate
= m_cal
->GetDate();
886 m_txt
->SetValue(m_currentDate
.Format(m_format
));
887 if (ev
.GetEventType() == wxEVT_CALENDAR_DOUBLECLICKED
)
893 ev
.SetEventObject(this);
895 GetParent()->ProcessEvent(ev
);
897 wxDateEvent
dev(this, ev
.GetDate(), wxEVT_DATE_CHANGED
);
898 GetParent()->ProcessEvent(dev
);
902 void wxDatePickerCtrlGeneric::OnText(wxCommandEvent
&ev
)
906 // artificial event resulting from our own SetValue() call, ignore it
910 ev
.SetEventObject(this);
912 GetParent()->ProcessEvent(ev
);
914 // We'll create an additional event if the date is valid.
915 // If the date isn't valid, the user's probably in the middle of typing
916 wxString txt
= m_txt
->GetValue();
920 dt
.ParseFormat(txt
, m_format
);
925 wxCalendarEvent
cev(m_cal
, wxEVT_CALENDAR_SEL_CHANGED
);
926 cev
.SetEventObject(this);
930 GetParent()->ProcessEvent(cev
);
932 wxDateEvent
dev(this, dt
, wxEVT_DATE_CHANGED
);
933 GetParent()->ProcessEvent(dev
);
937 void wxDatePickerCtrlGeneric::OnEditKey(wxKeyEvent
& ev
)
939 if (ev
.GetKeyCode() == WXK_DOWN
&& !ev
.HasModifiers())
946 void wxDatePickerCtrlGeneric::OnCalKey(wxKeyEvent
& ev
)
948 if (ev
.GetKeyCode() == WXK_ESCAPE
&& !ev
.HasModifiers())
954 #endif // wxUSE_DATEPICKCTRL_GENERIC
956 #endif // wxUSE_DATEPICKCTRL