diff --git a/include/wx/scrolwin.h b/include/wx/scrolwin.h index 3236e9e037..2783930247 100644 --- a/include/wx/scrolwin.h +++ b/include/wx/scrolwin.h @@ -168,7 +168,7 @@ public: wxEventType& evtHorzScroll, wxEventType& evtVertScroll) const; - // wxWidgets <= 3.3.1 mostly restricted autoscroll to the + // wxWidgets <= 3.3.2 mostly restricted autoscroll to the // window holding the mouse capture. However, when dragging // objects between windows, the destination window should be // the window that is scrolling (to allow positioning the diff --git a/interface/wx/scrolwin.h b/interface/wx/scrolwin.h index 5f2d9901b2..f0f6765a48 100644 --- a/interface/wx/scrolwin.h +++ b/interface/wx/scrolwin.h @@ -116,6 +116,18 @@ enum wxScrollbarVisibility To disable autoscrolling completely, call DisableAutoScrollOutside() without calling EnableAutoScrollInside(). + wxWidgets <= 3.3.2 mostly restricted autoscroll to the window holding the + mouse capture. However, when dragging objects between windows, the + destination window, which may or may not be the capturing window, should be + the window that is scrolling (to allow positioning the dragged object). + To support this, as of wxWidgets 3.3.3, a window can call + EnableAutoscrollWithoutCapture() when processing a drag-enter, and + DisableAutoscrollWithoutCapture() when processing a drag-exit or drag-drop. + (If a window supports dragging content to another window, it should use + EnableAutoScrollInside() and DisableAutoScrollOutside() to avoid + autoscrolling while a destination window is also autoscrolling.) See the + shape frame portion of the @sample{dnd} for an example of dragging + from one window to another. @beginStyleTable @style{wxHSCROLL} @@ -541,6 +553,23 @@ public: */ void Scroll(const wxPoint& pt); + /** + Set this window to autoscroll even if it has not captured the mouse + (assuming the mouse cursor is in its autoscroll zone). This is + intended to be called on drag-enter to support drag and drop between + windows. + + @since 3.3.3 + */ + void EnableAutoscrollWithoutCapture(); + /** + Undo EnableAutoscrollWithoutCapture(). This is intended to be called on + drag-exit and drag-drop when supporting drag and drop between windows. + + @since 3.3.3 + */ + void DisableAutoscrollWithoutCapture(); + /** Set the horizontal and vertical scrolling increment only. See the pixelsPerUnit parameter in SetScrollbars(). diff --git a/samples/dnd/d_and_d.txt b/samples/dnd/d_and_d.txt index f7dd7a5f47..b4d44ce505 100644 --- a/samples/dnd/d_and_d.txt +++ b/samples/dnd/d_and_d.txt @@ -84,10 +84,14 @@ must have been registered) and their values. drop source will delete the corresponding data - which would lead to data loss if you didn't paste it properly. + If wxScrolled<>::EnableAutoscrollWithoutCapture() was called previously, + wxScrolled<>::DisableAutoscrollWithoutCapture() should be called here. + c) OnEnter() called when the mouse enters the window: you might use this function to - give some additional visual feedback. + give some additional visual feedback and/or call + wxScrolled<>::EnableAutoscrollWithoutCapture(). d) OnLeave() @@ -140,7 +144,7 @@ blocking function which enters into its own message loop and may return after an arbitrarily long time interval. During it, the QueryContinueDrag() is called whenever the mouse or keyboard state changes. The default behaviour is quite reasonable for 99% of cases: the drag operation is cancelled if the key -is preessed and the drop is initiated if the mouse button is released. +is pressed and the drop is initiated if the mouse button is released. b) After the end of d&d @@ -160,6 +164,5 @@ wxDropSource::Move code. @@@@ TODO: support tymed != TYMED_HGLOBAL; better support of CF_BMP, CF_METAFILE - scrolling support!! (how?) sample demonstrating use of user-defined formats sample which really does something useful diff --git a/samples/dnd/dnd.cpp b/samples/dnd/dnd.cpp index 0350e85ede..33e08d102c 100644 --- a/samples/dnd/dnd.cpp +++ b/samples/dnd/dnd.cpp @@ -32,6 +32,7 @@ #endif #if wxUSE_DRAG_AND_DROP +class DnDShapeCanvas; // ---------------------------------------------------------------------------- // Derive two simple classes which just put in the listbox the strings (text or @@ -381,17 +382,14 @@ public: // well, it's a bit difficult to describe a triangle by position and // size, but we're not doing geometry here, do we? ;-) - wxPoint p1(m_pos); - wxPoint p2(m_pos.x + m_size.x, m_pos.y); - wxPoint p3(m_pos.x, m_pos.y + m_size.y); + wxPoint p[3] = { + { m_pos }, + { m_pos.x + m_size.x, m_pos.y }, + { m_pos.x, m_pos.y + m_size.y }, + }; - dc.DrawLine(p1, p2); - dc.DrawLine(p2, p3); - dc.DrawLine(p3, p1); - - //works in multicolor modes; on GTK (at least) will fail in 16-bit color dc.SetBrush(wxBrush(m_col)); - dc.FloodFill(GetCentre(), m_col, wxFLOOD_BORDER); + dc.DrawPolygon(3, p); } }; @@ -416,18 +414,8 @@ public: { DnDShape::Draw(dc); - wxPoint p1(m_pos); - wxPoint p2(p1.x + m_size.x, p1.y); - wxPoint p3(p2.x, p2.y + m_size.y); - wxPoint p4(p1.x, p3.y); - - dc.DrawLine(p1, p2); - dc.DrawLine(p2, p3); - dc.DrawLine(p3, p4); - dc.DrawLine(p4, p1); - dc.SetBrush(wxBrush(m_col)); - dc.FloodFill(GetCentre(), m_col, wxFLOOD_BORDER); + dc.DrawRectangle(m_pos, m_size); } }; @@ -452,10 +440,8 @@ public: { DnDShape::Draw(dc); - dc.DrawEllipse(m_pos, m_size); - dc.SetBrush(wxBrush(m_col)); - dc.FloodFill(GetCentre(), m_col, wxFLOOD_BORDER); + dc.DrawEllipse(m_pos, m_size); } }; @@ -669,7 +655,7 @@ private: class DnDShapeDialog : public wxDialog { public: - DnDShapeDialog(wxFrame *parent, DnDShape *shape); + DnDShapeDialog(wxWindow *parent, DnDShape *shape); DnDShape *GetShape() const; @@ -702,17 +688,34 @@ private: // A frame for the shapes which can be drag-and-dropped between frames // ---------------------------------------------------------------------------- -class DnDShapeFrame : public wxScrolled +class DnDShapeFrame : public wxFrame { public: DnDShapeFrame(wxFrame *parent); - ~DnDShapeFrame(); + +protected: + // The menu and updateUI events are sent to the frame, but + // I think implementing their handlers is more natural in + // the canvas. Use TryBefore() to forward those events to + // the canvas. + bool TryBefore(wxEvent& event) override; + +private: + DnDShapeCanvas *m_canvas = nullptr; +}; + +// ---------------------------------------------------------------------------- +// A canvas for the shapes which can be drag-and-dropped between frames +// ---------------------------------------------------------------------------- + +class DnDShapeCanvas : public wxScrolledCanvas +{ +public: + DnDShapeCanvas(DnDShapeFrame *parent); + + DnDShapeFrame *GetParent(); void SetShape(DnDShape *shape); - virtual bool SetShape(const wxRegion ®ion) - { - return wxFrame::SetShape( region ); - } // callbacks void OnNewShape(wxCommandEvent& event); @@ -730,9 +733,9 @@ public: void OnDrop(wxCoord x, wxCoord y, DnDShape *shape); private: - DnDShape *m_shape; + std::unique_ptr m_shape; - static DnDShapeFrame *ms_lastDropTarget; + static DnDShapeCanvas *ms_lastDropTarget; wxDECLARE_EVENT_TABLE(); }; @@ -744,31 +747,31 @@ private: class DnDShapeDropTarget : public wxDropTarget { public: - DnDShapeDropTarget(DnDShapeFrame *frame) + DnDShapeDropTarget(DnDShapeCanvas *canvas) : wxDropTarget(new DnDShapeDataObject) { - m_frame = frame; + m_canvas = canvas; } // override base class (pure) virtuals virtual wxDragResult OnEnter(wxCoord x, wxCoord y, wxDragResult def) override { #if wxUSE_STATUSBAR - m_frame->SetStatusText("Mouse entered the frame"); + m_canvas->GetParent()->SetStatusText("Mouse entered the frame"); #endif // wxUSE_STATUSBAR - m_frame->EnableAutoscrollWithoutCapture(); + m_canvas->EnableAutoscrollWithoutCapture(); return OnDragOver(x, y, def); } virtual void OnLeave() override { #if wxUSE_STATUSBAR - m_frame->SetStatusText("Mouse left the frame"); + m_canvas->GetParent()->SetStatusText("Mouse left the frame"); #endif // wxUSE_STATUSBAR - m_frame->DisableAutoscrollWithoutCapture(); + m_canvas->DisableAutoscrollWithoutCapture(); } virtual bool OnDrop(wxCoord x, wxCoord y) override { - m_frame->DisableAutoscrollWithoutCapture(); + m_canvas->DisableAutoscrollWithoutCapture(); return wxDropTarget::OnDrop(x, y); } virtual wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def) override @@ -780,14 +783,14 @@ public: return wxDragNone; } - m_frame->OnDrop(x, y, + m_canvas->OnDrop(x, y, ((DnDShapeDataObject *)GetDataObject())->GetShape()); return def; } private: - DnDShapeFrame *m_frame; + DnDShapeCanvas *m_canvas; }; #endif // wxUSE_DRAG_AND_DROP @@ -867,20 +870,20 @@ wxEND_EVENT_TABLE() #if wxUSE_DRAG_AND_DROP -wxBEGIN_EVENT_TABLE(DnDShapeFrame, wxFrame) - EVT_MENU(Menu_Shape_New, DnDShapeFrame::OnNewShape) - EVT_MENU(Menu_Shape_Edit, DnDShapeFrame::OnEditShape) - EVT_MENU(Menu_Shape_Clear, DnDShapeFrame::OnClearShape) +wxBEGIN_EVENT_TABLE(DnDShapeCanvas, wxScrolledCanvas) + EVT_MENU(Menu_Shape_New, DnDShapeCanvas::OnNewShape) + EVT_MENU(Menu_Shape_Edit, DnDShapeCanvas::OnEditShape) + EVT_MENU(Menu_Shape_Clear, DnDShapeCanvas::OnClearShape) - EVT_MENU(Menu_ShapeClipboard_Copy, DnDShapeFrame::OnCopyShape) - EVT_MENU(Menu_ShapeClipboard_Paste, DnDShapeFrame::OnPasteShape) + EVT_MENU(Menu_ShapeClipboard_Copy, DnDShapeCanvas::OnCopyShape) + EVT_MENU(Menu_ShapeClipboard_Paste, DnDShapeCanvas::OnPasteShape) - EVT_UPDATE_UI(Menu_ShapeClipboard_Copy, DnDShapeFrame::OnUpdateUICopy) - EVT_UPDATE_UI(Menu_ShapeClipboard_Paste, DnDShapeFrame::OnUpdateUIPaste) + EVT_UPDATE_UI(Menu_ShapeClipboard_Copy, DnDShapeCanvas::OnUpdateUICopy) + EVT_UPDATE_UI(Menu_ShapeClipboard_Paste, DnDShapeCanvas::OnUpdateUIPaste) - EVT_LEFT_DOWN(DnDShapeFrame::OnDrag) + EVT_LEFT_DOWN(DnDShapeCanvas::OnDrag) - EVT_PAINT(DnDShapeFrame::OnPaint) + EVT_PAINT(DnDShapeCanvas::OnPaint) wxEND_EVENT_TABLE() wxBEGIN_EVENT_TABLE(DnDShapeDialog, wxDialog) @@ -1596,7 +1599,7 @@ bool DnDFile::OnDropFiles(wxCoord, wxCoord, const wxArrayString& filenames) // DnDShapeDialog // ---------------------------------------------------------------------------- -DnDShapeDialog::DnDShapeDialog(wxFrame *parent, DnDShape *shape) +DnDShapeDialog::DnDShapeDialog(wxWindow *parent, DnDShape *shape) :wxDialog( parent, 6001, "Choose Shape", wxPoint( 10, 10 ), wxSize( 40, 40 ), wxDEFAULT_DIALOG_STYLE | wxRAISED_BORDER | wxRESIZE_BORDER ) @@ -1741,28 +1744,14 @@ void DnDShapeDialog::OnColour(wxCommandEvent& WXUNUSED(event)) // DnDShapeFrame // ---------------------------------------------------------------------------- -bool wxCreateScrolled(wxFrame* self, - wxWindow *parent, wxWindowID winid, - const wxPoint& WXUNUSED(pos), const wxSize& WXUNUSED(size), - long WXUNUSED(style), const wxString& name) -{ - return self->Create(parent, winid, name); -} - -DnDShapeFrame *DnDShapeFrame::ms_lastDropTarget = nullptr; - DnDShapeFrame::DnDShapeFrame(wxFrame *parent) - : wxScrolled(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxScrolledWindowStyle, "Shape Frame") + : wxFrame(parent, wxID_ANY, "Shape Frame") { #if wxUSE_STATUSBAR CreateStatusBar(); #endif // wxUSE_STATUSBAR - // this is completely arbitrary and is done just for illustration purposes - SetVirtualSize(1000, 1000); - SetScrollRate(20, 20); - EnableAutoScrollInside(20); - DisableAutoScrollOutside(); + m_canvas = new DnDShapeCanvas(this); wxMenu *menuShape = new wxMenu; menuShape->Append(Menu_Shape_New, "&New default shape\tCtrl-S"); @@ -1783,30 +1772,53 @@ DnDShapeFrame::DnDShapeFrame(wxFrame *parent) #if wxUSE_STATUSBAR SetStatusText("Press Ctrl-S to create a new shape"); #endif // wxUSE_STATUSBAR +} + +bool DnDShapeFrame::TryBefore(wxEvent& event) +{ + if (event.GetEventType() == wxEVT_MENU || + event.GetEventType() == wxEVT_UPDATE_UI) + { + wxPropagationDisabler disablePropagation(event); + if (m_canvas && m_canvas->ProcessWindowEvent(event)) + { + return true; + } + } + + return wxFrame::TryBefore(event); +} + +DnDShapeCanvas *DnDShapeCanvas::ms_lastDropTarget = nullptr; + +DnDShapeCanvas::DnDShapeCanvas(DnDShapeFrame *parent) + : wxScrolledCanvas(parent) +{ + // this is completely arbitrary and is done just for illustration purposes + SetVirtualSize(1000, 1000); + SetScrollRate(20, 20); + EnableAutoScrollInside(20); + DisableAutoScrollOutside(); SetDropTarget(new DnDShapeDropTarget(this)); - m_shape = nullptr; - SetBackgroundColour(*wxWHITE); } -DnDShapeFrame::~DnDShapeFrame() +DnDShapeFrame *DnDShapeCanvas::GetParent() { - if (m_shape) - delete m_shape; + wxWindow *parent = wxScrolledCanvas::GetParent(); + return wxCheckCast(parent); } -void DnDShapeFrame::SetShape(DnDShape *shape) +void DnDShapeCanvas::SetShape(DnDShape *shape) { - if (m_shape) - delete m_shape; - m_shape = shape; + m_shape.reset(shape); Refresh(); } // callbacks -void DnDShapeFrame::OnDrag(wxMouseEvent& event) +void DnDShapeCanvas::OnDrag(wxMouseEvent& event) { if ( !m_shape ) { @@ -1816,7 +1828,7 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) } // start drag operation - DnDShapeDataObject shapeData(m_shape); + DnDShapeDataObject shapeData(m_shape.get()); wxDropSource source(shapeData, this); wxString msg; @@ -1829,7 +1841,7 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) case wxDragNone: #if wxUSE_STATUSBAR - SetStatusText("Nothing happened"); + GetParent()->SetStatusText("Nothing happened"); #endif // wxUSE_STATUSBAR break; @@ -1848,7 +1860,7 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) case wxDragCancel: #if wxUSE_STATUSBAR - SetStatusText("Drag and drop operation cancelled"); + GetParent()->SetStatusText("Drag and drop operation cancelled"); #endif // wxUSE_STATUSBAR break; } @@ -1856,13 +1868,13 @@ void DnDShapeFrame::OnDrag(wxMouseEvent& event) if (msg.length() ) { #if wxUSE_STATUSBAR - SetStatusText(wxString("Shape successfully ") + msg); + GetParent()->SetStatusText(wxString("Shape successfully ") + msg); #endif // wxUSE_STATUSBAR } //else: status text already set } -void DnDShapeFrame::OnDrop(wxCoord x, wxCoord y, DnDShape *shape) +void DnDShapeCanvas::OnDrop(wxCoord x, wxCoord y, DnDShape *shape) { ms_lastDropTarget = this; @@ -1872,16 +1884,16 @@ void DnDShapeFrame::OnDrop(wxCoord x, wxCoord y, DnDShape *shape) #if wxUSE_STATUSBAR wxString s; s.Printf("Shape dropped at (%d, %d)", pt.x, pt.y); - SetStatusText(s); + GetParent()->SetStatusText(s); #endif // wxUSE_STATUSBAR shape->Move(pt); SetShape(shape); } -void DnDShapeFrame::OnEditShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnEditShape(wxCommandEvent& WXUNUSED(event)) { - DnDShapeDialog dlg(this, m_shape); + DnDShapeDialog dlg(this, m_shape.get()); if ( dlg.ShowModal() == wxID_OK ) { SetShape(dlg.GetShape()); @@ -1889,27 +1901,27 @@ void DnDShapeFrame::OnEditShape(wxCommandEvent& WXUNUSED(event)) #if wxUSE_STATUSBAR if ( m_shape ) { - SetStatusText("You can now drag the shape to another frame"); + GetParent()->SetStatusText("You can now drag the shape to another frame"); } #endif // wxUSE_STATUSBAR } } -void DnDShapeFrame::OnNewShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnNewShape(wxCommandEvent& WXUNUSED(event)) { SetShape(new DnDEllipticShape(wxPoint(10, 10), wxSize(80, 60), *wxRED)); #if wxUSE_STATUSBAR - SetStatusText("You can now drag the shape to another frame"); + GetParent()->SetStatusText("You can now drag the shape to another frame"); #endif // wxUSE_STATUSBAR } -void DnDShapeFrame::OnClearShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnClearShape(wxCommandEvent& WXUNUSED(event)) { SetShape(nullptr); } -void DnDShapeFrame::OnCopyShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnCopyShape(wxCommandEvent& WXUNUSED(event)) { if ( m_shape ) { @@ -1921,11 +1933,11 @@ void DnDShapeFrame::OnCopyShape(wxCommandEvent& WXUNUSED(event)) return; } - wxTheClipboard->AddData(new DnDShapeDataObject(m_shape)); + wxTheClipboard->AddData(new DnDShapeDataObject(m_shape.get())); } } -void DnDShapeFrame::OnPasteShape(wxCommandEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnPasteShape(wxCommandEvent& WXUNUSED(event)) { wxClipboardLocker clipLocker; if ( !clipLocker ) @@ -1946,17 +1958,17 @@ void DnDShapeFrame::OnPasteShape(wxCommandEvent& WXUNUSED(event)) } } -void DnDShapeFrame::OnUpdateUICopy(wxUpdateUIEvent& event) +void DnDShapeCanvas::OnUpdateUICopy(wxUpdateUIEvent& event) { event.Enable( m_shape != nullptr ); } -void DnDShapeFrame::OnUpdateUIPaste(wxUpdateUIEvent& event) +void DnDShapeCanvas::OnUpdateUIPaste(wxUpdateUIEvent& event) { event.Enable( wxTheClipboard->IsSupported(wxDataFormat(ShapeFormatId())) ); } -void DnDShapeFrame::OnPaint(wxPaintEvent& WXUNUSED(event)) +void DnDShapeCanvas::OnPaint(wxPaintEvent& WXUNUSED(event)) { wxPaintDC dc(this); DoPrepareDC(dc);