]> git.saurik.com Git - wxWidgets.git/blobdiff - src/generic/datavgen.cpp
Make wxWindow::HasScrollbar() respect wxScrolled::ShowScrollbars().
[wxWidgets.git] / src / generic / datavgen.cpp
index 8d386708761284f5e7cb65f2df7dc44f1cf1f983..d90cd83ef0fc81b2938042ef49e3d0b0f15e2172 100644 (file)
@@ -3,7 +3,6 @@
 // Purpose:     wxDataViewCtrl generic implementation
 // Author:      Robert Roebling
 // Modified by: Francesco Montorsi, Guru Kathiresan, Bo Yang
-// Id:          $Id$
 // Copyright:   (c) 1998 Robert Roebling
 // Licence:     wxWindows licence
 /////////////////////////////////////////////////////////////////////////////
@@ -80,7 +79,22 @@ static const int EXPANDER_OFFSET = 1;
 // For the generic implementation, both the leaf nodes and the nodes are sorted for
 // fast search when needed
 static wxDataViewModel* g_model;
-static int g_column = -2;
+
+// The column is either the index of the column to be used for sorting or one
+// of the special values in this enum:
+enum
+{
+    // Sort when we're thawed later.
+    SortColumn_OnThaw = -3,
+
+    // Don't sort at all.
+    SortColumn_None = -2,
+
+    // Sort using the model default sort order.
+    SortColumn_Default = -1
+};
+
+static int g_column = SortColumn_None;
 static bool g_asending = true;
 
 // ----------------------------------------------------------------------------
@@ -146,6 +160,16 @@ void wxDataViewColumn::UpdateDisplay()
     }
 }
 
+void wxDataViewColumn::UnsetAsSortKey()
+{
+    m_sort = false;
+
+    if ( m_owner )
+        m_owner->SetSortingColumnIndex(wxNOT_FOUND);
+
+    UpdateDisplay();
+}
+
 void wxDataViewColumn::SetSortOrder(bool ascending)
 {
     if ( !m_owner )
@@ -224,7 +248,7 @@ private:
     {
         const unsigned idx = event.GetColumn();
 
-        if ( SendEvent(wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_CLICK, idx) )
+        if ( SendEvent(wxEVT_DATAVIEW_COLUMN_HEADER_CLICK, idx) )
             return;
 
         // default handling for the column click is to sort by this column or
@@ -254,12 +278,12 @@ private:
 
         owner->OnColumnChange(idx);
 
-        SendEvent(wxEVT_COMMAND_DATAVIEW_COLUMN_SORTED, idx);
+        SendEvent(wxEVT_DATAVIEW_COLUMN_SORTED, idx);
     }
 
     void OnRClick(wxHeaderCtrlEvent& event)
     {
-        if ( !SendEvent(wxEVT_COMMAND_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK,
+        if ( !SendEvent(wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK,
                         event.GetColumn()) )
             event.Skip();
     }
@@ -347,7 +371,7 @@ public:
     static wxDataViewTreeNode* CreateRootNode()
     {
         wxDataViewTreeNode *n = new wxDataViewTreeNode(NULL, wxDataViewItem());
-        n->SetHasChildren(true);
+        n->m_branchData = new BranchNodeData;
         n->m_branchData->open = true;
         return n;
     }
@@ -416,6 +440,11 @@ public:
 
     void ToggleOpen()
     {
+        // We do not allow the (invisible) root node to be collapsed because
+        // there is no way to expand it again.
+        if ( !m_parent )
+            return;
+
         wxCHECK_RET( m_branchData != NULL, "can't open leaf node" );
 
         int sum = 0;
@@ -446,6 +475,11 @@ public:
 
     void SetHasChildren(bool has)
     {
+        // The invisible root item always has children, so ignore any attempts
+        // to change this.
+        if ( !m_parent )
+            return;
+
         if ( !has )
         {
             wxDELETE(m_branchData);
@@ -571,20 +605,50 @@ public:
         UpdateDisplay();
     }
 
+    // Override the base class method to resort if needed, i.e. if
+    // SortPrepare() was called -- and ignored -- while we were frozen.
+    virtual void DoThaw()
+    {
+        if ( g_column == SortColumn_OnThaw )
+        {
+            Resort();
+            g_column = SortColumn_None;
+        }
+
+        wxWindow::DoThaw();
+    }
+
     void SortPrepare()
     {
         g_model = GetModel();
+
         wxDataViewColumn* col = GetOwner()->GetSortingColumn();
         if( !col )
         {
             if (g_model->HasDefaultCompare())
-                g_column = -1;
+            {
+                // See below for the explanation of IsFrozen() test.
+                if ( IsFrozen() )
+                    g_column = SortColumn_OnThaw;
+                else
+                    g_column = SortColumn_Default;
+            }
             else
-                g_column = -2;
+                g_column = SortColumn_None;
 
             g_asending = true;
             return;
         }
+
+        // Avoid sorting while the window is frozen, this allows to quickly add
+        // many items without resorting after each addition and only resort
+        // them all at once when the window is finally thawed, see above.
+        if ( IsFrozen() )
+        {
+            g_column = SortColumn_OnThaw;
+            return;
+        }
+
         g_column = col->GetModelColumn();
         g_asending = col->IsSortOrderAscending();
     }
@@ -602,7 +666,7 @@ public:
     void OnPaint( wxPaintEvent &event );
     void OnCharHook( wxKeyEvent &event );
     void OnChar( wxKeyEvent &event );
-    void OnVerticalNavigation(unsigned int newCurrent, const wxKeyEvent& event);
+    void OnVerticalNavigation(int delta, const wxKeyEvent& event);
     void OnLeftKey();
     void OnRightKey();
     void OnMouse( wxMouseEvent &event );
@@ -637,7 +701,7 @@ public:
     // the displaying number of the tree are changing along with the
     // expanding/collapsing of the tree nodes
     unsigned int GetLastVisibleRow();
-    unsigned int GetRowCount();
+    unsigned int GetRowCount() const;
 
     const wxDataViewSelection& GetSelections() const { return m_selection; }
     void SetSelections( const wxDataViewSelection & sel )
@@ -668,6 +732,7 @@ public:
 
     void SetRowHeight( int lineHeight ) { m_lineHeight = lineHeight; }
     int GetRowHeight() const { return m_lineHeight; }
+    int GetDefaultRowHeight() const;
 
     // Some useful functions for row and item mapping
     wxDataViewItem GetItemByRow( unsigned int row ) const;
@@ -706,7 +771,7 @@ public:
     void StartEditing(const wxDataViewItem& item, const wxDataViewColumn* col);
 
 private:
-    int RecalculateCount();
+    int RecalculateCount() const;
 
     // Return false only if the event was vetoed by its handler.
     bool SendExpanderEvent(wxEventType type, const wxDataViewItem& item);
@@ -1007,11 +1072,13 @@ bool wxDataViewToggleRenderer::Render( wxRect cell, wxDC *dc, int WXUNUSED(state
         GetEnabled() == false)
         flags |= wxCONTROL_DISABLED;
 
-    // check boxes we draw must always have the same, standard size (if it's
-    // bigger than the cell size the checkbox will be truncated because the
-    // caller had set the clipping rectangle to prevent us from drawing outside
-    // the cell)
-    cell.SetSize(GetSize());
+    // Ensure that the check boxes always have at least the minimal required
+    // size, otherwise DrawCheckBox() doesn't really work well. If this size is
+    // greater than the cell size, the checkbox will be truncated but this is a
+    // lesser evil.
+    wxSize size = cell.GetSize();
+    size.IncTo(GetSize());
+    cell.SetSize(size);
 
     wxRendererNative::Get().DrawCheckBox(
             GetOwner()->GetOwner(),
@@ -1022,7 +1089,7 @@ bool wxDataViewToggleRenderer::Render( wxRect cell, wxDC *dc, int WXUNUSED(state
     return true;
 }
 
-bool wxDataViewToggleRenderer::WXActivateCell(const wxRect& WXUNUSED(cell),
+bool wxDataViewToggleRenderer::WXActivateCell(const wxRect& WXUNUSED(cellRect),
                                               wxDataViewModel *model,
                                               const wxDataViewItem& item,
                                               unsigned int col,
@@ -1030,7 +1097,8 @@ bool wxDataViewToggleRenderer::WXActivateCell(const wxRect& WXUNUSED(cell),
 {
     if ( mouseEvent )
     {
-        // only react to clicks directly on the checkbox, not elsewhere in the same cell:
+        // Only react to clicks directly on the checkbox, not elsewhere in the
+        // same cell.
         if ( !wxRect(GetSize()).Contains(mouseEvent->GetPosition()) )
             return false;
     }
@@ -1187,7 +1255,17 @@ bool wxDataViewIconTextRenderer::GetValueFromEditorCtrl( wxWindow *editor, wxVar
 {
     wxTextCtrl *text = (wxTextCtrl*) editor;
 
-    wxDataViewIconText iconText(text->GetValue(), m_value.GetIcon());
+    // The icon can't be edited so get its old value and reuse it.
+    wxVariant valueOld;
+    wxDataViewColumn* const col = GetOwner();
+    GetView()->GetModel()->GetValue(valueOld, m_item, col->GetModelColumn());
+
+    wxDataViewIconText iconText;
+    iconText << valueOld;
+
+    // But replace the text with the value entered by user.
+    iconText.SetText(text->GetValue());
+
     value << iconText;
     return true;
 }
@@ -1375,9 +1453,8 @@ wxDataViewMainWindow::wxDataViewMainWindow( wxDataViewCtrl *parent, wxWindowID i
     m_currentCol = NULL;
     m_currentColSetByKeyboard = false;
     m_useCellFocus = false;
-    m_currentRow = 0;
-
-    m_lineHeight = wxMax( 17, GetCharHeight() + 4 ); // 17 = mini icon height + 1
+    m_currentRow = (unsigned)-1;
+    m_lineHeight = GetDefaultRowHeight();
 
 #if wxUSE_DRAG_AND_DROP
     m_dragCount = 0;
@@ -1420,6 +1497,20 @@ wxDataViewMainWindow::~wxDataViewMainWindow()
 }
 
 
+int wxDataViewMainWindow::GetDefaultRowHeight() const
+{
+#ifdef __WXMSW__
+    // We would like to use the same line height that Explorer uses. This is
+    // different from standard ListView control since Vista.
+    if ( wxGetWinVersion() >= wxWinVersion_Vista )
+        return wxMax(16, GetCharHeight()) + 6; // 16 = mini icon height
+    else
+#endif // __WXMSW__
+        return wxMax(16, GetCharHeight()) + 1; // 16 = mini icon height
+}
+
+
+
 #if wxUSE_DRAG_AND_DROP
 bool wxDataViewMainWindow::EnableDragSource( const wxDataFormat &format )
 {
@@ -1468,11 +1559,12 @@ wxDragResult wxDataViewMainWindow::OnDragOver( wxDataFormat format, wxCoord x,
 
     wxDataViewModel *model = GetModel();
 
-    wxDataViewEvent event( wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner->GetId() );
+    wxDataViewEvent event( wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner->GetId() );
     event.SetEventObject( m_owner );
     event.SetItem( item );
     event.SetModel( model );
     event.SetDataFormat( format );
+    event.SetDropEffect( def );
     if (!m_owner->HandleWindowEvent( event ))
     {
         RemoveDropHint();
@@ -1511,7 +1603,7 @@ bool wxDataViewMainWindow::OnDrop( wxDataFormat format, wxCoord x, wxCoord y )
 
     wxDataViewModel *model = GetModel();
 
-    wxDataViewEvent event( wxEVT_COMMAND_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner->GetId() );
+    wxDataViewEvent event( wxEVT_DATAVIEW_ITEM_DROP_POSSIBLE, m_owner->GetId() );
     event.SetEventObject( m_owner );
     event.SetItem( item );
     event.SetModel( model );
@@ -1542,13 +1634,14 @@ wxDragResult wxDataViewMainWindow::OnData( wxDataFormat format, wxCoord x, wxCoo
 
     wxCustomDataObject *obj = (wxCustomDataObject *) GetDropTarget()->GetDataObject();
 
-    wxDataViewEvent event( wxEVT_COMMAND_DATAVIEW_ITEM_DROP, m_owner->GetId() );
+    wxDataViewEvent event( wxEVT_DATAVIEW_ITEM_DROP, m_owner->GetId() );
     event.SetEventObject( m_owner );
     event.SetItem( item );
     event.SetModel( model );
     event.SetDataFormat( format );
     event.SetDataSize( obj->GetSize() );
     event.SetDataBuffer( obj->GetData() );
+    event.SetDropEffect( def );
     if (!m_owner->HandleWindowEvent( event ))
         return wxDragNone;
 
@@ -1674,11 +1767,9 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
     wxDataViewModel *model = GetModel();
     wxAutoBufferedPaintDC dc( this );
 
-#ifdef __WXMSW__
     dc.SetBrush(GetOwner()->GetBackgroundColour());
     dc.SetPen( *wxTRANSPARENT_PEN );
     dc.DrawRectangle(GetClientSize());
-#endif
 
     if ( IsEmpty() )
     {
@@ -1702,7 +1793,7 @@ void wxDataViewMainWindow::OnPaint( wxPaintEvent &WXUNUSED(event) )
 
     // Send the event to wxDataViewCtrl itself.
     wxWindow * const parent = GetParent();
-    wxDataViewEvent cache_event(wxEVT_COMMAND_DATAVIEW_CACHE_HINT, parent->GetId());
+    wxDataViewEvent cache_event(wxEVT_DATAVIEW_CACHE_HINT, parent->GetId());
     cache_event.SetEventObject(parent);
     cache_event.SetCache(item_start, item_last - 1);
     parent->ProcessWindowEvent(cache_event);
@@ -2318,7 +2409,17 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
         // If this was the last child to be removed, it's possible the parent
         // node became a leaf. Let's ask the model about it.
         if ( parentNode->GetChildNodes().empty() )
-            parentNode->SetHasChildren(GetModel()->IsContainer(parent));
+        {
+            bool isContainer = GetModel()->IsContainer(parent);
+            parentNode->SetHasChildren(isContainer);
+            if ( isContainer )
+            {
+                // If it's still a container, make sure we show "+" icon for it
+                // and not "-" one as there is nothing to collapse any more.
+                if ( parentNode->IsOpen() )
+                    parentNode->ToggleOpen();
+            }
+        }
 
         // Update selection by removing 'item' and its entire children tree from the selection.
         if ( !m_selection.empty() )
@@ -2359,7 +2460,7 @@ bool wxDataViewMainWindow::ItemDeleted(const wxDataViewItem& parent,
     }
 
     // Change the current row to the last row if the current exceed the max row number
-    if( m_currentRow > GetRowCount() )
+    if ( m_currentRow >= GetRowCount() )
         ChangeCurrentRow(m_count - 1);
 
     GetOwner()->InvalidateColBestWidths();
@@ -2377,7 +2478,7 @@ bool wxDataViewMainWindow::ItemChanged(const wxDataViewItem & item)
 
     // Send event
     wxWindow *parent = GetParent();
-    wxDataViewEvent le(wxEVT_COMMAND_DATAVIEW_ITEM_VALUE_CHANGED, parent->GetId());
+    wxDataViewEvent le(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, parent->GetId());
     le.SetEventObject(parent);
     le.SetModel(GetModel());
     le.SetItem(item);
@@ -2418,7 +2519,7 @@ bool wxDataViewMainWindow::ValueChanged( const wxDataViewItem & item, unsigned i
 
     // Send event
     wxWindow *parent = GetParent();
-    wxDataViewEvent le(wxEVT_COMMAND_DATAVIEW_ITEM_VALUE_CHANGED, parent->GetId());
+    wxDataViewEvent le(wxEVT_DATAVIEW_ITEM_VALUE_CHANGED, parent->GetId());
     le.SetEventObject(parent);
     le.SetModel(GetModel());
     le.SetItem(item);
@@ -2433,9 +2534,17 @@ bool wxDataViewMainWindow::Cleared()
 {
     DestroyTree();
     m_selection.Clear();
+    m_currentRow = (unsigned)-1;
 
-    SortPrepare();
-    BuildTree( GetModel() );
+    if (GetModel())
+    {
+        SortPrepare();
+        BuildTree( GetModel() );
+    }
+    else
+    {
+        m_count = 0;
+    }
 
     GetOwner()->InvalidateColBestWidths();
     UpdateDisplay();
@@ -2569,12 +2678,14 @@ unsigned int wxDataViewMainWindow::GetLastVisibleRow()
     return wxMin( GetRowCount()-1, row );
 }
 
-unsigned int wxDataViewMainWindow::GetRowCount()
+unsigned int wxDataViewMainWindow::GetRowCount() const
 {
     if ( m_count == -1 )
     {
-        m_count = RecalculateCount();
-        UpdateDisplay();
+        wxDataViewMainWindow* const
+            self = const_cast<wxDataViewMainWindow*>(this);
+        self->m_count = RecalculateCount();
+        self->UpdateDisplay();
     }
     return m_count;
 }
@@ -2687,7 +2798,7 @@ bool wxDataViewMainWindow::IsRowSelected( unsigned int row )
 void wxDataViewMainWindow::SendSelectionChangedEvent( const wxDataViewItem& item)
 {
     wxWindow *parent = GetParent();
-    wxDataViewEvent le(wxEVT_COMMAND_DATAVIEW_SELECTION_CHANGED, parent->GetId());
+    wxDataViewEvent le(wxEVT_DATAVIEW_SELECTION_CHANGED, parent->GetId());
 
     le.SetEventObject(parent);
     le.SetModel(GetModel());
@@ -2954,6 +3065,9 @@ wxDataViewTreeNode * wxDataViewMainWindow::GetTreeNodeByRow(unsigned int row) co
 {
     wxASSERT( !IsVirtualList() );
 
+    if ( row == (unsigned)-1 )
+        return NULL;
+
     RowToTreeNodeJob job( row , -2, m_root );
     Walker( m_root , job );
     return job.GetResult();
@@ -2961,15 +3075,20 @@ wxDataViewTreeNode * wxDataViewMainWindow::GetTreeNodeByRow(unsigned int row) co
 
 wxDataViewItem wxDataViewMainWindow::GetItemByRow(unsigned int row) const
 {
+    wxDataViewItem item;
     if (IsVirtualList())
     {
-        return wxDataViewItem( wxUIntToPtr(row+1) );
+        if ( row < GetRowCount() )
+            item = wxDataViewItem(wxUIntToPtr(row+1));
     }
     else
     {
         wxDataViewTreeNode *node = GetTreeNodeByRow(row);
-        return node ? node->GetItem() : wxDataViewItem();
+        if ( node )
+            item = node->GetItem();
     }
+
+    return item;
 }
 
 bool
@@ -3028,44 +3147,44 @@ void wxDataViewMainWindow::Expand( unsigned int row )
     if (!node->HasChildren())
         return;
 
-            if (!node->IsOpen())
-            {
-                if ( !SendExpanderEvent(wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDING, node->GetItem()) )
-                {
-                    // Vetoed by the event handler.
-                    return;
-                }
+    if (!node->IsOpen())
+    {
+        if ( !SendExpanderEvent(wxEVT_DATAVIEW_ITEM_EXPANDING, node->GetItem()) )
+        {
+            // Vetoed by the event handler.
+            return;
+        }
 
-                node->ToggleOpen();
+        node->ToggleOpen();
 
-                // build the children of current node
-                if( node->GetChildNodes().empty() )
-                {
-                    SortPrepare();
-                    ::BuildTreeHelper(GetModel(), node->GetItem(), node);
-                }
+        // build the children of current node
+        if( node->GetChildNodes().empty() )
+        {
+            SortPrepare();
+            ::BuildTreeHelper(GetModel(), node->GetItem(), node);
+        }
 
-                // By expanding the node all row indices that are currently in the selection list
-                // and are greater than our node have become invalid. So we have to correct that now.
-                const unsigned rowAdjustment = node->GetSubTreeCount();
-                for(unsigned i=0; i<m_selection.size(); ++i)
-                {
-                    const unsigned testRow = m_selection[i];
-                    // all rows above us are not affected, so skip them
-                    if(testRow <= row)
-                        continue;
+        // By expanding the node all row indices that are currently in the selection list
+        // and are greater than our node have become invalid. So we have to correct that now.
+        const unsigned rowAdjustment = node->GetSubTreeCount();
+        for(unsigned i=0; i<m_selection.size(); ++i)
+        {
+            const unsigned testRow = m_selection[i];
+            // all rows above us are not affected, so skip them
+            if(testRow <= row)
+                continue;
 
-                    m_selection[i] += rowAdjustment;
-                }
+            m_selection[i] += rowAdjustment;
+        }
 
-                if(m_currentRow > row)
-                    ChangeCurrentRow(m_currentRow + rowAdjustment);
+        if(m_currentRow > row)
+            ChangeCurrentRow(m_currentRow + rowAdjustment);
 
-                m_count = -1;
-                UpdateDisplay();
-                // Send the expanded event
-                SendExpanderEvent(wxEVT_COMMAND_DATAVIEW_ITEM_EXPANDED,node->GetItem());
-            }
+        m_count = -1;
+        UpdateDisplay();
+        // Send the expanded event
+        SendExpanderEvent(wxEVT_DATAVIEW_ITEM_EXPANDED,node->GetItem());
+    }
 }
 
 void wxDataViewMainWindow::Collapse(unsigned int row)
@@ -3082,7 +3201,7 @@ void wxDataViewMainWindow::Collapse(unsigned int row)
 
         if (node->IsOpen())
         {
-            if ( !SendExpanderEvent(wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSING,node->GetItem()) )
+            if ( !SendExpanderEvent(wxEVT_DATAVIEW_ITEM_COLLAPSING,node->GetItem()) )
             {
                 // Vetoed by the event handler.
                 return;
@@ -3138,7 +3257,7 @@ void wxDataViewMainWindow::Collapse(unsigned int row)
 
             m_count = -1;
             UpdateDisplay();
-            SendExpanderEvent(wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSED,node->GetItem());
+            SendExpanderEvent(wxEVT_DATAVIEW_ITEM_COLLAPSED,node->GetItem());
         }
 }
 
@@ -3290,7 +3409,7 @@ wxRect wxDataViewMainWindow::GetItemRect( const wxDataViewItem & item,
     return itemRect;
 }
 
-int wxDataViewMainWindow::RecalculateCount()
+int wxDataViewMainWindow::RecalculateCount() const
 {
     if (IsVirtualList())
     {
@@ -3558,15 +3677,20 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
     switch ( event.GetKeyCode() )
     {
         case WXK_RETURN:
-            if ( !event.HasModifiers() )
+            if ( event.HasModifiers() )
+            {
+                event.Skip();
+                break;
+            }
+            else
             {
-                // Enter activates the item, i.e. sends wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED to
+                // Enter activates the item, i.e. sends wxEVT_DATAVIEW_ITEM_ACTIVATED to
                 // it. Only if that event is not handled do we activate column renderer (which
                 // is normally done by Space) or even inline editing.
 
                 const wxDataViewItem item = GetItemByRow(m_currentRow);
 
-                wxDataViewEvent le(wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED,
+                wxDataViewEvent le(wxEVT_DATAVIEW_ITEM_ACTIVATED,
                                    parent->GetId());
                 le.SetItem(item);
                 le.SetEventObject(parent);
@@ -3578,7 +3702,12 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
             }
 
         case WXK_SPACE:
-            if ( !event.HasModifiers() )
+            if ( event.HasModifiers() )
+            {
+                event.Skip();
+                break;
+            }
+            else
             {
                 // Space toggles activatable items or -- if not activatable --
                 // starts inline editing (this is normally done using F2 on
@@ -3608,7 +3737,12 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
             }
 
         case WXK_F2:
-            if ( !event.HasModifiers() )
+            if ( event.HasModifiers() )
+            {
+                event.Skip();
+                break;
+            }
+            else
             {
                 if( !m_selection.empty() )
                 {
@@ -3636,13 +3770,11 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
             break;
 
         case WXK_UP:
-            if ( m_currentRow > 0 )
-                OnVerticalNavigation( m_currentRow - 1, event );
+            OnVerticalNavigation( -1, event );
             break;
 
         case WXK_DOWN:
-            if ( m_currentRow + 1 < GetRowCount() )
-                OnVerticalNavigation( m_currentRow + 1, event );
+            OnVerticalNavigation( +1, event );
             break;
         // Add the process for tree expanding/collapsing
         case WXK_LEFT:
@@ -3654,37 +3786,19 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
             break;
 
         case WXK_END:
-        {
-            if (!IsEmpty())
-                OnVerticalNavigation( GetRowCount() - 1, event );
+            OnVerticalNavigation( +(int)GetRowCount(), event );
             break;
-        }
+
         case WXK_HOME:
-            if (!IsEmpty())
-                OnVerticalNavigation( 0, event );
+            OnVerticalNavigation( -(int)GetRowCount(), event );
             break;
 
         case WXK_PAGEUP:
-            {
-                int steps = pageSize - 1;
-                int index = m_currentRow - steps;
-                if (index < 0)
-                    index = 0;
-
-                OnVerticalNavigation( index, event );
-            }
+            OnVerticalNavigation( -(pageSize - 1), event );
             break;
 
         case WXK_PAGEDOWN:
-            {
-                int steps = pageSize - 1;
-                unsigned int index = m_currentRow + steps;
-                unsigned int count = GetRowCount();
-                if ( index >= count )
-                    index = count - 1;
-
-                OnVerticalNavigation( index, event );
-            }
+            OnVerticalNavigation( +(pageSize - 1), event );
             break;
 
         default:
@@ -3692,16 +3806,24 @@ void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
     }
 }
 
-void wxDataViewMainWindow::OnVerticalNavigation(unsigned int newCurrent, const wxKeyEvent& event)
+void wxDataViewMainWindow::OnVerticalNavigation(int delta, const wxKeyEvent& event)
 {
-    wxCHECK_RET( newCurrent < GetRowCount(),
-                wxT("invalid item index in OnVerticalNavigation()") );
-
     // if there is no selection, we cannot move it anywhere
-    if (!HasCurrentRow())
+    if (!HasCurrentRow() || IsEmpty())
         return;
 
+    int newRow = (int)m_currentRow + delta;
+
+    // let's keep the new row inside the allowed range
+    if ( newRow < 0 )
+        newRow = 0;
+
+    const int rowCount = (int)GetRowCount();
+    if ( newRow >= rowCount )
+        newRow = rowCount - 1;
+
     unsigned int oldCurrent = m_currentRow;
+    unsigned int newCurrent = (unsigned int)newRow;
 
     // in single selection we just ignore Shift as we can't select several
     // items anyhow
@@ -3753,6 +3875,8 @@ void wxDataViewMainWindow::OnLeftKey()
     else
     {
         wxDataViewTreeNode* node = GetTreeNodeByRow(m_currentRow);
+        if ( !node )
+            return;
 
         if ( TryAdvanceCurrentColumn(node, /*forward=*/false) )
             return;
@@ -3795,6 +3919,8 @@ void wxDataViewMainWindow::OnRightKey()
     else
     {
         wxDataViewTreeNode* node = GetTreeNodeByRow(m_currentRow);
+        if ( !node )
+            return;
 
         if ( node->HasChildren() )
         {
@@ -3879,9 +4005,13 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
         return;
     }
 
-    // set the focus to ourself if any of the mouse buttons are pressed
-    if(event.ButtonDown() && !HasFocus())
-        SetFocus();
+    if(event.ButtonDown())
+    {
+        // Not skipping button down events would prevent the system from
+        // setting focus to this window as most (all?) of them do by default,
+        // so skip it to enable default handling.
+        event.Skip();
+    }
 
     int x = event.GetX();
     int y = event.GetY();
@@ -3916,7 +4046,7 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
     if (event.RightUp())
     {
         wxWindow *parent = GetParent();
-        wxDataViewEvent le(wxEVT_COMMAND_DATAVIEW_ITEM_CONTEXT_MENU, parent->GetId());
+        wxDataViewEvent le(wxEVT_DATAVIEW_ITEM_CONTEXT_MENU, parent->GetId());
         le.SetEventObject(parent);
         le.SetModel(model);
 
@@ -3925,30 +4055,82 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
             le.SetItem( item );
             le.SetColumn( col->GetModelColumn() );
             le.SetDataViewColumn( col );
-
-            wxVariant value;
-            model->GetValue( value, item, col->GetModelColumn() );
-            le.SetValue(value);
         }
 
         parent->ProcessWindowEvent(le);
         return;
     }
 
-    if (!col)
+#if wxUSE_DRAG_AND_DROP
+    if (event.Dragging() || ((m_dragCount > 0) && event.Leaving()))
     {
-        event.Skip();
+        if (m_dragCount == 0)
+        {
+            // we have to report the raw, physical coords as we want to be
+            // able to call HitTest(event.m_pointDrag) from the user code to
+            // get the item being dragged
+            m_dragStart = event.GetPosition();
+        }
+
+        m_dragCount++;
+        if ((m_dragCount < 3) && (event.Leaving()))
+            m_dragCount = 3;
+        else if (m_dragCount != 3)
+            return;
+
+        if (event.LeftIsDown())
+        {
+            m_owner->CalcUnscrolledPosition( m_dragStart.x, m_dragStart.y,
+                                             &m_dragStart.x, &m_dragStart.y );
+            unsigned int drag_item_row = GetLineAt( m_dragStart.y );
+            wxDataViewItem itemDragged = GetItemByRow( drag_item_row );
+
+            // Notify cell about drag
+            wxDataViewEvent event( wxEVT_DATAVIEW_ITEM_BEGIN_DRAG, m_owner->GetId() );
+            event.SetEventObject( m_owner );
+            event.SetItem( itemDragged );
+            event.SetModel( model );
+            if (!m_owner->HandleWindowEvent( event ))
+                return;
+
+            if (!event.IsAllowed())
+                return;
+
+            wxDataObject *obj = event.GetDataObject();
+            if (!obj)
+                return;
+
+            wxDataViewDropSource drag( this, drag_item_row );
+            drag.SetData( *obj );
+            /* wxDragResult res = */ drag.DoDragDrop(event.GetDragFlags());
+            delete obj;
+        }
         return;
     }
+    else
+    {
+        m_dragCount = 0;
+    }
+#endif // wxUSE_DRAG_AND_DROP
 
-    wxDataViewRenderer *cell = col->GetRenderer();
-    if ((current >= GetRowCount()) || (x > GetEndOfLastCol()))
+    // Check if we clicked outside the item area.
+    if ((current >= GetRowCount()) || !col)
     {
-        // Unselect all if below the last row ?
+        // Follow Windows convention here: clicking either left or right (but
+        // not middle) button clears the existing selection.
+        if (m_owner && (event.LeftDown() || event.RightDown()))
+        {
+            if (!GetSelections().empty())
+            {
+                m_owner->UnselectAll();
+                SendSelectionChangedEvent(wxDataViewItem());
+            }
+        }
         event.Skip();
         return;
     }
 
+    wxDataViewRenderer *cell = col->GetRenderer();
     wxDataViewColumn* const
         expander = GetExpanderColumnOrFirstOne(GetOwner());
 
@@ -4004,57 +4186,6 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
         }
     }
 
-#if wxUSE_DRAG_AND_DROP
-    if (event.Dragging())
-    {
-        if (m_dragCount == 0)
-        {
-            // we have to report the raw, physical coords as we want to be
-            // able to call HitTest(event.m_pointDrag) from the user code to
-            // get the item being dragged
-            m_dragStart = event.GetPosition();
-        }
-
-        m_dragCount++;
-
-        if (m_dragCount != 3)
-            return;
-
-        if (event.LeftIsDown())
-        {
-            m_owner->CalcUnscrolledPosition( m_dragStart.x, m_dragStart.y,
-                                             &m_dragStart.x, &m_dragStart.y );
-            unsigned int drag_item_row = GetLineAt( m_dragStart.y );
-            wxDataViewItem itemDragged = GetItemByRow( drag_item_row );
-
-            // Notify cell about drag
-            wxDataViewEvent event( wxEVT_COMMAND_DATAVIEW_ITEM_BEGIN_DRAG, m_owner->GetId() );
-            event.SetEventObject( m_owner );
-            event.SetItem( itemDragged );
-            event.SetModel( model );
-            if (!m_owner->HandleWindowEvent( event ))
-                return;
-
-            if (!event.IsAllowed())
-                return;
-
-            wxDataObject *obj = event.GetDataObject();
-            if (!obj)
-                return;
-
-            wxDataViewDropSource drag( this, drag_item_row );
-            drag.SetData( *obj );
-            /* wxDragResult res = */ drag.DoDragDrop();
-            delete obj;
-        }
-        return;
-    }
-    else
-    {
-        m_dragCount = 0;
-    }
-#endif // wxUSE_DRAG_AND_DROP
-
     bool simulateClick = false;
 
     if (event.ButtonDClick())
@@ -4078,7 +4209,7 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
         else if ( current == m_lineLastClicked )
         {
             wxWindow *parent = GetParent();
-            wxDataViewEvent le(wxEVT_COMMAND_DATAVIEW_ITEM_ACTIVATED, parent->GetId());
+            wxDataViewEvent le(wxEVT_DATAVIEW_ITEM_ACTIVATED, parent->GetId());
             le.SetItem( item );
             le.SetColumn( col->GetModelColumn() );
             le.SetDataViewColumn( col );
@@ -4226,8 +4357,15 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
         m_currentCol = col;
         m_currentColSetByKeyboard = false;
 
+        // This flag is used to decide whether we should start editing the item
+        // label. We do it if the user clicks twice (but not double clicks,
+        // i.e. simulateClick is false) on the same item but not if the click
+        // was used for something else already, e.g. selecting the item (so it
+        // must have been already selected) or giving the focus to the control
+        // (so it must have had focus already).
         m_lastOnSame = !simulateClick && ((col == oldCurrentCol) &&
-                        (current == oldCurrentRow)) && oldWasSelected;
+                        (current == oldCurrentRow)) && oldWasSelected &&
+                        HasFocus();
 
         // Call ActivateCell() after everything else as under GTK+
         if ( IsCellEditableInMode(item, col, wxDATAVIEW_CELL_ACTIVATABLE) )
@@ -4241,33 +4379,38 @@ void wxDataViewMainWindow::OnMouse( wxMouseEvent &event )
                               GetLineHeight( current ) );
 
             // Report position relative to the cell's custom area, i.e.
-            // no the entire space as given by the control but the one
+            // not the entire space as given by the control but the one
             // used by the renderer after calculation of alignment etc.
+            //
+            // Notice that this results in negative coordinates when clicking
+            // in the upper left corner of a centre-aligned cell which doesn't
+            // fill its column entirely so this is somewhat surprising, but we
+            // do it like this for compatibility with the native GTK+ version,
+            // see #12270.
 
             // adjust the rectangle ourselves to account for the alignment
+            int align = cell->GetAlignment();
+            if ( align == wxDVR_DEFAULT_ALIGNMENT )
+                align = wxALIGN_CENTRE;
+
             wxRect rectItem = cell_rect;
-            const int align = cell->GetAlignment();
-            if ( align != wxDVR_DEFAULT_ALIGNMENT )
+            const wxSize size = cell->GetSize();
+            if ( size.x >= 0 && size.x < cell_rect.width )
             {
-                const wxSize size = cell->GetSize();
-
-                if ( size.x >= 0 && size.x < cell_rect.width )
-                {
-                    if ( align & wxALIGN_CENTER_HORIZONTAL )
-                        rectItem.x += (cell_rect.width - size.x)/2;
-                    else if ( align & wxALIGN_RIGHT )
-                        rectItem.x += cell_rect.width - size.x;
-                    // else: wxALIGN_LEFT is the default
-                }
+                if ( align & wxALIGN_CENTER_HORIZONTAL )
+                    rectItem.x += (cell_rect.width - size.x)/2;
+                else if ( align & wxALIGN_RIGHT )
+                    rectItem.x += cell_rect.width - size.x;
+                // else: wxALIGN_LEFT is the default
+            }
 
-                if ( size.y >= 0 && size.y < cell_rect.height )
-                {
-                    if ( align & wxALIGN_CENTER_VERTICAL )
-                        rectItem.y += (cell_rect.height - size.y)/2;
-                    else if ( align & wxALIGN_BOTTOM )
-                        rectItem.y += cell_rect.height - size.y;
-                    // else: wxALIGN_TOP is the default
-                }
+            if ( size.y >= 0 && size.y < cell_rect.height )
+            {
+                if ( align & wxALIGN_CENTER_VERTICAL )
+                    rectItem.y += (cell_rect.height - size.y)/2;
+                else if ( align & wxALIGN_BOTTOM )
+                    rectItem.y += cell_rect.height - size.y;
+                // else: wxALIGN_TOP is the default
             }
 
             wxMouseEvent event2(event);
@@ -4355,6 +4498,7 @@ void wxDataViewCtrl::Init()
     m_sortingColumnIdx = wxNOT_FOUND;
 
     m_headerArea = NULL;
+    m_clientArea = NULL;
 
     m_colsDirty = false;
 }
@@ -4468,18 +4612,53 @@ void wxDataViewCtrl::SetFocus()
         m_clientArea->SetFocus();
 }
 
+bool wxDataViewCtrl::SetFont(const wxFont & font)
+{
+    if (!wxControl::SetFont(font))
+        return false;
+
+    if (m_headerArea)
+        m_headerArea->SetFont(font);
+
+    if (m_clientArea)
+    {
+        m_clientArea->SetFont(font);
+        m_clientArea->SetRowHeight(m_clientArea->GetDefaultRowHeight());
+    }
+
+    if (m_headerArea || m_clientArea)
+    {
+        InvalidateColBestWidths();
+        Layout();
+    }
+
+    return true;
+}
+
+
+
 bool wxDataViewCtrl::AssociateModel( wxDataViewModel *model )
 {
     if (!wxDataViewCtrlBase::AssociateModel( model ))
         return false;
 
-    m_notifier = new wxGenericDataViewModelNotifier( m_clientArea );
-
-    model->AddNotifier( m_notifier );
+    if (model)
+    {
+        m_notifier = new wxGenericDataViewModelNotifier( m_clientArea );
+        model->AddNotifier( m_notifier );
+    }
+    else if (m_notifier)
+    {
+        m_notifier->Cleared();
+        m_notifier = NULL;
+    }
 
     m_clientArea->DestroyTree();
 
-    m_clientArea->BuildTree(model);
+    if (model)
+    {
+        m_clientArea->BuildTree(model);
+    }
 
     m_clientArea->UpdateDisplay();
 
@@ -4506,7 +4685,7 @@ bool wxDataViewCtrl::AppendColumn( wxDataViewColumn *col )
         return false;
 
     m_cols.Append( col );
-    m_colsBestWidths.push_back(0);
+    m_colsBestWidths.push_back(CachedColWidthInfo());
     OnColumnsCountChanged();
     return true;
 }
@@ -4517,7 +4696,7 @@ bool wxDataViewCtrl::PrependColumn( wxDataViewColumn *col )
         return false;
 
     m_cols.Insert( col );
-    m_colsBestWidths.insert(m_colsBestWidths.begin(), 0);
+    m_colsBestWidths.insert(m_colsBestWidths.begin(), CachedColWidthInfo());
     OnColumnsCountChanged();
     return true;
 }
@@ -4528,7 +4707,7 @@ bool wxDataViewCtrl::InsertColumn( unsigned int pos, wxDataViewColumn *col )
         return false;
 
     m_cols.Insert( pos, col );
-    m_colsBestWidths.insert(m_colsBestWidths.begin() + pos, 0);
+    m_colsBestWidths.insert(m_colsBestWidths.begin() + pos, CachedColWidthInfo());
     OnColumnsCountChanged();
     return true;
 }
@@ -4603,8 +4782,8 @@ int wxDataViewCtrl::GetColumnIndex(const wxDataViewColumn *column) const
 
 unsigned int wxDataViewCtrl::GetBestColumnWidth(int idx) const
 {
-    if ( m_colsBestWidths[idx] != 0 )
-        return m_colsBestWidths[idx];
+    if ( m_colsBestWidths[idx].width != 0 )
+        return m_colsBestWidths[idx].width;
 
     const int count = m_clientArea->GetRowCount();
     wxDataViewColumn *column = GetColumn(idx);
@@ -4677,6 +4856,8 @@ unsigned int wxDataViewCtrl::GetBestColumnWidth(int idx) const
                                   GetModel(), column->GetModelColumn(),
                                   m_clientArea->GetRowHeight());
 
+    calculator.UpdateWithWidth(column->GetMinWidth());
+
     if ( m_headerArea )
         calculator.UpdateWithWidth(m_headerArea->GetColumnTitleWidth(*column));
 
@@ -4752,7 +4933,7 @@ unsigned int wxDataViewCtrl::GetBestColumnWidth(int idx) const
     if ( max_width > 0 )
         max_width += 2 * PADDING_RIGHTLEFT;
 
-    const_cast<wxDataViewCtrl*>(this)->m_colsBestWidths[idx] = max_width;
+    const_cast<wxDataViewCtrl*>(this)->m_colsBestWidths[idx].width = max_width;
     return max_width;
 }
 
@@ -4797,12 +4978,14 @@ bool wxDataViewCtrl::ClearColumns()
 
 void wxDataViewCtrl::InvalidateColBestWidth(int idx)
 {
-    m_colsBestWidths[idx] = 0;
+    m_colsBestWidths[idx].width = 0;
+    m_colsBestWidths[idx].dirty = true;
     m_colsDirty = true;
 }
 
 void wxDataViewCtrl::InvalidateColBestWidths()
 {
+    // mark all columns as dirty:
     m_colsBestWidths.clear();
     m_colsBestWidths.resize(m_cols.size());
     m_colsDirty = true;
@@ -4810,14 +4993,27 @@ void wxDataViewCtrl::InvalidateColBestWidths()
 
 void wxDataViewCtrl::UpdateColWidths()
 {
+    m_colsDirty = false;
+
     if ( !m_headerArea )
         return;
 
     const unsigned len = m_colsBestWidths.size();
     for ( unsigned i = 0; i < len; i++ )
     {
-        if ( m_colsBestWidths[i] == 0 )
+        // Note that we have to have an explicit 'dirty' flag here instead of
+        // checking if the width==0, as is done in GetBestColumnWidth().
+        //
+        // Testing width==0 wouldn't work correctly if some code called
+        // GetWidth() after col. width invalidation but before
+        // wxDataViewCtrl::UpdateColWidths() was called at idle time. This
+        // would result in the header's column width getting out of sync with
+        // the control itself.
+        if ( m_colsBestWidths[i].dirty )
+        {
             m_headerArea->UpdateColumn(i);
+            m_colsBestWidths[i].dirty = false;
+        }
     }
 }
 
@@ -4826,10 +5022,7 @@ void wxDataViewCtrl::OnInternalIdle()
     wxDataViewCtrlBase::OnInternalIdle();
 
     if ( m_colsDirty )
-    {
-        m_colsDirty = false;
         UpdateColWidths();
-    }
 }
 
 int wxDataViewCtrl::GetColumnPosition( const wxDataViewColumn *column ) const
@@ -5039,14 +5232,20 @@ void wxDataViewCtrl::Expand( const wxDataViewItem & item )
 
     int row = m_clientArea->GetRowByItem( item );
     if (row != -1)
+    {
         m_clientArea->Expand(row);
+        InvalidateColBestWidths();
+    }
 }
 
 void wxDataViewCtrl::Collapse( const wxDataViewItem & item )
 {
     int row = m_clientArea->GetRowByItem( item );
     if (row != -1)
+    {
         m_clientArea->Collapse(row);
+        InvalidateColBestWidths();
+    }
 }
 
 bool wxDataViewCtrl::IsExpanded( const wxDataViewItem & item ) const