Vadim Zeitlin pushed to branch master at wxWidgets / wxWidgets
Commits:
5eb277b1 by John Paul Mattia at 2026-08-24T11:23:56-07:00
wxQt: wake the event loop with a posted event so cross-thread CallAfter() runs
wxQtEventLoopBase::WakeUp() only called QAbstractEventDispatcher::wakeUp(), which
returns a blocked event loop from its wait but posts no Qt event. On wxQt, wx
pending events (everything queued via wxEvtHandler::CallAfter(), including
cross-thread marshalling) are run by wxQtIdleTimer::idle() ->
ProcessPendingEvents(), and that idle timer is only (re)scheduled by the
application-wide event filter, which fires when a Qt *event* is dispatched.
So a CallAfter() posted from a worker thread woke the main loop but its work was
never processed until some unrelated Qt event happened to arrive -- effectively
stalling cross-thread CallAfter() indefinitely. (It works under wxGTK because
GLib's wakeup targets the shared main context regardless of the calling thread.)
Fix WakeUp() to post a no-op Qt event to the main-thread idle timer instead.
QCoreApplication::postEvent() is documented to be thread-safe; posting both wakes
the loop and drives the event filter, so the idle check -- and thus the queued
pending events -- run promptly. Falls back to the dispatcher wakeUp() if the idle
timer does not exist yet.
This was found via wxIPC, whose worker threads marshal socket I/O to the main
thread with CallAfter(): under wxQt only the first server-pushed Advise() was
delivered, the rest stalling until the next incoming message. Verified with a
minimal repro and the wxWidgets IPC tests, which pass under wxQt with this change.
- - - - -
fd414d56 by John Paul Mattia at 2026-08-24T11:23:56-07:00
tests: add cross-thread CallAfter() event-loop regression test
Add EvtloopTestCase::TestCrossThreadCallAfter, which starts a worker thread that
schedules a callback on the main thread via wxTheApp->CallAfter() once the main
loop is idle, and asserts the loop exits when that callback runs.
This guards the wxQt WakeUp() fix in the previous commit: without it the
cross-thread CallAfter() is never processed and the test hangs; with it (and on
all other ports, which already wake correctly) it passes. There was previously
no test coverage for cross-thread CallAfter()/wxWakeUpIdle() at all, which is why
this wxQt event-loop defect went unnoticed.
Verified under wxQt (offscreen): passes with the fix, hangs without it; also
passes on the console event loop.
- - - - -
783d9fb4 by John Paul Mattia at 2026-08-24T12:45:07-07:00
Enable IPC tests on wxQt
The IPC tests were excluded from wxQt because wxIPC worker threads
marshal their socket I/O to the main thread via CallAfter(), and a
cross-thread CallAfter() was not reliably processed by the wxQt event
loop, stalling the tests. Now that the previous commit makes WakeUp()
post a real Qt event, the whole suite runs on wxQt: remove the
__WXQT__ exclusions from the test guards (including the one in
tests/test.cpp that compiled out the re-exec'd server mode) and the
comments explaining them.
All 10 [ipc] cases pass repeatedly on wxQt in both test and test_gui,
with both a real X server and QT_QPA_PLATFORM=offscreen. Notably the
offscreen runs are meaningful for the previous commit's fix: with an X
server connected, X-connection traffic keeps rescheduling the idle
timer and can mask a missing WakeUp() event, while offscreen the
event loop goes truly idle.
- - - - -
79900d71 by Scott Talbert at 2026-08-26T16:32:33+02:00
Default wxUSE_ARTPROVIDER_TANGO to OFF for GTK in CMake build
Matches
configure.ac, which already skips the Tango art provider on
GTK ports since the native art provider replaces it there.
Closes #26929.
- - - - -
ae3a6134 by Richard at 2026-08-26T16:38:48+02:00
Test that moving wxGrid cursor over hidden lines works correctly
Add regression coverage for cursor movement across zero-height rows and
zero-width columns. The grid cursor should skip hidden lines when moving
in either direction and remain on a shown row or column.
See #9586.
Closes #26904.
- - - - -
68ef146f by Richard at 2026-08-26T16:41:58+02:00
Don't assume that wxPreviewCanvas is always in wxPreviewFrame
Avoid assuming that wxPreviewCanvas is parented directly by
wxPreviewFrame when handling keyboard and mouse-wheel events, as this
resulted in crashes when this was not the case.
Look up the preview control bar only after confirming that the preview
frame or parent really is a wxPreviewFrame, and skip the event when the
canvas is embedded elsewhere.
Fixes #9822.
Closes #26905.
- - - - -
0fbb475b by Paxipu at 2026-08-26T17:26:40+02:00
Don't let a page header or footer paint over the page body
wxHtmlPrintout::RenderPage() renders the body first and then the header
and footer through m_RendererHdr, calling
m_RendererHdr.Render(x, y);
which leaves wxHtmlDCRenderer::Render()'s "to" parameter at its INT_MAX
default. The header is therefore free to draw the full height of its
HTML, not just the header area -- and an HTML background colour drawn by
the header covers the body that was rendered underneath it.
Pass the header and footer heights, which RenderPage() already knows, as
the bound.
The test renders a page with a body background colour and a header into a
wxMemoryDC and checks that a pixel well inside the body still has the
body's colour.
Closes #26915.
Assisted-by: Claude
- - - - -
abf59bf7 by Paxipu at 2026-08-26T17:26:48+02:00
Set the scrolled window's sizer after reparenting its controls
wxWizard::DoLayoutAdaptation() moves a page's controls into a scrolled
window, and did it in this order:
page->SetSizer(newSizer, false);
scrolledWindow->SetSizer(oldSizer);
wxStandardDialogLayoutAdapter::DoReparentControls(page, scrolledWindow);
so the scrolled window was given a sizer managing controls that were, at
that moment, still children of the page. The controls only became its
children on the next line.
Reparent first and set the sizer afterwards, so that the sizer is
installed on a window that already owns everything it lays out.
At the moment this doesn't seem to change anything material, but it
may, in the future.
Closes #26916.
- - - - -
5505eccd by Vadim Zeitlin at 2026-08-26T17:26:48+02:00
Fix the type of the string caught in the "except" sample
We throw just a plain string, not wxChar one, since the changes of
b70ed2d8c8 (Remove more wxT() macros from samples, 2018-09-28) so catch
what we throw.
Closes #26917.
- - - - -
5e9ffaa1 by Vadim Zeitlin at 2026-08-26T17:26:48+02:00
Parent wxDebugReportDialog's controls to their static boxes
The contents of the two wxStaticBoxSizers in the debug report dialog were
created as children of the dialog rather than of the wxStaticBox.
That is the older convention, still supported, but
wxStaticBoxSizer::CheckIfNonBoxChild() warns about it in a debug build,
so simply raising a debug report produces warnings -- on every platform.
Create the buttons, the list of files, the notes field and the two
explanatory texts with the box as their parent. The texts need
CreateTextSizer() to be given a wxTextSizerWrapper for the box, since
they are wrapped relative to their parent.
Closes #26918.
Assisted-by: Claude
- - - - -
05716067 by Paxipu at 2026-08-26T17:26:48+02:00
Fix redrawing in "life" demo for the platforms without wxClientDC
Clicking or dragging in the Life canvas updated the Life object but drew
the affected cells straight onto a wxClientDC. Where that does not work
the population counter went up while the grid stayed unchanged, and the
cells only appeared once something else forced a repaint -- resizing the
window, for instance.
Refresh the affected cell's rectangle instead and let the existing paint
handler draw it from the Life object, which is where every other update
to the grid already comes from.
Closes #26922.
Assisted-by: Claude
- - - - -
2268aeee by Vadim Zeitlin at 2026-08-26T17:59:35+02:00
Remove mentions of non-existent wxTHICK_FRAME from the docs
This symbol was removed back in 967a94c91a (remove
WXWIN_COMPATIBILITY_2_6, add WXWIN_COMPATIBILITY_3_0 closes #15792,
2014-01-04).
Closes #26925.
- - - - -
ddd1e72e by got3nks at 2026-08-26T18:05:26+02:00
Don't call handlers unregistered during wxEpollDispatcher::Dispatch()
Dispatch() stored the wxFDIOHandler pointer in epoll_event::data.ptr and
called through the copy that epoll_wait() had filled in before any handler
ran. Servicing one event of a batch can unregister the descriptor belonging
to a later event of the same batch -- and, in the code owning it, destroy its
handler with it -- but nothing scrubs the pointer already copied into the
array, so the loop went on to make a virtual call on released memory.
Store the descriptor in epoll_event::data instead and look the handler up
when the event is actually processed, so that one which is no longer
registered is skipped rather than called. That is what wxSelectDispatcher has
always done, via FindHandler() in ProcessSets().
The map this needs is kept here rather than by deriving from
wxMappedFDIODispatcher, for two reasons:
- Its ModifyFD() asserts that the descriptor is already known, but
wxFDIOManagerUnix chooses between RegisterFD() and ModifyFD() from the mask
it keeps on the handler rather than from anything this dispatcher knows, so
the two can legitimately disagree. epoll_ctl(EPOLL_CTL_MOD) merely reports
ENOENT in that case, and that tolerance has to be preserved. Recording the
handler is therefore an unconditional assignment.
- Descriptors are registered and unregistered from worker threads, e.g. by
wxSocketImpl from whichever thread performs the socket operation, while
Dispatch() reads the map on the thread running the event loop. epoll_ctl()
is thread-safe so the previous implementation needed no locking; the map
does, and it is guarded accordingly. The lock is never held across a call
into a handler, which is free to register or unregister descriptors.
This only makes a handler safe against being unregistered by another handler
in the same batch, on the thread running the loop. A handler destroyed by a
different thread while the loop is between the lookup and the call was racy
before and still is.
The new test in tests/events/evtsource.cpp (which existed since many
years but was completely empty) covers the fixed case without depending
on timing: two pipes that are both readable are collected in one batch,
and the handler that runs first unregisters the other, which must then
not be called. It fails before this change -- both handlers run -- and
passes after.
Found while investigating a crash in aMule, where wxWebRequest's curl
backend destroys transfer event sources from inside curl callbacks that
themselves run during Dispatch(). Reported and reduced by ngosang at
https://github.com/amule-org/amule/issues/1136, which also has a real-world
reproducer built on wxFileSystemWatcher; under ASan it faulted in
wxEpollDispatcher::Dispatch() on 4 of 5 runs before this change and 0 of
8 after.
Closes #26924.
- - - - -
cd2df88d by Vadim Zeitlin at 2026-08-26T18:14:32+02:00
Merge branch 'jpmattia/wxQT-CallAfter-wxWakeUpIdle' of github.com:jpmattia/wxWidgets
Fix CallAfter() from another thread not waking the event loop in wxQt.
Also enable IPC tests for wxQt now that they work.
See #26913.
- - - - -
1b930f72 by Vadim Zeitlin at 2026-08-26T18:16:56+02:00
Reduce sleep time in EventLoop::TestCrossThreadCallAfter
100ms is enough in normal circumstances and 250ms is not guaranteed to
be enough in all cases anyhow, so reduce the sleep time to make the test
run faster in normal circumstances.
Ideal would be to block until the loop becomes idle, but there doesn't
seem to be any simple way to do this.
See #26913.
- - - - -
74c6c4a5 by PB at 2026-08-26T18:58:26+02:00
Fix handling menu-related Windows messages in dark mode
wxFrame window procedure called wxMSWDarkMode::HandleMenuMessage() which
couldn't call MSWWindowProc() on the provided window for the messages it
didn't process itself as this would have resulted in an infinite
recursion.
However we can, and should, call wxWindow::MSWWindowProc() instead of
just MSWDefWindowProc() from this function for the unprocessed messages
as this lets their default handling at wxWindow level to take place.
See #26785.
- - - - -
3c6fb854 by PB at 2026-08-26T19:15:59+02:00
Make scrollbar corner painting work in RTL layout too
Check whether the WS_EX_LAYOUTRTL flag, indicating the
vertical scrollbar is on the left, is set and if so,
adjust the corner horizontal coordinates accordingly.
This required changing PaintScrollBarCorner() to take a wxWindow instead
of HWND, so adjust the callers accordingly.
Co-authored-by: Vadim Zeitlin <
va...@wxwidgets.org>
- - - - -
f7dd5e59 by PB at 2026-08-26T19:20:23+02:00
Draw dark scrollbar corner for all windows when needed
Handle drawing the scrollbar corner in dark mode for all
windows to ensure its color is consistent with the theme.
Control-specific code in wxListCtrl and wxTreeCtrl is not needed any
more, so simply remove it.
Closes #26785.
- - - - -
fcc0fedd by Steve Cornett at 2026-08-26T19:22:44+02:00
Implement dark mode support for Windows common dialogs
Add dark mode support for wxColourDialog, wxFindReplaceDialog,
wxFontDialog and wxPrintDialog.
Centralize all the related code in CommonDialogHookProc() which is used
by all these dialogs.
Closes #26780.
- - - - -
30 changed files:
- build/cmake/options.cmake
- demos/life/life.cpp
- demos/life/life.h
- include/wx/msw/private/darkmode.h
- include/wx/unix/private/epolldispatcher.h
- interface/wx/dialog.h
- interface/wx/settings.h
- samples/except/except.cpp
- src/common/prntbase.cpp
- src/generic/dbgrptg.cpp
- src/generic/wizard.cpp
- src/html/htmprint.cpp
- src/msw/colordlg.cpp
- src/msw/darkmode.cpp
- src/msw/fdrepdlg.cpp
- src/msw/fontdlg.cpp
- src/msw/listctrl.cpp
- src/msw/printdlg.cpp
- src/msw/treectrl.cpp
- src/msw/window.cpp
- src/qt/evtloop.cpp
- src/unix/epolldispatcher.cpp
- tests/controls/gridtest.cpp
- tests/events/evtlooptest.cpp
- tests/events/evtsource.cpp
- tests/html/htmprint.cpp
- tests/net/ipc.cpp
- tests/net/ipc_test_server.cpp
- tests/net/ipc_test_server.h
- tests/test.cpp
Changes:
=====================================
build/cmake/options.cmake
=====================================
@@ -347,7 +347,13 @@ wx_option(wxUSE_ADDREMOVECTRL "use wxAddRemoveCtrl")
wx_option(wxUSE_ANIMATIONCTRL "use wxAnimationCtrl class")
wx_option(wxUSE_BANNERWINDOW "use wxBannerWindow class")
wx_option(wxUSE_ARTPROVIDER_STD "use standard XPM icons in wxArtProvider")
-wx_option(wxUSE_ARTPROVIDER_TANGO "use Tango icons in wxArtProvider")
+if(WXGTK)
+ # Tango-based art provider is not needed in GTK-based ports as the
+ # native art provider completely replaces it.
+ wx_option(wxUSE_ARTPROVIDER_TANGO "use Tango icons in wxArtProvider" OFF)
+else()
+ wx_option(wxUSE_ARTPROVIDER_TANGO "use Tango icons in wxArtProvider")
+endif()
wx_option(wxUSE_BMPBUTTON "use wxBitmapButton class")
wx_option(wxUSE_BITMAPCOMBOBOX "use wxBitmapComboBox class")
wx_option(wxUSE_BUTTON "use wxButton class")
=====================================
demos/life/life.cpp
=====================================
@@ -754,15 +754,22 @@ void LifeCanvas::SetCellSize(int cellsize)
Refresh(false);
}
-// draw a cell
-void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, bool alive)
+// the area a cell occupies on screen
+wxRect LifeCanvas::CellRect(wxInt32 i, wxInt32 j) const
{
- wxClientDC dc(this);
-
- dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
- dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
+ return wxRect(CellToX(i), CellToY(j), m_cellsize, m_cellsize);
+}
- DrawCell(i, j, dc);
+// mark a cell as needing to be repainted
+//
+// Drawing on a wxClientDC outside a paint handler does not reach the screen on
+// every platform -- see wxClientDC::CanBeUsedForDrawing(), which reports false
+// under wxGTK on Wayland and on the macOS and Qt ports. Repainting from
+// OnPaint(), which already draws whatever the Life object says is alive in the
+// damaged area, works everywhere.
+void LifeCanvas::RefreshCell(wxInt32 i, wxInt32 j)
+{
+ RefreshRect(CellRect(i, j), false);
}
void LifeCanvas::DrawCell(wxInt32 i, wxInt32 j, wxDC &dc)
@@ -923,17 +930,18 @@ void LifeCanvas::OnMouse(wxMouseEvent& event)
m_mi = i;
m_mj = j;
m_life->SetCell(i, j, m_status == MOUSE_DRAWING);
- DrawCell(i, j, m_status == MOUSE_DRAWING);
+ RefreshCell(i, j);
}
else if ((m_mi != i) || (m_mj != j))
{
// no: continue ongoing action
bool alive = (m_status == MOUSE_DRAWING);
- // prepare DC and pen + brush to optimize drawing
- wxClientDC dc(this);
- dc.SetPen(alive? *wxBLACK_PEN : *wxWHITE_PEN);
- dc.SetBrush(alive? *wxBLACK_BRUSH : *wxWHITE_BRUSH);
+ // Collect everything this segment touches and invalidate it in one
+ // go at the end. Repainting the whole enclosing rectangle costs a
+ // little more than the cells on the line, but the segment spans one
+ // mouse motion, and OnPaint() redraws any cell in it correctly.
+ wxRect damaged;
// draw a line of cells using Bresenham's algorithm
wxInt32 d, ii, jj, di, ai, si, dj, aj, sj;
@@ -955,7 +963,7 @@ void LifeCanvas::OnMouse(wxMouseEvent& event)
while (ii != i)
{
m_life->SetCell(ii, jj, alive);
- DrawCell(ii, jj, dc);
+ damaged.Union(CellRect(ii, jj));
if (d >= 0)
{
jj += sj;
@@ -973,7 +981,7 @@ void LifeCanvas::OnMouse(wxMouseEvent& event)
while (jj != j)
{
m_life->SetCell(ii, jj, alive);
- DrawCell(ii, jj, dc);
+ damaged.Union(CellRect(ii, jj));
if (d >= 0)
{
ii += si;
@@ -986,9 +994,11 @@ void LifeCanvas::OnMouse(wxMouseEvent& event)
// last cell
m_life->SetCell(ii, jj, alive);
- DrawCell(ii, jj, dc);
+ damaged.Union(CellRect(ii, jj));
m_mi = ii;
m_mj = jj;
+
+ RefreshRect(damaged, false);
}
((LifeFrame *) wxGetApp().GetTopWindow())->UpdateInfoText();
=====================================
demos/life/life.h
=====================================
@@ -36,7 +36,9 @@ public:
// drawing
void DrawChanged();
- void DrawCell(wxInt32 i, wxInt32 j, bool alive);
+
+ // mark a cell as needing to be repainted from the Life object
+ void RefreshCell(wxInt32 i, wxInt32 j);
private:
// any class wishing to process wxWidgets events must use this macro
@@ -45,6 +47,9 @@ private:
// draw a cell (parametrized by DC)
void DrawCell(wxInt32 i, wxInt32 j, wxDC &dc);
+ // the area a cell occupies on screen
+ wxRect CellRect(wxInt32 i, wxInt32 j) const;
+
// event handlers
void OnPaint(wxPaintEvent& event);
void OnMouse(wxMouseEvent& event);
=====================================
include/wx/msw/private/darkmode.h
=====================================
@@ -80,6 +80,9 @@ HandleMenuMessage(WXLRESULT* result,
void NotifySysColorChange();
+// Hook procedure to enable dark mode for a common dialog.
+UINT_PTR CALLBACK CommonDialogHookProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
+ LPARAM lParam);
// Return true if the DarkMode_DarkTheme theme is available. This theme was
// added in Windows 11 25H2 (build 26200).
bool HasDarkTheme();
@@ -95,7 +98,7 @@ void EnableRoundCorners(HWND hwnd);
// This function draws over the section where the scroll bars meet
// to maintain a consistent theme
-void PaintScrollBarCorner(HWND hwnd);
+void PaintScrollBarCorner(wxWindow* w);
} // namespace wxMSWImpl
=====================================
include/wx/unix/private/epolldispatcher.h
=====================================
@@ -16,6 +16,10 @@
#include "wx/private/fdiodispatcher.h"
+#if wxUSE_THREADS
+ #include "wx/thread.h"
+#endif
+
struct epoll_event;
class WXDLLIMPEXP_BASE wxEpollDispatcher : public wxFDIODispatcher
@@ -44,8 +48,33 @@ private:
// given timeout
int DoPoll(epoll_event *events, int numEvents, int timeout) const;
+ // Look up the handler currently registered for the given descriptor, or
+ // nullptr if there is none (any more).
+ wxFDIOHandler *FindHandler(int fd) const;
+
+ // Record, replace or forget the handler for a descriptor. Kept separate
+ // from the epoll_ctl() calls so that the map is only updated once the
+ // kernel has accepted the change.
+ void StoreHandler(int fd, wxFDIOHandler *handler);
+ void ForgetHandler(int fd);
+
int m_epollDescriptor;
+
+ // Maps the descriptors we have registered to their handlers. Dispatch()
+ // needs this because epoll_event::data is a union: it holds the descriptor
+ // so that a handler unregistered while the batch is being processed can be
+ // detected, which means the handler pointer has to be found elsewhere.
+ //
+ // Guarded because descriptors are registered and unregistered from worker
+ // threads -- wxSocketImpl does it from the thread performing the socket
+ // operation -- while Dispatch() reads the map from the thread running the
+ // event loop. epoll_ctl() itself is thread-safe, so nothing but this map
+ // needs the protection, and the lock is never held across a handler call.
+ wxFDIOHandlerMap m_handlers;
+#if wxUSE_THREADS
+ mutable wxCriticalSection m_handlersCS;
+#endif // wxUSE_THREADS
};
#endif // wxUSE_EPOLL_DISPATCHER
=====================================
interface/wx/dialog.h
=====================================
@@ -112,8 +112,6 @@ enum wxDialogLayoutAdaptationMode
@style{wxMINIMIZE_BOX}
Displays a minimize box on the dialog. This style implicitly enables
wxCAPTION too.
- @style{wxTHICK_FRAME}
- Display a thick frame around the window.
@style{wxSTAY_ON_TOP}
The dialog stays on top of all other windows.
@style{wxNO_3D}
=====================================
interface/wx/settings.h
=====================================
@@ -204,8 +204,8 @@ enum wxSystemMetric
wxSYS_WINDOWMIN_Y, //!< Minimum height of a window.
wxSYS_SCREEN_X, //!< Width of the screen in pixels.
wxSYS_SCREEN_Y, //!< Height of the screen in pixels.
- wxSYS_FRAMESIZE_X, //!< Width of the window frame for a wxTHICK_FRAME window.
- wxSYS_FRAMESIZE_Y, //!< Height of the window frame for a wxTHICK_FRAME window.
+ wxSYS_FRAMESIZE_X, //!< Width of the window frame for a window with wxRESIZE_BORDER.
+ wxSYS_FRAMESIZE_Y, //!< Height of the window frame for a window with wxRESIZE_BORDER.
wxSYS_SMALLICON_X, //!< Recommended width of a small icon (in window captions, and small icon view).
wxSYS_SMALLICON_Y, //!< Recommended height of a small icon (in window captions, and small icon view).
wxSYS_HSCROLL_Y, //!< Height of horizontal scrollbar in pixels.
=====================================
samples/except/except.cpp
=====================================
@@ -481,7 +481,7 @@ bool MyFrame::ProcessEvent(wxEvent& event)
{
return wxFrame::ProcessEvent(event);
}
- catch ( const wxChar *msg )
+ catch ( const char *msg )
{
wxLogMessage("Caught a string \"%s\" in MyFrame", msg);
=====================================
src/common/prntbase.cpp
=====================================
@@ -66,6 +66,24 @@
// at least use it here instead of hardcoding the number.
static const int DEFAULT_MAX_PAGES = 32000;
+namespace
+{
+
+wxPreviewControlBar *GetPreviewControlBar(wxPrintPreviewBase *preview,
+ wxWindow *parent)
+{
+ wxPreviewFrame *frame = nullptr;
+ if ( preview )
+ frame = wxDynamicCast(preview->GetFrame(), wxPreviewFrame);
+
+ if ( !frame )
+ frame = wxDynamicCast(parent, wxPreviewFrame);
+
+ return frame ? frame->GetControlBar() : nullptr;
+}
+
+} // anonymous namespace
+
//----------------------------------------------------------------------------
// wxPrintFactory
//----------------------------------------------------------------------------
@@ -1024,7 +1042,14 @@ void wxPreviewCanvas::OnSysColourChanged(wxSysColourChangedEvent& event)
void wxPreviewCanvas::OnChar(wxKeyEvent &event)
{
- wxPreviewControlBar* controlBar = ((wxPreviewFrame*) GetParent())->GetControlBar();
+ wxPreviewControlBar * const
+ controlBar = GetPreviewControlBar(m_printPreview, GetParent());
+ if ( !controlBar )
+ {
+ event.Skip();
+ return;
+ }
+
switch (event.GetKeyCode())
{
case WXK_RETURN:
@@ -1067,10 +1092,10 @@ void wxPreviewCanvas::OnChar(wxKeyEvent &event)
void wxPreviewCanvas::OnMouseWheel(wxMouseEvent& event)
{
- wxPreviewControlBar *
- controlBar = wxStaticCast(GetParent(), wxPreviewFrame)->GetControlBar();
+ wxPreviewControlBar * const
+ controlBar = GetPreviewControlBar(m_printPreview, GetParent());
- if ( controlBar )
+ if ( controlBar && m_printPreview )
{
if ( event.ControlDown() && event.GetWheelRotation() != 0 )
{
=====================================
src/generic/dbgrptg.cpp
=====================================
@@ -41,7 +41,9 @@
#endif
#include "wx/mimetype.h"
+#include "wx/statbox.h"
#include "wx/statline.h"
+#include "wx/textwrapper.h"
#ifdef __WXMSW__
#include "wx/evtloop.h" // for SetCriticalWindow()
@@ -329,23 +331,30 @@ wxDebugReportDialog::wxDebugReportDialog(wxDebugReport& dbgrpt)
const wxSizerFlags flagsExpand(SizerFlags(1));
const wxSizerFlags flagsExpand2(SizerFlags(2));
- wxSizer *sizerPreview =
+ // Note that everything inside a wxStaticBoxSizer has to be created as a
+ // child of its wxStaticBox rather than of the dialog: using the box's own
+ // parent still works, for compatibility, but warns in a debug build.
+ wxStaticBoxSizer *sizerPreview =
new wxStaticBoxSizer(wxVERTICAL, this, _("&Debug report preview:"));
- sizerPreview->Add(CreateTextSizer(msg), wxSizerFlags().Centre().Border());
+ wxWindow * const boxPreview = sizerPreview->GetStaticBox();
+
+ wxTextSizerWrapper wrapperPreview(boxPreview);
+ sizerPreview->Add(CreateTextSizer(msg, wrapperPreview),
+ wxSizerFlags().Centre().Border());
// ... and the list of files in this debug report with buttons to view them
wxSizer *sizerFileBtns = new wxBoxSizer(wxVERTICAL);
sizerFileBtns->AddStretchSpacer();
- sizerFileBtns->Add(new wxButton(this, wxID_VIEW_DETAILS, _("&View...")),
+ sizerFileBtns->Add(new wxButton(boxPreview, wxID_VIEW_DETAILS, _("&View...")),
wxSizerFlags().Border(wxBOTTOM));
- sizerFileBtns->Add(new wxButton(this, wxID_OPEN, _("&Open...")),
+ sizerFileBtns->Add(new wxButton(boxPreview, wxID_OPEN, _("&Open...")),
wxSizerFlags().Border(wxTOP));
sizerFileBtns->AddStretchSpacer();
#if wxUSE_CHECKLISTBOX
- m_checklst = new wxCheckListBox(this, wxID_ANY);
+ m_checklst = new wxCheckListBox(boxPreview, wxID_ANY);
#else
- m_checklst = new wxListBox(this, wxID_ANY);
+ m_checklst = new wxListBox(boxPreview, wxID_ANY);
#endif
wxSizer *sizerFiles = new wxBoxSizer(wxHORIZONTAL);
@@ -356,15 +365,18 @@ wxDebugReportDialog::wxDebugReportDialog(wxDebugReport& dbgrpt)
// lower part of the dialog: notes field
- wxSizer *sizerNotes = new wxStaticBoxSizer(wxVERTICAL, this, _("&Notes:"));
+ wxStaticBoxSizer *sizerNotes =
+ new wxStaticBoxSizer(wxVERTICAL, this, _("&Notes:"));
+ wxWindow * const boxNotes = sizerNotes->GetStaticBox();
msg = _("If you have any additional information pertaining to this bug\nreport, please enter it here and it will be joined to it:");
- m_notes = new wxTextCtrl(this, wxID_ANY, wxEmptyString,
+ m_notes = new wxTextCtrl(boxNotes, wxID_ANY, wxEmptyString,
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE);
- sizerNotes->Add(CreateTextSizer(msg), flagsFixed);
+ wxTextSizerWrapper wrapperNotes(boxNotes);
+ sizerNotes->Add(CreateTextSizer(msg, wrapperNotes), flagsFixed);
sizerNotes->Add(m_notes, flagsExpand);
=====================================
src/generic/wizard.cpp
=====================================
@@ -936,10 +936,10 @@ bool wxWizard::DoLayoutAdaptation()
page->SetSizer(newSizer, false /* don't delete the old sizer */);
- scrolledWindow->SetSizer(oldSizer);
-
wxStandardDialogLayoutAdapter::DoReparentControls(page, scrolledWindow);
+ scrolledWindow->SetSizer(oldSizer);
+
pages.Append(page);
windows.Append(scrolledWindow);
}
=====================================
src/html/htmprint.cpp
=====================================
@@ -556,12 +556,16 @@ void wxHtmlPrintout::RenderPage(wxDC *dc, int page)
if (!m_Headers[page % 2].empty())
{
m_RendererHdr.SetHtmlText(TranslateHeader(m_Headers[page % 2], page));
- m_RendererHdr.Render((int) (ppmm_h * m_MarginLeft), (int) (ppmm_v * m_MarginTop));
+ m_RendererHdr.Render((int) (ppmm_h * m_MarginLeft),
+ (int) (ppmm_v * m_MarginTop),
+ 0, m_HeaderHeight);
}
if (!m_Footers[page % 2].empty())
{
m_RendererHdr.SetHtmlText(TranslateHeader(m_Footers[page % 2], page));
- m_RendererHdr.Render((int) (ppmm_h * m_MarginLeft), (int) (pageHeight - ppmm_v * m_MarginBottom - m_FooterHeight));
+ m_RendererHdr.Render((int) (ppmm_h * m_MarginLeft),
+ (int) (pageHeight - ppmm_v * m_MarginBottom - m_FooterHeight),
+ 0, m_FooterHeight);
}
}
=====================================
src/msw/colordlg.cpp
=====================================
@@ -36,6 +36,7 @@
#include "wx/scopeguard.h"
#include "wx/msw/private.h"
+#include "wx/msw/private/darkmode.h"
#include "wx/msw/private/dpiaware.h"
#include <stdlib.h>
@@ -117,8 +118,8 @@ struct COLORINFO
UINT_PTR CALLBACK
wxColourDialogHookProc(HWND hwnd,
UINT uiMsg,
- WPARAM WXUNUSED(wParam),
- LPARAM WXUNUSED(lParam))
+ WPARAM wParam,
+ LPARAM lParam)
{
switch ( uiMsg )
{
@@ -144,7 +145,7 @@ wxColourDialogHookProc(HWND hwnd,
break;
}
- return 0;
+ return wxMSWDarkMode::CommonDialogHookProc(hwnd, uiMsg, wParam, lParam);
}
// ----------------------------------------------------------------------------
=====================================
src/msw/darkmode.cpp
=====================================
@@ -734,7 +734,7 @@ HandleMenuMessage(WXLRESULT* result,
// which is one pixel too small), so we have to draw over it here
// to get rid of it.
{
- *result = w->MSWDefWindowProc(nMsg, wParam, lParam);
+ *result = w->wxWindow::MSWWindowProc(nMsg, wParam, lParam);
HWND hwnd = GetHwndOf(w);
WindowHDC hdc(hwnd);
@@ -846,6 +846,250 @@ void NotifySysColorChange()
gs_hasChanged = true;
}
+// This subclass procedure draws check box controls.
+static LRESULT CALLBACK CommonDialogCheckBoxProc(HWND hwnd, UINT uMsg,
+ WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass,
+ DWORD_PTR WXUNUSED(dwRefData))
+{
+ switch (uMsg)
+ {
+ case WM_PAINT:
+ {
+ PAINTSTRUCT ps;
+ HDC hdc = ::BeginPaint(hwnd, &ps);
+ RECT rcClient;
+ ::GetClientRect(hwnd, &rcClient);
+
+ // Clear background.
+ AutoHBRUSH hBgBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW).GetPixel());
+ ::FillRect(hdc, &rcClient, hBgBrush);
+
+ // Get foreground colour
+ auto fgIdx = ::IsWindowEnabled(hwnd) ? wxSYS_COLOUR_BTNTEXT : wxSYS_COLOUR_GRAYTEXT;
+ auto fgCol = wxSystemSettings::GetColour(fgIdx).GetPixel();
+
+ // Get the box size
+ SIZE boxSize = { 13, 13 };
+ HTHEME hTheme = ::OpenThemeData(hwnd, L"BUTTON");
+ if ( hTheme )
+ {
+ ::GetThemePartSize(hTheme, hdc, BP_CHECKBOX, CBS_UNCHECKEDNORMAL,
+ nullptr, TS_TRUE, &boxSize);
+ ::CloseThemeData(hTheme);
+ }
+
+ // Draw the box.
+ RECT rcBox = { };
+ rcBox.top = (rcClient.bottom - boxSize.cy) / 2;
+ rcBox.right = boxSize.cx;
+ rcBox.bottom = rcBox.top + boxSize.cy;
+ AutoHBRUSH hFgBrush(fgCol);
+ ::FrameRect(hdc, &rcBox, hFgBrush);
+
+ // Set the font.
+ HFONT hFont = (HFONT)::SendMessage(hwnd, WM_GETFONT, 0, 0);
+ HFONT hOldFont = (HFONT)::SelectObject(hdc, hFont);
+
+ // Draw check mark, if checked.
+ ::SetBkMode(hdc, TRANSPARENT);
+ ::SetTextColor(hdc, fgCol);
+ if ( ::SendMessage(hwnd, BM_GETCHECK, 0, 0) == BST_CHECKED )
+ {
+ // Draw a Unicode check mark character.
+ ::DrawTextW(hdc, L"\x2714", -1, &rcBox, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
+ }
+
+ // Draw the text.
+ const auto text = L" " + wxGetWindowText(hwnd);
+ RECT textRect = rcClient;
+ textRect.left = boxSize.cx;
+ ::DrawTextW(hdc, text.wc_str(), -1, &textRect, DT_SINGLELINE | DT_VCENTER);
+
+ // Draw focus rectangle
+ if ( ::GetFocus() == hwnd )
+ {
+ ::SetBkColor(hdc, 0);
+ ::SetTextColor(hdc, 0xffffff);
+ textRect.left++;
+ ::DrawFocusRect(hdc, &textRect);
+ }
+
+ ::SelectObject(hdc, hOldFont);
+ ::EndPaint(hwnd, &ps);
+ return 0;
+ }
+
+ case WM_NCDESTROY:
+ ::RemoveWindowSubclass(hwnd, CommonDialogCheckBoxProc, uIdSubclass);
+ break;
+ }
+
+ return ::DefSubclassProc(hwnd, uMsg, wParam, lParam);
+}
+
+// This subclass procedure draws ComboBox controls. It handles WM_DRAWITEM in
+// both the dialog and the ComboBox. The WM_DRAWITEM for a ComboBox goes to
+// the dialog, whereas the WM_DRAWITEM for the ComboLBox goes to the ComboBox.
+static LRESULT CALLBACK CommonDialogComboBoxProc(HWND hwnd, UINT uMsg,
+ WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass,
+ DWORD_PTR WXUNUSED(dwRefData))
+{
+ switch ( uMsg )
+ {
+ // For a ComboBox (ODT_COMBOBOX), draw the selected item shown at the
+ // top. For a ComboLBox (ODT_LISTBOX), draw an item in the list.
+ case WM_DRAWITEM:
+ {
+ auto dis = (DRAWITEMSTRUCT*)lParam;
+ // If this is an empty ComboLBox, do nothing.
+ if ( dis->CtlType == ODT_LISTBOX && dis->itemID == (UINT)-1 )
+ return true;
+
+ // Determine background and text colours.
+ wxSystemColour bg = wxSYS_COLOUR_WINDOW;
+ wxSystemColour fg = wxSYS_COLOUR_WINDOWTEXT;
+ bool isSelected = (dis->itemState & ODS_SELECTED) != 0;
+ bool hasFocus = (dis->itemState & ODS_FOCUS) != 0;
+ if ( dis->CtlType == ODT_COMBOBOX )
+ {
+ if ( isSelected && hasFocus )
+ {
+ bg = wxSYS_COLOUR_HIGHLIGHT;
+ fg = wxSYS_COLOUR_HIGHLIGHTTEXT;
+ }
+ }
+ else
+ {
+ if ( isSelected )
+ bg = wxSYS_COLOUR_LISTBOXHIGHLIGHT;
+ else
+ bg = wxSYS_COLOUR_LISTBOX;
+ fg = wxSYS_COLOUR_LISTBOXTEXT;
+ }
+
+ // Paint background
+ AutoHBRUSH hBrush(wxSystemSettings::GetColour(bg).GetPixel());
+ HDC hdc = dis->hDC;
+ ::FillRect(hdc, &dis->rcItem, hBrush);
+
+ // Draw text, if any.
+ if ( dis->itemID != (UINT)-1 )
+ {
+ wchar_t itemText[256] = { 0 };
+ HWND tHwnd = dis->CtlType == ODT_COMBOBOX ? dis->hwndItem : hwnd;
+ ::SendMessageW(tHwnd, CB_GETLBTEXT, dis->itemID, (LPARAM)itemText);
+ ::SetBkMode(hdc, TRANSPARENT);
+ ::SetTextColor(hdc, wxSystemSettings::GetColour(fg).GetPixel());
+ ::DrawTextW(hdc, itemText, -1, &dis->rcItem,
+ DT_NOPREFIX | DT_SINGLELINE | DT_VCENTER);
+ }
+ return true;
+ }
+
+ case WM_NCDESTROY:
+ ::RemoveWindowSubclass(hwnd, CommonDialogComboBoxProc, uIdSubclass);
+ break;
+ }
+
+ return ::DefSubclassProc(hwnd, uMsg, wParam, lParam);
+}
+
+// Enable dark mode for a common dialog child control.
+static BOOL CALLBACK CommonDialogChild(HWND hwnd, LPARAM lParam)
+{
+ // Get control info.
+ const auto className = wxGetWindowClass(hwnd);
+ auto style = ::GetWindowLongPtrW(hwnd, GWL_STYLE);
+
+ if ( className == "ComboBox" )
+ {
+ // If the control is owner-draw, subclass it to draw the ComboLBox.
+ if ( style & CBS_OWNERDRAWFIXED )
+ ::SetWindowSubclass(hwnd, CommonDialogComboBoxProc, 1, 0);
+
+ // Handle the inner ComboLBox.
+ WinStruct<COMBOBOXINFO> info;
+ ::GetComboBoxInfo(hwnd, &info);
+ CommonDialogChild(info.hwndList, lParam);
+ }
+
+ // If available, apply DarkMode_DarkTheme. It makes most controls look good.
+ if ( wxCheckOsVersion(10, 0, 26200) )
+ {
+ AllowForWindow(hwnd, L"DarkMode_DarkTheme");
+ return true;
+ }
+
+ // Special handling for controls needed for older Windows versions.
+
+ if ( className == "ComboBox" )
+ AllowForWindow(hwnd, L"CFD");
+ else
+ AllowForWindow(hwnd);
+
+ if ( className == "Button" )
+ {
+ // For the button types below, the text should be white but is black.
+ // Disable theme rendering and instead rely on the colors set by
+ // handling WM_CTLCOLORSTATIC.
+ auto bs = style & BS_TYPEMASK;
+ if ( bs == BS_AUTORADIOBUTTON || bs == BS_GROUPBOX || bs == BS_RADIOBUTTON )
+ {
+ ::SetWindowTheme(hwnd, L"", L"");
+ }
+
+ // Custom draw check boxes.
+ if ( bs == BS_AUTOCHECKBOX )
+ ::SetWindowSubclass(hwnd, CommonDialogCheckBoxProc, 1, 0);
+ }
+ else if ( className == "Edit" )
+ {
+ // The border looks bad. Change it to a simple border.
+ auto exStyle = ::GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
+ exStyle &= ~WS_EX_CLIENTEDGE;
+ ::SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle);
+ style |= WS_BORDER;
+ ::SetWindowLongPtrW(hwnd, GWL_STYLE, style);
+ ::SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
+ SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
+ }
+ return true;
+}
+
+UINT_PTR CALLBACK CommonDialogHookProc(HWND hwnd, UINT uiMsg, WPARAM wParam,
+ LPARAM WXUNUSED(lParam))
+{
+ if ( !IsActive() )
+ return 0;
+
+ switch ( uiMsg )
+ {
+ case WM_INITDIALOG:
+ // Enable dark for dialog window.
+ wxMSWDarkMode::ConfigureTLW(hwnd);
+ // Enable dark mode for children.
+ ::EnumChildWindows(hwnd, CommonDialogChild, 0);
+ // Subclass the dialog.
+ ::SetWindowSubclass(hwnd, CommonDialogComboBoxProc, 1, 0);
+ break;
+
+ case WM_CTLCOLORBTN:
+ case WM_CTLCOLORDLG:
+ return (INT_PTR)GetBackgroundBrush();
+
+ case WM_CTLCOLOREDIT:
+ case WM_CTLCOLORLISTBOX:
+ case WM_CTLCOLORSTATIC:
+ ::SetBkColor((HDC)wParam,
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW).GetPixel());
+ ::SetTextColor((HDC)wParam,
+ wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT).GetPixel());
+ return (INT_PTR)GetBackgroundBrush();
+ }
+
+ return 0;
+}
+
bool HasDarkTheme()
{
return wxCheckOsVersion(10, 0, 26200);
@@ -853,8 +1097,9 @@ bool HasDarkTheme()
} // namespace wxMSWDarkMode
-void wxMSWImpl::PaintScrollBarCorner(HWND hwnd)
+void wxMSWImpl::PaintScrollBarCorner(wxWindow* w)
{
+ HWND hwnd = GetHwndOf(w);
WinStruct<SCROLLBARINFO> sbiV, sbiH;
if ( !::GetScrollBarInfo(hwnd, OBJID_VSCROLL, &sbiV) ||
@@ -868,12 +1113,14 @@ void wxMSWImpl::PaintScrollBarCorner(HWND hwnd)
const RECT windowRect = wxGetWindowRect(hwnd);
RECT rectToPaint;
- rectToPaint.left = sbiV.rcScrollBar.left - windowRect.left;
- rectToPaint.top = sbiH.rcScrollBar.top - windowRect.top;
- // Constrain outer limits by exactly -1 to snap cleanly to the visual frame edge
- rectToPaint.right = (windowRect.right - windowRect.left) - 1;
- rectToPaint.bottom = (windowRect.bottom - windowRect.top) - 1;
+ if ( ::GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_LAYOUTRTL )
+ rectToPaint.left = windowRect.right - sbiV.rcScrollBar.right;
+ else
+ rectToPaint.left = sbiV.rcScrollBar.left - windowRect.left;
+ rectToPaint.top = sbiH.rcScrollBar.top - windowRect.top;
+ rectToPaint.right = rectToPaint.left + wxGetSystemMetrics(SM_CXVSCROLL, w);
+ rectToPaint.bottom = rectToPaint.top + wxGetSystemMetrics(SM_CYHSCROLL, w);
WindowHDC hdcWin(hwnd);
AutoHBRUSH hBrush(RGB(0x17, 0x17, 0x17));
@@ -883,7 +1130,7 @@ void wxMSWImpl::PaintScrollBarCorner(HWND hwnd)
#else // !wxUSE_DARK_MODE
bool
-wxApp::MSWEnableDarkMode(int WXUNUSED(flags),
+wxApp::MSWEnableDarkMode(DarkMode WXUNUSED(flags),
wxDarkModeSettings* WXUNUSED(settings))
{
return false;
@@ -926,6 +1173,10 @@ void ConfigureTLW(HWND WXUNUSED(hwnd))
{
}
+void SetTheme(HWND WXUNUSED(hwnd), const wchar_t* WXUNUSED(themeName), const wchar_t* WXUNUSED(themeId))
+{
+}
+
void AllowForWindow(HWND WXUNUSED(hwnd), const wchar_t* WXUNUSED(themeClass), const wchar_t* WXUNUSED(themeId))
{
}
@@ -969,6 +1220,12 @@ void NotifySysColorChange()
{
}
+UINT_PTR CALLBACK CommonDialogHookProc(HWND WXUNUSED(hwnd),
+ UINT WXUNUSED(uiMsg), WPARAM WXUNUSED(wParam), LPARAM WXUNUSED(lParam))
+{
+ return 0;
+}
+
bool HasDarkTheme()
{
return false;
@@ -976,7 +1233,7 @@ bool HasDarkTheme()
} // namespace wxMSWDarkMode
-void wxMSWImpl::PaintScrollBarCorner(HWND WXUNUSED(hwnd))
+void wxMSWImpl::PaintScrollBarCorner(wxWindow* WXUNUSED(w))
{
}
=====================================
src/msw/fdrepdlg.cpp
=====================================
@@ -28,6 +28,7 @@
#endif
#include "wx/fdrepdlg.h"
+#include "wx/msw/private/darkmode.h"
// Use functions from src/msw/window.cpp
extern void wxRemoveHandleAssociation(wxWindowMSW *win);
@@ -268,7 +269,7 @@ wxFindReplaceDialogImpl::FindMessageHandler(wxWindow * WXUNUSED(win),
UINT_PTR CALLBACK
wxFindReplaceDialogHookProc(HWND hwnd,
UINT uiMsg,
- WPARAM WXUNUSED(wParam),
+ WPARAM wParam,
LPARAM lParam)
{
if ( uiMsg == WM_INITDIALOG )
@@ -278,11 +279,13 @@ wxFindReplaceDialogHookProc(HWND hwnd,
::SetWindowText(hwnd, dialog->GetTitle().t_str());
+ wxMSWDarkMode::CommonDialogHookProc(hwnd, uiMsg, wParam, lParam);
+
// don't return FALSE from here or the dialog won't be shown
return TRUE;
}
- return 0;
+ return wxMSWDarkMode::CommonDialogHookProc(hwnd, uiMsg, wParam, lParam);
}
// ============================================================================
=====================================
src/msw/fontdlg.cpp
=====================================
@@ -34,6 +34,7 @@
#include "wx/fontutil.h"
#include "wx/display.h"
+#include "wx/msw/private/darkmode.h"
#include "wx/msw/private/dpiaware.h"
#include <stdlib.h>
@@ -57,7 +58,7 @@ static
UINT_PTR CALLBACK
wxFontDialogHookProc(HWND hwnd,
UINT uiMsg,
- WPARAM WXUNUSED(wParam),
+ WPARAM wParam,
LPARAM lParam)
{
if ( uiMsg == WM_INITDIALOG )
@@ -65,11 +66,12 @@ wxFontDialogHookProc(HWND hwnd,
CHOOSEFONT *pCH = (CHOOSEFONT *)lParam;
wxFontDialog * const
dialog = reinterpret_cast<wxFontDialog *>(pCH->lCustData);
-
- ::SetWindowText(hwnd, dialog->GetTitle().t_str());
+ const auto& title = dialog->GetTitle();
+ if ( !title.empty() )
+ ::SetWindowText(hwnd, title.t_str());
}
- return 0;
+ return wxMSWDarkMode::CommonDialogHookProc(hwnd, uiMsg, wParam, lParam);
}
// ----------------------------------------------------------------------------
@@ -105,8 +107,7 @@ int wxFontDialog::ShowModal()
wxWindowDisabler disableOthers(this, parent);
- // It should be OK to always use GDI simulations
- DWORD flags = CF_SCREENFONTS /* | CF_NOSIMULATIONS */ ;
+ DWORD flags = CF_SCREENFONTS | CF_ENABLEHOOK;
LOGFONT logFont;
@@ -116,15 +117,8 @@ int wxFontDialog::ShowModal()
chooseFontStruct.lStructSize = sizeof(CHOOSEFONT);
chooseFontStruct.hwndOwner = hWndParent;
chooseFontStruct.lpLogFont = &logFont;
-
- // Currently we only use the hook to set the title, so only set it up if
- // we really need to do this.
- if ( !m_title.empty() )
- {
- flags |= CF_ENABLEHOOK;
- chooseFontStruct.lCustData = (LPARAM)this;
- chooseFontStruct.lpfnHook = wxFontDialogHookProc;
- }
+ chooseFontStruct.lCustData = (LPARAM)this;
+ chooseFontStruct.lpfnHook = wxFontDialogHookProc;
// The native font dialog does not support moving between displays with
// different DPIs. Check if it will be shown system-dpi-aware.
=====================================
src/msw/listctrl.cpp
=====================================
@@ -3742,20 +3742,6 @@ wxListCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
// PRF_CHILDREN flag, so leave it to the native control itself
return MSWDefWindowProc(nMsg, wParam, lParam);
- case WM_NCPAINT:
- // In dark mode the corner between the 2 scrollbars is not drawn in
- // the correct colour by default, so paint it over if necessary.
- if ( wxMSWDarkMode::IsActive() )
- {
- // Let the control paint itself first.
- auto const rc =
- wxListCtrlBase::MSWWindowProc(nMsg, wParam, lParam);
-
- wxMSWImpl::PaintScrollBarCorner(GetHwnd());
- return rc;
- }
- break;
-
case WM_CONTEXTMENU:
// because this message is propagated upwards the child-parent
// chain, we get it for the right clicks on the header window but
=====================================
src/msw/printdlg.cpp
=====================================
@@ -34,6 +34,7 @@
#include "wx/printdlg.h"
#include "wx/msw/printdlg.h"
#include "wx/msw/dcprint.h"
+#include "wx/msw/private/darkmode.h"
#include "wx/paper.h"
#include "wx/modalhook.h"
@@ -1192,7 +1193,7 @@ bool wxWindowsPageSetupDialog::ConvertToNative( wxPageSetupDialogData &data )
native_data->SetDevNames(nullptr);
}
- pd->Flags = PSD_MARGINS|PSD_MINMARGINS;
+ pd->Flags = PSD_MARGINS | PSD_MINMARGINS | PSD_ENABLEPAGESETUPHOOK;
if ( data.GetDefaultMinMargins() )
pd->Flags |= PSD_DEFAULTMINMARGINS;
@@ -1237,7 +1238,7 @@ bool wxWindowsPageSetupDialog::ConvertToNative( wxPageSetupDialogData &data )
wxPageToNative(data.GetMarginBottomRight().y, pageSetupUnits);
pd->lCustData = 0;
- pd->lpfnPageSetupHook = nullptr;
+ pd->lpfnPageSetupHook = wxMSWDarkMode::CommonDialogHookProc;
pd->lpfnPagePaintHook = nullptr;
pd->hPageSetupTemplate = nullptr;
pd->lpPageSetupTemplateName = nullptr;
=====================================
src/msw/treectrl.cpp
=====================================
@@ -36,7 +36,6 @@
#include <windowsx.h> // needed by GET_X_LPARAM and GET_Y_LPARAM macros
#include "wx/msw/private.h"
-#include "wx/msw/private/darkmode.h"
#include "wx/msw/winundef.h"
#include "wx/msw/private/winstyle.h"
@@ -2788,15 +2787,6 @@ wxTreeCtrl::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
WXLRESULT rc = 0;
bool isMultiple = HasFlag(wxTR_MULTIPLE);
- if ( nMsg == WM_NCPAINT && wxMSWDarkMode::IsActive() )
- {
- // As with wxListCtrl, we need to draw the corner between two
- // scrollbars ourselves in dark mode to give it correct colour.
- rc = wxTreeCtrlBase::MSWWindowProc(nMsg, wParam, lParam);
- wxMSWImpl::PaintScrollBarCorner(GetHwnd());
- return rc;
- }
-
if ( nMsg == WM_CONTEXTMENU )
{
int x = GET_X_LPARAM(lParam),
=====================================
src/msw/window.cpp
=====================================
@@ -3901,6 +3901,30 @@ wxWindowMSW::MSWHandleMessage(WXLRESULT *result,
wxWindowMSW::MSWDrawThemeBorder(hdc);
}
}
+
+ // In dark mode the corner between the two scrollbars is not
+ // drawn in the correct colour by Windows, so we need to paint
+ // it ourselves.
+ //
+ // Don't do it in wxUniv which doesn't support wxMSW dark mode.
+#ifndef __WXUNIVERSAL__
+ if ( wxMSWDarkMode::IsActive() )
+ {
+ const long style = ::GetWindowLong(GetHwnd(), GWL_STYLE);
+
+ if ( (style & WS_HSCROLL) && (style & WS_VSCROLL) )
+ {
+ // MSWDefWindowProc() may have been already called above.
+ if ( !processed )
+ {
+ rc.result = MSWDefWindowProc(message, wParam, lParam);
+ processed = true;
+ }
+
+ wxMSWImpl::PaintScrollBarCorner(this);
+ }
+ }
+#endif // !__WXUNIVERSAL__
}
break;
=====================================
src/qt/evtloop.cpp
=====================================
@@ -14,6 +14,7 @@
#include "wx/private/eventloopsourcesmanager.h"
#include <QtCore/QCoreApplication>
+#include <QtCore/QEvent>
#include <QtCore/QAbstractEventDispatcher>
#include <QtCore/QSocketNotifier>
#include <QtCore/QTimer>
@@ -156,8 +157,27 @@ bool wxQtEventLoopBase::QtDispatch() const
void wxQtEventLoopBase::WakeUp()
{
- QAbstractEventDispatcher *instance = QAbstractEventDispatcher::instance();
- if ( instance )
+ // Post a no-op Qt event to the (main-thread) idle timer rather than only
+ // calling QAbstractEventDispatcher::wakeUp().
+ //
+ // wx pending events queued via wxEvtHandler::CallAfter() (including the
+ // cross-thread marshalling used by e.g. wxIPC worker threads) are processed
+ // by wxQtIdleTimer::idle() -> ProcessPendingEvents(), and that idle timer is
+ // (re)scheduled by the application-wide event filter, which only runs when a
+ // real Qt *event* is dispatched. Merely waking the dispatcher returns the
+ // blocked event loop from its wait but posts no event, so the filter never
+ // runs, the idle timer is never rescheduled, and the queued pending events
+ // are not processed until some unrelated Qt event happens to arrive. This
+ // stalled cross-thread CallAfter() indefinitely under wxQt.
+ //
+ // QCoreApplication::postEvent() is documented to be thread-safe; posting to
+ // the idle timer (which lives in the main thread) both wakes the loop and
+ // drives the event filter so the idle check, and thus the pending events,
+ // run promptly.
+ if ( m_qtIdleTimer )
+ QCoreApplication::postEvent(m_qtIdleTimer.get(), new QEvent(QEvent::User));
+ else if ( QAbstractEventDispatcher *instance =
+ QAbstractEventDispatcher::instance() )
instance->wakeUp();
}
=====================================
src/unix/epolldispatcher.cpp
=====================================
@@ -104,11 +104,47 @@ wxEpollDispatcher::~wxEpollDispatcher()
}
}
+wxFDIOHandler *wxEpollDispatcher::FindHandler(int fd) const
+{
+#if wxUSE_THREADS
+ wxCriticalSectionLocker lock(m_handlersCS);
+#endif
+
+ const wxFDIOHandlerMap::const_iterator it = m_handlers.find(fd);
+
+ return it == m_handlers.end() ? nullptr : it->second.handler;
+}
+
+void wxEpollDispatcher::StoreHandler(int fd, wxFDIOHandler *handler)
+{
+#if wxUSE_THREADS
+ wxCriticalSectionLocker lock(m_handlersCS);
+#endif
+
+ // Deliberately an unconditional assignment and not an insertion that
+ // complains about an existing entry: wxFDIOManagerUnix decides between
+ // RegisterFD() and ModifyFD() from the mask it keeps on the handler, not
+ // from what this dispatcher knows, so the two can legitimately disagree
+ // about whether a descriptor is already registered.
+ m_handlers[fd] = wxFDIOHandlerEntry(handler, 0);
+}
+
+void wxEpollDispatcher::ForgetHandler(int fd)
+{
+#if wxUSE_THREADS
+ wxCriticalSectionLocker lock(m_handlersCS);
+#endif
+
+ m_handlers.erase(fd);
+}
+
bool wxEpollDispatcher::RegisterFD(int fd, wxFDIOHandler* handler, int flags)
{
epoll_event ev;
ev.events = GetEpollMask(flags, fd);
- ev.data.ptr = handler;
+
+ // The descriptor and not the handler: see Dispatch() for why.
+ ev.data.fd = fd;
const int ret = epoll_ctl(m_epollDescriptor, EPOLL_CTL_ADD, fd, &ev);
if ( ret != 0 )
@@ -118,6 +154,8 @@ bool wxEpollDispatcher::RegisterFD(int fd, wxFDIOHandler* handler, int flags)
return false;
}
+
+ StoreHandler(fd, handler);
wxLogTrace(wxEpollDispatcher_Trace,
wxT("Added fd %d (handler %p) to epoll %d"), fd, handler, m_epollDescriptor);
@@ -128,7 +166,7 @@ bool wxEpollDispatcher::ModifyFD(int fd, wxFDIOHandler* handler, int flags)
{
epoll_event ev;
ev.events = GetEpollMask(flags, fd);
- ev.data.ptr = handler;
+ ev.data.fd = fd;
const int ret = epoll_ctl(m_epollDescriptor, EPOLL_CTL_MOD, fd, &ev);
if ( ret != 0 )
@@ -139,6 +177,8 @@ bool wxEpollDispatcher::ModifyFD(int fd, wxFDIOHandler* handler, int flags)
return false;
}
+ StoreHandler(fd, handler);
+
wxLogTrace(wxEpollDispatcher_Trace,
wxT("Modified fd %d (handler: %p) on epoll %d"), fd, handler, m_epollDescriptor);
return true;
@@ -155,6 +195,11 @@ bool wxEpollDispatcher::UnregisterFD(int fd)
wxLogSysError(_("Failed to unregister descriptor %d from epoll descriptor %d"),
fd, m_epollDescriptor);
}
+ // Drop the handler even if epoll_ctl() above failed: the caller is done
+ // with it either way, and a stale entry here is exactly what Dispatch()
+ // must not find.
+ ForgetHandler(fd);
+
wxLogTrace(wxEpollDispatcher_Trace,
wxT("removed fd %d from %d"), fd, m_epollDescriptor);
return true;
@@ -218,12 +263,18 @@ int wxEpollDispatcher::Dispatch(int timeout)
int numEvents = 0;
for ( epoll_event *p = events; p < events + rc; p++ )
{
- wxFDIOHandler * const handler = (wxFDIOHandler *)(p->data.ptr);
+ // Look the handler up now instead of using a pointer recorded when
+ // the descriptor was registered: dispatching an earlier event of this
+ // batch may have unregistered -- and, in the code that owns it,
+ // destroyed -- the handler for a later one, and epoll_wait() filled
+ // this array in before any of that happened. Using the recorded
+ // pointer would then call a virtual function on a destroyed object.
+ //
+ // A descriptor that is no longer registered is therefore expected here
+ // rather than an error, and is simply skipped.
+ wxFDIOHandler * const handler = FindHandler(p->data.fd);
if ( !handler )
- {
- wxFAIL_MSG( wxT("null handler in epoll_event?") );
continue;
- }
// note that for compatibility with wxSelectDispatcher we call
// OnReadWaiting() on EPOLLHUP as this is what epoll_wait() returns
=====================================
tests/controls/gridtest.cpp
=====================================
@@ -901,6 +901,33 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Cursor", "[grid]")
CHECK(m_grid->GetGridCursorCol() == 1);
CHECK(m_grid->GetGridCursorRow() == 0);
+
+ m_grid->SetGridCursor(1, 0);
+ m_grid->HideRow(2);
+
+ CHECK(m_grid->MoveCursorDown(false));
+ CHECK(m_grid->GetGridCursorCol() == 0);
+ CHECK(m_grid->GetGridCursorRow() == 3);
+ CHECK(m_grid->IsRowShown(m_grid->GetGridCursorRow()));
+
+ CHECK(m_grid->MoveCursorUp(false));
+ CHECK(m_grid->GetGridCursorCol() == 0);
+ CHECK(m_grid->GetGridCursorRow() == 1);
+ CHECK(m_grid->IsRowShown(m_grid->GetGridCursorRow()));
+
+ m_grid->AppendCols(2);
+ m_grid->SetGridCursor(0, 0);
+ m_grid->HideCol(1);
+
+ CHECK(m_grid->MoveCursorRight(false));
+ CHECK(m_grid->GetGridCursorCol() == 2);
+ CHECK(m_grid->GetGridCursorRow() == 0);
+ CHECK(m_grid->IsColShown(m_grid->GetGridCursorCol()));
+
+ CHECK(m_grid->MoveCursorLeft(false));
+ CHECK(m_grid->GetGridCursorCol() == 0);
+ CHECK(m_grid->GetGridCursorRow() == 0);
+ CHECK(m_grid->IsColShown(m_grid->GetGridCursorCol()));
}
TEST_CASE_METHOD(GridTestCase, "Grid::KeyboardSelection", "[grid][selection]")
=====================================
tests/events/evtlooptest.cpp
=====================================
@@ -16,6 +16,9 @@
#include "wx/apptrait.h"
#include "wx/evtloop.h"
#include "wx/timer.h"
+#include "wx/app.h"
+#include "wx/evtloop.h"
+#include "wx/thread.h"
#include <memory>
@@ -126,3 +129,62 @@ TEST_CASE("EventLoop::TestExit", "[evtloop]")
timerRun2.StartOnce(1);
CHECK( loopOuter.Run() == EXIT_CODE_OUTER_LOOP );
}
+
+#if wxUSE_THREADS
+
+// Worker thread that, after giving the main thread time to enter and block in
+// its event loop, schedules a callback on the main thread via CallAfter().
+class ExitLoopFromThread : public wxThread
+{
+public:
+ ExitLoopFromThread(wxEventLoop& loop, int rc)
+ : wxThread(wxTHREAD_JOINABLE),
+ m_loop(loop),
+ m_rc(rc)
+ {
+ }
+
+protected:
+ virtual void *Entry() override
+ {
+ // Wait until the main thread is actually blocked waiting for events, so
+ // the CallAfter() below arrives while the loop is idle -- which is when
+ // it must still be able to wake the loop.
+ wxMilliSleep(100);
+
+ // wxEventLoop is not a wxEvtHandler, so route the deferred call through
+ // wxTheApp (which lives on, and runs the call on, the main thread).
+ wxEventLoop* const loop = &m_loop;
+ const int rc = m_rc;
+ wxTheApp->CallAfter([loop, rc] { loop->ScheduleExit(rc); });
+
+ return nullptr;
+ }
+
+private:
+ wxEventLoop& m_loop;
+ const int m_rc;
+
+ wxDECLARE_NO_COPY_CLASS(ExitLoopFromThread);
+};
+
+TEST_CASE("EventLoop::TestCrossThreadCallAfter", "[evtloop]")
+{
+ // A CallAfter() issued from another thread must wake the main event loop and
+ // run there, even when the loop is otherwise idle. This is a regression test
+ // for wxQt, where wxQtEventLoopBase::WakeUp() woke the loop without posting a
+ // Qt event, so queued pending events (CallAfter) were never processed and the
+ // Run() below would block forever. See src/qt/evtloop.cpp.
+ wxEventLoop loop;
+
+ ExitLoopFromThread thread(loop, EXIT_CODE_OUTER_LOOP);
+ REQUIRE( thread.Run() == wxTHREAD_NO_ERROR );
+
+ // If the cross-thread CallAfter() is delivered, the loop exits with the code
+ // the worker passed to ScheduleExit(); otherwise this hangs (the bug).
+ CHECK( loop.Run() == EXIT_CODE_OUTER_LOOP );
+
+ thread.Wait();
+}
+
+#endif // wxUSE_THREADS
=====================================
tests/events/evtsource.cpp
=====================================
@@ -1,9 +1,8 @@
///////////////////////////////////////////////////////////////////////////////
// Name: tests/events/evtsource.cpp
-// Purpose: Test the event sources
-// Author: Bartosz Bekier
-// Created: 2009-01-24
-// Copyright: (c) 2009 Bartosz Bekier <
bartosz...@gmail.com>
+// Purpose: Test wxFDIODispatcher under Unix
+// Created: 2026-08-26
+// Copyright: (c) 2026 wxWidgets development team
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
@@ -12,3 +11,135 @@
// ----------------------------------------------------------------------------
#include "testprec.h"
+
+#ifndef WX_PRECOMP
+#endif // WX_PRECOMP
+
+#ifdef __UNIX__
+
+#include "wx/private/fdiodispatcher.h"
+
+#include <unistd.h>
+
+// ----------------------------------------------------------------------------
+// a handler that unregisters another one from inside its own callback
+// ----------------------------------------------------------------------------
+
+namespace
+{
+
+// Both handlers below are registered for descriptors that are ready at the
+// same time, so a single Dispatch() call sees both of them in one batch. The
+// first one to run unregisters the other, which must then not be called: the
+// dispatcher has to notice that the registration is gone rather than reuse
+// whatever it recorded when the batch was collected.
+class UnregisteringHandler : public wxFDIOHandler
+{
+public:
+ explicit UnregisteringHandler(int fd) : m_fd(fd) { }
+
+ void OnReadWaiting() override
+ {
+ m_called = true;
+
+ if ( m_peer && m_peer->m_registered )
+ {
+ wxFDIODispatcher::Get()->UnregisterFD(m_peer->m_fd);
+ m_peer->m_registered = false;
+ }
+ }
+
+ void OnWriteWaiting() override { }
+ void OnExceptionWaiting() override { }
+
+ int m_fd;
+ UnregisteringHandler* m_peer = nullptr;
+ bool m_registered = false;
+ bool m_called = false;
+};
+
+// Pipe whose read end is already readable, so that registering it guarantees
+// the dispatcher reports it immediately.
+struct ReadablePipe
+{
+ ReadablePipe()
+ {
+ if ( pipe(m_fds) != 0 )
+ {
+ m_fds[0] =
+ m_fds[1] = -1;
+ return;
+ }
+
+ const char b = 'x';
+ if ( write(m_fds[1], &b, 1) != 1 )
+ {
+ // Leave the descriptors valid; the test below checks readiness.
+ }
+ }
+
+ ~ReadablePipe()
+ {
+ if ( IsOk() )
+ {
+ close(m_fds[0]);
+ close(m_fds[1]);
+ }
+ }
+
+ int ReadEnd() const { return m_fds[0]; }
+ bool IsOk() const { return m_fds[0] != -1; }
+
+ int m_fds[2];
+};
+
+} // anonymous namespace
+
+// ----------------------------------------------------------------------------
+// the test itself
+// ----------------------------------------------------------------------------
+
+// A descriptor unregistered while the dispatcher is still working through the
+// events it collected must not have its handler called afterwards.
+//
+// wxSelectDispatcher looks the handler up per ready descriptor and so has
+// always behaved this way. wxEpollDispatcher used to store the handler pointer
+// in the epoll_event itself and call through the copy taken before any handler
+// ran, which meant a handler unregistered — and, in real code, destroyed —
+// while servicing an earlier event of the same batch was still called, on
+// memory its owner had already released.
+TEST_CASE("EventSource::UnregisterDuringDispatch", "[fdiodispatcher]")
+{
+ wxFDIODispatcher * const dispatcher = wxFDIODispatcher::Get();
+ REQUIRE( dispatcher );
+
+ ReadablePipe pipe1, pipe2;
+ REQUIRE( pipe1.IsOk() );
+ REQUIRE( pipe2.IsOk() );
+
+ UnregisteringHandler handler1(pipe1.ReadEnd());
+ UnregisteringHandler handler2(pipe2.ReadEnd());
+ handler1.m_peer = &handler2;
+ handler2.m_peer = &handler1;
+
+ REQUIRE( dispatcher->RegisterFD(handler1.m_fd, &handler1, wxFDIO_INPUT) );
+ handler1.m_registered = true;
+ REQUIRE( dispatcher->RegisterFD(handler2.m_fd, &handler2, wxFDIO_INPUT) );
+ handler2.m_registered = true;
+
+ // Both descriptors are readable, so this collects both of them and then
+ // dispatches the first, which unregisters the second.
+ const int numEvents = dispatcher->Dispatch(0);
+
+ // Exactly one handler ran: the one that was unregistered before its turn
+ // came must have been skipped.
+ CHECK( handler1.m_called != handler2.m_called );
+ CHECK( numEvents == 1 );
+
+ if ( handler1.m_registered )
+ dispatcher->UnregisterFD(handler1.m_fd);
+ if ( handler2.m_registered )
+ dispatcher->UnregisterFD(handler2.m_fd);
+}
+
+#endif // __UNIX__
=====================================
tests/html/htmprint.cpp
=====================================
@@ -74,6 +74,40 @@ TEST_CASE("wxHtmlDCRenderer::BodyBgColour", "[html][print]")
CHECK(actual == bg);
}
+TEST_CASE("wxHtmlPrintout::HeaderDoesNotEraseBody", "[html][print]")
+{
+ // Mirror the dimensions and scaling used by print preview and verify that
+ // rendering the header after the body only affects the header area.
+ const wxColour bg(0x12, 0x34, 0x56);
+ wxBitmap bmp(560, 790);
+ {
+ wxMemoryDC dc(bmp);
+ dc.SetBackground(*wxWHITE_BRUSH);
+ dc.Clear();
+
+ wxHtmlPrintout pr;
+ pr.SetHtmlText("<body bgcolor=\"#123456\"><p>Body</p></body>");
+ pr.SetHeader("Header<hr>", wxPAGE_ALL);
+ REQUIRE( pr.SetUp(dc) );
+
+ pr.SetPPIScreen(96, 96);
+ pr.SetPPIPrinter(600, 600);
+ pr.SetPageSizePixels(4960, 7016);
+ pr.SetPageSizeMM(210, 297);
+ pr.SetPaperRectPixels(wxRect(0, 0, 4960, 7016));
+
+ REQUIRE_NOTHROW( pr.OnPreparePrinting() );
+ REQUIRE( pr.HasPage(1) );
+ REQUIRE( pr.OnPrintPage(1) );
+ }
+
+ const wxImage image = bmp.ConvertToImage();
+ const wxColour actual(image.GetRed(280, 395),
+ image.GetGreen(280, 395),
+ image.GetBlue(280, 395));
+ CHECK( actual == bg );
+}
+
TEST_CASE("wxHtmlPrintout::Pagination", "[html][print]")
{
wxHtmlPrintout pr;
=====================================
tests/net/ipc.cpp
=====================================
@@ -17,18 +17,9 @@
// here.
//
// This test requires wxUSE_THREADS==1 since it runs the test server concurrently
-// with the client. One build configuration is excluded: wxQt.
+// with the client.
//
-// wxQt is excluded because of a bug in wxQt found during our testing:
-// wxIPC worker threads marshal their socket I/O to the main thread via
-// CallAfter(), but a cross-thread CallAfter() is not reliably processed by the
-// wxQt event loop. wxQtEventLoopBase::WakeUp() wakes the loop without posting a
-// Qt event, so the idle handler that runs pending events is never scheduled, and
-// server-pushed Advise() notifications stall. That is a wxQt event-loop bug, not
-// a wxIPC bug; it is fixed separately on branch
-// jpmattia/wxQT-CallAfter-wxWakeUpIdle, which will be a separate PR.
-//
-#if wxUSE_THREADS && !defined(__WXQT__)
+#if wxUSE_THREADS
#ifndef WX_PRECOMP
#include "wx/app.h"
@@ -1055,4 +1046,4 @@ TEST_CASE_METHOD(IPCFixture,
CHECK( worker.m_error.empty() );
}
-#endif // wxUSE_THREADS && !__WXQT__
+#endif // wxUSE_THREADS
=====================================
tests/net/ipc_test_server.cpp
=====================================
@@ -10,7 +10,7 @@
// Match the guard in tests/net/ipc.cpp
-#if wxUSE_THREADS && !defined(__WXQT__)
+#if wxUSE_THREADS
#ifndef WX_PRECOMP
#include "wx/app.h"
@@ -714,4 +714,4 @@ void IPCServerThread::WaitForExit()
launcher.DoStop();
}
-#endif // wxUSE_THREADS && !__WXQT__
+#endif // wxUSE_THREADS
=====================================
tests/net/ipc_test_server.h
=====================================
@@ -15,7 +15,7 @@
// Match the guard in tests/net/ipc.cpp and ipc_test_server.cpp: the IPC test is
// excluded from wxQt, so its declarations must be too.
-#if wxUSE_THREADS && !defined(__WXQT__)
+#if wxUSE_THREADS
#include <memory>
@@ -57,6 +57,6 @@ inline void WaitForThreadWithDispatch(wxThread& thread)
thread.Wait();
}
-#endif // wxUSE_THREADS && !__WXQT__
+#endif // wxUSE_THREADS
#endif // _WX_TESTS_NET_IPC_TEST_SERVER_H_
=====================================
tests/test.cpp
=====================================
@@ -49,11 +49,7 @@
#include "wx/socket.h"
#include "wx/evtloop.h"
-// __WXQT__ guard: see the longer note in tests/net/ipc.cpp. The IPC test (and
-// its server) is excluded from wxQt (cross-thread CallAfter() not processed by
-// the wxQt event loop, fixed separately on branch
-// jpmattia/wxQT-CallAfter-wxWakeUpIdle).
-#if wxUSE_THREADS && defined(TEST_HAS_IPC_SERVER) && !defined(__WXQT__)
+#if wxUSE_THREADS && defined(TEST_HAS_IPC_SERVER)
#define wxHAS_TEST_IPC_SERVER
#include "net/ipc_test_server.h"
@@ -358,8 +354,7 @@ public:
#ifdef wxHAS_TEST_IPC_SERVER
// The IPC test re-executes this same binary as its server (with
// WX_IPC_TEST_SERVER set), so test_gui must run the server here too,
- // exactly as the console test does in the non-GUI OnRun() below. See the
- // note there and tests/net/ipc.cpp for the wxQt exclusion.
+ // exactly as the console test does in the non-GUI OnRun() below.
if ( ShouldRunTestIPCServer() )
{
// Suppress the idle-driven test runner: RunIPCServerUntilStopped()
View it on GitLab:
https://gitlab.com/wxwidgets/wxwidgets/-/compare/0789523f4af07f43d57579de0e07609dd3049f21...fcc0fedd0db7ce2ddef49eb409b10a9d6dee9482
--
View it on GitLab:
https://gitlab.com/wxwidgets/wxwidgets/-/compare/0789523f4af07f43d57579de0e07609dd3049f21...fcc0fedd0db7ce2ddef49eb409b10a9d6dee9482
You're receiving this email because of your account on
gitlab.com. Manage all notifications:
https://gitlab.com/-/profile/notifications | Help:
https://gitlab.com/help