[Gtk-sharp-list] autoconnection of signals in glade

Ricardo Fernández Pascual rfp1@ono.com
05 Sep 2002 20:01:48 +0200


--=-vFg/gAVfiMaQdvA+MMS4
Content-Type: text/plain; charset=ISO-8859-15
Content-Transfer-Encoding: quoted-printable


Hello,
    After learning enough about reflection, I have implemented signal
autoconnection for libglade. Attached is the patch and added files of
this implementation. Look at the test program to see how easy is to use
this.

    Implementing this I have found some issues:=20
    - Mapping from glib signal names (like "enter") to Gtk# event names
    (like "Entered"). To solve this, I have had to modify Gtk# code
    generation to add a custom attribute to the generated events. The
    attribute marks all events generated from glib signals, and stores
    the original name (cname). This is the cleaner way that I could
    think of doing this.
    - I have no idea of what to do with the "connect_after" option of
    signals in glade. Can this be implemented using Gtk#?
    - I have implemented the case when the handler object is specified
    in the glade file, but I think it is totally useless because
    signatures of handler methods have to match exactly (unlike in C).
    - You can use both static and instance methods as handlers, however
    see bugs #29807 (just fixed ;) and #29871.
    - I'm not sure of what to do when no signal handler is found. Should
    this throw an exception? For now it just shows a message...

Comments are very welcome.

Cheers,
Ricardo

--=20
Ricardo Fern=E1ndez Pascual
ric@users.sourceforge.net
Murcia. Espa=F1a.

--=-vFg/gAVfiMaQdvA+MMS4
Content-Description: Test program
Content-Disposition: inline; filename=GladeTest.cs
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=ISO-8859-15

// GladeViewer.cs - Tests for LibGlade in C#
//
// Author: Ricardo Fern=E1ndez Pascual <ric@users.sourceforge.net>
//
// (c) 2002 Ricardo Fern=E1ndez Pascual

namespace GladeSamples {
	using System;
=09
	using Gtk;
	using Gnome;
	using Glade;
	using GtkSharp;

	public class GladeTest : Program
	{
		public static void Main (string[] args)
		{
			new GladeTest (args).Run ();
		}
		public GladeTest (string[] args, params object[] props)=20
			: base ("GladeTest", "0.1", Modules.UI, args, props)
		{
			Glade.XML gxml =3D new Glade.XML ("test.glade", "main_window", null);
			gxml.Autoconnect (this);
		}

		void OnWindowDeleteEvent (object o, DeleteEventArgs args)=20
		{
			Quit ();
			args.RetVal =3D true;
		}
	=09
		private void OnButton1Clicked (Object b, EventArgs e)=20
		{
			Console.WriteLine ("Button 1 clicked");
		}

		public static void OnButton2Clicked (Object b, EventArgs e)=20
		{
			Console.WriteLine ("Button 2 clicked");
		}
	=09
		void OnButton2Entered (Object b, EventArgs e)=20
		{
			Console.WriteLine ("Button 2 entered");
		}
	}
}


--=-vFg/gAVfiMaQdvA+MMS4
Content-Description: Glade file for the test program
Content-Type: application/x-glade
Content-Disposition: attachment; filename=test.glade
Content-Transfer-Encoding: base64

PD94bWwgdmVyc2lvbj0iMS4wIiBzdGFuZGFsb25lPSJubyI/PiA8IS0tKi0gbW9kZTogeG1sIC0q
LS0+CjwhRE9DVFlQRSBnbGFkZS1pbnRlcmZhY2UgU1lTVEVNICJodHRwOi8vZ2xhZGUuZ25vbWUu
b3JnL2dsYWRlLTIuMC5kdGQiPgoKPGdsYWRlLWludGVyZmFjZT4KCjx3aWRnZXQgY2xhc3M9Ikd0
a1dpbmRvdyIgaWQ9Im1haW5fd2luZG93Ij4KICA8cHJvcGVydHkgbmFtZT0idmlzaWJsZSI+VHJ1
ZTwvcHJvcGVydHk+CiAgPHByb3BlcnR5IG5hbWU9InRpdGxlIiB0cmFuc2xhdGFibGU9InllcyI+
R2xhZGUjIHRlc3Q8L3Byb3BlcnR5PgogIDxwcm9wZXJ0eSBuYW1lPSJ0eXBlIj5HVEtfV0lORE9X
X1RPUExFVkVMPC9wcm9wZXJ0eT4KICA8cHJvcGVydHkgbmFtZT0id2luZG93X3Bvc2l0aW9uIj5H
VEtfV0lOX1BPU19OT05FPC9wcm9wZXJ0eT4KICA8cHJvcGVydHkgbmFtZT0ibW9kYWwiPkZhbHNl
PC9wcm9wZXJ0eT4KICA8cHJvcGVydHkgbmFtZT0icmVzaXphYmxlIj5UcnVlPC9wcm9wZXJ0eT4K
ICA8cHJvcGVydHkgbmFtZT0iZGVzdHJveV93aXRoX3BhcmVudCI+RmFsc2U8L3Byb3BlcnR5Pgog
IDxzaWduYWwgbmFtZT0iZGVsZXRlX2V2ZW50IiBoYW5kbGVyPSJPbldpbmRvd0RlbGV0ZUV2ZW50
IiBsYXN0X21vZGlmaWNhdGlvbl90aW1lPSJUdWUsIDAzIFNlcCAyMDAyIDE0OjQ3OjU3IEdNVCIv
PgoKICA8Y2hpbGQ+CiAgICA8d2lkZ2V0IGNsYXNzPSJHdGtWQm94IiBpZD0idmJveDEiPgogICAg
ICA8cHJvcGVydHkgbmFtZT0idmlzaWJsZSI+VHJ1ZTwvcHJvcGVydHk+CiAgICAgIDxwcm9wZXJ0
eSBuYW1lPSJob21vZ2VuZW91cyI+RmFsc2U8L3Byb3BlcnR5PgogICAgICA8cHJvcGVydHkgbmFt
ZT0ic3BhY2luZyI+MDwvcHJvcGVydHk+CgogICAgICA8Y2hpbGQ+Cgk8d2lkZ2V0IGNsYXNzPSJH
dGtCdXR0b24iIGlkPSJidXR0b24xIj4KCSAgPHByb3BlcnR5IG5hbWU9InZpc2libGUiPlRydWU8
L3Byb3BlcnR5PgoJICA8cHJvcGVydHkgbmFtZT0iY2FuX2ZvY3VzIj5UcnVlPC9wcm9wZXJ0eT4K
CSAgPHByb3BlcnR5IG5hbWU9ImxhYmVsIiB0cmFuc2xhdGFibGU9InllcyI+Q2xpY2sgaGVyZTwv
cHJvcGVydHk+CgkgIDxwcm9wZXJ0eSBuYW1lPSJ1c2VfdW5kZXJsaW5lIj5UcnVlPC9wcm9wZXJ0
eT4KCSAgPHByb3BlcnR5IG5hbWU9InJlbGllZiI+R1RLX1JFTElFRl9OT1JNQUw8L3Byb3BlcnR5
PgoJICA8c2lnbmFsIG5hbWU9ImNsaWNrZWQiIGhhbmRsZXI9Ik9uQnV0dG9uMUNsaWNrZWQiIGxh
c3RfbW9kaWZpY2F0aW9uX3RpbWU9IlR1ZSwgMTMgQXVnIDIwMDIgMTU6MjQ6MzUgR01UIi8+Cgk8
L3dpZGdldD4KCTxwYWNraW5nPgoJICA8cHJvcGVydHkgbmFtZT0icGFkZGluZyI+MDwvcHJvcGVy
dHk+CgkgIDxwcm9wZXJ0eSBuYW1lPSJleHBhbmQiPkZhbHNlPC9wcm9wZXJ0eT4KCSAgPHByb3Bl
cnR5IG5hbWU9ImZpbGwiPkZhbHNlPC9wcm9wZXJ0eT4KCTwvcGFja2luZz4KICAgICAgPC9jaGls
ZD4KCiAgICAgIDxjaGlsZD4KCTx3aWRnZXQgY2xhc3M9Ikd0a0J1dHRvbiIgaWQ9ImJ1dHRvbjIi
PgoJICA8cHJvcGVydHkgbmFtZT0idmlzaWJsZSI+VHJ1ZTwvcHJvcGVydHk+CgkgIDxwcm9wZXJ0
eSBuYW1lPSJjYW5fZm9jdXMiPlRydWU8L3Byb3BlcnR5PgoJICA8cHJvcGVydHkgbmFtZT0ibGFi
ZWwiIHRyYW5zbGF0YWJsZT0ieWVzIj5Eb24ndCBjbGljayBoZXJlPC9wcm9wZXJ0eT4KCSAgPHBy
b3BlcnR5IG5hbWU9InVzZV91bmRlcmxpbmUiPlRydWU8L3Byb3BlcnR5PgoJICA8cHJvcGVydHkg
bmFtZT0icmVsaWVmIj5HVEtfUkVMSUVGX05PUk1BTDwvcHJvcGVydHk+CgkgIDxzaWduYWwgbmFt
ZT0iY2xpY2tlZCIgaGFuZGxlcj0iT25CdXR0b24yQ2xpY2tlZCIgbGFzdF9tb2RpZmljYXRpb25f
dGltZT0iVHVlLCAxMyBBdWcgMjAwMiAxNToyNDozNSBHTVQiLz4KCSAgPHNpZ25hbCBuYW1lPSJl
bnRlciIgaGFuZGxlcj0iT25CdXR0b24yRW50ZXJlZCIgbGFzdF9tb2RpZmljYXRpb25fdGltZT0i
V2VkLCAwNCBTZXAgMjAwMiAxNDowNDo0MCBHTVQiLz4KCTwvd2lkZ2V0PgoJPHBhY2tpbmc+Cgkg
IDxwcm9wZXJ0eSBuYW1lPSJwYWRkaW5nIj4wPC9wcm9wZXJ0eT4KCSAgPHByb3BlcnR5IG5hbWU9
ImV4cGFuZCI+RmFsc2U8L3Byb3BlcnR5PgoJICA8cHJvcGVydHkgbmFtZT0iZmlsbCI+RmFsc2U8
L3Byb3BlcnR5PgoJPC9wYWNraW5nPgogICAgICA8L2NoaWxkPgogICAgPC93aWRnZXQ+CiAgPC9j
aGlsZD4KPC93aWRnZXQ+Cgo8L2dsYWRlLWludGVyZmFjZT4K

--=-vFg/gAVfiMaQdvA+MMS4
Content-Description: The new attribute
Content-Disposition: inline; filename=SignalAttribute.cs
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=ISO-8859-15

//
// SignalAttribute.cs
//
// Author:
//   Ricardo Fern=E1ndez Pascual <ric@users.sourceforge.net>
//
// (C) Ximian, Inc.  http://www.ximian.com
//

namespace GLib {

	using System;

	/// <summary>
	///   Marks events genrated from glib signals
	/// </summary>
	///
	/// <remarks>
	///   This attribute indentifies events generated from glib signals=20
	///   and allows obtaining its original name.
	/// </remarks>
	[Serializable]
	public class SignalAttribute : Attribute=20
	{
		private string cname;

		public SignalAttribute (string cname)
		{
			this.cname =3D cname;
		}

		private SignalAttribute () {}

		public string CName=20
		{
			get {
				return cname;
			}
		}
	}
}

--=-vFg/gAVfiMaQdvA+MMS4
Content-Description: The patch
Content-Disposition: inline; filename=ac.diff
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset=ISO-8859-15

? ac.diff
? params.diff
? api/Makefile
? api/generated-stamp
? glib/SignalAttribute.cs
? parser/Makefile
? parser/gapi_format_xml
? sample/61
? sample/Debugger.exe.config
? sample/GladeTest.cs
? sample/test.glade
? sample/test.gladep
Index: generator/Signal.cs
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
RCS file: /mono/gtk-sharp/generator/Signal.cs,v
retrieving revision 1.7
diff -u -r1.7 Signal.cs
--- generator/Signal.cs	20 Aug 2002 19:56:14 -0000	1.7
+++ generator/Signal.cs	5 Sep 2002 17:41:47 -0000
@@ -152,6 +152,7 @@
 			string argsname;=0D
 			string handler =3D GenHandler (out argsname);=0D
=20=0D
+			sw.WriteLine("\t\t[GLib.Signal("+ cname + ")]");=0D
 			sw.Write("\t\tpublic ");=0D
 			if (elem.HasAttribute("new_flag") || (container_type !=3D null && conta=
iner_type.GetSignalRecursively (Name) !=3D null) || (implementor !=3D null =
&& implementor.GetSignalRecursively (Name) !=3D null))=0D
 				sw.Write("new ");=0D
Index: glade/XML.custom
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
RCS file: /mono/gtk-sharp/glade/XML.custom,v
retrieving revision 1.1
diff -u -r1.1 XML.custom
--- glade/XML.custom	12 Aug 2002 19:14:43 -0000	1.1
+++ glade/XML.custom	5 Sep 2002 17:41:48 -0000
@@ -45,4 +45,156 @@
 			return ret;
 		}
=20
+		/* signal autoconnection using reflection */
+
+		/// <summary>Automatically connect signals</summary>
+		/// <remarks>Connects the signals defined in the glade file with handler=
 methods
+		///     provided by the given object.</remarks>
+		public void Autoconnect (object handler)
+		{
+			SignalConnector sc =3D new SignalConnector (this, handler);
+			sc.Autoconnect ();
+		}
 	=09
+		/// <summary>Automatically connect signals</summary>
+		/// <remarks>Connects the signals defined in the glade file with static =
handler=20
+		///     methods provided by the given type.</remarks>
+		public void Autoconnect (Type handler_class)
+		{
+			SignalConnector sc =3D new SignalConnector (this, handler_class);
+			sc.Autoconnect ();
+		}
+
+		class SignalConnector=20
+		{
+			/* the Glade.XML object whose signals we want to connect */
+			XML gxml;
+=09
+			/* the object to look for handlers */
+			object handler_object;
+=09
+			/* the type to look for handlers if no object has been specified */
+			Type handler_type;
+=09
+			public SignalConnector (XML gxml, object handler)=20
+			{
+				this.gxml =3D gxml;
+				this.handler_object =3D handler;
+				this.handler_type =3D handler.GetType ();
+			}
+=09
+			public SignalConnector (XML gxml, Type type)
+			{
+				this.gxml =3D gxml;
+				this.handler_object =3D null;
+				this.handler_type =3D type;
+			}
+		=09
+			delegate void RawXMLConnectFunc (string handler_name, IntPtr objekt,=20
+						         string signal_name, string signal_data,=20
+						         IntPtr connect_object, int after, IntPtr user_data);
+		=09
+			[DllImport("glade-2.0")]
+			static extern void glade_xml_signal_autoconnect_full (IntPtr raw, RawXM=
LConnectFunc func,
+									      IntPtr user_data);
+		=09
+			public void Autoconnect () {
+				RawXMLConnectFunc cf =3D new RawXMLConnectFunc (ConnectFunc);
+				glade_xml_signal_autoconnect_full (gxml.Handle, cf, IntPtr.Zero);
+			}
+		=09
+			void ConnectFunc (string handler_name, IntPtr objekt_ptr,=20
+					  string signal_name, string signal_data,=20
+					  IntPtr connect_object_ptr, int after, IntPtr user_data) {
+			=09
+				GLib.Object objekt =3D GLib.Object.GetObject (objekt_ptr);
+=09
+				/* if an connect_object_ptr is provided, use that as handler */
+				object connect_object =3D=20
+					connect_object_ptr =3D=3D IntPtr.Zero=20
+						? handler_object
+						: GLib.Object.GetObject (connect_object_ptr);
+			=09
+				/* search for the event to connect */
+				System.Reflection.MemberInfo[] evnts =3D objekt.GetType ().
+					FindMembers (System.Reflection.MemberTypes.Event,=20
+					     System.Reflection.BindingFlags.Instance=20
+					     | System.Reflection.BindingFlags.Static
+					     | System.Reflection.BindingFlags.Public=20
+					     | System.Reflection.BindingFlags.NonPublic,=20
+					     signalFilter, signal_name);
+				foreach (System.Reflection.EventInfo ei in evnts)=20
+				{
+					bool connected =3D false;
+					System.Reflection.MethodInfo add =3D ei.GetAddMethod ();
+					System.Reflection.ParameterInfo[] addpi =3D add.GetParameters ();
+					if (addpi.Length =3D=3D 1)=20
+					{ /* this should be always true, unless there's something broken */
+						Type delegate_type =3D addpi[0].ParameterType;
+=09
+						/* look for an instance method */
+						if (connect_object !=3D null) try=20
+						{
+							Delegate d =3D Delegate.CreateDelegate=20
+								(delegate_type, connect_object, handler_name);
+							add.Invoke (objekt, new object[] { d } );
+							connected =3D true;
+						}=20
+						catch (ArgumentException)=20
+						{
+							/* ignore if there is not such instance method */
+						}
+					=09
+						/* look for a static method if no instance method has been found */
+						if (!connected && handler_type !=3D null) try=20
+						{
+							Delegate d =3D Delegate.CreateDelegate=20
+								(delegate_type, handler_type, handler_name);
+							add.Invoke (null, new object[] { d } );
+							connected =3D true;
+						}=20
+						catch (ArgumentException)=20
+						{
+							/* ignore if there is not such static method */
+						}
+=09
+						if (!connected)=20
+						{
+							/* throw an exception? */
+							Console.WriteLine=20
+								("No handler {0} of type {1} found for signal {2} (event {3})",=20
+								 handler_name, delegate_type, signal_name, ei.Name);
+						}
+					}
+				}
+=09
+			}
+=09
+			System.Reflection.MemberFilter signalFilter =3D new System.Reflection.M=
emberFilter (SignalFilter);
+		=09
+			/* matches events to GLib signal names */
+			static bool SignalFilter (System.Reflection.MemberInfo m, object filter=
Criteria)=20
+			{
+				string signame =3D (filterCriteria as string);
+				object[] attrs =3D m.GetCustomAttributes (typeof (GLib.SignalAttribute=
), true);
+				if (attrs.Length > 0)
+				{
+					foreach (GLib.SignalAttribute a in attrs)
+					{
+						if (signame =3D=3D a.CName)
+						{
+							return true;
+						}
+					}
+					return false;
+				}
+				else
+				{
+					/* this tries to match the names when no attibutes are present.
+					   It is only a fallback. */
+					signame =3D signame.ToLower ().Replace ("_", "");
+					string evname =3D m.Name.ToLower ();
+					return signame =3D=3D evname;
+				}
+			}
+		}
Index: sample/Makefile.in
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=
=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D
RCS file: /mono/gtk-sharp/sample/Makefile.in,v
retrieving revision 1.16
diff -u -r1.16 Makefile.in
--- sample/Makefile.in	16 Aug 2002 05:00:31 -0000	1.16
+++ sample/Makefile.in	5 Sep 2002 17:41:49 -0000
@@ -6,7 +6,7 @@
=20
 @ENABLE_GLADE_TRUE@ GLADE_PATH=3D-L ../glade
 @ENABLE_GLADE_TRUE@ GLADE_ASSEMBLY=3D-r glade-sharp.dll
-@ENABLE_GLADE_TRUE@ GLADE_TARGETS=3Dglade-viewer.exe
+@ENABLE_GLADE_TRUE@ GLADE_TARGETS=3Dglade-viewer.exe glade-test.exe
=20
 local_paths=3D-L ../glib -L ../pango -L ../atk -L ../gdk -L ../gtk $(GNOME=
_PATH) $(GLADE_PATH)=20
 all_assemblies=3D-r glib-sharp.dll -r pango-sharp.dll -r atk-sharp.dll -r =
gdk-sharp.dll -r gtk-sharp.dll $(GNOME_ASSEMBLY) $(GLADE_ASSEMBLY) -r Syste=
m.Drawing
@@ -49,6 +49,9 @@
=20
 glade-viewer.exe: GladeViewer.cs
 	$(MCS) --unsafe -o glade-viewer.exe $(local_paths) $(all_assemblies) Glad=
eViewer.cs
+
+glade-test.exe: GladeTest.cs
+	$(MCS) --unsafe -o glade-test.exe $(local_paths) $(all_assemblies) GladeT=
est.cs
=20
 clean:
 	rm -f *.exe

--=-vFg/gAVfiMaQdvA+MMS4--