#include "wx/headerctrl.h"
#include "wx/dnd.h"
#include "wx/stopwatch.h"
+#include "wx/weakref.h"
//-----------------------------------------------------------------------------
// classes
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 OnLeftKey();
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 )
int GetLineAt( unsigned int y ) const; // y / m_lineHeight in fixed mode
void SetRowHeight( int lineHeight ) { m_lineHeight = lineHeight; }
+ int GetRowHeight() const { return m_lineHeight; }
// Some useful functions for row and item mapping
wxDataViewItem GetItemByRow( unsigned int row ) const;
int GetRowByItem( const wxDataViewItem & item ) const;
+ wxDataViewTreeNode * GetTreeNodeByRow( unsigned int row ) const;
+ // We did not need this temporarily
+ // wxDataViewTreeNode * GetTreeNodeByItem( const wxDataViewItem & item );
+
// Methods for building the mapping tree
void BuildTree( wxDataViewModel * model );
void DestroyTree();
void OnColumnsCountChanged();
-private:
- wxDataViewTreeNode * GetTreeNodeByRow( unsigned int row ) const;
- // We did not need this temporarily
- // wxDataViewTreeNode * GetTreeNodeByItem( const wxDataViewItem & item );
+ // 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);
- int RecalculateCount();
+private:
+ 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()
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:
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_useCellFocus = false;
m_currentRow = 0;
- m_lineHeight = wxMax( 17, GetCharHeight() + 2 ); // 17 = mini icon height + 1
+#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 )
+ m_lineHeight = wxMax(16, GetCharHeight()) + 6; // 16 = mini icon height
+ else
+#endif // __WXMSW__
+ m_lineHeight = wxMax(16, GetCharHeight()) + 1; // 16 = mini icon height
#if wxUSE_DRAG_AND_DROP
m_dragCount = 0;
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();
+ }
}
//-----------------------------------------------------------------------------
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;
}
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
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
// it. Only if that event is not handled do we activate column renderer (which
- // is normally done by Space).
+ // is normally done by Space) or even inline editing.
const wxDataViewItem item = GetItemByRow(m_currentRow);
}
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
+ // Windows, but Space is common everywhere else, so use it too
+ // for greater cross-platform compatibility).
+
const wxDataViewItem item = GetItemByRow(m_currentRow);
// Activate the current activatable column. If not column is focused (typically
wxDataViewRenderer *cell = activatableCol->GetRenderer();
cell->PrepareForItem(GetModel(), item, colIdx);
cell->WXActivateCell(cell_rect, GetModel(), item, colIdx, NULL);
+
+ break;
+ }
+ // else: fall through to WXK_F2 handling
+ }
+
+ case WXK_F2:
+ if ( event.HasModifiers() )
+ {
+ event.Skip();
+ break;
+ }
+ else
+ {
+ if( !m_selection.empty() )
+ {
+ // Mimic Windows 7 behavior: edit the item that has focus
+ // if it is selected and the first selected item if focus
+ // is out of selection.
+ int sel;
+ if ( m_selection.Index(m_currentRow) != wxNOT_FOUND )
+ sel = m_currentRow;
+ else
+ sel = m_selection[0];
+
+
+ const wxDataViewItem item = GetItemByRow(sel);
+
+ // Edit the current column. If no column is focused
+ // (typically because the user has full row selected), try
+ // to find the first editable column.
+ wxDataViewColumn *editableCol = FindColumnForEditing(item, wxDATAVIEW_CELL_EDITABLE);
+
+ if ( editableCol )
+ GetOwner()->EditItem(item, editableCol);
}
}
break;
}
break;
- case WXK_F2:
- {
- if( !m_selection.empty() )
- {
- // Mimic Windows 7 behavior: edit the item that has focus
- // if it is selected and the first selected item if focus
- // is out of selection.
- int sel;
- if ( m_selection.Index(m_currentRow) != wxNOT_FOUND )
- sel = m_currentRow;
- else
- sel = m_selection[0];
-
-
- const wxDataViewItem item = GetItemByRow(sel);
-
- // Edit the current column. If not column is focused
- // (typically because the user has full row selected), try
- // to find the first editable column.
- wxDataViewColumn *editableCol = FindColumnForEditing(item, wxDATAVIEW_CELL_EDITABLE);
-
- if ( editableCol )
- GetOwner()->StartEditor(item, GetOwner()->GetColumnIndex(editableCol));
- }
- }
- break;
-
default:
event.Skip();
}
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
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 );
}
(current == oldCurrentRow)) && oldWasSelected;
// 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());
class MaxWidthCalculator
{
public:
- MaxWidthCalculator(wxDataViewMainWindow *clientArea,
+ MaxWidthCalculator(const wxDataViewCtrl *dvc,
+ wxDataViewMainWindow *clientArea,
wxDataViewRenderer *renderer,
const wxDataViewModel *model,
- unsigned column)
+ unsigned column,
+ int expanderSize)
: m_width(0),
+ m_dvc(dvc),
m_clientArea(clientArea),
m_renderer(renderer),
m_model(model),
- m_column(column)
+ m_column(column),
+ m_expanderSize(expanderSize)
+
{
+ m_isExpanderCol =
+ !clientArea->IsList() &&
+ (column == 0 ||
+ GetExpanderColumnOrFirstOne(const_cast<wxDataViewCtrl*>(dvc)) == dvc->GetColumnAt(column));
}
void UpdateWithWidth(int width)
void UpdateWithRow(int row)
{
- wxDataViewItem item = m_clientArea->GetItemByRow(row);
+ int indent = 0;
+ wxDataViewItem item;
+
+ if ( m_isExpanderCol )
+ {
+ wxDataViewTreeNode *node = m_clientArea->GetTreeNodeByRow(row);
+ item = node->GetItem();
+ indent = m_dvc->GetIndent() * node->GetIndentLevel() + m_expanderSize;
+ }
+ else
+ {
+ item = m_clientArea->GetItemByRow(row);
+ }
+
m_renderer->PrepareForItem(m_model, item, m_column);
- m_width = wxMax(m_width, m_renderer->GetSize().x);
+ m_width = wxMax(m_width, m_renderer->GetSize().x + indent);
}
int GetMaxWidth() const { return m_width; }
private:
int m_width;
+ const wxDataViewCtrl *m_dvc;
wxDataViewMainWindow *m_clientArea;
wxDataViewRenderer *m_renderer;
const wxDataViewModel *m_model;
unsigned m_column;
+ bool m_isExpanderCol;
+ int m_expanderSize;
};
- MaxWidthCalculator calculator(m_clientArea, renderer,
- GetModel(), column->GetModelColumn());
+ MaxWidthCalculator calculator(this, m_clientArea, renderer,
+ GetModel(), column->GetModelColumn(),
+ m_clientArea->GetRowHeight());
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
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;
}
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);
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