Automate away GtkDrawinArea command "refresh"
[pipeglade.git] / pipeglade.c
blob49f112d67bebef486166af264b4aad31ac23c484
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 <stdio.h>
35 #include <stdarg.h>
36 #include <stdbool.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sys/select.h>
40 #include <sys/stat.h>
41 #include <time.h>
42 #include <unistd.h>
44 #define VERSION "4.7.0"
45 #define BUFLEN 256
46 #define WHITESPACE " \t\n"
47 #define MAIN_WIN "main"
48 #define USAGE \
49 "usage: pipeglade [[-i in-fifo] " \
50 "[-o out-fifo] " \
51 "[-b] " \
52 "[-u glade-file.ui] " \
53 "[-e xid]\n" \
54 " [-l log-file] " \
55 "[-O err-file] " \
56 "[--display X-server]] | " \
57 "[-h |" \
58 "-G |" \
59 "-V]\n"
61 #define ABORT \
62 { \
63 fprintf(stderr, \
64 "In %s (%s:%d): ", \
65 __func__, __FILE__, __LINE__); \
66 abort(); \
69 #define OOM_ABORT \
70 { \
71 fprintf(stderr, \
72 "Out of memory in %s (%s:%d): ", \
73 __func__, __FILE__, __LINE__); \
74 abort(); \
77 static FILE *out; /* UI feedback messages */
78 static FILE *save; /* saving user data */
79 static FILE *log_out; /* logging output */
80 static GtkBuilder *builder; /* to be read from .ui file */
83 * ============================================================
84 * Helper functions
85 * ============================================================
89 * Check if s1 and s2 are equal strings
91 static bool
92 eql(const char *s1, const char *s2)
94 return s1 != NULL && s2 != NULL && strcmp(s1, s2) == 0;
98 * Print a formatted message to stream s and give up with status
100 static void
101 bye(int status, FILE *s, const char *fmt, ...)
103 va_list ap;
105 va_start(ap, fmt);
106 vfprintf(s, fmt, ap);
107 va_end(ap);
108 exit(status);
111 static void
112 show_lib_versions(void)
114 bye(EXIT_SUCCESS, stdout,
115 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
116 "cairo v%s (running v%s)\n",
117 GTK_MAJOR_VERSION, GTK_MINOR_VERSION, GTK_MICRO_VERSION,
118 gtk_get_major_version(), gtk_get_minor_version(),
119 gtk_get_micro_version(),
120 CAIRO_VERSION_STRING, cairo_version_string());
124 * XEmbed us if xid_s is given, or show a standalone window; give up
125 * on errors
127 static void
128 xembed_if(char *xid_s, GObject *main_window)
130 char xid_s2[BUFLEN];
131 Window xid;
132 GtkWidget *plug, *body;
134 if (xid_s == NULL) { /* standalone */
135 gtk_widget_show(GTK_WIDGET(main_window));
136 return;
138 /* We're being XEmbedded */
139 xid = strtoul(xid_s, NULL, 10);
140 snprintf(xid_s2, BUFLEN, "%lu", xid);
141 if (!eql(xid_s, xid_s2))
142 bye(EXIT_FAILURE, stderr,
143 "%s is not a valid XEmbed socket id\n", xid_s);
144 body = gtk_bin_get_child(GTK_BIN(main_window));
145 gtk_container_remove(GTK_CONTAINER(main_window), body);
146 plug = gtk_plug_new(xid);
147 if (!gtk_plug_get_embedded(GTK_PLUG(plug)))
148 bye(EXIT_FAILURE, stderr,
149 "unable to embed into XEmbed socket %s\n", xid_s);
150 gtk_container_add(GTK_CONTAINER(plug), body);
151 gtk_widget_show(plug);
155 * If requested, redirect stderr to file name
157 static void
158 redirect_stderr(const char *name)
160 if (name && (freopen(name, "a", stderr)) == NULL)
161 /* complaining on stdout since stderr is closed now */
162 bye(EXIT_FAILURE, stdout, "couldn't redirect stderr to %s: %s\n",
163 name, strerror(errno));
167 * fork() if requested in bg; give up on errors
169 static void
170 go_bg_if(bool bg, FILE *in, FILE *out, char *err_file)
172 pid_t pid = 0;
174 if (!bg)
175 return;
176 if (in == stdin || out == stdout)
177 bye(EXIT_FAILURE, stderr,
178 "parameter -b requires both -i and -o\n");
179 pid = fork();
180 if (pid < 0)
181 bye(EXIT_FAILURE, stderr,
182 "going to background: %s\n", strerror(errno));
183 if (pid > 0)
184 bye(EXIT_SUCCESS, stdout, "%d\n", pid);
185 /* We're the child */
186 close(fileno(stdin)); /* making certain not-so-smart */
187 close(fileno(stdout)); /* system/run-shell commands happy */
188 if (err_file == NULL)
189 redirect_stderr("/dev/null");
193 * Print a warning about a malformed command to stderr
195 static void
196 ign_cmd(GType type, const char *msg)
198 const char *name, *pad = " ";
200 if (type == G_TYPE_INVALID) {
201 name = "";
202 pad = "";
203 } else
204 name = g_type_name(type);
205 fprintf(stderr, "ignoring %s%scommand \"%s\"\n", name, pad, msg);
209 * Check if n is, or can be made, the name of a fifo. Give up if n
210 * exists but is not a fifo.
212 static void
213 find_fifo(const char *n)
215 struct stat sb;
217 stat(n, &sb);
218 if (S_ISFIFO(sb.st_mode)) {
219 if (chmod(n, 0600) != 0)
220 bye(EXIT_FAILURE, stderr, "using pre-existing fifo %s: %s\n",
221 n, strerror(errno));
222 } else if (mkfifo(n, 0600) != 0)
223 bye(EXIT_FAILURE, stderr, "making fifo %s: %s\n",
224 n, strerror(errno));
228 * Create a fifo if necessary, and open it. Give up if the file
229 * exists but is not a fifo
231 static FILE *
232 open_in_fifo(const char *name)
234 int fd;
235 FILE *s = NULL;
237 if (name == NULL)
238 return stdin;
239 find_fifo(name);
240 if ((fd = open(name, O_RDWR | O_NONBLOCK)) < 0)
241 bye(EXIT_FAILURE, stderr, "opening fifo %s (r): %s\n",
242 name, strerror(errno));
243 if ((s = fdopen(fd, "r")) == NULL)
244 bye(EXIT_FAILURE, stderr, "opening fifo %s (r): %s\n",
245 name, strerror(errno));
246 setvbuf(s, NULL, _IONBF, 0);
247 return s;
250 static FILE *
251 open_out_fifo(const char *name)
253 FILE *s = NULL;
255 if (name == NULL)
256 return stdout;
257 find_fifo(name);
258 if ((s = fopen(name, "w+")) == NULL)
259 bye(EXIT_FAILURE, stderr, "opening fifo %s (w): %s\n",
260 name, strerror(errno));
261 setvbuf(s, NULL, _IOLBF, 0);
262 return s;
266 * Create a log file if necessary, and open it. A name of "-"
267 * requests use of stderr.
269 static FILE *
270 open_log(const char *name)
272 FILE *s = NULL;
274 if (eql(name, "-"))
275 s = stderr;
276 else if (name && (s = fopen(name, "a")) == NULL)
277 bye(EXIT_FAILURE, stderr, "opening log file %s: %s\n",
278 name, strerror(errno));
279 return s;
282 static void
283 rm_unless(FILE *forbidden, FILE *s, char *name)
285 if (s == forbidden)
286 return;
287 fclose(s);
288 unlink(name);
292 * Microseconds elapsed since start
294 static long int
295 usec_since(struct timespec *start)
297 struct timespec now;
299 clock_gettime(CLOCK_MONOTONIC, &now);
300 return (now.tv_sec - start->tv_sec) * 1e6 +
301 (now.tv_nsec - start->tv_nsec) / 1e3;
305 * Write log file
307 static void
308 log_msg(char *msg)
310 static struct timespec start;
311 static char *old_msg;
313 if (log_out == NULL) /* no logging */
314 return;
315 if (msg == NULL && old_msg == NULL)
316 fprintf(log_out,
317 "##########\t##### (New Pipeglade session) #####\n");
318 else if (msg == NULL && old_msg != NULL) { /* command done; start idle */
319 fprintf(log_out,
320 "%10ld\t%s\n", usec_since(&start), old_msg);
321 free(old_msg);
322 old_msg = NULL;
323 } else if (msg != NULL && old_msg == NULL) { /* idle done; start command */
324 fprintf(log_out,
325 "%10ld\t### (Idle) ###\n", usec_since(&start));
326 if ((old_msg = malloc(strlen(msg) + 1)) == NULL)
327 OOM_ABORT;
328 strcpy(old_msg, msg);
329 } else
330 ABORT;
331 clock_gettime(CLOCK_MONOTONIC, &start);
335 * Remove suffix from name; find the object named like this
337 static GObject *
338 obj_sans_suffix(const char *suffix, const char *name)
340 int str_l;
341 char str[BUFLEN + 1] = {'\0'};
343 str_l = suffix - name;
344 strncpy(str, name, str_l < BUFLEN ? str_l : BUFLEN);
345 return gtk_builder_get_object(builder, str);
349 * Read UI definition from ui_file; give up on errors
351 static GtkBuilder *
352 builder_from_file(char *ui_file)
354 GtkBuilder *b;
355 GError *error = NULL;
357 b = gtk_builder_new();
358 if (gtk_builder_add_from_file(b, ui_file, &error) == 0)
359 bye(EXIT_FAILURE, stderr, "%s\n", error->message);
360 return b;
363 static const char *
364 widget_name(GtkBuildable *obj)
366 return gtk_buildable_get_name(obj);
370 * Get the main window; give up on errors
372 static GObject *
373 find_main_window(void)
375 GObject *mw;
377 if (GTK_IS_WINDOW(mw = gtk_builder_get_object(builder, MAIN_WIN)))
378 return mw;
379 bye(EXIT_FAILURE, stderr, "no toplevel window named \'" MAIN_WIN "\'\n");
380 return NULL; /* NOT REACHED */
384 * Store a line from stream s into buf, which should have been malloc'd
385 * to bufsize. Enlarge buf and bufsize if necessary.
387 static size_t
388 read_buf(FILE *s, char **buf, size_t *bufsize)
390 size_t i = 0;
391 int c;
392 fd_set rfds;
393 int ifd = fileno(s);
394 bool esc = false;
396 FD_ZERO(&rfds);
397 FD_SET(ifd, &rfds);
398 for (;;) {
399 select(ifd + 1, &rfds, NULL, NULL, NULL);
400 c = getc(s);
401 if (c == '\n' || feof(s))
402 break;
403 if (i >= *bufsize - 1)
404 if ((*buf = realloc(*buf, *bufsize = *bufsize * 2)) == NULL)
405 OOM_ABORT;
406 if (esc) {
407 esc = false;
408 switch (c) {
409 case 'n': (*buf)[i++] = '\n'; break;
410 case 'r': (*buf)[i++] = '\r'; break;
411 default: (*buf)[i++] = c; break;
413 } else if (c == '\\')
414 esc = true;
415 else
416 (*buf)[i++] = c;
418 (*buf)[i] = '\0';
419 return i;
423 * ============================================================
424 * Receiving feedback from the GUI
425 * ============================================================
428 static void
429 send_msg_to(FILE* o, GtkBuildable *obj, const char *tag, va_list ap)
431 char *data;
432 const char *w_name = widget_name(obj);
433 fd_set wfds;
434 int ofd = fileno(o);
435 struct timeval timeout = {1, 0};
437 FD_ZERO(&wfds);
438 FD_SET(ofd, &wfds);
439 if (select(ofd + 1, NULL, &wfds, NULL, &timeout) == 1) {
440 fprintf(o, "%s:%s ", w_name, tag);
441 while ((data = va_arg(ap, char *)) != NULL) {
442 size_t i = 0;
443 char c;
445 while ((c = data[i++]) != '\0')
446 if (c == '\\')
447 fprintf(o, "\\\\");
448 else if (c == '\n')
449 fprintf(o, "\\n");
450 else
451 putc(c, o);
453 putc('\n', o);
454 } else
455 fprintf(stderr,
456 "send error; discarding feedback message %s:%s\n",
457 w_name, tag);
461 * Send GUI feedback to global stream "out". The message format is
462 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
463 * last argument must be NULL.
465 static void
466 send_msg(GtkBuildable *obj, const char *tag, ...)
468 va_list ap;
470 va_start(ap, tag);
471 send_msg_to(out, obj, tag, ap);
472 va_end(ap);
476 * Send message from GUI to global stream "save". The message format
477 * is "<origin>:<tag> <data ...>". The variadic arguments are strings;
478 * last argument must be NULL.
480 static void
481 save_msg(GtkBuildable *obj, const char *tag, ...)
483 va_list ap;
485 va_start(ap, tag);
486 send_msg_to(save, obj, tag, ap);
487 va_end(ap);
491 * Send message from GUI to global stream "save". The message format
492 * is "<origin>:set <data ...>". The variadic arguments are strings;
493 * last argument must be NULL.
495 static void
496 save_action_set_msg(GtkBuildable *obj, const char *tag, ...)
498 va_list ap;
500 va_start(ap, tag);
501 send_msg_to(save, obj, "set", ap);
502 va_end(ap);
506 * Use msg_sender() to send a message describing a particular cell
508 static void
509 send_tree_cell_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
510 GtkTreeModel *model, const char *path_s,
511 GtkTreeIter *iter, int col, GtkBuildable *obj)
513 GValue value = G_VALUE_INIT;
514 GType col_type;
515 char str[BUFLEN];
517 gtk_tree_model_get_value(model, iter, col, &value);
518 col_type = gtk_tree_model_get_column_type(model, col);
519 switch (col_type) {
520 case G_TYPE_INT:
521 snprintf(str, BUFLEN, " %d %d", col, g_value_get_int(&value));
522 msg_sender(obj, "gint", path_s, str, NULL);
523 break;
524 case G_TYPE_LONG:
525 snprintf(str, BUFLEN, " %d %ld", col, g_value_get_long(&value));
526 msg_sender(obj, "glong", path_s, str, NULL);
527 break;
528 case G_TYPE_INT64:
529 snprintf(str, BUFLEN, " %d %" PRId64, col, g_value_get_int64(&value));
530 msg_sender(obj, "gint64", path_s, str, NULL);
531 break;
532 case G_TYPE_UINT:
533 snprintf(str, BUFLEN, " %d %u", col, g_value_get_uint(&value));
534 msg_sender(obj, "guint", path_s, str, NULL);
535 break;
536 case G_TYPE_ULONG:
537 snprintf(str, BUFLEN, " %d %lu", col, g_value_get_ulong(&value));
538 msg_sender(obj, "gulong", path_s, str, NULL);
539 break;
540 case G_TYPE_UINT64:
541 snprintf(str, BUFLEN, " %d %" PRIu64, col, g_value_get_uint64(&value));
542 msg_sender(obj, "guint64", path_s, str, NULL);
543 break;
544 case G_TYPE_BOOLEAN:
545 snprintf(str, BUFLEN, " %d %d", col, g_value_get_boolean(&value));
546 msg_sender(obj, "gboolean", path_s, str, NULL);
547 break;
548 case G_TYPE_FLOAT:
549 snprintf(str, BUFLEN, " %d %f", col, g_value_get_float(&value));
550 msg_sender(obj, "gfloat", path_s, str, NULL);
551 break;
552 case G_TYPE_DOUBLE:
553 snprintf(str, BUFLEN, " %d %f", col, g_value_get_double(&value));
554 msg_sender(obj, "gdouble", path_s, str, NULL);
555 break;
556 case G_TYPE_STRING:
557 snprintf(str, BUFLEN, " %d ", col);
558 msg_sender(obj, "gchararray", path_s, str, g_value_get_string(&value), NULL);
559 break;
560 default:
561 fprintf(stderr, "column %d not implemented: %s\n", col, G_VALUE_TYPE_NAME(&value));
562 break;
564 g_value_unset(&value);
568 * Use msg_sender() to send one message per column for a single row
570 static void
571 send_tree_row_msg_by(void msg_sender(GtkBuildable *, const char *, ...),
572 GtkTreeModel *model, char *path_s,
573 GtkTreeIter *iter, GtkBuildable *obj)
575 int col;
577 for (col = 0; col < gtk_tree_model_get_n_columns(model); col++)
578 send_tree_cell_msg_by(msg_sender, model, path_s, iter, col, obj);
582 * send_tree_row_msg serves as an argument for
583 * gtk_tree_selection_selected_foreach()
585 static gboolean
586 send_tree_row_msg(GtkTreeModel *model,
587 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
589 char *path_s = gtk_tree_path_to_string(path);
591 send_tree_row_msg_by(send_msg, model, path_s, iter, obj);
592 g_free(path_s);
593 return FALSE;
597 * save_tree_row_msg serves as an argument for
598 * gtk_tree_model_foreach().
599 * Send message from GUI to global stream "save".
601 static gboolean
602 save_tree_row_msg(GtkTreeModel *model,
603 GtkTreePath *path, GtkTreeIter *iter, GtkBuildable *obj)
605 char *path_s = gtk_tree_path_to_string(path);
607 (void) path;
608 send_tree_row_msg_by(save_action_set_msg, model, path_s, iter, obj);
609 g_free(path_s);
610 return FALSE;
613 static void
614 cb_calendar(GtkBuildable *obj, const char *tag)
616 char str[BUFLEN];
617 unsigned int year = 0, month = 0, day = 0;
619 gtk_calendar_get_date(GTK_CALENDAR(obj), &year, &month, &day);
620 snprintf(str, BUFLEN, "%04u-%02u-%02u", year, ++month, day);
621 send_msg(obj, tag, str, NULL);
624 static void
625 cb_color_button(GtkBuildable *obj, const char *tag)
627 GdkRGBA color;
629 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj), &color);
630 send_msg(obj, tag, gdk_rgba_to_string(&color), NULL);
633 static void
634 cb_editable(GtkBuildable *obj, const char *tag)
636 send_msg(obj, tag, gtk_entry_get_text(GTK_ENTRY(obj)), NULL);
640 * Callback that sends a message about a pointer device button press
641 * in a GtkEventBox
643 static bool
644 cb_event_box_button(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
646 char data[BUFLEN];
648 snprintf(data, BUFLEN, "%d %.1lf %.1lf",
649 e->button.button, e->button.x, e->button.y);
650 send_msg(obj, user_data, data, NULL);
651 return true;
655 * Callback that sends in a message the name of the key pressed when
656 * a GtkEventBox is focused
658 static bool
659 cb_event_box_key(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
661 send_msg(obj, user_data, gdk_keyval_name(e->key.keyval), NULL);
662 return true;
666 * Callback that sends a message about pointer device motion in a
667 * GtkEventBox
669 static bool
670 cb_event_box_motion(GtkBuildable *obj, GdkEvent *e, gpointer user_data)
672 char data[BUFLEN];
674 snprintf(data, BUFLEN, "%.1lf %.1lf", e->button.x, e->button.y);
675 send_msg(obj, user_data, data, NULL);
676 return true;
680 * Callback that only sends "name:tag" and returns false
682 static bool
683 cb_event_simple(GtkBuildable *obj, GdkEvent *e, const char *tag)
685 (void) e;
686 send_msg(obj, tag, NULL);
687 return false;
690 static void
691 cb_file_chooser_button(GtkBuildable *obj, const char *tag)
693 send_msg(obj, tag, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj)), NULL);
696 static void
697 cb_font_button(GtkBuildable *obj, const char *tag)
699 send_msg(obj, tag, gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj)), NULL);
702 static void
703 cb_menu_item(GtkBuildable *obj, const char *tag)
705 send_msg(obj, tag, gtk_menu_item_get_label(GTK_MENU_ITEM(obj)), NULL);
708 static void
709 cb_range(GtkBuildable *obj, const char *tag)
711 char str[BUFLEN];
713 snprintf(str, BUFLEN, "%f", gtk_range_get_value(GTK_RANGE(obj)));
714 send_msg(obj, tag, str, NULL);
718 * Callback that sends user's selection from a file dialog
720 static void
721 cb_send_file_chooser_dialog_selection(gpointer user_data)
723 send_msg(user_data, "file",
724 gtk_file_chooser_get_filename(user_data), NULL);
725 send_msg(user_data, "folder",
726 gtk_file_chooser_get_current_folder(user_data), NULL);
730 * Callback that sends in a message the content of the text buffer
731 * passed in user_data
733 static void
734 cb_send_text(GtkBuildable *obj, gpointer user_data)
736 GtkTextIter a, b;
738 gtk_text_buffer_get_bounds(user_data, &a, &b);
739 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
743 * Callback that sends in a message the highlighted text from the text
744 * buffer which was passed in user_data
746 static void
747 cb_send_text_selection(GtkBuildable *obj, gpointer user_data)
749 GtkTextIter a, b;
751 gtk_text_buffer_get_selection_bounds(user_data, &a, &b);
752 send_msg(obj, "text", gtk_text_buffer_get_text(user_data, &a, &b, TRUE), NULL);
756 * Callback that only sends "name:tag" and returns true
758 static bool
759 cb_simple(GtkBuildable *obj, const char *tag)
761 send_msg(obj, tag, NULL);
762 return true;
765 static void
766 cb_switch(GtkBuildable *obj, void *pspec, void *user_data)
768 (void) pspec;
769 (void) user_data;
770 send_msg(obj, gtk_switch_get_active(GTK_SWITCH(obj)) ? "1" : "0", NULL);
773 static void
774 cb_toggle_button(GtkBuildable *obj, const char *tag)
776 (void) tag;
777 send_msg(obj, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj)) ? "1" : "0", NULL);
780 static void
781 cb_tree_selection(GtkBuildable *obj, const char *tag)
783 GtkTreeView *view;
785 view = gtk_tree_selection_get_tree_view(GTK_TREE_SELECTION(obj));
786 send_msg(GTK_BUILDABLE(view), tag, NULL);
787 gtk_tree_selection_selected_foreach(GTK_TREE_SELECTION(obj),
788 (GtkTreeSelectionForeachFunc) send_tree_row_msg,
789 view);
793 * ============================================================
794 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
795 * helper functions
796 * ============================================================
800 * The set of supported drawing operations
802 enum cairo_fn {
803 ARC,
804 ARC_NEGATIVE,
805 CLOSE_PATH,
806 CURVE_TO,
807 FILL,
808 FILL_PRESERVE,
809 LINE_TO,
810 MOVE_TO,
811 RECTANGLE,
812 REL_CURVE_TO,
813 REL_LINE_TO,
814 REL_MOVE_TO,
815 REL_MOVE_FOR,
816 SET_DASH,
817 SET_FONT_FACE,
818 SET_FONT_SIZE,
819 SET_LINE_CAP,
820 SET_LINE_JOIN,
821 SET_LINE_WIDTH,
822 SET_SOURCE_RGBA,
823 SHOW_TEXT,
824 STROKE,
825 STROKE_PRESERVE,
829 * Text placement mode for rel_move_for()
831 enum ref_point {
844 * One single element of a drawing
846 struct draw_op {
847 struct draw_op *next;
848 struct draw_op *prev;
849 unsigned int id;
850 enum cairo_fn op;
851 void *op_args;
855 * Argument sets for the various drawing operations
857 struct arc_args {
858 double x;
859 double y;
860 double radius;
861 double angle1;
862 double angle2;
865 struct curve_to_args {
866 double x1;
867 double y1;
868 double x2;
869 double y2;
870 double x3;
871 double y3;
874 struct move_to_args {
875 double x;
876 double y;
879 struct rectangle_args {
880 double x;
881 double y;
882 double width;
883 double height;
886 struct rel_move_for_args {
887 enum ref_point ref;
888 int len;
889 char text[];
892 struct set_dash_args {
893 int num_dashes;
894 double dashes[];
897 struct set_font_face_args {
898 cairo_font_slant_t slant;
899 cairo_font_weight_t weight;
900 char family[];
903 struct set_font_size_args {
904 double size;
907 struct set_line_cap_args {
908 cairo_line_cap_t line_cap;
911 struct set_line_join_args {
912 cairo_line_join_t line_join;
915 struct set_line_width_args {
916 double width;
919 struct set_source_rgba_args {
920 GdkRGBA color;
923 struct show_text_args {
924 int len;
925 char text[];
928 static void
929 draw(cairo_t *cr, enum cairo_fn op, void *op_args)
931 switch (op) {
932 case LINE_TO: {
933 struct move_to_args *args = op_args;
935 cairo_line_to(cr, args->x, args->y);
936 break;
938 case REL_LINE_TO: {
939 struct move_to_args *args = op_args;
941 cairo_rel_line_to(cr, args->x, args->y);
942 break;
944 case MOVE_TO: {
945 struct move_to_args *args = op_args;
947 cairo_move_to(cr, args->x, args->y);
948 break;
950 case REL_MOVE_TO: {
951 struct move_to_args *args = op_args;
953 cairo_rel_move_to(cr, args->x, args->y);
954 break;
956 case ARC: {
957 struct arc_args *args = op_args;
959 cairo_arc(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
960 break;
962 case ARC_NEGATIVE: {
963 struct arc_args *args = op_args;
965 cairo_arc_negative(cr, args->x, args->y, args->radius, args->angle1, args->angle2);
966 break;
968 case CURVE_TO: {
969 struct curve_to_args *args = op_args;
971 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
972 break;
974 case REL_CURVE_TO: {
975 struct curve_to_args *args = op_args;
977 cairo_curve_to(cr, args->x1, args->y1, args->x2, args->y2, args->x3, args->y3);
978 break;
980 case RECTANGLE: {
981 struct rectangle_args *args = op_args;
983 cairo_rectangle(cr, args->x, args->y, args->width, args->height);
984 break;
986 case CLOSE_PATH:
987 cairo_close_path(cr);
988 break;
989 case SHOW_TEXT: {
990 struct show_text_args *args = op_args;
992 cairo_show_text(cr, args->text);
993 break;
995 case REL_MOVE_FOR: {
996 struct rel_move_for_args *args = op_args;
997 cairo_text_extents_t e;
998 double dx = 0.0, dy = 0.0;
1000 cairo_text_extents(cr, args->text, &e);
1001 switch (args->ref) {
1002 case C: dx = -e.width / 2; dy = e.height / 2; break;
1003 case E: dx = -e.width; dy = e.height / 2; break;
1004 case N: dx = -e.width / 2; dy = e.height; break;
1005 case NE: dx = -e.width; dy = e.height; break;
1006 case NW: dy = e.height; break;
1007 case S: dx = -e.width / 2; break;
1008 case SE: dx = -e.width; break;
1009 case SW: break;
1010 case W: dy = e.height / 2; break;
1011 default: ABORT; break;
1013 cairo_rel_move_to(cr, dx, dy);
1014 break;
1016 case STROKE:
1017 cairo_stroke(cr);
1018 break;
1019 case STROKE_PRESERVE:
1020 cairo_stroke_preserve(cr);
1021 break;
1022 case FILL:
1023 cairo_fill(cr);
1024 break;
1025 case FILL_PRESERVE:
1026 cairo_fill_preserve(cr);
1027 break;
1028 case SET_DASH: {
1029 struct set_dash_args *args = op_args;
1031 cairo_set_dash(cr, args->dashes, args->num_dashes, 0);
1032 break;
1034 case SET_FONT_FACE: {
1035 struct set_font_face_args *args = op_args;
1037 cairo_select_font_face(cr, args->family, args->slant, args->weight);
1038 break;
1040 case SET_FONT_SIZE: {
1041 struct set_font_size_args *args = op_args;
1043 cairo_set_font_size(cr, args->size);
1044 break;
1046 case SET_LINE_CAP: {
1047 struct set_line_cap_args *args = op_args;
1049 cairo_set_line_cap(cr, args->line_cap);
1050 break;
1052 case SET_LINE_JOIN: {
1053 struct set_line_join_args *args = op_args;
1055 cairo_set_line_join(cr, args->line_join);
1056 break;
1058 case SET_LINE_WIDTH: {
1059 struct set_line_width_args *args = op_args;
1061 cairo_set_line_width(cr, args->width);
1062 break;
1064 case SET_SOURCE_RGBA: {
1065 struct set_source_rgba_args *args = op_args;
1067 gdk_cairo_set_source_rgba(cr, &args->color);
1068 break;
1070 default:
1071 ABORT;
1072 break;
1077 * Callback that draws on a GtkDrawingArea
1079 static gboolean
1080 cb_draw(GtkWidget *widget, cairo_t *cr, gpointer data)
1082 struct draw_op *op;
1084 (void) data;
1085 for (op = g_object_get_data(G_OBJECT(widget), "draw_ops");
1086 op != NULL;
1087 op = op->next)
1088 draw(cr, op->op, op->op_args);
1089 return FALSE;
1093 * ============================================================
1094 * Manipulating the GUI
1095 * ============================================================
1098 static void
1099 update_button(GObject *obj, const char *action,
1100 const char *data, const char *whole_msg, GType type)
1102 if (eql(action, "set_label"))
1103 gtk_button_set_label(GTK_BUTTON(obj), data);
1104 else
1105 ign_cmd(type, whole_msg);
1108 static void
1109 update_calendar(GObject *obj, const char *action,
1110 const char *data, const char *whole_msg, GType type)
1112 GtkCalendar *calendar = GTK_CALENDAR(obj);
1113 int year = 0, month = 0, day = 0;
1115 if (eql(action, "select_date")) {
1116 sscanf(data, "%d-%d-%d", &year, &month, &day);
1117 if (month > -1 && month <= 11 && day > 0 && day <= 31) {
1118 gtk_calendar_select_month(calendar, --month, year);
1119 gtk_calendar_select_day(calendar, day);
1120 } else
1121 ign_cmd(type, whole_msg);
1122 } else if (eql(action, "mark_day")) {
1123 day = strtol(data, NULL, 10);
1124 if (day > 0 && day <= 31)
1125 gtk_calendar_mark_day(calendar, day);
1126 else
1127 ign_cmd(type, whole_msg);
1128 } else if (eql(action, "clear_marks"))
1129 gtk_calendar_clear_marks(calendar);
1130 else
1131 ign_cmd(type, whole_msg);
1135 * Common actions for various kinds of window. Return false if
1136 * command is ignored
1138 static bool
1139 update_class_window(GObject *obj, const char *action,
1140 const char *data, const char *whole_msg, GType type)
1142 GtkWindow *window = GTK_WINDOW(obj);
1143 int x, y;
1144 bool result = true;
1146 if (eql(action, "set_title"))
1147 gtk_window_set_title(window, data);
1148 else if (eql(action, "fullscreen"))
1149 gtk_window_fullscreen(window);
1150 else if (eql(action, "unfullscreen"))
1151 gtk_window_unfullscreen(window);
1152 else if (eql(action, "resize")) {
1153 if (sscanf(data, "%d %d", &x, &y) != 2)
1154 gtk_window_get_default_size(window, &x, &y);
1155 gtk_window_resize(window, x, y);
1156 } else if (eql(action, "move")) {
1157 if (sscanf(data, "%d %d", &x, &y) == 2)
1158 gtk_window_move(window, x, y);
1159 else
1160 ign_cmd(type, whole_msg);
1161 } else
1162 result = false;
1163 return result;
1166 static void
1167 update_color_button(GObject *obj, const char *action,
1168 const char *data, const char *whole_msg, GType type)
1170 GdkRGBA color;
1172 if (eql(action, "set_color")) {
1173 gdk_rgba_parse(&color, data);
1174 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(obj), &color);
1175 } else
1176 ign_cmd(type, whole_msg);
1179 static void
1180 update_combo_box_text(GObject *obj, const char *action,
1181 const char *data, const char *whole_msg, GType type)
1183 GtkComboBoxText *combobox = GTK_COMBO_BOX_TEXT(obj);
1184 char data1[strlen(data) + 1];
1186 strcpy(data1, data);
1187 if (eql(action, "prepend_text"))
1188 gtk_combo_box_text_prepend_text(combobox, data1);
1189 else if (eql(action, "append_text"))
1190 gtk_combo_box_text_append_text(combobox, data1);
1191 else if (eql(action, "remove"))
1192 gtk_combo_box_text_remove(combobox, strtol(data1, NULL, 10));
1193 else if (eql(action, "insert_text")) {
1194 char *position = strtok(data1, WHITESPACE);
1195 char *text = strtok(NULL, WHITESPACE);
1196 gtk_combo_box_text_insert_text(combobox,
1197 strtol(position, NULL, 10), text);
1198 } else
1199 ign_cmd(type, whole_msg);
1203 * Maintaining a list of drawing operations. It is the responsibility
1204 * of cb_draw() to actually draw them. update_drawing_area() needs a
1205 * few helper functions.
1209 * Fill structure *op with the drawing operation according to action
1210 * and with the appropriate set of arguments
1212 static bool
1213 set_draw_op(struct draw_op *op, const char *action, const char *data)
1215 if (eql(action, "line_to")) {
1216 struct move_to_args *args;
1218 if ((args = malloc(sizeof(*args))) == NULL)
1219 OOM_ABORT;
1220 op->op = LINE_TO;
1221 op->op_args = args;
1222 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1223 return false;
1224 } else if (eql(action, "rel_line_to")) {
1225 struct move_to_args *args;
1227 if ((args = malloc(sizeof(*args))) == NULL)
1228 OOM_ABORT;
1229 op->op = REL_LINE_TO;
1230 op->op_args = args;
1231 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1232 return false;
1233 } else if (eql(action, "move_to")) {
1234 struct move_to_args *args;
1236 if ((args = malloc(sizeof(*args))) == NULL)
1237 OOM_ABORT;
1238 op->op = MOVE_TO;
1239 op->op_args = args;
1240 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1241 return false;
1242 } else if (eql(action, "rel_move_to")) {
1243 struct move_to_args *args;
1245 if ((args = malloc(sizeof(*args))) == NULL)
1246 OOM_ABORT;
1247 op->op = REL_MOVE_TO;
1248 op->op_args = args;
1249 if (sscanf(data, "%u %lf %lf", &op->id, &args->x, &args->y) != 3)
1250 return false;
1251 } else if (eql(action, "arc")) {
1252 struct arc_args *args;
1253 double deg1, deg2;
1255 if ((args = malloc(sizeof(*args))) == NULL)
1256 OOM_ABORT;
1257 op->op = ARC;
1258 op->op_args = args;
1259 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id,
1260 &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
1261 return false;
1262 args->angle1 = deg1 * (M_PI / 180.);
1263 args->angle2 = deg2 * (M_PI / 180.);
1264 } else if (eql(action, "arc_negative")) {
1265 struct arc_args *args;
1266 double deg1, deg2;
1268 if ((args = malloc(sizeof(*args))) == NULL)
1269 OOM_ABORT;
1270 op->op = ARC_NEGATIVE;
1271 op->op_args = args;
1272 if (sscanf(data, "%u %lf %lf %lf %lf %lf", &op->id,
1273 &args->x, &args->y, &args->radius, &deg1, &deg2) != 6)
1274 return false;
1275 args->angle1 = deg1 * (M_PI / 180.);
1276 args->angle2 = deg2 * (M_PI / 180.);
1277 } else if (eql(action, "curve_to")) {
1278 struct curve_to_args *args;
1280 if ((args = malloc(sizeof(*args))) == NULL)
1281 OOM_ABORT;
1282 op->op = CURVE_TO;
1283 op->op_args = args;
1284 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id,
1285 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
1286 return false;
1287 } else if (eql(action, "rel_curve_to")) {
1288 struct curve_to_args *args;
1290 if ((args = malloc(sizeof(*args))) == NULL)
1291 OOM_ABORT;
1292 op->op = REL_CURVE_TO;
1293 op->op_args = args;
1294 if (sscanf(data, "%u %lf %lf %lf %lf %lf %lf", &op->id,
1295 &args->x1, &args->y1, &args->x2, &args->y2, &args->x3, &args->y3) != 7)
1296 return false;
1297 } else if (eql(action, "rectangle")) {
1298 struct rectangle_args *args;
1300 if ((args = malloc(sizeof(*args))) == NULL)
1301 OOM_ABORT;
1302 op->op = RECTANGLE;
1303 op->op_args = args;
1304 if (sscanf(data, "%u %lf %lf %lf %lf", &op->id,
1305 &args->x, &args->y, &args->width, &args->height) != 5)
1306 return false;
1307 } else if (eql(action, "close_path")) {
1308 op->op = CLOSE_PATH;
1309 if (sscanf(data, "%u", &op->id) != 1)
1310 return false;
1311 op->op_args = NULL;
1312 } else if (eql(action, "show_text")) {
1313 struct show_text_args *args;
1314 int start, len;
1316 if (sscanf(data, "%u %n", &op->id, &start) < 1)
1317 return false;
1318 len = strlen(data + start) + 1;
1319 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1320 OOM_ABORT;
1321 op->op = SHOW_TEXT;
1322 op->op_args = args;
1323 args->len = len; /* not used */
1324 strncpy(args->text, (data + start), len);
1325 } else if (eql(action, "rel_move_for")) {
1326 struct rel_move_for_args *args;
1327 char ref_point[2 + 1];
1328 int start, len;
1330 if (sscanf(data, "%u %2s %n", &op->id, ref_point, &start) < 2)
1331 return false;
1332 len = strlen(data + start) + 1;
1333 if ((args = malloc(sizeof(*args) + len * sizeof(args->text[0]))) == NULL)
1334 OOM_ABORT;
1335 if (eql(ref_point, "c"))
1336 args->ref = C;
1337 else if (eql(ref_point, "e"))
1338 args->ref = E;
1339 else if (eql(ref_point, "n"))
1340 args->ref = N;
1341 else if (eql(ref_point, "ne"))
1342 args->ref = NE;
1343 else if (eql(ref_point, "nw"))
1344 args->ref = NW;
1345 else if (eql(ref_point, "s"))
1346 args->ref = S;
1347 else if (eql(ref_point, "se"))
1348 args->ref = SE;
1349 else if (eql(ref_point, "sw"))
1350 args->ref = SW;
1351 else if (eql(ref_point, "w"))
1352 args->ref = W;
1353 else
1354 return false;
1355 op->op = REL_MOVE_FOR;
1356 op->op_args = args;
1357 args->len = len; /* not used */
1358 strncpy(args->text, (data + start), len);
1359 } else if (eql(action, "stroke")) {
1360 op->op = STROKE;
1361 if (sscanf(data, "%u", &op->id) != 1)
1362 return false;
1363 op->op_args = NULL;
1364 } else if (eql(action, "stroke_preserve")) {
1365 op->op = STROKE_PRESERVE;
1366 if (sscanf(data, "%u", &op->id) != 1)
1367 return false;
1368 op->op_args = NULL;
1369 } else if (eql(action, "fill")) {
1370 op->op = FILL;
1371 if (sscanf(data, "%u", &op->id) != 1)
1372 return false;
1373 op->op_args = NULL;
1374 } else if (eql(action, "fill_preserve")) {
1375 op->op = FILL_PRESERVE;
1376 if (sscanf(data, "%u", &op->id) != 1)
1377 return false;
1378 op->op_args = NULL;
1379 } else if (eql(action, "set_dash")) {
1380 struct set_dash_args *args;
1381 int d_start, n, i;
1382 char data1[strlen(data) + 1];
1383 char *next, *end;
1385 strcpy(data1, data);
1386 if (sscanf(data1, "%u %n", &op->id, &d_start) < 1)
1387 return false;
1388 next = end = data1 + d_start;
1389 n = -1;
1390 do {
1391 n++;
1392 next = end;
1393 strtod(next, &end);
1394 } while (next != end);
1395 if ((args = malloc(sizeof(*args) + n * sizeof(args->dashes[0]))) == NULL)
1396 OOM_ABORT;
1397 op->op = SET_DASH;
1398 op->op_args = args;
1399 args->num_dashes = n;
1400 for (i = 0, next = data1 + d_start; i < n; i++, next = end) {
1401 args->dashes[i] = strtod(next, &end);
1403 } else if (eql(action, "set_font_face")) {
1404 struct set_font_face_args *args;
1405 int family_start, family_len;
1406 char slant[7 + 1];
1407 char weight[6 + 1];
1409 if (sscanf(data, "%u %s %s %n%*s", &op->id, slant, weight, &family_start) != 3)
1410 return false;
1411 family_len = strlen(data + family_start) + 1;
1412 if ((args = malloc(sizeof(*args) + family_len * sizeof(args->family[0]))) == NULL)
1413 OOM_ABORT;
1414 op->op = SET_FONT_FACE;
1415 op->op_args = args;
1416 strncpy(args->family, data + family_start, family_len);
1417 if (eql(slant, "normal"))
1418 args->slant = CAIRO_FONT_SLANT_NORMAL;
1419 else if (eql(slant, "italic"))
1420 args->slant = CAIRO_FONT_SLANT_ITALIC;
1421 else if (eql(slant, "oblique"))
1422 args->slant = CAIRO_FONT_SLANT_OBLIQUE;
1423 else
1424 return false;
1425 if (eql(weight, "normal"))
1426 args->weight = CAIRO_FONT_WEIGHT_NORMAL;
1427 else if (eql(weight, "bold"))
1428 args->weight = CAIRO_FONT_WEIGHT_BOLD;
1429 else
1430 return false;
1431 } else if (eql(action, "set_font_size")) {
1432 struct set_font_size_args *args;
1434 if ((args = malloc(sizeof(*args))) == NULL)
1435 OOM_ABORT;
1436 op->op = SET_FONT_SIZE;
1437 op->op_args = args;
1438 if (sscanf(data, "%u %lf", &op->id, &args->size) != 2)
1439 return false;
1440 } else if (eql(action, "set_line_cap")) {
1441 struct set_line_cap_args *args;
1442 char str[6 + 1];
1444 if ((args = malloc(sizeof(*args))) == NULL)
1445 OOM_ABORT;
1446 op->op = SET_LINE_CAP;
1447 op->op_args = args;
1448 if (sscanf(data, "%u %6s", &op->id, str) != 2)
1449 return false;
1450 if (eql(str, "butt"))
1451 args->line_cap = CAIRO_LINE_CAP_BUTT;
1452 else if (eql(str, "round"))
1453 args->line_cap = CAIRO_LINE_CAP_ROUND;
1454 else if (eql(str, "square"))
1455 args->line_cap = CAIRO_LINE_CAP_SQUARE;
1456 else
1457 return false;
1458 } else if (eql(action, "set_line_join")) {
1459 struct set_line_join_args *args;
1460 char str[5 + 1];
1462 if ((args = malloc(sizeof(*args))) == NULL)
1463 OOM_ABORT;
1464 op->op = SET_LINE_JOIN;
1465 op->op_args = args;
1466 if (sscanf(data, "%u %5s", &op->id, str) != 2)
1467 return false;
1468 if (eql(str, "miter"))
1469 args->line_join = CAIRO_LINE_JOIN_MITER;
1470 else if (eql(str, "round"))
1471 args->line_join = CAIRO_LINE_JOIN_ROUND;
1472 else if (eql(str, "bevel"))
1473 args->line_join = CAIRO_LINE_JOIN_BEVEL;
1474 else
1475 return false;
1476 } else if (eql(action, "set_line_width")) {
1477 struct set_line_width_args *args;
1479 if ((args = malloc(sizeof(*args))) == NULL)
1480 OOM_ABORT;
1481 op->op = SET_LINE_WIDTH;
1482 op->op_args = args;
1483 if (sscanf(data, "%u %lf", &op->id, &args->width) != 2)
1484 return false;
1485 } else if (eql(action, "set_source_rgba")) {
1486 struct set_source_rgba_args *args;
1487 int c_start;
1489 if ((args = malloc(sizeof(*args))) == NULL)
1490 OOM_ABORT;
1491 op->op = SET_SOURCE_RGBA;
1492 op->op_args = args;
1493 if ((sscanf(data, "%u %n", &op->id, &c_start) < 1))
1494 return false;;
1495 gdk_rgba_parse(&args->color, data + c_start);
1496 } else
1497 return false;
1498 return true;
1502 * Append another element to widget's "draw_ops" list
1504 static bool
1505 ins_draw_op(GObject *widget, const char *action, const char *data)
1507 struct draw_op *op, *draw_ops, *last_op;
1509 if ((op = malloc(sizeof(*op))) == NULL)
1510 OOM_ABORT;
1511 op->op_args = NULL;
1512 op->next = NULL;
1513 if (!set_draw_op(op, action, data)) {
1514 free(op->op_args);
1515 free(op);
1516 return false;
1518 if ((draw_ops = g_object_get_data(widget, "draw_ops")) == NULL)
1519 g_object_set_data(widget, "draw_ops", op);
1520 else {
1521 for (last_op = draw_ops;
1522 last_op->next != NULL;
1523 last_op = last_op->next);
1524 last_op->next = op;
1526 return true;
1530 * Remove all elements with the given id from widget's "draw_ops" list
1532 static bool
1533 rem_draw_op(GObject *widget, const char *data)
1535 struct draw_op *op, *next_op, *prev_op = NULL;
1536 unsigned int id;
1538 if (sscanf(data, "%u", &id) != 1)
1539 return false;
1540 op = g_object_get_data(widget, "draw_ops");
1541 while (op != NULL) {
1542 next_op = op->next;
1543 if (op->id == id) {
1544 if (prev_op == NULL) /* list head */
1545 g_object_set_data(widget, "draw_ops", op->next);
1546 else
1547 prev_op->next = op->next;
1548 free(op->op_args);
1549 free(op);
1550 } else
1551 prev_op = op;
1552 op = next_op;
1554 return true;
1557 static gboolean
1558 refresh_widget(GtkWidget *widget)
1560 gint width = gtk_widget_get_allocated_width(widget);
1561 gint height = gtk_widget_get_allocated_height(widget);
1563 gtk_widget_queue_draw_area(widget, 0, 0, width, height);
1564 return G_SOURCE_REMOVE;
1567 static void
1568 update_drawing_area(GObject *obj, const char *action,
1569 const char *data, const char *whole_msg, GType type)
1571 if (eql(action, "remove")) {
1572 if (!rem_draw_op(obj, data))
1573 ign_cmd(type, whole_msg);
1574 } else if (ins_draw_op(obj, action, data));
1575 else
1576 ign_cmd(type, whole_msg);
1577 if (eql(action, "stroke") ||
1578 eql(action, "fill") ||
1579 eql(action, "show_text") ||
1580 eql(action, "remove"))
1581 gdk_threads_add_idle_full(G_PRIORITY_LOW,
1582 (GSourceFunc) refresh_widget,
1583 GTK_WIDGET(obj), NULL);
1586 static void
1587 update_entry(GObject *obj, const char *action,
1588 const char *data, const char *whole_msg, GType type)
1590 GtkEntry *entry = GTK_ENTRY(obj);
1592 if (eql(action, "set_text"))
1593 gtk_entry_set_text(entry, data);
1594 else if (eql(action, "set_placeholder_text"))
1595 gtk_entry_set_placeholder_text(entry, data);
1596 else
1597 ign_cmd(type, whole_msg);
1600 static void
1601 update_expander(GObject *obj, const char *action,
1602 const char *data, const char *whole_msg, GType type)
1604 GtkExpander *expander = GTK_EXPANDER(obj);
1606 if (eql(action, "set_expanded"))
1607 gtk_expander_set_expanded(expander, strtol(data, NULL, 10));
1608 else if (eql(action, "set_label"))
1609 gtk_expander_set_label(expander, data);
1610 else
1611 ign_cmd(type, whole_msg);
1614 static void
1615 update_file_chooser_button(GObject *obj, const char *action,
1616 const char *data, const char *whole_msg, GType type)
1618 if (eql(action, "set_filename"))
1619 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(obj), data);
1620 else
1621 ign_cmd(type, whole_msg);
1624 static void
1625 update_file_chooser_dialog(GObject *obj, const char *action,
1626 const char *data, const char *whole_msg, GType type)
1628 GtkFileChooser *chooser = GTK_FILE_CHOOSER(obj);
1630 if (eql(action, "set_filename"))
1631 gtk_file_chooser_set_filename(chooser, data);
1632 else if (eql(action, "set_current_name"))
1633 gtk_file_chooser_set_current_name(chooser, data);
1634 else if (update_class_window(obj, action, data, whole_msg, type));
1635 else
1636 ign_cmd(type, whole_msg);
1639 static void
1640 update_focus(GObject *obj, const char *action,
1641 const char *data, const char *whole_msg, GType type)
1643 (void) action;
1644 (void) data;
1645 if (gtk_widget_get_can_focus(GTK_WIDGET(obj)))
1646 gtk_widget_grab_focus(GTK_WIDGET(obj));
1647 else
1648 ign_cmd(type, whole_msg);
1651 static void
1652 update_font_button(GObject *obj, const char *action,
1653 const char *data, const char *whole_msg, GType type)
1655 GtkFontButton *font_button = GTK_FONT_BUTTON(obj);
1657 if (eql(action, "set_font_name"))
1658 gtk_font_button_set_font_name(font_button, data);
1659 else
1660 ign_cmd(type, whole_msg);
1663 static void
1664 update_frame(GObject *obj, const char *action,
1665 const char *data, const char *whole_msg, GType type)
1667 if (eql(action, "set_label"))
1668 gtk_frame_set_label(GTK_FRAME(obj), data);
1669 else
1670 ign_cmd(type, whole_msg);
1673 static void
1674 update_image(GObject *obj, const char *action,
1675 const char *data, const char *whole_msg, GType type)
1677 GtkImage *image = GTK_IMAGE(obj);
1678 GtkIconSize size;
1680 gtk_image_get_icon_name(image, NULL, &size);
1681 if (eql(action, "set_from_file"))
1682 gtk_image_set_from_file(image, data);
1683 else if (eql(action, "set_from_icon_name"))
1684 gtk_image_set_from_icon_name(image, data, size);
1685 else
1686 ign_cmd(type, whole_msg);
1689 static void
1690 update_label(GObject *obj, const char *action,
1691 const char *data, const char *whole_msg, GType type)
1693 if (eql(action, "set_text"))
1694 gtk_label_set_text(GTK_LABEL(obj), data);
1695 else
1696 ign_cmd(type, whole_msg);
1699 static void
1700 update_notebook(GObject *obj, const char *action,
1701 const char *data, const char *whole_msg, GType type)
1703 if (eql(action, "set_current_page"))
1704 gtk_notebook_set_current_page(GTK_NOTEBOOK(obj), strtol(data, NULL, 10));
1705 else
1706 ign_cmd(type, whole_msg);
1709 static void
1710 update_nothing(GObject *obj, const char *action,
1711 const char *data, const char *whole_msg, GType type)
1713 (void) obj;
1714 (void) action;
1715 (void) data;
1716 (void) whole_msg;
1717 (void) type;
1720 static void
1721 update_print_dialog(GObject *obj, const char *action,
1722 const char *data, const char *whole_msg, GType type)
1724 GtkPrintUnixDialog *dialog = GTK_PRINT_UNIX_DIALOG(obj);
1725 gint response_id;
1726 GtkPrinter *printer;
1727 GtkPrintSettings *settings;
1728 GtkPageSetup *page_setup;
1729 GtkPrintJob *job;
1731 if (eql(action, "print")) {
1732 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
1733 switch (response_id) {
1734 case GTK_RESPONSE_OK:
1735 printer = gtk_print_unix_dialog_get_selected_printer(dialog);
1736 settings = gtk_print_unix_dialog_get_settings(dialog);
1737 page_setup = gtk_print_unix_dialog_get_page_setup(dialog);
1738 job = gtk_print_job_new(data, printer, settings, page_setup);
1739 if (gtk_print_job_set_source_file(job, data, NULL))
1740 gtk_print_job_send(job, NULL, NULL, NULL);
1741 else
1742 ign_cmd(type, whole_msg);
1743 g_clear_object(&settings);
1744 g_clear_object(&job);
1745 break;
1746 case GTK_RESPONSE_CANCEL:
1747 case GTK_RESPONSE_DELETE_EVENT:
1748 break;
1749 default:
1750 fprintf(stderr, "%s sent an unexpected response id (%d)\n",
1751 widget_name(GTK_BUILDABLE(dialog)), response_id);
1752 break;
1754 gtk_widget_hide(GTK_WIDGET(dialog));
1755 } else
1756 ign_cmd(type, whole_msg);
1759 static void
1760 update_progress_bar(GObject *obj, const char *action,
1761 const char *data, const char *whole_msg, GType type)
1763 GtkProgressBar *progressbar = GTK_PROGRESS_BAR(obj);
1765 if (eql(action, "set_text"))
1766 gtk_progress_bar_set_text(progressbar, *data == '\0' ? NULL : data);
1767 else if (eql(action, "set_fraction"))
1768 gtk_progress_bar_set_fraction(progressbar, strtod(data, NULL));
1769 else
1770 ign_cmd(type, whole_msg);
1773 static void
1774 update_scale(GObject *obj, const char *action,
1775 const char *data, const char *whole_msg, GType type)
1777 if (eql(action, "set_value"))
1778 gtk_range_set_value(GTK_RANGE(obj), strtod(data, NULL));
1779 else
1780 ign_cmd(type, whole_msg);
1783 static void
1784 update_scrolled_window(GObject *obj, const char *action,
1785 const char *data, const char *whole_msg, GType type)
1787 GtkScrolledWindow *window = GTK_SCROLLED_WINDOW(obj);
1788 GtkAdjustment *hadj = gtk_scrolled_window_get_hadjustment(window);
1789 GtkAdjustment *vadj = gtk_scrolled_window_get_vadjustment(window);
1790 double d0, d1;
1792 if (eql(action, "hscroll") && sscanf(data, "%lf", &d0) == 1)
1793 gtk_adjustment_set_value(hadj, d0);
1794 else if (eql(action, "vscroll") && sscanf(data, "%lf", &d0) == 1)
1795 gtk_adjustment_set_value(vadj, d0);
1796 else if (eql(action, "hscroll_to_range") &&
1797 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1798 gtk_adjustment_clamp_page(hadj, d0, d1);
1799 else if (eql(action, "vscroll_to_range") &&
1800 sscanf(data, "%lf %lf", &d0, &d1) == 2)
1801 gtk_adjustment_clamp_page(vadj, d0, d1);
1802 else
1803 ign_cmd(type, whole_msg);
1806 static void
1807 update_sensitivity(GObject *obj, const char *action,
1808 const char *data, const char *whole_msg, GType type)
1810 (void) action;
1811 (void) whole_msg;
1812 (void) type;
1813 gtk_widget_set_sensitive(GTK_WIDGET(obj), strtol(data, NULL, 10));
1816 static void
1817 update_size_request(GObject *obj, const char *action,
1818 const char *data, const char *whole_msg, GType type)
1820 int x, y;
1822 (void) action;
1823 (void) whole_msg;
1824 (void) type;
1825 if (sscanf(data, "%d %d", &x, &y) == 2)
1826 gtk_widget_set_size_request(GTK_WIDGET(obj), x, y);
1827 else
1828 gtk_widget_set_size_request(GTK_WIDGET(obj), -1, -1);
1831 static void
1832 update_socket(GObject *obj, const char *action,
1833 const char *data, const char *whole_msg, GType type)
1835 GtkSocket *socket = GTK_SOCKET(obj);
1836 Window id;
1837 char str[BUFLEN];
1839 (void) data;
1840 if (eql(action, "id")) {
1841 id = gtk_socket_get_id(socket);
1842 snprintf(str, BUFLEN, "%lu", id);
1843 send_msg(GTK_BUILDABLE(socket), "id", str, NULL);
1844 } else
1845 ign_cmd(type, whole_msg);
1848 static void
1849 update_spinner(GObject *obj, const char *action,
1850 const char *data, const char *whole_msg, GType type)
1852 GtkSpinner *spinner = GTK_SPINNER(obj);
1854 (void) data;
1855 if (eql(action, "start"))
1856 gtk_spinner_start(spinner);
1857 else if (eql(action, "stop"))
1858 gtk_spinner_stop(spinner);
1859 else
1860 ign_cmd(type, whole_msg);
1863 static void
1864 update_statusbar(GObject *obj, const char *action,
1865 const char *data, const char *whole_msg, GType type)
1867 GtkStatusbar *statusbar = GTK_STATUSBAR(obj);
1868 char *ctx_msg, *msg;
1869 size_t ctx_len;
1871 if ((ctx_msg = malloc(strlen(data) + 1)) == NULL)
1872 OOM_ABORT;
1873 strcpy(ctx_msg, data);
1874 ctx_len = strcspn(ctx_msg, WHITESPACE);
1875 if (ctx_len > 0) {
1876 ctx_msg[ctx_len] = '\0';
1877 msg = ctx_msg + ctx_len + 1;
1878 } else
1879 msg = ctx_msg + strlen(ctx_msg);
1880 if (eql(action, "push"))
1881 gtk_statusbar_push(statusbar,
1882 gtk_statusbar_get_context_id(statusbar, "0"),
1883 data);
1884 else if (eql(action, "push_id"))
1885 gtk_statusbar_push(statusbar,
1886 gtk_statusbar_get_context_id(statusbar, ctx_msg),
1887 msg);
1888 else if (eql(action, "pop"))
1889 gtk_statusbar_pop(statusbar,
1890 gtk_statusbar_get_context_id(statusbar, "0"));
1891 else if (eql(action, "pop_id"))
1892 gtk_statusbar_pop(statusbar,
1893 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1894 else if (eql(action, "remove_all"))
1895 gtk_statusbar_remove_all(statusbar,
1896 gtk_statusbar_get_context_id(statusbar, "0"));
1897 else if (eql(action, "remove_all_id"))
1898 gtk_statusbar_remove_all(statusbar,
1899 gtk_statusbar_get_context_id(statusbar, ctx_msg));
1900 else
1901 ign_cmd(type, whole_msg);
1902 free(ctx_msg);
1905 static void
1906 update_switch(GObject *obj, const char *action,
1907 const char *data, const char *whole_msg, GType type)
1909 if (eql(action, "set_active"))
1910 gtk_switch_set_active(GTK_SWITCH(obj), strtol(data, NULL, 10));
1911 else
1912 ign_cmd(type, whole_msg);
1915 static void
1916 update_text_view(GObject *obj, const char *action,
1917 const char *data, const char *whole_msg, GType type)
1919 GtkTextView *view = GTK_TEXT_VIEW(obj);
1920 GtkTextBuffer *textbuf = gtk_text_view_get_buffer(view);
1921 GtkTextIter a, b;
1923 if (eql(action, "set_text"))
1924 gtk_text_buffer_set_text(textbuf, data, -1);
1925 else if (eql(action, "delete")) {
1926 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1927 gtk_text_buffer_delete(textbuf, &a, &b);
1928 } else if (eql(action, "insert_at_cursor"))
1929 gtk_text_buffer_insert_at_cursor(textbuf, data, -1);
1930 else if (eql(action, "place_cursor")) {
1931 if (eql(data, "end"))
1932 gtk_text_buffer_get_end_iter(textbuf, &a);
1933 else /* numeric offset */
1934 gtk_text_buffer_get_iter_at_offset(textbuf, &a,
1935 strtol(data, NULL, 10));
1936 gtk_text_buffer_place_cursor(textbuf, &a);
1937 } else if (eql(action, "place_cursor_at_line")) {
1938 gtk_text_buffer_get_iter_at_line(textbuf, &a, strtol(data, NULL, 10));
1939 gtk_text_buffer_place_cursor(textbuf, &a);
1940 } else if (eql(action, "scroll_to_cursor"))
1941 gtk_text_view_scroll_to_mark(view, gtk_text_buffer_get_insert(textbuf),
1942 0., 0, 0., 0.);
1943 else if (eql(action, "save") && data != NULL &&
1944 (save = fopen(data, "w")) != NULL) {
1945 gtk_text_buffer_get_bounds(textbuf, &a, &b);
1946 save_msg(GTK_BUILDABLE(view), "insert_at_cursor",
1947 gtk_text_buffer_get_text(textbuf, &a, &b, TRUE), NULL);
1948 fclose(save);
1949 } else
1950 ign_cmd(type, whole_msg);
1953 static void
1954 update_toggle_button(GObject *obj, const char *action,
1955 const char *data, const char *whole_msg, GType type)
1957 if (eql(action, "set_label"))
1958 gtk_button_set_label(GTK_BUTTON(obj), data);
1959 else if (eql(action, "set_active"))
1960 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(obj), strtol(data, NULL, 10));
1961 else
1962 ign_cmd(type, whole_msg);
1965 static void
1966 update_tooltip_text(GObject *obj, const char *action,
1967 const char *data, const char *whole_msg, GType type)
1969 (void) action;
1970 (void) whole_msg;
1971 (void) type;
1972 gtk_widget_set_tooltip_text(GTK_WIDGET(obj), data);
1976 * update_tree_view() needs a few helper functions
1980 * Check if s is a valid string representation of a GtkTreePath
1982 static bool
1983 is_path_string(char *s)
1985 return s != NULL &&
1986 strlen(s) == strspn(s, ":0123456789") &&
1987 strstr(s, "::") == NULL &&
1988 strcspn(s, ":") > 0;
1991 static void
1992 tree_model_insert_before(GtkTreeModel *model, GtkTreeIter *iter,
1993 GtkTreeIter *parent, GtkTreeIter *sibling)
1995 if (GTK_IS_TREE_STORE(model))
1996 gtk_tree_store_insert_before(GTK_TREE_STORE(model),
1997 iter, parent, sibling);
1998 else if (GTK_IS_LIST_STORE(model))
1999 gtk_list_store_insert_before(GTK_LIST_STORE(model),
2000 iter, sibling);
2001 else
2002 ABORT;
2005 static void
2006 tree_model_insert_after(GtkTreeModel *model, GtkTreeIter *iter,
2007 GtkTreeIter *parent, GtkTreeIter *sibling)
2009 if (GTK_IS_TREE_STORE(model))
2010 gtk_tree_store_insert_after(GTK_TREE_STORE(model),
2011 iter, parent, sibling);
2012 else if (GTK_IS_LIST_STORE(model))
2013 gtk_list_store_insert_after(GTK_LIST_STORE(model),
2014 iter, sibling);
2015 else
2016 ABORT;
2019 static void
2020 tree_model_move_before(GtkTreeModel *model, GtkTreeIter *iter,
2021 GtkTreeIter *position)
2023 if (GTK_IS_TREE_STORE(model))
2024 gtk_tree_store_move_before(GTK_TREE_STORE(model), iter, position);
2025 else if (GTK_IS_LIST_STORE(model))
2026 gtk_list_store_move_before(GTK_LIST_STORE(model), iter, position);
2027 else
2028 ABORT;
2031 static void
2032 tree_model_remove(GtkTreeModel *model, GtkTreeIter *iter)
2034 if (GTK_IS_TREE_STORE(model))
2035 gtk_tree_store_remove(GTK_TREE_STORE(model), iter);
2036 else if (GTK_IS_LIST_STORE(model))
2037 gtk_list_store_remove(GTK_LIST_STORE(model), iter);
2038 else
2039 ABORT;
2042 static void
2043 tree_model_clear(GtkTreeModel *model)
2045 if (GTK_IS_TREE_STORE(model))
2046 gtk_tree_store_clear(GTK_TREE_STORE(model));
2047 else if (GTK_IS_LIST_STORE(model))
2048 gtk_list_store_clear(GTK_LIST_STORE(model));
2049 else
2050 ABORT;
2053 static void
2054 tree_model_set(GtkTreeModel *model, GtkTreeIter *iter, ...)
2056 va_list ap;
2058 va_start(ap, iter);
2059 if (GTK_IS_TREE_STORE(model))
2060 gtk_tree_store_set_valist(GTK_TREE_STORE(model), iter, ap);
2061 else if (GTK_IS_LIST_STORE(model))
2062 gtk_list_store_set_valist(GTK_LIST_STORE(model), iter, ap);
2063 else
2064 ABORT;
2065 va_end(ap);
2069 * Create an empty row at path if it doesn't yet exist. Create older
2070 * siblings and parents as necessary.
2072 static void
2073 create_subtree(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter)
2075 GtkTreePath *path_1; /* path's predecessor */
2076 GtkTreeIter iter_1; /* iter's predecessor */
2078 if (gtk_tree_model_get_iter(model, iter, path))
2079 return;
2080 path_1 = gtk_tree_path_copy(path);
2081 if (gtk_tree_path_prev(path_1)) { /* need an older sibling */
2082 create_subtree(model, path_1, iter);
2083 iter_1 = *iter;
2084 tree_model_insert_after(model, iter, NULL, &iter_1);
2085 } else if (gtk_tree_path_up(path_1)) { /* need a parent */
2086 create_subtree(model, path_1, iter);
2087 if (gtk_tree_path_get_depth(path_1) == 0)
2088 /* first toplevel row */
2089 tree_model_insert_after(model, iter, NULL, NULL);
2090 else { /* first row in a lower level */
2091 iter_1 = *iter;
2092 tree_model_insert_after(model, iter, &iter_1, NULL);
2094 } /* neither prev nor up mean we're at the root of an empty tree */
2095 gtk_tree_path_free(path_1);
2098 static bool
2099 set_tree_view_cell(GtkTreeModel *model, GtkTreeIter *iter,
2100 const char *path_s, int col, const char *new_text)
2102 GType col_type = gtk_tree_model_get_column_type(model, col);
2103 long long int n;
2104 double d;
2105 GtkTreePath *path;
2106 char *endptr;
2107 bool ok = false;
2109 path = gtk_tree_path_new_from_string(path_s);
2110 create_subtree(model, path, iter);
2111 gtk_tree_path_free(path);
2112 switch (col_type) {
2113 case G_TYPE_BOOLEAN:
2114 case G_TYPE_INT:
2115 case G_TYPE_LONG:
2116 case G_TYPE_INT64:
2117 case G_TYPE_UINT:
2118 case G_TYPE_ULONG:
2119 case G_TYPE_UINT64:
2120 errno = 0;
2121 endptr = NULL;
2122 n = strtoll(new_text, &endptr, 10);
2123 if (!errno && endptr != new_text) {
2124 tree_model_set(model, iter, col, n, -1);
2125 ok = true;
2127 break;
2128 case G_TYPE_FLOAT:
2129 case G_TYPE_DOUBLE:
2130 errno = 0;
2131 endptr = NULL;
2132 d = strtod(new_text, &endptr);
2133 if (!errno && endptr != new_text) {
2134 tree_model_set(model, iter, col, d, -1);
2135 ok = true;
2137 break;
2138 case G_TYPE_STRING:
2139 tree_model_set(model, iter, col, new_text, -1);
2140 ok = true;
2141 break;
2142 default:
2143 fprintf(stderr, "column %d: %s not implemented\n",
2144 col, g_type_name(col_type));
2145 ok = true;
2146 break;
2148 return ok;
2151 static void
2152 tree_view_set_cursor(GtkTreeView *view, GtkTreePath *path, GtkTreeViewColumn *col)
2154 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2155 /* is just fine and this function need not exist. */
2156 if (path == NULL)
2157 path = gtk_tree_path_new();
2158 gtk_tree_view_set_cursor(view, path, col, false);
2161 static void
2162 update_tree_view(GObject *obj, const char *action,
2163 const char *data, const char *whole_msg, GType type)
2165 GtkTreeView *view = GTK_TREE_VIEW(obj);
2166 GtkTreeModel *model = gtk_tree_view_get_model(view);
2167 GtkTreeIter iter0, iter1;
2168 GtkTreePath *path = NULL;
2169 bool iter0_valid, iter1_valid;
2170 char *tokens, *arg0, *arg1, *arg2;
2171 int col = -1; /* invalid column number */
2173 if (!GTK_IS_LIST_STORE(model) && !GTK_IS_TREE_STORE(model))
2175 fprintf(stderr, "missing model/");
2176 ign_cmd(type, whole_msg);
2177 return;
2179 if ((tokens = malloc(strlen(data) + 1)) == NULL)
2180 OOM_ABORT;
2181 strcpy(tokens, data);
2182 arg0 = strtok(tokens, WHITESPACE);
2183 arg1 = strtok(NULL, WHITESPACE);
2184 arg2 = strtok(NULL, "");
2185 iter0_valid = is_path_string(arg0) &&
2186 gtk_tree_model_get_iter_from_string(model, &iter0, arg0);
2187 iter1_valid = is_path_string(arg1) &&
2188 gtk_tree_model_get_iter_from_string(model, &iter1, arg1);
2189 if (is_path_string(arg1))
2190 col = strtol(arg1, NULL, 10);
2191 if (eql(action, "set") &&
2192 col > -1 && col < gtk_tree_model_get_n_columns(model) &&
2193 is_path_string(arg0)) {
2194 if (set_tree_view_cell(model, &iter0, arg0, col, arg2) == false)
2195 ign_cmd(type, whole_msg);
2196 } else if (eql(action, "scroll") && iter0_valid && iter1_valid) {
2197 path = gtk_tree_path_new_from_string(arg0);
2198 gtk_tree_view_scroll_to_cell (view,
2199 path,
2200 gtk_tree_view_get_column(view, col),
2201 0, 0., 0.);
2202 } else if (eql(action, "expand") && iter0_valid) {
2203 path = gtk_tree_path_new_from_string(arg0);
2204 gtk_tree_view_expand_row(view, path, false);
2205 } else if (eql(action, "expand_all") && iter0_valid) {
2206 path = gtk_tree_path_new_from_string(arg0);
2207 gtk_tree_view_expand_row(view, path, true);
2208 } else if (eql(action, "expand_all") && arg0 == NULL)
2209 gtk_tree_view_expand_all(view);
2210 else if (eql(action, "collapse") && iter0_valid) {
2211 path = gtk_tree_path_new_from_string(arg0);
2212 gtk_tree_view_collapse_row(view, path);
2213 } else if (eql(action, "collapse") && arg0 == NULL)
2214 gtk_tree_view_collapse_all(view);
2215 else if (eql(action, "set_cursor") && iter0_valid) {
2216 path = gtk_tree_path_new_from_string(arg0);
2217 tree_view_set_cursor(view, path, NULL);
2218 } else if (eql(action, "set_cursor") && arg0 == NULL) {
2219 tree_view_set_cursor(view, NULL, NULL);
2220 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2221 } else if (eql(action, "insert_row") && eql(arg0, "end"))
2222 tree_model_insert_before(model, &iter1, NULL, NULL);
2223 else if (eql(action, "insert_row") && iter0_valid && eql(arg1, "as_child"))
2224 tree_model_insert_after(model, &iter1, &iter0, NULL);
2225 else if (eql(action, "insert_row") && iter0_valid)
2226 tree_model_insert_before(model, &iter1, NULL, &iter0);
2227 else if (eql(action, "move_row") && iter0_valid && eql(arg1, "end"))
2228 tree_model_move_before(model, &iter0, NULL);
2229 else if (eql(action, "move_row") && iter0_valid && iter1_valid)
2230 tree_model_move_before(model, &iter0, &iter1);
2231 else if (eql(action, "remove_row") && iter0_valid)
2232 tree_model_remove(model, &iter0);
2233 else if (eql(action, "clear") && arg0 == NULL) {
2234 tree_view_set_cursor(view, NULL, NULL);
2235 gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(view));
2236 tree_model_clear(model);
2237 } else if (eql(action, "save") && arg0 != NULL &&
2238 (save = fopen(arg0, "w")) != NULL) {
2239 gtk_tree_model_foreach(model, (GtkTreeModelForeachFunc) save_tree_row_msg, view);
2240 fclose(save);
2241 } else
2242 ign_cmd(type, whole_msg);
2243 free(tokens);
2244 gtk_tree_path_free(path);
2247 static void
2248 update_visibility(GObject *obj, const char *action,
2249 const char *data, const char *whole_msg, GType type)
2251 (void) action;
2252 (void) whole_msg;
2253 (void) type;
2254 gtk_widget_set_visible(GTK_WIDGET(obj), strtol(data, NULL, 10));
2258 * Change the style of the widget passed
2260 static void
2261 update_widget_style(GObject *obj, const char *name,
2262 const char *data, const char *whole_msg, GType type)
2264 GtkStyleContext *context;
2265 GtkStyleProvider *style_provider;
2266 char *style_decl;
2267 const char *prefix = "* {", *suffix = "}";
2268 size_t sz;
2270 (void) name;
2271 (void) whole_msg;
2272 (void) type;
2273 style_provider = g_object_get_data(obj, "style_provider");
2274 sz = strlen(prefix) + strlen(suffix) + strlen(data) + 1;
2275 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2276 gtk_style_context_remove_provider(context, style_provider);
2277 if ((style_decl = malloc(sz)) == NULL)
2278 OOM_ABORT;
2279 strcpy(style_decl, prefix);
2280 strcat(style_decl, data);
2281 strcat(style_decl, suffix);
2282 gtk_style_context_add_provider(context, style_provider,
2283 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2284 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider),
2285 style_decl, -1, NULL);
2286 free(style_decl);
2289 static void
2290 update_window(GObject *obj, const char *action,
2291 const char *data, const char *whole_msg, GType type)
2293 if (!update_class_window(obj, action, data, whole_msg, type))
2294 ign_cmd(type, whole_msg);
2298 * Simulate user activity on various widgets
2300 static void
2301 fake_ui_activity(GObject *obj, const char *action,
2302 const char *data, const char *whole_msg, GType type)
2304 (void) action;
2305 (void) data;
2306 if (!GTK_IS_WIDGET(obj))
2307 ign_cmd(type, whole_msg);
2308 else if (GTK_IS_ENTRY(obj) || GTK_IS_SPIN_BUTTON(obj))
2309 cb_editable(GTK_BUILDABLE(obj), "text");
2310 else if (GTK_IS_SCALE(obj))
2311 cb_range(GTK_BUILDABLE(obj), "value");
2312 else if (GTK_IS_CALENDAR(obj))
2313 cb_calendar(GTK_BUILDABLE(obj), "clicked");
2314 else if (GTK_IS_FILE_CHOOSER_BUTTON(obj))
2315 cb_file_chooser_button(GTK_BUILDABLE(obj), "file");
2316 else if (!gtk_widget_activate(GTK_WIDGET(obj)))
2317 ign_cmd(type, whole_msg);
2321 * The final UI update
2323 static void
2324 main_quit(GObject *obj, const char *action,
2325 const char *data, const char *whole_msg, GType type)
2327 (void) obj;
2328 (void) action;
2329 (void) data;
2330 (void) whole_msg;
2331 (void) type;
2332 gtk_main_quit();
2336 * Don't update anything; just complain
2338 static void
2339 complain(GObject *obj, const char *action,
2340 const char *data, const char *whole_msg, GType type)
2342 (void) obj;
2343 (void) action;
2344 (void) data;
2345 ign_cmd(type, whole_msg);
2349 * Data to be passed to and from the GTK main loop
2351 struct ui_data {
2352 void (*fn)(GObject *, const char *action,
2353 const char *data, const char *msg, GType type);
2354 GObject *obj;
2355 char *action;
2356 char *data;
2357 char *msg;
2358 char *msg_tokens;
2359 GType type;
2363 * Parse command pointed to by ud, and act on ui accordingly; post
2364 * semaphore ud.msg_digested if done. Runs once per command inside
2365 * gtk_main_loop().
2367 static gboolean
2368 update_ui(struct ui_data *ud)
2370 (ud->fn)(ud->obj, ud->action, ud->data, ud->msg, ud->type);
2371 free(ud->msg_tokens);
2372 free(ud->msg);
2373 free(ud);
2374 return G_SOURCE_REMOVE;
2378 * Keep track of loading files to avoid recursive loading of the same
2379 * file. If filename = NULL, forget the most recently remembered file.
2381 static bool
2382 remember_loading_file(char *filename)
2384 static char *filenames[BUFLEN];
2385 static size_t latest = 0;
2386 size_t i;
2388 if (filename == NULL) { /* pop */
2389 if (latest < 1)
2390 ABORT;
2391 latest--;
2392 return false;
2393 } else { /* push */
2394 for (i = 1; i <= latest; i++)
2395 if (eql(filename, filenames[i]))
2396 return false;
2397 if (latest > BUFLEN -2)
2398 return false;
2399 filenames[++latest] = filename;
2400 return true;
2405 * Read lines from stream cmd and perform appropriate actions on the
2406 * GUI
2408 static void *
2409 digest_msg(FILE *cmd)
2411 FILE *load; /* restoring user data */
2412 char *name;
2413 static int recursion = -1; /* > 0 means this is a recursive call */
2415 recursion++;
2416 for (;;) {
2417 struct ui_data *ud;
2418 char first_char = '\0';
2419 size_t msg_size = 32;
2420 int name_start = 0, name_end = 0;
2421 int action_start = 0, action_end = 0;
2422 int data_start;
2424 if (feof(cmd))
2425 break;
2426 if ((ud = malloc(sizeof(*ud))) == NULL)
2427 OOM_ABORT;
2428 if ((ud->msg = malloc(msg_size)) == NULL)
2429 OOM_ABORT;
2430 ud->type = G_TYPE_INVALID;
2431 pthread_testcancel();
2432 if (recursion == 0)
2433 log_msg(NULL);
2434 read_buf(cmd, &ud->msg, &msg_size);
2435 if (recursion == 0)
2436 log_msg(ud->msg);
2437 data_start = strlen(ud->msg);
2438 if ((ud->msg_tokens = malloc(strlen(ud->msg) + 1)) == NULL)
2439 OOM_ABORT;
2440 strcpy(ud->msg_tokens, ud->msg);
2441 sscanf(ud->msg, " %c", &first_char);
2442 if (strlen(ud->msg) == 0 || first_char == '#') { /* comment */
2443 ud->fn = update_nothing;
2444 goto exec;
2446 sscanf(ud->msg_tokens,
2447 " %n%*[0-9a-zA-Z_]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2448 &name_start, &name_end, &action_start, &action_end, &data_start);
2449 ud->msg_tokens[name_end] = ud->msg_tokens[action_end] = '\0';
2450 name = ud->msg_tokens + name_start;
2451 ud->action = ud->msg_tokens + action_start;
2452 if (eql(ud->action, "main_quit")) {
2453 ud->fn = main_quit;
2454 goto exec;
2456 ud->data = ud->msg_tokens + data_start;
2457 if (eql(ud->action, "load") && strlen(ud->data) > 0 &&
2458 (load = fopen(ud->data, "r")) != NULL &&
2459 remember_loading_file(ud->data)) {
2460 digest_msg(load);
2461 fclose(load);
2462 remember_loading_file(NULL);
2463 ud->fn = update_nothing;
2464 goto exec;
2466 if ((ud->obj = (gtk_builder_get_object(builder, name))) == NULL) {
2467 ud->fn = complain;
2468 goto exec;
2470 ud->type = G_TYPE_FROM_INSTANCE(ud->obj);
2471 if (eql(ud->action, "force"))
2472 ud->fn = fake_ui_activity;
2473 else if (eql(ud->action, "set_sensitive"))
2474 ud->fn = update_sensitivity;
2475 else if (eql(ud->action, "set_visible"))
2476 ud->fn = update_visibility;
2477 else if (eql(ud->action, "set_size_request"))
2478 ud->fn = update_size_request;
2479 else if (eql(ud->action, "set_tooltip_text"))
2480 ud->fn = update_tooltip_text;
2481 else if (eql(ud->action, "grab_focus"))
2482 ud->fn = update_focus;
2483 else if (eql(ud->action, "style")) {
2484 ud->action = name;
2485 ud->fn = update_widget_style;
2486 } else if (ud->type == GTK_TYPE_DRAWING_AREA)
2487 ud->fn = update_drawing_area;
2488 else if (ud->type == GTK_TYPE_TREE_VIEW)
2489 ud->fn = update_tree_view;
2490 else if (ud->type == GTK_TYPE_COMBO_BOX_TEXT)
2491 ud->fn = update_combo_box_text;
2492 else if (ud->type == GTK_TYPE_LABEL)
2493 ud->fn = update_label;
2494 else if (ud->type == GTK_TYPE_IMAGE)
2495 ud->fn = update_image;
2496 else if (ud->type == GTK_TYPE_TEXT_VIEW)
2497 ud->fn = update_text_view;
2498 else if (ud->type == GTK_TYPE_NOTEBOOK)
2499 ud->fn = update_notebook;
2500 else if (ud->type == GTK_TYPE_EXPANDER)
2501 ud->fn = update_expander;
2502 else if (ud->type == GTK_TYPE_FRAME)
2503 ud->fn = update_frame;
2504 else if (ud->type == GTK_TYPE_SCROLLED_WINDOW)
2505 ud->fn = update_scrolled_window;
2506 else if (ud->type == GTK_TYPE_BUTTON)
2507 ud->fn = update_button;
2508 else if (ud->type == GTK_TYPE_FILE_CHOOSER_DIALOG)
2509 ud->fn = update_file_chooser_dialog;
2510 else if (ud->type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2511 ud->fn = update_file_chooser_button;
2512 else if (ud->type == GTK_TYPE_COLOR_BUTTON)
2513 ud->fn = update_color_button;
2514 else if (ud->type == GTK_TYPE_FONT_BUTTON)
2515 ud->fn = update_font_button;
2516 else if (ud->type == GTK_TYPE_PRINT_UNIX_DIALOG)
2517 ud->fn = update_print_dialog;
2518 else if (ud->type == GTK_TYPE_SWITCH)
2519 ud->fn = update_switch;
2520 else if (ud->type == GTK_TYPE_TOGGLE_BUTTON ||
2521 ud->type == GTK_TYPE_RADIO_BUTTON ||
2522 ud->type == GTK_TYPE_CHECK_BUTTON)
2523 ud->fn = update_toggle_button;
2524 else if (ud->type == GTK_TYPE_SPIN_BUTTON ||
2525 ud->type == GTK_TYPE_ENTRY)
2526 ud->fn = update_entry;
2527 else if (ud->type == GTK_TYPE_SCALE)
2528 ud->fn = update_scale;
2529 else if (ud->type == GTK_TYPE_PROGRESS_BAR)
2530 ud->fn = update_progress_bar;
2531 else if (ud->type == GTK_TYPE_SPINNER)
2532 ud->fn = update_spinner;
2533 else if (ud->type == GTK_TYPE_STATUSBAR)
2534 ud->fn = update_statusbar;
2535 else if (ud->type == GTK_TYPE_CALENDAR)
2536 ud->fn = update_calendar;
2537 else if (ud->type == GTK_TYPE_SOCKET)
2538 ud->fn = update_socket;
2539 else if (ud->type == GTK_TYPE_WINDOW ||
2540 ud->type == GTK_TYPE_DIALOG)
2541 ud->fn = update_window;
2542 else
2543 ud->fn = complain;
2544 exec:
2545 pthread_testcancel();
2546 gdk_threads_add_timeout(0, (GSourceFunc) update_ui, ud);
2548 recursion--;
2549 return NULL;
2553 * ============================================================
2554 * Initialization
2555 * ============================================================
2559 * Attach to renderer key "col_number". Associate "col_number" with
2560 * the corresponding column number in the underlying model.
2561 * Due to what looks like a gap in the GTK API, renderer id and column
2562 * number are taken directly from the XML .ui file.
2564 static bool
2565 tree_view_column_get_renderer_column(const char *ui_file, GtkTreeViewColumn *t_col,
2566 int n, GtkCellRenderer **renderer)
2568 xmlDocPtr doc;
2569 xmlXPathContextPtr xpath_ctx;
2570 xmlXPathObjectPtr xpath_obj;
2571 xmlNodeSetPtr nodes;
2572 xmlNodePtr cur;
2573 int i;
2574 xmlChar *xpath, *renderer_name = NULL, *m_col_s = NULL;
2575 char *xpath_base1 = "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
2576 const char *xpath_id = widget_name(GTK_BUILDABLE(t_col));
2577 char *xpath_base2 = "\"]/child[";
2578 size_t xpath_n_len = 3; /* Big Enough (TM) */
2579 char *xpath_base3 = "]/object[@class=\"GtkCellRendererText\""
2580 " or @class=\"GtkCellRendererToggle\"]/";
2581 char *xpath_text_col = "../attributes/attribute[@name=\"text\""
2582 " or @name=\"active\"]";
2583 char *xpath_renderer_id = "/@id";
2584 size_t xpath_len;
2585 bool r = false;
2587 if ((doc = xmlParseFile(ui_file)) == NULL)
2588 return false;
2589 if ((xpath_ctx = xmlXPathNewContext(doc)) == NULL) {
2590 xmlFreeDoc(doc);
2591 return false;
2593 xpath_len = 2 * (strlen(xpath_base1) + strlen(xpath_id) +
2594 strlen(xpath_base2) + xpath_n_len +
2595 strlen(xpath_base3))
2596 + 1 /* "|" */
2597 + strlen(xpath_text_col) + strlen(xpath_renderer_id)
2598 + 1; /* '\0' */
2599 if ((xpath = malloc(xpath_len)) == NULL) {
2600 xmlFreeDoc(doc);
2601 return false;
2603 snprintf((char *) xpath, xpath_len, "%s%s%s%d%s%s|%s%s%s%d%s%s",
2604 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_text_col,
2605 xpath_base1, xpath_id, xpath_base2, n, xpath_base3, xpath_renderer_id);
2606 if ((xpath_obj = xmlXPathEvalExpression(xpath, xpath_ctx)) == NULL) {
2607 xmlXPathFreeContext(xpath_ctx);
2608 free(xpath);
2609 xmlFreeDoc(doc);
2610 return false;
2612 if ((nodes = xpath_obj->nodesetval) != NULL) {
2613 for (i = 0; i < nodes->nodeNr; ++i) {
2614 if (nodes->nodeTab[i]->type == XML_ELEMENT_NODE) {
2615 cur = nodes->nodeTab[i];
2616 m_col_s = xmlNodeGetContent(cur);
2617 } else {
2618 cur = nodes->nodeTab[i];
2619 renderer_name = xmlNodeGetContent(cur);
2623 if (renderer_name) {
2624 *renderer = GTK_CELL_RENDERER(
2625 gtk_builder_get_object(builder, (char *) renderer_name));
2626 if (m_col_s) {
2627 g_object_set_data(G_OBJECT(*renderer), "col_number",
2628 GINT_TO_POINTER(strtol((char *) m_col_s,
2629 NULL, 10)));
2630 xmlFree(m_col_s);
2631 r = true;
2633 xmlFree(renderer_name);
2635 xmlXPathFreeObject(xpath_obj);
2636 xmlXPathFreeContext(xpath_ctx);
2637 free(xpath);
2638 xmlFreeDoc(doc);
2639 return r;
2643 * Callbacks that forward a modification of a tree view cell to the
2644 * underlying model
2646 static void
2647 cb_tree_model_edit(GtkCellRenderer *renderer, const gchar *path_s,
2648 const gchar *new_text, gpointer model)
2650 GtkTreeIter iter;
2651 GtkTreeView *view;
2652 void *col;
2654 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2655 view = g_object_get_data(G_OBJECT(renderer), "tree_view");
2656 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2657 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2658 new_text);
2659 send_tree_cell_msg_by(send_msg, model, path_s, &iter, GPOINTER_TO_INT(col),
2660 GTK_BUILDABLE(view));
2663 static void
2664 cb_tree_model_toggle(GtkCellRenderer *renderer, gchar *path_s, gpointer model)
2666 GtkTreeIter iter;
2667 void *col;
2668 bool toggle_state;
2670 gtk_tree_model_get_iter_from_string(model, &iter, path_s);
2671 col = g_object_get_data(G_OBJECT(renderer), "col_number");
2672 gtk_tree_model_get(model, &iter, col, &toggle_state, -1);
2673 set_tree_view_cell(model, &iter, path_s, GPOINTER_TO_INT(col),
2674 toggle_state? "0" : "1");
2677 static void
2678 connect_widget_signals(gpointer *obj, char *ui_file)
2680 const char *name = NULL;
2681 char *suffix = NULL;
2682 GObject *obj2;
2683 GType type = G_TYPE_INVALID;
2685 type = G_TYPE_FROM_INSTANCE(obj);
2686 if (GTK_IS_BUILDABLE(obj))
2687 name = widget_name(GTK_BUILDABLE(obj));
2688 if (type == GTK_TYPE_TREE_VIEW_COLUMN) {
2689 gboolean editable = FALSE;
2690 GtkTreeView *view;
2691 GtkTreeModel *model;
2692 GtkCellRenderer *renderer;
2693 int i;
2695 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2696 view = GTK_TREE_VIEW(gtk_tree_view_column_get_tree_view(GTK_TREE_VIEW_COLUMN(obj)));
2697 model = gtk_tree_view_get_model(view);
2698 for (i = 1;; i++) {
2699 if (!tree_view_column_get_renderer_column(ui_file, GTK_TREE_VIEW_COLUMN(obj), i, &renderer))
2700 break;
2701 g_object_set_data(G_OBJECT(renderer), "tree_view", view);
2702 if (GTK_IS_CELL_RENDERER_TEXT(renderer)) {
2703 g_object_get(renderer, "editable", &editable, NULL);
2704 if (editable)
2705 g_signal_connect(renderer, "edited", G_CALLBACK(cb_tree_model_edit), model);
2706 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer)) {
2707 g_object_get(renderer, "activatable", &editable, NULL);
2708 if (editable)
2709 g_signal_connect(renderer, "toggled", G_CALLBACK(cb_tree_model_toggle), model);
2712 } else if (type == GTK_TYPE_BUTTON) {
2713 /* Button associated with a GtkTextView. */
2714 if ((suffix = strstr(name, "_send_text")) != NULL &&
2715 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2716 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text),
2717 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2718 else if ((suffix = strstr(name, "_send_selection")) != NULL &&
2719 GTK_IS_TEXT_VIEW(obj2 = obj_sans_suffix(suffix, name)))
2720 g_signal_connect(obj, "clicked", G_CALLBACK(cb_send_text_selection),
2721 gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2)));
2722 else {
2723 g_signal_connect(obj, "clicked", G_CALLBACK(cb_simple), "clicked");
2724 /* Buttons associated with (and part of) a GtkDialog.
2725 * (We shun response ids which could be returned from
2726 * gtk_dialog_run() because that would require the
2727 * user to define those response ids in Glade,
2728 * numerically */
2729 if ((suffix = strstr(name, "_cancel")) != NULL &&
2730 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2731 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2732 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2733 else
2734 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2735 else if ((suffix = strstr(name, "_ok")) != NULL &&
2736 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name))) {
2737 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2))
2738 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), GTK_FILE_CHOOSER(obj2));
2739 if (eql(widget_name(GTK_BUILDABLE(obj2)), MAIN_WIN))
2740 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_main_quit), NULL);
2741 else
2742 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(gtk_widget_hide), obj2);
2743 } else if ((suffix = strstr(name, "_apply")) != NULL &&
2744 GTK_IS_FILE_CHOOSER_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2745 g_signal_connect_swapped(obj, "clicked", G_CALLBACK(cb_send_file_chooser_dialog_selection), obj2);
2747 } else if (GTK_IS_MENU_ITEM(obj))
2748 if ((suffix = strstr(name, "_invoke")) != NULL &&
2749 GTK_IS_DIALOG(obj2 = obj_sans_suffix(suffix, name)))
2750 g_signal_connect_swapped(obj, "activate", G_CALLBACK(gtk_widget_show), obj2);
2751 else
2752 g_signal_connect(obj, "activate", G_CALLBACK(cb_menu_item), "active");
2753 else if (GTK_IS_WINDOW(obj)) {
2754 g_signal_connect(obj, "delete-event", G_CALLBACK(cb_event_simple), "closed");
2755 if (eql(name, MAIN_WIN))
2756 g_signal_connect_swapped(obj, "delete-event", G_CALLBACK(gtk_main_quit), NULL);
2757 else
2758 g_signal_connect(obj, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);
2759 } else if (type == GTK_TYPE_FILE_CHOOSER_BUTTON)
2760 g_signal_connect(obj, "file-set", G_CALLBACK(cb_file_chooser_button), "file");
2761 else if (type == GTK_TYPE_COLOR_BUTTON)
2762 g_signal_connect(obj, "color-set", G_CALLBACK(cb_color_button), "color");
2763 else if (type == GTK_TYPE_FONT_BUTTON)
2764 g_signal_connect(obj, "font-set", G_CALLBACK(cb_font_button), "font");
2765 else if (type == GTK_TYPE_SWITCH)
2766 g_signal_connect(obj, "notify::active", G_CALLBACK(cb_switch), NULL);
2767 else if (type == GTK_TYPE_TOGGLE_BUTTON || type == GTK_TYPE_RADIO_BUTTON || type == GTK_TYPE_CHECK_BUTTON)
2768 g_signal_connect(obj, "toggled", G_CALLBACK(cb_toggle_button), NULL);
2769 else if (type == GTK_TYPE_SPIN_BUTTON || type == GTK_TYPE_ENTRY)
2770 g_signal_connect(obj, "changed", G_CALLBACK(cb_editable), "text");
2771 else if (type == GTK_TYPE_SCALE)
2772 g_signal_connect(obj, "value-changed", G_CALLBACK(cb_range), "value");
2773 else if (type == GTK_TYPE_CALENDAR) {
2774 g_signal_connect(obj, "day-selected-double-click", G_CALLBACK(cb_calendar), "doubleclicked");
2775 g_signal_connect(obj, "day-selected", G_CALLBACK(cb_calendar), "clicked");
2776 } else if (type == GTK_TYPE_TREE_SELECTION)
2777 g_signal_connect(obj, "changed", G_CALLBACK(cb_tree_selection), "clicked");
2778 else if (type == GTK_TYPE_SOCKET) {
2779 g_signal_connect(obj, "plug-added", G_CALLBACK(cb_simple), "plug-added");
2780 g_signal_connect(obj, "plug-removed", G_CALLBACK(cb_simple), "plug-removed");
2781 } else if (type == GTK_TYPE_DRAWING_AREA)
2782 g_signal_connect(obj, "draw", G_CALLBACK(cb_draw), NULL);
2783 else if (type == GTK_TYPE_EVENT_BOX) {
2784 gtk_widget_set_can_focus(GTK_WIDGET(obj), true);
2785 g_signal_connect(obj, "button-press-event", G_CALLBACK(cb_event_box_button), "button_press");
2786 g_signal_connect(obj, "button-release-event", G_CALLBACK(cb_event_box_button), "button_release");
2787 g_signal_connect(obj, "motion-notify-event", G_CALLBACK(cb_event_box_motion), "motion");
2788 g_signal_connect(obj, "key-press-event", G_CALLBACK(cb_event_box_key), "key_press");
2793 * We keep a style provider with each widget
2795 static void
2796 add_widget_style_provider(gpointer *obj, void *data)
2798 GtkStyleContext *context;
2799 GtkCssProvider *style_provider;
2801 (void) data;
2802 if (!GTK_IS_WIDGET(obj))
2803 return;
2804 style_provider = gtk_css_provider_new();
2805 context = gtk_widget_get_style_context(GTK_WIDGET(obj));
2806 gtk_style_context_add_provider(context,
2807 GTK_STYLE_PROVIDER(style_provider),
2808 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
2809 g_object_set_data(G_OBJECT(obj), "style_provider", style_provider);
2812 static void
2813 prepare_widgets(char *ui_file)
2815 GSList *objects = NULL;
2817 objects = gtk_builder_get_objects(builder);
2818 g_slist_foreach(objects, (GFunc) connect_widget_signals, ui_file);
2819 g_slist_foreach(objects, (GFunc) add_widget_style_provider, NULL);
2820 g_slist_free(objects);
2824 main(int argc, char *argv[])
2826 char opt;
2827 char *in_fifo = NULL, *out_fifo = NULL;
2828 char *ui_file = "pipeglade.ui", *log_file = NULL, *err_file = NULL;
2829 char *xid = NULL;
2830 bool bg = false;
2831 pthread_t receiver;
2832 GObject *main_window = NULL;
2833 FILE *in = NULL; /* command input */
2835 /* Disable runtime GLIB deprecation warnings: */
2836 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
2837 out = NULL;
2838 save = NULL;
2839 log_out = NULL;
2840 gtk_init(&argc, &argv);
2841 while ((opt = getopt(argc, argv, "bGhe:i:l:o:O:u:V")) != -1) {
2842 switch (opt) {
2843 case 'b': bg = true; break;
2844 case 'e': xid = optarg; break;
2845 case 'G': show_lib_versions(); break;
2846 case 'h': bye(EXIT_SUCCESS, stdout, USAGE); break;
2847 case 'i': in_fifo = optarg; break;
2848 case 'l': log_file = optarg; break;
2849 case 'o': out_fifo = optarg; break;
2850 case 'O': err_file = optarg; break;
2851 case 'u': ui_file = optarg; break;
2852 case 'V': bye(EXIT_SUCCESS, stdout, "%s\n", VERSION); break;
2853 case '?':
2854 default: bye(EXIT_FAILURE, stderr, USAGE); break;
2857 if (argv[optind] != NULL)
2858 bye(EXIT_FAILURE, stderr,
2859 "illegal parameter '%s'\n" USAGE, argv[optind]);
2860 redirect_stderr(err_file);
2861 in = open_in_fifo(in_fifo);
2862 out = open_out_fifo(out_fifo);
2863 go_bg_if(bg, in, out, err_file);
2864 builder = builder_from_file(ui_file);
2865 log_out = open_log(log_file);
2866 pthread_create(&receiver, NULL, (void *(*)(void *)) digest_msg, in);
2867 main_window = find_main_window();
2868 xmlInitParser();
2869 LIBXML_TEST_VERSION;
2870 prepare_widgets(ui_file);
2871 xembed_if(xid, main_window);
2872 gtk_main();
2873 rm_unless(stdin, in, in_fifo);
2874 rm_unless(stdout, out, out_fifo);
2875 pthread_cancel(receiver);
2876 pthread_join(receiver, NULL);
2877 xmlCleanupParser();
2878 exit(EXIT_SUCCESS);