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 <cairo-pdf.h>
26 #include <cairo-svg.h>
30 #include <gtk/gtkunixprint.h>
33 #include <libxml/xpath.h>
42 #include <sys/select.h>
47 #define VERSION "4.7.0"
49 #define WHITESPACE " \t\n"
50 #define MAIN_WIN "main"
52 "usage: pipeglade [[-i in-fifo] " \
55 "[-u glade-file.ui] " \
59 "[--display X-server]] | " \
68 __func__, __FILE__, __LINE__); \
75 "Out of memory in %s (%s:%d): ", \
76 __func__, __FILE__, __LINE__); \
82 * ============================================================
84 * ============================================================
88 * Check if s1 and s2 are equal strings
91 eql(const char *s1
, const char *s2
)
93 return s1
!= NULL
&& s2
!= NULL
&& strcmp(s1
, s2
) == 0;
97 * Print a formatted message to stream s and give up with status
100 bye(int status
, FILE *s
, const char *fmt
, ...)
105 vfprintf(s
, fmt
, ap
);
111 show_lib_versions(void)
113 bye(EXIT_SUCCESS
, stdout
,
114 "GTK+ v%d.%d.%d (running v%d.%d.%d)\n"
115 "cairo v%s (running v%s)\n",
116 GTK_MAJOR_VERSION
, GTK_MINOR_VERSION
, GTK_MICRO_VERSION
,
117 gtk_get_major_version(), gtk_get_minor_version(),
118 gtk_get_micro_version(),
119 CAIRO_VERSION_STRING
, cairo_version_string());
123 * XEmbed us if xid_s is given, or show a standalone window; give up
127 xembed_if(char *xid_s
, GObject
*main_window
)
129 GtkWidget
*plug
, *body
;
133 if (xid_s
== NULL
) { /* standalone */
134 gtk_widget_show(GTK_WIDGET(main_window
));
137 /* We're being XEmbedded */
138 xid
= strtoul(xid_s
, NULL
, 10);
139 snprintf(xid_s2
, BUFLEN
, "%lu", xid
);
140 if (!eql(xid_s
, xid_s2
))
141 bye(EXIT_FAILURE
, stderr
,
142 "%s is not a valid XEmbed socket id\n", xid_s
);
143 body
= gtk_bin_get_child(GTK_BIN(main_window
));
144 gtk_container_remove(GTK_CONTAINER(main_window
), body
);
145 plug
= gtk_plug_new(xid
);
146 if (!gtk_plug_get_embedded(GTK_PLUG(plug
)))
147 bye(EXIT_FAILURE
, stderr
,
148 "unable to embed into XEmbed socket %s\n", xid_s
);
149 gtk_container_add(GTK_CONTAINER(plug
), body
);
150 gtk_widget_show(plug
);
154 * If requested, redirect stderr to file name
157 redirect_stderr(const char *name
)
161 if (freopen(name
, "a", stderr
) == NULL
)
162 /* complaining on stdout since stderr is closed now */
163 bye(EXIT_FAILURE
, stdout
, "redirecting stderr to %s: %s\n",
164 name
, strerror(errno
));
165 if (fchmod(fileno(stderr
), 0600) < 0)
166 bye(EXIT_FAILURE
, stdout
, "setting permissions of %s: %s\n",
167 name
, strerror(errno
));
168 setvbuf(stderr
, NULL
, _IOLBF
, 0);
173 * fork() if requested in bg; give up on errors
176 go_bg_if(bool bg
, FILE *in
, FILE *out
, char *err_file
)
182 if (in
== stdin
|| out
== stdout
)
183 bye(EXIT_FAILURE
, stderr
,
184 "parameter -b requires both -i and -o\n");
187 bye(EXIT_FAILURE
, stderr
,
188 "going to background: %s\n", strerror(errno
));
190 bye(EXIT_SUCCESS
, stdout
, "%d\n", pid
);
191 /* We're the child */
192 close(fileno(stdin
)); /* making certain not-so-smart */
193 close(fileno(stdout
)); /* system/run-shell commands happy */
194 if (err_file
== NULL
)
195 freopen("/dev/null", "w", stderr
);
199 * Return the current locale and set it to "C". Should be free()d if
206 char *lc
= setlocale(LC_NUMERIC
, NULL
);
208 if ((lc_orig
= malloc(strlen(lc
) + 1)) == NULL
)
211 setlocale(LC_NUMERIC
, "C");
216 * Set locale (back) to lc; free lc
219 lc_numeric_free(char *lc
)
221 setlocale(LC_NUMERIC
, lc
);
226 * Print a warning about a malformed command to stderr. Runs inside
230 ign_cmd(GType type
, const char *msg
)
232 const char *name
, *pad
= " ";
234 if (type
== G_TYPE_INVALID
) {
238 name
= g_type_name(type
);
239 fprintf(stderr
, "ignoring %s%scommand \"%s\"\n", name
, pad
, msg
);
243 * Check if n is, or can be made, the name of a fifo, and put its
244 * struct stat into sb. Give up if n exists but is not a fifo.
247 find_fifo(const char *n
, struct stat
*sb
)
251 if ((fd
= open(n
, O_RDONLY
| O_NONBLOCK
)) > -1) {
252 if (fstat(fd
, sb
) == 0 &&
253 S_ISFIFO(sb
->st_mode
) &&
254 fchmod(fd
, 0600) == 0) {
259 bye(EXIT_FAILURE
, stderr
, "using pre-existing fifo %s: %s\n",
262 if (mkfifo(n
, 0600) != 0)
263 bye(EXIT_FAILURE
, stderr
, "making fifo %s: %s\n",
269 open_fifo(const char *name
, const char *fmode
, FILE *fallback
, int bmode
)
273 struct stat sb1
, sb2
;
278 find_fifo(name
, &sb1
);
279 /* TODO: O_RDWR on fifo is undefined in POSIX */
280 if (!((fd
= open(name
, O_RDWR
)) > -1 &&
281 fstat(fd
, &sb2
) == 0 &&
282 sb1
.st_mode
== sb2
.st_mode
&&
283 sb1
.st_ino
== sb2
.st_ino
&&
284 sb1
.st_dev
== sb2
.st_dev
&&
285 (s
= fdopen(fd
, fmode
)) != NULL
))
286 bye(EXIT_FAILURE
, stderr
, "opening fifo %s (%s): %s\n",
287 name
, fmode
, strerror(errno
));
289 setvbuf(s
, NULL
, bmode
, 0);
294 * Create a log file if necessary, and open it. A name of "-"
295 * requests use of stderr.
298 open_log(const char *name
)
306 if ((s
= fopen(name
, "a")) == NULL
)
307 bye(EXIT_FAILURE
, stderr
, "opening log file %s: %s\n",
308 name
, strerror(errno
));
309 if (fchmod(fileno(s
), 0600) < 0)
310 bye(EXIT_FAILURE
, stderr
, "setting permissions of %s: %s\n",
311 name
, strerror(errno
));
316 * Delete fifo fn if streams s and forbidden are distinct
319 rm_unless(FILE *forbidden
, FILE *s
, char *fn
)
328 * Microseconds elapsed since start
331 usec_since(struct timespec
*start
)
335 clock_gettime(CLOCK_MONOTONIC
, &now
);
336 return (now
.tv_sec
- start
->tv_sec
) * 1e6
+
337 (now
.tv_nsec
- start
->tv_nsec
) / 1e3
;
341 * Write string s to stream o, escaping newlines and backslashes
344 fputs_escaped(const char *s
, FILE *o
)
349 while ((c
= s
[i
++]) != '\0')
351 case '\\': fputs("\\\\", o
); break;
352 case '\n': fputs("\\n", o
); break;
353 default: putc(c
, o
); break;
361 log_msg(FILE *l
, char *msg
)
363 static char *old_msg
;
364 static struct timespec start
;
366 if (l
== NULL
) /* no logging */
368 if (msg
== NULL
&& old_msg
== NULL
)
369 fprintf(l
, "##########\t##### (New Pipeglade session) #####\n");
370 else if (msg
== NULL
&& old_msg
!= NULL
) {
371 /* command done; start idle */
372 fprintf(l
, "%10ld\t", usec_since(&start
));
373 fputs_escaped(old_msg
, l
);
377 } else if (msg
!= NULL
&& old_msg
== NULL
) {
378 /* idle done; start command */
379 fprintf(l
, "%10ld\t### (Idle) ###\n", usec_since(&start
));
380 if ((old_msg
= malloc(strlen(msg
) + 1)) == NULL
)
382 strcpy(old_msg
, msg
);
385 clock_gettime(CLOCK_MONOTONIC
, &start
);
389 has_suffix(const char *s
, const char *suffix
)
391 int s_suf
= strlen(s
) - strlen(suffix
);
395 return eql(suffix
, s
+ s_suf
);
399 * Remove suffix from name; find the object named like this
402 obj_sans_suffix(GtkBuilder
*builder
, const char *suffix
, const char *name
)
404 char str
[BUFLEN
+ 1] = {'\0'};
407 str_l
= suffix
- name
;
408 strncpy(str
, name
, str_l
< BUFLEN
? str_l
: BUFLEN
);
409 return gtk_builder_get_object(builder
, str
);
413 * Read UI definition from ui_file; give up on errors
416 builder_from_file(char *ui_file
)
418 GError
*error
= NULL
;
421 b
= gtk_builder_new();
422 if (gtk_builder_add_from_file(b
, ui_file
, &error
) == 0)
423 bye(EXIT_FAILURE
, stderr
, "%s\n", error
->message
);
428 * Return the id attribute of widget
431 widget_id(GtkBuildable
*widget
)
433 return gtk_buildable_get_name(widget
);
437 * Get the main window; give up on errors
440 find_main_window(GtkBuilder
*builder
)
444 if (GTK_IS_WINDOW(mw
= gtk_builder_get_object(builder
, MAIN_WIN
)))
446 bye(EXIT_FAILURE
, stderr
, "no toplevel window with id \'" MAIN_WIN
"\'\n");
447 return NULL
; /* NOT REACHED */
451 * Store a line from stream s into buf, which should have been malloc'd
452 * to bufsize. Enlarge buf and bufsize if necessary.
455 read_buf(FILE *s
, char **buf
, size_t *bufsize
)
466 select(ifd
+ 1, &rfds
, NULL
, NULL
, NULL
);
468 if (c
== '\n' || feof(s
))
470 if (i
>= *bufsize
- 1)
471 if ((*buf
= realloc(*buf
, *bufsize
*= 2)) == NULL
)
476 case 'n': (*buf
)[i
++] = '\n'; break;
477 case 'r': (*buf
)[i
++] = '\r'; break;
478 default: (*buf
)[i
++] = c
; break;
480 } else if (c
== '\\')
491 * ============================================================
492 * Receiving feedback from the GUI
493 * ============================================================
497 * Preclude triggering of GTK's default GtkLinkButton action which
498 * could otherwise interfere with pipeglade's own signal blocking
501 gtk_show_uri(GdkScreen
*s
, const gchar
*uri
, guint32 ts
, GError
**e
)
503 (void) s
; (void) uri
; (void) ts
; (void) e
;
508 send_msg_to(FILE* o
, GtkBuildable
*obj
, const char *tag
, va_list ap
)
511 const char *w_id
= widget_id(obj
);
514 struct timeval timeout
= {1, 0};
518 if (select(ofd
+ 1, NULL
, &wfds
, NULL
, &timeout
) == 1) {
519 fprintf(o
, "%s:%s ", w_id
, tag
);
520 while ((data
= va_arg(ap
, char *)) != NULL
)
521 fputs_escaped(data
, o
);
525 "send error; discarding feedback message %s:%s\n",
530 * Send GUI feedback to stream o. The message format is
531 * "<origin>:<tag> <data ...>". The variadic arguments are strings;
532 * last argument must be NULL.
535 send_msg(FILE *o
, GtkBuildable
*obj
, const char *tag
, ...)
540 send_msg_to(o
, obj
, tag
, ap
);
545 * Send message from GUI to stream o. The message format is
546 * "<origin>:set <data ...>", which happens to be a legal command.
547 * The variadic arguments are strings; last argument must be NULL.
550 send_msg_as_cmd(FILE *o
, GtkBuildable
*obj
, const char *tag
, ...)
555 send_msg_to(o
, obj
, "set", ap
);
560 * Stuff to pass around
563 FILE *fout
; /* UI feedback messages */
564 FILE *fin
; /* command input */
565 FILE *flog
; /* logging output */
566 GtkBuilder
*builder
; /* to be read from .ui file */
573 * Data to be passed to and from the GTK main loop
576 void (*fn
)(struct ui_data
*);
587 * Return pointer to a newly allocated struct info
590 info_new_full(FILE *stream
, GObject
*obj
, GtkTreeModel
*model
, char *txt
)
594 if ((ar
= malloc(sizeof(struct info
))) == NULL
)
607 info_txt_new(FILE *stream
, char *txt
)
609 return info_new_full(stream
, NULL
, NULL
, txt
);
613 info_obj_new(FILE *stream
, GObject
*obj
, GtkTreeModel
*model
)
615 return info_new_full(stream
, obj
, model
, NULL
);
619 * Use msg_sender() to send a message describing a particular cell
622 send_tree_cell_msg_by(void msg_sender(FILE *, GtkBuildable
*, const char *, ...),
624 GtkTreeIter
*iter
, int col
, struct info
*ar
)
626 GtkBuildable
*obj
= GTK_BUILDABLE(ar
->obj
);
627 GtkTreeModel
*model
= ar
->model
;
629 GValue value
= G_VALUE_INIT
;
630 char str
[BUFLEN
], *lc
= lc_numeric();
632 gtk_tree_model_get_value(model
, iter
, col
, &value
);
633 col_type
= gtk_tree_model_get_column_type(model
, col
);
636 snprintf(str
, BUFLEN
, " %d %d", col
, g_value_get_int(&value
));
637 msg_sender(ar
->fout
, obj
, "gint", path_s
, str
, NULL
);
640 snprintf(str
, BUFLEN
, " %d %ld", col
, g_value_get_long(&value
));
641 msg_sender(ar
->fout
, obj
, "glong", path_s
, str
, NULL
);
644 snprintf(str
, BUFLEN
, " %d %" PRId64
, col
, g_value_get_int64(&value
));
645 msg_sender(ar
->fout
, obj
, "gint64", path_s
, str
, NULL
);
648 snprintf(str
, BUFLEN
, " %d %u", col
, g_value_get_uint(&value
));
649 msg_sender(ar
->fout
, obj
, "guint", path_s
, str
, NULL
);
652 snprintf(str
, BUFLEN
, " %d %lu", col
, g_value_get_ulong(&value
));
653 msg_sender(ar
->fout
, obj
, "gulong", path_s
, str
, NULL
);
656 snprintf(str
, BUFLEN
, " %d %" PRIu64
, col
, g_value_get_uint64(&value
));
657 msg_sender(ar
->fout
, obj
, "guint64", path_s
, str
, NULL
);
660 snprintf(str
, BUFLEN
, " %d %d", col
, g_value_get_boolean(&value
));
661 msg_sender(ar
->fout
, obj
, "gboolean", path_s
, str
, NULL
);
664 snprintf(str
, BUFLEN
, " %d %f", col
, g_value_get_float(&value
));
665 msg_sender(ar
->fout
, obj
, "gfloat", path_s
, str
, NULL
);
668 snprintf(str
, BUFLEN
, " %d %f", col
, g_value_get_double(&value
));
669 msg_sender(ar
->fout
, obj
, "gdouble", path_s
, str
, NULL
);
672 snprintf(str
, BUFLEN
, " %d ", col
);
673 msg_sender(ar
->fout
, obj
, "gchararray", path_s
, str
, g_value_get_string(&value
), NULL
);
676 fprintf(stderr
, "column %d not implemented: %s\n", col
, G_VALUE_TYPE_NAME(&value
));
679 g_value_unset(&value
);
684 * Use msg_sender() to send one message per column for a single row
687 send_tree_row_msg_by(void msg_sender(FILE *, GtkBuildable
*, const char *, ...),
688 char *path_s
, GtkTreeIter
*iter
, struct info
*ar
)
692 for (col
= 0; col
< gtk_tree_model_get_n_columns(ar
->model
); col
++)
693 send_tree_cell_msg_by(msg_sender
, path_s
, iter
, col
, ar
);
697 * send_tree_row_msg serves as an argument for
698 * gtk_tree_selection_selected_foreach()
701 send_tree_row_msg(GtkTreeModel
*model
,
702 GtkTreePath
*path
, GtkTreeIter
*iter
, struct info
*ar
)
704 char *path_s
= gtk_tree_path_to_string(path
);
707 send_tree_row_msg_by(send_msg
, path_s
, iter
, ar
);
713 * save_tree_row_msg serves as an argument for
714 * gtk_tree_model_foreach().
715 * Send message from GUI to global stream "save".
718 save_tree_row_msg(GtkTreeModel
*model
,
719 GtkTreePath
*path
, GtkTreeIter
*iter
, struct info
*ar
)
721 char *path_s
= gtk_tree_path_to_string(path
);
724 send_tree_row_msg_by(send_msg_as_cmd
, path_s
, iter
, ar
);
730 cb_calendar(GtkBuildable
*obj
, struct info
*ar
)
733 unsigned int year
= 0, month
= 0, day
= 0;
735 gtk_calendar_get_date(GTK_CALENDAR(obj
), &year
, &month
, &day
);
736 snprintf(str
, BUFLEN
, "%04u-%02u-%02u", year
, ++month
, day
);
737 send_msg(ar
->fout
, obj
, ar
->txt
, str
, NULL
);
741 cb_color_button(GtkBuildable
*obj
, struct info
*ar
)
745 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(obj
), &color
);
746 send_msg(ar
->fout
, obj
, ar
->txt
, gdk_rgba_to_string(&color
), NULL
);
750 cb_editable(GtkBuildable
*obj
, struct info
*ar
)
752 send_msg(ar
->fout
, obj
, ar
->txt
, gtk_entry_get_text(GTK_ENTRY(obj
)), NULL
);
756 * Callback that sends a message about a pointer device button press
760 cb_event_box_button(GtkBuildable
*obj
, GdkEvent
*e
, struct info
*ar
)
762 char data
[BUFLEN
], *lc
= lc_numeric();
764 snprintf(data
, BUFLEN
, "%d %.1lf %.1lf",
765 e
->button
.button
, e
->button
.x
, e
->button
.y
);
766 send_msg(ar
->fout
, obj
, ar
->txt
, data
, NULL
);
772 * Callback that sends in a message the name of the key pressed when
773 * a GtkEventBox is focused
776 cb_event_box_key(GtkBuildable
*obj
, GdkEvent
*e
, struct info
*ar
)
778 send_msg(ar
->fout
, obj
, ar
->txt
, gdk_keyval_name(e
->key
.keyval
), NULL
);
783 * Callback that sends a message about pointer device motion in a
787 cb_event_box_motion(GtkBuildable
*obj
, GdkEvent
*e
, struct info
*ar
)
789 char data
[BUFLEN
], *lc
= lc_numeric();
791 snprintf(data
, BUFLEN
, "%.1lf %.1lf", e
->button
.x
, e
->button
.y
);
792 send_msg(ar
->fout
, obj
, ar
->txt
, data
, NULL
);
798 * Callback that only sends "name:tag" and returns false
801 cb_event_simple(GtkBuildable
*obj
, GdkEvent
*e
, struct info
*ar
)
804 send_msg(ar
->fout
, obj
, ar
->txt
, NULL
);
809 cb_file_chooser_button(GtkBuildable
*obj
, struct info
*ar
)
811 send_msg(ar
->fout
, obj
, ar
->txt
,
812 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(obj
)), NULL
);
816 cb_font_button(GtkBuildable
*obj
, struct info
*ar
)
818 send_msg(ar
->fout
, obj
, ar
->txt
,
819 gtk_font_button_get_font_name(GTK_FONT_BUTTON(obj
)), NULL
);
823 cb_menu_item(GtkBuildable
*obj
, struct info
*ar
)
825 send_msg(ar
->fout
, obj
, ar
->txt
,
826 gtk_menu_item_get_label(GTK_MENU_ITEM(obj
)), NULL
);
830 cb_range(GtkBuildable
*obj
, struct info
*ar
)
832 char str
[BUFLEN
], *lc
= lc_numeric();
834 snprintf(str
, BUFLEN
, "%f", gtk_range_get_value(GTK_RANGE(obj
)));
835 send_msg(ar
->fout
, obj
, ar
->txt
, str
, NULL
);
840 * Callback that sends user's selection from a file dialog
843 cb_send_file_chooser_dialog_selection(struct info
*ar
)
845 send_msg(ar
->fout
, GTK_BUILDABLE(ar
->obj
), "file",
846 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(ar
->obj
)),
848 send_msg(ar
->fout
, GTK_BUILDABLE(ar
->obj
), "folder",
849 gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(ar
->obj
)),
854 * Callback that sends in a message the content of the text buffer
855 * passed in user_data
858 cb_send_text(GtkBuildable
*obj
, struct info
*ar
)
862 gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(ar
->obj
), &a
, &b
);
863 send_msg(ar
->fout
, obj
, "text",
864 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar
->obj
), &a
, &b
, TRUE
),
869 * Callback that sends in a message the highlighted text from the text
870 * buffer which was passed in user_data
873 cb_send_text_selection(GtkBuildable
*obj
, struct info
*ar
)
877 gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(ar
->obj
), &a
, &b
);
878 send_msg(ar
->fout
, obj
, "text",
879 gtk_text_buffer_get_text(GTK_TEXT_BUFFER(ar
->obj
), &a
, &b
, TRUE
),
884 * Callback that only sends "name:tag" and returns true
887 cb_simple(GtkBuildable
*obj
, struct info
*ar
)
889 send_msg(ar
->fout
, obj
, ar
->txt
, NULL
);
894 cb_spin_button(GtkBuildable
*obj
, struct info
*ar
)
896 char str
[BUFLEN
], *lc
= lc_numeric();
898 snprintf(str
, BUFLEN
, "%f", gtk_spin_button_get_value(GTK_SPIN_BUTTON(obj
)));
899 send_msg(ar
->fout
, obj
, ar
->txt
, str
, NULL
);
904 cb_switch(GtkBuildable
*obj
, void *pspec
, struct info
*ar
)
907 send_msg(ar
->fout
, obj
,
908 gtk_switch_get_active(GTK_SWITCH(obj
)) ? "1" : "0",
913 cb_toggle_button(GtkBuildable
*obj
, struct info
*ar
)
915 send_msg(ar
->fout
, obj
,
916 gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(obj
)) ? "1" : "0",
921 cb_tree_selection(GtkBuildable
*obj
, struct info
*ar
)
923 GtkTreeSelection
*sel
= GTK_TREE_SELECTION(obj
);
924 GtkTreeView
*view
= gtk_tree_selection_get_tree_view(sel
);
926 ar
->obj
= G_OBJECT(view
);
927 send_msg(ar
->fout
, GTK_BUILDABLE(view
), ar
->txt
, NULL
);
928 gtk_tree_selection_selected_foreach(
929 sel
, (GtkTreeSelectionForeachFunc
) send_tree_row_msg
, ar
);
934 * ============================================================
935 * cb_draw() maintains a drawing on a GtkDrawingArea; it needs a few
937 * ============================================================
941 * The set of supported drawing operations
972 * Text placement mode for rel_move_for()
986 enum draw_op_policy
{
993 * One single element of a drawing
996 struct draw_op
*next
;
997 struct draw_op
*prev
;
998 unsigned long long int id
;
999 unsigned long long int before
;
1000 enum draw_op_policy policy
;
1006 * Argument sets for the various drawing operations
1016 struct curve_to_args
{
1025 struct move_to_args
{
1030 struct rectangle_args
{
1037 struct rel_move_for_args
{
1043 struct set_dash_args
{
1048 struct set_font_face_args
{
1049 cairo_font_slant_t slant
;
1050 cairo_font_weight_t weight
;
1054 struct set_font_size_args
{
1058 struct set_line_cap_args
{
1059 cairo_line_cap_t line_cap
;
1062 struct set_line_join_args
{
1063 cairo_line_join_t line_join
;
1066 struct set_line_width_args
{
1070 struct set_source_rgba_args
{
1074 struct show_text_args
{
1079 struct transform_args
{
1080 cairo_matrix_t matrix
;
1084 draw(cairo_t
*cr
, enum cairo_fn op
, void *op_args
)
1088 struct move_to_args
*args
= op_args
;
1090 cairo_line_to(cr
, args
->x
, args
->y
);
1094 struct move_to_args
*args
= op_args
;
1096 cairo_rel_line_to(cr
, args
->x
, args
->y
);
1100 struct move_to_args
*args
= op_args
;
1102 cairo_move_to(cr
, args
->x
, args
->y
);
1106 struct move_to_args
*args
= op_args
;
1108 cairo_rel_move_to(cr
, args
->x
, args
->y
);
1112 struct arc_args
*args
= op_args
;
1114 cairo_arc(cr
, args
->x
, args
->y
, args
->radius
, args
->angle1
, args
->angle2
);
1117 case ARC_NEGATIVE
: {
1118 struct arc_args
*args
= op_args
;
1120 cairo_arc_negative(cr
, args
->x
, args
->y
, args
->radius
, args
->angle1
, args
->angle2
);
1124 struct curve_to_args
*args
= op_args
;
1126 cairo_curve_to(cr
, args
->x1
, args
->y1
, args
->x2
, args
->y2
, args
->x3
, args
->y3
);
1129 case REL_CURVE_TO
: {
1130 struct curve_to_args
*args
= op_args
;
1132 cairo_curve_to(cr
, args
->x1
, args
->y1
, args
->x2
, args
->y2
, args
->x3
, args
->y3
);
1136 struct rectangle_args
*args
= op_args
;
1138 cairo_rectangle(cr
, args
->x
, args
->y
, args
->width
, args
->height
);
1142 cairo_close_path(cr
);
1145 struct show_text_args
*args
= op_args
;
1147 cairo_show_text(cr
, args
->text
);
1150 case REL_MOVE_FOR
: {
1151 cairo_text_extents_t e
;
1152 double dx
= 0.0, dy
= 0.0;
1153 struct rel_move_for_args
*args
= op_args
;
1155 cairo_text_extents(cr
, args
->text
, &e
);
1156 switch (args
->ref
) {
1157 case C
: dx
= -e
.width
/ 2; dy
= e
.height
/ 2; break;
1158 case E
: dx
= -e
.width
; dy
= e
.height
/ 2; break;
1159 case N
: dx
= -e
.width
/ 2; dy
= e
.height
; break;
1160 case NE
: dx
= -e
.width
; dy
= e
.height
; break;
1161 case NW
: dy
= e
.height
; break;
1162 case S
: dx
= -e
.width
/ 2; break;
1163 case SE
: dx
= -e
.width
; break;
1165 case W
: dy
= e
.height
/ 2; break;
1166 default: ABORT
; break;
1168 cairo_rel_move_to(cr
, dx
, dy
);
1172 cairo_identity_matrix(cr
);
1177 case STROKE_PRESERVE
:
1178 cairo_stroke_preserve(cr
);
1184 cairo_fill_preserve(cr
);
1187 struct set_dash_args
*args
= op_args
;
1189 cairo_set_dash(cr
, args
->dashes
, args
->num_dashes
, 0);
1192 case SET_FONT_FACE
: {
1193 struct set_font_face_args
*args
= op_args
;
1195 cairo_select_font_face(cr
, args
->family
, args
->slant
, args
->weight
);
1198 case SET_FONT_SIZE
: {
1199 struct set_font_size_args
*args
= op_args
;
1201 cairo_set_font_size(cr
, args
->size
);
1204 case SET_LINE_CAP
: {
1205 struct set_line_cap_args
*args
= op_args
;
1207 cairo_set_line_cap(cr
, args
->line_cap
);
1210 case SET_LINE_JOIN
: {
1211 struct set_line_join_args
*args
= op_args
;
1213 cairo_set_line_join(cr
, args
->line_join
);
1216 case SET_LINE_WIDTH
: {
1217 struct set_line_width_args
*args
= op_args
;
1219 cairo_set_line_width(cr
, args
->width
);
1222 case SET_SOURCE_RGBA
: {
1223 struct set_source_rgba_args
*args
= op_args
;
1225 gdk_cairo_set_source_rgba(cr
, &args
->color
);
1229 struct transform_args
*args
= op_args
;
1231 cairo_transform(cr
, &args
->matrix
);
1241 * Callback that draws on a GtkDrawingArea
1244 cb_draw(GtkWidget
*widget
, cairo_t
*cr
, gpointer data
)
1249 for (op
= g_object_get_data(G_OBJECT(widget
), "draw_ops");
1252 draw(cr
, op
->op
, op
->op_args
);
1258 * ============================================================
1259 * Manipulating the GUI
1260 * ============================================================
1264 * Generic actions that are applicable to most widgets
1268 * Simulate user activity on various widgets. Runs inside gtk_main().
1271 fake_ui_activity(struct ui_data
*ud
)
1275 if (!GTK_IS_WIDGET(ud
->obj
) || sscanf(ud
->data
, " %c", &dummy
) > 0)
1276 ign_cmd(ud
->type
, ud
->cmd
);
1277 else if (GTK_IS_SPIN_BUTTON(ud
->obj
)) {
1278 ud
->args
->txt
= "text";
1279 cb_spin_button(GTK_BUILDABLE(ud
->obj
), ud
->args
); /* TODO: rename to "value" */
1280 } else if (GTK_IS_SCALE(ud
->obj
)) {
1281 ud
->args
->txt
= "value";
1282 cb_range(GTK_BUILDABLE(ud
->obj
), ud
->args
);
1283 } else if (GTK_IS_ENTRY(ud
->obj
)) {
1284 ud
->args
->txt
= "text";
1285 cb_editable(GTK_BUILDABLE(ud
->obj
), ud
->args
);
1286 } else if (GTK_IS_CALENDAR(ud
->obj
)) {
1287 ud
->args
->txt
= "clicked";
1288 cb_calendar(GTK_BUILDABLE(ud
->obj
), ud
->args
);
1289 } else if (GTK_IS_FILE_CHOOSER_BUTTON(ud
->obj
)) {
1290 ud
->args
->txt
= "file";
1291 cb_file_chooser_button(GTK_BUILDABLE(ud
->obj
), ud
->args
);
1292 } else if (!gtk_widget_activate(GTK_WIDGET(ud
->obj
)))
1293 ign_cmd(ud
->type
, ud
->cmd
);
1297 update_focus(struct ui_data
*ud
){
1300 if (GTK_IS_WIDGET(ud
->obj
) &&
1301 sscanf(ud
->data
, " %c", &dummy
) < 1 &&
1302 gtk_widget_get_can_focus(GTK_WIDGET(ud
->obj
)))
1303 gtk_widget_grab_focus(GTK_WIDGET(ud
->obj
));
1305 ign_cmd(ud
->type
, ud
->cmd
);
1309 * Have the widget say "ping". Runs inside gtk_main().
1312 ping(struct ui_data
*ud
)
1316 if (!GTK_IS_WIDGET(ud
->obj
) || sscanf(ud
->data
, " %c", &dummy
) > 0)
1317 ign_cmd(ud
->type
, ud
->cmd
);
1318 ud
->args
->txt
= "ping";
1319 cb_simple(GTK_BUILDABLE(ud
->obj
), ud
->args
);
1323 * Write snapshot of widget in an appropriate format to file
1326 take_snapshot(struct ui_data
*ud
)
1328 cairo_surface_t
*sur
= NULL
;
1333 if (!GTK_IS_WIDGET(ud
->obj
) ||
1334 !gtk_widget_is_drawable(GTK_WIDGET(ud
->obj
))) {
1335 ign_cmd(ud
->type
, ud
->cmd
);
1338 height
= gtk_widget_get_allocated_height(GTK_WIDGET(ud
->obj
));
1339 width
= gtk_widget_get_allocated_width(GTK_WIDGET(ud
->obj
));
1340 if (has_suffix(ud
->data
, ".epsf") || has_suffix(ud
->data
, ".eps")) {
1341 sur
= cairo_ps_surface_create(ud
->data
, width
, height
);
1342 cairo_ps_surface_set_eps(sur
, TRUE
);
1343 } else if (has_suffix(ud
->data
, ".pdf"))
1344 sur
= cairo_pdf_surface_create(ud
->data
, width
, height
);
1345 else if (has_suffix(ud
->data
, ".ps"))
1346 sur
= cairo_ps_surface_create(ud
->data
, width
, height
);
1347 else if (has_suffix(ud
->data
, ".svg"))
1348 sur
= cairo_svg_surface_create(ud
->data
, width
, height
);
1350 ign_cmd(ud
->type
, ud
->cmd
);
1353 cr
= cairo_create(sur
);
1354 gtk_widget_draw(GTK_WIDGET(ud
->obj
), cr
);
1356 cairo_surface_destroy(sur
);
1360 unsigned int id
; /* returned by g_signal_connect() and friends */
1361 bool blocked
; /* we avoid multiple blocking/unblocking */
1362 struct handler_id
*next
;
1366 update_blocked(struct ui_data
*ud
)
1369 struct handler_id
*hid
;
1370 unsigned long int val
;
1372 if (sscanf(ud
->data
, "%lu %c", &val
, &dummy
) == 1 && val
< 2) {
1373 for (hid
= g_object_get_data(ud
->obj
, "signal-id");
1374 hid
!= NULL
; hid
= hid
->next
) {
1375 if (val
== 0 && hid
->blocked
== true) {
1376 g_signal_handler_unblock(ud
->obj
, hid
->id
);
1377 hid
->blocked
= false;
1378 } else if (val
== 1 && hid
->blocked
== false) {
1379 g_signal_handler_block(ud
->obj
, hid
->id
);
1380 hid
->blocked
= true;
1384 ign_cmd(ud
->type
, ud
->cmd
);
1388 update_sensitivity(struct ui_data
*ud
)
1393 if (GTK_IS_WIDGET(ud
->obj
) &&
1394 sscanf(ud
->data
, "%u %c", &val
, &dummy
) == 1 && val
< 2)
1395 gtk_widget_set_sensitive(GTK_WIDGET(ud
->obj
), val
);
1397 ign_cmd(ud
->type
, ud
->cmd
);
1401 update_size_request(struct ui_data
*ud
)
1406 if (GTK_IS_WIDGET(ud
->obj
) &&
1407 sscanf(ud
->data
, "%d %d %c", &x
, &y
, &dummy
) == 2)
1408 gtk_widget_set_size_request(GTK_WIDGET(ud
->obj
), x
, y
);
1409 else if (GTK_IS_WIDGET(ud
->obj
) &&
1410 sscanf(ud
->data
, " %c", &dummy
) < 1)
1411 gtk_widget_set_size_request(GTK_WIDGET(ud
->obj
), -1, -1);
1413 ign_cmd(ud
->type
, ud
->cmd
);
1417 update_tooltip_text(struct ui_data
*ud
)
1419 if (GTK_IS_WIDGET(ud
->obj
))
1420 gtk_widget_set_tooltip_text(GTK_WIDGET(ud
->obj
), ud
->data
);
1422 ign_cmd(ud
->type
, ud
->cmd
);
1426 update_visibility(struct ui_data
*ud
)
1431 if (GTK_IS_WIDGET(ud
->obj
) &&
1432 sscanf(ud
->data
, "%u %c", &val
, &dummy
) == 1 && val
< 2)
1433 gtk_widget_set_visible(GTK_WIDGET(ud
->obj
), val
);
1435 ign_cmd(ud
->type
, ud
->cmd
);
1439 * Change the style of the widget passed. Runs inside gtk_main().
1442 update_widget_style(struct ui_data
*ud
)
1444 GtkStyleContext
*context
;
1445 GtkStyleProvider
*style_provider
;
1447 const char *prefix
= "* {", *suffix
= "}";
1450 if (!GTK_IS_WIDGET(ud
->obj
)) {
1451 ign_cmd(ud
->type
, ud
->cmd
);
1454 style_provider
= g_object_get_data(ud
->obj
, "style_provider");
1455 sz
= strlen(prefix
) + strlen(suffix
) + strlen(ud
->data
) + 1;
1456 context
= gtk_widget_get_style_context(GTK_WIDGET(ud
->obj
));
1457 gtk_style_context_remove_provider(context
, style_provider
);
1458 if ((style_decl
= malloc(sz
)) == NULL
)
1460 strcpy(style_decl
, prefix
);
1461 strcat(style_decl
, ud
->data
);
1462 strcat(style_decl
, suffix
);
1463 gtk_style_context_add_provider(context
, style_provider
,
1464 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
1465 gtk_css_provider_load_from_data(GTK_CSS_PROVIDER(style_provider
),
1466 style_decl
, -1, NULL
);
1471 * Check if one of the generic actions is requested; complain if none
1475 try_generic_cmds(struct ui_data
*ud
)
1477 if (eql(ud
->action
, "block"))
1479 else if (eql(ud
->action
, "set_sensitive"))
1480 update_sensitivity(ud
);
1481 else if (eql(ud
->action
, "set_visible"))
1482 update_visibility(ud
);
1483 else if (eql(ud
->action
, "set_tooltip_text"))
1484 update_tooltip_text(ud
);
1485 else if (eql(ud
->action
, "grab_focus"))
1487 else if (eql(ud
->action
, "set_size_request"))
1488 update_size_request(ud
);
1489 else if (eql(ud
->action
, "style"))
1490 update_widget_style(ud
);
1491 else if (eql(ud
->action
, "force"))
1492 fake_ui_activity(ud
);
1493 /* next line intentionally mangled to exclude it from */
1494 /* auto-generated list of commands */
1495 else if (eql(ud
->action
, /* undocumented! */ "ping"))
1497 else if (eql(ud
->action
, "snapshot"))
1500 ign_cmd(ud
->type
, ud
->cmd
);
1504 * Manipulation of specific widgets
1508 update_button(struct ui_data
*ud
)
1510 if (eql(ud
->action
, "set_label"))
1511 gtk_button_set_label(GTK_BUTTON(ud
->obj
), ud
->data
);
1513 try_generic_cmds(ud
);
1517 update_calendar(struct ui_data
*ud
)
1519 GtkCalendar
*calendar
= GTK_CALENDAR(ud
->obj
);
1521 int year
= 0, month
= 0, day
= 0;
1523 if (eql(ud
->action
, "select_date") &&
1524 sscanf(ud
->data
, "%d-%d-%d %c", &year
, &month
, &day
, &dummy
) == 3) {
1525 if (month
> -1 && month
<= 11 && day
> 0 && day
<= 31) {
1526 gtk_calendar_select_month(calendar
, --month
, year
);
1527 gtk_calendar_select_day(calendar
, day
);
1529 ign_cmd(ud
->type
, ud
->cmd
);
1530 } else if (eql(ud
->action
, "mark_day") &&
1531 sscanf(ud
->data
, "%d %c", &day
, &dummy
) == 1) {
1532 if (day
> 0 && day
<= 31)
1533 gtk_calendar_mark_day(calendar
, day
);
1535 ign_cmd(ud
->type
, ud
->cmd
);
1536 } else if (eql(ud
->action
, "clear_marks") && sscanf(ud
->data
, " %c", &dummy
) < 1)
1537 gtk_calendar_clear_marks(calendar
);
1539 try_generic_cmds(ud
);
1543 * Common actions for various kinds of window. Return false if
1544 * command is ignored. Runs inside gtk_main().
1547 update_class_window(struct ui_data
*ud
)
1549 GtkWindow
*window
= GTK_WINDOW(ud
->obj
);
1553 if (eql(ud
->action
, "set_title"))
1554 gtk_window_set_title(window
, ud
->data
);
1555 else if (eql(ud
->action
, "fullscreen") && sscanf(ud
->data
, " %c", &dummy
) < 1)
1556 gtk_window_fullscreen(window
);
1557 else if (eql(ud
->action
, "unfullscreen") && sscanf(ud
->data
, " %c", &dummy
) < 1)
1558 gtk_window_unfullscreen(window
);
1559 else if (eql(ud
->action
, "resize") &&
1560 sscanf(ud
->data
, "%d %d %c", &x
, &y
, &dummy
) == 2)
1561 gtk_window_resize(window
, x
, y
);
1562 else if (eql(ud
->action
, "resize") && sscanf(ud
->data
, " %c", &dummy
) < 1) {
1563 gtk_window_get_default_size(window
, &x
, &y
);
1564 gtk_window_resize(window
, x
, y
);
1565 } else if (eql(ud
->action
, "move") &&
1566 sscanf(ud
->data
, "%d %d %c", &x
, &y
, &dummy
) == 2)
1567 gtk_window_move(window
, x
, y
);
1574 update_color_button(struct ui_data
*ud
)
1578 if (eql(ud
->action
, "set_color")) {
1579 gdk_rgba_parse(&color
, ud
->data
);
1580 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(ud
->obj
), &color
);
1582 try_generic_cmds(ud
);
1586 update_combo_box_text(struct ui_data
*ud
)
1588 GtkComboBoxText
*combobox
= GTK_COMBO_BOX_TEXT(ud
->obj
);
1592 if (eql(ud
->action
, "prepend_text"))
1593 gtk_combo_box_text_prepend_text(combobox
, ud
->data
);
1594 else if (eql(ud
->action
, "append_text"))
1595 gtk_combo_box_text_append_text(combobox
, ud
->data
);
1596 else if (eql(ud
->action
, "remove") &&
1597 sscanf(ud
->data
, "%d %c", &pos
, &dummy
) == 1)
1598 gtk_combo_box_text_remove(combobox
, pos
);
1599 else if (eql(ud
->action
, "insert_text") &&
1600 sscanf(ud
->data
, "%d %n", &pos
, &txt0
) == 1)
1601 gtk_combo_box_text_insert_text(combobox
, pos
, ud
->data
+ txt0
);
1603 try_generic_cmds(ud
);
1607 * update_drawing_area(), which runs inside gtk_main(), maintains a
1608 * list of drawing operations. It needs a few helper functions. It
1609 * is the responsibility of cb_draw() to actually execute the list.
1619 * Fill structure *op with the drawing operation according to action
1620 * and with the appropriate set of arguments
1622 static enum draw_op_stat
1623 set_draw_op(struct draw_op
*op
, const char *action
, const char *data
)
1626 const char *raw_args
= data
;
1627 enum draw_op_stat result
= SUCCESS
;
1630 if (sscanf(data
, "=%llu %n", &op
->id
, &args_start
) == 1) {
1631 op
->policy
= REPLACE
;
1632 result
= NEED_REDRAW
;
1633 } else if (sscanf(data
, "%llu<%llu %n", &op
->id
, &op
->before
, &args_start
) == 2) {
1634 op
->policy
= BEFORE
;
1635 result
= NEED_REDRAW
;
1636 } else if (sscanf(data
, "%llu %n", &op
->id
, &args_start
) == 1)
1637 op
->policy
= APPEND
;
1640 raw_args
+= args_start
;
1641 if (eql(action
, "line_to")) {
1642 struct move_to_args
*args
;
1644 if ((args
= malloc(sizeof(*args
))) == NULL
)
1648 if (sscanf(raw_args
, "%lf %lf %c", &args
->x
, &args
->y
, &dummy
) != 2)
1650 } else if (eql(action
, "rel_line_to")) {
1651 struct move_to_args
*args
;
1653 if ((args
= malloc(sizeof(*args
))) == NULL
)
1655 op
->op
= REL_LINE_TO
;
1657 if (sscanf(raw_args
, "%lf %lf %c", &args
->x
, &args
->y
, &dummy
) != 2)
1659 } else if (eql(action
, "move_to")) {
1660 struct move_to_args
*args
;
1662 if ((args
= malloc(sizeof(*args
))) == NULL
)
1666 if (sscanf(raw_args
, "%lf %lf %c", &args
->x
, &args
->y
, &dummy
) != 2)
1668 } else if (eql(action
, "rel_move_to")) {
1669 struct move_to_args
*args
;
1671 if ((args
= malloc(sizeof(*args
))) == NULL
)
1673 op
->op
= REL_MOVE_TO
;
1675 if (sscanf(raw_args
, "%lf %lf %c", &args
->x
, &args
->y
, &dummy
) != 2)
1677 } else if (eql(action
, "arc")) {
1678 struct arc_args
*args
;
1681 if ((args
= malloc(sizeof(*args
))) == NULL
)
1685 if (sscanf(raw_args
, "%lf %lf %lf %lf %lf %c",
1686 &args
->x
, &args
->y
, &args
->radius
, °1
, °2
, &dummy
) != 5)
1688 args
->angle1
= deg1
* (M_PI
/ 180.L
);
1689 args
->angle2
= deg2
* (M_PI
/ 180.L
);
1690 } else if (eql(action
, "arc_negative")) {
1692 struct arc_args
*args
;
1694 if ((args
= malloc(sizeof(*args
))) == NULL
)
1696 op
->op
= ARC_NEGATIVE
;
1698 if (sscanf(raw_args
, "%lf %lf %lf %lf %lf %c",
1699 &args
->x
, &args
->y
, &args
->radius
, °1
, °2
, &dummy
) != 5)
1701 args
->angle1
= deg1
* (M_PI
/ 180.L
);
1702 args
->angle2
= deg2
* (M_PI
/ 180.L
);
1703 } else if (eql(action
, "curve_to")) {
1704 struct curve_to_args
*args
;
1706 if ((args
= malloc(sizeof(*args
))) == NULL
)
1710 if (sscanf(raw_args
, "%lf %lf %lf %lf %lf %lf %c",
1711 &args
->x1
, &args
->y1
, &args
->x2
, &args
->y2
, &args
->x3
, &args
->y3
, &dummy
) != 6)
1713 } else if (eql(action
, "rel_curve_to")) {
1714 struct curve_to_args
*args
;
1716 if ((args
= malloc(sizeof(*args
))) == NULL
)
1718 op
->op
= REL_CURVE_TO
;
1720 if (sscanf(raw_args
, "%lf %lf %lf %lf %lf %lf %c",
1721 &args
->x1
, &args
->y1
, &args
->x2
, &args
->y2
, &args
->x3
, &args
->y3
, &dummy
) != 6)
1723 } else if (eql(action
, "rectangle")) {
1724 struct rectangle_args
*args
;
1726 if ((args
= malloc(sizeof(*args
))) == NULL
)
1730 if (sscanf(raw_args
, "%lf %lf %lf %lf %c",
1731 &args
->x
, &args
->y
, &args
->width
, &args
->height
, &dummy
) != 4)
1733 } else if (eql(action
, "close_path")) {
1734 op
->op
= CLOSE_PATH
;
1735 if (sscanf(raw_args
, " %c", &dummy
) > 0)
1738 } else if (eql(action
, "show_text")) {
1739 struct show_text_args
*args
;
1742 len
= strlen(raw_args
) + 1;
1743 if ((args
= malloc(sizeof(*args
) + len
* sizeof(args
->text
[0]))) == NULL
)
1747 args
->len
= len
; /* not used */
1748 strncpy(args
->text
, raw_args
, len
);
1749 result
= NEED_REDRAW
;
1750 } else if (eql(action
, "rel_move_for")) {
1751 char ref_point
[2 + 1];
1753 struct rel_move_for_args
*args
;
1755 if (sscanf(raw_args
, "%2s %n", ref_point
, &start
) < 1)
1757 len
= strlen(raw_args
+ start
) + 1;
1758 if ((args
= malloc(sizeof(*args
) + len
* sizeof(args
->text
[0]))) == NULL
)
1760 if (eql(ref_point
, "c"))
1762 else if (eql(ref_point
, "e"))
1764 else if (eql(ref_point
, "n"))
1766 else if (eql(ref_point
, "ne"))
1768 else if (eql(ref_point
, "nw"))
1770 else if (eql(ref_point
, "s"))
1772 else if (eql(ref_point
, "se"))
1774 else if (eql(ref_point
, "sw"))
1776 else if (eql(ref_point
, "w"))
1780 op
->op
= REL_MOVE_FOR
;
1782 args
->len
= len
; /* not used */
1783 strncpy(args
->text
, (raw_args
+ start
), len
);
1784 } else if (eql(action
, "stroke")) {
1786 if (sscanf(raw_args
, " %c", &dummy
) > 0)
1789 result
= NEED_REDRAW
;
1790 } else if (eql(action
, "stroke_preserve")) {
1791 op
->op
= STROKE_PRESERVE
;
1792 if (sscanf(raw_args
, " %c", &dummy
) > 0)
1795 result
= NEED_REDRAW
;
1796 } else if (eql(action
, "fill")) {
1798 if (sscanf(raw_args
, " %c", &dummy
) > 0)
1801 result
= NEED_REDRAW
;
1802 } else if (eql(action
, "fill_preserve")) {
1803 op
->op
= FILL_PRESERVE
;
1804 if (sscanf(raw_args
, " %c", &dummy
) > 0)
1807 result
= NEED_REDRAW
;
1808 } else if (eql(action
, "set_dash")) {
1810 char data1
[strlen(raw_args
) + 1];
1812 struct set_dash_args
*args
;
1814 strcpy(data1
, raw_args
);
1821 } while (next
!= end
);
1822 if ((args
= malloc(sizeof(*args
) + n
* sizeof(args
->dashes
[0]))) == NULL
)
1826 args
->num_dashes
= n
;
1827 for (i
= 0, next
= data1
; i
< n
; i
++, next
= end
) {
1828 args
->dashes
[i
] = strtod(next
, &end
);
1830 } else if (eql(action
, "set_font_face")) {
1831 char slant
[7 + 1]; /* "oblique" */
1832 char weight
[6 + 1]; /* "normal" */
1833 int family_start
, family_len
;
1834 struct set_font_face_args
*args
;
1836 if (sscanf(raw_args
, "%7s %6s %n%*s", slant
, weight
, &family_start
) != 2)
1838 family_len
= strlen(raw_args
+ family_start
) + 1;
1839 if ((args
= malloc(sizeof(*args
) + family_len
* sizeof(args
->family
[0]))) == NULL
)
1841 op
->op
= SET_FONT_FACE
;
1843 strncpy(args
->family
, raw_args
+ family_start
, family_len
);
1844 if (eql(slant
, "normal"))
1845 args
->slant
= CAIRO_FONT_SLANT_NORMAL
;
1846 else if (eql(slant
, "italic"))
1847 args
->slant
= CAIRO_FONT_SLANT_ITALIC
;
1848 else if (eql(slant
, "oblique"))
1849 args
->slant
= CAIRO_FONT_SLANT_OBLIQUE
;
1852 if (eql(weight
, "normal"))
1853 args
->weight
= CAIRO_FONT_WEIGHT_NORMAL
;
1854 else if (eql(weight
, "bold"))
1855 args
->weight
= CAIRO_FONT_WEIGHT_BOLD
;
1858 } else if (eql(action
, "set_font_size")) {
1859 struct set_font_size_args
*args
;
1861 if ((args
= malloc(sizeof(*args
))) == NULL
)
1863 op
->op
= SET_FONT_SIZE
;
1865 if (sscanf(raw_args
, "%lf %c", &args
->size
, &dummy
) != 1)
1867 } else if (eql(action
, "set_line_cap")) {
1868 char str
[6 + 1]; /* "square" */
1869 struct set_line_cap_args
*args
;
1871 if ((args
= malloc(sizeof(*args
))) == NULL
)
1873 op
->op
= SET_LINE_CAP
;
1875 if (sscanf(raw_args
, "%6s %c", str
, &dummy
) != 1)
1877 if (eql(str
, "butt"))
1878 args
->line_cap
= CAIRO_LINE_CAP_BUTT
;
1879 else if (eql(str
, "round"))
1880 args
->line_cap
= CAIRO_LINE_CAP_ROUND
;
1881 else if (eql(str
, "square"))
1882 args
->line_cap
= CAIRO_LINE_CAP_SQUARE
;
1885 } else if (eql(action
, "set_line_join")) {
1886 char str
[5 + 1]; /* "miter" */
1887 struct set_line_join_args
*args
;
1889 if ((args
= malloc(sizeof(*args
))) == NULL
)
1891 op
->op
= SET_LINE_JOIN
;
1893 if (sscanf(raw_args
, "%5s %c", str
, &dummy
) != 1)
1895 if (eql(str
, "miter"))
1896 args
->line_join
= CAIRO_LINE_JOIN_MITER
;
1897 else if (eql(str
, "round"))
1898 args
->line_join
= CAIRO_LINE_JOIN_ROUND
;
1899 else if (eql(str
, "bevel"))
1900 args
->line_join
= CAIRO_LINE_JOIN_BEVEL
;
1903 } else if (eql(action
, "set_line_width")) {
1904 struct set_line_width_args
*args
;
1906 if ((args
= malloc(sizeof(*args
))) == NULL
)
1908 op
->op
= SET_LINE_WIDTH
;
1910 if (sscanf(raw_args
, "%lf %c", &args
->width
, &dummy
) != 1)
1912 } else if (eql(action
, "set_source_rgba")) {
1913 struct set_source_rgba_args
*args
;
1915 if ((args
= malloc(sizeof(*args
))) == NULL
)
1917 op
->op
= SET_SOURCE_RGBA
;
1919 gdk_rgba_parse(&args
->color
, raw_args
);
1920 } else if (eql(action
, "transform")) {
1922 double xx
, yx
, xy
, yy
, x0
, y0
;
1924 if (sscanf(raw_args
, "%lf %lf %lf %lf %lf %lf %c",
1925 &xx
, &yx
, &xy
, &yy
, &x0
, &y0
, &dummy
) == 6) {
1926 struct transform_args
*args
;
1928 if ((args
= malloc(sizeof(*args
))) == NULL
)
1932 cairo_matrix_init(&args
->matrix
, xx
, yx
, xy
, yy
, x0
, y0
);
1933 } else if (sscanf(raw_args
, " %c", &dummy
) < 1) {
1938 } else if (eql(action
, "translate")) {
1940 struct transform_args
*args
;
1942 if ((args
= malloc(sizeof(*args
))) == NULL
)
1946 if (sscanf(raw_args
, "%lf %lf %c", &tx
, &ty
, &dummy
) != 2)
1948 cairo_matrix_init_translate(&args
->matrix
, tx
, ty
);
1949 } else if (eql(action
, "scale")) {
1951 struct transform_args
*args
;
1953 if ((args
= malloc(sizeof(*args
))) == NULL
)
1957 if (sscanf(raw_args
, "%lf %lf %c", &sx
, &sy
, &dummy
) != 2)
1959 cairo_matrix_init_scale(&args
->matrix
, sx
, sy
);
1960 } else if (eql(action
, "rotate")) {
1962 struct transform_args
*args
;
1964 if ((args
= malloc(sizeof(*args
))) == NULL
)
1968 if (sscanf(raw_args
, "%lf %c", &angle
, &dummy
) != 1)
1970 cairo_matrix_init_rotate(&args
->matrix
, angle
* (M_PI
/ 180.L
));
1977 * Add another element to widget's "draw_ops" list
1979 static enum draw_op_stat
1980 ins_draw_op(GObject
*widget
, const char *action
, const char *data
)
1982 enum draw_op_stat result
;
1983 struct draw_op
*new_op
= NULL
, *draw_ops
= NULL
, *prev_op
= NULL
;
1985 if ((new_op
= malloc(sizeof(*new_op
))) == NULL
)
1987 new_op
->op_args
= NULL
;
1988 new_op
->next
= NULL
;
1989 if ((result
= set_draw_op(new_op
, action
, data
)) == FAILURE
) {
1990 free(new_op
->op_args
);
1994 switch (new_op
->policy
) {
1996 if ((draw_ops
= g_object_get_data(widget
, "draw_ops")) == NULL
)
1997 g_object_set_data(widget
, "draw_ops", new_op
);
1999 for (prev_op
= draw_ops
;
2000 prev_op
->next
!= NULL
;
2001 prev_op
= prev_op
->next
);
2002 prev_op
->next
= new_op
;
2006 for (prev_op
= NULL
, draw_ops
= g_object_get_data(widget
, "draw_ops");
2007 draw_ops
!= NULL
&& draw_ops
->id
!= new_op
->before
;
2008 prev_op
= draw_ops
, draw_ops
= draw_ops
->next
);
2009 if (prev_op
== NULL
) { /* prepend a new first element */
2010 g_object_set_data(widget
, "draw_ops", new_op
);
2011 new_op
->next
= draw_ops
;
2012 } else if (draw_ops
== NULL
) /* append */
2013 prev_op
->next
= new_op
;
2015 new_op
->next
= draw_ops
;
2016 prev_op
->next
= new_op
;
2020 for (prev_op
= NULL
, draw_ops
= g_object_get_data(widget
, "draw_ops");
2021 draw_ops
!= NULL
&& draw_ops
->id
!= new_op
->id
;
2022 prev_op
= draw_ops
, draw_ops
= draw_ops
->next
);
2023 if (draw_ops
== NULL
&& prev_op
== NULL
) /* start a new list */
2024 g_object_set_data(widget
, "draw_ops", new_op
);
2025 else if (prev_op
== NULL
) { /* replace the first element */
2026 g_object_set_data(widget
, "draw_ops", new_op
);
2027 new_op
->next
= draw_ops
->next
;
2028 free(draw_ops
->op_args
);
2030 } else if (draw_ops
== NULL
) /* append */
2031 prev_op
->next
= new_op
;
2032 else { /* replace some other element */
2033 new_op
->next
= draw_ops
->next
;
2034 prev_op
->next
= new_op
;
2035 free(draw_ops
->op_args
);
2047 * Remove all elements with the given id from widget's "draw_ops" list
2049 static enum draw_op_stat
2050 rem_draw_op(GObject
*widget
, const char *data
)
2053 struct draw_op
*op
, *next_op
, *prev_op
= NULL
;
2054 unsigned long long int id
;
2056 if (sscanf(data
, "%llu %c", &id
, &dummy
) != 1)
2058 op
= g_object_get_data(widget
, "draw_ops");
2059 while (op
!= NULL
) {
2062 if (prev_op
== NULL
) /* list head */
2063 g_object_set_data(widget
, "draw_ops", op
->next
);
2065 prev_op
->next
= op
->next
;
2076 refresh_widget(GtkWidget
*widget
)
2078 gint height
= gtk_widget_get_allocated_height(widget
);
2079 gint width
= gtk_widget_get_allocated_width(widget
);
2081 gtk_widget_queue_draw_area(widget
, 0, 0, width
, height
);
2082 return G_SOURCE_REMOVE
;
2086 update_drawing_area(struct ui_data
*ud
)
2088 enum draw_op_stat dost
;
2090 if (eql(ud
->action
, "remove"))
2091 dost
= rem_draw_op(ud
->obj
, ud
->data
);
2093 dost
= ins_draw_op(ud
->obj
, ud
->action
, ud
->data
);
2096 gdk_threads_add_idle_full(G_PRIORITY_LOW
,
2097 (GSourceFunc
) refresh_widget
,
2098 GTK_WIDGET(ud
->obj
), NULL
);
2101 try_generic_cmds(ud
);
2112 update_entry(struct ui_data
*ud
)
2114 GtkEntry
*entry
= GTK_ENTRY(ud
->obj
);
2116 if (eql(ud
->action
, "set_text"))
2117 gtk_entry_set_text(entry
, ud
->data
);
2118 else if (eql(ud
->action
, "set_placeholder_text"))
2119 gtk_entry_set_placeholder_text(entry
, ud
->data
);
2121 try_generic_cmds(ud
);
2125 update_expander(struct ui_data
*ud
)
2127 GtkExpander
*expander
= GTK_EXPANDER(ud
->obj
);
2131 if (eql(ud
->action
, "set_expanded") &&
2132 sscanf(ud
->data
, "%u %c", &val
, &dummy
) == 1 && val
< 2)
2133 gtk_expander_set_expanded(expander
, val
);
2134 else if (eql(ud
->action
, "set_label"))
2135 gtk_expander_set_label(expander
, ud
->data
);
2137 try_generic_cmds(ud
);
2141 update_file_chooser_button(struct ui_data
*ud
)
2143 if (eql(ud
->action
, "set_filename"))
2144 gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(ud
->obj
), ud
->data
);
2146 try_generic_cmds(ud
);
2150 update_file_chooser_dialog(struct ui_data
*ud
)
2152 GtkFileChooser
*chooser
= GTK_FILE_CHOOSER(ud
->obj
);
2154 if (eql(ud
->action
, "set_filename"))
2155 gtk_file_chooser_set_filename(chooser
, ud
->data
);
2156 else if (eql(ud
->action
, "set_current_name"))
2157 gtk_file_chooser_set_current_name(chooser
, ud
->data
);
2158 else if (update_class_window(ud
));
2160 try_generic_cmds(ud
);
2164 update_font_button(struct ui_data
*ud
){
2165 GtkFontButton
*font_button
= GTK_FONT_BUTTON(ud
->obj
);
2167 if (eql(ud
->action
, "set_font_name"))
2168 gtk_font_button_set_font_name(font_button
, ud
->data
);
2170 try_generic_cmds(ud
);
2174 update_frame(struct ui_data
*ud
)
2176 if (eql(ud
->action
, "set_label"))
2177 gtk_frame_set_label(GTK_FRAME(ud
->obj
), ud
->data
);
2179 try_generic_cmds(ud
);
2183 update_image(struct ui_data
*ud
)
2186 GtkImage
*image
= GTK_IMAGE(ud
->obj
);
2188 gtk_image_get_icon_name(image
, NULL
, &size
);
2189 if (eql(ud
->action
, "set_from_file"))
2190 gtk_image_set_from_file(image
, ud
->data
);
2191 else if (eql(ud
->action
, "set_from_icon_name"))
2192 gtk_image_set_from_icon_name(image
, ud
->data
, size
);
2194 try_generic_cmds(ud
);
2198 update_label(struct ui_data
*ud
)
2200 if (eql(ud
->action
, "set_text"))
2201 gtk_label_set_text(GTK_LABEL(ud
->obj
), ud
->data
);
2203 try_generic_cmds(ud
);
2207 update_link_button(struct ui_data
*ud
)
2212 if (eql(ud
->action
, "set_visited") &&
2213 sscanf(ud
->data
, "%u %c", &val
, &dummy
) == 1 && val
< 2)
2214 gtk_link_button_set_visited(GTK_LINK_BUTTON(ud
->obj
), val
);
2220 update_menu(struct ui_data
*ud
)
2223 GtkMenu
* menu
= GTK_MENU(ud
->obj
);
2225 if (eql(ud
->action
, "popup") && sscanf(ud
->data
, " %c", &dummy
) < 1)
2226 gtk_menu_popup(menu
, NULL
, NULL
, NULL
, NULL
, 0,
2227 gtk_get_current_event_time());
2228 else if (eql(ud
->action
, "popdown") && sscanf(ud
->data
, " %c", &dummy
) < 1)
2229 gtk_menu_popdown(menu
);
2231 try_generic_cmds(ud
);
2235 update_menu_item(struct ui_data
*ud
)
2237 try_generic_cmds(ud
);
2241 update_notebook(struct ui_data
*ud
)
2244 int val
, n_pages
= gtk_notebook_get_n_pages(GTK_NOTEBOOK(ud
->obj
));
2246 if (eql(ud
->action
, "set_current_page") &&
2247 sscanf(ud
->data
, "%d %c", &val
, &dummy
) == 1 &&
2248 val
>= 0 && val
< n_pages
)
2249 gtk_notebook_set_current_page(GTK_NOTEBOOK(ud
->obj
), val
);
2251 try_generic_cmds(ud
);
2255 update_nothing(struct ui_data
*ud
)
2261 update_print_dialog(struct ui_data
*ud
)
2263 GtkPageSetup
*page_setup
;
2265 GtkPrintSettings
*settings
;
2266 GtkPrintUnixDialog
*dialog
= GTK_PRINT_UNIX_DIALOG(ud
->obj
);
2267 GtkPrinter
*printer
;
2270 if (eql(ud
->action
, "print")) {
2271 response_id
= gtk_dialog_run(GTK_DIALOG(dialog
));
2272 switch (response_id
) {
2273 case GTK_RESPONSE_OK
:
2274 printer
= gtk_print_unix_dialog_get_selected_printer(dialog
);
2275 settings
= gtk_print_unix_dialog_get_settings(dialog
);
2276 page_setup
= gtk_print_unix_dialog_get_page_setup(dialog
);
2277 job
= gtk_print_job_new(ud
->data
, printer
, settings
, page_setup
);
2278 if (gtk_print_job_set_source_file(job
, ud
->data
, NULL
))
2279 gtk_print_job_send(job
, NULL
, NULL
, NULL
);
2281 ign_cmd(ud
->type
, ud
->cmd
);
2282 g_clear_object(&settings
);
2283 g_clear_object(&job
);
2285 case GTK_RESPONSE_CANCEL
:
2286 case GTK_RESPONSE_DELETE_EVENT
:
2289 fprintf(stderr
, "%s sent an unexpected response id (%d)\n",
2290 widget_id(GTK_BUILDABLE(dialog
)), response_id
);
2293 gtk_widget_hide(GTK_WIDGET(dialog
));
2295 try_generic_cmds(ud
);
2299 update_progress_bar(struct ui_data
*ud
)
2301 GtkProgressBar
*progressbar
= GTK_PROGRESS_BAR(ud
->obj
);
2305 if (eql(ud
->action
, "set_text"))
2306 gtk_progress_bar_set_text(progressbar
, *(ud
->data
) == '\0' ? NULL
: ud
->data
);
2307 else if (eql(ud
->action
, "set_fraction") &&
2308 sscanf(ud
->data
, "%lf %c", &frac
, &dummy
) == 1)
2309 gtk_progress_bar_set_fraction(progressbar
, frac
);
2311 try_generic_cmds(ud
);
2315 update_scale(struct ui_data
*ud
)
2317 GtkRange
*range
= GTK_RANGE(ud
->obj
);
2321 if (eql(ud
->action
, "set_value") && sscanf(ud
->data
, "%lf %c", &val1
, &dummy
) == 1)
2322 gtk_range_set_value(range
, val1
);
2323 else if (eql(ud
->action
, "set_fill_level") &&
2324 sscanf(ud
->data
, "%lf %c", &val1
, &dummy
) == 1) {
2325 gtk_range_set_fill_level(range
, val1
);
2326 gtk_range_set_show_fill_level(range
, TRUE
);
2327 } else if (eql(ud
->action
, "set_fill_level") &&
2328 sscanf(ud
->data
, " %c", &dummy
) < 1)
2329 gtk_range_set_show_fill_level(range
, FALSE
);
2330 else if (eql(ud
->action
, "set_range") &&
2331 sscanf(ud
->data
, "%lf %lf %c", &val1
, &val2
, &dummy
) == 2)
2332 gtk_range_set_range(range
, val1
, val2
);
2333 else if (eql(ud
->action
, "set_increments") &&
2334 sscanf(ud
->data
, "%lf %lf %c", &val1
, &val2
, &dummy
) == 2)
2335 gtk_range_set_increments(range
, val1
, val2
);
2337 try_generic_cmds(ud
);
2341 update_scrolled_window(struct ui_data
*ud
)
2343 GtkScrolledWindow
*window
= GTK_SCROLLED_WINDOW(ud
->obj
);
2344 GtkAdjustment
*hadj
= gtk_scrolled_window_get_hadjustment(window
);
2345 GtkAdjustment
*vadj
= gtk_scrolled_window_get_vadjustment(window
);
2349 if (eql(ud
->action
, "hscroll") && sscanf(ud
->data
, "%lf %c", &d0
, &dummy
) == 1)
2350 gtk_adjustment_set_value(hadj
, d0
);
2351 else if (eql(ud
->action
, "vscroll") && sscanf(ud
->data
, "%lf %c", &d0
, &dummy
) == 1)
2352 gtk_adjustment_set_value(vadj
, d0
);
2353 else if (eql(ud
->action
, "hscroll_to_range") &&
2354 sscanf(ud
->data
, "%lf %lf %c", &d0
, &d1
, &dummy
) == 2)
2355 gtk_adjustment_clamp_page(hadj
, d0
, d1
);
2356 else if (eql(ud
->action
, "vscroll_to_range") &&
2357 sscanf(ud
->data
, "%lf %lf %c", &d0
, &d1
, &dummy
) == 2)
2358 gtk_adjustment_clamp_page(vadj
, d0
, d1
);
2360 try_generic_cmds(ud
);
2364 update_socket(struct ui_data
*ud
)
2366 GtkSocket
*socket
= GTK_SOCKET(ud
->obj
);
2368 char str
[BUFLEN
], dummy
;
2370 if (eql(ud
->action
, "id") && sscanf(ud
->data
, " %c", &dummy
) < 1) {
2371 id
= gtk_socket_get_id(socket
);
2372 snprintf(str
, BUFLEN
, "%lu", id
);
2373 send_msg(ud
->args
->fout
, GTK_BUILDABLE(socket
), "id", str
, NULL
);
2375 try_generic_cmds(ud
);
2379 update_spin_button(struct ui_data
*ud
)
2381 GtkSpinButton
*spinbutton
= GTK_SPIN_BUTTON(ud
->obj
);
2385 if (eql(ud
->action
, "set_text") && /* TODO: rename to "set_value" */
2386 sscanf(ud
->data
, "%lf %c", &val1
, &dummy
) == 1)
2387 gtk_spin_button_set_value(spinbutton
, val1
);
2388 else if (eql(ud
->action
, "set_range") &&
2389 sscanf(ud
->data
, "%lf %lf %c", &val1
, &val2
, &dummy
) == 2)
2390 gtk_spin_button_set_range(spinbutton
, val1
, val2
);
2391 else if (eql(ud
->action
, "set_increments") &&
2392 sscanf(ud
->data
, "%lf %lf %c", &val1
, &val2
, &dummy
) == 2)
2393 gtk_spin_button_set_increments(spinbutton
, val1
, val2
);
2395 try_generic_cmds(ud
);
2399 update_spinner(struct ui_data
*ud
)
2401 GtkSpinner
*spinner
= GTK_SPINNER(ud
->obj
);
2404 if (eql(ud
->action
, "start") && sscanf(ud
->data
, " %c", &dummy
) < 1)
2405 gtk_spinner_start(spinner
);
2406 else if (eql(ud
->action
, "stop") && sscanf(ud
->data
, " %c", &dummy
) < 1)
2407 gtk_spinner_stop(spinner
);
2409 try_generic_cmds(ud
);
2413 update_statusbar(struct ui_data
*ud
)
2415 GtkStatusbar
*statusbar
= GTK_STATUSBAR(ud
->obj
);
2416 char *ctx_msg
, dummy
;
2417 const char *status_msg
;
2420 /* TODO: remove "push", "pop", "remove_all"; rename "push_id" to "push", etc. */
2421 if ((ctx_msg
= malloc(strlen(ud
->data
) + 1)) == NULL
)
2423 t
= sscanf(ud
->data
, "%s %n%c", ctx_msg
, &ctx_len
, &dummy
);
2424 status_msg
= ud
->data
+ ctx_len
;
2425 if (eql(ud
->action
, "push"))
2426 gtk_statusbar_push(statusbar
,
2427 gtk_statusbar_get_context_id(statusbar
, "0"),
2429 else if (eql(ud
->action
, "push_id") && t
>= 1)
2430 gtk_statusbar_push(statusbar
,
2431 gtk_statusbar_get_context_id(statusbar
, ctx_msg
),
2433 else if (eql(ud
->action
, "pop") && t
< 1)
2434 gtk_statusbar_pop(statusbar
,
2435 gtk_statusbar_get_context_id(statusbar
, "0"));
2436 else if (eql(ud
->action
, "pop_id") && t
== 1)
2437 gtk_statusbar_pop(statusbar
,
2438 gtk_statusbar_get_context_id(statusbar
, ctx_msg
));
2439 else if (eql(ud
->action
, "remove_all") && t
< 1)
2440 gtk_statusbar_remove_all(statusbar
,
2441 gtk_statusbar_get_context_id(statusbar
, "0"));
2442 else if (eql(ud
->action
, "remove_all_id") && t
== 1)
2443 gtk_statusbar_remove_all(statusbar
,
2444 gtk_statusbar_get_context_id(statusbar
, ctx_msg
));
2446 try_generic_cmds(ud
);
2451 update_switch(struct ui_data
*ud
)
2456 if (eql(ud
->action
, "set_active") &&
2457 sscanf(ud
->data
, "%u %c", &val
, &dummy
) == 1 && val
< 2)
2458 gtk_switch_set_active(GTK_SWITCH(ud
->obj
), val
);
2460 try_generic_cmds(ud
);
2464 update_text_view(struct ui_data
*ud
)
2467 GtkTextView
*view
= GTK_TEXT_VIEW(ud
->obj
);
2468 GtkTextBuffer
*textbuf
= gtk_text_view_get_buffer(view
);
2473 if (eql(ud
->action
, "set_text"))
2474 gtk_text_buffer_set_text(textbuf
, ud
->data
, -1);
2475 else if (eql(ud
->action
, "delete") && sscanf(ud
->data
, " %c", &dummy
) < 1) {
2476 gtk_text_buffer_get_bounds(textbuf
, &a
, &b
);
2477 gtk_text_buffer_delete(textbuf
, &a
, &b
);
2478 } else if (eql(ud
->action
, "insert_at_cursor"))
2479 gtk_text_buffer_insert_at_cursor(textbuf
, ud
->data
, -1);
2480 else if (eql(ud
->action
, "place_cursor") && eql(ud
->data
, "end")) {
2481 gtk_text_buffer_get_end_iter(textbuf
, &a
);
2482 gtk_text_buffer_place_cursor(textbuf
, &a
);
2483 } else if (eql(ud
->action
, "place_cursor") &&
2484 sscanf(ud
->data
, "%d %c", &val
, &dummy
) == 1) {
2485 gtk_text_buffer_get_iter_at_offset(textbuf
, &a
, val
);
2486 gtk_text_buffer_place_cursor(textbuf
, &a
);
2487 } else if (eql(ud
->action
, "place_cursor_at_line") &&
2488 sscanf(ud
->data
, "%d %c", &val
, &dummy
) == 1) {
2489 gtk_text_buffer_get_iter_at_line(textbuf
, &a
, val
);
2490 gtk_text_buffer_place_cursor(textbuf
, &a
);
2491 } else if (eql(ud
->action
, "scroll_to_cursor") &&
2492 sscanf(ud
->data
, " %c", &dummy
) < 1)
2493 gtk_text_view_scroll_to_mark(view
, gtk_text_buffer_get_insert(textbuf
),
2495 else if (eql(ud
->action
, "save") && ud
->data
!= NULL
&&
2496 (sv
= fopen(ud
->data
, "w")) != NULL
) {
2497 gtk_text_buffer_get_bounds(textbuf
, &a
, &b
);
2498 send_msg(sv
, GTK_BUILDABLE(view
), "insert_at_cursor",
2499 gtk_text_buffer_get_text(textbuf
, &a
, &b
, TRUE
), NULL
);
2502 try_generic_cmds(ud
);
2506 update_toggle_button(struct ui_data
*ud
)
2511 if (eql(ud
->action
, "set_label"))
2512 gtk_button_set_label(GTK_BUTTON(ud
->obj
), ud
->data
);
2513 else if (eql(ud
->action
, "set_active") &&
2514 sscanf(ud
->data
, "%u %c", &val
, &dummy
) == 1 && val
< 2)
2515 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ud
->obj
), val
);
2517 try_generic_cmds(ud
);
2521 * update_tree_view(), which runs inside gtk_main(), needs a few
2526 * Check if s is a valid string representation of a GtkTreePath
2529 is_path_string(char *s
)
2532 strlen(s
) == strspn(s
, ":0123456789") &&
2533 strstr(s
, "::") == NULL
&&
2534 strcspn(s
, ":") > 0;
2538 tree_model_insert_before(GtkTreeModel
*model
, GtkTreeIter
*iter
,
2539 GtkTreeIter
*parent
, GtkTreeIter
*sibling
)
2541 if (GTK_IS_TREE_STORE(model
))
2542 gtk_tree_store_insert_before(GTK_TREE_STORE(model
),
2543 iter
, parent
, sibling
);
2544 else if (GTK_IS_LIST_STORE(model
))
2545 gtk_list_store_insert_before(GTK_LIST_STORE(model
),
2552 tree_model_insert_after(GtkTreeModel
*model
, GtkTreeIter
*iter
,
2553 GtkTreeIter
*parent
, GtkTreeIter
*sibling
)
2555 if (GTK_IS_TREE_STORE(model
))
2556 gtk_tree_store_insert_after(GTK_TREE_STORE(model
),
2557 iter
, parent
, sibling
);
2558 else if (GTK_IS_LIST_STORE(model
))
2559 gtk_list_store_insert_after(GTK_LIST_STORE(model
),
2566 tree_model_move_before(GtkTreeModel
*model
, GtkTreeIter
*iter
,
2567 GtkTreeIter
*position
)
2569 if (GTK_IS_TREE_STORE(model
))
2570 gtk_tree_store_move_before(GTK_TREE_STORE(model
), iter
, position
);
2571 else if (GTK_IS_LIST_STORE(model
))
2572 gtk_list_store_move_before(GTK_LIST_STORE(model
), iter
, position
);
2578 tree_model_remove(GtkTreeModel
*model
, GtkTreeIter
*iter
)
2580 if (GTK_IS_TREE_STORE(model
))
2581 gtk_tree_store_remove(GTK_TREE_STORE(model
), iter
);
2582 else if (GTK_IS_LIST_STORE(model
))
2583 gtk_list_store_remove(GTK_LIST_STORE(model
), iter
);
2589 tree_model_clear(GtkTreeModel
*model
)
2591 if (GTK_IS_TREE_STORE(model
))
2592 gtk_tree_store_clear(GTK_TREE_STORE(model
));
2593 else if (GTK_IS_LIST_STORE(model
))
2594 gtk_list_store_clear(GTK_LIST_STORE(model
));
2600 tree_model_set(GtkTreeModel
*model
, GtkTreeIter
*iter
, ...)
2605 if (GTK_IS_TREE_STORE(model
))
2606 gtk_tree_store_set_valist(GTK_TREE_STORE(model
), iter
, ap
);
2607 else if (GTK_IS_LIST_STORE(model
))
2608 gtk_list_store_set_valist(GTK_LIST_STORE(model
), iter
, ap
);
2615 * Create an empty row at path if it doesn't yet exist. Create older
2616 * siblings and parents as necessary.
2619 create_subtree(GtkTreeModel
*model
, GtkTreePath
*path
, GtkTreeIter
*iter
)
2621 GtkTreeIter iter_1
; /* iter's predecessor */
2622 GtkTreePath
*path_1
; /* path's predecessor */
2624 if (gtk_tree_path_get_depth(path
) > 0 &&
2625 gtk_tree_model_get_iter(model
, iter
, path
))
2627 path_1
= gtk_tree_path_copy(path
);
2628 if (gtk_tree_path_prev(path_1
)) { /* need an older sibling */
2629 create_subtree(model
, path_1
, iter
);
2631 tree_model_insert_after(model
, iter
, NULL
, &iter_1
);
2632 } else if (gtk_tree_path_up(path_1
)) { /* need a parent */
2633 create_subtree(model
, path_1
, iter
);
2634 if (gtk_tree_path_get_depth(path_1
) == 0)
2635 /* first toplevel row */
2636 tree_model_insert_after(model
, iter
, NULL
, NULL
);
2637 else { /* first row in a lower level */
2639 tree_model_insert_after(model
, iter
, &iter_1
, NULL
);
2641 } /* neither prev nor up mean we're at the root of an empty tree */
2642 gtk_tree_path_free(path_1
);
2646 set_tree_view_cell(GtkTreeModel
*model
, GtkTreeIter
*iter
,
2647 const char *path_s
, int col
, const char *new_text
)
2649 GType col_type
= gtk_tree_model_get_column_type(model
, col
);
2656 path
= gtk_tree_path_new_from_string(path_s
);
2658 case G_TYPE_BOOLEAN
:
2665 if (new_text
!= NULL
&&
2666 sscanf(new_text
, "%lld %c", &n
, &dummy
) == 1) {
2667 create_subtree(model
, path
, iter
);
2668 tree_model_set(model
, iter
, col
, n
, -1);
2674 if (new_text
!= NULL
&&
2675 sscanf(new_text
, "%lf %c", &d
, &dummy
) == 1) {
2676 create_subtree(model
, path
, iter
);
2677 tree_model_set(model
, iter
, col
, d
, -1);
2682 create_subtree(model
, path
, iter
);
2683 tree_model_set(model
, iter
, col
, new_text
, -1);
2687 fprintf(stderr
, "column %d: %s not implemented\n",
2688 col
, g_type_name(col_type
));
2692 gtk_tree_path_free(path
);
2697 tree_view_set_cursor(GtkTreeView
*view
, GtkTreePath
*path
, GtkTreeViewColumn
*col
)
2699 /* GTK+ 3.14 requires this. For 3.18, path = NULL */
2700 /* is just fine and this function need not exist. */
2702 path
= gtk_tree_path_new();
2703 gtk_tree_view_set_cursor(view
, path
, col
, false);
2707 update_tree_view(struct ui_data
*ud
)
2709 GtkTreeView
*view
= GTK_TREE_VIEW(ud
->obj
);
2710 GtkTreeIter iter0
, iter1
;
2711 GtkTreeModel
*model
= gtk_tree_view_get_model(view
);
2712 GtkTreePath
*path
= NULL
;
2713 GtkTreeSelection
*sel
= gtk_tree_view_get_selection(view
);
2714 bool iter0_valid
, iter1_valid
;
2715 char *tokens
, *arg0
, *arg1
, *arg2
;
2716 int col
= -1; /* invalid column number */
2719 if (!GTK_IS_LIST_STORE(model
) && !GTK_IS_TREE_STORE(model
))
2721 fprintf(stderr
, "missing model/");
2722 ign_cmd(ud
->type
, ud
->cmd
);
2725 if ((tokens
= malloc(strlen(ud
->data
) + 1)) == NULL
)
2727 strcpy(tokens
, ud
->data
);
2728 arg0
= strtok(tokens
, WHITESPACE
);
2729 arg1
= strtok(NULL
, WHITESPACE
);
2730 arg2
= strtok(NULL
, "");
2731 iter0_valid
= is_path_string(arg0
) &&
2732 gtk_tree_model_get_iter_from_string(model
, &iter0
, arg0
);
2733 iter1_valid
= is_path_string(arg1
) &&
2734 gtk_tree_model_get_iter_from_string(model
, &iter1
, arg1
);
2735 if (is_path_string(arg1
))
2736 col
= strtol(arg1
, NULL
, 10);
2737 if (eql(ud
->action
, "set") &&
2739 col
< gtk_tree_model_get_n_columns(model
) &&
2740 is_path_string(arg0
)) {
2741 if (set_tree_view_cell(model
, &iter0
, arg0
, col
, arg2
) == false)
2742 ign_cmd(ud
->type
, ud
->cmd
);
2743 } else if (eql(ud
->action
, "scroll") && iter0_valid
&& iter1_valid
&&
2745 path
= gtk_tree_path_new_from_string(arg0
);
2746 gtk_tree_view_scroll_to_cell (view
,
2748 gtk_tree_view_get_column(view
, col
),
2750 } else if (eql(ud
->action
, "expand") && iter0_valid
&& arg1
== NULL
) {
2751 path
= gtk_tree_path_new_from_string(arg0
);
2752 gtk_tree_view_expand_row(view
, path
, false);
2753 } else if (eql(ud
->action
, "expand_all") && iter0_valid
&& arg1
== NULL
) {
2754 path
= gtk_tree_path_new_from_string(arg0
);
2755 gtk_tree_view_expand_row(view
, path
, true);
2756 } else if (eql(ud
->action
, "expand_all") && arg0
== NULL
)
2757 gtk_tree_view_expand_all(view
);
2758 else if (eql(ud
->action
, "collapse") && iter0_valid
&& arg1
== NULL
) {
2759 path
= gtk_tree_path_new_from_string(arg0
);
2760 gtk_tree_view_collapse_row(view
, path
);
2761 } else if (eql(ud
->action
, "collapse") && arg0
== NULL
)
2762 gtk_tree_view_collapse_all(view
);
2763 else if (eql(ud
->action
, "set_cursor") && iter0_valid
&& arg1
== NULL
) {
2764 path
= gtk_tree_path_new_from_string(arg0
);
2765 tree_view_set_cursor(view
, path
, NULL
);
2766 } else if (eql(ud
->action
, "set_cursor") && arg0
== NULL
) {
2767 tree_view_set_cursor(view
, NULL
, NULL
);
2768 gtk_tree_selection_unselect_all(sel
);
2769 } else if (eql(ud
->action
, "insert_row") &&
2770 eql(arg0
, "end") && arg1
== NULL
)
2771 tree_model_insert_before(model
, &iter1
, NULL
, NULL
);
2772 else if (eql(ud
->action
, "insert_row") && iter0_valid
&&
2773 eql(arg1
, "as_child") && arg2
== NULL
)
2774 tree_model_insert_after(model
, &iter1
, &iter0
, NULL
);
2775 else if (eql(ud
->action
, "insert_row") && iter0_valid
&& arg1
== NULL
)
2776 tree_model_insert_before(model
, &iter1
, NULL
, &iter0
);
2777 else if (eql(ud
->action
, "move_row") && iter0_valid
&&
2778 eql(arg1
, "end") && arg2
== NULL
)
2779 tree_model_move_before(model
, &iter0
, NULL
);
2780 else if (eql(ud
->action
, "move_row") && iter0_valid
&& iter1_valid
&& arg2
== NULL
)
2781 tree_model_move_before(model
, &iter0
, &iter1
);
2782 else if (eql(ud
->action
, "remove_row") && iter0_valid
&& arg1
== NULL
)
2783 tree_model_remove(model
, &iter0
);
2784 else if (eql(ud
->action
, "clear") && arg0
== NULL
) {
2785 tree_view_set_cursor(view
, NULL
, NULL
);
2786 gtk_tree_selection_unselect_all(sel
);
2787 tree_model_clear(model
);
2788 } else if (eql(ud
->action
, "block") && arg0
!= NULL
) {
2789 ud
->obj
=G_OBJECT(sel
);
2791 } else if (eql(ud
->action
, "save") && arg0
!= NULL
&&
2792 (ar
.fout
= fopen(arg0
, "w")) != NULL
) {
2794 gtk_tree_model_foreach(model
,
2795 (GtkTreeModelForeachFunc
) save_tree_row_msg
,
2799 try_generic_cmds(ud
);
2801 gtk_tree_path_free(path
);
2805 update_window(struct ui_data
*ud
)
2807 if (!update_class_window(ud
))
2808 try_generic_cmds(ud
);
2812 * The final UI update. Runs inside gtk_main().
2815 main_quit(struct ui_data
*ud
)
2819 if (sscanf(ud
->data
, " %c", &dummy
) < 1)
2822 try_generic_cmds(ud
);
2826 * Don't update anything; just complain from inside gtk_main()
2829 complain(struct ui_data
*ud
)
2831 ign_cmd(ud
->type
, ud
->cmd
);
2835 * Parse command pointed to by ud, and act on ui accordingly. Runs
2836 * once per command inside gtk_main().
2839 update_ui(struct ui_data
*ud
)
2841 char *lc
= lc_numeric();
2844 free(ud
->cmd_tokens
);
2847 lc_numeric_free(lc
);
2848 return G_SOURCE_REMOVE
;
2852 * Keep track of loading files to avoid recursive loading of the same
2853 * file. If filename = NULL, forget the most recently remembered file.
2856 remember_loading_file(char *filename
)
2858 static char *filenames
[BUFLEN
];
2859 static size_t latest
= 0;
2862 if (filename
== NULL
) { /* pop */
2868 for (i
= 1; i
<= latest
; i
++)
2869 if (eql(filename
, filenames
[i
]))
2871 if (latest
> BUFLEN
-2)
2873 filenames
[++latest
] = filename
;
2879 * Read lines from stream cmd and perform appropriate actions on the
2880 * GUI. Runs inside receiver thread.
2883 digest_cmd(struct info
*ar
)
2885 static int recursion
= -1; /* > 0 means this is a recursive call */
2889 FILE *cmd
= ar
->fin
;
2890 struct ui_data
*ud
= NULL
;
2891 char first_char
= '\0';
2892 char *id
; /* widget id */
2893 size_t msg_size
= 32;
2894 int id_start
= 0, id_end
= 0;
2895 int action_start
= 0, action_end
= 0;
2900 if ((ud
= malloc(sizeof(*ud
))) == NULL
)
2902 if ((ud
->cmd
= malloc(msg_size
)) == NULL
)
2905 ud
->type
= G_TYPE_INVALID
;
2906 pthread_testcancel();
2908 log_msg(ar
->flog
, NULL
);
2909 data_start
= read_buf(cmd
, &ud
->cmd
, &msg_size
);
2911 log_msg(ar
->flog
, ud
->cmd
);
2912 if ((ud
->cmd_tokens
= malloc(strlen(ud
->cmd
) + 1)) == NULL
)
2914 sscanf(ud
->cmd
, " %c", &first_char
);
2915 if (data_start
== 0 || /* empty line */
2916 first_char
== '#') { /* comment */
2917 ud
->fn
= update_nothing
;
2920 strcpy(ud
->cmd_tokens
, ud
->cmd
);
2921 sscanf(ud
->cmd_tokens
,
2922 " %n%*[0-9a-zA-Z_-]%n:%n%*[0-9a-zA-Z_]%n%*1[ \t]%n",
2923 &id_start
, &id_end
, &action_start
, &action_end
, &data_start
);
2924 ud
->cmd_tokens
[id_end
] = ud
->cmd_tokens
[action_end
] = '\0';
2925 id
= ud
->cmd_tokens
+ id_start
;
2926 ud
->action
= ud
->cmd_tokens
+ action_start
;
2927 ud
->data
= ud
->cmd_tokens
+ data_start
;
2928 if (eql(ud
->action
, "main_quit")) {
2932 if (eql(ud
->action
, "load") && strlen(ud
->data
) > 0 &&
2933 remember_loading_file(ud
->data
)) {
2934 struct info a
= *ar
;
2936 if ((a
.fin
= fopen(ud
->data
, "r")) != NULL
) {
2939 ud
->fn
= update_nothing
;
2942 remember_loading_file(NULL
);
2945 if ((ud
->obj
= (gtk_builder_get_object(ar
->builder
, id
))) == NULL
) {
2949 ud
->type
= G_TYPE_FROM_INSTANCE(ud
->obj
);
2950 if (ud
->type
== GTK_TYPE_DRAWING_AREA
)
2951 ud
->fn
= update_drawing_area
;
2952 else if (ud
->type
== GTK_TYPE_TREE_VIEW
)
2953 ud
->fn
= update_tree_view
;
2954 else if (ud
->type
== GTK_TYPE_COMBO_BOX_TEXT
)
2955 ud
->fn
= update_combo_box_text
;
2956 else if (ud
->type
== GTK_TYPE_LABEL
)
2957 ud
->fn
= update_label
;
2958 else if (ud
->type
== GTK_TYPE_IMAGE
)
2959 ud
->fn
= update_image
;
2960 else if (ud
->type
== GTK_TYPE_TEXT_VIEW
)
2961 ud
->fn
= update_text_view
;
2962 else if (ud
->type
== GTK_TYPE_NOTEBOOK
)
2963 ud
->fn
= update_notebook
;
2964 else if (ud
->type
== GTK_TYPE_EXPANDER
)
2965 ud
->fn
= update_expander
;
2966 else if (ud
->type
== GTK_TYPE_FRAME
||
2967 ud
->type
== GTK_TYPE_ASPECT_FRAME
)
2968 ud
->fn
= update_frame
;
2969 else if (ud
->type
== GTK_TYPE_SCROLLED_WINDOW
)
2970 ud
->fn
= update_scrolled_window
;
2971 else if (ud
->type
== GTK_TYPE_LINK_BUTTON
)
2972 ud
->fn
= update_link_button
;
2973 else if (ud
->type
== GTK_TYPE_BUTTON
)
2974 ud
->fn
= update_button
;
2975 else if (ud
->type
== GTK_TYPE_MENU
)
2976 ud
->fn
= update_menu
;
2977 else if (ud
->type
== GTK_TYPE_MENU_ITEM
)
2978 ud
->fn
= update_menu_item
;
2979 else if (ud
->type
== GTK_TYPE_FILE_CHOOSER_DIALOG
)
2980 ud
->fn
= update_file_chooser_dialog
;
2981 else if (ud
->type
== GTK_TYPE_FILE_CHOOSER_BUTTON
)
2982 ud
->fn
= update_file_chooser_button
;
2983 else if (ud
->type
== GTK_TYPE_COLOR_BUTTON
)
2984 ud
->fn
= update_color_button
;
2985 else if (ud
->type
== GTK_TYPE_FONT_BUTTON
)
2986 ud
->fn
= update_font_button
;
2987 else if (ud
->type
== GTK_TYPE_PRINT_UNIX_DIALOG
)
2988 ud
->fn
= update_print_dialog
;
2989 else if (ud
->type
== GTK_TYPE_SWITCH
)
2990 ud
->fn
= update_switch
;
2991 else if (ud
->type
== GTK_TYPE_TOGGLE_BUTTON
||
2992 ud
->type
== GTK_TYPE_RADIO_BUTTON
||
2993 ud
->type
== GTK_TYPE_CHECK_BUTTON
)
2994 ud
->fn
= update_toggle_button
;
2995 else if (ud
->type
== GTK_TYPE_ENTRY
)
2996 ud
->fn
= update_entry
;
2997 else if (ud
->type
== GTK_TYPE_SPIN_BUTTON
)
2998 ud
->fn
= update_spin_button
;
2999 else if (ud
->type
== GTK_TYPE_SCALE
)
3000 ud
->fn
= update_scale
;
3001 else if (ud
->type
== GTK_TYPE_PROGRESS_BAR
)
3002 ud
->fn
= update_progress_bar
;
3003 else if (ud
->type
== GTK_TYPE_SPINNER
)
3004 ud
->fn
= update_spinner
;
3005 else if (ud
->type
== GTK_TYPE_STATUSBAR
)
3006 ud
->fn
= update_statusbar
;
3007 else if (ud
->type
== GTK_TYPE_CALENDAR
)
3008 ud
->fn
= update_calendar
;
3009 else if (ud
->type
== GTK_TYPE_SOCKET
)
3010 ud
->fn
= update_socket
;
3011 else if (ud
->type
== GTK_TYPE_WINDOW
||
3012 ud
->type
== GTK_TYPE_DIALOG
)
3013 ud
->fn
= update_window
;
3015 ud
->fn
= try_generic_cmds
;
3017 pthread_testcancel();
3018 gdk_threads_add_timeout(0, (GSourceFunc
) update_ui
, ud
);
3026 * ============================================================
3028 * ============================================================
3032 * Return the first string xpath obtains from ui_file.
3033 * xmlFree(string) must be called when done
3036 xpath1(xmlChar
*xpath
, const char *ui_file
)
3039 xmlDocPtr doc
= NULL
;
3040 xmlNodeSetPtr nodes
= NULL
;
3041 xmlXPathContextPtr ctx
= NULL
;
3042 xmlXPathObjectPtr xpath_obj
= NULL
;
3044 if ((doc
= xmlParseFile(ui_file
)) == NULL
)
3046 if ((ctx
= xmlXPathNewContext(doc
)) == NULL
)
3048 if ((xpath_obj
= xmlXPathEvalExpression(xpath
, ctx
)) == NULL
)
3050 if ((nodes
= xpath_obj
->nodesetval
) != NULL
&& nodes
->nodeNr
> 0)
3051 r
= xmlNodeGetContent(nodes
->nodeTab
[0]);
3052 xmlXPathFreeObject(xpath_obj
);
3054 xmlXPathFreeContext(ctx
);
3062 * Attach key "col_number" to renderer. Associate "col_number" with
3063 * the corresponding column number in the underlying model.
3064 * Due to what looks like a gap in the GTK API, renderer id and column
3065 * number are taken directly from the XML .ui file.
3068 tree_view_column_get_renderer_column(GtkBuilder
*builder
, const char *ui_file
,
3069 GtkTreeViewColumn
*t_col
, int n
,
3070 GtkCellRenderer
**rnd
)
3073 char *xp_bas1
= "//object[@class=\"GtkTreeViewColumn\" and @id=\"";
3074 char *xp_bas2
= "\"]/child[";
3075 char *xp_bas3
= "]/object[@class=\"GtkCellRendererText\""
3076 " or @class=\"GtkCellRendererToggle\"]/";
3077 char *xp_rnd_id
= "@id";
3078 char *xp_text_col
= "../attributes/attribute[@name=\"text\""
3079 " or @name=\"active\"]/text()";
3080 const char *tree_col_id
= widget_id(GTK_BUILDABLE(t_col
));
3081 size_t xp_rnd_nam_len
, xp_mod_col_len
;
3082 size_t xp_n_len
= 3; /* Big Enough (TM) */
3083 xmlChar
*xp_rnd_nam
= NULL
, *xp_mod_col
= NULL
;
3084 xmlChar
*rnd_nam
= NULL
, *mod_col
= NULL
;
3086 /* find name of nth cell renderer under the GtkTreeViewColumn */
3088 xp_rnd_nam_len
= strlen(xp_bas1
) + strlen(tree_col_id
) +
3089 strlen(xp_bas2
) + xp_n_len
+ strlen(xp_bas3
) +
3090 strlen(xp_rnd_id
) + sizeof('\0');
3091 if ((xp_rnd_nam
= malloc(xp_rnd_nam_len
)) == NULL
)
3093 snprintf((char *) xp_rnd_nam
, xp_rnd_nam_len
, "%s%s%s%d%s%s",
3094 xp_bas1
, tree_col_id
, xp_bas2
, n
,
3095 xp_bas3
, xp_rnd_id
);
3096 rnd_nam
= xpath1(xp_rnd_nam
, ui_file
);
3097 /* find the model column that is attached to the nth cell */
3098 /* renderer under GtkTreeViewColumn tree_col_id */
3099 xp_mod_col_len
= strlen(xp_bas1
) + strlen(tree_col_id
) +
3100 strlen(xp_bas2
) + xp_n_len
+ strlen(xp_bas3
) +
3101 strlen(xp_text_col
) + sizeof('\0');
3102 if ((xp_mod_col
= malloc(xp_mod_col_len
)) == NULL
)
3104 snprintf((char *) xp_mod_col
, xp_mod_col_len
, "%s%s%s%d%s%s",
3105 xp_bas1
, tree_col_id
, xp_bas2
, n
, xp_bas3
, xp_text_col
);
3106 mod_col
= xpath1(xp_mod_col
, ui_file
);
3108 *rnd
= GTK_CELL_RENDERER(
3109 gtk_builder_get_object(builder
, (char *) rnd_nam
));
3111 g_object_set_data(G_OBJECT(*rnd
), "col_number",
3112 GINT_TO_POINTER(strtol((char *) mod_col
,
3125 * Callbacks that forward a modification of a tree view cell to the
3129 cb_tree_model_edit(GtkCellRenderer
*renderer
, const gchar
*path_s
,
3130 const gchar
*new_text
, struct info
*ar
)
3133 int col
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer
),
3136 gtk_tree_model_get_iter_from_string(ar
->model
, &iter
, path_s
);
3137 set_tree_view_cell(ar
->model
, &iter
, path_s
, col
,
3139 send_tree_cell_msg_by(send_msg
, path_s
, &iter
, col
, ar
);
3143 cb_tree_model_toggle(GtkCellRenderer
*renderer
, gchar
*path_s
, struct info
*ar
)
3147 int col
= GPOINTER_TO_INT(g_object_get_data(G_OBJECT(renderer
),
3150 gtk_tree_model_get_iter_from_string(ar
->model
, &iter
, path_s
);
3151 gtk_tree_model_get(ar
->model
, &iter
, col
, &toggle_state
, -1);
3152 set_tree_view_cell(ar
->model
, &iter
, path_s
, col
,
3153 toggle_state
? "0" : "1");
3157 * Add new element containing id to the list of callback-handler ids
3158 * stored in obj's field named "signal_id"
3161 push_handler_id(gpointer
*obj
, unsigned int id
)
3163 struct handler_id
*prev_hid
, *hid
;
3165 prev_hid
= g_object_get_data(G_OBJECT(obj
), "signal-id");
3166 if ((hid
= malloc(sizeof(struct handler_id
))) == NULL
)
3168 hid
->next
= prev_hid
;
3170 hid
->blocked
= false;
3171 g_object_set_data(G_OBJECT(obj
), "signal-id", hid
);
3175 * Connect function cb to obj's widget signal sig, remembering the
3176 * handler id in a list in obj's field named "signal-id"
3179 sig_conn(gpointer
*obj
, char *sig
, GCallback cb
, struct info
*ar
)
3181 unsigned int handler_id
= g_signal_connect(obj
, sig
, cb
, ar
);
3183 push_handler_id(obj
, handler_id
);
3187 sig_conn_swapped(gpointer
*obj
, char *sig
, GCallback cb
, void *data
)
3189 unsigned int handler_id
= g_signal_connect_swapped(obj
, sig
, cb
, data
);
3191 push_handler_id(obj
, handler_id
);
3195 connect_widget_signals(gpointer
*obj
, struct info
*ar
)
3198 GType type
= G_TYPE_INVALID
;
3199 char *suffix
= NULL
;
3200 const char *w_id
= NULL
;
3203 type
= G_TYPE_FROM_INSTANCE(obj
);
3204 if (GTK_IS_BUILDABLE(obj
))
3205 w_id
= widget_id(GTK_BUILDABLE(obj
));
3206 if (type
== GTK_TYPE_TREE_VIEW_COLUMN
) {
3207 GList
*cells
= gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(obj
));
3208 GtkTreeViewColumn
*tv_col
= GTK_TREE_VIEW_COLUMN(obj
);
3209 GObject
*view
= G_OBJECT(
3210 gtk_tree_view_column_get_tree_view(tv_col
));
3211 unsigned int i
, n_cells
= g_list_length(cells
);
3214 sig_conn(obj
, "clicked", G_CALLBACK(cb_simple
), info_txt_new(o
, "clicked"));
3215 for (i
= 1; i
<= n_cells
; i
++) {
3216 GtkCellRenderer
*renderer
;
3217 gboolean editable
= FALSE
;
3219 if (!tree_view_column_get_renderer_column(ar
->builder
, ar
->txt
, tv_col
,
3222 if (GTK_IS_CELL_RENDERER_TEXT(renderer
)) {
3223 g_object_get(renderer
, "editable", &editable
, NULL
);
3225 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(view
));
3227 g_signal_connect(renderer
, "edited",
3228 G_CALLBACK(cb_tree_model_edit
),
3229 info_obj_new(o
, view
, model
));
3231 } else if (GTK_IS_CELL_RENDERER_TOGGLE(renderer
)) {
3232 g_object_get(renderer
, "activatable", &editable
, NULL
);
3234 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(view
));
3236 g_signal_connect(renderer
, "toggled",
3237 G_CALLBACK(cb_tree_model_toggle
),
3238 info_obj_new(o
, NULL
, model
));
3242 } else if (type
== GTK_TYPE_LINK_BUTTON
)
3243 sig_conn(obj
, "activate-link", G_CALLBACK(cb_simple
), info_txt_new(o
, "clicked"));
3244 else if (type
== GTK_TYPE_BUTTON
)
3245 /* Button associated with a GtkTextView. */
3246 if ((suffix
= strstr(w_id
, "_send_text")) != NULL
&&
3247 GTK_IS_TEXT_VIEW(obj2
= obj_sans_suffix(ar
->builder
, suffix
, w_id
)))
3248 sig_conn(obj
, "clicked", G_CALLBACK(cb_send_text
),
3249 info_obj_new(o
, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2
))), NULL
));
3250 else if ((suffix
= strstr(w_id
, "_send_selection")) != NULL
&&
3251 GTK_IS_TEXT_VIEW(obj2
= obj_sans_suffix(ar
->builder
, suffix
, w_id
)))
3252 sig_conn(obj
, "clicked", G_CALLBACK(cb_send_text_selection
),
3253 info_obj_new(o
, G_OBJECT(gtk_text_view_get_buffer(GTK_TEXT_VIEW(obj2
))), NULL
));
3255 sig_conn(obj
, "clicked", G_CALLBACK(cb_simple
), info_txt_new(o
, "clicked"));
3256 /* Buttons associated with (and part of) a GtkDialog.
3257 * (We shun response ids which could be returned from
3258 * gtk_dialog_run() because that would require the
3259 * user to define those response ids in Glade,
3261 if ((suffix
= strstr(w_id
, "_cancel")) != NULL
&&
3262 GTK_IS_DIALOG(obj2
= obj_sans_suffix(ar
->builder
, suffix
, w_id
)))
3263 if (eql(widget_id(GTK_BUILDABLE(obj2
)), MAIN_WIN
))
3264 sig_conn_swapped(obj
, "clicked",
3265 G_CALLBACK(gtk_main_quit
), NULL
);
3267 sig_conn_swapped(obj
, "clicked",
3268 G_CALLBACK(gtk_widget_hide
), obj2
);
3269 else if ((suffix
= strstr(w_id
, "_ok")) != NULL
&&
3270 GTK_IS_DIALOG(obj2
= obj_sans_suffix(ar
->builder
, suffix
, w_id
))) {
3271 if (GTK_IS_FILE_CHOOSER_DIALOG(obj2
))
3272 sig_conn_swapped(obj
, "clicked",
3273 G_CALLBACK(cb_send_file_chooser_dialog_selection
),
3274 info_obj_new(o
, obj2
, NULL
));
3275 if (eql(widget_id(GTK_BUILDABLE(obj2
)), MAIN_WIN
))
3276 sig_conn_swapped(obj
, "clicked",
3277 G_CALLBACK(gtk_main_quit
), NULL
);
3279 sig_conn_swapped(obj
, "clicked",
3280 G_CALLBACK(gtk_widget_hide
), obj2
);
3281 } else if ((suffix
= strstr(w_id
, "_apply")) != NULL
&&
3282 GTK_IS_FILE_CHOOSER_DIALOG(obj2
= obj_sans_suffix(ar
->builder
, suffix
, w_id
)))
3283 sig_conn_swapped(obj
, "clicked",
3284 G_CALLBACK(cb_send_file_chooser_dialog_selection
),
3285 info_obj_new(o
, obj2
, NULL
));
3287 else if (GTK_IS_MENU_ITEM(obj
))
3288 if ((suffix
= strstr(w_id
, "_invoke")) != NULL
&&
3289 GTK_IS_DIALOG(obj2
= obj_sans_suffix(ar
->builder
, suffix
, w_id
)))
3290 sig_conn_swapped(obj
, "activate",
3291 G_CALLBACK(gtk_widget_show
), obj2
);
3293 sig_conn(obj
, "activate",
3294 G_CALLBACK(cb_menu_item
), info_txt_new(o
, "active"));
3295 else if (GTK_IS_WINDOW(obj
)) {
3296 sig_conn(obj
, "delete-event",
3297 G_CALLBACK(cb_event_simple
), info_txt_new(o
, "closed"));
3298 if (eql(w_id
, MAIN_WIN
))
3299 sig_conn_swapped(obj
, "delete-event",
3300 G_CALLBACK(gtk_main_quit
), NULL
);
3302 sig_conn(obj
, "delete-event",
3303 G_CALLBACK(gtk_widget_hide_on_delete
), NULL
);
3304 } else if (type
== GTK_TYPE_FILE_CHOOSER_BUTTON
)
3305 sig_conn(obj
, "file-set",
3306 G_CALLBACK(cb_file_chooser_button
), info_txt_new(o
, "file"));
3307 else if (type
== GTK_TYPE_COLOR_BUTTON
)
3308 sig_conn(obj
, "color-set",
3309 G_CALLBACK(cb_color_button
), info_txt_new(o
, "color"));
3310 else if (type
== GTK_TYPE_FONT_BUTTON
)
3311 sig_conn(obj
, "font-set",
3312 G_CALLBACK(cb_font_button
), info_txt_new(o
, "font"));
3313 else if (type
== GTK_TYPE_SWITCH
)
3314 sig_conn(obj
, "notify::active",
3315 G_CALLBACK(cb_switch
), info_txt_new(o
, NULL
));
3316 else if (type
== GTK_TYPE_TOGGLE_BUTTON
||
3317 type
== GTK_TYPE_RADIO_BUTTON
||
3318 type
== GTK_TYPE_CHECK_BUTTON
)
3319 sig_conn(obj
, "toggled",
3320 G_CALLBACK(cb_toggle_button
), info_txt_new(o
, NULL
));
3321 else if (type
== GTK_TYPE_ENTRY
)
3322 sig_conn(obj
, "changed",
3323 G_CALLBACK(cb_editable
), info_txt_new(o
, "text"));
3324 else if (type
== GTK_TYPE_SPIN_BUTTON
)
3325 sig_conn(obj
, "value_changed",
3326 G_CALLBACK(cb_spin_button
), info_txt_new(o
, "text")); /* TODO: rename to "value" */
3327 else if (type
== GTK_TYPE_SCALE
)
3328 sig_conn(obj
, "value-changed",
3329 G_CALLBACK(cb_range
), info_txt_new(o
, "value"));
3330 else if (type
== GTK_TYPE_CALENDAR
) {
3331 sig_conn(obj
, "day-selected-double-click",
3332 G_CALLBACK(cb_calendar
), info_txt_new(o
, "doubleclicked"));
3333 sig_conn(obj
, "day-selected",
3334 G_CALLBACK(cb_calendar
), info_txt_new(o
, "clicked"));
3335 } else if (type
== GTK_TYPE_TREE_SELECTION
)
3336 sig_conn(obj
, "changed",
3337 G_CALLBACK(cb_tree_selection
), info_txt_new(o
, "clicked"));
3338 else if (type
== GTK_TYPE_SOCKET
) {
3339 sig_conn(obj
, "plug-added",
3340 G_CALLBACK(cb_simple
), info_txt_new(o
, "plug-added"));
3341 sig_conn(obj
, "plug-removed",
3342 G_CALLBACK(cb_simple
), info_txt_new(o
, "plug-removed"));
3343 /* TODO: rename to plug_added, plug_removed */
3344 } else if (type
== GTK_TYPE_DRAWING_AREA
)
3345 sig_conn(obj
, "draw", G_CALLBACK(cb_draw
), NULL
);
3346 else if (type
== GTK_TYPE_EVENT_BOX
) {
3347 gtk_widget_set_can_focus(GTK_WIDGET(obj
), true);
3348 sig_conn(obj
, "button-press-event",
3349 G_CALLBACK(cb_event_box_button
),
3350 info_txt_new(o
, "button_press"));
3351 sig_conn(obj
, "button-release-event",
3352 G_CALLBACK(cb_event_box_button
),
3353 info_txt_new(o
, "button_release"));
3354 sig_conn(obj
, "motion-notify-event",
3355 G_CALLBACK(cb_event_box_motion
),
3356 info_txt_new(o
, "motion"));
3357 sig_conn(obj
, "key-press-event",
3358 G_CALLBACK(cb_event_box_key
),
3359 info_txt_new(o
, "key_press"));
3364 * We keep a style provider with each widget
3367 add_widget_style_provider(gpointer
*obj
, void *data
)
3369 GtkCssProvider
*style_provider
;
3370 GtkStyleContext
*context
;
3373 if (!GTK_IS_WIDGET(obj
))
3375 style_provider
= gtk_css_provider_new();
3376 context
= gtk_widget_get_style_context(GTK_WIDGET(obj
));
3377 gtk_style_context_add_provider(context
,
3378 GTK_STYLE_PROVIDER(style_provider
),
3379 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
3380 g_object_set_data(G_OBJECT(obj
), "style_provider", style_provider
);
3384 prepare_widgets(GtkBuilder
*builder
, char *ui_file
, FILE *out
)
3386 GSList
*objects
= NULL
;
3387 struct info ar
= {.builder
= builder
, .fout
= out
, .txt
= ui_file
};
3389 objects
= gtk_builder_get_objects(builder
);
3390 g_slist_foreach(objects
, (GFunc
) connect_widget_signals
, &ar
);
3391 g_slist_foreach(objects
, (GFunc
) add_widget_style_provider
, NULL
);
3392 g_slist_free(objects
);
3396 main(int argc
, char *argv
[])
3398 GObject
*main_window
= NULL
;
3400 char *in_fifo
= NULL
, *out_fifo
= NULL
;
3401 char *ui_file
= "pipeglade.ui", *log_file
= NULL
, *err_file
= NULL
;
3407 /* Disable runtime GLIB deprecation warnings: */
3408 setenv("G_ENABLE_DIAGNOSTIC", "0", 0);
3409 gtk_init(&argc
, &argv
);
3410 while ((opt
= getopt(argc
, argv
, "bGhe:i:l:o:O:u:V")) != -1) {
3412 case 'b': bg
= true; break;
3413 case 'e': xid
= optarg
; break;
3414 case 'G': show_lib_versions(); break;
3415 case 'h': bye(EXIT_SUCCESS
, stdout
, USAGE
); break;
3416 case 'i': in_fifo
= optarg
; break;
3417 case 'l': log_file
= optarg
; break;
3418 case 'o': out_fifo
= optarg
; break;
3419 case 'O': err_file
= optarg
; break;
3420 case 'u': ui_file
= optarg
; break;
3421 case 'V': bye(EXIT_SUCCESS
, stdout
, "%s\n", VERSION
); break;
3423 default: bye(EXIT_FAILURE
, stderr
, USAGE
); break;
3426 if (argv
[optind
] != NULL
)
3427 bye(EXIT_FAILURE
, stderr
,
3428 "illegal parameter '%s'\n" USAGE
, argv
[optind
]);
3429 redirect_stderr(err_file
);
3430 ar
.fin
= open_fifo(in_fifo
, "r", stdin
, _IONBF
);
3431 ar
.fout
= open_fifo(out_fifo
, "w", stdout
, _IOLBF
);
3432 go_bg_if(bg
, ar
.fin
, ar
.fout
, err_file
);
3433 ar
.builder
= builder_from_file(ui_file
);
3434 ar
.flog
= open_log(log_file
);
3435 pthread_create(&receiver
, NULL
, (void *(*)(void *)) digest_cmd
, &ar
);
3436 main_window
= find_main_window(ar
.builder
);
3438 LIBXML_TEST_VERSION
;
3439 prepare_widgets(ar
.builder
, ui_file
, ar
.fout
);
3440 xembed_if(xid
, main_window
);
3442 pthread_cancel(receiver
);
3443 pthread_join(receiver
, NULL
);
3445 rm_unless(stdin
, ar
.fin
, in_fifo
);
3446 rm_unless(stdout
, ar
.fout
, out_fifo
);