<commit_before><?xml version="1.0" encoding="utf-8"?>
<!--
/*
	Weave (Web-based Analysis and Visualization Environment)
	Copyright (C) 2008-2011 University of Massachusetts Lowell
	
	This file is a part of Weave.
	
	Weave is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License, Version 3,
	as published by the Free Software Foundation.
	
	Weave 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 Weave.  If not, see <http://www.gnu.org/licenses/>.
*/
-->
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:ui="weave.ui.*"
    xmlns="weave.ui.*"
    width="100%" height="100%">
    <mx:DataGrid id="grid" editable="true" width="100%" height="100%" minWidth="0">
        <mx:columns>
            <mx:DataGridColumn headerText="Property" labelFunction="labelFunction" editable="false" width="{grid.width/3}"/>
            <mx:DataGridColumn id="valueCol" headerText="Value" dataField="value" editable="true" editorDataField="text"/>
        </mx:columns>
    </mx:DataGrid>
	<Indent label="Add property:">
		<CustomComboBox id="newPropCombo" editable="true" enter="addProp();" change="if (newPropCombo.selectedIndex >= 0) addProp();" width="100%"/>
        <mx:Button id="addPropButton" label="Add property" click="addProp();" enabled="{enableAddPropertyButtonForProp(newPropCombo.text)}"/>
	</Indent>
<mx:Script>
<![CDATA[
	import mx.collections.ArrayCollection;
	import mx.controls.TextInput;
	import mx.controls.dataGridClasses.DataGridColumn;
	import mx.events.CollectionEvent;
	import mx.events.DataGridEvent;
	import mx.utils.ObjectUtil;
	
	import weave.api.data.ColumnMetadata;
	import weave.compiler.StandardLib;
	import weave.core.UIUtils;
	
	/**
	 * Set this to the list of property names you want to display in the drop-down box for adding a new property. 
	 */
	[Bindable] public var selectablePropertyNames:Array = [];
	
	/**
	 * The name of a metadata property that should always be included in the list.
	 */
	public var defaultPropertyName:String;

	/**
	 * Whether or not 'get metadata' should return the full metadata or just a diff.
	 */
	public var fullMetadata:Boolean = false;
	
	/**
	 * A Function like function(propName:String):Array returning an Array of suggested String values for the specified metadata property.
	 */
	public var getSuggestedPropertyValues:Function = ColumnMetadata.getSuggestedPropertyValues;
	
	private var textEditFactory:ClassFactory;
	private var comboEditFactory:ClassFactory;
	
	override protected function childrenCreated():void
	{
		super.childrenCreated();
		
		textEditFactory = new ClassFactory(TextInput);
		comboEditFactory = new ClassFactory(CustomComboBox);
		comboEditFactory.properties = {editable: true};
		
		grid.addEventListener(DataGridEvent.ITEM_EDIT_BEGIN, handleItemEditBegin);
		grid.addEventListener(DataGridEvent.ITEM_EDIT_END, dispatchMetaChanged);
	}
	
	private function handleItemEditBegin(event:DataGridEvent):void
	{
		event.preventDefault();
		
		if (!gridItems || grid.columns[event.columnIndex] != valueCol)
			return;
		
		var item:MetadataGridItem = gridItems.getItemAt(event.rowIndex) as MetadataGridItem;
		var values:Array = getSuggestedPropertyValues != null ? getSuggestedPropertyValues(item.property) : null;
		if (values && values.length > 0)
		{
			valueCol.itemEditor = comboEditFactory;
			grid.createItemEditor(event.columnIndex, event.rowIndex);
			var comboBox:CustomComboBox = grid.itemEditorInstance as CustomComboBox;
			comboBox.setStyle('fontWeight', 'normal');
			
			// make a copy of the array and put the old value up front
			values = [item.oldValue].concat(values);
			// if current value was already in the list, remove it from its old position
			var index:int = values.indexOf(item.oldValue, 1);
			if (index > 0)
				values.splice(index, 1);
			
			comboBox.dataProvider = values;
			comboBox.selectedIndex = -1;
			comboBox.text = String(item.value);
			comboBox.setFocus();
		}
		else
		{
			valueCol.itemEditor = textEditFactory;
			grid.createItemEditor(event.columnIndex, event.rowIndex);
			var textInput:TextInput = grid.itemEditorInstance as TextInput;
			textInput.text = String(item.value);
			textInput.setFocus();
			textInput.setSelection(0, int.MAX_VALUE);
		}
		
		var event:DataGridEvent = new DataGridEvent(DataGridEvent.ITEM_FOCUS_IN);
		event.columnIndex = event.columnIndex;
		event.rowIndex = event.rowIndex;
		event.itemRenderer = grid.itemEditorInstance;
		dispatchEvent(event);
	}
	
	/**
	 * @return An Array of MetadataGridItem objects.
	 */
	private function get gridItems():ArrayCollection
	{
        return grid.dataProvider as ArrayCollection;
	}
	
    private function dispatchMetaChanged(o:Object = null):void
    {
        dispatchEvent(new Event("metaChanged"));
    }
    private static function labelFunction(item:MetadataGridItem, column:DataGridColumn):String
    {
		if (item.changed)
			return lang("*{0}", item.property);
		return item.property;
    }
	
	[Bindable(event="metaChanged")] public function get metadataChanged():Boolean
	{
		for each (var item:MetadataGridItem in gridItems)
			if (item.changed)
				return true;
		return false;
	}
	
    [Bindable(event="metaChanged")] public function get metadata():Object
    {
        var result:Object = (fullMetadata && originalValues) ? ObjectUtil.copy(originalValues) : {};
        for each (var item:MetadataGridItem in gridItems)
			if (fullMetadata || item.changed)
                result[item.property] = item.value || null; // avoid empty strings
		
        return result;
    }
    private var originalValues:Object = null;
    public function set metadata(values:Object):void
    {
		if (values)
		{
			originalValues = values;
	        var items:Array = new Array();
			if (values && defaultPropertyName && !values.hasOwnProperty(defaultPropertyName))
				items.push(new MetadataGridItem(defaultPropertyName));
	        for (var prop:String in values)
				if (propertyIgnoreList.indexOf(prop) < 0)
					items.push(new MetadataGridItem(prop, values[prop]));
			StandardLib.sort(items, propertySort);
	        grid.dataProvider = items;
	        gridItems.addEventListener(CollectionEvent.COLLECTION_CHANGE, dispatchMetaChanged);
			updateMissingPropNames();
	        dispatchMetaChanged();
		}
		else if (gridItems && gridItems.length)
		{
			grid.dataProvider = null;
			newPropCombo.dataProvider = null;
			dispatchMetaChanged();
		}
    }
	private function enableAddPropertyButtonForProp(prop:String):Boolean
	{
		return (prop && propertyIgnoreList.indexOf(prop) < 0) ? true : false
	}
    public function addProp():void
    {
        var prop:String = newPropCombo.text;
		
		if (!enableAddPropertyButtonForProp(prop))
			return;
		
        newPropCombo.text = null;
        if (!prop)
		{
            UIUtils.componentPulse(newPropCombo);
			return;
		}
		
		if (indexOfItemWithProperty(gridItems.source, 'property', prop) < 0)
		{
            gridItems.addItem(new MetadataGridItem(prop));
			updateMissingPropNames();
	        dispatchMetaChanged();
		}
		
		openEditor(prop);
    }
	private function updateMissingPropNames():void
	{
		var missingPropNames:Array = selectablePropertyNames.concat();
		for each (var item:MetadataGridItem in gridItems)
		{
			// exclude this prop from missingPropNames
			var index:int = missingPropNames.indexOf(item.property);
			if (index >= 0)
				missingPropNames.splice(index, 1);
		}
		StandardLib.sort(missingPropNames, propertySort);
		newPropCombo.dataProvider = missingPropNames;
		newPropCombo.selectedIndex = -1;
	}
	
	private function indexOfItemWithProperty(array:Array, property:String, value:String):int
	{
		for (var i:int = 0; i < array.length; i++)
			if (array[i][property] == value)
				return i;
		return -1;
	}
	private function openEditor(property:String):void
	{
		grid.editedItemPosition = {
			rowIndex: indexOfItemWithProperty(gridItems.source, 'property', property),
			columnIndex: grid.columns.indexOf(valueCol)
		};
	}
	
	/**
	 * This function compares two MetadataGridItem objects by comparing the property names.
	 * Items will be sorted according to preferredMetadataOrder.
	 * @param a The first value to compare, either a MetadataGridItem or a String.
	 * @param b The second value to compare, either a MetadataGridItem or a String.
	 * @return A value of -1, 0, or 1, which is used by Array.sort().
	 */
	private function propertySort(a:Object, b:Object):int
	{
		if (a is MetadataGridItem)
			a = (a as MetadataGridItem).property;
		if (b is MetadataGridItem)
			b = (b as MetadataGridItem).property;
		var order:Array = preferredMetadataOrder;
		var ia:int = order.indexOf(a);
		var ib:int = order.indexOf(b);
		if (ia >= 0 && ib >= 0)
			return ObjectUtil.numericCompare(ia, ib);
		if (ia >= 0)
			return -1;
		if (ib >= 0)
			return 1;
		
		return ObjectUtil.stringCompare(a as String, b as String, true);
	}
	public var preferredMetadataOrder:Array = [];
	public var propertyIgnoreList:Array = [];
]]>
</mx:Script>
</mx:VBox>

<commit_msg>fixed bug where set metadata() didn't unset originalValues if values was null.<commit_after><?xml version="1.0" encoding="utf-8"?>
<!--
/*
	Weave (Web-based Analysis and Visualization Environment)
	Copyright (C) 2008-2011 University of Massachusetts Lowell
	
	This file is a part of Weave.
	
	Weave is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License, Version 3,
	as published by the Free Software Foundation.
	
	Weave 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 Weave.  If not, see <http://www.gnu.org/licenses/>.
*/
-->
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:ui="weave.ui.*"
    xmlns="weave.ui.*"
    width="100%" height="100%">
    <mx:DataGrid id="grid" editable="true" width="100%" height="100%" minWidth="0">
        <mx:columns>
            <mx:DataGridColumn headerText="Property" labelFunction="labelFunction" editable="false" width="{grid.width/3}"/>
            <mx:DataGridColumn id="valueCol" headerText="Value" dataField="value" editable="true" editorDataField="text"/>
        </mx:columns>
    </mx:DataGrid>
	<Indent label="Add property:">
		<CustomComboBox id="newPropCombo" editable="true" enter="addProp();" change="if (newPropCombo.selectedIndex >= 0) addProp();" width="100%"/>
        <mx:Button id="addPropButton" label="Add property" click="addProp();" enabled="{enableAddPropertyButtonForProp(newPropCombo.text)}"/>
	</Indent>
<mx:Script>
<![CDATA[
	import mx.collections.ArrayCollection;
	import mx.controls.TextInput;
	import mx.controls.dataGridClasses.DataGridColumn;
	import mx.events.CollectionEvent;
	import mx.events.DataGridEvent;
	import mx.utils.ObjectUtil;
	
	import weave.api.data.ColumnMetadata;
	import weave.compiler.StandardLib;
	import weave.core.UIUtils;
	
	/**
	 * Set this to the list of property names you want to display in the drop-down box for adding a new property. 
	 */
	[Bindable] public var selectablePropertyNames:Array = [];
	
	/**
	 * The name of a metadata property that should always be included in the list.
	 */
	public var defaultPropertyName:String;

	/**
	 * Whether or not 'get metadata' should return the full metadata or just a diff.
	 */
	public var fullMetadata:Boolean = false;
	
	/**
	 * A Function like function(propName:String):Array returning an Array of suggested String values for the specified metadata property.
	 */
	public var getSuggestedPropertyValues:Function = ColumnMetadata.getSuggestedPropertyValues;
	
	private var textEditFactory:ClassFactory;
	private var comboEditFactory:ClassFactory;
	
	override protected function childrenCreated():void
	{
		super.childrenCreated();
		
		textEditFactory = new ClassFactory(TextInput);
		comboEditFactory = new ClassFactory(CustomComboBox);
		comboEditFactory.properties = {editable: true};
		
		grid.addEventListener(DataGridEvent.ITEM_EDIT_BEGIN, handleItemEditBegin);
		grid.addEventListener(DataGridEvent.ITEM_EDIT_END, dispatchMetaChanged);
	}
	
	private function handleItemEditBegin(event:DataGridEvent):void
	{
		event.preventDefault();
		
		if (!gridItems || grid.columns[event.columnIndex] != valueCol)
			return;
		
		var item:MetadataGridItem = gridItems.getItemAt(event.rowIndex) as MetadataGridItem;
		var values:Array = getSuggestedPropertyValues != null ? getSuggestedPropertyValues(item.property) : null;
		if (values && values.length > 0)
		{
			valueCol.itemEditor = comboEditFactory;
			grid.createItemEditor(event.columnIndex, event.rowIndex);
			var comboBox:CustomComboBox = grid.itemEditorInstance as CustomComboBox;
			comboBox.setStyle('fontWeight', 'normal');
			
			// make a copy of the array and put the old value up front
			values = [item.oldValue].concat(values);
			// if current value was already in the list, remove it from its old position
			var index:int = values.indexOf(item.oldValue, 1);
			if (index > 0)
				values.splice(index, 1);
			
			comboBox.dataProvider = values;
			comboBox.selectedIndex = -1;
			comboBox.text = String(item.value);
			comboBox.setFocus();
		}
		else
		{
			valueCol.itemEditor = textEditFactory;
			grid.createItemEditor(event.columnIndex, event.rowIndex);
			var textInput:TextInput = grid.itemEditorInstance as TextInput;
			textInput.text = String(item.value);
			textInput.setFocus();
			textInput.setSelection(0, int.MAX_VALUE);
		}
		
		var event:DataGridEvent = new DataGridEvent(DataGridEvent.ITEM_FOCUS_IN);
		event.columnIndex = event.columnIndex;
		event.rowIndex = event.rowIndex;
		event.itemRenderer = grid.itemEditorInstance;
		dispatchEvent(event);
	}
	
	/**
	 * @return An Array of MetadataGridItem objects.
	 */
	private function get gridItems():ArrayCollection
	{
        return grid.dataProvider as ArrayCollection;
	}
	
    private function dispatchMetaChanged(o:Object = null):void
    {
        dispatchEvent(new Event("metaChanged"));
    }
    private static function labelFunction(item:MetadataGridItem, column:DataGridColumn):String
    {
		if (item.changed)
			return lang("*{0}", item.property);
		return item.property;
    }
	
	[Bindable(event="metaChanged")] public function get metadataChanged():Boolean
	{
		for each (var item:MetadataGridItem in gridItems)
			if (item.changed)
				return true;
		return false;
	}
	
    [Bindable(event="metaChanged")] public function get metadata():Object
    {
        var result:Object = (fullMetadata && originalValues) ? ObjectUtil.copy(originalValues) : {};
        for each (var item:MetadataGridItem in gridItems)
			if (fullMetadata || item.changed)
                result[item.property] = item.value || null; // avoid empty strings
		
        return result;
    }
    private var originalValues:Object = null;
    public function set metadata(values:Object):void
    {
		originalValues = values;
		if (values)
		{
	        var items:Array = new Array();
			if (values && defaultPropertyName && !values.hasOwnProperty(defaultPropertyName))
				items.push(new MetadataGridItem(defaultPropertyName));
	        for (var prop:String in values)
				if (propertyIgnoreList.indexOf(prop) < 0)
					items.push(new MetadataGridItem(prop, values[prop]));
			StandardLib.sort(items, propertySort);
	        grid.dataProvider = items;
	        gridItems.addEventListener(CollectionEvent.COLLECTION_CHANGE, dispatchMetaChanged);
			updateMissingPropNames();
	        dispatchMetaChanged();
		}
		else if (gridItems && gridItems.length)
		{
			grid.dataProvider = null;
			newPropCombo.dataProvider = null;
			dispatchMetaChanged();
		}
    }
	private function enableAddPropertyButtonForProp(prop:String):Boolean
	{
		return (prop && propertyIgnoreList.indexOf(prop) < 0) ? true : false
	}
    public function addProp():void
    {
        var prop:String = newPropCombo.text;
		
		if (!enableAddPropertyButtonForProp(prop))
			return;
		
        newPropCombo.text = null;
        if (!prop)
		{
            UIUtils.componentPulse(newPropCombo);
			return;
		}
		
		if (indexOfItemWithProperty(gridItems.source, 'property', prop) < 0)
		{
            gridItems.addItem(new MetadataGridItem(prop));
			updateMissingPropNames();
	        dispatchMetaChanged();
		}
		
		openEditor(prop);
    }
	private function updateMissingPropNames():void
	{
		var missingPropNames:Array = selectablePropertyNames.concat();
		for each (var item:MetadataGridItem in gridItems)
		{
			// exclude this prop from missingPropNames
			var index:int = missingPropNames.indexOf(item.property);
			if (index >= 0)
				missingPropNames.splice(index, 1);
		}
		StandardLib.sort(missingPropNames, propertySort);
		newPropCombo.dataProvider = missingPropNames;
		newPropCombo.selectedIndex = -1;
	}
	
	private function indexOfItemWithProperty(array:Array, property:String, value:String):int
	{
		for (var i:int = 0; i < array.length; i++)
			if (array[i][property] == value)
				return i;
		return -1;
	}
	private function openEditor(property:String):void
	{
		grid.editedItemPosition = {
			rowIndex: indexOfItemWithProperty(gridItems.source, 'property', property),
			columnIndex: grid.columns.indexOf(valueCol)
		};
	}
	
	/**
	 * This function compares two MetadataGridItem objects by comparing the property names.
	 * Items will be sorted according to preferredMetadataOrder.
	 * @param a The first value to compare, either a MetadataGridItem or a String.
	 * @param b The second value to compare, either a MetadataGridItem or a String.
	 * @return A value of -1, 0, or 1, which is used by Array.sort().
	 */
	private function propertySort(a:Object, b:Object):int
	{
		if (a is MetadataGridItem)
			a = (a as MetadataGridItem).property;
		if (b is MetadataGridItem)
			b = (b as MetadataGridItem).property;
		var order:Array = preferredMetadataOrder;
		var ia:int = order.indexOf(a);
		var ib:int = order.indexOf(b);
		if (ia >= 0 && ib >= 0)
			return ObjectUtil.numericCompare(ia, ib);
		if (ia >= 0)
			return -1;
		if (ib >= 0)
			return 1;
		
		return ObjectUtil.stringCompare(a as String, b as String, true);
	}
	public var preferredMetadataOrder:Array = [];
	public var propertyIgnoreList:Array = [];
]]>
</mx:Script>
</mx:VBox>


{'fraction_non_alphanumeric': 0.1092319293331897, 'fraction_numerical': 0.006247980178821502, 'mean_word_length': 4.084063526834611, 'pattern_counts': {'":': 0, '<': 47, '<?xml version=': 2, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2123036', 'n_tokens_mistral': 6033, 'n_tokens_neox': 5406, 'n_words': 1457}