2 * Copyright (c) 2014-2016 Bert Burgemeister <trebbu@googlemail.com>
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 #include <gtk/gtkunixprint.h>
30 #include <libxml/xpath.h>
39 #include <sys/select.h>
44 #define VERSION "4.7.0"
46 #define WHITESPACE " \t\n"
47 #define MAIN_WIN "main"
49 "usage: pipeglade [[-i in-fifo] " \
52 "[-u glade-file.ui] " \
55 "[--display X-server]] | " \
64 __func__, __FILE__, __LINE__); \
71 "Out of memory in %s (%s:%d): ", \
72 __func__, __FILE__, __LINE__); \
76 static FILE *out
; /* UI feedback messages */
77 static FILE *save
; /* saving user data */
78 static FILE *log_out
; /* logging output */
79 static GtkBuilder
*builder
; /* to be read from .ui file */
82 * ============================================================
84 * ============================================================
88 * Check if s1 and s2 are equal strings
91 eql(const char *s1
, const char *s2
)
93 return s1
!= NULL
&& s2
!= NULL
&& strcmp(s1
, s2
) == 0;
97 * Print a formatted message to stream s and give up with status
100 bye(int status
, FILE *s
, const char *fmt
, ...)
105 vfprintf(s
, fmt
, ap
);
111 * Print a warning about a malformed command to stderr
114 ign_cmd(GType type
, const char *msg
)
116 const char *name
, *pad
= " ";
118 if (type
== G_TYPE_INVALID
) {
123 name
= g_type_name(type
);
124 fprintf(stderr
, "ignoring %s%scommand \"%s\"\n", name
, pad
, msg
);
128 * Create a fifo if necessary, and open it. Give up if the file
129 * exists but is not a fifo
132 fifo(const char *name
, const char *mode
)
141 if (S_ISFIFO(sb
.st_mode
)) {
142 if (chmod(name
, 0600) != 0)
143 bye(EXIT_FAILURE
, stderr
,
144 "using pre-existing fifo %s: %s\n",
145 name
, strerror(errno
));
146 } else if (mkfifo(name
, 0600) != 0)
147 bye(EXIT_FAILURE
, stderr
,
148 "making fifo %s: %s\n", name
, strerror(errno
));
156 if ((fd
= open(name
, O_RDWR
| O_NONBLOCK
)) < 0)
157 bye(EXIT_FAILURE
, stderr
,
158 "opening fifo %s (%s): %s\n",
159 name
, mode
, strerror(errno
));
168 s
= fopen(name
, "w+");
175 bye(EXIT_FAILURE
, stderr
, "opening fifo %s (%s): %s\n",
176 name
, mode
, strerror(errno
));
178 setvbuf(s
, NULL
, bufmode
, 0);
183 * Create a log file if necessary, and open it. A name of "-"
184 * requests use of stderr.
187 open_log(const char *name
)
193 else if (name
&& (s
= fopen(name
, "a")) == NULL
)
194 bye(EXIT_FAILURE
, stderr
,
195 "opening log file %s: %s\n", name
, strerror(errno
));
200 * Microseconds elapsed since start
203 usec_since(struct timespec
*start
)
207 clock_gettime(CLOCK_MONOTONIC
, &now
);
208 return (now
.tv_sec
- start
->tv_sec
) * 1e6
+
209 (now
.tv_nsec
- start
->tv_nsec
) / 1e3
;
218 static struct timespec start
;
219 static char *old_msg
;
221 if (log_out
== NULL
) /* no logging */
223 if (msg
== NULL
&& old_msg
== NULL
)
225 "##########\t##### (New Pipeglade session) #####\n");
226 else if (msg
== NULL
&& old_msg
!= NULL
) { /* command done; start idle */
228 "%10ld\t%s\n", usec_since(&start
), old_msg
);
231 } else if (msg
!= NULL
&& old_msg
== NULL
) { /* idle done; start command */
233 "%10ld\t### (Idle) ###\n", usec_since(&start
));
234 if ((old_msg
= malloc(strlen(msg
) + 1)) == NULL
)
236 strcpy(old_msg
, msg
);
239 clock_gettime(CLOCK_MONOTONIC
, &start
);
243 * Remove suffix from name; find the object named like this
246 obj_sans_suffix(const char *suffix
, const char *name
)
249 char str
[BUFLEN
+ 1] = {'\0'};
251 str_l
= suffix
- name
;
252 strncpy(str
, name
, str_l
< BUFLEN
? str_l
: BUFLEN
);
253 return gtk_builder_get_object(builder
, str
);
257 widget_name(GtkBuildable
*obj
)
259 return gtk_buildable_get_name(obj
);
263 * Store a line from stream s into buf, which should have been malloc'd
264 * to bufsize. Enlarge buf and bufsize if necessary.
267 read_buf(FILE *s
, char **buf
, size_t *bufsize
)
278 select(ifd
+ 1, &rfds
, NULL
, NULL
, NULL
);
280 if (c
== '\n' || feof(s
))
282 if (i
>= *bufsize
- 1)
283 if ((*buf
= realloc(*buf
, *bufsize
= *bufsize
* 2)) == NULL
)
288 case 'n': (*buf
)[i
++] = '\n'; break;
289 case 'r': (*buf
)[i
++] = '\r'; break;
290 default: (*buf
)[i
++] = c
; break;
292 } else if (c
== '\\')
302 * ============================================================
303 * Receiving feedback from the GUI
304 * ============================================================
308 send_msg_to(FILE* o
, GtkBuildable
*obj
, const char *tag
, va_list ap
)
311 const char *w_name
= widget_name(obj
);
314 struct timeval timeout
= {1, 0};
318 if (select(ofd
+ 1, NULL
, &wfds
, NULL
, &timeout
) == 1) {
319 fprintf(o
, "%s:%s ", w_name
, tag
);
320 while ((data
= va_arg(ap
, char *)) != NULL
) {
324 while ((c
= data
[i
++]) != '\0')
335 "send error; discarding feedback message %s:%s\n",
340 * Send GUI feedback to global stream "out". The message format is
341 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
342 * last argument must be NULL.
345 send_msg(GtkBuildable
*obj
, const char *tag
, ...)
350 send_msg_to(out
, obj
, tag
, ap
);
355 * Send message from GUI to global stream "save". The message format
356 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
357 * last argument must be NULL.
360 save_msg(GtkBuildable
*obj
, const char *tag
, ...)
365 send_msg_to(save
, obj
, tag
, ap
);
370 * Send message from GUI to global stream "save". The message format
371 * is "<origin>:set <data ...>". The variadic arguments are strings;
372 * last argument must be NULL.
375 save_action_set_msg(GtkBuildable
*obj
, const char *tag
, ...)
380 send_msg_to(save
, obj
, "set", ap
);
385 * Use msg_sender() to send a message describing a particular cell
388 send_tree_cell_msg_by(void msg_sender(GtkBuildable
*, const char *, ...),
389 GtkTreeModel
*model
, const char *path_s
,
390 GtkTreeIter
*iter
, int col
, GtkBuildable
*obj
)
392 GValue value
= G_VALUE_INIT
;
396 gtk_tree_model_get_value(model
, iter
, col
, &value
);
397 col_type
= gtk_tree_model_get_column_type(model
, col
);
400 snprintf(str
, BUFLEN
, " %d %d", col
, g_value_get_int(&value
));
401 msg_sender(obj
, "gint", path_s
, str
, NULL
);
404 snprintf(str
, BUFLEN
, " %d %ld", col
, g_value_get_long(&value
));
405 msg_sender(obj
, "glong", path_s
, str
, NULL
);
408 snprintf(str
, BUFLEN
, " %d %" PRId64
, col
, g_value_get_int64(&value
));
409 msg_sender(obj
, "gint64", path_s
, str
, NULL
);
412 snprintf(str
, BUFLEN
, " %d %u", col
, g_value_get_uint(&value
));
413 msg_sender(obj
, "guint", path_s
, str
, NULL
);
416 snprintf(str
, BUFLEN
, " %d %lu", col
, g_value_get_ulong(&value
));
417 msg_sender(obj
, "gulong", path_s
, str
, NULL
);
420 snprintf(str
, BUFLEN
, " %d %" PRIu64
, col
, g_value_get_uint64(&value
));
421 msg_sender(obj
, "guint64", path_s
, str
, NULL
);
424 snprintf(str
, BUFLEN
, " %d %d", col
, g_value_get_boolean(&value
));
425 msg_sender(obj
, "gboolean", path_s
, str
, NULL
);
428 snprintf(str
, BUFLEN
, " %d %f", col
, g_value_get_float(&value
));
429 msg_sender(obj
, "gfloat", path_s
, str
, NULL
);
432 snprintf(str
, BUFLEN
, " %d %f", col
, g_value_get_double(&value
));
433 msg_sender(obj
, "gdouble", path_s
, str
, NULL
);
436 snprintf(str
, BUFLEN
, " %d ", col
);
437 msg_sender(obj
, "gchararray", path_s
, str
, g_value_get_string(&value
), NULL
);
440 fprintf(stderr
, "column %d not implemented: %s\n", col
, G_VALUE_TYPE_NAME(&value
));
443 g_value_unset(&value
);
447 * Use msg_sender() to send one message per column for a single row
450 send_tree_row_msg_by(void msg_sender(GtkBuildable
*, const char *, ...),
451 GtkTreeModel
*model
, char *path_s
,
452 GtkTreeIter
*iter
, GtkBuildable
*obj
)
456 for (col
= 0; col
< gtk_tree_model_get_n_columns(model
); col
++)
457 send_tree_cell_msg_by(msg_sender
, model
, path_s
, iter
, col
, obj
);
461 * send_tree_row_msg serves as an argument for
462 * gtk_tree_selection_selected_foreach()
465 send_tree_row_msg(GtkTreeModel
*model
,
466 GtkTreePath
*path
, GtkTreeIter
*iter
, GtkBuildable
*obj
)
468 char *path_s
= gtk_tree_path_to_string(path
);
470 send_tree_row_msg_by(send_msg
, model
, path_s
, iter
, obj
);
476 * save_tree_row_msg serves as an argument for
477 * gtk_tree_model_foreach().
478 * Send message from GUI to global stream "save".
481 save_tree_row_msg(GtkTreeModel
*model
,
482 GtkTreePath
*path
, GtkTreeIter
*iter
, GtkBuildable
*obj
)
484 char *path_s
= gtk_tree_path_to_string(path
);
487 send_tree_row_msg_by(save_action_set_msg
, model
, path_s
, iter
, obj
);
493 cb_calendar(GtkBuildable
*obj
, const char *tag
)
496 unsigned int year
= 0, month
= 0, day
= 0;
498 gtk_calendar_get_date(GTK_CALENDAR(obj
), &year
, &month
, &day
);
499 snprintf(str
, BUFLEN
, "%04u-%02u-%02u", year
, ++month
, day
);
500 send_msg(obj
, tag
, str
, NULL
);
504 cb_color_button(GtkBuildable
*obj
, const char *tag
)
508 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj
), &color
);
509 send_msg(obj
, tag
, gdk_rgba_to_string(&color
), NULL
);
513 cb_editable(GtkBuildable
*obj
, const char *tag
)
515 send_msg(obj
, tag
, gtk_entry_get_text(GTK_ENTRY(obj
)), NULL
);
519 * Callback that sends a message about a pointer device button press
523 cb_event_box_button(GtkBuildable
*obj
, GdkEvent
*e
, gpointer user_data
)
527 snprintf(data
, BUFLEN
, "%d %.1lf %.1lf",
528 e
->button
.button
, e
->button
.x
, e
->button
.y
);
529 send_msg(obj
, user_data
, data
, NULL
);
534 * Callback that sends in a message the name of the key pressed when
535 * a GtkEventBox is focused
538 cb_event_box_key(GtkBuildable
*obj
, GdkEvent
*e
, gpointer user_data
)
540 send_msg(obj
, user_data
, gdk_keyval_name(e
->key
.keyval
), NULL
);
545 * Callback that sends a message about pointer device motion in a
549 cb_event_box_motion(GtkBuildable
*obj
, GdkEvent
*e
, gpointer user_data
)
553 snprintf(data
, BUFLEN
, "%.1lf %.1lf", e
->button
.x
, e
->button
.y
);
554 send_msg(obj
, user_data
, data
, NULL
);
559 * Callback that only sends "name:tag" and returns false
562 cb_event_simple(GtkBuildable
*obj
, GdkEvent
*e
, const char *tag
)
565 send_msg(obj
, tag
, NULL
);
570 cb_file_chooser_button(GtkBuildable
*obj
, const char *tag
)
572 send_msg(obj
, tag
, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj
)), NULL
);
576 cb_font_button(GtkBuildable
*obj
, const char *tag
)
578 send_msg(obj
, tag
, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj
)), NULL
);
582 cb_menu_item(GtkBuildable
*obj
, const char *tag
)
584 send_msg(obj
, tag
, gtk_menu_item_get_label(GTK_MENU_ITEM(obj
)), NULL
);
588 cb_range(GtkBuildable
*obj
, const char *tag
)
592 snprintf(str
, BUFLEN
, "%f", gtk_range_get_value(GTK_RANGE(obj
)));
593 send_msg(obj
, tag
, str
, NULL
);
597 * Callback that sends user's selection from a file dialog
600 cb_send_file_chooser_dialog_selection(gpointer user_data
)
602 send_msg(user_data
, "file",
603 gtk_file_chooser_get_filename(user_data
), NULL
);
604 send_msg(user_data
, "folder",
605 gtk_file_chooser_get_current_folder(user_data
), NULL
);
609 * Callback that sends in a message the content of the text buffer
610 * passed in user_data
613 cb_send_text(GtkBuildable
*obj
, gpointer user_data
)
617 gtk_text_buffer_get_bounds(user_data
, &a
, &b
);
618 send_msg(obj
, "text", gtk_text_buffer_get_text(user_data
, &a
, &b
, TRUE
), NULL
);
622 * Callback that sends in a message the highlighted text from the text
623 * buffer which was passed in user_data
626 cb_send_text_selection(GtkBuildable
*obj
, gpointer user_data
)
630 gtk_text_buffer_get_selection_bounds(user_data
, &a
, &b
);
631 send_msg(obj
, "text", gtk_text_buffer_get_text(user_data
, &a
, &b
, TRUE
), NULL
);
635 * Callback that only sends "name:tag" and returns true
638 cb_simple(GtkBuildable
*obj
, const char *tag
)
640 send_msg(obj
, tag
, NULL
);
645 cb_switch(GtkBuildable
*obj
, void *pspec
, void *user_data
)
649 send_msg(obj
, gtk_switch_get_active(GTK_SWITCH(obj
)) ? "1" : "0", NULL
);
653 cb_toggle_button(GtkBuildable
*obj
, const char *tag
)
656 send_msg(obj
, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj
)) ? "1" : "0", NULL
);
660 cb_tree_selection(GtkBuildable
*obj
, const char *tag
)
664 view
= gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj
));
665 send_msg(GTK_BUILDABLE(view
), tag
, NULL
);
666 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj
),
667 (GtkTreeSelectionForeachFunc
) send_tree_row_msg
,
672 * ============================================================
673 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
675 * ============================================================
679 * The set of supported drawing operations
708 * Text placement mode for rel_move_for()
723 * One single element of a drawing
726 struct draw_op
*next
;
727 struct draw_op
*prev
;
734 * Argument sets for the various drawing operations
744 struct curve_to_args
{
753 struct move_to_args
{
758 struct rectangle_args
{
765 struct rel_move_for_args
{
771 struct set_dash_args
{
776 struct set_font_face_args
{
777 cairo_font_slant_t slant
;
778 cairo_font_weight_t weight
;
782 struct set_font_size_args
{
786 struct set_line_cap_args
{
787 cairo_line_cap_t line_cap
;
790 struct set_line_join_args
{
791 cairo_line_join_t line_join
;
794 struct set_line_width_args
{
798 struct set_source_rgba_args
{
802 struct show_text_args
{
808 draw(cairo_t
*cr
, enum cairo_fn op
, void *op_args
)
812 struct move_to_args
*args
= op_args
;
814 cairo_line_to(cr
, args
->x
, args
->y
);
818 struct move_to_args
*args
= op_args
;
820 cairo_rel_line_to(cr
, args
->x
, args
->y
);
824 struct move_to_args
*args
= op_args
;
826 cairo_move_to(cr
, args
->x
, args
->y
);
830 struct move_to_args
*args
= op_args
;
832 cairo_rel_move_to(cr
, args
->x
, args
->y
);
836 struct arc_args
*args
= op_args
;
838 cairo_arc(cr
, args
->x
, args
->y
, args
->radius
, args
->angle1
, args
->angle2
);
842 struct arc_args
*args
= op_args
;
844 cairo_arc_negative(cr
, args
->x
, args
->y
, args
->radius
, args
->angle1
, args
->angle2
);
848 struct curve_to_args
*args
= op_args
;
850 cairo_curve_to(cr
, args
->x1
, args
->y1
, args
->x2
, args
->y2
, args
->x3
, args
->y3
);
854 struct curve_to_args
*args
= op_args
;
856 cairo_curve_to(cr
, args
->x1
, args
->y1
, args
->x2
, args
->y2
, args
->x3
, args
->y3
);
860 struct rectangle_args
*args
= op_args
;
862 cairo_rectangle(cr
, args
->x
, args
->y
, args
->width
, args
->height
);
866 cairo_close_path(cr
);
869 struct show_text_args
*args
= op_args
;
871 cairo_show_text(cr
, args
->text
);
875 struct rel_move_for_args
*args
= op_args
;
876 cairo_text_extents_t e
;
877 double dx
= 0.0, dy
= 0.0;
879 cairo_text_extents(cr
, args
->text
, &e
);
881 case C
: dx
= -e
.width
/ 2; dy
= e
.height
/ 2; break;
882 case E
: dx
= -e
.width
; dy
= e
.height
/ 2; break;
883 case N
: dx
= -e
.width
/ 2; dy
= e
.height
; break;
884 case NE
: dx
= -e
.width
; dy
= e
.height
; break;
885 case NW
: dy
= e
.height
; break;
886 case S
: dx
= -e
.width
/ 2; break;
887 case SE
: dx
= -e
.width
; break;
889 case W
: dy
= e
.height
/ 2; break;
890 default: ABORT
; break;
892 cairo_rel_move_to(cr
, dx
, dy
);
898 case STROKE_PRESERVE
:
899 cairo_stroke_preserve(cr
);
905 cairo_fill_preserve(cr
);
908 struct set_dash_args
*args
= op_args
;
910 cairo_set_dash(cr
, args
->dashes
, args
->num_dashes
, 0);
913 case SET_FONT_FACE
: {
914 struct set_font_face_args
*args
= op_args
;
916 cairo_select_font_face(cr
, args
->family
, args
->slant
, args
->weight
);
919 case SET_FONT_SIZE
: {
920 struct set_font_size_args
*args
= op_args
;
922 cairo_set_font_size(cr
, args
->size
);
926 struct set_line_cap_args
*args
= op_args
;
928 cairo_set_line_cap(cr
, args
->line_cap
);
931 case SET_LINE_JOIN
: {
932 struct set_line_join_args
*args
= op_args
;
934 cairo_set_line_join(cr
, args
->line_join
);
937 case SET_LINE_WIDTH
: {
938 struct set_line_width_args
*args
= op_args
;
940 cairo_set_line_width(cr
, args
->width
);
943 case SET_SOURCE_RGBA
: {
944 struct set_source_rgba_args
*args
= op_args
;
946 gdk_cairo_set_source_rgba(cr
, &args
->color
);
956 * Callback that draws on a GtkDrawingArea
959 cb_draw(GtkWidget
*widget
, cairo_t
*cr
, gpointer data
)
964 for (op
= g_object_get_data(G_OBJECT(widget
), "draw_ops");
967 draw(cr
, op
->op
, op
->op_args
);
972 * ============================================================
973 * Manipulating the GUI
974 * ============================================================
978 update_button(GObject
*obj
, const char *action
,
979 const char *data
, const char *whole_msg
, GType type
)
981 if (eql(action
, "set_label"))
982 gtk_button_set_label(GTK_BUTTON(obj
), data
);
984 ign_cmd(type
, whole_msg
);
988 update_calendar(GObject
*obj
, const char *action
,
989 const char *data
, const char *whole_msg
, GType type
)
991 GtkCalendar
*calendar
= GTK_CALENDAR(obj
);
992 int year
= 0, month
= 0, day
= 0;
994 if (eql(action
, "select_date")) {
995 sscanf(data
, "%d-%d-%d", &year
, &month
, &day
);
996 if (month
> -1 && month
<= 11 && day
> 0 && day
<= 31) {
997 gtk_calendar_select_month(calendar
, --month
, year
);
998 gtk_calendar_select_day(calendar
, day
);
1000 ign_cmd(type
, whole_msg
);
1001 } else if (eql(action
, "mark_day")) {
1002 day
= strtol(data
, NULL
, 10);
1003 if (day
> 0 && day
<= 31)
1004 gtk_calendar_mark_day(calendar
, day
);
1006 ign_cmd(type
, whole_msg
);
1007 } else if (eql(action
, "clear_marks"))
1008 gtk_calendar_clear_marks(calendar
);
1010 ign_cmd(type
, whole_msg
);
1014 * Common actions for various kinds of window. Return false if
1015 * command is ignored
1018 update_class_window(GObject
*obj
, const char *action
,
1019 const char *data
, const char *whole_msg
, GType type
)
1021 GtkWindow
*window
= GTK_WINDOW(obj
);
1025 if (eql(action
, "set_title"))
1026 gtk_window_set_title(window
, data
);
1027 else if (eql(action
, "fullscreen"))
1028 gtk_window_fullscreen(window
);
1029 else if (eql(action
, "unfullscreen"))
1030 gtk_window_unfullscreen(window
);
1031 else if (eql(action
, "resize")) {
1032 if (sscanf(data
, "%d %d", &x
, &y
) != 2)
1033 gtk_window_get_default_size(window
, &x
, &y
);
1034 gtk_window_resize(window
, x
, y
);
1035 } else if (eql(action
, "move")) {
1036 if (sscanf(data
, "%d %d", &x
, &y
) == 2)
1037 gtk_window_move(window
, x
, y
);
1039 ign_cmd(type
, whole_msg
);
1046 update_color_button(GObject
*obj
, const char *action
,
1047 const char *data
, const char *whole_msg
, GType type
)
1051 if (eql(action
, "set_color")) {
1052 gdk_rgba_parse(&color
, data
);
1053 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj
), &color
);
1055 ign_cmd(type
, whole_msg
);
1059 update_combo_box_text(GObject
*obj
, const char *action
,
1060 const char *data
, const char *whole_msg
, GType type
)
1062 GtkComboBoxText
*combobox
= GTK_COMBO_BOX_TEXT(obj
);
1063 char data1
[strlen(data
) + 1];
1065 strcpy(data1
, data
);
1066 if (eql(action
, "prepend_text"))
1067 gtk_combo_box_text_prepend_text(combobox
, data1
);
1068 else if (eql(action
, "append_text"))
1069 gtk_combo_box_text_append_text(combobox
, data1
);
1070 else if (eql(action
, "remove"))
1071 gtk_combo_box_text_remove(combobox
, strtol(data1
, NULL
, 10));
1072 else if (eql(action
, "insert_text")) {
1073 char *position
= strtok(data1
, WHITESPACE
);
1074 char *text
= strtok(NULL
, WHITESPACE
);
1075 gtk_combo_box_text_insert_text(combobox
,
1076 strtol(position
, NULL
, 10), text
);
1078 ign_cmd(type
, whole_msg
);
1082 * Maintaining a list of drawing operations. It is the responsibility
1083 * of cb_draw() to actually draw them. update_drawing_area() needs a
1084 * few helper functions.
1088 * Fill structure *op with the drawing operation according to action
1089 * and with the appropriate set of arguments
1092 set_draw_op(struct draw_op
*op
, const char *action
, const char *data
)
1094 if (eql(action
, "line_to")) {
1095 struct move_to_args
*args
;
1097 if ((args
= malloc(sizeof(*args
))) == NULL
)
1101 if (sscanf(data
, "%u %lf %lf", &op
->id
, &args
->x
, &args
->y
) != 3)
1103 } else if (eql(action
, "rel_line_to")) {
1104 struct move_to_args
*args
;
1106 if ((args
= malloc(sizeof(*args
))) == NULL
)
1108 op
->op
= REL_LINE_TO
;
1110 if (sscanf(data
, "%u %lf %lf", &op
->id
, &args
->x
, &args
->y
) != 3)
1112 } else if (eql(action
, "move_to")) {
1113 struct move_to_args
*args
;
1115 if ((args
= malloc(sizeof(*args
))) == NULL
)
1119 if (sscanf(data
, "%u %lf %lf", &op
->id
, &args
->x
, &args
->y
) != 3)
1121 } else if (eql(action
, "rel_move_to")) {
1122 struct move_to_args
*args
;
1124 if ((args
= malloc(sizeof(*args
))) == NULL
)
1126 op
->op
= REL_MOVE_TO
;
1128 if (sscanf(data
, "%u %lf %lf", &op
->id
, &args
->x
, &args
->y
) != 3)
1130 } else if (eql(action
, "arc")) {
1131 struct arc_args
*args
;
1134 if ((args
= malloc(sizeof(*args
))) == NULL
)
1138 if (sscanf(data
, "%u %lf %lf %lf %lf %lf", &op
->id
,
1139 &args
->x
, &args
->y
, &args
->radius
, °1
, °2
) != 6)
1141 args
->angle1
= deg1
* (M_PI
/ 180.);
1142 args
->angle2
= deg2
* (M_PI
/ 180.);
1143 } else if (eql(action
, "arc_negative")) {
1144 struct arc_args
*args
;
1147 if ((args
= malloc(sizeof(*args
))) == NULL
)
1149 op
->op
= ARC_NEGATIVE
;
1151 if (sscanf(data
, "%u %lf %lf %lf %lf %lf", &op
->id
,
1152 &args
->x
, &args
->y
, &args
->radius
, °1
, °2
) != 6)
1154 args
->angle1
= deg1
* (M_PI
/ 180.);
1155 args
->angle2
= deg2
* (M_PI
/ 180.);
1156 } else if (eql(action
, "curve_to")) {
1157 struct curve_to_args
*args
;
1159 if ((args
= malloc(sizeof(*args
))) == NULL
)
1163 if (sscanf(data
, "%u %lf %lf %lf %lf %lf %lf", &op
->id
,
1164 &args
->x1
, &args
->y1
, &args
->x2
, &args
->y2
, &args
->x3
, &args
->y3
) != 7)
1166 } else if (eql(action
, "rel_curve_to")) {
1167 struct curve_to_args
*args
;
1169 if ((args
= malloc(sizeof(*args
))) == NULL
)
1171 op
->op
= REL_CURVE_TO
;
1173 if (sscanf(data
, "%u %lf %lf %lf %lf %lf %lf", &op
->id
,
1174 &args
->x1
, &args
->y1
, &args
->x2
, &args
->y2
, &args
->x3
, &args
->y3
) != 7)
1176 } else if (eql(action
, "rectangle")) {
1177 struct rectangle_args
*args
;
1179 if ((args
= malloc(sizeof(*args
))) == NULL
)
1183 if (sscanf(data
, "%u %lf %lf %lf %lf", &op
->id
,
1184 &args
->x
, &args
->y
, &args
->width
, &args
->height
) != 5)
1186 } else if (eql(action
, "close_path")) {
1187 op
->op
= CLOSE_PATH
;
1188 if (sscanf(data
, "%u", &op
->id
) != 1)
1191 } else if (eql(action
, "show_text")) {
1192 struct show_text_args
*args
;
1195 if (sscanf(data
, "%u %n", &op
->id
, &start
) < 1)
1197 len
= strlen(data
+ start
) + 1;
1198 if ((args
= malloc(sizeof(*args
) + len
* sizeof(args
->text
[0]))) == NULL
)
1202 args
->len
= len
; /* not used */
1203 strncpy(args
->text
, (data
+ start
), len
);
1204 } else if (eql(action
, "rel_move_for")) {
1205 struct rel_move_for_args
*args
;
1206 char ref_point
[2 + 1];
1209 if (sscanf(data
, "%u %2s %n", &op
->id
, ref_point
, &start
) < 2)
1211 len
= strlen(data
+ start
) + 1;
1212 if ((args
= malloc(sizeof(*args
) + len
* sizeof(args
->text
[0]))) == NULL
)
1214 if (eql(ref_point
, "c"))
1216 else if (eql(ref_point
, "e"))
1218 else if (eql(ref_point
, "n"))
1220 else if (eql(ref_point
, "ne"))
1222 else if (eql(ref_point
, "nw"))
1224 else if (eql(ref_point
, "s"))
1226 else if (eql(ref_point
, "se"))
1228 else if (eql(ref_point
, "sw"))
1230 else if (eql(ref_point
, "w"))
1234 op
->op
= REL_MOVE_FOR
;
1236 args
->len
= len
; /* not used */
1237 strncpy(args
->text
, (data
+ start
), len
);
1238 } else if (eql(action
, "stroke")) {
1240 if (sscanf(data
, "%u", &op
->id
) != 1)
1243 } else if (eql(action
, "stroke_preserve")) {
1244 op
->op
= STROKE_PRESERVE
;
1245 if (sscanf(data
, "%u", &op
->id
) != 1)
1248 } else if (eql(action
, "fill")) {
1250 if (sscanf(data
, "%u", &op
->id
) != 1)
1253 } else if (eql(action
, "fill_preserve")) {
1254 op
->op
= FILL_PRESERVE
;
1255 if (sscanf(data
, "%u", &op
->id
) != 1)
1258 } else if (eql(action
, "set_dash")) {
1259 struct set_dash_args
*args
;
1261 char data1
[strlen(data
) + 1];
1264 strcpy(data1
, data
);
1265 if (sscanf(data1
, "%u %n", &op
->id
, &d_start
) < 1)
1267 next
= end
= data1
+ d_start
;
1273 } while (next
!= end
);
1274 if ((args
= malloc(sizeof(*args
) + n
* sizeof(args
->dashes
[0]))) == NULL
)
1278 args
->num_dashes
= n
;
1279 for (i
= 0, next
= data1
+ d_start
; i
< n
; i
++, next
= end
) {
1280 args
->dashes
[i
] = strtod(next
, &end
);
1282 } else if (eql(action
, "set_font_face")) {
1283 struct set_font_face_args
*args
;
1284 int family_start
, family_len
;
1288 if (sscanf(data
, "%u %s %s %n%*s", &op
->id
, slant
, weight
, &family_start
) != 3)
1290 family_len
= strlen(data
+ family_start
) + 1;
1291 if ((args
= malloc(sizeof(*args
) + family_len
* sizeof(args
->family
[0]))) == NULL
)
1293 op
->op
= SET_FONT_FACE
;
1295 strncpy(args
->family
, data
+ family_start
, family_len
);
1296 if (eql(slant
, "normal"))
1297 args
->slant
= CAIRO_FONT_SLANT_NORMAL
;
1298 else if (eql(slant
, "italic"))
1299 args
->slant
= CAIRO_FONT_SLANT_ITALIC
;
1300 else if (eql(slant
, "oblique"))
1301 args
->slant
= CAIRO_FONT_SLANT_OBLIQUE
;
1304 if (eql(weight
, "normal"))
1305 args
->weight
= CAIRO_FONT_WEIGHT_NORMAL
;
1306 else if (eql(weight
, "bold"))
1307 args
->weight
= CAIRO_FONT_WEIGHT_BOLD
;
1310 } else if (eql(action
, "set_font_size")) {
1311 struct set_font_size_args
*args
;
1313 if ((args
= malloc(sizeof(*args
))) == NULL
)
1315 op
->op
= SET_FONT_SIZE
;
1317 if (sscanf(data
, "%u %lf", &op
->id
, &args
->size
) != 2)
1319 } else if (eql(action
, "set_line_cap")) {
1320 struct set_line_cap_args
*args
;
1323 if ((args
= malloc(sizeof(*args
))) == NULL
)
1325 op
->op
= SET_LINE_CAP
;
1327 if (sscanf(data
, "%u %6s", &op
->id
, str
) != 2)
1329 if (eql(str
, "butt"))
1330 args
->line_cap
= CAIRO_LINE_CAP_BUTT
;
1331 else if (eql(str
, "round"))
1332 args
->line_cap
= CAIRO_LINE_CAP_ROUND
;
1333 else if (eql(str
, "square"))
1334 args
->line_cap
= CAIRO_LINE_CAP_SQUARE
;
1337 } else if (eql(action
, "set_line_join")) {
1338 struct set_line_join_args
*args
;
1341 if ((args
= malloc(sizeof(*args
))) == NULL
)
1343 op
->op
= SET_LINE_JOIN
;
1345 if (sscanf(data
, "%u %5s", &op
->id
, str
) != 2)
1347 if (eql(str
, "miter"))
1348 args
->line_join
= CAIRO_LINE_JOIN_MITER
;
1349 else if (eql(str
, "round"))
1350 args
->line_join
= CAIRO_LINE_JOIN_ROUND
;
1351 else if (eql(str
, "bevel"))
1352 args
->line_join
= CAIRO_LINE_JOIN_BEVEL
;
1355 } else if (eql(action
, "set_line_width")) {
1356 struct set_line_width_args
*args
;
1358 if ((args
= malloc(sizeof(*args
))) == NULL
)
1360 op
->op
= SET_LINE_WIDTH
;
1362 if (sscanf(data
, "%u %lf", &op
->id
, &args
->width
) != 2)
1364 } else if (eql(action
, "set_source_rgba")) {
1365 struct set_source_rgba_args
*args
;
1368 if ((args
= malloc(sizeof(*args
))) == NULL
)
1370 op
->op
= SET_SOURCE_RGBA
;
1372 if ((sscanf(data
, "%u %n", &op
->id
, &c_start
) < 1))
1374 gdk_rgba_parse(&args
->color
, data
+ c_start
);
1381 * Append another element to widget's "draw_ops" list
1384 ins_draw_op(GObject
*widget
, const char *action
, const char *data
)
1386 struct draw_op
*op
, *draw_ops
, *last_op
;
1388 if ((op
= malloc(sizeof(*op
))) == NULL
)
1392 if (!set_draw_op(op
, action
, data
)) {
1397 if ((draw_ops
= g_object_get_data(widget
, "draw_ops")) == NULL
)
1398 g_object_set_data(widget
, "draw_ops", op
);
1400 for (last_op
= draw_ops
;
1401 last_op
->next
!= NULL
;
1402 last_op
= last_op
->next
);
1409 * Remove all elements with the given id from widget's "draw_ops" list
1412 rem_draw_op(GObject
*widget
, const char *data
)
1414 struct draw_op
*op
, *next_op
, *prev_op
= NULL
;
1417 if (sscanf(data
, "%u", &id
) != 1)
1419 op
= g_object_get_data(widget
, "draw_ops");
1420 while (op
!= NULL
) {
1423 if (prev_op
== NULL
) /* list head */
1424 g_object_set_data(widget
, "draw_ops", op
->next
);
1426 prev_op
->next
= op
->next
;
1437 update_drawing_area(GObject
*obj
, const char *action
,
1438 const char *data
, const char *whole_msg
, GType type
)
1440 if (eql(action
, "remove")) {
1441 if (!rem_draw_op(obj
, data
))
1442 ign_cmd(type
, whole_msg
);
1443 } else if (eql(action
, "refresh")) {
1444 gint width
= gtk_widget_get_allocated_width(GTK_WIDGET(obj
));
1445 gint height
= gtk_widget_get_allocated_height(GTK_WIDGET(obj
));
1447 gtk_widget_queue_draw_area(GTK_WIDGET(obj
), 0, 0, width
, height
);
1448 } else if (ins_draw_op(obj
, action
, data
));
1450 ign_cmd(type
, whole_msg
);
1454 update_entry(GObject
*obj
, const char *action
,
1455 const char *data
, const char *whole_msg
, GType type
)
1457 GtkEntry
*entry
= GTK_ENTRY(obj
);
1459 if (eql(action
, "set_text"))
1460 gtk_entry_set_text(entry
, data
);
1461 else if (eql(action
, "set_placeholder_text"))
1462 gtk_entry_set_placeholder_text(entry
, data
);
1464 ign_cmd(type
, whole_msg
);
1468 update_expander(GObject
*obj
, const char *action
,
1469 const char *data
, const char *whole_msg
, GType type
)
1471 GtkExpander
*expander
= GTK_EXPANDER(obj
);
1473 if (eql(action
, "set_expanded"))
1474 gtk_expander_set_expanded(expander
, strtol(data
, NULL
, 10));
1475 else if (eql(action
, "set_label"))
1476 gtk_expander_set_label(expander
, data
);
1478 ign_cmd(type
, whole_msg
);
1482 update_file_chooser_button(GObject
*obj
, const char *action
,
1483 const char *data
, const char *whole_msg
, GType type
)
1485 if (eql(action
, "set_filename"))
1486 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj
), data
);
1488 ign_cmd(type
, whole_msg
);
1492 update_file_chooser_dialog(GObject
*obj
, const char *action
,
1493 const char *data
, const char *whole_msg
, GType type
)
1495 GtkFileChooser
*chooser
= GTK_FILE_CHOOSER(obj
);
1497 if (eql(action
, "set_filename"))
1498 gtk_file_chooser_set_filename(chooser
, data
);
1499 else if (eql(action
, "set_current_name"))
1500 gtk_file_chooser_set_current_name(chooser
, data
);
1501 else if (update_class_window(obj
, action
, data
, whole_msg
, type
));
1503 ign_cmd(type
, whole_msg
);
1507 update_focus(GObject
*obj
, const char *action
,
1508 const char *data
, const char *whole_msg
, GType type
)
1512 if (gtk_widget_get_can_focus(GTK_WIDGET(obj
)))
1513 gtk_widget_grab_focus(GTK_WIDGET(obj
));
1515 ign_cmd(type
, whole_msg
);
1519 update_font_button(GObject
*obj
, const char *action
,
1520 const char *data
, const char *whole_msg
, GType type
)
1522 GtkFontButton
*font_button
= GTK_FONT_BUTTON(obj
);
1524 if (eql(action
, "set_font_name"))
1525 gtk_font_button_set_font_name(font_button
, data
);
1527 ign_cmd(type
, whole_msg
);
1531 update_frame(GObject
*obj
, const char *action
,
1532 const char *data
, const char *whole_msg
, GType type
)
1534 if (eql(action
, "set_label"))
1535 gtk_frame_set_label(GTK_FRAME(obj
), data
);
1537 ign_cmd(type
, whole_msg
);
1541 update_image(GObject
*obj
, const char *action
,
1542 const char *data
, const char *whole_msg
, GType type
)
1544 GtkImage
*image
= GTK_IMAGE(obj
);
1547 gtk_image_get_icon_name(image
, NULL
, &size
);
1548 if (eql(action
, "set_from_file"))
1549 gtk_image_set_from_file(image
, data
);
1550 else if (eql(action
, "set_from_icon_name"))
1551 gtk_image_set_from_icon_name(image
, data
, size
);
1553 ign_cmd(type
, whole_msg
);
1557 update_label(GObject
*obj
, const char *action
,
1558 const char *data
, const char *whole_msg
, GType type
)
1560 if (eql(action
, "set_text"))
1561 gtk_label_set_text(GTK_LABEL(obj
), data
);
1563 ign_cmd(type
, whole_msg
);
1567 update_notebook(GObject
*obj
, const char *action
,
1568 const char *data
, const char *whole_msg
, GType type
)
1570 if (eql(action
, "set_current_page"))
1571 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj
), strtol(data
, NULL
, 10));
1573 ign_cmd(type
, whole_msg
);
1577 update_nothing(GObject
*obj
, const char *action
,
1578 const char *data
, const char *whole_msg
, GType type
)
1588 update_print_dialog(GObject
*obj
, const char *action
,
1589 const char *data
, const char *whole_msg
, GType type
)
1591 GtkPrintUnixDialog
*dialog
= GTK_PRINT_UNIX_DIALOG(obj
);
1593 GtkPrinter
*printer
;
1594 GtkPrintSettings
*settings
;
1595 GtkPageSetup
*page_setup
;
1598 if (eql(action
, "print")) {
1599 response_id
= gtk_dialog_run(GTK_DIALOG(dialog
));
1600 switch (response_id
) {
1601 case GTK_RESPONSE_OK
:
1602 printer
= gtk_print_unix_dialog_get_selected_printer(dialog
);
1603 settings
= gtk_print_unix_dialog_get_settings(dialog
);
1604 page_setup
= gtk_print_unix_dialog_get_page_setup(dialog
);
1605 job
= gtk_print_job_new(data
, printer
, settings
, page_setup
);
1606 if (gtk_print_job_set_source_file(job
, data
, NULL
))
1607 gtk_print_job_send(job
, NULL
, NULL
, NULL
);
1609 ign_cmd(type
, whole_msg
);
1610 g_clear_object(&settings
);
1611 g_clear_object(&job
);
1613 case GTK_RESPONSE_CANCEL
:
1614 case GTK_RESPONSE_DELETE_EVENT
:
1617 fprintf(stderr
, "%s sent an unexpected response id (%d)\n",
1618 widget_name(GTK_BUILDABLE(dialog
)), response_id
);
1621 gtk_widget_hide(GTK_WIDGET(dialog
));
1623 ign_cmd(type
, whole_msg
);
1627 update_progress_bar(GObject
*obj
, const char *action
,
1628 const char *data
, const char *whole_msg
, GType type
)
1630 GtkProgressBar
*progressbar
= GTK_PROGRESS_BAR(obj
);
1632 if (eql(action
, "set_text"))
1633 gtk_progress_bar_set_text(progressbar
, *data
== '\0' ? NULL
: data
);
1634 else if (eql(action
, "set_fraction"))
1635 gtk_progress_bar_set_fraction(progressbar
, strtod(data
, NULL
));
1637 ign_cmd(type
, whole_msg
);
1641 update_scale(GObject
*obj
, const char *action
,
1642 const char *data
, const char *whole_msg
, GType type
)
1644 if (eql(action
, "set_value"))
1645 gtk_range_set_value(GTK_RANGE(obj
), strtod(data
, NULL
));
1647 ign_cmd(type
, whole_msg
);
1651 update_scrolled_window(GObject
*obj
, const char *action
,
1652 const char *data
, const char *whole_msg
, GType type
)
1654 GtkScrolledWindow
*window
= GTK_SCROLLED_WINDOW(obj
);
1655 GtkAdjustment
*hadj
= gtk_scrolled_window_get_hadjustment(window
);
1656 GtkAdjustment
*vadj
= gtk_scrolled_window_get_vadjustment(window
);
1659 if (eql(action
, "hscroll") && sscanf(data
, "%lf", &d0
) == 1)
1660 gtk_adjustment_set_value(hadj
, d0
);
1661 else if (eql(action
, "vscroll") && sscanf(data
, "%lf", &d0
) == 1)
1662 gtk_adjustment_set_value(vadj
, d0
);
1663 else if (eql(action
, "hscroll_to_range") &&
1664 sscanf(data
, "%lf %lf", &d0
, &d1
) == 2)
1665 gtk_adjustment_clamp_page(hadj
, d0
, d1
);
1666 else if (eql(action
, "vscroll_to_range") &&
1667 sscanf(data
, "%lf %lf", &d0
, &d1
) == 2)
1668 gtk_adjustment_clamp_page(vadj
, d0
, d1
);
1670 ign_cmd(type
, whole_msg
);
1674 update_sensitivity(GObject
*obj
, const char *action
,
1675 const char *data
, const char *whole_msg
, GType type
)
1680 gtk_widget_set_sensitive(GTK_WIDGET(obj
), strtol(data
, NULL
, 10));
1684 update_size_request(GObject
*obj
, const char *action
,
1685 const char *data
, const char *whole_msg
, GType type
)
1692 if (sscanf(data
, "%d %d", &x
, &y
) == 2)
1693 gtk_widget_set_size_request(GTK_WIDGET(obj
), x
, y
);
1695 gtk_widget_set_size_request(GTK_WIDGET(obj
), -1, -1);
1699 update_socket(GObject
*obj
, const char *action
,
1700 const char *data
, const char *whole_msg
, GType type
)
1702 GtkSocket
*socket
= GTK_SOCKET(obj
);
1707 if (eql(action
, "id")) {
1708 id
= gtk_socket_get_id(socket
);
1709 snprintf(str
, BUFLEN
, "%lu", id
);
1710 send_msg(GTK_BUILDABLE(socket
), "id", str
, NULL
);
1712 ign_cmd(type
, whole_msg
);
1716 update_spinner(GObject
*obj
, const char *action
,
1717 const char *data
, const char *whole_msg
, GType type
)
1719 GtkSpinner
*spinner
= GTK_SPINNER(obj
);
1722 if (eql(action
, "start"))
1723 gtk_spinner_start(spinner
);
1724 else if (eql(action
, "stop"))
1725 gtk_spinner_stop(spinner
);
1727 ign_cmd(type
, whole_msg
);
1731 update_statusbar(GObject
*obj
, const char *action
,
1732 const char *data
, const char *whole_msg
, GType type
)
1734 GtkStatusbar
*statusbar
= GTK_STATUSBAR(obj
);
1735 char *ctx_msg
, *msg
;
1738 if ((ctx_msg
= malloc(strlen(data
) + 1)) == NULL
)
1740 strcpy(ctx_msg
, data
);
1741 ctx_len
= strcspn(ctx_msg
, WHITESPACE
);
1743 ctx_msg
[ctx_len
] = '\0';
1744 msg
= ctx_msg
+ ctx_len
+ 1;
1746 msg
= ctx_msg
+ strlen(ctx_msg
);
1747 if (eql(action
, "push"))
1748 gtk_statusbar_push(statusbar
,
1749 gtk_statusbar_get_context_id(statusbar
, "0"),
1751 else if (eql(action
, "push_id"))
1752 gtk_statusbar_push(statusbar
,
1753 gtk_statusbar_get_context_id(statusbar
, ctx_msg
),
1755 else if (eql(action
, "pop"))
1756 gtk_statusbar_pop(statusbar
,
1757 gtk_statusbar_get_context_id(statusbar
, "0"));
1758 else if (eql(action
, "pop_id"))
1759 gtk_statusbar_pop(statusbar
,
1760 gtk_statusbar_get_context_id(statusbar
, ctx_msg
));
1761 else if (eql(action
, "remove_all"))
1762 gtk_statusbar_remove_all(statusbar
,
1763 gtk_statusbar_get_context_id(statusbar
, "0"));
1764 else if (eql(action
, "remove_all_id"))
1765 gtk_statusbar_remove_all(statusbar
,
1766 gtk_statusbar_get_context_id(statusbar
, ctx_msg
));
1768 ign_cmd(type
, whole_msg
);
1773 update_switch(GObject
*obj
, const char *action
,
1774 const char *data
, const char *whole_msg
, GType type
)
1776 if (eql(action
, "set_active"))
1777 gtk_switch_set_active(GTK_SWITCH(obj
), strtol(data
, NULL
, 10));
1779 ign_cmd(type
, whole_msg
);
1783 update_text_view(GObject
*obj
, const char *action
,
1784 const char *data
, const char *whole_msg
, GType type
)
1786 GtkTextView
*view
= GTK_TEXT_VIEW(obj
);
1787 GtkTextBuffer
*textbuf
= gtk_text_view_get_buffer(view
);
1790 if (eql(action
, "set_text"))
1791 gtk_text_buffer_set_text(textbuf
, data
, -1);
1792 else if (eql(action
, "delete")) {
1793 gtk_text_buffer_get_bounds(textbuf
, &a
, &b
);
1794 gtk_text_buffer_delete(textbuf
, &a
, &b
);
1795 } else if (eql(action
, "insert_at_cursor"))
1796 gtk_text_buffer_insert_at_cursor(textbuf
, data
, -1);
1797 else if (eql(action
, "place_cursor")) {
1798 if (eql(data
, "end"))
1799 gtk_text_buffer_get_end_iter(textbuf
, &a
);
1800 else /* numeric offset */
1801 gtk_text_buffer_get_iter_at_offset(textbuf
, &a
,
1802 strtol(data
, NULL
, 10));
1803 gtk_text_buffer_place_cursor(textbuf
, &a
);
1804 } else if (eql(action
, "place_cursor_at_line")) {
1805 gtk_text_buffer_get_iter_at_line(textbuf
, &a
, strtol(data
, NULL
, 10));
1806 gtk_text_buffer_place_cursor(textbuf
, &a
);
1807 } else if (eql(action
, "scroll_to_cursor"))
1808 gtk_text_view_scroll_to_mark(view
, gtk_text_buffer_get_insert(textbuf
),
1810 else if (eql(action
, "save") && data
!= NULL
&&
1811 (save
= fopen(data
, "w")) != NULL
) {
1812 gtk_text_buffer_get_bounds(textbuf
, &a
, &b
);
1813 save_msg(GTK_BUILDABLE(view
), "insert_at_cursor",
1814 gtk_text_buffer_get_text(textbuf
, &a
, &b
, TRUE
), NULL
);
1817 ign_cmd(type
, whole_msg
);
1821 update_toggle_button(GObject
*obj
, const char *action
,
1822 const char *data
, const char *whole_msg
, GType type
)
1824 if (eql(action
, "set_label"))
1825 gtk_button_set_label(GTK_BUTTON(obj
), data
);
1826 else if (eql(action
, "set_active"))
1827 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj
), strtol(data
, NULL
, 10));
1829 ign_cmd(type
, whole_msg
);
1833 update_tooltip_text(GObject
*obj
, const char *action
,
1834 const char *data
, const char *whole_msg
, GType type
)
1839 gtk_widget_set_tooltip_text(GTK_WIDGET(obj
), data
);
1843 * update_tree_view() needs a few helper functions
1847 * Check if s is a valid string representation of a GtkTreePath
1850 is_path_string(char *s
)
1853 strlen(s
) == strspn(s
, ":0123456789") &&
1854 strstr(s
, "::") == NULL
&&
1855 strcspn(s
, ":") > 0;
1859 tree_model_insert_before(GtkTreeModel
*model
, GtkTreeIter
*iter
,
1860 GtkTreeIter
*parent
, GtkTreeIter
*sibling
)
1862 if (GTK_IS_TREE_STORE(model
))
1863 gtk_tree_store_insert_before(GTK_TREE_STORE(model
),
1864 iter
, parent
, sibling
);
1865 else if (GTK_IS_LIST_STORE(model
))
1866 gtk_list_store_insert_before(GTK_LIST_STORE(model
),
1873 tree_model_insert_after(GtkTreeModel
*model
, GtkTreeIter
*iter
,
1874 GtkTreeIter
*parent
, GtkTreeIter
*sibling
)
1876 if (GTK_IS_TREE_STORE(model
))
1877 gtk_tree_store_insert_after(GTK_TREE_STORE(model
),
1878 iter
, parent
, sibling
);
1879 else if (GTK_IS_LIST_STORE(model
))
1880 gtk_list_store_insert_after(GTK_LIST_STORE(model
),
1887 tree_model_move_before(GtkTreeModel
*model
, GtkTreeIter
*iter
,
1888 GtkTreeIter
*position
)
1890 if (GTK_IS_TREE_STORE(model
))
1891 gtk_tree_store_move_before(GTK_TREE_STORE(model
), iter
, position
);
1892 else if (GTK_IS_LIST_STORE(model
))
1893 gtk_list_store_move_before(GTK_LIST_STORE(model
), iter
, position
);
1899 tree_model_remove(GtkTreeModel
*model
, GtkTreeIter
*iter
)
1901 if (GTK_IS_TREE_STORE(model
))
1902 gtk_tree_store_remove(GTK_TREE_STORE(model
), iter
);
1903 else if (GTK_IS_LIST_STORE(model
))
1904 gtk_list_store_remove(GTK_LIST_STORE(model
), iter
);
1910 tree_model_clear(GtkTreeModel
*model
)
1912 if (GTK_IS_TREE_STORE(model
))
1913 gtk_tree_store_clear(GTK_TREE_STORE(model
));
1914 else if (GTK_IS_LIST_STORE(model
))
1915 gtk_list_store_clear(GTK_LIST_STORE(model
));
1921 tree_model_set(GtkTreeModel
*model
, GtkTreeIter
*iter
, ...)
1926 if (GTK_IS_TREE_STORE(model
))
1927 gtk_tree_store_set_valist(GTK_TREE_STORE(model
), iter
, ap
);
1928 else if (GTK_IS_LIST_STORE(model
))
1929 gtk_list_store_set_valist(GTK_LIST_STORE(model
), iter
, ap
);
1936 * Create an empty row at path if it doesn't yet exist. Create older
1937 * siblings and parents as necessary.
1940 create_subtree(GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
*iter
)
1942 GtkTreePath
*path_1
; /* path's predecessor */
1943 GtkTreeIter iter_1
; /* iter's predecessor */
1945 if (gtk_tree_model_get_iter(model
, iter
, path
))
1947 path_1
= gtk_tree_path_copy(path
);
1948 if (gtk_tree_path_prev(path_1
)) { /* need an older sibling */
1949 create_subtree(model
, path_1
, iter
);
1951 tree_model_insert_after(model
, iter
, NULL
, &iter_1
);
1952 } else if (gtk_tree_path_up(path_1
)) { /* need a parent */
1953 create_subtree(model
, path_1
, iter
);
1954 if (gtk_tree_path_get_depth(path_1
) == 0)
1955 /* first toplevel row */
1956 tree_model_insert_after(model
, iter
, NULL
, NULL
);
1957 else { /* first row in a lower level */
1959 tree_model_insert_after(model
, iter
, &iter_1
, NULL
);
1961 } /* neither prev nor up mean we're at the root of an empty tree */
1962 gtk_tree_path_free(path_1
);
1966 set_tree_view_cell(GtkTreeModel
*model
, GtkTreeIter
*iter
,
1967 const char *path_s
, int col
, const char *new_text
)
1969 GType col_type
= gtk_tree_model_get_column_type(model
, col
);
1976 path
= gtk_tree_path_new_from_string(path_s
);
1977 create_subtree(model
, path
, iter
);
1978 gtk_tree_path_free(path
);
1980 case G_TYPE_BOOLEAN
:
1989 n
= strtoll(new_text
, &endptr
, 10);
1990 if (!errno
&& endptr
!= new_text
) {
1991 tree_model_set(model
, iter
, col
, n
, -1);
1999 d
= strtod(new_text
, &endptr
);
2000 if (!errno
&& endptr
!= new_text
) {
2001 tree_model_set(model
, iter
, col
, d
, -1);
2006 tree_model_set(model
, iter
, col
, new_text
, -1);
2010 fprintf(stderr
, "column %d: %s not implemented\n",
2011 col
, g_type_name(col_type
));
2019 tree_view_set_cursor(GtkTreeView
*view
, GtkTreePath
*path
, GtkTreeViewColumn
*col
)
2021 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2022 /* is just fine and this function need not exist. */
2024 path
= gtk_tree_path_new();
2025 gtk_tree_view_set_cursor(view
, path
, col
, false);
2029 update_tree_view(GObject
*obj
, const char *action
,
2030 const char *data
, const char *whole_msg
, GType type
)
2032 GtkTreeView
*view
= GTK_TREE_VIEW(obj
);
2033 GtkTreeModel
*model
= gtk_tree_view_get_model(view
);
2034 GtkTreeIter iter0
, iter1
;
2035 GtkTreePath
*path
= NULL
;
2036 bool iter0_valid
, iter1_valid
;
2037 char *tokens
, *arg0
, *arg1
, *arg2
;
2038 int col
= -1; /* invalid column number */
2040 if (!GTK_IS_LIST_STORE(model
) && !GTK_IS_TREE_STORE(model
))
2042 fprintf(stderr
, "missing model/");
2043 ign_cmd(type
, whole_msg
);
2046 if ((tokens
= malloc(strlen(data
) + 1)) == NULL
)
2048 strcpy(tokens
, data
);
2049 arg0
= strtok(tokens
, WHITESPACE
);
2050 arg1
= strtok(NULL
, WHITESPACE
);
2051 arg2
= strtok(NULL
, "");
2052 iter0_valid
= is_path_string(arg0
) &&
2053 gtk_tree_model_get_iter_from_string(model
, &iter0
, arg0
);
2054 iter1_valid
= is_path_string(arg1
) &&
2055 gtk_tree_model_get_iter_from_string(model
, &iter1
, arg1
);
2056 if (is_path_string(arg1
))
2057 col
= strtol(arg1
, NULL
, 10);
2058 if (eql(action
, "set") &&
2059 col
> -1 && col
< gtk_tree_model_get_n_columns(model
) &&
2060 is_path_string(arg0
)) {
2061 if (set_tree_view_cell(model
, &iter0
, arg0
, col
, arg2
) == false)
2062 ign_cmd(type
, whole_msg
);
2063 } else if (eql(action
, "scroll") && iter0_valid
&& iter1_valid
) {
2064 path
= gtk_tree_path_new_from_string(arg0
);
2065 gtk_tree_view_scroll_to_cell (view
,
2067 gtk_tree_view_get_column(view
, col
),
2069 } else if (eql(action
, "expand") && iter0_valid
) {
2070 path
= gtk_tree_path_new_from_string(arg0
);
2071 gtk_tree_view_expand_row(view
, path
, false);
2072 } else if (eql(action
, "expand_all") && iter0_valid
) {
2073 path
= gtk_tree_path_new_from_string(arg0
);
2074 gtk_tree_view_expand_row(view
, path
, true);
2075 } else if (eql(action
, "expand_all") && arg0
== NULL
)
2076 gtk_tree_view_expand_all(view
);
2077 else if (eql(action
, "collapse") && iter0_valid
) {
2078 path
= gtk_tree_path_new_from_string(arg0
);
2079 gtk_tree_view_collapse_row(view
, path
);
2080 } else if (eql(action
, "collapse") && arg0
== NULL
)
2081 gtk_tree_view_collapse_all(view
);
2082 else if (eql(action
, "set_cursor") && iter0_valid
) {
2083 path
= gtk_tree_path_new_from_string(arg0
);
2084 tree_view_set_cursor(view
, path
, NULL
);
2085 } else if (eql(action
, "set_cursor") && arg0
== NULL
) {
2086 tree_view_set_cursor(view
, NULL
, NULL
);
2087 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view
));
2088 } else if (eql(action
, "insert_row") && eql(arg0
, "end"))
2089 tree_model_insert_before(model
, &iter1
, NULL
, NULL
);
2090 else if (eql(action
, "insert_row") && iter0_valid
&& eql(arg1
, "as_child"))
2091 tree_model_insert_after(model
, &iter1
, &iter0
, NULL
);
2092 else if (eql(action
, "insert_row") && iter0_valid
)
2093 tree_model_insert_before(model
, &iter1
, NULL
, &iter0
);
2094 else if (eql(action
, "move_row") && iter0_valid
&& eql(arg1
, "end"))
2095 tree_model_move_before(model
, &iter0
, NULL
);
2096 else if (eql(action
, "move_row") && iter0_valid
&& iter1_valid
)
2097 tree_model_move_before(model
, &iter0
, &iter1
);
2098 else if (eql(action
, "remove_row") && iter0_valid
)
2099 tree_model_remove(model
, &iter0
);
2100 else if (eql(action
, "clear") && arg0
== NULL
) {
2101 tree_view_set_cursor(view
, NULL
, NULL
);
2102 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view
));
2103 tree_model_clear(model
);
2104 } else if (eql(action
, "save") && arg0
!= NULL
&&
2105 (save
= fopen(arg0
, "w")) != NULL
) {
2106 gtk_tree_model_foreach(model
, (GtkTreeModelForeachFunc
) save_tree_row_msg
, view
);
2109 ign_cmd(type
, whole_msg
);
2111 gtk_tree_path_free(path
);
2115 update_visibility(GObject
*obj
, const char *action
,
2116 const char *data
, const char *whole_msg
, GType type
)
2121 gtk_widget_set_visible(GTK_WIDGET(obj
), strtol(data
, NULL
, 10));
2125 * Change the style of the widget passed
2128 update_widget_style(GObject
*obj
, const char *name
,
2129 const char *data
, const char *whole_msg
, GType type
)
2131 GtkStyleContext
*context
;
2132 GtkStyleProvider
*style_provider
;
2134 const char *prefix
= "* {", *suffix
= "}";
2140 style_provider
= g_object_get_data(obj
, "style_provider");
2141 sz
= strlen(prefix
) + strlen(suffix
) + strlen(data
) + 1;
2142 context
= gtk_widget_get_style_context(GTK_WIDGET(obj
));
2143 gtk_style_context_remove_provider(context
, style_provider
);
2144 if ((style_decl
= malloc(sz
)) == NULL
)
2146 strcpy(style_decl
, prefix
);
2147 strcat(style_decl
, data
);
2148 strcat(style_decl
, suffix
);
2149 gtk_style_context_add_provider(context
, style_provider
,
2150 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
2151 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider
),
2152 style_decl
, -1, NULL
);
2157 update_window(GObject
*obj
, const char *action
,
2158 const char *data
, const char *whole_msg
, GType type
)
2160 if (!update_class_window(obj
, action
, data
, whole_msg
, type
))
2161 ign_cmd(type
, whole_msg
);
2165 * Simulate user activity on various widgets
2168 fake_ui_activity(GObject
*obj
, const char *action
,
2169 const char *data
, const char *whole_msg
, GType type
)
2173 if (!GTK_IS_WIDGET(obj
))
2174 ign_cmd(type
, whole_msg
);
2175 else if (GTK_IS_ENTRY(obj
) || GTK_IS_SPIN_BUTTON(obj
))
2176 cb_editable(GTK_BUILDABLE(obj
), "text");
2177 else if (GTK_IS_SCALE(obj
))
2178 cb_range(GTK_BUILDABLE(obj
), "value");
2179 else if (GTK_IS_CALENDAR(obj
))
2180 cb_calendar(GTK_BUILDABLE(obj
), "clicked");
2181 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj
))
2182 cb_file_chooser_button(GTK_BUILDABLE(obj
), "file");
2183 else if (!gtk_widget_activate(GTK_WIDGET(obj
)))
2184 ign_cmd(type
, whole_msg
);
2188 * The final UI update
2191 main_quit(GObject
*obj
, const char *action
,
2192 const char *data
, const char *whole_msg
, GType type
)
2203 * Don't update anything; just complain
2206 complain(GObject
*obj
, const char *action
,
2207 const char *data
, const char *whole_msg
, GType type
)
2212 ign_cmd(type
, whole_msg
);
2216 * Data to be passed to and from the GTK main loop
2219 void (*fn
)(GObject
*, const char *action
,
2220 const char *data
, const char *msg
, GType type
);
2230 * Parse command pointed to by ud, and act on ui accordingly; post
2231 * semaphore ud.msg_digested if done. Runs once per command inside
2235 update_ui(struct ui_data
*ud
)
2237 (ud
->fn
)(ud
->obj
, ud
->action
, ud
->data
, ud
->msg
, ud
->type
);
2238 free(ud
->msg_tokens
);
2241 return G_SOURCE_REMOVE
;
2245 * Keep track of loading files to avoid recursive loading of the same
2246 * file. If filename = NULL, forget the most recently remembered file.
2249 remember_loading_file(char *filename
)
2251 static char *filenames
[BUFLEN
];
2252 static size_t latest
= 0;
2255 if (filename
== NULL
) { /* pop */
2261 for (i
= 1; i
<= latest
; i
++)
2262 if (eql(filename
, filenames
[i
]))
2264 if (latest
> BUFLEN
-2)
2266 filenames
[++latest
] = filename
;
2272 * Read lines from stream cmd and perform appropriate actions on the
2276 digest_msg(FILE *cmd
)
2278 FILE *load
; /* restoring user data */
2280 static int recursion
= -1; /* > 0 means this is a recursive call */
2285 char first_char
= '\0';
2286 size_t msg_size
= 32;
2287 int name_start
= 0, name_end
= 0;
2288 int action_start
= 0, action_end
= 0;
2293 if ((ud
= malloc(sizeof(*ud
))) == NULL
)
2295 if ((ud
->msg
= malloc(msg_size
)) == NULL
)
2297 ud
->type
= G_TYPE_INVALID
;
2298 pthread_testcancel();
2301 read_buf(cmd
, &ud
->msg
, &msg_size
);
2304 data_start
= strlen(ud
->msg
);
2305 if ((ud
->msg_tokens
= malloc(strlen(ud
->msg
) + 1)) == NULL
)
2307 strcpy(ud
->msg_tokens
, ud
->msg
);
2308 sscanf(ud
->msg
, " %c", &first_char
);
2309 if (strlen(ud
->msg
) == 0 || first_char
== '#') { /* comment */
2310 ud
->fn
= update_nothing
;
2313 sscanf(ud
->msg_tokens
,
2314 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2315 &name_start
, &name_end
, &action_start
, &action_end
, &data_start
);
2316 ud
->msg_tokens
[name_end
] = ud
->msg_tokens
[action_end
] = '\0';
2317 name
= ud
->msg_tokens
+ name_start
;
2318 ud
->action
= ud
->msg_tokens
+ action_start
;
2319 if (eql(ud
->action
, "main_quit")) {
2323 ud
->data
= ud
->msg_tokens
+ data_start
;
2324 if (eql(ud
->action
, "load") && strlen(ud
->data
) > 0 &&
2325 (load
= fopen(ud
->data
, "r")) != NULL
&&
2326 remember_loading_file(ud
->data
)) {
2329 remember_loading_file(NULL
);
2330 ud
->fn
= update_nothing
;
2333 if ((ud
->obj
= (gtk_builder_get_object(builder
, name
))) == NULL
) {
2337 ud
->type
= G_TYPE_FROM_INSTANCE(ud
->obj
);
2338 if (eql(ud
->action
, "force"))
2339 ud
->fn
= fake_ui_activity
;
2340 else if (eql(ud
->action
, "set_sensitive"))
2341 ud
->fn
= update_sensitivity
;
2342 else if (eql(ud
->action
, "set_visible"))
2343 ud
->fn
= update_visibility
;
2344 else if (eql(ud
->action
, "set_size_request"))
2345 ud
->fn
= update_size_request
;
2346 else if (eql(ud
->action
, "set_tooltip_text"))
2347 ud
->fn
= update_tooltip_text
;
2348 else if (eql(ud
->action
, "grab_focus"))
2349 ud
->fn
= update_focus
;
2350 else if (eql(ud
->action
, "style")) {
2352 ud
->fn
= update_widget_style
;
2353 } else if (ud
->type
== GTK_TYPE_DRAWING_AREA
)
2354 ud
->fn
= update_drawing_area
;
2355 else if (ud
->type
== GTK_TYPE_TREE_VIEW
)
2356 ud
->fn
= update_tree_view
;
2357 else if (ud
->type
== GTK_TYPE_COMBO_BOX_TEXT
)
2358 ud
->fn
= update_combo_box_text
;
2359 else if (ud
->type
== GTK_TYPE_LABEL
)
2360 ud
->fn
= update_label
;
2361 else if (ud
->type
== GTK_TYPE_IMAGE
)
2362 ud
->fn
= update_image
;
2363 else if (ud
->type
== GTK_TYPE_TEXT_VIEW
)
2364 ud
->fn
= update_text_view
;
2365 else if (ud
->type
== GTK_TYPE_NOTEBOOK
)
2366 ud
->fn
= update_notebook
;
2367 else if (ud
->type
== GTK_TYPE_EXPANDER
)
2368 ud
->fn
= update_expander
;
2369 else if (ud
->type
== GTK_TYPE_FRAME
)
2370 ud
->fn
= update_frame
;
2371 else if (ud
->type
== GTK_TYPE_SCROLLED_WINDOW
)
2372 ud
->fn
= update_scrolled_window
;
2373 else if (ud
->type
== GTK_TYPE_BUTTON
)
2374 ud
->fn
= update_button
;
2375 else if (ud
->type
== GTK_TYPE_FILE_CHOOSER_DIALOG
)
2376 ud
->fn
= update_file_chooser_dialog
;
2377 else if (ud
->type
== GTK_TYPE_FILE_CHOOSER_BUTTON
)
2378 ud
->fn
= update_file_chooser_button
;
2379 else if (ud
->type
== GTK_TYPE_COLOR_BUTTON
)
2380 ud
->fn
= update_color_button
;
2381 else if (ud
->type
== GTK_TYPE_FONT_BUTTON
)
2382 ud
->fn
= update_font_button
;
2383 else if (ud
->type
== GTK_TYPE_PRINT_UNIX_DIALOG
)
2384 ud
->fn
= update_print_dialog
;
2385 else if (ud
->type
== GTK_TYPE_SWITCH
)
2386 ud
->fn
= update_switch
;
2387 else if (ud
->type
== GTK_TYPE_TOGGLE_BUTTON
||
2388 ud
->type
== GTK_TYPE_RADIO_BUTTON
||
2389 ud
->type
== GTK_TYPE_CHECK_BUTTON
)
2390 ud
->fn
= update_toggle_button
;
2391 else if (ud
->type
== GTK_TYPE_SPIN_BUTTON
||
2392 ud
->type
== GTK_TYPE_ENTRY
)
2393 ud
->fn
= update_entry
;
2394 else if (ud
->type
== GTK_TYPE_SCALE
)
2395 ud
->fn
= update_scale
;
2396 else if (ud
->type
== GTK_TYPE_PROGRESS_BAR
)
2397 ud
->fn
= update_progress_bar
;
2398 else if (ud
->type
== GTK_TYPE_SPINNER
)
2399 ud
->fn
= update_spinner
;
2400 else if (ud
->type
== GTK_TYPE_STATUSBAR
)
2401 ud
->fn
= update_statusbar
;
2402 else if (ud
->type
== GTK_TYPE_CALENDAR
)
2403 ud
->fn
= update_calendar
;
2404 else if (ud
->type
== GTK_TYPE_SOCKET
)
2405 ud
->fn
= update_socket
;
2406 else if (ud
->type
== GTK_TYPE_WINDOW
||
2407 ud
->type
== GTK_TYPE_DIALOG
)
2408 ud
->fn
= update_window
;
2412 pthread_testcancel();
2413 gdk_threads_add_timeout(0, (GSourceFunc
) update_ui
, ud
);
2420 * ============================================================
2422 * ============================================================
2426 * Callbacks that forward a modification of a tree view cell to the
2430 cb_tree_model_edit(GtkCellRenderer
*renderer
, const gchar
*path_s
,
2431 const gchar
*new_text
, gpointer model
)
2437 gtk_tree_model_get_iter_from_string(model
, &iter
, path_s
);
2438 view
= g_object_get_data(G_OBJECT(renderer
), "tree_view");
2439 col
= g_object_get_data(G_OBJECT(renderer
), "col_number");
2440 set_tree_view_cell(model
, &iter
, path_s
, GPOINTER_TO_INT(col
),
2442 send_tree_cell_msg_by(send_msg
, model
, path_s
, &iter
, GPOINTER_TO_INT(col
),
2443 GTK_BUILDABLE(view
));
2447 cb_tree_model_toggle(GtkCellRenderer
*renderer
, gchar
*path_s
, gpointer model
)
2453 gtk_tree_model_get_iter_from_string(model
, &iter
, path_s
);
2454 col
= g_object_get_data(G_OBJECT(renderer
), "col_number");
2455 gtk_tree_model_get(model
, &iter
, col
, &toggle_state
, -1);
2456 set_tree_view_cell(model
, &iter
, path_s
, GPOINTER_TO_INT(col
),
2457 toggle_state
? "0" : "1");
2461 * Attach to renderer key "col_number". Associate "col_number" with
2462 * the corresponding column number in the underlying model.
2463 * Due to what looks like a gap in the GTK API, renderer id and column
2464 * number are taken directly from the XML .ui file.
2467 tree_view_column_get_renderer_column(const char *ui_file
, GtkTreeViewColumn
*t_col
,
2468 int n
, GtkCellRenderer
**renderer
)
2471 xmlXPathContextPtr xpath_ctx
;
2472 xmlXPathObjectPtr xpath_obj
;
2473 xmlNodeSetPtr nodes
;
2476 xmlChar
*xpath
, *renderer_name
= NULL
, *m_col_s
= NULL
;
2477 char *xpath_base1
= "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2478 const char *xpath_id
= widget_name(GTK_BUILDABLE(t_col
));
2479 char *xpath_base2
= "\"]/child[";
2480 size_t xpath_n_len
= 3; /* Big Enough (TM) */
2481 char *xpath_base3
= "]/object[@class=\"GtkCellRendererText\""
2482 " or @class=\"GtkCellRendererToggle\"]/";
2483 char *xpath_text_col
= "../attributes/attribute[@name=\"text\""
2484 " or @name=\"active\"]";
2485 char *xpath_renderer_id
= "/@id";
2489 if ((doc
= xmlParseFile(ui_file
)) == NULL
)
2491 if ((xpath_ctx
= xmlXPathNewContext(doc
)) == NULL
) {
2495 xpath_len
= 2 * (strlen(xpath_base1
) + strlen(xpath_id
) +
2496 strlen(xpath_base2
) + xpath_n_len
+
2497 strlen(xpath_base3
))
2499 + strlen(xpath_text_col
) + strlen(xpath_renderer_id
)
2501 if ((xpath
= malloc(xpath_len
)) == NULL
) {
2505 snprintf((char *) xpath
, xpath_len
, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2506 xpath_base1
, xpath_id
, xpath_base2
, n
, xpath_base3
, xpath_text_col
,
2507 xpath_base1
, xpath_id
, xpath_base2
, n
, xpath_base3
, xpath_renderer_id
);
2508 if ((xpath_obj
= xmlXPathEvalExpression(xpath
, xpath_ctx
)) == NULL
) {
2509 xmlXPathFreeContext(xpath_ctx
);
2514 if ((nodes
= xpath_obj
->nodesetval
) != NULL
) {
2515 for (i
= 0; i
< nodes
->nodeNr
; ++i
) {
2516 if (nodes
->nodeTab
[i
]->type
== XML_ELEMENT_NODE
) {
2517 cur
= nodes
->nodeTab
[i
];
2518 m_col_s
= xmlNodeGetContent(cur
);
2520 cur
= nodes
->nodeTab
[i
];
2521 renderer_name
= xmlNodeGetContent(cur
);
2525 if (renderer_name
) {
2526 *renderer
= GTK_CELL_RENDERER(
2527 gtk_builder_get_object(builder
, (char *) renderer_name
));
2529 g_object_set_data(G_OBJECT(*renderer
), "col_number",
2530 GINT_TO_POINTER(strtol((char *) m_col_s
,
2535 xmlFree(renderer_name
);
2537 xmlXPathFreeObject(xpath_obj
);
2538 xmlXPathFreeContext(xpath_ctx
);
2545 connect_widget_signals(gpointer
*obj
, char *ui_file
)
2547 const char *name
= NULL
;
2548 char *suffix
= NULL
;
2550 GType type
= G_TYPE_INVALID
;
2552 type
= G_TYPE_FROM_INSTANCE(obj
);
2553 if (GTK_IS_BUILDABLE(obj
))
2554 name
= widget_name(GTK_BUILDABLE(obj
));
2555 if (type
== GTK_TYPE_TREE_VIEW_COLUMN
) {
2556 gboolean editable
= FALSE
;
2558 GtkTreeModel
*model
;
2559 GtkCellRenderer
*renderer
;
2562 g_signal_connect(obj
, "clicked", G_CALLBACK(cb_simple
), "clicked");
2563 view
= GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj
)));
2564 model
= gtk_tree_view_get_model(view
);
2566 if (!tree_view_column_get_renderer_column(ui_file
, GTK_TREE_VIEW_COLUMN(obj
), i
, &renderer
))
2568 g_object_set_data(G_OBJECT(renderer
), "tree_view", view
);
2569 if (GTK_IS_CELL_RENDERER_TEXT(renderer
)) {
2570 g_object_get(renderer
, "editable", &editable
, NULL
);
2572 g_signal_connect(renderer
, "edited", G_CALLBACK(cb_tree_model_edit
), model
);
2573 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer
)) {
2574 g_object_get(renderer
, "activatable", &editable
, NULL
);
2576 g_signal_connect(renderer
, "toggled", G_CALLBACK(cb_tree_model_toggle
), model
);
2580 else if (type
== GTK_TYPE_BUTTON
) {
2581 /* Button associated with a GtkTextView. */
2582 if ((suffix
= strstr(name
, "_send_text")) != NULL
&&
2583 GTK_IS_TEXT_VIEW(obj2
= obj_sans_suffix(suffix
, name
)))
2584 g_signal_connect(obj
, "clicked", G_CALLBACK(cb_send_text
),
2585 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2
)));
2586 else if ((suffix
= strstr(name
, "_send_selection")) != NULL
&&
2587 GTK_IS_TEXT_VIEW(obj2
= obj_sans_suffix(suffix
, name
)))
2588 g_signal_connect(obj
, "clicked", G_CALLBACK(cb_send_text_selection
),
2589 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2
)));
2591 g_signal_connect(obj
, "clicked", G_CALLBACK(cb_simple
), "clicked");
2592 /* Buttons associated with (and part of) a GtkDialog.
2593 * (We shun response ids which could be returned from
2594 * gtk_dialog_run() because that would require the
2595 * user to define those response ids in Glade,
2597 if ((suffix
= strstr(name
, "_cancel")) != NULL
&&
2598 GTK_IS_DIALOG(obj2
= obj_sans_suffix(suffix
, name
)))
2599 if (eql(widget_name(GTK_BUILDABLE(obj2
)), MAIN_WIN
))
2600 g_signal_connect_swapped(obj
, "clicked", G_CALLBACK(gtk_main_quit
), NULL
);
2602 g_signal_connect_swapped(obj
, "clicked", G_CALLBACK(gtk_widget_hide
), obj2
);
2603 else if ((suffix
= strstr(name
, "_ok")) != NULL
&&
2604 GTK_IS_DIALOG(obj2
= obj_sans_suffix(suffix
, name
))) {
2605 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2
))
2606 g_signal_connect_swapped(obj
, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection
), GTK_FILE_CHOOSER(obj2
));
2607 if (eql(widget_name(GTK_BUILDABLE(obj2
)), MAIN_WIN
))
2608 g_signal_connect_swapped(obj
, "clicked", G_CALLBACK(gtk_main_quit
), NULL
);
2610 g_signal_connect_swapped(obj
, "clicked", G_CALLBACK(gtk_widget_hide
), obj2
);
2611 } else if ((suffix
= strstr(name
, "_apply")) != NULL
&&
2612 GTK_IS_FILE_CHOOSER_DIALOG(obj2
= obj_sans_suffix(suffix
, name
)))
2613 g_signal_connect_swapped(obj
, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection
), obj2
);
2615 } else if (GTK_IS_MENU_ITEM(obj
))
2616 if ((suffix
= strstr(name
, "_invoke")) != NULL
&&
2617 GTK_IS_DIALOG(obj2
= obj_sans_suffix(suffix
, name
)))
2618 g_signal_connect_swapped(obj
, "activate", G_CALLBACK(gtk_widget_show
), obj2
);
2620 g_signal_connect(obj
, "activate", G_CALLBACK(cb_menu_item
), "active");
2621 else if (GTK_IS_WINDOW(obj
)) {
2622 g_signal_connect(obj
, "delete-event", G_CALLBACK(cb_event_simple
), "closed");
2623 if (eql(name
, MAIN_WIN
))
2624 g_signal_connect_swapped(obj
, "delete-event", G_CALLBACK(gtk_main_quit
), NULL
);
2626 g_signal_connect(obj
, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
2627 } else if (type
== GTK_TYPE_FILE_CHOOSER_BUTTON
)
2628 g_signal_connect(obj
, "file-set", G_CALLBACK(cb_file_chooser_button
), "file");
2629 else if (type
== GTK_TYPE_COLOR_BUTTON
)
2630 g_signal_connect(obj
, "color-set", G_CALLBACK(cb_color_button
), "color");
2631 else if (type
== GTK_TYPE_FONT_BUTTON
)
2632 g_signal_connect(obj
, "font-set", G_CALLBACK(cb_font_button
), "font");
2633 else if (type
== GTK_TYPE_SWITCH
)
2634 g_signal_connect(obj
, "notify::active", G_CALLBACK(cb_switch
), NULL
);
2635 else if (type
== GTK_TYPE_TOGGLE_BUTTON
|| type
== GTK_TYPE_RADIO_BUTTON
|| type
== GTK_TYPE_CHECK_BUTTON
)
2636 g_signal_connect(obj
, "toggled", G_CALLBACK(cb_toggle_button
), NULL
);
2637 else if (type
== GTK_TYPE_SPIN_BUTTON
|| type
== GTK_TYPE_ENTRY
)
2638 g_signal_connect(obj
, "changed", G_CALLBACK(cb_editable
), "text");
2639 else if (type
== GTK_TYPE_SCALE
)
2640 g_signal_connect(obj
, "value-changed", G_CALLBACK(cb_range
), "value");
2641 else if (type
== GTK_TYPE_CALENDAR
) {
2642 g_signal_connect(obj
, "day-selected-double-click", G_CALLBACK(cb_calendar
), "doubleclicked");
2643 g_signal_connect(obj
, "day-selected", G_CALLBACK(cb_calendar
), "clicked");
2644 } else if (type
== GTK_TYPE_TREE_SELECTION
)
2645 g_signal_connect(obj
, "changed", G_CALLBACK(cb_tree_selection
), "clicked");
2646 else if (type
== GTK_TYPE_SOCKET
) {
2647 g_signal_connect(obj
, "plug-added", G_CALLBACK(cb_simple
), "plug-added");
2648 g_signal_connect(obj
, "plug-removed", G_CALLBACK(cb_simple
), "plug-removed");
2649 } else if (type
== GTK_TYPE_DRAWING_AREA
)
2650 g_signal_connect(obj
, "draw", G_CALLBACK(cb_draw
), NULL
);
2651 else if (type
== GTK_TYPE_EVENT_BOX
) {
2652 gtk_widget_set_can_focus(GTK_WIDGET(obj
), true);
2653 g_signal_connect(obj
, "button-press-event", G_CALLBACK(cb_event_box_button
), "button_press");
2654 g_signal_connect(obj
, "button-release-event", G_CALLBACK(cb_event_box_button
), "button_release");
2655 g_signal_connect(obj
, "motion-notify-event", G_CALLBACK(cb_event_box_motion
), "motion");
2656 g_signal_connect(obj
, "key-press-event", G_CALLBACK(cb_event_box_key
), "key_press");
2661 * We keep a style provider with each widget
2664 add_widget_style_provider(gpointer
*obj
, void *data
)
2666 GtkStyleContext
*context
;
2667 GtkCssProvider
*style_provider
;
2670 if (!GTK_IS_WIDGET(obj
))
2672 style_provider
= gtk_css_provider_new();
2673 context
= gtk_widget_get_style_context(GTK_WIDGET(obj
));
2674 gtk_style_context_add_provider(context
,
2675 GTK_STYLE_PROVIDER(style_provider
),
2676 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
2677 g_object_set_data(G_OBJECT(obj
), "style_provider", style_provider
);
2681 prepare_widgets(char *ui_file
)
2683 GSList
*objects
= NULL
;
2685 objects
= gtk_builder_get_objects(builder
);
2686 g_slist_foreach(objects
, (GFunc
) connect_widget_signals
, ui_file
);
2687 g_slist_foreach(objects
, (GFunc
) add_widget_style_provider
, NULL
);
2688 g_slist_free(objects
);
2692 main(int argc
, char *argv
[])
2695 char *in_fifo
= NULL
, *out_fifo
= NULL
, *ui_file
= NULL
;
2696 char *log_file
= NULL
;
2697 char *xid_s
= NULL
, xid_s2
[BUFLEN
];
2701 GtkWidget
*plug
, *body
;
2703 GError
*error
= NULL
;
2704 GObject
*main_window
= NULL
;
2705 FILE *in
= NULL
; /* command input */
2707 /* Disable runtime GLIB deprecation warnings: */
2708 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
2712 gtk_init(&argc
, &argv
);
2713 while ((opt
= getopt(argc
, argv
, "bhe:i:l:o:u:GV")) != -1) {
2715 case 'b': bg
= true; break;
2716 case 'e': xid_s
= optarg
; break;
2717 case 'i': in_fifo
= optarg
; break;
2718 case 'l': log_file
= optarg
; break;
2719 case 'o': out_fifo
= optarg
; break;
2720 case 'u': ui_file
= optarg
; break;
2721 case 'G': bye(EXIT_SUCCESS
, stdout
,
2722 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
2723 "cairo v%s (running v%s)\n",
2724 GTK_MAJOR_VERSION
, GTK_MINOR_VERSION
, GTK_MICRO_VERSION
,
2725 gtk_get_major_version(), gtk_get_minor_version(),
2726 gtk_get_micro_version(),
2727 CAIRO_VERSION_STRING
, cairo_version_string());
2729 case 'V': bye(EXIT_SUCCESS
, stdout
, "%s\n", VERSION
); break;
2730 case 'h': bye(EXIT_SUCCESS
, stdout
, USAGE
); break;
2732 default: bye(EXIT_FAILURE
, stderr
, USAGE
); break;
2735 if (argv
[optind
] != NULL
)
2736 bye(EXIT_FAILURE
, stderr
,
2737 "illegal parameter '%s'\n" USAGE
, argv
[optind
]);
2738 in
= fifo(in_fifo
, "r");
2739 out
= fifo(out_fifo
, "w");
2741 if (in
== stdin
|| out
== stdout
)
2742 bye(EXIT_FAILURE
, stderr
,
2743 "parameter -b requires both -i and -o\n");
2744 else if ((pid
= fork()) > 0)
2745 bye(EXIT_SUCCESS
, stdout
, "%d\n", pid
);
2747 bye(EXIT_FAILURE
, stderr
,
2748 "going to background: %s\n", strerror(errno
));
2750 if (ui_file
== NULL
)
2751 ui_file
= "pipeglade.ui";
2752 builder
= gtk_builder_new();
2753 if (gtk_builder_add_from_file(builder
, ui_file
, &error
) == 0)
2754 bye(EXIT_FAILURE
, stderr
, "%s\n", error
->message
);
2755 log_out
= open_log(log_file
);
2756 pthread_create(&receiver
, NULL
, (void *(*)(void *)) digest_msg
, in
);
2757 main_window
= gtk_builder_get_object(builder
, MAIN_WIN
);
2758 if (!GTK_IS_WINDOW(main_window
))
2759 bye(EXIT_FAILURE
, stderr
,
2760 "no toplevel window named \'" MAIN_WIN
"\'\n");
2762 LIBXML_TEST_VERSION
;
2763 prepare_widgets(ui_file
);
2764 if (xid_s
== NULL
) /* standalone */
2765 gtk_widget_show(GTK_WIDGET(main_window
));
2766 else { /* We're being XEmbedded */
2767 xid
= strtoul(xid_s
, NULL
, 10);
2768 snprintf(xid_s2
, BUFLEN
, "%lu", xid
);
2769 if (!eql(xid_s
, xid_s2
))
2770 bye(EXIT_FAILURE
, stderr
,
2771 "%s is not a valid XEmbed socket id\n", xid_s
);
2772 body
= gtk_bin_get_child(GTK_BIN(main_window
));
2773 gtk_container_remove(GTK_CONTAINER(main_window
), body
);
2774 plug
= gtk_plug_new(xid
);
2775 if (!gtk_plug_get_embedded(GTK_PLUG(plug
)))
2776 bye(EXIT_FAILURE
, stderr
,
2777 "unable to embed into XEmbed socket %s\n", xid_s
);
2778 gtk_container_add(GTK_CONTAINER(plug
), body
);
2779 gtk_widget_show(plug
);
2786 if (out
!= stdout
) {
2790 pthread_cancel(receiver
);
2791 pthread_join(receiver
, NULL
);