GTK+/Analiza interfejsu graficznego kalkulatora

Z Wikibooks, biblioteki wolnych podręczników.

Analizę tą zaczniemy od momentu, na którym skończyliśmy analizę ogólną. Jest to, również zgodne z kolejnością wykonywania się kodu, ponieważ zaczynamy od funkcji main().

	GtkWidget *tmp;
 
	gtk_init (&argc, &argv);
	// okno i jego podstawowe właściwości 
	kalkulator->window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 
	g_signal_connect (G_OBJECT (kalkulator->window), "delete_event",
			  G_CALLBACK (delete_event), NULL);
 
	g_signal_connect (G_OBJECT (kalkulator->window), "destroy",
			  G_CALLBACK (destroy), kalkulator);
 
	gtk_container_set_border_width (GTK_CONTAINER (kalkulator->window), 5);
	gtk_window_set_title (GTK_WINDOW (kalkulator->window), "kalkulator");
	gtk_window_set_resizable ( GTK_WINDOW (kalkulator->window), FALSE );

Do budowy całego kalkulatora wystarczy nam tylko jeden wskaźnik tmp typu GtkWidget. Inicjujemy GTK+ funkcją gtk_init(), Tworzymy główne okno kalkulatora - gtk_window_new(). Podłączamy dwa podstawowe sygnały delete_event klasy GtkWidget oraz destroy klasy GtkObject.
Teraz ustawiamy trzy właściwości naszego okna. Szerokość obramowania między kontrolkami a krawędzią okna na 5 używając funkcji gtk_container_set_border_width(). Ustawiamy tytuł okna na "kalkulator", funkcją gtk_window_set_title() oraz zabraniamy użytkownikowi zmiany wielkości okna przy pomocą funkcji gtk_window_set_resizable().
Wszystkie te funkcje, oprócz dwóch ostatnich zostały opisane w temacie "1.1 Struktura programu GTK+".

	// tabela
	/*
 	     0/0 - 1 - 2 - 3 - 4 - 5 - 6
 	     1 | _ | _ | _ | _ | _ | _ |
 	     2 | _ | _ | _ | _ | _ | _ |
 	     3 | _ | _ | _ | _ | _ | _ |
 	     4 | _ | _ | _ | _ | _ | _ |
 	*/
	kalkulator->table = gtk_table_new (4, 6, TRUE);
	gtk_container_add ( GTK_CONTAINER (kalkulator->window),
			    kalkulator->table);

Teraz tworzymy tabele, która będzie kontrolować rozmieszczenie kontrolek w głównym oknie. GtkTable w GTK+ przypomina tabele z HTML-a. Kontrolka może wypełniać więcej niz tylko jedną komórkę. Nasza tabela będzie miała rozmiar 4 wierszy i 6 kolumn. Pierwsze co robimy to tworzymy nową tabele przy pomocy funkcji gtk_table_new(). Przyjmuje ona trzy parametry: ilość wierszy, ilość kolumn oraz parametr zwany homogeneous odpowiadający za to czy wszystkie komórki w tabeli mają mieć taki sam rozmiar - dopasowany do największego widgetu znajdującego się w tabeli.

	// entry
	kalkulator->entry = gtk_entry_new ( );
	gtk_entry_set_alignment ( GTK_ENTRY( kalkulator->entry ), 1);
	gtk_entry_set_editable ( GTK_ENTRY( kalkulator->entry ), FALSE);
	// 0,6 - szerokość od 0 do 6
	// 0,1 - wysokość od 0 do 1
	gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
				    kalkulator->entry, 0, 6, 0, 1 );

Naszym wyświetlaczem będzie kontrolka GtkEntry. Tworzymy ją za pomocą funkcji gtk_entry_new(). Domyślnie we wszystkich kontrolkach tekstowych tekst wyświetlany jest po lewej stronie, natomiast z reguły w kalkulatorach po prawej. Dlatego zmieniamy tą właściwość funkcją gtk_entry_set_alignment(). Jej drugi parametr określa stronę wyrównania tekstu, 0 - lewa, 1 - prawa.
Nie chcemy też aby użytkownik mógł sam cokolwiek wpisywać do wyświetlacz. Jest to skuteczne zabezpieczenie - liczby będą miały prawidłowy format, bo określany tylko z klawiatury numerycznej kalkulatora. Aby wyłączyć możliwość edycji dla kontrolki GtkEntry należy funkcje gtk_entry_set_editable() wywołać z drugi parametr równym FALSE.
Stworzyliśmy już tabelę, została ona dodana do kontenera jakim jest nasze główne okno. Mamy też już kontrolkę reprezentującą wyświetlacz. Czas zapakować tę kontrolkę do tabeli. Jest to pierwszy raz i na dodatek nietypowy, ponieważ kontrolka GtkEntry będzie zajmowała cały pierwszy wiersz. Czyli będzie rozciągnięta na sześć kolumn. Do wstawiania kontrolek do tabeli służy funkcja gtk_table_attach_defaults(). Funkcja ta tak naprawdę wywołuje funkcję gtk_table_attach() ustawiając jej ostatnie cztery parametry na domyślne wartości. Oto zwięzły opis wszystkich parametrów:

  • table - to tabela, do której dodajemy elementy
  • widget - dodawany element
  • left_attach - lewa granica dla wstawianego widgetu
  • right_attach - prawe ograniczenie
  • top_attach - górne
  • bottom_attach - dolne

Parametry poniższe określamy tylko w funkcji gtk_table_attach(), gtk_table_attach_defaults() nadaje im domyślne wartości.

  • xoptions - określa w jaki sposób kontrolka ma zajmować przestrzeń komórki, w której się znajduje. Typ wyliczeniowy GtkAttachOptions definiuje 3 możliwe wartości:
    • GTK_FILL - kontrolka będzie wykorzystywać całą przydzieloną jej przestrzeń, które jest jej potrzebna.
    • GTK_EXPAND - jeżeli komórka jest za mała dla kontrolki, będzie się rozszerzać tak aby ją w sobie pomieścić.
    • GTK_SHRINK - kontrolka kurczy się podczas zmniejszania okna jak to tylko możliwe.
  • yoptions - tak samo jak opcja xoptions, dotyczy tylko ustawień względem osi Y.
  • xpadding - odstęp z prawej oraz lewej strony między kontrolką osadzona a krawędziami komórki tabeli
  • ypadding - odstęp z góry oraz dołu między kontrolką osadzona a krawędziami komórki tabeli

Najbardziej użyteczną wartością dla opcji xoptions oraz yoptions jest GTK_FILL. Można też używać kombinacji np. GTK_EXPAND | GTK_FILL. Właśnie taką kombinację dla tych parametrów wstawia funkcja gtk_table_attach_defaults(). natomiast dla wartości xpadding, ypadding nadaje wartość 0.

	// przyciski 1
	// 0,1 - szerokość od 0 do 1
	// 1,2 - wysokość od 1 do 2
	tmp = gtk_button_new_with_label ("1");
	g_signal_connect (G_OBJECT (tmp), "clicked",
		          G_CALLBACK (btn_1_clicked), kalkulator);
	gtk_table_attach_defaults ( GTK_TABLE (kalkulator->table),
				    tmp, 0, 1, 1, 2);

Ponieważ w naszej strukturze nie przechowujemy wskaźników do przycisków musimy wykorzystać dodatkowy wskaźnik. Do tworzenia wszystkich przycisków posłużymy się tylko jednym wskaźnikiem, któremu będziemy za każdym razem przypisywać nowo utworzony przycisk za pomocą funkcji gtk_button_new_with_label().
Dla każdego przycisku podłączamy funkcję zwrotną, która będzie wywoływana w reakcji na sygnał clicked. Do każdej z funkcji przekazujemy wskaźnik do naszej struktury danych.
W przypadku przycisków, każdy z nich zajmuje tylko jedną komórkę. Przycisk "1" zajmuję komórkę, której lewa krawędź to 0, prawa to 1 (czyli pierwsza kolumna), górna krawędź to 1 (czyli drugi wiersz, GtkEntry znajduje się powyżej, zajmuje pierwszy wiersz: 0-1), dolna krawędź to 2.

	gtk_widget_show_all (kalkulator->window);
 
	#ifdef DEBUG
	info(kalkulator,"przed gatk_main");
	#endif
	gtk_main ();
 
	return 0;

Funkcję main() kończymy wyświetleniem wszystkich stworzonych kontrolek. Można do tego użyć jednej funkcji gtk_widget_show_all(), która wyświetla daną kontrolkę oraz wszystkie inne potomne.

Po uruchomieniu programu nic się nie zacznie dziać, dopóki nie wciśniemy któregokolwiek z przycisków. Wtedy zostaną wywoływane odpowiednie funkcje zwrotne. Także cała obsługa działań jakie ma wykonywać program mieści się w tych funkcjach bądź jest z nich wywoływana. W każdym bądź razie funkcje te są swego rodzaju łącznikiem między interfejsem graficznym a docelowym przetwarzaniem danych przez program. Interfejs graficzny realizuje również funkcję pobierania tych danych od użytkownika oraz przedstawiania wyników operacji jakie zostały na nich wykonane. A nasza struktura danych Kalkulator pełni rolę przekaźnika tych danych między tak licznymi funkcjami zwrotnymi.
Oto wykaz wszystkich funkcji zwrotnych używanych w programie. Większość kodu w nich zawartych została opisana w temacie "2.1.1 Ogólna analiza kodu", resztę wyjaśnimy tu:

  • btn_1_clicked() - wciśnięto przycisk "1". Za pomocą funkcji gtk_entry_set_text() możemy ustawić tekst jaki ma się znajdować w polu tekstowym. gtk_entry_append_text() umożliwia dodawanie tekstu na koniec już istniejącego.
  • btn_2_clicked() - wciśnięto przycisk "2".
  • btn_3_clicked() - wciśnięto przycisk "3".
  • btn_4_clicked() - wciśnięto przycisk "4".
  • btn_5_clicked() - wciśnięto przycisk "5".
  • btn_6_clicked() - wciśnięto przycisk "6".
  • btn_7_clicked() - wciśnięto przycisk "7".
  • btn_8_clicked() - wciśnięto przycisk "8".
  • btn_9_clicked() - wciśnięto przycisk "9".
  • btn_0_clicked() - wciśnięto przycisk "0".
  • btn_comma_clicked() - wciśnięto przycisk ",".
  • btn_back_clicked() - wciśnięto przycisk "<-". Użyliśmy tu klasy (jeżeli można tak to nazwać) GString z biblioteki GLib. To prosta struktura, zawiera trzy pola, wskaźnik do ciągu znaków, długość jaką ma ten ciąg, oraz jaka długość/wielkość została dla niego zaalokowana. Funkcja g_string_new() tworzy nową strukturę GString. Można jej podać wartość jaką ma mieć napis, tak też robimy, pobieramy go bezpośrednio z GtkEntry. Gdy napis będzie już nam niepotrzebny należy zwolnić używaną przez niego pamięć przy pomocy funkcji g_string_free(). Pierwszy argument do wskaźnik do struktury, drugi to odpowiedz na pytanie, czy chcesz aby funkcja zwróciła zawartość zwalnianej pamięci - napis. Jeżeli nie zależy nam na nim jako drugi parametr wpisujemy TRUE co mówi dla funkcji aby nie kopiowała/zwracała nam zawartości napisu. My potrzebujemy w tej funkcji tego napisu więc wstawiamy FALSE. Funkcja g_string_truncate() służy do obcinania końcówki napisu licząc od drugiego parametru len do końca napisu.
  • btn_clear_clicked() - wciśnięto przycisk "C".
  • btn_add_clicked() - wciśnięto przycisk "+".
  • btn_sub_clicked() - wciśnięto przycisk "-".
  • btn_multi_clicked() - wciśnięto przycisk "*".
  • btn_div_clicked() - wciśnięto przycisk "/".
  • btn_result_clicked() - wciśnięto przycisk "=".
  • delete_event() - wciśnięto krzyżyk znajdujący się na pasku tytułowym okna, bądź kombinacje klawiszy Alt+F4 - typowe zamknięcie okna.
  • destroy - ostatni moment aby zwolnić zajmowaną pamięć itp., których nie będziemy mogli wykonać gdy nasze okno główne przestanie istnieć. Wywołujemy tu funkcję g_free() dla wskaźnika naszej struktury, ponieważ na samym początku funkcji main() zaalokowaliśmy dla niej pamięć przy pomocy funkcji g_malloc(). Tak naprawdę tą pamięć moglibyśmy zwolnić poza funkcją destroy() np. po funkcji gtk_main(), ponieważ nasza struktura nie jest w żaden sposób związana z GTK+ (mamy do niej również dostęp po zakończeniu działania GTK+) ale zawiera wskaźniki do struktury np. głównego okna. Więc lepszym pomysłem jest zrobienie tego tu.