1 /*
2 
3   Copyright (c) 2005-2013 uim Project https://github.com/uim/uim
4 
5   All rights reserved.
6 
7   Redistribution and use in source and binary forms, with or without
8   modification, are permitted provided that the following conditions
9   are met:
10 
11   1. Redistributions of source code must retain the above copyright
12      notice, this list of conditions and the following disclaimer.
13   2. Redistributions in binary form must reproduce the above copyright
14      notice, this list of conditions and the following disclaimer in the
15      documentation and/or other materials provided with the distribution.
16   3. Neither the name of authors nor the names of its contributors
17      may be used to endorse or promote products derived from this software
18      without specific prior written permission.
19 
20   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
21   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
24   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30   SUCH DAMAGE.
31 */
32 
33 #include <config.h>
34 
35 #include <gtk/gtk.h>
36 #include <string.h>
37 
38 #include "uim/uim.h"
39 #include "uim/uim-helper.h"
40 #include "uim/gettext.h"
41 
42 #include "caret-state-indicator.h"
43 /*
44  * caret state indicator is a state indicator nearby the caret.
45  */
46 
47 #define DEFAULT_WINDOW_WIDTH  20
48 #define DEFAULT_WINDOW_HEIGHT 20
49 
50 static gint get_current_time(void);
51 static gint caret_state_indicator_timeout(gpointer data);
52 
53 /* This function is not correct, size of tv_sec is glong, not gint */
54 static gint
get_current_time(void)55 get_current_time(void)
56 {
57   GTimeVal result;
58 
59   g_get_current_time(&result);
60   return result.tv_sec;
61 }
62 
63 static gint
caret_state_indicator_timeout(gpointer data)64 caret_state_indicator_timeout(gpointer data)
65 {
66   GtkWidget *window = GTK_WIDGET(data);
67   gint timeout, called_time, current_time;
68 
69   timeout = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window), "timeout"));
70   called_time = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window),
71 				"called_time"));
72   current_time = get_current_time();
73 
74   if ((current_time - called_time) * 1000 >= timeout)
75     gtk_widget_hide(window);
76 
77   g_object_set_data(G_OBJECT(window), "timeout-tag", GUINT_TO_POINTER(0));
78 
79   return FALSE;
80 }
81 
82 static gint
83 #if GTK_CHECK_VERSION(2, 90, 0)
caret_state_indicator_paint_window(GtkWidget * window,cairo_t * cr)84 caret_state_indicator_paint_window(GtkWidget *window, cairo_t *cr)
85 #else
86 caret_state_indicator_paint_window(GtkWidget *window)
87 #endif
88 {
89 #if GTK_CHECK_VERSION(2, 90, 0)
90   gtk_render_frame(gtk_widget_get_style_context(window), cr,
91              0, 0,
92              gtk_widget_get_allocated_width(window),
93              gtk_widget_get_allocated_height(window));
94 #else
95   gtk_paint_flat_box(gtk_widget_get_style(window),
96              gtk_widget_get_window(window),
97              GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, GTK_WIDGET(window),
98              "tooltip", 0, 0, -1, -1);
99 #endif
100 
101   return FALSE;
102 }
103 
104 static gint
caret_state_indicator_destroy_cb(GtkWidget * window)105 caret_state_indicator_destroy_cb(GtkWidget *window)
106 {
107   GList *label_list, *frame_list;
108 
109   label_list = g_object_get_data(G_OBJECT(window), "labels");
110   frame_list = g_object_get_data(G_OBJECT(window), "frames");
111 
112   g_list_free(label_list);
113   g_list_free(frame_list);
114 
115   return FALSE;
116 }
117 
118 GtkWidget *
caret_state_indicator_new(void)119 caret_state_indicator_new(void)
120 {
121   GtkWidget *window, *label, *hbox, *frame;
122   GList *label_list = NULL, *frame_list = NULL;
123 
124   window = gtk_window_new(GTK_WINDOW_POPUP);
125   label  = gtk_label_new("");
126   frame = gtk_frame_new(NULL);
127   gtk_container_add(GTK_CONTAINER(frame), label);
128 #if GTK_CHECK_VERSION(3, 2, 0)
129   hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
130 #else
131   hbox = gtk_hbox_new(TRUE, 0);
132 #endif
133   gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
134   gtk_container_add(GTK_CONTAINER(window), hbox);
135 
136   gtk_window_set_default_size(GTK_WINDOW(window),
137 			      DEFAULT_WINDOW_WIDTH,
138 			      DEFAULT_WINDOW_HEIGHT);
139   gtk_widget_set_app_paintable(window, TRUE);
140 
141 #if GTK_CHECK_VERSION(2, 90, 0)
142   g_signal_connect(window, "draw",
143 		   G_CALLBACK(caret_state_indicator_paint_window),
144 		   NULL);
145 #else
146   g_signal_connect(window, "expose_event",
147 		   G_CALLBACK(caret_state_indicator_paint_window),
148 		   NULL);
149 #endif
150   g_signal_connect(window, "destroy",
151 		   G_CALLBACK(caret_state_indicator_destroy_cb),
152 		   NULL);
153 
154   gtk_misc_set_alignment(GTK_MISC(label), 0.5, 0.5);
155 
156   label_list = g_list_append(label_list, label);
157   frame_list = g_list_append(frame_list, frame);
158   g_object_set_data(G_OBJECT(window), "frames", frame_list);
159   g_object_set_data(G_OBJECT(window), "labels", label_list);
160   g_object_set_data(G_OBJECT(window), "hbox", hbox);
161 
162   return window;
163 }
164 
165 void
caret_state_indicator_update(GtkWidget * window,gint topwin_x,gint topwin_y,const gchar * str)166 caret_state_indicator_update(GtkWidget *window, gint topwin_x, gint topwin_y, const gchar *str)
167 {
168   gint cursor_x, cursor_y;
169 
170   g_return_if_fail(window != NULL);
171 
172   cursor_x = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window), "cursor_x"));
173   cursor_y = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(window), "cursor_y"));
174 
175   if (str) {
176     gchar **cols;
177     GtkWidget *label, *hbox, *frame;
178     GList *label_list, *frame_list, *list1, *list2;
179     gint i;
180 
181     list1 = label_list = g_object_get_data(G_OBJECT(window), "labels");
182     list2 = frame_list = g_object_get_data(G_OBJECT(window), "frames");
183     hbox = g_object_get_data(G_OBJECT(window), "hbox");
184 
185     cols = g_strsplit(str, "\t", 0);
186     for (i = 0; cols[i] && strcmp("", cols[i]); i++) {
187       if (label_list) {
188 	label = label_list->data;
189 	gtk_label_set_text(GTK_LABEL(label), cols[i]);
190       } else {
191 	label = gtk_label_new(cols[i]);
192 	frame = gtk_frame_new(NULL);
193 	gtk_container_add(GTK_CONTAINER(frame), label);
194 	gtk_box_pack_start(GTK_BOX(hbox), frame, TRUE, TRUE, 0);
195 	list1 = g_list_append(list1, label);
196 	label_list = g_list_find(list1, label);
197 	list2 = g_list_append(list2, frame);
198 	frame_list = g_list_find(list2, frame);
199       }
200       label_list = label_list->next;
201       frame_list = frame_list->next;
202     }
203 
204     while (label_list) {
205       label = label_list->data;
206       frame = frame_list->data;
207       label_list = label_list->next;
208       frame_list = frame_list->next;
209       gtk_container_remove(GTK_CONTAINER(frame), label);
210       gtk_container_remove(GTK_CONTAINER(hbox), frame);
211       list1 = g_list_remove(list1, label);
212       list2 = g_list_remove(list2, frame);
213     }
214     g_object_set_data(G_OBJECT(window), "labels", list1);
215     g_object_set_data(G_OBJECT(window), "frames", list2);
216 
217     g_strfreev(cols);
218   }
219 
220   gtk_window_move(GTK_WINDOW(window), topwin_x + cursor_x,
221 		  topwin_y + cursor_y + 3);
222 }
223 
224 void
caret_state_indicator_set_cursor_location(GtkWidget * window,GdkRectangle * cursor_location)225 caret_state_indicator_set_cursor_location(GtkWidget *window, GdkRectangle *cursor_location)
226 {
227   g_return_if_fail(window != NULL);
228 
229   g_object_set_data(G_OBJECT(window), "cursor_x",
230 		    GINT_TO_POINTER(cursor_location->x));
231   g_object_set_data(G_OBJECT(window), "cursor_y",
232 		    GINT_TO_POINTER(cursor_location->y +
233 				    cursor_location->height));
234 }
235 
236 void
caret_state_indicator_set_timeout(GtkWidget * window,gint timeout)237 caret_state_indicator_set_timeout(GtkWidget *window, gint timeout)
238 {
239   gint current_time;
240   guint tag, oldtag;
241 
242   g_return_if_fail(window != NULL);
243 
244   oldtag = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(window), "timeout-tag"));
245 
246   if (oldtag > 0)
247     g_source_remove(oldtag);
248 
249   current_time = get_current_time();
250   tag = g_timeout_add(timeout, caret_state_indicator_timeout, (gpointer)window);
251 
252   g_object_set_data(G_OBJECT(window), "timeout-tag", GUINT_TO_POINTER(tag));
253   g_object_set_data(G_OBJECT(window), "timeout", GINT_TO_POINTER(timeout));
254   /* "called_time" stores the latest time when this function is called */
255   g_object_set_data(G_OBJECT(window), "called_time",
256 		    GINT_TO_POINTER(current_time));
257 }
258