class LocationCompleterView(QWidget): def __init__(self): super().__init__(None) self._view = None # QListView self._delegate = None # LocationCompleterDelegate self._searchEnginesLayout = None # QHBoxLayout self._resizeHeight = -1 self._resizeTimer = None # QTimer self._forceResize = True self.setAttribute(Qt.WA_ShowWithoutActivating) self.setAttribute(Qt.WA_X11NetWmWindowTypeCombo) if gVar.app.platformName() == 'xcb': self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.BypassWindowManagerHint) else: self.setWindowFlags(Qt.Popup) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self._view = QListView(self) layout.addWidget(self._view) self._view.setUniformItemSizes(True) self._view.setEditTriggers(QAbstractItemView.NoEditTriggers) self._view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self._view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setSelectionBehavior(QAbstractItemView.SelectRows) self._view.setSelectionMode(QAbstractItemView.SingleSelection) self._view.setMouseTracking(True) gVar.app.installEventFilter(self) self._delegate = LocationCompleterDelegate(self) self._view.setItemDelegate(self._delegate) searchFrame = QFrame(self) searchFrame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) searchLayout = QHBoxLayout(searchFrame) searchLayout.setContentsMargins(10, 4, 4, 4) searchSettingsButton = ToolButton(self) searchSettingsButton.setIcon(IconProvider.settingsIcon()) searchSettingsButton.setToolTip(_('Manage Search Engines')) searchSettingsButton.setAutoRaise(True) searchSettingsButton.setIconSize(QSize(16, 16)) searchSettingsButton.clicked.connect(self.searchEnginesDialogRequested) searchLabel = QLabel(_('Search with:')) self._searchEnginesLayout = QHBoxLayout() self._setupSearchEngines() gVar.app.searchEnginesManager().enginesChanged.connect( self._setupSearchEngines) searchLayout.addWidget(searchLabel) searchLayout.addLayout(self._searchEnginesLayout) searchLayout.addStretch() searchLayout.addWidget(searchSettingsButton) layout.addWidget(searchFrame) def model(self): ''' @return: QAbstractItemModel ''' return self._view.model() def setModel(self, model): ''' @param model QAbstractItemModel ''' self._view.setModel(model) def selectionModel(self): ''' @return: QItemSelectionModel ''' return self._view.selectionModel() def currentIndex(self): ''' @return: QModelIndex ''' return self._view.currentIndex() def setCurrentIndex(self, index): ''' @param index QModelIndex ''' self._view.setCurrentIndex(index) def adjustSize(self): maxItemsCount = 12 newHeight = self._view.sizeHintForRow(0) * min(maxItemsCount, self.model().rowCount()) if not self._resizeTimer: self._resizeTimer = QTimer(self) self._resizeTimer.setInterval(200) def func(): if self._resizeHeight > 0: self._view.setFixedHeight(self._resizeHeight) self.setFixedHeight(self.sizeHint().height()) self._resizeHeight = -1 self._resizeTimer.timeout.connect(func) if not self._forceResize: if newHeight == self._resizeHeight: return elif newHeight == self._view.height(): self._resizeHeight = -1 return elif newHeight < self._view.height(): self._resizeHeight = newHeight self._resizeTimer.start() return self._resizeHeight = -1 self._forceResize = False self._view.setFixedHeight(newHeight) self.setFixedHeight(self.sizeHint().height()) # override def eventFilter(self, obj, event): # noqa C901 ''' @param obj QObject @param event QEvent @return: bool ''' # Event filter based on QCompleter::eventFilter from qcompleter.cpp if obj == self or obj == self._view or not self.isVisible(): return False evtType = event.type() if obj == self._view.viewport(): if evtType == QEvent.MouseButtonRelease: # QMouseEvent e = event index = self._view.indexAt(e.pos()) if not index.isValid(): return False # Qt::MouseButton button = e.button() # Qt::KeyboardModifiers modifiers = e.modifiers() if button == Qt.LeftButton and modifiers == Qt.NoModifier: self.indexActivated.emit(index) return True if button == Qt.MiddleButton or (button == Qt.LeftButton and modifiers == Qt.ControlModifier): self.indexCtrlActivated.emit(index) return True if button == Qt.LeftButton and modifiers == Qt.ShiftModifier: self.indexShiftActivated.emit(index) return True return False if evtType == QEvent.KeyPress: # QKeyEvent keyEvent = event evtKey = keyEvent.key() modifiers = keyEvent.modifiers() index = self._view.currentIndex() item = self.model().index(0, 0) if item.data(LocationCompleterModel.VisitSearchItemRole): visitSearchIndex = item else: visitSearchIndex = QModelIndex() if (evtKey == Qt.Key_Up or evtKey == Qt.Key_Down) and \ self._view.currentIndex() != index: self._view.setCurrentIndex(index) # TODO: ? if evtKey in (Qt.Key_Return, Qt.Key_Enter): if index.isValid(): if modifiers == Qt.NoModifier or modifiers == Qt.KeypadModifier: self.indexActivated.emit(index) return True if modifiers == Qt.ControlModifier: self.indexCtrlActivated.emit(index) return True if modifiers == Qt.ShiftModifier: self.indexShiftActivated.emit(index) return True elif evtKey == Qt.Key_End: if modifiers & Qt.ControlModifier: self._view.setCurrentIndex(self.model().index( self.model().rowCount() - 1, 0)) return True else: self.close() elif evtKey == Qt.Key_Home: if modifiers & Qt.ControlModifier: self._view.setCurrentIndex(self.model().index(0, 0)) self._view.scrollToTop() return True else: self.close() elif evtKey == Qt.Key_Escape: self.close() return True elif evtKey == Qt.Key_F4: if modifiers == Qt.AltModifier: self.close() return False elif evtKey in (Qt.Key_Tab, Qt.Key_Backtab): if modifiers != Qt.NoModifier and modifiers != Qt.ShiftModifier: return False isBack = evtKey == Qt.Key_Backtab if evtKey == Qt.Key_Tab and modifiers == Qt.ShiftModifier: isBack = True ev = QKeyEvent(QKeyEvent.KeyPress, isBack and Qt.Key_Up or Qt.Key_Down, Qt.NoModifier) QApplication.sendEvent(self.focusProxy(), ev) return True elif evtKey in (Qt.Key_Up, Qt.Key_PageUp): if modifiers != Qt.NoModifier: return False step = evtKey == Qt.Key_PageUp and 5 or 1 if not index.isValid() or index == visitSearchIndex: rowCount = self.model().rowCount() lastIndex = self.model().index(rowCount - 1, 0) self._view.setCurrentIndex(lastIndex) elif index.row() == 0: self._view.setCurrentIndex(QModelIndex()) else: row = max(0, index.row() - step) self._view.setCurrentIndex(self.model().index(row, 0)) return True elif evtKey in (Qt.Key_Down, Qt.Key_PageDown): if modifiers != Qt.NoModifier: return False step = evtKey == Qt.Key_PageDown and 5 or 1 if not index.isValid(): firstIndex = self.model().index(0, 0) self._view.setCurrentIndex(firstIndex) elif index != visitSearchIndex and index.row( ) == self.model().rowCount() - 1: self._view.setCurrentIndex(visitSearchIndex) self._view.scrollToTop() else: row = min(self.model().rowCount() - 1, index.row() + step) self._view.setCurrentIndex(self.model().index(row, 0)) return True elif evtKey == Qt.Key_Delete: if index != visitSearchIndex and self._view.viewport().rect( ).contains(self._view.visualRect(index)): self.indexDeleteRequested.emit(index) return True elif evtKey == Qt.Key_Shift: self._delegate.setForceVisitItem(True) self._view.viewport().update() # end of switch evtKey if self.focusProxy(): self.focusProxy().event(keyEvent) return True elif evtType == QEvent.KeyRelease: if event.key() == Qt.Key_Shift: self._delegate.setForceVisitItem(False) self._view.viewport().update() return True elif evtType in (QEvent.Wheel, QEvent.MouseButtonPress): if not self.underMouse(): self.close() return False elif evtType == QEvent.FocusOut: # QFocusEvent focusEvent = event reason = focusEvent.reason() if reason != Qt.PopupFocusReason and reason != Qt.MouseFocusReason: self.close() elif evtType in (QEvent.Move, QEvent.Resize): w = obj if isinstance(w, QWidget) and w.isWindow() and self.focusProxy( ) and w == self.focusProxy().window(): self.close() # end of switch evtType return False # Q_SIGNALS closed = pyqtSignal() searchEnginesDialogRequested = pyqtSignal() loadRequested = pyqtSignal(LoadRequest) indexActivated = pyqtSignal(QModelIndex) indexCtrlActivated = pyqtSignal(QModelIndex) indexShiftActivated = pyqtSignal(QModelIndex) indexDeleteRequested = pyqtSignal(QModelIndex) # public Q_SLOTS: def close(self): self.hide() self._view.verticalScrollBar().setValue(0) self._delegate.setForceVisitItem(False) self._forceResize = True self.closed.emit() # private: def _setupSearchEngines(self): for idx in (range(self._searchEnginesLayout.count())): item = self._searchEnginesLayout.takeAt(0) item.deleteLater() engines = gVar.app.searchEnginesManager().allEngines() for engine in engines: button = ToolButton(self) button.setIcon(engine.icon) button.setToolTip(engine.name) button.setAutoRaise(True) button.setIconSize(QSize(16, 16)) def func(): text = self.model().index(0, 0).data( LocationCompleterModel.SearchStringRole) self.loadRequested.emit( gVar.app.searchEngineManager().searchResult(engine, text)) button.clicked.connect(func) self._searchEnginesLayout.addWidget(button)
class FiloIOTab(QWidget): '''This class houses all the set widgets used in the fileio tab. This class contains 4 attributes that are event-driven type of attribute. Attributes: import_folder_path_selected_state (str): A string message that tells whether user selected a folder to read .csv from file_from_folder_imported_state (str): A string message tells if user had determined to import .csv from the imported directory. export_folder_path_selected_state (str): A string message tells if user had determined a output file directory to save graphs save_figs (bool): A boolean tells other tabs if the figure needed to be saved offline. ''' # Class attributes import_folder_path_selected_state = pyqtSignal(str) file_from_folder_imported_state = pyqtSignal(str) export_folder_path_selected_state = pyqtSignal(str) save_figs = pyqtSignal(bool) def __init__(self): '''constructor function of the FiloIO tab. ''' super().__init__() # Instance variables self.import_folder_path = QDir.rootPath() self.export_folder_path = QDir.rootPath() # signal indicating if something has been successfuly # or unsuccessfully operated # define outer sturcture of this tab self.fileio_layout = QVBoxLayout() # file io tab label self.fileio_tab_title_label = QLabel() self.fileio_tab_title_label.setFont(QFont('Arial', 10)) self.fileio_tab_title_label.setText('<b>File IO Tab</b>') self.fileio_layout.addWidget(self.fileio_tab_title_label) # define the inner structure below the header self.fileio_tab_import_layout_grid = QGridLayout() # widget 1: fileio select folder widget self.fileio_import_select_folder_widgets() # widget 2: fileio import csv files from folder widget self.fileio_import_file_from_folder_widgets() # widget 3: fileio import configuration file widget # TODO: Unfinished # Import Configuration Parameters self.fileio_tab_import_configuration_layout = QVBoxLayout() # Import Configuration File PushButton self.fileio_tab_import_configuration_pushbutton = \ QPushButton("Select Config File") # Import Configuration Confirm PushButton self.fileio_tab_import_config_confirm_pushbutton = \ QPushButton("Import Config File") # Import Configuration State self.fileio_tab_import_config_label = QLabel('No Config Imported') self.fileio_tab_import_config_label.setAlignment(Qt.AlignCenter) # Application adding the import parameter widgets onto the application self.fileio_import_addwidget() # Output Directory Parameters self.fileio_tab_export_title_label = QLabel() self.fileio_tab_export_title_label.\ setFont(QFont('Arial', 10)) self.fileio_tab_export_title_label.\ setText('<b>Output Directory Parameters: </b>') self.fileio_layout.addWidget(self.fileio_tab_export_title_label) # Add in the grid for output self.fileio_tab_output_layout_grid = QGridLayout() # Add in the widgets for the export parameters self.fileio_export_select_folder_widgets() # Select directory to output to self.fileio_export_addwidget() self.setLayout(self.fileio_layout) def fileio_import_select_folder_widgets(self): """Initialize the widgets for folder selection. """ # File select button self.fileio_tab_select_folder_pushbutton = QPushButton('Select Folder') self.fileio_tab_select_folder_pushbutton.\ clicked.connect(self.fileio_import_select_folder_directory) self.import_folder_path_selected_state.\ connect(self.folder_path_enable_import_file_widgets) # File selected directory display box, for display only self.fileio_tab_select_folder_lineedit = QLineEdit('') self.fileio_tab_select_folder_lineedit.setReadOnly(True) def fileio_import_select_folder_directory(self): '''This function opens a file dialogue, and it will ask user to select a folder to open, and if the folder is imported then, it search for the .csv just in that folder. ''' self.fileio_tab_import_selected_pushbutton.setEnabled(False) # configure the input file dialog, dialog window title options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog dialog_box_title = 'Select Directory to Read CSV' self.import_folder_path = QFileDialog.\ getExistingDirectory(self, dialog_box_title) # save the folder path if successfully selected if self.import_folder_path: self.import_folder_path_selected_state.\ emit("Folder selection is successful") @pyqtSlot(str) def folder_path_enable_import_file_widgets(self, message): """This method is called when a folder is successfully being imported. And this is where user is allowed to load in and select the .csv from that folder Args: message (str): To check if folder selection is done correctly """ if message == "Folder selection is successful": print(message) self.fileio_tab_select_folder_lineedit.\ setText(self.import_folder_path) self.fileio_tab_import_selected_pushbutton.setEnabled(True) self.fileio_tab_import_selected_label.\ setText("Import Status: <b>Unimported</b>") def fileio_import_file_from_folder_widgets(self): """This method initialize the import file from folder widgets """ # Import selected file push button self.fileio_tab_import_selected_pushbutton = \ QPushButton('Import File in this folder') self.fileio_tab_import_selected_pushbutton.setFixedWidth(250) self.fileio_tab_import_selected_pushbutton.setEnabled(False) self.file_from_folder_imported_state.\ connect(self.fileio_import_enable_csv_file_selection) # Import Status # TODO: Add a progress bar, and show a dialogue upon finishes self.fileio_tab_import_selected_label = QLabel() self.fileio_tab_import_selected_label.setFont(QFont('Arial', 10)) self.fileio_tab_import_selected_label.\ setText('Import Status: <b>Unimported</b>') # Import selected file tableview self.fileio_tab_import_selected_table = QListView() self.fileio_tab_import_selected_table.setFixedHeight(200) self.fileio_tab_import_selected_pushbutton.\ clicked.connect(self.fileio_import_files_from_selected_folder) self.fileio_tab_import_selected_fileModel = QFileSystemModel() # List out the files in that folder that contains csv self.fileio_tab_import_selected_fileModel.\ setFilter(QDir.NoDotAndDotDot | QDir.Files) CSV_FILTER = ["*.csv"] self.fileio_tab_import_selected_fileModel.\ setNameFilters(CSV_FILTER) # show only the file with the extension .csv self.fileio_tab_import_selected_fileModel.\ setNameFilterDisables(False) # TODO: add in method that convert csv to a valid dataframe def fileio_import_files_from_selected_folder(self): """This method opens and grab all the .csv files that are found in the folder that user selected. """ self.fileio_tab_import_selected_table.\ setModel(self.fileio_tab_import_selected_fileModel) # store the file path if (self.import_folder_path != ""): self.file_from_folder_imported_state.\ emit("Successful file from folder import") @pyqtSlot(str) def fileio_import_enable_csv_file_selection(self, message): """This method is called when files from the folder were successfully displayed in the box. And now the user is allowed to select the .csv file they wish to analyze. Args: message (str): To check if file import is done correctly """ if message == "Successful file from folder import": print(message) folder_path = self.import_folder_path self.fileio_tab_import_selected_table.\ setRootIndex(self.fileio_tab_import_selected_fileModel. setRootPath(folder_path)) self.fileio_tab_import_selected_label.\ setText("Import Status: <b>Imported</b>") def fileio_export_select_folder_widgets(self): # checkbox to enable output graphing plots or not self.fileio_tab_export_checkbox = QCheckBox("Save output offline") # Method to respond to checkbox state self.fileio_tab_export_checkbox.stateChanged.\ connect(self.fileio_export_file_checkbox_figure_bool) # Determine if it's appropriate to save fig self.save_figs.connect(self.save_fig_state) # Select directory to output to self.fileio_tab_output_select_directory_pushbutton = \ QPushButton("Save Plot To Folder...") self.fileio_tab_output_select_directory_pushbutton.setFixedWidth(250) self.fileio_tab_output_select_directory_pushbutton.clicked.\ connect(self.fileio_export_select_folder_directory) # Update export directory lineedit self.export_folder_path_selected_state.\ connect(self.fileio_export_directory_update) self.fileio_tab_output_select_directory_lineedit = QLineEdit('') self.fileio_tab_output_select_directory_lineedit.setReadOnly(True) def fileio_export_select_folder_directory(self): """Generate a openfile dialogue for user to select a directory path that they wish to save their figure to. This method only handle selecting a path, but not determine whether the program will save it to the directory. """ options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog dialog_box_title = 'Select Output Directory to Export Graph' self.export_folder_path = QFileDialog.\ getExistingDirectory(self, dialog_box_title) # save the folder path if successfully selected if (self.export_folder_path) and (self.export_folder_path != ''): print("Output Directory: ", self.export_folder_path) self.export_folder_path_selected_state.\ emit("Output Directory Determined") @pyqtSlot(str) def fileio_export_directory_update(self, message): """This is the method that update the lineedit of the export file parameter when user select a folder to export the file to. Args: message (str): Check if there's changes to the export directory """ if message == "Output Directory Determined": print("Output Directory Printed on Lineedit") self.fileio_tab_output_select_directory_lineedit.\ setText(self.export_folder_path) def fileio_export_file_checkbox_figure_bool(self): """This method will tell whether outputting figure to a specific directory is valid. Only when the boolean here emit a true, that the program will allow the figure generated by this program to be saved. """ if self.fileio_tab_export_checkbox.isChecked(): if (self.fileio_tab_output_select_directory_lineedit.text() == ''): self.fileio_export_file_checkbox_uncheck() self.save_figs.emit(False) else: self.save_figs.emit(True) else: self.save_figs.emit(False) def fileio_export_file_checkbox_uncheck(self): """This method is called to uncheck the save output checkbox if user attempt to save output offline when the directory path is not given appropriately """ print("Empty directory, cannot be saved offline") self.fileio_tab_export_checkbox.setCheckState(False) @pyqtSlot(bool) def save_fig_state(self, box_checked): """This method will be further added with detail on telling the neighborig tab whether to run the line that will save the figure offline. Args: box_checked (bool): To save or not save the figure """ if box_checked: print("Figure will be saved") else: print("Figure will not be saved") def fileio_import_addwidget(self): """This method adds all the widgets for the import parameter section. """ self.fileio_tab_import_configuration_layout.\ addWidget(self.fileio_tab_import_configuration_pushbutton) self.fileio_tab_import_configuration_layout.\ addWidget(self.fileio_tab_import_config_confirm_pushbutton) self.fileio_tab_import_configuration_layout.\ addWidget(self.fileio_tab_import_config_label) # Add all import related widgets self.fileio_tab_import_layout_grid.\ addWidget(self.fileio_tab_select_folder_pushbutton, 0, 0) self.fileio_tab_import_layout_grid.\ addWidget(self.fileio_tab_select_folder_lineedit, 0, 1) self.fileio_tab_import_layout_grid.\ addWidget(self.fileio_tab_import_selected_pushbutton, 1, 0) self.fileio_tab_import_layout_grid.\ addWidget(self.fileio_tab_import_selected_label, 1, 1) self.fileio_tab_import_layout_grid.\ addLayout(self.fileio_tab_import_configuration_layout, 2, 0, Qt.AlignVCenter) self.fileio_tab_import_layout_grid.\ addWidget(self.fileio_tab_import_selected_table, 2, 1) self.fileio_layout.addLayout(self.fileio_tab_import_layout_grid) self.fileio_layout.addStretch(20) def fileio_export_addwidget(self): """This section adds all widgets for the export parameter section. """ # Add in all export related widgets self.fileio_tab_output_layout_grid.\ addWidget(self.fileio_tab_export_checkbox, 0, 0) self.fileio_tab_output_layout_grid.\ addWidget(self.fileio_tab_output_select_directory_pushbutton, 1, 0) self.fileio_tab_output_layout_grid.\ addWidget(self.fileio_tab_output_select_directory_lineedit, 1, 1) self.fileio_layout.addLayout(self.fileio_tab_output_layout_grid)