天天看點

讓GtkTextView支援undo/redo操作

讓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~~