-
Notifications
You must be signed in to change notification settings - Fork 89
Gtk text editor (tutorial)
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:
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
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.
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.
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.
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.
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);
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();
}
}