From bf7952a815885ce14bea55f7987fa9c5dc7c5223 Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 6 Nov 2026 14:09:25 +0100 Subject: [PATCH 1/2] [troot] support "Editor" mode kEditor was exists before, but not supported in TROOT::SetEditorMode --- core/base/src/TROOT.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/core/base/src/TROOT.cxx b/core/base/src/TROOT.cxx index 4603c0bf6979e..92a6386728614 100644 --- a/core/base/src/TROOT.cxx +++ b/core/base/src/TROOT.cxx @@ -2890,6 +2890,7 @@ void TROOT::SetEditorMode(const char *mode) if (!strcmp(mode,"CurlyLine")){fEditorMode = kCurlyLine; return;} if (!strcmp(mode,"CurlyArc")) {fEditorMode = kCurlyArc; return;} if (!strcmp(mode,"Text")) {fEditorMode = kText; return;} + if (!strcmp(mode,"Editor")) {fEditorMode = kEditor; return;} if (!strcmp(mode,"Marker")) {fEditorMode = kMarker; return;} if (!strcmp(mode,"CutG")) {fEditorMode = kCutG; return;} } From 85880dbce86ae149fa464c446ff7e11907479efc Mon Sep 17 00:00:00 2001 From: Sergey Linev Date: Fri, 6 Nov 2026 14:09:43 +0100 Subject: [PATCH 2/2] [pad] fix text insert via tool button When TLatex tool button was clicked before, any following mouse click on the pad insert new TLatex object keeping previous one with empty text. As a result - after many clicks dozens of dummy TLatex object were remain in the pad (just try gPad->ls()) Plus static fgText pointer used in TCreatePrimitives can store invalid pointer and may lead to ROOT crash. Therefore rewrite TCreatePrimitives::Text() functionality using `TPad::Interactive()` features. Like with objects dragging add temporary `TTextEditInteractive` instance which holds pointer on TLatex. Any keyboard press will be handled only when interactive object exists. Also avoid create multiple `TLatex` by multiple mouse clicking on pad. If latex was created, then second click will just finish editing of this latex object. --- graf2d/gpad/inc/TCreatePrimitives.h | 1 - graf2d/gpad/src/TCreatePrimitives.cxx | 143 +++++++++++++++++--------- graf2d/gpad/src/TPad.cxx | 3 +- 3 files changed, 98 insertions(+), 49 deletions(-) diff --git a/graf2d/gpad/inc/TCreatePrimitives.h b/graf2d/gpad/inc/TCreatePrimitives.h index 9183c26026ca8..26a41a736013f 100644 --- a/graf2d/gpad/inc/TCreatePrimitives.h +++ b/graf2d/gpad/inc/TCreatePrimitives.h @@ -26,7 +26,6 @@ class TCreatePrimitives { private: static TLine *fgLine; - static TLatex *fgText; static TCurlyLine *fgCLine; static TArrow *fgArrow; static TCurlyArc *fgCArc; diff --git a/graf2d/gpad/src/TCreatePrimitives.cxx b/graf2d/gpad/src/TCreatePrimitives.cxx index 0443a262ced41..b15150303897a 100644 --- a/graf2d/gpad/src/TCreatePrimitives.cxx +++ b/graf2d/gpad/src/TCreatePrimitives.cxx @@ -30,7 +30,6 @@ to create new primitives in gPad from the TPad toolbar. #include "KeySymbols.h" #include "TCutG.h" -TLatex *TCreatePrimitives::fgText = nullptr; TCurlyLine *TCreatePrimitives::fgCLine = nullptr; TArrow *TCreatePrimitives::fgArrow = nullptr; TLine *TCreatePrimitives::fgLine = nullptr; @@ -651,6 +650,50 @@ void TCreatePrimitives::PolyLine(Int_t event, Int_t px, Int_t py, Int_t mode) } } + +///////////////////////////////////////////// +/// Internal class implementing tlatex editing +class TTextEditInteractive : public TVirtualPad::TInteractive { + public: + + TTextEditInteractive(TLatex *ltx) + { + fLatex = ltx; + } + + void FinishEditing() + { + TString s = fLatex->GetTitle(); + Int_t l = s.Length(); + if (l > 1) { + // keep dummy symbol to let select it afterwards + s.Remove(l-1); + fLatex->SetTitle(s.Data()); + } + } + + bool PressBackspace() + { + TString s(fLatex->GetTitle()); + Int_t l = s.Length(); + if (l < 2) + return false; + s.Replace(l-2, 2, "<"); + fLatex->SetTitle(s.Data()); + return true; + } + + void AppendChar(char c) + { + TString s = fLatex->GetTitle(); + Int_t l = s.Length(); + s.Insert(l-1, c); + fLatex->SetTitle(s.Data()); + } + + TLatex *fLatex = nullptr; +}; + //////////////////////////////////////////////////////////////////////////////// /// Create a new TLatex at the cursor position in gPad. /// @@ -658,70 +701,76 @@ void TCreatePrimitives::PolyLine(Int_t event, Int_t px, Int_t py, Int_t mode) void TCreatePrimitives::Text(Int_t event, Int_t px, Int_t py, Int_t mode) { - static Double_t x, y; + + auto &parent = *gPad; + + auto inter = (mode == kEditor) ? parent.GetInteractive(&parent) : nullptr; + if (inter && !parent.FindObject(inter->fLatex)) { + parent.FreeInteractive(&parent); + inter = nullptr; + } switch (event) { - case kKeyPress: + case kKeyPress: { + // only modify in editor mode + if (!inter) + break; + if ((py == kKey_Return) || (py == kKey_Enter)) { - TString s(fgText->GetTitle()); - Int_t l = s.Length(); - s.Remove(l-1); - fgText->SetText(x,y,s.Data()); + inter->FinishEditing(); gSystem->ProcessEvents(); - gPad->Modified(kTRUE); + parent.Modified(kTRUE); gROOT->SetEditorMode(); - gPad->Update(); - gPad->GetCanvas()->Selected(gPad, fgText, kButton1Down); - fgText = nullptr; + parent.Update(); + parent.GetCanvas()->Selected(gPad, inter->fLatex, kButton1Down); + parent.FreeInteractive(&parent); } else if (py == kKey_Backspace) { - TString s(fgText->GetTitle()); - Int_t l = s.Length(); - if (l>1) { - s.Replace(l-2, 2, "<"); - fgText->SetText(x,y,s.Data()); - gPad->Modified(kTRUE); - gPad->Update(); + if (inter->PressBackspace()) { + parent.Modified(kTRUE); + parent.Update(); } } else if (isprint(py)) { - TString s(fgText->GetTitle()); - Int_t l = s.Length(); - s.Insert(l-1,(char)py); - fgText->SetText(x,y,s.Data()); - gPad->Modified(kTRUE); - gPad->Update(); + inter->AppendChar((char)py); + parent.Modified(kTRUE); + parent.Update(); } break; + } - case kButton1Down: - if (fgText) { - TString s(fgText->GetTitle()); - Int_t l = s.Length(); - s.Remove(l-1); - fgText->SetText(x,y,s.Data()); + case kButton1Down: { + if (inter) { + // second click during editing will finalize editor + inter->FinishEditing(); + gSystem->ProcessEvents(); + parent.Modified(kTRUE); + gROOT->SetEditorMode(); + parent.Update(); + parent.GetCanvas()->Selected(gPad, inter->fLatex, kButton1Down); + parent.FreeInteractive(&parent); + break; } - x = gPad->AbsPixeltoX(px); - y = gPad->AbsPixeltoY(py); - if (gPad->GetLogx()) x = TMath::Power(10,x); - if (gPad->GetLogy()) y = TMath::Power(10,y); + Double_t x = parent.PadtoX(parent.AbsPixeltoX(px)); + Double_t y = parent.PadtoY(parent.AbsPixeltoY(py)); if (mode == kMarker) { - TMarker *marker; - marker = new TMarker(x,y,28); - gPad->GetCanvas()->Selected(gPad, marker, kButton1Down); - marker->Draw(); + auto marker = new TMarker(x, y, 28); + parent.GetCanvas()->Selected(&parent, marker, kButton1Down); + parent.Add(marker); gROOT->SetEditorMode(); - break; + } else if (mode == kText) { + ((TPad *)&parent)->StartEditing(); + gSystem->ProcessEvents(); + auto ltx = new TLatex(x,y,"<"); + parent.GetCanvas()->Selected(gPad, ltx, kButton1Down); + parent.Add(ltx); + parent.Modified(kTRUE); + parent.Update(); + gROOT->SetEditorMode("Editor"); // switch to text editing + parent.MakeInteractive(&parent, ltx); } - - ((TPad *)gPad)->StartEditing(); - gSystem->ProcessEvents(); - - fgText = new TLatex(x,y,"<"); - fgText->Draw(); - gPad->Modified(kTRUE); - gPad->Update(); break; } + } } diff --git a/graf2d/gpad/src/TPad.cxx b/graf2d/gpad/src/TPad.cxx index 1db54d7242350..962196d40108c 100644 --- a/graf2d/gpad/src/TPad.cxx +++ b/graf2d/gpad/src/TPad.cxx @@ -2015,7 +2015,7 @@ void TPad::ExecuteEvent(Int_t event, Int_t px, Int_t py) if (fYlowNDC < 0 && event != kButton1Down) return; Int_t newcode = gROOT->GetEditorMode(); - if (newcode) + if (newcode && (newcode != kEditor)) parent.FreeInteractive(this); // delete any interactive object switch (newcode) { case kPad: @@ -2023,6 +2023,7 @@ void TPad::ExecuteEvent(Int_t event, Int_t px, Int_t py) break; case kMarker: case kText: + case kEditor: TCreatePrimitives::Text(event,px,py,newcode); break; case kLine: