import logging from PyQt5 import QtCore from PyQt5 import QtGui from PyQt5 import QtWidgets import mc.gui.safe_delete_dlg import mc.gui.warning_dlg import mc.model from mc import mc_global from mc.gui.reusable_components import PushButton, PhrasesList, PageGrid, ButtonGrid BREATHING_IN_DEFAULT_PHRASE = "Breathing in" BREATHING_OUT_DEFAULT_PHRASE = "Breathing out" BREATHING_IN_DEFAULT_SHORT_PHRASE = "in" BREATHING_OUT_DEFAULT_SHORT_PHRASE = "out" class BreathingPhraseListWt(QtWidgets.QWidget): phrase_changed_signal = QtCore.pyqtSignal(bool) selection_changed_signal = QtCore.pyqtSignal(bool) def __init__(self): super().__init__() self.updating_gui_bool = False self.edit_dialog = None self.list_widget = PhrasesList() self.list_widget.itemSelectionChanged.connect(self.on_selection_changed) self.add_to_list_qle = QtWidgets.QLineEdit() self.add_to_list_qle.setObjectName("add_to_list_qle") self.add_to_list_qle.setPlaceholderText(self.tr("New item")) self.add_to_list_qle.setFixedWidth(305) QtWidgets.QShortcut( QtGui.QKeySequence(QtCore.Qt.Key_Return), self.add_to_list_qle, member=self.add_new_phrase_button_clicked, context=QtCore.Qt.WidgetShortcut ) self.add_new_phrase_qpb = PushButton(self.tr("Add")) self.add_new_phrase_qpb.setFixedWidth(75) self.add_new_phrase_qpb.clicked.connect(self.add_new_phrase_button_clicked) add_new_phrase_qhl = QtWidgets.QHBoxLayout() add_new_phrase_qhl.addWidget(self.add_new_phrase_qpb) self.edit_texts_qpb = PushButton() self.edit_texts_qpb.setFixedWidth(75) self.edit_texts_qpb.setIcon(QtGui.QIcon(mc.mc_global.get_icon_path("pencil-2x.png"))) self.edit_texts_qpb.setToolTip(self.tr("Edit the selected breathing phrase")) self.edit_texts_qpb.clicked.connect(self.on_edit_texts_clicked) self.move_to_top_qpb = PushButton() self.move_to_top_qpb.setFixedWidth(75) self.move_to_top_qpb.setIcon(QtGui.QIcon(mc.mc_global.get_icon_path("data-transfer-upload-2x.png"))) self.move_to_top_qpb.setToolTip(self.tr("Move the selected breathing phrase to top")) self.move_to_top_qpb.clicked.connect(self.on_move_to_top_clicked) self.move_up_qpb = PushButton() self.move_up_qpb.setFixedWidth(75) self.move_up_qpb.setIcon(QtGui.QIcon(mc.mc_global.get_icon_path("arrow-top-2x.png"))) self.move_up_qpb.setToolTip(self.tr("Move the selected breathing phrase up")) self.move_up_qpb.clicked.connect(self.on_move_up_clicked) self.move_down_qpb = PushButton() self.move_down_qpb.setFixedWidth(75) self.move_down_qpb.setIcon(QtGui.QIcon(mc.mc_global.get_icon_path("arrow-bottom-2x.png"))) self.move_down_qpb.setToolTip(self.tr("Move the selected breathing phrase down")) self.move_down_qpb.clicked.connect(self.on_move_down_clicked) self.delete_phrase_qpb = PushButton() self.delete_phrase_qpb.setFixedWidth(75) self.delete_phrase_qpb.setIcon(QtGui.QIcon(mc.mc_global.get_icon_path("trash-2x.png"))) self.delete_phrase_qpb.setToolTip(self.tr("Delete the selected breathing phrase")) self.delete_phrase_qpb.clicked.connect(self.on_delete_clicked) button_bar_grid = ButtonGrid() button_bar_grid.addWidget(self.edit_texts_qpb, 0, 0) button_bar_grid.addWidget(self.move_to_top_qpb, 0, 1) button_bar_grid.addWidget(self.move_up_qpb, 0, 2) button_bar_grid.addWidget(self.move_down_qpb, 0, 3) button_bar_grid.addWidget(self.delete_phrase_qpb, 0, 5) button_bar_grid.setColumnStretch(4, 1) breathing_list_grid = PageGrid() breathing_list_grid.addWidget( QtWidgets.QLabel(self.tr("These are the sentences that appear in the `breathing dialog`")), 0, 0, 1, 3 ) breathing_list_grid.addWidget( QtWidgets.QLabel(self.tr("They also appear in the `breathing notification`")), 1, 0, 1, 3 ) breathing_list_grid.addWidget(self.list_widget, 2, 0, 1, 3) breathing_list_grid.addWidget(self.add_to_list_qle, 3, 0) breathing_list_grid.setColumnStretch(1, 1) breathing_list_grid.addLayout(add_new_phrase_qhl, 3, 2) breathing_list_grid.addLayout(button_bar_grid, 4, 0, 1, 3) self.setLayout(breathing_list_grid) self.update_gui() self.list_widget.setCurrentRow(0) # -the first row def set_button_states(self, status): # Disables or enables the buttons depending on breathing phrases list self.move_up_qpb.setDisabled(status) self.move_down_qpb.setDisabled(status) self.move_to_top_qpb.setDisabled(status) self.delete_phrase_qpb.setDisabled(status) self.edit_texts_qpb.setDisabled(status) def on_move_up_clicked(self): self.move_current_row_up_down(mc.model.MoveDirectionEnum.up) def on_move_down_clicked(self): self.move_current_row_up_down(mc.model.MoveDirectionEnum.down) def on_move_to_top_clicked(self): current_row_int = self.list_widget.currentRow() current_list_widget_item = self.list_widget.item(current_row_int) item_widget = self.list_widget.itemWidget(current_list_widget_item) self.list_widget.takeItem(current_row_int) # -IMPORTANT: item is removed from list only after the item widget has been extracted. # The reason for this is that if we take the item away from the list the associated # widget (in our case a CustomLabel) will not come with us (which makes sense # if the widget is stored in the list somehow) self.list_widget.insertItem(0, current_list_widget_item) # -0 for the topmost position self.list_widget.setItemWidget(current_list_widget_item, item_widget) self.list_widget.setCurrentRow(0) self.update_db_sort_order_for_all_rows() def update_db_sort_order_for_all_rows(self): logging.debug("update_db_sort_order_for_all_rows") count = 0 while count < self.list_widget.count(): q_list_item_widget = self.list_widget.item(count) custom_label = self.list_widget.itemWidget(q_list_item_widget) id_int = custom_label.entry_id row_int = self.list_widget.row(q_list_item_widget) mc.model.PhrasesM.update_sort_order( id_int, row_int ) logging.debug("id_int = " + str(id_int) + ", row_int = " + str(row_int)) count += 1 self.update_gui() self.update_selected() def move_current_row_up_down(self, i_move_direction: mc.model.MoveDirectionEnum) -> bool: current_row_int = self.list_widget.currentRow() if current_row_int == 0 and i_move_direction == mc.model.MoveDirectionEnum.up: return False if current_row_int == self.list_widget.count() - 1 and i_move_direction == mc.model.MoveDirectionEnum.down: return False current_list_widget_item = self.list_widget.item(current_row_int) item_widget = self.list_widget.itemWidget(current_list_widget_item) self.list_widget.takeItem(current_row_int) # -IMPORTANT: item is removed from list only after the item widget has been extracted. # The reason for this is that if we take the item away from the list the associated # widget (in our case a CustomLabel) will not come with us (which makes sense # if the widget is stored in the list somehow) if i_move_direction == mc.model.MoveDirectionEnum.up: # if main_sort_order_int == 0 or main_sort_order_int > len(QuestionM.get_all()): if current_row_int > 0: self.list_widget.insertItem(current_row_int - 1, current_list_widget_item) self.list_widget.setItemWidget(current_list_widget_item, item_widget) self.list_widget.setCurrentRow(current_row_int - 1) else: return False elif i_move_direction == mc.model.MoveDirectionEnum.down: # if main_sort_order_int < 0 or main_sort_order_int >= len(QuestionM.get_all()): if current_row_int < self.list_widget.count() - 1: self.list_widget.insertItem(current_row_int + 1, current_list_widget_item) self.list_widget.setItemWidget(current_list_widget_item, item_widget) self.list_widget.setCurrentRow(current_row_int + 1) else: return False self.update_db_sort_order_for_all_rows() return True def update_selected(self, i_default_phrase=1): if mc.mc_global.active_phrase_id_it == mc.mc_global.NO_PHRASE_SELECTED_INT: item = self.list_widget.item(i_default_phrase) item.setSelected(True) self.list_widget.setCurrentItem(item) # -important that we add this as well return for i in range(0, self.list_widget.count()): item = self.list_widget.item(i) phrase_qll = self.list_widget.itemWidget(item) logging.debug("custom_qll.entry_id = " + str(phrase_qll.entry_id)) if phrase_qll.entry_id == mc.mc_global.active_phrase_id_it: item.setSelected(True) self.list_widget.setCurrentItem(item) # -important that we add this as well return def on_edit_texts_clicked(self): id_int = mc.mc_global.active_phrase_id_it if id_int != mc.mc_global.NO_PHRASE_SELECTED_INT: self.edit_dialog = EditDialog() self.edit_dialog.finished.connect(self.on_edit_dialog_finished) self.edit_dialog.show() def on_return_shortcut_triggered(self): logging.debug("the return key has been pressed") def on_delete_clicked(self): # active_phrase = mc.model.PhrasesM.get(mc.mc_global.active_phrase_id_it) if mc.mc_global.active_phrase_id_it == mc.mc_global.NO_PHRASE_SELECTED_INT: # No phrase selected, nothing to delete logging.warning("No phrase selected") return conf_result_bool = mc.gui.safe_delete_dlg.SafeDeleteDlg.get_safe_confirmation_dialog( self.tr("Are you sure that you want to remove this entry?"), ) if conf_result_bool: mc.model.PhrasesM.remove(mc.mc_global.active_phrase_id_it) mc.mc_global.active_phrase_id_it = mc.mc_global.NO_PHRASE_SELECTED_INT self.update_gui(mc.mc_global.EventSource.breathing_phrase_deleted) else: pass def add_new_phrase_button_clicked(self): text_sg = self.add_to_list_qle.text().strip() # strip is needed to remove a newline at the end (why?) if not (text_sg and text_sg.strip()): conf_result_bool = mc.gui.warning_dlg.WarningDlg.get_safe_confirmation_dialog( self.tr("You have to write an item before you press 'Add'.") ) return mc.model.PhrasesM.add( text_sg, BREATHING_IN_DEFAULT_PHRASE, BREATHING_OUT_DEFAULT_PHRASE, BREATHING_IN_DEFAULT_SHORT_PHRASE, BREATHING_OUT_DEFAULT_SHORT_PHRASE, mc.mc_global.BreathingPhraseType.in_out ) self.add_to_list_qle.clear() self.update_gui() self.list_widget.setCurrentRow(self.list_widget.count() - 1) # self.in_breath_phrase_qle.setFocus() # if dialog_result == QtWidgets.QDialog.Accepted: self.edit_dialog = EditDialog() self.edit_dialog.finished.connect(self.on_edit_dialog_finished) self.edit_dialog.show() def on_edit_dialog_finished(self, i_result: int): if i_result == QtWidgets.QDialog.Accepted: assert mc.mc_global.active_phrase_id_it != mc.mc_global.NO_PHRASE_SELECTED_INT phrase = mc.model.PhrasesM.get(mc.mc_global.active_phrase_id_it) phrase.title = self.edit_dialog.breath_title_qle.text() phrase.ib = self.edit_dialog.in_breath_phrase_qle.text() phrase.ob = self.edit_dialog.out_breath_phrase_qle.text() else: pass self.phrase_changed_signal.emit(True) def on_selection_changed(self): if self.updating_gui_bool: return selected_model_index_list = self.list_widget.selectedIndexes() active_selected_bool = len(selected_model_index_list) >= 1 if active_selected_bool: selected_row_int = selected_model_index_list[0].row() # self.details_qgb.setDisabled(False) # TODO: setDisabled for other current_question_qli = self.list_widget.item(selected_row_int) customqlabel_widget = self.list_widget.itemWidget(current_question_qli) mc.mc_global.active_phrase_id_it = customqlabel_widget.entry_id else: mc.mc_global.active_phrase_id_it = mc.mc_global.NO_PHRASE_SELECTED_INT self.selection_changed_signal.emit(active_selected_bool) def on_new_row_selected_from_system_tray(self, i_id_of_selected_item: int): mc.mc_global.active_phrase_id_it = i_id_of_selected_item for i in range(0, self.list_widget.count()): item = self.list_widget.item(i) phrase_cqll = self.list_widget.itemWidget(item) logging.debug("phrase_cqll.entry_id = " + str(phrase_cqll.entry_id)) if phrase_cqll.entry_id == mc.mc_global.active_phrase_id_it: item.setSelected(True) return def update_gui(self, i_event_source=mc.mc_global.EventSource.undefined): self.updating_gui_bool = True # If the list is now empty, disabling buttons # If the list is no longer empty, enable buttons self.set_button_states(mc.model.PhrasesM.is_empty()) # List self.list_widget.clear() for l_phrase in mc.model.PhrasesM.get_all(): # self.list_widget.addItem(l_collection.title_str) custom_label = CustomQLabel(l_phrase.title, l_phrase.id) list_item = QtWidgets.QListWidgetItem() list_item.setSizeHint(QtCore.QSize(list_item.sizeHint().width(), mc_global.LIST_ITEM_HEIGHT_INT)) self.list_widget.addItem(list_item) self.list_widget.setItemWidget(list_item, custom_label) if i_event_source == mc.mc_global.EventSource.breathing_phrase_deleted: self.update_selected(0) else: self.update_selected() self.updating_gui_bool = False class CustomQLabel(QtWidgets.QLabel): entry_id = mc.mc_global.NO_PHRASE_SELECTED_INT # -"static" def __init__(self, i_text_sg, i_entry_id=mc.mc_global.NO_PHRASE_SELECTED_INT): super().__init__(i_text_sg) self.entry_id = i_entry_id class EditDialog(QtWidgets.QDialog): def __init__(self, i_parent=None): super(EditDialog, self).__init__(i_parent) self.setModal(True) self.setMinimumWidth(250) self.updating_gui_bool = False # If a phrase is not selected, default to phrase with id 1 if mc.mc_global.active_phrase_id_it == mc.mc_global.NO_PHRASE_SELECTED_INT: mc.mc_global.active_phrase_id_it = 1 active_phrase = mc.model.PhrasesM.get(mc.mc_global.active_phrase_id_it) vbox = QtWidgets.QVBoxLayout(self) self.breath_title_qle = QtWidgets.QLineEdit(active_phrase.title) vbox.addWidget(QtWidgets.QLabel(self.tr("Title"))) vbox.addWidget(self.breath_title_qle) vbox.addWidget(QtWidgets.QLabel("Phrase(s)")) self.in_breath_phrase_qle = QtWidgets.QLineEdit(active_phrase.ib) vbox.addWidget(self.in_breath_phrase_qle) self.out_breath_phrase_qle = QtWidgets.QLineEdit(active_phrase.ob) vbox.addWidget(self.out_breath_phrase_qle) self.button_box = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel, QtCore.Qt.Horizontal, self ) vbox.addWidget(self.button_box) self.button_box.accepted.connect(self.accept) self.button_box.rejected.connect(self.reject) # -accept and reject are "slots" built into Qt self.update_gui() def update_gui(self): self.updating_gui_bool = True self.adjustSize() self.updating_gui_bool = False