1 /* -*- Mode: C; tab-width: 2; indent-tabs-mode: t; c-basic-offset: 2 -*- */
2 /* IM-JA Japanese Input Method Module for GTK-2.0
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  *
19  * Authors: Botond Botyanszki <boti@rocketmail.com>
20  *
21  */
22 
23 #include <config.h>
24 
25 #include <gtk/gtk.h>
26 #include <gtk/gtkimmodule.h>
27 #include <string.h>
28 
29 #include "im-ja.h"
30 #include "im-ja-impl.h"
31 #include "common.h"
32 #include "error.h"
33 #include "conf.h"
34 #include "nls.h"
35 #include "preeditwin.h"
36 #include "preeditarea.h"
37 #include "actionmenu.h"
38 
39 extern IMJAConfig cfg;
40 GList *preedit_windows = NULL;
41 
42 static void destroy_win(PreeditWin *preedit_win, gboolean remove_signal);
43 
44 
preedit_window_disable_focus_out(GtkWidget * widget,GdkEvent * event,IMJAContext * cn)45 static void preedit_window_disable_focus_out(GtkWidget *widget, GdkEvent *event, IMJAContext* cn ) {
46 	cn->preedit_win->can_hide = FALSE;
47 }
48 
preedit_window_press_cb(GtkWidget * button,GdkEventButton * event,IMJAContext * cn)49 static void preedit_window_press_cb(GtkWidget *button, GdkEventButton *event, IMJAContext* cn) {
50 	IM_JA_DEBUG("preedit_window_press_cb\n");
51 	if (event->button == 1) {
52 		im_ja_actionmenu_button_press_cb(button, event, &cn->preedit_win->menu,
53 																		 IM_JA_STATUSWIN_MENU, cn);
54 	}
55 }
56 
preedit_window_show(IMJAContext * cn)57 void preedit_window_show(IMJAContext* cn) {
58 	IM_JA_DEBUG("preedit_window_show\n");
59 
60 	if ((cn->input_method == IM_JA_DIRECT_INPUT) || (cn->input_method == IM_JA_KANJIPAD_INPUT)) {
61 		preedit_window_hide(cn);
62 		return;
63 	}
64 
65 #ifdef IMJA_TARGET_GTK
66 	if ((cn->toplevel_gtk != NULL) && GTK_WIDGET_HAS_GRAB(cn->toplevel_gtk) == TRUE) {
67 		IM_JA_DEBUG("GRABBED TOPLEVEL!\n");
68 	}
69 
70 	/* FIXME: gtk bug */
71 	if (cn->client_gtk != NULL) {
72 		if (GTK_IS_ENTRY(cn->client_gtk) == TRUE) {
73 			if (gtk_editable_get_editable(GTK_EDITABLE(cn->client_gtk)) == FALSE) {
74 			IM_JA_DEBUG("non-editable GtkEntry.\n");
75 			return;
76 			}
77 		}
78 		if (GTK_IS_TEXT_VIEW(cn->client_gtk) == TRUE) {
79 			if (gtk_text_view_get_editable(GTK_TEXT_VIEW(cn->client_gtk)) == FALSE) {
80 				IM_JA_DEBUG("non-editable GtkTextView.\n");
81 				return;
82 			}
83 		}
84 	}
85 #endif
86 
87 	/* cn->preedit_win = g_object_get_data(G_OBJECT(cn->toplevel_gdk), "im-ja-preedit-window"); */
88 	if (cn->preedit_win == NULL) {
89 		GtkWidget *border;
90 		GtkWidget *input_border;
91 		GtkWidget *hbox;
92 		/* IM_JA_DEBUG("preedit_window_create\n"); */
93 		cn->preedit_win = g_new0(PreeditWin, 1);
94 		cn->preedit_win->menu = NULL;
95 
96 		/* g_object_set_data(G_OBJECT(cn->toplevel_gdk), "im-ja-preedit-window", cn->preedit_win); */
97 		preedit_windows = g_list_append(preedit_windows, cn->preedit_win);
98 		cn->preedit_win->can_hide = TRUE;
99 		cn->preedit_win->window = gtk_window_new(GTK_WINDOW_POPUP);
100 		im_ja_join_modal_window(cn, cn->preedit_win->window);
101 		/*gtk_window_set_decorated(GTK_WINDOW(cn->preedit_win->window), FALSE);*/
102 		gtk_window_set_title(GTK_WINDOW(cn->preedit_win->window), _("[preedit window]"));
103 		gtk_window_set_default_size(GTK_WINDOW(cn->preedit_win->window), 140, 25);
104 
105 		cn->preedit_win->eventbox = gtk_event_box_new();
106 		gtk_container_add(GTK_CONTAINER(cn->preedit_win->window), cn->preedit_win->eventbox);
107 		gtk_widget_show(cn->preedit_win->eventbox);
108 		gtk_widget_realize(cn->preedit_win->eventbox);
109 		gdk_window_set_cursor(cn->preedit_win->eventbox->window, gdk_cursor_new(GDK_HAND2));
110 
111 		/* If the preedit gets the focus, we should be still receiving keyboard input */
112 		g_signal_connect(G_OBJECT(cn->preedit_win->eventbox), "enter_notify_event",
113 										 G_CALLBACK(im_ja_grab_add), cn);
114 		g_signal_connect(G_OBJECT(cn->preedit_win->eventbox), "leave_notify_event",
115 										 G_CALLBACK(im_ja_grab_remove), cn);
116 		g_signal_connect(G_OBJECT(cn->preedit_win->eventbox), "button_press_event",
117 										 G_CALLBACK(preedit_window_press_cb), cn);
118 
119 		border = gtk_frame_new(NULL);
120 		gtk_frame_set_shadow_type(GTK_FRAME(border), GTK_SHADOW_OUT);
121 		gtk_container_add(GTK_CONTAINER(cn->preedit_win->eventbox), border);
122 
123 		hbox = gtk_hbox_new(FALSE, 5);
124 		gtk_container_add(GTK_CONTAINER(border), hbox);
125 
126 		input_border = gtk_frame_new(NULL);
127 
128 		/* cn->preedit_win->text_view = gtk_label_new(cn->preedit_buf); */
129 		cn->preedit_win->text_view = preedit_area_new(cn->preedit_buf);
130 
131 		gtk_misc_set_alignment(GTK_MISC(cn->preedit_win->text_view), 0.0, 0.5);
132 		gtk_container_add(GTK_CONTAINER(input_border), cn->preedit_win->text_view);
133 		gtk_box_pack_start(GTK_BOX(hbox), input_border, TRUE, TRUE, 0);
134 
135 		cn->preedit_win->label = gtk_label_new(cfg.status_win_labels[cn->input_method]);
136 		gtk_box_pack_start(GTK_BOX(hbox), cn->preedit_win->label, FALSE, FALSE, 0);
137 
138 
139 		/* This hack is to disable flashy behaviour when window gets the focus*/
140 		g_signal_connect(G_OBJECT(cn->preedit_win->window), "enter_notify_event",
141 										 G_CALLBACK(preedit_window_disable_focus_out), cn);
142 		g_signal_connect_swapped(G_OBJECT(cn->preedit_win->text_view), "realize",
143 														 G_CALLBACK(preedit_window_update_location), cn);
144 		/*
145 		cn->preedit_win->size_allocate_id = g_signal_connect_swapped(G_OBJECT(cn->toplevel_gtk), "size_allocate",
146 																																 G_CALLBACK(preedit_window_update_location), cn);
147 		g_signal_connect_swapped(G_OBJECT(cn->toplevel_gtk), "destroy",
148 														 G_CALLBACK(toplevel_destroyed_cb), cn->preedit_win);
149 		//cn->preedit_win->toplevel_gtk = cn->toplevel_gtk;
150 		*/
151 	}
152 	else {
153 		gtk_window_resize(GTK_WINDOW(cn->preedit_win->window), 140, 25);
154 		preedit_window_update(cn);
155 		gtk_label_set_text(GTK_LABEL(cn->preedit_win->label), cfg.status_win_labels[cn->input_method]);
156 		preedit_window_update_location(cn);
157 	}
158 #ifdef IMJA_TARGET_XIM
159 	preedit_window_hide_all();
160 #endif
161 	gtk_widget_show_all(cn->preedit_win->window);
162 	cn->preedit_win_on = TRUE;
163 }
164 
preedit_window_force_hide(IMJAContext * cn)165 void preedit_window_force_hide(IMJAContext *cn) {
166 	IM_JA_DEBUG("preedit_window_force_hide\n");
167 
168 	if (cn->preedit_win != NULL) {
169 		gtk_widget_hide_all(cn->preedit_win->window);
170 	}
171 }
172 
preedit_window_hide(IMJAContext * cn)173 void preedit_window_hide(IMJAContext *cn) {
174 	IM_JA_DEBUG("preedit_window_hide\n");
175 
176 	if (cn->preedit_win != NULL) {
177 		if (cn->preedit_win->menu != NULL) {
178 			/* don't hide if we have the menu pulled out */
179 			if ((GTK_WIDGET_HAS_GRAB(cn->preedit_win->menu) == TRUE)
180 					&& (cn->preedit_win_on == TRUE)) return;
181 		}
182 		if (gtk_widget_is_focus(cn->preedit_win->window) == TRUE) return;
183 
184 		if (cn->preedit_win->can_hide == TRUE) {
185 			gtk_widget_hide_all(cn->preedit_win->window);
186 		}
187 	}
188 }
189 
190 /*
191 static void toplevel_destroyed_cb(GtkWidget *toplevel, GdkEvent *event, PreeditWin *preedit_win) {
192 	IM_JA_DEBUG("toplevel_destroyed_cb()\n");
193 	destroy_win(preedit_win, FALSE);
194 }
195 */
196 
destroy_win(PreeditWin * preedit_win,gboolean remove_signal)197 static void destroy_win(PreeditWin *preedit_win, gboolean remove_signal) {
198 	IM_JA_DEBUG("destroy_win() [%d]\n", (int) preedit_win);
199 	if (preedit_win == NULL) return;
200 	if (g_list_find(preedit_windows, preedit_win) == NULL) {
201 		IM_JA_DEBUG("*ERROR* preedit window is already destroyed\n");
202 		return;
203 	}
204 	preedit_windows = g_list_remove(preedit_windows, preedit_win);
205 	/*
206 	if (remove_signal == TRUE) {
207 		if (GTK_IS_WIDGET(preedit_win->toplevel_gtk)) {
208 			g_signal_handler_disconnect(preedit_win->toplevel_gtk, preedit_win->size_allocate_id);
209 		}
210 	}
211 	*/
212 	if (GTK_IS_WIDGET(preedit_win->window) == TRUE) gtk_widget_destroy(preedit_win->window);
213 	/* g_free(preedit_win); this causes a segfault in some cases */
214 }
215 
preedit_window_destroy(IMJAContext * cn)216 void preedit_window_destroy(IMJAContext *cn) {
217 	IM_JA_DEBUG("preedit_window_destroy()\n");
218 	if (cn->preedit_win == NULL) return;
219 	destroy_win(cn->preedit_win, TRUE);
220 	cn->preedit_win = NULL;
221 }
222 
preedit_window_destroy_all()223 void preedit_window_destroy_all() {
224 	IM_JA_DEBUG("preedit_window_destroy_all()\n");
225 	g_list_foreach(preedit_windows, (GFunc) destroy_win, (gpointer) TRUE);
226 	g_list_free(preedit_windows);
227 	preedit_windows = NULL;
228 }
229 
preedit_window_hide_all()230 void preedit_window_hide_all() {
231 	GList *windows = preedit_windows;
232 	PreeditWin *preedit_win;
233 
234 	IM_JA_DEBUG("preedit_window_hide_all()\n");
235 
236 	while (windows != NULL) {
237 		preedit_win = (PreeditWin *) windows->data;
238 		if (preedit_win != NULL) {
239 			if (gtk_widget_is_focus(preedit_win->window) == TRUE) return;
240 			if (preedit_win->can_hide == TRUE) gtk_widget_hide_all(preedit_win->window);
241 		}
242 		windows = g_list_next(windows);
243 	}
244 }
245 
preedit_window_set_text(IMJAContext * cn,gchar * text)246 void preedit_window_set_text(IMJAContext *cn, gchar *text) {
247 	GtkRequisition requisition;
248 	gint width, height, new_w;
249 
250 	/* IM_JA_DEBUG("preedit_window_set_text\n"); */
251 
252 	preedit_area_set_text(PREEDIT_AREA(cn->preedit_win->text_view), text);
253 
254 	gtk_widget_size_request(GTK_WIDGET(cn->preedit_win->window), &requisition);
255 	gtk_window_get_size(GTK_WINDOW(cn->preedit_win->window), &width, &height);
256 
257 	if (requisition.width < width) {
258 		new_w = requisition.width > 140 ? requisition.width + 5 : 140;
259 		gtk_window_resize(GTK_WINDOW(cn->preedit_win->window), new_w, 25);
260 	}
261 
262 }
263 
preedit_window_set_attributes(IMJAContext * cn,PangoAttrList * attrs)264 void preedit_window_set_attributes(IMJAContext *cn, PangoAttrList *attrs) {
265 	/* IM_JA_DEBUG("preedit_window_set_attributes\n"); */
266 	preedit_area_set_attributes(PREEDIT_AREA(cn->preedit_win->text_view), attrs);
267 }
268 
preedit_window_set_cursor_pos(IMJAContext * cn,gint pos)269 void preedit_window_set_cursor_pos(IMJAContext *cn, gint pos) {
270 	/* IM_JA_DEBUG("preedit_window_set_cursor_pos\n"); */
271 	preedit_area_set_cursor_pos(PREEDIT_AREA(cn->preedit_win->text_view), pos);
272 }
273 
preedit_window_update_location(IMJAContext * cn)274 void preedit_window_update_location(IMJAContext *cn) {
275 	gint target_x = 0, target_y = 0;
276 	GdkRectangle toplevel_rect;
277 	GdkRectangle client_rect;
278 
279 	/* IM_JA_DEBUG("im_ja_update_preedit_window_location\n"); */
280 	if (cn->preedit_win !=	NULL) {
281 
282 		im_ja_get_toplevel_window_geometry(cn, &toplevel_rect);
283 
284 		if ((cn->preedit_win_pos_offset_x == 0) && (cn->preedit_win_pos_offset_y == 0)) {
285 			im_ja_attach_bottom_left(cn, cn->preedit_win->window);
286 		}
287 		else {
288 			im_ja_get_client_window_geometry(cn, &client_rect);
289 			target_x = client_rect.x + cn->preedit_win_pos_offset_x;
290 			target_y = client_rect.y + cn->preedit_win_pos_offset_y;
291 			/* IM_JA_DEBUG("update preedit window location x: %d, y: %d\n", target_x, target_y); */
292 
293 			im_ja_move_within_rect(cn, &target_x, &target_y, &client_rect);
294 			/* IM_JA_DEBUG("update preedit window location x: %d, y: %d\n", target_x, target_y); */
295 			gtk_window_move((GTK_WINDOW(cn->preedit_win->window)), target_x, target_y);
296 		}
297 	}
298 }
299 
preedit_window_update(IMJAContext * cn)300 void preedit_window_update(IMJAContext *cn) {
301 	PangoAttrList *preedit_win_attrs;
302 	PangoAttribute *backgr;
303 	PangoAttribute *foregr;
304 	PangoAttribute *underline;
305 	gint preedit_cursor = 0;
306 
307 	/* IM_JA_DEBUG("preedit_window_update\n"); */
308 
309 	if ((cn->preedit_win == NULL) || (GTK_IS_WIDGET(cn->preedit_win->window) == FALSE)) return;
310 
311 	if (strlen(cn->preedit_buf) == 0) {
312 		preedit_window_set_text(cn, "");
313 		preedit_window_set_cursor_pos(cn, 0);
314 	}
315 
316 	preedit_win_attrs = pango_attr_list_new(); /* Create new attrib list for the preedit window */
317 
318 	underline = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
319 	underline->start_index = 0;
320 	underline->end_index = strlen(cn->preedit_buf);
321 	pango_attr_list_insert(preedit_win_attrs, underline);
322 
323 	if (cfg.custom_preedit_n == TRUE) {
324 		foregr = pango_attr_foreground_new(cfg.preedit_colors[0].red, cfg.preedit_colors[0].green, cfg.preedit_colors[0].blue);
325 		backgr = pango_attr_background_new(cfg.preedit_colors[1].red, cfg.preedit_colors[1].green, cfg.preedit_colors[1].blue);
326 		backgr->start_index = 0;
327 		foregr->start_index = 0;
328 		backgr->end_index = strlen(cn->preedit_buf);
329 		foregr->end_index = strlen(cn->preedit_buf);
330 		pango_attr_list_insert(preedit_win_attrs, backgr);
331 		pango_attr_list_insert(preedit_win_attrs, foregr);
332 	}
333 
334 	if (cn->preedit_reverse_start != cn->preedit_reverse_end) {
335 		/* Highlighted (==reverse) field */
336 		if (cfg.custom_preedit_hl == TRUE) {
337 			foregr = pango_attr_foreground_new(cfg.preedit_colors[2].red, cfg.preedit_colors[2].green, cfg.preedit_colors[2].blue);
338 			backgr = pango_attr_background_new(cfg.preedit_colors[3].red, cfg.preedit_colors[3].green, cfg.preedit_colors[3].blue);
339 		}
340 		else { /* Use the default */
341 			foregr = pango_attr_foreground_new(cn->original_colors[0].red, cn->original_colors[0].green, cn->original_colors[0].blue);
342 			backgr = pango_attr_background_new(cn->original_colors[1].red, cn->original_colors[1].green, cn->original_colors[1].blue);
343 		}
344 		backgr->start_index = cn->preedit_reverse_start;
345 		foregr->start_index = cn->preedit_reverse_start;
346 		backgr->end_index = cn->preedit_reverse_end;
347 		foregr->end_index = cn->preedit_reverse_end;
348 
349 		pango_attr_list_insert(preedit_win_attrs, backgr);
350 		pango_attr_list_insert(preedit_win_attrs, foregr);
351 	}
352 
353 	preedit_cursor = im_ja_get_cursor_pos_bytes(cn);
354 	preedit_window_set_cursor_pos(cn, preedit_cursor);
355 	preedit_window_set_text(cn, cn->preedit_buf);
356 	preedit_window_set_attributes(cn, preedit_win_attrs);
357 
358 }
359