class QuickRenameView(QWidget): """Main view for QuickRename. Attributes: layout: Layout associated with QuickRenameView. folder_view: View which represents folder selection where renamable files live. limit_view: View which provides options for limiting what is renamed. file_view: View which provides an interface for renaming selected files. options_view: View which provides options which control how renaming is performed. buttons_view: View which provides buttons to trigger actions in rename process. """ def __init__(self, folder_view: object, limit_view: object, file_view: object, options_view: object, buttons_view: object): """Initialization of QuickRenameView. Args: folder_view: View which represents folder selection where renamable files live. limit_view: View which provides options for limiting what is renamed. file_view: View which provides an interface for renaming selected files. options_view: View which provides options which control how renaming is performed. buttons_view: View which provides buttons to trigger actions in rename process. """ super(QuickRenameView, self).__init__() self.layout = QVBoxLayout() self.folder_view = folder_view self.limit_view = limit_view self.file_view = file_view self.options_view = options_view self.buttons_view = buttons_view self._instructions_layout = QHBoxLayout() self._instructions = QLabel(prefs.INSTRUCTIONS) self._configure() def _configure(self) -> None: """Configure QuickRenameView.""" self.layout.setAlignment(Qt.AlignTop) self._instructions_layout.setAlignment(Qt.AlignCenter) self._instructions.setObjectName(prefs.INSTRUCTIONS_NAME) self._instructions_layout.addWidget(self._instructions) self.layout.addWidget(self.folder_view) self.layout.addWidget(self.limit_view) self.layout.addSpacerItem(QSpacerItem(*prefs.SPACING)) self.layout.addLayout(self._instructions_layout) self.layout.addWidget(self.file_view) self.layout.addWidget(self.options_view) self.layout.addWidget(self.buttons_view) self.setWindowTitle(prefs.TITLE) self.setWindowIcon(QIcon("icons/quick_rename_icon.png")) self.setStyleSheet(CSS) self.setLayout(self.layout)
def __init__(self, parent=None): super(Repertoire, self).__init__(parent) global filename self.monRepertoire = {} self.labelNom = QLabel("Nom") self.labelPrenom = QLabel("Prenom") self.labelTel = QLabel("Tel") self.leNom = QLineEdit() self.lePrenom = QLineEdit() self.leTel = QLineEdit() self.lwListeNoms = QListWidget() self.pbAjouter = QPushButton("Ajouter") self.pbModifier = QPushButton("Modifier") self.monRepertoire = self.lireJSON(filename) #self.lwListeNoms.itemClicked.connect(self.userSelected) self.pbAjouter.clicked.connect(self.addUser) #self.pbModifier.clicked.connect(self.modifyUser) layoutLabels = QVBoxLayout() layoutLabels.addWidget(self.labelNom) layoutLabels.addWidget(self.labelPrenom) layoutLabels.addWidget(self.labelTel) layoutLabels.addSpacerItem( QSpacerItem(10, 100, QSizePolicy.Expanding, QSizePolicy.Expanding)) layoutLineEdit = QVBoxLayout() layoutLineEdit.addWidget(self.leNom) layoutLineEdit.addWidget(self.lePrenom) layoutLineEdit.addWidget(self.leTel) layoutLineEdit.addSpacerItem( QSpacerItem(10, 100, QSizePolicy.Expanding, QSizePolicy.Expanding)) HLayout = QHBoxLayout() HLayout.addWidget(self.lwListeNoms) HLayout.addLayout(layoutLabels) HLayout.addLayout(layoutLineEdit) HLayoutButtons = QHBoxLayout() HLayoutButtons.addSpacerItem(QSpacerItem(10, 10, QSizePolicy.Expanding)) HLayoutButtons.addWidget(self.pbModifier) HLayoutButtons.addWidget(self.pbAjouter) genLayout = QVBoxLayout() genLayout.addLayout(HLayout) genLayout.addLayout(HLayoutButtons) self.setLayout(genLayout) self.updateListw()
class ConfigureUsersDialog(QDialog): def __init__(self, users): super().__init__() self.setWindowTitle("Configure Users") self.model = TableModel(users) self.layout = QVBoxLayout() self.table_layout = QHBoxLayout() self.users_table = QTableView() self.users_table.setModel(self.model) self.users_table.horizontalHeader().setStretchLastSection(True) self.table_layout.addWidget(self.users_table) self.table_buttons_layout = QVBoxLayout() self.add_user_button = QPushButton() self.add_user_button.setText("Add User") self.add_user_button.clicked.connect(self._add_user_clicked) self.delete_user_button = QPushButton() self.delete_user_button.setText("Delete User") self.delete_user_button.clicked.connect(self._delete_user_clicked) self.table_buttons_layout.addSpacerItem( QSpacerItem(0, 0, vData=QSizePolicy.Expanding)) self.table_buttons_layout.addWidget(self.add_user_button) self.table_buttons_layout.addWidget(self.delete_user_button) self.table_buttons_layout.addSpacerItem( QSpacerItem(0, 0, vData=QSizePolicy.Expanding)) self.table_layout.addLayout(self.table_buttons_layout) self.layout.addLayout(self.table_layout) self.error_text = QLabel() self.error_text.setStyleSheet("color: red;") self.layout.addWidget(self.error_text) self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.button_box.accepted.connect(self._on_accepted_clicked) self.button_box.rejected.connect(self.reject) self.layout.addWidget(self.button_box) self.setLayout(self.layout) self.resize(600, 400) def _on_accepted_clicked(self): self._complete_table() if not self.model.are_users_unique(): self.error_text.setText("Name must be unique for each user") return self.error_text.setText("") self.accept() def _complete_table(self): # The currently selected cell only updates the model when 'return' is # pressed or another cell is selected. If the user is updating a value # then clicks 'OK' without pressing 'return' then the change is lost. # This is a Qt thing - the workaround is to take focus from the table. self.button_box.button(self.button_box.Ok).setFocus() def _add_user_clicked(self): self.users_table.model().insertRow(self.model.num_rows) def _delete_user_clicked(self): rows_to_remove = set() for index in self.users_table.selectedIndexes(): rows_to_remove.add(index.row()) self.users_table.model().removeRows(list(rows_to_remove)) def get_users(self): return self.model.users
class RenameOptionsView(QWidget): """View responsible for holding renaming options. Attributes: layout (QVBoxLayout): Main layout of view. frame_layout (QVBoxLayout: Layout of frame which holds options. frame (QFrame): Frame surrounding options. prefix_h_layout (QHBoxLayout): Layout holding prefix options. complete_rename_h_layout (QHBoxLayout): Layout holding complete rename options. search_and_replace_h_layout (QHBoxLayout): Layout holding search and replace options. renumber_h_layout (QHBoxLayout): Layout holding renumber options. remove_ext_h_layout (QHBoxLayout): Layout holding remove options. change_ext_h_layout (QHBoxLayout): Layout holding change extension options. create_backup_h_layout (QHBoxLayout): Layout holding backup options. preview_h_layout (QHBoxLayout): Layout holding preview options. start_lbl (QLabel): Label for renumbering start. padding_lbl (QLabel): Label for renumbering padding. add_prefix_cb (QCheckBox): Used to signify the user wants to add a prefix to the renaming. prefix (QLineEdit): prefix to add. complete_rename_cb (QCheckBox): Used to signify the user wants to completely rename the file. new_name (QLineEdit): New name used when renaming. search_and_replace_cb (QCheckBox): Used to signify the user wants to partially rename files. find (QLineEdit): When searching and replacing this is what the user wants to search for. replace (QLineEdit): When searching and replacing this is what the user wants to replace with. renumber_cb (QCheckBox): Used to signify the user wants to renumber while renaming. start_num (QSpinBox): Number to start with when renumbering files. padding (QComboBox): Padding to apply to renaming when renumbering files. dot_cb (QCheckBox): When checked a dot will be used to separate the renumber from the name. remove_ext_cb (QCheckBox): Used to signify the user wants to remove extensions when renaming. backup_files_cb (QCheckBox): Used to signify the user wants to backup old files before renaming. change_ext_cb (QCheckBox): Used to signify the user wants to change the extension while renaming. change_ext (QLineEdit): New extension to add to the renamed file. preview_cb (QCheckBox): Used to signify the user wants to preview the rename before renaming. """ def __init__(self): super(RenameOptionsView, self).__init__() self.layout = QVBoxLayout() self.frame_layout = QVBoxLayout() self.options_lbl = QLabel(prefs.OPTIONS) self.frame = QFrame() self.prefix_h_layout = QHBoxLayout() self.complete_rename_h_layout = QHBoxLayout() self.search_and_replace_h_layout = QHBoxLayout() self.renumber_h_layout = QHBoxLayout() self.remove_ext_h_layout = QHBoxLayout() self.change_ext_h_layout = QHBoxLayout() self.create_backup_h_layout = QHBoxLayout() self.preview_h_layout = QHBoxLayout() self.start_lbl = QLabel(prefs.START_NUM) self.padding_lbl = QLabel(prefs.PADDING) self.add_prefix_cb = QCheckBox(prefs.PREFIX) self.prefix = QLineEdit(prefs.PREFIX_DEFAULT) self.complete_rename_cb = QCheckBox(prefs.COMPLETE_RENAME) self.new_name = QLineEdit(prefs.COMPLETE_RENAME_DEFAULT) self.search_and_replace_cb = QCheckBox(prefs.SEARCH_AND_REPLACE) self.find = QLineEdit(prefs.SEARCH_AND_REPLACE_DEFAULT) self.replace = QLineEdit(prefs.REPLACE_WITH_DEFAULT) self.renumber_cb = QCheckBox(prefs.RENUMBER) self.start_num = QSpinBox() self.padding = QComboBox() self.dot_cb = QCheckBox(prefs.USE_DOT) self.remove_ext_cb = QCheckBox(prefs.REMOVE_EXT) self.backup_files_cb = QCheckBox(prefs.BACKUP) self.change_ext_cb = QCheckBox(prefs.CHANGE_EXT) self.change_ext = QLineEdit(prefs.CHANGE_EXT_DEFAULT) self.preview_cb = QCheckBox(prefs.PREVIEW) self._configure() def _configure(self) -> None: """Configure the RenameOptionsView.""" self.frame.setLayout(self.frame_layout) self.frame.setFrameStyle(QFrame.StyledPanel | QFrame.Sunken) self.layout.addWidget(self.options_lbl) self.layout.addWidget(self.frame) self.add_prefix_cb.setToolTip(prefs.PREFIX_TOOLTIP) self.prefix.setDisabled(True) self.prefix.setMaximumWidth(prefs.PREFIX_WIDTH) self.prefix.setMinimumWidth(prefs.PREFIX_WIDTH) self.complete_rename_cb.setToolTip(prefs.COMPLETE_RENAME_TOOLTIP) self.new_name.setDisabled(True) self.new_name.setMaximumWidth(prefs.NEW_NAME_WIDTH) self.new_name.setMinimumWidth(prefs.NEW_NAME_WIDTH) self.search_and_replace_cb.setToolTip(prefs.SEARCH_AND_REPLACE_TOOLTIP) self.find.setDisabled(True) self.find.setMinimumWidth(prefs.FIND_WIDTH) self.find.setMaximumWidth(prefs.FIND_WIDTH) self.replace.setDisabled(True) self.replace.setMaximumWidth(prefs.REPLACE_WIDTH) self.replace.setMinimumWidth(prefs.REPLACE_WIDTH) self.renumber_cb.setToolTip(prefs.RENUMBER_TOOLTIP) self.start_num.setToolTip(prefs.START_NUM_TOOLTIP) self.start_num.setDisabled(True) self.start_num.setValue(prefs.START_NUM_DEFAULT) self.start_num.setMinimumWidth(prefs.START_NUM_MIN_WIDTH) self.padding.setToolTip(prefs.PADDING_TOOLTIP) self.padding.setDisabled(True) self.padding.addItems([str(x) for x in range(10)]) self.padding.setCurrentIndex(4) self.padding.setMinimumWidth(prefs.PADDING_MIN_WIDTH) self.dot_cb.setToolTip(prefs.USE_DOT_TOOLTIP) self.dot_cb.setDisabled(True) self.dot_cb.setChecked(True) self.dot_cb.setMinimumWidth(prefs.DOT_WIDTH) self.dot_cb.setMaximumWidth(prefs.DOT_WIDTH) self.remove_ext_cb.setToolTip(prefs.REMOVE_EXT_TOOLTIP) self.change_ext.setToolTip(prefs.CHANGE_EXT_TOOLTIP) self.change_ext.setDisabled(True) self.backup_files_cb.setToolTip(prefs.BACKUP_TOOLTIP) self.backup_files_cb.setChecked(True) self.preview_cb.setToolTip(prefs.PREVIEW_TOOLTIP) self.preview_cb.setChecked(True) self.prefix_h_layout.addWidget(self.add_prefix_cb) self.prefix_h_layout.addWidget(self.prefix) self.frame_layout.addLayout(self.prefix_h_layout) self.complete_rename_h_layout.addWidget(self.complete_rename_cb) self.complete_rename_h_layout.addWidget(self.new_name) self.frame_layout.addLayout(self.complete_rename_h_layout) self.search_and_replace_h_layout.addWidget(self.search_and_replace_cb) self.search_and_replace_h_layout.addWidget(self.find) self.search_and_replace_h_layout.addWidget(self.replace) self.frame_layout.addLayout(self.search_and_replace_h_layout) self.renumber_h_layout.addWidget(self.renumber_cb) self.renumber_h_layout.addStretch(1) self.renumber_h_layout.addWidget(self.start_lbl) self.renumber_h_layout.addWidget(self.start_num) self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE)) self.renumber_h_layout.addWidget(self.padding_lbl) self.renumber_h_layout.addWidget(self.padding) self.renumber_h_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE)) self.renumber_h_layout.addWidget(self.dot_cb) self.frame_layout.addLayout(self.renumber_h_layout) self.change_ext_h_layout.addWidget(self.change_ext_cb) self.change_ext_h_layout.addWidget(self.change_ext) self.frame_layout.addLayout(self.change_ext_h_layout) self.remove_ext_h_layout.addWidget(self.remove_ext_cb) self.frame_layout.addLayout(self.remove_ext_h_layout) self.create_backup_h_layout.addWidget(self.backup_files_cb) self.frame_layout.addLayout(self.create_backup_h_layout) self.preview_h_layout.addWidget(self.preview_cb) self.frame_layout.addLayout(self.preview_h_layout) self.frame_layout.addSpacerItem(QSpacerItem(*prefs.SPACER_SIZE)) self.setLayout(self.layout) def disable_change_ext(self) -> None: """Disable change extension.""" self.change_ext.setDisabled(True) def disable_dot(self) -> None: """Disable dot checkbox.""" self.dot_cb.setDisabled(True) def disable_find(self) -> None: """Disable find.""" self.find.setDisabled(True) def disable_new_name(self) -> None: """Disable new name.""" print("disable new name") self.new_name.setDisabled(True) def disable_padding(self) -> None: """Disable padding.""" self.padding.setDisabled(True) def disable_prefix(self) -> None: """Disable prefix.""" self.prefix.setDisabled(True) def disable_start_num(self) -> None: """Disable start num.""" self.start_num.setDisabled(True) def disable_replace(self) -> None: """Disable replace.""" self.replace.setDisabled(True) def enable_change_ext(self) -> None: """Disable change extension.""" self.change_ext.setDisabled(False) def enable_dot(self) -> None: """Enable dot checkbox.""" self.dot_cb.setEnabled(True) def enable_find(self) -> None: """Enable find.""" self.find.setEnabled(True) def enable_new_name(self) -> None: """Enable new name.""" print("enable new name.") self.new_name.setEnabled(True) def enable_padding(self) -> None: """Enable padding.""" self.padding.setEnabled(True) def enable_prefix(self) -> None: """Enable prefix.""" self.prefix.setEnabled(True) def enable_replace(self) -> None: """Enable replace.""" self.replace.setEnabled(True) def enable_start_num(self) -> None: """Enable start num.""" self.start_num.setEnabled(True) def get_add_prefix(self) -> bool: """Return if end user wants to add a prefix and it is not the default value.""" result = self.get_prefix_checked() if result and self.get_prefix() == prefs.PREFIX_DEFAULT: result = False return result def get_do_backup(self) -> bool: """Return if end user wants to backup files.""" return self.backup_files_cb.isChecked() def get_change_ext(self) -> bool: """Return if the change extension checkbox is checked.""" return self.change_ext_cb.isChecked() def get_do_complete_rename(self) -> bool: """Get if end user wants to completely rename.""" return self.complete_rename_cb.isChecked() def get_dot(self) -> str: """Return dot string based on end users configuration. Note: If the end user has not enable using dot separators an empty string will be returned. """ return "." if self.get_do_dot() else "" def get_do_dot(self) -> bool: """Return if the end user wants to use dot separators when renaming.""" return self.dot_cb.isChecked() def get_do_change_ext(self) -> bool: """Return if the end user wants to change the extension.""" result = self.change_ext_cb.isChecked() if self.get_new_ext() == prefs.CHANGE_EXT_DEFAULT: return False return result def get_do_padding(self) -> bool: """Return if the end user wants to add padding.""" return False if self.get_padding() == 0 else True def get_do_preview(self) -> bool: """Return if the end user wants to preview changes.""" return self.preview_cb.isChecked() def get_do_rename(self) -> bool: """Return if end user wants to rename the full item and it is not the default value.""" result = self.complete_rename_cb.isChecked() if result and self.get_new_name() == prefs.COMPLETE_RENAME_DEFAULT: result = False return result def get_do_renumber(self) -> bool: """Return if the end user wants to renumber.""" return self.renumber_cb.isChecked() def get_do_search(self) -> bool: """Return if end user wants to perform a search and replace AND it is not the default values respectfully. Note: If you only want to know if search and replace is checked use get_search_and_replace. """ result = self.search_and_replace_cb.isChecked() if result and (self.get_find() == prefs.SEARCH_AND_REPLACE_DEFAULT or self.get_replace() == prefs.REPLACE_WITH_DEFAULT): result = False return result def get_do_search_and_replace(self) -> bool: """Return if end user wants to perform a search and replace.""" return self.search_and_replace_cb.isChecked() def get_find(self) -> str: """Return find value.""" return str(self.find.text()) def get_new_ext(self) -> str: """Return new ext.""" return str(self.change_ext.text()) def get_new_name(self) -> str: """Return new_name value.""" return str(self.new_name.text()) def get_padding(self) -> int: """Return the current padding value.""" return int(self.padding.currentText()) def get_prefix_checked(self) -> bool: """Return if the prefix checkbox is checked.""" return self.add_prefix_cb.isChecked() def get_prefix(self) -> str: """Return the current prefix value end user has entered.""" return str(self.prefix.text()) def get_remove_ext(self) -> bool: """Return if end user has checked the remove extension checkbox.""" return self.remove_ext_cb.isChecked() def get_replace(self) -> str: """Return the current replace value end user has entered.""" return str(self.replace.text()) def get_start_num(self) -> int: """Return start number from view.""" return int(self.start_num.value()) def set_change_ext_style(self, style: str) -> None: """Set style of change extension. Args: style: Style sheet applied to change extension. """ self.change_ext.setStyleSheet(style) def set_disabled(self) -> None: """Disable View.""" self.setDisabled(True) def set_enable(self) -> None: """Enable View.""" self.setEnabled(True) def set_find(self, value: str) -> None: """Set the value of find. Args: value: Value applied to find """ self.find.setText(value) def set_find_style(self, style: str) -> None: """Set style of find. Args: style: Style sheet applied to find. """ self.find.setStyleSheet(style) def set_new_name(self, value: str) -> None: """Set the value of new name. Args: value: Value applied to new_name """ self.new_name.setText(value) def set_new_name_style(self, style: str) -> None: """Set style of new_name. Args: style: Style sheet applied to new_name. """ self.new_name.setStyleSheet(style) def set_prefix(self, value: str) -> None: """Set the value of prefix. Args: value: Value applied to prefix """ self.prefix.setText(value) def set_prefix_style(self, style: str) -> None: """Set style of prefix. Args: style: Style sheet applied to prefix. """ self.prefix.setStyleSheet(style) def set_remove_ext(self, state: bool) -> None: """Set the remove_ext checkbox as checked or unchecked. Args: state: Check state of remove_ext. """ self.remove_ext_cb.setCheckState(Qt.Checked if state else Qt.Unchecked) def set_replace(self, value: str) -> None: """Set the value of replace. Args: value: Value applied to replace """ self.replace.setText(value) def set_replace_style(self, style: str) -> None: """Set style of replace. Args: style: Style sheet applied to replace. """ self.replace.setStyleSheet(style)
class RootsApp(QMainWindow): standard_deviation_threshold = 0.1 # when I receive a measurement from the sensor I check if its standard deviation; if it's too low it means the sensor is not working temporary_database_filename = "temporary.db" # the current session is stored in a temporary database. When the user saves, it is copied at the desired location def __init__(self): super().__init__(); self.setWindowTitle("Roots") self.setFixedWidth(1200) self.resize(1200, 1200) self.threadpool = QThreadPool(); self.object_list = list() self.is_training_on = False self.interaction_under_training = None self.n_measurements_collected = 0 self.n_measurements_to_collect = 3 self.sensor_not_responding = True self.sensor_not_responding_timeout = 2000 # milliseconds self.database_connection = self.create_temporary_database() self.active_object = None self.number_of_objects_added = 0 self.sensor_start_freq = 250000 self.sensor_end_freq = 3000000 # creates the plot self.plotWidget = pyqtgraph.PlotWidget(title = "Sensor Response") self.plotWidget.setFixedHeight(300) self.plotWidget.getAxis("bottom").setLabel("Excitation frequency", "Hz") self.plotWidget.getAxis("left").setLabel("Volts", "V") self.dataPlot = self.plotWidget.plot() # timer used to see if the sensor is responding self.timer = QTimer() self.timer.setInterval(self.sensor_not_responding_timeout) self.timer.timeout.connect(self.timer_timeout) self.timer_timeout() # defines the actions in the file menu with button actions iconExit = QIcon("icons/icon_exit.png") btnActionExit = QAction(iconExit, "Exit", self) btnActionExit.setStatusTip("Click to terminate the program") btnActionExit.triggered.connect(self.exit) iconSave = QIcon("icons/icon_save.ico") buttonActionSave = QAction(iconSave, "Save current set of objects", self) # buttonActionSave.setStatusTip("Click to perform action 2") buttonActionSave.triggered.connect(self.save) iconOpen = QIcon("icons/icon_load.png") buttonActionOpen = QAction(iconOpen, "Load set of objects", self) buttonActionOpen.triggered.connect(self.open) # toolbar toolBar = QToolBar("Toolbar") toolBar.addAction(buttonActionSave) toolBar.addAction(buttonActionOpen) toolBar.setIconSize(QSize(64, 64)) toolBar.setStyleSheet(styles.toolbar) self.addToolBar(toolBar) # menu menuBar = self.menuBar() menuBar.setStyleSheet(styles.menuBar) menuFile = menuBar.addMenu("File") menuOptions = menuBar.addMenu("Options") menuView = menuBar.addMenu("View") menuConnect = menuBar.addMenu("Connect") menuFile.addAction(buttonActionSave) menuFile.addAction(buttonActionOpen) menuFile.addAction(btnActionExit) # status bar self.setStatusBar(QStatusBar(self)) # creates the "My Objects" label labelMyObjects = QLabel("My Objects") labelMyObjects.setFixedHeight(100) labelMyObjects.setAlignment(Qt.AlignCenter) labelMyObjects.setStyleSheet(styles.labelMyObjects) # button "add object" icon_plus = QIcon("icons/icon_add.png") self.btn_create_object = QPushButton("Add Object") self.btn_create_object.setCheckable(False) self.btn_create_object.setIcon(icon_plus) self.btn_create_object.setFixedHeight(80) self.btn_create_object.setStyleSheet(styles.addObjectButton) self.btn_create_object.clicked.connect(self.create_object) # defines the layout of the "My Objects" section self.verticalLayout = QVBoxLayout() self.verticalLayout.setContentsMargins(0,0,0,0) self.verticalLayout.addWidget(labelMyObjects) self.verticalLayout.addWidget(self.btn_create_object) self.spacer = QSpacerItem(0,2000, QSizePolicy.Expanding, QSizePolicy.Expanding) self.verticalLayout.setSpacing(0) self.verticalLayout.addSpacerItem(self.spacer) #adds spacer # defines the ComboBox which holds the names of the objects self.comboBox = QComboBox() self.comboBox.addItem("- no object selected") self.comboBox.currentIndexChanged.connect(self.comboBox_index_changed) self.comboBox.setFixedSize(300, 40) self.comboBox.setStyleSheet(styles.comboBox) self.update_comboBox() # defines the label "Selected Object" (above the comboBox) self.labelComboBox = QLabel() self.labelComboBox.setText("Selected Object:") self.labelComboBox.setStyleSheet(styles.labelComboBox) self.labelComboBox.adjustSize() # vertical layout for the combobox and its label self.VLayoutComboBox = QVBoxLayout() self.VLayoutComboBox.addWidget(self.labelComboBox) self.VLayoutComboBox.addWidget(self.comboBox) # label with the output text (the big one on the right) self.labelClassification = QLabel() self.labelClassification.setText("No interaction detected") self.labelClassification.setFixedHeight(80) self.labelClassification.setStyleSheet(styles.labelClassification) self.labelClassification.adjustSize() HLayoutComboBox = QHBoxLayout() HLayoutComboBox.addLayout(self.VLayoutComboBox) HLayoutComboBox.addSpacerItem(QSpacerItem(1000,0, QSizePolicy.Expanding, QSizePolicy.Expanding)); #adds spacer HLayoutComboBox.addWidget(self.labelClassification) # creates a frame that contains the combobox and the labels frame = QFrame() frame.setStyleSheet(styles.frame) frame.setLayout(HLayoutComboBox) # sets the window layout with the elements created before self.windowLayout = QVBoxLayout() self.windowLayout.addWidget(self.plotWidget) self.windowLayout.addWidget(frame) self.windowLayout.addLayout(self.verticalLayout) # puts everything into a frame and displays it on the window self.mainWindowFrame = QFrame() self.mainWindowFrame.setLayout(self.windowLayout) self.mainWindowFrame.setStyleSheet(styles.mainWindowFrame) self.setCentralWidget(self.mainWindowFrame) self.create_object() # creates one object at the beginning # ----------------------------------------------------------------------------------------------------------- # Shows a welcome message def show_welcome_msg(self): welcome_msg = QMessageBox() welcome_msg.setText("Welcome to the Roots application!") welcome_msg.setIcon(QMessageBox.Information) welcome_msg.setInformativeText(strings.welcome_text) welcome_msg.setWindowTitle("Welcome") welcome_msg.exec_() # ----------------------------------------------------------------------------------------------------------- # When the user changes the object in the combobox, updates the active object def comboBox_index_changed(self, index): object_name = self.comboBox.currentText() for object in self.object_list: if object.name == object_name: self.set_active_object(object) print("DEBUG: selected object changed. Object name: {0}".format(object.name)) return # ----------------------------------------------------------------------------------------------------------- # This function allows to save the current objects on a file def save(self): current_path = os.getcwd() directory_path = current_path + "/Saved_Workspaces" if not os.path.exists(directory_path): os.mkdir(directory_path) file_path = None [file_path, file_extension] = QFileDialog.getSaveFileName(self,"Roots", directory_path, "Roots database (*.db)") if file_path is None: return temp_database_path = current_path + "/" + RootsApp.temporary_database_filename shutil.copyfile(temp_database_path, file_path) # copies the temporary database to save the current workspace return # ----------------------------------------------------------------------------------------------------------- # this function creates a clean database where all the data of this session will be temporarily stored def create_temporary_database(self): current_path = os.getcwd() file_path = current_path + "/" + RootsApp.temporary_database_filename if os.path.exists(file_path): # if the database is already there it deletes it to reset it os.remove(file_path) print("DEBUG: removing database. (in 'RootsApp.create_temporary_database()'") database_connection = database.create_connection(RootsApp.temporary_database_filename) # creates the temporary database database.create_tables(database_connection) # initializes the database database.reset_db(database_connection) # resets the database (not needed but it doesn't cost anything to put it) return database_connection # ----------------------------------------------------------------------------------------------------------- # This function allows to load previously created objects from a file def open(self): current_path = os.getcwd() saved_files_directory = current_path + "/Saved_Workspaces" [file_path, file_extension] = QFileDialog.getOpenFileName(self,"Roots", saved_files_directory, "Roots database (*.db)"); if file_path == '': return for object in self.object_list.copy(): # deletes all the objects print("DEBUG: deleting object {0} (in 'open()')".format(object.name)) self.delete_object(object) temp_database_path = current_path + "/" + RootsApp.temporary_database_filename self.database_connection.close() os.remove(temp_database_path) shutil.copyfile(file_path, temp_database_path) # replaces the temporary database with the file to open self.database_connection = database.create_connection(temp_database_path) object_tuples = database.get_all_objects(self.database_connection) for object_tuple in object_tuples: object_ID, object_name = object_tuple location_IDs = database.get_locations_id_for_object(self.database_connection, object_ID) formatted_location_IDs = [] for location_ID in location_IDs: formatted_location_IDs.append(location_ID[0]) print("DEBUG: loading object {0} with location IDs {1}. (in 'RootsApp.open()')".format(object_name, formatted_location_IDs)) self.add_object(object_name, object_ID, formatted_location_IDs) self.train_classifiers() return # ----------------------------------------------------------------------------------------------------------- # This function updates the ComboBox whenever objects are created, destroyed or the active object has changed def update_comboBox(self): print("DEBUG: repainting ComboBox. (in 'RootsApp.update_comboBox()'") self.comboBox.clear() self.comboBox.addItem("none") for object in self.object_list: self.comboBox.addItem(object.name) self.comboBox.adjustSize() # ----------------------------------------------------------------------------------------------------------- # This is a timer which is restarted every time a measurement is received. If it elapses it means that the sesnor is not connected def timer_timeout(self): print("DEBUG: timer timeout. (in 'RootsApp.timer_timeout()'") self.sensor_not_responding = True self.statusBar().showMessage(strings.sensor_disconnected) self.statusBar().setStyleSheet(styles.statusBarError) self.plotWidget.setTitle("Sensor not connected") # ----------------------------------------------------------------------------------------------------------- # This function creates a new object in the database and then calls the "add_object" function, which adds the newly created object to the application def create_object(self): new_object_name = "Object {0}".format(self.number_of_objects_added + 1) [new_object_ID, location_IDs] = database.create_object(self.database_connection, new_object_name) self.add_object(new_object_name, new_object_ID, location_IDs) # ----------------------------------------------------------------------------------------------------------- # This function deletes an object from the database, and from the application object list. It alsos destroys the object def delete_object(self, object): print("DEBUG: deleting object {0}. (in 'RootsApp.delete_object()')".format(object.ID)) database.delete_object(self.database_connection, object.ID) self.object_list.remove(object) self.verticalLayout.removeItem(object.layout) self.update_comboBox() object.delete() # ----------------------------------------------------------------------------------------------------------- # This function adds an object to the current application. Note that if you want to create an object ex-novo you should call "create_object". This function is useful when loading existing objects from a file def add_object(self, name, object_ID, location_IDs): self.number_of_objects_added += 1 new_object = Object(name, object_ID, location_IDs, self) self.object_list.append(new_object) for ID in location_IDs: # initializes the measurements with 0 if the measurement is empty #print("DEBUG: initializing location ID {0}".format(ID)) measurements = database.get_measurements_for_location(self.database_connection, ID) print("DEBUG: location {0} of object {1} is trained: {2}. (in 'RootsApp.add_object()')".format(ID, new_object.name, database.is_location_trained(self.database_connection, ID))) if len(measurements) == 0: database.save_points(self.database_connection, [0], ID) database.set_location_trained(self.database_connection, ID, "FALSE") elif database.is_location_trained(self.database_connection, ID) == "TRUE": new_object.get_interaction_by_ID(ID).setCalibrated(True) # inserts the newly created object before the "Add Object" button index = self.verticalLayout.indexOf(self.btn_create_object) self.verticalLayout.insertLayout(index, new_object.layout) self.update_comboBox() print("DEBUG: object {0} added. (in 'RootsApp.add_object()')".format(new_object.name)) return # ----------------------------------------------------------------------------------------------------------- # This function takes as input the measurement data and formats it to plot it on the graph def update_graph(self, data): frequency_step = (self.sensor_end_freq - self.sensor_start_freq) / len(data) x_axis = numpy.arange(self.sensor_start_freq, self.sensor_end_freq, frequency_step) formatted_data = numpy.transpose(numpy.asarray([x_axis, data])) self.dataPlot.setData(formatted_data) # ----------------------------------------------------------------------------------------------------------- # This function starts the UDP server that receives the measurements def run_UDP_server(self, UDP_IP, UDP_PORT): self.UDPServer = UDPServer(UDP_IP, UDP_PORT) self.UDPServer.signals.measurementReceived.connect(self.process_measurement) self.threadpool.start(self.UDPServer) # ----------------------------------------------------------------------------------------------------------- # This function changes some global variables to tell the application to save the incoming measurements into the database. The measurements belong to the interaction passed as argument def start_collecting_measurements(self, interaction): if self.sensor_not_responding: print("DEBUG: warning! Can't start calibration, the sensor is not responding! (in 'RootsApp.start_collecting_measurements()')") error_msg = QMessageBox() error_msg.setText("Can't start calibration!") error_msg.setIcon(QMessageBox.Critical) error_msg.setInformativeText('The sensor is not responding, make sure it is connected') error_msg.setWindowTitle("Error") error_msg.exec_() else: print("starting to collect measurements into the database at location ID {0} (in 'RootsApp.start_collecting_measurements()')".format(interaction.ID)); self.is_training_on = True self.interaction_under_training = interaction database.delete_measurements_from_location(self.database_connection, interaction.ID) # resets the location measurements self.progress_dialog = QProgressDialog("Calibrating", "Abort", 0, self.n_measurements_to_collect, self) self.progress_dialog.setWindowModality(Qt.WindowModal) self.progress_dialog.setWindowTitle("Calibration") self.progress_dialog.setFixedSize(400, 200) self.progress_dialog.setValue(0) self.progress_dialog.exec_() # ----------------------------------------------------------------------------------------------------------- # This function is called by the UDP thread every time that a measurement is received. It does the following: # 1. Plots the incoming measurement # 2. IF training mode IS on: # Predicts the interaction (tries to guess where the user is touching) # ELSE: # Saves the measurement and retrains the classifier with the new data def process_measurement(self, received_data): self.sensor_not_responding = False self.plotWidget.setTitle("Sensor response") self.timer.start() # starts the timer that checks if we are receiving data from the sensor measurement = received_data.split(' ') # get rid of separator measurement = [float(i) for i in measurement] # convert strings to float self.update_graph(measurement) self.predict_interaction(measurement) # checks the standard deviation of the received data to see if the sensor is working well if (numpy.std(measurement) < self.standard_deviation_threshold): self.statusBar().showMessage(strings.sensor_not_working) self.statusBar().setStyleSheet(styles.statusBarError) else: self.statusBar().setStyleSheet(styles.statusBar) if self.is_training_on: print("saving measurement {0} into database at location_ID {1}. (in 'RootsApp.process_measurement()')".format(self.n_measurements_collected + 1, self.interaction_under_training.ID)) database.save_points(self.database_connection, measurement, self.interaction_under_training.ID) self.n_measurements_collected += 1 self.progress_dialog.setValue(self.n_measurements_collected) if (self.n_measurements_collected >= self.n_measurements_to_collect): self.is_training_on = False self.n_measurements_collected = 0 print("DEBUG: {0} measurements were saved at location_ID {1}. (in 'RootsApp.process_measurement()')".format(self.n_measurements_to_collect, self.interaction_under_training.ID)) self.train_classifiers() self.interaction_under_training.setCalibrated(True) # this makes the button "Calibrate" change coulour # ----------------------------------------------------------------------------------------------------------- # This function retrains the classifiers using all the measurements present in the database and assigns to each object its classifier def train_classifiers(self): #[objects_ID, classifiers] classifiers = classifier.get_classifiers(self.database_connection) print("DEBUG: the following classifiers were created: {0}. (in 'RootsApp.train_classifiers')".format(classifiers)) for object in self.object_list: for index, tuple in enumerate(classifiers): object_ID, classif = tuple; # extracts the object ID and the classifier from the tuple if object_ID == object.ID: object.classifier = classif del classifiers[index] # ----------------------------------------------------------------------------------------------------------- # This function changes the current active object (the software tries to guess where the user is touching using the calibration data from the active object) def set_active_object(self, active_object): self.active_object = active_object for obj in self.object_list: if obj == active_object: active_object.set_highlighted(True) else: obj.set_highlighted(False) index = self.comboBox.findText(self.active_object.name) # updates the index of the ComboBox self.comboBox.setCurrentIndex(index) # ----------------------------------------------------------------------------------------------------------- # This function changes the name of an object. It updates the database AND the application data structure. def rename_object(self, object, new_name): print("DEBUG: changing name of object '{0}' (in 'RootsApp.rename_object')".format(object.name)) object.set_name(new_name) database.rename_object(self.database_connection, object.ID, new_name) self.update_comboBox() # ----------------------------------------------------------------------------------------------------------- # This function uses the classifier of the active object to guess where the user is touching, based on the incoming measurement def predict_interaction(self, measurement): if (len(self.object_list) <= 0): self.labelClassification.setText("No objects available") self.statusBar().showMessage(strings.no_objects) return if self.active_object is None: self.labelClassification.setText("No object selected") self.statusBar().showMessage(strings.no_object_selected) return if self.active_object.classifier is None: self.labelClassification.setText("The object is not calibrated") self.statusBar().showMessage(strings.object_not_calibrated) return else: predicted_interaction_id = self.active_object.classifier(measurement) interaction = self.active_object.get_interaction_by_ID(predicted_interaction_id) self.labelClassification.setText(interaction.name) self.statusBar().showMessage("") #print("DEBUG: predicted interaction ID: ", interaction.ID) # ----------------------------------------------------------------------------------------------------------- # This is a system event that gets called whenever the user tries to close the application. It calls the "exit()" # function (just below) to open a dialog to make sure the user really wants to quit. def closeEvent(self, event): if not self.exit(): event.ignore() # ----------------------------------------------------------------------------------------------------------- # This function gets called when the user cliks on the "Exit" button in the "File" menu or when it tries to close the window (indirectly) # Here we open a dialog to make sure the user really wants to quit. def exit(self): dialogWindow = DialogExit() answer = dialogWindow.exec_() if (answer == True): self.UDPServer.stop() self.close() return answer
def executeProject(self, mainWindow, project_name): if project_name == "SmartGroceries": self.referenceItemsLabels = {} self.itemInHand = None self.itemsInCart = list() self.currentOrderReferenceItems = list() self.currentOrderUserId = 0 self.recommendedItemsButtons = list() self.previousRecommendations = list() self.numberOfGoodRecommendations = 0 self.supportedUsers = range(1, 1001) products = pd.read_csv( os.path.join( os.path.dirname(os.path.dirname( os.path.abspath(__file__))), "projects", "SmartGroceries", "data", "products.csv")) aisles = pd.read_csv( os.path.join( os.path.dirname(os.path.dirname( os.path.abspath(__file__))), "projects", "SmartGroceries", "data", "aisles.csv")) test_data = pd.read_csv( os.path.join( os.path.dirname(os.path.dirname( os.path.abspath(__file__))), "projects", "SmartGroceries", "data", "test_set.csv")) # print(products) # print(aisles) # print(test_data) aisle_name_to_id = { k: v for k, v in zip(aisles.aisle, aisles.aisle_id) } product_name_to_id = { k: v for k, v in zip(products.product_name, products.product_id) } product_id_to_name = { k: v for k, v in zip(products.product_id, products.product_name) } def changeCurrentitem(itemName): if itemName == "": self.itemInHand = None currentItemLabel.setText( f"<b>Select an item from the list or from the recommendations</b>s" ) addToCartButton.setEnabled(False) else: self.itemInHand = product_name_to_id[itemName] currentItemLabel.setText(f"Add <b>{itemName}</b> to Cart") addToCartButton.setEnabled(True) addToCartButton.setFocus() def handleNewOrderButtonClicked(): grouped = test_data.groupby('order_id') while True: order_number = random.sample(grouped.indices.keys(), 1)[0] currentOrder = grouped.get_group(order_number) self.currentOrderReferenceItems = currentOrder.product_id.tolist( ) self.currentOrderUserId = currentOrder.user_id.iloc[0] if len( self.currentOrderReferenceItems ) > 1 and self.currentOrderUserId in self.supportedUsers: break print(self.currentOrderReferenceItems) orderInfo = f"<b>Order ID: </b>{currentOrder.order_id.iloc[0]}<br>" orderInfo += f"<b>User ID: </b>{self.currentOrderUserId} | <b>DOW: </b>{calendar.day_name[currentOrder.order_dow.iloc[0]]} | <b>Hour of Day: </b>{currentOrder.order_hour_of_day.iloc[0]} | <b>Number of Items: </b>{len(self.currentOrderReferenceItems)}" orderInfo += "<br><b>Items in the Reference Order:</b>" for widget in self.referenceItemsLabels.values(): item = referenceItemsLayout.itemAt(0) widget.setVisible(False) referenceItemsLayout.removeItem(item) del item self.referenceItemsLabels.clear() currentCartItems.clear() self.itemsInCart.clear() self.previousRecommendations.clear() self.numberOfGoodRecommendations = 0 updateCurrentRecommendations(list()) for product in self.currentOrderReferenceItems: refItemName = product_id_to_name[product] refItemLabel = QPushButton(refItemName) refItemLabel.setContentsMargins(QMargins(0, 0, 0, 0)) refItemLabel.setStyleSheet("Text-align:left") refItemLabel.setFlat(False) refItemLabel.clicked.connect( partial(changeCurrentitem, refItemName)) self.referenceItemsLabels[product] = refItemLabel orderInfoLabel.setText( f"<b>Order Information</b><br>{orderInfo}") for referenceItemLabel in self.referenceItemsLabels.values(): referenceItemsLayout.addWidget(referenceItemLabel) runAutoButton.setFocus() def handleRunAutomatically(): for referenceItemLabel in self.referenceItemsLabels.values(): referenceItemLabel.click() addToCartButton.click() def updateCurrentRecommendations(recommendations): for widget in self.recommendedItemsButtons: item = recommendationsLayout.itemAt(0) widget.setVisible(False) recommendationsLayout.removeItem(item) del item self.recommendedItemsButtons.clear() for product in recommendations: recItemName = product_id_to_name[product] recItemButton = QPushButton(recItemName) recItemButton.setContentsMargins(QMargins(0, 0, 0, 0)) recItemButton.setStyleSheet("Text-align:left;") if product not in self.currentOrderReferenceItems: recItemButton.setFlat(True) recItemButton.clicked.connect( partial(changeCurrentitem, recItemName)) self.recommendedItemsButtons.append(recItemButton) for recItemButton in self.recommendedItemsButtons: recommendationsLayout.addWidget(recItemButton) if len(recommendations) > 0: currentRecommendationsLabel.setVisible(True) else: currentRecommendationsLabel.setVisible(False) self.previousRecommendations += recommendations def handleAddToCartButtonClicked(): print(self.currentOrderReferenceItems) print(self.itemInHand) if self.itemInHand not in self.currentOrderReferenceItems: QMessageBox( QMessageBox.Critical, "Error adding item to cart", "You can only add items that exists in the reference order" ).exec_() return elif self.itemInHand in self.itemsInCart: QMessageBox(QMessageBox.Critical, "Error adding item to cart", "This item is already in the cart").exec_() return self.referenceItemsLabels[self.itemInHand].setFlat(True) self.itemsInCart.append(self.itemInHand) currentCartItems.addItem(product_id_to_name[self.itemInHand]) if self.itemInHand in self.previousRecommendations: self.numberOfGoodRecommendations += 1 self.referenceItemsLabels[self.itemInHand].setStyleSheet( "Text-align:left; background-color:green;") self.referenceItemsLabels[self.itemInHand].setFlat(False) #update recommendations result = self._sendCommand( "PROCESS_PROJECT_GROUP_2", ";".join([ str(self.currentOrderUserId), ",".join([str(x) for x in self.itemsInCart]), ",".join([ str(x) for x in set(self.previousRecommendations) ]) ])) if result == FAILURE_CODE: self.log( "Processing Failed, error getting recommendations from the RPi" ) return else: try: recommendations = [int(id) for id in result.split(',')] except: recommendations = [] updateCurrentRecommendations(recommendations) if len(self.itemsInCart) == len( self.currentOrderReferenceItems): completionMessage = QMessageBox( QMessageBox.Information, "Order Completed", f"Order Completed with {self.numberOfGoodRecommendations} Good Recommendation(s)\nPress New Order to start a new order" ) if self.numberOfGoodRecommendations == 0: completionMessage.setIconPixmap( QPixmap('images/this_is_fine.jpg')) completionMessage.setWindowIcon(appIcon) completionMessage.exec_() newOrderButton.setFocus() def aisleChanged(): aisle_number = aisle_name_to_id[ selectAisleCombobox.currentText()] products_in_aisle = products[ products.aisle_id == aisle_number].product_name.tolist() selectproductCombobox.clear() selectproductCombobox.addItem("") selectproductCombobox.addItems(products_in_aisle) def itemChanged(): current_item = selectproductCombobox.currentText() changeCurrentitem(current_item) dialog = QDialog(mainWindow) appIcon = QIcon("images/this_is_fine.jpg") dialog.setWindowIcon(appIcon) dialog.setMinimumWidth(600) dialog.setWindowTitle("Smart Groceries Demo") layout = QVBoxLayout() newOrderButton = QPushButton("New Order") orderInfoLabel = QLabel() orderInfoLabel.setTextFormat(Qt.RichText) chooseItemLayout = QHBoxLayout() verticalSpacer = QSpacerItem(20, 20) currentCartItems = QListWidget() layoutWidget = QWidget() referenceItemsLayout = QVBoxLayout(layoutWidget) referenceItemsLayout.setSpacing(0) referenceItemsLayout.setMargin(0) scroll = QScrollArea(dialog) scroll.setWidgetResizable(True) scroll.setMinimumHeight(150) scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) scroll.setWidget(layoutWidget) selectAisleLabel = QLabel("Aisle: ") selectProductLabel = QLabel("Product: ") selectAisleCombobox = QComboBox() selectproductCombobox = QComboBox() chooseItemLayout.addWidget(selectAisleLabel, 0, Qt.AlignLeft) chooseItemLayout.addWidget(selectAisleCombobox, 0, Qt.AlignLeft) chooseItemLayout.addWidget(selectProductLabel, 0, Qt.AlignLeft) chooseItemLayout.addWidget(selectproductCombobox, 0, Qt.AlignLeft) addToCartButton = QPushButton("Add to Cart") currentItemLabel = QLabel() currentItemLabel.setTextFormat(Qt.RichText) if self.itemInHand is None: currentItemLabel.setText( f"<b>Select an item from the list or from the recommendations</b>" ) addToCartButton.setDisabled(True) currentItemLayout = QHBoxLayout() currentItemLayout.addWidget(currentItemLabel) currentItemLayout.addWidget(addToCartButton) recommendationsLayout = QVBoxLayout() recommendationsLayout.setSpacing(0) recommendationsLayout.setMargin(0) newOrderButton.clicked.connect(handleNewOrderButtonClicked) addToCartButton.clicked.connect(handleAddToCartButtonClicked) selectproductCombobox.currentIndexChanged.connect(itemChanged) selectAisleCombobox.currentIndexChanged.connect(aisleChanged) selectAisleCombobox.addItems(aisles.aisle.tolist()) layout.addWidget(newOrderButton) layout.addSpacerItem(verticalSpacer) layout.addWidget(orderInfoLabel) layout.addWidget(scroll) layout.addSpacerItem(verticalSpacer) layout.addLayout(chooseItemLayout) layout.addSpacerItem(verticalSpacer) itemsInTheCartLabel = QLabel("<b>Items in the Cart<b>") layout.addWidget(itemsInTheCartLabel) itemsInTheCartLabel.setTextFormat(Qt.RichText) layout.addWidget(currentCartItems) layout.addSpacerItem(verticalSpacer) currentRecommendationsLabel = QLabel( "<b>Current Recommendations<b>") layout.addWidget(currentRecommendationsLabel) currentRecommendationsLabel.setTextFormat(Qt.RichText) currentRecommendationsLabel.setVisible(False) layout.addLayout(recommendationsLayout) layout.addSpacerItem(verticalSpacer) layout.addLayout(currentItemLayout) runAutoButton = QPushButton("Run and Watch. TRUST ME, IT IS FUN!") layout.addWidget(runAutoButton) runAutoButton.clicked.connect(handleRunAutomatically) dialog.setLayout(layout) handleNewOrderButtonClicked() dialog.exec_() return