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);
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 );
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(),
m_currentCol = NULL;
m_currentColSetByKeyboard = false;
m_useCellFocus = false;
- m_currentRow = 0;
+ m_currentRow = (unsigned)-1;
#ifdef __WXMSW__
// We would like to use the same line height that Explorer uses. This is
wxDataViewModel *model = GetModel();
wxAutoBufferedPaintDC dc( this );
-#ifdef __WXMSW__
dc.SetBrush(GetOwner()->GetBackgroundColour());
dc.SetPen( *wxTRANSPARENT_PEN );
dc.DrawRectangle(GetClientSize());
-#endif
if ( IsEmpty() )
{
// 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();
{
DestroyTree();
m_selection.Clear();
+ m_currentRow = (unsigned)-1;
- SortPrepare();
- BuildTree( GetModel() );
+ if (GetModel())
+ {
+ SortPrepare();
+ BuildTree( GetModel() );
+ }
+ else
+ {
+ m_count = 0;
+ }
GetOwner()->InvalidateColBestWidths();
UpdateDisplay();
{
wxASSERT( !IsVirtualList() );
+ if ( row == (unsigned)-1 )
+ return NULL;
+
RowToTreeNodeJob job( row , -2, m_root );
Walker( m_root , job );
return job.GetResult();
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_COMMAND_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_COMMAND_DATAVIEW_ITEM_EXPANDED,node->GetItem());
+ }
}
void wxDataViewMainWindow::Collapse(unsigned int row)
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() )
{
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();
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) )
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);
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;
}
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