Last week, we discussed how to add page breaks into the page break template with a Legato script. As useful as that is, without a way to delete them or rename them, the function isn’t really complete. To do that, we’re making a page break manager this week. This will give us a new menu option, “Modify Page Break Templates”, which lets us manage our stored page breaks.
Friday, August 31. 2018
LDC #100: Page Break Template Manager, Part 2
We also have a couple of minor modifications to the functions from last week. The save_validate function, for example, now tests that the user supplied name is unique, so we can’t have duplicate names. The run_save function also now reads the list of available page breaks into an array, so the validation can check against the list. While running through the additions, you might notice that some of them are familiar. I took a lot of the code from the Preferences Switcher script that we discussed back in April. Using previous scripts for a basis is a great way to quickly and easily adapt to new requirements without having to reinvent the wheel. Let’s start out this week by taking a look at some of our new defines.
#define TEMPLATE_FILENAME "HeaderFooterList.htm" #define P_TEMPLATE "<P STYLE=\"margin: 0\">{%s}</P>" #define SYNTAX_TAG "HTML_TAG_NOT_TITLE" #define P_START "<P STYLE=\"margin: 0\">{" #define P_END "}</P>" #define END_TABLE "</TABLE>" #define END_P_NAME "end" #define TEMPLATE_HTTP_LOC "http://www.novaworkssoftware.com/files/HeaderFooterList.htm" #define TEMPLATE_BODY_LINE "<BODY STYLE=\"font: 11pt Times New Roman, Times, Serif\">" #define TEST_X 0 #define TEST_Y 10 #define TEST_SAVE false #define TEST_MODIFY true
Some of these should be familiar from last week, but we’ve added a few new ones. The SYNTAX_TAG define is used in the data structure to mark a bit of code in the template file that isn’t referencing a page break, but is part of the structure of the file. Both P_START and P_END are “wrappers” around the title of a page break, something we can use to strip the name out with a find/replace operation. The END_TABLE define is just the close table tag, and the END_P_NAME is the name of the page break paragraph that triggers the end of the template file. We’ve also added TEST_SAVE and TEST_MODIFY this week, which are used for debugging. If TEST_SAVE is true, we’re testing page break save in the main function. If TEST_MODIFY is true, we’re testing modify. Very handy for running the script in the IDE instead of using it as a hooked script.
The setup function has changed very little, we simply had to add an extra block of code to add our new menu item. The main function is also relatively unchanged, we just added if statements to check for our test defines, to see which test we’re running. Let's take a look at one of our new functions first, check_template_folder. This is something that we had in the save_ok function last time, but broke out into a separate function so run_modify can use it too.
/****************************************/ int check_template_folder(){ /* ensure the templates folder exists */ /****************************************/ int rc; /* return code */ string template_folder; /* the template folder */ string template_file; /* the template file */ /* */ template_folder = GetApplicationDataFolder(); /* Get the appdata directory */ template_folder = AddPaths(template_folder,"Templates"); /* build path to templates folder */ if (IsFolder(template_folder)==false){ /* if template folder doesn't exist */ rc = CreateFolder(template_folder); /* create the template folder */ if (IsError(rc)){ /* if we cannot create the folder */ MessageBox('x',"Cannot create template folder, error %0x",rc); /* display error */ return rc; /* return error code */ } } /* */ template_file = AddPaths(template_folder,TEMPLATE_FILENAME); /* set path to template file */ if (IsFile(template_file)==false){ /* if template file doesn't exist */ rc = HTTPGetFile(TEMPLATE_HTTP_LOC,template_file); /* get the template file */ if (IsError(rc)){ /* if we couldn't get the template */ MessageBox('x',"Cannot get template file, error %0x",rc); /* display error */ return rc; /* return with error */ } /* */ } /* */ return ERROR_NONE; /* return no error */ }
The actual code for this is unchanged, it’s basically just copy/pasted from our old function into this one, so it can be accessed by either run function. It checks to make sure the template folder exists, then checks to make sure the template file exists. If the template file doesn’t exist, we can download it from novaworkssoftware.com where a copy of the template is hosted.
/****************************************/ int save_ok(){ /* after validating the save dialog */ /****************************************/ int rc; /* return code */ int ix; /* iterator */ int size; /* size of the mapped text file */ string pb_name; /* page break name */ string line; /* content of line of mapped text file */ handle file; /* handle to the template file */ string template_file; /* the template file */ string template_folder; /* templates folder */ /* */ pb_name = EditGetText(TEMPLATE_NAME); /* get the page break template name */ rc = check_template_folder(); /* check the templates folder */ if (rc!=ERROR_NONE){ /* if we have an error */ return ERROR_EXIT; /* return error */ } /* */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ file = OpenMappedTextFile(template_file); /* open the mapped text file */ size = GetLineCount(file); /* get number of lines in mapped text */ while (line != TEMPLATE_BODY_LINE && ix<size){ /* while we haven't found the body */ line = ReadLine(file,ix); /* get the line */ ix++; /* increment line counter */ } /* */ if (line!=TEMPLATE_BODY_LINE){ /* if we couldn't find the body */ MessageBox('x',"Template file is not valid."); /* display an error message */ return ERROR_EXIT; /* return error */ } /* */ line = FormatString(P_TEMPLATE,pb_name); /* build line to insert */ line+= "\r\n\r\n"+table_code+"\r\n"; /* build line to insert */ InsertLine(file,ix,line); /* insert line */ MappedTextSave(file); /* save our modified file */ rc = GetLastError(); /* get the last error */ if (IsError(rc)){ /* if we can't save */ MessageBox('x',"Cannot save template file."); /* display an error */ return ERROR_EXIT; /* return an error */ } /* */ return ERROR_NONE; /* return no error */ } /* */
The save_ok function has also been slightly modified, so now it’s using the new check_template_folder function we have created above instead of duplicating code. Other than that, it’s not really been modified.
/****************************************/ int run_save(int f_id, string mode, handle window){ /* run the save function */ /****************************************/ int rc; /* return code */ dword window_type; /* get the edit window type */ handle edit_object; /* handle to the edit object */ int caret[]; /* caret positions */ string template_file; /* the template file path */ string tag; /* full text of element tag */ string element; /* the current element */ handle sgml; /* handle to the SGML parser */ /* */ if (mode != "preprocess"){ /* if not preprocess */ SetLastError(ERROR_NONE); /* set last error to nothing */ return ERROR_EXIT; /* bail out */ } /* */ if (IsWindowHandleValid(window)==false){ /* if we have no valid handle */ window = GetActiveEditWindow(); /* get the active edit window */ window_type = GetEditWindowType(window); /* get the edit window type */ window_type = window_type & EDX_TYPE_ID_MASK; /* mask file type */ if (window_type != EDX_TYPE_PSG_PAGE_VIEW){ /* if we're not in page view */ MessageBox('x',"This function must be used in page view."); /* display error */ return ERROR_EXIT; /* quit running */ } /* */ edit_object = GetEditObject(window); /* get edit object from window */ caret[0] = GetCaretXPosition(window); /* get the caret position in window */ caret[1] = GetCaretYPosition(window); /* get the caret position in window */ } /* */ else{ /* if we were passed a window */ edit_object = GetEditObject(window); /* get edit object from window */ caret[0] = TEST_X; /* set test position x */ caret[1] = TEST_Y; /* set test postion y */ } /* */ sgml = SGMLCreate(edit_object); /* get handle to SGML object */ SGMLSetPosition(sgml,caret[0],caret[1]); /* set position in SGML parser */ element = "init"; /* initialize element with a value */ while (element!="TABLE" && element!=""){ /* while element is not table and exists*/ tag = SGMLPreviousElement(sgml); /* get the previous SGML tag */ element = SGMLGetElementString(sgml); /* get the string value of the element */ } /* */ if (element == ""){ /* if the element is empty */ MessageBox('x',"This function must be run inside a table."); /* display message */ return ERROR_EXIT; /* quit running */ } /* */ table_code = SGMLFindClosingElement(sgml, /* get the code of the table */ SP_FCE_CODE_AS_IS | SP_FCE_INCLUDE_WRAPPER); /* get the code of the table */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ rc = read_page_breaks(template_file); /* read templates so we can validate */ if (rc!=ERROR_NONE){ /* if we cannot read the templates */ SetLastError(rc); /* set the error */ return ERROR_EXIT; /* return with error */ } /* */ rc = DialogBox(PAGEBREAK_TEMPLATES, "save_"); /* enter the save dialog */ SetLastError(rc); /* set last erorr */ return ERROR_EXIT; /* exit */ } /* */
We also had to slightly tweak the run_save function. I’ve highlighted the new line in this one, since it’s only a single line (but still very important) change. The new function read_page_breaks must be run here, in order to read the existing page breaks into memory before the dialog opens. This needs to be done, so that our validate function has a list of names to ensure we don’t allow the user to create a duplicate name in our list.
/****************************************/ int save_validate(){ /* validate the save name dialog */ /****************************************/ string pb_name; /* the name of the page break save */ string renaming_fn; /* full name of the pb we're renaming */ string fullname; /* full name of the page break */ int size; /* size of table */ int n_pos; /* position of name in array */ int ix; /* loop counter */ /* */ pb_name = EditGetText(TEMPLATE_NAME); /* get the template name */ if (pb_name == ""){ /* if page break name is blank */ MessageBox('x',"Template must have a name."); /* display error */ return ERROR_EXIT; /* return error */ } /* */ renaming_fn = FormatString(P_TEMPLATE,renaming); /* get full name of renaming template */ fullname = FormatString(P_TEMPLATE,pb_name); /* get full name of entered template */ n_pos = FindInTable(page_breaks,fullname,0,FIND_NO_CASE); /* check if name already exists */ if (n_pos >= 0 && fullname != renaming_fn){ /* if the name is found */ MessageBox('x',"Name already exists, cannot duplicate."); /* display error */ return ERROR_EXIT; /* return an error */ } /* */ return ERROR_NONE; /* return no error */ } /* */
The save_validate has been modified to check our new name against the existing page break names. This function is also going to be used as our validator for renaming, so it has a few lines that are not involved in saving, but in renaming page breaks. After we test if our new name is blank, we can format the name of the page break we’re renaming into a full page break name code, with the paragraph wrappers on it and everything. If we’re not renaming, but saving a new page break, this means our result of this format will just be an empty paragraph. We can also format our newly entered name into this same format, then try to locate the newly entered name in the stored array of all page breaks. If the name is found, and it’s not what we’re currently renaming (like if you went to rename something to itself, it should be allowed, it just doesn’t do anything) then a page break with that name exists, so we can display an error and return an error. Otherwise we continue without error.
Now we can look at some of our new functions. Let’s start with the read_page_breaks routine that, if you remember above we added a call to it in the run_save function. This function loads all our page breaks into memory.
/****************************************/ int read_page_breaks(string filename){ /* read the page breaks */ /****************************************/ .... variable declarations omitted .... file = OpenMappedTextFile(filename); /* open the template file */ rc = GetLastError(); /* get the last error */ if (IsError(rc)){ /* if we couldn't open the file */ MessageBox('x',"Cannot open template file."); /* display error message */ return ERROR_EXIT; /* return error */ } /* */ size = GetLineCount(file); /* get number of lines in file */ while (ix<size){ /* for each line in file */
The first thing we’re going to do in this function is open the template file. Whenever you’re doing file I/O operations, make sure to test for errors as well, because these operations can fail for a variety of reasons. Once we have the file open, we can get the number of lines in the file, and loop through each line.
line = ReadLine(file,ix); /* read a line from the file */ if (in_break){ /* if we're in a page break */ if (line!=""){ /* if the line has text */ content+=line+"\r\n"; /* add the line to the content */ } /* */ if (FindInString(line,END_TABLE)>=0){ /* if line has a close table */ in_break = false; /* we're not in a page break anymore */ page_breaks[px][1] = content+"\r\n"; /* store the page break in array */ content = ""; /* reset content variable */ px++; /* increment page counter */ } /* */ } /* */
For each line of our file, we want to first read the line, then check if we’re inside a page break. If we’re in a break, then we can check the line’s contents. If the line isn’t blank, we can add it to our “content” variable, followed by a newline character, so we can store it in the page_breaks array later as a string. If the line is a table end, we can assume that it’s the end of our current page break. Remember, each page break can only be a single table. If that is the case we can set in_break to false, store the content of our break in the page_breaks array, reset our content variable, and increment our page breaks counter by 1.
else{ /* if we're not in a page break */ if (FindInString(line,P_START)==0){ /* if line starts with paragraph */ if (FindInString(line,FormatString("{%s}",END_P_NAME))>0){ /* if the paragraph is an end para */ page_breaks[px][0] = SYNTAX_TAG; /* set title to keyword */ page_breaks[px][1] = line+"\r\n"; /* store content of line */ px++; /* increment page break counter */ } /* */ else{ /* if it's not the end para */ in_break = true; /* we're now in a page break */ page_breaks[px][0] = line+"\r\n"; /* get the title */ } /* */ } /* */ else{ /* if we're not starting a page break */ if (line!=""){ /* if the line isn't blank */ page_breaks[px][0] = SYNTAX_TAG; /* set title to keyword */ page_breaks[px][1] = line+"\r\n"; /* store content of line */ px++; /* increment page break counter */ } /* */ } /* */ } /* */ ix++; /* increment counter */ } /* */ CloseHandle(file); /* close the open file */ return ERROR_NONE; /* return without error */ } /* */
If we’re not in a page break, then we need to test if the line is a paragraph start. If so, we can check if it’s the end paragraph that signals the end of the file. If so, we need to store it in it’s own row in the table, with the SYNTAX_TAG constant as the value in the first column, so we know later that this isn’t really a page break, but just general syntax of the template file. If it’s not the end paragraph, then we can assume it’s the beginning of a page break, so we can set our in_break flag to true, and store the paragraph line as the first column value for this row so we know what the page break template is named.
If the line isn’t a paragraph tag at all, then if it’s not blank we can just store it as a new row in the data structure, with the SYNTAX_TAG constant in the first column. This way we know later that these entries are not really page breaks. Once we’ve done this processing for each row, we can close the handle to the file, and return without an error.
/****************************************/ int save_page_breaks(string filename){ /* save the page breaks to given file */ /****************************************/ handle file; /* the output file */ int rc; /* return code */ int size; /* size of page break array */ int ix; /* counter */ /* */ file = PoolCreate(); /* create a new string pool object */ rc = GetLastError(); /* */ if (IsError(rc)){ /* if there is an error */ SetLastError(rc); /* set the error code */ return ERROR_EXIT; /* return an error */ } /* */ size = ArrayGetAxisDepth(page_breaks); /* get size of page break array */ for(ix=0;ix<size;ix++){ /* for each page break */ if(page_breaks[ix][0]!=""){ /* if there's something in this row */ if (page_breaks[ix][0]!=SYNTAX_TAG){ /* if it's not just part of template */ PoolAppend(file,page_breaks[ix][0]); /* write the title of the page break */ PoolAppend(file,"\r\n"); /* write a blank line */ } /* */ PoolAppend(file,page_breaks[ix][1]); /* write the content */ } /* */ } /* */ PoolWriteFile(file,filename); /* write output */ CloseHandle(file); /* close handle to the file */ return ERROR_NONE; /* return no error */ } /* */
The counterpart to the read_page_breaks function here is the save_page_breaks function. It simply goes through our data structure and writes it back out to a file. We’re using a String Pool object to build the output, but there are a lot of other ways output can be written. First, we create the Pool with PoolCreate, and test to make sure it created alright. Then we can get the size of our page break array, and iterate over it with a for loop. If the page break array has something in that row, we can test to see if the first column value is our SYNTAX_TAG constant. If it isn’t, we can assume this is indeed a page break, and write out the first column (the paragraph code), and then a blank return. Then we can write out the column two value for this row, regardless of what the value for column 1 was, because it’s either the content of our page break or syntax that’s required for our template file anyway. Finally we can write the file, close our pool, and return without error.
Next, let’s take a look at some of the new UI functions we’ve added. The run_modify function is the basic function called by our menu hook, which runs the rest of our script. It uses dialog functions with the modify_ prefix, which in this script means the functions modify_load, modify_ok, modify_action, and modify_validate. Let’s start by looking at run_modify function.
/****************************************/ int run_modify(int f_id, string mode){ /* run the modify function */ /****************************************/ string template_file; /* the template file */ int rc; /* return code */ /* */ if (mode!="preprocess"){ /* if not preprocess */ SetLastError(ERROR_NONE); /* set the last error */ return ERROR_EXIT; /* return */ } /* */ selected_id = -1; /* unset renaming id */ renaming = ""; /* reset renaming */ modified = false; /* reset modified */ rc = check_template_folder(); /* check the templates folder */ if (rc!=ERROR_NONE){ /* if we have an error */ return ERROR_EXIT; /* return error */ } /* */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ rc = read_page_breaks(template_file); /* read the page break template */ if (rc!=ERROR_NONE){ /* if we have an error */ SetLastError(rc); /* set the last error message */ return ERROR_EXIT; /* return error */ } /* */ rc = DialogBox(MANAGE_TEMPLATES_DLG, "modify_"); /* enter the dialog */ SetLastError(rc); /* set the last error */ return ERROR_EXIT; /* return no error */ } /* */
The run_modify function starts by checking to make sure we’re running in preprocess mode. Then, it resets some of our global variables, selected_id, renaming, and modified, to make sure we’re not using any old data. We can then run our check_template_folder routine to check to make sure our template folder exists and it has a template file in it. If we made it here, and still have no errors, then we can go ahead and enter our modify dialog loop to let the user modify their page breaks.
/****************************************/ void modify_load(){ /* load options into the modify dialog */ /****************************************/ string names[]; /* list of names of page breaks */ string name; /* name of a page break */ int ix; /* loop counter */ int nx; /* name counter */ int size; /* size of page break array */ /* */ size = ArrayGetAxisDepth(page_breaks); /* get size of page breaks array */ DataControlResetContent(TEMPLATE_LIST); /* reset contents of the dialog */ DataControlSetColumnHeadings(TEMPLATE_LIST,"Template Name"); /* set the heading for the data control */ DataControlSetColumnPositions(TEMPLATE_LIST, 267); /* set the width of the heading */ for(ix=0;ix<size;ix++){ /* for each page break */ if((page_breaks[ix][0]!=SYNTAX_TAG) && /* if it's not a part of the HTML syntax*/ (page_breaks[ix][0]!="")){ /* if it's not blank */ name = ReplaceInString(page_breaks[ix][0],P_START,""); /* strip out p start tag */ name = ReplaceInString(name,P_END,""); /* strip out p end tag */ DataControlInsertString(TEMPLATE_LIST,ix,name); /* add it to the dialog */ } /* */ } /* */ }
This function is responsible for loading our information into the dialog that lists out all of our page breaks. First, it gets the size of the page breaks array, and then resets the content of our list, and sets some column headings up for it. Then, for each item in our page break array, we can test to see if it’s an actual page break or just part of the template’s syntax, and for each real page break we can strip out the opening and closing P tags with ReplaceInString and add it into our data control.
/****************************************/ void modify_ok(){ /* save changes to page breaks */ /****************************************/ string template_file; /* path to template file */ /* */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ if (modified == true){ /* if we changed anything */ save_page_breaks(template_file); /* save the page break template */ } /* */ } /* */
The modify_ok function is really simple, it’s triggered when you press the “save” button on the dialog window. It gets the path to the template file, and if the template file was modified, it will then save the page breaks to that template file with the save_page_breaks function. The modified flag is set by the rename and delete functions. Whenever either of those is run, they set the flag to true, so this function will know if it needs to re-write the file or not.
/****************************************/ void modify_action(int c_id, int action){ /* action on the modify dialog */ /****************************************/ .... variable declarations omitted .... /* */ if (c_id != TEMPLATE_RENAME && c_id != TEMPLATE_DELETE){ /* if not one of our two buttons */ return; /* return */ } /* */ /* */ selected = DataControlGetRowSelection(TEMPLATE_LIST); /* get the selected item from the list */ if(selected < 0){ /* if there no selection */ MessageBox('x',"No template selected, cannot edit."); /* display error */ return; /* return */ } /* */ /* */ data = DataControlGetTable(TEMPLATE_LIST); /* get the data from the list */ selected_nm = TrimPadding(data[selected][0]); /* get the name of the selected row */ selected_fn = FormatString(P_TEMPLATE,selected_nm); /* get full name of renaming template */ selected_id = -1; /* init renaming ID to -1 */ size = ArrayGetAxisDepth(page_breaks); /* get size of page break table */ for(ix=0;ix<size;ix++){ /* for each row in table */ if (selected_fn == TrimPadding(page_breaks[ix][0])){ /* if name matches the row in the db */ selected_id = ix; /* store ID for later */ break; /* break loop */ } /* */ } /* */ if (c_id == TEMPLATE_RENAME){ /* if rename was pressed */ rc = rename(selected_nm); /* run the rename function */ if (IsError(rc)==false){ /* if rename didn't return an error */ modify_load(); /* reload the list */ } /* */ return; /* return */ } /* */ if (c_id == TEMPLATE_DELETE){ /* if delete was pressed */ rc = delete(selected_nm); /* run the delete function */ if (IsError(rc)==false){ /* if delete didn't return an error */ modify_load(); /* reload the list */ } /* */ return; /* return */ } /* */ } /* */
The modify_action function is called whenever a user does anything on the dialog. In this case, we really only care if the user presses the TEMPLATE_RENAME or TEMPLATE_DELETE function, so if they do otherwise we can just return. We can get the selected row next, and if there is no selection, display a message and return, because we need a selection in order to actually do anything. Once we have our selected row, we can get the contents of the table, and get the name of the template from the selected row. We can then format that into a “full name”, the name wrapped by the normal paragraph codes the template uses to represent a name. Using that, we can then iterate over the page_breaks array, to get the row number of the selected page break in the array for use later. Now that we know what row in the page_breaks array the row we’ve selected corresponds to, we can determine if the user pressed the rename or delete button, and run the appropriate function. After the function selected runs, we just run the modify_load function to reload the dialog, and then return.
/****************************************/ int delete(string selected){ /* delete selected template */ /****************************************/ int rc; /* return code */ /* */ rc= YesNoBox('q',"This will delete '%s', do you want to continue?", /* ask user if they want to delete */ selected); /* ask user to delete */ if (rc==IDYES){ /* if user pressed yes */ page_breaks[selected_id][0] = ""; /* delete page break */ page_breaks[selected_id][1] = ""; /* delete page break */ modified = true; /* set modified flag */ } /* */ return ERROR_NONE; /* */ } /* */
The delete function is pretty simple. It just confirms that the user indeed wants to delete the page break, and if they press yes on the dialog, it sets the selected row in the page_breaks array to blank strings so that it won’t be written back out when our save_page_breaks function runs. Finally it sets the modified flag to true, then returns.
/****************************************/ int rename(string selected){ /* rename selected template */ /****************************************/ string fullname; /* full original name of break */ int rc; /* return code */ int s_pos; /* selected row position */ /* */ renaming = TrimPadding(selected); /* save the page break we're renaming */ rc = DialogBox(PAGEBREAK_TEMPLATES, "rename_"); /* enter the rename dialog */ return ERROR_NONE; /* return no error */ } /* */
Another very simple function, rename is going to store the name of the template we’re renaming in the global renaming, then launch our rename dialog. This re-uses the save dialog, but uses functions with the rename prefix to control it, in this case rename_load, rename_validate, and rename_ok.
/****************************************/ int rename_load(){ /* load the rename dialog */ /****************************************/ EditSetText(TEMPLATE_NAME,renaming); /* load name of what we're renaming */ } /* */ /****************************************/ int rename_validate(){ /* validate the save name dialog */ /****************************************/ return save_validate(); /* alias to save_validate */ } /* */ /****************************************/ int rename_ok(){ /* process the rename */ /****************************************/ string newname; /* new name of page break */ /* */ if (selected_id>=0){ /* if we're renaming something */ newname = TrimPadding(EditGetText(TEMPLATE_NAME)); /* get the base new name */ newname = FormatString(P_TEMPLATE,newname)+"\r\n"; /* get full new name */ if (page_breaks[selected_id][0] != newname){ /* if we're actually modifying the name */ page_breaks[selected_id][0]=newname; /* set new name */ modified = true; /* set modified flag */ } /* */ } /* */ } /* */
All of the rename functions are very simple as well. The rename_load function simply sets the content of the dialog to the name we’re going to be renaming. The rename_validate function is just an alias to the save_valdiate function, so it just calls that, since that validation already accounts for renaming. I figured it would be simpler to have one function do both validations, instead of having to repeat a lot of the code. The rename_ok function is called when the user presses the OK button after the dialog is validated. It is responsible for getting the value of the text field, formatting it into a full page break name, and then checking to see if this is actually a new name, and if so it stores the new name and sets the modified flag to true.
So now we’re like 80% of the way to having a fully useful page break function. However, we’re going to have to add a way to re-order this list as well, since grouping things together is going to be a pretty important function to keeping all of this organized. That’s going to have to wait for the next blog about this function though, since this is already a fairly complicated script. Here’s our entire script so far:
/* PageBreakTemplateManager.ms * * Author: Steven Horowitz * * Notes: Allows a user to save and edit page break templates. */ /************************************************/ /* Defined Error / Warning Messages */ /************************************************/ #define TEMPLATE_FILENAME "HeaderFooterList.htm" #define P_TEMPLATE "<P STYLE=\"margin: 0\">{%s}</P>" #define SYNTAX_TAG "HTML_TAG_NOT_TITLE" #define P_START "<P STYLE=\"margin: 0\">{" #define P_END "}</P>" #define END_TABLE "</TABLE>" #define END_P_NAME "end" #define TEMPLATE_HTTP_LOC "http://www.novaworkssoftware.com/files/HeaderFooterList.htm" #define TEMPLATE_BODY_LINE "<BODY STYLE=\"font: 11pt Times New Roman, Times, Serif\">" #define TEST_X 0 #define TEST_Y 10 #define TEST_SAVE false #define TEST_MODIFY true /************************************************/ /* Function Signatures */ /************************************************/ void setup(); /* set up the hooks */ int run_save(int f_id, string mode, handle window); /* run the save template script */ int run_modify(int f_id, string mode); /* run the modify template script */ void modify_load(); /* load the modify dialog */ int save_validate(); /* validate if we can save a page break name */ int read_page_breaks(string filename); /* read the page breaks from a given file */ int save_page_breaks(string filename); /* save the page breaks to a given file */ int rename(string selected); /* rename a template */ int delete(string selected); /* delete a template */ int check_template_folder(); /* checks to make sure the template file exists */ /* */ /************************************************/ /* global values */ /************************************************/ string table_code; /* code for a table being saved */ string page_breaks[][]; /* array of all page break text */ boolean modified; /* true if we modify the page break template */ string renaming; /* the template we're currently renaming */ int selected_id; /* the id of the template w're renaming */ /****************************************/ void setup() { /* Called from Application Startup */ /****************************************/ string fn; /* file name */ string item[10]; /* menu item array */ string settings; /* the settings file location */ /* */ item["Code"] = "SAVE_PAGE_BREAK"; /* set hook code */ item["Description"] ="Save the current table as page break preset.";/* set hook description */ item["MenuText"] = "Save Page Break Template"; /* set menu text */ item["Class"] = "DocumentExtension"; /* set class as document */ MenuAddFunction(item); /* add menu item to menu */ fn = GetScriptFilename(); /* get the filename of the script */ MenuSetHook("SAVE_PAGE_BREAK", fn, "run_save"); /* set the hook */ /* add modify function */ item["Code"] = "MODIFY_PAGE_BREAK"; /* set hook code */ item["Description"] ="Modify the Page Break Templates."; /* set hook description */ item["MenuText"] = "Modify Page Break Template"; /* set menu text */ item["Class"] = "DocumentExtension"; /* set class as document */ MenuAddFunction(item); /* add menu item to menu */ fn = GetScriptFilename(); /* get the filename of the script */ MenuSetHook("MODIFY_PAGE_BREAK", fn, "run_modify"); /* set the hook */ } /* */ /****************************************/ void main(){ /* main function */ /****************************************/ int ix; /* counter */ int size; /* number of open windows */ string filename; /* filename of a window */ string ext; /* extension of current filename */ string windows[][]; /* array of all available windows */ /* */ if(GetScriptParent()=="LegatoIDE"){ /* if we're in IDE mode */ if (TEST_MODIFY){ /* if we're testing modify */ run_modify(0,"preprocess"); /* run preprocess */ } /* */ if (TEST_SAVE){ /* if we're testing save */ windows = EnumerateEditWindows(); /* get all active edit windows */ size = ArrayGetAxisDepth(windows); /* get the number of open windows */ for(ix=0;ix<size;ix++){ /* for each open edit window */ filename = windows[ix]["Filename"]; /* get the filename of the window open */ ext = GetExtension(filename); /* get the extension to the filename */ if (ext == ".htm"){ /* if the extension is HTML */ MessageBox("running save on file %s",filename); /* display running save */ run_save(0,"preprocess", /* run the save function */ MakeHandle(windows[ix]["ClientHandle"])); /* run the save function on the window */ } /* */ } /* */ } /* */ } /* */ setup(); /* run setup */ } /* */ /****************************************/ int run_modify(int f_id, string mode){ /* run the modify function */ /****************************************/ string template_file; /* the template file */ int rc; /* return code */ /* */ if (mode!="preprocess"){ /* if not preprocess */ SetLastError(ERROR_NONE); /* set the last error */ return ERROR_EXIT; /* return */ } /* */ selected_id = -1; /* unset renaming id */ renaming = ""; /* reset renaming */ modified = false; /* reset modified */ rc = check_template_folder(); /* check the templates folder */ if (rc!=ERROR_NONE){ /* if we have an error */ return ERROR_EXIT; /* return error */ } /* */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ rc = read_page_breaks(template_file); /* read the page break template */ if (rc!=ERROR_NONE){ /* if we have an error */ SetLastError(rc); /* set the last error message */ return ERROR_EXIT; /* return error */ } /* */ rc = DialogBox(MANAGE_TEMPLATES_DLG, "modify_"); /* enter the save dialog */ SetLastError(rc); /* set the last error */ return ERROR_EXIT; /* return no error */ } /* */ /****************************************/ void modify_load(){ /* load options into the modify dialog */ /****************************************/ string names[]; /* list of names of page breaks */ string name; /* name of a page break */ int ix; /* loop counter */ int nx; /* name counter */ int size; /* size of page break array */ /* */ size = ArrayGetAxisDepth(page_breaks); /* get size of page breaks array */ DataControlResetContent(TEMPLATE_LIST); /* reset contents of the dialog */ DataControlSetColumnHeadings(TEMPLATE_LIST,"Template Name"); /* set the heading for the data control */ DataControlSetColumnPositions(TEMPLATE_LIST, 267); /* set the width of the heading */ for(ix=0;ix<size;ix++){ /* for each page break */ if((page_breaks[ix][0]!=SYNTAX_TAG) && /* if it's not a part of the HTML syntax*/ (page_breaks[ix][0]!="")){ /* if it's not blank */ name = ReplaceInString(page_breaks[ix][0],P_START,""); /* strip out p start tag */ name = ReplaceInString(name,P_END,""); /* strip out p end tag */ DataControlInsertString(TEMPLATE_LIST,ix,name); /* add it to the dialog */ } /* */ } /* */ } /****************************************/ void modify_ok(){ /* save changes to page breaks */ /****************************************/ string template_file; /* path to template file */ /* */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ if (modified == true){ /* if we changed anything */ save_page_breaks(template_file); /* save the page break template */ } /* */ } /* */ /****************************************/ void modify_action(int c_id, int action){ /* action on the modify dialog */ /****************************************/ string selected_nm; /* selected name */ string selected_fn; /* selected full name */ int size; /* size of page break table */ int ix; /* loop counter */ int selected; /* selected index value */ string data[][]; /* content of the dialog's list */ int rc; /* result code */ /* */ if (c_id != TEMPLATE_RENAME && c_id != TEMPLATE_DELETE){ /* if not one of our two buttons */ return; /* return */ } /* */ /* */ selected = DataControlGetRowSelection(TEMPLATE_LIST); /* get the selected item from the list */ if(selected < 0){ /* if there no selection */ MessageBox('x',"No template selected, cannot edit."); /* display error */ return; /* return */ } /* */ /* */ data = DataControlGetTable(TEMPLATE_LIST); /* get the data from the list */ selected_nm = TrimPadding(data[selected][0]); /* get the name of the selected row */ selected_fn = FormatString(P_TEMPLATE,selected_nm); /* get full name of renaming template */ selected_id = -1; /* init renaming ID to -1 */ size = ArrayGetAxisDepth(page_breaks); /* get size of page break table */ for(ix=0;ix<size;ix++){ /* for each row in table */ if (selected_fn == TrimPadding(page_breaks[ix][0])){ /* if name matches the row in the db */ selected_id = ix; /* store ID for later */ break; /* break loop */ } /* */ } /* */ if (c_id == TEMPLATE_RENAME){ /* if rename was pressed */ rc = rename(selected_nm); /* run the rename function */ if (IsError(rc)==false){ /* if rename didn't return an error */ modify_load(); /* reload the list */ } /* */ return; /* return */ } /* */ if (c_id == TEMPLATE_DELETE){ /* if delete was pressed */ rc = delete(selected_nm); /* run the delete function */ if (IsError(rc)==false){ /* if delete didn't return an error */ modify_load(); /* reload the list */ } /* */ return; /* return */ } /* */ } /* */ /****************************************/ int rename(string selected){ /* rename selected template */ /****************************************/ string fullname; /* full original name of break */ int rc; /* return code */ int s_pos; /* selected row position */ /* */ renaming = TrimPadding(selected); /* save the page break we're renaming */ rc = DialogBox(PAGEBREAK_TEMPLATES, "rename_"); /* enter the rename dialog */ return ERROR_NONE; /* return no error */ } /* */ /****************************************/ int delete(string selected){ /* delete selected template */ /****************************************/ int rc; /* return code */ /* */ rc= YesNoBox('q',"This will delete '%s', do you want to continue?", /* ask user if they want to delete */ selected); /* ask user to delete */ if (rc==IDYES){ /* if user pressed yes */ page_breaks[selected_id][0] = ""; /* delete page break */ page_breaks[selected_id][1] = ""; /* delete page break */ modified = true; /* set modified flag */ } /* */ return ERROR_NONE; /* */ } /* */ /****************************************/ int rename_load(){ /* load the rename dialog */ /****************************************/ EditSetText(TEMPLATE_NAME,renaming); /* load name of what we're renaming */ } /* */ /****************************************/ int rename_validate(){ /* validate the save name dialog */ /****************************************/ return save_validate(); /* alias to save_validate */ } /* */ /****************************************/ int rename_ok(){ /* process the rename */ /****************************************/ string newname; /* new name of page break */ /* */ if (selected_id>=0){ /* if we're renaming something */ newname = TrimPadding(EditGetText(TEMPLATE_NAME)); /* get the base new name */ newname = FormatString(P_TEMPLATE,newname)+"\r\n"; /* get full new name */ if (page_breaks[selected_id][0] != newname){ /* if we're actually modifying the name */ page_breaks[selected_id][0]=newname; /* set new name */ modified = true; /* set modified flag */ } /* */ } /* */ } /* */ /****************************************/ int read_page_breaks(string filename){ /* read the page breaks */ /****************************************/ handle file; /* handle to template file */ string line; /* individual line from template */ string next_word; /* next word in word parser */ string content; /* the content of the page break */ boolean in_break; /* true if in a page break */ int size; /* number of lines in file */ int ix; /* line counter */ int px; /* page counter */ int rc; /* return code */ /* */ file = OpenMappedTextFile(filename); /* open the template file */ rc = GetLastError(); /* get the last error */ if (IsError(rc)){ /* if we couldn't open the file */ MessageBox('x',"Cannot open template file."); /* display error message */ return ERROR_EXIT; /* return error */ } /* */ size = GetLineCount(file); /* get number of lines in file */ while (ix<size){ /* for each line in file */ line = ReadLine(file,ix); /* read a line from the file */ if (in_break){ /* if we're in a page break */ if (line!=""){ /* if the line has text */ content+=line+"\r\n"; /* add the line to the content */ } /* */ if (FindInString(line,END_TABLE)>=0){ /* if line has a close table */ in_break = false; /* we're not in a page break anymore */ page_breaks[px][1] = content+"\r\n"; /* store the page break in array */ content = ""; /* reset content variable */ px++; /* increment page counter */ } /* */ } /* */ else{ /* if we're not in a page break */ if (FindInString(line,P_START)==0){ /* if line starts with paragraph */ if (FindInString(line,FormatString("{%s}",END_P_NAME))>0){ /* if the paragraph is an end para */ page_breaks[px][0] = SYNTAX_TAG; /* set title to keyword */ page_breaks[px][1] = line+"\r\n"; /* store content of line */ px++; /* increment page break counter */ } /* */ else{ /* if it's not the end para */ in_break = true; /* we're now in a page break */ page_breaks[px][0] = line+"\r\n"; /* get the title */ } /* */ } /* */ else{ /* if we're not starting a page break */ if (line!=""){ /* if the line isn't blank */ page_breaks[px][0] = SYNTAX_TAG; /* set title to keyword */ page_breaks[px][1] = line+"\r\n"; /* store content of line */ px++; /* increment page break counter */ } /* */ } /* */ } /* */ ix++; /* increment counter */ } /* */ CloseHandle(file); /* close the open file */ return ERROR_NONE; /* return without error */ } /* */ /****************************************/ int save_page_breaks(string filename){ /* save the page breaks to given file */ /****************************************/ handle file; /* the output file */ int rc; /* return code */ int size; /* size of page break array */ int ix; /* counter */ /* */ file = PoolCreate(); /* create a new string pool object */ rc = GetLastError(); /* */ if (IsError(rc)){ /* if there is an error */ SetLastError(rc); /* set the error code */ return ERROR_EXIT; /* return an error */ } /* */ size = ArrayGetAxisDepth(page_breaks); /* get size of page break array */ for(ix=0;ix<size;ix++){ /* for each page break */ if(page_breaks[ix][0]!=""){ /* if there's something in this row */ if (page_breaks[ix][0]!=SYNTAX_TAG){ /* if it's not just part of template */ PoolAppend(file,page_breaks[ix][0]); /* write the title of the page break */ PoolAppend(file,"\r\n"); /* write a blank line */ } /* */ PoolAppend(file,page_breaks[ix][1]); /* write the content */ } /* */ } /* */ PoolWriteFile(file,filename); /* write output */ CloseHandle(file); /* close handle to the file */ return ERROR_NONE; /* return no error */ } /* */ /****************************************/ int check_template_folder(){ /* ensure the templates folder exists */ /****************************************/ int rc; /* return code */ string template_folder; /* the template folder */ string template_file; /* the template file */ /* */ template_folder = GetApplicationDataFolder(); /* Get the appdata directory */ template_folder = AddPaths(template_folder,"Templates"); /* build path to templates folder */ if (IsFolder(template_folder)==false){ /* if template folder doesn't exist */ rc = CreateFolder(template_folder); /* create the template folder */ if (IsError(rc)){ /* if we cannot create the folder */ MessageBox('x',"Cannot create template folder, error %0x",rc); /* display error */ return rc; /* return error code */ } } /* */ template_file = AddPaths(template_folder,TEMPLATE_FILENAME); /* set path to template file */ if (IsFile(template_file)==false){ /* if template file doesn't exist */ rc = HTTPGetFile(TEMPLATE_HTTP_LOC,template_file); /* get the template file */ if (IsError(rc)){ /* if we couldn't get the template */ MessageBox('x',"Cannot get template file, error %0x",rc); /* display error */ return rc; /* return with error */ } /* */ } /* */ return ERROR_NONE; /* return no error */ } /****************************************/ int run_save(int f_id, string mode, handle window){ /* run the save function */ /****************************************/ int rc; /* return code */ dword window_type; /* get the edit window type */ handle edit_object; /* handle to the edit object */ int caret[]; /* caret positions */ string template_file; /* the template file path */ string tag; /* full text of element tag */ string element; /* the current element */ handle sgml; /* handle to the SGML parser */ /* */ if (mode != "preprocess"){ /* if not preprocess */ SetLastError(ERROR_NONE); /* set last error to nothing */ return ERROR_EXIT; /* bail out */ } /* */ if (IsWindowHandleValid(window)==false){ /* if we have no valid handle */ /* */ window = GetActiveEditWindow(); /* get the active edit window */ window_type = GetEditWindowType(window); /* get the edit window type */ window_type = window_type & EDX_TYPE_ID_MASK; /* mask file type */ if (window_type != EDX_TYPE_PSG_PAGE_VIEW){ /* if we're not in page view */ MessageBox('x',"This function must be used in page view."); /* display error */ return ERROR_EXIT; /* quit running */ } /* */ edit_object = GetEditObject(window); /* get edit object from window */ caret[0] = GetCaretXPosition(window); /* get the caret position in window */ caret[1] = GetCaretYPosition(window); /* get the caret position in window */ } /* */ else{ /* if we were passed a window */ edit_object = GetEditObject(window); /* get edit object from window */ caret[0] = TEST_X; /* set test position x */ caret[1] = TEST_Y; /* set test postion y */ } /* */ sgml = SGMLCreate(edit_object); /* get handle to SGML object */ SGMLSetPosition(sgml,caret[0],caret[1]); /* set position in SGML parser */ element = "init"; /* initialize element with a value */ while (element!="TABLE" && element!=""){ /* while element is not table and exists*/ tag = SGMLPreviousElement(sgml); /* get the previous SGML tag */ element = SGMLGetElementString(sgml); /* get the string value of the element */ } /* */ if (element == ""){ /* if the element is empty */ MessageBox('x',"This function must be run inside a table."); /* display message */ return ERROR_EXIT; /* quit running */ } /* */ table_code = SGMLFindClosingElement(sgml, /* get the code of the table */ SP_FCE_CODE_AS_IS | SP_FCE_INCLUDE_WRAPPER); /* get the code of the table */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ rc = read_page_breaks(template_file); /* read templates so we can validate */ if (rc!=ERROR_NONE){ /* if we cannot read the templates */ SetLastError(rc); /* set the error */ return ERROR_EXIT; /* return with error */ } /* */ rc = DialogBox(PAGEBREAK_TEMPLATES, "save_"); /* enter the save dialog */ SetLastError(rc); /* set last erorr */ return ERROR_EXIT; /* exit */ } /* */ /****************************************/ int save_validate(){ /* validate the save name dialog */ /****************************************/ string pb_name; /* the name of the page break save */ string renaming_fn; /* full name of the pb we're renaming */ string fullname; /* full name of the page break */ int size; /* size of table */ int n_pos; /* position of name in array */ int ix; /* loop counter */ /* */ pb_name = EditGetText(TEMPLATE_NAME); /* get the template name */ if (pb_name == ""){ /* if page break name is blank */ MessageBox('x',"Template must have a name."); /* display error */ return ERROR_EXIT; /* return error */ } /* */ renaming_fn = FormatString(P_TEMPLATE,renaming); /* get full name of renaming template */ fullname = FormatString(P_TEMPLATE,pb_name); /* get full name of entered template */ size = ArrayGetAxisDepth(page_breaks); /* get the size of the page break array */ n_pos = FindInTable(page_breaks,fullname,0,FIND_NO_CASE); /* check if name already exists */ if (n_pos >= 0 && fullname != renaming_fn){ /* if the name is found */ MessageBox('x',"Name already exists, cannot duplicate."); /* display error */ return ERROR_EXIT; /* return an error */ } /* */ return ERROR_NONE; /* return no error */ } /* */ /****************************************/ int save_ok(){ /* after validating the save dialog */ /****************************************/ int rc; /* return code */ int ix; /* iterator */ int size; /* size of the mapped text file */ string pb_name; /* page break name */ string line; /* content of line of mapped text file */ handle file; /* handle to the template file */ string template_file; /* the template file */ string template_folder; /* templates folder */ /* */ pb_name = EditGetText(TEMPLATE_NAME); /* get the page break template name */ rc = check_template_folder(); /* check the templates folder */ if (rc!=ERROR_NONE){ /* if we have an error */ return ERROR_EXIT; /* return error */ } /* */ template_file = GetApplicationDataFolder(); /* Get the appdata directory */ template_file = AddPaths(template_file,"Templates"); /* build path to templates folder */ template_file = AddPaths(template_file,TEMPLATE_FILENAME); /* set path to template file */ file = OpenMappedTextFile(template_file); /* open the mapped text file */ size = GetLineCount(file); /* get number of lines in mapped text */ while (line != TEMPLATE_BODY_LINE && ix<size){ /* while we haven't found the body */ line = ReadLine(file,ix); /* get the line */ ix++; /* increment line counter */ } /* */ if (line!=TEMPLATE_BODY_LINE){ /* if we couldn't find the body */ MessageBox('x',"Template file is not valid."); /* display an error message */ return ERROR_EXIT; /* return error */ } /* */ line = FormatString(P_TEMPLATE,pb_name); /* build line to insert */ line+= "\r\n\r\n"+table_code+"\r\n"; /* build line to insert */ InsertLine(file,ix,line); /* insert line */ MappedTextSave(file); /* save our modified file */ rc = GetLastError(); /* get the last error */ if (IsError(rc)){ /* if we can't save */ MessageBox('x',"Cannot save template file."); /* display an error */ return ERROR_EXIT; /* return an error */ } /* */ return ERROR_NONE; /* return no error */ } /* */ /************************************************/ /* dialog controls */ /************************************************/ #beginresource /****************************************/ /* save template dialog */ /****************************************/ #define PAGEBREAK_TEMPLATES 101 #define TEMPLATE_PROPERTIES 102 #define TEMPLATE_NAME 103 PAGEBREAK_TEMPLATES DIALOGEX 0, 0, 240, 66 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_MODALFRAME | DS_3DLOOK | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Header / Footer Properties" FONT 8, "MS Sans Serif" { CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 132, 50, 50, 14 CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 187, 50, 50, 14 CONTROL "Name:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 14, 25, 40, 13, 0 CONTROL "Template Properties:", -1, "static", SS_LEFT | WS_CHILD | WS_VISIBLE, 10, 8, 86, 13, 0 CONTROL "Frame1", -1, "static", SS_ETCHEDFRAME | WS_CHILD | WS_VISIBLE, 84, 12, 146, 1, 0 CONTROL "", TEMPLATE_NAME, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL, 53, 25, 172, 12, 0 } #define MANAGE_TEMPLATES_DLG 200 #define TEMPLATE_LIST 201 #define TEMPLATE_DELETE 202 #define TEMPLATE_RENAME 203 MANAGE_TEMPLATES_DLG DIALOGEX 0, 0, 344, 143 EXSTYLE WS_EX_DLGMODALFRAME STYLE DS_3DLOOK | DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_SYSMENU CAPTION "Manage Preference Templates" FONT 8, "MS Shell Dlg" { CONTROL "", TEMPLATE_LIST, "data_control", 0x50A10003, 13, 6, 267, 106, 0x00000000 CONTROL "&Delete", TEMPLATE_DELETE, "button", BS_CENTER, 283, 7, 50, 14, WS_EX_LEFT CONTROL "&Rename", TEMPLATE_RENAME, "button", BS_CENTER, 283, 24, 50, 14, WS_EX_LEFT CONTROL "", -1, "static", SS_ETCHEDFRAME, 6, 218, 234, 1, WS_EX_LEFT CONTROL "Save", IDOK, "button", BS_PUSHBUTTON |BS_CENTER, 230, 123, 50, 14, WS_EX_LEFT CONTROL "Cancel", IDCANCEL, "button", BS_PUSHBUTTON |BS_CENTER, 283, 123, 50, 14, WS_EX_LEFT } #endresource
Steven Horowitz has been working for Novaworks for over five years as a technical expert with a focus on EDGAR HTML and XBRL. Since the creation of the Legato language in 2015, Steven has been developing scripts to improve the GoFiler user experience. He is currently working toward a Bachelor of Sciences in Software Engineering at RIT and MCC. |
Additional Resources
Legato Script Developers LinkedIn Group
Primer: An Introduction to Legato