1 #include <CtrlCore/CtrlCore.h>
2
3 #ifdef GUI_GTK
4
5 namespace Upp {
6
7 #define LLOG(x) // DLOG(x)
8
9 Vector<Event<> > Ctrl::hotkey;
10 Vector<dword> Ctrl::keyhot;
11 Vector<dword> Ctrl::modhot;
12
13 Vector<Ctrl::Win> Ctrl::wins;
14
15 Ptr<Ctrl> Ctrl::activeCtrl;
16
17 int Ctrl::WndCaretTime;
18 bool Ctrl::WndCaretVisible;
19
20 bool Ctrl::invalids;
21
FindId(int id)22 int Ctrl::FindId(int id)
23 {
24 for(int i = 0; i < wins.GetCount(); i++)
25 if(wins[i].id == id)
26 return i;
27 return -1;
28 }
29
FindCtrl(Ctrl * ctrl)30 int Ctrl::FindCtrl(Ctrl *ctrl)
31 {
32 for(int i = 0; i < wins.GetCount(); i++)
33 if(wins[i].ctrl == ctrl)
34 return i;
35 return -1;
36 }
37
FindGtkWindow(GtkWidget * gtk)38 int Ctrl::FindGtkWindow(GtkWidget *gtk)
39 {
40 for(int i = 0; i < wins.GetCount(); i++)
41 if(wins[i].gtk == gtk)
42 return i;
43 return -1;
44 }
45
FindGdkWindow(GdkWindow * gdk)46 int Ctrl::FindGdkWindow(GdkWindow *gdk)
47 {
48 for(int i = 0; i < wins.GetCount(); i++)
49 if(wins[i].gdk == gdk)
50 return i;
51 return -1;
52 }
53
IsAlphaSupported()54 bool Ctrl::IsAlphaSupported()
55 {
56 return false;
57 }
58
IsCompositedGui()59 bool Ctrl::IsCompositedGui()
60 {
61 return true; // limits some GUI effects that do not play well with advanced desktops
62 }
63
GetTopCtrls()64 Vector<Ctrl *> Ctrl::GetTopCtrls()
65 {
66 GuiLock __;
67 Vector<Ctrl *> h;
68 for(int i = 0; i < wins.GetCount(); i++)
69 h.Add(wins[i].ctrl);
70 return h;
71 }
72
73 cairo_surface_t *CreateCairoSurface(const Image& img);
74
SetMouseCursor(const Image & image)75 void Ctrl::SetMouseCursor(const Image& image)
76 {
77 LLOG("SetMouseCursor");
78 GuiLock __;
79 int64 id = image.GetSerialId();
80 Ctrl *topctrl = NULL;
81 Top *top = NULL;
82 if(mouseCtrl)
83 topctrl = mouseCtrl->GetTopCtrl();
84 else
85 topctrl = GetActiveCtrl();
86 if(topctrl)
87 top = topctrl->top;
88 if(top && id != top->cursor_id) {
89 top->cursor_id = id;
90 int64 aux = image.GetAuxData();
91 GdkCursor *c = NULL;
92 if(aux)
93 c = gdk_cursor_new_for_display(gdk_display_get_default(), (GdkCursorType)(aux - 1));
94 else
95 if(IsNull(image))
96 c = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_BLANK_CURSOR);
97 else {
98 Point p = image.GetHotSpot();
99
100 #if GTK_CHECK_VERSION(3, 10, 0)
101 cairo_surface_t *surface = CreateCairoSurface(image);
102 double scale = SCL(1);
103 cairo_surface_set_device_scale(surface, scale, scale);
104 c = gdk_cursor_new_from_surface(gdk_display_get_default(), surface, p.x / scale, p.y / scale);
105 cairo_surface_destroy(surface);
106 #else
107 ImageGdk m;
108 m.Set(image);
109 GdkPixbuf *pb = m;
110 if(pb)
111 c = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pb, p.x, p.y);
112 #endif
113 }
114 if(c && topctrl->IsOpen()) {
115 gdk_window_set_cursor(topctrl->gdk(), c);
116 g_object_unref(c);
117 gdk_display_flush(gdk_display_get_default()); // Make it visible immediately
118 }
119 }
120 }
121
GetOwner()122 Ctrl *Ctrl::GetOwner()
123 {
124 GuiLock __;
125 return IsOpen() ? top->owner : NULL;
126 }
127
GetActiveCtrl()128 Ctrl *Ctrl::GetActiveCtrl()
129 {
130 GuiLock __;
131 if(focusCtrl)
132 return focusCtrl->GetTopCtrl();
133 return activeCtrl;
134 }
135
136 // Vector<Event<> > Ctrl::hotkey;
137
138 #ifndef GDK_WINDOWING_X11
139
140 // There is no generic support in GTK for HotKey
141
RegisterSystemHotKey(dword key,Function<void ()> cb)142 int Ctrl::RegisterSystemHotKey(dword key, Function<void ()> cb)
143 {
144 return -1;
145 }
146
UnregisterSystemHotKey(int id)147 void Ctrl::UnregisterSystemHotKey(int id)
148 {
149 }
150
151 #endif
152
AnimateCaret()153 void Ctrl::AnimateCaret()
154 {
155 GuiLock __;
156 int v = !(((msecs() - WndCaretTime) / 500) & 1);
157 if(v != WndCaretVisible) {
158 WndCaretVisible = v;
159 RefreshCaret();
160 }
161 }
162
PaintCaret(SystemDraw & w)163 void Ctrl::PaintCaret(SystemDraw& w)
164 {
165 GuiLock __;
166 LLOG("PaintCaret " << Name() << ", caretCtrl: " << caretCtrl << ", WndCaretVisible: " << WndCaretVisible);
167 if(this == caretCtrl && WndCaretVisible)
168 w.DrawRect(caretx, carety, caretcx, caretcy, InvertColor);
169 }
170
SetCaret(int x,int y,int cx,int cy)171 void Ctrl::SetCaret(int x, int y, int cx, int cy)
172 {
173 GuiLock __;
174 LLOG("SetCaret " << Name());
175 if(this == caretCtrl)
176 RefreshCaret();
177 caretx = x;
178 carety = y;
179 caretcx = cx;
180 caretcy = cy;
181 if(this == caretCtrl) {
182 WndCaretTime = msecs();
183 RefreshCaret();
184 AnimateCaret();
185 }
186 }
187
SyncCaret()188 void Ctrl::SyncCaret() {
189 GuiLock __;
190 LLOG("SyncCaret");
191 if(focusCtrl != caretCtrl) {
192 LLOG("SyncCaret DO " << Upp::Name(caretCtrl) << " -> " << Upp::Name(focusCtrl));
193 RefreshCaret();
194 caretCtrl = focusCtrl;
195 RefreshCaret();
196 }
197 }
198
GetWndScreenRect() const199 Rect Ctrl::GetWndScreenRect() const
200 {
201 GuiLock __;
202 if(IsOpen()) {
203 gint x, y;
204 gdk_window_get_position(gdk(), &x, &y);
205 gint width = gdk_window_get_width(gdk());
206 gint height = gdk_window_get_height(gdk());
207 return SCL(x, y, width, height);
208 }
209 return Null;
210 }
211
WndShow(bool b)212 void Ctrl::WndShow(bool b)
213 {
214 GuiLock __;
215 LLOG("WndShow " << Name() << ", " << b);
216 if(IsOpen()) {
217 if(b)
218 gtk_widget_show_now(top->window);
219 else
220 gtk_widget_hide(top->window);
221 StateH(SHOW);
222 }
223 }
224
IsWndOpen() const225 bool Ctrl::IsWndOpen() const {
226 GuiLock __;
227 return top && top->window && gtk_widget_get_window(top->window);
228 }
229
SetAlpha(byte alpha)230 void Ctrl::SetAlpha(byte alpha)
231 {
232 GuiLock __;
233 }
234
GetWorkArea() const235 Rect Ctrl::GetWorkArea() const
236 {
237 GuiLock __;
238 static Array<Rect> rc;
239 if(rc.IsEmpty())
240 GetWorkArea(rc);
241
242 Point pt = GetScreenRect().TopLeft();
243 for (int i = 0; i < rc.GetCount(); i++)
244 if(rc[i].Contains(pt))
245 return rc[i];
246 return GetPrimaryWorkArea();
247 }
248
GetWorkArea(Array<Rect> & rc)249 void Ctrl::GetWorkArea(Array<Rect>& rc)
250 {
251 GuiLock __;
252 #if GTK_CHECK_VERSION(3, 22, 0)
253 GdkDisplay *s = gdk_display_get_default();
254 int n = gdk_display_get_n_monitors(s);
255 rc.Clear();
256 Vector<int> netwa;
257 for(int i = 0; i < n; i++) {
258 GdkRectangle rr;
259 gdk_monitor_get_workarea(gdk_display_get_monitor(s, i), &rr);
260 rc.Add(SCL(rr.x, rr.y, rr.width, rr.height));
261 }
262 #else
263 GdkScreen *s = gdk_screen_get_default();
264 int n = gdk_screen_get_n_monitors(s);
265 rc.Clear();
266 Vector<int> netwa;
267 for(int i = 0; i < n; i++) {
268 GdkRectangle rr;
269 Rect r;
270 gdk_screen_get_monitor_workarea(s, i, &rr);
271 r = RectC(rr.x, rr.y, rr.width, rr.height);
272 rc.Add(r);
273 }
274 #endif
275 }
276
GetVirtualWorkArea()277 Rect Ctrl::GetVirtualWorkArea()
278 {
279 GuiLock __;
280 static Rect r;
281 if(r.right == 0) {
282 r = GetPrimaryWorkArea();
283 Array<Rect> rc;
284 GetWorkArea(rc);
285 for(int i = 0; i < rc.GetCount(); i++)
286 r |= rc[i];
287 }
288 return r;
289 }
290
GetVirtualScreenArea()291 Rect Ctrl::GetVirtualScreenArea()
292 {
293 GuiLock __;
294 static Rect r;
295 if(r.right == 0) {
296 gint x, y, width, height;
297 gdk_window_get_geometry(gdk_screen_get_root_window(gdk_screen_get_default()),
298 &x, &y, &width, &height);
299 r = SCL(x, y, width, height);
300 }
301 return r;
302 }
303
GetPrimaryWorkArea()304 Rect Ctrl::GetPrimaryWorkArea()
305 {
306 GuiLock __;
307 #if GTK_CHECK_VERSION(3, 22, 0)
308 GdkRectangle rr;
309 gdk_monitor_get_workarea(gdk_display_get_primary_monitor(gdk_display_get_default()), &rr);
310 return SCL(rr.x, rr.y, rr.width, rr.height);
311 #else
312 static Rect r;
313 if (r.right == 0) {
314 Array<Rect> rc;
315 GetWorkArea(rc);
316 int primary = gdk_screen_get_primary_monitor(gdk_screen_get_default());
317 primary >= 0 && primary < rc.GetCount() ? r = rc[primary] : r = GetVirtualScreenArea();
318 }
319 return r;
320 #endif
321 }
322
GetPrimaryScreenArea()323 Rect Ctrl::GetPrimaryScreenArea()
324 {
325 return GetPrimaryWorkArea();
326 }
327
GetKbdDelay()328 int Ctrl::GetKbdDelay()
329 {
330 GuiLock __;
331 return 500;
332 }
333
GetKbdSpeed()334 int Ctrl::GetKbdSpeed()
335 {
336 GuiLock __;
337 return 1000 / 32;
338 }
339
SetWndForeground()340 void Ctrl::SetWndForeground()
341 {
342 GuiLock __;
343 if(IsOpen())
344 gtk_window_present(gtk());
345 }
346
IsWndForeground() const347 bool Ctrl::IsWndForeground() const
348 {
349 GuiLock __;
350 LLOG("IsWndForeground");
351 return IsOpen() && gtk_window_is_active(gtk());
352 }
353
HasWndFocus() const354 bool Ctrl::HasWndFocus() const
355 {
356 GuiLock __;
357 return IsOpen() && gtk_window_is_active(gtk());
358 }
359
FocusSync()360 void Ctrl::FocusSync()
361 {
362 GuiLock __;
363 static Ptr<Ctrl> ctrl;
364 if(focusCtrlWnd && focusCtrlWnd->IsOpen() && gtk_window_is_active(focusCtrlWnd->gtk())) {
365 ctrl = focusCtrlWnd;
366 return;
367 }
368 Ptr<Ctrl> focus = NULL;
369 for(int i = 0; i < wins.GetCount(); i++)
370 if(gtk_window_is_active((GtkWindow *)wins[i].gtk)) {
371 focus = wins[i].ctrl;
372 break;
373 }
374 if(focus != ctrl) {
375 if(ctrl)
376 ctrl->KillFocusWnd();
377 ctrl = focus;
378 if(ctrl)
379 ctrl->SetFocusWnd();
380 SyncCaret();
381 }
382 }
383
SetWndFocus()384 bool Ctrl::SetWndFocus()
385 {
386 GuiLock __;
387 LLOG("SetWndFocus0 " << Upp::Name(this) << ", top: " << top);
388 if(top) {
389 LLOG("SetWndFocus0 DO gdk: " << gdk());
390 SetWndForeground();
391 int t0 = msecs();
392 while(!gtk_window_is_active(gtk()) && msecs() - t0 < 500) // Wait up to 500ms for window to become active - not ideal, but only possibility
393 FetchEvents(true);
394 FocusSync();
395 }
396 return true;
397 }
398
399 void WakeUpGuiThread();
400
WndInvalidateRect(const Rect & r)401 void Ctrl::WndInvalidateRect(const Rect& r)
402 {
403 GuiLock __;
404 LLOG("WndInvalidateRect " << r);
405 Rect rr;
406 if(scale > 1) {
407 rr.left = r.left / 2;
408 rr.top = r.top / 2;
409 rr.right = (r.right + 1) / 2;
410 rr.bottom = (r.bottom + 1) / 2;
411 }
412 else
413 rr = r;
414
415 // as gtk3 dropped thread locking, we need to push invalid rectangles onto main loop
416 for(Win& win : wins) {
417 if(win.ctrl == this) {
418 if(win.invalid.GetCount() && win.invalid[0].right > 99999 && win.invalid[0].bottom > 99999)
419 return;
420 if(win.invalid.GetCount() > 200) { // keep things sane
421 win.invalid.Clear();
422 win.invalid.Add(Rect(0, 0, 100000, 100000));
423 }
424 else
425 win.invalid.Add(rr);
426 if(!invalids) {
427 invalids = true;
428 WakeUpGuiThread();
429 }
430 }
431 }
432 }
433
WndScrollView(const Rect & r,int dx,int dy)434 void Ctrl::WndScrollView(const Rect& r, int dx, int dy)
435 {
436 GuiLock __;
437 LLOG("ScrollView " << rect);
438 WndInvalidateRect(r);
439 }
440
SweepConfigure(bool wait)441 bool Ctrl::SweepConfigure(bool wait)
442 {
443 Ptr<Ctrl> this_ = this;
444 bool r = false;
445 FetchEvents(wait);
446 for(int i = 0; i < Events.GetCount() && this_; i++) {
447 GEvent& e = Events[i];
448 if(e.type == GDK_CONFIGURE && this_ && top->id == e.windowid) {
449 Rect rect = e.value;
450 LLOG("SweepConfigure " << rect);
451 if(GetRect() != rect)
452 SetWndRect(rect);
453 r = true;
454 e.type = EVENT_NONE;
455 }
456 }
457 return r;
458 }
459
WndSetPos(const Rect & rect)460 void Ctrl::WndSetPos(const Rect& rect)
461 {
462 LLOG("WndSetPos " << UPP::Name(this) << " " << rect);
463 GuiLock __;
464 if(!IsOpen())
465 return;
466 Ptr<Ctrl> this_ = this;
467 SweepConfigure(false); // Remove any previous GDK_CONFIGURE for this window
468 if(!this_ || !IsOpen())
469 return;
470
471 Rect m(0, 0, 0, 0);
472 if(dynamic_cast<TopWindow *>(this))
473 m = GetFrameMargins();
474 SetWndRect(rect);
475 if(TopWindow *tw = dynamic_cast<TopWindow *>(this))
476 tw->SyncSizeHints();
477 gdk_window_move_resize(gdk(), LSC(rect.left - m.left), LSC(rect.top - m.top), LSC(rect.GetWidth()), LSC(rect.GetHeight()));
478 LLOG("-- WndSetPos0 " << rect);
479 }
480
WndEnable(bool b)481 void Ctrl::WndEnable(bool b)
482 {
483 GuiLock __;
484 if(IsOpen()) {
485 gtk_widget_set_sensitive(top->window, b);
486 StateH(ENABLE);
487 }
488 }
489
WndUpdate(const Rect & r)490 void Ctrl::WndUpdate(const Rect& r)
491 {
492 GuiLock __;
493 LLOG("WndUpdate0r " << r);
494 WndUpdate(); // Not found a way how to update only part of window
495 }
496
WndUpdate()497 void Ctrl::WndUpdate()
498 {
499 GuiLock __;
500 LLOG("WndUpdate0");
501 // gdk_window_process_updates(gdk(), TRUE); // deprecated
502 FetchEvents(FALSE); // Should pickup GDK_EXPOSE and repaint the window
503 gdk_display_flush(gdk_display_get_default());
504 }
505
GetDefaultWindowRect()506 Rect Ctrl::GetDefaultWindowRect()
507 {
508 GuiLock __;
509 Rect r = GetPrimaryWorkArea();
510 Size sz = r.GetSize();
511
512 static int pos = min(sz.cx / 10, 50);
513 pos += 10;
514 int cx = sz.cx * 2 / 3;
515 int cy = sz.cy * 2 / 3;
516 if(pos + cx + 50 > sz.cx || pos + cy + 50 > sz.cy)
517 pos = 0;
518 return RectC(r.left + pos + 20, r.top + pos + 20, cx, cy);
519 }
520
TopFrameDraw(Ctrl * ctrl,const Rect & r)521 TopFrameDraw::TopFrameDraw(Ctrl *ctrl, const Rect& r)
522 {
523 EnterGuiMutex();
524 Ctrl *top = ctrl->GetTopCtrl();
525 #if GTK_CHECK_VERSION(3, 22, 0)
526 cairo_rectangle_int_t rr;
527 rr.x = Ctrl::LSC(r.left);
528 rr.y = Ctrl::LSC(r.top);
529 rr.width = Ctrl::LSC(r.GetWidth());
530 rr.height = Ctrl::LSC(r.GetHeight());
531 cairo_region_t *rg = cairo_region_create_rectangle(&rr);
532 ctx = gdk_window_begin_draw_frame(top->gdk(), rg);
533 cairo_region_destroy(rg);
534 cr = gdk_drawing_context_get_cairo_context(ctx);
535 #else
536 cr = gdk_cairo_create(top->gdk());
537 #endif
538 cairo_scale(cr, Ctrl::LSC(1), Ctrl::LSC(1));
539 Clipoff(r);
540 }
541
~TopFrameDraw()542 TopFrameDraw::~TopFrameDraw()
543 {
544 FlushText();
545 #if GTK_CHECK_VERSION(3, 22, 0)
546 gdk_window_end_draw_frame(gdk_drawing_context_get_window(ctx), ctx);
547 #else
548 cairo_destroy(cr);
549 #endif
550 LeaveGuiMutex();
551 }
552
SplitCmdLine__(const char * cmd)553 Vector<WString> SplitCmdLine__(const char *cmd)
554 {
555 Vector<WString> out;
556 while(*cmd)
557 if((byte)*cmd <= ' ')
558 cmd++;
559 else if(*cmd == '\"') {
560 WString quoted;
561 while(*++cmd && (*cmd != '\"' || *++cmd == '\"'))
562 quoted.Cat(FromSystemCharset(String(cmd, 1)).ToWString());
563 out.Add(quoted);
564 }
565 else {
566 const char *begin = cmd;
567 while((byte)*cmd > ' ')
568 cmd++;
569 out.Add(String(begin, cmd).ToWString());
570 }
571 return out;
572 }
573
574 }
575
576 #endif
577