From aef252d9da7e846f0526eb0ad0c91198034f0973 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 8 Dec 2008 14:40:42 +0000 Subject: [PATCH] implement column resizing events in wxHeaderCtrl git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@57190 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/generic/dataview.h | 12 ++-- include/wx/generic/headerctrlg.h | 20 ++++++ include/wx/headerctrl.h | 32 ++++++++- include/wx/msw/headerctrl.h | 11 ++-- interface/wx/headerctrl.h | 32 +++++++++ src/common/headerctrlcmn.cpp | 4 ++ src/generic/datavgen.cpp | 62 ++++++++++++------ src/generic/headerctrlg.cpp | 107 +++++++++++++++++++++++++++---- src/msw/headerctrl.cpp | 85 +++++++++++++++++------- 9 files changed, 295 insertions(+), 70 deletions(-) diff --git a/include/wx/generic/dataview.h b/include/wx/generic/dataview.h index 05ce149b3d..953c293142 100644 --- a/include/wx/generic/dataview.h +++ b/include/wx/generic/dataview.h @@ -359,11 +359,6 @@ private: m_sortAscending = true; } - // like SetWidth() but does not ask the header window of the - // wxDataViewCtrl to reflect the width-change. - void SetInternalWidth(int width); - - wxString m_title; int m_width, m_minWidth; @@ -482,8 +477,11 @@ public: // utility functions not part of the API // called by header window after reorder void ColumnMoved( wxDataViewColumn* col, unsigned int new_pos ); - // updates the header window after a change in a column setting - void OnColumnChange(); + // update the display after a change to an individual column + void OnColumnChange(unsigned int idx); + + // update after a change to the number of columns + void OnColumnsCountChanged(); wxWindow *GetMainWindow() { return (wxWindow*) m_clientArea; } diff --git a/include/wx/generic/headerctrlg.h b/include/wx/generic/headerctrlg.h index 7c86e97730..756d9c53cb 100644 --- a/include/wx/generic/headerctrlg.h +++ b/include/wx/generic/headerctrlg.h @@ -13,6 +13,7 @@ #include "wx/event.h" #include "wx/vector.h" +#include "wx/overlay.h" // ---------------------------------------------------------------------------- // wxHeaderCtrl @@ -64,6 +65,7 @@ private: // event handlers void OnPaint(wxPaintEvent& event); void OnMouse(wxMouseEvent& event); + void OnCaptureLost(wxMouseCaptureLostEvent& event); // return the horizontal start position of the given column int GetColStart(unsigned int idx) const; @@ -84,6 +86,17 @@ private: // column 1 but close enough to the divider separating it from column 0) int FindColumnAtPos(int x, bool& onSeparator) const; + // end any drag operation currently in progress (resizing or reordering) + void EndDragging(); + + // and the resizing operation currently in progress and generate an event + // about it with its cancelled flag set if width is -1 + void EndResizing(int width); + + // update the current position of the resizing marker if xPhysical is a + // valid physical coordinate value or remove it entirely if it is -1 + void UpdateResizingMarker(int xPhysical); + // number of columns in the control currently unsigned int m_numColumns; @@ -91,9 +104,16 @@ private: // index of the column under mouse or -1 if none unsigned int m_hover; + // the column being resized or -1 if there is no resizing operation in + // progress + unsigned int m_colBeingResized; + // the horizontal scroll offset int m_scrollOffset; + // the overlay display used during the dragging operations + wxOverlay m_overlay; + DECLARE_EVENT_TABLE() DECLARE_NO_COPY_CLASS(wxHeaderCtrl) diff --git a/include/wx/headerctrl.h b/include/wx/headerctrl.h index bf3a6632ba..51481efb67 100644 --- a/include/wx/headerctrl.h +++ b/include/wx/headerctrl.h @@ -269,25 +269,45 @@ class WXDLLIMPEXP_CORE wxHeaderCtrlEvent : public wxNotifyEvent { public: wxHeaderCtrlEvent(wxEventType commandType = wxEVT_NULL, int winid = 0) - : wxNotifyEvent(commandType, winid) + : wxNotifyEvent(commandType, winid), + m_col(-1), + m_width(0), + m_cancelled(false) { } wxHeaderCtrlEvent(const wxHeaderCtrlEvent& event) : wxNotifyEvent(event), - m_col(event.m_col) + m_col(event.m_col), + m_width(event.m_width), + m_cancelled(event.m_cancelled) { } + // the column which this event pertains to: valid for all header events int GetColumn() const { return m_col; } void SetColumn(int col) { m_col = col; } + // the width of the column: valid for column resizing/dragging events only + int GetWidth() const { return m_width; } + void SetWidth(int width) { m_width = width; } + + // was the drag operation cancelled or did it complete successfully? + bool IsCancelled() const { return m_cancelled; } + void SetCancelled() { m_cancelled = true; } + virtual wxEvent *Clone() const { return new wxHeaderCtrlEvent(*this); } protected: // the column affected by the event int m_col; + // the current width for the dragging events + int m_width; + + // was the drag operation cancelled? + bool m_cancelled; + private: DECLARE_DYNAMIC_CLASS_NO_ASSIGN(wxHeaderCtrlEvent) }; @@ -303,6 +323,10 @@ extern WXDLLIMPEXP_CORE const wxEventType wxEVT_COMMAND_HEADER_MIDDLE_DCLICK; extern WXDLLIMPEXP_CORE const wxEventType wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK; +extern WXDLLIMPEXP_CORE const wxEventType wxEVT_COMMAND_HEADER_BEGIN_DRAG; +extern WXDLLIMPEXP_CORE const wxEventType wxEVT_COMMAND_HEADER_DRAGGING; +extern WXDLLIMPEXP_CORE const wxEventType wxEVT_COMMAND_HEADER_END_DRAG; + typedef void (wxEvtHandler::*wxHeaderCtrlEventFunction)(wxHeaderCtrlEvent&); #define wxHeaderCtrlEventHandler(func) \ @@ -322,4 +346,8 @@ typedef void (wxEvtHandler::*wxHeaderCtrlEventFunction)(wxHeaderCtrlEvent&); #define EVT_HEADER_SEPARATOR_DCLICK(id, fn) wx__DECLARE_HEADER_EVT(SEPARATOR_DCLICK, id, fn) +#define EVT_HEADER_BEGIN_DRAG(id, fn) wx__DECLARE_HEADER_EVT(BEGIN_DRAG, id, fn) +#define EVT_HEADER_DRAGGING(id, fn) wx__DECLARE_HEADER_EVT(DRAGGING, id, fn) +#define EVT_HEADER_END_DRAG(id, fn) wx__DECLARE_HEADER_EVT(END_DRAG, id, fn) + #endif // _WX_HEADERCTRL_H_ diff --git a/include/wx/msw/headerctrl.h b/include/wx/msw/headerctrl.h index 8a48ef0d56..b0a3ae5c0b 100644 --- a/include/wx/msw/headerctrl.h +++ b/include/wx/msw/headerctrl.h @@ -71,13 +71,10 @@ private: enum Operation { Set, Insert }; void DoSetOrInsertItem(Operation oper, unsigned int idx); - // send an event of the given type for the given column, return true if it - // was processed - bool SendEvent(wxEventType evtType, unsigned int idx); - - // send a click or double click event (depending on dblclk value) for the - // click with the given button on the given item - bool SendClickEvent(bool dblclk, int button, unsigned int idx); + // get the event type corresponding to a click or double click event + // (depending on dblclk value) with the specified (using MSW convention) + // mouse button + wxEventType GetClickEventType(bool dblclk, int button); // the image list: initially NULL, created on demand diff --git a/interface/wx/headerctrl.h b/interface/wx/headerctrl.h index cca1310638..8bb10c924d 100644 --- a/interface/wx/headerctrl.h +++ b/interface/wx/headerctrl.h @@ -69,6 +69,22 @@ (this action is commonly used to resize the column to fit its contents width and the control provides UpdateColumnWidthToFit() method to make implementing this easier). + + @event{EVT_HEADER_BEGIN_DRAG(id, func)} + The user started to drag the column with the specified index (this + can only happen if wxHeaderColumn::IsResizeable() returned true for + this column). The event can be vetoed to prevent the control from + being resized, if it isn't, the dragging and end drag events will + be generated later. + @event{EVT_HEADER_DRAGGING(id, func)} + The user is dragging the column with the specified index and its + current width is wxHeaderCtrlEvent::GetWidth(). The event can be + vetoed to stop the dragging operation completely at any time. + @event{EVT_HEADER_END_DRAG(id, func)} + The user stopped dragging the column. If + wxHeaderCtrlEvent::IsCancelled() returns @true, nothing should + be done, otherwise the column should normally be resized to the + value of wxHeaderCtrlEvent::GetWidth(). @endEventTable @library{wxcore} @@ -389,6 +405,22 @@ class wxHeaderCtrlEvent : public wxNotifyEvent public: /** Return the index of the column affected by this event. + + This method can be called for all header control events. */ int GetColumn() const; + + /** + Return the current width of the column. + + This method can only be called for the dragging events. + */ + int GetWidth() const; + + /** + Return @true if the drag operation was cancelled. + + This method can only be called for the end drag event. + */ + bool IsCancelled() const; }; diff --git a/src/common/headerctrlcmn.cpp b/src/common/headerctrlcmn.cpp index a424e924d2..f17c3f8d71 100644 --- a/src/common/headerctrlcmn.cpp +++ b/src/common/headerctrlcmn.cpp @@ -177,3 +177,7 @@ const wxEventType wxEVT_COMMAND_HEADER_RIGHT_DCLICK = wxNewEventType(); const wxEventType wxEVT_COMMAND_HEADER_MIDDLE_DCLICK = wxNewEventType(); const wxEventType wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK = wxNewEventType(); + +const wxEventType wxEVT_COMMAND_HEADER_BEGIN_DRAG = wxNewEventType(); +const wxEventType wxEVT_COMMAND_HEADER_DRAGGING = wxNewEventType(); +const wxEventType wxEVT_COMMAND_HEADER_END_DRAG = wxNewEventType(); diff --git a/src/generic/datavgen.cpp b/src/generic/datavgen.cpp index 0fbcc1510c..3091a3adca 100644 --- a/src/generic/datavgen.cpp +++ b/src/generic/datavgen.cpp @@ -102,6 +102,7 @@ protected: int widthContents = owner->GetBestColumnWidth(idx); owner->GetColumn(idx)->SetWidth(wxMax(widthTitle, widthContents)); + owner->OnColumnChange(idx); return true; } @@ -136,6 +137,28 @@ private: event.Skip(); } + void OnBeginDrag(wxHeaderCtrlEvent& event) + { + if ( !GetColumn(event.GetColumn()).IsResizeable() ) + event.Veto(); + } + + void OnDragging(wxHeaderCtrlEvent& event) + { + const wxHeaderColumnBase& col = GetColumn(event.GetColumn()); + + const int minWidth = col.GetMinWidth(); + if ( event.GetWidth() < minWidth ) + event.Veto(); + } + + void OnEndDrag(wxHeaderCtrlEvent& event) + { + const unsigned col = event.GetColumn(); + GetColumn(col).SetWidth(event.GetWidth()); + GetOwner()->OnColumnChange(col); + } + DECLARE_EVENT_TABLE() DECLARE_NO_COPY_CLASS(wxDataViewHeaderWindow) }; @@ -143,6 +166,10 @@ private: BEGIN_EVENT_TABLE(wxDataViewHeaderWindow, wxHeaderCtrl) EVT_HEADER_CLICK(wxID_ANY, wxDataViewHeaderWindow::OnClick) EVT_HEADER_RIGHT_CLICK(wxID_ANY, wxDataViewHeaderWindow::OnRClick) + + EVT_HEADER_BEGIN_DRAG(wxID_ANY, wxDataViewHeaderWindow::OnBeginDrag) + EVT_HEADER_DRAGGING(wxID_ANY, wxDataViewHeaderWindow::OnDragging) + EVT_HEADER_END_DRAG(wxID_ANY, wxDataViewHeaderWindow::OnEndDrag) END_EVENT_TABLE() //----------------------------------------------------------------------------- @@ -3288,7 +3315,7 @@ bool wxDataViewCtrl::AppendColumn( wxDataViewColumn *col ) return false; m_cols.Append( col ); - OnColumnChange(); + OnColumnsCountChanged(); return true; } @@ -3298,7 +3325,7 @@ bool wxDataViewCtrl::PrependColumn( wxDataViewColumn *col ) return false; m_cols.Insert( col ); - OnColumnChange(); + OnColumnsCountChanged(); return true; } @@ -3308,11 +3335,19 @@ bool wxDataViewCtrl::InsertColumn( unsigned int pos, wxDataViewColumn *col ) return false; m_cols.Insert( pos, col ); - OnColumnChange(); + OnColumnsCountChanged(); return true; } -void wxDataViewCtrl::OnColumnChange() +void wxDataViewCtrl::OnColumnChange(unsigned int idx) +{ + if ( m_headerArea ) + m_headerArea->UpdateColumn(idx); + + m_clientArea->UpdateDisplay(); +} + +void wxDataViewCtrl::OnColumnsCountChanged() { if (m_headerArea) m_headerArea->SetColumnCount(GetColumnCount()); @@ -3335,20 +3370,9 @@ unsigned int wxDataViewCtrl::GetColumnCount() const return m_cols.GetCount(); } -wxDataViewColumn* wxDataViewCtrl::GetColumn( unsigned int pos ) const +wxDataViewColumn* wxDataViewCtrl::GetColumn( unsigned int idx ) const { - wxDataViewColumnList::const_iterator iter; - unsigned int i = 0; - for (iter = m_cols.begin(); iter!=m_cols.end(); iter++) - { - if (i == pos) - return *iter; - - if ((*iter)->IsHidden()) - continue; - i ++; - } - return NULL; + return m_cols[idx]; } void wxDataViewCtrl::ColumnMoved( wxDataViewColumn* col, unsigned int new_pos ) @@ -3371,7 +3395,7 @@ bool wxDataViewCtrl::DeleteColumn( wxDataViewColumn *column ) return false; m_cols.Erase(ret); - OnColumnChange(); + OnColumnsCountChanged(); return true; } @@ -3379,7 +3403,7 @@ bool wxDataViewCtrl::DeleteColumn( wxDataViewColumn *column ) bool wxDataViewCtrl::ClearColumns() { m_cols.Clear(); - OnColumnChange(); + OnColumnsCountChanged(); return true; } diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 8d8adc9fc4..be040a5ac6 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -57,7 +57,8 @@ const unsigned COL_NONE = (unsigned)-1; void wxHeaderCtrl::Init() { m_numColumns = 0; - m_hover = COL_NONE; + m_hover = + m_colBeingResized = COL_NONE; m_scrollOffset = 0; } @@ -209,6 +210,55 @@ void wxHeaderCtrl::RefreshColsAfter(unsigned int idx) RefreshRect(rect); } +// ---------------------------------------------------------------------------- +// wxHeaderCtrl dragging +// ---------------------------------------------------------------------------- + +void wxHeaderCtrl::UpdateResizingMarker(int xPhysical) +{ + // unfortunately drawing the marker over the parent window doesn't work as + // it's usually covered by another window (the main control view) so just + // draw the marker over the header itself, even if it makes it not very + // useful + wxClientDC dc(this); + + wxDCOverlay dcover(m_overlay, &dc); + dcover.Clear(); + + if ( xPhysical != -1 ) + { + dc.SetPen(*wxLIGHT_GREY_PEN); + dc.DrawLine(xPhysical, 0, xPhysical, GetClientSize().y); + } +} + +void wxHeaderCtrl::EndDragging() +{ + UpdateResizingMarker(-1); + + m_overlay.Reset(); +} + +void wxHeaderCtrl::EndResizing(int width) +{ + wxASSERT_MSG( m_colBeingResized != COL_NONE, + "shouldn't be called if we're not resizing" ); + + EndDragging(); + + wxHeaderCtrlEvent event(wxEVT_COMMAND_HEADER_END_DRAG, GetId()); + event.SetEventObject(this); + event.SetColumn(m_colBeingResized); + if ( width == -1 ) + event.SetCancelled(); + else + event.SetWidth(width); + + GetEventHandler()->ProcessEvent(event); + + m_colBeingResized = COL_NONE; +} + // ---------------------------------------------------------------------------- // wxHeaderCtrl event handlers // ---------------------------------------------------------------------------- @@ -217,6 +267,8 @@ BEGIN_EVENT_TABLE(wxHeaderCtrl, wxHeaderCtrlBase) EVT_PAINT(wxHeaderCtrl::OnPaint) EVT_MOUSE_EVENTS(wxHeaderCtrl::OnMouse) + + EVT_MOUSE_CAPTURE_LOST(wxHeaderCtrl::OnCaptureLost) END_EVENT_TABLE() void wxHeaderCtrl::OnPaint(wxPaintEvent& WXUNUSED(event)) @@ -283,18 +335,42 @@ void wxHeaderCtrl::OnPaint(wxPaintEvent& WXUNUSED(event)) } } +void wxHeaderCtrl::OnCaptureLost(wxMouseCaptureLostEvent& WXUNUSED(event)) +{ + if ( m_colBeingResized != COL_NONE ) + EndResizing(-1); +} + void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) { + // do this in advance to allow simply returning if we're not interested, + // we'll undo it if we do handle the event below mevent.Skip(); + // account for the control displacement - const int x = mevent.GetX() - m_scrollOffset; + const int xPhysical = mevent.GetX(); + const int xLogical = xPhysical - m_scrollOffset; + + // first deal with the [continuation of any] dragging operations in + // progress + if ( m_colBeingResized != COL_NONE ) + { + if ( mevent.LeftUp() ) + EndResizing(xLogical - GetColStart(m_colBeingResized)); + else // update the live separator position + UpdateResizingMarker(xPhysical); + + return; + } + // find if the event is over a column at all bool onSeparator; const unsigned col = mevent.Leaving() ? (onSeparator = false, COL_NONE) - : FindColumnAtPos(x, onSeparator); + : FindColumnAtPos(xLogical, onSeparator); + // update the highlighted column if it changed if ( col != m_hover ) @@ -306,9 +382,6 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) RefreshColIfNotNone(m_hover); } - if ( col == COL_NONE ) - return; - // update mouse cursor as it moves around if ( mevent.Moving() ) { @@ -316,20 +389,30 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) return; } + // all the other events only make sense when they happen over a column + if ( col == COL_NONE ) + return; + + + // enter various dragging modes on left mouse press if ( mevent.LeftDown() ) { - // TODO if ( onSeparator ) - // resize column - ; - else - // drag column + { + // start resizing the column + m_colBeingResized = col; + UpdateResizingMarker(xPhysical); + } + else // on column itself + { + // TODO: drag column ; + } return; } - // determine the type of header event corresponding to this mouse event + // determine the type of header event corresponding to click events wxEventType evtType = wxEVT_NULL; const bool click = mevent.ButtonUp(), dblclk = mevent.ButtonDClick(); diff --git a/src/msw/headerctrl.cpp b/src/msw/headerctrl.cpp index 8df0e8286c..e60b1bbc1b 100644 --- a/src/msw/headerctrl.cpp +++ b/src/msw/headerctrl.cpp @@ -260,16 +260,7 @@ void wxHeaderCtrl::DoSetOrInsertItem(Operation oper, unsigned int idx) // wxHeaderCtrl events // ---------------------------------------------------------------------------- -bool wxHeaderCtrl::SendEvent(wxEventType evtType, unsigned int idx) -{ - wxHeaderCtrlEvent event(evtType, GetId()); - event.SetEventObject(this); - event.SetColumn(idx); - - return GetEventHandler()->ProcessEvent(event); -} - -bool wxHeaderCtrl::SendClickEvent(bool dblclk, int button, unsigned int idx) +wxEventType wxHeaderCtrl::GetClickEventType(bool dblclk, int button) { wxEventType evtType; switch ( button ) @@ -291,23 +282,27 @@ bool wxHeaderCtrl::SendClickEvent(bool dblclk, int button, unsigned int idx) default: wxFAIL_MSG( wxS("unexpected event type") ); - return false; + evtType = wxEVT_NULL; } - return SendEvent(evtType, idx); + return evtType; } bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) { NMHEADER * const nmhdr = (NMHEADER *)lParam; - const int idx = nmhdr->iItem; + wxEventType evtType = wxEVT_NULL; + int idx = nmhdr->iItem; + int width = 0; switch ( const UINT code = nmhdr->hdr.code ) { + // click events + // ------------ + case HDN_ITEMCLICK: case HDN_ITEMDBLCLICK: - if ( SendClickEvent(code == HDN_ITEMDBLCLICK, nmhdr->iButton, idx) ) - return true; + evtType = GetClickEventType(code == HDN_ITEMDBLCLICK, nmhdr->iButton); break; // although we should get the notifications about the right clicks @@ -317,22 +312,66 @@ bool wxHeaderCtrl::MSWOnNotify(int idCtrl, WXLPARAM lParam, WXLPARAM *result) case NM_RDBLCLK: { POINT pt; - const int col = wxMSWGetColumnClicked(&nmhdr->hdr, &pt); - if ( col != wxNOT_FOUND ) - { - if ( SendClickEvent(code == NM_RDBLCLK, 1, col) ) - return true; - } + idx = wxMSWGetColumnClicked(&nmhdr->hdr, &pt); + if ( idx != wxNOT_FOUND ) + evtType = GetClickEventType(code == NM_RDBLCLK, 1); //else: ignore clicks outside any column } break; case HDN_DIVIDERDBLCLICK: - if ( SendEvent(wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK, idx) ) - return true; + evtType = wxEVT_COMMAND_HEADER_SEPARATOR_DCLICK; + break; + + + // column resizing events + // ---------------------- + + // see comments in wxListCtrl::MSWOnNotify() for why we catch both + // ASCII and Unicode versions of this message + case HDN_BEGINTRACKA: + case HDN_BEGINTRACKW: + evtType = wxEVT_COMMAND_HEADER_BEGIN_DRAG; + // fall through + + case HDN_TRACKA: + case HDN_TRACKW: + if ( evtType == wxEVT_NULL ) + evtType = wxEVT_COMMAND_HEADER_DRAGGING; + // fall through + + case HDN_ENDTRACKA: + case HDN_ENDTRACKW: + if ( evtType == wxEVT_NULL ) + evtType = wxEVT_COMMAND_HEADER_END_DRAG; + + width = nmhdr->pitem->cxy; break; } + + // do generate the corresponding wx event + if ( evtType != wxEVT_NULL ) + { + wxHeaderCtrlEvent event(evtType, GetId()); + event.SetEventObject(this); + event.SetColumn(idx); + event.SetWidth(width); + + if ( GetEventHandler()->ProcessEvent(event) ) + { + if ( !event.IsAllowed() ) + { + // all of HDN_BEGIN{DRAG,TRACK}, HDN_TRACK and HDN_ITEMCHANGING + // interpret TRUE return value as meaning to stop the control + // default handling of the message + *result = TRUE; + } + + return true; + } + } + return wxHeaderCtrlBase::MSWOnNotify(idCtrl, lParam, result); } -- 2.45.2