removed libsoup support
[k8lowj.git] / src / util-gtk.c
blobdca0fd61378bb0c213211b17a16af744d0ae5dd9
1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
5 */
7 #include "gtk-all.h"
9 #include "liblj/livejournal.h" /* lj_md5_hash */
11 #include "util-gtk.h"
13 gchar*
14 gettext_translate_func(const gchar *path, gpointer data) {
15 /* gettext returns a const, but gtk wants nonconst. *shrug*. */
16 return (gchar*)gettext(path);
19 GtkWidget* jam_table_new(int rows, int cols) {
20 GtkWidget *table;
21 table = gtk_table_new(rows, cols, FALSE);
22 return table;
24 GtkWidget* jam_table_label(GtkTable *table, int row, const char *text) {
25 GtkWidget *label = gtk_label_new_with_mnemonic(text);
26 gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f);
27 gtk_table_attach(table, GTK_WIDGET(label),
28 0, 1, row, row+1, GTK_FILL, GTK_FILL, 6, 6);
29 return label;
31 void jam_table_content(GtkTable *table, int row, GtkWidget *content) {
32 gtk_table_attach(table, GTK_WIDGET(content),
33 1, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 0, 0);
35 void jam_table_label_content_mne(GtkTable *table, int row, const char *text, GtkWidget *content, GtkWidget *mne) {
36 GtkWidget *label;
37 label = jam_table_label(table, row, text);
38 jam_table_content(table, row, content);
39 gtk_label_set_mnemonic_widget(GTK_LABEL(label), mne);
41 void jam_table_label_content(GtkTable *table, int row, const char *text, GtkWidget *content) {
42 jam_table_label_content_mne(table, row, text, content, content);
44 void jam_table_fillrow(GtkTable *table, int row, GtkWidget *content) {
45 gtk_table_attach(table, GTK_WIDGET(content),
46 0, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 2, 2);
49 void
50 jam_spin_button_set(GtkSpinButton *w,
51 gboolean numeric,
52 gdouble min, gdouble max,
53 gdouble step, gdouble page,
54 gint digits) {
55 g_assert(GTK_IS_SPIN_BUTTON(w));
57 gtk_spin_button_set_numeric(w, numeric);
58 gtk_spin_button_set_range(w, min, max);
59 gtk_spin_button_set_increments(w, step, page);
60 gtk_spin_button_set_digits(w, digits);
63 void
64 jam_win_set_size(GtkWindow *win, int width, int height) {
65 if (width > 0) {
66 if (height > 0) {
67 gtk_window_set_default_size(win, width, height);
68 } else {
69 gtk_window_set_default_size(win, width, (int)(0.618*width));
71 } else if (height > 0) {
72 gtk_window_set_default_size(win, (int)(1.618*height), height);
76 void
77 jam_window_init(GtkWindow *win, GtkWindow *parent, const gchar *title, int width, int height) {
78 if (parent)
79 gtk_window_set_transient_for(GTK_WINDOW(win), parent);
80 if (title)
81 gtk_window_set_title(GTK_WINDOW(win), title);
82 jam_win_set_size(GTK_WINDOW(win), width, height);
85 static void
86 jam_dialog_set_contents_container(GtkDialog *dlg, GtkWidget *container) {
87 gtk_container_set_border_width(GTK_CONTAINER(container), 12);
88 gtk_box_pack_start(GTK_BOX(dlg->vbox), container, TRUE, TRUE, 0);
89 gtk_widget_show_all(dlg->vbox);
90 if (GTK_IS_NOTEBOOK(container))
91 gtk_dialog_set_has_separator(dlg, FALSE);
94 GtkWidget*
95 jam_dialog_set_contents(GtkDialog *dlg, GtkWidget *contents) {
96 GtkWidget *vbox;
97 if (GTK_IS_CONTAINER(contents)) {
98 jam_dialog_set_contents_container(dlg, contents);
99 return contents;
101 vbox = gtk_vbox_new(FALSE, 5);
102 if (contents)
103 gtk_box_pack_start(GTK_BOX(vbox), contents, TRUE, TRUE, 0);
104 jam_dialog_set_contents_container(dlg, vbox);
105 return vbox;
108 GtkWidget*
109 jam_dialog_buttonbox_new(void) {
110 return gtk_hbox_new(FALSE, 5);
112 void
113 jam_dialog_buttonbox_add(GtkWidget *box, GtkWidget *button) {
114 gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
116 GtkWidget*
117 jam_dialog_buttonbox_button_with_label(GtkWidget *box, const char *label) {
118 GtkWidget *button;
119 char buf[100];
120 g_snprintf(buf, 100, " %s ", label);
121 button = gtk_button_new_with_mnemonic(buf);
122 jam_dialog_buttonbox_add(box, button);
123 return button;
125 GtkWidget*
126 jam_dialog_buttonbox_button_from_stock(GtkWidget *box, const char *id) {
127 GtkWidget *button = gtk_button_new_from_stock(id);
128 jam_dialog_buttonbox_add(box, button);
129 return button;
132 void
133 jam_dialog_set_contents_buttonbox(GtkWidget *dlg, GtkWidget *contents, GtkWidget *buttonbox) {
134 GtkWidget *vbox, *hbox;;
136 vbox = jam_dialog_set_contents(GTK_DIALOG(dlg), contents);
138 hbox = gtk_hbox_new(FALSE, 0);
139 gtk_box_pack_end(GTK_BOX(hbox), buttonbox, FALSE, FALSE, 0);
140 gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
141 gtk_widget_show_all(vbox);
145 jam_confirm(GtkWindow *parent, const char *title, const char *msg) {
146 GtkWidget *dlg;
147 int res;
149 dlg = gtk_message_dialog_new(GTK_WINDOW(parent), 0,
150 GTK_MESSAGE_QUESTION,
151 GTK_BUTTONS_YES_NO,
152 "%s", msg);
153 jam_window_init(GTK_WINDOW(dlg), parent, title, -1, -1);
154 res = (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_YES);
155 gtk_widget_destroy(dlg);
156 return res;
159 void
160 gdkcolor_to_hex(GdkColor *color, char* buf) {
161 g_snprintf(buf, 8, "#%02X%02X%02X",
162 color->red >> 8,
163 color->green >> 8,
164 color->blue >> 8);
167 GtkWidget*
168 scroll_wrap(GtkWidget *w) {
169 GtkWidget *scroll;
171 scroll = gtk_scrolled_window_new (NULL, NULL);
172 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
173 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
174 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
175 GTK_SHADOW_IN);
176 gtk_container_add(GTK_CONTAINER(scroll), w);
177 return scroll;
180 static void
181 geometry_save(Geometry *geom, GtkWindow *win, GtkPaned *paned) {
182 if (win && GTK_WIDGET(win)->window) {
183 gtk_window_get_position(win, &geom->x, &geom->y);
184 gtk_window_get_size(win, &geom->width, &geom->height);
186 if (paned) {
187 geom->panedpos = gtk_paned_get_position(paned);
191 static void
192 geometry_load(Geometry *geom, GtkWindow *win, GtkPaned *paned) {
193 if (win && geom->width > 0) {
194 gtk_window_move(win, geom->x, geom->y);
195 gtk_window_set_default_size(win, geom->width, geom->height);
197 if (paned && geom->panedpos > 0) {
198 gtk_paned_set_position(paned, geom->panedpos);
201 static gboolean
202 geometry_win_event(GtkWindow *win, GdkEvent *e, Geometry *geom) {
203 geometry_save(geom, win, NULL);
204 return FALSE;
206 static void
207 geometry_show_cb(GtkWindow *win, Geometry *geom) {
208 geometry_load(geom, win, NULL);
210 static gboolean
211 geometry_paned_event(GtkPaned *paned, gpointer d, Geometry *geom) {
212 geometry_save(geom, NULL, paned);
213 return FALSE;
215 void
216 geometry_tie(GtkWidget *win, GeometryType g) {
217 geometry_tie_full(g, GTK_WINDOW(win), NULL);
220 void
221 geometry_tie_full(GeometryType g, GtkWindow *win, GtkPaned *paned) {
222 Geometry *geom = &conf.geometries[g];
224 /* the reference point is at the top left corner of the window itself,
225 * ignoring window manager decorations. */
226 gtk_window_set_gravity(win, GDK_GRAVITY_STATIC);
228 /* load the existing geometry for this window */
229 geometry_load(geom, win, paned);
230 /* and track the new geometry */
231 g_signal_connect(G_OBJECT(win), "configure-event",
232 G_CALLBACK(geometry_win_event), geom);
233 g_signal_connect(G_OBJECT(win), "show",
234 G_CALLBACK(geometry_show_cb), geom);
235 if (paned)
236 g_signal_connect(G_OBJECT(paned), "notify::position",
237 G_CALLBACK(geometry_paned_event), geom);
240 static void
241 forget_cb(GObject *obj, gboolean *state) {
242 *state = !*state;
245 /* GLib unfortunately doesn't make up its mind over whether TRUE or
246 * FALSE should denote comparison success, so its provided g_str_equal
247 * is useless as a custom plugin to g_list_find_custom. */
248 static gboolean
249 _str_equal(gconstpointer v1, gconstpointer v2) {
250 return !g_str_equal(v1, v2);
253 void
254 jam_message_va(GtkWindow *parent, MessageType type, gboolean forgettable,
255 const char *title, const char *message, va_list ap) {
256 GtkWidget *dlg;
257 GtkWidget *forget_check;
258 gint msgtype = 0, buttontype = 0;
259 const gchar *mtitle = NULL;
260 /*gint res;*/
261 gboolean forget_state;
263 gchar ourhash[120];
265 char fullmsg[1024];
267 if (app.cli)
268 return;
270 g_vsnprintf(fullmsg, 1024, message, ap);
272 { /* compute hash of this message */
273 GString *id;
274 id = g_string_new(title);
275 g_string_append(id, message);
276 lj_md5_hash(id->str, ourhash);
277 g_string_free(id, TRUE);
280 /* do nothing if the user has asked not to view this message again */
281 if (g_slist_find_custom(app.quiet_dlgs, ourhash, _str_equal))
282 return;
284 mtitle = (const gchar *)title;
285 switch (type) {
286 case JAM_MSG_INFO:
287 msgtype = GTK_MESSAGE_INFO;
288 buttontype = GTK_BUTTONS_OK;
289 break;
290 case JAM_MSG_WARNING:
291 msgtype = GTK_MESSAGE_WARNING;
292 buttontype = GTK_BUTTONS_CLOSE;
293 if (!title) mtitle = _("Warning");
294 break;
295 case JAM_MSG_ERROR:
296 msgtype = GTK_MESSAGE_ERROR;
297 buttontype = GTK_BUTTONS_CLOSE;
298 if (!title) mtitle = _("Error");
299 break;
301 if (mtitle == NULL) mtitle = _("WTF?!");
303 /* TODO: switch to jam_dialogs, which are prettier */
304 dlg = gtk_message_dialog_new(parent, 0, msgtype,
305 buttontype,
306 "%s", fullmsg);
307 gtk_window_set_title(GTK_WINDOW(dlg), mtitle);
308 gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(parent));
310 if (forgettable) {
311 forget_state = FALSE;
312 forget_check = gtk_check_button_new_with_label(_("Do not show again"));
313 g_signal_connect(G_OBJECT(forget_check), "toggled",
314 G_CALLBACK(forget_cb), &forget_state);
315 gtk_box_set_homogeneous(GTK_BOX(GTK_DIALOG(dlg)->action_area),
316 FALSE); /* XXX: this doesn't work :( */
318 /* aggressively make this dialog less ugly */
319 gtk_button_box_set_layout(GTK_BUTTON_BOX(GTK_DIALOG(dlg)->action_area),
320 GTK_BUTTONBOX_EDGE);
321 gtk_box_set_child_packing(GTK_BOX(GTK_DIALOG(dlg)->action_area),
322 ((GtkBoxChild*)GTK_BOX(GTK_DIALOG(dlg)->action_area)->
323 children->data)->widget, FALSE, FALSE, 0, GTK_PACK_END);
325 /* force our checkbox to *really* be first */
326 gtk_container_add(GTK_CONTAINER((GTK_DIALOG(dlg)->action_area)),
327 forget_check);
328 gtk_box_reorder_child(GTK_BOX((GTK_DIALOG(dlg)->action_area)),
329 forget_check, 0);
331 gtk_widget_show_all(GTK_DIALOG(dlg)->action_area);
334 /*res =*/ gtk_dialog_run(GTK_DIALOG(dlg));
336 /* flag this dialog for oblivion if the user didn't like it */
337 if (forgettable && forget_state)
338 app.quiet_dlgs = g_slist_append(app.quiet_dlgs, g_strdup(ourhash));
340 gtk_widget_destroy(dlg);
343 void
344 jam_message(GtkWindow *parent, MessageType type, gboolean forgettable,
345 const char *title, const char *message, ...) {
346 va_list ap;
347 va_start(ap, message);
348 jam_message_va(parent, type, forgettable, title, message, ap);
349 va_end(ap);
352 void jam_warning(GtkWindow *parent, const char *message, ...) {
353 va_list ap;
355 va_start(ap, message);
356 jam_message_va(parent, JAM_MSG_WARNING, FALSE, NULL, message, ap);
357 va_end(ap);
360 /* utility thunk function */
361 void jam_messagebox(GtkWindow *parent, const char *title, const char *message) {
362 jam_message(parent, JAM_MSG_INFO, FALSE, title, message);
365 /* text sort utility function for GtkTreeModels */
366 gint
367 text_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b,
368 gpointer data) {
369 gchar *ta, *tb;
370 gint ret;
371 gtk_tree_model_get(model, a, 0, &ta, -1);
372 gtk_tree_model_get(model, b, 0, &tb, -1);
373 ret = g_ascii_strcasecmp(ta, tb);
374 g_free(ta);
375 g_free(tb);
376 return ret;
379 void
380 jam_widget_set_visible(GtkWidget *w, gboolean visible) {
381 if (visible)
382 gtk_widget_show(w);
383 else
384 gtk_widget_hide(w);
387 void
388 jam_widget_set_font(GtkWidget *w, const gchar *font_name) {
389 PangoFontDescription *font_desc;
391 font_desc = pango_font_description_from_string(font_name);
392 gtk_widget_modify_font(w, font_desc);
393 pango_font_description_free(font_desc);
396 GtkWidget*
397 labelled_box_new_all(const char *caption, GtkWidget *w,
398 gboolean expand, GtkSizeGroup *sg, GtkWidget *mne) {
399 GtkWidget *hbox, *l;
401 l = gtk_label_new_with_mnemonic(caption);
402 gtk_misc_set_alignment(GTK_MISC(l), 0, 0.5);
403 if (sg)
404 gtk_size_group_add_widget(sg, l);
405 if (!mne)
406 mne = w;
407 if (mne)
408 gtk_label_set_mnemonic_widget(GTK_LABEL(l), mne);
410 hbox = gtk_hbox_new(FALSE, 12);
411 gtk_box_pack_start(GTK_BOX(hbox), l, FALSE, FALSE, 0);
412 if (w)
413 gtk_box_pack_start(GTK_BOX(hbox), w, expand, expand, 0);
414 return hbox;
417 GtkWidget*
418 jam_form_label_new(const char *text) {
419 GtkWidget *label;
420 label = gtk_label_new(text);
421 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
422 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
423 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
424 return label;
427 static void
428 selection_enable_cb(GtkTreeSelection *sel, GtkWidget *w) {
429 gtk_widget_set_sensitive(w,
430 gtk_tree_selection_get_selected(sel, NULL, NULL));
432 static void
433 jr_up_cb(JamReorderable *jr) {
434 GtkTreeSelection *sel;
435 GtkTreeModel *model;
436 GtkTreeIter iter, i2;
437 GtkTreePath *path;
439 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jr->view));
440 if (!gtk_tree_selection_get_selected(sel, &model, &iter))
441 return;
442 path = gtk_tree_model_get_path(model, &iter);
443 if (gtk_tree_path_prev(path)) {
444 gtk_tree_model_get_iter(model, &i2, path);
445 gtk_list_store_swap(jr->store, &iter, &i2);
447 gtk_tree_path_free(path);
449 static void
450 jr_down_cb(JamReorderable *jr) {
451 GtkTreeSelection *sel;
452 GtkTreeIter iter, i2;
454 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jr->view));
455 if (!gtk_tree_selection_get_selected(sel, NULL, &iter))
456 return;
457 i2 = iter;
458 if (gtk_tree_model_iter_next(GTK_TREE_MODEL(jr->store), &i2))
459 gtk_list_store_swap(jr->store, &iter, &i2);
462 void
463 jam_reorderable_make(JamReorderable* jr) {
464 GtkWidget *hbox, *bbox;
465 GtkWidget *button;
466 GtkTreeSelection *sel;
468 jr->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(jr->store));
470 jr->box = hbox = gtk_hbox_new(FALSE, 6);
471 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jr->view));
472 gtk_box_pack_start(GTK_BOX(hbox), scroll_wrap(jr->view), TRUE, TRUE, 0);
474 bbox = gtk_vbutton_box_new();
475 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
476 gtk_box_set_spacing(GTK_BOX(bbox), 6);
478 jr->add = button = gtk_button_new_from_stock(GTK_STOCK_ADD);
479 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
481 jr->edit = button = gtk_button_new_from_stock(GTK_STOCK_PROPERTIES);
482 g_signal_connect(G_OBJECT(sel), "changed",
483 G_CALLBACK(selection_enable_cb), button);
484 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
486 jr->remove = button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
487 g_signal_connect(G_OBJECT(sel), "changed",
488 G_CALLBACK(selection_enable_cb), button);
489 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
491 button = gtk_button_new_from_stock(GTK_STOCK_GO_UP);
492 g_signal_connect(G_OBJECT(sel), "changed",
493 G_CALLBACK(selection_enable_cb), button);
494 g_signal_connect_swapped(G_OBJECT(button), "clicked",
495 G_CALLBACK(jr_up_cb), jr);
496 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
497 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), button, TRUE);
499 button = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN);
500 g_signal_connect(G_OBJECT(sel), "changed",
501 G_CALLBACK(selection_enable_cb), button);
502 g_signal_connect_swapped(G_OBJECT(button), "clicked",
503 G_CALLBACK(jr_down_cb), jr);
504 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
505 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), button, TRUE);
507 gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
509 g_signal_emit_by_name(G_OBJECT(sel), "changed");
512 /* picked up from gtk/gtkclipboard.c, to allow blocking clipboard requests
513 that time out */
514 typedef struct
516 GMainLoop *loop;
517 gpointer data;
518 } WaitResults;
520 /*static void
521 clipboard_received_func (GtkClipboard *clipboard,
522 GtkSelectionData *selection_data,
523 gpointer data)
525 WaitResults *results = data;
527 if (selection_data->length >= 0)
528 results->data = gtk_selection_data_copy (selection_data);
530 g_main_loop_quit (results->loop);
533 static void
534 clipboard_text_received_func (GtkClipboard *clipboard,
535 const gchar *text,
536 gpointer data)
538 WaitResults *results = data;
540 results->data = g_strdup (text);
541 g_main_loop_quit (results->loop);
544 /* adapted from gtk_clipboard_wait_for_text */
545 gchar *
546 jam_clipboard_wait_for_text_timeout (GtkClipboard *clipboard, gint timeout)
548 WaitResults results;
549 guint tag;
551 g_return_val_if_fail (clipboard != NULL, NULL);
552 g_return_val_if_fail (clipboard != NULL, NULL);
554 results.data = NULL;
555 results.loop = g_main_loop_new (NULL, TRUE);
557 tag = g_timeout_add(timeout, (GSourceFunc)g_main_loop_quit, results.loop);
559 gtk_clipboard_request_text (clipboard,
560 clipboard_text_received_func,
561 &results);
563 if (g_main_loop_is_running (results.loop))
565 GDK_THREADS_LEAVE ();
566 g_main_loop_run (results.loop);
567 GDK_THREADS_ENTER ();
570 g_source_remove(tag);
572 g_main_loop_unref (results.loop);
574 return results.data;