Gtk text editor (tutorial) - vilinski/nemerle GitHub Wiki

Table of Contents

Simple text editor in GTK#

In this tutorial you will see step-by-step how to build a simple text editor in Nemerle using Gtk#. Gtk# is a .Net binding for the GTK+ GUI toolkit. It is an alternative to Windows.Forms.

The final appearence of our application will be something like:

Image:Vsedit.png

Compiling examples

In order to compile and run examples presented below, you need to install Gtk#. Assuming you have pkg-config installed, you can compile gtk# examples using

  ncc example.n -pkg:gtk-sharp -out:example.exe

Otherwise you will need to copy gtk-sharp.dll to the current directory and use

  ncc example.n -r:gtk-sharp.dll -out:example.exe

Part I creating a window

First we must create an application and display a window, which will be the base of our editor:

using System;
using Gtk;

class MainWindow : Window
{
	public this()
	{
                // set caption of window
		base ("Very Simple Editor");
                // resize windows to some reasonable shape
		SetSizeRequest (300,200);
	}
}

module SimpleEditor
{ 
	Main() : void
	{
		Application.Init();
		def win = MainWindow();

                // exit application when editor window is deleted
                win.DeleteEvent += fun (_) { Application.Quit () };

                win.ShowAll ();
		Application.Run();
	}
}	

The MainWindow class is quite simple now, but we will add things to it in a moment.

SimpleEditor is a module with one method, Main - the entry point of our application. It is executed first when the program is run and creates the editor window.

One more thing to note is win.DeleteEvent, which is a signal triggered when the window is destroyed (for example user clicks close). We specify what should happen by attaching an anonymous function to the event. Here, we will simply quit the application.

Part II adding text input section

Now we need to add an area to the window where actual text can be written by the user.

Modify the MainWindow class to hold the TextView object:

class MainWindow : Window
{
        /// text input area of our window
	input : TextView;

	public this()
	{
                // set caption of window
		base ("Very Simple Editor");
                // resize windows to some reasonable shape
		SetSizeRequest (300,200);

		def scroll = ScrolledWindow ();		
		input = TextView ();
		scroll.Add (input);
		
                // place scrolledwin inside our main window
           	Add (scroll);
	}
}

In our window's constructor we must choose how the text input will be positioned. We use a Gtk container to encapsulate TextView into ScrolledWindow. This allows text in the text view to be easily viewed using scroll bars.

Add (scroll) adds the ScrolledWindow as part of our text editor window. So, now we have created working text input and viewing.

Part III a simple menu

But we still want to perform some more advanced operations in our editor. Ha, we will even need a menu! We will create a menu and put it into a vertical box. Instead of adding scrolling to the main window, put it into the box as well:

def menu = MenuBar ();
def mi = NMenuItem ("File");
menu.Append(mi);		
	
def vbox = VBox ();
vbox.PackStart (menu, false, false, 0u);
vbox.PackStart (scroll, true, true, 0u);

// place vertical box inside our main window
Add (vbox);

In the last instruction we add the entire vertical box to the main window, above the text view.

NMenuItem, defined below, is the next class we will work with. We will extend it and add some actions to it. Here is the initial code:

class NMenuItem : MenuItem
{
	public name : string;
	
	public this(l : string) 
	{
		base(l);
		name = l;		
	}
}

For now the constructor only sets the caption of the base MenuItem.

Part IV adding submenus

We want our only menu item, File, to have some submenus for the tasks available in the editor.

Add a property to NMenuItem, which allows easy adding of child menus:

// class NMenuItem : MenuItem 
// { 

// this property allows us to set submenus of this menu item
public SubmenuList : list [NMenuItem]
{ 
  set
  { 
    def sub = Menu();
    foreach (submenu in value) sub.Append (submenu);
    this.Submenu = sub;
  }
}

// }

SubmenuList simply iterates through the elements of the supplied list and adds each of them to the current instance's submenu.

Use this property in the constructor of MainWindow:

// def menu = MenuBar ();
// def mi = NMenuItem ("File");
mi.SubmenuList = 
[
  NMenuItem ("Open"),
  NMenuItem ("Save as...")
];
// menu.Append(mi);		

Note the commented out lines. We will uncomment these later.

Part V adding tasks to the submenus

Finally, we will attach actions to the items in our editor's menu. We will implement opening and saving files.

Add a second constructor to NMenuItem, which will accept an action function to be perfomed after choosing the given menu item:

public this(l : string, e : object * EventArgs -> void) 
{
  base(l);
  name = l;		
  this.Activated += e;
}

Like the previous one, this constructor sets the current menu item's caption, but it also attaches the given function to the Activated event of the item.

Now we will define a function which will handle opening and saving files by our editor. Add it to MainWindow:

// handler of opening and saving files
OnMenuFile (i : object, _ : EventArgs) : void
{
  def mi = i :> NMenuItem;
  def fs = FileSelection (mi.name);
		
  when (fs.Run () == ResponseType.Ok :> int) match (mi.name)
  {
    | "Open" =>
       def stream = IO.StreamReader (fs.Filename);
       input.Buffer.Text = stream.ReadToEnd();

    | "Save as..." =>
       def s = IO.StreamWriter(fs.Filename);
       s.Write(input.Buffer.Text);
       s.Close();

    | _ => ();
  };
  fs.Hide();
}

This method creates a window for selecting files (Gtk FileSelection), checks which menu item it was called from, and takes the appropriate action. Here we match on the name of NMenuItem, but you could also use something like enums to distinguish various menu items (or attach a specific handler to each of them).

Actions performed for opening and saving files are quite simple - we use Buffer.Text properties of TextView to get and set the contents of the editor.

The last thing to do is actually create the menu items using handlers. Uncomment the lines we entered in the earlier step to read:

def menu = MenuBar ();
def mi = NMenuItem ("File");
mi.SubmenuList = 
[
   NMenuItem ("Open", OnMenuFile),
   NMenuItem ("Save as...", OnMenuFile)
];
menu.Append(mi);	

Result

The result of these steps can be accessed here and it looks like this:

using System;
using Gtk;

class NMenuItem : MenuItem
{
	public name : string;
	
	public this(l : string) 
	{
		base(l);
		name = l;		
	}

	public this(l : string, e : object * EventArgs -> void) 
	{
		base(l);
		name = l;		
		this.Activated += e;
	}


        // this property allows us to set submenus of this menu item
	public SubmenuList : list [NMenuItem]
	{ 
		set
		{ 
			def sub = Menu();
                        foreach (submenu in value) sub.Append (submenu);
			this.Submenu = sub;
		}
	}
}


class MainWindow : Window
{
        /// text input area of our window
	input : TextView;

	public this()
	{
                // set caption of window
		base ("Very Simple Editor");
                // resize windows to some reasonable shape
		SetSizeRequest (300,200);

		def scroll = ScrolledWindow ();		
		input = TextView ();
		scroll.Add (input);

		def menu = MenuBar ();
		def mi = NMenuItem ("File");
		mi.SubmenuList = 
		[
			NMenuItem ("Open", OnMenuFile),
			NMenuItem ("Save as...", OnMenuFile)
		];
		menu.Append(mi);		
		
		def vbox = VBox ();
		vbox.PackStart (menu, false, false, 0u);
	        vbox.PackStart (scroll, true, true, 0u);

		// place vertical box inside our main window
		Add (vbox);
	}

        // handler of opening and saving files
	OnMenuFile (i : object, _ : EventArgs) : void
	{
		def mi = i :> NMenuItem;
		def fs = FileSelection (mi.name);
		
		when (fs.Run () == ResponseType.Ok :> int) match (mi.name)
		{
			| "Open" =>
                                def stream = IO.StreamReader (fs.Filename);
				input.Buffer.Text = stream.ReadToEnd();

			| "Save as..." =>
				def s = IO.StreamWriter(fs.Filename);
				s.Write(input.Buffer.Text);
				s.Close();
			| _ => ();
		};
		fs.Hide();
	}
}

module SimpleEditor
{ 
	Main() : void
	{
		Application.Init();
		def win = MainWindow();

                // exit application when editor window is deleted
                win.DeleteEvent += fun (_) { Application.Quit () };

                win.ShowAll ();
		Application.Run();
	}
}	
⚠️ **GitHub.com Fallback** ⚠️