/*
Copyright (C) 2015 Runtime Revolution 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 <http://www.gnu.org/licenses/>.  */

/*
This widget is a navigation bar.

Name: navigate
Type: message
Syntax: on navigate <pLabel>
Summary: Sent when a navigation item is selected

Parameters:
pLabel(string): The label of the selected navigation item

Example:
on navigate pLabel
	go card pLabel
end navigate

Description:
Handle the navigate message in the widget's object script to respond to a selection made by the user.
*/

-- declaring extension as widget, followed by identifier
widget com.livecode.widget.navbar
--

-- dependancy declarations
use com.livecode.canvas
use com.livecode.widget
use com.livecode.engine
use com.livecode.library.iconSVG
--

-- adding metadata to ensure the extension displays correctly in livecode
metadata author is "LiveCode"
metadata version is "1.0.2"
metadata title is "Navigation Bar"
metadata preferredSize is "320,49"
metadata svgicon is "M0,0v29.5h80.2V0H0z M21.1,21.5c-0.2-0.1-0.4-0.2-0.4-0.2c-0.5-0.2-0.9-0.3-1.4-0.4c-0.7-0.1-0.9-0.4-0.9-1.1c0-0.1-0.1-0.9,0-1c0.4-0.2,0.5-0.9,0.5-1.3c0-0.3,0.2-0.5,0.3-0.7c0.2-0.3,0.2-0.6,0.3-0.9c0.1-0.2,0.2-0.6,0.1-0.8c0-0.2-0.2-0.4-0.2-0.6c0-0.3,0.1-0.6,0.1-0.9c0-0.5,0-0.9,0-1.4c-0.2-1.2-1.4-1.6-2.5-1.9c-1-0.2-2.2,0.3-2.9,1c-0.3,0.3-0.6,0.7-0.7,1.1c-0.1,0.4,0,0.9,0,1.2c0,0.2,0,0.4,0.1,0.6c0,0.2,0.1,0.3,0.1,0.5c0,0.1-0.1,0.2-0.1,0.3c-0.2,0.4,0,1,0.2,1.4c0.1,0.2,0.2,0.4,0.3,0.5c0.2,0.2,0.2,0.3,0.2,0.6c0,0.4,0.1,1.2,0.6,1.4c0.1,0.1,0,0.8-0.1,0.9c0,0.7,0,1-0.6,1.1c-0.5,0.1-0.9,0.2-1.4,0.4c-0.2,0.1-0.4,0.2-0.7,0.3c-2-1.4-3.4-3.8-3.4-6.4c0-4.3,3.5-7.8,7.8-7.8s7.8,3.5,7.8,7.8C24.3,17.8,23,20.1,21.1,21.5z M45.1,22.7l-4.9-3.6v-0.7l0.2,0.2l3.6,2.6L42.6,17l-0.1-0.4l0.3-0.3l3.4-2.5H42h-0.4l-0.1-0.4l-1.4-4.2l-1.4,4.2l-0.1,0.4h-0.4h-4.3l3.4,2.5l0.3,0.3L37.5,17l-1.4,4.2l3.6-2.6l0.2-0.2v0.7L35,22.7l1.9-5.9l-4.9-3.6h6.1l1.9-5.9l1.9,5.9h6.1l-4.9,3.6L45.1,22.7z M71.1,23.1l-4.9-4.9c-1,0.9-2.4,1.4-3.9,1.5V19c3-0.1,5.4-2.5,5.4-5.5c0-3.1-2.5-5.5-5.5-5.5c-3.1,0-5.5,2.5-5.5,5.5c0,3,2.4,5.4,5.4,5.5v0.7c-3.4-0.1-6-2.8-6-6.2c0-3.4,2.8-6.2,6.2-6.2c3.4,0,6.2,2.8,6.2,6.2c0,1.5-0.6,2.9-1.5,4l4.9,4.9L71.1,23.1z"

--

/*
Syntax: set the navData of <widget> to <pNavArray>
Syntax: get the navData of <widget>

Summary:  The <navData> is all the collective data of the icons and labels of the navbar

Parameters:
pNavArray(array): An array containing all the navigation data:
{ key (integer): The index of the navigation item
	value (array): The array containing the data for the item at this index
	{ key : "label"
		value (string): The label of this item
		key : "icon_name"
		value (string) : The name of the icon to display when the item is not selected
		key : "selected_icon_name"
		value (string) : The name of the icon to display when the item is not selected
	}
}

Description:
The <navData> is a numerically keyed array, each element of which contains and array describing the label and icons of the
navigation item at that index. Any individual keys ("label", "icon_name", "selected_icon_name") missing when setting the <navData>
will be added, and set to their defaults - "circle" for the icons, and "New Item" for the label.

>*Note:* Setting the <navData> to a non-numerically keyed array will cause an error to be thrown

*/
property navData get getNavData set setNavData
metadata navData.editor is "com.livecode.pi.navbar"
metadata navData.label is "Navigation Data"

/*
Syntax: set the itemStyle of <widget> to <pItemStyle>
Syntax: get the itemStyle of <widget>

Summary: The display style of the widget

Parameters:
pItemStyle(enum): The display style
-"icons": display icons only
-"names": display names only
-"icons and names": display icons and names

Description:
Use the <itemStyle> property to control which elements of the navigation items are displayed.
*/
property itemStyle	get mItemStyle	set setItemStyle
metadata itemStyle.editor is "com.livecode.pi.enum"
metadata itemStyle.options is "icons,names,icons and names"
metadata itemStyle.default is "icons and names"
metadata itemStyle.label is "Display Style"

/*
Syntax: set the navHiliteColor of <widget> to <pColor>
Syntax: get the navHiliteColor of <widget>

Summary: The hilite color of the widget

Parameters:
pColor (color): The color of the navBar hilite

Description:
The color of the icon and text displayed when the navigation icon is selected.
*/
property navHiliteColor	get getHiliteColor set setHiliteColor
metadata navHiliteColor.editor is "com.livecode.pi.color"
metadata navHiliteColor.label is "Selected Item Color"
metadata navHiliteColor.default is "0,121,255"

/*
Syntax: set the selectedItem of <widget> to <pSelectedItem>
Syntax: get the selectedItem of <widget>

Summary: Manipulates the value of the selected navigation item

Parameters:
pSelectedItem(integer): The number of a nav item.

Description:
Selects a navigation item.
*/
property selectedItem	get mSelectedItem 	set setNavSelectedItem
metadata selectedItem.editor is "com.livecode.pi.integer"
metadata selectedItem.default is "1"
metadata selectedItem.label is "Selected Item Index"
metadata selectedItem.step is "1"
metadata selectedItem.min is "1"

/*
Syntax: set the editMode of <widget> to (true | false)
Syntax: get the editMode of <widget>

Summary: Whether the widget is in edit mode or not

Description:
This is currently an experimental feature of the navigation bar, and allows the icons to
be changed by clicking on the outlined regions, and new items to be added with the add button.
*/
property editMode get mEditMode set setEditMode
metadata editMode.user_visible is "false"

/*
Name: navNames
Type: property
Syntax: set the navNames of <widget> to <pNavNames>
Syntax: get the navNames of <widget>
Summary: Sets the list of names of navigation items

Parameters:
pNavNames(string): A comma delimited list of the navigation item names.

Description:
Sets the names of the navigation items of the widget. Adding an item to the navNames of a navbar widget causes a
new navigation item to be created with the default icon and label. Removing an item from the navNames causes the
corresponding item to be removed from the navigation items.
*/
property navNames		get getNavNames			set setNavNames
metadata navNames.user_visible is "false"

/*
Syntax: set the navIcons of <widget> to <pNavIcons>
Syntax: get the navIcons of <widget>
Summary: Sets the list of icons of navigation items

Parameters:
pNavIcons(string): A comma delimited list of the navigation item icons.

Description:
Sets the icons of the navigation items of the widget. Adding an item to the navIcons of a navbar widget causes a
new navigation item to be created with the default label and name.

The name of an icon must be one of the names returned by the iconNames() function of the com.livecode.library.iconSVG library.

>*Note:* Removing an item from the navIcons does not cause the corresponding item to be removed from the navigation items,
but merely resets it to the default icon.
*/
property navIcons		get getNavIcons			set setNavIcons
metadata navIcons.user_visible is "false"

/*
Syntax: set the navSelectedIcons of <widget> to <pNavSelectedIcons>
Syntax: get the navSelectedIcons of <widget>
Summary: Sets the list of icons of navigation items

Parameters:
pNavSelectedIcons(string): A comma delimited list of the navigation item selected icons.

Description:
Sets the selected icons of the navigation items of the widget. Adding an item to the <navSelectedIcons> of a navbar widget causes a
new navigation item to be created with the default label and name.

The name of an icon must be one of the names returned by the iconNames() function of the com.livecode.library.iconSVG library.

>*Note:* Removing an item from the navSelectedIcons does not cause the corresponding item to be removed from the navigation items,
but merely resets it to the default icon.
*/
property navSelectedIcons		get getNavSelectedIcons			set setNavSelectedIcons
metadata navSelectedIcons.user_visible is "false"

/*
Syntax: set the navLabels of <widget> to <pNavLabels>
Syntax: get the navLabels of <widget>
Summary: Sets the list of labels of navigation items

Parameters:
pNavLabels(string): A comma delimited list of the navigation item labels.

Description:
Sets the labels of the navigation items of the widget. Adding an item to the navLabels of a navbar widget causes a
new navigation item to be created with the default icon and name.

>*Note:* Removing an item from the navLabels does not cause the corresponding item to be removed from the navigation items,
but merely resets it to the default label.
*/
property navLabels		get getNavLabels		set setNavLabels
metadata navLabels.user_visible is "false"

/*
Syntax: get the desiredHeight of <widget>

Summary: The height that the widget ought to be displayed at

Example:
create widget as "com.livecode.widget.navbar"
set the height of it to the desiredHeight of it

Description:
Returns the height that the widget ought to be displayed at
*/
property desiredHeight	get getDesiredHeight
metadata desiredHeight.user_visible is "false"

/*
Syntax: set the <opaqueBackground> of <widget> to {true | false}
Syntax: get the <opaqueBackground> of <widget>

Summary: Whether the background of the navbar is drawn opaque or not

Description:
Use the <opaqueBackground> property to determine whether objects underneath the navbar are visible or not.

*/
property opaqueBackground get mOpaque set setOpaque
metadata opaqueBackground.default is "true"
metadata opaqueBackground.label is "Opaque Background"

/*
Syntax: set the <showDivide> of <widget> to {true | false}
Syntax: get the <showDivide> of <widget>

Summary: Whether the dividing line at the top of the navbar is drawn or not

Description:
Use the <showDivide> property to determine whether the dividing line at the top of the navbar is drawn or not.

*/
property showDivide get mShowDivide set setShowDivide
metadata showDivide.default is "true"
metadata showDivide.label is "Show Divide"

/*
Syntax: set the editMode of <widget> to (true | false)
Syntax: get the editMode of <widget>

Summary: Whether the widget is in edit mode or not

Description:
This is currently an experimental feature of the navigation bar, and allows the icons to
be changed by clicking on the outlined regions, and new items to be added with the add button.
*/

--

-- private instance variables
private variable mNavData as List

private variable mSelectedItem as Integer
private variable mItemStyle as String

private variable mNavItemCount as Integer
private variable mBoxWidth as Real

private variable mWidth as Real
private variable mHeight as Real

private variable mRecalculate as Boolean

private variable mEditMode as Boolean
private variable mAddIcon as Path
private variable mDeleteIcon as Path
private variable mReorderIcon as Path

private variable mDragData as Array

private variable mHiliteColor as Color

private variable mOpaque as Boolean
private variable mShowDivide as Boolean

--

constant kIconPaddingRatio is 0.1225
constant kLabelPaddingRatio is 0.06
constant kLabelHeightRatio is 0.165

constant kEditModePaddingUnit is 10
constant kEditModeLineHeight is 50

constant kIconOnlyPaddingRatio is 0.2

constant kDefaultNavName is "new item"
constant kDefaultNavLabel is "New Item"
constant kDefaultNavIconName is "circle"

public handler OnSave(out rProperties as Array)
	put the empty array into rProperties

	put getNavData() into rProperties["items"]
	put mSelectedItem into rProperties["selected"]
	put mItemStyle into rProperties["itemStyle"]
	put colorToString(mHiliteColor) into rProperties["hiliteColor"]

	return rProperties
end handler

public handler OnLoad(in pProperties as Array)
	setNavData(pProperties["items"])
	put pProperties["selected"] into mSelectedItem
	put pProperties["itemStyle"] into mItemStyle
	put stringToColor(pProperties["hiliteColor"]) into mHiliteColor
end handler

public handler OnCreate() returns nothing
	-- creating a list of navigation items

	variable tNavItems as List
	put the empty list into tNavItems

	variable tArray as Array
	put the empty array into tArray

	put "contacts" into tArray["name"]
	put "Contacts" into tArray["label"]
	put "user" into tArray["icon_name"]
	put "" into tArray["selected_icon_name"]
	put "" into tArray["icon"]
	put "" into tArray["selected_icon"]
	push tArray onto tNavItems

	put "favourites" into tArray["name"]
	put "Favourites" into tArray["label"]
	put "star empty" into tArray["icon_name"]
	put "" into tArray["selected_icon_name"]
	put "" into tArray["icon"]
	put "" into tArray["selected_icon"]
	push tArray onto tNavItems

	put "music" into tArray["name"]
	put "Music" into tArray["label"]
	put "music" into tArray["icon_name"]
	put "" into tArray["selected_icon_name"]
	put "" into tArray["icon"]
	put "" into tArray["selected_icon"]
	push tArray onto tNavItems

	put "search" into tArray["name"]
	put "Search" into tArray["label"]
	put "search" into tArray["icon_name"]
	put "" into tArray["selected_icon_name"]
	put "" into tArray["icon"]
	put "" into tArray["selected_icon"]
	push tArray onto tNavItems
	put the number of elements in tNavItems into mNavItemCount

	put tNavItems into mNavData

	put 1 into mSelectedItem
	put the empty array into mDragData
	put "icons and names" into mItemStyle

	put 0 into mBoxWidth

	initialiseEditData()

	put true into mRecalculate
	put false into mEditMode

	put path iconSVGPathFromName("plus") into mAddIcon
	put path iconSVGPathFromName("minus sign") into mDeleteIcon
	put path iconSVGPathFromName("reorder") into mReorderIcon

	put color [0, 121/255,1] into mHiliteColor

	put true into mOpaque
	put true into mShowDivide
end handler

public handler OnPaint() returns nothing

	if mRecalculate then
		updateParameters()
	end if

	if not mEditMode then
		drawNavBar()
	else
		drawNavBarEditMode()
	end if
end handler

private handler drawNavBar() returns nothing
	// background
	if mOpaque then
		set the paint of this canvas to getPaint("background","fill")
		fill getPath("background") on this canvas
	end if

	variable tX as Integer
	repeat with tX from 1 up to mNavItemCount
			-- Draw the text of the nav bar
			if mItemStyle contains "names" then
				drawLabel(mNavData[tX]["label"], mNavData[tX]["label_rect"], tX is mSelectedItem)
			end if

			if mItemStyle contains "icons" then
				-- Draw the icon
				if tX is mSelectedItem then
					drawIcon(mNavData[tX]["selected_icon_path"], true)
				else
					drawIcon(mNavData[tX]["icon_path"], false)
				end if

			end if
	end repeat

	-- Draw the top line
	if mShowDivide then
		set the paint of this canvas to getPaint("background","stroke")
		set the stroke width of this canvas to 0
		stroke getPath("line") on this canvas
	end if
end handler

private handler drawNavBarEditMode() returns nothing
	// Make sure the subsequent drawing operations are opaque
	set the opacity of this canvas to 1
	// background
	set the paint of this canvas to getPaint("background","editmode-fill")
	fill getPath("bounding box") on this canvas

	set the paint of this canvas to getPaint("background","editmode-stroke")
	set the stroke width of this canvas to 0
	stroke getPath("bounding box") on this canvas

	variable tX as Integer
	repeat with tX from 1 up to mNavItemCount
			-- Draw the text of the nav bar
			drawEditModeLabel(mNavData[tX]["label"], mNavData[tX]["label_rect"])
			-- Draw the icon
			drawIcon(mNavData[tX]["icon_path"], false)
			-- Draw the selectedIcon
			drawIcon(mNavData[tX]["selected_icon_path"], true)
			-- Draw the 'add item' item
			drawActionItems()
			-- Draw the rects round the editable elements
			--drawEditableRects(this canvas)
			-- Draw the line between items
			drawEditModeDivide(tX)
	end repeat

	if "item" is among the keys of mDragData then
		drawDragData()
	end if
end handler

private handler drawDragData()
	variable tTargetPath as Path
	if "target" is among the keys of mDragData then
		if mDragData["target"] is 0 then
			put line path from point [0, mDragData["target"] * kEditModeLineHeight + 2] to point [mWidth, mDragData["target"] * kEditModeLineHeight + 2] into tTargetPath
		else if mDragData["target"] is mNavItemCount then
			put line path from point [0, mDragData["target"] * kEditModeLineHeight - 2] to point [mWidth, mDragData["target"] * kEditModeLineHeight - 2] into tTargetPath
		else
			put line path from point [0, mDragData["target"] * kEditModeLineHeight] to point [mWidth, mDragData["target"] * kEditModeLineHeight] into tTargetPath
		end if
	end if
	set the paint of this canvas to getPaint("text", "selected-fill")
	set the stroke width of this canvas to 3
	stroke tTargetPath on this canvas

	set the opacity of this canvas to 0.4
	set the paint of this canvas to getPaint("background","drag-fill")
	fill rectangle path of mDragData["rect"] on this canvas
	-- Draw the text of the nav bar
	drawEditModeLabel(mDragData["label"], mDragData["label_rect"])
	-- Draw the icon
	drawIcon(mDragData["icon_path"], false)
	-- Draw the selectedIcon
	drawIcon(mDragData["selected_icon_path"], true)
end handler

private handler drawEditModeDivide(in pNumber as Number) returns nothing
	if pNumber is mNavItemCount then
		return
	end if
	variable tPath as Path
	put line path from point [1, kEditModeLineHeight * pNumber] to point [mWidth - 1, kEditModeLineHeight * pNumber + 1] into tPath
	set the paint of this canvas to getPaint("background", "stroke")
	set the stroke width of this canvas to 0
	stroke tPath on this canvas
end handler

private handler drawIcon(in pIconPath as Path, in pSelected as Boolean)
	if pSelected then
		set the paint of this canvas to getPaint("text","selected-fill")
	else
		set the paint of this canvas to getPaint("text","fill")
	end if
	fill pIconPath on this canvas
end handler

private handler drawLabel(in pLabel as String, in pRect as Rectangle, in pSelected as Boolean)
	if pSelected then
		set the paint of this canvas to getPaint("text","selected-fill")
	else
		set the paint of this canvas to getPaint("text","fill")
	end if

	set the font of this canvas to getFont("label")
	fill text pLabel at center of pRect on this canvas
end handler

private handler drawEditModeLabel(in pLabel as String, in pRect as Rectangle)
	set the paint of this canvas to getPaint("text","editmode-fill")
	set the font of this canvas to getFont("editmode-label")
	fill text pLabel at left of pRect on this canvas
end handler

private handler drawActionItems()
	variable tLineHeight as Real
	put mHeight / mNavItemCount into tLineHeight

	variable tX as Integer
	variable tRect as Rectangle
	variable tIconPath as Path

	repeat with tX from 1 up to mNavItemCount
		-- Draw the add item
		set the paint of this canvas to getPaint("text", "fill")
		put mNavData[tX]["add_icon_path"] into tIconPath
		fill tIconPath on this canvas

		-- Draw the delete item
		set the paint of this canvas to getPaint("background", "deleteitem")
		put mNavData[tX]["delete_icon_path"] into tIconPath
		fill tIconPath on this canvas

		-- Draw the reorder item
		set the paint of this canvas to getPaint("text", "fill")
		put mNavData[tX]["reorder_icon_path"] into tIconPath
		fill tIconPath on this canvas
	end repeat

end handler

public handler OnMouseUp() returns nothing
	variable tClickPos as Point
	put the click position into tClickPos
	if mEditMode then
		-- Dismiss the transient fields when the widget is clicked
		post "dismissTransient"
		widgetClicked(tClickPos, true)
	end if
end handler

public handler OnMouseDown() returns nothing
	variable tClickPos as Point
	put the click position into tClickPos
	if mEditMode then
		widgetClicked(tClickPos, false)
	else
		variable tSelected as Number
		put locToNavItem(tClickPos) into tSelected

		if tSelected > 0 then
			put tSelected into mSelectedItem
			redraw all

			variable tItemLabel as String
			put mNavData[mSelectedItem]["label"] into tItemLabel
			post "navigate" with [tItemLabel]
		end if
	end if
end handler

public handler OnMouseMove() returns nothing
		if "item" is among the keys of mDragData then
			calculateDragRow(the y of the mouse position)
			redraw all
		end if
end handler

private handler updateParameters()
	initialiseEditData()

	// update variables
	put my width into mWidth
	put my height into mHeight
	put the number of elements in mNavData into mNavItemCount
	put mWidth / mNavItemCount into mBoxWidth
	if mEditMode then
		calculateEditModeIconRects()
	else
		calculateIconRects()
	end if
	put false into mRecalculate
end handler

private handler calculateEditModeIconRects() returns nothing
	// calculate the drawing rects
	variable tX as Integer
	variable tIconPath as Path
	variable tIconRect as Rectangle
	variable tLineHeight as Real
	put kEditModeLineHeight into tLineHeight

	repeat with tX from 1 up to mNavItemCount
		variable tLabelLeft as Real
		variable tLabelRight as Real

		variable tLineTop as Real
		variable tLineBottom as Real
		put (tX - 1) * tLineHeight into tLineTop
		put tX * tLineHeight into tLineBottom

		-- One 1/2 line height icon + 2 lots of padding gives width of data to left of label
		put 2 * kEditModePaddingUnit + tLineHeight / 4 into tLabelLeft
		-- Two 1/4 line height icons + two line height icons + 4 lots of padding gives width of data to right of label
		put mWidth - (4 * kEditModePaddingUnit + 5 * tLineHeight / 2) into tLabelRight

		put rectangle [tLabelLeft, tLineTop + kEditModePaddingUnit, tLabelRight, tLineBottom - kEditModePaddingUnit] into mNavData[tX]["label_rect"]
		-- Allow label to be edited inline
		setEditableTextRect("label", tX, mNavData[tX]["label_rect"])

		if mNavData[tX]["icon_name"] is not empty then
			put iconSVGPathFromName(mNavData[tX]["icon_name"]) into mNavData[tX]["icon"]
		end if
		put path mNavData[tX]["icon"] into tIconPath

		variable tLeft as Real
		put tLabelRight + kEditModePaddingUnit into tLeft

		put rectangle [tLeft + kEditModePaddingUnit, tLineTop + kEditModePaddingUnit, tLeft + tLineHeight - kEditModePaddingUnit, tLineBottom - kEditModePaddingUnit] into tIconRect
		setIconPath(tIconRect, tIconPath)
		put tIconPath into mNavData[tX]["icon_path"]
		put tIconRect into mNavData[tX]["icon_rect"]

		if mNavData[tX]["selected_icon_name"] is not empty then
			put iconSVGPathFromName(mNavData[tX]["selected_icon_name"]) into mNavData[tX]["selected_icon"]
		end if

		if mNavData[tX]["selected_icon"] is not "" then
			put path mNavData[tX]["selected_icon"] into tIconPath
		end if

		add tLineHeight to tLeft
		put rectangle [tLeft + kEditModePaddingUnit, tLineTop + kEditModePaddingUnit, tLeft + tLineHeight - kEditModePaddingUnit, tLineBottom - kEditModePaddingUnit] into tIconRect
		setIconPath(tIconRect, tIconPath)
		put tIconPath into mNavData[tX]["selected_icon_path"]
		put tIconRect into mNavData[tX]["selected_icon_rect"]

		setEditableRect("icon", tX, mNavData[tX]["icon_rect"], "com.livecode.widget.iconPicker", iconSelected)
		setEditableRect("selectedIcon", tX, mNavData[tX]["selected_icon_rect"], "com.livecode.widget.iconPicker", iconSelected)

		variable tActionFraction
		put 1/4 into tActionFraction

		put mAddIcon into tIconPath
		add tLineHeight + kEditModePaddingUnit to tLeft
		put rectangle [tLeft, tLineTop + tLineHeight * (1 - tActionFraction) / 2, tLeft + tLineHeight * tActionFraction, tLineBottom - tLineHeight * (1 - tActionFraction) / 2] into tIconRect
		setIconPath(tIconRect, tIconPath)
		put tIconPath into mNavData[tX]["add_icon_path"]
		setActionRect("add_icon", tX, tIconRect, addItem, true)

		put mReorderIcon into tIconPath
		add tLineHeight * tActionFraction + kEditModePaddingUnit to tLeft
		put rectangle [tLeft, tLineTop + tLineHeight * (1 - tActionFraction) / 2, tLeft + tLineHeight * tActionFraction, tLineBottom - tLineHeight * (1 - tActionFraction) / 2] into tIconRect
		setIconPath(tIconRect, tIconPath)
		put tIconPath into mNavData[tX]["reorder_icon_path"]
		setActionRect("reorder_icon", tX, tIconRect, reorderItem, false)

		put mDeleteIcon into tIconPath
		put rectangle [kEditModePaddingUnit, tLineTop + tLineHeight * (1 - tActionFraction) / 2, kEditModePaddingUnit + tLineHeight * tActionFraction, tLineBottom - tLineHeight * (1 - tActionFraction) / 2] into tIconRect
		setIconPath(tIconRect, tIconPath)
		put tIconPath into mNavData[tX]["delete_icon_path"]
		setActionRect("delete_icon", tX, tIconRect, deleteItem, true)
	end repeat
end handler

private handler calculateDragRow(in pY as Real) returns nothing
	variable tLineHeight as Real
	put kEditModeLineHeight into tLineHeight

	-- calculate the drag destination
	put the floor of ((pY + tLineHeight / 2) / tLineHeight) into mDragData["target"]

	variable tLabelLeft as Real
	variable tLabelRight as Real

	variable tLineTop as Real
	variable tLineBottom as Real
	put pY - mDragData["offset"] into tLineTop
	put tLineTop + tLineHeight  into tLineBottom

	-- calculate the drawing rects
	put rectangle [0, tLineTop, mWidth, tLineBottom] into mDragData["rect"]

	-- One 1/2 line height icon + 2 lots of padding gives width of data to left of label
	put 2 * kEditModePaddingUnit + tLineHeight / 4 into tLabelLeft
	-- Two 1/4 line height icons + two line height icons + 4 lots of padding gives width of data to right of label
	put mWidth - (4 * kEditModePaddingUnit + 5 * tLineHeight / 2) into tLabelRight

	put rectangle [tLabelLeft, tLineTop + kEditModePaddingUnit, tLabelRight, tLineBottom - kEditModePaddingUnit] into mDragData["label_rect"]

	variable tIconPath as Path
	variable tIconRect as Rectangle
	put path mNavData[mDragData["item"]]["icon"] into tIconPath

		variable tLeft as Real
		put tLabelRight + kEditModePaddingUnit into tLeft

		put rectangle [tLeft + kEditModePaddingUnit, tLineTop + kEditModePaddingUnit, tLeft + tLineHeight - kEditModePaddingUnit, tLineBottom - kEditModePaddingUnit] into tIconRect
		setIconPath(tIconRect, tIconPath)
		put tIconPath into mDragData["icon_path"]

		if mNavData[mDragData["item"]]["selected_icon"] is not "" then
			put path mNavData[mDragData["item"]]["icon"] into tIconPath
		end if

		add tLineHeight to tLeft
		put rectangle [tLeft + kEditModePaddingUnit, tLineTop + kEditModePaddingUnit, tLeft + tLineHeight - kEditModePaddingUnit, tLineBottom - kEditModePaddingUnit] into tIconRect
		setIconPath(tIconRect, tIconPath)
		put tIconPath into mDragData["selected_icon_path"]
end handler

private handler calculateIconRects() returns nothing
	// calculate the drawing rects
	variable tX as Integer
	variable tIconPath as Path
	variable tIconRect as Rectangle

	repeat with tX from 1 up to mNavItemCount
		if mItemStyle is "names" then
			-- If we only show the names then center the label rect vertically
			put rectangle [((2 * tX - 1) * mBoxWidth - mHeight) / 2, 0, ((2 * tX - 1) * mBoxWidth + mHeight) / 2, mHeight] into mNavData[tX]["label_rect"]
		else if mItemStyle is "icons and names" then
			-- If we show icons and names then offset the label so its rect is below the icon
			put rectangle [(tX - 1) * mBoxWidth, mHeight * (1 - (kLabelPaddingRatio + kLabelHeightRatio)), tX * mBoxWidth, mHeight * (1 - kLabelPaddingRatio)] into mNavData[tX]["label_rect"]
		end if

		if mItemStyle contains "icons" then
			-- Ensure the icon name has been resolved into a path
			if mNavData[tX]["icon_name"] is not "" then
				put iconSVGPathFromName(mNavData[tX]["icon_name"]) into mNavData[tX]["icon"]
			end if
			put path mNavData[tX]["icon"] into tIconPath

			if mItemStyle is "icons and names" then
				put rectangle [((2 * tX - 1) * mBoxWidth - mHeight * (1 - kIconPaddingRatio)) / 2, mHeight * kIconPaddingRatio, ((2 * tX - 1) * mBoxWidth + mHeight * (1 - kIconPaddingRatio)) / 2, mHeight * (1 - (kIconPaddingRatio + kLabelHeightRatio + kLabelPaddingRatio))] into tIconRect
			else if mItemStyle is "icons" then
				variable tIconSpacing
				put (mWidth - 2 * mHeight) / (mNavItemCount - 1) into tIconSpacing
				put rectangle [(1- kIconOnlyPaddingRatio) * mHeight + (tX - 1) * tIconSpacing, kIconOnlyPaddingRatio * mHeight, (1+ kIconOnlyPaddingRatio) * mHeight + (tX - 1) * tIconSpacing, (1 - kIconOnlyPaddingRatio) * mHeight] into tIconRect
			end if

			setIconPath(tIconRect, tIconPath)
			put tIconPath into mNavData[tX]["icon_path"]
			put tIconRect into mNavData[tX]["icon_rect"]

			-- Ensure the selected icon name has been resolved into a path
			if mNavData[tX]["selected_icon_name"] is not empty then
				put iconSVGPathFromName(mNavData[tX]["selected_icon_name"]) into mNavData[tX]["selected_icon"]
			end if

			-- If there is no selected icon then just use the non-selected icon
			if mNavData[tX]["selected_icon"] is not "" then
				put path mNavData[tX]["selected_icon"] into tIconPath
			end if

			setIconPath(tIconRect, tIconPath)
			put tIconPath into mNavData[tX]["selected_icon_path"]
			put tIconRect into mNavData[tX]["selected_icon_rect"]
		end if
	end repeat
end handler


public handler OnGeometryChanged()
	put true into mRecalculate
end handler

--Callback when an icon is selected from the icon picker widget
public handler iconSelected(in pSelectedIcon as optional any, in pType as String, in pNumber as Number)
	if pSelectedIcon is not nothing then
		if pType is "selectedIcon" then
			put pSelectedIcon into mNavData[pNumber]["selected_icon_name"]
			put "" into mNavData[pNumber]["selected_icon"]
		else
			put pSelectedIcon into mNavData[pNumber]["icon_name"]
			put "" into mNavData[pNumber]["icon"]
		end if

		-- If the selected icon is the same as the icon, just put empty into the selected icon name
		-- that way, when the icon is changed, the selected icon will change too.
		if mNavData[pNumber]["selected_icon_name"] is mNavData[pNumber]["icon_name"] then
			put "" into mNavData[pNumber]["selected_icon_name"]
			put "" into mNavData[pNumber]["selected_icon"]
		end if
		post "navDataChanged"
		put true into mRecalculate
		redraw all
	end if
end handler

private handler clickPosToComponent(in pClickPos as Point) returns Integer
	variable tX as Integer
	repeat with tX from 1 up to mNavItemCount
		variable tIconRect as Rectangle
    	put mNavData[tX]["icon_rect"] into tIconRect
		if pClickPos is within tIconRect then
			return tX
		end if
	end repeat

	return 0
end handler

private handler locToNavItem(in pLoc as Point) returns Integer
	return the floor of (the x of pLoc / mBoxWidth) + 1
end handler

private handler yPosToLine(in pY as Number) returns Integer
	variable tLineHeight as Real
	put mHeight / mNavItemCount into tLineHeight
	return the floor of (pY / tLineHeight) + 1
end handler

--Translate and scale xPath so that it fits within pTargetRect
private handler setIconPath(in pTargetRect as Rectangle, inout xPath as Path)

	// Scale the icon
	variable tBounds
	put the bounding box of xPath into tBounds

	// Scale appropriately
	variable tXScale as Real
	variable tYScale as Real

	put the width of pTargetRect / the width of tBounds into tXScale
	put the height of pTargetRect / the height of tBounds into tYScale

	if tXScale > tYScale then
		put tYScale into tXScale
	else
		put tXScale into tYScale
	end if

    scale xPath by [tXScale, tYScale]

	variable tXTranslate as Real
	variable tYTranslate as Real

    put the bounding box of xPath into tBounds

	put the left of pTargetRect - the left of tBounds into tXTranslate
	put the top of pTargetRect - the top of tBounds into tYTranslate

    variable tXDiff as Real
    variable tYDiff as Real

    put the width of pTargetRect - the width of tBounds into tXDiff
    put the height of pTargetRect - the height of tBounds into tYDiff

	// align center
	divide tXDiff by 2
	divide tYDiff by 2

	translate xPath by [tXTranslate + tXDiff, tYTranslate + tYDiff]
end handler

public handler setHiliteColor(in pColor as String) returns nothing
	put stringToColor(pColor) into mHiliteColor
	redraw all
end handler

public handler getHiliteColor() returns String
	return colorToString(mHiliteColor)
end handler

public handler getPath(in pObject as String) returns Path
	variable tRect
	put my bounds into tRect
	if pObject is "bounding box" then
		return rectangle path of rectangle [the left of tRect + 0.5, the top of tRect + 0.5, the right of tRect - 0.5, the bottom of tRect - 0.5]
	else if pObject is "line" then
		return line path from point [0, 0.5] to point [my width, 0.5]
		-- Offset background by 1 from the top so that line is allowed to be transparent
	else if pObject is "background" then
		return rectangle path of rectangle [the left of tRect + 0.5, the top of tRect + 1, the right of tRect - 0.5, the bottom of tRect - 0.5]
	end if
end handler

public handler getPaint(pLocation, pType) returns Paint
	if pLocation is "background" then
		if pType is "fill" then
			return solid paint with color [246/255, 246/255, 247/255]
		else if pType is "drag-fill" then
			return solid paint with color [200/255, 200/255, 200/255]
		else if pType is "stroke" then
			return solid paint with color [0, 0, 0, 0.2]
		else if pType is "editmode-stroke" then
			return solid paint with color [193/255, 193/255, 193/255]
		else if pType is "additem" then
			return solid paint with color [0, 122/255 ,1]
		else if pType is "deleteitem" then
			return solid paint with color [240/255, 0 ,0]
		else if pType is "editmode-fill" then
			return solid paint with color [1, 1, 1]
		end if
	else if pLocation is "text" then
		if pType is "fill" then
			return solid paint with color [146/255, 146/255, 146/255]
		else if pType is "selected-fill" then
			return solid paint with mHiliteColor
		else if pType is "additem" or pType is "deleteitem" then
			return solid paint with color [1, 1, 1]
		else if pType is "editmode-fill" then
			return solid paint with color [0, 0, 0]
		end if
	end if

	return solid paint with color [1, 100/255, 200/255]
end handler

private handler getFont(in pType as String) returns Font
	variable tFont as Font
	put the font of this canvas into tFont

	if pType is "label" then
		set the size of tFont to 10
	else if pType is "editmode-label" then
		set the size of tFont to 12
	end if

	return tFont
end handler

public handler setNavSelectedItem(in pSelectedItem as Integer) returns nothing
	if pSelectedItem > 0 and pSelectedItem <= the number of elements in mNavData then
		put pSelectedItem into mSelectedItem
		post "navigate" with [mNavData[mSelectedItem]["label"]]
		redraw all
	end if
end handler

public handler setItemStyle(in pItemStyle as String) returns nothing
	put pItemStyle into mItemStyle
	put true into mRecalculate
	redraw all
end handler

public handler setEditMode(in pEditMode as Boolean) returns nothing
	put pEditMode into mEditMode

	put true into mRecalculate
	redraw all
end handler

--------------------------------------------------------------------------------
--
--		Data utilities
--
--------------------------------------------------------------------------------

private handler listToArray(in pList as List) returns Array
	variable tCount
	variable tArray
	put the empty array into tArray

	variable tKey as String
	repeat with tCount from 1 up to the number of elements in pList
		put tCount formatted as string into tKey
		put defaultNavArray() into tArray[tKey]

		variable tAttribute as String
		repeat for each key tAttribute in tArray[tKey]
			put pList[tCount][tAttribute] into tArray[tKey][tAttribute]
		end repeat
	end repeat
	return tArray
end handler

private handler setData(in pArray as Array, in pKeys as List, out rList as List)
	-- Excessive hoop-jumping to work around the fact that keys of numerically keyed LCS array
	-- comes into LCB as strings, and therefore must be parsed as number to sort, then formatted back
	-- to be used as keys again. Because parse/format of integer doesn't currently roundtrip, we need to
	-- create a lookup from the original strings.
	variable tKey
	variable tOrder as List
	put the empty list into tOrder
	variable tLookup as Array
	put the empty array into tLookup
	variable tNumericKey as Number
	repeat for each key tKey in pArray
		put tKey parsed as number into tNumericKey
		push tNumericKey onto tOrder
		put tKey into tLookup[tNumericKey formatted as string]
	end repeat
	sort tOrder in ascending numeric order

	variable tOrderKey
	variable tElement as Array
	variable tList as List
	put the empty list into tList

	variable tOriginalKey as String
	repeat for each element tOrderKey in tOrder
		put the empty array into tElement
		repeat for each element tKey in pKeys
			put tLookup[tOrderKey formatted as string] into tOriginalKey
			-- Use default item if key is not present
			if tKey is among the keys of pArray[tOriginalKey] then
				put pArray[tOriginalKey][tKey] into tElement[tKey]
			else
				put defaultNavElement(tKey) into tElement[tKey]
			end if
		end repeat
		if tElement["label"] is "" then
			put "New Item" into tElement["label"]
		end if
		-- If the selected icon is the same as the icon, just put empty into the selected icon name
		-- that way, when the icon is changed, the selected icon will change too.
		if tElement["selected_icon_name"] is tElement["icon_name"] then
			put "" into tElement["selected_icon_name"]
		end if
		push tElement onto tList
	end repeat

	put tList into rList
end handler

private handler getDataElement(in pElementName as String, in pList as List) returns String
	variable tList
	put the empty list into tList

	variable tElement
	repeat for each element tElement in pList
		push tElement[pElementName] onto tList
	end repeat

	variable tElements
	combine tList with "," into tElements
	return tElements
end handler

private handler setDataElement(in pElementName as String, in pElements as String, in pDefaultArray as Array, inout xList as List)
	variable tElements
	split pElements by "," into tElements

	variable tCount
	put 0 into tCount

	variable tElement
	repeat for each element tElement in xList
		add 1 to tCount
		if tCount > the number of elements in tElements then
			put pDefaultArray[pElementName] into xList[tCount][pElementName]
		else
			put tElements[tCount] into xList[tCount][pElementName]
		end if
	end repeat

	// If we there are more of the given element than there are items, generate more
	add 1 to tCount
	repeat with tCount from tCount up to the number of elements in tElements
		push pDefaultArray onto xList
		put tElements[tCount] into xList[tCount][pElementName]
	end repeat

	put true into mRecalculate
    post "navDataChanged"
	redraw all
end handler

// Only the primary data element ("name" for both actions and nav) can *reduce* the number of elements in the data list.
private handler setPrimaryDataElement(in pElementName as String, in pElements as String, in pDefaultArray as Array, inout xList as List)
	variable tElements
	split pElements by "," into tElements

	variable tCount
	put 0 into tCount

	variable tElement
	repeat for each element tElement in tElements
		add 1 to tCount
		if tCount > the number of elements in xList then
			push pDefaultArray onto xList
		end if

		put tElement into xList[tCount][pElementName]
	end repeat

	// If we there are more items than there are primary elements, pop them off.
	add 1 to tCount
	repeat with tCount from tCount up to the number of elements in xList
		pop xList
	end repeat

	put true into mRecalculate
    post "navDataChanged"
	redraw all
end handler

--------------------------------------------------------------------------------
--
--		Nav data handling
--
--------------------------------------------------------------------------------

private handler defaultNavElement(in pKey as String) returns String
	if pKey is "name" then
		return kDefaultNavName
	else if pKey is "label" then
		return kDefaultNavLabel
	else if pKey is "icon_name" then
		return kDefaultNavIconName
	else if pKey is "selected_icon_name" then
		return ""
	else if pKey is "icon" then
		return ""
	else if pKey is "selected_icon" then
		return ""
	end if
	return ""
end handler

private handler defaultNavArray() returns Array
	variable tArray as Array
	put the empty array into tArray
	put kDefaultNavName into tArray["name"]
	put kDefaultNavLabel into tArray["label"]
	put kDefaultNavIconName into tArray["icon_name"]
	put "" into tArray["selected_icon_name"]
	put "" into tArray["icon"]
	put "" into tArray["selected_icon"]
	return tArray
end handler

private handler navDataDifferent(in pLeft as List, in pRight as List) returns Boolean
	variable tIndex as Number
	repeat with tIndex from 1 up to the number of elements in pLeft
			if tIndex > the number of elements in pRight then
				return true
			end if

			if pLeft[tIndex]["label"] is not pRight[tIndex]["label"] then
				return true
			end if

			if pLeft[tIndex]["icon_name"] is not pRIght[tIndex]["icon_name"] then
				return true
			end if

			if pLeft[tIndex]["selected_icon_name"] is not pRight[tIndex]["selected_icon_name"] then
				if not (pLeft[tIndex]["selected_icon_name"] is "" and pRight[tIndex]["selected_icon_name"] is pRight[tIndex]["icon_name"]) and not (pLeft[tIndex]["selected_icon_name"] is pLeft[tIndex]["icon_name"] and pRight[tIndex]["selected_icon_name"] is "") then
					return true
				end if
			end if
	end repeat
	return false
end handler

private handler getNavData() returns Array
	return listToArray(mNavData)
end handler

private handler setNavData(in pNavData as Array)
	variable tNavData as List
	setData(pNavData, the keys of defaultNavArray(), tNavData)
	if navDataDifferent(tNavData, mNavData) then
		put tNavData into mNavData
		put true into mRecalculate
		post "navDataChanged"
		redraw all
	end if
end handler

private handler getNavNames() returns String
	return getDataElement("name", mNavData)
end handler

private handler setNavNames(in pNames as String)
	setPrimaryDataElement("name", pNames, defaultNavArray(), mNavData)
	put true into mRecalculate
	redraw all
end handler

private handler getNavIcons() returns String
	return getDataElement("icon_name", mNavData)
end handler

private handler setNavIcons(in pIcons as String)
	setDataElement("icon_name", pIcons, defaultNavArray(), mNavData)
	put true into mRecalculate
	redraw all
end handler

private handler getNavSelectedIcons() returns String
	return getDataElement("selected_icon_name", mNavData)
end handler

private handler setNavSelectedIcons(in pIcons as String)
	setDataElement("selected_icon_name", pIcons, defaultNavArray(), mNavData)
	put true into mRecalculate
	redraw all
end handler

private handler getNavLabels() returns String
	return getDataElement("label", mNavData)
end handler

private handler setNavLabels(in pLabels as String)
	setDataElement("label", pLabels, defaultNavArray(), mNavData)
	put true into mRecalculate
	redraw all
end handler

private handler addItem(in pItem as Number)
	splice [defaultNavArray()] after element pItem of mNavData
	put true into mRecalculate
	post "navDataChanged"
	redraw all
end handler

private handler deleteItem(in pItem as Number)
	delete element pItem of mNavData
	if mNavData is the empty list then
		put [defaultNavArray()] into mNavData
	end if
	put true into mRecalculate
	post "navDataChanged"
	redraw all
end handler

private handler moveItem(in pFrom as Number, in pTo as Number) returns nothing
	-- Do nothing if this isn't really a move.
	if pFrom is pTo then
		return
	end if

	variable tItem as Array
	put mNavData[pFrom] into tItem
	delete element pFrom of mNavData
	if pTo is 1 then
		splice [tItem] before element 1 of mNavData
	else
		splice [tItem] after element (pTo - 1) of mNavData
	end if
	post "navDataChanged"
	put true into mRecalculate
end handler

private handler reorderItem(in pItem as Number)
	put the y of the mouse position - ((pItem - 1) * kEditModeLineHeight) into mDragData["offset"]
	put pItem into mDragData["item"]
	put mNavData[pItem]["label"] into mDragData["label"]
	redraw all
end handler

private handler dragDrop() returns nothing
	if "target" is among the keys of mDragData then
		if mDragData["item"] > mDragData["target"] then
			moveItem(mDragData["item"], mDragData["target"] + 1)
		else if mDragData["target"] > mDragData["item"] then
			moveItem(mDragData["item"], mDragData["target"])
		end if
		put the empty array into mDragData
		redraw all
	end if
end handler

private handler getDesiredHeight() returns Number
	if mEditMode then
		return kEditModeLineHeight * mNavItemCount
	else
		return kEditModeLineHeight
	end if
end handler

private handler setOpaque(in pOpaque as Boolean) returns nothing
	if pOpaque is mOpaque then
		return
	end if

	put pOpaque into mOpaque
	redraw all
end handler

private handler setShowDivide(in pShowDivide as Boolean) returns nothing
	if pShowDivide is mShowDivide then
		return
	end if

	put pShowDivide into mShowDivide
	redraw all
end handler
--------------------------------------------------------------------------------
--
--		Editable rect things
--
--------------------------------------------------------------------------------

handler type PopupCallback(in pPopupResult as optional any, in pType as String, in pNumber as Number)
handler type ActionCallback(in pNumber as Number)

private variable mEditData as Array

public handler initialiseEditData()
	put the empty array into mEditData
end handler

public handler setEditableRect(in pComponent as String, in pNumber as Number, in pRect as Rectangle, in pPopup as String, in pCallback as PopupCallback)
	variable tComponentID as String
	put pComponent && pNumber formatted as string into tComponentID

	put the empty array into mEditData[tComponentID]

	put pRect into mEditData[tComponentID]["rect"]
	put pPopup into mEditData[tComponentID]["popup"]
	put pCallback into mEditData[tComponentID]["callback"]
	put pComponent into mEditData[tComponentID]["type"]
	put pNumber into mEditData[tComponentID]["number"]
	put true into mEditData[tComponentID]["mouseup"]
end handler

public handler setEditableTextRect(in pComponent as String, in pNumber as Number, in pRect as Rectangle)
	variable tComponentID as String
	put pComponent && pNumber formatted as string into tComponentID

	put the empty array into mEditData[tComponentID]

	put pRect into mEditData[tComponentID]["rect"]
	put pComponent into mEditData[tComponentID]["type"]
	put pNumber into mEditData[tComponentID]["number"]
	put true into mEditData[tComponentID]["mouseup"]
end handler

public handler setActionRect(in pComponent as String, in pNumber as Number, in pRect as Rectangle, in pAction as ActionCallback, in pMouseUp as Boolean)
	variable tComponentID as String
	put pComponent && pNumber formatted as string into tComponentID

	put the empty array into mEditData[tComponentID]

	put pRect into mEditData[tComponentID]["rect"]
	put pComponent into mEditData[tComponentID]["type"]
	put pNumber into mEditData[tComponentID]["number"]
	put pAction into mEditData[tComponentID]["action"]
	put pMouseUp into mEditData[tComponentID]["mouseup"]
end handler

public handler drawEditableRects(in pCanvas as Canvas)
	variable tElement as Array
	repeat for each element tElement in mEditData
		set the paint of pCanvas to solid paint with color [0,0,0]
		stroke rectangle path of tElement["rect"] on pCanvas
	end repeat
end handler

private handler widgetClicked(in pClickPosition as Point, in pMouseUp as Boolean)
	if pMouseUp then
		dragDrop()
	end if

	variable tElement as Array
	repeat for each element tElement in mEditData
		if tElement["mouseup"] is pMouseUp and pClickPosition is within tElement["rect"] then
			if "popup" is among the keys of tElement then
				doPopup(pClickPosition, tElement)
			else if "action" is among the keys of tElement then
				doAction(tElement)
			else
				addEditableField(tElement)
			end if
		end if
	end repeat
end handler

private handler doPopup(in pClickPos as Point, in pArray as Array)
	variable tResult

	-- Popup the icon picker with the currently selected icon as the initial selection
	variable tProperties as Array
	put the empty array into tProperties
	if pArray["type"] is "selectedIcon" and mNavData[pArray["number"]]["selected_icon_name"] is not "" then
		put mNavData[pArray["number"]]["selected_icon_name"] into tProperties["selectedIcon"]
	else
		put mNavData[pArray["number"]]["icon_name"] into tProperties["selectedIcon"]
	end if

	popup widget pArray["popup"] at pClickPos with properties tProperties
	put the result into tResult

	variable tCallback as PopupCallback
	put pArray["callback"] into tCallback
	tCallback(tResult, pArray["type"], pArray["number"])
end handler

private handler doAction(in pArray as Array)
	variable tCallback as ActionCallback
	put pArray["action"] into tCallback
	tCallback(pArray["number"])
end handler

private handler formatRectAsString(in pRect as Rectangle)
	variable tResult as String
	put (the left of pRect) formatted as string into tResult
	put "," & (the top of pRect) formatted as string after tResult
	put "," & (the right of pRect) formatted as string after tResult
	put "," & (the bottom of pRect) formatted as string after tResult
	return tResult
end handler

private handler addEditableField(in pElement as Array)
	-- Work around the fact that LCB currently doesn't have editable text fields
	post "editField" with [formatRectAsString(pElement["rect"]), pElement["number"]]
end handler

-- this handler converts a string of numbers to an RGBA color
private handler stringToColor(in pString as String) returns Color
	variable tRed as Real
	variable tGreen as Real
	variable tBlue as Real
	variable tAlpha as Real

	variable tComponentList as List
	split pString by "," into tComponentList

	variable tComponentCount
	put the number of elements in tComponentList into tComponentCount
	if tComponentCount is not 3 and tComponentCount is not 4 then
		// Invalid number of components detected
		throw "Invalid color"
	end if

	put (element 1 of tComponentList) parsed as number into tRed
	put (element 2 of tComponentList) parsed as number into tGreen
	put (element 3 of tComponentList) parsed as number into tBlue

	if tComponentCount is 4 then
		put (element 4 of tComponentList) parsed as number into tAlpha
	else
		put 255 into tAlpha
	end if

	return color [ tRed/255, tGreen/255, tBlue/255, tAlpha/255 ]
end handler
----------

-- this handler converts an RGBA color to a string
private handler colorToString(in pColor as Color) returns String
	return stripZeros((the rounded of ((the red of pColor) * 255)) formatted as string) & "," & stripZeros((the rounded of ((the green of pColor) * 255)) formatted as string) & "," & stripZeros((the rounded of ((the blue of pColor) * 255)) formatted as string) & "," & stripZeros((the rounded of ((the alpha of pColor) * 255)) formatted as string)
end handler
----------

-- this handler strips the zeros when a integer is formatted as a string
private handler stripZeros(in tString as String) returns String
	if tString contains "." then
		variable tCount as Integer
		repeat while ((the last char of tString) is in ".0")
			if the last char of tString is "." then
				delete the last char of tString
				exit repeat
			else
				delete the last char of tString
			end if
		end repeat
	end if
	return tString
end handler
----------

end widget
