A few weeks ago we talked about what the clipboard object is and how you can get information off of the clipboard using Legato. This week we’re going to talk about putting information onto the clipboard using Legato by modifying one of our previous scripts.
Friday, April 27. 2018
LDC #82: A Second Snap of the Clipboard
Currently Legato has the capacity to add several different formats to the clipboard: Unicode, RTF, Text, HTML, and CSV. These are all referenced by using their respective functions, ClipboardSet[Function]. The most important thing to remember when working with the clipboard is to always make sure that the clipboard handle gets closed before your script ends preferable as soon as possible. Legato automatically closes handles when a script ends but remember that hooks do not end until the program is closed. This means you could potentially lock other programs out of using the clipboard, which will start causing problems for your users.
If you’re a regular reader of the blog you may remember a few months ago I wrote a script to remove all of the links from a selected area in page view. In order to explore some clipboard options, I want to modify this script to take the targets of the hyperlinks being removed and add them to the clipboard after all the links are removed from the selected area. If you need a refresher, you can find the blog post and explanation here.
Here is the script in it’s entirety:
// // // GoFiler Legato Script - Remove Hyperlinks and put on clipboard // -------------------------------------------------------------- // // Rev 04/25/2018 // // (c) 2018 Novaworks, LLC -- All rights reserved. // // Place this and its companion file (.rc) into the Scripts folder of the application. GoFiler must be // restarted to have the menu functions added. // // Notes: // // - Requires GoFiler 4.21c or later. int run_delete (int f_id, string mode); void do_delete (int sx, int sy, int ex, int ey); handle sgml; /* sgml object */ handle edit_object; /* edit object */ int num_deletes; /* number of deletes */ string dc; /* deletion content */ /****************************************/ int setup() { /* Called from Application Startup */ /****************************************/ MenuSetHook("DOCUMENT_HYPERLINK_REMOVE", /* Set the Test Hook */ GetScriptFilename(), "run_delete"); /* * con't */ return ERROR_NONE; /* Return value (does not matter) */ } /* end setup */ /****************************************/ void main(){ /* main */ /****************************************/ setup(); /* run setup */ } /* */ /****************************************/ int run_delete(int f_id, string mode){ /* promote underlines to table cells */ /****************************************/ int ex,ey,sx,sy; /* positional variables */ int s_mode; /* selection mode */ int num_select; /* number of selected arrays */ int count; /* iterator */ boolean searching; /* currently searching */ dword token; /* token of current element */ string element; /* sgml element */ handle hClip; /* clipboard handle */ handle edit_window; /* edit window */ /* */ if (mode!="preprocess"){ /* if not running preprocess */ return ERROR_NONE; /* quit */ } /* */ edit_window = GetActiveEditWindow(); /* get handle to edit window */ if(IsError(edit_window)){ /* get active edit window */ MessageBox('x',"This is not an edit window."); /* display error */ CloseHandle(edit_object); /* close handle */ CloseHandle(edit_window); /* close handle */ CloseHandle(sgml); /* close handle */ return ERROR_NONE; /* return */ } /* */ edit_object = GetEditObject(edit_window); /* create edit object */ s_mode = GetSelectMode(edit_object); /* get selection mode of object */ sgml = SGMLCreate(edit_object); /* create sgml object */ dc = ""; /* deletion content blank */ num_deletes = 0; /* number of deletions */ switch (s_mode) { /* check selection mode */ case EDO_LINEAR_SELECT: /* if we have a selection */ sx = GetSelectStartXPosition(edit_window); /* get the selection start */ sy = GetSelectStartYPosition(edit_window); /* get the selection start */ ex = GetSelectEndXPosition(edit_window); /* get the selection end */ ey = GetSelectEndYPosition(edit_window); /* get the selection end */ if (sx == -1 || sy == -1){ /* if sx or sy is an error */ MessageBox('x',"Cannot get selection position."); /* display error */ CloseHandle(edit_object); /* close handle */ CloseHandle(edit_window); /* close handle */ CloseHandle(sgml); /* close handle */ return ERROR_NONE; /* continue onward */ } /* */ SGMLSetPosition(sgml, ex, ey); /* go to the end */ searching = true; /* searching */ while (searching) { /* while still searching */ element = SGMLNextElement(sgml); /* go forwards */ token = SGMLGetElementToken(sgml); /* get token of current element */ if (HTMLIsBlockElement(sgml) || token == HT_A) { /* if end of block or start of link */ element = SGMLPreviousElement(sgml); /* go backwards */ searching = false; /* reached the end */ ex = SGMLGetItemPosEX(sgml); /* get ex */ ey = SGMLGetItemPosEY(sgml); /* get ey */ } /* done */ if (token == HT__A) { /* if end of link */ searching = false; /* done searching */ ex = SGMLGetItemPosEX(sgml); /* get ex */ ey = SGMLGetItemPosEY(sgml); /* get ey */ } /* */ if (element == "") { /* if EOD */ searching = false; /* done searching and don't set new pos */ } /* */ } /* */ SGMLSetPosition(sgml, sx, sy); /* Go to the beginning */ element = SGMLNextItem(sgml); /* Prime the pump */ searching = true; /* searching */ while (searching) { /* while still searching */ element = SGMLPreviousElement(sgml); /* go backwards */ token = SGMLGetElementToken(sgml); /* get token of current element */ if (HTMLIsBlockElement(sgml) || token == HT__A) { /* if end of block or end of link */ element = SGMLNextElement(sgml); /* go forwards */ searching = false; /* done searching */ sx = SGMLGetItemPosSX(sgml); /* get sx */ sy = SGMLGetItemPosSY(sgml); /* get sy */ } /* */ if (token == HT_A) { /* if start of link */ searching = false; /* done searching */ sx = SGMLGetItemPosSX(sgml); /* get sx */ sy = SGMLGetItemPosSY(sgml); /* get sy */ } /* */ if (element == "") { /* if end of file */ searching = false; /* done searching */ } /* */ } /* */ do_delete(sx, sy, ex, ey); /* do delete */ break; case EDO_ARRAY_SELECT: /* if array select */ num_select = GetSelectCount(edit_window); /* get num cells selected */ count = 0; /* set iterator */ while (count < num_select) { /* go through select zones */ sx = GetSelectStartXPosition(edit_window, count); /* get the selection start */ sy = GetSelectStartYPosition(edit_window, count); /* get the selection start */ ex = GetSelectEndXPosition(edit_window, count); /* get the selection end */ ey = GetSelectEndYPosition(edit_window, count); /* get the selection end */ if (sx == -1 || sy == -1){ /* if sx or sy is an error */ MessageBox('x',"Cannot get selection position."); /* display error */ CloseHandle(edit_object); /* close handle */ CloseHandle(edit_window); /* close handle */ CloseHandle(sgml); /* close handle */ return ERROR_NONE; /* continue onward */ } /* */ do_delete(sx, sy, ex, ey); /* do the delete */ count++; /* increment */ } /* finish going through */ break; default: /* otherwise */ CloseHandle(edit_object); /* close handle */ CloseHandle(edit_window); /* close handle */ CloseHandle(sgml); /* close handle */ return ERROR_NONE; /* continue onward */ } /* */ if (num_deletes > 0) { /* if deletion happened */ hClip = ClipboardCreate(); /* clear clipboard */ ClipboardSetText(hClip, dc); /* set text */ ClipboardSetCSV(hClip, dc); /* set CSV */ CloseHandle(hClip); /* close handle */ } /* end if deletion */ CloseHandle(edit_object); /* close handle */ CloseHandle(edit_window); /* close handle */ CloseHandle(sgml); /* close handle */ return ERROR_EXIT; /* upon finishing stop menu from firing */ } /* */ /****************************************/ void do_delete(int sx, int sy, int ex, int ey) { /* * Run the delete */ /****************************************/ string element; /* element */ dword token; /* token */ SGMLSetDataRange(sgml, sx, sy, ex, ey); /* set the boundaries */ element = SGMLNextElement(sgml); /* get the first sgml element */ while(element != ""){ /* while element isn't empty */ token = SGMLGetElementToken(sgml); /* get token of current element */ if (token == HT_A || token == HT__A){ /* if we've got an A tag */ sx = SGMLGetItemPosSX(sgml); /* get sx */ sy = SGMLGetItemPosSY(sgml); /* get sy */ ex = SGMLGetItemPosEX(sgml); /* get ex */ ey = SGMLGetItemPosEY(sgml); /* get ey */ if (token == HT_A) { /* if A tag */ if (num_deletes > 0) { /* if more than one */ dc+= ", " + SGMLGetParameter(sgml, HA_HREF); /* add to current string */ } /* end if more than one */ else { /* if first */ dc = SGMLGetParameter(sgml, HA_HREF); /* make string */ } /* end if first */ num_deletes++; /* new deletion */ } /* end if A tag */ WriteSegment(edit_object,"",sx,sy,ex,ey); /* remove A tag */ SGMLSetPosition(sgml,sx,sy); /* reset parser to where tag used to be */ } /* */ element = SGMLNextElement(sgml); /* get the next sgml element */ } /* */ } /* */
I’m not going to break down the entire script, as that has been done before. Instead, I’m going to highlight the differences from the old script to this new script. We’ll start in the beginning:
int run_delete (int f_id, string mode); void do_delete (int sx, int sy, int ex, int ey); handle sgml; /* sgml object */ handle edit_object; /* edit object */ int num_deletes; /* number of deletes */ string dc; /* deletion content */
There are a couple of extra defines here. We need to keep track of the number of deletions that occur as the script runs because if the number ends up being zero, we don’t want to clear the clipboard. We also need a global string to keep track of the targets that are being deleted. I made a separate function to perform the deletion of the hyperlink and moved the SGML Object and Edit Object to global variables since they are needed by the new deletion function.
/****************************************/ int run_delete(int f_id, string mode){ /* promote underlines to table cells */ /****************************************/ int ex,ey,sx,sy; /* positional variables */ int s_mode; /* selection mode */ int num_select; /* number of selected arrays */ int count; /* iterator */ boolean searching; /* currently searching */ dword token; /* token of current element */ string element; /* sgml element */ handle hClip; /* clipboard handle */ handle edit_window; /* edit window */ ... dc = ""; /* deletion content blank */ num_deletes = 0; /* number of deletions */
In addition to the new global objects, there’s a handle declared in the main section of the script which references the clipboard as we’ll need this later on. Finally, we initialize the global variables before we start our search in order to make sure that the values are what we would like them to be.
/****************************************/ void do_delete(int sx, int sy, int ex, int ey) { /* * Run the delete */ /****************************************/ string element; /* element */ dword token; /* token */ SGMLSetDataRange(sgml, sx, sy, ex, ey); /* set the boundaries */ element = SGMLNextElement(sgml); /* get the first sgml element */ while(element != ""){ /* while element isn't empty */ token = SGMLGetElementToken(sgml); /* get token of current element */ if (token == HT_A || token == HT__A){ /* if we've got an A tag */ sx = SGMLGetItemPosSX(sgml); /* get sx */ sy = SGMLGetItemPosSY(sgml); /* get sy */ ex = SGMLGetItemPosEX(sgml); /* get ex */ ey = SGMLGetItemPosEY(sgml); /* get ey */ if (token == HT_A) { /* if A tag */ if (num_deletes > 0) { /* if more than one */ dc+= ", " + SGMLGetParameter(sgml, HA_HREF); /* add to current string */ } /* end if more than one */ else { /* if first */ dc = SGMLGetParameter(sgml, HA_HREF); /* make string */ } /* end if first */ num_deletes++; /* new deletion */ } /* end if A tag */ WriteSegment(edit_object,"",sx,sy,ex,ey); /* remove A tag */ SGMLSetPosition(sgml,sx,sy); /* reset parser to where tag used to be */ } /* */ element = SGMLNextElement(sgml); /* get the next sgml element */ } /* */ } /* */
Our delete function is similar, but now, before the deletion of the <A> or </A> tags, we check to see if the token is an open tag. If it is, we get the HREF of the tag and put it into our deletion content string. If the <A> tag is not the first we’ve come across we need to add a comma to the string then add our value behind it. If it is the first, we just add the value. This allows us to save the string so we can post it to the clipboard as a CSV later on.
if (num_deletes > 0) { /* if deletion happened */ hClip = ClipboardCreate(); /* clear clipboard */ ClipboardSetText(hClip, dc); /* set text */ ClipboardSetCSV(hClip, dc); /* set CSV */ CloseHandle(hClip); /* close handle */ } /* end if deletion */
Finally, back in the run_delete function, after all of the deletions have been performed, we check to see if we’ve deleted any <A> tags. If we have, we create the clipboard using the ClipboardCreate function. This function also clears the clipboard. We set the clipboard contents to have text and CSV all the time, which is fine for this example. For production, however, I would check to see if there is more than hyperlink before posting CSV to the clipboard object, as there is no need for it otherwise. Finally, we close the handle, which as I mentioned before is really the most important part. Since hClip is a local variable within this function, Legato will automatically close it when the function has completed execution. But if code after this point took a long time to complete, the clipboard would be locked, which is something we don’t want.
And there you have it. We’ve now taken our old script and added some new functionality to it. If you want some options for improvement, you could add in a dialog asking the user if they want the deleted contents on the clipboard. You could also move this into a completely separate toolset, rather than having it hook into the delete hyperlinks tool. Just remember to be responsible with your clipboard usage, as changing the clipboard without it being clear to the user can be one of the most frustrating things your user can encounter. As one of my favorite fictional characters once said, “With great power comes great responsibility.” Legato gives you this power, but it is up to you to use it in a way to improve your users’ experience.
Joshua Kwiatkowski is a developer at Novaworks, primarily working on Novaworks’ cloud-based solution, GoFiler Online. He is a graduate of the Rochester Institute of Technology with a Bachelor of Science degree in Game Design and Development. He has been with the company since 2013. |
Additional Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato