I'm creating a application using C & gtk+2 with custom text input, pango to draw the characters to GtkDrawingArea, everything works fine until a user tells me that he can't input Chinese characters in my application, he use a fcitx input method.
Currently I'm using simple key_press_event & key_release_event GSignals to implement character input, but I don't think it will work for input methods, I found a GtkIMContext api but not sure how to use it.
So, my question is, how to receive characters from fcitx/ibus input methods in gtk+2?
Finally I found the way myself.
First of all, I have a GtkWindow *.
GtkWidget *window;
To support input methods, I have to initialize a GtkIMContext and set the client window, gtk_im_context_set_client_window use GdkWindow * as its second parameter.
GtkIMContext *im_context = gtk_im_multicontext_new();
GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
gtk_im_context_set_client_window(im_context, gdk_window);
The last step, set focus to this im_context.
gtk_im_context_focus_in(im_context);
Now the input methods are available! After this, you can listen for signals of GtkIMContext to handle inputs.
g_signal_connect(im_context, "commit",
G_CALLBACK(commit_callback), NULL);
g_signal_connect(im_context, "preedit-changed",
G_CALLBACK(preedit_changed_callback), NULL);
g_signal_connect(im_context, "retrieve-surrounding",
G_CALLBACK(retrieve_surrounding_callback), NULL);
g_signal_connect(im_context, "delete-surrounding",
G_CALLBACK(delete_surrounding_callback), NULL);
In order to receive english characters in commit signal's callback, you must listen for key-press-event signal and use gtk_im_context_filter_keypress function.
static gboolean key_callback(GtkWidget *widget,
GdkEventKey *event,
GtkIMContext *im_context) {
return gtk_im_context_filter_keypress(im_context, event);
}
g_signal_connect(window, "key-press-event",
G_CALLBACK(key_callback), im_context);
Refenerces:
https://github.com/GNOME/gtk/blob/gtk-2-24/gtk/gtkentry.h
https://github.com/GNOME/gtk/blob/gtk-2-24/gtk/gtkentry.c
https://developer.gnome.org/gtk2/2.24/GtkIMContext.html
How do I get the gdk window for a gtk window in C?
Related
Within my GTK+ application, an instance of GtkFileChooserWidget is permanently visible. By selecting files (single click), the user might process a file. This is done by the callback function switch_file().
g_signal_connect (chooser, "selection-changed", G_CALLBACK (switch_file), gs);
The function switch_file() is sometimes slow as it waits for user response in modal dialogs. Everything works fine unless a file which is currently selected in the FileChooser is deleted (by the application itself or any other process on the system). The FileChooser, which obviously runs in an own thread, submits then a secondary call to switch_file() which leads to a mess. I tried to prevent the multiple calls by using a mutex:
static void switch_file (GtkWidget *widget, gpointer data)
{
info_t *gs = data;
int err;
if ((err = pthread_mutex_lock (&gs->mutex)))
DebugExit ("pthread_mutex_lock(): %s", strerror (err));
/* ... */
}
However, all calls to the callback function are done within the same thread on the same stack. So, the second call to pthread_mutex_lock() fails (PTHREAD_MUTEX_ERRORCHECK_NP is used) and lets the process exit.
Is there a possibility to postpone the calls to the callback function while switch_file() is working? For user events (key presses) it already works but not for signals caused in parallel by deletes files.
If the signals cannot be postponed: What would be a signal-safe solution to collect all selected files and process them subsequently (within the main thread)?
I found that the best solution is to ignore the signal. I am not sure whether the following solution is absolutely safe but it works.
static int switch_ignore = false;
static void switch_file (GtkWidget *widget, gpointer data)
{
info_t *gs = data;
if (switch_ignore)
return;
switch_ignore = true;
/* Do some work on the file system. */
switch_ignore = false;
}
I am new to Gtk and this is my very first Gtk project. I am using C and Gtk+ 2 on Code::Blocks on Windows 7. In my project, I have a main window which initially shows a home screen, holding a button to "Run" the project and some other buttons ("Exit", "About" etc). When this "Run" button is clicked, the program has to read and write some large text files, and then show a new screen with some new data (I am creating this new screen by destroying all the previous children of the main window and putting in new stuff into it).
Now due to large sizes of those text files, the program lags for some time when Run is clicked, so I want to show an intermediate screen having some message like "Loading...". But I am unable to do it, because this intermediate screen is never shown.
Here is what I have; I hope the code makes it clear:
GtkWidget *windowMain = NULL;
GtkWidget *vboxMain = NULL;
//These 2 are global.
void home_screen() //Works well
{
//...Created new main window...
//...Created new main vbox...
//...Added vboxMain to windowMain...
GtkWidget *menuButton = gtk_button_new_with_label("Run");
g_signal_connect (menuButton, "clicked", G_CALLBACK (intermediate_screen), NULL);
gtk_box_pack_start (GTK_BOX (vboxMain), menuButton, TRUE, TRUE, 0); //Add button to vboxMain.
gtk_widget_show_all (windowMain);
}
void intermediate_screen() //Is not shown at correct time
{
// CLEAR MAIN WINDOW:
GList *children, *iter;
children = gtk_container_get_children(GTK_CONTAINER(windowMain));
for(iter = children; iter != NULL; iter = g_list_next(iter))
gtk_widget_destroy(iter->data);
g_list_free(children);
GtkWidget *label = gtk_label_new(NULL);
gtk_label_set_markup(GTK_LABEL(label1), "<b>Loading...</b>");
gtk_container_add (GTK_CONTAINER (windowMain), label);
gtk_widget_show_all(windowMain);
prepare_files(); //Function to work with the text files
}
void prepare_files() //Starts working before "Loading..." is shown
{
//Some file handling which takes some time to complete.
next_screen();
}
void next_screen()
{
// CLEAR MAIN WINDOW AGAIN TO CLEAR THE "Loading..." LABEL:
GList *children, *iter;
children = gtk_container_get_children(GTK_CONTAINER(windowMain));
for(iter = children; iter != NULL; iter = g_list_next(iter))
gtk_widget_destroy(iter->data);
g_list_free(children);
vboxMain = gtk_vbox_new (FALSE, 5);
gtk_widget_set_size_request (vboxMain, 600, 600);
gtk_container_add (GTK_CONTAINER (windowMain), vboxMain);
//Add components to the vboxMain
gtk_widget_show_all(windowMain);
}
Problem is that intermediate_screen() shows the "Loading" message only after the prepare_files() function has been completed - and hence it is of no use. During all that time, only homescreen is being shown... In fact, the next_screen() is shown immediately after, so "Loading" does not even show up. But shouldn't it show the loading message during all that lag, as the prepare_files() function is called later?
What am I doing wrong and how should I correctly do it?
Sorry if this was something obvious. As I said I am a beginner in Gtk+ .
Gtk+ drawing happens on a timer that fires in the same thread as your code. In other words the drawing code can only run when your code is not running: when "Run" is clicked, the next draw can only happen after intermediate_screen() has returned (and "Loading..." screen has already been replaced).
You could add some hacks in intermediate_screen() that runs a few iterations of the main loop so at least one draw would happen but that would still be bad and unresponsive design. There are 2 possible better solutions:
Use an asynchronous API like GIO to read and write to files: this means no function in your code ever runs long enough to disrupt drawing or interacting with the UI. Implementing this is a little more complex than synchronous reads (like the ones you probably use now), one possible short version is: Create a GFile, call g_file_read_async(), in the callback call g_file_read_finish(), create a GDataInputStream from the GFileInputStream you get, then use g_data_input_stream_read_*_async() functions to start reading lines or other chunks of the file, and in that callback get the data with g_data_input_stream_read_*_finish().
Alternatively create a new thread and read the data in that thread using the same reading code you now use. The down side is that you now have to deal with thread-safety yourself -- this can be difficult, and threading bugs are the hardest to debug.
In my opinion option #1 is the best compromise in almost all situations where an asynchronous API is available. Note that if your own processing of the file contents takes a long time, you should do that in small chunks as well (usually it works out nicely so you can e.g. read one line or larger chunk asynchronously and process the line/chunk in the callback).
I use g_thread_new() to create a new thread to implement a flash image effect but only to show the later image but not to flash the 2 images.
And this is the code in the GThreadFunc function:
gdk_threads_enter();
draw_effect(background_map[mbullet.x][mbullet.y +1]);
g_usleep(110000);
draw_background(background_map[mbullet.x][mbullet.y +1]);
gdk_threads_leave();
And this is draw_**()function:
void draw_effect(GtkWidget *widget)
{
gtk_image_set_from_pixbuf(widget,fire_effect);
};
But the program can only show the image that draw_background()draws not draw_effct() does.
Unless I remove the gdk_threads_enter() andgdk_threads_leave() code,can the image flash as I wish.
What's wrong with my code?Can anybody help me?
You're modifying a widget from a thread other than the main thread, which usually leads to all kinds of problems and unexpected behaviors in many GUI libraries, not only gtk.
I do understand the motivation to use a thread here though, since you need to wait a while before redrawing the background.
I recommend that you call draw_effect directly instead of creating a thread, and then use g_timeout_add to schedule a call to a function that will redraw the background.
It should be something like this (please modify as needed and do read the docs):
Define this function:
gboolean
redraw_background (gpointer user_data) {
// ... get references to background_map and mbullet here ...
draw_background(background_map[mbullet.x][mbullet.y +1]);
return FALSE; // so it isn't called again
}
And elsewhere:
// Draw what you need to draw
draw_effect(background_map[mbullet.x][mbullet.y +1]);
// Schedule draw_background 110 ms from now
// user_data will be passed as a parameter to redraw_background
g_timeout_add (110, // milliseconds
redraw_background,
user_data);
What is the proper way to wait until an X11 window is mapped and viewable? Precisely, I want to wait until I can safely call XSetInputFocus() without running into any risks of the X server backfiring with the following error:
// X Error of failed request: BadMatch (invalid parameter attributes)
// Major opcode of failed request: 42 (X_SetInputFocus)
Currently this error happens quite often, especially on slow X servers or when trying to open a new window right after having changed the monitor resolution using libXrandr.
I already have a solution for this problem but it is pretty hacky because it polls the window attribute so I'd like to know whether or not there is a cleaner version.
Here is my current approach:
static Bool predicate(Display *display, XEvent *ev, XPointer arg)
{
return(ev->type == MapNotify);
}
static void waitmapnotify(struct osdisplayinfo *osd)
{
XEvent ev;
XWindowAttributes xwa;
XPeekIfEvent(osd->display, &ev, predicate, NULL);
do {
XGetWindowAttributes(osd->display, osd->window, &xwa);
usleep(1);
} while(xwa.map_state != IsViewable);
}
This code works fine but it is hacky so I'm putting it up for debate here - just in case there is a cleaner way of doing this.
Select SubstructureNotifyMask on the root window. You should get an event each time a top-level window is mapped, unmapped, moved, raised, resized etc. These are the events that potentially change visibility of top-level windows. This program prints a message whenever such an event happens:
#include <X11/Xlib.h>
#include <stdio.h>
int main ()
{
Display* d = XOpenDisplay(0);
int cnt = 0;
XEvent ev;
XSelectInput (d, RootWindow(d, DefaultScreen(d)), SubstructureNotifyMask);
while (1)
{
XNextEvent(d, &ev);
printf ("Got an event %d!\n", cnt++);
// <----- do your XGetWindowAttributes(...) check here
}
}
Note that you may not get events about your own windows getting mapped. This is because the WM is likely to reparent top-level windows to be children not of the root, but of intermediate decoration windows.
There are two ways to cope with the situation:
Check if your window parent, the parent of the parent, ... etc is the mapped window of the event.
Add XSelectInput (d, yourwindow, StructureNotifyMask); to the mix.
Note the first select has SubstructureNotifyMask and the second one StructureNotifyMask, a different mask.
As far as I know X11 lib does not expose any callback mechanics for the X11 Event handling. (you can easily build your own once you understand the event filtering model)
You might want to loop on the X11 event queue as I guest this should be more efficient being designed for that purpose. Moreover you can configure the events filter, so that you get only the events that are of interest for your specific window.
A useful (though dated) link might be:
Linux Journal X11 Tutorial Check page two for an example on installing filters and getting events from the X11 queue.
In my application the main window spawns several dialogs, often multiple dialogs are opened at the same time. On Ubuntu if I minimize the main window each of the child dialogs are also minimized. On Windows XP/Vista/7 the dialogs don't minimize with the main window.
On Windows, is there a way to get all top level windows to minimize when the main application gets minimized.
EDIT:
Solved by setting a signal for a "window-state-event" and manually using gtk_window_iconify.
Setting the signal:
g_signal_connect(G_OBJECT(Main_Application), "window-state-event", G_CALLBACK(minimize_windows), NULL);
Here is the minimize_windows function.
gboolean minimize_windows( GtkWidget *widget, GdkEventWindowState *event, gpointer user_data)
{
GList *glist;
/*Returns a GList of each toplevel window*/
glist = gtk_window_list_toplevels();
/*Iconify check*/
if(event->new_window_state & GDK_WINDOW_STATE_ICONIFIED)
g_list_foreach(glist, (GFunc)gtk_window_iconify,NULL);
/* If not make sure all the windows are deiconified */
else
g_list_foreach(glist, (GFunc)gtk_window_deiconify, NULL);
g_list_free(glist);
return TRUE;
}
Try making the parent form the owner of the child forms:
Form2 f2 = new Form2();
f2.Show(this);