Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
Gtk4DrawArea.cxx
Go to the documentation of this file.
1// Author: Sergey Linev, GSI 12/08/2026
2
3/*************************************************************************
4 * Copyright (C) 1995-2026, Rene Brun and Fons Rademakers. *
5 * All rights reserved. *
6 * *
7 * For the licensing terms see $ROOTSYS/LICENSE. *
8 * For the list of contributors see $ROOTSYS/README/CREDITS. *
9 *************************************************************************/
10
11#include "Gtk4DrawArea.h"
12
13#include <cmath>
14
15#include "TGtk4PadPainter.h"
16#include "TCanvas.h"
17
18#include <gtkmm.h>
19
20/** \class Gtk4DrawArea
21 \ingroup gtk4canvas
22*/
23
24
26{
27 // Bind the member function as the draw function
28 set_draw_func(sigc::mem_fun(*this, &Gtk4DrawArea::on_draw));
29
30 set_expand(true);
31
32 m_click_gesture = Gtk::GestureClick::create();
33 m_click_gesture->set_button(0);
34 m_click_gesture->signal_pressed().connect(
35 sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_click)
36 );
37 m_click_gesture->signal_released().connect(
38 sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_released)
39 );
40 add_controller(m_click_gesture);
41
42 // 2. Create and attach the motion event controller
43 m_motion_controller = Gtk::EventControllerMotion::create();
44 m_motion_controller->signal_motion().connect(
45 sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_move)
46 );
47
48 m_motion_controller->signal_enter().connect(
49 sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_enter)
50 );
51
52 // 3. Connect the leave signal
53 m_motion_controller->signal_leave().connect(
54 sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_leave)
55 );
56 add_controller(m_motion_controller);
57
58 m_scroll_controller = Gtk::EventControllerScroll::create();
59 m_scroll_controller->set_flags(Gtk::EventControllerScroll::Flags::VERTICAL);
60
61 m_scroll_controller->signal_scroll().connect(
62 sigc::mem_fun(*this, &Gtk4DrawArea::on_mouse_scroll),
63 false // Run during the capture phase? False means bubble phase (standard)
64 );
65
66 add_controller(m_scroll_controller);
67
68
69 signal_query_tooltip().connect(sigc::mem_fun(*this, &Gtk4DrawArea::on_my_query_tooltip), true);
70}
71
72void Gtk4DrawArea::on_draw(const Cairo::RefPtr<Cairo::Context>& cr, int width, int height)
73{
74 if (!fCanvas) {
75 // only for debug purposes to see not empty widget
76 cr->set_source_rgb(0.15, 0.15, 0.15);
77 cr->paint();
78
79 // 2. Draw a blue circle in the center that scales with the window
80 double radius = std::min(width, height) / 4.0;
81 cr->set_source_rgb(0.2, 0.5, 0.8);
82 cr->arc(width / 2.0, height / 2.0, radius, 0.0, 2.0 * 3.1415927);
83 cr->fill();
84 return;
85 }
86
87 if ((width != (int) fCanvas->GetPadWidth()) || (height != (int) fCanvas->GetPadHeight()))
88 fCanvas->Resize();
89
90 if (!fDrawingCache || fDrawingCache->get_width() != width || fDrawingCache->get_height() != height)
91 fDrawingCache = Cairo::ImageSurface::create(Cairo::Surface::Format::ARGB32, width, height);
92
93 // TODO: extend caching for each pad, making it very similar to double-buffer model of gVirtualX
94
95 auto cache_context = Cairo::Context::create(fDrawingCache);
96
97 cache_context->set_source_rgb(1., 1., 1.);
98 cache_context->paint();
99
100 // main place where painting happens - only here fContext pointer is set
101 // TODO: really repaint pad only when something changed
102
103 fContext = cache_context.get();
104
105 fCanvas->Paint();
106
107 fContext = nullptr;
108
109 // and now copy image to real context
110 cr->set_source(fDrawingCache, 0, 0);
111 cr->paint();
112}
113
114void Gtk4DrawArea::on_mouse_click(int n_press, double x, double y)
115{
116 if (!fCanvas)
117 return;
118
119 auto evnt = kNoEvent;
120
121 switch(m_click_gesture->get_current_button()) {
122 case GDK_BUTTON_PRIMARY: // 1, usually left
123 evnt = n_press > 1 ? kButton1Double : kButton1Down;
124 break;
125 case GDK_BUTTON_MIDDLE: // 2
126 evnt = n_press > 1 ? kButton2Double : kButton2Down;
127 break;
128 case GDK_BUTTON_SECONDARY: // 3, usually right
129 evnt = n_press > 1 ? kButton3Double : kButton3Down;
130 break;
131 default:
132 break;
133 }
134
135 if (evnt != kNoEvent)
136 fCanvas->HandleInput(evnt, (int) x, (int) y);
137}
138
139void Gtk4DrawArea::on_mouse_released(int n_press, double x, double y)
140{
141 (void) n_press;
142
143 if (!fCanvas)
144 return;
145
146 auto evnt = kNoEvent;
147
148 switch(m_click_gesture->get_current_button()) {
149 case GDK_BUTTON_PRIMARY: // 1, usually left
150 evnt = kButton1Up;
151 break;
152 case GDK_BUTTON_MIDDLE: // 2
153 evnt = kButton2Up;
154 break;
155 case GDK_BUTTON_SECONDARY: // 3, usually right
156 evnt = kButton3Up;
157 break;
158 default:
159 break;
160 }
161
162 if (evnt != kNoEvent)
163 fCanvas->HandleInput(evnt, (int) x, (int) y);
164}
165
166
167void Gtk4DrawArea::on_mouse_enter(double x, double y)
168{
169 fLastMouseX = x;
170 fLastMouseY = y;
171
172 if (fCanvas)
173 fCanvas->HandleInput(kMouseEnter, (int) x, (int) y);
174}
175
181
182
183// Callback handles mouse movements
184void Gtk4DrawArea::on_mouse_move(double x, double y)
185{
186 if (!fCanvas)
187 return;
188
189 fLastMouseX = x;
190 fLastMouseY = y;
191
192 Gdk::ModifierType modifiers = m_motion_controller->get_current_event_state();
193
194 auto evnt = kMouseMotion;
195 if ((modifiers & Gdk::ModifierType::BUTTON1_MASK) == Gdk::ModifierType::BUTTON1_MASK) {
196 evnt = kButton1Motion;
197 } else if ((modifiers & Gdk::ModifierType::BUTTON3_MASK) == Gdk::ModifierType::BUTTON3_MASK) {
198 evnt = kButton3Motion;
199 }
200
201 fCanvas->HandleInput(evnt, (int) x, (int) y);
202
203 if (fShowEventStatus) {
204 TObject *selected = fCanvas->GetSelected();
205 Int_t px = fCanvas->GetEventX();
206 Int_t py = fCanvas->GetEventY();
207 TString buffer = "";
208 if (selected) {
209 buffer = selected->GetName();
210 buffer.Append(" ");
211 buffer.Append(selected->GetObjectInfo(px, py));
212 } else {
213 buffer.Form("No selected object x = %d y = %d", px, py);
214 }
215
216 m_status_changed.emit(buffer.Data());
217 }
218}
219
220sigc::signal<void(const char *)> Gtk4DrawArea::status_changed()
221{
222 return m_status_changed;
223}
224
225
226bool Gtk4DrawArea::on_mouse_scroll(double dx, double dy)
227{
228 (void) dx;
229 if (fCanvas)
231
232 return true;
233}
234
235///////////////////////////////////////////////////////////////////////////////////////////
236/// Shows context menu with provided items and title at specified position
237
238void Gtk4DrawArea::ShowContextMenu(Glib::RefPtr<Gio::Menu> menu_model, const char *title, int x, int y)
239{
240 if (title && *title) {
241 auto item = Gio::MenuItem::create("", "");
242 item->set_attribute_value("custom", Glib::Variant<Glib::ustring>::create("header"));
243 menu_model->prepend_item(item);
244 }
245
246 auto menu = Gtk::make_managed<Gtk::PopoverMenu>(menu_model);
247 menu->set_parent(*this);
248 menu->set_has_arrow(false); // Removes the little popover triangle point
249
250 menu->add_css_class("compact-menu");
251
252 static bool has_compact_style = false;
253 if (!has_compact_style) {
254 auto css_provider = Gtk::CssProvider::create();
255 std::string css_data =
256 "popover.compact-menu contents modelbutton, "
257 "popover.compact-menu contents button.model { "
258 " padding-top: 1px; "
259 " padding-bottom: 0px; "
260 " margin-top: 0px; "
261 " margin-bottom: 0px; "
262 " min-height: 10px; "
263 "}";
264
265 css_provider->load_from_data(css_data);
266
267 // 4. Inject into the display context
268 Gtk::StyleContext::add_provider_for_display(
269 Gdk::Display::get_default(),
270 css_provider,
271 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
272 );
273
274 has_compact_style = true;
275 }
276
277 if (title && *title) {
278 auto label = Gtk::make_managed<Gtk::Label>(title);
279 label->add_css_class("heading");
280 menu->add_child(*label, "header");
281 }
282
283 Gdk::Rectangle rect(x, y, 1, 1);
284
285 // 2. Snap the context menu anchor to this exact tiny point rectangle
286 menu->set_pointing_to(rect);
287
288 // 3. Open the popup menu smoothly on screen
289 menu->popup();
290}
291
292bool Gtk4DrawArea::on_my_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr<Gtk::Tooltip>& tooltip)
293{
294 if (keyboard_tooltip)
295 return false; // Skip if triggered by keyboard focus
296
297 auto canv = GetCanvas();
298 if (!canv)
299 return false;
300
301 TObject *selected = canv->GetSelected();
302 Int_t px = canv->GetEventX();
303 Int_t py = canv->GetEventY();
304 TString customText;
305 if (selected)
306 customText.Form("%s::%s\n%s\n%d, %d\n%s",selected->ClassName() ,selected->GetName(),
307 selected->GetTitle(), px, py, selected->GetObjectInfo(px, py));
308 else
309 customText.Form("No selected object\n%d, %d", x, y);
310
311 tooltip->set_text(customText.Data());
312
313 return true; // Return true to instruct GTK to show the tooltip
314}
@ kMouseMotion
Definition Buttons.h:23
@ kWheelUp
Definition Buttons.h:18
@ kButton3Up
Definition Buttons.h:19
@ kButton3Motion
Definition Buttons.h:20
@ kButton3Down
Definition Buttons.h:17
@ kButton2Down
Definition Buttons.h:17
@ kButton2Double
Definition Buttons.h:24
@ kNoEvent
Definition Buttons.h:16
@ kButton1Double
Definition Buttons.h:24
@ kButton3Double
Definition Buttons.h:24
@ kButton1Motion
Definition Buttons.h:20
@ kButton1Up
Definition Buttons.h:19
@ kWheelDown
Definition Buttons.h:18
@ kButton2Up
Definition Buttons.h:19
@ kMouseLeave
Definition Buttons.h:23
@ kButton1Down
Definition Buttons.h:17
@ kMouseEnter
Definition Buttons.h:23
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t rect
Option_t Option_t width
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t height
bool on_mouse_scroll(double dx, double dy)
Cairo::RefPtr< Cairo::ImageSurface > fDrawingCache
void on_mouse_move(double x, double y)
Glib::RefPtr< Gtk::GestureClick > m_click_gesture
sigc::signal< void(const char *) status_changed)()
void on_mouse_enter(double x, double y)
Glib::RefPtr< Gtk::EventControllerMotion > m_motion_controller
TCanvas * GetCanvas() const
Glib::RefPtr< Gtk::EventControllerScroll > m_scroll_controller
sigc::signal< void(const char *) m_status_changed)
void on_mouse_leave()
void on_mouse_released(int n_press, double x, double y)
double fLastMouseY
void ShowContextMenu(Glib::RefPtr< Gio::Menu > menu_model, const char *title, int x, int y)
Shows context menu with provided items and title at specified position.
void on_draw(const Cairo::RefPtr< Cairo::Context > &cr, int width, int height)
void on_mouse_click(int n_press, double x, double y)
bool on_my_query_tooltip(int x, int y, bool keyboard_tooltip, const Glib::RefPtr< Gtk::Tooltip > &tooltip)
double fLastMouseX
TCanvas * fCanvas
Cairo::Context * fContext
bool fShowEventStatus
Int_t GetEventY() const override
Definition TCanvas.h:142
virtual void Resize(Option_t *option="")
Recompute canvas parameters following a X11 Resize.
Definition TCanvas.cxx:1688
virtual void HandleInput(EEventType button, Int_t x, Int_t y)
Handle Input Events.
Definition TCanvas.cxx:1241
TObject * GetSelected() const override
Definition TCanvas.h:146
Int_t GetEventX() const override
Definition TCanvas.h:141
UInt_t GetPadHeight() const override
Definition TCanvas.h:171
UInt_t GetPadWidth() const override
Definition TCanvas.h:170
void Paint(Option_t *option="") override
Paint canvas.
Definition TCanvas.cxx:1563
Mother of all ROOT objects.
Definition TObject.h:42
Basic string class.
Definition TString.h:137
const char * Data() const
Definition TString.h:385
TString & Append(const char *cs)
Definition TString.h:582
void Form(const char *fmt,...)
Formats a string using a printf style format descriptor.
Definition TString.cxx:2438
Double_t y[n]
Definition legend1.C:17
Double_t x[n]
Definition legend1.C:17