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 }