gui.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. # -*- coding: utf-8 -*-
  2. from pathlib import Path
  3. from typing import Generator, List
  4. from PySide6.QtWidgets import (QDialog, QMainWindow, QMessageBox,
  5. QFileDialog, QTableWidgetItem)
  6. from exceptions import NoSelectedQuakesError, ConnectDatabaseError, \
  7. FormatToStrError
  8. from quake_storages import save_quakes, get_storage
  9. from ui.main_window_ui import Ui_MainWindow # type: ignore
  10. from ui.db_conn_ui import Ui_Dialog # type: ignore
  11. import config
  12. from quakes_from_db import get_quakes, QueryParams
  13. import logging.config
  14. logging.config.dictConfig(config.LOG_CONFIG)
  15. log = logging.getLogger('gui_logger')
  16. class Window(QMainWindow, Ui_MainWindow):
  17. """Main window of application"""
  18. def __init__(self, parent=None):
  19. super().__init__(parent)
  20. self.setupUi(self)
  21. self._connect_signals_slots()
  22. self.file_filter = ';;'.join(config.FILES_FILTERS.values())
  23. self.quakes = None
  24. self.statusBar().showMessage('Ready')
  25. def search_quakes(self) -> None:
  26. self.progressBar.setValue(5)
  27. try:
  28. log.info(f'Start search for records. '
  29. f'DB connection config: {config.DB}.')
  30. query_params = self._get_query_params()
  31. log.info(f'{query_params}')
  32. self.quakes = get_quakes(query_params)
  33. self._set_data_into_table()
  34. self.statusBar().showMessage(f'Searched quakes: '
  35. f'{self.tableWidget.rowCount()}')
  36. self.progressBar.setValue(100)
  37. except ConnectDatabaseError as exc:
  38. self.statusBar().showMessage('Error: cannot connect to Database!')
  39. log.exception(exc)
  40. self._show_error_dialog(message=f'{exc.args[0]}\n\n'
  41. f'Check connection settings '
  42. f'(File->Settings->Connection) '
  43. f'and try again!')
  44. def get_selected_quakes(self) -> Generator:
  45. """Obtain tuple of Quake() according to selected quakes
  46. from the table of GUI"""
  47. selected_id = self._get_selected_quakes_id()
  48. return (quake for quake in self.quakes
  49. if quake.id in selected_id)
  50. def save_file(self) -> None:
  51. """Save function depending on ext of file."""
  52. self.progressBar.setValue(10)
  53. dialog = QFileDialog(self)
  54. file = dialog.getSaveFileName(self, dir='untitled.txt',
  55. filter=self.file_filter)[0]
  56. if not file:
  57. return self._show_error_dialog('File is not selected! '
  58. 'Select a file and try again, '
  59. 'please.')
  60. file = Path(file)
  61. log.info(f'file to save the quakes: {file}')
  62. ext = file.suffix
  63. try:
  64. quakes = self.get_selected_quakes()
  65. storage = get_storage(ext)
  66. save_quakes(quakes, storage(file))
  67. self.statusBar().showMessage('Writing into the file '
  68. 'completed successfully.')
  69. log.info(f'Writing into the file completed successfully.')
  70. self.progressBar.setValue(100)
  71. except (NoSelectedQuakesError, FormatToStrError,
  72. PermissionError) as exc:
  73. self.statusBar().showMessage('Error: cannot save the data!')
  74. log.exception(exc)
  75. self._show_error_dialog(message=f'Cannot save the data '
  76. f'into the file "{file}"!\n'
  77. f'\n{exc.args}')
  78. def _show_error_dialog(self, message) -> None:
  79. title = 'Something went wrong'
  80. QMessageBox.critical(self, title, message,
  81. buttons=QMessageBox.Ok,
  82. defaultButton=QMessageBox.Ok)
  83. def _connect_signals_slots(self) -> None:
  84. self.save_as_button.clicked.connect(self.save_file)
  85. self.actionBulletin.triggered.connect(self.save_file)
  86. self.actionCatalog.triggered.connect(self.save_file)
  87. self.action_NAS_bulletin.triggered.connect(self.save_file)
  88. self.action_ArcGIS.triggered.connect(self.save_file)
  89. self.actionConnection.triggered.connect(self._show_connection_dialog)
  90. self.actionAbout.triggered.connect(self.about)
  91. self.search_events_button.clicked.connect(self.search_quakes)
  92. def _show_connection_dialog(self) -> None:
  93. conn_dialog = ConnectionDialog(self)
  94. conn_dialog.exec()
  95. def _set_data_into_table(self) -> None:
  96. self.tableWidget.setRowCount(0)
  97. for quake in self.quakes:
  98. row = self.tableWidget.rowCount()
  99. self.tableWidget.insertRow(row)
  100. quake_vals = (quake.id, quake.origin_dt, quake.lat, quake.lon,
  101. quake.depth, quake.magnitude.ML,
  102. quake.magnitude.MPSP, quake.reg)
  103. for column, val in enumerate(quake_vals):
  104. self.tableWidget.setItem(
  105. row, column, QTableWidgetItem(f'{val}'))
  106. sta_ph_time = []
  107. for sta in quake.stations:
  108. sta_ph_time.append(
  109. ' '.join(
  110. (sta.name, sta.phase, f'{sta.phase_dt}')) + '\n')
  111. self.tableWidget.setItem(
  112. row, 8, QTableWidgetItem('\n'.join(sta_ph_time)))
  113. def _get_selected_quakes_id(self) -> List[str]:
  114. selected_items_amnt = len(self.tableWidget.selectedItems())
  115. log.info(f'selected items amount: {selected_items_amnt}')
  116. col_count = self.tableWidget.columnCount()
  117. if selected_items_amnt == 0 or selected_items_amnt % col_count != 0:
  118. raise NoSelectedQuakesError('Nothing is selected! At least one row'
  119. ' from the table must be selected!')
  120. log.info(f'selected quakes amount: {selected_items_amnt / col_count}')
  121. return [self.tableWidget.selectedItems()[i].text()
  122. for i in range(selected_items_amnt) if i % col_count == 0]
  123. def _get_query_params(self) -> QueryParams:
  124. return QueryParams(from_dt=self.from_dateTime.text(),
  125. to_dt=self.to_dateTime.text(),
  126. comment=self.comment_line.text(),
  127. sta=self.sta_line.text(),
  128. from_mag=f'{self.from_Mag.value()}',
  129. to_mag=f'{self.to_Mag.value()}')
  130. def about(self) -> None:
  131. QMessageBox.about(
  132. self,
  133. 'About getquakes ',
  134. '<p>Desktop application for obtaining a list of earthquakes from '
  135. 'a database and saving it as a bulletin or other file structure.'
  136. '</p>'
  137. '<p>The app built with:</p>'
  138. '<p>- Qt Designer</p>'
  139. '<p>- PySide6</p>'
  140. '<p>- Python</p>'
  141. '<p>Author: Alexey Danilov, a7exdanilov@gmail.com</p>',
  142. )
  143. class ConnectionDialog(QDialog, Ui_Dialog):
  144. def __init__(self, parent=None):
  145. super().__init__(parent)
  146. self.setupUi(self)
  147. self._connect_signals_slots()
  148. self._init_db_config_gui()
  149. def _connect_signals_slots(self) -> None:
  150. self.buttonBox.accepted.connect(self.set_db_conn_config)
  151. def _init_db_config_gui(self) -> None:
  152. self.host_line.setText(config.DB['host'])
  153. self.port_line.setText(config.DB['port'])
  154. self.db_name_line.setText(config.DB['database'])
  155. self.user_line.setText(config.DB['user'])
  156. self.password_line.setText(config.DB['password'])
  157. def set_db_conn_config(self) -> None:
  158. content_lst = Path('config.py').open('r', encoding='utf8').readlines()
  159. db_conf = {'host': self.host_line.text(),
  160. 'port': self.port_line.text(),
  161. 'database': self.db_name_line.text(),
  162. 'user': self.user_line.text(),
  163. 'password': self.password_line.text()}
  164. with Path('config.py').open('w', encoding='utf8') as config_file:
  165. config_file.writelines(content_lst[:3])
  166. config_file.write(f'DB = {db_conf}\n')
  167. config_file.writelines(content_lst[4:])
  168. self.close()
  169. config.DB = db_conf
  170. log.info(f'db connection config is changed to {db_conf}')