diff --git a/include/wx/datetimectrl.h b/include/wx/datetimectrl.h index 30f23dfffe..29cff3c7c9 100644 --- a/include/wx/datetimectrl.h +++ b/include/wx/datetimectrl.h @@ -32,6 +32,13 @@ public: // Set/get the date or time (in the latter case, time part is ignored). virtual void SetValue(const wxDateTime& dt) = 0; virtual wxDateTime GetValue() const = 0; + + // For the controls with wxDP_ALLOWNONE style, set the string displayed + // when the control doesn't have any valid value. Currently this is only + // actually used under MSW, where it can be used to override the previous + // value which is still displayed by the control in this case, and ignored + // elsewhere. + virtual void SetNullText(const wxString& WXUNUSED(text)) { } }; #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) diff --git a/include/wx/generic/timectrl.h b/include/wx/generic/timectrl.h index eea7daa2df..84f7a4f261 100644 --- a/include/wx/generic/timectrl.h +++ b/include/wx/generic/timectrl.h @@ -13,11 +13,13 @@ #include "wx/containr.h" #include "wx/compositewin.h" +typedef wxTimePickerCtrlCommonBase wxTimePickerCtrlGenericBase; + class WXDLLIMPEXP_ADV wxTimePickerCtrlGeneric - : public wxCompositeWindow< wxNavigationEnabled > + : public wxCompositeWindow< wxNavigationEnabled > { public: - typedef wxCompositeWindow< wxNavigationEnabled > Base; + typedef wxCompositeWindow< wxNavigationEnabled > Base; // Creating the control. wxTimePickerCtrlGeneric() { Init(); } diff --git a/include/wx/msw/datetimectrl.h b/include/wx/msw/datetimectrl.h index 97afc1f40c..a56c87d580 100644 --- a/include/wx/msw/datetimectrl.h +++ b/include/wx/msw/datetimectrl.h @@ -26,6 +26,8 @@ public: virtual void SetValue(const wxDateTime& dt) wxOVERRIDE; virtual wxDateTime GetValue() const wxOVERRIDE; + virtual void SetNullText(const wxString& text) wxOVERRIDE; + // returns true if the platform should explicitly apply a theme border virtual bool CanApplyThemeBorder() const wxOVERRIDE { return false; } @@ -46,43 +48,34 @@ protected: const wxValidator& validator, const wxString& name); - // Notice that the methods below must be overridden in all native MSW - // classes inheriting from this one but they can't be pure virtual because - // the generic implementations, not needing nor able to implement them, is - // also derived from this class currently. The real problem is, of course, - // this wrong class structure because the generic classes also inherit the - // wrong implementations of Set/GetValue() and DoGetBestSize() but as they - // override these methods anyhow, it does work -- but is definitely ugly - // and need to be changed (but how?) in the future. - #if wxUSE_INTL // Override to return the date/time format used by this control. - virtual wxLocaleInfo MSWGetFormat() const /* = 0 */ - { - wxFAIL_MSG( "Unreachable" ); - return wxLOCALE_TIME_FMT; - } + virtual wxLocaleInfo MSWGetFormat() const = 0; #endif // wxUSE_INTL // Override to indicate whether we can have no date at all. - virtual bool MSWAllowsNone() const /* = 0 */ - { - wxFAIL_MSG( "Unreachable" ); - return false; - } + virtual bool MSWAllowsNone() const = 0; // Override to update m_date and send the event when the control contents // changes, return true if the event was handled. - virtual bool MSWOnDateTimeChange(const tagNMDATETIMECHANGE& dtch) /* = 0 */ - { - wxUnusedVar(dtch); - wxFAIL_MSG( "Unreachable" ); - return false; - } + virtual bool MSWOnDateTimeChange(const tagNMDATETIMECHANGE& dtch) = 0; // the date currently shown by the control, may be invalid wxDateTime m_date; + +private: + // Helper setting the appropriate format depending on the passed in state. + void MSWUpdateFormat(bool valid); + + // Same thing, but only doing if the validity differs from the date + // validity, i.e. avoiding useless work if nothing needs to be done. + void MSWUpdateFormatIfNeeded(bool valid); + + + // shown when there is no valid value (so only used with wxDP_ALLOWNONE), + // always non-empty if SetNullText() was called, see the comments there + wxString m_nullText; }; #endif // _WX_MSW_DATETIMECTRL_H_ diff --git a/include/wx/timectrl.h b/include/wx/timectrl.h index 928d9aedf3..4e4a7a3758 100644 --- a/include/wx/timectrl.h +++ b/include/wx/timectrl.h @@ -29,7 +29,10 @@ enum // wxTimePickerCtrl: Allow the user to enter the time. // ---------------------------------------------------------------------------- -class WXDLLIMPEXP_ADV wxTimePickerCtrlBase : public wxDateTimePickerCtrl +// The template argument must be a class deriving from wxDateTimePickerCtrlBase +// (i.e. in practice either this class itself or wxDateTimePickerCtrl). +template +class wxTimePickerCtrlCommonBase : public Base { public: /* @@ -67,7 +70,7 @@ public: return false; } - SetValue(dt); + this->SetValue(dt); return true; } @@ -78,7 +81,7 @@ public: wxCHECK_MSG( hour && min && sec, false, wxS("Time component pointers must be non-NULL") ); - const wxDateTime::Tm tm = GetValue().GetTm(); + const wxDateTime::Tm tm = this->GetValue().GetTm(); *hour = tm.hour; *min = tm.min; *sec = tm.sec; @@ -87,6 +90,10 @@ public: } }; +// This class is defined mostly for compatibility and is used as the base class +// by native wxTimePickerCtrl implementations. +typedef wxTimePickerCtrlCommonBase wxTimePickerCtrlBase; + #if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) #include "wx/msw/timectrl.h" diff --git a/interface/wx/datectrl.h b/interface/wx/datectrl.h index 72199583e7..59d1eda289 100644 --- a/interface/wx/datectrl.h +++ b/interface/wx/datectrl.h @@ -168,6 +168,22 @@ public: */ virtual wxDateTime GetValue() const; + /** + Set the text to show when there is no valid value. + + For the controls with @c wxDP_ALLOWNONE style, set the string displayed + when the control doesn't have any valid value. Currently this is only + actually used under MSW, where it can be used to override the previous + value which is still displayed by the control in this case, and ignored + elsewhere. + + Notably, @a text can be empty to completely hide the date if no valid + date is specified. + + @since 3.1.5 + */ + void SetNullText(const wxString& text); + /** Sets the valid range for the date selection. If @a dt1 is valid, it becomes the earliest date (inclusive) accepted by the control. If diff --git a/misc/schema/xrc_schema.rnc b/misc/schema/xrc_schema.rnc index 8b63cbe642..23a132f74b 100644 --- a/misc/schema/xrc_schema.rnc +++ b/misc/schema/xrc_schema.rnc @@ -894,7 +894,8 @@ wxDatePickerCtrl = element object { attribute class { "wxDatePickerCtrl" } & stdObjectNodeAttributes & - stdWindowProperties + stdWindowProperties & + [xrc:p="o"] element null-text {_, t_text }* } diff --git a/samples/calendar/calendar.cpp b/samples/calendar/calendar.cpp index df95946a7d..28964de1f0 100644 --- a/samples/calendar/calendar.cpp +++ b/samples/calendar/calendar.cpp @@ -218,12 +218,24 @@ class MyTimeDialog : public wxDialog public: MyTimeDialog(wxWindow* parent); - wxDateTime GetTime() const { return m_timePicker->GetValue(); } + wxDateTime GetTime() const + { +#if wxUSE_TIMEPICKCTRL_GENERIC + if ( m_timePickerGeneric ) + return m_timePickerGeneric->GetValue(); +#endif // wxUSE_TIMEPICKCTRL_GENERIC + + return m_timePicker->GetValue(); + } private: void OnTimeChange(wxDateEvent& event); - wxTimePickerCtrlBase* m_timePicker; + wxTimePickerCtrl* m_timePicker; +#if wxUSE_TIMEPICKCTRL_GENERIC + wxTimePickerCtrlGeneric* m_timePickerGeneric; +#endif // wxUSE_TIMEPICKCTRL_GENERIC + wxStaticText* m_timeText; wxDECLARE_EVENT_TABLE(); @@ -987,20 +999,31 @@ wxEND_EVENT_TABLE() MyTimeDialog::MyTimeDialog(wxWindow *parent) : wxDialog(parent, wxID_ANY, wxString("Calendar: Choose time")) { + wxWindow* timePickerWindow = NULL; + #if wxUSE_TIMEPICKCTRL_GENERIC + m_timePickerGeneric = NULL; + m_timePicker = NULL; + wxFrame *frame = (wxFrame *)wxGetTopLevelParent(parent); if ( frame && frame->GetMenuBar()->IsChecked(Calendar_TimePicker_Generic) ) - m_timePicker = new wxTimePickerCtrlGeneric(this, wxID_ANY); + { + m_timePickerGeneric = new wxTimePickerCtrlGeneric(this, wxID_ANY); + timePickerWindow = m_timePickerGeneric; + } else #endif // wxUSE_TIMEPICKCTRL_GENERIC m_timePicker = new wxTimePickerCtrl(this, wxID_ANY); - m_timeText = new wxStaticText(this, wxID_ANY, - m_timePicker->GetValue().FormatISOTime()); + + if ( !timePickerWindow ) + timePickerWindow = m_timePicker; + + m_timeText = new wxStaticText(this, wxID_ANY, GetTime().FormatISOTime()); const wxSizerFlags flags = wxSizerFlags().Centre().Border(); wxFlexGridSizer* const sizerMain = new wxFlexGridSizer(2); sizerMain->Add(new wxStaticText(this, wxID_ANY, "Enter &time:"), flags); - sizerMain->Add(m_timePicker, flags); + sizerMain->Add(timePickerWindow, flags); sizerMain->Add(new wxStaticText(this, wxID_ANY, "Time in ISO format:"), flags); diff --git a/samples/widgets/datepick.cpp b/samples/widgets/datepick.cpp index 91b8392cdb..f0374900da 100644 --- a/samples/widgets/datepick.cpp +++ b/samples/widgets/datepick.cpp @@ -54,6 +54,7 @@ enum DatePickerPage_Reset = wxID_HIGHEST, DatePickerPage_Set, DatePickerPage_SetRange, + DatePickerPage_SetNullText, DatePickerPage_Picker }; @@ -78,6 +79,7 @@ protected: void OnButtonSet(wxCommandEvent& event); void OnButtonSetRange(wxCommandEvent& event); + void OnButtonSetNullText(wxCommandEvent& event); void OnButtonReset(wxCommandEvent& event); // reset the date picker parameters @@ -96,6 +98,7 @@ protected: wxTextCtrl *m_textCur; wxTextCtrl *m_textMin; wxTextCtrl *m_textMax; + wxTextCtrl *m_textNull; wxRadioBox* m_radioKind; wxCheckBox* m_chkStyleCentury; @@ -117,6 +120,7 @@ wxBEGIN_EVENT_TABLE(DatePickerWidgetsPage, WidgetsPage) EVT_BUTTON(DatePickerPage_Reset, DatePickerWidgetsPage::OnButtonReset) EVT_BUTTON(DatePickerPage_Set, DatePickerWidgetsPage::OnButtonSet) EVT_BUTTON(DatePickerPage_SetRange, DatePickerWidgetsPage::OnButtonSetRange) + EVT_BUTTON(DatePickerPage_SetNullText, DatePickerWidgetsPage::OnButtonSetNullText) EVT_DATE_CHANGED(wxID_ANY, DatePickerWidgetsPage::OnDateChanged) wxEND_EVENT_TABLE() @@ -197,6 +201,20 @@ void DatePickerWidgetsPage::CreateContent() sizerMiddle->Add(new wxButton(this, DatePickerPage_SetRange, "Set &range"), wxSizerFlags().Centre().Border()); + sizerMiddle->AddSpacer(10); + + sizerMiddle->Add(CreateSizerWithTextAndLabel + ( + "&Null text", + wxID_ANY, + &m_textNull + ), + wxSizerFlags().Expand().Border()); + + sizerMiddle->Add(new wxButton(this, DatePickerPage_SetNullText, + "Set &null text"), + wxSizerFlags().Centre().Border()); + // right pane: control itself wxSizer *sizerRight = new wxBoxSizer(wxHORIZONTAL); @@ -327,11 +345,24 @@ void DatePickerWidgetsPage::OnButtonSetRange(wxCommandEvent& WXUNUSED(event)) } } +void DatePickerWidgetsPage::OnButtonSetNullText(wxCommandEvent& WXUNUSED(event)) +{ + m_datePicker->SetNullText(m_textNull->GetValue()); +} + +// Helper function which has to be used here because the controls with +// wxDP_ALLOWNONE style can have invalid date, both in the control itself and +// in the event it generates. +static wxString FormatPossiblyInvalidDate(const wxDateTime& dt) +{ + return dt.IsValid() ? dt.FormatISOCombined() : wxString("[none]"); +} + void DatePickerWidgetsPage::OnDateChanged(wxDateEvent& event) { wxLogMessage("Date changed, now is %s (control value is %s).", - event.GetDate().FormatISOCombined(), - m_datePicker->GetValue().FormatISOCombined()); + FormatPossiblyInvalidDate(event.GetDate()), + FormatPossiblyInvalidDate(m_datePicker->GetValue())); } #endif // wxUSE_DATEPICKCTRL diff --git a/src/msw/datetimectrl.cpp b/src/msw/datetimectrl.cpp index df023a5231..41059fa360 100644 --- a/src/msw/datetimectrl.cpp +++ b/src/msw/datetimectrl.cpp @@ -99,6 +99,8 @@ void wxDateTimePickerCtrl::SetValue(const wxDateTime& dt) return; } + MSWUpdateFormatIfNeeded(dt.IsValid()); + m_date = dt; } @@ -107,6 +109,57 @@ wxDateTime wxDateTimePickerCtrl::GetValue() const return m_date; } +void wxDateTimePickerCtrl::MSWUpdateFormatIfNeeded(bool valid) +{ + if ( MSWAllowsNone() && !m_nullText.empty() && valid != m_date.IsValid() ) + MSWUpdateFormat(valid); +} + +void wxDateTimePickerCtrl::MSWUpdateFormat(bool valid) +{ + // We just use NULL to reset to the default format when the date is valid, + // as the control seems to remember whichever format was used when it was + // created, i.e. this works both with and without wxDP_SHOWCENTURY. + + // Note: due to a bug in MinGW headers, with missing parentheses around the + // macro argument (corrected in or before 8.2, but still existing in 5.3), + // we have to use a temporary variable here. + const TCHAR* const format = valid ? NULL : m_nullText.t_str(); + DateTime_SetFormat(GetHwnd(), format); +} + +void wxDateTimePickerCtrl::SetNullText(const wxString& text) +{ + m_nullText = text; + if ( m_nullText.empty() ) + { + // Using empty format doesn't work with the native control, it just + // uses the default short date format in this case, so set the format + // to the single space which is more or less guaranteed to work as it's + // the semi-official way to clear the control contents when it doesn't + // have any valid value, according to Microsoft's old KB document + // Q238077, which can still be found online by searching for its + // number, even if it's not available on Microsoft web site any more. + // + // Coincidentally, it's also convenient for us, as we can just check if + // null text is empty to see if we need to use it elsewhere in the code. + m_nullText = wxS(" "); + } + else + { + // We need to quote the text, as otherwise format specifiers (e.g. + // "d", "m" etc) would be interpreted specially by the control. To make + // things simple, we just quote it entirely and always. + m_nullText.Replace("'", "''"); + m_nullText.insert(0, "'"); + m_nullText.append("'"); + } + + // Apply it immediately if we don't have any value right now. + if ( !m_date.IsValid() ) + MSWUpdateFormat(false); +} + wxSize wxDateTimePickerCtrl::DoGetBestSize() const { wxClientDC dc(const_cast(this)); @@ -168,7 +221,12 @@ wxDateTimePickerCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) switch ( hdr->code ) { case DTN_DATETIMECHANGE: - if ( MSWOnDateTimeChange(*(NMDATETIMECHANGE*)(hdr)) ) + const NMDATETIMECHANGE& dtch = *(NMDATETIMECHANGE*)(hdr); + + // Update the format before showing the new date if necessary. + MSWUpdateFormatIfNeeded(dtch.dwFlags == GDT_VALID); + + if ( MSWOnDateTimeChange(dtch) ) { *result = 0; return true; diff --git a/src/xrc/xh_datectrl.cpp b/src/xrc/xh_datectrl.cpp index 5255e75864..6ee4f99f51 100644 --- a/src/xrc/xh_datectrl.cpp +++ b/src/xrc/xh_datectrl.cpp @@ -42,6 +42,10 @@ wxObject *wxDateCtrlXmlHandler::DoCreateResource() SetupWindow(picker); + // Note that we want to set this one even if it's empty. + if ( HasParam(wxS("null-text")) ) + picker->SetNullText(GetText(wxS("null-text"))); + return picker; }