Index: sandbox/workflow/trunk/inc/local/classes/class.wf_location.php
===================================================================
--- sandbox/workflow/trunk/inc/local/classes/class.wf_location.php	(revision 795)
+++ sandbox/workflow/trunk/inc/local/classes/class.wf_location.php	(revision 2160)
@@ -72,4 +72,5 @@
 			c.id_city AS id_city,
 			c.city_name AS city_name,
+			c.is_district,
 			s.id_state AS id_state,
 			s.state_name AS state_name,
@@ -125,10 +126,11 @@
 	 * Busca as cidades de um estado
 	 * @param int $state_id Numero ID do estado
-	 * @return mixed (array ou boolean)
-	 * @access public
-	 */
-	function getCitiesFromState($state_id)
-	{
-		if (!is_numeric($state_id))
+	 * @param bool $include_districts True, busca cidades e distritos. False, busca apenas cidades.
+	 * @return mixed (array ou boolean)
+	 * @access public
+	 */
+	function getCitiesFromState($state_id, $include_districts = true)
+	{
+		if (!is_numeric($state_id) || !is_bool($include_districts))
 			return false;
 
@@ -137,8 +139,14 @@
 			return $this->citiesFromState[$state_id];
 
+		$where = "";
+		if(!$include_districts){
+			$where = " AND c.is_district = 'F' ";
+		}
+
 		$sql =
 		"SELECT
 			c.id_city AS id_city,
-			c.city_name AS city_name
+			c.city_name AS city_name,
+			c.is_district
 		FROM
 			phpgw_cc_state s,
@@ -147,5 +155,5 @@
 			c.id_state = s.id_state AND
 			s.id_country = 'BR' AND
-			c.id_state = ?
+			c.id_state = ? " . $where . "
 		ORDER BY
 			city_name";
@@ -160,20 +168,43 @@
 	}
     /**
-	 * Busca os estados brasileiros
-	 * @return mixed (array ou boolean)
-	 * @access public
-	 */
-	function getStates()
-	{
-		$sql =
-		"SELECT
-			id_state,
-			state_name
-		FROM
-			phpgw_cc_state
-		WHERE
-			id_country = 'BR'
+	 * Busca as cidades por parte do nome, sem considerar maiúsculas e/ou minúsculas e nem acentuação (retorna 10 resultados)
+	 * @param string Parte do nome da cidade
+	 * @param int $state_id Numero ID do estado
+	 * @param bool $include_districts True, busca cidades e distritos. False, busca apenas cidades.
+	 * @return mixed (array ou boolean)
+	 * @access public
+	 */
+	function getCitiesByKey($key, $state_id = 0, $include_districts = true)
+	{
+		if (!is_string($key) || !is_numeric($state_id) || !is_bool($include_districts))
+			return false;
+
+		$where = "";
+		if($state_id > 0){
+			$where = " AND c.id_state = " . $state_id;
+		}
+
+		if(!$include_districts){
+			$where = " AND c.is_district = 'F' ";
+		}
+
+		$sql =
+		"SELECT
+			c.id_city AS id_city,
+			c.city_name AS city_name,
+			s.id_state AS id_state,
+			s.state_name AS state_name,
+			s.state_symbol AS state_symbol
+		FROM
+			phpgw_cc_state s,
+			phpgw_cc_city c
+		WHERE
+			c.id_state = s.id_state AND
+			s.id_country = 'BR' AND
+			TO_ASCII(c.city_name) ILIKE TO_ASCII('" . $key . "%')
+			" . $where . "
 		ORDER BY
-			state_name";
+			city_name
+		LIMIT 10";
 
 		$result = $this->db->query($sql);
@@ -184,4 +215,29 @@
 		return $output;
 	}
+	/**
+	 * Busca os estados brasileiros
+	 * @return mixed (array ou boolean)
+	 * @access public
+	 */
+	function getStates()
+	{
+		$sql =
+		"SELECT
+			id_state,
+			state_name
+		FROM
+			phpgw_cc_state
+		WHERE
+			id_country = 'BR'
+		ORDER BY
+			state_name";
+
+		$result = $this->db->query($sql);
+		$output = array();
+		while ($row = $result->fetchRow())
+			$output[] = $row;
+
+		return $output;
+	}
 }
 ?>
Index: sandbox/workflow/trunk/inc/smarty/wf_plugins/function.wf_autocomplete_input.php
===================================================================
--- sandbox/workflow/trunk/inc/smarty/wf_plugins/function.wf_autocomplete_input.php	(revision 1935)
+++ sandbox/workflow/trunk/inc/smarty/wf_plugins/function.wf_autocomplete_input.php	(revision 2160)
@@ -28,5 +28,6 @@
 	$defaultValues = array(
 		'minLength'	=> 1,
-		'style'		=> "width: 200px"
+		'style'		=> "width: 200px",
+		'mode'		=> "POPULATE_ON_LOAD"
 	);
 	$extractParams = array(
@@ -38,5 +39,6 @@
 		'idValue',
 		'textValue',
-		'style'
+		'style',
+		'mode'
 	);
 
@@ -79,10 +81,23 @@
 	$extraParams = json_encode($extraParams);
 
+	/* se o componente for do tipo padrão (POPULATE_ON_LOAD), não necessita de função para onkeyup */
+	if ($mode == 'POPULATE_ON_LOAD'){
+		$inputElement = <<<EOF
+		<input id="$name_input" name="$name_input" type="text" value="$textValue" style="$style" onfocus="checkDataLoaded('$name');" onblur="setTimeout('selectAutocompleteElement(\'$name\')', 500);"/>
+EOF;
+	}
+	elseif ($mode == 'REPOPULATE_ON_CHANGE'){
+		$inputElement = <<<EOF
+		<input id="$name_input" name="$name_input" type="text" value="$textValue" style="$style" onblur="setTimeout('selectAutocompleteElement(\'$name\')', 500);" onkeyup="updateCacheRequestsTimeout('$name', '$ajaxClass', '$ajaxMethod', this.value, '$mode')"/>
+EOF;
+	}
+
+	/* Cria todos os elementos HTML necessários para o componente */
 	$output = <<<EOF
 	<input id="$name" name="$name" type="hidden"/>
-	<input id="$name_input" name="$name_input" type="text" value="$textValue" style="$style" onfocus="checkDataLoaded('$name');" onblur="setTimeout('selectAutocompleteElement(\'$name\')', 500);"/>
+	$inputElement
 	<span id="$name_response"></span>
 	<div id="$name_list" class="autocomplete" style="display: none;"></div>
-	<script>autocompleteSelect('$name', '$ajaxClass', '$ajaxMethod', $sep$methodParam$sep, $extraParams);</script>
+	<script>autocompleteSelect('$name', '$ajaxClass', '$ajaxMethod', $sep$methodParam$sep, '$mode', $extraParams);</script>
 EOF;
 	return $output;
Index: sandbox/workflow/trunk/js/jscode/wf_autocomplete_input.js
===================================================================
--- sandbox/workflow/trunk/js/jscode/wf_autocomplete_input.js	(revision 1935)
+++ sandbox/workflow/trunk/js/jscode/wf_autocomplete_input.js	(revision 2160)
@@ -1,4 +1,7 @@
 var _cache = new Array();
-
+var autocompleterObjs = new Array();
+var myTimer;
+
+/* Verifica se o componente já foi populado (usado quando o tipo do componente é POPULATE_ON_LOAD) */
 function checkDataLoaded(elementId)
 {
@@ -18,9 +21,12 @@
 }
 
+/* Faz a verificação se o conteúdo digitado tem um correspondente na lista */
 function selectAutocompleteElement(elementId)
 {
 	if ($('input' + elementId) == null) return -1;
 	var value = $('input' + elementId).value;
-	var items = _cache['requests'][_cache['elements'][elementId]['hash']]['data'];
+	var items = new Object();
+	if(_cache['requests'][_cache['elements'][elementId]['hash']] != undefined)
+		items = _cache['requests'][_cache['elements'][elementId]['hash']]['data'];
 	var index = autocompleteIndexOf(items, value);
 	$(elementId).value = index;
@@ -30,9 +36,12 @@
 }
 
-function autocompleteIndexOf(array, value)
-{
-	for (key in array){
-		if (array[key].toLowerCase() == value.toLowerCase()){
-			return key;
+/* Percorre o objeto comparando cada item com o valor digitado no componente, e retorna o índice, se encontrar */
+function autocompleteIndexOf(items, value)
+{
+	for (key in items){
+		if(items.hasOwnProperty(key)){
+			if (items[key].toLowerCase() == value.toLowerCase()){
+				return key;
+			}
 		}
 	}
@@ -40,14 +49,32 @@
 }
 
-function arrayValues(array)
+function arrayValues(items)
 {
 	var i = 0;
 	var arr = Array();
-	for (key in array){
-		arr[i++] = array[key];
+	for (key in items){
+		arr[i++] = items[key];
 	}
 	return arr;
 }
 
+function removeAccents(text) {
+	var accents = 'áàãâäéèêëíìîïóòõôöúùûüçÁÀÃÂÄÉÈÊËÍÌÎÏÓÒÕÖÔÚÙÛÜÇ';
+	var normalLetters = 'aaaaaeeeeiiiiooooouuuucAAAAAEEEEIIIIOOOOOUUUUC';
+	var newText = '';
+
+	for (i = 0; i < text.length; i++) {
+		if (accents.search(text.substr(i, 1)) >= 0) {
+			newText += normalLetters.substr(accents.search(text.substr(i, 1)), 1);
+		}
+		else {
+			newText += text.substr(i, 1);
+		}
+	}
+
+	return newText;
+}
+
+/* Cria, ou recria, o objeto autocompleter do componente, carregando a lista com os valores que já devem estar no cache */
 function autocompletePopulate(elementId)
 {
@@ -55,22 +82,39 @@
 		var items = _cache['requests'][_cache['elements'][elementId]['hash']]['data'];
 		var values = arrayValues(items);
-		new Autocompleter.Local('input' + elementId, 'list' + elementId, values, {
+		autocompleterObjs[elementId] = new Autocompleter.Local('input' + elementId, 'list' + elementId, values, {
 			'choices':9,
 			'partialChars': _cache['elements'][elementId]['minLength']
 		});
-	}
-	_cache['elements'][elementId]['populated'] = true;
-}
-
-function catchJsonError(dados){ return false; }
-
-function autocompleteSelect(elementId, ajaxClass, ajaxMethod, methodParam, extraParams)
-{
-	// se o parÃ¢metro for um objeto, transforma em uma string para gerar o hash
+		_cache['elements'][elementId]['populated'] = true;
+	}
+}
+
+/* Cria array para armazenar os elementos */
+function createCacheElementsArray(){
+	if(_cache['elements'] == null)
+		_cache['elements'] = new Array();
+}
+
+/* Cria array para armazenar os valores das requisições ajax */
+function createCacheRequestsArray(){
+	if(_cache['requests'] == null)
+		_cache['requests'] = new Array();
+}
+
+/* Seta os valores do componente no _cache */
+function createCacheElement(elementId, ajaxClass, ajaxMethod, methodParam, componentMode, extraParams){
+	// se o parâmetro for um objeto, transforma em uma string para gerar o hash
 	var _param = (typeof(methodParam) == 'object')? JSON.stringify(methodParam) : methodParam;
-	var str = ajaxClass + ajaxMethod + _param;
-	// Cria hash que identifica classe, mÃ©todo e parÃ¢metro.
-	// Componente verifica se hash jÃ¡ existe para nÃ£o fazer requisiÃ§Ãµes ajax desnecessÃ¡rias
-	var hash = new SHA1(str).hexdigest();
+
+	// se o componente tiver que ser populado em sua criação, calcula o hash
+	if (componentMode == 'POPULATE_ON_LOAD'){
+		var str = ajaxClass + ajaxMethod + _param;
+		// Cria hash que identifica classe, método e parâmetro.
+		// Componente verifica se hash já existe para não fazer requisições ajax desnecessárias
+		var hash = new SHA1(str).hexdigest();
+	}
+	// senão, não há a necessidade de calcular o hash, pois a lista será carregada posteriormente
+	else
+		var hash = "";
 
 	if(extraParams['minLength'] == null){
@@ -89,20 +133,5 @@
 	}
 
-	func = function (dados)
-	{
-		var result = dados[ajaxMethod]['data'];
-		if (result !== false){
-			// guarda valores localmente
-			_cache['requests'][hash]['data'] = result;
-			_cache['requests'][hash]['populated'] = true;
-			// Envia dados para o componente
-			autocompletePopulate(elementId);
-		}
-	};
-
-	// Relaciona chamada ajax ao objeto
-	if(_cache['elements'] == null){
-		_cache['elements'] = new Array();
-	}
+	createCacheElementsArray();
 
 	if(_cache['elements'][elementId] == null){
@@ -111,8 +140,8 @@
 		_cache['elements'][elementId]['minLength'] = extraParams['minLength'];
 	} else {
-		// por algum motivo componente jÃ¡ existe.
-		// Se o _cache na posiÃ§Ã£o hash nÃ£o for null, requisiÃ§Ã£o ajax jÃ¡ foi feita.
-		// Se o objeto jÃ¡ foi populado e estÃ¡ sendo escrito novamente, manda popular denovo 
-		//   (isso pode acontecer quando o componente Ã© escrito atravÃ©s de javascript)
+		// por algum motivo componente já existe.
+		// Se o _cache na posição hash não for null, requisição ajax já foi feita.
+		// Se o objeto já foi populado e está sendo escrito novamente, manda popular denovo
+		//   (isso pode acontecer quando o componente é escrito através de javascript)
 		if(_cache['requests'][hash] != null)
 			if (_cache['requests'][hash]['populated'] == true){
@@ -121,8 +150,22 @@
 			}
 	}
-
-	// Cria array para armazenar localmente os valores da requisiÃ§Ã£o ajax
-	if(_cache['requests'] == null)
-		_cache['requests'] = new Array();
+}
+
+function catchJsonError(dados){ return false; }
+
+/* Controle de timeout para chamar a função updateCacheRequests quando o usuário ficar 0.3 segundos sem digitar. (chamada no onkeyup do componente do tipo REPOPULATE_ON_CHANGE) */
+function updateCacheRequestsTimeout(elementId, ajaxClass, ajaxMethod, methodParam, componentMode){
+	clearTimeout(myTimer);
+	myTimer = setTimeout("updateCacheRequests('" + elementId + "', '" + ajaxClass + "', '" + ajaxMethod + "', '" + methodParam + "', '" + componentMode + "')", 300);
+}
+
+/* Função que faz o gerenciamento das chamadas ajax e atualiza a lista de opções para seleção. */
+var updateCacheRequests = function(elementId, ajaxClass, ajaxMethod, methodParam, componentMode){
+	// se o parâmetro for um objeto, transforma em uma string para gerar o hash
+	var _param = (typeof(methodParam) == 'object')? JSON.stringify(methodParam) : methodParam;
+	var str = ajaxClass + ajaxMethod + _param;
+	// Cria hash que identifica classe, método e parâmetro.
+	// Componente verifica se hash já existe para não fazer requisições ajax desnecessárias
+	var hash = new SHA1(str).hexdigest();
 
 	if(_cache['requests'][hash] == null){
@@ -131,16 +174,84 @@
 		_cache['requests'][hash]['data'] = new Array();
 
-		// Faz a requisiÃ§Ã£o ajax/Json
-		var nc = new NanoController();
-		nc.setWfUrl();
-		nc.setSuccessHandler(func);
-		nc.setExceptionHandler(catchJsonError);
-		nc.setErrorHandler(catchJsonError);
-		nc.addVirtualRequest(ajaxMethod,
-		{
-			action: ajaxClass,
-			mode:   ajaxMethod
-		}, methodParam);
-		nc.sendRequest();
-	}
-}
+		if (componentMode == 'POPULATE_ON_LOAD'){
+			func = function (dados)
+			{
+				var result = dados[ajaxMethod]['data'];
+				if (result !== false){
+					// guarda valores localmente
+					_cache['requests'][hash]['data'] = result;
+					_cache['requests'][hash]['populated'] = true;
+					// Envia dados para o componente
+					autocompletePopulate(elementId);
+				}
+			};
+
+			// Faz a requisição ajax/Json
+			var nc = new NanoController();
+			nc.setWfUrl();
+			nc.setSuccessHandler(func);
+			nc.setExceptionHandler(catchJsonError);
+			nc.setErrorHandler(catchJsonError);
+			nc.addVirtualRequest(ajaxMethod,
+				{
+					action: ajaxClass,
+					mode:   ajaxMethod
+				}, methodParam);
+			nc.sendRequest();
+		}
+		else{
+			if(_param.length >= 3){
+				func = function (dados)
+				{
+					var result = dados[ajaxMethod]['data'];
+					if(typeof(result) == "object" && result.length != 0){
+						// guarda valores localmente
+						_cache['requests'][hash]['data'] = result;
+						_cache['requests'][hash]['populated'] = true;
+						_cache['elements'][elementId]['hash'] = hash;
+						var values = arrayValues(result);
+						autocompleterObjs[elementId].options.array = values;
+						autocompleterObjs[elementId].getUpdatedChoices();
+					}
+				};
+
+				// Faz a requisição ajax/Json
+				var nc = new NanoController();
+				nc.setWfUrl();
+				nc.setSuccessHandler(func);
+				nc.setExceptionHandler(catchJsonError);
+				nc.setErrorHandler(catchJsonError);
+				nc.addVirtualRequest(ajaxMethod,
+					{
+						action: ajaxClass,
+						mode:   ajaxMethod
+					}, removeAccents(methodParam));
+				nc.sendRequest();
+			}
+		}
+	}
+	else{
+		// Se for do modo que deve repopular a lista a cada tecla pressionada e o _cache referente à entrada digitada já estiver populada
+		if (componentMode == 'REPOPULATE_ON_CHANGE' && _cache['requests'][hash]['populated']){
+			_cache['elements'][elementId]['hash'] = hash;
+			var values = arrayValues(_cache['requests'][hash]['data']);
+			autocompleterObjs[elementId].options.array = values;
+		}
+	}
+}
+
+/* Função que prepara o _cache, criando e setando os valores para cada componente */
+function autocompleteSelect(elementId, ajaxClass, ajaxMethod, methodParam, componentMode, extraParams)
+{
+	createCacheElement(elementId, ajaxClass, ajaxMethod, methodParam, componentMode, extraParams);
+	createCacheRequestsArray();
+
+	// Se o componente é do tipo que deve repopular a lista a cada tecla, cria um objeto autocompleter com a lista vazia
+	if(componentMode == 'REPOPULATE_ON_CHANGE')
+		autocompleterObjs[elementId] = new Autocompleter.Local('input' + elementId, 'list' + elementId, new Array(), {
+			'choices':9,
+			'partialChars': _cache['elements'][elementId]['minLength']
+		});
+
+	updateCacheRequests(elementId, ajaxClass, ajaxMethod, methodParam, componentMode);
+}
Index: sandbox/workflow/trunk/js/jscode/get_form_fields.js
===================================================================
--- sandbox/workflow/trunk/js/jscode/get_form_fields.js	(revision 795)
+++ sandbox/workflow/trunk/js/jscode/get_form_fields.js	(revision 2160)
@@ -6,6 +6,6 @@
 function get_form_fields(form_id){
 	var elems = document.forms[form_id].elements;
-	var arr = new Object();
-	var j = 0;
+	var arr   = new Object();
+	var j     = 0;
 	for(var i = 0; i < elems.length; i++){
 		if(elems[i].id.substr(0,1) == "_"){
@@ -33,4 +33,18 @@
 				continue;
 			}
+
+			// if the element has [] in the end of its name, return its values (or checked elements) like an array
+			if(elems[i].name.substr(-2,2) == "[]"){
+				elem_name = elems[i].name.substr(0, elems[i].name.length - 2);
+				if(arr[elem_name] == undefined){
+					arr[elem_name] = new Array();
+				}
+
+				if(elems[i].type != "checkbox")
+					arr[elem_name].push(elems[i].value);
+				else if(elems[i].checked)
+					arr[elem_name].push(elems[i].value);
+			}
+
 			arr[elems[i].id] = elems[i].value;
 			j++;
