first try to journal_store_get_latest_id() for sqlite
[k8lowj.git] / src / util-gtk.c
blob37b5aa7dbdab4628f07c0553331a8ebf1ef36fdc
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 */
6 #include "gtk-all.h"
8 #include "liblj/livejournal.h" /* lj_md5_hash */
10 #include "util-gtk.h"
13 gchar *gettext_translate_func (const gchar *path, gpointer data) {
14 /* gettext returns a const, but gtk wants nonconst. *shrug*. */
15 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;
26 GtkWidget *jam_table_label (GtkTable *table, int row, const char *text) {
27 GtkWidget *label = gtk_label_new_with_mnemonic(text);
28 gtk_misc_set_alignment(GTK_MISC(label), 0.0f, 0.5f);
29 gtk_table_attach(table, GTK_WIDGET(label), 0, 1, row, row+1, GTK_FILL, GTK_FILL, 6, 6);
30 return label;
34 void jam_table_content (GtkTable *table, int row, GtkWidget *content) {
35 gtk_table_attach(table, GTK_WIDGET(content), 1, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 0, 0);
39 void jam_table_label_content_mne (GtkTable *table, int row, const char *text, GtkWidget *content, GtkWidget *mne) {
40 GtkWidget *label;
41 label = jam_table_label(table, row, text);
42 jam_table_content(table, row, content);
43 gtk_label_set_mnemonic_widget(GTK_LABEL(label), mne);
47 void jam_table_label_content (GtkTable *table, int row, const char *text, GtkWidget *content) {
48 jam_table_label_content_mne(table, row, text, content, content);
52 void jam_table_fillrow (GtkTable *table, int row, GtkWidget *content) {
53 gtk_table_attach(table, GTK_WIDGET(content), 0, 2, row, row+1, GTK_EXPAND|GTK_FILL, 0, 2, 2);
57 void jam_spin_button_set (GtkSpinButton *w, gboolean numeric, gdouble min, gdouble max, gdouble step, gdouble page, gint digits) {
58 g_assert(GTK_IS_SPIN_BUTTON(w));
59 gtk_spin_button_set_numeric(w, numeric);
60 gtk_spin_button_set_range(w, min, max);
61 gtk_spin_button_set_increments(w, step, page);
62 gtk_spin_button_set_digits(w, digits);
66 void jam_win_set_size (GtkWindow *win, int width, int height) {
67 if (width > 0) {
68 if (height > 0) {
69 gtk_window_set_default_size(win, width, height);
70 } else {
71 gtk_window_set_default_size(win, width, (int)(0.618 *width));
73 } else if (height > 0) {
74 gtk_window_set_default_size(win, (int)(1.618 *height), height);
79 void jam_window_init (GtkWindow *win, GtkWindow *parent, const gchar *title, int width, int height) {
80 if (parent) gtk_window_set_transient_for(GTK_WINDOW(win), parent);
81 if (title) gtk_window_set_title(GTK_WINDOW(win), title);
82 jam_win_set_size(GTK_WINDOW(win), width, height);
86 static void 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)) gtk_dialog_set_has_separator(dlg, FALSE);
94 GtkWidget *jam_dialog_set_contents (GtkDialog *dlg, GtkWidget *contents) {
95 GtkWidget *vbox;
96 if (GTK_IS_CONTAINER(contents)) {
97 jam_dialog_set_contents_container(dlg, contents);
98 return contents;
100 vbox = gtk_vbox_new(FALSE, 5);
101 if (contents) gtk_box_pack_start(GTK_BOX(vbox), contents, TRUE, TRUE, 0);
102 jam_dialog_set_contents_container(dlg, vbox);
103 return vbox;
107 GtkWidget *jam_dialog_buttonbox_new (void) {
108 return gtk_hbox_new(FALSE, 5);
112 void jam_dialog_buttonbox_add (GtkWidget *box, GtkWidget *button) {
113 gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0);
117 GtkWidget *jam_dialog_buttonbox_button_with_label (GtkWidget *box, const char *label) {
118 GtkWidget *button;
119 char buf[100];
120 g_snprintf(buf, sizeof(buf), " %s ", label);
121 button = gtk_button_new_with_mnemonic(buf);
122 jam_dialog_buttonbox_add(box, button);
123 return button;
127 GtkWidget *jam_dialog_buttonbox_button_from_stock (GtkWidget *box, const char *id) {
128 GtkWidget *button = gtk_button_new_from_stock(id);
129 jam_dialog_buttonbox_add(box, button);
130 return button;
134 void jam_dialog_set_contents_buttonbox (GtkWidget *dlg, GtkWidget *contents, GtkWidget *buttonbox) {
135 GtkWidget *vbox, *hbox;;
136 vbox = jam_dialog_set_contents(GTK_DIALOG(dlg), contents);
137 hbox = gtk_hbox_new(FALSE, 0);
138 gtk_box_pack_end(GTK_BOX(hbox), buttonbox, FALSE, FALSE, 0);
139 gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
140 gtk_widget_show_all(vbox);
144 int jam_confirm (GtkWindow *parent, const char *title, const char *msg) {
145 GtkWidget *dlg;
146 int res;
147 dlg = gtk_message_dialog_new(GTK_WINDOW(parent), 0, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", msg);
148 jam_window_init(GTK_WINDOW(dlg), parent, title, -1, -1);
149 res = (gtk_dialog_run(GTK_DIALOG(dlg)) == GTK_RESPONSE_YES);
150 gtk_widget_destroy(dlg);
151 return res;
155 void gdkcolor_to_hex (GdkColor *color, char *buf) {
156 g_snprintf(buf, 8, "#%02X%02X%02X", color->red>>8, color->green>>8, color->blue>>8);
160 GtkWidget *scroll_wrap (GtkWidget *w) {
161 GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL);
162 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
163 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll), GTK_SHADOW_IN);
164 gtk_container_add(GTK_CONTAINER(scroll), w);
165 return scroll;
169 static void geometry_save (Geometry *geom, GtkWindow *win, GtkPaned *paned) {
170 if (win && GTK_WIDGET(win)->window) {
171 gtk_window_get_position(win, &geom->x, &geom->y);
172 gtk_window_get_size(win, &geom->width, &geom->height);
174 if (paned) geom->panedpos = gtk_paned_get_position(paned);
178 static void geometry_load (Geometry *geom, GtkWindow *win, GtkPaned *paned) {
179 if (win && geom->width > 0) {
180 gtk_window_move(win, geom->x, geom->y);
181 gtk_window_set_default_size(win, geom->width, geom->height);
183 if (paned && geom->panedpos > 0) gtk_paned_set_position(paned, geom->panedpos);
187 static gboolean geometry_win_event (GtkWindow *win, GdkEvent *e, Geometry *geom) {
188 geometry_save(geom, win, NULL);
189 return FALSE;
193 static void geometry_show_cb (GtkWindow *win, Geometry *geom) {
194 geometry_load(geom, win, NULL);
198 static gboolean geometry_paned_event (GtkPaned *paned, gpointer d, Geometry *geom) {
199 geometry_save(geom, NULL, paned);
200 return FALSE;
204 void geometry_tie (GtkWidget *win, GeometryType g) {
205 geometry_tie_full(g, GTK_WINDOW(win), NULL);
209 void geometry_tie_full (GeometryType g, GtkWindow *win, GtkPaned *paned) {
210 Geometry *geom = &conf.geometries[g];
211 /* the reference point is at the top left corner of the window itself,
212 * ignoring window manager decorations. */
213 gtk_window_set_gravity(win, GDK_GRAVITY_STATIC);
214 /* load the existing geometry for this window */
215 geometry_load(geom, win, paned);
216 /* and track the new geometry */
217 g_signal_connect(G_OBJECT(win), "configure-event", G_CALLBACK(geometry_win_event), geom);
218 g_signal_connect(G_OBJECT(win), "show", G_CALLBACK(geometry_show_cb), geom);
219 if (paned) g_signal_connect(G_OBJECT(paned), "notify::position", G_CALLBACK(geometry_paned_event), geom);
223 static void forget_cb (GObject *obj, gboolean *state) {
224 *state = !*state;
228 /* GLib unfortunately doesn't make up its mind over whether TRUE or
229 * FALSE should denote comparison success, so its provided g_str_equal
230 * is useless as a custom plugin to g_list_find_custom. */
231 static gboolean _str_equal (gconstpointer v1, gconstpointer v2) {
232 return !g_str_equal(v1, v2);
236 void jam_message_va (GtkWindow *parent, MessageType type, gboolean forgettable, const char *title, const char *message, va_list ap) {
237 GtkWidget *dlg;
238 GtkWidget *forget_check;
239 gint msgtype = 0, buttontype = 0;
240 const gchar *mtitle = NULL;
241 gboolean forget_state;
242 gchar ourhash[120];
243 char fullmsg[1024];
245 if (app.cli) return;
246 g_vsnprintf(fullmsg, 1024, message, ap);
247 { /* compute hash of this message */
248 GString *id;
249 id = g_string_new(title);
250 g_string_append(id, message);
251 lj_md5_hash(id->str, ourhash);
252 g_string_free(id, TRUE);
254 /* do nothing if the user has asked not to view this message again */
255 if (g_slist_find_custom(app.quiet_dlgs, ourhash, _str_equal)) return;
256 mtitle = (const gchar *)title;
257 switch (type) {
258 case JAM_MSG_INFO:
259 msgtype = GTK_MESSAGE_INFO;
260 buttontype = GTK_BUTTONS_OK;
261 break;
262 case JAM_MSG_WARNING:
263 msgtype = GTK_MESSAGE_WARNING;
264 buttontype = GTK_BUTTONS_CLOSE;
265 if (!title) mtitle = _("Warning");
266 break;
267 case JAM_MSG_ERROR:
268 msgtype = GTK_MESSAGE_ERROR;
269 buttontype = GTK_BUTTONS_CLOSE;
270 if (!title) mtitle = _("Error");
271 break;
273 if (mtitle == NULL) mtitle = _("WTF?!");
274 /* TODO: switch to jam_dialogs, which are prettier */
275 dlg = gtk_message_dialog_new(parent, 0, msgtype, buttontype, "%s", fullmsg);
276 gtk_window_set_title(GTK_WINDOW(dlg), mtitle);
277 gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(parent));
278 if (forgettable) {
279 forget_state = FALSE;
280 forget_check = gtk_check_button_new_with_label(_("Do not show again"));
281 g_signal_connect(G_OBJECT(forget_check), "toggled", G_CALLBACK(forget_cb), &forget_state);
282 gtk_box_set_homogeneous(GTK_BOX(GTK_DIALOG(dlg)->action_area), FALSE); /* XXX: this doesn't work :( */
283 /* aggressively make this dialog less ugly */
284 gtk_button_box_set_layout(GTK_BUTTON_BOX(GTK_DIALOG(dlg)->action_area), GTK_BUTTONBOX_EDGE);
285 gtk_box_set_child_packing(GTK_BOX(GTK_DIALOG(dlg)->action_area), ((GtkBoxChild *)GTK_BOX(GTK_DIALOG(dlg)->action_area)->children->data)->widget, FALSE, FALSE, 0, GTK_PACK_END);
286 /* force our checkbox to *really* be first */
287 gtk_container_add(GTK_CONTAINER((GTK_DIALOG(dlg)->action_area)), forget_check);
288 gtk_box_reorder_child(GTK_BOX((GTK_DIALOG(dlg)->action_area)), forget_check, 0);
289 gtk_widget_show_all(GTK_DIALOG(dlg)->action_area);
291 gtk_dialog_run(GTK_DIALOG(dlg));
292 /* flag this dialog for oblivion if the user didn't like it */
293 if (forgettable && forget_state) app.quiet_dlgs = g_slist_append(app.quiet_dlgs, g_strdup(ourhash));
294 gtk_widget_destroy(dlg);
298 __attribute__((format(printf,5,6))) void jam_message (GtkWindow *parent, MessageType type, gboolean forgettable, const char *title, const char *message, ...) {
299 va_list ap;
300 va_start(ap, message);
301 jam_message_va(parent, type, forgettable, title, message, ap);
302 va_end(ap);
306 __attribute__((format(printf,2,3))) void jam_warning (GtkWindow *parent, const char *message, ...) {
307 va_list ap;
308 va_start(ap, message);
309 jam_message_va(parent, JAM_MSG_WARNING, FALSE, NULL, message, ap);
310 va_end(ap);
314 /* utility thunk function */
315 void jam_messagebox (GtkWindow *parent, const char *title, const char *message) {
316 jam_message(parent, JAM_MSG_INFO, FALSE, title, "%s", message);
320 /* text sort utility function for GtkTreeModels */
321 gint text_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data) {
322 gchar *ta, *tb;
323 gint ret;
324 gtk_tree_model_get(model, a, 0, &ta, -1);
325 gtk_tree_model_get(model, b, 0, &tb, -1);
326 ret = g_ascii_strcasecmp(ta, tb);
327 g_free(ta);
328 g_free(tb);
329 return ret;
333 void jam_widget_set_visible (GtkWidget *w, gboolean visible) {
334 if (visible) gtk_widget_show(w); else gtk_widget_hide(w);
338 void jam_widget_set_font (GtkWidget *w, const gchar *font_name) {
339 PangoFontDescription *font_desc;
340 font_desc = pango_font_description_from_string(font_name);
341 gtk_widget_modify_font(w, font_desc);
342 pango_font_description_free(font_desc);
346 GtkWidget *labelled_box_new_all (const char *caption, GtkWidget *w, gboolean expand, GtkSizeGroup *sg, GtkWidget *mne) {
347 GtkWidget *hbox, *l;
348 l = gtk_label_new_with_mnemonic(caption);
349 gtk_misc_set_alignment(GTK_MISC(l), 0, 0.5);
350 if (sg) gtk_size_group_add_widget(sg, l);
351 if (!mne) mne = w;
352 if (mne) gtk_label_set_mnemonic_widget(GTK_LABEL(l), mne);
353 hbox = gtk_hbox_new(FALSE, 12);
354 gtk_box_pack_start(GTK_BOX(hbox), l, FALSE, FALSE, 0);
355 if (w) gtk_box_pack_start(GTK_BOX(hbox), w, expand, expand, 0);
356 return hbox;
360 GtkWidget *jam_form_label_new (const char *text) {
361 GtkWidget *label;
362 label = gtk_label_new(text);
363 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
364 gtk_label_set_selectable(GTK_LABEL(label), TRUE);
365 gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
366 return label;
370 static void selection_enable_cb (GtkTreeSelection *sel, GtkWidget *w) {
371 gtk_widget_set_sensitive(w, gtk_tree_selection_get_selected(sel, NULL, NULL));
375 static void jr_up_cb (JamReorderable *jr) {
376 GtkTreeSelection *sel;
377 GtkTreeModel *model;
378 GtkTreeIter iter, i2;
379 GtkTreePath *path;
380 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jr->view));
381 if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return;
382 path = gtk_tree_model_get_path(model, &iter);
383 if (gtk_tree_path_prev(path)) {
384 gtk_tree_model_get_iter(model, &i2, path);
385 gtk_list_store_swap(jr->store, &iter, &i2);
387 gtk_tree_path_free(path);
391 static void jr_down_cb (JamReorderable *jr) {
392 GtkTreeSelection *sel;
393 GtkTreeIter iter, i2;
394 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jr->view));
395 if (!gtk_tree_selection_get_selected(sel, NULL, &iter)) return;
396 i2 = iter;
397 if (gtk_tree_model_iter_next(GTK_TREE_MODEL(jr->store), &i2)) gtk_list_store_swap(jr->store, &iter, &i2);
401 void jam_reorderable_make (JamReorderable *jr) {
402 GtkWidget *hbox, *bbox;
403 GtkWidget *button;
404 GtkTreeSelection *sel;
406 jr->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(jr->store));
408 jr->box = hbox = gtk_hbox_new(FALSE, 6);
409 sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(jr->view));
410 gtk_box_pack_start(GTK_BOX(hbox), scroll_wrap(jr->view), TRUE, TRUE, 0);
412 bbox = gtk_vbutton_box_new();
413 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_START);
414 gtk_box_set_spacing(GTK_BOX(bbox), 6);
416 jr->add = button = gtk_button_new_from_stock(GTK_STOCK_ADD);
417 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
419 jr->edit = button = gtk_button_new_from_stock(GTK_STOCK_PROPERTIES);
420 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(selection_enable_cb), button);
421 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
423 jr->remove = button = gtk_button_new_from_stock(GTK_STOCK_REMOVE);
424 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(selection_enable_cb), button);
425 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
427 button = gtk_button_new_from_stock(GTK_STOCK_GO_UP);
428 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(selection_enable_cb), button);
429 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(jr_up_cb), jr);
430 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
431 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), button, TRUE);
433 button = gtk_button_new_from_stock(GTK_STOCK_GO_DOWN);
434 g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(selection_enable_cb), button);
435 g_signal_connect_swapped(G_OBJECT(button), "clicked", G_CALLBACK(jr_down_cb), jr);
436 gtk_box_pack_start(GTK_BOX(bbox), button, FALSE, FALSE, 0);
437 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox), button, TRUE);
439 gtk_box_pack_start(GTK_BOX(hbox), bbox, FALSE, FALSE, 0);
441 g_signal_emit_by_name(G_OBJECT(sel), "changed");
445 /* picked up from gtk/gtkclipboard.c, to allow blocking clipboard requests that time out */
446 typedef struct {
447 GMainLoop *loop;
448 gpointer data;
449 } WaitResults;
452 static void clipboard_text_received_func (GtkClipboard *clipboard, const gchar *text, gpointer data) {
453 WaitResults *results = data;
454 results->data = g_strdup(text);
455 g_main_loop_quit(results->loop);
459 /* adapted from gtk_clipboard_wait_for_text */
460 gchar *jam_clipboard_wait_for_text_timeout (GtkClipboard *clipboard, gint timeout) {
461 WaitResults results;
462 guint tag;
463 g_return_val_if_fail(clipboard != NULL, NULL);
464 g_return_val_if_fail(clipboard != NULL, NULL);
465 results.data = NULL;
466 results.loop = g_main_loop_new(NULL, TRUE);
467 tag = g_timeout_add(timeout, (GSourceFunc) g_main_loop_quit, results.loop);
468 gtk_clipboard_request_text(clipboard, clipboard_text_received_func, &results);
469 if (g_main_loop_is_running(results.loop)) {
470 GDK_THREADS_LEAVE();
471 g_main_loop_run(results.loop);
472 GDK_THREADS_ENTER();
474 g_source_remove(tag);
475 g_main_loop_unref(results.loop);
476 return results.data;