[geany/geany-plugins] 7ba7d1: Merge pull request #191 from b4n/pohelper/wip

Frank Lanitz git-noreply at xxxxx
Mon Feb 16 22:59:12 UTC 2015


Branch:      refs/heads/master
Author:      Frank Lanitz <frank at frank.uvena.de>
Committer:   Frank Lanitz <frank at frank.uvena.de>
Date:        Mon, 16 Feb 2015 22:59:12 UTC
Commit:      7ba7d163c5c99dcf2aff4dffb6b212d1fc5ad05e
             https://github.com/geany/geany-plugins/commit/7ba7d163c5c99dcf2aff4dffb6b212d1fc5ad05e

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).


More information about the Plugins-Commits mailing list