diff --git a/ODMTools.py b/ODMTools.py index 9f5382a..0c6837e 100755 --- a/ODMTools.py +++ b/ODMTools.py @@ -22,9 +22,9 @@ import pymysql #import psycopg2 - tool = LoggerTool() -logger = tool.setupLogger('main', 'odmtools.log', 'a', logging.INFO) +# logger = tool.setupLogger('main', 'odmtools.log', 'a', logging.INFO) +logger = tool.setupLogger('main', 'odm2tools.log', 'a', logging.DEBUG) wx.Log.SetLogLevel(0) @@ -45,7 +45,7 @@ def OnInit(self): """ Initialize an App with a Frame """ - title = u'ODMTools' + title = u'ODM2Tools' kwargs = {} kwargs['parent'] = None @@ -57,18 +57,14 @@ def OnInit(self): kwargs['taskServer'] = self.taskserver kwargs['memdb']= self.memdb self.frame = frmODMToolsMain(**kwargs) + self.frame.CenterOnScreen() self.frame.Show(True) app= self.frame return True -def runODM(): - app = wx.App(False) - #frame = create(None) - #frame.Show() - app.MainLoop() if __name__ == '__main__': - logger.info("Welcome to ODMTools Python. Please wait as system loads") + logger.info("Welcome to ODM2Tools Python. Please wait as system loads") # https://docs.python.org/2/library/multiprocessing.html#miscellaneous # Add support for when a program which uses multiprocessing has been frozen to produce a Windows executable. diff --git a/doc/wxFormBuilder/clsIntro.py b/doc/wxFormBuilder/clsIntro.py index dd3d888..094fe78 100644 --- a/doc/wxFormBuilder/clsIntro.py +++ b/doc/wxFormBuilder/clsIntro.py @@ -29,7 +29,7 @@ def __init__( self, parent ): bSizer2 = wx.BoxSizer( wx.VERTICAL ) - self.lblHow = wx.StaticText( self.m_panel2, wx.ID_ANY, u"How would you like to save the series?", wx.DefaultPosition, wx.DefaultSize, 0 ) + self.lblHow = wx.StaticText( self.m_panel2, wx.ID_ANY, u"How would you like to save the series_service?", wx.DefaultPosition, wx.DefaultSize, 0 ) self.lblHow.Wrap( -1 ) bSizer2.Add( self.lblHow, 0, wx.ALL, 15 ) diff --git a/odmtools/__init__.py b/odmtools/__init__.py index df60136..5381d99 100644 --- a/odmtools/__init__.py +++ b/odmtools/__init__.py @@ -1,6 +1,5 @@ __author__ = 'Jacob' import odmdata -import odmservices import controller import gui import lib @@ -9,7 +8,6 @@ __all__ = [ 'odmdata', - 'odmservices', 'controller', 'gui', 'lib', diff --git a/odmtools/common/taskServer.py b/odmtools/common/taskServer.py index 0620112..ec373f4 100644 --- a/odmtools/common/taskServer.py +++ b/odmtools/common/taskServer.py @@ -130,14 +130,17 @@ def worker(cls, dispatcher): result = Statistics(task) if task_type == "InitEditValues": connection = SeriesService("sqlite:///:memory:") + # connection._ df = task[1] logger.debug("Load series from db") - df.to_sql(name="DataValues", con=connection._session_factory.engine, flavor='sqlite', index = False, chunksize = 10000) + #setSchema(self.mem_service._session_factory.engine) + df.to_sql(name="timeseriesresultvalues", con=connection._connection.engine, flavor='sqlite', index = False, chunksize = 10000) + logger.debug("done loading database") result = connection if task_type == "UpdateEditDF": connection = task[1] - result = connection.get_all_values_df() + result = connection.get_values() result = (task_type, result) diff --git a/odmtools/controller/frmBulkInsert.py b/odmtools/controller/BulkInsertController.py old mode 100755 new mode 100644 similarity index 79% rename from odmtools/controller/frmBulkInsert.py rename to odmtools/controller/BulkInsertController.py index b480145..ebb8ab5 --- a/odmtools/controller/frmBulkInsert.py +++ b/odmtools/controller/BulkInsertController.py @@ -2,7 +2,7 @@ Bulk Insert of points """ import wx -import odmtools.view.clsBulkInsert as clsBulkInsert +import odmtools.view.BulkInsertView as clsBulkInsert import odmtools.controller.olvAddPoint as olv import pandas as pd from pandas.parser import CParserError @@ -13,13 +13,12 @@ __author__ = 'Jacob' -class BulkInsert(clsBulkInsert.BulkInsert): +class BulkInsertController(clsBulkInsert.BulkInsertView): def __init__(self, parent): - clsBulkInsert.BulkInsert.__init__(self, parent) + clsBulkInsert.BulkInsertView.__init__(self, parent) self.parent = parent - self.col = ['DataValue', 'Date', 'Time', 'UTCOffSet', 'CensorCode', 'ValueAccuracy', 'OffSetValue', - 'OffSetType', 'QualifierCode', 'LabSampleCode'] + self.columns = ["DataValue", "Date", "Time", "UTFOffset", "CensorCode", "QualityCode", "TimeAggregationInterval", "TimeAggregationUnitID", "Annotation"] def obtainFilePath(self): ## Obtain CSV filepath @@ -53,16 +52,10 @@ def readDataFromCSV(self, filepath): try: #data = pd.read_csv(filepath, skiprows=[1], engine='c', lineterminator='\n') - data = pd.read_csv(csv_data, skiprows=[1], engine='c', converters={0: str.strip, - 1: str.strip, - 2: str.strip, - 3: str.strip, - 4: str.strip, - 5: str.strip, - 6: str.strip, - 7: str.strip, - 8: str.strip, - 9: str.strip}) + data = pd.read_csv(csv_data, skiprows=[1], engine='c', converters={ + 0: str.strip, 1: str.strip, 2: str.strip, 3: str.strip, + 4: str.strip, 5: str.strip, 6: str.strip, 7: str.strip, + 8: str.strip}) except CParserError as e: message = "There was an issue trying to parse your file. "\ "Please compare your csv with the template version as the file"\ @@ -112,6 +105,7 @@ def loadIntoDataFrame(self, data): dlg.Destroy() return pointList + def onUpload(self, event): """Reads csv into pandas object @@ -143,7 +137,7 @@ def onUpload(self, event): self.parent.Raise() event.Skip() - def onTemplate(self, event): + def onDownloadTemplateButton(self, event): """ DataValues: Floats or -9999 (No data value) Date: --+ String @@ -160,28 +154,25 @@ def onTemplate(self, event): :param event: :return: """ - saveFileDialog = wx.FileDialog(self, "Save Bulk Insert Template", "", "", "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) - value = saveFileDialog.ShowModal() + + file_dialog = wx.FileDialog(self, "Save Bulk Insert Template", "", "", "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + value = file_dialog.ShowModal() if value == wx.ID_CANCEL: return - filepath = saveFileDialog.GetPath() - df = pd.DataFrame(columns=self.col) + filepath = file_dialog.GetPath() + df = pd.DataFrame(columns=self.columns) df.loc[0] = ['FLOAT|INT', 'YYYY-MM-DD', 'HH:MM:SS', 'INT', 'gt|nc|lt|nd|pnq', 'FLOAT', 'FLOAT', - 'String', 'String', 'String'] - df.loc[1] = ['-9999', '2005-06-29', '14:20:15', '-7', 'nc', "1.2", "1", "NULL", "NULL", "NULL"] + 'String', 'String'] + df.loc[1] = ['-9999', '2005-06-29', '14:20:15', '-7', 'nc', "1.2", "1", "NULL", "NULL"] df.to_csv(filepath, index=False) - - self.EndModal(0) # Denver - #self.Hide() - self.parent.Raise() + self.onClose(None) def onClose(self, event): - self.EndModal(0) # Denver - #self.Hide() + self.EndModal(0) self.parent.Raise() if __name__ == '__main__': app = wx.App(useBestVisual=True) - m = BulkInsert(None) + m = BulkInsertController(None) m.Show() app.MainLoop() diff --git a/odmtools/controller/NewFlagValuesController.py b/odmtools/controller/NewFlagValuesController.py new file mode 100644 index 0000000..a933af4 --- /dev/null +++ b/odmtools/controller/NewFlagValuesController.py @@ -0,0 +1,65 @@ +import wx +from odmtools.view.NewFlagValuesView import NewFlagValuesView +import odmtools.controller.olvAddPoint + + +class NewFlagValuesController(NewFlagValuesView): + def __init__(self, parent, series_service, qualifier_choice, record_service): + + NewFlagValuesView.__init__(self, parent) + self.parent = parent + self.series_service = series_service + self.qualifer_choice = qualifier_choice + self.record_service = record_service + self.__new_annotation = "New Annontation" + + if self.qualifer_choice: + annotations = self.series_service.get_all_annotations() + self.append_items_to_annotation(annotations) + + self.annotation_combo.Append(self.__new_annotation) + self.annotation_combo.SetSelection(0) + + self.cancel_button.Bind(wx.EVT_BUTTON, self.on_cancel) + self.Bind(wx.EVT_CLOSE, self.on_cancel) + self.ok_button.Bind(wx.EVT_BUTTON, self.on_ok) + self.MakeModal(True) + + def append_items_to_annotation(self, annotations): + if not isinstance(annotations, list): + print "type(annotations) must be list of annotations" + return + + for item in annotations: + self.annotation_combo.Append(str(item.AnnotationCode + ":" + item.AnnotationText)) + + def on_cancel(self, event): + self.MakeModal(False) + self.Destroy() + + if event: + event.Skip() + + def on_ok(self, event): + selection = self.annotation_combo.GetValue() + if selection == self.__new_annotation: + code = self.code_textbox.GetValue() + text = self.text_textbox.GetValue() + + annotation = self.series_service.create_annotation(code, text) + else: + code = selection.split(':')[0] + annotation = self.series_service.get_annotation_by_code(code) + self.record_service.flag(annotation.AnnotationID) + + if isinstance(self.parent, odmtools.controller.olvAddPoint.OLVAddPoint): + self.parent.refresh_annotations() + + self.on_cancel(event) + +if __name__ == '__main__': + app = wx.App(False) + controller = NewFlagValuesController(None, None, None, None) + controller.Show() + app.MainLoop() + diff --git a/odmtools/controller/WizardActionController.py b/odmtools/controller/WizardActionController.py new file mode 100644 index 0000000..29edf21 --- /dev/null +++ b/odmtools/controller/WizardActionController.py @@ -0,0 +1,55 @@ +import wx +from odmtools.view.WizardActionView import WizardActionView +from wx.wizard import WizardPageSimple +from odm2api.ODM2.models import Affiliations + + +# Should we rename this to Affiliations instead of actions? Action is created else where +class WizardActionController(WizardPageSimple): + def __init__(self, parent, affiliations): + WizardPageSimple.__init__(self, parent) + self.action_view = WizardActionView(self) + self.affiliations = affiliations + + master_sizer = wx.BoxSizer(wx.VERTICAL) + master_sizer.Add(self.action_view, 1, wx.EXPAND | wx.RIGHT, 0) + self.SetSizer(master_sizer) + + if self.affiliations is None: + return + # Populate table with the affiliations + self.populate_affiliations_table() + + def populate_affiliations_table(self): + """ + self.affiliations must be a list affiliations + :return: + """ + if not isinstance(self.affiliations, list): + return + if not len(self.affiliations) and not isinstance(self.affiliations[0], Affiliations): + return + + data = [] + for affiliation in self.affiliations: + data.append([ + affiliation.PersonObj.PersonFirstName + " " + affiliation.PersonObj.PersonLastName, + affiliation.OrganizationObj.OrganizationName + ]) + + columns = ["Person", "Organization"] + self.action_view.affiliations_table.set_columns(columns) + self.action_view.affiliations_table.set_table_content(data) + + def get_affiliation(self): + index = self.action_view.affiliations_table.GetFirstSelected() + return self.affiliations[index] + + + + +if __name__ == '__main__': + app = wx.App(False) + controller = WizardActionController(None, None) + controller.Show() + app.MainLoop() \ No newline at end of file diff --git a/odmtools/controller/WizardMethodController.py b/odmtools/controller/WizardMethodController.py new file mode 100644 index 0000000..2bf8d8a --- /dev/null +++ b/odmtools/controller/WizardMethodController.py @@ -0,0 +1,124 @@ +import wx +from wx.wizard import WizardPageSimple +from odmtools.view.WizardMethodView import WizardMethodView +from odm2api.ODM2.models import Methods as Method + + +class WizardMethodController(WizardPageSimple): + def __init__(self, parent, series_service, current_method=None): + WizardPageSimple.__init__(self, parent) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + + self.method_view = WizardMethodView(self) + main_sizer.Add(self.method_view, 1, wx.EXPAND | wx.RIGHT, -16) # Sufficient to hide the scroll bar + self.SetSizer(main_sizer) + + self.series_service = series_service + table_columns = ["ID", "Descriptions", "Link", "Code", "Type"] + self.cv_types = [] + self.method_view.existing_method_table.set_columns(table_columns) + + self.on_auto_radio(None) + + self.all_methods = [] + self.current_method_in_series = current_method # Not the same as the selected method in the table + self.__populate_table() + self.select_current_method() + self.method_view.method_type_combo.AppendItems(self.cv_types) + self.on_existing_method_radio(None) + + self.method_view.auto_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_auto_radio) + self.method_view.existing_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_method_radio) + self.method_view.create_method_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_method_radio) + + def select_current_method(self): + if self.current_method_in_series is None: + return + + if self.current_method_in_series not in self.all_methods: + return # the current method is not in the table + + index = self.all_methods.index(self.current_method_in_series) + self.method_view.existing_method_table.Select(index) + + def on_auto_radio(self, event): + self.method_view.existing_method_table.Disable() + self.enable_create_method_section(False) + + def on_existing_method_radio(self, event): + self.method_view.existing_method_table.Enable() + self.enable_create_method_section(False) + self.method_view.existing_method_table.SetFocus() + + def enable_create_method_section(self, active): + if not isinstance(active, bool): + raise Exception("active must be type bool") + + self.method_view.method_code_text_ctrl.Enable(active) + self.method_view.method_name_text_ctrl.Enable(active) + self.method_view.method_type_combo.Enable(active) + self.method_view.organization_combo.Enable(active) + self.method_view.method_link_text_ctrl.Enable(active) + self.method_view.description_text_ctrl.Enable(active) + + def on_create_method_radio(self, event): + self.method_view.existing_method_table.Disable() + self.enable_create_method_section(True) + + def __populate_table(self): + self.all_methods = self.series_service.get_all_methods() + data = [] + for meth in self.all_methods: + data.append([ + meth.MethodID, meth.MethodDescription, + meth.MethodLink, meth.MethodCode, + meth.MethodTypeCV + ]) + + if meth.MethodTypeCV not in self.cv_types: + self.cv_types.append(meth.MethodTypeCV) + + self.method_view.existing_method_table.set_table_content(data=data) + + def get_method(self): + if self.method_view.auto_method_radio.GetValue(): + return self.__auto_generate_a_method() + + if self.method_view.existing_method_radio.GetValue(): + return self.__select_existing_method() + + if self.method_view.create_method_radio.GetValue(): + return self.__create_new_method() + + return None + + def __auto_generate_a_method(self): + code = "odmtools" + method = self.series_service.get_method_by_code(method_code=code) + if method is None: + method = Method() + method.MethodCode = code + method.MethodDescription = "Values derived from ODM Tools Python" + return method + + def __select_existing_method(self): + index = self.method_view.existing_method_table.GetFirstSelected() + return self.all_methods[index] + + def __create_new_method(self): + code = self.method_view.method_code_text_ctrl.GetValue() + name = self.method_view.method_name_text_ctrl.GetValue() + typeCV = self.method_view.method_type_combo.GetValue() + # organization = self.method_view.organization_combo.GetValue() + link = self.method_view.method_link_text_ctrl.GetValue() + description = self.method_view.description_text_ctrl.GetValue() + + method = Method() + method.MethodCode = code + method.MethodName = name + method.MethodTypeCV = typeCV + method.MethodLink = link + method.MethodDescription = description + + return method diff --git a/odmtools/controller/WizardProcessLevelController.py b/odmtools/controller/WizardProcessLevelController.py new file mode 100644 index 0000000..9b8cd21 --- /dev/null +++ b/odmtools/controller/WizardProcessLevelController.py @@ -0,0 +1,95 @@ +import wx +from odmtools.view.WizardProcessLevelView import WizardProcessLevelView +from wx.wizard import WizardPageSimple +from odm2api.ODM2.models import ProcessingLevels + + +class WizardProcessLevelController(WizardPageSimple): + def __init__(self, parent, service_manager, current_processing_level=None): + WizardPageSimple.__init__(self, parent) + + self.service_manager = service_manager + main_sizer = wx.BoxSizer(wx.VERTICAL) + self.processing_level_view = WizardProcessLevelView(self) + main_sizer.Add(self.processing_level_view, 1, wx.EXPAND | wx.RIGHT, -16) + self.SetSizer(main_sizer) + + self.all_processing_level = [] + table_columns = ["Code", "Definition", "Explanation", "ID"] + self.processing_level_view.existing_process_table.set_columns(table_columns) + self.current_processing_level = current_processing_level + self.__fetch_data() + self.select_current_processing_level() + + self.processing_level_view.create_process_level_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_radio) + self.processing_level_view.existing_process_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_radio) + + def select_current_processing_level(self): + if self.current_processing_level is None: + return + + index = -1 + for i in range(len(self.all_processing_level)): + if self.all_processing_level[i].ProcessingLevelID == self.current_processing_level.ProcessingLevelID: + index = i + break + + if index >= 0: + self.processing_level_view.existing_process_table.Select(index) + + def on_create_radio(self, event): + self.processing_level_view.existing_process_table.Enable(False) + self.__set_create_proces_section(True) + + def on_existing_radio(self, event): + self.processing_level_view.existing_process_table.Enable(True) + self.__set_create_proces_section(False) + self.processing_level_view.existing_process_table.SetFocus() + + def __set_create_proces_section(self, active): + if not isinstance(active, bool): + raise Exception("activet must be type bool") + + self.processing_level_view.level_code_text_ctrl.Enable(active) + self.processing_level_view.definition_text_ctrl.Enable(active) + self.processing_level_view.explanation_text_ctrl.Enable(active) + + def __fetch_data(self): + series_service = self.service_manager.get_series_service() + self.all_processing_level = series_service.get_all_processing_levels() + + data = [] + for proc in self.all_processing_level: + data.append([ + proc.ProcessingLevelCode, + proc.Definition, + proc.Explanation, + proc.ProcessingLevelID + ]) + + self.processing_level_view.existing_process_table.set_table_content(data=data) + + def get_processing_level(self): + if self.processing_level_view.create_process_level_radio.GetValue(): + return self.__create_processing_level() + + if self.processing_level_view.existing_process_radio.GetValue(): + return self.__select_existing_processing_level() + + return None + + def __select_existing_processing_level(self): + index = self.processing_level_view.existing_process_table.GetFirstSelected() + return self.all_processing_level[index] + + def __create_processing_level(self): + code = self.processing_level_view.level_code_text_ctrl.GetValue() + definition = self.processing_level_view.definition_text_ctrl.GetValue() + explanation = self.processing_level_view.explanation_text_ctrl.GetValue() + + proc = ProcessingLevels() + proc.ProcessingLevelCode = code + proc.Definition = definition + proc.Explanation = explanation + + return proc diff --git a/odmtools/controller/WizardVariableController.py b/odmtools/controller/WizardVariableController.py new file mode 100644 index 0000000..709c26c --- /dev/null +++ b/odmtools/controller/WizardVariableController.py @@ -0,0 +1,132 @@ +import wx +from odmtools.view.WizardVariableView import WizardVariableView +from wx.wizard import WizardPageSimple +from odm2api.ODM2.models import Variables as Variable + + +class WizardVariableController(WizardPageSimple): + def __init__(self, parent, service_manager, current_variable): + WizardPageSimple.__init__(self, parent) + + self.service_manager = service_manager + self.current_variable = current_variable + self.all_variables = [] + + main_sizer = wx.BoxSizer(wx.VERTICAL) + self.variable_view = WizardVariableView(self) + main_sizer.Add(self.variable_view, 1, wx.EXPAND | wx.RIGHT, -16) + self.SetSizer(main_sizer) + + table_columns = ["Code", "Name", "Speciation", "DataType", "NoDataValue", "ID"] + self.variable_view.variable_table.set_columns(table_columns) + self.on_current_radio(None) + + self.__fetch_data() + self.select_current_variable() + + self.variable_view.current_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_current_radio) + self.variable_view.existing_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_existing_radio) + self.variable_view.create_variable_radio.Bind(wx.EVT_RADIOBUTTON, self.on_create_radio) + + def select_current_variable(self): + if self.current_variable is None: + return + + index = -1 + for i in range(len(self.all_variables)): + if self.all_variables[i].VariableID == self.current_variable.VariableID: + index = i + break + + if index >= 0: + self.variable_view.variable_table.Select(index) + + def on_current_radio(self, event): + self.variable_view.variable_table.Enable(False) + self.__enable_create_variable_section(False) + + if event: + event.Skip() + + def on_create_radio(self, event): + self.variable_view.variable_table.Enable(False) + self.__enable_create_variable_section(True) + + if event: + event.Skip() + + def on_existing_radio(self, event): + self.variable_view.variable_table.Enable(True) + self.__enable_create_variable_section(False) + self.variable_view.variable_table.SetFocus() + + if event: + event.Skip() + + def __enable_create_variable_section(self, active): + if not isinstance(active, bool): + raise Exception("active must be type bool") + + self.variable_view.variable_code_text_ctrl.Enable(active) + self.variable_view.variable_name_combo.Enable(active) + self.variable_view.variable_type_combo.Enable(active) + self.variable_view.no_data_value_text_ctrl.Enable(active) + self.variable_view.speciation_combo.Enable(active) + self.variable_view.definition_text_ctrl.Enable(active) + + def __fetch_data(self): + self.__populate_variable_table() + + series_service = self.service_manager.get_series_service() + name_list = [x.Term for x in series_service.get_variable_name_cvs()] + var_unit = [x.UnitsName for x in series_service.get_units()] + spec_list = [x.Term for x in series_service.get_speciation_cvs()] + + self.variable_view.variable_name_combo.AppendItems(name_list) + self.variable_view.speciation_combo.AppendItems(spec_list) + self.variable_view.variable_type_combo.AppendItems(var_unit) + + def __populate_variable_table(self): + series_serivce = self.service_manager.get_series_service() + self.all_variables = series_serivce.get_all_variables() + data = [] + for var in self.all_variables: + data.append([var.VariableCode, + var.VariableNameCV, + var.SpeciationCV, + var.VariableTypeCV, + var.NoDataValue, + var.VariableID]) + + self.variable_view.variable_table.set_table_content(data=data) + + def get_variable(self): + if self.variable_view.current_variable_radio.GetValue(): + return self.current_variable + + if self.variable_view.existing_variable_radio.GetValue(): + return self.__select_existing_variable() + + if self.variable_view.create_variable_radio.GetValue(): + return self.__create_new_variable() + + return None + + def __select_existing_variable(self): + index = self.variable_view.variable_table.GetFirstSelected() + return self.all_variables[index] + + def __create_new_variable(self): + v = Variable() + v.code = self.variable_view.variable_code_text_ctrl.GetValue() if self.variable_view.variable_code_text_ctrl.GetValue() <> "" else None + v.name = self.variable_view.variable_name_combo.GetValue() if self.variable_view.variable_name_combo.GetValue() <> "" else None + v.speciation = self.variable_view.speciation_combo.GetValue() if self.variable_view.speciation_combo.GetValue() <> "" else None + v.variable_unit = self.service_manager.get_series_service() + v.no_data_value = self.variable_view.no_data_value_text_ctrl.GetValue() if self.variable_view.no_data_value_text_ctrl.GetValue() <> "" else None + + return v + + + + + diff --git a/odmtools/controller/frmAddPoints.py b/odmtools/controller/frmAddPoints.py old mode 100755 new mode 100644 index b254734..604cfe8 --- a/odmtools/controller/frmAddPoints.py +++ b/odmtools/controller/frmAddPoints.py @@ -1,13 +1,16 @@ """Subclass of AddPoints, which is generated by wxFormBuilder.""" import datetime import wx -from odmtools.controller.frmBulkInsert import BulkInsert +from odmtools.controller.BulkInsertController import BulkInsertController import odmtools.view.clsAddPoints as clsAddPoints try: from agw import genericmessagedialog as GMD except ImportError: import wx.lib.agw.genericmessagedialog as GMD import logging +from odmtools.odmservices.edit_service import EditService +from odmtools.controller.logicEditTools import EditTools + logger =logging.getLogger('main') # Implementing AddPoints @@ -16,7 +19,7 @@ def __init__(self, parent, **kwargs): if 'recordService' in kwargs: self.recordService = kwargs['recordService'] clsAddPoints.AddPoints.__init__(self, parent, **kwargs) - self.frame = BulkInsert(self) + self.bulkInsertCtrl = BulkInsertController(self) def checkIfEditing(self): # Deleting a cell being edited doesn't finish editing @@ -32,8 +35,13 @@ def onAddBtn(self, event): """ self.checkIfEditing() self.olv.AddObject(self.olv.sampleRow()) + self.auto_size_table() event.Skip() + def auto_size_table(self): + for i in range(self.olv.GetColumnCount()): + self.olv.SetColumnWidth(col=i, width=wx.LIST_AUTOSIZE_USEHEADER) + def onClearAllBtn(self, event): """ @@ -89,12 +97,6 @@ def onDeleteBtn(self, event): event.Skip() def customRemove(self, object): - """ - - - :param object: - :return: - """ obj = self.olv.GetObjects() if isinstance(object, list): for x in object: @@ -104,18 +106,13 @@ def customRemove(self, object): self.olv.SetObjects(obj) def onUploadBtn(self, event): - """ - - :param event: - :return: - """ self.checkIfEditing() - if not self.frame.IsShown(): - self.frame.CenterOnParent() - self.frame.ShowModal() - self.frame.SetFocus() + if not self.bulkInsertCtrl.IsShown(): + self.bulkInsertCtrl.CenterOnParent() + self.bulkInsertCtrl.ShowModal() + self.bulkInsertCtrl.SetFocus() else: - self.frame.Hide() + self.bulkInsertCtrl.Hide() event.Skip() @@ -126,7 +123,7 @@ def onInfoBtn(self, event): :return: """ self.checkIfEditing() - + #todo fran: update this for odm2 info. message = "DataValue: FLOAT\n" \ "Date: YYYY-MM-DD\n" \ "Time: HH:MM:SS\n" \ @@ -144,19 +141,9 @@ def onInfoBtn(self, event): event.Skip() def onFinishedBtn(self, event): - """ - - :param event: - :return: - """ self.checkIfEditing() - #try: points, isIncorrect = self.parseTable() - #except: - # return - - message = "" if not points and not isIncorrect: #print "Leaving..." @@ -222,49 +209,37 @@ def onSelected(self, event): event.Skip() def parseTable(self): - """ - - :return: - """ - series = self.recordService.get_series() + site_id = series.FeatureActionObj.SamplingFeatureID + variable_id = series.VariableID + method_id = series.FeatureActionObj.ActionObj.MethodID + organization_id = series.FeatureActionObj.ActionObj.MethodObj.OrganizationID + process_id = series.ProcessingLevelID + objects = self.olv.GetObjects() isIncorrect = False points = [] - for i in objects: - if self.olv.isCorrect(i): - row = [None] * 10 - if i.valueAccuracy != "NULL": - row[1] = i.valueAccuracy - if i.offSetType != "NULL": - row[6] = i.offSetType - if i.qualifierCode != "NULL": - code = i.qualifierCode.split(':')[0] - q=self.recordService._edit_service.memDB.series_service.get_qualifier_by_code(code=code) - row[8] = q.id - if i.labSampleCode != "NULL": - row[9] = i.labSampleCode - - row[0] = i.dataValue - - dt = self.combineDateTime(i.date, i.time) - row[2] = dt - ## UTC Offset - row[3] = i.utcOffSet - ## Calculate UTC time based off the localdatetime and utcOffSet - row[4] = dt - datetime.timedelta(hours=int(i.utcOffSet)) - row[7] = i.censorCode - - row.extend([ - series.site_id, series.variable_id, series.method_id, - series.source_id, series.quality_control_level_id - ] - ) - - points.append(tuple(row)) + for point in objects: + if self.olv.isCorrect(point): + row = [None] * self.olv.GetColumnCount() + row[0] = point.dataValue + dt = self.combineDateTime(point.date, point.time) + row[1] = dt + row[2] = dt - datetime.timedelta(hours=int(point.utcOffSet)) # Calculates the time offset + row[3] = point.utcOffSet + row[4] = point.censorCode + row[5] = point.qualityCodeCV + row[6] = point.timeAggInterval + # row[7] = point.timeAggregationUnitID + row[7] = self.olv.cellEdit.timeAggretaionUnitChoices[point.timeAggregationUnitID] + row[8] = self.olv.cellEdit.qualifierChoices[ point.annotation] if point.annotation != 'NULL' else point.annotation + + row.extend([site_id, variable_id, method_id, organization_id, process_id]) + + points.append(row) else: isIncorrect = True @@ -296,6 +271,11 @@ def onCheck(self, event): class Example(wx.Frame): def __init__(self, parent, *args, **kwargs): wx.Frame.__init__(self, parent, *args, **kwargs) + # edit_service = EditService(series_id=2, connection_string="pymysql+mysql://ODM:ODM123!!@jws.uwrl.usu.edu/odm2" ) + # edit_tool = EditTools(script="", edit_service=edit_service, connection_string="abc123") + # + # kwargs["record_service"] = edit_tool + m = AddPoints(parent) m.Show() diff --git a/odmtools/controller/frmCreateVariable.py b/odmtools/controller/frmCreateVariable.py index 5b49153..056cce6 100644 --- a/odmtools/controller/frmCreateVariable.py +++ b/odmtools/controller/frmCreateVariable.py @@ -2,7 +2,7 @@ import wx from odmtools.view.clsCreateVariable import clsCreateVariable -from odmtools.odmdata import Variable +# from odmtools.odmdata import Variable # Implementing clsCreateVariable class frmCreateVariable(clsCreateVariable): @@ -87,26 +87,30 @@ def OnBtnCreateButton(self, event): def createVariable(self): - v = Variable() - v.code = self.txtVarCode.GetValue() if self.txtVarCode.GetValue() <> u'' else None - v.name = self.cbVarName.GetValue() if self.cbVarName.GetValue() <> u'' else None - v.speciation = self.cbSpeciation.GetValue() if self.cbSpeciation.GetValue() <> u'' else None - v.variable_unit = self.series_service.get_unit_by_name( self.cbVarUnits.GetValue()) - v.variable_unit_id = v.variable_unit.id + code = self.txtVarCode.GetValue() if self.txtVarCode.GetValue() <> u'' else None + name = self.cbVarName.GetValue() if self.cbVarName.GetValue() <> u'' else None + speciation = self.cbSpeciation.GetValue() if self.cbSpeciation.GetValue() <> u'' else None - v.sample_medium = self.cbSampleMedium.GetValue() if self.cbSampleMedium.GetValue() <> u'' else None - v.value_type = self.cbValueType.GetValue() if self.cbValueType.GetValue() <> u'' else None - v.is_regular = self.cbIsRegular.GetValue() if self.cbIsRegular.GetValue() <> u'' else None - v.time_support = self.txtTSValue.GetValue() if self.txtTSValue.GetValue() <> u'' else None + variable_unit = self.series_service.get_unit_by_name( self.cbVarUnits.GetValue()) + variable_unit_id = variable_unit.id - v.time_unit = self.series_service.get_unit_by_name(self.cbTSUnits.GetValue()) - v.time_unit_id = v.time_unit.id + sample_medium = self.cbSampleMedium.GetValue() if self.cbSampleMedium.GetValue() <> u'' else None + value_type = self.cbValueType.GetValue() if self.cbValueType.GetValue() <> u'' else None + is_regular = self.cbIsRegular.GetValue() if self.cbIsRegular.GetValue() <> u'' else None + time_support = self.txtTSValue.GetValue() if self.txtTSValue.GetValue() <> u'' else None - v.data_type = self.cbDataType.GetValue() if self.cbDataType.GetValue() <> u'' else None - v.general_category = self.cbGenCat.GetValue() if self.cbGenCat.GetValue() <> u'' else None - v.no_data_value = self.txtNoDV.GetValue() if self.txtNoDV.GetValue() <> u'' else None + time_unit = self.series_service.get_unit_by_name(self.cbTSUnits.GetValue()) + time_unit_id = time_unit.id + + data_type = self.cbDataType.GetValue() if self.cbDataType.GetValue() <> u'' else None + general_category = self.cbGenCat.GetValue() if self.cbGenCat.GetValue() <> u'' else None + no_data_value = self.txtNoDV.GetValue() if self.txtNoDV.GetValue() <> u'' else None + v = self.series_service.create_variable( + code, name, speciation, variable_unit_id, sample_medium, + value_type, is_regular, time_support, time_unit_id, data_type, + general_category, no_data_value) return v def OnBtnCancelButton(self, event): diff --git a/odmtools/controller/frmDBConfig.py b/odmtools/controller/frmDBConfig.py old mode 100755 new mode 100644 index 6ef3005..4d99268 --- a/odmtools/controller/frmDBConfig.py +++ b/odmtools/controller/frmDBConfig.py @@ -93,10 +93,10 @@ def validateInput(self, conn_dict): wx.MessageBox(message, 'Test Connection', wx.OK) else: #TODO add error message if user cannont connect to the database ( not using VPN) but the db is still 1.1.1) - if not (self.service_manager.get_db_version(conn_dict)): - message = "Cannot connect to the database" - else: - message = "This connection is not a 1.1.1 Database" + + + message = "Cannot connect to the database" + wx.MessageBox(message, 'Error Occurred', wx.OK | wx.ICON_ERROR) return False @@ -109,27 +109,52 @@ def validateInput(self, conn_dict): return True - - # Returns a dictionary of the database values entered in the form def getFieldValues(self): - conn_dict = {} + if self.isFormEmpty(): + return {} + conn_dict = {} conn_dict['engine'] = self.choices[self.cbDatabaseType.GetValue()] conn_dict['user'] = self.txtUser.GetValue() conn_dict['password'] = self.txtPass.GetValue() conn_dict['address'] = self.txtServer.GetValue() conn_dict['db'] = self.txtDBName.GetValue() + conn_dict['version']= self.cbVersion.GetValue() return conn_dict + def isFormEmpty(self): + """ + If any of the entries are empty return true + :return: + """ + if not self.cbDatabaseType.GetValue().strip(): + return True + + if not self.txtUser.GetValue().strip(): + return True + + if not self.txtPass.GetValue().strip(): + return True + + if not self.txtServer.GetValue().strip(): + return True + + if not self.txtDBName.GetValue().strip(): + return True + + return False + def set_field_values(self): conn = self.service_manager.is_valid_connection() if conn is not None: self.txtServer.SetValue(conn['address']) self.txtDBName.SetValue(conn['db']) self.txtUser.SetValue(conn['user']) + self.cbVersion.SetValue(str(conn['version'])) for k, v in self.choices.iteritems(): if v == conn['engine']: self.cbDatabaseType.SetValue(k) + diff --git a/odmtools/controller/frmDataTable.py b/odmtools/controller/frmDataTable.py index ce6f008..be6ccbe 100644 --- a/odmtools/controller/frmDataTable.py +++ b/odmtools/controller/frmDataTable.py @@ -13,17 +13,14 @@ def __init__(self, parent, **kwargs): self.memDB = None DataTable.__init__(self, parent, **kwargs) + def init(self, memDB): + self.memDB = memDB + self.olvDataTable.init(self.memDB) - def init_publishers(self): Publisher.subscribe(self.onChangeSelection, "changeTableSelection") Publisher.subscribe(self.onRefresh, "refreshTable") Publisher.subscribe(self.olvDataTable.onDeselectAll, "deselectAllDataTable") - def init(self, memDB): - self.memDB = memDB - self.olvDataTable.init(self.memDB) - self.init_publishers() - def onItemSelected(self, event): pass diff --git a/odmtools/controller/frmLinearDrift.py b/odmtools/controller/frmLinearDrift.py index 86cb131..e991f8d 100644 --- a/odmtools/controller/frmLinearDrift.py +++ b/odmtools/controller/frmLinearDrift.py @@ -17,10 +17,14 @@ def OnBtnOKButton(self, event): if not result: dial = wx.MessageDialog( None, "Linear drift can only be performed on one continuous data selection. \nPlease modify your selection and try again.", "Bad Input", wx.OK) dial.ShowModal() - except Exception as e: + except ValueError as e: dial = wx.MessageDialog(None, "Unable to convert value to float %s" % e, "Bad Input", wx.OK | wx.ICON_ERROR) dial.ShowModal() + except Exception as e: + dial = wx.MessageDialog(None, "Unable perform linear drift %s" % e, "Bad Input", + wx.OK | wx.ICON_ERROR) + dial.ShowModal() self.Close() def OnBtnCancelButton(self, event): diff --git a/odmtools/controller/frmSeriesSelector.py b/odmtools/controller/frmSeriesSelector.py index bc090b3..bac9e29 100755 --- a/odmtools/controller/frmSeriesSelector.py +++ b/odmtools/controller/frmSeriesSelector.py @@ -1,635 +1,1284 @@ -import os -import wx -import logging - -from wx.lib.pubsub import pub as Publisher -from odmtools.lib.ObjectListView.Filter import TextSearch, Chain -import odmtools.gui.frmQueryBuilder as frmQueryBuilder - -from odmtools.common.logger import LoggerTool -from odmtools.odmdata import MemoryDatabase -from odmtools.view import clsSeriesSelector - -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -__author__ = 'Jacob' - -class FrmSeriesSelector(clsSeriesSelector.ClsSeriesSelector): - """ - - """ - def __init__(self, *args, **kwargs): - """ - - """ - - self.taskserver = kwargs.pop("taskserver") - self.memDB = kwargs.pop("memdb") - self.pnlPlot = kwargs.pop("plot") - - clsSeriesSelector.ClsSeriesSelector.__init__(self, *args, **kwargs) - - def initPubSub(self): - #Publisher.subscribe(self.onEditButton, ("selectEdit")) - Publisher.subscribe(self.refreshSeries, "refreshSeries") - - def resetDB(self, series_service): - """ - - :param series_service: - :return: - """ - - if not self.rbAll.GetValue(): - #self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) - wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) - self.rbAll.SetValue(True) - - #####INIT DB Connection - self.series_service = series_service - #self.refreshSeries() - self.cbVariables.Clear() - self.cbSites.Clear() - - self.siteList = None - self.varList = None - - self.initTableSeries() - self.initSVBoxes() - self.Layout() - - def initTableSeries(self): - """Set up columns and objects to be used in the objectlistview to be visible in the series selector - - :return: - """ - try: - self.memDB.set_series_service(self.series_service) - - object = self.series_service.get_all_series() - - if object: - self.tblSeries.SetObjects(object) - else: - self.tblSeries.SetObjects(None) - - except AttributeError as e: - logger.error(e) - #self.tblSeries.SaveObject(object) - - def refreshTableSeries(self, db): - """ Refreshes the objectlistview to include newly saved database series and preserve which series was 'checked' - for plotting/editing - - :return: - """ - chcklist= self.tblSeries.GetCheckedObjects() - - - self.memDB.set_series_service(db) - object = self.series_service.get_all_series() - #checkedObjs = self.tblSeries.GetCheckedObjects() - idList = [x.id for x in self.tblSeries.modelObjects] - - for x in object: - if x.id not in idList: - self.tblSeries.AddObject(x) - - - for c in chcklist: - self.tblSeries.SetCheckState(c, True) - - - #for x in checkedObjs: - # super(FastObjectListView, self.tblSeries).SetCheckState(x, True) - - def refreshSeries(self): - """ - - :return: - """ - chcklist= self.tblSeries.GetCheckedObjects() - self.series_service = None - - self.series_service = self.parent.Parent.createService() - #self.refreshTableSeries(self.dbservice) - self.resetDB(self.series_service) - - for c in chcklist: - for val in self.tblSeries.GetObjects(): - if c == val: - self.tblSeries.SetCheckState(val, True) - break - - logger.debug("Repopulate Series Selector") - - - - def initSVBoxes(self): - """ - - :return: - """ - - self.site_code = None - self.variable_code = None - - #####INIT drop down boxes for Simple Filter - try: - self.siteList = self.series_service.get_used_sites() - for site in self.siteList: - self.cbSites.Append(site.code + '-' + site.name) - self.cbSites.SetSelection(0) - self.site_code = self.siteList[0].code - - self.varList = self.series_service.get_used_variables() - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - except AttributeError as e: - logger.error(e) - - def OnTableRightDown(self, event): - """Right click down menu - - :param event: - :return: - """ - - # build pop-up menu for right-click display - self.selectedIndex = event.m_itemIndex - #self.selectedID = self.tableSeries.getColumnText(event.m_itemIndex, 1) - self.selectedID = self.tblSeries.GetSelectedObject().id - - # print self.selectedID - popup_edit_series = wx.NewId() - popup_plot_series = wx.NewId() - popup_export_data = wx.NewId() - popup_series_refresh = wx.NewId() - popup_clear_selected = wx.NewId() - - popup_export_metadata = wx.NewId() - popup_select_all = wx.NewId() - popup_select_none = wx.NewId() - - popup_menu = wx.Menu() - plotItem = popup_menu.Append(popup_plot_series, 'Plot') - editItem = popup_menu.Append(popup_edit_series, 'Edit') - - self.Bind(wx.EVT_MENU, self.onRightPlot, plotItem) - # TODO @jmeline needs to fix edit, it doesn't unedit when a plot is being edited - self.Bind(wx.EVT_MENU, self.onRightEdit, editItem) - # TODO @jmeline will refresh and clear selected as an enhancement - #self.Bind(wx.EVT_MENU, self.onRightRefresh, popup_menu.Append(popup_series_refresh, 'Refresh')) - #self.Bind(wx.EVT_MENU, self.onRightClearSelected, popup_menu.Append(popup_series_refresh, 'Clear Selected')) - - popup_menu.AppendSeparator() - self.Bind(wx.EVT_MENU, self.onRightExData, popup_menu.Append(popup_export_data, 'Export Data')) - self.Bind(wx.EVT_MENU, self.onRightExMeta, popup_menu.Append(popup_export_metadata, 'Export MetaData')) - - if self.isEditing: - popup_menu.Enable(popup_edit_series, False) - - self.tblSeries.PopupMenu(popup_menu) - event.Skip() - - def onPaneChanged(self, event=None): - #if event: - # print 'wx.EVT_COLLAPSIBLEPANE_CHANGED: %s\n' % event.Collapsed - self.Layout() - - def onRbAdvancedRadiobutton(self, event): - """ - - :param event: - :return: - """ - - self.cpnlSimple.Collapse(True) - self.Layout() - series_filter = frmQueryBuilder.frmQueryBuilder(self) - self.filterlist = series_filter.ShowModal() - event.Skip() - - def onRbAllRadiobutton(self, event): - """ - - :param event: - :return: - """ - - if self.checkSite.GetValue() or self.checkVariable.GetValue(): - self.checkSite.SetValue(False) - self.checkVariable.SetValue(False) - - #logger.debug("onRbAllRadioButton called! ") - self.cpnlSimple.Collapse(True) - self.Layout() - self.setFilter() - event.Skip() - - def onRbSimpleRadiobutton(self, event): - """ - - :param event: - :return: - """ - - self.cpnlSimple.Expand() - self.Layout() - - - ## - if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): - self.setFilter() - return - - self.setFilter(self.site_code, self.variable_code) - - event.Skip() - - def onRightPlot(self, event): - """ - - :param event: - :return: - """ - object = self.tblSeries.GetSelectedObject() - self.tblSeries.ToggleCheck(object) - self.onReadyToPlot(event) - event.Skip() - - def onRightEdit(self, event): - """ - - :param event: - :return: - """ - Publisher.sendMessage(("selectEdit"), event=event) - if self.isEditing: - Publisher.sendMessage("toggleEdit", checked=True) - event.Skip() - - # allows user to right-click refresh the Series Selector - def onRightRefresh(self, event): - """ - - :param event: - :return: - """ - self.refreshSeries() - event.Skip() - - def onRightClearSelected(self, event): - """ - - :param event: - :return: - """ - event.Skip() - - - - def onRightExData(self, event): - """ - - :param event: - :return: - """ - dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.csv", wx.SAVE | wx.OVERWRITE_PROMPT) - if dlg.ShowModal() == wx.ID_OK: - full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) - - #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) - series_id = self.tblSeries.GetSelectedObject().id - self.export_service.export_series_data(series_id, full_path, True, True, True, True, True, True, True) - self.Close() - - dlg.Destroy() - - event.Skip() - - def onRightExMeta(self, event): - """ - - :param event: - :return: - """ - dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.xml", wx.SAVE | wx.OVERWRITE_PROMPT) - if dlg.ShowModal() == wx.ID_OK: - full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) - - self.selectedIndex = self.tblSeries.GetSelectedObject().id - #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) - #print "series_id", series_id - - self.export_service.export_series_metadata(self.selectedIndex, full_path) - self.Close() - - dlg.Destroy() - - event.Skip() - - def onCbSitesCombobox(self, event): - """ - - :param event: - :return: - """ - - if self.checkSite.GetValue(): - self.site_code = self.siteList[event.GetSelection()].code - self.varList = self.series_service.get_variables_by_site_code(self.site_code) - - self.cbVariables.Clear() - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - - if (self.checkSite.GetValue() and not self.checkVariable.GetValue()): - self.variable_code = None - - self.setFilter(site_code=self.site_code, var_code=self.variable_code) - event.Skip() - - def onCbVariablesCombobox(self, event): - """ - - :param event: - :return: - """ - - if self.checkVariable.GetValue(): - self.variable_code = self.varList[event.GetSelection()].code - if (not self.checkSite.GetValue() and self.checkVariable.GetValue()): - self.site_code = None - self.setFilter(site_code=self.site_code, var_code=self.variable_code) - event.Skip() - - def siteAndVariables(self): - """ - - :return: - """ - self.site_code = self.siteList[self.cbSites.Selection].code - - self.cbVariables.Clear() - self.varList = self.series_service.get_variables_by_site_code(self.site_code) - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - - try: - self.variable_code = self.varList[self.cbVariables.Selection].code - self.setFilter(site_code=self.site_code, var_code=self.variable_code) - self.cbVariables.Enabled = True - self.cbSites.Enabled = True - except IndexError as i: - logger.error(i) - pass - - def siteOnly(self): - """ - - :return: - """ - self.cbVariables.Enabled = False - self.cbSites.Enabled = True - self.variable_code = None - - self.site_code = self.siteList[self.cbSites.Selection].code - self.setFilter(site_code=self.site_code) - - def variableOnly(self): - """ - - :return: - """ - self.site_code = None - self.cbVariables.Clear() - self.varList = self.series_service.get_used_variables() - for var in self.varList: - self.cbVariables.Append(var.code + '-' + var.name) - self.cbVariables.SetSelection(0) - self.cbSites.Enabled = False - self.cbVariables.Enabled = True - - self.variable_code = self.varList[0].code - - self.setFilter(var_code=self.variable_code) - - def onCheck(self, event): - """ - - :param event: - :return: - """ - # self.tableSeries.DeleteAllItems() - if self.rbAll.GetValue(): - #logger.debug("Called!") - self.rbAll.SetValue(False) - self.rbSimple.SetValue(True) - - if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): - self.setFilter() - self.cbSites.Enabled = False - self.cbVariables.Enabled = False - - elif self.checkSite.GetValue(): - if self.checkVariable.GetValue(): - self.siteAndVariables() - else: - self.siteOnly() - else: - if self.checkVariable.GetValue(): - self.variableOnly() - else: - self.cbSites.Enabled = False - self.cbVariables.Enabled = False - event.Skip() - - def setFilter(self, site_code='', var_code='', advfilter=''): - """ - - :param site_code: - :param var_code: - :param advfilter: - :return: - """ - if site_code and var_code: - self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[2:6],text=site_code) - self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:8],text=var_code) - self.tblSeries.SetFilter(Chain(self.siteFilter, self.variableFilter)) - elif site_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[2:6], text=site_code)) - elif var_code: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:8], text=var_code)) - elif advfilter: - self.tblSeries.SetFilter(advfilter) - else: - self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:1])) - self.tblSeries.RepopulateList() - - - def onReadyToPlot(self, event): - """Plots a series selected from the series selector - - :param event: EVT_OVL_CHECK_EVENT type - """ - logger.debug("Starting to Plot") - - checkedCount = len(self.tblSeries.GetCheckedObjects()) - - Publisher.sendMessage("EnablePlotButtons", plot=0, isActive=(checkedCount > 0)) - - logger.debug("Obtain object") - try: - object = event.object - except Exception as e : - object = self.tblSeries.GetSelectedObject() - - self.drawPlot(object) - logger.debug("refreshing...") - self.Refresh() - - logger.debug("Finish Plotting") - - def drawPlot(self, object): - - if not self.tblSeries.IsChecked(object): - Publisher.sendMessage("removePlot", seriesID=object.id) - Publisher.sendMessage("updateCursor", deselectedObject=object) - - else: - logger.debug("Obtained object, entering addplot") - self.pnlPlot.addPlot(self.memDB, object.id) - Publisher.sendMessage("updateCursor", selectedObject=object) - - #from meliae import scanner - #scanner.dump_all_objects("plot_plotting.dat") - - def getSelectedObject(self, event): - """Capture the currently selected Object to be used for editing - - :param event: wx.EVT_LIST_ITEM_FOCUSED type - """ - - logger.debug("Selecting object from Series Catalog") - - object = event.GetEventObject() - editingObject = object.innerList[object.FocusedItem] - - self.tblSeries.currentlySelectedObject = editingObject - - ## update Cursor - if self.parent.Parent.pnlPlot._seriesPlotInfo: - if self.parent.Parent.pnlPlot._seriesPlotInfo.isPlotted(editingObject.id): - #print "Updating Cursor", editingObject.id - Publisher.sendMessage("updateCursor", selectedObject=editingObject) - - def onBtnClear(self, event): - logger.debug("Clearing all selected objects from Series Catalog") - chcklist = self.tblSeries.GetCheckedObjects() - - self.tblSeries.RefreshObject(self.tblSeries.editingObject) - for c in chcklist: - self.tblSeries.SetCheckState(c, False) - Publisher.sendMessage("removeMultPlot", seriesIDs= chcklist) - Publisher.sendMessage("updateCursor", deselectedObject=object) - #self.drawPlot(c) - self.Refresh() - - def onReadyToEdit(self): - """Choose a series to edit from the series selector - - :return: - """ - - ovl = self.tblSeries - - object = ovl.currentlySelectedObject - if object is None: - # # Select the first one - if len(ovl.modelObjects) == 0: - logger.fatal("There are no model objects available to edit") - raise Exception() - object = ovl.modelObjects[0] - - if len(ovl.GetCheckedObjects()) <= ovl.allowedLimit: - - if object not in ovl.GetCheckedObjects(): - ovl.ToggleCheck(object) - - - - # logger.debug("Initializing DataTable") - # - # tasks = [("dataTable", self.memDB.conn)] - # self.taskserver.setTasks(tasks) - # self.taskserver.processTasks() - - self.isEditing = True - ovl.editingObject = object - ovl.RefreshObject(ovl.editingObject) - - - return True, object.id#, self.memDB - else: - isSelected = False - logger.debug("series was not checked") - val_2 = wx.MessageBox("Visualization is limited to 6 series.", "Can't add plot", - wx.OK | wx.ICON_INFORMATION) - - - self.isEditing = False - ovl.editingObject = None - return False, object.id#, self.memDB - - def stopEdit(self): - """When edit button is untoggled, the editing feature closes - :return: - """ - - self.isEditing = False - chcklist= self.tblSeries.GetCheckedObjects() - - self.tblSeries.RefreshObject(self.tblSeries.editingObject) - for c in chcklist: - self.tblSeries.SetCheckState(c, True) - - self.memDB.stopEdit() - - - def isEditing(self): - """ - - :return: - """ - return self.isEditing - - - def _rowFormatter(self, listItem, object): - """Handles the formatting of rows for object list view - :param: wx.ListCtrl listitem - :param: ModelObject object - - :rtype: None - """ - ''' - if self.tblSeries.editingObject and \ - object.id == self.tblSeries.editingObject.id: - #listItem.SetTextColour(wx.Colour(255, 25, 112)) - # font type: wx.DEFAULT, wx.DECORATIVE, wx.ROMAN, wx.SCRIPT, wx.SWISS, wx.MODERN - # slant: wx.NORMAL, wx.SLANT or wx.ITALIC - # weight: wx.NORMAL, wx.LIGHT or wx.BOLD - #font1 = wx.Font(10, wx.SWISS, wx.ITALIC, wx.NORMAL) - # use additional fonts this way ... - #font1 = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Comic Sans MS') - listItem.SetFont( - wx.Font(9, family=wx.DEFAULT, weight=wx.BOLD, style=wx.ITALIC)) - else: - listItem.SetTextColour(wx.Colour()) - listItem.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False)) - ''' + +import os +import wx +import logging + +from wx.lib.pubsub import pub as Publisher +from odmtools.lib.ObjectListView.Filter import TextSearch, Chain +import odmtools.gui.frmQueryBuilder as frmQueryBuilder + +from odmtools.common.logger import LoggerTool +from odmtools.odmdata import MemoryDatabase +from odmtools.odmdata import returnDict +from odmtools.view import clsSeriesSelector + +# tool = LoggerTool() +# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) +logger =logging.getLogger('main') + +__author__ = 'Jacob' + +class FrmSeriesSelector(clsSeriesSelector.ClsSeriesSelector): + """ + + """ + def __init__(self, *args, **kwargs): + """ + + """ + + self.taskserver = kwargs.pop("taskserver") + self.memDB = kwargs.pop("memdb") + self.pnlPlot = kwargs.pop("plot") + + + clsSeriesSelector.ClsSeriesSelector.__init__(self, *args, **kwargs) + + def initPubSub(self): + #Publisher.subscribe(self.onEditButton, ("selectEdit")) + Publisher.subscribe(self.refreshSeries, "refreshSeries") + + def resetDB(self, series_service): + """ + + :param series_service: + :return: + """ + + if not self.rbAll.GetValue(): + #self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) + wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) + self.rbAll.SetValue(True) + + #####INIT DB Connection + self.series_service = series_service + #self.refreshSeries() + self.cbVariables.Clear() + self.cbSites.Clear() + + self.siteList = None + self.varList = None + + self.initTableSeries() + self.initSVBoxes() + self.Layout() + + def initTableSeries(self): + """Set up columns and objects to be used in the objectlistview to be visible in the series selector + + :return: + """ + try: + self.memDB.set_series_service(self.series_service) + + object = self.series_service.get_all_series() + #cols = object[0].__dict__.keys() + cols = returnDict() + self.tblSeries._buildColumns(cols) + if object: + self.tblSeries.SetObjects(object) + else: + self.tblSeries.SetObjects(None) + + except AttributeError as e: + logger.error(e) + #self.tblSeries.SaveObject(object) + + def refreshTableSeries(self, db): + """ Refreshes the objectlistview to include newly saved database series and preserve which series was 'checked' + for plotting/editing + + :return: + """ + chcklist= self.tblSeries.GetCheckedObjects() + + + self.memDB.set_series_service(db) + object = self.series_service.get_all_series() + #checkedObjs = self.tblSeries.GetCheckedObjects() + + idList = [x.id for x in self.tblSeries.modelObjects] + + for x in object: + if x.id not in idList: + self.tblSeries.AddObject(x) + + + for c in chcklist: + self.tblSeries.SetCheckState(c, True) + + + #for x in checkedObjs: + # super(FastObjectListView, self.tblSeries).SetCheckState(x, True) + + def refreshSeries(self): + """ + + :return: + """ + chcklist= self.tblSeries.GetCheckedObjects() + self.series_service = None + + self.series_service = self.parent.Parent.createService() + #self.refreshTableSeries(self.dbservice) + self.resetDB(self.series_service) + + for c in chcklist: + for val in self.tblSeries.GetObjects(): + if c == val: + self.tblSeries.SetCheckState(val, True) + break + + logger.debug("Repopulate Series Selector") + + + + def initSVBoxes(self): + """ + + :return: + """ + + self.site_code = None + self.variable_code = None + + #####INIT drop down boxes for Simple Filter + try: + self.siteList = self.series_service.get_used_sites() + for site in self.siteList: + self.cbSites.Append("%s-%s"%(site.SamplingFeatureCode, site.SamplingFeatureName)) + self.cbSites.SetSelection(0) + self.site_code = self.siteList[0].SamplingFeatureCode + + self.varList = self.series_service.get_used_variables() + for var in self.varList: + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) + self.cbVariables.SetSelection(0) + except AttributeError as e: + logger.error(e) + + def OnTableRightDown(self, event): + """Right click down menu + + :param event: + :return: + """ + + # build pop-up menu for right-click display + self.selectedIndex = event.m_itemIndex + #self.selectedID = self.tableSeries.getColumnText(event.m_itemIndex, 1) + self.selectedID = self.tblSeries.GetSelectedObject().ResultID + + # print self.selectedID + popup_edit_series = wx.NewId() + popup_plot_series = wx.NewId() + popup_export_data = wx.NewId() + popup_series_refresh = wx.NewId() + popup_clear_selected = wx.NewId() + + popup_export_metadata = wx.NewId() + popup_select_all = wx.NewId() + popup_select_none = wx.NewId() + + popup_menu = wx.Menu() + plotItem = popup_menu.Append(popup_plot_series, 'Plot') + editItem = popup_menu.Append(popup_edit_series, 'Edit') + + self.Bind(wx.EVT_MENU, self.onRightPlot, plotItem) + # TODO @jmeline needs to fix edit, it doesn't unedit when a plot is being edited + self.Bind(wx.EVT_MENU, self.onRightEdit, editItem) + # TODO @jmeline will refresh and clear selected as an enhancement + + + + popup_menu.AppendSeparator() + self.Bind(wx.EVT_MENU, self.onRightExData, popup_menu.Append(popup_export_data, 'Export Data')) + self.Bind(wx.EVT_MENU, self.onRightExMeta, popup_menu.Append(popup_export_metadata, 'Export MetaData')) + + if self.isEditing: + popup_menu.Enable(popup_edit_series, False) + + self.tblSeries.PopupMenu(popup_menu) + event.Skip() + + def onPaneChanged(self, event=None): + #if event: + # print 'wx.EVT_COLLAPSIBLEPANE_CHANGED: %s\n' % event.Collapsed + self.Layout() + + def onRbAdvancedRadiobutton(self, event): + """ + + :param event: + :return: + """ + + self.cpnlSimple.Collapse(True) + self.Layout() + series_filter = frmQueryBuilder.frmQueryBuilder(self) + self.filterlist = series_filter.ShowModal() + event.Skip() + + def onRbAllRadiobutton(self, event): + """ + + :param event: + :return: + """ + + if self.checkSite.GetValue() or self.checkVariable.GetValue(): + self.checkSite.SetValue(False) + self.checkVariable.SetValue(False) + + #logger.debug("onRbAllRadioButton called! ") + self.cpnlSimple.Collapse(True) + self.Layout() + self.setFilter() + event.Skip() + + def onRbSimpleRadiobutton(self, event): + """ + + :param event: + :return: + """ + + self.cpnlSimple.Expand() + self.Layout() + + + ## + if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): + self.setFilter() + return + + self.setFilter(self.site_code, self.variable_code) + + event.Skip() + + def onRightPlot(self, event): + """ + + :param event: + :return: + """ + object = self.tblSeries.GetSelectedObject() + self.tblSeries.ToggleCheck(object) + self.onReadyToPlot(event) + event.Skip() + + def onRightEdit(self, event): + """ + + :param event: + :return: + """ + Publisher.sendMessage(("selectEdit"), event=event) + if self.isEditing: + Publisher.sendMessage("toggleEdit", checked=True) + event.Skip() + + # allows user to right-click refresh the Series Selector + def onRightRefresh(self, event): + """ + + :param event: + :return: + """ + self.refreshSeries() + event.Skip() + + def onRightClearSelected(self, event): + """ + + :param event: + :return: + """ + event.Skip() + + + + def onRightExData(self, event): + """ + + :param event: + :return: + """ + dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.csv", wx.SAVE | wx.OVERWRITE_PROMPT) + if dlg.ShowModal() == wx.ID_OK: + full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) + + #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) + series_id = self.tblSeries.GetSelectedObject().ResultID + self.export_service.export_series_data(series_id, full_path, True, True, True, True, True, True, True) + self.Close() + + dlg.Destroy() + + event.Skip() + + def onRightExMeta(self, event): + """ + + :param event: + :return: + """ + dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.xml", wx.SAVE | wx.OVERWRITE_PROMPT) + if dlg.ShowModal() == wx.ID_OK: + full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) + + self.selectedIndex = self.tblSeries.GetSelectedObject().id + #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) + #print "series_id", series_id + + self.export_service.export_series_metadata(self.selectedIndex, full_path) + self.Close() + + dlg.Destroy() + + event.Skip() + + def onCbSitesCombobox(self, event): + """ + + :param event: + :return: + """ + + if self.checkSite.GetValue(): + self.site_code = self.siteList[event.GetSelection()].SamplingFeatureCode + self.varList = self.series_service.get_variables_by_site_code(self.site_code) + + self.cbVariables.Clear() + for var in self.varList: + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) + self.cbVariables.SetSelection(0) + + if (self.checkSite.GetValue() and not self.checkVariable.GetValue()): + self.variable_code = None + + self.setFilter(site_code=self.site_code, var_code=self.variable_code) + event.Skip() + + def onCbVariablesCombobox(self, event): + """ + + :param event: + :return: + """ + + if self.checkVariable.GetValue(): + self.variable_code = self.varList[event.GetSelection()].VariableCode + if (not self.checkSite.GetValue() and self.checkVariable.GetValue()): + self.site_code = None + self.setFilter(site_code=self.site_code, var_code=self.variable_code) + event.Skip() + + def siteAndVariables(self): + """ + + :return: + """ + self.site_code = self.siteList[self.cbSites.Selection].SamplingFeatureCode + + self.cbVariables.Clear() + self.varList = self.series_service.get_variables_by_site_code(self.site_code) + for var in self.varList: + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) + self.cbVariables.SetSelection(0) + + try: + self.variable_code = self.varList[self.cbVariables.Selection].VariableCode + self.setFilter(site_code=self.site_code, var_code=self.variable_code) + self.cbVariables.Enabled = True + self.cbSites.Enabled = True + except IndexError as i: + logger.error(i) + pass + + def siteOnly(self): + """ + + :return: + """ + self.cbVariables.Enabled = False + self.cbSites.Enabled = True + self.variable_code = None + + self.site_code = self.siteList[self.cbSites.Selection].SamplingFeatureCode + self.setFilter(site_code=self.site_code) + + def variableOnly(self): + """ + + :return: + """ + self.site_code = None + self.cbVariables.Clear() + self.varList = self.series_service.get_used_variables() + for var in self.varList: + self.cbVariables.Append("%s-%s"%(var.VariableCode, var.VariableNameCV)) + self.cbVariables.SetSelection(0) + self.cbSites.Enabled = False + self.cbVariables.Enabled = True + + self.variable_code = self.varList[0].VariableCode + + self.setFilter(var_code=self.variable_code) + + def onCheck(self, event): + """ + + :param event: + :return: + """ + # self.tableSeries.DeleteAllItems() + if self.rbAll.GetValue(): + #logger.debug("Called!") + self.rbAll.SetValue(False) + self.rbSimple.SetValue(True) + + if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): + self.setFilter() + self.cbSites.Enabled = False + self.cbVariables.Enabled = False + + elif self.checkSite.GetValue(): + if self.checkVariable.GetValue(): + self.siteAndVariables() + else: + self.siteOnly() + else: + if self.checkVariable.GetValue(): + self.variableOnly() + else: + self.cbSites.Enabled = False + self.cbVariables.Enabled = False + event.Skip() + + def setFilter(self, site_code='', var_code='', advfilter=''): + """ + + :param site_code: + :param var_code: + :param advfilter: + :return: + """ + if site_code and var_code: + self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns,text=site_code) + self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns,text=var_code) + self.tblSeries.SetFilter(Chain(self.siteFilter, self.variableFilter)) + elif site_code: + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns, text=site_code)) + elif var_code: + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns, text=var_code)) + elif advfilter: + self.tblSeries.SetFilter(advfilter) + else: + self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns)) + self.tblSeries.RepopulateList() + + + def onReadyToPlot(self, event): + """Plots a series selected from the series selector + + :param event: EVT_OVL_CHECK_EVENT type + """ + logger.debug("Starting to Plot") + + checkedCount = len(self.tblSeries.GetCheckedObjects()) + + Publisher.sendMessage("EnablePlotButtons", plot=0, isActive=(checkedCount > 0)) + + logger.debug("Obtain object") + try: + object = event.object + except Exception as e : + object = self.tblSeries.GetSelectedObject() + + self.drawPlot(object) + logger.debug("refreshing...") + self.Refresh() + logger.debug("Finish Plotting") + + def drawPlot(self, object): + + if not self.tblSeries.IsChecked(object): + Publisher.sendMessage("removePlot", seriesID=object.ResultID) + Publisher.sendMessage("updateCursor", deselectedObject=object) + + else: + logger.debug("Obtained object, entering addplot") + self.pnlPlot.addPlot(self.memDB, object.ResultID) + Publisher.sendMessage("updateCursor", selectedObject=object) + + logger.debug("refreshing...") + self.Refresh() + + logger.debug("Finish Plotting") + + + #from meliae import scanner + #scanner.dump_all_objects("plot_plotting.dat") + + def getSelectedObject(self, event): + """Capture the currently selected Object to be used for editing + + :param event: wx.EVT_LIST_ITEM_FOCUSED type + """ + + logger.debug("Selecting object from Series Catalog") + + object = event.GetEventObject() + editingObject = object.innerList[object.FocusedItem] + + self.tblSeries.currentlySelectedObject = editingObject + + ## update Cursor + if self.parent.Parent.pnlPlot._seriesPlotInfo: + if self.parent.Parent.pnlPlot._seriesPlotInfo.isPlotted(editingObject.ResultID): + #print "Updating Cursor", editingObject.id + Publisher.sendMessage("updateCursor", selectedObject=editingObject) + + def onBtnClear(self, event): + logger.debug("Clearing all selected objects from Series Catalog") + chcklist = self.tblSeries.GetCheckedObjects() + self.tblSeries.RefreshObject(self.tblSeries.editingObject) + for c in chcklist: + self.tblSeries.SetCheckState(c, False) + Publisher.sendMessage("removeMultPlot", seriesIDs=chcklist) + Publisher.sendMessage("updateCursor", deselectedObject=object) + # self.drawPlot(c) + self.Refresh() + + def onReadyToEdit(self): + """Choose a series to edit from the series selector + + :return: + """ + + ovl = self.tblSeries + + object = ovl.currentlySelectedObject + if object is None: + # # Select the first one + if len(ovl.modelObjects) == 0: + logger.fatal("There are no model objects available to edit") + raise Exception() + object = ovl.modelObjects[0] + + if len(ovl.GetCheckedObjects()) <= ovl.allowedLimit: + + if object not in ovl.GetCheckedObjects(): + ovl.ToggleCheck(object) + + + + # logger.debug("Initializing DataTable") + # + # tasks = [("dataTable", self.memDB.conn)] + # self.taskserver.setTasks(tasks) + # self.taskserver.processTasks() + + self.isEditing = True + ovl.editingObject = object + ovl.RefreshObject(ovl.editingObject) + + + return True, object.ResultID#, self.memDB + else: + isSelected = False + logger.debug("series was not checked") + val_2 = wx.MessageBox("Visualization is limited to 6 series.", "Can't add plot", + wx.OK | wx.ICON_INFORMATION) + + + self.isEditing = False + ovl.editingObject = None + return False, object.id#, self.memDB + + def stopEdit(self): + """When edit button is untoggled, the editing feature closes + :return: + """ + + self.isEditing = False + chcklist= self.tblSeries.GetCheckedObjects() + + self.tblSeries.RefreshObject(self.tblSeries.editingObject) + for c in chcklist: + self.tblSeries.SetCheckState(c, True) + + self.memDB.stopEdit() + + + def isEditing(self): + """ + + :return: + """ + return self.isEditing + + + def _rowFormatter(self, listItem, object): + """Handles the formatting of rows for object list view + :param: wx.ListCtrl listitem + :param: ModelObject object + + :rtype: None + """ + ''' + if self.tblSeries.editingObject and \ + object.id == self.tblSeries.editingObject.id: + #listItem.SetTextColour(wx.Colour(255, 25, 112)) + # font type: wx.DEFAULT, wx.DECORATIVE, wx.ROMAN, wx.SCRIPT, wx.SWISS, wx.MODERN + # slant: wx.NORMAL, wx.SLANT or wx.ITALIC + # weight: wx.NORMAL, wx.LIGHT or wx.BOLD + #font1 = wx.Font(10, wx.SWISS, wx.ITALIC, wx.NORMAL) + # use additional fonts this way ... + #font1 = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Comic Sans MS') + listItem.SetFont( + wx.Font(9, family=wx.DEFAULT, weight=wx.BOLD, style=wx.ITALIC)) + else: + listItem.SetTextColour(wx.Colour()) + listItem.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False)) + ''' + + +# +# import os +# import wx +# import logging +# +# from wx.lib.pubsub import pub as Publisher +# from odmtools.lib.ObjectListView.Filter import TextSearch, Chain +# import odmtools.gui.frmQueryBuilder as frmQueryBuilder +# +# from odmtools.common.logger import LoggerTool +# from odmtools.odmdata import MemoryDatabase +# from odmtools.view import clsSeriesSelector +# +# # tool = LoggerTool() +# # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) +# logger =logging.getLogger('main') +# +# __author__ = 'Jacob' +# +# class FrmSeriesSelector(clsSeriesSelector.ClsSeriesSelector): +# """ +# +# """ +# def __init__(self, *args, **kwargs): +# """ +# +# """ +# +# self.taskserver = kwargs.pop("taskserver") +# self.memDB = kwargs.pop("memdb") +# self.pnlPlot = kwargs.pop("plot") +# +# clsSeriesSelector.ClsSeriesSelector.__init__(self, *args, **kwargs) +# +# def initPubSub(self): +# #Publisher.subscribe(self.onEditButton, ("selectEdit")) +# Publisher.subscribe(self.refreshSeries, "refreshSeries") +# +# def resetDB(self, series_service): +# """ +# +# :param series_service: +# :return: +# """ +# +# if not self.rbAll.GetValue(): +# #self.GetEventHandler().ProcessEvent(wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) +# wx.PostEvent(self.GetEventHandler(), wx.PyCommandEvent(wx.EVT_RADIOBUTTON.typeId, self.rbAll.Id)) +# self.rbAll.SetValue(True) +# +# #####INIT DB Connection +# self.series_service = series_service +# #self.refreshSeries() +# self.cbVariables.Clear() +# self.cbSites.Clear() +# +# self.siteList = None +# self.varList = None +# +# self.initTableSeries() +# self.initSVBoxes() +# self.Layout() +# +# def initTableSeries(self): +# """Set up columns and objects to be used in the objectlistview to be visible in the series selector +# +# :return: +# """ +# try: +# self.memDB.set_series_service(self.series_service) +# +# object = self.series_service.get_all_series() +# +# if object: +# self.tblSeries.SetObjects(object) +# else: +# self.tblSeries.SetObjects(None) +# +# except AttributeError as e: +# logger.error(e) +# #self.tblSeries.SaveObject(object) +# +# def refreshTableSeries(self, db): +# """ Refreshes the objectlistview to include newly saved database series and preserve which series was 'checked' +# for plotting/editing +# +# :return: +# """ +# chcklist= self.tblSeries.GetCheckedObjects() +# +# +# self.memDB.set_series_service(db) +# object = self.series_service.get_all_series() +# #checkedObjs = self.tblSeries.GetCheckedObjects() +# idList = [x.id for x in self.tblSeries.modelObjects] +# +# for x in object: +# if x.id not in idList: +# self.tblSeries.AddObject(x) +# +# +# for c in chcklist: +# self.tblSeries.SetCheckState(c, True) +# +# +# #for x in checkedObjs: +# # super(FastObjectListView, self.tblSeries).SetCheckState(x, True) +# +# def refreshSeries(self): +# """ +# +# :return: +# """ +# chcklist= self.tblSeries.GetCheckedObjects() +# self.series_service = None +# +# self.series_service = self.parent.Parent.createService() +# #self.refreshTableSeries(self.dbservice) +# self.resetDB(self.series_service) +# +# for c in chcklist: +# for val in self.tblSeries.GetObjects(): +# if c == val: +# self.tblSeries.SetCheckState(val, True) +# break +# +# logger.debug("Repopulate Series Selector") +# +# +# +# def initSVBoxes(self): +# """ +# +# :return: +# """ +# +# self.site_code = None +# self.variable_code = None +# +# #####INIT drop down boxes for Simple Filter +# try: +# self.siteList = self.series_service.get_used_sites() +# for site in self.siteList: +# self.cbSites.Append(site.code + '-' + site.name) +# self.cbSites.SetSelection(0) +# self.site_code = self.siteList[0].code +# +# self.varList = self.series_service.get_used_variables() +# for var in self.varList: +# self.cbVariables.Append(var.code + '-' + var.name) +# self.cbVariables.SetSelection(0) +# except AttributeError as e: +# logger.error(e) +# +# def OnTableRightDown(self, event): +# """Right click down menu +# +# :param event: +# :return: +# """ +# +# # build pop-up menu for right-click display +# self.selectedIndex = event.m_itemIndex +# #self.selectedID = self.tableSeries.getColumnText(event.m_itemIndex, 1) +# self.selectedID = self.tblSeries.GetSelectedObject().id +# +# # print self.selectedID +# popup_edit_series = wx.NewId() +# popup_plot_series = wx.NewId() +# popup_export_data = wx.NewId() +# popup_series_refresh = wx.NewId() +# popup_clear_selected = wx.NewId() +# +# popup_export_metadata = wx.NewId() +# popup_select_all = wx.NewId() +# popup_select_none = wx.NewId() +# +# popup_menu = wx.Menu() +# plotItem = popup_menu.Append(popup_plot_series, 'Plot') +# editItem = popup_menu.Append(popup_edit_series, 'Edit') +# +# self.Bind(wx.EVT_MENU, self.onRightPlot, plotItem) +# # TODO @jmeline needs to fix edit, it doesn't unedit when a plot is being edited +# self.Bind(wx.EVT_MENU, self.onRightEdit, editItem) +# # TODO @jmeline will refresh and clear selected as an enhancement +# #self.Bind(wx.EVT_MENU, self.onRightRefresh, popup_menu.Append(popup_series_refresh, 'Refresh')) +# #self.Bind(wx.EVT_MENU, self.onRightClearSelected, popup_menu.Append(popup_series_refresh, 'Clear Selected')) +# +# popup_menu.AppendSeparator() +# self.Bind(wx.EVT_MENU, self.onRightExData, popup_menu.Append(popup_export_data, 'Export Data')) +# self.Bind(wx.EVT_MENU, self.onRightExMeta, popup_menu.Append(popup_export_metadata, 'Export MetaData')) +# +# if self.isEditing: +# popup_menu.Enable(popup_edit_series, False) +# +# self.tblSeries.PopupMenu(popup_menu) +# event.Skip() +# +# def onPaneChanged(self, event=None): +# #if event: +# # print 'wx.EVT_COLLAPSIBLEPANE_CHANGED: %s\n' % event.Collapsed +# self.Layout() +# +# def onRbAdvancedRadiobutton(self, event): +# """ +# +# :param event: +# :return: +# """ +# +# self.cpnlSimple.Collapse(True) +# self.Layout() +# series_filter = frmQueryBuilder.frmQueryBuilder(self) +# self.filterlist = series_filter.ShowModal() +# event.Skip() +# +# def onRbAllRadiobutton(self, event): +# """ +# +# :param event: +# :return: +# """ +# +# if self.checkSite.GetValue() or self.checkVariable.GetValue(): +# self.checkSite.SetValue(False) +# self.checkVariable.SetValue(False) +# +# #logger.debug("onRbAllRadioButton called! ") +# self.cpnlSimple.Collapse(True) +# self.Layout() +# self.setFilter() +# event.Skip() +# +# def onRbSimpleRadiobutton(self, event): +# """ +# +# :param event: +# :return: +# """ +# +# self.cpnlSimple.Expand() +# self.Layout() +# +# +# ## +# if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): +# self.setFilter() +# return +# +# self.setFilter(self.site_code, self.variable_code) +# +# event.Skip() +# +# def onRightPlot(self, event): +# """ +# +# :param event: +# :return: +# """ +# object = self.tblSeries.GetSelectedObject() +# self.tblSeries.ToggleCheck(object) +# self.onReadyToPlot(event) +# event.Skip() +# +# def onRightEdit(self, event): +# """ +# +# :param event: +# :return: +# """ +# Publisher.sendMessage(("selectEdit"), event=event) +# if self.isEditing: +# Publisher.sendMessage("toggleEdit", checked=True) +# event.Skip() +# +# # allows user to right-click refresh the Series Selector +# def onRightRefresh(self, event): +# """ +# +# :param event: +# :return: +# """ +# self.refreshSeries() +# event.Skip() +# +# def onRightClearSelected(self, event): +# """ +# +# :param event: +# :return: +# """ +# event.Skip() +# +# +# +# def onRightExData(self, event): +# """ +# +# :param event: +# :return: +# """ +# dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.csv", wx.SAVE | wx.OVERWRITE_PROMPT) +# if dlg.ShowModal() == wx.ID_OK: +# full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) +# +# #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) +# series_id = self.tblSeries.GetSelectedObject().id +# self.export_service.export_series_data(series_id, full_path, True, True, True, True, True, True, True) +# self.Close() +# +# dlg.Destroy() +# +# event.Skip() +# +# def onRightExMeta(self, event): +# """ +# +# :param event: +# :return: +# """ +# dlg = wx.FileDialog(self, "Choose a save location", '', "", "*.xml", wx.SAVE | wx.OVERWRITE_PROMPT) +# if dlg.ShowModal() == wx.ID_OK: +# full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) +# +# self.selectedIndex = self.tblSeries.GetSelectedObject().id +# #series_id = self.tableSeries.getColumnText(self.selectedIndex, 1) +# #print "series_id", series_id +# +# self.export_service.export_series_metadata(self.selectedIndex, full_path) +# self.Close() +# +# dlg.Destroy() +# +# event.Skip() +# +# def onCbSitesCombobox(self, event): +# """ +# +# :param event: +# :return: +# """ +# +# if self.checkSite.GetValue(): +# self.site_code = self.siteList[event.GetSelection()].code +# self.varList = self.series_service.get_variables_by_site_code(self.site_code) +# +# self.cbVariables.Clear() +# for var in self.varList: +# self.cbVariables.Append(var.code + '-' + var.name) +# self.cbVariables.SetSelection(0) +# +# if (self.checkSite.GetValue() and not self.checkVariable.GetValue()): +# self.variable_code = None +# +# self.setFilter(site_code=self.site_code, var_code=self.variable_code) +# event.Skip() +# +# def onCbVariablesCombobox(self, event): +# """ +# +# :param event: +# :return: +# """ +# +# if self.checkVariable.GetValue(): +# self.variable_code = self.varList[event.GetSelection()].code +# if (not self.checkSite.GetValue() and self.checkVariable.GetValue()): +# self.site_code = None +# self.setFilter(site_code=self.site_code, var_code=self.variable_code) +# event.Skip() +# +# def siteAndVariables(self): +# """ +# +# :return: +# """ +# self.site_code = self.siteList[self.cbSites.Selection].code +# +# self.cbVariables.Clear() +# self.varList = self.series_service.get_variables_by_site_code(self.site_code) +# for var in self.varList: +# self.cbVariables.Append(var.code + '-' + var.name) +# self.cbVariables.SetSelection(0) +# +# try: +# self.variable_code = self.varList[self.cbVariables.Selection].code +# self.setFilter(site_code=self.site_code, var_code=self.variable_code) +# self.cbVariables.Enabled = True +# self.cbSites.Enabled = True +# except IndexError as i: +# logger.error(i) +# pass +# +# def siteOnly(self): +# """ +# +# :return: +# """ +# self.cbVariables.Enabled = False +# self.cbSites.Enabled = True +# self.variable_code = None +# +# self.site_code = self.siteList[self.cbSites.Selection].code +# self.setFilter(site_code=self.site_code) +# +# def variableOnly(self): +# """ +# +# :return: +# """ +# self.site_code = None +# self.cbVariables.Clear() +# self.varList = self.series_service.get_used_variables() +# for var in self.varList: +# self.cbVariables.Append(var.code + '-' + var.name) +# self.cbVariables.SetSelection(0) +# self.cbSites.Enabled = False +# self.cbVariables.Enabled = True +# +# self.variable_code = self.varList[0].code +# +# self.setFilter(var_code=self.variable_code) +# +# def onCheck(self, event): +# """ +# +# :param event: +# :return: +# """ +# # self.tableSeries.DeleteAllItems() +# if self.rbAll.GetValue(): +# #logger.debug("Called!") +# self.rbAll.SetValue(False) +# self.rbSimple.SetValue(True) +# +# if not self.checkSite.GetValue() and not self.checkVariable.GetValue(): +# self.setFilter() +# self.cbSites.Enabled = False +# self.cbVariables.Enabled = False +# +# elif self.checkSite.GetValue(): +# if self.checkVariable.GetValue(): +# self.siteAndVariables() +# else: +# self.siteOnly() +# else: +# if self.checkVariable.GetValue(): +# self.variableOnly() +# else: +# self.cbSites.Enabled = False +# self.cbVariables.Enabled = False +# event.Skip() +# +# def setFilter(self, site_code='', var_code='', advfilter=''): +# """ +# +# :param site_code: +# :param var_code: +# :param advfilter: +# :return: +# """ +# if site_code and var_code: +# self.siteFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[2:6],text=site_code) +# self.variableFilter = TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:8],text=var_code) +# self.tblSeries.SetFilter(Chain(self.siteFilter, self.variableFilter)) +# elif site_code: +# self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[2:6], text=site_code)) +# elif var_code: +# self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[3:8], text=var_code)) +# elif advfilter: +# self.tblSeries.SetFilter(advfilter) +# else: +# self.tblSeries.SetFilter(TextSearch(self.tblSeries, columns=self.tblSeries.columns[0:1])) +# self.tblSeries.RepopulateList() +# +# +# def onReadyToPlot(self, event): +# """Plots a series selected from the series selector +# +# :param event: EVT_OVL_CHECK_EVENT type +# """ +# logger.debug("Starting to Plot") +# +# checkedCount = len(self.tblSeries.GetCheckedObjects()) +# +# Publisher.sendMessage("EnablePlotButtons", plot=0, isActive=(checkedCount > 0)) +# +# logger.debug("Obtain object") +# try: +# object = event.object +# except Exception as e : +# object = self.tblSeries.GetSelectedObject() +# +# self.drawPlot(object) +# logger.debug("refreshing...") +# self.Refresh() +# +# logger.debug("Finish Plotting") +# +# def drawPlot(self, object): +# +# if not self.tblSeries.IsChecked(object): +# Publisher.sendMessage("removePlot", seriesID=object.id) +# Publisher.sendMessage("updateCursor", deselectedObject=object) +# +# else: +# logger.debug("Obtained object, entering addplot") +# self.pnlPlot.addPlot(self.memDB, object.id) +# Publisher.sendMessage("updateCursor", selectedObject=object) +# +# #from meliae import scanner +# #scanner.dump_all_objects("plot_plotting.dat") +# +# def getSelectedObject(self, event): +# """Capture the currently selected Object to be used for editing +# +# :param event: wx.EVT_LIST_ITEM_FOCUSED type +# """ +# +# logger.debug("Selecting object from Series Catalog") +# +# object = event.GetEventObject() +# editingObject = object.innerList[object.FocusedItem] +# +# self.tblSeries.currentlySelectedObject = editingObject +# +# ## update Cursor +# if self.parent.Parent.pnlPlot._seriesPlotInfo: +# if self.parent.Parent.pnlPlot._seriesPlotInfo.isPlotted(editingObject.id): +# #print "Updating Cursor", editingObject.id +# Publisher.sendMessage("updateCursor", selectedObject=editingObject) +# +# def onBtnClear(self, event): +# logger.debug("Clearing all selected objects from Series Catalog") +# chcklist = self.tblSeries.GetCheckedObjects() +# +# self.tblSeries.RefreshObject(self.tblSeries.editingObject) +# for c in chcklist: +# self.tblSeries.SetCheckState(c, False) +# Publisher.sendMessage("removeMultPlot", seriesIDs= chcklist) +# Publisher.sendMessage("updateCursor", deselectedObject=object) +# #self.drawPlot(c) +# self.Refresh() +# +# def onReadyToEdit(self): +# """Choose a series to edit from the series selector +# +# :return: +# """ +# +# ovl = self.tblSeries +# +# object = ovl.currentlySelectedObject +# if object is None: +# # # Select the first one +# if len(ovl.modelObjects) == 0: +# logger.fatal("There are no model objects available to edit") +# raise Exception() +# object = ovl.modelObjects[0] +# +# if len(ovl.GetCheckedObjects()) <= ovl.allowedLimit: +# +# if object not in ovl.GetCheckedObjects(): +# ovl.ToggleCheck(object) +# +# +# +# # logger.debug("Initializing DataTable") +# # +# # tasks = [("dataTable", self.memDB.conn)] +# # self.taskserver.setTasks(tasks) +# # self.taskserver.processTasks() +# +# self.isEditing = True +# ovl.editingObject = object +# ovl.RefreshObject(ovl.editingObject) +# +# +# return True, object.id#, self.memDB +# else: +# isSelected = False +# logger.debug("series was not checked") +# val_2 = wx.MessageBox("Visualization is limited to 6 series.", "Can't add plot", +# wx.OK | wx.ICON_INFORMATION) +# +# +# self.isEditing = False +# ovl.editingObject = None +# return False, object.id#, self.memDB +# +# def stopEdit(self): +# """When edit button is untoggled, the editing feature closes +# :return: +# """ +# +# self.isEditing = False +# chcklist= self.tblSeries.GetCheckedObjects() +# +# self.tblSeries.RefreshObject(self.tblSeries.editingObject) +# for c in chcklist: +# self.tblSeries.SetCheckState(c, True) +# +# self.memDB.stopEdit() +# +# +# def isEditing(self): +# """ +# +# :return: +# """ +# return self.isEditing +# +# +# def _rowFormatter(self, listItem, object): +# """Handles the formatting of rows for object list view +# :param: wx.ListCtrl listitem +# :param: ModelObject object +# +# :rtype: None +# """ +# ''' +# if self.tblSeries.editingObject and \ +# object.id == self.tblSeries.editingObject.id: +# #listItem.SetTextColour(wx.Colour(255, 25, 112)) +# # font type: wx.DEFAULT, wx.DECORATIVE, wx.ROMAN, wx.SCRIPT, wx.SWISS, wx.MODERN +# # slant: wx.NORMAL, wx.SLANT or wx.ITALIC +# # weight: wx.NORMAL, wx.LIGHT or wx.BOLD +# #font1 = wx.Font(10, wx.SWISS, wx.ITALIC, wx.NORMAL) +# # use additional fonts this way ... +# #font1 = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL, False, u'Comic Sans MS') +# listItem.SetFont( +# wx.Font(9, family=wx.DEFAULT, weight=wx.BOLD, style=wx.ITALIC)) +# else: +# listItem.SetTextColour(wx.Colour()) +# listItem.SetFont(wx.Font(9, wx.SWISS, wx.NORMAL, wx.NORMAL, False)) +# ''' +# >>>>>>> master diff --git a/odmtools/controller/logicCellEdit.py b/odmtools/controller/logicCellEdit.py old mode 100755 new mode 100644 index f2b11ed..1ad5267 --- a/odmtools/controller/logicCellEdit.py +++ b/odmtools/controller/logicCellEdit.py @@ -1,48 +1,63 @@ -""" - ADD Point Cell Editor Logic -""" from collections import OrderedDict import datetime - import wx import wx.combo from wx.lib import masked -from odmtools.gui.frmFlagValues import frmFlagValues -from odmtools.lib.ObjectListView import CellEditor +from odmtools.controller.NewFlagValuesController import NewFlagValuesController __author__ = 'Jacob' #### Options #### utcOffSetBounds = (-12, 12) NULL = "NULL" -NEW = "[New Qualifier]" +NEW = "[New Annotation]" class CellEdit(): def __init__(self, parent, serviceManager, recordService): self.parent = parent self.recordService = recordService - if serviceManager: - self.serviceManager = serviceManager - self.cv_service = serviceManager.get_cv_service() - self.series_service = serviceManager.get_series_service() - offsetChoices = OrderedDict((x.description, x.id) for x in - self.cv_service.get_offset_type_cvs()) - self.offSetTypeChoices = [NULL] + offsetChoices.keys() - - labChoices = OrderedDict((x.lab_sample_code, x.id) for x in self.cv_service.get_samples()) - - self.censorCodeChoices = [NULL] + [x.term for x in self.cv_service.get_censor_code_cvs()] - self.labSampleChoices = [NULL] + labChoices.keys() - - self.qualifierChoices = OrderedDict((x.code + ':' + x.description, x.id) - for x in self.series_service.get_all_qualifiers() if x.code and x.description) - self.qualifierCodeChoices = [NULL] + self.qualifierChoices.keys() + [NEW] - + self.serviceManager = serviceManager + if self.serviceManager: + self.series_service = self.serviceManager.get_series_service() + self.annotationChoices = self.fetch_annotations() + self.censorCodeChoices = self.fetchCensorCodeChoices() + self.qualityCodeChoices = self.fetchQualityCodeChoices() + self.timeAggregationInterval = -1 + self.timeAggretaionUnitChoices = self.fetchTimeUnitChoices() else: self.censorCodeChoices = [NULL] + ['SampleCensorCode1'] + ['SampleCensorCode2'] + ['SampleCensorCode3'] self.labSampleChoices = [NULL] + ['SampleLabSample1'] + ['SampleLabSample2'] + ['SampleLabSample3'] self.offSetTypeChoices = [NULL] + ['SampleOffsetType1'] + ['SampleOffsetType2'] + ['SampleOffsetType3'] - self.qualifierCodeChoices = [NULL] + ['SampleQualifierCode1'] + ['SampleQualifierCode2'] + ['SampleQualifierCode3'] + self.annotationChoices = [NULL] + ['SampleAnnotation1'] + ['SampleAnnotation2'] + ['SampleAnnotation3'] + + def fetch_annotations(self): + self.qualifierChoices = OrderedDict((x.AnnotationCode + ':' + x.AnnotationText, x.AnnotationID) + for x in self.series_service.get_all_qualifiers() if x.AnnotationCode and x.AnnotationText) + qualifierCodeChoices = [NULL] + self.qualifierChoices.keys() + [NEW] + return qualifierCodeChoices + + def fetchCensorCodeChoices(self): + if not self.serviceManager: + return [NULL] + + series_service = self.serviceManager.get_series_service() + return [NULL] + [x.Name for x in series_service.get_censor_code_cvs()] + + def fetchQualityCodeChoices(self): + """ + :return: type(list + """ + if not self.serviceManager: + return [NULL] + + series_service = self.serviceManager.get_series_service() + return [NULL] + [x.Name for x in series_service.get_quality_code()] + + def fetchTimeUnitChoices(self): + if not self.serviceManager: + return [NULL] + units = self.series_service.read.getUnits(type='time') + return {unit.UnitsName:unit.UnitsID for unit in units} """ -------------------- @@ -158,32 +173,9 @@ def imgGetterUTCOFFset(self, point): return "error" - def imgGetterValueAcc(self, point): - """ - """ - value = point.valueAccuracy - point.validValueAcc = False - if not value: - return "error" - - if value == NULL: - point.validValueAcc = True - return "check" - if isinstance(value, basestring): - for type in [int, float]: - try: - value = type(value) - if isinstance(value, type): - point.validValueAcc = True - return "check" - except ValueError: - continue - return "error" def imgGetterOffSetType(self, point): - """ - """ point.validOffSetType = False if not point.offSetType in self.offSetTypeChoices: return "error" @@ -191,9 +183,6 @@ def imgGetterOffSetType(self, point): return "check" def imgGetterOffSetValue(self, point): - """ - """ - point.validOffSetValue = False if point.offSetValue == NULL: point.validOffSetValue = True @@ -216,25 +205,30 @@ def imgGetterOffSetValue(self, point): return "check" return "error" + def imgGetterQualityCode(self, point): + point.validQualityCode = False + if not point.qualityCodeCV in self.qualityCodeChoices or point.qualityCodeCV == NULL: + return "error" + point.validQualityCode = True + return "check" - def imgGetterQualifierCode(self, point): - """ - """ + def imgGetterTimeAggregationInterval(self, point): + point.validTimeAggInterval = False - point.validQualifierCode = False - if not point.qualifierCode in self.qualifierCodeChoices: + if point.timeAggInterval == NULL: return "error" - point.validQualifierCode = True + + point.validTimeAggInterval = True return "check" - def imgGetterLabSampleCode(self, point): - """ - """ + def imgGetterTimeAggregationUnit(self, point): + point.validTimeAggUnit = False - point.validLabSampleCode = False - if not point.labSampleCode in self.labSampleChoices: + if not point.timeAggregationUnitID in self.timeAggretaionUnitChoices or point.timeAggregationUnitID == NULL: return "error" - point.validLabSampleCode = True + + point.validTimeAggUnit = True + return "check" """ @@ -262,39 +256,24 @@ def valueSetterUTCOffset(self, point, newValue): point.utcOffSet = newValue - """ ------------------------ Custom String Converters ------------------------ """ def strConverterDataValue(self, value): - """ - """ - try: return str(value) except Exception as e: return str(NULL) def strConverterLocalTime(self, time): - """Required Element - - :param time: - :return: - """ - return unicode(time) def strConverterUTCOffset(self, value): - """ - """ - return str(value) def strConverterOffSetValue(self, value): - """ - """ try: return str(value) except UnicodeEncodeError: @@ -307,110 +286,66 @@ def strConverterOffSetValue(self, value): """ def localTimeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - - # odcb = masked.TimeCtrl(olv, fmt24hr=True) odcb = TimePicker(olv) odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb def dateEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ odcb = DatePicker(olv) odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb def offSetTypeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - odcb = CustomComboBox(olv, choices=self.offSetTypeChoices, style=wx.CB_READONLY) - # OwnerDrawnComboxBoxes don't generate EVT_CHAR so look for keydown instead odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb - def qualifierCodeEditor(self, olv, rowIndex, subItemIndex): - """ - - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - def cbHandler(event): - """ - :param event: - :type wx.EVT_COMBOBOX: - """ - - if event.GetEventObject().Value == NEW: - dlg = frmFlagValues(self.parent, self.cv_service, self.qualifierChoices, isNew=True) - - value = dlg.ShowModal() - if value == wx.ID_OK and dlg.selectedValue: - self.qualifierCodeChoices.insert(0, dlg.selectedValue) - event.GetEventObject().SetItems(self.qualifierCodeChoices) - print event.GetEventObject().GetValue() - print type(event.GetEventObject()) - event.GetEventObject().SetValue(dlg.selectedValue) - print event.GetEventObject().GetValue() - #dlg.Destroy() - - try: - self.qualifierChoices = OrderedDict((x.code + ':' + x.description, x.id) - for x in self.cv_service.get_all_qualifiers() if x.code and x.description) - self.qualifierCodeChoices = [NULL] + self.qualifierChoices.keys() + [NEW] - except: - pass - odcb = CustomComboBox(olv, choices=self.qualifierCodeChoices, style=wx.CB_READONLY) + def censorCodeEditor(self, olv, rowIndex, subItemIndex): + odcb = CustomComboBox(olv, choices=self.censorCodeChoices, style=wx.CB_READONLY) # OwnerDrawnComboxBoxes don't generate EVT_CHAR so look for keydown instead odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) - odcb.Bind(wx.EVT_COMBOBOX, cbHandler) return odcb - def censorCodeEditor(self, olv, rowIndex, subItemIndex): - """ + def valueDateTimeEditor(self, olv, rowIndex, subItemIndex): + odcb = DatePicker(olv) + odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + return odcb - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ - odcb = CustomComboBox(olv, choices=self.censorCodeChoices, style=wx.CB_READONLY) - # OwnerDrawnComboxBoxes don't generate EVT_CHAR so look for keydown instead + def setComboForQualityCodeColumn(self, olv, rowIndex, subItemIndex): + odcb = CustomComboBox(olv, choices=self.qualityCodeChoices, style=wx.CB_READONLY) odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) return odcb - def labSampleCodeEditor(self, olv, rowIndex, subItemIndex): - """ + def setComboForTimeAggregationUnitIDCreator(self, olv, rowIndex, subItemIndex): + customCombo = CustomComboBox(olv, choices=self.timeAggretaionUnitChoices.keys(), style=wx.CB_READONLY) + customCombo.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + return customCombo - :param olv: - :param rowIndex: - :param subItemIndex: - :return: - """ + def setComboForAnnotation(self, olv, rowIndex, subItemIndex): + customCombo = CustomComboBox(olv, choices=self.annotationChoices, style=wx.CB_READONLY) + customCombo.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) + customCombo.Bind(wx.EVT_COMBOBOX, self.on_annotation_combo_change) + return customCombo + + def on_annotation_combo_change(self, event): + if not event: + return + + combo = event.GetEventObject() + if combo.GetValue() == NEW: + self.__show_flag_controller() + + event.Skip() + + def __show_flag_controller(self): + add_flag_controller = NewFlagValuesController(self.parent, series_service=self.series_service, + qualifier_choice=None, + record_service=self.recordService) + + add_flag_controller.collapsible_panel.expand_panel() + add_flag_controller.Show() - odcb = CustomComboBox(olv, choices=self.labSampleChoices, style=wx.CB_READONLY) - odcb.Bind(wx.EVT_KEY_DOWN, olv._HandleChar) - return odcb class DatePicker(wx.DatePickerCtrl): """ diff --git a/odmtools/controller/logicEditTools.py b/odmtools/controller/logicEditTools.py index e88cb10..12a5306 100644 --- a/odmtools/controller/logicEditTools.py +++ b/odmtools/controller/logicEditTools.py @@ -11,15 +11,15 @@ class EditTools(): # Script header (imports etc.) will be set up in Main when record is clicked. - def __init__(self, parent, script, edit_service, connection_string, record=False): + def __init__(self, script, edit_service, connection_string, record=False): self._script = script # logger.debug(dir(self._script))sr self._edit_service = edit_service self._connection_string = connection_string self._record = record - self._serv_man = parent + # self._serv_man = parent - self._edit_error = "no series selected for editing" + self._edit_error = "no series_service selected for editing" self._add_point_req_error = "A required field was left empty" self._add_point_format_error = "A date is not formatted correctly" @@ -60,9 +60,6 @@ def fill_gap(self, gap, fill): Publisher.sendMessage("scroll") - - - def data_gaps(self, value, time_period): self._edit_service.data_gaps(value, time_period) self.refresh_selection() @@ -126,7 +123,7 @@ def _dataframe2liststub(self, dataframe): result = None if isinstance(dataframe, pd.DataFrame): result = pd.DataFrame() - +#TODO update function recieves a dataframe extract "datavalues" and "localdatetime" convert to dictionary call update function from memory database ## return dataframe @@ -234,7 +231,7 @@ def restore(self): self._script("edit_service.restore()\n", 'black') Publisher.sendMessage("scroll") - def saveFactory(self, var=None, method=None, qcl=None): + def saveFactory(self, var=None, method=None, qcl=None, action= None, actionby= None): """ :param var: @@ -248,7 +245,7 @@ def saveFactory(self, var=None, method=None, qcl=None): values['method'] = ("new_method" if method else None) values['qcl'] = ("new_qcl" if qcl else None) #values['override'] = override - return values['var'], values['method'], values['qcl']#, values['isSave'] + return values['var'], values['method'], values['qcl'], action, actionby#, values['isSave'] def save(self, var=None, method=None, qcl=None): @@ -269,7 +266,7 @@ def save(self, var=None, method=None, qcl=None): Publisher.sendMessage("scroll") return result - def save_as(self, var=None, method=None, qcl=None): + def save_as(self, variable=None, method=None, proc_level=None, action = None, action_by = None): """ :param var: @@ -278,16 +275,16 @@ def save_as(self, var=None, method=None, qcl=None): :param override: :return: """ - result = self._edit_service.save_as(var=var, method=method, qcl=qcl) + result = self._edit_service.save_as(variable=variable, method=method, proc_level=proc_level, action = action, action_by = action_by) if self._record: self._script( - "edit_service.save_as(%s, %s, %s)\n" % (self.saveFactory(var, method, qcl)), + "edit_service.save_as(%s, %s, %s, %s, %s)\n" % (self.saveFactory(variable, method, proc_level, action, action_by)), 'black') #self._script("edit_service.save(%s, %s, %s, saveAs=%s)\n" % (var, method, qcl, isSave), 'black') Publisher.sendMessage("scroll") return result - def save_appending(self, var = None, method =None, qcl = None, overwrite = False): + def save_appending(self, result = None, overwrite = False): """ :param var: @@ -296,14 +293,14 @@ def save_appending(self, var = None, method =None, qcl = None, overwrite = False :param override: :return: """ - result = self._edit_service.save_appending(var=var, method=method, qcl=qcl, overwrite= overwrite) + result = self._edit_service.save_appending(result= result, overwrite= overwrite) if result: print "Save worked!" if self._record: self._script( - "edit_service.save_appending(%s, %s, %s, " % self.saveFactory(var, method, qcl)+str(overwrite )+")\n", + "edit_service.save_appending(%s, %s)\n" % (result, str(overwrite )), 'black') #self._script("edit_service.save(%s, %s, %s, saveAs=%s)\n" % (var, method, qcl, isSave), 'black') Publisher.sendMessage("scroll") @@ -314,7 +311,7 @@ def save_appending(self, var = None, method =None, qcl = None, overwrite = False return result - def save_existing(self, var=None, method=None, qcl=None): + def save_existing(self, result=None): """ :param var: @@ -323,14 +320,14 @@ def save_existing(self, var=None, method=None, qcl=None): :param override: :return: """ - result = self._edit_service.save_existing(var=var, method=method, qcl=qcl) + result = self._edit_service.save_existing(result = result) if result: print "Save worked!" if self._record: self._script( - "edit_service.save_existing(%s, %s, %s)\n" % (self.saveFactory(var, method, qcl)), + "edit_service.save_existing(%s)\n" % (result), 'black') #self._script("edit_service.save(%s, %s, %s, saveAs=%s)\n" % (var, method, qcl, isSave), 'black') Publisher.sendMessage("scroll") @@ -401,7 +398,7 @@ def toggle_record(self, value): # Creates ################### def create_qcl(self, code, definition, explanation): - qcl = self._edit_service.create_qcl(code, definition, explanation) + qcl = self._edit_service.create_processing_level(code, definition, explanation) if self._record: self._script('new_qcl = series_service.get_qcl_by_id(%s)\n' % (qcl.id)) Publisher.sendMessage("scroll") @@ -409,7 +406,7 @@ def create_qcl(self, code, definition, explanation): return qcl def create_qualifer(self, code, description): - qual = self._edit_service.create_qualifier(code, description) + qual = self._edit_service.create_annotation(code, description) if self._record: self._script('new_qual = series_service.get_qualifier_by_code(%s)\n' % (qual.code)) Publisher.sendMessage("scroll") diff --git a/odmtools/controller/logicPlotOptions.py b/odmtools/controller/logicPlotOptions.py index 25f69b6..ceeb6ef 100644 --- a/odmtools/controller/logicPlotOptions.py +++ b/odmtools/controller/logicPlotOptions.py @@ -54,7 +54,7 @@ def __init__(self, prnt): self.plotTitle = None self.numBins = 25 self.binWidth = 1.5 - self.boxWhiskerMethod = "Month" + self.boxWhiskerMethod = "month" self.yrange = 0 self.color = "" @@ -83,7 +83,6 @@ def __init__(self, memDB, taskserver): self.currentEnd = self.endDate self.isSubsetted = False - def getDates(self): return self.startDate, self.endDate, self.currentStart, self.currentEnd @@ -112,7 +111,6 @@ def resetDates(self): self.currentStart = self.startDate self.currentEnd = self.endDate - def isPlotted(self, sid): if int(sid) in self._seriesInfos: return True @@ -144,7 +142,6 @@ def setEditSeries(self, seriesID): self._seriesInfos[self.editID].plotcolor = self._seriesInfos[self.editID].color self._seriesInfos[self.editID].color = "Black" - def updateEditSeries(self): #update values if self.editID in self._seriesInfos: @@ -152,7 +149,6 @@ def updateEditSeries(self): data =self.memDB.getEditDataValuesforGraph() self._seriesInfos[self.editID].dataTable = data - def stopEditSeries(self): if self.editID in self._seriesInfos: data = self.memDB.getDataValuesforGraph( @@ -187,15 +183,14 @@ def update(self, key, isselected): #results = self.taskserver.getCompletedTasks() #self.memDB.setConnection(results["InitEditValues"]) - self._seriesInfos[key] = self.getSeriesInfo(key) self.getUpdatedData(key) def getUpdatedData(self, key): results = self.taskserver.getCompletedTasks() - self._seriesInfos[key].Probability = results['Probability'] self._seriesInfos[key].Statistics = results['Summary'] self._seriesInfos[key].BoxWhisker = results['BoxWhisker'] + self._seriesInfos[key].Probability = results['Probability'] def setBoxInterval(self, title): @@ -216,11 +211,13 @@ def getAllSeries(self): def getSeriesById(self, seriesID): try: - series = self.memDB.series_service.get_series_by_id(seriesID) + series = self.memDB.series_service.get_series(seriesID) self.memDB.series_service.reset_session() return series + except Exception as e : logger.error("Series Not Found %s"%e) + return None def getSelectedSeries(self, seriesID): @@ -229,8 +226,10 @@ def getSelectedSeries(self, seriesID): return self.createSeriesInfo(seriesID, seriesInfo, series) def createSeriesInfo(self, seriesID, seriesInfo, series): - startDate = series.begin_date_time - endDate = series.end_date_time + + dates = self.memDB.series_service.get_result_dates(series.ResultID) + startDate = dates[1] + endDate = dates[0] if endDate > self.endDate: self.endDate = endDate @@ -240,19 +239,21 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): if not self.isSubsetted: self.currentStart = self.startDate self.currentEnd = self.endDate - - variableName = series.variable_name - unitsName = series.variable_units_name - siteName = series.site_name - dataType = series.data_type - noDataValue = series.variable.no_data_value +#TODO odm2 + + unitsName = series.UnitsObj.UnitsName + siteName = series.FeatureActionObj.SamplingFeatureObj.SamplingFeatureName + dataType = "datatype"#series.data_type + #variable = self.memDB.series_service.get_variable_by_id(series.variable_id) + variable =series.VariableObj + variableName = variable.VariableNameCV + noDataValue = variable.NoDataValue if self.editID == seriesID: #d= DataFrame(pandas.read_sql()) logger.debug("editing -- getting datavalues for graph") data = self.memDB.getEditDataValuesforGraph() logger.debug("Finished editing -- getting datavalues for graph") - else: logger.debug("plotting -- getting datavalues for graph") data = self.memDB.getDataValuesforGraph(seriesID, noDataValue, self.currentStart, self.currentEnd) @@ -269,18 +270,16 @@ def createSeriesInfo(self, seriesID, seriesInfo, series): seriesInfo.siteName = siteName seriesInfo.variableName = variableName seriesInfo.variableUnits = unitsName - seriesInfo.plotTitle = "Site: " + siteName + "\nVarName: " + variableName + "\nQCL: " + series.quality_control_level_code - seriesInfo.axisTitle = variableName + " (" + unitsName + ")" + seriesInfo.plotTitle = "Site: %s \nVarName: %s \nQCL: %s" %(siteName, variableName, series.ProcessingLevelID) + seriesInfo.axisTitle = "%s (%s)"%(variableName, unitsName) seriesInfo.noDataValue = noDataValue seriesInfo.dataTable = data - if len(data) > 0: - seriesInfo.yrange = np.max(data['DataValue']) - np.min(data['DataValue']) + seriesInfo.yrange = np.max(data['datavalue']) - np.min(data['datavalue']) else: seriesInfo.yrange = 0 - logger.debug("Finished creating SeriesInfo") return seriesInfo @@ -303,9 +302,9 @@ def getSeriesInfo(self, seriesID): def buildPlotInfo(self, seriesInfo): #remove all of the nodatavalues from the pandas table - filteredData = seriesInfo.dataTable[seriesInfo.dataTable["DataValue"] != seriesInfo.noDataValue] - val = filteredData["Month"].map(calcSeason) - filteredData["Season"] = val + filteredData = seriesInfo.dataTable[seriesInfo.dataTable["datavalue"] != seriesInfo.noDataValue] + val = filteredData["month"].map(calcSeason) + filteredData["season"] = val # construct tasks for the task server tasks = [("Probability", filteredData), @@ -326,8 +325,6 @@ def buildPlotInfo(self, seriesInfo): seriesInfo.color = self.colorList.pop(0) return seriesInfo - - def updateDateRange(self, startDate=None, endDate=None): self.currentStart = startDate self.currentEnd = endDate @@ -360,12 +357,12 @@ class Statistics(object): def __init__(self, data): start_time = timeit.default_timer() - dvs = data["DataValue"] + dvs = data["datavalue"] count = len(dvs) if count > 0: time = timeit.default_timer() - self.NumberofCensoredObservations = len(data[data["CensorCode"] != "nc"]) + self.NumberofCensoredObservations = len(data[data["censorcodecv"] != "nc"]) elapsed = timeit.default_timer() - time logger.debug("censored observations using len: %s" % elapsed) @@ -376,7 +373,6 @@ def __init__(self, data): time = timeit.default_timer() - self.NumberofObservations = count self.ArithemticMean = round(np.mean(dvs), 5) self.Maximum = round(np.max(dvs), 5) @@ -404,14 +400,13 @@ def __init__(self, data, method): self.intervals = {} self.method = method - - interval_types = ["Overall", "Year", "Month", "Season"] - intervals = ["Overall", "Year", "Month", "Season"] + interval_types = ["overall", "year", "month", "season"] + intervals = ["overall", "year", "month", "season"] interval_options = zip(interval_types, intervals) for interval_type, interval in interval_options: start_time = timeit.default_timer() - if interval_type == "Overall": + if interval_type == "overall": interval = data else: interval = data.groupby(interval_type) @@ -430,18 +425,18 @@ def calculateBoxWhiskerData(self, interval, interval_type): results = self.calculateIntervalsOnGroups(interval) - if interval_type == "Season" or interval_type == "Month": + if interval_type == "season" or interval_type == "month": func = None - if interval_type == "Season": + if interval_type == "season": func = numToSeason - elif interval_type == "Month": + elif interval_type == "month": func = numToMonth self.intervals[interval_type] = BoxWhiskerPlotInfo( interval_type, interval_type, [func(x) for x in results["names"]], [results["median"], results["conflimit"], results["mean"], results["confint"]]) - elif interval_type == "Overall": + elif interval_type == "overall": self.intervals[interval_type] = BoxWhiskerPlotInfo( interval_type, None, [], [results["median"], results["conflimit"], results["mean"], results["confint"]]) @@ -451,7 +446,6 @@ def calculateBoxWhiskerData(self, interval, interval_type): interval_type, interval_type, results["names"], [results["median"], results["conflimit"], results["mean"], results["confint"]]) - def calculateIntervalsOnGroups(self, interval): mean = [] @@ -462,7 +456,7 @@ def calculateIntervalsOnGroups(self, interval): if isinstance(interval, pd.core.groupby.DataFrameGroupBy): for name, group in interval: - datavalue = group['DataValue'] + datavalue = group['datavalue'] group_mean = np.mean(datavalue) group_median = np.median(datavalue) group_std = math.sqrt(np.var(datavalue)) @@ -477,8 +471,8 @@ def calculateIntervalsOnGroups(self, interval): median.append(group_median) mean.append(group_mean) else: - name = "Overall" - datavalue = interval['DataValue'] + name = "overall" + datavalue = interval['datavalue'] data_mean = np.mean(datavalue) data_median = np.median(datavalue) data_std = math.sqrt(np.var(datavalue)) @@ -543,13 +537,13 @@ def __init__(self, data): #PrbExc = ranks/(length(sorted)+1)*100 #Here I plot the probability of exceedance (PrbExc) against the sorted initial values (sorted). - #plot(PrbExc, sorted, type='n', col='white', font.lab=1.5, xlab="Frequency of Exceedance, percent", ylab="TSS, mg/L",log="y") + #plot(PrbExc, sorted, type='n', columns='white', font.lab=1.5, xlab="Frequency of Exceedance, percent", ylab="TSS, mg/L",log="y") :param data: :return: """ - self.yAxis = data['DataValue'] + self.yAxis = data['datavalue'] # Determine rank, sorting values doesn't change outcome while using pandas. ranks = self.yAxis.rank() PrbExc = ranks / (len(ranks) + 1) * 100 @@ -595,4 +589,3 @@ def numToSeason(date): - diff --git a/odmtools/controller/olvAddPoint.py b/odmtools/controller/olvAddPoint.py old mode 100755 new mode 100644 index bc9c7a2..57f6eaa --- a/odmtools/controller/olvAddPoint.py +++ b/odmtools/controller/olvAddPoint.py @@ -1,50 +1,34 @@ -""" - Object List View Control used in Add Point Form -""" import wx import wx.lib.newevent from datetime import datetime from odmtools.common.icons import x_mark_16, star_16, star_32, x_mark_32, check_mark_3_16, check_mark_3_32 from odmtools.controller.logicCellEdit import CellEdit, NULL - - -OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() - - -__author__ = 'Jacob' - from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn +from odmtools.odmservices.service_manager import ServiceManager +OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() class Points(object): - """ - - """ - - def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00", utcOffSet=-7, - censorCode="NULL", valueAccuracy="NULL", offSetValue="NULL", offSetType="NULL", qualifierCode="NULL", - labSampleCode="NULL"): + def __init__(self, data_value="-9999", date=datetime.now().date(), time="00:00:00", utcOffSet=-7, + censor_code="NULL", quality_code="NULL", time_agg_interval="NULL", time_agg_unit="NULL", annotation="NULL"): try: - self.dataValue = str(dataValue) + self.dataValue = str(data_value) except: - self.dataValue = dataValue - - self.valueAccuracy = valueAccuracy - + self.dataValue = data_value try: self.time = str(time) except: self.time = time self.date = str(date) + self.valueDateTime = self.date self.utcOffSet = str(utcOffSet) - #self.dateTimeUTC = dateTimeUTC - self.offSetValue = offSetValue - self.offSetType = offSetType - self.censorCode = censorCode - self.qualifierCode = qualifierCode - self.labSampleCode = labSampleCode + self.censorCode = censor_code + self.qualityCodeCV = quality_code + self.timeAggInterval = time_agg_interval + self.timeAggregationUnitID = time_agg_unit + self.annotation = annotation ## determines whether a row is in correct format or now self.validDataValue = False @@ -53,80 +37,64 @@ def __init__(self, dataValue="-9999", date=datetime.now().date(), time="00:00:00 self.validUTCOffSet = False self.validCensorCode = False self.validValueAcc = False - self.validOffSetValue = False self.validOffSetType = False - self.validQualifierCode = False - self.validLabSampleCode = False - def __repr__(self): - """ - - :return: - """ - - return "" \ - % (self.dataValue, self.date, self.time, self.utcOffSet, self.censorCode) + self.validQualityCode = False + self.validTimeAggInterval = False + self.validTimeAggUnit = False class OLVAddPoint(FastObjectListView): - """ - - """ def __init__(self, *args, **kwargs): - """ - - :param args: - :param kwargs: - :return: - """ - - try: + if "serviceManager" in kwargs: self.serviceManager = kwargs.pop("serviceManager") - except: - self.serviceManager = None + else: + try: + self.serviceManager = ServiceManager() + except: + self.serviceManager = None + try: self.recordService = kwargs.pop("recordService") except: self.recordService = None - FastObjectListView.__init__(self, *args, **kwargs) - cellEdit = CellEdit(self, self.serviceManager, self.recordService) + self.cellEdit = CellEdit(self, self.serviceManager, self.recordService) self.checkedObjects = [] # # Custom Image Getters - self.imgGetterDataValue = cellEdit.imgGetterDataValue - self.imgGetterDate = cellEdit.imgGetterDate - self.imgGetterTime = cellEdit.imgGetterTime - self.imgGetterCensorCode = cellEdit.imgGetterCensorCode - self.imgGetterUTCOffset = cellEdit.imgGetterUTCOFFset - self.imgGetterValueAcc = cellEdit.imgGetterValueAcc - self.imgGetterlabSample = cellEdit.imgGetterLabSampleCode - self.imgGetterQualifier = cellEdit.imgGetterQualifierCode - self.imgGetterOffSetType = cellEdit.imgGetterOffSetType - self.imgGetterOffSetValue = cellEdit.imgGetterOffSetValue + self.imgGetterDataValue = self.cellEdit.imgGetterDataValue + self.imgGetterDate = self.cellEdit.imgGetterDate + self.imgGetterTime = self.cellEdit.imgGetterTime + self.imgGetterCensorCode = self.cellEdit.imgGetterCensorCode + self.imgGetterUTCOffset = self.cellEdit.imgGetterUTCOFFset + self.imgGetterQualityCode = self.cellEdit.imgGetterQualityCode + self.imgGetterTimeAggInterval = self.cellEdit.imgGetterTimeAggregationInterval + self.imgGetterTimeAggUnit = self.cellEdit.imgGetterTimeAggregationUnit ## Custom Value Setters ## Sets the value, can modify rules for setting value - self.valueSetterDataValue = cellEdit.valueSetterDataValue - self.valueSetterUTCOffset = cellEdit.valueSetterUTCOffset + self.valueSetterDataValue = self.cellEdit.valueSetterDataValue + self.valueSetterUTCOffset = self.cellEdit.valueSetterUTCOffset ## Custom String Converters ## Changes how the string will appear in the cell after editing - self.localtime2Str = cellEdit.strConverterLocalTime - self.str2DataValue = cellEdit.strConverterDataValue - self.utcOffSet2Str = cellEdit.strConverterUTCOffset - self.offSetValue2Str = cellEdit.strConverterOffSetValue + self.localtime2Str = self.cellEdit.strConverterLocalTime + self.str2DataValue = self.cellEdit.strConverterDataValue + self.utcOffSet2Str = self.cellEdit.strConverterUTCOffset + # self.offSetValue2Str = cellEdit.strConverterOffSetValue ## Custom CellEditors ## Custom cell editors for each cell - self.dateEditor = cellEdit.dateEditor - self.timeEditor = cellEdit.localTimeEditor - self.censorEditor = cellEdit.censorCodeEditor - self.offSetTypeEditor = cellEdit.offSetTypeEditor - self.qualifierCodeEditor = cellEdit.qualifierCodeEditor - self.labSampleEditor = cellEdit.labSampleCodeEditor + self.dateEditor = self.cellEdit.dateEditor + self.timeEditor = self.cellEdit.localTimeEditor + self.censorEditor = self.cellEdit.censorCodeEditor + self.valueDateTimeEditorCreator = self.cellEdit.valueDateTimeEditor + self.qualityCodeCreator = self.cellEdit.setComboForQualityCodeColumn + self.timeAggregationUnitIDCreator = self.cellEdit.setComboForTimeAggregationUnitIDCreator + self.annotationCreator = self.cellEdit.setComboForAnnotation self.SetEmptyListMsg("Add points either by csv or by adding a new row") self.AddNamedImages("error", x_mark_16.GetBitmap(), x_mark_32.GetBitmap()) @@ -139,57 +107,71 @@ def __init__(self, *args, **kwargs): self.oddRowsBackColor = wx.Colour(191, 239, 255) self.cellEditMode = self.CELLEDIT_DOUBLECLICK + def refresh_annotations(self): + self.cellEdit.annotationChoices = self.cellEdit.fetch_annotations() + def buildOlv(self): columns = [ - ## TODO This is needed for the windows version - #ColumnDefn("", "left", -1, valueSetter=self.emptyCol), - ColumnDefn("DataValue", "left", -1, minimumWidth=100, + ColumnDefn(title="DataValue", + minimumWidth=100, valueGetter='dataValue', valueSetter=self.valueSetterDataValue, imageGetter=self.imgGetterDataValue, stringConverter=self.str2DataValue, headerImage="star"), - ColumnDefn("Date", "left", -1, minimumWidth=120, + + ColumnDefn(title="Date", minimumWidth=120, valueGetter="date", imageGetter=self.imgGetterDate, cellEditorCreator=self.dateEditor, headerImage="star"), - ColumnDefn("Time", "left", -1, minimumWidth=100, + + ColumnDefn(title="Time", minimumWidth=100, valueGetter="time", imageGetter=self.imgGetterTime, cellEditorCreator=self.timeEditor, stringConverter=self.localtime2Str, headerImage="star"), - ColumnDefn("UTCOffset", "left", -1, minimumWidth=100, + + ColumnDefn(title="UTCOffset", + minimumWidth=100, valueGetter="utcOffSet", - #valueSetter=self.valueSetterUTCOffset, - #stringConverter=self.utcOffSet2Str, imageGetter=self.imgGetterUTCOffset, headerImage="star"), - ColumnDefn("CensorCode", "left", -1, valueGetter="censorCode", minimumWidth=110, + + ColumnDefn(title="CensorCode", + valueGetter="censorCode", + minimumWidth=110, cellEditorCreator=self.censorEditor, imageGetter=self.imgGetterCensorCode, headerImage="star"), - ColumnDefn("ValueAccuracy", "left", -1, valueGetter="valueAccuracy", minimumWidth=100, - imageGetter=self.imgGetterValueAcc), - ColumnDefn("OffsetValue", "left", -1, valueGetter="offSetValue", minimumWidth=100, - stringConverter=self.offSetValue2Str, - imageGetter=self.imgGetterOffSetValue), - ColumnDefn("OffsetType", "left", -1, valueGetter="offSetType", minimumWidth=100, - imageGetter=self.imgGetterOffSetType, - cellEditorCreator=self.offSetTypeEditor), - ColumnDefn("QualifierCode", "left", -1, valueGetter="qualifierCode", minimumWidth=130, - imageGetter=self.imgGetterQualifier, - cellEditorCreator=self.qualifierCodeEditor), - ColumnDefn("LabSampleCode", "left", -1, valueGetter="labSampleCode", minimumWidth=130, - imageGetter=self.imgGetterlabSample, - cellEditorCreator=self.labSampleEditor - ), + + ColumnDefn(title="Quality CodeCV", + valueGetter="qualityCodeCV", + minimumWidth=130, + cellEditorCreator=self.qualityCodeCreator, + imageGetter=self.imgGetterQualityCode, + headerImage="star"), + + ColumnDefn(title="TimeAggregationInterval", + minimumWidth=130, + valueGetter="timeAggInterval", + imageGetter=self.imgGetterTimeAggInterval, + headerImage="star"), + + ColumnDefn(title="TimeAggregationUnitID", + minimumWidth=130, + valueGetter="timeAggregationUnitID", + cellEditorCreator=self.timeAggregationUnitIDCreator, + imageGetter=self.imgGetterTimeAggUnit, headerImage="star"), + + ColumnDefn(title="Annotation", + minimumWidth=130, + valueGetter="annotation", + cellEditorCreator=self.annotationCreator) ] self.SetColumns(columns) - - # self.CreateCheckStateColumn() self.SetObjects(None) def rowFormatter(listItem, point): @@ -206,8 +188,7 @@ def rowFormatter(listItem, point): def isCorrect(self, point): validators = [ self.imgGetterDataValue, self.imgGetterDate, self.imgGetterTime, self.imgGetterCensorCode, - self.imgGetterUTCOffset, self.imgGetterValueAcc, self.imgGetterlabSample, - self.imgGetterQualifier, self.imgGetterOffSetType, self.imgGetterOffSetValue + self.imgGetterUTCOffset, self.imgGetterQualityCode, self.imgGetterTimeAggInterval, self.imgGetterTimeAggUnit ] isCorrect = True diff --git a/odmtools/controller/olvDataTable.py b/odmtools/controller/olvDataTable.py index 58110d1..1b4ff44 100644 --- a/odmtools/controller/olvDataTable.py +++ b/odmtools/controller/olvDataTable.py @@ -20,25 +20,72 @@ def __init__(self, parent, **kwargs): self.sortedColumnIndex = -1 self.currentItem = None self.dataframe = None + self.annotations = None + self.annotations_grouped = {} def init(self, memDB): self.memDB = memDB - columns = [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - stringConverter='%Y-%m-%d %H:%M:%S' if "date" in x.lower() else '%s') - for x, i in self.memDB.getEditColumns()] + self.useAlternateBackColors = True self.oddRowsBackColor = wx.Colour(191, 217, 217) - self.SetColumns(columns) + self.dataframe = self.memDB.getDataValuesDF() - sort_by_index = list(self.dataframe.columns).index("LocalDateTime") - self.dataframe.sort(self.dataframe.columns[sort_by_index], inplace=True) - self.dataObjects = self.dataframe.values.tolist() + self.annotations = self.memDB.get_annotations() + + sort_by_index = self.dataframe.columns.tolist().index("valuedatetime") + self.dataframe.sort_values(self.dataframe.columns[sort_by_index], inplace=True) + + self.annotations_grouped = self.__group_annotations() + self.dataObjects = self.__merge_dataframe_with_annotations() + + col = self.memDB.get_columns_with_annotations() + + columns = \ + [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, + stringConverter='%Y-%m-%d %H:%M:%S' if "valuedatetime" == x.lower() else '%s') + for x, i in col] + + self.SetColumns(columns) + self.SetObjectGetter(self.ObjectGetter) - self.SetItemCount(len(self.dataframe)) + self.SetItemCount(len(self.dataObjects)) + + def __merge_dataframe_with_annotations(self): + data_list = self.dataframe.values.tolist() + data = data_list + if self.annotations_grouped: + for key, value in self.annotations_grouped.iteritems(): + for i in range(0, len(data_list)): + if key in data[i]: + data[i].append(value) + break + + return data + + def __group_annotations(self): + """ + Ideally, this method should only be called once. Use self.grouped_annotations after calling this method + :return: + """ + if (self.annotations): + anno_list = self.annotations.values.tolist() + + anno = {} + for i in range(0, len(anno_list)): + value_id = anno_list[i][1] + annotation_code = anno_list[i][-1] + if value_id in anno: + anno[value_id].append(annotation_code) + else: + anno[value_id] = [annotation_code] + + return anno + else: + return None def EnableSorting(self): - self.Bind(wx.EVT_LIST_COL_CLICK, self.onColSelected) + self.Bind(wx.EVT_LIST_COL_CLICK, self.on_column_selected) if not self.smallImageList: self.SetImageLists() if (not self.smallImageList.HasName(ObjectListView.NAME_DOWN_IMAGE) and @@ -52,40 +99,58 @@ def ObjectGetter(self, index): """ return self.dataObjects[index % len(self.dataObjects)] - def onColSelected(self, evt): + def on_column_selected(self, event): """ Allows users to sort by clicking on columns """ - if isinstance(self.dataframe, pd.DataFrame): - if self.dataframe.empty: - return - else: - if not self.dataframe: - return + if not isinstance(self.dataframe, pd.DataFrame): + return + + if self.dataframe.empty: + return - logger.debug("Column: %s" % evt.m_col) - self.sortColumn(evt.m_col) + if not len(self.dataObjects): + return + + self.sortColumn(event.Column) def sortColumn(self, selected_column): + self.sortAscending = not self.sortAscending + oldSortColumnIndex = self.sortedColumnIndex self.sortedColumnIndex = selected_column - ascending = self.sortAscending - if ascending: - self.dataframe.sort(self.dataframe.columns[selected_column], inplace=True) - self.sortAscending = False - elif not ascending: - self.dataframe.sort(self.dataframe.columns[selected_column], ascending=False, inplace=True) - self.sortAscending = True self._UpdateColumnSortIndicators(selected_column, oldSortColumnIndex) - self.dataObjects = self.dataframe.values.tolist() - if self.GetItemCount: + if selected_column >= len(self.dataframe.columns): + self.dataObjects = self.sort_columns_by_annotation_code(reverse=self.sortAscending) + else: + self.dataframe.sort_values(self.dataframe.columns[selected_column], ascending=self.sortAscending, inplace=True) + self.dataObjects = self.__merge_dataframe_with_annotations() + + if self.GetItemCount(): itemFrom = self.GetTopItem() itemTo = self.GetTopItem() + 1 + self.GetCountPerPage() itemTo = min(itemTo, self.GetItemCount() - 1) self.RefreshItems(itemFrom, itemTo) + def sort_columns_by_annotation_code(self, reverse=False): + rows_with_annotation = [] + rows_without_annotation = [] + + column_number_of_dataframe = len(self.dataframe.columns) + + for i in self.dataObjects: + if len(i) > column_number_of_dataframe: + rows_with_annotation.append(i) + else: + rows_without_annotation.append(i) + + if reverse: + return rows_without_annotation + rows_with_annotation + else: + return rows_with_annotation + rows_without_annotation + def onItemSelected(self, event): """ diff --git a/odmtools/controller/olvSeriesSelector.py b/odmtools/controller/olvSeriesSelector.py index f49fdb0..a396e98 100644 --- a/odmtools/controller/olvSeriesSelector.py +++ b/odmtools/controller/olvSeriesSelector.py @@ -2,11 +2,11 @@ import wx import wx.lib.newevent -# from ObjectListView.ObjectListView import FastObjectListView, ColumnDefn + from odmtools.lib.ObjectListView import FastObjectListView, ColumnDefn + # from odmtools.common.logger import LoggerTool -from odmtools.odmdata import series # tool = LoggerTool() @@ -16,6 +16,8 @@ OvlCheckEvent, EVT_OVL_CHECK_EVENT = wx.lib.newevent.NewEvent() +# + class clsSeriesTable(FastObjectListView): def __init__(self, *args, **kwargs): FastObjectListView.__init__(self, *args, **kwargs) @@ -31,8 +33,8 @@ def __init__(self, *args, **kwargs): """Object being edited""" self.editingObject = None - self._buildColumns() - self.CreateCheckStateColumn() + #self._buildColumns() + def rowFormatter(listItem, point): listItem.SetFont(wx.Font(11, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)) @@ -44,16 +46,20 @@ def onKeyPress(self, evt): """Ignores Keypresses""" pass - def _buildColumns(self): - seriesColumns = [ - ColumnDefn(key, align="left", minimumWidth=100, valueGetter=value, - # stringConverter = '%s') - stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else'%s') - for key, value in series.returnDict().iteritems()] - self.SetColumns(seriesColumns) + def _buildColumns(self, columns): + series_columns = [] + for key in columns: + col = ColumnDefn(title=key, + minimumWidth=100, + valueGetter=key, + stringConverter=date_to_string if ("date" in key.lower()) else '%s') + + series_columns.append(col) - """User can select series using the mouse to click on check boxes """ + self.SetColumns(series_columns) + self.CreateCheckStateColumn() + """User can select series_service using the mouse to click on check boxes """ def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): """ This is the same code, just added the original _HandleLeftDownOnImage in ObjectListView but @@ -80,7 +86,7 @@ def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): self.RefreshIndex(rowIndex, modelObject) else: - wx.MessageBox("Visualization is limited to {0} series.".format(self.allowedLimit), "Can't add plot", + wx.MessageBox("Visualization is limited to {0} series_service.".format(self.allowedLimit), "Can't add plot", wx.OK | wx.ICON_INFORMATION) else: if checkedlen > 0: @@ -104,6 +110,11 @@ def GetModelObjects(self): return self._modelObjects if self._modelObjects else [] +def date_to_string(value): + try: + return value.strftime("%Y-%m-%d %H:%M:%S") + except AttributeError: + return "" diff --git a/odmtools/controller/pageExisting.py b/odmtools/controller/pageExisting.py index 0544cfe..ef04145 100644 --- a/odmtools/controller/pageExisting.py +++ b/odmtools/controller/pageExisting.py @@ -2,7 +2,7 @@ import wx from odmtools.view import clsExisting -from odmtools.odmdata import series +from odmtools.odmdata import returnDict import wx.wizard as wiz import datetime @@ -16,11 +16,12 @@ class pageExisting(wiz.WizardPageSimple): def __init__(self, parent, title, series_service , site): """Constructor""" wiz.WizardPageSimple.__init__(self, parent) - sizer = wx.BoxSizer(wx.VERTICAL) self.sizer = sizer self.SetSizer(sizer) - #self.series = series + self.site = site + self.series_service = series_service + #self.series_service = series_service title = wx.StaticText(self, -1, title) title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) @@ -28,9 +29,9 @@ def __init__(self, parent, title, series_service , site): sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) self.pnlExisting = clsExisting.pnlExisting(self) #, id=wxID_PNLEXISTING, name=u'pnlExisting', #pos=wx.Point(536, 285), size=wx.Size(439, 357), - #style=wx.TAB_TRAVERSAL)#, sm = service_man, series = series) + #style=wx.TAB_TRAVERSAL)#, sm = service_man, series_service = series_service) self.sizer.Add(self.pnlExisting, 85, wx.ALL, 5) - self._init_data(series_service, site.id) + self._init_data(series_service, site.SamplingFeatureID) self.pnlExisting.olvSeriesList.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnOLVItemSelected) @@ -43,10 +44,12 @@ def _init_data(self, series_serv, site_id): #if q.code == self.qcl.code: # index = i + self.pnlExisting.olvSeriesList.Focus(index) self.pnlExisting.olvSeriesList.Select(index) + # Handlers for pnlExisting events. def OnOLVItemSelected(self, event): # TODO: Implement OnOLVItemSelected @@ -65,18 +68,20 @@ def enableButtons(self, isEnabled): self.pnlExisting.rbOriginal.Enable(isEnabled) self.pnlExisting.lblOverlap.Enable(isEnabled) - def getSeries(self): + def get_selected_series(self): selectedObject = self.pnlExisting.olvSeriesList.GetSelectedObject() - return selectedObject.method, selectedObject.quality_control_level, selectedObject.variable + result = selectedObject.ResultObj#self.series_service.get_series(selectedObject.ResultID) + return result.FeatureActionObj.ActionObj.MethodObj, result.ProcessingLevelObj, result.VariableObj, result def initTable(self, dbservice, site_id): - """Set up columns and objects to be used in the objectlistview to be visible in the series selector""" - - seriesColumns = [clsExisting.ColumnDefn(key, align="left", - minimumWidth=-1, valueGetter=value, - stringConverter= '%Y-%m-%d %H:%M:%S' if 'date' in key.lower() else '%s') - for key, value in series.returnDict().iteritems()] + """Set up columns and objects to be used in the objectlistview to be visible in the series_service selector""" + objects = dbservice.get_all_series(siteid=site_id) + seriesColumns = [ + clsExisting.ColumnDefn(key, align="left", minimumWidth=100, valueGetter=key, + # stringConverter = '%s') + stringConverter='%Y-%m-%d %H:%M:%S' if "date" in key.lower() else '%s') + # for key in objects[0].__dict__.keys()] + for key in returnDict()] self.pnlExisting.olvSeriesList.SetColumns(seriesColumns) - objects = dbservice.get_series_by_site(site_id= site_id) self.pnlExisting.olvSeriesList.SetObjects(objects) diff --git a/odmtools/gui/frmConsole.py b/odmtools/gui/frmConsole.py index 5a112d0..1cf3c12 100644 --- a/odmtools/gui/frmConsole.py +++ b/odmtools/gui/frmConsole.py @@ -8,7 +8,7 @@ __author__ = 'Jacob' class ModifiedFrame(Frame): - """Override standard PyCrust frame in order to remove exit and about page""" + """Override standard PyCrust bulkInsertCtrl in order to remove exit and about page""" def __init__(self, *args, **kwargs): Frame.__init__(self, *args, **kwargs) diff --git a/odmtools/gui/frmDataExport.py b/odmtools/gui/frmDataExport.py index 800d950..2a1fe2e 100644 --- a/odmtools/gui/frmDataExport.py +++ b/odmtools/gui/frmDataExport.py @@ -79,7 +79,7 @@ def __init__(self, parent, series_id): self._init_ctrls(parent) sm = ServiceManager() - self.export_service = sm.get_export_service() + self.export_data = sm.get_export_service() def OnBtnOKButton(self, event): @@ -96,7 +96,8 @@ def OnBtnOKButton(self, event): full_path = os.path.join(dlg.GetDirectory(), dlg.GetFilename()) print full_path - self.export_service.export_series_data(self.series_id, full_path, utc, site, var, offset, qual, src, qcl) + #self.export_service.export_series_data(self.series_id, full_path, utc, site, var, offset, qual, src, qcl) + self.export_data.export_series_data(self.series_id, full_path, utc, site, var, offset, qual, src, qcl) self.Close() dlg.Destroy() diff --git a/odmtools/gui/frmFlagValues.py b/odmtools/gui/frmFlagValues.py old mode 100755 new mode 100644 index 32e4286..3c64444 --- a/odmtools/gui/frmFlagValues.py +++ b/odmtools/gui/frmFlagValues.py @@ -2,7 +2,7 @@ import wx -from odmtools.odmdata import Qualifier + def create(parent): @@ -151,7 +151,7 @@ def OnBtnOKButton(self, event): q.description = desc ''' - q=self.series_service.create_qualifier(code, desc) + q=self.series_service.create_annotation(code, desc) self.qid = q.id self.selectedValue = q.code + '-' + q.description diff --git a/odmtools/gui/frmODMTools.py b/odmtools/gui/frmODMTools.py index d48f642..602c53f 100755 --- a/odmtools/gui/frmODMTools.py +++ b/odmtools/gui/frmODMTools.py @@ -13,7 +13,7 @@ import mnuRibbon import pnlPlot import pnlPlot -import pnlDataTable +# import pnlDataTable import wx.lib.agw.aui as aui import wx.py.crust import wx.stc @@ -35,6 +35,7 @@ logger =logging.getLogger('main') + class frmODMToolsMain(wx.Frame): """ @@ -58,6 +59,7 @@ def __init__(self, **kwargs): self.record_service = None self.scriptcreate = False + series_service = self._init_database() if series_service: self._init_ctrls(series_service) @@ -70,7 +72,6 @@ def __init__(self, **kwargs): logger.info("System shutting down... ") sys.exit(0) - def _obtainScreenResolution(self): """ Calculates the size of ODMTools. Prevents ODMTools being larger than the available screen size typically a problem on laptops @@ -85,7 +86,7 @@ def _obtainScreenResolution(self): ''' if minimumAllowedSize >= wx.GetDisplaySize(): - logger.fatal("ODMTools cannot be displayed in this resolution: %s \n\tPlease use a larger resolution" + logger.fatal("ODM2Tools cannot be displayed in this resolution: %s \n\tPlease use a larger resolution" % wx.GetDisplaySize()) print "minimumAllowedsize: ", minimumAllowedSize, "display: ", wx.GetDisplaySize() sys.exit(0) @@ -103,10 +104,9 @@ def _obtainScreenResolution(self): elif screenWidth < defaultWidth: newSize = wx.Size(defaultHeight, screenWidth / 1.5) - logger.debug("ODMTools Window Size: %s" % newSize) + logger.debug("ODM2Tools Window Size: %s" % newSize) return newSize - #############Entire Form Sizers########## def _init_sizers(self): @@ -120,16 +120,13 @@ def _init_s_Items(self, parent): parent.AddWindow(self._ribbon, 0, wx.EXPAND) parent.AddWindow(self.pnlDocking, 85, flag=wx.ALL | wx.EXPAND) + def _init_database(self, quit_if_cancel=True): logger.info("Loading Database...") - - while True: ## Database connection is valid, therefore proceed through the rest of the program if self.service_manager.is_valid_connection(): - conn_dict = None - series_service = self.createService() conn_dict = self.service_manager.get_current_conn_dict() @@ -140,7 +137,7 @@ def _init_database(self, quit_if_cancel=True): db_config = frmDBConfig.frmDBConfig(None, self.service_manager, False) value = db_config.ShowModal() if value == wx.ID_CANCEL and quit_if_cancel: - logger.fatal("ODMTools is now closing because there is no database connection.") + logger.fatal("ODM2Tools is now closing because there is no database connection.") sys.exit(0) elif not quit_if_cancel: return series_service @@ -157,6 +154,7 @@ def _init_database(self, quit_if_cancel=True): return series_service + def onChangeDBConn(self, event): db_config = frmDBConfig.frmDBConfig(None, self.service_manager, False) value = db_config.ShowModal() @@ -181,6 +179,7 @@ def onChangeDBConn(self, event): self.dataTable.clear() + def servicesValid(self, service, displayMsg=True): """ @@ -193,13 +192,14 @@ def servicesValid(self, service, displayMsg=True): ## Test if Series Catalog is empty if not service.get_used_sites(): if displayMsg: - msg = wx.MessageDialog(None, 'Series Catalog cannot be empty. Please enter in a new database connection', - 'Series Catalog is empty', wx.OK | wx.ICON_ERROR ) + msg = wx.MessageDialog(None, + 'Series Catalog cannot be empty. Please enter in a new database connection', + 'Series Catalog is empty', wx.OK | wx.ICON_ERROR) msg.ShowModal() valid = False # @TODO If Jeff runs into other issues with services not being available, we can simply test different services here - #if not service.get_all_variables(): + # if not service.get_all_variables(): # valid = False return valid @@ -210,7 +210,7 @@ def on_about_request(self, event): def MacReopenApp(self): """Called when the doc icon is clicked, and ???""" - try: # it's possible for this event to come when the frame is closed + try: # it's possible for this event to come when the bulkInsertCtrl is closed self.GetTopWindow().Raise() except: pass @@ -219,7 +219,7 @@ def MacReopenApp(self): def _init_ctrls(self, series_service): # generated method, don't edit - logger.debug("Loading frame...") + logger.debug("Loading bulkInsertCtrl...") self.SetIcon(gtk_execute.getIcon()) @@ -234,7 +234,7 @@ def _init_ctrls(self, series_service): self.menu_bar = wx.MenuBar() self.help_menu = wx.Menu() - self.help_menu.Append(wx.ID_ABOUT, "&About ODMTools") + self.help_menu.Append(wx.ID_ABOUT, "&About ODM2Tools") self.menu_bar.Append(self.help_menu, "&Help") self.SetMenuBar(self.menu_bar) @@ -254,9 +254,8 @@ def _init_ctrls(self, series_service): ################ Series Selection Panel ################## logger.debug("Loading Series Selector ...") - - self.pnlSelector = FrmSeriesSelector(self.pnlDocking, series_service, plot=self.pnlPlot, taskserver=self.taskserver, memdb = self.memDB) - + self.pnlSelector = FrmSeriesSelector(self.pnlDocking, series_service, plot=self.pnlPlot, + taskserver=self.taskserver, memdb=self.memDB) ####################grid Table View################## logger.debug("Loading DataTable ...") @@ -268,15 +267,12 @@ def _init_ctrls(self, series_service): self.txtPythonConsole = ODMToolsConsole(parent=self.pnlDocking, size=wx.Size(200, 200)) wx.CallAfter(self._postStartup) - - logger.debug("Loading Python Script ...") self.txtPythonScript = pnlScript(name=u'txtPython', parent=self, - size=wx.Size(200, 200)) + size=wx.Size(200, 200)) self.Bind(wx.EVT_CLOSE, self.onClose) - Publisher.subscribe(self.onDocking, ("adjust.Docking")) Publisher.subscribe(self.onPlotSelection, ("select.Plot")) Publisher.subscribe(self.onExecuteScript, ("execute.script")) @@ -297,31 +293,31 @@ def _init_aui_manager(self): self._mgr.AddPane(self.pnlPlot, aui.AuiPaneInfo().CenterPane() .Name("Plot").Caption("Plot").MaximizeButton(True).DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.dataTable, aui.AuiPaneInfo().Right().Name("Table"). Show(show=False).Caption('Table View').MinSize(wx.Size(200, 200)).Floatable().Movable(). Position(1).MinimizeButton(True).MaximizeButton(True).DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.pnlSelector, aui.AuiPaneInfo().Bottom().Name("Selector").MinSize(wx.Size(50, 200)). Movable().Floatable().Position(0).MinimizeButton(True).MaximizeButton(True).CloseButton(True) .DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.txtPythonScript, aui.AuiPaneInfo().Caption('Script'). Name("Script").Movable().Floatable().Right() .MinimizeButton(True).MaximizeButton(True).FloatingSize(size=(400, 400)) .CloseButton(True).Float().FloatingPosition(pos=(self.Position)) .Hide().CloseButton(True).DestroyOnClose(False) - ) + ) self._mgr.AddPane(self.txtPythonConsole, aui.AuiPaneInfo().Caption('Python Console'). Name("Console").FloatingSize(size=(300, 400)).MinimizeButton( True).Movable().Floatable().MaximizeButton(True).CloseButton(True).Float() .FloatingPosition(pos=(self.Position)).Show(show=False).DestroyOnClose(False) - ) + ) ## TODO Fix loadingDockingSettings as it doesn't load it correctly. @@ -375,7 +371,6 @@ def getDBService(self): def onPlotSelection(self, value): self.pnlPlot.selectPlot(value) - def onSetScriptTitle(self, title): scriptPane = self._mgr.GetPane(self.txtPythonScript) scriptPane.Caption(title) @@ -385,7 +380,8 @@ def onSetScriptTitle(self, title): def addEdit(self, event): - with wx.BusyInfo("Please wait for a moment while ODMTools fetches the data and stores it in our database", parent=self): + + with wx.BusyInfo("Please wait for a moment while ODM2Tools fetches the data and stores it in our database", parent=self): self.scriptcreate = True isSelected, seriesID = self.pnlSelector.onReadyToEdit() logger.info("Beginning editing seriesID: %s"%str(seriesID)) @@ -432,16 +428,15 @@ def addEdit(self, event): logger.debug("Recording? %s" % self.record_service._record) - #self.record_service = None + # self.record_service = None self.txtPythonConsole.shell.run("edit_service = app.TopWindow.record_service", prompt=False, verbose=False) self.txtPythonConsole.shell.run("series_service = edit_service.get_series_service()", prompt=False, verbose=False) - #from meliae import scanner - #scanner.dump_all_objects("edit_plotting.dat") + # from meliae import scanner + # scanner.dump_all_objects("edit_plotting.dat") logger.info("Finished Setting up Editing Series: %s " % seriesID) - def stopEdit(self, event): self.pnlSelector.stopEdit() @@ -452,7 +447,6 @@ def stopEdit(self, event): self.record_service = None self._ribbon.toggleEditButtons(False) - def getRecordService(self): return self.record_service @@ -467,7 +461,10 @@ def createService(self, conn_dict=""): :return: """ - series_service = self.service_manager.get_series_service(conn_dict=conn_dict) + + series_service= self.service_manager.get_series_service(conn_dict=conn_dict)#=connection) + + return series_service def getServiceManager(self): @@ -484,12 +481,12 @@ def loadDockingSettings(self): # test if there is a perspective to load try: # TODO Fix resource_path to appdirs - os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config') - f = open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'r') + os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config') + f = open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'r') except: # Create the file if it doesn't exist - open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'w').close() - f = open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'r') + open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'w').close() + f = open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'r') self._mgr.LoadPerspective(f.read(), True) @@ -507,19 +504,19 @@ def onClose(self, event): if value == wx.ID_YES: self.txtPythonScript.OnSaveAs(event) - # deinitialize the frame manager + # deinitialize the bulkInsertCtrl manager self.pnlPlot.Close() try: - f = open(os.path.join(user_config_dir("ODMTools", "UCHIC"), 'ODMTools.config'), 'w') + f = open(os.path.join(user_config_dir("ODM2Tools", "UCHIC"), 'ODM2Tools.config'), 'w') f.write(self._mgr.SavePerspective()) except: print "error saving docking data" self._mgr.UnInit() - + # Shut down processes running in background if self.taskserver.numprocesses > 0 and self.taskserver.anyAlive: - busy = wx.BusyInfo("Closing ODMTools ...", parent=self) + busy = wx.BusyInfo("Closing ODM2Tools ...", parent=self) # Terminate the processes self.taskserver.processTerminate() @@ -542,7 +539,7 @@ def onClose(self, event): elif isinstance(item, wx.Dialog): item.Destroy() item.Close() - logger.info("Closing ODMTools\n") + logger.info("Closing ODM2Tools\n") self.Destroy() wx.GetApp().ExitMainLoop() diff --git a/odmtools/gui/mnuPlotToolbar.py b/odmtools/gui/mnuPlotToolbar.py index d969857..34e5e66 100644 --- a/odmtools/gui/mnuPlotToolbar.py +++ b/odmtools/gui/mnuPlotToolbar.py @@ -271,7 +271,7 @@ def on_toggle_zoom_data_tool(self, event): #date= [x[1] for x in self.editCurve.dataTable if x[0] != self.editCurve.noDataValue] axes = self.canvas.figure.axes[0] - axes.set_ylim(min(nodvvals["DataValue"]), max(nodvvals["DataValue"])) + axes.set_ylim(min(nodvvals["datavalue"]), max(nodvvals["datavalue"])) axes.set_xlim(dates.date2num([min(nodvvals.index), max(nodvvals.index)])) self.push_current() @@ -402,7 +402,8 @@ def SetString(self, tip): self.tooltip_string = tip def Enable(self, x): - print ("in custom tooltip set enable") + #todo fix tooltip for mac + #print ("in custom tooltip set enable") if x: self.SetTip(self.tooltip_string) else: self.SetTip("") diff --git a/odmtools/gui/mnuRibbon.py b/odmtools/gui/mnuRibbon.py old mode 100755 new mode 100644 index 0c872c8..eede1aa --- a/odmtools/gui/mnuRibbon.py +++ b/odmtools/gui/mnuRibbon.py @@ -11,7 +11,8 @@ from odmtools.controller.frmDataFilters import frmDataFilter from odmtools.controller.frmChangeValue import frmChangeValue -from frmFlagValues import frmFlagValues +# from frmFlagValues import frmFlagValues +from odmtools.controller.NewFlagValuesController import NewFlagValuesController from odmtools.controller.frmLinearDrift import frmLinearDrift from odmtools.controller.frmAbout import frmAbout from odmtools.controller.frmGapFill import frmGapFill @@ -128,6 +129,7 @@ def _init_ctrls(self, prnt): # ------------------------------------------------------------------------------- editPage = RB.RibbonPage(self, wx.ID_ANY, "Edit") + # editPage.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) main_panel = RB.RibbonPanel(editPage, wx.ID_ANY, "Main", wx.NullBitmap, wx.DefaultPosition, wx.DefaultSize, RB.RIBBON_PANEL_NO_AUTO_MINIMISE) @@ -203,14 +205,28 @@ def _init_ctrls(self, prnt): self.CurrPage = 1 self.SetActivePageByIndex(self.CurrPage) - self.bindEvents() + self.__bind_events() self.initPubSub() def __init__(self, parent, id, name): self.parent = parent self._init_ctrls(parent) - def bindEvents(self): + def on_mouse_enter(self, event): + ribbon_panel = event.GetEventObject().GetParent() + ribbon_panel._hovered = True + + self.Refresh() + event.Skip() + + def on_mouse_leave(self, event): + ribbon_panel = event.GetEventObject().GetParent() + ribbon_panel._hovered = False + + self.Refresh() + event.Skip() + + def __bind_events(self): ###Docking Window Selection self.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, self.onDocking, id=wxID_RIBBONVIEWTABLE) self.Bind(RB.EVT_RIBBONBUTTONBAR_CLICKED, self.onDocking, id=wxID_RIBBONVIEWSERIES) @@ -261,6 +277,24 @@ def bindEvents(self): ###Ribbon Event self.Bind(RB.EVT_RIBBONBAR_PAGE_CHANGED, self.onFileMenu, id=wxID_PANEL1) + # ENTER + self.main_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 1 + self.edit_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 2 + self.record_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 3 + self.PlotsOptions_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 4 + self.plots_bar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 5 + self.dateTime_buttonbar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 6 + self.scriptBar.Bind(wx.EVT_ENTER_WINDOW, self.on_mouse_enter) # 7 + + # LEAVE + self.main_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 1 + self.edit_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 2 + self.record_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 3 + self.PlotsOptions_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 4 + self.plots_bar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 5 + self.dateTime_buttonbar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 6 + self.scriptBar.Bind(wx.EVT_LEAVE_WINDOW, self.on_mouse_leave) # 7 + def initPubSub(self): Publisher.subscribe(self.toggleEditButtons, "EnableEditButtons") Publisher.subscribe(self.enableButtons, "EnablePlotButtons") @@ -395,7 +429,7 @@ def onEditFilter(self, event): data_filter = frmDataFilter(self.parent, self.parent.getRecordService()) if data_filter.Show() == wx.OK: - print "OK" + # print "OK" data_filter.Destroy() event.Skip() @@ -428,7 +462,12 @@ def onEditInterpolate(self, event): "You have chosen to interpolate the %s selected points.\nDo you want to continue?" % len(dataframe), 'Interpolation', wx.YES_NO | wx.ICON_QUESTION | wx.CENTRE, parent=self.parent) if val == 2: # wx.ID_YES: - self.parent.getRecordService().interpolate() + try: + self.parent.getRecordService().interpolate() + except Exception as e: + dial = wx.MessageDialog(None, "Unable perform interplation %s" % e, "Bad Input", + wx.OK | wx.ICON_ERROR) + dial.ShowModal() event.Skip() @@ -439,15 +478,17 @@ def onEditFlag(self, event): serviceManager = self.parent.getDBService() series_service = serviceManager.get_series_service() - qualifierChoices = OrderedDict((x.code + '-' + x.description, x.id) for x in series_service.get_all_qualifiers() - if x.code and x.description) - add_flag = frmFlagValues(self.parent, series_service, qualifierChoices) - val = add_flag.ShowModal() - - if val == wx.ID_OK: - logger.debug("FLAG Value: %s, type: %s" % (val, type(val))) - self.parent.getRecordService().flag(add_flag.GetValue()) - add_flag.Destroy() + qualifierChoices = OrderedDict((x.AnnotationCode + '-' + x.AnnotationText, x.AnnotationID) for x in series_service.get_all_qualifiers() + if x.AnnotationCode and x.AnnotationText) + # add_flag = frmFlagValues(self.parent, series_service, qualifierChoices) + add_flag_controller = NewFlagValuesController(self, series_service, qualifierChoices, self.parent.getRecordService()) + # val = add_flag.ShowModal() + add_flag_controller.Show() + + # if val == wx.ID_OK: + # logger.debug("FLAG Value: %s, type: %s" % (val, type(val))) + # self.parent.getRecordService().flag(add_flag.GetValue()) + # add_flag.Destroy() event.Skip() # ################################### @@ -521,7 +562,6 @@ def onEditSeries(self, event=None): else: Publisher.sendMessage(("stopEdit"), event=event) - event.Skip() def onBinChanged(self, event): @@ -610,7 +650,7 @@ def onPlotSelection(self, event): value = 3 elif event.Id == wxID_RIBBONPLOTSUMMARY: value = 4 - # TODO fix case where the plot enables the buttons without checking if series is actually selected + # TODO fix case where the plot enables the buttons without checking if series_service is actually selected self.enableButtons(value, True) Publisher.sendMessage(("select.Plot"), value=value) event.Skip() @@ -652,7 +692,7 @@ def enableButtons(self, plot, isActive): self.spnBins.Enabled = False self.enableDateSelection(False) - ##tims series or probability + ##tims series_service or probability elif plot == 0 or plot == 1: self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSTYPE, True) self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSLEGEND, True) @@ -660,7 +700,7 @@ def enableButtons(self, plot, isActive): self.spnBins.Enabled = False self.enableDateSelection(True) - ##HIstogram + ##Histogram elif plot == 2: self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSTYPE, False) self.PlotsOptions_bar.EnableButton(wxID_RIBBONPLOTTSLEGEND, False) @@ -687,7 +727,7 @@ def enableButtons(self, plot, isActive): # TODO change states when points are selected rather than all at once def toggleEditButtons(self, state): - # edit when series is selected for editing + # edit when series_service is selected for editing self.main_bar.EnableButton(wxID_RIBBONEDITRESTORE, state) self.main_bar.EnableButton(wxID_RIBBONEDITSAVE, state) self.edit_bar.EnableButton(wxID_RIBBONEDITFILTER, state) diff --git a/odmtools/gui/pageMethod.py b/odmtools/gui/pageMethod.py deleted file mode 100644 index df72837..0000000 --- a/odmtools/gui/pageMethod.py +++ /dev/null @@ -1,135 +0,0 @@ -#Boa:FramePanel:pnlMethods - -import wx -import wx.grid -import wx.richtext -from odmtools.odmdata import Method - -[wxID_PNLMETHOD, wxID_PNLMETHODSLISTCTRL1, wxID_PNLMETHODSRBCREATENEW, - wxID_PNLMETHODSRBGENERATE, wxID_PNLMETHODSRBSELECT, - wxID_PNLMETHODSRICHTEXTCTRL1, -] = [wx.NewId() for _init_ctrls in range(6)] - -# from odmtools.common.logger import LoggerTool -import logging -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - - -class pnlMethod(wx.Panel): - def _init_ctrls(self, prnt): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLMETHOD, name=u'pnlMethod', - parent=prnt, pos=wx.Point(135, 307), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL) - self.SetClientSize(wx.Size(423, 319)) - - self.rbGenerate = wx.RadioButton(id=wxID_PNLMETHODSRBGENERATE, - label=u'Automatically generate a Method ', name=u'rbGenerate', - parent=self, pos=wx.Point(16, 8), size=wx.Size(392, 16), style=0) - self.rbGenerate.SetValue(True) - self.rbGenerate.Bind(wx.EVT_RADIOBUTTON, self.OnRbGenerateRadiobutton, - id=wxID_PNLMETHODSRBGENERATE) - - self.rbSelect = wx.RadioButton(id=wxID_PNLMETHODSRBSELECT, - label=u'Select an existing Method', name=u'rbSelect', parent=self, - pos=wx.Point(16, 32), size=wx.Size(392, 13), style=0) - self.rbSelect.SetValue(False) - self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, - id=wxID_PNLMETHODSRBSELECT) - - self.rbCreateNew = wx.RadioButton(id=wxID_PNLMETHODSRBCREATENEW, - label=u'Create a new Method ', name=u'rbCreateNew', parent=self, - pos=wx.Point(16, 208), size=wx.Size(392, 13), style=0) - self.rbCreateNew.SetValue(False) - self.rbCreateNew.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateNewRadiobutton, - id=wxID_PNLMETHODSRBCREATENEW) - - self.txtMethodDescrip = wx.richtext.RichTextCtrl(id=wxID_PNLMETHODSRICHTEXTCTRL1, - parent=self, pos=wx.Point(16, 224), size=wx.Size(392, 84), - style=wx.richtext.RE_MULTILINE, value=u'Method Description') - self.txtMethodDescrip.Enable(False) - self.txtMethodDescrip.Bind(wx.EVT_SET_FOCUS, self.OnTxtMethodDescripSetFocus) - self.txtMethodDescrip.Bind(wx.EVT_KILL_FOCUS, self.OnTxtMethodDescripKillFocus) - - self.lstMethods = wx.ListCtrl(id=wxID_PNLMETHODSLISTCTRL1, - name='lstMethods', parent=self, pos=wx.Point(16, 48), - size=wx.Size(392, 152), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.lstMethods.Bind(wx.EVT_SET_FOCUS, self.OnLstMethodSetFocus) - - - self.lstMethods.InsertColumn(0, 'Description') - self.lstMethods.InsertColumn(1, 'Link') - self.lstMethods.InsertColumn(2, 'id') - self.lstMethods.SetColumnWidth(0, 200) - self.lstMethods.SetColumnWidth(1, 153) - self.lstMethods.SetColumnWidth(2,0) - # self.lstMethods.Enable(False) - - - - - def __init__(self, parent, id, pos, size, style, name, ss, method): - self.series_service = ss - self.prev_val = method - self._init_ctrls(parent) - - def OnLstMethodSetFocus(self, event): - self.rbSelect.SetValue(True) - - def OnRbGenerateRadiobutton(self, event): - # self.lstMethods.Enable(False) - self.txtMethodDescrip.Enable(False) - - event.Skip() - - def OnRbSelectRadiobutton(self, event): - self.lstMethods.Enable(True) - self.txtMethodDescrip.Enable(False) - - event.Skip() - - def OnRbCreateNewRadiobutton(self, event): - # self.lstMethods.Enable(False) - self.txtMethodDescrip.Enable(True) - - event.Skip() - - def OnTxtMethodDescripSetFocus(self, event): - if self.txtMethodDescrip.GetValue() =="Method Description": - self.txtMethodDescrip.SetValue("") - - event.Skip() - - def OnTxtMethodDescripKillFocus(self, event): - if self.txtMethodDescrip.GetValue() =="": - self.txtMethodDescrip.SetValue("Method Description") - - event.Skip() - - - def getMethod(self): - - m = Method() - if self.rbGenerate.Value: - genmethod = "Values derived from ODM Tools Python" - m= self.series_service.get_method_by_description(genmethod) - if m is None: - logger.debug("assigning new method description") - m = Method() - m.description = genmethod - - elif self.rbSelect.Value: - index = self.lstMethods.GetFirstSelected() - desc= self.lstMethods.GetItem(index, 0).GetText() - - logger.debug(desc) - m= self.series_service.get_method_by_description(desc) - - - - elif self.rbCreateNew.Value: - logger.debug("assigning new method description") - m.description = self.txtMethodDescrip.GetValue() - return m \ No newline at end of file diff --git a/odmtools/gui/pageQCL.py b/odmtools/gui/pageQCL.py deleted file mode 100644 index 57f4970..0000000 --- a/odmtools/gui/pageQCL.py +++ /dev/null @@ -1,133 +0,0 @@ -#Boa:FramePanel:pnlQCL - -import wx -from odmtools.odmdata import QualityControlLevel - -[wxID_PNLQCL, wxID_PNLQCLLBLCODE, wxID_PNLQCLLBLDEFINITION, - wxID_PNLQCLLBLEXPLANATION, wxID_PNLQCLLSTQCL, wxID_PNLQCLRBCREATE, - wxID_PNLQCLRBSELECT, wxID_PNLQCLTXTCODE, wxID_PNLQCLTXTDEFINITION, - wxID_PNLQCLTXTEXPLANATION, -] = [wx.NewId() for _init_ctrls in range(10)] - -# from odmtools.common.logger import LoggerTool -import logging -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -class pnlQCL(wx.Panel): - def _init_ctrls(self, prnt): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLQCL, name=u'pnlQCL', parent=prnt, - pos=wx.Point(589, 303), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL) - self.SetClientSize(wx.Size(423, 319)) - - self.rbSelect = wx.RadioButton(id=wxID_PNLQCLRBSELECT, - label=u'Select an existing Quality Control Level', - name=u'rbSelect', parent=self, pos=wx.Point(16, 8), - size=wx.Size(392, 13), style=0) - self.rbSelect.SetValue(True) - self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, - id=wxID_PNLQCLRBSELECT) - - self.rbCreate = wx.RadioButton(id=wxID_PNLQCLRBCREATE, - label=u'Create Quality Control Level', name=u'rbCreate', - parent=self, pos=wx.Point(16, 168), size=wx.Size(392, 13), - style=0) - self.rbCreate.SetValue(False) - self.rbCreate.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateRadiobutton, - id=wxID_PNLQCLRBCREATE) - - self.lblCode = wx.StaticText(id=wxID_PNLQCLLBLCODE, label=u'Code:', - name=u'lblCode', parent=self, pos=wx.Point(40, 184), - size=wx.Size(30, 13), style=0) - - self.txtCode = wx.TextCtrl(id=wxID_PNLQCLTXTCODE, name=u'txtCode', - parent=self, pos=wx.Point(88, 184), size=wx.Size(320, 21), - style=0, value=u'') - self.txtCode.Enable(False) - - self.lblDefinition = wx.StaticText(id=wxID_PNLQCLLBLDEFINITION, - label=u'Definition:', name=u'lblDefinition', parent=self, - pos=wx.Point(24, 216), size=wx.Size(50, 13), style=0) - - self.txtDefinition = wx.TextCtrl(id=wxID_PNLQCLTXTDEFINITION, - name=u'txtDefinition', parent=self, pos=wx.Point(88, 216), - size=wx.Size(320, 21), style=0, value=u'') - self.txtDefinition.Enable(False) - - self.lstQCL = wx.ListCtrl(id=wxID_PNLQCLLSTQCL, name=u'lstQCL', - parent=self, pos=wx.Point(16, 24), size=wx.Size(392, 136), - style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.lstQCL.InsertColumn(0, 'Code') - self.lstQCL.InsertColumn(1, 'Definition') - self.lstQCL.InsertColumn(2, 'Explanation') - self.lstQCL.InsertColumn(3, 'id') - self.lstQCL.SetColumnWidth(0, 50) - self.lstQCL.SetColumnWidth(1, 50) - self.lstQCL.SetColumnWidth(2, 200) - self.lstQCL.SetColumnWidth(3, 0) - - - - self.lstQCL.Bind(wx.EVT_LIST_ITEM_SELECTED, - self.OnListCtrl1ListItemSelected, id=wxID_PNLQCLLSTQCL) - self.lstQCL.Enable(True) - - self.txtExplanation = wx.TextCtrl(id=wxID_PNLQCLTXTEXPLANATION, - name=u'txtExplanation', parent=self, pos=wx.Point(88, 248), - size=wx.Size(320, 64), style=wx.TE_MULTILINE | wx.TE_WORDWRAP, - value=u'') - self.txtExplanation.Enable(False) - - self.lblExplanation = wx.StaticText(id=wxID_PNLQCLLBLEXPLANATION, - label=u'Explanation:', name=u'lblExplanation', parent=self, - pos=wx.Point(16, 248), size=wx.Size(61, 13), style=0) - - def __init__(self, parent, id, pos, size, style, name, ss, qcl): - self.series_service = ss - self.prev_val = qcl - self._init_ctrls(parent) - - def OnRbSelectRadiobutton(self, event): - self.txtExplanation.Enable(False) - self.txtDefinition.Enable(False) - self.txtCode.Enable(False) - self.lstQCL.Enable(True) - event.Skip() - - def OnRbCreateRadiobutton(self, event): - self.txtExplanation.Enable(True) - self.txtDefinition.Enable(True) - self.txtCode.Enable(True) - self.lstQCL.Enable(False) - event.Skip() - - def OnListCtrl1ListItemSelected(self, event): - event.Skip() - - def GetLstSelection(self): - return self.lstQCL.GetFirstSelected() - - def getQCL(self): - q = QualityControlLevel() - if self.rbCreate.Value: - q.code = self.txtCode.Value - q.definition= self.txtDefinition.Value - q.explanation = self.txtExplanation.Value - - elif self.rbSelect.Value: - index = self.GetLstSelection() - logger.debug("lstQCL: %s" %(self.lstQCL)) - code= self.lstQCL.GetItem(index, 0).GetText() - logger.debug(code) - q= self.series_service.get_qcl_by_code(code) - -## q.id = self.lstQCL.GetItem(index,3).GetText() -## q.code = self.lstQCL.GetItem(index, 0).GetText() -## q.definition= self.lstQCL.GetItem(index, 1).GetText() -## q.explanation=self.lstQCL.GetItem(index, 2).GetText() - - return q - diff --git a/odmtools/gui/pageSummary.py b/odmtools/gui/pageSummary.py index a9a9be2..bdd8a14 100644 --- a/odmtools/gui/pageSummary.py +++ b/odmtools/gui/pageSummary.py @@ -31,8 +31,6 @@ def _init_ctrls(self, prnt): parent=self, pos=wx.Point(0, 0), size=wx.Size(423, 319), style=wx.TR_HAS_BUTTONS|wx.TR_HIDE_ROOT) - - self._init_sizers() def __init__(self, parent, id, size, style, name, ss, pos= (0,0)): @@ -40,21 +38,19 @@ def __init__(self, parent, id, size, style, name, ss, pos= (0,0)): self._init_ctrls(parent) - -class MyTree(wx.TreeCtrl): - +class MyTree(wx.TreeCtrl): # Who's tree is this? Who said it was theirs? def __init__(self, parent, id, pos, size, style): wx.TreeCtrl.__init__(self, parent, id, pos, size, style) self.root = self.AddRoot('Series') - self.s = self.AppendItem(self.root, 'Site') + self.site = self.AppendItem(self.root, 'Site') self.v = self.AppendItem(self.root, 'Variable') self.m = self.AppendItem(self.root, 'Method') - self.so= self.AppendItem(self.root, 'Source') - self.q = self.AppendItem(self.root, 'Quality Control Level') + self.action = self.AppendItem(self.root, 'Action') + self.processing_level = self.AppendItem(self.root, 'Processing Level') - self.sc=self.AppendItem(self.s, 'Code: ') - self.sn=self.AppendItem(self.s, 'Name: ') + self.sc=self.AppendItem(self.site, 'Code: ') + self.sn=self.AppendItem(self.site, 'Name: ') self.vc=self.AppendItem(self.v, 'Code: ') self.vn=self.AppendItem(self.v, 'Name: ') @@ -68,11 +64,11 @@ def __init__(self, parent, id, pos, size, style): self.md=self.AppendItem(self.m, 'Description: ') - self.soo=self.AppendItem(self.so, 'Organization: ') - self.sod=self.AppendItem(self.so, 'Description: ') - self.soc=self.AppendItem(self.so, 'Citation: ') + # self.soc=self.AppendItem(self.action, 'Person: ') + self.soo=self.AppendItem(self.action, 'Organization: ') + self.sod=self.AppendItem(self.action, 'Description: ') - self.qc=self.AppendItem(self.q, 'Code: ') - self.qd=self.AppendItem(self.q, 'Definition: ') - self.qe=self.AppendItem(self.q, 'Explanation: ') + self.qc=self.AppendItem(self.processing_level, 'Code: ') + self.qd=self.AppendItem(self.processing_level, 'Definition: ') + self.qe=self.AppendItem(self.processing_level, 'Explanation: ') diff --git a/odmtools/gui/pageVariable.py b/odmtools/gui/pageVariable.py deleted file mode 100644 index d14c90b..0000000 --- a/odmtools/gui/pageVariable.py +++ /dev/null @@ -1,173 +0,0 @@ -#Boa:FramePanel:pnlVariable - -import wx -from odmtools.controller.frmCreateVariable import frmCreateVariable -from odmtools.odmdata import Variable - -[wxID_PNLVARIABLE, wxID_PNLVARIABLELSTVARIABLE, wxID_PNLVARIABLERBCREATE, - wxID_PNLVARIABLERBCURRENT, wxID_PNLVARIABLERBSELECT,wxID_PNLVARIABLETXTNEWVAR, -] = [wx.NewId() for _init_ctrls in range(6)] - -# from odmtools.common.logger import LoggerTool -import logging -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -class pnlVariable(wx.Panel): - def _init_ctrls(self, prnt): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLVARIABLE, name=u'pnlVariable', - parent=prnt, pos=wx.Point(1034, 305), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL) - self.SetClientSize(wx.Size(423, 319)) - - self.rbCurrent = wx.RadioButton(id=wxID_PNLVARIABLERBCURRENT, - label=u'Use Current Variable', name=u'rbCurrent', parent=self, - pos=wx.Point(16, 16), size=wx.Size(384, 13), style=0) - self.rbCurrent.SetValue(True) - self.rbCurrent.Bind(wx.EVT_RADIOBUTTON, self.OnRbCurrentRadiobutton, - id=wxID_PNLVARIABLERBCURRENT) - - self.rbSelect = wx.RadioButton(id=wxID_PNLVARIABLERBSELECT, - label=u'Select an existing Variable', name=u'rbSelect', - parent=self, pos=wx.Point(16, 56), size=wx.Size(384, 16), - style=0) - self.rbSelect.SetValue(False) - self.rbSelect.Bind(wx.EVT_RADIOBUTTON, self.OnRbSelectRadiobutton, - id=wxID_PNLVARIABLERBSELECT) - - self.rbCreate = wx.RadioButton(id=wxID_PNLVARIABLERBCREATE, - label=u'Create New Variable', name=u'rbCreate', parent=self, - pos=wx.Point(16, 256), size=wx.Size(368, 13), style=0) - self.rbCreate.SetValue(False) - self.rbCreate.Bind(wx.EVT_RADIOBUTTON, self.OnRbCreateRadiobutton, - id=wxID_PNLVARIABLERBCREATE) - - - self.lstVariable = wx.ListCtrl(id=wxID_PNLVARIABLELSTVARIABLE, - name=u'lstVariable', parent=self, pos=wx.Point(16, 80), - size=wx.Size(392, 160), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.lstVariable.InsertColumn(0, 'Code') - self.lstVariable.InsertColumn(1, 'Name') - self.lstVariable.InsertColumn(2, 'Speciation') - self.lstVariable.InsertColumn(3, 'Units') - self.lstVariable.InsertColumn(4, 'Sample Medium') - self.lstVariable.InsertColumn(5, 'Value Type') - self.lstVariable.InsertColumn(6, 'IsRegular') - self.lstVariable.InsertColumn(7, 'Time Support') - self.lstVariable.InsertColumn(8, 'Time Units') - self.lstVariable.InsertColumn(9, 'DataType') - self.lstVariable.InsertColumn(10, 'General Category') - self.lstVariable.InsertColumn(11, 'NoDataValue') - self.lstVariable.InsertColumn(12, 'id') - self.lstVariable.SetColumnWidth(0, 50) - self.lstVariable.SetColumnWidth(1, 100) - self.lstVariable.SetColumnWidth(12,0) - - - - self.lstVariable.Bind(wx.EVT_LIST_ITEM_SELECTED, - self.OnListCtrl1ListItemSelected, id=wxID_PNLVARIABLELSTVARIABLE) - - self.lstVariable.Enable(False) - ''' - self.txtNewVar = wx.TextCtrl(id=wxID_PNLVARIABLETXTNEWVAR, - name=u'txtNewVar', parent=self, pos=wx.Point(16, - 276), size=wx.Size(392, 21), style=0, value=u'') - ''' - - self.txtNewVar = wx.ListCtrl(id=wxID_PNLVARIABLELSTVARIABLE, - name=u'txtNewVar', parent=self, pos=wx.Point(16, 276), - size=wx.Size(392, 70), style=wx.LC_REPORT|wx.LC_SINGLE_SEL) - self.txtNewVar.InsertColumn(0, 'Code') - self.txtNewVar.InsertColumn(1, 'Name') - self.txtNewVar.InsertColumn(2, 'Speciation') - self.txtNewVar.InsertColumn(3, 'Units') - self.txtNewVar.InsertColumn(4, 'Sample Medium') - self.txtNewVar.InsertColumn(5, 'Value Type') - self.txtNewVar.InsertColumn(6, 'IsRegular') - self.txtNewVar.InsertColumn(7, 'Time Support') - self.txtNewVar.InsertColumn(8, 'Time Units') - self.txtNewVar.InsertColumn(9, 'DataType') - self.txtNewVar.InsertColumn(10, 'General Category') - self.txtNewVar.InsertColumn(11, 'NoDataValue') - self.txtNewVar.SetColumnWidth(0, 50) - self.txtNewVar.SetColumnWidth(1, 100) - self.txtNewVar.Enable(False) - - def __init__(self, parent, id, pos, size, style, name, sm, var): - self.prev_val= var - self.service_man = sm - self.series_service = sm.get_series_service() - self._init_ctrls(parent) - - def OnRbCurrentRadiobutton(self, event): - self.lstVariable.Enable(False) - self.txtNewVar.Enable(False) - - event.Skip() - - def OnRbSelectRadiobutton(self, event): - self.lstVariable.Enable(True) - self.txtNewVar.Enable(False) - - event.Skip() - - def OnRbCreateRadiobutton(self, event): - self.lstVariable.Enable(False) - - - create_Var = frmCreateVariable(self, self.service_man, self.prev_val) - returnVal = create_Var.ShowModal() - self.createdVar= create_Var.getVariable() - create_Var.Destroy() - - - # TODO if cancelled return to previous radio button - # else enable text box and enter the text info. - # get Variable object - if returnVal == wx.ID_CANCEL: - self.rbCurrent.SetValue(True) - else: - self.show_new_var(self.createdVar) - event.Skip() - - def OnListCtrl1ListItemSelected(self, event): - event.Skip() - - def getVariable(self): - - v = Variable() - if self.rbCurrent.Value: - v= self.prev_val - elif self.rbSelect.Value: - index = self.lstVariable.GetFirstSelected() - code= self.lstVariable.GetItem(index, 0).GetText() - logger.debug(code) - v= self.series_service.get_variable_by_code(code) - elif self.rbCreate.Value: - v = self.createdVar - return v - - - def show_new_var(self, var): - - self.txtNewVar.InsertStringItem(0, str(var.code)) - self.txtNewVar.SetStringItem(0, 1, str(var.name)) - self.txtNewVar.SetStringItem(0, 2, str(var.speciation)) - self.txtNewVar.SetStringItem(0, 3, str(var.variable_unit.name)) - self.txtNewVar.SetStringItem(0, 4, str(var.sample_medium)) - self.txtNewVar.SetStringItem(0, 5, str(var.value_type)) - self.txtNewVar.SetStringItem(0, 6, str(var.is_regular)) - self.txtNewVar.SetStringItem(0, 7, str(var.time_support)) - self.txtNewVar.SetStringItem(0, 8, str(var.time_unit.name)) - self.txtNewVar.SetStringItem(0, 9, str(var.data_type)) - self.txtNewVar.SetStringItem(0, 10, str(var.general_category)) - self.txtNewVar.SetStringItem(0, 11, str(var.no_data_value)) - #self.txtNewVar.SetStringItem(num_items, 12, str(var.id)) - - - self.txtNewVar.Focus(0) - self.txtNewVar.Select(0) - self.txtNewVar.Enable(True) diff --git a/odmtools/gui/plotBoxWhisker.py b/odmtools/gui/plotBoxWhisker.py index ea6ef84..3058d7d 100644 --- a/odmtools/gui/plotBoxWhisker.py +++ b/odmtools/gui/plotBoxWhisker.py @@ -116,17 +116,17 @@ def _createPlot(self, oneSeries, rows, cols, index): ax.scatter([range(1, len(med) + 1)], med, marker='s', c="k", s=10) # bp = onSeries.dataTable.boxplot( - bp = oneSeries.dataTable[oneSeries.dataTable["DataValue"]<>oneSeries.noDataValue].boxplot(column="DataValue", ax=ax, by=oneSeries.BoxWhisker.currinterval.groupby, + bp = oneSeries.dataTable[oneSeries.dataTable["datavalue"]<>oneSeries.noDataValue].boxplot(column="datavalue", ax=ax, by=oneSeries.BoxWhisker.currinterval.groupby, rot=35, notch=True, sym="-s", conf_intervals=ci, return_type='dict', grid=False) # Set Colors of the Box Whisker plot try: - plt.setp(bp['DataValue']['whiskers'], color='k', linestyle='-') - plt.setp(bp['DataValue']['medians'], color='k', linestyle='-') - plt.setp(bp['DataValue']['boxes'], color='GREY', linestyle='-') - plt.setp(bp['DataValue']['caps'], color='k') - plt.setp(bp['DataValue']['fliers'], markersize=3.5, color=oneSeries.color) + plt.setp(bp['datavalue']['whiskers'], color='k', linestyle='-') + plt.setp(bp['datavalue']['medians'], color='k', linestyle='-') + plt.setp(bp['datavalue']['boxes'], color='GREY', linestyle='-') + plt.setp(bp['datavalue']['caps'], color='k') + plt.setp(bp['datavalue']['fliers'], markersize=3.5, color=oneSeries.color) except: plt.setp(bp['whiskers'], color='k', linestyle='-') plt.setp(bp['medians'], color='k', linestyle='-') @@ -169,22 +169,22 @@ def setColor(self, color): def monthly(self, str): # print "monthly" - self.seriesPlotInfo.setBoxInterval("Month") + self.seriesPlotInfo.setBoxInterval("month") self.updatePlot() def seasonaly(self, str): # print"seasonal" - self.seriesPlotInfo.setBoxInterval("Season") + self.seriesPlotInfo.setBoxInterval("season") self.updatePlot() def yearly(self, str): # print "yearly" - self.seriesPlotInfo.setBoxInterval("Year") + self.seriesPlotInfo.setBoxInterval("year") self.updatePlot() def overall(self, str): # print "overall" - self.seriesPlotInfo.setBoxInterval("Overall") + self.seriesPlotInfo.setBoxInterval("overall") self.updatePlot() diff --git a/odmtools/gui/plotHistogram.py b/odmtools/gui/plotHistogram.py index a3a8231..6070da7 100644 --- a/odmtools/gui/plotHistogram.py +++ b/odmtools/gui/plotHistogram.py @@ -105,7 +105,7 @@ def _createPlot(self, oneSeries, rows, cols, index): # oneSeries.filteredData.hist(ax= ax, color='k', alpha=0.5, bins=50) - his = oneSeries.dataTable.hist(column="DataValue", ax=ax, bins=self.bins, + his = oneSeries.dataTable.hist(column="datavalue", ax=ax, bins=self.bins, facecolor=oneSeries.color, label=oneSeries.siteName + " " + oneSeries.variableName, grid=False) diff --git a/odmtools/gui/plotProbability.py b/odmtools/gui/plotProbability.py index 7cf1551..bcc3ddc 100644 --- a/odmtools/gui/plotProbability.py +++ b/odmtools/gui/plotProbability.py @@ -133,16 +133,12 @@ def updatePlot(self): self.plots.set_title("\n".join(textwrap.wrap(oneSeries.siteName, 55))) if len(oneSeries.dataTable) > 0: - #self.prob.append( - #prop = oneSeries.Probability.plot(column="DataValue", ax=self.plots) - #todo FutureWarning: order is deprecated, use sort_values(...) - #xValues = oneSeries.Probability.xAxis.order().values - xValues = oneSeries.Probability.xAxis.order().values - # yValues = oneSeries.Probability.yAxis.order().values - yValues = oneSeries.Probability.yAxis.order().values + xValues = oneSeries.Probability.xAxis.sort_values().values + yValues = oneSeries.Probability.yAxis.sort_values().values ax = self.plots.plot(xValues, yValues, 'bs', color=oneSeries.color, label=oneSeries.plotTitle) + self.axislist[oneSeries.axisTitle] = ax[0] self.setXaxis() diff --git a/odmtools/gui/plotSummary.py b/odmtools/gui/plotSummary.py index 095f342..12e07fb 100644 --- a/odmtools/gui/plotSummary.py +++ b/odmtools/gui/plotSummary.py @@ -110,7 +110,7 @@ def resizeLabel(self): def clear(self): self.btnExport.Enable(False) if self.grdSummary.GetNumberCols() > 0: - # for col in range(self.grdSummary.GetNumberCols()) + # for columns in range(self.grdSummary.GetNumberCols()) self.grdSummary.DeleteCols(pos=0, numCols=self.grdSummary.GetNumberCols(), updateLabels=True) @@ -149,7 +149,7 @@ def addCol(self, series): self.grdSummary.AppendCols(numCols=1, updateLabels=True) col = self.grdSummary.GetNumberCols() - 1 self.setColLabel(col, series.siteName + "- " + series.variableName) - #self.grdSummary.AutoSizeColLabelSize(col) + #self.grdSummary.AutoSizeColLabelSize(columns) stats = series.Statistics count = stats.NumberofObservations diff --git a/odmtools/gui/plotTimeSeries.py b/odmtools/gui/plotTimeSeries.py old mode 100755 new mode 100644 index 9591598..221005e --- a/odmtools/gui/plotTimeSeries.py +++ b/odmtools/gui/plotTimeSeries.py @@ -123,7 +123,7 @@ def changePlotSelection(self, filtered_datetime): if isinstance(filtered_datetime, list): df = self.editCurve.dataTable - result = df[df['LocalDateTime'].isin(filtered_datetime)].astype(datetime.datetime) + result = df[df['valuedatetime'].isin(filtered_datetime)].astype(datetime.datetime) if isinstance(result, pd.DataFrame): if result.empty: @@ -134,7 +134,7 @@ def changePlotSelection(self, filtered_datetime): self.canvas.draw() return - values = result['DataValue'].values.tolist() + values = result['datavalue'].values.tolist() dates = result.index.astype(datetime.datetime) self.selplot = self.axislist[self.editSeries.axisTitle].scatter( dates, values, s=35, c='red', edgecolors='none', zorder=12, marker='s', alpha=1) @@ -239,7 +239,7 @@ def drawEditPlot(self, oneSeries): dates = data.index.astype(datetime.datetime) curraxis = self.axislist[oneSeries.axisTitle] - curraxis.plot_date(dates, data['DataValue'], "-s", + curraxis.plot_date(dates, data['datavalue'], "-s", color=oneSeries.color, xdate=True, label=oneSeries.plotTitle, zorder=10, alpha=1, picker=5.0, pickradius=5.0, markersize=4.5) curraxis.set_xlabel('Date') @@ -250,7 +250,7 @@ def drawEditPlot(self, oneSeries): # scale = 1.5 # f = zoom_factory(curraxis , base_scale = scale) - self.xys = zip(convertedDates, oneSeries.dataTable['DataValue']) + self.xys = zip(convertedDates, oneSeries.dataTable['datavalue']) self.toolbar.editSeries(self.xys, self.editCurve) # self.pointPick = self.canvas.mpl_connect('pick_event', self._onPick) self.editSeries = oneSeries @@ -412,7 +412,7 @@ def updatePlot(self): data = oneSeries.dataTable dates = data.index.astype(datetime.datetime) #data.plot(ax=curraxis) - curraxis.plot_date(dates, data['DataValue'], + curraxis.plot_date(dates, data['datavalue'], color=oneSeries.color, fmt=self.format, xdate=True, tz=None, antialiased=True, label=oneSeries.plotTitle, alpha=self.alpha, picker=5.0, pickradius=5.0, markersize=4) @@ -484,7 +484,7 @@ def updateCursor(self, selectedObject=None, deselectedObject=None): Activate Cursor. Happens when a plot is selected """ if self.seriesPlotInfo: - seriesInfo = self.seriesPlotInfo.getSeries(selectedObject.id) + seriesInfo = self.seriesPlotInfo.getSeries(selectedObject.ResultID) if seriesInfo: currentAxis = None diff --git a/odmtools/gui/pnlDataTable.py b/odmtools/gui/pnlDataTable.py deleted file mode 100644 index 81bbc2a..0000000 --- a/odmtools/gui/pnlDataTable.py +++ /dev/null @@ -1,215 +0,0 @@ -import wx -import wx.grid -import logging -import itertools as iter - -import pandas as pd -from odmtools.lib.ObjectListView import ColumnDefn, VirtualObjectListView, ObjectListView -from wx.lib.pubsub import pub as Publisher -import numpy as np -import timeit - - -# from odmtools.common.logger import LoggerTool -# -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - -[wxID_PNLDATATABLE, wxID_PNLDATATABLEDATAGRID, -] = [wx.NewId() for _init_ctrls in range(2)] - - -class pnlDataTable(wx.Panel): - - toggle = iter.cycle([0, 1]).next - - def __init__(self, parent): - self.parent = parent - self._init_ctrls() - - def _init_ctrls(self): - # generated method, don't edit - wx.Panel.__init__(self, id=wxID_PNLDATATABLE, name=u'pnlDataTable', - parent=self.parent, size=wx.Size(677, 449), - style=wx.TAB_TRAVERSAL) - # self.record_service = self.parent.Parent.getRecordService() - self.myOlv = VirtualObjectListView(self, style=wx.LC_REPORT) - - self.myOlv.SetEmptyListMsg("No Series Selected for Editing") - self.currentItem = None - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.myOlv, 1, wx.ALL | wx.EXPAND, 4) - self.SetSizer(sizer_2) - - self.myOlv.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected) - self.myOlv.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.onItemSelected) - - self.EnableSorting() - - Publisher.subscribe(self.onChangeSelection, "changeTableSelection") - Publisher.subscribe(self.onRefresh, "refreshTable") - Publisher.subscribe(self.onDeselectAll, "deselectAllDataTable") - - self.ascending = False - self.enableSelectDataTable = False - - self.Layout() - - - - # def toggleBindings(self): - # """ Activates/Deactivates Datatable specific bindings - # - # :param activate: - # :return: - # """ - # - # if self.toggle(): - # #logger.info("binding activated...") - # try: - # self.myOlv.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.onItemSelected, id=self.myOlv.GetId()) - # self.myOlv.Bind(wx.EVT_CHAR, self.onKeyPress, id=self.myOlv.GetId()) - # self.myOlv.Bind(wx.EVT_LIST_KEY_DOWN, self.onKeyPress, id=self.myOlv.GetId()) - # except: - # pass - # else: - # #logger.info("binding deactivated...") - # try: - # self.myOlv.Unbind(wx.EVT_LIST_ITEM_FOCUSED, self.onItemSelected, id=self.myOlv.GetId()) - # self.myOlv.Unbind(wx.EVT_CHAR, self.onKeyPress, id=self.myOlv.GetId()) - # self.myOlv.Unbind(wx.EVT_LIST_KEY_DOWN, self.onKeyPress, id=self.myOlv.GetId()) - # except: - # pass - - def init(self, memDB): - self.memDB = memDB - - columns = [ColumnDefn(x.strip(), align="left", valueGetter=i, minimumWidth=125, width=125, - stringConverter='%Y-%m-%d %H:%M:%S' if "date" in x.lower() else '%s') - for x, i in self.memDB.getEditColumns()] - - self.myOlv.useAlternateBackColors = True - self.myOlv.oddRowsBackColor = wx.Colour(191, 217, 217) - - '''values = self.memDB.getDataValues() - value_length = len(values) - - self.myOlvDataFrame = pd.DataFrame(values, columns=[x.title for x in columns]) - ''' - self.myOlv.SetColumns(columns) - - self.myOlvDataFrame = self.memDB.getDataValuesDF() - sort_by_index = list(self.myOlvDataFrame.columns).index("LocalDateTime") - self.myOlvDataFrame.sort(self.myOlvDataFrame.columns[sort_by_index], inplace=True) - self.dataObjects = self.myOlvDataFrame.values.tolist() - - self.myOlv.SetObjectGetter(self.objectGetter) - self.myOlv.SetItemCount(len(self.myOlvDataFrame)) - - def EnableSorting(self): - self.myOlv.Bind(wx.EVT_LIST_COL_CLICK, self.onColSelected) - self.sortedColumnIndex = -1 - - if not self.myOlv.smallImageList: - self.myOlv.SetImageLists() - if (not self.myOlv.smallImageList.HasName(ObjectListView.NAME_DOWN_IMAGE) and - self.myOlv.smallImageList.GetSize(0) == (16, 16)): - self.myOlv.RegisterSortIndicators() - - def objectGetter(self, index): - """ - A Virtual list has to have a callable installed that says which model object is shown - at a given index - """ - return self.dataObjects[index % len(self.dataObjects)] - - def onColSelected(self, evt): - """ - Allows users to sort by clicking on columns - """ - logger.debug("Column: %s" % evt.m_col) - self.sortColumn(evt.m_col) - - def sortColumn(self, selected_column): - oldSortColumnIndex = self.sortedColumnIndex - self.sortedColumnIndex = selected_column - ascending = self.myOlv.sortAscending - if ascending: - self.myOlvDataFrame.sort(self.myOlvDataFrame.columns[selected_column], inplace=True) - self.myOlv.sortAscending = False - elif not ascending: - self.myOlvDataFrame.sort(self.myOlvDataFrame.columns[selected_column], ascending=False, inplace=True) - self.myOlv.sortAscending = True - - self.myOlv._UpdateColumnSortIndicators(selected_column, oldSortColumnIndex) - - self.dataObjects = self.myOlvDataFrame.values.tolist() - if self.myOlv.GetItemCount: - itemFrom = self.myOlv.GetTopItem() - itemTo = self.myOlv.GetTopItem()+1 + self.myOlv.GetCountPerPage() - itemTo = min(itemTo, self.myOlv.GetItemCount()-1) - self.myOlv.RefreshItems(itemFrom, itemTo) - - def onRefresh(self, e): - self.myOlvDataFrame = self.memDB.getDataValuesDF() - self.dataObjects = self.myOlvDataFrame.values.tolist() - # self.myOlv.RefreshItems() - - def clear(self): - self.memDB = None - self.myOlv.DeleteAllItems() - self.myOlvDataFrame = None - self.dataObjects = None - - def onItemSelected(self, event): - """ - - Disable selecting of an item in the DataTable, only sorting is available - """ - if not self.enableSelectDataTable: - self.myOlv.SetItemState(event.m_itemIndex, 0, wx.LIST_STATE_SELECTED) - - def onDeselectAll(self): - - selected_item = self.myOlv.GetFirstSelected() - while selected_item != -1: - self.myOlv.SetItemState(selected_item, 0, wx.LIST_STATE_SELECTED) - selected_item = self.myOlv.GetNextSelected(selected_item) - - - def onChangeSelection(self, datetime_list=[]): - """ - Select values within - """ - self.onDeselectAll() - - if isinstance(datetime_list, pd.DataFrame): - try: - self.enableSelectDataTable = True - olv = self.myOlvDataFrame.set_index("LocalDateTime") - filtered_dataframe = self.myOlvDataFrame[olv.index.isin(datetime_list.index)] - results = np.where(self.myOlvDataFrame.index.isin(filtered_dataframe.index)) - - for i in results[0]: - self.myOlv.SetItemState(i, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - self.myOlv.Focus(results[0][0]) - self.enableSelectDataTable = False - except: - pass - - def onKeyPress(self, evt): - """Ignores Keypresses""" - pass - - def stopEdit(self): - self.clear() - - - - - - - - diff --git a/odmtools/gui/pnlPlot.py b/odmtools/gui/pnlPlot.py index 40b7163..0ba02b1 100644 --- a/odmtools/gui/pnlPlot.py +++ b/odmtools/gui/pnlPlot.py @@ -157,7 +157,7 @@ def onRemovePlot(self, seriesID): def onRemovePlots(self, seriesIDs): for series in seriesIDs: - self._seriesPlotInfo.update(series.id, False) + self._seriesPlotInfo.update(series.ResultID, False) self.redrawPlots() def redrawPlots(self): diff --git a/odmtools/gui/wizSave.py b/odmtools/gui/wizSave.py index 4233854..a057ad2 100644 --- a/odmtools/gui/wizSave.py +++ b/odmtools/gui/wizSave.py @@ -1,14 +1,12 @@ -# Boa:Wizard:wizSave - import wx import wx.wizard as wiz - -# import * from WizardPanels from odmtools.controller import pageIntro, pageExisting -import pageMethod -import pageQCL -import pageVariable import pageSummary +from odm2api.ODM2.models import * +from odmtools.controller.WizardMethodController import WizardMethodController +from odmtools.controller.WizardProcessLevelController import WizardProcessLevelController +from odmtools.controller.WizardVariableController import WizardVariableController +from odmtools.controller.WizardActionController import WizardActionController [wxID_PNLINTRO, wxID_PNLVARIABLE, wxID_PNLMETHOD, wxID_PNLQCL, wxID_PNLSUMMARY, wxID_WIZSAVE, wxID_PNLEXISTING, @@ -17,134 +15,8 @@ from wx.lib.pubsub import pub as Publisher from odmtools.common.logger import LoggerTool import logging +logger = logging.getLogger('main') -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') - - -######################################################################## -class QCLPage(wiz.WizardPageSimple): - def __init__(self, parent, title, series_service, qcl): - """Constructor""" - wiz.WizardPageSimple.__init__(self, parent) - - sizer = wx.BoxSizer(wx.VERTICAL) - self.sizer = sizer - self.SetSizer(sizer) - self.qcl = qcl - - title = wx.StaticText(self, -1, title) - title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) - sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) - sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) - self.panel = pageQCL.pnlQCL(self, id=wxID_PNLQCL, name=u'pnlQCL', - pos=wx.Point(536, 285), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL, ss=series_service, qcl=qcl) - self.sizer.Add(self.panel, 85, wx.ALL, 5) - - self._init_data(self.panel.series_service) - - def _init_data(self, series): - qcl = series.get_all_qcls() - index = 0 - for q, i in zip(qcl, range(len(qcl))): - num_items = self.panel.lstQCL.GetItemCount() - self.panel.lstQCL.InsertStringItem(num_items, str(q.code)) - self.panel.lstQCL.SetStringItem(num_items, 1, str(q.definition)) - self.panel.lstQCL.SetStringItem(num_items, 2, str(q.explanation)) - self.panel.lstQCL.SetStringItem(num_items, 3, str(q.id)) - if q.code == self.qcl.code: - index = i - self.panel.lstQCL.Focus(index) - self.panel.lstQCL.Select(index) - - -######################################################################## -class VariablePage(wiz.WizardPageSimple): - def __init__(self, parent, title, service_manager, var): - """Constructor""" - wiz.WizardPageSimple.__init__(self, parent) - - sizer = wx.BoxSizer(wx.VERTICAL) - self.sizer = sizer - self.SetSizer(sizer) - self.variable = var - - title = wx.StaticText(self, -1, title) - title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) - sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) - sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) - self.panel = pageVariable.pnlVariable(self, id=wxID_PNLVARIABLE, name=u'pnlVariable', - pos=wx.Point(536, 285), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL, sm=service_manager, var=var) - self.sizer.Add(self.panel, 85, wx.ALL, 5) - - self._init_data(self.panel.series_service) - - def _init_data(self, series_service): - vars = series_service.get_all_variables() - index = 0 - for v, i in zip(vars, range(len(vars))): - num_items = self.panel.lstVariable.GetItemCount() - self.panel.lstVariable.InsertStringItem(num_items, str(v.code)) - self.panel.lstVariable.SetStringItem(num_items, 1, str(v.name)) - self.panel.lstVariable.SetStringItem(num_items, 2, str(v.speciation)) - self.panel.lstVariable.SetStringItem(num_items, 3, str(v.variable_unit.name)) - self.panel.lstVariable.SetStringItem(num_items, 4, str(v.sample_medium)) - self.panel.lstVariable.SetStringItem(num_items, 5, str(v.value_type)) - self.panel.lstVariable.SetStringItem(num_items, 6, str(v.is_regular)) - self.panel.lstVariable.SetStringItem(num_items, 7, str(v.time_support)) - self.panel.lstVariable.SetStringItem(num_items, 8, str(v.time_unit.name)) - self.panel.lstVariable.SetStringItem(num_items, 9, str(v.data_type)) - self.panel.lstVariable.SetStringItem(num_items, 10, str(v.general_category)) - self.panel.lstVariable.SetStringItem(num_items, 11, str(v.no_data_value)) - self.panel.lstVariable.SetStringItem(num_items, 12, str(v.id)) - - if v.code == self.variable.code: - index = i - self.panel.lstVariable.Focus(index) - self.panel.lstVariable.Select(index) - - -######################################################################## -class MethodPage(wiz.WizardPageSimple): - def __init__(self, parent, title, series_service, method): - """Constructor""" - wiz.WizardPageSimple.__init__(self, parent) - - sizer = wx.BoxSizer(wx.VERTICAL) - self.sizer = sizer - self.SetSizer(sizer) - self.method = method - - title = wx.StaticText(self, -1, title) - title.SetFont(wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD)) - sizer.Add(title, 10, wx.ALIGN_CENTRE | wx.ALL, 5) - sizer.Add(wx.StaticLine(self, -1), 5, wx.EXPAND | wx.ALL, 5) - self.panel = pageMethod.pnlMethod(self, id=wxID_PNLMETHOD, name=u'pnlMethod', - pos=wx.Point(536, 285), size=wx.Size(439, 357), - style=wx.TAB_TRAVERSAL, ss=series_service, method=method) - self.sizer.Add(self.panel, 85, wx.ALL, 5) - - self._init_data(self.panel.series_service) - - def _init_data(self, series): - meth = series.get_all_methods() - index = 0 - for m, i in zip(meth, range(len(meth))): - num_items = self.panel.lstMethods.GetItemCount() - - #124 - self.panel.lstMethods.InsertStringItem(num_items, m.description) - self.panel.lstMethods.SetStringItem(num_items, 1, str(m.link)) - self.panel.lstMethods.SetStringItem(num_items, 2, str(m.id)) - - if m.description == self.method.description: - index = i - - self.panel.lstMethods.Focus(index) - self.panel.lstMethods.Select(index) ######################################################################## @@ -168,39 +40,29 @@ def __init__(self, parent, title, series_service): def fill_summary(self): - Site, Variable, Method, Source, QCL = self.parent.get_metadata() - - ## self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, "Code: "+ str(QCL.code)) - + sampling_feature, variable, method, action, processing_level = self.parent.get_metadata() - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sc, 'Code: ' + str(Site.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sn, 'Name: ' + str(Site.name)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sc, 'Code: ' + str(sampling_feature.SamplingFeatureCode)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sn, 'Name: ' + str(sampling_feature.SamplingFeatureName)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vc, 'Code: ' + str(Variable.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vn, 'Name: ' + str(Variable.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vu, 'Units: ' + str(Variable.variable_unit.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vsm, 'Sample Medium: ' + str(Variable.sample_medium)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vvt, 'Value Type: ' + str(Variable.value_type)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vts, 'Time Support: ' + str(Variable.time_support)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vtu, 'Time Units: ' + str(Variable.time_unit.name)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vdt, 'Data Type: ' + str(Variable.data_type)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.vgc, - 'General Category: ' + str(Variable.general_category)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vc, 'Code: ' + str(variable.VariableCode)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.vn, 'Name: ' + str(variable.VariableNameCV)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(Method.description)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.md, 'Description: ' + str(method.MethodDescription)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(Source.organization)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(Source.description)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.soc, 'Citation: ' + str(Source.citation)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.soo, 'Organization: ' + str(action.MethodObj.OrganizationObj.OrganizationName)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.sod, 'Description: ' + str(action.MethodObj.OrganizationObj.OrganizationDescription)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(QCL.code)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(QCL.definition)) - self.panel.treeSummary.SetItemText(self.panel.treeSummary.qe, 'Explanation: ' + str(QCL.explanation)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qc, 'Code: ' + str(processing_level.ProcessingLevelCode)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qd, 'Definition: ' + str(processing_level.Definition)) + self.panel.treeSummary.SetItemText(self.panel.treeSummary.qe, 'Explanation: ' + str(processing_level.Explanation)) self.panel.treeSummary.ExpandAll() ######################################################################## + + class wizSave(wx.wizard.Wizard): def _init_ctrls(self, prnt): # generated method, don't edit @@ -216,25 +78,34 @@ def _init_ctrls(self, prnt): self.Bind(wx.wizard.EVT_WIZARD_FINISHED, self.on_wizard_finished) def get_metadata(self): + method = self.__method_from_series + processing_level = self.__processing_level_from_series + variable = self.__variable_from_series + affiliation = self.action_page.get_affiliation() + site = self.__site_from_series + if self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): - logger.debug("SaveAs") - method = self.pgMethod.panel.getMethod() - qcl = self.pgQCL.panel.getQCL() - variable = self.pgVariable.panel.getVariable() - elif self.pgIntro.pnlIntroduction.rbSave.GetValue(): - logger.debug("Save") - method = self.currSeries.method - qcl = self.currSeries.quality_control_level - variable = self.currSeries.variable + # Selected a new series + method = self.pgMethod.get_method() + processing_level = self.pgQCL.get_processing_level() + variable = self.pgVariable.get_variable() + elif self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue(): - logger.debug("Existing") - method, qcl, variable = self.pgExisting.getSeries() - site = self.currSeries.site - source = self.currSeries.source - logger.debug("site: %s, variable: %s, method: %s, source: %s, qcl: %s" % ( - str(site), str(variable), str(method), str(source), str(qcl))) - return site, variable, method, source, qcl + # selected an existing series + method, processing_level, variable, result = self.pgExisting.get_selected_series() + + # Create action + action = Actions() + action.MethodObj = method + action.MethodID = method.MethodID + action.ActionDescription = self.action_page.action_view.description_text_box.GetValue() + action.ActionFileLink = self.action_page.action_view.action_file_link_text_box.GetValue() + action.MethodObj.OrganizationObj = affiliation.OrganizationObj + action.BeginDateTime = self.currSeries.ResultDateTime + action.BeginDateTimeUTCOffset = self.currSeries.ResultDateTimeUTCOffset + + return site, variable, method, action, processing_level def __init__(self, parent, service_manager, record_service): self._init_ctrls(parent) @@ -248,10 +119,21 @@ def __init__(self, parent, service_manager, record_service): self.currSeries = record_service.get_series() self.pgIntro = pageIntro.pageIntro(self, "Intro") - self.pgMethod = MethodPage(self, "Method", self.series_service, self.currSeries.method) - self.pgQCL = QCLPage(self, "Quality Control Level", self.series_service, self.currSeries.quality_control_level) - self.pgVariable = VariablePage(self, "Variable", service_manager, self.currSeries.variable) - self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.currSeries.site) + + self.__method_from_series = self.currSeries.FeatureActionObj.ActionObj.MethodObj + self.__variable_from_series = self.currSeries.VariableObj + self.__processing_level_from_series = self.currSeries.ProcessingLevelObj + self.__all_affiliations = self.series_service.get_all_affiliations() + self.__site_from_series = self.currSeries.FeatureActionObj.SamplingFeatureObj + + self.pgMethod = WizardMethodController(self, self.series_service, current_method=self.__method_from_series) + self.pgQCL = WizardProcessLevelController(self, service_manager=service_manager, current_processing_level=self.__processing_level_from_series) + self.pgVariable = WizardVariableController(self, service_manager=service_manager, current_variable=self.__variable_from_series) + self.action_page = WizardActionController(self, affiliations=self.__all_affiliations) + + self.pgExisting = pageExisting.pageExisting(self, "Existing Series", self.series_service, self.__site_from_series) + + self.pgSummary = SummaryPage(self, "Summary", self.series_service) self.FitToPage(self.pgIntro) @@ -260,7 +142,6 @@ def __init__(self, parent, service_manager, record_service): self.pgIntro.SetNext(self.pgSummary) self.pgSummary.SetPrev(self.pgIntro) - #SaveAs Pages self.pgMethod.SetPrev(self.pgIntro) self.pgMethod.SetNext(self.pgQCL) @@ -269,7 +150,10 @@ def __init__(self, parent, service_manager, record_service): self.pgQCL.SetNext(self.pgVariable) self.pgVariable.SetPrev(self.pgQCL) - self.pgVariable.SetNext(self.pgSummary) + self.pgVariable.SetNext(self.action_page) + + self.action_page.SetPrev(self.pgVariable) + self.action_page.SetNext(self.pgSummary) #Save existing page self.pgExisting.SetPrev(self.pgIntro) @@ -280,7 +164,6 @@ def __init__(self, parent, service_manager, record_service): self.Destroy() def on_page_changed(self, event): - #if isinstance(event.Page, pageSummary.pnlSummary): if event.Page == self.pgSummary: self.pgSummary.fill_summary() @@ -292,7 +175,7 @@ def on_page_changing(self, event): elif self.pgIntro.pnlIntroduction.rbSaveAs.GetValue(): self.pgIntro.SetNext(self.pgMethod) - self.pgSummary.SetPrev(self.pgVariable) + self.pgSummary.SetPrev(self.action_page) else: self.pgIntro.SetNext(self.pgExisting) @@ -303,21 +186,22 @@ def on_wizard_finishedtest(self, event): self.Close() def on_wizard_finished(self, event): - Site, Variable, Method, Source, QCL = self.get_metadata() + site, variable, method, action, proc_level = self.get_metadata() #if qcl exits use its its closeSuccessful = False - - rbSave = self.pgIntro.pnlIntroduction.rbSave.GetValue() - rbSaveAsNew = self.pgIntro.pnlIntroduction.rbSaveAs.GetValue() - rbSaveAsExisting = self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue() - if rbSaveAsExisting: - append = self.pgExisting.pnlExisting.rbAppend.GetValue() - overwrite = self.pgExisting.pnlExisting.rbOverwrite.GetValue() - if append: - original = self.pgExisting.pnlExisting.rbOriginal.GetValue() - new = self.pgExisting.pnlExisting.rbNew.GetValue() - - if QCL.id == 0 and not rbSaveAsNew: + saveSuccessful=False + + self.rbSave = self.pgIntro.pnlIntroduction.rbSave.GetValue() + self.rbSaveAsNew = self.pgIntro.pnlIntroduction.rbSaveAs.GetValue() + self.rbSaveAsExisting = self.pgIntro.pnlIntroduction.rbSaveExisting.GetValue() + if self.rbSaveAsExisting: + self.append = self.pgExisting.pnlExisting.rbAppend.GetValue() + self.overwrite = self.pgExisting.pnlExisting.rbOverwrite.GetValue() + if self.append: + self.original = self.pgExisting.pnlExisting.rbOriginal.GetValue() + self.new = self.pgExisting.pnlExisting.rbNew.GetValue() + + if proc_level.ProcessingLevelID == 0 and not self.rbSaveAsNew: """ If we're looking at a QCL with Control level 0 and the following cases: Save @@ -329,19 +213,19 @@ def on_wizard_finished(self, event): wx.YES_NO | wx.ICON_QUESTION) if val == 2: logger.info("User selected yes to save a level 0 dataset") - val_2 = wx.MessageBox("This action cannot be undone.\nAre you sure you are sure?\n", + val_2 = wx.MessageBox("This cannot be undone.\nAre you sure you are sure?\n", 'Are you REALLY sure?', wx.YES_NO | wx.ICON_QUESTION) if val_2 == 2: closeSuccessful = True - elif rbSaveAsExisting: + elif self.rbSaveAsExisting: keyword = "overwrite" if self.pgExisting.pnlExisting.rbAppend.GetValue(): keyword = "append to" - message = "You are about to " + keyword + " an existing series,\nthis action cannot be undone.\nWould you like to continue?\n" + message = "You are about to " + keyword + " an existing series_service,\nthis cannot be undone.\nWould you like to continue?\n" cont = wx.MessageBox(message, 'Are you sure?', wx.YES_NO | wx.ICON_QUESTION) if cont == 2: closeSuccessful = True @@ -351,63 +235,80 @@ def on_wizard_finished(self, event): closeSuccessful = True if closeSuccessful: - #if qcl exists use its id - if self.series_service.qcl_exists(QCL): - if QCL == self.currSeries.quality_control_level: - QCL = None - else: - QCL = self.record_service.get_qcl(QCL) - else: - QCL = self.record_service.create_qcl(QCL.code, QCL.definition, QCL.explanation) - - #if variable exists use its id - if self.series_service.variable_exists(Variable): - Variable = self.record_service.get_variable(Variable) - else: - Variable = self.record_service.create_variable(Variable) - - #if method exists use its id - if self.series_service.method_exists(Method): - if Method == self.currSeries.method: - Method = None - else: - Method = self.record_service.get_method(Method) - else: - Method = self.record_service.create_method(Method) - - # initiate either "Save as" or "Save" - ''' - if self.page1.pnlIntroduction.rbSave.GetValue(): - result = self.record_service.save(Variable, Method, QCL, False) - else: - result = self.record_service.saveAs(Variable, Method, QCL, True) - ''' - try: - if rbSave: - result = self.record_service.save() - elif rbSaveAsNew: - result = self.record_service.save_as(Variable, Method, QCL) - elif rbSaveAsExisting: - if overwrite: - result = self.record_service.save_existing(Variable, Method, QCL) - elif append: - #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): - #TODO if i require that original or new is selected I can call once with overwrite = original - if original: - result = self.record_service.save_appending(Variable, Method, QCL, overwrite = False) - elif new: - result = self.record_service.save_appending(Variable, Method, QCL, overwrite = True) - - Publisher.sendMessage("refreshSeries") - - #self.page1.pnlIntroduction.rb + saveSuccessful = self.try_to_save(variable, method, proc_level, action) except Exception as e: message = "Save was unsuccessful %s" % e.message logger.error(message) wx.MessageBox(message, "Error!", wx.ICON_ERROR | wx.ICON_EXCLAMATION) + saveSuccessful=False + + if saveSuccessful: event.Skip() self.Close() + self.Destroy() + + def create_needed_meta(self, proc_level,variable, method): + if self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) is None: + proc_level = self.series_service.create_processing_level(proc_level.ProcessingLevelCode, proc_level.Definition, proc_level.Explanation) + elif proc_level.ProcessingLevelCode == self.__processing_level_from_series.ProcessingLevelCode: + proc_level = None + else: + proc_level = self.series_service.get_processing_level_by_code(proc_level.ProcessingLevelCode) + + + + if self.series_service.get_variable_by_code(variable.VariableCode) is None: + variable = self.series_service.create_variable_by_var(variable) + else: + variable = self.series_service.get_variable_by_code(variable.VariableCode) + + + + if self.series_service.get_method_by_code(method.MethodCode) is None: + method = self.series_service.create_method(method.MethodDescription, method.MethodLink) + elif method == self.__method_from_series: + method = None + else: + method = self.series_service.get_method_by_code(method.MethodCode) + + + def try_to_save(self, variable, method, proc_level, action): + self.create_needed_meta(proc_level, variable, method) + affiliation = self.action_page.get_affiliation() + + action_by = ActionBy() + # action_by.ActionID = action.ActionID + action_by.RoleDescription = self.action_page.action_view.role_description_text_box.GetValue() + action_by.AffiliationID = affiliation.AffiliationID + action_by.AffiliationObj = affiliation + + # result = self.series_service.getResult(var=variable, meth=method, proc=proc_level, action=action, actionby=action_by) + result = self.pgExisting.pnlExisting.olvSeriesList.GetSelectedObject().ResultObj + + if self.rbSave: + result = self.record_service.save() + elif self.rbSaveAsNew: + result = self.record_service.save_as(variable=variable, method=method, proc_level=proc_level, + action=action, action_by=action_by) + elif self.rbSaveAsExisting: + if self.overwrite: + result = self.record_service.save_existing(result=result) + elif self.append: + #TODO send in just the result + #def save_appending(self, var = None, method =None, qcl = None, overwrite = False): + #TODO if i require that original or new is selected I can call once with overwrite = original + if self.original: + result = self.record_service.save_appending(result=result, overwrite=False) + elif self.new: + result = self.record_service.save_appending(result=result, overwrite=True) + + Publisher.sendMessage("refreshSeries") + return True + + + + diff --git a/odmtools/lib/ObjectListView/CellEditor.py b/odmtools/lib/ObjectListView/CellEditor.py index aa71423..e492304 100644 --- a/odmtools/lib/ObjectListView/CellEditor.py +++ b/odmtools/lib/ObjectListView/CellEditor.py @@ -489,12 +489,12 @@ def MakeAutoCompleteComboBox(olv, columnIndex, maxObjectsToConsider=10000): maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) cb = wx.ComboBox(olv, choices=list(options), - style=wx.CB_DROPDOWN|wx.CB_SORT|wx.TE_PROCESS_ENTER) + style=wx.CB_DROPDOWN|wx.TE_PROCESS_ENTER)#|wx.CB_SORT) AutoCompleteHelper(cb) return cb -#------------------------------------------------------------------------- +# ------------------------------------------------------------------------- class AutoCompleteHelper(object): """ diff --git a/odmtools/lib/ObjectListView/ListCtrlPrinter.py b/odmtools/lib/ObjectListView/ListCtrlPrinter.py index e1b3fe5..2e6e3a2 100644 --- a/odmtools/lib/ObjectListView/ListCtrlPrinter.py +++ b/odmtools/lib/ObjectListView/ListCtrlPrinter.py @@ -76,7 +76,7 @@ particular, it configures the printing DC so that its origin and scale are correct. This enables the ``ReportEngine`` to simply render the report without knowing the characteristics of the underlying printer DPI, unprintable region, or the scale of a print -preview. When The ``ListCtrlPrintout`` encounters some action that is cannot perform (like +preview. When The ``ListCtrlPrintout`` encounters some interactive_item that is cannot perform (like actually rendering a page) it calls back into ``ListCtrlPrinter`` (which simply forwards to the ``ReportEngine``). @@ -1658,7 +1658,7 @@ def DrawSelf(self, dc, bounds): right = RectUtils.Right(x.cell) dc.DrawLine(right, top, right, bottom) - # Draw the surrounding frame + # Draw the surrounding bulkInsertCtrl left = RectUtils.Left(combined[0].cell) right = RectUtils.Right(combined[-1].cell) dc.DrawRectangle(left, top, right-left, bottom-top) @@ -1879,7 +1879,7 @@ def CalculateSlices(self, maxWidth, columnWidths): """ firstColumn = 0 - # If a GroupListView has a column just for the expand/collapse, don't include it + # If a GroupListView has a column just for the is_expanded/collapse, don't include it if hasattr(self.lv, "useExpansionColumn") and self.lv.useExpansionColumn: firstColumn = 1 @@ -2369,7 +2369,7 @@ class RectangleDecoration(Decoration): A RectangleDecoration draw a rectangle around or on the side of a block. The rectangle can be hollow, solid filled or gradient-filled. It can have - a frame drawn as well. + a bulkInsertCtrl drawn as well. """ diff --git a/odmtools/lib/ObjectListView/OLVEvent.py b/odmtools/lib/ObjectListView/OLVEvent.py index a447cb4..500a799 100644 --- a/odmtools/lib/ObjectListView/OLVEvent.py +++ b/odmtools/lib/ObjectListView/OLVEvent.py @@ -229,10 +229,10 @@ def __init__(self, objectListView, groups): class ExpandCollapseEvent(VetoableEvent): """ - The user wants to expand or collapse one or more groups, or has just done so. + The user wants to is_expanded or collapse one or more groups, or has just done so. If the handler calls Veto() for a Expanding or Collapsing event, - the expand/collapse action will be cancelled. + the is_expanded/collapse interactive_item will be cancelled. Calling Veto() has no effect on a Expanded or Collapsed event """ diff --git a/odmtools/lib/ObjectListView/ObjectListView.py b/odmtools/lib/ObjectListView/ObjectListView.py old mode 100755 new mode 100644 index a74d534..f6f144a --- a/odmtools/lib/ObjectListView/ObjectListView.py +++ b/odmtools/lib/ObjectListView/ObjectListView.py @@ -161,7 +161,7 @@ class ObjectListView(wx.ListCtrl): this is will also put a HTML version into the clipboard) Left-Arrow, Right-Arrow - [GroupListView only] This will collapse/expand all selected groups. + [GroupListView only] This will collapse/is_expanded all selected groups. * oddRowsBackColor When `useAlternateBackColors` is true, odd numbered rows will have this @@ -2620,7 +2620,7 @@ def __init__(self, *args, **kwargs): * useExpansionColumn If this is True (the default), the expansion/contraction icon will have its - own column at position 0. If this is false, the expand/contract icon will be + own column at position 0. If this is false, the is_expanded/contract icon will be in the first user specified column. This must be set before SetColumns() is called. If it is changed, SetColumns() must be called again. """ @@ -3088,7 +3088,7 @@ def Reveal(self, modelObject): return True # Find which group (if any) the object belongs to, and - # expand it and then try to reveal it again + # is_expanded it and then try to reveal it again for group in self.groups: if not group.isExpanded and modelObject in group.modelObjects: self.Expand(group) @@ -3197,7 +3197,7 @@ def _GetValuesAsMultiList(self, objects): """ Return a list of lists of the string of the aspects of the given objects """ - cols = self.columns[self.GetPrimaryColumnIndex():] # We don't want to copy the expand icon columns + cols = self.columns[self.GetPrimaryColumnIndex():] # We don't want to copy the is_expanded icon columns objects = [x for x in objects if x is not None and not isinstance(x, ListGroup)] return [[column.GetStringValue(x) for column in cols] for x in objects] @@ -3413,7 +3413,7 @@ class ColumnDefn(object): * isSpaceFilling Is this column a space filler? Space filling columns resize to occupy free space within the listview. As the listview is expanded, space filling columns - expand as well. Conversely, as the control shrinks these columns shrink too. + is_expanded as well. Conversely, as the control shrinks these columns shrink too. Space filling columns can disappear (i.e. have a width of 0) if the control becomes too small. You can set `minimumWidth` to prevent them from diff --git a/odmtools/lib/ObjectListView/WordWrapRenderer.py b/odmtools/lib/ObjectListView/WordWrapRenderer.py index c4dd1c9..b27cabf 100644 --- a/odmtools/lib/ObjectListView/WordWrapRenderer.py +++ b/odmtools/lib/ObjectListView/WordWrapRenderer.py @@ -184,7 +184,7 @@ def OnPaint(self, evt): inset = (20, 20, 20, 20) rect = [inset[0], inset[1], self.GetSize().width-(inset[0]+inset[2]), self.GetSize().height-(inset[1]+inset[3])] - # Calculate exactly how high the wrapped is going to be and put a frame around it. + # Calculate exactly how high the wrapped is going to be and put a bulkInsertCtrl around it. dc.SetFont(self.font) dc.SetPen(wx.RED_PEN) rect[3] = WordWrapRenderer.CalculateHeight(dc, self.text, rect[2]) diff --git a/odmtools/lib/ObjectListView/virtualObjectListviewExample.py b/odmtools/lib/ObjectListView/virtualObjectListviewExample.py index bebdb64..78d1433 100644 --- a/odmtools/lib/ObjectListView/virtualObjectListviewExample.py +++ b/odmtools/lib/ObjectListView/virtualObjectListviewExample.py @@ -183,7 +183,7 @@ def GetOLVColClicked(self, event): # each column consecutively until found. if left_pxl_col <= x <= right_pxl_col: - # Mouse was clicked in the current column "col"; done + # Mouse was clicked in the current column "columns"; done col_selected = col break diff --git a/odmtools/lib/oldOlv/CellEditor.py b/odmtools/lib/oldOlv/CellEditor.py deleted file mode 100644 index 1244941..0000000 --- a/odmtools/lib/oldOlv/CellEditor.py +++ /dev/null @@ -1,548 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: CellEditor.py -# Author: Phillip Piper -# Created: 3 April 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/04/04 JPP Initial version complete -#---------------------------------------------------------------------------- -# To do: -# - there has to be a better DateTimeEditor somewhere!! - -""" -The *CellEditor* module provides some editors for standard types that can be installed -in an *ObjectListView*. It also provides a *Registry* that maps between standard types -and functions that will create editors for that type. - -Cell Editors - - A cell editor can be any subclass of wx.Window provided that it supports the - following protocol: - - SetValue(self, value) - The editor should show the given value for editing - - GetValue(self) - The editor should return the value that it holds. Return None to indicate - an invalid value. The returned value should be of the correct type, i.e. - don't return a string if the editor was registered for the bool type. - - The editor should invoke FinishCellEdit() on its parent ObjectListView when it - loses focus or when the user commits the change by pressing Return or Enter. - - The editor should invoke CancelCellEdit() on its parent ObjectListView when - the user presses Escape. - -Editor Registry - - The editor registry remembers a function that will be called to create - an editor for a given type. -""" - -__author__ = "Phillip Piper" -__date__ = "3 May 2008" -__version__ = "1.0" - -import datetime -import wx - -#====================================================================== -# Editor Registry - -# Module level variable -_cellEditorRegistrySingleton = None - -def CellEditorRegistry(): - """ - Return the registry that is managing type to creator functions - """ - global _cellEditorRegistrySingleton - - if _cellEditorRegistrySingleton is None: - _cellEditorRegistrySingleton = EditorRegistry() - - return _cellEditorRegistrySingleton - - -class EditorRegistry: - """ - An *EditorRegistry* manages a mapping of types onto creator functions. - - When called, creator functions will create the appropriate kind of cell editor - """ - - def __init__(self): - self.typeToFunctionMap = {} - - # Standard types and their creator functions - self.typeToFunctionMap[str] = self._MakeStringEditor - self.typeToFunctionMap[unicode] = self._MakeStringEditor - self.typeToFunctionMap[bool] = self._MakeBoolEditor - self.typeToFunctionMap[int] = self._MakeIntegerEditor - self.typeToFunctionMap[long] = self._MakeLongEditor - self.typeToFunctionMap[float] = self._MakeFloatEditor - self.typeToFunctionMap[datetime.datetime] = self._MakeDateTimeEditor - self.typeToFunctionMap[datetime.date] = self._MakeDateEditor - self.typeToFunctionMap[datetime.time] = self._MakeTimeEditor - - # TODO: Install editors for mxDateTime if installed - - def GetCreatorFunction(self, aValue): - """ - Return the creator function that is register for the type of the given value. - Return None if there is no registered function for the type. - """ - return self.typeToFunctionMap.get(type(aValue), None) - - def RegisterCreatorFunction(self, aType, aFunction): - """ - Register the given function to be called when we need an editor for the given type. - - The function must accept three parameter: an ObjectListView, row index, and subitem index. - It should return a wxWindow that is parented on the listview, and that responds to: - - - SetValue(newValue) - - - GetValue() to return the value shown in the editor - - """ - self.typeToFunctionMap[aType] = aFunction - - #---------------------------------------------------------------------------- - # Creator functions for standard types - - @staticmethod - def _MakeStringEditor(olv, rowIndex, subItemIndex): - return BaseCellTextEditor(olv, subItemIndex) - - @staticmethod - def _MakeBoolEditor(olv, rowIndex, subItemIndex): - return BooleanEditor(olv) - - @staticmethod - def _MakeIntegerEditor(olv, rowIndex, subItemIndex): - return IntEditor(olv, subItemIndex, validator=NumericValidator()) - - @staticmethod - def _MakeLongEditor(olv, rowIndex, subItemIndex): - return LongEditor(olv, subItemIndex) - - @staticmethod - def _MakeFloatEditor(olv, rowIndex, subItemIndex): - return FloatEditor(olv, subItemIndex, validator=NumericValidator("0123456789-+eE.")) - - @staticmethod - def _MakeDateTimeEditor(olv, rowIndex, subItemIndex): - dte = DateTimeEditor(olv, subItemIndex) - - column = olv.columns[subItemIndex] - if isinstance(column.stringConverter, basestring): - dte.formatString = column.stringConverter - - return dte - - @staticmethod - def _MakeDateEditor(olv, rowIndex, subItemIndex): - dte = DateEditor(olv, style=wx.DP_DROPDOWN | wx.DP_SHOWCENTURY | wx.WANTS_CHARS) - #dte.SetValidator(MyValidator(olv)) - return dte - - @staticmethod - def _MakeTimeEditor(olv, rowIndex, subItemIndex): - editor = TimeEditor(olv, subItemIndex) - - column = olv.columns[subItemIndex] - if isinstance(column.stringConverter, basestring): - editor.formatString = column.stringConverter - - return editor - -#====================================================================== -# Cell editors - - -class BooleanEditor(wx.Choice): - """This is a simple editor to edit a boolean value that can be used in an - ObjectListView""" - - def __init__(self, *args, **kwargs): - kwargs["choices"] = ["True", "False"] - wx.Choice.__init__(self, *args, **kwargs) - - def GetValue(self): - "Get the value from the editor" - return self.GetSelection() == 0 - - def SetValue(self, value): - "Put a new value into the editor" - if value: - self.Select(0) - else: - self.Select(1) - -#---------------------------------------------------------------------------- - -class BaseCellTextEditor(wx.TextCtrl): - """This is a base text editor for text-like editors used in an ObjectListView""" - - def __init__(self, olv, subItemIndex, **kwargs): - style = wx.TE_PROCESS_ENTER | wx.TE_PROCESS_TAB - # Allow for odd case where parent isn't an ObjectListView - if hasattr(olv, "columns"): - if olv.HasFlag(wx.LC_ICON): - style |= (wx.TE_CENTRE | wx.TE_MULTILINE) - else: - style |= olv.columns[subItemIndex].GetAlignmentForText() - wx.TextCtrl.__init__(self, olv, style=style, **kwargs) - - # With the MULTILINE flag, the text control always has a vertical - # scrollbar, which looks stupid. I don't know how to get rid of it. - # This doesn't do it: - # self.ToggleWindowStyle(wx.VSCROLL) - -#---------------------------------------------------------------------------- - -class IntEditor(BaseCellTextEditor): - """This is a text editor for integers for use in an ObjectListView""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return int(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, int): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class LongEditor(BaseCellTextEditor): - """This is a text editor for long values for use in an ObjectListView""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return long(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, long): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class FloatEditor(BaseCellTextEditor): - """This is a text editor for floats for use in an ObjectListView. - - Because of the trouble of precisely converting floats to strings, - this editor sometimes behaves a little strangely.""" - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - try: - return float(s) - except ValueError: - return None - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, float): - value = repr(value) - wx.TextCtrl.SetValue(self, value) - -#---------------------------------------------------------------------------- - -class DateTimeEditor(BaseCellTextEditor): - """ - A DateTimeEditor allows the user to enter a date/time combination, where the time is optional - and many formats of date and time are allowed. - - The control accepts these date formats (in all cases, the year can be only 2 digits): - - '31/12/2008' - - '2008/12/31' - - '12/31/2008' - - '31 December 2008' - - '31 Dec 2008' - - 'Dec 31 2008' - - 'December 31 2008' - - Slash character can also be '-' or ' '. Consecutive whitespace are collapsed. - - The control accepts these time formats: - - '23:59:59' - - '11:59:59pm' - - '23:59' - - '11:59pm' - - '11pm' - - The colons are required. The am/pm is case insensitive. - - The implementation uses a brute force approach to parsing the data. - """ - # Acceptable formats: - # '31/12/2008', '2008/12/31', '12/31/2008', '31 December 2008', '31 Dec 2008', 'Dec 31 2007' - # second line is the same but with two-digit year. - # slash character can also be '-' or ' '. Consecutive whitespace are collapsed. - STD_DATE_FORMATS = ['%d %m %Y', '%Y %m %d', '%m %d %Y', '%d %B %Y', '%d %b %Y', '%b %d %Y', '%B %d %Y', - '%d %m %y', '%y %m %d', '%m %d %y', '%d %B %y', '%d %b %y', '%b %d %y', '%B %d %y'] - - STD_DATE_WITHOUT_YEAR_FORMATS = ['%d %m', '%m %d', '%d %B', '%d %b', '%B %d', '%b %d'] - - # Acceptable formats: '23:59:59', '11:59:59pm', '23:59', '11:59pm', '11pm' - STD_TIME_FORMATS = ['%H:%M:%S', '%I:%M:%S %p', '%H:%M', '%I:%M %p', '%I %p'] - - # These separators are treated as whitespace - STD_SEPARATORS = "/-," - - def __init__(self, *args, **kwargs): - BaseCellTextEditor.__init__(self, *args, **kwargs) - self.formatString = "%X %x" - - self.allDateTimeFormats = [] - for dtFmt in self.STD_DATE_FORMATS: - self.allDateTimeFormats.append(dtFmt) - for timeFmt in self.STD_TIME_FORMATS: - self.allDateTimeFormats.append("%s %s" % (dtFmt, timeFmt)) - - self.allDateTimeWithoutYearFormats = [] - for dtFmt in self.STD_DATE_WITHOUT_YEAR_FORMATS: - self.allDateTimeWithoutYearFormats.append(dtFmt) - for timeFmt in self.STD_TIME_FORMATS: - self.allDateTimeWithoutYearFormats.append("%s %s" % (dtFmt, timeFmt)) - - - def SetValue(self, value): - "Put a new value into the editor" - if isinstance(value, datetime.datetime): - value = value.strftime(self.formatString) - wx.TextCtrl.SetValue(self, value) - - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - return self._ParseDateTime(s) - - - def _ParseDateTime(self, s): - # Try the installed format string first - try: - return datetime.datetime.strptime(s, self.formatString) - except ValueError: - pass - - for x in self.STD_SEPARATORS: - s = s.replace(x, " ") - - # Because of the logic of strptime, we have to check shorter patterns first. - # For example: - # "31 12" matches "%d %m %y" => datetime(2012, 1, 3, 0, 0) ?? - # but we want: - # "31 12" to match "%d %m" => datetime(1900, 12, 31, 0, 0) - # JPP 4/4/2008 Python 2.5.1 - for fmt in self.allDateTimeWithoutYearFormats: - try: - dt = datetime.datetime.strptime(s, fmt) - return dt.replace(year=datetime.datetime.today().year) - except ValueError: - pass - - for fmt in self.allDateTimeFormats: - try: - return datetime.datetime.strptime(s, fmt) - except ValueError: - pass - - return None - -#---------------------------------------------------------------------------- - -class NumericValidator(wx.PyValidator): - """This validator only accepts numeric keys""" - - def __init__(self, acceptableChars="0123456789+-"): - wx.PyValidator.__init__(self) - self.Bind(wx.EVT_CHAR, self._OnChar) - self.acceptableChars = acceptableChars - self.acceptableCodes = [ord(x) for x in self.acceptableChars] - stdEditKeys = [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER, wx.WXK_ESCAPE, wx.WXK_CANCEL, - wx.WXK_TAB, wx.WXK_BACK, wx.WXK_DELETE, wx.WXK_HOME, wx.WXK_END, - wx.WXK_LEFT, wx.WXK_RIGHT] - self.acceptableCodes.extend(stdEditKeys) - - def Clone(self): - "Make a new copy of this validator" - return NumericValidator(self.acceptableChars) - - def _OnChar(self, event): - "Handle the OnChar event by rejecting non-numerics" - if event.GetModifiers() != 0 and event.GetModifiers() != wx.MOD_SHIFT: - event.Skip() - return - - if event.GetKeyCode() in self.acceptableCodes: - event.Skip() - return - - wx.Bell() - -#---------------------------------------------------------------------------- - -class DateEditor(wx.DatePickerCtrl): - """ - This control uses standard datetime. - wx.DatePickerCtrl works only with wx.DateTime, but they are strange beasts. - wx.DataTime use 0 indexed months, i.e. January==0 and December==11. - """ - - def __init__(self, *args, **kwargs): - wx.DatePickerCtrl.__init__(self, *args, **kwargs) - self.SetValue(None) - - def SetValue(self, value): - if value: - dt = wx.DateTime() - dt.Set(value.day, value.month-1, value.year) - else: - dt = wx.DateTime.Today() - wx.DatePickerCtrl.SetValue(self, dt) - - def GetValue(self): - "Get the value from the editor" - dt = wx.DatePickerCtrl.GetValue(self) - if dt.IsOk(): - return datetime.date(dt.Year, dt.Month+1, dt.Day) - else: - return None - -#---------------------------------------------------------------------------- - -class TimeEditor(BaseCellTextEditor): - """A text editor that expects and return time values""" - - # Acceptable formats: '23:59', '11:59pm', '11pm' - STD_TIME_FORMATS = ['%X', '%H:%M', '%I:%M %p', '%I %p'] - - def __init__(self, *args, **kwargs): - BaseCellTextEditor.__init__(self, *args, **kwargs) - self.formatString = "%X" - - def SetValue(self, value): - "Put a new value into the editor" - value = value or "" - if isinstance(value, datetime.time): - value = value.strftime(self.formatString) - wx.TextCtrl.SetValue(self, value) - - def GetValue(self): - "Get the value from the editor" - s = wx.TextCtrl.GetValue(self).strip() - fmts = self.STD_TIME_FORMATS[:] - if self.formatString not in fmts: - fmts.insert(0, self.formatString) - for fmt in fmts: - try: - dt = datetime.datetime.strptime(s, fmt) - return dt.time() - except ValueError: - pass - - return None - -#====================================================================== -# Auto complete controls - -def MakeAutoCompleteTextBox(olv, columnIndex, maxObjectsToConsider=10000): - """ - Return a TextCtrl that lets the user choose from all existing values in this column. - Do not call for large lists - """ - col = olv.columns[columnIndex] - #THINK: We could make this time based, i.e. it escapes after 1 second. - maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) - options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) - tb = BaseCellTextEditor(olv, columnIndex) - AutoCompleteHelper(tb, list(options)) - return tb - -def MakeAutoCompleteComboBox(olv, columnIndex, maxObjectsToConsider=10000): - """ - Return a ComboBox that lets the user choose from all existing values in this column. - Do not call for large lists - """ - col = olv.columns[columnIndex] - maxObjectsToConsider = min(maxObjectsToConsider, olv.GetItemCount()) - options = set(col.GetStringValue(olv.GetObjectAt(i)) for i in range(maxObjectsToConsider)) - cb = wx.ComboBox(olv, choices=list(options), - style=wx.CB_DROPDOWN|wx.CB_SORT|wx.TE_PROCESS_ENTER) - AutoCompleteHelper(cb) - return cb - - -#------------------------------------------------------------------------- - -class AutoCompleteHelper(object): - """ - This class operates on a text control or combobox, and automatically completes the - text typed by the user from a list of entries in a given list. - - """ - - def __init__(self, control, possibleValues=None): - self.control = control - self.lastUserEnteredString = self.control.GetValue() - self.control.Bind(wx.EVT_TEXT, self._OnTextEvent) - if isinstance(self.control, wx.ComboBox): - self.possibleValues = self.control.GetStrings() - else: - self.possibleValues = possibleValues or [] - self.lowerCasePossibleValues = [x.lower() for x in self.possibleValues] - - - def _OnTextEvent(self, evt): - evt.Skip() - # After the SetValue() we want to ignore this event. If we get this event - # and the value hasn't been modified, we know it was a SetValue() call. - if hasattr(self.control, "IsModified") and not self.control.IsModified(): - return - - # If the text has changed more than the user just typing one letter, - # then don't try to autocomplete it. - if len(evt.GetString()) != len(self.lastUserEnteredString)+1: - self.lastUserEnteredString = evt.GetString() - return - - self.lastUserEnteredString = evt.GetString() - s = evt.GetString().lower() - for i, x in enumerate(self.lowerCasePossibleValues): - if x.startswith(s): - self._AutocompleteWith(self.possibleValues[i]) - break - - - def _AutocompleteWith(self, newValue): - """Suggest the given value by autocompleting it.""" - # GetInsertionPoint() doesn't seem reliable under linux - insertIndex = len(self.control.GetValue()) - self.control.SetValue(newValue) - if isinstance(self.control, wx.ComboBox): - self.control.SetMark(insertIndex, len(newValue)) - else: - # Seems that under linux, selecting only seems to work here if we do it - # outside of the text event - wx.CallAfter(self.control.SetSelection, insertIndex, len(newValue)) diff --git a/odmtools/lib/oldOlv/Filter.py b/odmtools/lib/oldOlv/Filter.py deleted file mode 100644 index 97dcf5d..0000000 --- a/odmtools/lib/oldOlv/Filter.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: Filter.py -# Author: Phillip Piper -# Created: 26 August 2008 -# Copyright: (c) 2008 Phillip Piper -# SVN-ID: $Id$ -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/26 JPP First version -#---------------------------------------------------------------------------- -# To do: -# - -""" -Filters provide a structured mechanism to display only some of the model objects -given to an ObjectListView. Only those model objects which are 'chosen' by -an installed filter will be presented to the user. - -Filters are simple callable objects which accept a single parameter, which -is the list of model objects to be filtered, and returns a collection of -those objects which will be presented to the user. - -This module provides some standard filters. - -Filters almost always impose a performance penalty on the ObjectListView. -The penalty is normally O(n) since the filter normally examines each model -object to see if it should be included. Head() and Tail() are exceptions -to this observation. -""" - -def Predicate(predicate): - """ - Display only those objects that match the given predicate - - Example:: - self.olv.SetFilter(Filter.Predicate(lambda x: x.IsOverdue())) - """ - return lambda modelObjects: [x for x in modelObjects if predicate(x)] - - -def Head(num): - """ - Display at most the first N of the model objects - - Example:: - self.olv.SetFilter(Filter.Head(1000)) - """ - return lambda modelObjects: modelObjects[:num] - - -def Tail(num): - """ - Display at most the last N of the model objects - - Example:: - self.olv.SetFilter(Filter.Tail(1000)) - """ - return lambda modelObjects: modelObjects[-num:] - - -class TextSearch(object): - """ - Return only model objects that match a given string. If columns is not empty, - only those columns will be considered when searching for the string. Otherwise, - all columns will be searched. - - Example:: - self.olv.SetFilter(Filter.TextSearch(self.olv, text="findthis")) - self.olv.RepopulateList() - """ - - def __init__(self, objectListView, columns=(), text=""): - """ - Create a filter that includes on modelObject that have 'self.text' somewhere in the given columns. - """ - self.objectListView = objectListView - self.columns = columns - self.text = text - - def __call__(self, modelObjects): - """ - Return the model objects that contain our text in one of the columns to consider - """ - if not self.text: - return modelObjects - - # In non-report views, we can only search the primary column - if self.objectListView.InReportView(): - cols = self.columns or self.objectListView.columns - else: - cols = [self.objectListView.columns[0]] - - textToFind = self.text.lower() - - def _containsText(modelObject): - for col in cols: - if textToFind in col.GetStringValue(modelObject).lower(): - return True - return False - - return [x for x in modelObjects if _containsText(x)] - - def SetText(self, text): - """ - Set the text that this filter will match. Set this to None or "" to disable the filter. - """ - self.text = text - - -class Chain(object): - """ - Return only model objects that match all of the given filters. - - Example:: - # Show at most 100 people whose salary is over 50,000 - salaryFilter = Filter.Predicate(lambda person: person.GetSalary() > 50000) - self.olv.SetFilter(Filter.Chain(salaryFilter, Filter.Tail(100))) - self.olv.RepopulateList() - """ - - def __init__(self, *filters): - """ - Create a filter that performs all the given filters. - - The order of the filters is important. - """ - self.filters = filters - - - def __call__(self, modelObjects): - """ - Return the model objects that match all of our filters - """ - for filter in self.filters: - modelObjects = filter(modelObjects) - return modelObjects diff --git a/odmtools/lib/oldOlv/ListCtrlPrinter.py b/odmtools/lib/oldOlv/ListCtrlPrinter.py deleted file mode 100644 index 550ec6b..0000000 --- a/odmtools/lib/oldOlv/ListCtrlPrinter.py +++ /dev/null @@ -1,2797 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: ListCtrlPrinter.py -# Author: Phillip Piper -# Created: 17 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/17 JPP Inspired beginning -#---------------------------------------------------------------------------- -# To do: -# - persistence of ReportFormat -# - allow cell contents to be vertically aligned -# - in light of several of the "to dos", consider having CellBlockFormat object -# - write a data abstraction layer between the printer and the ListCtrl. -# This layer could understand ObjectListViews and be more efficient. -# - consider if this could be made to work with a wx.Grid (needs data abstraction layer) - -# Known issues: -# - the 'space' setting on decorations is not intuitive - - -""" -An ``ListCtrlPrinter`` takes an ``ObjectListView`` or ``wx.ListCtrl`` and turns it into a -pretty report. - -As always, the goal is for this to be as easy to use as possible. A typical -usage should be as simple as:: - - printer = ListCtrlPrinter(self.myOlv, "My Report Title") - printer.PrintPreview() - -This will produce a report with reasonable formatting. The formatting of a report is -controlled completely by the ReportFormat object of the ListCtrlPrinter. To change the appearance -of the report, you change the settings in this object. - -A report consists of various sections (called "blocks") and each of these blocks has a -matching BlockFormat object in the ReportFormat. So, to modify the format of the -page header, you change the ReportFormat.PageHeader object. - -A ReportFormat object has the following properties which control the appearance of the matching -sections of the report: - -* PageHeader -* ListHeader -* ColumnHeader -* GroupTitle -* Row -* ListFooter -* PageFooter -* Page - -These properties return BlockFormat objects, which have the following properties: - -* AlwaysCenter -* CanWrap -* Font -* Padding -* TextAlignment -* TextColor - -* CellPadding -* GridPen - -Implementation -============== - -A ``ListCtrlPrinter`` is a *Facade* over two classes: - - ``ListCtrlPrintout``, which handles the interface to the wx printing subsystem - - ``ReportEngine``, which does the actual work of creating the report - -The ``ListCtrlPrintout`` handles all the details of the wx printing subsystem. In -particular, it configures the printing DC so that its origin and scale are correct. This -enables the ``ReportEngine`` to simply render the report without knowing the -characteristics of the underlying printer DPI, unprintable region, or the scale of a print -preview. When The ``ListCtrlPrintout`` encounters some action that is cannot perform (like -actually rendering a page) it calls back into ``ListCtrlPrinter`` (which simply forwards -to the ``ReportEngine``). - -The ``ReportEngine`` uses a block structure approach to reports. Each element of -a report is a "block", which stacks vertically with other blocks. - -When a block is printed, it can either render itself into a given DC or it can replace -itself in the report structure with one or more other blocks. A ``TextBlock`` renders -itself by drawing into the DC. In contrast, the block that prints a ``ListCtrl`` replaces -itself with a ``ListHeaderBlock``, a ``ColumnHeaderBlock``, ``ListRowsBlock`` and finally a -``ListFooterBlock``. - -The blocks describe the structure of the report. The formatting of a report is -controlled by ``BlockFormat`` objects. The ``ReportFormat`` contains a ``BlockFormat`` -object for each formattable section of the report. - -Each section must be formatted in same fashion, i.e. all column headers will look the same, -all page footer will look the same. There is no way to make the first footer look one way, -and the second look a different way. -""" - -import datetime -import math - -import wx - -from WordWrapRenderer import WordWrapRenderer - -#---------------------------------------------------------------------------- - -class ListCtrlPrinter(object): - """ - An ListCtrlPrinter creates a pretty report from an ObjectListView/ListCtrl. - - """ - - def __init__(self, listCtrl=None, title="ListCtrl Printing"): - """ - """ - self.printout = ListCtrlPrintout(self) - self.engine = ReportEngine() - if listCtrl is not None: - self.AddListCtrl(listCtrl, title) - - #---------------------------------------------------------------------------- - # Accessing - - def GetPageFooter(self): - """ - Return a 3-tuple of the texts that will be shown in left, center, and right cells - of the page footer - """ - return self.engine.pageFooter - - def SetPageFooter(self, leftText="", centerText="", rightText=""): - """ - Set the texts that will be shown in various cells of the page footer. - - leftText can be a string or a 3-tuple of strings. - """ - if isinstance(leftText, (tuple, list)): - self.engine.pageFooter = leftText - else: - self.engine.pageFooter = (leftText, centerText, rightText) - - def GetPageHeader(self): - """ - Return a 3-tuple of the texts that will be shown in left, center, and right cells - of the page header - """ - return self.engine.pageHeader - - def SetPageHeader(self, leftText="", centerText="", rightText=""): - """ - Set the texts that will be shown in various cells of the page header - """ - if isinstance(leftText, (tuple, list)): - self.engine.pageHeader = leftText - else: - self.engine.pageHeader = (leftText, centerText, rightText) - - def GetPrintData(self): - """ - Return the wx.PrintData that controls the printing of this report - """ - return self.printout.printData - - def GetReportFormat(self): - """ - Return the ReportFormat object that controls the appearance of this printout - """ - return self.engine.reportFormat - - def SetReportFormat(self, fmt): - """ - Set the ReportFormat object that controls the appearance of this printout - """ - self.engine.reportFormat = fmt - - def GetWatermark(self, txt): - """ - Get the text that will be printed as a watermark on the report - """ - return self.engine.watermark - - def SetWatermark(self, txt): - """ - Set the text that will be printed as a watermark on the report - """ - self.engine.watermark = txt - - ReportFormat = property(GetReportFormat, SetReportFormat) - PageFooter = property(GetPageFooter, SetPageFooter) - PageHeader = property(GetPageHeader, SetPageHeader) - PrintData = property(GetPrintData) - Watermark = property(GetWatermark, SetWatermark) - - #---------------------------------------------------------------------------- - # Setup - - def AddListCtrl(self, listCtrl, title=None): - """ - Add the given list to those that will be printed by this report. - """ - self.engine.AddListCtrl(listCtrl, title) - - - def Clear(self): - """ - Remove all ListCtrls from this printer - """ - self.engine.ClearListCtrls() - - #---------------------------------------------------------------------------- - # Printing Commands - - def PageSetup(self, parent=None): - """ - Show a Page Setup dialog that will change the configuration of this printout - """ - self.printout.PageSetup(parent) - - - def PrintPreview(self, parent=None, title="ListCtrl Print Preview", bounds=(20, 50, 800, 800)): - """ - Show a Print Preview of this report - """ - self.printout.PrintPreview(parent, title, bounds) - - - def Print(self, parent=None): - """ - Print this report to the selected printer - """ - self.printout.DoPrint(parent) - - #---------------------------------------------------------------------------- - # Callbacks - # These methods are invoked by the ListCtrlPrintout when required - - def CalculateTotalPages(self, dc, bounds): - """ - Do the work of calculating how many pages this report will occupy? - - This is expensive because it basically prints the whole report. - """ - return self.engine.CalculateTotalPages(dc, bounds) - - - def StartPrinting(self): - """ - A new print job is about to begin. - """ - self.engine.StartPrinting() - - - def PrintPage(self, dc, pageNumber, bounds): - """ - Print the given page on the given device context. - """ - self.engine.PrintPage(dc, pageNumber, bounds) - -#---------------------------------------------------------------------------- - -class ReportEngine(object): - """ - A ReportEngine handles all the work of actually producing a report. - - Public instance variables (all others should be treated as private): - - * dateFormat - When the current date/time is substituted into report text, how should - the datetime be formatted? This must be a valid format string for the - strftime() method. - Default: "%x %X" - - """ - - def __init__(self): - """ - """ - self.currentPage = -1 - self.totalPages = -1 - self.blocks = list() - self.blockInsertionIndex = 0 - self.listCtrls = list() - self.dateFormat = "%x %X" - - self.reportFormat = ReportFormat.Normal() - - self.watermark = "" - self.pageHeader = list() - self.pageFooter = list() - #self.isPrintSelectionOnly = False # not currently implemented - - # If this is False, no drawing should be done. The engine is either counting - # pages, or skipping to a specific page - self.shouldDrawBlocks = True - - #---------------------------------------------------------------------------- - # Accessing - - def GetNamedFormat(self, name): - """ - Return the given format - """ - return self.reportFormat.GetNamedFormat(name) - - - def GetTotalPages(self): - """ - Return the total number of pages that this report will produce. - - CalculateTotalPages() must be called before this is accurate. - """ - return self.totalPages - - - def GetSubstitutionInfo(self): - """ - Return a dictionary that can be used for substituting values into strings - """ - dateString = datetime.datetime.now().strftime(self.dateFormat) - info = { - "currentPage": self.currentPage, - "date": dateString, - "totalPages": self.totalPages, - } - return info - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateTotalPages(self, dc, bounds): - """ - Do the work of calculating how many pages this report will occupy? - - This is expensive because it basically prints the whole report. - """ - self.StartPrinting() - self.totalPages = 1 - self.shouldDrawBlocks = False - while self.PrintOnePage(dc, self.totalPages, bounds): - self.totalPages += 1 - self.shouldDrawBlocks = True - return self.totalPages - - #---------------------------------------------------------------------------- - # Commands - - def AddBlock(self, block): - """ - Add the given block at the current insertion point - """ - self.blocks.insert(self.blockInsertionIndex, block) - self.blockInsertionIndex += 1 - block.engine = self - - - def AddListCtrl(self, listCtrl, title=None): - """ - Add the given list to those that will be printed by this report. - """ - if listCtrl.InReportView(): - self.listCtrls.append([listCtrl, title]) - - - def ClearListCtrls(self): - """ - Remove all ListCtrls from this report. - """ - self.listCtrls = list() - - - def DropCurrentBlock(self): - """ - Remove the current block from our list of blocks - """ - self.blocks.pop(0) - self.blockInsertionIndex = 1 - - #---------------------------------------------------------------------------- - # Printing - - def StartPrinting(self): - """ - Initial a print job on this engine - """ - self.currentPage = 0 - self.blockInsertionIndex = 0 - self.blocks = list() - self.AddBlock(ReportBlock()) - self.runningBlocks = list() - self.AddRunningBlock(PageHeaderBlock()) - self.AddRunningBlock(PageFooterBlock()) - - if self.watermark: - self._CreateReplaceWatermarkDecoration() - - - def AddRunningBlock(self, block): - """ - A running block is printed on every page until it is removed - """ - self.runningBlocks.append(block) - block.engine = self - - - def RemoveRunningBlock(self, block): - """ - A running block is printed on every page until it is removed - """ - self.runningBlocks.remove(block) - - - def PrintPage(self, dc, pageNumber, bounds): - """ - Print the given page on the given device context. - """ - # If the request page isn't next in order, we have to restart - # the printing process and advance until we reach the desired page. - if pageNumber != self.currentPage + 1: - self.StartPrinting() - self.shouldDrawBlocks = False - for i in range(1, pageNumber): - self.PrintOnePage(dc, i, bounds) - self.shouldDrawBlocks = True - - return self.PrintOnePage(dc, pageNumber, bounds) - - - def PrintOnePage(self, dc, pageNumber, bounds): - """ - Print the current page on the given device context. - - Return true if there is still more to print. - """ - # Initialize state - self.currentPage = pageNumber - self.pageBounds = list(bounds) - self.workBounds = list(self.pageBounds) - self.SubtractDecorations(dc) - - # Print page adornments, including under-text decorations - self.DrawPageDecorations(dc, False) - for x in self.runningBlocks: - x.Print(dc) - - # Print blocks until they won't fit or we run out of blocks - while len(self.blocks) and self.blocks[0].Print(dc): - self.DropCurrentBlock() - - # Finally, print over-the-text decorations - self.DrawPageDecorations(dc, True) - - return len(self.blocks) > 0 - - - def SubtractDecorations(self, dc): - """ - # Subtract the area used from the work area - """ - fmt = self.GetNamedFormat("Page") - self.workBounds = fmt.SubtractDecorations(dc, self.workBounds) - - - def DrawPageDecorations(self, dc, over): - """ - Draw the page decorations - """ - if not self.shouldDrawBlocks: - return - - fmt = self.GetNamedFormat("Page") - bounds = list(self.pageBounds) - fmt.DrawDecorations(dc, bounds, self, over) - - - def _CreateReplaceWatermarkDecoration(self): - """ - Create a watermark decoration, replacing any existing watermark - """ - pageFmt = self.GetNamedFormat("Page") - pageFmt.decorations = [x for x in pageFmt.decorations if not isinstance(x, WatermarkDecoration)] - - watermarkFmt = self.GetNamedFormat("Watermark") - pageFmt.Add(WatermarkDecoration(self.watermark, font=watermarkFmt.Font, - color=watermarkFmt.TextColor, angle=watermarkFmt.Angle, - over=watermarkFmt.Over)) - -#---------------------------------------------------------------------------- - -class ListCtrlPrintout(wx.Printout): - """ - An ListCtrlPrintout is the interface between the wx printing system - and ListCtrlPrinter. - """ - - def __init__(self, olvPrinter, margins=None): - """ - """ - wx.Printout.__init__(self) - self.olvPrinter = olvPrinter - self.margins = margins or (wx.Point(15, 15), wx.Point(15, 15)) - self.totalPages = -1 - - self.printData = wx.PrintData() - self.printData.SetPrinterName("") # Use default printer - self.printData.SetPaperId(wx.PAPER_A4) - self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) - - - #---------------------------------------------------------------------------- - # Accessing - - def HasPage(self, page): - """ - Return true if this printout has the given page number - """ - return page <= self.totalPages - - - def GetPageInfo(self): - """ - Return a 4-tuple indicating the ... - """ - return (1, self.totalPages, 1, 1) - - - def GetPrintPreview(self): - """ - Get a wxPrintPreview of this report - """ - data = wx.PrintDialogData(self.printData) - forViewing = ListCtrlPrintout(self.olvPrinter, self.margins) - forPrinter = ListCtrlPrintout(self.olvPrinter, self.margins) - preview = wx.PrintPreview(forViewing, forPrinter, data) - return preview - - #---------------------------------------------------------------------------- - # Commands - - def PageSetup(self, parent): - """ - Show a Page Setup dialog that will change the configuration of this printout - """ - data = wx.PageSetupDialogData() - data.SetPrintData(self.printData) - data.SetDefaultMinMargins(True) - data.SetMarginTopLeft(self.margins[0]) - data.SetMarginBottomRight(self.margins[1]) - dlg = wx.PageSetupDialog(parent, data) - if dlg.ShowModal() == wx.ID_OK: - data = dlg.GetPageSetupData() - self.printData = wx.PrintData(data.GetPrintData()) - self.printData.SetPaperId(data.GetPaperId()) - self.margins = (data.GetMarginTopLeft(), data.GetMarginBottomRight()) - dlg.Destroy() - - - - def PrintPreview(self, parent, title, bounds): - """ - Show a Print Preview of this report - """ - self.preview = self.GetPrintPreview() - - if not self.preview.Ok(): - return False - - pfrm = wx.PreviewFrame(self.preview, parent, title) - - pfrm.Initialize() - pfrm.SetPosition(bounds[0:2]) - pfrm.SetSize(bounds[2:4]) - pfrm.Show(True) - - return True - - - def DoPrint(self, parent): - """ - Send the report to the configured printer - """ - try: - pdd = wx.PrintDialogData(self.printData) - printer = wx.Printer(pdd) - - if printer.Print(parent, self, True): - self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) - else: - wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) - finally: - pdd.Destroy() - - - #---------------------------------------------------------------------------- - # Event handlers - - def OnPreparePrinting(self): - """ - Prepare for printing. This event is sent before any of the others - """ - dc = self.GetDC() - self.SetScaleAndBounds(dc) - self.totalPages = self.olvPrinter.CalculateTotalPages(dc, self.bounds) - self.olvPrinter.StartPrinting() - - def OnBeginDocument(self, start, end): - """ - Begin printing one copy of the document. Return False to cancel the job - """ - return super(ListCtrlPrintout, self).OnBeginDocument(start, end) - - def OnEndDocument(self): - super(ListCtrlPrintout, self).OnEndDocument() - - def OnBeginPrinting(self): - super(ListCtrlPrintout, self).OnBeginPrinting() - - def OnEndPrinting(self): - super(ListCtrlPrintout, self).OnEndPrinting() - - def OnPrintPage(self, page): - """ - Do the work of printing the given page number. - """ - # We bounce this back to the printer facade - dc = self.GetDC() - self.SetScaleAndBounds(dc) - return self.olvPrinter.PrintPage(dc, page, self.bounds) - - def SetScaleAndBounds(self, dc): - """ - Calculate the scale required for our printout to match what appears on screen, - and the bounds that will be effective at that scale and margins - """ - # This code comes from Robin Dunn's "wxPython In Action." - # Without that, I would never have figured this out. - ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() - ppiScreenX, ppiScreenY = self.GetPPIScreen() - logicalScale = float(ppiPrinterX) / float(ppiScreenX) - pw, ph = self.GetPageSizePixels() - dw, dh = dc.GetSize() - scale = logicalScale * float(dw)/float(pw) - dc.SetUserScale(scale, scale) - - # Now calculate our boundries - logicalUnitsMM = float(ppiPrinterX) / (logicalScale*25.4) - topLeft, bottomRight = self.margins - left = round(topLeft.x * logicalUnitsMM) - top = round(topLeft.y * logicalUnitsMM) - right = round(dc.DeviceToLogicalYRel(dw) - bottomRight.x * logicalUnitsMM) - bottom = round(dc.DeviceToLogicalYRel(dh) - bottomRight.y * logicalUnitsMM) - self.bounds = (left, top, right-left, bottom-top) - - -#---------------------------------------------------------------------------- - -class ReportFormat(object): - """ - A ReportFormat defines completely how a report is formatted. - - It holds a collection of BlockFormat objects which control the - formatting of individual blocks of the report - - Public instance variables: - - * IncludeImages - Should images from the ListCtrl be printed in the report? - Default: *True* - - * IsColumnHeadingsOnEachPage - Will the column headers be printed at the top of each page? - Default: *True* - - * IsShrinkToFit - Will the columns be shrunk so they all fit into the width of one page? - Default: *False* - - * UseListCtrlTextFormat - If this is True, the text format (i.e. font and text color) of each row will be taken from the ListCtrl, - rather than from the *Cell* format. - Default: *False* - - """ - - def __init__(self): - """ - """ - # Initialize the formats that control the various portions of the report - self.Page = BlockFormat() - self.PageHeader = BlockFormat() - self.ListHeader = BlockFormat() - self.GroupTitle = BlockFormat() - self.ColumnHeader = BlockFormat() - self.Row = BlockFormat() - self.ListFooter = BlockFormat() - self.PageFooter = BlockFormat() - self.Watermark = BlockFormat() - - self.IncludeImages = True - self.IsColumnHeadingsOnEachPage = False - self.IsShrinkToFit = False - self.UseListCtrlTextFormat = True - - # Initialize the watermark format to default values - self.WatermarkFormat() - - #---------------------------------------------------------------------------- - # Accessing - - def GetNamedFormat(self, name): - """ - Return the format used in to format a block with the given name. - """ - return getattr(self, name) - - #---------------------------------------------------------------------------- - # Commands - - def WatermarkFormat(self, font=None, color=None, angle=30, over=False): - """ - Change the format of the watermark printed on this report. - - The actual text of the water mark is set by `ListCtrlPrinter.Watermark` property. - """ - defaultFaceName = "Stencil" - self.Watermark.Font = font or wx.FFont(96, wx.FONTFAMILY_DEFAULT, 0, defaultFaceName) - self.Watermark.TextColor = color or wx.Colour(204, 204, 204) - self.Watermark.Angle = angle - self.Watermark.Over = over - - #---------------------------------------------------------------------------- - # Standard formats - # These are meant to be illustrative rather than definitive - - @staticmethod - def Minimal(headerFontName="Arial", rowFontName="Times New Roman"): - """ - Return a minimal format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = False - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(18, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - - fmt.GroupTitle.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.GroupTitle.Padding = (0, 12, 0, 12) - fmt.GroupTitle.Line(wx.BOTTOM, wx.BLACK, 1, space=5) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageFooter.Line(wx.TOP, wx.BLACK, 1, space=3) - fmt.PageFooter.Padding = (0, 16, 0, 0) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.Padding = (0, 12, 0, 12) - fmt.ColumnHeader.CellPadding = 5 - fmt.ColumnHeader.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) - fmt.ColumnHeader.AlwaysCenter = True - - fmt.Row.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=rowFontName) - fmt.Row.CellPadding = 5 - fmt.Row.Line(wx.BOTTOM, wx.Colour(192, 192, 192), 1, space=3) - fmt.Row.CanWrap = True - - return fmt - - @staticmethod - def Normal(headerFontName="Gill Sans", rowFontName="Times New Roman"): - """ - Return a reasonable default format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = True - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageHeader.Line(wx.BOTTOM, wx.BLUE, 2, space=5) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(26, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ListHeader.TextColor = wx.WHITE - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.TextAlignment = wx.ALIGN_LEFT - fmt.ListHeader.Background(wx.BLUE, wx.WHITE, space=(16, 4, 0, 4)) - - fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.GroupTitle.Line(wx.BOTTOM, wx.BLUE, 4, toColor=wx.WHITE, space=5) - fmt.GroupTitle.Padding = (0, 12, 0, 12) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DEFAULT, face=headerFontName) - fmt.PageFooter.Background(wx.WHITE, wx.BLUE, space=(0, 4, 0, 4)) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_DEFAULT, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.CellPadding = 2 - fmt.ColumnHeader.Background(wx.Colour(192, 192, 192)) - fmt.ColumnHeader.GridPen = wx.Pen(wx.WHITE, 1) - fmt.ColumnHeader.Padding = (0, 0, 0, 12) - fmt.ColumnHeader.AlwaysCenter = True - - fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_DEFAULT, face=rowFontName) - fmt.Row.Line(wx.BOTTOM, pen=wx.Pen(wx.BLUE, 1, wx.DOT), space=3) - fmt.Row.CellPadding = 2 - fmt.Row.CanWrap = True - - return fmt - - @staticmethod - def TooMuch(headerFontName="Chiller", rowFontName="Gill Sans"): - """ - Return a reasonable default format for a report - """ - fmt = ReportFormat() - fmt.IsShrinkToFit = False - - fmt.PageHeader.Font = wx.FFont(12, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.PageHeader.TextColor = wx.WHITE - fmt.PageHeader.Background(wx.GREEN, wx.RED, space=(16, 4, 0, 4)) - fmt.PageHeader.Padding = (0, 0, 0, 12) - - fmt.ListHeader.Font = wx.FFont(24, wx.FONTFAMILY_DECORATIVE, face=headerFontName) - fmt.ListHeader.TextColor = wx.WHITE - fmt.ListHeader.Padding = (0, 12, 0, 12) - fmt.ListHeader.TextAlignment = wx.ALIGN_CENTER - fmt.ListHeader.Background(wx.RED, wx.GREEN, space=(16, 4, 0, 4)) - - fmt.GroupTitle.Font = wx.FFont(14, wx.FONTFAMILY_DECORATIVE, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.GroupTitle.TextColor = wx.BLUE - fmt.GroupTitle.Padding = (0, 12, 0, 12) - fmt.GroupTitle.Line(wx.BOTTOM, wx.GREEN, 4, toColor=wx.WHITE, space=5) - - fmt.PageFooter.Font = wx.FFont(10, wx.FONTFAMILY_DECORATIVE, face=headerFontName) - fmt.PageFooter.Line(wx.TOP, wx.GREEN, 2, toColor=wx.RED, space=3) - fmt.PageFooter.Padding = (0, 16, 0, 0) - - fmt.ColumnHeader.Font = wx.FFont(14, wx.FONTFAMILY_SWISS, wx.FONTFLAG_BOLD, face=headerFontName) - fmt.ColumnHeader.Background(wx.Colour(255, 215, 0)) - fmt.ColumnHeader.CellPadding = 5 - fmt.ColumnHeader.GridPen = wx.Pen(wx.Colour(192, 192, 192), 1) - - fmt.Row.Font = wx.FFont(12, wx.FONTFAMILY_SWISS, face=rowFontName) - fmt.Row.CellPadding = 5 - fmt.Row.GridPen = wx.Pen(wx.BLUE, 1, wx.DOT) - fmt.Row.CanWrap = True - - fmt.Watermark.TextColor = wx.Colour(233, 150, 122) - - return fmt - -#---------------------------------------------------------------------------- - -class BlockFormat(object): - """ - A block format defines how a Block is formatted. - - These properties control the formatting of the matching Block: - - * CanWrap - If the text for this block cannot fit horizontally, should be wrap to a new line (True) - or should it be truncated (False)? - * Font - What font should be used to draw the text of this block - * Padding - How much padding should be applied to the block before the text or other decorations - are drawn? This can be a numeric (which will be applied to all sides) or it can be - a collection of the paddings to be applied to the various sides: (left, top, right, bottom). - * TextAlignment - How should text be aligned within this block? Can be wx.ALIGN_LEFT, wx.ALIGN_CENTER, or - wx.ALIGN_RIGHT. - * TextColor - In what color should be text be drawn? - - The blocks that are based on cells (PageHeader, ColumnHeader, Row, PageFooter) can also - have the following properties set: - - * AlwaysCenter - Will the text in the cells be center aligned, regardless of other settings? - * CellPadding - How much padding should be applied to this cell before the text or other decorations - are drawn? This can be a numeric (which will be applied to all sides) or it can be a - collection of the paddings to be applied to the various sides: (left, top, right, - bottom). - * GridPen - What Pen will be used to draw the grid lines of the cells? - - In addition to these properties, there are some methods which add various decorations to - the blocks: - - * Background(color=wx.BLUE, toColor=None, space=0) - - This gives the block a solid color background (or a gradient background if *toColor* - is not None). If *space* is not 0, *space* pixels will be subtracted from all sides - from the space available to the block. - - * Frame(pen=None, space=0) - - Draw a rectangle around the block in the given pen - - * Line(side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None) - - Draw a line on a given side of the block. If a pen is given, that is used to draw the - line (and the other parameters are ignored), otherwise a solid line (or a gradient - line is *toColor* is not None) of *width* pixels is drawn. - - """ - - def __init__(self): - """ - """ - self.padding = None - self.decorations = list() - self.font = wx.FFont(11, wx.FONTFAMILY_SWISS, face="Gill Sans") - self.textColor = None - self.textAlignment = wx.ALIGN_LEFT - self.alwaysCenter = False - self.canWrap = False - - #THINK: These attributes are only for grids. Should we have a GridBlockFormat object? - self.cellPadding = None - self.gridPen = None - - #---------------------------------------------------------------------------- - # Accessing - - def GetFont(self): - """ - Return the font used by this format - """ - return self.font - - def SetFont(self, font): - """ - Set the font used by this format - """ - self.font = font - - def GetTextAlignment(self): - """ - Return the alignment of text in this format - """ - return self.textAlignment - - def SetTextAlignment(self, alignment): - """ - Set the alignment of text in this format - """ - self.textAlignment = alignment - - def GetTextColor(self): - """ - Return the color of text in this format - """ - return self.textColor - - def SetTextColor(self, color): - """ - Set the color of text in this format - """ - self.textColor = color - - def GetPadding(self): - """ - Get the padding around this format - """ - return self.padding - - def SetPadding(self, padding): - """ - Set the padding around this format - - Padding is either a single numeric (indicating the values on all sides) - or a collection of paddings [left, top, right, bottom] - """ - self.padding = self._MakePadding(padding) - - def GetCellPadding(self): - """ - Get the padding around cells in this format - """ - return self.cellPadding - - def SetCellPadding(self, padding): - """ - Set the padding around cells in this format - - Padding is either a single numeric (indicating the values on all sides) - or a collection of paddings [left, top, right, bottom] - """ - self.cellPadding = self._MakePadding(padding) - - def GetGridPen(self): - """ - Return the pen used to draw a grid in this format - """ - return self.gridPen - - def SetGridPen(self, pen): - """ - Set the pen used to draw a grid in this format - """ - self.gridPen = pen - if self.gridPen: - # Other styles don't produce nice joins - self.gridPen.SetCap(wx.CAP_BUTT) - self.gridPen.SetJoin(wx.JOIN_MITER) - - def _MakePadding(self, padding): - try: - if len(padding) < 4: - return (tuple(padding) + (0, 0, 0, 0))[:4] - else: - return padding - except TypeError: - return (padding,) * 4 - - def GetAlwaysCenter(self): - """ - Return if the text controlled by this format should always be centered? - """ - return self.alwaysCenter - - def SetAlwaysCenter(self, value): - """ - Remember if the text controlled by this format should always be centered? - """ - self.alwaysCenter = value - - def GetCanWrap(self): - """ - Return if the text controlled by this format can wrap to cover more than one line? - """ - return self.canWrap - - def SetCanWrap(self, value): - """ - Remember if the text controlled by this format can wrap to cover more than one line? - """ - self.canWrap = value - - Font = property(GetFont, SetFont) - Padding = property(GetPadding, SetPadding) - TextAlignment = property(GetTextAlignment, SetTextAlignment) - TextColor = property(GetTextColor, SetTextColor) - CellPadding = property(GetCellPadding, SetCellPadding) - GridPen = property(GetGridPen, SetGridPen) - AlwaysCenter = property(GetAlwaysCenter, SetAlwaysCenter) - CanWrap = property(GetCanWrap, SetCanWrap) - - # Misspellers of the world Untie! - # Ok, ok... there're not really misspellings - just alternatives :) - TextAlign = property(GetTextAlignment, SetTextAlignment) - TextColour = property(GetTextColor, SetTextColor) - - #---------------------------------------------------------------------------- - # Calculations - - def CalculateCellPadding(self): - """ - Return a four-tuple that indicates pixel padding (left, top, right, bottom) - around cells in this format - """ - if self.CellPadding: - cellPadding = list(self.CellPadding) - else: - cellPadding = 0, 0, 0, 0 - - if self.GridPen: - penFactor = int((self.GridPen.GetWidth()+1)/2) - cellPadding = [x + penFactor for x in cellPadding] - - return cellPadding - - #---------------------------------------------------------------------------- - # Decorations - - def Add(self, decoration): - """ - Add the given decoration to those adorning blocks with this format - """ - self.decorations.append(decoration) - - - def Line(self, side=wx.BOTTOM, color=wx.BLACK, width=1, toColor=None, space=0, pen=None): - """ - Add a line to our decorations. - If a pen is given, we use a straight Line decoration, otherwise we use a - coloured rectangle - """ - if pen: - self.Add(LineDecoration(side, pen, space)) - else: - self.Add(RectangleDecoration(side, None, color, toColor, width, space)) - - - def Background(self, color=wx.BLUE, toColor=None, space=0): - """ - Add a coloured background to the block - """ - self.Add(RectangleDecoration(color=color, toColor=toColor, space=space)) - - - def Frame(self, pen=None, space=0): - """ - Add a rectangle around this block - """ - self.Add(RectangleDecoration(pen=pen, space=space)) - - #---------------------------------------------------------------------------- - # Commands - - def SubtractPadding(self, bounds): - """ - Subtract any padding used by this format from the given bounds - """ - if self.padding is None: - return bounds - else: - return RectUtils.InsetRect(bounds, self.padding) - - - def SubtractDecorations(self, dc, bounds): - """ - Subtract any space used by our decorations from the given bounds - """ - for x in self.decorations: - bounds = x.SubtractFrom(dc, bounds) - return bounds - - - def DrawDecorations(self, dc, bounds, block, over): - """ - Draw our decorations on the given block - """ - for x in self.decorations: - if x.IsDrawOver() == over: - x.DrawDecoration(dc, bounds, block) - - -#---------------------------------------------------------------------------- - -class Block(object): - """ - A Block is a portion of a report that will stack vertically with other - Block. A report consists of several Blocks. - """ - - def __init__(self, engine=None): - self.engine = engine # This is also set when the block is added to a print engine - - #---------------------------------------------------------------------------- - # Accessing - - def GetFont(self): - """ - Return Font that should be used to draw text in this block - """ - return self.GetFormat().GetFont() - - - def GetTextColor(self): - """ - Return Colour that should be used to draw text in this block - """ - return self.GetFormat().GetTextColor() - - - def GetFormat(self): - """ - Return the BlockFormat object that controls the formatting of this block - """ - return self.engine.GetNamedFormat(self.__class__.__name__[:-5]) - - - def GetReducedBlockBounds(self, dc, bounds=None): - """ - Return the bounds of this block once padding and decoration have taken their toll. - """ - bounds = bounds or list(self.GetWorkBounds()) - fmt = self.GetFormat() - bounds = fmt.SubtractPadding(bounds) - bounds = fmt.SubtractDecorations(dc, bounds) - return bounds - - - def GetWorkBounds(self): - """ - Return the boundaries of the work area for this block - """ - return self.engine.workBounds - - - def IsColumnHeadingsOnEachPage(self): - """ - Should the column headers be report at the top of each new page? - """ - return self.engine.reportFormat.IsColumnHeadingsOnEachPage - - - def IncludeImages(self): - """ - Should the images from the ListCtrl be printed in the report? - """ - return self.engine.reportFormat.IncludeImages - - - def IsShrinkToFit(self): - """ - Should row blocks be shrunk to fit within the width of a page? - """ - return self.engine.reportFormat.IsShrinkToFit - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - This allows, for example, footers to have '%(currentPage)d of %(totalPages)d' - """ - return True - - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateExtrasHeight(self, dc): - """ - Return the height of the padding and decorations themselves - """ - return 0 - RectUtils.Height(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) - - - def CalculateExtrasWidth(self, dc): - """ - Return the width of the padding and decorations themselves - """ - return 0 - RectUtils.Width(self.GetReducedBlockBounds(dc, [0, 0, 0, 0])) - - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - return -1 - - - def CalculateTextHeight(self, dc, txt, bounds=None, font=None): - """ - Return the height of the given txt in pixels - """ - bounds = bounds or self.GetReducedBlockBounds(dc) - font = font or self.GetFont() - dc.SetFont(font) - if self.GetFormat().CanWrap: - return WordWrapRenderer.CalculateHeight(dc, txt, RectUtils.Width(bounds)) - else: - # Calculate the height of one line. The 1000 pixel width - # ensures that 'Wy' doesn't wrap, which might happen if bounds is narrow - return WordWrapRenderer.CalculateHeight(dc, "Wy", 1000) - - - def CanFit(self, height): - """ - Can this block fit into the remaining work area on the page? - """ - return height <= RectUtils.Height(self.GetWorkBounds()) - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if not self.ShouldPrint(): - return True - - bounds = self.CalculateBounds(dc) - if not self.CanFit(RectUtils.Height(bounds)): - return False - - if self.engine.shouldDrawBlocks: - self.PreDraw(dc, bounds) - self.Draw(dc, bounds) - self.PostDraw(dc, bounds) - - self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) - return True - - - def ShouldPrint(self): - """ - Should this block be printed? - """ - # If this block does not have a format, it is simply skipped - return self.GetFormat() is not None - - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - bounds = RectUtils.SetHeight(bounds, height) - return bounds - - - def ChangeWorkBoundsBy(self, amt): - """ - Move the top of our work bounds down by the given amount - """ - RectUtils.MoveTopBy(self.engine.workBounds, amt) - - - def Draw(self, dc, bounds): - """ - Draw this block and its decorations allowing for any padding - """ - fmt = self.GetFormat() - decorationBounds = fmt.SubtractPadding(bounds) - fmt.DrawDecorations(dc, decorationBounds, self, False) - textBounds = fmt.SubtractDecorations(dc, list(decorationBounds)) - self.DrawSelf(dc, textBounds) - fmt.DrawDecorations(dc, decorationBounds, self, True) - - - def PreDraw(self, dc, bounds): - """ - Executed before any drawing is done - """ - pass - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - pass - - - def PostDraw(self, dc, bounds): - """ - Executed after drawing has completed - """ - pass - - - def DrawText(self, dc, txt, bounds, font=None, alignment=wx.ALIGN_LEFT, valignment=wx.ALIGN_CENTRE, - image=None, color=None, canWrap=True, imageIndex=-1, listCtrl=None): - """ - Draw the given text in the given DC according to the given characteristics. - - This is the workhorse text drawing method for our reporting engine. - - The *font*, *alignment*, and *color* attributes are applied to the drawn text. - - If *image* is not None, it will be drawn to the left of the text. All text is indented - by the width of the image, even if the text is multi-line. - - If *imageIndex* is 0 or more and *listCtrl* is not None, the corresponding image from - the ListCtrl's small image list will be drawn to the left of the text. - - If *canWrap* is True, the text will be wrapped to fit within the given bounds. If it is False, - then the first line of *txt* will be truncated at the edge of the given *bounds*. - """ - GAP_BETWEEN_IMAGE_AND_TEXT = 4 - - def _CalcBitmapPosition(r, height): - if valignment == wx.ALIGN_TOP: - return RectUtils.Top(r) - elif valignment == wx.ALIGN_CENTER: - return RectUtils.CenterY(r) - height / 2 - elif valignment == wx.ALIGN_BOTTOM: - return RectUtils.Bottom(r) - height - else: - return RectUtils.Top(r) - - # Draw any image - if image: - y = _CalcBitmapPosition(bounds, image.Height) - dc.DrawBitmap(image, RectUtils.Left(bounds), y) - RectUtils.MoveLeftBy(bounds, image.GetWidth()+GAP_BETWEEN_IMAGE_AND_TEXT) - elif listCtrl and imageIndex >=0: - imageList = listCtrl.GetImageList(wx.IMAGE_LIST_SMALL) - y = _CalcBitmapPosition(bounds, imageList.GetSize(0)[1]) - imageList.Draw(imageIndex, dc, RectUtils.Left(bounds), y, wx.IMAGELIST_DRAW_TRANSPARENT) - RectUtils.MoveLeftBy(bounds, imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT) - - # Draw the text - dc.SetFont(font or self.GetFont()) - dc.SetTextForeground(color or self.GetTextColor() or wx.BLACK) - if canWrap: - WordWrapRenderer.DrawString(dc, txt, bounds, alignment, valignment) - else: - WordWrapRenderer.DrawTruncatedString(dc, txt, bounds, alignment, valignment) - - - def PerformSubstitutions(self, strings): - """ - Substituted % markers in the given collection of strings. - """ - info = self.engine.GetSubstitutionInfo() - try: - # 'strings' could be a single string or a list of strings - try: - return strings % info - except TypeError: - return [x % info for x in strings] - except ValueError: - # We don't die if we get a substitution error - we just ignore it - return strings - -#---------------------------------------------------------------------------- - -class TextBlock(Block): - """ - A TextBlock prints a string objects. - """ - - def ShouldPrint(self): - """ - Should this block be printed? - """ - # If the block has no text, it should not be printed - if self.GetText(): - return Block.ShouldPrint(self) - else: - return False - - - def GetText(self): - return "Missing GetText() in class %s" % self.__class__.__name__ - - - def GetSubstitutedText(self): - """ - Return the text for this block after all markers have been substituted - """ - if self.IsUseSubstitution(): - return self.PerformSubstitutions(self.GetText()) - else: - return self.GetText() - - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - textHeight = self.CalculateTextHeight(dc, self.GetSubstitutedText()) - extras = self.CalculateExtrasHeight(dc) - return textHeight + extras - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - fmt = self.GetFormat() - self.DrawText(dc, self.GetSubstitutedText(), bounds, canWrap=fmt.CanWrap, alignment=fmt.TextAlignment) - - -#---------------------------------------------------------------------------- - -class CellBlock(Block): - """ - A CellBlock is a Block whose data is presented in a cell format. - """ - - def __init__(self): - self.scale = 1 - self.oldScale = 1 - - #---------------------------------------------------------------------------- - # Accessing - Subclasses should override - - def GetCellWidths(self): - """ - Return a list of the widths of the cells in this block. - """ - return list() - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return list() - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return list() - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - return list() - - - #---------------------------------------------------------------------------- - # Accessing - - - def GetCombinedLists(self): - """ - Return a collection of Buckets that hold all the values of the - subclass-overridable collections above - """ - buckets = [Bucket(cellWidth=x, text="", align=None, image=None) for x in self.GetCellWidths()] - for (i, x) in enumerate(self.GetSubstitutedTexts()): - buckets[i].text = x - for (i, x) in enumerate(self.GetAlignments()): - buckets[i].align = x - - if self.IncludeImages(): - for (i, x) in enumerate(self.GetImages()): - buckets[i].image = x - - # Calculate where the cell contents should be drawn - cellPadding = self.GetFormat().CalculateCellPadding() - for x in buckets: - x.innerCellWidth = max(0, x.cellWidth - (cellPadding[0] + cellPadding[2])) - - return buckets - - - def GetListCtrl(self): - """ - Return the ListCtrl that is behind this cell block. - """ - return None - - - def GetSubstitutedTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - if self.IsUseSubstitution(): - return self.PerformSubstitutions(self.GetTexts()) - else: - return self.GetTexts() - - - #---------------------------------------------------------------------------- - # Calculating - - def CalculateHeight(self, dc): - """ - Return the heights of this block in pixels - """ - GAP_BETWEEN_IMAGE_AND_TEXT = 4 - - # If cells can wrap, figure out the tallest, otherwise we just figure out the height of one line - if self.GetFormat().CanWrap: - font = self.GetFont() - height = 0 - for x in self.GetCombinedLists(): - textWidth = x.innerCellWidth - if self.GetListCtrl() and x.image != -1: - imageList = self.GetListCtrl().GetImageList(wx.IMAGE_LIST_SMALL) - textWidth -= imageList.GetSize(0)[0]+GAP_BETWEEN_IMAGE_AND_TEXT - bounds = [0, 0, textWidth, 99999] - height = max(height, self.CalculateTextHeight(dc, x.text, bounds, font)) - else: - height = self.CalculateTextHeight(dc, "Wy") - - # We also have to allow for cell padding, on top of the normal padding and decorations - cellPadding = self.GetFormat().CalculateCellPadding() - return height + cellPadding[1] + cellPadding[3] + self.CalculateExtrasHeight(dc) - - - def CalculateWidth(self, dc): - """ - Calculate the total width of this block (cells plus padding) - """ - return sum(x.cellWidth for x in self.GetCombinedLists()) + self.CalculateExtrasWidth(dc) - - #---------------------------------------------------------------------------- - # Commands - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - bounds = RectUtils.SetHeight(bounds, height) - bounds = RectUtils.SetWidth(bounds, self.CalculateWidth(dc)) - bounds = RectUtils.MultiplyOrigin(bounds, 1 / self.scale) - return bounds - - #def CanFit(self, height): - # height *= self.scale - # return Block.CanFit(self, height) - - def ChangeWorkBoundsBy(self, height): - """ - Change our workbounds by our scaled height - """ - Block.ChangeWorkBoundsBy(self, height * self.scale) - - - def PreDraw(self, dc, bounds): - """ - Apply our scale before performing any drawing - """ - self.oldScale = dc.GetUserScale() - dc.SetUserScale(self.scale * self.oldScale[0], self.scale * self.oldScale[1]) - - - def PostDraw(self, dc, bounds): - """ - Restore the scaling to what it used to be - """ - dc.SetUserScale(*self.oldScale) - - - def DrawSelf(self, dc, bounds): - """ - Do the actual work of rendering this block. - """ - cellFmt = self.GetFormat() - cellPadding = cellFmt.CalculateCellPadding() - combined = self.GetCombinedLists() - - # Calculate cell boundaries - cell = list(bounds) - RectUtils.SetWidth(cell, 0) - for x in combined: - RectUtils.SetLeft(cell, RectUtils.Right(cell)) - RectUtils.SetWidth(cell, x.cellWidth) - x.cell = list(cell) - - # Draw each cell - font = self.GetFont() - for x in combined: - cellBounds = RectUtils.InsetRect(x.cell, cellPadding) - self.DrawText(dc, x.text, cellBounds, font, x.align, imageIndex=x.image, - canWrap=cellFmt.CanWrap, listCtrl=self.GetListCtrl()) - - if cellFmt.GridPen and combined: - dc.SetPen(cellFmt.GridPen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - - top = RectUtils.Top(bounds) - bottom = RectUtils.Bottom(bounds) - - # Draw the interior dividers - for x in combined[:-1]: - right = RectUtils.Right(x.cell) - dc.DrawLine(right, top, right, bottom) - - # Draw the surrounding frame - left = RectUtils.Left(combined[0].cell) - right = RectUtils.Right(combined[-1].cell) - dc.DrawRectangle(left, top, right-left, bottom-top) - - -#---------------------------------------------------------------------------- - -class ThreeCellBlock(CellBlock): - """ - A ThreeCellBlock divides its space evenly into three cells. - """ - - def GetCellWidths(self): - """ - Return a list of the widths of the cells in this block - """ - # We divide the available space between the non-empty cells - numNonEmptyTexts = sum(1 for x in self.GetTexts() if x) - if not numNonEmptyTexts: - return (0, 0, 0) - - widths = list() - width = round(RectUtils.Width(self.GetWorkBounds()) / numNonEmptyTexts) - for x in self.GetTexts(): - if x: - widths.append(width) - else: - widths.append(0) - return widths - - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return (wx.ALIGN_LEFT, wx.ALIGN_CENTER_HORIZONTAL, wx.ALIGN_RIGHT) - -#---------------------------------------------------------------------------- - -class ReportBlock(Block): - """ - A ReportBlock is boot strap Block that represents an entire report. - """ - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if not self.engine.listCtrls: - return True - - # Print each ListView. Each list but the first starts on a separate page - self.engine.AddBlock(ListBlock(*self.engine.listCtrls[0])) - for (lv, title) in self.engine.listCtrls[1:]: - self.engine.AddBlock(PageBreakBlock()) - self.engine.AddBlock(ListBlock(lv, title)) - - return True - - -#---------------------------------------------------------------------------- - -class PageHeaderBlock(ThreeCellBlock): - """ - A PageHeaderBlock appears at the top of every page. - """ - - def GetTexts(self): - """ - Return the array of texts to be printed in the cells - """ - return self.engine.pageHeader - - - -#---------------------------------------------------------------------------- - -class PageFooterBlock(ThreeCellBlock): - """ - A PageFooterBlock appears at the bottom of every page. - """ - - def GetTexts(self): - """ - Return the array of texts to be printed in the cells - """ - return self.engine.pageFooter - - - #---------------------------------------------------------------------------- - # Printing - - - def CalculateBounds(self, dc): - """ - Calculate the bounds of this block - """ - # Draw the footer at the bottom of the page - height = self.CalculateHeight(dc) - bounds = list(self.GetWorkBounds()) - return [RectUtils.Left(bounds), RectUtils.Bottom(bounds) - height, - RectUtils.Width(bounds), height] - - - def ChangeWorkBoundsBy(self, height): - """ - The footer changes the bottom of the work bounds - """ - RectUtils.MoveBottomBy(self.engine.workBounds, -height) - - -#---------------------------------------------------------------------------- - -class PageBreakBlock(Block): - """ - A PageBreakBlock acts a page break. - """ - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - - # Completely fill the remaining area on the page, forcing a page break - bounds = self.GetWorkBounds() - self.ChangeWorkBoundsBy(RectUtils.Height(bounds)) - - return True - -#---------------------------------------------------------------------------- - -class RunningBlockPusher(Block): - """ - A RunningBlockPusher pushes (or pops) a running block onto the stack when it is executed. - """ - - def __init__(self, block, push=True): - """ - """ - self.block = block - self.push = push - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - if self.push: - self.engine.AddRunningBlock(self.block) - else: - self.engine.RemoveRunningBlock(self.block) - - return True - -#---------------------------------------------------------------------------- - -class ListBlock(Block): - """ - A ListBlock handles the printing of an entire ListCtrl. - """ - - def __init__(self, lv, title): - self.lv = lv - self.title = title - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - - cellWidths = self.CalculateCellWidths() - boundsWidth = RectUtils.Width(self.GetWorkBounds()) - - # Break the list into vertical slices. Each one but the first will be placed on a - # new page. - first = True - for (left, right) in self.CalculateSlices(boundsWidth, cellWidths): - if not first: - self.engine.AddBlock(PageBreakBlock()) - self.engine.AddBlock(ListHeaderBlock(self.lv, self.title)) - self.engine.AddBlock(ListSliceBlock(self.lv, left, right, cellWidths)) - self.engine.AddBlock(ListFooterBlock(self.lv, "")) - first = False - - return True - - - def CalculateCellWidths(self): - """ - Return a list of the widths of the cells in this lists - """ - columnHeaderFmt = self.engine.GetNamedFormat("ColumnHeader") - cellPadding = columnHeaderFmt.CalculateCellPadding() - padding = cellPadding[0] + cellPadding[2] - return [self.lv.GetColumnWidth(i) + padding for i in range(self.lv.GetColumnCount())] - - - def CalculateSlices(self, maxWidth, columnWidths): - """ - Return a list of integer pairs, where each pair represents - the left and right columns that can fit into the width of one page - """ - firstColumn = 0 - - # If a GroupListView has a column just for the expand/collapse, don't include it - if hasattr(self.lv, "useExpansionColumn") and self.lv.useExpansionColumn: - firstColumn = 1 - - # If we are shrinking to fit or all the columns fit, just return all columns - if self.IsShrinkToFit() or (sum(columnWidths)) <= maxWidth: - return [ [firstColumn, len(columnWidths)-1] ] - - pairs = list() - left = firstColumn - right = firstColumn - while right < len(columnWidths): - if (sum(columnWidths[left:right+1])) > maxWidth: - if left == right: - pairs.append([left, right]) - left += 1 - right += 1 - else: - pairs.append([left, right-1]) - left = right - else: - right += 1 - - if left < len(columnWidths): - pairs.append([left, right-1]) - - return pairs - - -#---------------------------------------------------------------------------- - -class ListHeaderBlock(TextBlock): - """ - A ListHeaderBlock is the title that comes before an ListCtrl. - """ - - def __init__(self, lv, title): - self.lv = lv - self.title = title - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.title - -#---------------------------------------------------------------------------- - -class ListFooterBlock(TextBlock): - """ - A ListFooterBlock is the text that comes after an ListCtrl. - """ - - def __init__(self, lv, text): - self.lv = lv - self.text = text - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.text - - -#---------------------------------------------------------------------------- - -class GroupTitleBlock(TextBlock): - """ - A GroupTitleBlock is the title that comes before a list group. - """ - - def __init__(self, lv, group): - self.lv = lv - self.group = group - - def GetText(self): - """ - Return the text that will be printed in this block - """ - return self.group.title - -#---------------------------------------------------------------------------- - -class ListSliceBlock(Block): - """ - A ListSliceBlock prints a vertical slice of an ListCtrl. - """ - - def __init__(self, lv, left, right, allCellWidths): - self.lv = lv - self.left = left - self.right = right - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # If we are shrinking our cells, calculate the shrink factor - if self.IsShrinkToFit(): - scale = self.CalculateShrinkToFit(dc) - else: - scale = 1 - - headerBlock = ColumnHeaderBlock(self.lv, self.left, self.right, scale, self.allCellWidths) - self.engine.AddBlock(headerBlock) - - if self.IsColumnHeadingsOnEachPage(): - self.engine.AddBlock(RunningBlockPusher(headerBlock)) - - # Are we printing a GroupListView? - # We can't use isinstance() since ObjectListView may not be installed - if hasattr(self.lv, "GetShowGroups") and self.lv.GetShowGroups(): - self.engine.AddBlock(GroupListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) - else: - self.engine.AddBlock(ListRowsBlock(self.lv, self.left, self.right, scale, self.allCellWidths)) - - if self.IsColumnHeadingsOnEachPage(): - self.engine.AddBlock(RunningBlockPusher(headerBlock, False)) - - return True - - def CalculateShrinkToFit(self, dc): - """ - How much do we have to shrink our rows by to fit onto the page? - """ - block = ColumnHeaderBlock(self.lv, self.left, self.right, 1, self.allCellWidths) - block.engine = self.engine - rowWidth = block.CalculateWidth(dc) - boundsWidth = RectUtils.Width(self.GetWorkBounds()) - - if rowWidth > boundsWidth: - return float(boundsWidth) / float(rowWidth) - else: - return 1 - -#---------------------------------------------------------------------------- - -class ColumnBasedBlock(CellBlock): - """ - A ColumnBasedBlock is a cell block that takes its width from the - columns of a ListCtrl. - - This is an abstract class - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - self.lv = lv - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Accessing - Subclasses should override - - def GetListCtrl(self): - """ - Return the ListCtrl that is behind this cell block. - """ - return self.lv - - - def GetCellWidths(self): - """ - Return the widths of the cells used in this block - """ - #return [self.allCellWidths[i] for i in range(self.left, self.right+1)] - return self.allCellWidths[self.left:self.right+1] - - #---------------------------------------------------------------------------- - # Utiltities - - def GetColumnAlignments(self, lv, left, right): - """ - Return the alignments of the given slice of columns - """ - listAlignments = [lv.GetColumn(i).GetAlign() for i in range(left, right+1)] - mapping = { - wx.LIST_FORMAT_LEFT: wx.ALIGN_LEFT, - wx.LIST_FORMAT_RIGHT: wx.ALIGN_RIGHT, - wx.LIST_FORMAT_CENTRE: wx.ALIGN_CENTRE, - } - return [mapping[x] for x in listAlignments] - - - -#---------------------------------------------------------------------------- - -class ColumnHeaderBlock(ColumnBasedBlock): - """ - A ColumnHeaderBlock prints a portion of the columns header in a ListCtrl. - """ - - #---------------------------------------------------------------------------- - # Accessing - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return [self.lv.GetColumn(i).GetText() for i in range(self.left, self.right+1)] - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - if self.GetFormat().AlwaysCenter: - return [wx.ALIGN_CENTRE for i in range(self.left, self.right+1)] - else: - return self.GetColumnAlignments(self.lv, self.left, self.right) - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - # For some reason, columns return 0 when they have no image, rather than -1 like they should - images = list() - for i in range(self.left, self.right+1): - imageIndex = self.lv.GetColumn(i).GetImage() - if imageIndex == 0: - imageIndex = -1 - images.append(imageIndex) - return images - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - Normally, we don't want to substitute within values that comes from the ListCtrl. - """ - return False - - -#---------------------------------------------------------------------------- - -class ListRowsBlock(Block): - """ - A ListRowsBlock prints rows of an ListCtrl. - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - """ - """ - self.lv = lv - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - self.currentIndex = 0 - self.totalRows = self.lv.GetItemCount() - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # This block works by printing a single row and then rescheduling itself - # to print the remaining rows after the current row has finished. - - if self.currentIndex < self.totalRows: - self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) - self.currentIndex += 1 - self.engine.AddBlock(self) - - return True - -#---------------------------------------------------------------------------- - -class GroupListRowsBlock(Block): - """ - A GroupListRowsBlock prints rows of an GroupListView. - """ - - def __init__(self, lv, left, right, scale, allCellWidths): - """ - """ - self.lv = lv # Must be a GroupListView - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - self.currentIndex = 0 - self.totalRows = self.lv.GetItemCount() - - #---------------------------------------------------------------------------- - # Commands - - def Print(self, dc): - """ - Print this Block. - - Return True if the Block has finished printing - """ - # This block works by printing a single row and then rescheduling itself - # to print the remaining rows after the current row has finished. - - if self.currentIndex >= self.totalRows: - return True - - # If GetObjectAt() return an object, then it's a normal row. - # Otherwise, if the innerList object isn't None, it must be a ListGroup - # We can't use isinstance(x, GroupListView) because ObjectListView may not be installed - if self.lv.GetObjectAt(self.currentIndex): - self.engine.AddBlock(RowBlock(self.lv, self.currentIndex, self.left, self.right, self.scale, self.allCellWidths)) - elif self.lv.innerList[self.currentIndex]: - self.engine.AddBlock(GroupTitleBlock(self.lv, self.lv.innerList[self.currentIndex])) - - # Schedule the printing of the remaining rows - self.currentIndex += 1 - self.engine.AddBlock(self) - - return True - - -#---------------------------------------------------------------------------- - -class RowBlock(ColumnBasedBlock): - """ - A RowBlock prints a vertical slice of a single row of an ListCtrl. - """ - - def __init__(self, lv, rowIndex, left, right, scale, allCellWidths): - self.lv = lv - self.rowIndex = rowIndex - self.left = left - self.right = right - self.scale = scale - self.allCellWidths = allCellWidths - - #---------------------------------------------------------------------------- - # Accessing - - - def GetFont(self): - """ - Return Font that should be used to draw text in this block - """ - font = None - if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): - # Figure out what font is being used for this row in the list. - # Unfortunately, there is no one way to find this information: virtual mode lists - # have to be treated in a different manner from non-virtual lists. - listCtrl = self.GetListCtrl() - if listCtrl.IsVirtual(): - attr = listCtrl.OnGetItemAttr(self.rowIndex) - if attr and attr.HasFont(): - font = attr.GetFont() - else: - font = listCtrl.GetItemFont(self.rowIndex) - - if font and font.IsOk(): - return font - else: - return self.GetFormat().GetFont() - - - def GetTextColor(self): - """ - Return Colour that should be used to draw text in this block - """ - color = None - if self.engine.reportFormat.UseListCtrlTextFormat and self.GetListCtrl(): - # Figure out what text colour is being used for this row in the list. - # Unfortunately, there is no one way to find this information: virtual mode lists - # have to be treated in a different manner from non-virtual lists. - listCtrl = self.GetListCtrl() - if listCtrl.IsVirtual(): - attr = listCtrl.OnGetItemAttr(self.rowIndex) - if attr and attr.HasTextColour(): - color = attr.GetTextColour() - else: - color = listCtrl.GetItemTextColour(self.rowIndex) - - if color and color.IsOk(): - return color - else: - return self.GetFormat().GetTextColor() - - - def IsUseSubstitution(self): - """ - Should the text values printed by this block have substitutions performed before being printed? - - Normally, we don't want to substitute within values that comes from the ListCtrl. - """ - return False - - #---------------------------------------------------------------------------- - # Overrides for CellBlock - - def GetTexts(self): - """ - Return a list of the texts that should be drawn with the cells - """ - return [self.lv.GetItem(self.rowIndex, i).GetText() for i in range(self.left, self.right+1)] - - def GetAlignments(self): - """ - Return a list indicating how the text within each cell is aligned. - """ - return self.GetColumnAlignments(self.lv, self.left, self.right) - - def GetImages(self): - """ - Return a list of the images that should be drawn in each cell - """ - return [self.lv.GetItem(self.rowIndex, i).GetImage() for i in range(self.left, self.right+1)] - - #def GetTexts(self): - # """ - # Return a list of the texts that should be drawn with the cells - # """ - # modelObjects = self.lv.GetObjectAt(self.rowIndex) - # return [self.lv.GetStringValueAt(modelObjects, i) for i in range(self.left, self.right+1)] - # - #def GetAlignments(self): - # """ - # Return a list indicating how the text within each cell is aligned. - # """ - # return [self.lv.columns[i].GetAlignment() for i in range(self.left, self.right+1)] - # - #def GetImages(self): - # """ - # Return a list of the images that should be drawn in each cell - # """ - # modelObjects = self.lv.GetObjectAt(self.rowIndex) - # return [self.lv.GetImageAt(modelObjects, i) for i in range(self.left, self.right+1)] - - -#---------------------------------------------------------------------------- - -class Decoration(object): - """ - A Decoration add some visual effect to a Block (e.g. borders, - background image, watermark). They can also reserve a chunk of their Blocks - space for their own use. - - Decorations are added to a BlockFormat which is then applied to a ReportBlock. - - All the decorations for a block are drawn into the same area. If two decorations are - added, they will draw over the top of each other. This is normally what is expected, - but may sometimes be surprising. For example, if you add two Lines to the left of the - same block, they will draw over the top of each other. - - """ - - #---------------------------------------------------------------------------- - # Accessing - - def IsDrawOver(self): - """ - Return True if this decoration should be drawn over it's block. If not, - it will be drawn underneath - """ - return False - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - return bounds - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - pass - -#---------------------------------------------------------------------------- - -class RectangleDecoration(Decoration): - """ - A RectangleDecoration draw a rectangle around or on the side of a block. - - The rectangle can be hollow, solid filled or gradient-filled. It can have - a frame drawn as well. - - """ - - def __init__(self, side=None, pen=None, color=None, toColor=None, width=0, space=0): - """ - If color is None, the rectangle will be hollow. - If toColor is None, the rectangle will be filled with "color" otherwise it will be - gradient-filled. - If pen is not None, the rectangle will be framed with that pen. - If no side is given, the rectangle will be drawn around the block. In that case, - space can be a list giving the space on each side. - """ - self.side = side - self.pen = pen - self.color = color - self.toColor = toColor - self.width = width - self.space = space - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - if self.side is None: - return RectUtils.InsetBy(RectUtils.InsetBy(bounds, self.space), self.width) - - inset = self.space + self.width - if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) - if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) - if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) - if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) - return bounds - - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - rect = self._CalculateRect(bounds) - - if self.color: - if self.toColor is None: - dc.SetPen(wx.TRANSPARENT_PEN) - dc.SetBrush(wx.Brush(self.color)) - dc.DrawRectangle(*rect) - else: - dc.GradientFillLinear(wx.Rect(*rect), self.color, self.toColor) - - if self.pen: - dc.SetPen(self.pen) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - dc.DrawRectangle(*rect) - - - def _CalculateRect(self, bounds): - """ - Calculate the rectangle that this decoration is going to paint - """ - if self.side is None: - return bounds - if self.side == wx.LEFT: - return (RectUtils.Left(bounds), RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) - if self.side == wx.RIGHT: - return (RectUtils.Right(bounds) - self.width, RectUtils.Top(bounds), self.width, RectUtils.Height(bounds)) - if self.side == wx.TOP: - return (RectUtils.Left(bounds), RectUtils.Top(bounds), RectUtils.Width(bounds), self.width) - if self.side == wx.BOTTOM: - return (RectUtils.Left(bounds), RectUtils.Bottom(bounds) - self.width, RectUtils.Width(bounds), self.width) - - return bounds - -#---------------------------------------------------------------------------- - -class LineDecoration(Decoration): - """ - A LineDecoration draws a line on the side of a decoration. - """ - - def __init__(self, side=wx.BOTTOM, pen=None, space=0): - self.side = side - self.pen = pen - self.space = space - - #---------------------------------------------------------------------------- - # Commands - - def SubtractFrom(self, dc, bounds): - """ - Subtract the space used by this decoration from the given bounds - """ - inset = self.space - if self.pen is not None: - inset += self.pen.GetWidth() - - if self.side == wx.LEFT: return RectUtils.MoveLeftBy(bounds, inset) - if self.side == wx.RIGHT: return RectUtils.MoveRightBy(bounds, -inset) - if self.side == wx.TOP: return RectUtils.MoveTopBy(bounds, inset) - if self.side == wx.BOTTOM: return RectUtils.MoveBottomBy(bounds, -inset) - return bounds - - - def DrawDecoration(self, dc, bounds, block): - """ - Draw this decoration - """ - if self.pen == None: - return - - if self.side == wx.LEFT: - pt1 = RectUtils.TopLeft(bounds) - pt2 = RectUtils.BottomLeft(bounds) - elif self.side == wx.RIGHT: - pt1 = RectUtils.TopRight(bounds) - pt2 = RectUtils.BottomRight(bounds) - elif self.side == wx.TOP: - pt1 = RectUtils.TopLeft(bounds) - pt2 = RectUtils.TopRight(bounds) - elif self.side == wx.BOTTOM: - pt1 = RectUtils.BottomLeft(bounds) - pt2 = RectUtils.BottomRight(bounds) - - dc.SetPen(self.pen) - dc.DrawLine(pt1[0], pt1[1], pt2[0], pt2[1]) - -#---------------------------------------------------------------------------- - -class WatermarkDecoration(Decoration): - """ - A WatermarkDecoration draws an angled line of text over each page. - - The watermark is rotated around the center of the page. - - If *over* is True, the watermark will be printed on top of the page. - Otherwise, it will be printed under the page. - - """ - - def __init__(self, text, font=None, color=None, angle=30, over=True): - """ - """ - self.text = text - self.color = color or wx.Color(128, 128, 128, 128) - self.font = font or wx.FFont(128, wx.SWISS, 0) - self.angle = angle - self.over = over - - def IsDrawOver(self): - """ - Should this decoration be drawn over the rest of page? - """ - return self.over - - def DrawDecoration(self, dc, bounds, block): - """ - Draw the decoration - """ - dc.SetFont(self.font) - dc.SetTextForeground(self.color) - - # Rotate the text around the center of the page - cx, cy = RectUtils.Center(bounds) - w, h = dc.GetTextExtent(self.text) - - x = cx - w/2 - y = cy - h/2 + (w/2 * math.sin(math.radians(self.angle))) - - dc.DrawRotatedText(self.text, x, y, self.angle) - -#---------------------------------------------------------------------------- - -class ImageDecoration(Decoration): - """ - A ImageDecoration draws an image over (or under) the given block. - - NB: Printer contexts do not honor alpha channels. - """ - - def __init__(self, image=None, horizontalAlign=wx.CENTER, verticalAlign=wx.CENTER, over=True): - """ - image must be either an wx.Image or a wx.Bitmap - """ - self.horizontalAlign = horizontalAlign - self.verticalAlign = verticalAlign - self.over = True - - self.bitmap = image - if isinstance(image, wx.Image): - self.bitmap = wx.BitmapFromImage(image) - - def DrawDecoration(self, dc, bounds, block): - """ - Draw the decoration - """ - if not self.bitmap: - return - - if self.horizontalAlign == wx.LEFT: - x = RectUtils.Left(bounds) - elif self.horizontalAlign == wx.RIGHT: - x = RectUtils.Right(bounds) - self.bitmap.Width - else: - x = RectUtils.CenterX(bounds) - self.bitmap.Width/2 - - if self.verticalAlign == wx.TOP: - y = RectUtils.Top(bounds) - elif self.verticalAlign == wx.BOTTOM: - y = RectUtils.Bottom(bounds) - self.bitmap.Height - else: - y = RectUtils.CenterY(bounds) - self.bitmap.Height/2 - - dc.DrawBitmap(self.bitmap, x, y, True) - - -#---------------------------------------------------------------------------- - -class Bucket(object): - """ - General purpose, hold-all data object - """ - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - strs = ["%s=%r" % kv for kv in self.__dict__.items()] - return "Bucket(" + ", ".join(strs) + ")" - -#---------------------------------------------------------------------------- - -class RectUtils: - """ - Static rectangle utilities. - - Rectangles are a list or tuple of 4-elements: [left, top, width, height] - """ - - #---------------------------------------------------------------------------- - # Accessing - - @staticmethod - def Left(r): return r[0] - - @staticmethod - def Top(r): return r[1] - - @staticmethod - def Width(r): return r[2] - - @staticmethod - def Height(r): return r[3] - - @staticmethod - def Right(r): return r[0] + r[2] - - @staticmethod - def Bottom(r): return r[1] + r[3] - - @staticmethod - def TopLeft(r): return [r[0], r[1]] - - @staticmethod - def TopRight(r): return [r[0] + r[2], r[1]] - - @staticmethod - def BottomLeft(r): return [r[0], r[1] + r[3]] - - @staticmethod - def BottomRight(r): return [r[0] + r[2], r[1] + r[3]] - - @staticmethod - def CenterX(r): return r[0] + r[2]/2 - - @staticmethod - def CenterY(r): return r[1] + r[3]/2 - - @staticmethod - def Center(r): return [r[0] + r[2]/2, r[1] + r[3]/2] - - #---------------------------------------------------------------------------- - # Modifying - - @staticmethod - def SetLeft(r, l): - r[0] = l - return r - - @staticmethod - def SetTop(r, t): - r[1] = t - return r - - @staticmethod - def SetWidth(r, w): - r[2] = w - return r - - @staticmethod - def SetHeight(r, h): - r[3] = h - return r - - @staticmethod - def MoveLeftBy(r, delta): - r[0] += delta - r[2] -= delta - return r - - @staticmethod - def MoveTopBy(r, delta): - r[1] += delta - r[3] -= delta - return r - - @staticmethod - def MoveRightBy(r, delta): - r[2] += delta - return r - - @staticmethod - def MoveBottomBy(r, delta): - r[3] += delta - return r - - #---------------------------------------------------------------------------- - # Calculations - - @staticmethod - def InsetBy(r, delta): - if delta is None: - return r - try: - delta[0] # is it indexable? - return RectUtils.InsetRect(r, delta) - except: - return RectUtils.InsetRect(r, (delta, delta, delta, delta)) - - @staticmethod - def InsetRect(r, r2): - if r2 is None: - return r - else: - return [r[0] + r2[0], r[1] + r2[1], r[2] - (r2[0] + r2[2]), r[3] - (r2[1] + r2[3])] - - @staticmethod - def MultiplyOrigin(r, factor): - return [r[0]*factor, r[1]*factor, r[2], r[3]] - -#---------------------------------------------------------------------------- -# TESTING ONLY -#---------------------------------------------------------------------------- - -if __name__ == '__main__': - import wx - from ObjectListView import ObjectListView, FastObjectListView, GroupListView, ColumnDefn - - # Where can we find the Example module? - import sys - sys.path.append("../Examples") - - import ExampleModel - import ExampleImages - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - #self.lv = ObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - self.lv = GroupListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - #self.lv = FastObjectListView(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.lv, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - musicImage = self.lv.AddImages(ExampleImages.getMusic16Bitmap(), ExampleImages.getMusic32Bitmap()) - artistImage = self.lv.AddImages(ExampleImages.getUser16Bitmap(), ExampleImages.getUser32Bitmap()) - - self.lv.SetColumns([ - ColumnDefn("Title", "left", 200, "title", imageGetter=musicImage), - ColumnDefn("Artist", "left", 150, "artist", imageGetter=artistImage), - ColumnDefn("Last Played", "left", 100, "lastPlayed"), - ColumnDefn("Size", "center", 100, "sizeInBytes"), - ColumnDefn("Rating", "center", 100, "rating"), - ]) - - #self.lv.CreateCheckStateColumn() - self.lv.SetSortColumn(self.lv.columns[2]) - self.lv.SetObjects(ExampleModel.GetTracks()) - - wx.CallLater(50, self.run) - - def run(self): - printer = ListCtrlPrinter(self.lv, "Playing with ListCtrl Printing") - printer.ReportFormat = ReportFormat.Normal() - printer.ReportFormat.WatermarkFormat(over=True) - printer.ReportFormat.IsColumnHeadingsOnEachPage = True - - #printer.ReportFormat.Page.Add(ImageDecoration(ExampleImages.getGroup32Bitmap(), wx.RIGHT, wx.BOTTOM)) - - #printer.PageHeader("%(listTitle)s") # nice idea but not possible at the moment - printer.PageHeader = "Playing with ListCtrl Printing" - printer.PageFooter = ("Bright Ideas Software", "%(date)s", "%(currentPage)d of %(totalPages)d") - printer.Watermark = "Sloth!" - - #printer.PageSetup() - printer.PrintPreview(self) - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() diff --git a/odmtools/lib/oldOlv/OLVEvent.py b/odmtools/lib/oldOlv/OLVEvent.py deleted file mode 100644 index 1c4c36f..0000000 --- a/odmtools/lib/oldOlv/OLVEvent.py +++ /dev/null @@ -1,279 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: OLVEvent.py -# Author: Phillip Piper -# Created: 3 April 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/18 JPP Added CELL_EDIT_STARTED and CELL_EDIT_FINISHED events -# 2008/07/16 JPP Added group-related events -# 2008/06/19 JPP Added EVT_SORT -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/04/04 JPP Initial version complete -#---------------------------------------------------------------------------- -# To do: - -""" -The OLVEvent module holds all the events used by the ObjectListView module. -""" - -__author__ = "Phillip Piper" -__date__ = "3 August 2008" -__version__ = "1.1" - -import wx - -#====================================================================== -# Event ids and types - -def _EventMaker(): - evt = wx.NewEventType() - return (evt, wx.PyEventBinder(evt)) - -(olv_EVT_CELL_EDIT_STARTING, EVT_CELL_EDIT_STARTING) = _EventMaker() -(olv_EVT_CELL_EDIT_STARTED, EVT_CELL_EDIT_STARTED) = _EventMaker() -(olv_EVT_CELL_EDIT_FINISHING, EVT_CELL_EDIT_FINISHING) = _EventMaker() -(olv_EVT_CELL_EDIT_FINISHED, EVT_CELL_EDIT_FINISHED) = _EventMaker() -(olv_EVT_SORT, EVT_SORT) = _EventMaker() -(olv_EVT_GROUP_CREATING, EVT_GROUP_CREATING) = _EventMaker() -(olv_EVT_GROUP_SORT, EVT_GROUP_SORT) = _EventMaker() -(olv_EVT_EXPANDING, EVT_EXPANDING) = _EventMaker() -(olv_EVT_EXPANDED, EVT_EXPANDED) = _EventMaker() -(olv_EVT_COLLAPSING, EVT_COLLAPSING) = _EventMaker() -(olv_EVT_COLLAPSED, EVT_COLLAPSED) = _EventMaker() - -#====================================================================== -# Event parameter blocks - -class VetoableEvent(wx.PyCommandEvent): - """ - Base class for all cancellable actions - """ - - def __init__(self, evtType): - wx.PyCommandEvent.__init__(self, evtType, -1) - self.veto = False - - def Veto(self, isVetoed=True): - """ - Veto (or un-veto) this event - """ - self.veto = isVetoed - - def IsVetoed(self): - """ - Has this event been vetod? - """ - return self.veto - -#---------------------------------------------------------------------------- - -class CellEditEvent(VetoableEvent): - """ - Base class for all cell editing events - """ - - def SetParameters(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor): - self.objectListView = objectListView - self.rowIndex = rowIndex - self.subItemIndex = subItemIndex - self.rowModel = rowModel - self.cellValue = cellValue - self.editor = editor - -#---------------------------------------------------------------------------- - -class CellEditStartedEvent(CellEditEvent): - """ - A cell has started to be edited. - - All attributes are public and should be considered read-only. - """ - - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTED) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.cellBounds = cellBounds - -#---------------------------------------------------------------------------- - -class CellEditStartingEvent(CellEditEvent): - """ - A cell is about to be edited. - - All attributes are public and should be considered read-only. Methods are provided for - information that can be changed. - """ - - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, cellBounds, editor): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_STARTING) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.cellBounds = cellBounds - self.newEditor = None - self.shouldConfigureEditor = True - - def SetCellBounds(self, rect): - """ - Change where the editor will be placed. - rect is a list: [left, top, width, height] - """ - self.cellBounds = rect - - def SetNewEditor(self, control): - """ - Use the given control instead of the editor. - """ - self.newEditor = control - - def DontConfigureEditor(self): - """ - The editor will not be automatically configured. - - If this is called, the event handler must handle all configuration. In - particular, it must configure its own event handlers to that - ObjectListView.CancelCellEdit() is called when the user presses Escape, - and ObjectListView.CommitCellEdit() is called when the user presses - Enter/Return or when the editor loses focus. """ - self.shouldConfigureEditor = False - -#---------------------------------------------------------------------------- - -class CellEditFinishedEvent(CellEditEvent): - """ - The user has finished editing a cell. - """ - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, userCancelled): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHED) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, None, None) - self.userCancelled = userCancelled - -#---------------------------------------------------------------------------- - -class CellEditFinishingEvent(CellEditEvent): - """ - The user is finishing editing a cell. - - If this event is vetoed, the edit will be cancelled silently. This is useful if the - event handler completely handles the model updating. - """ - def __init__(self, objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor, userCancelled): - CellEditEvent.__init__(self, olv_EVT_CELL_EDIT_FINISHING) - self.SetParameters(objectListView, rowIndex, subItemIndex, rowModel, cellValue, editor) - self.userCancelled = userCancelled - - def SetCellValue(self, value): - """ - If the event handler sets the cell value here, this value will be used to update the model - object, rather than the value that was actually in the cell editor - """ - self.cellValue = value - -#---------------------------------------------------------------------------- - -class SortEvent(VetoableEvent): - """ - The user wants to sort the ObjectListView. - - When sortModelObjects is True, the event handler should sort the model objects used by - the given ObjectListView. If the "modelObjects" instance variable is not None, that - collection of objects should be sorted, otherwise the "modelObjects" collection of the - ObjectListView should be sorted. For a VirtualObjectListView, "modelObjects" will - always be None and the programmer must sort the object in whatever backing store is - being used. - - When sortModelObjects is False, the event handler must sort the actual ListItems in - the OLV. It does this by calling SortListItemsBy(), passing a callable that accepts - two model objects as parameters. sortModelObjects must be True for a - VirtualObjectListView (or a FastObjectListView) since virtual lists cannot sort items. - - If the handler calls Veto(), no further default processing will be done. - If the handler calls Handled(), default processing concerned with UI will be done. This - includes updating sort indicators. - If the handler calls neither of these, all default processing will be done. - """ - def __init__(self, objectListView, sortColumnIndex, sortAscending, sortModelObjects, modelObjects=None): - VetoableEvent.__init__(self, olv_EVT_SORT) - self.objectListView = objectListView - self.sortColumnIndex = sortColumnIndex - self.sortAscending = sortAscending - self.sortModelObjects = sortModelObjects - self.modelObjects = modelObjects - self.wasHandled = False - - def Handled(self, wasHandled=True): - """ - Indicate that the event handler has sorted the ObjectListView. - The OLV will handle other tasks like updating sort indicators - """ - self.wasHandled = wasHandled - -#---------------------------------------------------------------------------- - -class GroupCreationEvent(wx.PyCommandEvent): - """ - The user is about to create one or more groups. - - The handler can mess with the list of groups before they are created: change their - names, give them icons, remove them from the list to stop them being created - (that last behaviour could be very confusing for the users). - """ - def __init__(self, objectListView, groups): - wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_CREATING, -1) - self.objectListView = objectListView - self.groups = groups - -#---------------------------------------------------------------------------- - -class ExpandCollapseEvent(VetoableEvent): - """ - The user wants to expand or collapse one or more groups, or has just done so. - - If the handler calls Veto() for a Expanding or Collapsing event, - the expand/collapse action will be cancelled. - - Calling Veto() has no effect on a Expanded or Collapsed event - """ - def __init__(self, eventType, objectListView, groups, isExpand): - VetoableEvent.__init__(self, eventType) - self.objectListView = objectListView - self.groups = groups - self.isExpand = isExpand - -def ExpandingCollapsingEvent(objectListView, groups, isExpand): - if isExpand: - return ExpandCollapseEvent(olv_EVT_EXPANDING, objectListView, groups, True) - else: - return ExpandCollapseEvent(olv_EVT_COLLAPSING, objectListView, groups, False) - -def ExpandedCollapsedEvent(objectListView, groups, isExpand): - if isExpand: - return ExpandCollapseEvent(olv_EVT_EXPANDED, objectListView, groups, True) - else: - return ExpandCollapseEvent(olv_EVT_COLLAPSED, objectListView, groups, False) - -#---------------------------------------------------------------------------- - -class SortGroupsEvent(wx.PyCommandEvent): - """ - The given list of groups needs to be sorted. - - Both the groups themselves and the model objects within the group should be sorted. - - The handler should rearrange the list of groups in the order desired. - """ - def __init__(self, objectListView, groups, sortColumn, sortAscending): - wx.PyCommandEvent.__init__(self, olv_EVT_GROUP_SORT, -1) - self.objectListView = objectListView - self.groups = groups - self.sortColumn = sortColumn - self.sortAscending = sortAscending - self.wasHandled = False - - def Handled(self, wasHandled=True): - """ - Indicate that the event handler has sorted the groups. - """ - self.wasHandled = wasHandled diff --git a/odmtools/lib/oldOlv/ObjectListView.py b/odmtools/lib/oldOlv/ObjectListView.py deleted file mode 100755 index 6e13ea4..0000000 --- a/odmtools/lib/oldOlv/ObjectListView.py +++ /dev/null @@ -1,4141 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: ObjectListView.py -# Author: Phillip Piper -# Created: 29 February 2008 -# Copyright: (c) 2008 Phillip Piper -# SVN-ID: $Id$ -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/09/02 JPP - Added BatchedUpdate adaptor -# - Improved speed of selecting and refreshing by keeping a map -# of objects to indicies -# - Added GetIndexOf() -# - Removed flicker from FastObjectListView.AddObjects() and RefreshObjects() -# 2008/08/27 JPP - Implemented filtering -# - Added GetObjects() and GetFilteredObjects() -# - Added resortNow parameter to SetSortColumn() -# 2008/08/23 JPP - Added AddObjects()/RemoveObjects() and friends -# - Removed duplicate code when building/refreshing/adding objects -# - One step closer to secondary sort column support -# 2008/08/18 JPP - Handle model objects that cannot be hashed -# - Added CELL_EDIT_STARTED and CELL_EDIT_FINISHED events -# 2008/08/16 JPP - Added ensureVisible parameter to SelectObject() -# 2008/08/05 JPP - GroupListView is now implemented as a virtual list. Much faster! -# v1.1 -# 2008/07/19 JPP - Added GroupListView -# - Broke common virtual list behaviour into AbstractVirtualListView -# 2008/07/13 JPP - Added CopySelectionToClipboard and CopyObjectsToClipboard -# 2008/07/08 JPP - Fixed several Linux specific bugs/limits -# 2008/07/03 JPP - Allow headers to have images -# v1.0.1 -# 2008/06/22 JPP - Allowed for custom sorting, even on virtual lists -# - Fixed bug where an imageGetter that returned 0 was treated -# as if it returned -1 (i.e. no image) -# 2008/06/17 JPP - Use binary searches when searching on sorted columns -# 2008/06/16 JPP - Search by sorted column works, even on virtual lists -# 2008/06/12 JPP - Added sortable parameter -# - Renamed sortColumn to be sortColumnIndex to make it clear -# - Allow returns in multiline cell editors -# v1.0 -# 2008/05/29 JPP Used named images internally -# 2008/05/26 JPP Fixed pyLint annoyances -# 2008/05/24 JPP Images can be referenced by name -# 2008/05/17 JPP Checkboxes supported -# 2008/04/18 JPP Cell editing complete -# 2008/03/31 JPP Added space filling columns -# 2008/03/29 JPP Added minimum, maximum and fixed widths for columns -# 2008/03/22 JPP Added VirtualObjectListView and FastObjectListView -# 2008/02/29 JPP Version converted from wax -# 2006/11/03 JPP First version under wax -#---------------------------------------------------------------------------- -# To do: -# - selectable columns, triggered on right click on header -# - secondary sort column -# - optionally preserve selection on RepopulateList -# - get rid of scrollbar when editing label in icon view -# - need a ChangeView() method to help when switching between views - -""" -An `ObjectListView` provides a more convienent and powerful interface to a ListCtrl. - -The major features of an `ObjectListView` are: - - * Automatically transforms a collection of model objects into a ListCtrl. - * Automatically sorts rows by their data type. - * Easily edits the values shown in the ListCtrl. - * Supports all ListCtrl views (report, list, large and small icons). - * Columns can be fixed-width, have a minimum and/or maximum width, or be space-filling. - * Displays a "list is empty" message when the list is empty (obviously). - * Supports custom formatting of rows - * Supports alternate rows background colors. - * Supports checkbox columns - * Supports searching (by typing) on the sorted column -- even on virtual lists. - * Supports filtering of rows - * The `FastObjectListView` version can build a list of 10,000 objects in less than 0.1 seconds. - * The `VirtualObjectListView` version supports millions of rows through ListCtrl's virtual mode. - * The `GroupListView` version partitions it's rows into collapsible groups. - -An `ObjectListView` works in a declarative manner: the programmer configures how it should -work, then gives it the list of objects to display. The primary configuration is in the -definitions of the columns. Columns are configured to know which aspect of their model -they should display, how it should be formatted, and even how new values should be written -back into the model. See `ColumnDefn` for more information. - -""" - -__author__ = "Phillip Piper" -__date__ = "18 June 2008" -__version__ = "1.1" - -import wx -import datetime -import itertools -import locale -import operator -import string -import time - -import CellEditor -import OLVEvent - - -class ObjectListView(wx.ListCtrl): - """ - An object list displays various aspects of a list of objects in a multi-column list control. - - To use an ObjectListView, the programmer defines what columns are in the control and which - bits of information each column should display. The programmer then calls `SetObjects` with - the list of objects that the ObjectListView should display. The ObjectListView then builds - the control. - - Columns hold much of the intelligence of this control. Columns define both the format - (width, alignment), the aspect to be shown in the column, and the columns behaviour. - See `ColumnDefn` for full details. - - These are public instance variables. (All other variables should be considered private.) - - * cellEditMode - This control whether and how the cells of the control are editable. It can be - set to one of the following values: - - CELLEDIT_NONE - Cell editing is not allowed on the control This is the default. - - CELLEDIT_SINGLECLICK - Single clicking on any subitem cell begins an edit operation on that cell. - Single clicking on the primaru cell does *not* start an edit operation. - It simply selects the row. Pressing F2 edits the primary cell. - - CELLEDIT_DOUBLECLICK - Double clicking any cell starts an edit operation on that cell, including - the primary cell. Pressing F2 edits the primary cell. - - CELLEDIT_F2ONLY - Pressing F2 edits the primary cell. Tab/Shift-Tab can be used to edit other - cells. Clicking does not start any editing. - - * evenRowsBackColor - When `useAlternateBackColors` is true, even numbered rows will have this - background color. - - * handleStandardKeys - When this is True (the default), several standard keys will be handled as - commands by the ObjectListView. If this is False, they will be ignored. - - Ctrl-A - Select all model objects - - Ctrl-C - Put a text version of the selected rows onto the clipboard (on Windows, - this is will also put a HTML version into the clipboard) - - Left-Arrow, Right-Arrow - [GroupListView only] This will collapse/expand all selected groups. - - * oddRowsBackColor - When `useAlternateBackColors` is true, odd numbered rows will have this - background color. - - * rowFormatter - To further control the formatting of individual rows, this property - can be set to a callable that expects two parameters: the listitem whose - characteristics are to be set, and the model object being displayed on that row. - - The row formatter is called after the alternate back colours (if any) have been - set. - - Remember: the background and text colours are overridden by system defaults - while a row is selected. - - * typingSearchesSortColumn - If this boolean is True (the default), when the user types into the list, the - control will try to find a prefix match on the values in the sort column. If this - is False, or the list is unsorted or if the sorted column is marked as not - searchable (via `isSearchable` attribute), the primary column will be matched. - - * useAlternateBackColors - If this property is true, even and odd rows will be given different - background. The background colors are controlled by the properties - `evenRowsBackColor` and `oddRowsBackColor`. This is true by default. - """ - - CELLEDIT_NONE = 0 - CELLEDIT_SINGLECLICK = 1 - CELLEDIT_DOUBLECLICK = 2 - CELLEDIT_F2ONLY = 3 - - """Names of standard images used within the ObjectListView. If you want to use your - own image in place of a standard one, simple register it with AddNamedImages() using - one of the following names.""" - NAME_DOWN_IMAGE = "objectListView.downImage" - NAME_UP_IMAGE = "objectListView.upImage" - NAME_CHECKED_IMAGE = "objectListView.checkedImage" - NAME_UNCHECKED_IMAGE = "objectListView.uncheckedImage" - NAME_UNDETERMINED_IMAGE = "objectListView.undeterminedImage" - NAME_EXPANDED_IMAGE = "objectListView.expandedImage" - NAME_COLLAPSED_IMAGE = "objectListView.collapsedImage" - - """When typing into the list, a delay between keystrokes greater than this (in seconds) - will be interpretted as a new search and any previous search text will be cleared""" - SEARCH_KEYSTROKE_DELAY = 0.75 - - """When typing into a list and searching on an unsorted column, we don't even try to search - if there are more than this many rows.""" - MAX_ROWS_FOR_UNSORTED_SEARCH = 100000 - - def __init__(self, *args, **kwargs): - """ - Create an ObjectListView. - - Apart from the normal ListCtrl parameters, this constructor looks for any of the - following optional parameters: - - * `cellEditMode` - * `rowFormatter` - * `sortable` - * `useAlternateBackColors` - - The behaviour of these properties are described in the class documentation, except - for `sortable.` - - `sortable` controls whether the rows of the control will be sorted when the user - clicks on the header. This is true by default. If it is False, clicking the header - will be nothing, and no images will be registered in the image lists. This - parameter only has effect at creation time -- it has no impact after creation. - - """ - - # We have two collections of objects: our model objects and our working list - # ("innerList"). The model objects are those given to use by the user; the working - # list is what is actually used to populate the control. This separation let us - # modify what is presented to the user without losing our base data. This allows - # to (in the future) implement filtering or some other view-like capabilities. - # Currently, for ObjectListView, these collections will be identical, but for a - # GroupListView they are different. - self.modelObjects = [] - self.innerList = [] - self.columns = [] - self.sortColumnIndex = -1 - self.sortAscending = True - self.smallImageList = None - self.normalImageList = None - self.cellEditor = None - self.cellBeingEdited = None - self.selectionBeforeCellEdit = [] - self.checkStateColumn = None - self.handleStandardKeys = True - self.searchPrefix = u"" - self.whenLastTypingEvent = 0 - self.filter = None - self.objectToIndexMap = None - - self.rowFormatter = kwargs.pop("rowFormatter", None) - self.useAlternateBackColors = kwargs.pop("useAlternateBackColors", True) - self.sortable = kwargs.pop("sortable", True) - self.cellEditMode = kwargs.pop("cellEditMode", self.CELLEDIT_NONE) - self.typingSearchesSortColumn = kwargs.pop("typingSearchesSortColumn", True) - - self.evenRowsBackColor = wx.Colour(240, 248, 255) # ALICE BLUE - self.oddRowsBackColor = wx.Colour(255, 250, 205) # LEMON CHIFFON - - wx.ListCtrl.__init__(self, *args, **kwargs) - - if self.sortable: - self.EnableSorting() - - # NOTE: On Windows, ListCtrl's don't trigger EVT_LEFT_UP :( - - self.Bind(wx.EVT_CHAR, self._HandleChar) - self.Bind(wx.EVT_LEFT_DOWN, self._HandleLeftDown) - self.Bind(wx.EVT_LEFT_UP, self._HandleLeftClickOrDoubleClick) - self.Bind(wx.EVT_LEFT_DCLICK, self._HandleLeftClickOrDoubleClick) - self.Bind(wx.EVT_LIST_COL_BEGIN_DRAG, self._HandleColumnBeginDrag) - self.Bind(wx.EVT_LIST_COL_END_DRAG, self._HandleColumnEndDrag) - self.Bind(wx.EVT_MOUSEWHEEL, self._HandleMouseWheel) - self.Bind(wx.EVT_SCROLLWIN, self._HandleScroll) - self.Bind(wx.EVT_SIZE, self._HandleSize) - - # When is this event triggered? - #self.Bind(wx.EVT_LIST_COL_DRAGGING, self._HandleColumnDragging) - - # For some reason under Linux, the default wx.StaticText always appears - # behind the ListCtrl. The GenStaticText class appears as it should. - if wx.Platform == "__WXGTK__": - from wx.lib.stattext import GenStaticText as StaticText - else: - StaticText = wx.StaticText - - self.stEmptyListMsg = StaticText(self, -1, "This list is empty", - wx.Point(0, 0), wx.Size(0, 0), wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE | wx.FULL_REPAINT_ON_RESIZE) - self.stEmptyListMsg.Hide() - self.stEmptyListMsg.SetForegroundColour(wx.LIGHT_GREY) - self.stEmptyListMsg.SetBackgroundColour(self.GetBackgroundColour()) - self.stEmptyListMsg.SetFont(wx.Font(24, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) - - - #--------------------------------------------------------------#000000#FFFFFF - # Setup - - def SetColumns(self, columns, repopulate=True): - """ - Set the list of columns that will be displayed. - - The elements of the list can be either ColumnDefn objects or a tuple holding the values - to be given to the ColumnDefn constructor. - - The first column is the primary column -- this will be shown in the the non-report views. - - This clears any preexisting CheckStateColumn. The first column that is a check state - column will be installed as the CheckStateColumn for this listview. - """ - sortCol = self.GetSortColumn() - wx.ListCtrl.ClearAll(self) - self.checkStateColumn = None - self.columns = [] - for x in columns: - if isinstance(x, ColumnDefn): - self.AddColumnDefn(x) - else: - self.AddColumnDefn(ColumnDefn(*x)) - # Try to preserve the column column - self.SetSortColumn(sortCol) - if repopulate: - self.RepopulateList() - - - def AddColumnDefn(self, defn): - """ - Append the given ColumnDefn object to our list of active columns. - - If this method is called directly, you must also call RepopulateList() - to populate the new column with data. - """ - self.columns.append(defn) - - info = wx.ListItem() - info.m_mask = wx.LIST_MASK_TEXT | wx.LIST_MASK_FORMAT - if isinstance(defn.headerImage, basestring) and self.smallImageList is not None: - info.m_image = self.smallImageList.GetImageIndex(defn.headerImage) - else: - info.m_image = defn.headerImage - if info.m_image != -1: - info.m_mask = info.m_mask | wx.LIST_MASK_IMAGE - info.m_format = defn.GetAlignment() - info.m_text = defn.title - info.m_width = defn.width - self.InsertColumnInfo(len(self.columns)-1, info) - - # Under Linux, the width doesn't take effect without this call - self.SetColumnWidth(len(self.columns)-1, defn.width) - - # The first checkbox column becomes the check state column for the control - if defn.HasCheckState() and self.checkStateColumn is None: - self.InstallCheckStateColumn(defn) - - - def _InitializeCheckBoxImages(self): - """ - Initialize some checkbox images for use by this control. - """ - def _makeBitmap(state, size): - bitmap = wx.EmptyBitmap(size, size) - dc = wx.MemoryDC(bitmap) - dc.Clear() - - # On Linux, the Renderer draws the checkbox too low - if wx.Platform == "__WXGTK__": - yOrigin = -1 - else: - yOrigin = 0 - wx.RendererNative.Get().DrawCheckBox(self, dc, (0, yOrigin, size, size), state) - dc.SelectObject(wx.NullBitmap) - return bitmap - - def _makeBitmaps(name, state): - self.AddNamedImages(name, _makeBitmap(state, 16), _makeBitmap(state, 32)) - - # If there isn't a small image list, make one - if self.smallImageList is None: - self.SetImageLists() - - _makeBitmaps(ObjectListView.NAME_CHECKED_IMAGE, wx.CONTROL_CHECKED) - _makeBitmaps(ObjectListView.NAME_UNCHECKED_IMAGE, wx.CONTROL_CURRENT) - _makeBitmaps(ObjectListView.NAME_UNDETERMINED_IMAGE, wx.CONTROL_UNDETERMINED) - - - def CreateCheckStateColumn(self, columnIndex=0): - """ - Create a fixed width column at the given index to show the checkedness - of objects in this list. - - If this is installed at column 0 (which is the default), the listview - should only be used in Report view. - - This should be called after SetColumns() has been called, since - SetColumns() removed any previous check state column. - - RepopulateList() or SetObjects() must be called after this. - """ - col = ColumnDefn("", fixedWidth=24, isEditable=False) - col.valueGetter = col.GetCheckState # Install a value getter so sorting works - col.stringConverter = lambda x: "" # We don't want any string for the value - col.isInternal = True # This is an autocreated column - self.columns.insert(columnIndex, col) - self.SetColumns(self.columns, False) - self.InstallCheckStateColumn(col) - - - def InstallCheckStateColumn(self, column): - """ - Configure the given column so that it shows the check state of each row in this - control. - - This column's checkbox will be toggled when the user pressed space when a row is - selected. - - `RepopulateList()` or `SetObjects()` must be called after a new check state column is - installed for the check state column to be visible. - - Set to None to remove the check state column. - """ - self.checkStateColumn = column - if column is None: - return - - if self.smallImageList == None or \ - not self.smallImageList.HasName(ObjectListView.NAME_CHECKED_IMAGE): - self._InitializeCheckBoxImages() - - # Is the column already configured to handle check state? - if column.HasCheckState(): - return - - # The column isn't managing it's own check state, so install handlers - # that will manage the state. This is useful when the checkedness is - # related to the view and is not an attribute of the model. - checkState = dict() - def _handleGetCheckState(modelObject): - return checkState.get(modelObject, False) # objects are not checked by default - - def _handleSetCheckState(modelObject, newValue): - checkState[modelObject] = newValue - return newValue - - column.checkStateGetter = _handleGetCheckState - column.checkStateSetter = _handleSetCheckState - - - def RegisterSortIndicators(self, sortUp=None, sortDown=None): - """ - Register the bitmaps that should be used to indicated which column is being sorted - These bitmaps must be the same dimensions as the small image list (not sure - why that should be so, but it is) - - If no parameters are given, 16x16 default images will be registered - """ - self.AddNamedImages(ObjectListView.NAME_DOWN_IMAGE, sortDown or _getSmallDownArrowBitmap()) - self.AddNamedImages(ObjectListView.NAME_UP_IMAGE, sortUp or _getSmallUpArrowBitmap()) - - - def SetImageLists(self, smallImageList=None, normalImageList=None): - """ - Remember the image lists to be used for this control. - - Call this without parameters to create reasonable default image lists. - - Use this to change the size of images shown by the list control. - """ - if isinstance(smallImageList, NamedImageList): - self.smallImageList = smallImageList - else: - self.smallImageList = NamedImageList(smallImageList, 16) - self.SetImageList(self.smallImageList.imageList, wx.IMAGE_LIST_SMALL) - - if isinstance(normalImageList, NamedImageList): - self.normalImageList = normalImageList - else: - self.normalImageList = NamedImageList(normalImageList, 32) - self.SetImageList(self.normalImageList.imageList, wx.IMAGE_LIST_NORMAL) - - - #--------------------------------------------------------------#000000#FFFFFF - # Commands - - def AddImages(self, smallImage=None, normalImage=None): - """ - Add the given images to the list of available images. Return the index of the image. - """ - return self.AddNamedImages(None, smallImage, normalImage) - - - def AddObject(self, modelObject): - """ - Add the given object to our collection of objects. - - The object will appear at its sorted location, or at the end of the list if - the list is unsorted - """ - self.AddObjects([modelObject]) - - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - - The objects will appear at their sorted locations, or at the end of the list if - the list is unsorted - """ - if len(self.innerList) == 0: - return self.SetObjects(modelObjects) - - try: - self.Freeze() - originalSize = len(self.innerList) - self.modelObjects.extend(modelObjects) - self._BuildInnerList() - item = wx.ListItem() - item.SetColumn(0) - for (i, x) in enumerate(self.innerList[originalSize:]): - item.Clear() - self._InsertUpdateItem(item, originalSize+i, x, True) - self._SortItemsNow() - finally: - self.Thaw() - - - def AddNamedImages(self, name, smallImage=None, normalImage=None): - """ - Add the given images to the list of available images. Return the index of the image. - - If a name is given, that name can later be used to refer to the images rather - than having to use the returned index. - """ - if isinstance(smallImage, basestring): - smallImage = wx.Bitmap(smallImage) - if isinstance(normalImage, basestring): - normalImage = wx.Bitmap(normalImage) - - # We must have image lists for images to be added to them - if self.smallImageList is None or self.normalImageList is None: - self.SetImageLists() - - # There must always be the same number of small and normal bitmaps, - # so if we aren't given one, we have to make an empty one of the right size - smallImage = smallImage or wx.EmptyBitmap(*self.smallImageList.GetSize(0)) - normalImage = normalImage or wx.EmptyBitmap(*self.normalImageList.GetSize(0)) - - self.smallImageList.AddNamedImage(name, smallImage) - return self.normalImageList.AddNamedImage(name, normalImage) - - - def AutoSizeColumns(self): - """ - Resize our auto sizing columns to match the data - """ - for (iCol, col) in enumerate(self.columns): - if col.width == wx.LIST_AUTOSIZE: - self.SetColumnWidth(iCol, wx.LIST_AUTOSIZE) - - # The new width must be within our minimum and maximum - colWidth = self.GetColumnWidth(iCol) - boundedWidth = col.CalcBoundedWidth(colWidth) - if colWidth != boundedWidth: - self.SetColumnWidth(iCol, boundedWidth) - - - def Check(self, modelObject): - """ - Mark the given model object as checked. - """ - self.SetCheckState(modelObject, True) - - - def ClearAll(self): - """ - Remove all items and columns - """ - wx.ListCtrl.ClearAll(self) - self.SetObjects(list()) - - - def CopyObjectsToClipboard(self, objects): - """ - Put a textual representation of the given objects onto the clipboard. - - This will be one line per object and tab-separated values per line. - Under windows there will be a HTML table version put on the clipboard as well. - """ - if objects is None or len(objects) == 0: - return - - # Get all the values of the given rows into multi-list - rows = self._GetValuesAsMultiList(objects) - - # Make a text version of the values - lines = [ "\t".join(x) for x in rows ] - txt = "\n".join(lines) + "\n" - - # Make a html version on Windows - try: - lines = [ "" + "".join(x) + "" for x in rows ] - html = "" + "".join(lines) + "
" - self._PutTextAndHtmlToClipboard(txt, html) - except ImportError: - cb = wx.Clipboard() - if cb.Open(): - cb.SetData(wx.TextDataObject(txt)) - cb.Flush() - cb.Close() - - def _GetValuesAsMultiList(self, objects): - """ - Return a list of lists of the string of the aspects of the given objects - """ - cols = self.columns[:] - if self.checkStateColumn is not None: - cols.remove(self.checkStateColumn) - return [[column.GetStringValue(x) for column in cols] for x in objects] - - - def _PutTextAndHtmlToClipboard(self, txt, fragment): - """ - Put the given text and html into the windows clipboard. - - The html will be written in accordance with strange "HTML Format" as specified - in http://msdn.microsoft.com/library/en-us/winui/winui/windowsuserinterface/dataexchange/clipboard/htmlclipboardformat.asp - """ - import win32clipboard - - MARKER_BLOCK_OUTPUT = \ - "Version:1.0\r\n" \ - "StartHTML:%09d\r\n" \ - "EndHTML:%09d\r\n" \ - "StartFragment:%09d\r\n" \ - "EndFragment:%09d\r\n" \ - "StartSelection:%09d\r\n" \ - "EndSelection:%09d\r\n" \ - "SourceURL:%s\r\n" - - DEFAULT_HTML_BODY = \ - "" \ - "%s" - - html = DEFAULT_HTML_BODY % fragment - source = "http://objectlistview.sourceforge.net/python" - - fragmentStart = selectionStart = html.index(fragment) - fragmentEnd = selectionEnd = fragmentStart + len(fragment) - - # How long is the prefix going to be? - dummyPrefix = MARKER_BLOCK_OUTPUT % (0, 0, 0, 0, 0, 0, source) - lenPrefix = len(dummyPrefix) - - prefix = MARKER_BLOCK_OUTPUT % (lenPrefix, len(html)+lenPrefix, - fragmentStart+lenPrefix, fragmentEnd+lenPrefix, - selectionStart+lenPrefix, selectionEnd+lenPrefix, - source) - htmlForClipboard = (prefix + html) - - try: - win32clipboard.OpenClipboard(0) - win32clipboard.EmptyClipboard() - cfText = 1 - win32clipboard.SetClipboardData(cfText, txt) - cfHtml = win32clipboard.RegisterClipboardFormat("HTML Format") - win32clipboard.SetClipboardData(cfHtml, htmlForClipboard) - finally: - win32clipboard.CloseClipboard() - - - def CopySelectionToClipboard(self): - """ - Copy the selected objects to the clipboard - """ - self.CopyObjectsToClipboard(self.GetSelectedObjects()) - - - def DeleteAllItems(self): - """ - Remove all items - """ - wx.ListCtrl.DeleteAllItems(self) - self.SetObjects(list()) - - - def EnsureCellVisible(self, rowIndex, subItemIndex): - """ - Make sure the user can see all of the given cell, scrolling if necessary. - Return the bounds to the cell calculated after the cell has been made visible. - Return None if the cell cannot be made visible (non-Windows platforms can't scroll - the listview horizontally) - - If the cell is bigger than the ListView, the top left of the cell will be visible. - """ - self.EnsureVisible(rowIndex) - bounds = self.GetSubItemRect(rowIndex, subItemIndex, wx.LIST_RECT_BOUNDS) - boundsRight = bounds[0]+bounds[2] - if bounds[0] < 0 or boundsRight > self.GetSize()[0]: - if bounds[0] < 0: - horizDelta = bounds[0] - (self.GetSize()[0] / 4) - else: - horizDelta = boundsRight - self.GetSize()[0] + (self.GetSize()[0] / 4) - if wx.Platform == "__WXMSW__": - self.ScrollList(horizDelta, 0) - else: - return None - - return self.GetSubItemRect(rowIndex, subItemIndex, wx.LIST_RECT_LABEL) - - - def _FormatAllRows(self): - """ - Set up the required formatting on all rows - """ - for i in range(self.GetItemCount()): - item = self.GetItem(i) - self._FormatOneItem(item, i, self.GetObjectAt(i)) - self.SetItem(item) - - - def _FormatOneItem(self, item, index, model): - """ - Give the given row it's correct background color - """ - if self.useAlternateBackColors and self.InReportView(): - if index & 1: - item.SetBackgroundColour(self.oddRowsBackColor) - else: - item.SetBackgroundColour(self.evenRowsBackColor) - - if self.rowFormatter is not None: - self.rowFormatter(item, model) - - - def RepopulateList(self): - """ - Completely rebuild the contents of the list control - """ - self._SortObjects() - self._BuildInnerList() - self.Freeze() - try: - wx.ListCtrl.DeleteAllItems(self) - if len(self.innerList) == 0 or len(self.columns) == 0: - self.Refresh() - self.stEmptyListMsg.Show() - return - - self.stEmptyListMsg.Hide() - - # Insert all the rows - item = wx.ListItem() - item.SetColumn(0) - for (i, x) in enumerate(self.innerList): - item.Clear() - self._InsertUpdateItem(item, i, x, True) - - # Auto-resize once all the data has been added - self.AutoSizeColumns() - finally: - self.Thaw() - - - def RefreshIndex(self, index, modelObject): - """ - Refresh the item at the given index with data associated with the given object - """ - self._InsertUpdateItem(self.GetItem(index), index, modelObject, False) - - - def _InsertUpdateItem(self, listItem, index, modelObject, isInsert): - if isInsert: - listItem.SetId(index) - listItem.SetData(index) - - listItem.SetText(self.GetStringValueAt(modelObject, 0)) - listItem.SetImage(self.GetImageAt(modelObject, 0)) - self._FormatOneItem(listItem, index, modelObject) - - if isInsert: - self.InsertItem(listItem) - else: - self.SetItem(listItem) - - for iCol in range(1, len(self.columns)): - self.SetStringItem(index, iCol, self.GetStringValueAt(modelObject, iCol), - self.GetImageAt(modelObject, iCol)) - - - def RefreshObject(self, modelObject): - """ - Refresh the display of the given model - """ - idx = self.GetIndexOf(modelObject) - if idx != -1: - self.RefreshIndex(self._MapModelIndexToListIndex(idx), modelObject) - - - def RefreshObjects(self, aList): - """ - Refresh all the objects in the given list - """ - try: - self.Freeze() - for x in aList: - self.RefreshObject(x) - finally: - self.Thaw() - - - def RemoveObject(self, modelObject): - """ - Remove the given object from our collection of objects. - """ - self.RemoveObjects([modelObject]) - - - def RemoveObjects(self, modelObjects): - """ - Remove the given collections of objects from our collection of objects. - """ - # Unlike AddObjects(), there is no clever way to do this -- we have to simply - # remove the objects and rebuild the whole list. We can't just remove the rows - # because every wxListItem holds the index of its matching model object. If we - # remove the first model object, the index of every object will change. - selection = self.GetSelectedObjects() - - # Use sets to quickly remove objects from self.modelObjects - # For large collections, this is MUCH faster. - try: - s1 = set(self.modelObjects) - s2 = set(modelObjects) - self.modelObjects = list(s1 - s2) - except TypeError: - # Not every object can be hashed, so some model objects cannot be placed in sets. - # For such objects, we have to resort to the slow method. - for x in modelObjects: - self.modelObjects.remove(x) - - self.RepopulateList() - self.SelectObjects(selection) - - - def _ResizeSpaceFillingColumns(self): - """ - Change the width of space filling columns so that they fill the - unoccupied width of the listview - """ - # If the list isn't in report view or there are no space filling columns, just return - if not self.InReportView(): - return - - # Don't do anything if there are no space filling columns - if True not in set(x.isSpaceFilling for x in self.columns): - return - - # Calculate how much free space is available in the control - totalFixedWidth = sum(self.GetColumnWidth(i) for (i, x) in enumerate(self.columns) - if not x.isSpaceFilling) - freeSpace = max(0, self.GetClientSizeTuple()[0] - totalFixedWidth) - - # Calculate the total number of slices the free space will be divided into - totalProportion = sum(x.freeSpaceProportion for x in self.columns if x.isSpaceFilling) - - # Space filling columns that would escape their boundary conditions - # are treated as fixed size columns - columnsToResize = [] - for (i, col) in enumerate(self.columns): - if col.isSpaceFilling: - newWidth = freeSpace * col.freeSpaceProportion / totalProportion - boundedWidth = col.CalcBoundedWidth(newWidth) - if newWidth == boundedWidth: - columnsToResize.append((i, col)) - else: - freeSpace -= boundedWidth - totalProportion -= col.freeSpaceProportion - if self.GetColumnWidth(i) != boundedWidth: - self.SetColumnWidth(i, boundedWidth) - - # Finally, give each remaining space filling column a proportion of the free space - for (i, col) in columnsToResize: - newWidth = freeSpace * col.freeSpaceProportion / totalProportion - boundedWidth = col.CalcBoundedWidth(newWidth) - if self.GetColumnWidth(i) != boundedWidth: - self.SetColumnWidth(i, boundedWidth) - - - def SetCheckState(self, modelObject, state): - """ - Set the check state of the given model object. - - 'state' can be True, False or None (which means undetermined) - """ - if self.checkStateColumn is None: - return None - else: - return self.checkStateColumn.SetCheckState(modelObject, state) - - - def SetColumnFixedWidth(self, colIndex, width): - """ - Make the given column to be fixed width - """ - if 0 <= colIndex < self.GetColumnCount(): - self.SetColumnWidth(colIndex, width) - self.columns[colIndex].SetFixedWidth(width) - - - def SetEmptyListMsg(self, msg): - """ - When there are no objects in the list, show this message in the control - """ - self.stEmptyListMsg.SetLabel(msg) - - - def SetEmptyListMsgFont(self, font): - """ - In what font should the empty list msg be rendered? - """ - self.stEmptyListMsg.SetFont(font) - - - def SetObjects(self, modelObjects, preserveSelection=False): - """ - Set the list of modelObjects to be displayed by the control. - """ - if preserveSelection: - selection = self.GetSelectedObjects() - - if modelObjects is None: - self.modelObjects = list() - else: - self.modelObjects = modelObjects[:] - - self.RepopulateList() - - if preserveSelection: - self.SelectObjects(selection) - - - # Synonym as per many wxWindows widgets - SetValue = SetObjects - - - def _BuildInnerList(self): - """ - Build the list that will actually populate the control - """ - # This is normally just the list of model objects - if self.filter: - self.innerList = self.filter(self.modelObjects) - else: - self.innerList = self.modelObjects - - # Our map isn't valid after doing this - self.objectToIndexMap = None - - - def ToggleCheck(self, modelObject): - """ - Toggle the "checkedness" of the given model. - - Checked becomes unchecked; unchecked or undetermined becomes checked. - """ - self.SetCheckState(modelObject, not self.IsChecked(modelObject)) - - - def Uncheck(self, modelObject): - """ - Mark the given model object as unchecked. - """ - self.SetCheckState(modelObject, False) - - #--------------------------------------------------------------#000000#FFFFFF - # Accessing - - def GetCheckedObjects(self): - """ - Return a collection of the modelObjects that are checked in this control. - """ - if self.checkStateColumn is None: - return list() - else: - return [x for x in self.innerList if self.IsChecked(x)] - - - def GetCheckState(self, modelObject): - """ - Return the check state of the given model object. - - Returns a boolean or None (which means undetermined) - """ - if self.checkStateColumn is None: - return None - else: - return self.checkStateColumn.GetCheckState(modelObject) - - - def GetFilter(self): - """ - Return the filter that is currently operating on this control. - """ - return self.filter - - - def GetFilteredObjects(self): - """ - Return the model objects that are actually displayed in the control. - - If no filter is in effect, this is the same as GetObjects(). - """ - return self.innerList - - - def GetFocusedRow(self): - """ - Return the index of the row that has the focus. -1 means no focus - """ - return self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_FOCUSED) - - - def GetIndexOf(self, modelObject): - """ - Return the index of the given modelObject in the list. - - This method works on the visible item in the control. If a filter - is in place, not all model object given to SetObjects() are visible. - """ - # Rebuild our index map if it has been invalidated. The TypeError - # exceptions are for objects that cannot be hashed (like lists) - if self.objectToIndexMap is None: - self.objectToIndexMap = dict() - for (i, x) in enumerate(self.innerList): - try: - self.objectToIndexMap[x] = i - except TypeError: - pass - - # Use our map to find the object (but fall back to simple search - # for non-hashable objects) - try: - return self.objectToIndexMap.get(modelObject, -1) - except TypeError: - try: - return self.innerList.index(modelObject) - except ValueError: - return -1 - - - def GetImageAt(self, modelObject, columnIndex): - """ - Return the index of the image that should be display at the given column of the given modelObject - """ - column = self.columns[columnIndex] - - # If the column is a checkbox column, return the image appropriate to the check - # state - if column.HasCheckState(): - name = { - True: ObjectListView.NAME_CHECKED_IMAGE, - False: ObjectListView.NAME_UNCHECKED_IMAGE, - None: ObjectListView.NAME_UNDETERMINED_IMAGE - }.get(column.GetCheckState(modelObject)) - return self.smallImageList.GetImageIndex(name) - - # Not a checkbox column, so just return the image - imageIndex = column.GetImage(modelObject) - if isinstance(imageIndex, basestring): - return self.smallImageList.GetImageIndex(imageIndex) - else: - return imageIndex - - - def GetObjectAt(self, index): - """ - Return the model object at the given row of the list. - """ - # Because of sorting, index can't be used directly, which is - # why we set the item data to be the real index - return self.innerList[self.GetItemData(index)] - - - def __getitem__(self, index): - """ - Synactic sugar over GetObjectAt() - """ - return self.GetObjectAt(index) - - - def GetObjects(self): - """ - Return the model objects that are available to the control. - - If no filter is in effect, this is the same as GetFilteredObjects(). - """ - return self.modelObjects - - - def GetPrimaryColumn(self): - """ - Return the primary column or None there is no primary column. - - The primary column is the first column given by the user. - This column is edited when F2 is pressed. - """ - i = self.GetPrimaryColumnIndex() - if i == -1: - return None - else: - return self.columns[i] - - - def GetPrimaryColumnIndex(self): - """ - Return the index of the primary column. Returns -1 when there is no primary column. - - The primary column is the first column given by the user. - This column is edited when F2 is pressed. - """ - for (i, x) in enumerate(self.columns): - if not x.isInternal: - return i - - return -1 - - - def GetSelectedObject(self): - """ - Return the selected modelObject or None if nothing is selected or if more than one is selected. - """ - model = None - for (i, x) in enumerate(self.YieldSelectedObjects()): - if i == 0: - model = x - else: - model = None - break - return model - - - def GetSelectedObjects(self): - """ - Return a list of the selected modelObjects - """ - return list(self.YieldSelectedObjects()) - - - def GetSortColumn(self): - """ - Return the column by which the rows of this control should be sorted - """ - if self.sortColumnIndex < 0 or self.sortColumnIndex >= len(self.columns): - return None - else: - return self.columns[self.sortColumnIndex] - - - def GetStringValueAt(self, modelObject, columnIndex): - """ - Return a string representation of the value that should be display at the given column of the given modelObject - """ - column = self.columns[columnIndex] - return column.GetStringValue(modelObject) - - - def GetValueAt(self, modelObject, columnIndex): - """ - Return the value that should be display at the given column of the given modelObject - """ - column = self.columns[columnIndex] - return column.GetValue(modelObject) - - - def IsCellEditing(self): - """ - Is some cell currently being edited? - """ - return self.cellEditor and self.cellEditor.IsShown() - - - def IsChecked(self, modelObject): - """ - Return a boolean indicating if the given modelObject is checked. - """ - return self.GetCheckState(modelObject) == True - - - def IsObjectSelected(self, modelObject): - """ - Is the given modelObject selected? - """ - return modelObject in self.GetSelectedObjects() - - - def SetFilter(self, filter): - """ - Remember the filter that is currently operating on this control. - Set this to None to clear the current filter. - - A filter is a callable that accepts one parameter: the original list - of model objects. The filter chooses which of these model objects should - be visible to the user, and returns a collection of only those objects. - - The Filter module has some useful standard filters. - - You must call RepopulateList() for changes to the filter to be visible. - """ - self.filter = filter - - - def SetSortColumn(self, column, resortNow=False): - """ - Set the column by which the rows should be sorted. - - 'column' can be None (which makes the list be unsorted), a ColumnDefn, - or the index of the column desired - """ - if column is None: - self.sortColumnIndex = -1 - elif isinstance(column, ColumnDefn): - try: - self.sortColumnIndex = self.columns.index(column) - except ValueError: - self.sortColumnIndex = -1 - else: - self.sortColumnIndex = column - if resortNow: - self.SortBy(self.sortColumnIndex) - else: - self._UpdateColumnSortIndicators() - - - def YieldSelectedObjects(self): - """ - Progressively yield the selected modelObjects - """ - i = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - while i != -1: - yield self.GetObjectAt(i) - i = self.GetNextItem(i, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - - - #---------------------------------------------------------------------------- - # Calculating - - def GetSubItemRect(self, rowIndex, subItemIndex, flag): - """ - Poor mans replacement for missing wxWindows method. - - The rect returned takes scroll position into account, so negative x and y are - possible. - """ - #print "GetSubItemRect(self, %d, %d, %d):" % (rowIndex, subItemIndex, flag) - - # Linux doesn't handle wx.LIST_RECT_LABEL flag. So we always get - # the whole bounds then par it down to the cell we want - rect = self.GetItemRect(rowIndex, wx.LIST_RECT_BOUNDS) - - if self.InReportView(): - rect = [0-self.GetScrollPos(wx.HORIZONTAL), rect.Y, 0, rect.Height] - for i in range(subItemIndex+1): - rect[0] += rect[2] - rect[2] = self.GetColumnWidth(i) - - # If we want only the label rect for sub items, we have to manually - # adjust for any image the subitem might have - if flag == wx.LIST_RECT_LABEL: - lvi = self.GetItem(rowIndex, subItemIndex) - if lvi.GetImage() != -1: - if self.HasFlag(wx.LC_ICON): - imageWidth = self.normalImageList.GetSize(0)[0] - rect[1] += imageWidth - rect[3] -= imageWidth - else: - imageWidth = self.smallImageList.GetSize(0)[0] + 1 - rect[0] += imageWidth - rect[2] -= imageWidth - - #print "rect=%s" % rect - return rect - - - def HitTestSubItem(self, pt): - """ - Return a tuple indicating which (item, subItem) the given pt (client coordinates) is over. - - This uses the buildin version on Windows, and poor mans replacement on other platforms. - """ - # The buildin version works on Windows - if wx.Platform == "__WXMSW__": - return wx.ListCtrl.HitTestSubItem(self, pt) - - (rowIndex, flags) = self.HitTest(pt) - - # Did the point hit any item? - if (flags & wx.LIST_HITTEST_ONITEM) == 0: - return (-1, 0, -1) - - # If it did hit an item and we are not in report mode, it must be the primary cell - if not self.InReportView(): - return (rowIndex, wx.LIST_HITTEST_ONITEM, 0) - - # Find which subitem is hit - right = 0 - scrolledX = self.GetScrollPos(wx.HORIZONTAL) + pt.x - for i in range(self.GetColumnCount()): - left = right - right += self.GetColumnWidth(i) - if scrolledX < right: - if (scrolledX - left) < self.smallImageList.GetSize(0)[0]: - flag = wx.LIST_HITTEST_ONITEMICON - else: - flag = wx.LIST_HITTEST_ONITEMLABEL - return (rowIndex, flag, i) - - return (rowIndex, 0, -1) - - - #---------------------------------------------------------------------------- - # Event handling - - def _HandleChar(self, evt): - if evt.GetKeyCode() == wx.WXK_F2 and not self.IsCellEditing(): - return self._PossibleStartCellEdit(self.GetFocusedRow(), self.GetPrimaryColumnIndex()) - - # We have to catch Return/Enter/Escape here since some types of controls - # (e.g. ComboBox, UserControl) don't trigger key events that we can listen for. - # Treat Return or Enter as committing the current edit operation unless the control - # is a multiline text control, in which case we treat it as data - if evt.GetKeyCode() in (wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER) and self.IsCellEditing(): - if self.cellEditor and self.cellEditor.HasFlag(wx.TE_MULTILINE): - return evt.Skip() - else: - return self.FinishCellEdit() - - # Treat Escape as cancel the current edit operation - if evt.GetKeyCode() in (wx.WXK_ESCAPE, wx.WXK_CANCEL) and self.IsCellEditing(): - return self.CancelCellEdit() - - # Tab to the next editable column - if evt.GetKeyCode() == wx.WXK_TAB and self.IsCellEditing(): - return self._HandleTabKey(evt.ShiftDown()) - - # Space bar with a selection on a listview with checkboxes toggles the checkboxes - if (evt.GetKeyCode() == wx.WXK_SPACE and - not self.IsCellEditing() and - self.checkStateColumn is not None and - self.GetSelectedItemCount() > 0): - return self._ToggleCheckBoxForSelection() - - if not self.IsCellEditing(): - if self._HandleTypingEvent(evt): - return - - if not self.IsCellEditing() and self.handleStandardKeys: - # Copy selection on Ctrl-C - # Why is Ctrl-C represented by 3?! Is this Windows only? - if (evt.GetKeyCode() == 3): - self.CopySelectionToClipboard() - return - # Select All on Ctrl-A - if (evt.GetKeyCode() == 1): - self.SelectAll() - return - - evt.Skip() - - - def _HandleTypingEvent(self, evt): - """ - """ - if self.GetItemCount() == 0 or self.GetColumnCount() == 0: - return False - - if evt.GetModifiers() != 0 and evt.GetModifiers() != wx.MOD_SHIFT: - return False - - if evt.GetKeyCode() > wx.WXK_START: - return False - - if evt.GetKeyCode() in (wx.WXK_BACK, wx.WXK_DELETE): - self.searchPrefix = u"" - return True - - # On which column are we going to compare values? If we should search on the - # sorted column, and there is a sorted column and it is searchable, we use that - # one, otherwise we fallback to the primary column - if self.typingSearchesSortColumn and self.GetSortColumn() and self.GetSortColumn().isSearchable: - searchColumn = self.GetSortColumn() - else: - searchColumn = self.GetPrimaryColumn() - - # On Linux, GetUnicodeKey() always returns 0 -- on my 2.8.7.1 (gtk2-unicode) - if evt.GetUnicodeKey() == 0: - uniChar = chr(evt.GetKeyCode()) - else: - uniChar = unichr(evt.GetUnicodeKey()) - if uniChar not in string.printable: - return False - - # On Linux, evt.GetTimestamp() isn't reliable so use time.time() instead - timeNow = time.time() - if (timeNow - self.whenLastTypingEvent) > self.SEARCH_KEYSTROKE_DELAY: - self.searchPrefix = uniChar - else: - self.searchPrefix += uniChar - self.whenLastTypingEvent = timeNow - - #self.__rows = 0 - self._FindByTyping(searchColumn, self.searchPrefix) - #print "Considered %d rows in %2f secs" % (self.__rows, time.time() - timeNow) - - return True - - def _FindByTyping(self, searchColumn, prefix): - """ - Select the first row passed the currently focused row that has a string representation - that begins with 'prefix' in the given column - """ - start = max(self.GetFocusedRow(), 0) - - # If the user is starting a new search, we don't want to consider the current row - if len(prefix) == 1: - start = (start + 1) % self.GetItemCount() - - # If we are searching on a sorted column, use a binary search - if self._CanUseBisect(searchColumn): - if self._FindByBisect(searchColumn, prefix, start, self.GetItemCount()): - return - if self._FindByBisect(searchColumn, prefix, 0, start): - return - else: - # A binary search on a sorted column can handle any number of rows. A linear - # search cannot. So we impose an arbitrary limit on the number of rows to - # consider. Above that, we don't even try - if self.GetItemCount() > self.MAX_ROWS_FOR_UNSORTED_SEARCH: - self._SelectAndFocus(0) - return - - # Do a linear, wrapping search to find the next match. To wrap, we consider - # the rows in two partitions: start to the end of the collection, and then - # from the beginning to the start position. Expressing this in other languages - # is a pain, but it's elegant in Python. I just love Python :) - for i in itertools.chain(range(start, self.GetItemCount()), range(0, start)): - #self.__rows += 1 - model = self.GetObjectAt(i) - if model is not None: - strValue = searchColumn.GetStringValue(model) - if strValue.lower().startswith(prefix): - self._SelectAndFocus(i) - return - wx.Bell() - - def _CanUseBisect(self, searchColumn): - """ - Return True if we can use binary search on the given column - """ - # If the list isn't sorted or if it's sorted by some other column, we can't - if self.GetSortColumn() != searchColumn: - return False - - # If the column doesn't knows whether it should or not, make a guess based on the - # type of data in the column (strings and booleans are probably safe). We already - # know that the list isn't empty. - if searchColumn.useBinarySearch is None: - aspect = searchColumn.GetValue(self.GetObjectAt(0)) - searchColumn.useBinarySearch = isinstance(aspect, (basestring, bool)) - - return searchColumn.useBinarySearch - - def _FindByBisect(self, searchColumn, prefix, start, end): - """ - Use a binary search to look for rows that match the given prefix between the rows given. - - If a match was found, select/focus/reveal that row and return True. - """ - - # If the sorting is ascending, we use less than to find the first match - # If the sort is descending, we have to use greater-equal, and suffix the - # search string to make sure we find the first match (without the suffix - # we always find the last match) - if self.sortAscending: - cmpFunc = operator.lt - searchFor = prefix - else: - cmpFunc = operator.ge - searchFor = prefix + "z" - - # Adapted from bisect std module - lo = start - hi = end - while lo < hi: - mid = (lo + hi) // 2 - strValue = searchColumn.GetStringValue(self.GetObjectAt(mid)) - if cmpFunc(searchFor, strValue.lower()): - hi = mid - else: - lo = mid+1 - - if lo < start or lo >= end: - return False - - strValue = searchColumn.GetStringValue(self.GetObjectAt(lo)) - if strValue.lower().startswith(prefix): - self._SelectAndFocus(lo) - return True - - return False - - def _SelectAndFocus(self, rowIndex): - """ - Select and focus on the given row. - """ - self.DeselectAll() - self.Select(rowIndex) - self.Focus(rowIndex) - - def _ToggleCheckBoxForSelection(self): - """ - Toggles the checkedness of the selected modelObjects. - """ - selection = self.GetSelectedObjects() - newValue = not self.IsChecked(selection[0]) - for x in selection: - self.SetCheckState(x, newValue) - self.RefreshObjects(selection) - - def _HandleColumnBeginDrag(self, evt): - """ - Handle when the user begins to resize a column - """ - self._PossibleFinishCellEdit() - colIndex = evt.GetColumn() - if 0 > colIndex >= len(self.columns): - evt.Skip() - else: - col = self.columns[colIndex] - if col.IsFixedWidth() or col.isSpaceFilling: - evt.Veto() - else: - evt.Skip() - - - def _HandleColumnClick(self, evt): - """ - The user has clicked on a column title - """ - evt.Skip() - self._PossibleFinishCellEdit() - - # Toggle the sort column on the second click - if evt.GetColumn() == self.sortColumnIndex: - self.sortAscending = not self.sortAscending - else: - self.sortAscending = True - - self.SortBy(evt.GetColumn(), self.sortAscending) - self._FormatAllRows() - - - def _HandleColumnDragging(self, evt): - """ - A column is being dragged - """ - # When is this triggered? - - # The processing should be the same processing as Dragged - evt.Skip() - - - def _HandleColumnEndDrag(self, evt): - """ - The user has finished resizing a column. Make sure that it is not - bigger than it should be, then resize any space filling columns. - """ - colIndex = evt.GetColumn() - if 0 > colIndex >= len(self.columns): - evt.Skip() - else: - currentWidth = self.GetColumnWidth(colIndex) - col = self.columns[colIndex] - newWidth = col.CalcBoundedWidth(currentWidth) - if currentWidth != newWidth: - wx.CallAfter(self._SetColumnWidthAndResize, colIndex, newWidth) - else: - evt.Skip() - wx.CallAfter(self._ResizeSpaceFillingColumns) - - def _SetColumnWidthAndResize(self, colIndex, newWidth): - self.SetColumnWidth(colIndex, newWidth) - self._ResizeSpaceFillingColumns() - - - def _HandleLeftDown(self, evt): - """ - Handle a left down on the ListView - """ - evt.Skip() - - # Test for a mouse down on the image of the check box column - if self.InReportView(): - (row, flags, subitem) = self.HitTestSubItem(evt.GetPosition()) - else: - (row, flags) = self.HitTest(evt.GetPosition()) - subitem = 0 - - if flags == wx.LIST_HITTEST_ONITEMICON: - self._HandleLeftDownOnImage(row, subitem) - - - def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): - """ - Handle a left click on the image at the given cell - """ - column = self.columns[subItemIndex] - if not column.HasCheckState(): - return - - self._PossibleFinishCellEdit() - modelObject = self.GetObjectAt(rowIndex) - if modelObject is not None: - column.SetCheckState(modelObject, not column.GetCheckState(modelObject)) - self.RefreshIndex(rowIndex, modelObject) - - - def _HandleLeftClickOrDoubleClick(self, evt): - """ - Handle a left click or left double click on the ListView - """ - evt.Skip() - - # IF any modifiers are down, OR - # the listview isn't editable, OR - # we should edit on double click and this is a single click, OR - # we should edit on single click and this is a double click, - # THEN we don't try to start a cell edit operation - if evt.m_altDown or evt.m_controlDown or evt.m_shiftDown: - return - if self.cellEditMode == self.CELLEDIT_NONE: - return - if evt.LeftUp() and self.cellEditMode == self.CELLEDIT_DOUBLECLICK: - return - if evt.LeftDClick() and self.cellEditMode == self.CELLEDIT_SINGLECLICK: - return - - # Which item did the user click? - (rowIndex, flags, subItemIndex) = self.HitTestSubItem(evt.GetPosition()) - if (flags & wx.LIST_HITTEST_ONITEM) == 0 or subItemIndex == -1: - return - - # A single click on column 0 doesn't start an edit - if subItemIndex == 0 and self.cellEditMode == self.CELLEDIT_SINGLECLICK: - return - - self._PossibleStartCellEdit(rowIndex, subItemIndex) - - - def _HandleMouseWheel(self, evt): - """ - The user spun the mouse wheel - """ - self._PossibleFinishCellEdit() - evt.Skip() - - - def _HandleScroll(self, evt): - """ - The ListView is being scrolled - """ - self._PossibleFinishCellEdit() - evt.Skip() - - - def _HandleSize(self, evt): - """ - The ListView is being resized - """ - self._PossibleFinishCellEdit() - evt.Skip() - self._ResizeSpaceFillingColumns() - # Make sure our empty msg is reasonably positioned - sz = self.GetClientSize() - self.stEmptyListMsg.SetDimensions(0, sz.GetHeight()/3, sz.GetWidth(), sz.GetHeight()) - #self.stEmptyListMsg.Wrap(sz.GetWidth()) - - - def _HandleTabKey(self, isShiftDown): - """ - Handle a Tab key during a cell edit operation - """ - (rowBeingEdited, subItem) = self.cellBeingEdited - - # Prevent a nasty flicker when tabbing between fields where the selected rows - # are restored at the end of one cell edit, and removed at the start of the next - shadowSelection = self.selectionBeforeCellEdit - self.selectionBeforeCellEdit = [] - self.FinishCellEdit() - - # If we are in report view, move to the next (or previous) editable subitem, - # wrapping at the edges - if self.HasFlag(wx.LC_REPORT): - columnCount = self.GetColumnCount() - for ignored in range(columnCount-1): - if isShiftDown: - subItem = (columnCount + subItem - 1) % columnCount - else: - subItem = (subItem + 1) % columnCount - if self.columns[subItem].isEditable and self.GetColumnWidth(subItem) > 0: - self.StartCellEdit(rowBeingEdited, subItem) - break - - self.selectionBeforeCellEdit = shadowSelection - - - #--------------------------------------------------------------#000000#FFFFFF - # Sorting - - def EnableSorting(self): - """ - Enable automatic sorting when the user clicks on a column title - """ - self.Bind(wx.EVT_LIST_COL_CLICK, self._HandleColumnClick) - - # Install sort indicators if they don't already exist - if self.smallImageList is None: - self.SetImageLists() - if (not self.smallImageList.HasName(ObjectListView.NAME_DOWN_IMAGE) and - self.smallImageList.GetSize(0) == (16,16)): - self.RegisterSortIndicators() - - - def SortBy(self, newColumnIndex, ascending=True): - """ - Sort the items by the given column - """ - oldSortColumnIndex = self.sortColumnIndex - self.sortColumnIndex = newColumnIndex - self.sortAscending = ascending - - # Let the world have a chance to sort the items - evt = OLVEvent.SortEvent(self, self.sortColumnIndex, self.sortAscending, self.IsVirtual()) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed(): - return - - if not evt.wasHandled: - self._SortItemsNow() - - self._UpdateColumnSortIndicators(self.sortColumnIndex, oldSortColumnIndex) - - - def _SortItemsNow(self): - """ - Sort the actual items in the list now, according to the current column and order - """ - sortColumn = self.GetSortColumn() - if not sortColumn: - return - - secondarySortColumn = None # self.GetSecondarySortColumn() - - def _singleObjectComparer(col, object1, object2): - value1 = col.GetValue(object1) - value2 = col.GetValue(object2) - try: - return locale.strcoll(value1.lower(), value2.lower()) - except: - return cmp(value1, value2) - - def _objectComparer(object1, object2): - result = _singleObjectComparer(sortColumn, object1, object2) - if secondarySortColumn and result == 0: - result = _singleObjectComparer(secondarySortColumn, object1, object2) - return result - - self.SortListItemsBy(_objectComparer) - - - def SortListItemsBy(self, cmpFunc, ascending=None): - """ - Sort the existing list items using the given comparison function. - - The comparison function must accept two model objects as parameters. - - The primary users of this method are handlers of the SORT event that want - to sort the items by their own special function. - """ - if ascending is None: - ascending = self.sortAscending - - def _sorter(key1, key2): - cmpVal = cmpFunc(self.innerList[key1], self.innerList[key2]) - if ascending: - return cmpVal - else: - return -cmpVal - - self.SortItems(_sorter) - - - def _SortObjects(self, modelObjects=None, sortColumn=None, secondarySortColumn=None): - """ - Sort the given modelObjects in place. - - This does not change the information shown in the control itself. - """ - if modelObjects is None: - modelObjects = self.modelObjects - if sortColumn is None: - sortColumn = self.GetSortColumn() - if secondarySortColumn == sortColumn: - secondarySortColumn = None - - # If we don't have a sort column, we can't sort -- duhh - if sortColumn is None: - return - - # Let the world have a chance to sort the model objects - evt = OLVEvent.SortEvent(self, self.sortColumnIndex, self.sortAscending, True) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed() or evt.wasHandled: - return - - # When sorting large groups, this is called a lot. Make it efficent. - # It is more efficient (by about 30%) to try to call lower() and catch the - # exception than it is to test for the class - def _getSortValue(x): - primary = sortColumn.GetValue(x) - try: - primary = primary.lower() - except AttributeError: - pass - if secondarySortColumn: - secondary = secondarySortColumn.GetValue(x) - try: - secondary = secondary.lower() - except AttributeError: - pass - return (primary, secondary) - else: - return primary - - modelObjects.sort(key=_getSortValue, reverse=(not self.sortAscending)) - - # Sorting invalidates our object map - self.objectToIndexMap = None - - - def _UpdateColumnSortIndicators(self, sortColumnIndex=None, oldSortColumnIndex=-1): - """ - Change the column that is showing a sort indicator - """ - if sortColumnIndex is None: - sortColumnIndex = self.sortColumnIndex - # Remove the sort indicator from the old sort column - if oldSortColumnIndex >= 0: - headerImage = self.columns[oldSortColumnIndex].headerImage - if isinstance(headerImage, basestring) and self.smallImageList is not None: - headerImage = self.smallImageList.GetImageIndex(headerImage) - self.SetColumnImage(oldSortColumnIndex, headerImage) - - if sortColumnIndex >= 0 and self.smallImageList is not None: - if self.sortAscending: - imageIndex = self.smallImageList.GetImageIndex(ObjectListView.NAME_UP_IMAGE) - else: - imageIndex = self.smallImageList.GetImageIndex(ObjectListView.NAME_DOWN_IMAGE) - - if imageIndex != -1: - self.SetColumnImage(sortColumnIndex, imageIndex) - - - #--------------------------------------------------------------#000000#FFFFFF - # Selecting - - def SelectAll(self): - """ - Selected all rows in the control - """ - # -1 indicates 'all items' - self.SetItemState(-1, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - - - def DeselectAll(self): - """ - De-selected all rows in the control - """ - # -1 indicates 'all items' - self.SetItemState(-1, 0, wx.LIST_STATE_SELECTED) - - - def SelectObject(self, modelObject, deselectOthers=True, ensureVisible=False): - """ - Select the given modelObject. If deselectOthers is True, all other rows will be deselected - """ - i = self.GetIndexOf(modelObject) - if i == -1: - return - - if deselectOthers: - self.DeselectAll() - - realIndex = self._MapModelIndexToListIndex(i) - self.SetItemState(realIndex, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - - if ensureVisible: - self.EnsureVisible(realIndex) - - - def SelectObjects(self, modelObjects, deselectOthers=True): - """ - Select all of the given modelObjects. If deselectOthers is True, all other rows will be deselected - """ - if deselectOthers: - self.DeselectAll() - - for x in modelObjects: - self.SelectObject(x, False) - - - def _MapModelIndexToListIndex(self, modelIndex): - """ - Return the index in the list where the given model index lives - """ - return self.FindItemData(-1, modelIndex) - - #---------------------------------------------------------------------------- - # Cell editing - - def _PossibleStartCellEdit(self, rowIndex, subItemIndex): - """ - Start an edit operation on the given cell after performing some sanity checks - """ - if 0 > rowIndex >= self.GetItemCount(): - return - - if 0 > subItemIndex >= self.GetColumnCount(): - return - - if self.cellEditMode == self.CELLEDIT_NONE: - return - - if not self.columns[subItemIndex].isEditable: - return - - if self.GetObjectAt(rowIndex) is None: - return - - self.StartCellEdit(rowIndex, subItemIndex) - - - def _PossibleFinishCellEdit(self): - """ - If a cell is being edited, finish and commit an edit operation on the given cell. - """ - if self.IsCellEditing(): - self.FinishCellEdit() - - - def _PossibleCancelCellEdit(self): - """ - If a cell is being edited, cancel the edit operation. - """ - if self.IsCellEditing(): - self.CancelCellEdit() - - - def StartCellEdit(self, rowIndex, subItemIndex): - """ - Begin an edit operation on the given cell. - """ - - # Collect the information we need for the StartingEditEvent - modelObject = self.GetObjectAt(rowIndex) - cellValue = self.GetValueAt(modelObject, subItemIndex) - - # Make sure the user can see where the editor is going to be. If the bounds are - # null, this means we needed to scroll horizontally but were unable (this can only - # happen on non-Windows platforms). In that case we can't let the edit happen - # since the user won't be able to see the cell - cellBounds = self.EnsureCellVisible(rowIndex, subItemIndex) - if cellBounds is None: - wx.Bell() - return - - # Give the world the chance to veto the edit, or to change its characteristics - defaultEditor = self._MakeDefaultCellEditor(rowIndex, subItemIndex, cellValue) - evt = OLVEvent.CellEditStartingEvent(self, rowIndex, subItemIndex, modelObject, - cellValue, cellBounds, defaultEditor) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed(): - defaultEditor.Destroy() - return - - # Remember that we are editing something (and make sure we can see it) - self.selectionBeforeCellEdit = self.GetSelectedObjects() - self.DeselectAll() - self.cellEditor = evt.newEditor or evt.editor - self.cellBeingEdited = (rowIndex, subItemIndex) - - # If we aren't using the default editor, destroy it - if self.cellEditor != defaultEditor: - defaultEditor.Destroy() - - # If the event handler hasn't already configured the editor, do it now. - if evt.shouldConfigureEditor: - self.cellEditor.SetFocus() - self.cellEditor.SetValue(evt.cellValue) - self._ConfigureCellEditor(self.cellEditor, evt.cellBounds, rowIndex, subItemIndex) - - # Let the world know the cell editing has started - evt = OLVEvent.CellEditStartedEvent(self, rowIndex, subItemIndex, modelObject, - cellValue, cellBounds, defaultEditor) - self.GetEventHandler().ProcessEvent(evt) - - self.cellEditor.Show() - self.cellEditor.Raise() - - - def _ConfigureCellEditor(self, editor, bounds, rowIndex, subItemIndex): - """ - Perform the normal configuration on the cell editor. - """ - editor.SetDimensions(*bounds) - - colour = self.GetItemBackgroundColour(rowIndex) - if colour.IsOk(): - editor.SetBackgroundColour(colour) - else: - editor.SetBackgroundColour(self.GetBackgroundColour()) - - colour = self.GetItemTextColour(rowIndex) - if colour.IsOk(): - editor.SetForegroundColour(colour) - else: - editor.SetForegroundColour(self.GetTextColour()) - - font = self.GetItemFont(rowIndex) - if font.IsOk(): - editor.SetFont(font) - else: - editor.SetFont(self.GetFont()) - - if hasattr(self.cellEditor, "SelectAll"): - self.cellEditor.SelectAll() - - editor.Bind(wx.EVT_CHAR, self._Editor_OnChar) - editor.Bind(wx.EVT_COMMAND_ENTER, self._Editor_OnChar) - editor.Bind(wx.EVT_KILL_FOCUS, self._Editor_KillFocus) - - - def _MakeDefaultCellEditor(self, rowIndex, subItemIndex, value): - """ - Return an editor that can edit the value of the given cell. - """ - - # The column could have editor creation function registered. - # Otherwise, we have to guess the editor from the type of the value. - # If the given cell actually holds None, we can't decide what editor to use. - # So we try to find any non-null value in the same column. - # If all else fails, we use a string editor. - creatorFunction = self.columns[subItemIndex].cellEditorCreator - if creatorFunction is None: - value = value or self._CalcNonNullValue(subItemIndex) - creatorFunction = CellEditor.CellEditorRegistry().GetCreatorFunction(value) - if creatorFunction is None: - creatorFunction = CellEditor.CellEditorRegistry().GetCreatorFunction("") - return creatorFunction(self, rowIndex, subItemIndex) - - - def _CalcNonNullValue(self, colIndex, maxRows=1000): - """ - Return the first non-null value in the given column, processing - at most maxRows rows - """ - column = self.columns[colIndex] - for i in range(min(self.GetItemCount(), maxRows)): - model = self.GetObjectAt(i) - if model is not None: - value = column.GetValue(model) - if value is not None: - return value - return None - - - def _Editor_OnChar(self, evt): - """ - A character has been pressed in a cell editor - """ - self._HandleChar(evt) - - - def _Editor_KillFocus(self, evt): - evt.Skip() - - # Some control trigger FocusLost events even when they still have focus - focusWindow = wx.Window.FindFocus() - #if focusWindow is not None and self.cellEditor != focusWindow: - if self.cellEditor != focusWindow: - self._PossibleFinishCellEdit() - - - def FinishCellEdit(self): - """ - Finish and commit an edit operation on the given cell. - """ - (rowIndex, subItemIndex) = self.cellBeingEdited - - # Give the world the chance to veto the edit, or to change its characteristics - rowModel = self.GetObjectAt(rowIndex) - evt = OLVEvent.CellEditFinishingEvent(self, rowIndex, subItemIndex, rowModel, - self.cellEditor.GetValue(), self.cellEditor, False) - self.GetEventHandler().ProcessEvent(evt) - if not evt.IsVetoed() and evt.cellValue is not None: - self.columns[subItemIndex].SetValue(rowModel, evt.cellValue) - self.RefreshIndex(rowIndex, rowModel) - - evt = OLVEvent.CellEditFinishedEvent(self, rowIndex, subItemIndex, rowModel, False) - self.GetEventHandler().ProcessEvent(evt) - - self._CleanupCellEdit() - - - def CancelCellEdit(self): - """ - Cancel an edit operation on the given cell. - """ - # Tell the world that the user cancelled the edit - (rowIndex, subItemIndex) = self.cellBeingEdited - evt = OLVEvent.CellEditFinishingEvent(self, rowIndex, subItemIndex, - self.GetObjectAt(rowIndex), - self.cellEditor.GetValue(), - self.cellEditor, - True) - self.GetEventHandler().ProcessEvent(evt) - - evt = OLVEvent.CellEditFinishedEvent(self, rowIndex, subItemIndex, rowModel, True) - self.GetEventHandler().ProcessEvent(evt) - - self._CleanupCellEdit() - - - def _CleanupCellEdit(self): - """ - Cleanup after finishing a cell edit operation - """ - self.SelectObjects(self.selectionBeforeCellEdit) - if self.cellEditor: - self.cellEditor.Hide() - self.cellEditor = None - self.cellBeingEdited = None - self.SetFocus() - - -######################################################################## - -class AbstractVirtualObjectListView(ObjectListView): - """ - This class holds the behaviour that is common to all virtual lists. - - A virtual list must be given an "object getter", which is a callable that accepts the - index of the model object required and returns the model. This can be set via the - SetObjectGetter() method, or passed into the constructor as the "getter" parameter. - - Due to the vagarities of virtual lists, rowFormatters must operate in a slightly - different manner for virtual lists. Instead of being passed a ListItem, rowFormatters - are passed a ListItemAttr instance. This supports the same formatting methods as a - ListItem -- SetBackgroundColour(), SetTextColour(), SetFont() -- but no other ListItem - methods. Obviously, being a virtual list, the rowFormatter cannot call any SetItem* - method on the ListView itself. - - """ - - def __init__(self, *args, **kwargs): - self.lastGetObjectIndex = -1 - self.lastGetObject = None - self.objectGetter = None - self.listItemAttr = None - #self.cacheHit = 0 - #self.cacheMiss = 0 - - self.SetObjectGetter(kwargs.pop("getter", None)) - - # We have to set the item count after the list has been created - if "count" in kwargs: - wx.CallAfter(self.SetItemCount, kwargs.pop("count")) - - # Virtual lists have to be in report format - kwargs["style"] = kwargs.get("style", 0) | wx.LC_REPORT | wx.LC_VIRTUAL - - ObjectListView.__init__(self, *args, **kwargs) - - - #---------------------------------------------------------------------------- - # Commands - - def ClearAll(self): - """ - Remove all items and columns - """ - ObjectListView.ClearAll(self) - self.lastGetObjectIndex = -1 - # Should this call SetItemCount()? - - - def DeleteAllItems(self): - """ - Remove all items - """ - ObjectListView.DeleteAllItems(self) - self.lastGetObjectIndex = -1 - # Should this call SetItemCount()? - - - def RefreshIndex(self, index, modelObject): - """ - Refresh the item at the given index with data associated with the given modelObject - """ - self.lastGetObjectIndex = -1 - self.RefreshItem(index) - - - def RefreshObject(self, modelObject): - """ - Refresh the display of the given modelObject - """ - # We only have a hammer so everything looks like a nail - self.RefreshObjects() - - - def RefreshObjects(self, aList=None): - """ - Refresh all the objects in the given list - """ - # We can only refresh everything - self.lastGetObjectIndex = -1 - if self.GetItemCount() > 0:self.RefreshItems(0, self.GetItemCount()-1) - #self.Refresh() - - - def RepopulateList(self): - """ - Completely rebuild the contents of the list control - """ - # Virtual lists never need to rebuild -- they simply redraw - self.RefreshObjects() - - - def SetItemCount(self, count): - """ - Change the number of items visible in the list - """ - wx.ListCtrl.SetItemCount(self, count) - self.stEmptyListMsg.Show(count == 0) - self.lastGetObjectIndex = -1 - - - def SetObjectGetter(self, aCallable): - """ - Remember the callback that will be used to fetch the objects being displayed in - this list - """ - self.objectGetter = aCallable - - - def _FormatAllRows(self): - """ - Set up the required formatting on all rows - """ - # This is handled within OnGetItemAttr() - pass - - #---------------------------------------------------------------------------- - # Virtual list callbacks. - # These are called a lot! Keep them efficient - - def OnGetItemText(self, itemIdx, colIdx): - """ - Return the text that should be shown at the given cell - """ - return self.GetStringValueAt(self.GetObjectAt(itemIdx), colIdx) - - - def OnGetItemImage(self, itemIdx): - """ - Return the image index that should be shown on the primary column of the given item - """ - return self.GetImageAt(self.GetObjectAt(itemIdx), 0) - - - def OnGetItemColumnImage(self, itemIdx, colIdx): - """ - Return the image index at should be shown at the given cell - """ - return self.GetImageAt(self.GetObjectAt(itemIdx), colIdx) - - - def OnGetItemAttr(self, itemIdx): - """ - Return the display attributes that should be used for the given row - """ - if not self.useAlternateBackColors and self.rowFormatter is None: - return None - - # We have to keep a reference to the ListItemAttr or the garbage collector - # will clear it up immeditately, before the ListCtrl has time to process it. - self.listItemAttr = wx.ListItemAttr() - self._FormatOneItem(self.listItemAttr, itemIdx, self.GetObjectAt(itemIdx)) - - return self.listItemAttr - - - #---------------------------------------------------------------------------- - # Accessing - - def GetObjectAt(self, index): - """ - Return the model modelObject at the given row of the list. - - This method is called a lot! Keep it as efficient as possible. - """ - - # For reasons of performance, it may even be worthwhile removing this test and - # ensure/assume that objectGetter is never None - if self.objectGetter is None: - return None - - #if index == self.lastGetObjectIndex: - # self.cacheHit += 1 - #else: - # self.cacheMiss += 1 - #print "hit: %d / miss: %d" % (self.cacheHit, self.cacheMiss) - - # Cache the last result (the hit rate is normally good: 5-10 hits to 1 miss) - if index != self.lastGetObjectIndex: - self.lastGetObjectIndex = index - self.lastGetObject = self.objectGetter(index) - - return self.lastGetObject - - - -######################################################################## - -class VirtualObjectListView(AbstractVirtualObjectListView): - """ - A virtual object list displays various aspects of an unlimited numbers of objects in a - multi-column list control. - - By default, a VirtualObjectListView cannot sort its rows when the user click on a header. - If you have a back store that can sort the data represented in the virtual list, you - can listen for the EVT_SORT events, and then order your model objects accordingly. - - Due to the vagarities of virtual lists, rowFormatters must operate in a slightly - different manner for virtual lists. Instead of being passed a ListItem, rowFormatters - are passed a ListItemAttr instance. This supports the same formatting methods as a - ListItem -- SetBackgroundColour(), SetTextColour(), SetFont() -- but no other ListItem - methods. Obviously, being a virtual list, the rowFormatter cannot call any SetItem* - method on the ListView itself. - - """ - - def __init__(self, *args, **kwargs): - - # By default, virtual lists aren't sortable - if "sortable" not in kwargs: - kwargs["sortable"] = False - - AbstractVirtualObjectListView.__init__(self, *args, **kwargs) - - #---------------------------------------------------------------------------- - # Commands - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - - This cannot work for virtual lists since the source of model objects is not - under the control of the VirtualObjectListView. - """ - pass - - - def RemoveObjects(self, modelObjects): - """ - Remove the given collections of objects from our collection of objects. - - This cannot work for virtual lists since the source of model objects is not - under the control of the VirtualObjectListView. - """ - pass - - - def SelectObject(self, modelObject, deselectOthers=True): - """ - Select the given modelObject. If deselectOthers is True, all other objects will be deselected - - This doesn't work for virtual lists, since the virtual list has no way - of knowing where 'modelObject' is within the list. - """ - pass - - - def SelectObjects(self, modelObjects, deselectOthers=True): - """ - Select all of the given modelObjects. If deselectOthers is True, all other modelObjects will be deselected - - This doesn't work for virtual lists, since the virtual list has no way - of knowing where any of the modelObjects are within the list. - """ - pass - - - #---------------------------------------------------------------------------- - # Sorting - - def _SortItemsNow(self): - """ - Sort the items by our current settings. - - VirtualObjectListView can't sort anything by themselves, so this is a no-op. - """ - pass - -######################################################################## - -class FastObjectListView(AbstractVirtualObjectListView): - """ - A fast object list view is a nice compromise between the functionality of an ObjectListView - and the speed of a VirtualObjectListView. - - This class codes around the limitations of a virtual list. Specifically, it allows - sorting and selection by object. - """ - - def __init__(self, *args, **kwargs): - - AbstractVirtualObjectListView.__init__(self, *args, **kwargs) - - self.SetObjectGetter(lambda index: self.innerList[index]) - - #---------------------------------------------------------------------------- - # Commands - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - """ - self.modelObjects.extend(modelObjects) - # We don't want to call RepopulateList() here since that makes the whole - # control redraw, which flickers slightly, which I *really* hate! So we - # most of the work of RepopulateList() but only redraw from the first - # added object down. - self._SortObjects() - self._BuildInnerList() - self.SetItemCount(len(self.innerList)) - - # Find where the first added object appears and make that and everything - # after it redraw - first = self.GetItemCount() - for x in modelObjects: - # Because of filtering the added objects may not be in the list - idx = self.GetIndexOf(x) - if idx != -1: - first = min(first, idx) - if first == 0: - break - - if first < self.GetItemCount(): - self.RefreshItems(first, self.GetItemCount() - 1) - - - def RepopulateList(self): - """ - Completely rebuild the contents of the list control - """ - self.lastGetObjectIndex = -1 - self._SortObjects() - self._BuildInnerList() - self.SetItemCount(len(self.innerList)) - self.RefreshObjects() - - # Auto-resize once all the data has been added - self.AutoSizeColumns() - - - def RefreshObjects(self, aList=None): - """ - Refresh all the objects in the given list - """ - self.lastGetObjectIndex = -1 - # If no list is given, refresh everything - if aList: - for x in aList: - idx = self.GetIndexOf(x) - if idx != -1: - self.RefreshItem(idx) - else: - if self.GetItemCount() > 0:self.RefreshItems(0, self.GetItemCount() - 1) - - #---------------------------------------------------------------------------- - # Accessing - - def _MapModelIndexToListIndex(self, modelIndex): - """ - Return the index in the list where the given model index lives - """ - - # In a FastListView, the model index is the same as the list index - return modelIndex - - #---------------------------------------------------------------------------- - # Sorting - - def _SortItemsNow(self): - """ - Sort the items by our current settings. - - FastObjectListView don't sort the items, they sort the model objects themselves. - """ - selection = self.GetSelectedObjects() - self._SortObjects() - - self.SelectObjects(selection) - self.RefreshObjects() - - -####################################################################### - -class GroupListView(FastObjectListView): - """ - An ObjectListView that allows model objects to be organised into collapsable groups. - - GroupListView only work in report view. - - The appearance of the group headers are controlled by the 'groupFont', 'groupTextColour', - and 'groupBackgroundColour' public variables. - - The images used for expanded and collapsed groups can be controlled by changing - the images name 'ObjectListView.NAME_EXPANDED_IMAGE' and 'ObjectListView.NAME_COLLAPSED_IMAGE' - respectfully. Like this:: - - self.AddNamedImages(ObjectListView.NAME_EXPANDED_IMAGE, myOtherImage1) - self.AddNamedImages(ObjectListView.NAME_COLLAPSED_IMAGE, myOtherImage2) - - Public variables: - - * putBlankLineBetweenGroups - When this is True (the default), the list will be built so there is a blank - line between groups. - - """ - - #---------------------------------------------------------------------------- - # Creation - - def __init__(self, *args, **kwargs): - """ - Create a GroupListView. - - Parameters: - - * showItemCounts - - If this is True (the default) Group title will include the count of the items - that are within that group. - - * useExpansionColumn - - If this is True (the default), the expansion/contraction icon will have its - own column at position 0. If this is false, the expand/contract icon will be - in the first user specified column. This must be set before SetColumns() is called. - If it is changed, SetColumns() must be called again. - """ - self.groups = list() - self.showGroups = True - self.putBlankLineBetweenGroups = True - self.alwaysGroupByColumnIndex = -1 - self.useExpansionColumn = kwargs.pop("useExpansionColumn", True) - self.showItemCounts = kwargs.pop("showItemCounts", True) - FastObjectListView.__init__(self, *args, **kwargs) - - # Setup default group characteristics - font = self.GetFont() - self.groupFont = wx.FFont(font.GetPointSize(), font.GetFamily(), wx.FONTFLAG_BOLD, font.GetFaceName()) - self.groupTextColour = wx.Colour(33, 33, 33, 255) - self.groupBackgroundColour = wx.Colour(159, 185, 250, 249) - - self._InitializeImages() - - - def _InitializeImages(self): - """ - Initialize the images used to indicate expanded/collapsed state of groups. - """ - def _makeBitmap(state, size): - bitmap = wx.EmptyBitmap(size, size) - dc = wx.MemoryDC(bitmap) - dc.SetBackground(wx.Brush(self.groupBackgroundColour)) - dc.Clear() - (x, y) = (0, 0) - # The image under Linux is smaller and needs to be offset somewhat to look reasonable - if wx.Platform == "__WXGTK__": - (x, y) = (4, 4) - wx.RendererNative.Get().DrawTreeItemButton(self, dc, (x, y, size, size), state) - dc.SelectObject(wx.NullBitmap) - return bitmap - - # If there isn't a small image list, make one - if self.smallImageList is None: - self.SetImageLists() - - size = self.smallImageList.GetSize()[0] - self.AddNamedImages(ObjectListView.NAME_EXPANDED_IMAGE, _makeBitmap(wx.CONTROL_EXPANDED, size)) - self.AddNamedImages(ObjectListView.NAME_COLLAPSED_IMAGE, _makeBitmap(0, size)) - - - #---------------------------------------------------------------------------- - # Accessing - - def GetShowGroups(self): - """ - Return whether or not this control is showing groups of objects or a straight list - """ - return self.showGroups - - - def SetShowGroups(self, showGroups=True): - """ - Set whether or not this control is showing groups of objects or a straight list - """ - if showGroups == self.showGroups: - return - - self.showGroups = showGroups - if not len(self.columns): - return - - if showGroups: - self.SetColumns(self.columns, False) - else: - if self.useExpansionColumn: - self.SetColumns(self.columns[1:], False) - - self.SetObjects(self.modelObjects) - - - def GetShowItemCounts(self): - """ - Return whether or not the number of items in a groups should be included in the title - """ - return self.showItemCounts - - - def SetShowItemCounts(self, showItemCounts=True): - """ - Set whether or not the number of items in a groups should be included in the title - """ - if showItemCounts != self.showItemCounts: - self.showItemCounts = showItemCounts - self._BuildGroupTitles(self.groups, self.GetGroupByColumn()) - self._SetGroups(self.groups) - - - def GetGroupByColumn(self): - """ - Return the column by which the rows should be grouped - """ - if self.alwaysGroupByColumnIndex >= 0: - return self.GetAlwaysGroupByColumn() - elif self.GetSortColumn() is None: - return self.GetPrimaryColumn() - else: - return self.GetSortColumn() - - - def GetAlwaysGroupByColumn(self): - """ - Get the column by which the rows should be always be grouped. - """ - try: - return self.columns[self.alwaysGroupByColumnIndex] - except IndexError: - return None - - - def SetAlwaysGroupByColumn(self, column): - """ - Set the column by which the rows should be always be grouped. - - 'column' can be None (which clears the setting), a ColumnDefn, - or the index of the column desired - """ - if column is None: - self.alwaysGroupByColumnIndex = -1 - elif isinstance(column, ColumnDefn): - try: - self.alwaysGroupByColumnIndex = self.columns.index(column) - except ValueError: - self.alwaysGroupByColumnIndex = -1 - else: - self.alwaysGroupByColumnIndex = column - - - #---------------------------------------------------------------------------- - # Commands - - def AddObjects(self, modelObjects): - """ - Add the given collections of objects to our collection of objects. - """ - self.groups = None - FastObjectListView.AddObjects(self, modelObjects) - - - def CreateCheckStateColumn(self, columnIndex=0): - """ - Create a fixed width column at the given index to show the checkedness - of objects in this list. - """ - # If the control is configured to have a separate expansion column, - # the check state column has to come after that - if self.useExpansionColumn and columnIndex == 0: - columnIndex = 1 - FastObjectListView.CreateCheckStateColumn(self, columnIndex) - - - def RemoveObjects(self, modelObjects): - """ - Remove the given collections of objects from our collection of objects. - """ - self.groups = None - FastObjectListView.RemoveObjects(self, modelObjects) - - - def SetColumns(self, columns, repopulate=True): - """ - Set the columns for this control. - """ - newColumns = columns[:] - # Insert the column used for expansion and contraction (if one isn't already there) - if self.showGroups and self.useExpansionColumn and len(newColumns) > 0: - if not isinstance(newColumns[0], ColumnDefn) or not newColumns[0].isInternal: - newColumns.insert(0, ColumnDefn("", fixedWidth=24, isEditable=False)) - newColumns[0].isInternal = True - FastObjectListView.SetColumns(self, newColumns, repopulate) - - - def SetGroups(self, groups): - """ - Present the collection of ListGroups in this control. - - Calling this automatically put the control into ShowGroup mode - """ - self.modelObjects = list() - self.SetShowGroups(True) - self._SetGroups(groups) - - - def SetObjects(self, modelObjects, preserveSelection=False): - """ - Set the list of modelObjects to be displayed by the control. - """ - # Force our groups to be rebuilt, if we are supposd to be showing them - if self.showGroups: - self.groups = None - else: - self.groups = list() - FastObjectListView.SetObjects(self, modelObjects, preserveSelection) - - - #---------------------------------------------------------------------------- - # Building - - def _SetGroups(self, groups): - """ - Present the collection of ListGroups in this control. - """ - self.groups = groups - self.RepopulateList() - - - def RebuildGroups(self): - """ - Completely rebuild our groups from our current list of model objects. - - Only use this if SetObjects() has been called. If you have specifically created - your groups and called SetGroups(), do not use this method. - """ - groups = self._BuildGroups() - self.SortGroups(groups) - self._SetGroups(groups) - - - def _BuildGroups(self, modelObjects=None): - """ - Partition the given list of objects into ListGroups depending on the given groupBy column. - - Returns the created collection of ListGroups - """ - if modelObjects is None: - modelObjects = self.modelObjects - if self.filter: - modelObjects = self.filter(modelObjects) - - groupingColumn = self.GetGroupByColumn() - - groupMap = {} - for model in modelObjects: - key = groupingColumn.GetGroupKey(model) - group = groupMap.get(key) - if group is None: - groupMap[key] = group = ListGroup(key, groupingColumn.GetGroupKeyAsString(key)) - group.Add(model) - - groups = groupMap.values() - - if self.GetShowItemCounts(): - self._BuildGroupTitles(groups, groupingColumn) - - # Let the world know that we are creating the given groups - evt = OLVEvent.GroupCreationEvent(self, groups) - self.GetEventHandler().ProcessEvent(evt) - - return evt.groups - - - def _BuildGroupTitles(self, groups, groupingColumn): - """ - Rebuild the titles of the given groups - """ - for x in groups: - x.title = groupingColumn.GetGroupTitle(x, self.GetShowItemCounts()) - - - def _BuildInnerList(self): - """ - Build the list that will be used to populate the ListCtrl. - - This internal list is an amalgum of model objects, ListGroups - and None (which are blank rows). - """ - self.objectToIndexMap = None - if not self.showGroups: - return ObjectListView._BuildInnerList(self) - - if not self.modelObjects: - self.groups = list() - self.innerList = list() - return - - if self.groups is None: - self.groups = self._BuildGroups() - self.SortGroups() - - self.innerList = list() - for grp in self.groups: - if len(self.innerList) and self.putBlankLineBetweenGroups: - self.innerList.append(None) - self.innerList.append(grp) - if grp.isExpanded: - self.innerList.extend(grp.modelObjects) - - #---------------------------------------------------------------------------- - # Virtual list callbacks. - # These are called a lot! Keep them efficient - - def OnGetItemText(self, itemIdx, colIdx): - """ - Return the text that should be shown at the given cell - """ - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return "" - - if isinstance(modelObject, ListGroup): - if self.GetPrimaryColumnIndex() == colIdx: - return modelObject.title - else: - return "" - - return self.GetStringValueAt(modelObject, colIdx) - - - def OnGetItemImage(self, itemIdx): - """ - Return the image index that should be shown on the primary column of the given item - """ - # I don't think this method is ever called. Maybe in non-details views. - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return -1 - - if isinstance(modelObject, ListGroup): - if modelObject.isExpanded: - imageKey = ObjectListView.NAME_EXPANDED_IMAGE - else: - imageKey = ObjectListView.NAME_COLLAPSED_IMAGE - return self.smallImageList.GetImageIndex(imageKey) - - return self.GetImageAt(modelObject, 0) - - - def OnGetItemColumnImage(self, itemIdx, colIdx): - """ - Return the image index at should be shown at the given cell - """ - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return -1 - - if isinstance(modelObject, ListGroup): - if colIdx == 0: - if modelObject.isExpanded: - imageKey = ObjectListView.NAME_EXPANDED_IMAGE - else: - imageKey = ObjectListView.NAME_COLLAPSED_IMAGE - return self.smallImageList.GetImageIndex(imageKey) - else: - return -1 - - return self.GetImageAt(modelObject, colIdx) - - - def OnGetItemAttr(self, itemIdx): - """ - Return the display attributes that should be used for the given row - """ - self.listItemAttr = wx.ListItemAttr() - - modelObject = self.innerList[itemIdx] - - if modelObject is None: - return self.listItemAttr - - if isinstance(modelObject, ListGroup): - # We have to keep a reference to the ListItemAttr or the garbage collector - # will clear it up immeditately, before the ListCtrl has time to process it. - if self.groupFont is not None: - self.listItemAttr.SetFont(self.groupFont) - if self.groupTextColour is not None: - self.listItemAttr.SetTextColour(self.groupTextColour) - if self.groupBackgroundColour is not None: - self.listItemAttr.SetBackgroundColour(self.groupBackgroundColour) - return self.listItemAttr - - return FastObjectListView.OnGetItemAttr(self, itemIdx) - - #---------------------------------------------------------------------------- - # Commands - - def ToggleExpansion(self, group): - """ - Toggle the expanded/collapsed state of the given group and redisplay the list - """ - self._DoExpandCollapse([group], not group.isExpanded) - - - def Expand(self, group): - """ - Expand the given group and redisplay the list - """ - self._DoExpandCollapse([group], True) - - - def Collapse(self, group): - """ - Collapse the given group and redisplay the list - """ - self._DoExpandCollapse([group], False) - - - def ExpandAll(self, groups=None): - """ - Expand the given groups (or all groups) and redisplay the list - """ - if groups is None: - groups = self.groups - self._DoExpandCollapse(groups, True) - - - def CollapseAll(self, groups=None): - """ - Collapse the given groups (or all groups) and redisplay the list - """ - if groups is None: - groups = self.groups - self._DoExpandCollapse(groups, False) - - - def _DoExpandCollapse(self, groups, isExpanding): - """ - Do the real work of expanding/collapsing the given groups - """ - # Cull groups that aren't going to change - groups = [x for x in groups if x.isExpanded != isExpanding] - if not groups: - return - - # Let the world know that the given groups are about to be expanded/collapsed - evt = OLVEvent.ExpandingCollapsingEvent(self, groups, isExpanding) - self.GetEventHandler().ProcessEvent(evt) - if evt.IsVetoed(): - return - - # Expand/contract the groups, then put those changes into effect - for x in evt.groups: - x.isExpanded = isExpanding - self._BuildInnerList() - self.SetItemCount(len(self.innerList)) - - # Refresh eveything from the first group down - i = min([self.GetIndexOf(x) for x in evt.groups]) - self.RefreshItems(i, len(self.innerList)-1) - - # Let the world know that the given groups have been expanded/collapsed - evt = OLVEvent.ExpandedCollapsedEvent(self, evt.groups, isExpanding) - self.GetEventHandler().ProcessEvent(evt) - - - def Reveal(self, modelObject): - """ - Ensure that the given modelObject is visible, expanding the group it belongs to, - if necessary - """ - # If it is already there, just make sure it is visible - i = self.GetIndexOf(modelObject) - if i != -1: - self.EnsureVisible(i) - return True - - # Find which group (if any) the object belongs to, and - # expand it and then try to reveal it again - for group in self.groups: - if not group.isExpanded and modelObject in group.modelObjects: - self.Expand(group) - return self.Reveal(modelObject) - - return False - - - def SelectAll(self): - """ - Selected all model objects in the control. - - In a GroupListView, this does not select blank lines or groups - """ - self.SetItemState(-1, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED) - - for (i, x) in enumerate(self.innerList): - if x is None or isinstance(x, ListGroup): - self.SetItemState(i, 0, wx.LIST_STATE_SELECTED) - - # With the current implemetation, these are synonyms - SelectGroup = ObjectListView.SelectObject - SelectGroups = ObjectListView.SelectObjects - - - #---------------------------------------------------------------------------- - # Accessing - - def FindGroupFor(self, modelObject): - """ - Return the group that contains the given object or None if the given - object is not found - """ - for group in self.groups: - if modelObject in group.modelObjects: - return group - return None - - - def GetSelectedGroups(self): - """ - Return a list of the groups that are selected - """ - selectedGroups = list() - i = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - while i != -1: - model = self.innerList[i] - if isinstance(model, ListGroup): - selectedGroups.append(model) - i = self.GetNextItem(i, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - return selectedGroups - - - def GetFilteredObjects(self): - """ - Return the model objects that are actually displayed in the control. - """ - objects = list() - for x in self.groups: - objects.extend(x.modelObjects) - return objects - - - def GetObjectAt(self, index): - """ - Return the model object at the given row of the list. - - With GroupListView, this method can return None, since the given - index may be a blank row or a group header. These do not have - corresponding model objects. - """ - try: - model = self.innerList[index] - if isinstance(model, ListGroup): - model = None - except IndexError: - model = None - - return model - - - def YieldSelectedObjects(self): - """ - Progressively yield the selected modelObjects. - - Only return model objects, not blank lines or ListGroups - """ - i = self.GetNextItem(-1, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - while i != -1: - model = self.GetObjectAt(i) - if model is not None: - yield model - i = self.GetNextItem(i, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) - - - def _CanUseBisect(self, searchColumn): - """ - Return True if we can use binary search on the given column. - - A GroupListView can never use binary search since its rows aren't sorted. - """ - return not self.showGroups - - - def _GetValuesAsMultiList(self, objects): - """ - Return a list of lists of the string of the aspects of the given objects - """ - cols = self.columns[self.GetPrimaryColumnIndex():] # We don't want to copy the expand icon columns - objects = [x for x in objects if x is not None and not isinstance(x, ListGroup)] - return [[column.GetStringValue(x) for column in cols] for x in objects] - - - #---------------------------------------------------------------------------- - # Event handlers - - def _HandleChar(self, evt): - - if not self.IsCellEditing() and self.handleStandardKeys: - if (evt.GetKeyCode() == wx.WXK_LEFT): - self.CollapseAll(self.GetSelectedGroups()) - return - if (evt.GetKeyCode() == wx.WXK_RIGHT): - self.ExpandAll(self.GetSelectedGroups()) - return - - FastObjectListView._HandleChar(self, evt) - - - def _HandleColumnClick(self, evt): - """ - The user has clicked on a column title - """ - - # If they click on a new column, we have to rebuild our groups - if evt.GetColumn() != self.sortColumnIndex: - self.groups = None - - FastObjectListView._HandleColumnClick(self, evt) - - - def _HandleLeftDownOnImage(self, rowIndex, subItemIndex): - """ - Handle a left click on the image at the given cell - """ - self._PossibleFinishCellEdit() - - listObject = self.innerList[rowIndex] - if subItemIndex == 0 and isinstance(listObject, ListGroup): - self.ToggleExpansion(listObject) - else: - FastObjectListView._HandleLeftDownOnImage(self, rowIndex, subItemIndex) - - - #---Sorting-------------------------------------------------------#000000#FFFFFF - - def SortGroups(self, groups=None, ascending=None): - """ - Sort the given collection of groups in the given direction (defaults to ascending). - - The model objects within each group will be sorted as well - """ - if groups is None: - groups = self.groups - if ascending is None: - ascending = self.sortAscending - - # If the groups are locked, we sort by the sort column, otherwise by the grouping column. - # The primary column is always used as a secondary sort key. - if self.GetAlwaysGroupByColumn(): - sortCol = self.GetSortColumn() - else: - sortCol = self.GetGroupByColumn() - - # Let the world have a change to sort the items - evt = OLVEvent.SortGroupsEvent(self, groups, sortCol, ascending) - self.GetEventHandler().ProcessEvent(evt) - if evt.wasHandled: - return - - # Sorting event wasn't handled, so we do the default sorting - def _getLowerCaseKey(group): - try: - return group.key.lower() - except: - return group.key - - groups.sort(key=_getLowerCaseKey, reverse=(not ascending)) - - # Sort the model objects within each group. - for x in groups: - self._SortObjects(x.modelObjects, sortCol, self.GetPrimaryColumn()) - - - def _SortItemsNow(self): - """ - Sort the items by our current settings. - - GroupListViews don't sort the items directly. We have to sort the groups - and then rebuild the list. - """ - if not self.showGroups: - return FastObjectListView._SortItemsNow(self) - - if self.groups is None: - self.groups = self._BuildGroups() - self.SortGroups(self.groups) - self._SetGroups(self.groups) - - - #def _FormatAllRows(self): - # """ - # GroupListViews don't need this method. - # """ - # pass - - - -####################################################################### - -class ListGroup(object): - """ - A ListGroup is a partition of model objects that can be presented - under a collapsible heading in a GroupListView. - """ - - def __init__(self, key, title, isExpanded=True): - self.key = key - self.title = title - - self.isExpanded = isExpanded - self.modelObjects = list() - - - def Add(self, model): - """ - Add the given model to those that belong to this group. - """ - self.modelObjects.append(model) - - -####################################################################### - -class ColumnDefn(object): - """ - A ColumnDefn controls how one column of information is sourced and formatted. - - Much of the intelligence and ease of use of an ObjectListView comes from the column - definitions. It is worthwhile gaining an understanding of the capabilities of this class. - - Public Attributes (alphabetically): - - * align - How will the title and the cells of the this column be aligned. Possible - values: 'left', 'centre', 'right' - - * cellEditorCreator - This is a callable that will be invoked to create an editor for value in this - column. The callable should accept three parameters: the objectListView starting - the edit, the rowIndex and the subItemIndex. It should create and return a Control - that is capable of editing the value. - - If this is None, a cell editor will be chosen based on the type of objects in this - column (See CellEditor.EditorRegistry). - - * freeSpaceProportion - If the column is space filling, this attribute controls what proportion of the - space should be given to this column. By default, all spacing filling column share - the free space equally. By changing this attribute, a column can be given a larger - proportion of the space. - - * groupKeyConverter - The groupKeyConverter converts a group key into the string that can be presented - to the user. This string will be used as part of the title for the group. - - Its behaviour is the same as "stringConverter." - - * groupKeyGetter - When this column is used to group the model objects, what groupKeyGetter extracts - the value from each model that will be used to partition the model objects into - groups. - - Its behaviour is the same as "valueGetter." - - If this is None, the value returned by valueGetter will be used. - - * groupTitleSingleItem - When a group is created that contains a single item, and the GroupListView - has "showItemCounts" turned on, this string will be used to create the title - of the group. The string should contain two placeholder: %(title)s and %(count)d. - Example: "%(title)s [only %(count)d song]" - - * groupTitlePluralItems - When a group is created that contains 0 items or >1 items, and the GroupListView - has "showItemCounts" turned on, this string will be used to create the title - of the group. The string should contain two placeholder: %(title)s and %(count)d. - Example: "%(title)s [%(count)d songs]" - - * headerImage - The index or name of the image that will be shown against the column header. - Remember, a column header can only show one image at a time, so if the column - is the sort column, it will show the sort indicator -- not this headerImage. - - * imageGetter - A string, callable or integer that is used to get a index of the image to be - shown in a cell. - - Strings and callable are used as for the `valueGetter` attribute. - - Integers are treated as constants (that is, all rows will have the same - image). - - * isEditable - Can the user edit cell values in this column? Default is True - - * isSearchable - If this column is the sort column, when the user types into the ObjectListView, - will a match be looked for using values from this column? If this is False, - values from column 0 will be used. - Default is True. - - * isSpaceFilling - Is this column a space filler? Space filling columns resize to occupy free - space within the listview. As the listview is expanded, space filling columns - expand as well. Conversely, as the control shrinks these columns shrink too. - - Space filling columns can disappear (i.e. have a width of 0) if the control - becomes too small. You can set `minimumWidth` to prevent them from - disappearing. - - * maximumWidth - An integer indicate the number of pixels above which this column will not resize. - Default is -1, which means there is no limit. - - * minimumWidth - An integer indicate the number of pixels below which this column will not resize. - Default is -1, which means there is no limit. - - * useBinarySearch - If isSearchable and useBinarySearch are both True, the ObjectListView will use a - binary search algorithm to locate a match. If useBinarySearch is False, a simple - linear search will be done. - - The binary search can quickly search large numbers of rows (10,000,000 in about 25 - comparisons), which makes them ideal for virtual lists. However, there are two - constraints: - - - the ObjectListView must be sorted by this column - - - sorting by string representation must give the same ordering as sorting - by the aspect itself. - - The second constraint is necessary because the user types characters expecting - them to match the string representation of the data. The binary search will make - its decisions using the string representation, but the rows ordered - by aspect value. This will only work if sorting by string representation - would give the same ordering as sorting by the aspect value. - - In general, binary searches work with strings, YYYY-MM-DD dates, and booleans. - They do not work with numerics or other date formats. - - If either of these constrains are not true, you must set - useBinarySearch to False and be content with linear searches. Otherwise, the - searching will not work correctly. - - * stringConverter - A string or a callable that will used to convert a cells value into a presentation - string. - - If it is a callble, it will be called with the value for the cell and must return - a string. - - If it is a string, it will be used as a format string with the % operator, e.g. - "self.stringConverter % value." For dates and times, the stringConverter will be - passed as the first parameter to the strftime() method on the date/time. - - * title - A string that will be used as the title of the column in the listview - - * valueGetter - A string, callable or integer that is used to get the value to be displayed in - a cell. See _Munge() for details on how this attribute is used. - - A callable is simply called and the result is the value for the cell. - - The string can be the name of a method to be invoked, the name of an attribute - to be fetched, or (for dictionary like objects) an index into the dictionary. - - An integer can only be used for list-like objects and is used as an index into - the list. - - * valueSetter - A string, callable or integer that is used to write an edited value back into the - model object. - - A callable is called with the model object and the new value. Example:: - - myCol.valueSetter(modelObject, newValue) - - An integer can only be used if the model object is a mutable sequence. The integer - is used as an index into the list. Example:: - - modelObject[myCol.valueSetter] = newValue - - The string can be: - - * the name of a method to be invoked, in which case the method should accept the - new value as its parameter. Example:: - - method = getattr(modelObject, myCol.valueSetter) - method(newValue) - - * the name of an attribute to be updated. This attribute will not be created: it - must already exist. Example:: - - setattr(modelObject, myCol.valueSetter, newValue) - - * for dictionary like model objects, an index into the dictionary. Example:: - - modelObject[myCol.valueSetter] = newValue - - * useInitialLetterForGroupKey - When this is true and the group key for a row is a string, only the first letter of - the string will be considered as the group key. This is often useful for grouping - row when the column contains a name. - - * width - How many pixels wide will the column be? -1 means auto size to contents. For a list with - thousands of items, autosize can be noticably slower than specifically setting the size. - - - The `title`, `align` and `width` attributes are only references when the column definition is given to the - ObjectListView via the `SetColumns()` or `AddColumnDefn()` methods. The other attributes are referenced - intermittently -- changing them will change the behaviour of the `ObjectListView`. - - Without a string converter, None will be converted to an empty string. Install a string converter ('%s' - will suffice) if you want to see the 'None' instead. - - BUG: Double-clicking on a divider (under Windows) can resize a column beyond its minimum and maximum widths. - """ - - def __init__(self, title="title", align="left", width=-1, - valueGetter=None, imageGetter=None, stringConverter=None, valueSetter=None, isEditable=True, - fixedWidth=None, minimumWidth=-1, maximumWidth=-1, isSpaceFilling=False, - cellEditorCreator=None, autoCompleteCellEditor=False, autoCompleteComboBoxCellEditor=False, - checkStateGetter=None, checkStateSetter=None, - isSearchable=True, useBinarySearch=None, headerImage=-1, - groupKeyGetter=None, groupKeyConverter=None, useInitialLetterForGroupKey=False, - groupTitleSingleItem=None, groupTitlePluralItems=None): - """ - Create a new ColumnDefn using the given attributes. - - The attributes behave as described in the class documentation, except for: - - * fixedWidth - An integer which indicates that this column has the given width and is not resizable. - Useful for column that always display fixed with data (e.g. a single icon). Setting this - parameter overrides the width, minimumWidth and maximumWidth parameters. - - * autoCompleteCellEditor - If this is True, the column will use an autocomplete TextCtrl when - values of this column are edited. This overrules the cellEditorCreator parameter. - - * autoCompleteComboBoxCellEditor - If this is True, the column will use an autocomplete ComboBox when - values of this column are edited. This overrules the cellEditorCreator parameter. - """ - self.title = title - self.align = align - self.valueGetter = valueGetter - self.imageGetter = imageGetter - self.stringConverter = stringConverter - self.valueSetter = valueSetter - self.isSpaceFilling = isSpaceFilling - self.cellEditorCreator = cellEditorCreator - self.freeSpaceProportion = 1 - self.isEditable = isEditable - self.isSearchable = isSearchable - self.useBinarySearch = useBinarySearch - self.headerImage = headerImage - self.groupKeyGetter = groupKeyGetter - self.groupKeyConverter = groupKeyConverter - self.useInitialLetterForGroupKey = useInitialLetterForGroupKey - self.groupTitleSingleItem = groupTitleSingleItem or "%(title)s (%(count)d item)" - self.groupTitlePluralItems = groupTitlePluralItems or "%(title)s (%(count)d items)" - self.isInternal = False # was this column created internally by ObjectListView? - - self.minimumWidth = minimumWidth - self.maximumWidth = maximumWidth - self.width = self.CalcBoundedWidth(width) - - if fixedWidth is not None: - self.SetFixedWidth(fixedWidth) - - if autoCompleteCellEditor: - self.cellEditorCreator = lambda olv, row, col: CellEditor.MakeAutoCompleteTextBox(olv, col) - - if autoCompleteComboBoxCellEditor: - self.cellEditorCreator = lambda olv, row, col: CellEditor.MakeAutoCompleteComboBox(olv, col) - - self.checkStateGetter = checkStateGetter - self.checkStateSetter = checkStateSetter - - #------------------------------------------------------------------------------- - # Column properties - - def GetAlignment(self): - """ - Return the alignment that this column uses - """ - alignment = { - "l": wx.LIST_FORMAT_LEFT, - "c": wx.LIST_FORMAT_CENTRE, - "r": wx.LIST_FORMAT_RIGHT - }.get(self.align[:1], wx.LIST_FORMAT_LEFT) - - return alignment - - def GetAlignmentForText(self): - """ - Return the alignment of this column in a form that can be used as - a style flag on a text control - """ - return { - "l": wx.TE_LEFT, - "c": wx.TE_CENTRE, - "r": wx.TE_RIGHT, - }.get(self.align[:1], wx.TE_LEFT) - - #------------------------------------------------------------------------------- - # Value accessing - - def GetValue(self, modelObject): - """ - Return the value for this column from the given modelObject - """ - return self._Munge(modelObject, self.valueGetter) - - - def GetStringValue(self, modelObject): - """ - Return a string representation of the value for this column from the given modelObject - """ - value = self.GetValue(modelObject) - return self._StringToValue(value, self.stringConverter) - - - def _StringToValue(self, value, converter): - """ - Convert the given value to a string, using the given converter - """ - try: - return converter(value) - except TypeError: - pass - - if converter and isinstance(value, (datetime.datetime, datetime.date, datetime.time)): - return value.strftime(self.stringConverter) - - # By default, None is changed to an empty string. - if not converter and not value: - return "" - - fmt = converter or "%s" - try: - return fmt % value - except UnicodeError: - return unicode(fmt) % value - - - def GetGroupKey(self, modelObject): - """ - Return the group key for this column from the given modelObject - """ - if self.groupKeyGetter is None: - key = self.GetValue(modelObject) - else: - key = self._Munge(modelObject, self.groupKeyGetter) - if self.useInitialLetterForGroupKey: - try: - return key[:1].upper() - except TypeError: - return key - else: - return key - - - def GetGroupKeyAsString(self, groupKey): - """ - Return the given group key as a human readable string - """ - # If there is no group key getter, we must have the normal aspect value. So if - # there isn't a special key converter, use the normal aspect to string converter. - if self.groupKeyGetter is None and self.groupKeyConverter is None: - return self._StringToValue(groupKey, self.stringConverter) - else: - return self._StringToValue(groupKey, self.groupKeyConverter) - - - def GetGroupTitle(self, group, useItemCount): - """ - Return a title of the group - """ - title = self.GetGroupKeyAsString(group.key) - if useItemCount: - objectCount = len(group.modelObjects) - if objectCount == 1: - fmt = self.groupTitleSingleItem - else: - fmt = self.groupTitlePluralItems - title = fmt % {"title":title, "count":objectCount} - return title - - - def GetImage(self, modelObject): - """ - Return the image index for this column from the given modelObject. -1 means no image. - """ - if self.imageGetter is None: - return -1 - - if isinstance(self.imageGetter, int): - return self.imageGetter - - idx = self._Munge(modelObject, self.imageGetter) - if idx is None: - return -1 - else: - return idx - - - def SetValue(self, modelObject, value): - """ - Set this columns aspect of the given modelObject to have the given value. - """ - if self.valueSetter is None: - return self._SetValueUsingMunger(modelObject, value, self.valueGetter, False) - else: - return self._SetValueUsingMunger(modelObject, value, self.valueSetter, True) - - - def _SetValueUsingMunger(self, modelObject, value, munger, shouldInvokeCallable): - """ - Look for ways to update modelObject with value using munger. If munger finds a - callable, it will be called if shouldInvokeCallable == True. - """ - # If there isn't a munger, we can't do anything - if munger is None: - return - - # Is munger a function? - if callable(munger): - if shouldInvokeCallable: - munger(modelObject, value) - return - - # Try indexed access for dictionary or list like objects - try: - modelObject[munger] = value - return - except: - pass - - # Is munger the name of some slot in the modelObject? - try: - attr = getattr(modelObject, munger) - except TypeError: - return - except AttributeError: - return - - # Is munger the name of a method? - if callable(attr): - if shouldInvokeCallable: - attr(value) - return - - # If we get to here, it seems that munger is the name of an attribute or - # property on modelObject. Try to set, realising that many things could still go wrong. - try: - setattr(modelObject, munger, value) - except: - pass - - - def _Munge(self, modelObject, munger): - """ - Wrest some value from the given modelObject using the munger. - With a description like that, you know this method is going to be obscure :-) - - 'munger' can be: - - 1) a callable. - This method will return the result of executing 'munger' with 'modelObject' as its parameter. - - 2) the name of an attribute of the modelObject. - If that attribute is callable, this method will return the result of executing that attribute. - Otherwise, this method will return the value of that attribute. - - 3) an index (string or integer) onto the modelObject. - This allows dictionary-like objects and list-like objects to be used directly. - """ - if munger is None: - return None - - # THINK: The following code treats an instance variable with the value of None - # as if it doesn't exist. Is that best? - - # Try attribute access - try: - attr = getattr(modelObject, munger, None) - if attr is not None: - try: - return attr() - except TypeError: - return attr - except TypeError: - # Happens when munger is not a string - pass - - # Use the callable directly, if possible. - # In accordance with Guido's rules for Python 3, we just call it and catch the - # exception - try: - return munger(modelObject) - except TypeError: - pass - - # Try dictionary-like indexing - try: - return modelObject[munger] - except: - return None - - #------------------------------------------------------------------------------- - # Width management - - def CalcBoundedWidth(self, width): - """ - Calculate the given width bounded by the (optional) minimum and maximum column widths - """ - - # Values of < 0 have special meanings, so just return them - if width < 0: - return width - - if self.maximumWidth >= 0: - width = min(self.maximumWidth, width) - return max(self.minimumWidth, width) - - - def IsFixedWidth(self): - """ - Is this column fixed width? - """ - return self.minimumWidth != -1 and \ - self.maximumWidth != -1 and \ - (self.minimumWidth >= self.maximumWidth) - - - def SetFixedWidth(self, width): - """ - Make this column fixed width - """ - self.width = self.minimumWidth = self.maximumWidth = width - - #---------------------------------------------------------------------------- - # Check state - - def HasCheckState(self): - """ - Return if this column is showing a check box? - """ - return self.checkStateGetter is not None - - - def GetCheckState(self, modelObject): - """ - Return the check state of the given model object - """ - if self.checkStateGetter is None: - return None - else: - return self._Munge(modelObject, self.checkStateGetter) - - - def SetCheckState(self, modelObject, state): - """ - Set the check state of the given model object - """ - if self.checkStateSetter is None: - return self._SetValueUsingMunger(modelObject, state, self.checkStateGetter, False) - else: - return self._SetValueUsingMunger(modelObject, state, self.checkStateSetter, True) - -#====================================================================== - -class NamedImageList(object): - """ - A named image list is an Adaptor that gives a normal image list - the ability to reference images by name, rather than just index - """ - - def __init__(self, imageList=None, imageSize=16): - """ - """ - self.imageList = imageList or wx.ImageList(imageSize, imageSize) - self.imageSize = imageSize - self.nameToImageIndexMap = {} - - - def GetSize(self, ignored=None): - """ - Return a pair that represents the size of the image in this list - """ - # Mac and Linux have trouble getting the size of empty image lists - if self.imageList.GetImageCount() == 0: - return (self.imageSize, self.imageSize) - else: - return self.imageList.GetSize(0) - - - def AddNamedImage(self, name, image): - """ - Add the given image to our list, and remember its name. - Returns the images index. - """ - imageIndex = self.imageList.Add(image) - if name is not None: - self.nameToImageIndexMap[name] = imageIndex - return imageIndex - - - def HasName(self, name): - """ - Does this list have an image with the given name?" - """ - return name in self.nameToImageIndexMap - - - def GetImageIndex(self, name): - """ - Return the image with the given name, or -1 if it doesn't exist - """ - return self.nameToImageIndexMap.get(name, -1) - -#====================================================================== - -class BatchedUpdate(object): - """ - This class is an *Adapter* around an ``ObjectListView`` which ensure that the list is updated, at most, - once every *N* seconds. - - Usage:: - - self.olv2 = BatchedUpdate(self.olv, 3) - # Now use olv2 in place of olv, and the list will only be updated at most once - # every 3 second, no many how many calls are made to it. - - This is useful for a certain class of problem where model objects are update frequently -- more - frequently than you wish to update the control. A backup program may be able to backup several - files a second, but does not wish to update the list ctrl that often. A packet sniffer will - receive hundreds of packets per second, but should not try to update the list ctrl for each - packet. A batched update adapter solves situations like these in a trivial manner. - - This class only intercepts the following messages: - * ``AddObject()``, ``AddObjects()`` - * ``RefreshObject()``, ``RefreshObjects()`` - * ``RemoveObject()``, ``RemoveObjects()`` - * ``RepopulateList()`` - * ``SetObjects()`` - - All other message are passed directly to the ``ObjectListView`` and are thus unbatched. This means - that sorting and changes to columns are unbatched and will take effect immediately. - - You need to be a little careful when using batched updates. There are at least two things - you need to avoid, or at least be careful about: - - 1) Don't mix batched and unbatched updates. If you go behind the back of the batched update - wrapper and make direct changes to the underlying control, you will probably get bitten by - difficult-to-reproduce bugs. For example:: - - self.olvBatched.SetObjects(objects) # Batched update - self.olvBatched.objectlistView.AddObject(aModel) # unbatched update - - This will almost certainly not do what you expect, or at best, will only sometimes do - what you want. - - 2) You cannot assume that objects will immediately appear in the list and - thus be available for further operations. For example:: - - self.olv.AddObject(aModel) - self.olv.Check(aModel) - - If *self.olv* is a batched update adapter, this code *may* not work since the - ``AddObject()`` might not have yet taken effect, so the ``Check()`` will not find - *aModel* in the control. Worse, it may work most of the time and fail only occassionally. - - If you need to be able to do further processing on objects just added, it would be better - not to use a batched adapter. - - """ - - # For SetObjects(), None and empty list are both possible valid values so we need a - # non-valid value that indicates that SetObjects() has not been called - NOT_SET = -1 - - def __init__(self, objectListView, updatePeriod=0): - self.objectListView = objectListView # Must not be None - self.updatePeriod = updatePeriod - - self.objectListView.Bind(wx.EVT_IDLE, self._HandleIdle) - - self.newModelObjects = BatchedUpdate.NOT_SET - self.objectsToAdd = list() - self.objectsToRefresh = list() - self.objectsToRemove = list() - self.freezeUntil = 0 - - - def __getattr__(self, name): - """ - Forward any unknown references to the original objectListView. - - This is what allows us to pretend to be an ObjectListView. - """ - return getattr(self.objectListView, name) - - - def RepopulateList(self): - """ - Remember the given model objects so that they can be displayed when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.RepopulateList() - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.newModelObjects = self.objectListView.modelObjects - self.objectsToRefresh = list() - - # Unlike SetObjects(), refreshing the list does NOT invalidate the objects to be added/removed - - - def SetObjects(self, modelObjects): - """ - Remember the given model objects so that they can be displayed when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.SetObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.newModelObjects = modelObjects - # Explicitly setting the objects to be shown renders void any previous Add/Refresh/Remove commands - self.objectsToAdd = list() - self.objectsToRefresh = list() - self.objectsToRemove = list() - - - def AddObject(self, modelObject): - """ - Add the given object to our collection of objects. - - The object will appear at its sorted location, or at the end of the list if - the list is unsorted - """ - self.AddObjects([modelObject]) - - - def AddObjects(self, modelObjects): - """ - Remember the given model objects so that they can be added when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.AddObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - # TODO: We should check that none of the model objects is already in the list - self.objectsToAdd.extend(modelObjects) - - # Since we are adding these objects, we must no longer remove them - if self.objectsToRemove: - for x in modelObjects: - self.objectsToRemove.remove(x) - - - def RefreshObject(self, modelObject): - """ - Refresh the display of the given model - """ - self.RefreshObjects([modelObject]) - - - def RefreshObjects(self, modelObjects): - """ - Refresh the information displayed about the given model objects - """ - if self.freezeUntil < time.clock(): - self.objectListView.RefreshObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.objectsToRefresh.extend(modelObjects) - - - def RemoveObject(self, modelObjects): - """ - Remember the given model objects so that they can be removed when the next update cycle occurs - """ - self.RemoveObjects([modelObject]) - - - def RemoveObjects(self, modelObjects): - """ - Remember the given model objects so that they can be removed when the next update cycle occurs - """ - if self.freezeUntil < time.clock(): - self.objectListView.RemoveObjects(modelObjects) - self.freezeUntil = time.clock() + self.updatePeriod - return - - self.objectsToRemove.extend(modelObjects) - - # Since we are removing these objects, we must no longer add them - if self.objectsToAdd: - for x in modelObjects: - self.objectsToAdd.remove(x) - - #---------------------------------------------------------------------------- - # Event processing - - def _HandleIdle(self, evt): - """ - The app is idle. Process any outstanding requests - """ - if (self.newModelObjects != BatchedUpdate.NOT_SET or - self.objectsToAdd or - self.objectsToRefresh or - self.objectsToRemove): - if self.freezeUntil < time.clock(): - self._ApplyChanges() - else: - evt.RequestMore() - - def _ApplyChanges(self): - """ - Apply any batched changes to the list - """ - if self.newModelObjects != BatchedUpdate.NOT_SET: - self.objectListView.SetObjects(self.newModelObjects) - - if self.objectsToAdd: - self.objectListView.AddObjects(self.objectsToAdd) - - if self.objectsToRemove: - self.objectListView.RemoveObjects(self.objectsToRemove) - - if self.objectsToRefresh: - self.objectListView.RefreshObjects(self.objectsToRefresh) - - self.newModelObjects = BatchedUpdate.NOT_SET - self.objectsToAdd = list() - self.objectsToRemove = list() - self.objectsToRefresh = list() - self.freezeUntil = time.clock() + self.updatePeriod - -#---------------------------------------------------------------------------- -# Built in images so clients don't have to do the same - -import cStringIO, zlib - -def _getSmallUpArrowData(): - return zlib.decompress( -'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\ -\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4[z\xba8\x86X\xf4&\ -\xa7\xa4$\xa5-`1\x08\\R\xcd"\x11\x10\x1f\xfe{~\x0es\xc2,N\xc9\xa6\xab\x0c%\ -\xbe?x\x0e\x1a0LO\x8ay\xe4sD\xe3\x90\xfay\x8bYB\xec\x8d\x8c\x0c\xc1\x01b9\ -\xe1\xbc\x8fw\x01\ra\xf0t\xf5sY\xe7\x94\xd0\x04\x00\xb7\x89#\xbb' ) - -def _getSmallUpArrowBitmap(): - stream = cStringIO.StringIO(_getSmallUpArrowData()) - return wx.BitmapFromImage(wx.ImageFromStream(stream)) - -def _getSmallDownArrowData(): - return zlib.decompress( -'x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd2\x02 \xcc\xc1\ -\x06$\xe5?\xffO\x04R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\x07x\xba8\x86X\xf4\ -&\xa7\xa4$\xa5-`1\x08\\R}\x85\x81\r\x04\xc4R\xfcjc\xdf\xd6;II\xcd\x9e%Y\xb8\ -\x8b!v\xd2\x844\x1e\xe6\x0f\x92M\xde2\xd9\x12\x0b\xb4\x8f\xbd6rSK\x9b\xb3c\ -\xe1\xc2\x87\xf6v\x95@&\xdb\xb1\x8bK|v22,W\x12\xd0\xdb-\xc4\xe4\x044\x9b\xc1\ -\xd3\xd5\xcfe\x9dSB\x13\x00$1+:' ) - -def _getSmallDownArrowBitmap(): - stream = cStringIO.StringIO(_getSmallDownArrowData()) - return wx.BitmapFromImage(wx.ImageFromStream(stream)) - - -# -####################################################################### -# TESTING ONLY - -if __name__ == '__main__': - pass diff --git a/odmtools/lib/oldOlv/README.md b/odmtools/lib/oldOlv/README.md deleted file mode 100644 index 3ddd068..0000000 --- a/odmtools/lib/oldOlv/README.md +++ /dev/null @@ -1,6 +0,0 @@ -!!Updated ObjectListView - -* Resolves "NameError: global name 'rowModel' is not defined" -* Resolves "wxPyDeprecationWarning: Accessing deprecated property" warning -* Resolves Mac/Linux undefined row number error -* Removing rows don't flicker as much \ No newline at end of file diff --git a/odmtools/lib/oldOlv/WordWrapRenderer.py b/odmtools/lib/oldOlv/WordWrapRenderer.py deleted file mode 100644 index 4920b3a..0000000 --- a/odmtools/lib/oldOlv/WordWrapRenderer.py +++ /dev/null @@ -1,232 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: WordWrapRenderer.py -# Author: Phillip Piper -# Created: 25 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/25 JPP Initial version -#---------------------------------------------------------------------------- -# To do: - -""" -A WordWrapRenderer encapsulates the ability to draw and measure word wrapped -strings directly to a device context. - -It is meant to be good enough for general use. It is not suitable for typographic layout --- it does not handle kerning or ligatures. - -The DC passed to these methods cannot be a GraphicContext DC. These methods use -GetPartialTextExtents() which does not work with GCDC's (as of wx 2.8). - -""" - -import wx -import bisect -from wx.lib.wordwrap import wordwrap - -class WordWrapRenderer: - """ - This renderer encapsulates the logic need to draw and measure a word-wrapped - string within a given rectangle. - """ - - #---------------------------------------------------------------------------- - # Calculating - - @staticmethod - def CalculateHeight(dc, text, width): - """ - Calculate the height of the given text when fitted within the given width. - - Remember to set the font on the dc before calling this method. - """ - # There is a bug in the wordwrap routine where a string that needs truncated and - # that ends with a single space causes the method to throw an error (wx 2.8). - # Our simple, but not always accurate, is to remove trailing spaces. - # This won't catch single trailing space imbedded in a multiline string. - text = text.rstrip(' ') - - lines = wordwrap(text, width, dc, True) - (width, height, descent, externalLeading) = dc.GetFullTextExtent("Wy") - return (lines.count("\n")+1) * (height + externalLeading) - - - #---------------------------------------------------------------------------- - # Rendering - - @staticmethod - def DrawString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, allowClipping=False): - """ - Draw the given text word-wrapped within the given bounds. - - bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). - - If allowClipping is True, this method changes the clipping region so that no - text is drawn outside of the given bounds. - """ - if not text: - return - - if align == wx.ALIGN_CENTER: - align = wx.ALIGN_CENTER_HORIZONTAL - - if valign == wx.ALIGN_CENTER: - valign = wx.ALIGN_CENTER_VERTICAL - - # DrawLabel only accepts a wx.Rect - try: - bounds = wx.Rect(*bounds) - except: - pass - - if allowClipping: - clipper = wx.DCClipper(dc, bounds) - - # There is a bug in the wordwrap routine where a string that needs truncated and - # that ends with a single space causes the method to throw an error (wx 2.8). - # Our simple, but not always accurate, is to remove trailing spaces. - # This won't catch single trailing space imbedded in a multiline string. - text = text.rstrip(' ') - - lines = wordwrap(text, bounds[2], dc, True) - dc.DrawLabel(lines, bounds, align|valign) - - - @staticmethod - def DrawTruncatedString(dc, text, bounds, align=wx.ALIGN_LEFT, valign=wx.ALIGN_TOP, ellipse=wx.RIGHT, ellipseChars="..."): - """ - Draw the given text truncated to the given bounds. - - bounds must be a wx.Rect or a 4-element collection: (left, top, width, height). - - If allowClipping is True, this method changes the clipping region so that no - text is drawn outside of the given bounds. - """ - if not text: - return - - if align == wx.ALIGN_CENTER: - align = wx.ALIGN_CENTER_HORIZONTAL - - if valign == wx.ALIGN_CENTER: - valign = wx.ALIGN_CENTER_VERTICAL - - try: - bounds = wx.Rect(*bounds) - except: - pass - lines = WordWrapRenderer._Truncate(dc, text, bounds[2], ellipse, ellipseChars) - dc.DrawLabel(lines, bounds, align|valign) - - - @staticmethod - def _Truncate(dc, text, maxWidth, ellipse, ellipseChars): - """ - Return a string that will fit within the given width. - """ - line = text.split("\n")[0] # only consider the first line - if not line: - return "" - - pte = dc.GetPartialTextExtents(line) - - # Does the whole thing fit within our given width? - stringWidth = pte[-1] - if stringWidth <= maxWidth: - return line - - # We (probably) have to ellipse the text so allow for ellipse - maxWidthMinusEllipse = maxWidth - dc.GetTextExtent(ellipseChars)[0] - - if ellipse == wx.LEFT: - i = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse) - return ellipseChars + line[i+1:] - - if ellipse == wx.CENTER: - i = bisect.bisect(pte, maxWidthMinusEllipse / 2) - j = bisect.bisect(pte, stringWidth - maxWidthMinusEllipse / 2) - return line[:i] + ellipseChars + line[j+1:] - - if ellipse == wx.RIGHT: - i = bisect.bisect(pte, maxWidthMinusEllipse) - return line[:i] + ellipseChars - - # No ellipsing, just truncating is the default - i = bisect.bisect(pte, maxWidth) - return line[:i] - -#====================================================================== -# TESTING ONLY -#====================================================================== - -if __name__ == '__main__': - - class TestPanel(wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent, -1, style=wx.FULL_REPAINT_ON_RESIZE) - self.Bind(wx.EVT_PAINT, self.OnPaint) - - self.text = """This is Thisisareallylongwordtoseewhathappens the text to be drawn. It needs to be long to see if wrapping works. to long words. -This is on new line by itself. - -This should have a blank line in front of it but still wrap when we reach the edge. - -The bottom of the red rectangle should be immediately below this.""" - self.font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL, faceName="Gill Sans") - - def OnPaint(self, evt): - dc = wx.PaintDC(self) - inset = (20, 20, 20, 20) - rect = [inset[0], inset[1], self.GetSize().width-(inset[0]+inset[2]), self.GetSize().height-(inset[1]+inset[3])] - - # Calculate exactly how high the wrapped is going to be and put a frame around it. - dc.SetFont(self.font) - dc.SetPen(wx.RED_PEN) - rect[3] = WordWrapRenderer.CalculateHeight(dc, self.text, rect[2]) - dc.DrawRectangle(*rect) - WordWrapRenderer.DrawString(dc, self.text, rect, wx.ALIGN_LEFT) - #WordWrapRenderer.DrawTruncatedString(dc, self.text, rect, wx.ALIGN_CENTER_HORIZONTAL,s ellipse=wx.CENTER) - - #bmp = wx.EmptyBitmap(rect[0]+rect[2], rect[1]+rect[3]) - #mdc = wx.MemoryDC(bmp) - #mdc.SetBackground(wx.Brush("white")) - #mdc.Clear() - #mdc.SetFont(self.font) - #mdc.SetPen(wx.RED_PEN) - #rect[3] = WordWrapRenderer.CalculateHeight(mdc, self.text, rect[2]) - #mdc.DrawRectangle(*rect) - #WordWrapRenderer.DrawString(mdc, self.text, rect, wx.ALIGN_LEFT) - #del mdc - #dc = wx.ScreenDC() - #dc.DrawBitmap(bmp, 20, 20) - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - self.testPanel = TestPanel(self.panel) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.testPanel, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() diff --git a/odmtools/lib/oldOlv/__init__.py b/odmtools/lib/oldOlv/__init__.py deleted file mode 100644 index 433e1b5..0000000 --- a/odmtools/lib/oldOlv/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -# -*- coding: utf-8 -*- -#---------------------------------------------------------------------------- -# Name: ObjectListView module initialization -# Author: Phillip Piper -# Created: 29 February 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/08/02 JPP Added list printing material -# 2008/07/24 JPP Added list group related material -# 2008/06/19 JPP Added sort event related material -# 2008/04/11 JPP Initial Version - -""" -An ObjectListView provides a more convienent and powerful interface to a ListCtrl. -""" - -__version__ = '1.2' -__copyright__ = "Copyright (c) 2008 Phillip Piper (phillip_piper@bigfoot.com)" - -from ObjectListView import ObjectListView, VirtualObjectListView, ColumnDefn, FastObjectListView, GroupListView, ListGroup, BatchedUpdate -from OLVEvent import CellEditFinishedEvent, CellEditFinishingEvent, CellEditStartedEvent, CellEditStartingEvent, SortEvent -from OLVEvent import EVT_CELL_EDIT_STARTING, EVT_CELL_EDIT_STARTED, EVT_CELL_EDIT_FINISHING, EVT_CELL_EDIT_FINISHED, EVT_SORT -from OLVEvent import EVT_COLLAPSING, EVT_COLLAPSED, EVT_EXPANDING, EVT_EXPANDED, EVT_GROUP_CREATING, EVT_GROUP_SORT -from CellEditor import CellEditorRegistry, MakeAutoCompleteTextBox, MakeAutoCompleteComboBox -from ListCtrlPrinter import ListCtrlPrinter, ReportFormat, BlockFormat, LineDecoration, RectangleDecoration, ImageDecoration - -import Filter -__all__ = [ - "BatchedUpdate", - "BlockFormat", - "CellEditFinishedEvent", - "CellEditFinishingEvent", - "CellEditorRegistry", - "CellEditStartedEvent", - "CellEditStartingEvent", - "ColumnDefn", - "EVT_CELL_EDIT_FINISHED", - "EVT_CELL_EDIT_FINISHING", - "EVT_CELL_EDIT_STARTED", - "EVT_CELL_EDIT_STARTING", - "EVT_COLLAPSED", - "EVT_COLLAPSING", - "EVT_EXPANDED", - "EVT_EXPANDING", - "EVT_GROUP_CREATING", - "EVT_GROUP_SORT" - "EVT_SORT", - "Filter", - "FastObjectListView", - "GroupListView", - "ListGroup", - "ImageDecoration", - "MakeAutoCompleteTextBox", - "MakeAutoCompleteComboBox", - "ListGroup", - "ObjectListView", - "ListCtrlPrinter", - "RectangleDecoration", - "ReportFormat", - "SortEvent", - "VirtualObjectListView", -] diff --git a/odmtools/lib/oldOlv/t.py b/odmtools/lib/oldOlv/t.py deleted file mode 100644 index a54cdd1..0000000 --- a/odmtools/lib/oldOlv/t.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -#!/usr/bin/env python -#---------------------------------------------------------------------------- -# Name: ListCtrlPrinter.py -# Author: Phillip Piper -# Created: 17 July 2008 -# SVN-ID: $Id$ -# Copyright: (c) 2008 by Phillip Piper, 2008 -# License: wxWindows license -#---------------------------------------------------------------------------- -# Change log: -# 2008/07/17 JPP Initial version -#---------------------------------------------------------------------------- -# To do: -# - scaling -# - gradients -# - images -# - attributes from ListCtrl -# - persistence of ReportFormat -# - use wx.wordwrap and DrawLabel -# - investigate DrawImageLabel - -""" -An ListCtrlPrinter takes an ObjectListView and turns it into a pretty report. - -As always, the goal is for this to be as easy to use as possible. A typical -usage should be as simple as:: - - printer = ListCtrlPrinter(self.myOlv, "My Report Title") - printer.PrintPreview() - -""" - -import wx - -#====================================================================== - -class TestPrinter(wx.Printout): - - def __init__(self, margins=None): - """ - """ - wx.Printout.__init__(self, "The title") - - self.printData = wx.PrintData() - self.printData.SetPaperId(wx.PAPER_A4) - self.printData.SetOrientation(wx.PORTRAIT) - self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER) - self.printData.SetNoCopies(1) - self.margins = margins or (wx.Point(0, 0), wx.Point(0, 0)) - - - #---------------------------------------------------------------------------- - # Accessing - - def HasPage(self, page): - return page <= 3 - - def GetPageInfo(self): - return (1, 3, 1, 1) - - #---------------------------------------------------------------------------- - # Commands - - - def PageSetup(self): - data = wx.PageSetupDialogData() - data.SetPrintData(self.printData) - data.SetDefaultMinMargins(True) - data.SetMarginTopLeft(self.margins[0]) - data.SetMarginBottomRight(self.margins[1]) - dlg = wx.PageSetupDialog(None, data) - if dlg.ShowModal() == wx.ID_OK: - data = dlg.GetPageSetupData() - self.printData = wx.PrintData(data.GetPrintData()) - self.printData.SetPaperId(data.GetPaperId()) - self.margins = (data.GetMarginTopLeft(), - data.GetMarginBottomRight()) - dlg.Destroy() - - def PrintPreview(self, parent=None, title="ObjectListView Print Preview", bounds=(20, 50, 800, 800)): - """ - Show a Print Preview of this report - """ - data = wx.PrintDialogData(self.printData) - t = TestPrinter(self.margins) - t2 = TestPrinter(self.margins) - self.preview = wx.PrintPreview(t, t2, data) - - if not self.preview.Ok(): - return False - - pfrm = wx.PreviewFrame(self.preview, parent, title) - - pfrm.Initialize() - pfrm.SetPosition(bounds[0:2]) - pfrm.SetSize(bounds[2:4]) - pfrm.Show(True) - - return True - - - def DoPrint(self, parent=None): - """ - Send the report to the configured printer - """ - pdd = wx.PrintDialogData(self.printData) - printer = wx.Printer(pdd) - - if printer.Print(parent, self, True): - self.printData = wx.PrintData(printer.GetPrintDialogData().GetPrintData()) - else: - wx.MessageBox("There was a problem printing.\nPerhaps your current printer is not set correctly?", "Printing", wx.OK) - - printout.Destroy() - - - #---------------------------------------------------------------------------- - # Event handlers - - def OnPreparePrinting(self): - """ - Prepare for printing. This event is sent before any of the others - """ - print "OnPreparePrinting" - print "self.GetDC() = %s" % self.GetDC() - - def OnBeginDocument(self, start, end): - """ - Begin printing one copy of the document. Return False to cancel the job - """ - print "OnBeginDocument(%d, %d)" % (start, end) - if not super(TestPrinter, self).OnBeginDocument(start, end): - return False - - return True - - def OnEndDocument(self): - print "OnEndDocument" - super(TestPrinter, self).OnEndDocument() - - def OnBeginPrinting(self): - print "OnBeginPrinting" - super(TestPrinter, self).OnBeginPrinting() - - def OnEndPrinting(self): - print "OnEndPrinting" - super(TestPrinter, self).OnEndPrinting() - - def OnPrintPage(self, page): - print "OnPrintPage(%d)" % page - dc = self.GetDC() - self.CalculateScale(dc) - self.CalculateLayout(dc) - dc.SetPen(wx.BLACK_PEN) - dc.SetBrush(wx.TRANSPARENT_BRUSH) - bounds = (self.x1, self.y1, self.x2-self.x1, self.y2-self.y1) - print bounds - print self.pageHeight - dc.DrawRectangle(*bounds) - font = wx.Font(10, wx.TELETYPE, wx.NORMAL, wx.NORMAL) - dc.SetFont(font) - dc.DrawText("this is a string", bounds[0], bounds[1]) - - def CalculateScale(self, dc): - # Scaling the DC to screen size - ppiPrinterX, ppiPrinterY = self.GetPPIPrinter() - ppiScreenX, ppiScreenY = self.GetPPIScreen() - logScale = float(ppiPrinterX)/float(ppiScreenX) - pw, ph = self.GetPageSizePixels() # Adjusting scale - dw, dh = dc.GetSize() - scale = logScale * float(dw)/float(pw) - dc.SetUserScale(scale, scale) - self.logUnitsMM = float(ppiPrinterX)/(logScale*25.4) - - def CalculateLayout(self, dc): - topLeft, bottomRight = self.margins - dw, dh = dc.GetSize() - self.x1 = topLeft.x * self.logUnitsMM - self.y1 = topLeft.y * self.logUnitsMM - self.x2 = dc.DeviceToLogicalYRel(dw) - bottomRight.x * self.logUnitsMM - self.y2 = dc.DeviceToLogicalYRel(dh) - bottomRight.y * self.logUnitsMM - self.pageHeight = self.y2 - self.y1 - 2*self.logUnitsMM - - - -#====================================================================== -# TESTING ONLY -#====================================================================== - -if __name__ == '__main__': - import wx - - # Where can we find the Example module? - import sys - - class MyFrame(wx.Frame): - def __init__(self, *args, **kwds): - kwds["style"] = wx.DEFAULT_FRAME_STYLE - wx.Frame.__init__(self, *args, **kwds) - - self.panel = wx.Panel(self, -1) - self.olv = wx.ListCtrl(self.panel, -1, style=wx.LC_REPORT|wx.SUNKEN_BORDER) - - sizer_2 = wx.BoxSizer(wx.VERTICAL) - sizer_2.Add(self.olv, 1, wx.ALL|wx.EXPAND, 4) - self.panel.SetSizer(sizer_2) - self.panel.Layout() - - sizer_1 = wx.BoxSizer(wx.VERTICAL) - sizer_1.Add(self.panel, 1, wx.EXPAND) - self.SetSizer(sizer_1) - self.Layout() - - wx.CallLater(50, self.run) - - def run(self): - printer = TestPrinter() - printer.PageSetup() - printer.PrintPreview(self) - - - app = wx.PySimpleApp(0) - wx.InitAllImageHandlers() - frame_1 = MyFrame(None, -1, "") - app.SetTopWindow(frame_1) - frame_1.Show() - app.MainLoop() diff --git a/odmtools/lib/oldOlv/timingTests.txt b/odmtools/lib/oldOlv/timingTests.txt deleted file mode 100644 index 1e80d0d..0000000 --- a/odmtools/lib/oldOlv/timingTests.txt +++ /dev/null @@ -1,245 +0,0 @@ -ListCtrlPrinter CalculateTotalPages 1862.3 ms -Tue Aug 05 10:50:53 2008 app.prof - - 267006 function calls in 1.742 CPU seconds - - Ordered by: internal time, call count - List reduced from 209 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 12584 0.211 0.000 0.291 0.000 _controls.py:4521(GetItem) - 18920 0.084 0.000 0.120 0.000 _controls.py:4178() - 6336 0.082 0.000 0.122 0.000 _controls.py:4491(GetColumn) - 3167 0.079 0.000 0.495 0.000 ListCtrlPrinter.py:1140(DrawText) - 305 0.078 0.000 1.064 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 3167 0.066 0.000 0.066 0.000 _gdi.py:3585(DrawLabel) - 572 0.061 0.000 0.245 0.000 ListCtrlPrinter.py:2027(GetTexts) - 572 0.061 0.000 0.237 0.000 ListCtrlPrinter.py:2039(GetImages) - 572 0.060 0.000 0.212 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 610 0.051 0.000 0.885 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 3163 0.049 0.000 0.260 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 3209 0.045 0.000 0.048 0.000 _gdi.py:3791(GetPartialTextExtents) - 18920 0.036 0.000 0.036 0.000 {method 'own' of 'PySwigObject' objects} - 18920 0.036 0.000 0.036 0.000 _controls.py:4184() - 6325 0.036 0.000 0.036 0.000 _controls.py:4501(GetColumnWidth) - 6288 0.033 0.000 0.102 0.000 ListCtrlPrinter.py:962(GetFormat) - 3163 0.030 0.000 0.098 0.000 WordWrapRenderer.py:113(_Truncate) - 6324 0.030 0.000 0.069 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.027 0.000 0.042 0.000 ListCtrlPrinter.py:2304(__init__) - 6324 0.025 0.000 0.039 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 6314 0.025 0.000 0.025 0.000 _controls.py:4261(GetText) - 3080 0.023 0.000 0.079 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 574 0.021 0.000 0.058 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 3168 0.018 0.000 0.025 0.000 _core.py:1133(__init__) - 6314 0.018 0.000 0.018 0.000 _controls.py:4265(GetImage) - 6292 0.018 0.000 0.018 0.000 _controls.py:4277(GetAlign) - 3179 0.016 0.000 0.016 0.000 _gdi.py:4022(SetTextForeground) - 6422 0.015 0.000 0.015 0.000 {method 'update' of 'dict' objects} - 3167 0.014 0.000 0.022 0.000 _core.py:1440(__getitem__) - 6324 0.014 0.000 0.014 0.000 {getattr} - -ListCtrlPrinter CalculateTotalPages 1770.4 ms -Tue Aug 05 11:11:21 2008 app.prof - - 266950 function calls in 1.722 CPU seconds - - Ordered by: internal time, call count - List reduced from 209 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 12584 0.208 0.000 0.288 0.000 _controls.py:4521(GetItem) - 3167 0.083 0.000 0.489 0.000 ListCtrlPrinter.py:1140(DrawText) - 18920 0.083 0.000 0.119 0.000 _controls.py:4178() - 6336 0.081 0.000 0.121 0.000 _controls.py:4491(GetColumn) - 305 0.078 0.000 1.058 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 3167 0.065 0.000 0.065 0.000 _gdi.py:3585(DrawLabel) - 572 0.061 0.000 0.236 0.000 ListCtrlPrinter.py:2041(GetImages) - 572 0.061 0.000 0.238 0.000 ListCtrlPrinter.py:2027(GetTexts) - 572 0.059 0.000 0.209 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 610 0.050 0.000 0.874 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 3163 0.046 0.000 0.251 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 3213 0.038 0.000 0.040 0.000 _gdi.py:3791(GetPartialTextExtents) - 18920 0.036 0.000 0.036 0.000 {method 'own' of 'PySwigObject' objects} - 18920 0.036 0.000 0.036 0.000 _controls.py:4184() - 6325 0.035 0.000 0.035 0.000 _controls.py:4501(GetColumnWidth) - 6288 0.034 0.000 0.100 0.000 ListCtrlPrinter.py:962(GetFormat) - 3163 0.030 0.000 0.092 0.000 WordWrapRenderer.py:113(_Truncate) - 6324 0.028 0.000 0.067 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.027 0.000 0.042 0.000 ListCtrlPrinter.py:2308(__init__) - 6324 0.025 0.000 0.039 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 3080 0.022 0.000 0.077 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 574 0.021 0.000 0.057 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 6314 0.019 0.000 0.019 0.000 _controls.py:4261(GetText) - 6314 0.019 0.000 0.019 0.000 _controls.py:4265(GetImage) - 3168 0.019 0.000 0.025 0.000 _core.py:1133(__init__) - 6292 0.017 0.000 0.017 0.000 _controls.py:4277(GetAlign) - 3179 0.016 0.000 0.016 0.000 _gdi.py:4022(SetTextForeground) - 6422 0.014 0.000 0.014 0.000 {method 'update' of 'dict' objects} - 3167 0.014 0.000 0.022 0.000 _core.py:1440(__getitem__) - 6324 0.014 0.000 0.014 0.000 {getattr} - - - -ListCtrlPrinter CalculateTotalPages 1855.8 ms -Tue Aug 05 10:56:46 2008 app.prof - - 294965 function calls in 1.805 CPU seconds - - Ordered by: internal time, call count - List reduced from 232 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 6336 0.084 0.000 0.126 0.000 _controls.py:4491(GetColumn) - 3167 0.079 0.000 0.560 0.000 ListCtrlPrinter.py:1140(DrawText) - 3167 0.078 0.000 0.097 0.000 _gdi.py:3585(DrawLabel) - 305 0.073 0.000 1.132 0.004 ListCtrlPrinter.py:1411(DrawSelf) - 6292 0.061 0.000 0.134 0.000 ObjectListView.py:929(GetImageAt) - 3224 0.060 0.000 0.083 0.000 _gdi.py:3791(GetPartialTextExtents) - 572 0.060 0.000 0.215 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 7436 0.059 0.000 0.081 0.000 ObjectListView.py:3420(_Munge) - 610 0.051 0.000 0.879 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 6292 0.050 0.000 0.246 0.000 ObjectListView.py:3274(GetStringValue) - 3163 0.048 0.000 0.324 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 6292 0.045 0.000 0.096 0.000 ObjectListView.py:3282(_StringToValue) - 6325 0.036 0.000 0.036 0.000 _controls.py:4501(GetColumnWidth) - 6292 0.035 0.000 0.100 0.000 ObjectListView.py:3267(GetValue) - 6288 0.033 0.000 0.106 0.000 ListCtrlPrinter.py:962(GetFormat) - 6324 0.032 0.000 0.046 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 6292 0.030 0.000 0.276 0.000 ObjectListView.py:1028(GetStringValueAt) - 3163 0.030 0.000 0.131 0.000 WordWrapRenderer.py:113(_Truncate) - 13760 0.029 0.000 0.029 0.000 {getattr} - 6924 0.029 0.000 0.051 0.000 cp1252.py:14(decode) - 6336 0.029 0.000 0.042 0.000 _controls.py:4178() - 6324 0.027 0.000 0.073 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.026 0.000 0.041 0.000 ListCtrlPrinter.py:2308(__init__) - 572 0.024 0.000 0.309 0.001 ListCtrlPrinter.py:2027(GetTexts) - 1188 0.023 0.000 0.023 0.000 {method 'strftime' of 'datetime.date' objects} - 3080 0.023 0.000 0.078 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 572 0.022 0.000 0.165 0.000 ListCtrlPrinter.py:2041(GetImages) - 6292 0.021 0.000 0.041 0.000 ObjectListView.py:3349(GetImage) - 6924 0.021 0.000 0.021 0.000 {_codecs.charmap_decode} - 574 0.021 0.000 0.058 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - -ListCtrlPrinter CalculateTotalPages 1700.5 ms -Tue Aug 05 11:14:24 2008 app.prof - - 275373 function calls in 1.665 CPU seconds - - Ordered by: internal time, call count - List reduced from 231 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 3167 0.078 0.000 0.564 0.000 ListCtrlPrinter.py:1140(DrawText) - 3167 0.076 0.000 0.095 0.000 _gdi.py:3585(DrawLabel) - 305 0.075 0.000 1.064 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 7436 0.061 0.000 0.083 0.000 ObjectListView.py:3420(_Munge) - 6292 0.060 0.000 0.135 0.000 ObjectListView.py:929(GetImageAt) - 3209 0.055 0.000 0.084 0.000 _gdi.py:3791(GetPartialTextExtents) - 610 0.052 0.000 0.743 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 6292 0.051 0.000 0.253 0.000 ObjectListView.py:3274(GetStringValue) - 3163 0.049 0.000 0.324 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 6292 0.045 0.000 0.099 0.000 ObjectListView.py:3282(_StringToValue) - 6325 0.036 0.000 0.036 0.000 _controls.py:4501(GetColumnWidth) - 6292 0.036 0.000 0.102 0.000 ObjectListView.py:3267(GetValue) - 6905 0.035 0.000 0.056 0.000 cp1252.py:14(decode) - 6292 0.034 0.000 0.045 0.000 ObjectListView.py:3241(GetAlignment) - 6288 0.033 0.000 0.102 0.000 ListCtrlPrinter.py:962(GetFormat) - 6292 0.030 0.000 0.283 0.000 ObjectListView.py:1028(GetStringValueAt) - 3163 0.030 0.000 0.132 0.000 WordWrapRenderer.py:113(_Truncate) - 13760 0.029 0.000 0.029 0.000 {getattr} - 6324 0.029 0.000 0.069 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 6422 0.026 0.000 0.041 0.000 ListCtrlPrinter.py:2309(__init__) - 6324 0.026 0.000 0.040 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 572 0.025 0.000 0.316 0.001 ListCtrlPrinter.py:2027(GetTexts) - 3080 0.023 0.000 0.081 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 572 0.023 0.000 0.166 0.000 ListCtrlPrinter.py:2042(GetImages) - 572 0.023 0.000 0.070 0.000 ListCtrlPrinter.py:2035(GetAlignments) - 574 0.022 0.000 0.060 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 1188 0.022 0.000 0.022 0.000 {method 'strftime' of 'datetime.date' objects} - 6292 0.021 0.000 0.042 0.000 ObjectListView.py:3349(GetImage) - 6905 0.021 0.000 0.021 0.000 {_codecs.charmap_decode} - 9724 0.020 0.000 0.020 0.000 {isinstance} - -#====================================================================== - -FASTLIST 10,000 rows - -ListCtrlPrinter CalculateTotalPages 142273.9 ms -Tue Aug 05 11:23:54 2008 app.prof - - 22351842 function calls in 142.228 CPU seconds - - Ordered by: internal time, call count - List reduced from 214 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 507298 19.119 0.000 70.122 0.000 _controls.py:4521(GetItem) - 507298 5.051 0.000 11.103 0.000 ObjectListView.py:929(GetImageAt) - 599534 5.027 0.000 6.828 0.000 ObjectListView.py:3420(_Munge) - 507298 4.048 0.000 20.396 0.000 ObjectListView.py:3274(GetStringValue) - 507298 3.718 0.000 7.838 0.000 ObjectListView.py:3282(_StringToValue) - 507298 3.444 0.000 28.113 0.000 ObjectListView.py:2116(OnGetItemText) - 760991 3.437 0.000 4.925 0.000 _controls.py:4178() - 253693 3.416 0.000 5.045 0.000 _controls.py:4491(GetColumn) - 507298 3.400 0.000 16.120 0.000 ObjectListView.py:2130(OnGetItemColumnImage) - 1014596 3.338 0.000 3.396 0.000 ObjectListView.py:2155(GetObjectAt) - 25042 3.274 0.000 75.245 0.003 ListCtrlPrinter.py:1411(DrawSelf) - 131137 3.253 0.000 21.029 0.000 ListCtrlPrinter.py:1140(DrawText) - 507298 2.930 0.000 8.510 0.000 ObjectListView.py:3267(GetValue) - 50082 2.834 0.000 97.934 0.002 ListCtrlPrinter.py:1269(GetCombinedLists) - 46118 2.810 0.000 39.491 0.001 ListCtrlPrinter.py:2027(GetTexts) - 46118 2.783 0.000 38.978 0.001 ListCtrlPrinter.py:2039(GetImages) - 46118 2.677 0.000 9.037 0.000 ListCtrlPrinter.py:1310(GetColumnAlignments) - 131137 2.601 0.000 2.601 0.000 _gdi.py:3585(DrawLabel) - 507298 2.494 0.000 22.890 0.000 ObjectListView.py:1028(GetStringValueAt) - 533378 2.348 0.000 4.062 0.000 cp1252.py:14(decode) - 990365 2.132 0.000 2.132 0.000 {getattr} - 386871 2.069 0.000 6.193 0.000 ListCtrlPrinter.py:962(GetFormat) - 131131 1.995 0.000 10.308 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 96209 1.921 0.000 1.921 0.000 {method 'strftime' of 'datetime.date' objects} - 507298 1.777 0.000 3.327 0.000 ObjectListView.py:3349(GetImage) - 144064 1.770 0.000 1.971 0.000 _gdi.py:3791(GetPartialTextExtents) - 157504 1.752 0.000 1.752 0.000 _gdi.py:3659(SetFont) - 390831 1.744 0.000 4.167 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 784006 1.722 0.000 1.722 0.000 {isinstance} - 533378 1.714 0.000 1.714 0.000 {_codecs.charmap_decode} - -ListCtrlPrinter CalculateTotalPages 79258.0 ms -Tue Aug 05 11:26:44 2008 app.prof - - 13485917 function calls in 79.211 CPU seconds - - Ordered by: internal time, call count - List reduced from 210 to 30 due to restriction <30> - - ncalls tottime percall cumtime percall filename:lineno(function) - 25042 3.295 0.000 45.882 0.002 ListCtrlPrinter.py:1411(DrawSelf) - 131137 3.253 0.000 23.338 0.000 ListCtrlPrinter.py:1140(DrawText) - 131137 2.955 0.000 3.753 0.000 _gdi.py:3585(DrawLabel) - 50082 2.806 0.000 32.646 0.001 ListCtrlPrinter.py:1269(GetCombinedLists) - 253649 2.452 0.000 5.388 0.000 ObjectListView.py:929(GetImageAt) - 299767 2.376 0.000 3.247 0.000 ObjectListView.py:3420(_Munge) - 144264 2.206 0.000 3.250 0.000 _gdi.py:3791(GetPartialTextExtents) - 386871 2.045 0.000 6.129 0.000 ListCtrlPrinter.py:962(GetFormat) - 131131 2.019 0.000 12.593 0.000 WordWrapRenderer.py:95(DrawTruncatedString) - 253649 1.997 0.000 9.879 0.000 ObjectListView.py:3274(GetStringValue) - 253649 1.808 0.000 3.832 0.000 ObjectListView.py:3282(_StringToValue) - 157504 1.788 0.000 1.788 0.000 _gdi.py:3659(SetFont) - 390831 1.705 0.000 4.125 0.000 ListCtrlPrinter.py:205(GetNamedFormat) - 390831 1.572 0.000 2.420 0.000 ListCtrlPrinter.py:603(GetNamedFormat) - 253682 1.489 0.000 1.489 0.000 _controls.py:4501(GetColumnWidth) - 690598 1.465 0.000 1.465 0.000 {getattr} - 253649 1.421 0.000 4.050 0.000 ObjectListView.py:3267(GetValue) - 253649 1.390 0.000 1.861 0.000 ObjectListView.py:3241(GetAlignment) - 300016 1.305 0.000 2.212 0.000 cp1252.py:14(decode) - 253649 1.242 0.000 11.121 0.000 ObjectListView.py:1028(GetStringValueAt) - 131131 1.201 0.000 4.871 0.000 WordWrapRenderer.py:113(_Truncate) - 46118 1.195 0.000 12.728 0.000 ListCtrlPrinter.py:2045(GetTexts) - 265551 1.117 0.000 1.741 0.000 ListCtrlPrinter.py:2324(__init__) - 46118 1.091 0.000 6.745 0.000 ListCtrlPrinter.py:2058(GetImages) - 46118 1.082 0.000 3.062 0.000 ListCtrlPrinter.py:2052(GetAlignments) - 101484 1.060 0.000 1.683 0.000 ListCtrlPrinter.py:1355(_CalculateCellPadding) - 46122 0.998 0.000 2.608 0.000 ListCtrlPrinter.py:1323(GetColumnWidths) - 123200 0.936 0.000 3.169 0.000 ListCtrlPrinter.py:1987(GetTextColor) - 50091 0.930 0.000 0.930 0.000 {method 'strftime' of 'datetime.date' objects} - 300016 0.907 0.000 0.907 0.000 {_codecs.charmap_decode} diff --git a/odmtools/meta/data.py b/odmtools/meta/data.py index 8a317c7..997f9fc 100644 --- a/odmtools/meta/data.py +++ b/odmtools/meta/data.py @@ -1,12 +1,12 @@ -app_name = "ODMTools" -version = "1.2.8_Beta" +app_name = "ODM2Tools" +version = "2.0.1_Beta" copyright = "Copyright (c) 2013 - 2015, Utah State University. All rights reserved." description = "ODMTools is a python application for managing observational data using the Observations Data Model. " \ "ODMTools allows you to query, visualize, and edit data stored in an Observations Data Model (ODM) database." \ " ODMTools was originally developed as part of the CUAHSI Hydrologic Information System." developers = ["Jeffery S. Horsburgh", "Amber Spackman Jones", - "Stephanie L. Reeder", "Jacob Meline", "James Patton"] + "Stephanie L. Reeder", "Francisco Arrieta", "Mikaila Young", "Jacob Meline", "James Patton"] license = "This material is copyright (c) 2013 - 2015 Utah State University." \ "\nIt is open and licensed under the New Berkeley Software Distribution (BSD) License. Full text of the license follows." \ @@ -18,5 +18,5 @@ "\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. " -website = ("http://uchic.github.io/ODMToolsPython/", "ODMTools home page") +website = ("http://odm2.github.io/ODMToolsPython/", "ODMTools home page") diff --git a/odmtools/odmdata/__init__.py b/odmtools/odmdata/__init__.py index dfb89a2..f8f5003 100644 --- a/odmtools/odmdata/__init__.py +++ b/odmtools/odmdata/__init__.py @@ -1,68 +1,37 @@ -from base import Base -from censor_code_cv import CensorCodeCV -from data_type_cv import DataTypeCV -from data_value import DataValue -from general_category_cv import GeneralCategoryCV -from iso_metadata import ISOMetadata -from lab_method import LabMethod -from method import Method -from odm_version import ODMVersion -from offset_type import OffsetType -from qualifier import Qualifier -from quality_control_level import QualityControlLevel -from sample import Sample -from sample_medium_cv import SampleMediumCV -from sample_type_cv import SampleTypeCV -from series import Series -from session_factory import SessionFactory -from site import Site -from site_type_cv import SiteTypeCV -from source import Source -from spatial_reference import SpatialReference -from speciation_cv import SpeciationCV -from topic_category_cv import TopicCategoryCV -from unit import Unit -from value_type_cv import ValueTypeCV -from variable import Variable -from variable_name_cv import VariableNameCV -from vertical_datum_cv import VerticalDatumCV -from memory_database import MemoryDatabase -from series import copy_series -from data_value import copy_data_value -__all__ = [ - 'Base', - 'CensorCodeCV', - 'DataTypeCV', - 'DataValue', - 'GeneralCategoryCV', - 'ISOMetadata', - 'LabMethod', - 'Method', - 'ODMVersion', - 'OffsetType', - 'Qualifier', - 'QualityControlLevel', - 'Sample', - 'SampleMediumCV', - 'SampleTypeCV', - 'Series', - 'SessionFactory', - 'Site', - 'SiteTypeCV', - 'Source', - 'SpatialReference', - 'SpeciationCV', - 'TopicCategoryCV', - 'Unit', - 'ValueTypeCV', - 'Variable', - 'VariableNameCV', - 'VerticalDatumCV', - 'MemoryDatabase', - 'copy_series', - 'copy_data_value' -] +from odm2api.ODMconnection import SessionFactory, dbconnection +from odm2api.ODM2.models import _changeSchema as change_schema +from odmtools.odmdata.memory_database import MemoryDatabase +#ODM = SeriesService.ODM +from collections import OrderedDict +def returnDict(): + keys = ['ResultID', 'SamplingFeatureCode', 'SamplingFeatureName', 'MethodCode', 'MethodName', 'VariableCode', 'VariableNameCV', 'ProcessingLevelCode','ProcessingLevelDefinition', 'UnitsName', 'ValueCount', 'BeginDateTime', 'EndDateTime'] + # keys = ['SeriesID', 'SiteID', 'SiteCode', 'SiteName', 'VariableID', 'VariableCode', 'VariableName', 'Speciation', + # 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', + # 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', + # 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', 'QualityControlLevelCode', + # 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' + # ] + # values = ['id', 'site_id', 'site_code', 'site_name', 'variable_id', 'variable_code', 'variable_name', 'speciation', + # 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', + # 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', + # 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', + # 'quality_control_level_code', 'begin_date_time', 'end_date_time', 'begin_date_time_utc', + # 'end_date_time_utc', 'value_count' + # ] + return OrderedDict(zip(keys, keys)) +__all__=[ + #'SessionFactory', + 'refreshDB', + 'change_schema', + #'returnDict', + #'ODM', + 'MemoryDatabase', + 'returnDict' + #'SeriesService' + 'readService', 'createService', 'updateService', 'deleteService' + 'dbconnection' + ] diff --git a/odmtools/odmdata/base.py b/odmtools/odmdata/base.py deleted file mode 100644 index c1da040..0000000 --- a/odmtools/odmdata/base.py +++ /dev/null @@ -1,2 +0,0 @@ -from sqlalchemy.ext.declarative import declarative_base -Base = declarative_base() \ No newline at end of file diff --git a/odmtools/odmdata/censor_code_cv.py b/odmtools/odmdata/censor_code_cv.py deleted file mode 100644 index 254a825..0000000 --- a/odmtools/odmdata/censor_code_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class CensorCodeCV(Base): - __tablename__ = 'CensorCodeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/data_type_cv.py b/odmtools/odmdata/data_type_cv.py deleted file mode 100644 index da23bfe..0000000 --- a/odmtools/odmdata/data_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class DataTypeCV(Base): - __tablename__ = 'DataTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/data_value.py b/odmtools/odmdata/data_value.py deleted file mode 100644 index 687414a..0000000 --- a/odmtools/odmdata/data_value.py +++ /dev/null @@ -1,74 +0,0 @@ -# Declare a mapped class -from sqlalchemy import Column, Integer, Float, DateTime, ForeignKey, String -from sqlalchemy.orm import relationship -from base import Base -from site import Site -from variable import Variable -from qualifier import Qualifier -from method import Method -from source import Source -from quality_control_level import QualityControlLevel -from offset_type import OffsetType -from sample import Sample - -def copy_data_value(from_dv): - new = DataValue() - new.data_value = from_dv.data_value - new.value_accuracy = from_dv.value_accuracy - new.local_date_time = from_dv.local_date_time - new.utc_offset = from_dv.utc_offset - new.date_time_utc = from_dv.date_time_utc - new.site_id = from_dv.site_id - new.variable_id = from_dv.variable_id - new.offset_value = from_dv.offset_value - new.offset_type_id = from_dv.offset_type_id - new.censor_code = from_dv.censor_code - new.qualifier_id = from_dv.qualifier_id - new.method_id = from_dv.method_id - new.source_id = from_dv.source_id - new.sample_id = from_dv.sample_id - new.derived_from_id = from_dv.derived_from_id - new.quality_control_level_id = from_dv.quality_control_level_id - return new - -class DataValue(Base): - __tablename__ = 'DataValues' - - id = Column('ValueID', Integer, primary_key=True) - data_value = Column('DataValue', Float) - value_accuracy = Column('ValueAccuracy', Float) - local_date_time = Column('LocalDateTime', DateTime) - utc_offset = Column('UTCOffset', Float) - date_time_utc = Column('DateTimeUTC', DateTime) - site_id = Column('SiteID', Integer, ForeignKey('Sites.SiteID'), nullable=False) - variable_id = Column('VariableID', Integer, ForeignKey('Variables.VariableID'), nullable=False) - offset_value = Column('OffsetValue', Float) - offset_type_id = Column('OffsetTypeID', Integer, ForeignKey('OffsetTypes.OffsetTypeID')) - censor_code = Column('CensorCode', String) - qualifier_id = Column('QualifierID', Integer, ForeignKey('Qualifiers.QualifierID')) - method_id = Column('MethodID', Integer, ForeignKey('Methods.MethodID'), nullable=False) - source_id = Column('SourceID', Integer, ForeignKey('Sources.SourceID'), nullable=False) - sample_id = Column('SampleID', Integer, ForeignKey('Samples.SampleID')) - derived_from_id = Column('DerivedFromID', Integer) - quality_control_level_id = Column('QualityControlLevelID', Integer, ForeignKey('QualityControlLevels.QualityControlLevelID'), nullable=False) - - # relationships - site = relationship(Site) - variable = relationship(Variable) - method = relationship(Method) - source = relationship(Source) - quality_control_level = relationship(QualityControlLevel) - - qualifier = relationship(Qualifier) - offset_type = relationship(OffsetType) - sample = relationship(Sample) - - def list_repr(self): - return [self.id, self.data_value, self.value_accuracy, self.local_date_time, - self.utc_offset, self.date_time_utc, self.site_id, self.variable_id, - self.offset_value, self.offset_type_id, self.censor_code, self.qualifier_id, - self.method_id, self.source_id, self.sample_id, self.derived_from_id, - self.quality_control_level_id] - - def __repr__(self): - return "" % (self.data_value, self.local_date_time, self.value_accuracy) diff --git a/odmtools/odmdata/general_category_cv.py b/odmtools/odmdata/general_category_cv.py deleted file mode 100644 index e721166..0000000 --- a/odmtools/odmdata/general_category_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class GeneralCategoryCV(Base): - __tablename__ = 'GeneralCategoryCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/iso_metadata.py b/odmtools/odmdata/iso_metadata.py deleted file mode 100644 index cd090d4..0000000 --- a/odmtools/odmdata/iso_metadata.py +++ /dev/null @@ -1,15 +0,0 @@ -from sqlalchemy import Column, Integer, String -from base import Base - -class ISOMetadata(Base): - __tablename__ = 'ISOMetadata' - - id = Column('MetadataID', Integer, primary_key=True) - topic_category = Column('TopicCategory', String, nullable=False) - title = Column('Title', String, nullable=False) - abstract = Column('Abstract', String, nullable=False) - profile_version = Column('ProfileVersion', String, nullable=False) - metadata_link = Column('MetadataLink', String) - - def __repr__(self): - return "" % (self.id, self.topic_category, self.title) \ No newline at end of file diff --git a/odmtools/odmdata/lab_method.py b/odmtools/odmdata/lab_method.py deleted file mode 100644 index 1b933cb..0000000 --- a/odmtools/odmdata/lab_method.py +++ /dev/null @@ -1,15 +0,0 @@ -from sqlalchemy import Column, String, Integer -from base import Base - -class LabMethod(Base): - __tablename__ = 'LabMethods' - - id = Column('LabMethodID', Integer, primary_key=True) - name = Column('LabName', String, nullable=False) - organization = Column('LabOrganization', String, nullable=False) - method_name = Column('LabMethodName', String, nullable=False) - method_description = Column('LabMethodDescription', String, nullable=False) - method_link = Column('LabMethodLink', String) - - def __repr__(self): - return "" % (self.id, self.name, self.organization, self.method_name) \ No newline at end of file diff --git a/odmtools/odmdata/memory_database.py b/odmtools/odmdata/memory_database.py index 34e5f09..e300244 100644 --- a/odmtools/odmdata/memory_database.py +++ b/odmtools/odmdata/memory_database.py @@ -1,18 +1,20 @@ -import timeit import logging + +from sqlalchemy import bindparam + from odmtools.common.logger import LoggerTool -from odmtools.odmservices import SeriesService -from odmtools.odmdata import DataValue -from sqlalchemy import update, bindparam -from odmtools.common.taskServer import TaskServerMP -from multiprocessing import cpu_count, freeze_support - -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) +from odmtools.odmservices import ServiceManager, SeriesService + +# from odmtools.odmdata import SeriesService#ODM +# ODM = SeriesService.ODM +from odm2api.ODM2.models import TimeSeriesResultValues as TSRV +from odm2api.ODM2.models import setSchema +import pandas as pd + logger =logging.getLogger('main') class MemoryDatabase(object): - ### this code should be changed to work with the database abstract layer so that sql queries are not in the code + ## this code should be changed to work with the database abstract layer so that sql queries are not in the code # series_service is a SeriesService def __init__(self, taskserver=None): @@ -21,9 +23,14 @@ def __init__(self, taskserver=None): self.df = None # Series_Service handles remote database self.series_service = None + self.results_annotations = None # Memory_service handles in memory database - self.mem_service = SeriesService("sqlite:///:memory:") + sm = ServiceManager() + self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + + setSchema(self.mem_service._session_factory.engine) + # TODO clean up closing of program # if taskserver is None: #numproc = cpu_count() @@ -31,14 +38,19 @@ def __init__(self, taskserver=None): #else: self.taskserver = taskserver + self.annotation_list = pd.DataFrame() + #self.annotation_list = pd.DataFrame() columns =['ResultID', 'ValueDateTime', 'ValueID', 'AnnotationID') + #send in engine def reset_edit(self): - self.mem_service = SeriesService("sqlite:///:memory:") + sm = ServiceManager() + self.mem_service = sm.get_series_service(conn_string="sqlite:///:memory:") + self.annotation_list = pd.DataFrame() + setSchema(self.mem_service._session_factory.engine) def set_series_service(self, service): self.series_service = service - ############## # DB Queries ############## @@ -53,12 +65,29 @@ def getDataValuesDF(self): #else: # self.updateDF() ''' + # TODO: fix me! this commit location is only temoporarily. should be flushing so that we can restore + self.mem_service._session.commit() + setSchema(self.mem_service._session_factory.engine) self.updateDF() # pick up thread here before it is needed logging.debug("done updating memory dataframe") return self.df + def get_annotations(self, query_db_again=False): + # self.mem_service._session.commit() + setSchema(self.series_service._session_factory.engine) + if self.results_annotations is None or query_db_again: + result_id = self.df.resultid[0] + annotation = self.series_service.get_annotations_by_result(resultid=result_id) + self.results_annotations = annotation + + return self.results_annotations + + def getDataValues(self): + # TODO: fix me! this commit location is only temoporarily. should be flushing so that we can restore + self.mem_service._session.commit() + setSchema(self.mem_service._session_factory.engine) return self.mem_service.get_all_values() def getEditRowCount(self): @@ -67,16 +96,32 @@ def getEditRowCount(self): def getEditColumns(self): columns = [] tmp_columns = self.df.columns.tolist() - tmp_columns.remove('DataValue') - tmp_columns.remove('LocalDateTime') - tmp_columns.remove('QualifierID') - columns.append('DataValue') - columns.append('LocalDateTime') - columns.append('QualifierID') + columns.extend(tmp_columns) return [(x, i) for (i, x) in enumerate(columns)] # return [(x, i) for (i, x) in enumerate(self.df.columns)] + def get_columns_with_annotations(self): + """ + If results_annotations has not been set then + :return: + """ + + if self.results_annotations is None or self.df is None: + print "self.df and self.results_annotations must be a pandas dataframe. Currently they are None" + return [] + + columns = [] + columns.extend(self.df.columns.tolist()) + + annotation_columns = self.results_annotations.columns.tolist() + index = annotation_columns.index("annotationcode") + annotation_code_column = annotation_columns[index] + + columns.append(annotation_code_column) + + return [(x, i) for (i, x) in enumerate(columns)] + def getDataValuesforGraph(self, seriesID, noDataValue, startDate=None, endDate=None): return self.series_service.get_plot_values(seriesID, noDataValue, startDate, endDate) @@ -84,10 +129,11 @@ def getEditDataValuesforGraph(self): return self.mem_service.get_all_plot_values() def commit(self): - self.mem_service._edit_session.commit() + self.mem_service._session.commit() + # self.mem_service._session.commit() def rollback(self): - self.mem_service._edit_session.rollback() + self.mem_service._session.rollback() # self.mem_service._session_factory.engine.connect().connection.rollback() #self.updateDF() @@ -100,13 +146,14 @@ def update(self, updates): ''' updates : list of dictionary that contains 2 items, id and value ''' + setSchema(self.mem_service._session_factory.engine) + stmt = (TSRV.__table__.update(). + where(TSRV.ValueDateTime == bindparam('id')). + values(datavalue=bindparam('value')) + ) - stmt = (DataValue.__table__.update(). - where(DataValue.local_date_time == bindparam('id')). - values(DataValue=bindparam('value')) - ) - - self.mem_service._edit_session.execute(stmt, updates) + self.mem_service._session.execute(stmt, updates) + #self.mem_service._session.query(TSRV).filter_by # self.updateDF() @@ -114,20 +161,22 @@ def update(self, updates): def updateValue(self, ids, operator, value): # query = DataValue.data_value+value if operator == '+': - query = DataValue.data_value + value + query = TSRV.DataValue + value elif operator == '-': - query = DataValue.data_value - value + query = TSRV.DataValue - value elif operator == '*': - query = DataValue.data_value * value + query = TSRV.DataValue * value elif operator == '=': query = value #break into chunks to get around sqlites restriction. allowing user to send in only 999 arguments at once chunks=self.chunking(ids) + setSchema(self.mem_service._session_factory.engine) for c in chunks: - q=self.mem_service._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(c)) - q.update({DataValue.data_value: query}, False) + q=self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c)) + q.update({TSRV.DataValue: query}, False) + #self.updateDF() @@ -139,11 +188,31 @@ def chunking(self, data): #break into chunks to get around sqlite's restriction. allowing user to send in only 999 arguments at once + + def updateFlag(self, ids, value): - chunks=self.chunking(ids) - for c in chunks: - self.mem_service._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(c))\ - .update({DataValue.qualifier_id: value}, False) + + + flags = pd.DataFrame(columns = ['annotationid', 'valuedatetime', 'resultid', 'valueid']) + flags["valuedatetime"] = ids + flags["annotationid"] = value + flags["resultid"] = self.series.ResultID + flags["valueid"] = None + + + #what if the column already exists + # chunks=self.chunking(ids) + # for c in chunks: + # # add entry in the Timeseriesresultvalueannotations table + # self.mem_service._session.query(TSRV).filter(TSRV.ValueDateTime.in_(c))\ + # .update({TSRV.qualifier_id: value}, False) + + frames = [self.annotation_list, flags] + self.annotation_list=pd.concat(frames) + print self.annotation_list + #todo: remove duplicates before saving + + def delete(self, ids): @@ -157,22 +226,25 @@ def addPoints(self, points): """ Takes in a list of points and loads each point into the database """ - stmt = DataValue.__table__.insert() + stmt = TSRV.__table__.insert() if not isinstance(points, list): points = [points] for point in points: - vals = {"DataValue": point[0], "ValueAccuracy": point[1], - "LocalDateTime": point[2], "UTCOffset": point[3], - "DateTimeUTC": point[4], "OffsetValue": point[5], - "OffsetTypeID": point[6], "CensorCode": point[7], - "QualifierID": point[8], "SampleID": point[9], - "SiteID": point[10], "VariableID": point[11], - "MethodID": point[12], "SourceID": point[13], - "QualityControlLevelID": point[14]} - self.mem_service._edit_session.execute(stmt, vals) - + vals = {"datavalue": point[0], "valuedatetime": point[1], + "valuedatetimeutcoffset": point[3], + "censorcodecv": point[4], "qualitycodecv": point[5], + "timeaggregationinterval": point[6], "timeaggregationintervalunitsid": point[7], + "resultid":self.df["resultid"][0] + # todo: Add annotations + } + if point[8] != 'NULL': + print point[8] + self.updateFlag([point[1]], [point[8]]) + + setSchema(self.mem_service._session_factory.engine) + self.mem_service._session.execute(stmt, vals) def stopEdit(self): self.editLoaded = False @@ -193,7 +265,8 @@ def updateDF(self): self.taskserver.processTasks() else: ''' - self.df = self.mem_service.get_all_values_df() + self.df = self.mem_service.get_values() + def initEditValues(self, seriesID): """ @@ -201,9 +274,12 @@ def initEditValues(self, seriesID): :return: nothing """ if not self.editLoaded: + logger.debug("Load series from db") - self.series = self.series_service.get_series_by_id(seriesID) - self.df = self.series_service.get_values_by_series(seriesID) + + self.series = self.series_service.get_series(seriesID) + self.df = self.series_service.get_values(series_id=seriesID) + self.editLoaded = True ''' @@ -214,33 +290,12 @@ def initEditValues(self, seriesID): # self.conn = results["InitEditValues"] else: ''' #TODO: Thread this call - if len(self.df)>0: - self.df.to_sql(name="DataValues", if_exists='replace', con=self.mem_service._session_factory.engine, + if self.df is not None and len(self.df)<=0: + logger.debug("no data in series") + else: + setSchema(self.mem_service._session_factory.engine) + self.df.to_sql(name="timeseriesresultvalues", if_exists='replace', con=self.mem_service._session_factory.engine, index=False)#,flavor='sqlite', chunksize=10000) logger.debug("done loading database") - else: - logger.debug("no data in series") - - def changeSeriesIDs(self, var=None, qcl=None, method=None): - """ - - :param var: - :param qcl: - :param method: - :return: - """ - query = self.mem_service._edit_session.query(DataValue) - if var is not None: - logger.debug(var) - query.update({DataValue.variable_id: var}) - - if method is not None: - logger.debug(method) - query.update({DataValue.method_id: method}) - # check that the code is not zero - # if qcl is not None and qcl.code != 0: - if qcl is not None: - logger.debug(qcl) - query.update({DataValue.quality_control_level_id: qcl}) diff --git a/odmtools/odmdata/method.py b/odmtools/odmdata/method.py deleted file mode 100644 index f3b9d8f..0000000 --- a/odmtools/odmdata/method.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy import Column, Integer, String -from base import Base - -class Method(Base): - __tablename__ = 'Methods' - - id = Column('MethodID', Integer, primary_key=True) - description = Column('MethodDescription', String, nullable=False) - link = Column('MethodLink', String) - - def __repr__(self): - return "" % (self.id, self.description, self.link) \ No newline at end of file diff --git a/odmtools/odmdata/odm_version.py b/odmtools/odmdata/odm_version.py deleted file mode 100644 index 3fe4420..0000000 --- a/odmtools/odmdata/odm_version.py +++ /dev/null @@ -1,10 +0,0 @@ -from sqlalchemy import String, Column -from base import Base - -class ODMVersion(Base): - __tablename__ = 'ODMVersion' - - version_number = Column('VersionNumber', String, primary_key=True) - - def __repr__(self): - return "" % (self.version_number) \ No newline at end of file diff --git a/odmtools/odmdata/offset_type.py b/odmtools/odmdata/offset_type.py deleted file mode 100644 index f1fc6ca..0000000 --- a/odmtools/odmdata/offset_type.py +++ /dev/null @@ -1,17 +0,0 @@ -from sqlalchemy import Column, Integer, ForeignKey, String -from sqlalchemy.orm import relationship -from base import Base -from unit import Unit - -class OffsetType(Base): - __tablename__ = 'OffsetTypes' - - id = Column('OffsetTypeID', Integer, primary_key=True) - unit_id = Column('OffsetUnitsID', Integer, ForeignKey('Units.UnitsID'), nullable=False) - description = Column('OffsetDescription', String) - - # relationships - unit = relationship(Unit) - - def __repr__(self): - return "" % (self.id, self.unit_id, self.description) \ No newline at end of file diff --git a/odmtools/odmdata/qualifier.py b/odmtools/odmdata/qualifier.py deleted file mode 100644 index 052febb..0000000 --- a/odmtools/odmdata/qualifier.py +++ /dev/null @@ -1,12 +0,0 @@ -from sqlalchemy import Column, String, Integer -from base import Base - -class Qualifier(Base): - __tablename__ = 'Qualifiers' - - id = Column('QualifierID', Integer, primary_key=True) - code = Column('QualifierCode', String, nullable=False) - description = Column('QualifierDescription', String, nullable=False) - - def __repr__(self): - return "" % (self.id, self.code, self.description) \ No newline at end of file diff --git a/odmtools/odmdata/quality_control_level.py b/odmtools/odmdata/quality_control_level.py deleted file mode 100644 index 5e3c226..0000000 --- a/odmtools/odmdata/quality_control_level.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import Column, String, Integer -from base import Base - -class QualityControlLevel(Base): - __tablename__ = 'QualityControlLevels' - - id = Column('QualityControlLevelID', Integer, primary_key=True) - code = Column('QualityControlLevelCode', String, nullable=False) - definition = Column('Definition', String, nullable=False) - explanation = Column('Explanation', String, nullable=False) - - def __repr__(self): - return "" % (self.id, self.code, self.definition, self.explanation) \ No newline at end of file diff --git a/odmtools/odmdata/sample.py b/odmtools/odmdata/sample.py deleted file mode 100644 index 9d18b33..0000000 --- a/odmtools/odmdata/sample.py +++ /dev/null @@ -1,18 +0,0 @@ -from sqlalchemy import Column, Integer, String, ForeignKey -from sqlalchemy.orm import relationship -from base import Base -from lab_method import LabMethod - -class Sample(Base): - __tablename__ = 'Samples' - - id = Column('SampleID', Integer, primary_key=True) - type = Column('SampleType', String, nullable=False) - lab_sample_code = Column('LabSampleCode', String, nullable=False) - lab_method_id = Column('LabMethodID', Integer, ForeignKey('LabMethods.LabMethodID'), nullable=False) - - # relationships - lab_method = relationship(LabMethod) - - def __repr__(self): - return "" % (self.id, self.type, self.lab_sample_code, self.lab_method_id) \ No newline at end of file diff --git a/odmtools/odmdata/sample_medium_cv.py b/odmtools/odmdata/sample_medium_cv.py deleted file mode 100644 index 277f2bd..0000000 --- a/odmtools/odmdata/sample_medium_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SampleMediumCV(Base): - __tablename__ = 'SampleMediumCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/sample_type_cv.py b/odmtools/odmdata/sample_type_cv.py deleted file mode 100644 index caaf4c0..0000000 --- a/odmtools/odmdata/sample_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SampleTypeCV(Base): - __tablename__ = 'SampleTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/series.py b/odmtools/odmdata/series.py deleted file mode 100644 index 7688555..0000000 --- a/odmtools/odmdata/series.py +++ /dev/null @@ -1,150 +0,0 @@ -from collections import OrderedDict # Requires Python 2.7 >= - -from sqlalchemy import Column, Integer, ForeignKey, String, Float, DateTime -from sqlalchemy.orm import relationship - -from base import Base -from site import Site -from variable import Variable -from method import Method -from source import Source -from quality_control_level import QualityControlLevel - -def copy_series(from_series): - new = Series() - new.site_id = from_series.site_id - new.site_code = from_series.site_code - new.site_name = from_series.site_name - new.variable_id = from_series.variable_id - new.variable_code = from_series.variable_code - new.variable_name = from_series.variable_name - new.speciation = from_series.speciation - new.variable_units_id = from_series.variable_units_id - new.variable_units_name = from_series.variable_units_name - new.sample_medium = from_series.sample_medium - new.value_type = from_series.value_type - new.time_support = from_series.time_support - new.time_units_id = from_series.time_units_id - new.time_units_name = from_series.time_units_name - new.data_type = from_series.data_type - new.general_category = from_series.general_category - new.method_id = from_series.method_id - new.method_description = from_series.method_description - new.source_id = from_series.source_id - new.source_description = from_series.source_description - new.organization = from_series.organization - new.citation = from_series.citation - new.quality_control_level_id = from_series.quality_control_level_id - new.quality_control_level_code = from_series.quality_control_level_code - new.begin_date_time = from_series.begin_date_time - new.begin_date_time_utc = from_series.begin_date_time_utc - new.end_date_time_utc = from_series.end_date_time_utc - new.value_count = from_series.value_count - return new -class Series(Base): - __tablename__ = 'seriescatalog' - - id = Column('SeriesID', Integer, primary_key=True) - site_id = Column('SiteID', Integer, ForeignKey('Sites.SiteID'), nullable=False) - site_code = Column('SiteCode', String) - site_name = Column('SiteName', String) - variable_id = Column('VariableID', Integer, ForeignKey('Variables.VariableID'), nullable=False) - variable_code = Column('VariableCode', String) - variable_name = Column('VariableName', String) - - speciation = Column('Speciation', String) - variable_units_id = Column('VariableUnitsID', Integer) - variable_units_name = Column('VariableUnitsName', String) - sample_medium = Column('SampleMedium', String) - value_type = Column('ValueType', String) - time_support = Column('TimeSupport', Float) - time_units_id = Column('TimeUnitsID', Integer) - time_units_name = Column('TimeUnitsName', String) - data_type = Column('DataType', String) - general_category = Column('GeneralCategory', String) - method_id = Column('MethodID', Integer, ForeignKey('Methods.MethodID'), nullable=False) - method_description = Column('MethodDescription', String) - source_id = Column('SourceID', Integer, ForeignKey('Sources.SourceID'), nullable=False) - source_description = Column('SourceDescription', String) - organization = Column('Organization', String) - citation = Column('Citation', String) - quality_control_level_id = Column('QualityControlLevelID', Integer, - ForeignKey('QualityControlLevels.QualityControlLevelID'), nullable=False) - quality_control_level_code = Column('QualityControlLevelCode', String) - begin_date_time = Column('BeginDateTime', DateTime) - end_date_time = Column('EndDateTime', DateTime) - begin_date_time_utc = Column('BeginDateTimeUTC', DateTime) - end_date_time_utc = Column('EndDateTimeUTC', DateTime) - value_count = Column('ValueCount', Integer) - - data_values = relationship("DataValue", - primaryjoin="and_(DataValue.site_id == Series.site_id, " - "DataValue.variable_id == Series.variable_id, " - "DataValue.method_id == Series.method_id, " - "DataValue.source_id == Series.source_id, " - "DataValue.quality_control_level_id == Series.quality_control_level_id)", - foreign_keys="[DataValue.site_id, DataValue.variable_id, DataValue.method_id, DataValue.source_id, DataValue.quality_control_level_id]", - order_by="DataValue.local_date_time", - backref="series") - - site = relationship(Site) - variable = relationship(Variable) - method = relationship(Method) - source = relationship(Source) - quality_control_level = relationship(QualityControlLevel) - - - def __repr__(self): - return "" % (self.id, self.site_name, self.variable_code, self.variable_name) - - def __eq__(self, other) : - # return self.__dict__ == other.__dict__ - return [self.id, self.site_id, self.site_code, self.site_name, self.variable_id, self.variable_code, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.quality_control_level_code, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] ==\ - [other.id, other.site_id, other.site_code, other.site_name, other.variable_id, other.variable_code, - other.variable_name, other.speciation, other.variable_units_id, other.variable_units_name, - other.sample_medium, other.value_type, other.time_support, other.time_units_id, other.time_units_name, - other.data_type, other.general_category, other.method_id, other.method_description, - other.source_id, other.source_description, other.organization, other.citation, - other.quality_control_level_id, other.quality_control_level_code, other.begin_date_time, - other.end_date_time, other.begin_date_time_utc, other.end_date_time_utc, other.value_count] - - - def get_table_columns(self): - return self.__table__.columns.keys() - - def list_repr(self): - return [self.id, self.site_code, self.variable_code, self.quality_control_level_code, - self.site_id, self.site_name, self.variable_id, - self.variable_name, self.speciation, self.variable_units_id, self.variable_units_name, - self.sample_medium, self.value_type, self.time_support, self.time_units_id, self.time_units_name, - self.data_type, self.general_category, self.method_id, self.method_description, - self.source_id, self.source_description, self.organization, self.citation, - self.quality_control_level_id, self.begin_date_time, - self.end_date_time, self.begin_date_time_utc, self.end_date_time_utc, self.value_count] - -def returnDict(): - keys = ['SeriesID', 'SiteCode','VariableCode','QualityControlLevelCode', - 'SiteID', 'SiteName', 'VariableID', 'VariableName', 'Speciation', - 'VariableUnitsID', 'VariableUnitsName', 'SampleMedium', 'ValueType', 'TimeSupport', 'TimeUnitsID', - 'TimeUnitsName', 'DataType', 'GeneralCategory', 'MethodID', 'MethodDescription', 'SourceID', - 'SourceDescription', 'Organization', 'Citation', 'QualityControlLevelID', - 'BeginDateTime', 'EndDateTime', 'BeginDateTimeUTC', 'EndDateTimeUTC', 'ValueCount' - ] - values = ['id', 'site_code','variable_code','quality_control_level_code', - 'site_id', 'site_name', 'variable_id', 'variable_name', 'speciation', - 'variable_units_id', 'variable_units_name', 'sample_medium', 'value_type', 'time_support', - 'time_units_id', 'time_units_name', 'data_type', 'general_category', 'method_id', 'method_description', - 'source_id', 'source_description', 'organization', 'citation', 'quality_control_level_id', - 'begin_date_time', 'end_date_time', 'begin_date_time_utc', - 'end_date_time_utc', 'value_count' - ] - return OrderedDict(zip(keys, values)) - - - diff --git a/odmtools/odmdata/session_factory.py b/odmtools/odmdata/session_factory.py deleted file mode 100644 index 0d2d488..0000000 --- a/odmtools/odmdata/session_factory.py +++ /dev/null @@ -1,57 +0,0 @@ -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker - - -class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_size=20) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - connect_args={'connect_timeout': 1}) - - ''' - # Removing pool_timeout and max_overflow allowed the tests to pass - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, pool_size=20, max_overflow=0) - self.psql_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - self.ms_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'timeout': 1}) - self.my_test_engine = create_engine(connection_string, encoding='utf-8', echo=echo, pool_recycle=3600, - pool_timeout=5, max_overflow=0, connect_args={'connect_timeout': 1}) - ''' - - ''' - # Old code - class SessionFactory(): - def __init__(self, connection_string, echo): - self.engine = create_engine(connection_string, encoding='utf-8', echo=echo, - #pool_size=20, - pool_recycle=3600) - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - ''' - - # Create session maker - self.Session = sessionmaker(bind=self.engine) - self.psql_test_Session = sessionmaker(bind=self.psql_test_engine) - self.ms_test_Session = sessionmaker(bind=self.ms_test_engine) - self.my_test_Session = sessionmaker(bind=self.my_test_engine) - - def get_session(self): - return self.Session() - - def __repr__(self): - return "" % (self.engine) - diff --git a/odmtools/odmdata/site.py b/odmtools/odmdata/site.py deleted file mode 100644 index c92998a..0000000 --- a/odmtools/odmdata/site.py +++ /dev/null @@ -1,37 +0,0 @@ -# Declare a mapped class -from sqlalchemy import Column, Integer, String, Float, ForeignKey -from sqlalchemy.orm import relationship -from base import Base -from spatial_reference import SpatialReference - -class Site(Base): - __tablename__ = 'Sites' - - id = Column('SiteID', Integer, primary_key=True) - code = Column('SiteCode', String) - name = Column('SiteName', String) - latitude = Column('Latitude', Float) - longitude = Column('Longitude', Float) - lat_long_datum_id = Column('LatLongDatumID', Integer, ForeignKey('SpatialReferences.SpatialReferenceID')) - elevation_m = Column('Elevation_m', Float) - vertical_datum_id = Column('VerticalDatum', Integer) - local_x = Column('LocalX', Float) - local_y = Column('LocalY', Float) - local_projection_id = Column('LocalProjectionID', Integer, ForeignKey('SpatialReferences.SpatialReferenceID')) - pos_accuracy_m = Column('PosAccuracy_m', Float) - state = Column('State', String) - county = Column('County', String) - comments = Column('Comments', String) - - type = Column('SiteType', String) - - # relationships - spatial_ref = relationship(SpatialReference, primaryjoin=("SpatialReference.id==Site.lat_long_datum_id")) - local_spatial_ref = relationship(SpatialReference, primaryjoin=("SpatialReference.id==Site.local_projection_id")) - - def __init__(self, site_code, site_name): - self.code = site_code - self.name = site_name - - def __repr__(self): - return "" % (self.code, self.name) \ No newline at end of file diff --git a/odmtools/odmdata/site_type_cv.py b/odmtools/odmdata/site_type_cv.py deleted file mode 100644 index fc41c7e..0000000 --- a/odmtools/odmdata/site_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SiteTypeCV(Base): - __tablename__ = 'SiteTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/source.py b/odmtools/odmdata/source.py deleted file mode 100644 index 08452cc..0000000 --- a/odmtools/odmdata/source.py +++ /dev/null @@ -1,27 +0,0 @@ -from sqlalchemy import Column, String, Integer, ForeignKey -from sqlalchemy.orm import relationship -from base import Base -from iso_metadata import ISOMetadata - -class Source(Base): - __tablename__ = 'Sources' - - id = Column('SourceID', Integer, primary_key=True) - organization = Column('Organization', String, nullable=False) - description = Column('SourceDescription', String, nullable=False) - link = Column('SourceLink', String) - contact_name = Column('ContactName', String, nullable=False) - phone = Column('Phone', String, nullable=False) - email = Column('Email', String, nullable=False) - address = Column('Address', String, nullable=False) - city = Column('City', String, nullable=False) - state = Column('State', String, nullable=False) - zip_code = Column('ZipCode', String, nullable=False) - citation = Column('Citation', String, nullable=False) - iso_metadata_id = Column('MetadataID', Integer, ForeignKey('ISOMetadata.MetadataID'), nullable=False) - - # relationships - iso_metadata = relationship(ISOMetadata) - - def __repr__(self): - return "" % (self.id, self.organization, self.description) \ No newline at end of file diff --git a/odmtools/odmdata/spatial_reference.py b/odmtools/odmdata/spatial_reference.py deleted file mode 100644 index 2b63f39..0000000 --- a/odmtools/odmdata/spatial_reference.py +++ /dev/null @@ -1,14 +0,0 @@ -from sqlalchemy import Column, Integer, String, Boolean -from base import Base - -class SpatialReference(Base): - __tablename__ = 'SpatialReferences' - - id = Column('SpatialReferenceID', Integer, primary_key=True) - srs_id = Column('SRSID', Integer) - srs_name = Column('SRSName', String) - is_geographic = Column('IsGeographic', Boolean) - notes = Column('Notes', String) - - def __repr__(self): - return "" % (self.id, self.srs_name) \ No newline at end of file diff --git a/odmtools/odmdata/speciation_cv.py b/odmtools/odmdata/speciation_cv.py deleted file mode 100644 index 7d6c19f..0000000 --- a/odmtools/odmdata/speciation_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class SpeciationCV(Base): - __tablename__ = 'SpeciationCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/topic_category_cv.py b/odmtools/odmdata/topic_category_cv.py deleted file mode 100644 index 54f65c5..0000000 --- a/odmtools/odmdata/topic_category_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class TopicCategoryCV(Base): - __tablename__ = 'TopicCategoryCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/unit.py b/odmtools/odmdata/unit.py deleted file mode 100644 index 853da31..0000000 --- a/odmtools/odmdata/unit.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import Column, Integer, String, Unicode -from base import Base - -class Unit(Base): - __tablename__ = 'Units' - - id = Column('UnitsID', Integer, primary_key=True) - name = Column('UnitsName', String) - type = Column('UnitsType', String) - abbreviation = Column('UnitsAbbreviation', String)#(convert_unicode=True)) - - def __repr__(self): - return "" % (self.id, self.name, self.type) \ No newline at end of file diff --git a/odmtools/odmdata/value_type_cv.py b/odmtools/odmdata/value_type_cv.py deleted file mode 100644 index 1a19d8a..0000000 --- a/odmtools/odmdata/value_type_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class ValueTypeCV(Base): - __tablename__ = 'ValueTypeCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/variable.py b/odmtools/odmdata/variable.py deleted file mode 100644 index 77d8109..0000000 --- a/odmtools/odmdata/variable.py +++ /dev/null @@ -1,28 +0,0 @@ -from sqlalchemy import Column, Integer, String, ForeignKey, Boolean, Float -from sqlalchemy.orm import relationship -from base import Base -from unit import Unit - -class Variable(Base): - __tablename__ = 'Variables' - - id = Column('VariableID', Integer, primary_key=True) - code = Column('VariableCode', String, nullable=False) - name = Column('VariableName', String, nullable=False) - speciation = Column('Speciation', String, nullable=False) - variable_unit_id = Column('VariableUnitsID', Integer, ForeignKey('Units.UnitsID'), nullable=False) - sample_medium = Column('SampleMedium', String, nullable=False) - value_type = Column('ValueType', String, nullable=False) - is_regular = Column('IsRegular', Boolean, nullable=False) - time_support = Column('TimeSupport', Float, nullable=False) - time_unit_id = Column('TimeUnitsID', Integer, ForeignKey('Units.UnitsID'), nullable=False) - data_type = Column('DataType', String, nullable=False) - general_category = Column('GeneralCategory', String, nullable=False) - no_data_value = Column('NoDataValue', Float, nullable=False) - - # relationships - variable_unit = relationship(Unit, primaryjoin=("Unit.id==Variable.variable_unit_id")) # <-- Uses class attribute names, not table column names - time_unit = relationship(Unit, primaryjoin=("Unit.id==Variable.time_unit_id")) - - def __repr__(self): - return "" % (self.id, self.code, self.name) \ No newline at end of file diff --git a/odmtools/odmdata/variable_name_cv.py b/odmtools/odmdata/variable_name_cv.py deleted file mode 100644 index 0b64596..0000000 --- a/odmtools/odmdata/variable_name_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class VariableNameCV(Base): - __tablename__ = 'VariableNameCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmdata/vertical_datum_cv.py b/odmtools/odmdata/vertical_datum_cv.py deleted file mode 100644 index b161b29..0000000 --- a/odmtools/odmdata/vertical_datum_cv.py +++ /dev/null @@ -1,11 +0,0 @@ -from sqlalchemy import Column, String -from base import Base - -class VerticalDatumCV(Base): - __tablename__ = 'VerticalDatumCV' - - term = Column('Term', String, primary_key=True) - definition = Column('Definition', String) - - def __repr__(self): - return "" % (self.term, self.definition) \ No newline at end of file diff --git a/odmtools/odmservices/__init__.py b/odmtools/odmservices/__init__.py index 6d161f5..7eee9b6 100644 --- a/odmtools/odmservices/__init__.py +++ b/odmtools/odmservices/__init__.py @@ -1,17 +1,17 @@ from service_manager import ServiceManager from series_service import SeriesService -from cv_service import CVService from edit_service import EditService from export_service import ExportService +# +# # need to explicitly import these for pyinstaller +# import pymysql +# import pyodbc +# #import psycopg2 + -# need to explicitly import these for pyinstaller -import pymysql -import pyodbc -#import psycopg2 __all__ = [ 'EditService', - 'CVService', 'SeriesService', 'ExportService', 'ServiceManager', diff --git a/odmtools/odmservices/cv_service.py b/odmtools/odmservices/cv_service.py deleted file mode 100644 index 24b5e81..0000000 --- a/odmtools/odmservices/cv_service.py +++ /dev/null @@ -1,98 +0,0 @@ -# CV imports -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import VerticalDatumCV -from odmtools.odmdata import SiteTypeCV -from odmtools.odmdata import VariableNameCV -from odmtools.odmdata import SpeciationCV -from odmtools.odmdata import SampleMediumCV -from odmtools.odmdata import ValueTypeCV -from odmtools.odmdata import DataTypeCV -from odmtools.odmdata import GeneralCategoryCV -from odmtools.odmdata import CensorCodeCV -from odmtools.odmdata import TopicCategoryCV -from odmtools.odmdata import SampleTypeCV -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -from odmtools.odmdata import Qualifier -from odmtools.odmdata import Unit -from sqlalchemy import not_ - - -class CVService(): - # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() - self._debug = debug - - # Controlled Vocabulary get methods - - #return a list of all terms in the cv - def get_vertical_datum_cvs(self): - result = self._edit_session.query(VerticalDatumCV).order_by(VerticalDatumCV.term).all() - return result - - def get_samples(self): - result = self._edit_session.query(Sample).order_by(Sample.lab_sample_code).all() - return result - - def get_site_type_cvs(self): - result = self._edit_session.query(SiteTypeCV).order_by(SiteTypeCV.term).all() - return result - - def get_variable_name_cvs(self): - result = self._edit_session.query(VariableNameCV).order_by(VariableNameCV.term).all() - return result - - def get_offset_type_cvs(self): - result = self._edit_session.query(OffsetType).order_by(OffsetType.id).all() - return result - - def get_speciation_cvs(self): - result = self._edit_session.query(SpeciationCV).order_by(SpeciationCV.term).all() - return result - - def get_sample_medium_cvs(self): - result = self._edit_session.query(SampleMediumCV).order_by(SampleMediumCV.term).all() - return result - - def get_value_type_cvs(self): - result = self._edit_session.query(ValueTypeCV).order_by(ValueTypeCV.term).all() - return result - - def get_data_type_cvs(self): - result = self._edit_session.query(DataTypeCV).order_by(DataTypeCV.term).all() - return result - - def get_general_category_cvs(self): - result = self._edit_session.query(GeneralCategoryCV).order_by(GeneralCategoryCV.term).all() - return result - - def get_censor_code_cvs(self): - result = self._edit_session.query(CensorCodeCV).order_by(CensorCodeCV.term).all() - return result - - def get_sample_type_cvs(self): - result = self._edit_session.query(SampleTypeCV).order_by(SampleTypeCV.term).all() - return result - - def get_units(self): - result = self._edit_session.query(Unit).all() - return result - - def get_units_not_uni(self): - result = self._edit_session.query(Unit).filter(not_(Unit.name.contains('angstrom'))).all() - return result - - def get_units_names(self): - result = self._edit_session.query(Unit.name).all() - return result - - # return a single cv - def get_unit_by_name(self, unit_name): - result = self._edit_session.query(Unit).filter_by(name=unit_name).first() - return result - - def get_unit_by_id(self, unit_id): - result = self._edit_session.query(Unit).filter_by(id=unit_id).first() - return result diff --git a/odmtools/odmservices/edit_service.py b/odmtools/odmservices/edit_service.py index e2eb70f..ef3c3ae 100644 --- a/odmtools/odmservices/edit_service.py +++ b/odmtools/odmservices/edit_service.py @@ -1,14 +1,15 @@ import sqlite3 -from odmtools.odmdata import DataValue +# from odmtools.odmdata import DataValue from series_service import SeriesService -from odmtools.odmdata import series as series_module +# from odmtools.odmdata import series as series_module import pandas as pd import datetime import numpy as np +from odm2api.ODM2.models import * import logging from odmtools.common.logger import LoggerTool @@ -48,12 +49,13 @@ def __init__(self, series_id, connection=None, connection_string="", debug=False self._filter_from_selection = False self._debug = debug - if connection_string is "" and connection is not None: - self.memDB= connection + if connection_string is "" and connection is not None: + self.memDB = connection elif connection_string is not "" and connection is None: from odmtools.odmdata import MemoryDatabase - self.memDB= MemoryDatabase() + self.memDB = MemoryDatabase() + # todo Stephanie: does not accept a string for the connection anymore self.memDB.set_series_service(SeriesService(connection_string, False)) @@ -114,9 +116,9 @@ def datetime2dataframe(self, datetime_list): if isinstance(datetime_list, list): - result = pd.DataFrame(datetime_list, columns=["LocalDateTime"]) + result = pd.DataFrame(datetime_list, columns=["valuedatetime"]) - result.set_index("LocalDateTime", inplace=True) + result.set_index("valuedatetime", inplace=True) return result @@ -146,10 +148,10 @@ def filter_value(self, value, ops): df = self._test_filter_previous() if ops == '>': - self.filtered_dataframe = df[df['DataValue'] > value] + self.filtered_dataframe = df[df['datavalue'] > value] if ops == '<': - self.filtered_dataframe = df[df['DataValue'] < value] + self.filtered_dataframe = df[df['datavalue'] < value] def filter_date(self, before, after): @@ -235,17 +237,15 @@ def data_gaps(self, value, time_period): self.filtered_dataframe= df[df.index.isin(newdf.drop_duplicates().dropna())] - - def change_value_threshold(self, value, operator): df = self._test_filter_previous() # make a copy of the dataframe in order to modify it to be in the form we need to determine data gaps copy_df = df - copy_df['values'] = df['DataValue'] + copy_df['values'] = df['datavalue'] copy_df['diff'] = copy_df['values'].shift() - copy_df["diff_date"] = copy_df['LocalDateTime'].shift() + copy_df["diff_date"] = copy_df['valuedatetime'].shift() copy_df['change_threshold'] = abs(df['values'] - df['diff']) if not isinstance(value, float): @@ -271,7 +271,6 @@ def duplicate_value_filter(self): #self.filtered_dataframe= df[df.index.get_duplicates()] self.filtered_dataframe= df[df.index.isin(df.index.get_duplicates())] #self.filtered_dataframe = df[df['DataValue'] < value] - print "dup value worked" def select_points_tf(self, tf_list): @@ -308,10 +307,10 @@ def get_toggle(self): # Gets ################### def get_series(self): - return self.memDB.series_service.get_series_by_id(self._series_id) + return self.memDB.series_service.get_series(self._series_id) def get_series_points(self): - # all point in the series + # all point in the series_service return self._series_points def get_series_points_df(self): @@ -338,11 +337,11 @@ def get_filtered_dates(self): return self.filtered_dataframe def get_filter_list(self): - # true or false list the length of the entire series. true indicate the point is selected + # true or false list the length of the entire series_service. true indicate the point is selected return self._filter_list def get_qcl(self, qcl_id): - return self.memDB.series_service.get_qcl_by_id(qcl_id) + return self.memDB.series_service.get_processing_level_by_id(qcl_id) def get_method(self, method_id): return self.memDB.series_service.get_method_by_id(method_id) @@ -394,13 +393,13 @@ def interpolate(self): df = self._series_points_df issel = df.index.isin(tmp_filter_list.index) - mdf = df["DataValue"].mask(issel) + mdf = df["datavalue"].mask(issel) mdf.interpolate(method = "time", inplace=True) - tmp_filter_list["DataValue"]=mdf[issel] + tmp_filter_list["datavalue"]=mdf[issel] ids = tmp_filter_list.index.tolist() #update_list = [(row["DataValue"], row["ValueID"]) for index, row in tmp_filter_list.iterrows()] - update_list = [{"value": row["DataValue"], "id": index} for index, row in tmp_filter_list.iterrows()] + update_list = [{"value": row["datavalue"], "id": index} for index, row in tmp_filter_list.iterrows()] self.memDB.update(update_list) @@ -411,18 +410,21 @@ def interpolate(self): def drift_correction(self, gap_width): - if self.isOneGroup(): tmp_filter_list =self.get_filtered_points() startdate =tmp_filter_list.index[0] x_l = (tmp_filter_list.index[-1]-startdate).total_seconds() - #nodv= self.memDB.series_service.get_variable_by_id(self.memDB.df["VariableID"][0]) - nodv = self.memDB.series.variable.no_data_value + + nodv = self.memDB.series.VariableObj.NoDataValue + # y_n = y_0 + G(x_i / x_l) - f = lambda row : row["DataValue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) if row["DataValue"] != nodv else row["DataValue"] - tmp_filter_list["DataValue"]=tmp_filter_list.apply(f, axis = 1) + # f = lambda row : row["datavalue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) - update_list = [{"value": row["DataValue"], "id":index} for index, row in tmp_filter_list.iterrows()] + + f = lambda row : row["datavalue"]+(gap_width * ((row.name-startdate).total_seconds() / x_l)) if row["datavalue"] != nodv else row["datavalue"] + tmp_filter_list["datavalue"]=tmp_filter_list.apply(f, axis = 1) + + update_list = [{"value": row["datavalue"], "id":index} for index, row in tmp_filter_list.iterrows()] ids = tmp_filter_list.index.tolist() self.memDB.update(update_list) @@ -495,189 +497,247 @@ def restore(self): self._populate_series() self.reset_filter() - def updateSeries(self, var=None, method=None, qcl=None, is_new_series=False, overwrite = True, append = False): - """ + def save(self, result=None): + try: + values = self.memDB.getDataValuesDF() - :param var: - :param method: - :param qcl: - :param is_new_series: - :return: - """ + if not result: + result = self.memDB.series_service.get_series(series_id = values['resultid'][0]) + else: + values["resultid"] = result.ResultID + + # update result + result.ValueCount = 0 + self.updateResult(result) + # upsert values + self.memDB.series_service.upsert_values(values) + # save new annotations + if len(self.memDB.annotation_list >0): + self.add_annotations(self.memDB.annotation_list) + return result + except Exception as e: + logger.error("Exception encountered while saving: {}".format(e)) + raise e + return None - var_id = var.id if var is not None else None - method_id = method.id if method is not None else None - qcl_id = qcl.id if qcl is not None else None - #self.memDB.changeSeriesIDs(var_id, method_id, qcl_id) - dvs = self.memDB.getDataValuesDF() - if var_id is not None: - dvs["VariableID"] = var_id - if method_id is not None: - dvs["MethodID"] = method_id - if qcl_id is not None: - dvs["QualityControlLevelID"] = qcl_id + def save_existing(self, result): + result = self.save(result) + return result + def save_appending(self, result, overwrite=True): + try: + + values = self.memDB.getDataValuesDF() + + # get value count + vc = result.ValueCount + # set in df + values["resultid"] = result.ResultID + + # count = overlap calc + count = self.overlapcalc(result, values, overwrite) + # set value count = res.vc+valuecount-count + valuecount = result.ValueCount + vc - count + # update result + self.updateResult(result, valuecount) + # insert values + self.memDB.series_service.upsert_values(values) + # save new annotations + if len(self.memDB.annotation_list >0): + self.add_annotations(self.memDB.annotation_list) + return result + except Exception as e: + logger.error("Exception encountered while performing a save as: {}".format(e)) + raise e + return None + def save_as(self, variable, method, proc_level, action, action_by): - #if is new series remove valueids - #if is_new_series: - dvs["ValueID"] = None - ''' - for dv in dvs: - dv.id = None - ''' + try: + #save as new series + values = self.memDB.getDataValuesDF() + # get all annotations for series + annolist= self.memDB.series_service.get_annotations_by_result(str(values["resultid"][0])) + annolist['valueid'] = None - series = self.memDB.series_service.get_series_by_id(self._series_id) - logger.debug("original editing series id: %s" % str(series.id)) - - if (var or method or qcl ): - tseries = self.memDB.series_service.get_series_by_id_quint(site_id=int(series.site_id), - var_id=var_id if var else int(series.variable_id), - method_id=method_id if method else int( - series.method_id), - source_id=series.source_id, - qcl_id=qcl_id if qcl else int( - series.quality_control_level_id)) - if tseries: - logger.debug("Save existing series ID: %s" % str(tseries.id)) - series = tseries - else: - print "Series doesn't exist (if you are not, you should be running SaveAs)" - - if is_new_series: - series = series_module.copy_series(series) - if var: - series.variable_id = var_id - series.variable_code = var.code - series.variable_name = var.name - series.speciation = var.speciation - series.variable_units_id = var.variable_unit_id - series.variable_units_name = var.variable_unit.name - series.sample_medium = var.sample_medium - series.value_type = var.value_type - series.time_support = var.time_support - series.time_units_id = var.time_unit_id - series.time_units_name = var.time_unit.name - series.data_type = var.data_type - series.general_category = var.general_category - - if method: - series.method_id = method_id - series.method_description = method.description - - if qcl: - series.quality_control_level_id = qcl_id - series.quality_control_level_code = qcl.code - ''' - dvs["LocalDateTime"] = pd.to_datetime(dvs["LocalDateTime"]) - dvs["DateTimeUTC"] = pd.to_datetime(dvs["DateTimeUTC"]) - ''' + # create series + result = self.getResult(variable, method, proc_level, action, action_by) - form = "%Y-%m-%d %H:%M:%S" + # set in df + values["valueid"] = None + values["resultid"] = result.ResultID + # insert values + self.memDB.series_service.insert_values(values) - if not append: - - series.begin_date_time = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form)#np.min(dvs["LocalDateTime"])#dvs[c0].local_date_time - series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form)#np.max(dvs["LocalDateTime"])#dvs[-1].local_date_time - series.begin_date_time_utc = datetime.datetime.strptime(str(np.min(dvs["DateTimeUTC"])), form) #dvs[0].date_time_utc - series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) #dvs[-1].date_time_utc - series.value_count = len(dvs) - - ## Override previous save - if not is_new_series: - # delete old dvs - #pass - self.memDB.series_service.delete_values_by_series(series) - elif append: - #if series end date is after dvs startdate - dbend = series.end_date_time - dfstart = datetime.datetime.strptime(str(np.min(dvs["LocalDateTime"])), form) - overlap = dbend>= dfstart - #leave series start dates to those previously set - series.end_date_time = datetime.datetime.strptime(str(np.max(dvs["LocalDateTime"])), form) - series.end_date_time_utc = datetime.datetime.strptime(str(np.max(dvs["DateTimeUTC"])), form) - #TODO figure out how to calculate the new value count - series.value_count = len(dvs) - - if overlap: - if overwrite: - #remove values from the database - self.memDB.series_service.delete_values_by_series(series, startdate=dfstart) - else: - #remove values from df - dvs = dvs[dvs["LocalDateTime"] > dbend] + #combine all of the annotations new annotations with the existing + frames = [self.memDB.annotation_list, annolist] + annolist = pd.concat(frames) + # save all annotations + if len(annolist > 0): + self.add_annotations(annolist) + return result + except Exception as e: + logger.error("Exception encountered while performing a save as: {}".format(e)) + raise e - #logger.debug("series.data_values: %s" % ([x for x in series.data_values])) - dvs.drop('ValueID', axis=1, inplace=True) - return series, dvs + def getResult(self, var, meth, proc, action, action_by): + id = self.memDB.getDataValuesDF()["resultid"] - def save(self): - """ Save to an existing catalog - :param var: - :param method: - :param qcl: - :return: - """ + # copy old + # what is my original result + result = self.memDB.series_service.get_series(str(id[0])) - series, dvs = self.updateSeries(is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save was unsuccessful") - return False + sfid = result.FeatureActionObj.SamplingFeatureID + aggcv = result.AggregationStatisticCV + itsp = result.IntendedTimeSpacing + itspunit = result.IntendedTimeSpacingUnitsID + status = result.StatusCV + type = result.ResultTypeCV + units = result.UnitsID + medium = result.SampledMediumCV - def save_as(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=True) - if self.memDB.series_service.save_new_series(series, dvs): - logger.debug("series saved!") - return True - else: - logger.debug("The Save As Function was Unsuccessful") - return False + self.memDB.series_service._session.expunge(result) + # change var, meth proc, in df #intend ts, agg sta + if var: + result.VariableID = var.VariableID - def save_appending(self, var= None, method = None, qcl=None, overwrite=False): - series, dvs = self.updateSeries(var, method, qcl, is_new_series=False, append= True, overwrite=overwrite) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") - return True + if proc: + result.ProcessingLevelID = proc.ProcessingLevelID + + result.ResultID=None + result.ResultUUID = None + + + #if result does not exist + if not self.memDB.series_service.resultExists(result): + try: + + #create Action + if meth: + id = meth.MethodID + # new_action.MethodObj = meth.MethodOb + else: + id = action.MethodID + new_action, action_by = self.memDB.series_service.create_action(id, action.ActionDescription, action.ActionFileLink, action.BeginDateTime, action.BeginDateTimeUTCOffset, action_by) + + # create FeatureAction (using current sampling feature id) + feature_action = self.memDB.series_service.createFeatureAction(sfid, new_action.ActionID) + + if var: + varid = var.VariableID + else: + varid = result.VariableID + + if proc: + procid= proc.ProcessingLevelID + else: + procid= result.ProcessingLevelID + result = self.memDB.series_service.create_result(varid, procid, feature_action.FeatureActionID, + aggcv, itsp, itspunit, status, type, units, medium) + + except Exception as ex: + self.memDB.series_service._session.rollback() + print ex + raise ex else: - logger.debug("The Append Existing Function was Unsuccessful") - return False + #if saveas called me throw an error that this series already exists + import inspect + (frame, filename, line_number, + function_name, lines, index) = inspect.getouterframes(inspect.currentframe())[1] - def save_existing(self, var=None, method=None, qcl=None): - """ - :param var: - :param method: - :param qcl: - :return: - """ - series, dvs = self.updateSeries(var, method, qcl, is_new_series=False) - if self.memDB.series_service.save_series(series, dvs): - logger.debug("series saved!") - return True + if function_name =='save_as': + raise Exception("this series already exists, but you have chosen to create a new series") + else: + #it already exists, so get it + result = self.memDB.series_service.get_series_by_meta(result) + + return self.updateResult(result) + + def updateResult(self, result, valuecount=-10): + form = "%Y-%m-%d %H:%M:%S" + # get pd + values = self.memDB.getDataValuesDF() + + # update count, dates, + action = result.FeatureActionObj.ActionObj + action.BeginDateTime = datetime.datetime.strptime(str(np.min(values["valuedatetime"])), form) + action.EndDateTime = datetime.datetime.strptime(str(np.max(values["valuedatetime"])), form) + + #TODO how does valuecount change, when do i send it in + if valuecount > 0: + result.ValueCount = valuecount else: - logger.debug("The Save As Existing Function was Unsuccessful") - return False + result.ValueCount = len(values) + + self.memDB.series_service.update_result(result=result) + self.memDB.series_service.update_action(action=action) + return result + + def overlapcalc(self, result, values, overwrite): + form = "%Y-%m-%d %H:%M:%S" + + # is there any overlap + dbend = result.FeatureActionObj.ActionObj.EndDateTime + dfstart = datetime.datetime.strptime(str(np.min(values["valuedatetime"])), form) + overlap = dbend >= dfstart + # number of overlapping values + overlapdf = values[(values["valuedatetime"] <= dfstart) & (values["valuedatetime"] >= dbend)] + count = len(overlapdf) + + # if not overwrite. remove any overlapping values from df + if overlap: + if not overwrite: + # delete overlapping from the data frame before saving to the database + values = values[values["valuedatetime"] > dbend] + + else: + # delete overlapping values from the series database + count = self.memDB.series_service.delete_values_by_series(str(values["resultid"]), dfstart) + + + # return the number of overlapping values + return count + + def add_annotations(self, annolist): + # match up with existing values and get value id + + engine = self.memDB.series_service._session_factory.engine + + q =self.memDB.series_service._session.query(TimeSeriesResultValues) \ + .filter(TimeSeriesResultValues.ResultID == int(min(annolist["resultid"]))) + + query = q.statement.compile(dialect=engine.dialect) + # data = pd.read_sql_query(sql=query, con=self._session_factory.engine, + # params=query.params) + # query = "SELECT ValueID, ResultID, ValueDateTime FROM TimeSeriesResultValues Where ResultID="+annolist["ResultID"][0] + + vals = pd.read_sql_query(sql=query, con=engine, params=query.params) + # remove any duplicates + annolist.drop_duplicates(["resultid", "annotationid", "valuedatetime"], keep='last', inplace=True) + newdf = pd.merge(annolist, vals, how='left', on=["resultid", "valuedatetime"], indicator=True) + + # get only AnnotationID and ValueID + mynewdf= newdf[["valueid_y","annotationid"]] + mynewdf.columns = ["ValueID", "AnnotationID"] + + + # save df to db + self.memDB.series_service.add_annotations(mynewdf) + + def create_qcl(self, code, definition, explanation): - return self.memDB.series_service.create_qcl(code, definition, explanation) + return self.memDB.series_service.create_processing_level(code, definition, explanation) def create_method(self, description, link): return self.memDB.series_service.create_method(description, link) def create_qualifier(self, code, definition): - return self.memDB.series_service.create_qualifier(code, definition) + return self.memDB.series_service.create_annotation(code, definition) def create_variable(self, code, name, speciation, variable_unit_id, sample_medium, value_type, is_regular, time_support, time_unit_id, data_type, general_category, no_data_value): @@ -687,8 +747,8 @@ def create_variable(self, code, name, speciation, variable_unit_id, sample_mediu general_category, no_data_value) def reconcile_dates(self, parent_series_id): - # FUTURE FEATURE: pull in new field data from another series and add to this series - # (i.e one series contains new field data of an edited series at a higher qcl) + # FUTURE FEATURE: pull in new field data from another series_service and add to this series_service + # (i.e one series_service contains new field data of an edited series_service at a higher qcl) pass diff --git a/odmtools/odmservices/export_data.py b/odmtools/odmservices/export_data.py new file mode 100644 index 0000000..549cade --- /dev/null +++ b/odmtools/odmservices/export_data.py @@ -0,0 +1,251 @@ +import csv +import xml.etree.cElementTree as ET +import datetime + +class ExportData(): + + def __init__(self, series_service): + self._series_service = series_service + self.dt_format_str = "%m/%d/%Y %I:%M:%S %p" + + def export_series_data(self, series_id, filename, utc=False, site=False, var=False, offset=False, qual=False, + src=False, qcl=False): + #series = self._series_service.get_series_by_id(series_id) + series = self._series_service.get_series(series_id) + + if series is None: + return False + + print "filename: " + print filename + plainWriter = open(filename, 'w') + self.write_text_header(plainWriter, series, utc, site, var, offset, qual, src, qcl) + plainWriter.close() + writer = csv.writer(open(filename, 'a')) + self.write_data_header(writer, utc, site, var, offset, qual, src, qcl) + # for dv in self._series_service.get_values(series.ResultID): + # self.write_data_row(writer, series, dv, utc, site, var, offset, qual, src, qcl) + vals = self._series_service.get_values(series.ResultID) + vals.to_csv(filename, ',', + columns = ['valuedatetime', 'valuedatetimeutcoffset', 'datavalue', 'censorcodecv', 'qualifiercodecv'], + header = ['LocalDateTime', 'UTCOffset', series.VariableObj.VariableCode, 'CensorCode', 'QualifierCode'], + mode = 'a', + index = False) + + def export_data(self, series_ids, filename): + if series_ids is None: + return + + try: + with open(filename): + file_exists = True + except IOError: + file_exists = False + + if file_exists: + pass + + def write_data_header(self, writer, utc, site, var, offset, qual, src, qcl): + # Build header list + header = [] + header.append("SeriesId") + header.append("ValueId") + header.append("DataValue") + header.append("ValueAccuracy") + header.append("LocalDateTime") + if utc: + header.append("UTCOffset") + header.append("DateTimeUTC") + header.append("SiteCode") + if site: + header.append("SiteName") + header.append("SiteType") + header.append("Latitude") + header.append("Longitude") + header.append("SRSName") + header.append("VariableCode") + if var: + header.append("VariableName") + header.append("Speciation") + header.append("VariableUnitsName") + header.append("VariableUnitsAbbreviation") + header.append("SampleMedium") + header.append("OffsetValue") + header.append("OffsetTypeID") + if offset: + header.append("OffsetDescription") + header.append("OffsetUnitsName") + header.append("CensorCode") + header.append("QualifierID") + if qual: + header.append("QualifierCode") + header.append("QualifierDescription") + if src: + header.append("Organization") + header.append("SourceDescription") + header.append("Citation") + if qcl: + header.append("QualityControlLevelCode") + header.append("Definition") + header.append("Explanation") + header.append("SampleID") + + writer.writerow(header) + + def write_data_row(self, writer, series, dv, utc, site, var, offset, qual, src, qcl): + data = [] + data.append(series.id) + data.append(dv.id) + data.append(dv.data_value) + data.append(dv.value_accuracy) + data.append(dv.local_date_time) + if utc: + data.append(dv.utc_offset) + data.append(dv.date_time_utc) + data.append(series.site_code) + if site: + data.append(series.site_name) + data.append(series.site.type) + data.append(series.site.latitude) + data.append(series.site.longitude) + data.append(series.site.spatial_ref.srs_name) + data.append(series.variable_code) + if var: + data.append(series.variable_name) + data.append(series.speciation) + data.append(series.variable_units_name) + data.append(series.variable.variable_unit.abbreviation) + data.append(series.sample_medium) + data.append(dv.offset_value) + data.append(dv.offset_type_id) + if offset: + if dv.offset_type is not None: + data.append(dv.offset_type.description) + data.append(dv.offset_type.unit.name) + else: + data.append('') + data.append('') + data.append(dv.censor_code) + data.append(dv.qualifier_id) + if qual: + if dv.qualifier is not None: + data.append(dv.qualifier.code) + data.append(dv.qualifier.description) + else: + data.append('') + data.append('') + if src: + data.append(series.organization) + data.append(series.source_description) + data.append(series.citation) + if qcl: + data.append(series.quality_control_level_code) + data.append(series.quality_control_level.definition) + data.append(series.quality_control_level.explanation) + data.append(dv.sample_id) + + writer.writerow(data) + + def write_text_header(self, plainWriter, series, utc, site, var, offset, qual, src, qcl): + self.write_warning_header(plainWriter) + self.write_site_information(plainWriter, series, site) + self.write_variable_and_method_information(plainWriter, series) + self.write_source_information(plainWriter, series) + self.write_qualifier_information(plainWriter, series) + + + def write_warning_header(self, plainWriter): + plainWriter.write( + '# ------------------------------------------------------------------------------------------\n') + plainWriter.write('# WARNING: The data are released on the condition that neither iUTAH nor any of its \n') + plainWriter.write('# participants may be held liable for any damages resulting from their use. The following \n') + plainWriter.write('# metadata describe the data in this file:\n') + plainWriter.write( + '# ------------------------------------------------------------------------------------------\n') + plainWriter.write('#\n') + plainWriter.write('# Quality Control Level Information\n') + plainWriter.write('# -----------------------------------------------\n') + plainWriter.write('# These data have passed QA/QC procedures such as sensor calibration and\n') + plainWriter.write('# visual inspection and removal of obvious errors. These data are approved\n') + plainWriter.write('# by Technicians as the best available version of the data. See published\n') + plainWriter.write('# script for correction steps specific to this data series.\n') + plainWriter.write('#\n') + + def write_site_information(self, plainWriter, series, site): + plainWriter.write('# Site Information\n') + plainWriter.write('# ----------------------------------\n') + plainWriter.write('# Network: TBD\n') + plainWriter.write('# SiteCode: '+str(series.FeatureActionObj.SamplingFeatureObj.SamplingFeatureCode)+'\n') + plainWriter.write('# SiteName: ' + str(series.FeatureActionObj.SamplingFeatureObj.SamplingFeatureName) + '\n') + plainWriter.write('# Latitude: ' + str(series.FeatureActionObj.SamplingFeatureObj.Latitude) + '\n') + plainWriter.write('# Longitude: ' + str(series.FeatureActionObj.SamplingFeatureObj.Longitude) + '\n') + plainWriter.write('# LatLonDatum: ' + 'TBD' + '\n') #FIX + plainWriter.write('# Elevation_m: ' + str(series.FeatureActionObj.SamplingFeatureObj.Elevation_m) + '\n') + plainWriter.write('# ElevationDatum: ' + str(series.FeatureActionObj.SamplingFeatureObj.ElevationDatumCV) + '\n') + plainWriter.write('# State: ' + 'TBD' + '\n') # FIX + plainWriter.write('# County: ' + 'TBD' + '\n') # FIX + plainWriter.write('# Comments: ' + 'TBD' + '\n') # FIX + plainWriter.write( + '# SiteType: ' + str(series.FeatureActionObj.SamplingFeatureObj.SiteTypeCV) + '\n') + plainWriter.write('#\n') + + def write_variable_and_method_information(self, plainWriter, series): + plainWriter.write('# Variable and Method Information\n') + plainWriter.write('# ----------------------------------\n') + plainWriter.write('# VariableCode: ' + str(series.VariableObj.VariableCode) + '\n') + plainWriter.write('# VariableName: ' + str(series.VariableObj.VariableNameCV) + '\n') + plainWriter.write('# ValueType: ' + 'TBD' + '\n') + plainWriter.write('# DataType: ' + 'TBD' + '\n') + plainWriter.write('# GeneralCategory: ' + 'TBD' + '\n') + plainWriter.write('# SampleMedium: ' + 'TBD' + '\n') + plainWriter.write('# VariableUnitsName: ' + str(series.UnitsObj.UnitsName) + '\n') + plainWriter.write('# VariableUnitsType: ' + str(series.UnitsObj.UnitsTypeCV) + '\n') + plainWriter.write('# VariableUnitsAbbreviation: ' + str(series.UnitsObj.UnitsAbbreviation) + '\n') + plainWriter.write('# NoDataValue: ' + str(series.VariableObj.NoDataValue) + '\n') + plainWriter.write('# TimeSupport: ' + 'TBD' + '\n') + plainWriter.write('# TimeSupportUnitsAbbreviation: ' + 'TBD' + '\n') + plainWriter.write('# TimeSupportUnitsType: ' + 'TBD' + '\n') + plainWriter.write('# TimeSupportUnitsName: ' + 'TBD' + '\n') + plainWriter.write('# MethodDescription: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.MethodDescription) + '\n') + plainWriter.write('# MethodLink: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.MethodLink) + '\n') + plainWriter.write('#\n') + + def write_source_information(self, plainWriter, series): + plainWriter.write('# Source Information\n') + plainWriter.write('# ----------------------------------\n') + if(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj != None): + plainWriter.write('# Organization: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.OrganizationName) + '\n') + plainWriter.write('# SourceDescription: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.OrganizationDescription) + + '\n') + plainWriter.write('# SourceLink: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.OrganizationLink) + '\n') + plainWriter.write('# ContactName: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj. + AffiliationObj.PersonObj.PersonFirstName) + ' ' + ( + series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj. + AffiliationObj.PersonObj.PersonLastName + ) + '\n') + plainWriter.write('# SourceLink: ' + + str(series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.AffiliationObj.PrimaryPhone) + '\n') + plainWriter.write('# SourceLink: ' + + str( + series.FeatureActionObj.ActionObj.MethodObj.OrganizationObj.AffiliationObj.PrimaryEmail) + '\n') + plainWriter.write('# Citation: ' + 'TBD' + '\n') + + plainWriter.write('#\n') + + def write_qualifier_information(self, plainWriter, series): + plainWriter.write('# Qualifier Information\n') + plainWriter.write('# ----------------------------------\n') + plainWriter.write('# Code Description\n') + plainWriter.write('# LI Linear Interpolation\n') + plainWriter.write('# SM Sensor Malfunction\n') + plainWriter.write('# PF Power Failure\n') + plainWriter.write('# S Suspicious Values\n') + plainWriter.write('# MNT Erroneous or missing data due to maintenance\n') + plainWriter.write('#\n') + plainWriter.write('#\n') diff --git a/odmtools/odmservices/export_service.py b/odmtools/odmservices/export_service.py index 4b6d1eb..9cbd7a5 100644 --- a/odmtools/odmservices/export_service.py +++ b/odmtools/odmservices/export_service.py @@ -14,7 +14,7 @@ def __init__(self, series_service): def export_series_data(self, series_id, filename, utc=False, site=False, var=False, offset=False, qual=False, src=False, qcl=False): - series = self._series_service.get_series_by_id(series_id) + series = self._series_service.get_series(series_id) if series is None: return False @@ -144,11 +144,11 @@ def export_series_metadata(self, series_ids, filename): pass if isinstance(series_ids, int): - series = self._series_service.get_series_by_id(series_ids) + series = self._series_service.get_series(series_ids) self.append_series_node(series, list_root) else: for series_id in series_ids: - series = self._series_service.get_series_by_id(series_id) + series = self._series_service.get_series(series_id) self.append_series_node(series, list_root) tree = ET.ElementTree(root) diff --git a/odmtools/odmservices/series_service.py b/odmtools/odmservices/series_service.py index 078e6fe..395b55d 100644 --- a/odmtools/odmservices/series_service.py +++ b/odmtools/odmservices/series_service.py @@ -1,127 +1,170 @@ import logging - - -from sqlalchemy import distinct, func - - -from odmtools.odmdata import SessionFactory -from odmtools.odmdata import Site -from odmtools.odmdata import Variable -from odmtools.odmdata import Unit -from odmtools.odmdata import Series -from odmtools.odmdata import DataValue -from odmtools.odmdata import Qualifier -from odmtools.odmdata import OffsetType -from odmtools.odmdata import Sample -from odmtools.odmdata import Method -from odmtools.odmdata import QualityControlLevel -from odmtools.odmdata import ODMVersion -from odmtools.common.logger import LoggerTool +from sqlalchemy import not_, bindparam, distinct, func, exists +from odm2api.ODM2.services import ReadODM2, UpdateODM2, DeleteODM2, CreateODM2 +from odm2api import serviceBase +from odm2api.ODM2.models import * +import datetime import pandas as pd +logger = logging.getLogger('main') -# tool = LoggerTool() -# logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) -logger =logging.getLogger('main') -class SeriesService(): +class SeriesService(serviceBase): # Accepts a string for creating a SessionFactory, default uses odmdata/connection.cfg - def __init__(self, connection_string="", debug=False): - self._session_factory = SessionFactory(connection_string, debug) - self._edit_session = self._session_factory.get_session() - self._debug = debug + def __init__(self, connection, debug=False): - def reset_session(self): - self._edit_session = self._session_factory.get_session() # Reset the session in order to prevent memory leaks + serviceBase.__init__(self, connection, debug) + self.read = ReadODM2(self._session_factory) + self.update = UpdateODM2(self._session_factory) + self.delete = DeleteODM2(self._session_factory) + self.create = CreateODM2(self._session_factory) + # send in engine + setSchema(self._session_factory.engine) - def get_db_version(self): - return self._edit_session.query(ODMVersion).first().version_number + def reset_session(self): + self.read.reset_session() + self.update.reset_session() + self.delete.reset_session() + self.create.reset_session() ##################### # -# Get functions +# Get functions # ##################### - # Site methods - def get_all_sites(self): - """ - - :return: List[Sites] - """ - return self._edit_session.query(Site).order_by(Site.code).all() - - def get_used_sites(self): """ Return a list of all sites that are being referenced in the Series Catalog Table :return: List[Sites] """ try: - site_ids = [x[0] for x in self._edit_session.query(distinct(Series.site_id)).all()] + fas=[x[0] for x in self._session.query(distinct(Results.FeatureActionID)).all()] except: - site_ids = None - - if not site_ids: return None - Sites = [] - for site_id in site_ids: - Sites.append(self._edit_session.query(Site).filter_by(id=site_id).first()) + sf = [x[0] for x in self._session.query(distinct(FeatureActions.SamplingFeatureID)) + .filter(FeatureActions.FeatureActionID.in_(fas)).all()] - return Sites + sites = self.read.getSamplingFeatures(type="site", ids=sf) + return sites - - def get_site_by_id(self, site_id): + def get_used_variables(self): """ - return a Site object that has an id=site_id - :param site_id: integer- the identification number of the site - :return: Sites + #get list of used variable ids + :return: List[Variables] """ try: - return self._edit_session.query(Site).filter_by(id=site_id).first() + ids= [x[0] for x in self._session.query(distinct(Results.VariableID)).all()] except: return None - # Variables methods - def get_used_variables(self): + vars= self.read.getVariables(ids = ids) + return vars + + # Query DetailedResultInfo/series object is for Display purposes + def get_all_series(self, siteid = None): """ - #get list of used variable ids - :return: List[Variables] + Returns all series as a modelObject + :return: List[Series] """ + setSchema(self._session_factory.engine) + + return self.read.getDetailedResultInfo('Time Series Coverage', sfID=siteid) + + def get_series(self, series_id=None): + """ + :param series_id: int + :return: Series + """ + # try: + # return self.read.getDetailedResultInfo('Time Series Coverage', resultID = series_id)[0] + # except Exception as e: + # print e + # return None + setSchema(self._session_factory.engine) + return self.read.getResults(ids=[str(series_id)])[0] + + # Query result objects for data purposes + def get_result_dates(self, result_id): + q = self.read._session.query( + func.max(TimeSeriesResultValues.ValueDateTime), func.min(TimeSeriesResultValues.ValueDateTime) + ).filter(TimeSeriesResultValues.ResultID == result_id) + return q.all()[0] + + def get_variables_by_site_code(self, site_code): + """ + Finds all of variables at a site + :param site_code: str + :return: List[Variables] + """ try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).all()] + var_ids = [x[0] for x in + self._session.query(distinct(Results.VariableID)) + .filter(Results.FeatureActionID == FeatureActions.FeatureActionID) + .filter(FeatureActions.SamplingFeatureID == SamplingFeatures.SamplingFeatureID) + .filter(SamplingFeatures.SamplingFeatureCode == site_code).all() + ] except: var_ids = None - if not var_ids: - return None + q = self._session.query(Variables).filter(Variables.VariableID.in_(var_ids)) + return q.all() - Variables = [] +# Series Catalog methods + def get_series_by_site(self, site_id): + # try: + # selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() + # return selectedSeries + # except: + # return None + """ + :param site_id: type(Int) + :return: list[Series] + """ - #create list of variables from the list of ids - for var_id in var_ids: - Variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) + # return self.read.getResults(type="timeSeries", ids=[site_id]) + # return self.read.getResults(type="site", ids= [site_id])[0] + return self.read.getResults(ids=[site_id]) - return Variables + # Site methods + def get_all_sites(self): + """ + :return: List[Sites] + """ + # return self._edit_session.query(Site).order_by(Site.code).all() + return self.read.getResults(type="site") - def get_all_variables(self): + def get_site_by_id(self, site_id): + """ + return a Site object that has an id=site_id + :param site_id: integer- the identification number of the site + :return: Sites """ +# try: +# return self._edit_session.query(Site).filter_by(id=site_id).first() +# except: +# return None + return self.read.getSampling(ids=[site_id])[0] + + def get_all_variables(self): + """ :return: List[Variables] """ - return self._edit_session.query(Variable).all() + # return self._edit_session.query(Variable).all() + return self.read.getVariables() def get_variable_by_id(self, variable_id): """ - :param variable_id: int :return: Variables """ - try: - return self._edit_session.query(Variable).filter_by(id=variable_id).first() - except: - return None +# try: +# return self._edit_session.query(Variable).filter_by(id=variable_id).first() +# except: +# return None + return self.read.getVariables(ids=[variable_id])[0] def get_variable_by_code(self, variable_code): """ @@ -129,28 +172,11 @@ def get_variable_by_code(self, variable_code): :param variable_code: str :return: Variables """ - try: - return self._edit_session.query(Variable).filter_by(code=variable_code).first() - except: - return None - - def get_variables_by_site_code(self, site_code): # covers NoDV, VarUnits, TimeUnits - """ - Finds all of variables at a site - :param site_code: str - :return: List[Variables] - """ - try: - var_ids = [x[0] for x in self._edit_session.query(distinct(Series.variable_id)).filter_by( - site_code=site_code).all()] - except: - var_ids = None - - variables = [] - for var_id in var_ids: - variables.append(self._edit_session.query(Variable).filter_by(id=var_id).first()) - - return variables + # try: + # return self._edit_session.query(Variable).filter_by(code=variable_code).first() + # except: + # return None + return self.read.getVariables(codes=[variable_code])[0] # Unit methods def get_all_units(self): @@ -158,18 +184,19 @@ def get_all_units(self): :return: List[Units] """ - return self._edit_session.query(Unit).all() + # return self._edit_session.query(Unit).all() + return self.read.getUnits() def get_unit_by_name(self, unit_name): """ - :param unit_name: str :return: Units """ - try: - return self._edit_session.query(Unit).filter_by(name=unit_name).first() - except: - return None + # try: + # return self._edit_session.query(Unit).filter_by(name=unit_name).first() + # except: + # return None + return self.read.getUnits(name=[unit_name])[0] def get_unit_by_id(self, unit_id): """ @@ -177,207 +204,177 @@ def get_unit_by_id(self, unit_id): :param unit_id: int :return: Units """ - try: - return self._edit_session.query(Unit).filter_by(id=unit_id).first() - except: - return None - + # try: + # return self._edit_session.query(Unit).filter_by(id=unit_id).first() + # except: + # return None + return self.read.getUnits(ids=[unit_id])[0] def get_all_qualifiers(self): """ :return: List[Qualifiers] """ - result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() - return result + # result = self._edit_session.query(Qualifier).order_by(Qualifier.code).all() + # return result + ann= self.read.getAnnotations() + return ann def get_qualifier_by_code(self, code): """ :return: Qualifiers - """ - result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() - return result + # """ + # result = self._edit_session.query(Qualifier).filter(Qualifier.code==code).first() + # return result + return self.read.getAnnotations(codes=[code])[0] # todo: CHECK ON THIS def get_qualifiers_by_series_id(self, series_id): - """ + return self.read.getAnnotations(ids=[series_id])[0] # todo: check on this - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() - return self._edit_session.query(Qualifier).join(subquery).distinct().all() + def get_all_processing_levels(self): + return self.read.getProcessingLevels(ids=None, codes=None) - #QCL methods - def get_all_qcls(self): - return self._edit_session.query(QualityControlLevel).all() +# """ +# +# :param series_id: +# :return: +# """ +# subquery = self._edit_session.query(DataValue.qualifier_id).outerjoin( +# Series.data_values).filter(Series.id == series_id, DataValue.qualifier_id != None).distinct().subquery() +# return self._edit_session.query(Qualifier).join(subquery).distinct().all() +# - def get_qcl_by_id(self, qcl_id): + def get_processing_level_by_id(self, qcl_id): try: - return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() + return self.read.getProcessingLevels(ids=[qcl_id])[0] + # return self._edit_session.query(QualityControlLevel).filter_by(id=qcl_id).first() except: return None - def get_qcl_by_code(self, qcl_code): + def get_processing_level_by_code(self, codes): try: - return self._edit_session.query(QualityControlLevel).filter_by(code=qcl_code).first() + return self.read.getProcessingLevels(codes=[codes])[0] except: return None # Method methods def get_all_methods(self): - return self._edit_session.query(Method).all() + # return self._edit_session.query(Method).all() + return self.read.getMethods() def get_method_by_id(self, method_id): + return self.read.getMethods(ids=[method_id])[0] + # try: + # result = self._edit_session.query(Method).filter_by(id=method_id).first() + # except: + # result = None + # return result + + def get_method_by_code(self, method_code): try: - result = self._edit_session.query(Method).filter_by(id=method_id).first() - except: - result = None - return result - - def get_method_by_description(self, method_code): - try: - result = self._edit_session.query(Method).filter_by(description=method_code).first() + return self.read.getMethods(codes=[method_code])[0] except: - result = None - logger.error("method not found") - return result - - def get_offset_types_by_series_id(self, series_id): - """ - - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.offset_type_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.offset_type_id != None).distinct().subquery() - return self._edit_session.query(OffsetType).join(subquery).distinct().all() + return None - def get_samples_by_series_id(self, series_id): - """ + # try: + # result = self._edit_session.query(Method).filter_by(description=method_code).first() + # except: + # result = None + # logger.error("method not found") + # return result - :param series_id: - :return: - """ - subquery = self._edit_session.query(DataValue.sample_id).outerjoin( - Series.data_values).filter(Series.id == series_id, DataValue.sample_id != None).distinct().subquery() - return self._edit_session.query(Sample).join(subquery).distinct().all() + # todo: Take another look at this # Series Catalog methods - def get_all_series(self): - """ - Returns all series as a modelObject - :return: List[Series] - """ - - #logger.debug("%s" % self._edit_session.query(Series).order_by(Series.id).all()) - return self._edit_session.query(Series).order_by(Series.id).all() - - def get_series_by_site(self , site_id): - """ - - :param site_id: int - :return: List[Series] - """ - try: - selectedSeries = self._edit_session.query(Series).filter_by(site_id=site_id).order_by(Series.id).all() - return selectedSeries - except: - return None - - def get_series_by_id(self, series_id): + def resultExists(self, result): """ - :param series_id: int + :param result :return: Series """ - try: - return self._edit_session.query(Series).filter_by(id=series_id).first() - except Exception as e: - print e - return None - - def get_series_by_id_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ + # unique Result + # FeatureActionID, ResultTypeCV, VariableID, UnitsID, ProcessingLevelID, SampledMediumCV - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: Series - """ try: - return self._edit_session.query(Series).filter_by( - site_id=site_id, variable_id=var_id, method_id=method_id, - source_id=source_id, quality_control_level_id=qcl_id).first() + # return self._edit_session.query(Results).filter_by( + # VariableID=var_id, MethodID=method_id, + # AnnotationID=qcl_id).first() + setSchema(self._session_factory.engine) + ret = self._session.query(exists().where(Results.ResultTypeCV == result.ResultTypeCV) + .where(Results.VariableID == result.VariableID) + .where(Results.UnitsID == result.UnitsID) + .where(Results.ProcessingLevelID == result.ProcessingLevelID) + .where(Results.SampledMediumCV == result.SampledMediumCV) + ) + # where(Results.FeatureActionID == result.FeatureActionID). + return ret.scalar() + except: return None + def get_series_by_meta(self, result): + setSchema(self._session_factory.engine) + id = self.read._session.query(Results)\ + .filter_by(ResultTypeCV=result.ResultTypeCV)\ + .filter_by(VariableID=result.VariableID)\ + .filter_by(UnitsID=result.UnitsID)\ + .filter_by(ProcessingLevelID=result.ProcessingLevelID)\ + .filter_by(SampledMediumCV=result.SampledMediumCV) + return id.first() + def get_series_from_filter(self): # Pass in probably a Series object, match it against the database pass - - #Data Value Methods - def get_values_by_series(self, series_id): - ''' - - :param series_id: Series id + # Data Value Methods + def get_values(self, series_id=None): + """ + :param series_id: :return: pandas dataframe - ''' - series= self.get_series_by_id(series_id) - if series: - q = self._edit_session.query(DataValue).filter_by( - site_id=series.site_id, - variable_id=series.variable_id, - method_id=series.method_id, - source_id=series.source_id, - quality_control_level_id=series.quality_control_level_id) - - query=q.statement.compile(dialect=self._session_factory.engine.dialect) - data= pd.read_sql_query(sql= query, - con = self._session_factory.engine, - params = query.params ) - #return data.set_index(data['LocalDateTime']) - return data - else: - return None + """ + # see get_annotations_by_result around line 850 + setSchema(self._session_factory.engine) + q = self.read._session.query(TimeSeriesResultValues) + if series_id: + q = q.filter_by(ResultID=series_id) + q = q.order_by(TimeSeriesResultValues.ValueDateTime) + query = q.statement.compile(dialect=self._session_factory.engine.dialect) + data = pd.read_sql_query(sql=query, + con=self._session_factory.engine, + params=query.params) + data.set_index(data['valuedatetime'], inplace=True) + + return data def get_all_values_df(self): + """ :return: Pandas DataFrame object """ - q = self._edit_session.query(DataValue).order_by(DataValue.local_date_time) + q = self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime) query = q.statement.compile(dialect=self._session_factory.engine.dialect) data = pd.read_sql_query(sql=query, con=self._session_factory.engine, - params=query.params) - columns = list(data) - - columns.insert(0, columns.pop(columns.index("DataValue"))) - columns.insert(1, columns.pop(columns.index("LocalDateTime"))) - columns.insert(2, columns.pop(columns.index("QualifierID"))) + params=query.params) - data = data.ix[:, columns] - return data.set_index(data['LocalDateTime']) + return data.set_index(data['ValueDateTime']) def get_all_values_list(self): """ :return: """ - result = self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() + result = self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() return [x.list_repr() for x in result] def get_all_values(self): - return self._edit_session.query(DataValue).order_by(DataValue.local_date_time).all() + return self.read._session.query(TimeSeriesResultValues).order_by(TimeSeriesResultValues.ValueDateTime).all() @staticmethod def calcSeason(row): - month = int(row["Month"]) + month = int(row["month"]) if month in [1, 2, 3]: return 1 @@ -388,424 +385,455 @@ def calcSeason(row): elif month in [10, 11, 12]: return 4 - def get_all_plot_values(self): + def get_plot_values(self, seriesID, noDataValue, startDate=None, endDate=None): """ - - :return: - """ - q = self._edit_session.query(DataValue.data_value.label('DataValue'), - DataValue.local_date_time.label('LocalDateTime'), - DataValue.censor_code.label('CensorCode'), - func.strftime('%m', DataValue.local_date_time).label('Month'), - func.strftime('%Y', DataValue.local_date_time).label('Year') - #DataValue.local_date_time.strftime('%m'), - #DataValue.local_date_time.strftime('%Y')) - ).order_by(DataValue.local_date_time) - query = q.statement.compile(dialect=self._session_factory.engine.dialect) - data = pd.read_sql_query(sql=query, - con=self._session_factory.engine, - params=query.params) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data.set_index(data['LocalDateTime']) - - def get_plot_values(self, seriesID, noDataValue, startDate = None, endDate = None ): - """ - :param seriesID: :param noDataValue: :param startDate: :param endDate: :return: """ - series = self.get_series_by_id(seriesID) - - DataValues = [ - (dv.data_value, dv.local_date_time, dv.censor_code, dv.local_date_time.strftime('%m'), - dv.local_date_time.strftime('%Y')) - for dv in series.data_values - if dv.data_value != noDataValue if dv.local_date_time >= startDate if dv.local_date_time <= endDate - ] - data = pd.DataFrame(DataValues, columns=["DataValue", "LocalDateTime", "CensorCode", "Month", "Year"]) - data.set_index(data['LocalDateTime'], inplace=True) - data["Season"] = data.apply(self.calcSeason, axis=1) - return data - - - - def get_data_value_by_id(self, id): - """ + setSchema(self._session_factory.engine) + Values = self.get_values(seriesID) + data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] + # data = data[data['datavalue'] != noDataValue] + data = data[(data['datavalue'] != noDataValue) & (data['valuedatetime'] >= startDate) & ( + data['valuedatetime'] <= endDate)] - :param id: - :return: - """ - try: - return self._edit_session.query(DataValue).filter_by(id=id).first() - except: - return None + data["month"] = data['valuedatetime'].apply(lambda x: x.month) + data["year"] = data['valuedatetime'].apply(lambda x: x.year) + data["season"] = data.apply(self.calcSeason, axis=1) + return data + def get_all_plot_values(self): + setSchema(self._session_factory.engine) + Values = self.get_values() + data = Values[['datavalue', 'censorcodecv', 'valuedatetime']] + data["month"] = data['valuedatetime'].apply(lambda x: x.month) + data["year"] = data['valuedatetime'].apply(lambda x: x.year) + data["season"] = data.apply(self.calcSeason, axis=1) + return data ##################### # -#Update functions +# Update functions # ##################### - def update_series(self, series): - """ + def update_result(self, result): + # self.update.updateResult(result.ResultID, result.ValueCount) + self.update.updateResult(result=result) - :param series: - :return: - """ - merged_series = self._edit_session.merge(series) - self._edit_session.add(merged_series) - self._edit_session.commit() - def update_dvs(self, dv_list): - """ - - :param dv_list: - :return: - """ - merged_dv_list = map(self._edit_session.merge, dv_list) - self._edit_session.add_all(merged_dv_list) - self._edit_session.commit() + def update_action(self, action): + self.update.updateAction(action=action) ##################### # -#Create functions +# Create functions # ##################### - def save_series(self, series, dvs): - """ Save to an Existing Series - :param series: - :param data_values: - :return: - """ - - if self.series_exists(series): - - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - logger.info("Existing File was overwritten with new information") - return True - else: - logger.debug("There wasn't an existing file to overwrite, please select 'Save As' first") - # there wasn't an existing file to overwrite - raise Exception("Series does not exist, unable to save. Please select 'Save As'") - - - def save_new_series(self, series, dvs): - """ Create as a new catalog entry - :param series: - :param data_values: - :return: - """ - # Save As case - if self.series_exists(series): - msg = "There is already an existing file with this information. Please select 'Save' or 'Save Existing' to overwrite" - logger.info(msg) - raise Exception(msg) - else: - try: - self._edit_session.add(series) - self._edit_session.commit() - self.save_values(dvs) - #self._edit_session.add_all(dvs) - except Exception as e: - self._edit_session.rollback() - raise e - - logger.info("A new series was added to the database, series id: "+str(series.id)) - return True - - def save_values(self, values): + # new series + def create_result(self, var, proc, feature_action, aggcv, itsp, itspunit, status, type, units, medium): + + new_result = TimeSeriesResults() + + time, offset = self.get_current_time_and_utcoffset() + new_result.ResultDateTime = time + new_result.ResultDateTimeUTCOffset = offset + + # create TimeSeriesResult - this should also contain all of the stuff for the Result + new_result.ValueCount = 0 + new_result.FeatureActionID = feature_action + new_result.ResultDateTime = time + new_result.ResultDateTimeUTCOffset = offset + new_result.VariableID = var + new_result.ProcessingLevelID = proc + new_result.AggregationStatisticCV = aggcv + new_result.IntendedTimeSpacingUnitsID = itspunit + new_result.IntendedTimeSpacing = itsp + new_result.StatusCV = status + new_result.ResultTypeCV = type + new_result.UnitsID = units + new_result.SampledMediumCV = medium + + + self.create.createResult(result=new_result) + self._session.refresh(new_result) + return new_result + + + def create_action(self, methodid, description, filelink, begindate, utc, actionby): + new_action = Actions() + new_action.MethodID= methodid + new_action.ActionDescription = description + new_action.ActionFileLink = filelink + new_action.BeginDateTime = begindate + new_action.BeginDateTimeUTCOffset = utc + new_action.EndDateTime = None + new_action.EndDateTimeUTCOffset = None + new_action.ActionTypeCV = "Derivation" + + self.create.createAction(new_action) + action_by = new_action + action_by.ActionID = new_action.ActionID + action_by.IsActionLead = True + + self.create.createActionby(action_by) + + return new_action, actionby + + def createFeatureAction(self, sfid, actionid): + feature_action = FeatureActions() + + feature_action.SamplingFeatureID = sfid + feature_action.ActionID = actionid + + self.create.createFeatureAction(feature_action) + + return feature_action + + def get_current_time_and_utcoffset(self): + current_time = datetime.datetime.now() + utc_time = datetime.datetime.utcnow() + + difference_in_timezone = current_time - utc_time + offset_in_hours = difference_in_timezone.total_seconds() / 3600 + + return current_time, offset_in_hours + + def upsert_values(self, values): + setSchema(self._session_factory.engine) + query = self._get_df_query(values) + newvals= self.get_insert(df=values, query=query, dup_cols=["valuedatetime", "resultid"], + engine=self._session_factory.engine) + if not newvals.empty: + self.insert_values(newvals) + delvals = self.get_delete(df= values, query = query, dup_cols=["valuedatetime", "resultid"], + engine=self._session_factory.engine) + if not delvals.empty: + self.delete_dvs(delvals["valuedatetime"].tolist()) + + upvals = self.get_update(df=values, query=query, dup_cols=["valuedatetime", "resultid"], + engine=self._session_factory.engine) + if not upvals.empty: + self.update_values(upvals) + self._session.commit() + + def insert_values(self, values): """ - :param values: pandas dataframe :return: """ - values.to_sql(name="datavalues", if_exists='append', con=self._session_factory.engine, index=False) + setSchema(self._session_factory.engine) + values.to_sql(name=TimeSeriesResultValues.__tablename__, + schema=TimeSeriesResultValues.__table_args__['schema'], + if_exists='append', + chunksize=1000, + con=self._session_factory.engine, + index=False) - def create_new_series(self, data_values, site_id, variable_id, method_id, source_id, qcl_id): - """ - - :param data_values: - :param site_id: - :param variable_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ - self.update_dvs(data_values) - series = Series() - series.site_id = site_id - series.variable_id = variable_id - series.method_id = method_id - series.source_id = source_id - series.quality_control_level_id = qcl_id - self._edit_session.add(series) - self._edit_session.commit() - return series + def update_values(self, updates): + ''' + updates : time series result values, pandas dataframe + ''' + setSchema(self._session_factory.engine) + + stmt = (TimeSeriesResultValues.__table__.update(). + where(TimeSeriesResultValues.ValueDateTime == bindparam('id')). + values(datavalue=bindparam('value')) + ) + update_list = [{"value": row["datavalue"], "id": index.to_pydatetime()} for index, row in updates.iterrows()] + # update_list = {'value':updates["datavalue"].tolist(), 'id':updates.index.to_pydatetime().tolist()} + vals = self.create._session.execute(stmt, update_list) + + def _get_df_query(self, values): + resid = str(values['resultid'][0]) + startdate = values['valuedatetime'].min() + ed = values['valuedatetime'].max() + q = self.read._session.query(TimeSeriesResultValues)\ + .filter(TimeSeriesResultValues.ResultID == resid) + return q.statement.compile(dialect=self._session_factory.engine.dialect) def create_method(self, description, link): """ - :param description: :param link: :return: """ - meth = Method() - meth.description = description + method = Methods() + method.MethodDescription = description if link is not None: - meth.link = link + method.MethodLink = link - self._edit_session.add(meth) - self._edit_session.commit() - return meth - - def create_variable_by_var(self, var): - """ - - :param var: Variable Object - :return: - """ - try: - self._edit_session.add(var) - self._edit_session.commit() - return var - except: - return None + return self.create_service.createMethod(method=method) def create_variable( - self, code, name, speciation, variable_unit_id, sample_medium, - value_type, is_regular, time_support, time_unit_id, data_type, - general_category, no_data_value): + self, code, name, speciation, no_data_value): """ - :param code: :param name: :param speciation: - :param variable_unit_id: - :param sample_medium: - :param value_type: - :param is_regular: - :param time_support: - :param time_unit_id: - :param data_type: - :param general_category: + :param no_data_value: :return: """ - var = Variable() - var.code = code - var.name = name - var.speciation = speciation - var.variable_unit_id = variable_unit_id - var.sample_medium = sample_medium - var.value_type = value_type - var.is_regular = is_regular - var.time_support = time_support - var.time_unit_id = time_unit_id - var.data_type = data_type - var.general_category = general_category - var.no_data_value = no_data_value - self._edit_session.add(var) - self._edit_session.commit() - return var + variable = Variables() + variable.VariableCode = code + variable.VariableNameCV = name + variable.SpeciationCV = speciation - def create_qcl(self, code, definition, explanation): - """ + variable.NoDataValue = no_data_value + + return self.create.createVariable(var=variable) + def create_processing_level(self, code, definition, explanation): + """ + qcl -> Processing Level in ODM2 :param code: :param definition: :param explanation: :return: """ - qcl = QualityControlLevel() - qcl.code = code - qcl.definition = definition - qcl.explanation = explanation - - self._edit_session.add(qcl) - self._edit_session.commit() - return qcl - + procLevel = ProcessingLevels() + procLevel.ProcessingLevelCode = code + procLevel.Definition = definition + procLevel.Explanation = explanation + return self.create.createProcessingLevel(proclevel=procLevel) - def create_qualifier_by_qual(self, qualifier): - self._edit_session.add(qualifier) - self._edit_session.commit() - return qualifier + def create_annotation_by_anno(self, annotation): + return self.create.createAnnotations(annotation) - def create_qualifier(self, code, description): + def create_annotation(self, code, text, link=None): """ - :param code: - :param description: + :param text: + :param link: :return: """ - qual = Qualifier() - qual.code = code - qual.description = description + annotation = Annotations() + annotation.AnnotationCode = code + annotation.AnnotationText = text + annotation.AnnotationTypeCV = "Time series result value annotation" - return self.create_qualifier_by_qual(qual) + time, offset = self.get_current_time_and_utcoffset() + annotation.AnnotationDateTime = time + annotation.AnnotationUTCOffset = offset -##################### -# -# Delete functions -# -##################### + annotation.AnnotationLink = link - def delete_series(self, series): - """ + return self.create_annotation_by_anno(annotation) - :param series: - :return: - """ + def add_annotations(self, anno_list): + setSchema(self._session_factory.engine) try: - self.delete_values_by_series(series) - delete_series = self._edit_session.merge(series) - self._edit_session.delete(delete_series) - self._edit_session.commit() + anno_list.to_sql(name="TimeSeriesResultValueAnnotations", + schema=TimeSeriesResultValueAnnotations.__table_args__['schema'], + if_exists='append', + chunksize=1000, + con=self._session_factory.engine, + index=False) + self._session.commit() + + return anno_list except Exception as e: - message = "series was not successfully deleted: %s" % e - print message - logger.error(message) - raise e + print(e) + return None - def delete_values_by_series(self, series, startdate = None): - """ + def get_vertical_datum_cvs(self): + return self.read.getCVs(type="Elevation Datum") - :param series: - :return: - """ - try: - q= self._edit_session.query(DataValue).filter_by(site_id = series.site_id, - variable_id = series.variable_id, - method_id = series.method_id, - source_id = series.source_id, - quality_control_level_id = series.quality_control_level_id) - if startdate is not None: - #start date indicates what day you should start deleting values. the values will delete to the end of the series - return q.filter(DataValue.local_date_time >= startdate).delete() - else: - return q.delete() + def get_samples(self): + return self.read.getSamplingFeatures(ids=None, codes=None, uuids=None, type=None, wkt=None) - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex + def get_site_type_cvs(self): + return self.read.getCVs(type="Site Type") # OR return self.read.getCVs(type="Sampling Feature Type") - def delete_dvs(self, id_list): - """ + def get_variable_name_cvs(self): + return self.read.getCVs(type="Variable Name") - :param id_list: list of datetimes - :return: - """ - try: - self._edit_session.query(DataValue).filter(DataValue.local_date_time.in_(id_list)).delete(False) - except Exception as ex: - message = "Values were not successfully deleted: %s" % ex - print message - logger.error(message) - raise ex + def get_offset_type_cvs(self): + return self.read.getCVs(type="Spatial Offset Type") -##################### -# -#Exist functions -# -##################### + def get_speciation_cvs(self): + return self.read.getCVs(type="Speciation") + def get_sample_medium_cvs(self): + return self.read.getCVs(type="Medium") - def series_exists(self, series): - """ + def get_value_type_cvs(self): + return self.read.getCVs(type="Result Type") - :param series: - :return: - """ - return self.series_exists_quint( - series.site_id, - series.variable_id, - series.method_id, - series.source_id, - series.quality_control_level_id - ) + def get_data_type_cvs(self): + return self.read.getCVs(type="dataset type") - def series_exists_quint(self, site_id, var_id, method_id, source_id, qcl_id): - """ + def get_general_category_cvs(self): + return self.read.getAnnotations(type="categoricalresultvalue") - :param site_id: - :param var_id: - :param method_id: - :param source_id: - :param qcl_id: - :return: - """ + def get_censor_code_cvs(self): + return self.read.getCVs(type="censorcode") + + # def get_sample_type_cvs(self): + # return self.read.getCVs(type="Sampling Feature Type") + + def get_units(self): + return self.read.getUnits(ids=None, name=None, type=None) + + def get_units_not_uni(self): + return self._session.query(Units).filter(not_(Units.UnitsName.contains('angstrom'))).all() + + def get_units_names(self): + return self._session.query(Units.UnitsName).all() + + def get_quality_code(self): + return self.read.getCVs(type="Quality Code") + + def get_annotation_by_code(self, code): try: - result = self._edit_session.query(Series).filter_by( - site_id=site_id, - variable_id=var_id, - method_id=method_id, - source_id=source_id, - quality_control_level_id=qcl_id - ).one() - - return True + return self.read.getAnnotations(codes=[code])[0] except: - return False + return None - def qcl_exists(self, q): - """ + def get_annotation_by_id(self, id): + try: + return self.read.getAnnotations(ids=[id])[0] + except: + return None - :param q: - :return: - """ + def get_all_annotations(self): try: - result = self._edit_session.query(QualityControlLevel).filter_by(code=q.code, definition=q.definition).one() - return True + return self.read.getAnnotations(type=None) except: + return None + + def get_annotations_by_result(self, resultid): + resultid = int(resultid) + setSchema(self._session_factory.engine) - return False + q = self.read._session.query(TimeSeriesResultValueAnnotations.AnnotationID, TimeSeriesResultValueAnnotations.ValueID, + TimeSeriesResultValues.ResultID, TimeSeriesResultValues.ValueDateTime, Annotations.AnnotationCode)\ + .filter(TimeSeriesResultValues.ResultID == resultid)\ + .filter(TimeSeriesResultValueAnnotations.ValueID == TimeSeriesResultValues.ValueID)\ + .filter(Annotations.AnnotationID==TimeSeriesResultValueAnnotations.AnnotationID) - def method_exists(self, m): + query = q.statement.compile(dialect=self._session_factory.engine.dialect) + data = pd.read_sql_query(sql=query, con=self._session_factory.engine, + params=query.params) + return data + + def get_aggregation_statistic(self): + return self.read.getCVs(type="aggregationstatistic") + + def get_all_affiliations(self): + return self.read.getAffiliations(ids=None, personfirst=None, personlast=None, orgcode=None) + + ##################### + # + # Delete functions + # + ##################### + # + # def delete_series(self, series): + # """ + # + # :param series: + # :return: + # """ + # try: + # self.delete_values_by_series(series) + # + # delete_series = self._edit_session.merge(series) + # self._edit_session.delete(delete_series) + # self._edit_session.commit() + # except Exception as e: + # message = "series was not successfully deleted: %s" % e + # print message + # logger.error(message) + # raise e + # + # + def delete_values_by_series(self, seriesid, startdate=None): """ - :param m: + :param series: :return: """ + try: - result = self._edit_session.query(Method).filter_by(description=m.description).one() - return True - except: - return False + return self.delete.deleteTSRValues(ids=[seriesid], startdate=startdate) + except Exception as ex: + message = "Values were not successfully deleted: %s" % ex + print message + logger.error(message) + raise ex + - def variable_exists(self, v): + def delete_dvs(self, id_list): """ - :param v: + :param id_list: list of datetimes :return: """ + setSchema(self._session_factory.engine) try: - result = self._edit_session.query(Variable).filter_by(code=v.code, - name=v.name, speciation=v.speciation, - variable_unit_id=v.variable_unit_id, - sample_medium=v.sample_medium, - value_type=v.value_type, is_regular=v.is_regular, - time_support=v.time_support, - time_unit_id=v.time_unit_id, data_type=v.data_type, - general_category=v.general_category, - no_data_value=v.no_data_value).one() - return result - except: - return None \ No newline at end of file + self.delete.deleteTSRValues(dates=id_list) + except Exception as ex: + message = "Values were not successfully deleted: %s" % ex + print message + logger.error(message) + raise ex + + def get_values_by_series(self, series_id): + setSchema(self._session_factory.engine) + q = self.read._session.query(TimeSeriesResultValues) + if series_id: + q = q.filter_by(ResultID=series_id) + q = q.order_by(TimeSeriesResultValues.ValueDateTime) + + return q.all() + + def get_delete(self, df, engine, query, dup_cols=[]): + + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='right', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + + def get_update(self, df, engine, query, dup_cols=[]): + + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='inner', on=dup_cols, indicator=True) + + newdf.drop(['_merge'], axis=1, inplace=True) + test = newdf[newdf['datavalue_x'] != newdf['datavalue_y']] + return df[df['valuedatetime'].isin(test['valuedatetime'])] + + def get_insert(self, df, engine, query, dup_cols=[]): + """ + Remove rows from a dataframe that already exist in a database + Required: + df : dataframe to remove duplicate rows from + engine: SQLAlchemy engine object + tablename: tablename to check duplicates in + dup_cols: list or tuple of column names to check for duplicate row values + Optional: + filter_continuous_col: the name of the continuous data column for BETWEEEN min/max filter + can be either a datetime, int, or float data type + useful for restricting the database table size to check + filter_categorical_col : the name of the categorical data column for Where = value check + Creates an "IN ()" check on the unique values in this column + Returns + Unique list of values from dataframe compared to database table + """ + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'left_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + diff --git a/odmtools/odmservices/service_manager.py b/odmtools/odmservices/service_manager.py index 1ad5ff1..d32b767 100755 --- a/odmtools/odmservices/service_manager.py +++ b/odmtools/odmservices/service_manager.py @@ -4,31 +4,37 @@ import urllib -from sqlalchemy.exc import SQLAlchemyError#OperationalError, DBAPIError -from odmtools.common.logger import LoggerTool -from series_service import SeriesService -from cv_service import CVService +from sqlalchemy.exc import SQLAlchemyError +from series_service import SeriesService from edit_service import EditService -from odmtools.controller import EditTools from export_service import ExportService +from export_data import ExportData + + +from odmtools.controller import EditTools from odmtools.lib.Appdirs.appdirs import user_config_dir -from odmtools.odmdata.session_factory import SessionFactory + +from odmtools.odmdata import dbconnection #ODM + # tool = LoggerTool() # logger = tool.setupLogger(__name__, __name__ + '.log', 'w', logging.DEBUG) logger =logging.getLogger('main') + class ServiceManager(): - def __init__(self, debug=False, conn_dict = None): + def __init__(self, debug=False, conn_dict=None): self.debug = debug f = self._get_file('r') self._conn_dicts = [] - self.version = 0 + + #self.version = 0 self._connection_format = "%s+%s://%s:%s@%s/%s" # Read all lines (connections) in the connection.cfg file + if conn_dict is None: while True: line = f.readline() @@ -46,10 +52,13 @@ def __init__(self, debug=False, conn_dict = None): line_dict['password'] = line[2] line_dict['address'] = line[3] line_dict['db'] = line[4] + line_dict['version']= float(line[5]) if len(line)>5 else 1.1 self._conn_dicts.append(line_dict) else: self._conn_dicts.append(conn_dict) + + if len(self._conn_dicts) is not 0: # The current connection defaults to the most recent (i.e. the last written to the file) self._current_conn_dict = self._conn_dicts[-1] @@ -61,18 +70,6 @@ def __init__(self, debug=False, conn_dict = None): def get_all_conn_dicts(self): return self._conn_dicts - def is_valid_connection(self): - if self._current_conn_dict: - conn_string = self._build_connection_string(self._current_conn_dict) - logger.debug("Conn_string: %s" % conn_string) - try: - if self.testEngine(conn_string): - return self.get_current_conn_dict() - except Exception as e: - logger.fatal("The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) - return None - return None - def get_current_conn_dict(self): return self._current_conn_dict @@ -85,85 +82,100 @@ def add_connection(self, conn_dict): # remove earlier connections that are identical to this one self.delete_connection(conn_dict) - if self.test_connection(conn_dict): - # write changes to connection file - self._conn_dicts.append(conn_dict) - self._current_conn_dict = self._conn_dicts[-1] - self._save_connections() - return True - else: - logger.error("Unable to save connection due to invalid connection to database") - return False - - - @classmethod - def testEngine(self, connection_string): - s = SessionFactory(connection_string, echo=False) - if 'mssql' in connection_string: - s.ms_test_Session().execute("Select top 1 VariableCode From Variables") - elif 'mysql' in connection_string: - s.my_test_Session().execute('Select "VariableCode" From Variables Limit 1') - elif 'postgresql' in connection_string: - #s.psql_test_Session().execute('Select "VariableCode" From "ODM2"."Variables" Limit 1') - s.psql_test_Session().execute('Select "VariableCode" From "Variables" Limit 1') + + #assume connection has already been tested + # if self.test_connection(conn_dict): + # write changes to connection file + self._conn_dicts.append(conn_dict) + self._current_conn_dict = self._conn_dicts[-1] + self._save_connections() + return True + # else: + # logger.error("Unable to save connection due to invalid connection to database") + # return False def test_connection(self, conn_dict): + try: - conn_string = self._build_connection_string(conn_dict) - if self.testEngine(conn_string) and self.get_db_version(conn_string) == '1.1.1': - return True + if dbconnection.isValidConnection( + dbconnection.buildConnectionString(conn_dict['engine'], conn_dict['address'], conn_dict['db'], + conn_dict['user'], + conn_dict['password']), dbtype=conn_dict['version']): + return self.get_current_conn_dict() + # except Exception as e: + # logger.fatal( + # "The previous database for some reason isn't accessible, please enter a new connection %s" % e.message) + # return None except SQLAlchemyError as e: logger.error("SQLAlchemy Error: %s" % e.message) raise e except Exception as e: - logger.error("Error: %s" % e) + logger.error("The database is not accessible please enter a new connection. Error: %s" % e.message) raise e - return False + + + def is_valid_connection(self): + # conn_string = self._build_connection_string(self._current_conn_dict) + # logger.debug("Conn_string: %s" % conn_string) + + if self.get_current_conn_dict(): + conn_dict = self.get_current_conn_dict() + return self.test_connection(conn_dict) + + return None + def delete_connection(self, conn_dict): self._conn_dicts[:] = [x for x in self._conn_dicts if x != conn_dict] - # Create and return services based on the currently active connection - def get_db_version_dict(self, conn_dict): - conn_string = self._build_connection_string(conn_dict) - self.get_db_version(conn_string) + def get_series_service(self, conn_dict=None, conn_string=""): + #TODO check connection what if they are changing - def get_db_version(self, conn_string): - if isinstance(conn_string, dict): - conn_string = self._build_connection_string(conn_string) - service = SeriesService(conn_string) - #if not self.version: - try: - self.version = service.get_db_version() - except Exception as e: - logger.error("Exception: %s" % e.message) - return None - return self.version - - def get_series_service(self, conn_dict=""): - conn_string = "" - if conn_dict: - conn_string = self._build_connection_string(conn_dict) - self._current_conn_dict = conn_dict + if 'series_service' in locals():# or self.series_service is None): + return self.series_service else: - conn_string = self._build_connection_string(self._current_conn_dict) - return SeriesService(conn_string, self.debug) + return self._create_series_service( conn_dict, conn_string) - def get_cv_service(self): - conn_string = self._build_connection_string(self._current_conn_dict) - return CVService(conn_string, self.debug) + def _create_series_service(self, conn_dict=None, conn_string=""): + if not conn_dict: + conn_dict = self.get_current_conn_dict() - def get_edit_service(self, series_id, connection): + if conn_string: + #todo how to get version from a connection string + conn = dbconnection.createConnectionFromString(conn_string, 2.0)#float(self.get_current_conn_dict()["version"])) + else: + conn = dbconnection.createConnection(conn_dict['engine'], conn_dict['address'], conn_dict['db'], conn_dict['user'], + conn_dict['password'], conn_dict['version']) + + + # version = 1.1 + # if conn_dict: + # conn_string = self._build_connection_string(conn_dict) + # #self._current_conn_dict = conn_dict + # + # version = float(conn_dict['version']) + # elif not conn_dict and not conn_string: + # conn_string = self._build_connection_string(self._current_conn_dict) + # version = float(self._current_conn_dict['version']) + # + # sf = SessionFactory(conn_string, self.debug, version = version) + self.series_service= SeriesService(conn) + return self.series_service + def get_edit_service(self, series_id, connection): return EditService(series_id, connection=connection, debug=self.debug) + # todo: Not using build_connection_string. Need to update this def get_record_service(self, script, series_id, connection): - return EditTools(self, script, self.get_edit_service(series_id, connection), - self._build_connection_string(self.is_valid_connection())) + + # return EditTools(self, script, self.get_edit_service(series_id, connection), + # connection) + return EditTools(script, self.get_edit_service(series_id, connection), connection_string=connection) + def get_export_service(self): - return ExportService(self.get_series_service()) + return ExportData(self.get_series_service()) ## ################### # private variables @@ -186,64 +198,13 @@ def _get_file(self, mode): open(fn, 'w').close() config_file = open(fn, mode) - return config_file - - def _build_connection_string(self, conn_dict): - - self._connection_format = "%s+%s://%s:%s@%s/%s" - - if conn_dict['engine'] == 'mssql' and sys.platform != 'win32': - driver = "pyodbc" - quoted = urllib.quote_plus('DRIVER={FreeTDS};DSN=%s;UID=%s;PWD=%s;' % (conn_dict['address'], conn_dict['user'], - conn_dict['password'])) - # quoted = urllib.quote_plus('DRIVER={FreeTDS};DSN=%s;UID=%s;PWD=%s;DATABASE=%s' % - # (conn_dict['address'], conn_dict['user'], conn_dict['password'],conn_dict['db'], - # )) - conn_string = 'mssql+pyodbc:///?odbc_connect={}'.format(quoted) - - elif conn_dict['engine']=='sqlite': - connformat = "%s:///%s" - conn_string = connformat%(conn_dict['engine'], conn_dict['address']) - else: - if conn_dict['engine'] == 'mssql': - driver = "pyodbc" - conn = "%s+%s://%s:%s@%s/%s?driver=SQL+Server" - if "sqlncli11.dll" in os.listdir("C:\\Windows\\System32"): - conn = "%s+%s://%s:%s@%s/%s?driver=SQL+Server+Native+Client+11.0" - self._connection_format = conn - conn_string = self._connection_format % ( - conn_dict['engine'], driver, conn_dict['user'], conn_dict['password'], conn_dict['address'], - conn_dict['db']) - elif conn_dict['engine'] == 'mysql': - driver = "pymysql" - conn_string = self.constringBuilder(conn_dict, driver) - elif conn_dict['engine'] == 'postgresql': - driver = "psycopg2" - conn_string = self.constringBuilder(conn_dict, driver) - else: - driver = "None" - conn_string = self.constringBuilder(conn_dict, driver) - - - # print "******", conn_string - return conn_string - + return config_file - def constringBuilder(self, conn_dict, driver): - if conn_dict['password'] is None or not conn_dict['password']: - conn_string = self._connection_format_nopassword % ( - conn_dict['engine'], driver, conn_dict['user'], conn_dict['address'], - conn_dict['db']) - else: - conn_string = self._connection_format % ( - conn_dict['engine'], driver, conn_dict['user'], conn_dict['password'], conn_dict['address'], - conn_dict['db']) - return conn_string def _save_connections(self): f = self._get_file('w') for conn in self._conn_dicts: - f.write("%s %s %s %s %s\n" % (conn['engine'], conn['user'], conn['password'], conn['address'], conn['db'])) + f.write("%s %s %s %s %s %s\n" % (conn['engine'], conn['user'], conn['password'], conn['address'], conn['db'], conn['version'])) f.close() diff --git a/odmtools/odmservices/to_sql_newrows.py b/odmtools/odmservices/to_sql_newrows.py new file mode 100644 index 0000000..e8cac3c --- /dev/null +++ b/odmtools/odmservices/to_sql_newrows.py @@ -0,0 +1,221 @@ +import os +import sys +import time +import pandas as pd +import numpy as np +from sqlalchemy import create_engine +import threading +from timeit import default_timer as timer + +os.path.dirname(os.path.abspath(__file__)) + + +# def get_df_query(df, tablename, dup_cols, filter_continuous_col=None, filter_categorical_col=None, filter_equal_col= None): +# +# +# +# args = 'SELECT %s FROM %s' % (', '.join(['"{0}"'.format(col) for col in dup_cols]), tablename) +# args_contin_filter, args_cat_filter, args_eq_filter = None, None, None +# if filter_continuous_col is not None: +# if df[filter_continuous_col].dtype == 'datetime64[ns]': +# args_contin_filter = """ "%s" BETWEEN Convert(datetime, '%s') +# AND Convert(datetime, '%s')""" % (filter_continuous_col, +# df[filter_continuous_col].min(), +# df[filter_continuous_col].max()) +# +# if filter_categorical_col is not None: +# args_cat_filter = ' "%s" in(%s)' % (filter_categorical_col, +# ', '.join(["'{0}'".format(value) for value in +# df[filter_categorical_col].unique()])) +# # if filter_equal_col is not None: +# # args_eq_filter = ' "%s" = %(s)' %(filter_categorical_col, df) +# +# if args_contin_filter and args_cat_filter: +# args += ' Where ' + args_contin_filter + ' AND' + args_cat_filter +# elif args_contin_filter: +# args += ' Where ' + args_contin_filter +# elif args_cat_filter: +# args += ' Where ' + args_cat_filter +# +# return args + +def get_delete(df, engine, query, dup_cols=[]): + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='right', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + +def get_update(df, engine, query, dup_cols=[]): + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='inner', on=dup_cols, indicator=True) + #newdf = newdf[newdf['_merge'] == 'right_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + test = newdf[newdf['datavalue_x'] != newdf['datavalue_y']] + return df[df['valuedatetime'].isin(test['valuedatetime'])] + +def get_insert(df, engine, query, dup_cols=[]): + """ + Remove rows from a dataframe that already exist in a database + Required: + df : dataframe to remove duplicate rows from + engine: SQLAlchemy engine object + tablename: tablename to check duplicates in + dup_cols: list or tuple of column names to check for duplicate row values + Optional: + filter_continuous_col: the name of the continuous data column for BETWEEEN min/max filter + can be either a datetime, int, or float data type + useful for restricting the database table size to check + filter_categorical_col : the name of the categorical data column for Where = value check + Creates an "IN ()" check on the unique values in this column + Returns + Unique list of values from dataframe compared to database table + """ + + #query = get_df_query(df, tablename, dup_cols, filter_continuous_col=filter_continuous_col, filter_categorical_col=filter_categorical_col, filter_equal_col= filter_equal_col) + df.drop_duplicates(dup_cols, keep='last', inplace=True) + newdf = pd.merge(df, pd.read_sql(query, engine), how='left', on=dup_cols, indicator=True) + newdf = newdf[newdf['_merge'] == 'left_only'] + newdf.drop(['_merge'], axis=1, inplace=True) + return df[df['valuedatetime'].isin(newdf['valuedatetime'])] + + +def to_sql_newrows(df, pool_size, *args, **kargs): + """ + Extend the Python pandas to_sql() method to thread database insertion + + Required: + df : pandas dataframe to insert new rows into a database table + POOL_SIZE : your sqlalchemy max connection pool size. Set < your db connection limit. + Example where this matters: your cloud DB has a connection limit. + *args: + Pandas to_sql() arguments. + + Required arguments are: + tablename : Database table name to write results to + engine : SqlAlchemy engine + + Optional arguments are: + 'if_exists' : 'append' or 'replace'. If table already exists, use append. + 'index' : True or False. True if you want to write index values to the db. + + + Credits for intial threading code: + http://techyoubaji.blogspot.com/2015/10/speed-up-pandas-tosql-with.html + """ + + CHUNKSIZE = 1000 + INITIAL_CHUNK = 100 + if len(df) > CHUNKSIZE: + # write the initial chunk to the database if df is bigger than chunksize + df.iloc[:INITIAL_CHUNK, :].to_sql(*args, **kargs) + else: + # if df is smaller than chunksize, just write it to the db now + df.to_sql(*args, **kargs) + + workers, i = [], 0 + + for i in range((df.shape[0] - INITIAL_CHUNK) / CHUNKSIZE): + t = threading.Thread( + target=lambda: df.iloc[INITIAL_CHUNK + i * CHUNKSIZE:INITIAL_CHUNK + (i + 1) * CHUNKSIZE].to_sql(*args, + **kargs)) + t.start() + workers.append(t) + + df.iloc[INITIAL_CHUNK + (i + 1) * CHUNKSIZE:, :].to_sql(*args, **kargs) + [t.join() for t in workers] + + + + +def setup(engine, tablename): + engine.execute("""DROP TABLE IF EXISTS "%s" """ % (tablename)) + + engine.execute("""CREATE TABLE "%s" ( + "A" INTEGER, + "B" INTEGER, + "C" INTEGER, + "D" INTEGER, + CONSTRAINT pk_A_B PRIMARY KEY ("A","B")) + """ % (tablename)) + + +if __name__ == '__main__': + DB_TYPE = 'postgresql' + DB_DRIVER = 'psycopg2' + DB_USER = 'admin' + DB_PASS = 'password' + DB_HOST = 'localhost' + DB_PORT = '5432' + DB_NAME = 'pandas_upsert' + POOL_SIZE = 50 + TABLENAME = 'test_upsert' + SQLALCHEMY_DATABASE_URI = '%s+%s://%s:%s@%s:%s/%s' % (DB_TYPE, DB_DRIVER, DB_USER, + DB_PASS, DB_HOST, DB_PORT, DB_NAME) + ENGINE = create_engine( + SQLALCHEMY_DATABASE_URI, pool_size=POOL_SIZE, max_overflow=0) + + print 'setting up db' + setup(ENGINE, TABLENAME) + + try: + i = 0 + prev = timer() + start = timer() + for i in range(10): + print 'running test %s' % (str(i)) + df = pd.DataFrame( + np.random.randint(0, 500, size=(100000, 4)), columns=list('ABCD')) + df = get_insert(df, TABLENAME, ENGINE, dup_cols=['A', 'B']) + print 'row count after drop db duplicates is now : %s' % (df.shape[0]) + df.to_sql(TABLENAME, ENGINE, if_exists='append', index=False) + end = timer() + elapsed_time = end - prev + prev = timer() + print 'completed loop in %s sec!' % (elapsed_time) + i += 1 + end = timer() + elapsed_time = end - start + print 'completed singlethread insert loops in %s sec!' % (elapsed_time) + inserted = pd.read_sql('SELECT count("A") from %s' % (TABLENAME), ENGINE) + print 'inserted %s new rows into database!' % (inserted.iloc[0]['count']) + + print '\n setting up db' + setup(ENGINE, TABLENAME) + print '\n' + + i = 0 + prev = timer() + start = timer() + for i in range(10): + print 'running test %s' % (str(i)) + df = pd.DataFrame( + np.random.randint(0, 500, size=(100000, 4)), columns=list('ABCD')) + df.drop_duplicates(['A', 'B'], keep='last', inplace=True) + df.to_sql('temp', ENGINE, if_exists='replace', index=False) + connection = ENGINE.connect() + args1 = """ INSERT INTO "test_upsert" + SELECT * FROM + (SELECT a.* + FROM "temp" a LEFT OUTER JOIN "test_upsert" b + ON (a."A" = b."A" and a."B"=b."B") + WHERE b."A" is null) b""" + result = connection.execute(args1) + args2 = """ DROP Table If Exists "temp" """ + connection.execute(args2) + connection.close() + end = timer() + elapsed_time = end - prev + prev = timer() + print 'completed loop in %s sec!' % (elapsed_time) + i += 1 + end = timer() + elapsed_time = end - start + print 'completed staging insert loops in %s sec!' % (elapsed_time) + inserted = pd.read_sql('SELECT count("A") from %s' % (TABLENAME), ENGINE) + print 'inserted %s new rows into database!' % (inserted.iloc[0]['count']) + + except KeyboardInterrupt: + print("Interrupted... exiting...") \ No newline at end of file diff --git a/odmtools/view/clsBulkInsert.py b/odmtools/view/BulkInsertView.py similarity index 88% rename from odmtools/view/clsBulkInsert.py rename to odmtools/view/BulkInsertView.py index 2b9a033..a605070 100644 --- a/odmtools/view/clsBulkInsert.py +++ b/odmtools/view/BulkInsertView.py @@ -22,7 +22,7 @@ ## Class BulkInsert ########################################################################### -class BulkInsert(wx.Dialog): +class BulkInsertView(wx.Dialog): def __init__(self, parent): wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title="- ODMTools Bulk Insert -", size=(400, 600)) @@ -30,12 +30,12 @@ def __init__(self, parent): self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize) self.uploadBtn = GB.GradientButton(mainPanel, wx.ID_ANY, upload_2_32.GetBitmap(), ' Upload') - self.templateBtn = GB.GradientButton(mainPanel, wx.ID_ANY, downloading_updates_32.GetBitmap(), ' Download') + self.downloadTemplateButton = GB.GradientButton(mainPanel, wx.ID_ANY, downloading_updates_32.GetBitmap(), ' Download') self.closeBtn = GB.GradientButton(mainPanel, wx.ID_ANY, close_window_32.GetBitmap(), " Close") self._initSizers(mainPanel) self.Bind(wx.EVT_BUTTON, self.onUpload, self.uploadBtn) - self.Bind(wx.EVT_BUTTON, self.onTemplate, self.templateBtn) + self.Bind(wx.EVT_BUTTON, self.onDownloadTemplateButton, self.downloadTemplateButton) self.Bind(wx.EVT_BUTTON, self.onClose, self.closeBtn) def _initSizers(self, mainPanel): @@ -54,7 +54,7 @@ def _initSizers(self, mainPanel): sbTemplateSizer = wx.StaticBoxSizer(wx.StaticBox(mainPanel, wx.ID_ANY, u"Download CSV Template"), wx.VERTICAL) fgTemplateSizer = wx.FlexGridSizer(0, 2, 0, 0) fgTemplateSizer.SetFlexibleDirection(wx.VERTICAL) - fgTemplateSizer.Add(self.templateBtn, 0, wx.ALL, 5) + fgTemplateSizer.Add(self.downloadTemplateButton, 0, wx.ALL, 5) sbTemplateSizer.Add(fgTemplateSizer, 1, wx.ALL | wx.ALIGN_CENTER_HORIZONTAL, 5) @@ -78,7 +78,7 @@ def __del__(self): ## Virtual event handlers, overridden in inherited class def onUpload(self, event): event.Skip() - def onTemplate(self, event): + def onDownloadTemplateButton(self, event): event.Skip() def onClose(self, event): event.Skip() diff --git a/odmtools/view/CustomCollapsiblePanel.py b/odmtools/view/CustomCollapsiblePanel.py new file mode 100644 index 0000000..95e3fb4 --- /dev/null +++ b/odmtools/view/CustomCollapsiblePanel.py @@ -0,0 +1,130 @@ +import wx + + +class CustomCollapsiblePanel(wx.Panel): + def __init__(self, parent, title="Sample Title", expand=0, use_combo=False, combo_trigger_item=-1): + wx.Panel.__init__(self, parent) + self.__master_sizer = wx.BoxSizer(wx.VERTICAL) + + self.parent = parent # parent of this panel + self.title = title + self.is_expanded = expand # is_expanded status + self.__using_combo = use_combo + self.trigger_item = combo_trigger_item + + # this will be the main sizer for this panel + self.vbox = wx.BoxSizer(wx.VERTICAL) + + # this sizer contains is_expanded button and title of frame + self.hbox = wx.BoxSizer(wx.VERTICAL) + + # self.interactive_item is the item that is interacted with to is_expanded or collapse the panel + if self.__using_combo: + self.interactive_item = wx.ComboBox(self, style=wx.CB_READONLY, name="interactive_item") + self.interactive_item.Bind(wx.EVT_COMBOBOX, self.on_interactive_item) + else: + self.interactive_item = wx.Button(self, label=title, size=(-1, 18), style=wx.BU_LEFT | wx.STATIC_BORDER, name='interactive_item') + self.interactive_item.Bind(wx.EVT_BUTTON, self.on_interactive_item) + + # self.lbl = wx.StaticText(self, -1, size=(-1, 5), name='cplbl') + + self.hbox.Add(self.interactive_item, 1, wx.EXPAND) + # self.hbox.Add(self.lbl, 0) + + # add to main sizer(vbox) + self.vbox.Add(self.hbox, 0, wx.EXPAND) + + def on_interactive_item(self, event=None): + """ + Case for combo box: If the selected item matches the trigger item then expand, otherwise collapse + Case for button: collapse if expanded and expand if collapsed + :param event: + :return: + """ + if isinstance(self.interactive_item, wx.ComboBox): + if self.interactive_item.GetStringSelection() == self.trigger_item: + self.expand_panel() + else: + self.collapse_panel() + else: + if self.is_expanded: + self.collapse_panel() + else: + self.expand_panel() + + if event: + event.Skip() + + def expand_panel(self): + self.is_expanded = True + self.__redraw_panel() + self.GetTopLevelParent().SetSize((400, 300)) + + def __redraw_panel(self): + for child in self.GetChildren(): + if child.GetName() == "interactive_item": + continue + child.Show(self.is_expanded) + self.parent.Layout() + self.parent.SendSizeEvent() # make scrollbars visible if parent is scrolledWindow and if they are required automatically + # self.lbl.SetFocus() # Remove focus from button when pressed + self.parent.Refresh() + + def collapse_panel(self): + self.is_expanded = False + self.__redraw_panel() + self.GetTopLevelParent().SetSize((400, 150)) + + def finish_layout(self): + + allSizers = [] + childSizer = None + # Get all the sizers containing all the children of this panel + for child in self.GetChildren(): + if child.GetName() == 'interactive_item' or child.GetName() == 'cplbl': + continue + + childSizer = child.GetContainingSizer() + if childSizer != None: + # add the sizer in the list if it's no there. + # can't use set as it changes the order of elements + # this way we can have unique sizer or not repeating ones + if not childSizer in allSizers: + allSizers.append(childSizer) + + # Get root level sizers and add to main sizer name 'vbox' + if len(allSizers): + for sizer in self.getRootSizers(allSizers): + self.vbox.Add(sizer, 0, wx.EXPAND) + else: + print 'children of this panel are not in any sizers. They should be in a sizer/s' + + # When deleting this panel in any case, masterSizer is also getting deleted. we have to create it again + if not isinstance(self.__master_sizer, wx._core.BoxSizer): + self.__master_sizer = wx.BoxSizer(wx.VERTICAL) + + self.__master_sizer.Add(self, 0, wx.EXPAND) + + # Rearrange everything + self.SetSizer(self.vbox) + self.Fit() + self.on_interactive_item() + + def getRootSizers(self, sizerList): + ''' + 'sizerList' contains many sizers and may possible nested sizers or sizers added inside another sizers. + This function process the list and returns only root level sizers. + We'll add only root level sizers to main sizer of this class name 'vbox'. + ''' + finalList = sizerList[:] + copyList = sizerList[:] + + for sizer in copyList: + if len(sizer.GetChildren()): + for child in sizer.GetChildren(): + if child.GetClassName() == 'wxSizerItem': + try: + finalList.remove(child.GetSizer()) + except: + pass + return finalList diff --git a/odmtools/view/CustomListCtrl.py b/odmtools/view/CustomListCtrl.py new file mode 100644 index 0000000..4dc2e9b --- /dev/null +++ b/odmtools/view/CustomListCtrl.py @@ -0,0 +1,177 @@ +import wx +import sys + + +# Place the CustomListCtrl on a panel so it does not grow horizontally +class CustomListCtrl(wx.ListCtrl): + def __init__(self, panel): + wx.ListCtrl.__init__(self, panel, style=wx.LC_REPORT) + self._auto_width_style = wx.LIST_AUTOSIZE + if sys.platform == "win32": + self._auto_width_style = wx.LIST_AUTOSIZE_USEHEADER + + # Message to show in the ListCtrl when it is empty + self.empty_list_message = wx.StaticText(parent=self, label="This list is empty", + style=wx.ALIGN_CENTER | wx.ST_NO_AUTORESIZE | wx.FULL_REPAINT_ON_RESIZE) + self.empty_list_message.Hide() + self.empty_list_message.SetForegroundColour(wx.LIGHT_GREY) + self.empty_list_message.SetBackgroundColour(self.GetBackgroundColour()) + self.empty_list_message.SetFont(wx.Font(24, wx.DEFAULT, wx.NORMAL, wx.NORMAL, 0, "")) + self.Bind(wx.EVT_LIST_COL_CLICK, self.on_list_column_clicked) + + def on_list_column_clicked(self, event): + self.__sort_table_by_column(event.GetColumn()) + + def alternate_row_color(self, color="#DCEBEE"): + for i in range(self.GetItemCount()): + if i % 2 == 0: + self.SetItemBackgroundColour(i, color) + + def auto_size_table(self): + for i in range(self.GetColumnCount()): + self.SetColumnWidth(col=i, width=self._auto_width_style) + + def clear_table(self): + """ + Clears everything in the table including the header names + :return: + """ + self.ClearAll() + + def clear_content(self): + """ + Clears everything in the table except the header names + :return: + """ + self.DeleteAllItems() + + def get_all_selected_rows(self): + """ + Gets all the selected rows + :return: 2D list, list(rows) + """ + index = self.GetFirstSelected() + if index == -1: + return [] # No selected row + + data = [self.get_row_at_index(index)] + for i in range(self.GetSelectedItemCount() - 1): + index = self.GetNextSelected(index) + data.append(self.get_row_at_index(index)) + + return data + + def get_row_at_index(self, index): + """ + Given a row number, return all the data related to the row + :param index: int + :return: list + """ + if index > self.GetItemCount(): + return [] # There are not that many rows in the table + + data = [] + for i in range(self.GetColumnCount()): + data.append(self.GetItem(index, i).GetText()) + return data + + def get_all_data_in_table(self): + data = [] + for i in range(self.GetItemCount()): + data.append(self.get_row_at_index(i)) + return data + + def __sort_table_by_column(self, column_number): + """ + Flip flop between sorting by ascending and descending order. + :param column_number: type(int) + :return: + """ + if column_number < 0: + return + + data = self.get_all_data_in_table() + + column_data = [] + for row in data: + column_data.append(row[column_number]) + + if sorted(column_data) == column_data: + column_data.reverse() + else: + column_data.sort() + + new_data = [] + for i in range(len(column_data)): + for j in range(len(data)): + if column_data[i] in data[j]: + if data[j] not in new_data: + new_data.append(data[j]) + break + + # write the data back + self.clear_content() + self.set_table_content(new_data) + + def get_selected_row(self): + """ + Gets the first selected row + :return: data: type(list). Return None if no row is selected + """ + row_number = self.GetFirstSelected() + if row_number == -1: + return None + + data = [] + for i in range(self.GetColumnCount()): + data.append(self.GetItem(row_number, i).GetText()) + return data + + def remove_selected_row(self): + """ + Only removes the top selected row + :return: + """ + row_number = self.GetFirstSelected() + if row_number == -1: + return # No row is selected + + self.DeleteItem(row_number) + + def set_columns(self, columns): + """ + Sets the name of the columns + :param columns: a list of strings + :return: + """ + self.clear_table() + for i in range(len(columns)): + self.InsertColumn(i, columns[i], width=wx.LIST_AUTOSIZE_USEHEADER) + + def set_empty_message_text(self, text): + self.empty_list_message.SetLabel(text) + + def set_table_content(self, data): + """ + data must be a 2D list [[row1 column1, row1 column2, ...], [row2, column1, row2 column2, ...]] + :param data: 2D list + :return: + """ + + number_of_columns = self.GetColumnCount() + if number_of_columns == 0: + print "No column headers have been created" + return + + # loop through all of the site metadata + for i in range(len(data)): + index = self.InsertStringItem(999999, "") + if number_of_columns < len(data[i]): + raise Exception("The length of the row must match the number of columns") + + for j in range(len(data[i])): + self.SetStringItem(index, j, str(data[i][j])) + + self.Select(0) + self.auto_size_table() + self.alternate_row_color() \ No newline at end of file diff --git a/odmtools/view/NewFlagValuesView.py b/odmtools/view/NewFlagValuesView.py new file mode 100644 index 0000000..850cdc6 --- /dev/null +++ b/odmtools/view/NewFlagValuesView.py @@ -0,0 +1,74 @@ +import wx +from odmtools.view.CustomCollapsiblePanel import CustomCollapsiblePanel +from wx.lib.scrolledpanel import ScrolledPanel + + +class NewFlagValuesView(wx.Frame): + def __init__(self, parent): + wx.Frame.__init__(self, parent, title="Flag Values", style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX) + + panel = wx.Panel(self) + content_panel = ScrolledPanel(panel) + bottom_panel = wx.Panel(panel) + + ########################################## + # CONTENT PANEL + ########################################## + content_panel.SetupScrolling() + + annotation_title = wx.StaticText(content_panel, label="Annotation") + self.collapsible_panel = CustomCollapsiblePanel(content_panel, title="Panel 1", expand=0, use_combo=True, combo_trigger_item="New Annontation") + self.annotation_combo = self.collapsible_panel.interactive_item + + code_title = wx.StaticText(self.collapsible_panel, label="Code") + self.code_textbox = wx.TextCtrl(self.collapsible_panel, size=(100, -1)) + text_title = wx.StaticText(self.collapsible_panel, label="Text") + self.text_textbox = wx.TextCtrl(self.collapsible_panel) + link_text = wx.StaticText(self.collapsible_panel, label="Link") + self.link_textbox = wx.TextCtrl(self.collapsible_panel) + + sizer = wx.BoxSizer(wx.VERTICAL) + content_panel_sizer = wx.BoxSizer(wx.VERTICAL) + content_panel_sizer.Add(code_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + content_panel_sizer.Add(self.code_textbox, 0, wx.LEFT | wx.RIGHT, 10) + content_panel_sizer.Add(text_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + content_panel_sizer.Add(self.text_textbox , 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + content_panel_sizer.Add(link_text, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + content_panel_sizer.Add(self.link_textbox, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + + self.collapsible_panel.finish_layout() + + sizer.Add(annotation_title, 0, wx.EXPAND | wx.ALL ^ wx.BOTTOM, 10) + sizer.Add(self.collapsible_panel, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10) + content_panel.SetSizer(sizer) + + ########################################## + # BOTTOM PANEL + ########################################## + + self.ok_button = wx.Button(bottom_panel, label="OK") + self.cancel_button = wx.Button(bottom_panel, label="CANCEL") + static_line = wx.StaticLine(bottom_panel) + self.cancel_button.SetDefault() + + bottom_panel_sizer = wx.BoxSizer(wx.VERTICAL) + button_sizer = wx.BoxSizer(wx.HORIZONTAL) + button_sizer.AddSpacer((0, 0), 1, wx.EXPAND, 2) + button_sizer.Add(self.ok_button, 0, wx.EXPAND | wx.ALL ^ wx.RIGHT, 5) + button_sizer.Add(self.cancel_button, 0, wx.EXPAND | wx.ALL, 5) + + bottom_panel_sizer.Add(static_line, 0, wx.EXPAND) + bottom_panel_sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT) + + bottom_panel.SetSizer(bottom_panel_sizer) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(content_panel, 1, wx.EXPAND | wx.ALL, 0) + main_sizer.Add(bottom_panel, 0, wx.EXPAND | wx.ALL, 0) + + panel.SetSizer(main_sizer) + main_sizer.Fit(self) + self.SetSize((400, 150)) + + + diff --git a/odmtools/view/WizardActionView.py b/odmtools/view/WizardActionView.py new file mode 100644 index 0000000..6a0be7c --- /dev/null +++ b/odmtools/view/WizardActionView.py @@ -0,0 +1,64 @@ +import wx +from odmtools.view.CustomListCtrl import CustomListCtrl +import wx.lib.scrolledpanel + + +class WizardActionView(wx.lib.scrolledpanel.ScrolledPanel): + def __init__(self, parent): + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) + + # Header + header_text = wx.StaticText(self, label="Action") + static_line = wx.StaticLine(self, size=(-1, 12)) + + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + + # REQUIRED FIELDS + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), + orient=wx.VERTICAL) + affiliations_text = wx.StaticText(self, label="Affiliations") + self.affiliations_table = CustomListCtrl(self) + + self.affiliations_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(affiliations_text, 0, wx.EXPAND) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(self.affiliations_table, 1, wx.EXPAND) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + # OPTIONAL FIELDS + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), + orient=wx.VERTICAL) + action_file_link_text = wx.StaticText(self, label="Action File Link") + self.action_file_link_text_box = wx.TextCtrl(self) + description_text = wx.StaticText(self, label="Description") + self.description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) + role_description_text = wx.StaticText(self, label="Role Description") + self.role_description_text_box = wx.TextCtrl(self, style=wx.TE_MULTILINE) + + flex_grid_sizer = wx.FlexGridSizer(rows=3, cols=2, vgap=9, hgap=25) + + flex_grid_sizer.AddMany([(action_file_link_text), (self.action_file_link_text_box, 1, wx.EXPAND), + (description_text), (self.description_text_box, 1, wx.EXPAND), + (role_description_text), (self.role_description_text_box, 1, wx.EXPAND) + ]) + + flex_grid_sizer.AddGrowableRow(1, 1) + flex_grid_sizer.AddGrowableCol(1, 1) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(flex_grid_sizer, 1, wx.EXPAND) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + master_sizer = wx.BoxSizer(wx.VERTICAL) + master_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + master_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + master_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 5) + master_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 5) + + self.SetSizer(master_sizer) + # master_sizer.Fit(self) diff --git a/odmtools/view/WizardMethodView.py b/odmtools/view/WizardMethodView.py new file mode 100644 index 0000000..8f357fe --- /dev/null +++ b/odmtools/view/WizardMethodView.py @@ -0,0 +1,88 @@ +import wx +import wx.lib.scrolledpanel +from odmtools.view.CustomListCtrl import CustomListCtrl + + +class WizardMethodView(wx.lib.scrolledpanel.ScrolledPanel): + def __init__(self, parent): + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) + + # Create components + header_text = wx.StaticText(self, label="Method") + static_line = wx.StaticLine(self, size=(-1, 12)) + self.auto_method_radio = wx.RadioButton(self, label="Automatically generate a method") + self.existing_method_radio = wx.RadioButton(self, label="Select an existing method") + table_panel = wx.Panel(self) + self.existing_method_table = CustomListCtrl(table_panel) + self.create_method_radio = wx.RadioButton(self, label="Create a new method") + + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + method_code_text = wx.StaticText(self, label="Method Code") + self.method_code_text_ctrl = wx.TextCtrl(self) + method_name_text = wx.StaticText(self, label="Method Name") + self.method_name_text_ctrl = wx.TextCtrl(self) + method_type_text = wx.StaticText(self, label="Method Type") + self.method_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) + + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + organization_text = wx.StaticText(self, label="Organization") + self.organization_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) + method_link_text = wx.StaticText(self, label="Method Link") + self.method_link_text_ctrl = wx.TextCtrl(self) + description_text = wx.StaticText(self, label="Description") + self.description_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + + # Style Components + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + self.method_type_combo.SetSelection(0) + self.organization_combo.SetSelection(0) + self.SetupScrolling() + self.existing_method_radio.SetValue(True) + self.existing_method_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) + + # Add components to sizer + table_sizer = wx.BoxSizer() + table_sizer.Add(self.existing_method_table, 0, wx.EXPAND | wx.ALL, 0) + table_panel.SetSizerAndFit(table_sizer) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_code_text, 0, wx.EXPAND) + row_sizer.Add(self.method_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 24) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_name_text, 0, wx.EXPAND) + row_sizer.Add(self.method_name_text_ctrl, 1, wx.EXPAND | wx.LEFT, 20) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_type_text, 0, wx.EXPAND) + row_sizer.Add(self.method_type_combo, 1, wx.EXPAND | wx.LEFT, 26) + required_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(organization_text, 0, wx.EXPAND) + row_sizer.Add(self.organization_combo, 1, wx.EXPAND | wx.LEFT, 27) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(method_link_text, 0, wx.EXPAND) + row_sizer.Add(self.method_link_text_ctrl, 1, wx.EXPAND | wx.LEFT, 27) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(description_text, 0, wx.EXPAND) + row_sizer.Add(self.description_text_ctrl, 1, wx.EXPAND | wx.LEFT, 34) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.auto_method_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.existing_method_radio, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.create_method_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + self.SetSizer(main_sizer) diff --git a/odmtools/view/WizardProcessLevelView.py b/odmtools/view/WizardProcessLevelView.py new file mode 100644 index 0000000..39d4509 --- /dev/null +++ b/odmtools/view/WizardProcessLevelView.py @@ -0,0 +1,61 @@ +import wx +import wx.lib.scrolledpanel +from odmtools.view.CustomListCtrl import CustomListCtrl + + +class WizardProcessLevelView(wx.lib.scrolledpanel.ScrolledPanel): + def __init__(self, parent): + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) + + # Create components + header_text = wx.StaticText(self, label="Processing Level") + static_line = wx.StaticLine(self, size=(-1, 12)) + self.existing_process_radio = wx.RadioButton(self, label="Select an existing Processing Level") + table_panel = wx.Panel(self) + self.existing_process_table = CustomListCtrl(table_panel) + self.create_process_level_radio = wx.RadioButton(self, label="Create Processing Level") + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + level_code = wx.StaticText(self, label="Processing Level Code") + self.level_code_text_ctrl = wx.TextCtrl(self) + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + definition_text = wx.StaticText(self, label="Definition") + self.definition_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + explanation_text = wx.StaticText(self, label="Explanation") + self.explanation_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + + # Style components + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + self.SetupScrolling() + self.existing_process_radio.SetValue(True) + self.existing_process_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) + + # Add components to sizer + table_sizer = wx.BoxSizer(wx.VERTICAL) + table_sizer.Add(self.existing_process_table, 0, wx.EXPAND | wx.ALL, 0) + table_panel.SetSizerAndFit(table_sizer) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(level_code, 0, wx.EXPAND) + row_sizer.Add(self.level_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 30) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(definition_text, 0, wx.EXPAND) + row_sizer.Add(self.definition_text_ctrl, 1, wx.EXPAND | wx.LEFT, 95) + optional_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(explanation_text, 0, wx.EXPAND) + row_sizer.Add(self.explanation_text_ctrl, 1, wx.EXPAND | wx.LEFT, 85) + optional_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.existing_process_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.create_process_level_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + self.SetSizer(main_sizer) diff --git a/odmtools/view/WizardVariableView.py b/odmtools/view/WizardVariableView.py new file mode 100644 index 0000000..2a2fb55 --- /dev/null +++ b/odmtools/view/WizardVariableView.py @@ -0,0 +1,88 @@ +import wx +import wx.lib.scrolledpanel +from odmtools.view.CustomListCtrl import CustomListCtrl + + +class WizardVariableView(wx.lib.scrolledpanel.ScrolledPanel): + def __init__(self, parent): + wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent) + + # Create components + header_text = wx.StaticText(self, label="Variable") + static_line = wx.StaticLine(self, size=(-1, 12)) + table_panel = wx.Panel(self) + self.current_variable_radio = wx.RadioButton(self, label="Use current variable") + self.variable_table = CustomListCtrl(table_panel) + self.existing_variable_radio = wx.RadioButton(self, label="Select an existing variable") + self.create_variable_radio = wx.RadioButton(self, label="Create new variable") + required_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Required Fields"), orient=wx.VERTICAL) + variable_code_text = wx.StaticText(self, label="Variable Code") + self.variable_code_text_ctrl = wx.TextCtrl(self) + variable_name_text = wx.StaticText(self, label="Variable Name") + self.variable_name_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) + variable_type_text = wx.StaticText(self, label="Variable Type") + self.variable_type_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY )#| wx.CB_SORT) + no_data_value_text = wx.StaticText(self, label="No Data Value") + self.no_data_value_text_ctrl = wx.TextCtrl(self, value="-9999") + + optional_static_box_sizer = wx.StaticBoxSizer(box=wx.StaticBox(self, label="Optional Fields"), orient=wx.VERTICAL) + speciation_text = wx.StaticText(self, label="Speciation") + self.speciation_combo = wx.ComboBox(self, choices=["---"], style=wx.CB_READONLY)# | wx.CB_SORT) + definition_text = wx.StaticText(self, label="Definition") + self.definition_text_ctrl = wx.TextCtrl(self, size=(-1, 75)) + + # Style Components + font = wx.Font(18, wx.SWISS, wx.NORMAL, wx.BOLD) + header_text.SetFont(font) + self.variable_name_combo.SetSelection(0) + self.speciation_combo.SetSelection(0) + self.variable_type_combo.SetSelection(0) + self.SetupScrolling() + self.current_variable_radio.SetValue(True) + self.variable_table.SetSingleStyle(wx.LC_SINGLE_SEL, add=True) + + # Add components to sizer + table_sizer = wx.BoxSizer() + table_sizer.Add(self.variable_table, 0, wx.EXPAND | wx.ALL, 0) + table_panel.SetSizerAndFit(table_sizer) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(variable_code_text, 0, wx.EXPAND) + row_sizer.Add(self.variable_code_text_ctrl, 1, wx.EXPAND | wx.LEFT, 30) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(variable_name_text, 0, wx.EXPAND) + row_sizer.Add(self.variable_name_combo, 1, wx.EXPAND | wx.LEFT, 27) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(variable_type_text, 0, wx.EXPAND) + row_sizer.Add(self.variable_type_combo, 1, wx.EXPAND | wx.LEFT, 33) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(no_data_value_text, 0, wx.EXPAND) + row_sizer.Add(self.no_data_value_text_ctrl, 1, wx.EXPAND | wx.LEFT, 29) + required_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(speciation_text, 0, wx.EXPAND) + row_sizer.Add(self.speciation_combo, 1, wx.EXPAND | wx.LEFT, 48) + optional_static_box_sizer.Add(row_sizer, 0, wx.EXPAND | wx.ALL, 5) + + row_sizer = wx.BoxSizer(wx.HORIZONTAL) + row_sizer.Add(definition_text, 0, wx.EXPAND) + row_sizer.Add(self.definition_text_ctrl, 1, wx.EXPAND | wx.LEFT, 52) + optional_static_box_sizer.Add(row_sizer, 1, wx.EXPAND | wx.ALL, 5) + + main_sizer = wx.BoxSizer(wx.VERTICAL) + main_sizer.Add(header_text, 0, wx.ALIGN_CENTER | wx.ALL, 5) + main_sizer.Add(static_line, 0, wx.EXPAND | wx.TOP, 5) + main_sizer.Add(self.current_variable_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.existing_variable_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(table_panel, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(self.create_variable_radio, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(required_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + main_sizer.Add(optional_static_box_sizer, 0, wx.EXPAND | wx.TOP, 10) + self.SetSizer(main_sizer) diff --git a/odmtools/view/clsAddPoints.py b/odmtools/view/clsAddPoints.py old mode 100755 new mode 100644 index 3ecf4e8..89aff19 --- a/odmtools/view/clsAddPoints.py +++ b/odmtools/view/clsAddPoints.py @@ -144,7 +144,7 @@ def onEditFinish(self, event): #print "Finished Editing Cell!", event.subItemIndex def onColClick(self, event): - ## Ignore col clicking + ## Ignore columns clicking pass def __del__(self): diff --git a/odmtools/view/clsCreateVariable.py b/odmtools/view/clsCreateVariable.py index 17615eb..430c83d 100644 --- a/odmtools/view/clsCreateVariable.py +++ b/odmtools/view/clsCreateVariable.py @@ -44,7 +44,7 @@ def __init__(self, parent): cbVarNameChoices = [] self.cbVarName = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbVarNameChoices, wx.CB_READONLY | wx.CB_SORT) + cbVarNameChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer1.Add(self.cbVarName, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL | wx.EXPAND, 5) self.stUnits = wx.StaticText(self, wx.ID_ANY, u"Units:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -55,7 +55,7 @@ def __init__(self, parent): cbVarUnitsChoices = [] self.cbVarUnits = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbVarUnitsChoices, wx.CB_READONLY | wx.CB_SORT) + cbVarUnitsChoices, wx.CB_READONLY )#| wx.CB_SORT) bSizer21.Add(self.cbVarUnits, 0, wx.ALL | wx.EXPAND, 5) self.stSpeciation = wx.StaticText(self, wx.ID_ANY, u"Speciation:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -64,7 +64,7 @@ def __init__(self, parent): cbSpeciationChoices = [] self.cbSpeciation = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbSpeciationChoices, wx.CB_READONLY | wx.CB_SORT) + cbSpeciationChoices, wx.CB_READONLY)# | wx.CB_SORT) bSizer21.Add(self.cbSpeciation, 0, wx.ALL | wx.EXPAND, 5) fgSizer1.Add(bSizer21, 1, wx.EXPAND, 5) @@ -90,7 +90,7 @@ def __init__(self, parent): cbTSUnitsChoices = [] self.cbTSUnits = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbTSUnitsChoices, wx.CB_READONLY | wx.CB_SORT) + cbTSUnitsChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer3.Add(self.cbTSUnits, 0, wx.ALL | wx.EXPAND, 5) sbSizer3.Add(fgSizer3, 1, wx.ALL | wx.EXPAND, 5) @@ -107,7 +107,7 @@ def __init__(self, parent): cbValueTypeChoices = [] self.cbValueType = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbValueTypeChoices, wx.CB_READONLY | wx.CB_SORT) + cbValueTypeChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer6.Add(self.cbValueType, 0, wx.ALL | wx.EXPAND, 5) self.stDataType = wx.StaticText(self, wx.ID_ANY, u"Data Type:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -116,7 +116,7 @@ def __init__(self, parent): cbDataTypeChoices = [] self.cbDataType = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbDataTypeChoices, wx.CB_READONLY | wx.CB_SORT) + cbDataTypeChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer6.Add(self.cbDataType, 0, wx.ALL | wx.EXPAND, 5) self.stGenCat = wx.StaticText(self, wx.ID_ANY, u"General Category:", wx.DefaultPosition, wx.DefaultSize, 0) @@ -143,7 +143,7 @@ def __init__(self, parent): cbSampleMediumChoices = [] self.cbSampleMedium = wx.combo.OwnerDrawnComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, - cbSampleMediumChoices, wx.CB_READONLY | wx.CB_SORT) + cbSampleMediumChoices, wx.CB_READONLY )#| wx.CB_SORT) fgSizer6.Add(self.cbSampleMedium, 0, wx.ALL | wx.EXPAND, 5) self.stReg = wx.StaticText(self, wx.ID_ANY, u"Is Regular:", wx.DefaultPosition, wx.DefaultSize, 0) diff --git a/odmtools/view/clsDBConfig.py b/odmtools/view/clsDBConfig.py index 4d2490e..8c3d0e8 100644 --- a/odmtools/view/clsDBConfig.py +++ b/odmtools/view/clsDBConfig.py @@ -1,4 +1,140 @@ -# -*- coding: utf-8 -*- +# # -*- coding: utf-8 -*- +# +# # ########################################################################## +# ## Python code generated with wxFormBuilder (version Jun 5 2014) +# ## http://www.wxformbuilder.org/ +# ## +# ## PLEASE DO "NOT" EDIT THIS FILE! +# ########################################################################### +# +# import wx +# import wx.xrc +# +# ########################################################################### +# ## Class clsDBConfiguration +# ########################################################################### +# +# class clsDBConfiguration(wx.Panel): +# def __init__(self, parent): +# +# wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 291), style=wx.SIMPLE_BORDER | wx.TAB_TRAVERSAL) +# +# self.SetMinSize(wx.Size(442, 291)) +# self.SetMaxSize(wx.Size(627, 291)) +# +# formSizer = wx.BoxSizer(wx.VERTICAL) +# +# sbSizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"Database Connection"), wx.VERTICAL) +# +# connectionSizer = wx.FlexGridSizer(0, 2, 0, 15) +# connectionSizer.AddGrowableCol(1) +# connectionSizer.SetFlexibleDirection(wx.VERTICAL) +# connectionSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) +# +# self.stConnType = wx.StaticText(self, wx.ID_ANY, u"Connection Type:", wx.DefaultPosition, wx.DefaultSize, +# wx.ALIGN_RIGHT) +# self.stConnType.Wrap(-1) +# connectionSizer.Add(self.stConnType, 0, wx.ALL | wx.EXPAND, 5) +# +# cbDatabaseTypeChoices = [] +# self.cbDatabaseType = wx.ComboBox(self, wx.ID_ANY, u"Combo!", wx.DefaultPosition, wx.DefaultSize, +# cbDatabaseTypeChoices, wx.CB_READONLY) +# connectionSizer.Add(self.cbDatabaseType, 1, wx.ALL | wx.EXPAND, 5) +# +# self.stServer = wx.StaticText(self, wx.ID_ANY, u"Server:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stServer.Wrap(-1) +# connectionSizer.Add(self.stServer, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtServer = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# 0 | wx.FULL_REPAINT_ON_RESIZE | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtServer, 1, wx.ALL | wx.EXPAND, 5) +# +# self.stDBName = wx.StaticText(self, wx.ID_ANY, u"Database:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stDBName.Wrap(-1) +# self.stDBName.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) +# +# connectionSizer.Add(self.stDBName, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtDBName = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# 0 | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtDBName, 0, wx.ALL | wx.EXPAND, 5) +# +# self.stUser = wx.StaticText(self, wx.ID_ANY, u"User:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stUser.Wrap(-1) +# self.stUser.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) +# +# connectionSizer.Add(self.stUser, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtUser = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# 0 | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtUser, 0, wx.ALL | wx.EXPAND, 5) +# +# self.stPass = wx.StaticText(self, wx.ID_ANY, u"Password:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) +# self.stPass.Wrap(-1) +# self.stPass.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) +# +# connectionSizer.Add(self.stPass, 0, wx.ALL | wx.EXPAND, 5) +# +# self.txtPass = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, +# wx.TE_PASSWORD | wx.SIMPLE_BORDER) +# connectionSizer.Add(self.txtPass, 0, wx.ALL | wx.EXPAND, 5) +# +# sbSizer.Add(connectionSizer, 1, wx.EXPAND, 5) +# +# formSizer.Add(sbSizer, 1, wx.EXPAND, 20) +# +# btnSizer = wx.FlexGridSizer(0, 3, 0, 25) +# btnSizer.AddGrowableCol(0) +# btnSizer.AddGrowableCol(1) +# btnSizer.AddGrowableCol(2) +# btnSizer.SetFlexibleDirection(wx.VERTICAL) +# btnSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) +# +# self.btnTest = wx.Button(self, wx.ID_ANY, u"Test Connection", wx.DefaultPosition, wx.DefaultSize, 0) +# btnSizer.Add(self.btnTest, 0, wx.ALL | wx.EXPAND, 5) +# +# self.btnSave = wx.Button(self, wx.ID_ANY, u"Save Connection", wx.DefaultPosition, wx.DefaultSize, 0) +# self.btnSave.Enable(False) +# +# btnSizer.Add(self.btnSave, 0, wx.ALL | wx.EXPAND, 5) +# +# self.btnCancel = wx.Button(self, wx.ID_ANY, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0) +# btnSizer.Add(self.btnCancel, 0, wx.ALL | wx.EXPAND, 5) +# +# formSizer.Add(btnSizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED, 15) +# +# self.SetSizer(formSizer) +# self.Layout() +# +# # Connect Events +# self.cbDatabaseType.Bind(wx.EVT_COMBOBOX, self.OnValueChanged) +# self.txtServer.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.txtDBName.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.txtUser.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.txtPass.Bind(wx.EVT_TEXT, self.OnValueChanged) +# self.btnTest.Bind(wx.EVT_BUTTON, self.OnBtnTest) +# self.btnSave.Bind(wx.EVT_BUTTON, self.OnBtnSave) +# self.btnCancel.Bind(wx.EVT_BUTTON, self.OnBtnCancel) +# +# def __del__(self): +# pass +# +# +# # Virtual event handlers, overide them in your derived class +# def OnValueChanged(self, event): +# event.Skip() +# +# +# def OnBtnTest(self, event): +# event.Skip() +# +# def OnBtnSave(self, event): +# event.Skip() +# +# def OnBtnCancel(self, event): +# event.Skip() +# +# -*- coding: utf-8 -*- # ########################################################################## ## Python code generated with wxFormBuilder (version Jun 5 2014) @@ -16,7 +152,6 @@ class clsDBConfiguration(wx.Panel): def __init__(self, parent): - wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 291), style=wx.SIMPLE_BORDER | wx.TAB_TRAVERSAL) @@ -32,19 +167,31 @@ def __init__(self, parent): connectionSizer.SetFlexibleDirection(wx.VERTICAL) connectionSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) - self.stConnType = wx.StaticText(self, wx.ID_ANY, u"Connection Type:", wx.DefaultPosition, wx.DefaultSize, + self.stVersion = wx.StaticText(self, wx.ID_ANY, u"DB Version:", wx.DefaultPosition, wx.DefaultSize, + wx.ALIGN_RIGHT) + self.stVersion.Wrap(-1) + connectionSizer.Add(self.stVersion, 0, wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND, 5) + + # version_choices = [ u"1.1", u"2.0"] + version_choices = [u"2.0"] + self.cbVersion = wx.ComboBox(self, wx.ID_ANY, u"2.0", wx.DefaultPosition, wx.DefaultSize, + version_choices, wx.CB_READONLY )#| wx.CB_SORT) + self.cbVersion.SetSelection(0) + connectionSizer.Add(self.cbVersion, 1, wx.ALL | wx.EXPAND, 5) + + self.stConnType = wx.StaticText(self, wx.ID_ANY, u"ODM Version:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stConnType.Wrap(-1) - connectionSizer.Add(self.stConnType, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stConnType, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) cbDatabaseTypeChoices = [] - self.cbDatabaseType = wx.ComboBox(self, wx.ID_ANY, u"Combo!", wx.DefaultPosition, wx.DefaultSize, - cbDatabaseTypeChoices, wx.CB_READONLY) + self.cbDatabaseType = wx.ComboBox(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, + cbDatabaseTypeChoices, wx.CB_READONLY )#| wx.CB_SORT) connectionSizer.Add(self.cbDatabaseType, 1, wx.ALL | wx.EXPAND, 5) self.stServer = wx.StaticText(self, wx.ID_ANY, u"Server:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stServer.Wrap(-1) - connectionSizer.Add(self.stServer, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stServer, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtServer = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 | wx.FULL_REPAINT_ON_RESIZE | wx.SIMPLE_BORDER) @@ -54,35 +201,35 @@ def __init__(self, parent): self.stDBName.Wrap(-1) self.stDBName.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) - connectionSizer.Add(self.stDBName, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stDBName, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtDBName = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 | wx.SIMPLE_BORDER) - connectionSizer.Add(self.txtDBName, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.txtDBName, 1, wx.ALL | wx.EXPAND, 5) self.stUser = wx.StaticText(self, wx.ID_ANY, u"User:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stUser.Wrap(-1) self.stUser.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) - connectionSizer.Add(self.stUser, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stUser, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtUser = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, 0 | wx.SIMPLE_BORDER) - connectionSizer.Add(self.txtUser, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.txtUser, 1, wx.ALL | wx.EXPAND, 5) self.stPass = wx.StaticText(self, wx.ID_ANY, u"Password:", wx.DefaultPosition, wx.DefaultSize, wx.ALIGN_RIGHT) self.stPass.Wrap(-1) self.stPass.SetFont(wx.Font(wx.NORMAL_FONT.GetPointSize(), 70, 90, 90, False, wx.EmptyString)) - connectionSizer.Add(self.stPass, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.stPass, 0, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5) self.txtPass = wx.TextCtrl(self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PASSWORD | wx.SIMPLE_BORDER) - connectionSizer.Add(self.txtPass, 0, wx.ALL | wx.EXPAND, 5) + connectionSizer.Add(self.txtPass, 1, wx.ALL | wx.EXPAND, 5) - sbSizer.Add(connectionSizer, 1, wx.EXPAND, 5) + sbSizer.Add(connectionSizer, 90, wx.EXPAND, 3) - formSizer.Add(sbSizer, 1, wx.EXPAND, 20) + formSizer.Add(sbSizer, 1, wx.ALL | wx.EXPAND, 7) btnSizer = wx.FlexGridSizer(0, 3, 0, 25) btnSizer.AddGrowableCol(0) @@ -92,17 +239,15 @@ def __init__(self, parent): btnSizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_ALL) self.btnTest = wx.Button(self, wx.ID_ANY, u"Test Connection", wx.DefaultPosition, wx.DefaultSize, 0) - btnSizer.Add(self.btnTest, 0, wx.ALL | wx.EXPAND, 5) + btnSizer.Add(self.btnTest, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5) self.btnSave = wx.Button(self, wx.ID_ANY, u"Save Connection", wx.DefaultPosition, wx.DefaultSize, 0) - self.btnSave.Enable(False) - - btnSizer.Add(self.btnSave, 0, wx.ALL | wx.EXPAND, 5) + btnSizer.Add(self.btnSave, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5) self.btnCancel = wx.Button(self, wx.ID_ANY, u"Cancel", wx.DefaultPosition, wx.DefaultSize, 0) - btnSizer.Add(self.btnCancel, 0, wx.ALL | wx.EXPAND, 5) + btnSizer.Add(self.btnCancel, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5) - formSizer.Add(btnSizer, 1, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.SHAPED, 15) + formSizer.Add(btnSizer, 10, wx.EXPAND, 2) self.SetSizer(formSizer) self.Layout() @@ -120,12 +265,10 @@ def __init__(self, parent): def __del__(self): pass - # Virtual event handlers, overide them in your derived class def OnValueChanged(self, event): event.Skip() - def OnBtnTest(self, event): event.Skip() @@ -135,3 +278,4 @@ def OnBtnSave(self, event): def OnBtnCancel(self, event): event.Skip() + diff --git a/odmtools/view/clsIntro.py b/odmtools/view/clsIntro.py index 8d9b338..2239e3b 100644 --- a/odmtools/view/clsIntro.py +++ b/odmtools/view/clsIntro.py @@ -34,7 +34,7 @@ # # bSizer2 = wx.BoxSizer(wx.VERTICAL) # -# self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series?", +# self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series_service?", # wx.DefaultPosition, wx.DefaultSize, 0) # self.lblHow.Wrap(-1) # bSizer2.Add(self.lblHow, 0, wx.ALL, 15) @@ -139,7 +139,7 @@ def __init__(self, parent): bSizer2 = wx.BoxSizer(wx.VERTICAL) - self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series?", + self.lblHow = wx.StaticText(self.m_panel2, wx.ID_ANY, u"How would you like to save the series_service?", wx.DefaultPosition, wx.DefaultSize, 0) self.lblHow.Wrap(-1) bSizer2.Add(self.lblHow, 0, wx.ALL, 15) diff --git a/odmtools/view/clsSeriesSelector.py b/odmtools/view/clsSeriesSelector.py old mode 100755 new mode 100644 index d47f689..e76eba5 --- a/odmtools/view/clsSeriesSelector.py +++ b/odmtools/view/clsSeriesSelector.py @@ -1,297 +1,297 @@ -__author__ = 'Jacob' - -import wx -from odmtools.controller.olvSeriesSelector import EVT_OVL_CHECK_EVENT -from odmtools.controller import olvSeriesSelector -from odmtools.odmservices import ServiceManager - -[wxID_PNLSERIESSELECTOR, wxID_PNLSERIESSELECTORCBSITES, wxID_PNLSERIESSELECTORCBVARIABLES, - wxID_PNLSERIESSELECTORCHECKSITE, wxID_PNLSERIESSELECTORCHECKVARIABLE, wxID_PNLSERIESSELECTORLBLSITE, - wxID_PNLSERIESSELECTORLBLVARIABLE, wxID_PNLSERIESSELECTORtableSeries, wxID_PNLSERIESSELECTORPANEL1, - wxID_PNLSERIESSELECTORPANEL2, wxID_PNLSIMPLE, wxID_PNLRADIO, wxID_FRAME1RBADVANCED, wxID_FRAME1RBALL, - wxID_FRAME1RBSIMPLE, wxID_FRAME1SPLITTER, wxID_PNLSPLITTER, wxID_PNLSERIESSELECTORtableSeriesTest,wxID_CLEAR, ] = [ - wx.NewId() for _init_ctrls in range(19)] - -class ClsSeriesSelector(wx.Panel): - - def __init__(self, parent, dbservice): - - self.parent = parent - wx.Panel.__init__(self, name=u'pnlSeriesSelector', parent=parent, - size=wx.Size(935, 270), style=wx.TAB_TRAVERSAL) - self._init_ctrls() - self.series_service = dbservice - self.initTableSeries() - self.initSVBoxes() - # Subscribe functions - self.initPubSub() - self.service_manager = ServiceManager() - self.export_service = self.service_manager.get_export_service() - self.selectedIndex = 0 - self.isEditing = False - - - ## Radio Sizer - def _init_coll_boxSizer5_Items(self, parent): - # generated method, don't edit - - pnlSpacer= wx.Panel(id=0, name='pnlSpacer', parent=self.pnlRadio, pos=wx.Point(3, 3), - size=wx.Size(0, 25), style=wx.TAB_TRAVERSAL) - parent.AddWindow(self.rbAll, 0, border=1, flag=wx.ALL) - parent.AddWindow(self.rbSimple, 0, border=1, flag=wx.ALL) - parent.AddWindow(self.rbAdvanced, 0, border=1, flag=wx.ALL) - parent.AddWindow(pnlSpacer, 100, border=1, flag=wx.EXPAND) - - parent.AddWindow(self.btnClear, 0, border=1, flag=wx.ALL) - - - ## Splitter Sizer - def _init_coll_boxSizer3_Items(self, parent): - # generated method, don't edit - parent.AddWindow(self.cpnlSimple, 0, flag=wx.RIGHT | wx.LEFT | wx.EXPAND) - parent.AddWindow(self.tblSeries, 100, flag=wx.EXPAND) - - ## Panel Sizer - def _init_coll_boxSizer1_Items(self, parent): - # generated method, don't edit - parent.AddSizer(self.pnlRadio, 0, border=7, flag=wx.LEFT | wx.RIGHT | wx.TOP) - parent.AddWindow(self.pnlData, 100, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) - - ## Site Sizer - def _init_coll_boxSizer4_Items(self, parent): - # generated method, don't edit - parent.AddWindow(self.checkSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.lblSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.cbSites, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) - - ## Variable Sizer - def _init_coll_boxSizer2_Items(self, parent): - # generated method, don't edit - parent.AddWindow(self.checkVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.lblVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) - parent.AddWindow(self.cbVariables, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) - - ## Simple Filter Sizer - def _init_coll_boxSizer6_Items(self, parent): - parent.AddWindow(self.pnlSite, 50, flag=wx.EXPAND) - parent.AddWindow(self.pnlVar, 50, flag=wx.EXPAND) - # parent.AddSizer(self.boxSizer4, 0, border=5, flag=wx.EXPAND) - # parent.AddSizer(self.boxSizer2, 0, border=5, flag=wx.EXPAND) - - - def _init_sizers(self): - # generated method, don't edit - boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL) - boxSizer2 = wx.BoxSizer(orient=wx.HORIZONTAL) - boxSizer3 = wx.BoxSizer(orient=wx.VERTICAL) - boxSizer4 = wx.BoxSizer(orient=wx.HORIZONTAL) - boxSizer5 = wx.BoxSizer(orient=wx.HORIZONTAL) - boxSizer6 = wx.BoxSizer(orient=wx.VERTICAL) - - self._init_coll_boxSizer1_Items(boxSizer1) - self._init_coll_boxSizer2_Items(boxSizer2) - self._init_coll_boxSizer3_Items(boxSizer3) - self._init_coll_boxSizer4_Items(boxSizer4) - self._init_coll_boxSizer5_Items(boxSizer5) - self._init_coll_boxSizer6_Items(boxSizer6) - - self.SetSizer(boxSizer1) - self.pnlRadio.SetSizer(boxSizer5) - self.pnlSite.SetSizer(boxSizer4) - self.pnlVar.SetSizer(boxSizer2) - self.cpnlSimple.SetSizer(boxSizer6) - self.pnlData.SetSizer(boxSizer3) - - def _init_ctrls(self): - # generated method, don't edit - - - self.SetClientSize(wx.Size(919, 232)) - self.Enable(True) - - ## Radio panel - self.pnlRadio = wx.Panel(id=wxID_PNLRADIO, name='pnlRadio', parent=self, pos=wx.Point(3, 3), - size=wx.Size(100000000, 25), style=wx.TAB_TRAVERSAL) - - self.rbAll = wx.RadioButton(id=wxID_FRAME1RBALL, label=u'All', name=u'rbAll', parent=self.pnlRadio, - pos=wx.Point(0, 0), size=wx.Size(81, 20), style=0) - - self.rbSimple = wx.RadioButton(id=wxID_FRAME1RBSIMPLE, label=u'Simple Filter', name=u'rbSimple', - parent=self.pnlRadio, pos=wx.Point(0, 0), size=wx.Size(112, 20), style=0) - - self.rbAdvanced = wx.RadioButton(id=wxID_FRAME1RBADVANCED, label=u'Advanced Filter', name=u'rbAdvanced', - parent=self.pnlRadio, pos=wx.Point(0, 0), size=wx.Size(104, 20), style=0) - - self.btnClear = wx.Button(id=wxID_CLEAR, label="Clear Plot", name=u'btnClear', parent=self.pnlRadio, - pos=wx.Point(0, 0), size=wx.Size(80, 20), style=0) - - self.rbAll.SetValue(True) - - self.rbAdvanced.Enable(False) - - ## Splitter panel - self.pnlData = wx.Panel(id=wxID_PNLSPLITTER, name='pnlData', parent=self, pos=wx.Point(0, -10), - size=wx.Size(900, 349), style=wx.TAB_TRAVERSAL) - - self.cpnlSimple = wx.CollapsiblePane(self.pnlData, label="", style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE) - - ## Site Panel - self.pnlSite = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL1, name='pnlSite', parent=self.cpnlSimple.GetPane(), - pos=wx.Point(3, 0), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) - - self.cbSites = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBSITES, name=u'cbSites', parent=self.pnlSite, - pos=wx.Point(100, 0), size=wx.Size(700, 23), style=wx.CB_READONLY, value=u'') - - self.checkSite = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKSITE, label=u'', name=u'checkSite', - parent=self.pnlSite, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) - - self.lblSite = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLSITE, label=u'Site', name=u'lblSite', - parent=self.pnlSite, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) - self.lblSite.SetToolTipString(u'') - - self.cbSites.SetLabel(u'') - #self.checkSite.SetValue(False) - - ### Variable Panel - self.pnlVar = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL2, name='pnlVar', parent=self.cpnlSimple.GetPane(), - pos=wx.Point(3, 26), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) - - self.lblVariable = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLVARIABLE, label=u'Variable', name=u'lblVariable', - parent=self.pnlVar, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) - - self.checkVariable = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKVARIABLE, label=u'', name=u'checkVariable', - parent=self.pnlVar, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) - - self.cbVariables = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBVARIABLES, name=u'cbVariables', - parent=self.pnlVar, pos=wx.Point(100, 0), size=wx.Size(700, 25), style=wx.CB_READONLY, - value='') - self.cbVariables.SetLabel(u'') - self.cbVariables.Enable(False) - - #wx.EVT_RADIOBUTTON(self, self.rbAll.Id, self.onRbAllRadiobutton) - self.rbAll.Bind(wx.EVT_RADIOBUTTON, self.onRbAllRadiobutton, id=wxID_FRAME1RBALL) - self.rbSimple.Bind(wx.EVT_RADIOBUTTON, self.onRbSimpleRadiobutton, id=wxID_FRAME1RBSIMPLE) - self.rbAdvanced.Bind(wx.EVT_RADIOBUTTON, self.onRbAdvancedRadiobutton, id=wxID_FRAME1RBADVANCED) - self.btnClear.Bind(wx.EVT_BUTTON, self.onBtnClear, id=wxID_CLEAR) - - self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.onPaneChanged, self.cpnlSimple) - self.checkSite.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKSITE) - self.checkVariable.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKVARIABLE) - self.cbSites.Bind(wx.EVT_COMBOBOX, self.onCbSitesCombobox, id=wxID_PNLSERIESSELECTORCBSITES) - self.cbVariables.Bind(wx.EVT_COMBOBOX, self.onCbVariablesCombobox, id=wxID_PNLSERIESSELECTORCBVARIABLES) - - - ### New Stuff ################################################################################################## - - self.tblSeries = olvSeriesSelector.clsSeriesTable(id=wxID_PNLSERIESSELECTORtableSeries, parent=self.pnlData, - name=u'tblSeries', size=wx.Size(950, 108), pos=wx.Point(5, 5), - style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VIRTUAL) - - self.tblSeries.SetEmptyListMsg("No Database Loaded") - - #self.tblSeries.rowFormatter = self._rowFormatter - self.tblSeries.Bind(EVT_OVL_CHECK_EVENT, self.onReadyToPlot) - self.tblSeries.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.getSelectedObject) - self.tblSeries.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnTableRightDown, - id=wxID_PNLSERIESSELECTORtableSeries) - self.tblSeries.handleStandardKeys = True - self.tblSeries.useAlternateBackColors = True - #self.tblSeries.oddRowsBackColor = wx.Colour(143, 188, 188) - self.tblSeries.oddRowsBackColor = wx.Colour(191, 217, 217) - self.cpnlSimple.Collapse(True) - self._init_sizers() - - ## Virtual Event Handlers - def onReadyToPlot(self, event): - pass - - def onReadyToEdit(self, event): - pass - - def onBtnClear(self, event): - pass - - def stopEdit(self): - pass - - def getSelectedObject(self, event): - event.Skip() - - def resetDB(self, dbservice): - pass - - def initTableSeries(self): - pass - - def refreshTableSeries(self, db): - pass - - def refreshSeries(self): - pass - - def initSVBoxes(self): - pass - - def initPubSub(self): - pass - - def OnTableRightDown(self, event): - event.Skip() - - def onPaneChanged(self, event=None): - pass - - def onRbAdvancedRadiobutton(self, event): - event.Skip() - - def onRbAllRadiobutton(self, event): - event.Skip() - - def onRbSimpleRadiobutton(self, event): - event.Skip() - - def onRightPlot(self, event): - event.Skip() - - def onRightEdit(self, event): - event.Skip() - - def onRightRefresh(self, event): - event.Skip() - - def onRightClearSelected(self, event): - event.Skip() - - def onRightExData(self, event): - event.Skip() - - def onRightExMeta(self, event): - event.Skip() - - def onCbSitesCombobox(self, event): - event.Skip() - - def onCbVariablesCombobox(self, event): - event.Skip() - - def siteAndVariables(self): - pass - - def siteOnly(self): - pass - - def variableOnly(self): - pass - - def onCheck(self, event): - event.Skip() - - def setFilter(self, site_code='', var_code='', advfilter=''): - pass - - def isEditing(self): - return self.isEditing - - - def _rowFormatter(self, listItem, object): +__author__ = 'Jacob' + +import wx +from odmtools.controller.olvSeriesSelector import EVT_OVL_CHECK_EVENT +from odmtools.controller import olvSeriesSelector +from odmtools.odmservices import ServiceManager + +[wxID_PNLSERIESSELECTOR, wxID_PNLSERIESSELECTORCBSITES, wxID_PNLSERIESSELECTORCBVARIABLES, + wxID_PNLSERIESSELECTORCHECKSITE, wxID_PNLSERIESSELECTORCHECKVARIABLE, wxID_PNLSERIESSELECTORLBLSITE, + wxID_PNLSERIESSELECTORLBLVARIABLE, wxID_PNLSERIESSELECTORtableSeries, wxID_PNLSERIESSELECTORPANEL1, + wxID_PNLSERIESSELECTORPANEL2, wxID_PNLSIMPLE, wxID_PNLRADIO, wxID_FRAME1RBADVANCED, wxID_FRAME1RBALL, + wxID_FRAME1RBSIMPLE, wxID_FRAME1SPLITTER, wxID_PNLSPLITTER, wxID_PNLSERIESSELECTORtableSeriesTest,wxID_CLEAR, ] = [ + wx.NewId() for _init_ctrls in range(19)] + +class ClsSeriesSelector(wx.Panel): + + def __init__(self, parent, dbservice): + + self.parent = parent + wx.Panel.__init__(self, name=u'pnlSeriesSelector', parent=parent, + size=wx.Size(935, 270), style=wx.TAB_TRAVERSAL) + self._init_ctrls() + self.series_service = dbservice + self.initTableSeries() + self.initSVBoxes() + # Subscribe functions + self.initPubSub() + self.service_manager = ServiceManager() + self.export_service = self.service_manager.get_export_service() + self.selectedIndex = 0 + self.isEditing = False + + + ## Radio Sizer + def _init_coll_boxSizer5_Items(self, parent): + # generated method, don't edit + + pnlSpacer= wx.Panel(id=0, name='pnlSpacer', parent=self.pnlRadio, pos=wx.Point(3, 3), + size=wx.Size(0, 25), style=wx.TAB_TRAVERSAL) + parent.AddWindow(self.rbAll, 0, border=1, flag=wx.ALL) + parent.AddWindow(self.rbSimple, 0, border=1, flag=wx.ALL) + parent.AddWindow(self.rbAdvanced, 0, border=1, flag=wx.ALL) + parent.AddWindow(pnlSpacer, 100, border=1, flag=wx.EXPAND) + + parent.AddWindow(self.btnClear, 0, border=1, flag=wx.ALL) + + + ## Splitter Sizer + def _init_coll_boxSizer3_Items(self, parent): + # generated method, don't edit + parent.AddWindow(self.cpnlSimple, 0, flag=wx.RIGHT | wx.LEFT | wx.EXPAND) + parent.AddWindow(self.tblSeries, 100, flag=wx.EXPAND) + + ## Panel Sizer + def _init_coll_boxSizer1_Items(self, parent): + # generated method, don't edit + parent.AddSizer(self.pnlRadio, 0, border=7, flag=wx.LEFT | wx.RIGHT | wx.TOP) + parent.AddWindow(self.pnlData, 100, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) + + ## Site Sizer + def _init_coll_boxSizer4_Items(self, parent): + # generated method, don't edit + parent.AddWindow(self.checkSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.lblSite, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.cbSites, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) + + ## Variable Sizer + def _init_coll_boxSizer2_Items(self, parent): + # generated method, don't edit + parent.AddWindow(self.checkVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.lblVariable, 0, border=3, flag=wx.LEFT | wx.RIGHT) + parent.AddWindow(self.cbVariables, 90, border=3, flag=wx.LEFT | wx.RIGHT | wx.EXPAND) + + ## Simple Filter Sizer + def _init_coll_boxSizer6_Items(self, parent): + parent.AddWindow(self.pnlSite, 50, flag=wx.EXPAND) + parent.AddWindow(self.pnlVar, 50, flag=wx.EXPAND) + # parent.AddSizer(self.boxSizer4, 0, border=5, flag=wx.EXPAND) + # parent.AddSizer(self.boxSizer2, 0, border=5, flag=wx.EXPAND) + + + def _init_sizers(self): + # generated method, don't edit + boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL) + boxSizer2 = wx.BoxSizer(orient=wx.HORIZONTAL) + boxSizer3 = wx.BoxSizer(orient=wx.VERTICAL) + boxSizer4 = wx.BoxSizer(orient=wx.HORIZONTAL) + boxSizer5 = wx.BoxSizer(orient=wx.HORIZONTAL) + boxSizer6 = wx.BoxSizer(orient=wx.VERTICAL) + + self._init_coll_boxSizer1_Items(boxSizer1) + self._init_coll_boxSizer2_Items(boxSizer2) + self._init_coll_boxSizer3_Items(boxSizer3) + self._init_coll_boxSizer4_Items(boxSizer4) + self._init_coll_boxSizer5_Items(boxSizer5) + self._init_coll_boxSizer6_Items(boxSizer6) + + self.SetSizer(boxSizer1) + self.pnlRadio.SetSizer(boxSizer5) + self.pnlSite.SetSizer(boxSizer4) + self.pnlVar.SetSizer(boxSizer2) + self.cpnlSimple.SetSizer(boxSizer6) + self.pnlData.SetSizer(boxSizer3) + + def _init_ctrls(self): + # generated method, don't edit + + + self.SetClientSize(wx.Size(919, 232)) + self.Enable(True) + + ## Radio panel + self.pnlRadio = wx.Panel(id=wxID_PNLRADIO, name='pnlRadio', parent=self, pos=wx.Point(3, 3), + size=wx.Size(100000000, 25), style=wx.TAB_TRAVERSAL) + + self.rbAll = wx.RadioButton(id=wxID_FRAME1RBALL, label=u'All', name=u'rbAll', parent=self.pnlRadio, + pos=wx.Point(0, 0), size=wx.Size(81, 20), style=0) + + self.rbSimple = wx.RadioButton(id=wxID_FRAME1RBSIMPLE, label=u'Simple Filter', name=u'rbSimple', + parent=self.pnlRadio, pos=wx.Point(0, 0), size=wx.Size(112, 20), style=0) + + self.rbAdvanced = wx.RadioButton(id=wxID_FRAME1RBADVANCED, label=u'Advanced Filter', name=u'rbAdvanced', + parent=self.pnlRadio, pos=wx.Point(0, 0), size=wx.Size(104, 20), style=0) + + self.btnClear = wx.Button(id=wxID_CLEAR, label="Clear Plot", name=u'btnClear', parent=self.pnlRadio, + pos=wx.Point(0, 0), size=wx.Size(80, 20), style=0) + + self.rbAll.SetValue(True) + + self.rbAdvanced.Enable(False) + + ## Splitter panel + self.pnlData = wx.Panel(id=wxID_PNLSPLITTER, name='pnlData', parent=self, pos=wx.Point(0, -10), + size=wx.Size(900, 349), style=wx.TAB_TRAVERSAL) + + self.cpnlSimple = wx.CollapsiblePane(self.pnlData, label="", style=wx.CP_DEFAULT_STYLE | wx.CP_NO_TLW_RESIZE) + + ## Site Panel + self.pnlSite = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL1, name='pnlSite', parent=self.cpnlSimple.GetPane(), + pos=wx.Point(3, 0), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) + + self.cbSites = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBSITES, name=u'cbSites', parent=self.pnlSite, + pos=wx.Point(100, 0), size=wx.Size(700, 23), style=wx.CB_READONLY, value=u'') + + self.checkSite = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKSITE, label=u'', name=u'checkSite', + parent=self.pnlSite, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) + + self.lblSite = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLSITE, label=u'Site', name=u'lblSite', + parent=self.pnlSite, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) + self.lblSite.SetToolTipString(u'') + + self.cbSites.SetLabel(u'') + #self.checkSite.SetValue(False) + + ### Variable Panel + self.pnlVar = wx.Panel(id=wxID_PNLSERIESSELECTORPANEL2, name='pnlVar', parent=self.cpnlSimple.GetPane(), + pos=wx.Point(3, 26), size=wx.Size(800, 25), style=wx.TAB_TRAVERSAL) + + self.lblVariable = wx.StaticText(id=wxID_PNLSERIESSELECTORLBLVARIABLE, label=u'Variable', name=u'lblVariable', + parent=self.pnlVar, pos=wx.Point(30, 0), size=wx.Size(60, 21), style=0) + + self.checkVariable = wx.CheckBox(id=wxID_PNLSERIESSELECTORCHECKVARIABLE, label=u'', name=u'checkVariable', + parent=self.pnlVar, pos=wx.Point(3, 0), size=wx.Size(21, 21), style=0) + + self.cbVariables = wx.ComboBox(choices=[], id=wxID_PNLSERIESSELECTORCBVARIABLES, name=u'cbVariables', + parent=self.pnlVar, pos=wx.Point(100, 0), size=wx.Size(700, 25), style=wx.CB_READONLY, + value='') + self.cbVariables.SetLabel(u'') + self.cbVariables.Enable(False) + + #wx.EVT_RADIOBUTTON(self, self.rbAll.Id, self.onRbAllRadiobutton) + self.rbAll.Bind(wx.EVT_RADIOBUTTON, self.onRbAllRadiobutton, id=wxID_FRAME1RBALL) + self.rbSimple.Bind(wx.EVT_RADIOBUTTON, self.onRbSimpleRadiobutton, id=wxID_FRAME1RBSIMPLE) + self.rbAdvanced.Bind(wx.EVT_RADIOBUTTON, self.onRbAdvancedRadiobutton, id=wxID_FRAME1RBADVANCED) + self.btnClear.Bind(wx.EVT_BUTTON, self.onBtnClear, id=wxID_CLEAR) + + self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.onPaneChanged, self.cpnlSimple) + self.checkSite.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKSITE) + self.checkVariable.Bind(wx.EVT_CHECKBOX, self.onCheck, id=wxID_PNLSERIESSELECTORCHECKVARIABLE) + self.cbSites.Bind(wx.EVT_COMBOBOX, self.onCbSitesCombobox, id=wxID_PNLSERIESSELECTORCBSITES) + self.cbVariables.Bind(wx.EVT_COMBOBOX, self.onCbVariablesCombobox, id=wxID_PNLSERIESSELECTORCBVARIABLES) + + + ### New Stuff ################################################################################################## + + self.tblSeries = olvSeriesSelector.clsSeriesTable(id=wxID_PNLSERIESSELECTORtableSeries, parent=self.pnlData, + name=u'tblSeries', size=wx.Size(950, 108), pos=wx.Point(5, 5), + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VIRTUAL) + + self.tblSeries.SetEmptyListMsg("No Database Loaded") + + #self.tblSeries.rowFormatter = self._rowFormatter + self.tblSeries.Bind(EVT_OVL_CHECK_EVENT, self.onReadyToPlot) + self.tblSeries.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.getSelectedObject) + self.tblSeries.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnTableRightDown, + id=wxID_PNLSERIESSELECTORtableSeries) + self.tblSeries.handleStandardKeys = True + self.tblSeries.useAlternateBackColors = True + #self.tblSeries.oddRowsBackColor = wx.Colour(143, 188, 188) + self.tblSeries.oddRowsBackColor = wx.Colour(191, 217, 217) + self.cpnlSimple.Collapse(True) + self._init_sizers() + + ## Virtual Event Handlers + def onReadyToPlot(self, event): + pass + + def onReadyToEdit(self, event): + pass + + def onBtnClear(self, event): + pass + + def stopEdit(self): + pass + + def getSelectedObject(self, event): + event.Skip() + + def resetDB(self, dbservice): + pass + + def initTableSeries(self): + pass + + def refreshTableSeries(self, db): + pass + + def refreshSeries(self): + pass + + def initSVBoxes(self): + pass + + def initPubSub(self): + pass + + def OnTableRightDown(self, event): + event.Skip() + + def onPaneChanged(self, event=None): + pass + + def onRbAdvancedRadiobutton(self, event): + event.Skip() + + def onRbAllRadiobutton(self, event): + event.Skip() + + def onRbSimpleRadiobutton(self, event): + event.Skip() + + def onRightPlot(self, event): + event.Skip() + + def onRightEdit(self, event): + event.Skip() + + def onRightRefresh(self, event): + event.Skip() + + def onRightClearSelected(self, event): + event.Skip() + + def onRightExData(self, event): + event.Skip() + + def onRightExMeta(self, event): + event.Skip() + + def onCbSitesCombobox(self, event): + event.Skip() + + def onCbVariablesCombobox(self, event): + event.Skip() + + def siteAndVariables(self): + pass + + def siteOnly(self): + pass + + def variableOnly(self): + pass + + def onCheck(self, event): + event.Skip() + + def setFilter(self, site_code='', var_code='', advfilter=''): + pass + + def isEditing(self): + return self.isEditing + + + def _rowFormatter(self, listItem, object): pass \ No newline at end of file diff --git a/profiler/runsnake.py b/profiler/runsnake.py index d01b4ac..49704c3 100644 --- a/profiler/runsnake.py +++ b/profiler/runsnake.py @@ -186,7 +186,7 @@ def mem_name( x ): class MainFrame(wx.Frame): - """The root frame for the display of a single data-set""" + """The root bulkInsertCtrl for the display of a single data-set""" loader = None percentageView = False diff --git a/setup/Mac/ODMTools.packproj b/setup/Mac/ODMTools.packproj index 462f175..c3bf177 100644 --- a/setup/Mac/ODMTools.packproj +++ b/setup/Mac/ODMTools.packproj @@ -566,11 +566,9 @@ IFPkgDescriptionDescription IFPkgDescriptionTitle - - ODMTools_v1.2.8 + ODMTools_v2.0.1 IFPkgDescriptionVersion - 1.2.8 Beta - + 2.0.1 Beta Display Information @@ -586,7 +584,7 @@ CFBundleName ODMTools CFBundleShortVersionString - 1.2.8 + 2.0.1 Options @@ -612,16 +610,16 @@ Version IFMajorVersion - 1 - IFMinorVersion 2 + IFMinorVersion + 0 IFPkgFlagPackageSelection 0 Name - ODMTools_v1.2.8-beta_Mac_installer + ODMTools_v2.0.1-beta_Mac_installer Status 1 Type diff --git a/setup/Mac/ODMTools.spec b/setup/Mac/ODMTools.spec index c009faf..0c4c2dc 100644 --- a/setup/Mac/ODMTools.spec +++ b/setup/Mac/ODMTools.spec @@ -5,33 +5,29 @@ block_cipher = None a = Analysis(['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'], pathex=['/Users/stephanie/DEV/ODMToolsPython/setup/Mac'], - binaries=None, - datas=None, hiddenimports=[], - hookspath=['/Users/stephanie/DEV/ODMToolsPython/setup/hooks'], + hookspath=None, runtime_hooks=None, - excludes=['PyQt4', 'PyQt4.QtCore', 'PyQt4.QtGui'], - win_no_prefer_redirects=False, - win_private_assemblies=False, + excludes=None, cipher=block_cipher) -pyz = PYZ(a.pure, a.zipped_data, +pyz = PYZ(a.pure, cipher=block_cipher) exe = EXE(pyz, a.scripts, exclude_binaries=True, name='ODMTools', debug=False, - strip=False, + strip=None, upx=True, - console=False , version='/Users/stephanie/DEV/ODMToolsPython/setup/version.txt', icon='/Users/stephanie/DEV/ODMToolsPython/odmtools/common/icons/ODMTools.icns') + console=False , version='/Users/stephanie/DEV/ODMToolsPython/setup/version.txt', icon='odmtools/common/icons/ODMTools.icns') coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, - strip=False, - upx=False, + strip=None, + upx=True, name='ODMTools') app = BUNDLE(coll, name='ODMTools.app', - icon='/Users/stephanie/DEV/ODMToolsPython/odmtools/common/icons/ODMTools.icns', + icon='odmtools/common/icons/ODMTools.icns', bundle_identifier=None) diff --git a/setup/Mac/Welcome.rtf b/setup/Mac/Welcome.rtf index 5df9cc8..0ff97b2 100644 --- a/setup/Mac/Welcome.rtf +++ b/setup/Mac/Welcome.rtf @@ -4,5 +4,5 @@ \margl1440\margr1440\vieww10800\viewh8400\viewkind0 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural -\f0\fs24 \cf0 Please click Continue to install ODMTools for your mac. \ +\f0\fs24 \cf0 Please click Continue to install ODM2Tools for your mac. \ } \ No newline at end of file diff --git a/setup/Mac/build.sh b/setup/Mac/build.sh index cbd8b59..5192562 100755 --- a/setup/Mac/build.sh +++ b/setup/Mac/build.sh @@ -2,10 +2,13 @@ echo "Building!!" +echo "cleanup" if [ -d build ] && [ -d dist ]; then echo "Cleaning up old build and dist files" - rm -ir build dist + rm -r --interactive=once build dist fi - -sudo python setup.py py2app +echo "activate environment" +source activate odmtools +echo "run py2app" +sudo python ../setuptest.py py2app #sudo /usr/local/Cellar/python/2.7.8/bin/python setup.py py2app diff --git a/setup/Windows/ODMTools.spec b/setup/Windows/ODMTools.spec index 464cbf5..cf6bc60 100644 --- a/setup/Windows/ODMTools.spec +++ b/setup/Windows/ODMTools.spec @@ -1,44 +1,10 @@ - - -a = Analysis(['D:\\DEV\\ODMTools\\ODMTools.py'], - pathex=['D:\\DEV\\ODMTools\\setup\\Windows'], - binaries=None, - datas=None, - hiddenimports=[], - hookspath=['../hooks'], - runtime_hooks=None, - excludes=['PyQt4', 'PyQt4.QtCore', 'PyQt4.QtGui'], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher) -pyz = PYZ(a.pure, a.zipped_data, - cipher=block_cipher) -exe = EXE(pyz, - a.scripts, - exclude_binaries=True, - name='ODMTools', - debug=False, - strip=False, - upx=False, - console=True , version='D:\\DEV\\ODMTools\\setup\\version.txt', icon='D:\\DEV\\ODMTools\odmtools\\common\\icons\\ODMTools.ico') -coll = COLLECT(exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - name='ODMTools') - - - - # -*- mode: python -*- block_cipher = None -a = Analysis(['ODMTools.py'], - pathex=['D:\\DEV\\ODMTools'], +a = Analysis(['D:\\DEV\\ODMTools\\ODMTools.py'], + pathex=['D:\\DEV\\ODMTools\\setup\\Windows'], binaries=None, datas=None, hiddenimports=[], @@ -56,12 +22,12 @@ exe = EXE(pyz, name='ODMTools', debug=False, strip=False, - upx=True, - console=True ) + upx=False, + console=True , version='D:\\DEV\\ODMTools\\setup\\version.txt', icon='D:\\DEV\\ODMTools\\odmtools\\common\\icons\\ODMTools.ico') coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, - upx=True, + upx=False, name='ODMTools') diff --git a/setup/Windows/odmtools_console.iss b/setup/Windows/odmtools_console.iss index 659ac95..4899e45 100644 --- a/setup/Windows/odmtools_console.iss +++ b/setup/Windows/odmtools_console.iss @@ -1,13 +1,13 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" +#define MyAppName "ODM2Tools" -#define MyAppVersion "1.2.8_Beta" -#define MyAppExeLongName "ODMTools_1.2.8_Beta_win32_x86_64_console.exe" +#define MyAppVersion "2.0.1_Beta" +#define MyAppExeLongName "ODM2Tools_2.0.1_Beta_win32_x86_64_console.exe" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" -#define MyAppExeName "ODMTools.exe" +#define MyAppExeName "ODM2Tools.exe" #define MyAppDir "D:\DEV\ODMTools" [Setup] diff --git a/setup/Windows/odmtools_no_console.iss b/setup/Windows/odmtools_no_console.iss index d429c56..adde9b8 100644 --- a/setup/Windows/odmtools_no_console.iss +++ b/setup/Windows/odmtools_no_console.iss @@ -4,12 +4,12 @@ #define MyAppName "ODMTools" -#define MyAppExeLongName "ODMTools_1.2.8_Beta_win32_x86_64.exe" -#define MyAppVersion "1.2.8_Beta" +#define MyAppExeLongName "ODM2Tools_2.0.1_Beta_win32_x86_64.exe" +#define MyAppVersion "2.0.1_Beta" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" -#define MyAppExeName "ODMTools.exe" +#define MyAppExeName "ODM2Tools.exe" #define MyAppDir "D:\DEV\ODMTools" [Setup] diff --git a/setup/Windows/odmtools_setup.iss b/setup/Windows/odmtools_setup.iss index 05bf730..a035311 100644 --- a/setup/Windows/odmtools_setup.iss +++ b/setup/Windows/odmtools_setup.iss @@ -1,11 +1,11 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" -#define MyAppVersion "1.2.8_Beta" +#define MyAppName "ODM2Tools" +#define MyAppVersion "2.0.1_Beta" #define MyAppPublisher "ODM2" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" -#define MyAppExeName "ODMTools.exe" +#define MyAppExeName "ODM2Tools.exe" #define MyAppDir "D:\DEV\ODMTools" [Setup] diff --git a/setup/Windows/odmtools_setup_build.iss b/setup/Windows/odmtools_setup_build.iss index cd72382..7c428de 100644 --- a/setup/Windows/odmtools_setup_build.iss +++ b/setup/Windows/odmtools_setup_build.iss @@ -3,9 +3,9 @@ ; http://www.jrsoftware.org/isinfo.php ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" +#define MyAppName "ODM2Tools" #define OrgName "UCHIC" -#define MyAppVersion "v1.2.8-beta" +#define MyAppVersion "v2.0.1-beta" #define MyAppURL "https://github.com/ODM2/ODMToolsPython" [Setup] @@ -23,7 +23,7 @@ AppUpdatesURL={#MyAppURL} DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} OutputBaseFilename={#MyAppName}_{#MyAppVersion}_Installer -SetupIconFile={#MyAppDir}\odmtools\common\icons\ODMTools.ico +SetupIconFile=D:\DEV\Releases\ODM2Tools\odmtools_beta_source_code\odmtools\common\icons\ODMTools.ico Compression=lzma SolidCompression=yes @@ -34,13 +34,13 @@ Name: "english"; MessagesFile: "compiler:Default.isl" Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked [Files] -Source: "D:\DEV\Releases\ODMTools\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +Source: "D:\DEV\Releases\ODM2Tools\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs ; NOTE: Don't use "Flags: ignoreversion" on any shared system files [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\run.bat"; WorkingDir: "{app}"; Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}" -Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon +Name: "{commondesktop}\{#MyAppName}"; Filename: "{app}\run.bat"; Tasks: desktopicon [Dirs] Name: "{localappdata}\{#OrgName}\{#MyAppName}" diff --git a/setup/Windows/test.iss b/setup/Windows/test.iss index bd60868..a420b09 100644 --- a/setup/Windows/test.iss +++ b/setup/Windows/test.iss @@ -1,11 +1,11 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppName "ODMTools" -#define MyAppVersion "1.2.8" +#define MyAppName "ODM2Tools" +#define MyAppVersion "2.0.1" #define MyAppPublisher "My Company, Inc." #define MyAppURL "http://www.example.com/" -#define MyAppExeName "ODMTools_console.exe" +#define MyAppExeName "ODM2Tools_console.exe" [Setup] ; NOTE: The value of AppId uniquely identifies this application. diff --git a/setup/__init__.py b/setup/__init__.py new file mode 100644 index 0000000..a24dbc6 --- /dev/null +++ b/setup/__init__.py @@ -0,0 +1 @@ +__author__ = 'stephanie' diff --git a/make.py b/setup/make.py similarity index 95% rename from make.py rename to setup/make.py index f0a5aa4..496952a 100644 --- a/make.py +++ b/setup/make.py @@ -16,7 +16,7 @@ ## Update odmtools.meta.data whenever creating a release from odmtools.meta import data -BASE_DIR = os.path.dirname(os.path.realpath(__file__)) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) SETUP_DIR = os.path.join(BASE_DIR, 'setup') WIN_DIR = os.path.join(SETUP_DIR, "Windows") MAC_DIR = os.path.join(SETUP_DIR, "Mac") @@ -28,11 +28,11 @@ MAC_WORK_DIR = os.path.join(MAC_DIR, "Temp") WORK_DIR = os.path.join(WIN_DIR, "Temp") -ICON_DIR = os.path.join('odmtools', 'common', "icons") +ICON_DIR = os.path.join(BASE_DIR, 'odmtools', 'common', "icons") WIN_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.ico") MAC_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.icns") -APP_DIR = os.path.join(MAC_DIR, 'Dist', "ODMTools.app") +APP_DIR = os.path.join(MAC_DIR, 'Dist', "ODM2Tools.app") # Location of Windows files APP_FILE = os.path.join(BASE_DIR, "ODMTools.py") MAKE_FILE = os.path.realpath(__file__) @@ -94,7 +94,7 @@ def zipdir(basedir, archivename): z.write(absfn, zfn) def printInfo(): print "=============================================================" - print "= ODMTools Installer " + print "= ODM2Tools Installer " print "= Be sure to update odmtools/meta/data with every release " print "= Building release: {version}".format(version=data.version), print "\n= Platform: {platform}, {architecture}".format(platform=sys.platform, architecture=platform.architecture()), "\n=" @@ -151,6 +151,7 @@ def run_pyinstaller(console=False): ## Console Version os.system('pyinstaller ' '--clean ' + '--name ODM2Tools ' '--distpath=%s ' % WIN_DIR + '--workpath=%s ' % WORK_DIR + '--specpath=%s ' % WIN_DIR + @@ -160,12 +161,14 @@ def run_pyinstaller(console=False): '--version-file=%s ' % VERSION_FILE + '--onedir ' # '--onefile ' + - '--exclude= + #'--exclude= '--noconfirm ' + APP_FILE) else: ## Non Console Version + val = os.system('pyinstaller ' '--clean ' + '--name ODM2Tools ' '--distpath=%s ' % WIN_DIR + '--workpath=%s ' % WORK_DIR + '--specpath=%s ' % WIN_DIR + @@ -178,6 +181,7 @@ def run_pyinstaller(console=False): '--noconsole ' '--noconfirm ' + APP_FILE) + return True except Exception as e: print (e) @@ -187,6 +191,7 @@ def mac_pyinstaller(): try: os.system('pyinstaller ' '--clean ' + '--name ODM2Tools ' '--distpath=%s ' % MAC_DIST_DIR + '--workpath=%s ' % MAC_WORK_DIR + '--specpath=%s ' % MAC_DIR + @@ -244,6 +249,7 @@ def main(): print "Creating Windows Executable..." if run_pyinstaller(): + scriptpath = os.path.join(WIN_DIR, "odmtools_no_console.iss") run_inno(script= scriptpath) @@ -252,6 +258,7 @@ def main(): scriptpath = os.path.join(WIN_DIR, "odmtools_console.iss") run_inno(scriptpath) + print "Create No Installer " ## Create Shortcut ## Create File diff --git a/setup/setup.py b/setup/setup.py index d61332f..069cae0 100644 --- a/setup/setup.py +++ b/setup/setup.py @@ -13,6 +13,23 @@ import sys +import os +if sys.platform == 'darwin': + import macholib + #print("~"*60 + "macholib verion: "+macholib.__version__) + if macholib.__version__ <= "1.7": + print("Applying macholib patch...") + import macholib.dyld + import macholib.MachOGraph + dyld_find_1_7 = macholib.dyld.dyld_find + def dyld_find(name, loader=None, **kwargs): + #print("~"*60 + "calling alternate dyld_find") + if loader is not None: + kwargs['loader_path'] = loader + return dyld_find_1_7(name, **kwargs) + macholib.MachOGraph.dyld_find = dyld_find + + ''' from setuptools import setup @@ -23,31 +40,43 @@ -NAME = 'ODMTools' +NAME = 'ODM2Tools' +BASE_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +ICON_DIR = os.path.join(BASE_DIR, 'odmtools', 'common', "icons") +WIN_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.ico") +MAC_ICON_FILE = os.path.join(ICON_DIR, "ODMTools.icns") +#APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] +APP = os.path.join(BASE_DIR, 'ODMTools.py') extra_options = None sys.setrecursionlimit(2000) if sys.platform == 'darwin': sys.argv.append('py2app') from setuptools import setup - APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] - LIBS = ['/usr/X11/lib/libfreetype.6.dylib', '/usr/X11/lib/libstdc++.6.dylib', '/usr/X11/lib/libpng15.15.dylib'] - OPTIONS = {'iconfile': '/Users/stephanie/DEV/ODMToolsPython/odmtools/common/icons/ODMTools.icns', - 'includes': ['pymysql', 'sqlalchemy', 'dateutil'], 'frameworks': LIBS} + # APP = ['/Users/stephanie/DEV/ODMToolsPython/ODMTools.py'] + LIBS = ['/usr/X11/lib/libfreetype.6.dylib', '/usr/X11/lib/libstdc++.6.dylib', '/usr/X11/lib/libpng15.15.dylib', '/anaconda/lib/libwx_osx_cocoau-3.0.0.0.0.dylib'] + OPTIONS = {'iconfile': MAC_ICON_FILE, + 'includes': ['pymysql', 'sqlalchemy', 'dateutil'], + 'frameworks': LIBS} extra_options = dict(app=APP, setup_requires=['py2app'], options={'py2app': OPTIONS}) elif sys.platform == 'win32': sys.argv.append('py2exe') from distutils.core import setup - APP = ['C:\Users\Jacob\Documents\ODMToolsPython\ODMTools.py'] + APP = ['D:\Dev\ODMTools\ODMTools.py'] import numpy import py2exe from glob import glob data_files = [ ("Microsoft.VC90.CRT", glob(r'C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\Microsoft.VC90.CRT\*.*')), - (r'mpl-data', [r'C:\Anaconda\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), - (r'mpl-data\images', glob(r'C:\Anaconda\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\images\*.*')), - (r'mpl-data\fonts', glob(r'C:\Anaconda\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\fonts\*.*'))] + (r'mpl-data', [r'C:\Anaconda3\envs\odmtools\Lib\site-packages\matplotlib\mpl-data\matplotlibrc']), + (r'mpl-data\images', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\images\*.*')), + (r'mpl-data\fonts', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\fonts\*.*')), + (r'mpl-data\stylelib', glob(r'C:\Anaconda3\envs\odmtools_release\Lib\site-packages\matplotlib\mpl-data\stylelib\*.*')), + # ('.', glob('*.dll')), + # ('.', glob('C:\Windows\system32\OPENGL32.dll'))] + ('.', glob(r'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4m.dll')), + ('.', glob(r'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4.dll'))] OPTIONS = { #'excludes': ['_ssl', 'pyreadline', 'difflib', 'doctest', 'optparse', 'pickle', 'calendar'], @@ -57,18 +86,22 @@ 'MSVCP90.dll', 'WS2_32.dll', 'WINSPOOL.DRV', 'GDI32.dll', 'KERNEL32.dll', 'ntdll.dll', 'COMCTL32.dll', 'COMDLG32.dll', 'msvcrt.dll', 'RPCRT4.dll'], "optimize": 2, + # "includes": ['C:\Windows\system32\OPENGL32.dll', + # 'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4.dll', + # 'C:\Anaconda3\envs\odmtools_release\Library\bin\mkl_p4m.dll'], "bundle_files": 3, "dist_dir": "dist", "xref": False, "skip_archive": False, "ascii": False, "custom_boot_script": '', - "packages": ['wx.lib.pubsub', 'ObjectListView', 'pyodbc'], + "packages": ['wx.lib.pubsub', 'pyodbc', 'numpy', 'scipy', 'sqlalchemy', 'wx', 'pandas'], #'ObjectListView', + #make sure that mkl_p4.dll and mkl_p4m.dll have been copied into the Dist folder } sys.path.append("C:\\Program Files (x86)\\Microsoft Visual Studio 10.0\\VC\\Microsoft.VC90.CRT") - sys.path.append("C:\\Users\\Jacob\\Documents\\ODMToolsPython") + sys.path.append(BASE_DIR) extra_options = dict(console=APP, data_files=data_files, options={'py2exe': OPTIONS}) setup(name=NAME, **extra_options) diff --git a/tests/test_controller/test_bulkInsert.py b/tests/test_controller/test_bulkInsert.py index e881d6c..8036625 100644 --- a/tests/test_controller/test_bulkInsert.py +++ b/tests/test_controller/test_bulkInsert.py @@ -1,11 +1,11 @@ import wx import os -from odmtools.controller.frmBulkInsert import BulkInsert +from odmtools.controller.BulkInsertController import BulkInsertController class TestBulkInsert: def setup(self): self.app = wx.App() - self.BulkInsert = BulkInsert(None) + self.BulkInsert = BulkInsertController(None) path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) self.CSVPath = os.path.join(path, 'example_files', 'exampleBulkInsert.csv') self.CSVPath2 =os.path.join(path, 'example_files', 'exampleBulkInsert_win.csv') @@ -39,7 +39,7 @@ def test_loadIntoDataFrame(self): def test_onUpload(self): assert self.BulkInsert - assert isinstance(self.BulkInsert.col, list) + assert isinstance(self.BulkInsert.columns, list) assert self.CSVPath diff --git a/tests/test_controller/test_frmAddPoints.py b/tests/test_controller/test_frmAddPoints.py index 777efdb..7acdca5 100755 --- a/tests/test_controller/test_frmAddPoints.py +++ b/tests/test_controller/test_frmAddPoints.py @@ -50,7 +50,7 @@ def FireEvent(self): ''' evt = wx.PyCommandEvent(wx.EVT_BUTTON.typeId, self.frame.addRowBtn.GetId()) - #wx.PostEvent(self.frame.addRowBtn, evt) + #wx.PostEvent(self.bulkInsertCtrl.addRowBtn, evt) self.frame.GetEventHandler().ProcessEvent(evt) assert self.olv.GetObjects() diff --git a/tests/test_controller/test_frmDBConfig.py b/tests/test_controller/test_frmDBConfig.py index a5c69d4..e0bc7da 100755 --- a/tests/test_controller/test_frmDBConfig.py +++ b/tests/test_controller/test_frmDBConfig.py @@ -19,7 +19,7 @@ def setup(self): ''' def test_form(self): """test form""" - assert self.frame.panel.choices == { + assert self.bulkInsertCtrl.panel.choices == { "Microsoft SQL Server": 'mssql', "MySQL": 'mysql', "PostgreSQL":"postgresql" } ''' diff --git a/tests/test_gui/test_plotProbability.py b/tests/test_gui/test_plotProbability.py index ffac10b..7a8ef57 100644 --- a/tests/test_gui/test_plotProbability.py +++ b/tests/test_gui/test_plotProbability.py @@ -23,8 +23,8 @@ def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine build_db(engine) self.memory_db = MemoryDatabase() @@ -33,7 +33,7 @@ def setup(self): self.series = add_series(self.session) print "Series: ", self.series self.memory_db.initEditValues(self.series.id) - # add_bulk_data_values(self.session, self.series) + # add_bulk_data_values(self.session, self.series_service) def test_onPlotType(self): diff --git a/tests/test_gui/test_pnlDataTable.py b/tests/test_gui/test_pnlDataTable.py index ad7004d..5993699 100644 --- a/tests/test_gui/test_pnlDataTable.py +++ b/tests/test_gui/test_pnlDataTable.py @@ -1,6 +1,6 @@ from odmtools.controller.frmDataTable import FrmDataTable from odmtools.odmdata import MemoryDatabase, DataValue -from odmtools.gui.pnlDataTable import pnlDataTable + from odmtools.odmdata import SessionFactory from odmtools.odmservices import SeriesService from tests import test_util @@ -14,8 +14,8 @@ def setup(self): #set up remote Database self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine test_util.build_db(engine) self.dvs_size = 100 diff --git a/tests/test_gui/test_wizSave.py b/tests/test_gui/test_wizSave.py index 038faee..c5bc834 100644 --- a/tests/test_gui/test_wizSave.py +++ b/tests/test_gui/test_wizSave.py @@ -32,14 +32,14 @@ def setup(self): self.memory_database = MemoryDatabase() self.memory_database.set_series_service(self.series_service) - # self.memory_database.initEditValues(self.series.id) + # self.memory_database.initEditValues(self.series_service.id) self.app = wx.App() self.frame = wx.Frame(None) self.wizard = wizSave(self.frame,self.sm, self.sm.get_edit_service(self.series.id, self.memory_database)) #TODO get wizard tests working # def test___init__(self): - # assert self.frame + # assert self.bulkInsertCtrl # assert self.wizard # self.wizard.init(self, self.sm, self.memory_database) # diff --git a/tests/test_odmdata/test_FreeTDS.py b/tests/test_odmdata/test_FreeTDS.py index 9f30a40..e2957cc 100644 --- a/tests/test_odmdata/test_FreeTDS.py +++ b/tests/test_odmdata/test_FreeTDS.py @@ -1,5 +1,8 @@ # -*- coding: utf-8 -*- -from odmtools.odmdata import SessionFactory, variable, series +#TODO remove variable and series access here +from odmtools.odmdata import SessionFactory +from odm2api.ODM2.models import Variables as variable +from odm2api.ODM2.models import Results as series from odmtools.odmservices import SeriesService from tests import test_util import sys diff --git a/tests/test_odmdata/test_memory_db.py b/tests/test_odmdata/test_memory_db.py index 4239295..090975b 100644 --- a/tests/test_odmdata/test_memory_db.py +++ b/tests/test_odmdata/test_memory_db.py @@ -10,8 +10,8 @@ class TestMemoryDB: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine test_util.build_db(engine) self.memory_db = MemoryDatabase() @@ -37,13 +37,13 @@ def test_update_points(self): self.memory_db.update([{"value":15,"id":self.sdate}]) dvs = self.memory_db.getDataValuesDF() - print dvs["DataValue"] - assert dvs["DataValue"][0] == 15 + print dvs["datavalue"] + assert dvs["datavalue"][0] == 15 def test_update_value(self): self.memory_db.updateValue([self.sdate],'+', 5 ) dvs = self.memory_db.getDataValuesDF() - assert dvs["DataValue"][0] == 14 + assert dvs["datavalue"][0] == 14 def test_add_points(self): #with pytest.raises(NotImplementedError): @@ -55,7 +55,7 @@ def test_add_points(self): dvs = self.memory_db.getDataValuesDF() assert len(dvs.index) == 11 - assert dvs["DataValue"][0] == -9999 + assert dvs["datavalue"][0] == -9999 def test_update_flag(self): self.memory_db.updateFlag([self.sdate], '50') @@ -66,7 +66,7 @@ def test_update_flag(self): def test_delete_points(self): stlen= len(self.memory_db.df.index) - self.memory_db.delete(self.memory_db.df["LocalDateTime"].tolist()[0:10]) + self.memory_db.delete(self.memory_db.df["resultdatetime"].tolist()[0:10]) dvs = self.memory_db.getDataValuesDF() assert len(dvs.index) == stlen-10 diff --git a/tests/test_odmdata/test_pandas_memory_db.py b/tests/test_odmdata/test_pandas_memory_db.py index 94ef7ee..d4c58cc 100644 --- a/tests/test_odmdata/test_pandas_memory_db.py +++ b/tests/test_odmdata/test_pandas_memory_db.py @@ -10,7 +10,7 @@ class TestPandasMemoryDB: """ - Test to Load up a series from a dataframe and load it into an in memory database + Test to Load up a series_service from a dataframe and load it into an in memory database """ def setup(self): self.connection_string = "sqlite:///:memory:" diff --git a/tests/test_odmservices/test_cv_service.py b/tests/test_odmservices/test_cv_service.py index 5bfd72a..4231653 100644 --- a/tests/test_odmservices/test_cv_service.py +++ b/tests/test_odmservices/test_cv_service.py @@ -1,19 +1,15 @@ -import pytest -import sqlalchemy.orm.exc -from odmtools.odmdata import Qualifier -from odmtools.odmservices import CVService - +from odmtools.odmservices import ReadService from tests import test_util session = None -class TestCVService: +class TestReadService: def setup(self): self.connection_string = "sqlite:///:memory:" - self.cv_service = CVService(self.connection_string, debug=False) - self.session = self.cv_service._session_factory.get_session() + self.cv_service = ReadService(self.connection_string, debug=False) + self.session = self.cv_service._session_factory.getSession() engine = self.cv_service._session_factory.engine test_util.build_db(engine) @@ -102,12 +98,12 @@ def test_get_censor_code_cvs(self): db_censor_code = self.cv_service.get_censor_code_cvs()[0] assert censor_code.term == db_censor_code.term - def test_get_sample_type_cvs(self): - assert self.cv_service.get_sample_type_cvs() == [] - - sample_type = test_util.add_sample_type_cv(self.session) - db_sample_type = self.cv_service.get_sample_type_cvs()[0] - assert sample_type.term == db_sample_type.term + # def test_get_sample_type_cvs(self): + # assert self.cv_service.get_sample_type_cvs() == [] + # + # sample_type = test_util.add_sample_type_cv(self.session) + # db_sample_type = self.cv_service.get_sample_type_cvs()[0] + # assert sample_type.term == db_sample_type.term def test_get_units(self): assert self.cv_service.get_units() == [] diff --git a/tests/test_odmservices/test_edit_service.py b/tests/test_odmservices/test_edit_service.py index 341bc5c..90b2d83 100644 --- a/tests/test_odmservices/test_edit_service.py +++ b/tests/test_odmservices/test_edit_service.py @@ -10,15 +10,15 @@ def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(connection_string=self.connection_string, debug=False) - engine = self.series_service._session_factory.engine + engine = self.series_service._connection.engine test_util.build_db(engine) self.memory_database = MemoryDatabase() self.memory_database.set_series_service(self.series_service) - self.session = self.memory_database.series_service._session_factory.get_session() + self.session = self.memory_database.series_service._session_factory.getSession() self.series = test_util.add_series_bulk_data(self.session) - #assert len(self.series.data_values) == 100 + #assert len(self.series_service.data_values) == 100 self.edit_service =EditService(1, connection= self.memory_database) @@ -72,7 +72,7 @@ def test_duplicate_values_filter(self): def test_save_series(self): stlen = len(self.series.data_values) assert self.edit_service.save() - val = self.series_service.get_series_by_id(self.series.id) + val = self.series_service.get_series(self.series.id) assert len(val.data_values)==stlen def test_save_as_series(self): @@ -90,13 +90,13 @@ def test_save_append_keep(self): #TODO add custon test len1= len(self.series.data_values) - # keep data from original series if overlap: + # keep data from original series_service if overlap: svalue = self.series.data_values[0] self.edit_service.memDB.updateValue([svalue.local_date_time],'+', 5 ) - news= self.edit_service.memDB.series_service.get_series_by_id(self.series.id) + news= self.edit_service.memDB.series_service.get_series(self.series.id) result = self.edit_service.save_appending(overwrite = False) len2= len(self.series.data_values) assert len1 == len2 @@ -108,7 +108,7 @@ def test_save_append_overwrite(self): svalue = self.series.data_values[0] self.edit_service.memDB.updateValue([svalue.local_date_time],'+', 5) - news= self.edit_service.memDB.series_service.get_series_by_id(self.series.id) + news= self.edit_service.memDB.series_service.get_series(self.series.id) result = self.edit_service.save_appending(overwrite = True) len2= len(self.series.data_values) assert len1 == len2 diff --git a/tests/test_odmservices/test_export_service.py b/tests/test_odmservices/test_export_service.py index acc146f..2cc8539 100644 --- a/tests/test_odmservices/test_export_service.py +++ b/tests/test_odmservices/test_export_service.py @@ -8,8 +8,10 @@ class TestExportService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() - engine = self.series_service._session_factory.engine + + self.session = self.series_service._connection.get_session() + engine = self.series_service._connection.engine + test_util.build_db(engine) self.series = test_util.add_series(self.session) diff --git a/tests/test_odmservices/test_series_service.py b/tests/test_odmservices/test_series_service.py index 5cf0e81..e98b551 100644 --- a/tests/test_odmservices/test_series_service.py +++ b/tests/test_odmservices/test_series_service.py @@ -1,5 +1,6 @@ from odmtools.odmdata import * from odmtools.odmservices import SeriesService +from odm2api.ODM2.models import Annotations from tests import test_util @@ -8,8 +9,13 @@ class TestSeriesService: def setup(self): self.connection_string = "sqlite:///:memory:" self.series_service = SeriesService(self.connection_string, debug=False) - self.session = self.series_service._session_factory.get_session() +# +# self.session = self.series_service._connection.get_session() +# engine = self.series_service._connection.engine +# ======= + self.session = self.series_service._session_factory.getSession() engine = self.series_service._session_factory.engine + test_util.build_db(engine) """ @pytest.fixture(scope="class", autouse=True) @@ -52,42 +58,41 @@ def setup(self): """ - def test_get_db_version(self): - version = test_util.add_version(self.session) - db_version = self.series_service.get_db_version() - assert version.version_number == db_version + # def test_get_db_version(self): + # version = test_util.add_version(self.session) + # db_version = self.series_service.get_db_version() + # assert version.version_number == db_version def test_get_all_sites_empty(self): sites = self.series_service.get_used_sites() #assert len(sites) == 0 assert sites is None - def test_create_qualifier(self): + def test_create_annotation(self): qual = Qualifier() qual.code = "ABC123" qual.description = "This is a test" - self.series_service.create_qualifier_by_qual(qual) + self.series_service.create_annotation_by_anno(qual) assert qual.id is not None - def test_get_qualifier_by_code(self): - assert self.series_service.get_all_qualifiers() == [] + def test_get_annotation_by_code(self): + assert self.series_service.get_all_qualifiers() == None - qual= self.series_service.create_qualifier("ABC123","This is a test") + qual = self.series_service.create_annotation("ABC123", "This is a test") db_qual = self.series_service.get_qualifier_by_code("ABC123") assert qual.id == db_qual.id - def test_get_qualifiers(self): - assert self.series_service.get_all_qualifiers() == [] + def test_get_annotation(self): + assert self.series_service.get_all_qualifiers() == None - qual= self.series_service.create_qualifier("ABC123","This is a test") + qual= self.series_service.create_annotation("ABC123", "This is a test") db_qual = self.series_service.get_all_qualifiers()[0] assert qual.id == db_qual.id - def test_get_all_sites(self): assert self.series_service.get_used_sites() is None @@ -104,7 +109,6 @@ def test_get_all_sites(self): if isinstance(sites, list) and len(sites) > 0: assert site.code == sites[0].code - def test_get_site_by_id_fail(self): assert self.series_service.get_site_by_id(0) == None @@ -179,19 +183,19 @@ def test_get_all_series(self): assert series.id == all_series[0].id def test_get_series_by_id(self): - assert self.series_service.get_series_by_id(10) == None + assert self.series_service.get_series(10) == None series = test_util.add_series(self.session) - db_series = self.series_service.get_series_by_id(series.id) + db_series = self.series_service.get_series(series.id) assert series.id == db_series.id def test_get_series_by_id_quint(self): - assert self.series_service.get_series_by_id_quint(10, 10, 10, 10, 10) == None + assert self.series_service.resultExists(10, 10, 10, 10, 10) == None series = test_util.add_series(self.session) - db_series = self.series_service.get_series_by_id_quint(series.site_id, series.variable_id, series.method_id, - series.source_id, series.quality_control_level_id) + db_series = self.series_service.resultExists(series.site_id, series.variable_id, series.method_id, + series.source_id, series.quality_control_level_id) assert series.id == db_series.id @@ -209,18 +213,18 @@ def test_series_exists(self): ''' def test_save_series(self): - series = Series() + series_service = Series() site = test_util.add_site(self.session) variable = test_util.add_variable(self.session) method = test_util.add_method(self.session) source = test_util.add_source(self.session) qcl = test_util.add_qcl(self.session) - series.site_id = site.id - series.variable_id = variable.id - series.method_id = method.id - series.source_id = source.id - series.quality_control_level_id = qcl.id + series_service.site_id = site.id + series_service.variable_id = variable.id + series_service.method_id = method.id + series_service.source_id = source.id + series_service.quality_control_level_id = qcl.id dvs = [] for val in range(10): @@ -233,10 +237,10 @@ def test_save_series(self): dv.quality_control_level_id = qcl.id dvs.append(dv) - print series.variable_code - assert self.series_service.save_series(series) + print series_service.variable_code + assert self.series_service.save_series(series_service) assert self.series_service.series_exists(site.id, variable.id, method.id, source.id, qcl.id) - assert not self.series_service.save_series(series) + assert not self.series_service.save_series(series_service) ''' def test_get_data_value_by_id(self): @@ -249,17 +253,17 @@ def test_get_data_value_by_id(self): assert dv.data_value == db_dv.data_value def test_get_qcl_by_id(self): - assert self.series_service.get_qcl_by_id(10) == None + assert self.series_service.get_processing_level_by_id(10) == None - qcl = test_util.add_qcl(self.session) - db_qcl = self.series_service.get_qcl_by_id(qcl.id) + qcl = test_util.add_process_level(self.session) + db_qcl = self.series_service.get_processing_level_by_id(qcl.id) assert qcl.code == db_qcl.code def test_get_all_qcls(self): - assert self.series_service.get_all_qcls() == [] + assert self.series_service.get_all_processing_levels() == [] - qcl = test_util.add_qcl(self.session) - all_qcls = self.series_service.get_all_qcls() + qcl = test_util.add_process_level(self.session) + all_qcls = self.series_service.get_all_processing_levels() assert len(all_qcls) == 1 assert qcl.id == all_qcls[0].id @@ -288,7 +292,7 @@ def test_delete_dvs(self): subset = dvs[:5] self.series_service.delete_dvs([x.local_date_time for x in subset]) assert self.series_service.get_data_value_by_id(subset[0].id) == None - series = self.series_service.get_series_by_id(series.id) # Reload + series = self.series_service.get_series(series.id) # Reload assert len(series.data_values) == 5 def test_update_dvs(self): @@ -300,7 +304,7 @@ def test_update_dvs(self): subset[i].data_value = 100 self.series_service.update_dvs(subset) - series = self.series_service.get_series_by_id(series.id) + series = self.series_service.get_series(series.id) assert series.data_values[0].data_value == 100 def test_create_new_series(self): @@ -308,7 +312,7 @@ def test_create_new_series(self): variable = test_util.add_variable(self.session) method = test_util.add_method(self.session) source = test_util.add_source(self.session) - qcl = test_util.add_qcl(self.session) + qcl = test_util.add_process_level(self.session) dvs = [] for val in range(10): @@ -334,7 +338,7 @@ def test_update_series(self): self.series_service.update_series(series) - series = self.series_service.get_series_by_id(series.id) + series = self.series_service.get_series(series.id) assert series.site_code == "NEW" assert series.variable_code == "NEW" @@ -349,41 +353,37 @@ def test_create_method(self): def test_create_variable(self): unit = test_util.add_unit(self.session) - variable = self.series_service.create_variable("Code", "Name", "Speciation", unit.id, "SampleMedium", - "ValueType", True, # is_regular - 5.0, # time_support - unit.id, # time_unit_id - "DataType", "GeneralCategory", -999.0) # no_data_value + variable = self.series_service.create_variable("Code", "Name", "Speciation", -9999.0) # no_data_value assert variable.id != None assert variable.code == "Code" assert variable.variable_unit_id == unit.id def test_create_qcl(self): - qcl = self.series_service.create_qcl("Code", "Definition", "Explanation") + qcl = self.series_service.create_processing_level("Code", "Definition", "Explanation") assert qcl.id != None assert qcl.code == "Code" def test_delete_series(self): series = test_util.add_series(self.session) - assert self.series_service.get_series_by_id(series.id) != None + assert self.series_service.get_series(series.id) != None self.series_service.delete_series(series) - assert self.series_service.get_series_by_id(series.id) == None + assert self.series_service.get_series(series.id) == None def test_delete_values(self): series = test_util.add_series(self.session) - assert self.series_service.get_series_by_id(series.id) != None + assert self.series_service.get_series(series.id) != None self.series_service.delete_values_by_series(series) - val = self.series_service.get_series_by_id(series.id) + val = self.series_service.get_series(series.id) print val assert val != None def test_qcl_exists(self): - qcl = test_util.add_qcl(self.session) + qcl = test_util.add_process_level(self.session) assert self.series_service.qcl_exists(qcl) == True qcl.code = "00000" diff --git a/tests/test_util.py b/tests/test_util.py index 43789d3..6827b9f 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -3,6 +3,8 @@ from odmtools.odmdata import * import os import sys +from odm2api.ODM2.models import * +from odm2api.ODM2.services.readService import DetailedResult def build_db(engine): @@ -13,7 +15,7 @@ def build_db(engine): def add_bulk_data_values(session, series, dvs_size): """ - Load up exampleData.csv into a series' datavalues field + Load up exampleData.csv into a series_service' datavalues field """ assert 10000 >= dvs_size > 0 path = os.path.dirname(os.path.realpath(__file__)) @@ -23,27 +25,23 @@ def add_bulk_data_values(session, series, dvs_size): df['DateTimeUTC'] = pd.to_datetime(df['DateTimeUTC']).astype(datetime.datetime) dvs = [] for record in df.to_dict('records')[:dvs_size]: - dv = DataValue() - dv.data_value = record['DataValue'] - dv.local_date_time = record['LocalDateTime'] - dv.utc_offset = record['UTCOffset'] - dv.date_time_utc = record['DateTimeUTC'] - dv.site_id = series.site_id - dv.variable_id = series.variable_id - dv.censor_code = record['CensorCode'] - dv.method_id = series.method_id - dv.source_id = series.source_id - dv.quality_control_level_id = series.quality_control_level_id - dvs.append(dv) + timeseries_result_value = TimeSeriesResultValues() + timeseries_result_value.DataValue = record["DataValue"] + timeseries_result_value.ValueDateTimeUTCOffset = record["UTCOffset"] + timeseries_result_value.ValueDateTime = record["DateTimeUTC"] + timeseries_result_value.CensorCodeCV = record["CensorCode"] + timeseries_result_value.QualityCodeCV = series.quality_control_level_code + dvs.append(timeseries_result_value) series.data_values = dvs session.add_all(dvs) session.commit() return df + def add_series_bulk_data(session, dvs_size=50): site = add_site(session) var = add_variable(session) - qcl = add_qcl(session) + qcl = add_process_level(session) method = add_method(session) source = add_source(session) @@ -73,50 +71,31 @@ def add_series_bulk_data(session, dvs_size=50): session.commit() return series + # Create Series objects def add_series(session): - site = add_site(session) + result = Results() var = add_variable(session) - qcl = add_qcl(session) - method = add_method(session) - source = add_source(session) - - series = Series() - series.site = site - series.site_code = site.code - series.variable = var - series.variable_code = var.code - series.method = method - series.source = source - series.quality_control_level_id = qcl.id - - dvs = add_data_values(session, series) - series.begin_date_time = dvs[0].local_date_time - series.end_date_time = dvs[-1].local_date_time - series.begin_date_time_utc = dvs[0].date_time_utc - series.end_date_time_utc = dvs[-1].date_time_utc - series.value_count = len(dvs) - - session.add(series) + qcl = add_process_level(session) + result.VariableObj = var + result.ProcessingLevelObj = qcl + result.ProcessingLevelID = qcl.ProcessingLevelID + session.add(result) session.commit() - return series + return result def add_data_values(session, series): dvs = [] for i in range(10): - dv = DataValue() - dv.data_value = i - dv.local_date_time = datetime.datetime.now() - datetime.timedelta(days=i) - dv.utc_offset = 0 - dv.date_time_utc = dv.local_date_time - dv.site_id = series.site_id - dv.variable_id = series.variable_id - dv.censor_code = "NC" - dv.method_id = series.method_id - dv.source_id = series.source_id - dv.quality_control_level_id = series.quality_control_level_id - dvs.append(dv) + timeseries = TimeSeriesResultValues() + timeseries.DataValue = i + timeseries.TimeAggregationInterval = datetime.datetime.now() - datetime.timedelta(days=i) + timeseries.ValueDateTimeUTCOffset = 0 + timeseries.ValueDateTime = timeseries.TimeAggregationInterval + timeseries.CensorCodeCV = "NC" + timeseries.QualityCodeCV = series.quality_control_level + dvs.append(timeseries) series.data_values = dvs session.add_all(dvs) @@ -126,14 +105,14 @@ def add_data_values(session, series): def add_site(session): spatial_ref = add_spatial_reference(session) - site = Site("ABC123", "Test Site") - site.latitude = 10.0 - site.longitude = 10.0 - site.lat_long_datum_id = spatial_ref.id - site.local_projection_id = spatial_ref.id - site.elevation_m = 1000 - site.local_x = 10.0 - site.local_y = 10.0 + site = Sites("ABC123", "Test Site") + site.Latitude = 10.0 + site.Longitude = 10.0 + site.lat_long_datum_id = spatial_ref.SpatialReferenceID + site.local_projection_id = spatial_ref.SpatialReferenceID + site.Elevation_m = 1000 + # site.local_x = 10.0 + # site.local_y = 10.0 session.add(site) session.commit() return site @@ -141,60 +120,48 @@ def add_site(session): def add_variable(session): unit = add_unit(session) - variable = Variable() - variable.code = "ABC123" - variable.name = "Test Variable" - variable.speciation = "Test" - variable.variable_unit_id = unit.id - variable.sample_medium = "Test Medium" - variable.value_type = "Test Val Type" - variable.is_regular = True - variable.time_support = 3.14 - variable.time_unit_id = unit.id - variable.data_type = "Test Data Type" - variable.general_category = "Test Category" - variable.no_data_value = -2000.0 + variable = Variables() + variable.VariableCode = "ABC123" + variable.VariableNameCV = "Test Variable" + variable.SpeciationCV = "Test" + variable.VariableID = unit.id + variable.NoDataValue = -2000.0 session.add(variable) session.commit() return variable def add_method(session): - method = Method() - method.description = "This is a test" + method = Methods() + method.MethodDescription = "This is a test" session.add(method) session.commit() return method -def add_qcl(session): - qcl = QualityControlLevel() - qcl.code = "ABC123" - qcl.definition = "This is a test" - qcl.explanation = "A test is a thing that tests code" - session.add(qcl) +def add_process_level(session): + proc_level = ProcessingLevels() + proc_level.ProcessingLevelCode = "ABC123" + proc_level.Definition = "This is a test" + proc_level.Explanation = "A test is a thing that tests code" + session.add(proc_level) session.commit() - return qcl + return proc_level def add_source(session): - source = Source() - source.organization = "Test Organization" - source.description = "This is a test" - source.contact_name = "Test Name" - source.phone = "555-1234" - source.email = "source@example.com" - source.address = "123 Test Street" - source.city = "Metropolis" - source.state = "NY" - source.zip_code = "12345" - source.citation = "Test Citation" - - iso = add_iso_metadata(session) - source.iso_metadata_id = iso.id - session.add(source) + organization = Organizations() + affiliation = Affiliations() + organization.OrganizationName = "Test Organization" + organization.OrganizationDescription = "This is a test" + affiliation.PersonLink = "Test Name" + affiliation.PrimaryPhone = "555-1234" + affiliation.PrimaryEmail = "source@example.com" + affiliation.PrimaryAddress = "123 Test Street" + affiliation.OrganizationObj = organization + session.add(affiliation) session.commit() - return source + return affiliation def add_iso_metadata(session): @@ -209,8 +176,8 @@ def add_iso_metadata(session): def add_spatial_reference(session): - spatial_ref = SpatialReference() - spatial_ref.srs_name = "This is a test" + spatial_ref = SpatialReferences() + spatial_ref.SRSName = "This is a test" session.add(spatial_ref) session.commit() return spatial_ref @@ -258,19 +225,19 @@ def add_site_type_cv(session): def add_variable_name_cv(session): - var_name_cv = VariableNameCV() - var_name_cv.term = "Test" - var_name_cv.definition = "This is a test" + var_name_cv = CVVariableName() + var_name_cv.Term = "Test" + var_name_cv.Definition = "This is a test" session.add(var_name_cv) session.commit() return var_name_cv def add_unit(session): - unit = Unit() - unit.name = "Test" - unit.type = "Test" - unit.abbreviation = "T" + unit = Units() + unit.UnitsName = "Test" + unit.UnitsTypeCV = "Test" + unit.UnitsAbbreviation = "T" session.add(unit) session.commit() return unit @@ -286,9 +253,9 @@ def add_offset_type_cv(session, unit_id): def add_speciation_cv(session): - spec = SpeciationCV() - spec.term = "Test" - spec.definition = "This is a test" + spec = CVSpeciation() + spec.Term = "Test" + spec.Definition = "This is a test" session.add(spec) session.commit() return spec