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/.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) 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)"); +} 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/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..c5620d93aee0b --- /dev/null +++ b/gui/qt6canvas/CMakeLists.txt @@ -0,0 +1,52 @@ +# 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 Widgets 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_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +ROOT_STANDARD_LIBRARY_PACKAGE(ROOTQt6Canvas + HEADERS + TQt6Application.h + TQt6Canvas.h + TQt6PadPainter.h + TQt6GuiFactory.h + SOURCES + src/QCanvasWidget.cpp + src/QPaintWidget.cpp + src/QRootMethodDialog.cpp + src/QRootContextMenu.cpp + src/TQt6Application.cxx + src/TQt6Canvas.cxx + src/TQt6PadPainter.cxx + src/TQt6GuiFactory.cxx + LIBRARIES + Qt6::Core + Qt6::Widgets + DEPENDENCIES + Core + Gpad +) + +target_include_directories(ROOTQt6Canvas PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) \ No newline at end of file diff --git a/gui/qt6canvas/inc/LinkDef.h b/gui/qt6canvas/inc/LinkDef.h new file mode 100644 index 0000000000000..e04d586303ba0 --- /dev/null +++ b/gui/qt6canvas/inc/LinkDef.h @@ -0,0 +1,22 @@ +// 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+; +#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..beba095366e2a --- /dev/null +++ b/gui/qt6canvas/inc/TQt6Application.h @@ -0,0 +1,45 @@ +// 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 {} + + static void CreateQApplication(int argc = 0, char **argv = nullptr); + + ClassDefOverride(TQt6Application,0) // ROOT Qt6 GUI application environment +}; + +} // namespace Experimental +} // namespace ROOT + + +#endif diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h new file mode 100644 index 0000000000000..725452d1ca306 --- /dev/null +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -0,0 +1,84 @@ +// 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 QWidget; +class QCanvasWidget; +class QPaintWidget; + +namespace ROOT { +namespace Experimental { + + +class TQt6Canvas : public TCanvasImp { + +protected: + + QCanvasWidget *fCanvasWidget = nullptr; + QPaintWidget *fPaintWidget = nullptr; + + Bool_t fFixedSize = kFALSE; /// +#include +#include +#include +#endif + +class QPaintWidget; + +namespace ROOT { +namespace Experimental { + +class TQt6Canvas; + +class TQt6PadPainter : public TPadPainterBase { + +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); + QPen GetLinePen(); + QBrush GetFillBrush(); + +public: + + TQt6PadPainter(QPaintWidget *widget = nullptr) { fPaintWidget = widget; } + + Bool_t HasTTFonts() const override { return kTRUE; } + + Bool_t IsNative() const override { return kTRUE; } + + + void SetOpacity(Int_t percent) override; + + //2. "Off-screen management" part. + // return non-zero value to let execute painting code + Int_t CreateDrawable(UInt_t, UInt_t) override { return 223344; } + void ClearDrawable() override {} + void CopyDrawable(Int_t, Int_t, Int_t) override {} + void DestroyDrawable(Int_t) override {} + void SelectDrawable(Int_t) override {} + void SetDoubleBuffer(Int_t /* device */, Int_t /* mode */) override {} + void SetCursor(Int_t, ECursor) override; + + //jpg, png, bmp, gif output. + void SaveImage(TVirtualPad *, const char *, Int_t) const override; + + //TASImage support (noop for a non-gl pad). + void DrawPixels(const unsigned char *pixelData, UInt_t width, UInt_t height, + Int_t dstX, Int_t dstY, Bool_t enableAlphaBlending) override; + + void DrawLine(Double_t x1, Double_t y1, Double_t x2, Double_t y2) override; + void DrawLineNDC(Double_t u1, Double_t v1, Double_t u2, Double_t v2) override; + + void DrawBox(Double_t x1, Double_t y1, Double_t x2, Double_t y2, EBoxMode mode) override; + //TPad needs double and float versions. + void DrawFillArea(Int_t n, const Double_t *x, const Double_t *y) override; + void DrawFillArea(Int_t n, const Float_t *x, const Float_t *y) override; + + //TPad needs both double and float versions of DrawPolyLine. + void DrawPolyLine(Int_t n, const Double_t *x, const Double_t *y) override; + void DrawPolyLine(Int_t n, const Float_t *x, const Float_t *y) override; + void DrawPolyLineNDC(Int_t n, const Double_t *u, const Double_t *v) override; + + //TPad needs both versions. + void DrawPolyMarker(Int_t n, const Double_t *x, const Double_t *y) override; + void DrawPolyMarker(Int_t n, const Float_t *x, const Float_t *y) override; + + void DrawText(Double_t x, Double_t y, const char *text, ETextMode mode) override; + void DrawText(Double_t x, Double_t y, const wchar_t *text, ETextMode mode) override; + void DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode mode) override; + void DrawTextNDC(Double_t u, Double_t v, const wchar_t *text, ETextMode mode) override; + + 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: + //Let's make this clear: + TQt6PadPainter(const TQt6PadPainter &rhs) = delete; + TQt6PadPainter(TQt6PadPainter && rhs) = delete; + TQt6PadPainter & operator = (const TQt6PadPainter &rhs) = delete; + TQt6PadPainter & operator = (TQt6PadPainter && rhs) = delete; + + ClassDefOverride(TQt6PadPainter, 0) // Pad painter on Qt6 canvas +}; + +} // namespace Experimental +} // namespace ROOT + +#endif diff --git a/gui/qt6canvas/index.md b/gui/qt6canvas/index.md new file mode 100644 index 0000000000000..b43a2f87f3823 --- /dev/null +++ b/gui/qt6canvas/index.md @@ -0,0 +1,3 @@ +\defgroup qt6canvas QT6 Canvas Display +\ingroup gpad +\brief Classes for canvas display using QT6 diff --git a/gui/qt6canvas/src/QCanvasWidget.cpp b/gui/qt6canvas/src/QCanvasWidget.cpp new file mode 100644 index 0000000000000..3a966fa52c4b0 --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.cpp @@ -0,0 +1,547 @@ +// 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 "QCanvasWidget.h" + +#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 +#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 } +}; + + + +/** \class QCanvasWidget + \ingroup qt6canvas + +Qt widget which display canvas and provides menu, toolbar and status bar +Actual graphics shown in the \ref QPaintWidget +*/ + + +//////////////////////////////////////////////////////////////////////////////// +/// constructor + +QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent) +{ + setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + setObjectName(name); + + connect(fPaintWidget, &QPaintWidget::CanvasStatusEvent, this, &QCanvasWidget::CanvasStatusEventSlot); + + // fMenuBar = new QMenuBar(fMenuFrame); + fMenuBar->setMinimumWidth(50); + fMenuBar->setNativeMenuBar(kFALSE); // disable putting this to screen menu. for MAC style WMs + + 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(); + + 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(); + + fileMenu->addAction("Print...", this, &QCanvasWidget::PrintCanvas); + fileMenu->addSeparator(); + + fileMenu->addAction("Quit ROOT", this, &QCanvasWidget::QuitRoot); + + + fMenuBar->addMenu("&Edit"); + + 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(false); + + 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"); + + fMenuBar->addMenu("&Tools"); + + auto spacer = new QWidget(this); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + fMenuBar->setCornerWidget(spacer, Qt::TopRightCorner); + + fMenuBar->addMenu("&Help"); + + // 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; + } + }); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// 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 + gROOT->SetInterrupt(kTRUE); + + if (gApplication) + 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 new file mode 100644 index 0000000000000..ec67f90c7e93c --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.h @@ -0,0 +1,71 @@ +// 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 +#define ROOT_QCanvasWidget + +#include +#include +#include "ui_QCanvasWidget.h" + +#include +#include + +class TH1F; +class TH2I; + +class QCanvasWidget : public QWidget, public Ui::QCanvasWidget { + Q_OBJECT + + QAction *fViewToolbar = nullptr; + QAction *fViewEventStatus = nullptr; + QAction *fViewToolTip = nullptr; + + QString fLastFileDir; + +protected: + + std::vector GetSupportedFileFormats(); + + void FillSaveMenu(QMenu *menu); + +public: + QCanvasWidget(QWidget *parent = nullptr, const char *name = nullptr); + + virtual ~QCanvasWidget(); + + QPaintWidget *GetPaintWidget() const { return fPaintWidget; } + + void SaveCanvas(const QString &fname); + + void ApplyCanvasStatusBits(); + +public slots: + + void NewCanvas(); + void OpenRootFile(); + void CloseCanvas(); + + 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 new file mode 100644 index 0000000000000..1285b7a0d4059 --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.ui @@ -0,0 +1,102 @@ + + Sergey Linev + + + QCanvasWidget + + + + 0 + 0 + 682 + 559 + + + + QtWeb ROOT example + + + + + + + 7 + 1 + 0 + 0 + + + + + 0 + 30 + + + + + + + + + 7 + 1 + 0 + 0 + + + + + 0 + 30 + + + + + + + + + 7 + 7 + 0 + 20 + + + + + 50 + 50 + + + + + + + + + 7 + 1 + 0 + 0 + + + + + 0 + 10 + + + + + + + + + + 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..07c7ec8572313 --- /dev/null +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -0,0 +1,304 @@ +// 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 + +#include "TCanvas.h" +#include "TROOT.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#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) +{ + setObjectName("QPaintWidget"); + + setSizeIncrement(QSize(100, 100)); + + setUpdatesEnabled(true); + setMouseTracking(true); + + setFocusPolicy(Qt::TabFocus); + setCursor(Qt::CrossCursor); + + setAcceptDrops(true); + + fCanvas = nullptr; + + 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()); + int scaledY = scaledPosition(e->position().y()); + return QPoint(scaledX, scaledY); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Generic event handler +/// +/// Used to handle tooltip event - if configured + +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); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Resize event handler + +void QPaintWidget::resizeEvent(QResizeEvent *) +{ + if (fCanvas) { + fCanvas->Resize(); + fCanvas->Modified(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// 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 { + QPainter painter(this); + + fPainter = &painter; + + fCanvas->Paint(); + + fPainter = nullptr; + + } catch(...) { + fPainter = nullptr; + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Mouse press event handler + +void QPaintWidget::mousePressEvent(QMouseEvent *e) +{ + QPoint scaled = scaledMousePoint(e); + + switch(e->button()) { + case Qt::LeftButton: + fCanvas->HandleInput(kButton1Down, scaled.x(), scaled.y()); + // emit PadClicked(pad, scaled.x(), scaled.y()); + break; + case Qt::RightButton : { + fCanvas->HandleInput(kButton3Down, scaled.x(), scaled.y()); + break; + } + case Qt::MiddleButton : + fCanvas->HandleInput(kButton2Down, scaled.x(), scaled.y()); + // emit SelectedPadChanged(pad); + break; + case Qt::NoButton : + break; + default: + break; + } + e->accept(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Mouse move event handler + +void QPaintWidget::mouseMoveEvent(QMouseEvent *e) +{ + e->accept(); + + 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); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Mouse release event handler + +void QPaintWidget::mouseReleaseEvent(QMouseEvent *event) +{ + QPoint scaled = scaledMousePoint(event); + + switch(event->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; + } + + event->accept(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Mouse double click event handler + +void QPaintWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + QPoint scaled = scaledMousePoint(event); + + switch(event->button()) { + case Qt::LeftButton : { + if (!fMaskDoubleClick) + fCanvas->HandleInput(kButton1Double, 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; + } + + event->accept(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Wheel event handler + +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(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Enter event handler + +void QPaintWidget::enterEvent(QEnterEvent *event) +{ + QWidget::enterEvent(event); + + fCanvas->HandleInput(kMouseEnter, 0, 0); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Leave event handler + +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 new file mode 100644 index 0000000000000..373d7ba52342b --- /dev/null +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -0,0 +1,71 @@ +// 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 ROOT_QPaintWidget +#define ROOT_QPaintWidget + +#include + +class TCanvas; +class TPad; +class TMethod; +class QPainter; + +class QPaintWidget : public QWidget { + + Q_OBJECT + +public: + QPaintWidget(QWidget *parent = nullptr); + virtual ~QPaintWidget(); + + /// returns canvas shown in the widget + TCanvas *getCanvas() { return fCanvas; } + + QPainter *getPainter() const { return fPainter; } + + void SetCanvas(TCanvas *canv) { fCanvas = canv; } + + 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; + + void mousePressEvent(QMouseEvent *event) override; + 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; } + + QPoint scaledMousePoint(QMouseEvent *event); + + TCanvas *fCanvas = nullptr; ///< canvas shown in the widget + + QPainter *fPainter = nullptr; ///< temporary painter created inside paintEvent handler + + double fQtScalingfactor = 1.; + + bool fMaskDoubleClick = false; + bool fShowEventStatus = true; + bool fShowToolTip = false; +}; + +#endif diff --git a/gui/qt6canvas/src/QRootContextMenu.cpp b/gui/qt6canvas/src/QRootContextMenu.cpp new file mode 100644 index 0000000000000..ed0961bbab96f --- /dev/null +++ b/gui/qt6canvas/src/QRootContextMenu.cpp @@ -0,0 +1,802 @@ +// Author: Sergey Linev 2/07/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + + +/** \class QRootContextMenu + \ingroup 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. +*/ + + +#include "QRootContextMenu.h" + +#include "TROOT.h" +#include "TContextMenu.h" +#include "TCanvas.h" +#include "TColor.h" +#include "TMethod.h" +#include "TDataMember.h" +#include "TToggle.h" +#include "TClassMenuItem.h" +#include "TAttText.h" +#include "TAttMarker.h" + +#include "QRootMethodDialog.h" +#include "QPaintWidget.h" +#include "TQt6Canvas.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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. + +QRootContextMenu::QRootContextMenu(TContextMenu *c, const char *) + : QObject(), TObject(), TContextMenuImp(c) +{ + gROOT->GetListOfCleanups()->Add(this); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Delete a context menu. + +QRootContextMenu::~QRootContextMenu() +{ + gROOT->GetListOfCleanups()->Remove(this); + fTrash.Delete(); +} + +//////////////////////////////////////////////////////////////////////////////// +/// 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()); + + auto object = fContextMenu->GetSelectedObject(); + if (!object) + return; + + fCustomArg.clear(); + fTrash.Delete(); + + + auto canv = dynamic_cast(fContextMenu->GetSelectedCanvas()); + auto canvimp = dynamic_cast(canv->GetCanvasImp()); + auto widget = canvimp->GetPaintWidget(); + + QPoint screenPos = widget->mapToGlobal(widget->rect().topLeft()); + + QMenu menu; + QSignalMapper map; + + QObject::connect(&map, &QSignalMapper::mappedInt, + this, &QRootContextMenu::executeMenu); + + // Add a title + QString buffer = fContextMenu->CreatePopupTitle(object); + addMenuAction(&menu, &map, buffer, -1, nullptr); + menu.addSeparator(); + bool last_separ = true; + + int entry = 0, toggle = kToggleStart, togglelist = kToggleListStart; + int userfunction = kUserFunctionStart; + + // Get list of menu items from the selected object's class + TList *menuItemList = object->IsA()->GetMenuList(); + + TIter nextItem(menuItemList); + + while (auto menuItem = (TClassMenuItem*) nextItem()) { + switch (menuItem->GetType()) { + case TClassMenuItem::kPopupSeparator: { + if (!last_separ) + 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)); +} + +//////////////////////////////////////////////////////////////////////////////// +/// 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 * func) +{ + QRootMethodDialog dlg; + dlg.methodDialog(fContextMenu, object, func); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Handle remove of some ROOT objects + +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); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Register menu action in signal map + +QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper *map, const QString& text, int id, void *arg) +{ + 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); + + fCustomArg[id] = arg; + + return act; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Add color elements to attributes editor dialog + +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); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Update color button with currently selected color + +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); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttLine editor + +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); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttFill editor + +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); + } +}; + + +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttText editor + +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()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Start TAttMarker editor + +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); + } + +} + +//////////////////////////////////////////////////////////////////////////////// +/// Execute specified menu item + +void QRootContextMenu::executeMenu(int id) +{ + if (id < 0) + return; + void *ud = fCustomArg[id]; + + if (ud) { + // retrieve the highlighted function + TFunction *function = nullptr; + if (id < kToggleStart) { + TMethod *m = (TMethod *)ud; + function = (TFunction *)m; + } else if (id >= kToggleStart && id < kUserFunctionStart) { + TToggle *t = (TToggle *)ud; + TMethodCall *mc = (TMethodCall *)t->GetSetter(); + function = (TFunction *)mc->GetMethod(); + } else { + TClassMenuItem *mi = (TClassMenuItem *)ud; + function = gROOT->GetGlobalFunctionWithPrototype(mi->GetFunctionName()); + } + if (function) + fContextMenu->SetMethod(function); + } + + if (id < kToggleStart) { + auto m = (TMethod *) ud; + + if (!strcmp(m->GetName(), "SetLineAttributes")) { + 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); + } 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 new file mode 100644 index 0000000000000..5bca77e42fd73 --- /dev/null +++ b/gui/qt6canvas/src/QRootContextMenu.h @@ -0,0 +1,64 @@ +// 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 + +#include "TContextMenuImp.h" +#include "TObject.h" +#include "TList.h" +#include + +class QSignalMapper; +class TList; +class QAction; +class QMenu; +class QFormLayout; +class QPushButton; + +class QRootContextMenu : public QObject, public TObject, public TContextMenuImp { + Q_OBJECT + +public slots: + void executeMenu(int id); + +protected: + + 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; + + 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/QRootMethodDialog.cpp b/gui/qt6canvas/src/QRootMethodDialog.cpp new file mode 100644 index 0000000000000..17dea3fa68f2d --- /dev/null +++ b/gui/qt6canvas/src/QRootMethodDialog.cpp @@ -0,0 +1,199 @@ +// 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 "TContextMenu.h" + +#include + +/** \class QRootMethodDialog + \ingroup qt6canvas + +Specialized dialog to enter arguments for method exection +*/ + +//////////////////////////////////////////////////////////////////////////////// +/// Create method dialog + +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); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Add argument + +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); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Get argument value + +QString QRootMethodDialog::getArg(int n) +{ + if ((n<0) || (n>=fArgs.size())) return QString(""); + return fArgs[n]->text(); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Run method dialog + +void QRootMethodDialog::methodDialog(TContextMenu *menu, TObject *object, TFunction* func) +{ + if (!menu || !object || !func) + return; + + setWindowTitle(menu->CreateDialogTitle(object, func)); + + // iterate through all arguments and create appropriate input-data objects: + // inputlines, option menus... + TIter next(func->GetListOfMethodArgs()); + + while (auto argument = (TMethodArg *) next()) { + TString argTitle = menu->CreateArgumentTitle(argument); + TString type = argument->GetTypeName(); + TDataType *datatype = gROOT->GetType(type); + TString basictype; + + if (datatype) { + basictype = datatype->GetTypeName(); + } else { + if (type.CompareTo("enum") != 0) + std::cout << "*** Warning in Dialog(): data type is not basic type, assuming (int)\n"; + basictype = "int"; + } + + if (TString(argument->GetTitle()).Index("*") != kNPOS) { + basictype += "*"; + type = "char*"; + } + + TDataMember *m = argument->GetDataMember(); + if (m && m->GetterMethod()) { + + m->GetterMethod()->Init(object->IsA(), m->GetterMethod()->GetMethodName(), ""); + + // Get the current value and form it as a text: + + TString val; + + if (basictype == "char*") { + char *tdefval = nullptr; + m->GetterMethod()->Execute(object, "", &tdefval); + if (tdefval) val = tdefval; + } else + if ((basictype == "float") || + (basictype == "double")) { + Double_t ddefval = 0.; + m->GetterMethod()->Execute(object, "", ddefval); + val = TString::Format("%g", ddefval); + } else + if ((basictype == "char") || + (basictype == "int") || + (basictype == "bool") || + (basictype == "long") || + (basictype == "short")) { + Longptr_t ldefval = 0; + m->GetterMethod()->Execute(object, "", ldefval); + val = TString::Format("%ld", (long) ldefval); + } + + // 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 ... + TString argDflt; + if (argument->GetDefault()) + argDflt = argument->GetDefault(); + + if ((argDflt.Length() > 1) && + (argDflt[0]=='\"') && (argDflt[argDflt.Length()-1]=='\"')) { + // cut "" from the string argument + argDflt.Remove(0,1); + argDflt.Remove(argDflt.Length()-1,1); + } + + addArg(argTitle.Data(), argDflt.Data(), type.Data()); + } + } + + if (exec() != QDialog::Accepted) + return; + + TObjArray tobjlist(func->GetListOfMethodArgs()->LastIndex() + 1); + for (int n = 0; n <= func->GetListOfMethodArgs()->LastIndex(); n++) { + QString s = getArg(n); + tobjlist.AddLast(new TObjString(s.toLatin1().constData())); + } + + menu->Execute(object, func, &tobjlist); +} diff --git a/gui/qt6canvas/src/QRootMethodDialog.h b/gui/qt6canvas/src/QRootMethodDialog.h new file mode 100644 index 0000000000000..9794184f60ec4 --- /dev/null +++ b/gui/qt6canvas/src/QRootMethodDialog.h @@ -0,0 +1,49 @@ +// 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 ROOT_QRootMethodDialog +#define ROOT_QRootMethodDialog + +#include +#include + +class QLineEdit; +class QVBoxLayout; +class TObject; +class TFunction; +class TContextMenu; + +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(TContextMenu *menu, TObject *object, TFunction* func); + + signals: + void MenuCommandExecuted(TObject *obj, const char *method_name); + + protected: + QVBoxLayout *argLayout{nullptr}; + + QVector fArgs; +}; + +#endif diff --git a/gui/qt6canvas/src/TQt6Application.cxx b/gui/qt6canvas/src/TQt6Application.cxx new file mode 100644 index 0000000000000..3869f8a3f624e --- /dev/null +++ b/gui/qt6canvas/src/TQt6Application.cxx @@ -0,0 +1,91 @@ +// 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. * + *************************************************************************/ + +#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 + +Provides Qt6-specific methods for TApplication. +Main purpose - runs Qt event loop +*/ + + +//////////////////////////////////////////////////////////////////////////////// +/// Create Qt6 application environment. + +TQt6Application::TQt6Application(const char *appClassName, + Int_t *argc, char **argv) : TApplicationImp(appClassName, argc, argv) +{ + CreateQApplication(*argc, argv); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Delete Qt6 application environment. + +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 new file mode 100644 index 0000000000000..42d3d336fc8df --- /dev/null +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -0,0 +1,292 @@ +// 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 "TQt6Canvas.h" + +#include "TQt6PadPainter.h" +#include "TQt6Application.h" + +#include "TSystem.h" +#include "TStyle.h" +#include "TCanvas.h" +#include "TROOT.h" +#include "TClass.h" + +#include +#include +#include +#include + +#include "QCanvasWidget.h" + +using namespace ROOT::Experimental; + +/** \class TQt6Canvas + \ingroup qt6canvas + \brief Basic TCanvasImp ABI implementation for Qt6 +*/ + +//////////////////////////////////////////////////////////////////////////////// +/// 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) +{ +} + + +//////////////////////////////////////////////////////////////////////////////// +/// 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(fPaintWidget); +} + + +////////////////////////////////////////////////////////////////////////////////////////// +/// 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) +{ + if (fCanvasWidget) + fCanvasWidget->move(x, y); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set window size of web canvas + +void TQt6Canvas::SetWindowSize(UInt_t w, UInt_t h) +{ + if (fCanvasWidget) + fCanvasWidget->resize(w, h); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set window title of web canvas + +void TQt6Canvas::SetWindowTitle(const char *newTitle) +{ + if (fCanvasWidget) + fCanvasWidget->setWindowTitle(QString::fromLatin1(newTitle)); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Set canvas size + +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() +{ + if (fCanvasWidget) + fCanvasWidget->showMinimized(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Raise browser window + +void TQt6Canvas::RaiseWindow() +{ + if (fCanvasWidget) { + fCanvasWidget->showNormal(); + fCanvasWidget->raise(); + fCanvasWidget->activateWindow(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// 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); + Canvas()->SetBit(TCanvas::kShowToolBar, bits & TCanvas::kShowToolBar); + if (fCanvasWidget) + fCanvasWidget->ApplyCanvasStatusBits(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// Returns canvas geometry + +void TQt6Canvas::GetCanvasGeometry(Int_t wid, UInt_t &w, UInt_t &h) +{ + (void) wid; + if (fPaintWidget) { + w = fPaintWidget->width(); + h = fPaintWidget->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) +{ + 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; + } + + return 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/// if canvas or any subpad was modified, +/// invoke Qt update() which will redraw area + +Bool_t TQt6Canvas::PerformUpdate(Bool_t /* async */) +{ + if (Canvas()->IsModified() && fPaintWidget) + fPaintWidget->update(); + 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 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) +{ + // ensure QApplication is exists + TQt6Application::CreateQApplication(); + + auto widget = new QCanvasWidget(); + widget->setWindowTitle(QString(c->GetTitle())); + 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); + + imp->fCanvasWidget = widget; + imp->fPaintWidget = widget->GetPaintWidget(); + + if (imp->fPaintWidget) + imp->fPaintWidget->SetCanvas(c); + + // set all internal dimensions + c->Resize(); + + // TODO: maybe apply same logic to adjust canvas dimension as in TRootCanvas + // Keep commented code here intentionally to be able find this place when + // search for correspondent class members + + // c->fWindowTopX = x; + // c->fWindowTopY = y; + // c->fWindowWidth = width; + // c->fWindowHeight = height; + // if (!gROOT->IsBatch() && (height > 25)) + // height -= 25; + // c->fCw = width; + // c->fCh = height; + + return imp; +} diff --git a/gui/qt6canvas/src/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx new file mode 100644 index 0000000000000..ab0f8e7d526ae --- /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 qt6canvas + +This class is a factory for ROOT GUI components. +For Qt6 it provides specialization for TCanvasImp and TContextMenuImp classes +*/ + + +#include "TQt6GuiFactory.h" + +#include "TQt6Canvas.h" +#include "TQt6Application.h" +#include "QRootContextMenu.h" + +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 new TQt6Application(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 *) +{ + return new QRootContextMenu(c, name); +} diff --git a/gui/qt6canvas/src/TQt6PadPainter.cxx b/gui/qt6canvas/src/TQt6PadPainter.cxx new file mode 100644 index 0000000000000..58bacf6f614af --- /dev/null +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -0,0 +1,720 @@ +// 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 "TSystem.h" +#include "TStyle.h" +#include "TEnv.h" +#include "TMath.h" +#include "TPad.h" +#include "TPoint.h" +#include "TROOT.h" +#include "TColor.h" +#include "RStipples.h" + +#include +#include + +#include "QPaintWidget.h" + +#include +#include +#include +#include + +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 + + Uses QPainter object which only exists inside paintEvent of Qt. +*/ + +////////////////////////////////////////////////////////////////////////// +/// Set opacity - similar to TVirtualPS usecase + +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. + +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) +{ + 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)); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Paint a simple line in normalized coordinates. + +void TQt6PadPainter::DrawLineNDC(Double_t u1, Double_t v1, Double_t u2, Double_t v2) +{ + 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. + +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; + + 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); + painter->setBrush(GetFillBrush()); + } + + QRect rectangle(TMath::Min(px1, px2), TMath::Min(py1, py2), TMath::Abs(px2 - px1), TMath::Abs(py2 - py1)); + painter->drawRect(rectangle); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint filled area. + +void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Double_t *xs, const Double_t *ys) +{ + auto painter = fPaintWidget->getPainter(); + + if (!painter || (GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) + return; + + QList points; + 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()); + painter->drawPolygon(points); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint filled area. + +void TQt6PadPainter::DrawFillArea(Int_t nPoints, const Float_t *xs, const Float_t *ys) +{ + auto painter = fPaintWidget->getPainter(); + + if (!painter || (GetAttFill().GetFillStyle() <= 0) || (nPoints < 3)) + return; + + QList points; + 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()); + painter->drawPolygon(points); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint Polyline. + +void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Double_t *xs, const Double_t *ys) +{ + auto painter = fPaintWidget->getPainter(); + if (!painter || (GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + return; + + QList points; + 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()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPolyline(points); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polyline. + +void TQt6PadPainter::DrawPolyLine(Int_t nPoints, const Float_t *xs, const Float_t *ys) +{ + auto painter = fPaintWidget->getPainter(); + if (!painter || (GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + return; + + QList points; + 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()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPolyline(points); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polyline in normalized coordinates. + +void TQt6PadPainter::DrawPolyLineNDC(Int_t nPoints, const Double_t *u, const Double_t *v) +{ + auto painter = fPaintWidget->getPainter(); + if (!painter || (GetAttLine().GetLineWidth() <= 0) || (nPoints < 2)) + return; + + QList points; + 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()); + + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPolyline(points); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Helper function to paint markers + +template +void drawMarkersHelper(Int_t nPoints, const T *x, const T *y, + const TAttMarker &attr, const QColor &col, QPainter *painter) +{ + 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); + + 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); + } + + for (Int_t n = 0; n < nPoints; ++n) { + Int_t px = gPad->XtoAbsPixel(x[n]); + Int_t py = gPad->YtoAbsPixel(y[n]); + + 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() - 2; 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 Double_t *x, const Double_t *y) +{ + drawMarkersHelper(nPoints, x, y, + GetAttMarker(), GetQColor(GetAttMarker().GetMarkerColor()), fPaintWidget->getPainter()); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polymarker. + +void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float_t *y) +{ + drawMarkersHelper(nPoints, x, y, + GetAttMarker(), GetQColor(GetAttMarker().GetMarkerColor()), fPaintWidget->getPainter()); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text. + +void TQt6PadPainter::DrawText(Double_t x, Double_t y, const char *text, ETextMode /*mode*/) +{ + const Int_t px = gPad->XtoAbsPixel(x); + const Int_t py = gPad->YtoAbsPixel(y); + + PaintQString(px, py, QString::fromLatin1(text)); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text with url + +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, QString::fromLatin1(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*/) +{ + const Int_t px = gPad->XtoAbsPixel(x); + const Int_t py = gPad->YtoAbsPixel(y); + + PaintQString(px, py, QString::fromWCharArray(text)); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text in normalized coordinates. + +void TQt6PadPainter::DrawTextNDC(Double_t u, Double_t v, const char *text, ETextMode /*mode*/) +{ + const Int_t px = gPad->UtoAbsPixel(u); + const Int_t py = gPad->VtoAbsPixel(v); + + PaintQString(px, py, QString::fromLatin1(text)); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint text in normalized coordinates. + +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)); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Produce image + +void TQt6PadPainter::SaveImage(TVirtualPad * /* pad */, const char * /* fileName */, Int_t /* gtype */) const +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// 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 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 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 + +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; + + auto textsize = att.GetTextSizePixels(*gPad); + Int_t pixelsize = (Int_t) (textsize*kScale+0.5); + + painter->setFont(QFont(family, pixelsize)); + + 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(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// 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; + + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); + QRect rect = fm.boundingRect(QString::fromLatin1(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; + + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); + 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; + + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); + QRect rect = fm.boundingRect(QString::fromLatin1(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; + + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); + 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; + Int_t pixelsize = (Int_t) (size*kScale+0.5); + + QFontMetrics fm(QFont(family, pixelsize)); + return fm.horizontalAdvance(QString::fromLatin1(text)); +}