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 <[EMAIL PROTECTED]> 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 _______________________________________________ dia-list mailing list [email protected] http://mail.gnome.org/mailman/listinfo/dia-list FAQ at http://live.gnome.org/Dia/Faq Main page at http://live.gnome.org/Dia
