1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
9 #include "liblj/livejournal.h" /* lj_md5_hash */
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
) {
21 table
= gtk_table_new(rows
, cols
, FALSE
);
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);
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
) {
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);
50 jam_spin_button_set(GtkSpinButton
*w
,
52 gdouble min
, gdouble max
,
53 gdouble step
, gdouble page
,
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
);
64 jam_win_set_size(GtkWindow
*win
, int width
, int height
) {
67 gtk_window_set_default_size(win
, width
, height
);
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
);
77 jam_window_init(GtkWindow
*win
, GtkWindow
*parent
, const gchar
*title
, int width
, int height
) {
79 gtk_window_set_transient_for(GTK_WINDOW(win
), parent
);
81 gtk_window_set_title(GTK_WINDOW(win
), title
);
82 jam_win_set_size(GTK_WINDOW(win
), width
, height
);
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
);
95 jam_dialog_set_contents(GtkDialog
*dlg
, GtkWidget
*contents
) {
97 if (GTK_IS_CONTAINER(contents
)) {
98 jam_dialog_set_contents_container(dlg
, contents
);
101 vbox
= gtk_vbox_new(FALSE
, 5);
103 gtk_box_pack_start(GTK_BOX(vbox
), contents
, TRUE
, TRUE
, 0);
104 jam_dialog_set_contents_container(dlg
, vbox
);
109 jam_dialog_buttonbox_new(void) {
110 return gtk_hbox_new(FALSE
, 5);
113 jam_dialog_buttonbox_add(GtkWidget
*box
, GtkWidget
*button
) {
114 gtk_box_pack_start(GTK_BOX(box
), button
, FALSE
, FALSE
, 0);
117 jam_dialog_buttonbox_button_with_label(GtkWidget
*box
, const char *label
) {
120 g_snprintf(buf
, 100, " %s ", label
);
121 button
= gtk_button_new_with_mnemonic(buf
);
122 jam_dialog_buttonbox_add(box
, button
);
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
);
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
) {
149 dlg
= gtk_message_dialog_new(GTK_WINDOW(parent
), 0,
150 GTK_MESSAGE_QUESTION
,
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
);
160 gdkcolor_to_hex(GdkColor
*color
, char* buf
) {
161 g_snprintf(buf
, 8, "#%02X%02X%02X",
168 scroll_wrap(GtkWidget
*w
) {
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
),
176 gtk_container_add(GTK_CONTAINER(scroll
), w
);
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
);
187 geom
->panedpos
= gtk_paned_get_position(paned
);
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
);
202 geometry_win_event(GtkWindow
*win
, GdkEvent
*e
, Geometry
*geom
) {
204 /* geometry is totally broken on win32--
205 * gtk_window_get_position() returns random values. */
207 geometry_save(geom
, win
, NULL
);
212 geometry_show_cb(GtkWindow
*win
, Geometry
*geom
) {
213 geometry_load(geom
, win
, NULL
);
216 geometry_paned_event(GtkPaned
*paned
, gpointer d
, Geometry
*geom
) {
217 geometry_save(geom
, NULL
, paned
);
221 geometry_tie(GtkWidget
*win
, GeometryType g
) {
222 geometry_tie_full(g
, GTK_WINDOW(win
), NULL
);
226 geometry_tie_full(GeometryType g
, GtkWindow
*win
, GtkPaned
*paned
) {
227 Geometry
*geom
= &conf
.geometries
[g
];
229 /* the reference point is at the top left corner of the window itself,
230 * ignoring window manager decorations. */
231 gtk_window_set_gravity(win
, GDK_GRAVITY_STATIC
);
233 /* load the existing geometry for this window */
234 geometry_load(geom
, win
, paned
);
235 /* and track the new geometry */
236 g_signal_connect(G_OBJECT(win
), "configure-event",
237 G_CALLBACK(geometry_win_event
), geom
);
238 g_signal_connect(G_OBJECT(win
), "show",
239 G_CALLBACK(geometry_show_cb
), geom
);
241 g_signal_connect(G_OBJECT(paned
), "notify::position",
242 G_CALLBACK(geometry_paned_event
), geom
);
246 forget_cb(GObject
*obj
, gboolean
*state
) {
250 /* GLib unfortunately doesn't make up its mind over whether TRUE or
251 * FALSE should denote comparison success, so its provided g_str_equal
252 * is useless as a custom plugin to g_list_find_custom. */
254 _str_equal(gconstpointer v1
, gconstpointer v2
) {
255 return !g_str_equal(v1
, v2
);
259 jam_message_va(GtkWindow
*parent
, MessageType type
, gboolean forgettable
,
260 const char *title
, const char *message
, va_list ap
) {
262 GtkWidget
*forget_check
;
263 gint msgtype
= 0, buttontype
= 0;
264 const gchar
*mtitle
= NULL
;
266 gboolean forget_state
;
275 g_vsnprintf(fullmsg
, 1024, message
, ap
);
277 { /* compute hash of this message */
279 id
= g_string_new(title
);
280 g_string_append(id
, message
);
281 lj_md5_hash(id
->str
, ourhash
);
282 g_string_free(id
, TRUE
);
285 /* do nothing if the user has asked not to view this message again */
286 if (g_slist_find_custom(app
.quiet_dlgs
, ourhash
, _str_equal
))
289 mtitle
= (const gchar
*)title
;
292 msgtype
= GTK_MESSAGE_INFO
;
293 buttontype
= GTK_BUTTONS_OK
;
295 case JAM_MSG_WARNING
:
296 msgtype
= GTK_MESSAGE_WARNING
;
297 buttontype
= GTK_BUTTONS_CLOSE
;
298 if (!title
) mtitle
= _("Warning");
301 msgtype
= GTK_MESSAGE_ERROR
;
302 buttontype
= GTK_BUTTONS_CLOSE
;
303 if (!title
) mtitle
= _("Error");
306 if (mtitle
== NULL
) mtitle
= _("WTF?!");
308 /* TODO: switch to jam_dialogs, which are prettier */
309 dlg
= gtk_message_dialog_new(parent
, 0, msgtype
,
312 gtk_window_set_title(GTK_WINDOW(dlg
), mtitle
);
313 gtk_window_set_transient_for(GTK_WINDOW(dlg
), GTK_WINDOW(parent
));
316 forget_state
= FALSE
;
317 forget_check
= gtk_check_button_new_with_label(_("Do not show again"));
318 g_signal_connect(G_OBJECT(forget_check
), "toggled",
319 G_CALLBACK(forget_cb
), &forget_state
);
320 gtk_box_set_homogeneous(GTK_BOX(GTK_DIALOG(dlg
)->action_area
),
321 FALSE
); /* XXX: this doesn't work :( */
323 /* aggressively make this dialog less ugly */
324 gtk_button_box_set_layout(GTK_BUTTON_BOX(GTK_DIALOG(dlg
)->action_area
),
326 gtk_box_set_child_packing(GTK_BOX(GTK_DIALOG(dlg
)->action_area
),
327 ((GtkBoxChild
*)GTK_BOX(GTK_DIALOG(dlg
)->action_area
)->
328 children
->data
)->widget
, FALSE
, FALSE
, 0, GTK_PACK_END
);
330 /* force our checkbox to *really* be first */
331 gtk_container_add(GTK_CONTAINER((GTK_DIALOG(dlg
)->action_area
)),
333 gtk_box_reorder_child(GTK_BOX((GTK_DIALOG(dlg
)->action_area
)),
336 gtk_widget_show_all(GTK_DIALOG(dlg
)->action_area
);
339 /*res =*/ gtk_dialog_run(GTK_DIALOG(dlg
));
341 /* flag this dialog for oblivion if the user didn't like it */
342 if (forgettable
&& forget_state
)
343 app
.quiet_dlgs
= g_slist_append(app
.quiet_dlgs
, g_strdup(ourhash
));
345 gtk_widget_destroy(dlg
);
349 jam_message(GtkWindow
*parent
, MessageType type
, gboolean forgettable
,
350 const char *title
, const char *message
, ...) {
352 va_start(ap
, message
);
353 jam_message_va(parent
, type
, forgettable
, title
, message
, ap
);
357 void jam_warning(GtkWindow
*parent
, const char *message
, ...) {
360 va_start(ap
, message
);
361 jam_message_va(parent
, JAM_MSG_WARNING
, FALSE
, NULL
, message
, ap
);
365 /* utility thunk function */
366 void jam_messagebox(GtkWindow
*parent
, const char *title
, const char *message
) {
367 jam_message(parent
, JAM_MSG_INFO
, FALSE
, title
, message
);
370 /* text sort utility function for GtkTreeModels */
372 text_sort_func(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
,
376 gtk_tree_model_get(model
, a
, 0, &ta
, -1);
377 gtk_tree_model_get(model
, b
, 0, &tb
, -1);
378 ret
= g_ascii_strcasecmp(ta
, tb
);
385 jam_widget_set_visible(GtkWidget
*w
, gboolean visible
) {
393 jam_widget_set_font(GtkWidget
*w
, const gchar
*font_name
) {
394 PangoFontDescription
*font_desc
;
396 font_desc
= pango_font_description_from_string(font_name
);
397 gtk_widget_modify_font(w
, font_desc
);
398 pango_font_description_free(font_desc
);
402 labelled_box_new_all(const char *caption
, GtkWidget
*w
,
403 gboolean expand
, GtkSizeGroup
*sg
, GtkWidget
*mne
) {
406 l
= gtk_label_new_with_mnemonic(caption
);
407 gtk_misc_set_alignment(GTK_MISC(l
), 0, 0.5);
409 gtk_size_group_add_widget(sg
, l
);
413 gtk_label_set_mnemonic_widget(GTK_LABEL(l
), mne
);
415 hbox
= gtk_hbox_new(FALSE
, 12);
416 gtk_box_pack_start(GTK_BOX(hbox
), l
, FALSE
, FALSE
, 0);
418 gtk_box_pack_start(GTK_BOX(hbox
), w
, expand
, expand
, 0);
423 jam_form_label_new(const char *text
) {
425 label
= gtk_label_new(text
);
426 gtk_misc_set_alignment(GTK_MISC(label
), 0, 0.5);
427 gtk_label_set_selectable(GTK_LABEL(label
), TRUE
);
428 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
433 selection_enable_cb(GtkTreeSelection
*sel
, GtkWidget
*w
) {
434 gtk_widget_set_sensitive(w
,
435 gtk_tree_selection_get_selected(sel
, NULL
, NULL
));
438 jr_up_cb(JamReorderable
*jr
) {
439 GtkTreeSelection
*sel
;
441 GtkTreeIter iter
, i2
;
444 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(jr
->view
));
445 if (!gtk_tree_selection_get_selected(sel
, &model
, &iter
))
447 path
= gtk_tree_model_get_path(model
, &iter
);
448 if (gtk_tree_path_prev(path
)) {
449 gtk_tree_model_get_iter(model
, &i2
, path
);
450 gtk_list_store_swap(jr
->store
, &iter
, &i2
);
452 gtk_tree_path_free(path
);
455 jr_down_cb(JamReorderable
*jr
) {
456 GtkTreeSelection
*sel
;
457 GtkTreeIter iter
, i2
;
459 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(jr
->view
));
460 if (!gtk_tree_selection_get_selected(sel
, NULL
, &iter
))
463 if (gtk_tree_model_iter_next(GTK_TREE_MODEL(jr
->store
), &i2
))
464 gtk_list_store_swap(jr
->store
, &iter
, &i2
);
468 jam_reorderable_make(JamReorderable
* jr
) {
469 GtkWidget
*hbox
, *bbox
;
471 GtkTreeSelection
*sel
;
473 jr
->view
= gtk_tree_view_new_with_model(GTK_TREE_MODEL(jr
->store
));
475 jr
->box
= hbox
= gtk_hbox_new(FALSE
, 6);
476 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(jr
->view
));
477 gtk_box_pack_start(GTK_BOX(hbox
), scroll_wrap(jr
->view
), TRUE
, TRUE
, 0);
479 bbox
= gtk_vbutton_box_new();
480 gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox
), GTK_BUTTONBOX_START
);
481 gtk_box_set_spacing(GTK_BOX(bbox
), 6);
483 jr
->add
= button
= gtk_button_new_from_stock(GTK_STOCK_ADD
);
484 gtk_box_pack_start(GTK_BOX(bbox
), button
, FALSE
, FALSE
, 0);
486 jr
->edit
= button
= gtk_button_new_from_stock(GTK_STOCK_PROPERTIES
);
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 jr
->remove
= button
= gtk_button_new_from_stock(GTK_STOCK_REMOVE
);
492 g_signal_connect(G_OBJECT(sel
), "changed",
493 G_CALLBACK(selection_enable_cb
), button
);
494 gtk_box_pack_start(GTK_BOX(bbox
), button
, FALSE
, FALSE
, 0);
496 button
= gtk_button_new_from_stock(GTK_STOCK_GO_UP
);
497 g_signal_connect(G_OBJECT(sel
), "changed",
498 G_CALLBACK(selection_enable_cb
), button
);
499 g_signal_connect_swapped(G_OBJECT(button
), "clicked",
500 G_CALLBACK(jr_up_cb
), jr
);
501 gtk_box_pack_start(GTK_BOX(bbox
), button
, FALSE
, FALSE
, 0);
502 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), button
, TRUE
);
504 button
= gtk_button_new_from_stock(GTK_STOCK_GO_DOWN
);
505 g_signal_connect(G_OBJECT(sel
), "changed",
506 G_CALLBACK(selection_enable_cb
), button
);
507 g_signal_connect_swapped(G_OBJECT(button
), "clicked",
508 G_CALLBACK(jr_down_cb
), jr
);
509 gtk_box_pack_start(GTK_BOX(bbox
), button
, FALSE
, FALSE
, 0);
510 gtk_button_box_set_child_secondary(GTK_BUTTON_BOX(bbox
), button
, TRUE
);
512 gtk_box_pack_start(GTK_BOX(hbox
), bbox
, FALSE
, FALSE
, 0);
514 g_signal_emit_by_name(G_OBJECT(sel
), "changed");
517 /* picked up from gtk/gtkclipboard.c, to allow blocking clipboard requests
526 clipboard_received_func (GtkClipboard *clipboard,
527 GtkSelectionData *selection_data,
530 WaitResults *results = data;
532 if (selection_data->length >= 0)
533 results->data = gtk_selection_data_copy (selection_data);
535 g_main_loop_quit (results->loop);
539 clipboard_text_received_func (GtkClipboard
*clipboard
,
543 WaitResults
*results
= data
;
545 results
->data
= g_strdup (text
);
546 g_main_loop_quit (results
->loop
);
549 /* adapted from gtk_clipboard_wait_for_text */
551 jam_clipboard_wait_for_text_timeout (GtkClipboard
*clipboard
, gint timeout
)
556 g_return_val_if_fail (clipboard
!= NULL
, NULL
);
557 g_return_val_if_fail (clipboard
!= NULL
, NULL
);
560 results
.loop
= g_main_loop_new (NULL
, TRUE
);
562 tag
= g_timeout_add(timeout
, (GSourceFunc
)g_main_loop_quit
, results
.loop
);
564 gtk_clipboard_request_text (clipboard
,
565 clipboard_text_received_func
,
568 if (g_main_loop_is_running (results
.loop
))
570 GDK_THREADS_LEAVE ();
571 g_main_loop_run (results
.loop
);
572 GDK_THREADS_ENTER ();
575 g_source_remove(tag
);
577 g_main_loop_unref (results
.loop
);