讓GtkTextView支援undo/redo操作
Gtk的TextView是一個功能強大的編輯控件,也是GTK+中最為複雜的控件之一。它基于MVC模型設計,GtkTextView是處理使用者界面的視圖部分,GtkTextBuffer是負責緩沖區管理的模型部分。它除實作了普通編輯控件的功能之外,還支援HTML中的一些基本TAG和圖文混排功能,使用也很友善。
唯一不爽的就是它不支援undo/redo操作,讓人挺郁悶的,偏偏SPEC又要求這個功能。一直沒有時間管它,最近進入完善階段了,今天花了點時間給它加了這個功能。在網上找了一下,發現最新的GTK+也沒有提供這個功能,看來GTK官方是沒有打算去做了,這次又要自己動手了。
至于undo/redo的實作,我倒是有類似的程式設計經驗,其實作雖然不算複雜,不過也要花不少時間。呵,現在忙得很,可沒有時間去玩這個,最好是能從其它編輯中移植過來。在網上找了一下,發現GtkSourceView支援undo/redo功能,馬上下了一個源碼包。
解開之後,浏覽了一下裡面的代碼,運氣不錯:它的undo/redo功能實作得非常獨立,隻要把gtksourceundomanager.c、gtksourceundomanager.h和gtksourceview-marshal.h拿過來就行了。
剩下的就是要與GtkTextView/GtkTextView結合起來,本來想參考GtkSourceView/GtkSourceBuffer裡面做法,不過發現要修改不少地方。我當然不希望修改太多地方,那樣可能會引入一些其它BUG。經過幾次試驗,終于找到一種不修改GtkTextView/GtkTextView的方法。其代碼如下:
#include "gtksourceundomanager.h" #define GET_UNDO_MANAGER(buffer) (GtkSourceUndoManager*)g_object_get_data(G_OBJECT(buffer), "undo_manager") static void undo_cb (GtkWidget *menuitem, GtkTextView* view) { GtkTextBuffer *buffer = NULL; GtkSourceUndoManager* undo_manager = NULL; g_return_if_fail (GTK_IS_TEXT_VIEW (view)); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (view)); undo_manager = GET_UNDO_MANAGER(buffer); if(gtk_source_undo_manager_can_undo(undo_manager)) { gtk_source_undo_manager_undo(undo_manager); gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (view), gtk_text_buffer_get_insert (buffer)); } } static void redo_cb (GtkWidget *menuitem, GtkTextView* view) if(gtk_source_undo_manager_can_redo(undo_manager)) gtk_source_undo_manager_redo(undo_manager); static void on_populate_popup (GtkTextView* view, GtkMenu *menu, GtkSourceUndoManager* undo_manager) GtkWidget* menu_item = NULL; menu_item = gtk_menu_item_new (); gtk_menu_shell_prepend (GTK_MENU_SHELL (menu), menu_item); gtk_widget_show (menu_item); /* create redo menu_item. */ menu_item = gtk_image_menu_item_new_from_stock ("gtk-redo", NULL); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (redo_cb), view); gtk_widget_set_sensitive (menu_item, gtk_source_undo_manager_can_redo(undo_manager)); /* create undo menu_item. */ menu_item = gtk_image_menu_item_new_from_stock ("gtk-undo", NULL); g_signal_connect (G_OBJECT (menu_item), "activate", G_CALLBACK (undo_cb), view); gtk_widget_set_sensitive (menu_item, gtk_source_undo_manager_can_undo(undo_manager)); return; gboolean gtk_text_view_decorate_undo_redo(GtkTextView* view) g_return_val_if_fail(GTK_IS_TEXT_VIEW(view), FALSE); if((undo_manager = gtk_source_undo_manager_new(GTK_TEXT_BUFFER (buffer))) != NULL) g_object_set_data_full(G_OBJECT(buffer), "undo_manager", undo_manager, (GDestroyNotify)g_object_unref); g_signal_connect(G_OBJECT(view), "populate_popup", G_CALLBACK(on_populate_popup), undo_manager); return undo_manager != NULL; } |
這裡采用類似裝飾模式的方法動态的給GtkTextView增加undo/redo功能。通過注冊GtkTextView的populate_popup事件,向右鍵彈出菜單中增加undo和redo菜單項,然後在菜單的事件處理函數中調用undo_manager的函數。
之是以這麼簡單,完全得益于GtkTextView和GtkSourceView精良的設計。
~~end~~