1 /+
2    Vasaro Copyright © 2018 Andrea Fontana
3    This file is part of Vasaro.
4 
5    Vasaro is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    Vasaro is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with Vasaro.  If not, see <http://www.gnu.org/licenses/>.
17 +/
18 module gtkattributes;
19 
20 template GtkAttributes()
21 {
22    import std.traits;
23    import std.exception : enforce;
24    import gtk.Builder;
25 
26    // UDA to support both @ui and @ui("id");
27    _ui ui(string id = "") { return _ui(id); }
28    struct _ui { string id = ""; }
29 
30    // UDA to tag gtk events
31    struct event(T)
32    {
33       string id; 
34       string eventName;
35       private alias eventType = T;
36    }
37 
38    private string __getWidgetId(alias T, string s)()
39    {
40          // The member we're analyzing
41       mixin("alias member = T." ~ s ~ ";");
42 
43       alias funcUDAs = getUDAs!(member, ui);    // @ui("id");
44       alias structUDAs = getUDAs!(member, _ui); // @ui
45 
46       static if (funcUDAs.length + structUDAs.length == 0) return "";
47       else if (funcUDAs.length + structUDAs.length > 1) assert(0, "You must use only one @ui attribute for `" ~ fullyQualifiedName!s ~ "`");
48       else
49       {
50          static if (funcUDAs.length == 1) return s;
51          else return structUDAs[0].id;
52       }
53 
54    }
55 
56    // Version for non-static methods/var
57    private void bindWidgets(T)(ref T obj, Builder b)
58    {
59       foreach( s; __traits( allMembers, T))
60       {
61          // The member we're analyzing
62          static if (__traits(compiles, mixin("T." ~ s)))
63          {
64             mixin("alias member = T." ~ s ~ ";");
65             
66             static if (isExpressions!member)
67             {
68                enum widgetId = __getWidgetId!(T,s);
69 
70                static if (widgetId.length == 0) continue;
71                else 
72                {
73                   auto tmpObj = b.getObject(widgetId);
74                   enforce(tmpObj !is null, "Can't find any widget with id `" ~ widgetId ~ "`");
75                   mixin("obj." ~ s ~ " = cast(typeof(member)) tmpObj;");
76                   mixin("auto tmpMember = obj." ~ s ~ ";");
77                   enforce(tmpMember !is null, "Can't convert `" ~ widgetId ~ "` to `" ~ fullyQualifiedName!(typeof(tmpMember)) ~ "`");
78                }
79             }
80          }
81       }
82    }
83 
84    // Version for static methods/var
85    private void bindWidgets(alias T)(Builder b)
86    {
87       foreach( s; __traits( allMembers, T))
88       {
89        
90         // The member we're analyzing
91          static if (__traits(compiles, mixin("T." ~ s)))
92          {
93             mixin("alias member = T." ~ s ~ ";");
94             
95             static if (isExpressions!member )
96             {
97                enum widgetId = __getWidgetId!(T,s);
98 
99                static if (widgetId.length == 0) continue;
100                else {
101                   auto tmpObj = b.getObject(widgetId);
102                   enforce(tmpObj !is null, "Can't find any widget with id `" ~ widgetId ~ "`");
103                   member = cast(typeof(member)) tmpObj;
104                   enforce(member !is null, "Can't convert `" ~ widgetId ~ "` to `" ~ fullyQualifiedName!(typeof(member)) ~ "`");
105                }
106             }
107          }
108       }
109    }
110 
111    // Version for static methods/var
112    private void bindEvents(alias T)(Builder b)
113    {
114       import std.functional : toDelegate;
115 
116       foreach( s; __traits( allMembers, T))
117       {
118          // The member we're analyzing
119          static if (__traits(compiles, mixin("T." ~ s)))
120          {
121             mixin("alias member = T." ~ s ~ ";");
122             
123             static if (isFunction!member)
124             {
125                alias udas = getUDAs!(member, event);
126 
127                static if (udas.length > 0)
128                   foreach(u; udas)
129                   {
130                      enum hasMethod = __traits(hasMember, u.eventType, "add" ~ u.eventName);
131                      static assert(hasMethod, "Can't find a method named `add" ~ u.eventName ~ "` for type `" ~ fullyQualifiedName!(u.eventType) ~"`");
132 
133                      auto tmpObj = b.getObject(u.id);
134                      enforce(tmpObj !is null, "Can't find any widget with id `" ~ u.id ~ "`");
135                      auto obj = cast(u.eventType) tmpObj;
136                      enforce(obj !is null, "Can't convert `" ~  u.id ~ "` to `" ~ fullyQualifiedName!(u.eventType) ~ "`");
137                      mixin ("obj.add" ~ u.eventName ~ "((&member).toDelegate());");
138                   }
139             }
140          }
141       }
142    }
143 
144    // Version for non-static methods/var
145    private void bindEvents(T)(ref T handler, Builder b)
146    {
147       foreach( s; __traits( allMembers, T))
148       {
149          static if (__traits(compiles, mixin("T." ~ s)))
150          {
151             mixin("alias member = T." ~ s ~ ";");
152             
153             static if (isFunction!member)
154             {
155                alias udas = getUDAs!(member, event);
156 
157                static if (udas.length > 0)
158                   foreach(u; udas)
159                   {
160                      enum hasMethod = __traits(hasMember, u.eventType, "add" ~ u.eventName);
161                      static assert(hasMethod, "Can't find a method named `add" ~ u.eventName ~ "` for type `" ~ fullyQualifiedName!(u.eventType) ~"`");
162 
163                      auto tmpObj = b.getObject(u.id);
164                      enforce(tmpObj !is null, "Can't find any widget with id `" ~ u.id ~ "`");
165                      auto obj = cast(u.eventType) tmpObj;
166                      enforce(obj !is null, "Can't convert `" ~  u.id ~ "` to `" ~ fullyQualifiedName!(u.eventType) ~ "`");
167                      mixin ("obj.add" ~ u.eventName ~ "(&handler." ~s ~");");
168                   }
169             }
170          }
171       }
172    }
173 
174    private void bindAll(T)(ref T handler, Builder b)
175    {
176       bindWidgets(handler, b);
177       bindEvents(handler, b);
178    }
179 
180    private void bindAll(alias T)(Builder b)
181    {
182       bindWidgets!T(b);
183       bindEvents!T(b);
184    }
185 
186 }