// 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
/////////////////////////////////////////////////////////////////////////////
#include "wx/headerctrl.h"
#include "wx/dnd.h"
#include "wx/stopwatch.h"
+#include "wx/weakref.h"
//-----------------------------------------------------------------------------
// classes
// 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;
// ----------------------------------------------------------------------------
}
}
+void wxDataViewColumn::UnsetAsSortKey()
+{
+ m_sort = false;
+
+ if ( m_owner )
+ m_owner->SetSortingColumnIndex(wxNOT_FOUND);
+
+ UpdateDisplay();
+}
+
void wxDataViewColumn::SetSortOrder(bool ascending)
{
if ( !m_owner )
{
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
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();
}
static wxDataViewTreeNode* CreateRootNode()
{
wxDataViewTreeNode *n = new wxDataViewTreeNode(NULL, wxDataViewItem());
- n->SetHasChildren(true);
+ n->m_branchData = new BranchNodeData;
n->m_branchData->open = true;
return n;
}
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;
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);
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();
}
wxBitmap CreateItemBitmap( unsigned int row, int &indent );
#endif // wxUSE_DRAG_AND_DROP
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 );
void ChangeCurrentRow( unsigned int row );
bool TryAdvanceCurrentColumn(wxDataViewTreeNode *node, bool forward);
+ wxDataViewColumn *GetCurrentColumn() const { return m_currentCol; }
+ void ClearCurrentColumn() { m_currentCol = NULL; }
+
bool IsSingleSel() const { return !GetParent()->HasFlag(wxDV_MULTIPLE); }
bool IsEmpty() { return GetRowCount() == 0; }
// 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 )
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;
void OnColumnsCountChanged();
+ // Called by wxDataViewCtrl and our own OnRenameTimer() to start edit the
+ // specified item in the given column.
+ 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);
wxDataViewColumn *FindColumnForEditing(const wxDataViewItem& item, wxDataViewCellMode mode);
+ bool IsCellEditableInMode(const wxDataViewItem& item, const wxDataViewColumn *col, wxDataViewCellMode mode) const;
+
+ void DrawCellBackground( wxDataViewRenderer* cell, wxDC& dc, const wxRect& rect );
+
private:
wxDataViewCtrl *m_owner;
int m_lineHeight;
// This is the tree node under the cursor
wxDataViewTreeNode * m_underMouse;
+ // The control used for editing or NULL.
+ wxWeakRef<wxWindow> m_editorCtrl;
+
+ // Id m_editorCtrl is non-NULL, pointer to the associated renderer.
+ wxDataViewRenderer* m_editorRenderer;
+
private:
DECLARE_DYNAMIC_CLASS(wxDataViewMainWindow)
DECLARE_EVENT_TABLE()
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(),
return true;
}
-bool wxDataViewToggleRenderer::WXActivateCell(const wxRect& WXUNUSED(cell),
+bool wxDataViewToggleRenderer::WXActivateCell(const wxRect& WXUNUSED(cellRect),
wxDataViewModel *model,
const wxDataViewItem& item,
unsigned int col,
const wxMouseEvent *mouseEvent)
{
- if ( !model->IsEnabled(item, col) )
- return false;
-
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;
}
{
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;
}
EVT_MOUSE_EVENTS (wxDataViewMainWindow::OnMouse)
EVT_SET_FOCUS (wxDataViewMainWindow::OnSetFocus)
EVT_KILL_FOCUS (wxDataViewMainWindow::OnKillFocus)
+ EVT_CHAR_HOOK (wxDataViewMainWindow::OnCharHook)
EVT_CHAR (wxDataViewMainWindow::OnChar)
END_EVENT_TABLE()
{
SetOwner( parent );
+ m_editorRenderer = NULL;
+
m_lastOnSame = false;
m_renameTimer = new wxDataViewRenameTimer( this );
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;
}
+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 )
{
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();
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 );
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;
wxDataViewModel *model = GetModel();
wxAutoBufferedPaintDC dc( this );
-#ifdef __WXMSW__
dc.SetBrush(GetOwner()->GetBackgroundColour());
dc.SetPen( *wxTRANSPARENT_PEN );
dc.DrawRectangle(GetClientSize());
-#endif
if ( IsEmpty() )
{
// 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);
x_last += col->GetWidth();
}
+ // Draw background of alternate rows specially if required
+ if ( m_owner->HasFlag(wxDV_ROW_LINES) )
+ {
+ wxColour altRowColour = m_owner->m_alternateRowColour;
+ if ( !altRowColour.IsOk() )
+ {
+ // Determine the alternate rows colour automatically from the
+ // background colour.
+ const wxColour bgColour = m_owner->GetBackgroundColour();
+
+ // Depending on the background, alternate row color
+ // will be 3% more dark or 50% brighter.
+ int alpha = bgColour.GetRGB() > 0x808080 ? 97 : 150;
+ altRowColour = bgColour.ChangeLightness(alpha);
+ }
+
+ dc.SetPen(*wxTRANSPARENT_PEN);
+ dc.SetBrush(wxBrush(altRowColour));
+
+ for (unsigned int item = item_start; item < item_last; item++)
+ {
+ if ( item % 2 )
+ {
+ dc.DrawRectangle(x_start,
+ GetLineStart(item),
+ GetClientSize().GetWidth(),
+ GetLineHeight(item));
+ }
+ }
+ }
+
// Draw horizontal rules if required
if ( m_owner->HasFlag(wxDV_HORIZ_RULES) )
{
cell_rect.y = GetLineStart( item );
cell_rect.height = GetLineHeight( item );
+ // draw the background
+ bool selected = m_selection.Index( item ) != wxNOT_FOUND;
+ if ( !selected )
+ DrawCellBackground( cell, dc, cell_rect );
+
// deal with the expander
int indent = 0;
if ((!IsList()) && (col == expander))
continue;
int state = 0;
- if (m_hasFocus && (m_selection.Index(item) != wxNOT_FOUND))
+ if (m_hasFocus && selected)
state |= wxDATAVIEW_CELL_SELECTED;
// TODO: it would be much more efficient to create a clipping
}
}
+
+void wxDataViewMainWindow::DrawCellBackground( wxDataViewRenderer* cell, wxDC& dc, const wxRect& rect )
+{
+ wxRect rectBg( rect );
+
+ // don't overlap the horizontal rules
+ if ( m_owner->HasFlag(wxDV_HORIZ_RULES) )
+ {
+ rectBg.x++;
+ rectBg.width--;
+ }
+
+ // don't overlap the vertical rules
+ if ( m_owner->HasFlag(wxDV_VERT_RULES) )
+ {
+ rectBg.y++;
+ rectBg.height--;
+ }
+
+ cell->RenderBackground(&dc, rectBg);
+}
+
void wxDataViewMainWindow::OnRenameTimer()
{
// We have to call this here because changes may just have
wxDataViewItem item = GetItemByRow( m_currentRow );
- wxRect labelRect = GetItemRect(item, m_currentCol);
+ StartEditing( item, m_currentCol );
+}
- m_currentCol->GetRenderer()->StartEditing( item, labelRect );
+void
+wxDataViewMainWindow::StartEditing(const wxDataViewItem& item,
+ const wxDataViewColumn* col)
+{
+ wxDataViewRenderer* renderer = col->GetRenderer();
+ if ( !IsCellEditableInMode(item, col, wxDATAVIEW_CELL_EDITABLE) )
+ return;
+
+ const wxRect itemRect = GetItemRect(item, col);
+ if ( renderer->StartEditing(item, itemRect) )
+ {
+ // 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;
+ m_editorCtrl = renderer->GetEditorCtrl();
+ }
}
//-----------------------------------------------------------------------------
// 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() )
}
// 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();
// 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);
// 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);
{
DestroyTree();
m_selection.Clear();
+ m_currentRow = (unsigned)-1;
- SortPrepare();
- BuildTree( GetModel() );
+ if (GetModel())
+ {
+ SortPrepare();
+ BuildTree( GetModel() );
+ }
+ else
+ {
+ m_count = 0;
+ }
GetOwner()->InvalidateColBestWidths();
UpdateDisplay();
int x, y;
m_owner->GetScrollPixelsPerUnit( &x, &y );
int sy = GetLineStart( rows )/y;
- int sx = 0;
+ int sx = -1;
if( column != -1 )
{
wxRect rect = GetClientRect();
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;
}
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());
{
wxASSERT( !IsVirtualList() );
+ if ( row == (unsigned)-1 )
+ return NULL;
+
RowToTreeNodeJob job( row , -2, m_root );
Walker( m_root , job );
return job.GetResult();
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
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)
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;
m_count = -1;
UpdateDisplay();
- SendExpanderEvent(wxEVT_COMMAND_DATAVIEW_ITEM_COLLAPSED,node->GetItem());
+ SendExpanderEvent(wxEVT_DATAVIEW_ITEM_COLLAPSED,node->GetItem());
}
}
return itemRect;
}
-int wxDataViewMainWindow::RecalculateCount()
+int wxDataViewMainWindow::RecalculateCount() const
{
if (IsVirtualList())
{
wxDataViewColumn *candidate = m_currentCol;
if ( candidate &&
- candidate->GetRenderer()->GetMode() != mode &&
+ !IsCellEditableInMode(item, candidate, mode) &&
!m_currentColSetByKeyboard )
{
// If current column was set by mouse to something not editable (in
if ( c->IsHidden() )
continue;
- if ( c->GetRenderer()->GetMode() == mode )
+ if ( IsCellEditableInMode(item, c, mode) )
{
candidate = c;
break;
if ( !candidate )
return NULL;
- if ( candidate->GetRenderer()->GetMode() != mode )
+ if ( !IsCellEditableInMode(item, candidate, mode) )
return NULL;
return candidate;
}
+bool wxDataViewMainWindow::IsCellEditableInMode(const wxDataViewItem& item,
+ const wxDataViewColumn *col,
+ wxDataViewCellMode mode) const
+{
+ if ( col->GetRenderer()->GetMode() != mode )
+ return false;
+
+ if ( !GetModel()->IsEnabled(item, col->GetModelColumn()) )
+ return false;
+
+ return true;
+}
+
+void wxDataViewMainWindow::OnCharHook(wxKeyEvent& event)
+{
+ if ( m_editorCtrl )
+ {
+ // Handle any keys special for the in-place editor and return without
+ // calling Skip() below.
+ switch ( event.GetKeyCode() )
+ {
+ case WXK_ESCAPE:
+ m_editorRenderer->CancelEditing();
+ return;
+
+ case WXK_RETURN:
+ m_editorRenderer->FinishEditing();
+ return;
+ }
+ }
+
+ event.Skip();
+}
+
void wxDataViewMainWindow::OnChar( wxKeyEvent &event )
{
wxWindow * const parent = GetParent();
switch ( event.GetKeyCode() )
{
case WXK_RETURN:
+ 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);
}
case WXK_SPACE:
+ 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
}
case WXK_F2:
+ if ( event.HasModifiers() )
+ {
+ event.Skip();
+ break;
+ }
+ else
{
if( !m_selection.empty() )
{
wxDataViewColumn *editableCol = FindColumnForEditing(item, wxDATAVIEW_CELL_EDITABLE);
if ( editableCol )
- GetOwner()->StartEditor(item, GetOwner()->GetColumnIndex(editableCol));
+ GetOwner()->EditItem(item, editableCol);
}
}
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:
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:
}
}
-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
else
{
wxDataViewTreeNode* node = GetTreeNodeByRow(m_currentRow);
+ if ( !node )
+ return;
if ( TryAdvanceCurrentColumn(node, /*forward=*/false) )
return;
else
{
wxDataViewTreeNode* node = GetTreeNodeByRow(m_currentRow);
+ if ( !node )
+ return;
if ( node->HasChildren() )
{
if ( idx >= (int)GetOwner()->GetColumnCount() )
return false;
+ GetOwner()->EnsureVisible(m_currentRow, idx);
+
if ( idx < 1 )
{
// We are going to the left of the second column. Reset to whole-row
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();
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);
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());
}
}
-#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())
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 );
if (m_lastOnSame && !ignore_other_columns)
{
if ((col == m_currentCol) && (current == m_currentRow) &&
- (cell->GetMode() & wxDATAVIEW_CELL_EDITABLE) )
+ IsCellEditableInMode(item, col, wxDATAVIEW_CELL_EDITABLE) )
{
m_renameTimer->Start( 100, true );
}
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 (cell->GetMode() & wxDATAVIEW_CELL_ACTIVATABLE)
+ if ( IsCellEditableInMode(item, col, wxDATAVIEW_CELL_ACTIVATABLE) )
{
// notify cell about click
cell->PrepareForItem(model, item, col->GetModelColumn());
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);
m_sortingColumnIdx = wxNOT_FOUND;
m_headerArea = NULL;
+ m_clientArea = NULL;
m_colsDirty = false;
}
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();
return false;
m_cols.Append( col );
- m_colsBestWidths.push_back(0);
+ m_colsBestWidths.push_back(CachedColWidthInfo());
OnColumnsCountChanged();
return true;
}
return false;
m_cols.Insert( col );
- m_colsBestWidths.insert(m_colsBestWidths.begin(), 0);
+ m_colsBestWidths.insert(m_colsBestWidths.begin(), CachedColWidthInfo());
OnColumnsCountChanged();
return true;
}
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;
}
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);
GetModel(), column->GetModelColumn(),
m_clientArea->GetRowHeight());
+ calculator.UpdateWithWidth(column->GetMinWidth());
+
if ( m_headerArea )
- {
- int header_width = m_headerArea->GetTextExtent(column->GetTitle()).x;
- // Labels on native MSW header are indented on both sides
- header_width +=
- wxRendererNative::Get().GetHeaderButtonMargin(m_headerArea);
- calculator.UpdateWithWidth(header_width);
- }
+ calculator.UpdateWithWidth(m_headerArea->GetColumnTitleWidth(*column));
// The code below deserves some explanation. For very large controls, we
// simply can't afford to calculate sizes for all items, it takes too
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;
}
m_colsBestWidths.erase(m_colsBestWidths.begin() + GetColumnIndex(column));
m_cols.Erase(ret);
+
+ if ( m_clientArea->GetCurrentColumn() == column )
+ m_clientArea->ClearCurrentColumn();
+
OnColumnsCountChanged();
return true;
SetExpanderColumn(NULL);
m_cols.Clear();
m_colsBestWidths.clear();
+
+ m_clientArea->ClearCurrentColumn();
+
OnColumnsCountChanged();
+
return true;
}
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;
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;
+ }
}
}
wxDataViewCtrlBase::OnInternalIdle();
if ( m_colsDirty )
- {
- m_colsDirty = false;
UpdateColWidths();
- }
}
int wxDataViewCtrl::GetColumnPosition( const wxDataViewColumn *column ) const
{
-#if 1
unsigned int len = GetColumnCount();
for ( unsigned int i = 0; i < len; i++ )
{
}
return wxNOT_FOUND;
-#else
- // This returns the position in pixels which is not what we want.
- int ret = 0,
- dummy = 0;
- unsigned int len = GetColumnCount();
- for ( unsigned int i = 0; i < len; i++ )
- {
- wxDataViewColumn * col = GetColumnAt(i);
- if (col->IsHidden())
- continue;
- ret += col->GetWidth();
- if (column==col)
- {
- CalcScrolledPosition( ret, dummy, &ret, &dummy );
- break;
- }
- }
- return ret;
-#endif
}
wxDataViewColumn *wxDataViewCtrl::GetSortingColumn() const
}
}
+wxDataViewColumn *wxDataViewCtrl::GetCurrentColumn() const
+{
+ return m_clientArea->GetCurrentColumn();
+}
+
int wxDataViewCtrl::GetSelectedItemsCount() const
{
return m_clientArea->GetSelections().size();
return false;
}
+void wxDataViewCtrl::SetAlternateRowColour(const wxColour& colour)
+{
+ m_alternateRowColour = colour;
+}
+
void wxDataViewCtrl::SelectAll()
{
m_clientArea->SelectAllRows(true);
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
return false;
}
-void wxDataViewCtrl::StartEditor( const wxDataViewItem & item, unsigned int column )
+void wxDataViewCtrl::EditItem(const wxDataViewItem& item, const wxDataViewColumn *column)
{
- wxDataViewColumn* col = GetColumn( column );
- if (!col)
- return;
+ wxCHECK_RET( item.IsOk(), "invalid item" );
+ wxCHECK_RET( column, "no column provided" );
- wxDataViewRenderer* renderer = col->GetRenderer();
- if (renderer->GetMode() != wxDATAVIEW_CELL_EDITABLE)
- return;
-
- const wxRect itemRect = GetItemRect(item, col);
- renderer->StartEditing(item, itemRect);
+ m_clientArea->StartEditing(item, column);
}
#endif // !wxUSE_GENERICDATAVIEWCTRL