From 53e3ba5c6d15cc816fb6262a6bffb048ec397ab3 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 10:15:04 +0200 Subject: [PATCH 01/30] [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 7051228f34bf4..b2053dcdcb113 100644 --- a/.github/workflows/root-ci-config/buildconfig/global.txt +++ b/.github/workflows/root-ci-config/buildconfig/global.txt @@ -64,6 +64,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 f943efe1d4edc..450bdc9286584 100644 --- a/cmake/modules/RootBuildOptions.cmake +++ b/cmake/modules/RootBuildOptions.cmake @@ -149,6 +149,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).") @@ -224,6 +225,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 97ecaf9b5a7b5cf25efbe34dae6da4094605ecbf Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 10:16:15 +0200 Subject: [PATCH 02/30] [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 | 75 ++++++++ gui/qt6canvas/inc/TQt6PadPainter.h | 88 +++++++++ gui/qt6canvas/index.md | 3 + gui/qt6canvas/src/TQt6Canvas.cxx | 258 +++++++++++++++++++++++++++ gui/qt6canvas/src/TQt6PadPainter.cxx | 184 +++++++++++++++++++ 9 files changed, 671 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..4e3855989f0ad --- /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 ROOT::Experimental::TQt6Canvas+; +#pragma link C++ class ROOT::Experimental::TQt6PadPainter+; + +#endif diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h new file mode 100644 index 0000000000000..fd56e6b160a18 --- /dev/null +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -0,0 +1,75 @@ +// 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; + +namespace ROOT { +namespace Experimental { + + +class TQt6Canvas : public TCanvasImp { + +protected: + + TQt6CanvasTimer *fTimer = nullptr; /// +#include +#include +#include +#include +#include +#include + +using namespace ROOT::Experimental; + +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); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// 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; +} diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx new file mode 100644 index 0000000000000..1fb41f9706f00 --- /dev/null +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -0,0 +1,184 @@ +// 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" + +using namespace ROOT::Experimental; + +/** \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 4f230fea5db0c9c53811c82cb1956942b10cbb6e Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 11:27:38 +0200 Subject: [PATCH 03/30] [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..43cc3be5095eb --- /dev/null +++ b/etc/plugins/TCanvasImp/P020_TQt6Canvas.C @@ -0,0 +1,5 @@ +void P020_TQt6Canvas() +{ + gPluginMgr->AddHandler("TCanvasImp", "TQt6Canvas", "ROOT::Experimental::TQt6Canvas", + "ROOTQt6Canvas", "NewCanvas(TCanvas *, const char *, Int_t, Int_t, UInt_t, UInt_t)"); +} From 0ad09ae960534ff9dbeb8a99ef9e4e39d89c18b4 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 13:29:23 +0200 Subject: [PATCH 04/30] [qt6canv] introduce QCanvasWidget It implements Qt6 widget with menu, status line and canvas paint area. Details to be implemented --- gui/qt6canvas/CMakeLists.txt | 9 +- gui/qt6canvas/inc/TQt6Canvas.h | 6 +- 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 | 49 +++++++++- 7 files changed, 337 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..efb6f4719298a 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,12 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas TQt6Canvas.h TQt6PadPainter.h SOURCES + src/QCanvasWidget.cpp src/TQt6Canvas.cxx src/TQt6PadPainter.cxx LIBRARIES - Qt6::Gui + Qt6::Core + Qt6::Widgets DEPENDENCIES Core Gpad diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index fd56e6b160a18..5a12eb34129cd 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -13,7 +13,7 @@ #include "TCanvasImp.h" -class TQt6CanvasTimer; +class QWidget; namespace ROOT { namespace Experimental { @@ -23,7 +23,8 @@ 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 ada738fc14fe4..74cc57a9fba1a 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 "TClass.h" #include @@ -27,17 +29,22 @@ #include #include +#include +#include "QCanvasWidget.h" + using namespace ROOT::Experimental; 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(); } }; @@ -63,9 +70,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; } @@ -76,7 +83,7 @@ TQt6Canvas::TQt6Canvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t wi TQt6Canvas::~TQt6Canvas() { - delete fTimer; + // delete fTimer; } @@ -243,8 +250,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 db48d81664409e3adab7c54f46710a8e42385a19 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 26 Jun 2026 16:00:25 +0200 Subject: [PATCH 05/30] [qt6canv] instroduce special widget for paint area --- gui/qt6canvas/inc/TQt6Canvas.h | 4 ++++ gui/qt6canvas/src/QCanvasWidget.cpp | 7 +++++++ gui/qt6canvas/src/TQt6Canvas.cxx | 27 +++++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index 5a12eb34129cd..e65f2f2517d41 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -14,6 +14,7 @@ #include "TCanvasImp.h" class QWidget; +class QCanvasWidget; namespace ROOT { namespace Experimental { @@ -23,6 +24,7 @@ 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 74cc57a9fba1a..5e23ffe67a5ea 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -213,11 +213,37 @@ void TQt6Canvas::AssignStatusBits(UInt_t bits) Canvas()->SetBit(TCanvas::kMenuBar, bits & TCanvas::kMenuBar); } +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns canvas geometry + +void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) +{ + (void) wid; + if (fWidget) { + w = fWidget->width(); + w = fWidget->height(); + } else { + w = 780; + h = 580; + } +} + + ////////////////////////////////////////////////////////////////////////////////////////// /// Returns window geometry including borders and menus 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; @@ -281,6 +307,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 f82881a8361023741e995a6cd227344cc511d22c Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 11:42:24 +0200 Subject: [PATCH 06/30] [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 | 5 ++-- 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(+), 15 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 efb6f4719298a..8cd0ae7531da3 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 @@ -41,3 +42,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 e65f2f2517d41..7224f13cb7c82 100644 --- a/gui/qt6canvas/inc/TQt6Canvas.h +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -15,6 +15,7 @@ class QWidget; class QCanvasWidget; +class QPaintWidget; namespace ROOT { namespace Experimental { @@ -25,7 +26,7 @@ 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 5e23ffe67a5ea..1a77e9b51ffe6 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -100,6 +100,7 @@ Int_t TQt6Canvas::InitWindow() TVirtualPadPainter *TQt6Canvas::CreatePadPainter() { + printf("Create pad painter\n"); return new TQt6PadPainter(); } @@ -219,9 +220,9 @@ void TQt6Canvas::AssignStatusBits(UInt_t bits) 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; @@ -234,12 +235,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; } @@ -260,7 +263,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; } ////////////////////////////////////////////////////////////////////////////////////////// @@ -302,21 +305,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 1fb41f9706f00..16955d89ca0d7 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -143,6 +143,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); } //////////////////////////////////////////////////////////////////////////////// @@ -165,6 +166,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 99480fc86e91df6cb557bb7bb741cf384ee11541 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 14:06:03 +0200 Subject: [PATCH 07/30] [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 | 19 ++- gui/qt6canvas/src/QPaintWidget.cpp | 24 ++++ gui/qt6canvas/src/QPaintWidget.h | 7 ++ gui/qt6canvas/src/TQt6Canvas.cxx | 9 +- gui/qt6canvas/src/TQt6PadPainter.cxx | 172 ++++++++++++++++++++++++++- 5 files changed, 222 insertions(+), 9 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index fcd6c6034c529..224f304730b21 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -13,6 +13,16 @@ #include "TPadPainterBase.h" +#ifdef __CLING__ +class QString; +class QColor; +#else +#include +#include +#endif + +class QPaintWidget; + namespace ROOT { namespace Experimental { @@ -24,9 +34,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 1a77e9b51ffe6..4cdf17440195f 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -101,7 +101,7 @@ Int_t TQt6Canvas::InitWindow() TVirtualPadPainter *TQt6Canvas::CreatePadPainter() { printf("Create pad painter\n"); - return new TQt6PadPainter(); + return new TQt6PadPainter(fPaintWidget); } @@ -258,12 +258,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 16955d89ca0d7..317a38d028c02 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -10,8 +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 using namespace ROOT::Experimental; @@ -143,7 +154,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); } //////////////////////////////////////////////////////////////////////////////// @@ -151,14 +165,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)); } //////////////////////////////////////////////////////////////////////////////// @@ -166,15 +188,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)); } @@ -185,3 +213,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 7428fef67535efbcaf6885687bffad94c2c36665 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 15:43:12 +0200 Subject: [PATCH 08/30] [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 224f304730b21..ae50b3378170f 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -93,6 +93,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 317a38d028c02..1071dec2365f0 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -171,7 +171,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. @@ -349,3 +348,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 558fc7591271e5f9168085654e2a6ecbf40ae579 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Mon, 29 Jun 2026 16:05:57 +0200 Subject: [PATCH 09/30] [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 | 3 + gui/qt6canvas/src/TQt6PadPainter.cxx | 132 +++++++++++++++++++++++++-- 2 files changed, 125 insertions(+), 10 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index ae50b3378170f..ede82c8fcec57 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -16,9 +16,11 @@ #ifdef __CLING__ class QString; class QColor; +class QPen; #else #include #include +#include #endif class QPaintWidget; @@ -40,6 +42,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 1071dec2365f0..e63c6f5426eeb 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 using namespace ROOT::Experimental; @@ -54,9 +58,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)); } @@ -65,10 +80,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. @@ -78,11 +104,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); } //////////////////////////////////////////////////////////////////////////////// @@ -109,8 +153,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); } //////////////////////////////////////////////////////////////////////////////// @@ -118,8 +173,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); } //////////////////////////////////////////////////////////////////////////////// @@ -127,8 +193,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); } //////////////////////////////////////////////////////////////////////////////// @@ -223,6 +300,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 it = std::stoi(tokens->At(j)->GetName()); + pattern.push_back(0.25 * it); + } + 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 18cfa909d90f2c6e568f8a5426c3f0c48445e906 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 09:53:12 +0200 Subject: [PATCH 10/30] [qt6canv] implement fill styles Support special fill patterns 3000..3025 reusing X11 pixmaps Implement box and fillarea painting --- gui/qt6canvas/inc/TQt6PadPainter.h | 3 ++ gui/qt6canvas/src/TQt6PadPainter.cxx | 58 ++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6PadPainter.h b/gui/qt6canvas/inc/TQt6PadPainter.h index ede82c8fcec57..85d7d7a55a8a3 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -17,10 +17,12 @@ class QString; class QColor; class QPen; +class QBrush; #else #include #include #include +#include #endif class QPaintWidget; @@ -43,6 +45,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 e63c6f5426eeb..f93618e4dcd3a 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 using namespace ROOT::Experimental; @@ -121,11 +121,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); } @@ -134,8 +133,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); } //////////////////////////////////////////////////////////////////////////////// @@ -143,9 +152,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); } //////////////////////////////////////////////////////////////////////////////// @@ -335,6 +353,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 bca64271501dec3f5d647fa7db15746c5a34a0e9 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 12:43:54 +0200 Subject: [PATCH 11/30] [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 f93618e4dcd3a..2ac6cea55bdd9 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -231,8 +231,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); + } } //////////////////////////////////////////////////////////////////////////////// @@ -240,8 +257,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 2712746f2866fd6bdb224ee32c96b8afcd53f3de Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 15:29:02 +0200 Subject: [PATCH 12/30] [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 | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 2ac6cea55bdd9..e383358ea0503 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -30,6 +30,9 @@ using namespace ROOT::Experimental; +// 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 @@ -504,7 +507,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())); @@ -548,7 +554,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(); @@ -564,7 +572,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(); @@ -580,7 +590,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(); @@ -596,7 +608,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(); @@ -611,7 +625,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 9b2671690e93e004547a4cc52ff333da99f56f8c Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 30 Jun 2026 16:06:07 +0200 Subject: [PATCH 13/30] [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 e383358ea0503..5d8d6308c1503 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -289,7 +289,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)); } //////////////////////////////////////////////////////////////////////////////// @@ -300,7 +300,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)); } //////////////////////////////////////////////////////////////////////////////// @@ -322,7 +322,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)); } //////////////////////////////////////////////////////////////////////////////// @@ -557,7 +557,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(); @@ -593,7 +593,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(); @@ -628,5 +628,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 3057fc4f9bf6c33b44fb091849135c02e9dc6d0d Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 1 Jul 2026 09:55:54 +0200 Subject: [PATCH 14/30] [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 8cd0ae7531da3..6a0306ef7d84b 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..c505dbb9d0ad6 --- /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")) { + Longptr_t ldefval = 0; + m->GetterMethod()->Execute(object, "", ldefval); + val = TString::Format("%ld", (long) 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 1570b0a335a57ea30217478a69cbe3def641f174 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 1 Jul 2026 12:14:59 +0200 Subject: [PATCH 15/30] [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 | 18 ++++++-- gui/qt6canvas/src/TQt6PadPainter.cxx | 67 +++++++++++++++++++++++----- 4 files changed, 86 insertions(+), 29 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index 7224f13cb7c82..ee621534650d2 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 85d7d7a55a8a3..a8986a4fe5b44 100644 --- a/gui/qt6canvas/inc/TQt6PadPainter.h +++ b/gui/qt6canvas/inc/TQt6PadPainter.h @@ -66,7 +66,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 4cdf17440195f..f150e76a053b3 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -156,6 +156,8 @@ Bool_t TQt6Canvas::HasToolTips() const void TQt6Canvas::SetWindowPosition(Int_t x, Int_t y) { + if (fCanvasWidget) + fCanvasWidget->move(x, y); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -163,6 +165,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); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -170,10 +174,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) { @@ -193,6 +199,8 @@ void TQt6Canvas::SetCanvasSize(UInt_t cw, UInt_t ch) void TQt6Canvas::Iconify() { + if (fCanvasWidget) + fCanvasWidget->showMinimized(); } ////////////////////////////////////////////////////////////////////////////////////////// @@ -200,6 +208,11 @@ void TQt6Canvas::Iconify() void TQt6Canvas::RaiseWindow() { + if (fCanvasWidget) { + fCanvasWidget->showNormal(); + fCanvasWidget->raise(); + fCanvasWidget->activateWindow(); + } } ////////////////////////////////////////////////////////////////////////////////////////// @@ -255,12 +268,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(); diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 5d8d6308c1503..d9bf515cc0311 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -46,6 +46,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. @@ -142,8 +172,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()); @@ -161,8 +194,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()); @@ -179,8 +215,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()); @@ -199,8 +238,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()); @@ -219,8 +261,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()); @@ -340,7 +385,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 c2ecc9fe1abc18fece0a5952904f84635f8f0daa Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 10:44:24 +0200 Subject: [PATCH 16/30] [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 Declare it in ROOT::Experimental namespace --- 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 | 41 +++++++++++ gui/qt6canvas/src/TQt6Canvas.cxx | 6 +- gui/qt6canvas/src/TQt6GuiFactory.cxx | 70 +++++++++++++++++++ 7 files changed, 125 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..abc515ff4bc25 --- /dev/null +++ b/etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C @@ -0,0 +1,5 @@ +void P020_TQt6GuiFactory() +{ + gPluginMgr->AddHandler("TGuiFactory", "qt6", "ROOT::Experimental::TQt6GuiFactory", + "ROOTQt6Canvas", "TQt6GuiFactory()"); +} diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index 6a0306ef7d84b..7a6b2aace983f 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::Core Qt6::Widgets diff --git a/gui/qt6canvas/inc/LinkDef.h b/gui/qt6canvas/inc/LinkDef.h index 4e3855989f0ad..b79bcc4ecbdf0 100644 --- a/gui/qt6canvas/inc/LinkDef.h +++ b/gui/qt6canvas/inc/LinkDef.h @@ -16,5 +16,6 @@ #pragma link C++ class ROOT::Experimental::TQt6Canvas+; #pragma link C++ class ROOT::Experimental::TQt6PadPainter+; +#pragma link C++ class ROOT::Experimental::TQt6GuiFactory+; #endif diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h index ee621534650d2..91ddf49cf996a 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..98eea97531063 --- /dev/null +++ b/gui/qt6canvas/inc/TQt6GuiFactory.h @@ -0,0 +1,41 @@ +// 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" + +namespace ROOT { +namespace Experimental { + +class TQt6GuiFactory : public TGuiFactory { + +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 +}; + +} // namespace Experimental +} // namespace ROOT + + +#endif diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index f150e76a053b3..a64da8b24c699 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -316,10 +316,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..662d0957745b0 --- /dev/null +++ b/gui/qt6canvas/src/TQt6GuiFactory.cxx @@ -0,0 +1,70 @@ +// 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 + +using namespace ROOT::Experimental; + +//////////////////////////////////////////////////////////////////////////////// +/// 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 9dfe218f5b896640c839073bce93b81d1d03befe Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 11:41:41 +0200 Subject: [PATCH 17/30] [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/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/TQt6GuiFactory.cxx | 7 +- 9 files changed, 282 insertions(+), 142 deletions(-) create mode 100644 gui/qt6canvas/src/QRootContextMenu.cpp create mode 100644 gui/qt6canvas/src/QRootContextMenu.h diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index 7a6b2aace983f..ee1bed1a1a5f7 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 91ddf49cf996a..725452d1ca306 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/TQt6GuiFactory.h b/gui/qt6canvas/inc/TQt6GuiFactory.h index 98eea97531063..e5d8891487734 100644 --- a/gui/qt6canvas/inc/TQt6GuiFactory.h +++ b/gui/qt6canvas/inc/TQt6GuiFactory.h @@ -31,7 +31,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 }; } // namespace Experimental 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..0f993bec31f81 --- /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 a64da8b24c699..20e3e953d6f8c 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -52,7 +52,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/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx index 662d0957745b0..3bed4eeaf236c 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 @@ -64,7 +65,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 d2995d4f42d44563a579053606d9d0d8d16e45fa Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 14:23:49 +0200 Subject: [PATCH 18/30] [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 0f993bec31f81..e985e7a0b0e70 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 6c2e5f6569d0a9aaa60e8765127f8c52f7693383 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 2 Jul 2026 14:49:32 +0200 Subject: [PATCH 19/30] [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 e985e7a0b0e70..b5fcd6881fbe5 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 c505dbb9d0ad6..128c8043eebd6 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")) { Longptr_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); From 7d5a22926e5a81074ccef6e37a29db2ca2929bbf Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 28 Jul 2026 11:33:18 +0200 Subject: [PATCH 20/30] [qt6canv] implement File menu commands These are: * open ROOT file, * Save as dialog * Menu items with supported formats Use QFileDialog, list of supported extensions can be moved later to base class --- gui/qt6canvas/src/QCanvasWidget.cpp | 181 ++++++++++++++++++++++++++-- gui/qt6canvas/src/QCanvasWidget.h | 12 ++ 2 files changed, 186 insertions(+), 7 deletions(-) diff --git a/gui/qt6canvas/src/QCanvasWidget.cpp b/gui/qt6canvas/src/QCanvasWidget.cpp index 46ced882537eb..aae924c3cdfc4 100644 --- a/gui/qt6canvas/src/QCanvasWidget.cpp +++ b/gui/qt6canvas/src/QCanvasWidget.cpp @@ -14,9 +14,15 @@ #include "TROOT.h" #include "TApplication.h" #include "TTimer.h" +#include "TFile.h" +#include "TError.h" #include #include +#include + +//////////////////////////////////////////////////////////////////////////////// +/// constructor QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent) { @@ -28,19 +34,25 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent fPaintWidget->SetStatusBar(fStatusBar); - 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"); + auto 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); + auto saveMenu = fileMenu->addMenu("&Save"); + FillSaveMenu(saveMenu); + connect(saveMenu, &QMenu::aboutToShow, this, [this, saveMenu]() { + saveMenu->clear(); + FillSaveMenu(saveMenu); + }); + + fileMenu->addAction("Save &As...", this, &QCanvasWidget::SaveCanvasAs); fileMenu->addSeparator(); @@ -58,7 +70,7 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent fMenuBar->addMenu("&Tools"); - QWidget *spacer = new QWidget(this); + auto spacer = new QWidget(this); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); fMenuBar->setCornerWidget(spacer, Qt::TopRightCorner); @@ -66,34 +78,189 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent } -QCanvasWidget::~QCanvasWidget() {} +//////////////////////////////////////////////////////////////////////////////// +/// Destructor + +QCanvasWidget::~QCanvasWidget() +{ +} +//////////////////////////////////////////////////////////////////////////////// +/// Create new canvas. void QCanvasWidget::NewCanvas() { - + gROOT->MakeDefCanvas(); } +//////////////////////////////////////////////////////////////////////////////// +/// Open ROOT file dialog + void QCanvasWidget::OpenRootFile() { + QFileDialog fd( this, + "Select a ROOT file(s) to open them", + fLastFileDir, + QString("Root files (*.root);;Root xml files (*.xml);;All files (*.*)")); + + fd.setFileMode(QFileDialog::ExistingFiles); + if (fd.exec() != QDialog::Accepted) + return; + + QStringList list = fd.selectedFiles(); + for (auto &fileName : list) { + fLastFileDir = QFileInfo(fileName).absolutePath(); + TFile::Open(fileName.toLatin1().constData(), "update"); + } } +//////////////////////////////////////////////////////////////////////////////// +/// Close the canvas. + void QCanvasWidget::CloseCanvas() { close(); } +//////////////////////////////////////////////////////////////////////////////// +/// Return vector with supported file formats with description and required option + +std::vector QCanvasWidget::GetSupportedFileFormats() +{ + return { + "Post Script", "ps", "ps", + "Post Script Portrait", "ps", "Portrait", + "Post Script Landscape", "ps", "Landscape", + "Encapsulated Post Script", "eps", "eps", + "Encapsulated Post Script preview", "eps", "Preview", + "GIF format", "gif", "gif", + "PDF format", "pdf", "pdf", + "SVG format", "svg", "svg", + "XPM format", "xpm", "xpm", + "PNG format", "png", "png", + "JPG format", "jpg", "jpg", + "BMP format", "bmp", "bmp", + "TIFF format", "tiff", "tiff", + "C++ Macro", "C", "cxx", + "json file", "json", "json", + "html file", "html", "html", + "root file", "root", "root" }; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Fill save menu with predefined items + +void QCanvasWidget::FillSaveMenu(QMenu *menu) +{ + auto canv = GetPaintWidget()->getCanvas(); + + auto fmts = GetSupportedFileFormats(); + + for (std::size_t pos = 0; pos < fmts.size(); pos += 3) { + auto ext = fmts[pos + 1]; + if ((ext != "C") && (ext != fmts[pos + 2])) + continue; + + QString filename = canv ? canv->GetName() : "c1"; + filename += "."; + filename += ext.c_str(); + auto act1 = menu->addAction(filename); + connect(act1, &QAction::triggered, this, [this, filename]() { + SaveCanvas(filename); + }); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Save canvas as file + +void QCanvasWidget::SaveCanvas(const QString &fname) +{ + auto canv = GetPaintWidget()->getCanvas(); + if (canv) + canv->SaveAs(fname.toLatin1().constData()); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Save canvas as + void QCanvasWidget::SaveCanvasAs() { + auto canv = GetPaintWidget()->getCanvas(); + if (!canv) + return; + + auto fmts = GetSupportedFileFormats(); + + QFileDialog fd( this, QString("Save ") + canv->GetName() + " As", fLastFileDir); + fd.setFileMode( QFileDialog::AnyFile ); + fd.setAcceptMode(QFileDialog::AcceptSave); + + QStringList flt; + + for (std::size_t pos = 0; pos < fmts.size(); pos += 3) { + QString filter = QString(fmts[pos].c_str()) + " (*." + fmts[pos+1].c_str() + ")"; + flt << filter; + } + + fd.setNameFilters(flt); + + QString filename0 = canv->GetName(); + + fd.selectFile(filename0 + ".png"); + + QObject::connect(&fd, &QFileDialog::filterSelected, [filename0, &fd](const QString &fltr) { + QStringList flst = fd.selectedFiles(); + if (flst.size() > 1) + return; + if ((flst.size() == 1) && (flst[0].indexOf(filename0 + ".") != 0)) + return; + + auto p = fltr.indexOf("(*."), p2 = fltr.lastIndexOf(")"); + fd.selectFile(filename0 + fltr.mid(p + 2, p2 - p - 2)); + }); + if (fd.exec() != QDialog::Accepted) + return; + + QStringList flst = fd.selectedFiles(); + if (flst.isEmpty()) return; + + QString filename = flst[0]; + + fLastFileDir = fd.directory().path(); + + std::string opt; + + for (std::size_t pos = 0; pos < fmts.size(); pos += 3) { + QString filter = QString(fmts[pos].c_str()) + " (*." + fmts[pos+1].c_str() + ")"; + if (filter != fd.selectedNameFilter()) + continue; + + if (!filename.endsWith(QString(".") + fmts[pos+1].c_str())) { + filename.append("."); + filename.append(fmts[pos+1].c_str()); + } + opt = fmts[pos + 2]; + break; + } + + if (!opt.empty()) + canv->Print(filename.toLatin1().constData(), opt.c_str()); } +//////////////////////////////////////////////////////////////////////////////// +/// Print the canvas. + void QCanvasWidget::PrintCanvas() { - + ::Info("QCanvasWidget::PrintCanvas", "To be implemented"); } +//////////////////////////////////////////////////////////////////////////////// +/// Quit ROOT + void QCanvasWidget::QuitRoot() { // set flag which sometimes checked in TSystem::ProcessEvents diff --git a/gui/qt6canvas/src/QCanvasWidget.h b/gui/qt6canvas/src/QCanvasWidget.h index f5725b2af18b3..518f35b811e50 100644 --- a/gui/qt6canvas/src/QCanvasWidget.h +++ b/gui/qt6canvas/src/QCanvasWidget.h @@ -15,6 +15,8 @@ #include #include "ui_QCanvasWidget.h" +#include +#include class TH1F; class TH2I; @@ -24,6 +26,14 @@ class QCanvasWidget : public QWidget, public Ui::QCanvasWidget { QMenuBar *fMenuBar = nullptr; + QString fLastFileDir; + +protected: + + std::vector GetSupportedFileFormats(); + + void FillSaveMenu(QMenu *menu); + public: QCanvasWidget(QWidget *parent = nullptr, const char *name = nullptr); @@ -31,6 +41,8 @@ class QCanvasWidget : public QWidget, public Ui::QCanvasWidget { QPaintWidget *GetPaintWidget() const { return fPaintWidget; } + void SaveCanvas(const QString &fname); + public slots: void NewCanvas(); From a15645fe7de300b6447b7e0d122efbc52fdb7f18 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 28 Jul 2026 14:11:24 +0200 Subject: [PATCH 21/30] [qt6canv] add View menu items Implement toggling of empty QToolBar Implement toggling of status branch Implement tooltip event handling and toggling of such mode --- gui/qt6canvas/src/QCanvasWidget.cpp | 129 +++++++++++++++++++++++++++- gui/qt6canvas/src/QCanvasWidget.h | 18 +++- gui/qt6canvas/src/QCanvasWidget.ui | 26 ++++-- gui/qt6canvas/src/QPaintWidget.cpp | 69 ++++++++++----- gui/qt6canvas/src/QPaintWidget.h | 12 +-- gui/qt6canvas/src/TQt6Canvas.cxx | 3 + 6 files changed, 220 insertions(+), 37 deletions(-) diff --git a/gui/qt6canvas/src/QCanvasWidget.cpp b/gui/qt6canvas/src/QCanvasWidget.cpp index aae924c3cdfc4..e57afb82b112d 100644 --- a/gui/qt6canvas/src/QCanvasWidget.cpp +++ b/gui/qt6canvas/src/QCanvasWidget.cpp @@ -12,10 +12,12 @@ #include "TCanvas.h" #include "TROOT.h" +#include "TMarker.h" #include "TApplication.h" #include "TTimer.h" #include "TFile.h" #include "TError.h" +#include "TColorWheel.h" #include #include @@ -32,9 +34,9 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent setObjectName(name); - fPaintWidget->SetStatusBar(fStatusBar); + connect(fPaintWidget, &QPaintWidget::CanvasStatusEvent, this, &QCanvasWidget::CanvasStatusEventSlot); - fMenuBar = new QMenuBar(fMenuFrame); + // fMenuBar = new QMenuBar(fMenuFrame); fMenuBar->setMinimumWidth(50); fMenuBar->setNativeMenuBar(kFALSE); // disable putting this to screen menu. for MAC style WMs @@ -64,7 +66,37 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent fMenuBar->addMenu("&Edit"); - fMenuBar->addMenu("&View"); + auto viewMenu = fMenuBar->addMenu("&View"); + auto act = viewMenu->addAction("&Editor"); + act->setEnabled(false); + + fViewToolbar = viewMenu->addAction("&Toolbar"); + fViewToolbar->setCheckable(true); + connect(fViewToolbar, &QAction::toggled, this, &QCanvasWidget::SetViewToolbar); + SetViewToolbar(false); + + fViewEventStatus = viewMenu->addAction("Event &Statusbar"); + fViewEventStatus->setCheckable(true); + connect(fViewEventStatus, &QAction::toggled, this, &QCanvasWidget::SetViewEventStatus); + SetViewEventStatus(true); + + fViewToolTip = viewMenu->addAction("T&ooltip Info"); + fViewToolTip->setCheckable(true); + connect(fViewToolTip, &QAction::toggled, this, &QCanvasWidget::SetViewToolTip); + SetViewToolTip(false); + + viewMenu->addSeparator(); + + viewMenu->addAction("&Colors", this, &QCanvasWidget::ShowColors); + + act = viewMenu->addAction("&Fonts"); + act->setEnabled(false); + + viewMenu->addAction("&Markers", this, &QCanvasWidget::ShowMarkers); + + viewMenu->addSeparator(); + viewMenu->addAction("&Iconify", this, &QCanvasWidget::IconifyCanvas); + fMenuBar->addMenu("&Options"); @@ -76,6 +108,8 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent fMenuBar->addMenu("&Help"); + // fToolBar = new QToolBar("Canvas tools", this); + fToolBar->setVisible(false); } //////////////////////////////////////////////////////////////////////////////// @@ -270,4 +304,93 @@ void QCanvasWidget::QuitRoot() TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()"); } +//////////////////////////////////////////////////////////////////////////////// +/// Set visibility of tool bar + +void QCanvasWidget::SetViewToolbar(bool on) +{ + fToolBar->setVisible(on); + fViewToolbar->setChecked(on); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Set visibility of event status + +void QCanvasWidget::SetViewEventStatus(bool on) +{ + fStatusBar->setVisible(on); + fViewEventStatus->setChecked(on); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Status event handler + +void QCanvasWidget::CanvasStatusEventSlot(const QString &msg) +{ + if (fStatusBar && fStatusBar->isVisible()) + fStatusBar->showMessage(msg); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Set visibility of tooltip + +void QCanvasWidget::SetViewToolTip(bool on) +{ + fViewToolTip->setChecked(on); + + fPaintWidget->SetShowToolTip(on); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Show new canvas with ROOT colors wheel + +void QCanvasWidget::ShowColors() +{ + TVirtualPad::TContext ctxt; + + auto wheel = new TColorWheel(); + wheel->Draw(); + gPad->Update(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Show new canvas with ROOT markers + +void QCanvasWidget::ShowMarkers() +{ + TVirtualPad::TContext ctxt; + + auto m = new TCanvas("markers","Marker Types",600,200); + TMarker::DisplayMarkerTypes(); + m->Update(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Iconify canvas widget + +void QCanvasWidget::IconifyCanvas() +{ + showMinimized(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Apply canvas status bits like show tooltip or menu bar + +void QCanvasWidget::ApplyCanvasStatusBits() +{ + auto canv = GetPaintWidget()->getCanvas(); + if (!canv) + return; + SetViewEventStatus(canv->TestBit(TCanvas::kShowEventStatus)); + + // canv->TestBit(TCanvas::kShowEditor); + + SetViewToolbar(canv->TestBit(TCanvas::kShowToolBar)); + + SetViewToolTip(canv->TestBit(TCanvas::kShowToolTips)); + + fMenuBar->setVisible(canv->TestBit(TCanvas::kMenuBar)); + + +} diff --git a/gui/qt6canvas/src/QCanvasWidget.h b/gui/qt6canvas/src/QCanvasWidget.h index 518f35b811e50..3a90b8426ac74 100644 --- a/gui/qt6canvas/src/QCanvasWidget.h +++ b/gui/qt6canvas/src/QCanvasWidget.h @@ -12,7 +12,7 @@ #define ROOT_QCanvasWidget_h #include -#include +#include #include "ui_QCanvasWidget.h" #include @@ -24,7 +24,9 @@ class TH2I; class QCanvasWidget : public QWidget, public Ui::QCanvasWidget { Q_OBJECT - QMenuBar *fMenuBar = nullptr; + QAction *fViewToolbar = nullptr; + QAction *fViewEventStatus = nullptr; + QAction *fViewToolTip = nullptr; QString fLastFileDir; @@ -43,6 +45,8 @@ class QCanvasWidget : public QWidget, public Ui::QCanvasWidget { void SaveCanvas(const QString &fname); + void ApplyCanvasStatusBits(); + public slots: void NewCanvas(); @@ -52,6 +56,16 @@ public slots: void SaveCanvasAs(); void PrintCanvas(); void QuitRoot(); + + void ShowColors(); + void ShowMarkers(); + void IconifyCanvas(); + + void SetViewToolbar(bool); + void SetViewEventStatus(bool); + void SetViewToolTip(bool); + + void CanvasStatusEventSlot(const QString &); }; #endif diff --git a/gui/qt6canvas/src/QCanvasWidget.ui b/gui/qt6canvas/src/QCanvasWidget.ui index 21fa473da169e..1285b7a0d4059 100644 --- a/gui/qt6canvas/src/QCanvasWidget.ui +++ b/gui/qt6canvas/src/QCanvasWidget.ui @@ -17,7 +17,7 @@ - + 7 @@ -32,15 +32,27 @@ 30 - - NoFrame + + + + + + + 7 + 1 + 0 + 0 + - - Plain + + + 0 + 30 + - + @@ -58,7 +70,7 @@ - + diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp index 9655e6707d7d3..68ea48c9df12c 100644 --- a/gui/qt6canvas/src/QPaintWidget.cpp +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -19,11 +19,12 @@ #include #include #include +#include #include +#include #include #include #include -#include QPaintWidget::QPaintWidget(QWidget *parent) : QWidget(parent) @@ -57,6 +58,36 @@ QPoint QPaintWidget::scaledMousePoint(QMouseEvent *e) } +bool QPaintWidget::event(QEvent *event) +{ + if (fShowToolTip && (event->type() == QEvent::ToolTip)) { + auto *helpEvent = static_cast(event); + + TObject *selected = fCanvas->GetSelected(); + Int_t px = fCanvas->GetEventX(); + Int_t py = fCanvas->GetEventY(); + QString customText; + if (selected) { + customText = QString("%1::%2
%3
%4, %5
%6") + .arg(selected->ClassName()).arg(selected->GetName()) + .arg(selected->GetTitle()) + .arg(px).arg(py) + .arg(selected->GetObjectInfo(px, py)); + } else { + customText = QString("No selected object
%1, %2") + .arg(px).arg(py); + } + + // 4. Force screen coordinates to bypass standard widget offset logic + QToolTip::showText(helpEvent->globalPos(), customText, this); + + return true; // Event handled successfully + } + + return QWidget::event(event); +} + + void QPaintWidget::resizeEvent(QResizeEvent *) { if (fCanvas) { @@ -131,26 +162,24 @@ void QPaintWidget::mouseMoveEvent(QMouseEvent *e) 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 (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); + } - if (fStatusBar) fStatusBar->showMessage(buffer.toLatin1().constData()); - } + emit CanvasStatusEvent(buffer); + } } void QPaintWidget::mouseReleaseEvent( QMouseEvent *e ) diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h index 0673b8da6e76b..57cfae555ff26 100644 --- a/gui/qt6canvas/src/QPaintWidget.h +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -17,7 +17,6 @@ class TCanvas; class TPad; class TMethod; class QPainter; -class QStatusBar; class QPaintWidget : public QWidget { @@ -34,9 +33,14 @@ class QPaintWidget : public QWidget { void SetCanvas(TCanvas *canv) { fCanvas = canv; } - void SetStatusBar(QStatusBar *bar) { fStatusBar = bar; } + void SetShowToolTip(bool on) { fShowToolTip = on; } + +signals: + void CanvasStatusEvent(const QString &msg); protected: + bool event(QEvent *event) override; + void resizeEvent(QResizeEvent *event) override; void paintEvent(QPaintEvent *event) override; @@ -56,11 +60,9 @@ class QPaintWidget : public QWidget { double fQtScalingfactor = 1.; - QStatusBar *fStatusBar = nullptr; - bool fMaskDoubleClick = false; bool fShowEventStatus = true; - + bool fShowToolTip = false; }; #endif diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 20e3e953d6f8c..81fae4ef1f8c2 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -224,6 +224,9 @@ void TQt6Canvas::AssignStatusBits(UInt_t bits) Canvas()->SetBit(TCanvas::kShowEditor, bits & TCanvas::kShowEditor); Canvas()->SetBit(TCanvas::kShowToolTips, bits & TCanvas::kShowToolTips); Canvas()->SetBit(TCanvas::kMenuBar, bits & TCanvas::kMenuBar); + Canvas()->SetBit(TCanvas::kShowToolBar, bits & TCanvas::kShowToolBar); + if (fCanvasWidget) + fCanvasWidget->ApplyCanvasStatusBits(); } ////////////////////////////////////////////////////////////////////////////////////////// From 0ffff3a9835259bbb03e20a6d4bf013cc39bd367 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 28 Jul 2026 16:23:34 +0200 Subject: [PATCH 22/30] [qt6canv] implement most buttons in toolbar --- gui/qt6canvas/src/QCanvasWidget.cpp | 148 +++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 3 deletions(-) diff --git a/gui/qt6canvas/src/QCanvasWidget.cpp b/gui/qt6canvas/src/QCanvasWidget.cpp index e57afb82b112d..8445a52a7493b 100644 --- a/gui/qt6canvas/src/QCanvasWidget.cpp +++ b/gui/qt6canvas/src/QCanvasWidget.cpp @@ -23,6 +23,55 @@ #include #include + +enum { kFileNewCanvas, kFileOpen, kFileSaveAs, kFilePrint, kOptionInterrupt, kOptionRefresh, + kInspectRoot, kToolsBrowser, kToolModify, kToolArc, kToolLine, kToolArrow, kToolDiamond, + kToolEllipse, kToolPad, kToolPave, kToolPLabel, kToolPText, kToolPsText, kToolGraph, + kToolCurlyLine, kToolCurlyArc, kToolLatex, kToolMarker, kToolCutG }; + +struct QToolBarData_t { + const char *fPixmap; + const char *fTipText; + Bool_t fStayDown; + Int_t fId; + void *fButton; +}; + + +static QToolBarData_t qToolBarData[] = { + // { filename, tooltip, staydown, id, button} + { "newcanvas.xpm", "New", kFALSE, kFileNewCanvas, 0 }, + { "open.xpm", "Open", kFALSE, kFileOpen, 0 }, + { "save.xpm", "Save As", kFALSE, kFileSaveAs, 0 }, + { "printer.xpm", "Print", kFALSE, kFilePrint, 0 }, + { "", "", kFALSE, -1, 0 }, + { "interrupt.xpm", "Interrupt", kFALSE, kOptionInterrupt,0 }, + { "refresh2.xpm", "Refresh", kFALSE, kOptionRefresh, 0 }, + { "", "", kFALSE, -1, 0 }, + { "inspect.xpm", "Inspect", kFALSE, kInspectRoot, 0 }, +// { "browser.xpm", "Browser", kFALSE, kToolsBrowser, 0 }, + { "", "", kFALSE, -1, 0 }, + { "pointer.xpm", "Modify", kFALSE, kToolModify, 0 }, + { "arc.xpm", "Arc", kFALSE, kToolArc, 0 }, + { "line.xpm", "Line", kFALSE, kToolLine, 0 }, + { "arrow.xpm", "Arrow", kFALSE, kToolArrow, 0 }, + { "diamond.xpm", "Diamond", kFALSE, kToolDiamond, 0 }, + { "ellipse.xpm", "Ellipse", kFALSE, kToolEllipse, 0 }, + { "pad.xpm", "Pad", kFALSE, kToolPad, 0 }, + { "pave.xpm", "Pave", kFALSE, kToolPave, 0 }, + { "pavelabel.xpm", "Pave Label", kFALSE, kToolPLabel, 0 }, + { "pavetext.xpm", "Pave Text", kFALSE, kToolPText, 0 }, + { "pavestext.xpm", "Paves Text", kFALSE, kToolPsText, 0 }, + { "graph.xpm", "Graph", kFALSE, kToolGraph, 0 }, + { "curlyline.xpm", "Curly Line", kFALSE, kToolCurlyLine, 0 }, + { "curlyarc.xpm", "Curly Arc", kFALSE, kToolCurlyArc, 0 }, + { "latex.xpm", "Text/Latex", kFALSE, kToolLatex, 0 }, + { "marker.xpm", "Marker", kFALSE, kToolMarker, 0 }, + { "cut.xpm", "Graphical Cut", kFALSE, kToolCutG, 0 }, + { 0, 0, kFALSE, 0, 0 } +}; + + //////////////////////////////////////////////////////////////////////////////// /// constructor @@ -78,7 +127,7 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent fViewEventStatus = viewMenu->addAction("Event &Statusbar"); fViewEventStatus->setCheckable(true); connect(fViewEventStatus, &QAction::toggled, this, &QCanvasWidget::SetViewEventStatus); - SetViewEventStatus(true); + SetViewEventStatus(false); fViewToolTip = viewMenu->addAction("T&ooltip Info"); fViewToolTip->setCheckable(true); @@ -108,8 +157,101 @@ QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent fMenuBar->addMenu("&Help"); - // fToolBar = new QToolBar("Canvas tools", this); - fToolBar->setVisible(false); + // configure tool bar + + //fToolBar->setStyleSheet("QToolBar { layout-spacing: 0px; }" + // "QToolBar QToolButton { padding: 0px; margin: 0px; border: none; }"); + fToolBar->setIconSize(QSize(16, 16)); + for (int i = 0; qToolBarData[i].fPixmap; i++) { + auto btn = &(qToolBarData[i]); + if (strlen(btn->fPixmap) == 0) { + fToolBar->addSeparator(); + continue; + } + + TString iconname = TROOT::GetIconPath(); + iconname.Append("/"); + iconname.Append(btn->fPixmap); + + QIcon buttonIcon(iconname.Data()); + + fToolBar->addAction(buttonIcon, btn->fTipText, [btn, this]() { + auto canv = GetPaintWidget()->getCanvas(); + + switch(btn->fId) { + case kFileNewCanvas: NewCanvas(); break; + case kFileOpen: OpenRootFile(); break; + case kFileSaveAs: SaveCanvasAs(); break; + case kFilePrint: PrintCanvas(); break; + case kOptionInterrupt: gROOT->SetInterrupt(); break; + case kOptionRefresh: + if (canv) { + canv->Modified(kTRUE); + // trigger update of the paint widget, paint will be called + canv->Update(); + } + break; + case kInspectRoot: + if (canv) { + canv->cd(); + gROOT->Inspect(); + canv->Update(); + } + break; + + case kToolsBrowser: break; + case kToolModify: gROOT->SetEditorMode(); break; + case kToolArc: + gROOT->SetEditorMode("Arc"); + break; + case kToolLine: + gROOT->SetEditorMode("Line"); + break; + case kToolArrow: + gROOT->SetEditorMode("Arrow"); + break; + case kToolDiamond: + gROOT->SetEditorMode("Diamond"); + break; + case kToolEllipse: + gROOT->SetEditorMode("Ellipse"); + break; + case kToolPad: + gROOT->SetEditorMode("Pad"); + break; + case kToolPave: + gROOT->SetEditorMode("Pave"); + break; + case kToolPLabel: + gROOT->SetEditorMode("PaveLabel"); + break; + case kToolPText: + gROOT->SetEditorMode("PaveText"); + break; + case kToolPsText: + gROOT->SetEditorMode("PavesText"); + break; + case kToolGraph: + gROOT->SetEditorMode("PolyLine"); + break; + case kToolCurlyLine: + gROOT->SetEditorMode("CurlyLine"); + break; + case kToolCurlyArc: + gROOT->SetEditorMode("CurlyArc"); + break; + case kToolLatex: + gROOT->SetEditorMode("Text"); + break; + case kToolMarker: + gROOT->SetEditorMode("Marker"); + break; + case kToolCutG: + gROOT->SetEditorMode("CutG"); + break; + } + }); + } } //////////////////////////////////////////////////////////////////////////////// From e6fc2de2ee4566c8f3f6279c3297a1af9423e7f4 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Tue, 28 Jul 2026 17:00:04 +0200 Subject: [PATCH 23/30] [qt6canv] provide basic attributes editors For line, fill, text, marker attributes provide dialogs to avoid ROOT crash while default editors does not work in Qt For all attributes reuse colors editors with opacity --- gui/qt6canvas/src/QRootContextMenu.cpp | 455 ++++++++++++++++++++++++- gui/qt6canvas/src/QRootContextMenu.h | 16 +- 2 files changed, 468 insertions(+), 3 deletions(-) diff --git a/gui/qt6canvas/src/QRootContextMenu.cpp b/gui/qt6canvas/src/QRootContextMenu.cpp index b5fcd6881fbe5..f8adec2f1ef69 100644 --- a/gui/qt6canvas/src/QRootContextMenu.cpp +++ b/gui/qt6canvas/src/QRootContextMenu.cpp @@ -25,10 +25,13 @@ The picture below shows a canvas with a pop-up menu. #include "TROOT.h" #include "TContextMenu.h" #include "TCanvas.h" +#include "TColor.h" #include "TMethod.h" #include "TDataMember.h" #include "TToggle.h" #include "TClassMenuItem.h" +#include "TAttText.h" +#include "TAttMarker.h" #include "QRootMethodDialog.h" #include "QPaintWidget.h" @@ -37,6 +40,16 @@ The picture below shows a canvas with a pop-up menu. #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include enum EContextMenu { kToggleStart = 1000, // first id of toggle menu items @@ -295,6 +308,433 @@ QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const return act; } +void QRootContextMenu::AddColorElements(int colindx, QFormLayout *layout) +{ + TColor *rootColor = gROOT->GetColor(colindx); + QColor initialColor = Qt::black; + int initialAlpha255 = 255; // Default fully opaque + + if (rootColor) { + initialColor = QColor(rootColor->GetRed() * 255, rootColor->GetGreen() * 255, rootColor->GetBlue() * 255); + initialAlpha255 = static_cast(rootColor->GetAlpha() * 255); + } + initialColor.setAlpha(initialAlpha255); + + fColorButton = new QPushButton(); + fColorButton->setFixedWidth(80); + + QSlider *alphaSlider = new QSlider(Qt::Horizontal); + alphaSlider->setRange(0, 255); + alphaSlider->setValue(initialAlpha255); + + // Visual preview of current color + fSelectedColor = initialColor; + UpdateColorElements(); + + QObject::connect(fColorButton, &QPushButton::clicked, [&, this]() { + QColor col = QColorDialog::getColor(fSelectedColor, nullptr, "Select Color"); + if (col.isValid()) { + fSelectedColor.setRed(col.red()); + fSelectedColor.setGreen(col.green()); + fSelectedColor.setBlue(col.blue()); + UpdateColorElements(); + } + }); + + // --- Slider Shift Connection --- + QObject::connect(alphaSlider, &QSlider::valueChanged, [&](int value) { + fSelectedColor.setAlpha(value); + UpdateColorElements(); + }); + + layout->addRow("Color:", fColorButton); + + layout->addRow("Opacity:", alphaSlider); +} + +void QRootContextMenu::UpdateColorElements() +{ + QString qss = QString("background-color: rgba(%1, %2, %3, %4); border: 1px solid gray;") + .arg(fSelectedColor.red()) + .arg(fSelectedColor.green()) + .arg(fSelectedColor.blue()) + .arg(fSelectedColor.alpha() / 255.0); + fColorButton->setStyleSheet(qss); +} + +void QRootContextMenu::SetLineAttributesDialog() +{ + auto attline = dynamic_cast(fContextMenu->GetSelectedObject()); + if (!attline) + return; + + QDialog dialog; + dialog.setWindowTitle("Edit Line Attributes"); + dialog.setModal(true); + + QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); + QFormLayout *formLayout = new QFormLayout(); + + // --- Color Selector --- + AddColorElements(attline->GetLineColor(), formLayout); + + // --- Line Style Selector --- + // ROOT Styles: 1=Solid, 2=Dashed, 3=Dotted, 4=Dash-Dot + QComboBox *styleCombo = new QComboBox(); + styleCombo->addItem("None (0)", 0); + styleCombo->addItem("Solid (1)", 1); + styleCombo->addItem("Dashed (2)", 2); + styleCombo->addItem("Dotted (3)", 3); + styleCombo->addItem("Dash-Dot (4)", 4); + styleCombo->addItem("Dash-Dot (5)", 5); + styleCombo->addItem("Dash-Dot-Dot-Dot (6)", 6); + styleCombo->addItem("Dashed medium (7)", 7); + styleCombo->addItem("Dash-Dot-Dot (8)", 8); + styleCombo->addItem("Dashed long (9)", 9); + styleCombo->addItem("Dash-Dot long (10)", 10); + + // Find and set current style + int currentStyle = attline->GetLineStyle(); + int styleIdx = styleCombo->findData(currentStyle); + if (styleIdx != -1) + styleCombo->setCurrentIndex(styleIdx); + else + styleCombo->addItem(QString("Custom (%1)").arg(currentStyle), currentStyle); + + formLayout->addRow("Style:", styleCombo); + + // --- Line Width Selector --- + QSpinBox *widthSpin = new QSpinBox(); + widthSpin->setRange(1, 20); + widthSpin->setValue(attline->GetLineWidth()); + formLayout->addRow("Width:", widthSpin); + + mainLayout->addLayout(formLayout); + + // --- Dialog Buttons (OK / Cancel) --- + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *okButton = new QPushButton("OK"); + QPushButton *cancelButton = new QPushButton("Cancel"); + buttonLayout->addStretch(); + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + mainLayout->addLayout(buttonLayout); + + QObject::connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept); + QObject::connect(cancelButton, &QPushButton::clicked, &dialog, &QDialog::reject); + + // 2. Execute Dialog and apply properties if accepted + if (dialog.exec() == QDialog::Accepted) { + // Update ROOT Color Index + Color_t newColorIdx = TColor::GetColor(fSelectedColor.red(), + fSelectedColor.green(), + fSelectedColor.blue(), + fSelectedColor.alpha() / 255.0); + attline->SetLineColor(newColorIdx); + + // Update Line Style + Style_t newStyle = styleCombo->currentData().toInt(); + attline->SetLineStyle(newStyle); + + // Update Line Width + Width_t newWidth = widthSpin->value(); + attline->SetLineWidth(newWidth); + } +} + +void QRootContextMenu::SetFillAttributesDialog() +{ + auto attfill = dynamic_cast(fContextMenu->GetSelectedObject()); + if (!attfill) + return; + + QDialog dialog; + dialog.setWindowTitle("Edit Fill Attributes"); + dialog.setModal(true); + + QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); + QFormLayout *formLayout = new QFormLayout(); + + // --- Color Selector --- + AddColorElements(attfill->GetFillColor(), formLayout); + + // --- Fill Style Selector --- + // ROOT Styles: 1=Solid, 2=Dashed, 3=Dotted, 4=Dash-Dot + QComboBox *styleCombo = new QComboBox(); + styleCombo->addItem("None (0)", 0); + styleCombo->addItem("Solid (1001)", 1001); + for (int s = 3001; s <= 3025; ++s) + styleCombo->addItem(QString("Style %1").arg(s), s); + for (int s = 3144; s <= 3944; s += 100) + styleCombo->addItem(QString("Style %1").arg(s), s); + for (int s = 3305; s <= 3395; s += 10) + styleCombo->addItem(QString("Style %1").arg(s), s); + for (int s = 3350; s <= 3359; s += 1) + styleCombo->addItem(QString("Style %1").arg(s), s); + for (int s = 3409; s <= 3490; s += 9) + styleCombo->addItem(QString("Style %1").arg(s), s); + for (int s = 3609; s <= 3690; s += 9) + styleCombo->addItem(QString("Style %1").arg(s), s); + + // Find and set current style + int currentStyle = attfill->GetFillStyle(); + int styleIdx = styleCombo->findData(currentStyle); + if (styleIdx != -1) + styleCombo->setCurrentIndex(styleIdx); + else + styleCombo->addItem(QString("Style %1").arg(currentStyle), currentStyle); + + formLayout->addRow("Style:", styleCombo); + + mainLayout->addLayout(formLayout); + + // --- Dialog Buttons (OK / Cancel) --- + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *okButton = new QPushButton("OK"); + QPushButton *cancelButton = new QPushButton("Cancel"); + buttonLayout->addStretch(); + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + mainLayout->addLayout(buttonLayout); + + QObject::connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept); + QObject::connect(cancelButton, &QPushButton::clicked, &dialog, &QDialog::reject); + + // 2. Execute Dialog and apply properties if accepted + if (dialog.exec() == QDialog::Accepted) { + // Update ROOT Color Index + Color_t newColorIdx = TColor::GetColor(fSelectedColor.red(), + fSelectedColor.green(), + fSelectedColor.blue(), + fSelectedColor.alpha() / 255.0); + attfill->SetFillColor(newColorIdx); + + // Update fill Style + Style_t newStyle = styleCombo->currentData().toInt(); + attfill->SetFillStyle(newStyle); + } +} + + +class CustomDoubleSpinBox : public QDoubleSpinBox { +protected: + QString textFromValue(double value) const override { + if (value == 0) return "Default"; + return QDoubleSpinBox::textFromValue(value); + } + + double valueFromText(const QString &text) const override { + if (text == "Default") return 0.; + return QDoubleSpinBox::valueFromText(text); + } +}; + +class CustomSpinBox : public QSpinBox { +protected: + QString textFromValue(int value) const override { + if (value == 0) return "Default"; + return QSpinBox::textFromValue(value); + } + + int valueFromText(const QString &text) const override { + if (text == "Default") return 0; + return QSpinBox::valueFromText(text); + } +}; + + +void QRootContextMenu::SetTextAttributesDialog() +{ + auto atttext = dynamic_cast(fContextMenu->GetSelectedObject()); + if (!atttext) + return; + + QDialog dialog; + dialog.setWindowTitle("Edit Text Attributes"); + dialog.setModal(true); + + QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); + QFormLayout *formLayout = new QFormLayout(); + + // --- Color Selector --- + AddColorElements(atttext->GetTextColor(), formLayout); + + QComboBox *fontCombo = new QComboBox(); + fontCombo->addItem("1. Times italic", 1); + fontCombo->addItem("2. Times bold", 2); + fontCombo->addItem("3. Times bold italic", 3); + fontCombo->addItem("4. Helvetica", 4); + fontCombo->addItem("5. Helvetica italic", 5); + fontCombo->addItem("6. Helvetica bold", 6); + fontCombo->addItem("7. Helvetica bold italic", 7); + fontCombo->addItem("8. Courier", 8); + fontCombo->addItem("9. Courier italic", 9); + fontCombo->addItem("10. Courier bold", 10); + fontCombo->addItem("11. Courier bold italic", 11); + fontCombo->addItem("12. Symbol", 12); + fontCombo->addItem("13. Times", 13); + fontCombo->addItem("14. Wingdings", 14); + fontCombo->addItem("15. Symbol italic", 15); + + // Find and set current style + int currentPrec = atttext->GetTextFont() % 10; + int currentFont = atttext->GetTextFont() / 10; + int styleIdx = fontCombo->findData(currentFont); + if (styleIdx >= 0) + fontCombo->setCurrentIndex(styleIdx); + else + fontCombo->addItem(QString("Font %1").arg(currentFont), currentFont); + + formLayout->addRow("Font:", fontCombo); + + QDoubleSpinBox* floatSpinBox = nullptr; + QSpinBox *intSpinBox = nullptr; + + if (currentPrec == 2) { + floatSpinBox = new CustomDoubleSpinBox(); + floatSpinBox->setRange(0.0, 1.0); // Set your minimum and maximum limits + floatSpinBox->setSingleStep(0.01); // Set step size to 00.1 + floatSpinBox->setDecimals(3); // Force it to show exactly 3 decimal places + floatSpinBox->setValue(atttext->GetTextSize()); + formLayout->addRow("Size:", floatSpinBox); + } else { + intSpinBox = new CustomSpinBox(); + intSpinBox->setRange(0, 128); + intSpinBox->setValue(atttext->GetTextSize()); + formLayout->addRow("Size:", intSpinBox); + } + + QComboBox *alignCombo = new QComboBox(); + alignCombo->addItem("11. Left Bottom", 11); + alignCombo->addItem("12. Left Center", 12); + alignCombo->addItem("13. Left Top", 13); + alignCombo->addItem("21. Middle Bottom", 21); + alignCombo->addItem("22. Middle Center", 22); + alignCombo->addItem("23. Middle Top", 23); + alignCombo->addItem("31. Right Bottom", 31); + alignCombo->addItem("32. Right Center", 32); + alignCombo->addItem("33. Right Top", 33); + + // Find and set current style + int alignIdx = alignCombo->findData(atttext->GetTextAlign()); + if (alignIdx < 0) + alignIdx = alignCombo->findData(11); + alignCombo->setCurrentIndex(alignIdx); + + formLayout->addRow("Align:", alignCombo); + + mainLayout->addLayout(formLayout); + + // --- Dialog Buttons (OK / Cancel) --- + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *okButton = new QPushButton("OK"); + QPushButton *cancelButton = new QPushButton("Cancel"); + buttonLayout->addStretch(); + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + mainLayout->addLayout(buttonLayout); + + QObject::connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept); + QObject::connect(cancelButton, &QPushButton::clicked, &dialog, &QDialog::reject); + + // 2. Execute Dialog and apply properties if accepted + if (dialog.exec() == QDialog::Accepted) { + // Update ROOT Color Index + Color_t newColorIdx = TColor::GetColor(fSelectedColor.red(), + fSelectedColor.green(), + fSelectedColor.blue(), + fSelectedColor.alpha() / 255.0); + atttext->SetTextColor(newColorIdx); + + // Update font Style + Int_t newFont = fontCombo->currentData().toInt(); + atttext->SetTextFont(newFont * 10 + currentPrec); + + if (floatSpinBox) + atttext->SetTextSize(floatSpinBox->value()); + else if (intSpinBox) + atttext->SetTextSize(intSpinBox->value()); + + atttext->SetTextAlign(alignCombo->currentData().toInt()); + } + +} + +void QRootContextMenu::SetMarkerAttributesDialog() +{ + auto attmarker = dynamic_cast(fContextMenu->GetSelectedObject()); + if (!attmarker) + return; + + QDialog dialog; + dialog.setWindowTitle("Edit Marker Attributes"); + dialog.setModal(true); + + QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); + QFormLayout *formLayout = new QFormLayout(); + + // --- Color Selector --- + AddColorElements(attmarker->GetMarkerColor(), formLayout); + + // --- Marker Style Selector --- + QComboBox *styleCombo = new QComboBox(); + for (int s = 1; s <= 49; ++s) + styleCombo->addItem(QString("Style %1").arg(s), s); + + // Find and set current style + int currentStyle = attmarker->GetMarkerStyle(); + int styleIdx = styleCombo->findData(currentStyle); + if (styleIdx != -1) + styleCombo->setCurrentIndex(styleIdx); + else + styleCombo->addItem(QString("Style %1").arg(currentStyle), currentStyle); + + formLayout->addRow("Style:", styleCombo); + + + QDoubleSpinBox* floatSpinBox = new QDoubleSpinBox(); + + // 2. Configure its ranges and step parameters + floatSpinBox->setRange(0.0, 10.0); // Set your minimum and maximum limits + floatSpinBox->setSingleStep(0.1); // Set step size to 0.1 + floatSpinBox->setDecimals(1); // Force it to show exactly 1 decimal place (e.g., 1.5) + floatSpinBox->setValue(attmarker->GetMarkerSize()); + formLayout->addRow("Size:", floatSpinBox); + + mainLayout->addLayout(formLayout); + + // --- Dialog Buttons (OK / Cancel) --- + QHBoxLayout *buttonLayout = new QHBoxLayout(); + QPushButton *okButton = new QPushButton("OK"); + QPushButton *cancelButton = new QPushButton("Cancel"); + buttonLayout->addStretch(); + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + mainLayout->addLayout(buttonLayout); + + QObject::connect(okButton, &QPushButton::clicked, &dialog, &QDialog::accept); + QObject::connect(cancelButton, &QPushButton::clicked, &dialog, &QDialog::reject); + + // 2. Execute Dialog and apply properties if accepted + if (dialog.exec() == QDialog::Accepted) { + // Update ROOT Color Index + Color_t newColorIdx = TColor::GetColor(fSelectedColor.red(), + fSelectedColor.green(), + fSelectedColor.blue(), + fSelectedColor.alpha() / 255.0); + attmarker->SetMarkerColor(newColorIdx); + + // Update Style + Style_t newStyle = styleCombo->currentData().toInt(); + attmarker->SetMarkerStyle(newStyle); + + float newsize = floatSpinBox->value(); + attmarker->SetMarkerSize(newsize); + } + +} + + void QRootContextMenu::executeMenu(int id) { if (id < 0) @@ -320,8 +760,19 @@ void QRootContextMenu::executeMenu(int id) } if (id < kToggleStart) { - TMethod *m = (TMethod *) ud; - fContextMenu->Action(m); + auto m = (TMethod *) ud; + + if (!strcmp(m->GetName(), "SetLineAttributes")) { + SetLineAttributesDialog(); + } else if (!strcmp(m->GetName(), "SetFillAttributes")) { + SetFillAttributesDialog(); + } else if (!strcmp(m->GetName(), "SetTextAttributes")) { + SetTextAttributesDialog(); + } else if (!strcmp(m->GetName(), "SetMarkerAttributes")) { + SetMarkerAttributesDialog(); + } else { + fContextMenu->Action(m); + } } else if (id >= kToggleStart && id < kToggleListStart) { TToggle *t = (TToggle *) ud; fContextMenu->Action(t); diff --git a/gui/qt6canvas/src/QRootContextMenu.h b/gui/qt6canvas/src/QRootContextMenu.h index 303102f5e11c5..5bca77e42fd73 100644 --- a/gui/qt6canvas/src/QRootContextMenu.h +++ b/gui/qt6canvas/src/QRootContextMenu.h @@ -13,6 +13,8 @@ #include #include +#include + #include "TContextMenuImp.h" #include "TObject.h" #include "TList.h" @@ -22,6 +24,8 @@ class QSignalMapper; class TList; class QAction; class QMenu; +class QFormLayout; +class QPushButton; class QRootContextMenu : public QObject, public TObject, public TContextMenuImp { Q_OBJECT @@ -30,12 +34,22 @@ public slots: void executeMenu(int id); protected: - TObject *fMenuObj = nullptr; // object use to fill menu + + QColor fSelectedColor; // selected color in attributes editors + QPushButton *fColorButton = nullptr; TList fTrash; std::map fCustomArg; QAction* addMenuAction(QMenu *menu, QSignalMapper *map, const QString &text, int id, void *arg = nullptr); + void AddColorElements(int colindx, QFormLayout *layout); + void UpdateColorElements(); + + void SetLineAttributesDialog(); + void SetFillAttributesDialog(); + void SetTextAttributesDialog(); + void SetMarkerAttributesDialog(); + public: QRootContextMenu(TContextMenu *c = nullptr, const char *name = "ROOT Context Menu"); ~QRootContextMenu() override; From 4d63bdb23d3dd8335fcd75e59274d4b5ccdd779c Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 29 Jul 2026 09:58:43 +0200 Subject: [PATCH 24/30] [qt6canv] add enter/leave/wheel handlers Required for zooming and corrects interactivity handling in ROOT --- gui/qt6canvas/src/QPaintWidget.cpp | 69 ++++++++++++++++++++++-------- gui/qt6canvas/src/QPaintWidget.h | 3 ++ 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp index 68ea48c9df12c..b7ba4df28d5bc 100644 --- a/gui/qt6canvas/src/QPaintWidget.cpp +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -19,8 +19,10 @@ #include #include #include +#include #include #include + #include #include #include @@ -112,17 +114,16 @@ void QPaintWidget::paintEvent(QPaintEvent *) } } -void QPaintWidget::mousePressEvent( QMouseEvent *e ) +void QPaintWidget::mousePressEvent(QMouseEvent *e) { - TObjLink* pickobj = nullptr; + //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(); + // 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 : + case Qt::LeftButton: fCanvas->HandleInput(kButton1Down, scaled.x(), scaled.y()); // emit PadClicked(pad, scaled.x(), scaled.y()); break; @@ -134,7 +135,7 @@ void QPaintWidget::mousePressEvent( QMouseEvent *e ) fCanvas->HandleInput(kButton2Down, scaled.x(), scaled.y()); // emit SelectedPadChanged(pad); break; - case Qt::NoButton : + case Qt::NoButton : break; default: break; @@ -182,11 +183,11 @@ void QPaintWidget::mouseMoveEvent(QMouseEvent *e) } } -void QPaintWidget::mouseReleaseEvent( QMouseEvent *e ) +void QPaintWidget::mouseReleaseEvent(QMouseEvent *event) { - QPoint scaled = scaledMousePoint(e); + QPoint scaled = scaledMousePoint(event); - switch(e->button()) { + switch(event->button()) { case Qt::LeftButton : fCanvas->HandleInput(kButton1Up, scaled.x(), scaled.y()); break; @@ -201,19 +202,20 @@ void QPaintWidget::mouseReleaseEvent( QMouseEvent *e ) default: break; } - e->accept(); + + event->accept(); } -void QPaintWidget::mouseDoubleClickEvent( QMouseEvent *e ) +void QPaintWidget::mouseDoubleClickEvent(QMouseEvent *event) { - QPoint scaled = scaledMousePoint(e); + QPoint scaled = scaledMousePoint(event); - switch(e->button()) { + switch(event->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); + // TObjLink* pickobj = nullptr; + // TPad *pad = fCanvas->Pick(scaled.x(), scaled.y(), pickobj); // emit PadDoubleClicked(pad, scaled.x(), scaled.y()); break; } @@ -228,5 +230,36 @@ void QPaintWidget::mouseDoubleClickEvent( QMouseEvent *e ) default: break; } - e->accept(); + + event->accept(); +} + +void QPaintWidget::wheelEvent(QWheelEvent *event) +{ + QPoint delta = event->pixelDelta(); + if (delta.isNull()) + delta = event->angleDelta() / 8; + bool positive = delta.x() > 0 || delta.y() > 0; + + int sx = scaledPosition(event->position().x()); + int sy = scaledPosition(event->position().y()); + + fCanvas->HandleInput(positive ? kWheelUp : kWheelDown, sx, sy); + + event->accept(); +} + +void QPaintWidget::enterEvent(QEnterEvent *event) +{ + QWidget::enterEvent(event); + + fCanvas->HandleInput(kMouseEnter, 0, 0); +} + + +void QPaintWidget::leaveEvent(QEvent *event) +{ + QWidget::leaveEvent(event); + + fCanvas->HandleInput(kMouseLeave, 0, 0); } diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h index 57cfae555ff26..900aa41f567d8 100644 --- a/gui/qt6canvas/src/QPaintWidget.h +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -49,6 +49,9 @@ class QPaintWidget : public QWidget { void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseDoubleClickEvent(QMouseEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + void enterEvent(QEnterEvent *event) override; + void leaveEvent(QEvent *event) override; double scaledPosition(int p) { return (double) p * fQtScalingfactor; } From 816a87201cf2002d70bc7ab0d6028cf2d4f6d5da Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 30 Jul 2026 10:46:37 +0200 Subject: [PATCH 25/30] [qt6canv] provide more documentation Add \class mark for all used classes Provide some remarks around QPainter and paintEvent handler Remove several debugging printf Make headers #ifdef with same syntax --- gui/qt6canvas/src/QCanvasWidget.cpp | 9 ++++ gui/qt6canvas/src/QCanvasWidget.h | 4 +- gui/qt6canvas/src/QPaintWidget.cpp | 59 ++++++++++++++++++++++--- gui/qt6canvas/src/QPaintWidget.h | 8 ++-- gui/qt6canvas/src/QRootContextMenu.cpp | 39 +++++++++++----- gui/qt6canvas/src/QRootMethodDialog.cpp | 18 ++++++++ gui/qt6canvas/src/QRootMethodDialog.h | 4 +- gui/qt6canvas/src/TQt6Canvas.cxx | 26 +++-------- gui/qt6canvas/src/TQt6GuiFactory.cxx | 5 +-- gui/qt6canvas/src/TQt6PadPainter.cxx | 2 + 10 files changed, 123 insertions(+), 51 deletions(-) diff --git a/gui/qt6canvas/src/QCanvasWidget.cpp b/gui/qt6canvas/src/QCanvasWidget.cpp index 8445a52a7493b..3a966fa52c4b0 100644 --- a/gui/qt6canvas/src/QCanvasWidget.cpp +++ b/gui/qt6canvas/src/QCanvasWidget.cpp @@ -72,6 +72,15 @@ static QToolBarData_t qToolBarData[] = { }; + +/** \class QCanvasWidget + \ingroup qt6canvas + +Qt widget which display canvas and provides menu, toolbar and status bar +Actual graphics shown in the \ref QPaintWidget +*/ + + //////////////////////////////////////////////////////////////////////////////// /// constructor diff --git a/gui/qt6canvas/src/QCanvasWidget.h b/gui/qt6canvas/src/QCanvasWidget.h index 3a90b8426ac74..ec67f90c7e93c 100644 --- a/gui/qt6canvas/src/QCanvasWidget.h +++ b/gui/qt6canvas/src/QCanvasWidget.h @@ -8,8 +8,8 @@ * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ -#ifndef ROOT_QCanvasWidget_h -#define ROOT_QCanvasWidget_h +#ifndef ROOT_QCanvasWidget +#define ROOT_QCanvasWidget #include #include diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp index b7ba4df28d5bc..1fee59e366f1f 100644 --- a/gui/qt6canvas/src/QPaintWidget.cpp +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -28,6 +28,18 @@ #include #include +/** \class QPaintWidget + \ingroup qt6canvas + +Represent area where actual canvas painting is performed + +Qt defines `paintEvent` virtual method where all painting should be done. +At this moment QPainter object is created which then used in \ref TVirtualPadPainter API. +*/ + + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor QPaintWidget::QPaintWidget(QWidget *parent) : QWidget(parent) { @@ -48,10 +60,16 @@ QPaintWidget::QPaintWidget(QWidget *parent) : QWidget(parent) fQtScalingfactor = (double) metric(QPaintDevice::PdmDevicePixelRatioScaled)/65536.; } +//////////////////////////////////////////////////////////////////////////////// +/// Destructor + QPaintWidget::~QPaintWidget() { } +//////////////////////////////////////////////////////////////////////////////// +/// Convert mouse event position to canvas coordinates + QPoint QPaintWidget::scaledMousePoint(QMouseEvent *e) { int scaledX = scaledPosition(e->position().x()); @@ -59,6 +77,10 @@ QPoint QPaintWidget::scaledMousePoint(QMouseEvent *e) return QPoint(scaledX, scaledY); } +//////////////////////////////////////////////////////////////////////////////// +/// Generic event handler +/// +/// Used to handle tooltip event - if configured bool QPaintWidget::event(QEvent *event) { @@ -89,6 +111,8 @@ bool QPaintWidget::event(QEvent *event) return QWidget::event(event); } +//////////////////////////////////////////////////////////////////////////////// +/// Resize event handler void QPaintWidget::resizeEvent(QResizeEvent *) { @@ -98,6 +122,14 @@ void QPaintWidget::resizeEvent(QResizeEvent *) } } +//////////////////////////////////////////////////////////////////////////////// +/// Paint event handler +/// +/// Only inside this method painting on QWidget is allowed +/// For this temporary QPainter object is created and assigned to fPainter +/// Any attempt to paint outside this method will be blocked - while fPainter will be 0 + + void QPaintWidget::paintEvent(QPaintEvent *) { try { @@ -114,13 +146,12 @@ void QPaintWidget::paintEvent(QPaintEvent *) } } +//////////////////////////////////////////////////////////////////////////////// +/// Mouse press event handler + 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: @@ -144,6 +175,9 @@ void QPaintWidget::mousePressEvent(QMouseEvent *e) } +//////////////////////////////////////////////////////////////////////////////// +/// Mouse move event handler + void QPaintWidget::mouseMoveEvent(QMouseEvent *e) { static ulong lastprocesstime = 0; @@ -183,6 +217,9 @@ void QPaintWidget::mouseMoveEvent(QMouseEvent *e) } } +//////////////////////////////////////////////////////////////////////////////// +/// Mouse release event handler + void QPaintWidget::mouseReleaseEvent(QMouseEvent *event) { QPoint scaled = scaledMousePoint(event); @@ -206,6 +243,9 @@ void QPaintWidget::mouseReleaseEvent(QMouseEvent *event) event->accept(); } +//////////////////////////////////////////////////////////////////////////////// +/// Mouse double click event handler + void QPaintWidget::mouseDoubleClickEvent(QMouseEvent *event) { QPoint scaled = scaledMousePoint(event); @@ -214,9 +254,6 @@ void QPaintWidget::mouseDoubleClickEvent(QMouseEvent *event) 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 : @@ -234,6 +271,9 @@ void QPaintWidget::mouseDoubleClickEvent(QMouseEvent *event) event->accept(); } +//////////////////////////////////////////////////////////////////////////////// +/// Wheel event handler + void QPaintWidget::wheelEvent(QWheelEvent *event) { QPoint delta = event->pixelDelta(); @@ -249,6 +289,9 @@ void QPaintWidget::wheelEvent(QWheelEvent *event) event->accept(); } +//////////////////////////////////////////////////////////////////////////////// +/// Enter event handler + void QPaintWidget::enterEvent(QEnterEvent *event) { QWidget::enterEvent(event); @@ -256,6 +299,8 @@ void QPaintWidget::enterEvent(QEnterEvent *event) fCanvas->HandleInput(kMouseEnter, 0, 0); } +//////////////////////////////////////////////////////////////////////////////// +/// Leave event handler void QPaintWidget::leaveEvent(QEvent *event) { diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h index 900aa41f567d8..373d7ba52342b 100644 --- a/gui/qt6canvas/src/QPaintWidget.h +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -8,8 +8,8 @@ * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ -#ifndef QPaintWidget_H -#define QPaintWidget_H +#ifndef ROOT_QPaintWidget +#define ROOT_QPaintWidget #include @@ -57,9 +57,9 @@ class QPaintWidget : public QWidget { QPoint scaledMousePoint(QMouseEvent *event); - TCanvas *fCanvas = nullptr; + TCanvas *fCanvas = nullptr; ///< canvas shown in the widget - QPainter *fPainter = nullptr; + QPainter *fPainter = nullptr; ///< temporary painter created inside paintEvent handler double fQtScalingfactor = 1.; diff --git a/gui/qt6canvas/src/QRootContextMenu.cpp b/gui/qt6canvas/src/QRootContextMenu.cpp index f8adec2f1ef69..ed0961bbab96f 100644 --- a/gui/qt6canvas/src/QRootContextMenu.cpp +++ b/gui/qt6canvas/src/QRootContextMenu.cpp @@ -9,14 +9,12 @@ *************************************************************************/ -/** \class RRootContextMenu +/** \class QRootContextMenu \ingroup qt6canvas -This class provides an interface to context sensitive popup menus. +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. - */ @@ -109,10 +107,6 @@ void QRootContextMenu::DisplayPopup(Int_t x, Int_t y) menu.addSeparator(); bool last_separ = true; - - - // addMenuAction(&menu, &map, buffer, curId++); - int entry = 0, toggle = kToggleStart, togglelist = kToggleListStart; int userfunction = kUserFunctionStart; @@ -268,8 +262,7 @@ void QRootContextMenu::Dialog(TObject * object, TFunction * func) } //////////////////////////////////////////////////////////////////////////////// -/// Close the context menu if the object is deleted in the -/// RecursiveRemove() operation. +/// Handle remove of some ROOT objects void QRootContextMenu::RecursiveRemove(TObject *obj) { @@ -284,7 +277,10 @@ void QRootContextMenu::RecursiveRemove(TObject *obj) } } -QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const QString& text, int id, void *arg) +//////////////////////////////////////////////////////////////////////////////// +/// Register menu action in signal map + +QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper *map, const QString& text, int id, void *arg) { bool enabled = true; @@ -308,6 +304,9 @@ QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const return act; } +//////////////////////////////////////////////////////////////////////////////// +/// Add color elements to attributes editor dialog + void QRootContextMenu::AddColorElements(int colindx, QFormLayout *layout) { TColor *rootColor = gROOT->GetColor(colindx); @@ -352,6 +351,9 @@ void QRootContextMenu::AddColorElements(int colindx, QFormLayout *layout) layout->addRow("Opacity:", alphaSlider); } +//////////////////////////////////////////////////////////////////////////////// +/// Update color button with currently selected color + void QRootContextMenu::UpdateColorElements() { QString qss = QString("background-color: rgba(%1, %2, %3, %4); border: 1px solid gray;") @@ -362,6 +364,9 @@ void QRootContextMenu::UpdateColorElements() fColorButton->setStyleSheet(qss); } +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttLine editor + void QRootContextMenu::SetLineAttributesDialog() { auto attline = dynamic_cast(fContextMenu->GetSelectedObject()); @@ -442,6 +447,9 @@ void QRootContextMenu::SetLineAttributesDialog() } } +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttFill editor + void QRootContextMenu::SetFillAttributesDialog() { auto attfill = dynamic_cast(fContextMenu->GetSelectedObject()); @@ -543,6 +551,9 @@ class CustomSpinBox : public QSpinBox { }; +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttText editor + void QRootContextMenu::SetTextAttributesDialog() { auto atttext = dynamic_cast(fContextMenu->GetSelectedObject()); @@ -657,9 +668,11 @@ void QRootContextMenu::SetTextAttributesDialog() atttext->SetTextAlign(alignCombo->currentData().toInt()); } - } +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttMarker editor + void QRootContextMenu::SetMarkerAttributesDialog() { auto attmarker = dynamic_cast(fContextMenu->GetSelectedObject()); @@ -734,6 +747,8 @@ void QRootContextMenu::SetMarkerAttributesDialog() } +//////////////////////////////////////////////////////////////////////////////// +/// Execute specified menu item void QRootContextMenu::executeMenu(int id) { diff --git a/gui/qt6canvas/src/QRootMethodDialog.cpp b/gui/qt6canvas/src/QRootMethodDialog.cpp index 128c8043eebd6..17dea3fa68f2d 100644 --- a/gui/qt6canvas/src/QRootMethodDialog.cpp +++ b/gui/qt6canvas/src/QRootMethodDialog.cpp @@ -32,6 +32,15 @@ #include +/** \class QRootMethodDialog + \ingroup qt6canvas + +Specialized dialog to enter arguments for method exection +*/ + +//////////////////////////////////////////////////////////////////////////////// +/// Create method dialog + QRootMethodDialog::QRootMethodDialog() : QDialog() { QGridLayout *gridLayout = new QGridLayout(this); @@ -59,6 +68,9 @@ QRootMethodDialog::QRootMethodDialog() : QDialog() gridLayout->addLayout(buttLayout, 1, 0, Qt::AlignBottom); } +//////////////////////////////////////////////////////////////////////////////// +/// Add argument + void QRootMethodDialog::addArg(const char *argname, const char *value, const char *) { QLabel* lbl = new QLabel(argname); @@ -73,6 +85,9 @@ void QRootMethodDialog::addArg(const char *argname, const char *value, const cha fArgs.push_back(le); } +//////////////////////////////////////////////////////////////////////////////// +/// Get argument value + QString QRootMethodDialog::getArg(int n) { if ((n<0) || (n>=fArgs.size())) return QString(""); @@ -80,6 +95,9 @@ QString QRootMethodDialog::getArg(int n) } +//////////////////////////////////////////////////////////////////////////////// +/// Run method dialog + void QRootMethodDialog::methodDialog(TContextMenu *menu, TObject *object, TFunction* func) { if (!menu || !object || !func) diff --git a/gui/qt6canvas/src/QRootMethodDialog.h b/gui/qt6canvas/src/QRootMethodDialog.h index c14d4ff2c931c..9794184f60ec4 100644 --- a/gui/qt6canvas/src/QRootMethodDialog.h +++ b/gui/qt6canvas/src/QRootMethodDialog.h @@ -13,8 +13,8 @@ *************************************************************************/ -#ifndef QRootMethodDialog_h -#define QRootMethodDialog_h +#ifndef ROOT_QRootMethodDialog +#define ROOT_QRootMethodDialog #include #include diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 81fae4ef1f8c2..802a469200c81 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -16,7 +16,6 @@ #include "TStyle.h" #include "TError.h" #include "TCanvas.h" -#include "TThread.h" #include "TROOT.h" #include "TApplication.h" #include "TClass.h" @@ -54,7 +53,6 @@ class TQt6CanvasTimer : public TTimer { \brief Basic TCanvasImp ABI implementation for Qt6 */ -using namespace std::string_literals; //////////////////////////////////////////////////////////////////////////////// /// Constructor @@ -62,18 +60,6 @@ using namespace std::string_literals; 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; } @@ -99,7 +85,6 @@ Int_t TQt6Canvas::InitWindow() TVirtualPadPainter *TQt6Canvas::CreatePadPainter() { - printf("Create pad painter\n"); return new TQt6PadPainter(fPaintWidget); } @@ -262,11 +247,6 @@ UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) h = 600; } - // x = Canvas()->fWindowTopX; - // y = Canvas()->fWindowTopY; - // w = Canvas()->fWindowWidth; - // h = Canvas()->fWindowHeight; - return 0; } @@ -290,7 +270,7 @@ void TQt6Canvas::ForceUpdate() ////////////////////////////////////////////////////////////////////////////////////////////////// /// Static method to create TQt6Canvas instance -/// Used by plugin manager +/// Used by plugin manager to directly create TQt6Canvas without gui factory TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height) { @@ -336,6 +316,10 @@ TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y // set all internal dimensions c->Resize(); + // TODO: maybe apply same logic to adjust canvas dimension as in TRootCanvas + // Keep commented code here intentionally to be able find this place when + // search for correspondent class members + // c->fWindowTopX = x; // c->fWindowTopY = y; // c->fWindowWidth = width; diff --git a/gui/qt6canvas/src/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx index 3bed4eeaf236c..2e5da5b1ed8f0 100644 --- a/gui/qt6canvas/src/TQt6GuiFactory.cxx +++ b/gui/qt6canvas/src/TQt6GuiFactory.cxx @@ -12,9 +12,8 @@ /** \class TQt6GuiFactory \ingroup qt6canvas -This class is a factory for ROOT GUI components. It overrides -the member functions of the ABS TGuiFactory. - +This class is a factory for ROOT GUI components. +For Qt6 it provides specialization for TCanvasImp and TContextMenuImp classes */ diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index d9bf515cc0311..4567d8bb9149c 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -36,6 +36,8 @@ const Float_t kScale = 0.75 * 0.93376068; /** \class TQt6PadPainter \ingroup qt6canvas \brief Implement TVirtualPadPainter for Qt6 graphics + + Uses QPainter object which only exists inside paintEvent of Qt. */ ////////////////////////////////////////////////////////////////////////// From 38edab681771f3b76347dcffcdf980c666ea0418 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 30 Jul 2026 14:13:35 +0200 Subject: [PATCH 26/30] [qt6canv] introduce specialized TApplicationImp class Idea to move dedicated application initialization there --- gui/qt6canvas/CMakeLists.txt | 2 ++ gui/qt6canvas/inc/LinkDef.h | 1 + gui/qt6canvas/inc/TQt6Application.h | 43 +++++++++++++++++++++++++++ gui/qt6canvas/src/TQt6Application.cxx | 38 +++++++++++++++++++++++ gui/qt6canvas/src/TQt6GuiFactory.cxx | 4 ++- 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 gui/qt6canvas/inc/TQt6Application.h create mode 100644 gui/qt6canvas/src/TQt6Application.cxx diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt index ee1bed1a1a5f7..c5620d93aee0b 100644 --- a/gui/qt6canvas/CMakeLists.txt +++ b/gui/qt6canvas/CMakeLists.txt @@ -28,6 +28,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas HEADERS + TQt6Application.h TQt6Canvas.h TQt6PadPainter.h TQt6GuiFactory.h @@ -36,6 +37,7 @@ ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas src/QPaintWidget.cpp src/QRootMethodDialog.cpp src/QRootContextMenu.cpp + src/TQt6Application.cxx src/TQt6Canvas.cxx src/TQt6PadPainter.cxx src/TQt6GuiFactory.cxx diff --git a/gui/qt6canvas/inc/LinkDef.h b/gui/qt6canvas/inc/LinkDef.h index b79bcc4ecbdf0..e04d586303ba0 100644 --- a/gui/qt6canvas/inc/LinkDef.h +++ b/gui/qt6canvas/inc/LinkDef.h @@ -17,5 +17,6 @@ #pragma link C++ class ROOT::Experimental::TQt6Canvas+; #pragma link C++ class ROOT::Experimental::TQt6PadPainter+; #pragma link C++ class ROOT::Experimental::TQt6GuiFactory+; +#pragma link C++ class ROOT::Experimental::TQt6Application+; #endif diff --git a/gui/qt6canvas/inc/TQt6Application.h b/gui/qt6canvas/inc/TQt6Application.h new file mode 100644 index 0000000000000..87cfb910d76b5 --- /dev/null +++ b/gui/qt6canvas/inc/TQt6Application.h @@ -0,0 +1,43 @@ +// Author: Sergey Linev 30/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_TQt6Application +#define ROOT_TQt6Application + + +#include "TApplicationImp.h" + +namespace ROOT { +namespace Experimental { + +class TQt6Application : public TApplicationImp { +public: + TQt6Application() = delete; + TQt6Application(const char *appClassName, Int_t *argc, char **argv); + ~TQt6Application() override; + + void Show() override {} + void Hide() override {} + void Iconify() override {} + Bool_t IsCmdThread() override { return kTRUE; } + void Init() override {} + void Open() override {} + void Raise() override {} + void Lower() override {} + + ClassDefOverride(TQt6Application,0) // ROOT Qt6 GUI application environment +}; + +} // namespace Experimental +} // namespace ROOT + + +#endif diff --git a/gui/qt6canvas/src/TQt6Application.cxx b/gui/qt6canvas/src/TQt6Application.cxx new file mode 100644 index 0000000000000..e737cc020ee0d --- /dev/null +++ b/gui/qt6canvas/src/TQt6Application.cxx @@ -0,0 +1,38 @@ +// Author: Sergey Linev 30/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 TQt6Application + \ingroup qt6canvas + +Provides Qt6-specific methods for TApplication. +Main purpose - runs Qt event loop +*/ + + +#include "TQt6Application.h" + + +using namespace ROOT::Experimental; + +//////////////////////////////////////////////////////////////////////////////// +/// Create Qt6 application environment. + +TQt6Application::TQt6Application(const char *appClassName, + Int_t *argc, char **argv) : TApplicationImp(appClassName, argc, argv) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Delete Qt6 application environment. + +TQt6Application::~TQt6Application() +{ +} diff --git a/gui/qt6canvas/src/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx index 2e5da5b1ed8f0..7e62cc1420266 100644 --- a/gui/qt6canvas/src/TQt6GuiFactory.cxx +++ b/gui/qt6canvas/src/TQt6GuiFactory.cxx @@ -18,7 +18,9 @@ For Qt6 it provides specialization for TCanvasImp and TContextMenuImp classes #include "TQt6GuiFactory.h" + #include "TQt6Canvas.h" +#include "TQt6Application.h" #include "QRootContextMenu.h" #include @@ -39,7 +41,7 @@ TQt6GuiFactory::TQt6GuiFactory(const char *name, const char *title) TApplicationImp *TQt6GuiFactory::CreateApplicationImp(const char *classname, Int_t *argc, char **argv) { - return TGuiFactory::CreateApplicationImp(classname, argc, argv); + return new TQt6Application(classname, argc, argv); } //////////////////////////////////////////////////////////////////////////////// From df6388bd07adfd34670f999121c9c5273dbfad23 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 30 Jul 2026 16:09:25 +0200 Subject: [PATCH 27/30] [qt6canvas] move QApplication initialization to TQt6Application class When TQt6Application is created immediately create also QApplication. In the future one can make it optional --- gui/qt6canvas/inc/TQt6Application.h | 2 + gui/qt6canvas/src/TQt6Application.cxx | 63 ++++++++++++++++++++++++--- gui/qt6canvas/src/TQt6Canvas.cxx | 45 ++----------------- 3 files changed, 63 insertions(+), 47 deletions(-) diff --git a/gui/qt6canvas/inc/TQt6Application.h b/gui/qt6canvas/inc/TQt6Application.h index 87cfb910d76b5..beba095366e2a 100644 --- a/gui/qt6canvas/inc/TQt6Application.h +++ b/gui/qt6canvas/inc/TQt6Application.h @@ -33,6 +33,8 @@ class TQt6Application : public TApplicationImp { void Raise() override {} void Lower() override {} + static void CreateQApplication(int argc = 0, char **argv = nullptr); + ClassDefOverride(TQt6Application,0) // ROOT Qt6 GUI application environment }; diff --git a/gui/qt6canvas/src/TQt6Application.cxx b/gui/qt6canvas/src/TQt6Application.cxx index e737cc020ee0d..3869f8a3f624e 100644 --- a/gui/qt6canvas/src/TQt6Application.cxx +++ b/gui/qt6canvas/src/TQt6Application.cxx @@ -8,6 +8,29 @@ * For the list of contributors see $ROOTSYS/README/CREDITS. * *************************************************************************/ +#include "TQt6Application.h" + +#include "TError.h" +#include "TTimer.h" +#include "TApplication.h" + +#include + +using namespace ROOT::Experimental; + +class TQt6EventsTimer : public TTimer { +public: + TQt6EventsTimer(Long_t milliSec, Bool_t mode) : + TTimer(milliSec, mode) {} + + /// used to send control messages to clients + void Timeout() override + { + QApplication::sendPostedEvents(); + QApplication::processEvents(); + } +}; + /** \class TQt6Application \ingroup qt6canvas @@ -17,17 +40,13 @@ Main purpose - runs Qt event loop */ -#include "TQt6Application.h" - - -using namespace ROOT::Experimental; - //////////////////////////////////////////////////////////////////////////////// /// Create Qt6 application environment. TQt6Application::TQt6Application(const char *appClassName, Int_t *argc, char **argv) : TApplicationImp(appClassName, argc, argv) { + CreateQApplication(*argc, argv); } //////////////////////////////////////////////////////////////////////////////// @@ -36,3 +55,37 @@ TQt6Application::TQt6Application(const char *appClassName, TQt6Application::~TQt6Application() { } + + +//////////////////////////////////////////////////////////////////////////////// +/// Create QApplication and timer to handle Qt events + +void TQt6Application::CreateQApplication([[maybe_unused]] int argc, char **argv) +{ + static QApplication *qapp = nullptr; + static int qargc = 1; + static char *qargv[2]; + + if (!qapp && !QApplication::instance()) { + + if (argv) + qargv[0] = argv[0]; + else if (gApplication) + qargv[0] = gApplication->Argv(0); + else { + ::Error("TQt6Application::CreateQApplication", "Not found gApplication to create QApplication"); + return; + } + qargv[1] = nullptr; + + qapp = new QApplication(qargc, qargv); + } + + static TQt6EventsTimer *timer = nullptr; + + if (!timer) { + timer = new TQt6EventsTimer(10, kTRUE); + timer->TurnOn(); + } + +} diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 802a469200c81..00f322544d874 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -11,13 +11,12 @@ #include "TQt6Canvas.h" #include "TQt6PadPainter.h" +#include "TQt6Application.h" #include "TSystem.h" #include "TStyle.h" -#include "TError.h" #include "TCanvas.h" #include "TROOT.h" -#include "TApplication.h" #include "TClass.h" #include @@ -28,32 +27,15 @@ #include #include -#include #include "QCanvasWidget.h" using namespace ROOT::Experimental; -class TQt6CanvasTimer : public TTimer { -public: - TQt6CanvasTimer(Long_t milliSec, Bool_t mode) : - TTimer(milliSec, mode) {} - - - /// used to send control messages to clients - void Timeout() override - { - QApplication::sendPostedEvents(); - QApplication::processEvents(); - } -}; - - /** \class TQt6Canvas \ingroup qt6canvas \brief Basic TCanvasImp ABI implementation for Qt6 */ - //////////////////////////////////////////////////////////////////////////////// /// Constructor @@ -274,29 +256,8 @@ 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(); - } + // ensure QApplication is exists + TQt6Application::CreateQApplication(); auto widget = new QCanvasWidget(); widget->setWindowTitle(QString(c->GetTitle())); From 93c95b50f5a107888b25acef61ebd4459fe6b344 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 30 Jul 2026 17:13:13 +0200 Subject: [PATCH 28/30] [qt6canvas] return false in TQt6GuiFactory::UseVirtualX() Gives information to application that virtualx is not required. --- gui/qt6canvas/inc/TQt6GuiFactory.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gui/qt6canvas/inc/TQt6GuiFactory.h b/gui/qt6canvas/inc/TQt6GuiFactory.h index e5d8891487734..0601594388eaf 100644 --- a/gui/qt6canvas/inc/TQt6GuiFactory.h +++ b/gui/qt6canvas/inc/TQt6GuiFactory.h @@ -24,6 +24,8 @@ class TQt6GuiFactory : public TGuiFactory { TQt6GuiFactory(const char *name = "RootQt6", const char *title = "ROOT Qt6 GUI Factory"); ~TQt6GuiFactory() override {} + Bool_t UseVirtualX() const override { return kFALSE; } + TApplicationImp *CreateApplicationImp(const char *classname, int *argc, char **argv) override; TCanvasImp *CreateCanvasImp(TCanvas *c, const char *title, UInt_t width, UInt_t height) override; From 9d38d41959b302c453deb40b512f2eb2c19029b1 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Wed, 5 Aug 2026 15:53:07 +0200 Subject: [PATCH 29/30] [qt6canvas] implement markers painting Use new TAttMarker::GetMarkerShape() method --- gui/qt6canvas/src/TQt6PadPainter.cxx | 98 ++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 29 deletions(-) diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 4567d8bb9149c..21844c4aa7f9a 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -15,6 +15,7 @@ #include "TEnv.h" #include "TMath.h" #include "TPad.h" +#include "TPoint.h" #include "TROOT.h" #include "TColor.h" #include "RStipples.h" @@ -277,55 +278,94 @@ void TQt6PadPainter::DrawPolyLineNDC(Int_t nPoints, const Double_t *u, const Dou } //////////////////////////////////////////////////////////////////////////////// -/// Paint polymarker. +/// Helper function to paint markers -void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Double_t *x, const Double_t *y) +template +void drawMarkersHelper(Int_t nPoints, const T *x, const T *y, + const TAttMarker &attr, const QColor &col, QPainter *painter) { - auto painter = fPaintWidget->getPainter(); - if (!painter || nPoints < 1) return; + auto markerLineWidth = TAttMarker::GetMarkerLineWidth(attr.GetMarkerStyle()); + Int_t markerSize = 0; ///< size of simple markers + std::vector markerShape; ///< marker shape points + auto markerType = attr.GetMarkerShape(markerSize, markerShape); + painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(Qt::NoPen); - painter->setBrush(QBrush(GetQColor(GetAttMarker().GetMarkerColor()))); - Int_t radius = GetAttMarker().GetMarkerSize(); - if (radius < 2) - radius = 2; + if ((markerType == TAttMarker::kShapeDot) || (markerLineWidth > 0)) { + QPen customPen; + customPen.setColor(col); + customPen.setWidth(markerLineWidth); + customPen.setStyle(Qt::SolidLine); + painter->setPen(customPen); + painter->setBrush(Qt::NoBrush); + } else { + QBrush customBrush(col); + painter->setBrush(customBrush); + painter->setPen(Qt::NoPen); + } - // 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); + + QList points; + for (auto &pnt : markerShape) + points.push_back({(qreal) (px + pnt.fX), (qreal) (py + pnt.fY)}); + + switch(markerType) { + case TAttMarker::kShapeDot: + painter->drawPoint(px, py); + break; + case TAttMarker::kShapeCircle: + case TAttMarker::kShapeFilledCircle: + // hollow or filled circle + painter->drawEllipse({px, py}, markerSize / 2, markerSize / 2); + break; + case TAttMarker::kShapePolyLine: + // hollow polygon + painter->drawPolyline(points); + break; + case TAttMarker::kShapeFilledArea: + // filled polygon + painter->drawPolygon(points); + break; + case TAttMarker::kShapeSegments: + // segmented line + painter->drawLines(points); + break; + case TAttMarker::kShapeTriangles: + // filled triangles + for (int i = 0; i < points.size(); i += 3) { + // Construct a temporary triangle polygon + QPolygonF triangle; + triangle << points[i] << points[i + 1] << points[i + 2]; + // Draw and fill the triangle + painter->drawPolygon(triangle); + } + break; + } } } //////////////////////////////////////////////////////////////////////////////// /// Paint polymarker. -void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float_t *y) +void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Double_t *x, const Double_t *y) { - auto painter = fPaintWidget->getPainter(); - - if (!painter || nPoints < 1) - return; - - painter->setRenderHint(QPainter::Antialiasing); - painter->setPen(Qt::NoPen); - painter->setBrush(QBrush(GetQColor(GetAttMarker().GetMarkerColor()))); + drawMarkersHelper(nPoints, x, y, + GetAttMarker(), GetQColor(GetAttMarker().GetMarkerColor()), fPaintWidget->getPainter()); +} - Int_t radius = GetAttMarker().GetMarkerSize(); - if (radius < 2) - radius = 2; +//////////////////////////////////////////////////////////////////////////////// +/// Paint polymarker. - // 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); - } +void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float_t *y) +{ + drawMarkersHelper(nPoints, x, y, + GetAttMarker(), GetQColor(GetAttMarker().GetMarkerColor()), fPaintWidget->getPainter()); } //////////////////////////////////////////////////////////////////////////////// From a7fecce1e61acfedab22d0d7bca67adf9bda3b93 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Thu, 6 Aug 2026 10:12:12 +0200 Subject: [PATCH 30/30] [qt6canvas] add several checks and missing includes Prevent access to nullptr Prevent out-of-bound access to markers Remove timeout from mouse handling AI-assist with qwen llmbot --- gui/qt6canvas/src/QPaintWidget.cpp | 6 ------ gui/qt6canvas/src/TQt6Canvas.cxx | 8 +++----- gui/qt6canvas/src/TQt6GuiFactory.cxx | 2 -- gui/qt6canvas/src/TQt6PadPainter.cxx | 3 ++- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/gui/qt6canvas/src/QPaintWidget.cpp b/gui/qt6canvas/src/QPaintWidget.cpp index 1fee59e366f1f..07c7ec8572313 100644 --- a/gui/qt6canvas/src/QPaintWidget.cpp +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -180,13 +180,7 @@ void QPaintWidget::mousePressEvent(QMouseEvent *e) 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); diff --git a/gui/qt6canvas/src/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx index 00f322544d874..42d3d336fc8df 100644 --- a/gui/qt6canvas/src/TQt6Canvas.cxx +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -21,11 +21,8 @@ #include #include -#include #include #include -#include -#include #include "QCanvasWidget.h" @@ -238,7 +235,7 @@ UInt_t TQt6Canvas::GetWindowGeometry(Int_t &x, Int_t &y, UInt_t &w, UInt_t &h) Bool_t TQt6Canvas::PerformUpdate(Bool_t /* async */) { - if (Canvas()->IsModified()) + if (Canvas()->IsModified() && fPaintWidget) fPaintWidget->update(); return kTRUE; } @@ -272,7 +269,8 @@ TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y imp->fCanvasWidget = widget; imp->fPaintWidget = widget->GetPaintWidget(); - imp->fPaintWidget->SetCanvas(c); + if (imp->fPaintWidget) + imp->fPaintWidget->SetCanvas(c); // set all internal dimensions c->Resize(); diff --git a/gui/qt6canvas/src/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx index 7e62cc1420266..ab0f8e7d526ae 100644 --- a/gui/qt6canvas/src/TQt6GuiFactory.cxx +++ b/gui/qt6canvas/src/TQt6GuiFactory.cxx @@ -23,8 +23,6 @@ For Qt6 it provides specialization for TCanvasImp and TContextMenuImp classes #include "TQt6Application.h" #include "QRootContextMenu.h" -#include - using namespace ROOT::Experimental; //////////////////////////////////////////////////////////////////////////////// diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx index 21844c4aa7f9a..58bacf6f614af 100644 --- a/gui/qt6canvas/src/TQt6PadPainter.cxx +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -21,6 +21,7 @@ #include "RStipples.h" #include +#include #include "QPaintWidget.h" @@ -338,7 +339,7 @@ void drawMarkersHelper(Int_t nPoints, const T *x, const T *y, break; case TAttMarker::kShapeTriangles: // filled triangles - for (int i = 0; i < points.size(); i += 3) { + for (int i = 0; i < points.size() - 2; i += 3) { // Construct a temporary triangle polygon QPolygonF triangle; triangle << points[i] << points[i + 1] << points[i + 2];