1 /// Use gtk.Builder for loading interface
2 module gtkui.builder;
3 
4 import std.experimental.logger;
5 import std.exception : enforce;
6 
7 import gtk.Builder;
8 import gtk.Widget;
9 import gtk.Window;
10 import gobject.ObjectG;
11 
12 import gtkui.base;
13 import gtkui.exception;
14 
15 ///
16 abstract class BuilderUI : GtkUI
17 {
18     /// For `@gtksignal` UDA
19     protected struct SignalUDA { string ns; bool swapped; }
20     /// ditto
21     static protected auto gtksignal(string ns="", bool swapped=false) @property
22     { return SignalUDA(ns, swapped); }
23     /// ditto
24     static protected auto gtksignal(bool swapped) @property
25     { return SignalUDA("", swapped); }
26 
27     mixin GtkUIHelper;
28 
29     /// parse xml ui and create elements
30     protected Builder builder;
31 
32     /++ Insert this mixin in all builder classes where need use signals
33         contains:
34             implementation of `void setUpGtkSignals()`
35             static extern(C) callback's for signals
36      +/
37     mixin template GtkBuilderHelper()
38     {
39         mixin GtkUIHelper;
40 
41         import std.traits : hasUDA;
42 
43         alias This = typeof(this);
44 
45         private static string[] signatureNames(string m)()
46         {
47             import std.format : format;
48             alias F = __traits(getMember, This, m);
49             string[] ret;
50             foreach (i, p; Parameters!F)
51                 ret ~= format!"p%d"(i);
52             return ret;
53         }
54 
55         import std.string : join;
56         import std.traits : Parameters;
57 
58         private static string getSignature(string m)()
59         {
60             import std.traits : getUDAs;
61             import std.algorithm : map;
62             import std.range : enumerate;
63             import std.format : format;
64             enum swapped = getUDAs!(__traits(getMember, This, m), SignalUDA)[0].swapped;
65             enum sNames = signatureNames!m;
66             enum params = sNames.length ?
67                 sNames.enumerate.map!(a=>"Parameters!("~m~")["~a.index.to!string~"] "~a.value).join(", ")
68                 : `void* obj`;
69             return swapped ? `void* user_data, `~params : params~`, void* user_data`;
70         }
71 
72         static foreach (m; __traits(allMembers, This))
73         {
74             static if (hasUDA!(__traits(getMember, This, m), SignalUDA))
75             {
76                 mixin(`protected static extern(C) void __g_signal_callback_`~m~`(`~getSignature!m~`)
77                 {
78                     import gtkui.exception;
79                     import std.exception : enforce;
80 
81                     auto t = enforce(cast(This)(user_data),
82                         new GUIException("user data pointer for signal '`~m~`' is not '`~This.stringof~`'"));
83                     t.`~m~`(`~signatureNames!(m).join(", ")~`);
84                 }`);
85             }
86         }
87 
88         protected override void setUpGtkSignals()
89         {
90             import std.traits : getUDAs, isCallable;
91 
92             static foreach (m; __traits(allMembers, This))
93             {
94                 static if (hasUDA!(__traits(getMember, This, m), SignalUDA))
95                 {{
96                     static if (!isCallable!(__traits(getMember, This, m)))
97                         static assert(0, "signal can be only callable object (field '"~m~"')");
98                     enum uda = getUDAs!(__traits(getMember, This, m), SignalUDA)[0];
99                     enum name = (uda.ns.length ? uda.ns ~ "." : "") ~ m;
100                     mixin(`builder.addCallbackSymbol(name, cast(GCallback)(&__g_signal_callback_`~m~`));`);
101                 }}
102             }
103             builder.connectSignals(cast(void*)this);
104         }
105     }
106 
107     ///
108     this(string xml)
109     {
110         builder = new Builder;
111         enforce(builder.addFromString(xml), new GUIException("cannot create ui"));
112         setUpGtkWidgetFields();
113         setUpGtkSignals();
114     }
115 
116     ///
117     protected void setUpGtkSignals() {}
118 
119     /// Use builder for getting objects
120     override ObjectG getObject(string name) { return builder.getObject(name); }
121 }
122 
123 /// Class for main UI controller
124 class MainBuilderUI : BuilderUI
125 {
126     import glib.Idle;
127     static import gtk.Main;
128     alias GtkMain = gtk.Main.Main;
129 
130 protected:
131     static bool __gtk_inited = false;
132 
133     /// run Main.init with empty args by default
134     static void initializeGtk(string[] args=[])
135     {
136         if (__gtk_inited) return;
137         __gtk_inited = true;
138         GtkMain.init(args);
139     }
140 
141     ///
142     void delegate()[] onQuitList;
143 
144     ///
145     void quit() { foreach(dlg; onQuitList) dlg(); }
146 
147 public:
148 
149     ///
150     Idle[] idles;
151 
152     ///
153     void addOnIdle(void delegate() fnc)
154     { idles ~= new Idle({ fnc(); return true; }); }
155 
156     ///
157     void runLoop() { GtkMain.run(); }
158 
159     ///
160     bool loopStep(bool block=false)
161     { return GtkMain.iterationDo(block); }
162 
163     ///
164     void exitLoop() { GtkMain.quit(); }
165 
166     ///
167     void addOnQuit(void delegate() dlg) { onQuitList ~= dlg; }
168 
169     ///
170     protected static string lastUsedCss;
171 
172     ///
173     static void updateStyle(string css, bool throwOnError=false)
174     {
175         import gtk.CssProvider;
176         import gdk.Screen;
177         import gtk.StyleContext;
178         import std.typecons : scoped;
179 
180         if (css != lastUsedCss)
181         {
182             lastUsedCss = css;
183             try
184             {
185                 auto prov = scoped!CssProvider;
186                 prov.loadFromData(css);
187 
188                 StyleContext.addProviderForScreen(Screen.getDefault(),
189                         prov, GTK_STYLE_PROVIDER_PRIORITY_USER);
190             }
191             catch (Throwable e)
192             {
193                 .error("error while loading style: ", e.msg);
194                 if (throwOnError) throw e;
195             }
196         }
197     }
198 
199     ///
200     this(string xml, string css="")
201     {
202         initializeGtk();
203         super(xml);
204         if (css.length)
205             updateStyle(css);
206     }
207 
208     /++
209         add on hide calling quit method
210      +/
211     void setupMainWindow(Window w)
212     {
213         w.addOnHide((Widget){ quit(); });
214         w.showAll();
215     }
216 }
217 
218 /// For child UI controllers
219 class ChildBuilderUI : BuilderUI
220 {
221     ///
222     this(string xml) { super(xml); }
223 
224     /// For adding by parent controller
225     abstract Widget mainWidget() @property;
226 }