1// Builder.custom - customizations to Gtk.Builder
2//
3// Authors: Stephane Delcroix  <stephane@delcroix.org>
4// The biggest part of this code is adapted from glade#, by
5//	Ricardo Fernández Pascual <ric@users.sourceforge.net>
6//	Rachel Hestilow <hestilow@ximian.com>
7//
8// Copyright (c) 2008, 2009 Novell, Inc.
9//
10// This program is free software; you can redistribute it and/or
11// modify it under the terms of version 2 of the Lesser GNU General
12// Public License as published by the Free Software Foundation.
13//
14// This program is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17// Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public
20// License along with this program; if not, write to the
21// Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22// Boston, MA 02111-1307, USA.
23
24[System.Serializable]
25public class HandlerNotFoundException : SystemException
26{
27	string handler_name;
28	string signal_name;
29	System.Reflection.EventInfo evnt;
30	Type delegate_type;
31
32	public HandlerNotFoundException (string handler_name, string signal_name,
33					 System.Reflection.EventInfo evnt, Type delegate_type)
34		: this (handler_name, signal_name, evnt, delegate_type, null)
35	{
36	}
37
38	public HandlerNotFoundException (string handler_name, string signal_name,
39					 System.Reflection.EventInfo evnt, Type delegate_type, Exception inner)
40		: base ("No handler " + handler_name + " found for signal " + signal_name,
41			inner)
42	{
43		this.handler_name = handler_name;
44		this.signal_name = signal_name;
45		this.evnt = evnt;
46		this.delegate_type = delegate_type;
47	}
48
49	public HandlerNotFoundException (string message, string handler_name, string signal_name,
50					 System.Reflection.EventInfo evnt, Type delegate_type)
51		: base ((message != null) ? message : "No handler " + handler_name + " found for signal " + signal_name,
52			null)
53	{
54		this.handler_name = handler_name;
55		this.signal_name = signal_name;
56		this.evnt = evnt;
57		this.delegate_type = delegate_type;
58	}
59
60	protected HandlerNotFoundException (System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
61		: base (info, context)
62	{
63		handler_name = info.GetString ("HandlerName");
64		signal_name = info.GetString ("SignalName");
65		evnt = info.GetValue ("Event", typeof (System.Reflection.EventInfo)) as System.Reflection.EventInfo;
66		delegate_type = info.GetValue ("DelegateType", typeof (Type)) as Type;
67	}
68
69	public string HandlerName
70	{
71		get {
72			return handler_name;
73		}
74	}
75
76	public string SignalName
77	{
78		get {
79			return signal_name;
80		}
81	}
82
83	public System.Reflection.EventInfo Event
84	{
85		get {
86			return evnt;
87		}
88	}
89
90	public Type DelegateType
91	{
92		get {
93			return delegate_type;
94		}
95	}
96
97	public override void GetObjectData (System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
98	{
99		base.GetObjectData (info, context);
100		info.AddValue ("HandlerName", handler_name);
101		info.AddValue ("SignalName", signal_name);
102		info.AddValue ("Event", evnt);
103		info.AddValue ("DelegateType", delegate_type);
104	}
105}
106
107
108[AttributeUsage (AttributeTargets.Field)]
109public class ObjectAttribute : Attribute
110{
111	private string name;
112	private bool specified;
113
114	public ObjectAttribute (string name)
115	{
116		specified = true;
117		this.name = name;
118	}
119
120	public ObjectAttribute ()
121	{
122		specified = false;
123	}
124
125	public string Name
126	{
127		get { return name; }
128	}
129
130	public bool Specified
131	{
132		get { return specified; }
133	}
134}
135
136public IntPtr GetRawObject(string name) {
137	IntPtr native_name = GLib.Marshaller.StringToPtrGStrdup (name);
138	IntPtr raw_ret = gtk_builder_get_object(Handle, native_name);
139	GLib.Marshaller.Free (native_name);
140	return raw_ret;
141}
142
143public Builder (System.IO.Stream s) : this (s, null)
144{
145}
146
147public Builder (System.IO.Stream s, string translation_domain)
148{
149	if (s == null)
150		throw new ArgumentNullException ("s");
151
152	int size = (int) s.Length;
153	byte[] buffer = new byte[size];
154	s.Read (buffer, 0, size);
155	s.Close ();
156
157	AddFromString(System.Text.Encoding.UTF8.GetString (buffer));
158
159	TranslationDomain = translation_domain;
160}
161
162public Builder (string resource_name) : this (resource_name, null)
163{
164}
165
166public Builder (string resource_name, string translation_domain) : this (System.Reflection.Assembly.GetEntryAssembly (), resource_name, translation_domain)
167{
168}
169
170public Builder (System.Reflection.Assembly assembly, string resource_name, string translation_domain) : this ()
171{
172	if (GetType() != typeof (Builder))
173		throw new InvalidOperationException ("Cannot chain to this constructor from subclasses.");
174
175	if (assembly == null)
176		assembly = System.Reflection.Assembly.GetCallingAssembly ();
177
178	System.IO.Stream s = assembly.GetManifestResourceStream (resource_name);
179	if (s == null)
180		throw new ArgumentException ("Cannot get resource file '" + resource_name + "'",
181					     "resource_name");
182
183	int size = (int) s.Length;
184	byte[] buffer = new byte[size];
185	s.Read (buffer, 0, size);
186	s.Close ();
187
188	AddFromString(System.Text.Encoding.UTF8.GetString (buffer));
189
190	TranslationDomain = translation_domain;
191}
192
193public void Autoconnect (object handler)
194{
195	BindFields (handler);
196	(new SignalConnector (this, handler)).ConnectSignals ();
197}
198
199public void Autoconnect (Type handler_class)
200{
201	BindFields (handler_class);
202	(new SignalConnector (this, handler_class)).ConnectSignals ();
203}
204
205class SignalConnector
206{
207	Builder builder;
208	Type handler_type;
209	object handler;
210
211	public SignalConnector (Builder builder, object handler)
212	{
213		this.builder = builder;
214		this.handler = handler;
215		handler_type = handler.GetType ();
216	}
217
218	public SignalConnector (Builder builder, Type handler_type)
219	{
220		this.builder = builder;
221		this.handler = null;
222		this.handler_type = handler_type;
223	}
224
225	[DllImport("libgtk-win32-2.0-0.dll")]
226	static extern void gtk_builder_connect_signals_full(IntPtr raw, GtkBeansSharp.BuilderConnectFuncNative func, IntPtr user_data);
227
228	public void ConnectSignals() {
229		GtkBeansSharp.BuilderConnectFuncWrapper func_wrapper = new GtkBeansSharp.BuilderConnectFuncWrapper (new GtkBeans.BuilderConnectFunc (ConnectFunc));
230		gtk_builder_connect_signals_full(builder.Handle, func_wrapper.NativeDelegate, IntPtr.Zero);
231	}
232
233	public void ConnectFunc (Builder builder, GLib.Object objekt, string signal_name, string handler_name, GLib.Object connect_object, GLib.ConnectFlags flags)
234	{
235		/* search for the event to connect */
236		System.Reflection.MemberInfo[] evnts = objekt.GetType ().
237			FindMembers (System.Reflection.MemberTypes.Event,
238			     System.Reflection.BindingFlags.Instance
239			     | System.Reflection.BindingFlags.Static
240			     | System.Reflection.BindingFlags.Public
241			     | System.Reflection.BindingFlags.NonPublic,
242			     new System.Reflection.MemberFilter (SignalFilter), signal_name);
243		foreach (System.Reflection.EventInfo ei in evnts) {
244			bool connected = false;
245			System.Reflection.MethodInfo add = ei.GetAddMethod ();
246			System.Reflection.ParameterInfo[] addpi = add.GetParameters ();
247			if (addpi.Length == 1) { /* this should be always true, unless there's something broken */
248				Type delegate_type = addpi[0].ParameterType;
249
250				/* look for an instance method */
251				if (connect_object != null || handler != null)
252					try {
253						Delegate d = Delegate.CreateDelegate (delegate_type, connect_object != null ? connect_object : handler, handler_name);
254						add.Invoke (objekt, new object[] { d } );
255						connected = true;
256					} catch (ArgumentException) { /* ignore if there is not such instance method */
257					}
258
259				/* look for a static method if no instance method has been found */
260				if (!connected && handler_type != null)
261					try  {
262						Delegate d = Delegate.CreateDelegate (delegate_type, handler_type, handler_name);
263						add.Invoke (objekt, new object[] { d } );
264						connected = true;
265					} catch (ArgumentException) { /* ignore if there is not such static method */
266					}
267
268				if (!connected) {
269					string msg = ExplainError (ei.Name, delegate_type, handler_type, handler_name);
270					throw new HandlerNotFoundException (msg, handler_name, signal_name, ei, delegate_type);
271				}
272			}
273		}
274	}
275
276	static bool SignalFilter (System.Reflection.MemberInfo m, object filterCriteria)
277	{
278		string signame = (filterCriteria as string);
279		object[] attrs = m.GetCustomAttributes (typeof (GLib.SignalAttribute), false);
280		if (attrs.Length > 0)
281		{
282			foreach (GLib.SignalAttribute a in attrs)
283			{
284				if (signame == a.CName)
285				{
286					return true;
287				}
288			}
289			return false;
290		}
291		else
292		{
293			/* this tries to match the names when no attibutes are present.
294			   It is only a fallback. */
295			signame = signame.ToLower ().Replace ("_", "");
296			string evname = m.Name.ToLower ();
297			return signame == evname;
298		}
299	}
300
301	static string GetSignature (System.Reflection.MethodInfo method)
302	{
303		if (method == null)
304			return null;
305
306		System.Reflection.ParameterInfo [] parameters = method.GetParameters ();
307		System.Text.StringBuilder sb = new System.Text.StringBuilder ();
308		sb.Append ('(');
309		foreach (System.Reflection.ParameterInfo info in parameters) {
310			sb.Append (info.ParameterType.ToString ());
311			sb.Append (',');
312		}
313		if (sb.Length != 0)
314			sb.Length--;
315
316		sb.Append (')');
317		return sb.ToString ();
318	}
319
320	static string GetSignature (Type delegate_type)
321	{
322		System.Reflection.MethodInfo method = delegate_type.GetMethod ("Invoke");
323		return GetSignature (method);
324	}
325
326	const System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.NonPublic |
327					System.Reflection.BindingFlags.Public |
328					System.Reflection.BindingFlags.Static |
329					System.Reflection.BindingFlags.Instance;
330	static string GetSignature (Type klass, string method_name)
331	{
332		try {
333			System.Reflection.MethodInfo method = klass.GetMethod (method_name, flags);
334			return GetSignature (method);
335		} catch {
336			// May be more than one method with that name and none matches
337			return null;
338		}
339	}
340
341
342	static string ExplainError (string event_name, Type deleg, Type klass, string method)
343	{
344		if (deleg == null || klass == null || method == null)
345			return null;
346
347		System.Text.StringBuilder sb = new System.Text.StringBuilder ();
348		string expected = GetSignature (deleg);
349		string actual = GetSignature (klass, method);
350		if (actual == null)
351			return null;
352			sb.AppendFormat ("The handler for the event {0} should take '{1}', " +
353				"but the signature of the provided handler ('{2}') is '{3}'\n",
354				event_name, expected, method, actual);
355		return sb.ToString ();
356	}
357
358}
359
360
361void BindFields (object target)
362{
363	BindFields (target, target.GetType ());
364}
365
366void BindFields (Type type)
367{
368	BindFields (null, type);
369}
370
371void BindFields (object target, Type type)
372{
373	System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly;
374	if (target != null)
375		flags |= System.Reflection.BindingFlags.Instance;
376	else
377		flags |= System.Reflection.BindingFlags.Static;
378
379	do {
380		System.Reflection.FieldInfo[] fields = type.GetFields (flags);
381		if (fields == null)
382			return;
383
384		foreach (System.Reflection.FieldInfo field in fields)
385		{
386			object[] attrs = field.GetCustomAttributes (typeof (ObjectAttribute), false);
387			if (attrs == null || attrs.Length == 0)
388				continue;
389			// The widget to field binding must be 1:1, so only check
390			// the first attribute.
391			ObjectAttribute attr = (ObjectAttribute) attrs[0];
392			GLib.Object gobject;
393			if (attr.Specified)
394				gobject = GetObject (attr.Name);
395			else
396				gobject = GetObject (field.Name);
397
398			if (gobject != null)
399				try {
400					field.SetValue (target, gobject, flags, null, null);
401				} catch (Exception e) {
402					Console.WriteLine ("Unable to set value for field " + field.Name);
403					throw e;
404				}
405		}
406		type = type.BaseType;
407	}
408	while (type != typeof(object) && type != null);
409}
410