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:
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ń):
Usuń z menu pozycję Widok (menuitem3) oraz nadaj nazwy pozycjom menu Pomoc zgodnie z poniższym obrazkiem:
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));
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));
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);
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.