CS4820-Winter2020 student project to create a COUNTER SUSHI R5 harvester and related functionality app for Windows and Mac
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

302 lines
14 KiB

import os
import sip
import json
from typing import Tuple, Dict
from PyQt5.QtGui import QIntValidator, QDoubleValidator
from PyQt5.QtWidgets import QFrame, QVBoxLayout, QComboBox, QLineEdit, QSpacerItem, QSizePolicy, QLabel
import ManageDB
from Settings import SettingsModel
from ui import SearchTab, SearchAndFrame, SearchOrFrame
from Constants import *
from GeneralUtils import *
class SearchController:
"""Controls the Search tab
:param search_ui: the UI for the search_widget
:param settings: the user's settings"""
def __init__(self, search_ui: SearchTab.Ui_search_tab, settings: SettingsModel):
self.main_window = search_ui
self.settings = settings
# set up report types combobox
self.report_parameter = search_ui.search_report_parameter_combobox
self.report_parameter.addItems(ALL_REPORTS)
# set up start year dateedit
self.start_year_parameter = search_ui.search_start_year_parameter_dateedit
self.start_year_parameter.setDate(QDate.currentDate())
# set up end year dateedit
self.end_year_parameter = search_ui.search_end_year_parameter_dateedit
self.end_year_parameter.setDate(QDate.currentDate())
# set up search button
self.search_button = search_ui.search_button
self.search_button.clicked.connect(self.search)
self.open_results_file_checkbox = search_ui.search_open_file_checkbox
self.open_results_folder_checkbox = search_ui.search_open_folder_checkbox
# set up export button
self.export_button = search_ui.search_export_button
self.export_button.clicked.connect(self.export_parameters)
# set up import button
self.import_button = search_ui.search_import_button
self.import_button.clicked.connect(self.import_parameters)
# set up add and clause button
def add_and_and_or_clause():
"""Invoked to add an and clause containing an or clause to the search"""
and_clause = self.add_and_clause()
self.add_or_clause(and_clause)
self.hide_or_label_in_first_or_clause(and_clause)
self.hide_and_label_in_first_and_clause()
self.add_and_button = search_ui.search_add_and_button
self.add_and_button.clicked.connect(add_and_and_or_clause)
# resets the search clauses when the report type is changed
def refresh_and_add_clauses():
"""Resets the search clauses, then adds an and clause containing an or clause"""
self.refresh_clauses()
add_and_and_or_clause()
self.report_parameter.currentTextChanged.connect(refresh_and_add_clauses)
self.and_clause_parameters_scrollarea = search_ui.search_and_clause_parameters_scrollarea
self.and_clause_parameters_frame = None
refresh_and_add_clauses()
def update_settings(self, settings: SettingsModel):
"""Called when the settings are saved
:param settings: the new settings"""
self.settings = settings
def refresh_clauses(self):
"""Resets the search clauses"""
self.and_clause_parameters_frame = QFrame()
self.and_clause_parameters_frame.setLayout(QVBoxLayout())
self.and_clause_parameters_frame.layout().addItem(QSpacerItem(0, 0, QSizePolicy.Expanding,
QSizePolicy.Expanding))
self.and_clause_parameters_scrollarea.setWidget(self.and_clause_parameters_frame)
def add_and_clause(self) -> SearchAndFrame.Ui_search_and_clause_parameter_frame:
"""Adds an and clause to the search"""
and_clause = QFrame()
and_clause_ui = SearchAndFrame.Ui_search_and_clause_parameter_frame()
and_clause_ui.setupUi(and_clause)
# set up add or clause button
def add_or_to_this_and():
"""Adds an or clause to this and clause"""
self.add_or_clause(and_clause_ui)
self.hide_or_label_in_first_or_clause(and_clause_ui)
and_clause_ui.search_add_or_clause_button.clicked.connect(add_or_to_this_and)
# set up remove current and clause button
def remove_this_and():
"""Removes this and clause"""
self.and_clause_parameters_frame.layout().removeWidget(and_clause)
sip.delete(and_clause)
self.hide_and_label_in_first_and_clause()
self.and_clause_parameters_frame.repaint()
and_clause_ui.search_remove_and_clause_button.clicked.connect(remove_this_and)
# add to the layout
self.and_clause_parameters_frame.layout().insertWidget(self.and_clause_parameters_frame.layout().count() - 1,
and_clause)
return and_clause_ui
def hide_and_label_in_first_and_clause(self):
"""Hides the and label for the first and clause in the search"""
and_clause = self.and_clause_parameters_frame.findChild(QFrame, 'search_and_clause_parameter_frame')
if and_clause:
and_clause.findChild(QLabel, "search_and_label").hide()
def add_or_clause(self, and_clause: SearchAndFrame.Ui_search_and_clause_parameter_frame) \
-> SearchOrFrame.Ui_search_or_clause_parameter_frame:
"""Adds an or clause to the search
:param and_clause: the and clause the or clause is added to"""
or_clause = QFrame()
or_clause_ui = SearchOrFrame.Ui_search_or_clause_parameter_frame()
or_clause_ui.setupUi(or_clause)
# fill field combobox
field_combobox = or_clause_ui.search_field_parameter_combobox
for field in ManageDB.get_view_report_fields_list(self.report_parameter.currentText()):
if field[NAME_KEY] not in FIELDS_NOT_IN_SEARCH_DROPDOWN:
field_combobox.addItem(field[NAME_KEY], field['type'])
type_label = or_clause_ui.search_type_label
value_lineedit = or_clause_ui.search_value_parameter_lineedit
def on_field_changed():
"""Invoked when the field parameter is changed"""
type_label.setText(field_combobox.currentData().capitalize() + " Input")
value_lineedit.setText(None)
if field_combobox.currentData() == 'INTEGER':
value_lineedit.setValidator(QIntValidator())
elif field_combobox.currentData() == 'REAL':
value_lineedit.setValidator(QDoubleValidator())
else:
value_lineedit.setValidator(None)
field_combobox.currentTextChanged.connect(on_field_changed)
on_field_changed()
# fill comparison operator combobox
comparison_combobox = or_clause_ui.search_comparison_parameter_combobox
comparison_combobox.addItems(COMPARISON_OPERATORS)
comparison_combobox.addItems(NON_COMPARISONS)
def on_comparison_changed():
"""Invoked when the comparison parameter is changed"""
if comparison_combobox.currentText() in NON_COMPARISONS:
value_lineedit.setText(None)
value_lineedit.setEnabled(False)
else:
value_lineedit.setEnabled(True)
comparison_combobox.currentTextChanged.connect(on_comparison_changed)
# set up remove current or clause button
def remove_this_or():
"""Removes this or clause"""
and_clause.search_or_clause_parameters_frame.layout().removeWidget(or_clause)
sip.delete(or_clause)
self.hide_or_label_in_first_or_clause(and_clause)
and_clause.search_or_clause_parameters_frame.repaint()
or_clause_ui.search_remove_or_clause_button.clicked.connect(remove_this_or)
# add to parent and clause's layout
and_clause.search_or_clause_parameters_frame.layout().addWidget(or_clause)
return or_clause_ui
def hide_or_label_in_first_or_clause(self, and_clause: SearchAndFrame.Ui_search_and_clause_parameter_frame):
"""Hides the or label for the first or clause in an and clause
:param and_clause: the and clause"""
or_clause = and_clause.search_or_clause_parameters_frame.findChild(QFrame, 'search_or_clause_parameter_frame')
if or_clause:
or_clause.findChild(QLabel, "search_or_label").hide()
def export_parameters(self):
"""Exports the current search parameters to the selected file"""
file_name = choose_save(JSON_FILTER)
if file_name != '':
if not file_name.lower().endswith('.dat'):
file_name += '.dat'
report, start_year, end_year, search_parameters = self.get_search_parameters()
file = open(file_name, 'w', encoding='utf-8-sig')
if file.mode == 'w':
json.dump({'report': report, 'start_year': start_year, 'end_year': end_year,
'search_parameters': search_parameters}, file)
show_message('Search saved to ' + file_name)
else:
print('Error, no file location selected')
def import_parameters(self):
"""Imports a new set of search parameters from the selected file"""
file_name = choose_file(JSON_FILTER)
if file_name != '':
fields = json.loads(read_json_file(file_name))
self.report_parameter.setCurrentText(fields['report'])
self.start_year_parameter.setDate(QDate(fields['start_year'], 1, 1))
self.end_year_parameter.setDate(QDate(fields['end_year'], 1, 1))
clauses = fields['search_parameters']
self.refresh_clauses()
for clause in clauses:
and_clause = self.add_and_clause()
for sub_clause in clause:
or_clause = self.add_or_clause(and_clause)
or_clause.search_field_parameter_combobox.setCurrentText(sub_clause[FIELD_KEY])
or_clause.search_comparison_parameter_combobox.setCurrentText(sub_clause[COMPARISON_KEY])
or_clause.search_value_parameter_lineedit.setText(str(sub_clause[VALUE_KEY]))
def search(self):
"""Queries the database based on the current search parameters and saves the results to the selected file"""
report, start_year, end_year, search_parameters = self.get_search_parameters()
# sql query to get search results
sql_text, data = ManageDB.search_sql_text(report, start_year, end_year, search_parameters)
headers = []
for field in ManageDB.get_view_report_fields_list(report):
headers.append(field[NAME_KEY])
file_name = choose_save(TSV_FILTER)
if file_name != '':
if not file_name.lower().endswith('.tsv'):
file_name += '.tsv'
connection = ManageDB.create_connection(DATABASE_LOCATION)
if connection is not None:
results = ManageDB.run_select_sql(connection, sql_text, data)
connection.close()
results.insert(0, headers)
if self.settings.show_debug_messages: print(results)
save_data_as_tsv(file_name, results)
if self.open_results_folder_checkbox.isChecked():
open_file_or_dir(os.path.dirname(file_name))
if self.open_results_file_checkbox.isChecked():
open_file_or_dir(file_name)
if not self.open_results_file_checkbox.isChecked():
show_message('Results saved to ' + file_name)
else:
print('Error, no connection')
else:
print('Error, no file location selected')
def get_search_parameters(self) -> Tuple[str, int, int, Sequence[Sequence[Dict[str, Any]]]]:
"""Reads the current search parameters from the UI
:returns: (report, start_year, end_year, search_parameters) a Tuple with the kind of report selected, the
starting year selected, the ending year selected, and a list of the search parameters in POS form (and of
ors)"""
# get report type
report = self.report_parameter.currentText()
# get start year
start_year = int(self.start_year_parameter.text())
# get end year
end_year = int(self.end_year_parameter.text())
search_parameters = []
for and_widget in self.and_clause_parameters_frame.findChildren(QFrame, 'search_and_clause_parameter_frame'):
# iterate over and clauses
or_clause_parameters = and_widget.findChild(QFrame, 'search_or_clause_parameters_frame')
or_clauses = []
for or_widget in or_clause_parameters.findChildren(QFrame, 'search_or_clause_parameter_frame'):
# iterate over child or clauses
# get parameters for clause
field_parameter_combobox = or_widget.findChild(QComboBox, 'search_field_parameter_combobox')
field_parameter = field_parameter_combobox.currentText()
comparison_parameter_combobox = or_widget.findChild(QComboBox, 'search_comparison_parameter_combobox')
comparison_parameter = comparison_parameter_combobox.currentText()
value_parameter_lineedit = or_widget.findChild(QLineEdit, 'search_value_parameter_lineedit')
value_parameter = None
if comparison_parameter in NON_COMPARISONS:
pass
elif field_parameter_combobox.currentData() == 'INTEGER':
value_parameter = int(value_parameter_lineedit.text())
elif field_parameter_combobox.currentData() == 'REAL':
value_parameter = float(value_parameter_lineedit.text())
else:
value_parameter = value_parameter_lineedit.text()
or_clauses.append(
{FIELD_KEY: field_parameter, COMPARISON_KEY: comparison_parameter, VALUE_KEY: value_parameter})
search_parameters.append(or_clauses)
return report, start_year, end_year, search_parameters