gpt4 book ai didi

c - GtkTextBuffer“mark_set”信号触发箭头键或鼠标单击回调3-4次

转载 作者:行者123 更新时间:2023-11-30 17:06:43 25 4
gpt4 key购买 nike

我正在尝试使用'mark_set'信号更新我的GtkTextBuffer中的row:col值。为了进行测试,我有一个简单的设置,其中textview在一个窗口内的滚动窗口内,例如:

window
scrolled window
textview


我使用一种结构来保存我的应用程序的各种值,例如:

typedef struct {
GtkWidget *window;
GtkWidget *view;
GtkTextBuffer *buffer;
GtkTextMark *cursor;
gint line;
gint col;
gint winwidth;
gint winheight;
} context;


我正在尝试在与我的应用程序一起使用的结构实例中更新当前的 linecol值,以跟踪缓冲区内的行和列位置。在create_window函数中,我初始化 context *app;的值(在 main()中定义),并将 'mark_set'信号连接到 on_mark_set()回调,将结构实例作为数据传递到回调。例如。:

g_signal_connect (app->buffer, "mark_set",
G_CALLBACK (on_mark_set), app);


on_mark_set()回调(例如g_print和调试目的)是:

void on_mark_set (GtkTextBuffer *buffer, context *app)
{
GtkTextIter iter;

app->cursor = gtk_text_buffer_get_insert (buffer);

gtk_text_buffer_get_iter_at_mark (buffer, &iter, app->cursor);

app->line = gtk_text_iter_get_line (&iter);
app->col = gtk_text_iter_get_line_offset (&iter);

g_print (" line: %3d col: %d\n", app->line + 1, app->col + 1);
}


在每次向缓冲区提供输入的按键之后,正确设置 app->lineapp->col的值(仅一次)。例如在文本视图中输入 'abc'将导致:

$ ./bin/text_mcve
line: 1 col: 2
line: 1 col: 3
line: 1 col: 4


但是,当我使用 arrow keys移动输入光标或使用 mouse重新定位时,回调三重触发或四重触发。例如按左箭头备份一个位置会导致以下结果:

line:   1 col: 3
line: 1 col: 3
line: 1 col: 3


通过单击鼠标重新定位到末尾将导致回调的四倍触发:

line:   1 col: 4
line: 1 col: 4
line: 1 col: 4
line: 1 col: 4


如何将 on_mark_set()回调的执行限制为单个调用,而不管是否输入了数据,或者是否使用箭头键或鼠标移动了光标?鉴于 'mark_set'是唯一可以覆盖输入->光标位置处理的信号,无论定位输入是来自按键还是鼠标单击。目标是利用 'mark_set'信号来处理所有 row:col更新,但是我必须找到一种方法来防止对于每个按键或鼠标单击事件多次触发回调。

当将 'key_press_event'与textview窗口小部件一起使用时,您可以传递创建一个gboolean回调并传递一个GdkEventKey并手动处理 event->keyval以使用键盘(包括箭头键)处理光标的重新定位,并通过 return告诉默认输入处理程序,对于任何给定的按键,不需要采取进一步的操作,但是不需要,并且不能与鼠标单击一起使用。因此,如果我可以通过 'mark_set'信号完成所有操作,那将是我的选择。

有什么方法可以对 'mark_set'事件执行相同的操作,以确保 on_mark_set()回调仅执行一次,而不管按键或鼠标单击如何?我已发布到gtk-app-devel-list,但尚未收到回复。所以。在gtk主题上可能比gtk-list本身更活跃。任何与这个难题的帮助将不胜感激。

MCVE测试

下面提供了用于测试目的的MCVE。用 gcc -o progname progname.c $(pkg-config --cflags --libs gtk+-2.0)编译

#include <gtk/gtk.h>

typedef struct {
GtkWidget *window;
GtkWidget *view;
GtkTextBuffer *buffer;
GtkTextMark *cursor;
gint line;
gint col;
gint winwidth;
gint winheight;
} context;

GtkWidget *create_window (context *app);
void on_window_destroy (GtkWidget *widget, context *app);
void on_mark_set (GtkTextBuffer *buffer, context *app);

int main (int argc, char **argv)
{
context *app = NULL;
app = g_slice_new (context);

gtk_init (&argc, &argv);

if ((app->window = create_window (app))) {
gtk_widget_show (app->window);
gtk_main();
}
else
g_print ("\nerror: create_window returned NULL\n\n");

g_slice_free (context, app);

return 0;
}

GtkWidget *create_window (context *app)
{
GtkWidget *scrolled_window;
GtkWidget *vbox;
PangoFontDescription *font_desc;

app->winwidth = 500; /* window width x height */
app->winheight = 350;

app->line = 0; /* initialize beginning pos line/col */
app->col = 0;

app->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (app->window), "mark_set MCVE");
gtk_window_set_default_size (GTK_WINDOW (app->window),
app->winwidth, app->winheight);
gtk_container_set_border_width (GTK_CONTAINER (app->window), 5);

vbox = gtk_vbox_new (FALSE, 0);
gtk_container_add (GTK_CONTAINER (app->window), vbox);

app->buffer = gtk_text_buffer_new (NULL);

app->view = gtk_text_view_new_with_buffer (app->buffer);
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (app->view), GTK_WRAP_WORD);
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (app->view), 10);
font_desc = pango_font_description_from_string ("DejaVu Sans Mono 8");
gtk_widget_modify_font (app->view, font_desc);
pango_font_description_free (font_desc);

scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);

gtk_container_add (GTK_CONTAINER (scrolled_window), app->view);
gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 5);

g_signal_connect (app->window, "destroy",
G_CALLBACK (on_window_destroy), app);
g_signal_connect (app->buffer, "mark_set",
G_CALLBACK (on_mark_set), app);

gtk_widget_show (app->view);
gtk_widget_show (scrolled_window);
gtk_widget_show (vbox);

return app->window;
}

void on_window_destroy (GtkWidget *widget, context *app)
{
GtkTextIter start, end;
gtk_text_buffer_get_bounds (app->buffer, &start, &end);
g_print ("Exiting... buffer contained:\n%s\n",
gtk_text_buffer_get_text (app->buffer, &start, &end, FALSE));
gtk_main_quit ();
if (widget) {}
}

void on_mark_set (GtkTextBuffer *buffer, context *app)
{
GtkTextIter iter;

app->cursor = gtk_text_buffer_get_insert (buffer);

gtk_text_buffer_get_iter_at_mark (buffer, &iter, app->cursor);

app->line = gtk_text_iter_get_line (&iter);
app->col = gtk_text_iter_get_line_offset (&iter);

g_print (" line: %3d col: %d\n", app->line + 1, app->col + 1);
}


再次感谢您的任何建议或帮助。注意:此代码是gtk + 2应用程序的一部分,但也可以使用gtk + 3进行编译,并且使用时建议使用的警告最少。





由于无法通过常规搜索手段在网络上找到此问题的解决方案,因此,我将发布以下答案所指出的正确方向后得出的解决方案。更改回调原型后,答案中的建议是,每次生成 mark_set信号时都要比较标记属性,并丢弃所有不符合必需属性的 mark_set调用。尽管由于没有可用的唯一属性来标识该方法不可行(可以为任何给定的插入光标移动生成 nullinsertselection_bound这三种名称可能性中的任何一种或全部),但是为区分新信号和当前 mark_set信号提供一种方法。

关键是在创建缓冲区时初始化缓冲区中的当前 line:col位置并将其存储在缓冲区中。这可以在 create_window()函数中完成:

GtkTextIter iterfirst;
...
app->cursor = gtk_text_buffer_get_insert (app->buffer);
gtk_text_buffer_get_iter_at_mark (app->buffer, &iterfirst, app->cursor);
app->line = gtk_text_iter_get_line (&iterfirst);
app->col = gtk_text_iter_get_line_offset (&iterfirst);


了解了当前的 line:col值后,您可以将其与根据作为参数传递给 line:col回调的 GtkTextIter *iter值而生成的新 on_mark_set()值进行比较。这样可以轻松地将电流与新值进行比较,从而使您仅响应导致 mark_set值发生变化的 line:col信号:

void on_mark_set (GtkTextBuffer *buffer, GtkTextIter *iter,
GtkTextMark *mark, context *app)
{
gint line, col;

line = gtk_text_iter_get_line (iter);
col = gtk_text_iter_get_line_offset (iter);

if (line == app->line && col == app->col) return;

app->line = line;
app->col = col;

g_print (" line: %3d col: %d\n", app->line + 1, app->col + 1);

if (mark) {}
}


(注意:debug g_print语句留在上方,为下面的输出提供上下文)进一步说明,由于 mark仅在箭头键时返回匹配值,因此无法在 gtk_text_buffer_get_insert (buffer)gtk_text_buffer_get_insert (buffer)之间进行比较。或鼠标单击输入。 (对于普通文本输入,比较失败)。

现在,当重复原始问题中提到的相同事件序列时(例如,输入 'abc',然后使用向左箭头备份1,然后在末尾单击鼠标左键以重新定位光标),表明 on_mark_set现在可以正确响应仅更新的 line:col值:

输出量

$ ./bin/text_mcve
line: 1 col: 2
line: 1 col: 3
line: 1 col: 4
line: 1 col: 3
line: 1 col: 4
Exiting... buffer contained:
abc




每个 iter位置上的多个标记(无特定顺序)

为了让这些问题作为一个好的参考,如果不是一个好的解决方案,我将进一步调试的结果纳入该主题。在其他任何地方都找不到有关此特定主题的完整信息。

进一步的调试显示了为什么在此方面存在困难,以及为什么不能可靠地单独使用 markgtk_text_buffer_get_insert (buffer)之间的简单比较来确定是否响应 "mark_set"信号。为什么?

每次生成 "mark_set"信号时,在任何给定的 marks位置上都可以有多个 iter。在正常输入的情况下(例如 'a''b'等),传递给 mark回调的 on_mark_set()不一定是 "insert"标记,而显然只是最后一个标记出现在那个位置。 (在每种情况下,匿名标记下方)可以通过 iter返回的标记 GSList找到在任何给定 gtk_text_iter_get_marks (iter)位置的标记列表。 (注意:返回的列表中的标记没有特定的顺序-这可能是整个问题的基础。请参见: gtk_text_iter_get_marks())例如,您可以使用以下调试代码检查标记:

void on_mark_set (GtkTextBuffer *buffer, GtkTextIter *iter,
GtkTextMark *mark, context *app)
{
gint line, col;

#ifdef DEBUG
g_print (" mark: %p - gtbgi (buffer): %p mark->name: %s\n", mark,
gtk_text_buffer_get_insert (buffer),
gtk_text_mark_get_name (mark));

GSList *marks = gtk_text_iter_get_marks (iter);
GSList *p = marks;
gint i = 0;
while (p) {
const gchar *name = gtk_text_mark_get_name (GTK_TEXT_MARK(p->data));
g_print (" mark[%d] : %p : %s\n", i++, GTK_TEXT_MARK(p->data), name);
p = p->next;
}
g_slist_free (marks);
#endif

line = gtk_text_iter_get_line (iter);
col = gtk_text_iter_get_line_offset (iter);

if (line == app->line && col == app->col) return;

app->line = line;
app->col = col;

#ifdef DEBUG
g_print (" line: %3d col: %d\n\n", app->line + 1, app->col + 1);
#endif

if (mark) {}
}


编译然后使用相同的命令(输入 'abc',然后按向左箭头,然后在最后单击鼠标),为每个输入的 on_mark_set()触发 'abc'回调:

$ ./bin/text_mcve_dbg
mark: 0x2458880 - gtbgi (buffer): 0x237d600 mark->name: (null)
mark[0] : 0x237d600 : insert
mark[1] : 0x237d620 : selection_bound
mark[2] : 0x237d7a0 : gtk_drag_target
mark[3] : 0x2458880 : (null)
line: 1 col: 2

mark: 0x24792c0 - gtbgi (buffer): 0x237d600 mark->name: (null)
mark[0] : 0x237d600 : insert
mark[1] : 0x237d620 : selection_bound
mark[2] : 0x237d7a0 : gtk_drag_target
mark[3] : 0x24792c0 : (null)
line: 1 col: 3

mark: 0x24797a0 - gtbgi (buffer): 0x237d600 mark->name: (null)
mark[0] : 0x237d600 : insert
mark[1] : 0x237d620 : selection_bound
mark[2] : 0x237d7a0 : gtk_drag_target
mark[3] : 0x24797a0 : (null)
line: 1 col: 4


检查一下,每个 iter位置都有4个标记,并且尽管实际上所有4个都在 mark位置,但回调传递的 mark[3]iter

按下向左箭头键时,回调会触发3次,每次出现的每个标记均会出现:

  mark: 0x237d600  - gtbgi (buffer): 0x237d600  mark->name: insert
mark[0] : 0x237d600 : insert
mark[1] : 0x237d620 : selection_bound
line: 1 col: 3

mark: 0x237d620 - gtbgi (buffer): 0x237d600 mark->name: selection_bound
mark[0] : 0x237d600 : insert
mark[1] : 0x237d620 : selection_bound
mark: 0x2479700 - gtbgi (buffer): 0x237d600 mark->name: (null)
mark[0] : 0x237d600 : insert
mark[1] : 0x237d620 : selection_bound
mark[2] : 0x2479700 : (null)


对于第一次触发回调,传递 "insert"标记,第二次触发 "selection_bound"标记,最后,传递匿名 'null'标记。本质上,当按下向左箭头键时,会为 iter位置上的每个标记触发一次回调。

单击鼠标以将插入点放置在缓冲区的末尾时,回调将触发4次,如下所示:

  mark: 0x237d600  - gtbgi (buffer): 0x237d600  mark->name: insert
mark[0] : 0x237d7a0 : gtk_drag_target
mark[1] : 0x237d600 : insert
mark[2] : 0x237d620 : selection_bound
line: 1 col: 4

mark: 0x237d620 - gtbgi (buffer): 0x237d600 mark->name: selection_bound
mark[0] : 0x237d7a0 : gtk_drag_target
mark[1] : 0x237d600 : insert
mark[2] : 0x237d620 : selection_bound
mark: 0x24792a0 - gtbgi (buffer): 0x237d600 mark->name: (null)
mark[0] : 0x237d7a0 : gtk_drag_target
mark[1] : 0x237d600 : insert
mark[2] : 0x237d620 : selection_bound
mark[3] : 0x24792a0 : (null)
mark: 0x2479200 - gtbgi (buffer): 0x237d600 mark->name: (null)
mark[0] : 0x237d7a0 : gtk_drag_target
mark[1] : 0x237d600 : insert
mark[2] : 0x237d620 : selection_bound
mark[3] : 0x2479200 : (null)
mark[4] : 0x24792a0 : (null)


单击鼠标时在其中包含 'gtk_drag_target'标记的位置,但除此之外,除了其他标记和其他匿名标记外,其行为类似于按左箭头键。

因此,最重要的是,由于每次触发都将 "insert"标记作为该位置处的标记之一包含在内,但没有作为参数 mark传递给普通文本输入中的回调,因此没有办法以防止在任何情况下多次触发回调。最好的办法是有效地确定回调是否需要响应 "mark_set"信号。在那种情况下,检查 "insert"标记是否存在以及 line:col位置是否有任何变化都差不多。

另一种选择是拆分责任,用于更新 line:col回调和输入处理程序回调之间的 on_mark_set()位置,并使输入处理程序更新 line:col进行常规文本输入,而 on_mark_set()仅在 "insert"作为参数传递。但是,我不确定这是否是更好的解决方案。

最佳答案

发出mark_set信号以更改任何标记,而不仅仅是光标位置。鼠标移动也可能会影响选择标记,这就是为什么它看起来好像收到多个信号的原因。正确的mark-set信号处理程序应检查mark参数,并忽略它不关心的标记,在这种情况下,除gtk_text_buffer_get_insert(buffer)返回的插入标记外的所有标记均如此。

最后一点导致了最初提供的代码中的问题。根据documentation,应将其声明为

void user_function (GtkTextBuffer *textbuffer, GtkTextIter *location,
GtkTextMark *mark, gpointer user_data)


...而您的处理程序仅接受缓冲区和用户数据。写入为 location迭代器保留的内存可能会导致代码的无关部分损坏,并使问题更加复杂。

一旦处理程序原型得到修复,并且忽略了非光标标记,多重调用问题就将消失。

关于c - GtkTextBuffer“mark_set”信号触发箭头键或鼠标单击回调3-4次,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34478787/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com