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