class ReportsWidget(QWidget): """Reports widget.""" def __init__(self, parent): """Initialiaze ReportsWidget.""" QWidget.__init__(self, parent) self.setWindowTitle("Reports") self.tabs = QTabWidget() self.tabs.setMovable(True) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_tab) self.renderviews = {} layout = QVBoxLayout() layout.addWidget(self.tabs) self.setLayout(layout) self.set_html('', 'Welcome') def set_html(self, html_text, name, base_url=None): """Set html text.""" renderview = self.renderviews.get(name) if 'Welcome' in self.renderviews: # Overwrite the welcome tab renderview =self.renderviews.pop('Welcome') self.renderviews[name] = renderview self.tabs.setTabText(0, name) if renderview is None: # create a new renderview renderview = RenderView(self) self.renderviews[name] = renderview self.tabs.addTab(renderview, name) if base_url is not None: renderview.setHtml(html_text, base_url) else: renderview.setHtml(html_text) self.tabs.setCurrentWidget(renderview) def close_tab(self, index): "Close tab, and remove its widget form renderviews." self.renderviews.pop(self.tabs.tabText(index)) self.tabs.removeTab(index) def clear_all(self): """Clear widget web view content.""" for name in self.renderviews: self.set_html('', name)
class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName(_fromUtf8("MainWindow")) MainWindow.resize(1155, 853) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(_fromUtf8("centralwidget")) self.verticalLayout = QVBoxLayout(self.centralwidget) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.tabWidget = QTabWidget(self.centralwidget) self.tabWidget.setObjectName(_fromUtf8("tabWidget")) self.tab = QWidget() self.tab.setObjectName(_fromUtf8("tab")) self.tabWidget.addTab(self.tab, _fromUtf8("")) self.tab_2 = QWidget() self.tab_2.setObjectName(_fromUtf8("tab_2")) self.tabWidget.addTab(self.tab_2, _fromUtf8("")) self.verticalLayout.addWidget(self.tabWidget) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menubar.setGeometry(QRect(0, 0, 1155, 20)) self.menubar.setObjectName(_fromUtf8("menubar")) MainWindow.setMenuBar(self.menubar) self.statusbar = QStatusBar(MainWindow) self.statusbar.setObjectName(_fromUtf8("statusbar")) MainWindow.setStatusBar(self.statusbar) self.retranslateUi(MainWindow) self.tabWidget.setCurrentIndex(0) QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Tab 1", None)) self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Tab 2", None))
class ImportWizard(BaseDialog): """Text data import wizard""" def __init__(self, parent, text, title=None, icon=None, contents_title=None, varname=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) if title is None: title = _("Import wizard") self.setWindowTitle(title) if icon is None: self.setWindowIcon(ima.icon('fileimport')) if contents_title is None: contents_title = _("Raw text") if varname is None: varname = _("variable_name") self.var_name, self.clip_data = None, None # Setting GUI self.tab_widget = QTabWidget(self) self.text_widget = ContentsWidget(self, text) self.table_widget = PreviewWidget(self) self.tab_widget.addTab(self.text_widget, _("text")) self.tab_widget.setTabText(0, contents_title) self.tab_widget.addTab(self.table_widget, _("table")) self.tab_widget.setTabText(1, _("Preview")) self.tab_widget.setTabEnabled(1, False) name_layout = QHBoxLayout() name_label = QLabel(_("Variable Name")) name_layout.addWidget(name_label) self.name_edt = QLineEdit() self.name_edt.setText(varname) name_layout.addWidget(self.name_edt) btns_layout = QHBoxLayout() cancel_btn = QPushButton(_("Cancel")) btns_layout.addWidget(cancel_btn) cancel_btn.clicked.connect(self.reject) h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) btns_layout.addItem(h_spacer) self.back_btn = QPushButton(_("Previous")) self.back_btn.setEnabled(False) btns_layout.addWidget(self.back_btn) self.back_btn.clicked.connect(ft_partial(self._set_step, step=-1)) self.fwd_btn = QPushButton(_("Next")) if not text: self.fwd_btn.setEnabled(False) btns_layout.addWidget(self.fwd_btn) self.fwd_btn.clicked.connect(ft_partial(self._set_step, step=1)) self.done_btn = QPushButton(_("Done")) self.done_btn.setEnabled(False) btns_layout.addWidget(self.done_btn) self.done_btn.clicked.connect(self.process) self.text_widget.asDataChanged.connect(self.fwd_btn.setEnabled) self.text_widget.asDataChanged.connect(self.done_btn.setDisabled) layout = QVBoxLayout() layout.addLayout(name_layout) layout.addWidget(self.tab_widget) layout.addLayout(btns_layout) self.setLayout(layout) def _focus_tab(self, tab_idx): """Change tab focus""" for i in range(self.tab_widget.count()): self.tab_widget.setTabEnabled(i, False) self.tab_widget.setTabEnabled(tab_idx, True) self.tab_widget.setCurrentIndex(tab_idx) def _set_step(self, step): """Proceed to a given step""" new_tab = self.tab_widget.currentIndex() + step assert new_tab < self.tab_widget.count() and new_tab >= 0 if new_tab == self.tab_widget.count() - 1: try: self.table_widget.open_data( self._get_plain_text(), self.text_widget.get_col_sep(), self.text_widget.get_row_sep(), self.text_widget.trnsp_box.isChecked(), self.text_widget.get_skiprows(), self.text_widget.get_comments()) self.done_btn.setEnabled(True) self.done_btn.setDefault(True) self.fwd_btn.setEnabled(False) self.back_btn.setEnabled(True) except (SyntaxError, AssertionError) as error: QMessageBox.critical( self, _("Import wizard"), _("<b>Unable to proceed to next step</b>" "<br><br>Please check your entries." "<br><br>Error message:<br>%s") % str(error)) return elif new_tab == 0: self.done_btn.setEnabled(False) self.fwd_btn.setEnabled(True) self.back_btn.setEnabled(False) self._focus_tab(new_tab) def get_data(self): """Return processed data""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.var_name, self.clip_data def _simplify_shape(self, alist, rec=0): """Reduce the alist dimension if needed""" if rec != 0: if len(alist) == 1: return alist[-1] return alist if len(alist) == 1: return self._simplify_shape(alist[-1], 1) return [self._simplify_shape(al, 1) for al in alist] def _get_table_data(self): """Return clipboard processed as data""" data = self._simplify_shape(self.table_widget.get_data()) if self.table_widget.array_btn.isChecked(): return array(data) elif pd and self.table_widget.df_btn.isChecked(): info = self.table_widget.pd_info buf = io.StringIO(self.table_widget.pd_text) return pd.read_csv(buf, **info) return data def _get_plain_text(self): """Return clipboard as text""" return self.text_widget.text_editor.toPlainText() @Slot() def process(self): """Process the data from clipboard""" var_name = self.name_edt.text() try: self.var_name = str(var_name) except UnicodeEncodeError: self.var_name = to_text_string(var_name) if self.text_widget.get_as_data(): self.clip_data = self._get_table_data() elif self.text_widget.get_as_code(): self.clip_data = try_to_eval(to_text_string( self._get_plain_text())) else: self.clip_data = to_text_string(self._get_plain_text()) self.accept()
class Main_Window(QMainWindow): def __init__(self, env): super().__init__() self._env = env self._extra_menu_actions = [] self._extra_toolbar_actions = [] self.update_title() main_menu = self.menuBar() self.main_menu = main_menu project_menu = main_menu.addMenu("&Project") toolbar = self.addToolBar("Top") self.toolbar = toolbar new_action = QAction("&New", self) new_action.setShortcut("Ctrl+N") new_action.setStatusTip("Create a new empty project") new_action.triggered.connect(lambda: env.new()) project_menu.addAction(new_action) open_action = QAction("&Open...", self) open_action.setShortcut("Ctrl+O") open_action.setStatusTip("Open an existing project") open_action.triggered.connect(lambda: env.open()) project_menu.addAction(open_action) save_action = QAction("&Save", self) self.save_action = save_action save_action.setShortcut("Ctrl+S") save_action.setIcon(QIcon.fromTheme("document-save")) #save_action.setIcon(self.style().standardIcon( # self.style().SP_DialogSaveButton)) save_action.setStatusTip("Save project") save_action.setEnabled(False) save_action.triggered.connect(lambda: env.save()) project_menu.addAction(save_action) toolbar.addAction(save_action) save_as_action = QAction("Save &As...", self) save_as_action.setStatusTip("Save project under a new name") save_as_action.triggered.connect(lambda: env.save_as()) project_menu.addAction(save_as_action) quit_action = QAction("&Quit", self) quit_action.setShortcut("Ctrl+Q") quit_action.setStatusTip("Quit Hildegard") quit_action.triggered.connect(self.handle_quit) project_menu.addAction(quit_action) self.tabs = QTabWidget() self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect( lambda index: env.close(self.tabs.widget(index).entity, quit=True)) self.tabs.currentChanged.connect(self._handle_switch_to_tab) self.setCentralWidget(self.tabs) self.statusBar() def _handle_switch_to_tab(self, index): for a in self._extra_menu_actions: self.main_menu.removeAction(a) for t in self._extra_toolbar_actions: self.toolbar.removeAction(t) if index >= 0: widget = self.tabs.widget(index) if hasattr(widget, "menus"): for m in widget.menus: a = self.main_menu.addMenu(m) self._extra_menu_actions.append(a) if hasattr(widget, "tools"): for t in widget.tools: self.toolbar.addAction(t) self._extra_toolbar_actions.append(t) def handle_quit(self): if self._env._ok_to_quit(): qApp.quit() def closeEvent(self, event): if not self._env._ok_to_quit(): event.ignore() def update_title(self): if self._env._file_name: base_name = os.path.basename(self._env._file_name) else: base_name = "Unsaved" modified_str = "" if self._env.modified: modified_str = "*" self.setWindowTitle(f"Hildegard: {base_name}{modified_str}") def update_tab_title(self, index): modified_str = "" widget = self.tabs.widget(index) if widget in self._env.modified_widgets: modified_str = "*" self.tabs.setTabText(index, f"{widget.entity.name}{modified_str}")
class ProjectsWidget(WidgetBase): """Main projects widget.""" sig_saved = Signal() sig_login_requested = Signal() def __init__(self, *args, **kwargs): super(ProjectsWidget, self).__init__(*args, **kwargs) self.api = AnacondaAPI() self.timer = None self.timer_content_changed = QTimer() self.project_path = None self.original_content = None self.config = CONF self.timer = None # Widgets self.frame_projects_header = FrameProjectDetailsHeader() self.frame_projects_footer = FrameProjectDetailsFooter() self.button_upload = ButtonPrimary('Upload to Anaconda Cloud') self.button_cancel = ButtonDanger('Cancel') self.label_project_location = LabelProjectLocation( '<b>Project location</b>') self.label_status_message = LabelBase('') self.text_project_location = TextProjectLocation() self.tab_details = QTabWidget() self.file_explorer = ExplorerWidget() self.editor = ProjectEditor(parent=self) # Wigets setup tabbar = self.tab_details.tabBar() tabbar.setFocusPolicy(Qt.StrongFocus) self.tab_details.addTab(self.file_explorer, 'Files') self.tab_details.addTab(self.editor, 'Edit') self.timer_content_changed.setInterval(2000) self.timer_content_changed.timeout.connect(self.check_content_change) self.timer_content_changed.start() # Layouts layout_upload = QHBoxLayout() layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(self.label_status_message) layout_upload.addStretch() layout_upload.addWidget(self.button_cancel) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(self.button_upload) layout_upload.addWidget(SpacerHorizontal()) layout_upload.addWidget(SpacerHorizontal()) layout_footer = QVBoxLayout() layout_footer.addWidget(SpacerVertical()) layout_footer.addWidget(self.tab_details) layout_footer.addLayout(layout_upload) layout_footer.addWidget(SpacerVertical()) layout_footer.addWidget(SpacerVertical()) self.frame_projects_footer.setLayout(layout_footer) layout = QVBoxLayout() layout.addWidget(self.frame_projects_footer) self.setLayout(layout) # Signals self.editor.sig_dirty_state.connect(self.set_dirty) self.editor.sig_saved.connect(self.save) self.button_upload.clicked.connect(self.upload) self.button_cancel.clicked.connect(self.cancel) self.file_explorer.sig_add_to_project.connect(self.add_to_project) self.button_cancel.setVisible(False) self.file_explorer.set_current_folder(HOME_PATH) def add_to_project(self, fname): """Add selected file to project.""" file_path = os.path.join( self.project_path, os.path.basename(fname), ) try: shutil.copyfile(fname, file_path) except Exception: pass def check_content_change(self): """Check if content of anaconda-project changed outside.""" if self.project_path: project_config_path = os.path.join(self.project_path, 'anaconda-project.yml') if os.path.isfile(project_config_path): current_content = self.editor.text() with open(project_config_path, 'r') as f: data = f.read() if (current_content != data and data != self.original_content): self.load_project(self.project_path) def set_dirty(self, state): """Set dirty state editor tab.""" text = 'Edit*' if state else 'Edit' self.tab_details.setTabText(1, text) def before_delete(self): """Before deleting a folder, ensure it is not the same as the cwd.""" self.file_explorer.set_current_folder(HOME_PATH) def clear(self): """Reset view for proect details.""" self.text_project_location.setText('') self.editor.set_text('') def cancel(self): """Cancel ongoing project process.""" # TODO: when running project. Cancel ongoing process! self.button_cancel.setVisible(False) self.button_upload.setEnabled(True) def _upload(self, worker, output, error): """Upload callback.""" error = error if error else '' errors = [] if output is not None: errors = output.errors # print(output.status_description) # print(output.logs) # print(errors) if error or errors: if errors: error_msg = error or '\n'.join(errors) elif error: error_msg = 'Upload failed!' self.update_status(error_msg) else: self.update_status('Project <b>{0}</b> upload successful'.format( worker.name)) self.timer = QTimer() self.timer.setSingleShot(True) self.timer.setInterval(10000) self.timer.timeout.connect(lambda: self.update_status('')) self.timer.start() self.button_upload.setEnabled(True) self.button_cancel.setVisible(False) def update_status(self, message): """Update Status Bar message.""" self.label_status_message.setText(message) def upload(self): """Upload project to Anaconda Cloud.""" # Check if is logged in? if not self.api.is_logged_in(): self.update_status('You need to log in to Anaconda Cloud') self.sig_login_requested.emit() self.update_status('') return project = self.api.project_load(self.project_path) name = project.name or os.path.basename(self.project_path) # Check if saved? if self.editor.is_dirty(): self.update_status('Saving project <b>{0}</b>'.format( project.name)) self.editor.save() project = self.api.project_load(self.project_path) if not project.problems: username, token = self.api.get_username_token() self.button_cancel.setVisible(True) worker = self.api.project_upload( project, username=username, token=token, ) worker.sig_finished.connect(self._upload) worker.name = project.name self.button_upload.setEnabled(False) msg = 'Uploading project <b>{0}</b> to Anaconda Cloud.'.format( project.name) self.update_status(msg) else: self.update_status( 'Problems must be fixed before uploading <b>{0}</b>' ''.format(name)) def save(self): """Save current edited project.""" project_config_path = os.path.join(self.project_path, 'anaconda-project.yml') data = self.editor.text() if os.path.isfile(project_config_path): with open(project_config_path, 'w') as f: data = f.write(data) self.load_project(self.project_path, overwrite=False) self.sig_saved.emit() def load_project(self, project_path, overwrite=True): """Load a conda project located at path.""" self.project_path = project_path project = self.api.project_load(project_path) self.project = project self.text_project_location.setText(project_path) self.file_explorer.set_current_folder(project_path) project_config_path = os.path.join(project_path, 'anaconda-project.yml') data = '' if os.path.isfile(project_config_path): with open(project_config_path, 'r') as f: data = f.read() self.original_content = data if overwrite: self.editor.set_text(data) self.set_dirty(False) self.file_explorer.set_home(project_path) self.update_error_status(project) self.update_status('') def ordered_widgets(self): """Return a list of the ordered widgets.""" tabbar = self.tab_details.tabBar() ordered_widgets = [tabbar] ordered_widgets += self.file_explorer.ordered_widgets() ordered_widgets += self.editor.ordered_widgets() ordered_widgets += [self.button_upload] return ordered_widgets def update_error_status(self, project): """Update problems and suggestions.""" if project: problems = project.problems suggestions = project.suggestions if problems or suggestions: icon = QIcon(WARNING_ICON) self.tab_details.setTabIcon(1, icon) else: self.tab_details.setTabIcon(1, QIcon()) self.editor.set_info(problems, suggestions)
class MainWindow(QMainWindow): def __init__(self, log, app): self.log = log.getChild('Main') self.app = app super().__init__() self.dark_theme = CONFIG['dark_theme_default'] self.single_tab_mode = CONFIG['single_tab_mode_default'] self.loggers_by_name = {} # name -> LoggerTab self.popped_out_loggers = {} self.server_running = False self.shutting_down = False self.setupUi() self.start_server() def setupUi(self): self.resize(800, 600) self.setWindowTitle('cutelog') self.loggerTabWidget = QTabWidget(self) self.loggerTabWidget.setTabsClosable(True) self.loggerTabWidget.setMovable(True) self.loggerTabWidget.setTabBarAutoHide(True) self.loggerTabWidget.currentChanged.connect(self.change_actions_state) self.setCentralWidget(self.loggerTabWidget) self.statusbar = QStatusBar(self) self.setStatusBar(self.statusbar) self.setup_menubar() self.setup_action_triggers() self.setup_shortcuts() self.loggerTabWidget.tabCloseRequested.connect(self.close_tab) self.reload_stylesheet() self.restore_geometry() self.show() def setup_menubar(self): self.menubar = QMenuBar(self) self.setMenuBar(self.menubar) # File menu self.menuFile = self.menubar.addMenu("File") self.actionLoadRecords = self.menuFile.addAction('Load records') self.actionSaveRecords = self.menuFile.addAction('Save records') self.menuFile.addSeparator() self.actionDarkTheme = self.menuFile.addAction('Dark theme') self.actionDarkTheme.setCheckable(True) self.actionDarkTheme.setChecked(self.dark_theme) self.actionSingleTab = self.menuFile.addAction('Single tab mode') self.actionSingleTab.setCheckable(True) self.actionSingleTab.setChecked(self.single_tab_mode) # self.actionReloadStyle = self.menuFile.addAction('Reload style') self.actionSettings = self.menuFile.addAction('Settings') self.menuFile.addSeparator() self.actionQuit = self.menuFile.addAction('Quit') # Tab menu self.menuTab = self.menubar.addMenu("Tab") self.actionCloseTab = self.menuTab.addAction('Close') self.actionPopOut = self.menuTab.addAction('Pop out') self.actionRenameTab = self.menuTab.addAction('Rename') self.menuTab.addSeparator() self.actionPopIn = self.menuTab.addAction('Pop in tabs...') self.actionMergeTabs = self.menuTab.addAction('Merge tabs...') self.menuTab.addSeparator() self.actionExtraMode = self.menuTab.addAction('Extra mode') self.actionExtraMode.setCheckable(True) # Server menu self.menuServer = self.menubar.addMenu("Server") self.actionRestartServer = self.menuServer.addAction('Restart server') self.actionStartStopServer = self.menuServer.addAction('Stop server') # Records menu self.menuRecords = self.menubar.addMenu("Records") self.actionTrimTabRecords = self.menuRecords.addAction('Trim records') self.actionSetMaxCapacity = self.menuRecords.addAction( 'Set max capacity') # Help menu self.menuHelp = self.menubar.addMenu("Help") self.actionAbout = self.menuHelp.addAction("About cutelog") self.change_actions_state( ) # to disable all logger actions, since they don't function yet def setup_action_triggers(self): # File menu self.actionLoadRecords.triggered.connect(self.open_load_records_dialog) self.actionSaveRecords.triggered.connect(self.open_save_records_dialog) self.actionDarkTheme.toggled.connect(self.toggle_dark_theme) self.actionSingleTab.triggered.connect(self.set_single_tab_mode) # self.actionReloadStyle.triggered.connect(self.reload_stylesheet) self.actionSettings.triggered.connect(self.settings_dialog) self.actionQuit.triggered.connect(self.shutdown) # Tab meny self.actionCloseTab.triggered.connect(self.close_current_tab) self.actionPopOut.triggered.connect(self.pop_out_tab) self.actionRenameTab.triggered.connect(self.rename_tab_dialog) self.actionPopIn.triggered.connect(self.pop_in_tabs_dialog) self.actionMergeTabs.triggered.connect(self.merge_tabs_dialog) self.actionExtraMode.triggered.connect(self.toggle_extra_mode) # Server menu self.actionRestartServer.triggered.connect(self.restart_server) self.actionStartStopServer.triggered.connect(self.start_or_stop_server) # Records menu self.actionTrimTabRecords.triggered.connect(self.trim_records_dialog) self.actionSetMaxCapacity.triggered.connect(self.max_capacity_dialog) # About menu self.actionAbout.triggered.connect(self.about_dialog) def change_actions_state(self, index=None): logger, _ = self.current_logger_and_index() # if there are no loggers in tabs, these actions will be disabled: actions = [ self.actionCloseTab, self.actionExtraMode, self.actionPopOut, self.actionRenameTab, self.actionPopIn, self.actionTrimTabRecords, self.actionSetMaxCapacity, self.actionSaveRecords ] if not logger: for action in actions: action.setDisabled(True) self.actionExtraMode.setChecked(False) if len(self.popped_out_loggers) > 0: self.actionPopIn.setDisabled(False) else: for action in actions: action.setDisabled(False) if len(self.loggers_by_name) == self.loggerTabWidget.count(): self.actionPopIn.setDisabled(True) self.actionExtraMode.setChecked(logger.extra_mode) # if all loggers are popped in if len(self.popped_out_loggers) == 0: self.actionPopIn.setDisabled(True) if len(self.loggers_by_name) <= 1: self.actionMergeTabs.setDisabled(True) else: self.actionMergeTabs.setDisabled(False) def set_single_tab_mode(self, enabled): self.single_tab_mode = enabled def setup_shortcuts(self): self.actionQuit.setShortcut('Ctrl+Q') self.actionDarkTheme.setShortcut('Ctrl+S') # self.actionReloadStyle.setShortcut('Ctrl+R') # self.actionSettings.setShortcut('Ctrl+A') self.actionCloseTab.setShortcut('Ctrl+W') def save_geometry(self): CONFIG.save_geometry(self.geometry()) def restore_geometry(self): geometry = CONFIG.load_geometry() if geometry: self.resize(geometry.width(), geometry.height()) def settings_dialog(self): d = SettingsDialog(self) d.setWindowModality(Qt.ApplicationModal) d.setAttribute(Qt.WA_DeleteOnClose, True) d.open() def about_dialog(self): d = AboutDialog(self) d.open() def reload_stylesheet(self): if self.dark_theme: self.reload_dark_style() else: self.reload_light_style() def reload_light_style(self): if CONFIG['light_theme_is_native']: self.set_style_to_stock() return f = QFile(":/light_theme.qss") f.open(QFile.ReadOnly | QFile.Text) ts = QTextStream(f) qss = ts.readAll() # f = open(Config.get_resource_path('light_theme.qss', 'resources/ui'), 'r') # qss = f.read() self.app.setStyleSheet(qss) def reload_dark_style(self): f = QFile(":/dark_theme.qss") f.open(QFile.ReadOnly | QFile.Text) ts = QTextStream(f) qss = ts.readAll() # f = open(Config.get_resource_path('dark_theme.qss', 'resources/ui'), 'r') # qss = f.read() self.app.setStyleSheet(qss) def set_style_to_stock(self): self.app.setStyleSheet('') def toggle_dark_theme(self, enabled): self.dark_theme = enabled self.reload_stylesheet() for logger in self.loggers_by_name.values(): logger.set_dark_theme(enabled) def toggle_extra_mode(self, enabled): logger, _ = self.current_logger_and_index() if not logger: return logger.set_extra_mode(enabled) def start_server(self): self.log.debug('Starting the server') self.server = LogServer(self, self.on_connection, self.log) self.server.start() self.server_running = True self.actionStartStopServer.setText('Stop server') def stop_server(self): if self.server_running: self.log.debug('Stopping the server') self.server.close_server() self.server_running = False del self.server self.server = None self.actionStartStopServer.setText('Start server') def restart_server(self): self.log.debug('Restarting the server') self.stop_server() self.start_server() def start_or_stop_server(self): if self.server_running: self.stop_server() else: self.start_server() def on_connection(self, conn, conn_id): self.log.debug('New connection id={}'.format(conn_id)) if self.single_tab_mode and len(self.loggers_by_name) > 0: new_logger = list(self.loggers_by_name.values())[0] new_logger.add_connection(conn) else: new_logger, index = self.create_logger(conn) self.loggerTabWidget.setCurrentIndex(index) conn.new_record.connect(new_logger.on_record) conn.connection_finished.connect(new_logger.remove_connection) if self.server.benchmark and conn_id == -1: from .listener import BenchmarkMonitor bm = BenchmarkMonitor(self, new_logger) bm.speed_readout.connect(self.set_status) conn.connection_finished.connect(bm.requestInterruption) self.server.threads.append(bm) bm.start() def create_logger(self, conn, name=None): name = self.make_logger_name_unique("Logger" if name is None else name) new_logger = LoggerTab(self.loggerTabWidget, name, conn, self.log, self) new_logger.set_dark_theme(self.dark_theme) self.loggers_by_name[name] = new_logger index = self.loggerTabWidget.addTab(new_logger, name) return new_logger, index def make_logger_name_unique(self, name): name_f = "{} {{}}".format(name) c = 1 while name in self.loggers_by_name: name = name_f.format(c) c += 1 return name def set_status(self, string, timeout=3000): self.statusBar().showMessage(string, timeout) def rename_tab_dialog(self): logger, index = self.current_logger_and_index() if not logger: return d = QInputDialog(self) d.setLabelText('Enter the new name for the "{}" tab:'.format( logger.name)) d.setWindowTitle('Rename the "{}" tab'.format(logger.name)) d.textValueSelected.connect(self.rename_current_tab) d.open() def rename_current_tab(self, new_name): logger, index = self.current_logger_and_index() if new_name in self.loggers_by_name and new_name != logger.name: show_warning_dialog( self, "Rename error", 'Logger named "{}" already exists.'.format(new_name)) return self.log.debug('Renaming logger "{}" to "{}"'.format( logger.name, new_name)) del self.loggers_by_name[logger.name] logger.name = new_name self.loggers_by_name[new_name] = logger logger.log.name = '.'.join( logger.log.name.split('.')[:-1]) + '.{}'.format(new_name) self.loggerTabWidget.setTabText(index, new_name) def trim_records_dialog(self): logger, index = self.current_logger_and_index() if not logger: return d = QInputDialog(self) d.setInputMode(QInputDialog.IntInput) d.setIntRange( 0, 100000000) # because it sets intMaximum to 99 by default. why?? d.setLabelText('Keep this many records out of {}:'.format( logger.record_model.rowCount())) d.setWindowTitle('Trim tab records of "{}" logger'.format(logger.name)) d.intValueSelected.connect(self.trim_current_tab_records) d.open() def trim_current_tab_records(self, n): logger, index = self.current_logger_and_index() logger.record_model.trim_except_last_n(n) def max_capacity_dialog(self): logger, index = self.current_logger_and_index() if not logger: return d = QInputDialog(self) d.setInputMode(QInputDialog.IntInput) d.setIntRange( 0, 100000000) # because it sets intMaximum to 99 by default. why?? max_now = logger.record_model.max_capacity max_now = "not set" if max_now is None else max_now label_str = 'Set max capacity for "{}" logger\nCurrently {}. Set to 0 to disable:' d.setLabelText(label_str.format(logger.name, max_now)) d.setWindowTitle('Set max capacity') d.intValueSelected.connect(self.set_max_capacity) d.open() def set_max_capacity(self, n): logger, index = self.current_logger_and_index() logger.set_max_capacity(n) def merge_tabs_dialog(self): d = MergeDialog(self, self.loggers_by_name) d.setWindowModality(Qt.WindowModal) d.merge_tabs_signal.connect(self.merge_tabs) d.show() def merge_tabs(self, dst, srcs, keep_alive): self.log.debug('Merging tabs: dst="{}", srcs={}, keep={}'.format( dst, srcs, keep_alive)) dst_logger = self.loggers_by_name[dst] for src_name in srcs: src_logger = self.loggers_by_name[src_name] dst_logger.merge_with_records(src_logger.record_model.records) if keep_alive: for conn in src_logger.connections: conn.new_record.disconnect(src_logger.on_record) conn.connection_finished.disconnect( src_logger.remove_connection) conn.new_record.connect(dst_logger.on_record) dst_logger.add_connection(conn) src_logger.connections.clear() self.destroy_logger(src_logger) def close_current_tab(self): _, index = self.current_logger_and_index() if index is None: return self.close_tab(index) def close_tab(self, index): self.log.debug("Tab close requested: {}".format(index)) logger = self.loggerTabWidget.widget(index) self.loggerTabWidget.removeTab(index) self.log.debug(logger.name) self.destroy_logger(logger) def destroy_logger(self, logger): del self.loggers_by_name[logger.name] logger.setParent(None) logger.destroy() del logger def close_popped_out_logger(self, logger): del self.loggers_by_name[logger.name] del self.popped_out_loggers[logger.name] del logger if len(self.popped_out_loggers): self.actionPopIn.setDisabled(True) def current_logger_and_index(self): index = self.loggerTabWidget.currentIndex() if index == -1: return None, None logger = self.loggerTabWidget.widget(index) return logger, index def pop_out_tab(self): logger, index = self.current_logger_and_index() if not logger: return self.log.debug("Tab pop out requested: {}".format(int(index))) logger.destroyed.connect(logger.closeEvent) logger.setAttribute(Qt.WA_DeleteOnClose, True) logger.setWindowFlags(Qt.Window) logger.setWindowTitle('cutelog: "{}"'.format( self.loggerTabWidget.tabText(index))) self.popped_out_loggers[logger.name] = logger self.loggerTabWidget.removeTab(index) logger.popped_out = True logger.show() center_widget_on_screen(logger) def pop_in_tabs_dialog(self): d = PopInDialog(self, self.loggers_by_name.values()) d.pop_in_tabs.connect(self.pop_in_tabs) d.setWindowModality(Qt.ApplicationModal) d.open() def pop_in_tabs(self, names): for name in names: self.log.debug('Popping in logger "{}"'.format(name)) logger = self.loggers_by_name[name] self.pop_in_tab(logger) def pop_in_tab(self, logger): logger.setWindowFlags(Qt.Widget) logger.setAttribute(Qt.WA_DeleteOnClose, False) logger.destroyed.disconnect(logger.closeEvent) logger.setWindowTitle(logger.name) logger.popped_out = False del self.popped_out_loggers[logger.name] index = self.loggerTabWidget.addTab(logger, logger.windowTitle()) self.loggerTabWidget.setCurrentIndex(index) def open_load_records_dialog(self): d = QFileDialog(self) d.setFileMode(QFileDialog.ExistingFile) d.fileSelected.connect(self.load_records) d.setWindowTitle('Load records from...') d.open() def load_records(self, load_path): import json from os import path class RecordDecoder(json.JSONDecoder): def __init__(self, *args, **kwargs): json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) def object_hook(self, obj): if '_created' in obj: obj['created'] = obj['_created'] del obj['_created'] record = LogRecord(obj) del record._logDict['created'] else: record = LogRecord(obj) return record name = path.basename(load_path) index = None try: with open(load_path, 'r') as f: records = json.load(f, cls=RecordDecoder) new_logger, index = self.create_logger(None, name) new_logger.merge_with_records(records) self.loggerTabWidget.setCurrentIndex(index) self.set_status('Records have been loaded into "{}" tab'.format( new_logger.name)) except Exception as e: if index: self.close_tab(index) text = "Error while loading records: \n{}".format(e) self.log.error(text, exc_info=True) show_critical_dialog(self, "Couldn't load records", text) def open_save_records_dialog(self): from functools import partial logger, _ = self.current_logger_and_index() if not logger: return d = QFileDialog(self) d.selectFile(logger.name + '.log') d.setFileMode(QFileDialog.AnyFile) d.fileSelected.connect(partial(self.save_records, logger)) d.setWindowTitle('Save records of "{}" tab to...'.format(logger.name)) d.open() def save_records(self, logger, path): import json # needed because a deque is not serializable class RecordList(list): def __init__(self, records): self.records = records def __len__(self): return len(self.records) def __iter__(self): for record in self.records: d = record._logDict if not d.get('created', False) and not d.get( 'time', False): d['_created'] = record.created yield d try: records = logger.record_model.records record_list = RecordList(records) with open(path, 'w') as f: json.dump(record_list, f, indent=2) self.set_status('Records have been saved to "{}"'.format(path)) except Exception as e: text = "Error while saving records: \n{}".format(e) self.log.error(text, exc_info=True) show_critical_dialog(self, "Couldn't save records", text) def closeEvent(self, event): self.log.info('Close event on main window') self.shutdown() event.ignore( ) # prevents errors due to closing the program before server has stopped def destroy_all_tabs(self): self.log.debug('Destroying tabs') delete_this = list(self.loggers_by_name.values() ) # to prevent changing during iteration for logger in delete_this: self.destroy_logger(logger) def shutdown(self): self.log.info('Shutting down') if self.shutting_down: self.log.error('Exiting forcefully') raise SystemExit self.shutting_down = True self.stop_server() self.save_geometry() self.destroy_all_tabs() self.app.quit() def signal_handler(self, *args): self.shutdown()
class ImportWizard(QDialog): """Text data import wizard""" def __init__(self, parent, text, title=None, icon=None, contents_title=None, varname=None): QDialog.__init__(self, parent) # Destroying the C++ object right after closing the dialog box, # otherwise it may be garbage-collected in another QThread # (e.g. the editor's analysis thread in Spyder), thus leading to # a segmentation fault on UNIX or an application crash on Windows self.setAttribute(Qt.WA_DeleteOnClose) if title is None: title = _("Import wizard") self.setWindowTitle(title) if icon is None: self.setWindowIcon(ima.icon('fileimport')) if contents_title is None: contents_title = _("Raw text") if varname is None: varname = _("variable_name") self.var_name, self.clip_data = None, None # Setting GUI self.tab_widget = QTabWidget(self) self.text_widget = ContentsWidget(self, text) self.table_widget = PreviewWidget(self) self.tab_widget.addTab(self.text_widget, _("text")) self.tab_widget.setTabText(0, contents_title) self.tab_widget.addTab(self.table_widget, _("table")) self.tab_widget.setTabText(1, _("Preview")) self.tab_widget.setTabEnabled(1, False) name_layout = QHBoxLayout() name_label = QLabel(_("Variable Name")) name_layout.addWidget(name_label) self.name_edt = QLineEdit() self.name_edt.setText(varname) name_layout.addWidget(self.name_edt) btns_layout = QHBoxLayout() cancel_btn = QPushButton(_("Cancel")) btns_layout.addWidget(cancel_btn) cancel_btn.clicked.connect(self.reject) h_spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) btns_layout.addItem(h_spacer) self.back_btn = QPushButton(_("Previous")) self.back_btn.setEnabled(False) btns_layout.addWidget(self.back_btn) self.back_btn.clicked.connect(ft_partial(self._set_step, step=-1)) self.fwd_btn = QPushButton(_("Next")) btns_layout.addWidget(self.fwd_btn) self.fwd_btn.clicked.connect(ft_partial(self._set_step, step=1)) self.done_btn = QPushButton(_("Done")) self.done_btn.setEnabled(False) btns_layout.addWidget(self.done_btn) self.done_btn.clicked.connect(self.process) self.text_widget.asDataChanged.connect(self.fwd_btn.setEnabled) self.text_widget.asDataChanged.connect(self.done_btn.setDisabled) layout = QVBoxLayout() layout.addLayout(name_layout) layout.addWidget(self.tab_widget) layout.addLayout(btns_layout) self.setLayout(layout) def _focus_tab(self, tab_idx): """Change tab focus""" for i in range(self.tab_widget.count()): self.tab_widget.setTabEnabled(i, False) self.tab_widget.setTabEnabled(tab_idx, True) self.tab_widget.setCurrentIndex(tab_idx) def _set_step(self, step): """Proceed to a given step""" new_tab = self.tab_widget.currentIndex() + step assert new_tab < self.tab_widget.count() and new_tab >= 0 if new_tab == self.tab_widget.count()-1: try: self.table_widget.open_data(self._get_plain_text(), self.text_widget.get_col_sep(), self.text_widget.get_row_sep(), self.text_widget.trnsp_box.isChecked(), self.text_widget.get_skiprows(), self.text_widget.get_comments()) self.done_btn.setEnabled(True) self.done_btn.setDefault(True) self.fwd_btn.setEnabled(False) self.back_btn.setEnabled(True) except (SyntaxError, AssertionError) as error: QMessageBox.critical(self, _("Import wizard"), _("<b>Unable to proceed to next step</b>" "<br><br>Please check your entries." "<br><br>Error message:<br>%s") % str(error)) return elif new_tab == 0: self.done_btn.setEnabled(False) self.fwd_btn.setEnabled(True) self.back_btn.setEnabled(False) self._focus_tab(new_tab) def get_data(self): """Return processed data""" # It is import to avoid accessing Qt C++ object as it has probably # already been destroyed, due to the Qt.WA_DeleteOnClose attribute return self.var_name, self.clip_data def _simplify_shape(self, alist, rec=0): """Reduce the alist dimension if needed""" if rec != 0: if len(alist) == 1: return alist[-1] return alist if len(alist) == 1: return self._simplify_shape(alist[-1], 1) return [self._simplify_shape(al, 1) for al in alist] def _get_table_data(self): """Return clipboard processed as data""" data = self._simplify_shape( self.table_widget.get_data()) if self.table_widget.array_btn.isChecked(): return array(data) elif pd and self.table_widget.df_btn.isChecked(): info = self.table_widget.pd_info buf = io.StringIO(self.table_widget.pd_text) return pd.read_csv(buf, **info) return data def _get_plain_text(self): """Return clipboard as text""" return self.text_widget.text_editor.toPlainText() @Slot() def process(self): """Process the data from clipboard""" var_name = self.name_edt.text() try: self.var_name = str(var_name) except UnicodeEncodeError: self.var_name = to_text_string(var_name) if self.text_widget.get_as_data(): self.clip_data = self._get_table_data() elif self.text_widget.get_as_code(): self.clip_data = try_to_eval( to_text_string(self._get_plain_text())) else: self.clip_data = to_text_string(self._get_plain_text()) self.accept()
def setTabText(self, index, tab_text): tab_text = self.unique_tab_text(tab_text, index) QTabWidget.setTabText(self, index, tab_text) return tab_text