diff --git a/src/a.txt b/src/a.txt deleted file mode 100644 index 9675108..0000000 --- a/src/a.txt +++ /dev/null @@ -1 +0,0 @@ -Logo \ No newline at end of file diff --git a/src/components/DynamicInvoiceForm.tsx b/src/components/DynamicInvoiceForm.tsx index bf6e843..6e3b35b 100644 --- a/src/components/DynamicInvoiceForm.tsx +++ b/src/components/DynamicInvoiceForm.tsx @@ -21,6 +21,7 @@ import { IonItemDivider, IonTextarea, IonChip, + IonAlert, } from "@ionic/react"; import { close, @@ -55,7 +56,7 @@ const DynamicInvoiceForm: React.FC = ({ isOpen, onClose, }) => { - const { activeTemplateData, currentSheetId } = useInvoice(); + const { activeTemplateData, currentSheetId, selectedFile } = useInvoice(); const [formData, setFormData] = useState({}); const [showToast, setShowToast] = useState(false); const [toastMessage, setToastMessage] = useState(""); @@ -63,6 +64,7 @@ const DynamicInvoiceForm: React.FC = ({ "success" | "danger" | "warning" >("success"); const [lastSyncTime, setLastSyncTime] = useState(0); + const [showRefreshAlert, setShowRefreshAlert] = useState(false); // Get current template data const currentTemplate = useMemo(() => { @@ -128,7 +130,7 @@ const DynamicInvoiceForm: React.FC = ({ useEffect(() => { if (isOpen && formSections.length > 0) { const timeoutId = setTimeout(() => { - handleRefresh(); + silentRefresh(); }, 200); return () => clearTimeout(timeoutId); @@ -263,7 +265,34 @@ const DynamicInvoiceForm: React.FC = ({ } }; - const handleRefresh = async () => { + const silentRefresh = async () => { + try { + // Get all cell references from the form sections + const cellReferences = + DynamicFormManager.getAllCellReferences(formSections); + + if (cellReferences.length > 0) { + console.log("Silently refreshing data from cells:", cellReferences); + + // Get current data from the spreadsheet + const currentCellData = await getDynamicInvoiceData(cellReferences); + + // Convert cell data back to form data structure + const refreshedFormData = + DynamicFormManager.convertFromSpreadsheetFormat( + currentCellData, + formSections + ); + + console.log("Silent refresh - form data updated:", refreshedFormData); + setFormData(refreshedFormData); + } + } catch (error) { + console.error("Error during silent refresh:", error); + } + }; + + const performRefresh = async () => { try { // Get all cell references from the form sections const cellReferences = @@ -294,6 +323,10 @@ const DynamicInvoiceForm: React.FC = ({ } }; + const handleRefresh = () => { + setShowRefreshAlert(true); + }; + const handleAddItem = (sectionTitle: string) => { const section = formSections.find((s) => s.title === sectionTitle); if (!section || !section.itemsConfig) return; @@ -318,11 +351,6 @@ const DynamicInvoiceForm: React.FC = ({ [sectionTitle]: [...currentItems, newItem], }; }); - - // Trigger save after state update (but don't close modal) - setTimeout(() => { - saveToSheet(); - }, 100); }; const handleRemoveItem = (sectionTitle: string, itemIndex: number) => { @@ -344,11 +372,6 @@ const DynamicInvoiceForm: React.FC = ({ return newFormData; }); - - // Trigger save after state update (but don't close modal) - setTimeout(() => { - saveToSheet(); - }, 100); }; const renderField = (field: DynamicFormField, sectionTitle: string) => { @@ -529,7 +552,7 @@ const DynamicInvoiceForm: React.FC = ({ - Dynamic Invoice Form + Edit {selectedFile} @@ -555,13 +578,7 @@ const DynamicInvoiceForm: React.FC = ({ > - Dynamic Invoice Form - - - - {currentTemplate.template} - - + Edit {selectedFile} @@ -614,6 +631,30 @@ const DynamicInvoiceForm: React.FC = ({ duration={3000} color={toastColor} /> + + setShowRefreshAlert(false)} + header="Sync Sheet Data" + message="Do you want to sync sheet data to the form? This will discard any changes you might have made." + buttons={[ + { + text: "Cancel", + role: "cancel", + handler: () => { + setShowRefreshAlert(false); + }, + }, + { + text: "Sync Data", + role: "confirm", + handler: () => { + setShowRefreshAlert(false); + performRefresh(); + }, + }, + ]} + /> ); }; diff --git a/src/components/Files/Files.tsx b/src/components/Files/Files.tsx index 1b0befa..a624755 100644 --- a/src/components/Files/Files.tsx +++ b/src/components/Files/Files.tsx @@ -105,9 +105,9 @@ const Files: React.FC<{ const [serverFilesLoading, setServerFilesLoading] = useState(false); - // Template selection states - const [selectedTemplateFilter, setSelectedTemplateFilter] = useState< - number | "all" + // Template selection states - now using category instead of specific template + const [selectedCategoryFilter, setSelectedCategoryFilter] = useState< + string | "all" >("all"); // Screen size state @@ -125,6 +125,31 @@ const Files: React.FC<{ })); }; + // Category helper functions + const categorizeTemplate = (template_id: number) => { + const metadata = tempMeta.find((meta) => meta.template_id === template_id); + if (!metadata?.category) return "web"; + + const category = metadata.category.toLowerCase(); + if (category === "mobile") { + return "mobile"; + } else if (category === "tablet") { + return "tablet"; + } else { + return "web"; + } + }; + + const getAvailableCategories = () => { + const categories = new Set(); + tempMeta.forEach((template) => { + if (template.category) { + categories.add(template.category); + } + }); + return Array.from(categories).sort(); + }; + const getTemplateInfo = (templateId: number) => { const template = DATA[templateId]; return template ? template.template : `Template ${templateId}`; @@ -456,12 +481,16 @@ const Files: React.FC<{ }; }); - // Filter by template if a specific template is selected + // Filter by category if a specific category is selected let filteredFiles = filesArray; - if (selectedTemplateFilter !== "all") { - filteredFiles = filesArray.filter( - (file) => file.templateMetadata?.templateId === selectedTemplateFilter - ); + if (selectedCategoryFilter !== "all") { + filteredFiles = filesArray.filter((file) => { + if (!file.templateMetadata?.templateId) return false; + const templateCategory = categorizeTemplate( + file.templateMetadata.templateId + ); + return templateCategory === selectedCategoryFilter.toLowerCase(); + }); } // Apply search filter @@ -470,10 +499,8 @@ const Files: React.FC<{ if (filteredFiles.length === 0) { const emptyMessage = searchQuery.trim() ? `No files found matching "${searchQuery}"` - : selectedTemplateFilter !== "all" - ? `No files found for ${getTemplateInfo( - selectedTemplateFilter as number - )}` + : selectedCategoryFilter !== "all" + ? `No files found for ${selectedCategoryFilter} category` : "No local files found"; content = ( @@ -690,7 +717,7 @@ const Files: React.FC<{ searchQuery, sortBy, serverFilesLoading, - selectedTemplateFilter, + selectedCategoryFilter, ]); // Check screen size @@ -749,7 +776,7 @@ const Files: React.FC<{ style={{ flex: "2", minWidth: "200px" }} /> - {/* Template Filter */} + {/* Category Filter */}
{!isSmallScreen && ( - setSelectedTemplateFilter(e.detail.value) + setSelectedCategoryFilter(e.detail.value) } style={{ flex: "1", @@ -780,23 +807,22 @@ const Files: React.FC<{ }} interface="popover" > - All Templates - {getAvailableTemplates().map((template) => ( - - {template.template} + + All Categories + + {getAvailableCategories().map((category) => ( + + {category} ))} )} {isSmallScreen && ( - setSelectedTemplateFilter(e.detail.value) + setSelectedCategoryFilter(e.detail.value) } style={{ flex: "1", @@ -807,13 +833,12 @@ const Files: React.FC<{ }} interface="popover" > - All Templates - {getAvailableTemplates().map((template) => ( - - {template.template} + + All Categories + + {getAvailableCategories().map((category) => ( + + {category} ))} diff --git a/src/components/TemplateModal/TemplateModal.tsx b/src/components/TemplateModal/TemplateModal.tsx index ef3b6ca..585e033 100644 --- a/src/components/TemplateModal/TemplateModal.tsx +++ b/src/components/TemplateModal/TemplateModal.tsx @@ -72,13 +72,15 @@ const TemplateModal: React.FC = ({ return tempMeta.find((meta) => meta.template_id === templateId); }; - // Categorize templates based on their names - const categorizeTemplate = (templateName: string | undefined) => { - if (!templateName) return "web"; - const name = templateName.toLowerCase(); - if (name.includes("mobile")) { + // Categorize templates based on their metadata category + const categorizeTemplate = (template_id: number) => { + const metadata = getTemplateMetadata(template_id); + if (!metadata?.category) return "web"; + + const category = metadata.category.toLowerCase(); + if (category === "mobile") { return "mobile"; - } else if (name.includes("tablet")) { + } else if (category === "tablet") { return "tablet"; } else { return "web"; @@ -90,19 +92,13 @@ const TemplateModal: React.FC = ({ const templates = tempMeta; const categorized = { web: templates.filter((t) => { - const metadata = getTemplateMetadata(t.template_id); - const templateName = metadata?.name || t.name || "Unknown Template"; - return categorizeTemplate(templateName) === "web"; + return categorizeTemplate(t.template_id) === "web"; }), mobile: templates.filter((t) => { - const metadata = getTemplateMetadata(t.template_id); - const templateName = metadata?.name || t.name || "Unknown Template"; - return categorizeTemplate(templateName) === "mobile"; + return categorizeTemplate(t.template_id) === "mobile"; }), tablet: templates.filter((t) => { - const metadata = getTemplateMetadata(t.template_id); - const templateName = metadata?.name || t.name || "Unknown Template"; - return categorizeTemplate(templateName) === "tablet"; + return categorizeTemplate(t.template_id) === "tablet"; }), }; return categorized; @@ -257,7 +253,9 @@ const TemplateModal: React.FC = ({ template.template || template.name || "Unknown Template"; - const category = categorizeTemplate(templateName); + const category = categorizeTemplate( + template.templateId || template.template_id + ); // Get the template data from DATA to access footers const templateData = DATA[template.templateId || template.template_id]; diff --git a/src/components/socialcalc/core/constants.js b/src/components/socialcalc/core/constants.js new file mode 100644 index 0000000..2123b70 --- /dev/null +++ b/src/components/socialcalc/core/constants.js @@ -0,0 +1,1039 @@ +/* eslint-disable */ +// SocialCalc Constants Module +// Extracted from the main SocialCalc.js file + +// UMD wrapper +(function (root, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.SocialCalcConstants = factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + + // Initialize SocialCalc namespace if not exists + var SocialCalc; + if (typeof window !== "undefined" && window.SocialCalc) { + SocialCalc = window.SocialCalc; + } else if (typeof global !== "undefined" && global.SocialCalc) { + SocialCalc = global.SocialCalc; + } else { + SocialCalc = {}; + } + +// The module of the SocialCalc package with customizable constants, strings, etc. +// This is where most of the common localizations are done. +// +// (c) Copyright 2008, 2009, 2010 Socialtext, Inc. +// All Rights Reserved. +// +// The contents of this file are subject to the Artistic License 2.0; you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at http://socialcalc.org/licenses/al-20/. +// +// Some of the other files in the SocialCalc package are licensed under +// different licenses. Please note the licenses of the modules you use. +// +// Code History: +// +// Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. +// Based in part on the SocialCalc 1.1.0 code written in Perl. +// The SocialCalc 1.1.0 code was: +// Portions (c) Copyright 2005, 2006, 2007 Software Garden, Inc. +// All Rights Reserved. +// Portions (c) Copyright 2007 Socialtext, Inc. +// All Rights Reserved. +// The Perl SocialCalc started as modifications to the wikiCalc(R) program, version 1.0. +// wikiCalc 1.0 was written by Software Garden, Inc. +// Unless otherwise specified, referring to "SocialCalc" in comments refers to this +// JavaScript version of the code, not the SocialCalc Perl code. +// + + // + /* + // The module of the SocialCalc package with customizable constants, strings, etc. + // This is where most of the common localizations are done. + // + // (c) Copyright 2008, 2009, 2010 Socialtext, Inc. + // All Rights Reserved. + // + // The contents of this file are subject to the Artistic License 2.0; you may not + // use this file except in compliance with the License. You may obtain a copy of + // the License at http://socialcalc.org/licenses/al-20/. + // + // Some of the other files in the SocialCalc package are licensed under + // different licenses. Please note the licenses of the modules you use. + // + // Code History: + // + // Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. + // Based in part on the SocialCalc 1.1.0 code written in Perl. + // The SocialCalc 1.1.0 code was: + // Portions (c) Copyright 2005, 2006, 2007 Software Garden, Inc. + // All Rights Reserved. + // Portions (c) Copyright 2007 Socialtext, Inc. + // All Rights Reserved. + // The Perl SocialCalc started as modifications to the wikiCalc(R) program, version 1.0. + // wikiCalc 1.0 was written by Software Garden, Inc. + // Unless otherwise specified, referring to "SocialCalc" in comments refers to this + // JavaScript version of the code, not the SocialCalc Perl code. + // + */ + + var SocialCalc; + if (!SocialCalc) SocialCalc = {}; + + // ************************************* + // + // TO LEARN HOW TO LOCALIZE OR CUSTOMIZE SOCIALCALC, PLEASE READ THIS: + // + // The constants are all properties of the SocialCalc.Constants object. + // They are grouped here by what they are for, which module uses them, etc. + // + // Properties whose names start with "s_" are strings, or arrays of strings, + // that are good candidates for translation from the English. + // + // Other properties relate to visual settings, localization parameters, etc. + // + // These values are not used when SocialCalc modules are first loaded. + // They may be modified before the first use of the routines that use them, + // e.g., before creating SocialCalc objects. + // + // The exceptions are: + // TooltipOffsetX and TooltipOffsetY, as described with their definitions. + // + // SocialCalc IS NOT DESIGNED FOR USE WITH A TRANSLATION FUNCTION each time a string + // is used. Instead, language translations may be done by modifying this object. + // + // To customize SocialCalc, you may either replace this file with a modified version + // or you can overwrite the values before use. An example would be to + // iterate over all the properties looking for names that start with "s_" and + // use some other mechanism to obtain a localized string and replace the values + // here with those translated values. + // + // There is also a function, SocialCalc.ConstantsSetClasses, that may be used + // to easily switch SocialCalc from using explicit CSS styles for many things + // to using CSS classes. See the function, below, for more information. + // + // ************************************* + + SocialCalc.Constants = { + // + // Main SocialCalc module, socialcalc-3.js: + // + + //*** Common Constants + + textdatadefaulttype: "t", // This sets the default type for text on reading source file + // It should normally be "t" + + //*** Common error messages + + s_BrowserNotSupported: "Browser not supported.", // error thrown if browser can't handle events like IE or Firefox. + s_InternalError: "Internal SocialCalc error (probably an internal bug): ", // hopefully unlikely, but a test failed + + //*** SocialCalc.ParseSheetSave + + // Errors thrown on unexpected value in save file: + + s_pssUnknownColType: "Unknown col type item", + s_pssUnknownRowType: "Unknown row type item", + s_pssUnknownLineType: "Unknown line type", + + //*** SocialCalc.CellFromStringParts + + // Error thrown on unexpected value in save file: + + s_cfspUnknownCellType: "Unknown cell type item", + + //*** SocialCalc.CanonicalizeSheet + + doCanonicalizeSheet: true, // if true, do the canonicalization calculations + + //*** ExecuteSheetCommand + + s_escUnknownSheetCmd: "Unknown sheet command: ", + s_escUnknownSetCoordCmd: "Unknown set coord command: ", + s_escUnknownCmd: "Unknown command: ", + + //*** SocialCalc.CheckAndCalcCell + + s_caccCircRef: "Circular reference to ", // circular reference found during recalc + + //*** SocialCalc.RenderContext + + defaultRowNameWidth: "30", // used to set minimum width of the row header column - a string in pixels + defaultAssumedRowHeight: 15, // used when guessing row heights - number + defaultCellIDPrefix: "cell_", // if non-null, each cell will render with an ID starting with this + + // Default sheet display values + + defaultCellLayout: "padding:2px 2px 1px 2px;vertical-align:top;", + defaultCellFontStyle: "normal normal", + defaultCellFontSize: "small", + defaultCellFontFamily: "Verdana,Arial,Helvetica,sans-serif", + + defaultPaneDividerWidth: "2", // a string + defaultPaneDividerHeight: "3", // a string + + defaultGridCSS: "1px solid #C0C0C0;", // used as style to set each border when grid enabled (was #ECECEC) + + defaultCommentClass: "", // class added to cells with non-null comments when grid enabled + defaultCommentStyle: + "background-repeat:no-repeat;background-position:top right;background-image:url(www/assets/images/sc_commentbg.gif);", // style added to cells with non-null comments when grid enabled + defaultCommentNoGridClass: "", // class added to cells with non-null comments when grid not enabled + defaultCommentNoGridStyle: "", // style added to cells with non-null comments when grid not enabled + + defaultColWidth: "80", // text + defaultMinimumColWidth: 10, // numeric + + // For each of the following default sheet display values at least one of class and/or style are needed + + defaultHighlightTypeCursorClass: "", + // defaultHighlightTypeCursorStyle: "color:#FFF;backgroundColor:#A6A6A6;", + defaultHighlightTypeCursorStyle: "", + defaultHighlightTypeRangeClass: "", + //defaultHighlightTypeRangeStyle: "color:#000;backgroundColor:#E5E5E5;", + defaultHighlightTypeRangeStyle: "", + + defaultColnameClass: "", // regular column heading letters, needs a cursor property + defaultColnameStyle: + "font-size:small;text-align:center;color:#FFFFFF;background-color:#808080;cursor:e-resize;", + defaultSelectedColnameClass: "", // column with selected cell, needs a cursor property + defaultSelectedColnameStyle: + "font-size:small;text-align:center;color:#FFFFFF;background-color:#404040;cursor:e-resize;", + defaultRownameClass: "", // regular row heading numbers + defaultRownameStyle: + "font-size:small;text-align:right;color:#FFFFFF;background-color:#808080;", + defaultSelectedRownameClass: "", // column with selected cell, needs a cursor property + defaultSelectedRownameStyle: + "font-size:small;text-align:right;color:#FFFFFF;background-color:#404040;", + defaultUpperLeftClass: "", // Corner cell in upper left + defaultUpperLeftStyle: + "font-size:small;text-align:center;color:#FFFFFF;background-color:#808080;cursor:e-resize;", + defaultSkippedCellClass: "", // used if present for spanned cells peeking into a pane (at least one of class/style needed) + defaultSkippedCellStyle: "font-size:small;background-color:#CCC", // used if present + defaultPaneDividerClass: "", // used if present for the look of the space between panes (at least one of class/style needed) + defaultPaneDividerStyle: + "font-size:small;background-color:#C0C0C0;padding:0px;", // used if present + + s_rcMissingSheet: "Render Context must have a sheet object", // unlikely thrown error + + //*** SocialCalc.format_text_for_display + + defaultLinkFormatString: + 'Link', // used for format "text-link"; you could make this an img tag if desired + // defaultLinkFormatString: 'Link out', + defaultPageLinkFormatString: + 'Page', // used for format "text-link"; you could make this an img tag if desired + + //*** SocialCalc.format_number_for_display + + defaultFormatdt: "d-mmm-yyyy h:mm:ss", + defaultFormatd: "d-mmm-yyyy", + defaultFormatt: "[h]:mm:ss", + defaultDisplayTRUE: "TRUE", // how TRUE shows when rendered + defaultDisplayFALSE: "FALSE", + + // + // SocialCalc Table Editor module, socialcalctableeditor.js: + // + + //*** SocialCalc.TableEditor + + defaultImagePrefix: "www/assets/images/sc_", // URL prefix for images (e.g., "/www/assets/images/sc") + defaultTableEditorIDPrefix: "te_", // if present, many TableEditor elements are assigned IDs with this prefix + defaultPageUpDnAmount: 15, // number of rows to move cursor on PgUp/PgDn keys (numeric) + + AllowCtrlS: true, // turns on Ctrl-S trapdoor for setting custom numeric formats and commands if true + + //*** SocialCalc.CreateTableEditor + + defaultTableControlThickness: 0, // the short size for the scrollbars, etc. (numeric in pixels) + cteGriddivClass: "", // if present, the class for the TableEditor griddiv element + + //** SocialCalc.EditorGetStatuslineString -- strings shown on status line + + s_statusline_executing: "Executing...", + s_statusline_displaying: "Displaying...", + s_statusline_ordering: "Ordering...", + s_statusline_calculating: "Calculating...", + s_statusline_calculatingls: "Calculating... Loading Sheet...", + s_statusline_doingserverfunc: "doing server function ", + s_statusline_incell: " in cell ", + s_statusline_calcstart: "Calculation start...", + s_statusline_sum: "SUM", + s_statusline_recalcneeded: + '(Recalc needed)', + s_statusline_circref: 'Circular reference: ', + + //** SocialCalc.InputBoxDisplayCellContents + + s_inputboxdisplaymultilinetext: + "[Multi-line text: Click icon on right to edit]", + + //** SocialCalc.InputEcho + + defaultInputEchoClass: "", // if present, the class of the popup inputEcho div + defaultInputEchoStyle: + "filter:alpha(opacity=90);opacity:.9;backgroundColor:#FFD;border:1px solid #884;" + + "fontSize:small;padding:2px 10px 1px 2px;cursor:default;", // if present, pseudo style + defaultInputEchoPromptClass: "", // if present, the class of the popup inputEcho div + defaultInputEchoPromptStyle: + "filter:alpha(opacity=90);opacity:.9;backgroundColor:#FFD;" + + "borderLeft:1px solid #884;borderRight:1px solid #884;borderBottom:1px solid #884;" + + "fontSize:small;fontStyle:italic;padding:2px 10px 1px 2px;cursor:default;", // if present, pseudo style + + //** SocialCalc.InputEchoText + + ietUnknownFunction: "Unknown function ", // displayed when typing "=unknown(" + + //** SocialCalc.CellHandles + + CH_radius1: 29.0, // extent of inner circle within 90px image + CH_radius2: 41.0, // extent of outer circle within 90px image + s_CHfillAllTooltip: "Fill Contents and Formats Down/Right", // tooltip for fill all handle + s_CHfillContentsTooltip: "Fill Contents Only Down/Right", // tooltip for fill formulas handle + s_CHmovePasteAllTooltip: "Move Contents and Formats", // etc. + s_CHmovePasteContentsTooltip: "Move Contents Only", + s_CHmoveInsertAllTooltip: "Slide Contents and Formats within Row/Col", + s_CHmoveInsertContentsTooltip: "Slide Contents within Row/Col", + s_CHindicatorOperationLookup: { + Fill: "Fill", + FillC: "Fill Contents", + Move: "Move", + MoveI: "Slide", + MoveC: "Move Contents", + MoveIC: "Slide Contents", + }, // short form of operation to follow drag + s_CHindicatorDirectionLookup: { + Down: " Down", + Right: " Right", + Horizontal: " Horizontal", + Vertical: " Vertical", + }, // direction that modifies operation during drag + + //*** SocialCalc.TableControl + + defaultTCSliderThickness: 9, // length of pane slider (numeric in pixels) + defaultTCButtonThickness: 20, // length of scroll +/- buttons (numeric in pixels) + defaultTCThumbThickness: 15, // length of thumb (numeric in pixels) + + //*** SocialCalc.CreateTableControl + + TCmainStyle: "backgroundColor:#EEE;", // if present, pseudo style (text-align is textAlign) for main div of a table control + TCmainClass: "", // if present, the CSS class of the main div for a table control + TCendcapStyle: "backgroundColor:#FFF;", // backgroundColor may be used while waiting for image that may not come + TCendcapClass: "", + TCpanesliderStyle: "backgroundColor:#CCC;", + TCpanesliderClass: "", + s_panesliderTooltiph: "Drag to lock pane vertically", // tooltip for horizontal table control pane slider + s_panesliderTooltipv: "Drag to lock pane horizontally", + TClessbuttonStyle: "backgroundColor:#AAA;", + TClessbuttonClass: "", + TClessbuttonRepeatWait: 300, // in milliseconds + TClessbuttonRepeatInterval: 20, //100, // in milliseconds + TCmorebuttonStyle: "backgroundColor:#AAA;", + TCmorebuttonClass: "", + TCmorebuttonRepeatWait: 300, // in milliseconds + TCmorebuttonRepeatInterval: 20, //100, // in milliseconds + TCscrollareaStyle: "backgroundColor:#DDD;", + TCscrollareaClass: "", + TCscrollareaRepeatWait: 500, // in milliseconds + TCscrollareaRepeatInterval: 100, // in milliseconds + TCthumbClass: "", + TCthumbStyle: "backgroundColor:#CCC;", + + //*** SocialCalc.TCPSDragFunctionStart + + TCPStrackinglineClass: "", // at least one of class/style for pane slider tracking line display in table control + TCPStrackinglineStyle: "overflow:hidden;position:absolute;zIndex:100;", + // if present, pseudo style (text-align is textAlign) + TCPStrackinglineThickness: "2px", // narrow dimension of trackling line (string with units) + + //*** SocialCalc.TCTDragFunctionStart + + TCTDFSthumbstatusvClass: "", // at least one of class/style for vertical thumb dragging status display in table control + TCTDFSthumbstatusvStyle: + "height:20px;width:auto;border:3px solid #808080;overflow:hidden;" + + "backgroundColor:#FFF;fontSize:small;position:absolute;zIndex:100;", + // if present, pseudo style (text-align is textAlign) + TCTDFSthumbstatushClass: "", // at least one of class/style for horizontal thumb dragging status display in table control + TCTDFSthumbstatushStyle: + "height:20px;width:auto;border:1px solid black;padding:2px;" + + "backgroundColor:#FFF;fontSize:small;position:absolute;zIndex:100;", + // if present, pseudo style (text-align is textAlign) + TCTDFSthumbstatusrownumClass: "", // at least one of class/style for thumb dragging status display in table control + TCTDFSthumbstatusrownumStyle: + "color:#FFF;background-color:#808080;font-size:small;white-space:nowrap;padding:3px;", // if present, real style + TCTDFStopOffsetv: 0, // offsets for thumbstatus display while dragging + TCTDFSleftOffsetv: -80, + s_TCTDFthumbstatusPrefixv: "Row ", // Text Control Drag Function text before row number + TCTDFStopOffseth: -30, + TCTDFSleftOffseth: 0, + s_TCTDFthumbstatusPrefixh: "Col ", // Text Control Drag Function text before col number + + //*** SocialCalc.TooltipInfo + + // Note: These two values are used to set the TooltipInfo initial values when the code is first read in. + // Modifying them here after loading has no effect -- you need to modify SocialCalc.TooltipInfo directly + // to dynamically set them. This is different than most other constants which may be modified until use. + + TooltipOffsetX: 2, // offset in pixels from mouse position (to right on left side of screen, to left on right) + TooltipOffsetY: 10, // offset in pixels above mouse position for lower edge + + //*** SocialCalc.TooltipDisplay + + TDpopupElementClass: "", // at least one of class/style for tooltip display + TDpopupElementStyle: + "border:1px solid black;padding:1px 2px 2px 2px;textAlign:center;backgroundColor:#FFF;" + + "fontSize:7pt;fontFamily:Verdana,Arial,Helvetica,sans-serif;" + + "position:absolute;width:auto;zIndex:110;", + // if present, pseudo style (text-align is textAlign) + + // + // SocialCalc Spreadsheet Control module, socialcalcspreadsheetcontrol.js: + // + + //*** SocialCalc.SpreadsheetControl + + SCToolbarbackground: "background-color:#404040;", + SCTabbackground: "background-color:#CCC;", + SCTabselectedCSS: + "font-size:small;padding:6px 30px 6px 8px;color:#FFF;background-color:#404040;cursor:default;border-right:1px solid #CCC;", + SCTabplainCSS: + "font-size:small;padding:6px 30px 6px 8px;color:#FFF;background-color:#808080;cursor:default;border-right:1px solid #CCC;", + SCToolbartext: + "font-size:x-small;font-weight:bold;color:#FFF;padding-bottom:4px;", + + SCFormulabarheight: 0, // in pixels, will contain a text input box + + SCStatuslineheight: 20, // in pixels + SCStatuslineCSS: "font-size:10px;padding:3px 0px;", + + // workbook + doWorkBook: true, + SCSheetBarHeight: 25, + SCSheetBarBackground: "background-color:#CCC;", + SCSheetBarCSS: "background-color:#CCC;", + SCSheetBarWidth: "70%", + + SCCellHandlesDisable: true, + SCNoInputEcho: true, // do we need an input echo ? + s_inputboxdisplaynoteditable: "[not editable]", + SCNoRowName: true, + SCNoRanging: true, + SCNoQuoteInInputBox: true, + // Constants for default Format tab (settings) + // + // *** EVEN THOUGH THESE DON'T START WITH s_: *** + // + // These should be carefully checked for localization. Make sure you understand what they do and how they work! + // The first part of "first:second|first:second|..." is what is displayed and the second is the value to be used. + // The value is normally not translated -- only the displayed part. The [cancel], [break], etc., are not translated -- + // they are commands to SocialCalc.SettingsControls.PopupListInitialize + + SCFormatNumberFormats: + "[cancel]:|[break]:|%loc!Default!:|[custom]:|%loc!Automatic!:general|%loc!Auto w/ commas!:[,]General|[break]:|" + + "00:00|000:000|0000:0000|00000:00000|[break]:|%loc!Formula!:formula|%loc!Hidden!:hidden|[newcol]:" + + "1234:0|1,234:#,##0|1,234.5:#,##0.0|1,234.56:#,##0.00|1,234.567:#,##0.000|1,234.5678:#,##0.0000|" + + "[break]:|1,234%:#,##0%|1,234.5%:#,##0.0%|1,234.56%:#,##0.00%|" + + "[newcol]:|$1,234:$#,##0|$1,234.5:$#,##0.0|$1,234.56:$#,##0.00|[break]:|" + + "(1,234):#,##0_);(#,##0)|(1,234.5):#,##0.0_);(#,##0.0)|(1,234.56):#,##0.00_);(#,##0.00)|[break]:|" + + "($1,234):#,##0_);($#,##0)|($1,234.5):$#,##0.0_);($#,##0.0)|($1,234.56):$#,##0.00_);($#,##0.00)|" + + "[newcol]:|1/4/06:m/d/yy|01/04/2006:mm/dd/yyyy|2006-01-04:yyyy-mm-dd|4-Jan-06:d-mmm-yy|04-Jan-2006:dd-mmm-yyyy|January 4, 2006:mmmm d, yyyy|" + + "[break]:|1\\c23:h:mm|1\\c23 PM:h:mm AM/PM|1\\c23\\c45:h:mm:ss|01\\c23\\c45:hh:mm:ss|26\\c23 (h\\cm):[hh]:mm|69\\c45 (m\\cs):[mm]:ss|69 (s):[ss]|" + + "[newcol]:|2006-01-04 01\\c23\\c45:yyyy-mm-dd hh:mm:ss|January 4, 2006:mmmm d, yyyy hh:mm:ss|Wed:ddd|Wednesday:dddd|", + SCFormatTextFormats: + "[cancel]:|[break]:|%loc!Default!:|[custom]:|%loc!Automatic!:general|%loc!Plain Text!:text-plain|" + + "HTML:text-html|%loc!Wikitext!:text-wiki|%loc!Link!:text-link|%loc!Formula!:formula|%loc!Hidden!:hidden|", + SCFormatPadsizes: + "[cancel]:|[break]:|%loc!Default!:|[custom]:|%loc!No padding!:0px|" + + "[newcol]:|1 pixel:1px|2 pixels:2px|3 pixels:3px|4 pixels:4px|5 pixels:5px|" + + "6 pixels:6px|7 pixels:7px|8 pixels:8px|[newcol]:|9 pixels:9px|10 pixels:10px|11 pixels:11px|" + + "12 pixels:12px|13 pixels:13px|14 pixels:14px|16 pixels:16px|" + + "18 pixels:18px|[newcol]:|20 pixels:20px|22 pixels:22px|24 pixels:24px|28 pixels:28px|36 pixels:36px|", + SCFormatFontsizes: + "[cancel]:|[break]:|%loc!Default!:|[custom]:|X-Small:x-small|Small:small|Medium:medium|Large:large|X-Large:x-large|" + + "[newcol]:|6pt:6pt|7pt:7pt|8pt:8pt|9pt:9pt|10pt:10pt|11pt:11pt|12pt:12pt|14pt:14pt|16pt:16pt|" + + "[newcol]:|18pt:18pt|20pt:20pt|22pt:22pt|24pt:24pt|28pt:28pt|36pt:36pt|48pt:48pt|72pt:72pt|" + + "[newcol]:|8 pixels:8px|9 pixels:9px|10 pixels:10px|11 pixels:11px|" + + "12 pixels:12px|13 pixels:13px|14 pixels:14px|[newcol]:|16 pixels:16px|" + + "18 pixels:18px|20 pixels:20px|22 pixels:22px|24 pixels:24px|28 pixels:28px|36 pixels:36px|", + SCFormatFontfamilies: + "[cancel]:|[break]:|%loc!Default!:|[custom]:|Verdana:Verdana,Arial,Helvetica,sans-serif|" + + "Arial:arial,helvetica,sans-serif|Courier:'Courier New',Courier,monospace|", + SCFormatFontlook: + "[cancel]:|[break]:|%loc!Default!:|%loc!Normal!:normal normal|%loc!Bold!:normal bold|%loc!Italic!:italic normal|" + + "%loc!Bold Italic!:italic bold", + SCFormatTextAlignhoriz: + "[cancel]:|[break]:|%loc!Default!:|%loc!Left!:left|%loc!Center!:center|%loc!Right!:right|", + SCFormatNumberAlignhoriz: + "[cancel]:|[break]:|%loc!Default!:|%loc!Left!:left|%loc!Center!:center|%loc!Right!:right|", + SCFormatAlignVertical: + "[cancel]:|[break]:|%loc!Default!:|%loc!Top!:top|%loc!Middle!:middle|%loc!Bottom!:bottom|", + SCFormatColwidth: + "[cancel]:|[break]:|%loc!Default!:|[custom]:|[newcol]:|" + + "20 pixels:20|40:40|60:60|80:80|100:100|120:120|140:140|160:160|" + + "[newcol]:|180 pixels:180|200:200|220:220|240:240|260:260|280:280|300:300|", + SCFormatRecalc: "[cancel]:|[break]:|%loc!Auto!:|%loc!Manual!:off|", + + //*** SocialCalc.InitializeSpreadsheetControl + + ISCButtonBorderNormal: "#404040", + ISCButtonBorderHover: "#999", + ISCButtonBorderDown: "#FFF", + ISCButtonDownBackground: "#888", + + //*** SocialCalc.SettingsControls.PopupListInitialize + + s_PopupListCancel: "[Cancel]", + s_PopupListCustom: "Custom", + + // *** + // + // s_loc_ constants accessed by SocialCalc.LocalizeString and SocialCalc.LocalizeSubstrings + // + // Used extensively by socialcalcspreadsheetcontrol.js + // + // *** + + s_loc_align_center: "Align Center", + s_loc_align_left: "Align Left", + s_loc_align_right: "Align Right", + s_loc_alignment: "Alignment", + s_loc_audit: "Audit", + s_loc_audit_trail_this_session: "Audit Trail This Session", + s_loc_auto: "Auto", + s_loc_auto_sum: "Auto Sum", + s_loc_auto_wX_commas: "Auto w/ commas", + s_loc_automatic: "Automatic", + s_loc_background: "Background", + s_loc_bold: "Bold", + s_loc_bold_XampX_italics: "Bold & Italics", + s_loc_bold_italic: "Bold Italic", + s_loc_borders: "Borders", + s_loc_borders_off: "Borders Off", + s_loc_borders_on: "Borders On", + s_loc_bottom: "Bottom", + s_loc_bottom_border: "Bottom Border", + s_loc_cell_settings: "CELL SETTINGS", + s_loc_csv_format: "CSV format", + s_loc_cancel: "Cancel", + s_loc_category: "Category", + s_loc_center: "Center", + s_loc_clear: "Clear", + s_loc_clear_socialcalc_clipboard: "Clear SocialCalc Clipboard", + s_loc_clipboard: "Clipboard", + s_loc_color: "Color", + s_loc_column_: "Column ", + s_loc_comment: "Comment", + s_loc_copy: "Copy", + s_loc_custom: "Custom", + s_loc_cut: "Cut", + s_loc_default: "Default", + s_loc_default_alignment: "Default Alignment", + s_loc_default_column_width: "Default Column Width", + s_loc_default_font: "Default Font", + s_loc_default_format: "Default Format", + s_loc_default_padding: "Default Padding", + s_loc_delete: "Delete", + s_loc_delete_column: "Delete Column", + s_loc_delete_contents: "Delete Contents", + s_loc_delete_row: "Delete Row", + s_loc_description: "Description", + s_loc_display_clipboard_in: "Display Clipboard in", + s_loc_down: "Down", + s_loc_edit: "Edit", + s_loc_existing_names: "Existing Names", + s_loc_family: "Family", + s_loc_fill_down: "Fill Down", + s_loc_fill_right: "Fill Right", + s_loc_font: "Font", + s_loc_format: "Format", + s_loc_formula: "Formula", + s_loc_function_list: "Function List", + s_loc_functions: "Functions", + s_loc_grid: "Grid", + s_loc_hidden: "Hidden", + s_loc_horizontal: "Horizontal", + s_loc_insert_column: "Insert Column", + s_loc_insert_row: "Insert Row", + s_loc_italic: "Italic", + s_loc_last_sort: "Last Sort", + s_loc_left: "Left", + s_loc_left_border: "Left Border", + s_loc_link: "Link", + s_loc_link_input_box: "Link Input Box", + s_loc_list: "List", + s_loc_load_socialcalc_clipboard_with_this: + "Load SocialCalc Clipboard With This", + s_loc_major_sort: "Major Sort", + s_loc_manual: "Manual", + s_loc_merge_cells: "Merge Cells", + s_loc_middle: "Middle", + s_loc_minor_sort: "Minor Sort", + s_loc_move_insert: "Move Insert", + s_loc_move_paste: "Move Paste", + s_loc_multiXline_input_box: "Multi-line Input Box", + s_loc_name: "Name", + s_loc_names: "Names", + s_loc_no_padding: "No padding", + s_loc_normal: "Normal", + s_loc_number: "Number", + s_loc_number_horizontal: "Number Horizontal", + s_loc_ok: "OK", + s_loc_padding: "Padding", + s_loc_page_name: "Page Name", + s_loc_paste: "Paste", + s_loc_paste_formats: "Paste Formats", + s_loc_plain_text: "Plain Text", + s_loc_recalc: "Recalc", + s_loc_recalculation: "Recalculation", + s_loc_redo: "Redo", + s_loc_right: "Right", + s_loc_right_border: "Right Border", + s_loc_sheet_settings: "SHEET SETTINGS", + s_loc_save: "Save", + s_loc_save_to: "Save to", + s_loc_set_cell_contents: "Set Cell Contents", + s_loc_set_cells_to_sort: "Set Cells To Sort", + s_loc_set_value_to: "Set Value To", + s_loc_set_to_link_format: "Set to Link format", + s_loc_setXclear_move_from: "Set/Clear Move From", + s_loc_show_cell_settings: "Show Cell Settings", + s_loc_show_sheet_settings: "Show Sheet Settings", + s_loc_show_in_new_browser_window: "Show in new browser window", + s_loc_size: "Size", + s_loc_socialcalcXsave_format: "SocialCalc-save format", + s_loc_sort: "Sort", + s_loc_sort_: "Sort ", + s_loc_sort_cells: "Sort Cells", + s_loc_swap_colors: "Swap Colors", + s_loc_tabXdelimited_format: "Tab-delimited format", + s_loc_text: "Text", + s_loc_text_horizontal: "Text Horizontal", + s_loc_this_is_aXbrXsample: "This is a
sample", + s_loc_top: "Top", + s_loc_top_border: "Top Border", + s_loc_undone_steps: "UNDONE STEPS", + s_loc_url: "URL", + s_loc_undo: "Undo", + s_loc_unmerge_cells: "Unmerge Cells", + s_loc_up: "Up", + s_loc_value: "Value", + s_loc_vertical: "Vertical", + s_loc_wikitext: "Wikitext", + s_loc_workspace: "Workspace", + s_loc_XnewX: "[New]", + s_loc_XnoneX: "[None]", + s_loc_Xselect_rangeX: "[select range]", + + // + // SocialCalc Spreadsheet Viewer module, socialcalcviewer.js: + // + + //*** SocialCalc.SpreadsheetViewer + + SVStatuslineheight: 20, // in pixels + SVStatuslineCSS: "font-size:10px;padding:3px 0px;", + + // + // SocialCalc Format Number module, formatnumber2.js: + // + + FormatNumber_separatorchar: ",", // the thousands separator character when formatting numbers for display + FormatNumber_decimalchar: ".", // the decimal separator character when formatting numbers for display + FormatNumber_defaultCurrency: "$", // the currency string used if none specified + + // The following constants are arrays of strings with the short (3 character) and full names of days and months + + s_FormatNumber_daynames: [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ], + s_FormatNumber_daynames3: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], + s_FormatNumber_monthnames: [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ], + s_FormatNumber_monthnames3: [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ], + s_FormatNumber_am: "AM", + s_FormatNumber_am1: "A", + s_FormatNumber_pm: "PM", + s_FormatNumber_pm1: "P", + + // + // SocialCalc Spreadsheet Formula module, formula1.js: + // + + s_parseerrexponent: "Improperly formed number exponent", + s_parseerrchar: "Unexpected character in formula", + s_parseerrstring: "Improperly formed string", + s_parseerrspecialvalue: "Improperly formed special value", + s_parseerrtwoops: + "Error in formula (two operators inappropriately in a row)", + s_parseerrmissingopenparen: + "Missing open parenthesis in list with comma(s). ", + s_parseerrcloseparennoopen: + "Closing parenthesis without open parenthesis. ", + s_parseerrmissingcloseparen: "Missing close parenthesis. ", + s_parseerrmissingoperand: "Missing operand. ", + s_parseerrerrorinformula: "Error in formula.", + s_calcerrerrorvalueinformula: "Error value in formula", + s_parseerrerrorinformulabadval: "Error in formula resulting in bad value", + s_formularangeresult: "Formula results in range value:", + s_calcerrnumericnan: "Formula results in an bad numeric value", + s_calcerrnumericoverflow: "Numeric overflow", + s_sheetunavailable: "Sheet unavailable:", // when FindSheetInCache returns null + s_calcerrcellrefmissing: "Cell reference missing when expected.", + s_calcerrsheetnamemissing: "Sheet name missing when expected.", + s_circularnameref: "Circular name reference to name", + s_calcerrunknownname: "Unknown name", + s_calcerrincorrectargstofunction: "Incorrect arguments to function", + s_sheetfuncunknownfunction: "Unknown function", + s_sheetfunclnarg: "LN argument must be greater than 0", + s_sheetfunclog10arg: "LOG10 argument must be greater than 0", + s_sheetfunclogsecondarg: + "LOG second argument must be numeric greater than 0", + s_sheetfunclogfirstarg: "LOG first argument must be greater than 0", + s_sheetfuncroundsecondarg: "ROUND second argument must be numeric", + s_sheetfuncddblife: "DDB life must be greater than 1", + s_sheetfuncslnlife: "SLN life must be greater than 1", + + // Function definition text + + s_fdef_ABS: "Absolute value function. ", + s_fdef_ACOS: "Trigonometric arccosine function. ", + s_fdef_AND: "True if all arguments are true. ", + s_fdef_ASIN: "Trigonometric arcsine function. ", + s_fdef_ATAN: "Trigonometric arctan function. ", + s_fdef_ATAN2: "Trigonometric arc tangent function (result is in radians). ", + s_fdef_AVERAGE: "Averages the values. ", + s_fdef_CHOOSE: + "Returns the value specified by the index. The values may be ranges of cells. ", + s_fdef_COLUMNS: "Returns the number of columns in the range. ", + s_fdef_COS: "Trigonometric cosine function (value is in radians). ", + s_fdef_COUNT: + "Counts the number of numeric values, not blank, text, or error. ", + s_fdef_COUNTA: "Counts the number of non-blank values. ", + s_fdef_COUNTBLANK: + 'Counts the number of blank values. (Note: "" is not blank.) ', + s_fdef_COUNTIF: + 'Counts the number of number of cells in the range that meet the criteria. The criteria may be a value ("x", 15, 1+3) or a test (>25). ', + s_fdef_DATE: + 'Returns the appropriate date value given numbers for year, month, and day. For example: DATE(2006,2,1) for February 1, 2006. Note: In this program, day "1" is December 31, 1899 and the year 1900 is not a leap year. Some programs use January 1, 1900, as day "1" and treat 1900 as a leap year. In both cases, though, dates on or after March 1, 1900, are the same. ', + s_fdef_DAVERAGE: + "Averages the values in the specified field in records that meet the criteria. ", + s_fdef_DAY: "Returns the day of month for a date value. ", + s_fdef_DCOUNT: + "Counts the number of numeric values, not blank, text, or error, in the specified field in records that meet the criteria. ", + s_fdef_DCOUNTA: + "Counts the number of non-blank values in the specified field in records that meet the criteria. ", + s_fdef_DDB: + "Returns the amount of depreciation at the given period of time (the default factor is 2 for double-declining balance). ", + s_fdef_DEGREES: "Converts value in radians into degrees. ", + s_fdef_DGET: + "Returns the value of the specified field in the single record that meets the criteria. ", + s_fdef_DMAX: + "Returns the maximum of the numeric values in the specified field in records that meet the criteria. ", + s_fdef_DMIN: + "Returns the maximum of the numeric values in the specified field in records that meet the criteria. ", + s_fdef_DPRODUCT: + "Returns the result of multiplying the numeric values in the specified field in records that meet the criteria. ", + s_fdef_DSTDEV: + "Returns the sample standard deviation of the numeric values in the specified field in records that meet the criteria. ", + s_fdef_DSTDEVP: + "Returns the standard deviation of the numeric values in the specified field in records that meet the criteria. ", + s_fdef_DSUM: + "Returns the sum of the numeric values in the specified field in records that meet the criteria. ", + s_fdef_DVAR: + "Returns the sample variance of the numeric values in the specified field in records that meet the criteria. ", + s_fdef_DVARP: + "Returns the variance of the numeric values in the specified field in records that meet the criteria. ", + s_fdef_EVEN: + "Rounds the value up in magnitude to the nearest even integer. ", + s_fdef_EXACT: + 'Returns "true" if the values are exactly the same, including case, type, etc. ', + s_fdef_EXP: "Returns e raised to the value power. ", + s_fdef_FACT: "Returns factorial of the value. ", + s_fdef_FALSE: 'Returns the logical value "false". ', + s_fdef_FIND: + 'Returns the starting position within string2 of the first occurrence of string1 at or after "start". If start is omitted, 1 is assumed. ', + s_fdef_FV: + "Returns the future value of repeated payments of money invested at the given rate for the specified number of periods, with optional present value (default 0) and payment type (default 0 = at end of period, 1 = beginning of period). ", + s_fdef_HLOOKUP: + "Look for the matching value for the given value in the range and return the corresponding value in the cell specified by the row offset. If rangelookup is 1 (the default) and not 0, match if within numeric brackets (match<=value) instead of exact match. ", + s_fdef_HOUR: "Returns the hour portion of a time or date/time value. ", + s_fdef_IF: + "Results in true-value if logical-expression is TRUE or non-zero, otherwise results in false-value. ", + s_fdef_INDEX: + "Returns a cell or range reference for the specified row and column in the range. If range is 1-dimensional, then only one of rownum or colnum are needed. If range is 2-dimensional and rownum or colnum are zero, a reference to the range of just the specified column or row is returned. You can use the returned reference value in a range, e.g., sum(A1:INDEX(A2:A10,4)). ", + s_fdef_INT: + "Returns the value rounded down to the nearest integer (towards -infinity). ", + s_fdef_IRR: + "Returns the interest rate at which the cash flows in the range have a net present value of zero. Uses an iterative process that will return #NUM! error if it does not converge. There may be more than one possible solution. Providing the optional guess value may help in certain situations where it does not converge or finds an inappropriate solution (the default guess is 10%). ", + s_fdef_ISBLANK: + 'Returns "true" if the value is a reference to a blank cell. ', + s_fdef_ISERR: + 'Returns "true" if the value is of type "Error" but not "NA". ', + s_fdef_ISERROR: 'Returns "true" if the value is of type "Error". ', + s_fdef_ISLOGICAL: + 'Returns "true" if the value is of type "Logical" (true/false). ', + s_fdef_ISNA: 'Returns "true" if the value is the error type "NA". ', + s_fdef_ISNONTEXT: 'Returns "true" if the value is not of type "Text". ', + s_fdef_ISNUMBER: + 'Returns "true" if the value is of type "Number" (including logical values). ', + s_fdef_ISTEXT: 'Returns "true" if the value is of type "Text". ', + s_fdef_LEFT: + "Returns the specified number of characters from the text value. If count is omitted, 1 is assumed. ", + s_fdef_LEN: "Returns the number of characters in the text value. ", + s_fdef_LN: "Returns the natural logarithm of the value. ", + s_fdef_LOG: "Returns the logarithm of the value using the specified base. ", + s_fdef_LOG10: "Returns the base 10 logarithm of the value. ", + s_fdef_LOWER: + "Returns the text value with all uppercase characters converted to lowercase. ", + s_fdef_MATCH: + "Look for the matching value for the given value in the range and return position (the first is 1) in that range. If rangelookup is 1 (the default) and not 0, match if within numeric brackets (match<=value) instead of exact match. If rangelookup is -1, act like 1 but the bracket is match>=value. ", + s_fdef_MAX: "Returns the maximum of the numeric values. ", + s_fdef_MID: + "Returns the specified number of characters from the text value starting from the specified position. ", + s_fdef_MIN: "Returns the minimum of the numeric values. ", + s_fdef_MINUTE: "Returns the minute portion of a time or date/time value. ", + s_fdef_MOD: + "Returns the remainder of the first value divided by the second. ", + s_fdef_MONTH: "Returns the month part of a date value. ", + s_fdef_N: "Returns the value if it is a numeric value otherwise an error. ", + s_fdef_NA: + "Returns the #N/A error value which propagates through most operations. ", + s_fdef_NOT: "Returns FALSE if value is true, and TRUE if it is false. ", + s_fdef_NOW: "Returns the current date/time. ", + s_fdef_NPER: + "Returns the number of periods at which payments invested each period at the given rate with optional future value (default 0) and payment type (default 0 = at end of period, 1 = beginning of period) has the given present value. ", + s_fdef_NPV: + "Returns the net present value of cash flows (which may be individual values and/or ranges) at the given rate. The flows are positive if income, negative if paid out, and are assumed at the end of each period. ", + s_fdef_ODD: "Rounds the value up in magnitude to the nearest odd integer. ", + s_fdef_OR: "True if any argument is true ", + s_fdef_PI: "The value 3.1415926... ", + s_fdef_PMT: + "Returns the amount of each payment that must be invested at the given rate for the specified number of periods to have the specified present value, with optional future value (default 0) and payment type (default 0 = at end of period, 1 = beginning of period). ", + s_fdef_POWER: "Returns the first value raised to the second value power. ", + s_fdef_PRODUCT: "Returns the result of multiplying the numeric values. ", + s_fdef_PROPER: + "Returns the text value with the first letter of each word converted to uppercase and the others to lowercase. ", + s_fdef_PV: + "Returns the present value of the given number of payments each invested at the given rate, with optional future value (default 0) and payment type (default 0 = at end of period, 1 = beginning of period). ", + s_fdef_RADIANS: "Converts value in degrees into radians. ", + s_fdef_RATE: + "Returns the rate at which the given number of payments each invested at the given rate has the specified present value, with optional future value (default 0) and payment type (default 0 = at end of period, 1 = beginning of period). Uses an iterative process that will return #NUM! error if it does not converge. There may be more than one possible solution. Providing the optional guess value may help in certain situations where it does not converge or finds an inappropriate solution (the default guess is 10%). ", + s_fdef_REPLACE: + "Returns text1 with the specified number of characters starting from the specified position replaced by text2. ", + s_fdef_REPT: "Returns the text repeated the specified number of times. ", + s_fdef_RIGHT: + "Returns the specified number of characters from the text value starting from the end. If count is omitted, 1 is assumed. ", + s_fdef_ROUND: + "Rounds the value to the specified number of decimal places. If precision is negative, then round to powers of 10. The default precision is 0 (round to integer). ", + s_fdef_ROWS: "Returns the number of rows in the range. ", + s_fdef_SECOND: + "Returns the second portion of a time or date/time value (truncated to an integer). ", + s_fdef_SIN: "Trigonometric sine function (value is in radians) ", + s_fdef_SLN: + "Returns the amount of depreciation at each period of time using the straight-line method. ", + s_fdef_SQRT: "Square root of the value ", + s_fdef_STDEV: + "Returns the sample standard deviation of the numeric values. ", + s_fdef_STDEVP: "Returns the standard deviation of the numeric values. ", + s_fdef_SUBSTITUTE: + 'Returns text1 with the all occurrences of oldtext replaced by newtext. If "occurrence" is present, then only that occurrence is replaced. ', + s_fdef_SUM: + "Adds the numeric values. The values to the sum function may be ranges in the form similar to A1:B5. ", + s_fdef_SUMIF: + 'Sums the numeric values of cells in the range that meet the criteria. The criteria may be a value ("x", 15, 1+3) or a test (>25). If range2 is present, then range1 is tested and the corresponding range2 value is summed. ', + s_fdef_SYD: "Depreciation by Sum of Year's Digits method. ", + s_fdef_T: "Returns the text value or else a null string. ", + s_fdef_TAN: "Trigonometric tangent function (value is in radians) ", + s_fdef_TIME: + "Returns the time value given the specified hour, minute, and second. ", + s_fdef_TODAY: + 'Returns the current date (an integer). Note: In this program, day "1" is December 31, 1899 and the year 1900 is not a leap year. Some programs use January 1, 1900, as day "1" and treat 1900 as a leap year. In both cases, though, dates on or after March 1, 1900, are the same. ', + s_fdef_TRIM: + "Returns the text value with leading, trailing, and repeated spaces removed. ", + s_fdef_TRUE: 'Returns the logical value "true". ', + s_fdef_TRUNC: + "Truncates the value to the specified number of decimal places. If precision is negative, truncate to powers of 10. ", + s_fdef_UPPER: + "Returns the text value with all lowercase characters converted to uppercase. ", + s_fdef_VALUE: + "Converts the specified text value into a numeric value. Various forms that look like numbers (including digits followed by %, forms that look like dates, etc.) are handled. This may not handle all of the forms accepted by other spreadsheets and may be locale dependent. ", + s_fdef_VAR: "Returns the sample variance of the numeric values. ", + s_fdef_VARP: "Returns the variance of the numeric values. ", + s_fdef_VLOOKUP: + "Look for the matching value for the given value in the range and return the corresponding value in the cell specified by the column offset. If rangelookup is 1 (the default) and not 0, match if within numeric brackets (match>=value) instead of exact match. ", + s_fdef_WEEKDAY: + "Returns the day of week specified by the date value. If type is 1 (the default), Sunday is day and Saturday is day 7. If type is 2, Monday is day 1 and Sunday is day 7. If type is 3, Monday is day 0 and Sunday is day 6. ", + s_fdef_YEAR: "Returns the year part of a date value. ", + + s_farg_v: "value", + s_farg_vn: "value1, value2, ...", + s_farg_xy: "valueX, valueY", + s_farg_choose: "index, value1, value2, ...", + s_farg_range: "range", + s_farg_rangec: "range, criteria", + s_farg_date: "year, month, day", + s_farg_dfunc: "databaserange, fieldname, criteriarange", + s_farg_ddb: "cost, salvage, lifetime, period [, factor]", + s_farg_find: "string1, string2 [, start]", + s_farg_fv: "rate, n, payment, [pv, [paytype]]", + s_farg_hlookup: "value, range, row, [rangelookup]", + s_farg_iffunc: "logical-expression, true-value, false-value", + s_farg_index: "range, rownum, colnum", + s_farg_irr: "range, [guess]", + s_farg_tc: "text, count", + s_farg_log: "value, base", + s_farg_match: "value, range, [rangelookup]", + s_farg_mid: "text, start, length", + s_farg_nper: "rate, payment, pv, [fv, [paytype]]", + s_farg_npv: "rate, value1, value2, ...", + s_farg_pmt: "rate, n, pv, [fv, [paytype]]", + s_farg_pv: "rate, n, payment, [fv, [paytype]]", + s_farg_rate: "n, payment, pv, [fv, [paytype, [guess]]]", + s_farg_replace: "text1, start, length, text2", + s_farg_vp: "value, [precision]", + s_farg_valpre: "value, precision", + s_farg_csl: "cost, salvage, lifetime", + s_farg_cslp: "cost, salvage, lifetime, period", + s_farg_subs: "text1, oldtext, newtext [, occurrence]", + s_farg_sumif: "range1, criteria [, range2]", + s_farg_hms: "hour, minute, second", + s_farg_txt: "text", + s_farg_vlookup: "value, range, col, [rangelookup]", + s_farg_weekday: "date, [type]", + s_farg_dt: "date", + + function_classlist: [ + "all", + "stat", + "lookup", + "datetime", + "financial", + "test", + "math", + "text", + ], // order of function classes + + s_fclass_all: "All", + s_fclass_stat: "Statistics", + s_fclass_lookup: "Lookup", + s_fclass_datetime: "Date & Time", + s_fclass_financial: "Financial", + s_fclass_test: "Test", + s_fclass_math: "Math", + s_fclass_text: "Text", + + lastone: null, + }; + + // Default classnames for use with SocialCalc.ConstantsSetClasses: + + SocialCalc.ConstantsDefaultClasses = { + defaultComment: "", + defaultCommentNoGrid: "", + defaultHighlightTypeCursor: "", + defaultHighlightTypeRange: "", + defaultColname: "", + defaultSelectedColname: "", + defaultRowname: "", + defaultSelectedRowname: "", + defaultUpperLeft: "", + defaultSkippedCell: "", + defaultPaneDivider: "", + cteGriddiv: "", // this one has no Style version with it + defaultInputEcho: { + classname: "", + style: "filter:alpha(opacity=90);opacity:.9;", + }, // so FireFox won't show warning + TCmain: "", + TCendcap: "", + TCpaneslider: "", + TClessbutton: "", + TCmorebutton: "", + TCscrollarea: "", + TCthumb: "", + TCPStrackingline: "", + TCTDFSthumbstatus: "", + TDpopupElement: "", + }; + + // + // SocialCalc.ConstantsSetClasses(prefix) + // + // This routine goes through all of the xyzClass/xyzStyle pairs and sets the Class to a default and + // turns off the Style, if present. The prefix is put before each default. + // The list of items to set is in SocialCalc.ConstantsDefaultClasses. The names there + // correspond to the "xyz" parts. If there is a value, it is the default to set. If the + // default is a null, no change is made. If the default is the null string (""), the + // name of the item is used (e.g., "defaultComment" would use the classname "defaultComment"). + // If the default is an object, then it expects {classname: classname, style: stylestring} - this + // lets you combine both. + // + + SocialCalc.ConstantsSetClasses = function (prefix) { + var defaults = SocialCalc.ConstantsDefaultClasses; + var scc = SocialCalc.Constants; + var item; + + prefix = prefix || ""; + + for (item in defaults) { + if (typeof defaults[item] == "string") { + scc[item + "Class"] = prefix + (defaults[item] || item); + if (scc[item + "Style"] !== undefined) { + scc[item + "Style"] = ""; + } + } else if (typeof defaults[item] == "object") { + scc[item + "Class"] = prefix + (defaults[item].classname || item); + scc[item + "Style"] = defaults[item].style; + } + } + }; + + + // Make sure SocialCalc is available globally + if (typeof window !== "undefined") { + window.SocialCalc = SocialCalc; + } else if (typeof global !== "undefined") { + global.SocialCalc = SocialCalc; + } + + return SocialCalc; +}); diff --git a/src/components/socialcalc/core/core.js b/src/components/socialcalc/core/core.js new file mode 100644 index 0000000..cb9bfb5 --- /dev/null +++ b/src/components/socialcalc/core/core.js @@ -0,0 +1,7115 @@ +/* eslint-disable */ +// SocialCalc Core Module (Cell, Sheet, and core functions) +// Extracted from the main SocialCalc.js file + +// UMD wrapper +(function (root, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.SocialCalcCore = factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + + // Get SocialCalc namespace + var SocialCalc; + if (typeof window !== "undefined" && window.SocialCalc) { + SocialCalc = window.SocialCalc; + } else if (typeof global !== "undefined" && global.SocialCalc) { + SocialCalc = global.SocialCalc; + } else { + SocialCalc = {}; + } + + + // + // The main SocialCalc code module of the SocialCalc package + // + /* + // (c) Copyright 2010 Socialtext, Inc. + // All Rights Reserved. + // + // The contents of this file are subject to the Artistic License 2.0; you may not + // use this file except in compliance with the License. You may obtain a copy of + // the License at http://socialcalc.org/licenses/al-20/. + // + // Some of the other files in the SocialCalc package are licensed under + // different licenses. Please note the licenses of the modules you use. + // + // Code History: + // + // Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. + // Based in part on the SocialCalc 1.1.0 code written in Perl. + // The SocialCalc 1.1.0 code was: + // Portions (c) Copyright 2005, 2006, 2007 Software Garden, Inc. + // All Rights Reserved. + // Portions (c) Copyright 2007 Socialtext, Inc. + // All Rights Reserved. + // The Perl SocialCalc started as modifications to the wikiCalc(R) program, version 1.0. + // wikiCalc 1.0 was written by Software Garden, Inc. + // Unless otherwise specified, referring to "SocialCalc" in comments refers to this + // JavaScript version of the code, not the SocialCalc Perl code. + // + */ + + /* + + **** Overview **** + + This is the beginning of a library of routines for displaying and editing spreadsheet + data in a browser. The HTML that includes this does not need to have anything + specific to the spreadsheet or editor already present -- everything is dynamically + added to the DOM by this code, including the rendered sheet and any editing controls. + + The library has a few parts. This is the main SocialCalc code module. + Other parts are the Table Editor module, the Formula module, and the Format Number module. + Note: The Table Editor module is licensed under a different license than this module. + + The class/object style is derived from O'Reilly's JavaScript by Flanagan, 5th Edition, + section 9.3, page 157. + + All of the data, object definitions, functions, etc., are stored as properties of the SocialCalc + object so as not to clutter up the global variables nor conflict with other names. + + A design goal (not tested yet for success) is to make it possible to have more than one + spreadsheet active on a page, perhaps even open for editing. It is assumed, though, that + there is only one mouse and one keyboard (a good assumption on most PCs today but not in the + new "touch and surface world" Apple and Microsoft are working towards). + + The testing has been on Windows Firefox (2 and 3), + Internet Explorer (6 and 7), Opera (9.23 and mainly later), Mac Safari (3.1), and Mac Firefox (2.0.0.6). + There are small issues with Firefox before 2.0 (cosmetic with drag handles) and larger ones + with Opera before 9.5 (the Delete key isn't recognized in some cases -- the 9.5 version was still + in beta and this bug affects other products like GMail, I believe). + + The data is stored in a SocialCalc.Sheet object. The data is organized in a form similar to that + used by SocialCalc 1.1.0. There is a function for converting a normal SocialCalc spreadsheet + save data string (the spreadsheet part of a SocialCalc data file) into this internal form. + + The SocialCalc.RenderContext class provides methods for rendering a table into the DOM representing + part of the spreadsheet. It is assumed that the spreadsheet could possibly be very large + and that rendering the whole thing at once could be too time consuming. It is also set up so + that it might be possible to have some of the sheet data only be loaded on demand (such as by Ajax). + The rendering can render cells to the right and below the already active area of the spreadsheet + so that you can scroll to that "clean" area without explicitly doing "add row/column". The class also + does simple operations such as "scrolling" within that table. The table may optionally include + row and column headers and may be split into panes. Most of the code assumes any number of panes, + but only the rightmost pane has scrolling code. In normal operation there would be one or two + panes horizontally and vertically. The panes may start on any row/column, though a given row/column + should only appear in one pane at a time (not all code enforces this, yet). + + The RenderContext is designed to be rendered as part of a SocialCalc.TableEditor. The TableEditor + includes the spreadsheet grid as well as scrollbars, pane sliders, and (eventually) editing controls. + The layout is dynamic and may be recomputed on the fly, such as in response to resizing the browser + window. + + The scrollbars and pane sliders are created using SocialCalc.TableControl objects. These in turn + make use of Dragging, ToolTip, Button, and MouseWheel functions. + + The keyboard input is handled by keyboard code. + + There are also some helper routines. + + More comments yet to come... + + */ + + var SocialCalc; + if (!SocialCalc) SocialCalc = {}; + + // ************************************* + // + // Shared values + // + // These are "global" values shared by the classes, including default settings + // + // ************************************* + + // Callbacks + + SocialCalc.Callbacks = { + // The next two are used by SocialCalc.format_text_for_display + + // The function to expand wiki text - should be set if you want wikitext expansion + // The form is: expand_wiki(displayvalue, sheetobj, linkstyle, valueformat) + // valueformat is text-wiki followed by optional sub-formats, e.g., text-wikipagelink + + expand_wiki: null, + + expand_markup: function ( + displayvalue, + sheetobj, + linkstyle // the old function to expand wiki text - may be replaced + ) { + return SocialCalc.default_expand_markup( + displayvalue, + sheetobj, + linkstyle + ); + }, + + // MakePageLink is used to create the href for a link to another "page" + // The form is: MakePageLink(pagename, workspacename, linktyle, valueformat), returns string + + MakePageLink: null, + + // NormalizeSheetName is used to make different variations of sheetnames use the same cache slot + + NormalizeSheetName: null, // use default - lowercase + }; + + // Shared flags + + // none at present + + // ************************************* + // + // Cell class: + // + // ************************************* + + // + // Class SocialCalc.Cell + // + // Usage: var s = new SocialCalc.Cell(coord); + // + // Cell attributes include: + // + // coord: the column/row as a string, e.g., "A1" + // datavalue: the value to be used for computation and formatting for display, + // string or numeric (tolerant of numbers stored as strings) + // datatype: if present, v=numeric value, t=text value, f=formula, + // or c=constant that is not a simple number (like "$1.20") + // formula: if present, the formula (without leading "=") for computation or the constant + // valuetype: first char is main type, the following are sub-types. + // Main types are b=blank cell, n=numeric, t=text, e=error + // Examples of using sub-types would be "nt" for a numeric time value, "n$" for currency, "nl" for logical + // displayvalue: if present, rendered version of datavalue with formatting attributes applied + // parseinfo: if present, cached parsed version of formula + // + // The following optional values, if present, are mainly used in rendering, overriding defaults: + // + // bt, br, bb, bl: number of border's definition + // layout: layout (vertical alignment, padding) definition number + // font: font definition number + // color: text color definition number + // bgcolor: background color definition number + // cellformat: cell format (horizontal alignment) definition number + // nontextvalueformat: custom format definition number for non-text values, e.g., numbers + // textvalueformat: custom format definition number for text values + // colspan, rowspan: number of cells to span for merged cells (only on main cell) + // cssc: custom css classname for cell, as text (no special chars) + // csss: custom css style definition + // mod: modification allowed flag "y" if present + // comment: cell comment string + // + + SocialCalc.Cell = function (coord) { + this.coord = coord; + this.datavalue = ""; + this.datatype = null; + this.formula = ""; + this.valuetype = "b"; + }; + + // The types of cell properties + // + // Type 1: Base, Type 2: Attribute, Type 3: Special (e.g., displaystring, parseinfo) + + SocialCalc.CellProperties = { + coord: 1, + datavalue: 1, + datatype: 1, + formula: 1, + valuetype: 1, + errors: 1, + comment: 1, + bt: 2, + br: 2, + bb: 2, + bl: 2, + layout: 2, + font: 2, + color: 2, + bgcolor: 2, + cellformat: 2, + nontextvalueformat: 2, + textvalueformat: 2, + colspan: 2, + rowspan: 2, + cssc: 2, + csss: 2, + mod: 2, + displaystring: 3, // used to cache rendered HTML of cell contents + parseinfo: 3, // used to cache parsed formulas + hcolspan: 3, + hrowspan: 3, // spans taking hidden cols/rows into account (!!! NOT YET !!!) + }; + + SocialCalc.CellPropertiesTable = { + bt: "borderstyle", + br: "borderstyle", + bb: "borderstyle", + bl: "borderstyle", + layout: "layout", + font: "font", + color: "color", + bgcolor: "color", + cellformat: "cellformat", + nontextvalueformat: "valueformat", + textvalueformat: "valueformat", + }; + + // ************************************* + // + // Sheet class: + // + // ************************************* + + // + // Class SocialCalc.Sheet + // + // Usage: var s = new SocialCalc.Sheet(); + // + + SocialCalc.Sheet = function () { + SocialCalc.ResetSheet(this); + + // Set other values: + // + // sheet.statuscallback(data, status, arg, this.statuscallbackparams) is called + // during recalc and commands. + // + // During recalc, data is the current recalcdata. + // The values for status and the corresponding arg are: + // + // calcorder, {coord: coord, total: celllist length, count: count} [0 or more times per recalc] + // calccheckdone, calclist length [once per recalc] + // calcstep, {coord: coord, total: calclist length, count: count} [0 or more times per recalc] + // calcloading, {sheetname: name-of-sheet} + // calcserverfunc, {funcname: name-of-function, coord: coord, total: calclist length, count: count} + // calcfinished, time in milliseconds [once per recalc] + // + // During commands, data is SocialCalc.SheetCommandInfo. + // These values for status and arg are: + // + // cmdstart, cmdstr + // cmdend + // + + this.statuscallback = null; // routine called with cmdstart, calcstart, etc., status and args: + // sheet.statuscallback(data, status, arg, params) + this.statuscallbackparams = null; // parameters passed to that routine + }; + + // + // SocialCalc.ResetSheet(sheet) + // + // Resets (and/or initializes) sheet data values. + // + + SocialCalc.ResetSheet = function (sheet, reload) { + // properties: + + sheet.cells = {}; // at least one for each non-blank cell: coord: cell-object + sheet.attribs = + // sheet attributes + { + lastcol: 1, + lastrow: 1, + defaultlayout: 0, + }; + sheet.rowattribs = { + hide: {}, // access by row number + height: {}, + }; + sheet.colattribs = { + width: {}, // access by col name + hide: {}, + }; + sheet.names = {}; // Each is: {desc: "optional description", definition: "B5, A1:B7, or =formula"} + sheet.layouts = []; + sheet.layouthash = {}; + sheet.fonts = []; + sheet.fonthash = {}; + sheet.colors = []; + sheet.colorhash = {}; + sheet.borderstyles = []; + sheet.borderstylehash = {}; + sheet.cellformats = []; + sheet.cellformathash = {}; + sheet.valueformats = []; + sheet.valueformathash = {}; + + sheet.copiedfrom = ""; // if a range, then this was loaded from a saved range as clipboard content + + sheet.changes = new SocialCalc.UndoStack(); + + sheet.renderneeded = false; + + sheet.changedrendervalues = true; // if true, spans and/or fonts have changed (set by ExecuteSheetCommand & GetStyle) + + sheet.recalcchangedavalue = false; // true if a recalc resulted in a change to a cell's calculated value + }; + + // Methods: + + SocialCalc.Sheet.prototype.ResetSheet = function () { + SocialCalc.ResetSheet(this); + }; + SocialCalc.Sheet.prototype.AddCell = function (newcell) { + return (this.cells[newcell.coord] = newcell); + }; + SocialCalc.Sheet.prototype.GetAssuredCell = function (coord) { + return this.cells[coord] || this.AddCell(new SocialCalc.Cell(coord)); + }; + SocialCalc.Sheet.prototype.ParseSheetSave = function (savedsheet) { + SocialCalc.ParseSheetSave(savedsheet, this); + }; + SocialCalc.Sheet.prototype.CellFromStringParts = function (cell, parts, j) { + return SocialCalc.CellFromStringParts(this, cell, parts, j); + }; + SocialCalc.Sheet.prototype.CreateSheetSave = function (range, canonicalize) { + return SocialCalc.CreateSheetSave(this, range, canonicalize); + }; + SocialCalc.Sheet.prototype.CellToString = function (cell) { + return SocialCalc.CellToString(this, cell); + }; + SocialCalc.Sheet.prototype.CanonicalizeSheet = function (full) { + return SocialCalc.CanonicalizeSheet(this, full); + }; + SocialCalc.Sheet.prototype.EncodeCellAttributes = function (coord) { + return SocialCalc.EncodeCellAttributes(this, coord); + }; + SocialCalc.Sheet.prototype.EncodeSheetAttributes = function () { + return SocialCalc.EncodeSheetAttributes(this); + }; + SocialCalc.Sheet.prototype.DecodeCellAttributes = function ( + coord, + attribs, + range + ) { + return SocialCalc.DecodeCellAttributes(this, coord, attribs, range); + }; + SocialCalc.Sheet.prototype.DecodeSheetAttributes = function (attribs) { + return SocialCalc.DecodeSheetAttributes(this, attribs); + }; + + SocialCalc.Sheet.prototype.ScheduleSheetCommands = function ( + cmd, + saveundo, + isRemote + ) { + return SocialCalc.ScheduleSheetCommands(this, cmd, saveundo, isRemote); + }; + SocialCalc.Sheet.prototype.SheetUndo = function () { + return SocialCalc.SheetUndo(this); + }; + SocialCalc.Sheet.prototype.SheetRedo = function () { + return SocialCalc.SheetRedo(this); + }; + SocialCalc.Sheet.prototype.CreateAuditString = function () { + return SocialCalc.CreateAuditString(this); + }; + SocialCalc.Sheet.prototype.GetStyleNum = function (atype, style) { + return SocialCalc.GetStyleNum(this, atype, style); + }; + SocialCalc.Sheet.prototype.GetStyleString = function (atype, num) { + return SocialCalc.GetStyleString(this, atype, num); + }; + SocialCalc.Sheet.prototype.RecalcSheet = function () { + return SocialCalc.RecalcSheet(this); + }; + + // + // Sheet save format: + // + // linetype:param1:param2:... + // + // Linetypes are: + // + // version:versionname - version of this format. Currently 1.4. + // + // cell:coord:type:value...:type:value... - Types are as follows: + // + // v:value - straight numeric value + // t:value - straight text/wiki-text in cell, encoded to handle \, :, newlines + // vt:fulltype:value - value with value type/subtype + // vtf:fulltype:value:formulatext - formula resulting in value with value type/subtype, value and text encoded + // vtc:fulltype:value:valuetext - formatted text constant resulting in value with value type/subtype, value and text encoded + // vf:fvalue:formulatext - formula resulting in value, value and text encoded (obsolete: only pre format version 1.1) + // fvalue - first char is "N" for numeric value, "T" for text value, "H" for HTML value, rest is the value + // e:errortext - Error text. Non-blank means formula parsing/calculation results in error. + // b:topborder#:rightborder#:bottomborder#:leftborder# - border# in sheet border list or blank if none + // l:layout# - number in cell layout list + // f:font# - number in sheet fonts list + // c:color# - sheet color list index for text + // bg:color# - sheet color list index for background color + // cf:format# - sheet cell format number for explicit format (align:left, etc.) + // cvf:valueformat# - sheet cell value format number (obsolete: only pre format v1.2) + // tvf:valueformat# - sheet cell text value format number + // ntvf:valueformat# - sheet cell non-text value format number + // colspan:numcols - number of columns spanned in merged cell + // rowspan:numrows - number of rows spanned in merged cell + // cssc:classname - name of CSS class to be used for cell when published instead of one calculated here + // csss:styletext - explicit CSS style information, encoded to handle :, etc. + // mod:allow - if "y" allow modification of cell for live "view" recalc + // comment:value - encoded text of comment for this cell (added in v1.5) + // + // col: + // w:widthval - number, "auto" (no width in tag), number%, or blank (use default) + // hide: - yes/no, no is assumed if missing + // row: + // hide - yes/no, no is assumed if missing + // + // sheet: + // c:lastcol - number + // r:lastrow - number + // w:defaultcolwidth - number, "auto", number%, or blank (default->80) + // h:defaultrowheight - not used + // tf:format# - cell format number for sheet default for text values + // ntf:format# - cell format number for sheet default for non-text values (i.e., numbers) + // layout:layout# - default cell layout number in cell layout list + // font:font# - default font number in sheet font list + // vf:valueformat# - default number value format number in sheet valueformat list (obsolete: only pre format version 1.2) + // ntvf:valueformat# - default non-text (number) value format number in sheet valueformat list + // tvf:valueformat# - default text value format number in sheet valueformat list + // color:color# - default number for text color in sheet color list + // bgcolor:color# - default number for background color in sheet color list + // circularreferencecell:coord - cell coord with a circular reference + // recalc:value - on/off (on is default). If not "off", appropriate changes to the sheet cause a recalc + // needsrecalc:value - yes/no (no is default). If "yes", formula values are not up to date + // + // name:name:description:value - name definition, name in uppercase, with value being "B5", "A1:B7", or "=formula"; + // description and value are encoded. + // font:fontnum:value - text of font definition (style weight size family) for font fontnum + // "*" for "style weight", size, or family, means use default (first look to sheet, then builtin) + // color:colornum:rgbvalue - text of color definition (e.g., rgb(255,255,255)) for color colornum + // border:bordernum:value - text of border definition (thickness style color) for border bordernum + // layout:layoutnum:value - text of vertical alignment and padding style for cell layout layoutnum (* for default): + // vertical-alignment:vavalue;padding:topval rightval bottomval leftval; + // cellformat:cformatnum:value - text of cell alignment (left/center/right) for cellformat cformatnum + // valueformat:vformatnum:value - text of number format (see FormatValueForDisplay) for valueformat vformatnum (changed in v1.2) + // clipboardrange:upperleftcoord:bottomrightcoord - ignored -- from wikiCalc + // clipboard:coord:type:value:... - ignored -- from wikiCalc + // + // If this is clipboard contents, then there is also information to facilitate pasting: + // + // copiedfrom:upperleftcoord:bottomrightcoord - range from which this was copied + // + + // Functions: + + SocialCalc.ParseSheetSave = function (savedsheet, sheetobj) { + var lines = savedsheet.split(/\r\n|\n/); + var parts = []; + var line; + var i, j, t, v, coord, cell, attribs, name; + var scc = SocialCalc.Constants; + + for (i = 0; i < lines.length; i++) { + line = lines[i]; + parts = line.split(":"); + switch (parts[0]) { + case "cell": + cell = sheetobj.GetAssuredCell(parts[1]); + j = 2; + sheetobj.CellFromStringParts(cell, parts, j); + break; + + case "col": + coord = parts[1]; + j = 2; + while ((t = parts[j++])) { + switch (t) { + case "w": + sheetobj.colattribs.width[coord] = parts[j++]; // must be text - could be auto or %, etc. + break; + case "hide": + sheetobj.colattribs.hide[coord] = parts[j++]; + break; + default: + throw scc.s_pssUnknownColType + " '" + t + "'"; + break; + } + } + break; + + case "row": + coord = parts[1] - 0; + j = 2; + while ((t = parts[j++])) { + switch (t) { + case "h": + sheetobj.rowattribs.height[coord] = parts[j++] - 0; + break; + case "hide": + sheetobj.rowattribs.hide[coord] = parts[j++]; + break; + default: + throw scc.s_pssUnknownRowType + " '" + t + "'"; + break; + } + } + break; + + case "sheet": + attribs = sheetobj.attribs; + j = 1; + while ((t = parts[j++])) { + switch (t) { + case "c": + attribs.lastcol = parts[j++] - 0; + break; + case "r": + attribs.lastrow = parts[j++] - 0; + break; + case "w": + attribs.defaultcolwidth = parts[j++] + ""; + break; + case "h": + attribs.defaultrowheight = parts[j++] - 0; + break; + case "tf": + attribs.defaulttextformat = parts[j++] - 0; + break; + case "ntf": + attribs.defaultnontextformat = parts[j++] - 0; + break; + case "layout": + attribs.defaultlayout = parts[j++] - 0; + break; + case "font": + attribs.defaultfont = parts[j++] - 0; + break; + case "tvf": + attribs.defaulttextvalueformat = parts[j++] - 0; + break; + case "ntvf": + attribs.defaultnontextvalueformat = parts[j++] - 0; + break; + case "color": + attribs.defaultcolor = parts[j++] - 0; + break; + case "bgcolor": + attribs.defaultbgcolor = parts[j++] - 0; + break; + case "circularreferencecell": + attribs.circularreferencecell = parts[j++]; + break; + case "recalc": + attribs.recalc = parts[j++]; + break; + case "needsrecalc": + attribs.needsrecalc = parts[j++]; + break; + default: + j += 1; + break; + } + } + break; + + case "name": + name = SocialCalc.decodeFromSave(parts[1]).toUpperCase(); + sheetobj.names[name] = { desc: SocialCalc.decodeFromSave(parts[2]) }; + sheetobj.names[name].definition = SocialCalc.decodeFromSave(parts[3]); + break; + + case "layout": + parts = lines[i].match(/^layout\:(\d+)\:(.+)$/); // layouts can have ":" in them + sheetobj.layouts[parts[1] - 0] = parts[2]; + sheetobj.layouthash[parts[2]] = parts[1] - 0; + break; + + case "font": + sheetobj.fonts[parts[1] - 0] = parts[2]; + sheetobj.fonthash[parts[2]] = parts[1] - 0; + break; + + case "color": + sheetobj.colors[parts[1] - 0] = parts[2]; + sheetobj.colorhash[parts[2]] = parts[1] - 0; + break; + + case "border": + sheetobj.borderstyles[parts[1] - 0] = parts[2]; + sheetobj.borderstylehash[parts[2]] = parts[1] - 0; + break; + + case "cellformat": + v = SocialCalc.decodeFromSave(parts[2]); + sheetobj.cellformats[parts[1] - 0] = v; + sheetobj.cellformathash[v] = parts[1] - 0; + break; + + case "valueformat": + v = SocialCalc.decodeFromSave(parts[2]); + sheetobj.valueformats[parts[1] - 0] = v; + sheetobj.valueformathash[v] = parts[1] - 0; + break; + + case "version": + break; + + case "copiedfrom": + sheetobj.copiedfrom = parts[1] + ":" + parts[2]; + break; + + case "clipboardrange": // in save versions up to 1.3. Ignored. + case "clipboard": + break; + + case "": + break; + + default: + alert(scc.s_pssUnknownLineType + " '" + parts[0] + "'"); + throw scc.s_pssUnknownLineType + " '" + parts[0] + "'"; + break; + } + parts = null; + } + }; + + // + // SocialCalc.CellFromStringParts(sheet, cell, parts, j) + // + // Takes string that has been split by ":" in parts, starting at item j, + // and fills in cell assuming save format. + // + + SocialCalc.CellFromStringParts = function (sheet, cell, parts, j) { + var cell, t, v; + + while ((t = parts[j++])) { + switch (t) { + case "v": + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]) - 0; + cell.datatype = "v"; + cell.valuetype = "n"; + break; + case "t": + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]); + cell.datatype = "t"; + cell.valuetype = SocialCalc.Constants.textdatadefaulttype; + break; + case "vt": + v = parts[j++]; + cell.valuetype = v; + if (v.charAt(0) == "n") { + cell.datatype = "v"; + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]) - 0; + } else { + cell.datatype = "t"; + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]); + } + break; + case "vtf": + v = parts[j++]; + cell.valuetype = v; + if (v.charAt(0) == "n") { + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]) - 0; + } else { + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]); + } + cell.formula = SocialCalc.decodeFromSave(parts[j++]); + cell.datatype = "f"; + break; + case "vtc": + v = parts[j++]; + cell.valuetype = v; + if (v.charAt(0) == "n") { + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]) - 0; + } else { + cell.datavalue = SocialCalc.decodeFromSave(parts[j++]); + } + cell.formula = SocialCalc.decodeFromSave(parts[j++]); + cell.datatype = "c"; + break; + case "e": + cell.errors = SocialCalc.decodeFromSave(parts[j++]); + break; + case "b": + cell.bt = parts[j++] - 0; + cell.br = parts[j++] - 0; + cell.bb = parts[j++] - 0; + cell.bl = parts[j++] - 0; + break; + case "l": + cell.layout = parts[j++] - 0; + break; + case "f": + cell.font = parts[j++] - 0; + break; + case "c": + cell.color = parts[j++] - 0; + break; + case "bg": + cell.bgcolor = parts[j++] - 0; + break; + case "cf": + cell.cellformat = parts[j++] - 0; + break; + case "ntvf": + cell.nontextvalueformat = parts[j++] - 0; + break; + case "tvf": + cell.textvalueformat = parts[j++] - 0; + break; + case "colspan": + cell.colspan = parts[j++] - 0; + break; + case "rowspan": + cell.rowspan = parts[j++] - 0; + break; + case "cssc": + cell.cssc = parts[j++]; + break; + case "csss": + cell.csss = SocialCalc.decodeFromSave(parts[j++]); + break; + case "mod": + j += 1; + break; + case "comment": + cell.comment = SocialCalc.decodeFromSave(parts[j++]); + break; + default: + throw SocialCalc.Constants.s_cfspUnknownCellType + " '" + t + "'"; + break; + } + } + }; + + SocialCalc.sheetfields = [ + "defaultrowheight", + "defaultcolwidth", + "circularreferencecell", + "recalc", + "needsrecalc", + ]; + SocialCalc.sheetfieldsshort = [ + "h", + "w", + "circularreferencecell", + "recalc", + "needsrecalc", + ]; + + SocialCalc.sheetfieldsxlat = [ + "defaulttextformat", + "defaultnontextformat", + "defaulttextvalueformat", + "defaultnontextvalueformat", + "defaultcolor", + "defaultbgcolor", + "defaultfont", + "defaultlayout", + ]; + SocialCalc.sheetfieldsxlatshort = [ + "tf", + "ntf", + "tvf", + "ntvf", + "color", + "bgcolor", + "font", + "layout", + ]; + SocialCalc.sheetfieldsxlatxlt = [ + "cellformat", + "cellformat", + "valueformat", + "valueformat", + "color", + "color", + "font", + "layout", + ]; + + // + // sheetstr = SocialCalc.CreateSheetSave(sheetobj, range, canonicalize) + // + // Creates a text representation of the sheetobj data. + // If the range is present then only those cells are saved + // (as clipboard data with "copiedfrom" set). + // + + SocialCalc.CreateSheetSave = function (sheetobj, range, canonicalize) { + var cell, + cr1, + cr2, + row, + col, + coord, + attrib, + line, + value, + formula, + i, + t, + r, + b, + l, + name, + blanklen; + var result = []; + + var prange; + + sheetobj.CanonicalizeSheet( + canonicalize || SocialCalc.Constants.doCanonicalizeSheet + ); + var xlt = sheetobj.xlt; + + if (range) { + prange = SocialCalc.ParseRange(range); + } else { + prange = { + cr1: { row: 1, col: 1 }, + cr2: { row: xlt.maxrow, col: xlt.maxcol }, + }; + } + cr1 = prange.cr1; + cr2 = prange.cr2; + + result.push("version:1.5"); + + for (row = cr1.row; row <= cr2.row; row++) { + for (col = cr1.col; col <= cr2.col; col++) { + coord = SocialCalc.crToCoord(col, row); + cell = sheetobj.cells[coord]; + if (!cell) continue; + line = sheetobj.CellToString(cell); + if (line.length == 0) continue; // ignore completely empty cells + line = "cell:" + coord + line; + result.push(line); + } + } + + for (col = 1; col <= xlt.maxcol; col++) { + coord = SocialCalc.rcColname(col); + if (sheetobj.colattribs.width[coord]) + result.push("col:" + coord + ":w:" + sheetobj.colattribs.width[coord]); + if (sheetobj.colattribs.hide[coord]) + result.push( + "col:" + coord + ":hide:" + sheetobj.colattribs.hide[coord] + ); + } + + for (row = 1; row <= xlt.maxrow; row++) { + if (sheetobj.rowattribs.height[row]) + result.push("row:" + row + ":h:" + sheetobj.rowattribs.height[row]); + if (sheetobj.rowattribs.hide[row]) + result.push("row:" + row + ":hide:" + sheetobj.rowattribs.hide[row]); + } + + line = "sheet:c:" + xlt.maxcol + ":r:" + xlt.maxrow; + + for (i = 0; i < SocialCalc.sheetfields.length; i++) { + // non-xlated values + value = SocialCalc.encodeForSave( + sheetobj.attribs[SocialCalc.sheetfields[i]] + ); + if (value) line += ":" + SocialCalc.sheetfieldsshort[i] + ":" + value; + } + for (i = 0; i < SocialCalc.sheetfieldsxlat.length; i++) { + // xlated values + value = sheetobj.attribs[SocialCalc.sheetfieldsxlat[i]]; + if (value) + line += + ":" + + SocialCalc.sheetfieldsxlatshort[i] + + ":" + + xlt[SocialCalc.sheetfieldsxlatxlt[i] + "sxlat"][value]; + } + + result.push(line); + + for (i = 1; i < xlt.newborderstyles.length; i++) { + result.push("border:" + i + ":" + xlt.newborderstyles[i]); + } + + for (i = 1; i < xlt.newcellformats.length; i++) { + result.push( + "cellformat:" + + i + + ":" + + SocialCalc.encodeForSave(xlt.newcellformats[i]) + ); + } + + for (i = 1; i < xlt.newcolors.length; i++) { + result.push("color:" + i + ":" + xlt.newcolors[i]); + } + + for (i = 1; i < xlt.newfonts.length; i++) { + result.push("font:" + i + ":" + xlt.newfonts[i]); + } + + for (i = 1; i < xlt.newlayouts.length; i++) { + result.push("layout:" + i + ":" + xlt.newlayouts[i]); + } + + for (i = 1; i < xlt.newvalueformats.length; i++) { + result.push( + "valueformat:" + + i + + ":" + + SocialCalc.encodeForSave(xlt.newvalueformats[i]) + ); + } + + for (i = 0; i < xlt.namesorder.length; i++) { + name = xlt.namesorder[i]; + result.push( + "name:" + + SocialCalc.encodeForSave(name).toUpperCase() + + ":" + + SocialCalc.encodeForSave(sheetobj.names[name].desc) + + ":" + + SocialCalc.encodeForSave(sheetobj.names[name].definition) + ); + } + + if (range) { + result.push( + "copiedfrom:" + + SocialCalc.crToCoord(cr1.col, cr1.row) + + ":" + + SocialCalc.crToCoord(cr2.col, cr2.row) + ); + } + + result.push(""); // one extra to get extra \n + + delete sheetobj.xlt; // clean up + + return result.join("\n"); + }; + + // + // line = SocialCalc.CellToString(sheet, cell) + // + + SocialCalc.CellToString = function (sheet, cell) { + var cell, line, value, formula, t, r, b, l, xlt; + + line = ""; + + if (!cell) return line; + + value = SocialCalc.encodeForSave(cell.datavalue); + if (cell.datatype == "v") { + if (cell.valuetype == "n") line += ":v:" + value; + else line += ":vt:" + cell.valuetype + ":" + value; + } else if (cell.datatype == "t") { + if (cell.valuetype == SocialCalc.Constants.textdatadefaulttype) + line += ":t:" + value; + else line += ":vt:" + cell.valuetype + ":" + value; + } else { + formula = SocialCalc.encodeForSave(cell.formula); + if (cell.datatype == "f") { + line += ":vtf:" + cell.valuetype + ":" + value + ":" + formula; + } else if (cell.datatype == "c") { + line += ":vtc:" + cell.valuetype + ":" + value + ":" + formula; + } + } + if (cell.errors) { + line += ":e:" + SocialCalc.encodeForSave(cell.errors); + } + t = cell.bt || ""; + r = cell.br || ""; + b = cell.bb || ""; + l = cell.bl || ""; + + if (sheet.xlt) { + // if have canonical save info + xlt = sheet.xlt; + if (t || r || b || l) + line += + ":b:" + + xlt.borderstylesxlat[t || 0] + + ":" + + xlt.borderstylesxlat[r || 0] + + ":" + + xlt.borderstylesxlat[b || 0] + + ":" + + xlt.borderstylesxlat[l || 0]; + if (cell.layout) line += ":l:" + xlt.layoutsxlat[cell.layout]; + if (cell.font) line += ":f:" + xlt.fontsxlat[cell.font]; + if (cell.color) line += ":c:" + xlt.colorsxlat[cell.color]; + if (cell.bgcolor) line += ":bg:" + xlt.colorsxlat[cell.bgcolor]; + if (cell.cellformat) + line += ":cf:" + xlt.cellformatsxlat[cell.cellformat]; + if (cell.textvalueformat) + line += ":tvf:" + xlt.valueformatsxlat[cell.textvalueformat]; + if (cell.nontextvalueformat) + line += ":ntvf:" + xlt.valueformatsxlat[cell.nontextvalueformat]; + } else { + if (t || r || b || l) line += ":b:" + t + ":" + r + ":" + b + ":" + l; + if (cell.layout) line += ":l:" + cell.layout; + if (cell.font) line += ":f:" + cell.font; + if (cell.color) line += ":c:" + cell.color; + if (cell.bgcolor) line += ":bg:" + cell.bgcolor; + if (cell.cellformat) line += ":cf:" + cell.cellformat; + if (cell.textvalueformat) line += ":tvf:" + cell.textvalueformat; + if (cell.nontextvalueformat) line += ":ntvf:" + cell.nontextvalueformat; + } + if (cell.colspan) line += ":colspan:" + cell.colspan; + if (cell.rowspan) line += ":rowspan:" + cell.rowspan; + if (cell.cssc) line += ":cssc:" + cell.cssc; + if (cell.csss) line += ":csss:" + SocialCalc.encodeForSave(cell.csss); + if (cell.mod) line += ":mod:" + cell.mod; + if (cell.comment) + line += ":comment:" + SocialCalc.encodeForSave(cell.comment); + + return line; + }; + + // + // SocialCalc.CanonicalizeSheet(sheetobj, full) + // + // Goes through the sheet and fills in sheetobj.xlt with the following: + // + // .maxrow, .maxcol - lastrow and lastcol are as small as possible + // .newlayouts - new version of sheetobj.layouts without unused ones and all in ascending order + // .layoutsxlat - maps old layouts index to new one + // same ".new" and ".xlat" for fonts, colors, borderstyles, cell and value formats + // .namesorder - array with names sorted + // + // If full or SocialCalc.Constants.doCanonicalizeSheet are not true, then the values will leave things unchanged (to save time, etc.) + // + // sheetobj.xlt should be deleted when you are finished using it + // + + SocialCalc.CanonicalizeSheet = function (sheetobj, full) { + var l, coord, cr, cell, filled, an, a, newa, newxlat, used, ahash, i, v; + var maxrow = 0; + var maxcol = 0; + var alist = [ + "borderstyle", + "cellformat", + "color", + "font", + "layout", + "valueformat", + ]; + + var xlt = {}; + + xlt.namesorder = []; // always return a sorted list + for (a in sheetobj.names) { + xlt.namesorder.push(a); + } + xlt.namesorder.sort(); + + if (!SocialCalc.Constants.doCanonicalizeSheet || !full) { + // return make-no-changes values if not wanted + for (an = 0; an < alist.length; an++) { + a = alist[an]; + xlt["new" + a + "s"] = sheetobj[a + "s"]; + l = sheetobj[a + "s"].length; + newxlat = new Array(l); + newxlat[0] = ""; + for (i = 1; i < l; i++) { + newxlat[i] = i; + } + xlt[a + "sxlat"] = newxlat; + } + + xlt.maxrow = sheetobj.attribs.lastrow; + xlt.maxcol = sheetobj.attribs.lastcol; + + sheetobj.xlt = xlt; + + return; + } + + for (an = 0; an < alist.length; an++) { + a = alist[an]; + xlt[a + "sUsed"] = {}; + } + + var colorsUsed = xlt.colorsUsed; + var borderstylesUsed = xlt.borderstylesUsed; + var fontsUsed = xlt.fontsUsed; + var layoutsUsed = xlt.layoutsUsed; + var cellformatsUsed = xlt.cellformatsUsed; + var valueformatsUsed = xlt.valueformatsUsed; + + for (coord in sheetobj.cells) { + // check all cells to see which values are used + cr = SocialCalc.coordToCr(coord); + cell = sheetobj.cells[coord]; + filled = false; + + if (cell.valuetype && cell.valuetype != "b") filled = true; + + if (cell.color) { + colorsUsed[cell.color] = 1; + filled = true; + } + + if (cell.bgcolor) { + colorsUsed[cell.bgcolor] = 1; + filled = true; + } + + if (cell.bt) { + borderstylesUsed[cell.bt] = 1; + filled = true; + } + if (cell.br) { + borderstylesUsed[cell.br] = 1; + filled = true; + } + if (cell.bb) { + borderstylesUsed[cell.bb] = 1; + filled = true; + } + if (cell.bl) { + borderstylesUsed[cell.bl] = 1; + filled = true; + } + + if (cell.layout) { + layoutsUsed[cell.layout] = 1; + filled = true; + } + + if (cell.font) { + fontsUsed[cell.font] = 1; + filled = true; + } + + if (cell.cellformat) { + cellformatsUsed[cell.cellformat] = 1; + filled = true; + } + + if (cell.textvalueformat) { + valueformatsUsed[cell.textvalueformat] = 1; + filled = true; + } + + if (cell.nontextvalueformat) { + valueformatsUsed[cell.nontextvalueformat] = 1; + filled = true; + } + + if (filled) { + if (cr.row > maxrow) maxrow = cr.row; + if (cr.col > maxcol) maxcol = cr.col; + } + } + + for (i = 0; i < SocialCalc.sheetfieldsxlat.length; i++) { + // do sheet values, too + v = sheetobj.attribs[SocialCalc.sheetfieldsxlat[i]]; + if (v) { + xlt[SocialCalc.sheetfieldsxlatxlt[i] + "sUsed"][v] = 1; + } + } + + a = { height: 1, hide: 1 }; // look at explicit row settings + for (v in a) { + for (cr in sheetobj.rowattribs[v]) { + if (cr > maxrow) maxrow = cr; + } + } + a = { hide: 1, width: 1 }; // look at explicit col settings + for (v in a) { + for (coord in sheetobj.colattribs[v]) { + cr = SocialCalc.coordToCr(coord + "1"); + if (cr.col > maxcol) maxcol = cr.col; + } + } + + for (an = 0; an < alist.length; an++) { + // go through the attribs we want + a = alist[an]; + + newa = []; + used = xlt[a + "sUsed"]; + for (v in used) { + newa.push(sheetobj[a + "s"][v]); + } + newa.sort(); + newa.unshift(""); + + newxlat = [""]; + ahash = sheetobj[a + "hash"]; + + for (i = 1; i < newa.length; i++) { + newxlat[ahash[newa[i]]] = i; + } + + xlt[a + "sxlat"] = newxlat; + xlt["new" + a + "s"] = newa; + } + + xlt.maxrow = maxrow || 1; + xlt.maxcol = maxcol || 1; + + sheetobj.xlt = xlt; // leave for use by caller + }; + + // + // result = SocialCalc.EncodeCellAttributes(sheet, coord) + // + // Returns the cell's attributes in an object, each in the following form: + // + // attribname: {def: true/false, val: full-value} + // + + SocialCalc.EncodeCellAttributes = function (sheet, coord) { + var value, i, b, bb, parts; + var result = {}; + + var InitAttrib = function (name) { + result[name] = { def: true, val: "" }; + }; + + var InitAttribs = function (namelist) { + for (var i = 0; i < namelist.length; i++) { + InitAttrib(namelist[i]); + } + }; + + var SetAttrib = function (name, v) { + result[name].def = false; + result[name].val = v || ""; + }; + + var SetAttribStar = function (name, v) { + if (v == "*") return; + result[name].def = false; + result[name].val = v; + }; + + var cell = sheet.GetAssuredCell(coord); + + // cellformat: alignhoriz + + InitAttrib("alignhoriz"); + if (cell.cellformat) { + SetAttrib("alignhoriz", sheet.cellformats[cell.cellformat]); + } + + // layout: alignvert, padtop, padright, padbottom, padleft + + InitAttribs(["alignvert", "padtop", "padright", "padbottom", "padleft"]); + if (cell.layout) { + parts = sheet.layouts[cell.layout].match( + /^padding:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+);vertical-align:\s*(\S+);/ + ); + SetAttribStar("padtop", parts[1]); + SetAttribStar("padright", parts[2]); + SetAttribStar("padbottom", parts[3]); + SetAttribStar("padleft", parts[4]); + SetAttribStar("alignvert", parts[5]); + } + + // font: fontfamily, fontlook, fontsize + + InitAttribs(["fontfamily", "fontlook", "fontsize"]); + if (cell.font) { + parts = sheet.fonts[cell.font].match(/^(\*|\S+? \S+?) (\S+?) (\S.*)$/); + SetAttribStar("fontfamily", parts[3]); + SetAttribStar("fontsize", parts[2]); + SetAttribStar("fontlook", parts[1]); + } + + // color: textcolor + + InitAttrib("textcolor"); + if (cell.color) { + SetAttrib("textcolor", sheet.colors[cell.color]); + } + + // bgcolor: bgcolor + + InitAttrib("bgcolor"); + if (cell.bgcolor) { + SetAttrib("bgcolor", sheet.colors[cell.bgcolor]); + } + + // formatting: numberformat, textformat + + InitAttribs(["numberformat", "textformat"]); + if (cell.nontextvalueformat) { + SetAttrib("numberformat", sheet.valueformats[cell.nontextvalueformat]); + } + if (cell.textvalueformat) { + SetAttrib("textformat", sheet.valueformats[cell.textvalueformat]); + } + + // merges: colspan, rowspan + + InitAttribs(["colspan", "rowspan"]); + SetAttrib("colspan", cell.colspan || 1); + SetAttrib("rowspan", cell.rowspan || 1); + + // borders: bXthickness, bXstyle, bXcolor for X = t, r, b, and l + + for (i = 0; i < 4; i++) { + b = "trbl".charAt(i); + bb = "b" + b; + InitAttrib(bb); + SetAttrib(bb, cell[bb] ? sheet.borderstyles[cell[bb]] : ""); + InitAttrib(bb + "thickness"); + InitAttrib(bb + "style"); + InitAttrib(bb + "color"); + if (cell[bb]) { + parts = sheet.borderstyles[cell[bb]].match(/(\S+)\s+(\S+)\s+(\S.+)/); + SetAttrib(bb + "thickness", parts[1]); + SetAttrib(bb + "style", parts[2]); + SetAttrib(bb + "color", parts[3]); + } + } + + // misc: cssc, csss, mod + + InitAttribs(["cssc", "csss", "mod"]); + SetAttrib("cssc", cell.cssc || ""); + SetAttrib("csss", cell.csss || ""); + SetAttrib("mod", cell.mod || "n"); + + return result; + }; + + // + // result = SocialCalc.EncodeSheetAttributes(sheet) + // + // Returns the sheet's attributes in an object, each in the following form: + // + // attribname: {def: true/false, val: full-value} + // + + SocialCalc.EncodeSheetAttributes = function (sheet) { + var value; + var attribs = sheet.attribs; + var result = {}; + + var InitAttrib = function (name) { + result[name] = { def: true, val: "" }; + }; + + var InitAttribs = function (namelist) { + for (var i = 0; i < namelist.length; i++) { + InitAttrib(namelist[i]); + } + }; + + var SetAttrib = function (name, v) { + result[name].def = false; + result[name].val = v || value; + }; + + var SetAttribStar = function (name, v) { + if (v == "*") return; + result[name].def = false; + result[name].val = v; + }; + + // sizes: colwidth, rowheight + + InitAttrib("colwidth"); + if (attribs.defaultcolwidth) { + SetAttrib("colwidth", attribs.defaultcolwidth); + } + + InitAttrib("rowheight"); + if (attribs.rowheight) { + SetAttrib("rowheight", attribs.defaultrowheight); + } + + // cellformat: textalignhoriz, numberalignhoriz + + InitAttrib("textalignhoriz"); + if (attribs.defaulttextformat) { + SetAttrib("textalignhoriz", sheet.cellformats[attribs.defaulttextformat]); + } + + InitAttrib("numberalignhoriz"); + if (attribs.defaultnontextformat) { + SetAttrib( + "numberalignhoriz", + sheet.cellformats[attribs.defaultnontextformat] + ); + } + + // layout: alignvert, padtop, padright, padbottom, padleft + + InitAttribs(["alignvert", "padtop", "padright", "padbottom", "padleft"]); + if (attribs.defaultlayout) { + parts = sheet.layouts[attribs.defaultlayout].match( + /^padding:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+);vertical-align:\s*(\S+);/ + ); + SetAttribStar("padtop", parts[1]); + SetAttribStar("padright", parts[2]); + SetAttribStar("padbottom", parts[3]); + SetAttribStar("padleft", parts[4]); + SetAttribStar("alignvert", parts[5]); + } + + // font: fontfamily, fontlook, fontsize + + InitAttribs(["fontfamily", "fontlook", "fontsize"]); + if (attribs.defaultfont) { + parts = sheet.fonts[attribs.defaultfont].match( + /^(\*|\S+? \S+?) (\S+?) (\S.*)$/ + ); + SetAttribStar("fontfamily", parts[3]); + SetAttribStar("fontsize", parts[2]); + SetAttribStar("fontlook", parts[1]); + } + + // color: textcolor + + InitAttrib("textcolor"); + if (attribs.defaultcolor) { + SetAttrib("textcolor", sheet.colors[attribs.defaultcolor]); + } + + // bgcolor: bgcolor + + InitAttrib("bgcolor"); + if (attribs.defaultbgcolor) { + SetAttrib("bgcolor", sheet.colors[attribs.defaultbgcolor]); + } + + // formatting: numberformat, textformat + + InitAttribs(["numberformat", "textformat"]); + if (attribs.defaultnontextvalueformat) { + SetAttrib( + "numberformat", + sheet.valueformats[attribs.defaultnontextvalueformat] + ); + } + if (attribs.defaulttextvalueformat) { + SetAttrib( + "textformat", + sheet.valueformats[attribs.defaulttextvalueformat] + ); + } + + // recalc: recalc + + InitAttrib("recalc"); + if (attribs.recalc) { + SetAttrib("recalc", attribs.recalc); + } + + return result; + }; + + // + // cmdstr = SocialCalc.DecodeCellAttributes(sheet, coord, attribs, range) + // + // Takes cell attributes in an object, each in the following form: + // + // attribname: {def: true/false, val: full-value} + // + // and returns the sheet commands to make the actual attributes correspond. + // Returns a non-null string if any commands are to be executed, null otherwise. + // + // If range is provided, the commands are executed on the whole range. + // + + SocialCalc.DecodeCellAttributes = function (sheet, coord, newattribs, range) { + var value, b, bb; + + var cell = sheet.GetAssuredCell(coord); + + var changed = false; + + var CheckChanges = function (attribname, oldval, cmdname) { + var val; + if (newattribs[attribname]) { + if (newattribs[attribname].def) { + val = ""; + } else { + val = newattribs[attribname].val; + } + if (val != (oldval || "")) { + DoCmd(cmdname + " " + val); + } + } + }; + + var cmdstr = ""; + + var DoCmd = function (str) { + if (cmdstr) cmdstr += "\n"; + cmdstr += "set " + (range || coord) + " " + str; + changed = true; + }; + + // cellformat: alignhoriz + + CheckChanges( + "alignhoriz", + sheet.cellformats[cell.cellformat], + "cellformat" + ); + + // layout: alignvert, padtop, padright, padbottom, padleft + + if ( + !newattribs.alignvert.def || + !newattribs.padtop.def || + !newattribs.padright.def || + !newattribs.padbottom.def || + !newattribs.padleft.def + ) { + value = + "padding:" + + (newattribs.padtop.def ? "* " : newattribs.padtop.val + " ") + + (newattribs.padright.def ? "* " : newattribs.padright.val + " ") + + (newattribs.padbottom.def ? "* " : newattribs.padbottom.val + " ") + + (newattribs.padleft.def ? "*" : newattribs.padleft.val) + + ";vertical-align:" + + (newattribs.alignvert.def ? "*;" : newattribs.alignvert.val + ";"); + } else { + value = ""; + } + + if (value != (sheet.layouts[cell.layout] || "")) { + DoCmd("layout " + value); + } + + // font: fontfamily, fontlook, fontsize + + if ( + !newattribs.fontlook.def || + !newattribs.fontsize.def || + !newattribs.fontfamily.def + ) { + value = + (newattribs.fontlook.def ? "* " : newattribs.fontlook.val + " ") + + (newattribs.fontsize.def ? "* " : newattribs.fontsize.val + " ") + + (newattribs.fontfamily.def ? "*" : newattribs.fontfamily.val); + } else { + value = ""; + } + + if (value != (sheet.fonts[cell.font] || "")) { + DoCmd("font " + value); + } + + // color: textcolor + + CheckChanges("textcolor", sheet.colors[cell.color], "color"); + + // bgcolor: bgcolor + + CheckChanges("bgcolor", sheet.colors[cell.bgcolor], "bgcolor"); + + // formatting: numberformat, textformat + + CheckChanges( + "numberformat", + sheet.valueformats[cell.nontextvalueformat], + "nontextvalueformat" + ); + + CheckChanges( + "textformat", + sheet.valueformats[cell.textvalueformat], + "textvalueformat" + ); + + // merges: colspan, rowspan - NOT HANDLED: IGNORED! + + // borders: bX for X = t, r, b, and l; bXthickness, bXstyle, bXcolor ignored + + for (var i = 0; i < 4; i++) { + b = "trbl".charAt(i); + bb = "b" + b; + CheckChanges(bb, sheet.borderstyles[cell[bb]], bb); + } + + // misc: cssc, csss, mod + + CheckChanges("cssc", cell.cssc, "cssc"); + + CheckChanges("csss", cell.csss, "csss"); + + if (newattribs.mod) { + if (newattribs.mod.def) { + value = "n"; + } else { + value = newattribs.mod.val; + } + if (value != (cell.mod || "n")) { + if (value == "n") value = ""; // restrict to "y" and "" normally + DoCmd("mod " + value); + } + } + + // if any changes return command(s) + + if (changed) { + return cmdstr; + } else { + return null; + } + }; + + // + // changed = SocialCalc.DecodeSheetAttributes(sheet, newattribs) + // + // Takes sheet attributes in an object, each in the following form: + // + // attribname: {def: true/false, val: full-value} + // + // and returns the sheet commands to make the actual attributes correspond. + // Returns a non-null string if any commands were executed, null otherwise. + // + + SocialCalc.DecodeSheetAttributes = function (sheet, newattribs) { + var value; + var attribs = sheet.attribs; + var changed = false; + + var CheckChanges = function (attribname, oldval, cmdname) { + var val; + if (newattribs[attribname]) { + if (newattribs[attribname].def) { + val = ""; + } else { + val = newattribs[attribname].val; + } + if (val != (oldval || "")) { + DoCmd(cmdname + " " + val); + } + } + }; + + var cmdstr = ""; + + var DoCmd = function (str) { + if (cmdstr) cmdstr += "\n"; + cmdstr += "set sheet " + str; + changed = true; + }; + + // sizes: colwidth, rowheight + + CheckChanges("colwidth", attribs.defaultcolwidth, "defaultcolwidth"); + + CheckChanges("rowheight", attribs.defaultrowheight, "defaultrowheight"); + + // cellformat: textalignhoriz, numberalignhoriz + + CheckChanges( + "textalignhoriz", + sheet.cellformats[attribs.defaulttextformat], + "defaulttextformat" + ); + + CheckChanges( + "numberalignhoriz", + sheet.cellformats[attribs.defaultnontextformat], + "defaultnontextformat" + ); + + // layout: alignvert, padtop, padright, padbottom, padleft + + if ( + !newattribs.alignvert.def || + !newattribs.padtop.def || + !newattribs.padright.def || + !newattribs.padbottom.def || + !newattribs.padleft.def + ) { + value = + "padding:" + + (newattribs.padtop.def ? "* " : newattribs.padtop.val + " ") + + (newattribs.padright.def ? "* " : newattribs.padright.val + " ") + + (newattribs.padbottom.def ? "* " : newattribs.padbottom.val + " ") + + (newattribs.padleft.def ? "*" : newattribs.padleft.val) + + ";vertical-align:" + + (newattribs.alignvert.def ? "*;" : newattribs.alignvert.val + ";"); + } else { + value = ""; + } + + if (value != (sheet.layouts[attribs.defaultlayout] || "")) { + DoCmd("defaultlayout " + value); + } + + // font: fontfamily, fontlook, fontsize + + if ( + !newattribs.fontlook.def || + !newattribs.fontsize.def || + !newattribs.fontfamily.def + ) { + value = + (newattribs.fontlook.def ? "* " : newattribs.fontlook.val + " ") + + (newattribs.fontsize.def ? "* " : newattribs.fontsize.val + " ") + + (newattribs.fontfamily.def ? "*" : newattribs.fontfamily.val); + } else { + value = ""; + } + + if (value != (sheet.fonts[attribs.defaultfont] || "")) { + DoCmd("defaultfont " + value); + } + + // color: textcolor + + CheckChanges( + "textcolor", + sheet.colors[attribs.defaultcolor], + "defaultcolor" + ); + + // bgcolor: bgcolor + + CheckChanges( + "bgcolor", + sheet.colors[attribs.defaultbgcolor], + "defaultbgcolor" + ); + + // formatting: numberformat, textformat + + CheckChanges( + "numberformat", + sheet.valueformats[attribs.defaultnontextvalueformat], + "defaultnontextvalueformat" + ); + + CheckChanges( + "textformat", + sheet.valueformats[attribs.defaulttextvalueformat], + "defaulttextvalueformat" + ); + + // recalc: recalc + + CheckChanges("recalc", sheet.attribs.recalc, "recalc"); + + // if any changes return command(s) + + if (changed) { + return cmdstr; + } else { + return null; + } + }; + + // ************************************* + // + // Sheet command routines + // + // ************************************* + + // + // SocialCalc.SheetCommandInfo - object with information used during command execution + // + + SocialCalc.SheetCommandInfo = { + // only one of these + + sheetobj: null, // sheet being operated on + parseobj: null, // SocialCalc.Parse object with the command string, etc. + timerobj: null, // used for timeslicing + firsttimerdelay: 50, // wait before starting cmds (for Chrome - to give time to update) + timerdelay: 1, // wait between slices + maxtimeslice: 100, // do another slice after this many milliseconds + saveundo: false, // arg for ExecuteSheetCommand + + CmdExtensionCallbacks: {}, // for startcmdextension, in form: cmdname, {func:function(cmdname, data, sheet, SocialCalc.Parse object, saveundo), data:whatever} + cmdextensionbusy: "", // if length>0, command loop waits for SocialCalc.ResumeFromCmdExtension() + + // statuscallback: null, // called during execution - obsolete: use sheet obj's + // statuscallbackparams: null + }; + + // + // SocialCalc.ScheduleSheetCommands + // + // statuscallback is called at the beginning (cmdstart) and end (cmdend). + // + + SocialCalc.ScheduleSheetCommands = function ( + sheet, + cmdstr, + saveundo, + isRemote + ) { + if (SocialCalc.Callbacks.broadcast && !isRemote) { + if ( + cmdstr != "redisplay" && + cmdstr != "set sheet defaulttextvalueformat text-wiki" && + cmdstr != "recalc" + ) { + SocialCalc.Callbacks.broadcast("execute", { + cmdtype: "scmd", + id: sheet.sheetid, + cmdstr: cmdstr, + saveundo: saveundo, + }); + } + } + + var sci = SocialCalc.SheetCommandInfo; + + sci.sheetobj = sheet; + sci.parseobj = new SocialCalc.Parse(cmdstr); + sci.saveundo = saveundo; + + if (sci.sheetobj.statuscallback) { + // notify others if requested + sheet.statuscallback( + sci, + "cmdstart", + "", + sci.sheetobj.statuscallbackparams + ); + } + + if (sci.saveundo) { + sci.sheetobj.changes.PushChange(""); // add a step to undo stack + } + + sci.timerobj = window.setTimeout( + SocialCalc.SheetCommandsTimerRoutine, + sci.firsttimerdelay + ); + }; + + SocialCalc.SheetCommandsTimerRoutine = function () { + var errortext; + var sci = SocialCalc.SheetCommandInfo; + var starttime = new Date(); + + sci.timerobj = null; + + while (!sci.parseobj.EOF()) { + // go through all commands (separated by newlines) + + errortext = SocialCalc.ExecuteSheetCommand( + sci.sheetobj, + sci.parseobj, + sci.saveundo + ); + if (errortext) alert(errortext); + + sci.parseobj.NextLine(); + + if (sci.cmdextensionbusy.length > 0) { + // forced wait + if (sci.sheetobj.statuscallback) { + // notify others if requested + sci.sheetobj.statuscallback( + sci, + "cmdextension", + sci.cmdextensionbusy, + sci.sheetobj.statuscallbackparams + ); + } + return; + } + + if (new Date() - starttime >= sci.maxtimeslice) { + // if taking too long, give up CPU for a while + sci.timerobj = window.setTimeout( + SocialCalc.SheetCommandsTimerRoutine, + sci.timerdelay + ); + return; + } + } + + if (sci.sheetobj.statuscallback) { + // notify others if requested + sci.sheetobj.statuscallback( + sci, + "cmdend", + "", + sci.sheetobj.statuscallbackparams + ); + } + }; + + SocialCalc.ResumeFromCmdExtension = function () { + var sci = SocialCalc.SheetCommandInfo; + + sci.cmdextensionbusy = ""; + + SocialCalc.SheetCommandsTimerRoutine(); + }; + + // + // errortext = SocialCalc.ExecuteSheetCommand(sheet, cmd, saveundo) + // + // cmd is a SocialCalc.Parse object. + // + // Executes commands that modify the sheet data. + // Sets sheet "needsrecalc" as needed. + // Sets sheet "changedrendervalues" as needed. + // + // The cmd string may be multiple commands, separated by newlines. In that case + // only one "step" is put on the undo stack representing all the commands. + // Note that because of this, in "set A1 text ..." and "set A1 comment ..." text is + // treated as encoded (newline => \n, \ => \b, : => \c). + // + // The commands are in the forms: + // + // set sheet attributename value (plus lastcol and lastrow) + // set 22 attributename value + // set B attributename value + // set A1 attributename value1 value2... (see each attribute in code for details) + // set A1:B5 attributename value1 value2... + // erase/copy/cut/paste/fillright/filldown A1:B5 all/formulas/format + // loadclipboard save-encoded-clipboard-data + // clearclipboard + // merge C3:F3 + // unmerge C3 + // insertcol/insertrow C5 + // deletecol/deleterow C5:E7 + // movepaste/moveinsert A1:B5 A8 all/formulas/format (if insert, destination must be in same rows or columns or else paste done) + // sort cr1:cr2 col1 up/down col2 up/down col3 up/down + // name define NAME definition + // name desc NAME description + // name delete NAME + // recalc + // redisplay + // changedrendervalues + // startcmdextension extension rest-of-command + // + // If saveundo is true, then undo information is saved in sheet.changes. + // + + SocialCalc.ExecuteSheetCommand = function (sheet, cmd, saveundo) { + var cmdstr, + cmd1, + rest, + what, + attrib, + num, + pos, + pos2, + errortext, + undostart, + val; + var cr1, cr2, col, row, cr, cell, newcell; + var fillright, rowstart, colstart, crbase, rowoffset, coloffset, basecell; + var clipsheet, cliprange, numcols, numrows, attribtable; + var colend, + rowend, + newcolstart, + newrowstart, + newcolend, + newrowend, + rownext, + colnext, + colthis, + cellnext; + var lastrow, lastcol, rowbefore, colbefore, oldformula, oldcr; + var cols, dirs, lastsortcol, i, sortlist, sortcells, sortvalues, sorttypes; + var sortfunction, slen, valtype, originalrow, sortedcr; + var name, v1, v2; + var cmdextension; + + var attribs = sheet.attribs; + var changes = sheet.changes; + var cellProperties = SocialCalc.CellProperties; + var scc = SocialCalc.Constants; + + var ParseRange = function () { + var prange = SocialCalc.ParseRange(what); + cr1 = prange.cr1; + cr2 = prange.cr2; + if (cr2.col > attribs.lastcol) attribs.lastcol = cr2.col; + if (cr2.row > attribs.lastrow) attribs.lastrow = cr2.row; + }; + + errortext = ""; + + cmdstr = cmd.RestOfStringNoMove(); + if (saveundo) { + sheet.changes.AddDo(cmdstr); + } + + cmd1 = cmd.NextToken(); + + switch (cmd1) { + case "set": + what = cmd.NextToken(); + attrib = cmd.NextToken(); + rest = cmd.RestOfString(); + undostart = "set " + what + " " + attrib; + + if (what == "sheet") { + sheet.renderneeded = true; + switch (attrib) { + case "defaultcolwidth": + if (saveundo) changes.AddUndo(undostart, attribs[attrib]); + attribs[attrib] = rest; + break; + case "defaultcolor": + case "defaultbgcolor": + if (saveundo) + changes.AddUndo( + undostart, + sheet.GetStyleString("color", attribs[attrib]) + ); + attribs[attrib] = sheet.GetStyleNum("color", rest); + break; + case "defaultlayout": + if (saveundo) + changes.AddUndo( + undostart, + sheet.GetStyleString("layout", attribs[attrib]) + ); + attribs[attrib] = sheet.GetStyleNum("layout", rest); + break; + case "defaultfont": + if (saveundo) + changes.AddUndo( + undostart, + sheet.GetStyleString("font", attribs[attrib]) + ); + if (rest == "* * *") rest = ""; // all default + attribs[attrib] = sheet.GetStyleNum("font", rest); + break; + case "defaulttextformat": + case "defaultnontextformat": + if (saveundo) + changes.AddUndo( + undostart, + sheet.GetStyleString("cellformat", attribs[attrib]) + ); + attribs[attrib] = sheet.GetStyleNum("cellformat", rest); + break; + case "defaulttextvalueformat": + case "defaultnontextvalueformat": + if (saveundo) + changes.AddUndo( + undostart, + sheet.GetStyleString("valueformat", attribs[attrib]) + ); + attribs[attrib] = sheet.GetStyleNum("valueformat", rest); + for (cr in sheet.cells) { + // forget all cached display strings + delete sheet.cells[cr].displaystring; + } + break; + case "lastcol": + case "lastrow": + if (saveundo) changes.AddUndo(undostart, attribs[attrib] - 0); + num = rest - 0; + if (typeof num == "number") attribs[attrib] = num > 0 ? num : 1; + break; + case "recalc": + if (saveundo) changes.AddUndo(undostart, attribs[attrib]); + if (rest == "off") { + attribs.recalc = rest; // manual recalc, not auto + } else { + // all values other than "off" mean "on" + delete attribs.recalc; + } + break; + default: + errortext = scc.s_escUnknownSheetCmd + cmdstr; + break; + } + } else if (/(^[A-Z])([A-Z])?(:[A-Z][A-Z]?){0,1}$/i.test(what)) { + // col attributes + sheet.renderneeded = true; + what = what.toUpperCase(); + pos = what.indexOf(":"); + if (pos >= 0) { + cr1 = SocialCalc.coordToCr(what.substring(0, pos) + "1"); + cr2 = SocialCalc.coordToCr(what.substring(pos + 1) + "1"); + } else { + cr1 = SocialCalc.coordToCr(what + "1"); + cr2 = cr1; + } + for (col = cr1.col; col <= cr2.col; col++) { + if (attrib == "width") { + cr = SocialCalc.rcColname(col); + if (saveundo) + changes.AddUndo( + "set " + cr + " width", + sheet.colattribs.width[cr] + ); + if (rest.length > 0) { + sheet.colattribs.width[cr] = rest; + } else { + delete sheet.colattribs.width[cr]; + } + } + } + } + + // !!!!! need row attribs !!!! + else if (/([a-z]){0,1}(\d+)/i.test(what)) { + // cell attributes + ParseRange(); + if ( + cr1.row != cr2.row || + cr1.col != cr2.col || + sheet.celldisplayneeded || + sheet.renderneeded + ) { + // not one cell + sheet.renderneeded = true; + sheet.celldisplayneeded = ""; + } else { + sheet.celldisplayneeded = SocialCalc.crToCoord(cr1.col, cr1.row); + } + for (row = cr1.row; row <= cr2.row; row++) { + for (col = cr1.col; col <= cr2.col; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.GetAssuredCell(cr); + if (saveundo) + changes.AddUndo("set " + cr + " all", sheet.CellToString(cell)); + if (attrib == "value") { + // set coord value type numeric-value + pos = rest.indexOf(" "); + cell.datavalue = rest.substring(pos + 1) - 0; + delete cell.errors; + cell.datatype = "v"; + cell.valuetype = rest.substring(0, pos); + delete cell.displaystring; + delete cell.parseinfo; + attribs.needsrecalc = "yes"; + } else if (attrib == "text") { + // set coord text type text-value + pos = rest.indexOf(" "); + cell.datavalue = SocialCalc.decodeFromSave( + rest.substring(pos + 1) + ); + delete cell.errors; + cell.datatype = "t"; + cell.valuetype = rest.substring(0, pos); + delete cell.displaystring; + delete cell.parseinfo; + attribs.needsrecalc = "yes"; + } else if (attrib == "formula") { + // set coord formula formula-body-less-initial-= + cell.datavalue = 0; // until recalc + delete cell.errors; + cell.datatype = "f"; + cell.valuetype = "e#N/A"; // until recalc + cell.formula = rest; + delete cell.displaystring; + delete cell.parseinfo; + attribs.needsrecalc = "yes"; + } else if (attrib == "constant") { + // set coord constant type numeric-value source-text + pos = rest.indexOf(" "); + pos2 = rest.substring(pos + 1).indexOf(" "); + cell.datavalue = rest.substring(pos + 1, pos + 1 + pos2) - 0; + cell.valuetype = rest.substring(0, pos); + if (cell.valuetype.charAt(0) == "e") { + // error + cell.errors = cell.valuetype.substring(1); + } else { + delete cell.errors; + } + cell.datatype = "c"; + cell.formula = rest.substring(pos + pos2 + 2); + delete cell.displaystring; + delete cell.parseinfo; + attribs.needsrecalc = "yes"; + } else if (attrib == "empty") { + // erase value + cell.datavalue = ""; + delete cell.errors; + cell.datatype = null; + cell.formula = ""; + cell.valuetype = "b"; + delete cell.displaystring; + delete cell.parseinfo; + attribs.needsrecalc = "yes"; + } else if (attrib == "all") { + // set coord all :this:val1:that:val2... + if (rest.length > 0) { + cell = new SocialCalc.Cell(cr); + sheet.CellFromStringParts(cell, rest.split(":"), 1); + sheet.cells[cr] = cell; + } else { + delete sheet.cells[cr]; + } + attribs.needsrecalc = "yes"; + } else if (/^b[trbl]$/.test(attrib)) { + // set coord bt 1px solid black + cell[attrib] = sheet.GetStyleNum("borderstyle", rest); + sheet.renderneeded = true; // affects more than just one cell + } else if (attrib == "color" || attrib == "bgcolor") { + cell[attrib] = sheet.GetStyleNum("color", rest); + } else if (attrib == "layout" || attrib == "cellformat") { + cell[attrib] = sheet.GetStyleNum(attrib, rest); + } else if (attrib == "font") { + // set coord font style weight size family + if (rest == "* * *") rest = ""; + cell[attrib] = sheet.GetStyleNum("font", rest); + } else if ( + attrib == "textvalueformat" || + attrib == "nontextvalueformat" + ) { + cell[attrib] = sheet.GetStyleNum("valueformat", rest); + delete cell.displaystring; + } else if (attrib == "cssc") { + rest = rest.replace(/[^a-zA-Z0-9\-]/g, ""); + cell.cssc = rest; + } else if (attrib == "csss") { + rest = rest.replace(/\n/g, ""); + cell.csss = rest; + } else if (attrib == "mod") { + rest = rest.replace(/[^yY]/g, "").toLowerCase(); + cell.mod = rest; + } else if (attrib == "comment") { + cell.comment = SocialCalc.decodeFromSave(rest); + } else { + errortext = scc.s_escUnknownSetCoordCmd + cmdstr; + } + } + } + } + break; + + case "merge": + sheet.renderneeded = true; + what = cmd.NextToken(); + rest = cmd.RestOfString(); + ParseRange(); + cell = sheet.GetAssuredCell(cr1.coord); + if (saveundo) changes.AddUndo("unmerge " + cr1.coord); + + if (cr2.col > cr1.col) cell.colspan = cr2.col - cr1.col + 1; + else delete cell.colspan; + if (cr2.row > cr1.row) cell.rowspan = cr2.row - cr1.row + 1; + else delete cell.rowspan; + + sheet.changedrendervalues = true; + + break; + + case "unmerge": + sheet.renderneeded = true; + what = cmd.NextToken(); + rest = cmd.RestOfString(); + ParseRange(); + cell = sheet.GetAssuredCell(cr1.coord); + if (saveundo) + changes.AddUndo( + "merge " + + cr1.coord + + ":" + + SocialCalc.crToCoord( + cr1.col + (cell.colspan || 1) - 1, + cr1.row + (cell.rowspan || 1) - 1 + ) + ); + + delete cell.colspan; + delete cell.rowspan; + + sheet.changedrendervalues = true; + + break; + + case "erase": + case "cut": + sheet.renderneeded = true; + sheet.changedrendervalues = true; + what = cmd.NextToken(); + rest = cmd.RestOfString(); + ParseRange(); + + if (saveundo) changes.AddUndo("changedrendervalues"); // to take care of undone pasted spans + if (cmd1 == "cut") { + // save copy of whole thing before erasing + if (saveundo) + changes.AddUndo( + "loadclipboard", + SocialCalc.encodeForSave(SocialCalc.Clipboard.clipboard) + ); + SocialCalc.Clipboard.clipboard = SocialCalc.CreateSheetSave( + sheet, + what + ); + } + + for (row = cr1.row; row <= cr2.row; row++) { + for (col = cr1.col; col <= cr2.col; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.GetAssuredCell(cr); + if (saveundo) + changes.AddUndo("set " + cr + " all", sheet.CellToString(cell)); + if (rest == "all") { + delete sheet.cells[cr]; + } else if (rest == "formulas") { + cell.datavalue = ""; + cell.datatype = null; + cell.formula = ""; + cell.valuetype = "b"; + delete cell.errors; + delete cell.displaystring; + delete cell.parseinfo; + if (cell.comment) { + // comments are considered content for erasing + delete cell.comment; + } + } else if (rest == "formats") { + newcell = new SocialCalc.Cell(cr); // create a new cell without attributes + newcell.datavalue = cell.datavalue; // copy existing values + newcell.datatype = cell.datatype; + newcell.formula = cell.formula; + newcell.valuetype = cell.valuetype; + if (cell.comment) { + newcell.comment = cell.comment; + } + sheet.cells[cr] = newcell; // replace + } + } + } + attribs.needsrecalc = "yes"; + break; + + case "fillright": + case "filldown": + sheet.renderneeded = true; + sheet.changedrendervalues = true; + if (saveundo) changes.AddUndo("changedrendervalues"); // to take care of undone pasted spans + what = cmd.NextToken(); + rest = cmd.RestOfString(); + ParseRange(); + if (cmd1 == "fillright") { + fillright = true; + rowstart = cr1.row; + colstart = cr1.col + 1; + } else { + fillright = false; + rowstart = cr1.row + 1; + colstart = cr1.col; + } + for (row = rowstart; row <= cr2.row; row++) { + for (col = colstart; col <= cr2.col; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.GetAssuredCell(cr); + if (saveundo) + changes.AddUndo("set " + cr + " all", sheet.CellToString(cell)); + if (fillright) { + crbase = SocialCalc.crToCoord(cr1.col, row); + coloffset = col - colstart + 1; + rowoffset = 0; + } else { + crbase = SocialCalc.crToCoord(col, cr1.row); + coloffset = 0; + rowoffset = row - rowstart + 1; + } + basecell = sheet.GetAssuredCell(crbase); + if (rest == "all" || rest == "formats") { + for (attrib in cellProperties) { + if (cellProperties[attrib] == 1) continue; // copy only format attributes + if ( + typeof basecell[attrib] === undefined || + cellProperties[attrib] == 3 + ) { + delete cell[attrib]; + } else { + cell[attrib] = basecell[attrib]; + } + } + } + if (rest == "all" || rest == "formulas") { + cell.datavalue = basecell.datavalue; + cell.datatype = basecell.datatype; + cell.valuetype = basecell.valuetype; + if (cell.datatype == "f") { + // offset relative coords, even in sheet references + cell.formula = SocialCalc.OffsetFormulaCoords( + basecell.formula, + coloffset, + rowoffset + ); + } else { + cell.formula = basecell.formula; + } + delete cell.parseinfo; + cell.errors = basecell.errors; + } + delete cell.displaystring; + } + } + + attribs.needsrecalc = "yes"; + break; + + case "copy": + what = cmd.NextToken(); + rest = cmd.RestOfString(); + if (saveundo) + changes.AddUndo( + "loadclipboard", + SocialCalc.encodeForSave(SocialCalc.Clipboard.clipboard) + ); + SocialCalc.Clipboard.clipboard = SocialCalc.CreateSheetSave( + sheet, + what + ); + break; + + case "loadclipboard": + rest = cmd.RestOfString(); + if (saveundo) + changes.AddUndo( + "loadclipboard", + SocialCalc.encodeForSave(SocialCalc.Clipboard.clipboard) + ); + SocialCalc.Clipboard.clipboard = SocialCalc.decodeFromSave(rest); + break; + + case "clearclipboard": + if (saveundo) + changes.AddUndo( + "loadclipboard", + SocialCalc.encodeForSave(SocialCalc.Clipboard.clipboard) + ); + SocialCalc.Clipboard.clipboard = ""; + break; + + case "paste": + sheet.renderneeded = true; + sheet.changedrendervalues = true; + if (saveundo) changes.AddUndo("changedrendervalues"); // to take care of undone pasted spans + what = cmd.NextToken(); + rest = cmd.RestOfString(); + ParseRange(); + if (!SocialCalc.Clipboard.clipboard) { + break; + } + clipsheet = new SocialCalc.Sheet(); // load clipboard contents as another sheet + clipsheet.ParseSheetSave(SocialCalc.Clipboard.clipboard); + cliprange = SocialCalc.ParseRange(clipsheet.copiedfrom); + coloffset = cr1.col - cliprange.cr1.col; // get sizes, etc. + rowoffset = cr1.row - cliprange.cr1.row; + numcols = cliprange.cr2.col - cliprange.cr1.col + 1; + numrows = cliprange.cr2.row - cliprange.cr1.row + 1; + if (cr1.col + numcols - 1 > attribs.lastcol) + attribs.lastcol = cr1.col + numcols - 1; + if (cr1.row + numrows - 1 > attribs.lastrow) + attribs.lastrow = cr1.row + numrows - 1; + + for (row = cr1.row; row < cr1.row + numrows; row++) { + for (col = cr1.col; col < cr1.col + numcols; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.GetAssuredCell(cr); + if (saveundo) + changes.AddUndo("set " + cr + " all", sheet.CellToString(cell)); + crbase = SocialCalc.crToCoord(col - coloffset, row - rowoffset); + basecell = clipsheet.GetAssuredCell(crbase); + if (rest == "all" || rest == "formats") { + for (attrib in cellProperties) { + if (cellProperties[attrib] == 1) continue; // copy only format attributes + if ( + typeof basecell[attrib] === undefined || + cellProperties[attrib] == 3 + ) { + delete cell[attrib]; + } else { + attribtable = SocialCalc.CellPropertiesTable[attrib]; + if (attribtable && basecell[attrib]) { + // table indexes to expand to strings since other sheet may have diff indexes + cell[attrib] = sheet.GetStyleNum( + attribtable, + clipsheet.GetStyleString(attribtable, basecell[attrib]) + ); + } else { + // these are not table indexes + cell[attrib] = basecell[attrib]; + } + } + } + } + if (rest == "all" || rest == "formulas") { + cell.datavalue = basecell.datavalue; + cell.datatype = basecell.datatype; + cell.valuetype = basecell.valuetype; + if (cell.datatype == "f") { + // offset relative coords, even in sheet references + cell.formula = SocialCalc.OffsetFormulaCoords( + basecell.formula, + coloffset, + rowoffset + ); + } else { + cell.formula = basecell.formula; + } + delete cell.parseinfo; + cell.errors = basecell.errors; + if (basecell.comment) { + // comments are pasted as part of content, though not filled, etc. + cell.comment = basecell.comment; + } else if (cell.comment) { + delete cell.comment; + } + } + delete cell.displaystring; + } + } + + attribs.needsrecalc = "yes"; + break; + + case "sort": // sort cr1:cr2 col1 up/down col2 up/down col3 up/down + sheet.renderneeded = true; + sheet.changedrendervalues = true; + if (saveundo) changes.AddUndo("changedrendervalues"); // to take care of undone pasted spans + what = cmd.NextToken(); + ParseRange(); + cols = []; // get columns and sort directions (or "") + dirs = []; + lastsortcol = 0; + for (i = 0; i <= 3; i++) { + cols[i] = cmd.NextToken(); + dirs[i] = cmd.NextToken(); + if (cols[i]) lastsortcol = i; + } + + sortcells = {}; // a copy of the data which will replace the original, but in the new order + sortlist = []; // an array of 0, 1, ..., nrows-1 needed for sorting + sortvalues = []; // values to be sorted corresponding to sortlist + sorttypes = []; // basic types of the values + + for (row = cr1.row; row <= cr2.row; row++) { + // fill in the sort info + for (col = cr1.col; col <= cr2.col; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.cells[cr]; + if (cell) { + // only copy non-empty cells + sortcells[cr] = sheet.CellToString(cell); + if (saveundo) + changes.AddUndo("set " + cr + " all", sortcells[cr]); + } else { + if (saveundo) changes.AddUndo("set " + cr + " all"); + } + } + sortlist.push(sortlist.length); + sortvalues.push([]); + sorttypes.push([]); + slast = sorttypes.length - 1; + for (i = 0; i <= lastsortcol; i++) { + cr = cols[i] + row; // get cr on this row in sort col + cell = sheet.GetAssuredCell(cr); + val = cell.datavalue; + valtype = cell.valuetype.charAt(0) || "b"; + if (valtype == "t") val = val.toLowerCase(); + sortvalues[slast].push(val); + sorttypes[slast].push(valtype); + } + } + + sortfunction = function (a, b) { + // a comparison function that can handle all the type variations + var i, a1, b1, ta, cresult; + for (i = 0; i <= lastsortcol; i++) { + if (dirs[i] == "up") { + // handle sort direction + a1 = a; + b1 = b; + } else { + a1 = b; + b1 = a; + } + ta = sorttypes[a1][i]; + tb = sorttypes[b1][i]; + if (ta == "t") { + // numbers < text < errors, blank always last no matter what dir + if (tb == "t") { + a1 = sortvalues[a1][i]; + b1 = sortvalues[b1][i]; + cresult = a1 > b1 ? 1 : a1 < b1 ? -1 : 0; + } else if (tb == "n") { + cresult = 1; + } else if (tb == "b") { + cresult = dirs[i] == "up" ? -1 : 1; + } else if (tb == "e") { + cresult = -1; + } + } else if (ta == "n") { + if (tb == "t") { + cresult = -1; + } else if (tb == "n") { + a1 = sortvalues[a1][i] - 0; // force to numeric, just in case + b1 = sortvalues[b1][i] - 0; + cresult = a1 > b1 ? 1 : a1 < b1 ? -1 : 0; + } else if (tb == "b") { + cresult = dirs[i] == "up" ? -1 : 1; + } else if (tb == "e") { + cresult = -1; + } + } else if (ta == "e") { + if (tb == "e") { + a1 = sortvalues[a1][i]; + b1 = sortvalues[b1][i]; + cresult = a1 > b1 ? 1 : a1 < b1 ? -1 : 0; + } else if (tb == "b") { + cresult = dirs[i] == "up" ? -1 : 1; + } else { + cresult = 1; + } + } else if (ta == "b") { + if (tb == "b") { + cresult = 0; + } else { + cresult = dirs[i] == "up" ? 1 : -1; + } + } + if (cresult) { + // return if tested not equal, otherwise do next column + return cresult; + } + } + cresult = a > b ? 1 : a < b ? -1 : 0; // equal - return position in original to maintain it + return cresult; + }; + + sortlist.sort(sortfunction); + + for (row = cr1.row; row <= cr2.row; row++) { + // copy original rows into sorted positions + originalrow = sortlist[row - cr1.row]; // relative position where it was in original + for (col = cr1.col; col <= cr2.col; col++) { + cr = SocialCalc.crToCoord(col, row); + sortedcr = SocialCalc.crToCoord(col, originalrow + cr1.row); // original cell to be put in new place + if (sortcells[sortedcr]) { + cell = new SocialCalc.Cell(cr); + sheet.CellFromStringParts( + cell, + sortcells[sortedcr].split(":"), + 1 + ); + if (cell.datatype == "f") { + // offset coord refs, even to ***relative*** coords in other sheets + cell.formula = SocialCalc.OffsetFormulaCoords( + cell.formula, + 0, + row - cr1.row - originalrow + ); + } + sheet.cells[cr] = cell; + } else { + delete sheet.cells[cr]; + } + } + } + + attribs.needsrecalc = "yes"; + break; + + case "insertcol": + case "insertrow": + sheet.renderneeded = true; + sheet.changedrendervalues = true; + what = cmd.NextToken(); + rest = cmd.RestOfString(); + ParseRange(); + + if (cmd1 == "insertcol") { + coloffset = 1; + colend = cr1.col; + rowoffset = 0; + rowend = 1; + newcolstart = cr1.col; + newcolend = cr1.col; + newrowstart = 1; + newrowend = attribs.lastrow; + if (saveundo) changes.AddUndo("deletecol " + cr1.coord); + } else { + coloffset = 0; + colend = 1; + rowoffset = 1; + rowend = cr1.row; + newcolstart = 1; + newcolend = attribs.lastcol; + newrowstart = cr1.row; + newrowend = cr1.row; + if (saveundo) changes.AddUndo("deleterow " + cr1.coord); + } + + for (row = attribs.lastrow; row >= rowend; row--) { + // copy the cells forward + for (col = attribs.lastcol; col >= colend; col--) { + crbase = SocialCalc.crToCoord(col, row); + cr = SocialCalc.crToCoord(col + coloffset, row + rowoffset); + if (!sheet.cells[crbase]) { + // copying empty cell + delete sheet.cells[cr]; // delete anything that may have been there + } else { + // overwrite existing cell with moved contents + sheet.cells[cr] = sheet.cells[crbase]; + } + } + } + + for (row = newrowstart; row <= newrowend; row++) { + // fill the "new" empty cells + for (col = newcolstart; col <= newcolend; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = new SocialCalc.Cell(cr); + sheet.cells[cr] = cell; + crbase = SocialCalc.crToCoord(col - coloffset, row - rowoffset); // copy attribs of the one before (0 gives you A or 1) + basecell = sheet.GetAssuredCell(crbase); + for (attrib in cellProperties) { + if (cellProperties[attrib] == 2) { + // copy only format attributes + cell[attrib] = basecell[attrib]; + } + } + } + } + + for (cr in sheet.cells) { + // update cell references to moved cells in calculated formulas + cell = sheet.cells[cr]; + if (cell && cell.datatype == "f") { + cell.formula = SocialCalc.AdjustFormulaCoords( + cell.formula, + cr1.col, + coloffset, + cr1.row, + rowoffset + ); + } + if (cell) { + delete cell.parseinfo; + } + } + + for (name in sheet.names) { + // update cell references to moved cells in names + if (sheet.names[name]) { + // works with "A1", "A1:A20", and "=formula" forms + v1 = sheet.names[name].definition; + v2 = ""; + if (v1.charAt(0) == "=") { + v2 = "="; + v1 = v1.substring(1); + } + sheet.names[name].definition = + v2 + + SocialCalc.AdjustFormulaCoords( + v1, + cr1.col, + coloffset, + cr1.row, + rowoffset + ); + } + } + + for ( + row = attribs.lastrow; + row >= rowend && cmd1 == "insertrow"; + row-- + ) { + // copy the row attributes forward + rownext = row + rowoffset; + for (attrib in sheet.rowattribs) { + val = sheet.rowattribs[attrib][row]; + if (sheet.rowattribs[attrib][rownext] != val) { + // make assignment only if different + if (val) { + sheet.rowattribs[attrib][rownext] = val; + } else { + delete sheet.rowattribs[attrib][rownext]; + } + } + } + } + + for ( + col = attribs.lastcol; + col >= colend && cmd1 == "insertcol"; + col-- + ) { + // copy the column attributes forward + colthis = SocialCalc.rcColname(col); + colnext = SocialCalc.rcColname(col + coloffset); + for (attrib in sheet.colattribs) { + val = sheet.colattribs[attrib][colthis]; + if (sheet.colattribs[attrib][colnext] != val) { + // make assignment only if different + if (val) { + sheet.colattribs[attrib][colnext] = val; + } else { + delete sheet.colattribs[attrib][colnext]; + } + } + } + } + + attribs.lastcol += coloffset; + attribs.lastrow += rowoffset; + attribs.needsrecalc = "yes"; + break; + + case "deletecol": + case "deleterow": + sheet.renderneeded = true; + sheet.changedrendervalues = true; + what = cmd.NextToken(); + rest = cmd.RestOfString(); + lastcol = attribs.lastcol; // save old values since ParseRange sets... + lastrow = attribs.lastrow; + ParseRange(); + + if (cmd1 == "deletecol") { + coloffset = cr1.col - cr2.col - 1; + rowoffset = 0; + colstart = cr2.col + 1; + rowstart = 1; + } else { + coloffset = 0; + rowoffset = cr1.row - cr2.row - 1; + colstart = 1; + rowstart = cr2.row + 1; + } + + for (row = rowstart; row <= lastrow - rowoffset; row++) { + // copy the cells backwards - extra so no dup of last set + for (col = colstart; col <= lastcol - coloffset; col++) { + cr = SocialCalc.crToCoord(col + coloffset, row + rowoffset); + if ( + saveundo && + (row < rowstart - rowoffset || col < colstart - coloffset) + ) { + // save cells that are overwritten as undo info + cell = sheet.cells[cr]; + if (!cell) { + // empty cell + changes.AddUndo("erase " + cr + " all"); + } else { + changes.AddUndo("set " + cr + " all", sheet.CellToString(cell)); + } + } + crbase = SocialCalc.crToCoord(col, row); + cell = sheet.cells[crbase]; + if (!cell) { + // copying empty cell + delete sheet.cells[cr]; // delete anything that may have been there + } else { + // overwrite existing cell with moved contents + sheet.cells[cr] = cell; + } + } + } + + //!!! multiple deletes isn't setting #REF!; need to fix up #REF!'s on undo but only those! + + for (cr in sheet.cells) { + // update cell references to moved cells in calculated formulas + cell = sheet.cells[cr]; + if (cell) { + if (cell.datatype == "f") { + oldformula = cell.formula; + cell.formula = SocialCalc.AdjustFormulaCoords( + oldformula, + cr1.col, + coloffset, + cr1.row, + rowoffset + ); + if (cell.formula != oldformula) { + delete cell.parseinfo; + if (saveundo && cell.formula.indexOf("#REF!") != -1) { + // save old version only if removed coord + oldcr = SocialCalc.coordToCr(cr); + changes.AddUndo( + "set " + + SocialCalc.rcColname(oldcr.col - coloffset) + + (oldcr.row - rowoffset) + + " formula " + + oldformula + ); + } + } + } else { + delete cell.parseinfo; + } + } + } + + for (name in sheet.names) { + // update cell references to moved cells in names + if (sheet.names[name]) { + // works with "A1", "A1:A20", and "=formula" forms + v1 = sheet.names[name].definition; + v2 = ""; + if (v1.charAt(0) == "=") { + v2 = "="; + v1 = v1.substring(1); + } + sheet.names[name].definition = + v2 + + SocialCalc.AdjustFormulaCoords( + v1, + cr1.col, + coloffset, + cr1.row, + rowoffset + ); + } + } + + for ( + row = rowstart; + row <= lastrow - rowoffset && cmd1 == "deleterow"; + row++ + ) { + // copy the row attributes backwards + rowbefore = row + rowoffset; + for (attrib in sheet.rowattribs) { + val = sheet.rowattribs[attrib][row]; + if (sheet.rowattribs[attrib][rowbefore] != val) { + // make assignment only if different + if (saveundo) + changes.AddUndo( + "set " + rowbefore + " " + attrib, + sheet.rowattribs[attrib][rowbefore] + ); + if (val) { + sheet.rowattribs[attrib][rowbefore] = val; + } else { + delete sheet.rowattribs[attrib][rowbefore]; + } + } + } + } + + for ( + col = colstart; + col <= lastcol - coloffset && cmd1 == "deletecol"; + col++ + ) { + // copy the column attributes backwards + colthis = SocialCalc.rcColname(col); + colbefore = SocialCalc.rcColname(col + coloffset); + for (attrib in sheet.colattribs) { + val = sheet.colattribs[attrib][colthis]; + if (sheet.colattribs[attrib][colbefore] != val) { + // make assignment only if different + if (saveundo) + changes.AddUndo( + "set " + colbefore + " " + attrib, + sheet.colattribs[attrib][colbefore] + ); + if (val) { + sheet.colattribs[attrib][colbefore] = val; + } else { + delete sheet.colattribs[attrib][colbefore]; + } + } + } + } + + if (saveundo) { + if (cmd1 == "deletecol") { + for (col = cr1.col; col <= cr2.col; col++) { + changes.AddUndo("insertcol " + SocialCalc.rcColname(col)); + } + } else { + for (row = cr1.row; row <= cr2.row; row++) { + changes.AddUndo("insertrow " + row); + } + } + } + + if (cmd1 == "deletecol") { + if (cr1.col <= lastcol) { + // shrink sheet unless deleted phantom cols off the end + if (cr2.col <= lastcol) { + attribs.lastcol += coloffset; + } else { + attribs.lastcol = cr1.col - 1; + } + } + } else { + if (cr1.row <= lastrow) { + // shrink sheet unless deleted phantom rows off the end + if (cr2.row <= lastrow) { + attribs.lastrow += rowoffset; + } else { + attribs.lastrow = cr1.row - 1; + } + } + } + attribs.needsrecalc = "yes"; + break; + + case "movepaste": + case "moveinsert": + var movingcells, + dest, + destcr, + inserthoriz, + insertvert, + pushamount, + movedto; + + sheet.renderneeded = true; + sheet.changedrendervalues = true; + if (saveundo) changes.AddUndo("changedrendervalues"); // to take care of undone pasted spans + what = cmd.NextToken(); + dest = cmd.NextToken(); + rest = cmd.RestOfString(); // rest is all/formulas/formats + if (rest == "") rest = "all"; + + ParseRange(); + + destcr = SocialCalc.coordToCr(dest); + + coloffset = destcr.col - cr1.col; + rowoffset = destcr.row - cr1.row; + numcols = cr2.col - cr1.col + 1; + numrows = cr2.row - cr1.row + 1; + + // get a copy of moving cells and erase from where they were + + movingcells = {}; + + for (row = cr1.row; row <= cr2.row; row++) { + for (col = cr1.col; col <= cr2.col; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.GetAssuredCell(cr); + if (saveundo) + changes.AddUndo("set " + cr + " all", sheet.CellToString(cell)); + + if (!sheet.cells[cr]) { + // if had nothing + continue; // don't save anything + } + movingcells[cr] = new SocialCalc.Cell(cr); // create new cell to copy + + for (attrib in cellProperties) { + // go through each property + if (typeof cell[attrib] === undefined) { + // don't copy undefined things and no need to delete + continue; + } else { + movingcells[cr][attrib] = cell[attrib]; // copy for potential moving + } + if (rest == "all") { + delete cell[attrib]; + } + if (rest == "formulas") { + if ( + cellProperties[attrib] == 1 || + cellProperties[attrib] == 3 + ) { + delete cell[attrib]; + } + } + if (rest == "formats") { + if (cellProperties[attrib] == 2) { + delete cell[attrib]; + } + } + } + if (rest == "formulas") { + // leave pristene deleted cell + cell.datavalue = ""; + cell.datatype = null; + cell.formula = ""; + cell.valuetype = "b"; + } + if (rest == "all") { + // leave nothing for move all + delete sheet.cells[cr]; + } + } + } + + // if moveinsert, check destination OK, and calculate pushing parameters + + if (cmd1 == "moveinsert") { + inserthoriz = false; + insertvert = false; + if ( + rowoffset == 0 && + (destcr.col < cr1.col || destcr.col > cr2.col) + ) { + if (destcr.col < cr1.col) { + // moving left + pushamount = cr1.col - destcr.col; + inserthoriz = -1; + } else { + destcr.col -= 1; + coloffset = destcr.col - cr2.col; + pushamount = destcr.col - cr2.col; + inserthoriz = 1; + } + } else if ( + coloffset == 0 && + (destcr.row < cr1.row || destcr.row > cr2.row) + ) { + if (destcr.row < cr1.row) { + // moving up + pushamount = cr1.row - destcr.row; + insertvert = -1; + } else { + destcr.row -= 1; + rowoffset = destcr.row - cr2.row; + pushamount = destcr.row - cr2.row; + insertvert = 1; + } + } else { + cmd1 = "movepaste"; // not allowed right now - ignore + } + } + + // push any cells that need pushing + + movedto = {}; // remember what was moved where + + if (insertvert) { + for (row = 0; row < pushamount; row++) { + for (col = cr1.col; col <= cr2.col; col++) { + if (insertvert < 0) { + crbase = SocialCalc.crToCoord( + col, + destcr.row + pushamount - row - 1 + ); // from cell + cr = SocialCalc.crToCoord(col, cr2.row - row); // to cell + } else { + crbase = SocialCalc.crToCoord( + col, + destcr.row - pushamount + row + 1 + ); // from cell + cr = SocialCalc.crToCoord(col, cr1.row + row); // to cell + } + + basecell = sheet.GetAssuredCell(crbase); + if (saveundo) + changes.AddUndo( + "set " + crbase + " all", + sheet.CellToString(basecell) + ); + + cell = sheet.GetAssuredCell(cr); + if (rest == "all" || rest == "formats") { + for (attrib in cellProperties) { + if (cellProperties[attrib] == 1) continue; // copy only format attributes + if ( + typeof basecell[attrib] === undefined || + cellProperties[attrib] == 3 + ) { + delete cell[attrib]; + } else { + cell[attrib] = basecell[attrib]; + } + } + } + if (rest == "all" || rest == "formulas") { + cell.datavalue = basecell.datavalue; + cell.datatype = basecell.datatype; + cell.valuetype = basecell.valuetype; + cell.formula = basecell.formula; + delete cell.parseinfo; + cell.errors = basecell.errors; + } + delete cell.displaystring; + + movedto[crbase] = cr; // old crbase is now at cr + } + } + } + if (inserthoriz) { + for (col = 0; col < pushamount; col++) { + for (row = cr1.row; row <= cr2.row; row++) { + if (inserthoriz < 0) { + crbase = SocialCalc.crToCoord( + destcr.col + pushamount - col - 1, + row + ); + cr = SocialCalc.crToCoord(cr2.col - col, row); + } else { + crbase = SocialCalc.crToCoord( + destcr.col - pushamount + col + 1, + row + ); + cr = SocialCalc.crToCoord(cr1.col + col, row); + } + + basecell = sheet.GetAssuredCell(crbase); + if (saveundo) + changes.AddUndo( + "set " + crbase + " all", + sheet.CellToString(basecell) + ); + + cell = sheet.GetAssuredCell(cr); + if (rest == "all" || rest == "formats") { + for (attrib in cellProperties) { + if (cellProperties[attrib] == 1) continue; // copy only format attributes + if ( + typeof basecell[attrib] === undefined || + cellProperties[attrib] == 3 + ) { + delete cell[attrib]; + } else { + cell[attrib] = basecell[attrib]; + } + } + } + if (rest == "all" || rest == "formulas") { + cell.datavalue = basecell.datavalue; + cell.datatype = basecell.datatype; + cell.valuetype = basecell.valuetype; + cell.formula = basecell.formula; + delete cell.parseinfo; + cell.errors = basecell.errors; + } + delete cell.displaystring; + + movedto[crbase] = cr; // old crbase is now at cr + } + } + } + + // paste moved cells into new place + + if (destcr.col + numcols - 1 > attribs.lastcol) + attribs.lastcol = destcr.col + numcols - 1; + if (destcr.row + numrows - 1 > attribs.lastrow) + attribs.lastrow = destcr.row + numrows - 1; + + for (row = cr1.row; row < cr1.row + numrows; row++) { + for (col = cr1.col; col < cr1.col + numcols; col++) { + cr = SocialCalc.crToCoord(col + coloffset, row + rowoffset); + cell = sheet.GetAssuredCell(cr); + if (saveundo) + changes.AddUndo("set " + cr + " all", sheet.CellToString(cell)); + + crbase = SocialCalc.crToCoord(col, row); // get old cell to move + + movedto[crbase] = cr; // old crbase (moved cell) will now be at cr (destination) + + if (rest == "all" && !movingcells[crbase]) { + // moving an empty cell + delete sheet.cells[cr]; // make the cell empty + continue; + } + + basecell = movingcells[crbase]; + if (!basecell) basecell = sheet.GetAssuredCell(crbase); + + if (rest == "all" || rest == "formats") { + for (attrib in cellProperties) { + if (cellProperties[attrib] == 1) continue; // copy only format attributes + if ( + typeof basecell[attrib] === undefined || + cellProperties[attrib] == 3 + ) { + delete cell[attrib]; + } else { + cell[attrib] = basecell[attrib]; + } + } + } + if (rest == "all" || rest == "formulas") { + cell.datavalue = basecell.datavalue; + cell.datatype = basecell.datatype; + cell.valuetype = basecell.valuetype; + cell.formula = basecell.formula; + delete cell.parseinfo; + cell.errors = basecell.errors; + if (basecell.comment) { + // comments are pasted as part of content, though not filled, etc. + cell.comment = basecell.comment; + } else if (cell.comment) { + delete cell.comment; + } + } + delete cell.displaystring; + } + } + + // do fixups + + for (cr in sheet.cells) { + // update cell references to moved cells in calculated formulas + cell = sheet.cells[cr]; + if (cell) { + if (cell.datatype == "f") { + oldformula = cell.formula; + cell.formula = SocialCalc.ReplaceFormulaCoords( + oldformula, + movedto + ); + if (cell.formula != oldformula) { + delete cell.parseinfo; + if (saveundo && !movedto[cr]) { + // moved cells are already saved for undo + changes.AddUndo("set " + cr + " formula " + oldformula); + } + } + } else { + delete cell.parseinfo; + } + } + } + + for (name in sheet.names) { + // update cell references to moved cells in names + if (sheet.names[name]) { + // works with "A1", "A1:A20", and "=formula" forms + v1 = sheet.names[name].definition; + oldformula = v1; + v2 = ""; + if (v1.charAt(0) == "=") { + v2 = "="; + v1 = v1.substring(1); + } + sheet.names[name].definition = + v2 + SocialCalc.ReplaceFormulaCoords(v1, movedto); + if (saveundo && sheet.names[name].definition != oldformula) { + // save changes + changes.AddUndo("name define " + name + " " + oldformula); + } + } + } + + attribs.needsrecalc = "yes"; + break; + + case "name": + what = cmd.NextToken(); + name = cmd.NextToken(); + rest = cmd.RestOfString(); + + name = name.toUpperCase().replace(/[^A-Z0-9_\.]/g, ""); + if (name == "") break; // must have something + + if (what == "define") { + if (rest == "") break; // must have something + if (sheet.names[name]) { + // already exists + if (saveundo) + changes.AddUndo( + "name define " + name + " " + sheet.names[name].definition + ); + sheet.names[name].definition = rest; + } else { + // new + if (saveundo) changes.AddUndo("name delete " + name); + sheet.names[name] = { definition: rest, desc: "" }; + } + } else if (what == "desc") { + if (sheet.names[name]) { + // must already exist + if (saveundo) + changes.AddUndo( + "name desc " + name + " " + sheet.names[name].desc + ); + sheet.names[name].desc = rest; + } + } else if (what == "delete") { + if (saveundo) { + if (sheet.names[name].desc) + changes.AddUndo( + "name desc " + name + " " + sheet.names[name].desc + ); + changes.AddUndo( + "name define " + name + " " + sheet.names[name].definition + ); + } + delete sheet.names[name]; + } + attribs.needsrecalc = "yes"; + + break; + + case "recalc": + attribs.needsrecalc = "yes"; // request recalc + sheet.recalconce = true; // even if turned off + break; + + case "redisplay": + sheet.renderneeded = true; + break; + + case "changedrendervalues": // needed for undo sometimes + sheet.changedrendervalues = true; + break; + + case "startcmdextension": // startcmdextension extension rest-of-command + name = cmd.NextToken(); + cmdextension = SocialCalc.SheetCommandInfo.CmdExtensionCallbacks[name]; + if (cmdextension) { + cmdextension.func(name, cmdextension.data, sheet, cmd, saveundo); + } + break; + + default: + errortext = scc.s_escUnknownCmd + cmdstr; + break; + } + + /* For Debugging: + var ustack=""; + for (var i=0;i= 0; i--) { + // do them backwards + if (cmdstr) cmdstr += "\n"; // concatenate with separate lines + cmdstr += tos.undo[i]; + } + sheet.changes.Undo(); + sheet.ScheduleSheetCommands(cmdstr, false); // do undo operations + }; + + SocialCalc.SheetRedo = function (sheet) { + var tos, i; + var didredo = sheet.changes.Redo(); + if (!didredo) { + sheet.ScheduleSheetCommands("", false); // schedule doing nothing + return; + } + tos = sheet.changes.TOS(); + var cmdstr = ""; + + for (i = 0; tos && i < tos.command.length; i++) { + if (cmdstr) cmdstr += "\n"; // concatenate with separate lines + cmdstr += tos.command[i]; + } + sheet.ScheduleSheetCommands(cmdstr, false); // do undo operations + }; + + SocialCalc.CreateAuditString = function (sheet) { + var i, j; + var result = ""; + var stack = sheet.changes.stack; + var tos = sheet.changes.tos; + for (i = 0; i <= tos; i++) { + for (j = 0; j < stack[i].command.length; j++) { + result += stack[i].command[j] + "\n"; + } + } + + return result; + }; + + SocialCalc.GetStyleNum = function (sheet, atype, style) { + var num; + + if (style.length == 0) return 0; // null means use zero, which means default or global default + + num = sheet[atype + "hash"][style]; + if (!num) { + if (sheet[atype + "s"].length < 1) sheet[atype + "s"].push(""); + num = sheet[atype + "s"].push(style) - 1; + sheet[atype + "hash"][style] = num; + sheet.changedrendervalues = true; + } + return num; + }; + + SocialCalc.GetStyleString = function (sheet, atype, num) { + if (!num) return null; // zero, null, and undefined return null + + return sheet[atype + "s"][num]; + }; + + // + // updatedformula = SocialCalc.OffsetFormulaCoords(formula, coloffset, rowoffset) + // + // Change relative cell references by offsets (even those to other worksheets so fill, paste, sort work as expected). + // If not what you want, use absolute references. + // + + SocialCalc.OffsetFormulaCoords = function (formula, coloffset, rowoffset) { + var parseinfo, ttext, ttype, i, cr, newcr; + var updatedformula = ""; + var scf = SocialCalc.Formula; + if (!scf) { + return "Need SocialCalc.Formula"; + } + var tokentype = scf.TokenType; + var token_op = tokentype.op; + var token_string = tokentype.string; + var token_coord = tokentype.coord; + var tokenOpExpansion = scf.TokenOpExpansion; + + parseinfo = scf.ParseFormulaIntoTokens(formula); + + for (i = 0; i < parseinfo.length; i++) { + ttype = parseinfo[i].type; + ttext = parseinfo[i].text; + if (ttype == token_coord) { + newcr = ""; + cr = SocialCalc.coordToCr(ttext); + if (ttext.charAt(0) != "$") { + // add col offset unless absolute column + cr.col += coloffset; + } else { + newcr += "$"; + } + newcr += SocialCalc.rcColname(cr.col); + if (ttext.indexOf("$", 1) == -1) { + // add row offset unless absolute row + cr.row += rowoffset; + } else { + newcr += "$"; + } + newcr += cr.row; + if (cr.row < 1 || cr.col < 1) { + newcr = "#REF!"; + } + updatedformula += newcr; + } else if (ttype == token_string) { + if (ttext.indexOf('"') >= 0) { + // quotes to double + updatedformula += '"' + ttext.replace(/"/, '""') + '"'; + } else updatedformula += '"' + ttext + '"'; + } else if (ttype == token_op) { + updatedformula += tokenOpExpansion[ttext] || ttext; // make sure short tokens (e.g., "G") go back full (">=") + } else { + // leave everything else alone + updatedformula += ttext; + } + } + + return updatedformula; + }; + + // + // updatedformula = SocialCalc.AdjustFormulaCoords(formula, col, coloffset, row, rowoffset) + // + // Change all cell references to cells starting with col/row by offsets + // + + SocialCalc.AdjustFormulaCoords = function ( + formula, + col, + coloffset, + row, + rowoffset + ) { + var ttype, ttext, i, newcr; + var updatedformula = ""; + var sheetref = false; + var scf = SocialCalc.Formula; + if (!scf) { + return "Need SocialCalc.Formula"; + } + var tokentype = scf.TokenType; + var token_op = tokentype.op; + var token_string = tokentype.string; + var token_coord = tokentype.coord; + var tokenOpExpansion = scf.TokenOpExpansion; + + parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(formula); + + for (i = 0; i < parseinfo.length; i++) { + ttype = parseinfo[i].type; + ttext = parseinfo[i].text; + if (ttype == token_op) { + // references with sheet specifier are not offset + if (ttext == "!") { + sheetref = true; // found a sheet reference + } else if (ttext != ":") { + // for everything but a range, reset + sheetref = false; + } + ttext = tokenOpExpansion[ttext] || ttext; // make sure short tokens (e.g., "G") go back full (">=") + } + if (ttype == token_coord) { + cr = SocialCalc.coordToCr(ttext); + if ( + (coloffset < 0 && cr.col >= col && cr.col < col - coloffset) || + (rowoffset < 0 && cr.row >= row && cr.row < row - rowoffset) + ) { + // refs to deleted cells become invalid + if (!sheetref) { + cr.col = 0; + cr.row = 0; + } + } + if (!sheetref) { + if (cr.col >= col) { + cr.col += coloffset; + } + if (cr.row >= row) { + cr.row += rowoffset; + } + } + if (ttext.charAt(0) == "$") { + newcr = "$" + SocialCalc.rcColname(cr.col); + } else { + newcr = SocialCalc.rcColname(cr.col); + } + if (ttext.indexOf("$", 1) != -1) { + newcr += "$" + cr.row; + } else { + newcr += cr.row; + } + if (cr.row < 1 || cr.col < 1) { + newcr = "#REF!"; + } + ttext = newcr; + } else if (ttype == token_string) { + if (ttext.indexOf('"') >= 0) { + // quotes to double + ttext = '"' + ttext.replace(/"/, '""') + '"'; + } else ttext = '"' + ttext + '"'; + } + updatedformula += ttext; + } + + return updatedformula; + }; + + // + // updatedformula = SocialCalc.ReplaceFormulaCoords(formula, movedto) + // + // Change all cell references to cells that are keys in moveto to be to moveto[coord]. + // Don't change references to other sheets. + // Handle range extents specially. + // + + SocialCalc.ReplaceFormulaCoords = function (formula, movedto) { + var ttype, ttext, i, newcr, coord; + var updatedformula = ""; + var sheetref = false; + var scf = SocialCalc.Formula; + if (!scf) { + return "Need SocialCalc.Formula"; + } + var tokentype = scf.TokenType; + var token_op = tokentype.op; + var token_string = tokentype.string; + var token_coord = tokentype.coord; + var tokenOpExpansion = scf.TokenOpExpansion; + + parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(formula); + + for (i = 0; i < parseinfo.length; i++) { + ttype = parseinfo[i].type; + ttext = parseinfo[i].text; + if (ttype == token_op) { + // references with sheet specifier are not change + if (ttext == "!") { + sheetref = true; // found a sheet reference + } else if (ttext != ":") { + // for everything but a range, reset + sheetref = false; + } + + //!!!! HANDLE RANGE EXTENT MOVES + + ttext = tokenOpExpansion[ttext] || ttext; // make sure short tokens (e.g., "G") go back full (">=") + } + if (ttype == token_coord) { + cr = SocialCalc.coordToCr(ttext); // get parts + coord = SocialCalc.crToCoord(cr.col, cr.row); // get "clean" reference + if (movedto[coord] && !sheetref) { + // this is a reference to a moved cell + cr = SocialCalc.coordToCr(movedto[coord]); // get new row and col + if (ttext.charAt(0) == "$") { + // copy absolute ref marks if present + newcr = "$" + SocialCalc.rcColname(cr.col); + } else { + newcr = SocialCalc.rcColname(cr.col); + } + if (ttext.indexOf("$", 1) != -1) { + newcr += "$" + cr.row; + } else { + newcr += cr.row; + } + ttext = newcr; + } + } else if (ttype == token_string) { + if (ttext.indexOf('"') >= 0) { + // quotes to double + ttext = '"' + ttext.replace(/"/, '""') + '"'; + } else ttext = '"' + ttext + '"'; + } + updatedformula += ttext; + } + + return updatedformula; + }; + + // ************************ + // + // Recalc Loop Code + // + // ************************ + + // + // How recalc works: + // + // !!!!!!!!!!!!!! + // + + // SocialCalc.RecalcInfo - object with global recalc info + + SocialCalc.RecalcInfo = { + sheet: null, // which sheet is being recalced + + currentState: 0, // current state + state: { start_calc: 1, order: 2, calc: 3, start_wait: 4, done_wait: 5 }, // allowed state values + + recalctimer: null, // value to cancel timer + maxtimeslice: 100, // maximum milliseconds per slice of recalc time before a wait + timeslicedelay: 1, // milliseconds to wait between recalc time slices + starttime: 0, // when recalc started + + // LoadSheet: a function that returns true if started a load or false if not. + // + + LoadSheet: function (sheetname) { + return false; + }, // default returns not found + }; + + // SocialCalc.RecalcData - object with recalc info while determining recalc order and afterward + + SocialCalc.RecalcData = function () { + // initialize a RecalcData object + + this.inrecalc = true; // if true, doing a recalc + this.celllist = []; // list with all potential cells to calculate + this.celllistitem = 0; // cell to check next when ordering + this.calclist = null; // object which is the chained list of cells to calculate + // each in the form of "coord: nextcoord" + // e.g., if B8 is calculated right after A8, then calclist.A8=="B8" + // if null, need to create the list + this.calclistlength = 0; // number of items in calclist + + this.firstcalc = null; // start of the calc list - a string or null + this.lastcalc = null; // last one on chain (used to add more to the end) + + this.nextcalc = null; // used to keep track during background recalc to make it restartable + this.count = 0; // number calculated + + // checkinfo is used when determining calc order: + + this.checkinfo = {}; // attributes are coords; if no attrib for a coord, it wasn't checked or doesn't need it + // values are RecalcCheckInfo objects while checking or TRUE when complete + }; + + // SocialCalc.RecalcCheckInfo - object that stores checking info while determining recalc order + + SocialCalc.RecalcCheckInfo = function () { + // initialize a RecalcCheckInfo object + + this.oldcoord = null; // chain back up of cells referring to cells + this.parsepos = 0; // which token we are up to + + // range info + + this.inrange = false; // if true, in the process of checking a range of coords + this.inrangestart = false; // if true, have not yet filled in range loop values + this.cr1 = null; // range first coord as a cr object + this.cr2 = null; // range second coord as a cr object + this.c1 = null; // range extents + this.c2 = null; + this.r1 = null; + this.r2 = null; + this.c = null; // looping values + this.r = null; + }; + + // Recalc the entire sheet + + SocialCalc.RecalcSheet = function (sheet) { + var coord, err, recalcdata; + var scri = SocialCalc.RecalcInfo; + + delete sheet.attribs.circularreferencecell; // reset recalc-wide things + SocialCalc.Formula.FreshnessInfoReset(); + + SocialCalc.RecalcClearTimeout(); + + scri.sheet = sheet; // set values needed by background recalc + scri.currentState = scri.state.start_calc; + + scri.starttime = new Date(); + + if (sheet.statuscallback) { + sheet.statuscallback(scri, "calcstart", null, sheet.statuscallbackparams); + } + + SocialCalc.RecalcSetTimeout(); + }; + + // + // SocialCalc.RecalcSetTimeout - set a timer for next recalc step + // + + SocialCalc.RecalcSetTimeout = function () { + var scri = SocialCalc.RecalcInfo; + + scri.recalctimer = window.setTimeout( + SocialCalc.RecalcTimerRoutine, + scri.timeslicedelay + ); + }; + + // + // SocialCalc.RecalcClearTimeout - cancel any timeouts + // + + SocialCalc.RecalcClearTimeout = function () { + var scri = SocialCalc.RecalcInfo; + + if (scri.recalctimer) { + window.clearTimeout(scri.recalctimer); + scri.recalctimer = null; + } + }; + + // + // SocialCalc.RecalcLoadedSheet(sheetname, str, recalcneeded) + // + // Called when a sheet finishes loading with name, string, and t/f whether it should be recalced. + // If loaded sheet has sheet.attribs.recalc=="off", then no recalc done. + // If sheetname is null, then the sheetname waiting for will be used. + // + + SocialCalc.RecalcLoadedSheet = function (sheetname, str, recalcneeded) { + var sheet; + var scri = SocialCalc.RecalcInfo; + var scf = SocialCalc.Formula; + + sheet = SocialCalc.Formula.AddSheetToCache( + sheetname || scf.SheetCache.waitingForLoading, + str + ); + + if (recalcneeded && sheet && sheet.attribs.recalc != "off") { + // if recalcneeded, and not manual sheet, chain in this new sheet to recalc loop + sheet.previousrecalcsheet = scri.sheet; + scri.sheet = sheet; + scri.currentState = scri.state.start_calc; + } + scf.SheetCache.waitingForLoading = null; + + SocialCalc.RecalcSetTimeout(); + }; + + // + // SocialCalc.RecalcTimerRoutine - handles the actual order determination and cell-by-cell recalculation in the background + // + + SocialCalc.RecalcTimerRoutine = function () { + var eresult, cell, coord, err, status; + var starttime = new Date(); + var count = 0; + var scf = SocialCalc.Formula; + if (!scf) { + return "Need SocialCalc.Formula"; + } + var scri = SocialCalc.RecalcInfo; + var sheet = scri.sheet; + if (!sheet) { + return; + } + var recalcdata = sheet.recalcdata; + + var do_statuscallback = function (status, arg) { + // routine to do callback if required + if (sheet.statuscallback) { + sheet.statuscallback( + recalcdata, + status, + arg, + sheet.statuscallbackparams + ); + } + }; + + SocialCalc.RecalcClearTimeout(); + + if (scri.currentState == scri.state.start_calc) { + recalcdata = new SocialCalc.RecalcData(); + sheet.recalcdata = recalcdata; + + for (coord in sheet.cells) { + // get list of cells to check for order + if (!coord) continue; + recalcdata.celllist.push(coord); + } + + recalcdata.calclist = {}; // start with empty list + scri.currentState = scri.state.order; // drop through to determining recalc order + } + + if (scri.currentState == scri.state.order) { + while (recalcdata.celllistitem < recalcdata.celllist.length) { + // check all the cells to see if they should be on the list + coord = recalcdata.celllist[recalcdata.celllistitem++]; + err = SocialCalc.RecalcCheckCell(sheet, coord); + if (new Date() - starttime >= scri.maxtimeslice) { + // if taking too long, give up CPU for a while + do_statuscallback("calcorder", { + coord: coord, + total: recalcdata.celllist.length, + count: recalcdata.celllistitem, + }); + SocialCalc.RecalcSetTimeout(); + return; + } + } + + do_statuscallback("calccheckdone", recalcdata.calclistlength); + + recalcdata.nextcalc = recalcdata.firstcalc; // start at the beginning of the recalc chain + scri.currentState = scri.state.calc; // loop through cells on next timer call + SocialCalc.RecalcSetTimeout(); + return; + } + + if (scri.currentState == scri.state.start_wait) { + // starting to wait for something + scri.currentState = scri.state.done_wait; // finished on next timer call + if (scri.LoadSheet) { + status = scri.LoadSheet(scf.SheetCache.waitingForLoading); + if (status) { + // started a load operation + return; + } + } + SocialCalc.RecalcLoadedSheet(null, "", false); + return; + } + + if (scri.currentState == scri.state.done_wait) { + scri.currentState = scri.state.calc; // loop through cells on next timer call + SocialCalc.RecalcSetTimeout(); + return; + } + + // otherwise should be scri.state.calc + + if (scri.currentState != scri.state.calc) { + alert( + "Recalc state error: " + + scri.currentState + + ". Error in SocialCalc code." + ); + } + + coord = sheet.recalcdata.nextcalc; + while (coord) { + cell = sheet.cells[coord]; + eresult = scf.evaluate_parsed_formula(cell.parseinfo, sheet, false); + if (scf.SheetCache.waitingForLoading) { + // wait until restarted + recalcdata.nextcalc = coord; // start with this cell again + recalcdata.count += count; + do_statuscallback("calcloading", { + sheetname: scf.SheetCache.waitingForLoading, + }); + scri.currentState = scri.state.start_wait; // start load on next timer call + SocialCalc.RecalcSetTimeout(); + return; + } + + if (scf.RemoteFunctionInfo.waitingForServer) { + // wait until restarted + recalcdata.nextcalc = coord; // start with this cell again + recalcdata.count += count; + do_statuscallback("calcserverfunc", { + funcname: scf.RemoteFunctionInfo.waitingForServer, + coord: coord, + total: recalcdata.calclistlength, + count: recalcdata.count, + }); + scri.currentState = scri.state.done_wait; // start load on next timer call + return; // return and wait for next recalc timer event + } + + if (cell.datavalue != eresult.value || cell.valuetype != eresult.type) { + // only update if changed from last time + cell.datavalue = eresult.value; + cell.valuetype = eresult.type; + delete cell.displaystring; + sheet.recalcchangedavalue = true; // remember something changed in case other code wants to know + } + if (eresult.error) { + cell.errors = eresult.error; + } + count++; + coord = sheet.recalcdata.calclist[coord]; + + if (new Date() - starttime >= scri.maxtimeslice) { + // if taking too long, give up CPU for a while + recalcdata.nextcalc = coord; // start with next cell on chain + recalcdata.count += count; + do_statuscallback("calcstep", { + coord: coord, + total: recalcdata.calclistlength, + count: recalcdata.count, + }); + SocialCalc.RecalcSetTimeout(); + return; + } + } + + recalcdata.inrecalc = false; + + delete sheet.recalcdata; // save memory and clear out for name lookup formula evaluation + + delete sheet.attribs.needsrecalc; // remember recalc done + + scri.sheet = sheet.previousrecalcsheet || null; // chain back if doing recalc of loaded sheets + if (scri.sheet) { + scri.currentState = scri.state.calc; // start where we left off + SocialCalc.RecalcSetTimeout(); + return; + } + + scf.FreshnessInfo.recalc_completed = true; // say freshness info is complete + + do_statuscallback("calcfinished", new Date() - scri.starttime); + }; + + // + // circref = SocialCalc.RecalcCheckCell(sheet, coord) + // + // Checks cell to put on calclist, looking at parsed tokens. + // Also checks cells this cell is dependent upon + // if it contains a formula with cell references. + // If circular reference, returns non-null. + // + + SocialCalc.RecalcCheckCell = function (sheet, startcoord) { + var parseinfo, + ttext, + ttype, + i, + rangecoord, + circref, + value, + pos, + pos2, + cell, + coordvals; + var scf = SocialCalc.Formula; + if (!scf) { + return "Need SocialCalc.Formula"; + } + var tokentype = scf.TokenType; + var token_op = tokentype.op; + var token_name = tokentype.name; + var token_coord = tokentype.coord; + + var recalcdata = sheet.recalcdata; + var checkinfo = recalcdata.checkinfo; + + var sheetref = false; // if true, a sheet reference is in effect, so don't check that + var oldcoord = null; // coord of formula that referred to this one when checking down the tree + var coord = startcoord; // the coord of the cell we are checking + + // Start with requested cell, and then continue down or up the dependency tree + // oldcoord (and checkinfo[coord].oldcoord) maintains the reference stack during the tree walk + // checkinfo[coord] maintains the stack of checking looping values, e.g., token number being checked + + mainloop: while (coord) { + cell = sheet.cells[coord]; + coordvals = checkinfo[coord]; + + if ( + !cell || + cell.datatype != "f" || // Don't calculate if not a formula + (coordvals && typeof coordvals != "object") + ) { + // Don't calc if already calculated + coord = oldcoord; // go back up dependency tree to coord that referred to us + if (checkinfo[coord]) oldcoord = checkinfo[coord].oldcoord; + continue; + } + + if (!coordvals) { + // do we have checking information about this cell? + coordvals = new SocialCalc.RecalcCheckInfo(); // no - make a place to hold it + checkinfo[coord] = coordvals; + } + + if (cell.errors) { + // delete errors from previous recalcs + delete cell.errors; + } + + if (!cell.parseinfo) { + // cache parsed formula + cell.parseinfo = scf.ParseFormulaIntoTokens(cell.formula); + } + parseinfo = cell.parseinfo; + + for (i = coordvals.parsepos; i < parseinfo.length; i++) { + // go through each token in formula + + if (coordvals.inrange) { + // processing a range of coords + if (coordvals.inrangestart) { + // first time - fill in other values + if (coordvals.cr1.col > coordvals.cr2.col) { + coordvals.c1 = coordvals.cr2.col; + coordvals.c2 = coordvals.cr1.col; + } else { + coordvals.c1 = coordvals.cr1.col; + coordvals.c2 = coordvals.cr2.col; + } + coordvals.c = coordvals.c1 - 1; // start one before + + if (coordvals.cr1.row > coordvals.cr2.row) { + coordvals.r1 = coordvals.cr2.row; + coordvals.r2 = coordvals.cr1.row; + } else { + coordvals.r1 = coordvals.cr1.row; + coordvals.r2 = coordvals.cr2.row; + } + coordvals.r = coordvals.r1; // start on this row + coordvals.inrangestart = false; + } else { + // not first time + } + coordvals.c += 1; // increment column + if (coordvals.c > coordvals.c2) { + // finished the columns of this row + coordvals.r += 1; // increment row + if (coordvals.r > coordvals.r2) { + // finished checking the entire range + coordvals.inrange = false; + continue; + } + coordvals.c = coordvals.c1; // start at the beginning of next row + } + rangecoord = SocialCalc.crToCoord(coordvals.c, coordvals.r); + + // now check that one + + coordvals.parsepos = i; // remember our position + coordvals.oldcoord = oldcoord; // remember back up chain + oldcoord = coord; // come back to us + coord = rangecoord; + if (checkinfo[coord] && typeof checkinfo[coord] == "object") { + // Circular reference + cell.errors = SocialCalc.Constants.s_caccCircRef + startcoord; // set on original cell making the ref + checkinfo[startcoord] = true; // this one should be calculated once at this point + if (!recalcdata.firstcalc) { + recalcdata.firstcalc = startcoord; + } else { + recalcdata.calclist[recalcdata.lastcalc] = startcoord; + } + recalcdata.lastcalc = startcoord; + recalcdata.calclistlength++; // count number on list + sheet.attribs.circularreferencecell = coord + "|" + oldcoord; // remember at least one circ ref + return cell.errors; + } + continue mainloop; + } + + ttype = parseinfo[i].type; // get token details + ttext = parseinfo[i].text; + if (ttype == token_op) { + // references with sheet specifier are not checked + if (ttext == "!") { + sheetref = true; // found a sheet reference + } else if (ttext != ":") { + // for everything but a range, reset + sheetref = false; + } + } + + if (ttype == token_name) { + // look for named range + value = scf.LookupName(sheet, ttext); + if (value.type == "range") { + // only need to recurse here for range, which may be just one cell + pos = value.value.indexOf("|"); + if (pos != -1) { + // range - check each cell + coordvals.cr1 = SocialCalc.coordToCr( + value.value.substring(0, pos) + ); + pos2 = value.value.indexOf("|", pos + 1); + coordvals.cr2 = SocialCalc.coordToCr( + value.value.substring(pos + 1, pos2) + ); + coordvals.inrange = true; + coordvals.inrangestart = true; + i = i - 1; // back up so will start up again here + continue; + } + } else if (value.type == "coord") { + // just a coord + ttype = token_coord; // treat as a coord inline + ttext = value.value; // and then drop through to next test which should succeed + } else { + // not a defined name - probably a function + } + } + + if (ttype == token_coord) { + // token is a coord + + if ( + i >= 2 && // look for a range + parseinfo[i - 1].type == token_op && + parseinfo[i - 1].text == ":" && + parseinfo[i - 2].type == token_coord && + !sheetref + ) { + // Range -- check each cell + coordvals.cr1 = SocialCalc.coordToCr(parseinfo[i - 2].text); // remember range extents + coordvals.cr2 = SocialCalc.coordToCr(ttext); + coordvals.inrange = true; // next time use the range looping code + coordvals.inrangestart = true; + i = i - 1; // back up so will start up again here + continue; + } else if (!sheetref) { + // Single cell reference + if (ttext.indexOf("$") != -1) ttext = ttext.replace(/\$/g, ""); // remove any $'s + coordvals.parsepos = i + 1; // remember our position - come back on next token + coordvals.oldcoord = oldcoord; // remember back up chain + oldcoord = coord; // come back to us + coord = ttext; + if (checkinfo[coord] && typeof checkinfo[coord] == "object") { + // Circular reference + cell.errors = SocialCalc.Constants.s_caccCircRef + startcoord; // set on original cell making the ref + checkinfo[startcoord] = true; // this one should be calculated once at this point + if (!recalcdata.firstcalc) { + // add to calclist + recalcdata.firstcalc = startcoord; + } else { + recalcdata.calclist[recalcdata.lastcalc] = startcoord; + } + recalcdata.lastcalc = startcoord; + recalcdata.calclistlength++; // count number on list + sheet.attribs.circularreferencecell = coord + "|" + oldcoord; // remember at least one circ ref + return cell.errors; + } + continue mainloop; + } + } + } + + sheetref = false; // make sure off when bump back up + + checkinfo[coord] = true; // this one is finished + if (!recalcdata.firstcalc) { + // add to calclist + recalcdata.firstcalc = coord; + } else { + recalcdata.calclist[recalcdata.lastcalc] = coord; + } + recalcdata.lastcalc = coord; + recalcdata.calclistlength++; // count number on list + + coord = oldcoord; // go back to the formula that referred to us and continue + oldcoord = checkinfo[coord] ? checkinfo[coord].oldcoord : null; + } + + return ""; + }; + + // ************************************* + // + // Parse class: + // + // Used by ExecuteSheetCommand to get elements of commands to execute. + // The string it works with consists of one or more lines each + // made up of one or more tokens separated by a delimiter. + // + // ************************************* + + // Initialize: set string to work with + + SocialCalc.Parse = function (str) { + // properties: + + this.str = str; + this.pos = 0; + this.delimiter = " "; + this.lineEnd = str.indexOf("\n"); + if (this.lineEnd < 0) { + this.lineEnd = str.length; + } + }; + + // Return next token as a string + + SocialCalc.Parse.prototype.NextToken = function () { + if (this.pos < 0) return ""; + var pos2 = this.str.indexOf(this.delimiter, this.pos); + var pos1 = this.pos; + if (pos2 > this.lineEnd) { + // don't go past end of line + pos2 = this.lineEnd; + } + if (pos2 >= 0) { + this.pos = pos2 + 1; + return this.str.substring(pos1, pos2); + } else { + this.pos = this.lineEnd; + return this.str.substring(pos1, this.lineEnd); + } + }; + + // Return everything from current point until end of line + + SocialCalc.Parse.prototype.RestOfString = function () { + var oldpos = this.pos; + if (this.pos < 0 || this.pos >= this.lineEnd) return ""; + this.pos = this.lineEnd; + return this.str.substring(oldpos, this.lineEnd); + }; + + SocialCalc.Parse.prototype.RestOfStringNoMove = function () { + if (this.pos < 0 || this.pos >= this.lineEnd) return ""; + return this.str.substring(this.pos, this.lineEnd); + }; + + // Move current point to next line + + SocialCalc.Parse.prototype.NextLine = function () { + this.pos = this.lineEnd + 1; + this.lineEnd = this.str.indexOf("\n", this.pos); + if (this.lineEnd < 0) { + this.lineEnd = this.str.length; + } + }; + + // Check to see if at end of string with no more to process + + SocialCalc.Parse.prototype.EOF = function () { + if (this.pos < 0 || this.pos >= this.str.length) return true; + return false; + }; + + // ************************************* + // + // UndoStack class: + // + // Implements the behavior needed for a normal application's undo/redo stack. + // You add a new change sequence with PushChange. + // The type argument is a string that can be used to lookup some general string + // like "typing" or "setting attribute" for the menu prompts for undo/redo. + // + // You add the "do" steps with AddDo. The non-null, non-undefined arguments are + // joined together with " " to make a command string to be saved. + // + // You add the undo steps as commands for the most recent change with AddUndo. + // The non-null, non-undefined arguments are joined together with " " to make + // a command string to be saved. + // + // The Undo and Redo functions move the Top Of Stack pointer through the changes stack + // so you can undo and redo. Doing a new PushChange removes all undone items + // after TOS. + // + // You can push more things than you can undo if you want. + // There is a maximum to remember as the "did" stack for an audit trail (and as redo). This may be unlimited. + // There is a separate maximum to remember that can be undone. This may be smaller than maxRedo. + // + // ************************************* + + SocialCalc.UndoStack = function () { + // properties: + + this.stack = []; // {command: [], type: type, undo: []} -- multiple dos and undos allowed + this.tos = -1; // top of stack position, used for undo/redo + this.maxRedo = 0; // Maximum size of redo stack (and audit trail which is this.stack[n].command) or zero if no limit + this.maxUndo = 50; // Maximum number of steps kept for undo (usually the memory intensive part) or zero if no limit + }; + + SocialCalc.UndoStack.prototype.PushChange = function (type) { + // adding a new thing to the stack + while (this.stack.length > 0 && this.stack.length - 1 > this.tos) { + // pop off things not redone + this.stack.pop(); + } + this.stack.push({ command: [], type: type, undo: [] }); + if (this.maxRedo && this.stack.length > this.maxRedo) { + // limit number kept as audit trail + this.stack.shift(); // remove the extra one + } + if (this.maxUndo && this.stack.length > this.maxUndo) { + // need to trim excess undo info + this.stack[this.stack.length - this.maxUndo - 1].undo = []; // only need to remove one + } + this.tos = this.stack.length - 1; + }; + + SocialCalc.UndoStack.prototype.AddDo = function () { + var args = []; + for (var i = 0; i < arguments.length; i++) { + if (arguments[i] != null) args.push(arguments[i]); // ignore null or undefined + } + var cmd = args.join(" "); + this.stack[this.stack.length - 1].command.push(cmd); + }; + + SocialCalc.UndoStack.prototype.AddUndo = function () { + var args = []; + for (var i = 0; i < arguments.length; i++) { + if (arguments[i] != null) args.push(arguments[i]); // ignore null or undefined + } + var cmd = args.join(" "); + this.stack[this.stack.length - 1].undo.push(cmd); + }; + + SocialCalc.UndoStack.prototype.TOS = function () { + if (this.tos >= 0) return this.stack[this.tos]; + else return null; + }; + + SocialCalc.UndoStack.prototype.Undo = function () { + if ( + this.tos >= 0 && + (!this.maxUndo || this.tos > this.stack.length - this.maxUndo - 1) + ) { + this.tos -= 1; + return true; + } else { + return false; + } + }; + + SocialCalc.UndoStack.prototype.Redo = function () { + if (this.tos < this.stack.length - 1) { + this.tos += 1; + return true; + } else { + return false; + } + }; + + // ************************************* + // + // Clipboard Object: + // + // This is a single object. + // Stores the clipboard, which is shared by all active sheets. + // Like the undo stack, it does not persist from one editing session to another. + // + // ************************************* + + SocialCalc.Clipboard = { + // properties: + + clipboard: "", // empty or string in save format with "copiedfrom:" set to a range + }; + + // ************************************* + // + // RenderContext class: + // + // ************************************* + + SocialCalc.RenderContext = function (sheetobj) { + var parts, num, s; + var attribs = sheetobj.attribs; + var scc = SocialCalc.Constants; + + // properties: + + this.sheetobj = sheetobj; + this.hideRowsCols = false; // Rendering with panes only works with "false" + // !!!! Note: not implemented yet in rendering, just saved as an attribute + this.showGrid = false; + this.showRCHeaders = false; + this.rownamewidth = scc.defaultRowNameWidth; + this.pixelsPerRow = scc.defaultAssumedRowHeight; + + this.cellskip = {}; // if present, coord of cell covering this cell + this.coordToCR = {}; // for cells starting spans, coordToCR[coord]={row:row, col:col} + this.colwidth = []; // precomputed column widths, taking into account defaults + this.totalwidth = 0; // precomputed total table width + + this.rowpanes = []; // for each pane, {first: firstrow, last: lastrow} + this.colpanes = []; // for each pane, {first: firstrow, last: lastrow} + this.maxcol = 0; // max col and row to display, adding long spans, etc. + this.maxrow = 0; + + this.highlights = {}; // for each cell with special display: coord:highlightType (see this.highlightTypes) + this.cursorsuffix = ""; // added to highlights[cr]=="cursor" to get type to lookup + + this.highlightTypes = + // attributes to change when highlit + { + cursor: { + style: scc.defaultHighlightTypeCursorStyle, + className: scc.defaultHighlightTypeCursorClass, + }, + range: { + style: scc.defaultHighlightTypeRangeStyle, + className: scc.defaultHighlightTypeRangeClass, + }, + cursorinsertup: { + style: + "color:#FFF;backgroundColor:#A6A6A6;backgroundRepeat:repeat-x;backgroundPosition:top left;backgroundImage:url(" + + scc.defaultImagePrefix + + "cursorinsertup.gif);", + className: scc.defaultHighlightTypeCursorClass, + }, + cursorinsertleft: { + style: + "color:#FFF;backgroundColor:#A6A6A6;backgroundRepeat:repeat-y;backgroundPosition:top left;backgroundImage:url(" + + scc.defaultImagePrefix + + "cursorinsertleft.gif);", + className: scc.defaultHighlightTypeCursorClass, + }, + range2: { + style: + "color:#000;backgroundColor:#FFF;backgroundImage:url(" + + scc.defaultImagePrefix + + "range2.gif);", + className: "", + }, + }; + + this.cellIDprefix = scc.defaultCellIDPrefix; // if non-null, each cell will render with an ID + + this.defaultlinkstyle = null; // default linkstyle object (allows you to pass values to link renderer) + this.defaultHTMLlinkstyle = { type: "html" }; // default linkstyle for standalone HTML + + // constants: + + this.defaultfontstyle = scc.defaultCellFontStyle; + this.defaultfontsize = scc.defaultCellFontSize; + this.defaultfontfamily = scc.defaultCellFontFamily; + + this.defaultlayout = scc.defaultCellLayout; + + this.defaultpanedividerwidth = scc.defaultPaneDividerWidth; + this.defaultpanedividerheight = scc.defaultPaneDividerHeight; + + this.gridCSS = scc.defaultGridCSS; + + this.commentClassName = scc.defaultCommentClass; // for cells with non-blank comments when this.showGrid is true + this.commentCSS = scc.defaultCommentStyle; // any combination of classnames and styles may be used + this.commentNoGridClassName = scc.defaultCommentNoGridClass; // for cells when this.showGrid is false + this.commentNoGridCSS = scc.defaultCommentNoGridStyle; // any combination of classnames and styles may be used + + this.classnames = + // any combination of classnames and explicitStyles can be used + { + colname: scc.defaultColnameClass, + rowname: scc.defaultRownameClass, + selectedcolname: scc.defaultSelectedColnameClass, + selectedrowname: scc.defaultSelectedRownameClass, + upperleft: scc.defaultUpperLeftClass, + skippedcell: scc.defaultSkippedCellClass, + panedivider: scc.defaultPaneDividerClass, + }; + + this.explicitStyles = + // these may be used so you won't need a stylesheet with the classnames + { + colname: scc.defaultColnameStyle, + rowname: scc.defaultRownameStyle, + selectedcolname: scc.defaultSelectedColnameStyle, + selectedrowname: scc.defaultSelectedRownameStyle, + upperleft: scc.defaultUpperLeftStyle, + skippedcell: scc.defaultSkippedCellStyle, + panedivider: scc.defaultPaneDividerStyle, + }; + + // processed info about cell skipping + + this.cellskip = null; + this.needcellskip = true; + + // precomputed values, filling in defaults indicated by "*" + + this.fonts = []; // for each fontnum, {style: fs, weight: fw, size: fs, family: ff} + this.layouts = []; // for each layout, "padding:Tpx Rpx Bpx Lpx;vertical-align:va;" + + this.needprecompute = true; // need to call PrecomputeSheetFontsAndLayouts + + // if have a sheet object, initialize constants and precomputed values + + if (sheetobj) { + this.rowpanes[0] = { first: 1, last: attribs.lastrow }; + this.colpanes[0] = { first: 1, last: attribs.lastcol }; + } else throw scc.s_rcMissingSheet; + }; + + // Methods: + + SocialCalc.RenderContext.prototype.PrecomputeSheetFontsAndLayouts = + function () { + SocialCalc.PrecomputeSheetFontsAndLayouts(this); + }; + SocialCalc.RenderContext.prototype.CalculateCellSkipData = function () { + SocialCalc.CalculateCellSkipData(this); + }; + SocialCalc.RenderContext.prototype.CalculateColWidthData = function () { + SocialCalc.CalculateColWidthData(this); + }; + SocialCalc.RenderContext.prototype.SetRowPaneFirstLast = function ( + panenum, + first, + last + ) { + this.rowpanes[panenum] = { first: first, last: last }; + }; + SocialCalc.RenderContext.prototype.SetColPaneFirstLast = function ( + panenum, + first, + last + ) { + this.colpanes[panenum] = { first: first, last: last }; + }; + SocialCalc.RenderContext.prototype.CoordInPane = function ( + coord, + rowpane, + colpane + ) { + return SocialCalc.CoordInPane(this, coord, rowpane, colpane); + }; + SocialCalc.RenderContext.prototype.CellInPane = function ( + row, + col, + rowpane, + colpane + ) { + return SocialCalc.CellInPane(this, row, col, rowpane, colpane); + }; + SocialCalc.RenderContext.prototype.InitializeTable = function (tableobj) { + SocialCalc.InitializeTable(this, tableobj); + }; + SocialCalc.RenderContext.prototype.RenderSheet = function ( + oldtable, + linkstyle + ) { + return SocialCalc.RenderSheet(this, oldtable, linkstyle); + }; + SocialCalc.RenderContext.prototype.RenderColGroup = function () { + return SocialCalc.RenderColGroup(this); + }; + SocialCalc.RenderContext.prototype.RenderColHeaders = function () { + return SocialCalc.RenderColHeaders(this); + }; + SocialCalc.RenderContext.prototype.RenderSizingRow = function () { + return SocialCalc.RenderSizingRow(this); + }; + SocialCalc.RenderContext.prototype.RenderRow = function ( + rownum, + rowpane, + linkstyle + ) { + return SocialCalc.RenderRow(this, rownum, rowpane, linkstyle); + }; + SocialCalc.RenderContext.prototype.RenderSpacingRow = function () { + return SocialCalc.RenderSpacingRow(this); + }; + SocialCalc.RenderContext.prototype.RenderCell = function ( + rownum, + colnum, + rowpane, + colpane, + noElement, + linkstyle + ) { + return SocialCalc.RenderCell( + this, + rownum, + colnum, + rowpane, + colpane, + noElement, + linkstyle + ); + }; + + // Functions: + + SocialCalc.PrecomputeSheetFontsAndLayouts = function (context) { + var defaultfont, parts, layoutre, dparts, sparts, num, s, i; + var sheetobj = context.sheetobj; + var attribs = sheetobj.attribs; + + if (attribs.defaultfont) { + defaultfont = sheetobj.fonts[attribs.defaultfont]; + defaultfont = defaultfont.replace( + /^\*/, + SocialCalc.Constants.defaultCellFontStyle + ); + defaultfont = defaultfont.replace( + /(.+)\*(.+)/, + "$1" + SocialCalc.Constants.defaultCellFontSize + "$2" + ); + defaultfont = defaultfont.replace( + /\*$/, + SocialCalc.Constants.defaultCellFontFamily + ); + parts = defaultfont.match(/^(\S+? \S+?) (\S+?) (\S.*)$/); + context.defaultfontstyle = parts[1]; + context.defaultfontsize = parts[2]; + context.defaultfontfamily = parts[3]; + } + + for (num = 1; num < sheetobj.fonts.length; num++) { + // precompute fonts by filling in the *'s + s = sheetobj.fonts[num]; + s = s.replace(/^\*/, context.defaultfontstyle); + s = s.replace(/(.+)\*(.+)/, "$1" + context.defaultfontsize + "$2"); + s = s.replace(/\*$/, context.defaultfontfamily); + parts = s.match(/^(\S+?) (\S+?) (\S+?) (\S.*)$/); + context.fonts[num] = { + style: parts[1], + weight: parts[2], + size: parts[3], + family: parts[4], + }; + } + + layoutre = + /^padding:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+);vertical-align:\s*(\S+);/; + dparts = SocialCalc.Constants.defaultCellLayout.match(layoutre); // get built-in defaults + + if (attribs.defaultlayout) { + sparts = sheetobj.layouts[attribs.defaultlayout].match(layoutre); // get sheet defaults, if set + } else { + sparts = ["", "*", "*", "*", "*", "*"]; + } + + for (num = 1; num < sheetobj.layouts.length; num++) { + // precompute layouts by filling in the *'s + s = sheetobj.layouts[num]; + parts = s.match(layoutre); + for (i = 1; i <= 5; i++) { + if (parts[i] == "*") { + parts[i] = sparts[i] != "*" ? sparts[i] : dparts[i]; // if *, sheet default or built-in + } + } + context.layouts[num] = + "padding:" + + parts[1] + + " " + + parts[2] + + " " + + parts[3] + + " " + + parts[4] + + ";vertical-align:" + + parts[5] + + ";"; + } + + context.needprecompute = false; + }; + + SocialCalc.CalculateCellSkipData = function (context) { + var row, + col, + coord, + cell, + contextcell, + colspan, + rowspan, + skiprow, + skipcol, + skipcoord; + + var sheetobj = context.sheetobj; + var sheetrowattribs = sheetobj.rowattribs; + var sheetcolattribs = sheetobj.colattribs; + context.maxrow = 0; + context.maxcol = 0; + context.cellskip = {}; // reset + + // Calculate cellskip data + var maxrow, maxcol; + + for (row = 1; row <= sheetobj.attribs.lastrow; row++) { + for (col = 1; col <= sheetobj.attribs.lastcol; col++) { + // look for spans and set cellskip for skipped cells + coord = SocialCalc.crToCoord(col, row); + cell = sheetobj.cells[coord]; + // don't look at undefined cells (they have no spans) or skipped cells + if (cell === undefined || context.cellskip[coord]) continue; + colspan = cell.colspan || 1; + rowspan = cell.rowspan || 1; + if (colspan > 1 || rowspan > 1) { + for (skiprow = row; skiprow < row + rowspan; skiprow++) { + for (skipcol = col; skipcol < col + colspan; skipcol++) { + // do the setting on individual cells + skipcoord = SocialCalc.crToCoord(skipcol, skiprow); + if (skipcoord == coord) { + // for coord, remember row and col + context.coordToCR[coord] = { row: row, col: col }; + } else { + // for other cells, flag with coord of here + context.cellskip[skipcoord] = coord; + } + if (skiprow > context.maxrow) maxrow = skiprow; + if (skipcol > context.maxcol) maxcol = skipcol; + } + } + } + } + } + + context.needcellskip = false; + }; + + SocialCalc.CalculateColWidthData = function (context) { + var colnum, colname, colwidth, totalwidth; + + var sheetobj = context.sheetobj; + var sheetcolattribs = sheetobj.colattribs; + + // Calculate column width data + + totalwidth = context.showRCHeaders ? context.rownamewidth - 0 : 0; + for (var colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + colname = SocialCalc.rcColname(colnum); + colwidth = + sheetobj.colattribs.width[colname] || + sheetobj.attribs.defaultcolwidth || + SocialCalc.Constants.defaultColWidth; + if (colwidth == "blank" || colwidth == "auto") colwidth = ""; + context.colwidth[colnum] = colwidth + ""; + totalwidth += colwidth && colwidth - 0 > 0 ? colwidth - 0 : 10; + } + } + context.totalwidth = totalwidth; + }; + + SocialCalc.InitializeTable = function (context, tableobj) { + /* + + Uses border-collapse so corners don't have holes + Note: IE and Firefox handle differently (IE adds borders and padding) + under border-collapse and Safari has problems with and wide text + Tablelayout "fixed" also leads to problems + + */ + + /* + + *** Discussion *** + + The rendering assumes fixed column widths, even though SocialCalc allows "auto". + There may be issues with "auto" and it is hard to make it work cross-browser + with border-collapse, etc. + + This and the RenderSheet routine are where in the code the specifics of + table attributes and column size definitions are set. As the browsers settle down + and when we decide if we don't need auto width, we may want to revisit the way the + code does this (e.g., use table-layout:fixed). + + */ + tableobj.style.borderCollapse = "collapse"; + tableobj.cellSpacing = "0"; + tableobj.cellPadding = "0"; + + tableobj.style.width = context.totalwidth + "px"; + }; + + // + // tableobj = SocialCalc.RenderSheet(context, oldtable, linkstyle) + // + // Renders a render context returning a DOM table object. + // If there is an oldtable object, it replaces it in the parent node. + // If oldtable is null, it just returns the new one. + // The linkstyle is "" or null for editing rendering + // and optionally an object passed on to formatting code. + // + + SocialCalc.RenderSheet = function (context, oldtable, linkstyle) { + var newrow, rowpane; + var tableobj, colgroupobj, tbodyobj, parentnode; + + // do precompute stuff if necessary + + if (context.sheetobj.changedrendervalues) { + context.needcellskip = true; + context.needprecompute = true; + context.sheetobj.changedrendervalues = false; + } + if (context.needcellskip) { + context.CalculateCellSkipData(); + } + if (context.needprecompute) { + context.PrecomputeSheetFontsAndLayouts(); + } + + context.CalculateColWidthData(); // always make sure col width values are up to date + + // make the table element and fill it in + + tableobj = document.createElement("table"); + context.InitializeTable(tableobj); + + colgroupobj = context.RenderColGroup(); + tableobj.appendChild(colgroupobj); + + tbodyobj = document.createElement("tbody"); + + tbodyobj.appendChild(context.RenderSizingRow()); + + if (context.showRCHeaders) { + newrow = context.RenderColHeaders(); + if (newrow) tbodyobj.appendChild(newrow); + } + + for (rowpane = 0; rowpane < context.rowpanes.length; rowpane++) { + for ( + var rownum = context.rowpanes[rowpane].first; + rownum <= context.rowpanes[rowpane].last; + rownum++ + ) { + newrow = context.RenderRow(rownum, rowpane, linkstyle); + tbodyobj.appendChild(newrow); + } + if (rowpane < context.rowpanes.length - 1) { + newrow = context.RenderSpacingRow(); + tbodyobj.appendChild(newrow); + } + } + + tableobj.appendChild(tbodyobj); + + if (oldtable) { + parentnode = oldtable.parentNode; + if (parentnode) parentnode.replaceChild(tableobj, oldtable); + } + + SocialCalc.EvalUserScripts(); + + return tableobj; + }; + + SocialCalc.RenderRow = function (context, rownum, rowpane, linkstyle) { + var sheetobj = context.sheetobj; + + var result = document.createElement("tr"); + var colnum, newcol, colpane, newdiv; + + if (context.showRCHeaders) { + newcol = document.createElement("td"); + if (context.classnames) newcol.className = context.classnames.rowname; + if (context.explicitStyles) + newcol.style.cssText = context.explicitStyles.rowname; + newcol.width = context.rownamewidth; + newcol.style.verticalAlign = "top"; // to get around Safari making top of centered row number be + // considered top of row (and can't get position in Safari) + if (!SocialCalc.Constants.SCNoRowName) { + newcol.innerHTML = rownum + ""; + } + result.appendChild(newcol); + } + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + newcol = context.RenderCell( + rownum, + colnum, + rowpane, + colpane, + null, + linkstyle + ); + if (newcol) result.appendChild(newcol); + } + if (colpane < context.colpanes.length - 1) { + newcol = document.createElement("td"); + newcol.width = context.defaultpanedividerwidth; + if (context.classnames.panedivider) + newcol.className = context.classnames.panedivider; + if (context.explicitStyles.panedivider) + newcol.style.cssText = context.explicitStyles.panedivider; + newdiv = document.createElement("div"); // for Firefox to avoid squishing + newdiv.style.width = context.defaultpanedividerwidth + "px"; + newdiv.style.overflow = "hidden"; + newcol.appendChild(newdiv); + result.appendChild(newcol); + } + } + return result; + }; + + SocialCalc.RenderSpacingRow = function (context) { + var colnum, newcol, colpane, w; + + var sheetobj = context.sheetobj; + + var result = document.createElement("tr"); + + if (context.showRCHeaders) { + newcol = document.createElement("td"); + newcol.width = context.rownamewidth; + newcol.height = context.defaultpanedividerheight; + if (context.classnames.panedivider) + newcol.className = context.classnames.panedivider; + if (context.explicitStyles.panedivider) + newcol.style.cssText = context.explicitStyles.panedivider; + result.appendChild(newcol); + } + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + newcol = document.createElement("td"); + w = context.colwidth[colnum]; + if (w) newcol.width = w; + newcol.height = context.defaultpanedividerheight; + if (context.classnames.panedivider) + newcol.className = context.classnames.panedivider; + if (context.explicitStyles.panedivider) + newcol.style.cssText = context.explicitStyles.panedivider; + if (newcol) result.appendChild(newcol); + } + if (colpane < context.colpanes.length - 1) { + newcol = document.createElement("td"); + newcol.width = context.defaultpanedividerwidth; + newcol.height = context.defaultpanedividerheight; + if (context.classnames.panedivider) + newcol.className = context.classnames.panedivider; + if (context.explicitStyles.panedivider) + newcol.style.cssText = context.explicitStyles.panedivider; + result.appendChild(newcol); + } + } + return result; + }; + + SocialCalc.RenderColHeaders = function (context) { + var sheetobj = context.sheetobj; + + var result = document.createElement("tr"); + var colnum, newcol; + + if (!context.showRCHeaders) return null; + + newcol = document.createElement("td"); + if (context.classnames) newcol.className = context.classnames.upperleft; + if (context.explicitStyles) + newcol.style.cssText = context.explicitStyles.upperleft; + newcol.width = context.rownamewidth; + result.appendChild(newcol); + + for (var colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + newcol = document.createElement("td"); + if (context.classnames) newcol.className = context.classnames.colname; + if (context.explicitStyles) + newcol.style.cssText = context.explicitStyles.colname; + if (SocialCalc.Constants.SCNoColNames) { + // newcol.innerHTML=" "; + // newcol.innerHTML=""; + } else { + newcol.innerHTML = SocialCalc.rcColname(colnum); + } + result.appendChild(newcol); + } + if (colpane < context.colpanes.length - 1) { + newcol = document.createElement("td"); + newcol.width = context.defaultpanedividerwidth; + if (context.classnames.panedivider) + newcol.className = context.classnames.panedivider; + if (context.explicitStyles.panedivider) + newcol.style.cssText = context.explicitStyles.panedivider; + result.appendChild(newcol); + } + } + return result; + }; + + SocialCalc.RenderColGroup = function (context) { + var colpane, colnum, newcol, t; + var sheetobj = context.sheetobj; + + var result = document.createElement("colgroup"); + + if (context.showRCHeaders) { + newcol = document.createElement("col"); + newcol.width = context.rownamewidth; + result.appendChild(newcol); + } + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + newcol = document.createElement("col"); + t = context.colwidth[colnum]; + if (t) newcol.width = t; + result.appendChild(newcol); + } + if (colpane < context.colpanes.length - 1) { + newcol = document.createElement("col"); + newcol.width = context.defaultpanedividerwidth; + result.appendChild(newcol); + } + } + return result; + }; + + SocialCalc.RenderSizingRow = function (context) { + var colpane, colnum, newcell, t; + var sheetobj = context.sheetobj; + + var result = document.createElement("tr"); + + if (context.showRCHeaders) { + newcell = document.createElement("td"); + newcell.style.width = context.rownamewidth + "px"; + newcell.height = "1"; + result.appendChild(newcell); + } + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + newcell = document.createElement("td"); + t = context.colwidth[colnum]; + if (t) newcell.width = t; + newcell.height = "1"; + result.appendChild(newcell); + } + if (colpane < context.colpanes.length - 1) { + newcell = document.createElement("td"); + newcell.width = context.defaultpanedividerwidth; + newcell.height = "1"; + result.appendChild(newcell); + } + } + return result; + }; + + SocialCalc.RenderCell = function ( + context, + rownum, + colnum, + rowpane, + colpane, + noElement, + linkstyle + ) { + var sheetobj = context.sheetobj; + + var num, t, result, span, stylename, cell, sheetattribs, scdefaults; + var stylestr = ""; + + rownum = rownum - 0; // make sure a number + colnum = colnum - 0; + + var coord = SocialCalc.crToCoord(colnum, rownum); + + if (context.cellskip[coord]) { + // skip if within a span + if (context.CoordInPane(context.cellskip[coord], rowpane, colpane)) { + return null; // span starts in this pane -- so just skip + } + result = noElement + ? SocialCalc.CreatePseudoElement() + : document.createElement("td"); // span start is scrolled away, so make a special cell + if (context.classnames.skippedcell) + result.className = context.classnames.skippedcell; + if (context.explicitStyles.skippedcell) + result.style.cssText = context.explicitStyles.skippedcell; + result.innerHTML = " "; // put something there so height is OK + // !!! Really need to add borders in case there isn't anything else shown in the pane to get height + return result; + } + + result = noElement + ? SocialCalc.CreatePseudoElement() + : document.createElement("td"); + + if (context.cellIDprefix) { + result.id = context.cellIDprefix + coord; + } + + cell = sheetobj.cells[coord]; + + if (!cell) { + cell = new SocialCalc.Cell(coord); + } + + sheetattribs = sheetobj.attribs; + var scc = SocialCalc.Constants; + + if (cell.colspan > 1) { + span = 1; + for (num = 1; num < cell.colspan; num++) { + if ( + sheetobj.colattribs.hide[SocialCalc.rcColname(colnum + num)] != + "yes" && + context.CellInPane(rownum, colnum + num, rowpane, colpane) + ) { + span++; + } + } + result.colSpan = span; + } + + if (cell.rowspan > 1) { + span = 1; + for (num = 1; num < cell.rowspan; num++) { + if ( + sheetobj.rowattribs.hide[rownum + num + ""] != "yes" && + context.CellInPane(rownum + num, colnum, rowpane, colpane) + ) + span++; + } + result.rowSpan = span; + } + + if (cell.displaystring == undefined) { + // cache the display value + cell.displaystring = SocialCalc.FormatValueForDisplay( + sheetobj, + cell.datavalue, + coord, + linkstyle || context.defaultlinkstyle + ); + } else { + // callout to execute scripts if needed + SocialCalc.CallOutOnRenderCell(sheetobj, cell.datavalue, coord); + } + result.innerHTML = cell.displaystring; + + num = cell.layout || sheetattribs.defaultlayout; + if (num) { + stylestr += context.layouts[num]; // use precomputed layout with "*"'s filled in + } else { + stylestr += scc.defaultCellLayout; + } + + num = cell.font || sheetattribs.defaultfont; + if (num) { + // get expanded font strings in context + t = context.fonts[num]; // do each - plain "font:" style sets all sorts of other values, too (Safari font-stretch problem on cssText) + stylestr += + "font-style:" + + t.style + + ";font-weight:" + + t.weight + + ";font-size:" + + t.size + + ";font-family:" + + t.family + + ";"; + } else { + if (scc.defaultCellFontSize) { + stylestr += "font-size:" + scc.defaultCellFontSize + ";"; + } + if (scc.defaultCellFontFamily) { + stylestr += "font-family:" + scc.defaultCellFontFamily + ";"; + } + } + + num = cell.color || sheetattribs.defaultcolor; + if (num) stylestr += "color:" + sheetobj.colors[num] + ";"; + + num = cell.bgcolor || sheetattribs.defaultbgcolor; + if (num) stylestr += "background-color:" + sheetobj.colors[num] + ";"; + + num = cell.cellformat; + if (num) { + stylestr += "text-align:" + sheetobj.cellformats[num] + ";"; + } else { + t = cell.valuetype.charAt(0); + if (t == "t") { + num = sheetattribs.defaulttextformat; + if (num) stylestr += "text-align:" + sheetobj.cellformats[num] + ";"; + } else if ((t = "n")) { + num = sheetattribs.defaultnontextformat; + if (num) { + stylestr += "text-align:" + sheetobj.cellformats[num] + ";"; + } else { + stylestr += "text-align:right;"; + } + } else stylestr += "text-align:left;"; + } + + num = cell.bt; + if (num) stylestr += "border-top:" + sheetobj.borderstyles[num] + ";"; + + num = cell.br; + if (num) stylestr += "border-right:" + sheetobj.borderstyles[num] + ";"; + else if (context.showGrid) { + if ( + context.CellInPane( + rownum, + colnum + (cell.colspan || 1), + rowpane, + colpane + ) + ) + t = SocialCalc.crToCoord(colnum + (cell.colspan || 1), rownum); + else t = "nomatch"; + if (context.cellskip[t]) t = context.cellskip[t]; + if (!sheetobj.cells[t] || !sheetobj.cells[t].bl) + stylestr += "border-right:" + context.gridCSS; + } + + num = cell.bb; + if (num) stylestr += "border-bottom:" + sheetobj.borderstyles[num] + ";"; + else if (context.showGrid) { + if ( + context.CellInPane( + rownum + (cell.rowspan || 1), + colnum, + rowpane, + colpane + ) + ) + t = SocialCalc.crToCoord(colnum, rownum + (cell.rowspan || 1)); + else t = "nomatch"; + if (context.cellskip[t]) t = context.cellskip[t]; + if (!sheetobj.cells[t] || !sheetobj.cells[t].bt) + stylestr += "border-bottom:" + context.gridCSS; + } + + num = cell.bl; + if (num) stylestr += "border-left:" + sheetobj.borderstyles[num] + ";"; + + if (cell.comment) { + if (context.showGrid) { + if (context.commentClassName) { + result.className = + (result.className ? result.className + " " : "") + + context.commentClassName; + } + stylestr += context.commentCSS; + } else { + if (context.commentNoGridClassName) { + result.className = + (result.className ? result.className + " " : "") + + context.commentNoGridClassName; + } + stylestr += context.commentNoGridCSS; + } + } + + result.style.cssText = stylestr; + + //!!!!!!!!! + // NOTE: csss and cssc are not supported yet. + // csss needs to be parsed into pieces to override just the attributes specified, not all with assignment to cssText. + // cssc just needs to set the className. + + t = context.highlights[coord]; + if (t) { + // this is a highlit cell: Override style appropriately + if (t == "cursor") t += context.cursorsuffix; // cursor can take alternative forms + if (context.highlightTypes[t].className) { + result.className = + (result.className ? result.className + " " : "") + + context.highlightTypes[t].className; + } + // only if cell is editable, set the cursor class + if ( + t == "cursor" && + SocialCalc.Callbacks.IsCoordEditable && + !SocialCalc.Callbacks.IsCoordEditable( + context.sheetobj.sheetname + "!" + coord + ) + ) { + SocialCalc.setStyles(result, ""); + } else { + SocialCalc.setStyles(result, context.highlightTypes[t].style); + } + } + + return result; + }; + + SocialCalc.CoordInPane = function (context, coord, rowpane, colpane) { + var coordToCR = context.coordToCR[coord]; + if (!coordToCR || !coordToCR.row || !coordToCR.col) + throw "Bad coordToCR for " + coord; + return context.CellInPane(coordToCR.row, coordToCR.col, rowpane, colpane); + }; + + SocialCalc.CellInPane = function (context, row, col, rowpane, colpane) { + var panerowlimits = context.rowpanes[rowpane]; + var panecollimits = context.colpanes[colpane]; + if (!panerowlimits || !panecollimits) + throw "CellInPane called with unknown panes " + rowpane + "/" + colpane; + if (row < panerowlimits.first || row > panerowlimits.last) return false; + if (col < panecollimits.first || col > panecollimits.last) return false; + return true; + }; + + SocialCalc.CreatePseudoElement = function () { + return { style: { cssText: "" }, innerHTML: "", className: "" }; + }; + + // ************************************* + // + // Misc. functions: + // + // ************************************* + + SocialCalc.rcColname = function (c) { + if (c > 702) c = 702; // maximum number of columns - ZZ + if (c < 1) c = 1; + var collow = ((c - 1) % 26) + 65; + var colhigh = Math.floor((c - 1) / 26); + if (colhigh) + return String.fromCharCode(colhigh + 64) + String.fromCharCode(collow); + else return String.fromCharCode(collow); + }; + + SocialCalc.letters = [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + ]; + + SocialCalc.crToCoord = function (c, r) { + var result; + if (c < 1) c = 1; + if (c > 702) c = 702; // maximum number of columns - ZZ + if (r < 1) r = 1; + var collow = (c - 1) % 26; + var colhigh = Math.floor((c - 1) / 26); + if (colhigh) + result = SocialCalc.letters[colhigh - 1] + SocialCalc.letters[collow] + r; + else result = SocialCalc.letters[collow] + r; + return result; + }; + + SocialCalc.coordToCol = {}; // too expensive to set in crToCoord since that is called so many times + SocialCalc.coordToRow = {}; + + SocialCalc.coordToCr = function (cr) { + var c, i, ch; + var r = SocialCalc.coordToRow[cr]; + if (r) return { row: r, col: SocialCalc.coordToCol[cr] }; + c = 0; + r = 0; + for (i = 0; i < cr.length; i++) { + // this was faster than using regexes; assumes well-formed + ch = cr.charCodeAt(i); + if (ch == 36); + else if (ch <= 57) + // skip $'s + r = 10 * r + ch - 48; + else if (ch >= 97) c = 26 * c + ch - 96; + else if (ch >= 65) c = 26 * c + ch - 64; + } + SocialCalc.coordToCol[cr] = c; + SocialCalc.coordToRow[cr] = r; + return { row: r, col: c }; + }; + + SocialCalc.ParseRange = function (range) { + var pos, cr, cr1, cr2; + if (!range) range = "A1:A1"; // error return, hopefully benign + range = range.toUpperCase(); + pos = range.indexOf(":"); + if (pos >= 0) { + cr = range.substring(0, pos); + cr1 = SocialCalc.coordToCr(cr); + cr1.coord = cr; + cr = range.substring(pos + 1); + cr2 = SocialCalc.coordToCr(cr); + cr2.coord = cr; + } else { + cr1 = SocialCalc.coordToCr(range); + cr1.coord = range; + cr2 = SocialCalc.coordToCr(range); + cr2.coord = range; + } + return { cr1: cr1, cr2: cr2 }; + }; + + SocialCalc.decodeFromSave = function (s) { + if (typeof s != "string") return s; + if (s.indexOf("\\") == -1) return s; // for performace reasons: replace nothing takes up time + var r = s.replace(/\\c/g, ":"); + r = r.replace(/\\n/g, "\n"); + return r.replace(/\\b/g, "\\"); + }; + + SocialCalc.decodeFromAjax = function (s) { + if (typeof s != "string") return s; + if (s.indexOf("\\") == -1) return s; // for performace reasons: replace nothing takes up time + var r = s.replace(/\\c/g, ":"); + r = r.replace(/\\n/g, "\n"); + r = r.replace(/\\e/g, "]]"); + return r.replace(/\\b/g, "\\"); + }; + + SocialCalc.encodeForSave = function (s) { + if (typeof s != "string") return s; + if (s.indexOf("\\") != -1) + // for performace reasons: replace nothing takes up time + s = s.replace(/\\/g, "\\b"); + if (s.indexOf(":") != -1) s = s.replace(/:/g, "\\c"); + if (s.indexOf("\n") != -1) s = s.replace(/\n/g, "\\n"); + return s; + }; + + // + // Returns estring where &, <, >, " are HTML escaped + // + SocialCalc.special_chars = function (string) { + if (/[&<>"]/.test(string)) { + // only do "slow" replaces if something to replace + string = string.replace(/&/g, "&"); + string = string.replace(//g, ">"); + string = string.replace(/"/g, """); + } + return string; + }; + + SocialCalc.Lookup = function (value, list) { + for (i = 0; i < list.length; i++) { + if (list[i] > value) { + if (i > 0) return i - 1; + else return null; + } + } + return list.length - 1; // if all smaller, matches last + }; + + // + // setStyles(element, cssText) + // + // Takes a pseudo style string (e.g., text-align must be textAlign) and sets + // the element's style value for each style name listed (leaving others unchanged). + // OK to call with null cssText. + // + + SocialCalc.setStyles = function (element, cssText) { + var parts, part, pos, name, value; + + if (!cssText) return; + + parts = cssText.split(";"); + for (part = 0; part < parts.length; part++) { + pos = parts[part].indexOf(":"); // find first colon (could be one in url) + if (pos != -1) { + name = parts[part].substring(0, pos); + value = parts[part].substring(pos + 1); + if (name && value) { + // if non-null name and value, set style + element.style[name] = value; + } + } + // namevalue = parts[part].split(":"); + // if (namevalue[0]) element.style[namevalue[0]] = namevalue[1]; + } + }; + + // + // GetViewportInfo() - returns object with viewport width and height, and scroll offsets + // + // Flanagan, JavaScript, 5th Edition, page 276 + // + + SocialCalc.GetViewportInfo = function () { + var result = {}; + + if (window.innerWidth) { + // all but IE + result.width = window.innerWidth; + result.height = window.innerHeight; + result.horizontalScroll = window.pageXOffset; + result.verticalScroll = window.pageYOffset; + } else { + if (document.documentElement && document.documentElement.clientWidth) { + result.width = document.documentElement.clientWidth; + result.height = document.documentElement.clientHeight; + result.horizontalScroll = document.documentElement.scrollLeft; + result.verticalScroll = document.documentElement.scrollTop; + } else if (document.body.clientWidth) { + result.width = document.body.clientWidth; + result.height = document.body.clientHeight; + result.horizontalScroll = document.body.scrollLeft; + result.verticalScroll = document.body.scrollTop; + } + } + + return result; + }; + + // + // GetElementPosition(element) - returns object with left and top position of the element in the document + // + // Goodman's JavaScript & DHTML Cookbook, 2nd Edition, page 415 + // + + SocialCalc.GetElementPosition = function (element) { + var offsetLeft = 0; + var offsetTop = 0; + while (element) { + offsetLeft += element.offsetLeft; + offsetTop += element.offsetTop; + element = element.offsetParent; + } + return { left: offsetLeft, top: offsetTop }; + }; + + // + // GetElementPositionWithScroll(element) - returns object with left and top position of the element in the document + // + // Takes into account scroll offsets by going through entire tree + // + + SocialCalc.GetElementPositionWithScroll = function (element) { + var offsetLeft = 0; + var offsetTop = 0; + var offsetElement = element; + while (element) { + if (element.tagName == "HTML") break; + if (element == offsetElement) { + offsetLeft += element.offsetLeft; + offsetTop += element.offsetTop; + offsetElement = element.offsetParent; + } + if (element.scrollLeft) { + offsetLeft -= element.scrollLeft; + } + if (element.scrollTop) { + offsetTop -= element.scrollTop; + } + element = element.parentNode; + } + return { left: offsetLeft, top: offsetTop }; + }; + + // + // LookupElement(element, array) - returns array element which is an object with "element" of element + // + + SocialCalc.LookupElement = function (element, array) { + var i; + for (i = 0; i < array.length; i++) { + if (array[i].element == element) return array[i]; + } + return null; + }; + + // + // AssignID(obj, element, id) - Optionally assigns an ID with a prefix to the element + // + + SocialCalc.AssignID = function (obj, element, id) { + if (obj.idPrefix) { + // Object must have a non-empty idPrefix attribute + element.id = obj.idPrefix + id; + } + }; + + // + // SocialCalc.GetCellContents(sheetobj, coord) + // + // Returns the contents (value, formula, constant, etc.) of a cell + // with appropriate prefix ("'", "=", etc.) + // + + SocialCalc.GetCellContents = function (sheetobj, coord) { + var result = ""; + var cellobj = sheetobj.cells[coord]; + if (cellobj) { + switch (cellobj.datatype) { + case "v": + result = cellobj.datavalue + ""; + break; + case "t": + result = "'" + cellobj.datavalue; + break; + case "f": + result = "=" + cellobj.formula; + break; + case "c": + result = cellobj.formula; + break; + default: + break; + } + } + + return result; + }; + + // + // Routines translated from the SocialCalc 1.1.0 Perl code: + // + // (Makes use of the FormatNumber JavaScript code translated from the Perl.) + // + + // + // displayvalue = FormatValueForDisplay(sheetobj, value, cr, linkstyle) + // + // Returns a string, in HTML, for the contents of a cell. + // + // The value is a either numeric or text, the cr is the coord of the cell + // (its cell properties are used to determine formatting), and linkstyle + // is a value passed to wiki-text expansion routines specifying the + // purpose of the rendering so, for example, links can be rendered differently + // during edit than with plain HTML. + // + + SocialCalc.FormatValueForDisplay = function (sheetobj, value, cr, linkstyle) { + var valueformat, has_parens, has_commas, valuetype, valuesubtype; + var displayvalue; + + var sheetattribs = sheetobj.attribs; + var scc = SocialCalc.Constants; + + var cell = sheetobj.cells[cr]; + + if (!cell) { + // get an empty cell if not there + cell = new SocialCalc.Cell(cr); + } + + displayvalue = value; + + valuetype = cell.valuetype || ""; // get type of value to determine formatting + valuesubtype = valuetype.substring(1); + valuetype = valuetype.charAt(0); + + if (cell.errors || valuetype == "e") { + displayvalue = cell.errors || valuesubtype || "Error in cell"; + return displayvalue; + } + + if (valuetype == "t") { + valueformat = + sheetobj.valueformats[cell.textvalueformat - 0] || + sheetobj.valueformats[sheetattribs.defaulttextvalueformat - 0] || + ""; + if (valueformat == "formula") { + if (cell.datatype == "f") { + displayvalue = + SocialCalc.special_chars("=" + cell.formula) || " "; + } else if (cell.datatype == "c") { + displayvalue = + SocialCalc.special_chars("'" + cell.formula) || " "; + } else { + displayvalue = + SocialCalc.special_chars("'" + displayvalue) || " "; + } + return displayvalue; + } + displayvalue = SocialCalc.format_text_for_display( + displayvalue, + cell.valuetype, + valueformat, + sheetobj, + linkstyle + ); + if (valueformat == "text-html") + SocialCalc.ScriptCheck(sheetobj.sheetid, cr, value); + } else if (valuetype == "n") { + valueformat = cell.nontextvalueformat; + if (valueformat == null || valueformat == "") { + // + valueformat = sheetattribs.defaultnontextvalueformat; + } + valueformat = sheetobj.valueformats[valueformat - 0]; + if (valueformat == null || valueformat == "none") { + valueformat = ""; + } + if (valueformat == "formula") { + if (cell.datatype == "f") { + displayvalue = + SocialCalc.special_chars("=" + cell.formula) || " "; + } else if (cell.datatype == "c") { + displayvalue = + SocialCalc.special_chars("'" + cell.formula) || " "; + } else { + displayvalue = + SocialCalc.special_chars("'" + displayvalue) || " "; + } + return displayvalue; + } else if (valueformat == "forcetext") { + if (cell.datatype == "f") { + displayvalue = + SocialCalc.special_chars("=" + cell.formula) || " "; + } else if (cell.datatype == "c") { + displayvalue = SocialCalc.special_chars(cell.formula) || " "; + } else { + displayvalue = SocialCalc.special_chars(displayvalue) || " "; + } + return displayvalue; + } + + displayvalue = SocialCalc.format_number_for_display( + displayvalue, + cell.valuetype, + valueformat + ); + } else { + // unknown type - probably blank + displayvalue = " "; + } + + return displayvalue; + }; + + // + // displayvalue = format_text_for_display(rawvalue, valuetype, valueformat, sheetobj, linkstyle) + // + + SocialCalc.format_text_for_display = function ( + rawvalue, + valuetype, + valueformat, + sheetobj, + linkstyle + ) { + var valueformat, valuesubtype, dvsc, dvue, textval; + var displayvalue; + + valuesubtype = valuetype.substring(1); + + displayvalue = rawvalue; + + if (valueformat == "none" || valueformat == null) valueformat = ""; + if (!/^(text-|custom|hidden)/.test(valueformat)) valueformat = ""; + if (valueformat == "" || valueformat == "General") { + // determine format from type + if (valuesubtype == "h") valueformat = "text-html"; + if (valuesubtype == "w" || valuesubtype == "r") valueformat = "text-wiki"; + if (valuesubtype == "l") valueformat = "text-link"; + if (!valuesubtype) valueformat = "text-plain"; + } + if (valueformat == "text-html") { + // HTML - output as it as is + } else if ( + SocialCalc.Callbacks.expand_wiki && + /^text-wiki/.test(valueformat) + ) { + // do general wiki markup + displayvalue = SocialCalc.Callbacks.expand_wiki( + displayvalue, + sheetobj, + linkstyle, + valueformat + ); + } else if (valueformat == "text-wiki") { + // Wiki-text - encode then output + displayvalue = SocialCalc.special_chars(displayvalue); + } else if (valueformat == "text-wiki") { + // wiki text + displayvalue = + (SocialCalc.Callbacks.expand_markup && + SocialCalc.Callbacks.expand_markup( + displayvalue, + sheetobj, + linkstyle + )) || // do old wiki markup + SocialCalc.special_chars(displayvalue); + } else if (valueformat == "text-url") { + // text is a URL for a link + dvsc = SocialCalc.special_chars(displayvalue); + dvue = encodeURI(displayvalue); + displayvalue = '' + dvsc + ""; + } else if (valueformat == "text-link") { + // more extensive link capabilities for regular web links + displayvalue = SocialCalc.expand_text_link( + displayvalue, + sheetobj, + linkstyle, + valueformat + ); + } else if (valueformat == "text-image") { + // text is a URL for an image + dvue = encodeURI(displayvalue); + displayvalue = ''; + } else if (valueformat.substring(0, 12) == "text-custom:") { + // construct a custom text format: @r = text raw, @s = special chars, @u = url encoded + dvsc = SocialCalc.special_chars(displayvalue); // do special chars + dvsc = dvsc.replace(/ /g, "  "); // keep multiple spaces + dvsc = dvsc.replace(/\n/g, "
"); // keep line breaks + dvue = encodeURI(displayvalue); + textval = {}; + textval.r = displayvalue; + textval.s = dvsc; + textval.u = dvue; + displayvalue = valueformat.substring(12); // remove "text-custom:" + displayvalue = displayvalue.replace(/@(r|s|u)/g, function (a, c) { + return textval[c]; + }); // replace placeholders + } else if (valueformat.substring(0, 6) == "custom") { + // custom + displayvalue = SocialCalc.special_chars(displayvalue); // do special chars + displayvalue = displayvalue.replace(/ /g, "  "); // keep multiple spaces + displayvalue = displayvalue.replace(/\n/g, "
"); // keep line breaks + displayvalue += " (custom format)"; + } else if (valueformat == "hidden") { + displayvalue = " "; + } else { + // plain text + displayvalue = SocialCalc.special_chars(displayvalue); // do special chars + displayvalue = displayvalue.replace(/ /g, "  "); // keep multiple spaces + displayvalue = displayvalue.replace(/\n/g, "
"); // keep line breaks + } + + return displayvalue; + }; + + // + // displayvalue = format_number_for_display(rawvalue, valuetype, valueformat) + // + + SocialCalc.format_number_for_display = function ( + rawvalue, + valuetype, + valueformat + ) { + var value, valuesubtype; + var scc = SocialCalc.Constants; + + value = rawvalue - 0; + + valuesubtype = valuetype.substring(1); + + if (valueformat == "Auto" || valueformat == "") { + // cases with default format + if (valuesubtype == "%") { + // will display a % character + valueformat = "#,##0.0%"; + } else if (valuesubtype == "$") { + valueformat = "[$]#,##0.00"; + } else if (valuesubtype == "dt") { + valueformat = scc.defaultFormatdt; + } else if (valuesubtype == "d") { + valueformat = scc.defaultFormatd; + } else if (valuesubtype == "t") { + valueformat = scc.defaultFormatt; + } else if (valuesubtype == "l") { + valueformat = "logical"; + } else { + valueformat = "General"; + } + } + + if (valueformat == "logical") { + // do logical format + return value ? scc.defaultDisplayTRUE : scc.defaultDisplayFALSE; + } + + if (valueformat == "hidden") { + // do hidden format + return " "; + } + + // Use format + + return SocialCalc.FormatNumber.formatNumberWithFormat( + rawvalue, + valueformat, + "" + ); + }; + + // + // valueinfo = DetermineValueType(rawvalue) + // + // Takes a value and looks for special formatting like $, %, numbers, etc. + // Returns the value as a number or string and the type as {value: value, type: type}. + // Tries to follow the spec for spreadsheet function VALUE(v). + // + + SocialCalc.DetermineValueType = function (rawvalue) { + var value = rawvalue + ""; + var type = "t"; + var tvalue, matches, year, hour, minute, second, denom, num, intgr, constr; + + tvalue = value.replace(/^\s+/, ""); // remove leading and trailing blanks + tvalue = tvalue.replace(/\s+$/, ""); + + if (value.length == 0) { + type = ""; + } else if (value.match(/^\s+$/)) { + // just blanks + // leave type "t" + } else if (tvalue.match(/^[-+]?\d*(?:\.)?\d*(?:[eE][-+]?\d+)?$/)) { + // general number, including E + value = tvalue - 0; // try converting to number + if (isNaN(value)) { + // leave alone - catches things like plain "-" + value = rawvalue + ""; + } else { + type = "n"; + } + } else if (tvalue.match(/^[-+]?\d*(?:\.)?\d*\s*%$/)) { + // percent form: 15.1% + value = (tvalue.slice(0, -1) - 0) / 100; // convert and scale + type = "n%"; + } else if ( + tvalue.match(/^[-+]?\$\s*\d*(?:\.)?\d*\s*$/) && + tvalue.match(/\d/) + ) { + // $ format: $1.49 + value = tvalue.replace(/\$/, "") - 0; + type = "n$"; + } else if (tvalue.match(/^[-+]?(\d*,\d*)+(?:\.)?\d*$/)) { + // number format ignoring commas: 1,234.49 + value = tvalue.replace(/,/g, "") - 0; + type = "n"; + } else if (tvalue.match(/^[-+]?(\d*,\d*)+(?:\.)?\d*\s*%$/)) { + // % with commas: 1,234.49% + value = (tvalue.replace(/[%,]/g, "") - 0) / 100; + type = "n%"; + } else if ( + tvalue.match(/^[-+]?\$\s*(\d*,\d*)+(?:\.)?\d*$/) && + tvalue.match(/\d/) + ) { + // $ and commas: $1,234.49 + value = tvalue.replace(/[\$,]/g, "") - 0; + type = "n$"; + } else if ( + (matches = value.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{1,4})\s*$/)) + ) { + // MM/DD/YYYY, MM/DD/YYYY + year = matches[3] - 0; + year = year < 1000 ? year + 2000 : year; + value = + SocialCalc.FormatNumber.convert_date_gregorian_to_julian( + year, + matches[1] - 0, + matches[2] - 0 + ) - 2415019; + type = "nd"; + } else if ( + (matches = value.match(/^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})\s*$/)) + ) { + // YYYY-MM-DD, YYYY/MM/DD + year = matches[1] - 0; + year = year < 1000 ? year + 2000 : year; + value = + SocialCalc.FormatNumber.convert_date_gregorian_to_julian( + year, + matches[2] - 0, + matches[3] - 0 + ) - 2415019; + type = "nd"; + } else if ((matches = value.match(/^(\d{1,2}):(\d{1,2})\s*$/))) { + // HH:MM + hour = matches[1] - 0; + minute = matches[2] - 0; + if (hour < 24 && minute < 60) { + value = hour / 24 + minute / (24 * 60); + type = "nt"; + } + } else if ((matches = value.match(/^(\d{1,2}):(\d{1,2}):(\d{1,2})\s*$/))) { + // HH:MM:SS + hour = matches[1] - 0; + minute = matches[2] - 0; + second = matches[3] - 0; + if (hour < 24 && minute < 60 && second < 60) { + value = hour / 24 + minute / (24 * 60) + second / (24 * 60 * 60); + type = "nt"; + } + } else if ((matches = value.match(/^\s*([-+]?\d+) (\d+)\/(\d+)\s*$/))) { + // 1 1/2 + intgr = matches[1] - 0; + num = matches[2] - 0; + denom = matches[3] - 0; + if (denom && denom > 0) { + value = intgr + (intgr < 0 ? -num / denom : num / denom); + type = "n"; + } + } else if ((constr = SocialCalc.InputConstants[value.toUpperCase()])) { + // special constants, like "false" and #N/A + num = constr.indexOf(","); + value = constr.substring(0, num) - 0; + type = constr.substring(num + 1); + } else if ( + tvalue.length > 7 && + tvalue.substring(0, 7).toLowerCase() == "http://" + ) { + // URL + value = tvalue; + type = "tl"; + } + + return { value: value, type: type }; + }; + + SocialCalc.InputConstants = { + // strings that turn into constants for SocialCalc.DetermineValueType + TRUE: "1,nl", + FALSE: "0,nl", + "#N/A": "0,e#N/A", + "#NULL!": "0,e#NULL!", + "#NUM!": "0,e#NUM!", + "#DIV/0!": "0,e#DIV/0!", + "#VALUE!": "0,e#VALUE!", + "#REF!": "0,e#REF!", + "#NAME?": "0,e#NAME?", + }; + + // + // result = default_expand_markup(displayvalue, sheetobj, linkstyle) + // + // Processes wiki-text -- this is a placeholder. + // Reference to here in SocialCalc.expand_markup should be replaced by application-specific routine. + // + + SocialCalc.default_expand_markup = function ( + displayvalue, + sheetobj, + linkstyle + ) { + var result = displayvalue; + + result = SocialCalc.special_chars(result); // do special chars + result = result.replace(/ /g, "  "); // keep multiple spaces + result = result.replace(/\n/g, "
"); // keep line breaks + + return result; // do very little by default + + result = result.replace(/('*)'''(.*?)'''/g, "$1$2"); // Wiki-style bold/italics + result = result.replace(/''(.*?)''/g, "$1"); + + return result; + }; + + // + // result = SocialCalc.expand_text_link(displayvalue, sheetobj, linkstyle, valueformat) + // + // Parses link text (URL, descriptions, pagenames, workspace names) and returns HTML + // + + SocialCalc.expand_text_link = function ( + displayvalue, + sheetobj, + linkstyle, + valueformat + ) { + var desc, tb, str; + + var scc = SocialCalc.Constants; + + var url = ""; + var parts = SocialCalc.ParseCellLinkText(displayvalue + ""); + + if (parts.desc) { + desc = SocialCalc.special_chars(parts.desc); + } else { + desc = parts.pagename + ? scc.defaultPageLinkFormatString + : scc.defaultLinkFormatString; + } + + if ( + displayvalue.length > 7 && + displayvalue.substring(0, 7).toLowerCase() == "http://" && + displayvalue.charAt(displayvalue.length - 1) != ">" + ) { + desc = desc.substring(7); // remove http:// unless explicit + } + + tb = parts.newwin || !linkstyle ? ' target="_blank"' : ""; + + if (parts.pagename) { + if (SocialCalc.Callbacks.MakePageLink) { + url = SocialCalc.Callbacks.MakePageLink( + parts.pagename, + parts.workspacename, + linkstyle, + valueformat + ); + } + // else if (parts.workspace) { + // url = "/" + encodeURI(parts.workspace) + "/" + encodeURI(parts.pagename); + // } + // else { + // url = parts.pagename; + // } + } else { + url = encodeURI(parts.url); + } + str = '" + desc + ""; + + return str; + }; + + // + // result = SocialCalc.ParseCellLinkText(str) + // + // Given: url = http://www.someurl.com/more, desc = Some descriptive text + // + // Takes the following: + // + // url + // + // desc + // "desc" + // <<>> instead of <> => target="_blank" (new window) + // + // [page name] + // "desc"[page name] + // desc[page name] + // {workspace name [page name]} + // "desc"{workspace name [page name]} + // [[]] instead of [] => target="_blank" (new window) + // + // + // Returns: {url: url, desc: desc, newwin: t/f, pagename: pagename, workspace: workspace} + // + + SocialCalc.ParseCellLinkText = function (str) { + var result = { + url: "", + desc: "", + newwin: false, + pagename: "", + workspace: "", + }; + + var pageform = false; + var urlend = str.length - 1; + var descstart = 0; + var lastlt = str.lastIndexOf("<"); + var lastbrkt = str.lastIndexOf("["); + var lastbrace = str.lastIndexOf("{"); + var descend = -1; + + if ( + (str.charAt(urlend) != ">" || lastlt == -1) && + (str.charAt(urlend) != "]" || lastbrkt == -1) && + (str.charAt(urlend) != "}" || + str.charAt(urlend - 1) != "]" || + lastbrace == -1 || + lastbrkt == -1 || + lastbrkt < lastbrace) + ) { + // plain url + urlend++; + descend = urlend; + } else { + // some markup + if (str.charAt(urlend) == ">") { + // url form + descend = lastlt - 1; + if ( + lastlt > 0 && + str.charAt(descend) == "<" && + str.charAt(urlend - 1) == ">" + ) { + descend--; + urlend--; + result.newwin = true; + } + } else if (str.charAt(urlend) == "]") { + // plain page form + descend = lastbrkt - 1; + pageform = true; + if ( + lastbrkt > 0 && + str.charAt(descend) == "[" && + str.charAt(urlend - 1) == "]" + ) { + descend--; + urlend--; + result.newwin = true; + } + } else if (str.charAt(urlend) == "}") { + // page and workspace form + descend = lastbrace - 1; + pageform = true; + wsend = lastbrkt; + urlend--; + if ( + lastbrkt > 0 && + str.charAt(lastbrkt - 1) == "[" && + str.charAt(urlend - 1) == "]" + ) { + wsend = lastbrkt - 1; + urlend--; + result.newwin = true; + } + if (str.charAt(wsend - 1) == " ") { + // trim trailing space in workspace name + wsend--; + } + result.workspace = str.substring(lastbrace + 1, wsend) || ""; + } + + if (str.charAt(descend) == " ") { + // trim trailing space on desc + descend--; + } + + if (str.charAt(descstart) == '"' && str.charAt(descend) == '"') { + descstart++; + descend--; + } + } + + if (pageform) { + result.pagename = str.substring(lastbrkt + 1, urlend) || ""; + } else { + result.url = str.substring(lastlt + 1, urlend) || ""; + } + + if (descend >= descstart) { + result.desc = str.substring(descstart, descend + 1); + } + + return result; + }; + + // + // result = SocialCalc.ConvertSaveToOtherFormat(savestr, outputformat, dorecalc) + // + // Returns a string in the specificed format: "scsave", "html", "csv", "tab" (tab delimited) + // If dorecalc is true, performs a recalc after loading (NO: obsolete!). + // + + SocialCalc.ConvertSaveToOtherFormat = function ( + savestr, + outputformat, + dorecalc + ) { + var sheet, context, clipextents, div, ele, row, col, cr, cell, str; + + var result = ""; + + if (outputformat == "scsave") { + return savestr; + } + + if (savestr == "") { + return ""; + } + + sheet = new SocialCalc.Sheet(); + sheet.ParseSheetSave(savestr); + + if (dorecalc) { + // no longer supported as of 9/10/08 + // Recalc is now async, so can't do it this way + throw "SocialCalc.ConvertSaveToOtherFormat: Not doing recalc."; + } + + if (sheet.copiedfrom) { + clipextents = SocialCalc.ParseRange(sheet.copiedfrom); + } else { + clipextents = { + cr1: { row: 1, col: 1 }, + cr2: { row: sheet.attribs.lastrow, col: sheet.attribs.lastcol }, + }; + } + + if (outputformat == "html") { + context = new SocialCalc.RenderContext(sheet); + if (sheet.copiedfrom) { + context.rowpanes[0] = { + first: clipextents.cr1.row, + last: clipextents.cr2.row, + }; + context.colpanes[0] = { + first: clipextents.cr1.col, + last: clipextents.cr2.col, + }; + } + div = document.createElement("div"); + ele = context.RenderSheet(null, context.defaultHTMLlinkstyle); + div.appendChild(ele); + context = undefined; + sheet = undefined; + result = div.innerHTML; + ele = undefined; + div = undefined; + return result; + } + + for (row = clipextents.cr1.row; row <= clipextents.cr2.row; row++) { + for (col = clipextents.cr1.col; col <= clipextents.cr2.col; col++) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.GetAssuredCell(cr); + + if (cell.errors) { + str = cell.errors; + } else { + str = cell.datavalue + ""; // get value as text + } + + if (outputformat == "csv") { + if (str.indexOf('"') != -1) { + str = str.replace(/"/g, '""'); // double quotes + } + if (/[, \n"]/.test(str)) { + str = '"' + str + '"'; // add quotes + } + if (col > clipextents.cr1.col) { + str = "," + str; // add commas + } + } else if (outputformat == "tab") { + if (str.indexOf("\n") != -1) { + // if multiple lines + if (str.indexOf('"') != -1) { + str = str.replace(/"/g, '""'); // double quotes + } + str = '"' + str + '"'; // add quotes + } + if (col > clipextents.cr1.col) { + str = "\t" + str; // add tabs + } + } + result += str; + } + result += "\n"; + } + + return result; + }; + + // + // result = SocialCalc.ConvertOtherFormatToSave(inputstr, inputformat) + // + // Returns a string converted from the specified format: "scsave", "csv", "tab" (tab delimited) + // + + SocialCalc.ConvertOtherFormatToSave = function (inputstr, inputformat) { + var sheet, + context, + lines, + i, + line, + value, + inquote, + j, + ch, + values, + row, + col, + cr, + maxc; + + var result = ""; + + var AddCell = function () { + col++; + if (col > maxc) maxc = col; + cr = SocialCalc.crToCoord(col, row); + SocialCalc.SetConvertedCell(sheet, cr, value); + value = ""; + }; + + if (inputformat == "scsave") { + return inputstr; + } + + sheet = new SocialCalc.Sheet(); + + lines = inputstr.split(/\r\n|\n/); + + maxc = 0; + if (inputformat == "csv") { + row = 0; + inquote = false; + for (i = 0; i < lines.length; i++) { + if (i == lines.length - 1 && lines[i] == "") { + // extra null line - ignore + break; + } + if (inquote) { + // if inquote, just continue from where left off + value += "\n"; + } else { + // otherwise next row + value = ""; + row++; + col = 0; + } + line = lines[i]; + for (j = 0; j < line.length; j++) { + ch = line.charAt(j); + if (ch == '"') { + if (inquote) { + if (j < line.length - 1 && line.charAt(j + 1) == '"') { + // double quotes + j++; // skip the second one + value += '"'; // add one quote + } else { + inquote = false; + if (j == line.length - 1) { + // at end of line + AddCell(); + } + } + } else { + inquote = true; + } + continue; + } + if (ch == "," && !inquote) { + AddCell(); + } else { + value += ch; + } + if (j == line.length - 1 && !inquote) { + AddCell(); + } + } + } + if (maxc > 0) { + sheet.attribs.lastrow = row; + sheet.attribs.lastcol = maxc; + result = sheet.CreateSheetSave("A1:" + SocialCalc.crToCoord(maxc, row)); + } + } + + if (inputformat == "tab") { + row = 0; + inquote = false; + for (i = 0; i < lines.length; i++) { + if (i == lines.length - 1 && lines[i] == "") { + // extra null line - ignore + break; + } + if (inquote) { + // if inquote, just continue from where left off + value += "\n"; + } else { + // otherwise next row + value = ""; + row++; + col = 0; + } + line = lines[i]; + for (j = 0; j < line.length; j++) { + ch = line.charAt(j); + if (ch == '"') { + if (inquote) { + if (j < line.length - 1) { + if (line.charAt(j + 1) == '"') { + // double quotes + j++; // skip the second one + value += '"'; // add one quote + } else if (line.charAt(j + 1) == "\t") { + // end of quoted item + j++; + inquote = false; + AddCell(); + } + } else { + // at end of line + inquote = false; + AddCell(); + } + continue; + } + if (value == "") { + // quote at start of item + inquote = true; + continue; + } + } + if (ch == "\t" && !inquote) { + AddCell(); + } else { + value += ch; + } + if (j == line.length - 1 && !inquote) { + AddCell(); + } + } + } + if (maxc > 0) { + sheet.attribs.lastrow = row; + sheet.attribs.lastcol = maxc; + result = sheet.CreateSheetSave("A1:" + SocialCalc.crToCoord(maxc, row)); + } + } + + return result; + }; + + // + // SocialCalc.SetConvertedCell(sheet, cr, rawvalue) + // + // Sets the cell cr with a value and type determined from rawvalue + // + + SocialCalc.SetConvertedCell = function (sheet, cr, rawvalue) { + var cell, value; + + cell = sheet.GetAssuredCell(cr); + + value = SocialCalc.DetermineValueType(rawvalue); + + if (value.type == "n" && value.value == rawvalue) { + // check that we don't need "constant" to remember original value + cell.datatype = "v"; + cell.valuetype = "n"; + cell.datavalue = value.value; + } else if (value.type.charAt(0) == "t") { + // text of some sort but left unchanged + cell.datatype = "t"; + cell.valuetype = value.type; + cell.datavalue = value.value; + } else { + // special number types + cell.datatype = "c"; + cell.valuetype = value.type; + cell.datavalue = value.value; + cell.formula = rawvalue; + } + }; + + // + // socialcalctouch adds touch gestures to SocialCalc + // + // As a start, touch gestures are modeled similar to + // mouse events + // + // Author: Ramu Ramamurthy + // + // + // + + // + // + // To initialize, SocialCalc.CreateTableEditor must call + // SocialCalc.TouchRegister(editor.toplevel, {Swipe: SocialCalc.EditorProcessSwipe, editor: editor}); + // Also, any element create inside the grid needs to be touch registered for it to work + // this includes buttons, etc + // + // TestCases: + // 1) tap to point to cell, and to move edit-cell + // 2) double-tap on a cell to open edit-box, then tap on edit box to pullup keyboard + // -- when editing, tap on any other cell to be done with edit + // *** figure out escape to cancel edit versus return to accept edit + // -- when editing, and if = seen than tapping on cell puts the cell name into the edit box + // 3) tap on input box on top opens edit on cell + // tap on any cell accepts and finishes this edit + // *** need a way to cancel the edit versus accept the edit + // + // 4) tap on a cell and move finger starts a range + // removing finger completes the range + // tap on cell cancels range + // + // 5) a swipe action scrolls the sheet + // swipe up/dn scrolls up/dn + // swipe lt/rt scrolls lt/rt + // + // + // Wishlist: + // 1) smooth scroll will be nice (in addition to swipe) -- using a two-touch pan ? + // + + var SocialCalc; + if (!SocialCalc) { + alert("Main SocialCalc code module needed"); + SocialCalc = {}; + } + + // ************************************* + // + // Touch functions: + // + // ************************************* + + SocialCalc.HasTouch = false; + + (function () { + // platform specific registration + var agent = navigator.userAgent.toLowerCase(); + + if ( + agent.indexOf("iphone") >= 0 || + agent.indexOf("ipad") >= 0 || + agent.indexOf("android") >= 0 + ) { + SocialCalc.HasTouch = true; + } + })(); + + SocialCalc.TouchInfo = { + // In sequence the following will be implemented + // swipe up/dn, left/right will scroll up/dn/left/right respectively + // + // touch on a cell will move ecell to that cell + // repeated touch on a cell will start edit on that cell + // touch on another cell will cancel edit if it was in progress (?) + // etc + // + // touch on buttons will activate the buttons + // + // The registeredElements array is used to identify items. + + // One item for each element to respond to the touch, each an object with: + // .element, .functionobj + + registeredElements: [], + + // for swipe + threshold_x: 20, + threshold_y: 20, + + orig_coord_x: 0, + orig_coord_y: 0, + final_coord_x: 0, + final_coord_y: 0, + + px_to_rows: 20, // 20 pixels is 1 row scroll + px_to_cols: 20, // 20 pixels is 1 col scroll + + touch_start: 0, + ranging: false, + ranging_threshold: 100, + move_start: 0, + + last_touch: 0, + timeout_handle: null, + doubletap_threshold: 500, // max milliseconds between taps + }; + + // + // TouchRegister(element, functionobj) - make element respond to touch + // + + SocialCalc.TouchRegister = function (element, functionobj) { + if (!SocialCalc.HasTouch) { + return; + } + + var touchinfo = SocialCalc.TouchInfo; + + if (SocialCalc.LookupElement(element, touchinfo.registeredElements)) { + // already registered + return; + } + + touchinfo.registeredElements.push({ + element: element, + functionobj: functionobj, + }); + + if (SocialCalc.HasTouch && element.addEventListener) { + // Webkit based (?) + element.addEventListener( + "touchstart", + SocialCalc.ProcessTouchStart, + false + ); + element.addEventListener("touchmove", SocialCalc.ProcessTouchMove, false); + element.addEventListener("touchend", SocialCalc.ProcessTouchEnd, false); + element.addEventListener( + "touchcancel", + SocialCalc.ProcessTouchCancel, + false + ); + // element.addEventListener("orientationchange", SocialCalc.ProcessOrientationChange, false); + } + }; + + SocialCalc.FindTouchElement = function (event) { + var touchinfo = SocialCalc.TouchInfo; + + var event = event || window.event; + + var ele = event.target || event.srcElement; // investigate + + for (var wobj = null; !wobj && ele; ele = ele.parentNode) { + // investigate + wobj = SocialCalc.LookupElement(ele, touchinfo.registeredElements); + } + + return wobj; + }; + + SocialCalc.ProcessTouchStart = function (event) { + var touchinfo = SocialCalc.TouchInfo; + touchinfo.orig_coord_x = event.targetTouches[0].pageX; + touchinfo.orig_coord_y = event.targetTouches[0].pageY; + + touchinfo.final_coord_x = touchinfo.orig_coord_x; + touchinfo.final_coord_y = touchinfo.orig_coord_y; + + touchinfo.touch_start = new Date().getTime(); + + event.preventDefault(); + }; + + SocialCalc.TouchGetSimulatedMouseEvent = function (event, mouse_evt_name) { + var touches = event.changedTouches; + var first = touches[0]; + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent( + mouse_evt_name, + true, + true, + window, + 1, + first.screenX, + first.screenY, + first.clientX, + first.clientY, + false, + false, + false, + false, + 0, + null + ); + return simulatedEvent; + }; + + SocialCalc.ProcessTouchMove = function (event) { + var touchinfo = SocialCalc.TouchInfo; + touchinfo.final_coord_x = event.targetTouches[0].pageX; + touchinfo.final_coord_y = event.targetTouches[0].pageY; + + var wobj = SocialCalc.FindTouchElement(event); + + if (!wobj) return; // not one of our elements + + if (touchinfo.move_start == 0) { + touchinfo.move_start = new Date().getTime(); + if ( + touchinfo.move_start - touchinfo.touch_start > + touchinfo.ranging_threshold + ) { + // This is a delayed move + // send a mouse down event + // This is for ranging and dragging + + touchinfo.ranging = true; + var mouseDn = SocialCalc.TouchGetSimulatedMouseEvent( + event, + "mousedown" + ); + wobj.functionobj.editor.fullgrid.dispatchEvent(mouseDn); + } + } else if (touchinfo.ranging) { + // already ranging + // send a mouse move event + + var mouseMv = SocialCalc.TouchGetSimulatedMouseEvent(event, "mousemove"); + wobj.functionobj.editor.fullgrid.dispatchEvent(mouseMv); + } + + event.preventDefault(); + }; + + SocialCalc.ProcessTouchEnd = function (e) { + var touchinfo = SocialCalc.TouchInfo; + + var changeX = touchinfo.orig_coord_x - touchinfo.final_coord_x; + var changeY = touchinfo.orig_coord_y - touchinfo.final_coord_y; + + var wobj = SocialCalc.FindTouchElement(event); + + if (!wobj) return; // not one of our elements + + var event = e || window.event; + + touchinfo.move_start = 0; + touchinfo.touch_start = 0; + + if (touchinfo.ranging) { + // in ranging, and dragging + // send a mouseup event + touchinfo.ranging = false; + var mouseUp = SocialCalc.TouchGetSimulatedMouseEvent(event, "mouseup"); + wobj.functionobj.editor.fullgrid.dispatchEvent(mouseUp); + } else if ( + Math.abs(changeY) > touchinfo.threshold_y || + Math.abs(changeX) > touchinfo.threshold_x + ) { + // check for swipe + + var amount_y = Math.floor(changeY / touchinfo.px_to_rows); + var amount_x = Math.floor(changeX / touchinfo.px_to_cols); + if (wobj.functionobj && wobj.functionobj.Swipe) { + wobj.functionobj.Swipe(event, touchinfo, wobj, amount_y, amount_x); + } + } else { + // detect a double tap + var now = new Date().getTime(); + var lasttouch = touchinfo.last_touch || now + 1; + var delta = now - lasttouch; + if (touchinfo.timeout_handle) { + clearTimeout(touchinfo.timeout_handle); + touchinfo.timeout_handle = null; + } + + if (delta < touchinfo.doubletap_threshold && delta > 0) { + // doubletap seen + if (wobj.functionobj && wobj.functionobj.DoubleTap) { + wobj.functionobj.DoubleTap(event, touchinfo, wobj); + } + } else { + // this is a single tap + touchinfo.last_touch = now; + var timeoutFn = function () { + if (wobj.functionobj && wobj.functionobj.SingleTap) { + wobj.functionobj.SingleTap(event, touchinfo, wobj); + } + }; + touchinfo.timeout_handle = setTimeout( + timeoutFn(), + touchinfo.doubletap_threshold + ); + } + touchinfo.last_touch = now; + } + e.preventDefault(); + }; + + SocialCalc.ProcessTouchCancel = function (event) { + var wobj = SocialCalc.FindTouchElement(event); + + if (!wobj) { + return; // do default behavior + } + + var touchinfo = SocialCalc.TouchInfo; + touchinfo.orig_coord_x = 0; + touchinfo.orig_coord_y = 0; + touchinfo.final_coord_x = 0; + touchinfo.final_coord_y = 0; + touchinfo.move_start = 0; + touchinfo.touch_start = 0; + touchinfo.ranging = false; + }; + + SocialCalc.EditorProcessSwipe = function ( + event, + touchinfo, + wobj, + swipevert, + swipehoriz + ) { + if (wobj.functionobj.editor.busy) { + return; // ignore if busy + } + + if (wobj.functionobj.editor.state != "start") { + return; // ignore if cell being edited + } + + if (swipevert != 0 || swipehoriz != 0) { + //wobj.functionobj.editor.ScrollRelativeBoth(swipevert,swipehoriz); + wobj.functionobj.editor.ScrollRelativeBoth(swipevert, 0); //ignore horiz swipes entirely + } + }; + + SocialCalc.EditorProcessSingleTap = function (event, touchinfo, wobj) { + if (wobj.functionobj.editor.busy) { + return; // ignore if busy + } + + // send mouse down + var mouseDn = SocialCalc.TouchGetSimulatedMouseEvent(event, "mousedown"); + wobj.functionobj.editor.fullgrid.dispatchEvent(mouseDn); + + // then send mouse up + var mouseUp = SocialCalc.TouchGetSimulatedMouseEvent(event, "mouseup"); + wobj.functionobj.editor.fullgrid.dispatchEvent(mouseUp); + }; + + SocialCalc.EditorProcessDoubleTap = function (event, touchinfo, wobj) { + if (wobj.functionobj.editor.busy) { + return; // ignore if busy + } + // simulate a mouse double click + var mouseDblClick = SocialCalc.TouchGetSimulatedMouseEvent( + event, + "dblclick" + ); + wobj.functionobj.editor.fullgrid.dispatchEvent(mouseDblClick); + }; + + SocialCalc.ProcessOrientationChange = function (event) { + console.log("orientation change"); + //var height = $(window).height(); + //var width = $(window).width(); + + //console.log("width="+width+",height="+height) + + // $(window).trigger( "orientationchange.htmlclass" ); + // spreadsheet.DoOnResize(); + }; + + + // Make sure SocialCalc is available globally + if (typeof window !== "undefined") { + window.SocialCalc = SocialCalc; + } else if (typeof global !== "undefined") { + global.SocialCalc = SocialCalc; + } + + return SocialCalc; +}); diff --git a/src/components/socialcalc/core/format-number.js b/src/components/socialcalc/core/format-number.js new file mode 100644 index 0000000..47a9dfa --- /dev/null +++ b/src/components/socialcalc/core/format-number.js @@ -0,0 +1,7034 @@ +/* eslint-disable */ +// SocialCalc Number Formatting Module +// Extracted from the main SocialCalc.js file + +// UMD wrapper +(function (root, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.SocialCalcFormatNumber = factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + + // Get SocialCalc namespace + var SocialCalc; + if (typeof window !== "undefined" && window.SocialCalc) { + SocialCalc = window.SocialCalc; + } else if (typeof global !== "undefined" && global.SocialCalc) { + SocialCalc = global.SocialCalc; + } else { + SocialCalc = {}; + } + + + // + /* + // SocialCalc Number Formatting Library + // + // Part of the SocialCalc package. + // + // (c) Copyright 2008 Socialtext, Inc. + // All Rights Reserved. + // + // The contents of this file are subject to the Artistic License 2.0; you may not + // use this file except in compliance with the License. You may obtain a copy of + // the License at http://socialcalc.org/licenses/al-20/. + // + // Some of the other files in the SocialCalc package are licensed under + // different licenses. Please note the licenses of the modules you use. + // + // Code History: + // + // Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. + // Based in part on the SocialCalc 1.1.0 code written in Perl. + // The SocialCalc 1.1.0 code was: + // Portions (c) Copyright 2005, 2006, 2007 Software Garden, Inc. + // All Rights Reserved. + // Portions (c) Copyright 2007 Socialtext, Inc. + // All Rights Reserved. + // The Perl SocialCalc started as modifications to the wikiCalc(R) program, version 1.0. + // wikiCalc 1.0 was written by Software Garden, Inc. + // Unless otherwise specified, referring to "SocialCalc" in comments refers to this + // JavaScript version of the code, not the SocialCalc Perl code. + // + */ + + var SocialCalc; + if (!SocialCalc) SocialCalc = {}; // May be used with other SocialCalc libraries or standalone + + SocialCalc.FormatNumber = {}; + + SocialCalc.FormatNumber.format_definitions = {}; // Parsed formats are stored here globally + + // Most constants that are often customized for localization are in the SocialCalc.Constants module. + // If you use this module standalone, provide at least the "FormatNumber" values. + // + + // The following values may be customized externally for further localization of the format definitions themselves, + // but that would make them incompatible with other uses and is discouraged. + // + + SocialCalc.FormatNumber.separatorchar = ","; + SocialCalc.FormatNumber.decimalchar = "."; + SocialCalc.FormatNumber.daynames = [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + ]; + SocialCalc.FormatNumber.daynames3 = [ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + ]; + SocialCalc.FormatNumber.monthnames3 = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ]; + SocialCalc.FormatNumber.monthnames = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + + SocialCalc.FormatNumber.allowedcolors = { + BLACK: "#000000", + BLUE: "#0000FF", + CYAN: "#00FFFF", + GREEN: "#00FF00", + MAGENTA: "#FF00FF", + RED: "#FF0000", + WHITE: "#FFFFFF", + YELLOW: "#FFFF00", + }; + + SocialCalc.FormatNumber.alloweddates = { + H: "h]", + M: "m]", + MM: "mm]", + S: "s]", + SS: "ss]", + }; + + // Other constants + + SocialCalc.FormatNumber.commands = { + copy: 1, + color: 2, + integer_placeholder: 3, + fraction_placeholder: 4, + decimal: 5, + currency: 6, + general: 7, + separator: 8, + date: 9, + comparison: 10, + section: 11, + style: 12, + }; + + SocialCalc.FormatNumber.datevalues = { + julian_offset: 2415019, + seconds_in_a_day: 24 * 60 * 60, + seconds_in_an_hour: 60 * 60, + }; + + /* ******************* + + result = SocialCalc.FormatNumber.formatNumberWithFormat = function(rawvalue, format_string, currency_char) + + ************************* */ + + SocialCalc.FormatNumber.formatNumberWithFormat = function ( + rawvalue, + format_string, + currency_char + ) { + var scc = SocialCalc.Constants; + var scfn = SocialCalc.FormatNumber; + + var op, operandstr, fromend, cval, operandstrlc; + var startval, estartval; + var hrs, mins, secs, ehrs, emins, esecs, ampmstr, ymd; + var minOK, mpos; + var result = ""; + var thisformat; + var section, gotcomparison, compop, compval, cpos, oppos; + var sectioninfo; + var i, + decimalscale, + scaledvalue, + strvalue, + strparts, + integervalue, + fractionvalue; + var integerdigits2, + integerpos, + fractionpos, + textcolor, + textstyle, + separatorchar, + decimalchar; + var value; // working copy to change sign, etc. + var mspos; + rawvalue = rawvalue - 0; // make sure a number + value = rawvalue; + if (!isFinite(value)) return "NaN"; + + var negativevalue = value < 0 ? 1 : 0; // determine sign, etc. + if (negativevalue) value = -value; + var zerovalue = value == 0 ? 1 : 0; + + currency_char = currency_char || scc.FormatNumber_DefaultCurrency; + + scfn.parse_format_string(scfn.format_definitions, format_string); // make sure format is parsed + thisformat = scfn.format_definitions[format_string]; // Get format structure + + if (!thisformat) throw "Format not parsed error!"; + + section = thisformat.sectioninfo.length - 1; // get number of sections - 1 + + if (thisformat.hascomparison) { + // has comparisons - determine which section + section = 0; // set to which section we will use + gotcomparison = 0; // this section has no comparison + for (cpos = 0; ; cpos++) { + // scan for comparisons + op = thisformat.operators[cpos]; + operandstr = thisformat.operands[cpos]; // get next operator and operand + if (!op) { + // at end with no match + if (gotcomparison) { + // if comparison but no match + format_string = "General"; // use default of General + scfn.parse_format_string(scfn.format_definitions, format_string); + thisformat = scfn.format_definitions[format_string]; + section = 0; + } + break; // if no comparision, matches on this section + } + if (op == scfn.commands.section) { + // end of section + if (!gotcomparison) { + // no comparison, so it's a match + break; + } + gotcomparison = 0; + section++; // check out next one + continue; + } + if (op == scfn.commands.comparison) { + // found a comparison - do we meet it? + i = operandstr.indexOf(":"); + compop = operandstr.substring(0, i); + compval = operandstr.substring(i + 1) - 0; + if ( + (compop == "<" && rawvalue < compval) || + (compop == "<=" && rawvalue <= compval) || + (compop == "=" && rawvalue == compval) || + (compop == "<>" && rawvalue != compval) || + (compop == ">=" && rawvalue >= compval) || + (compop == ">" && rawvalue > compval) + ) { + // a match + break; + } + gotcomparison = 1; + } + } + } else if (section > 0) { + // more than one section (separated by ";") + if (section == 1) { + // two sections + if (negativevalue) { + negativevalue = 0; // sign will provided by section, not automatically + section = 1; // use second section for negative values + } else { + section = 0; // use first for all others + } + } else if (section == 2) { + // three sections + if (negativevalue) { + negativevalue = 0; // sign will provided by section, not automatically + section = 1; // use second section for negative values + } else if (zerovalue) { + section = 2; // use third section for zero values + } else { + section = 0; // use first for positive + } + } + } + + sectioninfo = thisformat.sectioninfo[section]; // look at values for our section + + if (sectioninfo.commas > 0) { + // scale by thousands + for (i = 0; i < sectioninfo.commas; i++) { + value /= 1000; + } + } + if (sectioninfo.percent > 0) { + // do percent scaling + for (i = 0; i < sectioninfo.percent; i++) { + value *= 100; + } + } + + decimalscale = 1; // cut down to required number of decimal digits + for (i = 0; i < sectioninfo.fractiondigits; i++) { + decimalscale *= 10; + } + scaledvalue = Math.floor(value * decimalscale + 0.5); + scaledvalue = scaledvalue / decimalscale; + + if (typeof scaledvalue != "number") return "NaN"; + if (!isFinite(scaledvalue)) return "NaN"; + + strvalue = scaledvalue + ""; // convert to string (Number.toFixed doesn't do all we need) + + // strvalue = value.toFixed(sectioninfo.fractiondigits); // cut down to required number of decimal digits + // and convert to string + + if ( + scaledvalue == 0 && + (sectioninfo.fractiondigits || sectioninfo.integerdigits) + ) { + negativevalue = 0; // no "-0" unless using multiple sections or General + } + + if (strvalue.indexOf("e") >= 0) { + // converted to scientific notation + return rawvalue + ""; // Just return plain converted raw value + } + + strparts = strvalue.match(/^\+{0,1}(\d*)(?:\.(\d*)){0,1}$/); // get integer and fraction parts + if (!strparts) return "NaN"; // if not a number + integervalue = strparts[1]; + if (!integervalue || integervalue == "0") integervalue = ""; + fractionvalue = strparts[2]; + if (!fractionvalue) fractionvalue = ""; + + if (sectioninfo.hasdate) { + // there are date placeholders + if (rawvalue < 0) { + // bad date + return "??-???-?? ??:??:??"; + } + startval = + (rawvalue - Math.floor(rawvalue)) * scfn.datevalues.seconds_in_a_day; // get date/time parts + estartval = rawvalue * scfn.datevalues.seconds_in_a_day; // do elapsed time version, too + hrs = Math.floor(startval / scfn.datevalues.seconds_in_an_hour); + ehrs = Math.floor(estartval / scfn.datevalues.seconds_in_an_hour); + startval = startval - hrs * scfn.datevalues.seconds_in_an_hour; + mins = Math.floor(startval / 60); + emins = Math.floor(estartval / 60); + secs = startval - mins * 60; + decimalscale = 1; // round appropriately depending if there is ss.0 + for (i = 0; i < sectioninfo.fractiondigits; i++) { + decimalscale *= 10; + } + secs = Math.floor(secs * decimalscale + 0.5); + secs = secs / decimalscale; + esecs = Math.floor(estartval * decimalscale + 0.5); + esecs = esecs / decimalscale; + if (secs >= 60) { + // handle round up into next second, minute, etc. + secs = 0; + mins++; + emins++; + if (mins >= 60) { + mins = 0; + hrs++; + ehrs++; + if (hrs >= 24) { + hrs = 0; + rawvalue++; + } + } + } + fractionvalue = secs - Math.floor(secs) + ""; // for "hh:mm:ss.000" + fractionvalue = fractionvalue.substring(2); // skip "0." + + ymd = SocialCalc.FormatNumber.convert_date_julian_to_gregorian( + Math.floor(rawvalue + scfn.datevalues.julian_offset) + ); + + minOK = 0; // says "m" can be minutes if true + var mspos = sectioninfo.sectionstart; // m scan position in ops + for (; ; mspos++) { + // scan for "m" and "mm" to see if any minutes fields, and am/pm + op = thisformat.operators[mspos]; + operandstr = thisformat.operands[mspos]; // get next operator and operand + if (!op) break; // don't go past end + if (op == scfn.commands.section) break; + if (op == scfn.commands.date) { + if ( + (operandstr.toLowerCase() == "am/pm" || + operandstr.toLowerCase() == "a/p") && + !ampmstr + ) { + if (hrs >= 12) { + hrs -= 12; + ampmstr = + operandstr.toLowerCase() == "a/p" + ? scc.s_FormatNumber_pm1 + : scc.s_FormatNumber_pm; // "P" : "PM"; + } else { + ampmstr = + operandstr.toLowerCase() == "a/p" + ? scc.s_FormatNumber_am1 + : scc.s_FormatNumber_am; // "A" : "AM"; + } + if (operandstr.indexOf(ampmstr) < 0) + ampmstr = ampmstr.toLowerCase(); // have case match case in format + } + if (minOK && (operandstr == "m" || operandstr == "mm")) { + thisformat.operands[mspos] += "in"; // turn into "min" or "mmin" + } + if (operandstr.charAt(0) == "h") { + minOK = 1; // m following h or hh or [h] is minutes not months + } else { + minOK = 0; + } + } else if (op != scfn.commands.copy) { + // copying chars can be between h and m + minOK = 0; + } + } + minOK = 0; + for (--mspos; ; mspos--) { + // scan other way for s after m + op = thisformat.operators[mspos]; + operandstr = thisformat.operands[mspos]; // get next operator and operand + if (!op) break; // don't go past end + if (op == scfn.commands.section) break; + if (op == scfn.commands.date) { + if (minOK && (operandstr == "m" || operandstr == "mm")) { + thisformat.operands[mspos] += "in"; // turn into "min" or "mmin" + } + if (operandstr == "ss") { + minOK = 1; // m before ss is minutes not months + } else { + minOK = 0; + } + } else if (op != scfn.commands.copy) { + // copying chars can be between ss and m + minOK = 0; + } + } + } + + integerdigits2 = 0; // init counters, etc. + integerpos = 0; + fractionpos = 0; + textcolor = ""; + textstyle = ""; + separatorchar = scc.FormatNumber_separatorchar; + if (separatorchar.indexOf(" ") >= 0) + separatorchar = separatorchar.replace(/ /g, " "); + decimalchar = scc.FormatNumber_decimalchar; + if (decimalchar.indexOf(" ") >= 0) + decimalchar = decimalchar.replace(/ /g, " "); + + oppos = sectioninfo.sectionstart; + + while ((op = thisformat.operators[oppos])) { + // execute format + operandstr = thisformat.operands[oppos++]; // get next operator and operand + + if (op == scfn.commands.copy) { + // put char in result + result += operandstr; + } else if (op == scfn.commands.color) { + // set color + textcolor = operandstr; + } else if (op == scfn.commands.style) { + // set style + textstyle = operandstr; + } else if (op == scfn.commands.integer_placeholder) { + // insert number part + if (negativevalue) { + result += "-"; + negativevalue = 0; + } + integerdigits2++; + if (integerdigits2 == 1) { + // first one + if (integervalue.length > sectioninfo.integerdigits) { + // see if integer wider than field + for ( + ; + integerpos < integervalue.length - sectioninfo.integerdigits; + integerpos++ + ) { + result += integervalue.charAt(integerpos); + if (sectioninfo.thousandssep) { + // see if this is a separator position + fromend = integervalue.length - integerpos - 1; + if (fromend > 2 && fromend % 3 == 0) { + result += separatorchar; + } + } + } + } + } + if ( + integervalue.length < sectioninfo.integerdigits && + integerdigits2 <= sectioninfo.integerdigits - integervalue.length + ) { + // field is wider than value + if (operandstr == "0" || operandstr == "?") { + // fill with appropriate characters + result += operandstr == "0" ? "0" : " "; + if (sectioninfo.thousandssep) { + // see if this is a separator position + fromend = sectioninfo.integerdigits - integerdigits2; + if (fromend > 2 && fromend % 3 == 0) { + result += separatorchar; + } + } + } + } else { + // normal integer digit - add it + result += integervalue.charAt(integerpos); + if (sectioninfo.thousandssep) { + // see if this is a separator position + fromend = integervalue.length - integerpos - 1; + if (fromend > 2 && fromend % 3 == 0) { + result += separatorchar; + } + } + integerpos++; + } + } else if (op == scfn.commands.fraction_placeholder) { + // add fraction part of number + if (fractionpos >= fractionvalue.length) { + if (operandstr == "0" || operandstr == "?") { + result += operandstr == "0" ? "0" : " "; + } + } else { + result += fractionvalue.charAt(fractionpos); + } + fractionpos++; + } else if (op == scfn.commands.decimal) { + // decimal point + if (negativevalue) { + result += "-"; + negativevalue = 0; + } + result += decimalchar; + } else if (op == scfn.commands.currency) { + // currency symbol + if (negativevalue) { + result += "-"; + negativevalue = 0; + } + result += operandstr; + } else if (op == scfn.commands.general) { + // insert "General" conversion + + // *** Cut down number of significant digits to avoid floating point artifacts: + + if (value != 0) { + // only if non-zero + var factor = Math.floor(Math.LOG10E * Math.log(value)); // get integer magnitude as a power of 10 + factor = Math.pow(10, 13 - factor); // turn into scaling factor + value = Math.floor(factor * value + 0.5) / factor; // scale positive value, round, undo scaling + if (!isFinite(value)) return "NaN"; + } + if (negativevalue) { + result += "-"; + } + strvalue = value + ""; // convert original value to string + if (strvalue.indexOf("e") >= 0) { + // converted to scientific notation + result += strvalue; + continue; + } + strparts = strvalue.match(/^\+{0,1}(\d*)(?:\.(\d*)){0,1}$/); // get integer and fraction parts + integervalue = strparts[1]; + if (!integervalue || integervalue == "0") integervalue = ""; + fractionvalue = strparts[2]; + if (!fractionvalue) fractionvalue = ""; + integerpos = 0; + fractionpos = 0; + if (integervalue.length) { + for (; integerpos < integervalue.length; integerpos++) { + result += integervalue.charAt(integerpos); + if (sectioninfo.thousandssep) { + // see if this is a separator position + fromend = integervalue.length - integerpos - 1; + if (fromend > 2 && fromend % 3 == 0) { + result += separatorchar; + } + } + } + } else { + result += "0"; + } + if (fractionvalue.length) { + result += decimalchar; + for (; fractionpos < fractionvalue.length; fractionpos++) { + result += fractionvalue.charAt(fractionpos); + } + } + } else if (op == scfn.commands.date) { + // date placeholder + operandstrlc = operandstr.toLowerCase(); + if (operandstrlc == "y" || operandstrlc == "yy") { + result += (ymd.year + "").substring(2); + } else if (operandstrlc == "yyyy") { + result += ymd.year + ""; + } else if (operandstrlc == "d") { + result += ymd.day + ""; + } else if (operandstrlc == "dd") { + cval = 1000 + ymd.day; + result += (cval + "").substr(2); + } else if (operandstrlc == "ddd") { + cval = Math.floor(rawvalue + 6) % 7; + result += scc.s_FormatNumber_daynames3[cval]; + } else if (operandstrlc == "dddd") { + cval = Math.floor(rawvalue + 6) % 7; + result += scc.s_FormatNumber_daynames[cval]; + } else if (operandstrlc == "m") { + result += ymd.month + ""; + } else if (operandstrlc == "mm") { + cval = 1000 + ymd.month; + result += (cval + "").substr(2); + } else if (operandstrlc == "mmm") { + result += scc.s_FormatNumber_monthnames3[ymd.month - 1]; + } else if (operandstrlc == "mmmm") { + result += scc.s_FormatNumber_monthnames[ymd.month - 1]; + } else if (operandstrlc == "mmmmm") { + result += scc.s_FormatNumber_monthnames[ymd.month - 1].charAt(0); + } else if (operandstrlc == "h") { + result += hrs + ""; + } else if (operandstrlc == "h]") { + result += ehrs + ""; + } else if (operandstrlc == "mmin") { + cval = 1000 + mins + ""; + result += cval.substr(2); + } else if (operandstrlc == "mm]") { + if (emins < 100) { + cval = 1000 + emins + ""; + result += cval.substr(2); + } else { + result += emins + ""; + } + } else if (operandstrlc == "min") { + result += mins + ""; + } else if (operandstrlc == "m]") { + result += emins + ""; + } else if (operandstrlc == "hh") { + cval = 1000 + hrs + ""; + result += cval.substr(2); + } else if (operandstrlc == "s") { + cval = Math.floor(secs); + result += cval + ""; + } else if (operandstrlc == "ss") { + cval = 1000 + Math.floor(secs) + ""; + result += cval.substr(2); + } else if (operandstrlc == "am/pm" || operandstrlc == "a/p") { + result += ampmstr; + } else if (operandstrlc == "ss]") { + if (esecs < 100) { + cval = 1000 + Math.floor(esecs) + ""; + result += cval.substr(2); + } else { + cval = Math.floor(esecs); + result += cval + ""; + } + } + } else if (op == scfn.commands.section) { + // end of section + break; + } else if (op == scfn.commands.comparison) { + // ignore + continue; + } else { + result += "!! Parse error !!"; + } + } + + if (textcolor) { + result = '' + result + ""; + } + if (textstyle) { + result = '' + result + ""; + } + + return result; + }; + + /* ******************* + + SocialCalc.FormatNumber.parse_format_string(format_defs, format_string) + + Takes a format string (e.g., "#,##0.00_);(#,##0.00)") and fills in format_defs with the parsed info + + format_defs + ["#,##0.0"]->{} - elements in the hash are one hash for each format + .operators->[] - array of operators from parsing the format string (each a number) + .operands->[] - array of corresponding operators (each usually a string) + .sectioninfo->[] - one hash for each section of the format + .start + .integerdigits + .fractiondigits + .commas + .percent + .thousandssep + .hasdates + .hascomparison - true if any section has [<100], etc. + + ************************* */ + + SocialCalc.FormatNumber.parse_format_string = function ( + format_defs, + format_string + ) { + var scfn = SocialCalc.FormatNumber; + + var thisformat, section, sectionfinfo; + var integerpart = 1; // start out in integer part + var lastwasinteger; // last char was an integer placeholder + var lastwasslash; // last char was a backslash - escaping following character + var lastwasasterisk; // repeat next char + var lastwasunderscore; // last char was _ which picks up following char for width + var inquote, quotestr; // processing a quoted string + var inbracket, bracketstr, bracketdata; // processing a bracketed string + var ingeneral, gpos; // checks for characters "General" + var ampmstr, part; // checks for characters "A/P" and "AM/PM" + var indate; // keeps track of date/time placeholders + var chpos; // character position being looked at + var ch; // character being looked at + var sectioninfo; + + if (format_defs[format_string]) return; // already defined - nothing to do + + thisformat = { operators: [], operands: [], sectioninfo: [{}] }; // create info structure for this format + format_defs[format_string] = thisformat; // add to other format definitions + + section = 0; // start with section 0 + sectioninfo = thisformat.sectioninfo[section]; // get reference to info for current section + sectioninfo.sectionstart = 0; // position in operands that starts this section + sectioninfo.integerdigits = 0; // number of integer-part placeholders + sectioninfo.fractiondigits = 0; // fraction placeholders + sectioninfo.commas = 0; // commas encountered, to handle scaling + sectioninfo.percent = 0; // times to scale by 100 + + for (chpos = 0; chpos < format_string.length; chpos++) { + // parse + ch = format_string.charAt(chpos); // get next char to examine + if (inquote) { + if (ch == '"') { + inquote = 0; + thisformat.operators.push(scfn.commands.copy); + thisformat.operands.push(quotestr); + continue; + } + quotestr += ch; + continue; + } + if (inbracket) { + if (ch == "]") { + inbracket = 0; + bracketdata = + SocialCalc.FormatNumber.parse_format_bracket(bracketstr); + if (bracketdata.operator == scfn.commands.separator) { + sectioninfo.thousandssep = 1; // explicit [,] + continue; + } + if (bracketdata.operator == scfn.commands.date) { + sectioninfo.hasdate = 1; + } + if (bracketdata.operator == scfn.commands.comparison) { + thisformat.hascomparison = 1; + } + thisformat.operators.push(bracketdata.operator); + thisformat.operands.push(bracketdata.operand); + continue; + } + bracketstr += ch; + continue; + } + if (lastwasslash) { + thisformat.operators.push(scfn.commands.copy); + thisformat.operands.push(ch); + lastwasslash = false; + continue; + } + if (lastwasasterisk) { + thisformat.operators.push(scfn.commands.copy); + thisformat.operands.push(ch + ch + ch + ch + ch); // do 5 of them since no real tabs + lastwasasterisk = false; + continue; + } + if (lastwasunderscore) { + thisformat.operators.push(scfn.commands.copy); + thisformat.operands.push(" "); + lastwasunderscore = false; + continue; + } + if (ingeneral) { + if ("general".charAt(ingeneral) == ch.toLowerCase()) { + ingeneral++; + if (ingeneral == 7) { + thisformat.operators.push(scfn.commands.general); + thisformat.operands.push(ch); + ingeneral = 0; + } + continue; + } + ingeneral = 0; + } + if (indate) { + // last char was part of a date placeholder + if (indate.charAt(0) == ch) { + // another of the same char + indate += ch; // accumulate it + continue; + } + thisformat.operators.push(scfn.commands.date); // something else, save date info + thisformat.operands.push(indate); + sectioninfo.hasdate = 1; + indate = ""; + } + if (ampmstr) { + ampmstr += ch; + part = ampmstr.toLowerCase(); + if ( + part != "am/pm".substring(0, part.length) && + part != "a/p".substring(0, part.length) + ) { + ampstr = ""; + } else if (part == "am/pm" || part == "a/p") { + thisformat.operators.push(scfn.commands.date); + thisformat.operands.push(ampmstr); + ampmstr = ""; + } + continue; + } + if (ch == "#" || ch == "0" || ch == "?") { + // placeholder + if (integerpart) { + sectioninfo.integerdigits++; + if (sectioninfo.commas) { + // comma inside of integer placeholders + sectioninfo.thousandssep = 1; // any number is thousands separator + sectioninfo.commas = 0; // reset count of "thousand" factors + } + lastwasinteger = 1; + thisformat.operators.push(scfn.commands.integer_placeholder); + thisformat.operands.push(ch); + } else { + sectioninfo.fractiondigits++; + thisformat.operators.push(scfn.commands.fraction_placeholder); + thisformat.operands.push(ch); + } + } else if (ch == ".") { + // decimal point + lastwasinteger = 0; + thisformat.operators.push(scfn.commands.decimal); + thisformat.operands.push(ch); + integerpart = 0; + } else if (ch == "$") { + // currency char + lastwasinteger = 0; + thisformat.operators.push(scfn.commands.currency); + thisformat.operands.push(ch); + } else if (ch == ",") { + if (lastwasinteger) { + sectioninfo.commas++; + } else { + thisformat.operators.push(scfn.commands.copy); + thisformat.operands.push(ch); + } + } else if (ch == "%") { + lastwasinteger = 0; + sectioninfo.percent++; + thisformat.operators.push(scfn.commands.copy); + thisformat.operands.push(ch); + } else if (ch == '"') { + lastwasinteger = 0; + inquote = 1; + quotestr = ""; + } else if (ch == "[") { + lastwasinteger = 0; + inbracket = 1; + bracketstr = ""; + } else if (ch == "\\") { + lastwasslash = 1; + lastwasinteger = 0; + } else if (ch == "*") { + lastwasasterisk = 1; + lastwasinteger = 0; + } else if (ch == "_") { + lastwasunderscore = 1; + lastwasinteger = 0; + } else if (ch == ";") { + section++; // start next section + thisformat.sectioninfo[section] = {}; // create a new section + sectioninfo = thisformat.sectioninfo[section]; // get reference to info for current section + sectioninfo.sectionstart = 1 + thisformat.operators.length; // remember where it starts + sectioninfo.integerdigits = 0; // number of integer-part placeholders + sectioninfo.fractiondigits = 0; // fraction placeholders + sectioninfo.commas = 0; // commas encountered, to handle scaling + sectioninfo.percent = 0; // times to scale by 100 + integerpart = 1; // reset for new section + lastwasinteger = 0; + thisformat.operators.push(scfn.commands.section); + thisformat.operands.push(ch); + } else if (ch.toLowerCase() == "g") { + ingeneral = 1; + lastwasinteger = 0; + } else if (ch.toLowerCase() == "a") { + ampmstr = ch; + lastwasinteger = 0; + } else if ("dmyhHs".indexOf(ch) >= 0) { + indate = ch; + } else { + lastwasinteger = 0; + thisformat.operators.push(scfn.commands.copy); + thisformat.operands.push(ch); + } + } + + if (indate) { + // last char was part of unsaved date placeholder + thisformat.operators.push(scfn.commands.date); + thisformat.operands.push(indate); + sectioninfo.hasdate = 1; + } + + return; + }; + + /* ******************* + + bracketdata = SocialCalc.FormatNumber.parse_format_bracket(bracketstr) + + Takes a bracket contents (e.g., "RED", ">10") and returns an operator and operand + + bracketdata->{} + .operator + .operand + + ************************* */ + + SocialCalc.FormatNumber.parse_format_bracket = function (bracketstr) { + var scfn = SocialCalc.FormatNumber; + var scc = SocialCalc.Constants; + + var bracketdata = {}; + var parts; + + if (bracketstr.charAt(0) == "$") { + // currency + bracketdata.operator = scfn.commands.currency; + parts = bracketstr.match(/^\$(.+?)(\-.+?){0,1}$/); + if (parts) { + bracketdata.operand = + parts[1] || scc.FormatNumber_defaultCurrency || "$"; + } else { + bracketdata.operand = + bracketstr.substring(1) || scc.FormatNumber_defaultCurrency || "$"; + } + } else if (bracketstr == "?$") { + bracketdata.operator = scfn.commands.currency; + bracketdata.operand = "[?$]"; + } else if (scfn.allowedcolors[bracketstr.toUpperCase()]) { + bracketdata.operator = scfn.commands.color; + bracketdata.operand = scfn.allowedcolors[bracketstr.toUpperCase()]; + } else if ((parts = bracketstr.match(/^style=([^"]*)$/))) { + // [style=...] + bracketdata.operator = scfn.commands.style; + bracketdata.operand = parts[1]; + } else if (bracketstr == ",") { + bracketdata.operator = scfn.commands.separator; + bracketdata.operand = bracketstr; + } else if (scfn.alloweddates[bracketstr.toUpperCase()]) { + bracketdata.operator = scfn.commands.date; + bracketdata.operand = scfn.alloweddates[bracketstr.toUpperCase()]; + } else if ((parts = bracketstr.match(/^[<>=]/))) { + // comparison operator + parts = bracketstr.match(/^([<>=]+)(.+)$/); // split operator and value + bracketdata.operator = scfn.commands.comparison; + bracketdata.operand = parts[1] + ":" + parts[2]; + } else { + // unknown bracket + bracketdata.operator = scfn.commands.copy; + bracketdata.operand = "[" + bracketstr + "]"; + } + + return bracketdata; + }; + + /* ******************* + + juliandate = SocialCalc.FormatNumber.convert_date_gregorian_to_julian(year, month, day) + + From: http://aa.usno.navy.mil/faq/docs/JD_Formula.html + Uses: Fliegel, H. F. and van Flandern, T. C. (1968). Communications of the ACM, Vol. 11, No. 10 (October, 1968). + Translated from the FORTRAN + + I= YEAR + J= MONTH + K= DAY + C + JD= K-32075+1461*(I+4800+(J-14)/12)/4+367*(J-2-(J-14)/12*12) + 2 /12-3*((I+4900+(J-14)/12)/100)/4 + + ************************* */ + + SocialCalc.FormatNumber.convert_date_gregorian_to_julian = function ( + year, + month, + day + ) { + var juliandate; + + juliandate = + day - + 32075 + + SocialCalc.intFunc( + (1461 * (year + 4800 + SocialCalc.intFunc((month - 14) / 12))) / 4 + ); + juliandate += SocialCalc.intFunc( + (367 * (month - 2 - SocialCalc.intFunc((month - 14) / 12) * 12)) / 12 + ); + juliandate = + juliandate - + SocialCalc.intFunc( + (3 * + SocialCalc.intFunc( + (year + 4900 + SocialCalc.intFunc((month - 14) / 12)) / 100 + )) / + 4 + ); + + return juliandate; + }; + + /* ******************* + + ymd = SocialCalc.FormatNumber.convert_date_julian_to_gregorian(juliandate) + + ymd->{} + .year + .month + .day + + From: http://aa.usno.navy.mil/faq/docs/JD_Formula.html + Uses: Fliegel, H. F. and van Flandern, T. C. (1968). Communications of the ACM, Vol. 11, No. 10 (October, 1968). + Translated from the FORTRAN + + ************************* */ + + SocialCalc.FormatNumber.convert_date_julian_to_gregorian = function ( + juliandate + ) { + var L, N, I, J, K; + + L = juliandate + 68569; + N = Math.floor((4 * L) / 146097); + L = L - Math.floor((146097 * N + 3) / 4); + I = Math.floor((4000 * (L + 1)) / 1461001); + L = L - Math.floor((1461 * I) / 4) + 31; + J = Math.floor((80 * L) / 2447); + K = L - Math.floor((2447 * J) / 80); + L = Math.floor(J / 11); + J = J + 2 - 12 * L; + I = 100 * (N - 49) + I + L; + + return { year: I, month: J, day: K }; + }; + + SocialCalc.intFunc = function (n) { + if (n < 0) { + return -Math.floor(-n); + } else { + return Math.floor(n); + } + }; + + // + /* + // SocialCalc Spreadsheet Formula Library + // + // Part of the SocialCalc package + // + // (c) Copyright 2008 Socialtext, Inc. + // All Rights Reserved. + // + // The contents of this file are subject to the Artistic License 2.0; you may not + // use this file except in compliance with the License. You may obtain a copy of + // the License at http://socialcalc.org/licenses/al-20/. + // + // Some of the other files in the SocialCalc package are licensed under + // different licenses. Please note the licenses of the modules you use. + // + // Code History: + // + // Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. + // Based in part on the SocialCalc 1.1.0 code written in Perl. + // The SocialCalc 1.1.0 code was: + // Portions (c) Copyright 2005, 2006, 2007 Software Garden, Inc. + // All Rights Reserved. + // Portions (c) Copyright 2007 Socialtext, Inc. + // All Rights Reserved. + // The Perl SocialCalc started as modifications to the wikiCalc(R) program, version 1.0. + // wikiCalc 1.0 was written by Software Garden, Inc. + // Unless otherwise specified, referring to "SocialCalc" in comments refers to this + // JavaScript version of the code, not the SocialCalc Perl code. + // + */ + + var SocialCalc; + if (!SocialCalc) SocialCalc = {}; // May be used with other SocialCalc libraries or standalone + // In any case, requires SocialCalc.Constants. + + SocialCalc.Formula = {}; + + // + // Formula constants for parsing: + // + + SocialCalc.Formula.ParseState = { + num: 1, + alpha: 2, + coord: 3, + string: 4, + stringquote: 5, + numexp1: 6, + numexp2: 7, + alphanumeric: 8, + specialvalue: 9, + }; + + SocialCalc.Formula.TokenType = { + num: 1, + coord: 2, + op: 3, + name: 4, + error: 5, + string: 6, + space: 7, + }; + + SocialCalc.Formula.CharClass = { + num: 1, + numstart: 2, + op: 3, + eof: 4, + alpha: 5, + incoord: 6, + error: 7, + quote: 8, + space: 9, + specialstart: 10, + }; + + SocialCalc.Formula.CharClassTable = { + " ": 9, + "!": 3, + '"': 8, + "#": 10, + $: 6, + "%": 3, + "&": 3, + "(": 3, + ")": 3, + "*": 3, + "+": 3, + ",": 3, + "-": 3, + ".": 2, + "/": 3, + 0: 1, + 1: 1, + 2: 1, + 3: 1, + 4: 1, + 5: 1, + 6: 1, + 7: 1, + 8: 1, + 9: 1, + ":": 3, + "<": 3, + "=": 3, + ">": 3, + A: 5, + B: 5, + C: 5, + D: 5, + E: 5, + F: 5, + G: 5, + H: 5, + I: 5, + J: 5, + K: 5, + L: 5, + M: 5, + N: 5, + O: 5, + P: 5, + Q: 5, + R: 5, + S: 5, + T: 5, + U: 5, + V: 5, + W: 5, + X: 5, + Y: 5, + Z: 5, + "^": 3, + _: 5, + a: 5, + b: 5, + c: 5, + d: 5, + e: 5, + f: 5, + g: 5, + h: 5, + i: 5, + j: 5, + k: 5, + l: 5, + m: 5, + n: 5, + o: 5, + p: 5, + q: 5, + r: 5, + s: 5, + t: 5, + u: 5, + v: 5, + w: 5, + x: 5, + y: 5, + z: 5, + }; + + SocialCalc.Formula.UpperCaseTable = { + a: "A", + b: "B", + c: "C", + d: "D", + e: "E", + f: "F", + g: "G", + h: "H", + i: "I", + j: "J", + k: "K", + l: "L", + m: "M", + n: "N", + o: "O", + p: "P", + q: "Q", + r: "R", + s: "S", + t: "T", + u: "U", + v: "V", + w: "W", + x: "X", + y: "Y", + z: "Z", + }; + + SocialCalc.Formula.SpecialConstants = { + // names that turn into constants for name lookup + "#NULL!": "0,e#NULL!", + "#NUM!": "0,e#NUM!", + "#DIV/0!": "0,e#DIV/0!", + "#VALUE!": "0,e#VALUE!", + "#REF!": "0,e#REF!", + "#NAME?": "0,e#NAME?", + }; + + // Operator Precedence table + // + // 1- !, 2- : ,, 3- M P, 4- %, 5- ^, 6- * /, 7- + -, 8- &, 9- < > = G(>=) L(<=) N(<>), + // Negative value means Right Associative + + SocialCalc.Formula.TokenPrecedence = { + "!": 1, + ":": 2, + ",": 2, + M: -3, + P: -3, + "%": 4, + "^": 5, + "*": 6, + "/": 6, + "+": 7, + "-": 7, + "&": 8, + "<": 9, + ">": 9, + G: 9, + L: 9, + N: 9, + }; + + // Convert one-char token text to input text: + + SocialCalc.Formula.TokenOpExpansion = { + G: ">=", + L: "<=", + M: "-", + N: "<>", + P: "+", + }; + + // + // Information about the resulting value types when doing operations on values (used by LookupResultType) + // + // Each object entry is an object with specific types with result type info as follows: + // + // 'type1a': '|type2a:resulta|type2b:resultb|... + // Type of t* or n* matches any of those types not listed + // Results may be a type or the numbers 1 or 2 specifying to return type1 or type2 + + SocialCalc.Formula.TypeLookupTable = { + unaryminus: { + "n*": "|n*:1|", + "e*": "|e*:1|", + "t*": "|t*:e#VALUE!|", + b: "|b:n|", + }, + unaryplus: { + "n*": "|n*:1|", + "e*": "|e*:1|", + "t*": "|t*:e#VALUE!|", + b: "|b:n|", + }, + unarypercent: { + "n*": "|n:n%|n*:n|", + "e*": "|e*:1|", + "t*": "|t*:e#VALUE!|", + b: "|b:n|", + }, + plus: { + "n%": "|n%:n%|nd:n|nt:n|ndt:n|n$:n|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|", + nd: "|n%:n|nd:nd|nt:ndt|ndt:ndt|n$:n|n:nd|n*:n|b:n|e*:2|t*:e#VALUE!|", + nt: "|n%:n|nd:ndt|nt:nt|ndt:ndt|n$:n|n:nt|n*:n|b:n|e*:2|t*:e#VALUE!|", + ndt: "|n%:n|nd:ndt|nt:ndt|ndt:ndt|n$:n|n:ndt|n*:n|b:n|e*:2|t*:e#VALUE!|", + n$: "|n%:n|nd:n|nt:n|ndt:n|n$:n$|n:n$|n*:n|b:n|e*:2|t*:e#VALUE!|", + nl: "|n%:n|nd:n|nt:n|ndt:n|n$:n|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|", + n: "|n%:n|nd:nd|nt:nt|ndt:ndt|n$:n$|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|", + b: "|n%:n%|nd:nd|nt:nt|ndt:ndt|n$:n$|n:n|n*:n|b:n|e*:2|t*:e#VALUE!|", + "t*": "|n*:e#VALUE!|t*:e#VALUE!|b:e#VALUE!|e*:2|", + "e*": "|e*:1|n*:1|t*:1|b:1|", + }, + concat: { + t: "|t:t|th:th|tw:tw|tl:t|t*:2|e*:2|", + th: "|t:th|th:th|tw:t|tl:th|t*:t|e*:2|", + tw: "|t:tw|th:t|tw:tw|tl:tw|t*:t|e*:2|", + tl: "|t:tl|th:th|tw:tw|tl:tl|t*:t|e*:2|", + "e*": "|e*:1|n*:1|t*:1|", + }, + oneargnumeric: { + "n*": "|n*:n|", + "e*": "|e*:1|", + "t*": "|t*:e#VALUE!|", + b: "|b:n|", + }, + twoargnumeric: { + "n*": "|n*:n|t*:e#VALUE!|e*:2|", + "e*": "|e*:1|n*:1|t*:1|", + "t*": "|t*:e#VALUE!|n*:e#VALUE!|e*:2|", + }, + propagateerror: { + "n*": "|n*:2|e*:2|", + "e*": "|e*:2|", + "t*": "|t*:2|e*:2|", + b: "|b:2|e*:2|", + }, + }; + + /* ******************* + + parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(line) + + Parses a text string as if it was a spreadsheet formula + + This uses a simple state machine run on each character in turn. + States remember whether a number is being gathered, etc. + The result is parseinfo which is an array with one entry for each token: + parseinfo[i] = { + text: "the characters making up the parsed token", + type: the type of the token (a number), + opcode: a single character version of an operator suitable for use in the + precedence table and distinguishing between unary and binary + and -. + + ************************* */ + + SocialCalc.Formula.ParseFormulaIntoTokens = function (line) { + var i, + ch, + chclass, + haddecimal, + last_token, + last_token_type, + last_token_text, + t; + + var scf = SocialCalc.Formula; + var scc = SocialCalc.Constants; + var parsestate = scf.ParseState; + var tokentype = scf.TokenType; + var charclass = scf.CharClass; + var charclasstable = scf.CharClassTable; + var uppercasetable = scf.UpperCaseTable; // much faster than toUpperCase function + var pushtoken = scf.ParsePushToken; + var coordregex = /^\$?[A-Z]{1,2}\$?[1-9]\d*$/i; + + var parseinfo = []; + var str = ""; + var state = 0; + var haddecimal = false; + var cclass; + + for (i = 0; i <= line.length; i++) { + if (i < line.length) { + ch = line.charAt(i); + cclass = charclasstable[ch]; + } else { + ch = ""; + cclass = charclass.eof; + } + + if (state == parsestate.num) { + if (cclass == charclass.num) { + str += ch; + } else if (cclass == charclass.numstart && !haddecimal) { + haddecimal = true; + str += ch; + } else if (ch == "E" || ch == "e") { + str += ch; + haddecimal = false; + state = parsestate.numexp1; + } else { + // end of number - save it + pushtoken(parseinfo, str, tokentype.num, 0); + haddecimal = false; + state = 0; + } + } + + if (state == parsestate.numexp1) { + if (cclass == parsestate.num) { + state = parsestate.numexp2; + } else if ( + (ch == "+" || ch == "-") && + uppercasetable[str.charAt(str.length - 1)] == "E" + ) { + str += ch; + } else if (ch == "E" || ch == "e") { + } else { + pushtoken(parseinfo, scc.s_parseerrexponent, tokentype.error, 0); + state = 0; + } + } + + if (state == parsestate.numexp2) { + if (cclass == charclass.num) { + str += ch; + } else { + // end of number - save it + pushtoken(parseinfo, str, tokentype.num, 0); + state = 0; + } + } + + if (state == parsestate.alpha) { + if (cclass == charclass.num) { + state = parsestate.coord; + } else if (cclass == charclass.alpha || ch == ".") { + // alpha may be letters, numbers, "_", or "." + str += ch; + } else if (cclass == charclass.incoord) { + state = parsestate.coord; + } else if ( + cclass == charclass.op || + cclass == charclass.numstart || + cclass == charclass.space || + cclass == charclass.eof + ) { + pushtoken(parseinfo, str.toUpperCase(), tokentype.name, 0); + state = 0; + } else { + pushtoken(parseinfo, scc.s_parseerrchar, tokentype.error, 0); + state = 0; + } + } + + if (state == parsestate.coord) { + if (cclass == charclass.num) { + str += ch; + } else if (cclass == charclass.incoord) { + str += ch; + } else if (cclass == charclass.alpha) { + state = parsestate.alphanumeric; + } else if ( + cclass == charclass.op || + cclass == charclass.numstart || + cclass == charclass.eof || + cclass == charclass.space + ) { + if (coordregex.test(str)) { + t = tokentype.coord; + } else { + t = tokentype.name; + } + pushtoken(parseinfo, str.toUpperCase(), t, 0); + state = 0; + } else { + pushtoken(parseinfo, scc.s_parseerrchar, tokentype.error, 0); + state = 0; + } + } + + if (state == parsestate.alphanumeric) { + if (cclass == charclass.num || cclass == charclass.alpha) { + str += ch; + } else if ( + cclass == charclass.op || + cclass == charclass.numstart || + cclass == charclass.space || + cclass == charclass.eof + ) { + pushtoken(parseinfo, str.toUpperCase(), tokentype.name, 0); + state = 0; + } else { + pushtoken(parseinfo, scc.s_parseerrchar, tokentype.error, 0); + state = 0; + } + } + + if (state == parsestate.string) { + if (cclass == charclass.quote) { + state = parsestate.stringquote; // got quote in string: is it doubled (quote in string) or by itself (end of string)? + } else if (cclass == charclass.eof) { + pushtoken(parseinfo, scc.s_parseerrstring, tokentype.error, 0); + state = 0; + } else { + str += ch; + } + } else if (state == parsestate.stringquote) { + // note else if here + if (cclass == charclass.quote) { + str += '"'; + state = parsestate.string; // double quote: add one then continue getting string + } else { + // something else -- end of string + pushtoken(parseinfo, str, tokentype.string, 0); + state = 0; // drop through to process + } + } else if (state == parsestate.specialvalue) { + // special values like #REF! + if (str.charAt(str.length - 1) == "!") { + // done - save value as a name + pushtoken(parseinfo, str, tokentype.name, 0); + state = 0; // drop through to process + } else if (cclass == charclass.eof) { + pushtoken(parseinfo, scc.s_parseerrspecialvalue, tokentype.error, 0); + state = 0; + } else { + str += ch; + } + } + + if (state == 0) { + if (cclass == charclass.num) { + str = ch; + state = parsestate.num; + } else if (cclass == charclass.numstart) { + str = ch; + haddecimal = true; + state = parsestate.num; + } else if (cclass == charclass.alpha || cclass == charclass.incoord) { + str = ch; + state = parsestate.alpha; + } else if (cclass == charclass.specialstart) { + str = ch; + state = parsestate.specialvalue; + } else if (cclass == charclass.op) { + str = ch; + if (parseinfo.length > 0) { + last_token = parseinfo[parseinfo.length - 1]; + last_token_type = last_token.type; + last_token_text = last_token.text; + if (last_token_type == charclass.op) { + if (last_token_text == "<" || last_token_text == ">") { + str = last_token_text + str; + parseinfo.pop(); + if (parseinfo.length > 0) { + last_token = parseinfo[parseinfo.length - 1]; + last_token_type = last_token.type; + last_token_text = last_token.text; + } else { + last_token_type = charclass.eof; + last_token_text = "EOF"; + } + } + } + } else { + last_token_type = charclass.eof; + last_token_text = "EOF"; + } + t = tokentype.op; + if ( + parseinfo.length == 0 || + (last_token_type == charclass.op && + last_token_text != ")" && + last_token_text != "%") + ) { + // Unary operator + if (str == "-") { + // M is unary minus + str = "M"; + ch = "M"; + } else if (str == "+") { + // P is unary plus + str = "P"; + ch = "P"; + } else if (str == ")" && last_token_text == "(") { + // null arg list OK + } else if (str != "(") { + // binary-op open-paren OK, others no + t = tokentype.error; + str = scc.s_parseerrtwoops; + } + } else if (str.length > 1) { + if (str == ">=") { + // G is >= + str = "G"; + ch = "G"; + } else if (str == "<=") { + // L is <= + str = "L"; + ch = "L"; + } else if (str == "<>") { + // N is <> + str = "N"; + ch = "N"; + } else { + t = tokentype.error; + str = scc.s_parseerrtwoops; + } + } + pushtoken(parseinfo, str, t, ch); + state = 0; + } else if (cclass == charclass.quote) { + // starting a string + str = ""; + state = parsestate.string; + } else if (cclass == charclass.space) { + // store so can reconstruct spacing + pushtoken(parseinfo, " ", tokentype.space, 0); + } else if (cclass == charclass.eof) { + // ignore -- needed to have extra loop to close out other things + } else { + // unknown class - such as unknown char + pushtoken(parseinfo, scc.s_parseerrchar, tokentype.error, 0); + } + } + } + + return parseinfo; + }; + + SocialCalc.Formula.ParsePushToken = function ( + parseinfo, + ttext, + ttype, + topcode + ) { + parseinfo.push({ text: ttext, type: ttype, opcode: topcode }); + }; + + /* ******************* + + result = SocialCalc.Formula.evaluate_parsed_formula(parseinfo, sheet, allowrangereturn) + + Does the calculation expressed in a parsed formula, returning a value, its type, and error info + returns: {value: value, type: valuetype, error: errortext}. + + If allowrangereturn is present and true, can return a range (e.g., "A1:A10" - translated from "A1|A10|") + + ************************* */ + + SocialCalc.Formula.evaluate_parsed_formula = function ( + parseinfo, + sheet, + allowrangereturn + ) { + var result; + + var scf = SocialCalc.Formula; + var tokentype = scf.TokenType; + + var revpolish; + var parsestack = []; + + var errortext = ""; + + revpolish = scf.ConvertInfixToPolish(parseinfo); // result is either an array or a string with error text + + result = scf.EvaluatePolish(parseinfo, revpolish, sheet, allowrangereturn); + + return result; + }; + + // + // revpolish = SocialCalc.Formula.ConvertInfixToPolish(parseinfo) + // + // Convert infix to reverse polish notation + // + // Returns revpolish array with a sequence of references to tokens by number if successful. + // Errors return a string with the error. + // + // Based upon the algorithm shown in Wikipedia "Reverse Polish notation" article + // and then enhanced for additional spreadsheet things + // + + SocialCalc.Formula.ConvertInfixToPolish = function (parseinfo) { + var scf = SocialCalc.Formula; + var scc = SocialCalc.Constants; + var tokentype = scf.TokenType; + var token_precedence = scf.TokenPrecedence; + + var revpolish = []; + var parsestack = []; + + var errortext = ""; + + var function_start = -1; + + var i, pii, ttype, ttext, tprecedence, tstackprecedence; + + for (i = 0; i < parseinfo.length; i++) { + pii = parseinfo[i]; + ttype = pii.type; + ttext = pii.text; + if ( + ttype == tokentype.num || + ttype == tokentype.coord || + ttype == tokentype.string + ) { + revpolish.push(i); + } else if (ttype == tokentype.name) { + parsestack.push(i); + revpolish.push(function_start); + } else if (ttype == tokentype.space) { + // ignore + continue; + } else if (ttext == ",") { + while ( + parsestack.length && + parseinfo[parsestack[parsestack.length - 1]].text != "(" + ) { + revpolish.push(parsestack.pop()); + } + if (parsestack.length == 0) { + // no ( -- error + errortext = scc.s_parseerrmissingopenparen; + break; + } + } else if (ttext == "(") { + parsestack.push(i); + } else if (ttext == ")") { + while ( + parsestack.length && + parseinfo[parsestack[parsestack.length - 1]].text != "(" + ) { + revpolish.push(parsestack.pop()); + } + if (parsestack.length == 0) { + // no ( -- error + errortext = scc.s_parseerrcloseparennoopen; + break; + } + parsestack.pop(); + if ( + parsestack.length && + parseinfo[parsestack[parsestack.length - 1]].type == tokentype.name + ) { + revpolish.push(parsestack.pop()); + } + } else if (ttype == tokentype.op) { + if ( + parsestack.length && + parseinfo[parsestack[parsestack.length - 1]].type == tokentype.name + ) { + revpolish.push(parsestack.pop()); + } + while ( + parsestack.length && + parseinfo[parsestack[parsestack.length - 1]].type == tokentype.op && + parseinfo[parsestack[parsestack.length - 1]].text != "(" + ) { + tprecedence = token_precedence[pii.opcode]; + tstackprecedence = + token_precedence[ + parseinfo[parsestack[parsestack.length - 1]].opcode + ]; + if (tprecedence >= 0 && tprecedence < tstackprecedence) { + break; + } else if (tprecedence < 0) { + tprecedence = -tprecedence; + if (tstackprecedence < 0) tstackprecedence = -tstackprecedence; + if (tprecedence <= tstackprecedence) { + break; + } + } + revpolish.push(parsestack.pop()); + } + parsestack.push(i); + } else if (ttype == tokentype.error) { + errortext = ttext; + break; + } else { + errortext = "Internal error while processing parsed formula. "; + break; + } + } + while (parsestack.length > 0) { + if (parseinfo[parsestack[parsestack.length - 1]].text == "(") { + errortext = scc.s_parseerrmissingcloseparen; + break; + } + revpolish.push(parsestack.pop()); + } + + if (errortext) { + return errortext; + } + + return revpolish; + }; + + // + // result = SocialCalc.Formula.EvaluatePolish(parseinfo, revpolish, sheet, allowrangereturn) + // + // Execute reverse polish representation of formula + // + // Operand values are objects in the operand array with a "type" and an optional "value". + // Type can have these values (many are type and sub-type as two or more letters): + // "tw", "th", "t", "n", "nt", "coord", "range", "start", "eErrorType", "b" (blank) + // The value of a coord is in the form A57 or A57!sheetname + // The value of a range is coord|coord|number where number starts at 0 and is + // the offset of the next item to fetch if you are going through the range one by one + // The number starts as a null string ("A1|B3|") + // + + SocialCalc.Formula.EvaluatePolish = function ( + parseinfo, + revpolish, + sheet, + allowrangereturn + ) { + var scf = SocialCalc.Formula; + var scc = SocialCalc.Constants; + var tokentype = scf.TokenType; + var lookup_result_type = scf.LookupResultType; + var typelookup = scf.TypeLookupTable; + var operand_as_number = scf.OperandAsNumber; + var operand_as_text = scf.OperandAsText; + var operand_value_and_type = scf.OperandValueAndType; + var operands_as_coord_on_sheet = scf.OperandsAsCoordOnSheet; + var format_number_for_display = + SocialCalc.format_number_for_display || + function (v, t, f) { + return v + ""; + }; + + var errortext = ""; + var function_start = -1; + var missingOperandError = { + value: "", + type: "e#VALUE!", + error: scc.s_parseerrmissingoperand, + }; + var value; + + var operand = []; + var PushOperand = function (t, v) { + operand.push({ type: t, value: v }); + }; + + var i, + rii, + prii, + ttype, + ttext, + value1, + value2, + tostype, + tostype2, + resulttype, + valuetype, + cond, + vmatch, + smatch; + + if (!parseinfo.length || !(revpolish instanceof Array)) { + return { + value: "", + type: "e#VALUE!", + error: typeof revpolish == "string" ? revpolish : "", + }; + } + + for (i = 0; i < revpolish.length; i++) { + rii = revpolish[i]; + if (rii == function_start) { + // Remember the start of a function argument list + PushOperand("start", 0); + continue; + } + + prii = parseinfo[rii]; + ttype = prii.type; + ttext = prii.text; + + if (ttype == tokentype.num) { + PushOperand("n", ttext - 0); + } else if (ttype == tokentype.coord) { + PushOperand("coord", ttext); + } else if (ttype == tokentype.string) { + PushOperand("t", ttext); + } else if (ttype == tokentype.op) { + if (operand.length <= 0) { + // Nothing on the stack... + return missingOperandError; + break; // done + } + + // Unary minus + + if (ttext == "M") { + value1 = operand_as_number(sheet, operand); + resulttype = lookup_result_type( + value1.type, + value1.type, + typelookup.unaryminus + ); + PushOperand(resulttype, -value1.value); + } + + // Unary plus + else if (ttext == "P") { + value1 = operand_as_number(sheet, operand); + resulttype = lookup_result_type( + value1.type, + value1.type, + typelookup.unaryplus + ); + PushOperand(resulttype, value1.value); + } + + // Unary % - percent, left associative + else if (ttext == "%") { + value1 = operand_as_number(sheet, operand); + resulttype = lookup_result_type( + value1.type, + value1.type, + typelookup.unarypercent + ); + PushOperand(resulttype, 0.01 * value1.value); + } + + // & - string concatenate + else if (ttext == "&") { + if (operand.length <= 1) { + // Need at least two things on the stack... + return missingOperandError; + } + value2 = operand_as_text(sheet, operand); + value1 = operand_as_text(sheet, operand); + resulttype = lookup_result_type( + value1.type, + value1.type, + typelookup.concat + ); + PushOperand(resulttype, value1.value + value2.value); + } + + // : - Range constructor + else if (ttext == ":") { + if (operand.length <= 1) { + // Need at least two things on the stack... + return missingOperandError; + } + value1 = scf.OperandsAsRangeOnSheet(sheet, operand); // get coords even if use name on other sheet + if (value1.error) { + // not available + errortext = errortext || value1.error; + } + PushOperand(value1.type, value1.value); // push sheetname with range on that sheet + } + + // ! - sheetname!coord + else if (ttext == "!") { + if (operand.length <= 1) { + // Need at least two things on the stack... + return missingOperandError; + } + value1 = operands_as_coord_on_sheet(sheet, operand); // get coord even if name on other sheet + if (value1.error) { + // not available + errortext = errortext || value1.error; + } + PushOperand(value1.type, value1.value); // push sheetname with coord or range on that sheet + } + + // Comparison operators: < L = G > N (< <= = >= > <>) + else if ( + ttext == "<" || + ttext == "L" || + ttext == "=" || + ttext == "G" || + ttext == ">" || + ttext == "N" + ) { + if (operand.length <= 1) { + // Need at least two things on the stack... + errortext = scc.s_parseerrmissingoperand; // remember error + break; + } + value2 = operand_value_and_type(sheet, operand); + value1 = operand_value_and_type(sheet, operand); + if (value1.type.charAt(0) == "n" && value2.type.charAt(0) == "n") { + // compare two numbers + cond = 0; + if (ttext == "<") { + cond = value1.value < value2.value ? 1 : 0; + } else if (ttext == "L") { + cond = value1.value <= value2.value ? 1 : 0; + } else if (ttext == "=") { + cond = value1.value == value2.value ? 1 : 0; + } else if (ttext == "G") { + cond = value1.value >= value2.value ? 1 : 0; + } else if (ttext == ">") { + cond = value1.value > value2.value ? 1 : 0; + } else if (ttext == "N") { + cond = value1.value != value2.value ? 1 : 0; + } + PushOperand("nl", cond); + } else if (value1.type.charAt(0) == "e") { + // error on left + PushOperand(value1.type, 0); + } else if (value2.type.charAt(0) == "e") { + // error on right + PushOperand(value2.type, 0); + } else { + // text maybe mixed with numbers or blank + tostype = value1.type.charAt(0); + tostype2 = value2.type.charAt(0); + if (tostype == "n") { + value1.value = format_number_for_display(value1.value, "n", ""); + } else if (tostype == "b") { + value1.value = ""; + } + if (tostype2 == "n") { + value2.value = format_number_for_display(value2.value, "n", ""); + } else if (tostype2 == "b") { + value2.value = ""; + } + cond = 0; + value1.value = value1.value.toLowerCase(); // ignore case + value2.value = value2.value.toLowerCase(); + if (ttext == "<") { + cond = value1.value < value2.value ? 1 : 0; + } else if (ttext == "L") { + cond = value1.value <= value2.value ? 1 : 0; + } else if (ttext == "=") { + cond = value1.value == value2.value ? 1 : 0; + } else if (ttext == "G") { + cond = value1.value >= value2.value ? 1 : 0; + } else if (ttext == ">") { + cond = value1.value > value2.value ? 1 : 0; + } else if (ttext == "N") { + cond = value1.value != value2.value ? 1 : 0; + } + PushOperand("nl", cond); + } + } + + // Normal infix arithmethic operators: +, -. *, /, ^ + else { + // what's left are the normal infix arithmetic operators + if (operand.length <= 1) { + // Need at least two things on the stack... + errortext = scc.s_parseerrmissingoperand; // remember error + break; + } + value2 = operand_as_number(sheet, operand); + value1 = operand_as_number(sheet, operand); + if (ttext == "+") { + resulttype = lookup_result_type( + value1.type, + value2.type, + typelookup.plus + ); + PushOperand(resulttype, value1.value + value2.value); + } else if (ttext == "-") { + resulttype = lookup_result_type( + value1.type, + value2.type, + typelookup.plus + ); + PushOperand(resulttype, value1.value - value2.value); + } else if (ttext == "*") { + resulttype = lookup_result_type( + value1.type, + value2.type, + typelookup.plus + ); + PushOperand(resulttype, value1.value * value2.value); + } else if (ttext == "/") { + if (value2.value != 0) { + PushOperand("n", value1.value / value2.value); // gives plain numeric result type + } else { + PushOperand("e#DIV/0!", 0); + } + } else if (ttext == "^") { + value1.value = Math.pow(value1.value, value2.value); + value1.type = "n"; // gives plain numeric result type + if (isNaN(value1.value)) { + value1.value = 0; + value1.type = "e#NUM!"; + } + PushOperand(value1.type, value1.value); + } + } + } + + // function or name + else if (ttype == tokentype.name) { + errortext = scf.CalculateFunction(ttext, operand, sheet); + if (errortext) break; + } else { + errortext = + scc.s_InternalError + "Unknown token " + ttype + " (" + ttext + "). "; + break; + } + } + + // look at final value and handle special cases + + value = operand[0] ? operand[0].value : ""; + tostype = operand[0] ? operand[0].type : ""; + + if (tostype == "name") { + // name - expand it + value1 = SocialCalc.Formula.LookupName(sheet, value); + value = value1.value; + tostype = value1.type; + errortext = errortext || value1.error; + } + + if (tostype == "coord") { + // the value is a coord reference, get its value and type + value1 = operand_value_and_type(sheet, operand); + value = value1.value; + tostype = value1.type; + if (tostype == "b") { + tostype = "n"; + value = 0; + } + } + + if (operand.length > 1 && !errortext) { + // something left - error + errortext += scc.s_parseerrerrorinformula; + } + + // set return type + + valuetype = tostype; + + if (tostype.charAt(0) == "e") { + // error value + errortext = + errortext || tostype.substring(1) || scc.s_calcerrerrorvalueinformula; + } else if (tostype == "range") { + vmatch = value.match(/^(.*)\|(.*)\|/); + smatch = vmatch[1].indexOf("!"); + if (smatch >= 0) { + // swap sheetname + vmatch[1] = + vmatch[1].substring(smatch + 1) + + "!" + + vmatch[1].substring(0, smatch).toUpperCase(); + } else { + vmatch[1] = vmatch[1].toUpperCase(); + } + value = vmatch[1] + ":" + vmatch[2].toUpperCase(); + if (!allowrangereturn) { + errortext = scc.s_formularangeresult + " " + value; + } + } + + if (errortext && valuetype.charAt(0) != "e") { + value = errortext; + valuetype = "e"; + } + + // look for overflow + + if (valuetype.charAt(0) == "n" && (isNaN(value) || !isFinite(value))) { + value = 0; + valuetype = "e#NUM!"; + errortext = isNaN(value) + ? scc.s_calcerrnumericnan + : scc.s_calcerrnumericoverflow; + } + + return { value: value, type: valuetype, error: errortext }; + }; + + /* + # + # resulttype = SocialCalc.Formula.LookupResultType(type1, type2, typelookup); + # + # typelookup has values of the following form: + # + # typelookup{"typespec1"} = "|typespec2A:resultA|typespec2B:resultB|..." + # + # First type1 is looked up. If no match, then the first letter (major type) of type1 plus "*" is looked up + # resulttype is type1 if result is "1", type2 if result is "2", otherwise the value of result. + # + */ + + SocialCalc.Formula.LookupResultType = function (type1, type2, typelookup) { + var pos1, pos2, result; + + var table1 = typelookup[type1]; + + if (!table1) { + table1 = typelookup[type1.charAt(0) + "*"]; + if (!table1) { + return ( + "e#VALUE! (internal error, missing LookupResultType " + + type1.charAt(0) + + "*)" + ); // missing from table -- please add it + } + } + pos1 = table1.indexOf("|" + type2 + ":"); + if (pos1 >= 0) { + pos2 = table1.indexOf("|", pos1 + 1); + if (pos2 < 0) + return ( + "e#VALUE! (internal error, incorrect LookupResultType " + table1 + ")" + ); + result = table1.substring(pos1 + type2.length + 2, pos2); + if (result == "1") return type1; + if (result == "2") return type2; + return result; + } + pos1 = table1.indexOf("|" + type2.charAt(0) + "*:"); + if (pos1 >= 0) { + pos2 = table1.indexOf("|", pos1 + 1); + if (pos2 < 0) + return ( + "e#VALUE! (internal error, incorrect LookupResultType " + table1 + ")" + ); + result = table1.substring(pos1 + 4, pos2); + if (result == "1") return type1; + if (result == "2") return type2; + return result; + } + return "e#VALUE!"; + }; + + /* + # + # operandinfo = SocialCalc.Formula.TopOfStackValueAndType(sheet, operand) + # + # Returns top of stack value and type and then pops the stack. + # The result is {value: value, type: type, error: "only if bad error"} + # + */ + + SocialCalc.Formula.TopOfStackValueAndType = function (sheet, operand) { + var cellvtype, cell, pos, coordsheet; + var scf = SocialCalc.Formula; + + var result = { type: "", value: "" }; + + var stacklen = operand.length; + + if (!stacklen) { + // make sure something is there + result.error = + SocialCalc.Constants.s_InternalError + "no operand on stack"; + return result; + } + + result.value = operand[stacklen - 1].value; // get top of stack + result.type = operand[stacklen - 1].type; + operand.pop(); // we have data - pop stack + + if (result.type == "name") { + result = scf.LookupName(sheet, result.value); + } + + return result; + }; + + /* + # + # operandinfo = OperandAsNumber(sheet, operand) + # + # Uses operand_value_and_type to get top of stack and pops it. + # Returns numeric value and type. + # Text values are treated as 0 if they can't be converted somehow. + # + */ + + SocialCalc.Formula.OperandAsNumber = function (sheet, operand) { + var t, valueinfo; + var operandinfo = SocialCalc.Formula.OperandValueAndType(sheet, operand); + + t = operandinfo.type.charAt(0); + + if (t == "n") { + operandinfo.value = operandinfo.value - 0; + } else if (t == "b") { + // blank cell + operandinfo.type = "n"; + operandinfo.value = 0; + } else if (t == "e") { + // error + operandinfo.value = 0; + } else { + valueinfo = SocialCalc.DetermineValueType + ? SocialCalc.DetermineValueType(operandinfo.value) + : { value: operandinfo.value - 0, type: "n" }; // if without rest of SocialCalc + if (valueinfo.type.charAt(0) == "n") { + operandinfo.value = valueinfo.value - 0; + operandinfo.type = valueinfo.type; + } else { + operandinfo.value = 0; + operandinfo.type = valueinfo.type; + } + } + + return operandinfo; + }; + + /* + # + # operandinfo = OperandAsText(sheet, operand) + # + # Uses operand_value_and_type to get top of stack and pops it. + # Returns text value, preserving sub-type. + # + */ + + SocialCalc.Formula.OperandAsText = function (sheet, operand) { + var t, valueinfo; + var operandinfo = SocialCalc.Formula.OperandValueAndType(sheet, operand); + + t = operandinfo.type.charAt(0); + + if (t == "t") { + // any flavor of text returns as is + } else if (t == "n") { + operandinfo.value = SocialCalc.format_number_for_display + ? SocialCalc.format_number_for_display( + operandinfo.value, + operandinfo.type, + "" + ) + : (operandinfo.value = operandinfo.value + ""); + operandinfo.type = "t"; + } else if (t == "b") { + // blank + operandinfo.value = ""; + operandinfo.type = "t"; + } else if (t == "e") { + // error + operandinfo.value = ""; + } else { + operand.value = operandinfo.value + ""; + operand.type = "t"; + } + + return operandinfo; + }; + + /* + # + # result = SocialCalc.Formula.OperandValueAndType(sheet, operand) + # + # Pops the top of stack and returns it, following a coord reference if necessary. + # The result is {value: value, type: type, error: "only if bad error"} + # Ranges are returned as if they were pushed onto the stack first coord first + # Also sets type with "t", "n", "th", etc., as appropriate + # + */ + + SocialCalc.Formula.OperandValueAndType = function (sheet, operand) { + var cellvtype, cell, pos, coordsheet; + var scf = SocialCalc.Formula; + + var result = { type: "", value: "" }; + + var stacklen = operand.length; + + if (!stacklen) { + // make sure something is there + result.error = + SocialCalc.Constants.s_InternalError + "no operand on stack"; + return result; + } + + result.value = operand[stacklen - 1].value; // get top of stack + result.type = operand[stacklen - 1].type; + operand.pop(); // we have data - pop stack + + if (result.type == "name") { + result = scf.LookupName(sheet, result.value); + } + + if (result.type == "range") { + result = scf.StepThroughRangeDown(operand, result.value); + } + + if (result.type == "coord") { + // value is a coord reference + coordsheet = sheet; + pos = result.value.indexOf("!"); + if (pos != -1) { + // sheet reference + coordsheet = scf.FindInSheetCache(result.value.substring(pos + 1)); // get other sheet + if (coordsheet == null) { + // unavailable + result.type = "e#REF!"; + result.error = + SocialCalc.Constants.s_sheetunavailable + + " " + + result.value.substring(pos + 1); + result.value = 0; + return result; + } + result.value = result.value.substring(0, pos); // get coord part + } + + if (coordsheet) { + cell = coordsheet.cells[SocialCalc.Formula.PlainCoord(result.value)]; + if (cell) { + cellvtype = cell.valuetype; // get type of value in the cell it points to + result.value = cell.datavalue; + } else { + cellvtype = "b"; + } + } else { + cellvtype = "e#N/A"; + result.value = 0; + } + result.type = cellvtype || "b"; + if (result.type == "b") { + // blank + result.value = 0; + } + } + + return result; + }; + + /* + # + # operandinfo = SocialCalc.Formula.OperandAsCoord(sheet, operand) + # + # Gets top of stack and pops it. + # Returns coord value. All others are treated as an error. + # + */ + + SocialCalc.Formula.OperandAsCoord = function (sheet, operand) { + var scf = SocialCalc.Formula; + + var result = { type: "", value: "" }; + + var stacklen = operand.length; + + result.value = operand[stacklen - 1].value; // get top of stack + result.type = operand[stacklen - 1].type; + operand.pop(); // we have data - pop stack + if (result.type == "name") { + result = SocialCalc.Formula.LookupName(sheet, result.value); + } + if (result.type == "coord") { + // value is a coord reference + return result; + } else { + result.value = SocialCalc.Constants.s_calcerrcellrefmissing; + result.type = "e#REF!"; + return result; + } + }; + + /* + # + # result = SocialCalc.Formula.OperandsAsCoordOnSheet(sheet, operand) + # + # Gets 2 at top of stack and pops them, treating them as sheetname!coord-or-name. + # Returns stack-style coord value (coord!sheetname, or coord!sheetname|coord|) with + # a type of coord or range. All others are treated as an error. + # If sheetname not available, sets result.error. + # + */ + + SocialCalc.Formula.OperandsAsCoordOnSheet = function (sheet, operand) { + var sheetname, othersheet, pos1, pos2; + var value1 = {}; + var result = {}; + var scf = SocialCalc.Formula; + + var stacklen = operand.length; + value1.value = operand[stacklen - 1].value; // get top of stack - coord or name + value1.type = operand[stacklen - 1].type; + operand.pop(); // we have data - pop stack + + sheetname = scf.OperandAsSheetName(sheet, operand); // get sheetname as text + othersheet = scf.FindInSheetCache(sheetname.value); + if (othersheet == null) { + // unavailable + result.type = "e#REF!"; + result.value = 0; + result.error = + SocialCalc.Constants.s_sheetunavailable + " " + sheetname.value; + return result; + } + + if (value1.type == "name") { + value1 = scf.LookupName(othersheet, value1.value); + } + result.type = value1.type; + if (value1.type == "coord") { + // value is a coord reference + result.value = value1.value + "!" + sheetname.value; // return in the format as used on stack + } else if (value1.type == "range") { + // value is a range reference + pos1 = value1.value.indexOf("|"); + pos2 = value1.value.indexOf("|", pos1 + 1); + result.value = + value1.value.substring(0, pos1) + + "!" + + sheetname.value + + "|" + + value1.value.substring(pos1 + 1, pos2) + + "|"; + } else if (value1.type.charAt(0) == "e") { + result.value = value1.value; + } else { + result.error = SocialCalc.Constants.s_calcerrcellrefmissing; + result.type = "e#REF!"; + result.value = 0; + } + return result; + }; + + /* + # + # result = SocialCalc.Formula.OperandsAsRangeOnSheet(sheet, operand) + # + # Gets 2 at top of stack and pops them, treating them as coord2-or-name:coord1. + # Name is evaluated on sheet of coord1. + # Returns result with "value" of stack-style range value (coord!sheetname|coord|) and + # "type" of "range". All others are treated as an error. + # + */ + + SocialCalc.Formula.OperandsAsRangeOnSheet = function (sheet, operand) { + var value1, othersheet, pos1, pos2; + var value2 = {}; + var scf = SocialCalc.Formula; + var scc = SocialCalc.Constants; + + var stacklen = operand.length; + value2.value = operand[stacklen - 1].value; // get top of stack - coord or name for "right" side + value2.type = operand[stacklen - 1].type; + operand.pop(); // we have data - pop stack + + value1 = scf.OperandAsCoord(sheet, operand); // get "left" coord + if (value1.type != "coord") { + // not a coord, which it must be + return { value: 0, type: "e#REF!" }; + } + + othersheet = sheet; + pos1 = value1.value.indexOf("!"); + if (pos1 != -1) { + // sheet reference + pos2 = value1.value.indexOf("|", pos1 + 1); + if (pos2 < 0) pos2 = value1.value.length; + othersheet = scf.FindInSheetCache(value1.value.substring(pos1 + 1, pos2)); // get other sheet + if (othersheet == null) { + // unavailable + return { + value: 0, + type: "e#REF!", + errortext: + scc.s_sheetunavailable + + " " + + value1.value.substring(pos1 + 1, pos2), + }; + } + } + + if (value2.type == "name") { + // coord:name is allowed, if name is just one cell + value2 = scf.LookupName(othersheet, value2.value); + } + + if (value2.type == "coord") { + // value is a coord reference, so return the combined range + return { value: value1.value + "|" + value2.value + "|", type: "range" }; // return range in the format as used on stack + } else { + // bad form + return { value: scc.s_calcerrcellrefmissing, type: "e#REF!" }; + } + }; + + /* + # + # result = SocialCalc.Formula.OperandAsSheetName(sheet, operand) + # + # Gets top of stack and pops it. + # Returns sheetname value. All others are treated as an error. + # Accepts text, cell reference, and named value which is one of those two. + # + */ + + SocialCalc.Formula.OperandAsSheetName = function (sheet, operand) { + var nvalue, cell; + + var scf = SocialCalc.Formula; + + var result = { type: "", value: "" }; + + var stacklen = operand.length; + + result.value = operand[stacklen - 1].value; // get top of stack + result.type = operand[stacklen - 1].type; + operand.pop(); // we have data - pop stack + if (result.type == "name") { + nvalue = SocialCalc.Formula.LookupName(sheet, result.value); + if (!nvalue.value) { + // not a known name - return bare name as the name value + return result; + } + result.value = nvalue.value; + result.type = nvalue.type; + } + if (result.type == "coord") { + // value is a coord reference, follow it to find sheet name + cell = sheet.cells[SocialCalc.Formula.PlainCoord(result.value)]; + if (cell) { + result.value = cell.datavalue; + result.type = cell.valuetype; + } else { + result.value = ""; + result.type = "b"; + } + } + if (result.type.charAt(0) == "t") { + // value is a string which could be a sheet name + return result; + } else { + result.value = ""; + result.error = SocialCalc.Constants.s_calcerrsheetnamemissing; + return result; + } + }; + + // + // value = SocialCalc.Formula.LookupName(sheet, name) + // + // Returns value and type of a named value + // Names are case insensitive + // Names may have a definition which is a coord (A1), a range (A1:B7), or a formula (=OFFSET(A1,0,0,5,1)) + // Note: The range must not have sheet names ("!") in them. + // + + SocialCalc.Formula.LookupName = function (sheet, name) { + var pos, specialc, parseinfo; + var names = sheet.names; + var value = {}; + var startedwalk = false; + + if (names[name.toUpperCase()]) { + // is name defined? + + value.value = names[name.toUpperCase()].definition; // yes + + if (value.value.charAt(0) == "=") { + // formula + if (!sheet.checknamecirc) { + // are we possibly walking the name tree? + sheet.checknamecirc = {}; // not yet + startedwalk = true; // remember we are the reference that started it + } else { + if (sheet.checknamecirc[name]) { + // circular reference + value.type = "e#NAME?"; + value.error = + SocialCalc.Constants.s_circularnameref + ' "' + name + '".'; + return value; + } + } + sheet.checknamecirc[name] = true; + + parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens( + value.value.substring(1) + ); + value = SocialCalc.Formula.evaluate_parsed_formula(parseinfo, sheet, 1); // parse formula, allowing range return + + delete sheet.checknamecirc[name]; // done with us + if (startedwalk) { + delete sheet.checknamecirc; // done with walk + } + + if (value.type != "range") { + return value; + } + } + + pos = value.value.indexOf(":"); + if (pos != -1) { + // range + value.type = "range"; + value.value = + value.value.substring(0, pos) + + "|" + + value.value.substring(pos + 1) + + "|"; + value.value = value.value.toUpperCase(); + } else { + value.type = "coord"; + value.value = value.value.toUpperCase(); + } + return value; + } else if ( + (specialc = SocialCalc.Formula.SpecialConstants[name.toUpperCase()]) + ) { + // special constant, like #REF! + pos = specialc.indexOf(","); + value.value = specialc.substring(0, pos) - 0; + value.type = specialc.substring(pos + 1); + return value; + } else { + value.value = ""; + value.type = "e#NAME?"; + value.error = + SocialCalc.Constants.s_calcerrunknownname + ' "' + name + '"'; + return value; + } + }; + + /* + # + # coord = SocialCalc.Formula.StepThroughRangeDown(operand, rangevalue) + # + # Returns next coord in a range, keeping track on the operand stack + # Goes from upper left across and down to bottom right. + # + */ + + SocialCalc.Formula.StepThroughRangeDown = function (operand, rangevalue) { + var value1, value2, sequence, pos1, pos2, sheet1, rp, c, r, count; + var scf = SocialCalc.Formula; + + pos1 = rangevalue.indexOf("|"); + pos2 = rangevalue.indexOf("|", pos1 + 1); + value1 = rangevalue.substring(0, pos1); + value2 = rangevalue.substring(pos1 + 1, pos2); + sequence = rangevalue.substring(pos2 + 1) - 0; + + pos1 = value1.indexOf("!"); + if (pos1 != -1) { + sheet1 = value1.substring(pos1); + value1 = value1.substring(0, pos1); + } else { + sheet1 = ""; + } + pos1 = value2.indexOf("!"); + if (pos1 != -1) { + value2 = value2.substring(0, pos1); + } + + rp = scf.OrderRangeParts(value1, value2); + + count = 0; + for (r = rp.r1; r <= rp.r2; r++) { + for (c = rp.c1; c <= rp.c2; c++) { + count++; + if (count > sequence) { + if (r != rp.r2 || c != rp.c2) { + // keep on stack until done + scf.PushOperand( + operand, + "range", + value1 + sheet1 + "|" + value2 + "|" + count + ); + } + return { value: SocialCalc.crToCoord(c, r) + sheet1, type: "coord" }; + } + } + } + }; + + /* + # + # result = SocialCalc.Formula.DecodeRangeParts(sheetdata, range) + # + # Returns sheetdata for the sheet where the range is, as well as + # the number of the first column in the range, the number of columns, + # and equivalent row information: + # + # {sheetdata: sheet, sheetname: name-or-"", col1num: n, ncols: n, row1num: n, nrows: n} + # + # If any errors, a null result is returned. + # + */ + + SocialCalc.Formula.DecodeRangeParts = function (sheetdata, range) { + var value1, value2, pos1, pos2, sheet1, coordsheetdata, rp; + + var scf = SocialCalc.Formula; + + pos1 = range.indexOf("|"); + pos2 = range.indexOf("|", pos1 + 1); + value1 = range.substring(0, pos1); + value2 = range.substring(pos1 + 1, pos2); + + pos1 = value1.indexOf("!"); + if (pos1 != -1) { + sheet1 = value1.substring(pos1 + 1); + value1 = value1.substring(0, pos1); + } else { + sheet1 = ""; + } + pos1 = value2.indexOf("!"); + if (pos1 != -1) { + value2 = value2.substring(0, pos1); + } + + coordsheetdata = sheetdata; + if (sheet1) { + // sheet reference + coordsheetdata = scf.FindInSheetCache(sheet1); + if (coordsheetdata == null) { + // unavailable + return null; + } + } + + rp = scf.OrderRangeParts(value1, value2); + + return { + sheetdata: coordsheetdata, + sheetname: sheet1, + col1num: rp.c1, + ncols: rp.c2 - rp.c1 + 1, + row1num: rp.r1, + nrows: rp.r2 - rp.r1 + 1, + }; + }; + + //********************* + // + // Function Handling + // + //********************* + + // List of functions -- Define after functions are defined + // + // SocialCalc.Formula.FunctionList["function_name"] = [function_subroutine, number_of_arguments, arg_def, func_def, func_class] + // function_subroutine takes arguments (fname, operand, foperand, sheet), returns + // errortext or null, pushing result on operand stack. + // number_of_arguments is: + // 0 = no arguments + // >0 = exactly that many arguments + // <0 = that many arguments (abs value) or more + // 100 = don't check + // + // arg_def, if present, is the name of the element in SocialCalc.Formula.FunctionArgDefs. + // func_def, if present, is a string explaining the function. If not, looked up in SocialCalc.Constants. + // func_class, if present, is the comma-separated names of the elements in SocialCalc.Formula.FunctionClasses. + // + // To add a function, just add it to this object. + + if (!SocialCalc.Formula.FunctionList) { + // make sure it is defined (could have been in another module) + SocialCalc.Formula.FunctionList = {}; + } + + // FunctionClasses[classname] = {name: full-name-string, items: [sorted list of function names]}; + // filled in by SocialCalc.Formula.FillFunctionInfo + + SocialCalc.Formula.FunctionClasses = null; // start null to say needs filling in + + // FunctionArgDef[argname] = explicit-string-for-arg-list; + // filled in by SocialCalc.Formula.FillFunctionInfo + + SocialCalc.Formula.FunctionArgDefs = {}; + + /* + # + # errortext = SocialCalc.Formula.CalculateFunction(fname, operand, sheet) + # + # Dispatches for function fname. + # + */ + + SocialCalc.Formula.CalculateFunction = function (fname, operand, sheet) { + var fobj, foperand, ffunc, argnum, ttext; + var scf = SocialCalc.Formula; + var ok = 1; + var errortext = ""; + + fobj = scf.FunctionList[fname]; + + if (fobj) { + foperand = []; + ffunc = fobj[0]; + argnum = fobj[1]; + scf.CopyFunctionArgs(operand, foperand); + if (argnum != 100) { + if (argnum < 0) { + if (foperand.length < -argnum) { + errortext = scf.FunctionArgsError(fname, operand); + return errortext; + } + } else { + if (foperand.length != argnum) { + errortext = scf.FunctionArgsError(fname, operand); + return errortext; + } + } + } + errortext = ffunc(fname, operand, foperand, sheet); + } else { + ttext = fname; + + if (operand.length && operand[operand.length - 1].type == "start") { + // no arguments - name or zero arg function + operand.pop(); + scf.PushOperand(operand, "name", ttext); + } else { + errortext = + SocialCalc.Constants.s_sheetfuncunknownfunction + " " + ttext + ". "; + } + } + + return errortext; + }; + + // + // SocialCalc.Formula.PushOperand(operand, t, v) + // + // Pushes the type and value onto the operand stack + // + + SocialCalc.Formula.PushOperand = function (operand, t, v) { + operand.push({ type: t, value: v }); + }; + + // + // SocialCalc.Formula.CopyFunctionArgs(operand, foperand) + // + // Pops operands from operand and pushes on foperand up to function start + // reversing order in the process. + // + + SocialCalc.Formula.CopyFunctionArgs = function (operand, foperand) { + var fobj, foperand, ffunc, argnum; + var scf = SocialCalc.Formula; + var ok = 1; + var errortext = null; + + while (operand.length > 0 && operand[operand.length - 1].type != "start") { + // get each arg + foperand.push(operand.pop()); // copy it + } + operand.pop(); // get rid of "start" + + return; + }; + + // + // errortext = SocialCalc.Formula.FunctionArgsError(fname, operand) + // + // Pushes appropriate error on operand stack and returns errortext, including fname + // + + SocialCalc.Formula.FunctionArgsError = function (fname, operand) { + var errortext = + SocialCalc.Constants.s_calcerrincorrectargstofunction + + " " + + fname + + ". "; + SocialCalc.Formula.PushOperand(operand, "e#VALUE!", errortext); + + return errortext; + }; + + // + // errortext = SocialCalc.Formula.FunctionSpecificError(fname, operand, errortype, errortext) + // + // Pushes specified error and text on operand stack. + // + + SocialCalc.Formula.FunctionSpecificError = function ( + fname, + operand, + errortype, + errortext + ) { + SocialCalc.Formula.PushOperand(operand, errortype, errortext); + + return errortext; + }; + + // + // haserror = SocialCalc.Formula.CheckForErrorValue(operand, v) + // + // If v.type is an error, push it on operand stack and return true, otherwise return false. + // + + SocialCalc.Formula.CheckForErrorValue = function (operand, v) { + if (v.type.charAt(0) == "e") { + operand.push(v); + return true; + } else { + return false; + } + }; + + ///////////////////////// + // + // FUNCTION INFORMATION ROUTINES + // + + // + // SocialCalc.Formula.FillFunctionInfo() + // + // Goes through function definitions and fills out FunctionArgDefs and FunctionClasses. + // Execute this after any changes to SocialCalc.Constants but before UI is used. + // + + SocialCalc.Formula.FillFunctionInfo = function () { + var scf = SocialCalc.Formula; + var scc = SocialCalc.Constants; + + var fname, f, classes, cname, i; + + if (scf.FunctionClasses) { + // only do once + return; + } + + for (fname in scf.FunctionList) { + f = scf.FunctionList[fname]; + if (f[2]) { + // has an arg def + scf.FunctionArgDefs[f[2]] = scc["s_farg_" + f[2]] || ""; // get it from constants + } + if (!f[3]) { + // no text def, see if in constants + if (scc["s_fdef_" + fname]) { + scf.FunctionList[fname][3] = scc["s_fdef_" + fname]; + } + } + } + + scf.FunctionClasses = {}; + + for (i = 0; i < scc.function_classlist.length; i++) { + cname = scc.function_classlist[i]; + scf.FunctionClasses[cname] = { + name: scc["s_fclass_" + cname], + items: [], + }; + } + + for (fname in scf.FunctionList) { + f = scf.FunctionList[fname]; + classes = f[4] ? f[4].split(",") : []; // get classes + classes.push("all"); + for (i = 0; i < classes.length; i++) { + cname = classes[i]; + scf.FunctionClasses[cname].items.push(fname); + } + } + for (cname in scf.FunctionClasses) { + scf.FunctionClasses[cname].items.sort(); + } + }; + + // + // str = SocialCalc.Formula.FunctionArgString(fname) + // + // Returns a string representing the arguments to function fname. + // + + SocialCalc.Formula.FunctionArgString = function (fname) { + var scf = SocialCalc.Formula; + var fdata = scf.FunctionList[fname]; + var nargs, i, str; + + var adef = fdata[2]; + + if (!adef) { + nargs = fdata[1]; + if (nargs == 0) { + adef = " "; + } else if (nargs > 0) { + str = "v1"; + for (i = 2; i <= nargs; i++) { + str += ", v" + i; + } + return str; + } else if (nargs < 0) { + str = "v1"; + for (i = 2; i < -nargs; i++) { + str += ", v" + i; + } + return str + ", ..."; + } else { + return "nargs: " + nargs; + } + } + + str = scf.FunctionArgDefs[adef] || adef; + + return str; + }; + + ///////////////////////// + // + // FUNCTION DEFINITIONS + // + // The standard function definitions follow. + // + // Note that some need SocialCalc.DetermineValueType to be defined. + // + + /* + # + # AVERAGE(v1,c1:c2,...) + # COUNT(v1,c1:c2,...) + # COUNTA(v1,c1:c2,...) + # COUNTBLANK(v1,c1:c2,...) + # MAX(v1,c1:c2,...) + # MIN(v1,c1:c2,...) + # PRODUCT(v1,c1:c2,...) + # STDEV(v1,c1:c2,...) + # STDEVP(v1,c1:c2,...) + # SUM(v1,c1:c2,...) + # VAR(v1,c1:c2,...) + # VARP(v1,c1:c2,...) + # + # Calculate all of these and then return the desired one (overhead is in accessing not calculating) + # If this routine is changed, check the dseries_functions, too. + # + */ + + SocialCalc.Formula.SeriesFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var value1, t, v1; + + var scf = SocialCalc.Formula; + var operand_value_and_type = scf.OperandValueAndType; + var lookup_result_type = scf.LookupResultType; + var typelookupplus = scf.TypeLookupTable.plus; + + var PushOperand = function (t, v) { + operand.push({ type: t, value: v }); + }; + + var sum = 0; + var resulttypesum = ""; + var count = 0; + var counta = 0; + var countblank = 0; + var product = 1; + var maxval; + var minval; + var mk, sk, mk1, sk1; // For variance, etc.: M sub k, k-1, and S sub k-1 + // as per Knuth "The Art of Computer Programming" Vol. 2 3rd edition, page 232 + + while (foperand.length > 0) { + value1 = operand_value_and_type(sheet, foperand); + t = value1.type.charAt(0); + if (t == "n") count += 1; + if (t != "b") counta += 1; + if (t == "b") countblank += 1; + + if (t == "n") { + v1 = value1.value - 0; // get it as a number + sum += v1; + product *= v1; + maxval = maxval != undefined ? (v1 > maxval ? v1 : maxval) : v1; + minval = minval != undefined ? (v1 < minval ? v1 : minval) : v1; + if (count == 1) { + // initialize with first values for variance used in STDEV, VAR, etc. + mk1 = v1; + sk1 = 0; + } else { + // Accumulate S sub 1 through n as per Knuth noted above + mk = mk1 + (v1 - mk1) / count; + sk = sk1 + (v1 - mk1) * (v1 - mk); + sk1 = sk; + mk1 = mk; + } + resulttypesum = lookup_result_type( + value1.type, + resulttypesum || value1.type, + typelookupplus + ); + } else if (t == "e" && resulttypesum.charAt(0) != "e") { + resulttypesum = value1.type; + } + } + + resulttypesum = resulttypesum || "n"; + + switch (fname) { + case "SUM": + PushOperand(resulttypesum, sum); + break; + + case "PRODUCT": // may handle cases with text differently than some other spreadsheets + PushOperand(resulttypesum, product); + break; + + case "MIN": + PushOperand(resulttypesum, minval || 0); + break; + + case "MAX": + PushOperand(resulttypesum, maxval || 0); + break; + + case "COUNT": + PushOperand("n", count); + break; + + case "COUNTA": + PushOperand("n", counta); + break; + + case "COUNTBLANK": + PushOperand("n", countblank); + break; + + case "AVERAGE": + if (count > 0) { + PushOperand(resulttypesum, sum / count); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "STDEV": + if (count > 1) { + PushOperand(resulttypesum, Math.sqrt(sk / (count - 1))); // sk is never negative according to Knuth + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "STDEVP": + if (count > 1) { + PushOperand(resulttypesum, Math.sqrt(sk / count)); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "VAR": + if (count > 1) { + PushOperand(resulttypesum, sk / (count - 1)); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "VARP": + if (count > 1) { + PushOperand(resulttypesum, sk / count); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + } + + return null; + }; + + // Add to function list + SocialCalc.Formula.FunctionList["AVERAGE"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["COUNT"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["COUNTA"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["COUNTBLANK"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["MAX"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["MIN"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["PRODUCT"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["STDEV"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["STDEVP"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["SUM"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["VAR"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + SocialCalc.Formula.FunctionList["VARP"] = [ + SocialCalc.Formula.SeriesFunctions, + -1, + "vn", + null, + "stat", + ]; + + /* + # + # DAVERAGE(databaserange, fieldname, criteriarange) + # DCOUNT(databaserange, fieldname, criteriarange) + # DCOUNTA(databaserange, fieldname, criteriarange) + # DGET(databaserange, fieldname, criteriarange) + # DMAX(databaserange, fieldname, criteriarange) + # DMIN(databaserange, fieldname, criteriarange) + # DPRODUCT(databaserange, fieldname, criteriarange) + # DSTDEV(databaserange, fieldname, criteriarange) + # DSTDEVP(databaserange, fieldname, criteriarange) + # DSUM(databaserange, fieldname, criteriarange) + # DVAR(databaserange, fieldname, criteriarange) + # DVARP(databaserange, fieldname, criteriarange) + # + # Calculate all of these and then return the desired one (overhead is in accessing not calculating) + # If this routine is changed, check the series_functions, too. + # + */ + + SocialCalc.Formula.DSeriesFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var value1, + tostype, + cr, + dbrange, + fieldname, + criteriarange, + dbinfo, + criteriainfo; + var fieldasnum, targetcol, i, j, k, cell, criteriafieldnums; + var testok, criteriacr, criteria, testcol, testcr; + var t; + + var scf = SocialCalc.Formula; + var operand_value_and_type = scf.OperandValueAndType; + var lookup_result_type = scf.LookupResultType; + var typelookupplus = scf.TypeLookupTable.plus; + + var PushOperand = function (t, v) { + operand.push({ type: t, value: v }); + }; + + var value1 = {}; + + var sum = 0; + var resulttypesum = ""; + var count = 0; + var counta = 0; + var countblank = 0; + var product = 1; + var maxval; + var minval; + var mk, sk, mk1, sk1; // For variance, etc.: M sub k, k-1, and S sub k-1 + // as per Knuth "The Art of Computer Programming" Vol. 2 3rd edition, page 232 + + dbrange = scf.TopOfStackValueAndType(sheet, foperand); // get a range + fieldname = scf.OperandValueAndType(sheet, foperand); // get a value + criteriarange = scf.TopOfStackValueAndType(sheet, foperand); // get a range + + if (dbrange.type != "range" || criteriarange.type != "range") { + return scf.FunctionArgsError(fname, operand); + } + + dbinfo = scf.DecodeRangeParts(sheet, dbrange.value); + criteriainfo = scf.DecodeRangeParts(sheet, criteriarange.value); + + fieldasnum = scf.FieldToColnum( + dbinfo.sheetdata, + dbinfo.col1num, + dbinfo.ncols, + dbinfo.row1num, + fieldname.value, + fieldname.type + ); + if (fieldasnum <= 0) { + PushOperand("e#VALUE!", 0); + return; + } + + targetcol = dbinfo.col1num + fieldasnum - 1; + criteriafieldnums = []; + + for (i = 0; i < criteriainfo.ncols; i++) { + // get criteria field colnums + cell = criteriainfo.sheetdata.GetAssuredCell( + SocialCalc.crToCoord(criteriainfo.col1num + i, criteriainfo.row1num) + ); + criterianum = scf.FieldToColnum( + dbinfo.sheetdata, + dbinfo.col1num, + dbinfo.ncols, + dbinfo.row1num, + cell.datavalue, + cell.valuetype + ); + if (criterianum <= 0) { + PushOperand("e#VALUE!", 0); + return; + } + criteriafieldnums.push(dbinfo.col1num + criterianum - 1); + } + + for (i = 1; i < dbinfo.nrows; i++) { + // go through each row of the database + testok = false; + CRITERIAROW: for (j = 1; j < criteriainfo.nrows; j++) { + // go through each criteria row + for (k = 0; k < criteriainfo.ncols; k++) { + // look at each column + criteriacr = SocialCalc.crToCoord( + criteriainfo.col1num + k, + criteriainfo.row1num + j + ); // where criteria is + cell = criteriainfo.sheetdata.GetAssuredCell(criteriacr); + criteria = cell.datavalue; + if (typeof criteria == "string" && criteria.length == 0) continue; // blank items are OK + testcol = criteriafieldnums[k]; + testcr = SocialCalc.crToCoord(testcol, dbinfo.row1num + i); // cell to check + cell = criteriainfo.sheetdata.GetAssuredCell(testcr); + if ( + !scf.TestCriteria(cell.datavalue, cell.valuetype || "b", criteria) + ) { + continue CRITERIAROW; // does not meet criteria - check next row + } + } + testok = true; // met all the criteria + break CRITERIAROW; + } + if (!testok) { + continue; + } + + cr = SocialCalc.crToCoord(targetcol, dbinfo.row1num + i); // get cell of this row to do the function on + cell = dbinfo.sheetdata.GetAssuredCell(cr); + + value1.value = cell.datavalue; + value1.type = cell.valuetype; + t = value1.type.charAt(0); + if (t == "n") count += 1; + if (t != "b") counta += 1; + if (t == "b") countblank += 1; + + if (t == "n") { + v1 = value1.value - 0; // get it as a number + sum += v1; + product *= v1; + maxval = maxval != undefined ? (v1 > maxval ? v1 : maxval) : v1; + minval = minval != undefined ? (v1 < minval ? v1 : minval) : v1; + if (count == 1) { + // initialize with first values for variance used in STDEV, VAR, etc. + mk1 = v1; + sk1 = 0; + } else { + // Accumulate S sub 1 through n as per Knuth noted above + mk = mk1 + (v1 - mk1) / count; + sk = sk1 + (v1 - mk1) * (v1 - mk); + sk1 = sk; + mk1 = mk; + } + resulttypesum = lookup_result_type( + value1.type, + resulttypesum || value1.type, + typelookupplus + ); + } else if (t == "e" && resulttypesum.charAt(0) != "e") { + resulttypesum = value1.type; + } + } + + resulttypesum = resulttypesum || "n"; + + switch (fname) { + case "DSUM": + PushOperand(resulttypesum, sum); + break; + + case "DPRODUCT": // may handle cases with text differently than some other spreadsheets + PushOperand(resulttypesum, product); + break; + + case "DMIN": + PushOperand(resulttypesum, minval || 0); + break; + + case "DMAX": + PushOperand(resulttypesum, maxval || 0); + break; + + case "DCOUNT": + PushOperand("n", count); + break; + + case "DCOUNTA": + PushOperand("n", counta); + break; + + case "DAVERAGE": + if (count > 0) { + PushOperand(resulttypesum, sum / count); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "DSTDEV": + if (count > 1) { + PushOperand(resulttypesum, Math.sqrt(sk / (count - 1))); // sk is never negative according to Knuth + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "DSTDEVP": + if (count > 1) { + PushOperand(resulttypesum, Math.sqrt(sk / count)); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "DVAR": + if (count > 1) { + PushOperand(resulttypesum, sk / (count - 1)); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "DVARP": + if (count > 1) { + PushOperand(resulttypesum, sk / count); + } else { + PushOperand("e#DIV/0!", 0); + } + break; + + case "DGET": + if (count == 1) { + PushOperand(resulttypesum, sum); + } else if (count == 0) { + PushOperand("e#VALUE!", 0); + } else { + PushOperand("e#NUM!", 0); + } + break; + } + + return; + }; + + SocialCalc.Formula.FunctionList["DAVERAGE"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DCOUNT"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DCOUNTA"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DGET"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DMAX"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DMIN"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DPRODUCT"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DSTDEV"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DSTDEVP"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DSUM"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DVAR"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["DVARP"] = [ + SocialCalc.Formula.DSeriesFunctions, + 3, + "dfunc", + "", + "stat", + ]; + + /* + # + # colnum = SocialCalc.Formula.FieldToColnum(sheet, col1num, ncols, row1num, fieldname, fieldtype) + # + # If fieldname is a number, uses it, otherwise looks up string in cells in row to find field number + # + # If not found, returns 0. + # + */ + + SocialCalc.Formula.FieldToColnum = function ( + sheet, + col1num, + ncols, + row1num, + fieldname, + fieldtype + ) { + var colnum, cell, value; + + if (fieldtype.charAt(0) == "n") { + // number - return it if legal + colnum = fieldname - 0; // make sure a number + if (colnum <= 0 || colnum > ncols) { + return 0; + } + return Math.floor(colnum); + } + + if (fieldtype.charAt(0) != "t") { + // must be text otherwise + return 0; + } + + fieldname = fieldname ? fieldname.toLowerCase() : ""; + + for (colnum = 0; colnum < ncols; colnum++) { + // look through column headers for a match + cell = sheet.GetAssuredCell( + SocialCalc.crToCoord(col1num + colnum, row1num) + ); + value = cell.datavalue; + value = (value + "").toLowerCase(); // ignore case + if (value == fieldname) { + // match + return colnum + 1; + } + } + return 0; // looked at all and no match + }; + + /* + # + # HLOOKUP(value, range, row, [rangelookup]) + # VLOOKUP(value, range, col, [rangelookup]) + # MATCH(value, range, [rangelookup]) + # + */ + + SocialCalc.Formula.LookupFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var lookupvalue, range, offset, rangelookup, offsetvalue, rangeinfo; + var c, + r, + cincr, + rincr, + previousOK, + csave, + rsave, + cell, + value, + valuetype, + cr, + lookupvalue; + + var scf = SocialCalc.Formula; + var operand_value_and_type = scf.OperandValueAndType; + var lookup_result_type = scf.LookupResultType; + var typelookupplus = scf.TypeLookupTable.plus; + + var PushOperand = function (t, v) { + operand.push({ type: t, value: v }); + }; + + lookupvalue = operand_value_and_type(sheet, foperand); + if (typeof lookupvalue.value == "string") { + lookupvalue.value = lookupvalue.value.toLowerCase(); + } + + range = scf.TopOfStackValueAndType(sheet, foperand); + + rangelookup = 1; // default to true or 1 + if (fname == "MATCH") { + if (foperand.length) { + rangelookup = scf.OperandAsNumber(sheet, foperand); + if (rangelookup.type.charAt(0) != "n") { + PushOperand("e#VALUE!", 0); + return; + } + if (foperand.length) { + scf.FunctionArgsError(fname, operand); + return 0; + } + rangelookup = rangelookup.value - 0; + } + } else { + offsetvalue = scf.OperandAsNumber(sheet, foperand); + if (offsetvalue.type.charAt(0) != "n") { + PushOperand("e#VALUE!", 0); + return; + } + offsetvalue = Math.floor(offsetvalue.value); + if (foperand.length) { + rangelookup = scf.OperandAsNumber(sheet, foperand); + if (rangelookup.type.charAt(0) != "n") { + PushOperand("e#VALUE!", 0); + return; + } + if (foperand.length) { + scf.FunctionArgsError(fname, operand); + return 0; + } + rangelookup = rangelookup.value ? 1 : 0; // convert to 1 or 0 + } + } + lookupvalue.type = lookupvalue.type.charAt(0); // only deal with general type + if (lookupvalue.type == "n") { + // if number, make sure a number + lookupvalue.value = lookupvalue.value - 0; + } + + if (range.type != "range") { + scf.FunctionArgsError(fname, operand); + return 0; + } + + rangeinfo = scf.DecodeRangeParts(sheet, range.value, range.type); + if (!rangeinfo) { + PushOperand("e#REF!", 0); + return; + } + + c = 0; + r = 0; + cincr = 0; + rincr = 0; + if (fname == "HLOOKUP") { + cincr = 1; + if (offsetvalue > rangeinfo.nrows) { + PushOperand("e#REF!", 0); + return; + } + } else if (fname == "VLOOKUP") { + rincr = 1; + if (offsetvalue > rangeinfo.ncols) { + PushOperand("e#REF!", 0); + return; + } + } else if (fname == "MATCH") { + if (rangeinfo.ncols > 1) { + if (rangeinfo.nrows > 1) { + PushOperand("e#N/A", 0); + return; + } + cincr = 1; + } else { + rincr = 1; + } + } else { + scf.FunctionArgsError(fname, operand); + return 0; + } + if (offsetvalue < 1 && fname != "MATCH") { + PushOperand("e#VALUE!", 0); + return 0; + } + + previousOK; // if 1, previous test was <. If 2, also this one wasn't + + while (1) { + cr = SocialCalc.crToCoord(rangeinfo.col1num + c, rangeinfo.row1num + r); + cell = rangeinfo.sheetdata.GetAssuredCell(cr); + value = cell.datavalue; + valuetype = cell.valuetype ? cell.valuetype.charAt(0) : "b"; // only deal with general types + if (valuetype == "n") { + value = value - 0; // make sure number + } + if (rangelookup) { + // rangelookup type 1 or -1: look for within brackets for matches + if (lookupvalue.type == "n" && valuetype == "n") { + if (lookupvalue.value == value) { + // match + break; + } + if ( + (rangelookup > 0 && lookupvalue.value > value) || + (rangelookup < 0 && lookupvalue.value < value) + ) { + // possible match: wait and see + previousOK = 1; + csave = c; // remember col and row of last OK + rsave = r; + } else if (previousOK) { + // last one was OK, this one isn't + previousOK = 2; + break; + } + } else if (lookupvalue.type == "t" && valuetype == "t") { + value = typeof value == "string" ? value.toLowerCase() : ""; + if (lookupvalue.value == value) { + // match + break; + } + if ( + (rangelookup > 0 && lookupvalue.value > value) || + (rangelookup < 0 && lookupvalue.value < value) + ) { + // possible match: wait and see + previousOK = 1; + csave = c; + rsave = r; + } else if (previousOK) { + // last one was OK, this one isn't + previousOK = 2; + break; + } + } + } else { + // exact value matches + if (lookupvalue.type == "n" && valuetype == "n") { + if (lookupvalue.value == value) { + // match + break; + } + } else if (lookupvalue.type == "t" && valuetype == "t") { + value = typeof value == "string" ? value.toLowerCase() : ""; + if (lookupvalue.value == value) { + // match + break; + } + } + } + + r += rincr; + c += cincr; + if (r >= rangeinfo.nrows || c >= rangeinfo.ncols) { + // end of range to check, no exact match + if (previousOK) { + // at least one could have been OK + previousOK = 2; + break; + } + PushOperand("e#N/A", 0); + return; + } + } + + if (previousOK == 2) { + // back to last OK + r = rsave; + c = csave; + } + + if (fname == "MATCH") { + value = c + r + 1; // only one may be <> 0 + valuetype = "n"; + } else { + cr = SocialCalc.crToCoord( + rangeinfo.col1num + c + (fname == "VLOOKUP" ? offsetvalue - 1 : 0), + rangeinfo.row1num + r + (fname == "HLOOKUP" ? offsetvalue - 1 : 0) + ); + cell = rangeinfo.sheetdata.GetAssuredCell(cr); + value = cell.datavalue; + valuetype = cell.valuetype; + } + PushOperand(valuetype, value); + + return; + }; + + SocialCalc.Formula.FunctionList["HLOOKUP"] = [ + SocialCalc.Formula.LookupFunctions, + -3, + "hlookup", + "", + "lookup", + ]; + SocialCalc.Formula.FunctionList["MATCH"] = [ + SocialCalc.Formula.LookupFunctions, + -2, + "match", + "", + "lookup", + ]; + SocialCalc.Formula.FunctionList["VLOOKUP"] = [ + SocialCalc.Formula.LookupFunctions, + -3, + "vlookup", + "", + "lookup", + ]; + + /* + # + # INDEX(range, rownum, colnum) + # + */ + + SocialCalc.Formula.IndexFunction = function ( + fname, + operand, + foperand, + sheet + ) { + var range, sheetname, indexinfo, rowindex, colindex, result, resulttype; + + var scf = SocialCalc.Formula; + + var PushOperand = function (t, v) { + operand.push({ type: t, value: v }); + }; + + range = scf.TopOfStackValueAndType(sheet, foperand); // get range + if (range.type != "range") { + scf.FunctionArgsError(fname, operand); + return 0; + } + indexinfo = scf.DecodeRangeParts(sheet, range.value, range.type); + if (indexinfo.sheetname) { + sheetname = "!" + indexinfo.sheetname; + } else { + sheetname = ""; + } + + rowindex = { value: 0 }; + colindex = { value: 0 }; + + if (foperand.length) { + // look for row number + rowindex = scf.OperandAsNumber(sheet, foperand); + if (rowindex.type.charAt(0) != "n" || rowindex.value < 0) { + PushOperand("e#VALUE!", 0); + return; + } + if (foperand.length) { + // look for col number + colindex = scf.OperandAsNumber(sheet, foperand); + if (colindex.type.charAt(0) != "n" || colindex.value < 0) { + PushOperand("e#VALUE!", 0); + return; + } + if (foperand.length) { + scf.FunctionArgsError(fname, operand); + return 0; + } + } else { + // col number missing + if (indexinfo.nrows == 1) { + // if only one row, then rowindex is really colindex + colindex.value = rowindex.value; + rowindex.value = 0; + } + } + } + + if (rowindex.value > indexinfo.nrows || colindex.value > indexinfo.ncols) { + PushOperand("e#REF!", 0); + return; + } + + if (rowindex.value == 0) { + if (colindex.value == 0) { + if (indexinfo.nrows == 1 && indexinfo.ncols == 1) { + result = + SocialCalc.crToCoord(indexinfo.col1num, indexinfo.row1num) + + sheetname; + resulttype = "coord"; + } else { + result = + SocialCalc.crToCoord(indexinfo.col1num, indexinfo.row1num) + + sheetname + + "|" + + SocialCalc.crToCoord( + indexinfo.col1num + indexinfo.ncols - 1, + indexinfo.row1num + indexinfo.nrows - 1 + ) + + "|"; + resulttype = "range"; + } + } else { + if (indexinfo.nrows == 1) { + result = + SocialCalc.crToCoord( + indexinfo.col1num + colindex.value - 1, + indexinfo.row1num + ) + sheetname; + resulttype = "coord"; + } else { + result = + SocialCalc.crToCoord( + indexinfo.col1num + colindex.value - 1, + indexinfo.row1num + ) + + sheetname + + "|" + + SocialCalc.crToCoord( + indexinfo.col1num + colindex.value - 1, + indexinfo.row1num + indexinfo.nrows - 1 + ) + + "|"; + resulttype = "range"; + } + } + } else { + if (colindex.value == 0) { + if (indexinfo.ncols == 1) { + result = + SocialCalc.crToCoord( + indexinfo.col1num, + indexinfo.row1num + rowindex.value - 1 + ) + sheetname; + resulttype = "coord"; + } else { + result = + SocialCalc.crToCoord( + indexinfo.col1num, + indexinfo.row1num + rowindex.value - 1 + ) + + sheetname + + "|" + + SocialCalc.crToCoord( + indexinfo.col1num + indexinfo.ncols - 1, + indexinfo.row1num + rowindex.value - 1 + ) + + "|"; + resulttype = "range"; + } + } else { + result = + SocialCalc.crToCoord( + indexinfo.col1num + colindex.value - 1, + indexinfo.row1num + rowindex.value - 1 + ) + sheetname; + resulttype = "coord"; + } + } + + PushOperand(resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["INDEX"] = [ + SocialCalc.Formula.IndexFunction, + -1, + "index", + "", + "lookup", + ]; + + /* + # + # COUNTIF(c1:c2,"criteria") + # SUMIF(c1:c2,"criteria",[range2]) + # + */ + + SocialCalc.Formula.CountifSumifFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var range, + criteria, + sumrange, + f2operand, + result, + resulttype, + value1, + value2; + var sum = 0; + var resulttypesum = ""; + var count = 0; + + var scf = SocialCalc.Formula; + var operand_value_and_type = scf.OperandValueAndType; + var lookup_result_type = scf.LookupResultType; + var typelookupplus = scf.TypeLookupTable.plus; + + var PushOperand = function (t, v) { + operand.push({ type: t, value: v }); + }; + + range = scf.TopOfStackValueAndType(sheet, foperand); // get range or coord + criteria = scf.OperandAsText(sheet, foperand); // get criteria + if (fname == "SUMIF") { + if (foperand.length == 1) { + // three arg form of SUMIF + sumrange = scf.TopOfStackValueAndType(sheet, foperand); + } else if (foperand.length == 0) { + // two arg form + sumrange = { value: range.value, type: range.type }; + } else { + scf.FunctionArgsError(fname, operand); + return 0; + } + } else { + sumrange = { value: range.value, type: range.type }; + } + + if (criteria.type.charAt(0) == "n") { + criteria.value = criteria.value + ""; // make text + } else if (criteria.type.charAt(0) == "e") { + // error + criteria.value = null; + } else if (criteria.type.charAt(0) == "b") { + // blank here is undefined + criteria.value = null; + } + + if (range.type != "coord" && range.type != "range") { + scf.FunctionArgsError(fname, operand); + return 0; + } + + if ( + fname == "SUMIF" && + sumrange.type != "coord" && + sumrange.type != "range" + ) { + scf.FunctionArgsError(fname, operand); + return 0; + } + + foperand.push(range); + f2operand = []; // to allow for 3 arg form + f2operand.push(sumrange); + + while (foperand.length) { + value1 = operand_value_and_type(sheet, foperand); + value2 = operand_value_and_type(sheet, f2operand); + + if (!scf.TestCriteria(value1.value, value1.type, criteria.value)) { + continue; + } + + count += 1; + + if (value2.type.charAt(0) == "n") { + sum += value2.value - 0; + resulttypesum = lookup_result_type( + value2.type, + resulttypesum || value2.type, + typelookupplus + ); + } else if ( + value2.type.charAt(0) == "e" && + resulttypesum.charAt(0) != "e" + ) { + resulttypesum = value2.type; + } + } + + resulttypesum = resulttypesum || "n"; + + if (fname == "SUMIF") { + PushOperand(resulttypesum, sum); + } else if (fname == "COUNTIF") { + PushOperand("n", count); + } + + return; + }; + + SocialCalc.Formula.FunctionList["COUNTIF"] = [ + SocialCalc.Formula.CountifSumifFunctions, + 2, + "rangec", + "", + "stat", + ]; + SocialCalc.Formula.FunctionList["SUMIF"] = [ + SocialCalc.Formula.CountifSumifFunctions, + -2, + "sumif", + "", + "stat", + ]; + + /* + # + # IF(cond,truevalue,falsevalue) + # + */ + + SocialCalc.Formula.IfFunction = function (fname, operand, foperand, sheet) { + var cond, t; + + cond = SocialCalc.Formula.OperandValueAndType(sheet, foperand); + t = cond.type.charAt(0); + if (t != "n" && t != "b") { + operand.push({ type: "e#VALUE!", value: 0 }); + return; + } + + if (!cond.value) foperand.pop(); + operand.push(foperand.pop()); + if (cond.value) foperand.pop(); + + return null; + }; + + // Add to function list + SocialCalc.Formula.FunctionList["IF"] = [ + SocialCalc.Formula.IfFunction, + 3, + "iffunc", + "", + "test", + ]; + + /* + # + # DATE(year,month,day) + # + */ + + SocialCalc.Formula.DateFunction = function (fname, operand, foperand, sheet) { + var scf = SocialCalc.Formula; + var result = 0; + var year = scf.OperandAsNumber(sheet, foperand); + var month = scf.OperandAsNumber(sheet, foperand); + var day = scf.OperandAsNumber(sheet, foperand); + var resulttype = scf.LookupResultType( + year.type, + month.type, + scf.TypeLookupTable.twoargnumeric + ); + resulttype = scf.LookupResultType( + resulttype, + day.type, + scf.TypeLookupTable.twoargnumeric + ); + if (resulttype.charAt(0) == "n") { + result = + SocialCalc.FormatNumber.convert_date_gregorian_to_julian( + Math.floor(year.value), + Math.floor(month.value), + Math.floor(day.value) + ) - SocialCalc.FormatNumber.datevalues.julian_offset; + resulttype = "nd"; + } + scf.PushOperand(operand, resulttype, result); + return; + }; + + SocialCalc.Formula.FunctionList["DATE"] = [ + SocialCalc.Formula.DateFunction, + 3, + "date", + "", + "datetime", + ]; + + /* + # + # TIME(hour,minute,second) + # + */ + + SocialCalc.Formula.TimeFunction = function (fname, operand, foperand, sheet) { + var scf = SocialCalc.Formula; + var result = 0; + var hours = scf.OperandAsNumber(sheet, foperand); + var minutes = scf.OperandAsNumber(sheet, foperand); + var seconds = scf.OperandAsNumber(sheet, foperand); + var resulttype = scf.LookupResultType( + hours.type, + minutes.type, + scf.TypeLookupTable.twoargnumeric + ); + resulttype = scf.LookupResultType( + resulttype, + seconds.type, + scf.TypeLookupTable.twoargnumeric + ); + if (resulttype.charAt(0) == "n") { + result = + (hours.value * 60 * 60 + minutes.value * 60 + seconds.value) / + (24 * 60 * 60); + resulttype = "nt"; + } + scf.PushOperand(operand, resulttype, result); + return; + }; + + SocialCalc.Formula.FunctionList["TIME"] = [ + SocialCalc.Formula.TimeFunction, + 3, + "hms", + "", + "datetime", + ]; + + /* + # + # DAY(date) + # MONTH(date) + # YEAR(date) + # WEEKDAY(date, [type]) + # + */ + + SocialCalc.Formula.DMYFunctions = function (fname, operand, foperand, sheet) { + var ymd, dtype, doffset; + var scf = SocialCalc.Formula; + var result = 0; + + var datevalue = scf.OperandAsNumber(sheet, foperand); + var resulttype = scf.LookupResultType( + datevalue.type, + datevalue.type, + scf.TypeLookupTable.oneargnumeric + ); + + if (resulttype.charAt(0) == "n") { + ymd = SocialCalc.FormatNumber.convert_date_julian_to_gregorian( + Math.floor( + datevalue.value + SocialCalc.FormatNumber.datevalues.julian_offset + ) + ); + switch (fname) { + case "DAY": + result = ymd.day; + break; + + case "MONTH": + result = ymd.month; + break; + + case "YEAR": + result = ymd.year; + break; + + case "WEEKDAY": + dtype = { value: 1 }; + if (foperand.length) { + // get type if present + dtype = scf.OperandAsNumber(sheet, foperand); + if ( + dtype.type.charAt(0) != "n" || + dtype.value < 1 || + dtype.value > 3 + ) { + scf.PushOperand(operand, "e#VALUE!", 0); + return; + } + if (foperand.length) { + // extra args + scf.FunctionArgsError(fname, operand); + return; + } + } + doffset = 6; + if (dtype.value > 1) { + doffset -= 1; + } + result = + (Math.floor(datevalue.value + doffset) % 7) + + (dtype.value < 3 ? 1 : 0); + break; + } + } + + scf.PushOperand(operand, resulttype, result); + return; + }; + + SocialCalc.Formula.FunctionList["DAY"] = [ + SocialCalc.Formula.DMYFunctions, + 1, + "v", + "", + "datetime", + ]; + SocialCalc.Formula.FunctionList["MONTH"] = [ + SocialCalc.Formula.DMYFunctions, + 1, + "v", + "", + "datetime", + ]; + SocialCalc.Formula.FunctionList["YEAR"] = [ + SocialCalc.Formula.DMYFunctions, + 1, + "v", + "", + "datetime", + ]; + SocialCalc.Formula.FunctionList["WEEKDAY"] = [ + SocialCalc.Formula.DMYFunctions, + -1, + "weekday", + "", + "datetime", + ]; + + /* + # + # HOUR(datetime) + # MINUTE(datetime) + # SECOND(datetime) + # + */ + + SocialCalc.Formula.HMSFunctions = function (fname, operand, foperand, sheet) { + var hours, minutes, seconds, fraction; + var scf = SocialCalc.Formula; + var result = 0; + + var datetime = scf.OperandAsNumber(sheet, foperand); + var resulttype = scf.LookupResultType( + datetime.type, + datetime.type, + scf.TypeLookupTable.oneargnumeric + ); + + if (resulttype.charAt(0) == "n") { + if (datetime.value < 0) { + scf.PushOperand(operand, "e#NUM!", 0); // must be non-negative + return; + } + fraction = datetime.value - Math.floor(datetime.value); // fraction of a day + fraction *= 24; + hours = Math.floor(fraction); + fraction -= Math.floor(fraction); + fraction *= 60; + minutes = Math.floor(fraction); + fraction -= Math.floor(fraction); + fraction *= 60; + seconds = Math.floor(fraction + (datetime.value >= 0 ? 0.5 : -0.5)); + if (fname == "HOUR") { + result = hours; + } else if (fname == "MINUTE") { + result = minutes; + } else if (fname == "SECOND") { + result = seconds; + } + } + + scf.PushOperand(operand, resulttype, result); + return; + }; + + SocialCalc.Formula.FunctionList["HOUR"] = [ + SocialCalc.Formula.HMSFunctions, + 1, + "v", + "", + "datetime", + ]; + SocialCalc.Formula.FunctionList["MINUTE"] = [ + SocialCalc.Formula.HMSFunctions, + 1, + "v", + "", + "datetime", + ]; + SocialCalc.Formula.FunctionList["SECOND"] = [ + SocialCalc.Formula.HMSFunctions, + 1, + "v", + "", + "datetime", + ]; + + /* + # + # EXACT(v1,v2) + # + */ + + SocialCalc.Formula.ExactFunction = function ( + fname, + operand, + foperand, + sheet + ) { + var scf = SocialCalc.Formula; + var result = 0; + var resulttype = "nl"; + + var value1 = scf.OperandValueAndType(sheet, foperand); + var v1type = value1.type.charAt(0); + var value2 = scf.OperandValueAndType(sheet, foperand); + var v2type = value2.type.charAt(0); + + if (v1type == "t") { + if (v2type == "t") { + result = value1.value == value2.value ? 1 : 0; + } else if (v2type == "b") { + result = value1.value.length ? 0 : 1; + } else if (v2type == "n") { + result = value1.value == value2.value + "" ? 1 : 0; + } else if (v2type == "e") { + result = value2.value; + resulttype = value2.type; + } else { + result = 0; + } + } else if (v1type == "n") { + if (v2type == "n") { + result = value1.value - 0 == value2.value - 0 ? 1 : 0; + } else if (v2type == "b") { + result = 0; + } else if (v2type == "t") { + result = value1.value + "" == value2.value ? 1 : 0; + } else if (v2type == "e") { + result = value2.value; + resulttype = value2.type; + } else { + result = 0; + } + } else if (v1type == "b") { + if (v2type == "t") { + result = value2.value.length ? 0 : 1; + } else if (v2type == "b") { + result = 1; + } else if (v2type == "n") { + result = 0; + } else if (v2type == "e") { + result = value2.value; + resulttype = value2.type; + } else { + result = 0; + } + } else if (v1type == "e") { + result = value1.value; + resulttype = value1.type; + } + + scf.PushOperand(operand, resulttype, result); + return; + }; + + SocialCalc.Formula.FunctionList["EXACT"] = [ + SocialCalc.Formula.ExactFunction, + 2, + "", + "", + "text", + ]; + + /* + # + # FIND(key,string,[start]) + # LEFT(string,[length]) + # LEN(string) + # LOWER(string) + # MID(string,start,length) + # PROPER(string) + # REPLACE(string,start,length,new) + # REPT(string,count) + # RIGHT(string,[length]) + # SUBSTITUTE(string,old,new,[which]) + # TRIM(string) + # UPPER(string) + # + */ + + // SocialCalc.Formula.ArgList has an array for each function, one entry for each possible arg (up to max). + // Min args are specified in SocialCalc.Formula.FunctionList. + // If array element is 1 then it's a text argument, if it's 0 then it's numeric, if -1 then just get whatever's there + // Text values are manipulated as UTF-8, converting from and back to byte strings + + SocialCalc.Formula.ArgList = { + FIND: [1, 1, 0], + LEFT: [1, 0], + LEN: [1], + LOWER: [1], + MID: [1, 0, 0], + PROPER: [1], + REPLACE: [1, 0, 0, 1], + REPT: [1, 0], + RIGHT: [1, 0], + SUBSTITUTE: [1, 1, 1, 0], + TRIM: [1], + UPPER: [1], + }; + + SocialCalc.Formula.StringFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var i, value, offset, len, start, count; + var scf = SocialCalc.Formula; + var result = 0; + var resulttype = "e#VALUE!"; + + var numargs = foperand.length; + var argdef = scf.ArgList[fname]; + var operand_value = []; + var operand_type = []; + + for (i = 1; i <= numargs; i++) { + // go through each arg, get value and type, and check for errors + if (i > argdef.length) { + // too many args + scf.FunctionArgsError(fname, operand); + return; + } + if (argdef[i - 1] == 0) { + value = scf.OperandAsNumber(sheet, foperand); + } else if (argdef[i - 1] == 1) { + value = scf.OperandAsText(sheet, foperand); + } else if (argdef[i - 1] == -1) { + value = scf.OperandValueAndType(sheet, foperand); + } + operand_value[i] = value.value; + operand_type[i] = value.type; + if (value.type.charAt(0) == "e") { + scf.PushOperand(operand, value.type, result); + return; + } + } + + switch (fname) { + case "FIND": + offset = operand_type[3] ? operand_value[3] - 1 : 0; + if (offset < 0) { + result = "Start is before string"; // !! not displayed, no need to translate + } else { + result = operand_value[2].indexOf(operand_value[1], offset); // (null string matches first char) + if (result >= 0) { + result += 1; + resulttype = "n"; + } else { + result = "Not found"; // !! not displayed, error is e#VALUE! + } + } + break; + + case "LEFT": + len = operand_type[2] ? operand_value[2] - 0 : 1; + if (len < 0) { + result = "Negative length"; + } else { + result = operand_value[1].substring(0, len); + resulttype = "t"; + } + break; + + case "LEN": + result = operand_value[1].length; + resulttype = "n"; + break; + + case "LOWER": + result = operand_value[1].toLowerCase(); + resulttype = "t"; + break; + + case "MID": + start = operand_value[2] - 0; + len = operand_value[3] - 0; + if (len < 1 || start < 1) { + result = "Bad arguments"; + } else { + result = operand_value[1].substring(start - 1, start + len - 1); + resulttype = "t"; + } + break; + + case "PROPER": + result = operand_value[1].replace(/\b\w+\b/g, function (word) { + return word.substring(0, 1).toUpperCase() + word.substring(1); + }); // uppercase first character of words (see JavaScript, Flanagan, 5th edition, page 704) + resulttype = "t"; + break; + + case "REPLACE": + start = operand_value[2] - 0; + len = operand_value[3] - 0; + if (len < 0 || start < 1) { + result = "Bad arguments"; + } else { + result = + operand_value[1].substring(0, start - 1) + + operand_value[4] + + operand_value[1].substring(start - 1 + len); + resulttype = "t"; + } + break; + + case "REPT": + count = operand_value[2] - 0; + if (count < 0) { + result = "Negative count"; + } else { + result = ""; + for (; count > 0; count--) { + result += operand_value[1]; + } + resulttype = "t"; + } + break; + + case "RIGHT": + len = operand_type[2] ? operand_value[2] - 0 : 1; + if (len < 0) { + result = "Negative length"; + } else { + result = operand_value[1].slice(-len); + resulttype = "t"; + } + break; + + case "SUBSTITUTE": + fulltext = operand_value[1]; + oldtext = operand_value[2]; + newtext = operand_value[3]; + if (operand_value[4] != null) { + which = operand_value[4] - 0; + if (which <= 0) { + result = "Non-positive instance number"; + break; + } + } else { + which = 0; + } + count = 0; + oldpos = 0; + result = ""; + while (true) { + pos = fulltext.indexOf(oldtext, oldpos); + if (pos >= 0) { + count++; //!!!!!! old test just in case: if (count>1000) {alert(pos); break;} + result += fulltext.substring(oldpos, pos); + if (which == 0) { + result += newtext; // substitute + } else if (which == count) { + result += newtext + fulltext.substring(pos + oldtext.length); + break; + } else { + result += oldtext; // leave as was + } + oldpos = pos + oldtext.length; + } else { + // no more + result += fulltext.substring(oldpos); + break; + } + } + resulttype = "t"; + break; + + case "TRIM": + result = operand_value[1]; + result = result.replace(/^ */, ""); + result = result.replace(/ *$/, ""); + result = result.replace(/ +/g, " "); + resulttype = "t"; + break; + + case "UPPER": + result = operand_value[1].toUpperCase(); + resulttype = "t"; + break; + } + + scf.PushOperand(operand, resulttype, result); + return; + }; + + SocialCalc.Formula.FunctionList["FIND"] = [ + SocialCalc.Formula.StringFunctions, + -2, + "find", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["LEFT"] = [ + SocialCalc.Formula.StringFunctions, + -2, + "tc", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["LEN"] = [ + SocialCalc.Formula.StringFunctions, + 1, + "txt", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["LOWER"] = [ + SocialCalc.Formula.StringFunctions, + 1, + "txt", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["MID"] = [ + SocialCalc.Formula.StringFunctions, + 3, + "mid", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["PROPER"] = [ + SocialCalc.Formula.StringFunctions, + 1, + "v", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["REPLACE"] = [ + SocialCalc.Formula.StringFunctions, + 4, + "replace", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["REPT"] = [ + SocialCalc.Formula.StringFunctions, + 2, + "tc", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["RIGHT"] = [ + SocialCalc.Formula.StringFunctions, + -1, + "tc", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["SUBSTITUTE"] = [ + SocialCalc.Formula.StringFunctions, + -3, + "subs", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["TRIM"] = [ + SocialCalc.Formula.StringFunctions, + 1, + "v", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["UPPER"] = [ + SocialCalc.Formula.StringFunctions, + 1, + "v", + "", + "text", + ]; + + /* + # + # is_functions: + # + # ISBLANK(value) + # ISERR(value) + # ISERROR(value) + # ISLOGICAL(value) + # ISNA(value) + # ISNONTEXT(value) + # ISNUMBER(value) + # ISTEXT(value) + # + */ + + SocialCalc.Formula.IsFunctions = function (fname, operand, foperand, sheet) { + var scf = SocialCalc.Formula; + var result = 0; + var resulttype = "nl"; + + var value = scf.OperandValueAndType(sheet, foperand); + var t = value.type.charAt(0); + + switch (fname) { + case "ISBLANK": + result = value.type == "b" ? 1 : 0; + break; + + case "ISERR": + result = t == "e" ? (value.type == "e#N/A" ? 0 : 1) : 0; + break; + + case "ISERROR": + result = t == "e" ? 1 : 0; + break; + + case "ISLOGICAL": + result = value.type == "nl" ? 1 : 0; + break; + + case "ISNA": + result = value.type == "e#N/A" ? 1 : 0; + break; + + case "ISNONTEXT": + result = t == "t" ? 0 : 1; + break; + + case "ISNUMBER": + result = t == "n" ? 1 : 0; + break; + + case "ISTEXT": + result = t == "t" ? 1 : 0; + break; + } + + scf.PushOperand(operand, resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["ISBLANK"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["ISERR"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["ISERROR"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["ISLOGICAL"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["ISNA"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["ISNONTEXT"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["ISNUMBER"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["ISTEXT"] = [ + SocialCalc.Formula.IsFunctions, + 1, + "v", + "", + "test", + ]; + + /* + # + # ntv_functions: + # + # N(value) + # T(value) + # VALUE(value) + # + */ + + SocialCalc.Formula.NTVFunctions = function (fname, operand, foperand, sheet) { + var scf = SocialCalc.Formula; + var result = 0; + var resulttype = "e#VALUE!"; + + var value = scf.OperandValueAndType(sheet, foperand); + var t = value.type.charAt(0); + + switch (fname) { + case "N": + result = t == "n" ? value.value - 0 : 0; + resulttype = "n"; + break; + + case "T": + result = t == "t" ? value.value + "" : ""; + resulttype = "t"; + break; + + case "VALUE": + if (t == "n" || t == "b") { + result = value.value || 0; + resulttype = "n"; + } else if (t == "t") { + value = SocialCalc.DetermineValueType(value.value); + if (value.type.charAt(0) != "n") { + result = 0; + resulttype = "e#VALUE!"; + } else { + result = value.value - 0; + resulttype = "n"; + } + } + break; + } + + if (t == "e") { + // error trumps + resulttype = value.type; + } + + scf.PushOperand(operand, resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["N"] = [ + SocialCalc.Formula.NTVFunctions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["T"] = [ + SocialCalc.Formula.NTVFunctions, + 1, + "v", + "", + "text", + ]; + SocialCalc.Formula.FunctionList["VALUE"] = [ + SocialCalc.Formula.NTVFunctions, + 1, + "v", + "", + "text", + ]; + + /* + # + # ABS(value) + # ACOS(value) + # ASIN(value) + # ATAN(value) + # COS(value) + # DEGREES(value) + # EVEN(value) + # EXP(value) + # FACT(value) + # INT(value) + # LN(value) + # LOG10(value) + # ODD(value) + # RADIANS(value) + # SIN(value) + # SQRT(value) + # TAN(value) + # + */ + + SocialCalc.Formula.Math1Functions = function ( + fname, + operand, + foperand, + sheet + ) { + var v1, value, f; + var result = {}; + + var scf = SocialCalc.Formula; + + v1 = scf.OperandAsNumber(sheet, foperand); + value = v1.value; + result.type = scf.LookupResultType( + v1.type, + v1.type, + scf.TypeLookupTable.oneargnumeric + ); + + if (result.type == "n") { + switch (fname) { + case "ABS": + value = Math.abs(value); + break; + + case "ACOS": + if (value >= -1 && value <= 1) { + value = Math.acos(value); + } else { + result.type = "e#NUM!"; + } + break; + + case "ASIN": + if (value >= -1 && value <= 1) { + value = Math.asin(value); + } else { + result.type = "e#NUM!"; + } + break; + + case "ATAN": + value = Math.atan(value); + break; + + case "COS": + value = Math.cos(value); + break; + + case "DEGREES": + value = (value * 180) / Math.PI; + break; + + case "EVEN": + value = value < 0 ? -value : value; + if (value != Math.floor(value)) { + value = Math.floor(value + 1) + (Math.floor(value + 1) % 2); + } else { + // integer + value = value + (value % 2); + } + if (v1.value < 0) value = -value; + break; + + case "EXP": + value = Math.exp(value); + break; + + case "FACT": + f = 1; + value = Math.floor(value); + for (; value > 0; value--) { + f *= value; + } + value = f; + break; + + case "INT": + value = Math.floor(value); // spreadsheet INT is floor(), not int() + break; + + case "LN": + if (value <= 0) { + result.type = "e#NUM!"; + result.error = SocialCalc.Constants.s_sheetfunclnarg; + } + value = Math.log(value); + break; + + case "LOG10": + if (value <= 0) { + result.type = "e#NUM!"; + result.error = SocialCalc.Constants.s_sheetfunclog10arg; + } + value = Math.log(value) / Math.log(10); + break; + + case "ODD": + value = value < 0 ? -value : value; + if (value != Math.floor(value)) { + value = Math.floor(value + 1) + (1 - (Math.floor(value + 1) % 2)); + } else { + // integer + value = value + (1 - (value % 2)); + } + if (v1.value < 0) value = -value; + break; + + case "RADIANS": + value = (value * Math.PI) / 180; + break; + + case "SIN": + value = Math.sin(value); + break; + + case "SQRT": + if (value >= 0) { + value = Math.sqrt(value); + } else { + result.type = "e#NUM!"; + } + break; + + case "TAN": + if (Math.cos(value) != 0) { + value = Math.tan(value); + } else { + result.type = "e#NUM!"; + } + break; + } + } + + result.value = value; + operand.push(result); + + return null; + }; + + // Add to function list + SocialCalc.Formula.FunctionList["ABS"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["ACOS"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["ASIN"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["ATAN"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["COS"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["DEGREES"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["EVEN"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["EXP"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["FACT"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["INT"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["LN"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["LOG10"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["ODD"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["RADIANS"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["SIN"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["SQRT"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["TAN"] = [ + SocialCalc.Formula.Math1Functions, + 1, + "v", + "", + "math", + ]; + + /* + # + # ATAN2(x, y) + # MOD(a, b) + # POWER(a, b) + # TRUNC(value, precision) + # + */ + + SocialCalc.Formula.Math2Functions = function ( + fname, + operand, + foperand, + sheet + ) { + var xval, yval, value, quotient, decimalscale, i; + var result = {}; + + var scf = SocialCalc.Formula; + + xval = scf.OperandAsNumber(sheet, foperand); + yval = scf.OperandAsNumber(sheet, foperand); + value = 0; + result.type = scf.LookupResultType( + xval.type, + yval.type, + scf.TypeLookupTable.twoargnumeric + ); + + if (result.type == "n") { + switch (fname) { + case "ATAN2": + if (xval.value == 0 && yval.value == 0) { + result.type = "e#DIV/0!"; + } else { + result.value = Math.atan2(yval.value, xval.value); + } + break; + + case "POWER": + result.value = Math.pow(xval.value, yval.value); + if (isNaN(result.value)) { + result.value = 0; + result.type = "e#NUM!"; + } + break; + + case "MOD": // en.wikipedia.org/wiki/Modulo_operation, etc. + if (yval.value == 0) { + result.type = "e#DIV/0!"; + } else { + quotient = xval.value / yval.value; + quotient = Math.floor(quotient); + result.value = xval.value - quotient * yval.value; + } + break; + + case "TRUNC": + decimalscale = 1; // cut down to required number of decimal digits + if (yval.value >= 0) { + yval.value = Math.floor(yval.value); + for (i = 0; i < yval.value; i++) { + decimalscale *= 10; + } + result.value = + Math.floor(Math.abs(xval.value) * decimalscale) / decimalscale; + } else if (yval.value < 0) { + yval.value = Math.floor(-yval.value); + for (i = 0; i < yval.value; i++) { + decimalscale *= 10; + } + result.value = + Math.floor(Math.abs(xval.value) / decimalscale) * decimalscale; + } + if (xval.value < 0) { + result.value = -result.value; + } + } + } + + operand.push(result); + + return null; + }; + + // Add to function list + SocialCalc.Formula.FunctionList["ATAN2"] = [ + SocialCalc.Formula.Math2Functions, + 2, + "xy", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["MOD"] = [ + SocialCalc.Formula.Math2Functions, + 2, + "", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["POWER"] = [ + SocialCalc.Formula.Math2Functions, + 2, + "", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["TRUNC"] = [ + SocialCalc.Formula.Math2Functions, + 2, + "valpre", + "", + "math", + ]; + + /* + # + # LOG(value,[base]) + # + */ + + SocialCalc.Formula.LogFunction = function (fname, operand, foperand, sheet) { + var value, value2; + var result = {}; + + var scf = SocialCalc.Formula; + + result.value = 0; + + value = scf.OperandAsNumber(sheet, foperand); + result.type = scf.LookupResultType( + value.type, + value.type, + scf.TypeLookupTable.oneargnumeric + ); + if (foperand.length == 1) { + value2 = scf.OperandAsNumber(sheet, foperand); + if (value2.type.charAt(0) != "n" || value2.value <= 0) { + scf.FunctionSpecificError( + fname, + operand, + "e#NUM!", + SocialCalc.Constants.s_sheetfunclogsecondarg + ); + return 0; + } + } else if (foperand.length != 0) { + scf.FunctionArgsError(fname, operand); + return 0; + } else { + value2 = { value: Math.E, type: "n" }; + } + + if (result.type == "n") { + if (value.value <= 0) { + scf.FunctionSpecificError( + fname, + operand, + "e#NUM!", + SocialCalc.Constants.s_sheetfunclogfirstarg + ); + return 0; + } + result.value = Math.log(value.value) / Math.log(value2.value); + } + + operand.push(result); + + return; + }; + + SocialCalc.Formula.FunctionList["LOG"] = [ + SocialCalc.Formula.LogFunction, + -1, + "log", + "", + "math", + ]; + + /* + # + # ROUND(value,[precision]) + # + */ + + SocialCalc.Formula.RoundFunction = function ( + fname, + operand, + foperand, + sheet + ) { + var value2, decimalscale, scaledvalue, i; + + var scf = SocialCalc.Formula; + var result = 0; + var resulttype = "e#VALUE!"; + + var value = scf.OperandValueAndType(sheet, foperand); + var resulttype = scf.LookupResultType( + value.type, + value.type, + scf.TypeLookupTable.oneargnumeric + ); + + if (foperand.length == 1) { + value2 = scf.OperandValueAndType(sheet, foperand); + if (value2.type.charAt(0) != "n") { + scf.FunctionSpecificError( + fname, + operand, + "e#NUM!", + SocialCalc.Constants.s_sheetfuncroundsecondarg + ); + return 0; + } + } else if (foperand.length != 0) { + scf.FunctionArgsError(fname, operand); + return 0; + } else { + value2 = { value: 0, type: "n" }; // if no second arg, assume 0 for simple round + } + + if (resulttype == "n") { + value2.value = value2.value - 0; + if (value2.value == 0) { + result = Math.round(value.value); + } else if (value2.value > 0) { + decimalscale = 1; // cut down to required number of decimal digits + value2.value = Math.floor(value2.value); + for (i = 0; i < value2.value; i++) { + decimalscale *= 10; + } + scaledvalue = Math.round(value.value * decimalscale); + result = scaledvalue / decimalscale; + } else if (value2.value < 0) { + decimalscale = 1; // cut down to required number of decimal digits + value2.value = Math.floor(-value2.value); + for (i = 0; i < value2.value; i++) { + decimalscale *= 10; + } + scaledvalue = Math.round(value.value / decimalscale); + result = scaledvalue * decimalscale; + } + } + + scf.PushOperand(operand, resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["ROUND"] = [ + SocialCalc.Formula.RoundFunction, + -1, + "vp", + "", + "math", + ]; + + /* + # + # AND(v1,c1:c2,...) + # OR(v1,c1:c2,...) + # + */ + + SocialCalc.Formula.AndOrFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var value1, result; + + var scf = SocialCalc.Formula; + var resulttype = ""; + + if (fname == "AND") { + result = 1; + } else if (fname == "OR") { + result = 0; + } + + while (foperand.length) { + value1 = scf.OperandValueAndType(sheet, foperand); + if (value1.type.charAt(0) == "n") { + value1.value = value1.value - 0; + if (fname == "AND") { + result = value1.value != 0 ? result : 0; + } else if (fname == "OR") { + result = value1.value != 0 ? 1 : result; + } + resulttype = scf.LookupResultType( + value1.type, + resulttype || "nl", + scf.TypeLookupTable.propagateerror + ); + } else if (value1.type.charAt(0) == "e" && resulttype.charAt(0) != "e") { + resulttype = value1.type; + } + } + if (resulttype.length < 1) { + resulttype = "e#VALUE!"; + result = 0; + } + + scf.PushOperand(operand, resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["AND"] = [ + SocialCalc.Formula.AndOrFunctions, + -1, + "vn", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["OR"] = [ + SocialCalc.Formula.AndOrFunctions, + -1, + "vn", + "", + "test", + ]; + + /* + # + # NOT(value) + # + */ + + SocialCalc.Formula.NotFunction = function (fname, operand, foperand, sheet) { + var result = 0; + var scf = SocialCalc.Formula; + var value = scf.OperandValueAndType(sheet, foperand); + var resulttype = scf.LookupResultType( + value.type, + value.type, + scf.TypeLookupTable.propagateerror + ); + + if (value.type.charAt(0) == "n" || value.type == "b") { + result = value.value - 0 != 0 ? 0 : 1; // do the "not" operation + resulttype = "nl"; + } else if (value.type.charAt(0) == "t") { + resulttype = "e#VALUE!"; + } + + scf.PushOperand(operand, resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["NOT"] = [ + SocialCalc.Formula.NotFunction, + 1, + "v", + "", + "test", + ]; + + /* + # + # CHOOSE(index,value1,value2,...) + # + */ + + SocialCalc.Formula.ChooseFunction = function ( + fname, + operand, + foperand, + sheet + ) { + var resulttype, count, value1; + var result = 0; + var scf = SocialCalc.Formula; + + var cindex = scf.OperandAsNumber(sheet, foperand); + + if (cindex.type.charAt(0) != "n") { + cindex.value = 0; + } + cindex.value = Math.floor(cindex.value); + + count = 0; + while (foperand.length) { + value1 = scf.TopOfStackValueAndType(sheet, foperand); + count += 1; + if (cindex.value == count) { + result = value1.value; + resulttype = value1.type; + break; + } + } + if (resulttype) { + // found something + scf.PushOperand(operand, resulttype, result); + } else { + scf.PushOperand(operand, "e#VALUE!", 0); + } + + return; + }; + + SocialCalc.Formula.FunctionList["CHOOSE"] = [ + SocialCalc.Formula.ChooseFunction, + -2, + "choose", + "", + "lookup", + ]; + + /* + # + # COLUMNS(c1:c2) + # ROWS(c1:c2) + # + */ + + SocialCalc.Formula.ColumnsRowsFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var resulttype, rangeinfo; + var result = 0; + var scf = SocialCalc.Formula; + + var value1 = scf.TopOfStackValueAndType(sheet, foperand); + + if (value1.type == "coord") { + result = 1; + resulttype = "n"; + } else if (value1.type == "range") { + rangeinfo = scf.DecodeRangeParts(sheet, value1.value); + if (fname == "COLUMNS") { + result = rangeinfo.ncols; + } else if (fname == "ROWS") { + result = rangeinfo.nrows; + } + resulttype = "n"; + } else { + result = 0; + resulttype = "e#VALUE!"; + } + + scf.PushOperand(operand, resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["COLUMNS"] = [ + SocialCalc.Formula.ColumnsRowsFunctions, + 1, + "range", + "", + "lookup", + ]; + SocialCalc.Formula.FunctionList["ROWS"] = [ + SocialCalc.Formula.ColumnsRowsFunctions, + 1, + "range", + "", + "lookup", + ]; + + /* + # + # FALSE() + # NA() + # NOW() + # PI() + # TODAY() + # TRUE() + # + */ + + SocialCalc.Formula.ZeroArgFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var startval, tzoffset, start_1_1_1970, seconds_in_a_day, nowdays; + var result = { value: 0 }; + + switch (fname) { + case "FALSE": + result.type = "nl"; + result.value = 0; + break; + + case "NA": + result.type = "e#N/A"; + break; + + case "NOW": + startval = new Date(); + tzoffset = startval.getTimezoneOffset(); + startval = startval.getTime() / 1000; // convert to seconds + start_1_1_1970 = 25569; // Day number of 1/1/1970 starting with 1/1/1900 as 1 + seconds_in_a_day = 24 * 60 * 60; + nowdays = + start_1_1_1970 + startval / seconds_in_a_day - tzoffset / (24 * 60); + result.value = nowdays; + result.type = "ndt"; + SocialCalc.Formula.FreshnessInfo.volatile.NOW = true; // remember + break; + + case "PI": + result.type = "n"; + result.value = Math.PI; + break; + + case "TODAY": + startval = new Date(); + tzoffset = startval.getTimezoneOffset(); + startval = startval.getTime() / 1000; // convert to seconds + start_1_1_1970 = 25569; // Day number of 1/1/1970 starting with 1/1/1900 as 1 + seconds_in_a_day = 24 * 60 * 60; + nowdays = + start_1_1_1970 + startval / seconds_in_a_day - tzoffset / (24 * 60); + result.value = Math.floor(nowdays); + result.type = "nd"; + SocialCalc.Formula.FreshnessInfo.volatile.TODAY = true; // remember + break; + + case "TRUE": + result.type = "nl"; + result.value = 1; + break; + } + + operand.push(result); + + return null; + }; + + // Add to function list + SocialCalc.Formula.FunctionList["FALSE"] = [ + SocialCalc.Formula.ZeroArgFunctions, + 0, + "", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["NA"] = [ + SocialCalc.Formula.ZeroArgFunctions, + 0, + "", + "", + "test", + ]; + SocialCalc.Formula.FunctionList["NOW"] = [ + SocialCalc.Formula.ZeroArgFunctions, + 0, + "", + "", + "datetime", + ]; + SocialCalc.Formula.FunctionList["PI"] = [ + SocialCalc.Formula.ZeroArgFunctions, + 0, + "", + "", + "math", + ]; + SocialCalc.Formula.FunctionList["TODAY"] = [ + SocialCalc.Formula.ZeroArgFunctions, + 0, + "", + "", + "datetime", + ]; + SocialCalc.Formula.FunctionList["TRUE"] = [ + SocialCalc.Formula.ZeroArgFunctions, + 0, + "", + "", + "test", + ]; + + // + // * * * * * FINANCIAL FUNCTIONS * * * * * + // + + /* + # + # DDB(cost,salvage,lifetime,period,[method]) + # + # Depreciation, method defaults to 2 for double-declining balance + # See: http://en.wikipedia.org/wiki/Depreciation + # + */ + + SocialCalc.Formula.DDBFunction = function (fname, operand, foperand, sheet) { + var method, depreciation, accumulateddepreciation, i; + var scf = SocialCalc.Formula; + + var cost = scf.OperandAsNumber(sheet, foperand); + var salvage = scf.OperandAsNumber(sheet, foperand); + var lifetime = scf.OperandAsNumber(sheet, foperand); + var period = scf.OperandAsNumber(sheet, foperand); + + if (scf.CheckForErrorValue(operand, cost)) return; + if (scf.CheckForErrorValue(operand, salvage)) return; + if (scf.CheckForErrorValue(operand, lifetime)) return; + if (scf.CheckForErrorValue(operand, period)) return; + + if (lifetime.value < 1) { + scf.FunctionSpecificError( + fname, + operand, + "e#NUM!", + SocialCalc.Constants.s_sheetfuncddblife + ); + return 0; + } + + method = { value: 2, type: "n" }; + if (foperand.length > 0) { + method = scf.OperandAsNumber(sheet, foperand); + } + if (foperand.length != 0) { + scf.FunctionArgsError(fname, operand); + return 0; + } + if (scf.CheckForErrorValue(operand, method)) return; + + depreciation = 0; // calculated for each period + accumulateddepreciation = 0; // accumulated by adding each period's + + for (i = 1; i <= period.value - 0 && i <= lifetime.value; i++) { + // calculate for each period based on net from previous + depreciation = + (cost.value - accumulateddepreciation) * + (method.value / lifetime.value); + if (cost.value - accumulateddepreciation - depreciation < salvage.value) { + // don't go lower than salvage value + depreciation = cost.value - accumulateddepreciation - salvage.value; + } + accumulateddepreciation += depreciation; + } + + scf.PushOperand(operand, "n$", depreciation); + + return; + }; + + SocialCalc.Formula.FunctionList["DDB"] = [ + SocialCalc.Formula.DDBFunction, + -4, + "ddb", + "", + "financial", + ]; + + /* + # + # SLN(cost,salvage,lifetime) + # + # Depreciation for each period by straight-line method + # See: http://en.wikipedia.org/wiki/Depreciation + # + */ + + SocialCalc.Formula.SLNFunction = function (fname, operand, foperand, sheet) { + var depreciation; + var scf = SocialCalc.Formula; + + var cost = scf.OperandAsNumber(sheet, foperand); + var salvage = scf.OperandAsNumber(sheet, foperand); + var lifetime = scf.OperandAsNumber(sheet, foperand); + + if (scf.CheckForErrorValue(operand, cost)) return; + if (scf.CheckForErrorValue(operand, salvage)) return; + if (scf.CheckForErrorValue(operand, lifetime)) return; + + if (lifetime.value < 1) { + scf.FunctionSpecificError( + fname, + operand, + "e#NUM!", + SocialCalc.Constants.s_sheetfuncslnlife + ); + return 0; + } + + depreciation = (cost.value - salvage.value) / lifetime.value; + + scf.PushOperand(operand, "n$", depreciation); + + return; + }; + + SocialCalc.Formula.FunctionList["SLN"] = [ + SocialCalc.Formula.SLNFunction, + 3, + "csl", + "", + "financial", + ]; + + /* + # + # SYD(cost,salvage,lifetime,period) + # + # Depreciation by Sum of Year's Digits method + # + */ + + SocialCalc.Formula.SYDFunction = function (fname, operand, foperand, sheet) { + var depreciation, sumperiods; + var scf = SocialCalc.Formula; + + var cost = scf.OperandAsNumber(sheet, foperand); + var salvage = scf.OperandAsNumber(sheet, foperand); + var lifetime = scf.OperandAsNumber(sheet, foperand); + var period = scf.OperandAsNumber(sheet, foperand); + + if (scf.CheckForErrorValue(operand, cost)) return; + if (scf.CheckForErrorValue(operand, salvage)) return; + if (scf.CheckForErrorValue(operand, lifetime)) return; + if (scf.CheckForErrorValue(operand, period)) return; + + if (lifetime.value < 1 || period.value <= 0) { + scf.PushOperand(operand, "e#NUM!", 0); + return 0; + } + + sumperiods = ((lifetime.value + 1) * lifetime.value) / 2; // add up 1 through lifetime + depreciation = + ((cost.value - salvage.value) * (lifetime.value - period.value + 1)) / + sumperiods; // calc depreciation + + scf.PushOperand(operand, "n$", depreciation); + + return; + }; + + SocialCalc.Formula.FunctionList["SYD"] = [ + SocialCalc.Formula.SYDFunction, + 4, + "cslp", + "", + "financial", + ]; + + /* + # + # FV(rate, n, payment, [pv, [paytype]]) + # NPER(rate, payment, pv, [fv, [paytype]]) + # PMT(rate, n, pv, [fv, [paytype]]) + # PV(rate, n, payment, [fv, [paytype]]) + # RATE(n, payment, pv, [fv, [paytype, [guess]]]) + # + # Following the Open Document Format formula specification: + # + # PV = - Fv - (Payment * Nper) [if rate equals 0] + # Pv*(1+Rate)^Nper + Payment * (1 + Rate*PaymentType) * ( (1+Rate)^nper -1)/Rate + Fv = 0 + # + # For each function, the formulas are solved for the appropriate value (transformed using + # basic algebra). + # + */ + + SocialCalc.Formula.InterestFunctions = function ( + fname, + operand, + foperand, + sheet + ) { + var resulttype, result, dval, evalue, fval; + var pv, + fv, + rate, + n, + payment, + paytype, + guess, + part1, + part2, + part3, + part4, + part5; + var olddelta, maxloop, tries, deltaepsilon, rate, oldrate, m; + + var scf = SocialCalc.Formula; + + var aval = scf.OperandAsNumber(sheet, foperand); + var bval = scf.OperandAsNumber(sheet, foperand); + var cval = scf.OperandAsNumber(sheet, foperand); + + resulttype = scf.LookupResultType( + aval.type, + bval.type, + scf.TypeLookupTable.twoargnumeric + ); + resulttype = scf.LookupResultType( + resulttype, + cval.type, + scf.TypeLookupTable.twoargnumeric + ); + if (foperand.length) { + // optional arguments + dval = scf.OperandAsNumber(sheet, foperand); + resulttype = scf.LookupResultType( + resulttype, + dval.type, + scf.TypeLookupTable.twoargnumeric + ); + if (foperand.length) { + // optional arguments + evalue = scf.OperandAsNumber(sheet, foperand); + resulttype = scf.LookupResultType( + resulttype, + evalue.type, + scf.TypeLookupTable.twoargnumeric + ); + if (foperand.length) { + // optional arguments + if (fname != "RATE") { + // only rate has 6 possible args + scf.FunctionArgsError(fname, operand); + return 0; + } + fval = scf.OperandAsNumber(sheet, foperand); + resulttype = scf.LookupResultType( + resulttype, + fval.type, + scf.TypeLookupTable.twoargnumeric + ); + } + } + } + + if (resulttype == "n") { + switch (fname) { + case "FV": // FV(rate, n, payment, [pv, [paytype]]) + rate = aval.value; + n = bval.value; + payment = cval.value; + pv = dval != null ? dval.value : 0; // get value if present, or use default + paytype = evalue != null ? (evalue.value ? 1 : 0) : 0; + if (rate == 0) { + // simple calculation if no interest + fv = -pv - payment * n; + } else { + fv = -( + pv * Math.pow(1 + rate, n) + + (payment * (1 + rate * paytype) * (Math.pow(1 + rate, n) - 1)) / + rate + ); + } + result = fv; + resulttype = "n$"; + break; + + case "NPER": // NPER(rate, payment, pv, [fv, [paytype]]) + rate = aval.value; + payment = bval.value; + pv = cval.value; + fv = dval != null ? dval.value : 0; + paytype = evalue != null ? (evalue.value ? 1 : 0) : 0; + if (rate == 0) { + // simple calculation if no interest + if (payment == 0) { + scf.PushOperand(operand, "e#NUM!", 0); + return; + } + n = (pv + fv) / -payment; + } else { + part1 = (payment * (1 + rate * paytype)) / rate; + part2 = pv + part1; + if (part2 == 0 || rate <= -1) { + scf.PushOperand(operand, "e#NUM!", 0); + return; + } + part3 = (part1 - fv) / part2; + if (part3 <= 0) { + scf.PushOperand(operand, "e#NUM!", 0); + return; + } + part4 = Math.log(part3); + part5 = Math.log(1 + rate); // rate > -1 + n = part4 / part5; + } + result = n; + resulttype = "n"; + break; + + case "PMT": // PMT(rate, n, pv, [fv, [paytype]]) + rate = aval.value; + n = bval.value; + pv = cval.value; + fv = dval != null ? dval.value : 0; + paytype = evalue != null ? (evalue.value ? 1 : 0) : 0; + if (n == 0) { + scf.PushOperand(operand, "e#NUM!", 0); + return; + } else if (rate == 0) { + // simple calculation if no interest + payment = (fv - pv) / n; + } else { + payment = + (0 - fv - pv * Math.pow(1 + rate, n)) / + (((1 + rate * paytype) * (Math.pow(1 + rate, n) - 1)) / rate); + } + result = payment; + resulttype = "n$"; + break; + + case "PV": // PV(rate, n, payment, [fv, [paytype]]) + rate = aval.value; + n = bval.value; + payment = cval.value; + fv = dval != null ? dval.value : 0; + paytype = evalue != null ? (eval.value ? 1 : 0) : 0; + if (rate == -1) { + scf.PushOperand(operand, "e#DIV/0!", 0); + return; + } else if (rate == 0) { + // simple calculation if no interest + pv = -fv - payment * n; + } else { + pv = + (-fv - + (payment * (1 + rate * paytype) * (Math.pow(1 + rate, n) - 1)) / + rate) / + Math.pow(1 + rate, n); + } + result = pv; + resulttype = "n$"; + break; + + case "RATE": // RATE(n, payment, pv, [fv, [paytype, [guess]]]) + n = aval.value; + payment = bval.value; + pv = cval.value; + fv = dval != null ? dval.value : 0; + paytype = evalue != null ? (evalue.value ? 1 : 0) : 0; + guess = fval != null ? fval.value : 0.1; + + // rate is calculated by repeated approximations + // The deltas are used to calculate new guesses + + maxloop = 100; + tries = 0; + delta = 1; + epsilon = 0.0000001; // this is close enough + rate = guess || 0.00000001; // zero is not allowed + while ((delta >= 0 ? delta : -delta) > epsilon && rate != oldrate) { + delta = + fv + + pv * Math.pow(1 + rate, n) + + (payment * (1 + rate * paytype) * (Math.pow(1 + rate, n) - 1)) / + rate; + if (olddelta != null) { + m = (delta - olddelta) / (rate - oldrate) || 0.001; // get slope (not zero) + oldrate = rate; + rate = rate - delta / m; // look for zero crossing + olddelta = delta; + } else { + // first time - no old values + oldrate = rate; + rate = 1.1 * rate; + olddelta = delta; + } + tries++; + if (tries >= maxloop) { + // didn't converge yet + scf.PushOperand(operand, "e#NUM!", 0); + return; + } + } + result = rate; + resulttype = "n%"; + break; + } + } + + scf.PushOperand(operand, resulttype, result); + + return; + }; + + SocialCalc.Formula.FunctionList["FV"] = [ + SocialCalc.Formula.InterestFunctions, + -3, + "fv", + "", + "financial", + ]; + SocialCalc.Formula.FunctionList["NPER"] = [ + SocialCalc.Formula.InterestFunctions, + -3, + "nper", + "", + "financial", + ]; + SocialCalc.Formula.FunctionList["PMT"] = [ + SocialCalc.Formula.InterestFunctions, + -3, + "pmt", + "", + "financial", + ]; + SocialCalc.Formula.FunctionList["PV"] = [ + SocialCalc.Formula.InterestFunctions, + -3, + "pv", + "", + "financial", + ]; + SocialCalc.Formula.FunctionList["RATE"] = [ + SocialCalc.Formula.InterestFunctions, + -3, + "rate", + "", + "financial", + ]; + + /* + # + # NPV(rate,v1,v2,c1:c2,...) + # + */ + + SocialCalc.Formula.NPVFunction = function (fname, operand, foperand, sheet) { + var resulttypenpv, rate, sum, factor, value1; + + var scf = SocialCalc.Formula; + + var rate = scf.OperandAsNumber(sheet, foperand); + if (scf.CheckForErrorValue(operand, rate)) return; + + sum = 0; + resulttypenpv = "n"; + factor = 1; + + while (foperand.length) { + value1 = scf.OperandValueAndType(sheet, foperand); + if (value1.type.charAt(0) == "n") { + factor *= 1 + rate.value; + if (factor == 0) { + scf.PushOperand(operand, "e#DIV/0!", 0); + return; + } + sum += value1.value / factor; + resulttypenpv = scf.LookupResultType( + value1.type, + resulttypenpv || value1.type, + scf.TypeLookupTable.plus + ); + } else if ( + value1.type.charAt(0) == "e" && + resulttypenpv.charAt(0) != "e" + ) { + resulttypenpv = value1.type; + break; + } + } + + if (resulttypenpv.charAt(0) == "n") { + resulttypenpv = "n$"; + } + + scf.PushOperand(operand, resulttypenpv, sum); + + return; + }; + + SocialCalc.Formula.FunctionList["NPV"] = [ + SocialCalc.Formula.NPVFunction, + -2, + "npv", + "", + "financial", + ]; + + /* + # + # IRR(c1:c2,[guess]) + # + */ + + SocialCalc.Formula.IRRFunction = function (fname, operand, foperand, sheet) { + var value1, + guess, + oldsum, + maxloop, + tries, + epsilon, + rate, + oldrate, + m, + sum, + factor, + i; + var rangeoperand = []; + var cashflows = []; + + var scf = SocialCalc.Formula; + + rangeoperand.push(foperand.pop()); // first operand is a range + + while (rangeoperand.length) { + // get values from range so we can do iterative approximations + value1 = scf.OperandValueAndType(sheet, rangeoperand); + if (value1.type.charAt(0) == "n") { + cashflows.push(value1.value); + } else if (value1.type.charAt(0) == "e") { + scf.PushOperand(operand, "e#VALUE!", 0); + return; + } + } + + if (!cashflows.length) { + scf.PushOperand(operand, "e#NUM!", 0); + return; + } + + guess = { value: 0 }; + + if (foperand.length) { + // guess is provided + guess = scf.OperandAsNumber(sheet, foperand); + if (guess.type.charAt(0) != "n" && guess.type.charAt(0) != "b") { + scf.PushOperand(operand, "e#VALUE!", 0); + return; + } + if (foperand.length) { + // should be no more args + scf.FunctionArgsError(fname, operand); + return; + } + } + + guess.value = guess.value || 0.1; + + // rate is calculated by repeated approximations + // The deltas are used to calculate new guesses + + maxloop = 20; + tries = 0; + epsilon = 0.0000001; // this is close enough + rate = guess.value; + sum = 1; + + while ((sum >= 0 ? sum : -sum) > epsilon && rate != oldrate) { + sum = 0; + factor = 1; + for (i = 0; i < cashflows.length; i++) { + factor *= 1 + rate; + if (factor == 0) { + scf.PushOperand(operand, "e#DIV/0!", 0); + return; + } + sum += cashflows[i] / factor; + } + + if (oldsum != null) { + m = (sum - oldsum) / (rate - oldrate); // get slope + oldrate = rate; + rate = rate - sum / m; // look for zero crossing + oldsum = sum; + } else { + // first time - no old values + oldrate = rate; + rate = 1.1 * rate; + oldsum = sum; + } + tries++; + if (tries >= maxloop) { + // didn't converge yet + scf.PushOperand(operand, "e#NUM!", 0); + return; + } + } + + scf.PushOperand(operand, "n%", rate); + + return; + }; + + SocialCalc.Formula.FunctionList["IRR"] = [ + SocialCalc.Formula.IRRFunction, + -1, + "irr", + "", + "financial", + ]; + + // + // SHEET CACHE + // + + SocialCalc.Formula.SheetCache = { + // Sheet data: Attributes are each sheet in the cache with values of an object with: + // + // sheet: sheet-obj (or null, meaning not found) + // recalcstate: constants.asloaded = as loaded + // constants.recalcing = being recalced now + // constants.recalcdone = recalc done + // name: name of sheet (in case just have object and don't know name) + // + + sheets: {}, + + // Waiting for loading: + // If sheet is not in cache, this is set to the sheetname being loaded + // so it can be tested in the recalc loop to start load and then wait until restarted. + // Reset to null before restarting. + + waitingForLoading: null, + + // Constants to use for setting sheets[*].recalcstate: + + constants: { asloaded: 0, recalcing: 1, recalcdone: 2 }, + + loadsheet: null, // (deprecated - use SocialCalc.RecalcInfo.LoadSheet) + }; + + // + // othersheet = SocialCalc.Formula.FindInSheetCache(sheetname) + // + // Returns a SocialCalc.Sheet object corresponding to string sheetname + // or null if the sheet is not available or in error. + // + // Each sheet is loaded only once and then stored in a cache. + // Loading is handled elsewhere, e.g., in the recalc loop. + // + + SocialCalc.Formula.FindInSheetCache = function (sheetname) { + var str; + var sfsc = SocialCalc.Formula.SheetCache; + + var nsheetname = SocialCalc.Formula.NormalizeSheetName(sheetname); // normalize different versions + + if (sfsc.sheets[nsheetname]) { + // a sheet by that name is in the cache already + return sfsc.sheets[nsheetname].sheet; // return it + } + + if (sfsc.waitingForLoading) { + // waiting already - only queue up one + return null; // return not found + } + + sfsc.waitingForLoading = nsheetname; // let recalc loop know that we have a sheet to load + + return null; // return not found + }; + + // + // newsheet = SocialCalc.Formula.AddSheetToCache(sheetname, str) + // + // Adds a new sheet to the sheet cache. + // Returns the sheet object filled out with the str (a saved sheet). + // + + SocialCalc.Formula.AddSheetToCache = function (sheetname, str) { + var newsheet = null; + var sfsc = SocialCalc.Formula.SheetCache; + var sfscc = sfsc.constants; + var newsheetname = SocialCalc.Formula.NormalizeSheetName(sheetname); + + if (str) { + newsheet = new SocialCalc.Sheet(); + newsheet.ParseSheetSave(str); + } + + sfsc.sheets[newsheetname] = { + sheet: newsheet, + recalcstate: sfscc.asloaded, + name: newsheetname, + }; + + SocialCalc.Formula.FreshnessInfo.sheets[newsheetname] = true; + + return newsheet; + }; + + // + // nsheet = SocialCalc.Formula.NormalizeSheetName(sheetname) + // + + SocialCalc.Formula.NormalizeSheetName = function (sheetname) { + if (SocialCalc.Callbacks.NormalizeSheetName) { + return SocialCalc.Callbacks.NormalizeSheetName(sheetname); + } else { + return sheetname.toLowerCase(); + } + }; + + // + // REMOTE FUNCTION INFO + // + + SocialCalc.Formula.RemoteFunctionInfo = { + // Waiting for server: + // If waiting for an XHR response from the server, this is set to some non-blank status text + // so it can be tested in the recalc loop to start load and then wait until restarted. + // Reset to null before restarting. + + waitingForServer: null, + }; + + // + // FRESHNESS INFO + // + // This information is generated during recalc. + // It may be used to help determine when the recalc data in a spreadsheet + // may be out of date. + // For example, it may be used to display a message like: + // "Dependent on sheet 'FOO' which was updated more recently than this printout" + + SocialCalc.Formula.FreshnessInfo = { + // For each external sheet referenced successfully an attribute of that name with value true. + + sheets: {}, + + // For each volatile function that is called an attribute of that name with value true. + + volatile: {}, + + // Set to false when started and true when recalc completes + + recalc_completed: false, + }; + + SocialCalc.Formula.FreshnessInfoReset = function () { + var scffi = SocialCalc.Formula.FreshnessInfo; + + scffi.sheets = {}; + scffi.volatile = {}; + scffi.recalc_completed = false; + }; + + // + // MISC ROUTINES + // + + // + // result = SocialCalc.Formula.PlainCoord(coord) + // + // Returns: coord without any $'s + // + + SocialCalc.Formula.PlainCoord = function (coord) { + if (coord.indexOf("$") == -1) return coord; + + return coord.replace(/\$/g, ""); // remove any $'s + }; + + // + // result = SocialCalc.Formula.OrderRangeParts(coord1, coord2) + // + // Returns: {c1: col, r1: row, c2: col, r2 = row} with c1/r1 upper left + // + + SocialCalc.Formula.OrderRangeParts = function (coord1, coord2) { + var cr1, cr2; + var result = {}; + + cr1 = SocialCalc.coordToCr(coord1); + cr2 = SocialCalc.coordToCr(coord2); + if (cr1.col > cr2.col) { + result.c1 = cr2.col; + result.c2 = cr1.col; + } else { + result.c1 = cr1.col; + result.c2 = cr2.col; + } + if (cr1.row > cr2.row) { + result.r1 = cr2.row; + result.r2 = cr1.row; + } else { + result.r1 = cr1.row; + result.r2 = cr2.row; + } + + return result; + }; + + // + // cond = SocialCalc.Formula.TestCriteria(value, type, criteria) + // + // Determines whether a value/type meets the criteria. + // A criteria can be a numeric value, text beginning with <, <=, =, >=, >, <>, text by itself is start of text to match. + // Used by a variety of functions, including the "D" functions (DSUM, etc.). + // + // Returns true or false + // + + SocialCalc.Formula.TestCriteria = function (value, type, criteria) { + var comparitor, basestring, basevalue, cond, testvalue; + + if (criteria == null) { + // undefined (e.g., error value) is always false + return false; + } + + criteria = criteria + ""; + comparitor = criteria.charAt(0); // look for comparitor + if (comparitor == "=" || comparitor == "<" || comparitor == ">") { + basestring = criteria.substring(1); + } else { + comparitor = criteria.substring(0, 2); + if (comparitor == "<=" || comparitor == "<>" || comparitor == ">=") { + basestring = criteria.substring(2); + } else { + comparitor = "none"; + basestring = criteria; + } + } + + basevalue = SocialCalc.DetermineValueType(basestring); // get type of value being compared + if (!basevalue.type) { + // no criteria base value given + if (comparitor == "none") { + // blank criteria matches nothing + return false; + } + if (type.charAt(0) == "b") { + // comparing to empty cell + if (comparitor == "=") { + // empty equals empty + return true; + } + } else { + if (comparitor == "<>") { + // "something" does not equal empty + return true; + } + } + return false; // otherwise false + } + + cond = false; + + if (basevalue.type.charAt(0) == "n" && type.charAt(0) == "t") { + // criteria is number, but value is text + testvalue = SocialCalc.DetermineValueType(value); + if (testvalue.type.charAt(0) == "n") { + // could be number - make it one + value = testvalue.value; + type = testvalue.type; + } + } + + if (type.charAt(0) == "n" && basevalue.type.charAt(0) == "n") { + // compare two numbers + value = value - 0; // make sure numbers + basevalue.value = basevalue.value - 0; + switch (comparitor) { + case "<": + cond = value < basevalue.value; + break; + + case "<=": + cond = value <= basevalue.value; + break; + + case "=": + case "none": + cond = value == basevalue.value; + break; + + case ">=": + cond = value >= basevalue.value; + break; + + case ">": + cond = value > basevalue.value; + break; + + case "<>": + cond = value != basevalue.value; + break; + } + } else if (type.charAt(0) == "e") { + // error on left + cond = false; + } else if (basevalue.type.charAt(0) == "e") { + // error on right + cond = false; + } else { + // text, maybe mixed with number or blank + if (type.charAt(0) == "n") { + value = SocialCalc.format_number_for_display(value, "n", ""); + } + if (basevalue.type.charAt(0) == "n") { + return false; // if number and didn't match already, isn't a match + } + + value = value ? value.toLowerCase() : ""; + basevalue.value = basevalue.value ? basevalue.value.toLowerCase() : ""; + + switch (comparitor) { + case "<": + cond = value < basevalue.value; + break; + + case "<=": + cond = value <= basevalue.value; + break; + + case "=": + cond = value == basevalue.value; + break; + + case "none": + cond = value.substring(0, basevalue.value.length) == basevalue.value; + break; + + case ">=": + cond = value >= basevalue.value; + break; + + case ">": + cond = value > basevalue.value; + break; + + case "<>": + cond = value != basevalue.value; + break; + } + } + + return cond; + }; + + + // Make sure SocialCalc is available globally + if (typeof window !== "undefined") { + window.SocialCalc = SocialCalc; + } else if (typeof global !== "undefined") { + global.SocialCalc = SocialCalc; + } + + return SocialCalc; +}); diff --git a/src/components/socialcalc/core/formula.js b/src/components/socialcalc/core/formula.js new file mode 100644 index 0000000..36c8b0f --- /dev/null +++ b/src/components/socialcalc/core/formula.js @@ -0,0 +1,1675 @@ +/* eslint-disable */ +// SocialCalc Formula Module +// Extracted from the main SocialCalc.js file + +// UMD wrapper +(function (root, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.SocialCalcFormula = factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + + // Get SocialCalc namespace + var SocialCalc; + if (typeof window !== "undefined" && window.SocialCalc) { + SocialCalc = window.SocialCalc; + } else if (typeof global !== "undefined" && global.SocialCalc) { + SocialCalc = global.SocialCalc; + } else { + SocialCalc = {}; + } + + + // + /* + // The module of the SocialCalc package for the optional popup menus in socialcalcspreadsheetcontrol.js + // + // (c) Copyright 2009 Socialtext, Inc. + // All Rights Reserved. + // + // The contents of this file are subject to the Artistic License 2.0; you may not + // use this file except in compliance with the License. You may obtain a copy of + // the License at http://socialcalc.org/licenses/al-20/. + // + // Some of the other files in the SocialCalc package are licensed under + // different licenses. Please note the licenses of the modules you use. + // + // Code History: + // + // Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. + // + */ + + var SocialCalc; // All values are stored in the master SocialCalc object + if (!SocialCalc) { + SocialCalc = {}; + } + + // The main Popup data -- there is only one set + + SocialCalc.Popup = {}; + + // Routines and values for each type of control, indexed by type name + // The value for each is an object constructed as follows: + // + // Create = function(type, id, attribs) + // Initialize = function(type, id, data) + // SetValue = function(type, id, value) + // GetValue = function(type, id) returns value + // SetDisabled = function(type, id, t/f) + // Show = function(type, id) + // Hide = function(type, id) + // Cancel = function(type, id) + // Reset = function(type) + // + // data = object to hold type-specific data + // + + SocialCalc.Popup.Types = {}; + + // Definitions for each individual control, indexed by id + // The value for each is an object constructed as follows: + // + // type: type name of the control + // value: current value of the control (usually a string, but can depend on type) + // data: object with type-specific items + // + + SocialCalc.Popup.Controls = {}; + + // System-wide values of currently active control + // + // id: id of current control or null + // + + SocialCalc.Popup.Current = {}; + + // Other values used by the Popup system + // + + SocialCalc.Popup.imagePrefix = "www/assets/images/sc_"; // image prefix + + // Override this for localization + + SocialCalc.Popup.LocalizeString = function (str) { + return str; + }; + + // * * * * * * * * * * * * * * * * + // + // GENERAL ROUTINES + // + // * * * * * * * * * * * * * * * * + + // + // SocialCalc.Popup.Create(type, id, attribs) + // + // Creates a control of type "type" as the children of document element "id" using "attribs" + // + + SocialCalc.Popup.Create = function (type, id, attribs) { + var pt = SocialCalc.Popup.Types[type]; + if (pt && pt.Create) { + pt.Create(type, id, attribs); + } + }; + + // + // SocialCalc.Popup.SetValue(id, value) + // + // Sets the value of control. + // + + SocialCalc.Popup.SetValue = function (id, value) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (!spc[id]) { + alert("Unknown control " + id); + return; + } + + var type = spc[id].type; + var pt = spt[type]; + var spcdata = spc[id].data; + + if (pt && pt.Create) { + pt.SetValue(type, id, value); + if (spcdata.attribs && spcdata.attribs.changedcallback) { + spcdata.attribs.changedcallback(spcdata.attribs, id, value); + } + } + }; + + // + // SocialCalc.Popup.SetDisabled(id, disabled) + // + // Sets whether the control is disabled (true) or not (false). + // + + SocialCalc.Popup.SetDisabled = function (id, disabled) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (!spc[id]) { + alert("Unknown control " + id); + return; + } + + var type = spc[id].type; + + var pt = spt[type]; + if (pt && pt.Create) { + if (sp.Current.id && id == sp.Current.id) { + pt.Hide(type, sp.Current.id); + sp.Current.id = null; + } + pt.SetDisabled(type, id, disabled); + } + }; + + // + // SocialCalc.Popup.GetValue(id) + // + // Returns the value of control. + // + + SocialCalc.Popup.GetValue = function (id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (!spc[id]) { + alert("Unknown control " + id); + return; + } + + var type = spc[id].type; + + var pt = spt[type]; + if (pt && pt.Create) { + return pt.GetValue(type, id); + } + + return null; + }; + + // + // SocialCalc.Popup.Initialize(id, data) + // + // Gives "data" to the appropriate initialization code. + // + + SocialCalc.Popup.Initialize = function (id, data) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (!spc[id]) { + alert("Unknown control " + id); + return; + } + + var type = spc[id].type; + + var pt = spt[type]; + if (pt && pt.Initialize) { + pt.Initialize(type, id, data); + } + }; + + // + // SocialCalc.Popup.Reset(type) + // + // Resets Popup, such as when turning to page. + // + + SocialCalc.Popup.Reset = function (type) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (spt[type].Reset) spt[type].Reset(type); + }; + + // + // SocialCalc.Popup.CClick(id) + // + // Should be called when the user clicks on a control to do the popup + // + + SocialCalc.Popup.CClick = function (id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (!spc[id]) { + alert("Unknown control " + id); + return; + } + + if (spc[id].data && spc[id].data.disabled) return; + + var type = spc[id].type; + + var pt = spt[type]; + + if (sp.Current.id) { + spt[spc[sp.Current.id].type].Hide(type, sp.Current.id); + if (id == sp.Current.id) { + // same one - done + sp.Current.id = null; + return; + } + } + + if (pt && pt.Show) { + pt.Show(type, id); + } + + sp.Current.id = id; + }; + + // + // SocialCalc.Popup.Close() + // + // Used to close any open popup. + // + + SocialCalc.Popup.Close = function () { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (!sp.Current.id) return; + + sp.CClick(sp.Current.id); + }; + + // + // SocialCalc.Popup.Cancel() + // + // Closes Popup and restores old value + // + + SocialCalc.Popup.Cancel = function () { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (!sp.Current.id) return; + + var type = spc[sp.Current.id].type; + + var pt = spt[type]; + + pt.Cancel(type, sp.Current.id); + + sp.Current.id = null; + }; + + // + // ele = SocialCalc.Popup.CreatePopupDiv(id, attribs) + // + // Utility function to create the main popup div of width attribs.width. + // If attribs.title, create one with that text, and optionally attribs.moveable. + // + + SocialCalc.Popup.CreatePopupDiv = function (id, attribs) { + var pos, ele; + + var sp = SocialCalc.Popup; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var main = document.createElement("div"); + main.style.position = "absolute"; + + pos = SocialCalc.GetElementPositionWithScroll(spcdata.mainele); + + main.style.top = pos.top + spcdata.mainele.offsetHeight + "px"; + main.style.left = pos.left + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + if (attribs.width) { + main.style.width = attribs.width; + } + + spcdata.mainele.appendChild(main); + + if (attribs.title) { + main.innerHTML = + '' + + '" + + '
' + + attribs.title + + " X 
'; + + if (attribs.moveable) { + spcdata.dragregistered = + main.firstChild.firstChild.firstChild.firstChild; + SocialCalc.DragRegister(spcdata.dragregistered, true, true, { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + }); + } + } + + return main; + }; + + // + // SocialCalc.Popup.EnsurePosition(id, container) + // + // Utility function to make sure popup is positioned completely within container (both element objects) + // and appropriate with respect to the main element controlling the popup. + // + + SocialCalc.Popup.EnsurePosition = function (id, container) { + var sp = SocialCalc.Popup; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var main = spcdata.mainele.firstChild; + if (!main) { + alert("No main popup element firstChild."); + return; + } + var popup = spcdata.popupele; + + function GetLayoutValues(ele) { + var r = SocialCalc.GetElementPositionWithScroll(ele); + r.height = ele.offsetHeight; + r.width = ele.offsetWidth; + r.bottom = r.top + r.height; + r.right = r.left + r.width; + return r; + } + + var p = GetLayoutValues(popup); + var c = GetLayoutValues(container); + var m = GetLayoutValues(main); + var t = 0; // type of placement + //addmsg("popup t/r/b/l/h/w= "+p.top+"/"+p.right+"/"+p.bottom+"/"+p.left+"/"+p.height+"/"+p.width); + //addmsg("container t/r/b/l/h/w= "+c.top+"/"+c.right+"/"+c.bottom+"/"+c.left+"/"+c.height+"/"+c.width); + //addmsg("main t/r/b/l/h/w= "+m.top+"/"+m.right+"/"+m.bottom+"/"+m.left+"/"+m.height+"/"+m.width); + + // Check various layout cases in priority order + + if (m.bottom + p.height < c.bottom && m.left + p.width < c.right) { + // normal case: room on bottom and right + popup.style.top = m.bottom + "px"; + popup.style.left = m.left + "px"; + t = 1; + } else if (m.top - p.height > c.top && m.left + p.width < c.right) { + // room on top and right + popup.style.top = m.top - p.height + "px"; + popup.style.left = m.left + "px"; + t = 2; + } else if (m.bottom + p.height < c.bottom && m.right - p.width > c.left) { + // room on bottom and left + popup.style.top = m.bottom + "px"; + popup.style.left = m.right - p.width + "px"; + t = 3; + } else if (m.top - p.height > c.top && m.right - p.width > c.left) { + // room on top and left + popup.style.top = m.top - p.height + "px"; + popup.style.left = m.right - p.width + "px"; + t = 4; + } else if (m.bottom + p.height < c.bottom && p.width < c.width) { + // room on bottom and middle + popup.style.top = m.bottom + "px"; + popup.style.left = c.left + Math.floor((c.width - p.width) / 2) + "px"; + t = 5; + } else if (m.top - p.height > c.top && p.width < c.width) { + // room on top and middle + popup.style.top = m.top - p.height + "px"; + popup.style.left = c.left + Math.floor((c.width - p.width) / 2) + "px"; + t = 6; + } else if (p.height < c.height && m.right + p.width < c.right) { + // room on middle and right + popup.style.top = c.top + Math.floor((c.height - p.height) / 2) + "px"; + popup.style.left = m.right + "px"; + t = 7; + } else if (p.height < c.height && m.left - p.width > c.left) { + // room on middle and left + popup.style.top = c.top + Math.floor((c.height - p.height) / 2) + "px"; + popup.style.left = m.left - p.width + "px"; + t = 8; + } else { + // nothing works, so leave as it is + } + //addmsg("Popup layout "+t); + }; + + // + // ele = SocialCalc.Popup.DestroyPopupDiv(ele, dragregistered) + // + // Utility function to get rid of the main popup div. + // + + SocialCalc.Popup.DestroyPopupDiv = function (ele, dragregistered) { + if (!ele) return; + + ele.innerHTML = ""; + + SocialCalc.DragUnregister(dragregistered); // OK to do this even if not registered + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + }; + + // + // Color Utility Functions + // + + SocialCalc.Popup.RGBToHex = function (val) { + var sp = SocialCalc.Popup; + + if (val == "") { + return "000000"; + } + var rgbvals = val.match(/(\d+)\D+(\d+)\D+(\d+)/); + if (rgbvals) { + return sp.ToHex(rgbvals[1]) + sp.ToHex(rgbvals[2]) + sp.ToHex(rgbvals[3]); + } else { + return "000000"; + } + }; + + SocialCalc.Popup.HexDigits = "0123456789ABCDEF"; + + SocialCalc.Popup.ToHex = function (num) { + var sp = SocialCalc.Popup; + var first = Math.floor(num / 16); + var second = num % 16; + return sp.HexDigits.charAt(first) + sp.HexDigits.charAt(second); + }; + + SocialCalc.Popup.FromHex = function (str) { + var sp = SocialCalc.Popup; + var first = sp.HexDigits.indexOf(str.charAt(0).toUpperCase()); + var second = sp.HexDigits.indexOf(str.charAt(1).toUpperCase()); + return (first >= 0 ? first : 0) * 16 + (second >= 0 ? second : 0); + }; + + SocialCalc.Popup.HexToRGB = function (val) { + var sp = SocialCalc.Popup; + + return ( + "rgb(" + + sp.FromHex(val.substring(1, 3)) + + "," + + sp.FromHex(val.substring(3, 5)) + + "," + + sp.FromHex(val.substring(5, 7)) + + ")" + ); + }; + + SocialCalc.Popup.makeRGB = function (r, g, b) { + return ( + "rgb(" + + (r > 0 ? r : 0) + + "," + + (g > 0 ? g : 0) + + "," + + (b > 0 ? b : 0) + + ")" + ); + }; + + SocialCalc.Popup.splitRGB = function (rgb) { + var parts = rgb.match(/(\d+)\D+(\d+)\D+(\d+)\D/); + if (!parts) { + return { r: 0, g: 0, b: 0 }; + } else { + return { r: parts[1] - 0, g: parts[2] - 0, b: parts[3] - 0 }; + } + }; + + // * * * * * * * * * * * * * * * * + // + // ROUTINES FOR EACH TYPE + // + // * * * * * * * * * * * * * * * * + + // + // List + // + // type: List + // value: value of control, + // display: "value to display", + // custom: true if custom value, + // disabled: t/f, + // attribs: { + // title: "popup title string", + // moveable: t/f, + // width: optional width, e.g., "100px", + // ensureWithin: optional element object to ensure popup fits within if possible + // changedcallback: optional function(attribs, id, newvalue), + // ... + // } + // data: { + // ncols: calculated number of columns + // options: [ + // {o: option-name, v: value-to-return, + // a: {option attribs} // optional: {skip: true, custom: true, cancel: true, newcol: true} + // }, + // ...] + // } + // + // popupele: gets popup element object when created + // contentele: gets element created with all the content + // listdiv: gets div with list of items + // customele: gets input element with custom value + // dragregistered: gets element, if any, registered as draggable + // + + SocialCalc.Popup.Types.List = {}; + + SocialCalc.Popup.Types.List.Create = function (type, id, attribs) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + var spcid = { type: type, value: "", display: "", data: {} }; + if (spc[id]) { + return; + } + spc[id] = spcid; + var spcdata = spcid.data; + + spcdata.attribs = attribs || {}; + + var ele = document.getElementById(id); + if (!ele) { + alert("Missing element " + id); + return; + } + + spcdata.mainele = ele; + + ele.innerHTML = + ''; + + spcdata.options = []; // set to nothing - use Initialize to fill + }; + + SocialCalc.Popup.Types.List.SetValue = function (type, id, value) { + var i; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + spcdata.value = value; + spcdata.custom = false; + + for (i = 0; i < spcdata.options.length; i++) { + o = spcdata.options[i]; + if (o.a) { + if (o.a.skip || o.a.custom || o.a.cancel) { + continue; + } + } + if (o.v == spcdata.value) { + // matches value + spcdata.display = o.o; + break; + } + } + if (i == spcdata.options.length) { + // none found + spcdata.display = "Custom"; + spcdata.custom = true; + } + + if (spcdata.mainele && spcdata.mainele.firstChild) { + spcdata.mainele.firstChild.value = spcdata.display; + } + }; + + SocialCalc.Popup.Types.List.SetDisabled = function (type, id, disabled) { + var i; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + spcdata.disabled = disabled; + + if (spcdata.mainele && spcdata.mainele.firstChild) { + spcdata.mainele.firstChild.disabled = disabled; + } + }; + + SocialCalc.Popup.Types.List.GetValue = function (type, id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + return spcdata.value; + }; + + // data is: {value: initial value, attribs: {attribs stuff}, options: [{o: option-name, v: value-to-return, a: optional-attribs}, ...]} + + SocialCalc.Popup.Types.List.Initialize = function (type, id, data) { + var a; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + for (a in data.attribs) { + spcdata.attribs[a] = data.attribs[a]; + } + + spcdata.options = data ? data.options : []; + + if (data.value) { + // if has a value, set to it + sp.SetValue(id, data.value); + } + }; + + SocialCalc.Popup.Types.List.Reset = function (type) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (sp.Current.id && spc[sp.Current.id].type == type) { + // we have a popup + spt[type].Hide(type, sp.Current.id); + sp.Current.id = null; + } + }; + + SocialCalc.Popup.Types.List.Show = function (type, id) { + var i, ele, o, bg; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var str = ""; + + spcdata.popupele = sp.CreatePopupDiv(id, spcdata.attribs); + + if (spcdata.custom) { + str = SocialCalc.Popup.Types.List.MakeCustom(type, id); + + ele = document.createElement("div"); + ele.innerHTML = + '
' + + str + + "
"; + + spcdata.customele = ele.firstChild.firstChild.childNodes[1]; + spcdata.listdiv = null; + spcdata.contentele = ele; + } else { + str = SocialCalc.Popup.Types.List.MakeList(type, id); + + ele = document.createElement("div"); + ele.innerHTML = + '
' + str + "
"; + + spcdata.customele = null; + spcdata.listdiv = ele.firstChild; + spcdata.contentele = ele; + } + + if (spcdata.mainele && spcdata.mainele.firstChild) { + spcdata.mainele.firstChild.disabled = true; + } + + spcdata.popupele.appendChild(ele); + + if (spcdata.attribs.ensureWithin) { + SocialCalc.Popup.EnsurePosition(id, spcdata.attribs.ensureWithin); + } + }; + + SocialCalc.Popup.Types.List.MakeList = function (type, id) { + var i, ele, o, bg; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var str = ''; + var td = '" + td + "    " + "" + td; + spcdata.ncols += 1; + continue; + } + if (o.a.skip) { + str += + '
' + + o.o + + "
"; + continue; + } + } + if (o.v == spcdata.value && !(o.a && (o.a.custom || o.a.cancel))) { + // matches value + bg = "background-color:#DDF;"; + } else { + bg = ""; + } + str += + '
" + + o.o + + "
"; + } + + str += "
'; + + str += td; + + spcdata.ncols = 1; + + for (i = 0; i < spcdata.options.length; i++) { + o = spcdata.options[i]; + if (o.a) { + if (o.a.newcol) { + str += "
"; + + return str; + }; + + SocialCalc.Popup.Types.List.MakeCustom = function (type, id) { + var SPLoc = SocialCalc.Popup.LocalizeString; + + var i, ele, o, bg; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var style = 'style="font-size:smaller;"'; + + var str = ""; + + var val = spcdata.value; + val = SocialCalc.special_chars(val); + + str = + '

' + + '

' + + "" + + "" + + "' + + "
"; + + return str; + }; + + SocialCalc.Popup.Types.List.ItemClicked = function (id, num) { + var oele, str, nele; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var a = spcdata.options[num].a; + + if (a && a.custom) { + oele = spcdata.contentele; + str = SocialCalc.Popup.Types.List.MakeCustom("List", id); + nele = document.createElement("div"); + nele.innerHTML = + '
' + + str + + "
"; + spcdata.customele = nele.firstChild.firstChild.childNodes[1]; + spcdata.listdiv = null; + spcdata.contentele = nele; + spcdata.popupele.replaceChild(nele, oele); + if (spcdata.attribs.ensureWithin) { + SocialCalc.Popup.EnsurePosition(id, spcdata.attribs.ensureWithin); + } + return; + } + + if (a && a.cancel) { + SocialCalc.Popup.Close(); + return; + } + + SocialCalc.Popup.SetValue(id, spcdata.options[num].v); + + SocialCalc.Popup.Close(); + }; + + SocialCalc.Popup.Types.List.CustomToList = function (id) { + var oele, str, nele; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + oele = spcdata.contentele; + str = SocialCalc.Popup.Types.List.MakeList("List", id); + nele = document.createElement("div"); + nele.innerHTML = + '
' + str + "
"; + spcdata.customele = null; + spcdata.listdiv = nele.firstChild; + spcdata.contentele = nele; + spcdata.popupele.replaceChild(nele, oele); + + if (spcdata.attribs.ensureWithin) { + SocialCalc.Popup.EnsurePosition(id, spcdata.attribs.ensureWithin); + } + }; + + SocialCalc.Popup.Types.List.CustomOK = function (id) { + var i, c; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + SocialCalc.Popup.SetValue(id, spcdata.customele.value); + + SocialCalc.Popup.Close(); + }; + + SocialCalc.Popup.Types.List.MouseMove = function (id, ele) { + var col, i, c; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var list = spcdata.listdiv; + + if (!list) return; + + var rowele = list.firstChild.firstChild.firstChild; // div.table.tbody.tr + + for (col = 0; col < spcdata.ncols; col++) { + for (i = 0; i < rowele.childNodes[col * 2].childNodes.length; i++) { + rowele.childNodes[col * 2].childNodes[i].style.backgroundColor = "#FFF"; + } + } + + ele.style.backgroundColor = "#DDF"; + }; + + SocialCalc.Popup.Types.List.Hide = function (type, id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + sp.DestroyPopupDiv(spcdata.popupele, spcdata.dragregistered); + spcdata.popupele = null; + + if (spcdata.mainele && spcdata.mainele.firstChild) { + spcdata.mainele.firstChild.disabled = false; + } + }; + + SocialCalc.Popup.Types.List.Cancel = function (type, id) { + SocialCalc.Popup.Types.List.Hide(type, id); + }; + + // + // ColorChooser + // + // type: ColorChooser + // value: value of control as "rgb(r,g,b)" or "" if default, + // oldvalue: starting value to reset to on close, + // display: "value to display" as hex color value, + // custom: true if custom value, + // disabled: t/f, + // attribs: { + // title: "popup title string", + // moveable: t/f, + // width: optional width, e.g., "100px", of popup chooser + // ensureWithin: optional element object to ensure popup fits within if possible + // sampleWidth: optional width, e.g., "20px", + // sampleHeight: optional height, e.g., "20px", + // backgroundImage: optional background image for sample (transparent where want to show current color), e.g., "colorbg.gif" + // backgroundImageDefault: optional background image for sample when default (transparent shows white) + // backgroundImageDisabled: optional background image for sample when disabled (transparent shows gray) + // changedcallback: optional function(attribs, id, newvalue), + // ... + // } + // data: { + // } + // + // popupele: gets popup element object when created + // contentele: gets element created with all the content + // customele: gets input element with custom value + // + + SocialCalc.Popup.Types.ColorChooser = {}; + + SocialCalc.Popup.Types.ColorChooser.Create = function (type, id, attribs) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + var spcid = { type: type, value: "", display: "", data: {} }; + if (spc[id]) { + return; + } + spc[id] = spcid; + var spcdata = spcid.data; + + spcdata.attribs = attribs || {}; + var spca = spcdata.attribs; + + spcdata.value = ""; + + var ele = document.getElementById(id); + if (!ele) { + alert("Missing element " + id); + return; + } + + spcdata.mainele = ele; + + ele.innerHTML = + '
 
"; + }; + + SocialCalc.Popup.Types.ColorChooser.SetValue = function (type, id, value) { + var i, img, pos; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + var spca = spcdata.attribs; + + spcdata.value = value; + spcdata.custom = false; + + if (spcdata.mainele && spcdata.mainele.firstChild) { + if (spcdata.value) { + spcdata.mainele.firstChild.style.backgroundColor = spcdata.value; + if (spca.backgroundImage) { + img = "url(" + sp.imagePrefix + spca.backgroundImage + ")"; + } else { + img = ""; + } + pos = "center center"; + } else { + spcdata.mainele.firstChild.style.backgroundColor = "#FFF"; + if (spca.backgroundImageDefault) { + img = "url(" + sp.imagePrefix + spca.backgroundImageDefault + ")"; + pos = "center center"; + } else { + img = "url(" + sp.imagePrefix + "defaultcolor.gif)"; + pos = "left top"; + } + } + spcdata.mainele.firstChild.style.backgroundPosition = pos; + spcdata.mainele.firstChild.style.backgroundImage = img; + } + }; + + SocialCalc.Popup.Types.ColorChooser.SetDisabled = function ( + type, + id, + disabled + ) { + var i; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + var spca = spcdata.attribs; + + spcdata.disabled = disabled; + + if (spcdata.mainele && spcdata.mainele.firstChild) { + if (disabled) { + spcdata.mainele.firstChild.style.backgroundColor = "#DDD"; + if (spca.backgroundImageDisabled) { + img = "url(" + sp.imagePrefix + spca.backgroundImageDisabled + ")"; + pos = "center center"; + } else { + img = "url(" + sp.imagePrefix + "defaultcolor.gif)"; + pos = "left top"; + } + spcdata.mainele.firstChild.style.backgroundPosition = pos; + spcdata.mainele.firstChild.style.backgroundImage = img; + } else { + sp.SetValue(id, spcdata.value); + } + } + }; + + SocialCalc.Popup.Types.ColorChooser.GetValue = function (type, id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + return spcdata.value; + }; + + SocialCalc.Popup.Types.ColorChooser.Initialize = function (type, id, data) { + var a; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + for (a in data.attribs) { + spcdata.attribs[a] = data.attribs[a]; + } + + if (data.value) { + // if has a value, set to it + sp.SetValue(id, data.value); + } + }; + + SocialCalc.Popup.Types.ColorChooser.Reset = function (type) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + + if (sp.Current.id && spc[sp.Current.id].type == type) { + // we have a popup + spt[type].Hide(type, sp.Current.id); + sp.Current.id = null; + } + }; + + SocialCalc.Popup.Types.ColorChooser.Show = function (type, id) { + var i, ele, mainele; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var str = ""; + + spcdata.oldvalue = spcdata.value; // remember starting value + + spcdata.popupele = sp.CreatePopupDiv(id, spcdata.attribs); + + if (spcdata.custom) { + str = SocialCalc.Popup.Types.ColorChooser.MakeCustom(type, id); + + ele = document.createElement("div"); + ele.innerHTML = + '
' + + str + + "
"; + + spcdata.customele = ele.firstChild.firstChild.childNodes[2]; + spcdata.contentele = ele; + } else { + mainele = SocialCalc.Popup.Types.ColorChooser.CreateGrid(type, id); + + ele = document.createElement("div"); + ele.style.padding = "3px"; + ele.style.backgroundColor = "#CCC"; + ele.appendChild(mainele); + + spcdata.customele = null; + spcdata.contentele = ele; + } + + spcdata.popupele.appendChild(ele); + + if (spcdata.attribs.ensureWithin) { + SocialCalc.Popup.EnsurePosition(id, spcdata.attribs.ensureWithin); + } + }; + + SocialCalc.Popup.Types.ColorChooser.MakeCustom = function (type, id) { + var i, ele, o, bg; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + var SPLoc = sp.LocalizeString; + + var style = 'style="font-size:smaller;"'; + + var str = ""; + + str = + '

' + + '#

' + + "" + + "" + + "
"; + + return str; + }; + + SocialCalc.Popup.Types.ColorChooser.ItemClicked = function (id, num) { + var oele, str, nele; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + SocialCalc.Popup.Close(); + }; + + SocialCalc.Popup.Types.ColorChooser.CustomToList = function (id) { + var oele, str, nele; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + }; + + SocialCalc.Popup.Types.ColorChooser.CustomOK = function (id) { + var i, c; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + sp.SetValue(id, spcdata.customele.value); + + sp.Close(); + }; + + SocialCalc.Popup.Types.ColorChooser.Hide = function (type, id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + sp.DestroyPopupDiv(spcdata.popupele, spcdata.dragregistered); + spcdata.popupele = null; + }; + + SocialCalc.Popup.Types.ColorChooser.Cancel = function (type, id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + sp.SetValue(id, spcdata.oldvalue); // reset to old value + + SocialCalc.Popup.Types.ColorChooser.Hide(type, id); + }; + + SocialCalc.Popup.Types.ColorChooser.CreateGrid = function (type, id) { + var ele, pos, row, rowele, col, g; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var SPLoc = sp.LocalizeString; + var spcdata = spc[id].data; + spcdata.grid = {}; + var grid = spcdata.grid; + + var mainele = document.createElement("div"); + + ele = document.createElement("table"); + ele.cellSpacing = 0; + ele.cellPadding = 0; + ele.style.width = "100px"; + grid.table = ele; + + ele = document.createElement("tbody"); + grid.table.appendChild(ele); + grid.tbody = ele; + + for (row = 0; row < 16; row++) { + rowele = document.createElement("tr"); + for (col = 0; col < 5; col++) { + g = {}; + grid[row + "," + col] = g; + ele = document.createElement("td"); + ele.style.fontSize = "1px"; + ele.innerHTML = " "; + ele.style.height = "10px"; + if (col <= 1) { + ele.style.width = "17px"; + ele.style.borderRight = "3px solid white"; + } else { + ele.style.width = "20px"; + ele.style.backgroundRepeat = "no-repeat"; + } + rowele.appendChild(ele); + g.ele = ele; + } + grid.tbody.appendChild(rowele); + } + mainele.appendChild(grid.table); + + ele = document.createElement("div"); + ele.style.marginTop = "3px"; + ele.innerHTML = + '' + + '' + + '' + + '" + + "
 #' + + SPLoc("OK") + + "
"; + grid.defaultbox = ele.firstChild.firstChild.firstChild.childNodes[0]; + grid.defaultbox.onclick = spt.ColorChooser.DefaultClicked; + grid.custom = ele.firstChild.firstChild.firstChild.childNodes[1]; + grid.custom.onclick = spt.ColorChooser.CustomClicked; + grid.msg = ele.firstChild.firstChild.firstChild.childNodes[2]; + grid.msg.onclick = spt.ColorChooser.CloseOK; + mainele.appendChild(ele); + + grid.table.onmousedown = spt.ColorChooser.GridMouseDown; + + spt.ColorChooser.DetermineColors(id); + spt.ColorChooser.SetColors(id); + + return mainele; + }; + + SocialCalc.Popup.Types.ColorChooser.gridToG = function (grid, row, col) { + return grid[row + "," + col]; + }; + + SocialCalc.Popup.Types.ColorChooser.DetermineColors = function (id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var sptc = spt.ColorChooser; + var spc = sp.Controls; + var spcdata = spc[id].data; + var grid = spcdata.grid; + + var col, row; + var rgb = sp.splitRGB(spcdata.value); + var color; + + col = 2; + row = 16 - Math.floor((rgb.r + 16) / 16); + grid["selectedrow" + col] = row; + for (row = 0; row < 16; row++) { + sptc.gridToG(grid, row, col).rgb = sp.makeRGB(17 * (15 - row), 0, 0); + } + + col = 3; + row = 16 - Math.floor((rgb.g + 16) / 16); + grid["selectedrow" + col] = row; + for (row = 0; row < 16; row++) { + sptc.gridToG(grid, row, col).rgb = sp.makeRGB(0, 17 * (15 - row), 0); + } + + col = 4; + row = 16 - Math.floor((rgb.b + 16) / 16); + grid["selectedrow" + col] = row; + for (row = 0; row < 16; row++) { + sptc.gridToG(grid, row, col).rgb = sp.makeRGB(0, 0, 17 * (15 - row)); + } + + col = 1; + for (row = 0; row < 16; row++) { + sptc.gridToG(grid, row, col).rgb = sp.makeRGB( + 17 * (15 - row), + 17 * (15 - row), + 17 * (15 - row) + ); + } + + col = 0; + var steps = [0, 68, 153, 204, 255]; + var commonrgb = [ + "400", + "310", + "420", + "440", + "442", + "340", + "040", + "042", + "032", + "044", + "024", + "004", + "204", + "314", + "402", + "414", + ]; + var x; + for (row = 0; row < 16; row++) { + x = commonrgb[row]; + sptc.gridToG(grid, row, col).rgb = + "rgb(" + + steps[x.charAt(0) - 0] + + "," + + steps[x.charAt(1) - 0] + + "," + + steps[x.charAt(2) - 0] + + ")"; + } + }; + + SocialCalc.Popup.Types.ColorChooser.SetColors = function (id) { + var row, col, g, ele, rgb; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var sptc = spt.ColorChooser; + var spc = sp.Controls; + var spcdata = spc[id].data; + var grid = spcdata.grid; + + for (row = 0; row < 16; row++) { + for (col = 0; col < 5; col++) { + g = sptc.gridToG(grid, row, col); + g.ele.style.backgroundColor = g.rgb; + g.ele.title = sp.RGBToHex(g.rgb); + if (grid["selectedrow" + col] == row) { + g.ele.style.backgroundImage = + "url(" + sp.imagePrefix + "chooserarrow.gif)"; + } else { + g.ele.style.backgroundImage = ""; + } + } + } + + sp.SetValue(id, spcdata.value); + + grid.msg.style.backgroundColor = spcdata.value; + rgb = sp.splitRGB(spcdata.value || "rgb(255,255,255)"); + if (rgb.r + rgb.g + rgb.b < 220) { + grid.msg.style.color = "#FFF"; + } else { + grid.msg.style.color = "#000"; + } + if (!spcdata.value) { + // default + grid.msg.style.backgroundColor = "#FFF"; + grid.msg.style.backgroundImage = + "url(" + sp.imagePrefix + "defaultcolor.gif)"; + grid.msg.title = "Default"; + } else { + grid.msg.style.backgroundImage = ""; + grid.msg.title = sp.RGBToHex(spcdata.value); + } + }; + + SocialCalc.Popup.Types.ColorChooser.GridMouseDown = function (e) { + var event = e || window.event; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var sptc = spt.ColorChooser; + var spc = sp.Controls; + + var id = sp.Current.id; + if (!id) return; + + var spcdata = spc[id].data; + var grid = spcdata.grid; + + switch (event.type) { + case "mousedown": + grid.mousedown = true; + break; + case "mouseup": + grid.mousedown = false; + break; + case "mousemove": + if (!grid.mousedown) { + return; + } + break; + } + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + var gpos = SocialCalc.GetElementPosition(grid.table); + var row = Math.floor((clientY - gpos.top - 2) / 10); // -2 is to split the diff btw IE & FF + row = row < 0 ? 0 : row; + var col = Math.floor((clientX - gpos.left) / 20); + row = row < 0 ? 0 : row > 15 ? 15 : row; + col = col < 0 ? 0 : col > 4 ? 4 : col; + var color = sptc.gridToG(grid, row, col).ele.style.backgroundColor; + var newrgb = sp.splitRGB(color); + var oldrgb = sp.splitRGB(spcdata.value); + + switch (col) { + case 2: + spcdata.value = sp.makeRGB(newrgb.r, oldrgb.g, oldrgb.b); + break; + case 3: + spcdata.value = sp.makeRGB(oldrgb.r, newrgb.g, oldrgb.b); + break; + case 4: + spcdata.value = sp.makeRGB(oldrgb.r, oldrgb.g, newrgb.b); + break; + case 0: + case 1: + spcdata.value = color; + } + + sptc.DetermineColors(id); + sptc.SetColors(id); + }; + + SocialCalc.Popup.Types.ColorChooser.ControlClicked = function (id) { + var sp = SocialCalc.Popup; + var spt = sp.Types; + var sptc = spt.ColorChooser; + var spc = sp.Controls; + + var cid = sp.Current.id; + if (!cid || id != cid) { + sp.CClick(id); + return; + } + + sptc.CloseOK(); + }; + + SocialCalc.Popup.Types.ColorChooser.DefaultClicked = function (e) { + var event = e || window.event; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var sptc = spt.ColorChooser; + var spc = sp.Controls; + + var id = sp.Current.id; + if (!id) return; + + var spcdata = spc[id].data; + + spcdata.value = ""; + SocialCalc.Popup.SetValue(id, spcdata.value); + + SocialCalc.Popup.Close(); + }; + + SocialCalc.Popup.Types.ColorChooser.CustomClicked = function (e) { + var event = e || window.event; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var sptc = spt.ColorChooser; + var spc = sp.Controls; + + var id = sp.Current.id; + if (!id) return; + + var spcdata = spc[id].data; + + var oele, str, nele; + + oele = spcdata.contentele; + str = SocialCalc.Popup.Types.ColorChooser.MakeCustom("ColorChooser", id); + nele = document.createElement("div"); + nele.innerHTML = + '
' + + str + + "
"; + spcdata.customele = nele.firstChild.firstChild.childNodes[2]; + spcdata.contentele = nele; + spcdata.popupele.replaceChild(nele, oele); + + spcdata.customele.value = sp.RGBToHex(spcdata.value); + + if (spcdata.attribs.ensureWithin) { + SocialCalc.Popup.EnsurePosition(id, spcdata.attribs.ensureWithin); + } + }; + + SocialCalc.Popup.Types.ColorChooser.CustomToGrid = function (id) { + var oele, str, nele; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + SocialCalc.Popup.SetValue(id, sp.HexToRGB("#" + spcdata.customele.value)); + + var oele, mainele, nele; + + oele = spcdata.contentele; + mainele = SocialCalc.Popup.Types.ColorChooser.CreateGrid( + "ColorChooser", + id + ); + nele = document.createElement("div"); + nele.style.padding = "3px"; + nele.style.backgroundColor = "#CCC"; + nele.appendChild(mainele); + spcdata.customele = null; + spcdata.contentele = nele; + spcdata.popupele.replaceChild(nele, oele); + + if (spcdata.attribs.ensureWithin) { + SocialCalc.Popup.EnsurePosition(id, spcdata.attribs.ensureWithin); + } + }; + + SocialCalc.Popup.Types.ColorChooser.CustomOK = function (id) { + var i, c; + var sp = SocialCalc.Popup; + var spt = sp.Types; + var spc = sp.Controls; + var spcdata = spc[id].data; + + SocialCalc.Popup.SetValue(id, sp.HexToRGB("#" + spcdata.customele.value)); + + SocialCalc.Popup.Close(); + }; + + SocialCalc.Popup.Types.ColorChooser.CloseOK = function (e) { + var event = e || window.event; + + var sp = SocialCalc.Popup; + var spt = sp.Types; + var sptc = spt.ColorChooser; + var spc = sp.Controls; + + var id = sp.Current.id; + if (!id) return; + + var spcdata = spc[id].data; + + SocialCalc.Popup.SetValue(id, spcdata.value); + + SocialCalc.Popup.Close(); + }; + + + + // Make sure SocialCalc is available globally + if (typeof window !== "undefined") { + window.SocialCalc = SocialCalc; + } else if (typeof global !== "undefined") { + global.SocialCalc = SocialCalc; + } + + return SocialCalc; +}); diff --git a/src/components/socialcalc/core/index.js b/src/components/socialcalc/core/index.js new file mode 100644 index 0000000..2746ca9 --- /dev/null +++ b/src/components/socialcalc/core/index.js @@ -0,0 +1,28 @@ +/* eslint-disable */ +// SocialCalc Core Index - Imports all core modules +// This replaces the monolithic SocialCalc.js file + +// Import all core modules in dependency order +import "./constants.js"; // Must be first - defines constants +import "./core.js"; // Core functionality (Cell, Sheet, etc.) +import "./format-number.js"; // Number formatting +import "./formula.js"; // Formula parsing and calculation +import "./table-editor.js"; // Table editor and UI +import "./spreadsheet-control.js"; // High-level controls + +// The modules above all contribute to the global SocialCalc object +// which is available as window.SocialCalc or global.SocialCalc + +// Export the global SocialCalc for ES module compatibility +let SocialCalc; +if (typeof window !== "undefined" && window.SocialCalc) { + SocialCalc = window.SocialCalc; +} else if (typeof global !== "undefined" && global.SocialCalc) { + SocialCalc = global.SocialCalc; +} else { + console.error("SocialCalc not found after loading core modules"); + SocialCalc = {}; // Fallback +} + +export default SocialCalc; +export { SocialCalc }; diff --git a/src/components/socialcalc/core/spreadsheet-control.js b/src/components/socialcalc/core/spreadsheet-control.js new file mode 100644 index 0000000..34a0883 --- /dev/null +++ b/src/components/socialcalc/core/spreadsheet-control.js @@ -0,0 +1,7620 @@ +/* eslint-disable */ +// SocialCalc Spreadsheet Control Module +// Extracted from the main SocialCalc.js file + +// UMD wrapper +(function (root, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.SocialCalcSpreadsheetControl = factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + + // Get SocialCalc namespace + var SocialCalc; + if (typeof window !== "undefined" && window.SocialCalc) { + SocialCalc = window.SocialCalc; + } else if (typeof global !== "undefined" && global.SocialCalc) { + SocialCalc = global.SocialCalc; + } else { + SocialCalc = {}; + } + + + // + // SocialCalcSpreadsheetControl + // + /* +// The code module of the SocialCalc package that lets you embed a spreadsheet +// control with toolbar, etc., into a web page. +// +// (c) Copyright 2008, 2009, 2010 Socialtext, Inc. +// All Rights Reserved. +// +*/ + + /* + +LEGAL NOTICES REQUIRED BY THE COMMON PUBLIC ATTRIBUTION LICENSE: + +EXHIBIT A. Common Public Attribution License Version 1.0. + +The contents of this file are subject to the Common Public Attribution License Version 1.0 (the +"License"); you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://socialcalc.org. The License is based on the Mozilla Public License Version 1.1 but +Sections 14 and 15 have been added to cover use of software over a computer network and provide for +limited attribution for the Original Developer. In addition, Exhibit A has been modified to be +consistent with Exhibit B. + +Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY +KIND, either express or implied. See the License for the specific language governing rights and +limitations under the License. + +The Original Code is SocialCalc JavaScript SpreadsheetControl. + +The Original Developer is the Initial Developer. + +The Initial Developer of the Original Code is Socialtext, Inc. All portions of the code written by +Socialtext, Inc., are Copyright (c) Socialtext, Inc. All Rights Reserved. + +Contributor: Dan Bricklin. + + +EXHIBIT B. Attribution Information + +When the SpreadsheetControl is producing and/or controlling the display the Graphic Image must be +displayed on the screen visible to the user in a manner comparable to that in the +Original Code. The Attribution Phrase must be displayed as a "tooltip" or "hover-text" for +that image. The image must be linked to the Attribution URL so as to access that page +when clicked. If the user interface includes a prominent "about" display which includes +factual prominent attribution in a form similar to that in the "about" display included +with the Original Code, including Socialtext copyright notices and URLs, then the image +need not be linked to the Attribution URL but the "tool-tip" is still required. + +Attribution Copyright Notice: + + Copyright (C) 2010 Socialtext, Inc. + All Rights Reserved. + +Attribution Phrase (not exceeding 10 words): SocialCalc + +Attribution URL: http://www.socialcalc.org/ + +Graphic Image: The contents of the sc-logo.gif file in the Original Code or +a suitable replacement from http://www.socialcalc.org/licenses specified as +being for SocialCalc. + +Display of Attribution Information is required in Larger Works which are defined +in the CPAL as a work which combines Covered Code or portions thereof with code +not governed by the terms of the CPAL. + +*/ + + // + // Some of the other files in the SocialCalc package are licensed under + // different licenses. Please note the licenses of the modules you use. + // + // Code History: + // + // Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. + // Unless otherwise specified, referring to "SocialCalc" in comments refers to this + // JavaScript version of the code, not the SocialCalc Perl code. + // + + /* + +See the comments in the main SocialCalc code module file of the SocialCalc package. + +*/ + + var SocialCalc; + if (!SocialCalc) { + alert("Main SocialCalc code module needed"); + SocialCalc = {}; + } + if (!SocialCalc.TableEditor) { + alert("SocialCalc TableEditor code module needed"); + } + + // ************************************* + // + // SpreadsheetControl class: + // + // ************************************* + + // Global constants: + + SocialCalc.CurrentSpreadsheetControlObject = null; // right now there can only be one active at a time + + // Constructor: + + SocialCalc.SpreadsheetControl = function () { + var scc = SocialCalc.Constants; + + // Properties: + + this.parentNode = null; + this.spreadsheetDiv = null; + this.requestedHeight = 0; + this.requestedWidth = 0; + this.requestedSpaceBelow = 0; + this.height = 0; + this.width = 0; + this.viewheight = 0; // calculated amount for views below toolbar, etc. + + // Tab definitions: An array where each tab is an object of the form: + // + // name: "name", + // text: "text-on-tab", + // html: "html-to-create div", + // replacements: + // "%s.": "SocialCalc", "%id.": spreadsheet.idPrefix, "%tbt.": spreadsheet.toolbartext + // Other replacements from spreadsheet.tabreplacements: + // replacementname: {regex: regular-expression-to-match-with-g, replacement: string} + // view: "viewname", // view to show when selected; "sheet" or missing/null is spreadsheet + // oncreate: function(spreadsheet, tab-name), // called when first created to initialize + // onclick: function(spreadsheet, tab-name), missing/null is sheet default + // onclickFocus: text, // spreadsheet.idPrefix+text is given the focus if present instead of normal KeyboardFocus + // or if text isn't a string, that value (e.g., true) is used for SocialCalc.CmdGotFocus + // onunclick: function(spreadsheet, tab-name), missing/null is sheet default + + this.tabs = []; + this.tabnums = {}; // when adding tabs, add tab-name: array-index to this object + this.tabreplacements = {}; // see use above + this.currentTab = -1; // currently selected tab index in this.tabs or -1 (maintained by SocialCalc.SetTab) + + // View definitions: An object where each view is an object of the form: + // + // name: "name", // localized when first set using SocialCalc.LocalizeString + // element: node-in-the-dom, // filled in when initialized + // replacements: {}, // see below + // html: "html-to-create div", + // replacements: + // "%s.": "SocialCalc", "%id.": spreadsheet.idPrefix, "%tbt.": spreadsheet.toolbartext, "%img.": spreadsheet.imagePrefix, + // SocialCalc.LocalizeSubstring replacements ("%loc!string!" and "%ssc!constant-name!") + // Other replacements from viewobject.replacements: + // replacementname: {regex: regular-expression-to-match-with-g, replacement: string} + // divStyle: attributes for sheet div (SocialCalc.setStyles format) + // oncreate: function(spreadsheet, viewobject), // called when first created to initialize + // needsresize: true/false/null, // if true, do resize calc after displaying + // onresize: function(spreadsheet, viewobject), // called if needs resize + // values: {} // optional values to share with onclick handlers, etc. + // + // There is always a "sheet" view. + + this.views = {}; // {viewname: view-object, ...} + + // Dynamic properties: + + this.sheet = null; + this.context = null; + this.editor = null; + + this.spreadsheetDiv = null; + this.editorDiv = null; + + this.sortrange = ""; // remembered range for sort tab + + this.moverange = ""; // remembered range from movefrom used by movepaste/moveinsert + + // Constants: + + this.idPrefix = "SocialCalc-"; // prefix added to element ids used here, should end in "-" + this.multipartBoundary = "SocialCalcSpreadsheetControlSave"; // boundary used by SpreadsheetControlCreateSpreadsheetSave + this.imagePrefix = scc.defaultImagePrefix; // prefix added to img src + + this.toolbarbackground = scc.SCToolbarbackground; + this.tabbackground = scc.SCTabbackground; // "background-color:#CCC;"; + this.tabselectedCSS = scc.SCTabselectedCSS; + this.tabplainCSS = scc.SCTabplainCSS; + this.toolbartext = scc.SCToolbartext; + + this.formulabarheight = scc.SCFormulabarheight; // in pixels, will contain a text input box + + if (scc.doWorkBook) { + this.sheetbarheight = scc.SCSheetBarHeight; + this.sheetbarCSS = scc.SCSheetBarCSS; + } else { + this.sheetbarheight = 0; + } + + this.statuslineheight = scc.SCStatuslineheight; // in pixels + this.statuslineCSS = scc.SCStatuslineCSS; + + // Callbacks: + + this.ExportCallback = null; // a function called for Clipboard Export button: this.ExportCallback(spreadsheet_control_object) + + // Initialization Code: + + this.sheet = new SocialCalc.Sheet(); + this.context = new SocialCalc.RenderContext(this.sheet); + this.context.showGrid = true; + this.context.showRCHeaders = true; + this.editor = new SocialCalc.TableEditor(this.context); + this.editor.StatusCallback.statusline = { + func: SocialCalc.SpreadsheetControlStatuslineCallback, + params: { + statuslineid: this.idPrefix + "statusline", + recalcid1: this.idPrefix + "divider_recalc", + recalcid2: this.idPrefix + "button_recalc", + }, + }; + + SocialCalc.CurrentSpreadsheetControlObject = this; // remember this for rendezvousing on events + + this.editor.MoveECellCallback.movefrom = function (editor) { + var cr; + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + spreadsheet.context.cursorsuffix = ""; + if (editor.range2.hasrange && !editor.cellhandles.noCursorSuffix) { + if ( + editor.ecell.row == editor.range2.top && + (editor.ecell.col < editor.range2.left || + editor.ecell.col > editor.range2.right + 1) + ) { + spreadsheet.context.cursorsuffix = "insertleft"; + } + if ( + editor.ecell.col == editor.range2.left && + (editor.ecell.row < editor.range2.top || + editor.ecell.row > editor.range2.bottom + 1) + ) { + spreadsheet.context.cursorsuffix = "insertup"; + } + } + }; + + // formula bar buttons + + this.formulabuttons = { + formulafunctions: { + image: "formuladialog.gif", + tooltip: "Functions", // tooltips are localized when set below + command: SocialCalc.SpreadsheetControl.DoFunctionList, + }, + multilineinput: { + image: "multilinedialog.gif", + tooltip: "Multi-line Input Box", + command: SocialCalc.SpreadsheetControl.DoMultiline, + }, + link: { + image: "linkdialog.gif", + tooltip: "Link Input Box", + command: SocialCalc.SpreadsheetControl.DoLink, + }, + sum: { + image: "sumdialog.gif", + tooltip: "Auto Sum", + command: SocialCalc.SpreadsheetControl.DoSum, + }, + /* image: {image: "sumdialog.gif", tooltip: "Insert", + command: SocialCalc.Images.Insert }*/ + }; + + // Default tabs: + + // Edit + + this.tabnums.edit = this.tabs.length; + this.tabs.push({ + name: "edit", + text: "Edit", + html: + '
' + + ' ' + + ' ' + + '    ' + + '' + + ' ' + + ' ' + + '    ' + + '' + + ' ' + + '    ' + + '' + + ' ' + + '    ' + + '' + + ' ' + + ' ' + + '    ' + + '' + + ' ' + + ' ' + + '    ' + + ' ' + + ' ' + + ' ' + + '    ' + + ' ' + + ' ' + + '    ' + + ' ' + + ' ' + + '  ' + + ' ' + + '    ' + + ' ' + + "
", + oncreate: null, //function(spreadsheet, viewobject) {SocialCalc.DoCmd(null, "fill-rowcolstuff");}, + onclick: null, + }); + + // Settings (Format) + + this.tabnums.settings = this.tabs.length; + this.tabs.push({ + name: "settings", + text: "Format", + html: + '", + view: "settings", + onclick: function (s, t) { + SocialCalc.SettingsControls.idPrefix = s.idPrefix; // used to get color chooser div + SocialCalc.SettingControlReset(); + var sheetattribs = s.sheet.EncodeSheetAttributes(); + var cellattribs = s.sheet.EncodeCellAttributes(s.editor.ecell.coord); + SocialCalc.SettingsControlLoadPanel( + s.views.settings.values.sheetspanel, + sheetattribs + ); + SocialCalc.SettingsControlLoadPanel( + s.views.settings.values.cellspanel, + cellattribs + ); + document.getElementById(s.idPrefix + "settingsecell").innerHTML = + s.editor.ecell.coord; + SocialCalc.SpreadsheetControlSettingsSwitch("cell"); + s.views.settings.element.style.height = s.viewheight + "px"; + s.views.settings.element.firstChild.style.height = s.viewheight + "px"; + + var range; // set save message + if (s.editor.range.hasrange) { + range = + SocialCalc.crToCoord(s.editor.range.left, s.editor.range.top) + + ":" + + SocialCalc.crToCoord(s.editor.range.right, s.editor.range.bottom); + } else { + range = s.editor.ecell.coord; + } + document.getElementById(s.idPrefix + "settings-savecell").value = + SocialCalc.LocalizeString("Save to") + ": " + range; + }, + onclickFocus: true, + }); + + this.views["settings"] = { + name: "settings", + values: {}, + oncreate: function (s, viewobj) { + var scc = SocialCalc.Constants; + + viewobj.values.sheetspanel = { + // name: "sheet", + colorchooser: { id: s.idPrefix + "scolorchooser" }, + formatnumber: { + setting: "numberformat", + type: "PopupList", + id: s.idPrefix + "formatnumber", + initialdata: scc.SCFormatNumberFormats, + }, + formattext: { + setting: "textformat", + type: "PopupList", + id: s.idPrefix + "formattext", + initialdata: scc.SCFormatTextFormats, + }, + fontfamily: { + setting: "fontfamily", + type: "PopupList", + id: s.idPrefix + "fontfamily", + initialdata: scc.SCFormatFontfamilies, + }, + fontlook: { + setting: "fontlook", + type: "PopupList", + id: s.idPrefix + "fontlook", + initialdata: scc.SCFormatFontlook, + }, + fontsize: { + setting: "fontsize", + type: "PopupList", + id: s.idPrefix + "fontsize", + initialdata: scc.SCFormatFontsizes, + }, + textalignhoriz: { + setting: "textalignhoriz", + type: "PopupList", + id: s.idPrefix + "textalignhoriz", + initialdata: scc.SCFormatTextAlignhoriz, + }, + numberalignhoriz: { + setting: "numberalignhoriz", + type: "PopupList", + id: s.idPrefix + "numberalignhoriz", + initialdata: scc.SCFormatNumberAlignhoriz, + }, + alignvert: { + setting: "alignvert", + type: "PopupList", + id: s.idPrefix + "alignvert", + initialdata: scc.SCFormatAlignVertical, + }, + textcolor: { + setting: "textcolor", + type: "ColorChooser", + id: s.idPrefix + "textcolor", + }, + bgcolor: { + setting: "bgcolor", + type: "ColorChooser", + id: s.idPrefix + "bgcolor", + }, + padtop: { + setting: "padtop", + type: "PopupList", + id: s.idPrefix + "padtop", + initialdata: scc.SCFormatPadsizes, + }, + padright: { + setting: "padright", + type: "PopupList", + id: s.idPrefix + "padright", + initialdata: scc.SCFormatPadsizes, + }, + padbottom: { + setting: "padbottom", + type: "PopupList", + id: s.idPrefix + "padbottom", + initialdata: scc.SCFormatPadsizes, + }, + padleft: { + setting: "padleft", + type: "PopupList", + id: s.idPrefix + "padleft", + initialdata: scc.SCFormatPadsizes, + }, + colwidth: { + setting: "colwidth", + type: "PopupList", + id: s.idPrefix + "colwidth", + initialdata: scc.SCFormatColwidth, + }, + recalc: { + setting: "recalc", + type: "PopupList", + id: s.idPrefix + "recalc", + initialdata: scc.SCFormatRecalc, + }, + }; + viewobj.values.cellspanel = { + name: "cell", + colorchooser: { id: s.idPrefix + "scolorchooser" }, + cformatnumber: { + setting: "numberformat", + type: "PopupList", + id: s.idPrefix + "cformatnumber", + initialdata: scc.SCFormatNumberFormats, + }, + cformattext: { + setting: "textformat", + type: "PopupList", + id: s.idPrefix + "cformattext", + initialdata: scc.SCFormatTextFormats, + }, + cfontfamily: { + setting: "fontfamily", + type: "PopupList", + id: s.idPrefix + "cfontfamily", + initialdata: scc.SCFormatFontfamilies, + }, + cfontlook: { + setting: "fontlook", + type: "PopupList", + id: s.idPrefix + "cfontlook", + initialdata: scc.SCFormatFontlook, + }, + cfontsize: { + setting: "fontsize", + type: "PopupList", + id: s.idPrefix + "cfontsize", + initialdata: scc.SCFormatFontsizes, + }, + calignhoriz: { + setting: "alignhoriz", + type: "PopupList", + id: s.idPrefix + "calignhoriz", + initialdata: scc.SCFormatTextAlignhoriz, + }, + calignvert: { + setting: "alignvert", + type: "PopupList", + id: s.idPrefix + "calignvert", + initialdata: scc.SCFormatAlignVertical, + }, + ctextcolor: { + setting: "textcolor", + type: "ColorChooser", + id: s.idPrefix + "ctextcolor", + }, + cbgcolor: { + setting: "bgcolor", + type: "ColorChooser", + id: s.idPrefix + "cbgcolor", + }, + cbt: { setting: "bt", type: "BorderSide", id: s.idPrefix + "cbt" }, + cbr: { setting: "br", type: "BorderSide", id: s.idPrefix + "cbr" }, + cbb: { setting: "bb", type: "BorderSide", id: s.idPrefix + "cbb" }, + cbl: { setting: "bl", type: "BorderSide", id: s.idPrefix + "cbl" }, + cpadtop: { + setting: "padtop", + type: "PopupList", + id: s.idPrefix + "cpadtop", + initialdata: scc.SCFormatPadsizes, + }, + cpadright: { + setting: "padright", + type: "PopupList", + id: s.idPrefix + "cpadright", + initialdata: scc.SCFormatPadsizes, + }, + cpadbottom: { + setting: "padbottom", + type: "PopupList", + id: s.idPrefix + "cpadbottom", + initialdata: scc.SCFormatPadsizes, + }, + cpadleft: { + setting: "padleft", + type: "PopupList", + id: s.idPrefix + "cpadleft", + initialdata: scc.SCFormatPadsizes, + }, + }; + + SocialCalc.SettingsControlInitializePanel(viewobj.values.sheetspanel); + SocialCalc.SettingsControlInitializePanel(viewobj.values.cellspanel); + }, + replacements: { + itemtitle: { + regex: /\%itemtitle\./g, + replacement: + 'style="padding:12px 10px 0px 10px;font-weight:bold;text-align:right;vertical-align:top;font-size:small;"', + }, + sectiontitle: { + regex: /\%sectiontitle\./g, + replacement: + 'style="padding:16px 10px 0px 0px;font-weight:bold;vertical-align:top;font-size:small;color:#C00;"', + }, + parttitle: { + regex: /\%parttitle\./g, + replacement: + 'style="font-weight:bold;font-size:x-small;padding:0px 0px 3px 0px;"', + }, + itembody: { + regex: /\%itembody\./g, + replacement: + 'style="padding:12px 0px 0px 0px;vertical-align:top;font-size:small;"', + }, + bodypart: { + regex: /\%bodypart\./g, + replacement: + 'style="padding:0px 10px 0px 0px;font-size:small;vertical-align:top;"', + }, + }, + divStyle: "border:1px solid black;overflow:auto;", + html: + '' + + '' + + '
' + + '' + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + '' + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "" + + " " + + " " + + "" + + "

%loc!Format!:
" + + ' ' + + " " + + " " + + "
" + + "
%loc!Number!
" + + ' ' + + "
" + + "
%loc!Text!
" + + ' ' + + "
" + + "

%loc!Alignment!:
" + + ' ' + + " " + + " " + + "
" + + "
%loc!Horizontal!
" + + ' ' + + "
" + + "
%loc!Vertical!
" + + ' ' + + "
" + + "

%loc!Font!:
" + + ' ' + + " " + + " " + + " " + + " " + + " " + + "
" + + "
%loc!Family!
" + + ' ' + + "
" + + "
%loc!Bold & Italics!
" + + ' ' + + "
" + + "
%loc!Size!
" + + ' ' + + "
" + + "
%loc!Color!
" + + '
' + + "
" + + "
%loc!Background!
" + + '
' + + "
" + + "

%loc!Borders!:
" + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + " " + + "
%loc!Top Border!
%loc!Right Border!
%loc!Bottom Border!
%loc!Left Border!
" + + ' ' + + " " + + '
' + + "
    " + + ' ' + + " " + + '
' + + "
    " + + ' ' + + " " + + '
' + + "
    " + + ' ' + + " " + + '
' + + "
    
" + + "

%loc!Padding!:
" + + ' ' + + " " + + " " + + " " + + " " + + "
" + + "
%loc!Top!
" + + ' ' + + "
" + + "
%loc!Right!
" + + ' ' + + "
" + + "
%loc!Bottom!
" + + ' ' + + "
" + + "
%loc!Left!
" + + ' ' + + "
" + + "
" + + '
' + + '
' + + ' ' + + ' ' + + "
%loc!This is a
sample!
-1234.5
" + + "
" + + "
" + + "
", + }; + + // Sort + + this.tabnums.sort = this.tabs.length; + this.tabs.push({ + name: "sort", + text: "Sort", + html: + ' ", + onclick: SocialCalc.SpreadsheetControlSortOnclick, + }); + this.editor.SettingsCallbacks.sort = { + save: SocialCalc.SpreadsheetControlSortSave, + load: SocialCalc.SpreadsheetControlSortLoad, + }; + + // Audit + + this.tabnums.audit = this.tabs.length; + this.tabs.push({ + name: "audit", + text: "Audit", + html: + '", + view: "audit", + onclick: function (s, t) { + var SCLoc = SocialCalc.LocalizeString; + var i, j; + var str = + '
' + + SCLoc("Audit Trail This Session") + + ":

"; + var stack = s.sheet.changes.stack; + var tos = s.sheet.changes.tos; + for (i = 0; i < stack.length; i++) { + if (i == tos + 1) + str += + '
' + + SCLoc("UNDONE STEPS") + + ":
"; + for (j = 0; j < stack[i].command.length; j++) { + str += SocialCalc.special_chars(stack[i].command[j]) + "
"; + } + } + s.views.audit.element.innerHTML = str + "
"; + SocialCalc.CmdGotFocus(true); + }, + onclickFocus: true, + }); + + this.views["audit"] = { + name: "audit", + divStyle: "border:1px solid black;overflow:auto;", + html: "Audit Trail", + }; + + // Comment + + this.tabnums.comment = this.tabs.length; + this.tabs.push({ + name: "comment", + text: "Comment", + html: + '", + view: "sheet", + onclick: SocialCalc.SpreadsheetControlCommentOnclick, + onunclick: SocialCalc.SpreadsheetControlCommentOnunclick, + }); + + // Names + + this.tabnums.names = this.tabs.length; + this.tabs.push({ + name: "names", + text: "Names", + html: + '", + view: "sheet", + onclick: SocialCalc.SpreadsheetControlNamesOnclick, + onunclick: SocialCalc.SpreadsheetControlNamesOnunclick, + }); + + // Clipboard + + this.tabnums.clipboard = this.tabs.length; + this.tabs.push({ + name: "clipboard", + text: "Clipboard", + html: + '", + view: "clipboard", + onclick: SocialCalc.SpreadsheetControlClipboardOnclick, + onclickFocus: "clipboardtext", + }); + + this.views["clipboard"] = { + name: "clipboard", + divStyle: "overflow:auto;", + html: + '
' + + " %loc!Display Clipboard in!:" + + ' %loc!Tab-delimited format!  ' + + ' %loc!CSV format!  ' + + ' %loc!SocialCalc-save format!' + + "
" + + '   ' + + '   ' + + "
" + + ' ', + }; + + return; + }; + + // Methods: + + SocialCalc.SpreadsheetControl.prototype.InitializeSpreadsheetControl = + function (node, height, width, spacebelow) { + return SocialCalc.InitializeSpreadsheetControl( + this, + node, + height, + width, + spacebelow + ); + }; + SocialCalc.SpreadsheetControl.prototype.DoOnResize = function () { + return SocialCalc.DoOnResize(this); + }; + SocialCalc.SpreadsheetControl.prototype.SizeSSDiv = function () { + return SocialCalc.SizeSSDiv(this); + }; + SocialCalc.SpreadsheetControl.prototype.ExecuteCommand = function ( + combostr, + sstr + ) { + return SocialCalc.SpreadsheetControlExecuteCommand(this, combostr, sstr); + }; + SocialCalc.SpreadsheetControl.prototype.CreateSheetHTML = function () { + return SocialCalc.SpreadsheetControlCreateSheetHTML(this); + }; + SocialCalc.SpreadsheetControl.prototype.CreateSpreadsheetSave = function ( + otherparts + ) { + return SocialCalc.SpreadsheetControlCreateSpreadsheetSave(this, otherparts); + }; + SocialCalc.SpreadsheetControl.prototype.DecodeSpreadsheetSave = function ( + str + ) { + return SocialCalc.SpreadsheetControlDecodeSpreadsheetSave(this, str); + }; + SocialCalc.SpreadsheetControl.prototype.CreateCellHTML = function (coord) { + return SocialCalc.SpreadsheetControlCreateCellHTML(this, coord); + }; + SocialCalc.SpreadsheetControl.prototype.CreateCellHTMLSave = function ( + range + ) { + return SocialCalc.SpreadsheetControlCreateCellHTMLSave(this, range); + }; + + // Sheet Methods to make things a little easier: + + SocialCalc.SpreadsheetControl.prototype.ParseSheetSave = function (str) { + return this.sheet.ParseSheetSave(str); + }; + SocialCalc.SpreadsheetControl.prototype.CreateSheetSave = function () { + return this.sheet.CreateSheetSave(); + }; + + // Functions: + + // + // InitializeSpreadsheetControl(spreadsheet, node, height, width, spacebelow) + // + // Creates the control elements and makes them the child of node (string or element). + // If present, height and width specify size. + // If either is 0 or null (missing), the maximum that fits on the screen + // (taking spacebelow into account) is used. + // + // Displays the tabs and creates the views (other than "sheet"). + // The first tab is set as selected, but onclick is not invoked. + // + // You should do a redisplay or recalc (which redisplays) after running this. + // + + SocialCalc.InitializeSpreadsheetControl = function ( + spreadsheet, + node, + height, + width, + spacebelow + ) { + var scc = SocialCalc.Constants; + var SCLoc = SocialCalc.LocalizeString; + var SCLocSS = SocialCalc.LocalizeSubstrings; + + var html, child, i, vname, v, style, button, bele; + var tabs = spreadsheet.tabs; + var views = spreadsheet.views; + + spreadsheet.requestedHeight = height; + spreadsheet.requestedWidth = width; + spreadsheet.requestedSpaceBelow = spacebelow; + + if (typeof node == "string") node = document.getElementById(node); + + if (node == null) { + alert("SocialCalc.SpreadsheetControl not given parent node."); + } + + spreadsheet.parentNode = node; + + // create node to hold spreadsheet control + + spreadsheet.spreadsheetDiv = document.createElement("div"); + + spreadsheet.SizeSSDiv(); // calculate and fill in the size values + + for (child = node.firstChild; child != null; child = node.firstChild) { + node.removeChild(child); + } + + // create the tabbed UI at the top + + html = + '
'; + + for (i = 0; i < tabs.length; i++) { + html += tabs[i].html; + } + + html += + "
" + + '
' + + ''; + + for (i = 0; i < tabs.length; i++) { + html += + ' "; + } + + html += "
' + + SCLoc(tabs[i].text) + + "
"; + + spreadsheet.currentTab = 0; // this is where we started + + for (style in spreadsheet.tabreplacements) { + html = html.replace( + spreadsheet.tabreplacements[style].regex, + spreadsheet.tabreplacements[style].replacement + ); + } + html = html.replace(/\%s\./g, "SocialCalc."); + html = html.replace(/\%id\./g, spreadsheet.idPrefix); + html = html.replace(/\%tbt\./g, spreadsheet.toolbartext); + html = html.replace(/\%img\./g, spreadsheet.imagePrefix); + + html = SCLocSS(html); // localize with %loc!string! and %scc!constant! + + spreadsheet.spreadsheetDiv.innerHTML = html; + + node.appendChild(spreadsheet.spreadsheetDiv); + + // Initialize SocialCalc buttons + + spreadsheet.Buttons = { + button_undo: { tooltip: "Undo", command: "undo" }, + button_redo: { tooltip: "Redo", command: "redo" }, + button_copy: { tooltip: "Copy", command: "copy" }, + button_cut: { tooltip: "Cut", command: "cut" }, + button_paste: { tooltip: "Paste", command: "paste" }, + button_pasteformats: { + tooltip: "Paste Formats", + command: "pasteformats", + }, + button_delete: { tooltip: "Delete Contents", command: "delete" }, + button_filldown: { tooltip: "Fill Down", command: "filldown" }, + button_fillright: { tooltip: "Fill Right", command: "fillright" }, + button_movefrom: { tooltip: "Set/Clear Move From", command: "movefrom" }, + button_movepaste: { tooltip: "Move Paste", command: "movepaste" }, + button_moveinsert: { tooltip: "Move Insert", command: "moveinsert" }, + button_alignleft: { tooltip: "Align Left", command: "align-left" }, + button_aligncenter: { tooltip: "Align Center", command: "align-center" }, + button_alignright: { tooltip: "Align Right", command: "align-right" }, + button_borderon: { tooltip: "Borders On", command: "borderon" }, + button_borderoff: { tooltip: "Borders Off", command: "borderoff" }, + button_swapcolors: { tooltip: "Swap Colors", command: "swapcolors" }, + button_merge: { tooltip: "Merge Cells", command: "merge" }, + button_unmerge: { tooltip: "Unmerge Cells", command: "unmerge" }, + button_insertrow: { tooltip: "Insert Row", command: "insertrow" }, + button_insertcol: { tooltip: "Insert Column", command: "insertcol" }, + button_deleterow: { tooltip: "Delete Row", command: "deleterow" }, + button_deletecol: { tooltip: "Delete Column", command: "deletecol" }, + button_recalc: { tooltip: "Recalc", command: "recalc" }, + }; + + for (button in spreadsheet.Buttons) { + bele = document.getElementById(spreadsheet.idPrefix + button); + if (!bele) { + /*alert("Button "+(spreadsheet.idPrefix+button)+" missing");*/ continue; + } + bele.style.border = "1px solid " + scc.ISCButtonBorderNormal; + SocialCalc.TooltipRegister( + bele, + SCLoc(spreadsheet.Buttons[button].tooltip), + {} + ); + SocialCalc.ButtonRegister( + bele, + { + normalstyle: + "border:1px solid " + + scc.ISCButtonBorderNormal + + ";backgroundColor:" + + scc.ISCButtonBorderNormal + + ";", + hoverstyle: + "border:1px solid " + + scc.ISCButtonBorderHover + + ";backgroundColor:" + + scc.ISCButtonBorderNormal + + ";", + downstyle: + "border:1px solid " + + scc.ISCButtonBorderDown + + ";backgroundColor:" + + scc.ISCButtonDownBackground + + ";", + }, + { + MouseDown: SocialCalc.DoButtonCmd, + command: spreadsheet.Buttons[button].command, + } + ); + } + + // create formula bar + + spreadsheet.dummyFormulaDiv = document.createElement("div"); + spreadsheet.dummyFormulaDiv.style.height = + spreadsheet.formulabarheight + "px"; + spreadsheet.spreadsheetDiv.appendChild(spreadsheet.dummyFormulaDiv); + + spreadsheet.formulabarDiv = document.createElement("div"); + spreadsheet.formulabarDiv.id = "formulabardiv"; + spreadsheet.formulabarDiv.style.height = + spreadsheet.formulabarheight + "px"; + spreadsheet.formulabarDiv.innerHTML = + ''; //' '; + //spreadsheet.spreadsheetDiv.appendChild(spreadsheet.formulabarDiv); + var inputbox = new SocialCalc.InputBox( + spreadsheet.formulabarDiv.firstChild, + spreadsheet.editor + ); + + bele = document.createElement("img"); + bele.id = "testtest"; + bele.src = "lib/aspiring/www/assets/images/delete24.png"; + bele.style.verticalAlign = "middle"; + //bele.style.border = "1px solid #FFF"; + //bele.style.marginLeft = "4px"; + bele.style.display = "none"; + SocialCalc.ButtonRegister( + bele, + { normalstyle: "", hoverstyle: "", downstyle: "" }, + { MouseDown: SocialCalc.InputLineClearText } + ); + + // spreadsheet.formulabarDiv.appendChild(bele); + + /* + for (button in spreadsheet.formulabuttons) { + bele = document.createElement("img"); + bele.id = spreadsheet.idPrefix+button; + bele.src = spreadsheet.imagePrefix+spreadsheet.formulabuttons[button].image; + bele.style.verticalAlign = "middle"; + bele.style.border = "1px solid #FFF"; + bele.style.marginLeft = "4px"; + SocialCalc.TooltipRegister(bele, SCLoc(spreadsheet.formulabuttons[button].tooltip), {}); + SocialCalc.ButtonRegister(bele, + {normalstyle: "border:1px solid #FFF;backgroundColor:#FFF;", + hoverstyle: "border:1px solid #CCC;backgroundColor:#FFF;", + downstyle: "border:1px solid #000;backgroundColor:#FFF;"}, + {MouseDown: spreadsheet.formulabuttons[button].command}); + spreadsheet.formulabarDiv.appendChild(bele); + } + */ + // initialize tabs that need it + + for (i = 0; i < tabs.length; i++) { + // execute any tab-specific initialization code + if (tabs[i].oncreate) { + tabs[i].oncreate(spreadsheet, tabs[i].name); + } + } + + // create sheet view and others + if (!scc.doWorkBook) { + spreadsheet.nonviewheight = + spreadsheet.statuslineheight + + spreadsheet.spreadsheetDiv.firstChild.offsetHeight + + spreadsheet.spreadsheetDiv.lastChild.offsetHeight; + } else { + spreadsheet.nonviewheight = + 28 + + spreadsheet.sheetbarheight + + spreadsheet.spreadsheetDiv.firstChild.offsetHeight + + spreadsheet.spreadsheetDiv.lastChild.offsetHeight; + } + spreadsheet.viewheight = spreadsheet.height - spreadsheet.nonviewheight; + spreadsheet.editorDiv = spreadsheet.editor.CreateTableEditor( + spreadsheet.width, + spreadsheet.viewheight + ); + + spreadsheet.spreadsheetDiv.appendChild(spreadsheet.editorDiv); + + for (vname in views) { + html = views[vname].html; + for (style in views[vname].replacements) { + html = html.replace( + views[vname].replacements[style].regex, + views[vname].replacements[style].replacement + ); + } + html = html.replace(/\%s\./g, "SocialCalc."); + html = html.replace(/\%id\./g, spreadsheet.idPrefix); + html = html.replace(/\%tbt\./g, spreadsheet.toolbartext); + html = html.replace(/\%img\./g, spreadsheet.imagePrefix); + v = document.createElement("div"); + SocialCalc.setStyles(v, views[vname].divStyle); + v.style.display = "none"; + v.style.width = spreadsheet.width + "px"; + v.style.height = spreadsheet.viewheight + "px"; + + html = SCLocSS(html); // localize with %loc!string!, etc. + + v.innerHTML = html; + spreadsheet.spreadsheetDiv.appendChild(v); + views[vname].element = v; + if (views[vname].oncreate) { + views[vname].oncreate(spreadsheet, views[vname]); + } + } + + views.sheet = { name: "sheet", element: spreadsheet.editorDiv }; + + // create statusline + + if (!scc.doWorkBook) { + spreadsheet.statuslineDiv = document.createElement("div"); + spreadsheet.statuslineDiv.style.cssText = spreadsheet.statuslineCSS; + // spreadsheet.statuslineDiv.style.height = spreadsheet.statuslineheight + "px"; // didn't take padding into account! + spreadsheet.statuslineDiv.style.height = + spreadsheet.statuslineheight - + (spreadsheet.statuslineDiv.style.paddingTop.slice(0, -2) - 0) - + (spreadsheet.statuslineDiv.style.paddingBottom.slice(0, -2) - 0) + + "px"; + spreadsheet.statuslineDiv.id = spreadsheet.idPrefix + "statusline"; + spreadsheet.spreadsheetDiv.appendChild(spreadsheet.statuslineDiv); + } else { + SocialCalc.CreateSheetStatusBar(spreadsheet, scc); + } + + // done - refresh screen needed + + return; + }; + + SocialCalc.CreateSheetStatusBar = function (spreadsheet, scc) { + // create sheetbar + if (!scc.doWorkBook) { + return; + } + + // create a table with 1 row, containing 3 columns, 1 for sheetbar, 1 for separator, 1 for statusline + + spreadsheet.sheetstatusbarDiv = document.createElement("div"); + spreadsheet.sheetstatusbarDiv.style.height = + spreadsheet.sheetbarheight + 3 + "px"; + spreadsheet.sheetstatusbarDiv.style.backgroundColor = "#CCC"; + spreadsheet.sheetstatusbarDiv.id = spreadsheet.idPrefix + "sheetstatusbar"; + + spreadsheet.sheetbarDiv = document.createElement("div"); + //spreadsheet.sheetbarDiv.style.cssText = spreadsheet.sheetbarCSS; + spreadsheet.sheetbarDiv.id = spreadsheet.idPrefix + "sheetbar"; + + spreadsheet.statuslineDiv = document.createElement("div"); + spreadsheet.statuslineDiv.style.cssText = spreadsheet.statuslineCSS; + spreadsheet.statuslineDiv.id = spreadsheet.idPrefix + "statusline"; + + var table = document.createElement("table"); + spreadsheet.sheetstatusbartable = table; + table.cellSpacing = 0; + table.cellPadding = 0; + table.width = "100%"; + + var tbody = document.createElement("tbody"); + table.appendChild(tbody); + + var tr = document.createElement("tr"); + tbody.appendChild(tr); + var td = document.createElement("td"); + td.appendChild(spreadsheet.sheetbarDiv); + td.width = scc.SCSheetBarWidth; + tr.appendChild(td); + + td = document.createElement("td"); + td.innerHTML = " | "; + td.width = "1%"; + tr.appendChild(td); + + td = document.createElement("td"); + td.appendChild(spreadsheet.statuslineDiv); + tr.appendChild(td); + + spreadsheet.sheetstatusbarDiv.appendChild(table); + + spreadsheet.spreadsheetDiv.appendChild(spreadsheet.sheetstatusbarDiv); + + spreadsheet.sheetstatusbarDiv.style.display = "none"; + }; + + // + // outstr = SocialCalc.LocalizeString(str) + // + // SocialCalc function to make localization easier. + // If str is "Text to localize", it returns + // SocialCalc.Constants.s_loc_text_to_localize if + // it exists, or else with just "Text to localize". + // Note that spaces are replaced with "_" and other special + // chars with "X" in the name of the constant (e.g., "A & B" + // would look for SocialCalc.Constants.s_loc_a_X_b. + // + + SocialCalc.LocalizeString = function (str) { + var cstr = SocialCalc.LocalizeStringList[str]; // found already this session? + if (!cstr) { + // no - look up + cstr = + SocialCalc.Constants[ + "s_loc_" + str.toLowerCase().replace(/\s/g, "_").replace(/\W/g, "X") + ] || str; + SocialCalc.LocalizeStringList[str] = cstr; + } + return cstr; + }; + + SocialCalc.LocalizeStringList = {}; // a list of strings to localize accumulated by the routine + + // + // outstr = SocialCalc.LocalizeSubstrings(str) + // + // SocialCalc function to make localization easier using %loc and %scc. + // + // Replaces sections of str with: + // %loc!Text to localize! + // with SocialCalc.Constants.s_loc_text_to_localize if + // it exists, or else with just "Text to localize". + // Note that spaces are replaced with "_" and other special + // chars with "X" in the name of the constant (e.g., %loc!A & B! + // would look for SocialCalc.Constants.s_loc_a_X_b. + // Uses SocialCalc.LocalizeString for this. + // + // Replaces sections of str with: + // %ssc!constant-name! + // with SocialCalc.Constants.constant-name. + // If the constant doesn't exist, throws and alert. + // + + SocialCalc.LocalizeSubstrings = function (str) { + var SCLoc = SocialCalc.LocalizeString; + + return str.replace(/%(loc|ssc)!(.*?)!/g, function (a, t, c) { + if (t == "ssc") { + return SocialCalc.Constants[c] || alert("Missing constant: " + c); + } else { + return SCLoc(c); + } + }); + }; + + // + // obj = GetSpreadsheetControlObject() + // + // Returns the current spreadsheet control object + // + + SocialCalc.GetSpreadsheetControlObject = function () { + var csco = SocialCalc.CurrentSpreadsheetControlObject; + if (csco) return csco; + + // throw ("No current SpreadsheetControl object."); + }; + + // + // SocialCalc.DoOnResize(spreadsheet) + // + // Processes an onResize event, setting the different views. + // + + SocialCalc.DoOnResize = function (spreadsheet) { + var v; + var views = spreadsheet.views; + + var needresize = spreadsheet.SizeSSDiv(); + if (!needresize) return; + + for (vname in views) { + v = views[vname].element; + v.style.width = spreadsheet.width + "px"; + v.style.height = spreadsheet.height - spreadsheet.nonviewheight + "px"; + } + + spreadsheet.editor.ResizeTableEditor( + spreadsheet.width, + spreadsheet.height - spreadsheet.nonviewheight + ); + }; + + // + // resized = SocialCalc.SizeSSDiv(spreadsheet) + // + // Figures out a reasonable size for the spreadsheet, given any requested values and viewport. + // Sets ssdiv to that. + // Return true if different than existing values. + // + + SocialCalc.SizeSSDiv = function (spreadsheet) { + var sizes, pos, resized, nodestyle, newval; + var fudgefactorX = 10; // for IE + var fudgefactorY = 10; + + resized = false; + + sizes = SocialCalc.GetViewportInfo(); + pos = SocialCalc.GetElementPosition(spreadsheet.parentNode); + pos.bottom = 0; + pos.right = 0; + + nodestyle = spreadsheet.parentNode.style; + + if (nodestyle.marginTop) { + pos.top += nodestyle.marginTop.slice(0, -2) - 0; + } + if (nodestyle.marginBottom) { + pos.bottom += nodestyle.marginBottom.slice(0, -2) - 0; + } + if (nodestyle.marginLeft) { + pos.left += nodestyle.marginLeft.slice(0, -2) - 0; + } + if (nodestyle.marginRight) { + pos.right += nodestyle.marginRight.slice(0, -2) - 0; + } + + newval = + spreadsheet.requestedHeight || + sizes.height - + (pos.top + pos.bottom + fudgefactorY) - + (spreadsheet.requestedSpaceBelow || 0); + if (spreadsheet.height != newval) { + spreadsheet.height = newval; + spreadsheet.spreadsheetDiv.style.height = newval + "px"; + resized = true; + } + newval = + spreadsheet.requestedWidth || + sizes.width - (pos.left + pos.right + fudgefactorX) || + 700; + if (spreadsheet.width != newval) { + spreadsheet.width = newval; + spreadsheet.spreadsheetDiv.style.width = newval + "px"; + resized = true; + } + + return resized; + }; + + // + // SocialCalc.SetTab(obj) + // + // The obj argument is either a string with the tab name or a DOM element with an ID + // + + SocialCalc.SetTab = function (obj) { + var newtab, tname, newtabnum, newview, i, vname, ele; + var menutabs = {}; + var tools = {}; + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var tabs = spreadsheet.tabs; + var views = spreadsheet.views; + + if (typeof obj == "string") { + newtab = obj; + } else { + newtab = obj.id.slice(spreadsheet.idPrefix.length, -3); + } + + if ( + spreadsheet.editor.busy && // if busy and switching from "sheet", ignore + (!tabs[spreadsheet.currentTab].view || + tabs[spreadsheet.currentTab].view == "sheet") + ) { + for (i = 0; i < tabs.length; i++) { + if (tabs[i].name == newtab && tabs[i].view && tabs[i].view != "sheet") { + return; + } + } + } + + if (spreadsheet.tabs[spreadsheet.currentTab].onunclick) { + spreadsheet.tabs[spreadsheet.currentTab].onunclick( + spreadsheet, + spreadsheet.tabs[spreadsheet.currentTab].name + ); + } + + for (i = 0; i < tabs.length; i++) { + tname = tabs[i].name; + menutabs[tname] = document.getElementById( + spreadsheet.idPrefix + tname + "tab" + ); + tools[tname] = document.getElementById( + spreadsheet.idPrefix + tname + "tools" + ); + if (tname == newtab) { + newtabnum = i; + tools[tname].style.display = "block"; + menutabs[tname].style.cssText = spreadsheet.tabselectedCSS; + } else { + tools[tname].style.display = "none"; + menutabs[tname].style.cssText = spreadsheet.tabplainCSS; + } + } + + spreadsheet.currentTab = newtabnum; + + if (tabs[newtabnum].onclick) { + tabs[newtabnum].onclick(spreadsheet, newtab); + } + + for (vname in views) { + if ( + (!tabs[newtabnum].view && vname == "sheet") || + tabs[newtabnum].view == vname + ) { + views[vname].element.style.display = "block"; + newview = vname; + } else { + views[vname].element.style.display = "none"; + } + } + + if (tabs[newtabnum].onclickFocus) { + ele = tabs[newtabnum].onclickFocus; + if (typeof ele == "string") { + ele = document.getElementById(spreadsheet.idPrefix + ele); + ele.focus(); + } + SocialCalc.CmdGotFocus(ele); + } else { + SocialCalc.KeyboardFocus(); + } + + if (views[newview].needsresize && views[newview].onresize) { + views[newview].needsresize = false; + views[newview].onresize(spreadsheet, views[newview]); + } + + if (newview == "sheet") { + spreadsheet.statuslineDiv.style.display = "block"; + spreadsheet.editor.ScheduleRender(); + } else { + spreadsheet.statuslineDiv.style.display = "none"; + } + + return; + }; + + // + // SocialCalc.SpreadsheetControlStatuslineCallback + // + + SocialCalc.SpreadsheetControlStatuslineCallback = function ( + editor, + status, + arg, + params + ) { + var rele1, rele2; + + var ele = document.getElementById(params.statuslineid); + + if (ele) { + ele.innerHTML = editor.GetStatuslineString(status, arg, params); + } + + switch (status) { + case "cmdendnorender": + case "calcfinished": + case "doneposcalc": + rele1 = document.getElementById(params.recalcid1); + rele2 = document.getElementById(params.recalcid2); + if (!rele1 || !rele2) break; + if (editor.context.sheetobj.attribs.needsrecalc == "yes") { + rele1.style.display = "inline"; + rele2.style.display = "inline"; + } else { + rele1.style.display = "none"; + rele2.style.display = "none"; + } + break; + + default: + break; + } + }; + + // + // SocialCalc.UpdateSortRangeProposal(editor) + // + // Updates sort range proposed in the UI in element idPrefix+sortlist + // + + SocialCalc.UpdateSortRangeProposal = function (editor) { + var ele = document.getElementById( + SocialCalc.GetSpreadsheetControlObject().idPrefix + "sortlist" + ); + if (editor.range.hasrange) { + ele.options[0].text = + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom); + } else { + ele.options[0].text = SocialCalc.LocalizeString("[select range]"); + } + }; + + // + // SocialCalc.LoadColumnChoosers(spreadsheet) + // + // Updates list of columns for choosing which to sort for Major, Minor, and Last sort + // + + SocialCalc.LoadColumnChoosers = function (spreadsheet) { + var SCLoc = SocialCalc.LocalizeString; + + var sortrange, nrange, rparts, col, colname, sele, oldindex; + + if (spreadsheet.sortrange && spreadsheet.sortrange.indexOf(":") == -1) { + // sortrange is a named range + nrange = SocialCalc.Formula.LookupName( + spreadsheet.sheet, + spreadsheet.sortrange || "" + ); + if (nrange.type == "range") { + rparts = nrange.value.match(/^(.*)\|(.*)\|$/); + sortrange = rparts[1] + ":" + rparts[2]; + } else { + sortrange = "A1:A1"; + } + } else { + sortrange = spreadsheet.sortrange; + } + var range = SocialCalc.ParseRange(sortrange); + sele = document.getElementById(spreadsheet.idPrefix + "majorsort"); + oldindex = sele.selectedIndex; + sele.options.length = 0; + sele.options[sele.options.length] = new Option(SCLoc("[None]"), ""); + for (var col = range.cr1.col; col <= range.cr2.col; col++) { + colname = SocialCalc.rcColname(col); + sele.options[sele.options.length] = new Option( + SCLoc("Column ") + colname, + colname + ); + } + sele.selectedIndex = + oldindex > 1 && oldindex <= range.cr2.col - range.cr1.col + 1 + ? oldindex + : 1; // restore what was there if reasonable + sele = document.getElementById(spreadsheet.idPrefix + "minorsort"); + oldindex = sele.selectedIndex; + sele.options.length = 0; + sele.options[sele.options.length] = new Option(SCLoc("[None]"), ""); + for (var col = range.cr1.col; col <= range.cr2.col; col++) { + colname = SocialCalc.rcColname(col); + sele.options[sele.options.length] = new Option(colname, colname); + } + sele.selectedIndex = + oldindex > 0 && oldindex <= range.cr2.col - range.cr1.col + 1 + ? oldindex + : 0; // default to [none] + sele = document.getElementById(spreadsheet.idPrefix + "lastsort"); + oldindex = sele.selectedIndex; + sele.options.length = 0; + sele.options[sele.options.length] = new Option(SCLoc("[None]"), ""); + for (var col = range.cr1.col; col <= range.cr2.col; col++) { + colname = SocialCalc.rcColname(col); + sele.options[sele.options.length] = new Option(colname, colname); + } + sele.selectedIndex = + oldindex > 0 && oldindex <= range.cr2.col - range.cr1.col + 1 + ? oldindex + : 0; // default to [none] + }; + + // + // SocialCalc.CmdGotFocus(obj) + // + // Sets SocialCalc.Keyboard.passThru: obj should be element with focus or "true" + // + + SocialCalc.CmdGotFocus = function (obj) { + SocialCalc.Keyboard.passThru = obj; + }; + + // + // SocialCalc.DoButtonCmd(e, buttoninfo, bobj) + // + + SocialCalc.DoButtonCmd = function (e, buttoninfo, bobj) { + SocialCalc.DoCmd(bobj.element, bobj.functionobj.command); + }; + + // + // SocialCalc.DoCmd(obj, which) + // + // xxx + // + + SocialCalc.DoCmd = function (obj, which) { + var combostr, + sstr, + cl, + i, + clele, + slist, + slistele, + str, + sele, + rele, + lele, + ele, + sortrange, + nrange, + rparts; + var sheet, cell, color, bgcolor, defaultcolor, defaultbgcolor; + + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + + switch (which) { + case "undo": + spreadsheet.ExecuteCommand("undo", ""); + break; + + case "redo": + spreadsheet.ExecuteCommand("redo", ""); + break; + + case "fill-rowcolstuff": + case "fill-text": + cl = which.substring(5); + clele = document.getElementById(spreadsheet.idPrefix + cl + "list"); + clele.length = 0; + for (i = 0; i < SocialCalc.SpreadsheetCmdTable[cl].length; i++) { + clele.options[i] = new Option( + SocialCalc.SpreadsheetCmdTable[cl][i].t + ); + } + which = "changed-" + cl; // fall through to changed code + + case "changed-rowcolstuff": + case "changed-text": + cl = which.substring(8); + clele = document.getElementById(spreadsheet.idPrefix + cl + "list"); + slist = + SocialCalc.SpreadsheetCmdTable.slists[ + SocialCalc.SpreadsheetCmdTable[cl][clele.selectedIndex].s + ]; // get sList for this command + slistele = document.getElementById(spreadsheet.idPrefix + cl + "slist"); + slistele.length = 0; // reset + for (i = 0; i < (slist.length || 0); i++) { + slistele.options[i] = new Option(slist[i].t, slist[i].s); + } + return; // nothing else to do + + case "ok-rowcolstuff": + case "ok-text": + cl = which.substring(3); + clele = document.getElementById(spreadsheet.idPrefix + cl + "list"); + slistele = document.getElementById(spreadsheet.idPrefix + cl + "slist"); + combostr = SocialCalc.SpreadsheetCmdTable[cl][clele.selectedIndex].c; + sstr = slistele[slistele.selectedIndex].value; + SocialCalc.SpreadsheetControlExecuteCommand(obj, combostr, sstr); + break; + + case "ok-setsort": + lele = document.getElementById(spreadsheet.idPrefix + "sortlist"); + if (lele.selectedIndex == 0) { + if (editor.range.hasrange) { + spreadsheet.sortrange = + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom); + } else { + spreadsheet.sortrange = + editor.ecell.coord + ":" + editor.ecell.coord; + } + } else { + spreadsheet.sortrange = lele.options[lele.selectedIndex].value; + } + ele = document.getElementById(spreadsheet.idPrefix + "sortbutton"); + ele.value = SocialCalc.LocalizeString("Sort ") + spreadsheet.sortrange; + ele.style.visibility = "visible"; + SocialCalc.LoadColumnChoosers(spreadsheet); + if (obj && obj.blur) obj.blur(); + SocialCalc.KeyboardFocus(); + return; + + case "dosort": + if (spreadsheet.sortrange && spreadsheet.sortrange.indexOf(":") == -1) { + // sortrange is a named range + nrange = SocialCalc.Formula.LookupName( + spreadsheet.sheet, + spreadsheet.sortrange || "" + ); + if (nrange.type != "range") return; + rparts = nrange.value.match(/^(.*)\|(.*)\|$/); + sortrange = rparts[1] + ":" + rparts[2]; + } else { + sortrange = spreadsheet.sortrange; + } + if (sortrange == "A1:A1") return; + str = "sort " + sortrange + " "; + sele = document.getElementById(spreadsheet.idPrefix + "majorsort"); + rele = document.getElementById(spreadsheet.idPrefix + "majorsortup"); + str += + sele.options[sele.selectedIndex].value + + (rele.checked ? " up" : " down"); + sele = document.getElementById(spreadsheet.idPrefix + "minorsort"); + if (sele.selectedIndex > 0) { + rele = document.getElementById(spreadsheet.idPrefix + "minorsortup"); + str += + " " + + sele.options[sele.selectedIndex].value + + (rele.checked ? " up" : " down"); + } + sele = document.getElementById(spreadsheet.idPrefix + "lastsort"); + if (sele.selectedIndex > 0) { + rele = document.getElementById(spreadsheet.idPrefix + "lastsortup"); + str += + " " + + sele.options[sele.selectedIndex].value + + (rele.checked ? " up" : " down"); + } + spreadsheet.ExecuteCommand(str, ""); + break; + + case "merge": + combostr = SocialCalc.SpreadsheetCmdLookup[which] || ""; + sstr = SocialCalc.SpreadsheetCmdSLookup[which] || ""; + spreadsheet.ExecuteCommand(combostr, sstr); + if (editor.range.hasrange) { + // set ecell to upper left + editor.MoveECell( + SocialCalc.crToCoord(editor.range.left, editor.range.top) + ); + editor.RangeRemove(); + } + break; + + case "movefrom": + if (editor.range2.hasrange) { + // toggle if already there + spreadsheet.context.cursorsuffix = ""; + editor.Range2Remove(); + spreadsheet.ExecuteCommand("redisplay", ""); + } else if (editor.range.hasrange) { + // set range2 to range or one cell + editor.range2.top = editor.range.top; + editor.range2.right = editor.range.right; + editor.range2.bottom = editor.range.bottom; + editor.range2.left = editor.range.left; + editor.range2.hasrange = true; + editor.MoveECell( + SocialCalc.crToCoord(editor.range.left, editor.range.top) + ); + } else { + editor.range2.top = editor.ecell.row; + editor.range2.right = editor.ecell.col; + editor.range2.bottom = editor.ecell.row; + editor.range2.left = editor.ecell.col; + editor.range2.hasrange = true; + } + str = editor.range2.hasrange ? "" : "off"; + ele = document.getElementById(spreadsheet.idPrefix + "button_movefrom"); + ele.src = spreadsheet.imagePrefix + "movefrom" + str + ".gif"; + ele = document.getElementById( + spreadsheet.idPrefix + "button_movepaste" + ); + ele.src = spreadsheet.imagePrefix + "movepaste" + str + ".gif"; + ele = document.getElementById( + spreadsheet.idPrefix + "button_moveinsert" + ); + ele.src = spreadsheet.imagePrefix + "moveinsert" + str + ".gif"; + if (editor.range2.hasrange) editor.RangeRemove(); + break; + + case "movepaste": + case "moveinsert": + if (editor.range2.hasrange) { + spreadsheet.context.cursorsuffix = ""; + combostr = + which + + " " + + SocialCalc.crToCoord(editor.range2.left, editor.range2.top) + + ":" + + SocialCalc.crToCoord(editor.range2.right, editor.range2.bottom) + + " " + + editor.ecell.coord; + spreadsheet.ExecuteCommand(combostr, ""); + editor.Range2Remove(); + ele = document.getElementById( + spreadsheet.idPrefix + "button_movefrom" + ); + ele.src = spreadsheet.imagePrefix + "movefromoff.gif"; + ele = document.getElementById( + spreadsheet.idPrefix + "button_movepaste" + ); + ele.src = spreadsheet.imagePrefix + "movepasteoff.gif"; + ele = document.getElementById( + spreadsheet.idPrefix + "button_moveinsert" + ); + ele.src = spreadsheet.imagePrefix + "moveinsertoff.gif"; + } + break; + + case "swapcolors": + sheet = spreadsheet.sheet; + cell = sheet.GetAssuredCell(editor.ecell.coord); + defaultcolor = sheet.attribs.defaultcolor + ? sheet.colors[sheet.attribs.defaultcolor] + : "rgb(0,0,0)"; + defaultbgcolor = sheet.attribs.defaultbgcolor + ? sheet.colors[sheet.attribs.defaultbgcolor] + : "rgb(255,255,255)"; + color = cell.color ? sheet.colors[cell.color] : defaultcolor; // get color + if (color == defaultbgcolor) color = ""; // going to swap, so if same as background default, use default + bgcolor = cell.bgcolor ? sheet.colors[cell.bgcolor] : defaultbgcolor; + if (bgcolor == defaultcolor) bgcolor = ""; // going to swap, so if same as foreground default, use default + spreadsheet.ExecuteCommand( + "set %C color " + bgcolor + "%Nset %C bgcolor " + color, + "" + ); + break; + + default: + combostr = SocialCalc.SpreadsheetCmdLookup[which] || ""; + sstr = SocialCalc.SpreadsheetCmdSLookup[which] || ""; + spreadsheet.ExecuteCommand(combostr, sstr); + break; + } + + if (obj && obj.blur) obj.blur(); + SocialCalc.KeyboardFocus(); + }; + + SocialCalc.SpreadsheetCmdLookup = { + copy: "copy %C all", + cut: "cut %C all", + paste: "paste %C all", + pasteformats: "paste %C formats", + delete: "erase %C formulas", + filldown: "filldown %C all", + fillright: "fillright %C all", + erase: "erase %C all", + borderon: "set %C bt %S%Nset %C br %S%Nset %C bb %S%Nset %C bl %S", + borderoff: "set %C bt %S%Nset %C br %S%Nset %C bb %S%Nset %C bl %S", + merge: "merge %C", + unmerge: "unmerge %C", + "align-left": "set %C cellformat left", + "align-center": "set %C cellformat center", + "align-right": "set %C cellformat right", + "align-default": "set %C cellformat", + insertrow: "insertrow %C", + insertcol: "insertcol %C", + deleterow: "deleterow %C", + deletecol: "deletecol %C", + undo: "undo", + redo: "redo", + recalc: "recalc", + }; + + SocialCalc.SpreadsheetCmdSLookup = { + borderon: "1px solid rgb(0,0,0)", + borderoff: "", + }; + + /******* NO LONGER USED + +SocialCalc.SpreadsheetCmdTable = { + cmd: [ + {t:"Fill Right", s:"ffal", c:"fillright %C %S"}, + {t:"Fill Down", s:"ffal", c:"filldown %C %S"}, + {t:"Copy", s:"all", c:"copy %C %S"}, + {t:"Cut", s:"all", c:"cut %C %S"}, + {t:"Paste", s:"ffal", c:"paste %C %S"}, + {t:"Erase", s:"ffal", c:"erase %C %S"}, + {t:"Insert", s:"rowcol", c:"insert%S %C"}, + {t:"Delete", s:"rowcol", c:"delete%S %C"}, + {t:"Merge Cells", s:"none", c:"merge %C"}, + {t:"Unmerge", s:"none", c:"unmerge %C"}, + {t:"Sort", s:"sortcol", c:"sort %R %S"}, + {t:"Cell Color", s:"colors", c:"set %C color %S"}, + {t:"Cell Background", s:"colors", c:"set %C bgcolor %S"}, + {t:"Cell Number Format", s:"ntvf", c:"set %C nontextvalueformat %S"}, + {t:"Cell Font", s:"fonts", c:"set %C font %S"}, + {t:"Cell Align", s:"cellformat", c:"set %C cellformat %S"}, + {t:"Cell Borders", s:"borderOnOff", c:"set %C bt %S%Nset %C br %S%Nset %C bb %S%Nset %C bl %S"}, + {t:"Column Width", s:"colWidths", c:"set %W width %S"}, + {t:"Default Color", s:"colors", c:"set sheet defaultcolor %S"}, + {t:"Default Background", s:"colors", c:"set sheet defaultbgcolor %S"}, + {t:"Default Number Format", s:"ntvf", c:"set sheet defaultnontextvalueformat %S"}, + {t:"Default Font", s:"fonts", c:"set sheet defaultfont %S"}, + {t:"Default Text Align", s:"cellformat", c:"set sheet defaulttextformat %S"}, + {t:"Default Number Align", s:"cellformat", c:"set sheet defaultnontextformat %S"}, + {t:"Default Column Width", s:"colWidths", c:"set sheet defaultcolwidth %S"} + ], + rowcolstuff: [ + {t:"Insert", s:"rowcol", c:"insert%S %C"}, + {t:"Delete", s:"rowcol", c:"delete%S %C"}, + {t:"Paste", s:"ffal", c:"paste %C %S"}, + {t:"Erase", s:"ffal", c:"erase %C %S"}, + {t:"Fill Right", s:"ffal", c:"fillright %C %S"}, + {t:"Fill Down", s:"ffal", c:"filldown %C %S"} + ], + text: [ + {t:"Cell Color", s:"colors", c:"set %C color %S"}, + {t:"Cell Background", s:"colors", c:"set %C bgcolor %S"}, + {t:"Cell Number Format", s:"ntvf", c:"set %C nontextvalueformat %S"}, + {t:"Cell Text Format", s:"tvf", c:"set %C textvalueformat %S"}, + {t:"Cell Font", s:"fonts", c:"set %C font %S"}, + {t:"Cell Align", s:"cellformat", c:"set %C cellformat %S"}, + {t:"Default Color", s:"colors", c:"set sheet defaultcolor %S"}, + {t:"Default Background", s:"colors", c:"set sheet defaultbgcolor %S"}, + {t:"Default Number Format", s:"ntvf", c:"set sheet defaultnontextvalueformat %S"}, + {t:"Default Text Format", s:"tvf", c:"set sheet defaulttextvalueformat %S"}, + {t:"Default Font", s:"fonts", c:"set sheet defaultfont %S"}, + {t:"Default Text Align", s:"cellformat", c:"set sheet defaulttextformat %S"}, + {t:"Default Number Align", s:"cellformat", c:"set sheet defaultnontextformat %S"} + ], + slists: { + "colors": [ + {t:"Default", s:""}, + {t:"Black", s:"rgb(0,0,0)"}, + {t:"Dark Gray", s:"rgb(102,102,102)"}, // #666 + {t:"Gray", s:"rgb(204,204,204)"}, // #CCC + {t:"White", s:"rgb(255,255,255)"}, + {t:"Red", s:"rgb(255,0,0)"}, + {t:"Dark Red", s:"rgb(153,0,0)"}, + {t:"Orange", s:"rgb(255,153,0)"}, + {t:"Yellow", s:"rgb(255,255,0)"}, + {t:"Light Yellow", s:"rgb(255,255,204)"}, + {t:"Green", s:"rgb(0,255,0)"}, + {t:"Dark Green", s:"rgb(0,153,0)"}, + {t:"Blue", s:"rgb(0,0,255)"}, + {t:"Dark Blue", s:"rgb(0,0,153)"}, + {t:"Light Blue", s:"rgb(204,204,255)"} + ], + "fonts": [ // style weight size family + {t:"Default", s:""}, + {t:"Bold", s:"normal bold * *"}, + {t:"Italic", s:"italic normal * *"}, + {t:"Small", s:"* small *"}, + {t:"Medium", s:"* medium *"}, + {t:"Large", s:"* large *"}, + {t:"Bold Small", s:"normal bold small *"}, + {t:"Bold Medium", s:"normal bold medium *"}, + {t:"Bold Large", s:"normal bold large *"} + ], + "cellformat": [ + {t:"Default", s:""}, + {t:"Left", s:"left"}, + {t:"Right", s:"right"}, + {t:"Center", s:"center"} + ], + "borderOnOff": [ + {t:"On", s:"1px solid rgb(0,0,0)"}, + {t:"Off", s:""} + ], + "colWidths": [ + {t:"Default", s:""}, + {t:"20", s:"20"}, + {t:"40", s:"40"}, + {t:"60", s:"60"}, + {t:"80", s:"80"}, + {t:"100", s:"100"}, + {t:"120", s:"120"}, + {t:"140", s:"140"}, + {t:"160", s:"160"}, + {t:"180", s:"180"}, + {t:"200", s:"200"}, + {t:"220", s:"220"}, + {t:"240", s:"240"}, + {t:"260", s:"260"}, + {t:"280", s:"280"}, + {t:"300", s:"300"} + ], + "ntvf": [ + {t:"Default", s:""}, + {t:"1234", s:"0"}, + {t:"1,234", s:"#,##0"}, + {t:"1,234.5", s:"#,##0.0"}, + {t:"1,234.56", s:"#,##0.00"}, + {t:"1,234.567", s:"#,##0.000"}, + {t:"1,234%", s:"#,##0%"}, + {t:"1,234.5%", s:"#,##0.0%"}, + {t:"(1,234)", s:"#,##0_);(#,##0)"}, + {t:"(1,234.5)", s:"#,##0.0_);(#,##0.0)"}, + {t:"(1,234.56)", s:"#,##0.00_);(#,##0.00)"}, + {t:"00", s:"00"}, + {t:"000", s:"000"}, + {t:"0000", s:"0000"}, + {t:"$1,234.56", s:"$#,##0.00"}, + {t:"2006-01-04", s:"yyyy-mm-dd"}, + {t:"01:23:45", s:"hh:mm:ss"}, + {t:"2006-01-04 01:23:45", s:"yyyy-mm-dd hh:mm:ss"}, + {t:"Hidden", s:"hidden"} + ], + "tvf": [ + {t:"Default", s:""}, + {t:"Automatic", s:"general"}, + {t:"Plain Text", s:"text-plain"}, + {t:"HTML", s:"text-html"}, + {t:"Wiki", s:"text-wiki"}, + {t:"Hidden", s:"hidden"} + ], + "ffal": [ // Formulas, Formats, All + {t:"All", s:"all"}, + {t:"Contents", s:"formulas"}, + {t:"Formats", s:"formats"} + ], + "all": [ // All + {t:"All", s:"all"} + ], + "rowcol": [ + {t:"Row", s:"row"}, + {t:"Column", s:"col"} + ], + "sortcol": [ + {t:"A up", s:"A up"}, + {t:"B up", s:"B up"}, + {t:"C up", s:"C up"}, + {t:"A down", s:"A down"}, + {t:"B down", s:"B down"}, + {t:"C down", s:"C down"}, + {t:"A, B, C up", s:"A up B up C up"} + ], + "none": [ // nothing + {t:" ", s:" "} + ] + } + } +*********/ + + // + // SocialCalc.SpreadsheetControlExecuteCommand(obj, combostr, sstr) + // + // xxx + // + + SocialCalc.SpreadsheetControlExecuteCommand = function (obj, combostr, sstr) { + var i, commands; + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var eobj = spreadsheet.editor; + + var str = {}; + str.P = "%"; + str.N = "\n"; + if (eobj.range.hasrange) { + str.R = + SocialCalc.crToCoord(eobj.range.left, eobj.range.top) + + ":" + + SocialCalc.crToCoord(eobj.range.right, eobj.range.bottom); + str.C = str.R; + str.W = + SocialCalc.rcColname(eobj.range.left) + + ":" + + SocialCalc.rcColname(eobj.range.right); + } else { + str.C = eobj.ecell.coord; + str.R = eobj.ecell.coord + ":" + eobj.ecell.coord; + str.W = SocialCalc.rcColname(SocialCalc.coordToCr(eobj.ecell.coord).col); + } + str.S = sstr; + combostr = combostr.replace(/%C/g, str.C); + combostr = combostr.replace(/%R/g, str.R); + combostr = combostr.replace(/%N/g, str.N); + combostr = combostr.replace(/%S/g, str.S); + combostr = combostr.replace(/%W/g, str.W); + combostr = combostr.replace(/%P/g, str.P); + + eobj.EditorScheduleSheetCommands(combostr, true, false); + }; + + // + // result = SocialCalc.SpreadsheetControlCreateSheetHTML(spreadsheet) + // + // Returns the HTML representation of the whole spreadsheet + // + + SocialCalc.SpreadsheetControlCreateSheetHTML = function (spreadsheet) { + var context, div, ele; + + var result = ""; + + context = new SocialCalc.RenderContext(spreadsheet.sheet); + div = document.createElement("div"); + ele = context.RenderSheet(null, { type: "html" }); + div.appendChild(ele); + context = undefined; + result = div.innerHTML; + ele = undefined; + div = undefined; + return result; + }; + + // + // result = SocialCalc.SpreadsheetControlCreateCellHTML(spreadsheet, coord, linkstyle) + // + // Returns the HTML representation of a cell. Blank is "", not " ". + // + + SocialCalc.SpreadsheetControlCreateCellHTML = function ( + spreadsheet, + coord, + linkstyle + ) { + var result = ""; + var cell = spreadsheet.sheet.cells[coord]; + + if (!cell) return ""; + + if (cell.displaystring == undefined) { + result = SocialCalc.FormatValueForDisplay( + spreadsheet.sheet, + cell.datavalue, + coord, + linkstyle || spreadsheet.context.defaultHTMLlinkstyle + ); + } else { + result = cell.displaystring; + } + + if (result == " ") result = ""; + + return result; + }; + + // + // result = SocialCalc.SpreadsheetControlCreateCellHTMLSave(spreadsheet, range, linkstyle) + // + // Returns the HTML representation of a range of cells, or the whole sheet if range is null. + // The form is: + // version:1.0 + // coord:cell-HTML + // coord:cell-HTML + // ... + // + // Empty cells are skipped. The cell-HTML is encoded with ":"=>"\c", newline=>"\n", and "\"=>"\b". + // + + SocialCalc.SpreadsheetControlCreateCellHTMLSave = function ( + spreadsheet, + range, + linkstyle + ) { + var cr1, cr2, row, col, coord, cell, cellHTML; + var result = []; + var prange; + + if (range) { + prange = SocialCalc.ParseRange(range); + } else { + prange = { + cr1: { row: 1, col: 1 }, + cr2: { + row: spreadsheet.sheet.attribs.lastrow, + col: spreadsheet.sheet.attribs.lastcol, + }, + }; + } + cr1 = prange.cr1; + cr2 = prange.cr2; + + result.push("version:1.0"); + + for (row = cr1.row; row <= cr2.row; row++) { + for (col = cr1.col; col <= cr2.col; col++) { + coord = SocialCalc.crToCoord(col, row); + cell = spreadsheet.sheet.cells[coord]; + if (!cell) continue; + if (cell.displaystring == undefined) { + cellHTML = SocialCalc.FormatValueForDisplay( + spreadsheet.sheet, + cell.datavalue, + coord, + linkstyle || spreadsheet.context.defaultHTMLlinkstyle + ); + } else { + cellHTML = cell.displaystring; + } + if (cellHTML == " ") continue; + result.push(coord + ":" + SocialCalc.encodeForSave(cellHTML)); + } + } + + result.push(""); // one extra to get extra \n + return result.join("\n"); + }; + + // + // Formula Bar Button Routines + // + + SocialCalc.SpreadsheetControl.DoFunctionList = function () { + var i, cname, str, f, ele; + + var scf = SocialCalc.Formula; + var scc = SocialCalc.Constants; + var fcl = scc.function_classlist; + + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var idp = spreadsheet.idPrefix + "function"; + + ele = document.getElementById(idp + "dialog"); + if (ele) return; // already have one + + scf.FillFunctionInfo(); + + str = + '
%loc!Category!
' + + '
  %loc!Functions!
' + + '
' + + '
' + + SocialCalc.SpreadsheetControl.GetFunctionInfoStr( + scf.FunctionClasses[fcl[0]].items[0] + ) + + "
" + + '
' + + ' ' + + '
' + + "
"; + + var main = document.createElement("div"); + main.id = idp + "dialog"; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + str = + '' + + '" + + '
' + + " %loc!Function List!" + + " X 
' + + '
' + + str + + "
"; + + str = SocialCalc.LocalizeSubstrings(str); + + main.innerHTML = str; + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + spreadsheet.spreadsheetDiv.appendChild(main); + + ele = document.getElementById(idp + "name"); + ele.focus(); + SocialCalc.CmdGotFocus(ele); + //!!! need to do keyboard handling: if esc, hide; if All, letter scrolls to there + }; + + SocialCalc.SpreadsheetControl.GetFunctionNamesStr = function (cname) { + var i, f; + var scf = SocialCalc.Formula; + var str = ""; + + f = scf.FunctionClasses[cname]; + for (i = 0; i < f.items.length; i++) { + str += + '"; + } + + return str; + }; + + SocialCalc.SpreadsheetControl.FillFunctionNames = function (cname, ele) { + var i, f; + var scf = SocialCalc.Formula; + + ele.length = 0; + f = scf.FunctionClasses[cname]; + for (i = 0; i < f.items.length; i++) { + ele.options[i] = new Option(f.items[i], f.items[i]); + if (i == 0) { + ele.options[i].selected = true; + } + } + }; + + SocialCalc.SpreadsheetControl.GetFunctionInfoStr = function (fname) { + var scf = SocialCalc.Formula; + var f = scf.FunctionList[fname]; + var scsc = SocialCalc.special_chars; + + var str = + "" + fname + "(" + scsc(scf.FunctionArgString(fname)) + ")
"; + str += scsc(f[3]); + + return str; + }; + + SocialCalc.SpreadsheetControl.FunctionClassChosen = function (cname) { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var idp = spreadsheet.idPrefix + "function"; + var scf = SocialCalc.Formula; + + SocialCalc.SpreadsheetControl.FillFunctionNames( + cname, + document.getElementById(idp + "name") + ); + + SocialCalc.SpreadsheetControl.FunctionChosen( + scf.FunctionClasses[cname].items[0] + ); + }; + + SocialCalc.SpreadsheetControl.FunctionChosen = function (fname) { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var idp = spreadsheet.idPrefix + "function"; + + document.getElementById(idp + "desc").innerHTML = + SocialCalc.SpreadsheetControl.GetFunctionInfoStr(fname); + }; + + SocialCalc.SpreadsheetControl.HideFunctions = function () { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + + var ele = document.getElementById(spreadsheet.idPrefix + "functiondialog"); + ele.innerHTML = ""; + + SocialCalc.DragUnregister(ele); + + SocialCalc.KeyboardFocus(); + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + }; + + SocialCalc.SpreadsheetControl.DoFunctionPaste = function () { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + var ele = document.getElementById(spreadsheet.idPrefix + "functionname"); + var mele = document.getElementById( + spreadsheet.idPrefix + "multilinetextarea" + ); + + var text = ele.value + "("; + + SocialCalc.SpreadsheetControl.HideFunctions(); + + if (mele) { + // multi-line editing is in progress + mele.value += text; + mele.focus(); + SocialCalc.CmdGotFocus(mele); + } else { + editor.EditorAddToInput(text, "="); + } + }; + + SocialCalc.SpreadsheetControl.DoMultiline = function () { + var SCLocSS = SocialCalc.LocalizeSubstrings; + + var str, ele, text; + + var scc = SocialCalc.Constants; + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + var wval = editor.workingvalues; + + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var idp = spreadsheet.idPrefix + "multiline"; + + ele = document.getElementById(idp + "dialog"); + if (ele) return; // already have one + + switch (editor.state) { + case "start": + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + editor.RangeRemove(); + text = SocialCalc.GetCellContents(editor.context.sheetobj, wval.ecoord); + break; + + case "input": + case "inputboxdirect": + text = editor.inputBox.GetText(); + break; + } + + editor.inputBox.element.disabled = true; + + text = SocialCalc.special_chars(text); + + str = + '" + + '
' + + SCLocSS( + ' ' + + ' ' + + '
' + + "
" + ); + + var main = document.createElement("div"); + main.id = idp + "dialog"; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + SCLocSS(" %loc!Multi-line Input Box!") + + " X 
' + + '
' + + str + + "
"; + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + spreadsheet.spreadsheetDiv.appendChild(main); + + ele = document.getElementById(idp + "textarea"); + ele.focus(); + SocialCalc.CmdGotFocus(ele); + //!!! need to do keyboard handling: if esc, hide? + }; + + SocialCalc.SpreadsheetControl.HideMultiline = function () { + var scc = SocialCalc.Constants; + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + + var ele = document.getElementById(spreadsheet.idPrefix + "multilinedialog"); + ele.innerHTML = ""; + + SocialCalc.DragUnregister(ele); + + SocialCalc.KeyboardFocus(); + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + + switch (editor.state) { + case "start": + editor.inputBox.DisplayCellContents(null); + break; + + case "input": + case "inputboxdirect": + editor.inputBox.element.disabled = false; + editor.inputBox.Focus(); + break; + } + }; + + SocialCalc.SpreadsheetControl.DoMultilineClear = function () { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + + var ele = document.getElementById( + spreadsheet.idPrefix + "multilinetextarea" + ); + + ele.value = ""; + ele.focus(); + }; + + SocialCalc.SpreadsheetControl.DoMultilinePaste = function () { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + var wval = editor.workingvalues; + + var ele = document.getElementById( + spreadsheet.idPrefix + "multilinetextarea" + ); + + var text = ele.value; + + SocialCalc.SpreadsheetControl.HideMultiline(); + + switch (editor.state) { + case "start": + wval.partialexpr = ""; + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + break; + case "input": + case "inputboxdirect": + editor.inputBox.Blur(); + editor.inputBox.ShowInputBox(false); + editor.state = "start"; + break; + } + + editor.EditorSaveEdit(text); + }; + + SocialCalc.SpreadsheetControl.DoLink = function () { + var SCLoc = SocialCalc.LocalizeString; + + var str, ele, text, cell, setformat, popup; + + var scc = SocialCalc.Constants; + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + var wval = editor.workingvalues; + + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var idp = spreadsheet.idPrefix + "link"; + + ele = document.getElementById(idp + "dialog"); + if (ele) return; // already have one + + switch (editor.state) { + case "start": + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + editor.RangeRemove(); + text = SocialCalc.GetCellContents(editor.context.sheetobj, wval.ecoord); + break; + + case "input": + case "inputboxdirect": + text = editor.inputBox.GetText(); + break; + } + + editor.inputBox.element.disabled = true; + + if (text.charAt(0) == "'") { + text = text.slice(1); + } + + var parts = SocialCalc.ParseCellLinkText(text); + + text = SocialCalc.special_chars(text); + + cell = spreadsheet.sheet.cells[editor.ecell.coord]; + if (!cell || !cell.textvalueformat) { + // set to link format, but don't override + setformat = " checked"; + } else { + setformat = ""; + } + + popup = parts.newwin ? " checked" : ""; + + str = + '
' + + '' + + SCLoc("Description") + + "
" + + '
' + + '' + + SCLoc("URL") + + "
" + + '
'; + if (SocialCalc.Callbacks.MakePageLink) { + // only show if handling pagenames here + str += + '' + + SCLoc("Page Name") + + "
" + + '
' + + '' + + SCLoc("Workspace") + + "
" + + '
'; + } + str += SocialCalc.LocalizeSubstrings( + ' " + + '%loc!Set to Link format!
' + + ' " + + '%loc!Show in new browser window!' + + "
" + + '
' + + ' ' + + ' ' + + '
' + + "" + ); + + var main = document.createElement("div"); + main.id = idp + "dialog"; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + SCLoc("Link Input Box") + + " X 
' + + '
' + + str + + "
"; + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + spreadsheet.spreadsheetDiv.appendChild(main); + + ele = document.getElementById(idp + "url"); + ele.focus(); + SocialCalc.CmdGotFocus(ele); + //!!! need to do keyboard handling: if esc, hide? + }; + + SocialCalc.SpreadsheetControl.HideLink = function () { + var scc = SocialCalc.Constants; + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + + var ele = document.getElementById(spreadsheet.idPrefix + "linkdialog"); + ele.innerHTML = ""; + + SocialCalc.DragUnregister(ele); + + SocialCalc.KeyboardFocus(); + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + + switch (editor.state) { + case "start": + editor.inputBox.DisplayCellContents(null); + break; + + case "input": + case "inputboxdirect": + editor.inputBox.element.disabled = false; + editor.inputBox.Focus(); + break; + } + }; + + SocialCalc.SpreadsheetControl.DoLinkClear = function () { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + + document.getElementById(spreadsheet.idPrefix + "linkdesc").value = ""; + document.getElementById(spreadsheet.idPrefix + "linkpagename").value = ""; + document.getElementById(spreadsheet.idPrefix + "linkworkspace").value = ""; + + var ele = document.getElementById(spreadsheet.idPrefix + "linkurl"); + ele.value = ""; + ele.focus(); + }; + + SocialCalc.SpreadsheetControl.DoLinkPaste = function () { + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + var wval = editor.workingvalues; + + var descele = document.getElementById(spreadsheet.idPrefix + "linkdesc"); + var urlele = document.getElementById(spreadsheet.idPrefix + "linkurl"); + var pagenameele = document.getElementById( + spreadsheet.idPrefix + "linkpagename" + ); + var workspaceele = document.getElementById( + spreadsheet.idPrefix + "linkworkspace" + ); + var formatele = document.getElementById( + spreadsheet.idPrefix + "linkformat" + ); + var popupele = document.getElementById(spreadsheet.idPrefix + "linkpopup"); + + var text = ""; + + var ltsym, gtsym, obsym, cbsym; + + if (popupele.checked) { + ltsym = "<<"; + gtsym = ">>"; + obsym = "[["; + cbsym = "]]"; + } else { + ltsym = "<"; + gtsym = ">"; + obsym = "["; + cbsym = "]"; + } + + if (pagenameele && pagenameele.value) { + if (workspaceele.value) { + text = + descele.value + + "{" + + workspaceele.value + + obsym + + pagenameele.value + + cbsym + + "}"; + } else { + text = descele.value + obsym + pagenameele.value + cbsym; + } + } else { + text = descele.value + ltsym + urlele.value + gtsym; + } + + SocialCalc.SpreadsheetControl.HideLink(); + + switch (editor.state) { + case "start": + wval.partialexpr = ""; + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + break; + case "input": + case "inputboxdirect": + editor.inputBox.Blur(); + editor.inputBox.ShowInputBox(false); + editor.state = "start"; + break; + } + + if (formatele.checked) { + SocialCalc.SpreadsheetControlExecuteCommand( + null, + "set %C textvalueformat text-link", + "" + ); + } + + editor.EditorSaveEdit(text); + }; + + SocialCalc.SpreadsheetControl.DoSum = function () { + var cmd, cell, row, col, sel, cr, foundvalue; + + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var editor = spreadsheet.editor; + var sheet = editor.context.sheetobj; + + if (editor.range.hasrange) { + sel = + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom); + cmd = + "set " + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom + 1) + + " formula sum(" + + sel + + ")"; + } else { + row = editor.ecell.row - 1; + col = editor.ecell.col; + if (row <= 1) { + cmd = "set " + editor.ecell.coord + " constant e#REF! 0 #REF!"; + } else { + foundvalue = false; + while (row > 0) { + cr = SocialCalc.crToCoord(col, row); + cell = sheet.GetAssuredCell(cr); + if (!cell.datatype || cell.datatype == "t") { + if (foundvalue) { + row++; + break; + } + } else { + foundvalue = true; + } + row--; + } + cmd = + "set " + + editor.ecell.coord + + " formula sum(" + + SocialCalc.crToCoord(col, row) + + ":" + + SocialCalc.crToCoord(col, editor.ecell.row - 1) + + ")"; + } + } + + editor.EditorScheduleSheetCommands(cmd, true, false); + }; + + // + // TAB Routines + // + + // Sort + + SocialCalc.SpreadsheetControlSortOnclick = function (s, t) { + var name, i; + var namelist = []; + var nl = document.getElementById(s.idPrefix + "sortlist"); + SocialCalc.LoadColumnChoosers(s); + s.editor.RangeChangeCallback.sort = SocialCalc.UpdateSortRangeProposal; + + for (name in s.sheet.names) { + namelist.push(name); + } + namelist.sort(); + nl.length = 0; + nl.options[0] = new Option(SocialCalc.LocalizeString("[select range]")); + for (i = 0; i < namelist.length; i++) { + name = namelist[i]; + nl.options[i + 1] = new Option(name, name); + if (name == s.sortrange) { + nl.options[i + 1].selected = true; + } + } + if (s.sortrange == "") { + nl.options[0].selected = true; + } + + SocialCalc.UpdateSortRangeProposal(s.editor); + SocialCalc.KeyboardFocus(); + return; + }; + + SocialCalc.SpreadsheetControlSortSave = function (editor, setting) { + // Format is: + // sort:sortrange:major:up/down:minor:up/down:last:up/down + + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + var str, sele, rele; + + str = "sort:" + SocialCalc.encodeForSave(spreadsheet.sortrange) + ":"; + sele = document.getElementById(spreadsheet.idPrefix + "majorsort"); + rele = document.getElementById(spreadsheet.idPrefix + "majorsortup"); + str += sele.selectedIndex + (rele.checked ? ":up" : ":down"); + sele = document.getElementById(spreadsheet.idPrefix + "minorsort"); + if (sele.selectedIndex > 0) { + rele = document.getElementById(spreadsheet.idPrefix + "minorsortup"); + str += ":" + sele.selectedIndex + (rele.checked ? ":up" : ":down"); + } else { + str += "::"; + } + sele = document.getElementById(spreadsheet.idPrefix + "lastsort"); + if (sele.selectedIndex > 0) { + rele = document.getElementById(spreadsheet.idPrefix + "lastsortup"); + str += ":" + sele.selectedIndex + (rele.checked ? ":up" : ":down"); + } else { + str += "::"; + } + return str + "\n"; + }; + + SocialCalc.SpreadsheetControlSortLoad = function ( + editor, + setting, + line, + flags + ) { + var parts, ele; + + var spreadsheet = SocialCalc.GetSpreadsheetControlObject(); + + parts = line.split(":"); + spreadsheet.sortrange = SocialCalc.decodeFromSave(parts[1]); + ele = document.getElementById(spreadsheet.idPrefix + "sortbutton"); + if (spreadsheet.sortrange) { + ele.value = SocialCalc.LocalizeString("Sort ") + spreadsheet.sortrange; + ele.style.visibility = "visible"; + } else { + ele.style.visibility = "hidden"; + } + SocialCalc.LoadColumnChoosers(spreadsheet); + + sele = document.getElementById(spreadsheet.idPrefix + "majorsort"); + sele.selectedIndex = parts[2] - 0; + document.getElementById( + spreadsheet.idPrefix + "majorsort" + parts[3] + ).checked = true; + sele = document.getElementById(spreadsheet.idPrefix + "minorsort"); + if (parts[4]) { + sele.selectedIndex = parts[4] - 0; + document.getElementById( + spreadsheet.idPrefix + "minorsort" + parts[5] + ).checked = true; + } else { + sele.selectedIndex = 0; + document.getElementById( + spreadsheet.idPrefix + "minorsortup" + ).checked = true; + } + sele = document.getElementById(spreadsheet.idPrefix + "lastsort"); + if (parts[6]) { + sele.selectedIndex = parts[6] - 0; + document.getElementById( + spreadsheet.idPrefix + "lastsort" + parts[7] + ).checked = true; + } else { + sele.selectedIndex = 0; + document.getElementById( + spreadsheet.idPrefix + "lastsortup" + ).checked = true; + } + + return true; + }; + + // Comment + + SocialCalc.SpreadsheetControlCommentOnclick = function (s, t) { + s.editor.MoveECellCallback.comment = + SocialCalc.SpreadsheetControlCommentMoveECell; + SocialCalc.SpreadsheetControlCommentDisplay(s, t); + SocialCalc.KeyboardFocus(); + return; + }; + + SocialCalc.SpreadsheetControlCommentDisplay = function (s, t) { + var c = ""; + if ( + s.editor.ecell && + s.editor.ecell.coord && + s.sheet.cells[s.editor.ecell.coord] + ) { + c = s.sheet.cells[s.editor.ecell.coord].comment || ""; + } + document.getElementById(s.idPrefix + "commenttext").value = c; + }; + + SocialCalc.SpreadsheetControlCommentMoveECell = function (editor) { + SocialCalc.SpreadsheetControlCommentDisplay( + SocialCalc.GetSpreadsheetControlObject(), + "comment" + ); + }; + + SocialCalc.SpreadsheetControlCommentSet = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + s.ExecuteCommand( + "set %C comment " + + SocialCalc.encodeForSave( + document.getElementById(s.idPrefix + "commenttext").value + ) + ); + var cell = SocialCalc.GetEditorCellElement( + s.editor, + s.editor.ecell.row, + s.editor.ecell.col + ); + s.editor.UpdateCellCSS(cell, s.editor.ecell.row, s.editor.ecell.col); + SocialCalc.KeyboardFocus(); + }; + + SocialCalc.SpreadsheetControlCommentOnunclick = function (s, t) { + delete s.editor.MoveECellCallback.comment; + }; + + // Names + + SocialCalc.SpreadsheetControlNamesOnclick = function (s, t) { + document.getElementById(s.idPrefix + "namesname").value = ""; + document.getElementById(s.idPrefix + "namesdesc").value = ""; + document.getElementById(s.idPrefix + "namesvalue").value = ""; + s.editor.RangeChangeCallback.names = + SocialCalc.SpreadsheetControlNamesRangeChange; + s.editor.MoveECellCallback.names = + SocialCalc.SpreadsheetControlNamesRangeChange; + SocialCalc.SpreadsheetControlNamesRangeChange(s.editor); + SocialCalc.SpreadsheetControlNamesFillNameList(); + SocialCalc.SpreadsheetControlNamesChangedName(); + }; + + SocialCalc.SpreadsheetControlNamesFillNameList = function () { + var SCLoc = SocialCalc.LocalizeString; + var name, i; + var namelist = []; + var s = SocialCalc.GetSpreadsheetControlObject(); + var nl = document.getElementById(s.idPrefix + "nameslist"); + var currentname = document + .getElementById(s.idPrefix + "namesname") + .value.toUpperCase() + .replace(/[^A-Z0-9_\.]/g, ""); + for (name in s.sheet.names) { + namelist.push(name); + } + namelist.sort(); + nl.length = 0; + if (namelist.length > 0) { + nl.options[0] = new Option(SCLoc("[New]")); + } else { + nl.options[0] = new Option(SCLoc("[None]")); + } + for (i = 0; i < namelist.length; i++) { + name = namelist[i]; + nl.options[i + 1] = new Option(name, name); + if (name == currentname) { + nl.options[i + 1].selected = true; + } + } + if (currentname == "") { + nl.options[0].selected = true; + } + }; + + SocialCalc.SpreadsheetControlNamesChangedName = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + var nl = document.getElementById(s.idPrefix + "nameslist"); + var name = nl.options[nl.selectedIndex].value; + if (s.sheet.names[name]) { + document.getElementById(s.idPrefix + "namesname").value = name; + document.getElementById(s.idPrefix + "namesdesc").value = + s.sheet.names[name].desc || ""; + document.getElementById(s.idPrefix + "namesvalue").value = + s.sheet.names[name].definition || ""; + } else { + document.getElementById(s.idPrefix + "namesname").value = ""; + document.getElementById(s.idPrefix + "namesdesc").value = ""; + document.getElementById(s.idPrefix + "namesvalue").value = ""; + } + }; + + SocialCalc.SpreadsheetControlNamesRangeChange = function (editor) { + var s = SocialCalc.GetSpreadsheetControlObject(); + var ele = document.getElementById(s.idPrefix + "namesrangeproposal"); + if (editor.range.hasrange) { + ele.value = + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom); + } else { + ele.value = editor.ecell.coord; + } + }; + + SocialCalc.SpreadsheetControlNamesOnunclick = function (s, t) { + delete s.editor.RangeChangeCallback.names; + delete s.editor.MoveECellCallback.names; + }; + + SocialCalc.SpreadsheetControlNamesSetValue = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + document.getElementById(s.idPrefix + "namesvalue").value = + document.getElementById(s.idPrefix + "namesrangeproposal").value; + SocialCalc.KeyboardFocus(); + }; + + SocialCalc.SpreadsheetControlNamesSave = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + var name = document.getElementById(s.idPrefix + "namesname").value; + SocialCalc.SetTab(s.tabs[0].name); // return to first tab + SocialCalc.KeyboardFocus(); + if (name != "") { + s.ExecuteCommand( + "name define " + + name + + " " + + document.getElementById(s.idPrefix + "namesvalue").value + + "\n" + + "name desc " + + name + + " " + + document.getElementById(s.idPrefix + "namesdesc").value + ); + } + }; + + SocialCalc.SpreadsheetControlNamesDelete = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + var name = document.getElementById(s.idPrefix + "namesname").value; + SocialCalc.SetTab(s.tabs[0].name); // return to first tab + SocialCalc.KeyboardFocus(); + if (name != "") { + s.ExecuteCommand("name delete " + name); + // document.getElementById(s.idPrefix+"namesname").value = ""; + // document.getElementById(s.idPrefix+"namesvalue").value = ""; + // document.getElementById(s.idPrefix+"namesdesc").value = ""; + // SocialCalc.SpreadsheetControlNamesFillNameList(); + } + SocialCalc.KeyboardFocus(); + }; + + // Clipboard + + SocialCalc.SpreadsheetControlClipboardOnclick = function (s, t) { + var s = SocialCalc.GetSpreadsheetControlObject(); + clipele = document.getElementById(s.idPrefix + "clipboardtext"); + document.getElementById(s.idPrefix + "clipboardformat-tab").checked = true; + clipele.value = SocialCalc.ConvertSaveToOtherFormat( + SocialCalc.Clipboard.clipboard, + "tab" + ); + return; + }; + + SocialCalc.SpreadsheetControlClipboardFormat = function (which) { + var s = SocialCalc.GetSpreadsheetControlObject(); + clipele = document.getElementById(s.idPrefix + "clipboardtext"); + clipele.value = SocialCalc.ConvertSaveToOtherFormat( + SocialCalc.Clipboard.clipboard, + which + ); + }; + + SocialCalc.SpreadsheetControlClipboardLoad = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + var savetype = "tab"; + SocialCalc.SetTab(s.tabs[0].name); // return to first tab + SocialCalc.KeyboardFocus(); + if (document.getElementById(s.idPrefix + "clipboardformat-csv").checked) { + savetype = "csv"; + } else if ( + document.getElementById(s.idPrefix + "clipboardformat-scsave").checked + ) { + savetype = "scsave"; + } + s.editor.EditorScheduleSheetCommands( + "loadclipboard " + + SocialCalc.encodeForSave( + SocialCalc.ConvertOtherFormatToSave( + document.getElementById(s.idPrefix + "clipboardtext").value, + savetype + ) + ), + true, + false + ); + }; + + SocialCalc.SpreadsheetControlClipboardClear = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + var clipele = document.getElementById(s.idPrefix + "clipboardtext"); + clipele.value = ""; + s.editor.EditorScheduleSheetCommands("clearclipboard", true, false); + clipele.focus(); + }; + + SocialCalc.SpreadsheetControlClipboardExport = function () { + var s = SocialCalc.GetSpreadsheetControlObject(); + if (s.ExportCallback) { + s.ExportCallback(s); + } + SocialCalc.SetTab(s.tabs[0].name); // return to first tab + SocialCalc.KeyboardFocus(); + }; + + // Settings + + SocialCalc.SpreadsheetControlSettingsSwitch = function (target) { + SocialCalc.SettingControlReset(); + var s = SocialCalc.GetSpreadsheetControlObject(); + var sheettable = document.getElementById(s.idPrefix + "sheetsettingstable"); + var celltable = document.getElementById(s.idPrefix + "cellsettingstable"); + var sheettoolbar = document.getElementById( + s.idPrefix + "sheetsettingstoolbar" + ); + var celltoolbar = document.getElementById( + s.idPrefix + "cellsettingstoolbar" + ); + if (target == "sheet") { + sheettable.style.display = "block"; + celltable.style.display = "none"; + sheettoolbar.style.display = "block"; + celltoolbar.style.display = "none"; + SocialCalc.SettingsControlSetCurrentPanel( + s.views.settings.values.sheetspanel + ); + } else { + sheettable.style.display = "none"; + celltable.style.display = "block"; + sheettoolbar.style.display = "none"; + celltoolbar.style.display = "block"; + SocialCalc.SettingsControlSetCurrentPanel( + s.views.settings.values.cellspanel + ); + } + }; + + SocialCalc.SettingsControlSave = function (target) { + var range, cmdstr; + var s = SocialCalc.GetSpreadsheetControlObject(); + var sc = SocialCalc.SettingsControls; + var panelobj = sc.CurrentPanel; + var attribs = SocialCalc.SettingsControlUnloadPanel(panelobj); + + SocialCalc.SetTab(s.tabs[0].name); // return to first tab + SocialCalc.KeyboardFocus(); + + if (target == "sheet") { + cmdstr = s.sheet.DecodeSheetAttributes(attribs); + } else if (target == "cell") { + if (s.editor.range.hasrange) { + range = + SocialCalc.crToCoord(s.editor.range.left, s.editor.range.top) + + ":" + + SocialCalc.crToCoord(s.editor.range.right, s.editor.range.bottom); + } + cmdstr = s.sheet.DecodeCellAttributes( + s.editor.ecell.coord, + attribs, + range + ); + } else { + // Cancel + } + if (cmdstr) { + s.editor.EditorScheduleSheetCommands(cmdstr, true, false); + } + }; + + /////////////////////// + // + // SAVE / LOAD ROUTINES + // + /////////////////////// + + // + // result = SocialCalc.SpreadsheetControlCreateSpreadsheetSave(spreadsheet, otherparts) + // + // Saves the spreadsheet's sheet data, editor settings, and audit trail (redo stack). + // The serialized data strings are concatenated together in multi-part MIME format. + // The first part lists the types of the subsequent parts (e.g., "sheet", "editor", and "audit") + // in this format: + // # comments + // version:1.0 + // part:type1 + // part:type2 + // ... + // + // If otherparts is non-null, it is an object with: + // partname1: "part contents - should end with \n", + // partname2: "part contents - should end with \n" + // + + SocialCalc.SpreadsheetControlCreateSpreadsheetSave = function ( + spreadsheet, + otherparts + ) { + var result; + + var otherpartsstr = ""; + var otherpartsnames = ""; + var partname, extranl; + + if (otherparts) { + for (partname in otherparts) { + if (otherparts[partname].charAt(otherparts[partname] - 1) != "\n") { + extranl = "\n"; + } else { + extranl = ""; + } + otherpartsstr += + "--" + + spreadsheet.multipartBoundary + + "\nContent-type: text/plain; charset=UTF-8\n\n" + + otherparts[partname] + + extranl; + otherpartsnames += "part:" + partname + "\n"; + } + } + + result = + "socialcalc:version:1.0\n" + + "MIME-Version: 1.0\nContent-Type: multipart/mixed; boundary=" + + spreadsheet.multipartBoundary + + "\n" + + "--" + + spreadsheet.multipartBoundary + + "\nContent-type: text/plain; charset=UTF-8\n\n" + + "# SocialCalc Spreadsheet Control Save\nversion:1.0\npart:sheet\npart:edit\npart:audit\n" + + otherpartsnames + + "--" + + spreadsheet.multipartBoundary + + "\nContent-type: text/plain; charset=UTF-8\n\n" + + spreadsheet.CreateSheetSave() + + "--" + + spreadsheet.multipartBoundary + + "\nContent-type: text/plain; charset=UTF-8\n\n" + + spreadsheet.editor.SaveEditorSettings() + + "--" + + spreadsheet.multipartBoundary + + "\nContent-type: text/plain; charset=UTF-8\n\n" + + spreadsheet.sheet.CreateAuditString() + + otherpartsstr + + "--" + + spreadsheet.multipartBoundary + + "--\n"; + + return result; + }; + + // + // parts = SocialCalc.SpreadsheetControlDecodeSpreadsheetSave(spreadsheet, str) + // + // Separates the parts from a spreadsheet save string, returning an object with the sub-strings. + // + // {type1: {start: startpos, end: endpos}, type2:...} + // + + SocialCalc.SpreadsheetControlDecodeSpreadsheetSave = function ( + spreadsheet, + str + ) { + var pos1, + mpregex, + searchinfo, + boundary, + boundaryregex, + blanklineregex, + start, + ending, + lines, + i, + lines, + p, + pnun; + var parts = {}; + var partlist = []; + + pos1 = str.search(/^MIME-Version:\s1\.0/im); + if (pos1 < 0) return parts; + + mpregex = /^Content-Type:\s*multipart\/mixed;\s*boundary=(\S+)/gim; + mpregex.lastIndex = pos1; + + searchinfo = mpregex.exec(str); + if (mpregex.lastIndex <= 0) return parts; + boundary = searchinfo[1]; + + boundaryregex = new RegExp("^--" + boundary + "(?:\r\n|\n)", "mg"); + boundaryregex.lastIndex = mpregex.lastIndex; + + searchinfo = boundaryregex.exec(str); // find header top boundary + blanklineregex = /(?:\r\n|\n)(?:\r\n|\n)/gm; + blanklineregex.lastIndex = boundaryregex.lastIndex; + searchinfo = blanklineregex.exec(str); // skip to after blank line + if (!searchinfo) return parts; + start = blanklineregex.lastIndex; + boundaryregex.lastIndex = start; + searchinfo = boundaryregex.exec(str); // find end of header + if (!searchinfo) return parts; + ending = searchinfo.index; + + lines = str.substring(start, ending).split(/\r\n|\n/); // get header as lines + for (i = 0; i < lines.length; i++) { + line = lines[i]; + p = line.split(":"); + switch (p[0]) { + case "version": + break; + case "part": + partlist.push(p[1]); + break; + } + } + + for (pnum = 0; pnum < partlist.length; pnum++) { + // get each part + blanklineregex.lastIndex = ending; + searchinfo = blanklineregex.exec(str); // find blank line ending mime-part header + if (!searchinfo) return parts; + start = blanklineregex.lastIndex; + if (pnum == partlist.length - 1) { + // last one has different boundary + boundaryregex = new RegExp("^--" + boundary + "--$", "mg"); + } + boundaryregex.lastIndex = start; + searchinfo = boundaryregex.exec(str); // find ending boundary + if (!searchinfo) return parts; + ending = searchinfo.index; + parts[partlist[pnum]] = { start: start, end: ending }; // return position within full string + } + + return parts; + }; + + /* + * SettingsControls + * + * Each settings panel has an object in the following form: + * + * {ctrl-name1: {setting: setting-nameA, type: ctrl-type, id: id-component}, + * ctrl-name2: {setting: setting-nameB, type: ctrl-type, id: id-component, initialdata: optional-initialdata-override}, + * ...} + * + * The ctrl-types are names that correspond to: + * + * SocialCalc.SettingsControls.Controls = { + * ctrl-type1: { + * SetValue: function(panel-obj, ctrl-name, {def: true/false, val: value}) {...;}, + * ColorValues: if true, Onchanged converts between hex and RGB + * GetValue: function(panel-obj, ctrl-name) {...return {def: true/false, val: value};}, + * Initialize: function(panel-obj, ctrl-name) {...;}, // used to fill dropdowns, etc. + * InitialData: control-dependent, // used by Initialize (if no panel ctrlname.initialdata) + * OnReset: function(ctrl-name) {...;}, // called to put down popups, etc. + * ChangedCallback: function(ctrl-name) {...;} // if not null, called by control when user changes value + * } + * + */ + + SocialCalc.SettingsControls = { + Controls: {}, + CurrentPanel: null, // panel object to search on events + }; + + // + // SocialCalc.SettingsControlSetCurrentPanel(panel-object) + // + + SocialCalc.SettingsControlSetCurrentPanel = function (panelobj) { + SocialCalc.SettingsControls.CurrentPanel = panelobj; + + SocialCalc.SettingsControls.PopupChangeCallback( + { panelobj: panelobj }, + "", + null + ); + }; + + // + // SocialCalc.SettingsControlInitializePanel(panel-object) + // + + SocialCalc.SettingsControlInitializePanel = function (panelobj) { + var ctrlname; + var sc = SocialCalc.SettingsControls; + var ctrl; + + for (ctrlname in panelobj) { + if (ctrlname == "name") continue; + ctrl = sc.Controls[panelobj[ctrlname].type]; + if (ctrl && ctrl.Initialize) ctrl.Initialize(panelobj, ctrlname); + } + }; + + // + // SocialCalc.SettingsControlLoadPanel(panel-object, attribs) + // + + SocialCalc.SettingsControlLoadPanel = function (panelobj, attribs) { + var ctrlname; + var sc = SocialCalc.SettingsControls; + + for (ctrlname in panelobj) { + if (ctrlname == "name") continue; + ctrl = sc.Controls[panelobj[ctrlname].type]; + if (ctrl && ctrl.SetValue) + ctrl.SetValue(panelobj, ctrlname, attribs[panelobj[ctrlname].setting]); + } + }; + + // + // attribs = SocialCalc.SettingsControlUnloadPanel(panel-object) + // + + SocialCalc.SettingsControlUnloadPanel = function (panelobj) { + var ctrlname; + var sc = SocialCalc.SettingsControls; + var attribs = {}; + + for (ctrlname in panelobj) { + if (ctrlname == "name") continue; + ctrl = sc.Controls[panelobj[ctrlname].type]; + if (ctrl && ctrl.GetValue) + attribs[panelobj[ctrlname].setting] = ctrl.GetValue(panelobj, ctrlname); + } + + return attribs; + }; + + // + // SocialCalc.SettingsControls.PopupChangeCallback + // + + SocialCalc.SettingsControls.PopupChangeCallback = function ( + attribs, + id, + value + ) { + var sc = SocialCalc.Constants; + + var ele = document.getElementById("sample-text"); + + if (!ele || !attribs || !attribs.panelobj) return; + + var idPrefix = SocialCalc.CurrentSpreadsheetControlObject.idPrefix; + + var c = attribs.panelobj.name == "cell" ? "c" : ""; + + var v, a, parts, str1, str2, i; + + parts = + sc.defaultCellLayout.match( + /^padding.(\S+) (\S+) (\S+) (\S+).vertical.align.(\S+);$/ + ) || []; + + var cv = { + color: ["textcolor"], + backgroundColor: ["bgcolor", "#FFF"], + fontSize: ["fontsize", sc.defaultCellFontSize], + fontFamily: ["fontfamily"], + paddingTop: ["padtop", parts[1]], + paddingRight: ["padright", parts[2]], + paddingBottom: ["padbottom", parts[3]], + paddingLeft: ["padleft", parts[4]], + verticalAlign: ["alignvert", parts[5]], + }; + + for (a in cv) { + v = SocialCalc.Popup.GetValue(idPrefix + c + cv[a][0]) || cv[a][1] || ""; + ele.style[a] = v; + } + + if (c == "c") { + cv = { + borderTop: "cbt", + borderRight: "cbr", + borderBottom: "cbb", + borderLeft: "cbl", + }; + for (a in cv) { + v = SocialCalc.SettingsControls.BorderSideGetValue( + attribs.panelobj, + cv[a] + ); + ele.style[a] = v ? v.val || "" : ""; + } + v = SocialCalc.Popup.GetValue(idPrefix + "calignhoriz"); + ele.style.textAlign = v || "left"; + ele.childNodes[1].style.textAlign = v || "right"; + } else { + ele.style.border = ""; + v = SocialCalc.Popup.GetValue(idPrefix + "textalignhoriz"); + ele.style.textAlign = v || "left"; + v = SocialCalc.Popup.GetValue(idPrefix + "numberalignhoriz"); + ele.childNodes[1].style.textAlign = v || "right"; + } + + v = SocialCalc.Popup.GetValue(idPrefix + c + "fontlook"); + parts = v ? v.match(/^(\S+) (\S+)$/) || [] : []; + ele.style.fontStyle = parts[1] || ""; + ele.style.fontWeight = parts[2] || ""; + + v = SocialCalc.Popup.GetValue(idPrefix + c + "formatnumber") || "General"; + str1 = SocialCalc.FormatNumber.formatNumberWithFormat(9.8765, v, ""); + str2 = SocialCalc.FormatNumber.formatNumberWithFormat(-1234.5, v, ""); + if (str2 != "??-???-?? ??:??:??") { + // not bad date from negative number + str1 += "
" + str2; + } + + ele.childNodes[1].innerHTML = str1; + }; + + // + // PopupList Control + // + + SocialCalc.SettingsControls.PopupListSetValue = function ( + panelobj, + ctrlname, + value + ) { + if (!value) { + alert(ctrlname + " no value"); + return; + } + + var sp = SocialCalc.Popup; + + if (!value.def) { + sp.SetValue(panelobj[ctrlname].id, value.val); + } else { + sp.SetValue(panelobj[ctrlname].id, ""); + } + }; + + // + // SocialCalc.SettingsControls.PopupListGetValue + // + + SocialCalc.SettingsControls.PopupListGetValue = function ( + panelobj, + ctrlname + ) { + var ctl = panelobj[ctrlname]; + if (!ctl) return null; + + var value = SocialCalc.Popup.GetValue(ctl.id); + if (value) { + return { def: false, val: value }; + } else { + return { def: true, val: 0 }; + } + }; + + // + // SocialCalc.SettingsControls.PopupListInitialize + // + + SocialCalc.SettingsControls.PopupListInitialize = function ( + panelobj, + ctrlname + ) { + var i, val, pos, otext; + var sc = SocialCalc.SettingsControls; + var initialdata = + panelobj[ctrlname].initialdata || + sc.Controls[panelobj[ctrlname].type].InitialData || + ""; + initialdata = SocialCalc.LocalizeSubstrings(initialdata); + var optionvals = initialdata.split(/\|/); + + var options = []; + + for (i = 0; i < (optionvals.length || 0); i++) { + val = optionvals[i]; + pos = val.indexOf(":"); + otext = val.substring(0, pos); + if (otext.indexOf("\\") != -1) { + // escape any colons + otext = otext.replace(/\\c/g, ":"); + otext = otext.replace(/\\b/g, "\\"); + } + otext = SocialCalc.special_chars(otext); + if (otext == "[custom]") { + options[i] = { + o: SocialCalc.Constants.s_PopupListCustom, + v: val.substring(pos + 1), + a: { custom: true }, + }; + } else if (otext == "[cancel]") { + options[i] = { + o: SocialCalc.Constants.s_PopupListCancel, + v: "", + a: { cancel: true }, + }; + } else if (otext == "[break]") { + options[i] = { o: "-----", v: "", a: { skip: true } }; + } else if (otext == "[newcol]") { + options[i] = { o: "", v: "", a: { newcol: true } }; + } else { + options[i] = { o: otext, v: val.substring(pos + 1) }; + } + } + + SocialCalc.Popup.Create("List", panelobj[ctrlname].id, {}); + SocialCalc.Popup.Initialize(panelobj[ctrlname].id, { + options: options, + attribs: { + changedcallback: SocialCalc.SettingsControls.PopupChangeCallback, + panelobj: panelobj, + }, + }); + }; + + // + // SocialCalc.SettingsControls.PopupListReset + // + + SocialCalc.SettingsControls.PopupListReset = function (ctrlname) { + SocialCalc.Popup.Reset("List"); + }; + + SocialCalc.SettingsControls.Controls.PopupList = { + SetValue: SocialCalc.SettingsControls.PopupListSetValue, + GetValue: SocialCalc.SettingsControls.PopupListGetValue, + Initialize: SocialCalc.SettingsControls.PopupListInitialize, + OnReset: SocialCalc.SettingsControls.PopupListReset, + ChangedCallback: null, + }; + + // + // ColorChooser Control + // + + SocialCalc.SettingsControls.ColorChooserSetValue = function ( + panelobj, + ctrlname, + value + ) { + if (!value) { + alert(ctrlname + " no value"); + return; + } + + var sp = SocialCalc.Popup; + + if (!value.def) { + sp.SetValue(panelobj[ctrlname].id, value.val); + } else { + sp.SetValue(panelobj[ctrlname].id, ""); + } + }; + + // + // SocialCalc.SettingsControls.ColorChooserGetValue + // + + SocialCalc.SettingsControls.ColorChooserGetValue = function ( + panelobj, + ctrlname + ) { + var value = SocialCalc.Popup.GetValue(panelobj[ctrlname].id); + if (value) { + return { def: false, val: value }; + } else { + return { def: true, val: 0 }; + } + }; + + // + // SocialCalc.SettingsControls.ColorChooserInitialize + // + + SocialCalc.SettingsControls.ColorChooserInitialize = function ( + panelobj, + ctrlname + ) { + var i, val, pos, otext; + var sc = SocialCalc.SettingsControls; + + SocialCalc.Popup.Create("ColorChooser", panelobj[ctrlname].id, {}); + SocialCalc.Popup.Initialize(panelobj[ctrlname].id, { + attribs: { + title: " ", + moveable: true, + width: "106px", + changedcallback: SocialCalc.SettingsControls.PopupChangeCallback, + panelobj: panelobj, + }, + }); + }; + + // + // SocialCalc.SettingsControls.ColorChooserReset + // + + SocialCalc.SettingsControls.ColorChooserReset = function (ctrlname) { + SocialCalc.Popup.Reset("ColorChooser"); + }; + + SocialCalc.SettingsControls.Controls.ColorChooser = { + SetValue: SocialCalc.SettingsControls.ColorChooserSetValue, + GetValue: SocialCalc.SettingsControls.ColorChooserGetValue, + Initialize: SocialCalc.SettingsControls.ColorChooserInitialize, + OnReset: SocialCalc.SettingsControls.ColorChooserReset, + ChangedCallback: null, + }; + + // + // SocialCalc.SettingsControls.BorderSideSetValue + // + + SocialCalc.SettingsControls.BorderSideSetValue = function ( + panelobj, + ctrlname, + value + ) { + var sc = SocialCalc.SettingsControls; + var ele, found, idname, parts; + var idstart = panelobj[ctrlname].id; + + if (!value) { + alert(ctrlname + " no value"); + return; + } + + ele = document.getElementById(idstart + "-onoff-bcb"); // border checkbox + if (!ele) return; + + if (value.val) { + // border does not use default: it looks only to the value currently + ele.checked = true; + ele.value = value.val; + parts = value.val.match(/(\S+)\s+(\S+)\s+(\S.+)/); + idname = idstart + "-color"; + SocialCalc.Popup.SetValue(idname, parts[3]); + SocialCalc.Popup.SetDisabled(idname, false); + } else { + ele.checked = false; + ele.value = value.val; + idname = idstart + "-color"; + SocialCalc.Popup.SetValue(idname, ""); + SocialCalc.Popup.SetDisabled(idname, true); + } + }; + + // + // SocialCalc.SettingsControls.BorderSideGetValue + // + + SocialCalc.SettingsControls.BorderSideGetValue = function ( + panelobj, + ctrlname + ) { + var sc = SocialCalc.SettingsControls; + var ele, value; + var idstart = panelobj[ctrlname].id; + + ele = document.getElementById(idstart + "-onoff-bcb"); // border checkbox + if (!ele) return; + + if (ele.checked) { + // on + value = SocialCalc.Popup.GetValue(idstart + "-color"); + value = "1px solid " + (value || "rgb(0,0,0)"); + return { def: false, val: value }; + } else { + // off + return { def: false, val: "" }; + } + }; + + // + // SocialCalc.SettingsControls.BorderSideInitialize + // + + SocialCalc.SettingsControls.BorderSideInitialize = function ( + panelobj, + ctrlname + ) { + var sc = SocialCalc.SettingsControls; + var idstart = panelobj[ctrlname].id; + + SocialCalc.Popup.Create("ColorChooser", idstart + "-color", {}); + SocialCalc.Popup.Initialize(idstart + "-color", { + attribs: { + title: " ", + width: "106px", + moveable: true, + changedcallback: SocialCalc.SettingsControls.PopupChangeCallback, + panelobj: panelobj, + }, + }); + }; + + // + // SocialCalc.SettingsControlOnchangeBorder = function(ele) + // + + SocialCalc.SettingsControlOnchangeBorder = function (ele) { + var idname, value, found, ele2; + var sc = SocialCalc.SettingsControls; + var panelobj = sc.CurrentPanel; + + var nameparts = ele.id.match(/(^.*\-)(\w+)\-(\w+)\-(\w+)$/); + if (!nameparts) return; + var prefix = nameparts[1]; + var ctrlname = nameparts[2]; + var ctrlsubid = nameparts[3]; + var ctrlidsuffix = nameparts[4]; + var ctrltype = panelobj[ctrlname].type; + + switch (ctrlidsuffix) { + case "bcb": // border checkbox + if (ele.checked) { + sc.Controls[ctrltype].SetValue(sc.CurrentPanel, ctrlname, { + def: false, + val: ele.value || "1px solid rgb(0,0,0)", + }); + } else { + sc.Controls[ctrltype].SetValue(sc.CurrentPanel, ctrlname, { + def: false, + val: "", + }); + } + break; + } + }; + + SocialCalc.SettingsControls.Controls.BorderSide = { + SetValue: SocialCalc.SettingsControls.BorderSideSetValue, + GetValue: SocialCalc.SettingsControls.BorderSideGetValue, + OnClick: SocialCalc.SettingsControls.ColorComboOnClick, + Initialize: SocialCalc.SettingsControls.BorderSideInitialize, + InitialData: { thickness: "1 pixel:1px", style: "Solid:solid" }, + ChangedCallback: null, + }; + + SocialCalc.SettingControlReset = function () { + var sc = SocialCalc.SettingsControls; + var ctrlname; + + for (ctrlname in sc.Controls) { + if (sc.Controls[ctrlname].OnReset) + sc.Controls[ctrlname].OnReset(ctrlname); + } + }; + + /********************** + * + * CtrlSEditor implementation for editing SocialCalc.OtherSaveParts + * + */ + + SocialCalc.OtherSaveParts = {}; // holds other parts to save - must be set when loaded if you want to keep + + SocialCalc.CtrlSEditor = function (whichpart) { + var strtoedit, partname; + if (whichpart.length > 0) { + strtoedit = SocialCalc.special_chars( + SocialCalc.OtherSaveParts[whichpart] || "" + ); + } else { + strtoedit = "Listing of Parts\n"; + for (partname in SocialCalc.OtherSaveParts) { + strtoedit += SocialCalc.special_chars( + "\nPart: " + + partname + + "\n=====\n" + + SocialCalc.OtherSaveParts[partname] + + "\n" + ); + } + } + var editbox = document.createElement("div"); + editbox.style.cssText = + "position:absolute;z-index:500;width:300px;height:300px;left:100px;top:200px;border:1px solid black;background-color:#EEE;text-align:center;"; + editbox.id = "socialcalc-editbox"; + editbox.innerHTML = + whichpart + + '



'; + document.body.appendChild(editbox); + + var ebta = document.getElementById("socialcalc-editbox-textarea"); + ebta.focus(); + SocialCalc.CmdGotFocus(ebta); + }; + + SocialCalc.CtrlSEditorDone = function (idprefix, whichpart) { + var edittextarea = document.getElementById(idprefix + "-textarea"); + var text = edittextarea.value; + if (whichpart.length > 0) { + if (text.length > 0) { + SocialCalc.OtherSaveParts[whichpart] = text; + } else { + delete SocialCalc.OtherSaveParts[whichpart]; + } + } + + var editbox = document.getElementById(idprefix); + SocialCalc.KeyboardFocus(); + editbox.parentNode.removeChild(editbox); + }; + + // + // Workbook is a collection of sheets that are worked upon together + // + // The WorkBook class models and manages the collection of sheets + // + // Author: Ramu Ramamurthy + // + // + + var SocialCalc; + if (!SocialCalc) { + alert("Main SocialCalc code module needed"); + SocialCalc = {}; + } + + // Constructor: + + SocialCalc.WorkBook = function (spread) { + this.spreadsheet = spread; // this is the spreadsheet control + this.defaultsheetname = null; + this.sheetArr = {}; // misnomer, this is not really an array + this.clipsheet = {}; // for copy paste of sheets + }; + + // Methods + + SocialCalc.WorkBook.prototype.InitializeWorkBook = function (defaultsheet) { + return SocialCalc.InitializeWorkBook(this, defaultsheet); + }; + + SocialCalc.WorkBook.prototype.AddNewWorkBookSheetNoSwitch = function ( + sheetid, + sheetname, + savestr + ) { + return SocialCalc.AddNewWorkBookSheetNoSwitch( + this, + sheetid, + sheetname, + savestr + ); + }; + SocialCalc.WorkBook.prototype.AddNewWorkBookSheet = function ( + sheetname, + oldsheetname, + fromclip, + spread + ) { + return SocialCalc.AddNewWorkBookSheet( + this, + sheetname, + oldsheetname, + fromclip, + spread + ); + }; + SocialCalc.WorkBook.prototype.ActivateWorkBookSheet = function ( + sheetname, + oldsheetname + ) { + return SocialCalc.ActivateWorkBookSheet(this, sheetname, oldsheetname); + }; + SocialCalc.WorkBook.prototype.DeleteWorkBookSheet = function ( + sheetname, + cursheetname + ) { + return SocialCalc.DeleteWorkBookSheet(this, sheetname, cursheetname); + }; + SocialCalc.WorkBook.prototype.SaveWorkBookSheet = function (sheetid) { + return SocialCalc.SaveWorkBookSheet(this, sheetid); + }; + SocialCalc.WorkBook.prototype.LoadRenameWorkBookSheet = function ( + sheetid, + savestr, + newname + ) { + return SocialCalc.LoadRenameWorkBookSheet(this, sheetid, savestr, newname); + }; + SocialCalc.WorkBook.prototype.RenameWorkBookSheet = function ( + oldname, + newname, + sheetid + ) { + return SocialCalc.RenameWorkBookSheet(this, oldname, newname, sheetid); + }; + SocialCalc.WorkBook.prototype.CopyWorkBookSheet = function (sheetid) { + return SocialCalc.CopyWorkBookSheet(this, sheetid); + }; + SocialCalc.WorkBook.prototype.PasteWorkBookSheet = function (newid, oldid) { + return SocialCalc.PasteWorkBookSheet(this, newid, oldid); + }; + SocialCalc.WorkBook.prototype.RenderWorkBookSheet = function () { + return SocialCalc.RenderWorkBookSheet(this); + }; + + SocialCalc.WorkBook.prototype.SheetNameExistsInWorkBook = function (name) { + return SocialCalc.SheetNameExistsInWorkBook(this, name); + }; + + SocialCalc.WorkBook.prototype.WorkbookScheduleCommand = function ( + cmd, + isremote + ) { + return SocialCalc.WorkbookScheduleCommand(this, cmd, isremote); + }; + + SocialCalc.WorkBook.prototype.WorkbookScheduleSheetCommand = function ( + cmd, + isremote + ) { + return SocialCalc.WorkbookScheduleSheetCommand(this, cmd, isremote); + }; + + // schedule some command - could be for sheet or for the workbook itself + SocialCalc.WorkbookScheduleCommand = function WorkbookScheduleCommand( + workbook, + cmd, + isremote + ) { + //console.log("cmd ", cmd.cmdstr, cmd.cmdtype); + + if (cmd.cmdtype == "scmd") { + workbook.WorkbookScheduleSheetCommand(cmd, isremote); + } + }; + + SocialCalc.WorkbookScheduleSheetCommand = + function WorkbookScheduleSheetCommand(workbook, cmd, isremote) { + //console.log(cmd.cmdtype,cmd.id,cmd.cmdstr); + + // check if sheet exists first + if (workbook.sheetArr[cmd.id]) { + workbook.sheetArr[cmd.id].sheet.ScheduleSheetCommands( + cmd.cmdstr, + cmd.saveundo, + isremote + ); + } + }; + + SocialCalc.InitializeWorkBook = function InitializeWorkBook( + workbook, + defaultsheet + ) { + workbook.defaultsheetname = defaultsheet; + + var spreadsheet = workbook.spreadsheet; + var defaultsheetname = workbook.defaultsheetname; + + // Initialize the Spreadsheet Control and display it + + SocialCalc.Formula.SheetCache.sheets[defaultsheetname] = { + sheet: spreadsheet.sheet, + name: defaultsheetname, + }; + + spreadsheet.sheet.sheetid = defaultsheetname; + spreadsheet.sheet.sheetname = defaultsheetname; + + workbook.sheetArr[defaultsheetname] = {}; + workbook.sheetArr[defaultsheetname].sheet = spreadsheet.sheet; + workbook.sheetArr[defaultsheetname].context = spreadsheet.context; + + // if these were properties of the sheet, then we wouldnt need to do this ! + workbook.sheetArr[defaultsheetname].editorprop = {}; + workbook.sheetArr[defaultsheetname].editorprop.ecell = null; + workbook.sheetArr[defaultsheetname].editorprop.range = null; + workbook.sheetArr[defaultsheetname].editorprop.range2 = null; + + workbook.clipsheet.savestr = null; + workbook.clipsheet.copiedfrom = null; + workbook.clipsheet.editorprop = {}; + + spreadsheet.editor.workingvalues.currentsheet = spreadsheet.sheet.sheetname; + spreadsheet.editor.workingvalues.startsheet = + spreadsheet.editor.workingvalues.currentsheet; + spreadsheet.editor.workingvalues.currentsheetid = spreadsheet.sheet.sheetid; + }; + + SocialCalc.AddNewWorkBookSheetNoSwitch = function AddNewWorkBookSheetNoSwitch( + workbook, + sheetid, + sheetname, + savestr + ) { + //alert(sheetid+","+sheetname+","+savestr); + + var spreadsheet = workbook.spreadsheet; + + var newsheet = new SocialCalc.Sheet(); + + SocialCalc.Formula.SheetCache.sheets[sheetname] = { + sheet: newsheet, + name: sheetname, + }; + + newsheet.sheetid = sheetid; + newsheet.sheetname = sheetname; + + if (savestr) { + newsheet.ParseSheetSave(savestr); + } + + workbook.sheetArr[sheetid] = {}; + workbook.sheetArr[sheetid].sheet = newsheet; + workbook.sheetArr[sheetid].context = null; + + if (workbook.sheetArr[sheetid].sheet.attribs) { + workbook.sheetArr[sheetid].sheet.attribs.needsrecalc = "yes"; + } + + workbook.sheetArr[sheetid].editorprop = {}; + workbook.sheetArr[sheetid].editorprop.ecell = { + coord: "A1", + row: 1, + col: 1, + }; + workbook.sheetArr[sheetid].editorprop.range = null; + workbook.sheetArr[sheetid].editorprop.range2 = null; + }; + + SocialCalc.AddNewWorkBookSheet = function AddNewWorkBookSheet( + workbook, + sheetid, + oldsheetid, + fromclip, + spread + ) { + var spreadsheet = workbook.spreadsheet; + + //alert("create new sheet "+sheetid+" old="+oldsheetid+" def="+workbook.defaultsheetname); + + if (spread == null) { + spreadsheet.sheet = new SocialCalc.Sheet(); + SocialCalc.Formula.SheetCache.sheets[sheetid] = { + sheet: spreadsheet.sheet, + name: sheetid, + }; + spreadsheet.sheet.sheetid = sheetid; + spreadsheet.sheet.sheetname = sheetid; + } else { + //alert("existing spread") + spreadsheet.sheet = spread; + } + + spreadsheet.context = new SocialCalc.RenderContext(spreadsheet.sheet); + + spreadsheet.sheet.statuscallback = SocialCalc.EditorSheetStatusCallback; + spreadsheet.sheet.statuscallbackparams = spreadsheet.editor; + + workbook.sheetArr[sheetid] = {}; + workbook.sheetArr[sheetid].sheet = spreadsheet.sheet; + workbook.sheetArr[sheetid].context = spreadsheet.context; + + workbook.sheetArr[sheetid].editorprop = {}; + workbook.sheetArr[sheetid].editorprop.ecell = null; + workbook.sheetArr[sheetid].editorprop.range = null; + workbook.sheetArr[sheetid].editorprop.range2 = null; + + if (oldsheetid != null) { + workbook.sheetArr[oldsheetid].editorprop.ecell = spreadsheet.editor.ecell; + workbook.sheetArr[oldsheetid].editorprop.range = spreadsheet.editor.range; + workbook.sheetArr[oldsheetid].editorprop.range2 = + spreadsheet.editor.range2; + } + + spreadsheet.context.showGrid = true; + spreadsheet.context.showRCHeaders = true; + spreadsheet.editor.context = spreadsheet.context; + + if (!fromclip) { + spreadsheet.editor.ecell = { + coord: "A1", + row: 1, + col: 1, + }; + + spreadsheet.editor.range = { + hasrange: false, + }; + spreadsheet.editor.range2 = { + hasrange: false, + }; + } + + // set highlights + spreadsheet.context.highlights[spreadsheet.editor.ecell.coord] = "cursor"; + + if (fromclip) { + // this is the result of a paste sheet + //alert("from clip"); + + if (workbook.clipsheet.savestr != null) { + //alert("sheetdata = "+workbook.clipsheet.savestr); + spreadsheet.sheet.ParseSheetSave(workbook.clipsheet.savestr); + } + + spreadsheet.editor.ecell = workbook.clipsheet.editorprop.ecell; + spreadsheet.context.highlights[spreadsheet.editor.ecell.coord] = "cursor"; + + // range is not pasted ??!?? + } + + spreadsheet.editor.workingvalues.currentsheet = spreadsheet.sheet.sheetname; + spreadsheet.editor.workingvalues.startsheet = + spreadsheet.editor.workingvalues.currentsheet; + spreadsheet.editor.workingvalues.currentsheetid = spreadsheet.sheet.sheetid; + + spreadsheet.editor.FitToEditTable(); + spreadsheet.editor.ScheduleRender(); + //spreadsheet.ExecuteCommand('recalc', ''); + }; + + SocialCalc.ActivateWorkBookSheet = function ActivateWorkBookSheet( + workbook, + sheetnamestr, + oldsheetnamestr + ) { + var spreadsheet = workbook.spreadsheet; + + //alert("activate "+sheetnamestr+" old="+oldsheetnamestr); + + spreadsheet.sheet = workbook.sheetArr[sheetnamestr].sheet; + spreadsheet.context = workbook.sheetArr[sheetnamestr].context; + + if (spreadsheet.context == null) { + //alert("context null") + //for (var sheet in workbook.sheetArr) alert(sheet+spreadsheet.sheet ) + workbook.AddNewWorkBookSheet( + sheetnamestr, + oldsheetnamestr, + false, + spreadsheet.sheet + ); + return; + } + + spreadsheet.editor.context = spreadsheet.context; + + if (oldsheetnamestr != null) { + workbook.sheetArr[oldsheetnamestr].editorprop.ecell = + spreadsheet.editor.ecell; + } + spreadsheet.editor.ecell = workbook.sheetArr[sheetnamestr].editorprop.ecell; + + if (oldsheetnamestr != null) { + workbook.sheetArr[oldsheetnamestr].editorprop.range = + spreadsheet.editor.range; + } + spreadsheet.editor.range = workbook.sheetArr[sheetnamestr].editorprop.range; + + if (oldsheetnamestr != null) { + workbook.sheetArr[oldsheetnamestr].editorprop.range2 = + spreadsheet.editor.range2; + } + spreadsheet.editor.range2 = + workbook.sheetArr[sheetnamestr].editorprop.range2; + + spreadsheet.sheet.statuscallback = SocialCalc.EditorSheetStatusCallback; + spreadsheet.sheet.statuscallbackparams = spreadsheet.editor; + + // reset highlights ?? + + //spreadsheet.editor.FitToEditTable(); + + spreadsheet.editor.workingvalues.currentsheet = spreadsheet.sheet.sheetname; + spreadsheet.editor.workingvalues.currentsheetid = spreadsheet.sheet.sheetid; + + if (spreadsheet.editor.state != "start" && spreadsheet.editor.inputBox) + spreadsheet.editor.inputBox.element.focus(); + + if (spreadsheet.editor.state == "start") { + spreadsheet.editor.workingvalues.startsheet = + spreadsheet.editor.workingvalues.currentsheet; + } + + //spreadsheet.editor.ScheduleRender(); + + if (spreadsheet.editor.state != "start" && spreadsheet.editor.inputBox) { + spreadsheet.editor.ScheduleRender(); + } else { + if (spreadsheet.sheet.attribs) { + spreadsheet.sheet.attribs.needsrecalc = "yes"; + } else { + spreadsheet.sheet.attribs = {}; + spreadsheet.sheet.attribs.needsrecalc = "yes"; + } + + spreadsheet.ExecuteCommand("redisplay", ""); + } + }; + + SocialCalc.DeleteWorkBookSheet = function DeleteWorkBookSheet( + workbook, + oldname, + curname + ) { + //alert("delete "+oldname+","+curname); + + delete workbook.sheetArr[oldname].context; + delete workbook.sheetArr[oldname].sheet; + delete workbook.sheetArr[oldname]; + // take sheet out of the formula cache + delete SocialCalc.Formula.SheetCache.sheets[curname]; + }; + + SocialCalc.SaveWorkBookSheet = function CreateSaveWorkBook( + workbook, + sheetid + ) { + var sheetstr = {}; + sheetstr.savestr = workbook.sheetArr[sheetid].sheet.CreateSheetSave(); + return sheetstr; + }; + + SocialCalc.LoadRenameWorkBookSheet = function LoadRenameWorkBookSheet( + workbook, + sheetid, + savestr, + newname + ) { + workbook.sheetArr[sheetid].sheet.ResetSheet(); + workbook.sheetArr[sheetid].sheet.ParseSheetSave(savestr); + + if (workbook.sheetArr[sheetid].sheet.attribs) { + workbook.sheetArr[sheetid].sheet.attribs.needsrecalc = "yes"; + } + + delete SocialCalc.Formula.SheetCache.sheets[ + workbook.sheetArr[sheetid].sheet.sheetname + ]; + workbook.sheetArr[sheetid].sheet.sheetname = newname; + SocialCalc.Formula.SheetCache.sheets[newname] = { + sheet: workbook.sheetArr[sheetid].sheet, + name: newname, + }; + }; + + SocialCalc.RenderWorkBookSheet = function RenderWorkBookSheet(workbook) { + workbook.spreadsheet.editor.ScheduleRender(); + }; + + SocialCalc.RenameWorkBookSheetCell = function (formula, oldname, newname) { + var ttype, ttext, i, newcr; + var updatedformula = ""; + var sheetref = false; + var scf = SocialCalc.Formula; + if (!scf) { + return "Need SocialCalc.Formula"; + } + var tokentype = scf.TokenType; + var token_op = tokentype.op; + var token_string = tokentype.string; + var token_coord = tokentype.coord; + var tokenOpExpansion = scf.TokenOpExpansion; + + var parseinfo = SocialCalc.Formula.ParseFormulaIntoTokens(formula); + + for (i = 0; i < parseinfo.length; i++) { + ttype = parseinfo[i].type; + ttext = parseinfo[i].text; + //alert(ttype+","+ttext); + //console.log (scf.NormalizeSheetName(ttext) + " " + oldname); + if ( + ttype == tokentype.name && + scf.NormalizeSheetName(ttext) == oldname && + i < parseinfo.length + ) { + if (parseinfo[i + 1].type == token_op && parseinfo[i + 1].text == "!") { + updatedformula += newname; //console.log (updatedformula); + } else { + updatedformula += ttext; //console.log (updatedformula); + } + } else { + updatedformula += ttext; + } + } + //alert(updatedformula); + return updatedformula; + }; + + SocialCalc.RenameWorkBookSheet = function RenameWorkBookSheet( + workbook, + oldname, + newname, + sheetid + ) { + // for each sheet, fix up all the formula references + // + //alert (sheetid); + var oldsheet = SocialCalc.Formula.SheetCache.sheets[oldname].sheet; + delete SocialCalc.Formula.SheetCache.sheets[oldname]; + //alert (newname); // to check the newname + SocialCalc.Formula.SheetCache.sheets[newname] = { + sheet: oldsheet, + name: newname, + }; + workbook.sheetArr[sheetid].sheet.sheetname = newname; + // + // fix up formulas for sheet rename + // if formulas should not be fixed up upon sheet rename, then comment out the following + // block + // + for (var sheet in workbook.sheetArr) { + //alert("found sheet-"+sheet) + for (var cr in workbook.sheetArr[sheet].sheet.cells) { + // update cell references to sheet name + //alert(cr); + var cell = workbook.sheetArr[sheet].sheet.cells[cr]; + //if (cell) alert(cell.datatype) + if (cell && cell.datatype == "f") { + cell.formula = SocialCalc.RenameWorkBookSheetCell( + cell.formula, + oldname, + newname + ); + if (cell.parseinfo) { + delete cell.parseinfo; + } + } + } + } + // recalculate + workbook.spreadsheet.ExecuteCommand("recalc", ""); + }; + + SocialCalc.CopyWorkBookSheet = function CopyWorkBookSheet(workbook, sheetid) { + //alert("in copy "+sheetid); + workbook.clipsheet.savestr = + workbook.sheetArr[sheetid].sheet.CreateSheetSave(); + //alert("in copy save="+workbook.clipsheet.savestr); + workbook.clipsheet.copiedfrom = sheetid; + workbook.clipsheet.editorprop = {}; + workbook.clipsheet.editorprop.ecell = workbook.spreadsheet.editor.ecell; + //workbook.clipsheet.editorprop.range = workbook.spreadsheet.editor.range; + //workbook.clipsheet.editorprop.range2 = workbook.spreadsheet.editor.range2; + //workbook.clipsheet.highlights = workbook.spreadsheet.context.highlights; + + //alert("copied "+sheetid); + }; + + SocialCalc.PasteWorkBookSheet = function PasteWorkBookSheet( + workbook, + newsheetid, + oldsheetid + ) { + //alert(newsheetid+oldsheetid); + workbook.AddNewWorkBookSheet(newsheetid, oldsheetid, true); + + // clear the clip ? + }; + + SocialCalc.SheetNameExistsInWorkBook = function SheetNameExistsInWorkBook( + workbook, + name + ) { + for (var sheet in workbook.sheetArr) { + if (workbook.sheetArr[sheet].sheet.sheetname == name) { + return sheet; + } + } + return null; + }; + + // + // Workbook Control controls workbook actions (add/del/rename etc) and can appear at the + // bottom of the screen (?). Right now its just a proof of concept + // and appears at the top of the screen + // + // Author: Ramu Ramamurthy + // + // + + var SocialCalc; + if (!SocialCalc) { + alert("Main SocialCalc code module needed"); + SocialCalc = {}; + } + + SocialCalc.CurrentWorkbookControlObject = null; + + SocialCalc.TestWorkBookSaveStr = ""; + + // Constructor: + + SocialCalc.WorkBookControl = function (book, divid, defaultsheetname) { + this.workbook = book; + this.div = divid; + this.defaultsheetname = defaultsheetname; + this.sheetButtonArr = {}; + this.sheetCnt = 0; + this.numSheets = 0; + this.currentSheetButton = null; + this.renameDialogId = "sheetRenameDialog"; + this.deleteDialogId = "sheetDeleteDialog"; + this.hideDialogId = "sheetHideDialog"; + this.unhideDialogId = "sheetUnhideDialog"; + + this.sheetshtml = + ''; + + //this.buttonshtml = + //'
'+ + //'
'+ + //''+ + //''+ + //''+ + + // ''+ + // ''+ + // ''+ + + //''+ + //''+ + //'
'+ + //'
'; + + SocialCalc.CurrentWorkbookControlObject = this; + this.sheetbar = new SocialCalc.SheetBar(); + }; + + // methods + SocialCalc.WorkBookControl.prototype.GetCurrentWorkBookControl = function () { + return SocialCalc.GetCurrentWorkBookControl(); + }; + SocialCalc.WorkBookControl.prototype.InitializeWorkBookControl = function () { + return SocialCalc.InitializeWorkBookControl(this); + }; + + SocialCalc.WorkBookControl.prototype.ExecuteWorkBookControlCommand = + function (cmd, isremote) { + return SocialCalc.ExecuteWorkBookControlCommand(this, cmd, isremote); + }; + + SocialCalc.ExecuteWorkBookControlCommand = function (control, cmd, isremote) { + //console.log("cmd ", cmd.cmdstr, cmd.cmdtype); + + //if (!isremote) { + // return; + //} + + if (cmd.cmdtype == "scmd") { + // dispatch a sheet command + control.workbook.WorkbookScheduleCommand(cmd, isremote); + return; + } + + if (cmd.cmdtype != "wcmd") { + return; + } + + var parseobj = new SocialCalc.Parse(cmd.cmdstr); + + var cmd1 = parseobj.NextToken(); + + switch (cmd1) { + case "addsheet": + SocialCalc.WorkBookControlAddSheetRemote(null); + break; + + case "addsheetstr": + var sheetstr = cmd.sheetstr; + SocialCalc.WorkBookControlAddSheetRemote(sheetstr); + break; + + case "delsheet": + var sheetid = parseobj.NextToken(); + SocialCalc.WorkBookControlDelSheetRemote(sheetid); + break; + + case "rensheet": + var sheetid = parseobj.NextToken(); + var oldname = parseobj.NextToken(); + var newname = parseobj.NextToken(); + SocialCalc.WorkBookControlRenameSheetRemote(sheetid, oldname, newname); + break; + + case "activatesheet": + var sheetid = parseobj.NextToken(); + SocialCalc.WorkBookControlActivateSheet(sheetid); + break; + + case "hidesheet": + var sheetid = parseobj.NextToken(); + + break; + + case "unhidesheet": + var sheetid = parseobj.NextToken(); + + break; + } + }; + + SocialCalc.GetCurrentWorkBookControl = function () { + return SocialCalc.CurrentWorkbookControlObject; + }; + + SocialCalc.InitializeWorkBookControl = function (control) { + var element = document.createElement("div"); + element.innerHTML = control.sheetshtml; + var foo = document.getElementById(control.div); + foo.appendChild(element); + //var element2 = document.createElement("div"); + //element2.innerHTML = control.buttonshtml; + //foo.appendChild(element2); + SocialCalc.WorkBookControlAddSheet(false); // this is for the default sheet + }; + + SocialCalc.WorkBookControlDelSheetRemote = function (sheetid) { + var control = SocialCalc.GetCurrentWorkBookControl(); + if (sheetid == control.currentSheetButton.id) { + // the active sheet is being deleted + SocialCalc.WorkBookControlDelSheet(); + return; + } + // some non active sheet is being deleted + var foo = document.getElementById("fooBar"); + var deletedbutton = document.getElementById(sheetid); + + var did = deletedbutton.id; + var dname = deletedbutton.value; + delete control.sheetButtonArr[did]; + + foo.removeChild(deletedbutton); + var sheetbar = document.getElementById("SocialCalc-sheetbar-buttons"); + var sheetbarbutton = document.getElementById("sbsb-" + did); + // unregister with mouse ? etc + sheetbar.removeChild(sheetbarbutton); + + // delete the sheet + control.workbook.DeleteWorkBookSheet(did, dname); + control.numSheets = control.numSheets - 1; + }; + + // assumes that the current active sheet is being deleted + SocialCalc.WorkBookControlDelSheet = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit mode return + return; + } + if (control.numSheets == 1) { + //disallow this + var str = + '
' + + "" + + " A workbook must contain at least one worksheet " + + "

"; + str += + "To delete the selected sheet, you must first insert a new sheet.
"; + str += + '
' + + '
'; + var main = document.createElement("div"); + main.id = control.deleteDialogId; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + " X 
' + + '
' + + str + + "
"; + + //alert(main.innerHTML); + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + return; + } + + // do a popup to reaffirm the deletion of the sheet + // the popup has two buttons : Confirm and Cancel + var element = document.getElementById(control.deleteDialogId); + if (element) return; + + var currentsheet = control.currentSheetButton.value; + var str = + '
' + + "" + + "The selected sheet will be permanently deleted." + + "
"; + str += "
    "; + str += "
  • To delete the selected sheet, click OK.
  • "; + str += "
  • To cancel the deletion, click cancel.
  • "; + str += "
"; + str += + '
' + + ' ' + + '
'; + + var main = document.createElement("div"); + main.id = control.deleteDialogId; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + " X 
' + + '
' + + str + + "
"; + + //alert(main.innerHTML); + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + }; + + SocialCalc.WorkBookControlDeleteSheetHide = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + var spreadsheet = control.workbook.spreadsheet; + + var ele = document.getElementById(control.deleteDialogId); + ele.innerHTML = ""; + + SocialCalc.DragUnregister(ele); + + SocialCalc.KeyboardFocus(); + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + }; + + SocialCalc.WorkBookControlDeleteSheetSubmit = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + SocialCalc.WorkBookControlDeleteSheetHide(); + var foo = document.getElementById("fooBar"); + var current = document.getElementById(control.currentSheetButton.id); + + var name = current.id; + var curname = control.currentSheetButton.value; + delete control.sheetButtonArr[name]; + + foo.removeChild(current); + + var sheetbar = document.getElementById("SocialCalc-sheetbar-buttons"); + var sheetbarbutton = document.getElementById("sbsb-" + current.id); + // unregister with mouse ? etc + sheetbar.removeChild(sheetbarbutton); + + control.currentSheetButton = null; + // delete the sheets + control.workbook.DeleteWorkBookSheet(name, curname); + control.numSheets = control.numSheets - 1; + + var cmdstr = "delsheet " + name; + SocialCalc.Callbacks.broadcast("execute", { + cmdtype: "wcmd", + id: "0", + cmdstr: cmdstr, + }); + + // reset current sheet + for (var sheet in control.sheetButtonArr) { + if (sheet != null) { + control.currentSheetButton = control.sheetButtonArr[sheet]; + control.currentSheetButton.setAttribute( + "style", + "background-color:lightgreen" + ); + SocialCalc.SheetBarButtonActivate(control.currentSheetButton.id, true); + break; + } + } + if (control.currentSheetButton != null) { + control.workbook.ActivateWorkBookSheet( + control.currentSheetButton.id, + null + ); + } + }; + + // assumes that the current active sheet is being hidden + SocialCalc.WorkBookControlHideSheet = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + + var control = SocialCalc.GetCurrentWorkBookControl(); + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit mode return + return; + } + if (control.numSheets == 1) { + //disallow this + var str = + '
' + + "" + + " A workbook must contain at least one worksheet " + + "

"; + str += + "Before hiding the selected sheet, you must first insert a new sheet.
"; + str += + '
' + + '
'; + var main = document.createElement("div"); + main.id = control.hideDialogId; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + " X 
' + + '
' + + str + + "
"; + + //alert(main.innerHTML); + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + return; + } + + // do a popup to reaffirm the hiding of the sheet + // the popup has two buttons : Confirm and Cancel + var element = document.getElementById(control.hideDialogId); + if (element) return; + + var currentsheet = control.currentSheetButton.value; + var str = + '
' + + "" + + "The selected sheet will be hidden." + + "
"; + str += "
    "; + str += "
  • To hide the selected sheet, click OK.
  • "; + str += "
  • To cancel the hiding, click cancel.
  • "; + str += "
"; + str += + '
' + + ' ' + + '
'; + + var main = document.createElement("div"); + main.id = control.hideDialogId; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + " X 
' + + '
' + + str + + "
"; + + //alert(main.innerHTML); + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + }; + + SocialCalc.WorkBookControlHideSheetHide = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + var spreadsheet = control.workbook.spreadsheet; + + var ele = document.getElementById(control.hideDialogId); + ele.innerHTML = ""; + + SocialCalc.DragUnregister(ele); + + SocialCalc.KeyboardFocus(); + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + }; + + SocialCalc.WorkBookControlHideSheetSubmit = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + SocialCalc.WorkBookControlHideSheetHide(); + var foo = document.getElementById("fooBar"); + var current = document.getElementById(control.currentSheetButton.id); + + var name = current.id; + var curname = control.currentSheetButton.value; + + var sheetbar = document.getElementById("SocialCalc-sheetbar-buttons"); + var sheetbarbutton = document.getElementById("sbsb-" + current.id); + // unregister with mouse ? etc + SocialCalc.SheetBarButtonActivate(control.currentSheetButton.id, false); + sheetbarbutton.style.display = "none"; + control.currentSheetButton = null; + // delete the sheets + + control.numSheets = control.numSheets - 1; + + var cmdstr = "hidesheet " + name; + SocialCalc.Callbacks.broadcast("execute", { + cmdtype: "wcmd", + id: "0", + cmdstr: cmdstr, + }); + + // reset current sheet + for (var sheet in control.sheetButtonArr) { + if ( + sheet != null && + document.getElementById("sbsb-" + sheet).style.display != "none" + ) { + control.currentSheetButton = control.sheetButtonArr[sheet]; + break; + } + } + if (control.currentSheetButton != null) { + control.currentSheetButton.setAttribute( + "style", + "background-color:lightgreen" + ); + SocialCalc.SheetBarButtonActivate(control.currentSheetButton.id, true); + control.workbook.ActivateWorkBookSheet( + control.currentSheetButton.id, + null + ); + } + }; + + // displays all hidden sheets, and then unhides whatever is selected + SocialCalc.WorkBookControlUnhideSheet = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit mode return + return; + } + + var unhiddencount = 0; + for (var sheet in control.sheetButtonArr) { + if (document.getElementById("sbsb-" + sheet).style.display == "none") { + unhiddencount++; + } + } + + if (unhiddencount == 0) { + //no hidden sheets, error message here + var str = + '
' + + "" + + " There are no hidden worksheets. " + + "

"; + str += + "Before unhiding any sheets, you must first hide a sheet.
"; + str += + '
' + + '
'; + var main = document.createElement("div"); + main.id = control.unhideDialogId; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + " X 
' + + '
' + + str + + "
"; + + //alert(main.innerHTML); + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + return; + } + + var element = document.getElementById(control.unhideDialogId); + if (element) return; + + var currentsheet = control.currentSheetButton.value; + var str = + '
' + + "" + + "The following sheets are hidden." + + '
    ' + + ''; + for (var sheet in control.sheetButtonArr) { + if (document.getElementById("sbsb-" + sheet).style.display == "none") { + str += + '' + + control.sheetButtonArr[sheet].value + + "
    "; + } + } + + str += "
\n
    "; + str += "
  • To unhide the selected sheet, click OK.
  • "; + str += "
  • To cancel the unhiding, click cancel.
  • "; + str += "
"; + str += + '
' + + ' ' + + '
'; + + var main = document.createElement("div"); + main.id = control.unhideDialogId; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + " X 
' + + '
' + + str + + "
"; + + //alert(main.innerHTML); + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + }; + + SocialCalc.WorkBookControlUnhideSheetHide = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + var spreadsheet = control.workbook.spreadsheet; + + var ele = document.getElementById(control.unhideDialogId); + ele.innerHTML = ""; + + SocialCalc.DragUnregister(ele); + + SocialCalc.KeyboardFocus(); + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + }; + + SocialCalc.WorkBookControlUnhideSheetSubmit = function (name) { + var control = SocialCalc.GetCurrentWorkBookControl(); + SocialCalc.WorkBookControlUnhideSheetHide(); + var current = document.getElementById(control.currentSheetButton.id); + + var curid = current.id; + var curname = control.currentSheetButton.value; + + control.currentSheetButton.setAttribute("style", ""); + var old = control.currentSheetButton.id; + console.log(old); + SocialCalc.SheetBarButtonActivate(old, false); + + var sheetbarbutton = document.getElementById("sbsb-" + name); + // unhide the button + sheetbarbutton.style.display = "inline"; + control.currentSheetButton = null; + + control.numSheets = control.numSheets + 1; + + var cmdstr = "unhidesheet " + name; + SocialCalc.Callbacks.broadcast("execute", { + cmdtype: "wcmd", + id: "0", + cmdstr: cmdstr, + }); + + // reset current sheet + for (var sheet in control.sheetButtonArr) { + if ( + sheet != null && + document.getElementById("sbsb-" + sheet).style.display != "none" + ) { + control.currentSheetButton = control.sheetButtonArr[sheet]; + break; + } + } + + if (control.currentSheetButton != null) { + control.currentSheetButton.setAttribute( + "style", + "background-color:lightgreen" + ); + SocialCalc.SheetBarButtonActivate(control.currentSheetButton.id, true); + control.workbook.ActivateWorkBookSheet( + control.currentSheetButton.id, + null + ); + } + }; + + SocialCalc.WorkBookControlAddSheetButton = function (sheetname, sheetid) { + var control = SocialCalc.GetCurrentWorkBookControl(); + + //Create an input type dynamically. + var element = document.createElement("input"); + + var name = null; + + if (sheetid != null) { + name = sheetid; + } else { + name = "sheet" + (control.sheetCnt + 1).toString(); + control.sheetCnt = control.sheetCnt + 1; + } + + //Assign different attributes to the element. + element.setAttribute("type", "button"); + if (sheetname == null) { + element.setAttribute("value", name); + } else { + element.setAttribute("value", sheetname); + } + element.setAttribute("id", name); + element.setAttribute("name", name); + + var fnname = + "SocialCalc.WorkBookControlActivateSheet(" + "'" + name + "'" + ")"; + + element.setAttribute("onclick", fnname); + + control.sheetButtonArr[name] = element; + + var foo = document.getElementById("fooBar"); + + //Append the element in page (in span). + foo.appendChild(element); + + control.numSheets = control.numSheets + 1; + + var el = new SocialCalc.SheetBarSheetButton( + "sbsb-" + name, + sheetname ? sheetname : name, + document.getElementById("SocialCalc-sheetbar-buttons"), + { + //normalstyle: "border:1px solid #000;backgroundColor:#FFF;", + //downstyle: "border:1px solid #000;backgroundColor:#CCC;", + //hoverstyle: "border:1px solid #000;backgroundColor:#FFF;" + }, + { + MouseDown: function () { + SocialCalc.SheetBarSheetButtonPress(name); + }, + Repeat: function () {}, + Disabled: function () {}, + } + ); + + return element; + }; + + SocialCalc.WorkBookControlAddSheet = function (addworksheet, sheetname) { + var control = SocialCalc.GetCurrentWorkBookControl(); + + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit return + return; + } + + // first add the button + var element = SocialCalc.WorkBookControlAddSheetButton(sheetname); + + // then change the highlight + + var old = "sheet1"; + if (control.currentSheetButton != null) { + control.currentSheetButton.setAttribute("style", ""); + old = control.currentSheetButton.id; + SocialCalc.SheetBarButtonActivate(old, false); + } + + element.setAttribute("style", "background-color:lightgreen"); + control.currentSheetButton = element; + var newsheetid = element.id; + SocialCalc.SheetBarButtonActivate(newsheetid, true); + + // create the sheet + if (addworksheet) { + control.workbook.AddNewWorkBookSheet(newsheetid, old, false); + // broadcast an add command here + var cmdstr = "addsheet"; + SocialCalc.Callbacks.broadcast("execute", { + cmdtype: "wcmd", + id: "0", + cmdstr: cmdstr, + }); + } + }; + + SocialCalc.WorkBookControlAddSheetRemote = function (savestr) { + var control = SocialCalc.GetCurrentWorkBookControl(); + + // first add the button + var element = SocialCalc.WorkBookControlAddSheetButton(); + + // add the sheet, dont switch to it + control.workbook.AddNewWorkBookSheetNoSwitch( + element.id, + element.value, + savestr + ); + }; + + SocialCalc.WorkBookControlActivateSheet = function (name) { + //alert("in activate sheet="+name) + + var control = SocialCalc.GetCurrentWorkBookControl(); + + var foo = document.getElementById(name); + foo.setAttribute("style", "background-color:lightgreen;"); + SocialCalc.SheetBarButtonActivate(name, true); + + var old = control.currentSheetButton.id; + if (control.currentSheetButton.id != foo.id) { + control.currentSheetButton.setAttribute("style", ""); + SocialCalc.SheetBarButtonActivate(old, false); + } + + control.currentSheetButton = foo; + + control.workbook.ActivateWorkBookSheet(name, old); + }; + + SocialCalc.WorkBookControlHttpRequest = null; + + SocialCalc.WorkBookControlAlertContents = function () { + var loadedstr = ""; + var http_request = SocialCalc.WorkBookControlHttpRequest; + + if (http_request.readyState == 4) { + //addmsg("received:" + http_request.responseText.length + " chars"); + try { + if (http_request.status == 200) { + loadedstr = http_request.responseText || ""; + http_request = null; + } else { + } + } catch (e) {} + // do something with loaded str + //alert("loaded="+loadedstr); + SocialCalc.TestWorkBookSaveStr = loadedstr; + SocialCalc.Clipboard.clipboard = loadedstr; + } + }; + + SocialCalc.WorkBookControlAjaxCall = function (url, contents) { + var http_request = null; + + alert("in ajax"); + if (window.XMLHttpRequest) { + // Mozilla, Safari,... + http_request = new XMLHttpRequest(); + } else if (window.ActiveXObject) { + // IE + try { + http_request = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + try { + http_request = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + } + } + if (!http_request) { + alert("Giving up :( Cannot create an XMLHTTP instance"); + return false; + } + + // Make the actual request + SocialCalc.WorkBookControlHttpRequest = http_request; + + http_request.onreadystatechange = SocialCalc.WorkBookControlAlertContents; + http_request.open("POST", document.URL, true); // async + http_request.setRequestHeader( + "Content-Type", + "application/x-www-form-urlencoded" + ); + http_request.send(contents); + + return true; + }; + + SocialCalc.WorkBookControlSaveSheet = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + + var sheetsave = {}; + + sheetsave.numsheets = control.numSheets; + sheetsave.currentid = control.currentSheetButton.id; + sheetsave.currentname = control.currentSheetButton.value; + + sheetsave.sheetArr = {}; + for (var sheet in control.sheetButtonArr) { + var sheetstr = control.workbook.SaveWorkBookSheet(sheet); + sheetsave.sheetArr[sheet] = {}; + sheetsave.sheetArr[sheet].sheetstr = sheetstr; + sheetsave.sheetArr[sheet].name = control.sheetButtonArr[sheet].value; + sheetsave.sheetArr[sheet].hidden = + document.getElementById("sbsb-" + sheet).style.display == "none" + ? "1" + : "0"; + } + + // Save the editable cells if specified + if (SocialCalc.EditableCells && SocialCalc.EditableCells.allow) { + sheetsave.EditableCells = {}; + for (var i in SocialCalc.EditableCells) { + sheetsave.EditableCells[i] = SocialCalc.EditableCells[i]; + } + } + + var d = new Date(); + sheetsave["timestamp"] = d.toString(); + + SocialCalc.TestWorkBookSaveStr = JSON.stringify(sheetsave); + //alert(SocialCalc.TestWorkBookSaveStr); + // send it to the backend + // SocialCalc.WorkBookControlAjaxCall("/", "&sheetdata="+encodeURIComponent(SocialCalc.TestWorkBookSaveStr)); + return SocialCalc.TestWorkBookSaveStr; + }; + + // insert another workbook into an existing workbook + // assumption is at least 1 sheet exists in existing workbook + // sheets with same names will be overwritten ! + SocialCalc.WorkBookControlInsertWorkbook = function (savestr) { + var sheetsave; + if (savestr) { + sheetsave = JSON.parse(savestr); + } + var control = SocialCalc.GetCurrentWorkBookControl(); + for (var sheet in sheetsave.sheetArr) { + var savestr = sheetsave.sheetArr[sheet].sheetstr.savestr; + var parts = control.workbook.spreadsheet.DecodeSpreadsheetSave(savestr); + if (parts) { + if (parts.sheet) { + savestr = savestr.substring(parts.sheet.start, parts.sheet.end); + } + } + // check if sheetname already exists + var sheetname = sheetsave.sheetArr[sheet].name; + var sheetid = control.workbook.SheetNameExistsInWorkBook(sheetname); + if (sheetid) { + console.log(sheetname + "exists"); + control.workbook.LoadRenameWorkBookSheet(sheetid, savestr, sheetname); + } else { + //just test-brand new insert first + sheetid = "sheet" + (control.sheetCnt + 1).toString(); + control.sheetCnt = control.sheetCnt + 1; + SocialCalc.WorkBookControlAddSheetButton( + sheetsave.sheetArr[sheet].name, + sheetid + ); + // create the sheet + control.workbook.AddNewWorkBookSheetNoSwitch( + sheetid, + sheetsave.sheetArr[sheet].name, + savestr + ); + } + } + }; + + SocialCalc.WorkBookControlLoad = function (savestr) { + var sheetsave; + + if (savestr == "") return; + + if (savestr) { + sheetsave = JSON.parse(savestr); + } else { + sheetsave = JSON.parse(SocialCalc.TestWorkBookSaveStr); + } + //alert(sheetsave.currentid+","+sheetsave.currentname) + + // first create a new workbook + var control = SocialCalc.GetCurrentWorkBookControl(); + + SocialCalc.WorkBookControlCreateNewBook(); + + // at this point there is one sheet, and 1 button + // create the sequence of buttons, and sheets + var firstrun = true; + var newbuttons = 0; + var sheetid = null; + var currentsheetid = sheetsave.currentid; + //alert("button="+newbuttons) + for (var sheet in sheetsave.sheetArr) { + //alert(sheet); + if (newbuttons > sheetsave.numsheets) { + break; + } + //alert("button="+newbuttons) + var savestr = sheetsave.sheetArr[sheet].sheetstr.savestr; + var parts = control.workbook.spreadsheet.DecodeSpreadsheetSave(savestr); + if (parts) { + if (parts.sheet) { + savestr = savestr.substring(parts.sheet.start, parts.sheet.end); + } + } + if (firstrun) { + firstrun = false; + // set the first button's name correctly + sheetid = control.currentSheetButton.id; + control.currentSheetButton.value = sheetsave.sheetArr[sheet].name; + SocialCalc.SheetBarButtonSetName( + sheetid, + sheetsave.sheetArr[sheet].name + ); + // set the sheet data for the first sheet which already exists + control.workbook.LoadRenameWorkBookSheet( + sheetid, + savestr, + control.currentSheetButton.value + ); + // need to also set the formula cache + currentsheetid = sheetid; + } else { + sheetid = "sheet" + (control.sheetCnt + 1).toString(); + control.sheetCnt = control.sheetCnt + 1; + SocialCalc.WorkBookControlAddSheetButton( + sheetsave.sheetArr[sheet].name, + sheetid + ); + // create the sheet + control.workbook.AddNewWorkBookSheetNoSwitch( + sheetid, + sheetsave.sheetArr[sheet].name, + savestr + ); + } + if (sheetsave.sheetArr[sheet].hidden == "1") { + // unregister with mouse ? etc + var sheetbarbutton = document.getElementById("sbsb-" + sheetid); + sheetbarbutton.style.display = "none"; + SocialCalc.SheetBarButtonActivate(sheet, false); + newbuttons = newbuttons - 1; + } + if (sheet == sheetsave.currentid) { + currentsheetid = sheetid; + } + newbuttons = newbuttons + 1; + } + // Save the user script data + if (sheetsave.EditableCells) { + SocialCalc.EditableCells = {}; + for (var i in sheetsave.EditableCells) { + SocialCalc.EditableCells[i] = sheetsave.EditableCells[i]; + } + } + var timeoutFn = function () { + SocialCalc.WorkBookControlActivateSheet(currentsheetid); + }; + window.setTimeout(timeoutFn, 200); + }; + + SocialCalc.WorkBookControlRenameSheet = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit return + return; + } + + // do a popup to get the new name of the sheet + // the popup has an input element with submit, and cancel buttons + var element = document.getElementById(control.renameDialogId); + if (element) return; + + var currentsheet = control.currentSheetButton.value; + var str = + '
' + + '' + + "Rename-" + + currentsheet + + "
" + + '' + + "Please ensure that you DO NOT have ANY spaces in the sheet name." + + "" + + '
' + + "
"; + + str += + '
' + + ' ' + + '
'; + + var main = document.createElement("div"); + main.id = control.renameDialogId; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + + main.style.top = vp.height / 3 + "px"; + main.style.left = vp.width / 3 + "px"; + main.style.zIndex = 100; + main.style.backgroundColor = "#FFF"; + main.style.border = "1px solid black"; + + main.style.width = "400px"; + + main.innerHTML = + '' + + '" + + '
' + + " " + + " X 
' + + '
' + + str + + "
"; + + SocialCalc.DragRegister( + main.firstChild.firstChild.firstChild.firstChild, + true, + true, + { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: main, + } + ); + + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + + var ele = document.getElementById("newSheetName"); + ele.focus(); + SocialCalc.CmdGotFocus(ele); + }; + + SocialCalc.WorkBookControlRenameSheetHide = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + var spreadsheet = control.workbook.spreadsheet; + + var ele = document.getElementById(control.renameDialogId); + ele.innerHTML = ""; + + SocialCalc.DragUnregister(ele); + + SocialCalc.KeyboardFocus(); + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + }; + + SocialCalc.WorkBookControlRenameSheetSubmit = function () { + // this handles all the rename action + var ele = document.getElementById("newSheetName"); + //console.log(ele.value); + var control = SocialCalc.GetCurrentWorkBookControl(); + if (ele.value.length == 0) { + ele.focus(); + return; + } + var oldname = control.currentSheetButton.value; + var newname = ele.value; + if (newname.indexOf(" ") != -1) { + alert( + "A space was found in the new name. Please ensure that the new name has no sapces" + ); + return; + } + SocialCalc.WorkBookControlRenameSheetHide(); + // verify newname does not clash with any existing sheet name + // if so reject + var smallname = newname.toLowerCase(); //converting to lower case to normalise + //console.log(smallname + " old " + ele.value); + for (var sheet in workbook.sheetArr) { + console.log(workbook.sheetArr[sheet].sheet.sheetname); //checking in sheetarr for repeated names + if (workbook.sheetArr[sheet].sheet.sheetname == smallname) { + alert(newname + " already exists"); + return; + } + } // variation of Case in letters of a sheet name will give an error if smallname is used. + + control.currentSheetButton.value = smallname; + + SocialCalc.SheetBarButtonSetName(control.currentSheetButton.id, newname); + + // perform a rename for formula references to this sheet in all the + // sheets in the workbook + control.workbook.RenameWorkBookSheet( + oldname, + smallname, + control.currentSheetButton.id + ); + + var cmdstr = + "rensheet " + + control.currentSheetButton.id + + " " + + oldname + + " " + + newname; + //console.log(cmdstr); + SocialCalc.Callbacks.broadcast("execute", { + cmdtype: "wcmd", + id: "0", + cmdstr: cmdstr, + }); + }; + + SocialCalc.WorkBookControlRenameSheetRemote = function ( + sheetid, + oldname, + newname + ) { + //console.log("rename sheet ",sheetid, oldname, newname) + var control = SocialCalc.GetCurrentWorkBookControl(); + + var foo = document.getElementById("fooBar"); + var renbutton = document.getElementById(sheetid); + + renbutton.value = newname; + + SocialCalc.SheetBarButtonSetName(sheetid, newname); + + control.workbook.RenameWorkBookSheet(oldname, newname, sheetid); + }; + + SocialCalc.WorkBookControlCreateNewBook = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + + // delete all the sheets except 1 + for (var sheet in control.sheetButtonArr) { + if (sheet != control.currentSheetButton.id) { + control.workbook.DeleteWorkBookSheet( + control.sheetButtonArr[sheet].id, + control.sheetButtonArr[sheet].value + ); + } + } + // Reset that 1 sheet + + control.workbook.LoadRenameWorkBookSheet( + control.currentSheetButton.id, + "", + control.workbook.defaultsheetname + ); + + // delete all the buttons except 1 + for (var sheet in control.sheetButtonArr) { + if (sheet != control.currentSheetButton.id) { + var foo = document.getElementById("fooBar"); + var current = document.getElementById(control.sheetButtonArr[sheet].id); + + var name = current.id; + delete control.sheetButtonArr[name]; + + foo.removeChild(current); + + var sheetbar = document.getElementById("SocialCalc-sheetbar-buttons"); + var sheetbarbutton = document.getElementById("sbsb-" + name); + // unregister with mouse ? etc + sheetbar.removeChild(sheetbarbutton); + + control.numSheets = control.numSheets - 1; + } + } + // rename that button + control.currentSheetButton.value = control.workbook.defaultsheetname; + //alert("done new workbook") + }; + + SocialCalc.WorkBookControlNewBook = function () { + var control = SocialCalc.GetCurrentWorkBookControl(); + SocialCalc.WorkBookControlCreateNewBook(); + control.workbook.RenderWorkBookSheet(); + }; + + SocialCalc.WorkBookControlMove = function (direction) { + var control = SocialCalc.GetCurrentWorkBookControl(); + if (control.workbook.spreadsheet.editor.state != "start") { + return; + } + var sheetArr = control.sheetButtonArr; + var newSheetArr = {}; + var sheetid = control.currentSheetButton.id; + + var curr_button = document.getElementById(sheetid); + var curr_sb_button = document.getElementById("sbsb-" + sheetid); + var sib_button = null; + var sib_sb_button = null; + if (direction == "left") { + sib_button = curr_button.previousSibling; + sib_sb_button = curr_sb_button.previousSibling; + if (!sib_sb_button) { + alert("Cannot move leftmost Sheet further to the left"); + return; + } + } else { + sib_button = curr_button.nextSibling; + sib_sb_button = curr_sb_button.nextSibling; + if (!sib_sb_button) { + alert("Cannot move rightmost Sheet further to the right"); + return; + } + } + var currid = sheetid; + var sibid = sib_button.id; + var parent = curr_button.parentNode; + var sb_parent = curr_sb_button.parentNode; + + var cloned = {}; + var clonedsb = {}; + for (button in sheetArr) { + clonedsb[button] = document.getElementById("sbsb-" + button); + cloned[button] = document.getElementById(button); + sb_parent.removeChild(document.getElementById("sbsb-" + button)); + parent.removeChild(document.getElementById(button)); + } + for (button in sheetArr) { + if (button != currid && button != sibid) { + newSheetArr[button] = sheetArr[button]; + sb_parent.appendChild(clonedsb[button]); + parent.appendChild(cloned[button]); + } else if (button == currid) { + if (direction == "left") { + newSheetArr[currid] = sheetArr[currid]; + newSheetArr[sibid] = sheetArr[sibid]; + sb_parent.appendChild(clonedsb[currid]); + parent.appendChild(cloned[currid]); + sb_parent.appendChild(clonedsb[sibid]); + parent.appendChild(cloned[sibid]); + } else { + newSheetArr[sibid] = sheetArr[sibid]; + newSheetArr[currid] = sheetArr[currid]; + sb_parent.appendChild(clonedsb[sibid]); + parent.appendChild(cloned[sibid]); + sb_parent.appendChild(clonedsb[currid]); + parent.appendChild(cloned[currid]); + } + } + } + control.sheetButtonArr = newSheetArr; + SocialCalc.SheetBarButtonActivate(currid, true); + }; + + SocialCalc.WorkBookControlMoveLeft = function () { + SocialCalc.WorkBookControlMove("left"); + }; + SocialCalc.WorkBookControlMoveRight = function () { + SocialCalc.WorkBookControlMove("right"); + }; + + SocialCalc.WorkBookControlCopySheet = function () { + //alert("in copy"); + + var control = SocialCalc.GetCurrentWorkBookControl(); + + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit return + return; + } + + control.workbook.CopyWorkBookSheet(control.currentSheetButton.id); + + alert("copied sheet:" + control.currentSheetButton.value); + }; + + SocialCalc.WorkBookControlPasteSheet = function () { + //alert("in paste"); + + var control = SocialCalc.GetCurrentWorkBookControl(); + + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit return + return; + } + + var oldid = control.currentSheetButton.id; + + SocialCalc.WorkBookControlAddSheet(false); + + var newid = control.currentSheetButton.id; + + //alert(newid+oldid); + + control.workbook.PasteWorkBookSheet(newid, oldid); + + var cmdstr = "addsheetstr"; + SocialCalc.Callbacks.broadcast("execute", { + cmdtype: "wcmd", + id: "0", + cmdstr: cmdstr, + sheetstr: control.workbook.clipsheet.savestr, + }); + }; + + SocialCalc.SheetBar = function () { + this.baseDiv = document.getElementById("SocialCalc-sheetbar"); + + this.prebuttonsDiv = document.createElement("div"); + this.prebuttonsDiv.style.cssText = "display:inline;"; + this.prebuttonsDiv.innerHTML = "        "; + this.prebuttonsDiv.id = "SocialCalc-sheetbar-prebuttons"; + + this.buttonsDiv = document.createElement("div"); + this.buttonsDiv.id = "SocialCalc-sheetbar-buttons"; + this.buttonsDiv.style.cssText = "display:inline;"; + + this.buttonActionsDiv = document.createElement("div"); + this.buttonActionsDiv.id = "SocialCalc-sheetbar-buttonactions"; + this.buttonActionsDiv.style.display = "inline"; + var addbutton = new SocialCalc.SheetBarSheetButton( + "sbsba-add", + "sbsba-add", + this.buttonActionsDiv, + {}, + { + MouseDown: function () { + var abc = SocialCalc.WorkBookControlAddSheet(true); + }, + }, + "add-2.png" + ); + + this.baseDiv.appendChild(this.prebuttonsDiv); + this.baseDiv.appendChild(this.buttonsDiv); + this.baseDiv.appendChild(this.buttonActionsDiv); + }; + + // define a new class for sheetbarsheetbutton + + SocialCalc.SheetBarSheetButton = function ( + id, + name, + parentdiv, + params, + functions, + img + ) { + this.ele = document.createElement("div"); + this.ele.id = id; + this.ele.name = name; + if (!img) { + this.ele.innerHTML = name; + this.ele.style.cssText = + "font-size:small;display:inline;padding:5px 5px 2px 5px;border:1px solid #000;"; + imgele = document.createElement("img"); + imgele.id = id + "-img"; + imgele.src = + SocialCalc.Constants.defaultImagePrefix + "menu-dropdown.png"; + imgele.style.cssText = + "padding:0px 2px;width:16px;height:16px;vertical-align:middle;"; + this.ele.appendChild(imgele); + SocialCalc.ButtonRegister(this.ele, params, functions); + SocialCalc.ButtonRegister(imgele, params, functions); + } else { + var imgele = document.createElement("img"); + imgele.src = SocialCalc.Constants.defaultImagePrefix + img; + imgele.style.cssText = "width:16px;height:16px;vertical-align:middle;"; + this.ele.appendChild(imgele); + this.ele.style.cssText = "display:inline;padding:5px 5px 2px 5px;"; + SocialCalc.ButtonRegister(imgele, params, functions); + } + parentdiv.appendChild(this.ele); + }; + + SocialCalc.SheetBarButtonActivate = function (id, active) { + var sbbutton = document.getElementById("sbsb-" + id); + sbbutton.isactive = active; + if (active) { + sbbutton.style.backgroundColor = "#FFF"; + var imgele = document.getElementById("sbsb-" + id + "-img"); + if (!imgele) { + imgele = document.createElement("img"); + imgele.id = "sbsb-" + id + "-img"; + imgele.src = + SocialCalc.Constants.defaultImagePrefix + "menu-dropdown.png"; + imgele.style.cssText = + "padding:0px 2px;width:16px;height:16px;vertical-align:middle;"; + } + sbbutton.appendChild(imgele); + SocialCalc.ButtonRegister( + imgele, + {}, + { + MouseDown: function () { + SocialCalc.SheetBarSheetButtonPress(id); + }, + Repeat: function () {}, + Disabled: function () {}, + } + ); + } else { + sbbutton.style.backgroundColor = "#CCC"; + var imgele = document.getElementById("sbsb-" + id + "-img"); + if (imgele) { + sbbutton.removeChild(imgele); + } + } + var menu = document.getElementById("sbsb-menu"); + if (menu && menu.style.display != "none") { + menu.style.display = "none"; + } + }; + + SocialCalc.SheetBarButtonSetName = function (id, name) { + var sbbutton = document.getElementById("sbsb-" + id); + sbbutton.name = name; + sbbutton.innerHTML = name; + if (sbbutton.isactive) { + SocialCalc.SheetBarButtonActivate(id, true); + } + }; + + SocialCalc.SheetBarSheetButtonPress = function (id) { + //console.log("button press") + var sbbutton = document.getElementById("sbsb-" + id); + if (sbbutton && sbbutton.isactive) { + var menu = document.getElementById("sbsb-menu"); + if (!menu) { + var sbsbm = new SocialCalc.SheetBarSheetButtonMenu("sbsb-menu", id); + } else { + menu.clickedsheetid = id; + if (menu.style.display == "none") { + menu.style.display = "inline"; + SocialCalc.SheetBarSheetButtonMenuPosition(menu, id); + } else { + menu.style.display = "none"; + } + } + } else if (sbbutton) { + SocialCalc.WorkBookControlActivateSheet(id); + } + }; + + // define a new class for sheetbarsheet button menu item + + SocialCalc.SheetBarSheetButtonMenuItem = function (id, t) { + this.ele = document.createElement("div"); + this.ele.id = id; + this.ele.innerHTML = t; + this.ele.className = ""; + this.ele.style.cssText = + "padding:3px 4px;width:100px;height:20px;background-color:#FFF;"; + + var params = { + normalstyle: "backgroundColor:#FFF;", + downstyle: "backgroundColor:#CCC;", + hoverstyle: "backgroundColor:#CCC;", + }; + var functions = { + MouseDown: function () { + SocialCalc.SheetBarMenuItemPress(id); + }, + Repeat: function () {}, + Disabled: function () {}, + }; + + SocialCalc.ButtonRegister(this.ele, params, functions); + + SocialCalc.TouchRegister(this.ele, { SingleTap: functions.MouseDown }); + + return this.ele; + }; + + SocialCalc.SheetBarMenuItemPress = function (id) { + var menu = document.getElementById("sbsb-menu"); + if (!menu) return; + + var clickedsheetid = menu.clickedsheetid; + + switch (id) { + case "sbsb_deletesheet": + //console.log("delete "+clickedsheetid); + SocialCalc.WorkBookControlDelSheet(); + break; + case "sbsb_hidesheet": + //console.log("hide" +clickedsheetid); + SocialCalc.WorkBookControlHideSheet(); + break; + case "sbsb_unhidesheet": + //console.log("hide" +clickedsheetid); + SocialCalc.WorkBookControlUnhideSheet(); + break; + case "sbsb_copysheet": + //console.log("copy "+clickedsheetid); + SocialCalc.WorkBookControlCopySheet(); + break; + case "sbsb_moveleft": + //console.log("rename "+clickedsheetid); + SocialCalc.WorkBookControlMoveLeft(); + break; + case "sbsb_moveright": + //console.log("rename "+clickedsheetid); + SocialCalc.WorkBookControlMoveRight(); + break; + + case "sbsb_pastesheet": + //console.log("paste "+clickedsheetid); + SocialCalc.WorkBookControlPasteSheet(); + break; + case "sbsb_renamesheet": + //console.log("rename "+clickedsheetid); + SocialCalc.WorkBookControlRenameSheet(); + break; + case "sbsb_closemenu": + //console.log("rename "+clickedsheetid); + menu.style.display = "none"; + break; + default: + break; + } + menu.style.display = "none"; + }; + + // define a new class for sheetbarsheet button menu + SocialCalc.SheetBarSheetButtonMenu = function (id, clickedsheetid) { + this.ele = document.createElement("div"); + this.ele.id = id; + this.ele.className = ""; + this.ele.clickedsheetid = clickedsheetid; + this.ele.style.cssText = + "border:1px solid #000;position:absolute;top:200px;left:0px;width=100px;z-index:120"; + + var ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_deletesheet", + " Delete Sheet" + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_hidesheet", + " Hide Sheet " + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_unhidesheet", + " Unhide Sheet " + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_renamesheet", + " Rename Sheet " + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_moveleft", + " Move Left " + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_moveright", + " Move Right " + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_copysheet", + " Copy Sheet " + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_pastesheet", + " Paste Sheet " + ); + this.ele.appendChild(ele1); + ele1 = new SocialCalc.SheetBarSheetButtonMenuItem( + "sbsb_closemenu", + " Cancel" + ); + this.ele.appendChild(ele1); + + SocialCalc.SheetBarSheetButtonMenuPosition(this.ele, clickedsheetid); + + //var clickedsheet = document.getElementById(clickedsheetid); + //var position = SocialCalc.GetElementPosition(clickedsheet); + //console.log(clickedsheet.offsetHeight,clickedsheet.offsetWidth,clickedsheet.offsetLeft, clickedsheet.offsetTop); + + var control = SocialCalc.GetCurrentWorkBookControl(); + control.workbook.spreadsheet.editor.toplevel.appendChild(this.ele); + }; + + // position the sheet menu + SocialCalc.SheetBarSheetButtonMenuPosition = function (menu, clickedsheetid) { + var hlessbutton = document.getElementById("te_lessbuttonh"); + + //console.log(hlessbutton.style.top, hlessbutton.style.left); + + var sbbutton = document.getElementById("sbsb-" + clickedsheetid); + + //console.log(sbbutton.offsetLeft,clickedsheetid); + + var top = hlessbutton.style.top.slice(0, -2) - 220; + var left = sbbutton.offsetLeft + 7; + + menu.style.top = top + "px"; + menu.style.left = left + "px"; + + //console.log(menu.style.top, menu.style.left); + }; + + SocialCalc.ScriptInfo = { + scripts: {}, + handle: null, + }; + + SocialCalc.ScriptCheck = function (sheetid, coord, text) { + var commentstart = text.indexOf(""); + if (commentstart != -1 && commentend != -1) { + script = text.slice(commentstart + 10, commentend); + //alert(script); + SocialCalc.ScriptInfo.scripts[coord] = script; + if (SocialCalc.ScriptInfo.handle == null) { + SocialCalc.ScriptInfo.handle = window.setTimeout( + SocialCalc.EvalUserScripts, + 500 + ); + } + //alert(coord+"-"+sheetid); + } + }; + + SocialCalc.EvalUserScript = function (data) { + var head = + document.getElementsByTagName("head")[0] || document.documentElement; + + if (data == "") return; + + var script = document.createElement("script"); + + script.type = "text/javascript"; + try { + // doesn't work on ie... + script.appendChild(document.createTextNode(data)); + } catch (e) { + // IE has funky script nodes + script.text = data; + } + + head.insertBefore(script, head.firstChild); + head.removeChild(script); + }; + + SocialCalc.EvalUserScripts = function () { + for (var cr in SocialCalc.ScriptInfo.scripts) { + SocialCalc.EvalUserScript(SocialCalc.ScriptInfo.scripts[cr]); + //console.log(cr,SocialCalc.ScriptInfo.scripts[cr]) + } + SocialCalc.ScriptInfo.handle = null; + SocialCalc.ScriptInfo.scripts = {}; + }; + + SocialCalc.CallOutOnRenderCell = function (sheetobj, value, cr) { + var cell = sheetobj.cells[cr]; + if (!cell) return; + var valuetype = cell.valuetype || ""; // get type of value to determine formatting + var valuesubtype = valuetype.substring(1); + var sheetattribs = sheetobj.attribs; + var valueformat; + valuetype = valuetype.charAt(0); + if (valuetype == "t") { + valueformat = + sheetobj.valueformats[cell.textvalueformat - 0] || + sheetobj.valueformats[sheetattribs.defaulttextvalueformat - 0] || + ""; + if (valueformat == "text-html") { + SocialCalc.ScriptCheck(sheetobj.sheetid, cr, value); + } + } + }; + + SocialCalc.GetCellDataValue = function (coord) { + var sheetname = null; + var sheetid = ""; + var bindex = coord.indexOf("!"); + if (bindex != -1) { + sheetname = coord.slice(0, bindex); + coord = coord.slice(bindex + 1); + //console.log(sheetname,coord) + } + var control = SocialCalc.GetCurrentWorkBookControl(); + + if (sheetname == null) { + sheetid = control.currentSheetButton.id; + } else { + sheetid = control.workbook.SheetNameExistsInWorkBook(sheetname); + } + + if (sheetid == null || sheetid == "") { + return "0"; + } + + var sheetobj = control.workbook.sheetArr[sheetid].sheet; + + var cell = sheetobj.cells[coord]; + + if (cell) { + return cell.datavalue; + } else { + return 0; + } + }; + + SocialCalc.GetCellDataArray = function (coordstr, sheetname) { + var vals = []; + var coords = coordstr.split(","); + if (sheetname == null) { + sheetname = ""; + } else { + sheetname = sheetname + "!"; + } + for (var c in coords) { + vals.push(SocialCalc.GetCellDataValue(sheetname + coords[c])); + } + return vals; + }; + + SocialCalc.UserScriptData = {}; + + SocialCalc.WorkBookRecalculateInfo = { + sheets: [], + calcorder: [], + current: 0, + pass: 0, + }; + + SocialCalc.WorkBookRecalculateAll = function () { + // do it from the last sheet to the first sheet + // using the recalc-done signal to trigger the next sheet + + // if already in the middle of a recalculate-all, ignore this. + if ( + SocialCalc.WorkBookRecalculateInfo.current != 0 || + SocialCalc.WorkBookRecalculateInfo.calcorder.length != 0 || + SocialCalc.WorkBookRecalculateInfo.sheets.length != 0 + ) { + return; + } + + var control = SocialCalc.GetCurrentWorkBookControl(); + + if (control.workbook.spreadsheet.editor.state != "start") { + // if in edit return + return; + } + + SocialCalc.WorkBookRecalculateInfo.current = 0; + + for (var sheet in control.workbook.sheetArr) { + SocialCalc.WorkBookRecalculateInfo.sheets.push(sheet); + } + + var i = 0; + for (var c = SocialCalc.WorkBookRecalculateInfo.sheets.length; c > 0; c--) { + SocialCalc.WorkBookRecalculateInfo.calcorder[i] = + SocialCalc.WorkBookRecalculateInfo.sheets[c - 1]; + i++; + } + window.setTimeout(SocialCalc.WorkBookRecalculateStep, 500); + }; + + SocialCalc.WorkBookRecalculateStep = function () { + if ( + SocialCalc.WorkBookRecalculateInfo.current == + SocialCalc.WorkBookRecalculateInfo.calcorder.length + ) { + SocialCalc.WorkBookRecalculateInfo.current = 0; + SocialCalc.WorkBookRecalculateInfo.calcorder = []; + SocialCalc.WorkBookRecalculateInfo.sheets = []; + if (SocialCalc.WorkBookRecalculateInfo.pass == 1) { + SocialCalc.WorkBookRecalculateInfo.pass = 0; + SocialCalc.SpinnerWaitHide(); + //alert("load done"); + return; + } else { + SocialCalc.WorkBookRecalculateInfo.pass++; + SocialCalc.WorkBookRecalculateAll(); + return; + } + } + var control = SocialCalc.GetCurrentWorkBookControl(); + //alert("recalculate "+ + // SocialCalc.WorkBookRecalculateInfo.calcorder[ + // SocialCalc.WorkBookRecalculateInfo.current] + // ); + var sheetid = + SocialCalc.WorkBookRecalculateInfo.calcorder[ + SocialCalc.WorkBookRecalculateInfo.current + ]; + SocialCalc.WorkBookControlActivateSheet(sheetid); + SocialCalc.WorkBookRecalculateInfo.current++; + + window.setTimeout(SocialCalc.WorkBookRecalculateStep, 1000); + }; + + SocialCalc.SpinnerWaitCreate = function () { + // if the div exists already just use it + var ele = document.getElementById("waitloadingspinner"); + if (ele) { + return; + } + var main = document.createElement("div"); + main.id = "waitloadingspinner"; + + main.style.position = "absolute"; + + var vp = SocialCalc.GetViewportInfo(); + main.style.top = vp.height / 2 + "px"; + main.style.left = vp.width / 2 + "px"; + main.style.zIndex = 110; + + main.style.width = "50px"; + main.style.height = "50px"; + main.innerHTML = + 'Loading...'; + + var control = SocialCalc.GetCurrentWorkBookControl(); + control.workbook.spreadsheet.spreadsheetDiv.appendChild(main); + }; + + SocialCalc.SpinnerWaitHide = function () { + // if the div exists already just use it + + var ele = document.getElementById("waitloadingspinner"); + if (ele) { + ele.innerHTML = ""; + + if (ele.parentNode) { + ele.parentNode.removeChild(ele); + } + } + }; + + SocialCalc.EditableCells = {}; + SocialCalc.EditableCells.allow = false; + SocialCalc.EditableCells.cells = {}; + + SocialCalc.Callbacks.IsCoordEditable = function (sheetcoord) { + if (!SocialCalc.EditableCells.allow) { + // by default all cells are editable + return true; + } + if (SocialCalc.EditableCells.cells[sheetcoord]) { + // by default all cells are editable + return true; + } + + return false; + }; + + SocialCalc.Callbacks.IsCellEditable = function (editor) { + var cellname = editor.workingvalues.currentsheet + "!" + editor.ecell.coord; + if (!SocialCalc.EditableCells.allow) { + // by default all cells are editable + return true; + } + if (SocialCalc.EditableCells.cells[cellname]) { + // by default all cells are editable + return true; + } + + return false; + }; + + SocialCalc.IsScrollPossible = function ( + lastrow, + lastcol, + curr_vpos, + curr_hpos, + vamount, + hamount + ) { + //return false; + //console.log(lastrow+","+lastcol); + //console.log(curr_vpos+","+curr_hpos); + //console.log(vamount+","+hamount); + + if (curr_vpos + 10 + vamount > lastrow) { + return false; + } + if (curr_hpos + hamount > lastcol) { + return false; + } + return true; + }; + + // this is for checkmark toggling + SocialCalc.Callbacks.ToggleCell = function (cellname) { + var control = SocialCalc.GetCurrentWorkBookControl(); + var sheetid = control.currentSheetButton.id; + var sheetobj = control.workbook.sheetArr[sheetid].sheet; + var cell = sheetobj.cells[cellname]; + var sheetname = sheetobj.sheetname; + + // check if cell is in constraints + //console.log(sheetname); + //console.log(cellname); + + var constraint = + SocialCalc.EditableCells.constraints[sheetname + "!" + cellname]; + if (!constraint || constraint[0] != "tc") { + return; + } + + var cellinner = document.getElementById("cell_" + cellname); + + if (cellinner.innerHTML.indexOf(" ") != -1) { + // set the value to the img value + cellinner.innerHTML = + '
'; + if (cell) { + //cell.displaystring = '
' ; + //cell.datavalue = '
' ; + //console.log("found cell") + cell.displaystring = + '
'; + cell.datavalue = + '
'; + //http://img689.imageshack.us/img689/9234/checkmark.png + } + } else { + // set the value to a space + cellinner.innerHTML = "
 
"; + if (cell) { + cell.datavalue = "
 
"; + cell.displaystring = "
 
"; + } + } + }; + + SocialCalc.WorkbookControlCreateSheetHTML = function (sheetlist) { + var context, div, ele; + + var result = ""; + + var control = SocialCalc.GetCurrentWorkBookControl(); + + div = document.createElement("div"); + + if (!sheetlist) { + context = new SocialCalc.RenderContext(spreadsheet.sheet); + ele = context.RenderSheet(null, { type: "html" }); + div.appendChild(ele); + context = undefined; + } else { + for (var sheetid in sheetlist) { + context = new SocialCalc.RenderContext( + control.workbook.sheetArr[sheetid].sheet + ); + ele = context.RenderSheet(null, { type: "html" }); + context = undefined; + div.appendChild(ele); + if (sheetid.substring(5) == control.sheetCnt) { + ele.style.pageBreakAfter = "auto"; + } else { + ele.style.pageBreakAfter = "always"; + } + } + } + + result = div.innerHTML; + ele = undefined; + div = undefined; + //console.log(result); + return result; + }; + + /* + http://www.JSON.org/json2.js + 2010-08-25 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + + /*jslint evil: true, strict: false */ + + /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + // Create a JSON object only if one does not already exist. We create the + // methods in a closure to avoid creating global variables. + + var globalThis = (function() { + if (typeof window !== "undefined") return window; + if (typeof global !== "undefined") return global; + if (typeof self !== "undefined") return self; + return this; + })(); + + if (!globalThis.JSON) { + globalThis.JSON = {}; + } + + (function () { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? "0" + n : n; + } + + if (typeof Date.prototype.toJSON !== "function") { + Date.prototype.toJSON = function (key) { + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + + "-" + + f(this.getUTCMonth() + 1) + + "-" + + f(this.getUTCDate()) + + "T" + + f(this.getUTCHours()) + + ":" + + f(this.getUTCMinutes()) + + ":" + + f(this.getUTCSeconds()) + + "Z" + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = + function (key) { + return this.valueOf(); + }; + } + + var cx = + /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = + /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { + // table of character substitutions + "\b": "\\b", + "\t": "\\t", + "\n": "\\n", + "\f": "\\f", + "\r": "\\r", + '"': '\\"', + "\\": "\\\\", + }, + rep; + + function quote(string) { + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can safely slap some quotes around it. + // Otherwise we must also replace the offending characters with safe escape + // sequences. + + escapable.lastIndex = 0; + return escapable.test(string) + ? '"' + + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === "string" + ? c + : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }) + + '"' + : '"' + string + '"'; + } + + function str(key, holder) { + // Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + + // If the value has a toJSON method, call it to obtain a replacement value. + + if ( + value && + typeof value === "object" && + typeof value.toJSON === "function" + ) { + value = value.toJSON(key); + } + + // If we were called with a replacer function, then call the replacer to + // obtain a replacement value. + + if (typeof rep === "function") { + value = rep.call(holder, key, value); + } + + // What happens next depends on the value's type. + + switch (typeof value) { + case "string": + return quote(value); + + case "number": + // JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : "null"; + + case "boolean": + case "null": + // If the value is a boolean or null, convert it to a string. Note: + // typeof null does not produce 'null'. The case is included here in + // the remote chance that this gets fixed someday. + + return String(value); + + // If the type is 'object', we might be dealing with an object or an array or + // null. + + case "object": + // Due to a specification blunder in ECMAScript, typeof null is 'object', + // so watch out for that case. + + if (!value) { + return "null"; + } + + // Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + + // Is the value an array? + + if (Object.prototype.toString.apply(value) === "[object Array]") { + // The value is an array. Stringify every element. Use null as a placeholder + // for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || "null"; + } + + // Join all of the elements together, separated with commas, and wrap them in + // brackets. + + v = + partial.length === 0 + ? "[]" + : gap + ? "[\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "]" + : "[" + partial.join(",") + "]"; + gap = mind; + return v; + } + + // If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === "object") { + length = rep.length; + for (i = 0; i < length; i += 1) { + k = rep[i]; + if (typeof k === "string") { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ": " : ":") + v); + } + } + } + } else { + // Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ": " : ":") + v); + } + } + } + } + + // Join all of the member texts together, separated with commas, + // and wrap them in braces. + + v = + partial.length === 0 + ? "{}" + : gap + ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" + : "{" + partial.join(",") + "}"; + gap = mind; + return v; + } + } + + // If the JSON object does not yet have a stringify method, give it one. + + if (typeof globalThis.JSON.stringify !== "function") { + globalThis.JSON.stringify = function (value, replacer, space) { + // The stringify method takes a value and an optional replacer, and an optional + // space parameter, and returns a JSON text. The replacer can be a function + // that can replace values, or an array of strings that will select the keys. + // A default replacer method can be provided. Use of the space parameter can + // produce text that is more easily readable. + + var i; + gap = ""; + indent = ""; + + // If the space parameter is a number, make an indent string containing that + // many spaces. + + if (typeof space === "number") { + for (i = 0; i < space; i += 1) { + indent += " "; + } + + // If the space parameter is a string, it will be used as the indent string. + } else if (typeof space === "string") { + indent = space; + } + + // If there is a replacer, it must be a function or an array. + // Otherwise, throw an error. + + rep = replacer; + if ( + replacer && + typeof replacer !== "function" && + (typeof replacer !== "object" || typeof replacer.length !== "number") + ) { + throw new Error("JSON.stringify"); + } + + // Make a fake root object containing our value under the key of ''. + // Return the result of stringifying the value. + + return str("", { "": value }); + }; + } + + // If the JSON object does not yet have a parse method, give it one. + + if (typeof globalThis.JSON.parse !== "function") { + globalThis.JSON.parse = function (text, reviver) { + // The parse method takes a text and an optional reviver function, and returns + // a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + // The walk method is used to recursively walk the resulting structure so + // that modifications can be made. + + var k, + v, + value = holder[key]; + if (value && typeof value === "object") { + for (k in value) { + if (Object.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + // Parsing happens in four stages. In the first stage, we replace certain + // Unicode characters with escape sequences. JavaScript handles many characters + // incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + + // In the second stage, we run the text against regular expressions that look + // for non-JSON patterns. We are especially concerned with '()' and 'new' + // because they can cause invocation, and '=' because it can cause mutation. + // But just to be safe, we want to reject all unexpected forms. + + // We split the second stage into 4 regexp operations in order to work around + // crippling inefficiencies in IE's and Safari's regexp engines. First we + // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we + // replace all simple value tokens with ']' characters. Third, we delete all + // open brackets that follow a colon or comma or that begin the text. Finally, + // we look to see that the remaining characters are only whitespace or ']' or + // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if ( + /^[\],:{}\s]*$/.test( + text + .replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace( + /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, + "]" + ) + .replace(/(?:^|:|,)(?:\s*\[)+/g, "") + ) + ) { + // In the third stage we use the eval function to compile the text into a + // JavaScript structure. The '{' operator is subject to a syntactic ambiguity + // in JavaScript: it can begin a block or an object literal. We wrap the text + // in parens to eliminate the ambiguity. + + j = eval("(" + text + ")"); + + // In the optional fourth stage, we recursively walk the new structure, passing + // each name/value pair to a reviver function for possible transformation. + + return typeof reviver === "function" ? walk({ "": j }, "") : j; + } + + // If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError("JSON.parse"); + }; + } + })(); + + SocialCalc.oldBtnActive = 1; + SocialCalc.Constants.defaultImagePrefix = "www/assets/images/sc_"; + SocialCalc.Constants.defaultGridCSS = ""; + SocialCalc.Constants.SCNoColNames = true; + SocialCalc.Constants.SCNoRowName = true; + SocialCalc.Constants.defaultRownameStyle = ""; + SocialCalc.Constants.defaultSelectedRownameStyle = ""; + SocialCalc.Popup.imagePrefix = "www/assets/images/sc_"; + + SocialCalc.ToggleInputLineButtons = function (show) { + var bele = document.getElementById("testtest"); + if (!bele) return; + if (show) { + bele.style.display = "inline"; + } else { + bele.style.display = "none"; + } + }; + + SocialCalc.InputLineClearText = function () { + spreadsheet.editor.inputBox.SetText(""); + }; + + SocialCalc.Callbacks.broadcast = function (type, data) {}; + + // END OF FILE + + if ("undefined" === typeof document) { + // We don't really need a DOM-based presentation layer on the server + SocialCalc.GetEditorCellElement = function () {}; + SocialCalc.ReplaceCell = function () {}; + SocialCalc.EditorRenderSheet = function () {}; + SocialCalc.SpreadsheetControlSortSave = function () { + return ""; + }; + SocialCalc.SpreadsheetControlStatuslineCallback = function () {}; + SocialCalc.DoPositionCalculations = function (editor) { + SocialCalc.EditorSheetStatusCallback(null, "doneposcalc", null, editor); + }; + } + + // Compatibility with webworker-threads + if (typeof self !== "undefined" && self.thread) { + window.setTimeout = function (cb, ms) { + if (ms <= 1) { + self.thread.nextTick(cb); + } + }; + window.clearTimeout = function () {}; + } + + // Just return a value to define the module export. + return SocialCalc; +}); + + diff --git a/src/components/socialcalc/core/table-editor.js b/src/components/socialcalc/core/table-editor.js new file mode 100644 index 0000000..08dd693 --- /dev/null +++ b/src/components/socialcalc/core/table-editor.js @@ -0,0 +1,8133 @@ +/* eslint-disable */ +// SocialCalc Table Editor Module +// Extracted from the main SocialCalc.js file + +// UMD wrapper +(function (root, factory) { + if (typeof define === "function" && define.amd) { + define([], factory); + } else if (typeof module === "object" && module.exports) { + module.exports = factory(); + } else { + root.SocialCalcTableEditor = factory(); + } +})(typeof self !== "undefined" ? self : this, function () { + + // Get SocialCalc namespace + var SocialCalc; + if (typeof window !== "undefined" && window.SocialCalc) { + SocialCalc = window.SocialCalc; + } else if (typeof global !== "undefined" && global.SocialCalc) { + SocialCalc = global.SocialCalc; + } else { + SocialCalc = {}; + } + + + // + // SocialCalcTableEditor + // + /* + // The code module of the SocialCalc package that displays a scrolling grid with panes + // and handles keyboard and mouse I/O. + // + // (c) Copyright 2008, 2009, 2010 Socialtext, Inc. + // All Rights Reserved. + // + */ + + /* + + LEGAL NOTICES REQUIRED BY THE COMMON PUBLIC ATTRIBUTION LICENSE: + + EXHIBIT A. Common Public Attribution License Version 1.0. + + The contents of this file are subject to the Common Public Attribution License Version 1.0 (the + "License"); you may not use this file except in compliance with the License. You may obtain a copy + of the License at http://socialcalc.org. The License is based on the Mozilla Public License Version 1.1 but + Sections 14 and 15 have been added to cover use of software over a computer network and provide for + limited attribution for the Original Developer. In addition, Exhibit A has been modified to be + consistent with Exhibit B. + + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + KIND, either express or implied. See the License for the specific language governing rights and + limitations under the License. + + The Original Code is SocialCalc JavaScript TableEditor. + + The Original Developer is the Initial Developer. + + The Initial Developer of the Original Code is Socialtext, Inc. All portions of the code written by + Socialtext, Inc., are Copyright (c) Socialtext, Inc. All Rights Reserved. + + Contributor: Dan Bricklin. + + + EXHIBIT B. Attribution Information + + When the TableEditor is producing and/or controlling the display the Graphic Image must be + displayed on the screen visible to the user in a manner comparable to that in the + Original Code. The Attribution Phrase must be displayed as a "tooltip" or "hover-text" for + that image. The image must be linked to the Attribution URL so as to access that page + when clicked. If the user interface includes a prominent "about" display which includes + factual prominent attribution in a form similar to that in the "about" display included + with the Original Code, including Socialtext copyright notices and URLs, then the image + need not be linked to the Attribution URL but the "tool-tip" is still required. + + Attribution Copyright Notice: + + Copyright (C) 2010 Socialtext, Inc. + All Rights Reserved. + + Attribution Phrase (not exceeding 10 words): SocialCalc + + Attribution URL: http://www.socialcalc.org/xoattrib + + Graphic Image: The contents of the sc-logo.gif file in the Original Code or + a suitable replacement from http://www.socialcalc.org/licenses specified as + being for SocialCalc. + + Display of Attribution Information is required in Larger Works which are defined + in the CPAL as a work which combines Covered Code or portions thereof with code + not governed by the terms of the CPAL. + + */ + + // + // Some of the other files in the SocialCalc package are licensed under + // different licenses. Please note the licenses of the modules you use. + // + // Code History: + // + // Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc. + // Based in part on the SocialCalc 1.1.0 code written in Perl. + // The SocialCalc 1.1.0 code was: + // Portions (c) Copyright 2005, 2006, 2007 Software Garden, Inc. + // All Rights Reserved. + // Portions (c) Copyright 2007 Socialtext, Inc. + // All Rights Reserved. + // The Perl SocialCalc started as modifications to the wikiCalc(R) program, version 1.0. + // wikiCalc 1.0 was written by Software Garden, Inc. + // Unless otherwise specified, referring to "SocialCalc" in comments refers to this + // JavaScript version of the code, not the SocialCalc Perl code. + // + + /* + + See the comments in the main SocialCalc code module file of the SocialCalc package. + + */ + + var SocialCalc; + if (!SocialCalc) { + // created here, too, in case load order is wrong, but main routines are required + SocialCalc = {}; + } + + // ************************************* + // + // Table Editor class: + // + // ************************************* + + // Constructor: + + SocialCalc.TableEditor = function (context) { + var scc = SocialCalc.Constants; + + // Properties: + + this.context = context; // editing context + this.toplevel = null; // top level HTML element for this table editor + this.fullgrid = null; // rendered editing context + + this.noEdit = false; // if true, disable all edit UI and make read-only + + this.width = null; + this.tablewidth = null; + this.height = null; + this.tableheight = null; + + this.inputBox = null; + this.inputEcho = null; + this.verticaltablecontrol = null; + this.horizontaltablecontrol = null; + + this.logo = null; + + this.cellhandles = null; + + // Dynamic properties: + + this.timeout = null; // if non-null, timer id for position calculations + this.busy = false; // true when executing command, calculating, etc. + this.ensureecell = false; // if true, ensure ecell is visible after timeout + this.deferredCommands = []; // commands to execute after busy, in form: {cmdstr: "cmds", saveundo: t/f} + + this.gridposition = null; // screen coords of full grid + this.headposition = null; // screen coords of upper left of grid within header rows + this.firstscrollingrow = null; // row number of top row in last (the scrolling) pane + this.firstscrollingrowtop = null; // position of top row in last (the scrolling) pane + this.lastnonscrollingrow = null; // row number of last displayed row in last non-scrolling + // pane, or zero (for thumb position calculations) + this.lastvisiblerow = null; // used for paging down + this.firstscrollingcol = null; // column number of top col in last (the scrolling) pane + this.firstscrollingcolleft = null; // position of top col in last (the scrolling) pane + this.lastnonscrollingcol = null; // col number of last displayed column in last non-scrolling + // pane, or zero (for thumb position calculations) + this.lastvisiblecol = null; // used for paging right + + this.rowpositions = []; // screen positions of the top of some rows + this.colpositions = []; // screen positions of the left side of some rows + this.rowheight = []; // size in pixels of each row when last checked, or null/undefined, for page up + this.colwidth = []; // size in pixels of each column when last checked, or null/undefined, for page left + + this.ecell = null; // either null or {coord: c, row: r, col: c} + this.state = "start"; // the keyboard states: see EditorProcessKey + + this.workingvalues = {}; // values used during keyboard editing, etc. + + // Constants: + + this.imageprefix = scc.defaultImagePrefix; // URL prefix for images (e.g., "/www/assets/images/sc") + this.idPrefix = scc.defaultTableEditorIDPrefix; + this.pageUpDnAmount = scc.defaultPageUpDnAmount; // number of rows to move cursor on PgUp/PgDn keys (numeric) + + // Callbacks + + // recalcFunction: if present, function(editor) {...}, called to do a recalc + // Default (sheet.RecalcSheet) does all the right stuff. + + this.recalcFunction = function (editor) { + if (editor.context.sheetobj.RecalcSheet) { + editor.context.sheetobj.RecalcSheet( + SocialCalc.EditorSheetStatusCallback, + editor + ); + } else return null; + }; + + // ctrlkeyFunction: if present, function(editor, charname) {...}, called to handle ctrl-V, etc., at top level + // Returns true (pass through for continued processing) or false (stop processing this key). + + this.ctrlkeyFunction = function (editor, charname) { + var ta, ha, cell, position, cmd, sel, cliptext; + + switch (charname) { + case "[ctrl-c]": + case "[ctrl-x]": + ta = editor.pasteTextarea; + ta.value = ""; + cell = SocialCalc.GetEditorCellElement( + editor, + editor.ecell.row, + editor.ecell.col + ); + if (cell) { + position = SocialCalc.GetElementPosition(cell.element); + ta.style.left = position.left - 1 + "px"; + ta.style.top = position.top - 1 + "px"; + } + if (editor.range.hasrange) { + sel = + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom); + } else { + sel = editor.ecell.coord; + } + + // get what to copy to clipboard + cliptext = SocialCalc.ConvertSaveToOtherFormat( + SocialCalc.CreateSheetSave(editor.context.sheetobj, sel), + "tab" + ); + + if ( + charname == "[ctrl-c]" || + editor.noEdit || + (SocialCalc.Callbacks.IsCellEditable && + !SocialCalc.Callbacks.IsCellEditable(editor)) + ) { + // if copy or cut but in no edit + cmd = "copy " + sel + " formulas"; + } else { + // [ctrl-x] + cmd = "cut " + sel + " formulas"; + } + editor.EditorScheduleSheetCommands(cmd, true, false); // queue up command to put on SocialCalc clipboard + + /* Copy as HTML: This fails rather badly as it won't paste into Notepad as tab-delimited text. Oh well. + + ha = editor.pasteHTMLarea; + if (editor.range.hasrange) { + cell = SocialCalc.GetEditorCellElement(editor, editor.range.top, editor.range.left); + } + else { + cell = SocialCalc.GetEditorCellElement(editor, editor.ecell.row, editor.ecell.col); + } + if (cell) position = SocialCalc.GetElementPosition(cell.element); + + if (ha) { + if (position) { + ha.style.left = (position.left-1)+"px"; + ha.style.top = (position.top-1)+"px"; + } + ha.style.visibility="visible"; + cliptext = SocialCalc.ConvertSaveToOtherFormat(SocialCalc.CreateSheetSave(editor.context.sheetobj, sel), "html"); + ha.innerHTML = cliptext.replace(/]*>[\d\D]*?<\/tr\b[^>]*>/i, ''); + ha.focus(); + + var range = document.body.createControlRange(); + range.addElement(ha.childNodes[0]); + range.select(); + } + */ + ta.style.display = "block"; + ta.value = cliptext; // must follow "block" setting for Webkit + ta.focus(); + ta.select(); + window.setTimeout(function () { + if (!SocialCalc.GetSpreadsheetControlObject) return; // in case not loaded + var s = SocialCalc.GetSpreadsheetControlObject(); + if (!s) return; + var editor = s.editor; + /* + var ha = editor.pasteHTMLarea; + if (ha) { + ha.blur(); + ha.innerHTML = ''; + ha.style.visibility = 'hidden'; + } + */ + var ta = editor.pasteTextarea; + ta.blur(); + ta.style.display = "none"; + SocialCalc.KeyboardFocus(); + }, 200); + + return true; + + case "[ctrl-v]": + if (editor.noEdit) return true; // not if no edit + if (SocialCalc.Callbacks && SocialCalc.Callbacks.IsCellEditable) { + if (!SocialCalc.Callbacks.IsCellEditable(editor)) { + return true; + } + } + + var showPasteTextArea = function () { + ta = editor.pasteTextarea; + ta.value = ""; + + cell = SocialCalc.GetEditorCellElement( + editor, + editor.ecell.row, + editor.ecell.col + ); + if (cell) { + position = SocialCalc.GetElementPosition(cell.element); + ta.style.left = position.left - 1 + "px"; + ta.style.top = position.top - 1 + "px"; + } + ta.style.display = "block"; + ta.value = ""; // must follow "block" setting for Webkit + ta.focus(); + }; + + ha = editor.pasteHTMLarea; + if (ha) { + /* Pasting via HTML - Currently IE only */ + ha.style.visibility = "visible"; + ha.focus(); + } else { + showPasteTextArea(); + } + window.setTimeout(function () { + if (!SocialCalc.GetSpreadsheetControlObject) return; + var s = SocialCalc.GetSpreadsheetControlObject(); + if (!s) return; + var editor = s.editor; + var value = null; + var isPasteSameAsClipboard = false; + + ha = editor.pasteHTMLarea; + if (ha) { + /* IE: We append a U+FFFC to every TD that's not the last of its row, + * then we obtain innerText, then turn U+FFFC back to \t, + * thereby preserving the cell separations (which gets discarded + * if we simply paste via textarea. + */ + var _ObjectReplacementCharacter_ = String.fromCharCode(0xfffc); + var html = ha.innerHTML; + + if (html.search(/<(?![Bb][Rr])[A-Za-z]/) >= 0) { + /* HTML Paste: Mark TDs with U+FFFC accordingly.. */ + ha.innerHTML = html.replace( + /(?:<\/[Tt][Dd]>)/g, + _ObjectReplacementCharacter_ + ); + } else { + /* Text Paste: In IE, \t is transformed into  , so replace them with U+FFFC. */ + ha.innerHTML = html.replace( + /&[Nn][Bb][Ss][Pp];/g, + _ObjectReplacementCharacter_ + ); + } + + value = ha.innerText.replace( + new RegExp(_ObjectReplacementCharacter_, "g"), + "\t" + ); + + ha.innerHTML = ""; + ha.blur(); + ha.style.visibility = "hidden"; + } else { + var ta = editor.pasteTextarea; + value = ta.value; + ta.blur(); + ta.style.display = "none"; + } + + value = value.replace(/\r\n/g, "\n").replace(/\n?$/, "\n"); + var clipstr = SocialCalc.ConvertSaveToOtherFormat( + SocialCalc.Clipboard.clipboard, + "tab" + ); + if ( + value == clipstr || + (value.length - clipstr.length == 1 && + value.substring(0, value.length - 1) == clipstr) + ) { + isPasteSameAsClipboard = true; + } + + var cmd = ""; + // pastes SocialCalc clipboard if did a Ctrl-C and contents still the same + // Webkit adds an extra blank line, so need to allow for that + if (!isPasteSameAsClipboard) { + cmd = + "loadclipboard " + + SocialCalc.encodeForSave( + SocialCalc.ConvertOtherFormatToSave(value, "tab") + ) + + "\n"; + } + var cr; + if (editor.range.hasrange) { + cr = SocialCalc.crToCoord(editor.range.left, editor.range.top); + } else { + cr = editor.ecell.coord; + } + cmd += "paste " + cr + " formulas"; + editor.EditorScheduleSheetCommands(cmd, true, false); + SocialCalc.KeyboardFocus(); + }, 200); + return true; + + case "[ctrl-z]": + editor.EditorScheduleSheetCommands("undo", true, false); + return false; + + case "[ctrl-s]": // !!!! temporary hack + window.setTimeout(function () { + if (!SocialCalc.GetSpreadsheetControlObject) return; + var s = SocialCalc.GetSpreadsheetControlObject(); + if (!s) return; + var editor = s.editor; + var sheet = editor.context.sheetobj; + var cell = sheet.GetAssuredCell(editor.ecell.coord); + var ntvf = cell.nontextvalueformat + ? sheet.valueformats[cell.nontextvalueformat - 0] || "" + : ""; + var newntvf = window.prompt( + "Advanced Feature:\n\nCustom Numeric Format or Command", + ntvf + ); + if (newntvf != null) { + // not cancelled + if (newntvf.match(/^cmd:/)) { + cmd = newntvf.substring(4); // execute as command + } else if (newntvf.match(/^edit:/)) { + cmd = newntvf.substring(5); // execute as command + if (SocialCalc.CtrlSEditor) { + SocialCalc.CtrlSEditor(cmd); + } + return; + } else { + if (editor.range.hasrange) { + sel = + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord( + editor.range.right, + editor.range.bottom + ); + } else { + sel = editor.ecell.coord; + } + cmd = "set " + sel + " nontextvalueformat " + newntvf; + } + editor.EditorScheduleSheetCommands(cmd, true, false); + } + }, 200); + return false; + + default: + break; + } + return true; + }; + + // Set sheet's status callback: + + context.sheetobj.statuscallback = SocialCalc.EditorSheetStatusCallback; + context.sheetobj.statuscallbackparams = this; // this object: the table editor object + + // StatusCallback: all values are called at appropriate times, add with unique name, delete when done + // + // Each value must be an object in the form of: + // + // func: function(editor, status, arg, params) {...}, + // params: params value to call func with + // + // The values for status and arg are: + // + // all the SocialCalc RecalcSheet statuscallbacks, including: + // + // calccheckdone, calclist length + // calcorder, {coord: coord, total: celllist length, count: count} + // calcstep, {coord: coord, total: calclist length, count: count} + // calcfinished, time in milliseconds + // + // the command callbacks, like cmdstart and cmdend + // cmdendnorender + // + // calcstart, null + // moveecell, new ecell coord + // rangechange, "coord:coord" or "coord" or "" + // specialkey, keyname ("[esc]") + // + + this.StatusCallback = {}; + + this.MoveECellCallback = {}; // all values are called with editor as arg; add with unique name, delete when done + this.RangeChangeCallback = {}; // all values are called with editor as arg; add with unique name, delete when done + this.SettingsCallbacks = {}; // See SocialCalc.SaveEditorSettings + + // Set initial cursor + + this.ecell = { coord: "A1", row: 1, col: 1 }; + context.highlights[this.ecell.coord] = "cursor"; + + // Initialize range data + // Range has at least hasrange (true/false). + // It may also have: anchorcoord, anchorrow, anchorcol, top, bottom, left, and right. + + this.range = { hasrange: false }; + + // Initialize range2 data (used to show selections, such as for move) + // Range2 has at least hasrange (true/false). + // It may also have: top, bottom, left, and right. + + this.range2 = { hasrange: false }; + }; + + // Methods: + + SocialCalc.TableEditor.prototype.CreateTableEditor = function ( + width, + height + ) { + return SocialCalc.CreateTableEditor(this, width, height); + }; + SocialCalc.TableEditor.prototype.ResizeTableEditor = function ( + width, + height + ) { + return SocialCalc.ResizeTableEditor(this, width, height); + }; + + SocialCalc.TableEditor.prototype.SaveEditorSettings = function () { + return SocialCalc.SaveEditorSettings(this); + }; + SocialCalc.TableEditor.prototype.LoadEditorSettings = function (str, flags) { + return SocialCalc.LoadEditorSettings(this, str, flags); + }; + + SocialCalc.TableEditor.prototype.EditorRenderSheet = function () { + SocialCalc.EditorRenderSheet(this); + }; + SocialCalc.TableEditor.prototype.EditorScheduleSheetCommands = function ( + cmdstr, + saveundo, + ignorebusy + ) { + SocialCalc.EditorScheduleSheetCommands(this, cmdstr, saveundo, ignorebusy); + }; + SocialCalc.TableEditor.prototype.ScheduleSheetCommands = function ( + cmdstr, + saveundo + ) { + this.context.sheetobj.ScheduleSheetCommands(cmdstr, saveundo); + }; + SocialCalc.TableEditor.prototype.SheetUndo = function () { + this.context.sheetobj.SheetUndo(); + }; + SocialCalc.TableEditor.prototype.SheetRedo = function () { + this.context.sheetobj.SheetRedo(); + }; + SocialCalc.TableEditor.prototype.EditorStepSet = function (status, arg) { + SocialCalc.EditorStepSet(this, status, arg); + }; + SocialCalc.TableEditor.prototype.GetStatuslineString = function ( + status, + arg, + params + ) { + return SocialCalc.EditorGetStatuslineString(this, status, arg, params); + }; + + SocialCalc.TableEditor.prototype.EditorMouseRegister = function () { + return SocialCalc.EditorMouseRegister(this); + }; + SocialCalc.TableEditor.prototype.EditorMouseUnregister = function () { + return SocialCalc.EditorMouseUnregister(this); + }; + SocialCalc.TableEditor.prototype.EditorMouseRange = function (coord) { + return SocialCalc.EditorMouseRange(this, coord); + }; + + SocialCalc.TableEditor.prototype.EditorProcessKey = function (ch, e) { + return SocialCalc.EditorProcessKey(this, ch, e); + }; + SocialCalc.TableEditor.prototype.EditorAddToInput = function (str, prefix) { + return SocialCalc.EditorAddToInput(this, str, prefix); + }; + SocialCalc.TableEditor.prototype.DisplayCellContents = function () { + return SocialCalc.EditorDisplayCellContents(this); + }; + SocialCalc.TableEditor.prototype.EditorSaveEdit = function (text) { + return SocialCalc.EditorSaveEdit(this, text); + }; + SocialCalc.TableEditor.prototype.EditorApplySetCommandsToRange = function ( + cmdline, + type + ) { + return SocialCalc.EditorApplySetCommandsToRange(this, cmdline, type); + }; + + SocialCalc.TableEditor.prototype.MoveECellWithKey = function (ch) { + return SocialCalc.MoveECellWithKey(this, ch); + }; + SocialCalc.TableEditor.prototype.MoveECell = function (newcell) { + return SocialCalc.MoveECell(this, newcell); + }; + SocialCalc.TableEditor.prototype.ReplaceCell = function (cell, row, col) { + SocialCalc.ReplaceCell(this, cell, row, col); + }; + SocialCalc.TableEditor.prototype.UpdateCellCSS = function (cell, row, col) { + SocialCalc.UpdateCellCSS(this, cell, row, col); + }; + SocialCalc.TableEditor.prototype.SetECellHeaders = function (selected) { + SocialCalc.SetECellHeaders(this, selected); + }; + SocialCalc.TableEditor.prototype.EnsureECellVisible = function () { + SocialCalc.EnsureECellVisible(this); + }; + SocialCalc.TableEditor.prototype.RangeAnchor = function (coord) { + SocialCalc.RangeAnchor(this, coord); + }; + SocialCalc.TableEditor.prototype.RangeExtend = function (coord) { + SocialCalc.RangeExtend(this, coord); + }; + SocialCalc.TableEditor.prototype.RangeRemove = function () { + SocialCalc.RangeRemove(this); + }; + SocialCalc.TableEditor.prototype.Range2Remove = function () { + SocialCalc.Range2Remove(this); + }; + + SocialCalc.TableEditor.prototype.FitToEditTable = function () { + SocialCalc.FitToEditTable(this); + }; + SocialCalc.TableEditor.prototype.CalculateEditorPositions = function () { + SocialCalc.CalculateEditorPositions(this); + }; + SocialCalc.TableEditor.prototype.ScheduleRender = function () { + SocialCalc.ScheduleRender(this); + }; + SocialCalc.TableEditor.prototype.DoRenderStep = function () { + SocialCalc.DoRenderStep(this); + }; + SocialCalc.TableEditor.prototype.SchedulePositionCalculations = function () { + SocialCalc.SchedulePositionCalculations(this); + }; + SocialCalc.TableEditor.prototype.DoPositionCalculations = function () { + SocialCalc.DoPositionCalculations(this); + }; + SocialCalc.TableEditor.prototype.CalculateRowPositions = function ( + panenum, + positions, + sizes + ) { + return SocialCalc.CalculateRowPositions(this, panenum, positions, sizes); + }; + SocialCalc.TableEditor.prototype.CalculateColPositions = function ( + panenum, + positions, + sizes + ) { + return SocialCalc.CalculateColPositions(this, panenum, positions, sizes); + }; + + SocialCalc.TableEditor.prototype.ScrollRelative = function ( + vertical, + amount + ) { + SocialCalc.ScrollRelative(this, vertical, amount); + }; + SocialCalc.TableEditor.prototype.ScrollRelativeBoth = function ( + vamount, + hamount + ) { + SocialCalc.ScrollRelativeBoth(this, vamount, hamount); + }; + SocialCalc.TableEditor.prototype.PageRelative = function ( + vertical, + direction + ) { + SocialCalc.PageRelative(this, vertical, direction); + }; + SocialCalc.TableEditor.prototype.LimitLastPanes = function () { + SocialCalc.LimitLastPanes(this); + }; + + SocialCalc.TableEditor.prototype.ScrollTableUpOneRow = function () { + return SocialCalc.ScrollTableUpOneRow(this); + }; + SocialCalc.TableEditor.prototype.ScrollTableDownOneRow = function () { + return SocialCalc.ScrollTableDownOneRow(this); + }; + SocialCalc.TableEditor.prototype.ScrollTableLeftOneCol = function () { + return SocialCalc.ScrollTableLeftOneCol(this); + }; + SocialCalc.TableEditor.prototype.ScrollTableRightOneCol = function () { + return SocialCalc.ScrollTableRightOneCol(this); + }; + + //contact prototype + SocialCalc.TableEditor.prototype.EditorChangecontact = function ( + text, + name, + phone, + email, + street, + city, + company, + val + ) { + return SocialCalc.EditorChangecontact( + this, + text, + name, + phone, + email, + street, + city, + company, + val + ); + }; + SocialCalc.TableEditor.prototype.EditorChangecolorFromWidget = function ( + text + ) { + return SocialCalc.EditorChangecolorFromWidget(this, text); + }; + SocialCalc.TableEditor.prototype.EditorChangeSheetcolor = function (text) { + return SocialCalc.EditorChangeSheetcolor(this, text); + }; + + SocialCalc.TableEditor.prototype.EditorChangefontFromWidget = function ( + text + ) { + return SocialCalc.EditorChangefontFromWidget(this, text); + }; + SocialCalc.TableEditor.prototype.EditorChangeSheetfont = function (text) { + return SocialCalc.EditorChangeSheetfont(this, text); + }; + SocialCalc.TableEditor.prototype.EditorCut = function (text, no_of_cells) { + return SocialCalc.EditorCut(this, text, no_of_cells); + }; + SocialCalc.TableEditor.prototype.EditorClearSheet = function ( + text, + cell_to_clear + ) { + return SocialCalc.EditorClearSheet(this, text, cell_to_clear); + }; + + // Functions: + + SocialCalc.CreateTableEditor = function (editor, width, height) { + var scc = SocialCalc.Constants; + var AssignID = SocialCalc.AssignID; + + editor.toplevel = document.createElement("div"); + editor.width = width; + editor.height = height; + + editor.griddiv = document.createElement("div"); + editor.tablewidth = Math.max(0, width - scc.defaultTableControlThickness); + editor.tableheight = Math.max(0, height - scc.defaultTableControlThickness); + editor.griddiv.style.width = editor.tablewidth + "px"; + editor.griddiv.style.height = editor.tableheight + "px"; + editor.griddiv.style.overflow = "hidden"; + editor.griddiv.style.cursor = "default"; + if (scc.cteGriddivClass) editor.griddiv.className = scc.cteGriddivClass; + AssignID(editor, editor.griddiv, "griddiv"); + + editor.FitToEditTable(); + + editor.EditorRenderSheet(); + + editor.griddiv.appendChild(editor.fullgrid); + + editor.verticaltablecontrol = new SocialCalc.TableControl( + editor, + true, + editor.tableheight + ); + editor.verticaltablecontrol.CreateTableControl(); + AssignID(editor, editor.verticaltablecontrol.main, "tablecontrolv"); + + editor.horizontaltablecontrol = new SocialCalc.TableControl( + editor, + false, + editor.tablewidth + ); + editor.horizontaltablecontrol.CreateTableControl(); + AssignID(editor, editor.horizontaltablecontrol.main, "tablecontrolh"); + + var table, tbody, tr, td, img, anchor, ta, ha; + + table = document.createElement("table"); + editor.layouttable = table; + table.cellSpacing = 0; + table.cellPadding = 0; + AssignID(editor, table, "layouttable"); + + tbody = document.createElement("tbody"); + table.appendChild(tbody); + + tr = document.createElement("tr"); + tbody.appendChild(tr); + td = document.createElement("td"); + td.appendChild(editor.griddiv); + tr.appendChild(td); + td = document.createElement("td"); + //td.appendChild(editor.verticaltablecontrol.main); + tr.appendChild(td); + + tr = document.createElement("tr"); + tbody.appendChild(tr); + td = document.createElement("td"); + //td.appendChild(editor.horizontaltablecontrol.main); + tr.appendChild(td); + + td = document.createElement("td"); // logo display: Required by CPAL License for this code! + //td.style.background="url("+editor.imageprefix+"logo.gif) no-repeat center center"; + td.innerHTML = + "
"; + tr.appendChild(td); + editor.logo = td; + AssignID(editor, editor.logo, "logo"); + SocialCalc.TooltipRegister(td.firstChild.firstChild, "SocialCalc", null); + + editor.toplevel.appendChild(editor.layouttable); + + if (!editor.noEdit) { + editor.inputEcho = new SocialCalc.InputEcho(editor); + AssignID(editor, editor.inputEcho.main, "inputecho"); + } + + editor.cellhandles = new SocialCalc.CellHandles(editor); + + ta = document.createElement("textarea"); // used for ctrl-c/ctrl-v where an invisible text area is needed + SocialCalc.setStyles( + ta, + "display:none;position:absolute;height:1px;width:1px;opacity:0;filter:alpha(opacity=0);" + ); + ta.value = ""; + editor.pasteTextarea = ta; + AssignID(editor, editor.pasteTextarea, "pastetextarea"); + + if ( + navigator.userAgent.match(/Safari\//) && + !navigator.userAgent.match(/Chrome\//) + ) { + // special code for Safari 5 change + window.removeEventListener( + "beforepaste", + SocialCalc.SafariPasteFunction, + false + ); + window.addEventListener( + "beforepaste", + SocialCalc.SafariPasteFunction, + false + ); + window.removeEventListener( + "beforecopy", + SocialCalc.SafariPasteFunction, + false + ); + window.addEventListener( + "beforecopy", + SocialCalc.SafariPasteFunction, + false + ); + window.removeEventListener( + "beforecut", + SocialCalc.SafariPasteFunction, + false + ); + window.addEventListener( + "beforecut", + SocialCalc.SafariPasteFunction, + false + ); + } + + editor.toplevel.appendChild(editor.pasteTextarea); + + var div = document.createElement("div"); + div.innerHTML = "
"; + if (div.firstChild.nodeType == 1) { + /* We are running in IE -- Using HTML-based area for Ctrl-V */ + ha = document.createElement("div"); // used for ctrl-v where an invisible html area is needed + editor.pasteHTMLarea = ha; + editor.toplevel.appendChild(editor.pasteHTMLarea); + ha.contentEditable = true; + AssignID(editor, editor.pasteHTMLarea, "pastehtmlarea"); + SocialCalc.setStyles( + ha, + "display:block;visibility:hidden;position:absolute;height:1px;width:1px;opacity:0;filter:alpha(opacity=0);overflow:hidden" + ); + } + + SocialCalc.MouseWheelRegister(editor.toplevel, { + WheelMove: SocialCalc.EditorProcessMouseWheel, + editor: editor, + }); + + if (SocialCalc.HasTouch) { + SocialCalc.TouchRegister(editor.toplevel, { + Swipe: SocialCalc.EditorProcessSwipe, + DoubleTap: SocialCalc.EditorProcessDoubleTap, + SingleTap: SocialCalc.EditorProcessSingleTap, + editor: editor, + }); + } + + if (editor.inputBox) { + // this seems to fix an obscure bug with Firefox 2 Mac where Ctrl-V doesn't get fired right + if (editor.inputBox.element) { + editor.inputBox.element.focus(); + editor.inputBox.element.blur(); + } + } + SocialCalc.KeyboardSetFocus(editor); + + // do status reporting things + + SocialCalc.EditorSheetStatusCallback(null, "startup", null, editor); + + // done + + return editor.toplevel; + }; + + // Special code needed for change that occurred with Safari 5 that made paste not work for some reason + + SocialCalc.SafariPasteFunction = function (e) { + e.preventDefault(); + }; + + // + // SocialCalc.ResizeTableEditor(editor, width, height) + // + // Move things around as appropriate and resize + // + + SocialCalc.ResizeTableEditor = function (editor, width, height) { + var scc = SocialCalc.Constants; + + editor.width = width; + editor.height = height; + + editor.toplevel.style.width = width + "px"; + editor.toplevel.style.height = height + "px"; + + editor.tablewidth = Math.max(0, width - scc.defaultTableControlThickness); + editor.tableheight = Math.max(0, height - scc.defaultTableControlThickness); + editor.griddiv.style.width = editor.tablewidth + "px"; + editor.griddiv.style.height = editor.tableheight + "px"; + + editor.verticaltablecontrol.main.style.height = editor.tableheight + "px"; + editor.horizontaltablecontrol.main.style.width = editor.tablewidth + "px"; + + editor.FitToEditTable(); + + editor.ScheduleRender(); + + return; + }; + + // + // str = SaveEditorSettings(editor) + // + // Returns a string representation of the pane settings, etc. + // + // The format is: + // + // version:1.0 + // rowpane:panenumber:firstnum:lastnum + // colpane:panenumber:firstnum:lastnum + // ecell:coord -- if set + // range:anchorcoord:top:bottom:left:right -- if set + // + // You can add additional values to be saved by using editor.SettingsCallbacks: + // + // editor.SettingsCallbacks["item-name"] = {save: savefunction, load: loadfunction} + // + // where savefunction(editor, "item-name") returns a string with the new lines to be added to the saved settings + // which include the trailing newlines, and loadfunction(editor, "item-name", line, flags) is given the line to process + // without the trailing newlines. + // + + SocialCalc.SaveEditorSettings = function (editor) { + var i, setting; + var context = editor.context; + var range = editor.range; + var result = ""; + + result += "version:1.0\n"; + + for (i = 0; i < context.rowpanes.length; i++) { + result += + "rowpane:" + + i + + ":" + + context.rowpanes[i].first + + ":" + + context.rowpanes[i].last + + "\n"; + } + for (i = 0; i < context.colpanes.length; i++) { + result += + "colpane:" + + i + + ":" + + context.colpanes[i].first + + ":" + + context.colpanes[i].last + + "\n"; + } + + if (editor.ecell) { + result += "ecell:" + editor.ecell.coord + "\n"; + } + + if (range.hasrange) { + result += + "range:" + + range.anchorcoord + + ":" + + range.top + + ":" + + range.bottom + + ":" + + range.left + + ":" + + range.right + + "\n"; + } + + for (setting in editor.SettingsCallbacks) { + result += editor.SettingsCallbacks[setting].save(editor, setting); + } + + return result; + }; + + // + // LoadEditorSettings(editor, str, flags) + // + // Sets the editor settings based on str. See SocialCalc.SaveEditorSettings for more details. + // Unrecognized lines are ignored. + // + + SocialCalc.LoadEditorSettings = function (editor, str, flags) { + var lines = str.split(/\r\n|\n/); + var parts = []; + var line, i, cr, row, col, coord, setting; + var context = editor.context; + var highlights, range; + + context.rowpanes = [{ first: 1, last: 1 }]; // reset to start + context.colpanes = [{ first: 1, last: 1 }]; + editor.ecell = null; + editor.range = { hasrange: false }; + editor.range2 = { hasrange: false }; + range = editor.range; + context.highlights = {}; + highlights = context.highlights; + + for (i = 0; i < lines.length; i++) { + line = lines[i]; + parts = line.split(":"); + setting = parts[0]; + switch (setting) { + case "version": + break; + + case "rowpane": + context.rowpanes[parts[1] - 0] = { + first: parts[2] - 0, + last: parts[3] - 0, + }; + break; + + case "colpane": + context.colpanes[parts[1] - 0] = { + first: parts[2] - 0, + last: parts[3] - 0, + }; + break; + + case "ecell": + editor.ecell = SocialCalc.coordToCr(parts[1]); + editor.ecell.coord = parts[1]; + highlights[parts[1]] = "cursor"; + break; + + case "range": + range.hasrange = true; + range.anchorcoord = parts[1]; + cr = SocialCalc.coordToCr(range.anchorcoord); + range.anchorrow = cr.row; + range.anchorcol = cr.col; + range.top = parts[2] - 0; + range.bottom = parts[3] - 0; + range.left = parts[4] - 0; + range.right = parts[5] - 0; + for (row = range.top; row <= range.bottom; row++) { + for (col = range.left; col <= range.right; col++) { + coord = SocialCalc.crToCoord(col, row); + if (highlights[coord] != "cursor") { + highlights[coord] = "range"; + } + } + } + break; + + default: + if (editor.SettingsCallbacks[setting]) { + editor.SettingsCallbacks[setting].load( + editor, + setting, + line, + flags + ); + } + break; + } + } + + return; + }; + + // + // EditorRenderSheet(editor) + // + // Renders the sheet and updates editor.fullgrid. + // Sets event handlers. + // + + SocialCalc.EditorRenderSheet = function (editor) { + editor.EditorMouseUnregister(); + + editor.fullgrid = editor.context.RenderSheet(editor.fullgrid); + + if (editor.ecell) editor.SetECellHeaders("selected"); + + SocialCalc.AssignID(editor, editor.fullgrid, "fullgrid"); // give it an id + + editor.EditorMouseRegister(); + }; + + // + // EditorScheduleSheetCommands(editor, cmdstr, saveundo, ignorebusy) + // + + SocialCalc.EditorScheduleSheetCommands = function ( + editor, + cmdstr, + saveundo, + ignorebusy + ) { + if (editor.state != "start" && !ignorebusy) { + // ignore commands if editing a cell + return; + } + + if (editor.busy && !ignorebusy) { + // hold off on commands if doing one + editor.deferredCommands.push({ cmdstr: cmdstr, saveundo: saveundo }); + return; + } + + switch (cmdstr) { + case "recalc": + case "redisplay": + editor.context.sheetobj.ScheduleSheetCommands(cmdstr, false); + break; + + case "undo": + editor.SheetUndo(); + break; + + case "redo": + editor.SheetRedo(); + break; + + default: + editor.context.sheetobj.ScheduleSheetCommands(cmdstr, saveundo); + break; + } + }; + + // + // EditorSheetStatusCallback(recalcdata, status, arg, editor) + // + // Called during recalc, executing commands, etc. + // + + SocialCalc.EditorSheetStatusCallback = function ( + recalcdata, + status, + arg, + editor + ) { + var f, cell, dcmd; + var sheetobj = editor.context.sheetobj; + var cr; + + var signalstatus = function (s) { + for (f in editor.StatusCallback) { + if (editor.StatusCallback[f].func) { + editor.StatusCallback[f].func( + editor, + s, + arg, + editor.StatusCallback[f].params + ); + } + } + }; + + switch (status) { + case "startup": + break; + + case "cmdstart": + editor.busy = true; + sheetobj.celldisplayneeded = ""; + break; + + case "cmdextension": + break; + + case "cmdend": + signalstatus(status); + + if (sheetobj.changedrendervalues) { + editor.context.PrecomputeSheetFontsAndLayouts(); + editor.context.CalculateCellSkipData(); + sheetobj.changedrendervalues = false; + } + + if (sheetobj.celldisplayneeded && !sheetobj.renderneeded) { + cr = SocialCalc.coordToCr(sheetobj.celldisplayneeded); + cell = SocialCalc.GetEditorCellElement(editor, cr.row, cr.col); + editor.ReplaceCell(cell, cr.row, cr.col); + } + + if (editor.deferredCommands.length) { + dcmd = editor.deferredCommands.shift(); + editor.EditorScheduleSheetCommands(dcmd.cmdstr, dcmd.saveundo, true); + return; + } + + if ( + sheetobj.attribs.needsrecalc && + (sheetobj.attribs.recalc != "off" || sheetobj.recalconce) && + editor.recalcFunction + ) { + editor.FitToEditTable(); + sheetobj.renderneeded = false; // recalc will force a render + if (sheetobj.recalconce) delete sheetobj.recalconce; // only do once + editor.recalcFunction(editor); + } else { + if (sheetobj.renderneeded) { + editor.FitToEditTable(); + sheetobj.renderneeded = false; + editor.ScheduleRender(); + } else { + editor.SchedulePositionCalculations(); // just in case command changed positions + // editor.busy = false; + // signalstatus("cmdendnorender"); + } + } + return; + + case "calcstart": + editor.busy = true; + break; + + case "calccheckdone": + case "calcorder": + case "calcstep": + case "calcloading": + case "calcserverfunc": + break; + + case "calcfinished": + signalstatus(status); + editor.ScheduleRender(); + return; + + case "schedrender": + editor.busy = true; // in case got here without cmd or recalc + break; + + case "renderdone": + break; + + case "schedposcalc": + editor.busy = true; // in case got here without cmd or recalc + break; + + case "doneposcalc": + if (editor.deferredCommands.length) { + signalstatus(status); + dcmd = editor.deferredCommands.shift(); + editor.EditorScheduleSheetCommands(dcmd.cmdstr, dcmd.saveundo, true); + } else { + editor.busy = false; + signalstatus(status); + if (editor.state == "start") editor.DisplayCellContents(); // make sure up to date + } + return; + + default: + addmsg("Unknown status: " + status); + break; + } + + signalstatus(status); + + return; + }; + + // Timer-driven steps for use with SocialCalc.EditorSheetStatusCallback + + SocialCalc.EditorStepInfo = { + // status: "", // saved value to pass to callback + editor: null, // for callback + // arg: null, // for callback + // timerobj: null + }; + + /* + SocialCalc.EditorStepSet = function(editor, status, arg) { + var esi = SocialCalc.EditorStepInfo; + addmsg("step: "+status); + if (esi.timerobj) { + alert("Already waiting. Old/new: "+esi.status+"/"+status); + } + esi.editor = editor; + esi.status = status; + esi.timerobj = window.setTimeout(SocialCalc.EditorStepDone, 1); + } + + SocialCalc.EditorStepDone = function() { + var esi = SocialCalc.EditorStepInfo; + esi.timerobj = null; + SocialCalc.EditorSheetStatusCallback(null, esi.status, null, esi.editor); + } + */ + + // + // str = SocialCalc.EditorGetStatuslineString(editor, status, arg, params) + // + // Assumes params is an object where it can use "calculating" and "command" + // to keep track of state. + // Returns string for status line. + // + + SocialCalc.EditorGetStatuslineString = function ( + editor, + status, + arg, + params + ) { + var scc = SocialCalc.Constants; + + var sstr, progress, coord, circ, r, c, cell, sum, ele; + + progress = ""; + + switch (status) { + case "moveecell": + case "rangechange": + case "startup": + break; + case "cmdstart": + params.command = true; + document.body.style.cursor = "progress"; + editor.griddiv.style.cursor = "progress"; + progress = scc.s_statusline_executing; + break; + case "cmdextension": + progress = "Command Extension: " + arg; + break; + case "cmdend": + params.command = false; + break; + case "schedrender": + progress = scc.s_statusline_displaying; + break; + case "renderdone": + progress = " "; + break; + case "schedposcalc": + progress = scc.s_statusline_displaying; + break; + case "cmdendnorender": + case "doneposcalc": + document.body.style.cursor = "default"; + editor.griddiv.style.cursor = "default"; + break; + case "calcorder": + progress = + scc.s_statusline_ordering + + Math.floor((100 * arg.count) / (arg.total || 1)) + + "%"; + break; + case "calcstep": + progress = + scc.s_statusline_calculating + + Math.floor((100 * arg.count) / (arg.total || 1)) + + "%"; + break; + case "calcloading": + progress = scc.s_statusline_calculatingls + ": " + arg.sheetname; + break; + case "calcserverfunc": + progress = + scc.s_statusline_calculating + + Math.floor((100 * arg.count) / (arg.total || 1)) + + "%, " + + scc.s_statusline_doingserverfunc + + arg.funcname + + scc.s_statusline_incell + + arg.coord; + break; + case "calcstart": + params.calculating = true; + document.body.style.cursor = "progress"; + editor.griddiv.style.cursor = "progress"; // griddiv has an explicit cursor style + progress = scc.s_statusline_calcstart; + break; + case "calccheckdone": + break; + case "calcfinished": + params.calculating = false; + break; + default: + progress = status; + break; + } + + if (!progress && params.calculating) { + progress = scc.s_statusline_calculating; + } + + // if there is a range, calculate sum (not during busy times) + if ( + !params.calculating && + !params.command && + !progress && + editor.range.hasrange && + (editor.range.left != editor.range.right || + editor.range.top != editor.range.bottom) + ) { + sum = 0; + for (r = editor.range.top; r <= editor.range.bottom; r++) { + for (c = editor.range.left; c <= editor.range.right; c++) { + cell = editor.context.sheetobj.cells[SocialCalc.crToCoord(c, r)]; + if (!cell) continue; + if (cell.valuetype && cell.valuetype.charAt(0) == "n") { + sum += cell.datavalue - 0; + } + } + } + + sum = SocialCalc.FormatNumber.formatNumberWithFormat( + sum, + "[,]General", + "" + ); + + coord = + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom); + progress = + coord + + " (" + + (editor.range.right - editor.range.left + 1) + + "x" + + (editor.range.bottom - editor.range.top + 1) + + ") " + + scc.s_statusline_sum + + "=" + + sum + + " " + + progress; + } + sstr = editor.ecell.coord + "   " + progress; + + if ( + !params.calculating && + editor.context.sheetobj.attribs.needsrecalc == "yes" + ) { + sstr += "   " + scc.s_statusline_recalcneeded; + } + + circ = editor.context.sheetobj.attribs.circularreferencecell; + if (circ) { + circ = circ.replace(/\|/, " referenced by "); + sstr += "   " + scc.s_statusline_circref + circ + ""; + } + + return sstr; + }; + + // + // Mouse stuff + // + + SocialCalc.EditorMouseInfo = { + // The registeredElements array is used to identify editor grid in which the mouse is doing things. + + // One item for each active editor, each an object with: + // .element, .editor + + registeredElements: [], + + editor: null, // editor being processed (between mousedown and mouseup) + element: null, // element being processed + + ignore: false, // if true, mousedowns are ignored + + mousedowncoord: "", // coord where mouse went down for drag range + mouselastcoord: "", // coord where mouse last was during drag + mouseresizecol: "", // col being resized + mouseresizeclientx: null, // where resize started + mouseresizedisplay: null, // element tracking new size + }; + + // + // EditorMouseRegister(editor) + // + + SocialCalc.EditorMouseRegister = function (editor) { + var mouseinfo = SocialCalc.EditorMouseInfo; + var element = editor.fullgrid; + var i; + + for (i = 0; i < mouseinfo.registeredElements.length; i++) { + if (mouseinfo.registeredElements[i].editor == editor) { + if (mouseinfo.registeredElements[i].element == element) { + return; // already set - don't do it again + } + break; + } + } + + if (i < mouseinfo.registeredElements.length) { + mouseinfo.registeredElements[i].element = element; + } else { + mouseinfo.registeredElements.push({ element: element, editor: editor }); + } + + if (element.addEventListener) { + // DOM Level 2 -- Firefox, et al + element.addEventListener( + "mousedown", + SocialCalc.ProcessEditorMouseDown, + false + ); + element.addEventListener( + "dblclick", + SocialCalc.ProcessEditorDblClick, + false + ); + } else if (element.attachEvent) { + // IE 5+ + element.attachEvent("onmousedown", SocialCalc.ProcessEditorMouseDown); + element.attachEvent("ondblclick", SocialCalc.ProcessEditorDblClick); + } else { + // don't handle this + throw "Browser not supported"; + } + + mouseinfo.ignore = false; // just in case + + return; + }; + + // + // EditorMouseUnregister(editor) + // + + SocialCalc.EditorMouseUnregister = function (editor) { + var mouseinfo = SocialCalc.EditorMouseInfo; + var element = editor.fullgrid; + var i, oldelement; + + for (i = 0; i < mouseinfo.registeredElements.length; i++) { + if (mouseinfo.registeredElements[i].editor == editor) { + break; + } + } + + if (i < mouseinfo.registeredElements.length) { + oldelement = mouseinfo.registeredElements[i].element; // remove old handlers + if (oldelement.removeEventListener) { + // DOM Level 2 + oldelement.removeEventListener( + "mousedown", + SocialCalc.ProcessEditorMouseDown, + false + ); + oldelement.removeEventListener( + "dblclick", + SocialCalc.ProcessEditorDblClick, + false + ); + } else if (oldelement.detachEvent) { + // IE + oldelement.detachEvent( + "onmousedown", + SocialCalc.ProcessEditorMouseDown + ); + oldelement.detachEvent("ondblclick", SocialCalc.ProcessEditorDblClick); + } + mouseinfo.registeredElements.splice(i, 1); + } + + return; + }; + + SocialCalc.ProcessEditorMouseDown = function (e) { + var editor, result, coord, textarea, wval, range; + + var event = e || window.event; + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + var mouseinfo = SocialCalc.EditorMouseInfo; + var ele = event.target || event.srcElement; // source object is often within what we want + var mobj; + + if (mouseinfo.ignore) return; // ignore this + + for (mobj = null; !mobj && ele; ele = ele.parentNode) { + // go up tree looking for one of our elements + mobj = SocialCalc.LookupElement(ele, mouseinfo.registeredElements); + } + if (!mobj) { + mouseinfo.editor = null; + return; // not one of our elements + } + + editor = mobj.editor; + mouseinfo.element = ele; + range = editor.range; + result = SocialCalc.GridMousePosition(editor, clientX, clientY); + + if (!result || result.rowheader) return; // not on a cell or col header + mouseinfo.editor = editor; // remember for later + + if (result.colheader && result.coltoresize) { + // col header - do drag resize + SocialCalc.ProcessEditorColsizeMouseDown(e, ele, result); + return; + } + + if (!result.coord) return; // not us + + if (!range.hasrange) { + if (e.shiftKey) editor.RangeAnchor(); + } + + SocialCalc.Callbacks.ToggleCell(result.coord); + coord = editor.MoveECell(result.coord); + + if (range.hasrange) { + if (e.shiftKey) editor.RangeExtend(); + else editor.RangeRemove(); + } + + mouseinfo.mousedowncoord = coord; // remember if starting drag range select + mouseinfo.mouselastcoord = coord; + + editor.EditorMouseRange(coord); + + SocialCalc.KeyboardSetFocus(editor); + if (editor.state != "start" && editor.inputBox) + editor.inputBox.element.focus(); + + // Event code from JavaScript, Flanagan, 5th Edition, pg. 422 + if (document.addEventListener) { + // DOM Level 2 -- Firefox, et al + document.addEventListener( + "mousemove", + SocialCalc.ProcessEditorMouseMove, + true + ); // capture everywhere + document.addEventListener( + "mouseup", + SocialCalc.ProcessEditorMouseUp, + true + ); // capture everywhere + } else if (ele.attachEvent) { + // IE 5+ + ele.setCapture(); + ele.attachEvent("onmousemove", SocialCalc.ProcessEditorMouseMove); + ele.attachEvent("onmouseup", SocialCalc.ProcessEditorMouseUp); + ele.attachEvent("onlosecapture", SocialCalc.ProcessEditorMouseUp); + } + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + return; + }; + + SocialCalc.EditorMouseRange = function (editor, coord) { + var inputtext, wval; + var range = editor.range; + + switch ( + editor.state // editing a cell - shouldn't get here if no inputBox + ) { + case "input": + inputtext = editor.inputBox.GetText(); + wval = editor.workingvalues; + if ( + ("(+-*/,:!&<>=^".indexOf(inputtext.slice(-1)) >= 0 && + inputtext.slice(0, 1) == "=") || + inputtext == "=" + ) { + wval.partialexpr = inputtext; + } + if (wval.partialexpr) { + // if in pointing operation + if (coord) { + if (range.hasrange) { + var sheetpref = + wval.currentsheet == wval.startsheet + ? "" + : wval.currentsheet + "!"; + editor.inputBox.SetText( + wval.partialexpr + + sheetpref + + SocialCalc.crToCoord(range.left, range.top) + + ":" + + sheetpref + + SocialCalc.crToCoord(range.right, range.bottom) + ); + } else { + var sheetpref = + wval.currentsheet == wval.startsheet + ? "" + : wval.currentsheet + "!"; + editor.inputBox.SetText(wval.partialexpr + sheetpref + coord); + } + } + } else { + // not in point -- done editing + editor.inputBox.Blur(); + editor.inputBox.ShowInputBox(false); + editor.state = "start"; + editor.cellhandles.ShowCellHandles(true); + editor.EditorSaveEdit(); + editor.inputBox.DisplayCellContents(null); + } + break; + + case "inputboxdirect": + editor.inputBox.Blur(); + editor.inputBox.ShowInputBox(false); + editor.state = "start"; + editor.cellhandles.ShowCellHandles(true); + editor.EditorSaveEdit(); + editor.inputBox.DisplayCellContents(null); + break; + } + }; + + SocialCalc.ProcessEditorMouseMove = function (e) { + var editor, element, result, coord, now, textarea, sheetobj, cellobj, wval; + + var event = e || window.event; + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + var mouseinfo = SocialCalc.EditorMouseInfo; + editor = mouseinfo.editor; + if (!editor) return; // not us, ignore + if (mouseinfo.ignore) return; // ignore this + element = mouseinfo.element; + + result = SocialCalc.GridMousePosition(editor, clientX, clientY); // get cell with move + + if (!result) return; + + if (result && !result.coord) { + SocialCalc.SetDragAutoRepeat(editor, result); + return; + } + + SocialCalc.SetDragAutoRepeat(editor, null); // stop repeating if it was + + if (!result.coord) return; + + if (result.coord != mouseinfo.mouselastcoord) { + if (!e.shiftKey && !editor.range.hasrange) { + editor.RangeAnchor(mouseinfo.mousedowncoord); + } + editor.MoveECell(result.coord); + editor.RangeExtend(); + } + mouseinfo.mouselastcoord = result.coord; + + editor.EditorMouseRange(result.coord); + + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + return; + }; + + SocialCalc.ProcessEditorMouseUp = function (e) { + var editor, element, result, coord, now, textarea, sheetobj, cellobj, wval; + + var event = e || window.event; + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + var mouseinfo = SocialCalc.EditorMouseInfo; + editor = mouseinfo.editor; + if (!editor) return; // not us, ignore + if (mouseinfo.ignore) return; // ignore this + element = mouseinfo.element; + + result = SocialCalc.GridMousePosition(editor, clientX, clientY); // get cell with up + + SocialCalc.SetDragAutoRepeat(editor, null); // stop repeating if it was + + if (!result) return; + + if (!result.coord) result.coord = editor.ecell.coord; + + if (editor.range.hasrange) { + editor.MoveECell(result.coord); + editor.RangeExtend(); + } else if (result.coord && result.coord != mouseinfo.mousedowncoord) { + editor.RangeAnchor(mouseinfo.mousedowncoord); + editor.MoveECell(result.coord); + editor.RangeExtend(); + } + + editor.EditorMouseRange(result.coord); + + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + if (document.removeEventListener) { + // DOM Level 2 + document.removeEventListener( + "mousemove", + SocialCalc.ProcessEditorMouseMove, + true + ); + document.removeEventListener( + "mouseup", + SocialCalc.ProcessEditorMouseUp, + true + ); + } else if (element.detachEvent) { + // IE + element.detachEvent("onlosecapture", SocialCalc.ProcessEditorMouseUp); + element.detachEvent("onmouseup", SocialCalc.ProcessEditorMouseUp); + element.detachEvent("onmousemove", SocialCalc.ProcessEditorMouseMove); + element.releaseCapture(); + } + + mouseinfo.editor = null; + + return false; + }; + + SocialCalc.ProcessEditorColsizeMouseDown = function (e, ele, result) { + var event = e || window.event; + var mouseinfo = SocialCalc.EditorMouseInfo; + var editor = mouseinfo.editor; + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + + mouseinfo.mouseresizecolnum = result.coltoresize; // remember col being resized + mouseinfo.mouseresizecol = SocialCalc.rcColname(result.coltoresize); + mouseinfo.mousedownclientx = clientX; + + var sizedisplay = document.createElement("div"); + mouseinfo.mouseresizedisplay = sizedisplay; + sizedisplay.style.width = "auto"; + sizedisplay.style.position = "absolute"; + sizedisplay.style.zIndex = 100; + sizedisplay.style.top = editor.headposition.top + 0 + "px"; + sizedisplay.style.left = editor.colpositions[result.coltoresize] + "px"; + sizedisplay.innerHTML = + '' + + '
 
' + + editor.context.colwidth[mouseinfo.mouseresizecolnum] + + "
"; + SocialCalc.setStyles( + sizedisplay.firstChild.lastChild.firstChild.childNodes[0], + "filter:alpha(opacity=85);opacity:.85;" + ); // so no warning msg with Firefox about filter + + editor.toplevel.appendChild(sizedisplay); + + // Event code from JavaScript, Flanagan, 5th Edition, pg. 422 + if (document.addEventListener) { + // DOM Level 2 -- Firefox, et al + document.addEventListener( + "mousemove", + SocialCalc.ProcessEditorColsizeMouseMove, + true + ); // capture everywhere + document.addEventListener( + "mouseup", + SocialCalc.ProcessEditorColsizeMouseUp, + true + ); // capture everywhere + } else if (editor.toplevel.attachEvent) { + // IE 5+ + editor.toplevel.setCapture(); + editor.toplevel.attachEvent( + "onmousemove", + SocialCalc.ProcessEditorColsizeMouseMove + ); + editor.toplevel.attachEvent( + "onmouseup", + SocialCalc.ProcessEditorColsizeMouseUp + ); + editor.toplevel.attachEvent( + "onlosecapture", + SocialCalc.ProcessEditorColsizeMouseUp + ); + } + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + return; + }; + + SocialCalc.ProcessEditorColsizeMouseMove = function (e) { + var event = e || window.event; + var mouseinfo = SocialCalc.EditorMouseInfo; + var editor = mouseinfo.editor; + if (!editor) return; // not us, ignore + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + + var newsize = + editor.context.colwidth[mouseinfo.mouseresizecolnum] - + 0 + + (clientX - mouseinfo.mousedownclientx); + if (newsize < SocialCalc.Constants.defaultMinimumColWidth) + newsize = SocialCalc.Constants.defaultMinimumColWidth; + + var sizedisplay = mouseinfo.mouseresizedisplay; + // sizedisplay.firstChild.lastChild.firstChild.childNodes[1].firstChild.innerHTML = newsize+""; + // sizedisplay.firstChild.lastChild.firstChild.childNodes[0].firstChild.style.width = (newsize-2)+"px"; + sizedisplay.innerHTML = + '' + + '
 
' + + newsize + + "
"; + SocialCalc.setStyles( + sizedisplay.firstChild.lastChild.firstChild.childNodes[0], + "filter:alpha(opacity=85);opacity:.85;" + ); // so no warning msg with Firefox about filter + + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + return; + }; + + SocialCalc.ProcessEditorColsizeMouseUp = function (e) { + var event = e || window.event; + var mouseinfo = SocialCalc.EditorMouseInfo; + var editor = mouseinfo.editor; + if (!editor) return; // not us, ignore + element = mouseinfo.element; + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + if (document.removeEventListener) { + // DOM Level 2 + document.removeEventListener( + "mousemove", + SocialCalc.ProcessEditorColsizeMouseMove, + true + ); + document.removeEventListener( + "mouseup", + SocialCalc.ProcessEditorColsizeMouseUp, + true + ); + } else if (editor.toplevel.detachEvent) { + // IE + editor.toplevel.detachEvent( + "onlosecapture", + SocialCalc.ProcessEditorColsizeMouseUp + ); + editor.toplevel.detachEvent( + "onmouseup", + SocialCalc.ProcessEditorColsizeMouseUp + ); + editor.toplevel.detachEvent( + "onmousemove", + SocialCalc.ProcessEditorColsizeMouseMove + ); + editor.toplevel.releaseCapture(); + } + + var newsize = + editor.context.colwidth[mouseinfo.mouseresizecolnum] - + 0 + + (clientX - mouseinfo.mousedownclientx); + if (newsize < SocialCalc.Constants.defaultMinimumColWidth) + newsize = SocialCalc.Constants.defaultMinimumColWidth; + + editor.EditorScheduleSheetCommands( + "set " + mouseinfo.mouseresizecol + " width " + newsize, + true, + false + ); + + if (editor.timeout) window.clearTimeout(editor.timeout); + editor.timeout = window.setTimeout(SocialCalc.FinishColsize, 1); // wait - Firefox 2 has a bug otherwise with next mousedown + + return false; + }; + + SocialCalc.FinishColsize = function () { + var mouseinfo = SocialCalc.EditorMouseInfo; + var editor = mouseinfo.editor; + if (!editor) return; + + editor.toplevel.removeChild(mouseinfo.mouseresizedisplay); + mouseinfo.mouseresizedisplay = null; + + // editor.FitToEditTable(); + // editor.EditorRenderSheet(); + // editor.SchedulePositionCalculations(); + + mouseinfo.editor = null; + + return; + }; + + // + // Handle auto-repeat of dragging the cursor into the borders of the sheet + // + + SocialCalc.AutoRepeatInfo = { + timer: null, // timer object for repeating + mouseinfo: null, // result from SocialCalc.GridMousePosition + repeatinterval: 1000, // milliseconds to wait between repeats + editor: null, // editor object to use when it repeats + repeatcallback: null, // used instead of default when repeating (e.g., for cellhandles) + // called as: repeatcallback(newcoord, direction) + }; + + // Control auto-repeat. If mouseinfo==null, cancel. + + SocialCalc.SetDragAutoRepeat = function (editor, mouseinfo, callback) { + var repeatinfo = SocialCalc.AutoRepeatInfo; + var coord, direction; + + repeatinfo.repeatcallback = callback; // null in regular case + + if (!mouseinfo) { + // cancel + if (repeatinfo.timer) { + // If was repeating, stop + window.clearTimeout(repeatinfo.timer); // cancel timer + repeatinfo.timer = null; + } + repeatinfo.mouseinfo = null; + return; // done + } + + repeatinfo.editor = editor; + + if (repeatinfo.mouseinfo) { + // check for change while repeating + if (mouseinfo.rowheader || mouseinfo.rowfooter) { + if (mouseinfo.row != repeatinfo.mouseinfo.row) { + // changed row while dragging sidewards + coord = SocialCalc.crToCoord(editor.ecell.col, mouseinfo.row); // change to it + if (repeatinfo.repeatcallback) { + if (mouseinfo.row < repeatinfo.mouseinfo.row) { + direction = "left"; + } else if (mouseinfo.row > repeatinfo.mouseinfo.row) { + direction = "right"; + } else { + direction = ""; + } + repeatinfo.repeatcallback(coord, direction); + } else { + editor.MoveECell(coord); + editor.MoveECell(coord); + editor.RangeExtend(); + editor.EditorMouseRange(coord); + } + } + } else if (mouseinfo.colheader || mouseinfo.colfooter) { + if (mouseinfo.col != repeatinfo.mouseinfo.col) { + // changed col while dragging vertically + coord = SocialCalc.crToCoord(mouseinfo.col, editor.ecell.row); // change to it + if (repeatinfo.repeatcallback) { + if (mouseinfo.row < repeatinfo.mouseinfo.row) { + direction = "left"; + } else if (mouseinfo.row > repeatinfo.mouseinfo.row) { + direction = "right"; + } else { + direction = ""; + } + repeatinfo.repeatcallback(coord, direction); + } else { + editor.MoveECell(coord); + editor.RangeExtend(); + editor.EditorMouseRange(coord); + } + } + } + } + + repeatinfo.mouseinfo = mouseinfo; + + if (mouseinfo.distance < 5) repeatinfo.repeatinterval = 333; + else if (mouseinfo.distance < 10) repeatinfo.repeatinterval = 250; + else if (mouseinfo.distance < 25) repeatinfo.repeatinterval = 100; + else if (mouseinfo.distance < 35) repeatinfo.repeatinterval = 75; + else { + // too far - stop repeating + if (repeatinfo.timer) { + // if repeating, cancel it + window.clearTimeout(repeatinfo.timer); // cancel timer + repeatinfo.timer = null; + } + return; + } + + if (!repeatinfo.timer) { + // start if not already running + repeatinfo.timer = window.setTimeout( + SocialCalc.DragAutoRepeat, + repeatinfo.repeatinterval + ); + } + + return; + }; + + // + // DragAutoRepeat() + // + + SocialCalc.DragAutoRepeat = function () { + var repeatinfo = SocialCalc.AutoRepeatInfo; + var mouseinfo = repeatinfo.mouseinfo; + + var direction, coord, cr; + + if (mouseinfo.rowheader) direction = "left"; + else if (mouseinfo.rowfooter) direction = "right"; + else if (mouseinfo.colheader) direction = "up"; + else if (mouseinfo.colfooter) direction = "down"; + + if (repeatinfo.repeatcallback) { + cr = SocialCalc.coordToCr(repeatinfo.editor.ecell.coord); + if (direction == "left" && cr.col > 1) cr.col--; + else if (direction == "right") cr.col++; + else if (direction == "up" && cr.row > 1) cr.row--; + else if (direction == "down") cr.row++; + coord = SocialCalc.crToCoord(cr.col, cr.row); + repeatinfo.repeatcallback(coord, direction); + } else { + coord = repeatinfo.editor.MoveECellWithKey("[a" + direction + "]shifted"); + if (coord) repeatinfo.editor.EditorMouseRange(coord); + } + + repeatinfo.timer = window.setTimeout( + SocialCalc.DragAutoRepeat, + repeatinfo.repeatinterval + ); + }; + + // + // Handling Clicking + // + + SocialCalc.ProcessEditorDblClick = function (e) { + var editor, result, coord, textarea, wval, range, sheetobj; + + var event = e || window.event; + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + var mouseinfo = SocialCalc.EditorMouseInfo; + var ele = event.target || event.srcElement; // source object is often within what we want + var mobj; + + if (mouseinfo.ignore) return; // ignore this + + for (mobj = null; !mobj && ele; ele = ele.parentNode) { + // go up tree looking for one of our elements + mobj = SocialCalc.LookupElement(ele, mouseinfo.registeredElements); + } + if (!mobj) { + mouseinfo.editor = null; + return; // not one of our elements + } + + editor = mobj.editor; + + result = SocialCalc.GridMousePosition(editor, clientX, clientY); + if (!result || !result.coord) return; // not within cell area - ignore + + mouseinfo.editor = editor; // remember for later + mouseinfo.element = ele; + range = editor.range; + + sheetobj = editor.context.sheetobj; + + switch (editor.state) { + case "start": + SocialCalc.EditorOpenCellEdit(editor); + break; + + case "input": + break; + + default: + break; + } + + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + return; + }; + + SocialCalc.EditorOpenCellEdit = function (editor) { + var wval; + + if (!editor.ecell) return true; // no ecell + if (!editor.inputBox) return true; // no input box, so no editing (happens on noEdit) + if (SocialCalc.Callbacks && SocialCalc.Callbacks.IsCellEditable) { + if (!SocialCalc.Callbacks.IsCellEditable(editor)) { + return true; + } + } + if (editor.inputBox.element.disabled) return true; // multi-line: ignore + if (editor.inputBox.element.style.display == "none") { + for (f in editor.StatusCallback) { + editor.StatusCallback[f].func( + editor, + "editecell", + null, + editor.StatusCallback[f].params + ); + } + return true; // no inputBox display, so no editing + } + editor.inputBox.ShowInputBox(true); + editor.inputBox.Focus(); + + editor.state = "inputboxdirect"; + + editor.inputBox.SetText(""); + editor.inputBox.DisplayCellContents(); + editor.inputBox.Select("end"); + wval = editor.workingvalues; + wval.partialexpr = ""; + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + wval.startsheet = wval.currentsheet; + wval.startsheetid = wval.currentsheetid; + + return; + }; + + SocialCalc.EditorProcessKey = function (editor, ch, e) { + var result, cell, cellobj, valueinfo, fch, coord, inputtext, f; + + var sheetobj = editor.context.sheetobj; + var wval = editor.workingvalues; + var range = editor.range; + + if (typeof ch != "string") ch = ""; + + switch (editor.state) { + case "start": + if (e.shiftKey && ch.substr(0, 2) == "[a") { + ch = ch + "shifted"; + } + if (ch == "[enter]") ch = "[adown]"; + if (ch == "[tab]") ch = e.shiftKey ? "[aleft]" : "[aright]"; + if ( + ch.substr(0, 2) == "[a" || + ch.substr(0, 3) == "[pg" || + ch == "[home]" + ) { + result = editor.MoveECellWithKey(ch); + return !result; + } + if (ch == "[del]" || ch == "[backspace]") { + if (!editor.noEdit) { + editor.EditorApplySetCommandsToRange("empty", ""); + } + if (SocialCalc.Callbacks && SocialCalc.Callbacks.IsCellEditable) { + if (!SocialCalc.Callbacks.IsCellEditable(editor)) { + return true; + } + } + + break; + } + if (ch == "[esc]") { + if (range.hasrange) { + editor.RangeRemove(); + editor.MoveECell(range.anchorcoord); + for (f in editor.StatusCallback) { + editor.StatusCallback[f].func( + editor, + "specialkey", + ch, + editor.StatusCallback[f].params + ); + } + } + return false; + } + + if (ch == "[f2]") { + if (editor.noEdit) return true; + SocialCalc.EditorOpenCellEdit(editor); + return false; + } + + if ((ch.length > 1 && ch.substr(0, 1) == "[") || ch.length == 0) { + // some control key + if (editor.ctrlkeyFunction && ch.length > 0) { + return editor.ctrlkeyFunction(editor, ch); + } else { + return true; + } + } + if (!editor.ecell) return true; // no ecell + if (!editor.inputBox) return true; // no inputBox so no editing + if (editor.inputBox.element.style.display == "none") { + for (f in editor.StatusCallback) { + editor.StatusCallback[f].func( + editor, + "editecell", + ch, + editor.StatusCallback[f].params + ); + } + return true; // no inputBox display, so no editing + } + if (SocialCalc.Callbacks && SocialCalc.Callbacks.IsCellEditable) { + if (!SocialCalc.Callbacks.IsCellEditable(editor)) { + return true; + } + } + + editor.inputBox.element.disabled = false; // make sure editable + editor.state = "input"; + editor.inputBox.ShowInputBox(true); + editor.inputBox.Focus(); + editor.inputBox.SetText(ch); + editor.inputBox.Select("end"); + wval.partialexpr = ""; + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + wval.startsheet = wval.currentsheet; + wval.startsheetid = wval.currentsheetid; + editor.RangeRemove(); + break; + + case "input": + inputtext = editor.inputBox.GetText(); // should not get here if no inputBox + if (editor.inputBox.skipOne) return false; // ignore a key already handled + if ( + ch == "[esc]" || + ch == "[enter]" || + ch == "[tab]" || + (ch && ch.substr(0, 2) == "[a") + ) { + if ( + ("(+-*/,:!&<>=^".indexOf(inputtext.slice(-1)) >= 0 && + inputtext.slice(0, 1) == "=") || + inputtext == "=" + ) { + wval.partialexpr = inputtext; + } + if (wval.partialexpr) { + // if in pointing operation + if (e.shiftKey && ch.substr(0, 2) == "[a") { + ch = ch + "shifted"; + } + coord = editor.MoveECellWithKey(ch); + if (coord) { + if (range.hasrange) { + editor.inputBox.SetText( + wval.partialexpr + + SocialCalc.crToCoord(range.left, range.top) + + ":" + + SocialCalc.crToCoord(range.right, range.bottom) + ); + } else { + editor.inputBox.SetText(wval.partialexpr + coord); + } + return false; + } + } + editor.inputBox.Blur(); + editor.inputBox.ShowInputBox(false); + editor.state = "start"; + editor.cellhandles.ShowCellHandles(true); + if (ch != "[esc]") { + editor.EditorSaveEdit(); + if (editor.ecell.coord != wval.ecoord) { + editor.MoveECell(wval.ecoord); + } + if (ch == "[enter]") ch = "[adown]"; + if (ch == "[tab]") ch = e.shiftKey ? "[aleft]" : "[aright]"; + if (ch.substr(0, 2) == "[a") { + editor.MoveECellWithKey(ch); + } + } else { + editor.inputBox.DisplayCellContents(); + editor.RangeRemove(); + editor.MoveECell(wval.ecoord); + } + break; + } + if (wval.partialexpr && ch == "[backspace]") { + editor.inputBox.SetText(wval.partialexpr); + wval.partialexpr = ""; + editor.RangeRemove(); + editor.MoveECell(wval.ecoord); + editor.inputBox.ShowInputBox(true); // make sure it's moved back if necessary + return false; + } + if (ch == "[f2]") return false; + if (range.hasrange) { + editor.RangeRemove(); + } + editor.MoveECell(wval.ecoord); + if (wval.partialexpr) { + editor.inputBox.ShowInputBox(true); // make sure it's moved back if necessary + wval.partialexpr = ""; // not pointing + } + return true; + + case "inputboxdirect": + inputtext = editor.inputBox.GetText(); // should not get here if no inputBox + if (ch == "[esc]" || ch == "[enter]" || ch == "[tab]") { + editor.inputBox.Blur(); + editor.inputBox.ShowInputBox(false); + editor.state = "start"; + editor.cellhandles.ShowCellHandles(true); + if (ch == "[esc]") { + editor.inputBox.DisplayCellContents(); + } else { + editor.EditorSaveEdit(); + if (editor.ecell.coord != wval.ecoord) { + editor.MoveECell(wval.ecoord); + } + if (ch == "[enter]") ch = "[adown]"; + if (ch == "[tab]") ch = e.shiftKey ? "[aleft]" : "[aright]"; + if (ch.substr(0, 2) == "[a") { + editor.MoveECellWithKey(ch); + } + } + break; + } + if (ch == "[f2]") return false; + return true; + + case "skip-and-start": + editor.state = "start"; + editor.cellhandles.ShowCellHandles(true); + return false; + + default: + return true; + } + + return false; + }; + + SocialCalc.EditorAddToInput = function (editor, str, prefix) { + var wval = editor.workingvalues; + + if (editor.noEdit) return; + + if (SocialCalc.Callbacks && SocialCalc.Callbacks.IsCellEditable) { + if (!SocialCalc.Callbacks.IsCellEditable(editor)) { + return true; + } + } + + switch (editor.state) { + case "start": + editor.state = "input"; + editor.inputBox.ShowInputBox(true); + editor.inputBox.element.disabled = false; // make sure editable and overwrite old + editor.inputBox.Focus(); + editor.inputBox.SetText((prefix || "") + str); + editor.inputBox.Select("end"); + wval.partialexpr = ""; + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + wval.startsheet = wval.currentsheet; + wval.startsheetid = wval.currentsheetid; + editor.RangeRemove(); + break; + + case "input": + case "inputboxdirect": + editor.inputBox.element.focus(); + if (wval.partialexpr) { + editor.inputBox.SetText(wval.partialexpr); + wval.partialexpr = ""; + editor.RangeRemove(); + editor.MoveECell(wval.ecoord); + } + editor.inputBox.SetText(editor.inputBox.GetText() + str); + break; + + default: + break; + } + }; + + SocialCalc.EditorDisplayCellContents = function (editor) { + if (editor.inputBox) editor.inputBox.DisplayCellContents(); + }; + var arr = []; + SocialCalc.EditorSaveEdit = function (editor, text) { + //console.log("editorSaveEdit"); + var result, cell, valueinfo, fch, type, value, oldvalue, cmdline; + + var sheetobj = editor.context.sheetobj; + var wval = editor.workingvalues; + + if (SocialCalc.Callbacks && SocialCalc.Callbacks.IsCellEditable) { + if (!SocialCalc.Callbacks.IsCellEditable(editor)) { + return true; + } + } + + type = "text t"; + //changes for prompt + + value = typeof text == "string" ? text : editor.inputBox.GetText(); // either explicit or from input box + + oldvalue = SocialCalc.GetCellContents(sheetobj, wval.ecoord) + ""; + //console.log("old:"+oldvalue) + //console.log("new:"+value) + if (value == oldvalue) { + // no change + return; + } + if ("'" + value == oldvalue) { + return; + } + + fch = value.charAt(0); + if (fch == "=" && value.indexOf("\n") == -1) { + type = "formula"; + value = value.substring(1); + } else if (fch == "'") { + type = "text t"; + value = value.substring(1); + } else if (value.length == 0) { + type = "empty"; + } else { + valueinfo = SocialCalc.DetermineValueType(value); + if (valueinfo.type == "n" && value == valueinfo.value + "") { + // see if don't need "constant" + type = "value n"; + } else if (valueinfo.type.charAt(0) == "t") { + type = "text " + valueinfo.type; + } else if (valueinfo.type == "") { + type = "text t"; + } else { + type = "constant " + valueinfo.type + " " + valueinfo.value; + } + } + + if (type.charAt(0) == "t") { + // text + value = SocialCalc.encodeForSave(value); // newlines, :, and \ are escaped + } + + // if startsheet different from currentsheet, switch to start sheet + // for the save to take effect + if (SocialCalc.WorkBook && wval.currentsheet != wval.startsheet) { + var control = SocialCalc.GetCurrentWorkBookControl(); + var cmdstr = "activatesheet " + wval.startsheetid; + control.ExecuteWorkBookControlCommand( + { cmdtype: "wcmd", id: "0", cmdstr: cmdstr }, + false + ); + } + + cmdline = "set " + wval.ecoord + " " + type + " " + value; + editor.EditorScheduleSheetCommands(cmdline, true, false); + + return; + }; + + // + // SocialCalc.EditorApplySetCommandsToRange(editor, cmd) + // + // Takes ecell or range and does a "set" command with cmd. + // + + SocialCalc.EditorApplySetCommandsToRange = function (editor, cmd) { + var cell, row, col, line, errortext; + + var sheetobj = editor.context.sheetobj; + var ecell = editor.ecell; + var range = editor.range; + + if (range.hasrange) { + coord = + SocialCalc.crToCoord(range.left, range.top) + + ":" + + SocialCalc.crToCoord(range.right, range.bottom); + line = "set " + coord + " " + cmd; + errortext = editor.EditorScheduleSheetCommands(line, true, false); + } else { + line = "set " + ecell.coord + " " + cmd; + errortext = editor.EditorScheduleSheetCommands(line, true, false); + } + + editor.DisplayCellContents(); + }; + + SocialCalc.EditorProcessMouseWheel = function ( + event, + delta, + mousewheelinfo, + wobj + ) { + if (wobj.functionobj.editor.busy) return; // ignore if busy + + if (delta > 0) { + wobj.functionobj.editor.ScrollRelative(true, -1); + } + if (delta < 0) { + wobj.functionobj.editor.ScrollRelative(true, +1); + } + }; + + // + // GridMousePosition(editor, clientX, clientY) + // + // Returns an object with row and col numbers and coord (spans handled for coords), + // and rowheader/colheader true if in header (where coord will be undefined). + // If in colheader, will return coltoresize if on appropriate place in col header. + // Also, there is rowfooter (on right) and colfooter (on bottom). + // In row/col header/footer, returns "distance" as pixels over the edge. + // + + SocialCalc.GridMousePosition = function (editor, clientX, clientY) { + var row, col, colpane; + var result = {}; + + for (row = 1; row < editor.rowpositions.length; row++) { + if (!editor.rowheight[row]) continue; // not rendered yet -- may be above or below us + if (editor.rowpositions[row] + editor.rowheight[row] > clientY) { + break; + } + } + for (col = 1; col < editor.colpositions.length; col++) { + if (!editor.colwidth[col]) continue; + if (editor.colpositions[col] + editor.colwidth[col] > clientX) { + break; + } + } + + result.row = row; + result.col = col; + + if (editor.headposition) { + if ( + clientX < editor.headposition.left && + clientX >= editor.gridposition.left + ) { + result.rowheader = true; + result.distance = editor.headposition.left - clientX; + return result; + } else if ( + clientY < editor.headposition.top && + clientY > editor.gridposition.top + ) { + // > because of sizing row + result.colheader = true; + result.distance = editor.headposition.top - clientY; + result.coltoresize = + col - + (editor.colpositions[col] + editor.colwidth[col] / 2 > clientX + ? 1 + : 0) || 1; + for (colpane = 0; colpane < editor.context.colpanes.length; colpane++) { + if ( + result.coltoresize >= editor.context.colpanes[colpane].first && + result.coltoresize <= editor.context.colpanes[colpane].last + ) { + // visible column + return result; + } + } + delete result.coltoresize; + return result; + } else if (clientX >= editor.verticaltablecontrol.controlborder) { + result.rowfooter = true; + result.distance = clientX - editor.verticaltablecontrol.controlborder; + return result; + } else if (clientY >= editor.horizontaltablecontrol.controlborder) { + result.colfooter = true; + result.distance = clientY - editor.horizontaltablecontrol.controlborder; + return result; + } else if (clientX < editor.gridposition.left) { + result.rowheader = true; + result.distance = editor.headposition.left - clientX; + return result; + } else if (clientY <= editor.gridposition.top) { + result.colheader = true; + result.distance = editor.headposition.top - clientY; + return result; + } else { + result.coord = SocialCalc.crToCoord(result.col, result.row); + if (editor.context.cellskip[result.coord]) { + // handle skipped cells + result.coord = editor.context.cellskip[result.coord]; + } + return result; + } + } + + return null; + }; + + // + // GetEditorCellElement(editor, row, col) + // + // Returns an object with element, the table cell element in the DOM that corresponds to row and column, + // as well as rowpane and colpane, the panes with the cell. + // If no such element, then returns null; + // + + SocialCalc.GetEditorCellElement = function (editor, row, col) { + var rowpane, colpane, c, coord; + var rowindex = 0; + var colindex = 0; + + for (rowpane = 0; rowpane < editor.context.rowpanes.length; rowpane++) { + if ( + row >= editor.context.rowpanes[rowpane].first && + row <= editor.context.rowpanes[rowpane].last + ) { + for (colpane = 0; colpane < editor.context.colpanes.length; colpane++) { + if ( + col >= editor.context.colpanes[colpane].first && + col <= editor.context.colpanes[colpane].last + ) { + rowindex += row - editor.context.rowpanes[rowpane].first + 2; + for (c = editor.context.colpanes[colpane].first; c <= col; c++) { + coord = editor.context.cellskip[SocialCalc.crToCoord(c, row)]; + if ( + !coord || + !editor.context.CoordInPane(coord, rowpane, colpane) + ) + // don't count col-spanned cells + colindex++; + } + return { + element: + editor.griddiv.firstChild.lastChild.childNodes[rowindex] + .childNodes[colindex], + rowpane: rowpane, + colpane: colpane, + }; + } + for ( + c = editor.context.colpanes[colpane].first; + c <= editor.context.colpanes[colpane].last; + c++ + ) { + coord = editor.context.cellskip[SocialCalc.crToCoord(c, row)]; + if (!coord || !editor.context.CoordInPane(coord, rowpane, colpane)) + // don't count col-spanned cells + colindex++; + } + colindex += 1; + } + } + rowindex += + editor.context.rowpanes[rowpane].last - + editor.context.rowpanes[rowpane].first + + 1 + + 1; + } + + return null; + }; + + // + // cellcoord = MoveECellWithKey(editor, ch) + // + // Processes an arrow key, etc., moving the edit cell. + // If not a movement key, returns null. + // + + SocialCalc.MoveECellWithKey = function (editor, ch) { + var coord, row, col, cell; + var shifted = false; + + if (!editor.ecell) { + return null; + } + + if (ch.slice(-7) == "shifted") { + ch = ch.slice(0, -7); + shifted = true; + } + + row = editor.ecell.row; + col = editor.ecell.col; + cell = editor.context.sheetobj.cells[editor.ecell.coord]; + + switch (ch) { + case "[adown]": + row += (cell && cell.rowspan) || 1; + break; + case "[aup]": + row--; + break; + case "[pgdn]": + row += editor.pageUpDnAmount - 1 + ((cell && cell.rowspan) || 1); + break; + case "[pgup]": + row -= editor.pageUpDnAmount; + break; + case "[aright]": + col += (cell && cell.colspan) || 1; + break; + case "[aleft]": + col--; + break; + case "[home]": + row = 1; + col = 1; + break; + default: + return null; + } + + if (!editor.range.hasrange) { + if (shifted) editor.RangeAnchor(); + } + + coord = editor.MoveECell(SocialCalc.crToCoord(col, row)); + + if (editor.range.hasrange) { + if (shifted) editor.RangeExtend(); + else editor.RangeRemove(); + } + + return coord; + }; + + // + // cellcoord = MoveECell(editor, newecell) + // + // Takes a coordinate and returns the new edit cell coordinate (which may be + // different if newecell is covered by a span). + // + + SocialCalc.MoveECell = function (editor, newcell) { + var cell, f; + + var highlights = editor.context.highlights; + + if (editor.ecell) { + //changes for prompt + if (editor.ecell.coord == newcell) return newcell; + + if (SocialCalc.Callbacks.broadcast) { + SocialCalc.Callbacks.broadcast("ecell", { + original: editor.ecell.coord, + ecell: newcell, + }); + } + + cell = SocialCalc.GetEditorCellElement( + editor, + editor.ecell.row, + editor.ecell.col + ); + delete highlights[editor.ecell.coord]; + if ( + editor.range2.hasrange && + editor.ecell.row >= editor.range2.top && + editor.ecell.row <= editor.range2.bottom && + editor.ecell.col >= editor.range2.left && + editor.ecell.col <= editor.range2.right + ) { + highlights[editor.ecell.coord] = "range2"; + } + editor.UpdateCellCSS(cell, editor.ecell.row, editor.ecell.col); + editor.SetECellHeaders(""); // set to regular col/rowname styles + editor.cellhandles.ShowCellHandles(false); + } else if (SocialCalc.Callbacks.broadcast) { + SocialCalc.Callbacks.broadcast("ecell", { ecell: newcell }); + } + newcell = editor.context.cellskip[newcell] || newcell; + editor.ecell = SocialCalc.coordToCr(newcell); + editor.ecell.coord = newcell; + cell = SocialCalc.GetEditorCellElement( + editor, + editor.ecell.row, + editor.ecell.col + ); + highlights[newcell] = "cursor"; + + for (f in editor.MoveECellCallback) { + // let others know + editor.MoveECellCallback[f](editor); + } + + editor.UpdateCellCSS(cell, editor.ecell.row, editor.ecell.col); + editor.SetECellHeaders("selected"); + + for (f in editor.StatusCallback) { + // let status line, etc., know + editor.StatusCallback[f].func( + editor, + "moveecell", + newcell, + editor.StatusCallback[f].params + ); + } + + if (editor.busy) { + editor.ensureecell = true; // wait for when not busy + } else { + editor.ensureecell = false; + editor.EnsureECellVisible(); + } + + return newcell; + }; + + SocialCalc.EnsureECellVisible = function (editor) { + var vamount = 0; + var hamount = 0; + + if (editor.ecell.row > editor.lastnonscrollingrow) { + if (editor.ecell.row < editor.firstscrollingrow) { + vamount = editor.ecell.row - editor.firstscrollingrow; + } else if (editor.ecell.row > editor.lastvisiblerow) { + vamount = editor.ecell.row - editor.lastvisiblerow; + } + } + if (editor.ecell.col > editor.lastnonscrollingcol) { + if (editor.ecell.col < editor.firstscrollingcol) { + hamount = editor.ecell.col - editor.firstscrollingcol; + } else if (editor.ecell.col > editor.lastvisiblecol) { + hamount = editor.ecell.col - editor.lastvisiblecol; + } + } + + if (vamount != 0 || hamount != 0) { + editor.ScrollRelativeBoth(vamount, hamount); + } else { + editor.cellhandles.ShowCellHandles(true); + } + }; + + SocialCalc.ReplaceCell = function (editor, cell, row, col) { + var newelement, a; + if (!cell) return; + newelement = editor.context.RenderCell( + row, + col, + cell.rowpane, + cell.colpane, + true, + null + ); + if (newelement) { + // Don't use a real element and replaceChild, which seems to have focus issues with IE, Firefox, and speed issues + cell.element.innerHTML = newelement.innerHTML; + cell.element.style.cssText = ""; + cell.element.className = newelement.className; + for (a in newelement.style) { + if (newelement.style[a] != "cssText") + cell.element.style[a] = newelement.style[a]; + } + } + }; + + SocialCalc.UpdateCellCSS = function (editor, cell, row, col) { + var newelement, a; + if (!cell) return; + newelement = editor.context.RenderCell( + row, + col, + cell.rowpane, + cell.colpane, + true, + null + ); + if (newelement) { + cell.element.style.cssText = ""; + cell.element.className = newelement.className; + for (a in newelement.style) { + if (newelement.style[a] != "cssText") + cell.element.style[a] = newelement.style[a]; + } + } + }; + + SocialCalc.SetECellHeaders = function (editor, selected) { + var ecell = editor.ecell; + var context = editor.context; + + var rowpane, colpane, first, last; + var rowindex = 0; + var colindex = 0; + var headercell; + + if (!ecell) return; + + for (rowpane = 0; rowpane < context.rowpanes.length; rowpane++) { + first = context.rowpanes[rowpane].first; + last = context.rowpanes[rowpane].last; + if (ecell.row >= first && ecell.row <= last) { + headercell = + editor.fullgrid.childNodes[1].childNodes[ + 2 + rowindex + ecell.row - first + ].childNodes[0]; + if (headercell) { + if (context.classnames) + headercell.className = context.classnames[selected + "rowname"]; + if (context.explicitStyles) + headercell.style.cssText = + context.explicitStyles[selected + "rowname"]; + headercell.style.verticalAlign = "top"; // to get around Safari making top of centered row number be + // considered top of row (and can't get position in Safari) + } + } + rowindex += last - first + 1 + 1; + } + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + first = context.colpanes[colpane].first; + last = context.colpanes[colpane].last; + if (ecell.col >= first && ecell.col <= last) { + headercell = + editor.fullgrid.childNodes[1].childNodes[1].childNodes[ + 1 + colindex + ecell.col - first + ]; + if (headercell) { + if (context.classnames) + headercell.className = context.classnames[selected + "colname"]; + if (context.explicitStyles) + headercell.style.cssText = + context.explicitStyles[selected + "colname"]; + } + } + colindex += last - first + 1 + 1; + } + }; + + // + // RangeAnchor(editor, ecoord) + // + // Sets the anchor of a range to ecoord (or ecell if missing). + // + + SocialCalc.RangeAnchor = function (editor, ecoord) { + if (editor.range.hasrange) { + editor.RangeRemove(); + } + + editor.RangeExtend(ecoord); + }; + + // + // RangeExtend(editor, ecoord) + // + // Sets the other corner of the range to ecoord or, if missing, ecell. + // + + SocialCalc.RangeExtend = function (editor, ecoord) { + var a, cell, cr, coord, row, col, f; + + var highlights = editor.context.highlights; + var range = editor.range; + var range2 = editor.range2; + + var ecell; + if (ecoord) { + ecell = SocialCalc.coordToCr(ecoord); + ecell.coord = ecoord; + } else ecell = editor.ecell; + + if (!ecell) return; // just in case + if (SocialCalc.Constants.SCNoRanging) return; + + if (!range.hasrange) { + // called without RangeAnchor... + range.anchorcoord = ecell.coord; + range.anchorrow = ecell.row; + range.top = ecell.row; + range.bottom = ecell.row; + range.anchorcol = ecell.col; + range.left = ecell.col; + range.right = ecell.col; + range.hasrange = true; + } + + if (range.anchorrow < ecell.row) { + range.top = range.anchorrow; + range.bottom = ecell.row; + } else { + range.top = ecell.row; + range.bottom = range.anchorrow; + } + if (range.anchorcol < ecell.col) { + range.left = range.anchorcol; + range.right = ecell.col; + } else { + range.left = ecell.col; + range.right = range.anchorcol; + } + + for (coord in highlights) { + switch (highlights[coord]) { + case "range": + highlights[coord] = "unrange"; + break; + case "range2": + highlights[coord] = "unrange2"; + break; + } + } + + for (row = range.top; row <= range.bottom; row++) { + for (col = range.left; col <= range.right; col++) { + coord = SocialCalc.crToCoord(col, row); + switch (highlights[coord]) { + case "unrange": + highlights[coord] = "range"; + break; + case "cursor": + break; + case "unrange2": + default: + highlights[coord] = "newrange"; + break; + } + } + } + + for (row = range2.top; range2.hasrange && row <= range2.bottom; row++) { + for (col = range2.left; col <= range2.right; col++) { + coord = SocialCalc.crToCoord(col, row); + switch (highlights[coord]) { + case "unrange2": + highlights[coord] = "range2"; + break; + case "range": + case "newrange": + case "cursor": + break; + default: + highlights[coord] = "newrange2"; + break; + } + } + } + + for (coord in highlights) { + switch (highlights[coord]) { + case "unrange": + delete highlights[coord]; + break; + case "newrange": + highlights[coord] = "range"; + break; + case "newrange2": + highlights[coord] = "range2"; + break; + case "range": + case "range2": + case "cursor": + continue; + } + + cr = SocialCalc.coordToCr(coord); + cell = SocialCalc.GetEditorCellElement(editor, cr.row, cr.col); + editor.UpdateCellCSS(cell, cr.row, cr.col); + } + + for (f in editor.RangeChangeCallback) { + // let others know + editor.RangeChangeCallback[f](editor); + } + + // create range/coord string and do status callback + + coord = SocialCalc.crToCoord(editor.range.left, editor.range.top); + if ( + editor.range.left != editor.range.right || + editor.range.top != editor.range.bottom + ) { + // more than one cell + coord += + ":" + SocialCalc.crToCoord(editor.range.right, editor.range.bottom); + } + for (f in editor.StatusCallback) { + editor.StatusCallback[f].func( + editor, + "rangechange", + coord, + editor.StatusCallback[f].params + ); + } + + return; + }; + + // + // RangeRemove(editor) + // + // Turns off the range. + // + + SocialCalc.RangeRemove = function (editor) { + var cell, cr, coord, row, col, f; + + var highlights = editor.context.highlights; + var range = editor.range; + var range2 = editor.range2; + + if (!range.hasrange && !range2.hasrange) return; + + for (row = range2.top; range2.hasrange && row <= range2.bottom; row++) { + for (col = range2.left; col <= range2.right; col++) { + coord = SocialCalc.crToCoord(col, row); + switch (highlights[coord]) { + case "range": + highlights[coord] = "newrange2"; + break; + case "range2": + case "cursor": + break; + default: + highlights[coord] = "newrange2"; + break; + } + } + } + + for (coord in highlights) { + switch (highlights[coord]) { + case "range": + delete highlights[coord]; + break; + case "newrange2": + highlights[coord] = "range2"; + break; + case "cursor": + continue; + } + cr = SocialCalc.coordToCr(coord); + cell = SocialCalc.GetEditorCellElement(editor, cr.row, cr.col); + editor.UpdateCellCSS(cell, cr.row, cr.col); + } + + range.hasrange = false; + + for (f in editor.RangeChangeCallback) { + // let others know + editor.RangeChangeCallback[f](editor); + } + + for (f in editor.StatusCallback) { + editor.StatusCallback[f].func( + editor, + "rangechange", + "", + editor.StatusCallback[f].params + ); + } + + return; + }; + + // + // Range2Remove(editor) + // + // Turns off the range2. + // + + SocialCalc.Range2Remove = function (editor) { + var cell, cr, coord, row, col, f; + + var highlights = editor.context.highlights; + var range2 = editor.range2; + + if (!range2.hasrange) return; + + for (coord in highlights) { + switch (highlights[coord]) { + case "range2": + delete highlights[coord]; + break; + case "range": + case "cursor": + continue; + } + cr = SocialCalc.coordToCr(coord); + cell = SocialCalc.GetEditorCellElement(editor, cr.row, cr.col); + editor.UpdateCellCSS(cell, cr.row, cr.col); + } + + range2.hasrange = false; + + return; + }; + + // + // FitToEditTable(editor) + // + // Figure out (through column width declarations and approximation of pixels per row) + // how many rendered rows and columns you need to be at least a little larger than + // the editor's editing area. + // + + SocialCalc.FitToEditTable = function (editor) { + var colnum, colname, colwidth, totalwidth, totalrows, rowpane, needed; + + var context = editor.context; + var sheetobj = context.sheetobj; + var sheetcolattribs = sheetobj.colattribs; + + // Calculate column width data + + totalwidth = context.showRCHeaders ? context.rownamewidth - 0 : 0; + for (var colpane = 0; colpane < context.colpanes.length - 1; colpane++) { + // Get width of all but last pane + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + colname = SocialCalc.rcColname(colnum); + colwidth = + sheetobj.colattribs.width[colname] || + sheetobj.attribs.defaultcolwidth || + SocialCalc.Constants.defaultColWidth; + if (colwidth == "blank" || colwidth == "auto") colwidth = ""; + totalwidth += colwidth && colwidth - 0 > 0 ? colwidth - 0 : 10; + } + } + + for (colnum = context.colpanes[colpane].first; colnum <= 10000; colnum++) { + //!!! max for safety, but makes that col max!!! + colname = SocialCalc.rcColname(colnum); + colwidth = + sheetobj.colattribs.width[colname] || + sheetobj.attribs.defaultcolwidth || + SocialCalc.Constants.defaultColWidth; + if (colwidth == "blank" || colwidth == "auto") colwidth = ""; + totalwidth += colwidth && colwidth - 0 > 0 ? colwidth - 0 : 10; + if (totalwidth > editor.tablewidth) break; + } + + context.colpanes[colpane].last = colnum; + + // Calculate row height data + + totalrows = context.showRCHeaders ? 1 : 0; + for (rowpane = 0; rowpane < context.rowpanes.length - 1; rowpane++) { + // count all panes but last one + totalrows += + context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 1; + } + + needed = editor.tableheight - totalrows * context.pixelsPerRow; // estimate amount needed + + context.rowpanes[rowpane].last = + context.rowpanes[rowpane].first + + Math.floor(needed / context.pixelsPerRow) + + 1; + }; + + // + // CalculateEditorPositions(editor) + // + // Calculate the screen positions and other values of various editing elements + // These values change and need to be recomputed when the pane first/last or cell contents change, + // as well as new column widths, etc. + // + // Note: Only call this after the grid has been rendered! You may have to wait for a timeout... + // + + SocialCalc.CalculateEditorPositions = function (editor) { + var rowpane, colpane, i; + + editor.gridposition = SocialCalc.GetElementPosition(editor.griddiv); + editor.headposition = SocialCalc.GetElementPosition( + editor.griddiv.firstChild.lastChild.childNodes[2].childNodes[1] + ); // 3rd tr 2nd td + + editor.rowpositions = []; + for (rowpane = 0; rowpane < editor.context.rowpanes.length; rowpane++) { + editor.CalculateRowPositions( + rowpane, + editor.rowpositions, + editor.rowheight + ); + } + for (i = 0; i < editor.rowpositions.length; i++) { + if (editor.rowpositions[i] > editor.gridposition.top + editor.tableheight) + break; + } + editor.lastvisiblerow = i - 1; + + editor.colpositions = []; + for (colpane = 0; colpane < editor.context.colpanes.length; colpane++) { + editor.CalculateColPositions( + colpane, + editor.colpositions, + editor.colwidth + ); + } + for (i = 0; i < editor.colpositions.length; i++) { + if (editor.colpositions[i] > editor.gridposition.left + editor.tablewidth) + break; + } + editor.lastvisiblecol = i - 1; + + editor.firstscrollingrow = + editor.context.rowpanes[editor.context.rowpanes.length - 1].first; + editor.firstscrollingrowtop = + editor.rowpositions[editor.firstscrollingrow] || editor.headposition.top; + editor.lastnonscrollingrow = + editor.context.rowpanes.length - 1 > 0 + ? editor.context.rowpanes[editor.context.rowpanes.length - 2].last + : 0; + editor.firstscrollingcol = + editor.context.colpanes[editor.context.colpanes.length - 1].first; + editor.firstscrollingcolleft = + editor.colpositions[editor.firstscrollingcol] || editor.headposition.left; + editor.lastnonscrollingcol = + editor.context.colpanes.length - 1 > 0 + ? editor.context.colpanes[editor.context.colpanes.length - 2].last + : 0; + + // Now do the table controls + + editor.verticaltablecontrol.ComputeTableControlPositions(); + editor.horizontaltablecontrol.ComputeTableControlPositions(); + }; + + // + // ScheduleRender(editor) + // + // Do a series of timeouts to render the sheet, wait for background layout and + // rendering by the browser, and then update editor visuals, sliders, etc. + // + + SocialCalc.ScheduleRender = function (editor) { + if (editor.timeout) window.clearTimeout(editor.timeout); // in case called more than once, just use latest + + SocialCalc.EditorSheetStatusCallback(null, "schedrender", null, editor); + SocialCalc.EditorStepInfo.editor = editor; + editor.timeout = window.setTimeout(SocialCalc.DoRenderStep, 1); + }; + + // DoRenderStep() + // + + SocialCalc.DoRenderStep = function () { + var editor = SocialCalc.EditorStepInfo.editor; + + editor.timeout = null; + + editor.EditorRenderSheet(); + + SocialCalc.EditorSheetStatusCallback(null, "renderdone", null, editor); + + SocialCalc.EditorSheetStatusCallback(null, "schedposcalc", null, editor); + + editor.timeout = window.setTimeout(SocialCalc.DoPositionCalculations, 1); + }; + + // + // SocialCalc.SchedulePositionCalculations(editor) + // + + SocialCalc.SchedulePositionCalculations = function (editor) { + SocialCalc.EditorStepInfo.editor = editor; + + SocialCalc.EditorSheetStatusCallback(null, "schedposcalc", null, editor); + + editor.timeout = window.setTimeout(SocialCalc.DoPositionCalculations, 1); + }; + + // DoPositionCalculations(editor) + // + // Update editor visuals, sliders, etc. + // + // Note: Only call this after the DOM objects have been modified and rendered! + // + + SocialCalc.DoPositionCalculations = function () { + var editor = SocialCalc.EditorStepInfo.editor; + + editor.timeout = null; + + var ok = false; + try { + editor.CalculateEditorPositions(); + ok = true; + } catch (e) { } + + if (!ok) { + if (typeof $ != "undefined") { + $(window).trigger("resize"); + setTimeout(SocialCalc.DoPositionCalculations, 400); + } + return; /* Workaround IE6 partial-initialized-DOM bug */ + } + + editor.verticaltablecontrol.PositionTableControlElements(); + editor.horizontaltablecontrol.PositionTableControlElements(); + + SocialCalc.EditorSheetStatusCallback(null, "doneposcalc", null, editor); + + if (editor.ensureecell && editor.ecell && !editor.deferredCommands.length) { + // don't do if deferred cmd to execute + editor.ensureecell = false; + editor.EnsureECellVisible(); // this could cause another redisplay + } + + editor.cellhandles.ShowCellHandles(true); + + //!!! Need to now check to see if this positioned controls out of the editing area + //!!! (such as when there is a large wrapped cell and it pushes the pane boundary too far down). + + if (SocialCalc.Callbacks.broadcast) + SocialCalc.Callbacks.broadcast("ask.ecell"); + }; + + SocialCalc.CalculateRowPositions = function ( + editor, + panenum, + positions, + sizes + ) { + var toprow, rowpane, rownum, offset, trowobj, cellposition; + + var context = editor.context; + var sheetobj = context.sheetobj; + + var tbodyobj; + + if (!context.showRCHeaders) throw "Needs showRCHeaders=true"; + + tbodyobj = editor.fullgrid.lastChild; + + // Calculate start of this pane as row in this table: + + toprow = 2; + for (rowpane = 0; rowpane < panenum; rowpane++) { + toprow += + context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 2; // skip pane and spacing row + } + + offset = 0; + for ( + rownum = context.rowpanes[rowpane].first; + rownum <= context.rowpanes[rowpane].last; + rownum++ + ) { + trowobj = tbodyobj.childNodes[toprow + offset]; + offset++; + cellposition = SocialCalc.GetElementPosition(trowobj.firstChild); + + // Safari has problem: If a cell in the row is high, cell 1 is centered and it returns top of centered part + // but if you get position of row element, it always returns the same value (not the row's) + // So we require row number to be vertical aligned to top + + if (!positions[rownum]) { + positions[rownum] = cellposition.top; // first one takes precedence + sizes[rownum] = trowobj.firstChild.offsetHeight; + } + } + + return; + }; + + SocialCalc.CalculateColPositions = function ( + editor, + panenum, + positions, + sizes + ) { + var leftcol, colpane, colnum, offset, trowobj, cellposition; + + var context = editor.context; + var sheetobj = context.sheetobj; + + var tbodyobj; + + if (!context.showRCHeaders) throw "Needs showRCHeaders=true"; + + tbodyobj = editor.fullgrid.lastChild; + + // Calculate start of this pane as column in this table: + + leftcol = 1; + for (colpane = 0; colpane < panenum; colpane++) { + leftcol += + context.colpanes[colpane].last - context.colpanes[colpane].first + 2; // skip pane and spacing col + } + + trowobj = tbodyobj.childNodes[1]; // get heading row, which has all columns + offset = 0; + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + cellposition = SocialCalc.GetElementPosition( + trowobj.childNodes[leftcol + offset] + ); + if (!positions[colnum]) { + positions[colnum] = cellposition.left; // first one takes precedence + if (trowobj.childNodes[leftcol + offset]) { + sizes[colnum] = trowobj.childNodes[leftcol + offset].offsetWidth; + } + } + offset++; + } + + return; + }; + + // ScrollRelative(editor, vertical, amount) + // + // If vertical true, scrolls up(-)/down(+), else left(-)/right(+) + + SocialCalc.ScrollRelative = function (editor, vertical, amount) { + if (vertical) { + editor.ScrollRelativeBoth(amount, 0); + } else { + editor.ScrollRelativeBoth(0, amount); + } + return; + }; + + // ScrollRelativeBoth(editor, vamount, hamount) + // + // Does both with one render + + SocialCalc.ScrollRelativeBoth = function (editor, vamount, hamount) { + var context = editor.context; + + var vplen = context.rowpanes.length; + var vlimit = vplen > 1 ? context.rowpanes[vplen - 2].last + 1 : 1; // don't scroll past here + if (context.rowpanes[vplen - 1].first + vamount < vlimit) { + // limit amount + vamount = -context.rowpanes[vplen - 1].first + vlimit; + } + + var hplen = context.colpanes.length; + var hlimit = hplen > 1 ? context.colpanes[hplen - 2].last + 1 : 1; // don't scroll past here + + if (context.colpanes[hplen - 1].first + hamount < hlimit) { + // limit amount + hamount = -context.colpanes[hplen - 1].first + hlimit; + } + + if ( + SocialCalc.IsScrollPossible && + !SocialCalc.IsScrollPossible( + editor.context.sheetobj.attribs.lastrow, + editor.context.sheetobj.attribs.lastcol, + context.rowpanes[vplen - 1].first, + context.colpanes[hplen - 1].first, + vamount, + hamount + ) + ) { + return; + } + + if ((vamount == 1 || vamount == -1) && hamount == 0) { + // special case quick scrolls + if (vamount == 1) { + editor.ScrollTableUpOneRow(); + } else { + editor.ScrollTableDownOneRow(); + } + if (editor.ecell) editor.SetECellHeaders("selected"); + editor.SchedulePositionCalculations(); + return; + } + + // Do a gross move and render + + if (vamount != 0 || hamount != 0) { + context.rowpanes[vplen - 1].first += vamount; + context.rowpanes[vplen - 1].last += vamount; + context.colpanes[hplen - 1].first += hamount; + context.colpanes[hplen - 1].last += hamount; + editor.FitToEditTable(); + editor.ScheduleRender(); + } + }; + + // PageRelative(editor, vertical, direction) + // + // If vertical true, pages up(direction is -)/down(+), else left(-)/right(+) + + SocialCalc.PageRelative = function (editor, vertical, direction) { + var context = editor.context; + var panes = vertical ? "rowpanes" : "colpanes"; + var lastpane = context[panes][context[panes].length - 1]; + var lastvisible = vertical ? "lastvisiblerow" : "lastvisiblecol"; + var sizearray = vertical ? editor.rowheight : editor.colwidth; + var defaultsize = vertical + ? SocialCalc.Constants.defaultAssumedRowHeight + : SocialCalc.Constants.defaultColWidth; + var size, newfirst, totalsize, current; + + if (direction > 0) { + // down/right + newfirst = editor[lastvisible]; + if (newfirst == lastpane.first) newfirst += 1; // move at least one + } else { + if (vertical) { + // calculate amount to scroll + totalsize = + editor.tableheight - + (editor.firstscrollingrowtop - editor.gridposition.top); + } else { + totalsize = + editor.tablewidth - + (editor.firstscrollingcolleft - editor.gridposition.left); + } + totalsize -= + sizearray[editor[lastvisible]] > 0 + ? sizearray[editor[lastvisible]] + : defaultsize; + + for (newfirst = lastpane.first - 1; newfirst > 0; newfirst--) { + size = sizearray[newfirst] > 0 ? sizearray[newfirst] : defaultsize; + if (totalsize < size) break; + totalsize -= size; + } + + current = lastpane.first; + if (newfirst >= current) newfirst = current - 1; // move at least 1 + if (newfirst < 1) newfirst = 1; + } + + lastpane.first = newfirst; + lastpane.last = newfirst + 1; + editor.LimitLastPanes(); + editor.FitToEditTable(); + editor.ScheduleRender(); + }; + + // LimitLastPanes(editor) + // + // Makes sure that the "first" of the last panes isn't before the last of the previous pane + // + + SocialCalc.LimitLastPanes = function (editor) { + var context = editor.context; + var plen; + + plen = context.rowpanes.length; + if ( + plen > 1 && + context.rowpanes[plen - 1].first <= context.rowpanes[plen - 2].last + ) + context.rowpanes[plen - 1].first = context.rowpanes[plen - 2].last + 1; + + plen = context.colpanes.length; + if ( + plen > 1 && + context.colpanes[plen - 1].first <= context.colpanes[plen - 2].last + ) + context.colpanes[plen - 1].first = context.colpanes[plen - 2].last + 1; + }; + + SocialCalc.ScrollTableUpOneRow = function (editor) { + var toprow, + rowpane, + rownum, + colnum, + colpane, + cell, + oldrownum, + maxspan, + newbottomrow, + newrow, + oldchild, + bottomrownum; + var rowneedsrefresh = {}; + + var context = editor.context; + var sheetobj = context.sheetobj; + var tableobj = editor.fullgrid; + + var tbodyobj; + + tbodyobj = tableobj.lastChild; + + toprow = context.showRCHeaders ? 2 : 1; + for (rowpane = 0; rowpane < context.rowpanes.length - 1; rowpane++) { + toprow += + context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 2; // skip pane and spacing row + } + + tbodyobj.removeChild(tbodyobj.childNodes[toprow]); + + context.rowpanes[rowpane].first++; + context.rowpanes[rowpane].last++; + editor.FitToEditTable(); + context.CalculateColWidthData(); // Just in case, since normally done in RenderSheet + + newbottomrow = context.RenderRow(context.rowpanes[rowpane].last, rowpane); + tbodyobj.appendChild(newbottomrow); + + // if scrolled off a row with starting rowspans, replace rows for the largest rowspan + + var maxrowspan = 1; + oldrownum = context.rowpanes[rowpane].first - 1; + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + var coord = SocialCalc.crToCoord(colnum, oldrownum); + if (context.cellskip[coord]) continue; + cell = sheetobj.cells[coord]; + if (cell && cell.rowspan > maxrowspan) maxrowspan = cell.rowspan; + } + } + + if (maxrowspan > 1) { + for (rownum = 1; rownum < maxrowspan; rownum++) { + if (rownum + oldrownum >= context.rowpanes[rowpane].last) break; + newrow = context.RenderRow(rownum + oldrownum, rowpane); + oldchild = tbodyobj.childNodes[toprow + rownum - 1]; + tbodyobj.replaceChild(newrow, oldchild); + } + } + + // if added a row that includes rowspans from above, update the size of those to include new row + + bottomrownum = context.rowpanes[rowpane].last; + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + coord = context.cellskip[SocialCalc.crToCoord(colnum, bottomrownum)]; + if (!coord) continue; // only look at spanned cells + rownum = context.coordToCR[coord].row - 0; + if ( + rownum == context.rowpanes[rowpane].last || + rownum < context.rowpanes[rowpane].first + ) + continue; // this row (colspan) or starts above pane + cell = sheetobj.cells[coord]; + if (cell && cell.rowspan > 1) rowneedsrefresh[rownum] = true; // remember row num to update + } + } + + for (rownum in rowneedsrefresh) { + newrow = context.RenderRow(rownum, rowpane); + oldchild = + tbodyobj.childNodes[ + toprow + (rownum - context.rowpanes[rowpane].first) + ]; + tbodyobj.replaceChild(newrow, oldchild); + } + + return tableobj; + }; + + SocialCalc.ScrollTableDownOneRow = function (editor) { + var toprow, + rowpane, + rownum, + colnum, + colpane, + cell, + newrownum, + maxspan, + newbottomrow, + newrow, + oldchild, + bottomrownum, + maxrowspan, + coord; + var rowneedsrefresh = {}; + + var context = editor.context; + var sheetobj = context.sheetobj; + var tableobj = editor.fullgrid; + + var tbodyobj; + + tbodyobj = tableobj.lastChild; + + toprow = context.showRCHeaders ? 2 : 1; + for (rowpane = 0; rowpane < context.rowpanes.length - 1; rowpane++) { + toprow += + context.rowpanes[rowpane].last - context.rowpanes[rowpane].first + 2; // skip pane and spacing row + } + + tbodyobj.removeChild( + tbodyobj.childNodes[ + toprow + + (context.rowpanes[rowpane].last - context.rowpanes[rowpane].first) + ] + ); + + context.rowpanes[rowpane].first--; + context.rowpanes[rowpane].last--; + editor.FitToEditTable(); + context.CalculateColWidthData(); // Just in case, since normally done in RenderSheet + + newrow = context.RenderRow(context.rowpanes[rowpane].first, rowpane); + tbodyobj.insertBefore(newrow, tbodyobj.childNodes[toprow]); + + // if inserted a row with starting rowspans, replace rows for the largest rowspan + + maxrowspan = 1; + newrownum = context.rowpanes[rowpane].first; + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + coord = SocialCalc.crToCoord(colnum, newrownum); + if (context.cellskip[coord]) continue; + cell = sheetobj.cells[coord]; + if (cell && cell.rowspan > maxrowspan) maxrowspan = cell.rowspan; + } + } + + if (maxrowspan > 1) { + for (rownum = 1; rownum < maxrowspan; rownum++) { + if (rownum + newrownum > context.rowpanes[rowpane].last) break; + newrow = context.RenderRow(rownum + newrownum, rowpane); + oldchild = tbodyobj.childNodes[toprow + rownum]; + tbodyobj.replaceChild(newrow, oldchild); + } + } + + // if last row now includes rowspans or rowspans from above, update the size of those to remove deleted row + + bottomrownum = context.rowpanes[rowpane].last; + + for (colpane = 0; colpane < context.colpanes.length; colpane++) { + for ( + colnum = context.colpanes[colpane].first; + colnum <= context.colpanes[colpane].last; + colnum++ + ) { + coord = SocialCalc.crToCoord(colnum, bottomrownum); + cell = sheetobj.cells[coord]; + if (cell && cell.rowspan > 1) { + rowneedsrefresh[bottomrownum] = true; // need to update this row + continue; + } + coord = context.cellskip[SocialCalc.crToCoord(colnum, bottomrownum)]; + if (!coord) continue; // only look at spanned cells + rownum = context.coordToCR[coord].row - 0; + if (rownum == bottomrownum || rownum < context.rowpanes[rowpane].first) + continue; // this row (colspan) or starts above pane + cell = sheetobj.cells[coord]; + if (cell && cell.rowspan > 1) rowneedsrefresh[rownum] = true; // remember row num to update + } + } + + for (rownum in rowneedsrefresh) { + newrow = context.RenderRow(rownum, rowpane); + oldchild = + tbodyobj.childNodes[ + toprow + (rownum - context.rowpanes[rowpane].first) + ]; + tbodyobj.replaceChild(newrow, oldchild); + } + + return tableobj; + }; + + // ************************************* + // + // InputBox class: + // + // This class deals with the text box for editing cell contents. + // It mainly controls a user input box for typed content and is used to interact with + // the keyboard code, etc. + // + // You can use this inside a formula bar control of some sort. + // You create this after you have created a table editor object (but not necessarily + // done the CreateTableEditor method). + // + // When the user starts typing text, or double-clicks on a cell, this object + // comes into play. + // + // The element given when this is first constructed should be an input HTMLElement or + // something that acts like one. Check the code here to see what is done to it. + // + // ************************************* + + SocialCalc.InputBox = function (element, editor) { + if (!element) return; // invoked without enough data to work + + this.element = element; // the input element associated with this InputBox + this.editor = editor; // the TableEditor this belongs to + this.inputEcho = null; + + editor.inputBox = this; + + element.onmousedown = SocialCalc.InputBoxOnMouseDown; + + editor.MoveECellCallback.formulabar = function (e) { + if (e.state != "start") { + return; + } // if not in normal keyboard mode don't replace formula bar + editor.inputBox.DisplayCellContents(e.ecell.coord); + }; + }; + + // Methods: + + SocialCalc.InputBox.prototype.DisplayCellContents = function (coord) { + SocialCalc.InputBoxDisplayCellContents(this, coord); + }; + SocialCalc.InputBox.prototype.ShowInputBox = function (show) { + this.editor.inputEcho.ShowInputEcho(show); + }; + SocialCalc.InputBox.prototype.GetText = function () { + return this.element.value; + }; + SocialCalc.InputBox.prototype.SetText = function (newtext) { + if (!this.element) return; + this.element.value = newtext; + + if (!SocialCalc.Constants.SCNoInputEcho) { + this.editor.inputEcho.SetText(newtext + "_"); + } + }; + SocialCalc.InputBox.prototype.Focus = function () { + SocialCalc.InputBoxFocus(this); + }; + SocialCalc.InputBox.prototype.Blur = function () { + return this.element.blur(); + }; + SocialCalc.InputBox.prototype.Select = function (t) { + if (!this.element) return; + switch (t) { + case "end": + if (document.selection && document.selection.createRange) { + /* IE 4+ - Safer than setting .selectionEnd as it also works for Textareas. */ + var range = document.selection.createRange().duplicate(); + range.moveToElementText(this.element); + range.collapse(false); + range.select(); + } else if (this.element.selectionStart != undefined) { + this.element.selectionStart = this.element.value.length; + this.element.selectionEnd = this.element.value.length; + } + break; + } + }; + + // Functions: + + // + // SocialCalc.InputBoxDisplayCellContents(inputbox, coord) + // + // Sets input box to the contents of the specified cell (or ecell if null). + // + var CoordForColorChange; + var editCoord; + SocialCalc.InputBoxDisplayCellContents = function (inputbox, coord) { + // inputbox.element.disabled = true; + // return; + var scc = SocialCalc.Constants; + var cell, position; + + if (!inputbox) return; + + //changes for prompt + if (!coord) { + coord = inputbox.editor.ecell.coord; + } + var text = SocialCalc.GetCellContents( + inputbox.editor.context.sheetobj, + coord + ); + if (text.indexOf("\n") != -1) { + //text = scc.s_inputboxdisplaymultilinetext; + text = scc.s_inputboxdisplaynoteditable; + inputbox.element.disabled = true; + SocialCalc.ToggleInputLineButtons(false); + } else if (!SocialCalc.Callbacks.IsCellEditable(inputbox.editor)) { + text = scc.s_inputboxdisplaynoteditable; + SocialCalc.ToggleInputLineButtons(false); + inputbox.element.disabled = true; + inputbox.element.style.display = "none"; + } else { + CoordForColorChange = coord; + editCoord = coord; + + //changes for prompt + + var control = SocialCalc.GetCurrentWorkBookControl(); + var spreadsheet = control.workbook.spreadsheet; + cell = SocialCalc.GetEditorCellElement( + inputbox.editor, + inputbox.editor.ecell.row, + inputbox.editor.ecell.col + ); + var left = "100px"; + var top = "100px"; + var width = 0; + var height = 0; + if (cell) { + position = SocialCalc.GetElementPosition(cell.element); + left = position.left + "px"; + top = position.top + "px"; + width = cell.element.offsetWidth; + height = cell.element.offsetHeight; + } + if (!cell || width == 0) { + //scrolled off screen + SocialCalc.ToggleInputLineButtons(false); + inputbox.element.disabled = true; + inputbox.element.style.display = "none"; + // do nothing + return; + } + var ele = document.getElementById(spreadsheet.formulabarDiv.id); + if (ele) { + spreadsheet.spreadsheetDiv.removeChild(spreadsheet.formulabarDiv); + } + spreadsheet.formulabarDiv.style.left = left; + spreadsheet.formulabarDiv.style.top = top; + //spreadsheet.formulabarDiv.style.width = "30px"; + //spreadsheet.formulabarDiv.style.fontSize = "10px"; + spreadsheet.formulabarDiv.style.zIndex = 100; + spreadsheet.formulabarDiv.style.position = "absolute"; + var input = spreadsheet.formulabarDiv.firstChild; + //changes for prompt + + input.style.fontSize = "100%"; + input.style.backgroundColor = "white"; + input.style.color = "black"; + + input.style.borderBottomColor = "#306eff"; + input.style.borderBottomLeftRadius = "3px"; + input.style.borderBottomRightRadius = "3px"; + input.style.borderBottomStyle = "solid"; + input.style.borderBottomWidth = "2px"; + input.style.borderLeftColor = "#306eff"; + input.style.borderLeftStyle = "solid"; + input.style.borderLeftWidth = "2px"; + input.style.borderRightColor = "#306eff"; + input.style.borderRightStyle = "solid"; + input.style.borderRightWidth = "2px"; + input.style.borderTopColor = "#306eff"; + input.style.borderTopLeftRadius = "3px"; + input.style.borderTopRightRadius = "3px"; + input.style.borderTopStyle = "solid"; + input.style.borderTopWidth = "2px"; + + //console.log("cell width ="+width) + //changes for prompt + + input.style.width = width + "px"; + input.style.height = height + "px"; + spreadsheet.spreadsheetDiv.appendChild(spreadsheet.formulabarDiv); + + inputbox.element.disabled = false; + inputbox.element.style.display = "inline"; + SocialCalc.ToggleInputLineButtons(true); + } + if (scc.SCNoQuoteInInputBox && text.substring(0, 1) == "'") { + text = text.substring(1); + } + inputbox.SetText(text); + // autoSave(selectedFile); + }; + + // + // SocialCalc.InputBoxFocus(inputbox) + // + // Call this to have the input box get the focus and respond to keystrokes + // but still pass them off to SocialCalc.ProcessKey. + // + + SocialCalc.InputBoxFocus = function (inputbox) { + if (!inputbox) return; + inputbox.element.focus(); + var editor = inputbox.editor; + editor.state = "input"; + var wval = editor.workingvalues; + wval.partialexpr = ""; + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + }; + + // + // SocialCalc.InputBoxOnMouseDown(e) + // + // This is called when the input box gets the focus. It then responds to keystrokes + // and pass them off to SocialCalc.ProcessKey, but in a different editing state. + // + + SocialCalc.InputBoxOnMouseDown = function (e) { + var editor = SocialCalc.Keyboard.focusTable; // get TableEditor doing keyboard stuff + if (!editor) return true; // we're not handling it -- let browser do default + var wval = editor.workingvalues; + + switch (editor.state) { + case "start": + editor.state = "inputboxdirect"; + wval.partialexpr = ""; + wval.ecoord = editor.ecell.coord; + wval.erow = editor.ecell.row; + wval.ecol = editor.ecell.col; + editor.inputEcho.ShowInputEcho(true); + break; + + case "input": + wval.partialexpr = ""; // make sure not pointing + editor.MoveECell(wval.ecoord); + editor.state = "inputboxdirect"; + SocialCalc.KeyboardFocus(); // may have come here from outside of grid + break; + + case "inputboxdirect": + break; + } + }; + + // ************************************* + // + // InputEcho class: + // + // This object creates and controls an element that echos what's in the InputBox during editing + // It is draggable. + // + // ************************************* + + SocialCalc.InputEcho = function (editor) { + var scc = SocialCalc.Constants; + + this.editor = editor; // the TableEditor this belongs to + this.text = ""; // current value of what is displayed + this.interval = null; // timer handle + + this.container = null; // element containing main echo as well as prompt line + this.main = null; // main echo area + this.prompt = null; + + this.functionbox = null; // function chooser dialog + + this.container = document.createElement("div"); + SocialCalc.setStyles( + this.container, + "display:none;position:absolute;zIndex:10;" + ); + + this.topprompt = document.createElement("div"); + if (scc.defaultInputEchoPromptClass) + this.topprompt.className = scc.defaultInputEchoPromptClass; + if (scc.defaultInputEchoPromptStyle) + SocialCalc.setStyles(this.topprompt, scc.defaultInputEchoPromptStyle); + this.topprompt.innerHTML = ""; + + this.container.appendChild(this.topprompt); + + this.main = document.createElement("div"); + + if (scc.defaultInputEchoClass) + this.main.className = scc.defaultInputEchoClass; + if (scc.defaultInputEchoStyle) + SocialCalc.setStyles(this.main, scc.defaultInputEchoStyle); + + this.main.innerHTML = " "; + + this.container.appendChild(this.main); + + this.prompt = document.createElement("div"); + if (scc.defaultInputEchoPromptClass) + this.prompt.className = scc.defaultInputEchoPromptClass; + if (scc.defaultInputEchoPromptStyle) + SocialCalc.setStyles(this.prompt, scc.defaultInputEchoPromptStyle); + this.prompt.innerHTML = ""; + + this.container.appendChild(this.prompt); + + SocialCalc.DragRegister(this.main, true, true, { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + positionobj: this.container, + }); + + editor.toplevel.appendChild(this.container); + }; + + // Methods: + + SocialCalc.InputEcho.prototype.ShowInputEcho = function (show) { + return SocialCalc.ShowInputEcho(this, show); + }; + SocialCalc.InputEcho.prototype.SetText = function (str) { + return SocialCalc.SetInputEchoText(this, str); + }; + + // Functions: + + SocialCalc.ShowInputEcho = function (inputecho, show) { + var cell, position; + var editor = inputecho.editor; + + if (!editor) return; + if (SocialCalc.Constants.SCNoInputEcho) { + return; + } + + if (show) { + editor.cellhandles.ShowCellHandles(false); + cell = SocialCalc.GetEditorCellElement( + editor, + editor.ecell.row, + editor.ecell.col + ); + if (cell) { + position = SocialCalc.GetElementPosition(cell.element); + inputecho.container.style.left = position.left - 1 + "px"; + inputecho.container.style.top = position.top - 1 + "px"; + } + inputecho.container.style.display = "block"; + if (inputecho.interval) window.clearInterval(inputecho.interval); // just in case + inputecho.interval = window.setInterval( + SocialCalc.InputEchoHeartbeat, + 50 + ); + } else { + if (inputecho.interval) window.clearInterval(inputecho.interval); + inputecho.container.style.display = "none"; + inputecho.topprompt.innerHTML = ""; + } + }; + + SocialCalc.SetInputEchoText = function (inputecho, str) { + if (SocialCalc.Constants.SCNoInputEcho) { + return; + } + + var scc = SocialCalc.Constants; + var fname, fstr; + var newstr = SocialCalc.special_chars(str); + newstr = newstr.replace(/\n/g, "
"); + + if (inputecho.text != newstr) { + inputecho.main.innerHTML = newstr; + inputecho.text = newstr; + } + + var parts = str.match( + /.*[\+\-\*\/\&\^\<\>\=\,\(]([A-Za-z][A-ZA-z]\w*?)\([^\)]*$/ + ); + if (str.charAt(0) == "=" && parts) { + fname = parts[1].toUpperCase(); + if (SocialCalc.Formula.FunctionList[fname]) { + SocialCalc.Formula.FillFunctionInfo(); // make sure filled + fstr = SocialCalc.special_chars( + fname + "(" + SocialCalc.Formula.FunctionArgString(fname) + ")" + ); + } else { + fstr = scc.ietUnknownFunction + fname; + } + if (inputecho.prompt.innerHTML != fstr) { + inputecho.prompt.innerHTML = fstr; + inputecho.prompt.style.display = "block"; + } + } else if (inputecho.prompt.style.display != "none") { + inputecho.prompt.innerHTML = ""; + inputecho.prompt.style.display = "none"; + } + + var editor = inputecho.editor; + + if (editor.workingvalues.currentsheet != editor.workingvalues.startsheet) { + var promptstr = + "Editing:" + + editor.workingvalues.startsheet + + "!" + + editor.workingvalues.ecoord; + if (promptstr != inputecho.topprompt.innerHTML) { + inputecho.topprompt.innerHTML = + "Editing:" + + editor.workingvalues.startsheet + + "!" + + editor.workingvalues.ecoord; + inputecho.topprompt.style.display = "block"; + } + } else { + if (inputecho.topprompt.style.display != "none") { + inputecho.topprompt.innerHTML = ""; + inputecho.topprompt.style.display = "none"; + } + } + }; + + SocialCalc.InputEchoHeartbeat = function () { + var editor = SocialCalc.Keyboard.focusTable; // get TableEditor doing keyboard stuff + if (!editor) return true; // we're not handling it -- let browser do default + + if (SocialCalc.Constants.SCNoInputEcho) { + return; + } + + if (editor.state == "inputboxdirect") { + editor.inputEcho.SetText(editor.inputBox.GetText() + "_"); + } + }; + + SocialCalc.InputEchoMouseDown = function (e) { + var event = e || window.event; + + var editor = SocialCalc.Keyboard.focusTable; // get TableEditor doing keyboard stuff + if (!editor) return true; // we're not handling it -- let browser do default + + if (SocialCalc.Constants.SCNoInputEcho) { + return; + } + + // if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + // else event.cancelBubble = true; // IE 5+ + // if (event.preventDefault) event.preventDefault(); // DOM Level 2 + // else event.returnValue = false; // IE 5+ + + editor.inputBox.element.focus(); + + // return false; + }; + + // ************************************* + // + // CellHandles class: + // + // This object creates and controls the elements around the cursor cell for dragging, etc. + // + // ************************************* + + SocialCalc.CellHandles = function (editor) { + var scc = SocialCalc.Constants; + var functions; + + if (editor.noEdit) return; // leave us with nothing + if (scc.SCCellHandlesDisable) return; + + this.editor = editor; // the TableEditor this belongs to + + this.noCursorSuffix = false; + + this.movedmouse = false; // used to detect no-op + + this.draghandle = document.createElement("div"); + SocialCalc.setStyles( + this.draghandle, + "display:none;position:absolute;zIndex:8;border:1px solid white;width:4px;height:4px;fontSize:1px;backgroundColor:#0E93D8;cursor:default;" + ); + this.draghandle.innerHTML = " "; + editor.toplevel.appendChild(this.draghandle); + SocialCalc.AssignID(editor, this.draghandle, "draghandle"); + + var imagetype = "png"; + if (navigator.userAgent.match(/MSIE 6\.0/)) { + imagetype = "gif"; + } + + this.dragpalette = document.createElement("div"); + SocialCalc.setStyles( + this.dragpalette, + "display:none;position:absolute;zIndex:8;width:90px;height:90px;fontSize:1px;textAlign:center;cursor:default;" + + "backgroundImage:url(" + + SocialCalc.Constants.defaultImagePrefix + + "drag-handles." + + imagetype + + ");" + ); + this.dragpalette.innerHTML = " "; + editor.toplevel.appendChild(this.dragpalette); + SocialCalc.AssignID(editor, this.dragpalette, "dragpalette"); + + this.dragtooltip = document.createElement("div"); + SocialCalc.setStyles( + this.dragtooltip, + "display:none;position:absolute;zIndex:9;border:1px solid black;width:100px;height:auto;fontSize:10px;backgroundColor:#FFFFFF;" + ); + this.dragtooltip.innerHTML = " "; + editor.toplevel.appendChild(this.dragtooltip); + SocialCalc.AssignID(editor, this.dragtooltip, "dragtooltip"); + + this.fillinghandle = document.createElement("div"); + SocialCalc.setStyles( + this.fillinghandle, + "display:none;position:absolute;zIndex:9;border:1px solid black;width:auto;height:14px;fontSize:10px;backgroundColor:#FFFFFF;" + ); + this.fillinghandle.innerHTML = " "; + editor.toplevel.appendChild(this.fillinghandle); + SocialCalc.AssignID(editor, this.fillinghandle, "fillinghandle"); + + if (this.draghandle.addEventListener) { + // DOM Level 2 -- Firefox, et al + this.draghandle.addEventListener( + "mousemove", + SocialCalc.CellHandlesMouseMoveOnHandle, + false + ); + this.dragpalette.addEventListener( + "mousedown", + SocialCalc.CellHandlesMouseDown, + false + ); + this.dragpalette.addEventListener( + "mousemove", + SocialCalc.CellHandlesMouseMoveOnHandle, + false + ); + } else if (this.draghandle.attachEvent) { + // IE 5+ + this.draghandle.attachEvent( + "onmousemove", + SocialCalc.CellHandlesMouseMoveOnHandle + ); + this.dragpalette.attachEvent( + "onmousedown", + SocialCalc.CellHandlesMouseDown + ); + this.dragpalette.attachEvent( + "onmousemove", + SocialCalc.CellHandlesMouseMoveOnHandle + ); + } else { + // don't handle this + throw "Browser not supported"; + } + }; + + // Methods: + + SocialCalc.CellHandles.prototype.ShowCellHandles = function (show, moveshow) { + return SocialCalc.ShowCellHandles(this, show, moveshow); + }; + + // Functions: + + SocialCalc.ShowCellHandles = function (cellhandles, show, moveshow) { + var cell, cell2, position, position2; + var editor = cellhandles.editor; + var doshow = false; + var row, col, viewport; + + if (!editor) return; + + do { + // a block that can you can "break" out of easily + + if (!show) break; + + row = editor.ecell.row; + col = editor.ecell.col; + + if (editor.state != "start") break; + if (row >= editor.lastvisiblerow) break; + if (col >= editor.lastvisiblecol) break; + if (row < editor.firstscrollingrow) break; + if (col < editor.firstscrollingcol) break; + + if ( + editor.rowpositions[row + 1] + 20 > + editor.horizontaltablecontrol.controlborder + ) { + break; + } + if (editor.rowpositions[row + 1] - 10 < editor.headposition.top) { + break; + } + if ( + editor.colpositions[col + 1] + 20 > + editor.verticaltablecontrol.controlborder + ) { + break; + } + if (editor.colpositions[col + 1] - 30 < editor.headposition.left) { + break; + } + + cellhandles.draghandle.style.left = + editor.colpositions[col + 1] - 1 + "px"; + cellhandles.draghandle.style.top = + editor.rowpositions[row + 1] - 1 + "px"; + cellhandles.draghandle.style.display = "block"; + + if (moveshow) { + cellhandles.draghandle.style.display = "none"; + cellhandles.dragpalette.style.left = + editor.colpositions[col + 1] - 45 + "px"; + cellhandles.dragpalette.style.top = + editor.rowpositions[row + 1] - 45 + "px"; + cellhandles.dragpalette.style.display = "block"; + viewport = SocialCalc.GetViewportInfo(); + cellhandles.dragtooltip.style.right = + viewport.width - (editor.colpositions[col + 1] - 1) + "px"; + cellhandles.dragtooltip.style.bottom = + viewport.height - (editor.rowpositions[row + 1] - 1) + "px"; + cellhandles.dragtooltip.style.display = "none"; + } + + doshow = true; + } while (false); // only do once + + if (!doshow) { + cellhandles.draghandle.style.display = "none"; + } + if (!moveshow) { + cellhandles.dragpalette.style.display = "none"; + cellhandles.dragtooltip.style.display = "none"; + } + }; + + SocialCalc.CellHandlesMouseMoveOnHandle = function (e) { + var scc = SocialCalc.Constants; + + var event = e || window.event; + var target = event.target || event.srcElement; + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + editor = SocialCalc.Keyboard.focusTable; // get TableEditor doing keyboard stuff + if (!editor) return true; // we're not handling it -- let browser do default + var cellhandles = editor.cellhandles; + if (!cellhandles.editor) return true; // no handles + + if (!editor.cellhandles.mouseDown) { + editor.cellhandles.ShowCellHandles(true, true); // show move handles, too + + if (target == cellhandles.dragpalette) { + var whichhandle = SocialCalc.SegmentDivHit( + [scc.CH_radius1, scc.CH_radius2], + editor.cellhandles.dragpalette, + clientX, + clientY + ); + if (whichhandle == 0) { + // off of active part of palette + SocialCalc.CellHandlesHoverTimeout(); + return; + } + if (cellhandles.tooltipstimer) { + window.clearTimeout(cellhandles.tooltipstimer); + cellhandles.tooltipstimer = null; + } + cellhandles.tooltipswhichhandle = whichhandle; + cellhandles.tooltipstimer = window.setTimeout( + SocialCalc.CellHandlesTooltipsTimeout, + 700 + ); + } + + if (cellhandles.timer) { + window.clearTimeout(cellhandles.timer); + cellhandles.timer = null; + } + cellhandles.timer = window.setTimeout( + SocialCalc.CellHandlesHoverTimeout, + 3000 + ); + } + + return; + }; + + // + // whichsegment = SocialCalc.SegmentDivHit(segtable, divWithMouseHit, x, y) + // + // Takes segtable = [upperleft quadrant, upperright, bottomright, bottomleft] + // where each quadrant is either: + // 0 = ignore hits here + // number = return this value + // array = a new segtable for this subquadrant + // + // Alternatively, segtable can be: + // [radius 1, radius 2] and it returns 0 if no hit, + // -1, -2, -3, -4 for inner quadrants, and +1...+4 for outer quadrants + // + + SocialCalc.SegmentDivHit = function (segtable, divWithMouseHit, x, y) { + var width = divWithMouseHit.offsetWidth; + var height = divWithMouseHit.offsetHeight; + var left = divWithMouseHit.offsetLeft; + var top = divWithMouseHit.offsetTop; + var v = 0; + var table = segtable; + var len = Math.sqrt( + Math.pow(x - left - (width / 2.0 - 0.5), 2) + + Math.pow(y - top - (height / 2.0 - 0.5), 2) + ); + + if (table.length == 2) { + // type 2 segtable + if ( + x >= left && + x < left + width / 2 && + y >= top && + y < top + height / 2 + ) { + // upper left + if (len <= segtable[0]) v = -1; + else if (len <= segtable[1]) v = 1; + } + if ( + x >= left + width / 2 && + x < left + width && + y >= top && + y < top + height / 2 + ) { + // upper right + if (len <= segtable[0]) v = -2; + else if (len <= segtable[1]) v = 2; + } + if ( + x >= left + width / 2 && + x < left + width && + y >= top + height / 2 && + y < top + height + ) { + // bottom right + if (len <= segtable[0]) v = -3; + else if (len <= segtable[1]) v = 3; + } + if ( + x >= left && + x < left + width / 2 && + y >= top + height / 2 && + y < top + height + ) { + // bottom right + if (len <= segtable[0]) v = -4; + else if (len <= segtable[1]) v = 4; + } + return v; + } + + while (true) { + if ( + x >= left && + x < left + width / 2 && + y >= top && + y < top + height / 2 + ) { + // upper left + quadrant += "1"; + v = table[0]; + if (typeof v == "number") { + break; + } + table = v; + width = width / 2; + height = height / 2; + continue; + } + if ( + x >= left + width / 2 && + x < left + width && + y >= top && + y < top + height / 2 + ) { + // upper right + quadrant += "2"; + v = table[1]; + if (typeof v == "number") { + break; + } + table = v; + width = width / 2; + left = left + width; + height = height / 2; + continue; + } + if ( + x >= left + width / 2 && + x < left + width && + y >= top + height / 2 && + y < top + height + ) { + // bottom right + quadrant += "3"; + v = table[2]; + if (typeof v == "number") { + break; + } + table = v; + width = width / 2; + left = left + width; + height = height / 2; + top = top + height; + continue; + } + if ( + x >= left && + x < left + width / 2 && + y >= top + height / 2 && + y < top + height + ) { + // bottom right + quadrant += "4"; + v = table[3]; + if (typeof v == "number") { + break; + } + table = v; + width = width / 2; + height = height / 2; + top = top + height; + continue; + } + return 0; // didn't match + } + + //addmsg((x-divWithMouseHit.offsetLeft)+","+(y-divWithMouseHit.offsetTop)+"="+quadrant+" "+v); + return v; + }; + + SocialCalc.CellHandlesHoverTimeout = function () { + editor = SocialCalc.Keyboard.focusTable; // get TableEditor doing keyboard stuff + if (!editor) return true; // we're not handling it -- let browser do default + var cellhandles = editor.cellhandles; + if (cellhandles.timer) { + window.clearTimeout(cellhandles.timer); + cellhandles.timer = null; + } + if (cellhandles.tooltipstimer) { + window.clearTimeout(cellhandles.tooltipstimer); + cellhandles.tooltipstimer = null; + } + editor.cellhandles.ShowCellHandles(true, false); // hide move handles + }; + + SocialCalc.CellHandlesTooltipsTimeout = function () { + editor = SocialCalc.Keyboard.focusTable; // get TableEditor doing keyboard stuff + if (!editor) return true; // we're not handling it -- let browser do default + var cellhandles = editor.cellhandles; + if (cellhandles.tooltipstimer) { + window.clearTimeout(cellhandles.tooltipstimer); + cellhandles.tooltipstimer = null; + } + + var whichhandle = cellhandles.tooltipswhichhandle; + if (whichhandle == 0) { + // off of active part of palette + SocialCalc.CellHandlesHoverTimeout(); + return; + } + if (whichhandle == -3) { + cellhandles.dragtooltip.innerHTML = scc.s_CHfillAllTooltip; + } else if (whichhandle == 3) { + cellhandles.dragtooltip.innerHTML = scc.s_CHfillContentsTooltip; + } else if (whichhandle == -2) { + cellhandles.dragtooltip.innerHTML = scc.s_CHmovePasteAllTooltip; + } else if (whichhandle == -4) { + cellhandles.dragtooltip.innerHTML = scc.s_CHmoveInsertAllTooltip; + } else if (whichhandle == 2) { + cellhandles.dragtooltip.innerHTML = scc.s_CHmovePasteContentsTooltip; + } else if (whichhandle == 4) { + cellhandles.dragtooltip.innerHTML = scc.s_CHmoveInsertContentsTooltip; + } else { + cellhandles.dragtooltip.innerHTML = " "; + cellhandles.dragtooltip.style.display = "none"; + return; + } + + cellhandles.dragtooltip.style.display = "block"; + }; + + SocialCalc.CellHandlesMouseDown = function (e) { + var scc = SocialCalc.Constants; + var editor, result, coord, textarea, wval, range; + + var event = e || window.event; + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + var mouseinfo = SocialCalc.EditorMouseInfo; + + editor = SocialCalc.Keyboard.focusTable; // get TableEditor doing keyboard stuff + if (!editor) return true; // we're not handling it -- let browser do default + + if (editor.busy) return; // don't do anything when busy (is this correct?) + + var cellhandles = editor.cellhandles; + + cellhandles.movedmouse = false; // detect no-op + + if (cellhandles.timer) { + // cancel timer + window.clearTimeout(cellhandles.timer); + cellhandles.timer = null; + } + if (cellhandles.tooltipstimer) { + window.clearTimeout(cellhandles.tooltipstimer); + cellhandles.tooltipstimer = null; + } + cellhandles.dragtooltip.innerHTML = " "; + cellhandles.dragtooltip.style.display = "none"; + + range = editor.range; + + var whichhandle = SocialCalc.SegmentDivHit( + [scc.CH_radius1, scc.CH_radius2], + editor.cellhandles.dragpalette, + clientX, + clientY + ); + if (whichhandle == 1 || whichhandle == -1 || whichhandle == 0) { + cellhandles.ShowCellHandles(true, false); // hide move handles + return; + } + + mouseinfo.ignore = true; // stop other code from looking at the mouse + + if (whichhandle == -3) { + cellhandles.dragtype = "Fill"; + // mouseinfo.element = editor.cellhandles.fillhandle; + cellhandles.noCursorSuffix = false; + } else if (whichhandle == 3) { + cellhandles.dragtype = "FillC"; + // mouseinfo.element = editor.cellhandles.fillhandle; + cellhandles.noCursorSuffix = false; + } else if (whichhandle == -2) { + cellhandles.dragtype = "Move"; + // mouseinfo.element = editor.cellhandles.movehandle1; + cellhandles.noCursorSuffix = true; + } else if (whichhandle == -4) { + cellhandles.dragtype = "MoveI"; + // mouseinfo.element = editor.cellhandles.movehandle2; + cellhandles.noCursorSuffix = false; + } else if (whichhandle == 2) { + cellhandles.dragtype = "MoveC"; + // mouseinfo.element = editor.cellhandles.movehandle1; + cellhandles.noCursorSuffix = true; + } else if (whichhandle == 4) { + cellhandles.dragtype = "MoveIC"; + // mouseinfo.element = editor.cellhandles.movehandle2; + cellhandles.noCursorSuffix = false; + } + + cellhandles.filltype = null; + + switch (cellhandles.dragtype) { + case "Fill": + case "FillC": + if (!range.hasrange) { + editor.RangeAnchor(); + } + break; + + case "Move": + case "MoveI": + case "MoveC": + case "MoveIC": + if (!range.hasrange) { + editor.RangeAnchor(); + } + editor.range2.top = editor.range.top; + editor.range2.right = editor.range.right; + editor.range2.bottom = editor.range.bottom; + editor.range2.left = editor.range.left; + editor.range2.hasrange = true; + editor.RangeRemove(); + break; + + default: + return; // not for us + } + + cellhandles.fillinghandle.style.left = clientX + "px"; + cellhandles.fillinghandle.style.top = clientY - 17 + "px"; + cellhandles.fillinghandle.innerHTML = + scc.s_CHindicatorOperationLookup[cellhandles.dragtype] + + (scc.s_CHindicatorDirectionLookup[editor.cellhandles.filltype] || ""); + cellhandles.fillinghandle.style.display = "block"; + + cellhandles.ShowCellHandles(true, false); // hide move handles + cellhandles.mouseDown = true; + + mouseinfo.editor = editor; // remember for later + + coord = editor.ecell.coord; // start with cell with handles + + cellhandles.startingcoord = coord; + cellhandles.startingX = clientX; + cellhandles.startingY = clientY; + + mouseinfo.mouselastcoord = coord; + + SocialCalc.KeyboardSetFocus(editor); + + if (document.addEventListener) { + // DOM Level 2 -- Firefox, et al + document.addEventListener( + "mousemove", + SocialCalc.CellHandlesMouseMove, + true + ); // capture everywhere + document.addEventListener("mouseup", SocialCalc.CellHandlesMouseUp, true); // capture everywhere + } else if (cellhandles.draghandle.attachEvent) { + // IE 5+ + cellhandles.draghandle.setCapture(); + cellhandles.draghandle.attachEvent( + "onmousemove", + SocialCalc.CellHandlesMouseMove + ); + cellhandles.draghandle.attachEvent( + "onmouseup", + SocialCalc.CellHandlesMouseUp + ); + cellhandles.draghandle.attachEvent( + "onlosecapture", + SocialCalc.CellHandlesMouseUp + ); + } + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + return; + }; + + SocialCalc.CellHandlesMouseMove = function (e) { + var scc = SocialCalc.Constants; + var editor, element, result, coord, now, textarea, sheetobj, cellobj, wval; + var crstart, crend, cr, c, r; + + var event = e || window.event; + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + var mouseinfo = SocialCalc.EditorMouseInfo; + editor = mouseinfo.editor; + if (!editor) return; // not us, ignore + var cellhandles = editor.cellhandles; + + element = mouseinfo.element; + + result = SocialCalc.GridMousePosition(editor, clientX, clientY); // get cell with move + + if (!result) return; + + if (result && !result.coord) { + SocialCalc.SetDragAutoRepeat( + editor, + result, + SocialCalc.CellHandlesDragAutoRepeat + ); + return; + } + + SocialCalc.SetDragAutoRepeat(editor, null); // stop repeating if it was + + if (!result.coord) return; + + crstart = SocialCalc.coordToCr(editor.cellhandles.startingcoord); + crend = SocialCalc.coordToCr(result.coord); + + cellhandles.movedmouse = true; // did move, so not no-op + + switch (cellhandles.dragtype) { + case "Fill": + case "FillC": + if (result.coord == cellhandles.startingcoord) { + // reset when come back + cellhandles.filltype = null; + cellhandles.startingX = clientX; + cellhandles.startingY = clientY; + } else { + if (cellhandles.filltype) { + // moving and have already determined filltype + if (cellhandles.filltype == "Down") { + // coerse to that + crend.col = crstart.col; + if (crend.row < crstart.row) crend.row = crstart.row; + } else { + crend.row = crstart.row; + if (crend.col < crstart.col) crend.col = crstart.col; + } + } else { + if (Math.abs(clientY - cellhandles.startingY) > 10) { + cellhandles.filltype = "Down"; + } else if (Math.abs(clientX - cellhandles.startingX) > 10) { + cellhandles.filltype = "Right"; + } + crend.col = crstart.col; // until decide, leave it at start + crend.row = crstart.row; + } + } + result.coord = SocialCalc.crToCoord(crend.col, crend.row); + if (result.coord != mouseinfo.mouselastcoord) { + editor.MoveECell(result.coord); + editor.RangeExtend(); + } + break; + + case "Move": + case "MoveC": + if (result.coord != mouseinfo.mouselastcoord) { + editor.MoveECell(result.coord); + c = editor.range2.right - editor.range2.left + result.col; + r = editor.range2.bottom - editor.range2.top + result.row; + editor.RangeAnchor(SocialCalc.crToCoord(c, r)); + editor.RangeExtend(); + } + break; + + case "MoveI": + case "MoveIC": + if (result.coord == cellhandles.startingcoord) { + // reset when come back + cellhandles.filltype = null; + cellhandles.startingX = clientX; + cellhandles.startingY = clientY; + } else { + if (cellhandles.filltype) { + // moving and have already determined filltype + if (cellhandles.filltype == "Vertical") { + // coerse to that + crend.col = editor.range2.left; + if ( + crend.row >= editor.range2.top && + crend.row <= editor.range2.bottom + 1 + ) + crend.row = editor.range2.bottom + 2; + } else { + crend.row = editor.range2.top; + if ( + crend.col >= editor.range2.left && + crend.col <= editor.range2.right + 1 + ) + crend.col = editor.range2.right + 2; + } + } else { + if (Math.abs(clientY - cellhandles.startingY) > 10) { + cellhandles.filltype = "Vertical"; + } else if (Math.abs(clientX - cellhandles.startingX) > 10) { + cellhandles.filltype = "Horizontal"; + } + crend.col = crstart.col; // until decide, leave it at start + crend.row = crstart.row; + } + } + result.coord = SocialCalc.crToCoord(crend.col, crend.row); + if (result.coord != mouseinfo.mouselastcoord) { + editor.MoveECell(result.coord); + if (!cellhandles.filltype) { + // no fill type + editor.RangeRemove(); + } else { + c = editor.range2.right - editor.range2.left + crend.col; + r = editor.range2.bottom - editor.range2.top + crend.row; + editor.RangeAnchor(SocialCalc.crToCoord(c, r)); + editor.RangeExtend(); + } + } + break; + } + + cellhandles.fillinghandle.style.left = clientX + "px"; + cellhandles.fillinghandle.style.top = clientY - 17 + "px"; + cellhandles.fillinghandle.innerHTML = + scc.s_CHindicatorOperationLookup[cellhandles.dragtype] + + (scc.s_CHindicatorDirectionLookup[editor.cellhandles.filltype] || ""); + cellhandles.fillinghandle.style.display = "block"; + + mouseinfo.mouselastcoord = result.coord; + + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + return; + }; + + SocialCalc.CellHandlesDragAutoRepeat = function (coord, direction) { + var mouseinfo = SocialCalc.EditorMouseInfo; + var editor = mouseinfo.editor; + if (!editor) return; // not us, ignore + var cellhandles = editor.cellhandles; + + var crstart = SocialCalc.coordToCr(editor.cellhandles.startingcoord); + var crend = SocialCalc.coordToCr(coord); + + var newcoord, c, r; + + var vscroll = 0; + var hscroll = 0; + + if (direction == "left") hscroll = -1; + else if (direction == "right") hscroll = 1; + else if (direction == "up") vscroll = -1; + else if (direction == "down") vscroll = 1; + editor.ScrollRelativeBoth(vscroll, hscroll); + + switch (cellhandles.dragtype) { + case "Fill": + case "FillC": + if (cellhandles.filltype) { + // moving and have already determined filltype + if (cellhandles.filltype == "Down") { + // coerse to that + crend.col = crstart.col; + if (crend.row < crstart.row) crend.row = crstart.row; + } else { + crend.row = crstart.row; + if (crend.col < crstart.col) crend.col = crstart.col; + } + } else { + crend.col = crstart.col; // until decide, leave it at start + crend.row = crstart.row; + } + + newcoord = SocialCalc.crToCoord(crend.col, crend.row); + if (newcoord != mouseinfo.mouselastcoord) { + editor.MoveECell(coord); + editor.RangeExtend(); + } + break; + + case "Move": + case "MoveC": + if (coord != mouseinfo.mouselastcoord) { + editor.MoveECell(coord); + c = editor.range2.right - editor.range2.left + editor.ecell.col; + r = editor.range2.bottom - editor.range2.top + editor.ecell.row; + editor.RangeAnchor(SocialCalc.crToCoord(c, r)); + editor.RangeExtend(); + } + break; + + case "MoveI": + case "MoveIC": + if (cellhandles.filltype) { + // moving and have already determined filltype + if (cellhandles.filltype == "Vertical") { + // coerse to that + crend.col = editor.range2.left; + if ( + crend.row >= editor.range2.top && + crend.row <= editor.range2.bottom + 1 + ) + crend.row = editor.range2.bottom + 2; + } else { + crend.row = editor.range2.top; + if ( + crend.col >= editor.range2.left && + crend.col <= editor.range2.right + 1 + ) + crend.col = editor.range2.right + 2; + } + } else { + crend.col = crstart.col; // until decide, leave it at start + crend.row = crstart.row; + } + + newcoord = SocialCalc.crToCoord(crend.col, crend.row); + if (newcoord != mouseinfo.mouselastcoord) { + editor.MoveECell(newcoord); + c = editor.range2.right - editor.range2.left + crend.col; + r = editor.range2.bottom - editor.range2.top + crend.row; + editor.RangeAnchor(SocialCalc.crToCoord(c, r)); + editor.RangeExtend(); + } + break; + } + + mouseinfo.mouselastcoord = newcoord; + }; + + SocialCalc.CellHandlesMouseUp = function (e) { + var editor, + element, + result, + coord, + now, + textarea, + sheetobj, + cellobj, + wval, + cstr, + cmdtype, + cmdtype2; + var crstart, crend; + var sizec, sizer, deltac, deltar; + + var event = e || window.event; + + var viewport = SocialCalc.GetViewportInfo(); + var clientX = event.clientX + viewport.horizontalScroll; + var clientY = event.clientY + viewport.verticalScroll; + + var mouseinfo = SocialCalc.EditorMouseInfo; + editor = mouseinfo.editor; + if (!editor) return; // not us, ignore + var cellhandles = editor.cellhandles; + + element = mouseinfo.element; + + mouseinfo.ignore = false; + + result = SocialCalc.GridMousePosition(editor, clientX, clientY); // get cell with up + + SocialCalc.SetDragAutoRepeat(editor, null); // stop repeating if it was + + cellhandles.mouseDown = false; + cellhandles.noCursorSuffix = false; + + cellhandles.fillinghandle.style.display = "none"; + + if (!result) result = {}; + if (!result.coord) result.coord = editor.ecell.coord; + + switch (cellhandles.dragtype) { + case "Fill": + case "Move": + case "MoveI": + cmdtype2 = " all"; + break; + case "FillC": + case "MoveC": + case "MoveIC": + cmdtype2 = " formulas"; + break; + } + + if (!cellhandles.movedmouse) { + // didn't move: just leave one cell selected + cellhandles.dragtype = "Nothing"; + } + + switch (cellhandles.dragtype) { + case "Nothing": + editor.Range2Remove(); + editor.RangeRemove(); + break; + + case "Fill": + case "FillC": + crstart = SocialCalc.coordToCr(cellhandles.startingcoord); + crend = SocialCalc.coordToCr(result.coord); + if (cellhandles.filltype) { + if (cellhandles.filltype == "Down") { + crend.col = crstart.col; + } else { + crend.row = crstart.row; + } + } + result.coord = SocialCalc.crToCoord(crend.col, crend.row); + + editor.MoveECell(result.coord); + editor.RangeExtend(); + + if (editor.cellhandles.filltype == "Right") { + cmdtype = "right"; + } else { + cmdtype = "down"; + } + cstr = + "fill" + + cmdtype + + " " + + SocialCalc.crToCoord(editor.range.left, editor.range.top) + + ":" + + SocialCalc.crToCoord(editor.range.right, editor.range.bottom) + + cmdtype2; + editor.EditorScheduleSheetCommands(cstr, true, false); + break; + + case "Move": + case "MoveC": + editor.context.cursorsuffix = ""; + cstr = + "movepaste " + + SocialCalc.crToCoord(editor.range2.left, editor.range2.top) + + ":" + + SocialCalc.crToCoord(editor.range2.right, editor.range2.bottom) + + " " + + editor.ecell.coord + + cmdtype2; + editor.EditorScheduleSheetCommands(cstr, true, false); + editor.Range2Remove(); + + break; + + case "MoveI": + case "MoveIC": + editor.context.cursorsuffix = ""; + sizec = editor.range2.right - editor.range2.left; + sizer = editor.range2.bottom - editor.range2.top; + deltac = editor.ecell.col - editor.range2.left; + deltar = editor.ecell.row - editor.range2.top; + cstr = + "moveinsert " + + SocialCalc.crToCoord(editor.range2.left, editor.range2.top) + + ":" + + SocialCalc.crToCoord(editor.range2.right, editor.range2.bottom) + + " " + + editor.ecell.coord + + cmdtype2; + editor.EditorScheduleSheetCommands(cstr, true, false); + editor.Range2Remove(); + editor.RangeRemove(); + if (editor.cellhandles.filltype == " Horizontal" && deltac > 0) { + editor.MoveECell( + SocialCalc.crToCoord(editor.ecell.col - sizec - 1, editor.ecell.row) + ); + } else if (editor.cellhandles.filltype == " Vertical" && deltar > 0) { + editor.MoveECell( + SocialCalc.crToCoord(editor.ecell.col, editor.ecell.row - sizer - 1) + ); + } + editor.RangeAnchor( + SocialCalc.crToCoord( + editor.ecell.col + sizec, + editor.ecell.row + sizer + ) + ); + editor.RangeExtend(); + + break; + } + + if (event.stopPropagation) event.stopPropagation(); // DOM Level 2 + else event.cancelBubble = true; // IE 5+ + if (event.preventDefault) event.preventDefault(); // DOM Level 2 + else event.returnValue = false; // IE 5+ + + if (document.removeEventListener) { + // DOM Level 2 + document.removeEventListener( + "mousemove", + SocialCalc.CellHandlesMouseMove, + true + ); + document.removeEventListener( + "mouseup", + SocialCalc.CellHandlesMouseUp, + true + ); + } else if (cellhandles.draghandle.detachEvent) { + // IE + cellhandles.draghandle.detachEvent( + "onlosecapture", + SocialCalc.CellHandlesMouseUp + ); + cellhandles.draghandle.detachEvent( + "onmouseup", + SocialCalc.CellHandlesMouseUp + ); + cellhandles.draghandle.detachEvent( + "onmousemove", + SocialCalc.CellHandlesMouseMove + ); + cellhandles.draghandle.releaseCapture(); + } + + mouseinfo.editor = null; + + return false; + }; + + // ************************************* + // + // TableControl class: + // + // This class deals with the horizontal and verical scrollbars and pane sliders. + // + // +--------------+ + // | Endcap | + // +- - - - - - - + + // | | + // +--------------+ + // | Pane Slider | + // +--------------+ + // | | + // | Less Button | + // | | + // +--------------+ + // | Scroll Area | + // | | + // | | + // +--------------+ + // | Thumb | + // +--------------+ + // | | + // +--------------+ + // | | + // | More Button | + // | | + // +--------------+ + // + // ************************************* + + SocialCalc.TableControl = function (editor, vertical, size) { + var scc = SocialCalc.Constants; + + this.editor = editor; // the TableEditor this belongs to + + this.vertical = vertical; // true if vertical control, false if horizontal + this.size = size; // length in pixels + + this.main = null; // main element containing all the others + this.endcap = null; // the area at the top/left between the end and the pane slider + this.paneslider = null; // the slider to adjust the pane split + this.lessbutton = null; // the top/left scroll button + this.morebutton = null; // the bottom/right scroll button + this.scrollarea = null; // the area between the scroll buttons + this.thumb = null; // the sliding thing in the scrollarea + + // computed position values: + + this.controlborder = null; // left or top screen position for vertical or horizontal control + this.endcapstart = null; // top or left screen position for vertical or horizontal control + this.panesliderstart = null; + this.lessbuttonstart = null; + this.morebuttonstart = null; + this.scrollareastart = null; + this.scrollareaend = null; + this.scrollareasize = null; + this.thumbpos = null; + + // constants: + + this.controlthickness = scc.defaultTableControlThickness; // other dimension of complete control in pixels + this.sliderthickness = scc.defaultTCSliderThickness; + this.buttonthickness = scc.defaultTCButtonThickness; + this.thumbthickness = scc.defaultTCThumbThickness; + this.minscrollingpanesize = + this.buttonthickness + this.buttonthickness + this.thumbthickness + 20; // the 20 is to leave a little space + }; + + // Methods: + + SocialCalc.TableControl.prototype.CreateTableControl = function () { + return SocialCalc.CreateTableControl(this); + }; + SocialCalc.TableControl.prototype.PositionTableControlElements = function () { + SocialCalc.PositionTableControlElements(this); + }; + SocialCalc.TableControl.prototype.ComputeTableControlPositions = function () { + SocialCalc.ComputeTableControlPositions(this); + }; + + // Functions: + + SocialCalc.CreateTableControl = function (control) { + var s, functions, params; + var AssignID = SocialCalc.AssignID; + var setStyles = SocialCalc.setStyles; + var scc = SocialCalc.Constants; + var TooltipRegister = function (element, etype, vh) { + if (scc["s_" + etype + "Tooltip" + vh]) { + SocialCalc.TooltipRegister( + element, + scc["s_" + etype + "Tooltip" + vh], + null + ); + } + }; + + var imageprefix = control.editor.imageprefix; + var vh = control.vertical ? "v" : "h"; + + control.main = document.createElement("div"); + s = control.main.style; + s.height = + (control.vertical ? control.size : control.controlthickness) + "px"; + s.width = + (control.vertical ? control.controlthickness : control.size) + "px"; + s.zIndex = 0; + setStyles(control.main, scc.TCmainStyle); + s.backgroundImage = "url(" + imageprefix + "main-" + vh + ".gif)"; + if (scc.TCmainClass) control.main.className = scc.TCmainClass; + + control.main.style.display = "none"; // wait for layout + + control.endcap = document.createElement("div"); + s = control.endcap.style; + s.height = control.controlthickness + "px"; + s.width = control.controlthickness + "px"; + s.zIndex = 1; + s.overflow = "hidden"; // IE will make the DIV at least font-size height...so use this + s.position = "absolute"; + setStyles(control.endcap, scc.TCendcapStyle); + s.backgroundImage = "url(" + imageprefix + "endcap-" + vh + ".gif)"; + if (scc.TCendcapClass) control.endcap.className = scc.TCendcapClass; + AssignID(control.editor, control.endcap, "endcap" + vh); + + control.main.appendChild(control.endcap); + + control.paneslider = document.createElement("div"); + s = control.paneslider.style; + s.height = + (control.vertical ? control.sliderthickness : control.controlthickness) + + "px"; + s.overflow = "hidden"; // IE will make the DIV at least font-size height...so use this + s.width = + (control.vertical ? control.controlthickness : control.sliderthickness) + + "px"; + s.position = "absolute"; + s[control.vertical ? "top" : "left"] = "4px"; + s.zIndex = 3; + setStyles(control.paneslider, scc.TCpanesliderStyle); + s.backgroundImage = "url(" + imageprefix + "paneslider-" + vh + ".gif)"; + if (scc.TCpanesliderClass) + control.paneslider.className = scc.TCpanesliderClass; + AssignID(control.editor, control.paneslider, "paneslider" + vh); + TooltipRegister(control.paneslider, "paneslider", vh); + + functions = { + MouseDown: SocialCalc.TCPSDragFunctionStart, + MouseMove: SocialCalc.TCPSDragFunctionMove, + MouseUp: SocialCalc.TCPSDragFunctionStop, + Disabled: function () { + return control.editor.busy; + }, + }; + + functions.control = control; // make sure this is there + + SocialCalc.DragRegister( + control.paneslider, + control.vertical, + !control.vertical, + functions + ); + + control.main.appendChild(control.paneslider); + + control.lessbutton = document.createElement("div"); + s = control.lessbutton.style; + s.height = + (control.vertical ? control.buttonthickness : control.controlthickness) + + "px"; + s.width = + (control.vertical ? control.controlthickness : control.buttonthickness) + + "px"; + s.zIndex = 2; + s.overflow = "hidden"; // IE will make the DIV at least font-size height...so use this + s.position = "absolute"; + setStyles(control.lessbutton, scc.TClessbuttonStyle); + s.backgroundImage = "url(" + imageprefix + "less-" + vh + "n.gif)"; + if (scc.TClessbuttonClass) + control.lessbutton.className = scc.TClessbuttonClass; + AssignID(control.editor, control.lessbutton, "lessbutton" + vh); + + params = { + repeatwait: scc.TClessbuttonRepeatWait, + repeatinterval: scc.TClessbuttonRepeatInterval, + normalstyle: + "backgroundImage:url(" + imageprefix + "less-" + vh + "n.gif);", + downstyle: + "backgroundImage:url(" + imageprefix + "less-" + vh + "d.gif);", + hoverstyle: + "backgroundImage:url(" + imageprefix + "less-" + vh + "h.gif);", + }; + functions = { + MouseDown: function () { + if (!control.editor.busy) + control.editor.ScrollRelative(control.vertical, -1); + }, + Repeat: function () { + if (!control.editor.busy) + control.editor.ScrollRelative(control.vertical, -1); + }, + Disabled: function () { + return control.editor.busy; + }, + }; + + SocialCalc.ButtonRegister(control.lessbutton, params, functions); + + control.main.appendChild(control.lessbutton); + + control.morebutton = document.createElement("div"); + s = control.morebutton.style; + s.height = + (control.vertical ? control.buttonthickness : control.controlthickness) + + "px"; + s.width = + (control.vertical ? control.controlthickness : control.buttonthickness) + + "px"; + s.zIndex = 2; + s.overflow = "hidden"; // IE will make the DIV at least font-size height...so use this + s.position = "absolute"; + setStyles(control.morebutton, scc.TCmorebuttonStyle); + s.backgroundImage = "url(" + imageprefix + "more-" + vh + "n.gif)"; + if (scc.TCmorebuttonClass) + control.morebutton.className = scc.TCmorebuttonClass; + AssignID(control.editor, control.morebutton, "morebutton" + vh); + + params = { + repeatwait: scc.TCmorebuttonRepeatWait, + repeatinterval: scc.TCmorebuttonRepeatInterval, + normalstyle: + "backgroundImage:url(" + imageprefix + "more-" + vh + "n.gif);", + downstyle: + "backgroundImage:url(" + imageprefix + "more-" + vh + "d.gif);", + hoverstyle: + "backgroundImage:url(" + imageprefix + "more-" + vh + "h.gif);", + }; + functions = { + MouseDown: function () { + if (!control.editor.busy) + control.editor.ScrollRelative(control.vertical, +1); + }, + Repeat: function () { + if (!control.editor.busy) + control.editor.ScrollRelative(control.vertical, +1); + }, + Disabled: function () { + return control.editor.busy; + }, + }; + + SocialCalc.ButtonRegister(control.morebutton, params, functions); + + control.main.appendChild(control.morebutton); + + control.scrollarea = document.createElement("div"); + s = control.scrollarea.style; + s.height = control.controlthickness + "px"; + s.width = control.controlthickness + "px"; + s.zIndex = 1; + s.overflow = "hidden"; // IE will make the DIV at least font-size height...so use this + s.position = "absolute"; + setStyles(control.scrollarea, scc.TCscrollareaStyle); + s.backgroundImage = "url(" + imageprefix + "scrollarea-" + vh + ".gif)"; + if (scc.TCscrollareaClass) + control.scrollarea.className = scc.TCscrollareaClass; + AssignID(control.editor, control.scrollarea, "scrollarea" + vh); + + params = { + repeatwait: scc.TCscrollareaRepeatWait, + repeatinterval: scc.TCscrollareaRepeatWait, + }; + functions = { + MouseDown: SocialCalc.ScrollAreaClick, + Repeat: SocialCalc.ScrollAreaClick, + Disabled: function () { + return control.editor.busy; + }, + }; + functions.control = control; + + SocialCalc.ButtonRegister(control.scrollarea, params, functions); + + control.main.appendChild(control.scrollarea); + + control.thumb = document.createElement("div"); + s = control.thumb.style; + s.height = + (control.vertical ? control.thumbthickness : control.controlthickness) + + "px"; + s.width = + (control.vertical ? control.controlthickness : control.thumbthickness) + + "px"; + s.zIndex = 2; + s.overflow = "hidden"; // IE will make the DIV at least font-size height...so use this + s.position = "absolute"; + setStyles(control.thumb, scc.TCthumbStyle); + control.thumb.style.backgroundImage = + "url(" + imageprefix + "thumb-" + vh + "n.gif)"; + if (scc.TCthumbClass) control.thumb.className = scc.TCthumbClass; + AssignID(control.editor, control.thumb, "thumb" + vh); + + functions = { + MouseDown: SocialCalc.TCTDragFunctionStart, + MouseMove: SocialCalc.TCTDragFunctionMove, + MouseUp: SocialCalc.TCTDragFunctionStop, + Disabled: function () { + return control.editor.busy; + }, + }; + functions.control = control; // make sure this is there + SocialCalc.DragRegister( + control.thumb, + control.vertical, + !control.vertical, + functions + ); + + params = { + normalstyle: + "backgroundImage:url(" + imageprefix + "thumb-" + vh + "n.gif)", + name: "Thumb", + downstyle: + "backgroundImage:url(" + imageprefix + "thumb-" + vh + "d.gif)", + hoverstyle: + "backgroundImage:url(" + imageprefix + "thumb-" + vh + "h.gif)", + }; + SocialCalc.ButtonRegister(control.thumb, params, null); // give it button-like visual behavior + + control.main.appendChild(control.thumb); + + return control.main; + }; + + // + // ScrollAreaClick - Button function to process pageup/down clicks + // + + SocialCalc.ScrollAreaClick = function (e, buttoninfo, bobj) { + var control = bobj.functionobj.control; + var bposition = SocialCalc.GetElementPosition(bobj.element); + var clickpos = control.vertical ? buttoninfo.clientY : buttoninfo.clientX; + if (control.editor.busy) { + // ignore if busy - wait for next repeat + return; + } + control.editor.PageRelative( + control.vertical, + clickpos > control.thumbpos ? 1 : -1 + ); + + return; + }; + + // + // PositionTableControlElements + // + + SocialCalc.PositionTableControlElements = function (control) { + var border, realend, thumbpos; + + var editor = control.editor; + + if (control.vertical) { + border = control.controlborder + "px"; + control.endcap.style.top = control.endcapstart + "px"; + control.endcap.style.left = border; + control.paneslider.style.top = control.panesliderstart + "px"; + control.paneslider.style.left = border; + control.lessbutton.style.top = control.lessbuttonstart + "px"; + control.lessbutton.style.left = border; + control.morebutton.style.top = control.morebuttonstart + "px"; + control.morebutton.style.left = border; + control.scrollarea.style.top = control.scrollareastart + "px"; + control.scrollarea.style.left = border; + control.scrollarea.style.height = control.scrollareasize + "px"; + realend = Math.max( + editor.context.sheetobj.attribs.lastrow, + editor.firstscrollingrow + 1 + ); + thumbpos = + ((editor.firstscrollingrow - (editor.lastnonscrollingrow + 1)) * + (control.scrollareasize - 3 * control.thumbthickness)) / + (realend - (editor.lastnonscrollingrow + 1)) + + control.scrollareastart - + 1; + thumbpos = Math.floor(thumbpos); + control.thumb.style.top = thumbpos + "px"; + control.thumb.style.left = border; + } else { + border = control.controlborder + "px"; + control.endcap.style.left = control.endcapstart + "px"; + control.endcap.style.top = border; + control.paneslider.style.left = control.panesliderstart + "px"; + control.paneslider.style.top = border; + control.lessbutton.style.left = control.lessbuttonstart + "px"; + control.lessbutton.style.top = border; + control.morebutton.style.left = control.morebuttonstart + "px"; + control.morebutton.style.top = border; + control.scrollarea.style.left = control.scrollareastart + "px"; + control.scrollarea.style.top = border; + control.scrollarea.style.width = control.scrollareasize + "px"; + realend = Math.max( + editor.context.sheetobj.attribs.lastcol, + editor.firstscrollingcol + 1 + ); + thumbpos = + ((editor.firstscrollingcol - (editor.lastnonscrollingcol + 1)) * + (control.scrollareasize - control.thumbthickness)) / + (realend - editor.lastnonscrollingcol) + + control.scrollareastart - + 1; + thumbpos = Math.floor(thumbpos); + control.thumb.style.left = thumbpos + "px"; + control.thumb.style.top = border; + } + control.thumbpos = thumbpos; + control.main.style.display = "block"; + }; + + // + // ComputeTableControlPositions + // + // This routine computes the screen positions and other values needed for laying out + // the table control elements. + // + + SocialCalc.ComputeTableControlPositions = function (control) { + var editor = control.editor; + + if (!editor.gridposition || !editor.headposition) + throw "Can't compute table control positions before editor positions"; + + if (control.vertical) { + control.controlborder = editor.gridposition.left + editor.tablewidth; // border=left position + control.endcapstart = editor.gridposition.top; // start=top position + control.panesliderstart = + editor.firstscrollingrowtop - control.sliderthickness; + control.lessbuttonstart = editor.firstscrollingrowtop - 1; + control.morebuttonstart = + editor.gridposition.top + editor.tableheight - control.buttonthickness; + control.scrollareastart = + editor.firstscrollingrowtop - 1 + control.buttonthickness; + control.scrollareaend = control.morebuttonstart - 1; + control.scrollareasize = + control.scrollareaend - control.scrollareastart + 1; + } else { + control.controlborder = editor.gridposition.top + editor.tableheight; // border=top position + control.endcapstart = editor.gridposition.left; // start=left position + control.panesliderstart = + editor.firstscrollingcolleft - control.sliderthickness; + control.lessbuttonstart = editor.firstscrollingcolleft - 1; + control.morebuttonstart = + editor.gridposition.left + editor.tablewidth - control.buttonthickness; + control.scrollareastart = + editor.firstscrollingcolleft - 1 + control.buttonthickness; + control.scrollareaend = control.morebuttonstart - 1; + control.scrollareasize = + control.scrollareaend - control.scrollareastart + 1; + } + }; + + ////// TCPS - TableControl Pan Slider methods + + // + // TCPSDragFunctionStart(event, draginfo, dobj) + // + // TableControlPaneSlider function for starting drag + // + + SocialCalc.TCPSDragFunctionStart = function (event, draginfo, dobj) { + var editor = dobj.functionobj.control.editor; + var scc = SocialCalc.Constants; + + SocialCalc.DragFunctionStart(event, draginfo, dobj); + + draginfo.trackingline = document.createElement("div"); + draginfo.trackingline.style.height = dobj.vertical + ? scc.TCPStrackinglineThickness + : editor.tableheight - + (editor.headposition.top - editor.gridposition.top) + + "px"; + draginfo.trackingline.style.width = dobj.vertical + ? editor.tablewidth - + (editor.headposition.left - editor.gridposition.left) + + "px" + : scc.TCPStrackinglineThickness; + draginfo.trackingline.style.backgroundImage = + "url(" + + editor.imageprefix + + "trackingline-" + + (dobj.vertical ? "v" : "h") + + ".gif)"; + if (scc.TCPStrackinglineClass) + draginfo.trackingline.className = scc.TCPStrackinglineClass; + SocialCalc.setStyles(draginfo.trackingline, scc.TCPStrackinglineStyle); + + if (dobj.vertical) { + row = SocialCalc.Lookup( + draginfo.clientY + dobj.functionobj.control.sliderthickness, + editor.rowpositions + ); + draginfo.trackingline.style.top = + (editor.rowpositions[row] || editor.headposition.top) + "px"; + draginfo.trackingline.style.left = editor.headposition.left + "px"; + if (editor.context.rowpanes.length - 1) { + // has 2 already + editor.context.SetRowPaneFirstLast( + 1, + editor.context.rowpanes[0].last + 1, + editor.context.rowpanes[0].last + 1 + ); + editor.FitToEditTable(); + editor.ScheduleRender(); + } + } else { + col = SocialCalc.Lookup( + draginfo.clientX + dobj.functionobj.control.sliderthickness, + editor.colpositions + ); + draginfo.trackingline.style.top = editor.headposition.top + "px"; + draginfo.trackingline.style.left = + (editor.colpositions[col] || editor.headposition.left) + "px"; + if (editor.context.colpanes.length - 1) { + // has 2 already + editor.context.SetColPaneFirstLast( + 1, + editor.context.colpanes[0].last + 1, + editor.context.colpanes[0].last + 1 + ); + editor.FitToEditTable(); + editor.ScheduleRender(); + } + } + + editor.griddiv.appendChild(draginfo.trackingline); + }; + + // + // TCPSDragFunctionMove(event, draginfo, dobj) + // + + SocialCalc.TCPSDragFunctionMove = function (event, draginfo, dobj) { + var row, col, max, min; + var control = dobj.functionobj.control; + var sliderthickness = control.sliderthickness; + var editor = control.editor; + + if (dobj.vertical) { + max = + control.morebuttonstart - + control.minscrollingpanesize - + draginfo.offsetY; // restrict movement + if (draginfo.clientY > max) draginfo.clientY = max; + min = editor.headposition.top - sliderthickness - draginfo.offsetY; + if (draginfo.clientY < min) draginfo.clientY = min; + + row = SocialCalc.Lookup( + draginfo.clientY + sliderthickness, + editor.rowpositions + ); + draginfo.trackingline.style.top = + (editor.rowpositions[row] || editor.headposition.top) + "px"; + } else { + max = + control.morebuttonstart - + control.minscrollingpanesize - + draginfo.offsetX; + if (draginfo.clientX > max) draginfo.clientX = max; + min = editor.headposition.left - sliderthickness - draginfo.offsetX; + if (draginfo.clientX < min) draginfo.clientX = min; + + col = SocialCalc.Lookup( + draginfo.clientX + sliderthickness, + editor.colpositions + ); + draginfo.trackingline.style.left = + (editor.colpositions[col] || editor.headposition.left) + "px"; + } + + SocialCalc.DragFunctionPosition(event, draginfo, dobj); + }; + + // + // TCPSDragFunctionStop(event, draginfo, dobj) + // + + SocialCalc.TCPSDragFunctionStop = function (event, draginfo, dobj) { + var row, col, max, min; + var control = dobj.functionobj.control; + var sliderthickness = control.sliderthickness; + var editor = control.editor; + + if (dobj.vertical) { + max = + control.morebuttonstart - + control.minscrollingpanesize - + draginfo.offsetY; // restrict movement + if (draginfo.clientY > max) draginfo.clientY = max; + min = editor.headposition.top - sliderthickness - draginfo.offsetY; + if (draginfo.clientY < min) draginfo.clientY = min; + + row = SocialCalc.Lookup( + draginfo.clientY + sliderthickness, + editor.rowpositions + ); + if (row > editor.context.sheetobj.attribs.lastrow) + row = editor.context.sheetobj.attribs.lastrow; // can't extend sheet here + if (!row || row <= editor.context.rowpanes[0].first) { + // set to no panes, leaving first pane settings + if (editor.context.rowpanes.length > 1) + editor.context.rowpanes.length = 1; + } else if (editor.context.rowpanes.length - 1) { + // has 2 already + if (!editor.timeout) { + // not waiting for position calc (so positions could be wrong) + editor.context.SetRowPaneFirstLast( + 0, + editor.context.rowpanes[0].first, + row - 1 + ); + editor.context.SetRowPaneFirstLast(1, row, row); + } + } else { + editor.context.SetRowPaneFirstLast( + 0, + editor.context.rowpanes[0].first, + row - 1 + ); + editor.context.SetRowPaneFirstLast(1, row, row); + } + } else { + max = + control.morebuttonstart - + control.minscrollingpanesize - + draginfo.offsetX; + if (draginfo.clientX > max) draginfo.clientX = max; + min = editor.headposition.left - sliderthickness - draginfo.offsetX; + if (draginfo.clientX < min) draginfo.clientX = min; + + col = SocialCalc.Lookup( + draginfo.clientX + sliderthickness, + editor.colpositions + ); + if (col > editor.context.sheetobj.attribs.lastcol) + col = editor.context.sheetobj.attribs.lastcol; // can't extend sheet here + if (!col || col <= editor.context.colpanes[0].first) { + // set to no panes, leaving first pane settings + if (editor.context.colpanes.length > 1) + editor.context.colpanes.length = 1; + } else if (editor.context.colpanes.length - 1) { + // has 2 already + if (!editor.timeout) { + // not waiting for position calc (so positions could be wrong) + editor.context.SetColPaneFirstLast( + 0, + editor.context.colpanes[0].first, + col - 1 + ); + editor.context.SetColPaneFirstLast(1, col, col); + } + } else { + editor.context.SetColPaneFirstLast( + 0, + editor.context.colpanes[0].first, + col - 1 + ); + editor.context.SetColPaneFirstLast(1, col, col); + } + } + + editor.FitToEditTable(); + + editor.griddiv.removeChild(draginfo.trackingline); + + editor.ScheduleRender(); + }; + + ////// TCT - TableControl Thumb methods + + //!!!! Note: Need to make start use same code as move/stop for determining row/col, since stop will set that + //!!!! Note: Need to make start/move/stop use positioning code that corresponds closer to + //!!!! ComputeTableControlPositions calculations. + + // + // TCTDragFunctionStart(event, draginfo, dobj) + // + // TableControlThumb function for starting drag + // + + SocialCalc.TCTDragFunctionStart = function (event, draginfo, dobj) { + var rowpane, colpane, row, col; + + var control = dobj.functionobj.control; + var editor = control.editor; + var scc = SocialCalc.Constants; + + SocialCalc.DragFunctionStart(event, draginfo, dobj); + + if (draginfo.thumbstatus) { + // get rid of old one if mouseup was out of window + if (draginfo.thumbstatus.rowmsgele) draginfo.thumbstatus.rowmsgele = null; + if (draginfo.thumbstatus.rowpreviewele) + draginfo.thumbstatus.rowpreviewele = null; + editor.toplevel.removeChild(draginfo.thumbstatus); + draginfo.thumbstatus = null; + } + + draginfo.thumbstatus = document.createElement("div"); + + if (dobj.vertical) { + if (scc.TCTDFSthumbstatusvClass) + draginfo.thumbstatus.className = scc.TCTDFSthumbstatusvClass; + SocialCalc.setStyles(draginfo.thumbstatus, scc.TCTDFSthumbstatusvStyle); + draginfo.thumbstatus.style.top = + draginfo.clientY + scc.TCTDFStopOffsetv + "px"; + draginfo.thumbstatus.style.left = + control.controlborder - 10 - editor.tablewidth / 2 + "px"; + draginfo.thumbstatus.style.width = editor.tablewidth / 2 + "px"; + + draginfo.thumbcontext = new SocialCalc.RenderContext( + editor.context.sheetobj + ); + draginfo.thumbcontext.showGrid = true; + draginfo.thumbcontext.rowpanes = [{ first: 1, last: 1 }]; + var pane = editor.context.colpanes[editor.context.colpanes.length - 1]; + draginfo.thumbcontext.colpanes = [{ first: pane.first, last: pane.last }]; + draginfo.thumbstatus.innerHTML = + '
msg
preview
'; + draginfo.thumbstatus.rowmsgele = + draginfo.thumbstatus.firstChild.firstChild.firstChild.firstChild.firstChild; + draginfo.thumbstatus.rowpreviewele = + draginfo.thumbstatus.firstChild.firstChild.firstChild.childNodes[1].firstChild; + editor.toplevel.appendChild(draginfo.thumbstatus); + SocialCalc.TCTDragFunctionRowSetStatus( + draginfo, + editor, + editor.firstscrollingrow || 1 + ); + } else { + if (scc.TCTDFSthumbstatushClass) + draginfo.thumbstatus.className = scc.TCTDFSthumbstatushClass; + SocialCalc.setStyles(draginfo.thumbstatus, scc.TCTDFSthumbstatushStyle); + draginfo.thumbstatus.style.top = + control.controlborder + scc.TCTDFStopOffseth + "px"; + draginfo.thumbstatus.style.left = + draginfo.clientX + scc.TCTDFSleftOffseth + "px"; + editor.toplevel.appendChild(draginfo.thumbstatus); + draginfo.thumbstatus.innerHTML = + scc.s_TCTDFthumbstatusPrefixh + + SocialCalc.rcColname(editor.firstscrollingcol); + } + }; + + // + // SocialCalc.TCTDragFunctionRowSetStatus(draginfo, editor, row) + // + // Render partial row + // + + SocialCalc.TCTDragFunctionRowSetStatus = function (draginfo, editor, row) { + var scc = SocialCalc.Constants; + var msg = scc.s_TCTDFthumbstatusPrefixv + row + " "; + + draginfo.thumbstatus.rowmsgele.innerHTML = msg; + + draginfo.thumbcontext.rowpanes = [{ first: row, last: row }]; + draginfo.thumbrowshown = row; + + var ele = draginfo.thumbcontext.RenderSheet( + draginfo.thumbstatus.rowpreviewele.firstChild, + { type: "html" } + ); + }; + + // + // TCTDragFunctionMove(event, draginfo, dobj) + // + + SocialCalc.TCTDragFunctionMove = function (event, draginfo, dobj) { + var first, msg; + var control = dobj.functionobj.control; + var thumbthickness = control.thumbthickness; + var editor = control.editor; + var scc = SocialCalc.Constants; + + if (dobj.vertical) { + if ( + draginfo.clientY > + control.scrollareaend - draginfo.offsetY - control.thumbthickness + 2 + ) + draginfo.clientY = + control.scrollareaend - draginfo.offsetY - control.thumbthickness + 2; + if (draginfo.clientY < control.scrollareastart - draginfo.offsetY - 1) + draginfo.clientY = control.scrollareastart - draginfo.offsetY - 1; + draginfo.thumbstatus.style.top = draginfo.clientY + "px"; + + first = + ((draginfo.clientY + draginfo.offsetY - control.scrollareastart + 1) / + (control.scrollareasize - control.thumbthickness)) * + (editor.context.sheetobj.attribs.lastrow - + editor.lastnonscrollingrow) + + editor.lastnonscrollingrow + + 1; + first = Math.floor(first); + if (first <= editor.lastnonscrollingrow) + first = editor.lastnonscrollingrow + 1; + if (first > editor.context.sheetobj.attribs.lastrow) + first = editor.context.sheetobj.attribs.lastrow; + // msg = scc.s_TCTDFthumbstatusPrefixv+first; + if (first != draginfo.thumbrowshown) { + SocialCalc.TCTDragFunctionRowSetStatus(draginfo, editor, first); + } + } else { + if ( + draginfo.clientX > + control.scrollareaend - draginfo.offsetX - control.thumbthickness + 2 + ) + draginfo.clientX = + control.scrollareaend - draginfo.offsetX - control.thumbthickness + 2; + if (draginfo.clientX < control.scrollareastart - draginfo.offsetX - 1) + draginfo.clientX = control.scrollareastart - draginfo.offsetX - 1; + draginfo.thumbstatus.style.left = draginfo.clientX + "px"; + + first = + ((draginfo.clientX + draginfo.offsetX - control.scrollareastart + 1) / + (control.scrollareasize - control.thumbthickness)) * + (editor.context.sheetobj.attribs.lastcol - + editor.lastnonscrollingcol) + + editor.lastnonscrollingcol + + 1; + first = Math.floor(first); + if (first <= editor.lastnonscrollingcol) + first = editor.lastnonscrollingcol + 1; + if (first > editor.context.sheetobj.attribs.lastcol) + first = editor.context.sheetobj.attribs.lastcol; + msg = scc.s_TCTDFthumbstatusPrefixh + SocialCalc.rcColname(first); + draginfo.thumbstatus.innerHTML = msg; + } + + SocialCalc.DragFunctionPosition(event, draginfo, dobj); + }; + + // + // TCTDragFunctionStop(event, draginfo, dobj) + // + + SocialCalc.TCTDragFunctionStop = function (event, draginfo, dobj) { + var first; + var control = dobj.functionobj.control; + var editor = control.editor; + + if (dobj.vertical) { + first = + ((draginfo.clientY + draginfo.offsetY - control.scrollareastart + 1) / + (control.scrollareasize - control.thumbthickness)) * + (editor.context.sheetobj.attribs.lastrow - + editor.lastnonscrollingrow) + + editor.lastnonscrollingrow + + 1; + first = Math.floor(first); + if (first <= editor.lastnonscrollingrow) + first = editor.lastnonscrollingrow + 1; + if (first > editor.context.sheetobj.attribs.lastrow) + first = editor.context.sheetobj.attribs.lastrow; + + editor.context.SetRowPaneFirstLast( + editor.context.rowpanes.length - 1, + first, + first + 1 + ); + } else { + first = + ((draginfo.clientX + draginfo.offsetX - control.scrollareastart + 1) / + (control.scrollareasize - control.thumbthickness)) * + (editor.context.sheetobj.attribs.lastcol - + editor.lastnonscrollingcol) + + editor.lastnonscrollingcol + + 1; + first = Math.floor(first); + if (first <= editor.lastnonscrollingcol) + first = editor.lastnonscrollingcol + 1; + if (first > editor.context.sheetobj.attribs.lastcol) + first = editor.context.sheetobj.attribs.lastcol; + + editor.context.SetColPaneFirstLast( + editor.context.colpanes.length - 1, + first, + first + 1 + ); + } + + editor.FitToEditTable(); + + if (draginfo.thumbstatus.rowmsgele) draginfo.thumbstatus.rowmsgele = null; + if (draginfo.thumbstatus.rowpreviewele) + draginfo.thumbstatus.rowpreviewele = null; + editor.toplevel.removeChild(draginfo.thumbstatus); + draginfo.thumbstatus = null; + + editor.ScheduleRender(); + }; + + // ************************************* + // + // Dragging functions: + // + // ************************************* + + SocialCalc.DragInfo = { + // There is only one of these -- no "new" is done. + // Only one dragging operation can be active at a time. + // The registeredElements array is used to decide which item to drag. + + // One item for each draggable thing, each an object with: + // .element, .vertical, .horizontal, .functionobj + + registeredElements: [], + + // Items used during a drag + + draggingElement: null, // item being processed (.element is the actual element) + startX: 0, + startY: 0, + startZ: 0, + clientX: 0, // modifyable version to restrict movement + clientY: 0, + offsetX: 0, + offsetY: 0, + horizontalScroll: 0, // retrieved at drag start + verticalScroll: 0, + }; + + // + // DragRegister(element, vertical, horizontal, functionobj) - make element draggable + // + // The functionobj defaults to moving the element contrained only by vertical and horizontal settings. + // + + SocialCalc.DragRegister = function ( + element, + vertical, + horizontal, + functionobj + ) { + var draginfo = SocialCalc.DragInfo; + + if (!functionobj) { + functionobj = { + MouseDown: SocialCalc.DragFunctionStart, + MouseMove: SocialCalc.DragFunctionPosition, + MouseUp: SocialCalc.DragFunctionPosition, + Disabled: null, + }; + } + + draginfo.registeredElements.push({ + element: element, + vertical: vertical, + horizontal: horizontal, + functionobj: functionobj, + }); + + if (element.addEventListener) { + // DOM Level 2 -- Firefox, et al + element.addEventListener("mousedown", SocialCalc.DragMouseDown, false); + } else if (element.attachEvent) { + // IE 5+ + element.attachEvent("onmousedown", SocialCalc.DragMouseDown); + } else { + // don't handle this + throw SocialCalc.Constants.s_BrowserNotSupported; + } + }; + + // + // DragUnregister(element) - remove object from list + // + + SocialCalc.DragUnregister = function (element) { + var draginfo = SocialCalc.DragInfo; + + var i; + + if (!element) return; + + for (i = 0; i < draginfo.registeredElements.length; i++) { + if (draginfo.registeredElements[i].element == element) { + draginfo.registeredElements.splice(i, 1); + if (element.removeEventListener) { + // DOM Level 2 -- Firefox, et al + element.removeEventListener( + "mousedown", + SocialCalc.DragMouseDown, + false + ); + } else { + // IE 5+ + element.detachEvent("onmousedown", SocialCalc.DragMouseDown); + } + return; + } + } + + return; // ignore if not in list + }; + + // + // DragMouseDown(event) + // + + SocialCalc.DragMouseDown = function (event) { + var e = event || window.event; + + var draginfo = SocialCalc.DragInfo; + + var dobj = SocialCalc.LookupElement( + e.target || e.srcElement, + draginfo.registeredElements + ); + if (!dobj) return; + + if (dobj && dobj.functionobj && dobj.functionobj.Disabled) { + if (dobj.functionobj.Disabled(e, draginfo, dobj)) { + return; + } + } + + draginfo.draggingElement = dobj; + + var viewportinfo = SocialCalc.GetViewportInfo(); + draginfo.horizontalScroll = viewportinfo.horizontalScroll; + draginfo.verticalScroll = viewportinfo.verticalScroll; + + draginfo.clientX = e.clientX + draginfo.horizontalScroll; // get document-relative coordinates + draginfo.clientY = e.clientY + draginfo.verticalScroll; + draginfo.startX = draginfo.clientX; + draginfo.startY = draginfo.clientY; + draginfo.startZ = dobj.element.style.zIndex; + draginfo.offsetX = 0; + draginfo.offsetY = 0; + + dobj.element.style.zIndex = "100"; + + // Event code from JavaScript, Flanagan, 5th Edition, pg. 422 + if (document.addEventListener) { + // DOM Level 2 -- Firefox, et al + document.addEventListener("mousemove", SocialCalc.DragMouseMove, true); // capture everywhere + document.addEventListener("mouseup", SocialCalc.DragMouseUp, true); + } else if (dobj.element.attachEvent) { + // IE 5+ + dobj.element.setCapture(); + dobj.element.attachEvent("onmousemove", SocialCalc.DragMouseMove); + dobj.element.attachEvent("onmouseup", SocialCalc.DragMouseUp); + dobj.element.attachEvent("onlosecapture", SocialCalc.DragMouseUp); + } + if (e.stopPropagation) e.stopPropagation(); // DOM Level 2 + else e.cancelBubble = true; // IE 5+ + if (e.preventDefault) e.preventDefault(); // DOM Level 2 + else e.returnValue = false; // IE 5+ + + if (dobj && dobj.functionobj && dobj.functionobj.MouseDown) + dobj.functionobj.MouseDown(e, draginfo, dobj); + + return false; + }; + + // + // DragMouseMove(event) + // + + SocialCalc.DragMouseMove = function (event) { + var e = event || window.event; + + var draginfo = SocialCalc.DragInfo; + draginfo.clientX = e.clientX + draginfo.horizontalScroll; + draginfo.clientY = e.clientY + draginfo.verticalScroll; + + var dobj = draginfo.draggingElement; + + if (e.stopPropagation) e.stopPropagation(); // DOM Level 2 + else e.cancelBubble = true; // IE 5+ + + if (dobj && dobj.functionobj && dobj.functionobj.MouseMove) + dobj.functionobj.MouseMove(e, draginfo, dobj); + + return false; + }; + + // + // DragMouseUp(event) + // + + SocialCalc.DragMouseUp = function (event) { + var e = event || window.event; + + var draginfo = SocialCalc.DragInfo; + draginfo.clientX = e.clientX + draginfo.horizontalScroll; + draginfo.clientY = e.clientY + draginfo.verticalScroll; + + var dobj = draginfo.draggingElement; + + dobj.element.style.zIndex = draginfo.startZ; + + if (dobj && dobj.functionobj && dobj.functionobj.MouseUp) + dobj.functionobj.MouseUp(e, draginfo, dobj); + + if (e.stopPropagation) e.stopPropagation(); // DOM Level 2 + else e.cancelBubble = true; // IE 5+ + + if (document.removeEventListener) { + // DOM Level 2 + document.removeEventListener("mousemove", SocialCalc.DragMouseMove, true); + document.removeEventListener("mouseup", SocialCalc.DragMouseUp, true); + // Note: In old (1.5?) versions of Firefox, this causes the browser to skip the MouseUp for + // the button code. https://bugzilla.mozilla.org/show_bug.cgi?id=174320 + // Firefox 1.5 is <1% share (http://marketshare.hitslink.com/report.aspx?qprid=7) + } else if (dobj.element.detachEvent) { + // IE + dobj.element.detachEvent("onlosecapture", SocialCalc.DragMouseUp); + dobj.element.detachEvent("onmouseup", SocialCalc.DragMouseUp); + dobj.element.detachEvent("onmousemove", SocialCalc.DragMouseMove); + dobj.element.releaseCapture(); + } + + draginfo.draggingElement = null; + + return false; + }; + + // + // DragFunctionStart(event, draginfo, dobj) + // + + SocialCalc.DragFunctionStart = function (event, draginfo, dobj) { + var val; + var element = dobj.functionobj.positionobj || dobj.element; + + val = element.style.top.match(/\d*/); + draginfo.offsetY = (val ? val[0] - 0 : 0) - draginfo.clientY; + val = element.style.left.match(/\d*/); + draginfo.offsetX = (val ? val[0] - 0 : 0) - draginfo.clientX; + }; + + // + // DragFunctionPosition(event, draginfo, dobj) + // + + SocialCalc.DragFunctionPosition = function (event, draginfo, dobj) { + var element = dobj.functionobj.positionobj || dobj.element; + + if (dobj.vertical) + element.style.top = draginfo.clientY + draginfo.offsetY + "px"; + if (dobj.horizontal) + element.style.left = draginfo.clientX + draginfo.offsetX + "px"; + }; + + // ************************************* + // + // Tooltip functions: + // + // ************************************* + + SocialCalc.TooltipInfo = { + // There is only one of these -- no "new" is done. + // Only one tooltip operation can be active at a time. + // The registeredElements array is used to identify items. + + // One item for each element with a tooltip, each an object with: + // .element, .tiptext, .functionobj + // Currently .functionobj can only contain .offsetx and .offsety. + // If present they are used instead of the default ones. + + registeredElements: [], + + registered: false, // if true, an event handler has been registered for this functionality + + // Items used during hover over an element + + tooltipElement: null, // item being processed (.element is the actual element) + timer: null, // timer object waiting to see if holding over element + popupElement: null, // tooltip element being displayed + clientX: 0, // modifyable version to restrict movement + clientY: 0, + offsetX: SocialCalc.Constants.TooltipOffsetX, // modifyable version to allow positioning + offsetY: SocialCalc.Constants.TooltipOffsetY, + }; + + // + // TooltipRegister(element, tiptext, functionobj) - make element have a tooltip + // + + SocialCalc.TooltipRegister = function (element, tiptext, functionobj) { + var tooltipinfo = SocialCalc.TooltipInfo; + tooltipinfo.registeredElements.push({ + element: element, + tiptext: tiptext, + functionobj: functionobj, + }); + + if (tooltipinfo.registered) return; // only need to add event listener once + + if (document.addEventListener) { + // DOM Level 2 -- Firefox, et al + document.addEventListener( + "mousemove", + SocialCalc.TooltipMouseMove, + false + ); + } else if (document.attachEvent) { + // IE 5+ + document.attachEvent("onmousemove", SocialCalc.TooltipMouseMove); + } else { + // don't handle this + throw SocialCalc.Constants.s_BrowserNotSupported; + } + + tooltipinfo.registered = true; // remember + + return; + }; + + // + // TooltipMouseMove(event) + // + + SocialCalc.TooltipMouseMove = function (event) { + var e = event || window.event; + + var tooltipinfo = SocialCalc.TooltipInfo; + + tooltipinfo.viewport = SocialCalc.GetViewportInfo(); + tooltipinfo.clientX = e.clientX + tooltipinfo.viewport.horizontalScroll; + tooltipinfo.clientY = e.clientY + tooltipinfo.viewport.verticalScroll; + + var tobj = SocialCalc.LookupElement( + e.target || e.srcElement, + tooltipinfo.registeredElements + ); + + if (tooltipinfo.timer) { + // waiting to see if holding still: didn't hold still + window.clearTimeout(tooltipinfo.timer); // cancel timer + tooltipinfo.timer = null; + } + + if (tooltipinfo.popupElement) { + // currently displaying a tip: hide it + SocialCalc.TooltipHide(); + } + + tooltipinfo.tooltipElement = tobj || null; + + if (!tobj || SocialCalc.ButtonInfo.buttonDown) return; // if not an object with a tip or a "button" is down, ignore + + tooltipinfo.timer = window.setTimeout(SocialCalc.TooltipWaitDone, 700); + + if (tooltipinfo.tooltipElement.element.addEventListener) { + // Register event for mouse down which cancels tooltip stuff + tooltipinfo.tooltipElement.element.addEventListener( + "mousedown", + SocialCalc.TooltipMouseDown, + false + ); + } else if (tooltipinfo.tooltipElement.element.attachEvent) { + // IE + tooltipinfo.tooltipElement.element.attachEvent( + "onmousedown", + SocialCalc.TooltipMouseDown + ); + } + + return; + }; + + // + // TooltipMouseDown(event) + // + + SocialCalc.TooltipMouseDown = function (event) { + var e = event || window.event; + + var tooltipinfo = SocialCalc.TooltipInfo; + + if (tooltipinfo.timer) { + window.clearTimeout(tooltipinfo.timer); // cancel timer + tooltipinfo.timer = null; + } + + if (tooltipinfo.popupElement) { + // currently displaying a tip: hide it + SocialCalc.TooltipHide(); + } + + if (tooltipinfo.tooltipElement) { + if (tooltipinfo.tooltipElement.element.removeEventListener) { + // DOM Level 2 -- Firefox, et al + tooltipinfo.tooltipElement.element.removeEventListener( + "mousedown", + SocialCalc.TooltipMouseDown, + false + ); + } else if (tooltipinfo.tooltipElement.element.attachEvent) { + // IE 5+ + tooltipinfo.tooltipElement.element.detachEvent( + "onmousedown", + SocialCalc.TooltipMouseDown + ); + } + tooltipinfo.tooltipElement = null; + } + + return; + }; + + // + // TooltipDisplay(tobj) + // + + SocialCalc.TooltipDisplay = function (tobj) { + var tooltipinfo = SocialCalc.TooltipInfo; + var scc = SocialCalc.Constants; + var offsetX = + tobj.functionobj && typeof tobj.functionobj.offsetx == "number" + ? tobj.functionobj.offsetx + : tooltipinfo.offsetX; + var offsetY = + tobj.functionobj && typeof tobj.functionobj.offsety == "number" + ? tobj.functionobj.offsety + : tooltipinfo.offsetY; + + tooltipinfo.popupElement = document.createElement("div"); + if (scc.TDpopupElementClass) + tooltipinfo.popupElement.className = scc.TDpopupElementClass; + SocialCalc.setStyles(tooltipinfo.popupElement, scc.TDpopupElementStyle); + + tooltipinfo.popupElement.innerHTML = tobj.tiptext; + + if (tooltipinfo.clientX > tooltipinfo.viewport.width / 2) { + // on right side of screen + tooltipinfo.popupElement.style.bottom = + tooltipinfo.viewport.height - tooltipinfo.clientY + offsetY + "px"; + tooltipinfo.popupElement.style.right = + tooltipinfo.viewport.width - tooltipinfo.clientX + offsetX + "px"; + } else { + // on left side of screen + tooltipinfo.popupElement.style.bottom = + tooltipinfo.viewport.height - tooltipinfo.clientY + offsetY + "px"; + tooltipinfo.popupElement.style.left = + tooltipinfo.clientX + offsetX + "px"; + } + + if (tooltipinfo.clientY < 50) { + // make sure fits on screen if nothing above grid + tooltipinfo.popupElement.style.bottom = + tooltipinfo.viewport.height - tooltipinfo.clientY + offsetY - 50 + "px"; + } + + document.body.appendChild(tooltipinfo.popupElement); + }; + + // + // TooltipHide() + // + + SocialCalc.TooltipHide = function () { + var tooltipinfo = SocialCalc.TooltipInfo; + + if (tooltipinfo.popupElement) { + tooltipinfo.popupElement.parentNode.removeChild(tooltipinfo.popupElement); + tooltipinfo.popupElement = null; + } + }; + + // + // TooltipWaitDone() + // + + SocialCalc.TooltipWaitDone = function () { + var tooltipinfo = SocialCalc.TooltipInfo; + + tooltipinfo.timer = null; + + SocialCalc.TooltipDisplay(tooltipinfo.tooltipElement); + }; + + // ************************************* + // + // Button functions: + // + // ************************************* + + SocialCalc.ButtonInfo = { + // There is only one of these -- no "new" is done. + // Only one button operation can be active at a time. + // The registeredElements array is used to identify items. + + // One item for each clickable element, each an object with: + // .element, .normalstyle, .hoverstyle, .downstyle, .repeatinterval, .functionobj + // + // .functionobj is an object with optional function objects for: + // mouseover, mouseout, mousedown, repeatinterval, mouseup, disabled + + registeredElements: [], + + // Items used during hover over an element, clicking, repeating, etc. + + buttonElement: null, // item being processed, hover or down (.element is the actual element) + doingHover: false, // true if mouse is over one of our elements + buttonDown: false, // true if button down and buttonElement not null + timer: null, // timer object for repeating + + // Used while processing an event + + horizontalScroll: 0, + verticalScroll: 0, + clientX: 0, + clientY: 0, + }; + + // + // ButtonRegister(element, paramobj, functionobj) - make element clickable + // + // The arguments (other than element) may be null (meaning no change for style and no repeat) + // The paramobj has the optional normalstyle, hoverstyle, downstyle, repeatwait, repeatinterval settings + + SocialCalc.ButtonRegister = function (element, paramobj, functionobj) { + var buttoninfo = SocialCalc.ButtonInfo; + + if (!paramobj) paramobj = {}; + + buttoninfo.registeredElements.push({ + name: paramobj.name, + element: element, + normalstyle: paramobj.normalstyle, + hoverstyle: paramobj.hoverstyle, + downstyle: paramobj.downstyle, + repeatwait: paramobj.repeatwait, + repeatinterval: paramobj.repeatinterval, + functionobj: functionobj, + }); + + if (element.addEventListener) { + // DOM Level 2 -- Firefox, et al + element.addEventListener("mousedown", SocialCalc.ButtonMouseDown, false); + element.addEventListener("mouseover", SocialCalc.ButtonMouseOver, false); + element.addEventListener("mouseout", SocialCalc.ButtonMouseOut, false); + } else if (element.attachEvent) { + // IE 5+ + element.attachEvent("onmousedown", SocialCalc.ButtonMouseDown); + element.attachEvent("onmouseover", SocialCalc.ButtonMouseOver); + element.attachEvent("onmouseout", SocialCalc.ButtonMouseOut); + } else { + // don't handle this + throw SocialCalc.Constants.s_BrowserNotSupported; + } + + return; + }; + + // + // ButtonMouseOver(event) + // + + SocialCalc.ButtonMouseOver = function (event) { + var e = event || window.event; + + var buttoninfo = SocialCalc.ButtonInfo; + + var bobj = SocialCalc.LookupElement( + e.target || e.srcElement, + buttoninfo.registeredElements + ); + + if (!bobj) return; + + if (buttoninfo.buttonDown) { + if (buttoninfo.buttonElement == bobj) { + buttoninfo.doingHover = true; // keep track whether we are on the pressed button or not + } + return; + } + + if ( + buttoninfo.buttonElement && + buttoninfo.buttonElement != bobj && + buttoninfo.doingHover + ) { + // moved to a new one, undo hover there + SocialCalc.setStyles( + buttoninfo.buttonElement.element, + buttoninfo.buttonElement.normalstyle + ); + } + + buttoninfo.buttonElement = bobj; // remember this one is hovering + buttoninfo.doingHover = true; + + SocialCalc.setStyles(bobj.element, bobj.hoverstyle); // set style (if provided) + + if (bobj && bobj.functionobj && bobj.functionobj.MouseOver) + bobj.functionobj.MouseOver(e, buttoninfo, bobj); + + return; + }; + + // + // ButtonMouseOut(event) + // + + SocialCalc.ButtonMouseOut = function (event) { + var e = event || window.event; + + var buttoninfo = SocialCalc.ButtonInfo; + + if (buttoninfo.buttonDown) { + buttoninfo.doingHover = false; // keep track of overs and outs + return; + } + + var bobj = SocialCalc.LookupElement( + e.target || e.srcElement, + buttoninfo.registeredElements + ); + + if (buttoninfo.doingHover) { + // if there was a hover, undo it + if (buttoninfo.buttonElement) + SocialCalc.setStyles( + buttoninfo.buttonElement.element, + buttoninfo.buttonElement.normalstyle + ); + buttoninfo.buttonElement = null; + buttoninfo.doingHover = false; + } + + if (bobj && bobj.functionobj && bobj.functionobj.MouseOut) + bobj.functionobj.MouseOut(e, buttoninfo, bobj); + + return; + }; + + // + // ButtonMouseDown(event) + // + + SocialCalc.ButtonMouseDown = function (event) { + var e = event || window.event; + + var buttoninfo = SocialCalc.ButtonInfo; + + var viewportinfo = SocialCalc.GetViewportInfo(); + + var bobj = SocialCalc.LookupElement( + e.target || e.srcElement, + buttoninfo.registeredElements + ); + + if (!bobj) return; // not one of our elements + + if (bobj && bobj.functionobj && bobj.functionobj.Disabled) { + if (bobj.functionobj.Disabled(e, buttoninfo, bobj)) { + return; + } + } + + buttoninfo.buttonElement = bobj; + buttoninfo.buttonDown = true; + + SocialCalc.setStyles(bobj.element, buttoninfo.buttonElement.downstyle); + + // Register event handler for mouse up + + // Event code from JavaScript, Flanagan, 5th Edition, pg. 422 + if (document.addEventListener) { + // DOM Level 2 -- Firefox, et al + document.addEventListener("mouseup", SocialCalc.ButtonMouseUp, true); // capture everywhere + } else if (bobj.element.attachEvent) { + // IE 5+ + bobj.element.setCapture(); + bobj.element.attachEvent("onmouseup", SocialCalc.ButtonMouseUp); + bobj.element.attachEvent("onlosecapture", SocialCalc.ButtonMouseUp); + } + if (e.stopPropagation) e.stopPropagation(); // DOM Level 2 + else e.cancelBubble = true; // IE 5+ + if (e.preventDefault) e.preventDefault(); // DOM Level 2 + else e.returnValue = false; // IE 5+ + + buttoninfo.horizontalScroll = viewportinfo.horizontalScroll; + buttoninfo.verticalScroll = viewportinfo.verticalScroll; + buttoninfo.clientX = e.clientX + buttoninfo.horizontalScroll; // get document-relative coordinates + buttoninfo.clientY = e.clientY + buttoninfo.verticalScroll; + + if (bobj && bobj.functionobj && bobj.functionobj.MouseDown) + bobj.functionobj.MouseDown(e, buttoninfo, bobj); + + if (bobj.repeatwait) { + // if a repeat wait is set, then starting waiting for first repetition + buttoninfo.timer = window.setTimeout( + SocialCalc.ButtonRepeat, + bobj.repeatwait + ); + } + + return; + }; + + // + // ButtonMouseUp(event) + // + + SocialCalc.ButtonMouseUp = function (event) { + var e = event || window.event; + + var buttoninfo = SocialCalc.ButtonInfo; + var bobj = buttoninfo.buttonElement; + + if (buttoninfo.timer) { + // if repeating, cancel it + window.clearTimeout(buttoninfo.timer); // cancel timer + buttoninfo.timer = null; + } + + if (!buttoninfo.buttonDown) return; // already did this (e.g., in IE, releaseCapture fires losecapture) + + if (e.stopPropagation) e.stopPropagation(); // DOM Level 2 + else e.cancelBubble = true; // IE 5+ + if (e.preventDefault) e.preventDefault(); // DOM Level 2 + else e.returnValue = false; // IE 5+ + + if (document.removeEventListener) { + // DOM Level 2 + document.removeEventListener("mouseup", SocialCalc.ButtonMouseUp, true); + } else if (document.detachEvent) { + // IE + bobj.element.detachEvent("onlosecapture", SocialCalc.ButtonMouseUp); + bobj.element.detachEvent("onmouseup", SocialCalc.ButtonMouseUp); + bobj.element.releaseCapture(); + } + + if (buttoninfo.buttonElement.downstyle) { + if (buttoninfo.doingHover) + SocialCalc.setStyles(bobj.element, buttoninfo.buttonElement.hoverstyle); + else + SocialCalc.setStyles( + bobj.element, + buttoninfo.buttonElement.normalstyle + ); + } + + buttoninfo.buttonDown = false; + + if (bobj && bobj.functionobj && bobj.functionobj.MouseUp) + bobj.functionobj.MouseUp(e, buttoninfo, bobj); + }; + + // + // ButtonRepeat() + // + + SocialCalc.ButtonRepeat = function () { + var buttoninfo = SocialCalc.ButtonInfo; + var bobj = buttoninfo.buttonElement; + + if (!bobj) return; + + if (bobj && bobj.functionobj && bobj.functionobj.Repeat) + bobj.functionobj.Repeat(null, buttoninfo, bobj); + + buttoninfo.timer = window.setTimeout( + SocialCalc.ButtonRepeat, + bobj.repeatinterval || 100 + ); + }; + + // ************************************* + // + // MouseWheel functions: + // + // ************************************* + + SocialCalc.MouseWheelInfo = { + // There is only one of these -- no "new" is done. + // The mousewheel only affects the one area the mouse pointer is over + // The registeredElements array is used to identify items. + + // One item for each element to respond to the mousewheel, each an object with: + // .element, .functionobj + + registeredElements: [], + }; + + // + // MouseWheelRegister(element, functionobj) - make element respond to mousewheel + // + + SocialCalc.MouseWheelRegister = function (element, functionobj) { + var mousewheelinfo = SocialCalc.MouseWheelInfo; + + mousewheelinfo.registeredElements.push({ + element: element, + functionobj: functionobj, + }); + + if (element.addEventListener) { + // DOM Level 2 -- Firefox, et al + element.addEventListener( + "DOMMouseScroll", + SocialCalc.ProcessMouseWheel, + false + ); + element.addEventListener( + "mousewheel", + SocialCalc.ProcessMouseWheel, + false + ); // Opera needs this + } else if (element.attachEvent) { + // IE 5+ + element.attachEvent("onmousewheel", SocialCalc.ProcessMouseWheel); + } else { + // don't handle this + throw SocialCalc.Constants.s_BrowserNotSupported; + } + + return; + }; + + SocialCalc.ProcessMouseWheel = function (e) { + var event = e || window.event; + var delta, coord; + + if (SocialCalc.Keyboard.passThru) return; // ignore + + var mousewheelinfo = SocialCalc.MouseWheelInfo; + var ele = event.target || event.srcElement; // source object is often within what we want + var wobj; + + for (wobj = null; !wobj && ele; ele = ele.parentNode) { + // go up tree looking for one of our elements + wobj = SocialCalc.LookupElement(ele, mousewheelinfo.registeredElements); + } + if (!wobj) return; // not one of our elements + + if (event.wheelDelta) { + delta = event.wheelDelta / 120; + } else delta = -event.detail / 3; + if (!delta) delta = 0; + + if (wobj.functionobj && wobj.functionobj.WheelMove) + wobj.functionobj.WheelMove(event, delta, mousewheelinfo, wobj); + + if (event.preventDefault) event.preventDefault(); + event.returnValue = false; + }; + + // ************************************* + // + // Keyboard functions: + // + // For more information about keyboard handling, see: http://unixpapa.com/js/key.html + // + // ************************************* + + SocialCalc.keyboardTables = { + specialKeysCommon: { + 8: "[backspace]", + 9: "[tab]", + 13: "[enter]", + 25: "[tab]", + 27: "[esc]", + 33: "[pgup]", + 34: "[pgdn]", + 35: "[end]", + 36: "[home]", + 37: "[aleft]", + 38: "[aup]", + 39: "[aright]", + 40: "[adown]", + 45: "[ins]", + 46: "[del]", + 113: "[f2]", + }, + + specialKeysIE: { + 8: "[backspace]", + 9: "[tab]", + 13: "[enter]", + 25: "[tab]", + 27: "[esc]", + 33: "[pgup]", + 34: "[pgdn]", + 35: "[end]", + 36: "[home]", + 37: "[aleft]", + 38: "[aup]", + 39: "[aright]", + 40: "[adown]", + 45: "[ins]", + 46: "[del]", + 113: "[f2]", + }, + + controlKeysIE: { + 67: "[ctrl-c]", + 83: "[ctrl-s]", + 86: "[ctrl-v]", + 88: "[ctrl-x]", + 90: "[ctrl-z]", + }, + + specialKeysOpera: { + 8: "[backspace]", + 9: "[tab]", + 13: "[enter]", + 25: "[tab]", + 27: "[esc]", + 33: "[pgup]", + 34: "[pgdn]", + 35: "[end]", + 36: "[home]", + 37: "[aleft]", + 38: "[aup]", + 39: "[aright]", + 40: "[adown]", + 45: "[ins]", // issues with releases before 9.5 - same as "-" ("-" changed in 9.5) + 46: "[del]", // issues with releases before 9.5 - same as "." ("." changed in 9.5) + 113: "[f2]", + }, + + controlKeysOpera: { + 67: "[ctrl-c]", + 83: "[ctrl-s]", + 86: "[ctrl-v]", + 88: "[ctrl-x]", + 90: "[ctrl-z]", + }, + + specialKeysSafari: { + 8: "[backspace]", + 9: "[tab]", + 13: "[enter]", + 25: "[tab]", + 27: "[esc]", + 63232: "[aup]", + 63233: "[adown]", + 63234: "[aleft]", + 63235: "[aright]", + 63272: "[del]", + 63273: "[home]", + 63275: "[end]", + 63276: "[pgup]", + 63277: "[pgdn]", + 63237: "[f2]", + }, + + controlKeysSafari: { + 99: "[ctrl-c]", + 115: "[ctrl-s]", + 118: "[ctrl-v]", + 120: "[ctrl-x]", + 122: "[ctrl-z]", + }, + + ignoreKeysSafari: { + 63236: "[f1]", + 63238: "[f3]", + 63239: "[f4]", + 63240: "[f5]", + 63241: "[f6]", + 63242: "[f7]", + 63243: "[f8]", + 63244: "[f9]", + 63245: "[f10]", + 63246: "[f11]", + 63247: "[f12]", + 63289: "[numlock]", + }, + + specialKeysFirefox: { + 8: "[backspace]", + 9: "[tab]", + 13: "[enter]", + 25: "[tab]", + 27: "[esc]", + 33: "[pgup]", + 34: "[pgdn]", + 35: "[end]", + 36: "[home]", + 37: "[aleft]", + 38: "[aup]", + 39: "[aright]", + 40: "[adown]", + 45: "[ins]", + 46: "[del]", + 113: "[f2]", + }, + + controlKeysFirefox: { + 99: "[ctrl-c]", + 115: "[ctrl-s]", + 118: "[ctrl-v]", + 120: "[ctrl-x]", + 122: "[ctrl-z]", + }, + + ignoreKeysFirefox: { + 16: "[shift]", + 17: "[ctrl]", + 18: "[alt]", + 20: "[capslock]", + 19: "[pause]", + 44: "[printscreen]", + 91: "[windows]", + 92: "[windows]", + 112: "[f1]", + 114: "[f3]", + 115: "[f4]", + 116: "[f5]", + 117: "[f6]", + 118: "[f7]", + 119: "[f8]", + 120: "[f9]", + 121: "[f10]", + 122: "[f11]", + 123: "[f12]", + 144: "[numlock]", + 145: "[scrolllock]", + 224: "[cmd]", + }, + }; + + SocialCalc.Keyboard = { + areListener: false, // if true, we have been installed as a listener for keyboard events + focusTable: null, // the table editor object that gets keystrokes or null + passThru: null, // if not null, control element with focus to pass keyboard events to (has blur method), or "true" + didProcessKey: false, // did SocialCalc.ProcessKey in keydown + statusFromProcessKey: false, // the status from the keydown SocialCalc.ProcessKey + repeatingKeyPress: false, // some browsers (Opera, Gecko Mac) repeat special keys as KeyPress not KeyDown + chForProcessKey: "", // remember so can do repeat in those cases + }; + + SocialCalc.KeyboardSetFocus = function (editor) { + SocialCalc.Keyboard.focusTable = editor; + + if (!SocialCalc.Keyboard.areListener) { + document.onkeydown = SocialCalc.ProcessKeyDown; + document.onkeypress = SocialCalc.ProcessKeyPress; + SocialCalc.Keyboard.areListener = true; + } + if (SocialCalc.Keyboard.passThru) { + if (SocialCalc.Keyboard.passThru.blur) { + SocialCalc.Keyboard.passThru.blur(); + } + SocialCalc.Keyboard.passThru = null; + } + window.focus(); + }; + + SocialCalc.KeyboardFocus = function () { + SocialCalc.Keyboard.passThru = null; + window.focus(); + }; + + SocialCalc.ProcessKeyDown = function (e) { + var kt = SocialCalc.keyboardTables; + kt.didProcessKey = false; // always start false + kt.statusFromProcessKey = false; + kt.repeatingKeyPress = false; + + var ch = ""; + var status = true; + + if (SocialCalc.Keyboard.passThru) return; // ignore + + e = e || window.event; + + // IE and Safari 3.1+ won't fire keyPress, so check for special keys here. + if (e.which == undefined || typeof e.keyIdentifier == "string") { + ch = kt.specialKeysCommon[e.keyCode]; + if (!ch) { + if (e.ctrlKey) { + ch = kt.controlKeysIE[e.keyCode]; + } + if (!ch) return true; + } + status = SocialCalc.ProcessKey(ch, e); + + if (!status) { + if (e.preventDefault) e.preventDefault(); + e.returnValue = false; + } + } else { + ch = kt.specialKeysCommon[e.keyCode]; + if (!ch) { + // return true; + if (e.ctrlKey || e.metaKey) { + ch = kt.controlKeysIE[e.keyCode]; // this works here + } + if (!ch) return true; + } + + status = SocialCalc.ProcessKey(ch, e); // process the key + kt.didProcessKey = true; // remember what happened + kt.statusFromProcessKey = status; + kt.chForProcessKey = ch; + } + + return status; + }; + + SocialCalc.ProcessKeyPress = function (e) { + var kt = SocialCalc.keyboardTables; + + var ch = ""; + + e = e || window.event; + + if (SocialCalc.Keyboard.passThru) return; // ignore + if (kt.didProcessKey) { + // already processed this key + if (kt.repeatingKeyPress) { + return SocialCalc.ProcessKey(kt.chForProcessKey, e); // process the same key as on KeyDown + } else { + kt.repeatingKeyPress = true; // see if get another KeyPress before KeyDown + return kt.statusFromProcessKey; // do what it said to do + } + } + + if (e.which == undefined) { + // IE + // Note: Esc and Enter will come through here, too, if not stopped at KeyDown + ch = String.fromCharCode(e.keyCode); // convert to a character (special chars handled at ev1) + } else { + // not IE + if (!e.which) return false; // ignore - special key + if (e.charCode == undefined) { + // Opera + if (e.which != 0) { + // character + if (e.which < 32 || e.which == 144) { + // special char (144 is numlock) + ch = kt.specialKeysOpera[e.which]; + if (ch) { + return true; + } + } else { + if (e.ctrlKey) { + ch = kt.controlKeysOpera[e.keyCode]; + } else { + ch = String.fromCharCode(e.which); + } + } + } else { + // special char + return true; + } + } else if (e.keyCode == 0 && e.charCode == 0) { + // OLPC Fn key or something + return; // ignore + } else if (e.keyCode == e.charCode) { + // Safari + ch = kt.specialKeysSafari[e.keyCode]; + if (!ch) { + if (kt.ignoreKeysSafari[e.keyCode]) + // pass this through + return true; + if (e.metaKey) { + ch = kt.controlKeysSafari[e.keyCode]; + } else { + ch = String.fromCharCode(e.which); + } + } + } else { + // Firefox + if (kt.specialKeysFirefox[e.keyCode]) { + return true; + } + ch = String.fromCharCode(e.which); + if (e.ctrlKey || e.metaKey) { + ch = kt.controlKeysFirefox[e.which]; + } + } + } + + var status = SocialCalc.ProcessKey(ch, e); + + if (!status) { + if (e.preventDefault) e.preventDefault(); + e.returnValue = false; + } + + return status; + }; + + /* + * + * OLD ProcessKeyDown and ProcessKeyPress -- replaced for handling newer browsers, including Safari 3.1 and Opera 9.5 + * + + SocialCalc.ProcessKeyDown = function(e) { + + var kt = SocialCalc.keyboardTables; + + var ch=""; + var status=true; + + if (SocialCalc.Keyboard.passThru) return; // ignore + + e = e || window.event; + + if (e.which==undefined) { // IE + ch = kt.specialKeysIE[e.keyCode]; + if (!ch) { + if (e.ctrlKey) { + ch=kt.controlKeysIE[e.keyCode]; + } + if (!ch) + return true; + } + + status = SocialCalc.ProcessKey(ch, e); + + if (!status) { + if (e.preventDefault) e.preventDefault(); + e.returnValue = false; + } + } + + else { // don't do anything for other browsers - wait for keyPress + ; // special key repeats are done as keypress in those browsers + } + + return status; + + } + + SocialCalc.ProcessKeyPress = function(e) { + + var kt = SocialCalc.keyboardTables; + + var ch=""; + + if (SocialCalc.Keyboard.passThru) return; // ignore + + e = e || window.event; + + if (e.which==undefined) { // IE + // Note: Esc and Enter will come through here, too, if not stopped at KeyDown + ch=String.fromCharCode(e.keyCode); // convert to a character (special chars handled at ev1) + } + + else { // not IE + if (e.charCode==undefined) { // Opera + if (e.which!=0) { // character + if (e.which<32) { // special char + ch = kt.specialKeysOpera[e.keyCode]; + if (!ch) + return true; + } + else { + if (e.ctrlKey) { + ch=kt.controlKeysOpera[e.keyCode]; + } + else { + ch = String.fromCharCode(e.which); + } + } + } + else { // special char + ch = kt.specialKeysOpera[e.keyCode]; + if (!ch) + return true; + } + } + + else if (e.keyCode==0 && e.charCode==0) { // OLPC Fn key or something + return; // ignore + } + + else if (e.keyCode==e.charCode) { // Safari + ch = kt.specialKeysSafari[e.keyCode]; + if (!ch) { + if (kt.ignoreKeysSafari[e.keyCode]) // pass this through + return true; + if (e.metaKey) { + ch=kt.controlKeysSafari[e.keyCode]; + } + else { + ch = String.fromCharCode(e.which); + } + } + } + + else { // Firefox + ch = kt.specialKeysFirefox[e.keyCode]; + if (!ch) { + if (kt.ignoreKeysFirefox[e.keyCode]) // pass this through + return true; + if (e.which) { // normal char + if (e.ctrlKey || e.metaKey) { + ch = kt.controlKeysFirefox[e.which]; + } + else { + ch = String.fromCharCode(e.which); + } + } + else { // usually a special char + return true; // old Firefox gives extra, empty keyPress for "/" - ignore + } + } + } + } + + var status = SocialCalc.ProcessKey(ch, e); + + if (!status) { + if (e.preventDefault) e.preventDefault(); + e.returnValue = false; + } + + return status; + + } + */ + + // + // status = SocialCalc.ProcessKey(ch, e) + // + // Take a key representation as a character string and dispatch to appropriate routine + // + + SocialCalc.ProcessKey = function (ch, e) { + var ft = SocialCalc.Keyboard.focusTable; + + if (!ft) return true; // we're not handling it -- let browser do default + + return ft.EditorProcessKey(ch, e); + }; + + // For Contact + SocialCalc.EditorChangecontact = function ( + editor, + text, + name, + phone, + email, + street, + city, + company, + val + ) { + // alert('changecontact called'); + //var wval = editor.workingvalues; + if (name != "") { + cmdline = "set " + name + " " + "text t" + " " + contactname; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + cmdline = "set " + phone + " " + "text t" + " " + contactphoneNumber; + editor.EditorScheduleSheetCommands(cmdline, true, false); + if (email != "") { + cmdline = "set " + email + " " + "text t" + " " + contactemail; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + cmdline = "set " + street + " " + "text t" + " " + contactStreet; + editor.EditorScheduleSheetCommands(cmdline, true, false); + cmdline = "set " + city + " " + "text t" + " " + contactcity; + editor.EditorScheduleSheetCommands(cmdline, true, false); + if (val == "y") { + cmdline = "set " + company + " " + "text t" + " " + contactcompany; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + + return; + }; + + //changes by mini for font and color + + SocialCalc.EditorChangeSheetcolor = function (editor, text) { + var result, cell, valueinfo, fch, type, value, oldvalue, cmdline; + + var sheetobj = editor.context.sheetobj; + var wval = editor.workingvalues; + + type = "text t"; + //value = typeof text == "string" ? text : editor.inputBox.GetText(); // either explicit or from input box + value = text; + + oldvalue = SocialCalc.GetCellContents(sheetobj, wval.ecoord) + ""; + if (value == oldvalue) { + // no change + return; + } + fch = value.charAt(0); + if (fch == "=" && value.indexOf("\n") == -1) { + type = "formula"; + value = value.substring(1); + } else if (fch == "'") { + type = "text t"; + value = value.substring(1); + } else if (value.length == 0) { + type = "empty"; + } else { + valueinfo = SocialCalc.DetermineValueType(value); + if (valueinfo.type == "n" && value == valueinfo.value + "") { + // see if don't need "constant" + type = "value n"; + } else if (valueinfo.type.charAt(0) == "t") { + type = "text " + valueinfo.type; + } else if (valueinfo.type == "") { + type = "text t"; + } else { + type = "constant " + valueinfo.type + " " + valueinfo.value; + } + } + + if (type.charAt(0) == "t") { + // text + value = SocialCalc.encodeForSave(value); // newlines, :, and \ are escaped + } + + // if startsheet different from currentsheet, switch to start sheet + // for the save to take effect + if (SocialCalc.WorkBook && wval.currentsheet != wval.startsheet) { + var control = SocialCalc.GetCurrentWorkBookControl(); + var cmdstr = "activatesheet " + wval.startsheetid; + control.ExecuteWorkBookControlCommand( + { cmdtype: "wcmd", id: "0", cmdstr: cmdstr }, + false + ); + } + + //cmdline = "set "+wval.ecoord+" "+"font"+" "+value; + cmdline = "set " + "sheet" + " " + "defaultcolor" + " " + value; + //cmdline = "set "+"sheet"+" "+"defaultcolor green"; + //var control = SocialCalc.GetCurrentWorkBookControl(); + //alert('u reach sheet' + " "+control.workbook.sheetArr["sheet1"].sheet); + //cmdline = "set "+sheetobj+" "+"font"+" "+value; + //cmdline="set "+ control.workbook.sheetArr["sheet1"].sheet+" color blue" ; + editor.EditorScheduleSheetCommands(cmdline, true, false); + + return; + }; + + SocialCalc.EditorChangecolorFromWidget = function (editor, text) { + var result, cell, valueinfo, fch, type, value, oldvalue, cmdline; + + var sheetobj = editor.context.sheetobj; + var wval = editor.workingvalues; + if (!CoordForColorChange) { + alert("No active cell"); + return; + } + + // if startsheet different from currentsheet, switch to start sheet + // for the save to take effect + if (SocialCalc.WorkBook && wval.currentsheet != wval.startsheet) { + var control = SocialCalc.GetCurrentWorkBookControl(); + var cmdstr = "activatesheet " + wval.startsheetid; + control.ExecuteWorkBookControlCommand( + { cmdtype: "wcmd", id: "0", cmdstr: cmdstr }, + false + ); + } + + cmdline = "set " + CoordForColorChange + " " + "color" + " " + text; + editor.EditorScheduleSheetCommands(cmdline, true, false); + + return; + }; + + SocialCalc.EditorChangefontFromWidget = function (editor, text) { + var result, cell, valueinfo, fch, type, value, oldvalue, cmdline; + + var sheetobj = editor.context.sheetobj; + var wval = editor.workingvalues; + if (!CoordForColorChange) { + alert("No active cell"); + return; + } + //alert(CoordForColorChange); + // if startsheet different from currentsheet, switch to start sheet + // for the save to take effect + if (SocialCalc.WorkBook && wval.currentsheet != wval.startsheet) { + var control = SocialCalc.GetCurrentWorkBookControl(); + var cmdstr = "activatesheet " + wval.startsheetid; + control.ExecuteWorkBookControlCommand( + { cmdtype: "wcmd", id: "0", cmdstr: cmdstr }, + false + ); + } + + //cmdline = "set "+CoordForColorChange+" "+"font"+" "+text; + //editor.EditorScheduleSheetCommands(cmdline, true, false); + var value = text; + if (value == "a") { + cmdline = "set " + CoordForColorChange + " font * 12px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + if (value == "b") { + cmdline = "set " + CoordForColorChange + " font * 14px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + if (value == "c") { + cmdline = "set " + CoordForColorChange + " font * 16px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + if (value == "d") { + cmdline = "set " + CoordForColorChange + " font * 18px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + + return; + }; + + SocialCalc.EditorChangeSheetfont = function (editor, text) { + var result, cell, valueinfo, fch, type, value, oldvalue, cmdline; + + var sheetobj = editor.context.sheetobj; + var wval = editor.workingvalues; + + type = "text t"; + //value = typeof text == "string" ? text : editor.inputBox.GetText(); // either explicit or from input box + value = text; + + oldvalue = SocialCalc.GetCellContents(sheetobj, wval.ecoord) + ""; + if (value == oldvalue) { + // no change + return; + } + fch = value.charAt(0); + if (fch == "=" && value.indexOf("\n") == -1) { + type = "formula"; + value = value.substring(1); + } else if (fch == "'") { + type = "text t"; + value = value.substring(1); + } else if (value.length == 0) { + type = "empty"; + } else { + valueinfo = SocialCalc.DetermineValueType(value); + if (valueinfo.type == "n" && value == valueinfo.value + "") { + // see if don't need "constant" + type = "value n"; + } else if (valueinfo.type.charAt(0) == "t") { + type = "text " + valueinfo.type; + } else if (valueinfo.type == "") { + type = "text t"; + } else { + type = "constant " + valueinfo.type + " " + valueinfo.value; + } + } + + if (type.charAt(0) == "t") { + // text + value = SocialCalc.encodeForSave(value); // newlines, :, and \ are escaped + } + + // if startsheet different from currentsheet, switch to start sheet + // for the save to take effect + if (SocialCalc.WorkBook && wval.currentsheet != wval.startsheet) { + var control = SocialCalc.GetCurrentWorkBookControl(); + var cmdstr = "activatesheet " + wval.startsheetid; + control.ExecuteWorkBookControlCommand( + { cmdtype: "wcmd", id: "0", cmdstr: cmdstr }, + false + ); + } + + //cmdline = "set "+wval.ecoord+" "+"font"+" "+value; + //cmdline = "set "+"sheet"+" "+"defaultcolor"+" "+ value; + + //cmdline = "set "+"sheet"+" "+"defaultfont"+" * "+value+" *"; + + //cmdline = "set sheet defaultfont normal normal "+value +" *"; + //cmdline="set sheet defaultfont"; + // alert(cmdline); + //editor.EditorScheduleSheetCommands(cmdline, true, false); + if (value == "a") { + cmdline = "set sheet defaultfont * 12px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + if (value == "b") { + cmdline = "set sheet defaultfont * 14px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + if (value == "c") { + cmdline = "set sheet defaultfont * 16px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + if (value == "d") { + cmdline = "set sheet defaultfont * 18px *"; + editor.EditorScheduleSheetCommands(cmdline, true, false); + } + + //cmdline = ""+"set sheet defaultfont * 8pt *"; + //cmdline = "set "+"sheet"+" "+"defaultcolor green"; + //var control = SocialCalc.GetCurrentWorkBookControl(); + //alert('u reach sheet' + " "+control.workbook.sheetArr["sheet1"].sheet); + //cmdline = "set "+sheetobj+" "+"font"+" "+value; + //cmdline="set "+ control.workbook.sheetArr["sheet1"].sheet+" color blue" ; + + return; + }; + + SocialCalc.EditorCut = function (editor, value) { + var result, cell, valueinfo, fch, type, value, oldvalue, cmdline; + var sheetobj = editor.context.sheetobj; + var wval = editor.workingvalues; + if (SocialCalc.Callbacks && SocialCalc.Callbacks.IsCellEditable) { + if (!SocialCalc.Callbacks.IsCellEditable(editor)) { + return true; + } + } + if (value == "a") { + cmdline = "cut " + editCoord + " all"; + } else if (value == "b") { + cmdline = "copy " + editCoord + " all"; + } else if (value == "c") { + cmdline = "paste " + editCoord + " all"; + } else if (value == "d") { + cmdline = "erase " + editCoord + " formulas"; + } + editor.EditorScheduleSheetCommands(cmdline, true, false); + }; + + SocialCalc.EditorClearSheet = function (editor, cell_to_clear) { + //alert("clear"); + var cmdline; + var coord = cell_to_clear; + cmdline = "erase " + coord + " formulas"; + //console.log(cmdline); + editor.EditorScheduleSheetCommands(cmdline, true, false); + }; + + // Make sure SocialCalc is available globally + if (typeof window !== "undefined") { + window.SocialCalc = SocialCalc; + } else if (typeof global !== "undefined") { + global.SocialCalc = SocialCalc; + } + + return SocialCalc; +}); diff --git a/src/components/socialcalc/index.js b/src/components/socialcalc/index.js index 709c61d..611ba51 100644 --- a/src/components/socialcalc/index.js +++ b/src/components/socialcalc/index.js @@ -1,8 +1,7 @@ -// Import as a side-effect to load the global -import "./aspiring/SocialCalc.js"; +import SocialCalcFromCore from "./core/index.js"; -// Access the global SocialCalc that was created by the UMD module -let SocialCalc; +// Use the imported SocialCalc +let SocialCalc = SocialCalcFromCore; // Ensure SocialCalc is loaded from the global scope if (typeof window !== "undefined" && window.SocialCalc) { diff --git a/src/pages/FilesPage.tsx b/src/pages/FilesPage.tsx index 607b5c8..97506d3 100644 --- a/src/pages/FilesPage.tsx +++ b/src/pages/FilesPage.tsx @@ -82,13 +82,14 @@ const FilesPage: React.FC = () => { return tempMeta.find((meta) => meta.template_id === templateId); }; - // Categorize templates based on their names - const categorizeTemplate = (templateName: string | undefined) => { - if (!templateName) return "web"; - const name = templateName.toLowerCase(); - if (name.includes("mobile")) { + // Categorize templates based on their category + const categorizeTemplate = (templateId: number) => { + const metadata = getTemplateMetadata(templateId); + if (!metadata?.category) return "web"; + const category = metadata.category.toLowerCase(); + if (category === "mobile") { return "mobile"; - } else if (name.includes("tablet")) { + } else if (category === "tablet") { return "tablet"; } else { return "web"; @@ -119,19 +120,13 @@ const FilesPage: React.FC = () => { const templates = tempMeta; const categorized = { web: templates.filter((t) => { - const metadata = getTemplateMetadata(t.template_id); - const templateName = metadata?.name || t.name || "Unknown Template"; - return categorizeTemplate(templateName) === "web"; + return categorizeTemplate(t.template_id) === "web"; }), mobile: templates.filter((t) => { - const metadata = getTemplateMetadata(t.template_id); - const templateName = metadata?.name || t.name || "Unknown Template"; - return categorizeTemplate(templateName) === "mobile"; + return categorizeTemplate(t.template_id) === "mobile"; }), tablet: templates.filter((t) => { - const metadata = getTemplateMetadata(t.template_id); - const templateName = metadata?.name || t.name || "Unknown Template"; - return categorizeTemplate(templateName) === "tablet"; + return categorizeTemplate(t.template_id) === "tablet"; }), }; return categorized; @@ -685,7 +680,9 @@ const FilesPage: React.FC = () => { template.template || template.name || "Unknown Template"; - const category = categorizeTemplate(templateName); + const category = categorizeTemplate( + template.templateId || template.template_id + ); // Get the template data from DATA to access footers const templateData = DATA[template.templateId || template.template_id]; @@ -943,7 +940,7 @@ const FilesPage: React.FC = () => { return (
setShowSharedTemplateModal(true)} + onClick={() => handleTemplateSelect(template.templateId)} style={{ border: "2px solid var(--ion-color-light)", borderRadius: "12px", @@ -1153,7 +1150,7 @@ const FilesPage: React.FC = () => { return (
setShowSharedTemplateModal(true)} + onClick={() => handleTemplateSelect(template.templateId)} style={{ minWidth: "110px", width: "110px", @@ -1337,6 +1334,48 @@ const FilesPage: React.FC = () => { color={toastMessage.includes("successfully") ? "success" : "warning"} position="top" /> + + {/* File Name Prompt */} + { + setShowFileNamePrompt(false); + setSelectedTemplateForFile(null); + setNewFileName(""); + }} + header="Create New File" + message="Enter a name for your new file:" + inputs={[ + { + name: "fileName", + type: "text", + placeholder: "File name", + value: newFileName, + }, + ]} + buttons={[ + { + text: "Cancel", + role: "cancel", + handler: () => { + setShowFileNamePrompt(false); + setSelectedTemplateForFile(null); + setNewFileName(""); + }, + }, + { + text: "Create", + handler: async (data) => { + if (data.fileName && selectedTemplateForFile !== null) { + await createNewFileWithTemplate( + selectedTemplateForFile, + data.fileName + ); + } + }, + }, + ]} + /> ); }; diff --git a/src/templates-meta.ts b/src/templates-meta.ts index afcdda5..6f201ea 100644 --- a/src/templates-meta.ts +++ b/src/templates-meta.ts @@ -1,46 +1,59 @@ export let tempMeta = [ + { + name: "Mobile-Invoice-1", + category: "Mobile", + + template_id: 1001, + ImageUri: + "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UwkJCUoppebOnauMjY2Vi4uLsrGxUaampmr16tVa+ylTpqgBAwZor+Pj45Wbm5uqXLmy9l8fHx+llFJ79uxRxsbGytPTU+fnzJkzeR6Pi4uLSk5OVtHR0apfv34v9DsRoqSU8VgpFgZ5RvqsTz75BE9PT1xdXXWWGxkZMWTIEDZv3szs2bO15du3b6dPnz5UrVqVEydO8NlnnxEQEEDHjh0BmD59OhMnTsTLy4v69evn2N+kSZPIzMwkKioKKysrUlNTiY2N1dabmZmxf//+Ao09ISGBypUrY2ZmxvHjx+nQocOL/AqEEMWoXFwjdXZ2ZtSoUfy/9u48Kss6///4CwSFEEiR3Cj30tHMssW9LGwqLZvS1MKac1pmWqapppk60zfbbdoszcpGMUsr7dSxRc3lGE67hokRYcqUCyKKy1FIEYT37w+P92/ubjDkDUHwfJzDOXJd1+e6P9fNfT+97+u6xcLCwpB1KSkpWrdunVatWiVJOnDggN577z1dddVVkqTnnntOl19+eSCikvT4448rKSlJr776asj+vvvuO82ZM0fPPPOMEhISJEnNmjVT+/btj3neM2bM0IUXXqgffvhBw4YN0/3336833nhDixYt0uLFi9W+fXstXbr0mPcLoGY1+JCGhYWpR48eGj9+vG666aaQ9T169FC/fv00d+5cSdKCBQvUpEkTXXrppZKktWvXatCgQUFjIiIidPbZZ2vDhg0h+1u7dq1iY2ODwvtzpaWleuSRRwJfJSUlFW53ww03aMCAAXrllVe0bNkytWzZUunp6brkkkvUrFkz5efnq1mzZlW+LwDUjgYf0iPuuecebdq0Sa+99lrIunHjxumtt95SWVmZ3nnnHY0ePVqRkZGSpN27dysuLi5kTFxcnIqKikKW5+fnq127djU2708//VSDBg3S9u3b1aZNG4WFhUmShg4dqrKyMp177rk1dlsAqqfBnyM1M0lSeHi4Zs+erUGDBun2228PBEk6HNK7775b7777rt577z0tXLgwsO7EE09UXl5eyH43b96srl27hiw/4YQTtG3btqPOKTIyMuicbEV++OEHzZkzR7m5uXryySeVk5OjwsLCQFgB1B+N5hWpJHXp0kUTJkzQww8/HAisJCUmJmr48OG644471Lp1aw0dOjSw7pxzztHixYuD9rNz506lpaXp4osvDrmNPn36aN++ffrggw9cc23RooUOHjyoUaNGafDgwcrPz9cNN9xQ4cUtAHWrUYVUkm6++eYKz1+OGzdOubm5Gj16dNCr1bvvvlurV6/Www8/rL179yorK0ujR49Wnz59dNFFF4Xsp2fPnrrjjjt0/fXXa8GCBSoqKtJnn32miy66SD/99JMkqby8XCtWrAj6+vlpghYtWmj37t0aN26czjvvPO3evVtjx44NhDQzM1PXXHONsrKyavLuAVANjS6kkvTqq6+GnPe87LLLdPzxx2vs2LFBy7t27aqPPvpIb7/9to4//nidccYZateunT788MNK9z9p0iT95S9/UUpKimJjY3XNNddo2LBhgQtDxcXFGjp0aNBXTk5OyH5Wrlypc845R+vXrw/6BwCStGXLFs2dO1e5ubnVvRsA1JAw+9/3uPVMWFiYamt6W7Zs0Yknnhi0LCMjQ3369Kl0zLp165SUlKTmzZtX6TZKS0v13XffqXfv3kGvcqvCzLR69WqdeeaZ2rVrl4qKitShQ4egbbZv367WrVsf036B2labz9v6qtGGFEDtaIzP20b51h4AahIhBQAnQgoAToQUAJwIKQA4EVIAcCKkAOBESAHAiZACgBMhBQAnQgoAToQUAJwIKQA4EVIAcCKkAOBESAHAiZACgBMhBQAnQgoAToQUAJwIKQA4EVIAcCKkAOBESAHAiZACgBMhBQCniLqeQE0rKyvTJ598IkmKjo5W9+7dFR8fX+G269evV3R0tE488URlZ2dr+/btFW7XqlUr9ejRI7Df/9WxY0d17Nixxubv9fHHHys9PV3nn3+++vTpU+E233zzjZKSktSyZUuZmT788EOtW7dOgwcP1llnnVXhmB9//FGdOnWqcN2+ffuUk5OjM844Q5K0bds2vf/++0pISNDw4cMVHR0tSSosLNTq1auDxnbr1k3t27ev7uEC9YPVY9WZXmFhoUmyc845x84++2yLjIy0yy67zH788ceQbW+55RZ78MEHzcxs4sSJlpycbMnJyda2bVvr0KFD4PsJEyYE7ffI8uTkZJs1a5b3MGvMPffcY6effrr96U9/sujoaJs+fXrINiUlJdaxY0fbvn27mZm9+eabdvrpp9tdd91lPXr0sPvuuy9kzEcffWQdOnSo9HYnT55sd911l5mZLVmyxKKiouzqq6+20aNHW+fOnW3r1q1mZvbV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8cfrwgsvVFZWlr744guNGTNGkydPVuvWrXXfffdpzpw5Gjt2rN58801J0uOPP67bbrtN5557rlq1aqUxY8Zo586dkqS//e1vKi4u1j/+8Q8NGzZMBQUFIeMladasWerZs6datmypUaNGqaCgQJK0aNEiPfHEE7rtttvUsmVLDRw4UBkZGb/afVtXzEzTp0/XtddeG3Q/SVJKSooWLFigXr16qV27dnrooYc0ceJEdezYUb169QqK3KpVqzR48GDFx8dr0KBBWrVqlSTp+++/17Bhw4L2e/XVV+vTTz/V1q1bde211+rf//63OnfurJNPPlnz5s2TJGVkZGjmzJnKyMjQsGHD9Mwzz9TyPdGIWD1W1enV88MIUlhYaJLsiy++CFoeFxdnqampVlxcbKeeeqpNmjTJduzYYbNnz7bY2Fhbu3atffLJJybJ0tPTLSMjw9566y0zM3v++ectKSnJlixZYnv27LHU1FQrKiqyBQsWWNOmTe26666zgoICKygosI0bN9rYsWPtuuuuMzOz6667zvr162cbN2601atX28CBA23o0KFmZrZmzRqLioqyyZMnW1pamhUXF4eMnz9/vjVv3tzmz59v3333nV1wwQU2YMAAKy8vt9TUVIuOjraFCxdabm6uXXPNNda/f/9f7b72qu7javHixda9e3fbtm2bRUREWG5ubmBdp06dbPDgwZaVlWWTJk0ySXbnnXfajh077O6777YWLVrYgQMHbMuWLRYXF2cPP/ywbd261f785z/b8ccfb3l5efbVV1+FzK1Tp0727rvv2oYNG0ySTZw40Xbs2GFPPvmkRUdH286dO23Pnj12880321lnnWVpaWm2bt26Wrk/fkvPx5pSr4+4If7gKgtpQkKCTZ8+3V577TXr2bOnpaWlBb5GjBhhd955p61fv97Cw8Pt8ccft/Ly8sDYLl262CuvvBJyW0dCunv37qDld911V1BI77rrrsC6zMxMk2QbNmwwM7PjjjvO0tLSKh0/cuRIu/XWWwPrVq1aZZIsMzPTUlNT7ayzzgqsW7FihTVv3rzK91Vdq+7javTo0fbggw+amdn5559vEydODKw7ErwjmjdvHrh/8/PzTZJlZWXZ008/bb169QpsV1ZWZi1atLAXXnihSiHds2dPYFxERIR9+eWXZmb2xBNPWHJycrWOqyE+H2sKb+3rgS1b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", + }, + { + name: "Mobile-Tax-Invoice", + category: "Mobile", + + template_id: 1002, + ImageUri: + "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uVz84x//oH///le6G1cNXsHwippXbLwv7z0opQeJZ3jvffmJsCFSip/oqopa8mKO4rNX31j1VxG6IDc7F5fbdVHzl562QVVMKPkDVu9efiVfwItynasoZVBIfXE4HH6eXkREBADPP/88eXl5vPTSS6SmpnLy5ElsNhupqalMmzaN+vXr07p1a8CzxfLuu+8mOjqaFi1aGBn0wZOwY+jQocTFxVG3bl3effddANLT05k8eTLp6emkpqby/vvvB+yfEILPP/+cgQMHFskyr+s6zz33HBUrVqR9+/ZF0gFOnTqV2rVrU79+faZMmWJcX79+PX379mXcuHHEx8f7JWRu3bo1MTExdOnShf379wNw4sQJBg8eTEJCAu3bt2fFihW43W5effVV6tatS6NGjf42b7es4+sl+oqdr8iWJFq+guwtr2laEaFUFMUQWoEoMamJn8DnJ0fxJIzWiI6JwhoaWqRN38/F4XI5cLrseEXS+8vj+h28l0yZmiMtzEMPPcQ//vEP3G43Tz31lHEg3gMPPMAnn3zCgAEDaNSoEVFRUTidTpYvX05OTg5LlixB0zTsdjt33XUXgwcPZuLEiSxZsoR77rmHtWvX0qhRI4YMGYKu66xfv57jx4/zwAMPEBUVRb9+/bj11lvZvHkzI0eOLHb76dKlSwkNDeWdd96hatWqHDlyhCpVqgDw9ttvs2bNGhYtWkSlSpXo3bu3UW/VqlUMHz6cTz/9lPbt2/PCCy8YyUcyMzNZsGABoaGh7NixA/Bsmx08eDBjx46lbdu2jBkzhtTUVLZt28akSZPYsGEDmzdvZvPmzURFRbF+/XreeOMNNm/ejMlkuujEJpKC4bcRM1poDvViPFVFUVDxzJN65x29YVSKohgr/L6p/C5gEZfLTXa2jRCrmbDw0IIIAW88q9F20V1TiqIQHhFmeMTGc1zv4/cSKNMe6RtvvMGUKVOYN28eNWrU4JtvvgEwEm00atSIDh06+G3tfPnll6levTqJiYnMmjULXddp2rQpO3bsIDExkfbt2/PFF19w6NAhJk2axL333ssff/yBw+GgX79+TJo0iZiYGGrUqEF0dDQdOnSgXr16Afs3adIk+vXrR6VKlWjXrp2R/T4rK4u3336bzz77jJtvvpnq1av7HWj35ptvMnz4cHr27En58uXp2rVrEdsffPABsbGxxMbG8v77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bDYbo0aNon79+kRGRt620a4Qd607t6b0zSu8Qv7IkSOV2WxWH330kcrIyNC379q1S3l6eqrPPvtM/fzzzyo7O1ulp6crQLVp00YdPXpUJScnq+zsbNWkSRM1YcIEdfbsWTVz5kxVoUIFtWfPHqWUUgMGDFD9+/dXhw8fVhs2bFB16tRR//73v1VaWpoaOHCgioqKUj///LNKTEwstr/Lly9X4eHh6vTp08pkMqkTJ07o+8aMGaNatGihNm/erI4ePapatGihBgwYoJRSas2aNSooKEjNnz9fpaamqv79+6uwsDCllFJLlixRFotF9evXT507d06dO3dOJSYmqpCQEDV//nx17tw5NWzYMFW3bl2VmZmpxo4dq8LDw9XJkyfVokWL1NatW9W6desUoLZv3652796tZs+e7dRns9l86/7QhLgHuPWIdMyYMUybNo358+cTEhLCokWLAMeCzwaDgcjISGJiYvDw8NCPGTlyJLVr16ZGjRrMnj0bu91O8+bN2b9/PzVq1KBjx4588803HD9+nClTptC7d2+Sk5PJzc2lT58+TJkyBT8/P0JCQqhUqRIxMTE0aNCg2P5NmTKFPn36UK1aNTp06KAvr5eens7YsWP58ssveeCBB6hdu7bTikpxcXEMHz6cnj17Urly5WLfx584cSKBgYEEBgby6aef0rZtW/z9/UlISCA2Npbs7GxWrFhBUFAQJ06cIDExkccee4yoqCiCgoIwGAysWrWKyMhIevXqdSv/WIS457jVXfviPPfcczz77LO88847PP/886SkpODj41Ni+Tp16ujfHzhwgPPnz/PBBx84lalSpQq//vorJpOpyGV7WFhYqfp1/vx5FixYwPjx4wHHItOTJ09mxIgR7Nu3j6ysLCIiIoocp5Ri/fr1xa76n69q1apOC0EXdx4NGzbEYrHw4osv8ttvv9GzZ0969+7Nxx9/TFhYGN9++y0jRoxg2bJlfP3116U+LyFEUW4fSMGRi+j1119n3LhxHDp0iObNmwOOecjr8fPzo0aNGqxatarIvs2bN2O1Wpk1a1aRNU7zqeus9/L1119jtVqLLGm3efNmvL29Adi+fTvt2rUrci6enp7s2rWLbt26Xbf/Bc8jKiqKCRMmFLt/3LhxDBkyhCeffJIBAwYwb948nnnmGf70pz/x5ptv0q5dO06fPl1irichxPW59aX9hg0bSExM5Ny5c3zwwQdYLBZ9XdLatWuzc+fO6x7/yCOPsG/fPj01ic1m4+effwYc0wMNGzbkjTfe0JPlFUwMFxwcTGJiop5DviClFFOnTuXDDz/Uk9cppejcuTPfffcd4eHhhIaG8vHHH7Np0yaUUpw4cUI/vnv37vz3v/9l2bJl5OTkcPLkyeueR8+ePZkyZQrbtm0DIC0tjV27dgGOhZoPHTpEYGAg7dq148KFC1itVn766SfMZjOPPPIIqamp5OXlXbcNIcR13MH52ZtW+GZTnz59FKAAdd9996k5c+bo+2bNmqVMJpMC1Pvvv6/fbDpw4IBTnYsXL1YBAQEKUJUqVVJPPvmkvi8xMVE1btxYAcpkMqkOHTqoY8eOKaUc6Ylbt26tAGUwGFROTo5+3KpVqxSgjh8/7tRWft753NxctWHDBlWlShUFKF9fXxUZGanfbEpJSVFRUVH6ubVp08bpZlONGjWKfDbvvfeeXj40NFTFxcUppZSaO3eu8vX1VYCqWbOm2rhxozp06JCqWbOmApSXl5f66quv9HrkZpMQN8+t1iMtLtVIcnIyFy5cICIiosil6cmTJzl8+DDNmzenYsWKJdabnZ1NQkICTZs2LbJavd1uZ8+ePdSpU6fIosY2m42tW7fi4+NDZGTkTZ9PTk4Oe/fupXHjxvrlfuF2a9SoUeq3tdLS0jh+/DhNmzZ12p6VlUViYqKeLjq/7/v27SM0NNTpvFasWMGjjz5aYipqIURRbhdIn3rqKRo2bFgk1Yhw3fjx40lMTOS7776TQCrETXCrm013KtXIvWLjxo1kZWXx4IMP3umuCOFW3GpEKoQQ5ZFb37UXQojyQAKpEEK4SAKpEEK4SAKpEEK4SAKpEEK4yK0efyou1QjAjh072LJlC3a7nZYtWxIVFYXRaEQpxS+//EKLFi2oUKHCLe1LSkoKCxYswNPTk9atW9OwYcNbWv+tkpSURHJycpHtrVu3xtPTk6NHj5Kenk6TJk2uW8/GjRv1V2Q7duxIhw4dyqS/QrgjtwqkcXFx+gP5+WbOnEnfvn0JCwvDZDJx4MABNm3aROvWrTl79iydOnVi2bJldOvWjRUrVpCRkcGTTz7pUj+2bt1Kt27dqFWrFlWqVOHjjz/m119/BbjpNm5Vn0qyevVq5s6dq/+8f/9+AgIC2L17NwATJkxg165drFu3rlT1TZs2DZPJJIFUiILu4OupN63wu/YJCQnKZDKpadOm6dt+//13p2N27NihbDabUkqpHj16qNGjR7vcj759+6rnn39e/zk9PV3//mbbuFV9Ko309HRVpUoV9f333+vbzp8/r44cOVLqOqKjo9XYsWPLoHdCuC+3niMdO3YsDz30EC+++KK+LTQ0VP++W7duDB8+nNOnTzN9+nS2bt3KzJkz6dy5M6tXr2bq1KmMHDnSqc7Bgwc7jeCKY7Vai01xUlwbAJ9//jnNmzcnMDCQnj17cvDgweuWLynFiasmTZpE9erV6dOnDwDffvstffr00dOgLF26lOeee45nnnkGf39/OnbsqK8iJYQomVsH0j179tC5c+cS9w8aNIjVq1eTlZVFx44dCQsLo0OHDrzzzjs0adKEli1b8umnn3Lx4kXAsejH5MmTad269XXbfeGFF1iyZAn9+vUjJSVF315cGwAmk4lnnnmGSZMmceTIEV566aXrln/99de5cuUKmzZt4ptvvuHLL7/Ul/r7o65cucLEiROdMoRGR0cTGBioB/aUlBRWrlzJwIED2bNnD+Hh4XTt2pWzZ8+61LYQdzu3DqQnT56katWqJe5v27at/n1ISAj+/v7Url2bmJgYgoKCiIyMpHnz5vqIbPbs2bRr144aNWpct91u3bqxbt06rly5Qq1atXjvvfdQShXbBsBf/vIX/va3v9G0aVOefvpptm/fjt1uL7b89VKcuGLSpEmEhITwxBNP6Ntq167Nfffd51QuNDSUDh06UKtWLT7//HMsFgtLlixxqW0h7nZudbOpsOrVqzuNCP+Ifv36MX36dAYOHMjcuXP1y94bad26NfPmzWPp0qU88sgjdOjQocTFPkaOHMmUKVNo164dBoOB3NxcMjMz9SmBglxNcVKc/NFofs6o0jIajYSGhnL69Ok/3LYQ9wK3DqTNmjVj3rx5181vVFjh9CPPP/88f/vb31i7di0bNmxg9uzZN9WH7t27ExERwb59+/RAWrCNo0eP8sEHH7B3716aNGlCQkICc+bMKbFPfn5+N0xxcrMmTZpEgwYNSp26JF9GRgY7d+50moMWQhTl1pf2H3zwAYcOHaJfv34cOXKElJQU3n333RLnE4ODg9m9e7dT4PLx8eFPf/oTffv2pUePHvj7+7Nu3Tr69u3LhQsXiq3n119/ZfPmzWRnZ/P111+TkJBAvXr1SmwDIDMzE4CVK1cCjiBVXPkbpTg5deoUL7zwAtu3by/VZ5Sens7EiRPp3r078fHx+ldaWlqx5fNTjhw+fJjRo0ejlLrpACzEPedOPzZwMwo//qSUIx1Iu3bt9HQgPXr0UAkJCUoppdLS0hSgfvvtN6WUUgcOHFBBQUEKUA8//LBex+rVqxWg5s2bp5RS6scff1QWi0Vt3Lix2H5MmjRJT2Pi5eWl3n77bX1fcW0MHjxYmc1mFRkZqf7yl7+okJAQNX78+BLLXy/FydGjR1XNmjX142/kww8/1FOQFPxavXq13rd+/foppZSaMmWK8vLy0st4eXmpqVOnOtUnjz8JUZTbB9J8R48eVampqTes48qVKyo+Pl4lJyfr2xISElRAQIDKzc1VSilltVpV48aNVWZmZon1XL58WW3dulVlZGSUuo0TJ04opRzPbh46dOi65W02m9q5c6e6ePFikfr79Omj1q9ff8NzvVlTpkxRUVFRKiMjQ23ZsqXY85dAKkRRbjdHOmvWLPbu3Vsk1Uh+9tAb8fHxoWPHjvrPcXFxzJo1iyFDhuj5mt566y3i4uLw8vIqsZ4KFSoQFRVVqjYAGjdurH8fEBBAQEDAdcsbDAY9rXRBc+bMoW7dukXSON9K3t7etGrVymlb/iuiBbOdCiEc3CqQlkWqkZSUFMaPH++UUG/kyJFFEt2VF126dKFXr15lUne1atW4//77i9134sQJ1q5dS926dalZs2aZtC+Eu5JUI0II4SK3vmsvhBDlgQRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkds8/pSamsq+ffswm82EhoY6rVq0YMECvL296dq16x3soRDiXuU2I9LNmzfz0EMPMWrUKBo2bEjdunWZOnUqAIsWLdLfYRdCiNvNbUakAJ6envz0009YrVamTp3KgAEDqFOnzp3ulhDiHuc2I9KCTCYTf/7zn6latSoHDhwAHNkyY2JiqFy5Mn379uXy5csA5ObmMmzYMIKDg6lVqxZDhgzRV1UaN24c33//PbGxsfj7+9O7d2+nt6bi4+Np06YNfn5+xMbGcuTIEX1ft27d6N69+208ayFEeeWWgRRg+/btnD17Vk8LkpSUxMSJE4mPj2fPnj16nqO33nqLxYsXs3z5cr788kt++OEH3n77bQAOHjzI2LFjGTduHOvXrycpKUk/7uDBg/Tv359hw4Zx+PBhIiMj6dy5M1lZWQDYbDZsNtsdOHMhRHnjVoE0Ozubzp0706RJE2JiYhg/fry+sEeXLl1o3rw5kZGRPP300yQkJADw9ddf895779GkSRNiY2N55ZVXmDlzpl5nly5daNasGY0bN6Znz576CPfTTz+lbdu2+Pv7k5CQQGxsLNnZ2axYsQKAVatW6d8LIe5tbjVHajQa6dChA0FBQbRt25aIiIhiy3l6epKRkcHly5dJT093WmSjRYsWpKSk6Jf3BXl7e+sjzgMHDnD+/Hk++OADfX/Dhg2xWCy3+KyEEO7OrQKp2Wzm3XffLXX5ihUrUrFiRRISEvREeKdOnSI4OBiT6fqn7ufnR1RUFBMmTHCpz0KIu59bXdr/ET179mTOnDmkpqaSm5vLtGnTnDJpXu+4KVOmsG3bNsCRqrlgjvfRo0cTFxdXZv0WQriPuz6QxsXFcfHiRcLDw6lduzZZWVmMGjXqhse99NJLDB48mFatWqFpGi1atGDp0qX6/pUrV7Jq1aqy7LoQwk3cE+uR2u12EhMTMZlMhIWFoWlaqY9NS0vj+PHjNG3a1Gn7lStX0DQNHx+fW91dIYSbuScCqRBClKW7/tJeCCHKmgRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkdu8a5+faqSwZs2a4efnd0vaUErxyy+/0KJFCzw8PPjwww8ZOHAggYGBt6R+IcTdyW0eyP/xxx957LHHePDBB522f/zxxzRr1uyWtHHmzBmqVavGsmXLaN++PRUqVODAgQOEh4ffkvqFEHcntxmRgmN5vLJ8vz0oKIgdO3bQrFkzMjMzy6wdIcTd5a6YI126dCkTJkzglVdewd/fn+joaJYuXUpMTAyBgYH07duXjIwMANasWUNMTAyVKlUiOjqa5cuX6/V069aN4cOHc/r06WLbiYuLo06dOpw5c+a2nJcQwj24VSDNy8tjzJgx+ldubi4AKSkpjBgxgsaNG7Nu3Try8vIYMGAAn3zyCatWrWLt2rVMnjwZcKxp2qZNG7755huqVKlCr1699DxNgwYNYvXq1frizoUppUhPT8fLy+v2nLAQwi241aX99TRt2pTXX38dgF69erF8+XJatmwJwKOPPsrevXsBiI6OJjo6muTkZDw8PFiwYAEHDhygffv2+uLPJXn33XdvamFpIcS9wa0CaWlXyC+8TF7lypVJSUkBYOHChQwaNIh69erRuHFjLBYLly5dKpP+CiHuDW51aX8rDBs2jKFDh7JmzRomTZpE5cqV73SXhBBuzq1GpHa7nfj4eKdt+ZfvNyM7OxuALVu2cOHCBf1GVEG+vr40atSIPXv26I8/zZ8/nx9++IF//etfsqCzEELnVoE0OzubTp06OW0rmEepNMaOHcuzzz7Lt99+S6VKlejatSszZsygd+/eRcqOGTOGIUOG4OvryyOPPMK2bdtYsmQJH374oQRSIYTObR7Iv5VOnTpFeno69evXx2q1smfPnhJHtlarlYyMDCpVqoRSirNnzxIUFHSbeyyEKM/uyUAqhBC30j13s0kIIW41CaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEi053uwPVomnanuyCEuE3cOTN8uQ6kAElJSXe6C0LcFnXr1r1nf9/r1q17p7vgErm0F0IIF0kgFUIIF0kgFUIIF0kgFUIIF5X7m013s2PHjrF48WIAvL29qVu3Lu3bt8doNBbZX1B0dDRNmzblyJEjfPvtt6SkpNCwYUOeeuopqlevrpf77rvv2LFjB9nZ2TRq1IiuXbtSr149Fi9ezLFjx9A0jeDgYFq3bk21atX042bNmkVISAgPPPCA3gej0Uj16tWJiIigXr16AKxcuZJDhw4Ve24VKlSgX79+TnXlmzdvHhs3biQjI4O6devy4osvUqVKFf2c09LSaNasmVN9X3zxBbGxsdSuXfuPfNR3lc8//5y//vWvRbYvWrSIxx9/3GnbDz/8wGOPPea07Uaf/+LFixk4cKD+ewgwbdo07r//fpo2bcrixYvp0aOH01M1O3fuJCkpic6dO/Ptt9+W2PcHH3yQRo0a/aHzLs9kRHoHHTt2jIkTJ3L8+HGOHz/OuHHjeOGFFzh//rzT/i1btrB161b96+zZsyQnJ/P444+TnJxMeHg4SUlJpKWl6XWPGzeOuLg4NE2jatWqLFu2jOTkZAB+/PFHFixYwKVLl5g5cyYdO3ZkxowZ+rFz585l27ZtTn04efIkK1asoFevXrz44otcunSJpKQkvU+zZ89m5syZ+s979+4tUhfAjBkzePvttwGoWbMmS5cu5YknnuD48eN6e6+99prTuYAjkB47duxW/xG4nbNnz/KPf/xD/3zzZWVlMWzYMC5evKhvO3ToEOPHj3cqV5rPf+LEiVitVqfjpk+fzr59+wD473//yxdffOG0f9euXcybN4+cnBz9d2Djxo1MnDiR+Ph4fVv+7/bdRkak5cCYMWPw8PDAbrczdOhQPvjgAyZOnKjv//rrr/Hw8HA6ZubMmfj5+fGf//ynSH2bNm3i66+/Zvbs2bRo0aLYNqOjo/W/UF9++SUfffQRzz77LCZT8b8S7777Lh4eHpw7d44HH3yQb7/9lldffZWBAwcC8N5775GSklLkL1hBp06dYuzYsXz66af06NEDgFdffZU+ffowceJE/ZxPnz7N4MGDmTp1qjxLXMicOXMwmUwsXryYyMhIfXtiYiJWq5UVK1bw9NNPA/Dzzz9z5swZUlJSqFatWqk//9KYOHEiUVFRtGzZ0ml7UFCQ/o/yuXPnaN26NX//+99p0qSJq6dersmItBwxGAy8+OKL/PDDD/oIoSR+fn5cvnyZ9PT0IvumTp1Kt27dSgyihd13333Y7XbsdvsNy1apUoUGDRpw5syZUtVd0Pz586lWrRqPPPKIvs3Pz4+//OUvrFixQm/fw8OD8+fPM3Xq1Jtu426mlGLWrFm89tprua2ZTQAAIABJREFULFy40GnUePDgQcAx3ZLvl19+ASAhIQEo/edfGpGRkQwdOpTLly+7dE53Cwmk5UxYWBiAfhkF8PLLL9O3b1/69u2rX54//PDDREZG8uSTTzJlyhQuXLigl09KSiIiIuK67Zw9e5YtW7YQHx/PV199xeOPP47FYrnuMUop1q5dy65du6hTp85Nn9uxY8eoX79+kVFmSEgIOTk5nDlzBk3TMBqNfPLJJ/zzn/9kz549N93O3WrdunVYrVZeeeUVjEajHijBEUg7dOjA+vXrycjIIC0tje3btxMTE8P+/fuB0n3+paFpGl27dqVZs2YMHTr01p2gG5NL+3LGbDZjMBjw8vLSt7Vo0UK/5Pb29gbAy8uL6dOnM2fOHD7++GO++OILZs+eTWhoKOnp6VSsWPG67ezevZvt27eTmppKv379eOedd65b/uWXX+bw4cOcPXuWJk2a6JePNyMjIwMfH58i2z09PQHIzMzUtzVo0IA33niD//u//+PHH3+86bbuRnPmzOGxxx7DYDDw0EMPsWjRIh566CHAcWnfrVs38vLyWLFiBQaDgVatWtGuXTs2btwI3NznXxpjxoyhe/fuTvPr9yoZkZYzx44dw263O92dHjhwIK+99hqvvfYaAQEB+nZN0+jduzfLli2jcuXK/OMf/wCgRo0anDhxosQ28kcUW7dupVOnTmzevPmG/erRowevvvoq33//PQsWLHAK9KVVvXr1Ykc9+X297777UErp71y/+OKL1KpVixEjRqBp2j09X5qWlsaKFSv0y/KHH36Y1atXc/nyZex2O7/++ivh4eHExsayatUq4uPj6dKlCw0bNmT37t1A6T7/0sj/M/L19eXjjz/mo48+4uDBg/f0n48E0nLm66+/pmXLljf17nFgYCA9e/bk8OHDALRs2ZIff/wRm81WbPmCwSouLo4TJ07ccD7yiSee4Pnnn6dVq1Z/+C/M/fffz44dOzh37pzT9pUrV9KhQ4ciwVnTND755BM2bNjA5cuX3XpRC1fNmjULm83GE088Qd26dXn55ZfJycnhxx9/5OjRo1y5coXw8HC6devG2rVrWb9+PV27diUiIoK0tDROnDhRqs8/MDAQwOnuel5eHufPn9cfkSrogQceoG/fvsybN++e/vORQFoObNu2jZUrVzJo0CDWrl3L+++/X2T/li1b9K/U1FTOnz/Pl19+SWZmJtu3b2fRokU0b94cgL/+9a9kZWXx17/+laSkJNLT0/nuu++Ii4sr0na1atUYMmQIkyZN4vTp02V6nrGxsTRq1Ig333yTpKQkLl68yFdffcUPP/zAq6++WuwxVatWLbbf9xKlFHPmzGHQoEEkJSXpX8899xyLFy/mwIED1KpVi0qVKhEQEECrVq1o0KABQUFBVKhQgdq1a7Nv375Sff5hYWFUq1aNCRMm6P8Q//Of/8Tb29vpWeCC3nzzzRvOyd/tJJCWAy+99BL/+c9/CAkJYenSpYSHhzvt79evH88++6z+tXnzZo4fP86UKVP0+crKlSvzxhtvAODv78/8+fPJyMigS5cuNGvWjCVLlvDwww8X237fvn2pX79+mQcsg8HA9OnT8ff3158qWLBgAdOnTy/yGE1BsbGxPPXUU2Xat/Js48aNHD16lJ49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6tSpo4KCAu3atUtNmzYt7cOCmyGkAGCJ90gBwBIhBQBLhBQALBFSALBESAHAEiEFAEvuc0UsgNuCpUZujOtI4Ta+/vpr7d27VwEBAXr00UdVv379EvdfvHixfHx81L59+zt0hK6HpUZuUinNzA/cUmPGjDEeHh6mWbNmJigoyHh5eZmZM2c6x/ft22cmT55c5D4xMTHm1VdfvdOH6lJYauTm8B4pXN4PP/yg8ePHa8OGDYqLi1N6erpefvllvfDCCzpy5Igkafny5frqq69K+Uhdz7Jly9S1a1fn5CJPPvmktm7des0JmZOTk7V792716dNH0pUZ8CMiIhQXF1fimDsgpHB5H3zwgbp166aIiAjntvHjxyskJERz5szRzp079emnn2rnzp2Kjo7WpEmTnPulpKQoMjJS9957rwYMGKCff/7ZOTZ16lTVqVNH9913nwYPHuycOervf/+73n77bcXExCgwMFDLly+/cyd7h7HUyM0hpHB5u3btKrbssqenp5o3b64DBw7I4XDo8ccfV40aNTRy5Eh17tzZuV9KSor++te/asOGDdq1a5fee+89SdKcOXM0f/58zZ49W1u3blVaWpqGDBkiSTp06JDGjx+vJk2aKCkpSe3atbtzJ3uHsdTIzSGkcHmnT5/WPffcU2z7Pffco+zsbAUEBMjhcMjf31+RkZGqW7euc5927dqpcePGatiwoXr37q2EhARJV57Rdu7cWXl5eUpJSdGgQYP02WefOZfaaNiwoV566SUFBQWpXLlyd+ZES8GtWGokKCioxDF3wOVPcHnVqlXT8ePHi20/cuSIatWqddOPU758eeXk5OjixYtKTk7W0qVL9e9//9s5HhUVpdzcXElS7dq17Q/cBfy3S43UrFnTuX+PHj1KHHMHPCOFy2vRooVzqeWrTp06pW+++UYdO3Z0bjM3eaWft7e3fH199cc//lFr164t8ic4OPiWHvvdjqVGbg4hhct77bXXtH37do0dO1bnzp3T3r171bNnT/3ud79Thw4dJEnBwcFKSkoq8mHS9Xh4eKhHjx56++23lZmZKUlKTEy85gcs7o6lRm5SaV9/BdwKW7duNaGhoUaS8fb2Nn379jVnz551jufm5ppHH33USDIeHh7m0qVLxa4jnTRpkomKijLGGHP+/Hnz1FNPGUlGkmnSpIlZtmyZMcaY2NhY079//zt7gqUsJyfHHD58uNj29PR0U1hYWGx7UlKSyc/Pv+ZjlTTmqvhmE9xKUlKSQkJC5OvrW2zs8uXLiouLU8WKFdWwYcOberzU1FRJUo0aNW7pccK9EFIAsMR7pABgiZACgCWuI4VL+vU66nB9rvwuIyGFS3Llf3RwP7y0BwBLhBQALBFSALDEe6QAnPLz85WRkaGQkBDntt27d2vDhg2qX7++2rZte8MP+lizCXAx8fHxWrly5XXHX3zxRfn4+Gj8+PHavXu3KlSooIYNG2r48OHy9va+qfsHBARox44dysjIcH53X5IOHjyoefPm6c0335Snp+s/JykoKFDPnj3Vpk0bvfTSS5KksWPH6v3339czzzyjXbt2KSAgQMuWLZOHx7VfzLJmE+CCVq5caaKiokxUVJRp1KiR8fT0dN6OiooyJ06cMD169DAPPPCAGT58uBk4cKAJCAgwrVq1Mnl5eTd1f2OMGTp0qOnatWuRn7169WojybmekStbs2aNcy6CKVOmOLd/99135siRI8YYY1JTU40ks3Hjxus+zm91zSZCCrcxf/584+/vX2TbggULjI+Pjzl48KBz2/bt20358uXNxx9/fMP7G2NMdna2CQgIMD4+PubkyZPO7e4U0mHDhpn58+eb0NBQ87e//e2a+xw7dqzEkCYlJRlJRSaL6datm/nTn/5U4pg74MMmuLV//OMf6t69u3MyYUl65JFH1K9fPy1duvSmHmPBggVq1qyZWrZsqc8+++x2HWqpmjp1qnr37i3p+tfozpkzRw6HQ82bN7/mOGs2AW4qJSVFoaGhxbbXqlVLhw8fvqnHmDVrlnr16qUnn3zSbUN6I8eOHdN7772niRMnXncOUdZsAtzU2bNn5efnV2x7hQoVlJ2dfcP779mzR1u2bFH37t3VvXt3xcXFKTEx8XYc6l1tyJAheuyxx0pcGuS3vGYTIYVbczgcOnbsWLHtP/30kxwOxw3vP3PmTHXq1EmBgYG6//77FRYWpjlz5tyGI717zZ8/X5s2bdKMGTNK3O+X6zJdtW/fPrVo0aLEMXdASOHWWrZsWWzd+cLCQi1dulRPPPFEife9dOmSPvvsMy1ZskRlypRRmTJl9OOPP2ru3Lm6fPny7Tzsu0ZWVpZeeeUV9erVSwcOHNCGDRu0adMmSazZ9EuEFG7t9ddf1+HDhzVixAhlZWUpJSVFMTExunDhgp5//vkS77tgwQJdvHhR58+fl7lyhYtOnDih9PR0rV+/3rnfd999pw0bNjj/uMsHKJIUGxur9PR0zZgxQ23atFGbNm2cH0qxZtMvlPJVA8Atc73Ll/bs2WNatGjhXK8pOjrapKam3vD+kZGRpl+/fsX2a926tYmJiXFe/vTrP/Pnz7+l53U3Y82mK/hmE34zUlJSFBgYqHvvvbe0DwVuhpACgCXeIwUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwNL/Bww6nVhM8tI9AAAAAElFTkSuQmCC", + }, + { + name: "Mobile-Multi-Invoice", + category: "Mobile", + + template_id: 1003, + ImageUri: + "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", + }, { name: "Web-Invoice-1", + category: "Web", template_id: 3001, ImageUri: "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hBASEAghhBBCAgIhhBBCUIsDAtM0+f777/n+++85efKkr4tToZ07d3oWeSlWUFDApk2bKuX8DofD8zuUXkBGiPNRU+pWWloaKSkpXtukbglRsVobEJReovX555/nmmuuYdiwYaxcuRKA7777rtwV2s7V9u3b2bZtW4X7H3jggQrzefbZZ/nll1+8tqWlpXH//fdfcLlAlo4Vla903Vq+fDkDBw5k2LBhzJkzBzhznThbR48ePe1SygsXLuSbb74pd9/y5cuZO3eu1zapW0JUrNYGBHBqidawsDBeeeUVVqxYwRdffMHVV18NwJNPPlnmRn306FGvCVdKLnFcrOTSrADz589n48aN5Zbh5MmTzJ8/n4ULF3ptz8rKYv/+/V7blFJs27YNl8vltd00Tc/6CeXlD+6WhpKvqhR/l6VjRVUoufzxtGnTeO211/jiiy+YPHkyUH6dyM/PJzk52fP9bOrWxo0b+eijjyosxwcffMD//vc/r212u53t27d7bbuQupWSkuLVAiB1S9RWl8TUxcHBwRiGwapVqzzBwJYtW/jzzz/573//y+DBg3E4HDzxxBO0bNmSPn36MGnSpHKXOC69NOvUqVNZt24du3btwjTNMgsdzZ8/n/vvv58PP/yQO++8E4C1a9dyzz33EBsbS2JiomdCo7Fjx2IYhlczbP/+/YmKiqJOnTo8/fTT/Oc///HKf+bMmcTHx9O2bVucTievv/661/e33367yn9fcWlr1aoVn332GVOmTEHXdbKzs73qxJgxY2jZsiV9+/alWbNmzJgx46zq1uzZs1m6dClbt25lxowZTJs2DV0/9QyzZ88eQkNDSUlJITs7m/DwcDIyMhg+fDidO3dm06ZNXHPNNcD51a3Zs2fz8MMP88cff5CZmcnnn3/O0KFDpW6J2ktVsio45XnJz89XjRs39nxPSkpS/fv3V8OGDVNHjhxRSil1xRVXqH379imllFq2bJm65pprPOnnzJmj/vOf/yillDp+/Lhq1aqV2rhxo7r++us9aa688kq1e/du9a9//Uu999575ZajR48e6tixY2rgwIFqx44dnnxPnDihlFJq0qRJatmyZerTTz9VM2fOVEoptW/fPnXFFVd40q5cuVIppcrN/9dff1WNGzdWycnJSimlMjMzvb4X69atm+dahbgQpetWRkaGmjRpkurQoYPauHGjUkp51Yn8/HwVGhqqTp48qZQ6t7q1bNkyNWnSpHLLMWXKFPXll1+qmTNnqldffVUppdT999+vVq9erZRS6r333lP/+te/zrtu7d69W8XGxqoFCxYopaRuierhy3tore4yKKljx44kJibSr18/7r333nLTNG3a1PP3rVu3kpSUxIwZM3jhhReYOHGi19KsM2bMOOPSrD///DOGYfDbb7/Rtm1b5s2bR3p6OpmZmQQHB3ulXbVqVYVztDdv3hyg3PyjoqJ49913uf3223nuueeoW7eu13chqlpERARvv/02L7/8MkOHDi03Tf369QkMDAQqp24VFBQwf/58AgICiIqKYv78+QCsXLmSFi1aeKU937rl7+/v6ZKYNGkSNptN6pao1S6JLgOA3NxcgoOD6devn2dQocViIS8vr9z05S1xvHHjxnKXZrVYLJ414Et666236NChA2vWrCEiIoJ3332Xxx9/nPT0dI4ePUpERIQnbbNmzdiwYQNjx46t8BoqWhrWarWyYsUKWrRowS233ELHjh29vkdGRp75BxLiPBXXrcsvvxyXy4VpmhXWCTi3urVr165yz7Nw4UI6dOjgWZ306NGj7Nixg+bNm7N+/XpGjBjhSXshdevQoUN8+eWXXH/99SxdupS+fftK3RK11iURELhcLvr370+9evXIycnhlVdeAWDgwIEkJCQQFxfHDTfc4HVMRUscl7c065VXXsltt93Gp59+yueff05wcDDHjx/nyy+/ZP/+/QQEBADusQPfffcdjz/+ODExMXTu3Jn09HRGjhzJhAkTuPLKK+natSuNGzcu9zrKWxp28uTJtG/fnk6dOtGxY0cMw6Bt27ae73Xr1q3aH1dc8u6++24OHjxIVlYWs2bNQtd1rzpRelDgudStLl268NNPPxEfH89jjz1Gv379AJg7dy5PPvkkcXFxABiGwfvvv89DDz3EiBEjeO655zAMg/79+5933XriiSe45ZZbyMzMRNM0YmJivOqa1C1R29TaxY1KL9HqcDjYtm0bHTp0wM/Pz8elq36ydKyoLOUtf7xt2zaio6MJCwvzYcl8Q+qWqEyyuFEVKblEq9VqpVu3bpdcMCBLx4qqUHr5444dO15ywYDULVHb1NoWAiGEEKKmkRYCIYQQQviUBARCCCGEkIBACCGEELX4tUPTNFm9ejUAPXv2JCgoyMcl8r3du3dz8OBBGjRoQExMjK+LI2ooqVtlZWRksH37dmw2G7Gxsb4ujhDnpda2EJReka2wsJD777+fPn36EBcX55mH/J577vH843Y+qx/m5+dzxx13cO211zJo0CCys7PPuEIbnHkVtzMJCgpC0zTPZ9euXQBcccUVFS5Ju2zZMh544AGeffbZ885XiNJ1C+C1114jNjaWAQMG8I9//AOAjz/+mFmzZgGVV7dM0yyzUFhpZ5PmdK6//nqvuvXOO+8Ap69bycnJnnkLhKixKnsu5Co45XkpPd/61KlT1SOPPHLaY0qubXC2PvvsMzVhwgSvbeXNv37gwAGVmpp62jR2u11t2rRJ2e32s87/m2++UTfffHOF+3/77TeVn5/v+b5o0aIK54YX4myUrltLly5V8fHxKi8vr8JjKqtu5efnq9atW3ttK123yktzPnXr6NGjql27dqqwsLDc/aXrVkZGRpl8hThXvryH1toWgtLmzp3Lv/71L69ty5cvp1GjRvz4449eqx+uXLmSa6+91rOe+4IFC5gyZUq5523RogWbN28mJSXFs63kCm2mafLZZ5/x97//nenTp3tWQyydJjk5mR49evDaa6/Ro0cPduzYcVbX9eijj3qu6z//+Q8hISGAeza4nj178uKLL3pWfhOiKrz11ltMnjzZMyMnQFpaGh06dODpp58uU7dmzZrFf//7XwCOHDlCr169yj1veXXr66+/JisrixkzZpCSklJu3Sqd5nzr1qxZs7j//vvx8/OTuiUuDZUdYVTBKc9LyaeY9PR0ryeakm666Sa1Zs0apZT3U8xHH32k7r33XqWUUsOGDVObN2+uMK8lS5aoFi1aqEceeUTZ7fYKn/7Xr1+vIiIiyk1zww03qPXr1yullPr222/VLbfccsZr/OSTT8q0DhRf55133qmWLFmilHK3IowaNUopJS0E4sKVbiHo0qWLZyXPkl555RU1a9YspZR33Tp06JDq1KmTUkqpZ599Vr344osV5lW6blX09F+ybpVOcz51KzU1VbVs2dKrdeBMdUtaCERl8OU99JJoIahbty5//vknOTk5Z33MiBEj+PLLL0lLS+PAgQN07dq1wrSDBw9m8+bN7N+/n7feeqvM/tdff52BAwfyxRdfYLPZyMzMLJPmt99+Y8mSJcyYMYP169cTHx9/2vKZpsn06dPLtHqUJzo6moMH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gCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgSVVHH0Bbq1QqHX0IAHSQUkpHH0Kn1eWCIPGCAE5vlUrFONUO/A/hyXHLAAAQBHReBw4cSKVSSaVSydChQ3PPPffk4MGDbb6f7373u/n85z/f6jLLly/P5s2bkyTNzc0599xzs2XLljY/FvhfHT58OOeee2527dp1Svf70ksvZd68eTl8+PAp3S//f4KATu9Pf/pT5s+fn8ceeywzZsxomb537940Njbm0KFDLdMOHTqU1atXH7V+U1NTnn766TQ3Nx81ffXq1XnppZcyceLETJ8+vWV6KSUrV648Kj5uv/32/O1vf0uSVFdX58EHH8zAgQNbll+1alVefPHFo7Z/ZHpTU9NJXgE4sUceeSTr16/PggULjpm3fv367N27N0myZs2ao34GTvS6PZGXX345q1atarkVsnnz5kyZMkUQdCaljbXDJjvV/jl19u/fX5KUJ598spRSyvz580uPHj3KoUOHygMPPFD69u1bRo0aVc4777yyfv36sm7dunL22WeXiy++uHzmM58ppZSyYMGCUltbWxoaGsrAgQNLc3NzGT58eGloaCjDhw8v3/zmN0uSMnHixFJKKb179y7vfOc7S319fRk8eHBZu3ZtmTdvXqmpqSmXXHJJ+frXv14+/vGPlyRl1apVZfv27eXiiy8ugwYNKjU1NWXOnDmllFKGDx9eLr/88nLRRReVurq68tRTT3XMRaRDnMpx6rrrrivXXXddaWhoaJl2xx13lMGDB5cRI0aU7t27l3e9611l5MiRZcCAAaWxsbHV1+3SpUtLKaVMnTq1TJ48ufz9738v9fX15cILLyzDhg0rDQ0NZd++feW2224rScr48ePLww8/fErOtSuM/x15DoKATuv/BsGTTz5ZkpRNmzaV6urqsmzZslJKKZ/97GfLLbfcUh566KHSv3//8uc//7mUUsqePXtKdXV1Wbx4cSmllB07dpRSXhn0pk2b1rKfWbNmHRUEjzzySNm/f3/54Ac/WD71qU+VUkrp379/efTRR486rlWrVpW77767XHnllaWpqaksXbq0VFVVlZ07d5bhw4eX73//+6WUUiZOnFhmzpzZ3peL08ipGqe2bdtWqqqqytatW0ttbW357W9/W0p5JQhuueWWUkopH/7wh8vnPve5UkopY8aMKXPmzGn1dXu8IEhS1q5dWw4fPlze+ta3lp///Odl1apVJUlpbm4+JedaStcY/zvyHNwyoMtobGxMz549c+jQoTQ3N2fu3Lm5+uqrs27duvTt2zfXXnttbrzxxowdOzZTp07Ntm3b0tzcnPe9731Jkn79+rVsa8yYMSfczxlnnJHu3btn9OjR2bZtW6vH9I9//CMjR45MTU1Nrrzyyhw8eLBlnbe85S1Jkvr6+rzwwgsnefZwrIULF2bgwIHZtGlTLrzwwvzoRz9qmdenT58kSa9evVJbW5vklZ+BAwcOtPq6PZF+/fqlUqmkvr4+//rXv9rvpGg3goAu4fHHH8/s2bNz55135uyzz05dXV0mT56cX/7yl1m8eHHmzJmTmpqafPnLX87DDz+c+++/P29+85tTV1eXJUuWpKmpKTt27Hhd+9q3b19efvnl/PGPf8yIESOSJFVVVdm9e3fL/dgjzj///DzxxBNpbm7O008/ndra2gwaNKitTx+OUUrJvHnzMnr06KxYsSKjRo3KkiVLXtc/1id63Z511lkt8bp9+/ZWt1FV9cq32o/3c8HpSRDQ6V122WW544478slPfjLf+ta30r1798yePTtTpkxJpVLJu9/97uzfvz+PP/54Bg4cmClTpmTChAkZMGBAZs+enVmzZqV79+65+uqrX9cH/D7wgQ+kW7dueeaZZ1q+fXDNNdfkox/9aK644oqjlr3tttty4MCBDBs2LB/5yEdy7733pkePHu1yHeDVfvWrX2XLli257777cs8992TOnDnp379/Fi5c+Jrrnuh1e+ONN2bWrFnp1q1bVq5c2eo2hgwZkgsuuCB9+/bNV77ylbY6LdpR5T/3LNpugx38Czc6ev90bXV1dfnZz36Wyy+/vKMPhU7MONU+usJ17chz8A4BAOAdAoBTzTjVPrrCdfUOAQDQobrkw4084AI43RmnON10ySDo7G8ZAV1bV3hr+3Qksk6OWwYAgCAAAAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAAEQQAQAQBABBBAABEEAAASao6+gDaQ6VS6ehDAGiVcYrTTZcMglJKRx8CwAlVKhXjVDsQWSfHLQMAQBAAAIIAAEgX/QxBkjQ2Nmb58uXHnTdkyJDcdNNNWbp0aZYvX55du3Zl5MiRmT59empra1/XuqWU3HDDDVm0aFG6devWMv/+++/P2972tnzsYx9rl/MC6Gy2b9+eb3zjG9m6dWtGjRqVGTNmpKam5oTLL1q0KMuXL0+vXr1y6623ZsyYMa9rHienywbBs88+m9/97ndJkrVr12bnzp257LLLkiT79u3L3LlzM23atEyYMCGDBg3KD3/4wyxbtix/+MMfXnPdJPnFL36Rhx56KBMmTMikSZNa9vud73wnl156qSAASLJ///6MHz8+STJ27Nh87Wtfy4YNG7Jw4cLjLj9//vxMnjw5t956a1auXJmrrroqjY2NOf/881udRxsobawdNnnS+7/55pvL0KFDW/7+3HPPld69e5dJkya1TNuyZUuprq4u9957b6vrHnHDDTeUJOX973//UdOHDh1abr755pM9DaAL6+hx8lSaPXt2qaqqKtu2bSullPKlL32pdO/evezcufOYZfft21f69etX7rrrrlJKKS+88EKprq4ud999d6vzjugK17Ujz+EN+RmCRx99NLt27cpdd93VMu2cc87JuHHjTnir4NWef/75LFu2LJMmTcqvf/3rrF+/vj0PF6DTeuKJJzJ8+PAMGDAgSXLppZfmwIED+ctf/nLMsuvWrctzzz3XchugT58+GTFiRNasWdPqPNrGGzIINm/enCR5xzvecdT0/v37Z9OmTa+5/o9//ONUV1dn7ty56dOnT+bNm9cuxwnQ2T377LPp06dPy9+P/HnHjh3HXfbVyxz5844dO1qdR9t4QwbBGWeckSTZs2fPUdN37tyZM8888zXXX7BgQa699tqceeaZuf7667No0aI0Nze3y7ECdGa9e/du+exV8t9xt1evXsddNskxy9fW1rY6j7bxhgyCIx9AWbFiRcu0PXv25Pe//30uuuiiVtddsWJFVq9enSVLlqRSqeQHP/hB/vnPf+anP/1pux4zQGd01llnZePGjTl8+HCS/75De9555x2zbH19fZJk48aNLdM2btyYYcOGtTqPttFlv2XQmnHjxuWCCy7I9OnTs3v37vTu3Tvf+9738vzzz+eLX/xiq+s+8MADqaury09+8pOWX5M5efLkLFy4MNdff32SV94K+81vftOyzjnnnJMhQ4a02/kAnK4+9KEPZf78+Zk2bVq+8IUvZPHixbnmmmsyYMCA7N69O1OnTs173vOefPrTn87gwYNzySWX5Nvf/nauuuqqPPXUU3nxxRdz0003tTqPNtLWn1Jsh02e9P6P902Bv/71r2XIkCElSUlSevbsWR588MFW1927d2/p2bNn+cQnPnHUMjNnzixvetObyjPPPFOGDh3ass0j/82aNavtThDo9Dp6nDzV7rvvvlJXV1eSlIEDB5YNGzaUUkpZu3ZtSVJGjx7dsuzWrVvL+PHjW8bPV3+LoLV5pXSN69qR51D5zwG0mY5+aMf/sv+DBw9m/fr1OXDgQBoaGo76BUMA7aWjx8mO0NTUlDVr1mTEiBFH/VKixsbG1NfXt3wL4YgNGzakR48eefvb337Mtk40rytc1448hzd0EAB0BONU++gK17Ujz+EN+aFCAOBoXfJDhZ6JDZzujFOcbrpcEHT2t4sAoCO4ZQAACAIAQBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAECSqvbYaKVSaY/NAgDtpM2DoJTS1psEANqZWwYAgCAAAAQBABBBAABEEAAAEQQAQAQBABBBAAAk+TfASBETqTACzQAAAABJRU5ErkJggg==", }, { name: "Web-Invoice-2", + category: "Web", template_id: 3002, ImageUri: "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vXr1qFixIvfddx979uwBYOLEifTu3RuA+Ph4Bg0aRPny5WndujVz5swhIyPDa7KQUorp06d71W38+PEopShdujSRkZFs27bNa//MmTMZN24cAI888kie6yml2Ldv3wXLEUJcidyLC4NpakytQNmuyY/GClpjmgrTNC7/xzN8IAGByOuGDwhyOnDgAIMGDfLqWgsNDeW+++5jwYIFAJw6dYply5YxZMgQfvrpJyZOnMh//vMf1q1bh1KKBx98ME/X3IABAzh9+jSbN2/m448/plWrVp59M2bMYNWqVaxatYo+ffrkqdO9995LTEwMd911F127duXIkSOefZ9//jlffPEFDoeDF154gVWrVvH000/TuHFjzzUrVqzosxytNfv27SvUz0+Iksn3xL+8YcLV45oPYKEwGnLX5EMJCEReJSogAFi5ciVvvPGG17aHH36YH374gfT0dObNm0ezZs1o2bIls2bNYsiQIXTr1o1bbrmF4cOHs2PHDuLi4jzn/v3336xYsYIvvviCmjVr0q5dO5o0aZKn3Jo1a1K1alWfdSpfvjzPPPMMZcuWZe7cuQDs2LGD6OhowsPDWbhwIQ0bNqRr167Url2b4OBgunbtSteuXQkICPBZznfffUd4eDgbN2680o9MiBIunwWIrmGbqrUmPS3dtdTwlZJYQOSjxMwhcI+ZTZs2jccff5wKFSp4tvfo0YPg4GCioqKYP38+Q4cOBSA6OprOnTt7rtG4cWMAr4DgwIEDhIaGUqlSJZ/lzp49m6CgIAYNGkTNmjULrGPz5s091541axa9e/fmlltuYdasWTz44IMFnpu7nB49ejB58mRatGhR4HlCiAtQORYHyn60Tymw2cBiyV4KOHueX/Z8Qc+/Xgqa85frXEOB1xOMWmG1lMbpTMH0zCG4dFar62+e1U8ePRR5lZiAwN3N36FDB9555x3Gjh3r2W6xWBg0aBDvvvsuO3fuZODAgQBUqFCB2NhYzzX++ecfAGrUqOHZVrFiRZKSkoiLi/N03+c0bdo0WrdufVH1++uvv4iIiCA9PZ0vvviCMWPGUKZMGZYvX87+/fupV69evufnLicsLMwz/0AIcQVyTSp0fbnQBAZorDYDpa5+diCtNYF2G84sA6fz8r/iK6WwGIrAIAsWqwZZn0jkUGJixJzP5j799NOeCYFuQ4cOZd26dfTr14+w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yXCByEECAiGEuEiumfiuxtWicn7rdrfqvr/xZ5/tY/vlnEMB+y50vZznSc+A8CaPHQohhBDXUHFt86S/SAghhBASEAghhBBCAgIhhBBCIAGBEEIIIZCAQAghhBBIQCCEEEIIJCAQQgghBBIQCCGEEAIJCIQQQgiBBARCCCGEQAICIYQQQiABgRBCCCGQgEAIIYQQSEAghBBCCCQgEEIIIQQSEAghhBACCQiEEEIIgQQEQgghhEACAiGEEEIgAYEQQgghkIBACCGEEEhAIIQQQghKQEDQuXNnlFJUq1aNJ554gsTERACCg4P5888/Wbp0KWFhYT7Pe//9931e85FHHkEpledn7969PPXUU1SsWJE6derw5JNPesoToiQaP358nvskMTHR674cNGgQR44cASA9PT3fe2j+/Pk8++yznmt3796dAwcOFMn7EuJGdMMHBADvvfceGzduJCoqiq+//hqAlJQUtNaXdb0XXniBVatW8fTTT9O4cWNWrVrFqlWr+Oyzz9i4cSMbNmxgzpw5/P7777z00ksAZGRkEBMTU2jvSYjrRefOnT33yKpVqwgKCgLgtdde448//iA1NZWpU6cCMHHixHzvoa1bt7Jw4UIAzpw5w+rVq9myZQsg95cQhaFEBASHDx9mxYoVZGRk0K5duyu+XsOGDenatSu1a9cmODiYrl270rVrV2bPns348eOpVasWHTt25J577mHNmjUAPPfcczRt2vSKyxbieta2bVusVqvndeXKlbFarcTGxgIwY8aMfO+hbdu2ERMTw+bNm1m8eDGmabJ161ZA7i8hCkOJCAhWrFjBRx99hN1uJyEh4aqUcerUKRITE7n55ps92xo1asTJkycBGDZsGB999NFVKVuI4mzfvn288cYbvPHGG8THx3u2v/LKK/j7+7Np0yZGjx59wXto8+bNNGvWjMWLF/Pzzz/TrFkztm3bBsj9JURhKBEBwfDhw9myZQsvv/wyr7322lUpo2zZsgCebzoAMTEx1KhRA4DmzZvz6KOPXpWyhSjOIiIiWL58OcuXL/dq7MeNG8fo0aPJysqicePGBd5D+/fv59y5c0yZMoUFCxawfPlyXn/9dTZv3oxpmnJ/CVEISkRAAOB0Ovnrr79QSl2V61utVvr06cPkyZM5duyYp1eid+/eAOzZs4dvvvnmqpQtxPVGKUXp0qV55513CA0N5dlnny3wHtq6dSuNGzemR48eOJ1Ounbtyh133EFKSgr79u2T+0uIQlAiAgL3H5s1a9Z4Ji/llJSU5DUL+tixY57z3NtGjhx5wXImT57M8ePHqV69OpGRkbRu3ZoxY8YA8OGHHzJ06NDLnsgoxI1Ea43WGj8/P7744gu++OILfvvtt3zvoS1bttC8eXMAHn30UQYNGoTNZqNJkyZs2bJF7i8hCoHSxeAOUkrdMDdyVlYWO3bsoHz58l7dowkJCZw8eZKGDRsWYe2EKP7yu4cKIveXuJ4U1zZPAgIhhBDiGiqubV6JGDIQQgghRMEkIBBCCCGEBARCCCGEkIBACCGEEEhAIIQQQggkIBBCCCEEYL3wIde36Ohojhw5QoUKFWjUqNFVL2/Tpk2kpqYSHh5OpUqVrnp5QhRnCQkJbN++HZvNRocOHa56eQcOHCA2NpZKlSoRHh5+1csT4kZyw/cQzJo1i6eeeoqFCxeydOlS3n33Xc++2bNnM2HChEItb8aMGQwdOpSlS5cW6nWFuB5t2rSJhx9+mA8++ICMjAzuvfdez76DBw/SrVu3Qi0vKiqKsWPHet3nQoiLc8MHBACjR4/mlVdeAWD8+PH8+eefeY7Jyspi27Zt2O12r+12u53t27djmiapqakcOHDAa39SUhI7d+70vP7ss8/o06fPVXgXQlyfunXrxoIFCwBYtGgR77//vs/jdu7cSVJSks/tGRkZOJ1OduzY4bWgi/u+zcrKAmDUqFG89NJLV+FdCHHju+GHDHJSSnHHHXfwxBNPsGLFCs920zTp27cvderUYf369UyfPp1WrVrRrl07mjZtSlpaGv/88w8333wzmZmZVKxYkenTp7NgwQImTZpEixYtiI2NZcmSJfj7+xfhOxSi+FJKUadOHZYsWULHjh092Q3Blfnw9OnTHDlyhAceeIARI0YwcuRIUlNTCQsLY8WKFTRv3pyyZcuyZcsWVq9eTXR0NAMGDODWW29l48aNzJ8/nwYNGhThOxTi+lYieghyqlixIq+99hpDhgwBXH+kDMNgyZIlPP/883Ts2JFFixYBcO7cOd5++22++uorDh06xNy5c/nyyy/54YcfsNvtTJgwgfXr1zNnzhzatWvHV199VYTvTIjizzAMvvjiC0aMGEFKSoon++hbb73F1KlTGTx4MPPmzfMcf9999zFt2jSaN2/O//7v//LJJ58QEBDA/v37eemll5gxYwYzZszg3Xff5a233iqqtyXEDaFE9RC4M6zdfffdrFixgtmzZ9O9e3fi4+MZOHAg1apVIysri9DQUM85NpsNcKU3dv9YLBaOHj1Kamoq7733HgB+fn7Uq1evSN6XENcD9/1XvXp1Jk6cyFNPPYWfnx8Aw4YNIyEhgfr163PmzBnPOTnvP4vFAkCZMmWw2+38/fffLFmyhGXLlgEQGRl5jd+REDeWEhUQ5PTmm2/Spk0bANauXUvlypWZPXs28+fP5/fff7/g+TfffDP+/v6MHTuWoKCgq11dIW4o9957L1FRUezfv5+zZ8+yfPlyjhw5wokTJ/jpp58u6hrNmjWje/fudO3a9epWVogSosQGBAEBAcybN49FixbRqVMnRo0axfDhw0lKSuKff/654Pk2m41XX32ViIgIKlWqRGBgID/++OPVr7gQN4j33nuPp556ijJlylC/fn3uv/9+tNZkZGRw4sSJC54/adIkBg0aRPny5UlOTmbRokVUrFjxGtRciBvTDZ/+ePz48dSoUYMRI0Zclev7MmbMGFq0aMGjjz56zcoUojhaunQpX331FV9++eU1K3PhwoX88ssvzJw585qVKcSlkPTHReijjz7itddeuyZlDR8+3DMpUQgBK1eu5P77778mZU2bNo3XX3/9mpQlxI3mhu8hEEIIIYqT4trmlYgeAiGEEEIUTAICIYQQQkhAIIQQQogSEBBER0ezevVqdu3aVdRVyVdGRgZbt27Ns33v3r0kJCQUShkHDhxg9erV7N27t1CuJ8TFSEhIYPXq1fzxxx9FXZUCbdiwAdM0vbadPHmS6OjoQrn+6dOnWb16NRs2bCiU6wlxNdzwAUHObIcHDx6kX79+3H333Tz22GMAxMfH8+uvv15xOaZp8s033+S7/+TJk7zwwgv57hszZkye7e+++y6bNm264rqBZIETRSNntsP09HRGjBjBHXfcwd13301iYuIF75tLsWLFChITE/Pd/8gjj+S7b/DgwXkSmy1dupRZs2YVSt12797Nq6++yuDBgwvlekJcDTd8QADnsx1Onz6dXr168fPPP/Ppp58CsGXLFq+10932799PSkoKkDejIeTNsma323n55ZfzrcPMmTP59ttvcTqdnm1aa3bu3Om1DeDs2bPExMTkuUZ8fDxHjx71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lMI3n07WrVu3rFmzJv/85z+z7777JkkGDBiQu+66K3379t1suVWrVm3zEcVHHXXUzt40aPYOOuignH766bnhhhvSu3fvTJ8+fbP9au3atVm+fHkGDx6cZNN3+m8+PrxNmzZJko4dO+bVV1/d5iOKPdYb/jstIgiOOeaY9O/ff5vLHHrooXn88cdTW1ubRYsWpW/fvltcbmuPKE423UNwwAEHbJcxw+7i4IMPzsknn7zNZfr06ZPFixfn1FNPzcKFC1NTU7PF5bb2iOJk0z0E+++/f/bYo0VdCYXtpkUEwbtx7bXX5lOf+lT69OmTl19+OXPmzMm99977tuX+/0cUV1VVpba2Nt/4xjeyYMGCHHvssXnllVfygQ98YBdsAbx/jRkzJkOGDMnMmTOzatWqppt/36qysrLpEcXdu3dP27ZtM3369CSbYn3SpEk566yzdubQYbfh4UYAsBM113Oen60BAIIAABAEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAARBAAABEEAEAEAQAQQQAAJGm1qwewo1RUVOzqIQCwGyil7Ooh7BS7bRAkLed/IvDuVFRUNMvjQnMd15a8n8a6PbSkby5dMgAABAEAIAjYhd54441UVFSkoqIihxxySK677rps2LBhu7/Oj370o3zxi1/c6vx169ZlwoQJ2bhxY5KkoaEhhx56aP7xj39s97HQPN17772prq5u+vucOXM2+3tzM3jw4FRUVKRnz54577zz8vzzz2912fnz5+eBBx7YiaPbugsuuCB33333Tn/dGTNm5Lnnntvpr/t+IwjY5f785z9n4sSJmTt3bq666qqm6a+//noeffTRNDY2Nk1rbGzME088sdn69fX1WbhwYRoaGpqmbdiwIY899lgaGxszcuTIXHnllU3zSil5/PHHm+Ljueeey+jRo5uCoLKyMr/4xS/Sq1evpuUXL16cf/3rX5u97pvT6+vrt9NXguaqvr4+ixYtetf/r9esWZMlS5Yk2fJ7dnv45je/mQcffDDr1q3LjTfemGRTZC9cuDCvvPJK03Ljx4/P7NmzN1t3S/vWjvbaa69l6tSpmTBhwtvmrVixIitWrEiSPP/883n11Vc3m//888/n6aefflevs6X98rOf/WyeffbZ/2L0LURpBnbEMJrJprENdXV1JUmZP39+KaWUiRMnlrZt25bGxsYyefLk0qlTp3L00UeXvn37lqeffro89dRTZf/99y9HHXVU+fznP19KKWXSpEmlqqqqHHbYYaVXr17lueeeK0nKEUccUfr161fuueeekqSMHDmylFJKhw4dysEHH1y6detWDjjggLJkyZIyZsyYkqQMGTKkTJ8+vQwfPrwkKYsXLy4rVqwoRx11VOndu3dp3bp1GT9+fCmllJqamnLiiSeWgQMHlurq6rJgwYJd80XkPdnacWHWrFmlffv2Ze7cuWXu3Lll/PjxpUOHDqWUUu6///7SvXv3ctBBB5UuXbqU++67ryxbtqwkKatWrSqllDJw4MAyefLkctttt5Xu3buXmpqaMnjw4PLkk0++7T37Xsa1NYMGDSrf/OY3S319fRk+fHg5++yzSymlXH/99eXYY48tHTp0KF/60pfKggULSo8ePcrBBx/ctMyW9q334j89tt50003ljDPOKO3bty8vvvhiKaWU2267rey3336lb9++pXXr1uXYY48tNTU1paqqqkycOLFs3LixjBo1qlRVVZXOnTuXs88+u2zcuLFccsklZezYsaWUUqZNm1YOOeSQUsqW98sJEyaU1q1bl2OOOab8z//8z3sed0s6PzWLUbWkLzj/561BMH/+/JKkPPPMM6WysrLceeedpZRSLrvssnLBBReUqVOnlu7du5dHHnmklFLK2rVrS2VlZZkyZUoppZSXXnqp6UD95rqllPLVr351syCYNWtWqaurK0OHDi0XXnhhWbx4cUlSGhoaNhvX4sWLy9VXX11OOumkUl9fX6ZNm1ZatWpVXn311VJTU1N+8pOflFJKGTlyZPnKV76yc75o/Fe2FQStWrUqQ4YMKUOGDCnHHHNMUxDU1taWK6+8spRSyuWXX15qa2u3GQRVVVVl+fLlpZTytvfsex3X1gwaNKgkKUlKr169ygMPPNA077XXXivjx48vVVVVpZRN78/rr7++lPJ/+8xb96334j89th522GHl5z//efnEJz5RvvWtb5VSNgVBnz59Sn19ffnud79bjjjiiNLY2FiuueaactZZZ5WZM2eW6urq8sILL5QXX3yxVFZWlpkzZ24zCLa0X3bv3r387ne/+4/G3ZLOTy4Z0Gw8+uij2XvvvdPY2JiGhobceOON+ehHP5qnnnoqnTp1yqmnnppzzz03gwYNytixY7N8+fI0NDTk5JNPTpJ06dKl6d+qra3d6uu0a9cubdq0yQknnJDly5dvc0wvvPBCDj/88LRu3TonnXRSNmzY0LRO586dkyTdunXL6tWr/8utZ1dr165dZs+endmzZ+c73/lO0/Rly5blQx/6UJJN1+7f6d6Sgw46KD169EiSt71nt6drrrkml19+eRoaGtK/f/80Njbm7LPPzplnnpnnnnsua9asybp16zZbZ8WKFVvct3a0efPm5S9/+Uvat2+f3r17Z8KECU2XK9q1a5fWrVtnn332SVVVVfbYY4906dIldXV1eeGFF9KrV6/su+++6d69e/r3759ly5Zt87Xsl/85QUCzMG/evIwbNy6XXnpp9t9//1RXV+fiiy/O7NmzM2XKlIwfPz6tW7fOtddem+nTp+emm25K+/btU11dnTvuuCP19fV56aWX3tVrrV+/Pv/+97/z0EMPpX///mnVatPHcaxZsyavv/76Zsv269cvDz/8cBoaGrJw4cJUVVWld+/e23vzacYOO+ywppvyFi1alP79+6djx45p3bp1XnnlldTV1b3tmveb3vqe3V4nqIqKirRr1y7jxo1Lhw4d8uUvfzlLly7N1KlTc/vtt+eEE05Isun+hVatWmXt2rWpr69Pt27dtrhv7WiTJk3K4MGDs3jx4nTt2jVr167NrFmz3nG9mpqaLFmyJCtWrMiqVauydOnS9OvXL126dGm6T+LNew+2pVWrVlvcv9ncbv3BRLw/1NbW5tBDD82oUaNy1VVXZc8998y4ceMyevTonHfeeenVq1eefPLJLFiwIKecckq6du2a0047LT169Mi4cePyhS98IVdccUUOP/zw/OY3v3nH1/v4xz+eZNPJ/uabb07Xrl0zYMCAdOrUKWPHjt3su8MxY8bkjjvuSJ8+fbJy5cqMHz8+bdu23WFfC5qf6667LmeeeWbuvvvuvPbaa/ntb3+bdu3aZdSoUTnqqKNSXV3ddEPqW82bN2+z92zHjh23y5jKpsu9ad26dX7605+mtrY2I0eOzHHHHZfBgwdnr732SocOHbJgwYIMGTIko0ePzve///2sWrVqi/vWjnxPr1u3LlOmTMntt9+eM888M8mmG3lvvfXWDB8+fJvrnnjiiRkxYkRqampSWVmZoUOHZtCgQWnTpk3OPffcVFRU5IgjjnjHMZxyyin55Cc/mYEDB2bRokXbZbt2RxWl7PqPnNoRn3zV0j5Ni3enuro6d911V0488cRdPRR2geZ6XGiu49qS99NYt4eWdH5yyQAA8BMCoOVorseF5jquLXk/jXV7aEnnp936HoKW9FAK4N1prseF5jquLXk/jZV3b7cOguZYYMCu01y/M2uu49qS99NYt4eWFD/uIQAABAEAIAgAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAIggAgAgCACCCAACIIAAAkrTa1QPYkSoqKnb1EIBmprkeF5rruLbk/TRW3r3dOghKKbt6CAC7lYqKihZ1bG1J8eOSAQAgCACA3fySQZLcdNNNee2117Y478ILL0yPHj3yve99L/Pnz89ee+2V448/PpdddlkqKirecd2ePXtm2rRpWbNmTS666KKmeY8++mhmzJiRz33uc+ncufMO2S6AlmzDhg359re/nUWLFuWAAw7IVVddlR49emx1+Xnz5uWWW25JXV1dhg4dms985jPval5LstsHwfz587NixYrU1dXlwQcfzIABA9KtW7ckybBhwzJ27Njcc889Of/887Nu3bpcccUVWbhwYSZNmvSO6ybJD37wg6xcuTIXXnhh07Wmxx57LNdff31GjBghCAB2gNGjR2fmzJk555xzMnXq1MyYMSN//etf06rV209rjz/+eIYOHZrjjjsu++yzT0aNGpUNGzbk4osv3ua8Fqc0AztiGG/9N5988smSpEyePLlp2qxZs0qScssttzRNu+OOO0qS8qc//Wmb65ZSylNPPVWSlCRlzpw5TdMnT55ckpQnn3xye28WwC7VHE4bDz/8cElSfvnLX5ZSSpkxY0ZJUmbOnLnF5U8//fRy5JFHlsbGxlJKKccff3ypra19x3ml7JzzU3PRou8hmD59ejp16rTZj4eGDx+etm3bZvr06e+4/s0335x+/fqltrY2kyZN2pFDBeD/eeihh5IkgwYNSpIcf/zxSZInnnhii8v/8Y9/zIc//OHsscemU95xxx2XRx555B3ntTQtOgieffbZ9O7dO5WVlU3T9txzz/To0SNLly7d5roNDQ352c9+lhEjRmTYsGGZNm3aVu83AGD7WblyZZKkY8eOm/33pZdeetuyb7zxRl599dVUV1c3Tauurk5dXV1Wrly51Xkt8XjeooOgffv2Wbt27dumr1q1arM3yJb86le/yssvv5zhw4dnxIgReeONN/yUAGAn6NChQ5Jk/fr1SdJ0HK+qqnrbsm3atEnbtm2bln1z+T322COdOnXa6rx27drtyE1ollp0ENTU1OTpp5/OsmXLmqbdf//9Wbt2bQYOHLjNdSdPnpwkGTBgQD74wQ8mSSZMmLDDxgrAJl27dk2S/O1vf0uy6ae9SdKnT58tLr/ffvvlmWeeafr70qVL069fv7Rq1Wqb81qaFh0EY8aMSZs2bTJs2LBMnTo1P/zhD3POOeekZ8+eOf/887e63rPPPpt77703l19+eebOnZu5c+fm61//ep544onMmzevabn58+fn97//fdOfjRs37ozNAtitfexjH8vee++dq6++OkuXLs2Pf/zjdO7cOWeccUaS5IYbbshll12WDRs2JElOO+203H333ZkyZUoeeOCB3Hffffn0pz/9jvNanF19V2Mpu+63DEop5c477yx77713028LHHjggeXRRx/d5rrXXXddSVL+/ve/Ny2zdu3a0qZNm3LRRRc1/ZbBW//U1dVt9+0E2JmayWmj/OEPfyiHHHJISVIqKyvLnXfe2TSvZ8+epbKysqxevbqUUsr69evLpZdeWiorK0uS8pGPfKSsW7fuHeeV0rJ+y6CilF3/odQ74rOx38u/uX79+ixZsiQdOnTIgQcemD333HO7jgVgd9GcnmVQSsljjz2WAw88cLP7B5YuXZq6urrU1NRstvzq1auzYsWK1NTUvO0ZBVubt6vPTzuTIADgXWtpx9aWdH5q0fcQAACbCAIAYPd+lkFLeo41wM7i2Lp72m2DoDlenwGA5solAwBAEAAAggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAAiCAAACIIAIAIAgAgggAASNJqVw/gTRUVFbt6CADQYjWLICil7OohAECL5pIBACAIAABBAABEEAAAEQQAQAQBABBBAABEEAAASf4XeKYNJpiT2FkAAAAASUVORK5CYII=", }, { name: "Company-Invoice-1", + category: "Web", + template_id: 3003, ImageUri: "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UsK6det49tlneffdd8nIyKCgoIAvv/ySZ555xr9kt8/mzZv58ccf2bFjBy+//DJvvvlmuXUPtNYsX76cF198kWnTpvmX4PbJz89n1apVACxevJhnnnmGzz//nJKSEgAyMjJYsmRJub8ucnJyWLJkyTF/MTgcDkaNGnXMMlVp3+rVqwH46quv+Pe//80XX3yB0+lk7969vPvuu7zwwgvlnhgA71j3xx9/zNNPP80nn3xCXl5epe0QQpx+48aNKzcptyrKbgLpG54C7zyY4cOHV3rdJZdc4n+fn5/PrFmzjnmft956y/+4NxCwKN/xXHbZZQE9Tk888QRut7vK14sgcyaXFT5Rr7zySrlltPfu3asBPX78+ONe71va+rzzztP16tXT0dHRev78+f7zderU0f3799cNGjTQ559/vrbb7bpRo0Y6KSlJt2zZUjdu3FgD+o477vBfc9VVV+no6GgdExOjzz//fB0aGqoNw9BPPfWUv8yUKVM0oNu2baubNWumAX3dddf5z69bt04D+s4779T169fXnTt31oA+//zztdZa79u3TxuGoadNmxbwfaZMmaIjIyN1Xl5ehd83KSlJX3PNNbpLly768ssv9x9/4YUXNOC/rqrtu+iii3Tbtm11fHy8BnSPHj107dq1defOnXVYWJgG9Hvvvee/7ocfftBNmjTRDodDd+vWTYeGhuomTZrotWvXBrRz4sSJAe0RQlSuOlsUhIeHV1jHoUOHdOvWrcttZVD2d+vRnE6nrlu3brlr/va3vx2zvcXFxf7fnZRuYbBv374Ky27ZskWfe+65/rINGjQo93vheFsUjBkzJqB99957r7Ysq9K2PfHEE+XOyxYFwaHGBJlFixZpQL/99tvHvNa3/4YvuLhcLn3dddfp2NhYfeDAAa21N8gMGTJEu1wurbXWCxYs0IAeN26c/5qRI0dqQB88eFBr7Q0yLVq00JmZmVprrbOzs/Vll12mAb1hwwattTeIrFu3Tmuttcfj0ffff78GdGpqqtb6SFB46KGH/P+HmjZtmgb0ihUrtNZaDxkyRPfo0SPgO7Vr105fe+21lX7npKQkfd111+m0tDQdGRmpp0+frrUuH2Sq2r4pU6b4654wYYIG9Jdffqm11jonJ0e3bdtWd+vWTWutdUlJie7UqZO++uqrdW5urv8+SUlJunv37gHtlCAjRNWdiiCjtdYbNmzQoaGhVQ4yWms9duzYckFm1apVx23zU089FXBNhw4d/L8ftda6sLBQf/7557pOnToB5f7973+Xq+t4QWbXrl3+P6x8r2uuuSbgu5WUlOgVK1bobt26aaDcH4llg0yzZs30t99+e8zXd999d9yfgTj1aszQkm/IpF69escsN23aNC666CJ/N6fNZqvwEcJ27dr5n/Lp2LEjcGSM1mazMWzYMAC2bdvmv6Z+/fr+1Szj4uL8E2x9c1MaNmxI586dKSgoYN26df5x3B07dgS08YILLvA/NTBw4EDgyPLco0ePZuXKlWzatAmA9evX8/PPPzNy5Mjj/ozatm3LU089xb333svWrVvLna9q+zp16uR/36FDB//PC7zdy5dccom/vStXrmTjxo2MGzeO6Oho/33+/ve/s3r16oCuaSHEH69Tp0688MIL1brm2muvDfjcsWNHunfvftzrbr/99oCh/tTUVBITE2nUqBHdu3f37+uUlZXlL5OQkMCYMWOq1T7wzsl57LHHAo598MEHtG7dmkaNGpGcnExUVBQ9e/b0D6M//PDDAfcu61gL4vle1Rn+EqdOjQkyvuf4y85yr8jmzZvLzZnxzaavzmN6vs3HXC5XpWV8wcf3H+s9e/Zw+eWXExsby80338zkyZMBOHjwYKV1+BZy8q27cOmll9KwYUOmTZsGwEcffUTdunUZMGBApXXoMk8gjBkzhuTkZK655hp/232h6UTaV5HatWv75/X4FrBKTk5GKeV/+YLXoUOHqlW3EOLUu+222wLmvhxP165dA/6gOXqDyMrExsYyf/78cr+D9+/fzw8//FBucb4LLriAxYsXn/ByDPfffz/ffPNNuT9w9+/fz+rVqwOe2HQ4HNx6660BT8GK4FBjgoyv9+C77747Zrno6Gjy8/MDjvl6c0716o++en2LQV133XX8+uuv/Prrr6xfv/6EHgm32+3cdNNNvPvuuxQXF/Pxxx9z/fXXY5pmlet455132L17N88//zxwJOicivYdzbcwVlpaGto7lBnwSkhIOOl7CCFO3htvvFGtJ4N8G0mGhoYyatSoKl/XqlUrUlNTef7552nZsmWFZRo1asSTTz7JsmXLyu3ZVF0pKSn89NNPlS7616RJEwYNGsTq1auZPHkyISEhJ3U/8ccLygXxKlK7dm2GDBnCO++8w4033hiwa6rL5aK4uJioqCgSExP57rvvsCzL/x/Zr7/+GqDKG6hV1aeffgpAYmIiubm5LF26lBdffPGkNxcbNWoUkydP5u6772bHjh1VGlYqq2HDhsyYMYMhQ4b4j53K9pWVmJgIeJ8SK7tooRDi5Jx77rmVrvcCcN9993HfffdVub7w8HAWLFjA1q1bady48XHL33777XTr1o3IyMhyK/cej8Ph4J577uH//u//+P777wPWjqpVq1ZAb09lpk+fzvTp06t0v8aNG7N06VJyc3PJysoiMzOTiIgIWrVqdczenrILdYo/rxoTZABeeOEFNm3axKWXXsr48eNJSkpi165dvPXWW7Rq1Yr33nuPsWPH0qtXL+6++24effRRtm7dyoMPPkhycnJA+DkRv/76K2vXriU+Pp65c+fyj3/8g6SkJAYOHIhlWURGRrJ06VJGjhxJZmYmDz/8MECVN27zadWqFf369WP69OkkJCTQpUuXarf10ksv5cYbb2TmzJmAd/ntU9W+srp168aoUaOYMGECERERDBs2jN9++42ZM2cyYcKEgE3hhBBnVqNGjaq8D1J0dDT9+vU7qfuZpknv3r1Pqo7qiImJISYmptKeIBGcaszQEkDz5s1ZtWoVF110EY899hgDBw7k/vvvp3Xr1jz99NMA9OzZk7feeotp06ZRt25devbsSa1atQJWpzxRNpuNIUOGEBUVxTXXXEOLFi34+OOPUUphmiZvvPEGCxYsoE6dOlx22WVceeWVDB8+nJdeeqnaaxz4unWPnnRXHS+++CLnnnsuwClvX1nTp0/nH//4Bw888AAxMTH06tWLnTt3kpGRccJ1CiGEEABKH6tv8k/q1VdfZcyYMWzbtq3SvTdKSkpITU0lISEhYHE8n/z8fNLS0oiNjaVFixblNjmrrquvvpq9e/eyaNEifvjhB5o2bUrz5s3L7VlSUFDAzp07/U/7uFwuVq5cSa9evarVhi+++IIrrriC33///aQ2LNu5cydNmzb1z7E5Ve2riMfjYcOGDZX+b/LII4/w+OOPk5eXR2Rk5EndSwghxNmhRg0tlRUSEnLMOS+RkZF07dr1lN83LCyMvn37Vno+IiLCHxLAO3m3KjvP+hQWFrJo0SImTpzIkCFDTnrX1aP3RTnZ9h2LaZqnfB6SEEKIs1tQDy21bt0apRRr16490035w0yePJnLLruM+vXrB+wAHszGjRuHUorHH3/8TDdFCCFEkAnKoaW9e/cGLETXpUuXM/7s/+bNmykpKTmhibfVsWPHDrKzs09Lb9KZsn379oCF8Xr37l2tx8mFEEKcvYIyyAghhBBCQJAPLQkhhBDi7CZBRgghhBBBKyiDzMGDB1myZAnLly8/0005ab4NGk+XzZs3s2TJErZv337a7iGEEEKcKUEZZD799FNuvvlmXn/9dcC7Adill15K//79ueCCC6q9yeEf5ZdffuHyyy+nf//+/t1clyxZwnPPPXfa7jljxgxGjx7NnDlzTts9hBBCiDMlKINMamoqd999t3/jxL59+zJixAgWLVrE999/H7Az9fr16ykqKgK8C7Lt3buX7OxsUlNTAQLeezwe9uzZQ3Z2Nrt37w64p6/nxLdjdFZWFkVFRWzdupX09PQqtfvJJ59kzJgxLFq0yP/o9IYNG+jcubO/TF5eHuvXrw/YBbawsJCtW7eSn5/v35Pk6PYUFRWRmZlJZmYmmzZt8l/77LPPkpSUFLA2jBBCCFFTBG2Q6dixI+Bd/r5t27Zcf/31AWXS0tLo0qUL06ZNo0ePHixevJj58+eTkpLC+PHjGTFiBEOHDmXChAkMGTKEefPm8e233zJgwAAmTpxISkoKd955JwBr167lwgsv5N1336Vdu3Zs376dW265heuuu45XXnmFtm3bsn///oBHwMePH8+TTz4Z0CatNXPmzAkIKWWDzOeff06PHj148803SUpK4sCBAyxZsoRevXrxr3/9i4SEBLZv315he1599VWGDRvGY489xrBhw/jiiy/899i8ebP/5yWEEELUKDoIxcTE6PT0dK211sOHD9effPJJuTLJycl6yZIlWmutn376af33v/9df/LJJ/qOO+7QWms9Y8YMPW7cOK211hMnTtTPPvusnj17th47dqzWWuv09HQdFxenXS6X7tWrl87NzdVaax0fH69LSkr0JZdcojdu3Ki11nrw4MF6+fLlOjExUe/YsUMXFxfr5s2b6+zs7IA2bdu2TXfr1k2PGjVKO51OrbXWrVu31ocOHdL79+/XLVq08F8zdOhQ/eWXX+quXbvq33//XWutdcOGDXVBQUGF7ZkyZYp++eWXtdZaP/XUU3rKlClaa63z8/N1TEzMSf/MhRBCiD+joOuR2bt3LyEhIdSvXx+Affv2ERsbG1Am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evbZZ/XFF1/cls9g2rRp8vX1vS37gl2sevz6pvz582vBggW6fv26HnnkEe3YseOOHKdEiRLav3+/Yzk5OVnnz59X2bJlJUn9+/e/I8cFgKxycnLSsGHDtHr1ag0aNChNzaSnn35aI0eOVI8ePXTx4kX16NFDMTExiouLU5EiRRz7qFWrlq5fv66YmBjHujNnzihXrlwqUaJEhsddsGCBihQponbt2ql69eppthUtWlQVKlRQo0aNdPLkST300EN65pln0rQpW7as9uzZo6pVq97yZ5ArVy7Vrl37lvcDO1l5RuamEydO6PLly3ds/82bN9fevXu1ePFiRUdHa/z48XJycpKPj4+MMVq8eDE3lgHIVsYYOTs7a+HChdq4caN27drluC+mc+fOio+P13/+8x/16tVLBQoUULFixVSiRAkdPnzYsY/w8HCVLFlSpUuXdqxzc3NTamqq496ZP5syZYoCAgL06quvptv21ltv6fvvv9fHH3+sWbNmacGCBUpKSkrXrn79+ipUqNCtfgS4z1kZZGJjY1WlShW1bdtWvXv3VvPmzdO1ee+99xxPH40ZMyZNv5s/N+/ez4ynp6deeOEFvfHGG2rYsKFWrVqlyZMnq3DhwoqMjNQbb7yh5cuX35E5AsA/4ebmpsWLFys5OdmxLk+ePOrZs6e2b9+e5oxIly5d9NFHHyk8PFw7d+7UW2+95Xjq86aHHnpI7u7uGjRokE6cOKG9e/dq+vTpWRpL3759ValSJfn7+ytv3rzavXu38uTJk6bN7bxHBvc36y4tNW/ePMO731955RXHnxcuXJhh36zcNb979+40y6+99poGDBigM2fOqEaNGo4vo5ubm3788UfejArgntG4cWNNmjQpzbo+ffooMDBQTZs2dax788031alTJ1WtWlX58+fX0KFDNW7cuDT98uTJoy+//FK9e/dWpUqVVLRoUQ0cONCxvVWrVo4/v/POOxo7dqxj2d/fX23bttXq1avVrl27DMd66dIlPffcc3r77bfT9AX+KauqX5PcgduLIH7/SklJUWhoqCpXrvyXl3eSk5O1f/9+1apVS/ny5butYwgODlbVqlW5vIRbQpAB7mMEGQC2s/IeGQAAAMmyMzIAAAB/xBkZAABgLeueWkpNTdWmTZskSQ0bNlSBAgXu6PEuXLigAwcOyNXVVY0aNbqjxwIAAP+M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AACAtQgyAADAWgQZAABgLYIMAACwFkEGAABYiyADAACsRZABAADWIsgAAABrEWQAAIC1CDIAAMBaBBkAAGAtggwAALAWQQYAAFiLIAMAAKxFkAEAANYiyAAAAGsRZAAAgLUIMgAAwFoEGQAAYC2CDAAAsBZBBgAAWIsgAwAArEWQAQAA1iLIAAAAaxFkAACAtQgyAADAWgQZAABgLYIMAACwFkEGAABYiyADAACsRZABAADWIsgAAABrEWQAAIC1CDIAAMBaBBkAAGAtggwAALAWQQYAAFiLIAMAAKxFkAEAANYiyAAAAGsRZAAAgLUIMgAAwFoEGQAAYC2CDAAAsBZBBgAAWIsgAwAArEWQAQAA1iLIAAAAaxFkAACAtQgyAADAWgQZAABgLYIMAACwFkEGAABYiyADAACsRZABAADWIsgAAABrEWQAAIC1CDIAAMBaBBkAAGAtggwAALAWQQYAAFiLIAMAAKxFkAEAANYiyAAAAGsRZAAAgLUIMgAAwFoEGQAAYC2CDAAAsBZBBgAAWIsgAwAArEWQAQAA1iLIAAAAaxFkAACAtQgyAADAWgQZAABgLYIMAACwFkEGAABYiyADAACsRZABAADWIsgAAABrEWQAAIC1CDIAAMBaBBkAAGAtggwAALAWQQYAAFiLIAMAAKxFkAEAANYiyAAAAGsRZAAAgLUIMgAAwFoEGQAAYC2CDAAAsBZBBgAAWIsgAwAArEWQAQAA1iLIAAAAaxFkAACAtQgyAADAWgQZAABgLYIMAACwFkEGAABYiyADAACsRZABAADWIsgAAABrEWQAAIC1CDIAAMBaBBkAAGAtggwAALAWQQYAAFiLIAMAAKxFkAEAANYiyAAAAGsRZAAAgLUIMgAAwFoEGQAAYC2CDAAAsBZBBgAAWIsgAwAArEWQAQAA1iLIAAAAaxFkAACAtQgyAADAWgQZAABgLYIMAACwFkEGAABYiyADAACsRZABAADWIsgAAABrEWQAAIC1CDIAAMBaBBkAAGAtggwAALAWQQYAAFiLIAMAAKxFkAEAANYiyAAAAGsRZAAAgLUIMgAAwFoEGQAAYC2CDAAAsBZBBgAAWIsgAwAArEWQAQAA1iLIAAAAaxFkAACAtQgyAADAWgQZAABgLYIMAACwFkEGAABYiyADAACsRZABAADWIsgAAABrEWQAAIC1CDIA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", }, { name: "Company-Invoice-2", + category: "Web", + template_id: 3004, ImageUri: "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mEhxcTE1a9Zk6dKlhIWFcdlll9GuXTurjFKKH374gc2bNxMaGsoll1xC9+7drdfT0tLIy8ujefPmLFmyhN9//51u3bpZ+47s3LmTzMxMLrvssoD3sm/fPvbt20e/fv0q/Po4HA7Gjx/P5s2biY6OLrfMqeqXl5dHUlISPXv2ZPny5Wzfvp1WrVoxZMgQMjIyWLNmDVlZWQwYMIAOHToE3PvYsWMsX76c/fv306xZMwYNGlTuKp9CiL+u6OhoFi5cyGWXXValltyxY8fy4IMPWi0qpbuJKtrpGnwL4D300EPWfk9z5szh6quvrrD8zp07AxbeGzRoEJGRkZWu54gRI/jPf/4DwI4dO/jggw9kQbxgoILQm2++qQC1a9cu69z8+fMVoL7++utTXn/77bcrQF1yySWqXr16KjIyUi1btsx6vU6dOqp///6qQYMGqkePHsput6tGjRqp2NhY1bJlS9W4cWMFqLvuusu6ZvTo0SoyMlJFRUWpHj16qNDQUKXrupo+fbpV5vnnn1eAatOmjWrWrJkC1E033WS9/uqrr6qYmBg1atQo1alTJ9WgQQMFqMmTJyullJo3b54C1G+//RbwfgYPHqz69u1b4fuNjY1V48aNU926dVPXXXddwPMAlZubW6n6JSQkKEANGDBAtWnTRrVt21YBqlevXqp27dqqa9euKiwsTAFq3rx51nU//fSTatKkiXI4HKp79+4qNDRUNWnSRG3atCmgnk899VRAfYQQZ1/r1q0VoAA1bty4Mq9HRUVZr8+YMaPce7z++utWGf/HrbfeetLnjhw5ssw1rVu3PmV9R48ebZUPDQ1V3377bbnlcnNzVf/+/QPuv3HjxoAy+/btC3h91qxZAa+npKQou91uvR4TE6MSExMrrNvatWvVihUrAs699NJLAc9ITU095XsUZ+aCCTLPPPOMAtTvv/9+0msXLFigACu4uN1uddNNN6no6Gh1+PBhpZQvyAwbNky53W6llFIrV64MCBRut1vdeOONClBHjhxRSvn+sbVo0UJlZGQopZTKyspS1157rQLU1q1blVJKHTx4UCUkJCillPJ6verhhx9WgEpKSlJKHQ8Wy5cvt8rccsstqkaNGqq4uFgVFRWpOnXqqEceecR6P4cPH1a6rqu33367wvccGxurbrrpJpWcnKycTqd65513Ap7nDw6nqp8/yDz/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9x5PPvnkGdUVym6Udqb1OxnDMM76OCQhhBDVW1B3LbVq1QpN09i0adP5rsqfZtSoUdx1113ccMMN3Hvvvee7OmfF5MmT0TSN55577nxXRQghRJAJyq6lAwcOBCxE161bt/O+b49/r6Vu3bqd0+f490qq7Nb0wWD37t0B6/9cccUVVleXEEIIcTJBGWSEEEIIISDIu5aEEEIIUb1JkBFCCCFE0ArKIHPkyBHWrl3LunXrzndVzti5fA9paWmsXbu2Wg2GFkIIUb0EZZBZtGgREydOtFbr/eOPP7jmmmvo378/l112WZU3OTxfCgoKzmhBu1NZtWoVDzzwwGnvAiuEEEL81QVlkElKSuLee+/lgw8+oKioiL59+zJ27FhWr17N+vXrA3am3rJlC4WFhYBvsbesrCz27NlDRkYG4NvM0L/CrNfrJS0tjaysLFJTUwOemZ+fT0JCgrVjdGZmJoWFhezcuZNDhw5Vuu4pKSnWTta//PJLwCynE+vrr1NiYiJKKXJycgDfLqwJCQnWXiOmaZKamkpBQQGbN2+26jh+/HhuvfVWaxduIYQQ4kITlAviJSUlce211wK+5e/btGnDzTffHFAmOTmZMWPG0Lt3b9avX8/MmTNZvnw5GzdupHPnzixatIiBAwcSGRnJ3Llz2b9/Pz///DP33XcfV155JatWreKqq65i1qxZbNq0ibvvvpvevXvz5Zdfsnz5ch566CEMw6BJkyb897//JTk5mdatW5ObmwvAP/7xDyIiInj88cetOt16663YbDZ27drF119/zdatW61NFcurb9u2bRkxYgRdunThxx9/5J577mHkyJGMGjWKzp07s27dOiZPnkyrVq24+eab6dOnDzt37iQ2NpaZM2daX6t+/fr9CX8rQgghxHmgglBUVJQ6dOiQUkqpMWPGqE8//bRMmbi4OLV27VqllFL//ve/1UMPPaTuu+8+9eGHHyqllJowYYJavHixUkqpK6+8Um3atEktXLhQ3X///UoppQ4dOqRq1qyp3G636t27t8rJyVFKKdW2bVtVXFysrr76arVt2zallFJDhw5V69atU126dFF79+5VRUVFqnnz5iorK8uqj8vlUlFRUWrv3r3Wudtvv119/vnnFdb3wQcfVHPmzFFKKTVu3Dj19ddfq4kTJ6qNGzda72Ht2rXq+++/V2PHjlVKKbV+/Xo1ePBg6xm9evVSW7ZsOf0vthBCCPEXFnRdSwcOHCAkJIT69esDcPDgQaKjowPKZGdnk52dbW0V4Ha7qVWrFklJSVYLyM6dO+natSsA+/fvp23btiQlJVnnDMOgQYMGrFmzhg4dOhAZGUl6ejq1a9fG4XCwfft2ayn/tLQ0Lr74Yjp06EBycjLz5s1j9OjRAVsk2O12Zs6cyeDBg3nrrbcA2LZtG926dauwvqGhoeTm5rJ37142btxIp06d+Omnn+jZsycACQkJdOvWjcTERKveaWlptGzZEvB1OW3fvr3Su9IKIYQQwSbogkxiYmLAfj2tW7fmyy+/RClFUVERycnJ5Ofnc/ToUZRSZGVl8eGHHzJixAgSExNp164dpmmya9cumjZtSn5+PoZhEBERQVJSkrXJ5Pz58xk+fDh79+4lNDQUgM8++4wOHTrwxx9/0LRpU3Rdx+VykZ6eTv369enYsSNJSUn85z//4b777guod0FBAbfddhuzZs3is88+w+PxkJqaSkxMTIX1ffbZZ/nmm294+OGHWbJkCVlZWdhsvt7AHTt2UFhYiNPpZPv27Va9/e8RsDbVdDgc5/zvRQghhDgfgm6MTFJSUsCmhpMnT2bgwIEsXbqU4uJi5s+fT9u2bRk2bBjt2rXD5XIxY8YMoqKiaNy4Mbquk5ycbP3g37ZtGx07drTuff/99+NwOPB6vSxatIj9+/fz1FNPkZCQAEDjxo3LXOOvz5AhQxg6dChDhgyhcePGAfV+5ZVXWLFiBRkZGTz99NMYhkFcXByLFi1i1KhRZerbtm1bANq0acOyZcuYNWsW//rXv3C73XTv3p2oqCi8Xi9KKbZv386jjz5q1cc/JiYxMdGqpxBCCHEhCrotCiZOnMgVV1zB+PHjrXNut5tt27bRrl07wsPDT+u+x44do3fv3nz//ffs37+fjh07omlale6Rnp7OqFGjeO+992jVqtVp1aO0ffv20axZMzIyMhgwYABbt26t0vXPPvssDocjYMCxEEIIcSEJuhaZHTt2sG3bNqKiohgxYgTgG39ypps1+ltZoqKirNaaqvjwww+54447eO21185KiElNTaV79+60aNGCkJAQZs2aVaXrhw0bxtatW6t8nRBCCBFMgq5FRgghhBDCL+gG+wohhBBC+EmQEUIIIUTQkiAjhBBCiKAVdIN9hRBCVN0Dfb3nuwp/KS9/a5zvKoizRFpkhBBCCBG0pEVGCCFEBf7MSa1VW7dLCD8JMkIIIQIoFMr0YuLhrISZCm+hAZrvf5oOmo6m+Y6FqCwJMkIIISxKKRReMIoJqZWL4fCe0FhSNpWoMqdVwKG3GJQXvEUaKF9RhYamNFAaGqEoUwevHWXa0TXDF2yEqAQJMkIIIQB/iFGYyo3DmU3v0TnUbmTgTx+maVLeEqpKmSilrDBjer14zZLX8IUYt1thegA0XB4Hphs8+SbKq3C54HBaCEd+d+I6Fo5phqIpO5qmS+uMOCUJMkIIISxKefGqYrzkEhHpJrKmr/3E9xqYpsL0KpQCTQNdB1BopolumsfLajpe3cBUCm9JgCl5AppWjDIVuteD1+3B7VXEtNfIOJDPb+ucZKXWAHckhuYAzajyvneiepEgI4QQwqJQuL35eIqyMM0w35gVTcM0weNSpOzI52hGER43GHaNBs1DaNxAw75nJ460NFAKpet4LmqGu3krDh1wcXB3MUqBbmhE1rXRol0YNq+L0K1bMI5mkef24NYMGl/cnrABjdixsZCMPSaeAicGYejKJmFGVEiCjBBCiONK+o5OGPKC16NI2+1i4b/SyTxQhK5r1KxvY8T9DQjJ3UX4S/9CO3AANA1Vty6eu+4mr1YrVrx3lOQNeQCER9oYcHNtWrTy4EjcTtgbr6MfOkQIEN20KTn/cy8pKhQ9wqDtlTb2/OimOAuUGQZImBHlk9FUQgghTkopKMhTfLcwgwO7CyjI9VJcaNK0XTgtWypClv4f2q5daMeOQWEh3ktiKWrfla0/FJG8IY+CY16K8k3qNnXQtns4ofnZhC/4GP3339Fyc0EpPL37kNOwOQnf5fPLDynUb1pEz2F5hEQdxaQIpbzIHseiPBJkhBBClEvT8HUreSElqYCkH47h9SgMQ6NhyxAuH1aDGnt+xbZhPZrHA4aBatqUoqHXcCDTwQ+LMijM86JpUKOmjZ5DI6lZy4v+8yaMhAQ0jwdlGJht21J4ZX/2pxaza1M+6YeOkn4gl3aXOuh81THsNbIxKUap42NwhPCTICOEEKIMDdA0HaUg/5iXTcuzyT/qQQNCnQaXDojkojr52D77FDIyQNMwIyJwXz2YwsYt2PpdLpmpxaB8Y2k6XRlF224RhB5JJ2zp/0F+PgDK6aRo0GCyQhvw0xIPxw4ZqCIbWRnZhITZ6Xi5gzaX56GF5mIqF6Yyz+vXRfz1SJARQghRLk3X8HogaUM+OzYcQ5m+AbsXtQ+l+4BInElbsK1fj+b1gmFgduhA8dVDSEkx2LwyF3eRiW5o1Kzn4LJhNagR7sax8muMbdvQlELZbHi7XkLRpXHs3l7M7k2FmG6F0rx4PEXYDIMa0Xa69rNTr20eHi0XpTzSxSQCSJARQghRPgWZaR7Wf5FFcZ4Xm0Mjqo6dXkNqUav4D/Tly8DjQdntmDVr4r56MLmhtdi4JJNjmW4Mu0Z4lEGv62rRqEUYIfv3YV+3DrxelN2OatqUwtHXc9gVxFUUQAAAIABJREFUzYYvc3AVmRiaHYdy4ozWMWwGNpuNyNpw6VUeIhvk4SYPU8KMKEVmLQkhhKhQbrabuo0N6jSMAk2jfvMwOveNxLFrD6pOHYr790ez21ENGuC5/HIK8g3CIhx07heFpimi69npMaQGIWEaKusoxc1bQOMmYBjQqSPuzp3J3+2lXlM70XVr4PV6MCMKqdfEgc1uoGkQohQxraBL/2J+WlyANz8UpXRAl5lMQoKMEEKICmjQsnMELTuHYpoKXdfQDQ1D11CXdMXVqRNeTSsZUKODrlO3Jgy/p2HJgnkeNE3DZvcNHHbFxWFe2t13bwW63UDTNZq1h5jWIZhK4Spyc+iQlwingxCHA4/uAcAMM2nfw0tuVg6/fmvHW6CjK0fJVgYSZqozCTJCCCEqFBKigan7unJ0rWSdGd/4Fs1mw/B6UZqOpuuYKHRdEWpTvvKaHjDJyBbigJCSrRBMBZrvc7sDDJtWUtbEbncQFh6KzeFAobArhWmahDlNLrnKJOtQLqlbdJRZA5QDXTPO01dH/BVIkBFCCFEuQ9cxCgqx/bgRZTOgdRu05F9RR7OhXl1fgNm/H9WyJe7YS0D3BR6llLVHktJUmRnTmqb5zkNJi4pC133bHyilME0vGhq6rqPrBuABDd/g3ygbXa8yydiXT9FRHV0ZKOliqtYkyAghhCiXpunYcrN9s5JcLjh8CD1pB6bHg5adjTp2DL1+fTzhYaiSrQyUUui67mtxAd9+k5ov2JQeoFs6ePi6oTRAUVhYRHrGQRR12btrDy63C4/Hg67r2AwHugF1G+v0uMZk01IozLSXhCZZ+be6kiAjhBCifErhjQjHCAlBud2+8NKsGabHA3XqoOfloerUwVu3nm8QDPhCjFJoekmLjHm8dUZDQ5XspO0PHf5w49/TKSTERoOGTXAVFbFmzRoKi4pQysTr8WLY7DgcNhrWb0Stho1pe3kU27524C3UMQhD0+RHWnUUdH/rpmny3XffAdC9e3ciIiLOc43Kl5ycTL169ahVq5Z1rqioiB07dhAbG3vG93e73fzwww8AxMXFERoaesb3FEKIE6nIKNx9+vg2g/R14uA1zeObSSqF8q2eZ11TOqRYrSTKtyGlhmaNjfGX1UqFIEdICDVr1qSooAg0hdvlwmua5Obm4Pa4cbvdZGZmUFRYRGSjRsR0gf2bNbwuDVSYjJephoJuHRmXy8XgwYOZNm0a6enpvPzyywwYMIDhw4ezZs0aAFatWkV2dvYZPyspKYnExMQKX3/ggQcqfM6///1vfv7554Bzhw4d4v777z/jegEUFBQwbdo0hg8fzqFDh87KPYUQIoAG6Dq6zYZut2PYHWh2O4bDgWazgWGgGQaaXv4YFU3zjXPxhxVN94UYzZrpFFhe133lQ0PtRNWMpGGjRjRo2IRGjWJo0eJiWrVszcUtWtOoYQxh4U4coSatexTSsF0W2PNlfZlqKuiCDECdOnVYuXIlUVFRvPnmm6xcuZLFixdz1VVXAfDcc8+VCRiZmZmkpqZax9u3byc3NzegTEpKCgcPHrSO58+fzy+//FJuHfLz85k/fz4LFiwIOH/06FFSUlICzimlSExMxOv1Bpw3TZPt27db5098Pvhadkr/w/QfR0VFsXLlSlq2bFlu/YQQ4lwpE1DQ0LXAMKMR2EKj6cevQcP6XNcDfwx5PSZFRYWEhTuoERlBVM0aRNeKpG79etRtUJ969RtSt14DatWqTVR0NLUbanQe4Ca8TmHJqr+yhUF1E3RdS6U5nU4Mw+Cbb76xQsyWLVtIS0vj9ddfZ+jQobjdbp599llatmxJr169mDhxIkOGDCEmJoaEhASeeuopRo0axSOPPML27dsB6Ny5M48//jgbNmxg586dmKbJhAkTAp49f/587r//fubNm8ff//53ANavX88999xDXFwcq1ev5vrrrwfgxhtvxDAM8kv2FgHo06cP9erVo2bNmrzwwgu8+OKLAc+fNm0a8fHxtGnTBo/Hw9tvvx1w/O67757zr68QojrTSnX7nLyVQ9d1THW8u8n65aucy8p0OynfVgj+sl6PSXFxMYbhCzo2m8IsGTisFNjtvtePHMkhqqYTm82GM8qG0nPxKCe6FnK2vgAiSAR1kHE4HCxatIhJkybx2muv8c4779C1a1eaNGnCPffcQ7NmzVixYgURERF88MEHALz88stcffXVPPzww+Tm5hIbG0vz5s1JTk5m2bJlAPTr14/MzEx69epF69atufXWW8s8e/bs2Xz99desXbuW5ORk2rZty2OPPca3336L0+nk9ttvB+Czzz6jU6dOPPHEE+zbt49bbrnFusekSZO48sor2bx5c5nnb926ld27dzNr1izatWtHVlZWwLEQQpxruqaj+7bAtqZVlxYwvqVkzRiFChwbA9Z5/zXl8d87JNROpBZ5vLVH09D1kuVrUCiloUwbhs3AMHRsNgObzQuaae2OXeb54oIW1EEGoGPHjqxevZpXXnmFe++9l48++qhMmYsuusj6fNu2bXi9XqZOnQrAhAkT2LJlC8eOHbPO9e/fn5CQilP9jz/+iGEYbN26lTZt2jB37lzuu+8+jhw5gtPpDCj7zTff0KtXr3Lv07x5c4Byn1+vXj3ef/99br/9dkaOHMnkyZPLHAshxDmlgab71nnxKx0SAqZT+wfxmsenX1vdS6UzjQoMNNYsppJZTjabDjjKBBHfoVayUrCN+g0a4HA4KMlZWHdTpiz0W80EfZDJy8vD6XTSu3dva7CvzWajoKCg3PKdO3cmLy+PKVOmWOd++eUX7HZ7wDn/fQoLC8vc45133qFDhw58//331KlTh/fff59nnnmGw4cPk5mZSZ06dayyzZo146effuLGG2+s8D106dKl3Ofb7XZWrlxJixYtuOWWW+jYsWPAcd26dU/9BRJCiDPkDxXaCS0zAd1IJ5YtCSamMq3p1v4QVNE1/utOHDzsDzulTuB2uQkLC/WNtdEVIGNjqqugDjJer5c+ffpQu3ZtcnJyePPNNwEYOHAgI0eOpF+/fowYMSLgmrvuuouhQ4eydu1aXC4Xjz/+OEOGDKFTp0707NmTyMhILrvsMp599ln69u3LbbfdxqJFi/j8889xOp0cO3aML7/8kpSUFMLCwgDf2JhVq1bxzDPP0LZtWzp37szhw4e5/vrrGT9+PH379rW6vMrTrVu3Ms9/8MEHad++PZ06daJjx44YhkGbNm2s49LTuoUQ4twIHLDrDyCGYWCavuBwYjgp05KiSlpqSqZeK05e3u32UlxUhCPEQUmfFKZSgY0sGoSFhVAyxMZqjSHgT1FdaCrI5qoVFRXRqlUrawaS2+0mMTGRDh064HA4znPt/nyxsbF89tlnNGvW7HxXRQjxF/ZAX+8pyyil8JouiryZ6BH7ufnx2jRuEUZ5w03MkrVk/IHGf3159zzxc+tPThh3oyA3p4CMjAzq1a+Hy+UCFMdycnHWiMDhCEVDcSw3l+KiYho2boDdbpB3zMNnrxeQta8R4bZ6GHrIKcfIvPytrDdzoQjK6deZmZnEx8fz+++/Y7fbueSSS6pdiMnJySE+Pp49e/ac76oIIS5EJeu9+Na6KxsKSk+bLj0wt+zYlvLP+VtodO34fRRQlO/ARn3S9oWQmxdObm4ExYW1OHo4jKPpNnKO2CkqikIz6pOba0OVjM2x212YqtAaVCyqj6DrWgoNDS133Ep1419HRgghzgVd131ToEsG+1or+ZZqgTlxHRj/6xWNhyk9PVsv+T3aWu1Xgcet8XuyjleFcDhV4XKF4MkHW5gNswh0TSMkVOHx6tRs6KVujEHNWl7fYF+dUiHmeIeTuPAFXZARQghx7mklLTKlp1/7zpe/+aP/nLXXUgXlAp+hHd93SQPDAGeUorDQxBltwxGmwAP2cIVdU+RkGqDAHqIRWd9GvUa+FiPD8AUZXz1O3YUmLiwSZIQQQlSsZEG8gJlF5awrU1FQObGcP+SUNxPK7tBoH+vA6/W1qBzfNkkD0wRNxzcJSgdMNN083v0FaOWsdSMufBJkhBBCVOB42IATNoGkbFdS6fOllTezqaIp2LoNdMO/S7a/s0iVzHwyOT6kxhewlNKsDSslwlRPEmSEEEKUS9cDW01O7Ebyj5EpHVQqCimlW2D8ypuCbZR8bppmqS4tDaUp//K+pe9cMgXbhkJmIVVXEmSEEEKUUJRu19AqGDBb3kykyoaYEwcLV9QV5B9ErJSyrvGvGHzizCTDUOi6kuG91ZQEGSGEECXKG+fibzEptfRcBWGldJipzCq+JzqxrFK+0OJv9QHfyr/+1YL9gcbQtXLXuhHVgwQZIYQQ5dN8U5594UPH6zV9XTkK68+A9ezU8Qv9wcIfXLzeE7cQON5t5G8F0tCsVXxNVWqhvRNaYDR00FRAeVF9SZARQghRLg0Nr6nIO1aIx6tZZyscVltyuvTKvSe+Vt5FXq8Xr8ddsnu1Vonrjp/UDXDYDAkz1ZgEGSGEECVOSA0amKbGsVw7XlWDyi0yV8W5QwpM00tOdiYej/v4gyspNMRNvbpG6S2wRTUTdEHGNE2+++47ALp3705ERMR5rtFfy65duzhw4AANGjSgbdu257s6QoigUjYJmKaJUgZgL/f1QKcxAVoDpUy8JnjNqu+ao5Skl+ou6PZacrlcDB48mGnTppGenk5xcTH3338/vXr1ol+/frz77rsAfPLJJzz//PMArFq1iuzs7Co9p7CwkDvvvJNBgwYxZMgQsrOzyczMZPXq1Se9rjJlKisiIiJg/5KdO3eyb98+hg8fXuE1y5cv54EHHuDf//73WamDEKK6KlnXxdqCoDKBQTu9D03nTH4c+b5HnvblIsgFXYsMQJ06dax9hp544gnCwsLYsGFDQJkxY8ZYnz/33HPMnTuX6OjoSj9j+fLluN1uVqxYYZ1bsWIFCxcupH///ta51NRUNE2jSZMmAPzyyy9lyvh36O7YsSN2u73SdcjPzwdg2bJlzJ8/n9atWwOwePFiq4zL5SI5OZmOHTui6zr33HMPjRo1YtmyZZV+jhBCVMQ3WPdcpgSF2+XGNE9/a4FT7XQtLmxB1yJzov/93//liSeeCDh36NAhOnTowAsvvMCWLVtIS0vj9ddfZ82aNQwaNIjExEQAPv74YyZPnlzufVu0aEFCQgJ79+61zi1btoxt27YxdepUTNPks88+4+6772bKlClMmDCh3DI7duzg0ksv5a233uLSSy/l119/rfJ7fOqpp6z3OHLkSAYPHgxA+/btueGGG3jyySe54ooryM3NrfK9hRCiIrphoGkQEgLOCI2IcC3gT2e47/MaJ5xzlioX8NqJ58I1IsJ1atQIISzUICSEKn/YbL7hMYY96H+cidMUlC0yfunp6TgcDmrUqBFwvkGDBkyaNImcnBy6du1KkyZNuOeee2jWrBnp6em88847vPrqq3z88cc888wz5d67S5cuTJs2jfj4eMaMGcNzzz3H4MGDycvLY8qUKYAvVAwbNozNmzdzzTXX4Ha7y5T5xz/+wezZs+nZsycrV65k+vTpzJkzp9Lv8dNPP6Vt27a0a9cO8LUuPfzww9brc+fOxel08vjjjzNnzhz+3//7f1X5EgohRCknTnMGXYeICHA4dM5Ny4zC5TJwu3Q8Hq1qw2w00PGtLuwI06VlppoK6ghbq1Yt0tLSyMnJqfQ1o0aN4ssvv+TQoUPs37+frl27Vlh26NChJCQkkJKSwjvvvFPm9bfffpuBAweyePFiQkNDOXLkSJkyW7duZcmSJUydOpWNGzcSHx9f6bqapsmUKVPKtDiVJyYmhgMHDlT63kIIUVb5QUDXfIHm3HxoaJqJhln151hr1ciEpeosqFtkbDYbI0eO5Pnnn2fGjBl4PB7Wr19Pnz59ypQrKCgAwG63M3r0aMaMGcP48eNRSrFlyxYuueSSgGtM06S4uJjIyEi6du1KTk4ONpuNwsJCq8z//u//Mn/+fFq1asVXX32FUqpMmS5dutC/f3/69esXcP8tW7bQqVMnDKPi/UHmzp3LpZdearXGnMyGDRsYNGjQKcsJIURl/ZmDaE+7NUXDv/zwWa2PCB5B3SIDMGPGDBYvXkzr1q2JjY1l06ZNZcoMHDiQkSNHctdddwEwduxYfvjhB8aNG8exY8cYPnx4wFgYgKSkJHr27El8fDwrV67kzjvvpEuXLvzwww/Ex8ezbt06rrvuOm6++WbGjBlDjRo12LBhQ5kyU6dO5YEHHiA+Pp64uDgOHz4MwNNPP33SLibTNPnnP//J9u3biY+PJz4+nqVLl5YpN2LECHr37k2jRo3429/+diZfSiGEOK5ULlAcX8X37H8oUDpKaad1vW9jbAkx1VlQt8gAXHzxxSQlJbFt2zbatWtHaGgoAHfffbdV5rHHHuOxxx6zjkNDQxk9ejR169YFfF1IMTExAfft1KkT69atIyUlhY4dO1q/Lezbt88q07t3b5588skydSpdBiAhIaFMmT59+tCrV68K35eu6+zevbvc10rPSPr8889xOp0V3kcIIU6HrmvoNt+4mIJ8k4ICL+eqA8flNsk5pvB4TtyM4OQ0IMTu26pAxsdUX0EZZDIzM4mPj2f27Nk0b94cwzDKdA1V5KuvvmLGjBm8//77ACQmJjJ58uRyp0VHRkbSqVOns1p38E2Z7tChQ6W6jKrq9ddf57333qNbt25n/d5CiAtdqRihab4NGk2NomLfnkbnaKwvXlPh9oDHXcWxvhrYDA1ND/rOBXEGNFXRHupCCCEuGA/0PfU6LUopvGYxRd4jhNb+g7v+GUN0XYPUNBOTSM5Vi4zXU8DRo0fweKq+lkx4qJtmFzlZ8n4GO76rQajeGIctAk07ebh5+duKxyeK4CIxVgghRFkaaLqOrhvnfhytZudMfhzpuoYuXUvVVlB2LQkhhDgXSsao+Kc16xqaptB1hTLP3TNNXMAZruwrOabakiAjhBCiRNn5P7oOTifo+rnriil2heFy2fC63VR140mHQ2ZeV3cSZIQQQpQoJ0RovlV9bbZzt+eSpmmEhWh4bVXbzVrTwDD8rTGSZqorCTJCCCFKlA0DvkXxzu3CeLruQdNN8J5G64oCXdPRdQky1ZUM9hVCCHEeKVA2qEJLTGmmCWgaNptNGmWqKWmREUIIUaKcBekUmF4PXorxTWXynSt/JMvprebh9nhQmGiaWfV7KFWyS4GkmOpKgowQQogSJw721bDZbYSGFWOax6yzvm0FSiKHUiiUb7uAihMOgS9oAefsNkWtaA1T2Y7fHw3/Mmdaybnybq1pNmw2I+CuonqRICOEEKJCNrudqJpRvoOSvZGU6dsWwAoymkKZpi9IKOv/OB5DfH8qpVDK9H1OyXxupQKvUWAqE003ArKPaR5vrfGtGaNQSmF6TczT7JYSF4YLIsgcPnyYvLw8WrZseb6rIoQQFwxd19ANHWWaoPnCRFG+SdKmLHIz3L5WGM2kVddIGjU0cP+yCQ4eBEAZNjwdOmBr2pw9SblkpHowTQWaot5F4VzcKRyOHoHNmyE3z/dAQ0eL7YandgN2bs0h65DLVw8DmrYJJ6ZlGCo9HfXLL6i8XJSpwOlEj7tMWmOqsaALMitWrODaa68lPj6eiIgIJk2aRGpqKtu2bWPGjBnnu3pCCHHBMDTfqr66zfC1iCiNXxOyef/pJI4eKkbToHbjECa92BH3kT0UPTcVlZoKgNmiBfbp0zmQks/7z/3KH3uLQEFkbTs3PnYxRucIXN+sxTVzJlpeHugaWoeOONq2J/n3o8ybvoeMtGIA6jYNZcIzraHYwLVoEWrePFReHhgG2lVXYevRC9+OBBJnqqOgnLU0cuRIlixZwtNPP81NN91knU9NTSUrKyugbHJyMunp6QHnPB4P27dvDzjndrtJSEjA7XZb5zIyMsjLyzsH70AIIf6KSo9D8e8o7QsHylQUF5ms/GgfKTtyOXq4mIJjHroPqEWT+iZF776LuX07Kj0d8+hR7D16EBLTjE0r09m5KZejh4o5dsRF03Y16NizFuQcxf3FF5CS4rsmNw/9qqsoqFGfbz79g9+355J9uJi8oy5aXVKDmJbhuFN+x7tgASotDY4cAcDWvz+a04luNyTHVFNB1yJTmmEYHDlyBKUUS5cuJTs7m2XLlvH2228zaNAgxo4di2EY7N+/n9GjRzN58mR69OhB+/btycvLIysrizVr1rBjxw7GjRtHz549+fHHH/n4449p164dd955J82bN2fmzJnn+60KIcSfoOzKvgAo8LgVv6w+yJZVGXhcCl2Hi9rXoN/wprBuBebatVBc7FsIpnMnbNeNYM9eF99/cQh3sRcU1G0STv8xTYlwmBR+8hXm5s1oHg8YBkZcHI7+A/jxp3wS1mbhdvnG0LToEkm/0Y0I9RTg+XgBpKaCx4NyODAGD8aI64my2yTDVGNBGWR+/fVX7r33XhYsWMBDDz2EpmnEx8czc+ZMFi9ezKeffopSivDwcObOnUtRURG1a9fmf/7nf8jLy+Oll16idu3a9OjRg4MHD/KPf/yD2bNn07NnT1auXMn06dOZM2cOr732GmFhYef77QohxHllmoqMA0V88Z995GS4QCmc0Q6uGnsRDaJc5C9ciMrKQlMKFRVFyA034KrfjK+mpZCaXIAywR6qEze0Pm0uiUSl7MT73/fRsrN9D2jQANvf/saxkDp8v3gneVluUBAeaWPAjU2IaRaG+ct6zBUr0Ip93U3aRRdhv244enQ0GHY0WRCv2grKIFO3bl1GjhzJbbfdRteuXfnwww+x2+0A1K9fn9zcXA4ePEjDhg0BCA0NpW3bthw6dAiAkJAQq2x6ejpbt25lyZIlfP311wDEx8cDEBMT82e/NSGEOI/KnzvtcSvWL8vgt03H8HoVNrtOl351uGJgLYqXL8T788++lhWbDT0ujpBrrmH7lny2rjmEq9CLpkGz9hH0HdWICIebgiVLULt3+65xOLD174+9V282LM0gaf0RPG4T3abRtmc0sf3q4ijIxvXRR77WGNNERUZiv2EseseOoOtomo70K1VfQRlk6tSpQ79+/U5aplWrVnzxxRcAZGZmkpGRQZMmTcot26VLF/r371/mnhkZGYSFheF0Os9GtYUQ4i/ueBjQtJLtAkw4+Hsh679KxVPsQdc16jcNZ9CNzXEeSyVv/ny0/HwwDIiJIfTmm8lVTr77/Ffys93ohkao02Dgzc246OIIin9ej/nFF74Qo+vQvDn2kaNIy9RYu+AQrgITTdeoFxPK4AlNiY428C5f7wtLSoFhoPfqRci1w1GhoSUVPX9fMXH+BWWQqYy+ffvy6quv0q1bNwoLC3n88cdxOBzllp06dSo33XQTderUITc3l8WLF1O/fn0ZIyOEqGZ8g319K+X6znhNjd9/y6Fu0xDi6tRH0zTadqtJxx7RuNb9DA0bojVo4NsmoFccIVdeScrOYoxQG5cOrlcyNsZJryGNsOsm7tRU6NgRrXVrlGFg9OuL3rEj+3/MJ7pRGLH1QrHZNC7uFkmnXrUwlAtPWhp6XBy4XGDYsN8wBlWvrm9etq/aohrTlH/pRCGEEBesB/p6T1lGKYXXdFFkZhJa+xB/f7YFdRvZycv14CryzejUgNBwG2HhOiovzzcNWvc132gREejOGhQWeijI9fjKa2CzGzij7GjKizc7G1VY4HuepqE5nRAaRn6Bl+J8Xx11Q8MWohEapqGZJnpeLprbdz8FmM4IVEiI7+YKUDor5mfw4xKDEBrhsEWUdDdV7OVvjdP7Qoq/nAu2RUYIIcRp0HQMQinIdvDhzF/xOA7jUcXWtgS+Iv51fRWY/pOUdPPox1cALmne0dBK1nlRmB4PyvSHKs0Xgvx3M0tN/rZ23FYlq/+W+p3b3++laWjohFKLvPRaoOpKN1M1JEFGCCGERUdD1+yYrnB+T8yl0KXwlmnc8IeK4+vMlH2tonM2lDp7P3o0NMI0GyGhoUTYnRi6o5w6iQuZBBkhhBCAfwdpHUNz4NBrgkPHpkcBir/sdkYKDN1OiC0KmxaOrsuPtepG/saFEEKU4muRcRgRGJqNUKMWvkHAJV1JJWXKUiec928sWfZ8xddX5d6lmok0DUN3oGsGGnpJIBPVhQQZIYQQFt/YFANNaeiaDVCYqmTnad+m0xVcyPG8UV6ZqpyvVFnNmlmloYOmlwzTkRBT3UiQEUIIUYam6dZgW02VmuFzspygnaJMVc5XoayEl+pNpl8LIYQQImgF5e7XQgghhBAgQUYIIYQQQUyCjBBCCCGClgQZIYQQQgQtCTKAaZps3LjxfFdDCCGEEFUUdEFmxYoVJesc+D5at2592vf65JNPeP7553G5XNxyyy1nsZZCCCGE+DMEXZABuOGGG0o2JFPs3LnTOn/06FHr+PDhwxw+fNh6ze12k5CQgMvlss6NGTOGJ554osz9U1JSME3zHL4DIYQQQpwNQRlkMjMzWbt2Ld999x0AaWlpxMbGMmXKFCZOnMjQoUN56qmnGDVqFHPmzME0TUaMGMGcOXO44oor+OWXXzh06BAdOnTghRdeKHP/Ll26sHjx4j/7bQkhhBCiioIyyCQlJTFt2jRmzJhhnTMMgzfeeIO33nqLwsJC/vOf//D000+zdOlSdF1nyZIlPPLII/Tu3ZvFixfToEEDJk2aVO79v/vuO4YNG/ZnvR0hhBBCnKag3KKgb9++fPTRRwHn7HY7ADabDcPwLadds2ZNXC4XmZmZ3HjjjTRp0gS3201UVNRJ79+5c+dzU3EhhBBCnFVB2SJTVevWraNhw4a89957DB06lBN3ZbDZbHg8Huu8jJERQgghgkNQBplvv/2W+Ph44uPjueOOO05Z/oorrmDNmjXccccdfPbZZ/z8888Br9tsNoYPH84jjzwCyBgZIYQQIljIppHgwb1xAAAgAElEQVRCCCGECFpB2SIjhBBCCAESZIQQQggRxCTICCGEECJoSZARQgghRNCSICOEEEKIoCVBRgghhBBBS4KMEEIIIYKWBBkhhBBCBC0JMkIIIYQIWhJkhBBCCBG0JMgIIYQQImhJkBFCCCFE0JIgI4QQQoigJUFGCCGEEEFLgowQQgghgpYEGSGEEEIELQkyQgghhAhaEmSEEEIIEbRs57sClbV3797zXQUhxBlq0aLF+a6CEOICIy0yQgghhAhaEmSEEEIIEbSCpmvJLycnh+TkZAA6dOiA0+kEoKioiIMHD9KiRQtSU1PJzc2lffv21nVKKX766f+zd+dhVVX9+/jv4wHFEUFUVFAGFQEFTExJRNJQccghMs0ppZBm0zIrHMmyckqyAR9z+DwhWg484ggimYoTooCIA4ICijLIPHPW9w9/7F9HQE1FWnK/rovrcq+z9t5rnQ6nmz29T8La2hotWrSost0rV64gMzOzSnu3bt2gr6+PtLQ0ZGRkwMrKCrq6urU0OyKi2nHnzh2cO3cODRo0QJcuXdCuXbsqfc6fPw9bW1sAwLFjx1BaWlqlj52dHaKjo7XaevTogVatWtXOwIkeQCWEEHU9iIdReY3MsWPHMHnyZLzwwguIi4uDq6srli9fjhMnTuD1119HQkICVq1ahTNnzmDTpk3K+hUVFejatSsCAgLQp0+fKtufPXs2du7cWaV9/fr1CAoKQmhoKAwMDFBQUICVK1fC2dm59iZL9IziNTJ15+DBg3jppZeU5XHjxmHdunXKH4MajQYmJibYunUrnJ2dYWpqipSUlCrb2b9/P4YMGaLVFhwcjOHDh9fuBIhqIO2ppf/7v//DDz/8gJ07dyInJ+ext7d8+XIkJCTgu+++g6mpKRISEpCQkIAbN27g9OnTCA0NRUhICEaPHo05c+agrKwMOTk52Lx5MwoLC5/AjIiIal9JSQl2796Nc+fO4ZNPPlHag4ODcfPmTaxfvx4AkJycDCEEPD09MXXqVAghIISAWq0GAGVZCIHhw4fjzp07+OWXX5Cfn18n86L6S9og88MPPyAwMBB9+vSBvr5+re3nwIEDGDFiBFq3bg1dXV14enri1q1bOH36NI4cOQIfHx+cPn261vZPRPQkNWzYEMOGDcNrr72GXbt2Ke0bNmzA5MmTERgY+FB/HIaHhyM8PBy3bt0CcPe70tvbG0eOHKm1sRNVR7prZCppNBpoNBoAQHZ2dq3tJysrC82bN1eWjY2N0bx5c9y5cwfu7u7YsmULHB0da23/RES1wc7ODunp6QCAmzdvYteuXbhw4QIiIyMREBCAt99++77rL1myBADw6aefom3btnj11VfRoUMHnnanp07aIPPBBx8AuHued/fu3ejcuXOt7KdDhw5ISkpSltPT05GXlwczMzM0aNCAIYaIpBQVFQVLS0sAd4/G6OvrY/PmzTA0NIS/v/8Dg0xISIjWcoMGDRhiqE5Ie2oJANLS0nDlypVavUZl4MCB2LdvH44dO4abN2/i888/R69evWBjY8NrZIhIOmlpadi9ezf8/f3x2muvQQiBdevWKRcCDxo0CGfPnsWJEyf+0XZ5jQzVFWmPyFT+JTF8+HC89tpruHDhgtbrR48eVfro6OggLi4OAPD6668rfT7++OMH/tUxfPhw7N69G5MnTwYAuLm5wc/PDwBw6tQp+Pj4wMLCoto7oYiI/m0sLCxgb2+Pd999F5988gkOHjyIxMREHDlyBMbGxgCAw4cP49dff73v95pKpVL+fejQIeTk5MDb2xtWVlZwdXWt7WkQKaS7/bquJCcnQ61Wo3379kqbRqPBmTNneHqJ6CHx9utnl0ajwbFjx3h6iZ46BhkiemoYZIjoSZP6GhkiIiKq36Q5IkNERER0Lx6RISIiImlJd9eSRqPB4cOHAQC9e/dG06ZNa3V/6enpOH/+PPT09NC3b99a3RcRERH9M9IdkSktLYW7uzuWLFmC27dvY8KECVpP9rWwsEB5efkT219cXBwWLVqEKVOmPLFtEhE9bUVFRbh48SKAu898SU1NVV7LysrCkSNHkJqaij///FNrvZiYGMTHxyvLKSkpiIyM1KqMnZSUhDNnzmitl5SUhOjoaFRevZCYmKiUNaj8qaioqDLO4uJinD59GhUVFYiIiHj8if9DN2/efOr7pMckJFNUVCRMTEyU5f79+4tRo0Ypy+bm5qKsrEwIIcT169dFcnJylW1cvXpVZGZmCiGEOH/+vCgqKtJ6PTo6WuTm5irL6enpokuXLk90HkRET1N4eLio/MoPDQ0VNjY2oqCgQAghxL59+4SRkZE4c+aMACBiY2OFEEKUl5eLNm3aiLVr14qSkhIxadIk0aJFC2Fubi5at24tDhw4IIQQYvHixcLFxUUIIURGRoYYPHiwaNGihbC2thZWVlaivLxczJ8/XwDQ+snLy6syzt27d4tJkyaJ48ePi6FDhz6Nt0YRGBgopkyZ8lT3SY9PuiMy91KpVDA0NMTq1au12rdv34533nkH8+bNw7Rp0wAAv/32G9zd3bFy5Uq88MILGDduHH766Sf07dsXMTExKCkpwaBBg7B8+XI4Oztj27ZtdTElIqJaFxcXBy8vL622nj17ws7ODtu3bwcAhIaGorCwEBMmTMCGDRtw5MgRXLx4EfHx8Zg0aRKmTZumdWQGAHx8fJCbm4ukpCTExcUhJCREqZj90ksvaVXNbtasmda68fHx2Lp1K1q1aoWAgAAYGxtXKWAZEhKiXF7wJC1YsADLli1TaviRPKQPMgDwxRdfYOvWrTh79qzSNnbsWGzfvh3e3t4IDg5GWVkZAMDa2hqrV6/GBx98AGtra/j5+WHkyJH4888/8eOPP2Lo0KHKL+zcuXPrakpERLVKT08P+/fvx3/+8x+t9qlTpypBZsuWLRg3bhyaNm2KnTt3Yvz48TA2NkbDhg0xa9YspKamVql2/dtvv+GTTz6BgYEBAMDU1FR5LScnRzmtVFJSUmVMFy9exPbt26Gvr48dO3bA0NCwSpCZPXs2vvjiiyfyHvydh4cHfHx8nvh2qfZJd7HvvYQQUKvV+O9//4tx48ahqKgIAPDzzz9jy5YtcHJygp6eHjIzMwEAurq6AO6WLdDRuTt9AwMDlJaWIiYmBhUVFfD19QUA5UgOEdGzRKVSQU9PDwEBAXjllVfg6+urlByYPHky5s6di5iYGOzYsQNBQUEA7t74oK+vr2zDxMQE+vr6yncrcPdam7y8PLRt27ba/SYmJipVswMCAtC6dWut152dndGhQwfMmzcPa9euxXfffYcGDbT/3g4KClK+u5+kHj168MGrkpI+yFQyMzPDhx9+iEmTJgEA1q1bh4CAAHTp0gW7du1SLji7Hzs7O+Tn52PevHm1PVwiojpTeWrHzc0Nn3zyCT777DPlDtDWrVvD3d0dXl5eaNeuHVxcXAAAnTp1wuXLl5VtpKWlIScnB126dFEuBjYwMECTJk1w+fJl9OvXr8p+HRwcqlTNrnTlyhVMnDgRd+7cwYsvvoji4mL8+uuvePPNN7X6mZubP5H3gJ4dz8SppUoTJ07E+PHjAQCjR4/G5MmTMW7cODRv3vyhrn739vbGoUOHMGjQIPTv3x979uyp7SETEdUpHx+fKvWRJkyYgOPHjyvFcgFgxIgR2LZtGw4ePIjk5GS89dZb6NevHxwcHJQ+KpUKY8eOxeLFi/HXX3/h9u3bWLx4cZXraKrTuXNndOvWDf7+/ujduzeWLVtWJcQAtXeNDEmsDi80fiT33rX0NPCuJSKS3b13Lenr6yuvZWRkCAcHB2W5qKhIGBgYiNTUVKWtsLBQuLu7K3ccjR49Wnn973ctXb9+XfTq1UsAEDo6OmL8+PHi1q1b1d619HcajUa0b99eFBQUCBsbG3H79u1q59G7d2/x0ksvPZk35R47d+4UkyZNqpVtU+2RrkRBcXExDAwM4OzsDH9//1o/zPjnn39i4cKFSE1NxaVLl2p1X0RE/3aJiYlQq9Xo2LFjjX2EELh8+TJatGgBY2PjJ75/HR0drYuIqX6TLsgQERERVXqmrpEhIiKi+oVBhoiIiKTFIENERETSku45Mk+7+rVMLl++jNTUVBgbG6Nbt251PRwiIqJaJ90RmXurX5eUlGDmzJlwcnKCq6trlcdt/1sUFxdj9OjRyvKVK1cwcODAJ7qPffv24aOPPsKyZcue6HaJiIj+raQLMgBgZGSEkJAQmJubY9GiRWjcuDEiIiIQHh6u9QCl9PR0rfLzwN0gFB0dDY1Gg4KCAq0nVVaKjo5GcXGxslxWVoaoqKgqD3XKz89HQkLCQ487KCgIK1asqPa12NjYKjVFKtuLi4tRUVGBmJgYrScUV46rso7U+++/z1ohRERUr0h3aule69atw5UrV6q0L1u2DDt37lSeYfD777/DyckJdnZ2KCwsRFJSEjp27IiSkhK0bdsWP/74I7y9vREZGQk7Ozv89ddfCAwMhIODA8aMGYPOnTsjIiICP/74I9q2bYsRI0agZ8+eiI2NxYgRIzBw4ED4+flh69atAABbW1uEh4crtURUKhU6d+6M4OBgODs7w9DQUBnr3LlzkZ6ejuvXr+PVV1+Fl5cXvL29UVBQgJYtWyI0NBQODg4wNDREZGQkwsPDcfXqVUyYMAF9+vTBiRMnEBgYCGtr66fwjhMREf2L1OXT+B7F35/se+vWrWqf8pudnS26dOkiysvLhRBCDBo0SBw4cEBYW1uLrKwsIYQQxsbGoqysTBQUFIh27doJIYSYMWOGCA4OFkIIsWfPHjFu3Dhlm6mpqWLWrFli3rx5Ijk5WfTs2VMIIURWVpbo3r27EEIIOzs7cfPmTXHy5Enh4eGhNabi4mLRtWtXcf36dWFvby+ioqLEwIEDlddzc3PFpk2bhKurqzKWHTt2CCGEGD9+vAgNDRVCCDFgwABx7tw5MWbMGHH8+HEhhBAHDhwQU6ZMEUII8ccffwhPT89He3OJiIgkI/URGUNDQ6SkpCAnJ0erKmtaWhqMjIygVqsB3K1qmpKSAqBq9WsdHR2l39+ZmpoiNTUVGRkZeP3112FiYoKysjJlP3p6egDuFknLzs4GALzxxhvYsGEDbt++rRSvrCT+vyJtpqamWLhwId577z00bNgQAODp6YmsrCxYWVlpVZL9+1grx1hZqfvcuXMIDg7GgQMHAABubm6P81YSERFJSeogo6Ojg7Fjx+Krr77CN998g/Lychw7dgyOjo5ITExEeXk51Go1oqKi8Nprr/2jbR87dgy2trY4cuQI2rVrh19//RWBgYH466+/alxn0qRJePHFF9GgQQN88803NfYbPXo09u3bh0uXLuHOnTsICQnB9evXcfPmTezevfuhxmdvb49BgwbB1dX1H82LiIjoWSJ1kAGAb775BiNGjMCOHTugp6eHN954Ay4uLpgzZw7MzMxgYmICc3Nz9O3b96G2t3DhQixbtgzGxsb4/vvvoVar8f777+Ott95CTk4OkpKSaly3devW6NKlCywsLJSjKTVZvnw53nvvPRgYGMDKygoeHh4QQqC4uBg3b9584Dh9fX0xadIkGBkZIS8vD0FBQWjbtu1DzZGIiOhZIV2tpeLiYnTp0gXJyclKW0VFBaKjo2Ftba2c8nkU3t7eGDlyJIYPH/7I2xg/fjw+//xz2NnZPfI2Hse2bduwd+/ef+1t6ERERE+SlLdfZ2RkwM3NDYmJiQAAtVqNnj17PlaIeRLGjBkDCwuLOgsxfn5++PLLL+tk30RERHVBuiMyRERERJWkPCJDREREBDDIEBERkcQYZIiIiEha0gUZjUaD8PBwhIeHo6CgoK6HU6P4+HhkZWVptRUXF+PMmTNPZPtlZWXK+/D3ulBERET1iXRB5t7q1ytXrsRLL72EUaNGISwsDAAQGhqqPG33cZw/fx6xsbE1vv7RRx/VuJ9ly5bh1KlTWm1paWmYOXPmY48LAAoLC7FkyRKMGjUKaWlpT2SbREREspEuyAD/f/VrfX19rFmzBiEhIQgKCsLAgQMBAIsXL64SMDIyMrSePRMTE4O8vDytPteuXcONGzeU5YCAAERGRlY7hoKCAgQEBGDLli1a7Xfu3MG1a9e02oQQiI2NRUVFhVa7RqNBTEyM0n7v/oG7R3b+fmNZ5bK+vj5CQkJgaWlZ7fiIiIjqA6mf7NusWTOo1WocOnRICTFnz55FSkoK/Pz8MHz4cJSVlWHRokWwtLSEk5MTPD09MWzYMJiamiIqKgrz58/HK6+8gjlz5iAmJgYAYGdnh88++wwRERG4dOkSNBoNpk2bprXvgIAAzJw5E//9738xY8YMAHfLGrz//vvo27cvDh48iFdffRUA8Prrr0OtVmudCnNxcUGbNm1gYGCAr7/+Gt9++63W/pcsWQI3NzdYWVmhvLwcP//8s9YyH3hHREQEuatfCyFETEyMcHFxEaNGjRK3b98WQgjRv39/kZiYKIQQYt++feKll15S+q9YsUJ8++23Qoi7Fac7d+4sIiMjxciRI5U+AwYMEJcvXxaff/652LBhQ7XjcHR0FFlZWWLw4MHiwoULyn7z8vKEEEJ4enqKffv2iW3btoklS5YIIYRITEwU/fv3V/qGhYUJIUS1+z99+rQwMTERcXFxQgghMjMztZYr9ezZU5krERFRfSP1ERkA6N69Ow4ePIhVq1bhgw8+wObNm6v06dSpk/Lv6OhoVFRUwNfXFwAwbdo0nD17Frm5uUrboEGD0KhRoxr3eeLECajVapw7dw5WVlbYtGkTPvzwQ2RmZqJZs2ZafQ8dOgQnJ6dqt2Nubg4A1e6/TZs2WL9+Pd58802MHTsWs2fPrrJMRERU30kfZPLz89GsWTM4OzsrF/vq6OigsLCw2v52dnbIz8/HvHnzlLbIyEjo6upqtVVup6ioqMo21q5dC1tbW/z1118wMjLC+vXrsXDhQty6dQsZGRkwMjJS+pqZmeHkyZN4/fXXa5yDvb19tfvX1dVFSEgILCwsMGXKFHTv3l1ruXXr1g9+g4iIiJ5hUgeZiooKuLi4oFWrVsjJycGaNWsAAIMHD8bYsWPh6uqKMWPGaK3j7e2N4cOHIzw8HKWlpfjss88wbNgw9OjRA3369EGLFi3wwgsvYNGiRRgwYACmT5+Obdu2YceOHWjWrBlyc3Pxv//9D9euXUPjxo0B3L02JjQ0FAsXLkS3bt1gZ2eHW7du4dVXX8Ubb7yBAQMGwMHBASYmJtXOo1evXlX2P2vWLNjY2KBHjx7o3r071Go1rKyslGVDQ8PafXOJiIgkIF2tpXurX5eVlSE2Nha2trZo2LBhHY/u6Xvuueewfft2mJmZ1fVQiIiInjopb7/+e/VrXV1d9OzZs96FmJycHLi5uSEhIaGuh0JERFRnpDsiQ0RERFRJyiMyRERERACDDBEREUmMQYaIiIikxSBDRERE0pLuOTIajQaHDx8GAPTu3RtNmzat4xH9u1y+fBmpqakwNjZGt27d6no4REREtUq6IzKlpaVwd3fHkiVLcPv2bZSUlGDmzJlwcnKCq6urUkxx69at+OqrrwAAoaGhVaphP0hRURG8vLwwZMgQDBs2DNnZ2cjIyMDBgwfvu97D9HlYTZs2hUqlUn4uXbqEpKQkjBo1qsZ19u3bh48++gjLli17ImMgIiL6N5PuiAwAGBkZISQkBADw+eefo3HjxoiIiNDqM27cOOXfixcvxqZNm9CyZcuH3se+fftQVlaG/fv3K2379+/H77//jkGDBiltycnJUKlUylN7IyMjq/SpfGhf9+7doaur+9BjqKyWvXfvXgQEBKBr164AgKCgIKVPaWkp4uPj0b17dzRo0ADvv/8+2rdvj7179z70foiIiGQl3RGZe61btw6ff/65VltaWhpsbW3x9ddf4+zZs0hJSYGfnx/CwsIwZMgQxMbGAgACAwNrLL5oYWGBqKgoXL16VWnbu3cvoqOj4evrC41Gg+3bt+Odd97BvHnzMG3atGr7xMXFwdHRET/99BMcHR1x4cKFfzzH+fPnK3McO3Ys3N3dAQA2NjYYP348vvjiC/Tv3x95eXn/eNtERERSq9vi2/9cUVGRMDExEUIIcevWLeXf91qzZo346quvhBBC9O/fXyQmJgohhNi8ebP44IMPhBBCjBo1SkRFRdW4r+DgYGFhYSHmzp0rSktLxb59+4Snp6dWn9LSUnH8+HFhZGRUbZ8xY8aI48ePCyGEOHDggJgyZco/mu/vv/8uJk2apCzHxMSIoUOHCiGEsLa2Fnl5eUIIIebOnSv8/PyEEEL88ccfVcZJRET0LJL6iIyhoSFSUlKQk5Pz0Ou88sor+N///oe0tDRcv34dDg4ONfYdPnw4oqKicO3aNaxdu7bK6z///DMGDx6MoKAg6OnpITMzs0qfc+fOITg4GL6+vjh+/Djc3NweeqwajQbz5s2rcsSpOqampkhNTX3obRMRET0LpLxGppKOjg7Gjh2Lr776Ct988w3Ky8tx7NgxuLi4VOlXWFgIANDV1YWHhwfGjRuHN954A0IInD17Fj179tRaR6PRoKSkBC1atICDgwNycnKgo6ODoqIipc+6desQEBCALl26YNeuXRBCVOljb2+PQYMGwdXVVWv7Z8+eRY8ePaBWq2uc36ZNm+Do6Ahra+sHvhcREREYMmTIA/sRERE9S6Q+IgMA33zzDYKCgtC1a1c899xzOH36dJU+gwcPxtixY+Ht7Q0AeO2113D06FFMmDABubm5GDVqlNa1MABw/vx59OnTB25ubggJCYGXlxfs7e1x9OhRuLm54ciRIxg9ejQmT56McePGoXnz5oiIiKjSx9fXFx999BHc3NzQt29f3Lp1CwCwYMECbNy4scZ5aTQafPnll4iJiYGbmxvc3NywZ8+eKv3GjBkDZ2dntG/fHhMnTnyct5KIiEg60hWNLC4uRpcuXZCcnKy0VVRUIDo6GtbW1tDT03vgNmJjY+Hr64stW7YAAN5++22sXr26yh1Fubm5uHbtGrp37w6VSvVE57F8+XIMGzbsoY621MTGxgYnT55Es2bNtNq3bduGvXv3KreiExERPaukPCKTkZEBNzc3JCYmAgDUajV69uz5UCFm165d8Pb2xpdffgngbqiZPXt2tbdFt2jRAj169HjiIaa0tBS2traPFWJq4ufnp8yNiIjoWSfdERkiIiKiSlIekSEiIiICGGSIiIhIYgwyREREJK1nIsjcunULCQkJdT0MIiIiesqkCzL79+9Ho0aNMGLECLz22ms4fPgwQkND4e/vX9dDIyIioqdMuiAD3C2cGBwcjAULFmDSpElKe3JyMrKysrT6xsfH4/bt21pt5eXliImJ0WorKytDVFQUysrKlLb09HTk5+fXwgyIiIjoSZC6RIFarUZmZiaEENizZw+ys7Oxd+9e/PzzzxgyZAhee+01qNVqXL9+HR4eHpg9ezaef/552NjYID8/H1lZWQgLC0NcXBwmTJiAPn364MSJEwgMDIS1tTW8vLxgbm6OFStW1PVUiYiIqBpSBpkLFy7ggw8+wJYtW/Dxxx9DpVLBzc0NK1asQFBQEP744w8IIdCkSRNs2rQJxcXFaNWqFd5++23k5+dj+fLlaNWqFZ5//nncuHEDPj4+8Pf3R58+fRASEoKlS5di48aNWL16NRo3blzX0yUiIqIaSBlkWrdujbFjx2L69OlwcHDAb7/9pjyZt23btsjLy8ONGzfQrl07AICenh66deuGtLQ0AECjRo2Uvrdv31YqVB84cAAAlArVpqamT3tqRERE9A9IGWSMjIyqVJO+V5cuXbBz504Ad0sapKenw8TEpNq+NVWoTk9PR+PGjavUMiIiIqJ/Bykv9n0YAwYMQMOGDdGrVy+4uLjgs88+Q8OGDavtW1OFai8vL8yfP/9pDpuIiIj+AdZaIiIiImk9s0dkiIiI6NnHIENERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNLSqesBPCyVSlXXQyAiInqmCSHqegj/mDRBBgASEhLqeghEVI9YWlpK/73zLMyhtvC90WZpaVnXQ3gkUgUZkpsQAidPngQAGBsbo2PHjsqRtuTkZNy4cUOrv6OjI9RqNbKyslBQUABTU1Ot11NSUpCfnw8rKyuoVCplG7q6urC2tkbjxo2Vfjk5ObC1tUVcXBzy8vJgamqK9u3bAwBu3bqFpKSkKuNt37498vLy0Lx5c2XfZWVluHjxIoyMjGBsbKzM68qVK+jSpQsAQKPR4NSpU+jRoweaNGnyhN49+req/FxbW1ujRYsWAIAzZ86gY8eOMDIyAlDz5+be9U6ePImuXbtCpVIhPj4eAGBlZYWWLVsq+9NoNIiPj0fnzp3RsGHDpznVGlX+XjVr1gzdunWDWq3Wev3WrVto0qQJmjdvjjNnzqCsrKzKNnr37o1Tp05ptbVr1w4dO3as1bE/rsuXLyu/+5Xi4uJgY2OjLMfHx6Nbt27K8qN8HoqLi9G2bVsAQHZ2Nq5cuQJbW1tER0dXGVPz5s219v+sUwlJjiOpVComZ8lVVFSga9euyrKtrS2+//57mJubY9WqVfDz89PqHxMTg4ULFyIsLAzNmjVDr169sHz5cmRnZ2PmzJmIioqCsbExNBoN9u3bBz8/P2UbOjo6ePXVV/Hll1/ihx9+wNGjR7F582ZMnDgRx48fBwC0atUKX3zxBYqKivDFF19UGe/777+PqKgo2NvbY9asWTh+/DhmzpwJAwMDXLp0CUOHDsWKFSugo6MDBwcH7NixA507d0ZJSQlsbGywa9euevVl8g+wNmkAACAASURBVCx6mL/YKz/XAQEB6NOnDwCgX79+mDNnDkaNGnXfz82963Xv3h1+fn5o1KgRJk+erOzD0dER69evx8WLF/Huu+/C0NAQycnJ+O9//4sePXo89hwe199/r5o3bw4fHx94eHgor8+cORNWVlZ4++230a9fP6SlpVXZxoULF2Btba3V5uXlhU8//bTWxv24741Go0G/fv3g5+cHR0dHpd3NzQ0rV65E9+7dcf78eXz66acIDg4GgEf+PCxatAg7d+5E48aNcfjwYcyaNQtbtmzB4MGDq4yrb9+++O233/7xfCwtLaU8tcSLfempCwgIwPbt29G2bVu8++67qKioAHD3yz8hIUH5AYBt27bhl19+waFDh5QvtOXLlyM/Px+HDx/G/v37sWnTJuUvwJ49eyI6OhoLFizA5s2bcfv27Sr7/+ijj/DXX3/BxcUFq1evxvjx45GQkIA///wTABAWFoaEhATMnDlTWaesrAyfffYZpkyZgr1792Lv3r2IiorC5s2bAQCFhYV49913UVRUVHtvHEnnQZ+bB4mMjMSWLVsQFxeHsLAwHDhwAN26dUNwcDDCw8P/VUHZy8sLx44dw/Dhw7Fu3Tql/c6dOzhw4AD++OMPCCFw9OhRJCQkYNy4cRg7dqzy+175OxwQEKC01WaIeRLCwsJw+/Zt/PHHH0pbXl4erl69irCwMABAeHg4Lly4gOLi4sf6PFy5cqXKH1yVQSwhIQFmZmZYunQpEhISHinEyIxBhuqEvb09pk2bhosXLyIxMRHA3S+AEydO4MSJEygtLUXDhg1haWmJrVu3Ijc3F23atAEABAUF4a233oK+vj6Au4ef/65p06Zo2rQpGjduXOOh9/bt26Nr165KiHqQ6OhoXL9+HW+88QYAoGvXrnB1dUVoaKjSp6CgAD4+Pv/ofaBnR3x8vPL5rTx18jCfm/tp2LAhWrVqBZVKhWbNmsHS0hLnzp1DREQEDAwMqpzCqWtGRkbQ0dFBs2bNlLYdO3bg+eefR15eHo4ePfrAbVS+j1evXq3NoT4R27Ztw5gxYxAcHIy8vDwAQGxsLAAoQebQoUMA7n4WHufz0KhRIxw+fBhbtmyphZnIjdfIUJ2pPIyck5MD4O51MmvWrAEArFq1CoaGhti6dSvmz58PFxcXeHt7Y8KECSgoKFCuPbhXVFQULC0toVarsXr1aq1rCyrt2LEDx48fx5kzZzB//vyHGmtWVhYaNWqkdc2LpaWl1vnpRYsW4YsvvsDWrVsB8E67+mbnzp1o3rw5ACA/Px/Aw31u7qfytNGbb74JV1dXAIC+vj4+/PBDdOzYEd999x3Mzc2f4Cwenb+/P/z9/WFqagp/f3+l/ffff8f06dNhaWmJ33//Hc7OzvfdTuX76OLiAgsLi9oe9iO7ffs2Dh48iJCQEMTExOB///sfJk6ciPPnz8PKygrx8fE4c+YMzp07B2tra1y4cAHt27d/pM+DSqVCo0aNsGrVKrzzzjuYNWsWv1/+hkdkqM5U/uXSoUMHAICNjQ02bdqETZs2wdDQEADQsmVLrF69GrNnz8ayZctQUlKCxo0bV3txrkqlwnPPPYejR49CT08P5eXl1e63Q4cOGDJkCHbv3o3x48c/1Fg7dOiAkpISJCcnK23JyckwMzNTltu0aYOVK1fi22+/BSDnbYz06ObOnat8fg0MDAA83Ofm758TjUaDBg0aKP+TOnfuHGxsbFBSUqL0cXNzw65du1BWVoaNGzfW8qwejkqlwowZM/DTTz8hKysLTZs2BQCcPn0aly5dwqVLl3Dnzh3s378fmZmZ991W5fv45ptvPo2hP7Jt27ahefPm2LVrF1q2bInAwEAAd6/16du3L4YMGQJfX1+4ubmhX79+iI2NfeTPgxACQgg4OzvDy8sL3333Hb9f/oZBhp66rKwsxMXFYdmyZXByclKu2q/OiRMnAEC5a6ioqAhDhgyBn58fTp06hczMTPj5+WndBWFsbIzZs2fD19cX2dnZVbb5/PPPY/Lkyf/oL1lra2uYmZlh1apVyMzMxJ49e7Bt2zaMGzdOq5+TkxPeeeedh94uPdvu97lRq9Xo1KmT8hk/duwYSkpKtD6XDRo0wNKlS7F582acPn0aKSkpuHHjBpo0aYJWrVopR37+LQYPHoz+/fsr17b88ccf6NmzJ/T19WFhYQFjY2Ns3769jkf5+IQQ2Lp1K/r16wcAeOGFFxAXF4ezZ8/i/PnzsLW1xZgxYxAdHY2xY8eiW7duiImJeezPAwC8++67WhcWE08tUR348MMPYW5uDkdHR63/6R89elTrOQaXL1/Gl19+ibi4ODRq1AiffPIJzMzM8PHHH8Pb2xvjx4+HWq2Gu7s7cnNzlb9aAGDKlCnYtWsXfH19n8ihd5VKBR8fH3z00UfYuXMn2rZtq5zyuvc6G29vb0RGRj72Pkl+9/vcAMCCBQswe/ZsrF69Gnp6evDx8UHHjh2RkpKibMPW1hbTpk3Dp59+ivfeew9z585FeXk5unfvjgULFtTV1LT8/Xdv0aJFcHNzw6ZNmxAcHIyvv/4aI0eOBACUl5cjMDAQb731Vo3bev3115V/f/TRR3jvvfdqd/CP4NixY0hJScHWrVvRunVrAHdvlQ4ICMDly5dha2uLrl27olu3bhg4cCAuX76My5cvo6Sk5LE/DyqVCt9//73WXW31HW+/pn81jUaDixcvwtTUVOsCQiEEkpKS0KxZM+WL5GkoKirC1atXq31WBj17ntSty/f73JSWluLixYuwsLBQTsncT3Z2NtLS0pTnJz0IH/pWs7p6b57k5+FJkvX2awYZIqIaPAsh4FmYQ23he6NN1iDDa2SIiIhIWgwyREREJC2pTi0RERFR7ZEkEmiR6q4lGd9gIpKXSqWS/nvnWZhDbeF7o03WAwY8tURERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNJikCEiIiJpMcgQERGRtBhkiIiISFoMMkRERCQtBhkiIiKSFoMMERERSYtBhoiIiKTFIENERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNJikCEiIiJpMcgQERGRtBhkiIiISFoMMkRERCQtBhkiIiKSFoMMERERSYtBhoiIiKTFIENERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNJikCEiIiJpMcgQERGRtBhkiIiISFoMMkRERCQtBhkiIiKSFoMMERERSYtBhoiIiKTFIENERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNJikCEiIiJpMcgQERGRtBhkiIiISFoMMkRERCQtBhkiIiKSFoMMERERSYtBhoiIiKTFIENERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNJikCEiIiJpMcgQERGRtBhkiIiISFoMMkRERCQtBhkiIiKSFoMMERERSYtBhoiIiKTFIENERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNJikCEiIiJpMcgQERGRtBhkiIiISFoMMkRERCQtBhkiIiKSFoMMERERSYtBhoiIiKTFIENERETSYpAhIiIiaTHIEBERkbQYZIiIiEhaDDJEREQkLQYZIiIikhaDDBEREUmLQYaIiIikxSBDRERE0mKQISIiImkxyBAREZG0GGSIiIhIWgwyREREJC0GGSIiIpIWgwwRERFJi0GGiIiIpMUgQ0RERNJikCEiIiJpMcgQERGRtBhkiIiISFoMMkRERCQtBhkiIiKSFoMMERERSYtBhoiIiKTFIENERETSUgkhRF0P4mGoVKq6HgIREdEzTZJIoEWnrgfwsGR8c4mIiKh28dQSERERSYtBhoiIFCUlJYiMjKy17V+9ehVpaWm1tn2qfxhkiIjqgStXrmDs2LEYNmwYZsyYUWO/9PR0vP/++1Xaz58/j9jY2PvuIyMjAwcPHrxvn3Xr1mH//v0PN2i6r+LiYnh6esLR0REvv/wybt68WaVPWFgYevfuDWdnZ/z8888AgKSkJAwePBhOTk7w9vZGeXn50x76E8UgQ0RUD/z4448YMWIE9uzZg19++eWB/dPS0nDjxg1lOSAgoMqRmvT0dMTHxyvLkZGR2Lx5s1af2NhY5OTkPOboqTobN25EVlYWIiIi4OHhgWXLlmm9XlFRgenTp2P16tX4888/sWbNGqSnp8PHxwcvvvgiIiIiUFFRgd27d9fRDJ4MBhkionrA0tISISEhyMvLU9omT56M8PBwAHeDztdffw0AuHDhAhYsWAB3d3d8/PHHyM7ORkREBIKDg7F+/XoAwLJlyzBmzBj4+PjAw8MDQgjs3bsX0dHR8PX1hUajwdy5c7Fy5Up4eHjA39//qc/5WRcZGYmhQ4dCV1cXDg4OiImJ0Xo9NTUV5eXlcHJyglqtho2NDeLi4nD69GmMHj0aAGBvb19lPdlIc9cSERE9urfffhtFRUWwsrLCt99+i0mTJtXY19raGr/88guEEHj++efxxhtvwMnJCV27dsXUqVORk5MDf39/XLhwAWq1Gi+99BJCQ0Ph7u6O/Px8zJs3DwCwdOlS5OXlYefOnfj111/h5eX1tKZbLxQVFcHMzAwA0LRpU62QCgCFhYWwsLBQlps0aYK8vDwUFBTA3Nxcabt+/fpTG3NtYJAhIqoHGjRogI8//hgeHh4YOXIkHBwcHriOSqVCt27dtE4xAXdPOxkZGUGtVgMAevTogZSUFLRv316rn6enJ7KysmBlZYXMzMwnNxkCABgaGiI1NRUAcOfOHbRu3Vrr9VatWimvA0B2djZat24NIyMjpKamwtLSUmmTGU8tERHVA5V/rZuZmcHMzAw5OTlo2rQp7ty5AwBVwkqlhIQE2NjYQEdHB0VFRQAAU1NTJCYmory8HEIIREVFwdraWqvPnTt3EBISgh07duDDDz9UngXWrFkzpQ89nueeew6XL18GAJw8eRK9e/cGAFy7dg0ajQatW7eGWq1GQUEBKioqEBsbCzs7O/Tq1QuXLl0CAJw6dUpZT1YMMkRE9UBgYCD69u2Lfv36wdzcHE5OThg1ahTmz5+PUaNGITo6WumbmJgINzc39OrVCy+++CJMTEwwYMAALF26FG5ubtBoNJgzZw7MzMzg5OSEdu3aoW/fvrC3t8fRo0fh5uaG8+fPw8rKCh4eHnjvvfdQXFyMmzdv4uWXX8amTZtw4cKFOnw3ng0eHh64ceMGhg4dirVr18LT0xPA3etegoKCAAArV67EK6+8gj59+mD69Olo3Lgx5syZg59++glubm7Izc3FgAED6nIaj02aEgVERPR4kpOToVKpYGJiUtdDIXpiGGSIiIhIWjy1RERERNJikCEiIiJpMcgQERGRtBhkiIiISFrSPRDvzp07KCwsRIcOHQAAWVlZiIuLg7Ozs9InJSUFt27dQo8ePdCwYUMUFhbi5MmTVbalr6+Pli1b4tq1a1rt/fv3h1qtRnp6OvLy8rSejEhEJKN27dpVqTr94YcfYtWqVf9oO+7u7rhy5QpMTU1hb2+PFStWQKVSVds3NDQUjo6OaNmy5SOPm+4Wh3z33Xdx7tw5tG/fHr/88gvatWun1ScpKQleXl7Iy8uDvb09fvjhB6SkpFRp27BhA+Lj46HRaLBixQrMmTMHCxcuRJMmTepodo9PuiMyZ86cweDBg1FYWAjg7sN8xowZAwAoLS3F5MmTYWtri1dffRUmJiYICQnB9evX8eKLL1b5mT17NjZs2FClvaioCNOmTYONjQ3c3NwwefLkupwyEdFju3nzJoQQGD16NA4dOgQhBFatWvVIRR3/7//+D2FhYaioqMCePXsAAGVlZYiKikJpaanSb/HixcjOztZa99q1azU+fI+q96DikACqLQRZXVtcXByWLVuG/Px8HDp0CM8//7zUIQaQMMgAQFxcXLU1OzZs2IAjR47g4sWLiI+Px6RJkzBt2jRYWFhACAEhBLp06YJ169ZBCIGwsDAAwEsvvaS8Xnk3+oYNG7Bz505cuXIF33777VOdHxHR01BdUceRI0di9+7dyM7ORq9evZCSklLj+vn5+UhKSoJGo8GYMWOwceNG9O/fH5GRkTh79ixSUlLg5+enfNfOmTMH3t7e8PT0xKeffvpU5vgseFBxSADVFoKsrq179+7YunUrmjZtih07dsDDw+OpzqU2SBlk9PT0sH//fvznP//Rat+5cyfGjx8PY2NjNGzYELNmzUJqaiqOHDly3+3l5OQgPDwc4eHhKCkpQaNGjdCtWzesW7cO2dnZVQ7hERE9C5YuXYpVq1ZhypQp2Lx5MwBg7dq1mDdvHqZPn44lS5ZU+/C8mTNnwsXFBampqZg6dSoaNGiA4OBgzJkzB87OzggKCoKDgwNMTEzw/vvvY+DAgThz5gzi4+Oxd+9e7N27FydOnMCVK1ee9pSl9KDikACqFILMzc2ttm369Ono3bs3zMzM4O3trVWLSVbSBRmVSgU9PT0EBARg1qxZiI+PV87PpqenQ19fX+lrYmICfX39BxYrS0xMxJIlS7BkyRLk5uZCV1cXR44cQWFhIczMzJTS9kREzxJPT09MmTIF58+fV74njY2NMXLkSBQVFWHo0KFV1lGpVPj+++8xYsQIdOzYEc2aNUNGRgYGDx4MHx8f3L59G1lZWVXWO3v2LHJzc+Hr6wtfX18MGjQIjRo1qvU5PgseVBwSgFIIEqhaHPLvbQDQqFEj5OTkYNGiRdi0aRPCw8OfzkRqiXRBpvL0j5ubGz755BN89tlnyumgTp06KQW0gLsVWnNyctClS5f7btPBwQEhISEICQlR/kO3atUKgYGBWLJkCT7//HPcvHmz9iZFRPSU1VTU8erVq9i9ezeaNGmCwMDAGtefNWsWTpw4gYiICBw5cgTt2rXDr7/+iuHDhyvb0tHRUa5ntLe3h66uLubNm6f8mJqa1v5EnwE1FYdMT09Hfn4+AFRbCLKm4pArV67E5MmTYWdnh3HjxiEiIuJpT+mJki7I/J2Pj4/W3UojRozAtm3bcPDgQSQnJ+Ott95Cv379Hqpc/b3+/PNPAFAOy1V+WIiIngUGBgZVijpev34dEydOxJo1a7Bx40Z88803iIqK0lqv8o9JHR0d/PLLL5gxYwacnJwQFhaGt956C9u3b8epU6cAAIMHD8bYsWPh7e2NXr16oUePHujTpw/c3NywYMGCupi2lGoqDunl5YX58+cDQLWFIKtrCw8PR9++fWFubo6UlBQsX74cTk5OdTm9xyckExoaKvT19ZXljIwM4eDgIIQQorCwULi7uwsAAoAYPXq0SE1N1Vq/S5cuYt26dcry/Pnzlf6VPxUVFaJnz54CgNDT0xNff/3105kcERER/SPPZNHIxMREqNVqdOzY8ZG3odFoEBMTA3Nzc7Ro0eIJjo6IiIielGcyyBAREVH9IPU1MkRERFS/McgQERGRtKSptVRTLQ8iotrEs+9E/27SBBl+mRAREdG9eGqJiIiIpMUgQ0RUD7Rr1w4qlUrrZ+bMmXU9LHoMxcXF8PT0hKOjI15++eVqn0AfFhaG3r17w9nZGT///DMAICkpCYMHD4aTkxO8vb1RXl7+tIf+RDHIEBHVAzdv3oQQAqNHj8ahQ4cghMCqVasQGxuLnJycuh4ePYKNGzciKysLERER8PDwwLJly7Rer6iowPTp07F69Wr8+eefWLNmDdLT0+Hj44MXX3wRERERqKiowO7du+toBk8GgwwRUT01d+5crFy5Eh4eHvD39wcAjBw5Ert370Z2djZ69eqFlJSUOh4l1SQyMhJDhw6Frq4uHBwcEBMTo/V6amoqysvL4eTkBLVaDRsbG8TFxeH06dMYPXo0gLs1sO5dTzbSXOxLRERP1tKlS5GXl4edO3fi119/hZeXF9auXYthw4bBzMwMS5YsgYmJSV0Pk2pQVFQEMzMzAEDTpk2Rl5en9XphYSEsLCyU5SZNmiAvLw8FBQVKHcEmTZrg+vXrT23MtYFHZIiI6ilPT09MmTIF58+fR2ZmJgDA2NgYI0eORFFREYYOHVrHI6T7MTQ0RGpqKoC71cxbt26t9XqrVq2U1wEgOzsbrVu3hpGRkdJe2SYzBhkionrozp07CAkJwY4dO/Dhhx8qj7i4evUqdu/ejSZNmiAwMLCOR0n389xzz+Hy5csAgJMnT6J3794AgGvXrkGj0aB169ZQq9UoKChARUUFYmNjYWdnh169euHSpUsAgFOnTinryYqnloiI6iEDAwNYWVnBw8MDQggUFxfj+vXrmDhxItasWQNbW1v0798fVlZW6NmzZ10Pl6rh4eGBd955B0OHDsWtW7eUi3bt7e2xfv16jBkzBitXrsQrr7yCjIwMTJ8+HY0bN8acOXPw8ccfY8WKFWjYsCEGDBhQxzN5PCwaSURERNLiqSUiIiKSFoMMERERSYtBhoiIiKTFIENERETSYpAhIiIiaTHIEBGRlhMnTqCioqKuh0H0UBhkiIjqieDgYLi4uMDBwQGvvPIKLly4AADIyMjAwYMHlX7Tpk1DUVFRXQ2THhKrX9/FIENEVA/cuHED8+fPx65du3Dy5EnY2tpi0qRJAO4WH9y8ebNW/7KyMsTExODvjxorKytDVFQUysrKtPoWFRUhLi6u9idBWlj9+i4GGSKieiAgIADjx4+Hvr4+GjZsiJkzZ+LMmTPIzs7G3r17ER0dDV9fX2g0GgDAjBkzsGDBAgwcOBAAEBcXB0dHR/z0009wdHTEhQsXUFxcjA4dOmDq1KnYsGFDHc6ufmL167tYooCIqB64du0aHBwclGVDQ0PY2Njg1q1bcHd3R35+PubNm6e8/vPPP8PQ0BDPP/88rl69Ch8fH/j7+6NPnz4ICQnB0qVL8csvvyA/Px8bNmxAkyZN6mJa9RqrX9/FIENEVA+0adMGKSkpyrIQAhkZGejQoQOSkpKq9G/YsCEAoG3btsjNzcW5c+cQHByMAwcOAADc3NyU1xli6sbjVr+2tLR8JqpfM8gQEdUDY8eOhYeHBz744AMYGBjgp59+gp2dHZo1awYdHZ0HXtxrb2+PQYMGwdXVVWkrLi6u5VHT/Tz33HOIj48HULX6tampqVb1az09vSrVry0tLXHq1CnMmDGjLqfx2BhkiIjqAVtbWyxduhR9+vSBnp4e8vPzERQUBOBuSDl69Cjc3NywYMGCatf39fXFpEmTYGRkhLy8PAQFBUFfX/9pToHuwerXd7H6NRFRPVJSUoILFy7A1tYWurq6dT0cosfGIENERETS4u3XREREJC0GGSIiIpIWL/YlInrGqVSquh4CSUDWK00YZIiInnGy/g+K6GHw1BIRERFJi0GGiIiIpMUgQ0RUD5iamkKlUmn9dO3aFUVFRXjvvffQtm1bdO7cGe+++y6ys7MxefLkKv1VKhWSkpKg0WhgZWWFnJwcZfsuLi5YsWJFHc6wfvH390eHDh3QvXt3hIWFVdvn9u3bGDZsGFq2bImpU6cqVcsfZl2Z8BoZIqJ6YMuWLSgtLcXixYvRvn17vPnmm9DT08PChQtx4sQJHD9+HKmpqXjnnXfg4+ODzz77DJ6enti5cycOHjwIPz8/AICxsTH27duHS5cuYf369Zg5c2Ydz6z+OX36ND755BOEhoYiJiYGkyZNQlJSklIfq5KXlxfatWuHc+fOYfz48fDz84OLi8tDrSsTHpEhIqoHXnjhBbi6uqJVq1bo0KEDXF1d0bdvX/j7++OLL76Aubk5nJ2dMXLkSBw+fBg2NjZwdXWFhYUFmjdvDldXV7i6ukJPTw8bNmzAq6++iv/85z91Pa16afv27ejevTt69+6NqVOn4ubNmzh16pRWn8LCQgQFBeGVV15Bp06d4O7ujtDQ0IdaVzYMMkRE9dTt27eRnZ2Njh07Km22trZIS0urcZ2bN29ix44dWL58OZKTk3H48OGnMVT6mzt37sDc3BwAoFar0bJlS2RkZFTpA0Dp17x5c2RkZDzUurJhkCEiqqcMDQ0BAKmpqUrbtWvX0KlTpxrX2bhxIzp16oSEhAQ4ODjwqEwdMDAwUCqPV1RUIDc3F23atNHqU/nftrJfXl4e2rRp81DryoZBhoiontLR0cGoUaPw9ddfIyUlBaGhoVi9ejVefvnlavsLIbBu3To4OTnhr7/+Qq9evRAYGIjMzMynPPL6rU+fPoiIiEBBQQFCQ0NhamqKXr16ITMzE2vXrkVhYSEaN26Mnj174uDBgxBC4ODBg3B3d69xXakJIiKqbBlvQAAAAYdJREFUNzw8PMScOXOU5bi4ONGpUycBQAAQI0aMELm5ucrr33//vXBychJCCBESEiJ0dXVFZmam8rqpqalYvny56N+/v7INAGLGjBlPb1L1TGlp6f9r745tK4TBKIy6oqNjETomeNMwFGPQUSPRMY6R/lcnUtq8XOWc3pIr65Nt2fV6vaq1VvM813meVVW1bVu11uo4jqqq2ve9hmGocRxrXdfqvf84NpnfrwH+ud57u++7TdP05b4MWZ7nadd1tWVZPj2VXyVkAIBY7sgAALFiHsTzeyvwCTat4W+LCRmLCQDwnaMlACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYgkZACCWkAEAYr0BkaNMWFB3NJ4AAAAASUVORK5CYII", }, - { - name: "Mobile-Invoice-1", - template_id: 1001, - ImageUri: - "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UwkJCUoppebOnauMjY2Vi4uLsrGxUaampmr16tVa+ylTpqgBAwZor+Pj45Wbm5uqXLmy9l8fHx+llFJ79uxRxsbGytPTU+fnzJkzeR6Pi4uLSk5OVtHR0apfv34v9DsRoqSU8VgpFgZ5RvqsTz75BE9PT1xdXXWWGxkZMWTIEDZv3szs2bO15du3b6dPnz5UrVqVEydO8NlnnxEQEEDHjh0BmD59OhMnTsTLy4v69evn2N+kSZPIzMwkKioKKysrUlNTiY2N1dabmZmxf//+Ao09ISGBypUrY2ZmxvHjx+nQocOL/AqEEMWoXFwjdXZ2ZtSoUfy/9u48Kss6///4CwSFEEiR3Cj30tHMssW9LGwqLZvS1MKac1pmWqapppk60zfbbdoszcpGMUsr7dSxRc3lGE67hokRYcqUCyKKy1FIEYT37w+P92/ubjDkDUHwfJzDOXJd1+e6P9fNfT+97+u6xcLCwpB1KSkpWrdunVatWiVJOnDggN577z1dddVVkqTnnntOl19+eSCikvT4448rKSlJr776asj+vvvuO82ZM0fPPPOMEhISJEnNmjVT+/btj3neM2bM0IUXXqgffvhBw4YN0/3336833nhDixYt0uLFi9W+fXstXbr0mPcLoGY1+JCGhYWpR48eGj9+vG666aaQ9T169FC/fv00d+5cSdKCBQvUpEkTXXrppZKktWvXatCgQUFjIiIidPbZZ2vDhg0h+1u7dq1iY2ODwvtzpaWleuSRRwJfJSUlFW53ww03aMCAAXrllVe0bNkytWzZUunp6brkkkvUrFkz5efnq1mzZlW+LwDUjgYf0iPuuecebdq0Sa+99lrIunHjxumtt95SWVmZ3nnnHY0ePVqRkZGSpN27dysuLi5kTFxcnIqKikKW5+fnq127djU2708//VSDBg3S9u3b1aZNG4WFhUmShg4dqrKyMp177rk1dlsAqqfBnyM1M0lSeHi4Zs+erUGDBun2228PBEk6HNK7775b7777rt577z0tXLgwsO7EE09UXl5eyH43b96srl27hiw/4YQTtG3btqPOKTIyMuicbEV++OEHzZkzR7m5uXryySeVk5OjwsLCQFgB1B+N5hWpJHXp0kUTJkzQww8/HAisJCUmJmr48OG644471Lp1aw0dOjSw7pxzztHixYuD9rNz506lpaXp4osvDrmNPn36aN++ffrggw9cc23RooUOHjyoUaNGafDgwcrPz9cNN9xQ4cUtAHWrUYVUkm6++eYKz1+OGzdOubm5Gj16dNCr1bvvvlurV6/Www8/rL179yorK0ujR49Wnz59dNFFF4Xsp2fPnrrjjjt0/fXXa8GCBSoqKtJnn32miy66SD/99JMkqby8XCtWrAj6+vlpghYtWmj37t0aN26czjvvPO3evVtjx44NhDQzM1PXXHONsrKyavLuAVANjS6kkvTqq6+GnPe87LLLdPzxx2vs2LFBy7t27aqPPvpIb7/9to4//nidccYZateunT788MNK9z9p0iT95S9/UUpKimJjY3XNNddo2LBhgQtDxcXFGjp0aNBXTk5OyH5Wrlypc845R+vXrw/6BwCStGXLFs2dO1e5ubnVvRsA1JAw+9/3uPVMWFiYamt6W7Zs0Yknnhi0LCMjQ3369Kl0zLp165SUlKTmzZtX6TZKS0v13XffqXfv3kGvcqvCzLR69WqdeeaZ2rVrl4qKitShQ4egbbZv367WrVsf036B2labz9v6qtGGFEDtaIzP20b51h4AahIhBQAnQgoAToQUAJwIKQA4EVIAcCKkAOBESAHAiZACgBMhBQAnQgoAToQUAJwIKQA4EVIAcCKkAOBESAHAiZACgBMhBQAnQgoAToQUAJwIKQA4EVIAcCKkAOBESAHAiZACgBMhBQCniLqeQE0rKyvTJ598IkmKjo5W9+7dFR8fX+G269evV3R0tE488URlZ2dr+/btFW7XqlUr9ejRI7Df/9WxY0d17Nixxubv9fHHHys9PV3nn3+++vTpU+E233zzjZKSktSyZUuZmT788EOtW7dOgwcP1llnnVXhmB9//FGdOnWqcN2+ffuUk5OjM844Q5K0bds2vf/++0pISNDw4cMVHR0tSSosLNTq1auDxnbr1k3t27ev7uEC9YPVY9WZXmFhoUmyc845x84++2yLjIy0yy67zH788ceQbW+55RZ78MEHzcxs4sSJlpycbMnJyda2bVvr0KFD4PsJEyYE7ffI8uTkZJs1a5b3MGvMPffcY6effrr96U9/sujoaJs+fXrINiUlJdaxY0fbvn27mZm9+eabdvrpp9tdd91lPXr0sPvuuy9kzEcffWQdOnSo9HYnT55sd911l5mZLVmyxKKiouzqq6+20aNHW+fOnW3r1q1mZvbV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8cfrwgsvVFZWlr744guNGTNGkydPVuvWrXXfffdpzpw5Gjt2rN58801J0uOPP67bbrtN5557rlq1aqUxY8Zo586dkqS//e1vKi4u1j/+8Q8NGzZMBQUFIeMladasWerZs6datmypUaNGqaCgQJK0aNEiPfHEE7rtttvUsmVLDRw4UBkZGb/afVtXzEzTp0/XtddeG3Q/SVJKSooWLFigXr16qV27dnrooYc0ceJEdezYUb169QqK3KpVqzR48GDFx8dr0KBBWrVqlSTp+++/17Bhw4L2e/XVV+vTTz/V1q1bde211+rf//63OnfurJNPPlnz5s2TJGVkZGjmzJnKyMjQsGHD9Mwzz9TyPdGIWD1W1enV88MIUlhYaJLsiy++CFoeFxdnqampVlxcbKeeeqpNmjTJduzYYbNnz7bY2Fhbu3atffLJJybJ0tPTLSMjw9566y0zM3v++ectKSnJlixZYnv27LHU1FQrKiqyBQsWWNOmTe26666zgoICKygosI0bN9rYsWPtuuuuMzOz6667zvr162cbN2601atX28CBA23o0KFmZrZmzRqLioqyyZMnW1pamhUXF4eMnz9/vjVv3tzmz59v3333nV1wwQU2YMAAKy8vt9TUVIuOjraFCxdabm6uXXPNNda/f/9f7b72qu7javHixda9e3fbtm2bRUREWG5ubmBdp06dbPDgwZaVlWWTJk0ySXbnnXfajh077O6777YWLVrYgQMHbMuWLRYXF2cPP/ywbd261f785z/b8ccfb3l5efbVV1+FzK1Tp0727rvv2oYNG0ySTZw40Xbs2GFPPvmkRUdH286dO23Pnj12880321lnnWVpaWm2bt26Wrk/fkvPx5pSr4+4If7gKgtpQkKCTZ8+3V577TXr2bOnpaWlBb5GjBhhd955p61fv97Cw8Pt8ccft/Ly8sDYLl262CuvvBJyW0dCunv37qDld911V1BI77rrrsC6zMxMk2QbNmwwM7PjjjvO0tLSKh0/cuRIu/XWWwPrVq1aZZIsMzPTUlNT7ayzzgqsW7FihTVv3rzK91Vdq+7javTo0fbggw+amdn5559vEydODKw7ErwjmjdvHrh/8/PzTZJlZWXZ008/bb169QpsV1ZWZi1atLAXXnihSiHds2dPYFxERIR9+eWXZmb2xBNPWHJycrWOqyE+H2sKb+3rgS1b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", - }, - { - name: "Mobile-Tax-Invoice", - template_id: 1002, - ImageUri: - "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uVz84x//oH///le6G1cNXsHwippXbLwv7z0opQeJZ3jvffmJsCFSip/oqopa8mKO4rNX31j1VxG6IDc7F5fbdVHzl562QVVMKPkDVu9efiVfwItynasoZVBIfXE4HH6eXkREBADPP/88eXl5vPTSS6SmpnLy5ElsNhupqalMmzaN+vXr07p1a8CzxfLuu+8mOjqaFi1aGBn0wZOwY+jQocTFxVG3bl3effddANLT05k8eTLp6emkpqby/vvvB+yfEILPP/+cgQMHFskyr+s6zz33HBUrVqR9+/ZF0gFOnTqV2rVrU79+faZMmWJcX79+PX379mXcuHHEx8f7JWRu3bo1MTExdOnShf379wNw4sQJBg8eTEJCAu3bt2fFihW43W5effVV6tatS6NGjf42b7es4+sl+oqdr8iWJFq+guwtr2laEaFUFMUQWoEoMamJn8DnJ0fxJIzWiI6JwhoaWqRN38/F4XI5cLrseEXS+8vj+h28l0yZmiMtzEMPPcQ//vEP3G43Tz31lHEg3gMPPMAnn3zCgAEDaNSoEVFRUTidTpYvX05OTg5LlixB0zTsdjt33XUXgwcPZuLEiSxZsoR77rmHtWvX0qhRI4YMGYKu66xfv57jx4/zwAMPEBUVRb9+/bj11lvZvHkzI0eOLHb76dKlSwkNDeWdd96hatWqHDlyhCpVqgDw9ttvs2bNGhYtWkSlSpXo3bu3UW/VqlUMHz6cTz/9lPbt2/PCCy8YyUcyMzNZsGABoaGh7NixA/Bsmx08eDBjx46lbdu2jBkzhtTUVLZt28akSZPYsGEDmzdvZvPmzURFRbF+/XreeOMNNm/ejMlkuujEJpKC4bcRM1poDvViPFVFUVDxzJN65x29YVSKohgr/L6p/C5gEZfLTXa2jRCrmbDw0IIIAW88q9F20V1TiqIQHhFmeMTGc1zv4/cSKNMe6RtvvMGUKVOYN28eNWrU4JtvvgEwEm00atSIDh06+G3tfPnll6levTqJiYnMmjULXddp2rQpO3bsIDExkfbt2/PFF19w6NAhJk2axL333ssff/yBw+GgX79+TJo0iZiYGGrUqEF0dDQdOnSgXr16Afs3adIk+vXrR6VKlWjXrp2R/T4rK4u3336bzz77jJtvvpnq1av7HWj35ptvMnz4cHr27En58uXp2rVrEdsffPABsbGxxMbG8v77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bDYbo0aNon79+kRGRt620a4Qd607t6b0zSu8Qv7IkSOV2WxWH330kcrIyNC379q1S3l6eqrPPvtM/fzzzyo7O1ulp6crQLVp00YdPXpUJScnq+zsbNWkSRM1YcIEdfbsWTVz5kxVoUIFtWfPHqWUUgMGDFD9+/dXhw8fVhs2bFB16tRR//73v1VaWpoaOHCgioqKUj///LNKTEwstr/Lly9X4eHh6vTp08pkMqkTJ07o+8aMGaNatGihNm/erI4ePapatGihBgwYoJRSas2aNSooKEjNnz9fpaamqv79+6uwsDCllFJLlixRFotF9evXT507d06dO3dOJSYmqpCQEDV//nx17tw5NWzYMFW3bl2VmZmpxo4dq8LDw9XJkyfVokWL1NatW9W6desUoLZv3652796tZs+e7dRns9l86/7QhLgHuPWIdMyYMUybNo358+cTEhLCokWLAMeCzwaDgcjISGJiYvDw8NCPGTlyJLVr16ZGjRrMnj0bu91O8+bN2b9/PzVq1KBjx4588803HD9+nClTptC7d2+Sk5PJzc2lT58+TJkyBT8/P0JCQqhUqRIxMTE0aNCg2P5NmTKFPn36UK1aNTp06KAvr5eens7YsWP58ssveeCBB6hdu7bTikpxcXEMHz6cnj17Urly5WLfx584cSKBgYEEBgby6aef0rZtW/z9/UlISCA2Npbs7GxWrFhBUFAQJ06cIDExkccee4yoqCiCgoIwGAysWrWKyMhIevXqdSv/WIS457jVXfviPPfcczz77LO88847PP/886SkpODj41Ni+Tp16ujfHzhwgPPnz/PBBx84lalSpQq//vorJpOpyGV7WFhYqfp1/vx5FixYwPjx4wHHItOTJ09mxIgR7Nu3j6ysLCIiIoocp5Ri/fr1xa76n69q1apOC0EXdx4NGzbEYrHw4osv8ttvv9GzZ0969+7Nxx9/TFhYGN9++y0jRoxg2bJlfP3116U+LyFEUW4fSMGRi+j1119n3LhxHDp0iObNmwOOecjr8fPzo0aNGqxatarIvs2bN2O1Wpk1a1aRNU7zqeus9/L1119jtVqLLGm3efNmvL29Adi+fTvt2rUrci6enp7s2rWLbt26Xbf/Bc8jKiqKCRMmFLt/3LhxDBkyhCeffJIBAwYwb948nnnmGf70pz/x5ptv0q5dO06fPl1irichxPW59aX9hg0bSExM5Ny5c3zwwQdYLBZ9XdLatWuzc+fO6x7/yCOPsG/fPj01ic1m4+effwYc0wMNGzbkjTfe0JPlFUwMFxwcTGJiop5DviClFFOnTuXDDz/Uk9cppejcuTPfffcd4eHhhIaG8vHHH7Np0yaUUpw4cUI/vnv37vz3v/9l2bJl5OTkcPLkyeueR8+ePZkyZQrbtm0DIC0tjV27dgGOhZoPHTpEYGAg7dq148KFC1itVn766SfMZjOPPPIIqamp5OXlXbcNIcR13MH52ZtW+GZTnz59FKAAdd9996k5c+bo+2bNmqVMJpMC1Pvvv6/fbDpw4IBTnYsXL1YBAQEKUJUqVVJPPvmkvi8xMVE1btxYAcpkMqkOHTqoY8eOKaUc6Ylbt26tAGUwGFROTo5+3KpVqxSgjh8/7tRWft753NxctWHDBlWlShUFKF9fXxUZGanfbEpJSVFRUVH6ubVp08bpZlONGjWKfDbvvfeeXj40NFTFxcUppZSaO3eu8vX1VYCqWbOm2rhxozp06JCqWbOmApSXl5f66quv9HrkZpMQN8+t1iMtLtVIcnIyFy5cICIiosil6cmTJzl8+DDNmzenYsWKJdabnZ1NQkICTZs2LbJavd1uZ8+ePdSpU6fIosY2m42tW7fi4+NDZGTkTZ9PTk4Oe/fupXHjxvrlfuF2a9SoUeq3tdLS0jh+/DhNmzZ12p6VlUViYqKeLjq/7/v27SM0NNTpvFasWMGjjz5aYipqIURRbhdIn3rqKRo2bFgk1Yhw3fjx40lMTOS7776TQCrETXCrm013KtXIvWLjxo1kZWXx4IMP3umuCOFW3GpEKoQQ5ZFb37UXQojyQAKpEEK4SAKpEEK4SAKpEEK4SAKpEEK4yK0efyou1QjAjh072LJlC3a7nZYtWxIVFYXRaEQpxS+//EKLFi2oUKHCLe1LSkoKCxYswNPTk9atW9OwYcNbWv+tkpSURHJycpHtrVu3xtPTk6NHj5Kenk6TJk2uW8/GjRv1V2Q7duxIhw4dyqS/QrgjtwqkcXFx+gP5+WbOnEnfvn0JCwvDZDJx4MABNm3aROvWrTl79iydOnVi2bJldOvWjRUrVpCRkcGTTz7pUj+2bt1Kt27dqFWrFlWqVOHjjz/m119/BbjpNm5Vn0qyevVq5s6dq/+8f/9+AgIC2L17NwATJkxg165drFu3rlT1TZs2DZPJJIFUiILu4OupN63wu/YJCQnKZDKpadOm6dt+//13p2N27NihbDabUkqpHj16qNGjR7vcj759+6rnn39e/zk9PV3//mbbuFV9Ko309HRVpUoV9f333+vbzp8/r44cOVLqOqKjo9XYsWPLoHdCuC+3niMdO3YsDz30EC+++KK+LTQ0VP++W7duDB8+nNOnTzN9+nS2bt3KzJkz6dy5M6tXr2bq1KmMHDnSqc7Bgwc7jeCKY7Vai01xUlwbAJ9//jnNmzcnMDCQnj17cvDgweuWLynFiasmTZpE9erV6dOnDwDffvstffr00dOgLF26lOeee45nnnkGf39/OnbsqK8iJYQomVsH0j179tC5c+cS9w8aNIjVq1eTlZVFx44dCQsLo0OHDrzzzjs0adKEli1b8umnn3Lx4kXAsejH5MmTad269XXbfeGFF1iyZAn9+vUjJSVF315cGwAmk4lnnnmGSZMmceTIEV566aXrln/99de5cuUKmzZt4ptvvuHLL7/Ul/r7o65cucLEiROdMoRGR0cTGBioB/aUlBRWrlzJwIED2bNnD+Hh4XTt2pWzZ8+61LYQdzu3DqQnT56katWqJe5v27at/n1ISAj+/v7Url2bmJgYgoKCiIyMpHnz5vqIbPbs2bRr144aNWpct91u3bqxbt06rly5Qq1atXjvvfdQShXbBsBf/vIX/va3v9G0aVOefvpptm/fjt1uL7b89VKcuGLSpEmEhITwxBNP6Ntq167Nfffd51QuNDSUDh06UKtWLT7//HMsFgtLlixxqW0h7nZudbOpsOrVqzuNCP+Ifv36MX36dAYOHMjcuXP1y94bad26NfPmzWPp0qU88sgjdOjQocTFPkaOHMmUKVNo164dBoOB3NxcMjMz9SmBglxNcVKc/NFofs6o0jIajYSGhnL69Ok/3LYQ9wK3DqTNmjVj3rx5181vVFjh9CPPP/88f/vb31i7di0bNmxg9uzZN9WH7t27ExERwb59+/RAWrCNo0eP8sEHH7B3716aNGlCQkICc+bMKbFPfn5+N0xxcrMmTZpEgwYNSp26JF9GRgY7d+50moMWQhTl1pf2H3zwAYcOHaJfv34cOXKElJQU3n333RLnE4ODg9m9e7dT4PLx8eFPf/oTffv2pUePHvj7+7Nu3Tr69u3LhQsXiq3n119/ZfPmzWRnZ/P111+TkJBAvXr1SmwDIDMzE4CVK1cCjiBVXPkbpTg5deoUL7zwAtu3by/VZ5Sens7EiRPp3r078fHx+ldaWlqx5fNTjhw+fJjRo0ejlLrpACzEPedOPzZwMwo//qSUIx1Iu3bt9HQgPXr0UAkJCUoppdLS0hSgfvvtN6WUUgcOHFBBQUEKUA8//LBex+rVqxWg5s2bp5RS6scff1QWi0Vt3Lix2H5MmjRJT2Pi5eWl3n77bX1fcW0MHjxYmc1mFRkZqf7yl7+okJAQNX78+BLLXy/FydGjR1XNmjX142/kww8/1FOQFPxavXq13rd+/foppZSaMmWK8vLy0st4eXmpqVOnOtUnjz8JUZTbB9J8R48eVampqTes48qVKyo+Pl4lJyfr2xISElRAQIDKzc1VSilltVpV48aNVWZmZon1XL58WW3dulVlZGSUuo0TJ04opRzPbh46dOi65W02m9q5c6e6ePFikfr79Omj1q9ff8NzvVlTpkxRUVFRKiMjQ23ZsqXY85dAKkRRbjdHOmvWLPbu3Vsk1Uh+9tAb8fHxoWPHjvrPcXFxzJo1iyFDhuj5mt566y3i4uLw8vIqsZ4KFSoQFRVVqjYAGjdurH8fEBBAQEDAdcsbDAY9rXRBc+bMoW7dukXSON9K3t7etGrVymlb/iuiBbOdCiEc3CqQlkWqkZSUFMaPH++UUG/kyJFFEt2VF126dKFXr15lUne1atW4//77i9134sQJ1q5dS926dalZs2aZtC+Eu5JUI0II4SK3vmsvhBDlgQRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkds8/pSamsq+ffswm82EhoY6rVq0YMECvL296dq16x3soRDiXuU2I9LNmzfz0EMPMWrUKBo2bEjdunWZOnUqAIsWLdLfYRdCiNvNbUakAJ6envz0009YrVamTp3KgAEDqFOnzp3ulhDiHuc2I9KCTCYTf/7zn6latSoHDhwAHNkyY2JiqFy5Mn379uXy5csA5ObmMmzYMIKDg6lVqxZDhgzRV1UaN24c33//PbGxsfj7+9O7d2+nt6bi4+Np06YNfn5+xMbGcuTIEX1ft27d6N69+208ayFEeeWWgRRg+/btnD17Vk8LkpSUxMSJE4mPj2fPnj16nqO33nqLxYsXs3z5cr788kt++OEH3n77bQAOHjzI2LFjGTduHOvXrycpKUk/7uDBg/Tv359hw4Zx+PBhIiMj6dy5M1lZWQDYbDZsNtsdOHMhRHnjVoE0Ozubzp0706RJE2JiYhg/fry+sEeXLl1o3rw5kZGRPP300yQkJADw9ddf895779GkSRNiY2N55ZVXmDlzpl5nly5daNasGY0bN6Znz576CPfTTz+lbdu2+Pv7k5CQQGxsLNnZ2axYsQKAVatW6d8LIe5tbjVHajQa6dChA0FBQbRt25aIiIhiy3l6epKRkcHly5dJT093WmSjRYsWpKSk6Jf3BXl7e+sjzgMHDnD+/Hk++OADfX/Dhg2xWCy3+KyEEO7OrQKp2Wzm3XffLXX5ihUrUrFiRRISEvREeKdOnSI4OBiT6fqn7ufnR1RUFBMmTHCpz0KIu59bXdr/ET179mTOnDmkpqaSm5vLtGnTnDJpXu+4KVOmsG3bNsCRqrlgjvfRo0cTFxdXZv0WQriPuz6QxsXFcfHiRcLDw6lduzZZWVmMGjXqhse99NJLDB48mFatWqFpGi1atGDp0qX6/pUrV7Jq1aqy7LoQwk3cE+uR2u12EhMTMZlMhIWFoWlaqY9NS0vj+PHjNG3a1Gn7lStX0DQNHx+fW91dIYSbuScCqRBClKW7/tJeCCHKmgRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkQRSIYRwkdu8a5+faqSwZs2a4efnd0vaUErxyy+/0KJFCzw8PPjwww8ZOHAggYGBt6R+IcTdyW0eyP/xxx957LHHePDBB522f/zxxzRr1uyWtHHmzBmqVavGsmXLaN++PRUqVODAgQOEh4ffkvqFEHcntxmRgmN5vLJ8vz0oKIgdO3bQrFkzMjMzy6wdIcTd5a6YI126dCkTJkzglVdewd/fn+joaJYuXUpMTAyBgYH07duXjIwMANasWUNMTAyVKlUiOjqa5cuX6/V069aN4cOHc/r06WLbiYuLo06dOpw5c+a2nJcQwj24VSDNy8tjzJgx+ldubi4AKSkpjBgxgsaNG7Nu3Try8vIYMGAAn3zyCatWrWLt2rVMnjwZcKxp2qZNG7755huqVKlCr1699DxNgwYNYvXq1frizoUppUhPT8fLy+v2nLAQwi241aX99TRt2pTXX38dgF69erF8+XJatmwJwKOPPsrevXsBiI6OJjo6muTkZDw8PFiwYAEHDhygffv2+uLPJXn33XdvamFpIcS9wa0CaWlXyC+8TF7lypVJSUkBYOHChQwaNIh69erRuHFjLBYLly5dKpP+CiHuDW51aX8rDBs2jKFDh7JmzRomTZpE5cqV73SXhBBuzq1GpHa7nfj4eKdt+ZfvNyM7OxuALVu2cOHCBf1GVEG+vr40atSIPXv26I8/zZ8/nx9++IF//etfsqCzEELnVoE0OzubTp06OW0rmEepNMaOHcuzzz7Lt99+S6VKlejatSszZsygd+/eRcqOGTOGIUOG4OvryyOPPMK2bdtYsmQJH374oQRSIYTObR7Iv5VOnTpFeno69evXx2q1smfPnhJHtlarlYyMDCpVqoRSirNnzxIUFHSbeyyEKM/uyUAqhBC30j13s0kIIW41CaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEiCaRCCOEi053uwPVomnanuyCEuE3cOTN8uQ6kAElJSXe6C0LcFnXr1r1nf9/r1q17p7vgErm0F0IIF0kgFUIIF0kgFUIIF0kgFUIIF5X7m013s2PHjrF48WIAvL29qVu3Lu3bt8doNBbZX1B0dDRNmzblyJEjfPvtt6SkpNCwYUOeeuopqlevrpf77rvv2LFjB9nZ2TRq1IiuXbtSr149Fi9ezLFjx9A0jeDgYFq3bk21atX042bNmkVISAgPPPCA3gej0Uj16tWJiIigXr16AKxcuZJDhw4Ve24VKlSgX79+TnXlmzdvHhs3biQjI4O6devy4osvUqVKFf2c09LSaNasmVN9X3zxBbGxsdSuXfuPfNR3lc8//5y//vWvRbYvWrSIxx9/3GnbDz/8wGOPPea07Uaf/+LFixk4cKD+ewgwbdo07r//fpo2bcrixYvp0aOH01M1O3fuJCkpic6dO/Ptt9+W2PcHH3yQRo0a/aHzLs9kRHoHHTt2jIkTJ3L8+HGOHz/OuHHjeOGFFzh//rzT/i1btrB161b96+zZsyQnJ/P444+TnJxMeHg4SUlJpKWl6XWPGzeOuLg4NE2jatWqLFu2jOTkZAB+/PFHFixYwKVLl5g5cyYdO3ZkxowZ+rFz585l27ZtTn04efIkK1asoFevXrz44otcunSJpKQkvU+zZ89m5syZ+s979+4tUhfAjBkzePvttwGoWbMmS5cu5YknnuD48eN6e6+99prTuYAjkB47duxW/xG4nbNnz/KPf/xD/3zzZWVlMWzYMC5evKhvO3ToEOPHj3cqV5rPf+LEiVitVqfjpk+fzr59+wD473//yxdffOG0f9euXcybN4+cnBz9d2Djxo1MnDiR+Ph4fVv+7/bdRkak5cCYMWPw8PDAbrczdOhQPvjgAyZOnKjv//rrr/Hw8HA6ZubMmfj5+fGf//ynSH2bNm3i66+/Zvbs2bRo0aLYNqOjo/W/UF9++SUfffQRzz77LCZT8b8S7777Lh4eHpw7d44HH3yQb7/9lldffZWBAwcC8N5775GSklLkL1hBp06dYuzYsXz66af06NEDgFdffZU+ffowceJE/ZxPnz7N4MGDmTp1qjxLXMicOXMwmUwsXryYyMhIfXtiYiJWq5UVK1bw9NNPA/Dzzz9z5swZUlJSqFatWqk//9KYOHEiUVFRtGzZ0ml7UFCQ/o/yuXPnaN26NX//+99p0qSJq6dersmItBwxGAy8+OKL/PDDD/oIoSR+fn5cvnyZ9PT0IvumTp1Kt27dSgyihd13333Y7XbsdvsNy1apUoUGDRpw5syZUtVd0Pz586lWrRqPPPKIvs3Pz4+//OUvrFixQm/fw8OD8+fPM3Xq1Jtu426mlGLWrFm89tprua2ZTQAAIABJREFULFy40GnUePDgQcAx3ZLvl19+ASAhIQEo/edfGpGRkQwdOpTLly+7dE53Cwmk5UxYWBiAfhkF8PLLL9O3b1/69u2rX54//PDDREZG8uSTTzJlyhQuXLigl09KSiIiIuK67Zw9e5YtW7YQHx/PV199xeOPP47FYrnuMUop1q5dy65du6hTp85Nn9uxY8eoX79+kVFmSEgIOTk5nDlzBk3TMBqNfPLJJ/zzn/9kz549N93O3WrdunVYrVZeeeUVjEajHijBEUg7dOjA+vXrycjIIC0tje3btxMTE8P+/fuB0n3+paFpGl27dqVZs2YMHTr01p2gG5NL+3LGbDZjMBjw8vLSt7Vo0UK/5Pb29gbAy8uL6dOnM2fOHD7++GO++OILZs+eTWhoKOnp6VSsWPG67ezevZvt27eTmppKv379eOedd65b/uWXX+bw4cOcPXuWJk2a6JePNyMjIwMfH58i2z09PQHIzMzUtzVo0IA33niD//u//+PHH3+86bbuRnPmzOGxxx7DYDDw0EMPsWjRIh566CHAcWnfrVs38vLyWLFiBQaDgVatWtGuXTs2btwI3NznXxpjxoyhe/fuTvPr9yoZkZYzx44dw263O92dHjhwIK+99hqvvfYaAQEB+nZN0+jduzfLli2jcuXK/OMf/wCgRo0anDhxosQ28kcUW7dupVOnTmzevPmG/erRowevvvoq33//PQsWLHAK9KVVvXr1Ykc9+X297777UErp71y/+OKL1KpVixEjRqBp2j09X5qWlsaKFSv0y/KHH36Y1atXc/nyZex2O7/++ivh4eHExsayatUq4uPj6dKlCw0bNmT37t1A6T7/0sj/M/L19eXjjz/mo48+4uDBg/f0n48E0nLm66+/pmXLljf17nFgYCA9e/bk8OHDALRs2ZIff/wRm81WbPmCwSouLo4TJ07ccD7yiSee4Pnnn6dVq1Z/+C/M/fffz44dOzh37pzT9pUrV9KhQ4ciwVnTND755BM2bNjA5cuX3XpRC1fNmjULm83GE088Qd26dXn55ZfJycnhxx9/5OjRo1y5coXw8HC6devG2rVrWb9+PV27diUiIoK0tDROnDhRqs8/MDAQwOnuel5eHufPn9cfkSrogQceoG/fvsybN++e/vORQFoObNu2jZUrVzJo0CDWrl3L+++/X2T/li1b9K/U1FTOnz/Pl19+SWZmJtu3b2fRokU0b94cgL/+9a9kZWXx17/+laSkJNLT0/nuu++Ii4sr0na1atUYMmQIkyZN4vTp02V6nrGxsTRq1Ig333yTpKQkLl68yFdffcUPP/zAq6++WuwxVatWLbbf9xKlFHPmzGHQoEEkJSXpX8899xyLFy/mwIED1KpVi0qVKhEQEECrVq1o0KABQUFBVKhQgdq1a7Nv375Sff5hYWFUq1aNCRMm6P8Q//Of/8Tb29vpWeCC3nzzzRvOyd/tJJCWAy+99BL/+c9/CAkJYenSpYSHhzvt79evH88++6z+tXnzZo4fP86UKVP0+crKlSvzxhtvAODv78/8+fPJyMigS5cuNGvWjCVLlvDwww8X237fvn2pX79+mQcsg8HA9OnT8ff3158qWLBgAdOnTy/yGE1BsbGxPPXUU2Xat/Js48aNHD16lJ49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6tSpo4KCAu3atUtNmzYt7cOCmyGkAGCJ90gBwBIhBQBLhBQALBFSALBESAHAEiEFAEvuc0UsgNuCpUZujOtI4Ta+/vpr7d27VwEBAXr00UdVv379EvdfvHixfHx81L59+zt0hK6HpUZuUinNzA/cUmPGjDEeHh6mWbNmJigoyHh5eZmZM2c6x/ft22cmT55c5D4xMTHm1VdfvdOH6lJYauTm8B4pXN4PP/yg8ePHa8OGDYqLi1N6erpefvllvfDCCzpy5Igkafny5frqq69K+Uhdz7Jly9S1a1fn5CJPPvmktm7des0JmZOTk7V792716dNH0pUZ8CMiIhQXF1fimDsgpHB5H3zwgbp166aIiAjntvHjxyskJERz5szRzp079emnn2rnzp2Kjo7WpEmTnPulpKQoMjJS9957rwYMGKCff/7ZOTZ16lTVqVNH9913nwYPHuycOervf/+73n77bcXExCgwMFDLly+/cyd7h7HUyM0hpHB5u3btKrbssqenp5o3b64DBw7I4XDo8ccfV40aNTRy5Eh17tzZuV9KSor++te/asOGDdq1a5fee+89SdKcOXM0f/58zZ49W1u3blVaWpqGDBkiSTp06JDGjx+vJk2aKCkpSe3atbtzJ3uHsdTIzSGkcHmnT5/WPffcU2z7Pffco+zsbAUEBMjhcMjf31+RkZGqW7euc5927dqpcePGatiwoXr37q2EhARJV57Rdu7cWXl5eUpJSdGgQYP02WefOZfaaNiwoV566SUFBQWpXLlyd+ZES8GtWGokKCioxDF3wOVPcHnVqlXT8ePHi20/cuSIatWqddOPU758eeXk5OjixYtKTk7W0qVL9e9//9s5HhUVpdzcXElS7dq17Q/cBfy3S43UrFnTuX+PHj1KHHMHPCOFy2vRooVzqeWrTp06pW+++UYdO3Z0bjM3eaWft7e3fH199cc//lFr164t8ic4OPiWHvvdjqVGbg4hhct77bXXtH37do0dO1bnzp3T3r171bNnT/3ud79Thw4dJEnBwcFKSkoq8mHS9Xh4eKhHjx56++23lZmZKUlKTEy85gcs7o6lRm5SaV9/BdwKW7duNaGhoUaS8fb2Nn379jVnz551jufm5ppHH33USDIeHh7m0qVLxa4jnTRpkomKijLGGHP+/Hnz1FNPGUlGkmnSpIlZtmyZMcaY2NhY079//zt7gqUsJyfHHD58uNj29PR0U1hYWGx7UlKSyc/Pv+ZjlTTmqvhmE9xKUlKSQkJC5OvrW2zs8uXLiouLU8WKFdWwYcOberzU1FRJUo0aNW7pccK9EFIAsMR7pABgiZACgCWuI4VL+vU66nB9rvwuIyGFS3Llf3RwP7y0BwBLhBQALBFSALDEe6QAnPLz85WRkaGQkBDntt27d2vDhg2qX7++2rZte8MP+lizCXAx8fHxWrly5XXHX3zxRfn4+Gj8+PHavXu3KlSooIYNG2r48OHy9va+qfsHBARox44dysjIcH53X5IOHjyoefPm6c0335Snp+s/JykoKFDPnj3Vpk0bvfTSS5KksWPH6v3339czzzyjXbt2KSAgQMuWLZOHx7VfzLJmE+CCVq5caaKiokxUVJRp1KiR8fT0dN6OiooyJ06cMD169DAPPPCAGT58uBk4cKAJCAgwrVq1Mnl5eTd1f2OMGTp0qOnatWuRn7169WojybmekStbs2aNcy6CKVOmOLd/99135siRI8YYY1JTU40ks3Hjxus+zm91zSZCCrcxf/584+/vX2TbggULjI+Pjzl48KBz2/bt20358uXNxx9/fMP7G2NMdna2CQgIMD4+PubkyZPO7e4U0mHDhpn58+eb0NBQ87e//e2a+xw7dqzEkCYlJRlJRSaL6datm/nTn/5U4pg74MMmuLV//OMf6t69u3MyYUl65JFH1K9fPy1duvSmHmPBggVq1qyZWrZsqc8+++x2HWqpmjp1qnr37i3p+tfozpkzRw6HQ82bN7/mOGs2AW4qJSVFoaGhxbbXqlVLhw8fvqnHmDVrlnr16qUnn3zSbUN6I8eOHdN7772niRMnXncOUdZsAtzU2bNn5efnV2x7hQoVlJ2dfcP779mzR1u2bFH37t3VvXt3xcXFKTEx8XYc6l1tyJAheuyxx0pcGuS3vGYTIYVbczgcOnbsWLHtP/30kxwOxw3vP3PmTHXq1EmBgYG6//77FRYWpjlz5tyGI717zZ8/X5s2bdKMGTNK3O+X6zJdtW/fPrVo0aLEMXdASOHWWrZsWWzd+cLCQi1dulRPPPFEife9dOmSPvvsMy1ZskRlypRRmTJl9OOPP2ru3Lm6fPny7Tzsu0ZWVpZeeeUV9erVSwcOHNCGDRu0adMmSazZ9EuEFG7t9ddf1+HDhzVixAhlZWUpJSVFMTExunDhgp5//vkS77tgwQJdvHhR58+fl7lyhYtOnDih9PR0rV+/3rnfd999pw0bNjj/uMsHKJIUGxur9PR0zZgxQ23atFGbNm2cH0qxZtMvlPJVA8Atc73Ll/bs2WNatGjhXK8pOjrapKam3vD+kZGRpl+/fsX2a926tYmJiXFe/vTrP/Pnz7+l53U3Y82mK/hmE34zUlJSFBgYqHvvvbe0DwVuhpACgCXeIwUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwBIhBQBLhBQALBFSALBESAHAEiEFAEuEFAAsEVIAsERIAcASIQUAS4QUACwRUgCwREgBwNL/Bww6nVhM8tI9AAAAAElFTkSuQmCC", - }, - { - name: "Mobile-Multi-Invoice", - template_id: 1003, - ImageUri: - "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", - }, + // { // name: "Health-Log", // template_id: 5001, diff --git a/src/templates.ts b/src/templates.ts index 5da6cfd..d73fdc8 100644 --- a/src/templates.ts +++ b/src/templates.ts @@ -23,6 +23,7 @@ export interface CellMapping { export interface TemplateData { template: string; templateId: number; + category: string; msc: { numsheets: number; currentid: string; @@ -62,6 +63,7 @@ export let DATA: { [key: number]: TemplateData } = { 1001: { template: "Mobile-Invoice-1", templateId: 1001, + category: "Mobile", footers: [{ name: "Invoice", index: 1, isActive: true }], logoCell: { sheet1: "F5", @@ -72,18 +74,7 @@ export let DATA: { [key: number]: TemplateData } = { cellMappings: { sheet1: { Heading: "B2", - Items: { - Name: "Items", - Rows: { - start: 23, - end: 35, - }, - Columns: { - Description: "C", - Amount: "F", - }, - }, - + bidi: "B80", Date: "D20", InvoiceNumber: "C18", From: { @@ -100,6 +91,17 @@ export let DATA: { [key: number]: TemplateData } = { Phone: "C8", Email: "C9", }, + Items: { + Name: "Items", + Rows: { + start: 23, + end: 35, + }, + Columns: { + Description: "C", + Amount: "F", + }, + }, }, }, @@ -217,6 +219,7 @@ export let DATA: { [key: number]: TemplateData } = { 1002: { template: "Mobile-Tax-Invoice", templateId: 1002, + category: "Mobile", footers: [{ name: "Invoice", index: 1, isActive: true }], logoCell: { sheet1: "F7", @@ -399,6 +402,7 @@ export let DATA: { [key: number]: TemplateData } = { 1003: { template: "Mobile-Multi-Invoice", templateId: 1003, + category: "Mobile", footers: [ { name: "Detail1", index: 1, isActive: true }, { name: "Detail2", index: 2, isActive: false }, @@ -728,6 +732,7 @@ export let DATA: { [key: number]: TemplateData } = { 3001: { template: "Web-Invoice-1", templateId: 3001, + category: "Web", footers: [{ name: "Invoice", index: 1, isActive: true }], logoCell: { sheet1: "F4", @@ -835,6 +840,7 @@ export let DATA: { [key: number]: TemplateData } = { 3002: { template: "Web-Invoice-2", templateId: 3002, + category: "Web", footers: [{ name: "Invoice", index: 1, isActive: true }], logoCell: { sheet1: "F4", @@ -958,6 +964,7 @@ export let DATA: { [key: number]: TemplateData } = { 3003: { template: "Company-Invoice-1", templateId: 3003, + category: "Web", footers: [{ name: "Invoice", index: 1, isActive: true }], logoCell: { sheet1: "F4", @@ -1082,6 +1089,7 @@ export let DATA: { [key: number]: TemplateData } = { 3004: { template: "Company-Invoice-2", templateId: 3004, + category: "Web", footers: [{ name: "Invoice", index: 1, isActive: true }], logoCell: { sheet1: "F4",