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
) {
203 geometry_save(geom
, win
, NULL
);
207 geometry_show_cb(GtkWindow
*win
, Geometry
*geom
) {
208 geometry_load(geom
, win
, NULL
);
211 geometry_paned_event(GtkPaned
*paned
, gpointer d
, Geometry
*geom
) {
212 geometry_save(geom
, NULL
, paned
);
216 geometry_tie(GtkWidget
*win
, GeometryType g
) {
217 geometry_tie_full(g
, GTK_WINDOW(win
), NULL
);
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
);
236 g_signal_connect(G_OBJECT(paned
), "notify::position",
237 G_CALLBACK(geometry_paned_event
), geom
);
241 forget_cb(GObject
*obj
, gboolean
*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. */
249 _str_equal(gconstpointer v1
, gconstpointer v2
) {
250 return !g_str_equal(v1
, v2
);
254 jam_message_va(GtkWindow
*parent
, MessageType type
, gboolean forgettable
,
255 const char *title
, const char *message
, va_list ap
) {
257 GtkWidget
*forget_check
;
258 gint msgtype
= 0, buttontype
= 0;
259 const gchar
*mtitle
= NULL
;
261 gboolean forget_state
;
270 g_vsnprintf(fullmsg
, 1024, message
, ap
);
272 { /* compute hash of this message */
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
))
284 mtitle
= (const gchar
*)title
;
287 msgtype
= GTK_MESSAGE_INFO
;
288 buttontype
= GTK_BUTTONS_OK
;
290 case JAM_MSG_WARNING
:
291 msgtype
= GTK_MESSAGE_WARNING
;
292 buttontype
= GTK_BUTTONS_CLOSE
;
293 if (!title
) mtitle
= _("Warning");
296 msgtype
= GTK_MESSAGE_ERROR
;
297 buttontype
= GTK_BUTTONS_CLOSE
;
298 if (!title
) mtitle
= _("Error");
301 if (mtitle
== NULL
) mtitle
= _("WTF?!");
303 /* TODO: switch to jam_dialogs, which are prettier */
304 dlg
= gtk_message_dialog_new(parent
, 0, msgtype
,
307 gtk_window_set_title(GTK_WINDOW(dlg
), mtitle
);
308 gtk_window_set_transient_for(GTK_WINDOW(dlg
), GTK_WINDOW(parent
));
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
),
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
)),
328 gtk_box_reorder_child(GTK_BOX((GTK_DIALOG(dlg
)->action_area
)),
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
);
344 jam_message(GtkWindow
*parent
, MessageType type
, gboolean forgettable
,
345 const char *title
, const char *message
, ...) {
347 va_start(ap
, message
);
348 jam_message_va(parent
, type
, forgettable
, title
, message
, ap
);
352 void jam_warning(GtkWindow
*parent
, const char *message
, ...) {
355 va_start(ap
, message
);
356 jam_message_va(parent
, JAM_MSG_WARNING
, FALSE
, NULL
, message
, 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 */
367 text_sort_func(GtkTreeModel
*model
, GtkTreeIter
*a
, GtkTreeIter
*b
,
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
);
380 jam_widget_set_visible(GtkWidget
*w
, gboolean visible
) {
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
);
397 labelled_box_new_all(const char *caption
, GtkWidget
*w
,
398 gboolean expand
, GtkSizeGroup
*sg
, GtkWidget
*mne
) {
401 l
= gtk_label_new_with_mnemonic(caption
);
402 gtk_misc_set_alignment(GTK_MISC(l
), 0, 0.5);
404 gtk_size_group_add_widget(sg
, l
);
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);
413 gtk_box_pack_start(GTK_BOX(hbox
), w
, expand
, expand
, 0);
418 jam_form_label_new(const char *text
) {
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
);
428 selection_enable_cb(GtkTreeSelection
*sel
, GtkWidget
*w
) {
429 gtk_widget_set_sensitive(w
,
430 gtk_tree_selection_get_selected(sel
, NULL
, NULL
));
433 jr_up_cb(JamReorderable
*jr
) {
434 GtkTreeSelection
*sel
;
436 GtkTreeIter iter
, i2
;
439 sel
= gtk_tree_view_get_selection(GTK_TREE_VIEW(jr
->view
));
440 if (!gtk_tree_selection_get_selected(sel
, &model
, &iter
))
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
);
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
))
458 if (gtk_tree_model_iter_next(GTK_TREE_MODEL(jr
->store
), &i2
))
459 gtk_list_store_swap(jr
->store
, &iter
, &i2
);
463 jam_reorderable_make(JamReorderable
* jr
) {
464 GtkWidget
*hbox
, *bbox
;
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
521 clipboard_received_func (GtkClipboard *clipboard,
522 GtkSelectionData *selection_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);
534 clipboard_text_received_func (GtkClipboard
*clipboard
,
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 */
546 jam_clipboard_wait_for_text_timeout (GtkClipboard
*clipboard
, gint timeout
)
551 g_return_val_if_fail (clipboard
!= NULL
, NULL
);
552 g_return_val_if_fail (clipboard
!= NULL
, 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
,
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
);