class AttributeCreatorWidget(CustomScrollableListItem): def __init__(self, document_base_creator_widget) -> None: super(AttributeCreatorWidget, self).__init__(document_base_creator_widget) self.document_base_creator_widget = document_base_creator_widget self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(10) self.name = QLineEdit() self.name.setFont(CODE_FONT_BOLD) self.name.setStyleSheet("border: none") self.layout.addWidget(self.name) self.delete_button = QPushButton() self.delete_button.setIcon(QIcon("aset_ui/resources/trash.svg")) self.delete_button.setFlat(True) self.delete_button.clicked.connect(self._delete_button_clicked) self.layout.addWidget(self.delete_button) def update_item(self, item, params=None): self.name.setText(item) def _delete_button_clicked(self): self.document_base_creator_widget.delete_attribute(self.name.text()) def enable_input(self): self.delete_button.setEnabled(True) def disable_input(self): self.delete_button.setEnabled(False)
class NuggetListWidget(QWidget): def __init__(self, interactive_matching_widget): super(NuggetListWidget, self).__init__(interactive_matching_widget) self.interactive_matching_widget = interactive_matching_widget self.layout = QVBoxLayout(self) self.layout.setContentsMargins(0, 0, 0, 0) self.layout.setSpacing(10) # top widget self.top_widget = QWidget() self.top_layout = QHBoxLayout(self.top_widget) self.top_layout.setContentsMargins(0, 0, 0, 0) self.top_layout.setSpacing(10) self.layout.addWidget(self.top_widget) self.description = QLabel( "Below you see a list of guessed matches for you to confirm or correct." ) self.description.setFont(LABEL_FONT) self.top_layout.addWidget(self.description) self.stop_button = QPushButton("Continue With Next Attribute") self.stop_button.setFont(BUTTON_FONT) self.stop_button.clicked.connect(self._stop_button_clicked) self.stop_button.setMaximumWidth(240) self.top_layout.addWidget(self.stop_button) # nugget list self.nugget_list = CustomScrollableList(self, NuggetListItemWidget) self.layout.addWidget(self.nugget_list) def update_nuggets(self, nuggets): max_start_chars = max([ nugget[CachedContextSentenceSignal]["start_char"] for nugget in nuggets ]) self.nugget_list.update_item_list(nuggets, max_start_chars) def _stop_button_clicked(self): self.interactive_matching_widget.main_window.give_feedback_task( {"message": "stop-interactive-matching"}) def enable_input(self): self.stop_button.setEnabled(True) self.nugget_list.enable_input() def disable_input(self): self.stop_button.setDisabled(True) self.nugget_list.disable_input()
def __init__(self, table_view): """ A simple class that inherits QWidget and acts as a container for items added to QTableView cells """ super().__init__() self.table_view = table_view self.setAutoFillBackground(True) view_button = QPushButton(QIcon("icons/view.png"), None) view_button.clicked.connect(self.displayItemValues) delete_button = QPushButton(QIcon("icons/trash.png"), None) delete_button.clicked.connect(self.deleteTableRows) # Create the layout for the buttons cell_layout = QHBoxLayout() cell_layout.setContentsMargins(0, 0, 0, 0) cell_layout.setSpacing(0) cell_layout.addWidget(view_button) cell_layout.addWidget(delete_button) self.setLayout(cell_layout)
def __init__(self, *args): QFrame.__init__(self, *args) self.setFrameStyle(QFrame.Shape.StyledPanel | QFrame.Shadow.Sunken) self.edit = QTextEdit() self.edit.setFrameStyle(QFrame.Shape.NoFrame) self.edit.setAcceptRichText(False) self.edit.setLineWrapMode(QTextEdit.LineWrapMode.NoWrap) self.number_bar = self.NumberBar() self.number_bar.set_text_edit(self.edit) hbox = QHBoxLayout(self) hbox.setSpacing(0) hbox.setContentsMargins(10,0,0,0) hbox.addWidget(self.number_bar) hbox.addWidget(self.edit) self.edit.installEventFilter(self) self.edit.viewport().installEventFilter(self)
def initUI(self): self.setWindowTitle(self.title) self.setMinimumHeight(self.height) self.setMinimumWidth(self.width) bar = self.menuBar() about = bar.addMenu("About") config = bar.addMenu("Configuration") info = QAction("App Information", self) about.triggered[QAction].connect(self.aboutThis) about.addAction(info) configAction = QAction("System configuration", self) config.triggered[QAction].connect(self.configWindow) config.addAction(configAction) self.setGeometry(self.left, self.top, self.width, self.height) self.setCentralWidget(self.table_widget) widget = QWidget() container = QHBoxLayout() icon = self.style().standardIcon( QStyle.StandardPixmap.SP_MessageBoxInformation) self.iconLabel.setPixmap(icon.pixmap(QSize(16, 16))) self.restartProcessButton.clicked.connect(self.restartProcess) container.setSpacing(5) container.addWidget(self.iconLabel) container.addWidget(self.informationLabel) if self.classifyExercises is None: self.informationLabel.setText( 'Myo Connect is not running!Please start process.') container.addWidget(self.restartProcessButton) widget.setLayout(container) self.statusBar.addWidget(widget) self.setStatusBar(self.statusBar) self.show()
class DocumentBaseViewerWidget(MainWindowContent): def __init__(self, main_window): super(DocumentBaseViewerWidget, self).__init__(main_window, "Document Base") # controls self.controls = QWidget() self.controls_layout = QHBoxLayout(self.controls) self.controls_layout.setContentsMargins(0, 0, 0, 0) self.controls_layout.setSpacing(10) self.controls_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.layout.addWidget(self.controls) self.create_document_base_button = QPushButton( "Create a new Document Base") self.create_document_base_button.setFont(BUTTON_FONT) self.create_document_base_button.clicked.connect( self.main_window.show_document_base_creator_widget_task) self.controls_layout.addWidget(self.create_document_base_button) self.load_and_run_default_preprocessing_phase_button = QPushButton( "Preprocess the Document Base") self.load_and_run_default_preprocessing_phase_button.setFont( BUTTON_FONT) self.load_and_run_default_preprocessing_phase_button.clicked.connect( self.main_window.load_and_run_default_preprocessing_phase_task) self.controls_layout.addWidget( self.load_and_run_default_preprocessing_phase_button) self.load_and_run_default_matching_phase_button = QPushButton( "Match the Nuggets to the Attributes") self.load_and_run_default_matching_phase_button.setFont(BUTTON_FONT) self.load_and_run_default_matching_phase_button.clicked.connect( self.main_window.load_and_run_default_matching_phase_task) self.controls_layout.addWidget( self.load_and_run_default_matching_phase_button) self.save_table_button = QPushButton("Export the Table to CSV") self.save_table_button.setFont(BUTTON_FONT) self.save_table_button.clicked.connect( self.main_window.save_table_to_csv_task) self.controls_layout.addWidget(self.save_table_button) # documents self.documents = MainWindowContentSection(self, "Documents:") self.layout.addWidget(self.documents) self.num_documents = QLabel("number of documents: -") self.num_documents.setFont(LABEL_FONT) self.documents.layout.addWidget(self.num_documents) self.num_nuggets = QLabel("number of nuggets: -") self.num_nuggets.setFont(LABEL_FONT) self.documents.layout.addWidget(self.num_nuggets) # attributes self.attributes = MainWindowContentSection(self, "Attributes:") self.layout.addWidget(self.attributes) self.add_attribute_button = QPushButton("Add Attribute") self.add_attribute_button.setFont(BUTTON_FONT) self.add_attribute_button.clicked.connect( self.main_window.add_attribute_task) self.attributes_list = CustomScrollableList(self, AttributeWidget, self.add_attribute_button) self.attributes.layout.addWidget(self.attributes_list) def update_document_base(self, document_base): # update documents self.num_documents.setText( f"number of documents: {len(document_base.documents)}") self.num_nuggets.setText( f"number of nuggets: {len(document_base.nuggets)}") # update attributes self.attributes_list.update_item_list(document_base.attributes, document_base) def enable_input(self): self.create_document_base_button.setEnabled(True) if self.main_window.document_base is not None: self.load_and_run_default_preprocessing_phase_button.setEnabled( True) self.load_and_run_default_matching_phase_button.setEnabled(True) self.save_table_button.setEnabled(True) self.add_attribute_button.setEnabled(True) self.attributes_list.enable_input() def disable_input(self): self.create_document_base_button.setDisabled(True) self.load_and_run_default_preprocessing_phase_button.setDisabled(True) self.load_and_run_default_matching_phase_button.setDisabled(True) self.save_table_button.setDisabled(True) self.add_attribute_button.setDisabled(True) self.attributes_list.disable_input()
class DocumentBaseCreatorWidget(MainWindowContent): def __init__(self, main_window) -> None: super(DocumentBaseCreatorWidget, self).__init__(main_window, "Create Document Base") self.documents = MainWindowContentSection(self, "Documents:") self.layout.addWidget(self.documents) self.documents_explanation = QLabel( "Enter the path of the directory that contains the documents as .txt files." ) self.documents_explanation.setFont(LABEL_FONT) self.documents.layout.addWidget(self.documents_explanation) self.path_widget = QFrame() self.path_layout = QHBoxLayout(self.path_widget) self.path_layout.setContentsMargins(20, 0, 20, 0) self.path_layout.setSpacing(10) self.path_widget.setStyleSheet("background-color: white") self.path_widget.setFixedHeight(40) self.documents.layout.addWidget(self.path_widget) self.path = QLineEdit() self.path.setFont(CODE_FONT_BOLD) self.path.setStyleSheet("border: none") self.path_layout.addWidget(self.path) self.edit_path_button = QPushButton() self.edit_path_button.setIcon(QIcon("aset_ui/resources/folder.svg")) self.edit_path_button.setFlat(True) self.edit_path_button.clicked.connect(self._edit_path_button_clicked) self.path_layout.addWidget(self.edit_path_button) self.attributes = MainWindowContentSection(self, "Attributes:") self.layout.addWidget(self.attributes) self.labels_explanation = QLabel("Enter the attribute names.") self.labels_explanation.setFont(LABEL_FONT) self.attributes.layout.addWidget(self.labels_explanation) self.create_attribute_button = QPushButton("New Attribute") self.create_attribute_button.setFont(BUTTON_FONT) self.create_attribute_button.clicked.connect( self._create_attribute_button_clicked) self.attribute_names = [] self.attributes_list = CustomScrollableList( self, AttributeCreatorWidget, self.create_attribute_button) self.attributes.layout.addWidget(self.attributes_list) self.buttons_widget = QWidget() self.buttons_layout = QHBoxLayout(self.buttons_widget) self.buttons_layout.setContentsMargins(0, 0, 0, 0) self.buttons_layout.setSpacing(10) self.buttons_layout.setAlignment(Qt.AlignmentFlag.AlignRight) self.attributes.layout.addWidget(self.buttons_widget) self.cancel_button = QPushButton("Cancel") self.cancel_button.setFont(BUTTON_FONT) self.cancel_button.clicked.connect(self._cancel_button_clicked) self.buttons_layout.addWidget(self.cancel_button) self.create_document_base_button = QPushButton("Create Document Base") self.create_document_base_button.setFont(BUTTON_FONT) self.create_document_base_button.clicked.connect( self._create_document_base_button_clicked) self.buttons_layout.addWidget(self.create_document_base_button) def enable_input(self): self.edit_path_button.setEnabled(True) self.create_attribute_button.setEnabled(True) self.cancel_button.setEnabled(True) self.create_document_base_button.setEnabled(True) self.attributes_list.enable_input() def disable_input(self): self.edit_path_button.setDisabled(True) self.create_attribute_button.setDisabled(True) self.cancel_button.setDisabled(True) self.create_document_base_button.setDisabled(True) self.attributes_list.disable_input() def initialize_for_new_document_base(self): self.path.setText("") self.attribute_names = [] self.attributes_list.update_item_list([]) def delete_attribute(self, attribute_name): self.attribute_names = [] for attribute_widget in self.attributes_list.item_widgets[:self. attributes_list . num_visible_item_widgets]: self.attribute_names.append(attribute_widget.name.text()) self.attribute_names.remove(attribute_name) self.attributes_list.update_item_list(self.attribute_names) self.attributes_list.last_item_widget().name.setFocus() def _edit_path_button_clicked(self): path = str( QFileDialog.getExistingDirectory( self, "Choose a directory of text files.")) if path != "": path = f"{path}/*.txt" self.path.setText(path) def _create_attribute_button_clicked(self): self.attribute_names = [] for attribute_widget in self.attributes_list.item_widgets[:self. attributes_list . num_visible_item_widgets]: self.attribute_names.append(attribute_widget.name.text()) self.attribute_names.append("") self.attributes_list.update_item_list(self.attribute_names) self.attributes_list.last_item_widget().name.setFocus() def _cancel_button_clicked(self): self.main_window.show_start_menu_widget() self.main_window.enable_global_input() def _create_document_base_button_clicked(self): self.attribute_names = [] for attribute_widget in self.attributes_list.item_widgets[:self. attributes_list . num_visible_item_widgets]: self.attribute_names.append(attribute_widget.name.text()) self.main_window.create_document_base_task(self.path.text(), self.attribute_names)
class AttributeWidget(CustomScrollableListItem): def __init__(self, document_base_viewer): super(AttributeWidget, self).__init__(document_base_viewer) self.document_base_viewer = document_base_viewer self.attribute = None self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(40) self.attribute_name = QLabel() self.attribute_name.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.attribute_name, alignment=Qt.AlignmentFlag.AlignLeft) self.num_matched = QLabel("matches: -") self.num_matched.setFont(CODE_FONT) self.layout.addWidget(self.num_matched, alignment=Qt.AlignmentFlag.AlignLeft) self.buttons_widget = QWidget() self.buttons_layout = QHBoxLayout(self.buttons_widget) self.buttons_layout.setContentsMargins(0, 0, 0, 0) self.buttons_layout.setSpacing(10) self.layout.addWidget(self.buttons_widget, alignment=Qt.AlignmentFlag.AlignRight) self.forget_matches_button = QPushButton() self.forget_matches_button.setIcon(QIcon("aset_ui/resources/redo.svg")) self.forget_matches_button.setToolTip( "Forget matches for this attribute.") self.forget_matches_button.setFlat(True) self.forget_matches_button.clicked.connect( self._forget_matches_button_clicked) self.buttons_layout.addWidget(self.forget_matches_button) self.remove_button = QPushButton() self.remove_button.setIcon(QIcon("aset_ui/resources/trash.svg")) self.remove_button.setToolTip("Remove this attribute.") self.remove_button.setFlat(True) self.remove_button.clicked.connect(self._remove_button_clicked) self.buttons_layout.addWidget(self.remove_button) def update_item(self, item, params=None): self.attribute = item if len(params.attributes) == 0: max_attribute_name_len = 10 else: max_attribute_name_len = max( len(attribute.name) for attribute in params.attributes) self.attribute_name.setText(self.attribute.name + ( " " * (max_attribute_name_len - len(self.attribute.name)))) mappings_in_some_documents = False no_mappings_in_some_documents = False num_matches = 0 for document in params.documents: if self.attribute.name in document.attribute_mappings.keys(): mappings_in_some_documents = True if document.attribute_mappings[self.attribute.name] != []: num_matches += 1 else: no_mappings_in_some_documents = True if not mappings_in_some_documents and no_mappings_in_some_documents: self.num_matched.setText("not matched yet") elif mappings_in_some_documents and no_mappings_in_some_documents: self.num_matched.setText("only partly matched") else: self.num_matched.setText(f"matches: {num_matches}") def enable_input(self): self.forget_matches_button.setEnabled(True) self.remove_button.setEnabled(True) def disable_input(self): self.forget_matches_button.setDisabled(True) self.remove_button.setDisabled(True) def _forget_matches_button_clicked(self): self.document_base_viewer.main_window.forget_matches_for_attribute_with_given_name_task( self.attribute.name) def _remove_button_clicked(self): self.document_base_viewer.main_window.remove_attribute_with_given_name_task( self.attribute.name)
class ManageData(QMainWindow): populateGallery = pyqtSignal() closedWindow = pyqtSignal(object) def __init__(self): super(ManageData, self).__init__() self.setWindowTitle('Manage Data') self.configure_gui() self.create_widgets() self.create_menu() self.showMaximized() self.mysql = Authenticate() def configure_gui(self): self.center = QWidget(self) self.layout = QHBoxLayout() self.center.setLayout(self.layout) self.setCentralWidget(self.center) self.layout.setContentsMargins(5, 0, 5, 0) self.layout.setSpacing(0) def create_widgets(self): self.windows = set() self.gallery = Gallery(self) self.preview = Preview(self, 'white') self.layout.addWidget(self.gallery) self.layout.addWidget(self.preview) self.statusbar = QStatusBar(self) self.setStatusBar(self.statusbar) self.statusbar.setFixedHeight(25) def create_menu(self): pass def keyPressEvent(self, event): key_press = event.key() modifiers = event.modifiers() alt = modifiers == Qt.Modifier.ALT if alt: if key_press == Qt.Key_Left: self.ribbon.go_back() elif key_press == Qt.Key_Right: self.ribbon.go_forward() if key_press == Qt.Key.Key_F4: self.ribbon.tags.setFocus() elif key_press == Qt.Key.Key_F5: self.select_records() elif key_press == Qt.Key.Key_Delete: self.delete_records(self.gallery.selectedIndexes()) elif key_press == Qt.Key.Key_Escape: self.close() def closeEvent(self, event): self.mysql.close() for window in self.windows: window.close() self.closedWindow.emit(self)
class MainWindow(QMainWindow): ################################ # signals (aset ui --> aset api) ################################ create_document_base = pyqtSignal(str, list) add_attribute = pyqtSignal(str, ASETDocumentBase) remove_attribute = pyqtSignal(str, ASETDocumentBase) forget_matches_for_attribute = pyqtSignal(str, ASETDocumentBase) load_document_base_from_bson = pyqtSignal(str) save_document_base_to_bson = pyqtSignal(str, ASETDocumentBase) save_table_to_csv = pyqtSignal(str, ASETDocumentBase) forget_matches = pyqtSignal(ASETDocumentBase) load_preprocessing_phase_from_config = pyqtSignal(str) save_preprocessing_phase_to_config = pyqtSignal(str, PreprocessingPhase) load_matching_phase_from_config = pyqtSignal(str) save_matching_phase_to_config = pyqtSignal(str, BaseMatchingPhase) run_preprocessing_phase = pyqtSignal(ASETDocumentBase, PreprocessingPhase, Statistics) run_matching_phase = pyqtSignal(ASETDocumentBase, BaseMatchingPhase, Statistics) save_statistics_to_json = pyqtSignal(str, Statistics) load_and_run_default_preprocessing_phase = pyqtSignal(ASETDocumentBase, Statistics) load_and_run_default_matching_phase = pyqtSignal(ASETDocumentBase, Statistics) ############################## # slots (aset api --> aset ui) ############################## @pyqtSlot(str, float) def status(self, message, progress): logger.debug("Called slot 'status'.") self.status_widget_message.setText(message) if progress == -1: self.status_widget_progress.setRange(0, 0) else: self.status_widget_progress.setRange(0, 100) self.status_widget_progress.setValue(int(progress * 100)) @pyqtSlot(str) def finished(self, message): logger.debug("Called slot 'finished'.") self.status_widget_message.setText(message) self.status_widget_progress.setRange(0, 100) self.status_widget_progress.setValue(100) self.show_document_base_viewer_widget() self.enable_global_input() @pyqtSlot(str) def error(self, message): logger.debug("Called slot 'error'.") self.status_widget_message.setText(message) self.status_widget_progress.setRange(0, 100) self.status_widget_progress.setValue(0) self.show_document_base_viewer_widget() self.enable_global_input() @pyqtSlot(ASETDocumentBase) def document_base_to_ui(self, document_base): logger.debug("Called slot 'document_base_to_ui'.") self.document_base = document_base self.document_base_viewer_widget.update_document_base(self.document_base) self._was_enabled.append(self.save_document_base_to_bson_action) self._was_enabled.append(self.save_table_to_csv_action) self._was_enabled.append(self.add_attribute_action) self._was_enabled.append(self.remove_attribute_action) self._was_enabled.append(self.forget_matches_for_attribute_action) self._was_enabled.append(self.forget_matches_action) self._was_enabled.append(self.load_and_run_default_preprocessing_phase_action) self._was_enabled.append(self.load_and_run_default_matching_phase_action) if self.preprocessing_phase is not None: self._was_enabled.append(self.run_preprocessing_phase_action) if self.matching_phase is not None: self._was_enabled.append(self.run_matching_phase_action) @pyqtSlot(PreprocessingPhase) def preprocessing_phase_to_ui(self, preprocessing_phase): logger.debug("Called slot 'preprocessing_phase_to_ui'.") self.preprocessing_phase = preprocessing_phase self._was_enabled.append(self.save_preprocessing_phase_to_config_action) if self.document_base is not None: self._was_enabled.append(self.run_preprocessing_phase_action) @pyqtSlot(BaseMatchingPhase) def matching_phase_to_ui(self, matching_phase): logger.debug("Called slot 'matching_phase_to_ui'.") self.matching_phase = matching_phase self._was_enabled.append(self.save_matching_phase_to_config_action) if self.document_base is not None: self._was_enabled.append(self.run_preprocessing_phase_action) @pyqtSlot(Statistics) def statistics_to_ui(self, statistics): logger.debug("Called slot 'statistics_to_ui'.") self.statistics = statistics @pyqtSlot(dict) def feedback_request_to_ui(self, feedback_request): logger.debug("Called slot 'feedback_request_to_ui'.") self.interactive_matching_widget.handle_feedback_request(feedback_request) # noinspection PyUnresolvedReferences def _connect_slots_and_signals(self): self.create_document_base.connect(self.api.create_document_base) self.add_attribute.connect(self.api.add_attribute) self.remove_attribute.connect(self.api.remove_attribute) self.forget_matches_for_attribute.connect(self.api.forget_matches_for_attribute) self.load_document_base_from_bson.connect(self.api.load_document_base_from_bson) self.save_document_base_to_bson.connect(self.api.save_document_base_to_bson) self.save_table_to_csv.connect(self.api.save_table_to_csv) self.forget_matches.connect(self.api.forget_matches) self.load_preprocessing_phase_from_config.connect(self.api.load_preprocessing_phase_from_config) self.save_preprocessing_phase_to_config.connect(self.api.save_preprocessing_phase_to_config) self.load_matching_phase_from_config.connect(self.api.load_matching_phase_from_config) self.save_matching_phase_to_config.connect(self.api.save_matching_phase_to_config) self.run_preprocessing_phase.connect(self.api.run_preprocessing_phase) self.run_matching_phase.connect(self.api.run_matching_phase) self.save_statistics_to_json.connect(self.api.save_statistics_to_json) self.load_and_run_default_preprocessing_phase.connect(self.api.load_and_run_default_preprocessing_phase) self.load_and_run_default_matching_phase.connect(self.api.load_and_run_default_matching_phase) self.api.status.connect(self.status) self.api.finished.connect(self.finished) self.api.error.connect(self.error) self.api.document_base_to_ui.connect(self.document_base_to_ui) self.api.preprocessing_phase_to_ui.connect(self.preprocessing_phase_to_ui) self.api.matching_phase_to_ui.connect(self.matching_phase_to_ui) self.api.statistics_to_ui.connect(self.statistics_to_ui) self.api.feedback_request_to_ui.connect(self.feedback_request_to_ui) ####### # tasks ####### def load_document_base_from_bson_task(self): logger.info("Execute task 'load_document_base_from_bson_task'.") path, ok = QFileDialog.getOpenFileName(self, "Choose a document collection .bson file!") if ok: self.disable_global_input() # noinspection PyUnresolvedReferences self.load_document_base_from_bson.emit(str(path)) def save_document_base_to_bson_task(self): logger.info("Execute task 'save_document_base_to_bson_task'.") if self.document_base is not None: path, ok = QFileDialog.getSaveFileName(self, "Choose where to save the document collection .bson file!") if ok: self.disable_global_input() # noinspection PyUnresolvedReferences self.save_document_base_to_bson.emit(str(path), self.document_base) def add_attribute_task(self): logger.info("Execute task 'add_attribute_task'.") if self.document_base is not None: name, ok = QInputDialog.getText(self, "Create Attribute", "Attribute name:") if ok: self.disable_global_input() # noinspection PyUnresolvedReferences self.add_attribute.emit(str(name), self.document_base) def remove_attribute_task(self): logger.info("Execute task 'remove_attribute_task'.") if self.document_base is not None: name, ok = QInputDialog.getText(self, "Remove Attribute", "Attribute name:") if ok: self.disable_global_input() # noinspection PyUnresolvedReferences self.remove_attribute.emit(str(name), self.document_base) def remove_attribute_with_given_name_task(self, attribute_name): logger.info("Execute task 'remove_attribute_with_given_name_task'.") if self.document_base is not None: self.disable_global_input() # noinspection PyUnresolvedReferences self.remove_attribute.emit(str(attribute_name), self.document_base) def forget_matches_for_attribute_task(self): logger.info("Execute task 'forget_matches_for_attribute_task'.") if self.document_base is not None: name, ok = QInputDialog.getText(self, "Forget Matches for Attribute", "Attribute name:") if ok: self.disable_global_input() # noinspection PyUnresolvedReferences self.forget_matches_for_attribute.emit(str(name), self.document_base) def forget_matches_for_attribute_with_given_name_task(self, attribute_name): logger.info("Execute task 'forget_matches_for_attribute_with_given_name_task'.") if self.document_base is not None: self.disable_global_input() # noinspection PyUnresolvedReferences self.forget_matches_for_attribute.emit(attribute_name, self.document_base) def forget_matches_task(self): logger.info("Execute task 'forget_matches_task'.") if self.document_base is not None: self.disable_global_input() # noinspection PyUnresolvedReferences self.forget_matches.emit(self.document_base) def enable_collect_statistics_task(self): logger.info("Execute task 'task_enable_collect_statistics'.") self.collect_statistics = True self.enable_collect_statistics_action.setEnabled(False) self.disable_collect_statistics_action.setEnabled(True) def disable_collect_statistics_task(self): logger.info("Execute task 'disable_collect_statistics_task'.") self.collect_statistics = False self.disable_collect_statistics_action.setEnabled(False) self.enable_collect_statistics_action.setEnabled(True) def save_statistics_to_json_task(self): logger.info("Execute task 'save_statistics_to_json_task'.") if self.statistics is not None: path = str(QFileDialog.getSaveFileName(self, "Choose where to save the statistics .json file!")[0]) if path != "": self.disable_global_input() # noinspection PyUnresolvedReferences self.save_statistics_to_json.emit(path, self.statistics) def show_document_base_creator_widget_task(self): logger.info("Execute task 'show_document_base_creator_widget_task'.") self.disable_global_input() self.document_base_creator_widget.enable_input() self.document_base_creator_widget.initialize_for_new_document_base() self.show_document_base_creator_widget() self.document_base_creator_widget.path.setFocus() def create_document_base_task(self, path, attribute_names): logger.info("Execute task 'create_document_base_task'.") self.disable_global_input() # noinspection PyUnresolvedReferences self.create_document_base.emit(path, attribute_names) def save_table_to_csv_task(self): logger.info("Execute task 'save_table_to_csv_task'.") if self.document_base is not None: path = str(QFileDialog.getSaveFileName(self, "Choose where to save the table .csv file!")[0]) if path != "": self.disable_global_input() # noinspection PyUnresolvedReferences self.save_table_to_csv.emit(path, self.document_base) def load_preprocessing_phase_from_config_task(self): logger.info("Execute task 'load_preprocessing_phase_from_config_task'.") path = str(QFileDialog.getOpenFileName(self, "Choose a configuration .json file!")[0]) if path != "": self.disable_global_input() # noinspection PyUnresolvedReferences self.load_preprocessing_phase_from_config.emit(path) def save_preprocessing_phase_to_config_task(self): logger.info("Execute task 'save_preprocessing_phase_to_config_task'.") if self.preprocessing_phase is not None: path = str(QFileDialog.getSaveFileName(self, "Choose where to save the configuration .json file!")[0]) if path != "": self.disable_global_input() # noinspection PyUnresolvedReferences self.save_preprocessing_phase_to_config.emit(path, self.preprocessing_phase) def load_matching_phase_from_config_task(self): logger.info("Execute task 'load_matching_phase_from_config_task'.") path = str(QFileDialog.getOpenFileName(self, "Choose a configuration .json file!")[0]) if path != "": self.disable_global_input() # noinspection PyUnresolvedReferences self.load_matching_phase_from_config.emit(path) def save_matching_phase_to_config_task(self): logger.info("Execute task 'save_matching_phase_to_config_task'.") if self.matching_phase is not None: path = str(QFileDialog.getSaveFileName(self, "Choose where to save the configuration .json file!")[0]) if path != "": self.disable_global_input() # noinspection PyUnresolvedReferences self.save_matching_phase_to_config.emit(path, self.matching_phase) def run_preprocessing_phase_task(self): logger.info("Execute task 'run_preprocessing_phase_task'.") if self.document_base is not None and self.preprocessing_phase is not None: self.statistics = Statistics(self.collect_statistics) self.save_statistics_to_json_action.setEnabled(self.collect_statistics) self.disable_global_input() # noinspection PyUnresolvedReferences self.run_preprocessing_phase.emit(self.document_base, self.preprocessing_phase, self.statistics) def run_matching_phase_task(self): logger.info("Execute task 'run_matching_phase_task'.") if self.document_base is not None and self.matching_phase is not None: self.statistics = Statistics(self.collect_statistics) self.save_statistics_to_json_action.setEnabled(self.collect_statistics) self.disable_global_input() self.interactive_matching_widget.enable_input() self.show_interactive_matching_widget() # noinspection PyUnresolvedReferences self.run_matching_phase.emit(self.document_base, self.matching_phase, self.statistics) def give_feedback_task(self, feedback): logger.info("Execute task 'give_feedback_task'.") self.api.feedback = feedback self.feedback_cond.wakeAll() def load_and_run_default_preprocessing_phase_task(self): logger.info("Execute task 'load_and_run_default_preprocessing_phase_task'.") if self.document_base is not None: self.statistics = Statistics(self.collect_statistics) self.save_statistics_to_json_action.setEnabled(self.collect_statistics) self.disable_global_input() # noinspection PyUnresolvedReferences self.load_and_run_default_preprocessing_phase.emit(self.document_base, self.statistics) def load_and_run_default_matching_phase_task(self): logger.info("Execute task 'load_and_run_default_matching_phase_task'.") if self.document_base is not None: self.statistics = Statistics(self.collect_statistics) self.save_statistics_to_json_action.setEnabled(self.collect_statistics) self.disable_global_input() self.interactive_matching_widget.enable_input() self.show_interactive_matching_widget() # noinspection PyUnresolvedReferences self.load_and_run_default_matching_phase.emit(self.document_base, self.statistics) ################## # controller logic ################## def enable_global_input(self): for action in self._was_enabled: action.setEnabled(True) self.document_base_creator_widget.enable_input() self.document_base_viewer_widget.enable_input() self.interactive_matching_widget.enable_input() self._was_enabled = [] def disable_global_input(self): for action in self._all_actions: if action.isEnabled(): self._was_enabled.append(action) action.setEnabled(False) self.document_base_creator_widget.disable_input() self.document_base_viewer_widget.disable_input() self.interactive_matching_widget.disable_input() def show_document_base_viewer_widget(self): if self.document_base_viewer_widget.isHidden(): self.central_widget_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.start_menu_widget.hide() self.interactive_matching_widget.hide() self.document_base_creator_widget.hide() self.central_widget_layout.removeWidget(self.start_menu_widget) self.central_widget_layout.removeWidget(self.interactive_matching_widget) self.central_widget_layout.removeWidget(self.document_base_creator_widget) self.central_widget_layout.addWidget(self.document_base_viewer_widget) self.document_base_viewer_widget.show() self.central_widget_layout.update() def show_interactive_matching_widget(self): if self.interactive_matching_widget.isHidden(): self.central_widget_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.start_menu_widget.hide() self.document_base_viewer_widget.hide() self.document_base_creator_widget.hide() self.central_widget_layout.removeWidget(self.start_menu_widget) self.central_widget_layout.removeWidget(self.document_base_viewer_widget) self.central_widget_layout.removeWidget(self.document_base_creator_widget) self.central_widget_layout.addWidget(self.interactive_matching_widget) self.interactive_matching_widget.show() self.central_widget_layout.update() def show_document_base_creator_widget(self): if self.document_base_creator_widget.isHidden(): self.central_widget_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.start_menu_widget.hide() self.document_base_viewer_widget.hide() self.interactive_matching_widget.hide() self.central_widget_layout.removeWidget(self.start_menu_widget) self.central_widget_layout.removeWidget(self.document_base_viewer_widget) self.central_widget_layout.removeWidget(self.interactive_matching_widget) self.central_widget_layout.addWidget(self.document_base_creator_widget) self.document_base_creator_widget.show() self.central_widget_layout.update() def show_start_menu_widget(self): if self.start_menu_widget.isHidden(): self.central_widget_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) self.document_base_viewer_widget.hide() self.document_base_creator_widget.hide() self.interactive_matching_widget.hide() self.central_widget_layout.removeWidget(self.document_base_viewer_widget) self.central_widget_layout.removeWidget(self.document_base_creator_widget) self.central_widget_layout.removeWidget(self.interactive_matching_widget) self.central_widget_layout.addWidget(self.start_menu_widget) self.start_menu_widget.show() self.central_widget_layout.update() def __init__(self) -> None: super(MainWindow, self).__init__() self.setWindowTitle("ASET") self.document_base = None self.preprocessing_phase = None self.matching_phase = None self.statistics = Statistics(True) self.collect_statistics = True # set up the api_thread and api and connect slots and signals self.feedback_mutex = QMutex() self.feedback_cond = QWaitCondition() self.api = ASETAPI(self.feedback_mutex, self.feedback_cond) self.api_thread = QThread() self.api.moveToThread(self.api_thread) self._connect_slots_and_signals() self.api_thread.start() # set up the status bar self.status_bar = self.statusBar() self.status_bar.setFont(STATUS_BAR_FONT) self.status_widget = QWidget(self.status_bar) self.status_widget_layout = QHBoxLayout(self.status_widget) self.status_widget_layout.setContentsMargins(0, 0, 0, 0) self.status_widget_message = QLabel() self.status_widget_message.setFont(STATUS_BAR_FONT) self.status_widget_message.setMinimumWidth(10) self.status_widget_layout.addWidget(self.status_widget_message) self.status_widget_progress = QProgressBar() self.status_widget_progress.setMinimumWidth(10) self.status_widget_progress.setMaximumWidth(200) self.status_widget_progress.setTextVisible(False) self.status_widget_progress.setMaximumHeight(20) self.status_widget_layout.addWidget(self.status_widget_progress) self.status_bar.addPermanentWidget(self.status_widget) # set up the actions self._all_actions = [] self._was_enabled = [] self.exit_action = QAction("&Exit", self) self.exit_action.setIcon(QIcon("aset_ui/resources/leave.svg")) self.exit_action.setStatusTip("Exit the application.") self.exit_action.triggered.connect(QApplication.instance().quit) self._all_actions.append(self.exit_action) self.show_document_base_creator_widget_action = QAction("&Create new document base", self) self.show_document_base_creator_widget_action.setIcon(QIcon("aset_ui/resources/two_documents.svg")) self.show_document_base_creator_widget_action.setStatusTip( "Create a new document base from a collection of .txt files and a list of attribute names." ) self.show_document_base_creator_widget_action.triggered.connect(self.show_document_base_creator_widget_task) self._all_actions.append(self.show_document_base_creator_widget_action) self.add_attribute_action = QAction("&Add attribute", self) self.add_attribute_action.setIcon(QIcon("aset_ui/resources/plus.svg")) self.add_attribute_action.setStatusTip("Add a new attribute to the document base.") self.add_attribute_action.triggered.connect(self.add_attribute_task) self.add_attribute_action.setEnabled(False) self._all_actions.append(self.add_attribute_action) self.remove_attribute_action = QAction("&Remove attribute", self) self.remove_attribute_action.setIcon(QIcon("aset_ui/resources/trash.svg")) self.remove_attribute_action.setStatusTip("Remove an attribute from the document base.") self.remove_attribute_action.triggered.connect(self.remove_attribute_task) self.remove_attribute_action.setEnabled(False) self._all_actions.append(self.remove_attribute_action) self.forget_matches_for_attribute_action = QAction("&Forget matches for attribute", self) self.forget_matches_for_attribute_action.setIcon(QIcon("aset_ui/resources/redo.svg")) self.forget_matches_for_attribute_action.setStatusTip("Forget the matches for a single attribute.") self.forget_matches_for_attribute_action.triggered.connect(self.forget_matches_for_attribute_task) self.forget_matches_for_attribute_action.setEnabled(False) self._all_actions.append(self.forget_matches_for_attribute_action) self.load_document_base_from_bson_action = QAction("&Load document base", self) self.load_document_base_from_bson_action.setIcon(QIcon("aset_ui/resources/folder.svg")) self.load_document_base_from_bson_action.setStatusTip("Load an existing document base from a .bson file.") self.load_document_base_from_bson_action.triggered.connect(self.load_document_base_from_bson_task) self._all_actions.append(self.load_document_base_from_bson_action) self.save_document_base_to_bson_action = QAction("&Save document base", self) self.save_document_base_to_bson_action.setIcon(QIcon("aset_ui/resources/save.svg")) self.save_document_base_to_bson_action.setStatusTip("Save the document base in a .bson file.") self.save_document_base_to_bson_action.triggered.connect(self.save_document_base_to_bson_task) self.save_document_base_to_bson_action.setEnabled(False) self._all_actions.append(self.save_document_base_to_bson_action) self.save_table_to_csv_action = QAction("&Export table to CSV", self) self.save_table_to_csv_action.setIcon(QIcon("aset_ui/resources/table.svg")) self.save_table_to_csv_action.setStatusTip("Save the table to a .csv file.") self.save_table_to_csv_action.triggered.connect(self.save_table_to_csv_task) self.save_table_to_csv_action.setEnabled(False) self._all_actions.append(self.save_table_to_csv_action) self.forget_matches_action = QAction("&Forget all matches", self) self.forget_matches_action.setIcon(QIcon("aset_ui/resources/redo.svg")) self.forget_matches_action.setStatusTip("Forget the matches for all attributes.") self.forget_matches_action.triggered.connect(self.forget_matches_task) self.forget_matches_action.setEnabled(False) self._all_actions.append(self.forget_matches_action) self.load_and_run_default_preprocessing_phase_action = QAction( "&Load and run default preprocessing phase", self ) self.load_and_run_default_preprocessing_phase_action.setStatusTip( "Load the default preprocessing phase and run it on the document collection." ) self.load_and_run_default_preprocessing_phase_action.setIcon(QIcon("aset_ui/resources/run_run.svg")) self.load_and_run_default_preprocessing_phase_action.setDisabled(True) self.load_and_run_default_preprocessing_phase_action.triggered.connect( self.load_and_run_default_preprocessing_phase_task ) self._all_actions.append(self.load_and_run_default_preprocessing_phase_action) self.load_preprocessing_phase_from_config_action = QAction("&Load preprocessing phase", self) self.load_preprocessing_phase_from_config_action.setStatusTip( "Load a preprocessing phase from a .json configuration file." ) self.load_preprocessing_phase_from_config_action.triggered.connect( self.load_preprocessing_phase_from_config_task ) self._all_actions.append(self.load_preprocessing_phase_from_config_action) self.save_preprocessing_phase_to_config_action = QAction("&Save preprocessing phase", self) self.save_preprocessing_phase_to_config_action.setStatusTip( "Save the preprocessing phase in a .json configuration file." ) self.save_preprocessing_phase_to_config_action.triggered.connect(self.save_preprocessing_phase_to_config_task) self.save_preprocessing_phase_to_config_action.setEnabled(False) self._all_actions.append(self.save_preprocessing_phase_to_config_action) self.run_preprocessing_phase_action = QAction("Run preprocessing phase", self) self.run_preprocessing_phase_action.setIcon(QIcon("aset_ui/resources/run.svg")) self.run_preprocessing_phase_action.setStatusTip("Run the preprocessing phase on the document collection.") self.run_preprocessing_phase_action.triggered.connect(self.run_preprocessing_phase_task) self.run_preprocessing_phase_action.setEnabled(False) self._all_actions.append(self.run_preprocessing_phase_action) self.load_and_run_default_matching_phase_action = QAction( "&Load and run default matching phase", self ) self.load_and_run_default_matching_phase_action.setStatusTip( "Load the default matching phase and run it on the document collection." ) self.load_and_run_default_matching_phase_action.setIcon(QIcon("aset_ui/resources/run_run.svg")) self.load_and_run_default_matching_phase_action.setDisabled(True) self.load_and_run_default_matching_phase_action.triggered.connect( self.load_and_run_default_preprocessing_phase_task ) self._all_actions.append(self.load_and_run_default_matching_phase_action) self.load_matching_phase_from_config_action = QAction("&Load matching phase", self) self.load_matching_phase_from_config_action.setStatusTip( "Load a matching phase from a .json configuration file." ) self.load_matching_phase_from_config_action.triggered.connect(self.load_matching_phase_from_config_task) self._all_actions.append(self.load_matching_phase_from_config_action) self.save_matching_phase_to_config_action = QAction("&Save matching phase", self) self.save_matching_phase_to_config_action.setStatusTip("Save the matching phase in a .json configuration file.") self.save_matching_phase_to_config_action.triggered.connect(self.save_matching_phase_to_config_task) self.save_matching_phase_to_config_action.setEnabled(False) self._all_actions.append(self.save_matching_phase_to_config_action) self.run_matching_phase_action = QAction("Run matching phase", self) self.run_matching_phase_action.setIcon(QIcon("aset_ui/resources/run.svg")) self.run_matching_phase_action.setStatusTip("Run the matching phase on the document collection.") self.run_matching_phase_action.triggered.connect(self.run_matching_phase_task) self.run_matching_phase_action.setEnabled(False) self._all_actions.append(self.run_matching_phase_action) self.enable_collect_statistics_action = QAction("&Enable statistics", self) self.enable_collect_statistics_action.setIcon(QIcon("aset_ui/resources/statistics.svg")) self.enable_collect_statistics_action.setStatusTip("Enable collecting statistics.") self.enable_collect_statistics_action.triggered.connect(self.enable_collect_statistics_task) self.enable_collect_statistics_action.setEnabled(False) self._all_actions.append(self.enable_collect_statistics_action) self.disable_collect_statistics_action = QAction("&Disable statistics", self) self.disable_collect_statistics_action.setIcon(QIcon("aset_ui/resources/statistics_incorrect.svg")) self.disable_collect_statistics_action.setStatusTip("Disable collecting statistics.") self.disable_collect_statistics_action.triggered.connect(self.disable_collect_statistics_task) self._all_actions.append(self.disable_collect_statistics_action) self.save_statistics_to_json_action = QAction("&Save statistics", self) self.save_statistics_to_json_action.setIcon(QIcon("aset_ui/resources/statistics_save.svg")) self.save_statistics_to_json_action.setStatusTip("Save the statistics to a .json file.") self.save_statistics_to_json_action.triggered.connect(self.save_statistics_to_json_task) self.save_statistics_to_json_action.setEnabled(False) self._all_actions.append(self.save_statistics_to_json_action) # set up the menu bar self.menubar = self.menuBar() self.menubar.setFont(MENU_FONT) self.file_menu = self.menubar.addMenu("&File") self.file_menu.setFont(MENU_FONT) self.file_menu.addAction(self.exit_action) self.document_base_menu = self.menubar.addMenu("&Document Base") self.document_base_menu.setFont(MENU_FONT) self.document_base_menu.addAction(self.show_document_base_creator_widget_action) self.document_base_menu.addSeparator() self.document_base_menu.addAction(self.load_document_base_from_bson_action) self.document_base_menu.addAction(self.save_document_base_to_bson_action) self.document_base_menu.addSeparator() self.document_base_menu.addAction(self.save_table_to_csv_action) self.document_base_menu.addSeparator() self.document_base_menu.addAction(self.add_attribute_action) self.document_base_menu.addAction(self.remove_attribute_action) self.document_base_menu.addSeparator() self.document_base_menu.addAction(self.forget_matches_for_attribute_action) self.document_base_menu.addAction(self.forget_matches_action) self.preprocessing_menu = self.menubar.addMenu("&Preprocessing") self.preprocessing_menu.setFont(MENU_FONT) self.preprocessing_menu.addAction(self.load_and_run_default_preprocessing_phase_action) self.preprocessing_menu.addSeparator() self.preprocessing_menu.addAction(self.load_preprocessing_phase_from_config_action) self.preprocessing_menu.addAction(self.save_preprocessing_phase_to_config_action) self.preprocessing_menu.addSeparator() self.preprocessing_menu.addAction(self.run_preprocessing_phase_action) self.matching_menu = self.menubar.addMenu("&Matching") self.matching_menu.setFont(MENU_FONT) self.matching_menu.addAction(self.load_and_run_default_matching_phase_action) self.matching_menu.addSeparator() self.matching_menu.addAction(self.load_matching_phase_from_config_action) self.matching_menu.addAction(self.save_matching_phase_to_config_action) self.matching_menu.addSeparator() self.matching_menu.addAction(self.run_matching_phase_action) self.statistics_menu = self.menubar.addMenu("&Statistics") self.statistics_menu.setFont(MENU_FONT) self.statistics_menu.addAction(self.enable_collect_statistics_action) self.statistics_menu.addAction(self.disable_collect_statistics_action) self.statistics_menu.addSeparator() self.statistics_menu.addAction(self.save_statistics_to_json_action) # start menu self.start_menu_widget = QWidget() self.start_menu_layout = QVBoxLayout(self.start_menu_widget) self.start_menu_layout.setContentsMargins(0, 0, 0, 0) self.start_menu_layout.setSpacing(30) self.start_menu_layout.setAlignment(Qt.AlignmentFlag.AlignTop) self.start_menu_widget.setMaximumWidth(400) self.start_menu_header = QLabel("Welcome to ASET!") self.start_menu_header.setFont(HEADER_FONT) self.start_menu_layout.addWidget(self.start_menu_header) self.start_menu_create_new_document_base_widget = QWidget() self.start_menu_create_new_document_base_layout = QVBoxLayout(self.start_menu_create_new_document_base_widget) self.start_menu_create_new_document_base_layout.setContentsMargins(0, 0, 0, 0) self.start_menu_create_new_document_base_layout.setSpacing(10) self.start_menu_layout.addWidget(self.start_menu_create_new_document_base_widget) self.start_menu_create_new_document_base_subheader = QLabel("Create a new document base.") self.start_menu_create_new_document_base_subheader.setFont(SUBHEADER_FONT) self.start_menu_create_new_document_base_layout.addWidget(self.start_menu_create_new_document_base_subheader) self.start_menu_create_new_document_base_wrapper_widget = QWidget() self.start_menu_create_new_document_base_wrapper_layout = QHBoxLayout( self.start_menu_create_new_document_base_wrapper_widget) self.start_menu_create_new_document_base_wrapper_layout.setContentsMargins(0, 0, 0, 0) self.start_menu_create_new_document_base_wrapper_layout.setSpacing(20) self.start_menu_create_new_document_base_wrapper_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.start_menu_create_new_document_base_layout.addWidget( self.start_menu_create_new_document_base_wrapper_widget) self.start_menu_create_document_base_button = QPushButton() self.start_menu_create_document_base_button.setFixedHeight(45) self.start_menu_create_document_base_button.setFixedWidth(45) self.start_menu_create_document_base_button.setIcon(QIcon("aset_ui/resources/two_documents.svg")) self.start_menu_create_document_base_button.clicked.connect(self.show_document_base_creator_widget_task) self.start_menu_create_new_document_base_wrapper_layout.addWidget(self.start_menu_create_document_base_button) self.start_menu_create_document_base_label = QLabel( "Create a new document base from a directory\nof .txt files and a list of attribute names.") self.start_menu_create_document_base_label.setFont(LABEL_FONT) self.start_menu_create_new_document_base_wrapper_layout.addWidget(self.start_menu_create_document_base_label) self.start_menu_load_document_base_widget = QWidget() self.start_menu_load_document_base_layout = QVBoxLayout(self.start_menu_load_document_base_widget) self.start_menu_load_document_base_layout.setContentsMargins(0, 0, 0, 0) self.start_menu_load_document_base_layout.setSpacing(10) self.start_menu_layout.addWidget(self.start_menu_load_document_base_widget) self.start_menu_load_document_base_subheader = QLabel("Load an existing document base.") self.start_menu_load_document_base_subheader.setFont(SUBHEADER_FONT) self.start_menu_load_document_base_layout.addWidget(self.start_menu_load_document_base_subheader) self.start_menu_load_document_base_wrapper_widget = QWidget() self.start_menu_load_document_base_wrapper_layout = QHBoxLayout( self.start_menu_load_document_base_wrapper_widget) self.start_menu_load_document_base_wrapper_layout.setContentsMargins(0, 0, 0, 0) self.start_menu_load_document_base_wrapper_layout.setSpacing(20) self.start_menu_load_document_base_wrapper_layout.setAlignment(Qt.AlignmentFlag.AlignLeft) self.start_menu_load_document_base_layout.addWidget(self.start_menu_load_document_base_wrapper_widget) self.start_menu_load_document_base_button = QPushButton() self.start_menu_load_document_base_button.setFixedHeight(45) self.start_menu_load_document_base_button.setFixedWidth(45) self.start_menu_load_document_base_button.setIcon(QIcon("aset_ui/resources/folder.svg")) self.start_menu_load_document_base_button.clicked.connect(self.load_document_base_from_bson_task) self.start_menu_load_document_base_wrapper_layout.addWidget(self.start_menu_load_document_base_button) self.start_menu_load_document_base_label = QLabel("Load an existing document base\nfrom a .bson file.") self.start_menu_load_document_base_label.setFont(LABEL_FONT) self.start_menu_load_document_base_wrapper_layout.addWidget(self.start_menu_load_document_base_label) # main UI self.central_widget = QWidget(self) self.central_widget_layout = QHBoxLayout(self.central_widget) self.central_widget_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) self.setCentralWidget(self.central_widget) self.document_base_creator_widget = DocumentBaseCreatorWidget(self) self.document_base_viewer_widget = DocumentBaseViewerWidget(self) self.interactive_matching_widget = InteractiveMatchingWidget(self) self.document_base_creator_widget.hide() self.document_base_viewer_widget.hide() self.interactive_matching_widget.hide() self.central_widget_layout.addWidget(self.start_menu_widget) self.central_widget_layout.update() self.resize(1400, 800) self.show() logger.info("Initialized MainWindow.")
class NuggetListItemWidget(CustomScrollableListItem): def __init__(self, nugget_list_widget): super(NuggetListItemWidget, self).__init__(nugget_list_widget) self.nugget_list_widget = nugget_list_widget self.nugget = None self.setFixedHeight(40) self.setStyleSheet("background-color: white") self.layout = QHBoxLayout(self) self.layout.setContentsMargins(20, 0, 20, 0) self.layout.setSpacing(10) self.info_label = QLabel() self.info_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.info_label) self.left_split_label = QLabel("|") self.left_split_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.left_split_label) self.text_edit = QTextEdit() self.text_edit.setReadOnly(True) self.text_edit.setFrameStyle(0) self.text_edit.setFont(CODE_FONT) self.text_edit.setLineWrapMode(QTextEdit.LineWrapMode.FixedPixelWidth) self.text_edit.setLineWrapColumnOrWidth(10000) self.text_edit.setHorizontalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setVerticalScrollBarPolicy( Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.text_edit.setFixedHeight(30) self.text_edit.setText("") self.layout.addWidget(self.text_edit) self.right_split_label = QLabel("|") self.right_split_label.setFont(CODE_FONT_BOLD) self.layout.addWidget(self.right_split_label) self.match_button = QPushButton() self.match_button.setIcon(QIcon("aset_ui/resources/correct.svg")) self.match_button.setFlat(True) self.match_button.clicked.connect(self._match_button_clicked) self.layout.addWidget(self.match_button) self.fix_button = QPushButton() self.fix_button.setIcon(QIcon("aset_ui/resources/incorrect.svg")) self.fix_button.setFlat(True) self.fix_button.clicked.connect(self._fix_button_clicked) self.layout.addWidget(self.fix_button) def update_item(self, item, params=None): self.nugget = item sentence = self.nugget[CachedContextSentenceSignal]["text"] start_char = self.nugget[CachedContextSentenceSignal]["start_char"] end_char = self.nugget[CachedContextSentenceSignal]["end_char"] self.text_edit.setText("") formatted_text = ( f"{' ' * (params - start_char)}{sentence[:start_char]}" f"<span style='background-color: #FFFF00'><b>{sentence[start_char:end_char]}</b></span>" f"{sentence[end_char:]}{' ' * 50}") self.text_edit.textCursor().insertHtml(formatted_text) scroll_cursor = QTextCursor(self.text_edit.document()) scroll_cursor.setPosition(params + 50) self.text_edit.setTextCursor(scroll_cursor) self.text_edit.ensureCursorVisible() self.info_label.setText( f"{str(round(self.nugget[CachedDistanceSignal], 2)).ljust(4)}") def _match_button_clicked(self): self.nugget_list_widget.interactive_matching_widget.main_window.give_feedback_task( { "message": "is-match", "nugget": self.nugget }) def _fix_button_clicked(self): self.nugget_list_widget.interactive_matching_widget.get_document_feedback( self.nugget) def enable_input(self): self.match_button.setEnabled(True) self.fix_button.setEnabled(True) def disable_input(self): self.match_button.setDisabled(True) self.fix_button.setDisabled(True)
class MainWindow(QWidget): def reload(self): if len(self.file_list): return self.webEngineView.reload() def on_downloadRequestState(self,event): if not len(self.file_list): self.label.setText('Статус:...') self.reload() try: for b in self.buttons: b.setEnabled(True) except: pass return if event.value>1: if event.value==2: if len(self.file_list[-1])>5: h0=self.file_list[-1][6] h1=gethash(self.file_list[-1][-1]) if h0==h1: try: tm=mktime(datetime.strptime(self.file_list[-1][4],"%d.%m.%Y %H:%M").timetuple()) tm+=(int(self.file_list[-1][5])%60) utime(self.file_list[-1][-1],(tm,tm)) except: pass else: print('Error. ("%s") - %i'%(self.file_list[-1][-1],event.value)) self.label.setText('Статус: ошибка скачивания файла ("'+self.file_list[-1][-1]+'")') self.file_list.pop() self.download_files() else: print('Error. ("%s") - %i'%(self.file_list[-1][-1],event.value)) self.label.setText('Статус: ошибка скачивания файла ("%s", код ошибки: %i)!'%(self.file_list[-1][-1],event.value)) self.reload() try: for b in self.buttons: b.setEnabled(True) except: pass def check_canceled(self,event): if event: self.canceled_callback() def on_downloadRequested(self,event): self.webEngineView.page().runJavaScript(\ """ function check_canceled(){ if(window.process_canceled) return 1; else return 0; }; check_canceled(); """,self.check_canceled) if event.state().value==0: event.stateChanged.connect(lambda event: self.on_downloadRequestState(event)) event.accept() def download_files(self): if len(self.file_list): if not self.file_number: self.file_number=len(self.file_list) tmp=self.file_list[-1] if exists(tmp[-1]): try: remove(tmp[-1]) except: pass self.webEngineView.page().profile().downloadRequested.connect(lambda event:\ self.on_downloadRequested(event)) self.label.setText('Статус: скачивание файла "%s" ( %i из %i )'%\ (tmp[2],self.file_number-len(self.file_list)+1,self.file_number)) self.webEngineView.page().download(QUrl(tmp[3]),tmp[-1]) else: self.webEngineView.page().profile().downloadRequested.disconnect() self.label.setText('Статус:...') self.reload() try: for b in self.buttons: b.setEnabled(True) except: pass def canceled_callback(self): self.label.setText('Статус: операция отменена!') self.file_list=[] self.file_number=0 self.reload() try: for b in self.buttons: b.setEnabled(True) except: pass def getfilelist_error_callback(self): self.label.setText('Статус: ошибка при получении списка файлов для скачивания!') self.file_list=[] self.file_number=0 self.reload() try: for b in self.buttons: b.setEnabled(True) except: pass def getfilelist_success_callback(self,links): for link in links: path=join(self.basepath,link[0],link[1]) try: makedirs(path,exist_ok=True) except: return link.append(realpath(join(path,link[2]))) self.file_list.append(link) if len(self.file_list): self.download_files() def progress_callback(self,counter): self.label.setText('Статус: получение списка файлов для скачивания (получено %d ссылок)...'%counter) def getfileslist(self): if len(self.file_list): return self.url=self.webEngineView.url().toString() if not('Files/prt0' in self.url): return self.file_list=[] self.file_number=0 try: for b in self.buttons: b.setEnabled(False) except: pass self.label.setText('Статус: получение списка файлов для скачивания...') self.webEngineView.page().runJavaScript(\ self.qwebchannel_js+self.busy+""" function get_sig_links_details_parse(file_links,details_links,partitions,partition,section,data){ if(window.process_canceled){ cancel(); return; }; var parser=new DOMParser(); var doc=parser.parseFromString(data,'text/html'); var req=doc.querySelectorAll('a[href*=\"GetFile\"]'); for(const e of req){ if(e.text.includes('.sig')){ file_links.push([pd_replace(partition),sec_replace(section),e.text,e.href]); progress(file_links); }; }; if(details_links.length) get_sig_links_details(file_links,details_links,partitions); else success(file_links); }; function start_parser(){ var file_links=[]; var details_links=[]; var url=window.location.href.toString(); var path=''; if(url.includes('ProjectDocuments')) path='ProjectDocuments'; else if(url.includes('RIIDocuments')) path='RIIDocuments'; else if(url.includes('SmetaDocuments')) path='SmetaDocuments'; else if(url.includes('IrdDocuments')) path='IrdDocuments'; var href=''.concat('https://lk.spbexp.ru/Grid/',path,'FilesRead/own', window.location.href.toString().split('\/').pop()); var v=document.querySelector('span[style=\"font-weight:600; color:darkblue\"]');\ if(v==null) return; var section=v.textContent; var links=[[path,section,href]]; new QWebChannel(qt.webChannelTransport,(channel)=>{ window.qtwebchannel=channel.objects.backend; }); get_pd_links_details(links,file_links,details_links,[]); }; start_parser(); """,lambda x: None) def getallfileslist(self): if len(self.file_list): return self.url=self.webEngineView.url().toString() if not('lk.spbexp.ru/Zeg/Zegmain' in self.url or\ ('lk.spbexp.ru/SF/' in self.url and '/prt0/' in self.url)): return self.file_list=[] self.file_number=0 try: for b in self.buttons: b.setEnabled(False) except: pass self.label.setText('Статус: получение списка файлов для скачивания...') self.webEngineView.page().runJavaScript(\ self.qwebchannel_js+self.busy+""" function get_sig_links_details_parse(file_links,details_links,partitions,partition,section,data){ if(window.process_canceled){ cancel(); return; }; var parser=new DOMParser(); var doc=parser.parseFromString(data,'text/html'); var req=doc.querySelectorAll('a[href*=\"GetFile\"]'); for(const e of req){ if(e.text.includes('.sig')){ file_links.push([pd_replace(partition),sec_replace(section),e.text,e.href]); progress(file_links); }; }; if(details_links.length) get_sig_links_details(file_links,details_links,partitions); else get_file_links(file_links,details_links,partitions); }; function get_pd_links_parse(path,file_links,details_links,partitions,data){ if(window.process_canceled){ cancel(); return; }; var links=[]; for(const d of data.Data){ if(d['NumberOfFiles']){ var href=''.concat('https://lk.spbexp.ru/Grid/',path,'FilesRead/own',d['IDRow']); if(path.includes('IrdDocuments')) links.push([path,d['Content'],href]); else links.push([path,d['Nazvanie'],href]); }; }; if(links.length) get_pd_links_details(links,file_links,details_links,partitions); }; async function get_pd_links(path,file_links,details_links,partitions){ if(window.process_canceled){ cancel(); return; }; var req=document.querySelector('a[href*="/Zeg/Zegmain1"]'); if(req){ var href=''.concat('https://lk.spbexp.ru/Grid/',path,'Read/own',req.pathname.split('\/').pop()); return await fetch(href) .then(async (response) => { const j=await response.json(); get_pd_links_parse(path,file_links,details_links,partitions,j); }).catch((error) => { errlog(error); }); }; }; function get_file_links(file_links,details_links,partitions){ if(window.process_canceled){ cancel(); return; }; if(partitions.length) get_pd_links(partitions.pop(),file_links,details_links,partitions); else success(file_links); }; function start_parser(){ var file_links=[]; var details_links=[]; var partitions=['ProjectDocuments','RIIDocuments','SmetaDocuments','IrdDocuments']; new QWebChannel(qt.webChannelTransport,(channel)=>{ window.qtwebchannel=channel.objects.backend; }); get_file_links(file_links,details_links,partitions); }; start_parser(); """,lambda x: None) def __init__(self): super().__init__() self.setWindowTitle('ExpGet') self.busy=""" window.process_canceled=false; function errlog(error){ window.qtwebchannel.backrun_error(error); }; function success(file_links){ window.qtwebchannel.backrun_success(file_links); }; function progress(file_links){ window.qtwebchannel.backrun_progress(file_links.length); }; async function cancel(){ window.qtwebchannel.backrun_cancel(); }; function pd_replace(partition){ switch(partition){ case 'ProjectDocuments': return 'ПД'; case 'RIIDocuments': return 'РИИ'; case 'SmetaDocuments': return 'СД'; case 'IrdDocuments': return 'ИРД'; }; }; function sec_replace(section){ return section.trim().replaceAll('"','_').slice(0,64).trim(); }; async function get_sig_links_details(file_links,details_links,partitions){ if(window.process_canceled){ cancel(); return; }; if(details_links.length){ var d=details_links.pop(); return await fetch(d[2]) .then(async (response) => { const j=await response.text(); get_sig_links_details_parse(file_links,details_links,partitions,d[0],d[1],j); }).catch((error) => { errlog(error); }); }; }; function get_pd_links_details_parse(links,file_links,details_links,partitions,partition,section,data){ if(window.process_canceled){ cancel(); return; }; for(const d of data.Data){ file_links.push([pd_replace(partition),sec_replace(section),d['Nazvanie'], ''.concat('https://lk.spbexp.ru/File/GetFile/',d['IDRow']),d['sDateTo'],d['Number'],d['sMD5']]); progress(file_links); details_links.push([partition,section,''.concat('https://lk.spbexp.ru/SF/FileCspViewer/',d['IDRow'])]); }; if(links.length) get_pd_links_details(links,file_links,details_links,partitions); else get_sig_links_details(file_links,details_links,partitions); }; async function get_pd_links_details(links,file_links,details_links,partitions){ if(window.process_canceled){ cancel(); return; }; if(links.length){ var d=links.pop(); return await fetch(d[2]) .then(async (response) => { const j=await response.json(); get_pd_links_details_parse(links,file_links,details_links,partitions,d[0],d[1],j); }).catch((error) => { errlog(error); }); }; }; function button_click(){ window.process_canceled=true; }; class ProgressRing extends HTMLElement{ constructor(){ super(); const stroke=this.getAttribute('stroke'); const radius=this.getAttribute('radius'); const normalizedRadius=radius-stroke*2; this._circumference=normalizedRadius*2*Math.PI; this._root=this.attachShadow({mode:'open'}); this._root.innerHTML=` <svg height="${radius*2}" width="${radius*2}"> <circle class="ring" stroke="#3c3b3a" stroke-width="${stroke*2}" stroke-opacity="0.5" fill="transparent" r="${normalizedRadius}" cx="${radius}" cy="${radius}" shape-rendering="geometricPrecision" /> <circle class="ring" stroke="#c8c5c3" stroke-dasharray="${this._circumference} ${this._circumference}" style="stroke-dashoffset:${this._circumference}" stroke-width="${stroke}" fill="transparent" r="${normalizedRadius}" cx="${radius}" cy="${radius}" shape-rendering="geometricPrecision" /> <circle class="button" stroke="#191919" stroke-width="1" fill="#f44336" r="${normalizedRadius-stroke}" cx="${radius}" cy="${radius}" shape-rendering="geometricPrecision" onclick="button_click()" /> <text class="txt" x="50%" y="52%" text-rendering="geometricPrecision">STOP</text> <circle class="ring" stroke="#000000" stroke-width="1" stroke-opacity="0.5" fill="transparent" r="${normalizedRadius+8}" cx="${radius}" cy="${radius}" shape-rendering="geometricPrecision" /> </svg> <style> .ring { transition: stroke-dashoffset 0.35s; transform: rotate(-90deg); transform-origin: 50% 50%; pointer-events: none; } .button:hover { fill: #ce000f; opacity: 1; } .txt { font: bold 40px sans-serif; fill: #323232; stroke: #4c4c4c; stroke-width: 1px; text-anchor: middle; dominant-baseline: middle; pointer-events: none; } </style> `; } setProgress(percent){ const offset=this._circumference-(percent/100*this._circumference); const circle=this._root.querySelectorAll('circle')[1]; circle.style.strokeDashoffset=offset; } setTransition(value){ const circle=this._root.querySelectorAll('circle')[1]; circle.style.transition='stroke-dashoffset '+value+'s'; } static get observedAttributes(){ return ['progress','transition']; } attributeChangedCallback(name,oldValue,newValue){ if(name==='progress') this.setProgress(newValue); if(name==='transition') this.setTransition(newValue); } } function overlay() { [].forEach.call(document.querySelectorAll('nav'), function (el) { el.style.visibility = 'hidden'; }); [].forEach.call(document.querySelectorAll('*[class*=popover]'), function (el) { el.style.visibility = 'hidden'; }); var overlay=document.createElement("div"); overlay.style.opacity=0.9; overlay.style.width='100%'; overlay.style.height='100%'; overlay.style.top='0px'; overlay.style.left='0px'; overlay.style.backgroundColor='#666666'; overlay.style.zIndex='1000'; overlay.style.position = 'absolute'; document.getElementsByTagName('body')[0].appendChild(overlay); window.customElements.define('progress-ring',ProgressRing); const circle=document.createElement("div"); circle.innerHTML=`<progress-ring stroke="8" radius="88" progress="0"></progress-ring>` circle.style.top='50%'; circle.style.left='50%'; circle.style.marginTop='-88px' circle.style.marginLeft='-88px' circle.style.zIndex='2000'; circle.style.position='absolute'; window.cprogress=0; document.body.appendChild(circle); document.body.style.overflow='hidden'; }; overlay(); const interval=setInterval(()=>{ const el=document.querySelector('progress-ring'); if(window.cprogress==200){ el.setAttribute('transition','0'); window.cprogress=0; el.setAttribute('progress',window.cprogress); } else{ if(window.cprogress==0) el.setAttribute('transition','0.35'); window.cprogress+=2; el.setAttribute('progress',window.cprogress); }; },100); """ self.webEngineView=QWebEngineView() ws=self.webEngineView.page().profile().defaultProfile() ws.setHttpCacheMaximumSize(0) ws=self.webEngineView.settings() ws.setAttribute(ws.WebAttribute.AutoLoadImages,True) ws.setAttribute(ws.WebAttribute.PluginsEnabled,False) qwebchannel_js=QFile(':/qtwebchannel/qwebchannel.js') if not qwebchannel_js.open(QIODeviceBase.OpenModeFlag.ReadOnly): raise SystemExit( 'Failed to load qwebchannel.js with error: %s' % qwebchannel_js.errorString()) self.qwebchannel_js=bytes(qwebchannel_js.readAll()).decode('utf-8') self.channel=QWebChannel() self.handler=CallHandler() self.handler.set_parent(self) self.channel.registerObject('backend',self.handler) self.webEngineView.page().setWebChannel(self.channel) self.grid=QGridLayout() self.grid.setContentsMargins(0,0,0,0) self.grid.setVerticalSpacing(0) self.grid.setHorizontalSpacing(0) self.hbox=QHBoxLayout() self.hbox.setSpacing(0) self.reload_button=QPushButton('Обновить страницу') self.get_data_button=QPushButton('Получить данные') self.get_all_data_button=QPushButton('Скачать данные проекта') self.buttons=[self.reload_button,self.get_data_button,self.get_all_data_button] self.hbox.addWidget(self.reload_button,1) self.hbox.addWidget(self.get_data_button,1) self.hbox.addWidget(self.get_all_data_button,1) self.grid.addLayout(self.hbox,0,0) self.grid.addWidget(self.webEngineView,1,0) self.label=QLabel('Статус:...') self.label.setStyleSheet("QLabel {background-color:gray;color:black;}") self.grid.addWidget(self.label,2,0) self.grid.setRowStretch(0,0) self.grid.setRowStretch(1,1) self.grid.setRowStretch(2,0) self.setLayout(self.grid) self.webEngineView.load(QUrl('https://lk.spbexp.ru')) self.webEngineView.loadFinished.connect(self.login) self.file_list=[] self.file_number=0 self.lock=False self.basepath=realpath('out') try: makedirs(self.basepath,exist_ok=True) except: pass self.reload_button.clicked.connect(self.reload) self.get_data_button.clicked.connect(self.getfileslist) self.get_all_data_button.clicked.connect(self.getallfileslist) def goto_projects(self): self.webEngineView.loadFinished.disconnect() self.webEngineView.load(QUrl('https://lk.spbexp.ru/SF/Zayava/')) def login_callback(self,result): if len(result): self.webEngineView.loadFinished.connect(self.goto_projects) def login(self): l=p='' try: f=open('login','r') d=f.readlines() f.close() l=d[0].strip() p=d[1].strip() except: self.webEngineView.loadFinished.disconnect() return self.webEngineView.page().runJavaScript(""" function find_login_form(){ var v=document.querySelector('input[id=Login]'); if(v!=null){ v.value=`%s`; v=document.querySelector('input[id=Password]'); if(v!=null){ v.value=`%s`; v=document.querySelector('button[type=submit]'); if(v!=null){ v.click(); return 'ok'; }; }; }; return ''; }; find_login_form();"""%(l,p),self.login_callback)