diff --git a/build/pairtaghighlighter.m4 b/build/pairtaghighlighter.m4 index 285edb2fb..40fc103dc 100644 --- a/build/pairtaghighlighter.m4 +++ b/build/pairtaghighlighter.m4 @@ -5,5 +5,6 @@ AC_DEFUN([GP_CHECK_PAIRTAGHIGHLIGHTER], AC_CONFIG_FILES([ pairtaghighlighter/Makefile pairtaghighlighter/src/Makefile + pairtaghighlighter/data/Makefile ]) ]) diff --git a/pairtaghighlighter/Makefile.am b/pairtaghighlighter/Makefile.am index 9caa92013..b02711c4b 100644 --- a/pairtaghighlighter/Makefile.am +++ b/pairtaghighlighter/Makefile.am @@ -1,4 +1,4 @@ include $(top_srcdir)/build/vars.auxfiles.mk -SUBDIRS = src +SUBDIRS = src data plugin = pairtaghighlighter diff --git a/pairtaghighlighter/data/Makefile.am b/pairtaghighlighter/data/Makefile.am new file mode 100644 index 000000000..05c23c47b --- /dev/null +++ b/pairtaghighlighter/data/Makefile.am @@ -0,0 +1,4 @@ +include $(top_srcdir)/build/vars.docs.mk +plugin = pairtaghighlighter + +dist_plugindata_DATA = prefs.ui diff --git a/pairtaghighlighter/data/prefs.ui b/pairtaghighlighter/data/prefs.ui new file mode 100644 index 000000000..dcf40029e --- /dev/null +++ b/pairtaghighlighter/data/prefs.ui @@ -0,0 +1,225 @@ + + + + + + 255 + 1 + -10 + + + True + False + 0 + none + + + True + False + 12 + + + True + False + + + True + False + 6 + + + True + False + Color to highlight matching tag pairs + Matched pair: + True + matching-pair-color-button + + + False + True + 0 + + + + + True + True + True + Color to highlight matching tag pairs + + + False + True + end + 1 + + + + + False + True + 0 + + + + + True + False + 6 + + + True + False + Color to highlight an unmatched tag + Unmatched tag: + True + nonmatching-pair-color-button + + + False + True + 0 + + + + + True + True + True + Color to highlight an unmatched tag + + + False + True + end + 1 + + + + + False + True + 1 + + + + + True + False + 6 + + + True + False + Color to highlight an empty tag +e.g. <br> + Empty tag: + True + empty-tag-color-button + + + False + True + 0 + + + + + True + True + True + Color to highlight an empty tag +e.g. <br> + + + False + True + end + 1 + + + + + False + True + 2 + + + + + True + False + 20 + + + True + False + Opaqueness of the highlight colors +Higher numbers are less transparent + Color alpha: + + + False + False + 0 + + + + + True + True + Opaqueness of the highlight colors +Higher numbers are less transparent + alpha-adjustment + 1 + 0 + left + + + True + True + end + 2 + + + + + False + True + 10 + 3 + + + + + Defaults + True + True + True + Return highlight color settings to the defaults + False + + + False + False + 5 + 4 + + + + + + + + + True + False + Highlight Colors + + + + + + + diff --git a/pairtaghighlighter/src/pair_tag_highlighter.c b/pairtaghighlighter/src/pair_tag_highlighter.c index b0081bb8a..9b7c624c5 100644 --- a/pairtaghighlighter/src/pair_tag_highlighter.c +++ b/pairtaghighlighter/src/pair_tag_highlighter.c @@ -11,15 +11,30 @@ #include "config.h" #include #include +#include +#include +#include #include "Scintilla.h" /* for the SCNotification struct */ #include "SciLexer.h" #define INDICATOR_TAGMATCH 9 #define MAX_TAG_NAME 64 +#define HIGHLIGHT_ALPHA_MIN 0 +#define HIGHLIGHT_ALPHA_MAX 255 +#define HIGHLIGHT_ALPHA_DEFAULT 60 -#define MATCHING_PAIR_COLOR 0x00ff00 /* green */ -#define NONMATCHING_PAIR_COLOR 0xff0000 /* red */ -#define EMPTY_TAG_COLOR 0xffff00 /* yellow */ +/* Tag types */ +enum { + MATCHING_PAIR, + NONMATCHING_PAIR, + EMPTY_TAG, + TAG_TYPE_COUNT +}; + +/* Colors for tag highlighting */ +const gint HIGHLIGHT_COLOR_DEFAULTS[TAG_TYPE_COUNT] = {0x00ff00, 0xff0000, 0xffff00}; +static gint highlightColor_G[TAG_TYPE_COUNT]; +static glong highlightAlpha_G = HIGHLIGHT_ALPHA_DEFAULT; /* Keyboard Shortcut */ enum { @@ -27,6 +42,17 @@ enum { KB_COUNT }; +/* Preference dialog objects */ +typedef struct PrefDialogObjects PrefDialogObjects; +struct PrefDialogObjects { + GtkWidget *main_prefs_frame; + GtkWidget *matching_pair_color_button; + GtkWidget *nonmatching_pair_color_button; + GtkWidget *empty_tag_color_button; + GObject *alpha_adjust; + GtkWidget *defaults_button; +}; + /* These items are set by Geany before plugin_init() is called. */ GeanyPlugin *geany_plugin; GeanyData *geany_data; @@ -41,7 +67,6 @@ PLUGIN_SET_TRANSLATABLE_INFO(LOCALEDIR, GETTEXT_PACKAGE, _("Pair Tag Highlighter _("Finds and highlights matching opening/closing HTML tag"), "1.1", "Volodymyr Kononenko ") - /* Searches tag brackets. * direction variable shows sets search direction: * TRUE - to the right @@ -137,7 +162,7 @@ static void highlight_tag(ScintillaObject *sci, gint openingBracket, scintilla_send_message(sci, SCI_INDICSETSTYLE, INDICATOR_TAGMATCH, INDIC_ROUNDBOX); scintilla_send_message(sci, SCI_INDICSETFORE, INDICATOR_TAGMATCH, rgb2bgr(color)); - scintilla_send_message(sci, SCI_INDICSETALPHA, INDICATOR_TAGMATCH, 60); + scintilla_send_message(sci, SCI_INDICSETALPHA, INDICATOR_TAGMATCH, highlightAlpha_G); scintilla_send_message(sci, SCI_INDICATORFILLRANGE, openingBracket, closingBracket-openingBracket+1); } @@ -146,9 +171,9 @@ static void highlight_tag(ScintillaObject *sci, gint openingBracket, static void highlight_matching_pair(ScintillaObject *sci) { highlight_tag(sci, highlightedBrackets[0], highlightedBrackets[1], - MATCHING_PAIR_COLOR); + highlightColor_G[MATCHING_PAIR]); highlight_tag(sci, highlightedBrackets[2], highlightedBrackets[3], - MATCHING_PAIR_COLOR); + highlightColor_G[MATCHING_PAIR]); } @@ -269,7 +294,7 @@ static void findMatchingOpeningTag(ScintillaObject *sci, gchar *tagName, gint op } } highlight_tag(sci, highlightedBrackets[0], highlightedBrackets[1], - NONMATCHING_PAIR_COLOR); + highlightColor_G[NONMATCHING_PAIR]); } @@ -318,7 +343,7 @@ static void findMatchingClosingTag(ScintillaObject *sci, gchar *tagName, gint cl } } highlight_tag(sci, highlightedBrackets[0], highlightedBrackets[1], - NONMATCHING_PAIR_COLOR); + highlightColor_G[NONMATCHING_PAIR]); } @@ -331,7 +356,7 @@ static void findMatchingTag(ScintillaObject *sci, gint openingBracket, gint clos return; if(is_tag_self_closing(sci, closingBracket) || is_tag_empty(tagName)) { - highlight_tag(sci, openingBracket, closingBracket, EMPTY_TAG_COLOR); + highlight_tag(sci, openingBracket, closingBracket, highlightColor_G[EMPTY_TAG]); } else { if(isTagOpening) findMatchingClosingTag(sci, tagName, closingBracket); @@ -425,6 +450,176 @@ on_kb_goto_matching_tag (guint key_id) } } +/*** Configuration file ***/ + +/* Returns the name of the configuration file. The string must be freed by the caller */ +static gchar *get_config_filename (void) +{ + return g_build_filename (geany_data->app->configdir, "plugins", + PLUGIN, PLUGIN".conf", NULL); +} + +/* Read a color setting from the key file. Returns -1 if not valid or can't be read */ +static gint key_file_get_color (GKeyFile *key_file, const gchar *group, const gchar *key) +{ + glong value = -1; + gchar *value_str = g_key_file_get_value (key_file, group, key, NULL); + + if (value_str) { + glong temp_value; + gchar *index = value_str; + + /* get to the first hex digit */ + while (*index != '\0' && !isxdigit(*index)) + index++; + + /* attempt to convert the value string to an RGB color integer */ + if (*index != '\0') { + errno = 0; + temp_value = strtol (index, NULL, 16); + if (!errno) + if (temp_value >= 0 && temp_value <= 0xffffff) + value = temp_value; + } + } + + return (gint) value; +} + +/* Write a color setting to the key file */ +static void key_file_write_color (GKeyFile *key_file, const gchar *group, + const gchar *key, gint color) +{ + if (color >= 0 && color <= 0xffffff) { + gchar value_str[8]; + g_snprintf (value_str, sizeof (value_str), "#%.6X", color); + g_key_file_set_value (key_file, group, key, value_str); + } else + g_warning ("key_file_write_color(): Value out of bounds %d\n", color); +} + +/* Read a long integer value from the key file. Returns FALSE if there is an error + * reading the value, TRUE otherwise + */ +static gboolean key_file_get_long (GKeyFile *key_file, const gchar *group, + const gchar *key, glong *value) +{ + gboolean success = FALSE; + gchar *value_str = g_key_file_get_value (key_file, group, key, NULL); + + if (value_str) { + glong temp_value; + gchar *end_ptr; + + /* attempt to convert the value string to an integer */ + errno = 0; + temp_value = strtol (value_str, &end_ptr, 10); + if (!errno && (end_ptr != value_str)) { + *value = temp_value; + success = TRUE; + } + } + + return success; +} + +/* Write a long integer setting to the key file */ +static void key_file_write_long (GKeyFile *key_file, const gchar *group, + const gchar *key, glong value) +{ + gchar *value_str; + value_str = g_strdup_printf ("%ld", value); + g_key_file_set_value (key_file, group, key, value_str); + g_free (value_str); +} + +/* Loads the key file from the config file. Returns TRUE if successful, FALSE otherwise */ +static gboolean load_key_file (GKeyFile *key_file, gchar *config_file, GKeyFileFlags flags) +{ + gboolean success = TRUE; + GError *error = NULL; + + if (!g_key_file_load_from_file(key_file, config_file, flags, &error)) { + if (error->code != G_FILE_ERROR_NOENT) + g_warning ("load_key_file(): Config file error\n%s", error->message); + g_error_free (error); + success = FALSE; + } + return success; +} + +/* Load the configuration from the config file, if it exists */ +static void load_config (void) +{ + GKeyFile *key_file = g_key_file_new (); + gchar *config_file = get_config_filename (); + + if (load_key_file (key_file, config_file, G_KEY_FILE_NONE)) { + gint color; + glong temp_value; + color = key_file_get_color (key_file, "color", "matching-pair"); + if (color != -1) + highlightColor_G[MATCHING_PAIR] = color; + else + g_warning("load_config(): matching-pair not loaded\n"); + color = key_file_get_color (key_file, "color", "nonmatching-pair"); + if (color != -1) + highlightColor_G[NONMATCHING_PAIR] = color; + else + g_warning("load_config(): nonmatching-pair not loaded\n"); + color = key_file_get_color (key_file, "color", "empty-tag"); + if (color != -1) + highlightColor_G[EMPTY_TAG] = color; + else + g_warning("load_config(): empty-tag not read\n"); + if (key_file_get_long (key_file, "color", "alpha", &temp_value)) + if (temp_value >= HIGHLIGHT_ALPHA_MIN && temp_value <= HIGHLIGHT_ALPHA_MAX) + highlightAlpha_G = temp_value; + else + g_warning("load_config(): alpha out of range\n"); + else + g_warning("load_config(): alpha not loaded\n"); + } + + g_key_file_free (key_file); + g_free (config_file); +} + +/* Write the configuration to the config file */ +static void write_config (void) +{ + GKeyFile *key_file = g_key_file_new (); + gchar *config_file = get_config_filename (); + gchar *dir_name = g_path_get_dirname (config_file); + gint error_code; + GError *error = NULL; + + load_key_file (key_file, config_file, G_KEY_FILE_KEEP_COMMENTS); + key_file_write_color (key_file, "color", "matching-pair", + highlightColor_G[MATCHING_PAIR]); + key_file_write_color (key_file, "color", "nonmatching-pair", + highlightColor_G[NONMATCHING_PAIR]); + key_file_write_color (key_file, "color", "empty-tag", + highlightColor_G[EMPTY_TAG]); + key_file_write_long (key_file, "color", "alpha", highlightAlpha_G); + + /* create the config file's directory, if it doesn't exist */ + error_code = utils_mkdir (dir_name, TRUE); + if (! error_code) { + if (! g_key_file_save_to_file (key_file, config_file, &error)) { + g_warning ("write_config(): Failed to write to file '%s'\n%s", + config_file, error->message); + g_error_free (error); + } + } else + g_warning ("write_config(): Failed to create directory '%s'\n%s", + dir_name, g_strerror (error_code)); + + g_key_file_free (key_file); + g_free (dir_name); + g_free (config_file); +} + PluginCallback plugin_callbacks[] = { { "editor-notify", (GCallback) &on_editor_notify, FALSE, NULL }, @@ -438,6 +633,8 @@ void plugin_init(GeanyData *data) group = plugin_set_key_group (geany_plugin, "Pair Tag Highlighter", KB_COUNT, NULL); keybindings_set_item (group, KB_MATCH_TAG, on_kb_goto_matching_tag, 0, 0, "goto_matching_tag", _("Go To Matching Tag"), NULL); + memcpy (&highlightColor_G, &HIGHLIGHT_COLOR_DEFAULTS, sizeof (highlightColor_G)); + load_config (); } @@ -451,3 +648,196 @@ void plugin_cleanup(void) clear_previous_highlighting(doc->editor->sci, highlightedBrackets[2], highlightedBrackets[3]); } } + +/*** Preference dialog ***/ + +/* Returns the path of the plugin's data directory. The path must be freed + * by the caller + */ +static gchar * get_data_dir_path (const gchar *filename) +{ + gchar *prefix = NULL; + gchar *path; + +#ifdef G_OS_WIN32 + prefix = g_win32_get_package_installation_directory_of_module (NULL); +#elif defined(__APPLE__) + if (g_getenv ("GEANY_PLUGINS_SHARE_PATH")) + return g_build_filename (g_getenv ("GEANY_PLUGINS_SHARE_PATH"), + PLUGIN, filename, NULL); +#endif + path = g_build_filename (prefix ? prefix : "", PLUGINDATADIR, filename, NULL); + g_free (prefix); + return path; +} + +#if GTK_CHECK_VERSION(3,4,0) +/* Convert an RGB (0xRRGGBB) number in integer form into a GdkRGBA */ +static void rgba_from_int (GdkRGBA *color, gint num) +{ + gfloat conv_factor = 1.0 / 255; + color->red = ((num & 0xff0000) >> 16) * conv_factor; + color->green = ((num & 0x00ff00) >> 8) * conv_factor; + color->blue = (num & 0x0000ff) * conv_factor; + color->alpha = 1.0; +} + +/* Convert a GdkRGBA into an integer RGB value (0xRRGGBB) */ +static int int_from_rbga (GdkRGBA *color) +{ + gfloat conv_factor = 255.0; + int red = (int) (color->red * conv_factor); + int green = (int) (color->green * conv_factor); + int blue = (int) (color->blue * conv_factor); + return ((red << 16) | (green << 8) | blue); +} +#else +/* Convert an RGB (0xRRGGBB) number in integer form into a GdkColor */ +static void color_from_int (GdkColor *color, gint num) +{ + gint conv_factor = 65535 / 255; + color->red = ((num & 0xff0000) >> 16) * conv_factor; + color->green = ((num & 0x00ff00) >> 8) * conv_factor; + color->blue = (num & 0x0000ff) * conv_factor; +} + +/* Convert a GdkColor into an integer RGB value (0xRRGGBB) */ +static int int_from_color (GdkColor *color) +{ + gfloat conv_factor = 255.0 / 65535.0; + int red = (int) (color->red * conv_factor); + int green = (int) (color->green * conv_factor); + int blue = (int) (color->blue * conv_factor); + return ((red << 16) | (green << 8) | blue); +} +#endif + +static void free_config_dialog_objects (PrefDialogObjects *dialog_objects) +{ + g_object_unref (dialog_objects->main_prefs_frame); + g_free (dialog_objects); +} + +/* Set the color of the color button from an integer RGB value */ +static void set_color_button_color (GtkWidget *color_button, gint value) +{ +#if GTK_CHECK_VERSION(3,4,0) + GdkRGBA color; + rgba_from_int (&color, value); + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (color_button), &color); +#else + GdkColor color; + color_from_int (&color, value); + gtk_color_button_set_color (GTK_COLOR_BUTTON (color_button), &color); +#endif +} + +/* Get the color in integer RGB form of the color button */ +static gint get_color_button_color (GtkWidget *color_button) +{ +#if GTK_CHECK_VERSION(3,4,0) + GdkRGBA color; + gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (color_button), &color); + return int_from_rbga (&color); +#else + GdkColor color; + gtk_color_button_get_color (GTK_COLOR_BUTTON (color_button), &color); + return int_from_color (&color); +#endif +} + +/* Handle responses to the preferences dialog */ +static void on_configure_response (GtkDialog *dialog, gint response, + PrefDialogObjects *dialog_objects) +{ + if (response == GTK_RESPONSE_APPLY || response == GTK_RESPONSE_OK) { + /* update configuration values from the preferences dialog */ + highlightColor_G[MATCHING_PAIR] = + get_color_button_color (dialog_objects->matching_pair_color_button); + highlightColor_G[NONMATCHING_PAIR] = + get_color_button_color (dialog_objects->nonmatching_pair_color_button); + highlightColor_G[EMPTY_TAG] = + get_color_button_color (dialog_objects->empty_tag_color_button); + highlightAlpha_G = + (gint) gtk_adjustment_get_value (GTK_ADJUSTMENT (dialog_objects->alpha_adjust)); + + write_config (); + + /* update the current document, if it's a markup document */ + GeanyDocument *document = document_get_current (); + if (document) { + gint lexer; + lexer = sci_get_lexer(document->editor->sci); + if((lexer == SCLEX_HTML) || (lexer == SCLEX_XML) || (lexer == SCLEX_PHPSCRIPT)) + run_tag_highlighter(document->editor->sci); + } + } +} + +/* Return the settings in the preferences dialog to the defaults */ +static void on_defaults_button_pressed (GtkButton *button, PrefDialogObjects *dialog_objects) +{ + set_color_button_color (dialog_objects->matching_pair_color_button, + HIGHLIGHT_COLOR_DEFAULTS[MATCHING_PAIR]); + set_color_button_color (dialog_objects->nonmatching_pair_color_button, + HIGHLIGHT_COLOR_DEFAULTS[NONMATCHING_PAIR]); + set_color_button_color (dialog_objects->empty_tag_color_button, + HIGHLIGHT_COLOR_DEFAULTS[EMPTY_TAG]); + gtk_adjustment_set_value (GTK_ADJUSTMENT (dialog_objects->alpha_adjust), + (gdouble) HIGHLIGHT_ALPHA_DEFAULT); +} + +/* Set up the preferences dialog */ +GtkWidget * +plugin_configure (GtkDialog *dialog) +{ + GError *error = NULL; + GtkWidget *prefs_frame = NULL; + GtkBuilder *builder = gtk_builder_new (); + gchar *path = get_data_dir_path ("prefs.ui"); + + /* get the builder from the UI file */ + gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE); + if (! gtk_builder_add_from_file (builder, path, &error)) { + g_critical (_("Can't find the preferences UI file!\n%s"), error->message); + g_error_free (error); + } else { + /* get the dialog objects from the builder */ + PrefDialogObjects *dialog_objects = g_malloc(sizeof (PrefDialogObjects)); + dialog_objects->main_prefs_frame = + GTK_WIDGET (gtk_builder_get_object (builder, "prefs-frame")); + dialog_objects->matching_pair_color_button = + GTK_WIDGET (gtk_builder_get_object (builder, "matching-pair-color-button")); + set_color_button_color (dialog_objects->matching_pair_color_button, + highlightColor_G[MATCHING_PAIR]); + dialog_objects->nonmatching_pair_color_button = + GTK_WIDGET (gtk_builder_get_object (builder, "nonmatching-pair-color-button")); + set_color_button_color (dialog_objects->nonmatching_pair_color_button, + highlightColor_G[NONMATCHING_PAIR]); + dialog_objects->empty_tag_color_button = + GTK_WIDGET (gtk_builder_get_object (builder, "empty-tag-color-button")); + set_color_button_color (dialog_objects->empty_tag_color_button, + highlightColor_G[EMPTY_TAG]); + dialog_objects->alpha_adjust = + G_OBJECT (gtk_builder_get_object (builder, "alpha-adjustment")); + gtk_adjustment_set_value (GTK_ADJUSTMENT (dialog_objects->alpha_adjust), + (gdouble) highlightAlpha_G); + dialog_objects->defaults_button = + GTK_WIDGET (gtk_builder_get_object (builder, "defaults-button")); + + /* increase the reference count of the dialog frame */ + prefs_frame = g_object_ref_sink (dialog_objects->main_prefs_frame); + + /* connect handlers */ + g_signal_connect_data (dialog, "response", + G_CALLBACK (on_configure_response), dialog_objects, + (GClosureNotify) free_config_dialog_objects, 0); + g_signal_connect (dialog_objects->defaults_button, "clicked", + G_CALLBACK (on_defaults_button_pressed), dialog_objects); + } + + g_free (path); + g_object_unref (builder); + + return prefs_frame; +}