A program has a menu bar, which has a menu, which has a menu item. The menu item is enabled or disabled depending on some circumstances. Checking of circumstances and enabling/disabling of the menu item is done when a user clicks on the menu that holds that menu item.
But, there is also a shortcut associated with that menu item. The shortcut functions only when the menu item is enabled.
How can we set the state of a menu item without clicking on the menu that holds it, when we want to use a shortcut?
Here is an example program that will hopefully make my question more clear:
#include <gtk/gtk.h>
struct check_sensitivity
{
GtkWidget *menuitem;
GtkTextBuffer *buffer;
};
void sensitivity(GtkWidget *menu, struct check_sensitivity *sens)
{
if (gtk_text_buffer_get_modified(sens->buffer))
gtk_widget_set_sensitive(sens->menuitem, TRUE);
else
gtk_widget_set_sensitive(sens->menuitem, FALSE);
}
void menuitem_click(GtkWidget *menuitem, GtkTextBuffer *buffer)
{
gtk_text_buffer_set_text(buffer, "", -1);
gtk_text_buffer_set_modified(buffer, FALSE);
}
int main(int argc, char **argv)
{
gtk_init(&argc, &argv);
struct check_sensitivity sens;
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
GtkWidget *view = gtk_text_view_new();
sens.buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
GtkWidget *menubar = gtk_menu_bar_new();
GtkWidget *menu = gtk_menu_new();
GtkAccelGroup *shortcuts = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(window), shortcuts);
GtkWidget *menuitem = gtk_menu_item_new_with_label("Menu");
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
g_signal_connect(menuitem, "activate", G_CALLBACK(sensitivity), &sens);
menuitem = gtk_menu_item_new_with_label("Clear");
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_widget_add_accelerator(menuitem, "activate", shortcuts, GDK_KEY_D, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
sens.menuitem = menuitem;
g_signal_connect(sens.menuitem, "activate", G_CALLBACK(menuitem_click), sens.buffer);
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(box), menubar, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(box), view, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), box);
gtk_widget_show_all(window);
gtk_main();
}
In this program, the state of menu item "Clear" depends on that whether the buffer is modified or not. The state is, of course, set when the user clicks on the menu "Menu".
There is also a shortcut Ctrl+D that does the same thing as clicking on "Clear". The shortcut works as it should depending on the state of the menu item, but what is wrong is the state of the menu item. Let me explain:
Since the menu item is (un)set when a user click on "Menu", to update the state of "Clear" before using a shortcut, a user must click on it. If the shortcut is used before updating of the state of the menu item, it may not be able to do its function when it should.
Here is what you can try: open the program and write something. Try clearing it with Ctrl+D. You'll see that nothing happens even though it should, because the state of the menu item is not updated. If you click on "Menu", then try using the shortcut again, the text buffer will clear.
I'm just asking for a why to update the state when the shortcut is pressed as well. I tried a few ways that base on setting the state when Ctrl is pressed (I can't check only for Ctrl+D, I may have another shortcuts in a program), but all of them failed or produced bugs. One of those ways you can see here.
If you want to update the menuitem sensitivity depending on if there is text in your textbuffer, connect to the textbuffer changed or modified-changed signal. Since I am not good at C, but normally use Python:
textbuffer.connect("changed", update_menuitem_sensitivity)
changed
modified-changed
Finally found a working method.
#include <gtk/gtk.h>
struct check_sensitivity
{
GtkWidget *menuitem;
GtkTextBuffer *buffer;
};
void sensitivity(GtkWidget *menu, struct check_sensitivity *sens);
void check_sensitivity_on_ctrl(GtkWidget *window, GdkEventKey *key, struct check_sensitivity *sens)
{
if(key->keyval == GDK_KEY_Control_L || key->keyval == GDK_KEY_Control_R)
{
sensitivity(NULL, sens);
}
}
void sensitivity(GtkWidget *menu, struct check_sensitivity *sens)
{
if (gtk_text_buffer_get_modified(sens->buffer))
gtk_widget_set_sensitive(sens->menuitem, TRUE);
else
gtk_widget_set_sensitive(sens->menuitem, FALSE);
}
void menuitem_click(GtkWidget *menuitem, GtkTextBuffer *buffer)
{
gtk_text_buffer_set_text(buffer, "", -1);
gtk_text_buffer_set_modified(buffer, FALSE);
}
int main(int argc, char **argv)
{
gtk_init(&argc, &argv);
struct check_sensitivity sens;
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
g_signal_connect(window, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
GtkWidget *view = gtk_text_view_new();
sens.buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
gtk_text_buffer_set_modified(sens.buffer, FALSE);
GtkWidget *menubar = gtk_menu_bar_new();
GtkWidget *menu = gtk_menu_new();
GtkAccelGroup *shortcuts = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(window), shortcuts);
GtkWidget *menuitem = gtk_menu_item_new_with_label("Menu");
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), menu);
gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
g_signal_connect(menuitem, "activate", G_CALLBACK(sensitivity), &sens);
menuitem = gtk_menu_item_new_with_label("Clear");
gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
gtk_widget_add_accelerator(menuitem, "activate", shortcuts, GDK_KEY_D, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
sens.menuitem = menuitem;
g_signal_connect(sens.menuitem, "activate", G_CALLBACK(menuitem_click), sens.buffer);
g_signal_connect_after(window, "key-press-event", G_CALLBACK(check_sensitivity_on_ctrl), &sens);
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start(GTK_BOX(box), menubar, FALSE, FALSE, 0);
gtk_box_pack_start(GTK_BOX(box), view, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(window), box);
gtk_widget_show_all(window);
gtk_main();
}
Related
what I am trying to do is close tabs in a GTK notebook by a button added to the tab but the function to get the page number does not seem to work and it looks as though the pages close from the last created backwards.
void close_tab(GtkWidget *button, gpointer data){
gint pg_num = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), button);
gtk_notebook_remove_page(GTK_NOTEBOOK(notebook) , pg_num);
}
void add_tab (char *name){
GtkWidget *textview = gtk_text_view_new();
GtkWidget *text = gtk_label_new(name);
GtkWidget *label = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
GtkWidget *icon = gtk_image_new_from_file ("close.png");
GtkWidget *button = gtk_button_new();
gtk_button_set_image(GTK_BUTTON(button), icon);
gtk_widget_set_tooltip_text(button , "Close Tab");
gtk_box_pack_start(GTK_BOX(label), text, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(label), button, FALSE, FALSE, 0);
g_signal_connect(GTK_WIDGET(button), "clicked",
G_CALLBACK(close_tab),
NULL);
GtkWidget *scrollwindow = gtk_scrolled_window_new(NULL, NULL);
gtk_container_add(GTK_CONTAINER(scrollwindow), textview);
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrollwindow, label);
gtk_widget_show_all(label);
gtk_widget_show_all(scrollwindow);
}
void button_click(GtkWidget *button, gpointer data){
char *btn = (char *) data;
if (strcmp(btn, "New") == 0);
add_tab("new tab");
}
my experiment is with two tabs the first is made automatically with the 'text' label as "untitled", and the second as "new tab" and tried giving the button the name passed to the add tab function but the result was the same so I don't know how to make the button know what page it belongs to.
I figured out the solution was to pass a direct child of the page as the gpointer data, the only one that seems to work is the scrollwindow.
the remove tab function change
void close_tab(GtkWidget *button, gpointer data){
int pg_num = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), data);
gtk_notebook_remove_page(GTK_NOTEBOOK(notebook) , pg_num);
}
and a change inside the add tab function
GtkWidget *scrollwindow = gtk_scrolled_window_new(NULL, NULL);
gtk_container_add(GTK_CONTAINER(scrollwindow), textview);
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrollwindow, label);
g_signal_connect(GTK_WIDGET(button), "clicked",
G_CALLBACK(close_tab),
scrollwindow);
edit: The problem described here seems to be theme related. I'm using Greybird from XUbuntu here.
The program
In my program you can click a button to create more buttons in a vertical GtkBox. If the buttons exceed the visible space a GtkScrollbar will be shown:
Problem
When the GtkScrollbar is shown for the first time, it dos not show on the side, as it should be; It's shown below the buttons:
This is weird, because the scrollbar is actually inside a horizontal GtkBox in a cell next to those buttons.
In this state, the buttons are not clickable anymore until either of of the following happens:
click the scroll bar
scroll via mouse wheel
click the window border
focus another application
UI Architecture
This is the architecture of my UI:
Whether the scrollbar is shown or not, is decided when the "page-size" property the GtkAdjustment is changed.
Code
#include <gtk/gtk.h>
gboolean
adj_page_sizechanged (GtkAdjustment *adjustment,
GdkEvent *unused,
GtkScrollbar *scrollbar)
{
gdouble maxValue = gtk_adjustment_get_upper (adjustment);
gdouble maxSize = gtk_adjustment_get_page_size (adjustment);
gboolean show = (maxValue - maxSize >= 0.000001);
g_object_set (scrollbar, "visible", show, NULL);
return FALSE;
}
/**
* Creates a `automatic` scollbar for `widget`, that will never
* overlay (hide) the content of `widget`.
**/
GtkWidget *
gtkx_scrollable_widget_vertical_new (GtkWidget *widget,
GtkAdjustment *adjustment_nullable)
{
GtkWidget *root = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
// setup adjustment
GtkAdjustment * adj_v = adjustment_nullable;
if (NULL == adj_v)
adj_v = gtk_adjustment_new (0,0,100,1,10,10);
// setup scrollbar
{
GtkWidget * scrollbar = gtk_scrollbar_new (GTK_ORIENTATION_VERTICAL, adj_v);
g_signal_connect(adj_v, "notify::page-size", G_CALLBACK (adj_page_sizechanged), scrollbar);
gtk_box_pack_end (GTK_BOX (root), scrollbar, FALSE, FALSE, 0);
}
// setup scrollable area
{
GtkWidget * scrolled_window = gtk_scrolled_window_new (NULL, adj_v);
gtk_container_add (GTK_CONTAINER (scrolled_window), widget);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_NEVER, GTK_POLICY_EXTERNAL);
gtk_box_pack_start (GTK_BOX (root), scrolled_window, TRUE, TRUE, 0);
}
return root;
}
void
add_button (GtkButton *button,
GtkBox *box)
{
g_print("add button\n");
gtk_container_add (GTK_CONTAINER (box), gtk_button_new_with_label ("b 2"));
gtk_widget_show_all (GTK_WIDGET (box));
}
GtkWidget *
create_ui()
{
GtkWidget * action_button = NULL;
GtkWidget * box_outer = NULL;
GtkWidget * box_inner = NULL;
box_inner = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
action_button = gtk_button_new_with_label ("click me");
gtk_container_add (GTK_CONTAINER (box_inner), action_button);
g_signal_connect (action_button, "clicked", G_CALLBACK (add_button), box_inner);
box_outer = gtkx_scrollable_widget_vertical_new (box_inner, NULL);
return box_outer;
}
/**
* standard stuff. See `create_ui()`
**/
int main(int argc, char *argv[])
{
gtk_init (&argc, &argv);
GtkWidget *window;
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_default_size (GTK_WINDOW (window), -1, 60);
GtkWidget * ui = create_ui ();
gtk_container_add (GTK_CONTAINER (window), ui);
g_signal_connect (G_OBJECT (window), "destroy",
G_CALLBACK (gtk_main_quit), G_OBJECT (window));
gtk_widget_show_all(window);
gtk_main();
return 0;
}
Notes
I don't use GtkScrolledWindow's internal scrollbars, because they will overlay the content. I don't want that. Therefore I'm making this implementation.
I could imagine this to be a bug within GTK. Even if it is, is there a way to work around it?
I'm running Gtk3.24 under XOrg, XUb 21.04, Theme: Greybird.
edit
It looks like this behavior is triggered depending on the theme.
I'm modifying an existing program wrote in C in which I added a GtkEntry.
When I try to write, for example, "qwerty" in the entry, it's filled only with "qwrty" because the character "e" is used as a shortcut (accelerator) to call another function, and that function is also activated when "e" is pressed.
Is there any way to avoid accelerator callbacks while we are writing in the entry?
Yes, you can disconnect your accelerator group in the focus-in-event callback of GtkEntry, and connect it again when you focus back out of the entry. Here's an example:
#include <stdio.h>
#include <gtk/gtk.h>
GtkAccelGroup *accel_group;
GClosure *closure;
void accelerator_pressed(void)
{
printf("Accelerator pressed!\n");
}
gboolean focus_in_callback(void)
{
gtk_accel_group_disconnect(accel_group, closure);
g_closure_unref(closure);
return GDK_EVENT_PROPAGATE;
}
gboolean focus_out_callback(void)
{
closure = g_cclosure_new(accelerator_pressed, 0, 0);
gtk_accel_group_connect(accel_group, GDK_KEY_e, (GdkModifierType)0, GTK_ACCEL_VISIBLE, closure);
return GDK_EVENT_PROPAGATE;
}
int main()
{
gtk_init(NULL, NULL);
GtkWidget *window, *box, *entry, *button;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
entry = gtk_entry_new();
button = gtk_button_new_with_label("click me");
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(entry), TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(button), TRUE, TRUE, 0);
g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
g_signal_connect(GTK_WIDGET(entry), "focus-in-event", G_CALLBACK(focus_in_callback), NULL);
g_signal_connect(GTK_WIDGET(entry), "focus-out-event", G_CALLBACK(focus_out_callback), accel_group);
accel_group = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
gtk_container_add(GTK_CONTAINER(window), box);
gtk_widget_show_all(window);
gtk_main();
}
I am trying to integrate appindicator with gtk menu.
I want some of menu items have shortcuts like here:
Unfortunately after adding accelerator to second menu item I still get item with no icon and no shortcut.
What I expect to see is a shortcut to the right of "item2".
int main (int argc, char **argv) {
gtk_init(&argc, &argv);
// main window
GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "menu-with-shortcuts-window");
gtk_window_set_icon_name(GTK_WINDOW(window), "menu-with-shortcuts-icon");
g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(gtk_main_quit), NULL);
gtk_widget_show_all(window);
// indicator
AppIndicator* indicator = app_indicator_new("menu-with-shortcuts-appindicator", "transmission-tray-icon", APP_INDICATOR_CATEGORY_APPLICATION_STATUS);
// menu
GtkWidget* indicator_menu = gtk_menu_new();
GtkWidget* item1 = gtk_menu_item_new_with_label("item1");
gtk_menu_shell_append(GTK_MENU_SHELL(indicator_menu), item1);
g_signal_connect(item1, "activate", G_CALLBACK(Item1Clicked), NULL);
gtk_widget_show(item1);
GtkAccelGroup* accel_group = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
GtkWidget* item2 = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group);
gtk_widget_add_accelerator(item2, "activate", accel_group, GDK_q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
gtk_menu_shell_append(GTK_MENU_SHELL(indicator_menu), item2);
g_signal_connect(item2, "activate", G_CALLBACK(Item2Clicked), NULL);
gtk_widget_show(item2);
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
app_indicator_set_attention_icon(indicator, "menu-with-shortcuts-appindicator");
app_indicator_set_menu(indicator, GTK_MENU(indicator_menu));
gtk_widget_show_all(window);
gtk_main();
return 0;
}
I'm trying to implement a text view that will have all the text selected when the user clicks on it. However, when I tried the following, the text isn't selected when the text view is clicked, although it is selected after dragging the window.
Consider this minimal example:
#include <gtk/gtk.h>
gboolean cb(GtkWidget *tv, GdkEvent *event, gpointer user_data) {
GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv));
GtkTextIter start, end;
gtk_text_buffer_get_start_iter(buf, &start);
gtk_text_buffer_get_end_iter(buf, &end);
gtk_text_buffer_select_range(buf, &start, &end);
return FALSE;
}
int main(int argc, char *argv[]) {
gtk_init(&argc, &argv);
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
GtkWidget *box1 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_container_add(GTK_CONTAINER(window), box1);
GtkWidget *tv1 = gtk_text_view_new();
GtkWidget *tv2 = gtk_text_view_new();
gtk_box_pack_start(GTK_BOX(box1), tv1, TRUE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(box1), tv2, TRUE, TRUE, 0);
GtkTextBuffer *buf1 = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv1));
GtkTextBuffer *buf2 = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tv2));
gtk_text_buffer_set_text(buf1, "asdf", -1);
gtk_text_buffer_set_text(buf2, "ghjkl", -1);
g_signal_connect(tv2, "focus-in-event", G_CALLBACK(cb), NULL);
gtk_widget_show(tv1);
gtk_widget_show(tv2);
gtk_widget_show(box1);
gtk_widget_show(window);
gtk_main();
return 0;
}
What should I change to get the desired result?
Connect the callback function to the button-release-event instead.
I expect the focus-in-event isn't triggered when you click on the text view when it already has the input focus. Try using button-press-event instead.