diff --git a/.github/workflows/root-ci-config/buildconfig/global.txt b/.github/workflows/root-ci-config/buildconfig/global.txt
index b6eedd626835b..1b0fc15e0ce63 100644
--- a/.github/workflows/root-ci-config/buildconfig/global.txt
+++ b/.github/workflows/root-ci-config/buildconfig/global.txt
@@ -71,6 +71,7 @@ pyroot=ON
pythia8=OFF
qt6web=OFF
qt6canv=OFF
+gtk4canv=OFF
roofit=ON
root7=ON
rootbench=OFF
diff --git a/cmake/modules/RootBuildOptions.cmake b/cmake/modules/RootBuildOptions.cmake
index d50fccf104969..ab3e7129c4dec 100644
--- a/cmake/modules/RootBuildOptions.cmake
+++ b/cmake/modules/RootBuildOptions.cmake
@@ -155,6 +155,7 @@ ROOT_BUILD_OPTION(opengl ON "Enable support for OpenGL (requires libGL and libGL
ROOT_BUILD_OPTION(pyroot ON "Enable support for automatic Python bindings (PyROOT)")
ROOT_BUILD_OPTION(pythia8 OFF "Enable support for Pythia 8.x [GPL]")
ROOT_BUILD_OPTION(qt6canv OFF "Enable Qt6 canvas (requires Qt6::Gui)")
+ROOT_BUILD_OPTION(gtk4canv OFF "Enable GTK4 canvas (requires GTK4)")
ROOT_BUILD_OPTION(qt6web OFF "Enable support for Qt6 web-based display (requires Qt6::WebEngineCore and Qt6::WebEngineWidgets)")
ROOT_BUILD_OPTION(roofit ON "Build the advanced fitting package RooFit, and RooStats for statistical tests. If xml is available, also build HistFactory.")
ROOT_BUILD_OPTION(root7 ON "Build ROOT 7 experimental components of ROOT")
@@ -245,6 +246,7 @@ if(all)
set(pythia8_defvalue ON)
set(pyroot_defvalue ON)
set(qt6canv_defvalue ON)
+ set(gtk4canv_defvalue ON)
set(qt6web_defvalue ON)
set(r_defvalue ON)
set(roofit_defvalue ON)
diff --git a/etc/plugins/TCanvasImp/P030_TGtk4Canvas.C b/etc/plugins/TCanvasImp/P030_TGtk4Canvas.C
new file mode 100644
index 0000000000000..f9d6f6ea1b14e
--- /dev/null
+++ b/etc/plugins/TCanvasImp/P030_TGtk4Canvas.C
@@ -0,0 +1,5 @@
+void P030_TGtk4Canvas()
+{
+ gPluginMgr->AddHandler("TCanvasImp", "TGtk4Canvas", "ROOT::Experimental::TGtk4Canvas",
+ "ROOTGtk4Canvas", "NewCanvas(TCanvas *, const char *, Int_t, Int_t, UInt_t, UInt_t)");
+}
diff --git a/etc/plugins/TGuiFactory/P030_TGtk4GuiFactory.C b/etc/plugins/TGuiFactory/P030_TGtk4GuiFactory.C
new file mode 100644
index 0000000000000..6ba1ec47adec0
--- /dev/null
+++ b/etc/plugins/TGuiFactory/P030_TGtk4GuiFactory.C
@@ -0,0 +1,5 @@
+void P030_TGtk4GuiFactory()
+{
+ gPluginMgr->AddHandler("TGuiFactory", "gtk4", "ROOT::Experimental::TGtk4GuiFactory",
+ "ROOTGtk4Canvas", "TGtk4GuiFactory()");
+}
diff --git a/etc/plugins/TVirtualPadEditor/P030_TGtk4GedEditor.C b/etc/plugins/TVirtualPadEditor/P030_TGtk4GedEditor.C
new file mode 100644
index 0000000000000..6ba40f9fc19f0
--- /dev/null
+++ b/etc/plugins/TVirtualPadEditor/P030_TGtk4GedEditor.C
@@ -0,0 +1,5 @@
+void P030_TGtk4GedEditor()
+{
+ gPluginMgr->AddHandler("TVirtualPadEditor", "gtk4", "ROOT::Experimental::TGtk4GedEditor",
+ "ROOTGtk4Canvas", "TGtk4GedEditor(TCanvas*)");
+}
diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt
index c8ed70ae4f357..582d92606bca6 100644
--- a/gui/CMakeLists.txt
+++ b/gui/CMakeLists.txt
@@ -16,6 +16,10 @@ if(qt6canv)
add_subdirectory(qt6canvas)
endif()
+if(gtk4canv)
+ add_subdirectory(gtk4canvas)
+endif()
+
if(webgui)
if(cefweb)
add_subdirectory(cefdisplay)
diff --git a/gui/gtk4canvas/CMakeLists.txt b/gui/gtk4canvas/CMakeLists.txt
new file mode 100644
index 0000000000000..a0e2e9d0e2439
--- /dev/null
+++ b/gui/gtk4canvas/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Copyright (C) 1995-2026, Rene Brun and Fons Rademakers.
+# All rights reserved.
+#
+# For the licensing terms see $ROOTSYS/LICENSE.
+# For the list of contributors see $ROOTSYS/README/CREDITS.
+
+############################################################################
+# CMakeLists.txt file for building ROOT gui/gtk4canvas package
+############################################################################
+
+find_package(PkgConfig)
+
+pkg_check_modules(GTKMM REQUIRED IMPORTED_TARGET gtkmm-4.0)
+
+if(NOT GTKMM_FOUND)
+ if(fail-on-missing)
+ message(FATAL_ERROR "Could NOT find gtkmm-4.0 components")
+ else()
+ message(WARNING "GTKMM not found, disabling option 'gtk4canv'")
+ set(gtk4canv OFF CACHE BOOL "Disabled because GTKMM not found" FORCE)
+ return()
+ endif()
+endif()
+
+
+ROOT_STANDARD_LIBRARY_PACKAGE(ROOTGtk4Canvas
+ HEADERS
+ TGtk4Canvas.h
+ TGtk4GuiFactory.h
+ TGtk4PadPainter.h
+ TGtk4GedEditor.h
+ SOURCES
+ Gtk4DrawArea.cxx
+ Gtk4CanvasWindow.cxx
+ Gtk4MethodDialog.cxx
+ TGtk4ContextMenu.cxx
+ TGtk4Canvas.cxx
+ TGtk4GuiFactory.cxx
+ TGtk4PadPainter.cxx
+ TGtk4GedEditor.cxx
+ LIBRARIES
+ PkgConfig::GTKMM
+ DEPENDENCIES
+ Core
+ Gpad
+)
diff --git a/gui/gtk4canvas/inc/LinkDef.h b/gui/gtk4canvas/inc/LinkDef.h
new file mode 100644
index 0000000000000..bacef72c08f31
--- /dev/null
+++ b/gui/gtk4canvas/inc/LinkDef.h
@@ -0,0 +1,22 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#ifdef __CLING__
+
+#pragma link off all globals;
+#pragma link off all classes;
+#pragma link off all functions;
+
+#pragma link C++ class ROOT::Experimental::TGtk4Canvas+;
+#pragma link C++ class ROOT::Experimental::TGtk4PadPainter+;
+#pragma link C++ class ROOT::Experimental::TGtk4GuiFactory+;
+#pragma link C++ class ROOT::Experimental::TGtk4GedEditor+;
+
+#endif
diff --git a/gui/gtk4canvas/inc/TGtk4Canvas.h b/gui/gtk4canvas/inc/TGtk4Canvas.h
new file mode 100644
index 0000000000000..319d3869b02f1
--- /dev/null
+++ b/gui/gtk4canvas/inc/TGtk4Canvas.h
@@ -0,0 +1,84 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#ifndef ROOT_TGtk4Canvas
+#define ROOT_TGtk4Canvas
+
+#include "TCanvasImp.h"
+
+class Gtk4CanvasWindow;
+class Gtk4DrawArea;
+
+namespace ROOT {
+namespace Experimental {
+
+
+class TGtk4Canvas : public TCanvasImp {
+
+protected:
+
+ Gtk4CanvasWindow *fCanvasWindow = nullptr;
+ Gtk4DrawArea *fDrawArea = nullptr;
+
+ Bool_t fFixedSize = kFALSE; ///
+
+class TVirtualPad;
+class TAttMarker;
+class TAttText;
+class TAttLine;
+class TAttFill;
+class TClass;
+
+class Gtk4MethodDialog;
+
+namespace Gtk {
+ class DropDown;
+ class SpinButton;
+ class ColorDialogButton;
+}
+
+
+namespace ROOT {
+namespace Experimental {
+
+class TGtk4GedEditor : public TVirtualPadEditor, public TObject, public TQObject {
+
+ protected:
+
+ TCanvas *fCanvas = nullptr;
+ TVirtualPad *fPad = nullptr;
+ TObject *fModel = nullptr;
+ Bool_t fGlobal = kTRUE;
+
+ Gtk4MethodDialog *fDialog = nullptr;
+
+ TAttLine *fAttLine = nullptr;
+ TAttFill *fAttFill = nullptr;
+ TAttText *fAttText = nullptr;
+ TAttMarker *fAttMarker = nullptr;
+
+ Gtk::ColorDialogButton* m_line_color = nullptr;
+ Gtk::DropDown *m_line_style = nullptr;
+ Gtk::SpinButton *m_line_width = nullptr;
+
+ Gtk::ColorDialogButton* m_fill_color = nullptr;
+ Gtk::ColorDialogButton* m_text_color = nullptr;
+ Gtk::ColorDialogButton* m_marker_color = nullptr;
+
+ Int_t GetRootColor(Gtk::ColorDialogButton* btn);
+
+ void FillDialogsElements();
+
+ void ModifiedPad();
+
+ void FillGed(TClass *cl);
+
+ void on_ok_button_clicked();
+
+ public:
+
+ TGtk4GedEditor(TCanvas *c = nullptr);
+ virtual ~TGtk4GedEditor();
+
+ Bool_t IsGlobal() const override { return fGlobal; }
+ void SetGlobal(Bool_t on) override { fGlobal = on; }
+
+ TCanvas* GetCanvas() const override { return fCanvas; }
+
+ void ConnectToCanvas(TCanvas *c);
+ void DisconnectFromCanvas();
+ void SetCanvas(TCanvas *c);
+
+ void Show() override;
+ void Hide() override;
+
+ void RecursiveRemove(TObject* obj) override;
+
+ virtual void SetModel(TVirtualPad* pad, TObject* obj, Int_t event);
+
+ // specific editors
+ void AddTAttLine(TAttLine *);
+ void AddTAttFill(TAttFill *);
+ void AddTAttText(TAttText *);
+ void AddTAttMarker(TAttMarker *);
+
+ ClassDefOverride(TGtk4GedEditor, 0) // Implementation for Ged Editor with GTK4
+};
+
+} // namespace Experimental
+} // namespace ROOT
+
+#endif
diff --git a/gui/gtk4canvas/inc/TGtk4GuiFactory.h b/gui/gtk4canvas/inc/TGtk4GuiFactory.h
new file mode 100644
index 0000000000000..af9abb4f0b917
--- /dev/null
+++ b/gui/gtk4canvas/inc/TGtk4GuiFactory.h
@@ -0,0 +1,41 @@
+// Author: Sergey Linev 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#ifndef ROOT_TGtk4GuiFactory
+#define ROOT_TGtk4GuiFactory
+
+#include "TGuiFactory.h"
+
+namespace ROOT {
+namespace Experimental {
+
+class TGtk4GuiFactory : public TGuiFactory {
+
+public:
+ TGtk4GuiFactory(const char *name = "gtk4", const char *title = "ROOT Gtk4 GUI Factory");
+ ~TGtk4GuiFactory() override {}
+
+ Bool_t UseVirtualX() const override { return kFALSE; }
+
+ TApplicationImp *CreateApplicationImp(const char *classname, int *argc, char **argv) override;
+
+ TCanvasImp *CreateCanvasImp(TCanvas *c, const char *title, UInt_t width, UInt_t height) override;
+ TCanvasImp *CreateCanvasImp(TCanvas *c, const char *title, Int_t x, Int_t y, UInt_t width, UInt_t height) override;
+
+ TContextMenuImp *CreateContextMenuImp(TContextMenu *c, const char *name, const char *title) override;
+
+ ClassDefOverride(TGtk4GuiFactory,0) //Factory for Gtk4 GUI components
+};
+
+} // namespace Experimental
+} // namespace ROOT
+
+
+#endif
diff --git a/gui/gtk4canvas/inc/TGtk4PadPainter.h b/gui/gtk4canvas/inc/TGtk4PadPainter.h
new file mode 100644
index 0000000000000..65fbc98dc3deb
--- /dev/null
+++ b/gui/gtk4canvas/inc/TGtk4PadPainter.h
@@ -0,0 +1,104 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#ifndef ROOT_TGtk4PadPainter
+#define ROOT_TGtk4PadPainter
+
+#include "TPadPainterBase.h"
+
+
+class Gtk4DrawArea;
+class TTFhandle;
+
+namespace ROOT {
+namespace Experimental {
+
+class TGtk4Canvas;
+
+class TGtk4PadPainter : public TPadPainterBase {
+
+friend class TGtk4Canvas;
+
+protected:
+
+ Gtk4DrawArea *fDrawArea = nullptr;
+
+ int fCustomPattern = 0;
+
+ void SetGtk4Color(Color_t id);
+ Bool_t SetLinePen();
+ Bool_t SetFillBrush();
+ void ApplyFillBrush();
+
+public:
+
+ TGtk4PadPainter(Gtk4DrawArea *widget = nullptr) { fDrawArea = widget; }
+
+ Bool_t HasTTFonts() const override { return kTRUE; }
+
+ Bool_t IsNative() const override { return kTRUE; }
+
+ void SetOpacity(Int_t percent) override;
+
+ //2. "Off-screen management" part.
+ // return non-zero value to let execute painting code
+ Int_t CreateDrawable(UInt_t, UInt_t) override { return 223344; }
+ void ClearDrawable() override {}
+ void CopyDrawable(Int_t, Int_t, Int_t) override {}
+ void DestroyDrawable(Int_t) override {}
+ void SelectDrawable(Int_t) override {}
+ void SetDoubleBuffer(Int_t /* device */, Int_t /* mode */) override {}
+
+ //jpg, png, bmp, gif output.
+ void SaveImage(TVirtualPad *, const char *, Int_t) const override;
+
+ //TASImage support (noop for a non-gl pad).
+ void DrawPixels(const unsigned char *pixelData, UInt_t width, UInt_t height,
+ Int_t dstX, Int_t dstY, Bool_t enableAlphaBlending) override;
+
+ void DrawLine(Double_t x1, Double_t y1, Double_t x2, Double_t y2) override;
+ void DrawLineNDC(Double_t u1, Double_t v1, Double_t u2, Double_t v2) override;
+
+ void DrawBox(Double_t x1, Double_t y1, Double_t x2, Double_t y2, EBoxMode mode) override;
+ //TPad needs double and float versions.
+ void DrawFillArea(Int_t n, const Double_t *x, const Double_t *y) override;
+ void DrawFillArea(Int_t n, const Float_t *x, const Float_t *y) override;
+
+ //TPad needs both double and float versions of DrawPolyLine.
+ void DrawPolyLine(Int_t n, const Double_t *x, const Double_t *y) override;
+ void DrawPolyLine(Int_t n, const Float_t *x, const Float_t *y) override;
+ void DrawPolyLineNDC(Int_t n, const Double_t *u, const Double_t *v) override;
+
+ //TPad needs both versions.
+ void DrawPolyMarker(Int_t n, const Double_t *x, const Double_t *y) override;
+ void DrawPolyMarker(Int_t n, const Float_t *x, const Float_t *y) override;
+
+ void DrawTTFglyphs(Int_t x, Int_t y, TTFhandle &ttf, ETextMode mode) override;
+
+ void DrawImage(TImage *img, Int_t x, Int_t y, Int_t flags = 0) override;
+
+ Bool_t FillImageFromPad(TImage *image, TVirtualPad *pad) const override;
+
+ Bool_t IsSupportAlpha() const override { return kTRUE; }
+
+private:
+ //Let's make this clear:
+ TGtk4PadPainter(const TGtk4PadPainter &rhs) = delete;
+ TGtk4PadPainter(TGtk4PadPainter && rhs) = delete;
+ TGtk4PadPainter & operator = (const TGtk4PadPainter &rhs) = delete;
+ TGtk4PadPainter & operator = (TGtk4PadPainter && rhs) = delete;
+
+ ClassDefOverride(TGtk4PadPainter, 0) // Pad painter on Gtk4 canvas
+};
+
+} // namespace Experimental
+} // namespace ROOT
+
+#endif
diff --git a/gui/gtk4canvas/index.md b/gui/gtk4canvas/index.md
new file mode 100644
index 0000000000000..1e6ce2479d313
--- /dev/null
+++ b/gui/gtk4canvas/index.md
@@ -0,0 +1,28 @@
+\defgroup gtk4canvas GTK4 Canvas Display
+\ingroup gpad
+\brief Classes for canvas display using GTK4
+
+CAUTION! Highly experimental, constantly changing code
+
+Provides alternative graphics implementation in the ROOT.
+Allows non-GL painting in GTK4. Implementes:
+ - painting via TVirtualPadPainter interface
+ - canvas menu
+ - status bar
+ - objects context menu
+ - basics graphical editors
+
+To use this class one need to compile ROOT with `-Dgtk4canv=ON` flag.
+GTK4 library should be installed on the system.
+To activate GTK4 canvas in ROOT application one should create .rootrc file with following entries:
+
+```
+Gui.Factory: gtk4
+Root.PadEditor: gtk4
+```
+
+
+For compilation on windows one can use `vcpkg` to download all necessary gtk4 libraries.
+When calling cmake, one can provide following arguments:
+
+ -DCMAKE_TOOLCHAIN_FILE="C:\git\vcpkg\scripts\buildsystems\vcpkg.cmake" -DVCPKG_TARGET_TRIPLET=x64-windows
\ No newline at end of file
diff --git a/gui/gtk4canvas/src/Gtk4CanvasWindow.cxx b/gui/gtk4canvas/src/Gtk4CanvasWindow.cxx
new file mode 100644
index 0000000000000..c41dc7afaa4a2
--- /dev/null
+++ b/gui/gtk4canvas/src/Gtk4CanvasWindow.cxx
@@ -0,0 +1,629 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+/* gtkmm example Copyright (C) 2002 gtkmm development team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see .
+ */
+
+#include "Gtk4CanvasWindow.h"
+
+#include
+
+#include
+#include
+
+#include "TROOT.h"
+#include "TTimer.h"
+#include "TFile.h"
+#include "TCanvas.h"
+#include "TError.h"
+#include "TApplication.h"
+
+
+enum { kFileNewCanvas, kFileOpen, kFileSaveAs, kFilePrint, kOptionInterrupt, kOptionRefresh,
+ kInspectRoot, kToolsBrowser, kToolModify, kToolArc, kToolLine, kToolArrow, kToolDiamond,
+ kToolEllipse, kToolPad, kToolPave, kToolPLabel, kToolPText, kToolPsText, kToolGraph,
+ kToolCurlyLine, kToolCurlyArc, kToolLatex, kToolMarker, kToolCutG };
+
+struct ToolBarData_t {
+ const char *fPixmap;
+ const char *fTipText;
+ Bool_t fStayDown;
+ Int_t fId;
+ void *fButton;
+};
+
+static ToolBarData_t toolBarData[] = {
+ // { filename, tooltip, staydown, id, button}
+ { "newcanvas.xpm", "New", kFALSE, kFileNewCanvas, 0 },
+ { "open.xpm", "Open", kFALSE, kFileOpen, 0 },
+ { "save.xpm", "Save As", kFALSE, kFileSaveAs, 0 },
+ { "printer.xpm", "Print", kFALSE, kFilePrint, 0 },
+ { "", "", kFALSE, -1, 0 },
+ { "interrupt.xpm", "Interrupt", kFALSE, kOptionInterrupt,0 },
+ { "refresh2.xpm", "Refresh", kFALSE, kOptionRefresh, 0 },
+ { "", "", kFALSE, -1, 0 },
+ { "inspect.xpm", "Inspect", kFALSE, kInspectRoot, 0 },
+// { "browser.xpm", "Browser", kFALSE, kToolsBrowser, 0 },
+ { "", "", kFALSE, -1, 0 },
+ { "pointer.xpm", "Modify", kFALSE, kToolModify, 0 },
+ { "arc.xpm", "Arc", kFALSE, kToolArc, 0 },
+ { "line.xpm", "Line", kFALSE, kToolLine, 0 },
+ { "arrow.xpm", "Arrow", kFALSE, kToolArrow, 0 },
+ { "diamond.xpm", "Diamond", kFALSE, kToolDiamond, 0 },
+ { "ellipse.xpm", "Ellipse", kFALSE, kToolEllipse, 0 },
+ { "pad.xpm", "Pad", kFALSE, kToolPad, 0 },
+ { "pave.xpm", "Pave", kFALSE, kToolPave, 0 },
+ { "pavelabel.xpm", "Pave Label", kFALSE, kToolPLabel, 0 },
+ { "pavetext.xpm", "Pave Text", kFALSE, kToolPText, 0 },
+ { "pavestext.xpm", "Paves Text", kFALSE, kToolPsText, 0 },
+ { "graph.xpm", "Graph", kFALSE, kToolGraph, 0 },
+ { "curlyline.xpm", "Curly Line", kFALSE, kToolCurlyLine, 0 },
+ { "curlyarc.xpm", "Curly Arc", kFALSE, kToolCurlyArc, 0 },
+ { "latex.xpm", "Text/Latex", kFALSE, kToolLatex, 0 },
+ { "marker.xpm", "Marker", kFALSE, kToolMarker, 0 },
+ { "cut.xpm", "Graphical Cut", kFALSE, kToolCutG, 0 },
+ { 0, 0, kFALSE, 0, 0 }
+};
+
+
+
+/** \class Gtk4CanvasWindow
+ \ingroup gtk4canvas
+*/
+
+
+Gtk4CanvasWindow::Gtk4CanvasWindow(TCanvas *canv, unsigned width, unsigned height)
+: m_Box(Gtk::Orientation::VERTICAL),
+ m_ToolBar(Gtk::Orientation::HORIZONTAL, 4)
+{
+ set_title("canvas widget");
+ set_default_size(width, height);
+
+ set_child(m_Box); //We can put a MenuBar at the top of the box and other stuff below it.
+
+ // all actions used with canvas
+
+
+ fillMenuBar(canv);
+
+ fillToolBar();
+
+ m_ToolBar.set_visible(canv->TestBit(TCanvas::kShowToolBar));
+ m_Box.append(m_ToolBar);
+
+ m_DrawArea.set_has_tooltip(canv->TestBit(TCanvas::kShowToolTips));
+ m_DrawArea.set_hexpand(true);
+ m_DrawArea.set_vexpand(true);
+ m_Box.append(m_DrawArea);
+
+ m_DrawArea.status_changed().connect(sigc::mem_fun(*this, &Gtk4CanvasWindow::on_status_changed));
+
+ fContextId = m_StatusBar.get_context_id("main_status");
+ m_StatusBar.push("Ready to draw", fContextId);
+ m_StatusBar.set_visible(canv->TestBit(TCanvas::kShowEventStatus));
+ m_Box.append(m_StatusBar);
+}
+
+Gtk4CanvasWindow::~Gtk4CanvasWindow()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Fill or update canvas save menu
+/// Only performed when canvas name is changed
+
+void Gtk4CanvasWindow::fillSaveMenu(TCanvas *canv)
+{
+ std::string name = canv ? canv->GetName() : "";
+ if (name.empty())
+ name = "c1";
+
+ if (fCanvasFileName == name)
+ return;
+
+ fCanvasFileName = name;
+
+ fSaveMenu->remove_all();
+
+ auto &fmts = GetSupportedFileFormats();
+ R__ASSERT(fmts.size() % 3 == 0);
+ for (std::size_t p = 0; p < fmts.size(); p += 3) {
+ std::string ext = fmts[p+1];
+ // cannot define filters with same extension - cannot distinguish which was selected
+ if ((ext != "C") && (ext != fmts[p + 2]))
+ continue;
+ fSaveMenu->append(fCanvasFileName + "." + ext, "canvas.save_" + ext);
+ }
+}
+
+void Gtk4CanvasWindow::fillMenuBar(TCanvas *canv)
+{
+ m_action_group = Gio::SimpleActionGroup::create();
+
+ m_action_group->add_action("new", []() { std::cout << "New canvas Clicked!\n"; });
+ m_action_group->add_action("open", [this]() { on_openRootFile(); });
+ m_action_group->add_action("saveas", [this]() { on_saveCanvasAs(); });
+
+ auto &fmts = GetSupportedFileFormats();
+ R__ASSERT(fmts.size() % 3 == 0);
+ for (std::size_t p = 0; p < fmts.size(); p += 3) {
+ std::string ext = fmts[p+1];
+ std::string opt = fmts[p+2];
+ // cannot define filters with same extension - cannot distinguish which was selected
+ if ((ext != "C") && (ext != opt))
+ continue;
+ m_action_group->add_action("save_" + ext, [this, ext, opt]() { on_saveCanvas(ext, opt); });
+ }
+
+ m_action_group->add_action("close", [this]() { close(); });
+ m_action_group->add_action("quit", []() {
+ gROOT->SetInterrupt(kTRUE);
+
+ if (gApplication)
+ TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()");
+ });
+
+ bool isShowToolbar = canv->TestBit(TCanvas::kShowToolBar);
+
+ bool isShowEventStatus = canv->TestBit(TCanvas::kShowEventStatus);
+
+ bool isShowToolTips = canv->TestBit(TCanvas::kShowToolTips);
+
+ bool isShowMenuBar = canv->TestBit(TCanvas::kMenuBar);
+
+ auto toggle_toolbar = Gio::SimpleAction::create_bool("show-toolbar", isShowToolbar);
+ toggle_toolbar->signal_change_state().connect(sigc::mem_fun(*this, &Gtk4CanvasWindow::on_show_toolbar_changed));
+ m_action_group->add_action(toggle_toolbar);
+
+ auto toggle_statusbar = Gio::SimpleAction::create_bool("show-eventstatus", isShowEventStatus);
+ toggle_statusbar->signal_change_state().connect(sigc::mem_fun(*this, &Gtk4CanvasWindow::on_show_eventstatus_changed));
+ m_action_group->add_action(toggle_statusbar);
+
+ auto toggle_tooltip = Gio::SimpleAction::create_bool("show-tooltip", isShowToolTips);
+ toggle_tooltip->signal_change_state().connect(sigc::mem_fun(*this, &Gtk4CanvasWindow::on_show_tooltip_changed));
+ m_action_group->add_action(toggle_tooltip);
+
+
+ // Bind actions to an identifier prefix name string ("win.new", "win.copy", etc.)
+ insert_action_group("canvas", m_action_group);
+
+ // 3. Construct the Abstract Menu Tree Model
+ auto menu_model = Gio::Menu::create();
+
+
+ // --- SECTION "File"
+
+ auto sec1 = Gio::Menu::create();
+ sec1->append("New Canvas", "canvas.new");
+ sec1->append("Open", "canvas.open");
+ sec1->append("Close Canvas", "canvas.close");
+
+ auto sec2 = Gio::Menu::create();
+
+ fSaveMenu = Gio::Menu::create();
+ fillSaveMenu(canv);
+ sec2->append_submenu("Save", fSaveMenu);
+ sec2->append("Save as...", "canvas.saveas");
+
+ auto sec3 = Gio::Menu::create();
+ sec3->append("Quit ROOT", "canvas.quit");
+
+ auto file_menu = Gio::Menu::create();
+ file_menu->append_section(sec1);
+ file_menu->append_section(sec2);
+ file_menu->append_section(sec3);
+
+ // Attach File structure onto root Menu Model Bar
+ menu_model->append_submenu("File", file_menu);
+
+
+ // --- SECTION "Edit"
+ auto edit_menu = Gio::Menu::create();
+ edit_menu->append("Clear pad", "canvas.clearpad");
+ edit_menu->append("Clear canvas", "canvas.clearcanvas");
+
+ // CREATE A SUB-MENU ITEM (Nested Layer)
+ auto special_paste_submenu = Gio::Menu::create();
+ special_paste_submenu->append("Paste Text Only", "canvas.paste");
+ special_paste_submenu->append("Paste with Formatting", "canvas.paste");
+
+ // Nest the submenu inside the parent Edit menu container
+ edit_menu->append_submenu("Paste Special...", special_paste_submenu);
+
+ // Attach Edit structure onto root Menu Model Bar
+ menu_model->append_submenu("Edit", edit_menu);
+
+
+ // --- SECTION "View"
+ auto view_menu = Gio::Menu::create();
+ view_menu->append("Editor", "canvas.show-editor");
+ view_menu->append("Toolbar", "canvas.show-toolbar");
+ view_menu->append("Event Status", "canvas.show-eventstatus");
+ view_menu->append("Tooltip", "canvas.show-tooltip");
+
+ menu_model->append_submenu("View", view_menu);
+
+ // 4. Instantiate the PopoverMenuBar from our structured menu tree
+ // Gtk::PopoverMenuBar requires a Glib::RefPtr
+ auto menu_bar = Gtk::make_managed(menu_model);
+
+ menu_bar->set_visible(isShowMenuBar);
+
+ menu_bar->signal_state_flags_changed().connect([this](Gtk::StateFlags) {
+ fillSaveMenu(m_DrawArea.GetCanvas());
+ });
+
+ m_Box.append(*menu_bar);
+}
+
+
+Gtk::Image* create_resized_xpm_image(const char *filename)
+{
+ // 2. Initialize a pointer for our final texture image
+ Glib::RefPtr texture;
+
+ try {
+ // Load the original XPM file safely into a Pixbuf
+ auto original_pixbuf = Gdk::Pixbuf::create_from_file(filename);
+
+ // Force-resize the XPM image to precisely 16x16 using Bilinear interpolation
+ auto scaled_pixbuf = original_pixbuf->scale_simple(16, 16, Gdk::InterpType::BILINEAR);
+
+ // Convert the scaled Pixbuf into a GTK4 Paintable Texture
+ texture = Gdk::Texture::create_for_pixbuf(scaled_pixbuf);
+
+ } catch (const Glib::FileError& e) {
+ std::cerr << "File system error: " << e.what() << std::endl;
+ return nullptr; // Returns an empty image
+ } catch (const Gdk::PixbufError& e) {
+ std::cerr << "Failed to parse XPM image format: " << e.what() << std::endl;
+ return nullptr; // Returns an empty image
+ }
+
+ // 3. Create the GTK4 Image widget directly from the validated texture
+ auto image = Gtk::make_managed(texture);
+
+ return image;
+}
+
+
+void Gtk4CanvasWindow::fillToolBar()
+{
+
+ auto css_provider = Gtk::CssProvider::create();
+
+ // Target the button widget structure to minimize interior padding spaces
+ css_provider->load_from_data("button { padding: 2px 2px; min-width: 0; min-height: 0; }");
+
+ for (int i = 0; toolBarData[i].fPixmap; i++) {
+ auto btn = &(toolBarData[i]);
+ if (strlen(btn->fPixmap) == 0) {
+ // fToolBar->addSeparator();
+ auto separator = Gtk::make_managed(Gtk::Orientation::VERTICAL);
+ m_ToolBar.append(*separator);
+ continue;
+ }
+
+ auto button = Gtk::make_managed();
+
+ TString iconname = TROOT::GetIconPath();
+ iconname.Append("/");
+ iconname.Append(btn->fPixmap);
+
+ // 2. Load the icon from the file path
+ auto image = create_resized_xpm_image(iconname.Data());
+
+ if (image)
+ button->set_child(*image);
+
+ button->get_style_context()->add_provider(css_provider, GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+
+ button->set_tooltip_text(btn->fTipText);
+
+ button->signal_clicked().connect([this, btn]() {
+ clickToolButton(btn->fId);
+ });
+
+
+ m_ToolBar.append(*button);
+ }
+}
+
+void Gtk4CanvasWindow::clickToolButton(int id)
+{
+ auto canv = m_DrawArea.GetCanvas();
+ switch (id) {
+ case kFileNewCanvas:
+ gROOT->MakeDefCanvas();
+ break;
+ case kFileOpen:
+ on_openRootFile();
+ break;
+ case kFileSaveAs:
+ on_saveCanvasAs();
+ break;
+ case kFilePrint:
+ on_printCanvas();
+ break;
+ case kOptionInterrupt: gROOT->SetInterrupt(); break;
+ case kOptionRefresh:
+ if (canv) {
+ canv->Modified(kTRUE);
+ // trigger update of the paint widget, paint will be called
+ canv->Update();
+ }
+ break;
+ case kInspectRoot:
+ if (canv) {
+ canv->cd();
+ gROOT->Inspect();
+ canv->Update();
+ }
+ break;
+
+ case kToolsBrowser: break;
+ case kToolModify: gROOT->SetEditorMode(); break;
+ case kToolArc: gROOT->SetEditorMode("Arc"); break;
+ case kToolLine: gROOT->SetEditorMode("Line"); break;
+ case kToolArrow: gROOT->SetEditorMode("Arrow"); break;
+ case kToolDiamond: gROOT->SetEditorMode("Diamond"); break;
+ case kToolEllipse: gROOT->SetEditorMode("Ellipse"); break;
+ case kToolPad: gROOT->SetEditorMode("Pad"); break;
+ case kToolPave: gROOT->SetEditorMode("Pave"); break;
+ case kToolPLabel: gROOT->SetEditorMode("PaveLabel"); break;
+ case kToolPText: gROOT->SetEditorMode("PaveText"); break;
+ case kToolPsText: gROOT->SetEditorMode("PavesText"); break;
+ case kToolGraph: gROOT->SetEditorMode("PolyLine"); break;
+ case kToolCurlyLine: gROOT->SetEditorMode("CurlyLine"); break;
+ case kToolCurlyArc: gROOT->SetEditorMode("CurlyArc"); break;
+ case kToolLatex: gROOT->SetEditorMode("Text"); break;
+ case kToolMarker: gROOT->SetEditorMode("Marker"); break;
+ case kToolCutG: gROOT->SetEditorMode("CutG"); break;
+ }
+}
+
+void Gtk4CanvasWindow::on_status_changed(const char *msg)
+{
+ m_StatusBar.remove_all_messages(fContextId);
+ m_StatusBar.push(msg, fContextId);
+}
+
+void Gtk4CanvasWindow::on_show_toolbar_changed(const Glib::VariantBase& value)
+{
+ bool is_checked = Glib::VariantBase::cast_dynamic>(value).get();
+
+ m_ToolBar.set_visible(is_checked);
+
+ // Remember to update the actual action state so the checkmark reflects visually
+ auto action = m_action_group->lookup_action("show-toolbar");
+ if (action) {
+ auto simple_action = std::dynamic_pointer_cast(action);
+ simple_action->set_state(value);
+ }
+}
+
+void Gtk4CanvasWindow::on_show_eventstatus_changed(const Glib::VariantBase &value)
+{
+ bool is_checked = Glib::VariantBase::cast_dynamic>(value).get();
+
+ m_StatusBar.set_visible(is_checked);
+
+ // Remember to update the actual action state so the checkmark reflects visually
+ auto action = m_action_group->lookup_action("show-eventstatus");
+ if (action) {
+ auto simple_action = std::dynamic_pointer_cast(action);
+ simple_action->set_state(value);
+ }
+}
+
+void Gtk4CanvasWindow::on_show_tooltip_changed(const Glib::VariantBase &value)
+{
+ bool is_checked = Glib::VariantBase::cast_dynamic>(value).get();
+
+ m_DrawArea.set_has_tooltip(is_checked);
+
+ // Remember to update the actual action state so the checkmark reflects visually
+ auto action = m_action_group->lookup_action("show-tooltip");
+ if (action) {
+ auto simple_action = std::dynamic_pointer_cast(action);
+ simple_action->set_state(value);
+ }
+
+}
+
+void Gtk4CanvasWindow::on_openRootFile()
+{
+ if (!m_file_dialog) {
+ m_file_dialog = Gtk::FileDialog::create();
+ m_file_dialog->set_title("Select a ROOT File");
+ m_file_dialog->set_modal(true);
+
+ auto filter_root_files = Gtk::FileFilter::create();
+ filter_root_files->set_name("ROOT files (*.root)");
+ filter_root_files->add_pattern("*.root");
+
+ auto filter_xml_files = Gtk::FileFilter::create();
+ filter_xml_files->set_name("ROOT XML files (*.xml)");
+ filter_xml_files->add_pattern("*.xml");
+
+ auto filter_all_files = Gtk::FileFilter::create();
+ filter_all_files->set_name("All files (*.*)");
+ filter_all_files->add_pattern("*.*");
+
+ auto filters_list = Gio::ListStore::create();
+ filters_list->append(filter_root_files);
+ filters_list->append(filter_xml_files);
+ filters_list->append(filter_all_files);
+
+ m_file_dialog->set_filters(filters_list);
+
+ // Optional: Set which filter is selected by default (e.g., Images)
+ m_file_dialog->set_default_filter(filter_root_files);
+ }
+
+ m_file_dialog->open(*this, sigc::mem_fun(*this, &Gtk4CanvasWindow::on_file_selected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Signal handler when open file dialog select file
+
+void Gtk4CanvasWindow::on_file_selected(const Glib::RefPtr &result)
+{
+ try {
+ // Retrieve the selected file reference
+ Glib::RefPtr file = m_file_dialog->open_finish(result);
+
+ TFile::Open(file->get_path().c_str(), "update");
+
+ } catch (const Gtk::DialogError &err) {
+ // Triggers if the user cancels or closes the dialog window
+ // std::cout << "Dialog closed or dismissed: " << err.what() << std::endl;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Return vector with supported file formats with description and print option
+/// To be moved in base class
+
+const std::vector &Gtk4CanvasWindow::GetSupportedFileFormats() const
+{
+ static std::vector lst = {
+ "Post Script", "ps", "ps",
+ "Post Script Portrait", "ps", "Portrait",
+ "Post Script Landscape", "ps", "Landscape",
+ "Encapsulated Post Script", "eps", "eps",
+ "Encapsulated Post Script preview", "eps", "Preview",
+ "GIF format", "gif", "gif",
+ "PDF format", "pdf", "pdf",
+ "SVG format", "svg", "svg",
+ "XPM format", "xpm", "xpm",
+ "PNG format", "png", "png",
+ "JPG format", "jpg", "jpg",
+ "BMP format", "bmp", "bmp",
+ "TIFF format", "tiff", "tiff",
+ "C++ Macro", "C", "cxx",
+ "json file", "json", "json",
+ "html file", "html", "html",
+ "root file", "root", "root"
+ };
+ return lst;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+/// Save canvas with specified format
+
+void Gtk4CanvasWindow::on_saveCanvas(const std::string &fmt, const std::string &opt)
+{
+ auto canv = m_DrawArea.GetCanvas();
+ if (!canv)
+ return;
+ std::string fname = fCanvasFileName + "." + fmt;
+ canv->Print(fname.c_str(), opt.c_str());
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+/// Start Save as dialog
+
+void Gtk4CanvasWindow::on_saveCanvasAs()
+{
+ if (!m_save_dialog) {
+ m_save_dialog = Gtk::FileDialog::create();
+ m_save_dialog->set_title("Save canvas as");
+ m_save_dialog->set_modal(true);
+
+ auto filters_list = Gio::ListStore::create();
+
+ auto &fmts = GetSupportedFileFormats();
+ R__ASSERT(fmts.size() % 3 == 0);
+ for (std::size_t p = 0; p < fmts.size(); p += 3) {
+ std::string ext = fmts[p + 1];
+ // cannot define filters with same extension - cannot distinguish which was selected
+ if ((ext != "C") && (ext != fmts[p + 2]))
+ continue;
+ auto name = TString::Format("%s (*.%s)", fmts[p].c_str(), ext.c_str());
+ auto pattern = TString::Format("(*.%s)", ext.c_str());
+
+ auto filter = Gtk::FileFilter::create();
+ filter->set_name(name.Data());
+ filter->add_pattern(pattern.Data());
+ filters_list->append(filter);
+
+ if (fmts[p+1] == "png")
+ m_save_dialog->set_default_filter(filter);
+ }
+
+ m_save_dialog->set_filters(filters_list);
+ }
+
+ auto canv = m_DrawArea.GetCanvas();
+ if (canv) {
+ std::string name = canv->GetName();
+ m_save_dialog->set_initial_name(name + ".png");
+ }
+
+ m_save_dialog->save(*this, sigc::mem_fun(*this, &Gtk4CanvasWindow::on_save_selected));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Signal handler when save as dialog select file
+
+void Gtk4CanvasWindow::on_save_selected(const Glib::RefPtr &result)
+{
+ auto canv = m_DrawArea.GetCanvas();
+ if (!canv)
+ return;
+
+ try {
+ Glib::RefPtr file = m_save_dialog->save_finish(result);
+
+ std::string raw_path = file->get_path();
+ std::filesystem::path sys_path(raw_path);
+ std::string extension = sys_path.extension().string(); //
+ std::string print_opt;
+
+ auto &fmts = GetSupportedFileFormats();
+ R__ASSERT(fmts.size() % 3 == 0);
+ for (std::size_t p = 0; p < fmts.size(); p += 3) {
+ if (extension == "." + fmts[p + 1]) {
+ print_opt = fmts[p + 2];
+ break;
+ }
+ }
+
+ if (print_opt.empty())
+ ::Error("Gtk4CanvasWindow::on_save_selected", "Cannot detect file format from selected extension %s", extension.c_str());
+ else {
+ printf("Store canvas %s as %s opt %s\n", canv->ClassName(), raw_path.c_str(), print_opt.c_str());
+ canv->Print(raw_path.c_str(), print_opt.c_str());
+ }
+
+ } catch (const Gtk::DialogError &err) {
+ }
+}
+
+
+void Gtk4CanvasWindow::on_printCanvas()
+{
+}
diff --git a/gui/gtk4canvas/src/Gtk4CanvasWindow.h b/gui/gtk4canvas/src/Gtk4CanvasWindow.h
new file mode 100644
index 0000000000000..cba611be94f44
--- /dev/null
+++ b/gui/gtk4canvas/src/Gtk4CanvasWindow.h
@@ -0,0 +1,89 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+/* gtkmm example Copyright (C) 2002 gtkmm development team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see .
+ */
+
+#ifndef ROOT_Gtk4CanvasWindow_H
+#define ROOT_Gtk4CanvasWindow_H
+
+#include
+
+#include "Gtk4DrawArea.h"
+#include
+#include
+
+class TCanvas;
+
+class Gtk4CanvasWindow : public Gtk::Window {
+public:
+ Gtk4CanvasWindow(TCanvas *canv = nullptr, unsigned width = 400, unsigned height = 200);
+ virtual ~Gtk4CanvasWindow();
+
+ Gtk4DrawArea *GetDrawArea() { return &m_DrawArea; }
+
+private:
+ const std::vector &GetSupportedFileFormats() const;
+
+ // Signal handlers:
+
+ void on_status_changed(const char *msg);
+ void on_show_toolbar_changed(const Glib::VariantBase &);
+ void on_show_eventstatus_changed(const Glib::VariantBase &);
+ void on_show_tooltip_changed(const Glib::VariantBase &);
+ void on_file_selected(const Glib::RefPtr &result);
+ void on_save_selected(const Glib::RefPtr &result);
+ void on_openRootFile();
+ void on_saveCanvas(const std::string &fname, const std::string &opt);
+ void on_saveCanvasAs();
+ void on_printCanvas();
+
+ // void on_action_file_new();
+ // void on_action_file_quit();
+ // void on_action_others();
+ // void on_action_toggle();
+
+ void fillSaveMenu(TCanvas *canv);
+ void fillMenuBar(TCanvas *canv);
+
+ void fillToolBar();
+
+ void clickToolButton(int id);
+
+ // Child widgets:
+ Gtk::Box m_Box;
+
+ Glib::RefPtr m_action_group;
+ std::string fCanvasFileName;
+ Glib::RefPtr fSaveMenu;
+
+ Glib::RefPtr m_file_dialog;
+ Glib::RefPtr m_save_dialog;
+
+ Gtk::Box m_ToolBar;
+ Gtk4DrawArea m_DrawArea;
+ Gtk::Statusbar m_StatusBar;
+ unsigned int fContextId = 0;
+};
+
+#endif //GTKMM_Gtk4CanvasWindow_H
diff --git a/gui/gtk4canvas/src/Gtk4DrawArea.cxx b/gui/gtk4canvas/src/Gtk4DrawArea.cxx
new file mode 100644
index 0000000000000..f6c680037e230
--- /dev/null
+++ b/gui/gtk4canvas/src/Gtk4DrawArea.cxx
@@ -0,0 +1,314 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#include "Gtk4DrawArea.h"
+
+#include
+
+#include "TGtk4PadPainter.h"
+#include "TCanvas.h"
+
+#include
+
+/** \class Gtk4DrawArea
+ \ingroup gtk4canvas
+*/
+
+
+Gtk4DrawArea::Gtk4DrawArea()
+{
+ // Bind the member function as the draw function
+ set_draw_func(sigc::mem_fun(*this, &Gtk4DrawArea::on_draw));
+
+ set_expand(true);
+
+ m_click_gesture = Gtk::GestureClick::create();
+ m_click_gesture->set_button(0);
+ m_click_gesture->signal_pressed().connect(
+ sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_click)
+ );
+ m_click_gesture->signal_released().connect(
+ sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_released)
+ );
+ add_controller(m_click_gesture);
+
+ // 2. Create and attach the motion event controller
+ m_motion_controller = Gtk::EventControllerMotion::create();
+ m_motion_controller->signal_motion().connect(
+ sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_move)
+ );
+
+ m_motion_controller->signal_enter().connect(
+ sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_enter)
+ );
+
+ // 3. Connect the leave signal
+ m_motion_controller->signal_leave().connect(
+ sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_leave)
+ );
+ add_controller(m_motion_controller);
+
+ m_scroll_controller = Gtk::EventControllerScroll::create();
+ m_scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
+
+ m_scroll_controller->signal_scroll().connect(
+ sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_scroll),
+ false // Run during the capture phase? False means bubble phase (standard)
+ );
+
+ add_controller(m_scroll_controller);
+
+
+ signal_query_tooltip().connect(sigc::mem_fun(*this, &Gtk4DrawArea::on_my_query_tooltip), true);
+}
+
+void Gtk4DrawArea::on_draw(const Cairo::RefPtr& cr, int width, int height)
+{
+ if (!fCanvas) {
+ // only for debug purposes to see not empty widget
+ cr->set_source_rgb(0.15, 0.15, 0.15);
+ cr->paint();
+
+ // 2. Draw a blue circle in the center that scales with the window
+ double radius = std::min(width, height) / 4.0;
+ cr->set_source_rgb(0.2, 0.5, 0.8);
+ cr->arc(width / 2.0, height / 2.0, radius, 0.0, 2.0 * 3.1415927);
+ cr->fill();
+ return;
+ }
+
+ if ((width != (int) fCanvas->GetPadWidth()) || (height != (int) fCanvas->GetPadHeight()))
+ fCanvas->Resize();
+
+ if (!fDrawingCache || fDrawingCache->get_width() != width || fDrawingCache->get_height() != height)
+ fDrawingCache = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, width, height);
+
+ // TODO: extend caching for each pad, making it very similar to double-buffer model of gVirtualX
+
+ auto cache_context = Cairo::Context::create(fDrawingCache);
+
+ cache_context->set_source_rgb(1., 1., 1.);
+ cache_context->paint();
+
+ // main place where painting happens - only here fContext pointer is set
+ // TODO: really repaint pad only when something changed
+
+ fContext = cache_context.get();
+
+ fCanvas->Paint();
+
+ fContext = nullptr;
+
+ // and now copy image to real context
+ cr->set_source(fDrawingCache, 0, 0);
+ cr->paint();
+}
+
+void Gtk4DrawArea::on_mouse_click(int n_press, double x, double y)
+{
+ if (!fCanvas)
+ return;
+
+ auto evnt = kNoEvent;
+
+ switch(m_click_gesture->get_current_button()) {
+ case GDK_BUTTON_PRIMARY: // 1, usually left
+ evnt = n_press > 1 ? kButton1Double : kButton1Down;
+ break;
+ case GDK_BUTTON_MIDDLE: // 2
+ evnt = n_press > 1 ? kButton2Double : kButton2Down;
+ break;
+ case GDK_BUTTON_SECONDARY: // 3, usually right
+ evnt = n_press > 1 ? kButton3Double : kButton3Down;
+ break;
+ default:
+ break;
+ }
+
+ if (evnt != kNoEvent)
+ fCanvas->HandleInput(evnt, (int) x, (int) y);
+}
+
+void Gtk4DrawArea::on_mouse_released(int n_press, double x, double y)
+{
+ (void) n_press;
+
+ if (!fCanvas)
+ return;
+
+ auto evnt = kNoEvent;
+
+ switch(m_click_gesture->get_current_button()) {
+ case GDK_BUTTON_PRIMARY: // 1, usually left
+ evnt = kButton1Up;
+ break;
+ case GDK_BUTTON_MIDDLE: // 2
+ evnt = kButton2Up;
+ break;
+ case GDK_BUTTON_SECONDARY: // 3, usually right
+ evnt = kButton3Up;
+ break;
+ default:
+ break;
+ }
+
+ if (evnt != kNoEvent)
+ fCanvas->HandleInput(evnt, (int) x, (int) y);
+}
+
+
+void Gtk4DrawArea::on_mouse_enter(double x, double y)
+{
+ fLastMouseX = x;
+ fLastMouseY = y;
+
+ if (fCanvas)
+ fCanvas->HandleInput(kMouseEnter, (int) x, (int) y);
+}
+
+void Gtk4DrawArea::on_mouse_leave()
+{
+ if (fCanvas)
+ fCanvas->HandleInput(kMouseLeave, 0, 0);
+}
+
+
+// Callback handles mouse movements
+void Gtk4DrawArea::on_mouse_move(double x, double y)
+{
+ if (!fCanvas)
+ return;
+
+ fLastMouseX = x;
+ fLastMouseY = y;
+
+ Gdk::ModifierType modifiers = m_motion_controller->get_current_event_state();
+
+ auto evnt = kMouseMotion;
+ if ((modifiers & Gdk::ModifierType::BUTTON1_MASK) == Gdk::ModifierType::BUTTON1_MASK) {
+ evnt = kButton1Motion;
+ } else if ((modifiers & Gdk::ModifierType::BUTTON3_MASK) == Gdk::ModifierType::BUTTON3_MASK) {
+ evnt = kButton3Motion;
+ }
+
+ fCanvas->HandleInput(evnt, (int) x, (int) y);
+
+ if (fShowEventStatus) {
+ TObject *selected = fCanvas->GetSelected();
+ Int_t px = fCanvas->GetEventX();
+ Int_t py = fCanvas->GetEventY();
+ TString buffer = "";
+ if (selected) {
+ buffer = selected->GetName();
+ buffer.Append(" ");
+ buffer.Append(selected->GetObjectInfo(px, py));
+ } else {
+ buffer.Form("No selected object x = %d y = %d", px, py);
+ }
+
+ m_status_changed.emit(buffer.Data());
+ }
+}
+
+sigc::signal Gtk4DrawArea::status_changed()
+{
+ return m_status_changed;
+}
+
+
+bool Gtk4DrawArea::on_mouse_scroll(double dx, double dy)
+{
+ (void) dx;
+ if (fCanvas)
+ fCanvas->HandleInput(dy < 0 ? kWheelUp : kWheelDown, (int) fLastMouseX, (int) fLastMouseY);
+
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+/// Shows context menu with provided items and title at specified position
+
+void Gtk4DrawArea::ShowContextMenu(Glib::RefPtr menu_model, const char *title, int x, int y)
+{
+ if (title && *title) {
+ auto item = Gio::MenuItem::create("", "");
+ item->set_attribute_value("custom", Glib::Variant::create("header"));
+ menu_model->prepend_item(item);
+ }
+
+ auto menu = Gtk::make_managed(menu_model);
+ menu->set_parent(*this);
+ menu->set_has_arrow(false); // Removes the little popover triangle point
+
+ menu->add_css_class("compact-menu");
+
+ static bool has_compact_style = false;
+ if (!has_compact_style) {
+ auto css_provider = Gtk::CssProvider::create();
+ std::string css_data =
+ "popover.compact-menu contents modelbutton, "
+ "popover.compact-menu contents button.model { "
+ " padding-top: 1px; "
+ " padding-bottom: 0px; "
+ " margin-top: 0px; "
+ " margin-bottom: 0px; "
+ " min-height: 10px; "
+ "}";
+
+ css_provider->load_from_data(css_data);
+
+ // 4. Inject into the display context
+ Gtk::StyleContext::add_provider_for_display(
+ Gdk::Display::get_default(),
+ css_provider,
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
+ );
+
+ has_compact_style = true;
+ }
+
+ if (title && *title) {
+ auto label = Gtk::make_managed(title);
+ label->add_css_class("heading");
+ menu->add_child(*label, "header");
+ }
+
+ Gdk::Rectangle rect(x, y, 1, 1);
+
+ // 2. Snap the context menu anchor to this exact tiny point rectangle
+ menu->set_pointing_to(rect);
+
+ // 3. Open the popup menu smoothly on screen
+ menu->popup();
+}
+
+bool Gtk4DrawArea::on_my_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip)
+{
+ if (keyboard_tooltip)
+ return false; // Skip if triggered by keyboard focus
+
+ auto canv = GetCanvas();
+ if (!canv)
+ return false;
+
+ TObject *selected = canv->GetSelected();
+ Int_t px = canv->GetEventX();
+ Int_t py = canv->GetEventY();
+ TString customText;
+ if (selected)
+ customText.Form("%s::%s\n%s\n%d, %d\n%s",selected->ClassName() ,selected->GetName(),
+ selected->GetTitle(), px, py, selected->GetObjectInfo(px, py));
+ else
+ customText.Form("No selected object\n%d, %d", x, y);
+
+ tooltip->set_text(customText.Data());
+
+ return true; // Return true to instruct GTK to show the tooltip
+}
diff --git a/gui/gtk4canvas/src/Gtk4DrawArea.h b/gui/gtk4canvas/src/Gtk4DrawArea.h
new file mode 100644
index 0000000000000..2a31f10e13f43
--- /dev/null
+++ b/gui/gtk4canvas/src/Gtk4DrawArea.h
@@ -0,0 +1,63 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#ifndef ROOT_Gtk4DrawArea_H
+#define ROOT_Gtk4DrawArea_H
+
+#include
+#include
+
+class TCanvas;
+
+class Gtk4DrawArea : public Gtk::DrawingArea {
+public:
+ Gtk4DrawArea();
+ virtual ~Gtk4DrawArea() = default;
+
+ void SetCanvas(TCanvas *c) { fCanvas = c; }
+ TCanvas *GetCanvas() const { return fCanvas; }
+
+ Cairo::Context *GetContext() const { return fContext; }
+
+ Cairo::ImageSurface *GetCache() const { return fDrawingCache.get(); }
+
+ void ShowContextMenu(Glib::RefPtr menu_model, const char *title, int x, int y);
+
+ sigc::signal status_changed();
+
+
+
+protected:
+ // The render function for gtkmm 4
+ void on_draw(const Cairo::RefPtr& cr, int width, int height);
+
+ TCanvas *fCanvas = nullptr;
+ Cairo::Context *fContext = nullptr;
+ double fLastMouseX = 0;
+ double fLastMouseY = 0;
+ bool fShowEventStatus = true;
+ Cairo::RefPtr fDrawingCache;
+
+ void on_mouse_click(int n_press, double x, double y);
+ void on_mouse_released(int n_press, double x, double y);
+ void on_mouse_enter(double x, double y);
+ void on_mouse_move(double x, double y);
+ void on_mouse_leave();
+ bool on_mouse_scroll(double dx, double dy);
+ bool on_my_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr& tooltip);
+
+ sigc::signal m_status_changed;
+
+ Glib::RefPtr m_click_gesture;
+ Glib::RefPtr m_motion_controller;
+ Glib::RefPtr m_scroll_controller;
+};
+
+#endif
diff --git a/gui/gtk4canvas/src/Gtk4MethodDialog.cxx b/gui/gtk4canvas/src/Gtk4MethodDialog.cxx
new file mode 100644
index 0000000000000..47b3ba69b8aed
--- /dev/null
+++ b/gui/gtk4canvas/src/Gtk4MethodDialog.cxx
@@ -0,0 +1,253 @@
+// Author: Sergey Linev, GSI 14/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#include "Gtk4MethodDialog.h"
+
+#include "TString.h"
+#include "TROOT.h"
+#include "TVirtualPad.h"
+#include "TError.h"
+#include "TMethod.h"
+#include "TMethodArg.h"
+#include "TMethodCall.h"
+#include "TObjString.h"
+#include "TContextMenu.h"
+#include "TColor.h"
+
+#include
+
+/** \class Gtk4MethodDialog
+ \ingroup gtk4canvas
+
+Specialized dialog to enter arguments for method exection
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+/// Create method dialog
+
+Gtk4MethodDialog::Gtk4MethodDialog(unsigned width, unsigned height)
+{
+ set_title("method dialog");
+ set_default_size(width, height);
+
+ m_main_box = Gtk::make_managed(Gtk::Orientation::VERTICAL, 10);
+ m_main_box->set_margin(5);
+ set_child(*m_main_box);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// clear all elements inside dialog main box
+
+void Gtk4MethodDialog::clearAllElements()
+{
+ while (auto* child = m_main_box->get_first_child())
+ m_main_box->remove(*child);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// add line with color button
+
+Gtk::ColorDialogButton *Gtk4MethodDialog::addColorInput(const char *title, Int_t colindx)
+{
+ TColor *rootColor = gROOT->GetColor(colindx);
+ Gdk::RGBA initial_color;
+ initial_color.set_rgba(0.0, 0.0, 0.0, 1.0);
+
+ if (rootColor)
+ initial_color.set_rgba(rootColor->GetRed(), rootColor->GetGreen(), rootColor->GetBlue(), rootColor->GetAlpha());
+
+ auto color_dialog = Gtk::ColorDialog::create();
+ color_dialog->set_title("Select a Custom Color");
+ color_dialog->set_modal(true);
+
+ // Instantiate the specific ColorDialogButton using our dialog rules
+ auto m_color_button = Gtk::make_managed(color_dialog);
+ m_color_button->set_halign(Gtk::Align::END);
+
+ // Set an initial default color (e.g., solid Red)
+ m_color_button->set_rgba(initial_color);
+
+ addLine(title, m_color_button);
+
+ return m_color_button;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Add single dialog line
+
+void Gtk4MethodDialog::addLine(const char *txt, Gtk::Widget *widget)
+{
+ auto layout_grid = Gtk::make_managed();
+ layout_grid->set_column_spacing(15);
+ layout_grid->set_hexpand(true);
+ layout_grid->set_halign(Gtk::Align::FILL);
+ m_main_box->append(*layout_grid);
+
+ auto label = Gtk::make_managed(txt);
+ label->set_halign(Gtk::Align::START);
+ label->set_hexpand(true); // Wants to expand
+ label->set_halign(Gtk::Align::FILL); // Stretch to fill its 50% allocation
+ label->set_xalign(0.0);
+
+
+ widget->set_hexpand(true); // Wants to expand equally with the label
+ widget->set_halign(Gtk::Align::FILL);
+
+ layout_grid->attach(*label, 0, 0, 3, 1);
+ layout_grid->attach(*widget, 3, 0, 7, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Add method argument
+
+void Gtk4MethodDialog::addArg(const char *argname, const char *value, const char *)
+{
+ auto entry = Gtk::make_managed();
+ entry->set_text(value);
+ addLine(TString::Format("%s:", argname).Data(), entry);
+ fArgs.push_back(entry);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+/// Add Ok and Cancel buttons
+
+Gtk::Button *Gtk4MethodDialog::addOkCancelButtons()
+{
+ auto hbox = Gtk::make_managed();
+ hbox->set_hexpand(true);
+ hbox->set_halign(Gtk::Align::END);
+ m_main_box->append(*hbox);
+
+ auto button1 = Gtk::make_managed("Ok");
+
+ auto button2 = Gtk::make_managed("Cancel");
+ button2->signal_clicked().connect([this]() { close(); });
+
+ hbox->append(*button1);
+ hbox->append(*button2);
+
+ return button1;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Show method dialog
+
+void Gtk4MethodDialog::methodDialog(TContextMenu *menu, TObject *object, TFunction* func)
+{
+ if (!menu || !object || !func)
+ return;
+
+ set_title(menu->CreateDialogTitle(object, func));
+
+ // iterate through all arguments and create appropriate input-data objects:
+ // inputlines, option menus...
+ TIter next(func->GetListOfMethodArgs());
+
+ while (auto argument = (TMethodArg *) next()) {
+ TString argTitle = menu->CreateArgumentTitle(argument);
+ TString type = argument->GetTypeName();
+ TDataType *datatype = gROOT->GetType(type);
+ TString basictype;
+
+ if (datatype) {
+ basictype = datatype->GetTypeName();
+ } else {
+ if (type.CompareTo("enum") != 0)
+ std::cout << "*** Warning in Dialog(): data type is not basic type, assuming (int)\n";
+ basictype = "int";
+ }
+
+ if (TString(argument->GetTitle()).Index("*") != kNPOS) {
+ basictype += "*";
+ type = "char*";
+ }
+
+ TDataMember *m = argument->GetDataMember();
+ if (m && m->GetterMethod()) {
+
+ m->GetterMethod()->Init(object->IsA(), m->GetterMethod()->GetMethodName(), "");
+
+ // Get the current value and form it as a text:
+
+ TString val;
+
+ if (basictype == "char*") {
+ char *tdefval = nullptr;
+ m->GetterMethod()->Execute(object, "", &tdefval);
+ if (tdefval) val = tdefval;
+ } else
+ if ((basictype == "float") ||
+ (basictype == "double")) {
+ Double_t ddefval = 0.;
+ m->GetterMethod()->Execute(object, "", ddefval);
+ val = TString::Format("%g", ddefval);
+ } else
+ if ((basictype == "char") ||
+ (basictype == "int") ||
+ (basictype == "bool") ||
+ (basictype == "long") ||
+ (basictype == "short")) {
+ Longptr_t ldefval = 0;
+ m->GetterMethod()->Execute(object, "", ldefval);
+ val = TString::Format("%ld", (long) ldefval);
+ }
+
+ // Warn aout options ...
+ if (m->GetOptions())
+ ::Warning("Gtk4MethodDialog::Dialog", "option menu not yet implemented");
+
+ addArg(argTitle.Data(), val.Data(), type.Data());
+ } else { // if m not found ...
+ TString argDflt;
+ if (argument->GetDefault())
+ argDflt = argument->GetDefault();
+
+ if ((argDflt.Length() > 1) &&
+ (argDflt[0]=='\"') && (argDflt[argDflt.Length()-1]=='\"')) {
+ // cut "" from the string argument
+ argDflt.Remove(0,1);
+ argDflt.Remove(argDflt.Length()-1,1);
+ }
+
+ addArg(argTitle.Data(), argDflt.Data(), type.Data());
+ }
+ }
+
+ auto okBtn = addOkCancelButtons();
+
+ okBtn->signal_clicked().connect(sigc::mem_fun(*this, &Gtk4MethodDialog::on_ok_button_clicked));
+
+ present();
+
+ fMenu = menu;
+ fObject = object;
+ fFunc = func;
+}
+
+
+void Gtk4MethodDialog::on_ok_button_clicked()
+{
+ if (fMenu && fObject) {
+
+ TObjArray argslist(fArgs.size());
+
+ for (auto entry : fArgs) {
+ Glib::ustring v = entry->get_text();
+ argslist.AddLast(new TObjString(v.c_str()));
+ }
+
+ close();
+
+ fMenu->Execute(fObject, fFunc, &argslist);
+ } else {
+ close();
+ }
+}
diff --git a/gui/gtk4canvas/src/Gtk4MethodDialog.h b/gui/gtk4canvas/src/Gtk4MethodDialog.h
new file mode 100644
index 0000000000000..049d0d55017bc
--- /dev/null
+++ b/gui/gtk4canvas/src/Gtk4MethodDialog.h
@@ -0,0 +1,51 @@
+// Author: Sergey Linev, GSI 14/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+
+#ifndef ROOT_Gtk4MethodDialog
+#define ROOT_Gtk4MethodDialog
+
+#include
+#include
+
+class TContextMenu;
+class TObject;
+class TFunction;
+
+class Gtk4MethodDialog: public Gtk::Window {
+
+ public:
+ Gtk4MethodDialog(unsigned width = 400, unsigned height = 200);
+
+ void methodDialog(TContextMenu *menu, TObject *object, TFunction* func);
+
+ void clearAllElements();
+
+ void addLine(const char *txt, Gtk::Widget *widget);
+
+ Gtk::ColorDialogButton *addColorInput(const char *title, int colindx);
+
+ void addArg(const char *argname, const char *value, const char *type);
+
+ Gtk::Button *addOkCancelButtons();
+
+ protected:
+
+ void on_ok_button_clicked();
+
+ Gtk::Box *m_main_box = nullptr;
+
+ std::vector fArgs;
+ TContextMenu *fMenu = nullptr;
+ TObject *fObject = nullptr;
+ TFunction *fFunc = nullptr;
+};
+
+#endif
diff --git a/gui/gtk4canvas/src/TGtk4Canvas.cxx b/gui/gtk4canvas/src/TGtk4Canvas.cxx
new file mode 100644
index 0000000000000..08e831e0dc5a7
--- /dev/null
+++ b/gui/gtk4canvas/src/TGtk4Canvas.cxx
@@ -0,0 +1,377 @@
+// Author: Sergey Linev, GSI 12/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#include "TGtk4Canvas.h"
+
+#include "TGtk4PadPainter.h"
+
+#include "TSystem.h"
+#include "TStyle.h"
+#include "TCanvas.h"
+#include "TROOT.h"
+#include "TClass.h"
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "Gtk4CanvasWindow.h"
+
+using namespace ROOT::Experimental;
+
+/** \class TGtk4Canvas
+ \ingroup gtk4canvas
+ \brief Basic TCanvasImp ABI implementation for Gtk4
+*/
+
+////////////////////////////////////////////////////////////////////////////////
+/// Constructor
+
+TGtk4Canvas::TGtk4Canvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height)
+ : TCanvasImp(c, name, x, y, width, height)
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Destructor
+
+TGtk4Canvas::~TGtk4Canvas()
+{
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Initialize window for the Gtk4 canvas
+
+Int_t TGtk4Canvas::InitWindow()
+{
+ return 111222333; // should not be used at all
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Creates pad painter
+
+TVirtualPadPainter *TGtk4Canvas::CreatePadPainter()
+{
+ return new TGtk4PadPainter(fDrawArea);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Close canvas - not implemented
+
+void TGtk4Canvas::Close()
+{
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Show canvas
+
+void TGtk4Canvas::Show()
+{
+ if (fCanvasWindow)
+ fCanvasWindow->present();
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Returns kTRUE if web canvas has graphical editor
+
+Bool_t TGtk4Canvas::HasEditor() const
+{
+ return (fClientBits & TCanvas::kShowEditor) != 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Returns kTRUE if web canvas has menu bar
+
+Bool_t TGtk4Canvas::HasMenuBar() const
+{
+ return (fClientBits & TCanvas::kMenuBar) != 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Returns kTRUE if web canvas has status bar
+
+Bool_t TGtk4Canvas::HasStatusBar() const
+{
+ return (fClientBits & TCanvas::kShowEventStatus) != 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Returns kTRUE if tooltips are activated in web canvas
+
+Bool_t TGtk4Canvas::HasToolTips() const
+{
+ return (fClientBits & TCanvas::kShowToolTips) != 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Set window position of web canvas
+
+void TGtk4Canvas::SetWindowPosition(Int_t, Int_t)
+{
+ // not directly possible in gtk4
+ //if (fCanvasWindow)
+ // fCanvasWindow->move(x, y);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Set window size of web canvas
+
+void TGtk4Canvas::SetWindowSize(UInt_t w, UInt_t h)
+{
+ if (fCanvasWindow)
+ fCanvasWindow->set_default_size(w, h);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Set window title of web canvas
+
+void TGtk4Canvas::SetWindowTitle(const char *newTitle)
+{
+ if (fCanvasWindow)
+ fCanvasWindow->set_title(newTitle);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Set fixed canvas size - not supported yet in gtk
+
+void TGtk4Canvas::SetCanvasSize(UInt_t cw, UInt_t ch)
+{
+ fFixedSize = kTRUE;
+ if ((cw > 0) && (ch > 0)) {
+ // Canvas()->fCw = cw;
+ // Canvas()->fCh = ch;
+ } else {
+ // temporary value, will be reported back from client
+ // Canvas()->fCw = Canvas()->fWindowWidth;
+ // Canvas()->fCh = Canvas()->fWindowHeight;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+/// Set cursor
+
+void TGtk4Canvas::SetCursor(ECursor cursor)
+{
+ const char *id = nullptr;
+ switch(cursor) {
+ case kBottomLeft: id = "sw-resize"; break;
+ case kBottomRight: id = "se-resize"; break;
+ case kTopLeft: id = "nw-resize"; break;
+ case kTopRight: id = "ne-resize"; break;
+ case kBottomSide: id = "s-resize"; break;
+ case kLeftSide: id = "w-resize"; break;
+ case kTopSide: id = "n-resize"; break;
+ case kRightSide: id = "r-resize"; break;
+ case kMove: id = "move"; break;
+ case kCross: id = "crosshair"; break;
+ case kArrowHor: id = "ns-resize"; break;
+ case kArrowVer: id = "ew-resize"; break;
+ case kHand: id = "grab"; break;
+ case kRotate: id = "grab"; break;
+ case kPointer: id = "pointer"; break;
+ case kArrowRight: id = "ew-resize"; break;
+ case kCaret: id = "text"; break;
+ case kWatch: id = "wait"; break;
+ case kNoDrop: id = "no-drop"; break;
+ default: id = "default"; break;
+ }
+ if (id && fDrawArea)
+ fDrawArea->set_cursor(id);
+}
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Iconify browser window
+
+void TGtk4Canvas::Iconify()
+{
+ if (fCanvasWindow)
+ fCanvasWindow->minimize();
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Raise browser window
+
+void TGtk4Canvas::RaiseWindow()
+{
+ if (fCanvasWindow)
+ fCanvasWindow->present();
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Assign clients bits
+
+void TGtk4Canvas::AssignStatusBits(UInt_t bits)
+{
+ fClientBits = bits;
+ Canvas()->SetBit(TCanvas::kShowEventStatus, bits & TCanvas::kShowEventStatus);
+ Canvas()->SetBit(TCanvas::kShowEditor, bits & TCanvas::kShowEditor);
+ Canvas()->SetBit(TCanvas::kShowToolTips, bits & TCanvas::kShowToolTips);
+ Canvas()->SetBit(TCanvas::kMenuBar, bits & TCanvas::kMenuBar);
+ Canvas()->SetBit(TCanvas::kShowToolBar, bits & TCanvas::kShowToolBar);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Returns canvas geometry
+
+void TGtk4Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h)
+{
+ (void) wid;
+ if (fDrawArea) {
+ w = fDrawArea->get_width();
+ h = fDrawArea->get_height();
+ } else {
+ w = 780;
+ h = 580;
+ }
+ // printf("CanvasGeometry %u %u\n", w, h);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Returns window geometry including borders and menus
+
+UInt_t TGtk4Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h)
+{
+ if (fCanvasWindow) {
+ // gtk4 does not deliver position
+ x = y = 0;
+ w = fCanvasWindow->get_width();
+ h = fCanvasWindow->get_height();
+ } else {
+ x = y = 0;
+ w = 800;
+ h = 600;
+ }
+
+ // printf("WindowGeometry %u %u\n", w, h);
+
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// if canvas or any subpad was modified,
+/// invoke gtk4 queue_draw() which will trigger redraw area
+
+Bool_t TGtk4Canvas::PerformUpdate(Bool_t async)
+{
+ if (!Canvas()->IsAnyNeedRepaint() || !fDrawArea)
+ return kTRUE;
+
+ fDrawArea->queue_draw();
+
+ if (!async) {
+ auto display = fDrawArea->get_display();
+ if (display) {
+ // Flushes all render requests and locks the local thread
+ // until the windowing manager acknowledges processing complete.
+ display->sync();
+ }
+ }
+
+ return kTRUE;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////
+/// Increment canvas version and force sending data to client - do not wait for reply
+
+void TGtk4Canvas::ForceUpdate()
+{
+ if (fDrawArea)
+ fDrawArea->queue_draw();
+}
+
+
+Glib::RefPtr gt4k_context;
+Glib::RefPtr gt4k_app;
+
+class TGtk4Timer : public TTimer {
+public:
+ TGtk4Timer(Long_t milliSec, Bool_t mode) :
+ TTimer(milliSec, mode) {}
+
+ void Timeout() override
+ {
+ // just flush all events,
+ // false - do not wait/sleep for next mouse events, just exit
+ while (gt4k_context->pending())
+ gt4k_context->iteration(false);
+ }
+
+};
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+/// Static method to create TGtk4Canvas instance
+/// Used by plugin manager to directly create TGtk4Canvas without gui factory
+
+TCanvasImp *TGtk4Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height)
+{
+ static TGtk4Timer *timer = nullptr;
+ if (!timer) {
+
+ Glib::init();
+
+ gt4k_app = Gtk::Application::create("org.example.customloop");
+
+ gt4k_context = Glib::MainContext::get_default();
+
+ timer = new TGtk4Timer(5, kTRUE);
+ timer->TurnOn();
+ }
+
+ auto widget = new Gtk4CanvasWindow(c, width, height);
+
+ auto draw_area = widget->GetDrawArea();
+
+ draw_area->queue_draw();
+
+ widget->present();
+
+ // workaround to let gtk4 time for first rendering of the widget
+ int cnt = 100;
+ while (cnt-- > 0 && (draw_area->get_width() == 0)) {
+ while (gt4k_context->pending())
+ gt4k_context->iteration(false);
+ }
+
+
+ auto imp = new TGtk4Canvas(c, name, x, y, width, height);
+
+ imp->fCanvasWindow = widget;
+ imp->fDrawArea = draw_area;
+
+ // now can set canvas
+ draw_area->SetCanvas(c);
+ draw_area->queue_draw(); // request to update painting
+
+ // set all internal dimensions
+ // c->Resize();
+
+
+ // TODO: maybe apply same logic to adjust canvas dimension as in TRootCanvas
+ // Keep commented code here intentionally to be able find this place when
+ // search for correspondent class members
+
+ // c->fWindowTopX = x;
+ // c->fWindowTopY = y;
+ // c->fWindowWidth = width;
+ // c->fWindowHeight = height;
+ // if (!gROOT->IsBatch() && (height > 25))
+ // height -= 25;
+ // c->fCw = width;
+ // c->fCh = height;
+
+ return imp;
+}
diff --git a/gui/gtk4canvas/src/TGtk4ContextMenu.cxx b/gui/gtk4canvas/src/TGtk4ContextMenu.cxx
new file mode 100644
index 0000000000000..693f8351fca9d
--- /dev/null
+++ b/gui/gtk4canvas/src/TGtk4ContextMenu.cxx
@@ -0,0 +1,388 @@
+// Author: Sergey Linev 13/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+
+/** \class TGtk4ContextMenu
+ \ingroup gtk4canvas
+
+This class provides an interface to context-sensitive popup menus.
+These menus pop up when the user hits the right mouse button, and
+are destroyed when the menu pops downs.
+*/
+
+
+#include "TGtk4ContextMenu.h"
+
+#include "TROOT.h"
+#include "TContextMenu.h"
+#include "TCanvas.h"
+#include "TColor.h"
+#include "TMethod.h"
+#include "TDataMember.h"
+#include "TToggle.h"
+#include "TClassMenuItem.h"
+#include "TAttText.h"
+#include "TAttMarker.h"
+
+#include "TGtk4Canvas.h"
+#include "Gtk4DrawArea.h"
+#include "Gtk4MethodDialog.h"
+
+#include
+
+enum EContextMenu {
+ kToggleStart = 1000, // first id of toggle menu items
+ kToggleListStart = 2000, // first id of toggle list menu items
+ kUserFunctionStart = 3000 // first id of user added functions/methods, etc...
+};
+
+////////////////////////////////////////////////////////////////////////////////
+/// Create context menu.
+
+TGtk4ContextMenu::TGtk4ContextMenu(TContextMenu *c, const char *)
+ : TObject(), TContextMenuImp(c)
+{
+ gROOT->GetListOfCleanups()->Add(this);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Delete a context menu.
+
+TGtk4ContextMenu::~TGtk4ContextMenu()
+{
+ gROOT->GetListOfCleanups()->Remove(this);
+ fTrash.Delete();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Display context popup menu for currently selected object.
+
+void TGtk4ContextMenu::DisplayPopup(Int_t x, Int_t y)
+{
+ // add menu items to popup menu
+ // CreateMenu(fContextMenu->GetSelectedObject());
+
+ auto object = fContextMenu->GetSelectedObject();
+ if (!object)
+ return;
+
+ fCustomArg.clear();
+ fTrash.Delete();
+
+
+ auto canv = dynamic_cast(fContextMenu->GetSelectedCanvas());
+ auto canvimp = dynamic_cast(canv->GetCanvasImp());
+ auto widget = canvimp->GetDrawArea();
+
+// QPoint screenPos = widget->mapToGlobal(widget->rect().topLeft());
+
+// QMenu menu;
+// QSignalMapper map;
+
+// QObject::connect(&map, &QSignalMapper::mappedInt,
+// this, &TGtk4ContextMenu::executeMenu);
+
+ auto action_group = Gio::SimpleActionGroup::create();
+
+ action_group->add_action_with_parameter(
+ "action",
+ Glib::VariantType("i"), // Expected parameter type (int32)
+ [this](const Glib::VariantBase& parameter) {
+ if (auto var = Glib::VariantBase::cast_dynamic>(parameter)) {
+ int value = var.get();
+ std::cout << "Action triggered with integer value: " << value << std::endl;
+ executeMenu(value);
+ }
+ }
+ );
+
+ auto menu_model = Gio::Menu::create();
+
+ auto current_section = Gio::Menu::create();
+
+
+ auto addSeparator = [&menu_model, ¤t_section]() {
+ menu_model->append_section(current_section);
+ current_section = Gio::Menu::create();
+ };
+
+ auto addMenuAction = [¤t_section, &action_group, this](const TString &text, int id, void *arg = nullptr, int checked = 0) {
+ bool enabled = true;
+ if ((text == "DrawClone") || (text == "DrawClass") || (text == "Inspect") ||
+ (text == "SetShowProjectionX") || (text == "SetShowProjectionY") ||
+ (text == "DrawPanel") || (text == "FitPanel"))
+ enabled = false;
+
+
+ if (checked != 0) {
+ TString action_name = TString::Format("toggle-item-%d", id);
+ auto action = Gio::SimpleAction::create_bool(action_name.Data(), checked > 0);
+ action->signal_activate().connect([action,this, id](const Glib::VariantBase &) {
+ bool current;
+ action->get_state(current);
+ action->set_state(Glib::Variant::create(!current));
+ std::cout << "Action triggered with integer value: " << id << std::endl;
+ executeMenu(id);
+ });
+ action->set_enabled(enabled);
+ action_group->add_action(action);
+
+ action_name.Prepend("context.");
+ auto item = Gio::MenuItem::create(text.Data(), action_name.Data());
+ current_section->append_item(item);
+ } else if (enabled) {
+ auto item = Gio::MenuItem::create(text.Data(), "");
+ item->set_action_and_target("context.action", Glib::Variant::create(id));
+ current_section->append_item(item);
+ } else {
+ // just refer to non-existing action
+ current_section->append(text.Data(), "context.dummy");
+ }
+
+ fCustomArg[id] = arg;
+ };
+
+ TString title = fContextMenu->CreatePopupTitle(object);
+
+ bool last_separ = true;
+ int entry = 0, toggle = kToggleStart, togglelist = kToggleListStart;
+ int userfunction = kUserFunctionStart;
+
+ // Get list of menu items from the selected object's class
+ TList *menuItemList = object->IsA()->GetMenuList();
+
+ TIter nextItem(menuItemList);
+
+ while (auto menuItem = (TClassMenuItem*) nextItem()) {
+ switch (menuItem->GetType()) {
+ case TClassMenuItem::kPopupSeparator: {
+ if (!last_separ)
+ addSeparator();
+ last_separ = true;
+ break;
+ }
+ case TClassMenuItem::kPopupStandardList: {
+ // Standard list of class methods. Rebuild from scratch.
+ // Get linked list of objects menu items (i.e. member functions
+ // with the token *MENU in their comment fields.
+ TList *methodList = new TList;
+ object->IsA()->GetMenuItems(methodList);
+
+ TMethod *method;
+ TClass *classPtr = nullptr;
+ TIter next(methodList);
+ Bool_t needSep = kFALSE;
+
+ while ((method = (TMethod*) next())) {
+ if (classPtr != method->GetClass()) {
+ needSep = kTRUE;
+ classPtr = method->GetClass();
+ }
+
+ EMenuItemKind menuKind = method->IsMenuItem();
+ TString last_component;
+
+ switch (menuKind) {
+ case kMenuDialog:
+ // search for arguments to the MENU statement
+ if (needSep) {
+ addSeparator();
+ needSep = kFALSE;
+ }
+ addMenuAction(method->GetName(), entry++, method);
+ break;
+ case kMenuSubMenu:
+ if (auto m = method->FindDataMember()) {
+ if (needSep) {
+ addSeparator();
+ needSep = kFALSE;
+ }
+
+ // TODO: implement selection menu
+ if (m->GetterMethod()) {
+ // QMenu *r = menu.addMenu(method->GetName());
+ TIter nxt(m->GetOptions());
+ while (auto it = (TOptionListItem*) nxt()) {
+ TToggle *t = new TToggle;
+ t->SetToggledObject(object, method);
+ t->SetOnValue(it->fValue);
+ fTrash.Add(t);
+
+ // TODO: implement checked state
+ addMenuAction(it->fOptName.Data(), togglelist++, t, t->GetState() ? 1 : -1);
+ }
+ } else {
+ addMenuAction(method->GetName(), entry++, method);
+ }
+ }
+ break;
+
+ case kMenuToggle: {
+ if (needSep) {
+ addSeparator();
+ needSep = kFALSE;
+ }
+
+ TToggle *t = new TToggle;
+ t->SetToggledObject(object, method);
+ t->SetOnValue(1);
+ fTrash.Add(t);
+
+ addMenuAction(method->GetName(), toggle++, t, t->GetState() ? 1 : -1);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ delete methodList;
+ }
+ break;
+ case TClassMenuItem::kPopupUserFunction: {
+ const char* menuItemTitle = menuItem->GetTitle();
+ if (menuItem->IsToggle()) {
+ TMethod* method = object->IsA()->GetMethodWithPrototype(menuItem->GetFunctionName(),menuItem->GetArgs());
+ if (method) {
+ TToggle *t = new TToggle;
+ t->SetToggledObject(object, method);
+ t->SetOnValue(1);
+ fTrash.Add(t);
+
+ if (strlen(menuItemTitle)==0)
+ menuItemTitle = method->GetName();
+ addMenuAction(menuItemTitle, toggle++, t, t->GetState() ? 1 : -1);
+ }
+ } else {
+ if (strlen(menuItemTitle)==0)
+ menuItemTitle = menuItem->GetFunctionName();
+ addMenuAction(menuItemTitle, userfunction++, menuItem);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ widget->insert_action_group("context", action_group);
+
+ widget->ShowContextMenu(menu_model, title.Data(), x, y);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Create dialog object with OK and Cancel buttons. This dialog
+/// prompts for the arguments of "method".
+
+void TGtk4ContextMenu::Dialog(TObject *object, TMethod *method)
+{
+ Dialog(object, (TFunction *)method);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Create dialog object with OK and Cancel buttons. This dialog
+/// prompts for the arguments of "function".
+/// function may be a global function or a method
+
+void TGtk4ContextMenu::Dialog(TObject * object, TFunction * func)
+{
+ auto widget = new Gtk4MethodDialog(300, 300);
+
+ widget->methodDialog(fContextMenu, object, func);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+/// Handle remove of some ROOT objects
+
+void TGtk4ContextMenu::RecursiveRemove(TObject *obj)
+{
+ if (obj == fContextMenu->GetSelectedCanvas())
+ fContextMenu->SetCanvas(nullptr);
+ if (obj == fContextMenu->GetSelectedPad())
+ fContextMenu->SetPad(nullptr);
+ if (obj == fContextMenu->GetSelectedObject()) {
+ // if the object being deleted is the one selected,
+ // ungrab the mouse pointer and terminate (close) the menu
+ fContextMenu->SetObject(nullptr);
+ }
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+/// Execute specified menu item
+
+void TGtk4ContextMenu::executeMenu(int id)
+{
+ if (id < 0)
+ return;
+ void *ud = fCustomArg[id];
+
+ if (ud) {
+ // retrieve the highlighted function
+ TFunction *function = nullptr;
+ if (id < kToggleStart) {
+ TMethod *m = (TMethod *)ud;
+ function = (TFunction *)m;
+ } else if (id >= kToggleStart && id < kUserFunctionStart) {
+ TToggle *t = (TToggle *)ud;
+ TMethodCall *mc = (TMethodCall *)t->GetSetter();
+ function = (TFunction *)mc->GetMethod();
+ } else {
+ TClassMenuItem *mi = (TClassMenuItem *)ud;
+ function = gROOT->GetGlobalFunctionWithPrototype(mi->GetFunctionName());
+ }
+ if (function)
+ fContextMenu->SetMethod(function);
+ }
+
+ if (id < kToggleStart) {
+ auto m = (TMethod *) ud;
+
+ /*
+ if (!strcmp(m->GetName(), "SetLineAttributes")) {
+ auto attline = dynamic_cast(fContextMenu->GetSelectedObject());
+ if (attline) {
+ auto widget = new Gtk4MethodDialog(300, 300);
+ widget->attLineDialog(attline);
+ }
+ } else if (!strcmp(m->GetName(), "SetFillAttributes")) {
+ auto attfill = dynamic_cast(fContextMenu->GetSelectedObject());
+ if (attfill) {
+ auto widget = new Gtk4MethodDialog(300, 300);
+ widget->attFillDialog(attfill);
+ }
+ } else if (!strcmp(m->GetName(), "SetTextAttributes")) {
+ auto atttext = dynamic_cast(fContextMenu->GetSelectedObject());
+ if (atttext) {
+ auto widget = new Gtk4MethodDialog(300, 300);
+ widget->attTextDialog(atttext);
+ }
+ } else if (!strcmp(m->GetName(), "SetMarkerAttributes")) {
+ auto attmark = dynamic_cast(fContextMenu->GetSelectedObject());
+ if (attmark) {
+ auto widget = new Gtk4MethodDialog(300, 300);
+ widget->attMarkerDialog(attmark);
+ }
+ } else {
+ */
+ fContextMenu->Action(m);
+
+ } else if (id >= kToggleStart && id < kToggleListStart) {
+ TToggle *t = (TToggle *) ud;
+ fContextMenu->Action(t);
+ } else if (id >= kToggleListStart && id < kUserFunctionStart) {
+ TToggle *t = (TToggle *) ud;
+ if (t->GetState() == 0)
+ t->SetState(1);
+ } else {
+ TClassMenuItem *mi = (TClassMenuItem*)ud;
+ fContextMenu->Action(mi);
+ }
+}
diff --git a/gui/gtk4canvas/src/TGtk4ContextMenu.h b/gui/gtk4canvas/src/TGtk4ContextMenu.h
new file mode 100644
index 0000000000000..d7d787717fe38
--- /dev/null
+++ b/gui/gtk4canvas/src/TGtk4ContextMenu.h
@@ -0,0 +1,52 @@
+// Author: Sergey Linev 13/08/2026
+
+/*************************************************************************
+ * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
+ * All rights reserved. *
+ * *
+ * For the licensing terms see $ROOTSYS/LICENSE. *
+ * For the list of contributors see $ROOTSYS/README/CREDITS. *
+ *************************************************************************/
+
+#ifndef ROOT_TGtk4ContextMenu
+#define ROOT_TGtk4ContextMenu
+
+#include "TContextMenuImp.h"
+#include "TObject.h"
+#include "TList.h"
+#include