1 module easyd.gtk.base;
2 
3 // (C) 2014-2019 by Matthias Rossmy
4 // This file is distributed under the "Fair Use License v2"
5 
6 //TODO: .tr wieder einkommentieren, sobald die Funktion portiert wurde
7 
8 /* Example:
9 mixin GuiApp!(TestWin);
10 
11 class TestWin : ILayoutWindow
12 {
13     void init()
14     {
15         title="GtkTest";
16     }
17 }
18 */
19 
20 import easyd.base;
21 import std.conv;
22 import std.stdio;
23 
24 public import gtk.Main;
25 import gtk.Clipboard;
26 import gtk.Grid;
27 import gtk.Box;
28 import gtk.Widget;
29 import gtk.MainWindow;
30 import gtk.MenuBar;
31 import gtk.Menu;
32 import gtk.ImageMenuItem;
33 import gtk.SeparatorMenuItem;
34 import gtk.AccelGroup;
35 import gdk.Atom;
36 import gdk.Event;
37 import gobject.ObjectG;
38 
39 mixin template GuiApp(T)
40 {
41 	T gMainWin;
42 
43 	void main(string[] args)
44 	{
45 		Main.init(args);
46 		gMainWin = new T();
47 		Main.run();
48 	}
49 }
50 
51 void handleGtkEvents(bool sleep=false, bool dbg=false)
52 {
53 	bool eventsProcessed=false;
54 	while(Main.eventsPending)
55 	{
56 		Main.iteration;
57 		eventsProcessed=true;
58 	}
59 	if(dbg) writeln(eventsProcessed?"Events processed":"No events");
60     if(sleep) sleepMsec(1);
61 }
62 
63 Clipboard clipBoard(string name="CLIPBOARD")
64 {
65 	return Clipboard.get(intern(name, true));
66 }
67 
68 class GridLayout : Grid
69 {
70     protected Widget lastWidget = null;
71     protected bool hasColStretch = false;
72     protected bool hasRowStretch = false;
73 
74     @property void spacing(int value)
75     {
76         setRowSpacing(value);
77         setColumnSpacing(value);
78     }
79 
80     @property void margin(int value)
81     {
82         setMarginLeft(value);
83         setMarginRight(value);
84         setMarginTop(value);
85         setMarginBottom(value);
86     }
87 
88     T add(T)(T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
89     {
90         if(getOrientation()==GtkOrientation.HORIZONTAL)
91         {
92             return addRight(widget, colspan, rowspan, colstretch, rowstretch, width, height);
93         }
94         else
95         {
96             return addBelow(widget, colspan, rowspan, colstretch, rowstretch, width, height);
97         }
98     }
99 
100     T addBelow(T)(T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
101     {
102         return addBelow(lastWidget,widget,colspan,rowspan,colstretch,rowstretch,width,height);
103     }
104 
105     T addAbove(T)(T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
106     {
107         return addAbove(lastWidget,widget,colspan,rowspan,colstretch,rowstretch,width,height);
108     }
109 
110     T addLeft(T)(T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
111     {
112         return addLeftOf(lastWidget,widget,colspan,rowspan,colstretch,rowstretch,width,height);
113     }
114 
115     T addRight(T)(T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
116     {
117         return addRightOf(lastWidget,widget,colspan,rowspan,colstretch,rowstretch,width,height);
118     }
119 
120     T addBelow(T)(Widget reference, T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
121     {
122         setupWidget(widget,colstretch,rowstretch,width,height);
123         attachNextTo(widget,reference,GtkPositionType.BOTTOM,colspan,rowspan);
124         lastWidget = widget;
125         return widget;
126     }
127 
128     T addAbove(T)(Widget reference, T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
129     {
130         setupWidget(widget,colstretch,rowstretch,width,height);
131         attachNextTo(widget,reference,GtkPositionType.TOP,colspan,rowspan);
132         lastWidget = widget;
133         return widget;
134     }
135 
136     T addLeftOf(T)(Widget reference, T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
137     {
138         setupWidget(widget,colstretch,rowstretch,width,height);
139         attachNextTo(widget,reference,GtkPositionType.LEFT,colspan,rowspan);
140         lastWidget = widget;
141         return widget;
142     }
143 
144     T addRightOf(T)(Widget reference, T widget, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false, int width=0, int height=0)
145     {
146         setupWidget(widget,colstretch,rowstretch,width,height);
147         attachNextTo(widget,reference,GtkPositionType.RIGHT,colspan,rowspan);
148         lastWidget = widget;
149         return widget;
150     }
151 
152     typeof(this) addSubLayout(int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
153     {
154         if(getOrientation()==GtkOrientation.HORIZONTAL)
155         {
156             return addSubLayoutRight(colspan, rowspan, colstretch, rowstretch);
157         }
158         else
159         {
160             return addSubLayoutBelow(colspan, rowspan, colstretch, rowstretch);
161         }
162     }
163 
164     typeof(this) addSubLayoutBelow(int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
165     {
166         return addSubLayoutBelow(lastWidget, colspan, rowspan, colstretch, rowstretch);
167     }
168 
169     typeof(this) addSubLayoutAbove(int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
170     {
171         return addSubLayoutAbove(lastWidget, colspan, rowspan, colstretch, rowstretch);
172     }
173 
174     typeof(this) addSubLayoutLeft(int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
175     {
176         return addSubLayoutLeftOf(lastWidget, colspan, rowspan, colstretch, rowstretch);
177     }
178 
179     typeof(this) addSubLayoutRight(int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
180     {
181         return addSubLayoutRightOf(lastWidget, colspan, rowspan, colstretch, rowstretch);
182     }
183 
184     typeof(this) addSubLayoutBelow(Widget reference, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
185     {
186         auto widget = createSubLayout(colstretch, rowstretch);
187         attachNextTo(widget,reference,GtkPositionType.BOTTOM,colspan,rowspan);
188         lastWidget = widget;
189         return widget;
190     }
191 
192     typeof(this) addSubLayoutAbove(Widget reference, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
193     {
194         auto widget = createSubLayout(colstretch, rowstretch);
195         attachNextTo(widget,reference,GtkPositionType.TOP,colspan,rowspan);
196         lastWidget = widget;
197         return widget;
198     }
199 
200     typeof(this) addSubLayoutLeftOf(Widget reference, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
201     {
202         auto widget = createSubLayout(colstretch, rowstretch);
203         attachNextTo(widget,reference,GtkPositionType.LEFT,colspan,rowspan);
204         lastWidget = widget;
205         return widget;
206     }
207 
208     typeof(this) addSubLayoutRightOf(Widget reference, int colspan=1, int rowspan=1, bool colstretch=false, bool rowstretch=false)
209     {
210         auto widget = createSubLayout(colstretch, rowstretch);
211         attachNextTo(widget,reference,GtkPositionType.RIGHT,colspan,rowspan);
212         lastWidget = widget;
213         return widget;
214     }
215 
216     void autoStretch()
217     {
218         auto children = getChildren();
219         for(;children !is null;children=children.next)
220         {
221             Widget w = ObjectG.getDObject!(Widget)(cast(GtkWidget*) children.data);
222             if(typeid(w).to!string == typeid(this).to!string)
223             {
224                 (cast(typeof(this)) w).autoStretch();
225             }
226             else
227             {
228                 if(!hasColStretch) w.setHexpand(1);
229                 if(!hasRowStretch) w.setVexpand(1);
230             }
231         }
232     }
233 
234     protected void setupWidget(T)(T widget, bool colstretch, bool rowstretch, int width, int height)
235     {
236         if(width>0 || height>0)
237         {
238             widget.setSizeRequest(width,height);
239         }
240         if(colstretch)
241         {
242             widget.setHexpand(1);
243             hasColStretch = true;
244         }
245         if(rowstretch)
246         {
247             widget.setVexpand(1);
248             hasRowStretch = true;
249         }
250     }
251 
252     typeof(this) createSubLayout(bool colstretch, bool rowstretch)
253     {
254         auto l = new typeof(this);
255         l.spacing = getRowSpacing;
256         if(!colstretch) l.hasColStretch = true;
257         if(!rowstretch) l.hasRowStretch = true;
258         return l;
259     }
260 }
261 
262 interface IInit
263 {
264     void init();
265 }
266 
267 abstract class ILayoutWindow : MainWindow, IInit
268 {
269     public GridLayout layout;
270     public bool autoStretchAfterInit = true;
271     void delegate()[] onClose;
272     protected MenuBar topMenuBarIntern;
273     protected MenuBar bottomMenuBarIntern;
274     public AccelGroup accelGroup;
275     protected Box vBox;
276 
277     public MenuBar topMenuBar()
278     {
279         return topMenuBarIntern.create;
280     }
281 
282     public MenuBar bottomMenuBar()
283     {
284         return bottomMenuBarIntern.create;
285     }
286 
287     this(int minWidth=200, int minHeight=30)
288     {
289         super("");
290         setDefaultSize(minWidth,minHeight);
291         setPosition(WindowPosition.CENTER);
292 
293         layout = new GridLayout();
294         layout.margin = 5;
295         layout.spacing = 5;
296 
297         init();
298 
299         vBox = new Box(Orientation.VERTICAL,0);
300         add(vBox);
301         if(topMenuBarIntern !is null) vBox.add(topMenuBarIntern);
302         vBox.add(layout);
303         if(bottomMenuBarIntern !is null) vBox.add(bottomMenuBarIntern);
304 
305         if(autoStretchAfterInit) layout.autoStretch();
306 
307         addOnDelete(&onCloseFunc);
308 
309         showAll();
310     }
311 
312     protected bool onCloseFunc(Event e, Widget w)
313     {
314         onClose.trigger();
315         return false;
316     }
317 
318     @property void title(string value)
319     {
320         setTitle(value);
321     }
322 }
323 
324 interface IBindable(T)
325 {
326 	T value();
327 	void value(T val);
328 }
329 
330 abstract class IBinding
331 {
332 	void delegate()[] onEdit;
333 	void load();
334 	void apply();
335 }
336 
337 class Binding(T) : IBinding
338 {
339 	T* data;
340 	IBindable!T control;
341 	string oldValue;
342 
343 	override void load()
344 	{
345 		control.value = *data;
346 		oldValue = control.value.to!string; //TODO: to!string durch String-Serialisierung ersetzen (damit Binding auch strukturierte Datentypen unterstützt)
347 	}
348 	
349 	override void apply()
350 	{
351 		string newvalue = control.value.to!string; //TODO: to!string durch String-Serialisierung ersetzen
352 		if(newvalue != oldValue) //strings vergleichen, weil der Vergleich von 2 gleichen class-Objekten trotzdem ungleich zurückgeben kann
353 		{
354 			*data = control.value;
355 			static if(__traits(hasMember, T, "update")) (*data).update;
356 			onEdit.trigger;
357 			oldValue = newvalue;
358 		}
359 	}
360 }
361 
362 struct BindingSet
363 {
364 	IBinding[] bindings;
365 	alias bindings this;
366 
367 	void add(T)(T* data, IBindable!T control)
368 	{
369 		auto b = new Binding!T;
370 		b.data=data;
371 		b.control=control;
372 		bindings ~= b;
373 	}
374 
375 	void add(T)(ref T data, IBindable!T control)
376 	{
377 		add(&data,control);
378 	}
379 
380 	void load()
381 	{
382 		foreach(b;bindings) b.load;
383 	}
384 
385 	void apply()
386 	{
387 		foreach(b;bindings) b.apply;
388 	}
389 }
390 
391 class MenuItem : ImageMenuItem
392 {
393     Menu subMenuIntern;
394     void delegate()[] onClick;
395 
396     this(string s, void delegate() onclick=null)
397     {
398         super(s);
399         setupEvents(onclick);
400     }
401 
402     this(StockID id, void delegate() onclick=null)
403     {
404         super(id,null);
405         setupEvents(onclick);
406     }
407 
408     this(string s, StockID id, void delegate() onclick=null)
409     {
410         super(id,null);
411         setLabel(s);
412         setupEvents(onclick);
413     }
414 
415     this(Widget w, void delegate() onclick=null)
416     {
417         super();
418         setImage(w);
419         setupEvents(onclick);
420     }
421 
422     Menu subMenu()
423     {
424         if(subMenuIntern is null)
425         {
426             subMenuIntern = new Menu();
427             setSubmenu(subMenuIntern);
428         }
429         return subMenuIntern;
430     }
431 
432     MenuItem add(string s, void delegate() onclick=null)
433     {
434         auto item = new MenuItem(s,onclick);
435         subMenu.append(item);
436         return item;
437     }
438 
439     MenuItem add(StockID id, void delegate() onclick=null)
440     {
441         auto item = new MenuItem(id,onclick);
442         subMenu.append(item);
443         return item;
444     }
445 
446     MenuItem add(string s, StockID id, void delegate() onclick=null)
447     {
448         auto item = new MenuItem(s,id,onclick);
449         subMenu.append(item);
450         return item;
451     }
452 
453     MenuItem addWidget(Widget w, void delegate() onclick=null)
454     {
455         auto item = new MenuItem(w,onclick);
456         subMenu.append(item);
457         return item;
458     }
459 
460 	void addSeparator()
461 	{
462 		subMenu.append(new SeparatorMenuItem);
463 	}
464 
465 	void addAccel(ILayoutWindow win, string shortcut)
466     {
467         uint accelKey;
468         GdkModifierType accelMods;
469         AccelGroup.acceleratorParse(shortcut,accelKey,accelMods);
470         addAccelerator("activate",win.accelGroup,accelKey,accelMods,GtkAccelFlags.VISIBLE);
471     }
472 
473     protected void setupEvents(void delegate() onclick)
474     {
475         addOnActivate(&onClickFunc);
476         //addOnButtonPress(&onPressFunc);
477         if(onclick !is null)
478         {
479             onClick ~= onclick;
480         }
481     }
482 
483     /*protected bool onPressFunc(Event event, Widget widget)
484     {
485         onClick.trigger();
486         return onClick.length>0;
487     }*/
488 
489     protected void onClickFunc(gtk.MenuItem.MenuItem item)
490     {
491         onClick.trigger();
492     }
493 }
494 
495 mixin template MenuAdd()
496 {
497     MenuItem add(string s, void delegate() onclick=null)
498     {
499         auto item = new MenuItem(s,onclick);
500         append(item);
501         return item;
502     }
503 
504     MenuItem add(StockID id, void delegate() onclick=null)
505     {
506         auto item = new MenuItem(id,onclick);
507         append(item);
508         return item;
509     }
510 
511     MenuItem add(string s, StockID id, void delegate() onclick=null)
512     {
513         auto item = new MenuItem(s,id,onclick);
514         append(item);
515         return item;
516     }
517 
518     MenuItem addWidget(Widget w, void delegate() onclick=null)
519     {
520         auto item = new MenuItem(w,onclick);
521         append(item);
522         return item;
523     }
524 }
525 
526 class Menu : gtk.Menu.Menu
527 {
528     mixin MenuAdd;
529 }
530 
531 class MenuBar : gtk.MenuBar.MenuBar
532 {
533     mixin MenuAdd;
534 }
535