Get rid of silly GTK runtime complaints during testing
[pipeglade.git] / pipeglade.c
blobc1e05f1814bd7b3667190bb806c9aa823fa96853
1 /*
2 * Copyright (c) 2014, 2015 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.3.0"
47 #define BUFLEN 256
48 #define WHITESPACE " \t\n"
49 #define MAIN_WIN "main"
50 #define USAGE \
51 "usage: pipeglade " \
52 "[-h] " \
53 "[-e xid] " \
54 "[-i in-fifo] " \
55 "[-o out-fifo] " \
56 "[-u glade-file.ui] " \
57 "[-G] " \
58 "[-V]\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 *in; /* commands */
77 static FILE *out; /* UI feedback messages */
78 static FILE *save; /* saving user data */
79 static char *loading_files[BUFLEN]; /* Keep track of */
80 static size_t newest_loading_file; /* loading files */
81 struct ui_data {
82 void (*fn)(GObject *, const char *action,
83 const char *data, const char *msg, GType type);
84 GObject *obj;
85 char *action;
86 char *data;
87 char *msg;
88 char *msg_tokens;
89 GType type;
90 sem_t msg_digested;
92 static GtkBuilder *builder; /* to be read from .ui file */
95 * Print a formatted message to stream s and give up with status
97 static void
98 bye(int status, FILE *s, const char *fmt, ...)
100 va_list ap;
102 va_start(ap, fmt);
103 vfprintf(s, fmt, ap);
104 va_end(ap);
105 exit(status);
109 * Check if string s1 and s2 are equal
111 static bool
112 eql(const char *s1, const char *s2)
114 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
117 static const char *
118 widget_name(void *obj)
120 return gtk_buildable_get_name(GTK_BUILDABLE(obj));
123 static void
124 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
126 char *data;
127 const char *w_name = widget_name(obj);
128 fd_set wfds;
129 int ofd = fileno(o);
130 struct timeval timeout = {1, 0};
132 FD_ZERO(&wfds);
133 FD_SET(ofd, &wfds);
134 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
135 fprintf(o, "%s:%s ", w_name, tag);
136 while ((data = va_arg(ap, char *)) != NULL) {
137 size_t i = 0;
138 char c;
140 while ((c = data[i++]) != '\0')
141 if (c == '\\')
142 fprintf(o, "\\\\");
143 else if (c == '\n')
144 fprintf(o, "\\n");
145 else
146 putc(c, o);
148 putc('\n', o);
149 } else
150 fprintf(stderr,
151 "send error; discarding feedback message %s:%s\n",
152 w_name, tag);
156 * Send GUI feedback to global stream "out". The message format is
157 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
158 * last argument must be NULL.
160 static void
161 send_msg(GtkBuildable *obj, const char *tag, ...)
163 va_list ap;
164 va_start(ap, tag);
165 send_msg_to(out, obj, tag, ap);
166 va_end(ap);
170 * Send message from GUI to global stream "save". The message format
171 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
172 * last argument must be NULL.
174 static void
175 save_msg(GtkBuildable *obj, const char *tag, ...)
177 va_list ap;
178 va_start(ap, tag);
179 send_msg_to(save, obj, tag, ap);
180 va_end(ap);
184 * Send message from GUI to global stream "save". The message format
185 * is "<origin>:set <data ...>". The variadic arguments are strings;
186 * last argument must be NULL.
188 static void
189 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
191 va_list ap;
192 va_start(ap, tag);
193 send_msg_to(save, obj, "set", ap);
194 va_end(ap);
198 * Callback that sends user's selection from a file dialog
200 static void
201 cb_send_file_chooser_dialog_selection(gpointer user_data)
203 send_msg(user_data, "file",
204 gtk_file_chooser_get_filename(user_data), NULL);
205 send_msg(user_data, "folder",
206 gtk_file_chooser_get_current_folder(user_data), NULL);
210 * Callback that sends in a message the content of the text buffer
211 * passed in user_data
213 static void
214 cb_send_text(GtkBuildable *obj, gpointer user_data)
216 GtkTextIter a, b;
218 gtk_text_buffer_get_bounds(user_data, &a, &b);
219 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
223 * Callback that sends in a message the highlighted text from the text
224 * buffer which was passed in user_data
226 static void
227 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
229 GtkTextIter a, b;
231 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
232 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
236 * Use msg_sender() to send a message describing a particular cell.
238 static void
239 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
240 GtkTreeModel *model, const char *path_s,
241 GtkTreeIter *iter, int col, GtkBuildable *obj)
243 GValue value = G_VALUE_INIT;
244 GType col_type;
245 char str[BUFLEN];
247 gtk_tree_model_get_value(model, iter, col, &value);
248 col_type = gtk_tree_model_get_column_type(model, col);
249 switch (col_type) {
250 case G_TYPE_INT:
251 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
252 msg_sender(obj, "gint", path_s, str, NULL);
253 break;
254 case G_TYPE_LONG:
255 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
256 msg_sender(obj, "glong", path_s, str, NULL);
257 break;
258 case G_TYPE_INT64:
259 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
260 msg_sender(obj, "gint64", path_s, str, NULL);
261 break;
262 case G_TYPE_UINT:
263 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
264 msg_sender(obj, "guint", path_s, str, NULL);
265 break;
266 case G_TYPE_ULONG:
267 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
268 msg_sender(obj, "gulong", path_s, str, NULL);
269 break;
270 case G_TYPE_UINT64:
271 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
272 msg_sender(obj, "guint64", path_s, str, NULL);
273 break;
274 case G_TYPE_BOOLEAN:
275 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
276 msg_sender(obj, "gboolean", path_s, str, NULL);
277 break;
278 case G_TYPE_FLOAT:
279 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
280 msg_sender(obj, "gfloat", path_s, str, NULL);
281 break;
282 case G_TYPE_DOUBLE:
283 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
284 msg_sender(obj, "gdouble", path_s, str, NULL);
285 break;
286 case G_TYPE_STRING:
287 snprintf(str, BUFLEN, " %d ", col);
288 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
289 break;
290 default:
291 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
292 break;
294 g_value_unset(&value);
298 * Use msg_sender() to send one message per column for a single row.
300 static void
301 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
302 GtkTreeModel *model, char *path_s,
303 GtkTreeIter *iter, GtkBuildable *obj)
305 int col;
306 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
307 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
311 * send_tree_row_msg serves as an argument for
312 * gtk_tree_selection_selected_foreach()
314 static gboolean
315 send_tree_row_msg(GtkTreeModel *model,
316 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
318 char *path_s = gtk_tree_path_to_string(path);
320 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
321 g_free(path_s);
322 return FALSE;
326 * save_tree_row_msg serves as an argument for
327 * gtk_tree_model_foreach().
328 * Send message from GUI to global stream "save".
330 static gboolean
331 save_tree_row_msg(GtkTreeModel *model,
332 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
334 char *path_s = gtk_tree_path_to_string(path);
336 (void)path;
337 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
338 g_free(path_s);
339 return FALSE;
343 * Callback that sends message(s) whose nature depends on the
344 * arguments passed. A call to this function will also be initiated
345 * by the user command ...:force.
347 static void
348 cb(GtkBuildable *obj, const char *tag)
350 char str[BUFLEN];
351 GdkRGBA color;
352 GtkTreeView *view;
353 unsigned int year = 0, month = 0, day = 0;
355 if (GTK_IS_ENTRY(obj))
356 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
357 else if (GTK_IS_MENU_ITEM(obj))
358 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
359 else if (GTK_IS_RANGE(obj)) {
360 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
361 send_msg(obj, tag, str, NULL);
362 } else if (GTK_IS_SWITCH(obj))
363 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
364 else if (GTK_IS_TOGGLE_BUTTON(obj))
365 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
366 else if (GTK_IS_COLOR_BUTTON(obj)) {
367 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
368 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
369 } else if (GTK_IS_FONT_BUTTON(obj))
370 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
371 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
372 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
373 else if (GTK_IS_BUTTON(obj) || GTK_IS_TREE_VIEW_COLUMN(obj) || GTK_IS_SOCKET(obj))
374 send_msg(obj, tag, NULL);
375 else if (GTK_IS_CALENDAR(obj)) {
376 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
377 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
378 send_msg(obj, tag, str, NULL);
379 } else if (GTK_IS_TREE_SELECTION(obj)) {
380 view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
381 send_msg(GTK_BUILDABLE(view), tag, NULL);
382 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
383 (GtkTreeSelectionForeachFunc)send_tree_row_msg,
384 view);
385 } else
386 fprintf(stderr, "ignoring callback from %s\n", widget_name(obj));
390 * Callback like cb(), but returning true.
392 static bool
393 cb_true(GtkBuildable *obj, const char *tag)
395 cb(obj, tag);
396 return true;
400 * Store a line from stream s into buf, which should have been malloc'd
401 * to bufsize. Enlarge buf and bufsize if necessary.
403 static size_t
404 read_buf(FILE *s, char **buf, size_t *bufsize)
406 size_t i = 0;
407 int c;
408 fd_set rfds;
409 int ifd = fileno(s);
410 bool esc = false;
412 FD_ZERO(&rfds);
413 FD_SET(ifd, &rfds);
414 for (;;) {
415 select(ifd + 1, &rfds, NULL, NULL, NULL);
416 c = getc(s);
417 if (c == '\n' || feof(s))
418 break;
419 if (i >= *bufsize - 1)
420 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
421 OOM_ABORT;
422 if (esc) {
423 esc = false;
424 switch (c) {
425 case 'n': (*buf)[i++] = '\n'; break;
426 case 'r': (*buf)[i++] = '\r'; break;
427 default: (*buf)[i++] = c; break;
429 } else if (c == '\\')
430 esc = true;
431 else
432 (*buf)[i++] = c;
434 (*buf)[i] = '\0';
435 return i;
439 * Warning message
441 static void
442 ign_cmd(GType type, const char *msg)
444 const char *name, *pad = " ";
446 if (type == G_TYPE_INVALID) {
447 name = "";
448 pad = "";
450 else
451 name = g_type_name(type);
452 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
456 * Drawing on a GtkDrawingArea
458 enum cairo_fn {
459 RECTANGLE,
460 ARC,
461 ARC_NEGATIVE,
462 CURVE_TO,
463 REL_CURVE_TO,
464 LINE_TO,
465 REL_LINE_TO,
466 MOVE_TO,
467 REL_MOVE_TO,
468 CLOSE_PATH,
469 SET_SOURCE_RGBA,
470 SET_DASH,
471 SET_LINE_CAP,
472 SET_LINE_JOIN,
473 SET_LINE_WIDTH,
474 FILL,
475 FILL_PRESERVE,
476 STROKE,
477 STROKE_PRESERVE,
478 SHOW_TEXT,
479 SET_FONT_SIZE,
483 * One single element of a drawing
485 struct draw_op {
486 struct draw_op *next;
487 struct draw_op *prev;
488 unsigned int id;
489 enum cairo_fn op;
490 void *op_args;
493 struct rectangle_args {
494 double x;
495 double y;
496 double width;
497 double height;
500 struct arc_args {
501 double x;
502 double y;
503 double radius;
504 double angle1;
505 double angle2;
508 struct curve_to_args {
509 double x1;
510 double y1;
511 double x2;
512 double y2;
513 double x3;
514 double y3;
517 struct move_to_args {
518 double x;
519 double y;
522 struct set_source_rgba_args {
523 GdkRGBA color;
526 struct set_dash_args {
527 int num_dashes;
528 double dashes[];
531 struct set_line_cap_args {
532 cairo_line_cap_t line_cap;
535 struct set_line_join_args {
536 cairo_line_join_t line_join;
539 struct set_line_width_args {
540 double width;
543 struct show_text_args {
544 int len;
545 char text[];
548 struct set_font_size_args {
549 double size;
552 static void
553 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
555 switch (op) {
556 case RECTANGLE: {
557 struct rectangle_args *args = op_args;
559 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
560 break;
562 case ARC: {
563 struct arc_args *args = op_args;
565 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
566 break;
568 case ARC_NEGATIVE: {
569 struct arc_args *args = op_args;
571 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
572 break;
574 case CURVE_TO: {
575 struct curve_to_args *args = op_args;
577 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
578 break;
580 case REL_CURVE_TO: {
581 struct curve_to_args *args = op_args;
583 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
584 break;
586 case LINE_TO: {
587 struct move_to_args *args = op_args;
589 cairo_line_to(cr, args->x, args->y);
590 break;
592 case REL_LINE_TO: {
593 struct move_to_args *args = op_args;
595 cairo_rel_line_to(cr, args->x, args->y);
596 break;
598 case MOVE_TO: {
599 struct move_to_args *args = op_args;
601 cairo_move_to(cr, args->x, args->y);
602 break;
604 case REL_MOVE_TO: {
605 struct move_to_args *args = op_args;
607 cairo_rel_move_to(cr, args->x, args->y);
608 break;
610 case CLOSE_PATH:
611 cairo_close_path(cr);
612 break;
613 case SET_SOURCE_RGBA: {
614 struct set_source_rgba_args *args = op_args;
616 gdk_cairo_set_source_rgba(cr, &args->color);
617 break;
619 case SET_DASH: {
620 struct set_dash_args *args = op_args;
622 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
623 break;
625 case SET_LINE_CAP: {
626 struct set_line_cap_args *args = op_args;
628 cairo_set_line_cap(cr, args->line_cap);
629 break;
631 case SET_LINE_JOIN: {
632 struct set_line_join_args *args = op_args;
634 cairo_set_line_join(cr, args->line_join);
635 break;
637 case SET_LINE_WIDTH: {
638 struct set_line_width_args *args = op_args;
640 cairo_set_line_width(cr, args->width);
641 break;
643 case FILL:
644 cairo_fill(cr);
645 break;
646 case FILL_PRESERVE:
647 cairo_fill_preserve(cr);
648 break;
649 case STROKE:
650 cairo_stroke(cr);
651 break;
652 case STROKE_PRESERVE:
653 cairo_stroke_preserve(cr);
654 break;
655 case SHOW_TEXT: {
656 struct show_text_args *args = op_args;
658 cairo_show_text(cr, args->text);
659 break;
661 case SET_FONT_SIZE: {
662 struct set_font_size_args *args = op_args;
664 cairo_set_font_size(cr, args->size);
665 break;
667 default:
668 ABORT;
669 break;
673 static bool
674 set_draw_op(struct draw_op *op, const char *action, const char *data)
676 if (eql(action, "rectangle")) {
677 struct rectangle_args *args;
679 if ((args = malloc(sizeof(*args))) == NULL)
680 OOM_ABORT;
681 op->op = RECTANGLE;
682 op->op_args = args;
683 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->width, &args->height) != 5)
684 return false;
685 } else if (eql(action, "arc")) {
686 struct arc_args *args;
687 double deg1, deg2;
689 if ((args = malloc(sizeof(*args))) == NULL)
690 OOM_ABORT;
691 op->op = ARC;
692 op->op_args = args;
693 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
694 return false;
695 args->angle1 = deg1 * (M_PI / 180.);
696 args->angle2 = deg2 * (M_PI / 180.);
697 } else if (eql(action, "arc_negative")) {
698 struct arc_args *args;
699 double deg1, deg2;
701 if ((args = malloc(sizeof(*args))) == NULL)
702 OOM_ABORT;
703 op->op = ARC_NEGATIVE;
704 op->op_args = args;
705 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id, &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
706 return false;
707 args->angle1 = deg1 * (M_PI / 180.);
708 args->angle2 = deg2 * (M_PI / 180.);
709 } else if (eql(action, "curve_to")) {
710 struct curve_to_args *args;
712 if ((args = malloc(sizeof(*args))) == NULL)
713 OOM_ABORT;
714 op->op = CURVE_TO;
715 op->op_args = args;
716 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)
717 return false;
718 } else if (eql(action, "rel_curve_to")) {
719 struct curve_to_args *args;
721 if ((args = malloc(sizeof(*args))) == NULL)
722 OOM_ABORT;
723 op->op = REL_CURVE_TO;
724 op->op_args = args;
725 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)
726 return false;
727 } else if (eql(action, "line_to")) {
728 struct move_to_args *args;
730 if ((args = malloc(sizeof(*args))) == NULL)
731 OOM_ABORT;
732 op->op = LINE_TO;
733 op->op_args = args;
734 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
735 return false;
736 } else if (eql(action, "rel_line_to")) {
737 struct move_to_args *args;
739 if ((args = malloc(sizeof(*args))) == NULL)
740 OOM_ABORT;
741 op->op = REL_LINE_TO;
742 op->op_args = args;
743 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
744 return false;
745 } else if (eql(action, "move_to")) {
746 struct move_to_args *args;
748 if ((args = malloc(sizeof(*args))) == NULL)
749 OOM_ABORT;
750 op->op = MOVE_TO;
751 op->op_args = args;
752 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
753 return false;
754 } else if (eql(action, "rel_move_to")) {
755 struct move_to_args *args;
757 if ((args = malloc(sizeof(*args))) == NULL)
758 OOM_ABORT;
759 op->op = REL_MOVE_TO;
760 op->op_args = args;
761 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
762 return false;
763 } else if (eql(action, "close_path")) {
764 op->op = CLOSE_PATH;
765 if (sscanf(data, "%u", &op->id) != 1)
766 return false;
767 op->op_args = NULL;
768 } else if (eql(action, "set_source_rgba")) {
769 struct set_source_rgba_args *args;
770 int c_start;
772 if ((args = malloc(sizeof(*args))) == NULL)
773 OOM_ABORT;
774 op->op = SET_SOURCE_RGBA;
775 op->op_args = args;
776 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
777 return false;;
778 gdk_rgba_parse(&args->color, data + c_start);
779 } else if (eql(action, "set_dash")) {
780 struct set_dash_args *args;
781 int d_start, n, i;
782 char data1[strlen(data) + 1];
783 char *next, *end;
785 strcpy(data1, data);
786 if (sscanf(data1, "%u %n", &op->id, &d_start) < 1)
787 return false;
788 next = end = data1 + d_start;
789 n = -1;
790 do {
791 n++;
792 next = end;
793 strtod(next, &end);
794 } while (next != end);
795 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
796 OOM_ABORT;
797 op->op = SET_DASH;
798 op->op_args = args;
799 args->num_dashes = n;
800 for (i = 0, next = data1 + d_start; i < n; i++, next = end) {
801 args->dashes[i] = strtod(next, &end);
803 } else if (eql(action, "set_line_cap")) {
804 struct set_line_cap_args *args;
805 char str[6 + 1];
807 if ((args = malloc(sizeof(*args))) == NULL)
808 OOM_ABORT;
809 op->op = SET_LINE_CAP;
810 op->op_args = args;
811 if (sscanf(data, "%u %6s", &op->id, str) != 2)
812 return false;
813 if (eql(str, "butt"))
814 args->line_cap = CAIRO_LINE_CAP_BUTT;
815 else if (eql(str, "round"))
816 args->line_cap = CAIRO_LINE_CAP_ROUND;
817 else if (eql(str, "square"))
818 args->line_cap = CAIRO_LINE_CAP_SQUARE;
819 else
820 return false;
821 } else if (eql(action, "set_line_join")) {
822 struct set_line_join_args *args;
823 char str[5 + 1];
825 if ((args = malloc(sizeof(*args))) == NULL)
826 OOM_ABORT;
827 op->op = SET_LINE_JOIN;
828 op->op_args = args;
829 if (sscanf(data, "%u %5s", &op->id, str) != 2)
830 return false;
831 if (eql(str, "miter"))
832 args->line_join = CAIRO_LINE_JOIN_MITER;
833 else if (eql(str, "round"))
834 args->line_join = CAIRO_LINE_JOIN_ROUND;
835 else if (eql(str, "bevel"))
836 args->line_join = CAIRO_LINE_JOIN_BEVEL;
837 else
838 return false;
839 } else if (eql(action, "set_line_width")) {
840 struct set_line_width_args *args;
842 if ((args = malloc(sizeof(*args))) == NULL)
843 OOM_ABORT;
844 op->op = SET_LINE_WIDTH;
845 op->op_args = args;
846 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
847 return false;
848 } else if (eql(action, "fill")) {
849 op->op = FILL;
850 if (sscanf(data, "%u", &op->id) != 1)
851 return false;
852 op->op_args = NULL;
853 } else if (eql(action, "fill_preserve")) {
854 op->op = FILL_PRESERVE;
855 if (sscanf(data, "%u", &op->id) != 1)
856 return false;
857 op->op_args = NULL;
858 } else if (eql(action, "stroke")) {
859 op->op = STROKE;
860 if (sscanf(data, "%u", &op->id) != 1)
861 return false;
862 op->op_args = NULL;
863 } else if (eql(action, "stroke_preserve")) {
864 op->op = STROKE_PRESERVE;
865 if (sscanf(data, "%u", &op->id) != 1)
866 return false;
867 op->op_args = NULL;
868 } else if (eql(action, "show_text")) {
869 struct show_text_args *args;
870 int start, len;
872 if (sscanf(data, "%u %n", &op->id, &start) < 1)
873 return false;
874 len = strlen(data + start) + 1;
875 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
876 OOM_ABORT;
877 op->op = SHOW_TEXT;
878 op->op_args = args;
879 args->len = len; /* not used */
880 strncpy(args->text, (data + start), len);
881 } else if (eql(action, "set_font_size")) {
882 struct set_font_size_args *args;
884 if ((args = malloc(sizeof(*args))) == NULL)
885 OOM_ABORT;
886 op->op = SET_FONT_SIZE;
887 op->op_args = args;
888 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
889 return false;
890 } else
891 return false;
892 return true;
896 * Add another element to widget's "draw_ops" list
898 static bool
899 ins_draw_op(GObject *widget, const char *action, const char *data)
901 struct draw_op *op, *draw_ops, *last_op;
903 if ((op = malloc(sizeof(*op))) == NULL)
904 OOM_ABORT;
905 op->op_args = NULL;
906 if (!set_draw_op(op, action, data)) {
907 free(op->op_args);
908 free(op);
909 return false;
911 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL) {
912 g_object_set_data(widget, "draw_ops", op);
913 insque(op, NULL);
914 } else {
915 for (last_op = draw_ops; last_op->next != NULL; last_op = last_op->next);
916 insque(op, last_op);
918 return true;
922 * Remove all elements with the given id from widget's "draw_ops" list
924 static bool
925 rem_draw_op(GObject *widget, const char *data)
927 struct draw_op *op, *next_op;
928 unsigned int id;
930 if (sscanf(data, "%u", &id) != 1)
931 return false;
932 op = g_object_get_data(widget, "draw_ops");
933 while (op != NULL) {
934 next_op = op->next;
935 if (op->id == id) {
936 if (op->prev == NULL)
937 g_object_set_data(widget, "draw_ops", next_op);
938 remque(op);
939 free(op->op_args);
940 free(op);
942 op = next_op;
944 return true;
948 * Callback that draws on a GtkDrawingArea
950 static gboolean
951 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
953 struct draw_op *op;
955 (void)data;
956 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
957 op != NULL;
958 op = op->next)
959 draw(cr, op->op, op->op_args);
960 return FALSE;
964 * Change the style of the widget passed
966 static void
967 update_widget_style(GObject *obj, const char *name,
968 const char *data, const char *whole_msg, GType type)
970 GtkStyleContext *context;
971 GtkStyleProvider *style_provider;
972 char *style_decl;
973 const char *prefix = "* {", *suffix = "}";
974 size_t sz;
976 (void)name;
977 (void)whole_msg;
978 (void)type;
979 style_provider = g_object_get_data(obj, "style_provider");
980 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
981 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
982 gtk_style_context_remove_provider(context, style_provider);
983 if ((style_decl = malloc(sz)) == NULL)
984 OOM_ABORT;
985 strcpy(style_decl, prefix);
986 strcat(style_decl, data);
987 strcat(style_decl, suffix);
988 gtk_style_context_add_provider(context, style_provider,
989 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
990 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
991 style_decl, -1, NULL);
992 free(style_decl);
996 * Update various kinds of widgets according to the respective action
997 * parameter
999 static void
1000 update_button(GObject *obj, const char *action,
1001 const char *data, const char *whole_msg, GType type)
1003 if (eql(action, "set_label"))
1004 gtk_button_set_label(GTK_BUTTON(obj), data);
1005 else
1006 ign_cmd(type, whole_msg);
1009 static void
1010 update_calendar(GObject *obj, const char *action,
1011 const char *data, const char *whole_msg, GType type)
1013 GtkCalendar *calendar = GTK_CALENDAR(obj);
1014 int year = 0, month = 0, day = 0;
1016 if (eql(action, "select_date")) {
1017 sscanf(data, "%d-%d-%d", &year, &month, &day);
1018 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1019 gtk_calendar_select_month(calendar, --month, year);
1020 gtk_calendar_select_day(calendar, day);
1021 } else
1022 ign_cmd(type, whole_msg);
1023 } else if (eql(action, "mark_day")) {
1024 day = strtol(data, NULL, 10);
1025 if (day > 0 && day <= 31)
1026 gtk_calendar_mark_day(calendar, day);
1027 else
1028 ign_cmd(type, whole_msg);
1029 } else if (eql(action, "clear_marks"))
1030 gtk_calendar_clear_marks(calendar);
1031 else
1032 ign_cmd(type, whole_msg);
1035 static void
1036 update_color_button(GObject *obj, const char *action,
1037 const char *data, const char *whole_msg, GType type)
1039 GdkRGBA color;
1041 if (eql(action, "set_color")) {
1042 gdk_rgba_parse(&color, data);
1043 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1044 } else
1045 ign_cmd(type, whole_msg);
1048 static void
1049 update_combo_box_text(GObject *obj, const char *action,
1050 const char *data, const char *whole_msg, GType type)
1052 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1053 char data1[strlen(data) + 1];
1055 strcpy(data1, data);
1056 if (eql(action, "prepend_text"))
1057 gtk_combo_box_text_prepend_text(combobox, data1);
1058 else if (eql(action, "append_text"))
1059 gtk_combo_box_text_append_text(combobox, data1);
1060 else if (eql(action, "remove"))
1061 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1062 else if (eql(action, "insert_text")) {
1063 char *position = strtok(data1, WHITESPACE);
1064 char *text = strtok(NULL, WHITESPACE);
1065 gtk_combo_box_text_insert_text(combobox, strtol(position, NULL, 10), text);
1066 } else
1067 ign_cmd(type, whole_msg);
1070 static void
1071 update_frame(GObject *obj, const char *action,
1072 const char *data, const char *whole_msg, GType type)
1074 if (eql(action, "set_label"))
1075 gtk_frame_set_label(GTK_FRAME(obj), data);
1076 else
1077 ign_cmd(type, whole_msg);
1080 static void
1081 update_drawing_area(GObject *obj, const char *action,
1082 const char *data, const char *whole_msg, GType type)
1084 if (eql(action, "remove")) {
1085 if (!rem_draw_op(obj, data))
1086 ign_cmd(type, whole_msg);
1087 } else if (eql(action, "refresh")) {
1088 gint width = gtk_widget_get_allocated_width(GTK_WIDGET(obj));
1089 gint height = gtk_widget_get_allocated_height(GTK_WIDGET(obj));
1091 gtk_widget_queue_draw_area(GTK_WIDGET(obj), 0, 0, width, height);
1092 } else if (ins_draw_op(obj, action, data));
1093 else
1094 ign_cmd(type, whole_msg);
1097 static void
1098 update_entry(GObject *obj, const char *action,
1099 const char *data, const char *whole_msg, GType type)
1101 GtkEntry *entry = GTK_ENTRY(obj);
1103 if (eql(action, "set_text"))
1104 gtk_entry_set_text(entry, data);
1105 else if (eql(action, "set_placeholder_text"))
1106 gtk_entry_set_placeholder_text(entry, data);
1107 else
1108 ign_cmd(type, whole_msg);
1111 static void
1112 update_label(GObject *obj, const char *action,
1113 const char *data, const char *whole_msg, GType type)
1115 if (eql(action, "set_text"))
1116 gtk_label_set_text(GTK_LABEL(obj), data);
1117 else
1118 ign_cmd(type, whole_msg);
1121 static void
1122 update_expander(GObject *obj, const char *action,
1123 const char *data, const char *whole_msg, GType type)
1125 GtkExpander *expander = GTK_EXPANDER(obj);
1127 if (eql(action, "set_expanded"))
1128 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1129 else if (eql(action, "set_label"))
1130 gtk_expander_set_label(expander, data);
1131 else
1132 ign_cmd(type, whole_msg);
1135 static void
1136 update_file_chooser_button(GObject *obj, const char *action,
1137 const char *data, const char *whole_msg, GType type)
1139 if (eql(action, "set_filename"))
1140 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1141 else
1142 ign_cmd(type, whole_msg);
1145 static void
1146 update_file_chooser_dialog(GObject *obj, const char *action,
1147 const char *data, const char *whole_msg, GType type)
1149 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1151 if (eql(action, "set_filename"))
1152 gtk_file_chooser_set_filename(chooser, data);
1153 else if (eql(action, "set_current_name"))
1154 gtk_file_chooser_set_current_name(chooser, data);
1155 else
1156 ign_cmd(type, whole_msg);
1159 static void
1160 update_font_button(GObject *obj, const char *action,
1161 const char *data, const char *whole_msg, GType type)
1163 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1165 if (eql(action, "set_font_name"))
1166 gtk_font_button_set_font_name(font_button, data);
1167 else
1168 ign_cmd(type, whole_msg);
1171 static void
1172 update_print_dialog(GObject *obj, const char *action,
1173 const char *data, const char *whole_msg, GType type)
1175 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1176 gint response_id;
1177 GtkPrinter *printer;
1178 GtkPrintSettings *settings;
1179 GtkPageSetup *page_setup;
1180 GtkPrintJob *job;
1182 if (eql(action, "print")) {
1183 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1184 switch (response_id) {
1185 case GTK_RESPONSE_OK:
1186 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1187 settings = gtk_print_unix_dialog_get_settings(dialog);
1188 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1189 job = gtk_print_job_new(data, printer, settings, page_setup);
1190 if (gtk_print_job_set_source_file(job, data, NULL))
1191 gtk_print_job_send(job, NULL, NULL, NULL);
1192 else
1193 ign_cmd(type, whole_msg);
1194 g_clear_object(&settings);
1195 g_clear_object(&job);
1196 break;
1197 case GTK_RESPONSE_CANCEL:
1198 case GTK_RESPONSE_DELETE_EVENT:
1199 break;
1200 default:
1201 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1202 widget_name(GTK_WIDGET(dialog)), response_id);
1203 break;
1205 gtk_widget_hide(GTK_WIDGET(dialog));
1206 } else
1207 ign_cmd(type, whole_msg);
1210 static void
1211 update_image(GObject *obj, const char *action,
1212 const char *data, const char *whole_msg, GType type)
1214 GtkImage *image = GTK_IMAGE(obj);
1215 GtkIconSize size;
1217 gtk_image_get_icon_name(image, NULL, &size);
1218 if (eql(action, "set_from_file"))
1219 gtk_image_set_from_file(image, data);
1220 else if (eql(action, "set_from_icon_name"))
1221 gtk_image_set_from_icon_name(image, data, size);
1222 else
1223 ign_cmd(type, whole_msg);
1226 static void
1227 update_notebook(GObject *obj, const char *action,
1228 const char *data, const char *whole_msg, GType type)
1230 if (eql(action, "set_current_page"))
1231 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), strtol(data, NULL, 10));
1232 else
1233 ign_cmd(type, whole_msg);
1236 static void
1237 update_progress_bar(GObject *obj, const char *action,
1238 const char *data, const char *whole_msg, GType type)
1240 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1242 if (eql(action, "set_text"))
1243 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1244 else if (eql(action, "set_fraction"))
1245 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1246 else
1247 ign_cmd(type, whole_msg);
1250 static void
1251 update_scale(GObject *obj, const char *action,
1252 const char *data, const char *whole_msg, GType type)
1254 if (eql(action, "set_value"))
1255 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
1256 else
1257 ign_cmd(type, whole_msg);
1260 static void
1261 update_spinner(GObject *obj, const char *action,
1262 const char *data, const char *whole_msg, GType type)
1264 GtkSpinner *spinner = GTK_SPINNER(obj);
1266 (void)data;
1267 if (eql(action, "start"))
1268 gtk_spinner_start(spinner);
1269 else if (eql(action, "stop"))
1270 gtk_spinner_stop(spinner);
1271 else
1272 ign_cmd(type, whole_msg);
1275 static void
1276 update_statusbar(GObject *obj, const char *action,
1277 const char *data, const char *whole_msg, GType type)
1279 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
1281 if (eql(action, "push"))
1282 gtk_statusbar_push(statusbar, 0, data);
1283 else if (eql(action, "pop"))
1284 gtk_statusbar_pop(statusbar, 0);
1285 else if (eql(action, "remove_all"))
1286 gtk_statusbar_remove_all(statusbar, 0);
1287 else
1288 ign_cmd(type, whole_msg);
1291 static void
1292 update_switch(GObject *obj, const char *action,
1293 const char *data, const char *whole_msg, GType type)
1295 if (eql(action, "set_active"))
1296 gtk_switch_set_active(GTK_SWITCH(obj), strtol(data, NULL, 10));
1297 else
1298 ign_cmd(type, whole_msg);
1301 static void
1302 update_text_view(GObject *obj, const char *action,
1303 const char *data, const char *whole_msg, GType type)
1305 GtkTextView *view = GTK_TEXT_VIEW(obj);
1306 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1307 GtkTextIter a, b;
1309 if (eql(action, "set_text"))
1310 gtk_text_buffer_set_text(textbuf, data, -1);
1311 else if (eql(action, "delete")) {
1312 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1313 gtk_text_buffer_delete(textbuf, &a, &b);
1314 } else if (eql(action, "insert_at_cursor"))
1315 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1316 else if (eql(action, "place_cursor")) {
1317 if (eql(data, "end"))
1318 gtk_text_buffer_get_end_iter(textbuf, &a);
1319 else /* numeric offset */
1320 gtk_text_buffer_get_iter_at_offset(textbuf, &a,
1321 strtol(data, NULL, 10));
1322 gtk_text_buffer_place_cursor(textbuf, &a);
1323 } else if (eql(action, "place_cursor_at_line")) {
1324 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1325 gtk_text_buffer_place_cursor(textbuf, &a);
1326 } else if (eql(action, "scroll_to_cursor"))
1327 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
1328 0., 0, 0., 0.);
1329 else if (eql(action, "save") && data != NULL &&
1330 (save = fopen(data, "w")) != NULL) {
1331 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1332 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
1333 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
1334 fclose(save);
1335 } else
1336 ign_cmd(type, whole_msg);
1339 static void
1340 update_toggle_button(GObject *obj, const char *action,
1341 const char *data, const char *whole_msg, GType type)
1343 if (eql(action, "set_label"))
1344 gtk_button_set_label(GTK_BUTTON(obj), data);
1345 else if (eql(action, "set_active"))
1346 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
1347 else
1348 ign_cmd(type, whole_msg);
1352 * Check if s is a valid string representation of a GtkTreePath
1354 static bool
1355 is_path_string(char *s)
1357 return s != NULL &&
1358 strlen(s) == strspn(s, ":0123456789") &&
1359 strstr(s, "::") == NULL &&
1360 strcspn(s, ":") > 0;
1363 static void
1364 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
1365 GtkTreeIter *parent, GtkTreeIter *sibling)
1367 if (GTK_IS_TREE_STORE(model))
1368 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
1369 iter, parent, sibling);
1370 else if (GTK_IS_LIST_STORE(model))
1371 gtk_list_store_insert_before(GTK_LIST_STORE(model),
1372 iter, sibling);
1373 else
1374 ABORT;
1377 static void
1378 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
1379 GtkTreeIter *parent, GtkTreeIter *sibling)
1381 if (GTK_IS_TREE_STORE(model))
1382 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
1383 iter, parent, sibling);
1384 else if (GTK_IS_LIST_STORE(model))
1385 gtk_list_store_insert_after(GTK_LIST_STORE(model),
1386 iter, sibling);
1387 else
1388 ABORT;
1391 static void
1392 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
1393 GtkTreeIter *position)
1395 if (GTK_IS_TREE_STORE(model))
1396 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
1397 else if (GTK_IS_LIST_STORE(model))
1398 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
1399 else
1400 ABORT;
1403 static void
1404 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
1406 if (GTK_IS_TREE_STORE(model))
1407 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
1408 else if (GTK_IS_LIST_STORE(model))
1409 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
1410 else
1411 ABORT;
1414 static void
1415 tree_model_clear(GtkTreeModel *model)
1417 if (GTK_IS_TREE_STORE(model))
1418 gtk_tree_store_clear(GTK_TREE_STORE(model));
1419 else if (GTK_IS_LIST_STORE(model))
1420 gtk_list_store_clear(GTK_LIST_STORE(model));
1421 else
1422 ABORT;
1425 static void
1426 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
1428 va_list ap;
1430 va_start(ap, iter);
1431 if (GTK_IS_TREE_STORE(model))
1432 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
1433 else if (GTK_IS_LIST_STORE(model))
1434 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
1435 else
1436 ABORT;
1437 va_end(ap);
1441 * Create an empty row at path if it doesn't yet exist. Create older
1442 * siblings and parents as necessary.
1444 static void
1445 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
1447 GtkTreePath *path_1; /* path's predecessor */
1448 GtkTreeIter iter_1; /* iter's predecessor */
1450 if (gtk_tree_model_get_iter(model, iter, path))
1451 return;
1452 path_1 = gtk_tree_path_copy(path);
1453 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
1454 create_subtree(model, path_1, iter);
1455 iter_1 = *iter;
1456 tree_model_insert_after(model, iter, NULL, &iter_1);
1457 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
1458 create_subtree(model, path_1, iter);
1459 if (gtk_tree_path_get_depth(path_1) == 0)
1460 /* first toplevel row */
1461 tree_model_insert_after(model, iter, NULL, NULL);
1462 else { /* first row in a lower level */
1463 iter_1 = *iter;
1464 tree_model_insert_after(model, iter, &iter_1, NULL);
1466 } /* neither prev nor up mean we're at the root of an empty tree */
1467 gtk_tree_path_free(path_1);
1470 static bool
1471 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
1472 const char *path_s, int col, const char *new_text)
1474 GType col_type = gtk_tree_model_get_column_type(model, col);
1475 long long int n;
1476 double d;
1477 GtkTreePath *path;
1478 char *endptr;
1479 bool ok = false;
1481 path = gtk_tree_path_new_from_string(path_s);
1482 create_subtree(model, path, iter);
1483 gtk_tree_path_free(path);
1484 switch (col_type) {
1485 case G_TYPE_BOOLEAN:
1486 case G_TYPE_INT:
1487 case G_TYPE_LONG:
1488 case G_TYPE_INT64:
1489 case G_TYPE_UINT:
1490 case G_TYPE_ULONG:
1491 case G_TYPE_UINT64:
1492 errno = 0;
1493 endptr = NULL;
1494 n = strtoll(new_text, &endptr, 10);
1495 if (!errno && endptr != new_text) {
1496 tree_model_set(model, iter, col, n, -1);
1497 ok = true;
1499 break;
1500 case G_TYPE_FLOAT:
1501 case G_TYPE_DOUBLE:
1502 errno = 0;
1503 endptr = NULL;
1504 d = strtod(new_text, &endptr);
1505 if (!errno && endptr != new_text) {
1506 tree_model_set(model, iter, col, d, -1);
1507 ok = true;
1509 break;
1510 case G_TYPE_STRING:
1511 tree_model_set(model, iter, col, new_text, -1);
1512 ok = true;
1513 break;
1514 default:
1515 fprintf(stderr, "column %d: %s not implemented\n",
1516 col, g_type_name(col_type));
1517 ok = true;
1518 break;
1520 return ok;
1523 static void
1524 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
1526 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
1527 /* is just fine and this function need not exist. */
1528 if (path == NULL)
1529 path = gtk_tree_path_new();
1530 gtk_tree_view_set_cursor(view, path, col, false);
1533 static void
1534 update_tree_view(GObject *obj, const char *action,
1535 const char *data, const char *whole_msg, GType type)
1537 GtkTreeView *view = GTK_TREE_VIEW(obj);
1538 GtkTreeModel *model = gtk_tree_view_get_model(view);
1539 GtkTreeIter iter0, iter1;
1540 GtkTreePath *path = NULL;
1541 bool iter0_valid, iter1_valid;
1542 char *tokens, *arg0, *arg1, *arg2;
1543 int col = -1; /* invalid column number */
1545 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
1547 fprintf(stderr, "missing model/");
1548 ign_cmd(type, whole_msg);
1549 return;
1551 if ((tokens = malloc(strlen(data) + 1)) == NULL)
1552 OOM_ABORT;
1553 strcpy(tokens, data);
1554 arg0 = strtok(tokens, WHITESPACE);
1555 arg1 = strtok(NULL, WHITESPACE);
1556 arg2 = strtok(NULL, "");
1557 iter0_valid = is_path_string(arg0) &&
1558 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
1559 iter1_valid = is_path_string(arg1) &&
1560 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
1561 if (is_path_string(arg1))
1562 col = strtol(arg1, NULL, 10);
1563 if (eql(action, "set")
1564 && col > -1 && col < gtk_tree_model_get_n_columns(model) &&
1565 is_path_string(arg0)) {
1566 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
1567 ign_cmd(type, whole_msg);
1568 } else if (eql(action, "scroll") && iter0_valid && iter1_valid) {
1569 path = gtk_tree_path_new_from_string(arg0);
1570 gtk_tree_view_scroll_to_cell (view,
1571 path,
1572 gtk_tree_view_get_column(view, col),
1573 0, 0., 0.);
1574 } else if (eql(action, "expand") && iter0_valid) {
1575 path = gtk_tree_path_new_from_string(arg0);
1576 gtk_tree_view_expand_row(view, path, false);
1577 } else if (eql(action, "expand_all") && iter0_valid) {
1578 path = gtk_tree_path_new_from_string(arg0);
1579 gtk_tree_view_expand_row(view, path, true);
1580 } else if (eql(action, "expand_all") && arg0 == NULL)
1581 gtk_tree_view_expand_all(view);
1582 else if (eql(action, "collapse") && iter0_valid) {
1583 path = gtk_tree_path_new_from_string(arg0);
1584 gtk_tree_view_collapse_row(view, path);
1585 } else if (eql(action, "collapse") && arg0 == NULL)
1586 gtk_tree_view_collapse_all(view);
1587 else if (eql(action, "set_cursor") && iter0_valid) {
1588 path = gtk_tree_path_new_from_string(arg0);
1589 tree_view_set_cursor(view, path, NULL);
1590 } else if (eql(action, "set_cursor") && arg0 == NULL) {
1591 tree_view_set_cursor(view, NULL, NULL);
1592 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1593 } else if (eql(action, "insert_row") && eql(arg0, "end"))
1594 tree_model_insert_before(model, &iter1, NULL, NULL);
1595 else if (eql(action, "insert_row") && iter0_valid && eql(arg1, "as_child"))
1596 tree_model_insert_after(model, &iter1, &iter0, NULL);
1597 else if (eql(action, "insert_row") && iter0_valid)
1598 tree_model_insert_before(model, &iter1, NULL, &iter0);
1599 else if (eql(action, "move_row") && iter0_valid && eql(arg1, "end"))
1600 tree_model_move_before(model, &iter0, NULL);
1601 else if (eql(action, "move_row") && iter0_valid && iter1_valid)
1602 tree_model_move_before(model, &iter0, &iter1);
1603 else if (eql(action, "remove_row") && iter0_valid)
1604 tree_model_remove(model, &iter0);
1605 else if (eql(action, "clear") && arg0 == NULL) {
1606 tree_view_set_cursor(view, NULL, NULL);
1607 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
1608 tree_model_clear(model);
1609 } else if (eql(action, "save") && arg0 != NULL &&
1610 (save = fopen(arg0, "w")) != NULL) {
1611 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc)save_tree_row_msg, view);
1612 fclose(save);
1613 } else
1614 ign_cmd(type, whole_msg);
1615 free(tokens);
1616 gtk_tree_path_free(path);
1619 static void
1620 update_socket(GObject *obj, const char *action,
1621 const char *data, const char *whole_msg, GType type)
1623 GtkSocket *socket = GTK_SOCKET(obj);
1624 Window id;
1625 char str[BUFLEN];
1627 (void)data;
1628 if (eql(action, "id")) {
1629 id = gtk_socket_get_id(socket);
1630 snprintf(str, BUFLEN, "%lu", id);
1631 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1632 } else
1633 ign_cmd(type, whole_msg);
1636 static void
1637 update_window(GObject *obj, const char *action,
1638 const char *data, const char *whole_msg, GType type)
1640 GtkWindow *window = GTK_WINDOW(obj);
1641 int x, y;
1643 if (eql(action, "set_title"))
1644 gtk_window_set_title(window, data);
1645 else if (eql(action, "fullscreen"))
1646 gtk_window_fullscreen(window);
1647 else if (eql(action, "unfullscreen"))
1648 gtk_window_unfullscreen(window);
1649 else if (eql(action, "resize")) {
1650 if (sscanf(data, "%d %d", &x, &y) != 2)
1651 gtk_window_get_default_size(window, &x, &y);
1652 gtk_window_resize(window, x, y);
1653 } else if (eql(action, "move")) {
1654 if (sscanf(data, "%d %d", &x, &y) == 2)
1655 gtk_window_move(window, x, y);
1656 else
1657 ign_cmd(type, whole_msg);
1658 } else
1659 ign_cmd(type, whole_msg);
1662 static void
1663 update_sensitivity(GObject *obj, const char *action,
1664 const char *data, const char *whole_msg, GType type)
1666 (void)action;
1667 (void)whole_msg;
1668 (void)type;
1669 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1672 static void
1673 update_visibility(GObject *obj, const char *action,
1674 const char *data, const char *whole_msg, GType type)
1676 (void)action;
1677 (void)whole_msg;
1678 (void)type;
1679 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
1682 static void
1683 fake_ui_activity(GObject *obj, const char *action,
1684 const char *data, const char *whole_msg, GType type)
1686 (void)action;
1687 (void)data;
1688 if (!GTK_IS_WIDGET(obj))
1689 ign_cmd(type, whole_msg);
1690 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
1691 cb(GTK_BUILDABLE(obj), "text");
1692 else if (GTK_IS_SCALE(obj))
1693 cb(GTK_BUILDABLE(obj), "value");
1694 else if (GTK_IS_CALENDAR(obj))
1695 cb(GTK_BUILDABLE(obj), "clicked");
1696 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
1697 cb(GTK_BUILDABLE(obj), "file");
1698 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
1699 ign_cmd(type, whole_msg);
1702 static void
1703 main_quit(GObject *obj, const char *action,
1704 const char *data, const char *whole_msg, GType type)
1706 (void)obj;
1707 (void)action;
1708 (void)data;
1709 (void)whole_msg;
1710 (void)type;
1712 gtk_main_quit();
1715 static void
1716 complain(GObject *obj, const char *action,
1717 const char *data, const char *whole_msg, GType type)
1719 (void)obj;
1720 (void)action;
1721 (void)data;
1723 ign_cmd(type, whole_msg);
1727 * Parse command pointed to by ud, and act on ui accordingly
1729 static void
1730 update_ui(struct ui_data *ud)
1732 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
1735 static void
1736 free_at(void **mem)
1738 free(*mem);
1742 * Keep track of loading files to avoid recursive loading of the same
1743 * file
1745 static bool
1746 push_loading_file(char *fn)
1748 size_t i;
1750 for (i = 1; i <= newest_loading_file; i++)
1751 if (eql(fn, loading_files[i]))
1752 return false;
1753 if (newest_loading_file > BUFLEN -2)
1754 return false;
1755 loading_files[++newest_loading_file] = fn;
1756 return true;
1759 static void
1760 pop_loading_file()
1762 if (newest_loading_file < 1)
1763 ABORT;
1764 newest_loading_file--;
1768 * Parse command pointed to by ud, and act on ui accordingly; post
1769 * semaphore ud.msg_digested if done. Runs once per command inside
1770 * gtk_main_loop()
1772 static gboolean
1773 update_ui_in(struct ui_data *ud)
1775 update_ui(ud);
1776 sem_post(&ud->msg_digested);
1777 return G_SOURCE_REMOVE;
1781 * Read lines from stream cmd and perform appropriate actions on the
1782 * GUI
1784 static void *
1785 digest_msg(FILE *cmd)
1787 struct ui_data ud;
1788 FILE *load; /* restoring user data */
1789 char *name;
1791 sem_init(&ud.msg_digested, 0, 0);
1792 for (;;) {
1793 char first_char = '\0';
1794 size_t msg_size = 32;
1795 int name_start = 0, name_end = 0;
1796 int action_start = 0, action_end = 0;
1797 int data_start;
1799 ud.type = G_TYPE_INVALID;
1800 if (feof(cmd))
1801 break;
1802 if ((ud.msg = malloc(msg_size)) == NULL)
1803 OOM_ABORT;
1804 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg);
1805 pthread_testcancel();
1806 read_buf(cmd, &ud.msg, &msg_size);
1807 data_start = strlen(ud.msg);
1808 if ((ud.msg_tokens = malloc(strlen(ud.msg) + 1)) == NULL)
1809 OOM_ABORT;
1810 pthread_cleanup_push((void(*)(void *))free_at, &ud.msg_tokens);
1811 strcpy(ud.msg_tokens, ud.msg);
1812 sscanf(ud.msg, " %c", &first_char);
1813 if (strlen(ud.msg) == 0 || first_char == '#') /* comment */
1814 goto cleanup;
1815 sscanf(ud.msg_tokens,
1816 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
1817 &name_start, &name_end, &action_start, &action_end, &data_start);
1818 ud.msg_tokens[name_end] = ud.msg_tokens[action_end] = '\0';
1819 name = ud.msg_tokens + name_start;
1820 ud.action = ud.msg_tokens + action_start;
1821 if (eql(ud.action, "main_quit")) {
1822 ud.fn = main_quit;
1823 goto exec;
1825 ud.data = ud.msg_tokens + data_start;
1826 if (eql(ud.action, "load") && strlen(ud.data) > 0 &&
1827 (load = fopen(ud.data, "r")) != NULL &&
1828 push_loading_file(ud.data)) {
1829 digest_msg(load);
1830 fclose(load);
1831 pop_loading_file();
1832 goto cleanup;
1834 if ((ud.obj = (gtk_builder_get_object(builder, name))) == NULL) {
1835 ud.fn = complain;
1836 goto exec;
1838 ud.type = G_TYPE_FROM_INSTANCE(ud.obj);
1839 if (eql(ud.action, "force"))
1840 ud.fn = fake_ui_activity;
1841 else if (eql(ud.action, "set_sensitive"))
1842 ud.fn = update_sensitivity;
1843 else if (eql(ud.action, "set_visible"))
1844 ud.fn = update_visibility;
1845 else if (eql(ud.action, "style")) {
1846 ud.action = name;
1847 ud.fn = update_widget_style;
1848 } else if (ud.type == GTK_TYPE_TREE_VIEW)
1849 ud.fn = update_tree_view;
1850 else if (ud.type == GTK_TYPE_DRAWING_AREA)
1851 ud.fn = update_drawing_area;
1852 else if (ud.type == GTK_TYPE_LABEL)
1853 ud.fn = update_label;
1854 else if (ud.type == GTK_TYPE_IMAGE)
1855 ud.fn = update_image;
1856 else if (ud.type == GTK_TYPE_TEXT_VIEW)
1857 ud.fn = update_text_view;
1858 else if (ud.type == GTK_TYPE_NOTEBOOK)
1859 ud.fn = update_notebook;
1860 else if (ud.type == GTK_TYPE_EXPANDER)
1861 ud.fn = update_expander;
1862 else if (ud.type == GTK_TYPE_FRAME)
1863 ud.fn = update_frame;
1864 else if (ud.type == GTK_TYPE_BUTTON)
1865 ud.fn = update_button;
1866 else if (ud.type == GTK_TYPE_FILE_CHOOSER_DIALOG)
1867 ud.fn = update_file_chooser_dialog;
1868 else if (ud.type == GTK_TYPE_FILE_CHOOSER_BUTTON)
1869 ud.fn = update_file_chooser_button;
1870 else if (ud.type == GTK_TYPE_COLOR_BUTTON)
1871 ud.fn = update_color_button;
1872 else if (ud.type == GTK_TYPE_FONT_BUTTON)
1873 ud.fn = update_font_button;
1874 else if (ud.type == GTK_TYPE_PRINT_UNIX_DIALOG)
1875 ud.fn = update_print_dialog;
1876 else if (ud.type == GTK_TYPE_SWITCH)
1877 ud.fn = update_switch;
1878 else if (ud.type == GTK_TYPE_TOGGLE_BUTTON || ud.type == GTK_TYPE_RADIO_BUTTON || ud.type == GTK_TYPE_CHECK_BUTTON)
1879 ud.fn = update_toggle_button;
1880 else if (ud.type == GTK_TYPE_SPIN_BUTTON || ud.type == GTK_TYPE_ENTRY)
1881 ud.fn = update_entry;
1882 else if (ud.type == GTK_TYPE_SCALE)
1883 ud.fn = update_scale;
1884 else if (ud.type == GTK_TYPE_PROGRESS_BAR)
1885 ud.fn = update_progress_bar;
1886 else if (ud.type == GTK_TYPE_SPINNER)
1887 ud.fn = update_spinner;
1888 else if (ud.type == GTK_TYPE_COMBO_BOX_TEXT)
1889 ud.fn = update_combo_box_text;
1890 else if (ud.type == GTK_TYPE_STATUSBAR)
1891 ud.fn = update_statusbar;
1892 else if (ud.type == GTK_TYPE_CALENDAR)
1893 ud.fn = update_calendar;
1894 else if (ud.type == GTK_TYPE_SOCKET)
1895 ud.fn = update_socket;
1896 else if (ud.type == GTK_TYPE_WINDOW)
1897 ud.fn = update_window;
1898 else
1899 ud.fn = complain;
1900 exec:
1901 pthread_testcancel();
1902 gdk_threads_add_timeout(1, (GSourceFunc)update_ui_in, &ud);
1903 sem_wait(&ud.msg_digested);
1904 cleanup:
1905 pthread_cleanup_pop(1); /* free ud.msg_tokens */
1906 pthread_cleanup_pop(1); /* free ud.msg */
1908 return NULL;
1912 * Create a fifo if necessary, and open it. Give up if the file
1913 * exists but is not a fifo
1915 static FILE *
1916 fifo(const char *name, const char *mode)
1918 struct stat sb;
1919 int fd;
1920 FILE *s = NULL;
1921 int bufmode;
1923 if (name != NULL && (stat(name, &sb), !S_ISFIFO(sb.st_mode)))
1924 if (mkfifo(name, 0666) != 0)
1925 bye(EXIT_FAILURE, stderr,
1926 "making fifo %s: %s\n", name, strerror(errno));
1927 switch (mode[0]) {
1928 case 'r':
1929 bufmode = _IONBF;
1930 if (name == NULL)
1931 s = stdin;
1932 else {
1933 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
1934 bye(EXIT_FAILURE, stderr,
1935 "opening fifo %s (%s): %s\n",
1936 name, mode, strerror(errno));
1937 s = fdopen(fd, "r");
1939 break;
1940 case 'w':
1941 bufmode = _IOLBF;
1942 if (name == NULL)
1943 s = stdout;
1944 else
1945 s = fopen(name, "w+");
1946 break;
1947 default:
1948 ABORT;
1949 break;
1951 if (s == NULL)
1952 bye(EXIT_FAILURE, stderr, "opening fifo %s (%s): %s\n",
1953 name, mode, strerror(errno));
1954 else
1955 setvbuf(s, NULL, bufmode, 0);
1956 return s;
1960 * Remove suffix from name; find the object named like this
1962 static GObject *
1963 obj_sans_suffix(const char *suffix, const char *name)
1965 int str_l;
1966 char str[BUFLEN + 1] = {'\0'};
1968 str_l = suffix - name;
1969 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
1970 return gtk_builder_get_object(builder, str);
1974 * Callback that forwards a modification of a tree view cell to the
1975 * underlying model
1977 static void
1978 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
1979 const gchar *new_text, gpointer model)
1981 GtkTreeIter iter;
1982 GtkTreeView *view;
1983 void *col;
1985 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
1986 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
1987 col = g_object_get_data(G_OBJECT(renderer), "col_number");
1988 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
1989 new_text);
1990 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
1991 GTK_BUILDABLE(view));
1994 static void
1995 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
1997 GtkTreeIter iter;
1998 void *col;
1999 bool toggle_state;
2001 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2002 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2003 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2004 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2005 toggle_state? "0" : "1");
2009 * Attach to renderer key "col_number". Associate "col_number" with
2010 * the corresponding column number in the underlying model.
2011 * Due to what looks like a gap in the GTK API, renderer id and column
2012 * number are taken directly from the XML .ui file.
2014 static bool
2015 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2016 int n, GtkCellRenderer **renderer)
2018 xmlDocPtr doc;
2019 xmlXPathContextPtr xpath_ctx;
2020 xmlXPathObjectPtr xpath_obj;
2021 xmlNodeSetPtr nodes;
2022 xmlNodePtr cur;
2023 int i;
2024 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2025 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2026 const char *xpath_id = widget_name(t_col);
2027 char *xpath_base2 = "\"]/child[";
2028 size_t xpath_n_len = 3; /* Big Enough (TM) */
2029 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2030 " or @class=\"GtkCellRendererToggle\"]/";
2031 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2032 " or @name=\"active\"]";
2033 char *xpath_renderer_id = "/@id";
2034 size_t xpath_len;
2035 bool r = false;
2037 if ((doc = xmlParseFile(ui_file)) == NULL)
2038 return false;
2039 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2040 xmlFreeDoc(doc);
2041 return false;
2043 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2044 strlen(xpath_base2) + xpath_n_len +
2045 strlen(xpath_base3))
2046 + 1 /* "|" */
2047 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2048 + 1; /* '\0' */
2049 if ((xpath = malloc(xpath_len)) == NULL) {
2050 xmlFreeDoc(doc);
2051 return false;
2053 snprintf((char *)xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2054 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2055 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2056 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2057 xmlXPathFreeContext(xpath_ctx);
2058 free(xpath);
2059 xmlFreeDoc(doc);
2060 return false;
2062 if ((nodes = xpath_obj->nodesetval) != NULL) {
2063 for (i = 0; i < nodes->nodeNr; ++i) {
2064 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2065 cur = nodes->nodeTab[i];
2066 m_col_s = xmlNodeGetContent(cur);
2067 } else {
2068 cur = nodes->nodeTab[i];
2069 renderer_name = xmlNodeGetContent(cur);
2073 if (renderer_name) {
2074 *renderer = GTK_CELL_RENDERER(
2075 gtk_builder_get_object(builder, (char *)renderer_name));
2076 if (m_col_s) {
2077 g_object_set_data(G_OBJECT(*renderer), "col_number",
2078 GINT_TO_POINTER(strtol((char *)m_col_s,
2079 NULL, 10)));
2080 xmlFree(m_col_s);
2081 r = true;
2083 xmlFree(renderer_name);
2085 xmlXPathFreeObject(xpath_obj);
2086 xmlXPathFreeContext(xpath_ctx);
2087 free(xpath);
2088 xmlFreeDoc(doc);
2089 return r;
2092 static void
2093 connect_widget_signals(gpointer *obj, char *ui_file)
2095 const char *name = NULL;
2096 char *suffix = NULL;
2097 GObject *obj2;
2098 GType type = G_TYPE_INVALID;
2100 type = G_TYPE_FROM_INSTANCE(obj);
2101 if (GTK_IS_BUILDABLE(obj))
2102 name = widget_name(obj);
2103 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2104 gboolean editable = FALSE;
2105 GtkTreeView *view;
2106 GtkTreeModel *model;
2107 GtkCellRenderer *renderer;
2108 int i;
2110 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2111 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2112 model = gtk_tree_view_get_model(view);
2113 for (i = 1;; i++) {
2114 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2115 break;
2116 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2117 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2118 g_object_get(renderer, "editable", &editable, NULL);
2119 if (editable)
2120 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
2121 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
2122 g_object_get(renderer, "activatable", &editable, NULL);
2123 if (editable)
2124 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
2128 else if (type == GTK_TYPE_BUTTON) {
2129 /* Button associated with a GtkTextView. */
2130 if ((suffix = strstr(name, "_send_text")) != NULL &&
2131 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2132 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
2133 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2134 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
2135 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2136 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
2137 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2138 /* Buttons associated with (and part of) a GtkDialog.
2139 * (We shun response ids which could be returned from
2140 * gtk_dialog_run() because that would require the
2141 * user to define those response ids in Glade,
2142 * numerically */
2143 else if ((suffix = strstr(name, "_cancel")) != NULL &&
2144 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2145 if (eql(widget_name(obj2), MAIN_WIN))
2146 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2147 else
2148 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2149 else if ((suffix = strstr(name, "_ok")) != NULL &&
2150 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
2151 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
2152 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
2153 else /* generic button */
2154 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2155 if (eql(widget_name(obj2), MAIN_WIN))
2156 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2157 else
2158 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2159 } else if ((suffix = strstr(name, "_apply")) != NULL &&
2160 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2161 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
2162 else /* generic button */
2163 g_signal_connect(obj, "clicked", G_CALLBACK(cb), "clicked");
2164 } else if (GTK_IS_MENU_ITEM(obj))
2165 if ((suffix = strstr(name, "_invoke")) != NULL &&
2166 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2167 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
2168 else
2169 g_signal_connect(obj, "activate", G_CALLBACK(cb), "active");
2170 else if (GTK_IS_WINDOW(obj))
2171 if (eql(name, MAIN_WIN))
2172 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
2173 else
2174 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2175 else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2176 g_signal_connect(obj, "file-set", G_CALLBACK(cb), "file");
2177 else if (type == GTK_TYPE_COLOR_BUTTON)
2178 g_signal_connect(obj, "color-set", G_CALLBACK(cb), "color");
2179 else if (type == GTK_TYPE_FONT_BUTTON)
2180 g_signal_connect(obj, "font-set", G_CALLBACK(cb), "font");
2181 else if (type == GTK_TYPE_SWITCH)
2182 g_signal_connect(obj, "notify::active", G_CALLBACK(cb), NULL);
2183 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
2184 g_signal_connect(obj, "toggled", G_CALLBACK(cb), NULL);
2185 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
2186 g_signal_connect(obj, "changed", G_CALLBACK(cb), "text");
2187 else if (type == GTK_TYPE_SCALE)
2188 g_signal_connect(obj, "value-changed", G_CALLBACK(cb), "value");
2189 else if (type == GTK_TYPE_CALENDAR) {
2190 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb), "doubleclicked");
2191 g_signal_connect(obj, "day-selected", G_CALLBACK(cb), "clicked");
2192 } else if (type == GTK_TYPE_TREE_SELECTION)
2193 g_signal_connect(obj, "changed", G_CALLBACK(cb), "clicked");
2194 else if (type == GTK_TYPE_SOCKET) {
2195 g_signal_connect(obj, "plug-added", G_CALLBACK(cb), "plug-added");
2196 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_true), "plug-removed");
2197 } else if (type == GTK_TYPE_DRAWING_AREA)
2198 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
2202 * We keep a style provider with each widget
2204 static void
2205 add_widget_style_provider(gpointer *obj, void *data)
2207 GtkStyleContext *context;
2208 GtkCssProvider *style_provider;
2210 (void)data;
2211 if (!GTK_IS_WIDGET(obj))
2212 return;
2213 style_provider = gtk_css_provider_new();
2214 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2215 gtk_style_context_add_provider(context,
2216 GTK_STYLE_PROVIDER(style_provider),
2217 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2218 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
2221 static void
2222 prepare_widgets(char *ui_file)
2224 GSList *objects = NULL;
2226 objects = gtk_builder_get_objects(builder);
2227 g_slist_foreach(objects, (GFunc)connect_widget_signals, ui_file);
2228 g_slist_foreach(objects, (GFunc)add_widget_style_provider, NULL);
2229 g_slist_free(objects);
2233 main(int argc, char *argv[])
2235 char opt;
2236 char *in_fifo = NULL, *out_fifo = NULL, *ui_file = NULL;
2237 char *xid_s = NULL, xid_s2[BUFLEN];
2238 Window xid;
2239 GtkWidget *plug, *body;
2240 pthread_t receiver;
2241 GError *error = NULL;
2242 GObject *main_window = NULL;
2244 /* Disable runtime GLIB deprecation warnings: */
2245 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
2246 in = NULL;
2247 out = NULL;
2248 save = NULL;
2249 newest_loading_file = 0;
2250 gtk_init(&argc, &argv);
2251 while ((opt = getopt(argc, argv, "he:i:o:u:GV")) != -1) {
2252 switch (opt) {
2253 case 'e': xid_s = optarg; break;
2254 case 'i': in_fifo = optarg; break;
2255 case 'o': out_fifo = optarg; break;
2256 case 'u': ui_file = optarg; break;
2257 case 'G': bye(EXIT_SUCCESS, stdout, "GTK+ v%d.%d.%d (running v%d.%d.%d)\n",
2258 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
2259 gtk_get_major_version(), gtk_get_minor_version(),
2260 gtk_get_micro_version());
2261 break;
2262 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
2263 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
2264 case '?':
2265 default: bye(EXIT_FAILURE, stderr, USAGE); break;
2268 if (argv[optind] != NULL)
2269 bye(EXIT_FAILURE, stderr,
2270 "illegal parameter '%s'\n" USAGE, argv[optind]);
2271 in = fifo(in_fifo, "r");
2272 out = fifo(out_fifo, "w");
2273 if (ui_file == NULL)
2274 ui_file = "pipeglade.ui";
2275 builder = gtk_builder_new();
2276 if (gtk_builder_add_from_file(builder, ui_file, &error) == 0)
2277 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
2278 pthread_create(&receiver, NULL, (void *(*)(void *))digest_msg, in);
2279 main_window = gtk_builder_get_object(builder, MAIN_WIN);
2280 if (!GTK_IS_WINDOW(main_window))
2281 bye(EXIT_FAILURE, stderr,
2282 "no toplevel window named \'" MAIN_WIN "\'\n");
2283 xmlInitParser();
2284 LIBXML_TEST_VERSION;
2285 prepare_widgets(ui_file);
2286 if (xid_s == NULL) /* standalone */
2287 gtk_widget_show(GTK_WIDGET(main_window));
2288 else { /* We're being XEmbedded */
2289 xid = strtoul(xid_s, NULL, 10);
2290 snprintf(xid_s2, BUFLEN, "%lu", xid);
2291 if (!eql(xid_s, xid_s2))
2292 bye(EXIT_FAILURE, stderr,
2293 "%s is not a valid XEmbed socket id\n", xid_s);
2294 body = gtk_bin_get_child(GTK_BIN(main_window));
2295 gtk_container_remove(GTK_CONTAINER(main_window), body);
2296 plug = gtk_plug_new(xid);
2297 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
2298 bye(EXIT_FAILURE, stderr,
2299 "unable to embed into XEmbed socket %s\n", xid_s);
2300 gtk_container_add(GTK_CONTAINER(plug), body);
2301 gtk_widget_show(plug);
2303 gtk_main();
2304 if (in != stdin) {
2305 fclose(in);
2306 unlink(in_fifo);
2308 if (out != stdout) {
2309 fclose(out);
2310 unlink(out_fifo);
2312 pthread_cancel(receiver);
2313 pthread_join(receiver, NULL);
2314 xmlCleanupParser();
2315 exit(EXIT_SUCCESS);