def __init__(self, settings_ini: (str, SettingsINI), parent=None): """ :param settings_ini: (str, SettingsINI) can be a settings_ini file path or configured SettingsINI object. """ self.df_manager = DataFrameModelManager() QtGui.QMainWindow.__init__(self, parent=parent) self.setupUi(self) self.icons = Icons() self.dialog_settings = SettingsDialog(settings=settings_ini) self.dialog_merge_purge = MergePurgeDialog(self.df_manager) self.dialog_export = DataFrameModelExportDialog(self.df_manager, parent=self) self.dialog_import = DataFrameModelImportDialog(self.df_manager, parent=self) self.dialog_new_folder = DirectoryPathCreateDialog(self.treeView, parent=self) self.dialog_cloud = None self.key_delete = QtGui.QShortcut(self) self.key_enter = QtGui.QShortcut(self) self.key_zip = QtGui.QShortcut(self) self.key_rename = QtGui.QShortcut(self) self.connect_window_title() self.connect_actions() self.connect_treeview() self.connect_icons() self.connect_settings_dialog() self.connect_import_dialog() self.connect_export_dialog() self.connect_cloud_dialog() self.current_model = None
def dialog(self, qtbot, example_file_path) -> DataFrameModelImportDialog: dfm = DataFrameModelManager() dialog = DataFrameModelImportDialog(dfm, file_path=example_file_path) dialog.show() qtbot.add_widget(dialog) return dialog
def __init__(self, directory, settings_ini, default_dirs=True, tree_view=None, main_control=None, **kwargs): self.parent = kwargs.get('parent', None) self.main_control = main_control if default_dirs is True: settings_ini.set_safe('GENERAL', 'ROOT_DIRECTORY', directory) settings_ini.set_safe('GENERAL', 'LOG_DIRECTORY', os.path.join(directory, 'logs')) self._directory = directory self._df_manager = DataFrameModelManager() self._file_tree_model = FileTreeModel(root_dir=directory, parent=self.parent) self._dialog_export_df_model = DataFrameModelExportDialog( self.df_manager) self._dialog_merge_purge = MergePurgeDialog(self.df_manager, parent=self.parent) self._dialog_add_directory = DirectoryPathCreateDialog( base_dirname=directory, parent=self.parent) self._dialog_import_df_model = DataFrameModelImportDialog( self.df_manager, parent=self.parent) self._dialog_import_df_model.signalImported.connect( self._handle_imported_df_model) self._dialog_settings = ProjectSettingsDialog(settings_ini) self._tree_view = None self.tree_view = tree_view
def test_separators(self, dialog: DataFrameModelImportDialog, example_file_path, sep): out_path = os.path.splitext(example_file_path)[0] + "_sep_test.csv" if os.path.exists(out_path): os.remove(out_path) df = pandatools.superReadFile(example_file_path) df.to_csv(out_path, sep=SEPARATORS[sep], index=False) try: assert out_path not in dialog.df_manager.file_paths idx = dialog.comboBoxSeparator.findText(sep) assert idx >= 0 dialog.comboBoxSeparator.setCurrentIndex(idx) dialog.set_file_path(out_path) dialog.checkBoxParseDates.setChecked(False) dialog.checkBoxTrimSpaces.setChecked(False) dialog.checkBoxScrubLinebreaks.setChecked(False) dialog.checkBoxHasHeaders.setChecked(True) dialog.execute() assert out_path in dialog.df_manager.file_paths finally: os.remove(out_path)
class ProjectMainWindow(QtGui.QMainWindow, Ui_ProjectWindow): """ The ProjectMainWindow displays a project that the user wants to work on. The project lives in a directory within the ZeexApp.ROOT_DIRECTORY. Each project gets a DataFrameModelManager object which controls the DataFrameModel of each file opened. The main window shows all files in this directory and provides very convenient features to work with files containing rows/columns. Project's settings are stored in a .ini file in the root project directory. """ signalModelChanged = QtCore.Signal(str) signalModelOpened = QtCore.Signal(str) signalModelDestroyed = QtCore.Signal(str) def __init__(self, settings_ini: (str, SettingsINI), parent=None): """ :param settings_ini: (str, SettingsINI) can be a settings_ini file path or configured SettingsINI object. """ self.df_manager = DataFrameModelManager() QtGui.QMainWindow.__init__(self, parent=parent) self.setupUi(self) self.icons = Icons() self.dialog_settings = SettingsDialog(settings=settings_ini) self.dialog_merge_purge = MergePurgeDialog(self.df_manager) self.dialog_export = DataFrameModelExportDialog(self.df_manager, parent=self) self.dialog_import = DataFrameModelImportDialog(self.df_manager, parent=self) self.dialog_new_folder = DirectoryPathCreateDialog(self.treeView, parent=self) self.dialog_cloud = None self.key_delete = QtGui.QShortcut(self) self.key_enter = QtGui.QShortcut(self) self.key_zip = QtGui.QShortcut(self) self.key_rename = QtGui.QShortcut(self) self.connect_window_title() self.connect_actions() self.connect_treeview() self.connect_icons() self.connect_settings_dialog() self.connect_import_dialog() self.connect_export_dialog() self.connect_cloud_dialog() self.current_model = None @property def project_directory(self): """ The project's root directory stores everything for the project. :return: (str) ProjectMainWindow.treeView.QFileSystemModel.rootPath """ return self.treeView.model().rootPath() @property def log_directory(self): """ The project's log directory stores output logs. :return: (str) ProjectMainWindow.project_directory/log """ return os.path.join(self.project_directory, 'log') @QtCore.Slot(SettingsINI, str) def sync_settings(self, config: SettingsINI = None, file_path=None): """ Anytime settings are saved this method gets triggered. Sets defaults for various views. :param config: :param file_path: :return: """ self.connect_export_dialog() self.connect_import_dialog() def connect_window_title(self): """ Sets the ProjectMainWindow.windowTitle to "Project - dirname - dirpath" :return: None """ root_dir = self.dialog_settings.rootDirectoryLineEdit.text() base_name = os.path.basename(root_dir) self.dialog_settings.setWindowTitle("{} - Settings".format(base_name)) self.setWindowTitle("Project: {} - {}".format( base_name, root_dir.replace(base_name, ""))) def connect_actions(self): """ Connects all project actions. :return: None """ self.actionPreferences.triggered.connect(self.dialog_settings.show) self.actionAddFolder.triggered.connect(self.dialog_new_folder.show) self.actionNew.triggered.connect(self.dialog_import.show) self.actionOpen.triggered.connect(self.open_tableview_window) self.actionSave.triggered.connect(self.dialog_export.show) self.actionRemove.triggered.connect(self.remove_tree_selected_path) self.actionRename.triggered.connect(self.open_rename_path_dialog) self.actionMergePurge.triggered.connect(self.open_merge_purge_dialog) self.actionUnzip.triggered.connect(self.handle_compression) self.actionZip.triggered.connect(self.handle_compression) self.key_delete.setKey('del') self.key_enter.setKey('return') self.key_zip.setKey(QtGui.QKeySequence(self.tr('Ctrl+Z'))) self.key_rename.setKey(QtGui.QKeySequence(self.tr('Ctrl+R'))) self.key_delete.activated.connect(self.remove_tree_selected_path) self.key_enter.activated.connect(self.open_tableview_window) self.key_zip.activated.connect(self.handle_compression) self.key_rename.activated.connect(self.open_rename_path_dialog) def connect_icons(self): """ Sets all the menu/window icons. :return: None """ self.setWindowIcon(self.icons['folder']) self.actionNew.setIcon(self.icons['add']) self.actionAddFolder.setIcon(self.icons['folder']) self.actionOpen.setIcon(self.icons['spreadsheet']) self.actionPreferences.setIcon(self.icons['settings']) self.actionRemove.setIcon(self.icons['delete']) self.actionSave.setIcon(self.icons['save']) self.dialog_settings.setWindowIcon(self.icons['settings']) self.actionMergePurge.setIcon(self.icons['merge']) self.actionRename.setIcon(self.icons['rename']) self.dialog_merge_purge.setWindowIcon(self.icons['merge']) self.actionZip.setIcon(self.icons['archive']) self.dialog_new_folder.setWindowIcon(self.icons['folder']) self.actionUnzip.setIcon(self.icons['unzip']) def connect_treeview(self): """ Uses the ProjectMainWindow.dialog_settings.rootDirectoryLineEdit to get the root directory name of the project. It then connects the filetree using a QFileSystemModel. :return: None """ rootdir = self.dialog_settings.rootDirectoryLineEdit.text() model = FileTreeModel(root_dir=rootdir) self.treeView.setModel(model) self.treeView.setRootIndex(model.index(rootdir)) self.treeView.setColumnWidth(0, 400) self.treeView.setSelectionMode(self.treeView.ExtendedSelection) def connect_settings_dialog(self): """ Re-purposes the ProjectMainWindow.dialog_settings dialog to fit the scope of a project rather than the application as a whole. :return: None """ #Adjust the box to remove irrelevant items. self.dialog_settings.cloudProviderComboBox.hide() self.dialog_settings.cloudProviderLabel.hide() self.dialog_settings.btnLogDirectory.hide() self.dialog_settings.btnRootDirectory.hide() self.dialog_settings.themeComboBox.hide() self.dialog_settings.themeLabel.hide() # Override the log/root directory options self.dialog_settings.logDirectoryLineEdit.setText(self.log_directory) self.dialog_settings.logDirectoryLineEdit.setReadOnly(True) self.dialog_settings.rootDirectoryLineEdit.setText( self.project_directory) self.dialog_settings.rootDirectoryLineEdit.setReadOnly(True) self.dialog_settings.btnSetDefault.setVisible(False) self.dialog_settings.signalSettingsSaved.connect(self.sync_settings) self.dialog_new_folder.base_dirname = self.dialog_settings.rootDirectoryLineEdit.text( ) def connect_cloud_dialog(self): try: self.dialog_cloud = DropBoxViewDialog(self.treeView, self) self.actionViewCloud.triggered.connect(self.dialog_cloud.show) self.actionViewCloud.setIcon(self.icons['cloud']) except Exception as e: logging.error("Error connecting to cloud: {}".format(e)) self.actionViewCloud.setVisible(False) def connect_export_dialog(self): """ Sets defaults of the DataFrameModelExport Dialog. :return: None """ self.dialog_export.signalExported.connect(self._flush_export) self.dialog_export.setWindowIcon(self.icons['export_generic']) sep = self.dialog_settings.separatorComboBox.currentText() enc = self.dialog_settings.encodingComboBox.currentText() self.dialog_export.set_encoding(enc) self.dialog_export.set_separator(sep) def connect_import_dialog(self): """ Sets defaults of the DataFrameModelImport Dialog. :return: None """ self.dialog_import.signalImported.connect(self.import_file) self.dialog_import.setWindowIcon(self.icons['add']) sep = self.dialog_settings.separatorComboBox.currentText() enc = self.dialog_settings.encodingComboBox.currentText() self.dialog_import.set_encoding(enc) self.dialog_import.set_separator(sep) def open_tableview_window(self, model: DataFrameModel = None): """ Opens a FileTableWindow for the filename selected in the ProjectMainWindow.treeView. :param model: The qtpandas.models.DataFrameModel to edit. :return: None """ if model is None: # Maybe it's selected on the tree? model = self.get_tree_selected_model() if model is None: # No, not sure why this was called.. #box = get_ok_msg_box(self, "No model available to open.") #box.show() pass self.df_manager.get_fileview_window(model.filePath).show() self.add_recent_file_menu_entry(model.filePath, model) def open_merge_purge_dialog(self, model: DataFrameModel = None): if model is None: model = self.get_tree_selected_model() current_model = self.dialog_merge_purge.source_model if current_model is None or current_model.filePath is not model.filePath: self.dialog_merge_purge.set_source_model(model=model, configure=True) self.dialog_merge_purge.show() def get_tree_selected_model(self, raise_on_error=True) -> (DataFrameModel, None): """ Returns a DataFrameModel based on the filepath selected in the ProjectMainWindow.treeView. :return: qtpandas.DataFrameModel """ # Check if file is selected in tree view selected = self.treeView.selectedIndexes() if selected: idx = selected[0] file_path = self.treeView.model().filePath(idx) return self.df_manager.read_file(file_path) return None def get_tree_selected_path(self): selected = self.treeView.selectedIndexes() if selected: idx = selected[0] return self.treeView.model().filePath(idx) return None def remove_tree_selected_path(self): #TODO: need to emit a signal here. idxes = self.treeView.selectedIndexes() if idxes: file_model = self.treeView.model() for idx in idxes: if not file_model.isDir(idx): file_model.remove(idx) else: file_model.rmdir(idx) def open_tableview_current(self, model: DataFrameModel = None): """ Opens a tableview window for the current_model or the model kwarg. :param model: DataFrameModel :return: None """ if model is None: model = self.current_model else: self.set_current_df_model(model) assert isinstance(model, DataFrameModel), "No current DataFrame model." return self.open_tableview_window(model) def set_current_df_model(self, model: DataFrameModel): """ Sets the current dataframe model. for the project. :param model: DataFrameModel :return: None """ self.df_manager.set_model(model, model._filePath) self.current_model = self.df_manager.get_model(model._filePath) @QtCore.Slot('DataFrameModel', str) def import_file(self, filepath, open=True): if isinstance(filepath, DataFrameModel): model = filepath filepath = model.filePath self.df_manager.set_model(model, filepath) model = self.df_manager.get_model(filepath) name = os.path.basename(model.filePath) dirname = self.dialog_settings.rootDirectoryLineEdit.text() assert os.path.isdir( dirname), "Root Directory is not a directory: {}".format(dirname) if os.path.dirname(filepath) != dirname: newpath = os.path.join(dirname, name) self.df_manager.save_file(filepath, save_as=newpath, keep_orig=False) filepath = newpath if open is True: model = self.df_manager.get_model(filepath) self.open_tableview_window(model) def add_recent_file_menu_entry(self, name, model): action = QtGui.QAction(name, self.menuRecent_Files) action.triggered.connect(partial(self.open_tableview_window, model)) actions = self.menuRecent_Files.actions() if actions: self.menuRecent_Files.insertAction(actions[0], action) else: self.menuRecent_Files.addAction(action) def maybe_save_copy(self, df, filepath, max_size=20000, **kwargs): """ Saves a copy of the file to the project folder if its smaller than max_size its larger than 0 rows :param df: DataFrame: to save :param filepath: str: the filepath of the DataFrame :param max_size: int: max size of DataFrame :param kwargs: DataFrame.to_csv(**kwargs) :return: None """ if df.index.size <= max_size and not df.empty: kwargs['index'] = kwargs.get('index', False) df.to_csv(filepath, **kwargs) def _flush_export(self, orig_path, new_path): if orig_path != new_path: self.add_recent_file_menu_entry( new_path, self.df_manager.get_model(new_path)) def open_rename_path_dialog(self): current_path = self.get_tree_selected_path() dialog = FilePathRenameDialog(current_path, parent=self) dialog.show() def handle_compression(self, fpath=None, **kwargs): if fpath is None: fpath = self.get_tree_selected_path() assert fpath is not None, "No selected path!" if not fpath.lower().endswith('.zip'): return ostools.zipfile_compress(fpath, **kwargs) else: return ostools.zipfile_unzip( file_path=fpath, dir=self.dialog_settings.rootDirectoryLineEdit.text())
def test_general(self, dialog: DataFrameModelImportDialog, example_file_path): assert example_file_path not in dialog.df_manager.file_paths dialog.execute() assert example_file_path in dialog.df_manager.file_paths