Various edits in manual page and project html page
[pipeglade.git] / pipeglade.c
blob48a1733f3050739fc38d1c311909ab8ea8629336
1 /*
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.
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <gtk/gtk.h>
27 #include <gtk/gtkunixprint.h>
28 #include <gtk/gtkx.h>
29 #include <inttypes.h>
30 #include <libxml/xpath.h>
31 #include <locale.h>
32 #include <math.h>
33 #include <pthread.h>
34 #include <search.h>
35 #include <semaphore.h>
36 #include <stdio.h>
37 #include <stdarg.h>
38 #include <stdbool.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/select.h>
42 #include <sys/stat.h>
43 #include <time.h>
44 #include <unistd.h>
46 #define VERSION "4.6.0"
47 #define BUFLEN 256
48 #define WHITESPACE " \t\n"
49 #define MAIN_WIN "main"
50 #define USAGE \
51 "usage: pipeglade [-h] " \
52 "[-e xid] " \
53 "[-i in-fifo] " \
54 "[-o out-fifo] " \
55 "[-u glade-file.ui]\n" \
56 " [-G] " \
57 "[-V] " \
58 "[--display X-server]\n"
60 #define ABORT \
61 { \
62 fprintf(stderr, \
63 "In %s (%s:%d): ", \
64 __func__, __FILE__, __LINE__); \
65 abort(); \
68 #define OOM_ABORT \
69 { \
70 fprintf(stderr, \
71 "Out of memory in %s (%s:%d): ", \
72 __func__, __FILE__, __LINE__); \
73 abort(); \
74 } \
76 static FILE *out; /* UI feedback messages */
77 static FILE *save; /* saving user data */
78 static GtkBuilder *builder; /* to be read from .ui file */
79 struct ui_data {
80 void (*fn)(GObject *, const char *action,
81 const char *data, const char *msg, GType type);
82 GObject *obj;
83 char *action;
84 char *data;
85 char *msg;
86 char *msg_tokens;
87 GType type;
88 sem_t msg_digested;
92 * Print a formatted message to stream s and give up with status
94 static void
95 bye(int status, FILE *s, const char *fmt, ...)
97 va_list ap;
99 va_start(ap, fmt);
100 vfprintf(s, fmt, ap);
101 va_end(ap);
102 exit(status);
106 * Check if string s1 and s2 are equal
108 static bool
109 eql(const char *s1, const char *s2)
111 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
114 static const char *
115 widget_name(void *obj)
117 return gtk_buildable_get_name(GTK_BUILDABLE(obj));
120 static void
121 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
123 char *data;
124 const char *w_name = widget_name(obj);
125 fd_set wfds;
126 int ofd = fileno(o);
127 struct timeval timeout = {1, 0};
129 FD_ZERO(&wfds);
130 FD_SET(ofd, &wfds);
131 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
132 fprintf(o, "%s:%s ", w_name, tag);
133 while ((data = va_arg(ap, char *)) != NULL) {
134 size_t i = 0;
135 char c;
137 while ((c = data[i++]) != '\0')
138 if (c == '\\')
139 fprintf(o, "\\\\");
140 else if (c == '\n')
141 fprintf(o, "\\n");
142 else
143 putc(c, o);
145 putc('\n', o);
146 } else
147 fprintf(stderr,
148 "send error; discarding feedback message %s:%s\n",
149 w_name, tag);
153 * Send GUI feedback to global stream "out". The message format is
154 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
155 * last argument must be NULL.
157 static void
158 send_msg(GtkBuildable *obj, const char *tag, ...)
160 va_list ap;
161 va_start(ap, tag);
162 send_msg_to(out, obj, tag, ap);
163 va_end(ap);
167 * Send message from GUI to global stream "save". The message format
168 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
169 * last argument must be NULL.
171 static void
172 save_msg(GtkBuildable *obj, const char *tag, ...)
174 va_list ap;
175 va_start(ap, tag);
176 send_msg_to(save, obj, tag, ap);
177 va_end(ap);
181 * Send message from GUI to global stream "save". The message format
182 * is "<origin>:set <data ...>". The variadic arguments are strings;
183 * last argument must be NULL.
185 static void
186 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
188 va_list ap;
189 va_start(ap, tag);
190 send_msg_to(save, obj, "set", ap);
191 va_end(ap);
195 * Callback that sends user's selection from a file dialog
197 static void
198 cb_send_file_chooser_dialog_selection(gpointer user_data)
200 send_msg(user_data, "file",
201 gtk_file_chooser_get_filename(user_data), NULL);
202 send_msg(user_data, "folder",
203 gtk_file_chooser_get_current_folder(user_data), NULL);
207 * Callback that sends in a message the content of the text buffer
208 * passed in user_data
210 static void
211 cb_send_text(GtkBuildable *obj, gpointer user_data)
213 GtkTextIter a, b;
215 gtk_text_buffer_get_bounds(user_data, &a, &b);
216 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
220 * Callback that sends in a message the highlighted text from the text
221 * buffer which was passed in user_data
223 static void
224 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
226 GtkTextIter a, b;
228 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
229 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
233 * Callbacks that send messages about pointer device activity in a
234 * GtkEventBox.
236 static bool
237 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
239 char data[BUFLEN];
241 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
242 e->button.button, e->button.x, e->button.y);
243 send_msg(obj, user_data, data, NULL);
244 return true;
247 static bool
248 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
250 char data[BUFLEN];
252 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
253 send_msg(obj, user_data, data, NULL);
254 return true;
258 * Callback that sends in a message the name of the key pressed when
259 * a GtkEventBox is focused.
261 static bool
262 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
264 send_msg(obj, user_data, gdk_keyval_name(e->key.keyval), NULL);
265 return true;
269 * Use msg_sender() to send a message describing a particular cell.
271 static void
272 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
273 GtkTreeModel *model, const char *path_s,
274 GtkTreeIter *iter, int col, GtkBuildable *obj)
276 GValue value = G_VALUE_INIT;
277 GType col_type;
278 char str[BUFLEN];
280 gtk_tree_model_get_value(model, iter, col, &value);
281 col_type = gtk_tree_model_get_column_type(model, col);
282 switch (col_type) {
283 case G_TYPE_INT:
284 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
285 msg_sender(obj, "gint", path_s, str, NULL);
286 break;
287 case G_TYPE_LONG:
288 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
289 msg_sender(obj, "glong", path_s, str, NULL);
290 break;
291 case G_TYPE_INT64:
292 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
293 msg_sender(obj, "gint64", path_s, str, NULL);
294 break;
295 case G_TYPE_UINT:
296 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
297 msg_sender(obj, "guint", path_s, str, NULL);
298 break;
299 case G_TYPE_ULONG:
300 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
301 msg_sender(obj, "gulong", path_s, str, NULL);
302 break;
303 case G_TYPE_UINT64:
304 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
305 msg_sender(obj, "guint64", path_s, str, NULL);
306 break;
307 case G_TYPE_BOOLEAN:
308 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
309 msg_sender(obj, "gboolean", path_s, str, NULL);
310 break;
311 case G_TYPE_FLOAT:
312 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
313 msg_sender(obj, "gfloat", path_s, str, NULL);
314 break;
315 case G_TYPE_DOUBLE:
316 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
317 msg_sender(obj, "gdouble", path_s, str, NULL);
318 break;
319 case G_TYPE_STRING:
320 snprintf(str, BUFLEN, " %d ", col);
321 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
322 break;
323 default:
324 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
325 break;
327 g_value_unset(&value);
331 * Use msg_sender() to send one message per column for a single row.
333 static void
334 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
335 GtkTreeModel *model, char *path_s,
336 GtkTreeIter *iter, GtkBuildable *obj)
338 int col;
339 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
340 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
344 * send_tree_row_msg serves as an argument for
345 * gtk_tree_selection_selected_foreach()
347 static gboolean
348 send_tree_row_msg(GtkTreeModel *model,
349 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
351 char *path_s = gtk_tree_path_to_string(path);
353 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
354 g_free(path_s);
355 return FALSE;
359 * save_tree_row_msg serves as an argument for
360 * gtk_tree_model_foreach().
361 * Send message from GUI to global stream "save".
363 static gboolean
364 save_tree_row_msg(GtkTreeModel *model,
365 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
367 char *path_s = gtk_tree_path_to_string(path);
369 (void)path;
370 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
371 g_free(path_s);
372 return FALSE;
376 * Callback that sends message(s) whose nature depends on the
377 * arguments passed. A call to this function will also be initiated
378 * by the user command ...:force.
380 static void
381 cb(GtkBuildable *obj, const char *tag)
383 char str[BUFLEN];
384 GdkRGBA color;
385 GtkTreeView *view;
386 unsigned int year = 0, month = 0, day = 0;
388 if (GTK_IS_ENTRY(obj))
389 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
390 else if (GTK_IS_MENU_ITEM(obj))
391 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
392 else if (GTK_IS_RANGE(obj)) {
393 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
394 send_msg(obj, tag, str, NULL);
395 } else if (GTK_IS_SWITCH(obj))
396 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
397 else if (GTK_IS_TOGGLE_BUTTON(obj))
398 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
399 else if (GTK_IS_COLOR_BUTTON(obj)) {
400 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
401 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
402 } else if (GTK_IS_FONT_BUTTON(obj))
403 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
404 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
405 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
406 else if (GTK_IS_BUTTON(obj) || GTK_IS_TREE_VIEW_COLUMN(obj) || GTK_IS_SOCKET(obj))
407 send_msg(obj, tag, NULL);
408 else if (GTK_IS_CALENDAR(obj)) {
409 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
410 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
411 send_msg(obj, tag, str, NULL);
412 } else if (GTK_IS_TREE_SELECTION(obj)) {
413 view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
414 send_msg(GTK_BUILDABLE(view), tag, NULL);
415 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
416 (GtkTreeSelectionForeachFunc)send_tree_row_msg,
417 view);
418 } else
419 fprintf(stderr, "ignoring callback from %s\n", widget_name(obj));
423 * Callback like cb(), but returning true.
425 static bool
426 cb_true(GtkBuildable *obj, const char *tag)
428 cb(obj, tag);
429 return true;
433 * Store a line from stream s into buf, which should have been malloc'd
434 * to bufsize. Enlarge buf and bufsize if necessary.
436 static size_t
437 read_buf(FILE *s, char **buf, size_t *bufsize)
439 size_t i = 0;
440 int c;
441 fd_set rfds;
442 int ifd = fileno(s);
443 bool esc = false;
445 FD_ZERO(&rfds);
446 FD_SET(ifd, &rfds);
447 for (;;) {
448 select(ifd + 1, &rfds, NULL, NULL, NULL);
449 c = getc(s);
450 if (c == '\n' || feof(s))
451 break;
452 if (i >= *bufsize - 1)
453 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
454 OOM_ABORT;
455 if (esc) {
456 esc = false;
457 switch (c) {
458 case 'n': (*buf)[i++] = '\n'; break;
459 case 'r': (*buf)[i++] = '\r'; break;
460 default: (*buf)[i++] = c; break;
462 } else if (c == '\\')
463 esc = true;
464 else
465 (*buf)[i++] = c;
467 (*buf)[i] = '\0';
468 return i;
472 * Warning message
474 static void
475 ign_cmd(GType type, const char *msg)
477 const char *name, *pad = " ";
479 if (type == G_TYPE_INVALID) {
480 name = "";
481 pad = "";
483 else
484 name = g_type_name(type);
485 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
489 * Drawing on a GtkDrawingArea
491 enum cairo_fn {
492 RECTANGLE,
493 ARC,
494 ARC_NEGATIVE,
495 CURVE_TO,
496 REL_CURVE_TO,
497 LINE_TO,
498 REL_LINE_TO,
499 MOVE_TO,
500 REL_MOVE_TO,
501 CLOSE_PATH,
502 SET_SOURCE_RGBA,
503 SET_DASH,
504 SET_LINE_CAP,
505 SET_LINE_JOIN,
506 SET_LINE_WIDTH,
507 FILL,
508 FILL_PRESERVE,
509 STROKE,
510 STROKE_PRESERVE,
511 SHOW_TEXT,
512 SET_FONT_SIZE,
516 * One single element of a drawing
518 struct draw_op {
519 struct draw_op *next;
520 struct draw_op *prev;
521 unsigned int id;
522 enum cairo_fn op;
523 void *op_args;
526 struct rectangle_args {
527 double x;
528 double y;
529 double width;
530 double height;
533 struct arc_args {
534 double x;
535 double y;
536 double radius;
537 double angle1;
538 double angle2;
541 struct curve_to_args {
542 double x1;
543 double y1;
544 double x2;
545 double y2;
546 double x3;
547 double y3;
550 struct move_to_args {
551 double x;
552 double y;
555 struct set_source_rgba_args {
556 GdkRGBA color;
559 struct set_dash_args {
560 int num_dashes;
561 double dashes[];
564 struct set_line_cap_args {
565 cairo_line_cap_t line_cap;
568 struct set_line_join_args {
569 cairo_line_join_t line_join;
572 struct set_line_width_args {
573 double width;
576 struct show_text_args {
577 int len;
578 char text[];
581 struct set_font_size_args {
582 double size;
585 static void
586 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
588 switch (op) {
589 case RECTANGLE: {
590 struct rectangle_args *args = op_args;
592 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
593 break;
595 case ARC: {
596 struct arc_args *args = op_args;
598 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
599 break;
601 case ARC_NEGATIVE: {
602 struct arc_args *args = op_args;
604 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
605 break;
607 case CURVE_TO: {
608 struct curve_to_args *args = op_args;
610 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
611 break;
613 case REL_CURVE_TO: {
614 struct curve_to_args *args = op_args;
616 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
617 break;
619 case LINE_TO: {
620 struct move_to_args *args = op_args;
622 cairo_line_to(cr, args->x, args->y);
623 break;
625 case REL_LINE_TO: {
626 struct move_to_args *args = op_args;
628 cairo_rel_line_to(cr, args->x, args->y);
629 break;
631 case MOVE_TO: {
632 struct move_to_args *args = op_args;
634 cairo_move_to(cr, args->x, args->y);
635 break;
637 case REL_MOVE_TO: {
638 struct move_to_args *args = op_args;
640 cairo_rel_move_to(cr, args->x, args->y);
641 break;
643 case CLOSE_PATH:
644 cairo_close_path(cr);
645 break;
646 case SET_SOURCE_RGBA: {
647 struct set_source_rgba_args *args = op_args;
649 gdk_cairo_set_source_rgba(cr, &args->color);
650 break;
652 case SET_DASH: {
653 struct set_dash_args *args = op_args;
655 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
656 break;
658 case SET_LINE_CAP: {
659 struct set_line_cap_args *args = op_args;
661 cairo_set_line_cap(cr, args->line_cap);
662 break;
664 case SET_LINE_JOIN: {
665 struct set_line_join_args *args = op_args;
667 cairo_set_line_join(cr, args->line_join);
668 break;
670 case SET_LINE_WIDTH: {
671 struct set_line_width_args *args = op_args;
673 cairo_set_line_width(cr, args->width);
674 break;
676 case FILL:
677 cairo_fill(cr);
678 break;
679 case FILL_PRESERVE:
680 cairo_fill_preserve(cr);
681 break;
682 case STROKE:
683 cairo_stroke(cr);
684 break;
685 case STROKE_PRESERVE:
686 cairo_stroke_preserve(cr);
687 break;
688 case SHOW_TEXT: {
689 struct show_text_args *args = op_args;
691 cairo_show_text(cr, args->text);
692 break;
694 case SET_FONT_SIZE: {
695 struct set_font_size_args *args = op_args;
697 cairo_set_font_size(cr, args->size);
698 break;
700 default:
701 ABORT;
702 break;
706 static bool
707 set_draw_op(struct draw_op *op, const char *action, const char *data)
709 if (eql(action, "rectangle")) {
710 struct rectangle_args *args;
712 if ((args = malloc(sizeof(*args))) == NULL)
713 OOM_ABORT;
714 op->op = RECTANGLE;
715 op->op_args = args;
716 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->width, &args->height) != 5)
717 return false;
718 } else if (eql(action, "arc")) {
719 struct arc_args *args;
720 double deg1, deg2;
722 if ((args = malloc(sizeof(*args))) == NULL)
723 OOM_ABORT;
724 op->op = ARC;
725 op->op_args = args;
726 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
727 return false;
728 args->angle1 = deg1 * (M_PI / 180.);
729 args->angle2 = deg2 * (M_PI / 180.);
730 } else if (eql(action, "arc_negative")) {
731 struct arc_args *args;
732 double deg1, deg2;
734 if ((args = malloc(sizeof(*args))) == NULL)
735 OOM_ABORT;
736 op->op = ARC_NEGATIVE;
737 op->op_args = args;
738 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
739 return false;
740 args->angle1 = deg1 * (M_PI / 180.);
741 args->angle2 = deg2 * (M_PI / 180.);
742 } else if (eql(action, "curve_to")) {
743 struct curve_to_args *args;
745 if ((args = malloc(sizeof(*args))) == NULL)
746 OOM_ABORT;
747 op->op = CURVE_TO;
748 op->op_args = args;
749 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id, &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
750 return false;
751 } else if (eql(action, "rel_curve_to")) {
752 struct curve_to_args *args;
754 if ((args = malloc(sizeof(*args))) == NULL)
755 OOM_ABORT;
756 op->op = REL_CURVE_TO;
757 op->op_args = args;
758 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id, &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
759 return false;
760 } else if (eql(action, "line_to")) {
761 struct move_to_args *args;
763 if ((args = malloc(sizeof(*args))) == NULL)
764 OOM_ABORT;
765 op->op = LINE_TO;
766 op->op_args = args;
767 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
768 return false;
769 } else if (eql(action, "rel_line_to")) {
770 struct move_to_args *args;
772 if ((args = malloc(sizeof(*args))) == NULL)
773 OOM_ABORT;
774 op->op = REL_LINE_TO;
775 op->op_args = args;
776 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
777 return false;
778 } else if (eql(action, "move_to")) {
779 struct move_to_args *args;
781 if ((args = malloc(sizeof(*args))) == NULL)
782 OOM_ABORT;
783 op->op = MOVE_TO;
784 op->op_args = args;
785 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
786 return false;
787 } else if (eql(action, "rel_move_to")) {
788 struct move_to_args *args;
790 if ((args = malloc(sizeof(*args))) == NULL)
791 OOM_ABORT;
792 op->op = REL_MOVE_TO;
793 op->op_args = args;
794 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
795 return false;
796 } else if (eql(action, "close_path")) {
797 op->op = CLOSE_PATH;
798 if (sscanf(data, "%u", &op->id) != 1)
799 return false;
800 op->op_args = NULL;
801 } else if (eql(action, "set_source_rgba")) {
802 struct set_source_rgba_args *args;
803 int c_start;
805 if ((args = malloc(sizeof(*args))) == NULL)
806 OOM_ABORT;
807 op->op = SET_SOURCE_RGBA;
808 op->op_args = args;
809 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
810 return false;;
811 gdk_rgba_parse(&args->color, data + c_start);
812 } else if (eql(action, "set_dash")) {
813 struct set_dash_args *args;
814 int d_start, n, i;
815 char data1[strlen(data) + 1];
816 char *next, *end;
818 strcpy(data1, data);
819 if (sscanf(data1, "%u %n", &op->id, &d_start) < 1)
820 return false;
821 next = end = data1 + d_start;
822 n = -1;
823 do {
824 n++;
825 next = end;
826 strtod(next, &end);
827 } while (next != end);
828 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
829 OOM_ABORT;
830 op->op = SET_DASH;
831 op->op_args = args;
832 args->num_dashes = n;
833 for (i = 0, next = data1 + d_start; i < n; i++, next = end) {
834 args->dashes[i] = strtod(next, &end);
836 } else if (eql(action, "set_line_cap")) {
837 struct set_line_cap_args *args;
838 char str[6 + 1];
840 if ((args = malloc(sizeof(*args))) == NULL)
841 OOM_ABORT;
842 op->op = SET_LINE_CAP;
843 op->op_args = args;
844 if (sscanf(data, "%u %6s", &op->id, str) != 2)
845 return false;
846 if (eql(str, "butt"))
847 args->line_cap = CAIRO_LINE_CAP_BUTT;
848 else if (eql(str, "round"))
849 args->line_cap = CAIRO_LINE_CAP_ROUND;
850 else if (eql(str, "square"))
851 args->line_cap = CAIRO_LINE_CAP_SQUARE;
852 else
853 return false;
854 } else if (eql(action, "set_line_join")) {
855 struct set_line_join_args *args;
856 char str[5 + 1];
858 if ((args = malloc(sizeof(*args))) == NULL)
859 OOM_ABORT;
860 op->op = SET_LINE_JOIN;
861 op->op_args = args;
862 if (sscanf(data, "%u %5s", &op->id, str) != 2)
863 return false;
864 if (eql(str, "miter"))
865 args->line_join = CAIRO_LINE_JOIN_MITER;
866 else if (eql(str, "round"))
867 args->line_join = CAIRO_LINE_JOIN_ROUND;
868 else if (eql(str, "bevel"))
869 args->line_join = CAIRO_LINE_JOIN_BEVEL;
870 else
871 return false;
872 } else if (eql(action, "set_line_width")) {
873 struct set_line_width_args *args;
875 if ((args = malloc(sizeof(*args))) == NULL)
876 OOM_ABORT;
877 op->op = SET_LINE_WIDTH;
878 op->op_args = args;
879 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
880 return false;
881 } else if (eql(action, "fill")) {
882 op->op = FILL;
883 if (sscanf(data, "%u", &op->id) != 1)
884 return false;
885 op->op_args = NULL;
886 } else if (eql(action, "fill_preserve")) {
887 op->op = FILL_PRESERVE;
888 if (sscanf(data, "%u", &op->id) != 1)
889 return false;
890 op->op_args = NULL;
891 } else if (eql(action, "stroke")) {
892 op->op = STROKE;
893 if (sscanf(data, "%u", &op->id) != 1)
894 return false;
895 op->op_args = NULL;
896 } else if (eql(action, "stroke_preserve")) {
897 op->op = STROKE_PRESERVE;
898 if (sscanf(data, "%u", &op->id) != 1)
899 return false;
900 op->op_args = NULL;
901 } else if (eql(action, "show_text")) {
902 struct show_text_args *args;
903 int start, len;
905 if (sscanf(data, "%u %n", &op->id, &start) < 1)
906 return false;
907 len = strlen(data + start) + 1;
908 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
909 OOM_ABORT;
910 op->op = SHOW_TEXT;
911 op->op_args = args;
912 args->len = len; /* not used */
913 strncpy(args->text, (data + start), len);
914 } else if (eql(action, "set_font_size")) {
915 struct set_font_size_args *args;
917 if ((args = malloc(sizeof(*args))) == NULL)
918 OOM_ABORT;
919 op->op = SET_FONT_SIZE;
920 op->op_args = args;
921 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
922 return false;
923 } else
924 return false;
925 return true;
929 * Add another element to widget's "draw_ops" list
931 static bool
932 ins_draw_op(GObject *widget, const char *action, const char *data)
934 struct draw_op *op, *draw_ops, *last_op;
936 if ((op = malloc(sizeof(*op))) == NULL)
937 OOM_ABORT;
938 op->op_args = NULL;
939 if (!set_draw_op(op, action, data)) {
940 free(op->op_args);
941 free(op);
942 return false;
944 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL) {
945 g_object_set_data(widget, "draw_ops", op);
946 insque(op, NULL);
947 } else {
948 for (last_op = draw_ops; last_op->next != NULL; last_op = last_op->next);
949 insque(op, last_op);
951 return true;
955 * Remove all elements with the given id from widget's "draw_ops" list
957 static bool
958 rem_draw_op(GObject *widget, const char *data)
960 struct draw_op *op, *next_op;
961 unsigned int id;
963 if (sscanf(data, "%u", &id) != 1)
964 return false;
965 op = g_object_get_data(widget, "draw_ops");
966 while (op != NULL) {
967 next_op = op->next;
968 if (op->id == id) {
969 if (op->prev == NULL)
970 g_object_set_data(widget, "draw_ops", next_op);
971 remque(op);
972 free(op->op_args);
973 free(op);
975 op = next_op;
977 return true;
981 * Callback that draws on a GtkDrawingArea
983 static gboolean
984 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
986 struct draw_op *op;
988 (void)data;
989 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
990 op != NULL;
991 op = op->next)
992 draw(cr, op->op, op->op_args);
993 return FALSE;
997 * Change the style of the widget passed
999 static void
1000 update_widget_style(GObject *obj, const char *name,
1001 const char *data, const char *whole_msg, GType type)
1003 GtkStyleContext *context;
1004 GtkStyleProvider *style_provider;
1005 char *style_decl;
1006 const char *prefix = "* {", *suffix = "}";
1007 size_t sz;
1009 (void)name;
1010 (void)whole_msg;
1011 (void)type;
1012 style_provider = g_object_get_data(obj, "style_provider");
1013 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
1014 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
1015 gtk_style_context_remove_provider(context, style_provider);
1016 if ((style_decl = malloc(sz)) == NULL)
1017 OOM_ABORT;
1018 strcpy(style_decl, prefix);
1019 strcat(style_decl, data);
1020 strcat(style_decl, suffix);
1021 gtk_style_context_add_provider(context, style_provider,
1022 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1023 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
1024 style_decl, -1, NULL);
1025 free(style_decl);
1029 * Update various kinds of widgets according to the respective action
1030 * parameter
1032 static void
1033 update_button(GObject *obj, const char *action,
1034 const char *data, const char *whole_msg, GType type)
1036 if (eql(action, "set_label"))
1037 gtk_button_set_label(GTK_BUTTON(obj), data);
1038 else
1039 ign_cmd(type, whole_msg);
1042 static void
1043 update_calendar(GObject *obj, const char *action,
1044 const char *data, const char *whole_msg, GType type)
1046 GtkCalendar *calendar = GTK_CALENDAR(obj);
1047 int year = 0, month = 0, day = 0;
1049 if (eql(action, "select_date")) {
1050 sscanf(data, "%d-%d-%d", &year, &month, &day);
1051 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1052 gtk_calendar_select_month(calendar, --month, year);
1053 gtk_calendar_select_day(calendar, day);
1054 } else
1055 ign_cmd(type, whole_msg);
1056 } else if (eql(action, "mark_day")) {
1057 day = strtol(data, NULL, 10);
1058 if (day > 0 && day <= 31)
1059 gtk_calendar_mark_day(calendar, day);
1060 else
1061 ign_cmd(type, whole_msg);
1062 } else if (eql(action, "clear_marks"))
1063 gtk_calendar_clear_marks(calendar);
1064 else
1065 ign_cmd(type, whole_msg);
1068 static void
1069 update_color_button(GObject *obj, const char *action,
1070 const char *data, const char *whole_msg, GType type)
1072 GdkRGBA color;
1074 if (eql(action, "set_color")) {
1075 gdk_rgba_parse(&color, data);
1076 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1077 } else
1078 ign_cmd(type, whole_msg);
1081 static void
1082 update_combo_box_text(GObject *obj, const char *action,
1083 const char *data, const char *whole_msg, GType type)
1085 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1086 char data1[strlen(data) + 1];
1088 strcpy(data1, data);
1089 if (eql(action, "prepend_text"))
1090 gtk_combo_box_text_prepend_text(combobox, data1);
1091 else if (eql(action, "append_text"))
1092 gtk_combo_box_text_append_text(combobox, data1);
1093 else if (eql(action, "remove"))
1094 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1095 else if (eql(action, "insert_text")) {
1096 char *position = strtok(data1, WHITESPACE);
1097 char *text = strtok(NULL, WHITESPACE);
1098 gtk_combo_box_text_insert_text(combobox, strtol(position, NULL, 10), text);
1099 } else
1100 ign_cmd(type, whole_msg);
1103 static void
1104 update_frame(GObject *obj, const char *action,
1105 const char *data, const char *whole_msg, GType type)
1107 if (eql(action, "set_label"))
1108 gtk_frame_set_label(GTK_FRAME(obj), data);
1109 else
1110 ign_cmd(type, whole_msg);
1113 static void
1114 update_scrolled_window(GObject *obj, const char *action,
1115 const char *data, const char *whole_msg, GType type)
1117 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
1118 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
1119 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
1120 double d0, d1;
1122 if (eql(action, "hscroll") && sscanf(data, "%lf", &d0) == 1)
1123 gtk_adjustment_set_value(hadj, d0);
1124 else if (eql(action, "vscroll") && sscanf(data, "%lf", &d0) == 1)
1125 gtk_adjustment_set_value(vadj, d0);
1126 else if (eql(action, "hscroll_to_range") &&
1127 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1128 gtk_adjustment_clamp_page(hadj, d0, d1);
1129 else if (eql(action, "vscroll_to_range") &&
1130 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1131 gtk_adjustment_clamp_page(vadj, d0, d1);
1132 else
1133 ign_cmd(type, whole_msg);
1136 static void
1137 update_drawing_area(GObject *obj, const char *action,
1138 const char *data, const char *whole_msg, GType type)
1140 if (eql(action, "remove")) {
1141 if (!rem_draw_op(obj, data))
1142 ign_cmd(type, whole_msg);
1143 } else if (eql(action, "refresh")) {
1144 gint width = gtk_widget_get_allocated_width(GTK_WIDGET(obj));
1145 gint height = gtk_widget_get_allocated_height(GTK_WIDGET(obj));
1147 gtk_widget_queue_draw_area(GTK_WIDGET(obj), 0, 0, width, height);
1148 } else if (ins_draw_op(obj, action, data));
1149 else
1150 ign_cmd(type, whole_msg);
1153 static void
1154 update_entry(GObject *obj, const char *action,
1155 const char *data, const char *whole_msg, GType type)
1157 GtkEntry *entry = GTK_ENTRY(obj);
1159 if (eql(action, "set_text"))
1160 gtk_entry_set_text(entry, data);
1161 else if (eql(action, "set_placeholder_text"))
1162 gtk_entry_set_placeholder_text(entry, data);
1163 else
1164 ign_cmd(type, whole_msg);
1167 static void
1168 update_label(GObject *obj, const char *action,
1169 const char *data, const char *whole_msg, GType type)
1171 if (eql(action, "set_text"))
1172 gtk_label_set_text(GTK_LABEL(obj), data);
1173 else
1174 ign_cmd(type, whole_msg);
1177 static void
1178 update_expander(GObject *obj, const char *action,
1179 const char *data, const char *whole_msg, GType type)
1181 GtkExpander *expander = GTK_EXPANDER(obj);
1183 if (eql(action, "set_expanded"))
1184 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1185 else if (eql(action, "set_label"))
1186 gtk_expander_set_label(expander, data);
1187 else
1188 ign_cmd(type, whole_msg);
1191 static void
1192 update_file_chooser_button(GObject *obj, const char *action,
1193 const char *data, const char *whole_msg, GType type)
1195 if (eql(action, "set_filename"))
1196 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1197 else
1198 ign_cmd(type, whole_msg);
1201 static void
1202 update_file_chooser_dialog(GObject *obj, const char *action,
1203 const char *data, const char *whole_msg, GType type)
1205 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1207 if (eql(action, "set_filename"))
1208 gtk_file_chooser_set_filename(chooser, data);
1209 else if (eql(action, "set_current_name"))
1210 gtk_file_chooser_set_current_name(chooser, data);
1211 else
1212 ign_cmd(type, whole_msg);
1215 static void
1216 update_font_button(GObject *obj, const char *action,
1217 const char *data, const char *whole_msg, GType type)
1219 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1221 if (eql(action, "set_font_name"))
1222 gtk_font_button_set_font_name(font_button, data);
1223 else
1224 ign_cmd(type, whole_msg);
1227 static void
1228 update_print_dialog(GObject *obj, const char *action,
1229 const char *data, const char *whole_msg, GType type)
1231 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1232 gint response_id;
1233 GtkPrinter *printer;
1234 GtkPrintSettings *settings;
1235 GtkPageSetup *page_setup;
1236 GtkPrintJob *job;
1238 if (eql(action, "print")) {
1239 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1240 switch (response_id) {
1241 case GTK_RESPONSE_OK:
1242 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1243 settings = gtk_print_unix_dialog_get_settings(dialog);
1244 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1245 job = gtk_print_job_new(data, printer, settings, page_setup);
1246 if (gtk_print_job_set_source_file(job, data, NULL))
1247 gtk_print_job_send(job, NULL, NULL, NULL);
1248 else
1249 ign_cmd(type, whole_msg);
1250 g_clear_object(&settings);
1251 g_clear_object(&job);
1252 break;
1253 case GTK_RESPONSE_CANCEL:
1254 case GTK_RESPONSE_DELETE_EVENT:
1255 break;
1256 default:
1257 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1258 widget_name(GTK_WIDGET(dialog)), response_id);
1259 break;
1261 gtk_widget_hide(GTK_WIDGET(dialog));
1262 } else
1263 ign_cmd(type, whole_msg);
1266 static void
1267 update_image(GObject *obj, const char *action,
1268 const char *data, const char *whole_msg, GType type)
1270 GtkImage *image = GTK_IMAGE(obj);
1271 GtkIconSize size;
1273 gtk_image_get_icon_name(image, NULL, &size);
1274 if (eql(action, "set_from_file"))
1275 gtk_image_set_from_file(image, data);
1276 else if (eql(action, "set_from_icon_name"))
1277 gtk_image_set_from_icon_name(image, data, size);
1278 else
1279 ign_cmd(type, whole_msg);
1282 static void
1283 update_notebook(GObject *obj, const char *action,
1284 const char *data, const char *whole_msg, GType type)
1286 if (eql(action, "set_current_page"))
1287 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), strtol(data, NULL, 10));
1288 else
1289 ign_cmd(type, whole_msg);
1292 static void
1293 update_progress_bar(GObject *obj, const char *action,
1294 const char *data, const char *whole_msg, GType type)
1296 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1298 if (eql(action, "set_text"))
1299 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1300 else if (eql(action, "set_fraction"))
1301 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1302 else
1303 ign_cmd(type, whole_msg);
1306 static void
1307 update_scale(GObject *obj, const char *action,
1308 const char *data, const char *whole_msg, GType type)
1310 if (eql(action, "set_value"))
1311 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
1312 else
1313 ign_cmd(type, whole_msg);
1316 static void
1317 update_spinner(GObject *obj, const char *action,
1318 const char *data, const char *whole_msg, GType type)
1320 GtkSpinner *spinner = GTK_SPINNER(obj);
1322 (void)data;
1323 if (eql(action, "start"))
1324 gtk_spinner_start(spinner);
1325 else if (eql(action, "stop"))
1326 gtk_spinner_stop(spinner);
1327 else
1328 ign_cmd(type, whole_msg);
1331 static void
1332 update_statusbar(GObject *obj, const char *action,
1333 const char *data, const char *whole_msg, GType type)
1335 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
1336 unsigned int id;
1337 size_t id_len;
1339 if (eql(action, "push"))
1340 gtk_statusbar_push(statusbar, 0, data);
1341 else if (eql(action, "push_id") &&
1342 sscanf(data, "%u%zn", &id, &id_len) == 1)
1343 gtk_statusbar_push(statusbar, id, data + id_len +
1344 (size_t) (id_len < strlen(data) ? 1 : 0));
1345 else if (eql(action, "pop"))
1346 gtk_statusbar_pop(statusbar, 0);
1347 else if (eql(action, "pop_id") &&
1348 sscanf(data, "%u", &id) == 1)
1349 gtk_statusbar_pop(statusbar, id);
1350 else if (eql(action, "remove_all"))
1351 gtk_statusbar_remove_all(statusbar, 0);
1352 else
1353 ign_cmd(type, whole_msg);
1356 static void
1357 update_switch(GObject *obj, const char *action,
1358 const char *data, const char *whole_msg, GType type)
1360 if (eql(action, "set_active"))
1361 gtk_switch_set_active(GTK_SWITCH(obj), strtol(data, NULL, 10));
1362 else
1363 ign_cmd(type, whole_msg);
1366 static void
1367 update_text_view(GObject *obj, const char *action,
1368 const char *data, const char *whole_msg, GType type)
1370 GtkTextView *view = GTK_TEXT_VIEW(obj);
1371 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1372 GtkTextIter a, b;
1374 if (eql(action, "set_text"))
1375 gtk_text_buffer_set_text(textbuf, data, -1);
1376 else if (eql(action, "delete")) {
1377 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1378 gtk_text_buffer_delete(textbuf, &a, &b);
1379 } else if (eql(action, "insert_at_cursor"))
1380 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1381 else if (eql(action, "place_cursor")) {
1382 if (eql(data, "end"))
1383 gtk_text_buffer_get_end_iter(textbuf, &a);
1384 else /* numeric offset */
1385 gtk_text_buffer_get_iter_at_offset(textbuf, &a,
1386 strtol(data, NULL, 10));
1387 gtk_text_buffer_place_cursor(textbuf, &a);
1388 } else if (eql(action, "place_cursor_at_line")) {
1389 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1390 gtk_text_buffer_place_cursor(textbuf, &a);
1391 } else if (eql(action, "scroll_to_cursor"))
1392 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
1393 0., 0, 0., 0.);
1394 else if (eql(action, "save") && data != NULL &&
1395 (save = fopen(data, "w")) != NULL) {
1396 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1397 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
1398 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
1399 fclose(save);
1400 } else
1401 ign_cmd(type, whole_msg);
1404 static void
1405 update_toggle_button(GObject *obj, const char *action,
1406 const char *data, const char *whole_msg, GType type)
1408 if (eql(action, "set_label"))
1409 gtk_button_set_label(GTK_BUTTON(obj), data);
1410 else if (eql(action, "set_active"))
1411 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
1412 else
1413 ign_cmd(type, whole_msg);
1417 * Check if s is a valid string representation of a GtkTreePath
1419 static bool
1420 is_path_string(char *s)
1422 return s != NULL &&
1423 strlen(s) == strspn(s, ":0123456789") &&
1424 strstr(s, "::") == NULL &&
1425 strcspn(s, ":") > 0;
1428 static void
1429 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
1430 GtkTreeIter *parent, GtkTreeIter *sibling)
1432 if (GTK_IS_TREE_STORE(model))
1433 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
1434 iter, parent, sibling);
1435 else if (GTK_IS_LIST_STORE(model))
1436 gtk_list_store_insert_before(GTK_LIST_STORE(model),
1437 iter, sibling);
1438 else
1439 ABORT;
1442 static void
1443 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
1444 GtkTreeIter *parent, GtkTreeIter *sibling)
1446 if (GTK_IS_TREE_STORE(model))
1447 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
1448 iter, parent, sibling);
1449 else if (GTK_IS_LIST_STORE(model))
1450 gtk_list_store_insert_after(GTK_LIST_STORE(model),
1451 iter, sibling);
1452 else
1453 ABORT;
1456 static void
1457 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
1458 GtkTreeIter *position)
1460 if (GTK_IS_TREE_STORE(model))
1461 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
1462 else if (GTK_IS_LIST_STORE(model))
1463 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
1464 else
1465 ABORT;
1468 static void
1469 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
1471 if (GTK_IS_TREE_STORE(model))
1472 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
1473 else if (GTK_IS_LIST_STORE(model))
1474 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
1475 else
1476 ABORT;
1479 static void
1480 tree_model_clear(GtkTreeModel *model)
1482 if (GTK_IS_TREE_STORE(model))
1483 gtk_tree_store_clear(GTK_TREE_STORE(model));
1484 else if (GTK_IS_LIST_STORE(model))
1485 gtk_list_store_clear(GTK_LIST_STORE(model));
1486 else
1487 ABORT;
1490 static void
1491 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
1493 va_list ap;
1495 va_start(ap, iter);
1496 if (GTK_IS_TREE_STORE(model))
1497 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
1498 else if (GTK_IS_LIST_STORE(model))
1499 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
1500 else
1501 ABORT;
1502 va_end(ap);
1506 * Create an empty row at path if it doesn't yet exist. Create older
1507 * siblings and parents as necessary.
1509 static void
1510 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
1512 GtkTreePath *path_1; /* path's predecessor */
1513 GtkTreeIter iter_1; /* iter's predecessor */
1515 if (gtk_tree_model_get_iter(model, iter, path))
1516 return;
1517 path_1 = gtk_tree_path_copy(path);
1518 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
1519 create_subtree(model, path_1, iter);
1520 iter_1 = *iter;
1521 tree_model_insert_after(model, iter, NULL, &iter_1);
1522 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
1523 create_subtree(model, path_1, iter);
1524 if (gtk_tree_path_get_depth(path_1) == 0)
1525 /* first toplevel row */
1526 tree_model_insert_after(model, iter, NULL, NULL);
1527 else { /* first row in a lower level */
1528 iter_1 = *iter;
1529 tree_model_insert_after(model, iter, &iter_1, NULL);
1531 } /* neither prev nor up mean we're at the root of an empty tree */
1532 gtk_tree_path_free(path_1);
1535 static bool
1536 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
1537 const char *path_s, int col, const char *new_text)
1539 GType col_type = gtk_tree_model_get_column_type(model, col);
1540 long long int n;
1541 double d;
1542 GtkTreePath *path;
1543 char *endptr;
1544 bool ok = false;
1546 path = gtk_tree_path_new_from_string(path_s);
1547 create_subtree(model, path, iter);
1548 gtk_tree_path_free(path);
1549 switch (col_type) {
1550 case G_TYPE_BOOLEAN:
1551 case G_TYPE_INT:
1552 case G_TYPE_LONG:
1553 case G_TYPE_INT64:
1554 case G_TYPE_UINT:
1555 case G_TYPE_ULONG:
1556 case G_TYPE_UINT64:
1557 errno = 0;
1558 endptr = NULL;
1559 n = strtoll(new_text, &endptr, 10);
1560 if (!errno && endptr != new_text) {
1561 tree_model_set(model, iter, col, n, -1);
1562 ok = true;
1564 break;
1565 case G_TYPE_FLOAT:
1566 case G_TYPE_DOUBLE:
1567 errno = 0;
1568 endptr = NULL;
1569 d = strtod(new_text, &endptr);
1570 if (!errno && endptr != new_text) {
1571 tree_model_set(model, iter, col, d, -1);
1572 ok = true;
1574 break;
1575 case G_TYPE_STRING:
1576 tree_model_set(model, iter, col, new_text, -1);
1577 ok = true;
1578 break;
1579 default:
1580 fprintf(stderr, "column %d: %s not implemented\n",
1581 col, g_type_name(col_type));
1582 ok = true;
1583 break;
1585 return ok;
1588 static void
1589 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
1591 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
1592 /* is just fine and this function need not exist. */
1593 if (path == NULL)
1594 path = gtk_tree_path_new();
1595 gtk_tree_view_set_cursor(view, path, col, false);
1598 static void
1599 update_tree_view(GObject *obj, const char *action,
1600 const char *data, const char *whole_msg, GType type)
1602 GtkTreeView *view = GTK_TREE_VIEW(obj);
1603 GtkTreeModel *model = gtk_tree_view_get_model(view);
1604 GtkTreeIter iter0, iter1;
1605 GtkTreePath *path = NULL;
1606 bool iter0_valid, iter1_valid;
1607 char *tokens, *arg0, *arg1, *arg2;
1608 int col = -1; /* invalid column number */
1610 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
1612 fprintf(stderr, "missing model/");
1613 ign_cmd(type, whole_msg);
1614 return;
1616 if ((tokens = malloc(strlen(data) + 1)) == NULL)
1617 OOM_ABORT;
1618 strcpy(tokens, data);
1619 arg0 = strtok(tokens, WHITESPACE);
1620 arg1 = strtok(NULL, WHITESPACE);
1621 arg2 = strtok(NULL, "");
1622 iter0_valid = is_path_string(arg0) &&
1623 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
1624 iter1_valid = is_path_string(arg1) &&
1625 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
1626 if (is_path_string(arg1))
1627 col = strtol(arg1, NULL, 10);
1628 if (eql(action, "set")
1629 && col > -1 && col < gtk_tree_model_get_n_columns(model) &&
1630 is_path_string(arg0)) {
1631 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
1632 ign_cmd(type, whole_msg);
1633 } else if (eql(action, "scroll") && iter0_valid && iter1_valid) {
1634 path = gtk_tree_path_new_from_string(arg0);
1635 gtk_tree_view_scroll_to_cell (view,
1636 path,
1637 gtk_tree_view_get_column(view, col),
1638 0, 0., 0.);
1639 } else if (eql(action, "expand") && iter0_valid) {
1640 path = gtk_tree_path_new_from_string(arg0);
1641 gtk_tree_view_expand_row(view, path, false);
1642 } else if (eql(action, "expand_all") && iter0_valid) {
1643 path = gtk_tree_path_new_from_string(arg0);
1644 gtk_tree_view_expand_row(view, path, true);
1645 } else if (eql(action, "expand_all") && arg0 == NULL)
1646 gtk_tree_view_expand_all(view);
1647 else if (eql(action, "collapse") && iter0_valid) {
1648 path = gtk_tree_path_new_from_string(arg0);
1649 gtk_tree_view_collapse_row(view, path);
1650 } else if (eql(action, "collapse") && arg0 == NULL)
1651 gtk_tree_view_collapse_all(view);
1652 else if (eql(action, "set_cursor") && iter0_valid) {
1653 path = gtk_tree_path_new_from_string(arg0);
1654 tree_view_set_cursor(view, path, NULL);
1655 } else if (eql(action, "set_cursor") && arg0 == NULL) {
1656 tree_view_set_cursor(view, NULL, NULL);
1657 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1658 } else if (eql(action, "insert_row") && eql(arg0, "end"))
1659 tree_model_insert_before(model, &iter1, NULL, NULL);
1660 else if (eql(action, "insert_row") && iter0_valid && eql(arg1, "as_child"))
1661 tree_model_insert_after(model, &iter1, &iter0, NULL);
1662 else if (eql(action, "insert_row") && iter0_valid)
1663 tree_model_insert_before(model, &iter1, NULL, &iter0);
1664 else if (eql(action, "move_row") && iter0_valid && eql(arg1, "end"))
1665 tree_model_move_before(model, &iter0, NULL);
1666 else if (eql(action, "move_row") && iter0_valid && iter1_valid)
1667 tree_model_move_before(model, &iter0, &iter1);
1668 else if (eql(action, "remove_row") && iter0_valid)
1669 tree_model_remove(model, &iter0);
1670 else if (eql(action, "clear") && arg0 == NULL) {
1671 tree_view_set_cursor(view, NULL, NULL);
1672 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1673 tree_model_clear(model);
1674 } else if (eql(action, "save") && arg0 != NULL &&
1675 (save = fopen(arg0, "w")) != NULL) {
1676 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc)save_tree_row_msg, view);
1677 fclose(save);
1678 } else
1679 ign_cmd(type, whole_msg);
1680 free(tokens);
1681 gtk_tree_path_free(path);
1684 static void
1685 update_socket(GObject *obj, const char *action,
1686 const char *data, const char *whole_msg, GType type)
1688 GtkSocket *socket = GTK_SOCKET(obj);
1689 Window id;
1690 char str[BUFLEN];
1692 (void)data;
1693 if (eql(action, "id")) {
1694 id = gtk_socket_get_id(socket);
1695 snprintf(str, BUFLEN, "%lu", id);
1696 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1697 } else
1698 ign_cmd(type, whole_msg);
1701 static void
1702 update_window(GObject *obj, const char *action,
1703 const char *data, const char *whole_msg, GType type)
1705 GtkWindow *window = GTK_WINDOW(obj);
1706 int x, y;
1708 if (eql(action, "set_title"))
1709 gtk_window_set_title(window, data);
1710 else if (eql(action, "fullscreen"))
1711 gtk_window_fullscreen(window);
1712 else if (eql(action, "unfullscreen"))
1713 gtk_window_unfullscreen(window);
1714 else if (eql(action, "resize")) {
1715 if (sscanf(data, "%d %d", &x, &y) != 2)
1716 gtk_window_get_default_size(window, &x, &y);
1717 gtk_window_resize(window, x, y);
1718 } else if (eql(action, "move")) {
1719 if (sscanf(data, "%d %d", &x, &y) == 2)
1720 gtk_window_move(window, x, y);
1721 else
1722 ign_cmd(type, whole_msg);
1723 } else
1724 ign_cmd(type, whole_msg);
1727 static void
1728 update_sensitivity(GObject *obj, const char *action,
1729 const char *data, const char *whole_msg, GType type)
1731 (void)action;
1732 (void)whole_msg;
1733 (void)type;
1734 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1737 static void
1738 update_visibility(GObject *obj, const char *action,
1739 const char *data, const char *whole_msg, GType type)
1741 (void)action;
1742 (void)whole_msg;
1743 (void)type;
1744 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
1747 static void
1748 update_focus(GObject *obj, const char *action,
1749 const char *data, const char *whole_msg, GType type)
1751 (void)action;
1752 (void)data;
1753 if (gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1754 gtk_widget_grab_focus(GTK_WIDGET(obj));
1755 else
1756 ign_cmd(type, whole_msg);
1759 static void
1760 update_size_request(GObject *obj, const char *action,
1761 const char *data, const char *whole_msg, GType type)
1763 int x, y;
1765 (void)action;
1766 (void)whole_msg;
1767 (void)type;
1768 if (sscanf(data, "%d %d", &x, &y) == 2)
1769 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
1770 else
1771 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
1774 static void
1775 update_tooltip_text(GObject *obj, const char *action,
1776 const char *data, const char *whole_msg, GType type)
1778 (void)action;
1779 (void)whole_msg;
1780 (void)type;
1781 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
1784 static void
1785 fake_ui_activity(GObject *obj, const char *action,
1786 const char *data, const char *whole_msg, GType type)
1788 (void)action;
1789 (void)data;
1790 if (!GTK_IS_WIDGET(obj))
1791 ign_cmd(type, whole_msg);
1792 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
1793 cb(GTK_BUILDABLE(obj), "text");
1794 else if (GTK_IS_SCALE(obj))
1795 cb(GTK_BUILDABLE(obj), "value");
1796 else if (GTK_IS_CALENDAR(obj))
1797 cb(GTK_BUILDABLE(obj), "clicked");
1798 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
1799 cb(GTK_BUILDABLE(obj), "file");
1800 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
1801 ign_cmd(type, whole_msg);
1804 static void
1805 main_quit(GObject *obj, const char *action,
1806 const char *data, const char *whole_msg, GType type)
1808 (void)obj;
1809 (void)action;
1810 (void)data;
1811 (void)whole_msg;
1812 (void)type;
1814 gtk_main_quit();
1817 static void
1818 complain(GObject *obj, const char *action,
1819 const char *data, const char *whole_msg, GType type)
1821 (void)obj;
1822 (void)action;
1823 (void)data;
1825 ign_cmd(type, whole_msg);
1829 * Parse command pointed to by ud, and act on ui accordingly
1831 static void
1832 update_ui(struct ui_data *ud)
1834 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
1837 static void
1838 free_at(void **mem)
1840 free(*mem);
1844 * Keep track of loading files to avoid recursive loading of the same
1845 * file. If filename = NULL, forget the most recently remembered file.
1847 static bool
1848 remember_loading_file(char *filename)
1850 static char *filenames[BUFLEN];
1851 static size_t latest = 0;
1852 size_t i;
1854 if (filename == NULL) { /* pop */
1855 if (latest < 1)
1856 ABORT;
1857 latest--;
1858 return false;
1859 } else { /* push */
1860 for (i = 1; i <= latest; i++)
1861 if (eql(filename, filenames[i]))
1862 return false;
1863 if (latest > BUFLEN -2)
1864 return false;
1865 filenames[++latest] = filename;
1866 return true;
1871 * Parse command pointed to by ud, and act on ui accordingly; post
1872 * semaphore ud.msg_digested if done. Runs once per command inside
1873 * gtk_main_loop()
1875 static gboolean
1876 update_ui_in(struct ui_data *ud)
1878 update_ui(ud);
1879 sem_post(&ud->msg_digested);
1880 return G_SOURCE_REMOVE;
1884 * Read lines from stream cmd and perform appropriate actions on the
1885 * GUI
1887 static void *
1888 digest_msg(FILE *cmd)
1890 struct ui_data ud;
1891 FILE *load; /* restoring user data */
1892 char *name;
1894 sem_init(&ud.msg_digested, 0, 0);
1895 for (;;) {
1896 char first_char = '\0';
1897 size_t msg_size = 32;
1898 int name_start = 0, name_end = 0;
1899 int action_start = 0, action_end = 0;
1900 int data_start;
1902 ud.type = G_TYPE_INVALID;
1903 if (feof(cmd))
1904 break;
1905 if ((ud.msg = malloc(msg_size)) == NULL)
1906 OOM_ABORT;
1907 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg);
1908 pthread_testcancel();
1909 read_buf(cmd, &ud.msg, &msg_size);
1910 data_start = strlen(ud.msg);
1911 if ((ud.msg_tokens = malloc(strlen(ud.msg) + 1)) == NULL)
1912 OOM_ABORT;
1913 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg_tokens);
1914 strcpy(ud.msg_tokens, ud.msg);
1915 sscanf(ud.msg, " %c", &first_char);
1916 if (strlen(ud.msg) == 0 || first_char == '#') /* comment */
1917 goto cleanup;
1918 sscanf(ud.msg_tokens,
1919 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
1920 &name_start, &name_end, &action_start, &action_end, &data_start);
1921 ud.msg_tokens[name_end] = ud.msg_tokens[action_end] = '\0';
1922 name = ud.msg_tokens + name_start;
1923 ud.action = ud.msg_tokens + action_start;
1924 if (eql(ud.action, "main_quit")) {
1925 ud.fn = main_quit;
1926 goto exec;
1928 ud.data = ud.msg_tokens + data_start;
1929 if (eql(ud.action, "load") && strlen(ud.data) > 0 &&
1930 (load = fopen(ud.data, "r")) != NULL &&
1931 remember_loading_file(ud.data)) {
1932 digest_msg(load);
1933 fclose(load);
1934 remember_loading_file(NULL);
1935 goto cleanup;
1937 if ((ud.obj = (gtk_builder_get_object(builder, name))) == NULL) {
1938 ud.fn = complain;
1939 goto exec;
1941 ud.type = G_TYPE_FROM_INSTANCE(ud.obj);
1942 if (eql(ud.action, "force"))
1943 ud.fn = fake_ui_activity;
1944 else if (eql(ud.action, "set_sensitive"))
1945 ud.fn = update_sensitivity;
1946 else if (eql(ud.action, "set_visible"))
1947 ud.fn = update_visibility;
1948 else if (eql(ud.action, "set_size_request"))
1949 ud.fn = update_size_request;
1950 else if (eql(ud.action, "set_tooltip_text"))
1951 ud.fn = update_tooltip_text;
1952 else if (eql(ud.action, "grab_focus"))
1953 ud.fn = update_focus;
1954 else if (eql(ud.action, "style")) {
1955 ud.action = name;
1956 ud.fn = update_widget_style;
1957 } else if (ud.type == GTK_TYPE_TREE_VIEW)
1958 ud.fn = update_tree_view;
1959 else if (ud.type == GTK_TYPE_DRAWING_AREA)
1960 ud.fn = update_drawing_area;
1961 else if (ud.type == GTK_TYPE_LABEL)
1962 ud.fn = update_label;
1963 else if (ud.type == GTK_TYPE_IMAGE)
1964 ud.fn = update_image;
1965 else if (ud.type == GTK_TYPE_TEXT_VIEW)
1966 ud.fn = update_text_view;
1967 else if (ud.type == GTK_TYPE_NOTEBOOK)
1968 ud.fn = update_notebook;
1969 else if (ud.type == GTK_TYPE_EXPANDER)
1970 ud.fn = update_expander;
1971 else if (ud.type == GTK_TYPE_FRAME)
1972 ud.fn = update_frame;
1973 else if (ud.type == GTK_TYPE_SCROLLED_WINDOW)
1974 ud.fn = update_scrolled_window;
1975 else if (ud.type == GTK_TYPE_BUTTON)
1976 ud.fn = update_button;
1977 else if (ud.type == GTK_TYPE_FILE_CHOOSER_DIALOG)
1978 ud.fn = update_file_chooser_dialog;
1979 else if (ud.type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1980 ud.fn = update_file_chooser_button;
1981 else if (ud.type == GTK_TYPE_COLOR_BUTTON)
1982 ud.fn = update_color_button;
1983 else if (ud.type == GTK_TYPE_FONT_BUTTON)
1984 ud.fn = update_font_button;
1985 else if (ud.type == GTK_TYPE_PRINT_UNIX_DIALOG)
1986 ud.fn = update_print_dialog;
1987 else if (ud.type == GTK_TYPE_SWITCH)
1988 ud.fn = update_switch;
1989 else if (ud.type == GTK_TYPE_TOGGLE_BUTTON || ud.type == GTK_TYPE_RADIO_BUTTON || ud.type == GTK_TYPE_CHECK_BUTTON)
1990 ud.fn = update_toggle_button;
1991 else if (ud.type == GTK_TYPE_SPIN_BUTTON || ud.type == GTK_TYPE_ENTRY)
1992 ud.fn = update_entry;
1993 else if (ud.type == GTK_TYPE_SCALE)
1994 ud.fn = update_scale;
1995 else if (ud.type == GTK_TYPE_PROGRESS_BAR)
1996 ud.fn = update_progress_bar;
1997 else if (ud.type == GTK_TYPE_SPINNER)
1998 ud.fn = update_spinner;
1999 else if (ud.type == GTK_TYPE_COMBO_BOX_TEXT)
2000 ud.fn = update_combo_box_text;
2001 else if (ud.type == GTK_TYPE_STATUSBAR)
2002 ud.fn = update_statusbar;
2003 else if (ud.type == GTK_TYPE_CALENDAR)
2004 ud.fn = update_calendar;
2005 else if (ud.type == GTK_TYPE_SOCKET)
2006 ud.fn = update_socket;
2007 else if (ud.type == GTK_TYPE_WINDOW)
2008 ud.fn = update_window;
2009 else
2010 ud.fn = complain;
2011 exec:
2012 pthread_testcancel();
2013 gdk_threads_add_timeout(1, (GSourceFunc)update_ui_in, &ud);
2014 sem_wait(&ud.msg_digested);
2015 cleanup:
2016 pthread_cleanup_pop(1); /* free ud.msg_tokens */
2017 pthread_cleanup_pop(1); /* free ud.msg */
2019 return NULL;
2023 * Create a fifo if necessary, and open it. Give up if the file
2024 * exists but is not a fifo
2026 static FILE *
2027 fifo(const char *name, const char *mode)
2029 struct stat sb;
2030 int fd;
2031 FILE *s = NULL;
2032 int bufmode;
2034 if (name != NULL) {
2035 stat(name, &sb);
2036 if (S_ISFIFO(sb.st_mode)) {
2037 if (chmod(name, 0600) != 0)
2038 bye(EXIT_FAILURE, stderr,
2039 "using pre-existing fifo %s: %s\n",
2040 name, strerror(errno));
2041 } else if (mkfifo(name, 0600) != 0)
2042 bye(EXIT_FAILURE, stderr,
2043 "making fifo %s: %s\n", name, strerror(errno));
2045 switch (mode[0]) {
2046 case 'r':
2047 bufmode = _IONBF;
2048 if (name == NULL)
2049 s = stdin;
2050 else {
2051 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
2052 bye(EXIT_FAILURE, stderr,
2053 "opening fifo %s (%s): %s\n",
2054 name, mode, strerror(errno));
2055 s = fdopen(fd, "r");
2057 break;
2058 case 'w':
2059 bufmode = _IOLBF;
2060 if (name == NULL)
2061 s = stdout;
2062 else
2063 s = fopen(name, "w+");
2064 break;
2065 default:
2066 ABORT;
2067 break;
2069 if (s == NULL)
2070 bye(EXIT_FAILURE, stderr, "opening fifo %s (%s): %s\n",
2071 name, mode, strerror(errno));
2072 else
2073 setvbuf(s, NULL, bufmode, 0);
2074 return s;
2078 * Remove suffix from name; find the object named like this
2080 static GObject *
2081 obj_sans_suffix(const char *suffix, const char *name)
2083 int str_l;
2084 char str[BUFLEN + 1] = {'\0'};
2086 str_l = suffix - name;
2087 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
2088 return gtk_builder_get_object(builder, str);
2092 * Callback that forwards a modification of a tree view cell to the
2093 * underlying model
2095 static void
2096 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2097 const gchar *new_text, gpointer model)
2099 GtkTreeIter iter;
2100 GtkTreeView *view;
2101 void *col;
2103 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2104 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
2105 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2106 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2107 new_text);
2108 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
2109 GTK_BUILDABLE(view));
2112 static void
2113 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
2115 GtkTreeIter iter;
2116 void *col;
2117 bool toggle_state;
2119 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2120 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2121 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2122 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2123 toggle_state? "0" : "1");
2127 * Attach to renderer key "col_number". Associate "col_number" with
2128 * the corresponding column number in the underlying model.
2129 * Due to what looks like a gap in the GTK API, renderer id and column
2130 * number are taken directly from the XML .ui file.
2132 static bool
2133 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2134 int n, GtkCellRenderer **renderer)
2136 xmlDocPtr doc;
2137 xmlXPathContextPtr xpath_ctx;
2138 xmlXPathObjectPtr xpath_obj;
2139 xmlNodeSetPtr nodes;
2140 xmlNodePtr cur;
2141 int i;
2142 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2143 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2144 const char *xpath_id = widget_name(t_col);
2145 char *xpath_base2 = "\"]/child[";
2146 size_t xpath_n_len = 3; /* Big Enough (TM) */
2147 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2148 " or @class=\"GtkCellRendererToggle\"]/";
2149 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2150 " or @name=\"active\"]";
2151 char *xpath_renderer_id = "/@id";
2152 size_t xpath_len;
2153 bool r = false;
2155 if ((doc = xmlParseFile(ui_file)) == NULL)
2156 return false;
2157 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2158 xmlFreeDoc(doc);
2159 return false;
2161 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2162 strlen(xpath_base2) + xpath_n_len +
2163 strlen(xpath_base3))
2164 + 1 /* "|" */
2165 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2166 + 1; /* '\0' */
2167 if ((xpath = malloc(xpath_len)) == NULL) {
2168 xmlFreeDoc(doc);
2169 return false;
2171 snprintf((char *)xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2172 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2173 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2174 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2175 xmlXPathFreeContext(xpath_ctx);
2176 free(xpath);
2177 xmlFreeDoc(doc);
2178 return false;
2180 if ((nodes = xpath_obj->nodesetval) != NULL) {
2181 for (i = 0; i < nodes->nodeNr; ++i) {
2182 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2183 cur = nodes->nodeTab[i];
2184 m_col_s = xmlNodeGetContent(cur);
2185 } else {
2186 cur = nodes->nodeTab[i];
2187 renderer_name = xmlNodeGetContent(cur);
2191 if (renderer_name) {
2192 *renderer = GTK_CELL_RENDERER(
2193 gtk_builder_get_object(builder, (char *)renderer_name));
2194 if (m_col_s) {
2195 g_object_set_data(G_OBJECT(*renderer), "col_number",
2196 GINT_TO_POINTER(strtol((char *)m_col_s,
2197 NULL, 10)));
2198 xmlFree(m_col_s);
2199 r = true;
2201 xmlFree(renderer_name);
2203 xmlXPathFreeObject(xpath_obj);
2204 xmlXPathFreeContext(xpath_ctx);
2205 free(xpath);
2206 xmlFreeDoc(doc);
2207 return r;
2210 static void
2211 connect_widget_signals(gpointer *obj, char *ui_file)
2213 const char *name = NULL;
2214 char *suffix = NULL;
2215 GObject *obj2;
2216 GType type = G_TYPE_INVALID;
2218 type = G_TYPE_FROM_INSTANCE(obj);
2219 if (GTK_IS_BUILDABLE(obj))
2220 name = widget_name(obj);
2221 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2222 gboolean editable = FALSE;
2223 GtkTreeView *view;
2224 GtkTreeModel *model;
2225 GtkCellRenderer *renderer;
2226 int i;
2228 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2229 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2230 model = gtk_tree_view_get_model(view);
2231 for (i = 1;; i++) {
2232 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2233 break;
2234 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2235 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2236 g_object_get(renderer, "editable", &editable, NULL);
2237 if (editable)
2238 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
2239 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
2240 g_object_get(renderer, "activatable", &editable, NULL);
2241 if (editable)
2242 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
2246 else if (type == GTK_TYPE_BUTTON) {
2247 /* Button associated with a GtkTextView. */
2248 if ((suffix = strstr(name, "_send_text")) != NULL &&
2249 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2250 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
2251 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2252 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
2253 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2254 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
2255 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2256 /* Buttons associated with (and part of) a GtkDialog.
2257 * (We shun response ids which could be returned from
2258 * gtk_dialog_run() because that would require the
2259 * user to define those response ids in Glade,
2260 * numerically */
2261 else if ((suffix = strstr(name, "_cancel")) != NULL &&
2262 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2263 if (eql(widget_name(obj2), MAIN_WIN))
2264 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2265 else
2266 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2267 else if ((suffix = strstr(name, "_ok")) != NULL &&
2268 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
2269 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
2270 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
2271 else /* generic button */
2272 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2273 if (eql(widget_name(obj2), MAIN_WIN))
2274 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2275 else
2276 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2277 } else if ((suffix = strstr(name, "_apply")) != NULL &&
2278 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2279 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
2280 else /* generic button */
2281 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2282 } else if (GTK_IS_MENU_ITEM(obj))
2283 if ((suffix = strstr(name, "_invoke")) != NULL &&
2284 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2285 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
2286 else
2287 g_signal_connect(obj, "activate", G_CALLBACK(cb), "active");
2288 else if (GTK_IS_WINDOW(obj))
2289 if (eql(name, MAIN_WIN))
2290 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
2291 else
2292 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2293 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2294 g_signal_connect(obj, "file-set", G_CALLBACK(cb), "file");
2295 else if (type == GTK_TYPE_COLOR_BUTTON)
2296 g_signal_connect(obj, "color-set", G_CALLBACK(cb), "color");
2297 else if (type == GTK_TYPE_FONT_BUTTON)
2298 g_signal_connect(obj, "font-set", G_CALLBACK(cb), "font");
2299 else if (type == GTK_TYPE_SWITCH)
2300 g_signal_connect(obj, "notify::active", G_CALLBACK(cb), NULL);
2301 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
2302 g_signal_connect(obj, "toggled", G_CALLBACK(cb), NULL);
2303 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
2304 g_signal_connect(obj, "changed", G_CALLBACK(cb), "text");
2305 else if (type == GTK_TYPE_SCALE)
2306 g_signal_connect(obj, "value-changed", G_CALLBACK(cb), "value");
2307 else if (type == GTK_TYPE_CALENDAR) {
2308 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb), "doubleclicked");
2309 g_signal_connect(obj, "day-selected", G_CALLBACK(cb), "clicked");
2310 } else if (type == GTK_TYPE_TREE_SELECTION)
2311 g_signal_connect(obj, "changed", G_CALLBACK(cb), "clicked");
2312 else if (type == GTK_TYPE_SOCKET) {
2313 g_signal_connect(obj, "plug-added", G_CALLBACK(cb), "plug-added");
2314 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_true), "plug-removed");
2315 } else if (type == GTK_TYPE_DRAWING_AREA)
2316 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
2317 else if (type == GTK_TYPE_EVENT_BOX) {
2318 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
2319 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
2320 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
2321 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
2322 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
2327 * We keep a style provider with each widget
2329 static void
2330 add_widget_style_provider(gpointer *obj, void *data)
2332 GtkStyleContext *context;
2333 GtkCssProvider *style_provider;
2335 (void)data;
2336 if (!GTK_IS_WIDGET(obj))
2337 return;
2338 style_provider = gtk_css_provider_new();
2339 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2340 gtk_style_context_add_provider(context,
2341 GTK_STYLE_PROVIDER(style_provider),
2342 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2343 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
2346 static void
2347 prepare_widgets(char *ui_file)
2349 GSList *objects = NULL;
2351 objects = gtk_builder_get_objects(builder);
2352 g_slist_foreach(objects, (GFunc)connect_widget_signals, ui_file);
2353 g_slist_foreach(objects, (GFunc)add_widget_style_provider, NULL);
2354 g_slist_free(objects);
2358 main(int argc, char *argv[])
2360 char opt;
2361 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
2362 char *xid_s = NULL, xid_s2[BUFLEN];
2363 Window xid;
2364 GtkWidget *plug, *body;
2365 pthread_t receiver;
2366 GError *error = NULL;
2367 GObject *main_window = NULL;
2368 FILE *in = NULL; /* command input */
2370 /* Disable runtime GLIB deprecation warnings: */
2371 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
2372 out = NULL;
2373 save = NULL;
2374 gtk_init(&argc, &argv);
2375 while ((opt = getopt(argc, argv, "he:i:o:u:GV")) != -1) {
2376 switch (opt) {
2377 case 'e': xid_s = optarg; break;
2378 case 'i': in_fifo = optarg; break;
2379 case 'o': out_fifo = optarg; break;
2380 case 'u': ui_file = optarg; break;
2381 case 'G': bye(EXIT_SUCCESS, stdout, "GTK+ v%d.%d.%d (running v%d.%d.%d)\n",
2382 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
2383 gtk_get_major_version(), gtk_get_minor_version(),
2384 gtk_get_micro_version());
2385 break;
2386 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
2387 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
2388 case '?':
2389 default: bye(EXIT_FAILURE, stderr, USAGE); break;
2392 if (argv[optind] != NULL)
2393 bye(EXIT_FAILURE, stderr,
2394 "illegal parameter '%s'\n" USAGE, argv[optind]);
2395 in = fifo(in_fifo, "r");
2396 out = fifo(out_fifo, "w");
2397 if (ui_file == NULL)
2398 ui_file = "pipeglade.ui";
2399 builder = gtk_builder_new();
2400 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0)
2401 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
2402 pthread_create(&receiver, NULL, (void *(*)(void *))digest_msg, in);
2403 main_window = gtk_builder_get_object(builder, MAIN_WIN);
2404 if (!GTK_IS_WINDOW(main_window))
2405 bye(EXIT_FAILURE, stderr,
2406 "no toplevel window named \'" MAIN_WIN "\'\n");
2407 xmlInitParser();
2408 LIBXML_TEST_VERSION;
2409 prepare_widgets(ui_file);
2410 if (xid_s == NULL) /* standalone */
2411 gtk_widget_show(GTK_WIDGET(main_window));
2412 else { /* We're being XEmbedded */
2413 xid = strtoul(xid_s, NULL, 10);
2414 snprintf(xid_s2, BUFLEN, "%lu", xid);
2415 if (!eql(xid_s, xid_s2))
2416 bye(EXIT_FAILURE, stderr,
2417 "%s is not a valid XEmbed socket id\n", xid_s);
2418 body = gtk_bin_get_child(GTK_BIN(main_window));
2419 gtk_container_remove(GTK_CONTAINER(main_window), body);
2420 plug = gtk_plug_new(xid);
2421 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
2422 bye(EXIT_FAILURE, stderr,
2423 "unable to embed into XEmbed socket %s\n", xid_s);
2424 gtk_container_add(GTK_CONTAINER(plug), body);
2425 gtk_widget_show(plug);
2427 gtk_main();
2428 if (in != stdin) {
2429 fclose(in);
2430 unlink(in_fifo);
2432 if (out != stdout) {
2433 fclose(out);
2434 unlink(out_fifo);
2436 pthread_cancel(receiver);
2437 pthread_join(receiver, NULL);
2438 xmlCleanupParser();
2439 exit(EXIT_SUCCESS);