Merge miscellaneous wxDataViewCtrl-related bug fixes

Make it possible to define custom renderers using text controls reacting to
error presses in at least wxMSW and wxGTK.

Closes https://github.com/wxWidgets/wxWidgets/pull/221
This commit is contained in:
Vadim Zeitlin
2016-02-27 18:06:13 +01:00
17 changed files with 261 additions and 85 deletions

View File

@@ -180,6 +180,9 @@ public:
// wxDVR_DEFAULT_ALIGNMENT.
int GetEffectiveAlignment() const;
// Send wxEVT_DATAVIEW_ITEM_EDITING_STARTED event.
void NotifyEditingStarted(const wxDataViewItem& item);
protected:
// These methods are called from PrepareForItem() and should do whatever is
// needed for the current platform to ensure that the item is rendered
@@ -199,7 +202,7 @@ protected:
wxString m_variantType;
wxDataViewColumn *m_owner;
wxWeakRef<wxWindow> m_editorCtrl;
wxDataViewItem m_item; // for m_editorCtrl
wxDataViewItem m_item; // Item being currently edited, if valid.
// internal utility, may be used anywhere the window associated with the
// renderer is required

View File

@@ -3671,6 +3671,15 @@ protected:
virtual bool TryParent(wxEvent& event), return DoTryApp(event); )
#endif // WXWIN_COMPATIBILITY_2_8
// Overriding this method allows filtering the event handlers dynamically
// connected to this object. If this method returns false, the handler is
// not connected at all. If it returns true, it is connected using the
// possibly modified fields of the given entry.
virtual bool OnDynamicBind(wxDynamicEventTableEntry& WXUNUSED(entry))
{
return true;
}
static const wxEventTable sm_eventTable;
virtual const wxEventTable *GetEventTable() const;

View File

@@ -181,7 +181,10 @@ public:
void OnSetFocus(wxFocusEvent& event);
// intercept WM_GETDLGCODE
virtual WXLRESULT MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam);
virtual bool MSWHandleMessage(WXLRESULT *result,
WXUINT message,
WXWPARAM wParam,
WXLPARAM lParam);
virtual bool MSWShouldPreProcessMessage(WXMSG* pMsg);
virtual WXDWORD MSWGetStyle(long style, WXDWORD *exstyle) const;

View File

@@ -14,6 +14,8 @@
#include "wx/settings.h" // solely for wxSystemColour
class WXDLLIMPEXP_FWD_CORE wxButton;
// if this is set to 1, we use deferred window sizing to reduce flicker when
// resizing complicated window hierarchies, but this can in theory result in
// different behaviour than the old code so we keep the possibility to use it
@@ -534,6 +536,16 @@ public:
virtual wxMenu* MSWFindMenuFromHMENU(WXHMENU hMenu);
#endif // wxUSE_MENUS && !__WXUNIVERSAL__
// Return the default button for the TLW containing this window or NULL if
// none.
static wxButton* MSWGetDefaultButtonFor(wxWindow* win);
// Simulate a click on the given button if it is non-null, enabled and
// shown.
//
// Return true if the button was clicked, false otherwise.
static bool MSWClickButtonIfPossible(wxButton* btn);
protected:
// this allows you to implement standard control borders without
// repeating the code in different classes that are not derived from

View File

@@ -743,6 +743,11 @@ public:
}
protected:
// Override wxEvtHandler method to check for a common problem of binding
// wxEVT_TEXT_ENTER to a control without wxTE_PROCESS_ENTER style, which is
// never going to work.
virtual bool OnDynamicBind(wxDynamicEventTableEntry& entry);
// override streambuf method
#if wxHAS_TEXT_WINDOW_STREAM
int overflow(int i) wxOVERRIDE;

View File

@@ -173,10 +173,12 @@ private:
class MyCustomRenderer: public wxDataViewCustomRenderer
{
public:
MyCustomRenderer()
: wxDataViewCustomRenderer("string",
wxDATAVIEW_CELL_ACTIVATABLE,
wxALIGN_CENTER)
// This renderer can be either activatable or editable, for demonstration
// purposes. In real programs, you should select whether the user should be
// able to activate or edit the cell and it doesn't make sense to switch
// between the two -- but this is just an example, so it doesn't stop us.
explicit MyCustomRenderer(wxDataViewCellMode mode)
: wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER)
{ }
virtual bool Render( wxRect rect, wxDC *dc, int state ) wxOVERRIDE
@@ -223,6 +225,34 @@ public:
virtual bool GetValue( wxVariant &WXUNUSED(value) ) const wxOVERRIDE { return true; }
virtual bool HasEditorCtrl() const wxOVERRIDE { return true; }
virtual wxWindow*
CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) wxOVERRIDE
{
wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value,
labelRect.GetPosition(),
labelRect.GetSize(),
wxTE_PROCESS_ENTER);
text->SetInsertionPointEnd();
return text;
}
virtual bool
GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) wxOVERRIDE
{
wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl);
if ( !text )
return false;
value = text->GetValue();
return true;
}
private:
wxString m_value;
};
@@ -614,7 +644,7 @@ void MyFrame::BuildDataViewCtrl(wxPanel* parent, unsigned int nPanel, unsigned l
// column 5 of the view control:
MyCustomRenderer *cr = new MyCustomRenderer;
MyCustomRenderer *cr = new MyCustomRenderer(wxDATAVIEW_CELL_ACTIVATABLE);
wxDataViewColumn *column5 =
new wxDataViewColumn( "custom", cr, 5, -1, wxALIGN_LEFT,
wxDATAVIEW_COL_RESIZABLE );
@@ -662,7 +692,7 @@ void MyFrame::BuildDataViewCtrl(wxPanel* parent, unsigned int nPanel, unsigned l
m_ctrl[1]->AppendColumn(
new wxDataViewColumn("custom renderer",
new MyCustomRenderer,
new MyCustomRenderer(wxDATAVIEW_CELL_EDITABLE),
MyListModel::Col_Custom)
);
}

View File

@@ -444,7 +444,13 @@ void MyListModel::GetValueByRow( wxVariant &variant,
break;
case Col_Custom:
variant = wxString::Format("%d", row % 100);
{
IntToStringMap::const_iterator it = m_customColValues.find(row);
if ( it != m_customColValues.end() )
variant = it->second;
else
variant = wxString::Format("%d", row % 100);
}
break;
case Col_Max:
@@ -531,10 +537,13 @@ bool MyListModel::SetValueByRow( const wxVariant &variant,
case Col_Date:
case Col_TextWithAttr:
case Col_Custom:
wxLogError("Cannot edit the column %d", col);
break;
case Col_Custom:
m_customColValues[row] = variant.GetString();
break;
case Col_Max:
wxFAIL_MSG( "invalid column" );
}

View File

@@ -8,6 +8,10 @@
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
#include "wx/hashmap.h"
WX_DECLARE_HASH_MAP(unsigned, wxString, wxIntegerHash, wxIntegerEqual,
IntToStringMap);
// ----------------------------------------------------------------------------
// MyMusicTreeModelNode: a node inside MyMusicTreeModel
@@ -235,6 +239,7 @@ public:
private:
wxArrayString m_textColValues;
wxArrayString m_iconColValues;
IntToStringMap m_customColValues;
wxIcon m_icon[2];
};

View File

@@ -2603,7 +2603,14 @@ void TestDefaultActionDialog::OnCatchListBoxDClick(wxCommandEvent& WXUNUSED(even
void TestDefaultActionDialog::OnTextEnter(wxCommandEvent& event)
{
wxLogMessage("Text \"%s\" entered.", event.GetString());
const wxString& text = event.GetString();
if ( text.empty() )
{
event.Skip();
return;
}
wxLogMessage("Text \"%s\" entered.", text);
}
void MyFrame::OnTestDefaultActionDialog(wxCommandEvent& WXUNUSED(event))

View File

@@ -681,8 +681,6 @@ bool wxDataViewRendererBase::StartEditing( const wxDataViewItem &item, wxRect la
if( !start_event.IsAllowed() )
return false;
m_item = item; // remember for later
unsigned int col = GetOwner()->GetModelColumn();
const wxVariant& value = CheckedGetValue(dv_ctrl->GetModel(), item, col);
@@ -703,15 +701,23 @@ bool wxDataViewRendererBase::StartEditing( const wxDataViewItem &item, wxRect la
m_editorCtrl->SetFocus();
#endif
// Now we should send Editing Started event
return true;
}
void wxDataViewRendererBase::NotifyEditingStarted(const wxDataViewItem& item)
{
// Remember the item being edited for use in FinishEditing() later.
m_item = item;
wxDataViewColumn* const column = GetOwner();
wxDataViewCtrl* const dv_ctrl = column->GetOwner();
wxDataViewEvent event( wxEVT_DATAVIEW_ITEM_EDITING_STARTED, dv_ctrl->GetId() );
event.SetDataViewColumn( GetOwner() );
event.SetDataViewColumn( column );
event.SetModel( dv_ctrl->GetModel() );
event.SetItem( item );
event.SetEventObject( dv_ctrl );
dv_ctrl->GetEventHandler()->ProcessEvent( event );
return true;
}
void wxDataViewRendererBase::DestroyEditControl()
@@ -745,9 +751,10 @@ bool wxDataViewRendererBase::FinishEditing()
if (!m_editorCtrl)
return true;
// Try to get the value, normally we should succeed but if we fail, don't
// return immediately, we still need to destroy the edit control.
wxVariant value;
if ( !GetValueFromEditorCtrl(m_editorCtrl, value) )
return false;
const bool gotValue = GetValueFromEditorCtrl(m_editorCtrl, value);
wxDataViewCtrl* dv_ctrl = GetOwner()->GetOwner();
@@ -755,6 +762,9 @@ bool wxDataViewRendererBase::FinishEditing()
dv_ctrl->GetMainWindow()->SetFocus();
if ( !gotValue )
return false;
bool isValid = Validate(value);
unsigned int col = GetOwner()->GetModelColumn();
@@ -769,13 +779,16 @@ bool wxDataViewRendererBase::FinishEditing()
event.SetEventObject( dv_ctrl );
dv_ctrl->GetEventHandler()->ProcessEvent( event );
bool accepted = false;
if ( isValid && event.IsAllowed() )
{
dv_ctrl->GetModel()->ChangeValue(value, m_item, col);
return true;
accepted = true;
}
return false;
m_item = wxDataViewItem();
return accepted;
}
wxVariant
@@ -1044,17 +1057,20 @@ void wxDataViewEditorCtrlEvtHandler::OnChar( wxKeyEvent &event )
{
switch ( event.m_keyCode )
{
case WXK_RETURN:
m_finished = true;
m_owner->FinishEditing();
break;
case WXK_ESCAPE:
{
m_finished = true;
m_owner->CancelEditing();
break;
}
case WXK_RETURN:
if ( !event.HasAnyModifiers() )
{
m_finished = true;
m_owner->FinishEditing();
break;
}
wxFALLTHROUGH; // Ctrl/Alt/Shift-Enter is not handled specially
default:
event.Skip();
}

View File

@@ -1693,6 +1693,13 @@ void wxEvtHandler::DoBind(int id,
wxDynamicEventTableEntry *entry =
new wxDynamicEventTableEntry(eventType, id, lastId, func, userData);
// Check if the derived class allows binding such event handlers.
if ( !OnDynamicBind(*entry) )
{
delete entry;
return;
}
if (!m_dynamicEvents)
m_dynamicEvents = new DynamicEvents;

View File

@@ -1194,6 +1194,21 @@ void wxTextCtrlBase::DoUpdateWindowUI(wxUpdateUIEvent& event)
}
}
bool wxTextCtrlBase::OnDynamicBind(wxDynamicEventTableEntry& entry)
{
if ( entry.m_eventType == wxEVT_TEXT_ENTER )
{
wxCHECK_MSG
(
HasFlag(wxTE_PROCESS_ENTER),
false,
wxS("Must have wxTE_PROCESS_ENTER for wxEVT_TEXT_ENTER to work")
);
}
return wxControl::OnDynamicBind(entry);
}
// ----------------------------------------------------------------------------
// hit testing
// ----------------------------------------------------------------------------

View File

@@ -120,8 +120,8 @@ wxDataViewColumn* GetExpanderColumnOrFirstOne(wxDataViewCtrl* dataview)
wxTextCtrl *CreateEditorTextCtrl(wxWindow *parent, const wxRect& labelRect, const wxString& value)
{
wxTextCtrl* ctrl = new wxTextCtrl(parent, wxID_ANY, value,
wxPoint(labelRect.x,labelRect.y),
wxSize(labelRect.width,labelRect.height),
labelRect.GetPosition(),
labelRect.GetSize(),
wxTE_PROCESS_ENTER);
// Adjust size of wxTextCtrl editor to fit text, even if it means being
@@ -2251,6 +2251,8 @@ wxDataViewMainWindow::StartEditing(const wxDataViewItem& item,
const wxRect itemRect = GetItemRect(item, col);
if ( renderer->StartEditing(item, itemRect) )
{
renderer->NotifyEditingStarted(item);
// Save the renderer to be able to finish/cancel editing it later and
// save the control to be able to detect if we're still editing it.
m_editorRenderer = renderer;
@@ -3632,7 +3634,17 @@ void wxDataViewMainWindow::OnCharHook(wxKeyEvent& event)
return;
case WXK_RETURN:
// Shift-Enter is not special neither.
if ( event.ShiftDown() )
break;
wxFALLTHROUGH;
case WXK_TAB:
// Ctrl/Alt-Tab or Enter could be used for something else, so
// don't handle them here.
if ( event.HasModifiers() )
break;
m_editorRenderer->FinishEditing();
return;
}

View File

@@ -1749,20 +1749,11 @@ bool wxGtkDataViewModelNotifier::Cleared()
// wxDataViewRenderer
// ---------------------------------------------------------
static gpointer s_user_data = NULL;
static void
wxgtk_cell_editable_editing_done( GtkCellEditable *WXUNUSED(editable),
wxDataViewRenderer *wxrenderer )
{
wxDataViewColumn *column = wxrenderer->GetOwner();
wxDataViewCtrl *dv = column->GetOwner();
wxDataViewEvent event( wxEVT_DATAVIEW_ITEM_EDITING_DONE, dv->GetId() );
event.SetDataViewColumn( column );
event.SetModel( dv->GetModel() );
wxDataViewItem item( s_user_data );
event.SetItem( item );
dv->HandleWindowEvent( event );
wxrenderer->FinishEditing();
}
static void
@@ -1774,17 +1765,11 @@ wxgtk_renderer_editing_started( GtkCellRenderer *WXUNUSED(cell), GtkCellEditable
wxDataViewColumn *column = wxrenderer->GetOwner();
wxDataViewCtrl *dv = column->GetOwner();
wxDataViewEvent event( wxEVT_DATAVIEW_ITEM_EDITING_STARTED, dv->GetId() );
event.SetDataViewColumn( column );
event.SetModel( dv->GetModel() );
wxDataViewItem item(dv->GTKPathToItem(wxGtkTreePath(path)));
event.SetItem( item );
dv->HandleWindowEvent( event );
wxrenderer->NotifyEditingStarted(item);
if (GTK_IS_CELL_EDITABLE(editable))
{
s_user_data = item.GetID();
g_signal_connect (editable, "editing_done",
G_CALLBACK (wxgtk_cell_editable_editing_done),
(gpointer) wxrenderer );

View File

@@ -2058,14 +2058,48 @@ void wxTextCtrl::OnKeyDown(wxKeyEvent& event)
event.Skip();
}
WXLRESULT wxTextCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
bool
wxTextCtrl::MSWHandleMessage(WXLRESULT *rc,
WXUINT nMsg,
WXWPARAM wParam,
WXLPARAM lParam)
{
WXLRESULT lRc = wxTextCtrlBase::MSWWindowProc(nMsg, wParam, lParam);
bool processed = wxTextCtrlBase::MSWHandleMessage(rc, nMsg, wParam, lParam);
// Handle the special case of "Enter" key: the user code needs to specify
// wxTE_PROCESS_ENTER style to get it in the first place, but if this flag
// is used, then even if the wxEVT_TEXT_ENTER handler skips the event, the
// normal action of this key is not performed because IsDialogMessage() is
// not called and, also, an annoying beep is generated by EDIT default
// WndProc.
//
// Fix these problems by explicitly performing the default function of this
// key (which would be done by MSWProcessMessage() if we didn't have
// wxTE_PROCESS_ENTER) and preventing the default WndProc from getting it.
if ( nMsg == WM_CHAR &&
!processed &&
HasFlag(wxTE_PROCESS_ENTER) &&
wParam == VK_RETURN &&
!wxIsAnyModifierDown() )
{
MSWClickButtonIfPossible(MSWGetDefaultButtonFor(this));
processed = true;
}
switch ( nMsg )
{
case WM_GETDLGCODE:
{
// Ensure that the result value is initialized even if the base
// class didn't handle WM_GETDLGCODE but just update the value
// returned by it if it did handle it.
if ( !processed )
{
*rc = MSWDefWindowProc(nMsg, wParam, lParam);
processed = true;
}
// we always want the chars and the arrows: the arrows for
// navigation and the chars because we want Ctrl-C to work even
// in a read only control
@@ -2089,7 +2123,7 @@ WXLRESULT wxTextCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPara
if ( HasFlag(wxTE_PROCESS_TAB) )
lDlgCode |= DLGC_WANTTAB;
lRc |= lDlgCode;
*rc |= lDlgCode;
}
else // !editable
{
@@ -2098,11 +2132,17 @@ WXLRESULT wxTextCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPara
// including DLGC_WANTMESSAGE). This is strange (how
// does it work in the native Win32 apps?) but for now
// live with it.
lRc = lDlgCode;
*rc = lDlgCode;
}
if ( IsMultiLine() )
{
// The presence of this style, coming from the default EDIT
// WndProc, indicates that the control contents should be
// selected when it gets focus, but we don't want this to
// happen for the multiline controls, so clear it.
*rc &= ~DLGC_HASSETSEL;
}
if (IsMultiLine())
// Clear the DLGC_HASSETSEL bit from the return value
lRc &= ~DLGC_HASSETSEL;
}
break;
@@ -2122,7 +2162,7 @@ WXLRESULT wxTextCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lPara
#endif // wxUSE_MENUS
}
return lRc;
return processed;
}
// ----------------------------------------------------------------------------

View File

@@ -2316,7 +2316,7 @@ bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg)
// currently active button should get enter press even
// if there is a default button elsewhere so check if
// this window is a button first
wxWindow *btn = NULL;
wxButton *btn = NULL;
if ( lDlgCode & DLGC_DEFPUSHBUTTON )
{
// let IsDialogMessage() handle this for all
@@ -2325,8 +2325,11 @@ bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg)
long style = ::GetWindowLong(msg->hwnd, GWL_STYLE);
if ( (style & BS_OWNERDRAW) == BS_OWNERDRAW )
{
// emulate the button click
btn = wxFindWinFromHandle(msg->hwnd);
btn = wxDynamicCast
(
wxFindWinFromHandle(msg->hwnd),
wxButton
);
}
}
else // not a button itself, do we have default button?
@@ -2367,25 +2370,12 @@ bool wxWindowMSW::MSWProcessMessage(WXMSG* pMsg)
);
}
}
else // bCtrlDown
{
win = wxGetTopLevelParent(win);
}
wxTopLevelWindow * const
tlw = wxDynamicCast(win, wxTopLevelWindow);
if ( tlw )
{
btn = wxDynamicCast(tlw->GetDefaultItem(),
wxButton);
}
btn = MSWGetDefaultButtonFor(win);
}
if ( btn && btn->IsEnabled() && btn->IsShownOnScreen() )
{
btn->MSWCommand(BN_CLICKED, 0 /* unused */);
if ( MSWClickButtonIfPossible(btn) )
return true;
}
// This "Return" key press won't be actually used for
// navigation so don't generate wxNavigationKeyEvent
@@ -2544,6 +2534,36 @@ bool wxWindowMSW::MSWSafeIsDialogMessage(WXMSG* msg)
#endif // __WXUNIVERSAL__
/* static */
wxButton* wxWindowMSW::MSWGetDefaultButtonFor(wxWindow* win)
{
#if wxUSE_BUTTON
win = wxGetTopLevelParent(win);
wxTopLevelWindow *const tlw = wxDynamicCast(win, wxTopLevelWindow);
if ( tlw )
return wxDynamicCast(tlw->GetDefaultItem(), wxButton);
#endif // wxUSE_BUTTON
return NULL;
}
/* static */
bool wxWindowMSW::MSWClickButtonIfPossible(wxButton* btn)
{
#if wxUSE_BUTTON
if ( btn && btn->IsEnabled() && btn->IsShownOnScreen() )
{
btn->MSWCommand(BN_CLICKED, 0 /* unused */);
return true;
}
#endif // wxUSE_BUTTON
wxUnusedVar(btn);
return false;
}
// ---------------------------------------------------------------------------
// message params unpackers
// ---------------------------------------------------------------------------

View File

@@ -1886,22 +1886,20 @@ outlineView:(NSOutlineView*)outlineView
wxDataViewColumn* const
col([static_cast<wxDVCNSTableColumn*>(tableColumn) getColumnPointer]);
wxDataViewCtrl* const dvc = implementation->GetDataViewCtrl();
// stop editing of a custom item first (if necessary)
dvc->FinishCustomItemEditing();
// now, send the event:
wxDataViewEvent
event(wxEVT_DATAVIEW_ITEM_EDITING_STARTED,dvc->GetId());
event.SetEventObject(dvc);
event.SetItem(
wxDataViewItemFromItem([self itemAtRow:currentlyEditedRow]));
event.SetColumn(dvc->GetColumnPosition(col));
event.SetDataViewColumn(col);
dvc->GetEventHandler()->ProcessEvent(event);
wxDataViewRenderer* const renderer = col->GetRenderer();
if ( renderer )
{
renderer->NotifyEditingStarted
(
wxDataViewItemFromItem([self itemAtRow:currentlyEditedRow])
);
}
//else: we should always have a renderer but don't crash if for some
// unfathomable reason we don't have it
}
-(void) textDidEndEditing:(NSNotification*)notification