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 eafb656ada6f7..7654b1d2799e4 100644 --- a/.github/workflows/root-ci-config/buildconfig/global.txt +++ b/.github/workflows/root-ci-config/buildconfig/global.txt @@ -63,6 +63,7 @@ opengl=ON pyroot=ON pythia8=OFF qt6web=OFF +qt6canv=OFF roofit=ON roofit_multiprocess=OFF root7=ON diff --git a/cmake/modules/RootBuildOptions.cmake b/cmake/modules/RootBuildOptions.cmake index a365b15ee6b4f..53f1a0c64f776 100644 --- a/cmake/modules/RootBuildOptions.cmake +++ b/cmake/modules/RootBuildOptions.cmake @@ -147,6 +147,7 @@ ROOT_BUILD_OPTION(mpi OFF "Enable support for Message Passing Interface (MPI)") ROOT_BUILD_OPTION(opengl ON "Enable support for OpenGL (requires libGL and libGLU)") ROOT_BUILD_OPTION(pyroot ON "Enable support for automatic Python bindings (PyROOT)") ROOT_BUILD_OPTION(pythia8 OFF "Enable support for Pythia 8.x [GPL]") +ROOT_BUILD_OPTION(qt6canv OFF "Enable Qt6 canvas (requires Qt6::Gui)") ROOT_BUILD_OPTION(qt6web OFF "Enable support for Qt6 web-based display (requires Qt6::WebEngineCore and Qt6::WebEngineWidgets)") ROOT_BUILD_OPTION(roofit ON "Build the advanced fitting package RooFit, and RooStats for statistical tests. If xml is available, also build HistFactory.") ROOT_BUILD_OPTION(roofit_multiprocess OFF "Build RooFit::MultiProcess and multi-process RooFit::TestStatistics classes (requires ZeroMQ >= 4.3.5 built with -DENABLE_DRAFTS and cppzmq).") @@ -222,6 +223,7 @@ if(all) set(opengl_defvalue ON) set(pythia8_defvalue ON) set(pyroot_defvalue ON) + set(qt6canv_defvalue ON) set(qt6web_defvalue ON) set(r_defvalue ON) set(roofit_defvalue ON) diff --git a/core/gui/src/TGuiFactory.cxx b/core/gui/src/TGuiFactory.cxx index 3c192df533f35..cacc618583bdc 100644 --- a/core/gui/src/TGuiFactory.cxx +++ b/core/gui/src/TGuiFactory.cxx @@ -56,8 +56,18 @@ TApplicationImp *TGuiFactory::CreateApplicationImp(const char *classname, int *a TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t width, UInt_t height) { + TString canvName; if (gROOT->IsWebDisplay()) { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + canvName = "TWebCanvas"; + } else { + canvName = gEnv->GetValue("Canvas.Name", ""); + // in batch web display should be configured explicitely + if (canvName == "TWebCanvas") + canvName.Clear(); + } + + if (!canvName.IsNull() && (canvName != "TRootCanvas") && (canvName != "TCanvasImp")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { auto imp = (TCanvasImp *)ph->ExecPlugin(6, c, title, 0, 0, width, height); @@ -65,7 +75,7 @@ TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t w return imp; } - Error("CreateCanvasImp", "Fail to create TWebCanvas, please provide missing libWebGui6 or run 'root --web=off'"); + Error("CreateCanvasImp", "Fail to create %s canvas implementation", canvName.Data()); } return new TCanvasImp(c, title, width, height); @@ -76,8 +86,18 @@ TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t w TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, Int_t x, Int_t y, UInt_t width, UInt_t height) { + TString canvName; if (gROOT->IsWebDisplay()) { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + canvName = "TWebCanvas"; + } else { + canvName = gEnv->GetValue("Canvas.Name", ""); + // in batch web display should be configured explicitely + if (canvName == "TWebCanvas") + canvName.Clear(); + } + + if (!canvName.IsNull() && (canvName != "TRootCanvas") && (canvName != "TCanvasImp")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { auto imp = (TCanvasImp *)ph->ExecPlugin(6, c, title, x, y, width, height); @@ -85,7 +105,7 @@ TCanvasImp *TGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, Int_t x, return imp; } - Error("CreateCanvasImp", "Fail to create TWebCanvas, please provide missing libWebGui6 or run 'root --web=off'"); + Error("CreateCanvasImp", "Fail to create %s canvas implementation", canvName.Data()); } return new TCanvasImp(c, title, x, y, width, height); diff --git a/etc/plugins/TCanvasImp/P020_TQt6Canvas.C b/etc/plugins/TCanvasImp/P020_TQt6Canvas.C new file mode 100644 index 0000000000000..8acb2d102f74f --- /dev/null +++ b/etc/plugins/TCanvasImp/P020_TQt6Canvas.C @@ -0,0 +1,5 @@ +void P020_TQt6Canvas() +{ + gPluginMgr->AddHandler("TCanvasImp", "TQt6Canvas", "TQt6Canvas", + "ROOTQt6Canvas", "NewCanvas(TCanvas *, const char *, Int_t, Int_t, UInt_t, UInt_t)"); +} diff --git a/etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C b/etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C new file mode 100644 index 0000000000000..18b654eac441c --- /dev/null +++ b/etc/plugins/TGuiFactory/P020_TQt6GuiFactory.C @@ -0,0 +1,5 @@ +void P020_TQt6GuiFactory() +{ + gPluginMgr->AddHandler("TGuiFactory", "qt6", "TQt6GuiFactory", + "ROOTQt6Canvas", "TQt6GuiFactory()"); +} diff --git a/gui/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/gui/src/TRootGuiFactory.cxx b/gui/gui/src/TRootGuiFactory.cxx index 410ff3d28416c..bff81c12e5768 100644 --- a/gui/gui/src/TRootGuiFactory.cxx +++ b/gui/gui/src/TRootGuiFactory.cxx @@ -78,11 +78,12 @@ TCanvasImp *TRootGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, UInt_t width, UInt_t height) { TString canvName = gEnv->GetValue("Canvas.Name", "TWebCanvas"); - if (canvName == "TWebCanvas") { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + if (!canvName.IsNull() && (canvName != "TRootCanvas")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { - ShowWebCanvasWarning(); + if (canvName == "TWebCanvas") + ShowWebCanvasWarning(); auto imp = (TCanvasImp *) ph->ExecPlugin(6, c, title, 0, 0, width, height); if (imp) return imp; } @@ -98,11 +99,12 @@ TCanvasImp *TRootGuiFactory::CreateCanvasImp(TCanvas *c, const char *title, Int_t x, Int_t y, UInt_t width, UInt_t height) { TString canvName = gEnv->GetValue("Canvas.Name", "TWebCanvas"); - if (canvName == "TWebCanvas") { - auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", "TWebCanvas"); + if (!canvName.IsNull() && (canvName != "TRootCanvas")) { + auto ph = gROOT->GetPluginManager()->FindHandler("TCanvasImp", canvName); if (ph && ph->LoadPlugin() != -1) { - ShowWebCanvasWarning(); + if (canvName == "TWebCanvas") + ShowWebCanvasWarning(); auto imp = (TCanvasImp *) ph->ExecPlugin(6, c, title, x, y, width, height); if (imp) return imp; } diff --git a/gui/qt6canvas/CMakeLists.txt b/gui/qt6canvas/CMakeLists.txt new file mode 100644 index 0000000000000..4b32de50341ea --- /dev/null +++ b/gui/qt6canvas/CMakeLists.txt @@ -0,0 +1,49 @@ +# 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 + TQt6Canvas.h + TQt6PadPainter.h + TQt6GuiFactory.h + SOURCES + src/QCanvasWidget.cpp + src/QPaintWidget.cpp + src/QRootMethodDialog.cpp + src/QRootContextMenu.cpp + src/TQt6Canvas.cxx + src/TQt6PadPainter.cxx + src/TQt6GuiFactory.cxx + LIBRARIES + 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..a7ecc53c20666 --- /dev/null +++ b/gui/qt6canvas/inc/LinkDef.h @@ -0,0 +1,21 @@ +// Author: Sergey Linev, GSI 26/06/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ class TQt6Canvas+; +#pragma link C++ class TQt6PadPainter+; +#pragma link C++ class TQt6GuiFactory+; + +#endif diff --git a/gui/qt6canvas/inc/TQt6Canvas.h b/gui/qt6canvas/inc/TQt6Canvas.h new file mode 100644 index 0000000000000..65459f388c283 --- /dev/null +++ b/gui/qt6canvas/inc/TQt6Canvas.h @@ -0,0 +1,83 @@ +// 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 TPad; +class TExec; +class QWidget; +class QCanvasWidget; +class QPaintWidget; + +class TQt6Canvas : public TCanvasImp { + +protected: + + QCanvasWidget *fCanvasWidget = nullptr; + QPaintWidget *fPaintWidget = nullptr; + + Bool_t fFixedSize = kFALSE; /// +#include +#include +#include + +class TQt6Canvas; +class QPaintWidget; + +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 +}; + +#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..46ced882537eb --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.cpp @@ -0,0 +1,106 @@ +// 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 "TApplication.h" +#include "TTimer.h" + +#include +#include + +QCanvasWidget::QCanvasWidget(QWidget *parent, const char *name) : QWidget(parent) +{ + setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + setObjectName(name); + + fPaintWidget->SetStatusBar(fStatusBar); + + + fMenuBar = new QMenuBar(fMenuFrame); + fMenuBar->setMinimumWidth(50); + fMenuBar->setNativeMenuBar(kFALSE); // disable putting this to screen menu. for MAC style WMs + + QMenu* fileMenu = fMenuBar->addMenu("F&ile"); + fileMenu->addAction("&New canvas", this, &QCanvasWidget::NewCanvas); + fileMenu->addAction("Open ...", this, &QCanvasWidget::OpenRootFile); + fileMenu->addAction("Cl&ose canvas", this, &QCanvasWidget::CloseCanvas); + + fileMenu->addSeparator(); + + fileMenu->addAction("Save as...", this, &QCanvasWidget::SaveCanvasAs); + + fileMenu->addSeparator(); + + fileMenu->addAction("Print...", this, &QCanvasWidget::PrintCanvas); + fileMenu->addSeparator(); + + fileMenu->addAction("Quit ROOT", this, &QCanvasWidget::QuitRoot); + + + fMenuBar->addMenu("&Edit"); + + fMenuBar->addMenu("&View"); + + fMenuBar->addMenu("&Options"); + + fMenuBar->addMenu("&Tools"); + + QWidget *spacer = new QWidget(this); + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + fMenuBar->setCornerWidget(spacer, Qt::TopRightCorner); + + fMenuBar->addMenu("&Help"); + +} + +QCanvasWidget::~QCanvasWidget() {} + + +void QCanvasWidget::NewCanvas() +{ + +} + +void QCanvasWidget::OpenRootFile() +{ + +} + +void QCanvasWidget::CloseCanvas() +{ + close(); +} + +void QCanvasWidget::SaveCanvasAs() +{ + +} + +void QCanvasWidget::PrintCanvas() +{ + +} + +void QCanvasWidget::QuitRoot() +{ + // set flag which sometimes checked in TSystem::ProcessEvents + gROOT->SetInterrupt(kTRUE); + + if (gApplication) + TTimer::SingleShot(100, "TApplication", gApplication, "Terminate()"); +} + + diff --git a/gui/qt6canvas/src/QCanvasWidget.h b/gui/qt6canvas/src/QCanvasWidget.h new file mode 100644 index 0000000000000..f5725b2af18b3 --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.h @@ -0,0 +1,45 @@ +// Author: Sergey Linev, GSI 26/06/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_QCanvasWidget_h +#define ROOT_QCanvasWidget_h + +#include +#include +#include "ui_QCanvasWidget.h" + + +class TH1F; +class TH2I; + +class QCanvasWidget : public QWidget, public Ui::QCanvasWidget { + Q_OBJECT + + QMenuBar *fMenuBar = nullptr; + +public: + QCanvasWidget(QWidget *parent = nullptr, const char *name = nullptr); + + virtual ~QCanvasWidget(); + + QPaintWidget *GetPaintWidget() const { return fPaintWidget; } + +public slots: + + void NewCanvas(); + void OpenRootFile(); + void CloseCanvas(); + + void SaveCanvasAs(); + void PrintCanvas(); + void QuitRoot(); +}; + +#endif diff --git a/gui/qt6canvas/src/QCanvasWidget.ui b/gui/qt6canvas/src/QCanvasWidget.ui new file mode 100644 index 0000000000000..21fa473da169e --- /dev/null +++ b/gui/qt6canvas/src/QCanvasWidget.ui @@ -0,0 +1,90 @@ + + Sergey Linev + + + QCanvasWidget + + + + 0 + 0 + 682 + 559 + + + + QtWeb ROOT example + + + + + + + 7 + 1 + 0 + 0 + + + + + 0 + 30 + + + + NoFrame + + + Plain + + + + + + + + 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..9655e6707d7d3 --- /dev/null +++ b/gui/qt6canvas/src/QPaintWidget.cpp @@ -0,0 +1,203 @@ +// 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 + + +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.; +} + +QPaintWidget::~QPaintWidget() +{ +} + +QPoint QPaintWidget::scaledMousePoint(QMouseEvent *e) +{ + int scaledX = scaledPosition(e->position().x()); + int scaledY = scaledPosition(e->position().y()); + return QPoint(scaledX, scaledY); +} + + +void QPaintWidget::resizeEvent(QResizeEvent *) +{ + if (fCanvas) { + fCanvas->Resize(); + fCanvas->Modified(); + } +} + +void QPaintWidget::paintEvent(QPaintEvent *) +{ + try { + QPainter painter(this); + + fPainter = &painter; + + fCanvas->Paint(); + + fPainter = nullptr; + + } catch(...) { + fPainter = nullptr; + } +} + +void QPaintWidget::mousePressEvent( QMouseEvent *e ) +{ + TObjLink* pickobj = nullptr; + QPoint scaled = scaledMousePoint(e); + QPoint menu_pnt = e->globalPosition().toPoint(); + + TPad *pad = fCanvas->Pick(scaled.x(), scaled.y(), pickobj); + TObject *selected = fCanvas->GetSelected(); + + switch(e->button()) { + case Qt::LeftButton : + fCanvas->HandleInput(kButton1Down, scaled.x(), scaled.y()); + // emit PadClicked(pad, scaled.x(), scaled.y()); + break; + case Qt::RightButton : { + 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(); +} + + +void QPaintWidget::mouseMoveEvent(QMouseEvent *e) +{ + static ulong lastprocesstime = 0; + static ulong delta = 100; + ulong timestamp = e->timestamp(); + e->accept(); + if(timestamp - delta < lastprocesstime) + return; + lastprocesstime = timestamp; + + if (fCanvas) { + QPoint pnt = scaledMousePoint(e); + + if (e->buttons() & Qt::LeftButton) + fCanvas->HandleInput(kButton1Motion, pnt.x(), pnt.y()); + else + fCanvas->HandleInput(kMouseMotion, pnt.x(), pnt.y()); + } + + if(fShowEventStatus) { + TObject *selected = fCanvas->GetSelected(); + Int_t px = fCanvas->GetEventX(); + Int_t py = fCanvas->GetEventY(); + QString buffer = ""; + if (selected) { + buffer = selected->GetName(); + buffer += " "; + buffer += selected->GetObjectInfo(px, py); + } else { + buffer = "No selected object x = "; + buffer += QString::number(px); + buffer += " y = "; + buffer += QString::number(py); + } + + // emit CanvasStatusEvent(buffer.toLatin1().constData()); + + if (fStatusBar) fStatusBar->showMessage(buffer.toLatin1().constData()); + } +} + +void QPaintWidget::mouseReleaseEvent( QMouseEvent *e ) +{ + QPoint scaled = scaledMousePoint(e); + + switch(e->button()) { + case Qt::LeftButton : + fCanvas->HandleInput(kButton1Up, scaled.x(), scaled.y()); + break; + case Qt::RightButton : + fCanvas->HandleInput(kButton3Up, scaled.x(), scaled.y()); + break; + case Qt::MiddleButton : + fCanvas->HandleInput(kButton2Up, scaled.x(), scaled.y()); + break; + case Qt::NoButton : + break; + default: + break; + } + e->accept(); +} + +void QPaintWidget::mouseDoubleClickEvent( QMouseEvent *e ) +{ + QPoint scaled = scaledMousePoint(e); + + switch(e->button()) { + case Qt::LeftButton : { + if (!fMaskDoubleClick) + fCanvas->HandleInput(kButton1Double, scaled.x(), scaled.y()); + TObjLink* pickobj = nullptr; + TPad *pad = fCanvas->Pick(scaled.x(), scaled.y(), pickobj); + // emit PadDoubleClicked(pad, scaled.x(), scaled.y()); + break; + } + case Qt::RightButton : + fCanvas->HandleInput(kButton3Double, scaled.x(), scaled.y()); + break; + case Qt::MiddleButton : + fCanvas->HandleInput(kButton2Double, scaled.x(), scaled.y()); + break; + case Qt::NoButton : + break; + default: + break; + } + e->accept(); +} diff --git a/gui/qt6canvas/src/QPaintWidget.h b/gui/qt6canvas/src/QPaintWidget.h new file mode 100644 index 0000000000000..0673b8da6e76b --- /dev/null +++ b/gui/qt6canvas/src/QPaintWidget.h @@ -0,0 +1,66 @@ +// Author: Sergey Linev, GSI 29/06/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef QPaintWidget_H +#define QPaintWidget_H + +#include + +class TCanvas; +class TPad; +class TMethod; +class QPainter; +class QStatusBar; + +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 SetStatusBar(QStatusBar *bar) { fStatusBar = bar; } + +protected: + void resizeEvent(QResizeEvent *event) override; + + void paintEvent(QPaintEvent *event) override; + + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + + double scaledPosition(int p) { return (double) p * fQtScalingfactor; } + + QPoint scaledMousePoint(QMouseEvent *event); + + TCanvas *fCanvas = nullptr; + + QPainter *fPainter = nullptr; + + double fQtScalingfactor = 1.; + + QStatusBar *fStatusBar = nullptr; + + bool fMaskDoubleClick = false; + bool fShowEventStatus = true; + +}; + +#endif diff --git a/gui/qt6canvas/src/QRootContextMenu.cpp b/gui/qt6canvas/src/QRootContextMenu.cpp new file mode 100644 index 0000000000000..985a3b452cd92 --- /dev/null +++ b/gui/qt6canvas/src/QRootContextMenu.cpp @@ -0,0 +1,336 @@ +// Author: Sergey Linev 2/07/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + + +/** \class RRootContextMenu + \ingroup qt6canvas + +This class provides an interface to context sensitive popup menus. +These menus pop up when the user hits the right mouse button, and +are destroyed when the menu pops downs. +The picture below shows a canvas with a pop-up menu. + +*/ + + +#include "QRootContextMenu.h" + +#include "TROOT.h" +#include "TContextMenu.h" +#include "TCanvas.h" +#include "TMethod.h" +#include "TDataMember.h" +#include "TToggle.h" +#include "TClassMenuItem.h" + +#include "QRootMethodDialog.h" +#include "QPaintWidget.h" +#include "TQt6Canvas.h" + +#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; + + + + // addMenuAction(&menu, &map, buffer, curId++); + + int entry = 0, toggle = kToggleStart, togglelist = kToggleListStart; + int userfunction = kUserFunctionStart; + + // Get list of menu items from the selected object's class + TList *menuItemList = object->IsA()->GetMenuList(); + + 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); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Close the context menu if the object is deleted in the +/// RecursiveRemove() operation. + +void QRootContextMenu::RecursiveRemove(TObject *obj) +{ + if (obj == fContextMenu->GetSelectedCanvas()) + fContextMenu->SetCanvas(nullptr); + if (obj == fContextMenu->GetSelectedPad()) + fContextMenu->SetPad(nullptr); + if (obj == fContextMenu->GetSelectedObject()) { + // if the object being deleted is the one selected, + // ungrab the mouse pointer and terminate (close) the menu + fContextMenu->SetObject(nullptr); + } +} + +QAction* QRootContextMenu::addMenuAction(QMenu* menu, QSignalMapper* map, const QString& text, int id, 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; +} + +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) { + TMethod *m = (TMethod *) ud; + fContextMenu->Action(m); + } else if (id >= kToggleStart && id < kToggleListStart) { + TToggle *t = (TToggle *) ud; + fContextMenu->Action(t); + } else if (id >= kToggleListStart && id < kUserFunctionStart) { + TToggle *t = (TToggle *) ud; + if (t->GetState() == 0) + t->SetState(1); + } else { + TClassMenuItem *mi = (TClassMenuItem*)ud; + fContextMenu->Action(mi); + } +} diff --git a/gui/qt6canvas/src/QRootContextMenu.h b/gui/qt6canvas/src/QRootContextMenu.h new file mode 100644 index 0000000000000..303102f5e11c5 --- /dev/null +++ b/gui/qt6canvas/src/QRootContextMenu.h @@ -0,0 +1,50 @@ +// Author: Sergey Linev 2/07/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT_QRootContextMenu +#define ROOT_QRootContextMenu + +#include +#include +#include "TContextMenuImp.h" +#include "TObject.h" +#include "TList.h" +#include + +class QSignalMapper; +class TList; +class QAction; +class QMenu; + +class QRootContextMenu : public QObject, public TObject, public TContextMenuImp { + Q_OBJECT + +public slots: + void executeMenu(int id); + +protected: + TObject *fMenuObj = nullptr; // object use to fill menu + + TList fTrash; + std::map fCustomArg; + QAction* addMenuAction(QMenu *menu, QSignalMapper *map, const QString &text, int id, void *arg = nullptr); + +public: + QRootContextMenu(TContextMenu *c = nullptr, const char *name = "ROOT Context Menu"); + ~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..346102d4e0497 --- /dev/null +++ b/gui/qt6canvas/src/QRootMethodDialog.cpp @@ -0,0 +1,181 @@ +// 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 + +QRootMethodDialog::QRootMethodDialog() : QDialog() +{ + QGridLayout *gridLayout = new QGridLayout(this); + gridLayout->setSpacing(1); + gridLayout->setContentsMargins(1,1,1,1); + + QHBoxLayout *buttLayout = new QHBoxLayout(); + + QPushButton *bOk = new QPushButton(this); + bOk->setText("Apply"); + QObject::connect(bOk, &QPushButton::clicked, this, &QRootMethodDialog::accept); + buttLayout->addWidget(bOk); + + QPushButton *bCancel = new QPushButton(this); + bCancel->setText("Cancel"); + QObject::connect(bCancel, &QPushButton::clicked, this, &QRootMethodDialog::reject); + buttLayout->addWidget(bCancel); + + argLayout = new QVBoxLayout(); + + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Expanding)); + + gridLayout->addLayout(argLayout, 0, 0); + gridLayout->addLayout(buttLayout, 1, 0, Qt::AlignBottom); +} + +void QRootMethodDialog::addArg(const char *argname, const char *value, const char *) +{ + QLabel* lbl = new QLabel(argname); + argLayout->addWidget(lbl); + + QLineEdit* le = new QLineEdit(); + le->setGeometry(10,10, 130, 30); + le->setFocus(); + le->setText(value); + argLayout->addWidget(le); + + fArgs.push_back(le); +} + +QString QRootMethodDialog::getArg(int n) +{ + if ((n<0) || (n>=fArgs.size())) return QString(""); + return fArgs[n]->text(); +} + + +void QRootMethodDialog::methodDialog(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")) { + Long_t ldefval = 0; + m->GetterMethod()->Execute(object, "", ldefval); + val = TString::Format("%ld", ldefval); + } + + // Find out whether we have options ... + + TList *opt; + if ((opt = m->GetOptions()) != nullptr) { + // should stop dialog + // workaround JAM: do not stop dialog, use textfield (for time display toggle) + addArg(argTitle.Data(), val.Data(), type.Data()); + //return; + } else { + // we haven't got options - textfield ... + addArg(argTitle.Data(), val.Data(), type.Data()); + } + } else { // if m not found ... + 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..c14d4ff2c931c --- /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 QRootMethodDialog_h +#define QRootMethodDialog_h + +#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/TQt6Canvas.cxx b/gui/qt6canvas/src/TQt6Canvas.cxx new file mode 100644 index 0000000000000..29f117058e540 --- /dev/null +++ b/gui/qt6canvas/src/TQt6Canvas.cxx @@ -0,0 +1,429 @@ +// 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 "TSystem.h" +#include "TStyle.h" +#include "TError.h" +#include "TCanvas.h" +#include "TThread.h" +#include "TROOT.h" +#include "TApplication.h" +#include "TVirtualX.h" +#include "TVirtualPS.h" +#include "TClass.h" +#include "TExec.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "QCanvasWidget.h" + +class TQt6CanvasTimer : public TTimer { +public: + TQt6CanvasTimer(Long_t milliSec, Bool_t mode) : + TTimer(milliSec, mode) {} + + + /// used to send control messages to clients + void Timeout() override + { + QApplication::sendPostedEvents(); + QApplication::processEvents(); + } +}; + + +/** \class TQt6Canvas + \ingroup qt6canvas + \brief Basic TCanvasImp ABI implementation for Qt6 +*/ + +using namespace std::string_literals; + +//////////////////////////////////////////////////////////////////////////////// +/// Constructor + +TQt6Canvas::TQt6Canvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height) + : TCanvasImp(c, name, x, y, width, height) +{ + // Workaround for multi-threaded environment + // Ensure main thread id picked when canvas implementation is created - + // otherwise it may be assigned in other thread and screw-up gPad access. + // Workaround may not work if main thread id was wrongly initialized before + // This resolves issue https://github.com/root-project/root/issues/15498 + TThread::SelfId(); + + // fTimer = new TQt6CanvasTimer(*this); + + // fTimer->TurnOn(); + + // fAsyncMode = kTRUE; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Destructor + +TQt6Canvas::~TQt6Canvas() +{ + // delete fTimer; +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Initialize window for the qt6 canvas + +Int_t TQt6Canvas::InitWindow() +{ + return 111222333; // should not be used at all +} + +//////////////////////////////////////////////////////////////////////////////// +/// Creates pad painter + +TVirtualPadPainter *TQt6Canvas::CreatePadPainter() +{ + printf("Create pad painter\n"); + 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); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Process TExec objects in the pad + +void TQt6Canvas::ProcessExecs(TPad *pad, TExec *extra) +{ + auto execs = pad ? pad->GetListOfExecs() : nullptr; + + if ((!execs || !execs->GetSize()) && !extra) + return; + + auto saveps = gVirtualPS; + gVirtualPS = nullptr; + + auto savex = gVirtualX; + TVirtualX x; + gVirtualX = &x; + + TIter next(execs); + while (auto obj = next()) { + auto exec = dynamic_cast(obj); + if (exec) + exec->Exec(); + } + + if (extra) + extra->Exec(); + + gVirtualPS = saveps; + gVirtualX = savex; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Return 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; + } + + // x = Canvas()->fWindowTopX; + // y = Canvas()->fWindowTopY; + // w = Canvas()->fWindowWidth; + // h = Canvas()->fWindowHeight; + + 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->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 + +TCanvasImp *TQt6Canvas::NewCanvas(TCanvas *c, const char *name, Int_t x, Int_t y, UInt_t width, UInt_t height) +{ + static QApplication *qapp = nullptr; + static int qargc = 1; + static char *qargv[2]; + + if (!qapp && !QApplication::instance()) { + + if (!gApplication) { + ::Error("TQt6Canvas::NewCanvas", "Not found gApplication to create QApplication"); + return nullptr; + } + + qargv[0] = gApplication->Argv(0); + qargv[1] = nullptr; + + qapp = new QApplication(qargc, qargv); + } + + static TQt6CanvasTimer *timer = nullptr; + + if (!timer) { + timer = new TQt6CanvasTimer(10, kTRUE); + timer->TurnOn(); + } + + auto widget = new QCanvasWidget(); + widget->setWindowTitle(QString(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(); + + imp->fPaintWidget->SetCanvas(c); + + // set all internal dimensions + c->Resize(); + + // c->fWindowTopX = x; + // c->fWindowTopY = y; + // c->fWindowWidth = width; + // c->fWindowHeight = height; + // if (!gROOT->IsBatch() && (height > 25)) + // height -= 25; + // c->fCw = width; + // c->fCh = height; + + return imp; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +/// Create TCanvas and assign TQt6Canvas implementation to it +/// Canvas is not displayed automatically, therefore canv->Show() method must be called +/// Or canvas can be embed in other widgets. + +TCanvas *TQt6Canvas::CreateQt6Canvas(const char *name, const char *title, UInt_t width, UInt_t height) +{ + auto canvas = new TCanvas(kFALSE); + canvas->SetName(name); + canvas->SetTitle(title); + canvas->ResetBit(TCanvas::kShowEditor); + canvas->ResetBit(TCanvas::kShowToolBar); + canvas->SetBit(TCanvas::kMenuBar, kTRUE); + canvas->SetCanvas(canvas); + canvas->SetBatch(kTRUE); // mark canvas as batch + canvas->SetEditable(kTRUE); // ensure fPrimitives are created + + // copy gStyle attributes + canvas->SetFillColor(gStyle->GetCanvasColor()); + canvas->SetFillStyle(1001); + canvas->SetGrid(gStyle->GetPadGridX(),gStyle->GetPadGridY()); + canvas->SetTicks(gStyle->GetPadTickX(),gStyle->GetPadTickY()); + canvas->SetLogx(gStyle->GetOptLogx()); + canvas->SetLogy(gStyle->GetOptLogy()); + canvas->SetLogz(gStyle->GetOptLogz()); + canvas->SetBottomMargin(gStyle->GetPadBottomMargin()); + canvas->SetTopMargin(gStyle->GetPadTopMargin()); + canvas->SetLeftMargin(gStyle->GetPadLeftMargin()); + canvas->SetRightMargin(gStyle->GetPadRightMargin()); + canvas->SetBorderSize(gStyle->GetCanvasBorderSize()); + canvas->SetBorderMode(gStyle->GetCanvasBorderMode()); + + auto imp = static_cast (NewCanvas(canvas, name, 0, 0, width, height)); + + canvas->SetCanvasImp(imp); + + canvas->cd(); + + { + R__LOCKGUARD(gROOTMutex); + auto l1 = gROOT->GetListOfCleanups(); + if (!l1->FindObject(canvas)) + l1->Add(canvas); + auto l2 = gROOT->GetListOfCanvases(); + if (!l2->FindObject(canvas)) + l2->Add(canvas); + } + + return canvas; +} diff --git a/gui/qt6canvas/src/TQt6ContextMenu.cxx b/gui/qt6canvas/src/TQt6ContextMenu.cxx new file mode 100644 index 0000000000000..a432f6a015206 --- /dev/null +++ b/gui/qt6canvas/src/TQt6ContextMenu.cxx @@ -0,0 +1,93 @@ +// Author: Sergey Linev 2/07/2026 + +/************************************************************************* + * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + + +/** \class QRootContextMenu + \ingroup guiwidgets + +This class provides an interface to context sensitive popup menus. +These menus pop up when the user hits the right mouse button, and +are destroyed when the menu pops downs. +The picture below shows a canvas with a pop-up menu. + +*/ + + +#include "QRootContextMenu.h" + +#include "TROOT.h" +#include "TContextMenu.h" +#include "TVirtualPad.h" + + +//////////////////////////////////////////////////////////////////////////////// +/// Create context menu. + +QRootContextMenu::QRootContextMenu(TContextMenu *c, const char *) + : TObject(), TContextMenuImp(c) +{ + gROOT->GetListOfCleanups()->Add(this); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Delete a context menu. + +QRootContextMenu::~QRootContextMenu() +{ + gROOT->GetListOfCleanups()->Remove(this); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Display context popup menu for currently selected object. + +void QRootContextMenu::DisplayPopup(Int_t x, Int_t y) +{ + // add menu items to popup menu + // CreateMenu(fContextMenu->GetSelectedObject()); + + printf("Start menu for %p\n", fContextMenu->GetSelectedObject()); +} + + +//////////////////////////////////////////////////////////////////////////////// +/// Create dialog object with OK and Cancel buttons. This dialog +/// prompts for the arguments of "method". + +void QRootContextMenu::Dialog(TObject *object, TMethod *method) +{ + Dialog(object, (TFunction *)method); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create dialog object with OK and Cancel buttons. This dialog +/// prompts for the arguments of "function". +/// function may be a global function or a method + +void QRootContextMenu::Dialog(TObject *object, TFunction *function) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Close the context menu if the object is deleted in the +/// RecursiveRemove() operation. + +void QRootContextMenu::RecursiveRemove(TObject *obj) +{ + if (obj == fContextMenu->GetSelectedCanvas()) + fContextMenu->SetCanvas(nullptr); + if (obj == fContextMenu->GetSelectedPad()) + fContextMenu->SetPad(nullptr); + if (obj == fContextMenu->GetSelectedObject()) { + // if the object being deleted is the one selected, + // ungrab the mouse pointer and terminate (close) the menu + fContextMenu->SetObject(nullptr); + } +} + diff --git a/gui/qt6canvas/src/TQt6GuiFactory.cxx b/gui/qt6canvas/src/TQt6GuiFactory.cxx new file mode 100644 index 0000000000000..c7404c41fc76c --- /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. It overrides +the member functions of the ABS TGuiFactory. + +*/ + + +#include "TQt6GuiFactory.h" +#include "TQt6Canvas.h" +#include "QRootContextMenu.h" + +#include + + +//////////////////////////////////////////////////////////////////////////////// +/// TQt6GuiFactory ctor. + +TQt6GuiFactory::TQt6GuiFactory(const char *name, const char *title) + : TGuiFactory(name, title) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TApplicationImp + +TApplicationImp *TQt6GuiFactory::CreateApplicationImp(const char *classname, + Int_t *argc, char **argv) +{ + return TGuiFactory::CreateApplicationImp(classname, argc, argv); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TCanvasImp + +TCanvasImp *TQt6GuiFactory::CreateCanvasImp(TCanvas *c, const char *title, + UInt_t width, UInt_t height) +{ + return TQt6Canvas::NewCanvas(c, title, -1, -1, width, height); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TCanvasImp + +TCanvasImp *TQt6GuiFactory::CreateCanvasImp(TCanvas *c, const char *title, + Int_t x, Int_t y, UInt_t width, UInt_t height) +{ + return TQt6Canvas::NewCanvas(c, title, x, y, width, height); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Create a ROOT native GUI version of TContextMenuImp + +TContextMenuImp *TQt6GuiFactory::CreateContextMenuImp(TContextMenu *c, + const char *name, const char *) +{ + 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..e50e2a5d64b96 --- /dev/null +++ b/gui/qt6canvas/src/TQt6PadPainter.cxx @@ -0,0 +1,676 @@ +// 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 "TROOT.h" +#include "TColor.h" +#include "RStipples.h" + +#include + +#include "QPaintWidget.h" + +#include +#include +#include +#include + + +// to scale fonts to the same size as in the TTF +const Float_t kScale = 0.75 * 0.93376068; + +/** \class TQt6PadPainter + \ingroup qt6canvas + \brief Implement TVirtualPadPainter for Qt6 graphics +*/ + +////////////////////////////////////////////////////////////////////////// +/// 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); +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polymarker. + +void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Double_t *x, const Double_t *y) +{ + auto painter = fPaintWidget->getPainter(); + + if (!painter || nPoints < 1) + return; + + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + painter->setBrush(QBrush(GetQColor(GetAttMarker().GetMarkerColor()))); + + Int_t radius = GetAttMarker().GetMarkerSize(); + if (radius < 2) + radius = 2; + + // TODO: implement all markers types - once method exists + for (Int_t n = 0; n < nPoints; ++n) { + Int_t px = gPad->XtoAbsPixel(x[n]); + Int_t py = gPad->YtoAbsPixel(y[n]); + painter->drawEllipse({px, py}, radius, radius); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// Paint polymarker. + +void TQt6PadPainter::DrawPolyMarker(Int_t nPoints, const Float_t *x, const Float_t *y) +{ + auto painter = fPaintWidget->getPainter(); + + if (!painter || nPoints < 1) + return; + + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::NoPen); + painter->setBrush(QBrush(GetQColor(GetAttMarker().GetMarkerColor()))); + + Int_t radius = GetAttMarker().GetMarkerSize(); + if (radius < 2) + radius = 2; + + // TODO: implement all markers types - once method exists + for (Int_t n = 0; n < nPoints; ++n) { + Int_t px = gPad->XtoAbsPixel(x[n]); + Int_t py = gPad->YtoAbsPixel(y[n]); + painter->drawEllipse({px, py}, radius, radius); + } +} + +//////////////////////////////////////////////////////////////////////////////// +/// 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_t it = std::stoi(tokens->At(j)->GetName()); + pattern.push_back(0.25 * it / att.GetLineWidth()); + } + if (pattern.size() > 1) { + customPen.setStyle(Qt::CustomDashLine); + customPen.setDashPattern(pattern); + } + } + + return customPen; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Return 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)); +}