diff --git a/README.md b/README.md index a7e7fcd..071d8ef 100644 --- a/README.md +++ b/README.md @@ -97,9 +97,17 @@ Usage: will undo the last drawing stroke (or "-z") gromit-mpx --redo will redo the last undone drawing stroke (or "-y") + gromit-mpx --change-tool + will redefine a tool, using the same syntax as in the .cfg file (or "-T") + e.g. gromit-mpx --change-tool '"default"=RECT(color="yellow" size=2)' + gromit-mpx --change-attribute + will change one or several attributes of a tool, keeping the others + as they were. This can be used to change e.g. to color or type of a tool (or "-A") + e.g. gromit-mpx --change-attribute '"default"=(color="cyan")' + gromit-mpx --change-attribute '"default"=LINE' gromit-mpx --line will draw a straight line with characteristics specified by the arguments (or "-l") - eg: gromit-mpx -l 200 200 400 400 '#C4A7E7' 6 + e.g.: gromit-mpx -l 200 200 400 400 '#C4A7E7' 6 If activated Gromit-MPX prevents you from using other programs with the mouse. You can press the button and paint on the screen. Key presses diff --git a/gromit-mpx.1 b/gromit-mpx.1 index dc48576..28476ff 100644 --- a/gromit-mpx.1 +++ b/gromit-mpx.1 @@ -1,5 +1,5 @@ .\" Hey, vim: ft=nroff -.TH GROMIT-MPX 1 "November 3, 2018" +.TH GROMIT-MPX 1 "March 31, 2024" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: @@ -101,6 +101,16 @@ will cause the main Gromit-MPX process to quit. .B \-t, \-\-toggle will toggle the grabbing of the cursor. .TP +.B \-T , \-\-change-tool +will change the definition of a tool. The syntax is as in the .cfg file +read at startup, except for the trailing semicolon. +(example: gromit-mpx -T '"default"=RECT(color="yellow" size=2)' +.TP +.B \-A , \-\-change-attribute +will change one or several attributes of a tool, keeping the other settings. +(example 1: change just the color: gromit-mpx -A '"default"=(color="yellow")'; +example 2: change tool type, keeping color, size etc: gromit-mpx -A '"default"=LINE') +.TP .B \-v, \-\-visibility will toggle the visibility of the window. .TP diff --git a/src/callbacks.c b/src/callbacks.c index 239b590..a4796d9 100644 --- a/src/callbacks.c +++ b/src/callbacks.c @@ -141,10 +141,9 @@ void on_monitors_changed ( GdkScreen *screen, parse_config(data); // also calls paint_context_new() :-( - - data->default_pen = paint_context_new (data, GROMIT_PEN, data->red, 7, 0, GROMIT_ARROW_END, + data->default_pen = paint_context_new (data, GROMIT_PEN, gdk_rgba_copy(data->red), 7, 0, GROMIT_ARROW_END, 5, 10, 15, 25, 1, 0, G_MAXUINT); - data->default_eraser = paint_context_new (data, GROMIT_ERASER, data->red, 75, 0, GROMIT_ARROW_END, + data->default_eraser = paint_context_new (data, GROMIT_ERASER, gdk_rgba_copy(data->red), 75, 0, GROMIT_ARROW_END, 5, 10, 15, 25, 1, 0, G_MAXUINT); if(!data->composited) // set shape @@ -211,7 +210,10 @@ void on_clientapp_selection_get (GtkWidget *widget, g_printerr("DEBUG: clientapp received request.\n"); - if (gtk_selection_data_get_target(selection_data) == GA_TOGGLEDATA || gtk_selection_data_get_target(selection_data) == GA_LINEDATA) + if (gtk_selection_data_get_target(selection_data) == GA_TOGGLEDATA || + gtk_selection_data_get_target(selection_data) == GA_LINEDATA || + gtk_selection_data_get_target(selection_data) == GA_CHGTOOLDATA || + gtk_selection_data_get_target(selection_data) == GA_CHGATTRDATA) { ans = data->clientdata; } @@ -398,9 +400,8 @@ gboolean on_motion (GtkWidget *win, } if (type == GROMIT_LINE) { - GromitArrowType atype = devdata->cur_context->arrow_type; draw_line (data, ev->device, devdata->lastx, devdata->lasty, ev->x, ev->y); - if (devdata->cur_context->arrowsize > 0) + if (devdata->cur_context->arrowsize > 0.0) { GromitArrowType atype = devdata->cur_context->arrow_type; gint width = devdata->cur_context->arrowsize * devdata->cur_context->width / 2; @@ -528,7 +529,7 @@ void on_mainapp_selection_get (GtkWidget *widget, gpointer user_data) { GromitData *data = (GromitData *) user_data; - + gchar *uri = "OK"; GdkAtom action = gtk_selection_data_get_target(selection_data); @@ -558,10 +559,20 @@ void on_mainapp_selection_get (GtkWidget *widget, undo_drawing (data); else if (action == GA_REDO) redo_drawing (data); + else if (action == GA_CHGTOOL) + { + gtk_selection_convert(data->win, GA_DATA, GA_CHGTOOLDATA, time); + gtk_main(); + } + else if (action == GA_CHGATTR) + { + gtk_selection_convert(data->win, GA_DATA, GA_CHGATTRDATA, time); + gtk_main(); + } else uri = "NOK"; - + gtk_selection_data_set (selection_data, gtk_selection_data_get_target(selection_data), 8, (guchar*)uri, strlen (uri)); @@ -574,6 +585,7 @@ void on_mainapp_selection_received (GtkWidget *widget, gpointer user_data) { GromitData *data = (GromitData *) user_data; + gchar *name = NULL; if(gtk_selection_data_get_length(selection_data) < 0) { @@ -585,20 +597,20 @@ void on_mainapp_selection_received (GtkWidget *widget, if(gtk_selection_data_get_target(selection_data) == GA_TOGGLEDATA ) { intptr_t dev_nr = strtoull((gchar*)gtk_selection_data_get_data(selection_data), NULL, 10); - + if(data->debug) g_printerr("DEBUG: mainapp got toggle id '%ld' back from client.\n", (long)dev_nr); if(dev_nr < 0) toggle_grab(data, NULL); /* toggle all */ - else + else { /* find dev numbered dev_nr */ GHashTableIter it; gpointer value; - GromitDeviceData* devdata = NULL; + GromitDeviceData* devdata = NULL; g_hash_table_iter_init (&it, data->devdatatable); - while (g_hash_table_iter_next (&it, NULL, &value)) + while (g_hash_table_iter_next (&it, NULL, &value)) { devdata = value; if(devdata->index == dev_nr) @@ -606,14 +618,14 @@ void on_mainapp_selection_received (GtkWidget *widget, else devdata = NULL; } - + if(devdata) toggle_grab(data, devdata->device); else g_printerr("ERROR: No device at index %ld.\n", (long)dev_nr); } } - else if (gtk_selection_data_get_target(selection_data) == GA_LINEDATA) + else if (gtk_selection_data_get_target(selection_data) == GA_LINEDATA) { gchar** line_args = g_strsplit((gchar*)gtk_selection_data_get_data(selection_data), " ", 6); @@ -624,7 +636,7 @@ void on_mainapp_selection_received (GtkWidget *widget, gchar* hex_code = line_args[4]; int thickness = atoi(line_args[5]); - if(data->debug) + if(data->debug) { g_printerr("DEBUG: mainapp got line data back from client:\n"); g_printerr("startX startY endX endY: %d %d %d %d\n", startX, startY, endX, endY); @@ -662,14 +674,156 @@ void on_mainapp_selection_received (GtkWidget *widget, cairo_stroke(line_ctx->paint_ctx); data->modified = 1; - gdk_window_invalidate_rect(gtk_widget_get_window(data->win), &rect, 0); + gdk_window_invalidate_rect(gtk_widget_get_window(data->win), &rect, 0); data->painted = 1; g_free(line_ctx); g_free (color); - } + } + else + { + GdkAtom atom = gtk_selection_data_get_target(selection_data); + if (atom == GA_CHGTOOLDATA || atom == GA_CHGATTRDATA) + { + gchar *a = (gchar *)gtk_selection_data_get_data(selection_data); + if(data->debug) g_printerr("DEBUG: define tool: %s\n", a); + + GScanner *scanner = g_scanner_new(NULL); + scanner_init(scanner); + g_scanner_input_text(scanner, a, strlen(a)); + + GTokenType token = g_scanner_get_next_token (scanner); + + GromitPaintContext style; + style.paint_color = NULL; + + if (token != G_TOKEN_STRING || ! (name = parse_name (scanner))) + { + g_printerr("Could not parse tool in expression '%s'!\n", a); + goto cleanup; + } + + GromitPaintContext *context = g_hash_table_lookup(data->tool_config, name); + if (context == NULL) + { + g_printerr("Cannot change tool '%s' because it does not exist...\n", name); + goto cleanup; + } + + if (atom == GA_CHGATTRDATA) + { + if (g_scanner_cur_token(scanner) != G_TOKEN_EQUAL_SIGN) + { + g_printerr("Expected equal sign after tool name...\n"); + goto cleanup; + } + token = g_scanner_get_next_token(scanner); + if (token == G_TOKEN_SYMBOL) + { + context->type = (GromitPaintType) scanner->value.v_symbol; + token = g_scanner_get_next_token (scanner); + } + if (token == G_TOKEN_LEFT_PAREN) + { + style.paint_color = g_malloc(sizeof(GdkRGBA)); + *style.paint_color = *data->red; + scanner->scope_id = 2; + while (token != G_TOKEN_RIGHT_PAREN) + { + token = g_scanner_get_next_token(scanner); + if (token == G_TOKEN_SYMBOL) + { + switch (parse_attribute(scanner, &style)) + { + case SYM_SIZE: + context->width = style.width; + cairo_set_line_width(context->paint_ctx, style.width); + break; + case SYM_COLOR: + *context->paint_color = *style.paint_color; + cairo_set_source_rgba(context->paint_ctx, + style.paint_color->red, + style.paint_color->green, + style.paint_color->blue, + style.paint_color->alpha); + break; + case SYM_ARROWSIZE: + context->arrowsize = style.arrowsize; + break; + case SYM_ARROWTYPE: + context->arrow_type = style.arrow_type; + break; + case SYM_MINSIZE: + context->minwidth = style.minwidth; + break; + case SYM_MAXSIZE: + context->maxwidth = style.maxwidth; + break; + case SYM_MINLEN: + context->minlen = style.minlen; + break; + case SYM_MAXANGLE: + context->maxangle = style.maxangle; + break; + case SYM_RADIUS: + context->radius = style.radius; + break; + case SYM_SIMPLIFY: + context->simplify = style.simplify; + break; + case SYM_SNAP: + context->snapdist = style.snapdist; + break; + case SYM_ERROR: + break; + } + } + } + if (g_scanner_get_next_token(scanner) != G_TOKEN_EOF) + g_printerr("WARNING: skipping extra content after ')' !\n"); + } + } + else + { + if (!parse_tool(data, scanner, &style)) goto cleanup; + if (g_scanner_cur_token(scanner) != G_TOKEN_LEFT_PAREN) goto cleanup; + if (! parse_style(scanner, &style)) goto cleanup; + if (g_scanner_cur_token(scanner) != G_TOKEN_EOF) goto cleanup; + + context->type = style.type; + context->width = style.width; + context->arrowsize = style.arrowsize; + context->arrow_type = style.arrow_type; + context->minwidth = style.minwidth; + context->maxwidth = style.maxwidth; + context->radius = style.radius; + context->minlen = style.minlen; + context->maxangle = style.maxangle; + context->simplify = style.simplify; + context->snapdist = style.snapdist; + *context->paint_color = *style.paint_color; + + cairo_set_source_rgba(context->paint_ctx, + style.paint_color->red, + style.paint_color->green, + style.paint_color->blue, + style.paint_color->alpha); + cairo_set_line_width(context->paint_ctx, style.width); + } + if (g_scanner_get_next_token (scanner) != G_TOKEN_EOF) + { + g_printerr("WARNING: skipping extra content tool definition!\n"); + } + + cleanup: + if (style.paint_color) g_free(style.paint_color); + g_scanner_destroy (scanner); + } // if (tool change) ... + } } - + + if (name) g_free(name); + gtk_main_quit (); } diff --git a/src/config.c b/src/config.c index c441de2..9bf183b 100644 --- a/src/config.c +++ b/src/config.c @@ -41,10 +41,64 @@ static gpointer HOTKEY_SYMBOL_VALUE = (gpointer) 3; static gpointer UNDOKEY_SYMBOL_VALUE = (gpointer) 4; /* - * Functions for parsing the Configuration-file + * IMPORTANT RULE: the color field in GromitPaintContext is + * _always_ individually allocated, i.e. GdkRGBA pointers all point to + * separate copies. */ -static gchar* parse_name (GScanner *scanner) +/* + * initialize GScanner for the parsing of tool definitions + */ +void scanner_init(GScanner *scanner) +{ + scanner->config->case_sensitive = 0; + scanner->config->scan_octal = 0; + scanner->config->identifier_2_string = 0; + scanner->config->char_2_token = 1; + scanner->config->numbers_2_int = 1; + scanner->config->int_2_float = 1; + + g_scanner_scope_add_symbol (scanner, 0, "PEN", (gpointer) GROMIT_PEN); + g_scanner_scope_add_symbol (scanner, 0, "LINE", (gpointer) GROMIT_LINE); + g_scanner_scope_add_symbol (scanner, 0, "RECT", (gpointer) GROMIT_RECT); + g_scanner_scope_add_symbol (scanner, 0, "SMOOTH", (gpointer) GROMIT_SMOOTH); + g_scanner_scope_add_symbol (scanner, 0, "ORTHOGONAL",(gpointer) GROMIT_ORTHOGONAL); + g_scanner_scope_add_symbol (scanner, 0, "ERASER", (gpointer) GROMIT_ERASER); + g_scanner_scope_add_symbol (scanner, 0, "RECOLOR", (gpointer) GROMIT_RECOLOR); + g_scanner_scope_add_symbol (scanner, 0, "HOTKEY", HOTKEY_SYMBOL_VALUE); + g_scanner_scope_add_symbol (scanner, 0, "UNDOKEY", UNDOKEY_SYMBOL_VALUE); + + g_scanner_scope_add_symbol (scanner, 1, "BUTTON1", (gpointer) 1); + g_scanner_scope_add_symbol (scanner, 1, "BUTTON2", (gpointer) 2); + g_scanner_scope_add_symbol (scanner, 1, "BUTTON3", (gpointer) 3); + g_scanner_scope_add_symbol (scanner, 1, "BUTTON4", (gpointer) 4); + g_scanner_scope_add_symbol (scanner, 1, "BUTTON5", (gpointer) 5); + g_scanner_scope_add_symbol (scanner, 1, "SHIFT", (gpointer) 11); + g_scanner_scope_add_symbol (scanner, 1, "CONTROL", (gpointer) 12); + g_scanner_scope_add_symbol (scanner, 1, "META", (gpointer) 13); + g_scanner_scope_add_symbol (scanner, 1, "ALT", (gpointer) 13); + + g_scanner_scope_add_symbol (scanner, 2, "size", (gpointer) SYM_SIZE); + g_scanner_scope_add_symbol (scanner, 2, "color", (gpointer) SYM_COLOR); + g_scanner_scope_add_symbol (scanner, 2, "arrowsize", (gpointer) SYM_ARROWSIZE); + g_scanner_scope_add_symbol (scanner, 2, "arrowtype", (gpointer) SYM_ARROWTYPE); + g_scanner_scope_add_symbol (scanner, 2, "minsize", (gpointer) SYM_MINSIZE); + g_scanner_scope_add_symbol (scanner, 2, "maxsize", (gpointer) SYM_MAXSIZE); + g_scanner_scope_add_symbol (scanner, 2, "radius", (gpointer) SYM_RADIUS); + g_scanner_scope_add_symbol (scanner, 2, "maxangle", (gpointer) SYM_MAXANGLE); + g_scanner_scope_add_symbol (scanner, 2, "minlen", (gpointer) SYM_MINLEN); + g_scanner_scope_add_symbol (scanner, 2, "simplify", (gpointer) SYM_SIMPLIFY); + g_scanner_scope_add_symbol (scanner, 2, "snap", (gpointer) SYM_SNAP); + + g_scanner_set_scope (scanner, 0); + scanner->config->scope_0_fallback = 0; +} + +/* + * returns the name of the tool, or NULL + * the string returned is allocated and then owned by the caller + */ +gchar* parse_name (GScanner *scanner) { GTokenType token; @@ -64,13 +118,8 @@ static gchar* parse_name (GScanner *scanner) len = strlen (scanner->value.v_string); name = g_strndup (scanner->value.v_string, len + 3); - token = g_scanner_get_next_token (scanner); - /* - * Are there any options to limit the scope of the definition? - */ - if (token == G_TOKEN_LEFT_BRACE) { g_scanner_set_scope (scanner, 1); @@ -111,20 +160,224 @@ static gchar* parse_name (GScanner *scanner) return name; } +/* + * get the "type" of the tool, e.g. PEN (e.g. =PEN), or a base tool + * style that is inherited (e.g. ="red pen") and store characteristics + * in style. + * + * allocates a new GdkRGBA color + * + * returns FALSE upon error + */ +gboolean parse_tool(GromitData *data, GScanner *scanner, GromitPaintContext *style) +{ + GTokenType token = g_scanner_cur_token(scanner); + gboolean color_allocated = FALSE; + + if (token != G_TOKEN_EQUAL_SIGN) + { + g_scanner_unexp_token( + scanner, G_TOKEN_EQUAL_SIGN, NULL, NULL, NULL, "aborting", TRUE); + goto cleanup; + } + + token = g_scanner_get_next_token (scanner); -enum tool_arguments { - SYM_SIZE = 1, - SYM_COLOR, - SYM_ARROWSIZE, - SYM_ARROWTYPE, - SYM_MINSIZE, - SYM_MAXSIZE, - SYM_MINLEN, - SYM_MAXANGLE, - SYM_RADIUS, - SYM_SIMPLIFY, - SYM_SNAP, -}; + // style defaults + style->type = GROMIT_PEN; + style->width = 7; + style->arrowsize = 0; + style->arrow_type = GROMIT_ARROW_END; + style->radius = 10; + style->minlen = style->radius * 5 / 2; + style->maxangle = 15; + style->simplify = 10; + style->snapdist = 0; + style->minwidth = 1; + style->maxwidth = G_MAXUINT; + + // allocate new color and copy default red fields + style->paint_color = g_malloc(sizeof (GdkRGBA)); + color_allocated = TRUE; + *style->paint_color = *data->red; + + if (token == G_TOKEN_SYMBOL) + { + style->type = (GromitPaintType) scanner->value.v_symbol; + token = g_scanner_get_next_token (scanner); + } + else if (token == G_TOKEN_STRING) + { + gchar *copy = parse_name (scanner); + g_printerr("parse_tool: string: %s\n", copy); + if(!copy) + goto cleanup; + token = g_scanner_cur_token(scanner); + GromitPaintContext *context = g_hash_table_lookup (data->tool_config, copy); + if (context) + { + // copy fields of context that is inherited from + style->type = context->type; + style->width = context->width; + style->arrowsize = context->arrowsize; + style->arrow_type = context->arrow_type; + style->radius = context->radius; + style->minlen = context->minlen; + style->maxangle = context->maxangle; + style->simplify = context->simplify; + style->snapdist = context->snapdist; + style->minwidth = context->minwidth; + style->maxwidth = context->maxwidth; + *style->paint_color = *context->paint_color; + + // nullify superfluous fields, although not really necessary + style->paint_ctx = NULL; + style->pressure = 0; + } + else + { + g_printerr ("WARNING: Unable to copy \"%s\": " + "not yet defined!\n", copy); + } + } + else + { + g_printerr ("Expected tool definition or name of template tool\n"); + goto cleanup; + } + return TRUE; + + cleanup: + if (color_allocated) + { + g_free(style->paint_color); + style->paint_color = NULL; + } + return FALSE; +} + +/* + * parse a single attribute (e.g. size=6) and store it in style, + * returning the attribute ID (here SYM_SIZE), or SYM_ERR if something + * went wrong. + */ +ToolAttribute parse_attribute(GScanner *scanner, GromitPaintContext *style) +{ + const gint id = (intptr_t) scanner->value.v_symbol; + GTokenType token; + if (id == SYM_SIZE) + { + gfloat v = parse_get_float(scanner, "Missing size"); + if (isnan(v)) return SYM_ERROR; + style->width = v + 0.5; + } + else if (id == SYM_COLOR) + { + token = g_scanner_get_next_token (scanner); + if (token != G_TOKEN_EQUAL_SIGN) + { + g_printerr ("Missing \"=\"... aborting\n"); + return SYM_ERROR; + } + token = g_scanner_get_next_token (scanner); + if (token != G_TOKEN_STRING) + { + g_printerr ("Missing Color (string)... aborting\n"); + return SYM_ERROR; + } + if (! gdk_rgba_parse (style->paint_color, scanner->value.v_string)) + { + g_printerr ("Unable to parse color. Keeping default.\n"); + } + } + else if (id == SYM_ARROWSIZE) + { + gfloat v = parse_get_float(scanner, "Missing arrowsize"); + if (isnan(v)) return SYM_ERROR; + style->arrowsize = v; + } + else if (id == SYM_ARROWTYPE) + { + token = g_scanner_get_next_token (scanner); + if (token != G_TOKEN_EQUAL_SIGN) + { + g_printerr ("Missing \"=\"... aborting\n"); + return SYM_ERROR; + } + token = g_scanner_get_next_token (scanner); + if (token != G_TOKEN_STRING) + { + g_printerr ("Missing arrowtype (string)... aborting\n"); + return SYM_ERROR; + } + if (! strcasecmp(scanner->value.v_string, "end")) + { + style->arrow_type = GROMIT_ARROW_END; + } + else if (! strcasecmp(scanner->value.v_string, "start")) + { + style->arrow_type = GROMIT_ARROW_START; + } + else if (! strcasecmp(scanner->value.v_string, "double")) + { + style->arrow_type = GROMIT_ARROW_DOUBLE; + } + else + { + g_printerr ("Arrow type must be \"start\", \"end\", or \"double\"... " + "aborting\n"); + return SYM_ERROR; + } + } + else if ((intptr_t) scanner->value.v_symbol == SYM_RADIUS) + { + gfloat v = parse_get_float(scanner, "Missing radius (float)"); + if (isnan(v)) return SYM_ERROR; + style->radius = v; + } + else if ((intptr_t) scanner->value.v_symbol == SYM_MAXANGLE) + { + gfloat v = parse_get_float(scanner, "Missing angle (float)"); + if (isnan(v)) return SYM_ERROR; + style->maxangle = v; + } + else if ((intptr_t) scanner->value.v_symbol == SYM_SIMPLIFY) + { + gfloat v = parse_get_float(scanner, "Missing simplify value (float)"); + if (isnan(v)) return SYM_ERROR; + style->simplify = v; + } + else if ((intptr_t) scanner->value.v_symbol == SYM_MINLEN) + { + gfloat v = parse_get_float(scanner, "Missing minlen value (float)"); + if (isnan(v)) return SYM_ERROR; + style->minlen = v; + } + else if ((intptr_t) scanner->value.v_symbol == SYM_SNAP) + { + gfloat v = parse_get_float(scanner, "Missing snap distance (float)"); + if (isnan(v)) return SYM_ERROR; + style->snapdist = v; + } + else if (id == SYM_MINSIZE) + { + gfloat v = parse_get_float(scanner, "Missing minsize"); + if (isnan(v)) return SYM_ERROR; + style->minwidth = v + 0.5; + } + else if (id == SYM_MAXSIZE) + { + gfloat v = parse_get_float(scanner, "Missing maxsize"); + if (isnan(v)) return SYM_ERROR; + style->maxwidth = v + 0.5; + } + else + { + g_printerr ("Unknown tool type?????\n"); + return SYM_ERROR; + } + return id; +} /* * get "=VALUE", where VALUE is a float @@ -147,25 +400,44 @@ gfloat parse_get_float(GScanner *scanner, const gchar *msg) } return scanner->value.v_float; } +/* + * parses a pen style definition (e.g. (color="red" size=3) ) + * and stores fields found in GromitStyleDef. + * + * returns FALSE upon any error + */ +gboolean parse_style(GScanner *scanner, GromitPaintContext *style) +{ + g_scanner_set_scope (scanner, 2); + scanner->config->int_2_float = 1; + GTokenType token = g_scanner_get_next_token (scanner); + while (token != G_TOKEN_RIGHT_PAREN) + { + if (token == G_TOKEN_SYMBOL) + { + if (parse_attribute(scanner, style) == SYM_ERROR) + return FALSE; + } + token = g_scanner_get_next_token (scanner); + } + g_scanner_set_scope (scanner, 0); + token = g_scanner_get_next_token (scanner); + return TRUE; +} +/* + * parse config file + */ gboolean parse_config (GromitData *data) { gboolean status = FALSE; GromitPaintContext *context=NULL; - GromitPaintContext *context_template=NULL; GScanner *scanner; GTokenType token; gchar *filename; int file; - - gchar *name, *copy; - - GromitPaintType type; - GdkRGBA *fg_color=NULL; - guint width, arrowsize, minwidth, maxwidth; - guint minlen, maxangle, radius, simplify, snapdist; - GromitArrowType arrowtype; + gchar *name; /* try user config location */ filename = g_strjoin (G_DIR_SEPARATOR_S, @@ -190,293 +462,58 @@ gboolean parse_config (GromitData *data) if (file < 0) { g_free(filename); GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(data->win), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_CLOSE, - _("No usable config file found, falling back to default tools.")); + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CLOSE, + _("No usable config file found, falling back to default tools.")); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); return FALSE; } scanner = g_scanner_new (NULL); + scanner_init(scanner); scanner->input_name = filename; - scanner->config->case_sensitive = 0; - scanner->config->scan_octal = 0; - scanner->config->identifier_2_string = 0; - scanner->config->char_2_token = 1; - scanner->config->numbers_2_int = 1; - scanner->config->int_2_float = 1; - - g_scanner_scope_add_symbol (scanner, 0, "PEN", (gpointer) GROMIT_PEN); - g_scanner_scope_add_symbol (scanner, 0, "LINE", (gpointer) GROMIT_LINE); - g_scanner_scope_add_symbol (scanner, 0, "RECT", (gpointer) GROMIT_RECT); - g_scanner_scope_add_symbol (scanner, 0, "SMOOTH", (gpointer) GROMIT_SMOOTH); - g_scanner_scope_add_symbol (scanner, 0, "ORTHOGONAL",(gpointer) GROMIT_ORTHOGONAL); - g_scanner_scope_add_symbol (scanner, 0, "ERASER", (gpointer) GROMIT_ERASER); - g_scanner_scope_add_symbol (scanner, 0, "RECOLOR", (gpointer) GROMIT_RECOLOR); - g_scanner_scope_add_symbol (scanner, 0, "HOTKEY", HOTKEY_SYMBOL_VALUE); - g_scanner_scope_add_symbol (scanner, 0, "UNDOKEY", UNDOKEY_SYMBOL_VALUE); - - g_scanner_scope_add_symbol (scanner, 1, "BUTTON1", (gpointer) 1); - g_scanner_scope_add_symbol (scanner, 1, "BUTTON2", (gpointer) 2); - g_scanner_scope_add_symbol (scanner, 1, "BUTTON3", (gpointer) 3); - g_scanner_scope_add_symbol (scanner, 1, "BUTTON4", (gpointer) 4); - g_scanner_scope_add_symbol (scanner, 1, "BUTTON5", (gpointer) 5); - g_scanner_scope_add_symbol (scanner, 1, "SHIFT", (gpointer) 11); - g_scanner_scope_add_symbol (scanner, 1, "CONTROL", (gpointer) 12); - g_scanner_scope_add_symbol (scanner, 1, "META", (gpointer) 13); - g_scanner_scope_add_symbol (scanner, 1, "ALT", (gpointer) 13); - - g_scanner_scope_add_symbol (scanner, 2, "size", (gpointer) SYM_SIZE); - g_scanner_scope_add_symbol (scanner, 2, "color", (gpointer) SYM_COLOR); - g_scanner_scope_add_symbol (scanner, 2, "arrowsize", (gpointer) SYM_ARROWSIZE); - g_scanner_scope_add_symbol (scanner, 2, "arrowtype", (gpointer) SYM_ARROWTYPE); - g_scanner_scope_add_symbol (scanner, 2, "minsize", (gpointer) SYM_MINSIZE); - g_scanner_scope_add_symbol (scanner, 2, "maxsize", (gpointer) SYM_MAXSIZE); - g_scanner_scope_add_symbol (scanner, 2, "radius", (gpointer) SYM_RADIUS); - g_scanner_scope_add_symbol (scanner, 2, "maxangle", (gpointer) SYM_MAXANGLE); - g_scanner_scope_add_symbol (scanner, 2, "minlen", (gpointer) SYM_MINLEN); - g_scanner_scope_add_symbol (scanner, 2, "simplify", (gpointer) SYM_SIMPLIFY); - g_scanner_scope_add_symbol (scanner, 2, "snap", (gpointer) SYM_SNAP); - - g_scanner_set_scope (scanner, 0); - scanner->config->scope_0_fallback = 0; - g_scanner_input_file (scanner, file); - token = g_scanner_get_next_token (scanner); + + GromitPaintContext style; + while (token != G_TOKEN_EOF) { if (token == G_TOKEN_STRING) { - /* - * New tool definition - */ - + // New tool definition name = parse_name (scanner); - if(!name) goto cleanup; - token = g_scanner_cur_token(scanner); - - if (token != G_TOKEN_EQUAL_SIGN) - { - g_scanner_unexp_token (scanner, G_TOKEN_EQUAL_SIGN, NULL, - NULL, NULL, "aborting", TRUE); - goto cleanup; - } - - token = g_scanner_get_next_token (scanner); - - /* defaults */ - type = GROMIT_PEN; - width = 7; - arrowsize = 0; - arrowtype = GROMIT_ARROW_END; - minwidth = 1; - maxwidth = G_MAXUINT; - radius = 10; - minlen = 2 * radius + radius / 2; - maxangle = 15; - simplify = 10; - snapdist = 0; - fg_color = data->red; - - if (token == G_TOKEN_SYMBOL) - { - type = (GromitPaintType) scanner->value.v_symbol; - token = g_scanner_get_next_token (scanner); - } - else if (token == G_TOKEN_STRING) - { - copy = parse_name (scanner); - if(!copy) - goto cleanup; - token = g_scanner_cur_token(scanner); - context_template = g_hash_table_lookup (data->tool_config, copy); - if (context_template) - { - type = context_template->type; - width = context_template->width; - arrowsize = context_template->arrowsize; - arrowtype = context_template->arrow_type; - radius = context_template->radius; - simplify = context_template->simplify; - minlen = context_template->minlen; - maxangle = context_template->maxangle; - snapdist = context_template->snapdist; - minwidth = context_template->minwidth; - maxwidth = context_template->maxwidth; - fg_color = context_template->paint_color; - } - else - { - g_printerr ("WARNING: Unable to copy \"%s\": " - "not yet defined!\n", copy); - } - } - else + if (!parse_tool(data, scanner, &style)) { - g_printerr ("Expected Tool-definition " - "or name of template tool\n"); + g_printerr("parse tool failed\n"); goto cleanup; } - /* Are there any tool-options? - */ - + // are there any tool-options? + token = g_scanner_cur_token(scanner); if (token == G_TOKEN_LEFT_PAREN) { - GdkRGBA *color = NULL; - g_scanner_set_scope (scanner, 2); - scanner->config->int_2_float = 1; - token = g_scanner_get_next_token (scanner); - while (token != G_TOKEN_RIGHT_PAREN) - { - if (token == G_TOKEN_SYMBOL) - { - if ((intptr_t) scanner->value.v_symbol == SYM_SIZE) - { - gfloat v = parse_get_float(scanner, "Missing Size (float)"); - if (isnan(v)) goto cleanup; - width = (guint) (v + 0.5); - } - else if ((intptr_t) scanner->value.v_symbol == SYM_COLOR) - { - token = g_scanner_get_next_token (scanner); - if (token != G_TOKEN_EQUAL_SIGN) - { - g_printerr ("Missing \"=\"... aborting\n"); - goto cleanup; - } - token = g_scanner_get_next_token (scanner); - if (token != G_TOKEN_STRING) - { - g_printerr ("Missing Color (string)... " - "aborting\n"); - goto cleanup; - } - color = g_malloc (sizeof (GdkRGBA)); - if (gdk_rgba_parse (color, scanner->value.v_string)) - { - fg_color = color; - } - else - { - g_printerr ("Unable to parse color. " - "Keeping default.\n"); - g_free (color); - } - color = NULL; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_ARROWSIZE) - { - gfloat v = parse_get_float(scanner, "Missing arrowsize (float)"); - if (isnan(v)) goto cleanup; - arrowsize = (guint)(v + 0.5); - arrowtype = GROMIT_ARROW_END; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_ARROWTYPE) - { - token = g_scanner_get_next_token (scanner); - if (token != G_TOKEN_EQUAL_SIGN) - { - g_printerr ("Missing \"=\"... aborting\n"); - goto cleanup; - } - token = g_scanner_get_next_token (scanner); - if (token != G_TOKEN_STRING) - { - g_printerr ("Missing Arrowsize (string)... " - "aborting\n"); - goto cleanup; - } - if (! strcasecmp(scanner->value.v_string, "end")) - arrowtype = GROMIT_ARROW_END; - else if (! strcasecmp(scanner->value.v_string, "start")) - arrowtype = GROMIT_ARROW_START; - else if (! strcasecmp(scanner->value.v_string, "double")) - arrowtype = GROMIT_ARROW_DOUBLE; - else - { - g_printerr ("Arrow type must be \"start\", \"end\", or \"double\"... " - "aborting\n"); - goto cleanup; - } - } - else if ((intptr_t) scanner->value.v_symbol == SYM_MINSIZE) - { - gfloat v = parse_get_float(scanner, "Missing minsize (float)"); - if (isnan(v)) goto cleanup; - minwidth = v; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_MAXSIZE) - { - gfloat v = parse_get_float(scanner, "Missing maxsize (float)"); - if (isnan(v)) goto cleanup; - maxwidth = v; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_RADIUS) - { - gfloat v = parse_get_float(scanner, "Missing radius (float)"); - if (isnan(v)) goto cleanup; - radius = v; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_MAXANGLE) - { - gfloat v = parse_get_float(scanner, "Missing angle (float)"); - if (isnan(v)) goto cleanup; - maxangle = v; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_SIMPLIFY) - { - gfloat v = parse_get_float(scanner, "Missing simplify value (float)"); - if (isnan(v)) goto cleanup; - simplify = v; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_MINLEN) - { - gfloat v = parse_get_float(scanner, "Missing minlen value (float)"); - if (isnan(v)) goto cleanup; - minlen = v; - } - else if ((intptr_t) scanner->value.v_symbol == SYM_SNAP) - { - gfloat v = parse_get_float(scanner, "Missing snap distance (float)"); - if (isnan(v)) goto cleanup; - snapdist = v; - } - else - { - g_printerr ("Unknown tool type?????\n"); - } - } - else - { - g_printerr ("skipped token!!!\n"); - } - token = g_scanner_get_next_token (scanner); - } - g_scanner_set_scope (scanner, 0); - token = g_scanner_get_next_token (scanner); + if (! parse_style(scanner, &style)) + goto cleanup; } - /* - * Finally we expect a semicolon - */ - + // finally we expect a semicolon + token = g_scanner_cur_token (scanner); if (token != ';') { g_printerr ("Expected \";\"\n"); goto cleanup; } - context = paint_context_new (data, type, fg_color, width, - arrowsize, arrowtype, - simplify, radius, maxangle, minlen, snapdist, - minwidth, maxwidth); + context = paint_context_new(data, style.type, style.paint_color, style.width, + style.arrowsize, style.arrow_type, + style.simplify, style.radius, style.maxangle, style.minlen, style.snapdist, + style.minwidth, style.maxwidth); g_hash_table_insert (data->tool_config, name, context); } else if (token == G_TOKEN_SYMBOL && @@ -547,11 +584,11 @@ gboolean parse_config (GromitData *data) /* alert user */ GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(data->win), - GTK_DIALOG_DESTROY_WITH_PARENT, - GTK_MESSAGE_WARNING, - GTK_BUTTONS_CLOSE, - _("Failed parsing config file %s, falling back to default tools."), - filename); + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_CLOSE, + _("Failed parsing config file %s, falling back to default tools."), + filename); gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); } diff --git a/src/config.h b/src/config.h index 52219f1..c3a2bb2 100644 --- a/src/config.h +++ b/src/config.h @@ -34,8 +34,33 @@ Returns TRUE if something got parsed successfully, FALSE otherwise. */ gboolean parse_config (GromitData *data); + +typedef enum { + SYM_ERROR = 0, + SYM_SIZE = 1, + SYM_COLOR, + SYM_ARROWSIZE, + SYM_ARROWTYPE, + SYM_MINSIZE, + SYM_MAXSIZE, + SYM_MINLEN, + SYM_MAXANGLE, + SYM_RADIUS, + SYM_SIMPLIFY, + SYM_SNAP +} ToolAttribute; + +void scanner_init(GScanner *scanner); +gboolean parse_tool(GromitData *data, GScanner *scanner, GromitPaintContext *style); +gchar* parse_name (GScanner *scanner); +gfloat parse_get_float(GScanner *scanner, const gchar *msg); +gboolean parse_style(GScanner *scanner, GromitPaintContext *style); +ToolAttribute parse_attribute(GScanner *scanner, GromitPaintContext *style); + int parse_args (int argc, char **argv, GromitData *data); + + /* fallback hot key, if not specified on command line or in config file */ #ifndef DEFAULT_HOTKEY #define DEFAULT_HOTKEY "F9" diff --git a/src/main.c b/src/main.c index de8c696..4b2a667 100644 --- a/src/main.c +++ b/src/main.c @@ -79,10 +79,9 @@ GromitPaintContext *paint_context_new (GromitData *data, if (type == GROMIT_ERASER) cairo_set_operator(context->paint_ctx, CAIRO_OPERATOR_CLEAR); - else - if (type == GROMIT_RECOLOR) + else if (type == GROMIT_RECOLOR) cairo_set_operator(context->paint_ctx, CAIRO_OPERATOR_ATOP); - else /* GROMIT_PEN */ + else /* GROMIT_PEN */ cairo_set_operator(context->paint_ctx, CAIRO_OPERATOR_OVER); return context; @@ -142,13 +141,16 @@ void paint_context_print (gchar *name, g_printerr(" radius: %u, minlen: %u, maxangle: %u ", context->radius, context->minlen, context->maxangle); } - g_printerr ("color: %s\n", gdk_rgba_to_string(context->paint_color)); + gchar *color_string = gdk_rgba_to_string(context->paint_color); + g_printerr ("color: %s\n", color_string); + g_free(color_string); } void paint_context_free (GromitPaintContext *context) { cairo_destroy(context->paint_ctx); + gdk_rgba_free(context->paint_color); g_free (context); } @@ -285,9 +287,9 @@ void select_tool (GromitData *data, guint req_buttons = 0, req_modifier = 0; guint i, j, success = 0; GromitPaintContext *context = NULL; - guchar *slave_name; - guchar *name; - guchar *default_name; + guchar *slave_name = NULL; + guchar *name = NULL; + guchar *default_name = NULL; /* get the data for this device */ GromitDeviceData *devdata = g_hash_table_lookup(data->devdatatable, device); @@ -394,6 +396,7 @@ void select_tool (GromitData *data, g_free (name); g_free (default_name); + g_free (slave_name); } else g_printerr ("ERROR: select_tool attempted to select nonexistent device!\n"); @@ -753,9 +756,8 @@ void setup_main_app (GromitData *data, int argc, char ** argv) gtk_selection_add_target (data->win, GA_CONTROL, GA_UNDO, 8); gtk_selection_add_target (data->win, GA_CONTROL, GA_REDO, 9); gtk_selection_add_target (data->win, GA_CONTROL, GA_LINE, 10); - - - + gtk_selection_add_target (data->win, GA_CONTROL, GA_CHGTOOL, 11); + gtk_selection_add_target (data->win, GA_CONTROL, GA_CHGATTR, 12); /* * Parse Config file @@ -1158,6 +1160,36 @@ int main_client (int argc, char **argv, GromitData *data) { action = GA_REDO; } + else if (strcmp (arg, "--change-tool") == 0 || + strcmp(arg, "-T") == 0) + { + if (argc <= i+1) + { + wrong_arg = TRUE; + g_printerr("--change-tool requires an argument\n"); + } + else + { + i++; + action = GA_CHGTOOL; + data->clientdata = argv[i]; + } + } + else if (strcmp (arg, "--change-attribute") == 0 || + strcmp(arg, "-A") == 0) + { + if (argc <= i+1) + { + wrong_arg = TRUE; + g_printerr("--change-attribute requires an argument\n"); + } + else + { + i++; + action = GA_CHGATTR; + data->clientdata = argv[i]; + } + } else { g_printerr ("Unknown Option to control a running Gromit-MPX process: \"%s\"\n", arg); @@ -1240,8 +1272,8 @@ int main (int argc, char **argv) gtk_selection_owner_set (data->win, GA_DATA, GDK_CURRENT_TIME); gtk_selection_add_target (data->win, GA_DATA, GA_TOGGLEDATA, 1007); gtk_selection_add_target (data->win, GA_DATA, GA_LINEDATA, 1008); - - + gtk_selection_add_target (data->win, GA_DATA, GA_CHGTOOLDATA, 1009); + gtk_selection_add_target (data->win, GA_DATA, GA_CHGATTRDATA, 1010); /* Try to get a status message. If there is a response gromit * is already active. @@ -1261,6 +1293,9 @@ int main (int argc, char **argv) gtk_main (); shutdown_input_devices(data); write_keyfile(data); // save keyfile config + g_free(data->red); + g_free(data->white); + g_free(data->black); g_free (data); return 0; } diff --git a/src/main.h b/src/main.h index 77ae382..9ccee8d 100644 --- a/src/main.h +++ b/src/main.h @@ -54,10 +54,14 @@ #define GA_RELOAD gdk_atom_intern ("Gromit/reload", FALSE) #define GA_UNDO gdk_atom_intern ("Gromit/undo", FALSE) #define GA_REDO gdk_atom_intern ("Gromit/redo", FALSE) +#define GA_CHGTOOL gdk_atom_intern ("Gromit/chgtool", FALSE) +#define GA_CHGATTR gdk_atom_intern ("Gromit/chgattr", FALSE) #define GA_DATA gdk_atom_intern ("Gromit/data", FALSE) #define GA_TOGGLEDATA gdk_atom_intern ("Gromit/toggledata", FALSE) #define GA_LINEDATA gdk_atom_intern ("Gromit/linedata", FALSE) +#define GA_CHGTOOLDATA gdk_atom_intern ("Gromit/chgtooldata", FALSE) +#define GA_CHGATTRDATA gdk_atom_intern ("Gromit/chgattrdata", FALSE) #define GROMIT_MAX_UNDO 100