From 26cd0293c488c3b684cb06de14568ae3d512c3cc Mon Sep 17 00:00:00 2001 From: livecodeali Date: Wed, 2 Dec 2015 14:35:08 +0000 Subject: [PATCH 1/7] [[ Tree View ]] Refactor the tree view's array to list conversion --- extensions/widgets/treeview/treeview.lcb | 68 ++++++++++++------------ 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/extensions/widgets/treeview/treeview.lcb b/extensions/widgets/treeview/treeview.lcb index deabc9c0c1a..f27b6df562a 100644 --- a/extensions/widgets/treeview/treeview.lcb +++ b/extensions/widgets/treeview/treeview.lcb @@ -1225,44 +1225,46 @@ private handler convertArrayToList(in pArray as Array, in pLevel as Integer, in end if variable tKey as String - variable tElement as Array - repeat for each element tKey in tKeys - put the empty array into tElement - put tKey into tElement["key"] - put pLevel into tElement["indent"] - put false into tElement["selected"] + push elementForList(tKey, pPath, pLevel, pArray) onto tList + end repeat + + return tList +end handler + +private handler elementForList(in pKey as String, pPath as List, in pLevel as Integer, in pArray as Array) returns Array + variable tElement as Array + put the empty array into tElement + put pKey into tElement["key"] + put pLevel into tElement["indent"] + put false into tElement["selected"] - variable tPath as List - put pPath into tPath - push tKey onto tPath + variable tPath as List + put pPath into tPath + push pKey onto tPath - put tPath into tElement["path"] - if pArray[tKey] is an array then - put false into tElement["leaf"] - put true into tElement["folded"] - push tElement onto tList + put tPath into tElement["path"] + if pArray[pKey] is an array then + put false into tElement["leaf"] + put true into tElement["folded"] + else + put true into tElement["leaf"] + put pArray[pKey] into tElement["value"] + variable tString as optional String + if pArray[pKey] is nothing then + put "" into tString + else if pArray[pKey] is a string then + put pArray[pKey] into tString + else if pArray[pKey] is a boolean then + format pArray[pKey] as string into tString + else if pArray[pKey] is a number then + format pArray[pKey] as string into tString else - put true into tElement["leaf"] - put pArray[tKey] into tElement["value"] - variable tString as optional String - if pArray[tKey] is nothing then - put "" into tString - else if pArray[tKey] is a string then - put pArray[tKey] into tString - else if pArray[tKey] is a boolean then - format pArray[tKey] as string into tString - else if pArray[tKey] is a number then - format pArray[tKey] as string into tString - else - put "Can't display value" into tString - end if - put tString into tElement["string_value"] - push tElement onto tList + put "Can't display value" into tString end if - end repeat - - return tList + put tString into tElement["string_value"] + end if + return tElement end handler // A handler type for passing into 'applyToNode' From a070de354d31cdf921ced88829a1aaefd0cb0d8a Mon Sep 17 00:00:00 2001 From: livecodeali Date: Wed, 2 Dec 2015 14:36:32 +0000 Subject: [PATCH 2/7] [[ Tree View ]] Implement handler to update the current data list on a given path --- extensions/widgets/treeview/treeview.lcb | 118 +++++++++++++++-------- 1 file changed, 80 insertions(+), 38 deletions(-) diff --git a/extensions/widgets/treeview/treeview.lcb b/extensions/widgets/treeview/treeview.lcb index f27b6df562a..8ef6c3d3513 100644 --- a/extensions/widgets/treeview/treeview.lcb +++ b/extensions/widgets/treeview/treeview.lcb @@ -1200,6 +1200,31 @@ private handler CompareKeysNumeric(in pLeft as any, in pRight as any) returns In end if end handler +private handler CompareKeys(in pLeft as String, in pRight as String) returns Integer + if mSortNumeric then + return CompareKeysNumeric(pLeft, pRight) + end if + + if pLeft is pRight then + return 0 + end if + + if pLeft < pRight then + if mSortAscending then + return -1 + else + return 1 + end if + end if + + if mSortAscending then + return 1 + else + return -1 + end if + +end handler + // Convert an array to a list, as used by this widget. Ignoring the 'folded' ans selected // parameters, mDataList should always be the result of calling this on mData private handler convertArrayToList(in pArray as Array, in pLevel as Integer, in pPath as List) returns List @@ -1555,53 +1580,70 @@ private handler setRowBackgrounds(in pShowAlternateBackgrounds as Boolean) retur end handler // Recursive implementation of setting data on a given path. -// Acts on a list (in the format used by this widget) and an array simultaneously. -private handler setDataListOnPath(in pPath as List, in pValue as any, in pLevel as Integer, inout xList as List, inout xArray as Array) returns nothing - +// Acts on the list in the format used by this widget. +private handler setValueOfDataListOnPath(in pPath as List, in pValue as any, in pLevel as Integer, in pStart as Integer, in pArray as Array, inout xList as List) returns nothing variable tElement as Array variable tCount as Integer - variable tNewElement as Array - put the empty array into tNewElement + + variable tPath as List + if pLevel is 0 then + put [] into tPath + else + put element 1 to pLevel of pPath into tPath + end if + + // If we're past the end of the list, we just need to add the current key + if pStart >= the number of elements in xList then + splice convertArrayToList(pArray, pLevel, tPath) after element -1 of xList + return + end if - repeat with tCount from 1 up to the number of elements in xList + // Go through the list looking for the key at the appropriate level + repeat with tCount from pStart + 1 up to the number of elements in xList put element tCount of xList into tElement + + // Skip any elements of the list that are children at the level we're looking + if tElement["indent"] > pLevel then + next repeat + end if - // The easy case - just insert the new element - if tElement["key"] > element pLevel + 1 of pPath then - - // update the backing array - setValueOnPath(element pLevel + 1 to -1 of pPath, pValue, tNewElement) - put tNewElement[element pLevel + 1 of pPath] into xArray[element pLevel + 1 of pPath] - - // update the list - splice convertArrayToList(tNewElement, pLevel, pPath) before element tCount of xList + // if the next indent is less that where we're looking, we know this key is + // new and can be added below the current one. + if tElement["indent"] < pLevel then + splice convertArrayToList(pArray, pLevel, tPath) before element tCount of xList return - - // We already have a key for the start of this path - else if tElement["key"] is element pLevel + 1 of pPath then - // The new element replaces the old one if the new one is just a string... - if the number of elements in pPath is pLevel + 1 then - put pValue into xArray[element pLevel + 1 of pPath] - put pValue into tElement["value"] - - // ...or if the old one was just as string. - else if tElement["leaf"] then - setValueOnPath(element pLevel + 2 to -1 of pPath, pValue, tNewElement) - put tNewElement into xArray[element pLevel + 1 of pPath] - splice convertArrayToList(tNewElement, pLevel, pPath) after element tCount of xList - - // Otherwise, go one step further into the path and set - else - setDataListOnPath(pPath, pValue, pLevel + 1, element tCount + 1 to -1 of xList, xArray[element pLevel + 1 of pPath]) - end if + end if + + // Otherwise the level is correct and we compare keys to find where to put the value + variable tComparison as Integer + put CompareKeys(element pLevel + 1 of pPath, tElement["key"]) into tComparison + + // If this is greater than the key at the current row, go to the next row + if tComparison is 1 then + next repeat + end if + + // If it is less, then add the key before the current row + if tComparison is -1 then + splice convertArrayToList(pArray, pLevel, tPath) before element tCount of xList return end if + + // If we get here, the key already exists and we're either replacing it or recursing + if tElement["leaf"] or tElement["folded"] then + // If the old element was a leaf or was folded then just replace it - we don't have to + // deal with potentially removing subelements + splice convertArrayToList(pArray, pLevel, tPath) into element tCount to tCount of xList + else if the number of elements in pPath is pLevel + 1 then + // We have a previously unfolded subelement, so remove its subelements and re-unfold + deleteSubElements(tCount, tElement["indent"], xList) + splice convertArrayToList(pArray, pLevel, tPath) into element tCount to tCount of xList + else + setValueOfDataListOnPath(pPath, pValue, pLevel + 1, tCount, pArray[element pLevel + 1 of pPath], xList) + end if + + exit repeat end repeat - - // If we reached the end of the list then append this to the end - setValueOnPath(element pLevel + 1 to -1 of pPath, pValue, tNewElement) - put tNewElement[element pLevel + 1 of pPath] into xArray[element pLevel + 1 of pPath] - splice convertArrayToList(tNewElement, pLevel, pPath) after element tCount of xList end handler -------------------------------------------------------------------------------- From 5c902f511dc41f0ea0302395918cf4be9bb6a198 Mon Sep 17 00:00:00 2001 From: livecodeali Date: Wed, 2 Dec 2015 14:42:04 +0000 Subject: [PATCH 3/7] [[ Tree View ]] Implement get/setArrayDataOfElement --- extensions/widgets/treeview/treeview.lcb | 53 ++++++++++++++---------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/extensions/widgets/treeview/treeview.lcb b/extensions/widgets/treeview/treeview.lcb index 8ef6c3d3513..73a49ec525a 100644 --- a/extensions/widgets/treeview/treeview.lcb +++ b/extensions/widgets/treeview/treeview.lcb @@ -1553,27 +1553,6 @@ private handler applyFoldState(in pLevel as Integer, in pStart as Integer, in pF end handler -// Given a 'path' of array keys as a list, and a value, adjust the mDataList and mData variables -// to reflect the action of putting pValue into mData[element 1 of pPath][element 2 of pPath]... -private handler setDataOnPath(in pPath as List) returns nothing - - // pPath having 1 element is an error, as that means mData is just a string rather than an array - if the number of elements in pPath is 1 then - return - end if - - // The last element of pPath is the actual value, the rest of it is the keys. - variable tActualPath as List - put element 1 to -2 of pPath into tActualPath - - variable tValue as String - put element -1 of pPath into tValue - - // Simultaneously adjust the backing list and array to reflect the added value. - setDataListOnPath(tActualPath, tValue, 0, mDataList, mData) - -end handler - private handler setRowBackgrounds(in pShowAlternateBackgrounds as Boolean) returns nothing put pShowAlternateBackgrounds into mAlternateRowBackgrounds redraw all @@ -1659,6 +1638,38 @@ private handler setArrayData(in pData as Array) returns nothing redraw all end handler +// Replace the existing data wholesale with a new array pData +public handler getArrayDataOfElement(in pPath as List) returns any + variable tValue + put mData into tValue + + variable tElement as String + repeat for each element tElement in pPath + put tValue[tElement] into tValue + end repeat + + return tValue +end handler + +// Given a 'path' of array keys as a list, and a value, adjust the mDataList and mData variables +// to reflect the action of putting pValue into mData[element 1 of pPath][element 2 of pPath]... +public handler setArrayDataOfElement(in pPath as List, in pValue as any) returns nothing + // Update the array + setValueOnPath(pPath, pValue, mData) + + // Update the display list + setValueOfDataListOnPath(pPath, pValue, 0, 1, mData, mDataList) + + // Call unfold on the changed path. This only results in a display change if + // all keys in pPath except the last are already unfolded. + unfoldPath(pPath) + + post "dataChanged" + + put true into mUpdateSeparator + redraw all +end handler + private handler setFrameBorder(in pFrameBorder as Boolean) returns nothing put pFrameBorder into mFrameBorder redraw all From e7f98dc0569445e7467c21558d6ca3c8439c5aa9 Mon Sep 17 00:00:00 2001 From: livecodeali Date: Wed, 2 Dec 2015 17:25:29 +0000 Subject: [PATCH 4/7] [[ Tree View ]] Move implementation of getArrayData to logical location --- extensions/widgets/treeview/treeview.lcb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/extensions/widgets/treeview/treeview.lcb b/extensions/widgets/treeview/treeview.lcb index 73a49ec525a..92a75683821 100644 --- a/extensions/widgets/treeview/treeview.lcb +++ b/extensions/widgets/treeview/treeview.lcb @@ -1151,11 +1151,6 @@ private handler setValueOnPath(in pPath as List, in pValue as any, inout xArray end if end handler -// Return the whole stored array -private handler getArrayData() returns Array - return mData -end handler - -- Sorts numerically, converting strings to numbers where possible private handler CompareKeysNumeric(in pLeft as any, in pRight as any) returns Integer variable tLeft as optional Number @@ -1631,6 +1626,11 @@ end handler -- -------------------------------------------------------------------------------- +// Return the whole stored array +private handler getArrayData() returns Array + return mData +end handler + // Replace the existing data wholesale with a new array pData private handler setArrayData(in pData as Array) returns nothing put pData into mData From 5d1f09f4bc3c1c726abf9e57164c30fb8a117983 Mon Sep 17 00:00:00 2001 From: livecodeali Date: Wed, 2 Dec 2015 17:26:45 +0000 Subject: [PATCH 5/7] [[ Tree View ]] Fix a couple of issues with adding new keys via property of chunk --- extensions/widgets/treeview/treeview.lcb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/extensions/widgets/treeview/treeview.lcb b/extensions/widgets/treeview/treeview.lcb index 92a75683821..36de88b76d8 100644 --- a/extensions/widgets/treeview/treeview.lcb +++ b/extensions/widgets/treeview/treeview.lcb @@ -378,6 +378,8 @@ public handler OnCreate() returns nothing put the empty array into mFoldState put the empty array into mData + put [] into mDataList + push the empty array onto mDataList end handler public handler OnSave(out rProperties as Array) @@ -1141,12 +1143,13 @@ end handler // the value of xArray[element 1 of pPath][element 2 of pPath]... is pValue // creating the keys if necessary private handler setValueOnPath(in pPath as List, in pValue as any, inout xArray as Array) - if the number of elements in pPath is 1 then + if (element 1 of pPath) is not among the keys of xArray then + put the empty array into xArray[element 1 of pPath] + end if + + if the number of elements in pPath is 1 then put pValue into xArray[element 1 of pPath] else - if (element 1 of pPath) is not among the keys of xArray then - put the empty array into xArray[element 1 of pPath] - end if setValueOnPath(element 2 to -1 of pPath, pValue, xArray[element 1 of pPath]) end if end handler @@ -1638,7 +1641,6 @@ private handler setArrayData(in pData as Array) returns nothing redraw all end handler -// Replace the existing data wholesale with a new array pData public handler getArrayDataOfElement(in pPath as List) returns any variable tValue put mData into tValue @@ -1660,12 +1662,10 @@ public handler setArrayDataOfElement(in pPath as List, in pValue as any) returns // Update the display list setValueOfDataListOnPath(pPath, pValue, 0, 1, mData, mDataList) - // Call unfold on the changed path. This only results in a display change if - // all keys in pPath except the last are already unfolded. - unfoldPath(pPath) - post "dataChanged" + // The data count may have changed. + put true into mRecalculate put true into mUpdateSeparator redraw all end handler From b3b3182bd2602840751ccf3c056025f52e2982c9 Mon Sep 17 00:00:00 2001 From: livecodeali Date: Thu, 3 Dec 2015 10:33:44 +0000 Subject: [PATCH 6/7] [[ Tree View ]] Add docs and release note --- .../notes/feature-array_data_element.md | 11 +++++ extensions/widgets/treeview/treeview.lcb | 46 +++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 extensions/widgets/treeview/notes/feature-array_data_element.md diff --git a/extensions/widgets/treeview/notes/feature-array_data_element.md b/extensions/widgets/treeview/notes/feature-array_data_element.md new file mode 100644 index 00000000000..27c248b7532 --- /dev/null +++ b/extensions/widgets/treeview/notes/feature-array_data_element.md @@ -0,0 +1,11 @@ +# Ability to access individual node data + +The following handlers have been added to the tree view widget: +- `SetArrayDataOfElement(, )` +- `GetArrayDataOfElement()` + +This allows the use of syntax such as +`the arrayData of element "a" of element "b" of widget "Tree View"` +in LiveCode Script, in order to access the data `tArray["b"]["a"]`, where tArray is the +Tree View's underlying array. This allows modification of a particular node of the Tree +View without causing a costly complete recalculation. \ No newline at end of file diff --git a/extensions/widgets/treeview/treeview.lcb b/extensions/widgets/treeview/treeview.lcb index 36de88b76d8..eddeaff9346 100644 --- a/extensions/widgets/treeview/treeview.lcb +++ b/extensions/widgets/treeview/treeview.lcb @@ -76,15 +76,51 @@ metadata svgicon is "M152.4,249.7c-6.4,0-11.8,4.3-13.5,10.1h-10v-26.7h10c1.7,5.8 -- property declarations /** -Syntax: set the arrayData of to -Syntax: get the arrayData of -Summary: The array being displayed by the widget +Syntax: set the arrayData of [element of [element of ...]] to +Syntax: get the arrayData of [element of [element of ...]] + +Summary: The data from the array being displayed by the widget Parameters: -pArray (array): The array data. +pValue: Any value to insert at the specified location in the array. +pIndex: Either a string, or a numerically indexed array, giving the list of keys for the target array element. + +Example: +local tArray +put "a" into tArray["x"]["y"] + +local tTreeView +create widget "Tree View" as "com.livecode.widget.treeView" +put it into tTreeView + +set the arrayData of tTreeView to tArray + +put the arrayData of element "y" of element "x" of tTreeView -- puts "a" + +Example: +local tArray +put "a" into tArray["x"]["y"]["z"] + +local tTreeView +create widget "Tree View" as "com.livecode.widget.treeView" +put it into tTreeView + +set the arrayData of tTreeView to tArray + +local tPath +put "x" into tPath[1] +put "y" into tPath[2] +put "z" into tPath[3] + +set the arrayData of element tPath of tTreeView to "b" + +put the arrayData of tTreeView into tArray +put tArray["x"]["y"]["z"] -- puts "b" + Description: -The arrayData is the data currently being displayed by the tree view widget. +The is the data currently being displayed by the tree view widget. Using the `of element` form allows +access to the individual nodes of the tree view via the path to its elements. **/ property arrayData get getArrayData set setArrayData From 73cdb1973566feaf426a032e727e091ead48eda1 Mon Sep 17 00:00:00 2001 From: livecodeali Date: Thu, 3 Dec 2015 11:38:13 +0000 Subject: [PATCH 7/7] [[ Tree View ]] Add tests and fix issue with get for non-existent key --- .../treeview/tests/element.livecodescript | 116 ++++++++++++++++++ extensions/widgets/treeview/treeview.lcb | 8 +- 2 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 extensions/widgets/treeview/tests/element.livecodescript diff --git a/extensions/widgets/treeview/tests/element.livecodescript b/extensions/widgets/treeview/tests/element.livecodescript new file mode 100644 index 00000000000..40ebc6c1ffb --- /dev/null +++ b/extensions/widgets/treeview/tests/element.livecodescript @@ -0,0 +1,116 @@ +script "TreeViewElementChunk" +/* +Copyright (C) 2015 LiveCode Ltd. + +This file is part of LiveCode. + +LiveCode is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License v3 as published by the Free +Software Foundation. + +LiveCode is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +for more details. + +You should have received a copy of the GNU General Public License +along with LiveCode. If not see . */ + +on TestSetup + TestLoadExtension "com.livecode.widget-utils" + TestLoadExtension "com.livecode.widget.treeView" +end TestSetup + +private function getTestArray + local tArray + put "a" into tArray[1] + put "b" into tArray[2][1] + put "c" into tArray[3][1][1] + return tArray +end getTestArray + +on TestSetElementEmpty + create widget "Tree View" as "com.livecode.widget.treeView" + set the arrayData of element 1 of widget "Tree View" to "a" + + get the arrayData of widget "Tree View" + + TestAssert "set element of previously empty tree view", it[1] is "a" +end TestSetElementEmpty + +on TestGetElementEmpty + create widget "Tree View" as "com.livecode.widget.treeView" + + TestDiagnostic "ArrayData is" && the arrayData of element 1 of widget "Tree View" + + TestAssert "get element of empty tree view", the arrayData of element 1 of widget "Tree View" is empty +end TestGetElementEmpty + +on TestGetElement + create widget "Tree View" as "com.livecode.widget.treeView" + + local tArray + put getTestArray() into tArray + set the arrayData of widget "Tree View" to tArray + + local tValue + put the arrayData of element 1 of widget "Tree View" into tValue + TestAssert "get element of tree view", tValue is tArray[1] + + put the arrayData of element 1 of element 2 of widget "Tree View" into tValue + TestAssert "get nested element of tree view", tValue is tArray[2][1] + + local tPath + put "3" into tPath[1] + put "1" into tPath[2] + put "1" into tPath[3] + + put the arrayData of element tPath of widget "Tree View" into tValue + TestAssert "get path element of tree view", tValue is tArray[3][1][1] + + local tPath1, tPath2 + put "1" into tPath1[1] + put "2" into tPath2[1] + put the arrayData of element tPath1 of element tPath2 of widget "Tree View" into tValue + TestAssert "get nested path element of tree view", tValue is tArray[2][1] + + put the arrayData of element tPath1 of element "2" of widget "Tree View" into tValue + TestAssert "get nested mixed element of tree view", tValue is tArray[2][1] +end TestGetElement + +on TestSetElement + create widget "Tree View" as "com.livecode.widget.treeView" + + local tArray + put getTestArray() into tArray + set the arrayData of widget "Tree View" to tArray + + set the arrayData of element 1 of widget "Tree View" to "value1" + put the arrayData of widget "Tree View" into tArray + TestAssert "set element of tree view", tArray[1] is "value1" + + set the arrayData of element 1 of element 2 of widget "Tree View" to "value2" + put the arrayData of widget "Tree View" into tArray + TestAssert "set nested element of tree view", tArray[2][1] is "value2" + + local tPath + put "3" into tPath[1] + put "1" into tPath[2] + put "1" into tPath[3] + + set the arrayData of element tPath of widget "Tree View" to "value3" + put the arrayData of widget "Tree View" into tArray + TestAssert "get path element of tree view", tArray[3][1][1] is "value3" + + local tPath1, tPath2 + put "1" into tPath1[1] + put "2" into tPath2[1] + set the arrayData of element tPath1 of element tPath2 of widget "Tree View" to "value4" + put the arrayData of widget "Tree View" into tArray + TestAssert "get nested path element of tree view", tArray[2][1] is "value4" + + set the arrayData of element tPath1 of element "2" of widget "Tree View" to "value5" + put the arrayData of widget "Tree View" into tArray + TestAssert "get nested mixed element of tree view", tArray[2][1] is "value5" + +end TestSetElement diff --git a/extensions/widgets/treeview/treeview.lcb b/extensions/widgets/treeview/treeview.lcb index eddeaff9346..a8fbac59de6 100644 --- a/extensions/widgets/treeview/treeview.lcb +++ b/extensions/widgets/treeview/treeview.lcb @@ -1677,13 +1677,17 @@ private handler setArrayData(in pData as Array) returns nothing redraw all end handler -public handler getArrayDataOfElement(in pPath as List) returns any +public handler getArrayDataOfElement(in pPath as List) returns optional any variable tValue put mData into tValue variable tElement as String repeat for each element tElement in pPath - put tValue[tElement] into tValue + if tValue is an array and tElement is among the keys of tValue then + put tValue[tElement] into tValue + else + return nothing + end if end repeat return tValue