abbra: (Default)
[personal profile] abbra
С подачи [livejournal.com profile] bugware я играюсь с Vala и построением интерфейсов на GTK+. Vala -- это такой продвинутый макротранслятор с C#-подобного по синтаксису языка на C с активным применением GObject. Все, что не очень хорошо читалось в коде на C при программировании GTK+, преображается в Vala.
Мы строим интерфейс программы в Glade, конвертируем его с помощью gtk-builder-convert в формат, который понимает класс Gtk.Builder из GTK+ 2.14 и старше, а затем динамически загружаем этот интерфейс в программу и используем его.

Сам по себе Vala -- полноценный язык, на нем можно просто писать приложения и без использования GTK+. В данном случае мне было интересно, а как сделать так, чтобы интерфейс рисовался в Glade, грузился во время исполнения, а обработчики сигналов к элементам интерфейса задавались бы в виде лямбда-функций, которые поддерживаются в Vala.

Естественно, можно автоматически связывать обработчики сигналов с загруженным интерфейсом через Gtk.Builder.connect_signals(), который просто берет имена обработчиков из описания интерфейса и ищет эти символы в загруженном приложении. Но хотелось разобраться с лямбдами.

Код приложения на Vala, в котором у нас в интерфейсе только две работающие команды: File->Open вызывает диалог выбора файла (и показ имени выбранного файла), а File->Quit выполняет выход из программы, выглядит так:
using GLib;
using Gtk;

public class App : Gtk.Builder {
	private Gtk.Window _main_window;

	public Gtk.Window window { get { return _main_window; }}

	public signal void open_file(string file_name);

	public void exit_with_message_on_error(string error_message) {
		Gtk.MessageDialog message = new 
			Gtk.MessageDialog(null, DialogFlags.MODAL,
					  MessageType.ERROR, ButtonsType.OK,
					  error_message);
		message.run();
		message.destroy();
		Gtk.main_quit();
		/* never reached */
	}

	public void connect_my_signals(Gtk.Builder builder, GLib.Object object, 
				   string signal_name, string handler_name, 
				   GLib.Object connect_object, GLib.ConnectFlags flags) {
		Gtk.Action action = object as Gtk.Action;
		
		if (null == action) {
			exit_with_message_on_error("Expected Gtk.Action!\n");
			return;
		}
		
		switch (signal_name) {
		case "activate": 
			switch (action.name) {
			case "File|Menu|Open":
				/* Define signal handler as lambda function */
				action.activate += (app) => {
					Gtk.FileChooserDialog fchooser = new 
					Gtk.FileChooserDialog("Open a file", (app as App).window, 
								  FileChooserAction.OPEN,
								  STOCK_CANCEL, ResponseType.CANCEL,
								  STOCK_OPEN, ResponseType.ACCEPT);
					int result = fchooser.run();
					fchooser.hide();
					if (result == ResponseType.ACCEPT) {
						open_file(fchooser.get_filename());
					}
					fchooser.destroy();
				};
				break;
			/* This is File|Quit in our sample UI */
			case "File|Menu|Quit":
				action.activate += Gtk.main_quit;
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
	}
	
	public void process_files() {
		try {
			add_from_file("myapp.gtk");
		} catch (GLib.Error er) {
			exit_with_message_on_error(er.message);
			return;
		}
		
		_main_window = get_object("main_window") as Gtk.Window;
		connect_signals_full(connect_my_signals);
		_main_window.destroy += Gtk.main_quit;
		_main_window.realize();
		_main_window.show_all();

		/* Process file opening after selection as lambda function */
		open_file += (app, file_name) => {
			Gtk.MessageDialog message = new 
			Gtk.MessageDialog(app.window, DialogFlags.MODAL, 
					  MessageType.INFO, ButtonsType.OK, file_name);
			message.run();
			message.destroy();
		};
		Gtk.main();
	}
	
	static int main (string[] args){
		Gtk.init(ref args);
		App app = new App();
		app.process_files();
		return 0;
	}

}


В App.connect_my_signals() мы используем switch для расстановки наших обработчиков сигналов. В Gtk.Builder предполагается, что мы по имени обработчика, прописанного в интерфейсе, можем определить какую функцию в нашем коде регистрировать как сигнал (если используем штатный Gtk.Builder.connect_signals(), который делает это автоматически).

В нашем случае мы хотим эти сигналы связать вручную (у лямбда-функций нет заранее известных имен). Для повышения читаемости исходного кода на Vala мы используем switch по имени сигнала и по имени виджета, к которому относится сигнал. Имена виджетов, как и имена обработчиков сигналов, мы задаем в Glade (или руками в файле описания интерфейса Gtk.Builder) заранее, поэтому можем сами себе гарантировать их уникальность. Поскольку по этим именам виджетов Gtk.Builder делает поиск ссылок между объектами при загрузке описания интерфейса, то имена попадают в специальный кэш, используемый функциями g_quark_from_string()/g_quark_from_string_static(). Туда же попадают и имена сигналов, которые регистрируют классы GObject при создании их в системе.

То есть, к моменту вызова App.connect_my_signals() у нас в кэше кварков уже есть все нужные строки и сравнение по ним будет довольно эффективным. Чего не скажешь об именах обработчиков сигналов, описанных в файле интерфейса: эти имена используем только мы и только в момент назначения сигналов.

В результате (можно посмотреть на сгенерированный Vala код на C), получается довольно эффективный код, который в своем первоначальном виде на Vala к тому же хорошо читаем человеком. А это, пожалуй, главное. Для автоматической регистрации пришлось бы только вместо connect_signals_full() вызывать connect_signals(self), но тогда бы лямбды не использовались. Реализацию оставлю вам. :-)

Код примера вместе с описанием интерфейса можно скачать здесь: http://boids.name/extract/myapp.tar.bz2
Собирается он так: valac -g --pkg glib-2.0 --pkg gtk+-2.0 myapp.vala, запускать надо myapp.

P.S. В Сизиф я Vala скоро соберу, текущая работа идет тут: http://git.altlinux.org/people/ab/packages/vala.git. Есть несколько нюансов, связанных с тем, что я хочу аккуратно запаковать все расширения, чтобы у них были правильные зависимости. Но это будет скоро сделано.
This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org

April 2016

S M T W T F S
     12
3456789
1011121314 1516
17181920212223
24252627282930

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 19th, 2025 02:38 am
Powered by Dreamwidth Studios