Branch: refs/heads/master Author: Frank Lanitz frank@frank.uvena.de Committer: Frank Lanitz frank@frank.uvena.de Date: Mon, 16 Feb 2015 22:59:12 UTC Commit: 7ba7d163c5c99dcf2aff4dffb6b212d1fc5ad05e https://github.com/geany/geany-plugins/commit/7ba7d163c5c99dcf2aff4dffb6b212...
Log Message: ----------- Merge pull request #191 from b4n/pohelper/wip
Various PoHelper improvement, including a pretty stats dialog
Modified Paths: -------------- build/pohelper.m4 po/POTFILES.in pohelper/data/Makefile.am pohelper/data/menus.ui pohelper/data/stats.ui pohelper/src/gph-plugin.c
Modified: build/pohelper.m4 5 lines changed, 4 insertions(+), 1 deletions(-) =================================================================== @@ -1,6 +1,9 @@ AC_DEFUN([GP_CHECK_POHELPER], [ - GP_ARG_DISABLE([PoHelper], [yes]) + GP_ARG_DISABLE([PoHelper], [auto]) + + dnl We require GLib >= 2.22, and Geany only depends on 2.20 + GP_CHECK_PLUGIN_DEPS([PoHelper], [GLIB], [glib-2.0 >= 2.22])
GP_COMMIT_PLUGIN_STATUS([PoHelper])
Modified: po/POTFILES.in 3 lines changed, 2 insertions(+), 1 deletions(-) =================================================================== @@ -220,11 +220,12 @@ geniuspaste/src/geniuspaste.c markdown/src/conf.c markdown/src/plugin.c
- # Pairtaghighlighter pairtaghighlighter/src/pair_tag_highlighter.c + # PoHelper [type: gettext/glade]pohelper/data/menus.ui +[type: gettext/glade]pohelper/data/stats.ui pohelper/src/gph-plugin.c
# Pretty-printer
Modified: pohelper/data/Makefile.am 2 lines changed, 1 insertions(+), 1 deletions(-) =================================================================== @@ -1,4 +1,4 @@ include $(top_srcdir)/build/vars.docs.mk plugin = pohelper
-dist_plugindata_DATA = menus.ui +dist_plugindata_DATA = menus.ui stats.ui
Modified: pohelper/data/menus.ui 16 lines changed, 16 insertions(+), 0 deletions(-) =================================================================== @@ -224,6 +224,22 @@ </object> </child> <child> + <object class="GtkMenuItem" id="show_stats"> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="tooltip_text" translatable="yes">Show statistics of the current document</property> + <property name="label" translatable="yes">_Show stats</property> + <property name="use_underline">True</property> + </object> + </child> + <child> + <object class="GtkSeparatorMenuItem" id="separator6"> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + </child> + <child> <object class="GtkCheckMenuItem" id="update_headers_upon_save"> <property name="use_action_appearance">False</property> <property name="visible">True</property>
Modified: pohelper/data/stats.ui 231 lines changed, 231 insertions(+), 0 deletions(-) =================================================================== @@ -0,0 +1,231 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <requires lib="gtk+" version="2.16"/> + <!-- interface-naming-policy toplevel-contextual --> + <object class="GtkDialog" id="dialog"> + <property name="can_focus">False</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Translation statistics</property> + <property name="modal">True</property> + <property name="type_hint">dialog</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">2</property> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="button1"> + <property name="label">gtk-close</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="can_default">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">6</property> + <property name="spacing">5</property> + <child> + <object class="GtkDrawingArea" id="drawing_area"> + <property name="height_request">24</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkTable" id="table1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="n_rows">3</property> + <property name="n_columns">3</property> + <property name="column_spacing">6</property> + <child> + <object class="GtkColorButton" id="color_translated"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">none</property> + <property name="title" translatable="yes">Choose a color for translated strings</property> + <property name="color">#7373d2d21616</property> + </object> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="color_fuzzy"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">none</property> + <property name="title" translatable="yes">Choose a color for fuzzily translated strings</property> + <property name="color">#ededd4d40000</property> + </object> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkColorButton" id="color_untranslated"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="relief">none</property> + <property name="title" translatable="yes">Choose a color for untranslated strings</property> + <property name="color">#cccc00000000</property> + </object> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Translated:</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Fuzzy:</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">1</property> + <property name="label" translatable="yes">Untranslated:</property> + <attributes> + <attribute name="weight" value="bold"/> + </attributes> + </object> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="n_translated"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label">0</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="n_fuzzy"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label">0</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="n_untranslated"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="xalign">0</property> + <property name="label">0</property> + </object> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options">GTK_FILL</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="-7">button1</action-widget> + </action-widgets> + </object> +</interface>
Modified: pohelper/src/gph-plugin.c 661 lines changed, 593 insertions(+), 68 deletions(-) =================================================================== @@ -57,16 +57,26 @@ enum { GPH_KB_PASTE_UNTRANSLATED, GPH_KB_REFLOW, GPH_KB_TOGGLE_FUZZY, + GPH_KB_SHOW_STATS, GPH_KB_COUNT };
static struct Plugin { gboolean update_headers; + /* stats dialog colors */ + GdkColor color_translated; + GdkColor color_fuzzy; + GdkColor color_untranslated;
+ GeanyKeyGroup *key_group; GtkWidget *menu_item; } plugin = { TRUE, + { 0, 0x7373, 0xd2d2, 0x1616 }, /* tango mid green */ + { 0, 0xeded, 0xd4d4, 0x0000 }, /* tango mid yellow */ + { 0, 0xcccc, 0x0000, 0x0000 }, /* tango mid red */ + NULL, NULL };
@@ -126,6 +136,36 @@ find_style (ScintillaObject *sci, return pos; }
+/* like find_style(), but searches for the first style change from @start to + * @end. Returns the first position in the search direction with a style + * different from the one at @start, or -1 */ +static gint +find_style_boundary (ScintillaObject *sci, + gint start, + gint end) +{ + gint style = sci_get_style_at (sci, start); + gint pos; + + if (start > end) { /* search backwards */ + for (pos = start; pos >= end; pos--) { + if (sci_get_style_at (sci, pos) != style) + break; + } + if (pos < end) + return -1; + } else { + for (pos = start; pos < end; pos++) { + if (sci_get_style_at (sci, pos) != style) + break; + } + if (pos >= end) + return -1; + } + + return pos; +} + /* * find_message: * @doc: A #GeanyDocument @@ -147,6 +187,29 @@ find_message (GeanyDocument *doc, ScintillaObject *sci = doc->editor->sci; gint pos = find_style (sci, SCE_PO_MSGSTR, start, end);
+ /* if searching backwards and already in a msgstr style, search previous + * again not to go to current's start */ + if (pos >= 0 && start > end) { + gint style = sci_get_style_at (sci, start); + + /* don't take default style into account, so find previous non-default */ + if (style == SCE_PO_DEFAULT) { + gint style_pos = find_style_boundary (sci, start, end); + if (style_pos >= 0) { + style = sci_get_style_at (sci, style_pos); + } + } + + if (style == SCE_PO_MSGSTR || + style == SCE_PO_MSGSTR_TEXT || + style == SCE_PO_MSGSTR_TEXT_EOL) { + pos = find_style_boundary (sci, pos, end); + if (pos >= 0) { + pos = find_style (sci, SCE_PO_MSGSTR, pos, end); + } + } + } + if (pos >= 0) { pos = find_style (sci, SCE_PO_MSGSTR_TEXT, pos, sci_get_length (sci)); if (pos >= 0) { @@ -177,15 +240,10 @@ find_untranslated (GeanyDocument *doc, { if (doc_is_po (doc)) { ScintillaObject *sci = doc->editor->sci; - gboolean backwards = start > end;
while (start >= 0) { gint pos;
- if (backwards) { - start = find_style (sci, SCE_PO_MSGID, start, end); - } - pos = find_message (doc, start, end); if (pos < 0) { return -1; @@ -298,14 +356,11 @@ static void goto_prev (GeanyDocument *doc) { if (doc_is_po (doc)) { - gint pos = sci_get_current_position (doc->editor->sci); + gint pos = find_message (doc, sci_get_current_position (doc->editor->sci), + 0);
- pos = find_style (doc->editor->sci, SCE_PO_MSGID, pos, 0); if (pos >= 0) { - pos = find_message (doc, pos, 0); - if (pos >= 0) { - editor_goto_pos (doc->editor, pos, FALSE); - } + editor_goto_pos (doc->editor, pos, FALSE); } } } @@ -480,10 +535,20 @@ on_document_save (GObject *obj, }
static void -update_menus (GeanyDocument *doc) +update_menu_items_sensitivity (GeanyDocument *doc) { - if (plugin.menu_item) { - gtk_widget_set_sensitive (plugin.menu_item, doc_is_po (doc)); + gboolean sensitive = doc_is_po (doc); + guint i; + + /* since all the document-sensitive items have keybindings and all + * keybinginds that have a widget are document-sensitive, just walk + * the keybindings list to fetch the widgets */ + for (i = 0; i < GPH_KB_COUNT; i++) { + GeanyKeyBinding *kb = keybindings_get_item (plugin.key_group, i); + + if (kb->menu_item) { + gtk_widget_set_sensitive (kb->menu_item, sensitive); + } } }
@@ -492,7 +557,7 @@ on_document_activate (GObject *obj, GeanyDocument *doc, gpointer user_data) { - update_menus (doc); + update_menu_items_sensitivity (doc); }
static void @@ -501,7 +566,7 @@ on_document_filetype_set (GObject *obj, GeanyFiletype *old_ft, gpointer user_data) { - update_menus (doc); + update_menu_items_sensitivity (doc); }
static void @@ -509,7 +574,13 @@ on_document_close (GObject *obj, GeanyDocument *doc, gpointer user_data) { - update_menus (NULL); + GtkNotebook *nb = GTK_NOTEBOOK (geany_data->main_widgets->notebook); + + /* the :document-close signal is emitted before a document gets closed, + * so there always still is the current document open (hence the < 2) */ + if (gtk_notebook_get_n_pages (nb) < 2) { + update_menu_items_sensitivity (NULL); + } }
static void @@ -702,6 +773,7 @@ get_msgstr_text_at (GeanyDocument *doc, if (pos >= 0) { ScintillaObject *sci = doc->editor->sci; GString *msgstr = g_string_new (NULL); + gint length = sci_get_length (sci);
while (sci_get_style_at (sci, pos) == SCE_PO_MSGSTR_TEXT) { pos++; /* skip opening quote */ @@ -712,7 +784,7 @@ get_msgstr_text_at (GeanyDocument *doc, pos++; /* skip closing quote */
/* skip until next non-default style */ - while (sci_get_style_at (sci, pos) == SCE_PO_DEFAULT) { + while (pos < length && sci_get_style_at (sci, pos) == SCE_PO_DEFAULT) { pos++; } } @@ -723,6 +795,70 @@ get_msgstr_text_at (GeanyDocument *doc, return NULL; }
+/* finds the start of the msgid text at @pos. the returned position is the + * start of the msgid text style, so it's on the first opening quote. Returns + * -1 if none found */ +static gint +find_msgid_start_at (GeanyDocument *doc, + gint pos) +{ + if (doc_is_po (doc)) { + ScintillaObject *sci = doc->editor->sci; + gint style = sci_get_style_at (sci, pos); + + /* find the previous non-default style */ + while (pos > 0 && style == SCE_PO_DEFAULT) { + style = sci_get_style_at (sci, --pos); + } + + /* if a msgid or msgstr, go to the msgstr keyword */ + if (style == SCE_PO_MSGID_TEXT || + style == SCE_PO_MSGSTR || + style == SCE_PO_MSGSTR_TEXT) { + pos = find_style (sci, SCE_PO_MSGID, pos, 0); + if (pos >= 0) + style = SCE_PO_MSGID; + } + + if (style == SCE_PO_MSGID) { + return find_style (sci, SCE_PO_MSGID_TEXT, pos, sci_get_length (sci)); + } + } + + return -1; +} + +static GString * +get_msgid_text_at (GeanyDocument *doc, + gint pos) +{ + pos = find_msgid_start_at (doc, pos); + + if (pos >= 0) { + ScintillaObject *sci = doc->editor->sci; + GString *msgid = g_string_new (NULL); + gint length = sci_get_length (sci); + + while (sci_get_style_at (sci, pos) == SCE_PO_MSGID_TEXT) { + pos++; /* skip opening quote */ + while (sci_get_style_at (sci, pos + 1) == SCE_PO_MSGID_TEXT) { + g_string_append_c (msgid, sci_get_char_at (sci, pos)); + pos++; + } + pos++; /* skip closing quote */ + + /* skip until next non-default style */ + while (pos < length && sci_get_style_at (sci, pos) == SCE_PO_DEFAULT) { + pos++; + } + } + + return msgid; + } + + return NULL; +} + /* cuts @str in human-readable chunks for max @len. * cuts first at \n, then at spaces and punctuation */ static gchar ** @@ -895,6 +1031,76 @@ parse_flags_line (ScintillaObject *sci, } }
+static gint +find_msgid_line_at (GeanyDocument *doc, + gint pos) +{ + ScintillaObject *sci = doc->editor->sci; + gint line = sci_get_line_from_position (sci, pos); + gint style = find_first_non_default_style_on_line (sci, line); + + while (line > 0 && + (style == SCE_PO_DEFAULT || + (style == SCE_PO_MSGID && ! line_is_primary_msgid (sci, line)) || + style == SCE_PO_MSGID_TEXT || + style == SCE_PO_MSGSTR || + style == SCE_PO_MSGSTR_TEXT)) { + line--; + style = find_first_non_default_style_on_line (sci, line); + } + while (line < sci_get_line_count (sci) && + (style == SCE_PO_COMMENT || + style == SCE_PO_PROGRAMMER_COMMENT || + style == SCE_PO_REFERENCE || + style == SCE_PO_FLAGS || + style == SCE_PO_FUZZY)) { + line++; + style = find_first_non_default_style_on_line (sci, line); + } + + return (style == SCE_PO_MSGID) ? line : -1; +} + +static gint +find_flags_line_at (GeanyDocument *doc, + gint pos) +{ + gint line = find_msgid_line_at (doc, pos); + + if (line > 0) { + gint style; + + do { + line--; + style = find_first_non_default_style_on_line (doc->editor->sci, line); + } while (line > 0 && + (style == SCE_PO_COMMENT || + style == SCE_PO_PROGRAMMER_COMMENT || + style == SCE_PO_REFERENCE)); + + if (style != SCE_PO_FLAGS && style != SCE_PO_FUZZY) { + line = -1; + } + } + + return line; +} + +static GPtrArray * +get_flags_at (GeanyDocument *doc, + gint pos) +{ + GPtrArray *flags = NULL; + gint line = find_flags_line_at (doc, pos); + + if (line >= 0) { + flags = g_ptr_array_new_with_free_func (g_free); + parse_flags_line (doc->editor->sci, line, flags); + } + + return flags; +} + /* adds or remove @flag from @flags. returns whether the flag was added */ static gboolean toggle_flag (GPtrArray *flags, @@ -962,66 +1168,332 @@ on_kb_toggle_fuzziness (guint key_id) if (doc_is_po (doc)) { ScintillaObject *sci = doc->editor->sci; gint pos = sci_get_current_position (sci); - gint line = sci_get_line_from_position (sci, pos); - gint style = find_first_non_default_style_on_line (sci, line); - - /* find the msgid for the current line */ - while (line > 0 && - (style == SCE_PO_DEFAULT || - (style == SCE_PO_MSGID && ! line_is_primary_msgid (sci, line)) || - style == SCE_PO_MSGID_TEXT || - style == SCE_PO_MSGSTR || - style == SCE_PO_MSGSTR_TEXT)) { - line--; - style = find_first_non_default_style_on_line (sci, line); - } - while (line < sci_get_line_count (sci) && - (style == SCE_PO_COMMENT || - style == SCE_PO_PROGRAMMER_COMMENT || - style == SCE_PO_REFERENCE || - style == SCE_PO_FLAGS || - style == SCE_PO_FUZZY)) { - line++; - style = find_first_non_default_style_on_line (sci, line); - } + gint msgid_line = find_msgid_line_at (doc, pos); + gint flags_line = find_flags_line_at (doc, pos);
- if (style == SCE_PO_MSGID) { - gint msgid_line = line; - GPtrArray *flags = g_ptr_array_new (); + if (flags_line >= 0 || msgid_line >= 0) { + GPtrArray *flags = g_ptr_array_new_with_free_func (g_free);
sci_start_undo_action (sci);
- if (line > 0) { - /* search for an existing flags line */ - do { - line--; - style = find_first_non_default_style_on_line (sci, line); - } while (line > 0 && - (style == SCE_PO_COMMENT || - style == SCE_PO_PROGRAMMER_COMMENT || - style == SCE_PO_REFERENCE)); - - if (style == SCE_PO_FLAGS || style == SCE_PO_FUZZY) { - /* ok we got a line with flags, parse them and remove them */ - parse_flags_line (sci, line, flags); - delete_line (sci, line); - } else { - /* no flags, add the line */ - line = msgid_line; - } + if (flags_line >= 0) { + parse_flags_line (sci, flags_line, flags); + delete_line (sci, flags_line); + } else { + flags_line = msgid_line; }
toggle_flag (flags, "fuzzy"); - write_flags (sci, sci_get_position_from_line (sci, line), flags); + write_flags (sci, sci_get_position_from_line (sci, flags_line), flags);
sci_end_undo_action (sci);
- g_ptr_array_foreach (flags, (GFunc) g_free, NULL); g_ptr_array_free (flags, TRUE); } } }
+typedef struct { + gdouble translated; + gdouble fuzzy; + gdouble untranslated; +} StatsGraphData; + +/* + * rounded_rectangle: + * @cr: a Cairo context + * @x: X coordinate of the top-left corner of the rectangle + * @y: Y coordinate of the top-left corner of the rectangle + * @width: width of the rectangle + * @height: height of the rectangle + * @r1: radius of the top-left corner + * @r2: radius of the top-right corner + * @r3: radius of the bottom-right corner + * @r4: radius of the bottom-left corner + * + * Creates a rectangle path with rounded corners. + * + * Warning: The rectangle should be big enough to include the corners, + * otherwise the result will be weird. For example, if all corners + * radius are set to 5, the rectangle should be at least 10x10. + */ +static void +rounded_rectangle (cairo_t *cr, + gdouble x, + gdouble y, + gdouble width, + gdouble height, + gdouble r1, + gdouble r2, + gdouble r3, + gdouble r4) +{ + cairo_move_to (cr, x + r1, y); + cairo_arc (cr, x + width - r2, y + r2, r2, -G_PI/2.0, 0); + cairo_arc (cr, x + width - r3, y + height - r3, r3, 0, G_PI/2.0); + cairo_arc (cr, x + r4, y + height - r4, r4, G_PI/2.0, -G_PI); + cairo_arc (cr, x + r1, y + r1, r1, -G_PI, -G_PI/2.0); + cairo_close_path (cr); +} + +#if ! GTK_CHECK_VERSION (3, 0, 0) && ! defined (gtk_widget_get_allocated_width) +# define gtk_widget_get_allocated_width(w) (GTK_WIDGET (w)->allocation.width) +#endif +#if ! GTK_CHECK_VERSION (3, 0, 0) && ! defined (gtk_widget_get_allocated_height) +# define gtk_widget_get_allocated_height(w) (GTK_WIDGET (w)->allocation.height) +#endif + +static gboolean +stats_graph_draw (GtkWidget *widget, + cairo_t *cr, + gpointer user_data) +{ + const StatsGraphData *data = user_data; + const gint width = gtk_widget_get_allocated_width (widget); + const gint height = gtk_widget_get_allocated_height (widget); + const gdouble translated = width * data->translated; + const gdouble fuzzy = width * data->fuzzy; + const gdouble untranslated = width * data->untranslated; + const gdouble r = MIN (width / 4, height / 4); + cairo_pattern_t *pat; + + rounded_rectangle (cr, 0, 0, width, height, r, r, r, r); + cairo_clip (cr); + + gdk_cairo_set_source_color (cr, &plugin.color_translated); + cairo_rectangle (cr, 0, 0, translated, height); + cairo_fill (cr); + + gdk_cairo_set_source_color (cr, &plugin.color_fuzzy); + cairo_rectangle (cr, translated, 0, fuzzy, height); + cairo_fill (cr); + + gdk_cairo_set_source_color (cr, &plugin.color_untranslated); + cairo_rectangle (cr, translated + fuzzy, 0, untranslated, height); + cairo_fill (cr); + + /* draw a nice thin border */ + cairo_set_line_width (cr, 1.0); + cairo_set_source_rgba (cr, 0, 0, 0, 0.2); + rounded_rectangle (cr, 0.5, 0.5, width - 1, height - 1, r, r, r, r); + cairo_stroke (cr); + + /* draw a gradient to give the graph a little depth */ + pat = cairo_pattern_create_linear (0, 0, 0, height); + cairo_pattern_add_color_stop_rgba (pat, 0, 1, 1, 1, 0.2); + cairo_pattern_add_color_stop_rgba (pat, height, 0, 0, 0, 0.2); + cairo_set_source (cr, pat); + cairo_pattern_destroy (pat); + cairo_rectangle (cr, 0, 0, width, height); + cairo_paint (cr); + + return TRUE; +} + +static gboolean +stats_graph_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip *tooltip, + gpointer user_data) +{ + const StatsGraphData *data = user_data; + gchar *markup = NULL; + + if (keyboard_mode) { + gchar *translated_str = g_strdup_printf (_("<b>Translated:</b> %.3g%%"), + data->translated * 100); + gchar *fuzzy_str = g_strdup_printf (_("<b>Fuzzy:</b> %.3g%%"), + data->fuzzy * 100); + gchar *untranslated_str = g_strdup_printf (_("<b>Untranslated:</b> %.3g%%"), + data->untranslated * 100); + + markup = g_strconcat (translated_str, "\n", + fuzzy_str, "\n", + untranslated_str, NULL); + g_free (translated_str); + g_free (fuzzy_str); + g_free (untranslated_str); + } else { + const gint width = gtk_widget_get_allocated_width (widget); + + if (x <= width * data->translated) { + markup = g_strdup_printf (_("<b>Translated:</b> %.3g%%"), + data->translated * 100); + } else if (x <= width * (data->translated + data->fuzzy)) { + markup = g_strdup_printf (_("<b>Fuzzy:</b> %.3g%%"), data->fuzzy * 100); + } else { + markup = g_strdup_printf (_("<b>Untranslated:</b> %.3g%%"), + data->untranslated * 100); + } + } + + gtk_tooltip_set_markup (tooltip, markup); + g_free (markup); + + return TRUE; +} + +#if ! GTK_CHECK_VERSION (3, 0, 0) +static gboolean +on_stats_graph_expose_event (GtkWidget *widget, + GdkEvent *event, + gpointer data) +{ + cairo_t *cr = gdk_cairo_create (GDK_DRAWABLE (widget->window)); + gboolean ret = stats_graph_draw (widget, cr, data); + + cairo_destroy (cr); + + return ret; +} +#endif + +static void +on_color_button_color_notify (GtkWidget *widget, + GParamSpec *pspec, + gpointer user_data) +{ + gtk_color_button_get_color (GTK_COLOR_BUTTON (widget), user_data); +} + +static void +show_stats_dialog (guint all, + guint translated, + guint untranslated, + guint fuzzy) +{ + GError *error = NULL; + GtkBuilder *builder = gtk_builder_new (); + + gtk_builder_set_translation_domain (builder, GETTEXT_PACKAGE); + if (! gtk_builder_add_from_file (builder, PKGDATADIR"/pohelper/stats.ui", + &error)) { + g_critical (_("Failed to load UI definition, please check your " + "installation. The error was: %s"), error->message); + g_error_free (error); + } else { + StatsGraphData data; + GObject *dialog; + GObject *drawing_area; + + data.translated = all ? (translated * 1.0 / all) : 0; + data.fuzzy = all ? (fuzzy * 1.0 / all) : 0; + data.untranslated = all ? (untranslated * 1.0 / all) : 0; + + drawing_area = gtk_builder_get_object (builder, "drawing_area"); +#if ! GTK_CHECK_VERSION (3, 0, 0) + g_signal_connect (drawing_area, + "expose-event", G_CALLBACK (on_stats_graph_expose_event), + &data); +#else + g_signal_connect (drawing_area, + "draw", G_CALLBACK (stats_graph_draw), + &data); +#endif + g_signal_connect (drawing_area, + "query-tooltip", G_CALLBACK (stats_graph_query_tooltip), + &data); + gtk_widget_set_has_tooltip (GTK_WIDGET (drawing_area), TRUE); + + #define SET_LABEL_N(id, value) \ + do { \ + GObject *obj__ = gtk_builder_get_object (builder, (id)); \ + \ + if (! obj__) { \ + g_warning ("Object "%s" is missing from the UI definition", (id)); \ + } else { \ + gchar *text__ = g_strdup_printf (_("%u (%.3g%%)"), \ + (value), \ + all ? ((value) * 100.0 / all) : 0); \ + \ + gtk_label_set_text (GTK_LABEL (obj__), text__); \ + g_free (text__); \ + } \ + } while (0) + + SET_LABEL_N ("n_translated", translated); + SET_LABEL_N ("n_fuzzy", fuzzy); + SET_LABEL_N ("n_untranslated", untranslated); + + #undef SET_LABEL_N + + #define BIND_COLOR_BTN(id, color) \ + do { \ + GObject *obj__ = gtk_builder_get_object (builder, (id)); \ + \ + if (! obj__) { \ + g_warning ("Object "%s" is missing from the UI definition", (id)); \ + } else { \ + gtk_color_button_set_color (GTK_COLOR_BUTTON (obj__), (color)); \ + g_signal_connect (obj__, "notify::color", \ + G_CALLBACK (on_color_button_color_notify), \ + (color)); \ + /* queue a redraw on the drawing area so it uses the new color */ \ + g_signal_connect_swapped (obj__, "notify::color", \ + G_CALLBACK (gtk_widget_queue_draw), \ + drawing_area); \ + } \ + } while (0) + + BIND_COLOR_BTN ("color_translated", &plugin.color_translated); + BIND_COLOR_BTN ("color_fuzzy", &plugin.color_fuzzy); + BIND_COLOR_BTN ("color_untranslated", &plugin.color_untranslated); + + #undef BIND_COLOR_BTN + + dialog = gtk_builder_get_object (builder, "dialog"); + gtk_window_set_transient_for (GTK_WINDOW (dialog), + GTK_WINDOW (geany_data->main_widgets->window)); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (GTK_WIDGET (dialog)); + } + g_object_unref (builder); +} + +static void +on_kb_show_stats (guint key_id) +{ + GeanyDocument *doc = document_get_current (); + + if (doc_is_po (doc)) { + ScintillaObject *sci = doc->editor->sci; + const gint len = sci_get_length (sci); + gint pos = 0; + guint all = 0; + guint untranslated = 0; + guint fuzzy = 0; + + /* don't use find_message() because we want only match one block, not each + * msgstr as there might be plural forms */ + while ((pos = find_style (sci, SCE_PO_MSGID, pos, len)) >= 0 && + (pos = find_style (sci, SCE_PO_MSGSTR, pos, len)) >= 0) { + GString *msgid = get_msgid_text_at (doc, pos); + GString *msgstr = get_msgstr_text_at (doc, pos); + + if (msgid->len > 0) { + all++; + if (msgstr->len < 1) { + untranslated++; + } else { + GPtrArray *flags = get_flags_at (doc, pos); + + if (flags) { + fuzzy += ! toggle_flag (flags, "fuzzy"); + + g_ptr_array_free (flags, TRUE); + } + } + } + g_string_free (msgstr, TRUE); + g_string_free (msgid, TRUE); + } + + show_stats_dialog (all, all - untranslated - fuzzy, fuzzy, untranslated); + } +} + static const struct Action { guint id; const gchar *name; @@ -1064,7 +1536,10 @@ static const struct Action { N_("Reflow the current translation string"), "reflow_translation" }, { GPH_KB_TOGGLE_FUZZY, "toggle-fuzziness", on_kb_toggle_fuzziness, - N_("Toggle current translation fuzziness"), "toggle_fuzziness" } + N_("Toggle current translation fuzziness"), "toggle_fuzziness" }, + { GPH_KB_SHOW_STATS, "show-stats", + on_kb_show_stats, + N_("Show statistics of the current document"), "show_stats" } };
static void @@ -1138,6 +1613,47 @@ write_keyfile (GKeyFile *kf, return success; }
+/* + * get_setting_color: + * @kf: a #GKeyFile from which load the color + * @group: the key file group + * @key: the key file key + * @color: (out): the color to fill with the read value. If the key is not + * found, the color isn't updated + * + * Loads a color from a key file entry. + * + * Returns: %TRUE if the color was loaded, %FALSE otherwise. + */ +static gboolean +get_setting_color (GKeyFile *kf, + const gchar *group, + const gchar *key, + GdkColor *color) +{ + gboolean success = FALSE; + gchar *value = g_key_file_get_value (kf, group, key, NULL); + + if (value) { + success = gdk_color_parse (value, color); + g_free (value); + } + + return success; +} + +static void +set_setting_color (GKeyFile *kf, + const gchar *group, + const gchar *key, + const GdkColor *color) +{ + gchar *value = gdk_color_to_string (color); + + g_key_file_set_value (kf, group, key, value); + g_free (value); +} + static void load_config (void) { @@ -1148,6 +1664,9 @@ load_config (void) plugin.update_headers = utils_get_setting_boolean (kf, "general", "update-headers", plugin.update_headers); + get_setting_color (kf, "colors", "translated", &plugin.color_translated); + get_setting_color (kf, "colors", "fuzzy", &plugin.color_fuzzy); + get_setting_color (kf, "colors", "untranslated", &plugin.color_untranslated); } g_key_file_free (kf); g_free (filename); @@ -1162,6 +1681,9 @@ save_config (void) load_keyfile (kf, filename, G_KEY_FILE_KEEP_COMMENTS); g_key_file_set_boolean (kf, "general", "update-headers", plugin.update_headers); + set_setting_color (kf, "colors", "translated", &plugin.color_translated); + set_setting_color (kf, "colors", "fuzzy", &plugin.color_fuzzy); + set_setting_color (kf, "colors", "untranslated", &plugin.color_untranslated); write_keyfile (kf, filename);
g_key_file_free (kf); @@ -1171,7 +1693,6 @@ save_config (void) void plugin_init (GeanyData *data) { - GeanyKeyGroup *group; GtkBuilder *builder; GError *error = NULL; guint i; @@ -1213,7 +1734,8 @@ plugin_init (GeanyData *data) G_CALLBACK (on_document_save), NULL);
/* add keybindings */ - group = plugin_set_key_group (geany_plugin, "pohelper", GPH_KB_COUNT, NULL); + plugin.key_group = plugin_set_key_group (geany_plugin, "pohelper", + GPH_KB_COUNT, NULL);
for (i = 0; i < G_N_ELEMENTS (G_actions); i++) { GtkWidget *widget = NULL; @@ -1232,9 +1754,12 @@ plugin_init (GeanyData *data) } }
- keybindings_set_item (group, G_actions[i].id, G_actions[i].callback, 0, 0, - G_actions[i].name, _(G_actions[i].label), widget); + keybindings_set_item (plugin.key_group, G_actions[i].id, + G_actions[i].callback, 0, 0, G_actions[i].name, + _(G_actions[i].label), widget); } + /* initial items sensitivity update */ + update_menu_items_sensitivity (document_get_current ());
if (builder) { g_object_unref (builder);
-------------- This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).
plugins-commits@lists.geany.org