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