From 0dedc6cd6af82dfc08d65896c51ea2e1941117cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Fri, 16 Jun 2023 15:55:59 +0800 Subject: [PATCH 01/34] Fix wxRibbonButtonBar's layout overall size computation. Since we've already had CalculateOverallSize, we should avoid duplicate the error-prone algorithm here. It has bug found by the assertion within TryCollapseLayout in our application. --- src/ribbon/buttonbar.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ribbon/buttonbar.cpp b/src/ribbon/buttonbar.cpp index 98b559538e54..6f4aad921043 100644 --- a/src/ribbon/buttonbar.cpp +++ b/src/ribbon/buttonbar.cpp @@ -1066,7 +1066,6 @@ void wxRibbonButtonBar::MakeLayouts() // small buttons small, stacked vertically wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout; wxPoint cursor(0, 0); - layout->overall_size.SetHeight(0); for(btn_i = 0; btn_i < btn_count; ++btn_i) { wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i); @@ -1102,8 +1101,7 @@ void wxRibbonButtonBar::MakeLayouts() } layout->buttons.Add(instance); } - layout->overall_size.SetHeight(available_height); - layout->overall_size.SetWidth(cursor.x + stacked_width); + layout->CalculateOverallSize(); m_layouts.Add(layout); } if(btn_count >= 2) From 232ec629166f650daeb8d37859ccccaecf42925a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 16:23:20 +0800 Subject: [PATCH 02/34] wxGTK: Fix UI refreshing delay when dragging the header border of generic wxDataViewCtrl to resize column. --- src/generic/headerctrlg.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 79a31a0eb285..541bef81a356 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -350,6 +350,7 @@ void wxHeaderCtrl::StartOrContinueResizing(unsigned int col, int xPhysical) //else: we had already done the above when we started } + RefreshColsAfter(col); } void wxHeaderCtrl::EndResizing(int xPhysical) From cd69ca232735b7daa8c8a1d8789d1428bdf8f4e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 14:10:37 +0800 Subject: [PATCH 03/34] wxGTK: Fix improper wxEVT_LEAVE_WINDOW event Do Not emit wxEVT_LEAVE_WINDOW when GdkEventCrossing mode is GDK_CROSSING_GRAB or GDK_CROSSING_GTK_GRAB --- src/gtk/window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index 900bb4870169..aeebc9c0c044 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -2273,7 +2273,7 @@ gtk_window_leave_callback( GtkWidget*, win->GTKUpdateCursor(); // Event was emitted after an ungrab - if (gdk_event->mode != GDK_CROSSING_NORMAL) return FALSE; + if (gdk_event->mode != GDK_CROSSING_NORMAL && gdk_event->mode != GDK_CROSSING_GRAB && gdk_event->mode != GDK_CROSSING_GTK_GRAB) return FALSE; wxMouseEvent event( wxEVT_LEAVE_WINDOW ); InitMouseEvent(win, event, gdk_event); From 59e89f9370520de2c6f716fef789e41c09432afb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 15:20:18 +0800 Subject: [PATCH 04/34] MacOSX: Change the two dummy messages to use systemUpTime as timestamp to fix CEF renderer process crash. Begin with Chromium 97, EventLatencyTracingRecorder was added to trace the performance. And the timestamp 0 used will lead to 'DCHECK(!dispatch_timestamp.is_null());' in function EventLatencyTracingRecorder::RecordEventLatencyTraceEvent see: https://github.com/chromium/chromium/blob/109.0.5414.120/cc/metrics/event_latency_tracing_recorder.cc Way to reproduce the crash: Right click to popup the context-menu created by CEF. Then the CEF renderer process will crash. Set the timestamp to [[NSProcessInfo processInfo] systemUptime] to fix it. --- src/osx/cocoa/evtloop.mm | 2 +- src/osx/cocoa/utils.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/osx/cocoa/evtloop.mm b/src/osx/cocoa/evtloop.mm index f0ca589d4224..954964af1173 100644 --- a/src/osx/cocoa/evtloop.mm +++ b/src/osx/cocoa/evtloop.mm @@ -380,7 +380,7 @@ static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat) NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0.0, 0.0) modifierFlags:0 - timestamp:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:0 context:nil subtype:0 data1:0 data2:0]; diff --git a/src/osx/cocoa/utils.mm b/src/osx/cocoa/utils.mm index bd1a8a47cec5..db915a044be1 100644 --- a/src/osx/cocoa/utils.mm +++ b/src/osx/cocoa/utils.mm @@ -409,7 +409,7 @@ - (void)sendEvent:(NSEvent *)anEvent NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0.0, 0.0) modifierFlags:0 - timestamp:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:0 context:nil subtype:0 data1:0 data2:0]; From 69a24bc82e9edb118c947f615aa425aa8ea37f18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 14:37:38 +0800 Subject: [PATCH 05/34] MacOSX: Keep DoDragDrop() from re-entering to enable a proper working CEF. DoDragDrop() may make CEF cease to work. One way to reproduce the problem: Do drag-n-drop repeatly, that is, start drag immediately after one drop, then there're chances we can see large amount of DoDragDrop() in the callstack of the UI-thread. And finally leads to stack overflow. Messages processed by CEF are handled when wxEVT_IDLE is triggered. The basic loop are: -> Step1, cocoa call OSXDefaultModeObserverCallBack(in src/osx/core/evtloop_cf.cpp) -> Step2, wxEVT_IDLE is triggered. -> Step3, wxWebViewChroium::OnIdle() takes the cake. -> Step4, CefDoMessageLoopWork() do the jobs for CEF. If DoDragDrop() is invoked during CefDoMessageLoopWork(), and together there comes a recursive call to it, then the previous DoDragDrop() can't return anymore, and will, in turn, prevent CefDoMessageLoopWork() from returning to the caller. And finally overflow the stack. And now we add the global dnd flag to stay away from this kind of tragic. --- src/osx/cocoa/dnd.mm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/osx/cocoa/dnd.mm b/src/osx/cocoa/dnd.mm index d8786cc2de76..4f7a2242e6af 100644 --- a/src/osx/cocoa/dnd.mm +++ b/src/osx/cocoa/dnd.mm @@ -486,11 +486,16 @@ - (nullable id)pasteboardPropertyListForType:(nonnull NSPasteboardType)type wxDragResult wxDropSource::DoDragDrop(int flags) { wxASSERT_MSG( m_data, wxT("Drop source: no data") ); - + static bool g_in_dnd = false; + wxDragResult result = wxDragNone; if ((m_data == NULL) || (m_data->GetFormatCount() == 0)) return result; - + + if (g_in_dnd) + return wxDragNone; + + g_in_dnd = true; NSView* view = m_window->GetPeer()->GetWXWidget(); if (view) { @@ -555,8 +560,8 @@ - (nullable id)pasteboardPropertyListForType:(nonnull NSPasteboardType)type gCurrentSource = NULL; } - - + + g_in_dnd = false; return result; } From 22513cf841c1842ddafe44037e5a8b7f7b9bb547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 15:59:16 +0800 Subject: [PATCH 06/34] wxOSX: Fix wxPopupTransientWindow's mouse event handling. In MacOSX/cocoa, wxPopupTransientWindow needs to do mouse capture handling during OnIdle(). But it's done wrongly when decide whether the mouse is within the popup. Not fixed with simply checking the mouse pos against the client rect of the popup. --- src/common/popupcmn.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/common/popupcmn.cpp b/src/common/popupcmn.cpp index e959f1a77538..e2d522cf2771 100644 --- a/src/common/popupcmn.cpp +++ b/src/common/popupcmn.cpp @@ -451,19 +451,23 @@ void wxPopupTransientWindow::OnIdle(wxIdleEvent& event) if (IsShown() && m_child) { - // Store the last mouse position to minimize the number of calls to - // wxFindWindowAtPoint() which are quite expensive. + // Store the last mouse position static wxPoint s_posLast; const wxPoint pos = wxGetMousePosition(); if ( pos != s_posLast ) { s_posLast = pos; - - wxWindow* const winUnderMouse = wxFindWindowAtPoint(pos); + + // DO NOT use wxFindWindowAtPoint() because if there're multiple top level windows, + // it will 'Find the deepest window at the given mouse position in screen coordinates', + // which is not the right one for this logic. + auto screen_rect = GetScreenRect(); + const auto mouse_within_me = (pos.x >= screen_rect.GetTopLeft().x and pos.y >= screen_rect.GetTopLeft().y and + pos.x <= screen_rect.GetBottomRight().x and pos.y <= screen_rect.GetBottomRight().y); // We release the mouse capture while the mouse is inside the popup // itself to allow using it normally with the controls inside it. - if ( wxGetTopLevelParent(winUnderMouse) == this ) + if ( mouse_within_me ) { if ( m_child->HasCapture() ) { From b678693a9040e480200107100057b282afd04a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 11:08:43 +0800 Subject: [PATCH 07/34] MacOSX: Make wxWebviewWebkit capture hotkeys less eagerly. On MacOSX, the wxWebviewWebkit will eagerly capture the hotkeys despite the fact that it might be the one in focus. This will interfere other widgets' functions. It should only handle the hotkeys when it is the firstResponder. More about firstResponder: https://developer.apple.com/documentation/appkit/nswindow/1419440-firstresponder --- src/osx/webview_webkit.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index bd8cadd53404..c40a22a73e9e 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -618,6 +618,10 @@ -(id)validRequestorForSendType:(NSString*)sendType returnType:(NSString*)returnT - (BOOL)performKeyEquivalent:(NSEvent *)event { + if (self.window.firstResponder != self) + { + return NO; + } if ([event modifierFlags] & NSCommandKeyMask) { switch ([event.characters characterAtIndex:0]) From 116734986ffbe8353cd66be9a7da1b626ec41ab6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 15:19:09 +0800 Subject: [PATCH 08/34] wxOSX: Add support for wxDropSource icons. The wxDropSource has API for icons of different dnd states indication. But it is not supported at all on cocoa. No icons at all on OSX. Now add the proper support for this API. --- include/wx/dnd.h | 4 +-- include/wx/osx/cocoa/private/dnd.h | 36 +++++++++++++++++++ src/osx/cocoa/dnd.mm | 58 ++++++++++++++++++++---------- src/osx/cocoa/window.mm | 19 ++++++++++ 4 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 include/wx/osx/cocoa/private/dnd.h diff --git a/include/wx/dnd.h b/include/wx/dnd.h index c43083645b5f..5faed56c16c8 100644 --- a/include/wx/dnd.h +++ b/include/wx/dnd.h @@ -95,7 +95,6 @@ class WXDLLIMPEXP_CORE wxDropSourceBase // give the default feedback virtual bool GiveFeedback(wxDragResult WXUNUSED(effect)) { return false; } -protected: const wxCursor& GetCursor(wxDragResult res) const { if ( res == wxDragCopy ) @@ -105,7 +104,8 @@ class WXDLLIMPEXP_CORE wxDropSourceBase else return m_cursorStop; } - + +protected: // the data we're dragging wxDataObject *m_data; diff --git a/include/wx/osx/cocoa/private/dnd.h b/include/wx/osx/cocoa/private/dnd.h new file mode 100644 index 000000000000..b7955da86c62 --- /dev/null +++ b/include/wx/osx/cocoa/private/dnd.h @@ -0,0 +1,36 @@ +#include "wx/wxprec.h" + +#if wxUSE_DRAG_AND_DROP || wxUSE_CLIPBOARD + +#ifndef WX_PRECOMP +#include "wx/object.h" +#endif + +#include "wx/dnd.h" + +#include "wx/osx/private.h" +#include "wx/osx/private/datatransfer.h" + +@interface DropSourceDelegate : NSObject +{ + BOOL dragFinished; + int resultCode; + wxDropSource* impl; + + // Flags for drag and drop operations (wxDrag_* ). + int m_dragFlags; + NSImage* m_copy_cursor; + NSImage* m_move_cursor; + NSImage* m_none_cursor; +} + +- (void)setImplementation:(nonnull wxDropSource *)dropSource flags:(int)flags; +- (BOOL)finished; +- (NSDragOperation)code; +- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context; +- (void)draggedImage:(nonnull NSImage *)anImage movedTo:(NSPoint)aPoint; +- (void)draggedImage:(nonnull NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation; +- (nullable NSImage*)cursorForStatus:(wxDragResult)status; +@end + +#endif \ No newline at end of file diff --git a/src/osx/cocoa/dnd.mm b/src/osx/cocoa/dnd.mm index 4f7a2242e6af..005f8b3e055b 100644 --- a/src/osx/cocoa/dnd.mm +++ b/src/osx/cocoa/dnd.mm @@ -17,6 +17,7 @@ #endif #include "wx/dnd.h" +#include "wx/osx/cocoa/private/dnd.h" #include "wx/clipbrd.h" #include "wx/filename.h" @@ -268,24 +269,6 @@ wxDragResult NSDragOperationToWxDragResult(NSDragOperation code) return wxDragNone; } -@interface DropSourceDelegate : NSObject -{ - BOOL dragFinished; - int resultCode; - wxDropSource* impl; - - // Flags for drag and drop operations (wxDrag_* ). - int m_dragFlags; -} - -- (void)setImplementation:(wxDropSource *)dropSource flags:(int)flags; -- (BOOL)finished; -- (NSDragOperation)code; -- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context; -- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint; -- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation; -@end - @implementation DropSourceDelegate - (id)init @@ -296,6 +279,9 @@ - (id)init resultCode = NSDragOperationNone; impl = 0; m_dragFlags = wxDrag_CopyOnly; + m_copy_cursor = nil; + m_move_cursor = nil; + m_none_cursor = nil; } return self; } @@ -397,6 +383,42 @@ - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDra dragFinished = YES; } +- (NSImage*)cursorForStatus:(wxDragResult)status +{ + NSImage* cursor_img = nil; + switch(status){ + case wxDragCopy: + cursor_img = m_copy_cursor; + break; + case wxDragMove: + cursor_img = m_move_cursor; + break; + default: + cursor_img = m_none_cursor; + break; + } + if (cursor_img != nil) + return cursor_img; + + wxCursor indicate_cursor = impl->GetCursor(status); + + if (indicate_cursor.IsOk()) { + NSCursor *cursor = (NSCursor*)indicate_cursor.GetHCURSOR(); + cursor_img = [[cursor image] retain]; + switch(status){ + case wxDragCopy: + m_copy_cursor = cursor_img; + break; + case wxDragMove: + m_move_cursor = cursor_img; + break; + default: + m_none_cursor = cursor_img; + break; + } + } + return cursor_img; +} @end wxDropTarget::wxDropTarget( wxDataObject *data ) diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 5c49221b0332..0e78a1108fbc 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -36,6 +36,9 @@ #if wxUSE_DRAG_AND_DROP #include "wx/dnd.h" #include "wx/clipbrd.h" + #ifdef __WXOSX_MAC__ + #include "wx/osx/cocoa/private/dnd.h" + #endif #endif #if wxUSE_TOOLTIPS @@ -1370,6 +1373,22 @@ When dragging the bottom part of the DND sample ("Drag text from here!") default : break; } + if (!entered){ + NSView* the_view = viewImpl->GetWXWidget(); + [sender enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent forView:the_view + classes:[NSArray arrayWithObject:[NSPasteboardItem class]] searchOptions:nil + usingBlock:^(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop) { + wxUnusedVar(idx); + NSRect theFrame = draggingItem.draggingFrame; + DropSourceDelegate* drop_src_delegate = sender.draggingSource; + NSImage* newDragImage = [drop_src_delegate cursorForStatus:result]; + if (newDragImage != nil){ + NSRect newFrame = NSMakeRect(theFrame.origin.x, theFrame.origin.y, newDragImage.size.width, newDragImage.size.height); + [draggingItem setDraggingFrame:newFrame contents:newDragImage]; + } + *stop = NO; + }]; + } return nsresult; } From a48678d21984275088d8b36ebc1f03e861766c7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 15:34:18 +0800 Subject: [PATCH 09/34] wxOSX: Relax DoDragDrop() constraints. DoDragDrop() not assert 'It must be called in response to a mouse down or drag event.'. But when the user start DnD something from a frame rendered by CEF or similar framework, it should be perfectly valid. --- src/osx/cocoa/dnd.mm | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/osx/cocoa/dnd.mm b/src/osx/cocoa/dnd.mm index 005f8b3e055b..a652894a0d37 100644 --- a/src/osx/cocoa/dnd.mm +++ b/src/osx/cocoa/dnd.mm @@ -522,8 +522,21 @@ - (nullable id)pasteboardPropertyListForType:(nonnull NSPasteboardType)type if (view) { NSEvent* theEvent = (NSEvent*)wxTheApp->MacGetCurrentEvent(); - wxASSERT_MSG(theEvent, "DoDragDrop must be called in response to a mouse down or drag event."); - + // relax the constraint set for DoDragDrop(). + // if the user start DnD something from a frame rendered by CEF or similar framework, it should be perfectly valid. + if (theEvent == nil){ + NSPoint mouse_location = [NSEvent mouseLocation]; + theEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged + location:mouse_location + modifierFlags:0 + timestamp: 0 + windowNumber: [NSWindow windowNumberAtPoint:mouse_location belowWindowWithWindowNumber:0] + context:nil + eventNumber: 0 + clickCount: 0 + pressure: 1.0]; + } + gCurrentSource = this; DropSourceDelegate* delegate = [[DropSourceDelegate alloc] init]; From 33c801b5b2f7f099d0bf325ac0a99d1952162498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 14:27:41 +0800 Subject: [PATCH 10/34] wxMSW: Optimize AlphaBlt. We found the bottleneck in this function. Optimize it to speed up things and it do fix problems observed in our app. --- src/msw/dc.cpp | 55 +++++++++----------------------------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/src/msw/dc.cpp b/src/msw/dc.cpp index 8646f9b822f6..d60a824e168c 100644 --- a/src/msw/dc.cpp +++ b/src/msw/dc.cpp @@ -2934,52 +2934,17 @@ static bool AlphaBlt(wxMSWDCImpl* dcDst, const wxBitmap& bmpDst = dcDst->GetSelectedBitmap(); if ( bmpDst.IsOk() && !bmpDst.HasAlpha() && bmpDst.GetDepth() == 32 ) { - // We need to deselect the bitmap from the memory DC it is - // currently selected into before modifying it. - wxBitmap bmpOld = bmpDst; - dcDst->DoSelect(wxNullBitmap); + wxBitmap bmp(dstWidth, dstHeight, 32); + wxMemoryDC dc(bmp); - // Notice the extra block: we must destroy wxAlphaPixelData - // before selecting the bitmap into the DC again. - { - // Since drawn bitmap can only partially overlap - // with destination bitmap we need to calculate - // efective drawing area location. - const wxRect rectDst(bmpOld.GetSize()); - const wxRect rectDrawn(x, y, dstWidth, dstHeight); - const wxRect r = rectDrawn.Intersect(rectDst); - - wxAlphaPixelData data(bmpOld); - if ( data ) - { - wxAlphaPixelData::Iterator p(data); - - p.Offset(data, r.GetLeft(), r.GetTop()); - for ( int old_y = 0; old_y < r.GetHeight(); old_y++ ) - { - wxAlphaPixelData::Iterator rowStart = p; - for ( int old_x = 0; old_x < r.GetWidth(); old_x++ ) - { - // We choose to use wxALPHA_TRANSPARENT instead - // of perhaps more logical wxALPHA_OPAQUE here - // to ensure that the bitmap remains the same - // as before, i.e. without any alpha at all. - p.Alpha() = wxALPHA_TRANSPARENT; - ++p; - } - - p = rowStart; - p.OffsetY(data, 1); - } - } - } - - // Using wxAlphaPixelData sets the internal "has alpha" flag - // which is usually what we need, but in this particular case - // we use it to get rid of alpha, not set it, so reset it back. - bmpOld.ResetAlpha(); - - dcDst->DoSelect(bmpOld); + // Fetch the content of the destination area into the temporary buffer. + wxRect r(x, y, dstWidth, dstHeight); + if (bmpDst.GetWidth() < x + dstWidth || bmpDst.GetHeight() < y + dstHeight) + return true; + dc.DrawBitmap(dcDst->DoGetAsBitmap(&r), 0, 0); + // Drawing the source over the temporary buffer. + dcDst->DoBlit(x, y, dstWidth, dstHeight, &dc, 0, 0); + return true; } #endif // wxHAS_RAW_BITMAP From 4f22df06bd295f170b4588331bbe62c309c2964c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 16:13:34 +0800 Subject: [PATCH 11/34] wxGTK: Add private options to bring back traditional scrollbar(always visible). GTK3 defaults to enable the 'overlay-scrolling' features, that is, only show scrollbar when the mouse/touching hover the scrollbar area. It is undesirable to many of our clients. Yet wxWidgets does NOT exposing any API for us to do the GTK customization. So we need to add our 'CMExtension' to workaround this problem. This MAY be a temporary solution. We may make a new patch in the future by introducing a new properly designed API to wxWidgets for this sort of native/per-platform customization. --- include/wx/window.h | 6 ++++++ src/gtk/window.cpp | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/include/wx/window.h b/include/wx/window.h index d7283458b1dd..dc5af35e976c 100644 --- a/include/wx/window.h +++ b/include/wx/window.h @@ -49,6 +49,12 @@ #define wxUSE_MENUS_NATIVE wxUSE_MENUS #endif // __WXUNIVERSAL__/!__WXUNIVERSAL__ +#ifdef __WXGTK__ +namespace CMExtension { + extern bool g_need_traditional_scrollbar; +} +#endif + // ---------------------------------------------------------------------------- // forward declarations // ---------------------------------------------------------------------------- diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index aeebc9c0c044..7451f51fe76d 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -72,6 +72,10 @@ typedef guint KeySym; #define PANGO_VERSION_CHECK(a,b,c) 0 #endif +namespace CMExtension { + bool g_need_traditional_scrollbar = false; +} + //----------------------------------------------------------------------------- // documentation on internals //----------------------------------------------------------------------------- @@ -2764,6 +2768,9 @@ void wxWindowGTK::GTKCreateScrolledWindowWith(GtkWidget* view) m_widget = gtk_scrolled_window_new( NULL, NULL ); GtkScrolledWindow *scrolledWindow = GTK_SCROLLED_WINDOW(m_widget); + + if (CMExtension::g_need_traditional_scrollbar) + g_object_set(scrolledWindow, "overlay-scrolling", false, NULL); // There is a conflict with default bindings at GTK+ // level between scrolled windows and notebooks both of which want to use From be09c5f47385e9d843fa59133cb0f59a17b823ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Thu, 9 May 2024 15:07:52 +0800 Subject: [PATCH 12/34] Fix DND reordering of generic verion wxHeaderCtrl The old DND reorder implementation is buggy and wrong in many ways. This is a rework that fix almost everything with better user experiences, better dropped pos indication. ToDo: Better explanation code comments for several main changes. I'm now too busy to leave an explanation with enough details. --- include/wx/generic/headerctrlg.h | 13 ++- src/generic/headerctrlg.cpp | 160 ++++++++++++++++++++++++------- 2 files changed, 137 insertions(+), 36 deletions(-) diff --git a/include/wx/generic/headerctrlg.h b/include/wx/generic/headerctrlg.h index 77da772496cb..ff47ea4be54f 100644 --- a/include/wx/generic/headerctrlg.h +++ b/include/wx/generic/headerctrlg.h @@ -52,6 +52,12 @@ class WXDLLIMPEXP_CORE wxHeaderCtrl : public wxHeaderCtrlBase private: + enum class Region{ + NoWhere, + LeftHalf, + RightHalf, + Separator + }; // implement base class pure virtuals virtual void DoSetCount(unsigned int count) wxOVERRIDE; virtual unsigned int DoGetCount() const wxOVERRIDE; @@ -96,11 +102,14 @@ class WXDLLIMPEXP_CORE wxHeaderCtrl : public wxHeaderCtrlBase // position is near the divider at the right end of this column (notice // that this means that we return column 0 even if the position is over // column 1 but close enough to the divider separating it from column 0) - unsigned int FindColumnAtPoint(int x, bool *onSeparator = NULL) const; + unsigned int FindColumnAtPoint(int x, Region& pos_region) const; // return the result of FindColumnAtPoint() if it is a valid column, // otherwise the index of the last (rightmost) displayed column - unsigned int FindColumnClosestToPoint(int xPhysical) const; + unsigned int FindColumnClosestToPoint(int xPhysical, Region& pos_region) const; + + unsigned int FindColumnAfter(const unsigned int column_idx) const; + unsigned int FindColumnBefore(const unsigned int column_idx) const; // return true if a drag resizing operation is currently in progress bool IsResizing() const; diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 541bef81a356..49e650836c40 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -156,7 +156,7 @@ int wxHeaderCtrl::GetColStart(unsigned int idx) const if ( col.IsShown() ) pos += col.GetWidth(); } - + return pos; } @@ -167,7 +167,7 @@ int wxHeaderCtrl::GetColEnd(unsigned int idx) const return x + GetColumn(idx).GetWidth(); } -unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, bool *onSeparator) const +unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, Region& pos_region) const { int pos = 0; int xLogical = xPhysical - m_scrollOffset; @@ -178,7 +178,8 @@ unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, bool *onSeparator) c const wxHeaderColumn& col = GetColumn(idx); if ( col.IsHidden() ) continue; - + + const auto last_col_end = pos; pos += col.GetWidth(); // TODO: don't hardcode sensitivity @@ -188,33 +189,34 @@ unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, bool *onSeparator) c // line separating it from the next column if ( col.IsResizeable() && abs(xLogical - pos) < separatorClickMargin ) { - if ( onSeparator ) - *onSeparator = true; + pos_region = Region::Separator; return idx; } // inside this column? - if ( xLogical < pos ) + if ( xLogical < pos && xLogical >= last_col_end) { - if ( onSeparator ) - *onSeparator = false; + if ( xLogical - last_col_end < pos - xLogical) + pos_region = Region::LeftHalf; + else + pos_region = Region::RightHalf; return idx; } } - if ( onSeparator ) - *onSeparator = false; + pos_region = Region::NoWhere; return COL_NONE; } -unsigned int wxHeaderCtrl::FindColumnClosestToPoint(int xPhysical) const +unsigned int wxHeaderCtrl::FindColumnClosestToPoint(int xPhysical, Region& pos_region) const { - const unsigned int colIndexAtPoint = FindColumnAtPoint(xPhysical); - + const unsigned int colIndexAtPoint = FindColumnAtPoint(xPhysical, pos_region); + // valid column found? if ( colIndexAtPoint != COL_NONE ) return colIndexAtPoint; - + + pos_region = Region::NoWhere; // if not, xPhysical must be beyond the rightmost column, so return its // index instead -- if we have it const unsigned int count = GetColumnCount(); @@ -224,6 +226,37 @@ unsigned int wxHeaderCtrl::FindColumnClosestToPoint(int xPhysical) const return m_colIndices[count - 1]; } +unsigned int wxHeaderCtrl::FindColumnAfter(const unsigned int column_idx) const{ + const unsigned count = GetColumnCount(); + auto after_idx = COL_NONE; + //auto target_column_found = false; + for ( unsigned n = 0; n < count; n++ ) + { + if (m_colIndices[n] == column_idx && n + 1 < count ){ + after_idx = m_colIndices[n + 1]; + break; + } + } + return after_idx; +} + +unsigned int wxHeaderCtrl::FindColumnBefore(const unsigned int column_idx) const{ + const unsigned count = GetColumnCount(); + auto before_idx = COL_NONE; + auto target_column_found = false; + for ( unsigned n = 0; n < count; n++ ) + { + if (m_colIndices[n] == column_idx){ + target_column_found = true; + break; + } + before_idx = m_colIndices[n]; + } + if (not target_column_found) + before_idx = COL_NONE; + return before_idx; +} + // ---------------------------------------------------------------------------- // wxHeaderCtrl repainting // ---------------------------------------------------------------------------- @@ -389,14 +422,21 @@ void wxHeaderCtrl::UpdateReorderingMarker(int xPhysical) // and also a hint indicating where it is going to be inserted if it's // dropped now - unsigned int col = FindColumnClosestToPoint(xPhysical); + auto hover_region = Region::NoWhere; + unsigned int col = FindColumnClosestToPoint(xPhysical, hover_region); if ( col != COL_NONE ) { static const int DROP_MARKER_WIDTH = 4; dc.SetBrush(*wxBLUE); - dc.DrawRectangle(GetColEnd(col) - DROP_MARKER_WIDTH/2, 0, - DROP_MARKER_WIDTH, y); + if (hover_region == Region::LeftHalf){ + dc.DrawRectangle(GetColStart(col) - DROP_MARKER_WIDTH/2, 0, + DROP_MARKER_WIDTH, y); + } + else if (hover_region != Region::NoWhere){ + dc.DrawRectangle(GetColEnd(col) - DROP_MARKER_WIDTH/2, 0, + DROP_MARKER_WIDTH, y); + } } } @@ -432,10 +472,39 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) ReleaseMouse(); const int colOld = m_colBeingReordered; - const unsigned colNew = FindColumnClosestToPoint(xPhysical); + auto dropped_region = Region::NoWhere; + unsigned colNew = FindColumnClosestToPoint(xPhysical, dropped_region); m_colBeingReordered = COL_NONE; - + auto reg_str = std::string{}; + switch(dropped_region){ + case Region::NoWhere: + reg_str = "NoWhere"; + break; + case Region::LeftHalf: + reg_str = "LeftHalf"; + break; + case Region::RightHalf: + reg_str = "RightHalf"; + break; + case Region::Separator: + reg_str = "Separator"; + break; + } + //printf("Dropped to col %d, region : %s\n", colNew, reg_str.c_str()); + // The actual dropped pos should not simply be colNew, it should also depends on + // which region the user dropped in. + // if the user dropped the col on the RightHalf, the colNew should the one next to it on the right. + auto located_by_previous_col = false; + if ((dropped_region == Region::RightHalf || dropped_region == Region::Separator) && colNew != COL_NONE){ + //printf("Looking for the next column pos to inserted for col %d\n", colNew); + auto nextColumn = FindColumnAfter(colNew); + if (nextColumn != COL_NONE){ + //printf("Next col for col %d is %d\n", colNew, nextColumn); + colNew = nextColumn; + located_by_previous_col = true; + } + } // mouse drag must be longer than min distance m_dragOffset if ( xPhysical - GetColStart(colOld) == m_dragOffset ) { @@ -447,22 +516,45 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) { return false; } - if ( static_cast(colNew) != colOld ) { wxHeaderCtrlEvent event(wxEVT_HEADER_END_REORDER, GetId()); event.SetEventObject(this); event.SetColumn(colOld); - - const unsigned pos = GetColumnPos(colNew); - event.SetNewOrder(pos); - - if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) - { - // do reorder the columns - DoMoveCol(colOld, pos); + auto new_pos = GetColumnPos(colNew); + auto old_pos = GetColumnPos(colOld); + // when the user drag one col from left-to-right(i.e. from low pos to higher one), + // the actual pos to dropped should be the one just before colNew, i.e. the one on the left hand side of colNew. + auto move_left = false; + if (old_pos < new_pos) { + // the last column is a bit special, we should consider it differently. + if (new_pos != GetColumnCount() - 1 || located_by_previous_col || dropped_region == Region::LeftHalf){ + colNew = FindColumnBefore(colNew); + assert(colNew != COL_NONE); + new_pos = GetColumnPos(colNew); + } + move_left = true; + } + // Simulate the reorder before we actually accept it. + // Why??? + // ToDo: better code comments to explain the logic, a diagram should be better + auto new_colIndices = m_colIndices; + MoveColumnInOrderArray(new_colIndices, colOld, new_pos); + auto old_after_pos = new_colIndices.Index(colNew); + auto new_after_pos = new_colIndices.Index(colOld); + if (old_after_pos != old_pos || new_after_pos != new_pos || move_left || old_pos > new_pos){ + event.SetNewOrder(new_pos); + + //printf("Move col %d to %d, pos to %d\n", colOld, colNew, new_pos); + if ( GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) + { + // do reorder the columns + DoMoveCol(colOld, new_pos); + } } } + //else + //printf("ColNew and ColOld the same, do not reordering.\n"); // whether we moved the column or not, the user did move the mouse and so // did try to do it so return true @@ -651,10 +743,10 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) // find if the event is over a column at all - bool onSeparator; + Region mouse_region = Region::NoWhere; const unsigned col = mevent.Leaving() - ? (onSeparator = false, COL_NONE) - : FindColumnAtPoint(xPhysical, &onSeparator); + ? COL_NONE + : FindColumnAtPoint(xPhysical, mouse_region); // update the highlighted column if it changed @@ -670,7 +762,7 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) // update mouse cursor as it moves around if ( mevent.Moving() ) { - SetCursor(onSeparator ? wxCursor(wxCURSOR_SIZEWE) : wxNullCursor); + SetCursor(mouse_region == Region::Separator ? wxCursor(wxCURSOR_SIZEWE) : wxNullCursor); return; } @@ -682,7 +774,7 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) // enter various dragging modes on left mouse press if ( mevent.LeftDown() ) { - if ( onSeparator ) + if ( mouse_region == Region::Separator ) { // start resizing the column wxASSERT_MSG( !IsResizing(), "reentering column resize mode?" ); @@ -712,7 +804,7 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) { case wxMOUSE_BTN_LEFT: // treat left double clicks on separator specially - if ( onSeparator && dblclk ) + if ( mouse_region == Region::Separator && dblclk ) { evtType = wxEVT_HEADER_SEPARATOR_DCLICK; m_wasSeparatorDClick = true; From ffa3bb78e6a386ddffb3996f148b02b45704a7f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 13 May 2024 10:37:17 +0800 Subject: [PATCH 13/34] Revert the old correct behavior that allow application to decide whether reordering is allowed. --- src/generic/headerctrlg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 49e650836c40..26c1423e3468 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -546,7 +546,7 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) event.SetNewOrder(new_pos); //printf("Move col %d to %d, pos to %d\n", colOld, colNew, new_pos); - if ( GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) + if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) { // do reorder the columns DoMoveCol(colOld, new_pos); From e3f8306eaee1481174ae90f87ff974e80391d629 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 13 May 2024 15:32:17 +0800 Subject: [PATCH 14/34] When the column header is dragged pass the leftmost/rightmost column, make it a no-op, i.e. refuse the reroder. The old behavior is to position the dragged column to the rightmost. It is counter-intuitive. Forbid the reordering make more sense and is a de facto accepted standard. --- src/generic/headerctrlg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 26c1423e3468..26b373b01e4c 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -516,7 +516,7 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) { return false; } - if ( static_cast(colNew) != colOld ) + if ( static_cast(colNew) != colOld && dropped_region != Region::NoWhere ) { wxHeaderCtrlEvent event(wxEVT_HEADER_END_REORDER, GetId()); event.SetEventObject(this); From b968c7cfa0b72fd2a3bfa84d4f696706e1b06e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Fri, 31 May 2024 11:49:03 +0800 Subject: [PATCH 15/34] MacOS: Catch all exceptions thrown by wxCFEventLoop::OSXDoRun() for cocoa modal dialog. The cocoa framework is not bug-free. Some version(I'd called it buggy) may throw unexpected exceptions. In order to avoid the ShowModal() to died and break the modal stack, and finally comes you a assertion failure on broken modal stack, we should catch exceptions here. --- src/osx/cocoa/evtloop.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/osx/cocoa/evtloop.mm b/src/osx/cocoa/evtloop.mm index 954964af1173..1c1fed003b27 100644 --- a/src/osx/cocoa/evtloop.mm +++ b/src/osx/cocoa/evtloop.mm @@ -434,7 +434,15 @@ static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat) if ( m_modalWindow ) { BeginModalSession(m_modalWindow); - wxCFEventLoop::OSXDoRun(); + while(true){ + try { + wxCFEventLoop::OSXDoRun(); + break; + } + catch(...){ + printf("wxModalEventLoop::OSXDoRun caught unknown exception\n"); + } + } EndModalSession(); } else From dd0c776a4e67af74810cb61627bd88cb469ec3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Thu, 20 Jun 2024 16:45:42 +0800 Subject: [PATCH 16/34] Fix keyboard focus problem when embedd CEF window. When wxGTKWindow::SetFocus, we will bring up the toplevel window to the user before we set the focus to the child widget. And now, we no longer need to consider whether gtk_window_is_active(tlw). Because of these two reasons: 1. if the gtk_window_is_active(tlw), gtk_window_preset() don't harm anyway; 2. when foreign XWindow embedded(not via GtkPlug), maybe use XWindow controlled by GDK as their parent, for example, a CEF browser window(which is now an XWindow) take ours as parent, our tlw active state maybe altered(when CEF window take the keyboard focus) and unknown to us. i.e. gtk_window_is_active(tlw) may still true while the focus is already taken by the CEF window. we need to force to bring up our tlw to the user at this moment. --- src/gtk/window.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index 7451f51fe76d..32b5060208d1 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -4866,7 +4866,15 @@ void wxWindowGTK::SetFocus() // But avoid activating if tlw is not yet shown, as that will // cause it to be immediately shown. GtkWidget* tlw = gtk_widget_get_ancestor(m_widget, GTK_TYPE_WINDOW); - if (tlw && gtk_widget_get_visible(tlw) && !gtk_window_is_active(GTK_WINDOW(tlw))) + if (tlw && gtk_widget_get_visible(tlw)) + // we don't need to consider whether the tlw is active or not, + // for these two reasons: + // 1. if the gtk_window_is_active(tlw), gtk_window_preset() don't harm anyway; + // 2. when foreign XWindow embedded(not via GtkPlug), maybe use XWindow controlled by GDK as their parent, + // for example, a CEF browser window(which is now an XWindow) take ours as parent, + // our tlw active state maybe altered(when CEF window take the keyboard focus) and unknown to us. + // i.e. gtk_window_is_active(tlw) may still true while the focus is already taken by the CEF window. + // we need to force to bring up our tlw to the user at this moment. gtk_window_present(GTK_WINDOW(tlw)); GtkWidget *widget = m_wxwindow ? m_wxwindow : m_focusWidget; From 9f3e2cccea7404769e3a0ec348036252033346ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Fri, 16 Jun 2023 15:55:59 +0800 Subject: [PATCH 17/34] Fix wxRibbonButtonBar's layout overall size computation. Since we've already had CalculateOverallSize, we should avoid duplicate the error-prone algorithm here. It has bug found by the assertion within TryCollapseLayout in our application. --- src/ribbon/buttonbar.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ribbon/buttonbar.cpp b/src/ribbon/buttonbar.cpp index 98b559538e54..6f4aad921043 100644 --- a/src/ribbon/buttonbar.cpp +++ b/src/ribbon/buttonbar.cpp @@ -1066,7 +1066,6 @@ void wxRibbonButtonBar::MakeLayouts() // small buttons small, stacked vertically wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout; wxPoint cursor(0, 0); - layout->overall_size.SetHeight(0); for(btn_i = 0; btn_i < btn_count; ++btn_i) { wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i); @@ -1102,8 +1101,7 @@ void wxRibbonButtonBar::MakeLayouts() } layout->buttons.Add(instance); } - layout->overall_size.SetHeight(available_height); - layout->overall_size.SetWidth(cursor.x + stacked_width); + layout->CalculateOverallSize(); m_layouts.Add(layout); } if(btn_count >= 2) From 091ed36265477dff82859eba704e8ae5c66ba255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 16:23:20 +0800 Subject: [PATCH 18/34] wxGTK: Fix UI refreshing delay when dragging the header border of generic wxDataViewCtrl to resize column. --- src/generic/headerctrlg.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 79a31a0eb285..541bef81a356 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -350,6 +350,7 @@ void wxHeaderCtrl::StartOrContinueResizing(unsigned int col, int xPhysical) //else: we had already done the above when we started } + RefreshColsAfter(col); } void wxHeaderCtrl::EndResizing(int xPhysical) From 391aff4aa72053157a2261760b58bffb3fc5e332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 14:10:37 +0800 Subject: [PATCH 19/34] wxGTK: Fix improper wxEVT_LEAVE_WINDOW event Do Not emit wxEVT_LEAVE_WINDOW when GdkEventCrossing mode is GDK_CROSSING_GRAB or GDK_CROSSING_GTK_GRAB --- src/gtk/window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index 2730674e22e7..eff794d5f74b 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -2273,7 +2273,7 @@ gtk_window_leave_callback( GtkWidget*, win->GTKUpdateCursor(); // Event was emitted after an ungrab - if (gdk_event->mode != GDK_CROSSING_NORMAL) return FALSE; + if (gdk_event->mode != GDK_CROSSING_NORMAL && gdk_event->mode != GDK_CROSSING_GRAB && gdk_event->mode != GDK_CROSSING_GTK_GRAB) return FALSE; wxMouseEvent event( wxEVT_LEAVE_WINDOW ); InitMouseEvent(win, event, gdk_event); From 3021cbcd635f6e66e21c154b3f8f45e481bd602b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 15:20:18 +0800 Subject: [PATCH 20/34] MacOSX: Change the two dummy messages to use systemUpTime as timestamp to fix CEF renderer process crash. Begin with Chromium 97, EventLatencyTracingRecorder was added to trace the performance. And the timestamp 0 used will lead to 'DCHECK(!dispatch_timestamp.is_null());' in function EventLatencyTracingRecorder::RecordEventLatencyTraceEvent see: https://github.com/chromium/chromium/blob/109.0.5414.120/cc/metrics/event_latency_tracing_recorder.cc Way to reproduce the crash: Right click to popup the context-menu created by CEF. Then the CEF renderer process will crash. Set the timestamp to [[NSProcessInfo processInfo] systemUptime] to fix it. --- src/osx/cocoa/evtloop.mm | 2 +- src/osx/cocoa/utils.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/osx/cocoa/evtloop.mm b/src/osx/cocoa/evtloop.mm index 8820d597012f..399e1beae92c 100644 --- a/src/osx/cocoa/evtloop.mm +++ b/src/osx/cocoa/evtloop.mm @@ -380,7 +380,7 @@ static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat) NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0.0, 0.0) modifierFlags:0 - timestamp:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:0 context:nil subtype:0 data1:0 data2:0]; diff --git a/src/osx/cocoa/utils.mm b/src/osx/cocoa/utils.mm index 66d663325f83..81ce3a686313 100644 --- a/src/osx/cocoa/utils.mm +++ b/src/osx/cocoa/utils.mm @@ -421,7 +421,7 @@ - (void)sendEvent:(NSEvent *)anEvent NSEvent *event = [NSEvent otherEventWithType:NSApplicationDefined location:NSMakePoint(0.0, 0.0) modifierFlags:0 - timestamp:0 + timestamp:[[NSProcessInfo processInfo] systemUptime] windowNumber:0 context:nil subtype:0 data1:0 data2:0]; From 41a1e0c1b549a6f08dd7672c46b3a1ae04da0a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 14:37:38 +0800 Subject: [PATCH 21/34] MacOSX: Keep DoDragDrop() from re-entering to enable a proper working CEF. DoDragDrop() may make CEF cease to work. One way to reproduce the problem: Do drag-n-drop repeatly, that is, start drag immediately after one drop, then there're chances we can see large amount of DoDragDrop() in the callstack of the UI-thread. And finally leads to stack overflow. Messages processed by CEF are handled when wxEVT_IDLE is triggered. The basic loop are: -> Step1, cocoa call OSXDefaultModeObserverCallBack(in src/osx/core/evtloop_cf.cpp) -> Step2, wxEVT_IDLE is triggered. -> Step3, wxWebViewChroium::OnIdle() takes the cake. -> Step4, CefDoMessageLoopWork() do the jobs for CEF. If DoDragDrop() is invoked during CefDoMessageLoopWork(), and together there comes a recursive call to it, then the previous DoDragDrop() can't return anymore, and will, in turn, prevent CefDoMessageLoopWork() from returning to the caller. And finally overflow the stack. And now we add the global dnd flag to stay away from this kind of tragic. --- src/osx/cocoa/dnd.mm | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/osx/cocoa/dnd.mm b/src/osx/cocoa/dnd.mm index d8786cc2de76..4f7a2242e6af 100644 --- a/src/osx/cocoa/dnd.mm +++ b/src/osx/cocoa/dnd.mm @@ -486,11 +486,16 @@ - (nullable id)pasteboardPropertyListForType:(nonnull NSPasteboardType)type wxDragResult wxDropSource::DoDragDrop(int flags) { wxASSERT_MSG( m_data, wxT("Drop source: no data") ); - + static bool g_in_dnd = false; + wxDragResult result = wxDragNone; if ((m_data == NULL) || (m_data->GetFormatCount() == 0)) return result; - + + if (g_in_dnd) + return wxDragNone; + + g_in_dnd = true; NSView* view = m_window->GetPeer()->GetWXWidget(); if (view) { @@ -555,8 +560,8 @@ - (nullable id)pasteboardPropertyListForType:(nonnull NSPasteboardType)type gCurrentSource = NULL; } - - + + g_in_dnd = false; return result; } From a92922ddfe84ab0f8a7bda5031f1287e98facb30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 15:59:16 +0800 Subject: [PATCH 22/34] wxOSX: Fix wxPopupTransientWindow's mouse event handling. In MacOSX/cocoa, wxPopupTransientWindow needs to do mouse capture handling during OnIdle(). But it's done wrongly when decide whether the mouse is within the popup. Not fixed with simply checking the mouse pos against the client rect of the popup. --- src/common/popupcmn.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/common/popupcmn.cpp b/src/common/popupcmn.cpp index e959f1a77538..e2d522cf2771 100644 --- a/src/common/popupcmn.cpp +++ b/src/common/popupcmn.cpp @@ -451,19 +451,23 @@ void wxPopupTransientWindow::OnIdle(wxIdleEvent& event) if (IsShown() && m_child) { - // Store the last mouse position to minimize the number of calls to - // wxFindWindowAtPoint() which are quite expensive. + // Store the last mouse position static wxPoint s_posLast; const wxPoint pos = wxGetMousePosition(); if ( pos != s_posLast ) { s_posLast = pos; - - wxWindow* const winUnderMouse = wxFindWindowAtPoint(pos); + + // DO NOT use wxFindWindowAtPoint() because if there're multiple top level windows, + // it will 'Find the deepest window at the given mouse position in screen coordinates', + // which is not the right one for this logic. + auto screen_rect = GetScreenRect(); + const auto mouse_within_me = (pos.x >= screen_rect.GetTopLeft().x and pos.y >= screen_rect.GetTopLeft().y and + pos.x <= screen_rect.GetBottomRight().x and pos.y <= screen_rect.GetBottomRight().y); // We release the mouse capture while the mouse is inside the popup // itself to allow using it normally with the controls inside it. - if ( wxGetTopLevelParent(winUnderMouse) == this ) + if ( mouse_within_me ) { if ( m_child->HasCapture() ) { From 4647c3cefe1039d6fbf752937e58fd096913f0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 29 Apr 2024 11:08:43 +0800 Subject: [PATCH 23/34] MacOSX: Make wxWebviewWebkit capture hotkeys less eagerly. On MacOSX, the wxWebviewWebkit will eagerly capture the hotkeys despite the fact that it might be the one in focus. This will interfere other widgets' functions. It should only handle the hotkeys when it is the firstResponder. More about firstResponder: https://developer.apple.com/documentation/appkit/nswindow/1419440-firstresponder --- src/osx/webview_webkit.mm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/osx/webview_webkit.mm b/src/osx/webview_webkit.mm index bd8cadd53404..c40a22a73e9e 100644 --- a/src/osx/webview_webkit.mm +++ b/src/osx/webview_webkit.mm @@ -618,6 +618,10 @@ -(id)validRequestorForSendType:(NSString*)sendType returnType:(NSString*)returnT - (BOOL)performKeyEquivalent:(NSEvent *)event { + if (self.window.firstResponder != self) + { + return NO; + } if ([event modifierFlags] & NSCommandKeyMask) { switch ([event.characters characterAtIndex:0]) From 19f05115fb7e2d5a84a3002e766b4ebd5550ca8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 15:19:09 +0800 Subject: [PATCH 24/34] wxOSX: Add support for wxDropSource icons. The wxDropSource has API for icons of different dnd states indication. But it is not supported at all on cocoa. No icons at all on OSX. Now add the proper support for this API. --- include/wx/dnd.h | 4 +-- include/wx/osx/cocoa/private/dnd.h | 36 +++++++++++++++++++ src/osx/cocoa/dnd.mm | 58 ++++++++++++++++++++---------- src/osx/cocoa/window.mm | 19 ++++++++++ 4 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 include/wx/osx/cocoa/private/dnd.h diff --git a/include/wx/dnd.h b/include/wx/dnd.h index c43083645b5f..5faed56c16c8 100644 --- a/include/wx/dnd.h +++ b/include/wx/dnd.h @@ -95,7 +95,6 @@ class WXDLLIMPEXP_CORE wxDropSourceBase // give the default feedback virtual bool GiveFeedback(wxDragResult WXUNUSED(effect)) { return false; } -protected: const wxCursor& GetCursor(wxDragResult res) const { if ( res == wxDragCopy ) @@ -105,7 +104,8 @@ class WXDLLIMPEXP_CORE wxDropSourceBase else return m_cursorStop; } - + +protected: // the data we're dragging wxDataObject *m_data; diff --git a/include/wx/osx/cocoa/private/dnd.h b/include/wx/osx/cocoa/private/dnd.h new file mode 100644 index 000000000000..b7955da86c62 --- /dev/null +++ b/include/wx/osx/cocoa/private/dnd.h @@ -0,0 +1,36 @@ +#include "wx/wxprec.h" + +#if wxUSE_DRAG_AND_DROP || wxUSE_CLIPBOARD + +#ifndef WX_PRECOMP +#include "wx/object.h" +#endif + +#include "wx/dnd.h" + +#include "wx/osx/private.h" +#include "wx/osx/private/datatransfer.h" + +@interface DropSourceDelegate : NSObject +{ + BOOL dragFinished; + int resultCode; + wxDropSource* impl; + + // Flags for drag and drop operations (wxDrag_* ). + int m_dragFlags; + NSImage* m_copy_cursor; + NSImage* m_move_cursor; + NSImage* m_none_cursor; +} + +- (void)setImplementation:(nonnull wxDropSource *)dropSource flags:(int)flags; +- (BOOL)finished; +- (NSDragOperation)code; +- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context; +- (void)draggedImage:(nonnull NSImage *)anImage movedTo:(NSPoint)aPoint; +- (void)draggedImage:(nonnull NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation; +- (nullable NSImage*)cursorForStatus:(wxDragResult)status; +@end + +#endif \ No newline at end of file diff --git a/src/osx/cocoa/dnd.mm b/src/osx/cocoa/dnd.mm index 4f7a2242e6af..005f8b3e055b 100644 --- a/src/osx/cocoa/dnd.mm +++ b/src/osx/cocoa/dnd.mm @@ -17,6 +17,7 @@ #endif #include "wx/dnd.h" +#include "wx/osx/cocoa/private/dnd.h" #include "wx/clipbrd.h" #include "wx/filename.h" @@ -268,24 +269,6 @@ wxDragResult NSDragOperationToWxDragResult(NSDragOperation code) return wxDragNone; } -@interface DropSourceDelegate : NSObject -{ - BOOL dragFinished; - int resultCode; - wxDropSource* impl; - - // Flags for drag and drop operations (wxDrag_* ). - int m_dragFlags; -} - -- (void)setImplementation:(wxDropSource *)dropSource flags:(int)flags; -- (BOOL)finished; -- (NSDragOperation)code; -- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context; -- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint; -- (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation; -@end - @implementation DropSourceDelegate - (id)init @@ -296,6 +279,9 @@ - (id)init resultCode = NSDragOperationNone; impl = 0; m_dragFlags = wxDrag_CopyOnly; + m_copy_cursor = nil; + m_move_cursor = nil; + m_none_cursor = nil; } return self; } @@ -397,6 +383,42 @@ - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDra dragFinished = YES; } +- (NSImage*)cursorForStatus:(wxDragResult)status +{ + NSImage* cursor_img = nil; + switch(status){ + case wxDragCopy: + cursor_img = m_copy_cursor; + break; + case wxDragMove: + cursor_img = m_move_cursor; + break; + default: + cursor_img = m_none_cursor; + break; + } + if (cursor_img != nil) + return cursor_img; + + wxCursor indicate_cursor = impl->GetCursor(status); + + if (indicate_cursor.IsOk()) { + NSCursor *cursor = (NSCursor*)indicate_cursor.GetHCURSOR(); + cursor_img = [[cursor image] retain]; + switch(status){ + case wxDragCopy: + m_copy_cursor = cursor_img; + break; + case wxDragMove: + m_move_cursor = cursor_img; + break; + default: + m_none_cursor = cursor_img; + break; + } + } + return cursor_img; +} @end wxDropTarget::wxDropTarget( wxDataObject *data ) diff --git a/src/osx/cocoa/window.mm b/src/osx/cocoa/window.mm index 6e5256f35763..e47b1aaf3f79 100644 --- a/src/osx/cocoa/window.mm +++ b/src/osx/cocoa/window.mm @@ -37,6 +37,9 @@ #if wxUSE_DRAG_AND_DROP #include "wx/dnd.h" #include "wx/clipbrd.h" + #ifdef __WXOSX_MAC__ + #include "wx/osx/cocoa/private/dnd.h" + #endif #endif #if wxUSE_TOOLTIPS @@ -1374,6 +1377,22 @@ When dragging the bottom part of the DND sample ("Drag text from here!") default : break; } + if (!entered){ + NSView* the_view = viewImpl->GetWXWidget(); + [sender enumerateDraggingItemsWithOptions:NSDraggingItemEnumerationConcurrent forView:the_view + classes:[NSArray arrayWithObject:[NSPasteboardItem class]] searchOptions:nil + usingBlock:^(NSDraggingItem *draggingItem, NSInteger idx, BOOL *stop) { + wxUnusedVar(idx); + NSRect theFrame = draggingItem.draggingFrame; + DropSourceDelegate* drop_src_delegate = sender.draggingSource; + NSImage* newDragImage = [drop_src_delegate cursorForStatus:result]; + if (newDragImage != nil){ + NSRect newFrame = NSMakeRect(theFrame.origin.x, theFrame.origin.y, newDragImage.size.width, newDragImage.size.height); + [draggingItem setDraggingFrame:newFrame contents:newDragImage]; + } + *stop = NO; + }]; + } return nsresult; } From b77c1b4a97c280f1780ffeb62b14c68ffa7e4b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 15:34:18 +0800 Subject: [PATCH 25/34] wxOSX: Relax DoDragDrop() constraints. DoDragDrop() not assert 'It must be called in response to a mouse down or drag event.'. But when the user start DnD something from a frame rendered by CEF or similar framework, it should be perfectly valid. --- src/osx/cocoa/dnd.mm | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/osx/cocoa/dnd.mm b/src/osx/cocoa/dnd.mm index 005f8b3e055b..a652894a0d37 100644 --- a/src/osx/cocoa/dnd.mm +++ b/src/osx/cocoa/dnd.mm @@ -522,8 +522,21 @@ - (nullable id)pasteboardPropertyListForType:(nonnull NSPasteboardType)type if (view) { NSEvent* theEvent = (NSEvent*)wxTheApp->MacGetCurrentEvent(); - wxASSERT_MSG(theEvent, "DoDragDrop must be called in response to a mouse down or drag event."); - + // relax the constraint set for DoDragDrop(). + // if the user start DnD something from a frame rendered by CEF or similar framework, it should be perfectly valid. + if (theEvent == nil){ + NSPoint mouse_location = [NSEvent mouseLocation]; + theEvent = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDragged + location:mouse_location + modifierFlags:0 + timestamp: 0 + windowNumber: [NSWindow windowNumberAtPoint:mouse_location belowWindowWithWindowNumber:0] + context:nil + eventNumber: 0 + clickCount: 0 + pressure: 1.0]; + } + gCurrentSource = this; DropSourceDelegate* delegate = [[DropSourceDelegate alloc] init]; From 3341263355f2356d305c2cd8ee05fb4111583d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 14:27:41 +0800 Subject: [PATCH 26/34] wxMSW: Optimize AlphaBlt. We found the bottleneck in this function. Optimize it to speed up things and it do fix problems observed in our app. --- src/msw/dc.cpp | 55 +++++++++----------------------------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/src/msw/dc.cpp b/src/msw/dc.cpp index 8646f9b822f6..d60a824e168c 100644 --- a/src/msw/dc.cpp +++ b/src/msw/dc.cpp @@ -2934,52 +2934,17 @@ static bool AlphaBlt(wxMSWDCImpl* dcDst, const wxBitmap& bmpDst = dcDst->GetSelectedBitmap(); if ( bmpDst.IsOk() && !bmpDst.HasAlpha() && bmpDst.GetDepth() == 32 ) { - // We need to deselect the bitmap from the memory DC it is - // currently selected into before modifying it. - wxBitmap bmpOld = bmpDst; - dcDst->DoSelect(wxNullBitmap); + wxBitmap bmp(dstWidth, dstHeight, 32); + wxMemoryDC dc(bmp); - // Notice the extra block: we must destroy wxAlphaPixelData - // before selecting the bitmap into the DC again. - { - // Since drawn bitmap can only partially overlap - // with destination bitmap we need to calculate - // efective drawing area location. - const wxRect rectDst(bmpOld.GetSize()); - const wxRect rectDrawn(x, y, dstWidth, dstHeight); - const wxRect r = rectDrawn.Intersect(rectDst); - - wxAlphaPixelData data(bmpOld); - if ( data ) - { - wxAlphaPixelData::Iterator p(data); - - p.Offset(data, r.GetLeft(), r.GetTop()); - for ( int old_y = 0; old_y < r.GetHeight(); old_y++ ) - { - wxAlphaPixelData::Iterator rowStart = p; - for ( int old_x = 0; old_x < r.GetWidth(); old_x++ ) - { - // We choose to use wxALPHA_TRANSPARENT instead - // of perhaps more logical wxALPHA_OPAQUE here - // to ensure that the bitmap remains the same - // as before, i.e. without any alpha at all. - p.Alpha() = wxALPHA_TRANSPARENT; - ++p; - } - - p = rowStart; - p.OffsetY(data, 1); - } - } - } - - // Using wxAlphaPixelData sets the internal "has alpha" flag - // which is usually what we need, but in this particular case - // we use it to get rid of alpha, not set it, so reset it back. - bmpOld.ResetAlpha(); - - dcDst->DoSelect(bmpOld); + // Fetch the content of the destination area into the temporary buffer. + wxRect r(x, y, dstWidth, dstHeight); + if (bmpDst.GetWidth() < x + dstWidth || bmpDst.GetHeight() < y + dstHeight) + return true; + dc.DrawBitmap(dcDst->DoGetAsBitmap(&r), 0, 0); + // Drawing the source over the temporary buffer. + dcDst->DoBlit(x, y, dstWidth, dstHeight, &dc, 0, 0); + return true; } #endif // wxHAS_RAW_BITMAP From 0b638b0c44ff27158fd2757451de182bf64de812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Tue, 30 Apr 2024 16:13:34 +0800 Subject: [PATCH 27/34] wxGTK: Add private options to bring back traditional scrollbar(always visible). GTK3 defaults to enable the 'overlay-scrolling' features, that is, only show scrollbar when the mouse/touching hover the scrollbar area. It is undesirable to many of our clients. Yet wxWidgets does NOT exposing any API for us to do the GTK customization. So we need to add our 'CMExtension' to workaround this problem. This MAY be a temporary solution. We may make a new patch in the future by introducing a new properly designed API to wxWidgets for this sort of native/per-platform customization. --- include/wx/window.h | 6 ++++++ src/gtk/window.cpp | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/include/wx/window.h b/include/wx/window.h index d7283458b1dd..dc5af35e976c 100644 --- a/include/wx/window.h +++ b/include/wx/window.h @@ -49,6 +49,12 @@ #define wxUSE_MENUS_NATIVE wxUSE_MENUS #endif // __WXUNIVERSAL__/!__WXUNIVERSAL__ +#ifdef __WXGTK__ +namespace CMExtension { + extern bool g_need_traditional_scrollbar; +} +#endif + // ---------------------------------------------------------------------------- // forward declarations // ---------------------------------------------------------------------------- diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index eff794d5f74b..8c4bec38fcb2 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -72,6 +72,10 @@ typedef guint KeySym; #define PANGO_VERSION_CHECK(a,b,c) 0 #endif +namespace CMExtension { + bool g_need_traditional_scrollbar = false; +} + //----------------------------------------------------------------------------- // documentation on internals //----------------------------------------------------------------------------- @@ -2764,6 +2768,9 @@ void wxWindowGTK::GTKCreateScrolledWindowWith(GtkWidget* view) m_widget = gtk_scrolled_window_new( NULL, NULL ); GtkScrolledWindow *scrolledWindow = GTK_SCROLLED_WINDOW(m_widget); + + if (CMExtension::g_need_traditional_scrollbar) + g_object_set(scrolledWindow, "overlay-scrolling", false, NULL); // There is a conflict with default bindings at GTK+ // level between scrolled windows and notebooks both of which want to use From d790ac307d6bab73b74c9a6b635409020b33720d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Thu, 9 May 2024 15:07:52 +0800 Subject: [PATCH 28/34] Fix DND reordering of generic verion wxHeaderCtrl The old DND reorder implementation is buggy and wrong in many ways. This is a rework that fix almost everything with better user experiences, better dropped pos indication. ToDo: Better explanation code comments for several main changes. I'm now too busy to leave an explanation with enough details. --- include/wx/generic/headerctrlg.h | 13 ++- src/generic/headerctrlg.cpp | 160 ++++++++++++++++++++++++------- 2 files changed, 137 insertions(+), 36 deletions(-) diff --git a/include/wx/generic/headerctrlg.h b/include/wx/generic/headerctrlg.h index 77da772496cb..ff47ea4be54f 100644 --- a/include/wx/generic/headerctrlg.h +++ b/include/wx/generic/headerctrlg.h @@ -52,6 +52,12 @@ class WXDLLIMPEXP_CORE wxHeaderCtrl : public wxHeaderCtrlBase private: + enum class Region{ + NoWhere, + LeftHalf, + RightHalf, + Separator + }; // implement base class pure virtuals virtual void DoSetCount(unsigned int count) wxOVERRIDE; virtual unsigned int DoGetCount() const wxOVERRIDE; @@ -96,11 +102,14 @@ class WXDLLIMPEXP_CORE wxHeaderCtrl : public wxHeaderCtrlBase // position is near the divider at the right end of this column (notice // that this means that we return column 0 even if the position is over // column 1 but close enough to the divider separating it from column 0) - unsigned int FindColumnAtPoint(int x, bool *onSeparator = NULL) const; + unsigned int FindColumnAtPoint(int x, Region& pos_region) const; // return the result of FindColumnAtPoint() if it is a valid column, // otherwise the index of the last (rightmost) displayed column - unsigned int FindColumnClosestToPoint(int xPhysical) const; + unsigned int FindColumnClosestToPoint(int xPhysical, Region& pos_region) const; + + unsigned int FindColumnAfter(const unsigned int column_idx) const; + unsigned int FindColumnBefore(const unsigned int column_idx) const; // return true if a drag resizing operation is currently in progress bool IsResizing() const; diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 541bef81a356..49e650836c40 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -156,7 +156,7 @@ int wxHeaderCtrl::GetColStart(unsigned int idx) const if ( col.IsShown() ) pos += col.GetWidth(); } - + return pos; } @@ -167,7 +167,7 @@ int wxHeaderCtrl::GetColEnd(unsigned int idx) const return x + GetColumn(idx).GetWidth(); } -unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, bool *onSeparator) const +unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, Region& pos_region) const { int pos = 0; int xLogical = xPhysical - m_scrollOffset; @@ -178,7 +178,8 @@ unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, bool *onSeparator) c const wxHeaderColumn& col = GetColumn(idx); if ( col.IsHidden() ) continue; - + + const auto last_col_end = pos; pos += col.GetWidth(); // TODO: don't hardcode sensitivity @@ -188,33 +189,34 @@ unsigned int wxHeaderCtrl::FindColumnAtPoint(int xPhysical, bool *onSeparator) c // line separating it from the next column if ( col.IsResizeable() && abs(xLogical - pos) < separatorClickMargin ) { - if ( onSeparator ) - *onSeparator = true; + pos_region = Region::Separator; return idx; } // inside this column? - if ( xLogical < pos ) + if ( xLogical < pos && xLogical >= last_col_end) { - if ( onSeparator ) - *onSeparator = false; + if ( xLogical - last_col_end < pos - xLogical) + pos_region = Region::LeftHalf; + else + pos_region = Region::RightHalf; return idx; } } - if ( onSeparator ) - *onSeparator = false; + pos_region = Region::NoWhere; return COL_NONE; } -unsigned int wxHeaderCtrl::FindColumnClosestToPoint(int xPhysical) const +unsigned int wxHeaderCtrl::FindColumnClosestToPoint(int xPhysical, Region& pos_region) const { - const unsigned int colIndexAtPoint = FindColumnAtPoint(xPhysical); - + const unsigned int colIndexAtPoint = FindColumnAtPoint(xPhysical, pos_region); + // valid column found? if ( colIndexAtPoint != COL_NONE ) return colIndexAtPoint; - + + pos_region = Region::NoWhere; // if not, xPhysical must be beyond the rightmost column, so return its // index instead -- if we have it const unsigned int count = GetColumnCount(); @@ -224,6 +226,37 @@ unsigned int wxHeaderCtrl::FindColumnClosestToPoint(int xPhysical) const return m_colIndices[count - 1]; } +unsigned int wxHeaderCtrl::FindColumnAfter(const unsigned int column_idx) const{ + const unsigned count = GetColumnCount(); + auto after_idx = COL_NONE; + //auto target_column_found = false; + for ( unsigned n = 0; n < count; n++ ) + { + if (m_colIndices[n] == column_idx && n + 1 < count ){ + after_idx = m_colIndices[n + 1]; + break; + } + } + return after_idx; +} + +unsigned int wxHeaderCtrl::FindColumnBefore(const unsigned int column_idx) const{ + const unsigned count = GetColumnCount(); + auto before_idx = COL_NONE; + auto target_column_found = false; + for ( unsigned n = 0; n < count; n++ ) + { + if (m_colIndices[n] == column_idx){ + target_column_found = true; + break; + } + before_idx = m_colIndices[n]; + } + if (not target_column_found) + before_idx = COL_NONE; + return before_idx; +} + // ---------------------------------------------------------------------------- // wxHeaderCtrl repainting // ---------------------------------------------------------------------------- @@ -389,14 +422,21 @@ void wxHeaderCtrl::UpdateReorderingMarker(int xPhysical) // and also a hint indicating where it is going to be inserted if it's // dropped now - unsigned int col = FindColumnClosestToPoint(xPhysical); + auto hover_region = Region::NoWhere; + unsigned int col = FindColumnClosestToPoint(xPhysical, hover_region); if ( col != COL_NONE ) { static const int DROP_MARKER_WIDTH = 4; dc.SetBrush(*wxBLUE); - dc.DrawRectangle(GetColEnd(col) - DROP_MARKER_WIDTH/2, 0, - DROP_MARKER_WIDTH, y); + if (hover_region == Region::LeftHalf){ + dc.DrawRectangle(GetColStart(col) - DROP_MARKER_WIDTH/2, 0, + DROP_MARKER_WIDTH, y); + } + else if (hover_region != Region::NoWhere){ + dc.DrawRectangle(GetColEnd(col) - DROP_MARKER_WIDTH/2, 0, + DROP_MARKER_WIDTH, y); + } } } @@ -432,10 +472,39 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) ReleaseMouse(); const int colOld = m_colBeingReordered; - const unsigned colNew = FindColumnClosestToPoint(xPhysical); + auto dropped_region = Region::NoWhere; + unsigned colNew = FindColumnClosestToPoint(xPhysical, dropped_region); m_colBeingReordered = COL_NONE; - + auto reg_str = std::string{}; + switch(dropped_region){ + case Region::NoWhere: + reg_str = "NoWhere"; + break; + case Region::LeftHalf: + reg_str = "LeftHalf"; + break; + case Region::RightHalf: + reg_str = "RightHalf"; + break; + case Region::Separator: + reg_str = "Separator"; + break; + } + //printf("Dropped to col %d, region : %s\n", colNew, reg_str.c_str()); + // The actual dropped pos should not simply be colNew, it should also depends on + // which region the user dropped in. + // if the user dropped the col on the RightHalf, the colNew should the one next to it on the right. + auto located_by_previous_col = false; + if ((dropped_region == Region::RightHalf || dropped_region == Region::Separator) && colNew != COL_NONE){ + //printf("Looking for the next column pos to inserted for col %d\n", colNew); + auto nextColumn = FindColumnAfter(colNew); + if (nextColumn != COL_NONE){ + //printf("Next col for col %d is %d\n", colNew, nextColumn); + colNew = nextColumn; + located_by_previous_col = true; + } + } // mouse drag must be longer than min distance m_dragOffset if ( xPhysical - GetColStart(colOld) == m_dragOffset ) { @@ -447,22 +516,45 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) { return false; } - if ( static_cast(colNew) != colOld ) { wxHeaderCtrlEvent event(wxEVT_HEADER_END_REORDER, GetId()); event.SetEventObject(this); event.SetColumn(colOld); - - const unsigned pos = GetColumnPos(colNew); - event.SetNewOrder(pos); - - if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) - { - // do reorder the columns - DoMoveCol(colOld, pos); + auto new_pos = GetColumnPos(colNew); + auto old_pos = GetColumnPos(colOld); + // when the user drag one col from left-to-right(i.e. from low pos to higher one), + // the actual pos to dropped should be the one just before colNew, i.e. the one on the left hand side of colNew. + auto move_left = false; + if (old_pos < new_pos) { + // the last column is a bit special, we should consider it differently. + if (new_pos != GetColumnCount() - 1 || located_by_previous_col || dropped_region == Region::LeftHalf){ + colNew = FindColumnBefore(colNew); + assert(colNew != COL_NONE); + new_pos = GetColumnPos(colNew); + } + move_left = true; + } + // Simulate the reorder before we actually accept it. + // Why??? + // ToDo: better code comments to explain the logic, a diagram should be better + auto new_colIndices = m_colIndices; + MoveColumnInOrderArray(new_colIndices, colOld, new_pos); + auto old_after_pos = new_colIndices.Index(colNew); + auto new_after_pos = new_colIndices.Index(colOld); + if (old_after_pos != old_pos || new_after_pos != new_pos || move_left || old_pos > new_pos){ + event.SetNewOrder(new_pos); + + //printf("Move col %d to %d, pos to %d\n", colOld, colNew, new_pos); + if ( GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) + { + // do reorder the columns + DoMoveCol(colOld, new_pos); + } } } + //else + //printf("ColNew and ColOld the same, do not reordering.\n"); // whether we moved the column or not, the user did move the mouse and so // did try to do it so return true @@ -651,10 +743,10 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) // find if the event is over a column at all - bool onSeparator; + Region mouse_region = Region::NoWhere; const unsigned col = mevent.Leaving() - ? (onSeparator = false, COL_NONE) - : FindColumnAtPoint(xPhysical, &onSeparator); + ? COL_NONE + : FindColumnAtPoint(xPhysical, mouse_region); // update the highlighted column if it changed @@ -670,7 +762,7 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) // update mouse cursor as it moves around if ( mevent.Moving() ) { - SetCursor(onSeparator ? wxCursor(wxCURSOR_SIZEWE) : wxNullCursor); + SetCursor(mouse_region == Region::Separator ? wxCursor(wxCURSOR_SIZEWE) : wxNullCursor); return; } @@ -682,7 +774,7 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) // enter various dragging modes on left mouse press if ( mevent.LeftDown() ) { - if ( onSeparator ) + if ( mouse_region == Region::Separator ) { // start resizing the column wxASSERT_MSG( !IsResizing(), "reentering column resize mode?" ); @@ -712,7 +804,7 @@ void wxHeaderCtrl::OnMouse(wxMouseEvent& mevent) { case wxMOUSE_BTN_LEFT: // treat left double clicks on separator specially - if ( onSeparator && dblclk ) + if ( mouse_region == Region::Separator && dblclk ) { evtType = wxEVT_HEADER_SEPARATOR_DCLICK; m_wasSeparatorDClick = true; From 9d94399f1ad5c01f437b4aedb5ffaf3919ac5a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 13 May 2024 10:37:17 +0800 Subject: [PATCH 29/34] Revert the old correct behavior that allow application to decide whether reordering is allowed. --- src/generic/headerctrlg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 49e650836c40..26c1423e3468 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -546,7 +546,7 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) event.SetNewOrder(new_pos); //printf("Move col %d to %d, pos to %d\n", colOld, colNew, new_pos); - if ( GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) + if ( !GetEventHandler()->ProcessEvent(event) || event.IsAllowed() ) { // do reorder the columns DoMoveCol(colOld, new_pos); From 5594ee39f4643ef142cfd3a7946d1028a20a9457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 13 May 2024 15:32:17 +0800 Subject: [PATCH 30/34] When the column header is dragged pass the leftmost/rightmost column, make it a no-op, i.e. refuse the reroder. The old behavior is to position the dragged column to the rightmost. It is counter-intuitive. Forbid the reordering make more sense and is a de facto accepted standard. --- src/generic/headerctrlg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generic/headerctrlg.cpp b/src/generic/headerctrlg.cpp index 26c1423e3468..26b373b01e4c 100644 --- a/src/generic/headerctrlg.cpp +++ b/src/generic/headerctrlg.cpp @@ -516,7 +516,7 @@ bool wxHeaderCtrl::EndReordering(int xPhysical) { return false; } - if ( static_cast(colNew) != colOld ) + if ( static_cast(colNew) != colOld && dropped_region != Region::NoWhere ) { wxHeaderCtrlEvent event(wxEVT_HEADER_END_REORDER, GetId()); event.SetEventObject(this); From 2c3da0e522d1d04227c6a3563e1b0e90c93286d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Fri, 31 May 2024 11:49:03 +0800 Subject: [PATCH 31/34] MacOS: Catch all exceptions thrown by wxCFEventLoop::OSXDoRun() for cocoa modal dialog. The cocoa framework is not bug-free. Some version(I'd called it buggy) may throw unexpected exceptions. In order to avoid the ShowModal() to died and break the modal stack, and finally comes you a assertion failure on broken modal stack, we should catch exceptions here. --- src/osx/cocoa/evtloop.mm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/osx/cocoa/evtloop.mm b/src/osx/cocoa/evtloop.mm index 399e1beae92c..4831de0765f4 100644 --- a/src/osx/cocoa/evtloop.mm +++ b/src/osx/cocoa/evtloop.mm @@ -434,7 +434,15 @@ static NSUInteger CalculateNSEventMaskFromEventCategory(wxEventCategory cat) if ( m_modalWindow ) { BeginModalSession(m_modalWindow); - wxCFEventLoop::OSXDoRun(); + while(true){ + try { + wxCFEventLoop::OSXDoRun(); + break; + } + catch(...){ + printf("wxModalEventLoop::OSXDoRun caught unknown exception\n"); + } + } EndModalSession(); } else From a6efe6dc63a786133e27a9787e7260b6e17b0be0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Thu, 20 Jun 2024 16:45:42 +0800 Subject: [PATCH 32/34] Fix keyboard focus problem when embedd CEF window. When wxGTKWindow::SetFocus, we will bring up the toplevel window to the user before we set the focus to the child widget. And now, we no longer need to consider whether gtk_window_is_active(tlw). Because of these two reasons: 1. if the gtk_window_is_active(tlw), gtk_window_preset() don't harm anyway; 2. when foreign XWindow embedded(not via GtkPlug), maybe use XWindow controlled by GDK as their parent, for example, a CEF browser window(which is now an XWindow) take ours as parent, our tlw active state maybe altered(when CEF window take the keyboard focus) and unknown to us. i.e. gtk_window_is_active(tlw) may still true while the focus is already taken by the CEF window. we need to force to bring up our tlw to the user at this moment. --- src/gtk/window.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index 8c4bec38fcb2..dfa3d1b2800e 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -4866,7 +4866,15 @@ void wxWindowGTK::SetFocus() // But avoid activating if tlw is not yet shown, as that will // cause it to be immediately shown. GtkWidget* tlw = gtk_widget_get_ancestor(m_widget, GTK_TYPE_WINDOW); - if (tlw && gtk_widget_get_visible(tlw) && !gtk_window_is_active(GTK_WINDOW(tlw))) + if (tlw && gtk_widget_get_visible(tlw)) + // we don't need to consider whether the tlw is active or not, + // for these two reasons: + // 1. if the gtk_window_is_active(tlw), gtk_window_preset() don't harm anyway; + // 2. when foreign XWindow embedded(not via GtkPlug), maybe use XWindow controlled by GDK as their parent, + // for example, a CEF browser window(which is now an XWindow) take ours as parent, + // our tlw active state maybe altered(when CEF window take the keyboard focus) and unknown to us. + // i.e. gtk_window_is_active(tlw) may still true while the focus is already taken by the CEF window. + // we need to force to bring up our tlw to the user at this moment. gtk_window_present(GTK_WINDOW(tlw)); GtkWidget *widget = m_wxwindow ? m_wxwindow : m_focusWidget; From 69dbef5fcf97249e01876ce66a686a3aa18ea870 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=89=AC=E6=96=8C?= Date: Mon, 15 Jul 2024 18:33:52 +0800 Subject: [PATCH 33/34] GTK: Fix wxGUIEventLoop::DoYieldFor compatibility when using with CEF DO NOT replace the global GDK event handler with our 'wxgtk_main_do_event'. Because this trick rely on one uncertain assumption: No one besides us, had done gdk_event_handler_set() already. In most case, this might be true. But a single exception can destroy this trick completely. For example, when we want to embed CEF, Chromium will use a custom gdk_event_handler too. This trick will render all CEF browser window/view no longer usable. GTK3 should be blamed for not offering any gdk_event_handler_get() or similar stuff for a safe trick here. In GTK4, there's a more sane way to get the job done. So let's do it in a more conservative way. If there're GdkEvents, we handle them via 'wxgtk_main_do_event', all other events should be handle by one gtk_main_iteration(). I'm not sure whether this is really okay, but it seems a nicer and less intrusive way to do things. --- src/gtk/evtloop.cpp | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/src/gtk/evtloop.cpp b/src/gtk/evtloop.cpp index eb9751c85a30..12f10a2c1d11 100644 --- a/src/gtk/evtloop.cpp +++ b/src/gtk/evtloop.cpp @@ -365,22 +365,29 @@ static void wxgtk_main_do_event(GdkEvent* event, void* data) } void wxGUIEventLoop::DoYieldFor(long eventsToProcess) -{ - // temporarily replace the global GDK event handler with our function, which - // categorizes the events and using m_eventsToProcessInsideYield decides - // if an event should be processed immediately or not - // NOTE: this approach is better than using gdk_display_get_event() because - // gtk_main_iteration() does more than just calling gdk_display_get_event() - // and then call gtk_main_do_event()! - // In particular in this way we also process input from sources like - // GIOChannels (this is needed for e.g. wxGUIAppTraits::WaitForChild). - gdk_event_handler_set(wxgtk_main_do_event, this, NULL); - while (Pending()) // avoid false positives from our idle source - gtk_main_iteration(); - - wxGCC_WARNING_SUPPRESS_CAST_FUNCTION_TYPE() - gdk_event_handler_set ((GdkEventFunc)gtk_main_do_event, NULL, NULL); - wxGCC_WARNING_RESTORE_CAST_FUNCTION_TYPE() +{ + // DO NOT replace the global GDK event handler with our 'wxgtk_main_do_event'. + // Because this trick rely on one uncertain assumption: + // No one besides us, had done gdk_event_handler_set() already. + // In most case, this might be true. + // But a single exception can destroy this trick completely. + // For example, when we want to embed CEF, Chromium will use a custom gdk_event_handler too. + // This trick will render all CEF browser window/view no longer usable. + // GTK3 should be blamed for not offering any gdk_event_handler_get() or similar stuff for a safe trick here. + // In GTK4, there's a more sane way to get the job done. + // So let's do it in a more conservative way. + // If there're GdkEvents, we handle them via 'wxgtk_main_do_event', + // all other events should be handle by one gtk_main_iteration(). + // I'm not sure whether this is really okay, but it seems a nicer and less intrusive way to do things. + while(Pending()){ + auto gdk_event_ = gdk_event_get(); + if (gdk_event_ != nullptr){ + wxgtk_main_do_event(gdk_event_, this); + gdk_event_free(gdk_event_); + } + else + gtk_main_iteration(); + } wxEventLoopBase::DoYieldFor(eventsToProcess); From 627420ecee1f4c6b58de81e2645dbc7842479211 Mon Sep 17 00:00:00 2001 From: crml2024 Date: Tue, 23 Jul 2024 13:29:35 +0000 Subject: [PATCH 34/34] GTK: Fix early exit from `wxDropSource::DoDragDrop` Missing of mouse event is not an error. It's not very clear whether g_lastButtonNumber is needed to be correct. --- src/gtk/dnd.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gtk/dnd.cpp b/src/gtk/dnd.cpp index 1a00bb67c346..619f0567f132 100644 --- a/src/gtk/dnd.cpp +++ b/src/gtk/dnd.cpp @@ -852,13 +852,11 @@ wxDragResult wxDropSource::DoDragDrop(int flags) if (g_blockEventsOnDrag) return wxDragNone; +#if 0 // behaves bad for Coremail Air Client, need to investigate whether the early exiting is correct // don't start dragging if no button is down if (g_lastButtonNumber == 0) return wxDragNone; - - // we can only start a drag after a mouse event - if (g_lastMouseEvent == NULL) - return wxDragNone; +#endif GTKConnectDragSignals(); wxON_BLOCK_EXIT_OBJ0(*this, wxDropSource::GTKDisconnectDragSignals);