Progmar Marcin Załęczny

Podpięcie akcji pod opcje menu głównego

Dostosowanie menu głównego

Na początek dostosujmy menu główne do naszych potrzeb. W tym celu otwórz w programie Glade plik main_window.ui i rozwiń w prawym drzewku widgetów całą strukturę pod gałęzią menubar1. Następnie pozmieniaj nazwy widgetów znajdujących się w menu Plik zgodnie z poniższymi rysunkami:

Widget tree with selected menu file

Widget properties - name

Następnie przejdź do menu Edycja i tam również nadaj znaczące nazwy poszczególnym opcjom. Opcje Wytnij, Wklej, Usuń usuń z tego menu (prawy klik na pozycji i z menu kontekstowego wybieramy opcję Usuń):

Widget tree - mnuEdit   Widget tree - mnuEdit 2

Usuń z menu pozycję Widok (menuitem3) oraz nadaj nazwy pozycjom menu Pomoc zgodnie z poniższym obrazkiem:

Widget tree - mnuHelp  

Otwarcie nowej instancji programu

Teraz możemy wziąć się za kodowanie. Najpierw obsłużmy kliknięcie pozycji menu Nowy. Do pliku MainWindow.h dodaj nagłówek funkcji obsługi kliknięcia tej pozycji menu:


	static void tbAboutClicked(GtkWidget* button, MainWindow* window);
	
	static void mnuNowyActivated(GtkWidget* menuItem, MainWindow* window);
	
protected:
	WebKitWebView *webView;

Na końcu pliku MainWindow.cpp dodaj definicję tej funkcji:


void MainWindow::mnuNowyActivated(GtkWidget* menuItem, MainWindow* window) {
	GError* error = NULL;
	
	if (!g_spawn_command_line_async("./internet_viewer", &error)) {
		g_print("Błąd otwarcia programu: %s", error->message);
		g_error_free(error);
	}
}
	

Jak widzimy na powyższym listingu, nowy program otwieramy przy pomocy funkcji g_spawn_command_line_async. Jako pierwszy argument przyjmuje ona linię poleceń programu. W naszym przypadku jest to tylko nazwa programu poprzedzona ciągiem ./ oznaczającym, że funkcja szuka programu w bieżącym katalogu. Drugi argument to zmienna przechowująca informacje o błędzie jeśli operacja uruchomienia programu się nie powiodła. Funkcja otwiera nowy program asynchronicznie (tzn. nie czeka na zamknięcie programu) i zwraca TRUE jeśli operacja się powiodła. W powyższym przykładzie jeśli operacja się nie powiodła to wypisujemy stosowny komunikat i zwalniamy pamięć przydzieloną strukturze GError.

Pozostało jeszcze podpiąć w konstruktorze powyższą funkcję pod sygnał activate:


GtkWidget* tbAbout = gui->getWidget("tbAbout");
g_signal_connect(tbAbout, "clicked", G_CALLBACK(MainWindow::tbAboutClicked), this);

GtkWidget* menuItem = gui->getWidget("mnuNowy");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuNowyActivated), this);

webkit_web_view_load_uri(this->webView, this->homePage);
gtk_widget_grab_focus(GTK_WIDGET(this->webView));
	

Podpięcie funkcji obsługi menu Otwórz, Zakończ i O programie

Powyższe funkcje zostały już obsłużone przez przyciski toolbara, więc obsłużenie ich przez pozycje menu będzie stsunkowo łatwe. Wystarczy wywołać odpowiednie funkcje w funkcjach obsługi kliknięcia pozycji menu. Do pliku MainWindow.h dodaj następujące linijki:


	static void mnuNowyActivated(GtkWidget* menuItem, MainWindow* window);
	static void mnuOtworzActivated(GtkWidget* menuItem, MainWindow* window);
	static void mnuZakonczActivated(GtkWidget* menuItem, MainWindow* window);
	static void mnuOProgramieActivated(GtkWidget* menuItem, MainWindow* window);
	
protected:
	

A na końcu pliku MainWindow.cpp dodaj definicje tych funkcji:


void MainWindow::mnuOtworzActivated(GtkWidget* menuItem, MainWindow* window) {
	MainWindow::tbOpenClicked(NULL, window);
}

void MainWindow::mnuZakonczActivated(GtkWidget* menuItem, MainWindow* window) {
	MainWindow::tbCloseClicked(NULL, window);
}

void MainWindow::mnuOProgramieActivated(GtkWidget* menuItem, MainWindow* window) {
	MainWindow::tbAboutClicked(NULL, window);
}
	

Jako pierwszy argument metod tbOpenClicked, tbCloseClicked, tbAboutClicked przekazujemy wartość NULL, gdyż nie został kliknięty przycisk toolbara a funkcja menu. Parametr ten nie jest używany w powyższych metodach, więc możemy przekazać wartość NULL. Pozostało podpięcie tych funkcji pod sygnał activate. Dokonujemy tego w konstruktorze:


GtkWidget* menuItem = gui->getWidget("mnuNowy");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuNowyActivated), this);
menuItem = gui->getWidget("mnuOtworz");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuOtworzActivated), this);
menuItem = gui->getWidget("mnuZakoncz");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuZakonczActivated), this);
menuItem = gui->getWidget("mnuOProgramie");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuOProgramieActivated), this);

webkit_web_view_load_uri(this->webView, this->homePage);
gtk_widget_grab_focus(GTK_WIDGET(this->webView));
	

Zapisywanie strony do pliku

Obsłużmy teraz zapisywanie wyświetlanej strony do pliku. W tym celu zadeklarujmy atrybut fileName przechowujący nazwę pliku, pod którą zapiszemy wyświetlaną stronę:


protected:
	WebKitWebView *webView;
	GtkWidget* edtUrl;
	const gchar* homePage;
	GString* fileName;
};
	

Wyzerujmy w konstruktorze początkową wartość tego atrybutu:


MainWindow::MainWindow(GtkWidget *wnd) 
	: Window(wnd) {
	this->fileName = NULL;
	g_signal_connect(G_OBJECT(this->wnd), "delete-event", G_CALLBACK(MainWindow::onDeleteEvent), NULL);
	g_signal_connect(G_OBJECT(this->wnd), "destroy", G_CALLBACK(MainWindow::onDestroy), NULL);
	

W destruktorze natomiast zadbajmy o zwolnienie zajmowanej przez ten atrybut pamięci:



MainWindow::~MainWindow() {
    if (this->fileName) {
        g_string_free(this->fileName, TRUE);
    }
}
	

Funkcja g_string_free zwalnia zajmowaną przez zmienną typu GString* pamięć. Jeśli drugi argument jest równy TRUE, to zwalniana jest również pamięć zajmowana przez łańcuch znaków przechowywany w tej zmiennej.
Teraz dodajmy funkcję obsługi kliknięcia menu Zapisz jako:


	static void mnuZakonczActivated(GtkWidget* menuItem, MainWindow* window);
	static void mnuOProgramieActivated(GtkWidget* menuItem, MainWindow* window);
	static void mnuZapiszJakoActivated(GtkWidget* menuItem, MainWindow* window);
	
protected:
	WebKitWebView *webView;
	

Do pliku MainWindow.cpp dodaj na końcu definicję tej funkcji:


void MainWindow::mnuZapiszJakoActivated(GtkWidget* menuItem, MainWindow* window) {
	GtkWidget *dialog;
	dialog = gtk_file_chooser_dialog_new("Zapisz jako", GTK_WINDOW(window->wnd),
		GTK_FILE_CHOOSER_ACTION_SAVE,
		GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
		GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
		NULL);
										  
	GtkFileFilter *fileFilter = gtk_file_filter_new();
	gtk_file_filter_add_pattern(fileFilter, "*.html");
	gtk_file_filter_add_pattern(fileFilter, "*.htm");
	gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), fileFilter);
	
	if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
		gchar *filename;
		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
		if (window->fileName) {
			g_string_assign(window->fileName, filename);
		} else {
			window->fileName = g_string_new(filename);
		}
		g_free(filename);
		
		window->saveWebPage();
	}

	gtk_widget_destroy (dialog);
}
	

Kod powyższej metody statycznej jest podobny do kodu otwarcia pliku. Najpierw tworzymy okno dialogowe GtkFileChooserDialog z nagłówkiem Zapisz jako (pierwszy parametr). Jako trzeci parametr przekazujemy GTK_FILE_CHOOSER_ACTION_SAVE zamiast GTK_FILE_CHOOSER_ACTION_OPEN. Pozwoli to użytkownikowi podać zarówno istniejący plik jak i plik, który jeszcze nie istnieje. Ostatnią różnicą jest wyświetlenie przycisku GTK_STOCK_SAVE zamiast GTK_STOCK_OPEN.
Następnie ustawiamy filtry pozwalające wyświetlać w oknie dialogowym tylko pliki typów: *.html i *.htm.

Jeśli użytkownik wcisnął przycisk GTK_STOCK_SAVE (dialog zwrócił wartość GTK_RESPONSE_ACCEPT), to do zmiennej filename pobieramy pełną ścieżkę do wybranego pliku. Następnie zmienną tą ładujemy do atrybutu fileName. Jeśli atrybut fileName został już wcześniej utworzony (window->FileName != NULL), to korzystamy z funkcji g_string_assign, żeby zawartość zmiennej filename przypisać (skopiować) do atrybutu fileName. W przeciwnym razie - przy pomocy funkcji g_string_new - tworzymy ten atrybut z zawartością zmiennej filename.
Na koniec wywołujemy funkcję window->saveWebPage() (jeszcze nie zdefiniowana) w celu zapisania strony pod podaną nazwą.

Zdefiniujmy teraz funkcję window->saveWebPage(). W tym celu w pliku MainWindow.h dodajmy jej nagłówek:


protected:
	WebKitWebView *webView;
	GtkWidget* edtUrl;
	const gchar* homePage;
	GString* fileName;
	
	void saveWebPage();
};
	

W pliku MainWindow.cpp dodajmy jej treść:


MainWindow::~MainWindow() {
	if (this->fileName) {
		g_string_free(this->fileName, TRUE);
	}
}

void MainWindow::saveWebPage() {
    GError *error = NULL;
    GtkWidget *statusbar = gui->getWidget("statusbar1");
	
    WebKitWebFrame *webFrame = webkit_web_view_get_focused_frame(this->webView);
    if (webFrame) {
        WebKitWebDataSource *webDataSource = webkit_web_frame_get_data_source(webFrame);
        GString *content = webkit_web_data_source_get_data(webDataSource);
        if (content) {
            if (g_file_set_contents(this->fileName->str, content->str, -1, &error)) {
                gchar *info = g_strdup_printf("Plik %s pomyślnie zapisany", this->fileName->str);
                gtk_statusbar_pop(GTK_STATUSBAR(statusbar), 0);
                gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, info);
                g_free(info);
                return;
            }
        }
    }
	
    gchar *info = g_strdup_printf("Błąd podczas zapisywania pliku %s", this->fileName->str);
    gtk_statusbar_pop(GTK_STATUSBAR(statusbar), 0);
    gtk_statusbar_push(GTK_STATUSBAR(statusbar), 0, info);
    g_free(info);
    if (error) {
        g_error_free(error);
    }
}

// ==================================================================================================
// Static functions - event handlers
// ==================================================================================================
void MainWindow::onDestroy(GtkWidget* widget, gpointer data) {
	gtk_main_quit();
}
	

Jest to najbardziej skomplikowany (choć krótki) fragment w całym rozdziale. Na początku powyższej funkcji pobieramy wskaźnik na komponent GtkStatusbar, w którym wyświetlimy komunikat o tym czy udało się poprawnie zapisać stronę do pliku. Następnie pobieramy wskaźnik na aktualnie aktywną ramkę WebKitWebFrame przy pomocy funkcji webkit_web_view_get_focused_frame. Jeśli udało się pobrać ramkę, to - korzystając z funkcji webkit_web_frame_get_data_source - pobieramy wyświetlane w niej źródło danych WebKitWebDataSource. W kolejnym kroku pobieramy dane (kod strony) przy pomocy funkcji webkit_web_data_source_get_data. Jeśli poprawnie udało się pobrać dane to przy pomocy funkcji g_file_set_contents zapisujemy dane do wskazanego przez użytkownika pliku. Funkcja ta w pierwszym parametrze pobiera ścieżkę do zapisywanego pliku, w drugim dane (typu gchar*), w trzecim wartość -1 oznaczającą, że dane są zakończone znakiem NULL i w ostatnim parametrze wskaźnik na zmienną przechowującą informacje o potencjalnym błędzie. Funkcja zwraca TRUE, jeśli udało się poprawnie zapisać dane. Zauważ, że łańcuch znaków typu gchar* pobieramy ze zmiennej GString przy pomocy jej atrybutu str.

Zarówno w przypadku powodzenia jak i niepowodzenia wyświetlamy stosowny komunikat w pasku statusu. Używamy do tego funkcji gtk_statusbar_pop do usunięcia istniejącego w pasku statusu komunikatu oraz funkcji gtk_statusbar_push do wyświetlenia w nim nowego komunikatu. Obie funkcje pobierają tzw. identyfikator kontekstu, którym jest arbitralnie wybrana liczba całkowita. Możliwe jest wyświetlanie w pasku statusu komunikatów o różnych identyfikatorach kontekstu, dlatego ważne jest przy usuwaniu komunikatu podanie tego samego kontekstu, z którym ten komunikat został dodany na stos.

Zaimplementujmy teraz opcję menu Zapisz. W tym celu w pliku MainWindow.h dodaj nagłówek funkcji:


	static void mnuZapiszJakoActivated(GtkWidget* menuItem, MainWindow* window);
	static void mnuZapiszActivated(GtkWidget* menuItem, MainWindow* window);
	
protected:
	

Zaś na końcu pliku MainWindow.cpp dodaj poniższy kod:


void MainWindow::mnuZapiszActivated(GtkWidget* menuItem, MainWindow* window) {
    if (window->fileName) {
        window->saveWebPage();
    } else {
        MainWindow::mnuZapiszJakoActivated(menuItem, window);
    }
}
	

Jak widzimy, jeśli nazwa pliku do zapisu została już wcześniej podana, to od razu zapisujemy do niej zawartość strony. W przeciwnym przypadku wywołujemy obsługę funkcji Zapisz jako przekazując jej parametry menuItem i window.

Podepnijmy teraz w konstruktorze powyższą funkcję pod sygnał activate pozycji mnuZapisz:


menuItem = gui->getWidget("mnuZapiszJako");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuZapiszJakoActivated), this);
menuItem = gui->getWidget("mnuZapisz");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuZapiszActivated), this);

webkit_web_view_load_uri(this->webView, this->homePage);
	

Kopiowanie do schowka zaznaczonego fragmentu strony

Pozostało już tylko obsłużenie kliknięcia pozycji mnuSkopiuj. W tym celu do pliku MainWindow.h dodaj następującą deklarację funkcji:


    static void mnuZapiszActivated(GtkWidget* menuItem, MainWindow* window);
    static void mnuSkopiujActivated(GtkWidget* menuItem, MainWindow* window);
	
protected:
	

Na końcu pliku MainWindow.cpp dodaj definicję tej funkcji:


void MainWindow::mnuSkopiujActivated(GtkWidget* menuItem, MainWindow* window) {
    webkit_web_view_copy_clipboard(window->webView);
}
	

Zaznaczoną treść na stronie kopiujemy do schowka przy pomocy funkcji webkit_web_view_copy_clipboard.

Pozostało już tylko podpięcie w konstruktorze klasy MainWindow pod sygnał activate funkcji MainWindow::mnuSkopiujActivated dla pozycji mnuSkopiuj:


menuItem = gui->getWidget("mnuZapisz");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuZapiszActivated), this);
menuItem = gui->getWidget("mnuSkopiuj");
g_signal_connect(menuItem, "activate", G_CALLBACK(MainWindow::mnuSkopiujActivated), this);

webkit_web_view_load_uri(this->webView, this->homePage);
	

W tym momencie nasz program jest prawie kompletny. W następnym rozdziale dodamy jeszcze funkcjonalność przechodzenia do następnej i poprzedniej strony a także wywołanie przejścia pod wskazany adres po wciśnięciu przycisku Enter na polu tekstowym z adresem strony.