• Facebook
  • Twitter
  • Reddit
  • StumbleUpon
  • Digg
  • email

#-*- encoding: iso-8859-1 -*-
#-*- coding: iso-8859-1 -*-
 
import re
import sqlalchemy
import PyQt4
from pyHed.common import *
from pyHed.components import *
import frameCustomBtn
 
"""
	TODO:
	ok - usar a gerência de condições parecida com a que existe no php2go:
	ok - Usar o listBox ou um grid para listar os filtros informados pelo usuário. Caso ele só use um filtro, quando ele clicar em pesquisar executar o filtro
	informado.
	ok - Somente adicionar um filtro na lista quando ele clicar no botão adicionar filtro.
	- Adicionar capacidade de comboBox nos campos para filtros.
"""
 
class FrameCustomSearcher(frameCustomBtn.FrameCustomBtn):
	"""
		Classe pai para gerências no sistema. Também é usada para fazer gerências automáticas no sistema, a partir de telas que derivam do FrmCustomBtnDb
		Edgar, 02/set/2008
	"""
	def __init__(self, parent, params, hidePnlSubMenu=True, showAllRecords = True):
		# verifica se o programador informou o sql da maneira certa (para o imbecil não perder 1 hora procurando o que está acontecendo...)
		if params['SearchSQL'].find('%WHERECLAUSE%') == -1:
			raise Exception(u'The %WHERECLAUSE% constant was not informed on the SQL!')
 
		# mostra todos os registros da pesquisa ou não
		self.__showAllRecords = showAllRecords
 
		# armazena a sql de pesquisa do parent
		self.sql = params['SearchSQL']
		# campos para pesquisa
		self.searchFields = params['SearchFields']
		# títulos das colunas que devem aparecer
		self.columnsTitle = params['SearchColumnsTitle']
		# armazena o nome da primary key para filtro posterior na tela de edição
		self.__primaryKeyFieldName = params['PrimaryKey']
 
		# tipos de pesquisa, captions e seus respectivos representantes na SQL. NÃO SE ESQUECA: caso você adicione um operador novo, não se esqueça de adicionar  
		# nas três listas
		equal = pyHedConsts.translation.getItem('framecustomsearcher', 'equal_condition')
		different = pyHedConsts.translation.getItem('framecustomsearcher', 'different_condition')
		lessThan = pyHedConsts.translation.getItem('framecustomsearcher', 'lessthan_condition')
		biggerThan = pyHedConsts.translation.getItem('framecustomsearcher', 'biggerthan_condition')
		empty = pyHedConsts.translation.getItem('framecustomsearcher', 'empty_condition')
		notEmpty = pyHedConsts.translation.getItem('framecustomsearcher', 'notempty_condition')
		having = pyHedConsts.translation.getItem('framecustomsearcher', 'having_condition')
		begging = pyHedConsts.translation.getItem('framecustomsearcher', 'begging_condition')
		self.__searchOperatorsTexts = {
									   'integer': '%s,%s,%s,%s,%s,%s' % (equal, different, lessThan, biggerThan, empty, notEmpty),
									   'string': '%s,%s,%s,%s' % (having, begging, equal, different),
									   'date': '%s,%s,%s,%s' % (equal, different, biggerThan, lessThan)
		}
		self.__searchOperatorsValues = {
										'integer': '=,<>,<=, >=, is null, is not null',
										'string': ' like , like ,=,!=',
										'date': '=, !=, >=, <='
		}
		self.__searchOperatorsSimbols = {
									   'integer': 'equal,diferent,<=,>=,null,not null',
									   'string': 'having,begin,=,!=',
									   'date': '=,!=,>=,<='
		}		
 
		# oculta o pnlSubMenu
		# TODO: criar uma propriedade para ocultar/desocultar o pnlSubMenu
		self.__hidePnlSubMenu = hidePnlSubMenu
 
		super(FrameCustomSearcher, self).__init__(parent)
 
		self.title = params['Caption']
 
		# armazena os filtros e as condições
		self.filterList = []
 
	def onPaint(self):
		super(FrameCustomSearcher, self).onPaint()
 
		# oculta o panel lateral
		if self.__hidePnlSubMenu:
			self.hidePnlSubMenu()
 
		# botão para adicionar condição
		self.btnNewCondition = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustomsearcher', 'add_condition_button'), icon='%s/icon_adicionar_cond_pesquisa.gif' % pyHedConsts.pyHedImagePath, x=10, y=4, width=140, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'add_condition_button_hint'))
		self.connect(self.btnNewCondition, PyQt4.QtCore.SIGNAL('clicked()'), self.evtNewCondition)
		# Separador de Botões.
		self.separator = components.Panel(self.PnlButtons, width=1, height=self.heightPnlButtons - 9, x=self.btnNewCondition.x() + self.btnNewCondition.width() + 10, y=4, bgColor='#000000')
		# botao para remover condição de pesquisa
		self.btnDelCondition = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustomsearcher', 'remove_condition_button'), icon='%s/icon_remover_cond_pesquisa.gif' % pyHedConsts.pyHedImagePath, x=self.btnNewCondition.x() + self.btnNewCondition.width() + 20, y=4, width=140, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'remove_condition_button_hint'))
		self.connect(self.btnDelCondition, PyQt4.QtCore.SIGNAL('clicked()'), self.evtDelCondition)
		# Separador de Botões.
		self.separator2 = components.Panel(self.PnlButtons, width=1, height=self.heightPnlButtons - 9, x=self.btnDelCondition.x() + self.btnDelCondition.width() + 10, y=4, bgColor='#000000')
		# botão para executar pesquisa
		self.btnExecuteSearch = components.Button(parent=self.PnlButtons, text=pyHedConsts.translation.getItem('framecustomsearcher', 'search_button'), icon='%s/icon_executar_pesquisa.gif' % pyHedConsts.pyHedImagePath, x=self.btnDelCondition.x() + self.btnDelCondition.width() + 20, y=4, width=140, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'search_button_hint'))
		self.connect(self.btnExecuteSearch, PyQt4.QtCore.SIGNAL('clicked()'), self.evtExecuteSearch)
 
		# panel que armazena combo com a lista de campos para pesquisa, condição e valor à pesquisar
		self.pnlSearch = components.Panel(self.PnlMain, width=self.pnlButtons.width(), height=130, x=0, y=0, bgColor='#FFFFFF')
		# ComboBox de campos para pesquisa 
		self.cmbxSearchFields = components.ComboBox(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'label_field_combo_box'), x=10, y=5, width=150)
		# adiciona os campos para pesquisa
		for fieldName in self.searchFields:
			self.cmbxSearchFields.addItem(fieldName.split(',')[0])
 
		# ComboBox de tipos de pesquisa: iniciando, contendo, etc...
		self.cmbxOperation = components.ComboBox(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'label_operation_combo_box'), x=self.cmbxSearchFields.x() + self.cmbxSearchFields.width() + 20, y=5, width=150)
		self.connect(self.cmbxOperation, PyQt4.QtCore.SIGNAL('currentIndexChanged(int)'), self.evtCmbxOperationChanged)
 
		# edit para preenchimento do valor pelo usuário
		self.edtValue = components.Edit(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'label_condition_edit'), x=self.cmbxOperation.x() + self.cmbxOperation.width() + 20, y=5, width=250)
		self.connect(self.edtValue, PyQt4.QtCore.SIGNAL('returnPressed()'), self.evtEdtValueKeyPressed)
 
		# evento change do combo de fields. TEM QUE SER DECLARADO AQUI PORQUE O cmbxOperation deve ser declarado antes
		self.connect(self.cmbxSearchFields, PyQt4.QtCore.SIGNAL('currentIndexChanged(int)'), self.evtCmbxSearchFieldsChanged)
		# já inicia o combo
		self.evtCmbxSearchFieldsChanged(0)
 
		# cria o listBox que lista os filtros
		self.listFilters = components.ListBox(self.pnlSearch, text=pyHedConsts.translation.getItem('framecustomsearcher', 'search_filter_list_box'), x=10, y=58, height=50, width=600)
 
		# texto de Limite do resultado
		if self.__showAllRecords:
			lblSqlLimitText = pyHedConsts.translation.getItem('framecustomsearcher', 'show_all_records_text') 
		else:
			lblSqlLimitText = pyHedConsts.translation.getItem('framecustomsearcher', 'show_50_records_text')
 
		self.lblSqlLimit = components.Label(self.PnlMain, text=lblSqlLimitText, x=self.pnlSearch.width() - 280, y=self.pnlSearch.height() + self.pnlSearch.x() - 20, width=260, useDot=False, hint=pyHedConsts.translation.getItem('framecustomsearcher', 'lbl_limit_hint'), onClickEvent=self.evtLblSqlLimitClicked)
		self.lblSqlLimit.setAlignment(PyQt4.QtCore.Qt.AlignRight)
		self.lblSqlLimit.setStyleSheet("QLabel { text-decoration: underline; font-weight: bold;}")		
		self.lblSqlLimit.setCursor(PyQt4.QtCore.Qt.PointingHandCursor)
 
		# cria o grid para listar os dados.
		self.gridResult = dbComponents.DbGrid(self.PnlMain, top=self.pnlSearch.height(), height=self.PnlMain.height()-(self.pnlSearch.height()+72), width=self.PnlMain.width()-12)
		self.connect(self.gridResult, PyQt4.QtCore.SIGNAL('cellDoubleClicked(int,int)'), self.evtGridResultDoubleClicked)
		self.gridResult.setAutoSize(True)
 
	def evtLblSqlLimitClicked(self):
		self.__showAllRecords = not self.__showAllRecords
 
		if not self.__showAllRecords:
			self.lblSqlLimit.setText(pyHedConsts.translation.getItem('framecustomsearcher', 'show_50_records_text'))
		else:
			self.lblSqlLimit.setText(pyHedConsts.translation.getItem('framecustomsearcher', 'show_all_records_text'))
 
	def __showResult(self, sql):
		"""
			Procedure responsável por exibir o resultado da pesquisa no grid e habilitar os componentes necessários
			Edgar, 04/set/2008
		"""
		self.setCursor(PyQt4.QtCore.Qt.WaitCursor)
		try:
			# Seta a Ordenação do Grid Como Default.
			self.gridResult.sortItems(PyQt4.QtCore.Qt.AscendingOrder)
			# limpa o grid
			self.gridResult.clearGrid()
			# re-nomeia os títulos do Grid
			self.gridResult.setColumnsHeaders(self.columnsTitle)
			# exibe o resultado no grid
			self.gridResult.addRecordSet(pyHedConsts.dbInst.getConnection().execute(sql).fetchall())
 
			# oculta determinada coluna que representa a pk do select
			self.gridResult.hideIndexColumn('*')
 
			# TODO: melhorar esta funcionalidade. O ideal seria ocultar o grid e no lugar dele mostrar uma msg com este aviso.
			if self.gridResult.rowCount() == 0:
				components.messageBox(pyHedConsts.translation.getItem('framecustomsearcher', 'lbl_no_records_returned'))
		finally:
			self.setCursor(PyQt4.QtCore.Qt.ArrowCursor)
 
	def evtGridResultDoubleClicked(self, aRow, aCol):
		"""
			Evento de duplo click do gridResult.
			Edgar, 05/set/2008
		"""
		# bah!!! Que horror
		self.parent.filterRegister(str(self.__primaryKeyFieldName + '=' + str(self.gridResult.item(aRow, self.gridResult.getIndexColumn()).text())))
		self.closeFrame()
 
	def evtSetWhereClause(self, fieldName):
		"""
			ATENÇÃO: caso você queira fazer algum select especial a partir de um campo ou algo parecido, ESTE É O SEU EVENTO!!! :-)
			Esta é uma função que retorna o select de determinado field. Caso ela retorne vazio, então o filtro seguirá o padrão da gerência, caso contrário,
			o sql que retorne desta procedure será usado como sql para o filtro determinado e o campo será "pulado" da lista.
			Edgar, 04/set/2008
		"""
		pass
 
	def __getWhereClause(self):
		"""
			Retorna a cláusula where para pesquisa
		"""
		# TODO: usar o evtSetWhereClause no momento do filtro
 
		# armazena o valor normalizado
		searchValue = re.compile('''("|'|<|>|=)''').sub('', unicode(self.edtValue.displayText()))
 
		if searchValue == '' and self.cmbxOperation.currentText() != pyHedConsts.translation.getItem('framecustomsearcher', 'notempty_condition'):
			raise pyHedExceptions.UserException(pyHedConsts.translation.getItem('framecustomsearcher', 'search_value_required'))
 
		# verifica qual o tipo de campo selecionado
		if (self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[2]).strip(' ').upper() == 'INTEGER':
			filteredSQL = self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1] + self.__searchOperatorsValues['integer'].split(',')[self.cmbxOperation.currentIndex()] + searchValue
		elif (self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[2]).strip(' ').upper() == 'STRING':
			# like '%<VALOR>%'
			if self.__searchOperatorsSimbols['string'].split(',')[self.cmbxOperation.currentIndex()].upper() == 'HAVING':
				filteredSQL = pyHedConsts.dbInst.normalizeField(self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1]) + self.__searchOperatorsValues['string'].split(',')[self.cmbxOperation.currentIndex()] + pyHedConsts.dbInst.normalizeValue("'%" + searchValue + "%'")				
			# like '<VALOR>%'
			elif self.__searchOperatorsSimbols['string'].split(',')[self.cmbxOperation.currentIndex()].upper() == 'BEGIN':
				filteredSQL = pyHedConsts.dbInst.normalizeField(self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1]) + self.__searchOperatorsValues['string'].split(',')[self.cmbxOperation.currentIndex()] + pyHedConsts.dbInst.normalizeValue("'" + searchValue + "%'")
			# = '<valor>' ou != '<valor>'
			else:
				filteredSQL = self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1] + self.__searchOperatorsValues['string'].split(',')[self.cmbxOperation.currentIndex()] + "'" + searchValue + "'"
		elif(self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[2]).strip(' ').upper() == 'DATE':
			filteredSQL = self.searchFields[self.cmbxSearchFields.currentIndex()].split(',')[1] + self.__searchOperatorsValues['date'].split(',')[self.cmbxOperation.currentIndex()] + pyHedConsts.dbInst.normalizeDateValue(searchValue)
		else:
			raise Exception("There is not such operation")
 
		return filteredSQL
 
	def evtExecuteSearch(self):
		"""
			Executa a pesquisa a partir dos filtros informados pelo usuário. É chamado pelo botão btnExecuteSearch
			Edgar, 04/set/2008
		"""
		filterList = ''
 
		# se possui uma lista de filtros, utiliza a mesma no where
		if len(self.filterList) > 0:
			for afilter in self.filterList:
				filterList = filterList + afilter + ' and '
			filterList = filterList[:len(filterList)-4]
		else:
			filterList = self.__getWhereClause()
 
		# se tem limitador de registros, então usa o rowNum
		# TODO: usar o próprio Alchemy para isso
		if not self.__showAllRecords:
			filterList = filterList + ' and rownum <= 50'
 
		sql = self.sql.replace('%WHERECLAUSE%', filterList)
		# executa o sql
		self.__showResult(sql)
 
	def evtDelCondition(self):
		"""
			Remove o último o filtro da lista de filtros da gerência. Se existir somente um filtro, não faz nada.
			Edgar, 04/set/2008
		"""
		# verifica se existem itens para pesquisa
		if len(self.filterList) == 0:
			raise pyHedExceptions.UserException(pyHedConsts.translation.getItem('framecustomsearcher', 'no_filters_message'))
 
		# verifica se o usuário selecionou um item para exclusão. Se não houver nada selecionado, seleciona o último item da lista
		if self.listFilters.selectedItems() == []:
			self.listFilters.setCurrentRow(self.listFilters.count() - 1)
 
		# remove o item da lista de filtros SQL
		self.filterList.pop(self.listFilters.currentRow())
		# remove a linha o ListBox
		item = self.listFilters.takeItem(self.listFilters.currentRow())
		del item
 
	def evtNewCondition(self):
		"""
			Adiciona um novo filtro para pesquisa. É adicionado logo abaixo do filtro atual.
			Edgar, 04/set/2008
		"""
		# adiciona o filtro na lista
		self.filterList.append(self.__getWhereClause())
		# adiciona o filtro no listBox
		self.listFilters.addItem("campo '%s' %s o valor '%s'" % (self.cmbxSearchFields.itemText(self.cmbxSearchFields.currentIndex()), self.cmbxOperation.itemText(self.cmbxOperation.currentIndex()), self.edtValue.text()))
 
	def evtCmbxOperationChanged(self, index):
		if self.cmbxOperation.currentText() == pyHedConsts.translation.getItem('framecustomsearcher', 'notempty_condition'):
			self.edtValue.visible(False)
		else:
			self.edtValue.visible(True)
 
	def evtCmbxSearchFieldsChanged(self, index):
		"""
			Preenche os tipos de condições que o campo (de acordo com o tipo: string, integer, date, etc) selecionado pelo usuário pode ter.
			Edgar, 04/set/2008
		"""
		# TODO: ESTUDAR itemData no ComboBox. Verificar a possibilidade de eliminar alguns dos arrays que temos nesta classe
 
		# limpa os valores de pesquisa e as operações do campo anterior, após preenche as corretas
		self.edtValue.clear()
		self.cmbxOperation.clear()
		for op in self.__searchOperatorsTexts[(self.searchFields[index].split(',')[2]).strip(' ').lower()].split(','):
			self.cmbxOperation.addItem(op)
 
		if (self.searchFields[index].split(',')[2]).strip(' ').lower() == 'date':
			# userDateFormat is a constant set on the config.ini of the application. If not set, the default is 00/00/0000
			# used to set the format that the user will see the date
			if pyHedConsts.config.has_key('userdateformat'):
				self.edtValue.setInputMask(pyHedConsts.config['userdateformat'])
			else:
				self.edtValue.setInputMask('00/00/0000')
		elif (self.searchFields[index].split(',')[2]).strip(' ').lower() == 'integer':
			self.edtValue.setValidator(PyQt4.QtGui.QIntValidator(self))
			self.edtValue.setInputMask('')
		else:
			self.edtValue.setValidator(None)
			self.edtValue.setInputMask('')
 
	def evtEdtValueKeyPressed(self):
		self.evtExecuteSearch()
 
	def paintEvent(self, ev):
		"""
			Evento para update da tela.
		"""
		pass
 
	def closeFrame(self):
		# BAH, QUE HORROR!!!! SUPER ULTRA BACALHAU!!!
		# desbloqueia o botão de fechar enquanto a pesquisa não retornar...
		# TODO: péssima solução! O ideal aqui seria usar os QDialog... Avaliar... - Edgar, 06/out/08 PARABÉNS PARA MIM!!! :-)
		if (issubclass(self.parent.__class__, frameCustomBtn.FrameCustomBtn)):
			self.parent.btnClose.setEnabled(True)
 
		super(FrameCustomSearcher, self).closeFrame()
		# re-opens the tab that actived the search frame
		pyHedConsts.frmMain.tabs.setCurrentIndex(pyHedConsts.frmMain.tabs.indexOf(self.parent))