Cinema 4D - my C++ plugins - find replace dialog

From NoskeWiki
Jump to navigation Jump to search
Find and replace plugin dialog

About

NOTE: This page is a daughter page of: Cinema 4D


This page contains code for a C++ plugin I've written for Cinema 4D. To understand how it compiles see Cinema 4D - my C++ plugins.

findialog.cpp - for doing a find and replace on the names of objects and/or materials

//------------------
//-- FILE:    finddialog.cpp
//-- AUTHORS: Andrew Noske
//--
//-- This Cinema 4D menu plugin provides "find and replace" and "find next"
//-- functionality to help renaming objects, tags and/or materials.
//-- 
//-- For more information see: http://www.andrewnoske.com/wiki/index.php?title=Cinema_4D

//########################################
//-- Includes and Globals Variables:

#include "c4d.h"

#define ID_FINDDIALOGTEST 1023838
                                    // My unique registered plugin ID "Find And Replace".

enum 
{
  DLG_EDIT_FIND          = 1000,
  DLG_EDIT_REPLACE       = 1001,
  DLG_CHK_OBJECTS,
  DLG_CHK_TAGS,
  DLG_CHK_MATERIALS,
  DLG_CHK_SELOBJECTONLY,
  DLG_BTN_FINDNEXT,
  DLG_BTN_REPLACEALL,
  DLG_LBL1,
  DLG_LBL2,
  DLG_LBL3,
  DLG_LBL4,
  DLG_GRP1,
  DLG_GRP2,
  DLG_GRP3
};

//########################################
//-- Functions:

//------------------
//-- Returns the value of the matching element ID within "dialog" as a String.
//-- NOTE: Designed for EditText() or StaticText().

inline String GetStringFromElement( GeDialog *dialog, int id )
{
  String str;
  dialog->GetString(id,str);
  return str;
}

//------------------
//-- Returns the value of the matching element ID within "dialog" as a Bool.
//-- NOTE: Designed for Checkbox().

inline bool GetBoolFromElement( GeDialog *dialog, int id )
{
  Bool boolVal;
  dialog->GetBool(id,boolVal);
  return boolVal;
}

//------------------
//-- Inputs a string "str" and replaces the first occurance of "find" with "replace".
//-- Returns 1 if a replacement was made, or 0 if "find" was not found.

int StringReplaceFirstOccurance( String *str, String *find, String *replace )
{
  LONG pos;
  Bool stringFound = str->FindFirst( *find, &pos, 0);
  if( stringFound )
  {
    str->Delete( pos, find->GetLength() );
    str->Insert( pos, *replace );
    return 1;
  }
  return 0;
}

//------------------
//-- Recursive function which inputs an object and traverses down the 
//-- heirarchy (depth first search) replacing any fist occurances of the string
//-- "find" with "replace" for all object names (if "renameObjs" is true),
//-- and the name of all tags (if "renameTags" is true) of these objects.
//-- The number of objects and tags replaced is tallied into
//-- "numObjsReplaced" and "numTagsReplaced" respectively.

void RenameObjects(BaseObject *obj, String *find, String *replace,
                  int *numObjsReplaced, int *numTagsReplaced,
                  Bool renameObjs, Bool renameTags, int depth = 0)
{
  if(depth==0)
  {
    *numObjsReplaced = 0;
    *numTagsReplaced = 0;
  }
  
  while( obj )
  {
    BaseObject *child = obj->GetDown();
    if( child )
      RenameObjects(child, find, replace, numObjsReplaced, numTagsReplaced,
                    renameObjs, renameTags, depth+1);    // recursive call
    
    if( renameObjs )
    {
      String objName = obj->GetName();
      if( StringReplaceFirstOccurance(&objName, find, replace) )
      {
        *numObjsReplaced = *numObjsReplaced + 1;
        obj->SetName(objName);
        obj->Message(MSG_UPDATE);
      }
    }
    
    if( renameTags )
    {
      for( BaseTag *tag = obj->GetFirstTag(); (tag); tag = tag->GetNext() )
      {
        String tagName = tag->GetName();
        if( StringReplaceFirstOccurance(&tagName, find, replace) )
        {
          *numTagsReplaced = *numTagsReplaced + 1;
          tag->SetName(tagName);
          tag->Message(MSG_UPDATE);
        }
      }
    }
    obj = obj->GetNext();
  }
}

//------------------
//-- Recursive function which inputs an object and traverses down the 
//-- heirarchy (depth first search) and returns the first object
//-- which name contains the string "find" (if "searchObjs" is true)
//-- and/or has a tag containing "find" (if "searchTags" is true).
//-- Note that if "startObj" is not NULL it will only select the first object
//-- after "startObj" has been traversed.

BaseObject *matchingObj = NULL;
bool startObjFound      = false;

BaseObject *FindNextObjectMatch( BaseObject *obj, String *find,
                                 Bool searchObjs, Bool searchTags,
                                 BaseObject *startObj, int depth = 0 )
{
  if( depth == 0 )
  {
    matchingObj  = NULL;
    startObjFound = !startObj;
  }
  
  while( obj && !matchingObj )
  {
    LONG pos;
    if( startObjFound )
    {
      if( searchObjs )
      {
        if( obj->GetName().FindFirst(*find,&pos,0) )
        {
          matchingObj = obj;
          return (obj);
        }
      }
      if( searchTags )
      {
        int i=1;
        for( BaseTag *tag = obj->GetFirstTag(); (tag); tag = tag->GetNext(), i++ )
        {
          if( tag->GetName().FindFirst(*find,&pos,0) )
          {
            GePrint("Matching tag found: " + RealToString(i) );
            matchingObj = obj;
            return (obj);
          }
        }
      }
    }
    
    if(obj == startObj)
      startObjFound = true;
    
    BaseObject *child = obj->GetDown();
    if( child )
      FindNextObjectMatch(child, find, searchObjs, searchTags, startObj, depth+1);    // Recursive call.
    
    obj = obj->GetNext();
  }
  
  return (matchingObj);
}
 
//########################################
//-- FindDialog class: (the main dialog for this plugin)

class FindDialog : public GeDialog
{
 public:
  FindDialog(void)            {}
  virtual ~FindDialog(void)   {}
  
  virtual Bool CreateLayout(void)
  {
    this->SetTitle( "Find and Replace" );
    
    LONG SC_VH = BFH_SCALEFIT | BFV_SCALEFIT;
    
    GroupBorderSpace( 10, 10, 10, 10 );
    
    GroupBegin(DLG_GRP1,SC_VH,2,0,"",0);
      
      AddStaticText   (DLG_LBL1, BFH_LEFT,0,0,"find:", BORDER_NONE);
      AddEditText     (DLG_EDIT_FIND, SC_VH);
      
      AddStaticText   (DLG_LBL2, BFH_LEFT,0,0,"replace:", BORDER_NONE);
      AddEditText     (DLG_EDIT_REPLACE,SC_VH);
      
    GroupEnd();
    
    GroupBegin( DLG_GRP2,SC_VH,1,0,"search:",BFV_GRIDGROUP_EQUALROWS );
      
      GroupBorder(BORDER_WITH_TITLE|BORDER_THIN_IN);
      GroupBorderSpace( 20, 10, 10, 10 );
      AddCheckbox     (DLG_CHK_OBJECTS,   SC_VH,0,0,"objects");
      AddCheckbox     (DLG_CHK_TAGS,      SC_VH,0,0,"object tags");
      AddCheckbox     (DLG_CHK_MATERIALS, SC_VH,0,0,"materials");
      AddSeparatorH   (SC_VH);
      AddCheckbox     (DLG_CHK_SELOBJECTONLY,  SC_VH,0,0,"selected object only");
      
    GroupEnd();
    
    GroupBegin(DLG_GRP3,SC_VH,2,0,"",BFV_GRIDGROUP_EQUALCOLS);
      
      GroupBorderSpace( 10, 10, 10, 10 );
      AddButton       (DLG_BTN_FINDNEXT,BFH_SCALEFIT,0,0,"Find Next");
      AddButton       (DLG_BTN_REPLACEALL,BFH_SCALEFIT,0,0,"Replace All");
    GroupEnd();
    
    return TRUE;
  }
  
  
  virtual Bool InitValues(void)
  {
    this->SetString      (DLG_EDIT_FIND, "Sphere");
    this->SetString      (DLG_EDIT_REPLACE, "sph");
    this->SetBool        (DLG_CHK_OBJECTS, true);
    this->SetBool        (DLG_CHK_TAGS, false);
    this->SetBool        (DLG_CHK_MATERIALS, false);
    this->SetBool        (DLG_CHK_SELOBJECTONLY, false);
    return TRUE;
  }
  
  
  virtual Bool Command(LONG id,const BaseContainer &msg)
  {
    switch (id)
    {
      case (DLG_BTN_REPLACEALL):
      {
        BaseDocument *doc = GetActiveDocument();
        
        //## GET USER ENTERED DATA:
        
        String find       = GetStringFromElement(this,DLG_EDIT_FIND);
        String replace    = GetStringFromElement(this,DLG_EDIT_REPLACE);
        
        Bool renameObjs   = GetBoolFromElement(this,DLG_CHK_OBJECTS);
        Bool renameTags   = GetBoolFromElement(this,DLG_CHK_TAGS);
        Bool renameMats   = GetBoolFromElement(this,DLG_CHK_MATERIALS);
        Bool currObjOnly  = GetBoolFromElement(this,DLG_CHK_SELOBJECTONLY);
        
        doc->StartUndo();
        
        //## TRAVERSE OBJECTS AND RENAME ANY OCCURANCES OF THE FIND STRING
        //## IN THE NAME OF OBJECTS AND/OR TAGS:
        
        int numObjsReplaced = 0;
        int numTagsReplaced = 0;
        int numMatsReplaced = 0;
        
        if( renameObjs || renameTags )
        {
          BaseObject *startObj = doc->GetFirstObject();
          if( currObjOnly )
          {
            startObj = doc->GetActiveObject();
            if( startObj )
            {
              MessageDialog( "You have not selected a valid object!" );
              return FALSE;
            }
          }
          
          RenameObjects( startObj, &find, &replace,
                         &numObjsReplaced, &numTagsReplaced,
                         renameObjs, renameTags );
        }
        
        //## TRAVERSE ALL MATERIALS AND RENAME ANY OCCURANCES OF THE FIND STRING:
       
        if( renameMats )
        {
          for( BaseMaterial *mat = doc->GetFirstMaterial(); (mat); mat = mat->GetNext() )
          {
            String matName = mat->GetName();
            if( StringReplaceFirstOccurance(&matName, &find, &replace) )
            {
              numMatsReplaced++;
              mat->SetName(matName);
              mat->Message(MSG_UPDATE);
            }
          }
        }
        
        MessageDialog( "Renamed:  \n   > "
                       + RealToString(numObjsReplaced) + " objects \n   > "
                       + RealToString(numTagsReplaced) + " tags \n   > "
                       + RealToString(numMatsReplaced) + " materials");
        
        doc->EndUndo();
        DrawViews( DRAWFLAGS_NO_THREAD );
      }
      break;
        
      case (DLG_BTN_FINDNEXT):
      {          
        //## FIND AND SELECT THE FIRST OBJECT MATCHING THE "FIND" CRITERIA
        //## AFTER THE CURRENTLY SELECTED OBJECT:
        
        BaseDocument *doc   = GetActiveDocument();
        String find         = GetStringFromElement(this,DLG_EDIT_FIND);
        Bool searchObjs     = GetBoolFromElement(this,DLG_CHK_OBJECTS);
        Bool searchTags     = GetBoolFromElement(this,DLG_CHK_TAGS);
        
        BaseObject *rootObj = doc->GetFirstObject();
        BaseObject *currObj = doc->GetActiveObject();
        
        BaseObject *match   = FindNextObjectMatch( rootObj, &find,
                                                     searchObjs, searchTags, currObj );
        
        if( !match )
          MessageDialog("No more occurances of '" + find +  "' were found");
        
        doc->SetActiveObject( match );
        
        DrawViews( DRAWFLAGS_NO_THREAD );
      }
       break;
    }
    return TRUE;
  }
};

//########################################

class FindDialogTest : public CommandData
{
 private:
  FindDialog dlg;

 public:
  virtual Bool Execute(BaseDocument *doc)
  {
    return dlg.Open(DLG_TYPE_ASYNC, ID_FINDDIALOGTEST,-1,-1);
  }
};

//########################################

Bool RegisterFindDialog(void)
{
  return RegisterCommandPlugin(ID_FINDDIALOGTEST, "Find And Replace",
                               0,NULL,String("Find and Replace"),gNew FindDialogTest );
}


See Also


Code license
For all of the code on my site... if there are specific instruction or licence comments please leave them in. If you copy my code with minimum modifications to another webpage, or into any code other people will see I would love an acknowledgment to my site.... otherwise, the license for this code is more-or-less WTFPL (do what you want)! If only copying <20 lines, then don't bother. That said - if you'd like to add a web-link to my site www.andrewnoske.com or (better yet) the specific page with code, that's a really sweet gestures! Links to the page may be useful to yourself or your users and helps increase traffic to my site. Hope my code is useful! :)