Dia find & replace?



Hi Dia folks.

I sent this patch for "find & replace" to Hans a few days ago.  I know he's a very busy guy, but I am a 
little worried that I may have been spam-filtered!  :)

This patch adds a find&replace option for *all* string/text properties (not just names) to Dia.

So, for example, you can find & replace attribute names or types inside UML class objects.

Details, some questions, and the patch diff are all below.  Any suggestions, criticisms, etc would be most 
welcome!

Thanks,

Johann

--- On Tue, 10/7/08, Johann Tienhaara <jtienhaara yahoo com> wrote:
Hi Hans.

I downloaded Dia trunk to add "find &
replace" to Dia, only to discover, to my delight, that
you had already added the feature quite some time ago! 
That'll teach me to stop using my ancient distro-bundled
version...  I'm going to live on the bleeding edge from
now on.  :)

I made a few changes to find-and-replace.c so that --
optionally -- any and all text properties can be found &
replaced (rather than just the "name" and
"text" properties of an object).

This gives me the rest of what I was looking for -- the
ability to replace text in UML attribute types and operation
parameter names and what-not.

I would definitely appreciate your feedback.  Especially
since some of the data structures had me scratching my head
a bit (like the list-of-property-lists "records"
inside sarray and darray properties -- why a list-of-lists
of Properties?  Why not just a list of Properties?).  I
suspect I've made a few hacks that should be re-done
with cleaner/safer/better solutions.

I also am not yet sure whether there's a gauntlet of
automated and/or manual tests that you run these sorts of
changes through...?

I have not yet touched the i18n/l10n stuff either.  There
is currently a rather cumbersome message for the "all
properties" checkbox.  I'd rather have a
terse-yet-still-meaningful message before I figure out how
to hack the message catalogs.


At the advice of the woefully out of date FAQ :)
(http://www.gnome.org/projects/dia/faq.html#MakingPatches) I
decided to send the patch in its current state directly to
you, since the text is about 10K (and it's pretty hard
to read in an email, to boot).

Let me know if you have any questions, or if you want me to
redirect my email somewhere else, etc.  Otherwise I would
greatly appreciate any feedback you can provide, whenever
you have a chance.

Thanks very much Hans & all the best,

Johann Tienhaara
Halifax, N.S., Canada


Index: app/find-and-replace.c
===================================================================
--- app/find-and-replace.c      (revision 4119)
+++ app/find-and-replace.c      (working copy)
@@ -4,6 +4,7 @@
  * find-and-replace.c - common functionality applied to diagram
  *
  * Copyright (C) 2008 Hans Breuer
+ * Copyright (C) 2008 Johann Tienhaara (patched)
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -45,7 +46,10 @@
 
 enum {
   MATCH_CASE = (1<<0),
-  MATCH_WORD = (1<<1)
+  MATCH_WORD = (1<<1),
+
+  /* Don't just match the name/text - match UML attributes etc too? */
+  MATCH_ALL_PROPERTIES = (1<<2)
 };
 
 typedef struct _SearchData {
@@ -58,42 +62,41 @@
   gboolean seen_last;
 } SearchData;
 
-/*! Match and possibly modify the given objects property */
-Property *
-_match_string_prop (DiaObject *obj, const SearchData *sd, const gchar *replacement)
+
+/*! Match and possibly modify the given object's given property.
+ * Returns FALSE if not matched or if the input property is NULL. */
+gboolean
+_match_text_prop (DiaObject *obj, const SearchData *sd, const gchar *replacement, Property *prop, gchar 
**value_to_match )
 {
-  Property *prop;
-  gchar   **name;
-  gboolean ret = FALSE;
+  gboolean is_match = FALSE;
   gchar    *repl = NULL;
 
-  if ((prop = object_prop_by_name(obj, "name")) != NULL)
-    name = &((StringProperty *)prop)->string_data;
-  else if ((prop = object_prop_by_name(obj, "text")) != NULL)
-    name = &((TextProperty *)prop)->text_data;
-
-  if (!prop)
-    return NULL;
+  if (!prop) {
+    return FALSE;
+  }
+  else if ( value_to_match == NULL || *value_to_match == NULL ) {
+    return FALSE;
+  }
 
   /* search part */
   if (sd->flags & MATCH_CASE) {
-    const gchar *p = strstr (*name, sd->key);
-    ret = p != NULL;
+    const gchar *p = strstr (*value_to_match, sd->key);
+    is_match = p != NULL;
     if (p && replacement) {
-      gchar *a = g_strndup (*name, p - *name);
+      gchar *a = g_strndup (*value_to_match, p - *value_to_match);
       gchar *b = g_strdup (p + strlen(sd->key));
       repl = g_strdup_printf ("%s%s%s", a, replacement, b);
       g_free (a);
       g_free (b);
     }
   } else {
-    gchar *s1 = g_utf8_casefold (*name, -1);
+    gchar *s1 = g_utf8_casefold (*value_to_match, -1);
     gchar *s2 = g_utf8_casefold (sd->key, -1);
     const gchar *p = strstr (s1, s2);
-    ret = p != NULL;
+    is_match = p != NULL;
     if (p && replacement) {
-      gchar *a = g_strndup (*name, p - s1);
-      gchar *b = g_strdup (*name + strlen(a) + strlen(sd->key));
+      gchar *a = g_strndup (*value_to_match, p - s1);
+      gchar *b = g_strdup (*value_to_match + strlen(a) + strlen(sd->key));
       repl = g_strdup_printf ("%s%s%s", a, replacement, b);
       g_free (a);
       g_free (b);
@@ -103,36 +106,242 @@
   }
 
   if (sd->flags & MATCH_WORD)
-    ret = (ret && strlen(*name) == strlen(sd->key));
+    is_match = (is_match && strlen(*value_to_match) == strlen(sd->key));
 
   /* replace part */
-  if (ret && replacement) {
-    g_free (*name);
-    *name = repl;
+  if (is_match && replacement) {
+    g_free (*value_to_match);
+    *value_to_match = repl;
   } else {
     g_free (repl);
   }
   
-  if (ret)
-    return prop;
-    
-  prop->ops->free(prop);
-  return NULL;
+  if ( is_match ) {
+    return TRUE;
+  }
+  else
+  {
+    return FALSE;
+  }
 }
 
+
+/*! Match and possibly modify the given object's name/text property */
+GPtrArray *
+_match_name_prop (DiaObject *obj, const SearchData *sd, const gchar *replacement)
+{
+  Property *prop;
+  gchar   **name;
+  gboolean is_match = FALSE;
+  GPtrArray *plist = NULL;
+
+  if ((prop = object_prop_by_name(obj, "name")) != NULL) {
+    name = &((StringProperty *)prop)->string_data;
+  }
+  else if ((prop = object_prop_by_name(obj, "text")) != NULL) {
+    name = &((TextProperty *)prop)->text_data;
+  }
+
+  if ( prop == NULL ) {
+    return NULL;
+  }
+
+  is_match = _match_text_prop ( obj, sd, replacement, prop, name );
+
+  if ( is_match == FALSE ) {
+    prop->ops->free ( prop );
+    return NULL;
+  }
+
+  plist = prop_list_from_single (prop);
+
+  return plist;
+}
+
+
+/*! Match and possibly modify one property in an object. */
+gboolean
+_match_prop ( DiaObject *obj, const SearchData *sd, const gchar *replacement, Property *prop )
+{
+  PropertyType prop_type;
+  gboolean is_match = FALSE;
+  gchar **text_data;
+
+  if ( prop == NULL ) {
+    return FALSE;
+  }
+
+  /* TODO: We could probably speed this up by using the type_quark,
+   *       but I don't know enough yet to use it safely... */
+  prop_type = prop->type;
+  if ( prop_type == NULL ) {
+    return FALSE;
+  }
+
+  /* Special case: array of sub-properties.  Do not continue with
+   * checking text for this property.  Instead, just
+   * recurse into _match_prop() for each sub-property in
+   * the array. */
+  if ( strcmp ( prop_type, PROP_TYPE_SARRAY ) == 0
+           || strcmp ( prop_type, PROP_TYPE_DARRAY ) == 0 )
+  {
+    GPtrArray *records = ((ArrayProperty *) prop)->records;
+    guint rnum;
+
+    if ( records == NULL ) {
+      return FALSE;
+    }
+
+    for ( rnum = 0; rnum < records->len; rnum ++ ) {
+      GPtrArray *sub_props = g_ptr_array_index ( records, rnum );
+      guint sub_num;
+
+      for ( sub_num = 0; sub_num < sub_props->len; sub_num ++ ) {
+       const gchar *sub_prop_name;
+       Property *sub_prop = g_ptr_array_index ( sub_props, sub_num );
+
+        is_match |= _match_prop ( obj, sd, replacement, sub_prop );
+      }
+    }
+
+    /* Done. */
+    return is_match;
+  }
+
+
+  /* Check for string / text property. */
+  if ( strcmp ( prop_type, PROP_TYPE_MULTISTRING ) == 0
+       || strcmp ( prop_type, PROP_TYPE_STRING ) == 0 )
+  {
+    text_data = &((StringProperty *) prop)->string_data;
+  }
+  else if ( strcmp ( prop_type, PROP_TYPE_TEXT ) == 0 )
+  {
+    text_data = &((TextProperty *) prop)->text_data;
+  }
+  /* TODO future:
+  else if ( strcmp ( prop_type, PROP_TYPE_STRINGLIST ) == 0 )
+  {
+  }
+  */
+  else
+  {
+    /* Not a type we're interested in (int, real, geometry, etc). */
+    text_data = NULL;
+  }
+
+
+  if ( text_data == NULL ) {
+    return FALSE;
+  }
+
+  return _match_text_prop ( obj, sd, replacement, prop, text_data );
+}
+
+
+/*! Match and possibly modify all the given object's properties. */
+GPtrArray *
+_match_all_props (DiaObject *obj, const SearchData *sd, const gchar *replacement)
+{
+  GPtrArray *all_plist = NULL;
+  GPtrArray *matched_plist = NULL;
+  PropDescription *prop_descriptions;
+  guint pnum;
+
+  if ( obj == NULL ) {
+    return NULL;
+  }
+
+  prop_descriptions = object_get_prop_descriptions ( obj );
+
+  if ( prop_descriptions == NULL ) {
+    return NULL;
+  }
+
+  all_plist = prop_list_from_descs ( prop_descriptions, pdtpp_true );
+  if ( all_plist == NULL ) {
+    return NULL;
+  }
+
+  /* Step though all object properties.
+   * Along the way, construct a list of matching properties (or
+   * replaced properties). */
+  for ( pnum = 0; pnum < all_plist->len; pnum ++ ) {
+    Property *prop = g_ptr_array_index ( all_plist, pnum );
+    gboolean is_match = FALSE;
+    const gchar *prop_name;
+
+    if ( prop == NULL || prop->name == NULL ) {
+      continue;
+    }
+
+    /* This extra step seems to be necessary to populate the property data. */
+    prop_name = prop->name;
+    prop->ops->free ( prop );
+    prop = object_prop_by_name ( obj, prop_name );
+
+    is_match = _match_prop ( obj, sd, replacement, prop );
+
+    if ( is_match == FALSE ) {
+      prop->ops->free ( prop );
+      continue;
+    }
+
+    /* We have a match. */
+    if ( matched_plist == NULL ) {
+      /* First time. */
+      matched_plist = prop_list_from_single (prop);
+    }
+    else {
+      /* Subsequent finds. */
+      GPtrArray *append_plist;
+      append_plist = prop_list_from_single ( prop );
+      prop_list_add_list ( matched_plist, append_plist );
+      prop_list_free ( append_plist );
+    }
+
+  } /* Continue stepping through all object properties. */
+
+  return matched_plist;
+}
+
+
+/*! Match and possibly modify one or more properties in an object.
+ *  Returns a list of modified Properties. */
+GPtrArray *
+_match_props ( DiaObject *obj, const SearchData *sd, const gchar *replacement )
+{
+  if ( obj == NULL || sd == NULL ) {
+    return FALSE;
+  }
+
+  if ( sd->flags & MATCH_ALL_PROPERTIES ) {
+    return _match_all_props ( obj, sd, replacement );
+  }
+  else {
+    return _match_name_prop ( obj, sd, replacement );
+  }
+}
+
+
+/* Only match (find), do not replace any values. */
 static gboolean
 _matches (DiaObject *obj, const SearchData *sd)
 {
-  Property *prop = NULL;
+  GPtrArray *plist = NULL;
 
-  if (!obj)
+  if (!obj) {
     return FALSE;
+  }
 
-  prop = _match_string_prop (obj, sd, NULL);
-  if (prop)
-    prop->ops->free(prop);
+  plist = _match_props (obj, sd, NULL);
+  if (plist == NULL) {
+    return FALSE;
+  }
 
-  return (prop != NULL);    
+  prop_list_free ( plist );
+
+  return TRUE;
 }
 
 static void
@@ -153,18 +362,20 @@
   }
 }
 
+/* Match and replace property values. */
 static gboolean
 _replace (DiaObject *obj, const SearchData *sd, const char *replacement)
 {
   ObjectChange *obj_change;
-  Property *prop;
-  GPtrArray *plist;
+  GPtrArray *plist = NULL;
 
-  prop = _match_string_prop (obj, sd, replacement);
-  if (!prop)
+  plist = _match_props (obj, sd, replacement );
+
+  if ( plist == NULL ) {
     return FALSE;
-    
-  plist = prop_list_from_single (prop);
+  }
+
+  /* Refresh screen and free the list of modified properties. */
   obj_change = object_apply_props (obj, plist);
   prop_list_free (plist);
 
@@ -194,6 +405,8 @@
                   g_object_get_data (G_OBJECT (widget), "match-case"))) ? MATCH_CASE : 0;
   sd.flags |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON ( 
                   g_object_get_data (G_OBJECT (widget), "match-word"))) ? MATCH_WORD : 0;
+  sd.flags |= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON ( 
+                 g_object_get_data (G_OBJECT (widget), "match-all-properties"))) ? MATCH_ALL_PROPERTIES : 0;
   
 
   switch (response_id) {
@@ -266,6 +479,7 @@
   GtkWidget *search_entry;
   GtkWidget *match_case;
   GtkWidget *match_word;
+  GtkWidget *match_all_properties;
 
   gtk_dialog_set_default_response (GTK_DIALOG (dialog), RESPONSE_FIND);
 
@@ -311,6 +525,10 @@
   gtk_box_pack_start (GTK_BOX (vbox), match_word, FALSE, FALSE, 6);
   g_object_set_data (G_OBJECT (dialog), "match-word", match_word);
 
+  match_all_properties = gtk_check_button_new_with_mnemonic (_("Match _all properties (not just object 
name)"));
+  gtk_box_pack_start (GTK_BOX (vbox), match_all_properties, FALSE, FALSE, 6);
+  g_object_set_data (G_OBJECT (dialog), "match-all-properties", match_all_properties);
+
   gtk_widget_show_all (vbox);
 }



      __________________________________________________________________
Be smarter than spam. See how smart SpamGuard is at giving junk email the boot with the All-new Yahoo! Mail.  
Click on Options in Mail and switch to New Mail today or register for free at http://mail.yahoo.ca



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]