Пример #1
0
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)
Пример #2
0
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))
Пример #3
0
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()
Пример #4
0
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}")
Пример #5
0
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)
Пример #6
0
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()
Пример #7
0
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()
Пример #8
0
 def setTabText(self, index, tab_text):
     tab_text = self.unique_tab_text(tab_text, index)
     QTabWidget.setTabText(self, index, tab_text)
     return tab_text