]> git.saurik.com Git - wxWidgets.git/blobdiff - src/generic/treelist.cpp
Add wxAnyScrollHelperBase to reduce code duplication in wxVarScrollHelperBase.
[wxWidgets.git] / src / generic / treelist.cpp
index 21a490552984549119c0fa5730009fa0de0c2922..4d249c2cb50f2808110e3c2f06fe6f6a8ca58d00 100644 (file)
@@ -3,7 +3,6 @@
 // Purpose:     Generic wxTreeListCtrl implementation.
 // Author:      Vadim Zeitlin
 // Created:     2011-08-19
-// RCS-ID:      $Id: wxhead.cpp,v 1.11 2010-04-22 12:44:51 zeitlin Exp $
 // Copyright:   (c) 2011 Vadim Zeitlin <vadim@wxwidgets.org>
 // Licence:     wxWindows licence
 ///////////////////////////////////////////////////////////////////////////////
@@ -23,6 +22,8 @@
     #pragma hdrstop
 #endif
 
+#if wxUSE_TREELISTCTRL
+
 #ifndef WX_PRECOMP
     #include "wx/dc.h"
 #endif // WX_PRECOMP
@@ -160,6 +161,37 @@ public:
         }
     }
 
+    void OnDeleteColumn(unsigned col, unsigned numColumns)
+    {
+        wxASSERT_MSG( col, "Shouldn't be called for the first column" );
+
+        if ( !m_columnsTexts )
+            return;
+
+        wxScopedArray<wxString> oldTexts(m_columnsTexts);
+        m_columnsTexts = new wxString[numColumns - 2];
+        for ( unsigned n = 1, m = 1; n < numColumns - 1; n++, m++ )
+        {
+            if ( n == col )
+            {
+                n--;
+            }
+            else // Not the deleted column.
+            {
+                m_columnsTexts[n - 1] = oldTexts[m - 1];
+            }
+        }
+    }
+
+    void OnClearColumns()
+    {
+        if ( m_columnsTexts )
+        {
+            delete [] m_columnsTexts;
+            m_columnsTexts = NULL;
+        }
+    }
+
 
     // Functions for modifying the tree.
 
@@ -306,6 +338,8 @@ public:
 
     // Methods called by wxTreeListCtrl.
     void InsertColumn(unsigned col);
+    void DeleteColumn(unsigned col);
+    void ClearColumns();
 
     Node* InsertItem(Node* parent,
                      Node* previous,
@@ -342,6 +376,11 @@ public:
     virtual bool HasContainerColumns(const wxDataViewItem& item) const;
     virtual unsigned GetChildren(const wxDataViewItem& item,
                                  wxDataViewItemArray& children) const;
+    virtual bool IsListModel() const { return m_isFlat; }
+    virtual int Compare(const wxDataViewItem& item1,
+                        const wxDataViewItem& item2,
+                        unsigned col,
+                        bool ascending) const;
 
 private:
     // The control we're associated with.
@@ -352,6 +391,11 @@ private:
 
     // Number of columns we maintain.
     unsigned m_numColumns;
+
+    // Set to false as soon as we have more than one level, i.e. as soon as any
+    // items with non-root item as parent are added (and currently never reset
+    // after this).
+    bool m_isFlat;
 };
 
 // ============================================================================
@@ -504,23 +548,17 @@ public:
     }
 
     // Event handlers toggling the items checkbox if it was clicked.
-    virtual bool Activate(const wxRect& WXUNUSED(cell),
-                          wxDataViewModel* model,
-                          const wxDataViewItem& item,
-                          unsigned int WXUNUSED(col))
-    {
-        static_cast<wxTreeListModel*>(model)->ToggleItem(item);
-        return true;
-    }
-
-    virtual bool LeftClick(const wxPoint& pos,
-                           const wxRect& WXUNUSED(cell),
-                           wxDataViewModel* model,
-                           const wxDataViewItem& item,
-                           unsigned int WXUNUSED(col))
+    virtual bool ActivateCell(const wxRect& WXUNUSED(cell),
+                              wxDataViewModel *model,
+                              const wxDataViewItem & item,
+                              unsigned int WXUNUSED(col),
+                              const wxMouseEvent *mouseEvent)
     {
-        if ( !wxRect(GetCheckSize()).Contains(pos) )
-            return false;
+        if ( mouseEvent )
+        {
+            if ( !wxRect(GetCheckSize()).Contains(mouseEvent->GetPosition()) )
+                return false;
+        }
 
         static_cast<wxTreeListModel*>(model)->ToggleItem(item);
         return true;
@@ -554,6 +592,7 @@ wxTreeListModel::wxTreeListModel(wxTreeListCtrl* treelist)
       m_root(new Node(NULL))
 {
     m_numColumns = 0;
+    m_isFlat = true;
 }
 
 wxTreeListModel::~wxTreeListModel()
@@ -576,6 +615,32 @@ void wxTreeListModel::InsertColumn(unsigned col)
     }
 }
 
+void wxTreeListModel::DeleteColumn(unsigned col)
+{
+    wxCHECK_RET( col < m_numColumns, "Invalid column index" );
+
+    // Update all the items to remove the text for the non first columns.
+    if ( col > 0 )
+    {
+        for ( Node* node = m_root->GetChild(); node; node = node->NextInTree() )
+        {
+            node->OnDeleteColumn(col, m_numColumns);
+        }
+    }
+
+    m_numColumns--;
+}
+
+void wxTreeListModel::ClearColumns()
+{
+    m_numColumns = 0;
+
+    for ( Node* node = m_root->GetChild(); node; node = node->NextInTree() )
+    {
+        node->OnClearColumns();
+    }
+}
+
 wxTreeListModelNode*
 wxTreeListModel::InsertItem(Node* parent,
                             Node* previous,
@@ -590,19 +655,29 @@ wxTreeListModel::InsertItem(Node* parent,
     wxCHECK_MSG( previous, NULL,
                  "Must have a valid previous item (maybe wxTLI_FIRST/LAST?)" );
 
+    if ( m_isFlat && parent != m_root )
+    {
+        // Not flat any more, this is a second level child.
+        m_isFlat = false;
+    }
+
     wxScopedPtr<Node>
         newItem(new Node(parent, text, imageClosed, imageOpened, data));
 
+    // FIXME-VC6: This compiler refuses to compare "Node* previous" with
+    //            wxTLI_XXX without some help.
+    const wxTreeListItem previousItem(previous);
+
     // If we have no children at all, then inserting as last child is the same
     // as inserting as the first one so check for it here too.
-    if ( previous == wxTLI_FIRST ||
-            (previous == wxTLI_LAST && !parent->GetChild()) )
+    if ( previousItem == wxTLI_FIRST ||
+            (previousItem == wxTLI_LAST && !parent->GetChild()) )
     {
         parent->InsertChild(newItem.get());
     }
     else // Not the first item, find the previous one.
     {
-        if ( previous == wxTLI_LAST )
+        if ( previousItem == wxTLI_LAST )
         {
             previous = parent->GetChild();
 
@@ -682,7 +757,12 @@ const wxString& wxTreeListModel::GetItemText(Node* item, unsigned col) const
     // empty string we can return reference to.
     wxCHECK_MSG( item, m_root->m_text, "Invalid item" );
 
-    return col == 0 ? item->m_text : item->GetColumnText(col);
+    // Notice that asking for the text of a column of an item that doesn't have
+    // any column texts is not an error so we simply return an empty string in
+    // this case.
+    return col == 0 ? item->m_text
+                    : item->HasColumnsTexts() ? item->GetColumnText(col)
+                                              : m_root->m_text;
 }
 
 void wxTreeListModel::SetItemText(Node* item, unsigned col, const wxString& text)
@@ -879,6 +959,27 @@ wxTreeListModel::GetChildren(const wxDataViewItem& item,
     return numChildren;
 }
 
+int
+wxTreeListModel::Compare(const wxDataViewItem& item1,
+                         const wxDataViewItem& item2,
+                         unsigned col,
+                         bool ascending) const
+{
+    // Compare using default alphabetical order if no custom comparator.
+    wxTreeListItemComparator* const comp = m_treelist->m_comparator;
+    if ( !comp )
+        return wxDataViewModel::Compare(item1, item2, col, ascending);
+
+    // Forward comparison to the comparator:
+    int result = comp->Compare(m_treelist, col, FromDVI(item1), FromDVI(item2));
+
+    // And adjust by the sort order if necessary.
+    if ( !ascending )
+        result = -result;
+
+    return result;
+}
+
 // ============================================================================
 // wxTreeListCtrl implementation
 // ============================================================================
@@ -889,6 +990,7 @@ BEGIN_EVENT_TABLE(wxTreeListCtrl, wxWindow)
     EVT_DATAVIEW_ITEM_EXPANDED(wxID_ANY, wxTreeListCtrl::OnItemExpanded)
     EVT_DATAVIEW_ITEM_ACTIVATED(wxID_ANY, wxTreeListCtrl::OnItemActivated)
     EVT_DATAVIEW_ITEM_CONTEXT_MENU(wxID_ANY, wxTreeListCtrl::OnItemContextMenu)
+    EVT_DATAVIEW_COLUMN_SORTED(wxID_ANY, wxTreeListCtrl::OnColumnSorted)
 
     EVT_SIZE(wxTreeListCtrl::OnSize)
 END_EVENT_TABLE()
@@ -901,6 +1003,7 @@ void wxTreeListCtrl::Init()
 {
     m_view = NULL;
     m_model = NULL;
+    m_comparator = NULL;
 }
 
 bool wxTreeListCtrl::Create(wxWindow* parent,
@@ -925,10 +1028,14 @@ bool wxTreeListCtrl::Create(wxWindow* parent,
     }
 
     m_view = new wxDataViewCtrl;
+    long styleDataView = HasFlag(wxTL_MULTIPLE) ? wxDV_MULTIPLE
+                                                : wxDV_SINGLE;
+    if ( HasFlag(wxTL_NO_HEADER) )
+        styleDataView |= wxDV_NO_HEADER;
+
     if ( !m_view->Create(this, wxID_ANY,
                          wxPoint(0, 0), GetClientSize(),
-                         HasFlag(wxTL_MULTIPLE) ? wxDV_MULTIPLE
-                                                : wxDV_SINGLE) )
+                         styleDataView) )
     {
         delete m_view;
         m_view = NULL;
@@ -950,6 +1057,13 @@ wxTreeListCtrl::~wxTreeListCtrl()
         m_model->DecRef();
 }
 
+wxWindowList wxTreeListCtrl::GetCompositeWindowParts() const
+{
+    wxWindowList parts;
+    parts.push_back(m_view);
+    return parts;
+}
+
 // ----------------------------------------------------------------------------
 // Columns
 // ----------------------------------------------------------------------------
@@ -1013,13 +1127,24 @@ bool wxTreeListCtrl::DeleteColumn(unsigned col)
 {
     wxCHECK_MSG( col < GetColumnCount(), false, "Invalid column index" );
 
-    return m_view->DeleteColumn(m_view->GetColumn(col));
+    if ( !m_view->DeleteColumn(m_view->GetColumn(col)) )
+        return false;
+
+    m_model->DeleteColumn(col);
+
+    return true;
 }
 
 void wxTreeListCtrl::ClearColumns()
 {
-    if ( m_view )
-        m_view->ClearColumns();
+    // Don't assert here, clearing columns of the control before it's created
+    // can be considered valid (just useless).
+    if ( !m_model )
+        return;
+
+    m_view->ClearColumns();
+
+    m_model->ClearColumns();
 }
 
 void wxTreeListCtrl::SetColumnWidth(unsigned col, int width)
@@ -1029,7 +1154,7 @@ void wxTreeListCtrl::SetColumnWidth(unsigned col, int width)
     wxDataViewColumn* const column = m_view->GetColumn(col);
     wxCHECK_RET( column, "No such column?" );
 
-    return column->SetWidth(width);
+    column->SetWidth(width);
 }
 
 int wxTreeListCtrl::GetColumnWidth(unsigned col) const
@@ -1362,11 +1487,48 @@ wxTreeListCtrl::AreAllChildrenInState(wxTreeListItem item,
     return true;
 }
 
+// ----------------------------------------------------------------------------
+// Sorting
+// ----------------------------------------------------------------------------
+
+void wxTreeListCtrl::SetSortColumn(unsigned col, bool ascendingOrder)
+{
+    wxCHECK_RET( col < m_view->GetColumnCount(), "Invalid column index" );
+
+    m_view->GetColumn(col)->SetSortOrder(ascendingOrder);
+}
+
+bool wxTreeListCtrl::GetSortColumn(unsigned* col, bool* ascendingOrder)
+{
+    const unsigned numColumns = m_view->GetColumnCount();
+    for ( unsigned n = 0; n < numColumns; n++ )
+    {
+        wxDataViewColumn* const column = m_view->GetColumn(n);
+        if ( column->IsSortKey() )
+        {
+            if ( col )
+                *col = n;
+
+            if ( ascendingOrder )
+                *ascendingOrder = column->IsSortOrderAscending();
+
+            return true;
+        }
+    }
+
+    return false;
+}
+
+void wxTreeListCtrl::SetItemComparator(wxTreeListItemComparator* comparator)
+{
+    m_comparator = comparator;
+}
+
 // ----------------------------------------------------------------------------
 // Events
 // ----------------------------------------------------------------------------
 
-void wxTreeListCtrl::SendEvent(wxEventType evt, wxDataViewEvent& eventDV)
+void wxTreeListCtrl::SendItemEvent(wxEventType evt, wxDataViewEvent& eventDV)
 {
     wxTreeListEvent eventTL(evt, this, m_model->FromDVI(eventDV.GetItem()));
 
@@ -1382,10 +1544,27 @@ void wxTreeListCtrl::SendEvent(wxEventType evt, wxDataViewEvent& eventDV)
     }
 }
 
+void wxTreeListCtrl::SendColumnEvent(wxEventType evt, wxDataViewEvent& eventDV)
+{
+    wxTreeListEvent eventTL(evt, this, wxTreeListItem());
+    eventTL.SetColumn(eventDV.GetColumn());
+
+    if ( !ProcessWindowEvent(eventTL) )
+    {
+        eventDV.Skip();
+        return;
+    }
+
+    if ( !eventTL.IsAllowed() )
+    {
+        eventDV.Veto();
+    }
+}
+
 void
 wxTreeListCtrl::OnItemToggled(wxTreeListItem item, wxCheckBoxState stateOld)
 {
-    wxTreeListEvent event(wxEVT_COMMAND_TREELIST_ITEM_CHECKED, this, item);
+    wxTreeListEvent event(wxEVT_TREELIST_ITEM_CHECKED, this, item);
     event.SetOldCheckedState(stateOld);
 
     ProcessWindowEvent(event);
@@ -1393,27 +1572,32 @@ wxTreeListCtrl::OnItemToggled(wxTreeListItem item, wxCheckBoxState stateOld)
 
 void wxTreeListCtrl::OnSelectionChanged(wxDataViewEvent& event)
 {
-    SendEvent(wxEVT_COMMAND_TREELIST_SELECTION_CHANGED, event);
+    SendItemEvent(wxEVT_TREELIST_SELECTION_CHANGED, event);
 }
 
 void wxTreeListCtrl::OnItemExpanding(wxDataViewEvent& event)
 {
-    SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDING, event);
+    SendItemEvent(wxEVT_TREELIST_ITEM_EXPANDING, event);
 }
 
 void wxTreeListCtrl::OnItemExpanded(wxDataViewEvent& event)
 {
-    SendEvent(wxEVT_COMMAND_TREELIST_ITEM_EXPANDED, event);
+    SendItemEvent(wxEVT_TREELIST_ITEM_EXPANDED, event);
 }
 
 void wxTreeListCtrl::OnItemActivated(wxDataViewEvent& event)
 {
-    SendEvent(wxEVT_COMMAND_TREELIST_ITEM_ACTIVATED, event);
+    SendItemEvent(wxEVT_TREELIST_ITEM_ACTIVATED, event);
 }
 
 void wxTreeListCtrl::OnItemContextMenu(wxDataViewEvent& event)
 {
-    SendEvent(wxEVT_COMMAND_TREELIST_ITEM_CONTEXT_MENU, event);
+    SendItemEvent(wxEVT_TREELIST_ITEM_CONTEXT_MENU, event);
+}
+
+void wxTreeListCtrl::OnColumnSorted(wxDataViewEvent& event)
+{
+    SendColumnEvent(wxEVT_TREELIST_COLUMN_SORTED, event);
 }
 
 // ----------------------------------------------------------------------------
@@ -1430,40 +1614,58 @@ void wxTreeListCtrl::OnSize(wxSizeEvent& event)
         const wxRect rect = GetClientRect();
         m_view->SetSize(rect);
 
-        // Resize the first column to take the remaining available space, if
-        // any.
+#ifdef wxHAS_GENERIC_DATAVIEWCTRL
+        // The generic implementation doesn't refresh itself immediately which
+        // is annoying during "live resizing", so do it forcefully here to
+        // ensure that the items are re-laid out and the focus rectangle is
+        // redrawn correctly (instead of leaving traces) while our size is
+        // being changed.
+        wxWindow* const view = GetView();
+        view->Refresh();
+        view->Update();
+#endif // wxHAS_GENERIC_DATAVIEWCTRL
+
+        // Resize the first column to take the remaining available space.
         const unsigned numColumns = GetColumnCount();
         if ( !numColumns )
             return;
 
         // There is a bug in generic wxDataViewCtrl: if the column width sums
         // up to the total size, horizontal scrollbar (unnecessarily) appears,
-        // so subtract 10 pixels to ensure this doesn't happen.
-        int remainingWidth = rect.width - 10;
+        // so subtract a bit to ensure this doesn't happen.
+        int remainingWidth = rect.width - 5;
         for ( unsigned n = 1; n < GetColumnCount(); n++ )
         {
             remainingWidth -= GetColumnWidth(n);
-            if ( remainingWidth < 0 )
-                break;
+            if ( remainingWidth <= 0 )
+            {
+                // There is not enough space, as we're not going to give the
+                // first column negative width anyhow, just don't do anything.
+                return;
+            }
         }
 
-        // We don't decrease the width of the first column, even if we had
-        // increased it ourselves, because we want to avoid changing its size
-        // if the user resized it. We might want to remember if this was the
-        // case or if we only ever adjusted it automatically in the future.
-        if ( remainingWidth > GetColumnWidth(0) )
-            SetColumnWidth(0, remainingWidth);
+        SetColumnWidth(0, remainingWidth);
     }
 }
 
+wxWindow* wxTreeListCtrl::GetView() const
+{
+#ifdef wxHAS_GENERIC_DATAVIEWCTRL
+    return m_view->GetMainWindow();
+#else
+    return m_view;
+#endif
+}
+
 // ============================================================================
 // wxTreeListEvent implementation
 // ============================================================================
 
-wxIMPLEMENT_ABSTRACT_CLASS(wxTreeListEvent, wxNotifyEvent)
+wxIMPLEMENT_DYNAMIC_CLASS(wxTreeListEvent, wxNotifyEvent)
 
 #define wxDEFINE_TREELIST_EVENT(name) \
-    wxDEFINE_EVENT(wxEVT_COMMAND_TREELIST_##name, wxTreeListEvent)
+    wxDEFINE_EVENT(wxEVT_TREELIST_##name, wxTreeListEvent)
 
 wxDEFINE_TREELIST_EVENT(SELECTION_CHANGED);
 wxDEFINE_TREELIST_EVENT(ITEM_EXPANDING);
@@ -1471,5 +1673,8 @@ wxDEFINE_TREELIST_EVENT(ITEM_EXPANDED);
 wxDEFINE_TREELIST_EVENT(ITEM_CHECKED);
 wxDEFINE_TREELIST_EVENT(ITEM_ACTIVATED);
 wxDEFINE_TREELIST_EVENT(ITEM_CONTEXT_MENU);
+wxDEFINE_TREELIST_EVENT(COLUMN_SORTED);
 
 #undef wxDEFINE_TREELIST_EVENT
+
+#endif // wxUSE_TREELISTCTRL