OSDN Git Service

add optional tree layout of the results list; [--enable-gstreamer] builds fine, yet...
[eb123/eb123.git] / src / popupwnd.c
1
2 #include "defs.h"
3
4 #include "dicts.h"
5 #include "ebook.h"
6 #include "jcode.h"
7 #include "history.h"
8 #include "mainwnd.h"
9 #include "popupwnd.h"
10 #include "textview.h"
11
12 #define SELECTION_TIMEOUT 300
13 #define POPUPWND_TIMEOUT 1000
14 #define DISTANCE_OFFSET 20
15
16 Popupwnd *_popupwnd = NULL;
17
18 G_DEFINE_TYPE(Popupwnd, popupwnd, GTK_TYPE_WINDOW);
19
20 static void popupwnd_set_property(GObject *object, guint param_id, const GValue *value, GParamSpec *pspec)
21 {
22     Popupwnd *pw = POPUPWND(object);
23
24     switch(param_id)
25     {
26         case 1:
27             pw->mainwnd = g_value_get_pointer(value);
28             Mainwnd *mw = MAINWND(pw->mainwnd);
29             pw->prefs = mw->prefs;
30             pw->builder = mw->builder;
31             GtkWidget *vbox = GTK_WIDGET(gtk_builder_get_object(GTK_BUILDER(pw->builder), "vbox9"));
32             gtk_widget_reparent(vbox, GTK_WIDGET(pw));
33             GtkContainer *scroll = GTK_CONTAINER(gtk_builder_get_object(GTK_BUILDER(pw->builder), "popupwnd_scroll"));
34             pw->text = g_object_new(TYPE_TEXTVIEW, "prefs", pw->prefs, NULL);
35             gtk_container_add(scroll, GTK_WIDGET(pw->text));
36             break;
37         default:
38             G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
39             break;
40     }
41 }
42
43 static gboolean popuwnd_hide_timeout_cb(gpointer data)
44 {
45     Popupwnd *pw = POPUPWND(data);
46     GtkWidget *wt = GTK_WIDGET(pw);
47
48     if(popupwnd_locked(pw))
49         return True;
50     if(!gtk_widget_get_visible(wt))
51         return False;
52
53     if(pw->menu)
54     {
55         if(gtk_widget_get_visible(pw->menu))
56             return True;
57     }
58
59     int x, y, w, h, cx, cy;
60     gdk_window_get_root_origin(wt->window, &x, &y);
61     w = wt->allocation.width;
62     h = wt->allocation.height;
63     cx = x + w/2;
64     cy = y + h/2;
65
66     GdkScreen *screen = gtk_window_get_screen(GTK_WINDOW(pw));
67     GdkDisplay *display = gdk_screen_get_display(screen);
68     gdk_display_get_pointer(display, NULL, &x, &y, NULL);
69
70     if((abs(x - cx) < (w/2 + DISTANCE_OFFSET)) && (abs(y - cy) < (h/2 + DISTANCE_OFFSET)))
71         return True;
72
73     gtk_widget_hide(wt);
74     return False;
75 }
76
77 static void popupwnd_timer(Popupwnd *self)
78 {
79     static gint timeout_id = 0;
80     if(timeout_id != 0)
81         g_source_remove(timeout_id);
82     timeout_id = 0;
83     timeout_id = g_timeout_add(POPUPWND_TIMEOUT, popuwnd_hide_timeout_cb, self);
84 }
85
86 static void popupwnd_class_init(PopupwndClass *klass)
87 {
88     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
89     gobject_class->set_property = popupwnd_set_property;
90     g_object_class_install_property(gobject_class, 1, g_param_spec_pointer("mainwnd", _("Mainwnd"), _("Mainwnd"), G_PARAM_WRITABLE | G_PARAM_CONSTRUCT));
91 }
92
93 static void popupwnd_init(Popupwnd *self)
94 {
95     if(!_popupwnd)
96         _popupwnd = self;
97     else
98         g_assert(1);
99
100     self->timeout_id = 0;
101     self->lookup_started = False;
102     self->lookup_suspended = True;
103     self->drag = False;
104     self->menu = NULL;
105     self->binfo = NULL;
106     self->results = g_sequence_new(result_free);
107     self->x = -1;
108     self->y = -1;
109     gtk_window_set_position(GTK_WINDOW(self), GTK_WIN_POS_CENTER);
110 }
111
112 const gchar* popupwnd_selection_copy(Popupwnd *self, gboolean force_copy)
113 {
114     GtkClipboard* clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
115     gchar *str = gtk_clipboard_wait_for_text(clipboard);
116     static gchar *sel = NULL, *sel_prev = NULL;
117     gint maxchar = prefs_get_int(self->prefs, "selection.maxchar");
118     if(!str || (self->lookup_suspended && !force_copy))
119     {
120         if(sel_prev)
121             g_free(sel_prev);
122         sel_prev = NULL;
123         return NULL;
124     }
125     g_strstrip(str);
126     if(strlen(str) > maxchar)
127     {
128         if(sel_prev)
129             g_free(sel_prev);
130         sel_prev = NULL;
131         g_free(str);
132         return NULL;
133     }
134     if(force_copy)
135         sel = str;
136     else
137     {
138         if(!sel)
139         {
140             sel = str;
141             return NULL;
142         }
143         if(strncmp(str, sel, maxchar))
144         {
145             g_free(sel);
146             sel = str;
147             return NULL;
148         }
149         g_free(str);
150         if((sel_prev) ? !strncmp(sel_prev, sel, maxchar) : FALSE)
151             return NULL;
152     }
153     sel_prev = sel;
154     sel = NULL;
155     return sel_prev;
156 }
157
158 static void popupwnd_dict_changed_cb(GtkWidget *w, gpointer data)
159 {
160     _popupwnd->binfo = (BOOK_INFO*)data;
161     popupwnd_search(_popupwnd, NULL);
162 }
163
164 static gboolean popupwnd_dict_menu_append(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data)
165 {
166     Popupwnd *pw = POPUPWND(data);
167     static GSList *group = NULL;
168     static GtkWidget *submenu = NULL;
169     GtkWidget *item;
170     if(!model)
171     {
172         if(pw->menu)
173             gtk_widget_destroy(pw->menu);
174         group = NULL;
175         pw->menu = gtk_menu_new();
176         item = gtk_radio_menu_item_new_with_label(group, _("Main window selection"));
177         g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(popupwnd_dict_changed_cb), NULL);
178         group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
179         gtk_menu_shell_append(GTK_MENU_SHELL(pw->menu), item);
180         gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); 
181         submenu = NULL;
182         return FALSE;
183     }
184     if(gtk_tree_path_get_depth(path) == 1)
185     {
186         gchar *name;
187         submenu = gtk_menu_new();
188         gtk_tree_model_get(model, iter, DICT_ALIAS, &name, -1);
189         gtk_menu_shell_append(GTK_MENU_SHELL(pw->menu), item = gtk_menu_item_new_with_label(name));
190         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
191         g_free(name);
192     }
193     else
194     {
195         gchar *name;
196         BOOK_INFO *binfo;
197         gtk_tree_model_get(model, iter, DICT_ALIAS, &name, DICT_BINFO, &binfo, -1);
198         item = gtk_radio_menu_item_new_with_label(group, binfo->title);
199         group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(item));
200         gtk_menu_shell_append(GTK_MENU_SHELL(submenu), item);
201         if(binfo == pw->binfo)
202             gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE); 
203         g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(popupwnd_dict_changed_cb), binfo);
204         g_free(name);
205     }
206     return FALSE;
207 }
208
209 static void popupwnd_dict_menu_create(Popupwnd *self, gboolean newmenu)
210 {
211     if(newmenu || !self->menu)
212     {
213         popupwnd_dict_menu_append(NULL, NULL, NULL, self);
214         GtkTreeModel *store = GTK_TREE_MODEL(gtk_builder_get_object(GTK_BUILDER(self->builder), "dicts_store"));
215         gtk_tree_model_foreach(GTK_TREE_MODEL(store), popupwnd_dict_menu_append, self);
216     }
217 }
218
219 void popupwnd_dict_btn_clicked_cb(GtkWidget *widget, gpointer data)
220 {
221     Mainwnd *mw = MAINWND(data);
222     Popupwnd *pw = mw->popupwnd;
223     popupwnd_dict_menu_create(pw, False);
224     gtk_widget_show_all(pw->menu);
225     gtk_menu_popup(GTK_MENU(pw->menu), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time());
226 }
227
228 void popupwnd_render(gpointer data, gpointer user_data)
229 {
230     Popupwnd *pw = POPUPWND(user_data);
231     const gchar *word = builder_get_str(pw->builder, "popupwnd_label");
232     RESULT *res = (RESULT*)data;
233     textview_open(pw->text, res, FALSE, word);
234 }
235
236 static void popupwnd_calculate_coordinates(Popupwnd *self, gint *x, gint *y, gint *w, gint *h)
237 {
238     GdkModifierType mask;
239     gint pointer_x, pointer_y, root_x, root_y;
240
241     *w = prefs_get_int(self->prefs, "popupwnd.w");
242     *h = prefs_get_int(self->prefs, "popupwnd.h");
243     *w = (*w > 100) ? *w : 100;
244     *h = (*h > 100) ? *h : 100;
245     gboolean remember_pos = prefs_get_int(self->prefs, "popupwnd.remember_pos");
246     if(remember_pos && (self->x >= 0) && (self->y >= 0))
247     {
248         *x = self->x;
249         *y = self->y;
250     }
251     else
252     {
253         GdkWindow *root_win = gdk_x11_window_foreign_new_for_display(gdk_display_get_default(), GDK_ROOT_WINDOW());
254         root_x = gdk_window_get_width(root_win);
255         root_y = gdk_window_get_height(root_win);
256         gdk_window_get_pointer(root_win, &pointer_x, &pointer_y, &mask);
257         *x = pointer_x + 10;
258         *y = pointer_y + 10;
259
260         if(*x + *w > root_x)
261             *x = root_x - *w;
262
263         if(*y + *h > root_y)
264             *y = root_y - *h;
265     }
266 }
267
268 void popupwnd_search(Popupwnd *self, const gchar *str)
269 {
270     GtkLabel *label = GTK_LABEL(gtk_builder_get_object(GTK_BUILDER(self->builder), "popupwnd_label"));
271     popupwnd_dict_menu_create(self, False);
272     textview_clear_textbuf(GTK_TEXT_VIEW(self->text));
273     if(str)
274         gtk_label_set_text(label, str);
275     if(!str)
276     {
277         str = gtk_label_get_text(label);
278         if(!str)
279             textview_insert_message(self->text, "<No text selected>", TRUE);
280     }
281     if(!gtk_widget_get_visible(GTK_WIDGET(self)))
282     {
283         gint x, y, w, h;
284         popupwnd_calculate_coordinates(self, &x, &y, &w, &h);
285         gtk_window_move(GTK_WINDOW(self), x, y);
286         gtk_widget_set_size_request(GTK_WIDGET(self), w, h);
287     }
288
289     popupwnd_timer(self);
290     gtk_widget_show_all(GTK_WIDGET(self));
291     while(gtk_events_pending())
292         gtk_main_iteration();
293
294     if(!str)
295         return;
296
297     gint search_method = prefs_get_int(self->prefs, "popupwnd.search_method");
298     gint maxhits = prefs_get_int(self->prefs, "popupwnd.maxhits");
299     if(self->binfo)
300         ebook_search_book(str, search_method, self->results, maxhits, self->binfo);
301     else
302         ebook_search(str, search_method, self->results, maxhits, False);
303
304     if(g_sequence_get_length(self->results))
305         g_sequence_foreach(self->results, popupwnd_render, self);
306     else
307         textview_insert_message(self->text, "<No hits>", TRUE);
308 }
309
310 gboolean popupwnd_search_cb(gpointer data)
311 {
312     Popupwnd *self = POPUPWND(data);
313     const gchar *sel = popupwnd_selection_copy(self, False);
314     if(sel)
315     {
316         gint jap_only = prefs_get_int(self->prefs, "popupwnd.jap_only");
317         if(jap_only)
318             if(!validate_utf8(sel, True))
319                 return True;
320         popupwnd_search(self, sel);
321     }
322     return True;
323 }
324
325 void popupwnd_lookup_resume(Popupwnd *self)
326 {
327     if(self->lookup_started)
328     {
329         if(self->timeout_id != 0)
330             g_source_remove(self->timeout_id);
331         self->timeout_id = g_timeout_add(SELECTION_TIMEOUT, popupwnd_search_cb, self);
332     }
333     self->lookup_suspended = FALSE;
334 }
335
336 void popupwnd_lookup_start(Popupwnd *self)
337 {
338     self->lookup_started = TRUE;
339     popupwnd_lookup_resume(self);
340 }
341
342 void popupwnd_lookup_suspend(Popupwnd *self)
343 {
344     if(self->timeout_id != 0)
345         g_source_remove(self->timeout_id);
346     self->timeout_id = 0;
347     self->lookup_suspended = TRUE;
348     gtk_widget_hide(GTK_WIDGET(self));
349 }
350
351 void popupwnd_lookup_stop(Popupwnd *self)
352 {
353     self->lookup_started = False;
354     popupwnd_lookup_suspend(self);
355 }
356
357 void popupwnd_find_btn_clicked_cb(GtkWidget *widget, gpointer data)
358 {
359     Mainwnd *mw = MAINWND(data);
360     const gchar *str = builder_get_str(mw->builder, "popupwnd_label");
361     gtk_window_deiconify(GTK_WINDOW(mw));
362     mainwnd_search(mw, str, -1);
363 }
364
365 void popupwnd_close_btn_clicked_cb(GtkWidget *widget, gpointer data)
366 {
367     Mainwnd *mw = MAINWND(data);
368     Popupwnd *self = mw->popupwnd;
369     gtk_widget_hide(GTK_WIDGET(self));
370 }
371
372 gint popupwnd_evtbox_motion_notify_event_cb(GtkWidget *widget, GdkEventMotion *event, gpointer data)
373 {
374     Mainwnd *mw = MAINWND(data);
375     Popupwnd *self = mw->popupwnd;
376     if(!self->drag)
377         return TRUE;
378     gint x = self->dx + event->x_root;
379     gint y = self->dy + event->y_root;
380     if(x < 0) x = 0;
381     if(y < 0) y = 0;
382     gtk_window_move(GTK_WINDOW(self), x, y);
383     self->x = x;
384     self->y = y;
385     return TRUE;
386 }
387
388 gint popupwnd_evtbox_button_press_event_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
389 {
390     Mainwnd *mw = MAINWND(data);
391     Popupwnd *self = mw->popupwnd;
392     if(event->type==GDK_2BUTTON_PRESS)
393     {
394         self->drag = FALSE;
395         popupwnd_lookup_stop(self);
396     }
397     else
398     {
399         self->drag = TRUE;
400         gint ex, ey, wx, wy;
401         ex = event->x_root;
402         ey = event->y_root;
403         gtk_window_get_position(GTK_WINDOW(self), &wx, &wy);
404         self->dx = wx - ex;
405         self->dy = wy - ey;
406     }
407     return FALSE;
408 }
409
410 gint popupwnd_evtbox_button_release_event_cb(GtkWidget *widget, GdkEventButton *event, gpointer data)
411 {
412     Mainwnd *mw = MAINWND(data);
413     Popupwnd *self = mw->popupwnd;
414     self->drag = FALSE;
415     return FALSE;
416 }
417
418 void popupwnd_search_selection(gpointer data)
419 {
420     Mainwnd *mw = MAINWND(data);
421     const gchar *word = popupwnd_selection_copy(mw->popupwnd, True);
422     popupwnd_search(mw->popupwnd, word);
423 }
424
425 gboolean popupwnd_locked(Popupwnd *self)
426 {
427     if(!gtk_widget_get_visible(GTK_WIDGET(self)))
428         return False;
429     GtkToggleButton *btn = GTK_TOGGLE_BUTTON(gtk_builder_get_object(GTK_BUILDER(self->builder), "popupwnd_lock_btn"));
430     return gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(btn));
431 }
432