From 8e030f37c7b25e3ed4e09b7362481e4dbcd19735 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 10:15:04 +0200 Subject: [PATCH 01/20] [qt6canv] introduce qt6canv build option to ROOT --- .github/workflows/root-ci-config/buildconfig/global.txt | 1 + cmake/modules/RootBuildOptions.cmake | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/root-ci-config/buildconfig/global.txt b/.github/workflows/root-ci-config/buildconfig/global.txt index eafb656ada6f7..7654b1d2799e4 100644 --- a/.github/workflows/root-ci-config/buildconfig/global.txt +++ b/.github/workflows/root-ci-config/buildconfig/global.txt @@ -63,6 +63,7 @@ opengl=ON pyroot=ON pythia8=OFF qt6web=OFF +qt6canv=OFF roofit=ON roofit_multiprocess=OFF root7=ON diff --git a/cmake/modules/RootBuildOptions.cmake b/cmake/modules/RootBuildOptions.cmake index a365b15ee6b4f..53f1a0c64f776 100644 --- a/cmake/modules/RootBuildOptions.cmake +++ b/cmake/modules/RootBuildOptions.cmake @@ -147,6 +147,7 @@ ROOT_BUILD_OPTION(mpi OFF "Enable support for Message Passing Interface (MPI)") ROOT_BUILD_OPTION(opengl ON "Enable support for OpenGL (requires libGL and libGLU)") 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(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(roofit_multiprocess OFF "Build RooFit::MultiProcess and multi-process RooFit::TestStatistics classes (requires ZeroMQ >= 4.3.5 built with -DENABLE_DRAFTS and cppzmq).") @@ -222,6 +223,7 @@ if(all) set(opengl_defvalue ON) set(pythia8_defvalue ON) set(pyroot_defvalue ON) + set(qt6canv_defvalue ON) set(qt6web_defvalue ON) set(r_defvalue ON) set(roofit_defvalue ON) From 6f5b84e7cfbd9d53b67f580ea05695d951fd8e4d Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 10:16:15 +0200 Subject: [PATCH 02/20] [qt6canv] add TQt6Canvas and TQt6PadPainter classes These will be core classes to provide Qt6 graphics independent from TVirtualX --- .github/CODEOWNERS | 1 + gui/CMakeLists.txt | 4 + gui/qt6canvas/CMakeLists.txt | 38 +++ gui/qt6canvas/inc/LinkDef.h | 20 ++ gui/qt6canvas/inc/TQt6Canvas.h | 74 ++++++ gui/qt6canvas/inc/TQt6PadPainter.h | 83 +++++++ gui/qt6canvas/index.md | 3 + gui/qt6canvas/src/TQt6Canvas.cxx | 346 +++++++++++++++++++++++++++ gui/qt6canvas/src/TQt6PadPainter.cxx | 183 ++++++++++++++ 9 files changed, 752 insertions(+) create mode 100644 gui/qt6canvas/CMakeLists.txt create mode 100644 gui/qt6canvas/inc/LinkDef.h create mode 100644 gui/qt6canvas/inc/TQt6Canvas.h create mode 100644 gui/qt6canvas/inc/TQt6PadPainter.h create mode 100644 gui/qt6canvas/index.md create mode 100644 gui/qt6canvas/src/TQt6Canvas.cxx create mode 100644 gui/qt6canvas/src/TQt6PadPainter.cxx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 19ada8b58b46b..b1950571c5f99 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -67,6 +67,7 @@ /gui/webdisplay/ @linev /gui/cefdisplay/ @linev /gui/qt6webdisplay/ @linev +/gui/qt6canvas/ @linev /gui/webgui6/ @linev /gui/fitpanelv7/ @linev /gui/browsable/ @bellenot @linev diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 51f0425e3ccb3..c8ed70ae4f357 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -12,6 +12,10 @@ add_subdirectory(guihtml) add_subdirectory(recorder) add_subdirectory(treemap) +if(qt6canv) + add_subdirectory(qt6canvas) +endif() + if(webgui) if(cefweb) add_subdirectory(cefdisplay) diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt new file mode 100644 index 0000000000000..8446019ad3a6f --- /dev/null +++ b/gui/qt6canvas/CMakeLists.txt @@ -0,0 +1,38 @@ +# 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/qt6canvas package +############################################################################ + +find_package(Qt6 COMPONENTS Core Gui CONFIG) + +if(NOT Qt6_FOUND) + if(fail-on-missing) + message(FATAL_ERROR "Could NOT find Qt6 with Gui components") + else() + message(WARNING "Qt6 (Gui) not found, disabling option 'qt6canvas'") + set(qt6canv OFF CACHE BOOL "Disabled because Qt6 Gui not found" FORCE) + return() + endif() +endif() + +set(CMAKE_AUTOMOC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas + HEADERS + TQt6Canvas.h + TQt6PadPainter.h + SOURCES + src/TQt6Canvas.cxx + src/TQt6PadPainter.cxx + LIBRARIES + Qt6::Gui + DEPENDENCIES + Core + Gpad +) diff --git a/gui/qt6canvas/inc/LinkDef.h b/gui/qt6canvas/inc/LinkDef.h new file mode 100644 index 0000000000000..64f79c00f1e72 --- /dev/null +++ b/gui/qt6canvas/inc/LinkDef.h @@ -0,0 +1,20 @@ +// Author: Sergey Linev, GSI 26/06/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 TQt6Canvas+; +#pragma link C++ class TQt6PadPainter+; + +#endif diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h new file mode 100644 index 0000000000000..c4d26986a1d6a --- /dev/null +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -0,0 +1,74 @@ +// Author: Sergey Linev, GSI 26/06/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_TQt6Canvas +#define ROOT_TQt6Canvas + +#include "TCanvasImp.h" + +class TQt6CanvasTimer; +class TPad; +class TExec; + +class TQt6Canvas : public TCanvasImp { + +protected: + + TQt6CanvasTimer *fTimer = nullptr; /// +#include +#include +#include +#include +#include +#include + + +class TQt6CanvasTimer : public TTimer { + TQt6Canvas &fCanv; +public: + TQt6CanvasTimer(TQt6Canvas &canv) : TTimer(10, kTRUE), fCanv(canv) {} + + + /// used to send control messages to clients + void Timeout() override + { + } +}; + + +/** \class TQt6Canvas + \ingroup qt6canvas + \brief Basic TCanvasImp ABI implementation for Qt6 + +*/ + +using namespace std::string_literals; + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor + +TQt6Canvas::TQt6Canvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height) + : TCanvasImp(c, name, x, y, width, height) +{ + // Workaround for multi-threaded environment + // Ensure main thread id picked when canvas implementation is created - + // otherwise it may be assigned in other thread and screw-up gPad access. + // Workaround may not work if main thread id was wrongly initialized before + // This resolves issue https://github.com/root-project/root/issues/15498 + TThread::SelfId(); + + fTimer = new TQt6CanvasTimer(*this); + + fTimer->TurnOn(); + + // fAsyncMode = kTRUE; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Destructor + +TQt6Canvas::~TQt6Canvas() +{ + delete fTimer; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Initialize window for the qt6 canvas + +Int_t TQt6Canvas::InitWindow() +{ + return 111222333; // should not be used at all +} + +//////////////////////////////////////////////////////////////////////////////// +/// Creates pad painter + +TVirtualPadPainter *TQt6Canvas::CreatePadPainter() +{ + return new TQt6PadPainter(); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +/// Close qt6 canvas - not implemented + +void TQt6Canvas::Close() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Show qt6 canvas + +void TQt6Canvas::Show() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns kTRUE if web canvas has graphical editor + +Bool_t TQt6Canvas::HasEditor() const +{ + return (fClientBits & TCanvas::kShowEditor) != 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns kTRUE if web canvas has menu bar + +Bool_t TQt6Canvas::HasMenuBar() const +{ + return (fClientBits & TCanvas::kMenuBar) != 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns kTRUE if web canvas has status bar + +Bool_t TQt6Canvas::HasStatusBar() const +{ + return (fClientBits & TCanvas::kShowEventStatus) != 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns kTRUE if tooltips are activated in web canvas + +Bool_t TQt6Canvas::HasToolTips() const +{ + return (fClientBits & TCanvas::kShowToolTips) != 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set window position of web canvas + +void TQt6Canvas::SetWindowPosition(Int_t x, Int_t y) +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set window size of web canvas + +void TQt6Canvas::SetWindowSize(UInt_t w, UInt_t h) +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set window title of web canvas + +void TQt6Canvas::SetWindowTitle(const char *newTitle) +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set canvas size of web canvas + +void TQt6Canvas::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; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Iconify browser window + +void TQt6Canvas::Iconify() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Raise browser window + +void TQt6Canvas::RaiseWindow() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Assign clients bits + +void TQt6Canvas::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); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Process TExec objects in the pad + +void TQt6Canvas::ProcessExecs(TPad *pad, TExec *extra) +{ + auto execs = pad ? pad->GetListOfExecs() : nullptr; + + if ((!execs || !execs->GetSize()) && !extra) + return; + + auto saveps = gVirtualPS; + gVirtualPS = nullptr; + + auto savex = gVirtualX; + TVirtualX x; + gVirtualX = &x; + + TIter next(execs); + while (auto obj = next()) { + auto exec = dynamic_cast(obj); + if (exec) + exec->Exec(); + } + + if (extra) + extra->Exec(); + + gVirtualPS = saveps; + gVirtualX = savex; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns window geometry including borders and menus + +UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) +{ + // x = Canvas()->fWindowTopX; + // y = Canvas()->fWindowTopY; + // w = Canvas()->fWindowWidth; + // h = Canvas()->fWindowHeight; + + return 0; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +/// if canvas or any subpad was modified, +/// scan all primitives in the TCanvas and subpads and convert them into +/// the structure which will be delivered to JSROOT client + +Bool_t TQt6Canvas::PerformUpdate(Bool_t async) +{ + return kTRUE; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Increment canvas version and force sending data to client - do not wait for reply + +void TQt6Canvas::ForceUpdate() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Static method to create TQt6Canvas instance +/// Used by plugin manager + +TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height) +{ + auto imp = new TQt6Canvas(c, name, x, y, width, height); + + // 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; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Create TCanvas and assign TQt6Canvas implementation to it +/// Canvas is not displayed automatically, therefore canv->Show() method must be called +/// Or canvas can be embed in other widgets. + +TCanvas *TQt6Canvas::CreateQt6Canvas(const char *name, const char *title, UInt_t width, UInt_t height) +{ + auto canvas = new TCanvas(kFALSE); + canvas->SetName(name); + canvas->SetTitle(title); + canvas->ResetBit(TCanvas::kShowEditor); + canvas->ResetBit(TCanvas::kShowToolBar); + canvas->SetBit(TCanvas::kMenuBar, kTRUE); + canvas->SetCanvas(canvas); + canvas->SetBatch(kTRUE); // mark canvas as batch + canvas->SetEditable(kTRUE); // ensure fPrimitives are created + + // copy gStyle attributes + canvas->SetFillColor(gStyle->GetCanvasColor()); + canvas->SetFillStyle(1001); + canvas->SetGrid(gStyle->GetPadGridX(),gStyle->GetPadGridY()); + canvas->SetTicks(gStyle->GetPadTickX(),gStyle->GetPadTickY()); + canvas->SetLogx(gStyle->GetOptLogx()); + canvas->SetLogy(gStyle->GetOptLogy()); + canvas->SetLogz(gStyle->GetOptLogz()); + canvas->SetBottomMargin(gStyle->GetPadBottomMargin()); + canvas->SetTopMargin(gStyle->GetPadTopMargin()); + canvas->SetLeftMargin(gStyle->GetPadLeftMargin()); + canvas->SetRightMargin(gStyle->GetPadRightMargin()); + canvas->SetBorderSize(gStyle->GetCanvasBorderSize()); + canvas->SetBorderMode(gStyle->GetCanvasBorderMode()); + + auto imp = static_cast (NewCanvas(canvas, name, 0, 0, width, height)); + + canvas->SetCanvasImp(imp); + + canvas->cd(); + + { + R__LOCKGUARD(gROOTMutex); + auto l1 = gROOT->GetListOfCleanups(); + if (!l1->FindObject(canvas)) + l1->Add(canvas); + auto l2 = gROOT->GetListOfCanvases(); + if (!l2->FindObject(canvas)) + l2->Add(canvas); + } + + // ensure creation of web window + // imp->CreateWebWindow(); + + return canvas; +} diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx new file mode 100644 index 0000000000000..22ac2dfd406c7 --- /dev/null +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -0,0 +1,183 @@ +// Author: Sergey Linev, GSI 26/06/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 "TQt6PadPainter.h" +#include "TError.h" +#include "TImage.h" +#include "TPad.h" + + +/** \class TQt6PadPainter + \ingroup qt6canvas + \brief Implement TVirtualPadPainter for Qt6 graphics +*/ + +////////////////////////////////////////////////////////////////////////// +/// Set opacity - similar to TVirtualPS usecase + +void TQt6PadPainter::SetOpacity(Int_t percent) +{ + fAttFill.SetFillStyle(4000 + percent); +} + +//////////////////////////////////////////////////////////////////////////////// +///Noop, for non-gl pad TASImage calls gVirtualX->CopyArea. + +void TQt6PadPainter::DrawPixels(const unsigned char * /*pixelData*/, UInt_t /*width*/, UInt_t /*height*/, + Int_t /*dstX*/, Int_t /*dstY*/, Bool_t /*enableAlphaBlending*/) +{ + +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Paint a simple line. + +void TQt6PadPainter::DrawLine(Double_t x1, Double_t y1, Double_t x2, Double_t y2) +{ + if (GetAttLine().GetLineWidth() <= 0) + return; + +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Paint a simple line in normalized coordinates. + +void TQt6PadPainter::DrawLineNDC(Double_t u1, Double_t v1, Double_t u2, Double_t v2) +{ + if (GetAttLine().GetLineWidth() <= 0) + return; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Paint a simple box. + +void TQt6PadPainter::DrawBox(Double_t x1, Double_t y1, Double_t x2, Double_t y2, EBoxMode mode) +{ + if (GetAttLine().GetLineWidth() <= 0 && mode == TVirtualPadPainter::kHollow) + return; + + // if (mode == TVirtualPadPainter::kHollow) + // draw only border + // else + // draw only fill + +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint filled area. + +void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Double_t *xs, const Double_t *ys) +{ + if ((GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint filled area. + +void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Float_t *xs, const Float_t *ys) +{ + if ((GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) + return; + +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint Polyline. + +void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Double_t *xs, const Double_t *ys) +{ + if ((GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polyline. + +void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Float_t *xs, const Float_t *ys) +{ + if ((GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polyline in normalized coordinates. + +void TQt6PadPainter::DrawPolyLineNDC(Int_t nPoints, const Double_t *u, const Double_t *v) +{ + if ((GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polymarker. + +void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Double_t *x, const Double_t *y) +{ + if (nPoints < 1) + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polymarker. + +void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float_t *y) +{ + if (nPoints < 1) + return; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text. + +void TQt6PadPainter::DrawText(Double_t x, Double_t y, const char *text, ETextMode /*mode*/) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text with url + +void TQt6PadPainter::DrawTextUrl(Double_t x, Double_t y, const char *text, const char * /* url */) +{ +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Special version working with wchar_t and required by TMathText. + +void TQt6PadPainter::DrawText(Double_t x, Double_t y, const wchar_t * /*text*/, ETextMode /*mode*/) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text in normalized coordinates. + +void TQt6PadPainter::DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode /*mode*/) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text in normalized coordinates. + +void TQt6PadPainter::DrawTextNDC(Double_t u , Double_t v, const wchar_t * /*text*/, ETextMode /*mode*/) +{ +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Produce image + +void TQt6PadPainter::SaveImage(TVirtualPad *pad, const char *fileName, Int_t /* gtype */) const +{ +} + From df878016ba9e87825335b28904d1c7d56c02b1f3 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 11:27:38 +0200 Subject: [PATCH 03/20] [qt6canv] add plugin fot TQt6Canvas --- etc/plugins/TCanvasImp/P020_TQt6Canvas.C | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 etc/plugins/TCanvasImp/P020_TQt6Canvas.C diff --git a/etc/plugins/TCanvasImp/P020_TQt6Canvas.C b/etc/plugins/TCanvasImp/P020_TQt6Canvas.C new file mode 100644 index 0000000000000..8acb2d102f74f --- /dev/null +++ b/etc/plugins/TCanvasImp/P020_TQt6Canvas.C @@ -0,0 +1,5 @@ +void P020_TQt6Canvas() +{ + gPluginMgr->AddHandler("TCanvasImp", "TQt6Canvas", "TQt6Canvas", + "ROOTQt6Canvas", "NewCanvas(TCanvas *, const char *, Int_t, Int_t, UInt_t, UInt_t)"); +} From 2713e9abd60cd31390e22d3823bcd611e00fa9a4 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 11:29:20 +0200 Subject: [PATCH 04/20] [gui] allow to create custom canvas implementation Up to now only TRootCanvas and TWebCanvas are supported - all other classes were ignored. Now try to load plugin when something different from TRootCanvas is specified. Also in batch mode use of external canvas should be possible --- core/gui/src/TGuiFactory.cxx | 28 ++++++++++++++++++++++++---- gui/gui/src/TRootGuiFactory.cxx | 14 ++++++++------ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/core/gui/src/TGuiFactory.cxx b/core/gui/src/TGuiFactory.cxx index 3c192df533f35..cacc618583bdc 100644 --- a/core/gui/src/TGuiFactory.cxx +++ b/core/gui/src/TGuiFactory.cxx @@ -56,8 +56,18 @@ TApplicationImp *TGuiFactory::CreateApplicationImp(const char *classname, int *a TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t width, UInt_t height) { + TString canvName; if (gROOT->IsWebDisplay()) { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + canvName = "TWebCanvas"; + } else { + canvName = gEnv->GetValue("Canvas.Name", ""); + // in batch web display should be configured explicitely + if (canvName == "TWebCanvas") + canvName.Clear(); + } + + if (!canvName.IsNull() && (canvName != "TRootCanvas") && (canvName != "TCanvasImp")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { auto imp = (TCanvasImp *)ph->ExecPlugin(6, c, title, 0, 0, width, height); @@ -65,7 +75,7 @@ TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t w return imp; } - Error("CreateCanvasImp", "Fail to create TWebCanvas, please provide missing libWebGui6 or run 'root --web=off'"); + Error("CreateCanvasImp", "Fail to create %s canvas implementation", canvName.Data()); } return new TCanvasImp(c, title, width, height); @@ -76,8 +86,18 @@ TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t w TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, Int_t x, Int_t y, UInt_t width, UInt_t height) { + TString canvName; if (gROOT->IsWebDisplay()) { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + canvName = "TWebCanvas"; + } else { + canvName = gEnv->GetValue("Canvas.Name", ""); + // in batch web display should be configured explicitely + if (canvName == "TWebCanvas") + canvName.Clear(); + } + + if (!canvName.IsNull() && (canvName != "TRootCanvas") && (canvName != "TCanvasImp")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { auto imp = (TCanvasImp *)ph->ExecPlugin(6, c, title, x, y, width, height); @@ -85,7 +105,7 @@ TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, Int_t x, return imp; } - Error("CreateCanvasImp", "Fail to create TWebCanvas, please provide missing libWebGui6 or run 'root --web=off'"); + Error("CreateCanvasImp", "Fail to create %s canvas implementation", canvName.Data()); } return new TCanvasImp(c, title, x, y, width, height); diff --git a/gui/gui/src/TRootGuiFactory.cxx b/gui/gui/src/TRootGuiFactory.cxx index 410ff3d28416c..bff81c12e5768 100644 --- a/gui/gui/src/TRootGuiFactory.cxx +++ b/gui/gui/src/TRootGuiFactory.cxx @@ -78,11 +78,12 @@ TCanvasImp *TRootGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t width, UInt_t height) { TString canvName = gEnv->GetValue("Canvas.Name", "TWebCanvas"); - if (canvName == "TWebCanvas") { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + if (!canvName.IsNull() && (canvName != "TRootCanvas")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { - ShowWebCanvasWarning(); + if (canvName == "TWebCanvas") + ShowWebCanvasWarning(); auto imp = (TCanvasImp *) ph->ExecPlugin(6, c, title, 0, 0, width, height); if (imp) return imp; } @@ -98,11 +99,12 @@ TCanvasImp *TRootGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, Int_t x, Int_t y, UInt_t width, UInt_t height) { TString canvName = gEnv->GetValue("Canvas.Name", "TWebCanvas"); - if (canvName == "TWebCanvas") { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + if (!canvName.IsNull() && (canvName != "TRootCanvas")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { - ShowWebCanvasWarning(); + if (canvName == "TWebCanvas") + ShowWebCanvasWarning(); auto imp = (TCanvasImp *) ph->ExecPlugin(6, c, title, x, y, width, height); if (imp) return imp; } From 85e1befc166785500768b17d9fdd66f78c49f892 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 13:29:23 +0200 Subject: [PATCH 05/20] [qt6canv] introduce QCanvasWidget It implements Qt6 widget with menu, status line and canvas paint area. Details to be implemented --- gui/qt6canvas/CMakeLists.txt | 8 +- gui/qt6canvas/inc/TQt6Canvas.h | 8 +- gui/qt6canvas/inc/TQt6PadPainter.h | 1 + gui/qt6canvas/src/QCanvasWidget.cpp | 97 +++++++++++++++++++ gui/qt6canvas/src/QCanvasWidget.h | 45 +++++++++ gui/qt6canvas/src/QCanvasWidget.ui | 139 ++++++++++++++++++++++++++++ gui/qt6canvas/src/TQt6Canvas.cxx | 56 ++++++++++- 7 files changed, 345 insertions(+), 9 deletions(-) create mode 100644 gui/qt6canvas/src/QCanvasWidget.cpp create mode 100644 gui/qt6canvas/src/QCanvasWidget.h create mode 100644 gui/qt6canvas/src/QCanvasWidget.ui diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index 8446019ad3a6f..63edcf1267d21 100644 --- a/gui/qt6canvas/CMakeLists.txt +++ b/gui/qt6canvas/CMakeLists.txt @@ -8,7 +8,7 @@ # CMakeLists.txt file for building ROOT gui/qt6canvas package ############################################################################ -find_package(Qt6 COMPONENTS Core Gui CONFIG) +find_package(Qt6 COMPONENTS Core Widgets CONFIG) if(NOT Qt6_FOUND) if(fail-on-missing) @@ -20,7 +20,10 @@ if(NOT Qt6_FOUND) endif() endif() + set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas @@ -28,10 +31,11 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas TQt6Canvas.h TQt6PadPainter.h SOURCES + src/QCanvasWidget.cpp src/TQt6Canvas.cxx src/TQt6PadPainter.cxx LIBRARIES - Qt6::Gui + Qt6::Widgets DEPENDENCIES Core Gpad diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index c4d26986a1d6a..84314bdb8d69f 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -13,15 +13,16 @@ #include "TCanvasImp.h" -class TQt6CanvasTimer; class TPad; class TExec; +class QWidget; class TQt6Canvas : public TCanvasImp { protected: - TQt6CanvasTimer *fTimer = nullptr; /// +#include + +QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent) +{ + setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + setObjectName(name); + + + fMenuBar = new QMenuBar(fMenuFrame); + fMenuBar->setMinimumWidth(50); + fMenuBar->setNativeMenuBar(kFALSE); // disable putting this to screen menu. for MAC style WMs + + QMenu* fileMenu = fMenuBar->addMenu("F&ile"); + fileMenu->addAction("&New canvas", this, &QCanvasWidget::NewCanvas); + fileMenu->addAction("Open ...", this, &QCanvasWidget::OpenRootFile); + fileMenu->addAction("Cl&ose canvas", this, &QCanvasWidget::CloseCanvas); + + fileMenu->addSeparator(); + + fileMenu->addAction("Save as...", this, &QCanvasWidget::SaveCanvasAs); + + fileMenu->addSeparator(); + + fileMenu->addAction("Print...", this, &QCanvasWidget::PrintCanvas); + fileMenu->addSeparator(); + + fileMenu->addAction("Quit ROOT", this, &QCanvasWidget::QuitRoot); + + + fMenuBar->addMenu("&Edit"); + + fMenuBar->addMenu("&View"); + + fMenuBar->addMenu("&Options"); + + fMenuBar->addMenu("&Tools"); + + QWidget *spacer = new QWidget(this); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + fMenuBar->setCornerWidget(spacer, Qt::TopRightCorner); + + fMenuBar->addMenu("&Help"); + +} + +QCanvasWidget::~QCanvasWidget() {} + + +void QCanvasWidget::NewCanvas() +{ + +} + +void QCanvasWidget::OpenRootFile() +{ + +} + +void QCanvasWidget::CloseCanvas() +{ + close(); +} + +void QCanvasWidget::SaveCanvasAs() +{ + +} + +void QCanvasWidget::PrintCanvas() +{ + +} + +void QCanvasWidget::QuitRoot() +{ +} + + diff --git a/gui/qt6canvas/src/QCanvasWidget.h b/gui/qt6canvas/src/QCanvasWidget.h new file mode 100644 index 0000000000000..cffb862f3bac0 --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.h @@ -0,0 +1,45 @@ +// Author: Sergey Linev, GSI 26/06/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_QCanvasWidget_h +#define ROOT_QCanvasWidget_h + +#include +#include +#include "ui_QCanvasWidget.h" + + +class TH1F; +class TH2I; + +class QCanvasWidget : public QWidget, public Ui::QCanvasWidget { + Q_OBJECT + + QMenuBar *fMenuBar = nullptr; + +public: + QCanvasWidget(QWidget *parent = nullptr, const char *name = nullptr); + + virtual ~QCanvasWidget(); + + QWidget *GetCanvasWidget() const { return fCanvasWidget; } + +public slots: + + void NewCanvas(); + void OpenRootFile(); + void CloseCanvas(); + + void SaveCanvasAs(); + void PrintCanvas(); + void QuitRoot(); +}; + +#endif diff --git a/gui/qt6canvas/src/QCanvasWidget.ui b/gui/qt6canvas/src/QCanvasWidget.ui new file mode 100644 index 0000000000000..11a26143b71df --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.ui @@ -0,0 +1,139 @@ + + Sergey Linev + + + QCanvasWidget + + + + 0 + 0 + 682 + 559 + + + + QtWeb ROOT example + + + + + + + 7 + 1 + 0 + 0 + + + + + 0 + 30 + + + + NoFrame + + + Plain + + + + + + + + 7 + 7 + 0 + 20 + + + + + 50 + 50 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 7 + 1 + 0 + 0 + + + + + 0 + 10 + + + + + 1 + + + 2 + + + 2 + + + + + Label 1 + + + false + + + + + + + Label 2 + + + false + + + + + + + Label 3 + + + false + + + + + + + Label 4 + + + false + + + + + + + + + + diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 90a63c578c7b1..4416a6927e536 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -14,9 +14,11 @@ #include "TSystem.h" #include "TStyle.h" +#include "TError.h" #include "TCanvas.h" #include "TThread.h" #include "TROOT.h" +#include "TApplication.h" #include "TVirtualX.h" #include "TVirtualPS.h" #include "TClass.h" @@ -30,16 +32,20 @@ #include #include +#include +#include "QCanvasWidget.h" class TQt6CanvasTimer : public TTimer { - TQt6Canvas &fCanv; public: - TQt6CanvasTimer(TQt6Canvas &canv) : TTimer(10, kTRUE), fCanv(canv) {} + TQt6CanvasTimer(Long_t milliSec, Bool_t mode) : + TTimer(milliSec, mode) {} /// used to send control messages to clients void Timeout() override { + QApplication::sendPostedEvents(); + QApplication::processEvents(); } }; @@ -65,9 +71,9 @@ TQt6Canvas::TQt6Canvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t wi // This resolves issue https://github.com/root-project/root/issues/15498 TThread::SelfId(); - fTimer = new TQt6CanvasTimer(*this); + // fTimer = new TQt6CanvasTimer(*this); - fTimer->TurnOn(); + // fTimer->TurnOn(); // fAsyncMode = kTRUE; } @@ -78,7 +84,7 @@ TQt6Canvas::TQt6Canvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t wi TQt6Canvas::~TQt6Canvas() { - delete fTimer; + // delete fTimer; } @@ -240,6 +246,14 @@ void TQt6Canvas::ProcessExecs(TPad *pad, TExec *extra) } +void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) +{ + (void) wid; + w = 800; + h = 600; +} + + ////////////////////////////////////////////////////////////////////////////////////////// /// Returns window geometry including borders and menus @@ -277,8 +291,40 @@ void TQt6Canvas::ForceUpdate() TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height) { + static QApplication *qapp = nullptr; + static int qargc = 1; + static char *qargv[2]; + + if (!qapp && !QApplication::instance()) { + + if (!gApplication) { + ::Error("TQt6Canvas::NewCanvas", "Not found gApplication to create QApplication"); + return nullptr; + } + + qargv[0] = gApplication->Argv(0); + qargv[1] = nullptr; + + qapp = new QApplication(qargc, qargv); + } + + static TQt6CanvasTimer *timer = nullptr; + + if (!timer) { + timer = new TQt6CanvasTimer(10, kTRUE); + timer->TurnOn(); + } + + + auto widget = new QCanvasWidget(); + widget->setWindowTitle(QString("QtWeb application, build with qt ") + QT_VERSION_STR); + widget->show(); + auto imp = new TQt6Canvas(c, name, x, y, width, height); + imp->fWidget = widget->GetCanvasWidget(); + + // c->fWindowTopX = x; // c->fWindowTopY = y; // c->fWindowWidth = width; From a535bbe0550090bf596edc999a0d27efd3b312c4 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 16:00:25 +0200 Subject: [PATCH 06/20] [qt6canv] instroduce special widget for paint area --- gui/qt6canvas/inc/TQt6Canvas.h | 3 +++ gui/qt6canvas/src/QCanvasWidget.cpp | 7 +++++++ gui/qt6canvas/src/TQt6Canvas.cxx | 20 ++++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index 84314bdb8d69f..2ee8016bc3c19 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -16,11 +16,13 @@ class TPad; class TExec; class QWidget; +class QCanvasWidget; class TQt6Canvas : public TCanvasImp { protected: + QCanvasWidget *fCanvasWidget = nullptr; QWidget *fWidget = nullptr; Bool_t fFixedSize = kFALSE; /// #include @@ -92,6 +94,11 @@ void QCanvasWidget::PrintCanvas() void QCanvasWidget::QuitRoot() { + // set flag which sometimes checked in TSystem::ProcessEvents + gROOT->SetInterrupt(kTRUE); + + if (gApplication) + TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()"); } diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 4416a6927e536..eda6d5ec8a49a 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -249,8 +249,13 @@ void TQt6Canvas::ProcessExecs(TPad *pad, TExec *extra) void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) { (void) wid; - w = 800; - h = 600; + if (fWidget) { + w = fWidget->width(); + w = fWidget->height(); + } else { + w = 780; + h = 580; + } } @@ -259,6 +264,16 @@ void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) { + x = 0; + y = 0; + if (fCanvasWidget) { + w = fCanvasWidget->width(); + h = fCanvasWidget->height(); + } else { + w = 800; + h = 600; + } + // x = Canvas()->fWindowTopX; // y = Canvas()->fWindowTopY; // w = Canvas()->fWindowWidth; @@ -322,6 +337,7 @@ TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y auto imp = new TQt6Canvas(c, name, x, y, width, height); + imp->fCanvasWidget = widget; imp->fWidget = widget->GetCanvasWidget(); From 61182944fd94977c1af533efecb74ec967abd29a Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 11:42:24 +0200 Subject: [PATCH 07/20] [qt6canv] introduce QPaintWidget It is specialized QWidget which will handle paint area for the canvas. Start with resize handler to track changes in canvas width and height. --- gui/qt6canvas/CMakeLists.txt | 3 ++ gui/qt6canvas/inc/TQt6Canvas.h | 6 ++-- gui/qt6canvas/inc/TQt6PadPainter.h | 7 ++++- gui/qt6canvas/src/QCanvasWidget.h | 2 +- gui/qt6canvas/src/QCanvasWidget.ui | 10 ++++++- gui/qt6canvas/src/QPaintWidget.cpp | 43 ++++++++++++++++++++++++++++ gui/qt6canvas/src/QPaintWidget.h | 39 +++++++++++++++++++++++++ gui/qt6canvas/src/TQt6Canvas.cxx | 28 +++++++++++------- gui/qt6canvas/src/TQt6PadPainter.cxx | 3 ++ 9 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 gui/qt6canvas/src/QPaintWidget.cpp create mode 100644 gui/qt6canvas/src/QPaintWidget.h diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index 63edcf1267d21..11bd68eb087e1 100644 --- a/gui/qt6canvas/CMakeLists.txt +++ b/gui/qt6canvas/CMakeLists.txt @@ -32,6 +32,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas TQt6PadPainter.h SOURCES src/QCanvasWidget.cpp + src/QPaintWidget.cpp src/TQt6Canvas.cxx src/TQt6PadPainter.cxx LIBRARIES @@ -40,3 +41,5 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas Core Gpad ) + +target_include_directories(ROOTQt6Canvas PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) \ No newline at end of file diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index 2ee8016bc3c19..371e8eadd47d0 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -17,13 +17,14 @@ class TPad; class TExec; class QWidget; class QCanvasWidget; +class QPaintWidget; class TQt6Canvas : public TCanvasImp { protected: QCanvasWidget *fCanvasWidget = nullptr; - QWidget *fWidget = nullptr; + QPaintWidget *fPaintWidget = nullptr; Bool_t fFixedSize = kFALSE; /// - + 7 @@ -136,4 +136,12 @@ + + + QPaintWidget + QWidget +
QPaintWidget.h
+ 0 +
+
diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp new file mode 100644 index 0000000000000..0628c38b70c7b --- /dev/null +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -0,0 +1,43 @@ +// Author: Sergey Linev, GSI 29/06/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 "QPaintWidget.h" + +#include "TCanvas.h" + +QPaintWidget::QPaintWidget(QWidget *parent) : QWidget(parent) +{ + setObjectName("QPaintWidget"); + + setSizeIncrement(QSize(100, 100)); + + setUpdatesEnabled(true); + setMouseTracking(true); + + setFocusPolicy(Qt::TabFocus); + setCursor(Qt::CrossCursor); + + setAcceptDrops(true); + + fCanvas = nullptr; +} + +QPaintWidget::~QPaintWidget() +{ +} + +void QPaintWidget::resizeEvent(QResizeEvent *) +{ + if (fCanvas) { + fCanvas->Resize(); + fCanvas->Modified(); + } +} + diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h new file mode 100644 index 0000000000000..84feb3d9a70cd --- /dev/null +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -0,0 +1,39 @@ +// Author: Sergey Linev, GSI 29/06/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 QPaintWidget_H +#define QPaintWidget_H + +#include + +class TCanvas; +class TPad; +class TObject; + +class QPaintWidget : public QWidget { + + Q_OBJECT + +public: + QPaintWidget(QWidget *parent = nullptr); + virtual ~QPaintWidget(); + + /// returns canvas shown in the widget + TCanvas *getCanvas() { return fCanvas; } + + void SetCanvas(TCanvas *canv) { fCanvas = canv; } + +protected: + void resizeEvent(QResizeEvent *event) override; + + TCanvas *fCanvas = nullptr; +}; + +#endif diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index eda6d5ec8a49a..adeb9d15a87f9 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -101,6 +101,7 @@ Int_t TQt6Canvas::InitWindow() TVirtualPadPainter *TQt6Canvas::CreatePadPainter() { + printf("Create pad painter\n"); return new TQt6PadPainter(); } @@ -249,9 +250,9 @@ void TQt6Canvas::ProcessExecs(TPad *pad, TExec *extra) void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) { (void) wid; - if (fWidget) { - w = fWidget->width(); - w = fWidget->height(); + if (fPaintWidget) { + w = fPaintWidget->width(); + h = fPaintWidget->height(); } else { w = 780; h = 580; @@ -264,12 +265,14 @@ void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) { - x = 0; - y = 0; if (fCanvasWidget) { + auto pos = fCanvasWidget->pos(); + x = pos.x(); + y = pos.y(); w = fCanvasWidget->width(); h = fCanvasWidget->height(); } else { + x = y = 0; w = 800; h = 600; } @@ -290,7 +293,7 @@ UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) Bool_t TQt6Canvas::PerformUpdate(Bool_t async) { - return kTRUE; + return kFALSE; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -332,21 +335,26 @@ TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y auto widget = new QCanvasWidget(); - widget->setWindowTitle(QString("QtWeb application, build with qt ") + QT_VERSION_STR); + widget->setWindowTitle(QString(c->GetTitle())); + widget->setGeometry(x, y, width, height); widget->show(); auto imp = new TQt6Canvas(c, name, x, y, width, height); imp->fCanvasWidget = widget; - imp->fWidget = widget->GetCanvasWidget(); + imp->fPaintWidget = widget->GetPaintWidget(); + imp->fPaintWidget->SetCanvas(c); + + // set all internal dimensions + c->Resize(); // c->fWindowTopX = x; // c->fWindowTopY = y; // c->fWindowWidth = width; // c->fWindowHeight = height; - if (!gROOT->IsBatch() && (height > 25)) - height -= 25; + // if (!gROOT->IsBatch() && (height > 25)) + // height -= 25; // c->fCw = width; // c->fCh = height; diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 22ac2dfd406c7..6ff67f2821f58 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -142,6 +142,7 @@ void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float void TQt6PadPainter::DrawText(Double_t x, Double_t y, const char *text, ETextMode /*mode*/) { + printf("DrawText %5.3f %5.3f %s\n", x, y, text); } //////////////////////////////////////////////////////////////////////////////// @@ -164,6 +165,8 @@ void TQt6PadPainter::DrawText(Double_t x, Double_t y, const wchar_t * /*text*/, void TQt6PadPainter::DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode /*mode*/) { + printf("DrawTextNDC %5.3f %5.3f %s\n", u, v, text); + } //////////////////////////////////////////////////////////////////////////////// From 3c37be838d61063a1c441c7a91f7caf0ccfa33a2 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 14:06:03 +0200 Subject: [PATCH 08/20] [qt6canv] use paintEvent for canvas paint In the Qt one cannot start painting on the canvas in the arbitrary moment - one only can do this inside paintEvent() handler. Therefore only there call pad->Paint method which will invoke correct painting inside Because of this canvas->Update() does not call directly Paint() but uses QWidget::update() which invalidates paint area and invokes paintEvent then. Start implementation of TQt6PadPainter with text drawing. One can use TTF directly with Qt without need to extra handle of fonts ourselfs. Handle color and rotation as well --- gui/qt6canvas/inc/TQt6PadPainter.h | 12 +- gui/qt6canvas/src/QPaintWidget.cpp | 24 ++++ gui/qt6canvas/src/QPaintWidget.h | 7 ++ gui/qt6canvas/src/TQt6Canvas.cxx | 9 +- gui/qt6canvas/src/TQt6PadPainter.cxx | 171 ++++++++++++++++++++++++++- 5 files changed, 214 insertions(+), 9 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index 25fe03423595d..6193b529284e1 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -13,8 +13,11 @@ #include "TPadPainterBase.h" +#include +#include class TQt6Canvas; +class QPaintWidget; class TQt6PadPainter : public TPadPainterBase { @@ -22,9 +25,16 @@ friend class TQt6Canvas; protected: + QPaintWidget *fPaintWidget = nullptr; + + void PaintQString(int x, int y, const QString &s); + + static QString GetFontFamily(Font_t id); + static QColor GetQColor(Color_t id); + public: - TQt6PadPainter() {} // NOLINT: not allowed to use = default because of TObject::kIsOnHeap detection, see ROOT-10300 + TQt6PadPainter(QPaintWidget *widget = nullptr) { fPaintWidget = widget; } Bool_t HasTTFonts() const override { return kTRUE; } diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp index 0628c38b70c7b..edb96a6d918cc 100644 --- a/gui/qt6canvas/src/QPaintWidget.cpp +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -12,6 +12,11 @@ #include "TCanvas.h" +#include +#include +#include + + QPaintWidget::QPaintWidget(QWidget *parent) : QWidget(parent) { setObjectName("QPaintWidget"); @@ -35,9 +40,28 @@ QPaintWidget::~QPaintWidget() void QPaintWidget::resizeEvent(QResizeEvent *) { + printf("Call resize event\n"); if (fCanvas) { + fCanvas->Resize(); fCanvas->Modified(); } } +void QPaintWidget::paintEvent(QPaintEvent *) +{ + printf("Call paint event\n"); + + try { + QPainter painter(this); + + fPainter = &painter; + + fCanvas->Paint(); + + fPainter = nullptr; + + } catch(...) { + fPainter = nullptr; + } +} diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h index 84feb3d9a70cd..d28d78227250c 100644 --- a/gui/qt6canvas/src/QPaintWidget.h +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -16,6 +16,7 @@ class TCanvas; class TPad; class TObject; +class QPainter; class QPaintWidget : public QWidget { @@ -28,12 +29,18 @@ class QPaintWidget : public QWidget { /// returns canvas shown in the widget TCanvas *getCanvas() { return fCanvas; } + QPainter *getPainter() const { return fPainter; } + void SetCanvas(TCanvas *canv) { fCanvas = canv; } protected: void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + TCanvas *fCanvas = nullptr; + + QPainter *fPainter = nullptr; }; #endif diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index adeb9d15a87f9..91def06d39da3 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -102,7 +102,7 @@ Int_t TQt6Canvas::InitWindow() TVirtualPadPainter *TQt6Canvas::CreatePadPainter() { printf("Create pad painter\n"); - return new TQt6PadPainter(); + return new TQt6PadPainter(fPaintWidget); } @@ -288,12 +288,13 @@ UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) ////////////////////////////////////////////////////////////////////////////////////////// /// if canvas or any subpad was modified, -/// scan all primitives in the TCanvas and subpads and convert them into -/// the structure which will be delivered to JSROOT client +/// invoke Qt update() which will redraw area Bool_t TQt6Canvas::PerformUpdate(Bool_t async) { - return kFALSE; + if (Canvas()->IsModified()) + fPaintWidget->update(); + return kTRUE; } ////////////////////////////////////////////////////////////////////////////////////////// diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 6ff67f2821f58..b43c0b00ae9de 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -10,9 +10,19 @@ #include "TQt6PadPainter.h" #include "TError.h" +#include "TSystem.h" +#include "TEnv.h" #include "TImage.h" #include "TPad.h" +#include "TROOT.h" +#include "TColor.h" +#include "QPaintWidget.h" + +#include +#include +#include +#include /** \class TQt6PadPainter \ingroup qt6canvas @@ -142,7 +152,10 @@ void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float void TQt6PadPainter::DrawText(Double_t x, Double_t y, const char *text, ETextMode /*mode*/) { - printf("DrawText %5.3f %5.3f %s\n", x, y, text); + const Int_t px = gPad->XtoAbsPixel(x); + const Int_t py = gPad->YtoAbsPixel(y); + + PaintQString(px, py, text); } //////////////////////////////////////////////////////////////////////////////// @@ -150,14 +163,22 @@ void TQt6PadPainter::DrawText(Double_t x, Double_t y, const char *text, ETextMod void TQt6PadPainter::DrawTextUrl(Double_t x, Double_t y, const char *text, const char * /* url */) { + const Int_t px = gPad->XtoAbsPixel(x); + const Int_t py = gPad->YtoAbsPixel(y); + + PaintQString(px, py, text); } //////////////////////////////////////////////////////////////////////////////// /// Special version working with wchar_t and required by TMathText. -void TQt6PadPainter::DrawText(Double_t x, Double_t y, const wchar_t * /*text*/, ETextMode /*mode*/) +void TQt6PadPainter::DrawText(Double_t x, Double_t y, const wchar_t *text, ETextMode /*mode*/) { + const Int_t px = gPad->XtoAbsPixel(x); + const Int_t py = gPad->YtoAbsPixel(y); + + PaintQString(px, py, QString::fromWCharArray(text)); } //////////////////////////////////////////////////////////////////////////////// @@ -165,15 +186,21 @@ void TQt6PadPainter::DrawText(Double_t x, Double_t y, const wchar_t * /*text*/, void TQt6PadPainter::DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode /*mode*/) { - printf("DrawTextNDC %5.3f %5.3f %s\n", u, v, text); + const Int_t px = gPad->UtoAbsPixel(u); + const Int_t py = gPad->VtoAbsPixel(v); + PaintQString(px, py, text); } //////////////////////////////////////////////////////////////////////////////// /// Paint text in normalized coordinates. -void TQt6PadPainter::DrawTextNDC(Double_t u , Double_t v, const wchar_t * /*text*/, ETextMode /*mode*/) +void TQt6PadPainter::DrawTextNDC(Double_t u, Double_t v, const wchar_t *text, ETextMode /*mode*/) { + const Int_t px = gPad->UtoAbsPixel(u); + const Int_t py = gPad->VtoAbsPixel(v); + + PaintQString(px, py, QString::fromWCharArray(text)); } @@ -184,3 +211,139 @@ void TQt6PadPainter::SaveImage(TVirtualPad *pad, const char *fileName, Int_t /* { } +//////////////////////////////////////////////////////////////////////////////// +/// Return QColor created from specified TColor + +QColor TQt6PadPainter::GetQColor(Color_t id) +{ + auto c = gROOT->GetColor(id); + if (c) + return QColor((int)(c->GetRed() * 255), (int)(c->GetGreen() * 255), (int)(c->GetBlue() * 255), (int)(c->GetAlpha() * 255)); + return QColor(0, 0, 0); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return font family for specified ROOT font id +/// If necessary, register TTF font to Qt first + +QString TQt6PadPainter::GetFontFamily(Font_t fontnumber) +{ + // TODO: make special generic method, used from several places + static const char *fonttable[][2] = { + { "Root.TTFont.0", "FreeSansBold.otf" }, + { "Root.TTFont.1", "FreeSerifItalic.otf" }, + { "Root.TTFont.2", "FreeSerifBold.otf" }, + { "Root.TTFont.3", "FreeSerifBoldItalic.otf" }, + { "Root.TTFont.4", "texgyreheros-regular.otf" }, + { "Root.TTFont.5", "texgyreheros-italic.otf" }, + { "Root.TTFont.6", "texgyreheros-bold.otf" }, + { "Root.TTFont.7", "texgyreheros-bolditalic.otf" }, + { "Root.TTFont.8", "FreeMono.otf" }, + { "Root.TTFont.9", "FreeMonoOblique.otf" }, + { "Root.TTFont.10", "FreeMonoBold.otf" }, + { "Root.TTFont.11", "FreeMonoBoldOblique.otf" }, + { "Root.TTFont.12", "symbol.ttf" }, + { "Root.TTFont.13", "FreeSerif.otf" }, + { "Root.TTFont.14", "wingding.ttf" }, + { "Root.TTFont.15", "symbol.ttf" }, + { "Root.TTFont.STIXGen", "STIXGeneral.otf" }, + { "Root.TTFont.STIXGenIt", "STIXGeneralItalic.otf" }, + { "Root.TTFont.STIXGenBd", "STIXGeneralBol.otf" }, + { "Root.TTFont.STIXGenBdIt", "STIXGeneralBolIta.otf" }, + { "Root.TTFont.STIXSiz1Sym", "STIXSiz1Sym.otf" }, + { "Root.TTFont.STIXSiz1SymBd", "STIXSiz1SymBol.otf" }, + { "Root.TTFont.STIXSiz2Sym", "STIXSiz2Sym.otf" }, + { "Root.TTFont.STIXSiz2SymBd", "STIXSiz2SymBol.otf" }, + { "Root.TTFont.STIXSiz3Sym", "STIXSiz3Sym.otf" }, + { "Root.TTFont.STIXSiz3SymBd", "STIXSiz3SymBol.otf" }, + { "Root.TTFont.STIXSiz4Sym", "STIXSiz4Sym.otf" }, + { "Root.TTFont.STIXSiz4SymBd", "STIXSiz4SymBol.otf" }, + { "Root.TTFont.STIXSiz5Sym", "STIXSiz5Sym.otf" }, + { "Root.TTFont.ME", "DroidSansFallback.ttf" }, + { "Root.TTFont.CJKMing", "DroidSansFallback.ttf" }, + { "Root.TTFont.CJKGothic", "DroidSansFallback.ttf" } + }; + + int fontid = fontnumber / 10; + if (fontid < 0 || fontid > 31) + fontid = 0; + + static std::map registeredFonts; + + auto iter = registeredFonts.find(fontid); + if (iter != registeredFonts.end()) + return iter->second; + + const char *ttpath = gEnv->GetValue("Root.TTFontPath", + TROOT::GetTTFFontDir()); + + TString fname = gEnv->GetValue(fonttable[fontid][0], fonttable[fontid][1]); + + const char *ttfont = gSystem->FindFile(ttpath, fname, kReadPermission); + + if (!ttfont) { + ::Error("TQt6PadPainter::GetFontFamily", "Not found font %s in configured path %s", fname.Data(), ttpath); + return ""; + } + + int qtId = QFontDatabase::addApplicationFont(ttfont); + if (qtId == -1) { + ::Error("TQt6PadPainter::GetFontFamily", "No able to add font %s to QFontDataBase", ttfont); + return ""; + } + + QString fontFamily = QFontDatabase::applicationFontFamilies(qtId).at(0); + + registeredFonts[fontid] = fontFamily; + + return fontFamily; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Actual text painting image + +void TQt6PadPainter::PaintQString(int x, int y, const QString &s) +{ + auto painter = fPaintWidget->getPainter(); + if (!painter) + return; + + const TAttText &att = GetAttText(); + auto family = GetFontFamily(att.GetTextFont()); + if (family.isEmpty()) + return; + + painter->setFont(QFont(family, att.GetTextSizePixels(*gPad))); + + painter->setPen(GetQColor(att.GetTextColor())); + + Int_t txalh = att.GetTextAlign() / 10; + Int_t txalv = att.GetTextAlign() % 10; + + auto fm = painter->fontMetrics(); + + switch (txalh) { + case 0: + case 1: break; //left + case 2: x -= fm.horizontalAdvance(s) / 2; break; //center + case 3: x -= fm.horizontalAdvance(s); break; //right + } + + switch (txalv) { + case 1: break; //bottom + case 2: y += fm.height() / 2; break; // middle + case 3: y += fm.height(); break; //top + } + + if (att.GetTextAngle() == 0) { + // Just draw text + painter->drawText(x, y, s); + } else { + // Draw with rotation + painter->save(); + painter->translate(x, y); + painter->rotate(-att.GetTextAngle()); + painter->drawText(0, 0, s); + painter->restore(); + } +} From e0e96f137f4505482a5ff31b16f25e3abe2ef8c1 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 15:43:12 +0200 Subject: [PATCH 09/20] [qt6canv] implement text metrics with QFont While text painting done with QFont, also text metrics implement with QFont functionality --- gui/qt6canvas/inc/TQt6PadPainter.h | 6 +++ gui/qt6canvas/src/TQt6PadPainter.cxx | 78 +++++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index 6193b529284e1..463a88a8eced2 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -84,6 +84,12 @@ friend class TQt6Canvas; void DrawTextUrl(Double_t x, Double_t y, const char *text, const char *url) override; + void GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const char *mess) override; + void GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const wchar_t *mess) override; + void GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, UInt_t &d, const char *mess) override; + void GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, UInt_t &d, const wchar_t *mess) override; + UInt_t GetTextAdvance(Font_t font, Double_t size, const char *text, Bool_t kern) override; + Bool_t IsSupportAlpha() const override { return kTRUE; } private: diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index b43c0b00ae9de..71d5bb8c45c18 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -169,7 +169,6 @@ void TQt6PadPainter::DrawTextUrl(Double_t x, Double_t y, const char *text, const PaintQString(px, py, text); } - //////////////////////////////////////////////////////////////////////////////// /// Special version working with wchar_t and required by TMathText. @@ -347,3 +346,80 @@ void TQt6PadPainter::PaintQString(int x, int y, const QString &s) painter->restore(); } } + +//////////////////////////////////////////////////////////////////////////////// +/// Returns text extent + +void TQt6PadPainter::GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const char *mess) +{ + auto family = GetFontFamily(font); + if (family.isEmpty()) + return; + + QFontMetrics fm(QFont(family, size)); + QRect rect = fm.boundingRect(mess); + + w = rect.width(); + h = rect.height(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns text extent + +void TQt6PadPainter::GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t &h, const wchar_t *mess) +{ + auto family = GetFontFamily(font); + if (family.isEmpty()) + return; + + QFontMetrics fm(QFont(family, size)); + QRect rect = fm.boundingRect(QString::fromWCharArray(mess)); + + w = rect.width(); + h = rect.height(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns text accent / descent + +void TQt6PadPainter::GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, UInt_t &d, const char *mess) +{ + auto family = GetFontFamily(font); + if (family.isEmpty()) + return; + + QFontMetrics fm(QFont(family, size)); + QRect rect = fm.boundingRect(mess); + + a = -rect.top(); + d = rect.bottom(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns text accent / descent + +void TQt6PadPainter::GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, UInt_t &d, const wchar_t *mess) +{ + auto family = GetFontFamily(font); + if (family.isEmpty()) + return; + + QFontMetrics fm(QFont(family, size)); + QRect rect = fm.boundingRect(QString::fromWCharArray(mess)); + + a = -rect.top(); + d = rect.bottom(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Returns text advance + +UInt_t TQt6PadPainter::GetTextAdvance(Font_t font, Double_t size, const char *text, Bool_t) +{ + auto family = GetFontFamily(font); + if (family.isEmpty()) + return 0; + + QFontMetrics fm(QFont(family, size)); + return fm.horizontalAdvance(QString(text)); +} From 05d9ab2a9402f3b7ac2905ceb2de42159067b9e0 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 16:05:57 +0200 Subject: [PATCH 10/20] [qt6canv] implement lines and polylines drawing Handle TAttLine with custom colors, width and style Create QPen which contains such properties --- gui/qt6canvas/inc/TQt6PadPainter.h | 2 + gui/qt6canvas/src/TQt6PadPainter.cxx | 132 +++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 10 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index 463a88a8eced2..86445bdee6568 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -15,6 +15,7 @@ #include #include +#include class TQt6Canvas; class QPaintWidget; @@ -31,6 +32,7 @@ friend class TQt6Canvas; static QString GetFontFamily(Font_t id); static QColor GetQColor(Color_t id); + QPen GetLinePen(); public: diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 71d5bb8c45c18..fe39885749257 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -11,17 +11,21 @@ #include "TQt6PadPainter.h" #include "TError.h" #include "TSystem.h" +#include "TStyle.h" #include "TEnv.h" #include "TImage.h" #include "TPad.h" #include "TROOT.h" #include "TColor.h" +#include + #include "QPaintWidget.h" #include #include #include +#include #include /** \class TQt6PadPainter @@ -52,9 +56,20 @@ void TQt6PadPainter::DrawPixels(const unsigned char * /*pixelData*/, UInt_t /*wi void TQt6PadPainter::DrawLine(Double_t x1, Double_t y1, Double_t x2, Double_t y2) { - if (GetAttLine().GetLineWidth() <= 0) + auto painter = fPaintWidget->getPainter(); + if (!painter || GetAttLine().GetLineWidth() <= 0) return; + const Int_t px1 = gPad->XtoAbsPixel(x1); + const Int_t py1 = gPad->YtoAbsPixel(y1); + const Int_t px2 = gPad->XtoAbsPixel(x2); + const Int_t py2 = gPad->YtoAbsPixel(y2); + + painter->setPen(GetLinePen()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawLine(QPoint(px1, py1), QPoint(px2, py2)); } @@ -63,10 +78,21 @@ void TQt6PadPainter::DrawLine(Double_t x1, Double_t y1, Double_t x2, Double_t y2 void TQt6PadPainter::DrawLineNDC(Double_t u1, Double_t v1, Double_t u2, Double_t v2) { - if (GetAttLine().GetLineWidth() <= 0) + auto painter = fPaintWidget->getPainter(); + if (!painter || GetAttLine().GetLineWidth() <= 0) return; -} + const Int_t px1 = gPad->UtoAbsPixel(u1); + const Int_t py1 = gPad->VtoAbsPixel(v1); + const Int_t px2 = gPad->UtoAbsPixel(u2); + const Int_t py2 = gPad->VtoAbsPixel(v2); + + painter->setPen(GetLinePen()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawLine(QPoint(px1, py1), QPoint(px2, py2)); +} //////////////////////////////////////////////////////////////////////////////// /// Paint a simple box. @@ -76,11 +102,29 @@ void TQt6PadPainter::DrawBox(Double_t x1, Double_t y1, Double_t x2, Double_t y2, if (GetAttLine().GetLineWidth() <= 0 && mode == TVirtualPadPainter::kHollow) return; - // if (mode == TVirtualPadPainter::kHollow) - // draw only border - // else - // draw only fill + auto painter = fPaintWidget->getPainter(); + if (!painter) + return; + + const Int_t px1 = gPad->XtoAbsPixel(x1); + const Int_t py1 = gPad->YtoAbsPixel(y1); + const Int_t px2 = gPad->XtoAbsPixel(x2); + const Int_t py2 = gPad->YtoAbsPixel(y2); + if (mode == TVirtualPadPainter::kHollow) { + // draw only border + painter->setPen(GetLinePen()); + painter->setRenderHint(QPainter::Antialiasing); + painter->setBrush(Qt::NoBrush); + } else { + // draw only fill + painter->setPen(Qt::NoPen); + QBrush fillBrush(GetQColor(GetAttFill().GetFillColor())); // A solid flat green + painter->setBrush(fillBrush); + } + + QRect rectangle(px1, py1, px2, py2); + painter->drawRect(rectangle); } //////////////////////////////////////////////////////////////////////////////// @@ -107,8 +151,19 @@ void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Float_t *xs, const Float_ void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Double_t *xs, const Double_t *ys) { - if ((GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + auto painter = fPaintWidget->getPainter(); + if (!painter || (GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) return; + + QList points; + for (Int_t n = 0; n < nPoints; ++n) + points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + + painter->setPen(GetLinePen()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPolyline(points); } //////////////////////////////////////////////////////////////////////////////// @@ -116,8 +171,19 @@ void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Double_t *xs, const Doubl void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Float_t *xs, const Float_t *ys) { - if ((GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + auto painter = fPaintWidget->getPainter(); + if (!painter || (GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) return; + + QList points; + for (Int_t n = 0; n < nPoints; ++n) + points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + + painter->setPen(GetLinePen()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPolyline(points); } //////////////////////////////////////////////////////////////////////////////// @@ -125,8 +191,19 @@ void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Float_t *xs, const Float_ void TQt6PadPainter::DrawPolyLineNDC(Int_t nPoints, const Double_t *u, const Double_t *v) { - if ((GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + auto painter = fPaintWidget->getPainter(); + if (!painter || (GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) return; + + QList points; + for (Int_t n = 0; n < nPoints; ++n) + points.push_back({gPad->UtoAbsPixel(u[n]), gPad->VtoAbsPixel(v[n])}); + + painter->setPen(GetLinePen()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPolyline(points); } //////////////////////////////////////////////////////////////////////////////// @@ -221,6 +298,41 @@ QColor TQt6PadPainter::GetQColor(Color_t id) return QColor(0, 0, 0); } +//////////////////////////////////////////////////////////////////////////////// +/// Return QPen for lines drawing + +QPen TQt6PadPainter::GetLinePen() +{ + auto &att = GetAttLine(); + + auto style = att.GetLineStyle(); + + QPen customPen; + customPen.setColor(GetQColor(att.GetLineColor())); + customPen.setWidth(att.GetLineWidth()); + customPen.setStyle(Qt::SolidLine); + + TString patt; + + if (style > 1) + patt = gStyle->GetLineStyleString(style); + + if (patt.Length() > 2) { + QList pattern; + std::unique_ptr tokens(patt.Tokenize(" ")); + for (Int_t j = 0; j < tokens->GetEntries(); j++) { + Int_t it = std::stoi(tokens->At(j)->GetName()); + pattern.push_back(0.25 * it / att.GetLineWidth()); + } + if (pattern.size() > 1) { + customPen.setStyle(Qt::CustomDashLine); + customPen.setDashPattern(pattern); + } + } + + return customPen; +} + //////////////////////////////////////////////////////////////////////////////// /// Return font family for specified ROOT font id /// If necessary, register TTF font to Qt first From 42d0f22701644d920eedb41ab0ab9582a67fe6ff Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 09:53:12 +0200 Subject: [PATCH 11/20] [qt6canv] implement fill styles Support special fill patterns 3000..3025 reusing X11 pixmaps Implement box and fillarea painting --- gui/qt6canvas/inc/TQt6PadPainter.h | 2 + gui/qt6canvas/src/TQt6PadPainter.cxx | 58 ++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index 86445bdee6568..029e2d44ccd4f 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -16,6 +16,7 @@ #include #include #include +#include class TQt6Canvas; class QPaintWidget; @@ -33,6 +34,7 @@ friend class TQt6Canvas; static QString GetFontFamily(Font_t id); static QColor GetQColor(Color_t id); QPen GetLinePen(); + QBrush GetFillBrush(); public: diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index fe39885749257..de139a373102d 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -13,10 +13,11 @@ #include "TSystem.h" #include "TStyle.h" #include "TEnv.h" -#include "TImage.h" +#include "TMath.h" #include "TPad.h" #include "TROOT.h" #include "TColor.h" +#include "RStipples.h" #include @@ -25,7 +26,6 @@ #include #include #include -#include #include /** \class TQt6PadPainter @@ -119,11 +119,10 @@ void TQt6PadPainter::DrawBox(Double_t x1, Double_t y1, Double_t x2, Double_t y2, } else { // draw only fill painter->setPen(Qt::NoPen); - QBrush fillBrush(GetQColor(GetAttFill().GetFillColor())); // A solid flat green - painter->setBrush(fillBrush); + painter->setBrush(GetFillBrush()); } - QRect rectangle(px1, py1, px2, py2); + QRect rectangle(TMath::Min(px1, px2), TMath::Min(py1, py2), TMath::Abs(px2 - px1), TMath::Abs(py2 - py1)); painter->drawRect(rectangle); } @@ -132,8 +131,18 @@ void TQt6PadPainter::DrawBox(Double_t x1, Double_t y1, Double_t x2, Double_t y2, void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Double_t *xs, const Double_t *ys) { - if ((GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) + auto painter = fPaintWidget->getPainter(); + + if (!painter || (GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) return; + + QList points; + for (Int_t n = 0; n < nPoints; ++n) + points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + + painter->setPen(Qt::NoPen); + painter->setBrush(GetFillBrush()); + painter->drawPolygon(points); } //////////////////////////////////////////////////////////////////////////////// @@ -141,9 +150,18 @@ void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Double_t *xs, const Doubl void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Float_t *xs, const Float_t *ys) { - if ((GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) + auto painter = fPaintWidget->getPainter(); + + if (!painter || (GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) return; + QList points; + for (Int_t n = 0; n < nPoints; ++n) + points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + + painter->setPen(Qt::NoPen); + painter->setBrush(GetFillBrush()); + painter->drawPolygon(points); } //////////////////////////////////////////////////////////////////////////////// @@ -333,6 +351,32 @@ QPen TQt6PadPainter::GetLinePen() return customPen; } +//////////////////////////////////////////////////////////////////////////////// +/// Return QBrush for fill drawing drawing + +QBrush TQt6PadPainter::GetFillBrush() +{ + auto &att = GetAttFill(); + + Int_t style = att.GetFillStyle() / 1000; + + if (style == 1) + return QBrush(GetQColor(att.GetFillColor())); + + if (style == 3) { + Int_t fasi = att.GetFillStyle() % 1000; + Int_t stn = (fasi >= 1 && fasi <=25) ? fasi : 2; + QBitmap bitmap = QBitmap::fromData(QSize(16, 16), (uchar *)gStipples[stn]); + QImage image = bitmap.toImage(); + image.setColor(0, qRgba(0, 0, 0, 0)); // transparent + image.setColor(1, GetQColor(att.GetFillColor()).rgba()); + return QBrush(QPixmap::fromImage(image.copy())); + } + + return QBrush(Qt::NoBrush); +} + + //////////////////////////////////////////////////////////////////////////////// /// Return font family for specified ROOT font id /// If necessary, register TTF font to Qt first From be7608a7c4d97de2a697169ddb70e55c34ac2c40 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 12:43:54 +0200 Subject: [PATCH 12/20] [qt6canv] simple marker painting Once generic API exists to paint markers - use it here instead. There are 5 different places in ROOT which repeats again and again all markers implementation --- gui/qt6canvas/src/TQt6PadPainter.cxx | 38 ++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index de139a373102d..1647e64bdb49d 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -229,8 +229,25 @@ void TQt6PadPainter::DrawPolyLineNDC(Int_t nPoints, const Double_t *u, const Dou void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Double_t *x, const Double_t *y) { - if (nPoints < 1) + auto painter = fPaintWidget->getPainter(); + + if (!painter || nPoints < 1) return; + + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + painter->setBrush(QBrush(GetQColor(GetAttMarker().GetMarkerColor()))); + + Int_t radius = GetAttMarker().GetMarkerSize(); + if (radius < 2) + radius = 2; + + // TODO: implement all markers types - once method exists + for (Int_t n = 0; n < nPoints; ++n) { + Int_t px = gPad->XtoAbsPixel(x[n]); + Int_t py = gPad->YtoAbsPixel(y[n]); + painter->drawEllipse({px, py}, radius, radius); + } } //////////////////////////////////////////////////////////////////////////////// @@ -238,8 +255,25 @@ void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Double_t *x, const Doub void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float_t *y) { - if (nPoints < 1) + auto painter = fPaintWidget->getPainter(); + + if (!painter || nPoints < 1) return; + + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + painter->setBrush(QBrush(GetQColor(GetAttMarker().GetMarkerColor()))); + + Int_t radius = GetAttMarker().GetMarkerSize(); + if (radius < 2) + radius = 2; + + // TODO: implement all markers types - once method exists + for (Int_t n = 0; n < nPoints; ++n) { + Int_t px = gPad->XtoAbsPixel(x[n]); + Int_t py = gPad->YtoAbsPixel(y[n]); + painter->drawEllipse({px, py}, radius, radius); + } } //////////////////////////////////////////////////////////////////////////////// From 73374defac813933384e145e4877926fae722886 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 15:29:02 +0200 Subject: [PATCH 13/20] [qt6canv] use scaling factor for the QFont size It is not equivalent with TTF font and must be about 0.75 --- gui/qt6canvas/src/TQt6PadPainter.cxx | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 1647e64bdb49d..53911c1e09c96 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -28,6 +28,10 @@ #include #include + +// to scale fonts to the same size as in the TTF +const Float_t kScale = 0.75 * 0.93376068; + /** \class TQt6PadPainter \ingroup qt6canvas \brief Implement TVirtualPadPainter for Qt6 graphics @@ -502,7 +506,10 @@ void TQt6PadPainter::PaintQString(int x, int y, const QString &s) if (family.isEmpty()) return; - painter->setFont(QFont(family, att.GetTextSizePixels(*gPad))); + auto textsize = att.GetTextSizePixels(*gPad); + Int_t pixelsize = (Int_t) (textsize*kScale+0.5); + + painter->setFont(QFont(family, pixelsize)); painter->setPen(GetQColor(att.GetTextColor())); @@ -546,7 +553,9 @@ void TQt6PadPainter::GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t if (family.isEmpty()) return; - QFontMetrics fm(QFont(family, size)); + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); QRect rect = fm.boundingRect(mess); w = rect.width(); @@ -562,7 +571,9 @@ void TQt6PadPainter::GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t if (family.isEmpty()) return; - QFontMetrics fm(QFont(family, size)); + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); QRect rect = fm.boundingRect(QString::fromWCharArray(mess)); w = rect.width(); @@ -578,7 +589,9 @@ void TQt6PadPainter::GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, if (family.isEmpty()) return; - QFontMetrics fm(QFont(family, size)); + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); QRect rect = fm.boundingRect(mess); a = -rect.top(); @@ -594,7 +607,9 @@ void TQt6PadPainter::GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, if (family.isEmpty()) return; - QFontMetrics fm(QFont(family, size)); + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); QRect rect = fm.boundingRect(QString::fromWCharArray(mess)); a = -rect.top(); @@ -609,7 +624,8 @@ UInt_t TQt6PadPainter::GetTextAdvance(Font_t font, Double_t size, const char *te auto family = GetFontFamily(font); if (family.isEmpty()) return 0; + Int_t pixelsize = (Int_t) (size*kScale+0.5); - QFontMetrics fm(QFont(family, size)); + QFontMetrics fm(QFont(family, pixelsize)); return fm.horizontalAdvance(QString(text)); } From 7bf6136ead85980e0174f6a0011fec0eaab6a802 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 16:06:07 +0200 Subject: [PATCH 14/20] [qt6canv] correctly convert const char * to QString Use QString::toLatin1() method, otherwise special characters like used in symbols.ttf, are handled as utf-8 as displayed wrongly --- gui/qt6canvas/src/TQt6PadPainter.cxx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 53911c1e09c96..3d84d2f43fd14 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -288,7 +288,7 @@ void TQt6PadPainter::DrawText(Double_t x, Double_t y, const char *text, ETextMod const Int_t px = gPad->XtoAbsPixel(x); const Int_t py = gPad->YtoAbsPixel(y); - PaintQString(px, py, text); + PaintQString(px, py, QString::fromLatin1(text)); } //////////////////////////////////////////////////////////////////////////////// @@ -299,7 +299,7 @@ void TQt6PadPainter::DrawTextUrl(Double_t x, Double_t y, const char *text, const const Int_t px = gPad->XtoAbsPixel(x); const Int_t py = gPad->YtoAbsPixel(y); - PaintQString(px, py, text); + PaintQString(px, py, QString::fromLatin1(text)); } //////////////////////////////////////////////////////////////////////////////// @@ -321,7 +321,7 @@ void TQt6PadPainter::DrawTextNDC(Double_t u, Double_t v, const char *text, EText const Int_t px = gPad->UtoAbsPixel(u); const Int_t py = gPad->VtoAbsPixel(v); - PaintQString(px, py, text); + PaintQString(px, py, QString::fromLatin1(text)); } //////////////////////////////////////////////////////////////////////////////// @@ -556,7 +556,7 @@ void TQt6PadPainter::GetTextExtent(Font_t font, Double_t size, UInt_t &w, UInt_t Int_t pixelsize = (Int_t) (size*kScale+0.5); QFontMetrics fm(QFont(family, pixelsize)); - QRect rect = fm.boundingRect(mess); + QRect rect = fm.boundingRect(QString::fromLatin1(mess)); w = rect.width(); h = rect.height(); @@ -592,7 +592,7 @@ void TQt6PadPainter::GetTextAscentDescent(Font_t font, Double_t size, UInt_t &a, Int_t pixelsize = (Int_t) (size*kScale+0.5); QFontMetrics fm(QFont(family, pixelsize)); - QRect rect = fm.boundingRect(mess); + QRect rect = fm.boundingRect(QString::fromLatin1(mess)); a = -rect.top(); d = rect.bottom(); @@ -627,5 +627,5 @@ UInt_t TQt6PadPainter::GetTextAdvance(Font_t font, Double_t size, const char *te Int_t pixelsize = (Int_t) (size*kScale+0.5); QFontMetrics fm(QFont(family, pixelsize)); - return fm.horizontalAdvance(QString(text)); + return fm.horizontalAdvance(QString::fromLatin1(text)); } From 68e3b665b89857021c5f2d505050ad4e4e7d6fd6 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 1 Jul 2026 09:55:54 +0200 Subject: [PATCH 15/20] [qt6canv] provide mouse events handling Like press, move, release mouse button. On the right mouse button actiate context menu. For methods with arguments provide special dialog. Code partially copied from Go4 project and originally was developed in 2002 by Denis Bertini and improved later by Sergey Linev --- gui/qt6canvas/CMakeLists.txt | 1 + gui/qt6canvas/src/QCanvasWidget.cpp | 2 + gui/qt6canvas/src/QCanvasWidget.ui | 61 +----- gui/qt6canvas/src/QPaintWidget.cpp | 266 +++++++++++++++++++++++- gui/qt6canvas/src/QPaintWidget.h | 33 +++ gui/qt6canvas/src/QRootMethodDialog.cpp | 220 ++++++++++++++++++++ gui/qt6canvas/src/QRootMethodDialog.h | 48 +++++ 7 files changed, 568 insertions(+), 63 deletions(-) create mode 100644 gui/qt6canvas/src/QRootMethodDialog.cpp create mode 100644 gui/qt6canvas/src/QRootMethodDialog.h diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index 11bd68eb087e1..54fdc75e56713 100644 --- a/gui/qt6canvas/CMakeLists.txt +++ b/gui/qt6canvas/CMakeLists.txt @@ -33,6 +33,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas SOURCES src/QCanvasWidget.cpp src/QPaintWidget.cpp + src/QRootMethodDialog.cpp src/TQt6Canvas.cxx src/TQt6PadPainter.cxx LIBRARIES diff --git a/gui/qt6canvas/src/QCanvasWidget.cpp b/gui/qt6canvas/src/QCanvasWidget.cpp index ba2445ed1f285..46ced882537eb 100644 --- a/gui/qt6canvas/src/QCanvasWidget.cpp +++ b/gui/qt6canvas/src/QCanvasWidget.cpp @@ -26,6 +26,8 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent setObjectName(name); + fPaintWidget->SetStatusBar(fStatusBar); + fMenuBar = new QMenuBar(fMenuFrame); fMenuBar->setMinimumWidth(50); diff --git a/gui/qt6canvas/src/QCanvasWidget.ui b/gui/qt6canvas/src/QCanvasWidget.ui index 3d2a639ec60dd..21fa473da169e 100644 --- a/gui/qt6canvas/src/QCanvasWidget.ui +++ b/gui/qt6canvas/src/QCanvasWidget.ui @@ -59,13 +59,7 @@
- - - QFrame::StyledPanel - - - QFrame::Raised - + 7 @@ -80,58 +74,7 @@ 10 - - - 1 - - - 2 - - - 2 - - - - - Label 1 - - - false - - - - - - - Label 2 - - - false - - - - - - - Label 3 - - - false - - - - - - - Label 4 - - - false - - - - - + diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp index edb96a6d918cc..287db9e35d3ee 100644 --- a/gui/qt6canvas/src/QPaintWidget.cpp +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -10,11 +10,26 @@ #include "QPaintWidget.h" +#include + #include "TCanvas.h" +#include "TROOT.h" +#include "TMethod.h" + +#include "QRootMethodDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include +#include QPaintWidget::QPaintWidget(QWidget *parent) : QWidget(parent) @@ -32,17 +47,25 @@ QPaintWidget::QPaintWidget(QWidget *parent) : QWidget(parent) setAcceptDrops(true); fCanvas = nullptr; + + fQtScalingfactor = (double) metric(QPaintDevice::PdmDevicePixelRatioScaled)/65536.; } QPaintWidget::~QPaintWidget() { } +QPoint QPaintWidget::scaledMousePoint(QMouseEvent *e) +{ + int scaledX = scaledPosition(e->position().x()); + int scaledY = scaledPosition(e->position().y()); + return QPoint(scaledX, scaledY); +} + + void QPaintWidget::resizeEvent(QResizeEvent *) { - printf("Call resize event\n"); if (fCanvas) { - fCanvas->Resize(); fCanvas->Modified(); } @@ -50,8 +73,6 @@ void QPaintWidget::resizeEvent(QResizeEvent *) void QPaintWidget::paintEvent(QPaintEvent *) { - printf("Call paint event\n"); - try { QPainter painter(this); @@ -65,3 +86,240 @@ void QPaintWidget::paintEvent(QPaintEvent *) fPainter = nullptr; } } + +void QPaintWidget::mousePressEvent( QMouseEvent *e ) +{ + TObjLink* pickobj = nullptr; + QPoint scaled = scaledMousePoint(e); + QPoint menu_pnt = e->globalPosition().toPoint(); + + TPad *pad = fCanvas->Pick(scaled.x(), scaled.y(), pickobj); + TObject *selected = fCanvas->GetSelected(); + + switch(e->button()) { + case Qt::LeftButton : + fCanvas->HandleInput(kButton1Down, scaled.x(), scaled.y()); + // emit PadClicked(pad, scaled.x(), scaled.y()); + break; + case Qt::RightButton : { + TString selectedOpt; + if (pad) { + if (!pickobj) { + fCanvas->SetSelected(pad); + selected = pad; + } else if(!selected) { + selected = pickobj->GetObject(); + selectedOpt = pickobj->GetOption(); + } + pad->cd(); + } + fCanvas->SetSelectedPad(pad); + gROOT->SetSelectedPrimitive(selected); + fMousePosX = gPad->AbsPixeltoX(gPad->GetEventX()); + fMousePosY = gPad->AbsPixeltoY(gPad->GetEventY()); + + QMenu menu; + QSignalMapper map; + + QObject::connect(&map, &QSignalMapper::mappedInt, + this, &QPaintWidget::executeMenu); + fMenuObj = selected; + fMenuMethods = new TList; + TClass *cl = fMenuObj->IsA(); + int curId = -1; + + QString buffer = TString::Format("%s::%s", cl->GetName(), fMenuObj->GetName()).Data(); + addMenuAction(&menu, &map, buffer, curId++); + + cl->GetMenuItems(fMenuMethods); + menu.addSeparator(); + + TIter iter(fMenuMethods); + while (auto method = dynamic_cast(iter())) { + buffer = method->GetName(); + addMenuAction(&menu, &map, buffer, curId++); + } + + if (menu.exec(menu_pnt) == nullptr) { + fMenuObj = nullptr; + delete fMenuMethods; + fMenuMethods = nullptr; + } + + break; + } + case Qt::MiddleButton : + fCanvas->HandleInput(kButton2Down, scaled.x(), scaled.y()); + // emit SelectedPadChanged(pad); + break; + case Qt::NoButton : + break; + default: + break; + } + e->accept(); +} + + +void QPaintWidget::mouseMoveEvent(QMouseEvent *e) +{ + static ulong lastprocesstime = 0; + static ulong delta = 100; + ulong timestamp = e->timestamp(); + e->accept(); + if(timestamp - delta < lastprocesstime) + return; + lastprocesstime = timestamp; + + if (fCanvas) { + QPoint pnt = scaledMousePoint(e); + + if (e->buttons() & Qt::LeftButton) + fCanvas->HandleInput(kButton1Motion, pnt.x(), pnt.y()); + else + fCanvas->HandleInput(kMouseMotion, pnt.x(), pnt.y()); + } + + if(fShowEventStatus) { + TObject *selected = fCanvas->GetSelected(); + Int_t px = fCanvas->GetEventX(); + Int_t py = fCanvas->GetEventY(); + QString buffer = ""; + if (selected) { + buffer = selected->GetName(); + buffer += " "; + buffer += selected->GetObjectInfo(px, py); + } else { + buffer = "No selected object x = "; + buffer += QString::number(px); + buffer += " y = "; + buffer += QString::number(py); + } + + // emit CanvasStatusEvent(buffer.toLatin1().constData()); + + if (fStatusBar) fStatusBar->showMessage(buffer.toLatin1().constData()); + } +} + +void QPaintWidget::mouseReleaseEvent( QMouseEvent *e ) +{ + QPoint scaled = scaledMousePoint(e); + + switch(e->button()) { + case Qt::LeftButton : + fCanvas->HandleInput(kButton1Up, scaled.x(), scaled.y()); + break; + case Qt::RightButton : + fCanvas->HandleInput(kButton3Up, scaled.x(), scaled.y()); + break; + case Qt::MiddleButton : + fCanvas->HandleInput(kButton2Up, scaled.x(), scaled.y()); + break; + case Qt::NoButton : + break; + default: + break; + } + e->accept(); +} + +void QPaintWidget::mouseDoubleClickEvent( QMouseEvent *e ) +{ + QPoint scaled = scaledMousePoint(e); + + switch(e->button()) { + case Qt::LeftButton : { + if (!fMaskDoubleClick) + fCanvas->HandleInput(kButton1Double, scaled.x(), scaled.y()); + TObjLink* pickobj = nullptr; + TPad *pad = fCanvas->Pick(scaled.x(), scaled.y(), pickobj); + // emit PadDoubleClicked(pad, scaled.x(), scaled.y()); + break; + } + case Qt::RightButton : + fCanvas->HandleInput(kButton3Double, scaled.x(), scaled.y()); + break; + case Qt::MiddleButton : + fCanvas->HandleInput(kButton2Double, scaled.x(), scaled.y()); + break; + case Qt::NoButton : + break; + default: + break; + } + e->accept(); +} + +QAction* QPaintWidget::addMenuAction(QMenu* menu, QSignalMapper* map, const QString& text, int id) +{ + bool enabled = true; + + QAction* act = new QAction(text, menu); + + if (!enabled) + if ((text.compare("DrawClone") == 0) || (text.compare("DrawClass") == 0) || (text.compare("Inspect") == 0) || + (text.compare("SetShowProjectionX") == 0) || (text.compare("SetShowProjectionY") == 0) || + (text.compare("DrawPanel") == 0) || (text.compare("FitPanel") == 0)) + act->setEnabled(false); + + QObject::connect(act, &QAction::triggered, [id, map]() { + map->mappedInt(id); + }); + + menu->addAction(act); + map->setMapping(act, id); + + return act; +} + +void QPaintWidget::executeMenu(int id) +{ + QString text; + bool ok = false; + if (id >= 0) { + + // save global to Pad before calling TObject::Execute() + + TVirtualPad *psave = gROOT->GetSelectedPad(); + TMethod *method = (TMethod *) fMenuMethods->At(id); + + /// test: do this in any case! + fCanvas->HandleInput(kButton3Up, gPad->XtoAbsPixel(fMousePosX), gPad->YtoAbsPixel(fMousePosY)); + + // change current dir that all new histograms appear here + gROOT->cd(); + + if (method->GetListOfMethodArgs()->First()) { + QRootMethodDialog dlg; + + dlg.methodDialog(fMenuObj, method); + } else { + gROOT->SetFromPopUp(kTRUE); + fMenuObj->Execute(method->GetName(), ""); + + if (fMenuObj->TestBit(TObject::kNotDeleted)) { + // emit MenuCommandExecuted(fMenuObj, method->GetName()); + } else { + fMenuObj = nullptr; + } + + } + + fCanvas->GetPadSave()->Update(); + fCanvas->GetPadSave()->Modified(); + + gROOT->SetSelectedPad(psave); + + gROOT->GetSelectedPad()->Update(); + gROOT->GetSelectedPad()->Modified(); + + fCanvas->Modified(); + fCanvas->ForceUpdate(); + gROOT->SetFromPopUp(kFALSE); + } + + fMenuObj = nullptr; + delete fMenuMethods; + fMenuMethods = nullptr; +} diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h index d28d78227250c..ec9efa516a597 100644 --- a/gui/qt6canvas/src/QPaintWidget.h +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -16,7 +16,11 @@ class TCanvas; class TPad; class TObject; +class TList; +class TMethod; class QPainter; +class QSignalMapper; +class QStatusBar; class QPaintWidget : public QWidget { @@ -33,14 +37,43 @@ class QPaintWidget : public QWidget { void SetCanvas(TCanvas *canv) { fCanvas = canv; } + void SetStatusBar(QStatusBar *bar) { fStatusBar = bar; } + +public slots: + void executeMenu(int id); + protected: void resizeEvent(QResizeEvent *event) override; void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + + QAction* addMenuAction(QMenu *menu, QSignalMapper *map, const QString &text, int id); + + double scaledPosition(int p) { return (double) p * fQtScalingfactor; } + + QPoint scaledMousePoint(QMouseEvent *event); + TCanvas *fCanvas = nullptr; QPainter *fPainter = nullptr; + + double fQtScalingfactor = 1.; + + QStatusBar *fStatusBar = nullptr; + + bool fMaskDoubleClick = false; + double fMousePosX = 0; // mouse position in user coordinate when activate menu + double fMousePosY = 0; // mouse position in user coordinate when activate menu + + TObject *fMenuObj = nullptr; // object use to fill menu + TList *fMenuMethods = nullptr; // list of menu methods + bool fShowEventStatus = true; + }; #endif diff --git a/gui/qt6canvas/src/QRootMethodDialog.cpp b/gui/qt6canvas/src/QRootMethodDialog.cpp new file mode 100644 index 0000000000000..ee428fd15f0aa --- /dev/null +++ b/gui/qt6canvas/src/QRootMethodDialog.cpp @@ -0,0 +1,220 @@ +// Original code derived from QRootDialog +// from https://go4.gsi.de project +// Author : Denis Bertini 01.11.2000 + +// Author: Sergey Linev, GSI 30/06/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 "QRootMethodDialog.h" + +#include +#include +#include +#include +#include +#include + +#include "TString.h" +#include "TROOT.h" +#include "TVirtualPad.h" +#include "TMethod.h" +#include "TMethodArg.h" +#include "TMethodCall.h" +#include "TObjString.h" + +#include + +QRootMethodDialog::QRootMethodDialog() : QDialog() +{ + QGridLayout *gridLayout = new QGridLayout(this); + gridLayout->setSpacing(1); + gridLayout->setContentsMargins(1,1,1,1); + + QHBoxLayout *buttLayout = new QHBoxLayout(); + + QPushButton *bOk = new QPushButton(this); + bOk->setText("Apply"); + QObject::connect(bOk, &QPushButton::clicked, this, &QRootMethodDialog::accept); + buttLayout->addWidget(bOk); + + QPushButton *bCancel = new QPushButton(this); + bCancel->setText("Cancel"); + QObject::connect(bCancel, &QPushButton::clicked, this, &QRootMethodDialog::reject); + buttLayout->addWidget(bCancel); + + argLayout = new QVBoxLayout(); + + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding)); + + gridLayout->addLayout(argLayout, 0, 0); + gridLayout->addLayout(buttLayout, 1, 0, Qt::AlignBottom); +} + +void QRootMethodDialog::addArg(const char *argname, const char *value, const char *) +{ + QLabel* lbl = new QLabel(argname); + argLayout->addWidget(lbl); + + QLineEdit* le = new QLineEdit(); + le->setGeometry(10,10, 130, 30); + le->setFocus(); + le->setText(value); + argLayout->addWidget(le); + + fArgs.push_back(le); +} + +QString QRootMethodDialog::getArg(int n) +{ + if ((n<0) || (n>=fArgs.size())) return QString(""); + return fArgs[n]->text(); +} + + +void QRootMethodDialog::methodDialog(TObject *object, TMethod* method) +{ + if (!object || !method) + return; + + setWindowTitle(TString::Format("%s:%s", object->GetName(), method->GetName()).Data()); + + // iterate through all arguments and create appropriate input-data objects: + // inputlines, option menus... + TIter next(method->GetListOfMethodArgs()); + + while (auto argument = (TMethodArg *) next()) { + TString argTitle = TString::Format("(%s) %s", argument->GetTitle(), argument->GetName()); + TString argDflt = argument->GetDefault() ? argument->GetDefault() : ""; + if (argDflt.Length() > 0) + argTitle += TString::Format(" [default: %s]", argDflt.Data()); + 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 == "long") || + (basictype == "short")) { + Long_t ldefval = 0; + m->GetterMethod()->Execute(object, "", ldefval); + val = TString::Format("%ld", ldefval); + } + + // Find out whether we have options ... + + TList *opt; + if ((opt = m->GetOptions()) != nullptr) { + // should stop dialog + // workaround JAM: do not stop dialog, use textfield (for time display toggle) + addArg(argTitle.Data(), val.Data(), type.Data()); + //return; + } else { + // we haven't got options - textfield ... + addArg(argTitle.Data(), val.Data(), type.Data()); + } + } else { // if m not found ... + 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()); + } + } + + if (exec() != QDialog::Accepted) return; + + Bool_t deletion = kFALSE; + + qDebug("DIAL executeMethod: simple version\n"); + TVirtualPad *psave = gROOT->GetSelectedPad(); + + qDebug("DIAL saved pad: %s gPad:%s \n",psave->GetName(),gPad->GetName()); + + qDebug("DIAL obj:%s meth:%s \n", object->GetName(), method->GetName()); + + TObjArray tobjlist(method->GetListOfMethodArgs()->LastIndex() + 1); + for (int n = 0; n <= method->GetListOfMethodArgs()->LastIndex(); n++) { + QString s = getArg(n); + qDebug( "** QString values (first ) :%s \n", s.toLatin1().constData() ); + tobjlist.AddLast(new TObjString(s.toLatin1().constData())); + } + + // handle command if existing object + if(strcmp(method->GetName(),"Delete") == 0) { + // here call explicitly the dtor + qDebug(" DIAL obj name deleted :%s \n", object->GetName()); + emit MenuCommandExecuted(object, "Delete"); + delete object; + object = nullptr; + deletion = kTRUE; + qDebug(" DIAL deletion done closing ... \n"); + } else { + // here call cint call + qDebug("TCint::Execute called !\n"); + + object->Execute(method, &tobjlist); + + if (object->TestBit(TObject::kNotDeleted)) { + emit MenuCommandExecuted(object, method->GetName()); + } else { + deletion = true; + object = nullptr; + } + } + + if(!deletion ) { + qDebug("DIAL set saved pad: %s herit:%s gPad:%s\n", + psave->GetName(), psave->ClassName(), gPad->GetName()); + gROOT->SetSelectedPad(psave); + gROOT->GetSelectedPad()->Modified(); + gROOT->GetSelectedPad()->Update(); + qDebug("DIAL update done on %s \n", gROOT->GetSelectedPad()->GetName()); + } else { + gROOT->SetSelectedPad(gPad); + gROOT->GetSelectedPad()->Update(); + } +} diff --git a/gui/qt6canvas/src/QRootMethodDialog.h b/gui/qt6canvas/src/QRootMethodDialog.h new file mode 100644 index 0000000000000..134e68c628cfc --- /dev/null +++ b/gui/qt6canvas/src/QRootMethodDialog.h @@ -0,0 +1,48 @@ +// Original code derived from QRootDialog +// from https://go4.gsi.de project +// Author : Denis Bertini 01.11.2000 + +// Author: Sergey Linev, GSI 30/06/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 QRootMethodDialog_h +#define QRootMethodDialog_h + +#include +#include + +class QLineEdit; +class QVBoxLayout; +class TObject; +class TMethod; + +class QRootMethodDialog: public QDialog { + Q_OBJECT + + public: + QRootMethodDialog(); + + void addArg(const char *argname, const char *value, const char *type); + + QString getArg(int n); + + void methodDialog(TObject *object, TMethod* method); + + signals: + void MenuCommandExecuted(TObject *obj, const char *method_name); + + protected: + QVBoxLayout *argLayout{nullptr}; + + QVector fArgs; +}; + +#endif From ba899cf685690a5067fc1e8ff6e4162d7409b7c8 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 1 Jul 2026 12:14:59 +0200 Subject: [PATCH 16/20] [qt6canv] provide cursors and window title and size --- gui/qt6canvas/inc/TQt6Canvas.h | 28 ++++++------ gui/qt6canvas/inc/TQt6PadPainter.h | 2 +- gui/qt6canvas/src/TQt6Canvas.cxx | 23 +++++++--- gui/qt6canvas/src/TQt6PadPainter.cxx | 67 +++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 32 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index 371e8eadd47d0..b43bd627c3bc7 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -50,20 +50,20 @@ class TQt6Canvas : public TCanvasImp { void ResizeCanvasWindow(Int_t ) override {} - void ShowMenuBar(Bool_t show = kTRUE) override { } - void ShowStatusBar(Bool_t show = kTRUE) override { } - void ShowEditor(Bool_t show = kTRUE) override { } - void ShowToolBar(Bool_t show = kTRUE) override { } - void ShowToolTips(Bool_t show = kTRUE) override { } - - void ForceUpdate() override; - - void SetWindowPosition(Int_t x, Int_t y) override; - void SetWindowSize(UInt_t w, UInt_t h) override; - void SetWindowTitle(const char *newTitle) override; - void SetCanvasSize(UInt_t w, UInt_t h) override; - void Iconify() override; - void RaiseWindow() override; + void ShowMenuBar(Bool_t = kTRUE) override { } + void ShowStatusBar(Bool_t = kTRUE) override { } + void ShowEditor(Bool_t = kTRUE) override { } + void ShowToolBar(Bool_t = kTRUE) override { } + void ShowToolTips(Bool_t = kTRUE) override { } + + void ForceUpdate() override; + + void SetWindowPosition(Int_t x, Int_t y) override; + void SetWindowSize(UInt_t w, UInt_t h) override; + void SetWindowTitle(const char *newTitle) override; + void SetCanvasSize(UInt_t w, UInt_t h) override; + void Iconify() override; + void RaiseWindow() override; Bool_t HasEditor() const override; Bool_t HasMenuBar() const override; diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index 029e2d44ccd4f..231c8a3330c86 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -55,7 +55,7 @@ friend class TQt6Canvas; void DestroyDrawable(Int_t) override {} void SelectDrawable(Int_t) override {} void SetDoubleBuffer(Int_t /* device */, Int_t /* mode */) override {} - void SetCursor(Int_t /* win */, ECursor /* cursor */) override {} + void SetCursor(Int_t, ECursor) override; //jpg, png, bmp, gif output. void SaveImage(TVirtualPad *, const char *, Int_t) const override; diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 91def06d39da3..84e07c96015c5 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -157,6 +157,8 @@ Bool_t TQt6Canvas::HasToolTips() const void TQt6Canvas::SetWindowPosition(Int_t x, Int_t y) { + if (fCanvasWidget) + fCanvasWidget->move(x, y); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -164,6 +166,8 @@ void TQt6Canvas::SetWindowPosition(Int_t x, Int_t y) void TQt6Canvas::SetWindowSize(UInt_t w, UInt_t h) { + if (fCanvasWidget) + fCanvasWidget->resize(w, h); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -171,10 +175,12 @@ void TQt6Canvas::SetWindowSize(UInt_t w, UInt_t h) void TQt6Canvas::SetWindowTitle(const char *newTitle) { + if (fCanvasWidget) + fCanvasWidget->setWindowTitle(QString::fromLatin1(newTitle)); } ////////////////////////////////////////////////////////////////////////////////////////// -/// Set canvas size of web canvas +/// Set canvas size void TQt6Canvas::SetCanvasSize(UInt_t cw, UInt_t ch) { @@ -194,6 +200,8 @@ void TQt6Canvas::SetCanvasSize(UInt_t cw, UInt_t ch) void TQt6Canvas::Iconify() { + if (fCanvasWidget) + fCanvasWidget->showMinimized(); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -201,6 +209,11 @@ void TQt6Canvas::Iconify() void TQt6Canvas::RaiseWindow() { + if (fCanvasWidget) { + fCanvasWidget->showNormal(); + fCanvasWidget->raise(); + fCanvasWidget->activateWindow(); + } } ////////////////////////////////////////////////////////////////////////////////////////// @@ -246,6 +259,8 @@ void TQt6Canvas::ProcessExecs(TPad *pad, TExec *extra) gVirtualX = savex; } +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Return canvas geometry void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) { @@ -285,12 +300,11 @@ UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) return 0; } - ////////////////////////////////////////////////////////////////////////////////////////// /// if canvas or any subpad was modified, /// invoke Qt update() which will redraw area -Bool_t TQt6Canvas::PerformUpdate(Bool_t async) +Bool_t TQt6Canvas::PerformUpdate(Bool_t /* async */) { if (Canvas()->IsModified()) fPaintWidget->update(); @@ -410,8 +424,5 @@ TCanvas *TQt6Canvas::CreateQt6Canvas(const char *name, const char *title, UInt_t l2->Add(canvas); } - // ensure creation of web window - // imp->CreateWebWindow(); - return canvas; } diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 3d84d2f43fd14..e50e2a5d64b96 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -45,6 +45,36 @@ void TQt6PadPainter::SetOpacity(Int_t percent) fAttFill.SetFillStyle(4000 + percent); } +////////////////////////////////////////////////////////////////////////// +/// Set cursor + +void TQt6PadPainter::SetCursor(Int_t, ECursor cursor) +{ + switch(cursor) { + case kBottomLeft: fPaintWidget->setCursor(Qt::SizeBDiagCursor); break; + case kBottomRight: fPaintWidget->setCursor(Qt::SizeFDiagCursor); break; + case kTopLeft: fPaintWidget->setCursor(Qt::SizeFDiagCursor); break; + case kTopRight: fPaintWidget->setCursor(Qt::SizeBDiagCursor); break; + case kBottomSide: fPaintWidget->setCursor(Qt::SizeVerCursor); break; + case kLeftSide: fPaintWidget->setCursor(Qt::SizeHorCursor); break; + case kTopSide: fPaintWidget->setCursor(Qt::SizeVerCursor); break; + case kRightSide: fPaintWidget->setCursor(Qt::SizeHorCursor); break; + case kMove: fPaintWidget->setCursor(Qt::DragMoveCursor); break; + case kCross: fPaintWidget->setCursor(Qt::CrossCursor); break; + case kArrowHor: fPaintWidget->setCursor(Qt::SizeHorCursor); break; + case kArrowVer: fPaintWidget->setCursor(Qt::UpArrowCursor); break; + case kHand: fPaintWidget->setCursor(Qt::OpenHandCursor); break; + case kRotate: fPaintWidget->setCursor(Qt::ClosedHandCursor); break; + case kPointer: fPaintWidget->setCursor(Qt::ArrowCursor); break; + case kArrowRight: fPaintWidget->setCursor(Qt::SizeHorCursor); break; + case kCaret: fPaintWidget->setCursor(Qt::WaitCursor); break; + case kWatch: fPaintWidget->setCursor(Qt::WaitCursor); break; + case kNoDrop: fPaintWidget->setCursor(Qt::ForbiddenCursor); break; + default: + fPaintWidget->unsetCursor(); + } +} + //////////////////////////////////////////////////////////////////////////////// ///Noop, for non-gl pad TASImage calls gVirtualX->CopyArea. @@ -141,8 +171,11 @@ void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Double_t *xs, const Doubl return; QList points; - for (Int_t n = 0; n < nPoints; ++n) - points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + for (Int_t n = 0; n < nPoints; ++n) { + auto px = gPad->XtoAbsPixel(xs[n]); + auto py = gPad->YtoAbsPixel(ys[n]); + points.push_back({(qreal) px, (qreal) py}); + } painter->setPen(Qt::NoPen); painter->setBrush(GetFillBrush()); @@ -160,8 +193,11 @@ void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Float_t *xs, const Float_ return; QList points; - for (Int_t n = 0; n < nPoints; ++n) - points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + for (Int_t n = 0; n < nPoints; ++n) { + auto px = gPad->XtoAbsPixel(xs[n]); + auto py = gPad->YtoAbsPixel(ys[n]); + points.push_back({(qreal) px, (qreal) py}); + } painter->setPen(Qt::NoPen); painter->setBrush(GetFillBrush()); @@ -178,8 +214,11 @@ void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Double_t *xs, const Doubl return; QList points; - for (Int_t n = 0; n < nPoints; ++n) - points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + for (Int_t n = 0; n < nPoints; ++n) { + auto px = gPad->XtoAbsPixel(xs[n]); + auto py = gPad->YtoAbsPixel(ys[n]); + points.push_back({(qreal) px, (qreal) py}); + } painter->setPen(GetLinePen()); @@ -198,8 +237,11 @@ void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Float_t *xs, const Float_ return; QList points; - for (Int_t n = 0; n < nPoints; ++n) - points.push_back({gPad->XtoAbsPixel(xs[n]), gPad->YtoAbsPixel(ys[n])}); + for (Int_t n = 0; n < nPoints; ++n) { + auto px = gPad->XtoAbsPixel(xs[n]); + auto py = gPad->YtoAbsPixel(ys[n]); + points.push_back({(qreal) px, (qreal) py}); + } painter->setPen(GetLinePen()); @@ -218,8 +260,11 @@ void TQt6PadPainter::DrawPolyLineNDC(Int_t nPoints, const Double_t *u, const Dou return; QList points; - for (Int_t n = 0; n < nPoints; ++n) - points.push_back({gPad->UtoAbsPixel(u[n]), gPad->VtoAbsPixel(v[n])}); + for (Int_t n = 0; n < nPoints; ++n) { + auto px = gPad->UtoAbsPixel(u[n]); + auto py = gPad->VtoAbsPixel(v[n]); + points.push_back({(qreal) px, (qreal) py}); + } painter->setPen(GetLinePen()); @@ -339,7 +384,7 @@ void TQt6PadPainter::DrawTextNDC(Double_t u, Double_t v, const wchar_t *text, E //////////////////////////////////////////////////////////////////////////////// /// Produce image -void TQt6PadPainter::SaveImage(TVirtualPad *pad, const char *fileName, Int_t /* gtype */) const +void TQt6PadPainter::SaveImage(TVirtualPad * /* pad */, const char * /* fileName */, Int_t /* gtype */) const { } From 5ff28f21365f14258c6313c379ac241dbdc13c38 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 10:44:24 +0200 Subject: [PATCH 17/20] [qt6canv] introduce TQt6GuiFactory It fully dismiss standard GUI classes and only allows to create Qt6canvas. Aim to provide specialized implementation for application and context menu --- etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C | 5 ++ gui/qt6canvas/CMakeLists.txt | 2 + gui/qt6canvas/inc/LinkDef.h | 1 + gui/qt6canvas/inc/TQt6Canvas.h | 4 +- gui/qt6canvas/inc/TQt6GuiFactory.h | 38 ++++++++++ gui/qt6canvas/src/TQt6Canvas.cxx | 6 +- gui/qt6canvas/src/TQt6GuiFactory.cxx | 69 +++++++++++++++++++ 7 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C create mode 100644 gui/qt6canvas/inc/TQt6GuiFactory.h create mode 100644 gui/qt6canvas/src/TQt6GuiFactory.cxx diff --git a/etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C b/etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C new file mode 100644 index 0000000000000..18b654eac441c --- /dev/null +++ b/etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C @@ -0,0 +1,5 @@ +void P020_TQt6GuiFactory() +{ + gPluginMgr->AddHandler("TGuiFactory", "qt6", "TQt6GuiFactory", + "ROOTQt6Canvas", "TQt6GuiFactory()"); +} diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index 54fdc75e56713..006c229a1c1c3 100644 --- a/gui/qt6canvas/CMakeLists.txt +++ b/gui/qt6canvas/CMakeLists.txt @@ -30,12 +30,14 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas HEADERS TQt6Canvas.h TQt6PadPainter.h + TQt6GuiFactory.h SOURCES src/QCanvasWidget.cpp src/QPaintWidget.cpp src/QRootMethodDialog.cpp src/TQt6Canvas.cxx src/TQt6PadPainter.cxx + src/TQt6GuiFactory.cxx LIBRARIES Qt6::Widgets DEPENDENCIES diff --git a/gui/qt6canvas/inc/LinkDef.h b/gui/qt6canvas/inc/LinkDef.h index 64f79c00f1e72..a7ecc53c20666 100644 --- a/gui/qt6canvas/inc/LinkDef.h +++ b/gui/qt6canvas/inc/LinkDef.h @@ -16,5 +16,6 @@ #pragma link C++ class TQt6Canvas+; #pragma link C++ class TQt6PadPainter+; +#pragma link C++ class TQt6GuiFactory+; #endif diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index b43bd627c3bc7..60e0db0cebc3a 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -47,8 +47,8 @@ class TQt6Canvas : public TCanvasImp { UInt_t GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) override; void GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) override; - void ResizeCanvasWindow(Int_t ) override {} - + void ResizeCanvasWindow(Int_t) override {} + void UpdateDisplay(Int_t = 0, Bool_t = kFALSE) {} void ShowMenuBar(Bool_t = kTRUE) override { } void ShowStatusBar(Bool_t = kTRUE) override { } diff --git a/gui/qt6canvas/inc/TQt6GuiFactory.h b/gui/qt6canvas/inc/TQt6GuiFactory.h new file mode 100644 index 0000000000000..370f1760ccdad --- /dev/null +++ b/gui/qt6canvas/inc/TQt6GuiFactory.h @@ -0,0 +1,38 @@ +// Author: Sergey Linev 2/07/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_TQt6GuiFactory +#define ROOT_TQt6GuiFactory + + +#include "TGuiFactory.h" + + +class TQt6GuiFactory : public TGuiFactory { + +protected: + void ShowWebCanvasWarning(); + +public: + TQt6GuiFactory(const char *name = "RootQt6", const char *title = "ROOT Qt6 GUI Factory"); + ~TQt6GuiFactory() override {} + + 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(TQt6GuiFactory,0) //Factory for ROOT GUI components +}; + +#endif diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 84e07c96015c5..50745385bb035 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -348,10 +348,12 @@ TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y timer->TurnOn(); } - auto widget = new QCanvasWidget(); widget->setWindowTitle(QString(c->GetTitle())); - widget->setGeometry(x, y, width, height); + if ((x < 0) && (y < 0)) + widget->resize(width, height); + else + widget->setGeometry(x, y, width, height); widget->show(); auto imp = new TQt6Canvas(c, name, x, y, width, height); diff --git a/gui/qt6canvas/src/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx new file mode 100644 index 0000000000000..18e6511c276e4 --- /dev/null +++ b/gui/qt6canvas/src/TQt6GuiFactory.cxx @@ -0,0 +1,69 @@ +// Author: Sergey Linev 2/07/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 TQt6GuiFactory + \ingroup guiwidgets + +This class is a factory for ROOT GUI components. It overrides +the member functions of the ABS TGuiFactory. + +*/ + + +#include "TQt6GuiFactory.h" +#include "TQt6Canvas.h" + +#include + + +//////////////////////////////////////////////////////////////////////////////// +/// TQt6GuiFactory ctor. + +TQt6GuiFactory::TQt6GuiFactory(const char *name, const char *title) + : TGuiFactory(name, title) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TApplicationImp + +TApplicationImp *TQt6GuiFactory::CreateApplicationImp(const char *classname, + Int_t *argc, char **argv) +{ + return TGuiFactory::CreateApplicationImp(classname, argc, argv); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TCanvasImp + +TCanvasImp *TQt6GuiFactory::CreateCanvasImp(TCanvas *c, const char *title, + UInt_t width, UInt_t height) +{ + return TQt6Canvas::NewCanvas(c, title, -1, -1, width, height); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TCanvasImp + +TCanvasImp *TQt6GuiFactory::CreateCanvasImp(TCanvas *c, const char *title, + Int_t x, Int_t y, UInt_t width, UInt_t height) +{ + return TQt6Canvas::NewCanvas(c, title, x, y, width, height); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TContextMenuImp + +TContextMenuImp *TQt6GuiFactory::CreateContextMenuImp(TContextMenu *c, + const char *name, const char *title) +{ + return TGuiFactory::CreateContextMenuImp(c, name, title); +} From d8ab4f36aa2d8153503052bf50a335a74cdcaec7 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 11:41:41 +0200 Subject: [PATCH 18/20] [qt6canv] introduce QRootContextMenu It is implementation for context menu used inside qt6_canvas Idea to let handle right mouse button click in the canvas handler which will invoke implementation class --- gui/qt6canvas/CMakeLists.txt | 1 + gui/qt6canvas/inc/TQt6Canvas.h | 4 +- gui/qt6canvas/inc/TQt6ContextMenu.h | 37 +++++ gui/qt6canvas/inc/TQt6GuiFactory.h | 2 +- gui/qt6canvas/src/QPaintWidget.cpp | 124 +------------- gui/qt6canvas/src/QPaintWidget.h | 13 -- gui/qt6canvas/src/QRootContextMenu.cpp | 222 +++++++++++++++++++++++++ gui/qt6canvas/src/QRootContextMenu.h | 50 ++++++ gui/qt6canvas/src/TQt6Canvas.cxx | 1 - gui/qt6canvas/src/TQt6ContextMenu.cxx | 93 +++++++++++ gui/qt6canvas/src/TQt6GuiFactory.cxx | 7 +- 11 files changed, 412 insertions(+), 142 deletions(-) create mode 100644 gui/qt6canvas/inc/TQt6ContextMenu.h create mode 100644 gui/qt6canvas/src/QRootContextMenu.cpp create mode 100644 gui/qt6canvas/src/QRootContextMenu.h create mode 100644 gui/qt6canvas/src/TQt6ContextMenu.cxx diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index 006c229a1c1c3..4b32de50341ea 100644 --- a/gui/qt6canvas/CMakeLists.txt +++ b/gui/qt6canvas/CMakeLists.txt @@ -35,6 +35,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas src/QCanvasWidget.cpp src/QPaintWidget.cpp src/QRootMethodDialog.cpp + src/QRootContextMenu.cpp src/TQt6Canvas.cxx src/TQt6PadPainter.cxx src/TQt6GuiFactory.cxx diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index 60e0db0cebc3a..65459f388c283 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -41,6 +41,8 @@ class TQt6Canvas : public TCanvasImp { TQt6Canvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height); ~TQt6Canvas() override; + QPaintWidget *GetPaintWidget() const { return fPaintWidget; } + Int_t InitWindow() override; void Close() override; void Show() override; @@ -48,7 +50,7 @@ class TQt6Canvas : public TCanvasImp { UInt_t GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) override; void GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) override; void ResizeCanvasWindow(Int_t) override {} - void UpdateDisplay(Int_t = 0, Bool_t = kFALSE) {} + void UpdateDisplay(Int_t = 0, Bool_t = kFALSE) override {} void ShowMenuBar(Bool_t = kTRUE) override { } void ShowStatusBar(Bool_t = kTRUE) override { } diff --git a/gui/qt6canvas/inc/TQt6ContextMenu.h b/gui/qt6canvas/inc/TQt6ContextMenu.h new file mode 100644 index 0000000000000..6f3cb99dfcf1f --- /dev/null +++ b/gui/qt6canvas/inc/TQt6ContextMenu.h @@ -0,0 +1,37 @@ +// Author: Sergey Linev 2/07/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_QRootContextMenu +#define ROOT_QRootContextMenu + +#include "TContextMenuImp.h" +#include "TObject.h" + +class QRootContextMenu : public TObject, public TContextMenuImp { + +private: + + QRootContextMenu(const QRootContextMenu&) = delete; + QRootContextMenu& operator=(const QRootContextMenu&) = delete; + +public: + QRootContextMenu(TContextMenu *c = nullptr, const char *name = "ROOT Context Menu"); + ~QRootContextMenu() override; + + void DisplayPopup(Int_t x, Int_t y) override; + void Dialog(TObject *object, TMethod *method) override; + void Dialog(TObject *object, TFunction *function) override; + + void RecursiveRemove(TObject *obj) override; + + ClassDefOverride(QRootContextMenu,0) // Qt6 GUI context sensitive popup menu +}; + +#endif diff --git a/gui/qt6canvas/inc/TQt6GuiFactory.h b/gui/qt6canvas/inc/TQt6GuiFactory.h index 370f1760ccdad..6490e1569ef46 100644 --- a/gui/qt6canvas/inc/TQt6GuiFactory.h +++ b/gui/qt6canvas/inc/TQt6GuiFactory.h @@ -32,7 +32,7 @@ class TQt6GuiFactory : public TGuiFactory { TContextMenuImp *CreateContextMenuImp(TContextMenu *c, const char *name, const char *title) override; - ClassDefOverride(TQt6GuiFactory,0) //Factory for ROOT GUI components + ClassDefOverride(TQt6GuiFactory,0) //Factory for Qt6 GUI components }; #endif diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp index 287db9e35d3ee..9655e6707d7d3 100644 --- a/gui/qt6canvas/src/QPaintWidget.cpp +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -14,18 +14,12 @@ #include "TCanvas.h" #include "TROOT.h" -#include "TMethod.h" -#include "QRootMethodDialog.h" - -#include #include #include #include #include #include -#include -#include #include #include #include @@ -102,50 +96,7 @@ void QPaintWidget::mousePressEvent( QMouseEvent *e ) // emit PadClicked(pad, scaled.x(), scaled.y()); break; case Qt::RightButton : { - TString selectedOpt; - if (pad) { - if (!pickobj) { - fCanvas->SetSelected(pad); - selected = pad; - } else if(!selected) { - selected = pickobj->GetObject(); - selectedOpt = pickobj->GetOption(); - } - pad->cd(); - } - fCanvas->SetSelectedPad(pad); - gROOT->SetSelectedPrimitive(selected); - fMousePosX = gPad->AbsPixeltoX(gPad->GetEventX()); - fMousePosY = gPad->AbsPixeltoY(gPad->GetEventY()); - - QMenu menu; - QSignalMapper map; - - QObject::connect(&map, &QSignalMapper::mappedInt, - this, &QPaintWidget::executeMenu); - fMenuObj = selected; - fMenuMethods = new TList; - TClass *cl = fMenuObj->IsA(); - int curId = -1; - - QString buffer = TString::Format("%s::%s", cl->GetName(), fMenuObj->GetName()).Data(); - addMenuAction(&menu, &map, buffer, curId++); - - cl->GetMenuItems(fMenuMethods); - menu.addSeparator(); - - TIter iter(fMenuMethods); - while (auto method = dynamic_cast(iter())) { - buffer = method->GetName(); - addMenuAction(&menu, &map, buffer, curId++); - } - - if (menu.exec(menu_pnt) == nullptr) { - fMenuObj = nullptr; - delete fMenuMethods; - fMenuMethods = nullptr; - } - + fCanvas->HandleInput(kButton3Down, scaled.x(), scaled.y()); break; } case Qt::MiddleButton : @@ -250,76 +201,3 @@ void QPaintWidget::mouseDoubleClickEvent( QMouseEvent *e ) } e->accept(); } - -QAction* QPaintWidget::addMenuAction(QMenu* menu, QSignalMapper* map, const QString& text, int id) -{ - bool enabled = true; - - QAction* act = new QAction(text, menu); - - if (!enabled) - if ((text.compare("DrawClone") == 0) || (text.compare("DrawClass") == 0) || (text.compare("Inspect") == 0) || - (text.compare("SetShowProjectionX") == 0) || (text.compare("SetShowProjectionY") == 0) || - (text.compare("DrawPanel") == 0) || (text.compare("FitPanel") == 0)) - act->setEnabled(false); - - QObject::connect(act, &QAction::triggered, [id, map]() { - map->mappedInt(id); - }); - - menu->addAction(act); - map->setMapping(act, id); - - return act; -} - -void QPaintWidget::executeMenu(int id) -{ - QString text; - bool ok = false; - if (id >= 0) { - - // save global to Pad before calling TObject::Execute() - - TVirtualPad *psave = gROOT->GetSelectedPad(); - TMethod *method = (TMethod *) fMenuMethods->At(id); - - /// test: do this in any case! - fCanvas->HandleInput(kButton3Up, gPad->XtoAbsPixel(fMousePosX), gPad->YtoAbsPixel(fMousePosY)); - - // change current dir that all new histograms appear here - gROOT->cd(); - - if (method->GetListOfMethodArgs()->First()) { - QRootMethodDialog dlg; - - dlg.methodDialog(fMenuObj, method); - } else { - gROOT->SetFromPopUp(kTRUE); - fMenuObj->Execute(method->GetName(), ""); - - if (fMenuObj->TestBit(TObject::kNotDeleted)) { - // emit MenuCommandExecuted(fMenuObj, method->GetName()); - } else { - fMenuObj = nullptr; - } - - } - - fCanvas->GetPadSave()->Update(); - fCanvas->GetPadSave()->Modified(); - - gROOT->SetSelectedPad(psave); - - gROOT->GetSelectedPad()->Update(); - gROOT->GetSelectedPad()->Modified(); - - fCanvas->Modified(); - fCanvas->ForceUpdate(); - gROOT->SetFromPopUp(kFALSE); - } - - fMenuObj = nullptr; - delete fMenuMethods; - fMenuMethods = nullptr; -} diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h index ec9efa516a597..0673b8da6e76b 100644 --- a/gui/qt6canvas/src/QPaintWidget.h +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -15,11 +15,8 @@ class TCanvas; class TPad; -class TObject; -class TList; class TMethod; class QPainter; -class QSignalMapper; class QStatusBar; class QPaintWidget : public QWidget { @@ -39,9 +36,6 @@ class QPaintWidget : public QWidget { void SetStatusBar(QStatusBar *bar) { fStatusBar = bar; } -public slots: - void executeMenu(int id); - protected: void resizeEvent(QResizeEvent *event) override; @@ -52,8 +46,6 @@ public slots: void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent* event) override; - QAction* addMenuAction(QMenu *menu, QSignalMapper *map, const QString &text, int id); - double scaledPosition(int p) { return (double) p * fQtScalingfactor; } QPoint scaledMousePoint(QMouseEvent *event); @@ -67,11 +59,6 @@ public slots: QStatusBar *fStatusBar = nullptr; bool fMaskDoubleClick = false; - double fMousePosX = 0; // mouse position in user coordinate when activate menu - double fMousePosY = 0; // mouse position in user coordinate when activate menu - - TObject *fMenuObj = nullptr; // object use to fill menu - TList *fMenuMethods = nullptr; // list of menu methods bool fShowEventStatus = true; }; diff --git a/gui/qt6canvas/src/QRootContextMenu.cpp b/gui/qt6canvas/src/QRootContextMenu.cpp new file mode 100644 index 0000000000000..dc221e01eb1b3 --- /dev/null +++ b/gui/qt6canvas/src/QRootContextMenu.cpp @@ -0,0 +1,222 @@ +// Author: Sergey Linev 2/07/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 RRootContextMenu + \ingroup qt6canvas + +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. +The picture below shows a canvas with a pop-up menu. + +*/ + + +#include "QRootContextMenu.h" + +#include "TROOT.h" +#include "TContextMenu.h" +#include "TCanvas.h" +#include "TMethod.h" + + +#include "QRootMethodDialog.h" +#include "QPaintWidget.h" +#include "TQt6Canvas.h" + +#include +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +/// Create context menu. + +QRootContextMenu::QRootContextMenu(TContextMenu *c, const char *) + : QObject(), TObject(), TContextMenuImp(c) +{ + gROOT->GetListOfCleanups()->Add(this); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Delete a context menu. + +QRootContextMenu::~QRootContextMenu() +{ + gROOT->GetListOfCleanups()->Remove(this); + + fMenuObj = nullptr; + delete fMenuMethods; + fMenuMethods = nullptr; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Display context popup menu for currently selected object. + +void QRootContextMenu::DisplayPopup(Int_t x, Int_t y) +{ + // add menu items to popup menu + // CreateMenu(fContextMenu->GetSelectedObject()); + + fMenuObj = nullptr; + delete fMenuMethods; + fMenuMethods = nullptr; + + printf("Start menu for %p\n", fContextMenu->GetSelectedObject()); + + auto canv = dynamic_cast(fContextMenu->GetSelectedCanvas()); + auto canvimp = dynamic_cast(canv->GetCanvasImp()); + auto widget = canvimp->GetPaintWidget(); + + QPoint screenPos = widget->mapToGlobal(widget->rect().topLeft()); + + QMenu menu; + QSignalMapper map; + + QObject::connect(&map, &QSignalMapper::mappedInt, + this, &QRootContextMenu::executeMenu); + + fMousePosX = x; + fMousePosY = y; + + fMenuObj = fContextMenu->GetSelectedObject(); + fMenuMethods = new TList; + TClass *cl = fMenuObj->IsA(); + int curId = -1; + + QString buffer = TString::Format("%s::%s", cl->GetName(), fMenuObj->GetName()).Data(); + addMenuAction(&menu, &map, buffer, curId++); + + cl->GetMenuItems(fMenuMethods); + menu.addSeparator(); + + TIter iter(fMenuMethods); + while (auto method = dynamic_cast(iter())) { + buffer = method->GetName(); + addMenuAction(&menu, &map, buffer, curId++); + } + + if (menu.exec(screenPos + QPoint(x, y)) == nullptr) { + fMenuObj = nullptr; + delete fMenuMethods; + fMenuMethods = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create dialog object with OK and Cancel buttons. This dialog +/// prompts for the arguments of "method". + +void QRootContextMenu::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 QRootContextMenu::Dialog(TObject * /* object */, TFunction * /* function */) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Close the context menu if the object is deleted in the +/// RecursiveRemove() operation. + +void QRootContextMenu::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); + } +} + +QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const QString& text, int id) +{ + bool enabled = true; + + QAction* act = new QAction(text, menu); + + if (!enabled) + if ((text.compare("DrawClone") == 0) || (text.compare("DrawClass") == 0) || (text.compare("Inspect") == 0) || + (text.compare("SetShowProjectionX") == 0) || (text.compare("SetShowProjectionY") == 0) || + (text.compare("DrawPanel") == 0) || (text.compare("FitPanel") == 0)) + act->setEnabled(false); + + QObject::connect(act, &QAction::triggered, [id, map]() { + map->mappedInt(id); + }); + + menu->addAction(act); + map->setMapping(act, id); + + return act; +} + +void QRootContextMenu::executeMenu(int id) +{ + QString text; + bool ok = false; + if (id >= 0) { + + // save global to Pad before calling TObject::Execute() + + TVirtualPad *psave = gROOT->GetSelectedPad(); + + auto canv = dynamic_cast(fContextMenu->GetSelectedCanvas()); + + TMethod *method = (TMethod *) fMenuMethods->At(id); + + /// test: do this in any case! + canv->HandleInput(kButton3Up, gPad->XtoAbsPixel(fMousePosX), gPad->YtoAbsPixel(fMousePosY)); + + // change current dir that all new histograms appear here + gROOT->cd(); + + if (method->GetListOfMethodArgs()->First()) { + QRootMethodDialog dlg; + + dlg.methodDialog(fMenuObj, method); + } else { + gROOT->SetFromPopUp(kTRUE); + fMenuObj->Execute(method->GetName(), ""); + + if (fMenuObj->TestBit(TObject::kNotDeleted)) { + // emit MenuCommandExecuted(fMenuObj, method->GetName()); + } else { + fMenuObj = nullptr; + } + + } + + canv->GetPadSave()->Update(); + canv->GetPadSave()->Modified(); + + gROOT->SetSelectedPad(psave); + + gROOT->GetSelectedPad()->Update(); + gROOT->GetSelectedPad()->Modified(); + + canv->Modified(); + canv->ForceUpdate(); + gROOT->SetFromPopUp(kFALSE); + } + + fMenuObj = nullptr; + delete fMenuMethods; + fMenuMethods = nullptr; +} diff --git a/gui/qt6canvas/src/QRootContextMenu.h b/gui/qt6canvas/src/QRootContextMenu.h new file mode 100644 index 0000000000000..159d567ee6edb --- /dev/null +++ b/gui/qt6canvas/src/QRootContextMenu.h @@ -0,0 +1,50 @@ +// Author: Sergey Linev 2/07/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_QRootContextMenu +#define ROOT_QRootContextMenu + +#include +#include +#include "TContextMenuImp.h" +#include "TObject.h" + +class QSignalMapper; +class TList; +class QAction; +class QMenu; + +class QRootContextMenu : public QObject, public TObject, public TContextMenuImp { + Q_OBJECT + +public slots: + void executeMenu(int id); + +protected: + int fMousePosX = 0; // mouse position in user coordinate when activate menu + int fMousePosY = 0; // mouse position in user coordinate when activate menu + + TObject *fMenuObj = nullptr; // object use to fill menu + TList *fMenuMethods = nullptr; // list of menu methods + + QAction* addMenuAction(QMenu *menu, QSignalMapper *map, const QString &text, int id); + +public: + QRootContextMenu(TContextMenu *c = nullptr, const char *name = "ROOT Context Menu"); + ~QRootContextMenu() override; + + void DisplayPopup(Int_t x, Int_t y) override; + void Dialog(TObject *object, TMethod *method) override; + void Dialog(TObject *object, TFunction *function) override; + + void RecursiveRemove(TObject *obj) override; +}; + +#endif diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 50745385bb035..29f117058e540 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -53,7 +53,6 @@ class TQt6CanvasTimer : public TTimer { /** \class TQt6Canvas \ingroup qt6canvas \brief Basic TCanvasImp ABI implementation for Qt6 - */ using namespace std::string_literals; diff --git a/gui/qt6canvas/src/TQt6ContextMenu.cxx b/gui/qt6canvas/src/TQt6ContextMenu.cxx new file mode 100644 index 0000000000000..a432f6a015206 --- /dev/null +++ b/gui/qt6canvas/src/TQt6ContextMenu.cxx @@ -0,0 +1,93 @@ +// Author: Sergey Linev 2/07/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 QRootContextMenu + \ingroup guiwidgets + +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. +The picture below shows a canvas with a pop-up menu. + +*/ + + +#include "QRootContextMenu.h" + +#include "TROOT.h" +#include "TContextMenu.h" +#include "TVirtualPad.h" + + +//////////////////////////////////////////////////////////////////////////////// +/// Create context menu. + +QRootContextMenu::QRootContextMenu(TContextMenu *c, const char *) + : TObject(), TContextMenuImp(c) +{ + gROOT->GetListOfCleanups()->Add(this); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Delete a context menu. + +QRootContextMenu::~QRootContextMenu() +{ + gROOT->GetListOfCleanups()->Remove(this); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Display context popup menu for currently selected object. + +void QRootContextMenu::DisplayPopup(Int_t x, Int_t y) +{ + // add menu items to popup menu + // CreateMenu(fContextMenu->GetSelectedObject()); + + printf("Start menu for %p\n", fContextMenu->GetSelectedObject()); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Create dialog object with OK and Cancel buttons. This dialog +/// prompts for the arguments of "method". + +void QRootContextMenu::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 QRootContextMenu::Dialog(TObject *object, TFunction *function) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Close the context menu if the object is deleted in the +/// RecursiveRemove() operation. + +void QRootContextMenu::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); + } +} + diff --git a/gui/qt6canvas/src/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx index 18e6511c276e4..c7404c41fc76c 100644 --- a/gui/qt6canvas/src/TQt6GuiFactory.cxx +++ b/gui/qt6canvas/src/TQt6GuiFactory.cxx @@ -10,7 +10,7 @@ /** \class TQt6GuiFactory - \ingroup guiwidgets + \ingroup qt6canvas This class is a factory for ROOT GUI components. It overrides the member functions of the ABS TGuiFactory. @@ -20,6 +20,7 @@ the member functions of the ABS TGuiFactory. #include "TQt6GuiFactory.h" #include "TQt6Canvas.h" +#include "QRootContextMenu.h" #include @@ -63,7 +64,7 @@ TCanvasImp *TQt6GuiFactory::CreateCanvasImp(TCanvas *c, const char *title, /// Create a ROOT native GUI version of TContextMenuImp TContextMenuImp *TQt6GuiFactory::CreateContextMenuImp(TContextMenu *c, - const char *name, const char *title) + const char *name, const char *) { - return TGuiFactory::CreateContextMenuImp(c, name, title); + return new QRootContextMenu(c, name); } From 6009e51438784327c33db8770d095ceaeb0118e3 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 14:23:49 +0200 Subject: [PATCH 19/20] [qt6canv] use list of TContextMenuItem from TClass This is default place where all normal or custom items for the class are collected. By large copy code from TRootContextMenu. --- gui/qt6canvas/src/QRootContextMenu.cpp | 262 ++++++++++++++++++------- gui/qt6canvas/src/QRootContextMenu.h | 12 +- 2 files changed, 193 insertions(+), 81 deletions(-) diff --git a/gui/qt6canvas/src/QRootContextMenu.cpp b/gui/qt6canvas/src/QRootContextMenu.cpp index dc221e01eb1b3..ab68947e53714 100644 --- a/gui/qt6canvas/src/QRootContextMenu.cpp +++ b/gui/qt6canvas/src/QRootContextMenu.cpp @@ -26,7 +26,9 @@ The picture below shows a canvas with a pop-up menu. #include "TContextMenu.h" #include "TCanvas.h" #include "TMethod.h" - +#include "TDataMember.h" +#include "TToggle.h" +#include "TClassMenuItem.h" #include "QRootMethodDialog.h" #include "QPaintWidget.h" @@ -36,6 +38,12 @@ The picture below shows a canvas with a pop-up menu. #include #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. @@ -51,10 +59,7 @@ QRootContextMenu::QRootContextMenu(TContextMenu *c, const char *) QRootContextMenu::~QRootContextMenu() { gROOT->GetListOfCleanups()->Remove(this); - - fMenuObj = nullptr; - delete fMenuMethods; - fMenuMethods = nullptr; + fTrash.Delete(); } //////////////////////////////////////////////////////////////////////////////// @@ -65,11 +70,13 @@ void QRootContextMenu::DisplayPopup(Int_t x, Int_t y) // add menu items to popup menu // CreateMenu(fContextMenu->GetSelectedObject()); - fMenuObj = nullptr; - delete fMenuMethods; - fMenuMethods = nullptr; + auto object = fContextMenu->GetSelectedObject(); + if (!object) + return; + + fCustomArg.clear(); + fTrash.Delete(); - printf("Start menu for %p\n", fContextMenu->GetSelectedObject()); auto canv = dynamic_cast(fContextMenu->GetSelectedCanvas()); auto canvimp = dynamic_cast(canv->GetCanvasImp()); @@ -83,31 +90,148 @@ void QRootContextMenu::DisplayPopup(Int_t x, Int_t y) QObject::connect(&map, &QSignalMapper::mappedInt, this, &QRootContextMenu::executeMenu); - fMousePosX = x; - fMousePosY = y; + // Add a title + QString buffer = fContextMenu->CreatePopupTitle(object); + addMenuAction(&menu, &map, buffer, -1, nullptr); + menu.addSeparator(); + bool last_separ = true; - fMenuObj = fContextMenu->GetSelectedObject(); - fMenuMethods = new TList; - TClass *cl = fMenuObj->IsA(); - int curId = -1; - QString buffer = TString::Format("%s::%s", cl->GetName(), fMenuObj->GetName()).Data(); - addMenuAction(&menu, &map, buffer, curId++); - cl->GetMenuItems(fMenuMethods); - menu.addSeparator(); + // addMenuAction(&menu, &map, buffer, curId++); - TIter iter(fMenuMethods); - while (auto method = dynamic_cast(iter())) { - buffer = method->GetName(); - addMenuAction(&menu, &map, buffer, curId++); - } + 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(); - if (menu.exec(screenPos + QPoint(x, y)) == nullptr) { - fMenuObj = nullptr; - delete fMenuMethods; - fMenuMethods = nullptr; + TIter nextItem(menuItemList); + + while (auto menuItem = (TClassMenuItem*) nextItem()) { + switch (menuItem->GetType()) { + case TClassMenuItem::kPopupSeparator: { + if (!last_separ) + menu.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) { + menu.addSeparator(); + needSep = kFALSE; + } + addMenuAction(&menu, &map, method->GetName(), entry++, method); + break; + case kMenuSubMenu: + if (auto m = method->FindDataMember()) { + if (needSep) { + menu.addSeparator(); + needSep = kFALSE; + } + + if (m->GetterMethod()) { + + QMenu *r = menu.addMenu(method->GetName()); + TIter nxt(m->GetOptions()); + while (auto it = (TOptionListItem*) nxt()) { + const char *name = it->fOptName; + Long_t val = it->fValue; + + TToggle *t = new TToggle; + t->SetToggledObject(object, method); + t->SetOnValue(val); + fTrash.Add(t); + + auto act = addMenuAction(r, &map, name, togglelist++, t); + act->setCheckable(true); + if (t->GetState()) + act->setChecked(true); + } + } else { + addMenuAction(&menu, &map, method->GetName(), entry++, method); + } + } + break; + + case kMenuToggle: { + if (needSep) { + menu.addSeparator(); + needSep = kFALSE; + } + + TToggle *t = new TToggle; + t->SetToggledObject(object, method); + t->SetOnValue(1); + fTrash.Add(t); + + auto act = addMenuAction(&menu, &map, method->GetName(), toggle++, t); + act->setCheckable(true); + if (t->GetState()) + act->setChecked(true); + 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(); + auto act = addMenuAction(&menu, &map, menuItemTitle, toggle++, t); + act->setCheckable(true); + if (t->GetState()) + act->setChecked(true); + } + } else { + if (strlen(menuItemTitle)==0) + menuItemTitle = menuItem->GetFunctionName(); + addMenuAction(&menu, &map, menuItemTitle, userfunction++, menuItem); + } + break; + } + + default: + break; + } } + + menu.exec(screenPos + QPoint(x, y)); } //////////////////////////////////////////////////////////////////////////////// @@ -145,7 +269,7 @@ void QRootContextMenu::RecursiveRemove(TObject *obj) } } -QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const QString& text, int id) +QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const QString& text, int id, void *arg) { bool enabled = true; @@ -164,59 +288,47 @@ QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const menu->addAction(act); map->setMapping(act, id); + fCustomArg[id] = arg; + return act; } void QRootContextMenu::executeMenu(int id) { - QString text; - bool ok = false; - if (id >= 0) { - - // save global to Pad before calling TObject::Execute() - - TVirtualPad *psave = gROOT->GetSelectedPad(); - - auto canv = dynamic_cast(fContextMenu->GetSelectedCanvas()); - - TMethod *method = (TMethod *) fMenuMethods->At(id); - - /// test: do this in any case! - canv->HandleInput(kButton3Up, gPad->XtoAbsPixel(fMousePosX), gPad->YtoAbsPixel(fMousePosY)); - - // change current dir that all new histograms appear here - gROOT->cd(); - - if (method->GetListOfMethodArgs()->First()) { - QRootMethodDialog dlg; - - dlg.methodDialog(fMenuObj, method); + 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 { - gROOT->SetFromPopUp(kTRUE); - fMenuObj->Execute(method->GetName(), ""); - - if (fMenuObj->TestBit(TObject::kNotDeleted)) { - // emit MenuCommandExecuted(fMenuObj, method->GetName()); - } else { - fMenuObj = nullptr; - } - + TClassMenuItem *mi = (TClassMenuItem *)ud; + function = gROOT->GetGlobalFunctionWithPrototype(mi->GetFunctionName()); } - - canv->GetPadSave()->Update(); - canv->GetPadSave()->Modified(); - - gROOT->SetSelectedPad(psave); - - gROOT->GetSelectedPad()->Update(); - gROOT->GetSelectedPad()->Modified(); - - canv->Modified(); - canv->ForceUpdate(); - gROOT->SetFromPopUp(kFALSE); + if (function) + fContextMenu->SetMethod(function); } - fMenuObj = nullptr; - delete fMenuMethods; - fMenuMethods = nullptr; + if (id < kToggleStart) { + TMethod *m = (TMethod *) ud; + 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/qt6canvas/src/QRootContextMenu.h b/gui/qt6canvas/src/QRootContextMenu.h index 159d567ee6edb..303102f5e11c5 100644 --- a/gui/qt6canvas/src/QRootContextMenu.h +++ b/gui/qt6canvas/src/QRootContextMenu.h @@ -15,6 +15,8 @@ #include #include "TContextMenuImp.h" #include "TObject.h" +#include "TList.h" +#include class QSignalMapper; class TList; @@ -28,13 +30,11 @@ public slots: void executeMenu(int id); protected: - int fMousePosX = 0; // mouse position in user coordinate when activate menu - int fMousePosY = 0; // mouse position in user coordinate when activate menu + TObject *fMenuObj = nullptr; // object use to fill menu - TObject *fMenuObj = nullptr; // object use to fill menu - TList *fMenuMethods = nullptr; // list of menu methods - - QAction* addMenuAction(QMenu *menu, QSignalMapper *map, const QString &text, int id); + TList fTrash; + std::map fCustomArg; + QAction* addMenuAction(QMenu *menu, QSignalMapper *map, const QString &text, int id, void *arg = nullptr); public: QRootContextMenu(TContextMenu *c = nullptr, const char *name = "ROOT Context Menu"); From 7a2af2b857b5b681b0719962a1600e9e46069ed8 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 14:49:32 +0200 Subject: [PATCH 20/20] [qt6canv] implement arguments dialog for context menu Reuse several methods from TContextMenu to create labels for arguments and afterwards execute selected method with provided list of arguments. --- gui/qt6canvas/src/QRootContextMenu.cpp | 4 +- gui/qt6canvas/src/QRootMethodDialog.cpp | 73 ++++++------------------- gui/qt6canvas/src/QRootMethodDialog.h | 5 +- 3 files changed, 23 insertions(+), 59 deletions(-) diff --git a/gui/qt6canvas/src/QRootContextMenu.cpp b/gui/qt6canvas/src/QRootContextMenu.cpp index ab68947e53714..985a3b452cd92 100644 --- a/gui/qt6canvas/src/QRootContextMenu.cpp +++ b/gui/qt6canvas/src/QRootContextMenu.cpp @@ -248,8 +248,10 @@ void QRootContextMenu::Dialog(TObject *object, TMethod *method) /// prompts for the arguments of "function". /// function may be a global function or a method -void QRootContextMenu::Dialog(TObject * /* object */, TFunction * /* function */) +void QRootContextMenu::Dialog(TObject * object, TFunction * func) { + QRootMethodDialog dlg; + dlg.methodDialog(fContextMenu, object, func); } //////////////////////////////////////////////////////////////////////////////// diff --git a/gui/qt6canvas/src/QRootMethodDialog.cpp b/gui/qt6canvas/src/QRootMethodDialog.cpp index ee428fd15f0aa..346102d4e0497 100644 --- a/gui/qt6canvas/src/QRootMethodDialog.cpp +++ b/gui/qt6canvas/src/QRootMethodDialog.cpp @@ -28,6 +28,7 @@ #include "TMethodArg.h" #include "TMethodCall.h" #include "TObjString.h" +#include "TContextMenu.h" #include @@ -79,24 +80,21 @@ QString QRootMethodDialog::getArg(int n) } -void QRootMethodDialog::methodDialog(TObject *object, TMethod* method) +void QRootMethodDialog::methodDialog(TContextMenu *menu, TObject *object, TFunction* func) { - if (!object || !method) + if (!menu || !object || !func) return; - setWindowTitle(TString::Format("%s:%s", object->GetName(), method->GetName()).Data()); + setWindowTitle(menu->CreateDialogTitle(object, func)); // iterate through all arguments and create appropriate input-data objects: // inputlines, option menus... - TIter next(method->GetListOfMethodArgs()); + TIter next(func->GetListOfMethodArgs()); while (auto argument = (TMethodArg *) next()) { - TString argTitle = TString::Format("(%s) %s", argument->GetTitle(), argument->GetName()); - TString argDflt = argument->GetDefault() ? argument->GetDefault() : ""; - if (argDflt.Length() > 0) - argTitle += TString::Format(" [default: %s]", argDflt.Data()); + TString argTitle = menu->CreateArgumentTitle(argument); TString type = argument->GetTypeName(); - TDataType *datatype = gROOT->GetType(type); + TDataType *datatype = gROOT->GetType(type); TString basictype; if (datatype) { @@ -134,6 +132,7 @@ void QRootMethodDialog::methodDialog(TObject *object, TMethod* method) } else if ((basictype == "char") || (basictype == "int") || + (basictype == "bool") || (basictype == "long") || (basictype == "short")) { Long_t ldefval = 0; @@ -154,6 +153,10 @@ void QRootMethodDialog::methodDialog(TObject *object, TMethod* method) 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 @@ -165,56 +168,14 @@ void QRootMethodDialog::methodDialog(TObject *object, TMethod* method) } } - if (exec() != QDialog::Accepted) return; - - Bool_t deletion = kFALSE; - - qDebug("DIAL executeMethod: simple version\n"); - TVirtualPad *psave = gROOT->GetSelectedPad(); - - qDebug("DIAL saved pad: %s gPad:%s \n",psave->GetName(),gPad->GetName()); - - qDebug("DIAL obj:%s meth:%s \n", object->GetName(), method->GetName()); + if (exec() != QDialog::Accepted) + return; - TObjArray tobjlist(method->GetListOfMethodArgs()->LastIndex() + 1); - for (int n = 0; n <= method->GetListOfMethodArgs()->LastIndex(); n++) { + TObjArray tobjlist(func->GetListOfMethodArgs()->LastIndex() + 1); + for (int n = 0; n <= func->GetListOfMethodArgs()->LastIndex(); n++) { QString s = getArg(n); - qDebug( "** QString values (first ) :%s \n", s.toLatin1().constData() ); tobjlist.AddLast(new TObjString(s.toLatin1().constData())); } - // handle command if existing object - if(strcmp(method->GetName(),"Delete") == 0) { - // here call explicitly the dtor - qDebug(" DIAL obj name deleted :%s \n", object->GetName()); - emit MenuCommandExecuted(object, "Delete"); - delete object; - object = nullptr; - deletion = kTRUE; - qDebug(" DIAL deletion done closing ... \n"); - } else { - // here call cint call - qDebug("TCint::Execute called !\n"); - - object->Execute(method, &tobjlist); - - if (object->TestBit(TObject::kNotDeleted)) { - emit MenuCommandExecuted(object, method->GetName()); - } else { - deletion = true; - object = nullptr; - } - } - - if(!deletion ) { - qDebug("DIAL set saved pad: %s herit:%s gPad:%s\n", - psave->GetName(), psave->ClassName(), gPad->GetName()); - gROOT->SetSelectedPad(psave); - gROOT->GetSelectedPad()->Modified(); - gROOT->GetSelectedPad()->Update(); - qDebug("DIAL update done on %s \n", gROOT->GetSelectedPad()->GetName()); - } else { - gROOT->SetSelectedPad(gPad); - gROOT->GetSelectedPad()->Update(); - } + menu->Execute(object, func, &tobjlist); } diff --git a/gui/qt6canvas/src/QRootMethodDialog.h b/gui/qt6canvas/src/QRootMethodDialog.h index 134e68c628cfc..c14d4ff2c931c 100644 --- a/gui/qt6canvas/src/QRootMethodDialog.h +++ b/gui/qt6canvas/src/QRootMethodDialog.h @@ -22,7 +22,8 @@ class QLineEdit; class QVBoxLayout; class TObject; -class TMethod; +class TFunction; +class TContextMenu; class QRootMethodDialog: public QDialog { Q_OBJECT @@ -34,7 +35,7 @@ class QRootMethodDialog: public QDialog { QString getArg(int n); - void methodDialog(TObject *object, TMethod* method); + void methodDialog(TContextMenu *menu, TObject *object, TFunction* func); signals: void MenuCommandExecuted(TObject *obj, const char *method_name);