# Mantid Repository : https://github.com/mantidproject/mantid # # Copyright © 2017 ISIS Rutherford Appleton Laboratory UKRI, # NScD Oak Ridge National Laboratory, European Spallation Source # & Institut Laue - Langevin # SPDX - License - Identifier: GPL - 3.0 + # This file is part of the mantidqt package # # from __future__ import (absolute_import, unicode_literals) # std imports import os.path as osp # 3rd party imports from qtpy.QtCore import Qt from qtpy.QtWidgets import (QTabWidget, QToolButton, QVBoxLayout, QWidget) # local imports from mantidqt.widgets.codeeditor.interpreter import PythonFileInterpreter NEW_TAB_TITLE = 'New' MODIFIED_MARKER = '*' def _tab_title_and_toolip(filename): """Create labels for the tab title and tooltip from a filename""" if filename is None: return NEW_TAB_TITLE, NEW_TAB_TITLE else: return osp.basename(filename), filename class MultiPythonFileInterpreter(QWidget): """Provides a tabbed widget for editing multiple files""" def __init__(self, default_content=None, parent=None): super(MultiPythonFileInterpreter, self).__init__(parent) # attributes self.default_content = default_content # widget setup self._tabs = self.create_tabwidget() layout = QVBoxLayout() layout.addWidget(self._tabs) self.setLayout(layout) layout.setContentsMargins(0, 0, 0, 0) # add a single editor by default self.append_new_editor() @property def editor_count(self): return self._tabs.count() def append_new_editor(self, content=None, filename=None): if content is None: content = self.default_content interpreter = PythonFileInterpreter(content, filename=filename, parent=self._tabs) # monitor future modifications interpreter.sig_editor_modified.connect(self.mark_current_tab_modified) interpreter.sig_filename_modified.connect(self.on_filename_modified) tab_title, tab_tooltip = _tab_title_and_toolip(filename) tab_idx = self._tabs.addTab(interpreter, tab_title) self._tabs.setTabToolTip(tab_idx, tab_tooltip) self._tabs.setCurrentIndex(tab_idx) return tab_idx def abort_current(self): """Request that that the current execution be cancelled""" self.current_editor().abort() def close_all(self): """ Close all tabs :return: True if all tabs are closed, False if cancelled """ for idx in reversed(range(self.editor_count)): if not self.close_tab(idx): return False return True def close_tab(self, idx): """ Close the tab at the given index. :param idx: The tab index :return: True if tab is to be closed, False if cancelled """ if idx >= self.editor_count: return True # Make the current tab active so that it is clear what you # are being prompted to save self._tabs.setCurrentIndex(idx) if self.current_editor().confirm_close(): self._tabs.removeTab(idx) else: return False # we never want an empty widget if self.editor_count == 0: self.append_new_editor(content=self.default_content) return True def create_tabwidget(self): """Create a new QTabWidget with a button to add new tabs""" tabs = QTabWidget(self) tabs.setMovable(True) tabs.setTabsClosable(True) # create a button to add new tabs plus_btn = QToolButton(tabs) plus_btn.setText('+') plus_btn.clicked.connect(self.plus_button_clicked) tabs.setCornerWidget(plus_btn, Qt.TopLeftCorner) tabs.tabCloseRequested.connect(self.close_tab) return tabs def current_editor(self): print(self._tabs.currentWidget()) return self._tabs.currentWidget() def editor_at(self, idx): """Return the editor at the given index. Must be in range""" return self._tabs.widget(idx) def execute_current(self): """Execute content of the current file. If a selection is active then only this portion of code is executed""" self.current_editor().execute_async() def mark_current_tab_modified(self, modified): """Update the current tab title to indicate that the content has been modified""" self.mark_tab_modified(self._tabs.currentIndex(), modified) def mark_tab_modified(self, idx, modified): """Update the tab title to indicate that the content has been modified or not""" title_cur = self._tabs.tabText(idx) if modified: if not title_cur.endswith(MODIFIED_MARKER): title_new = title_cur + MODIFIED_MARKER else: title_new = title_cur else: if title_cur.endswith(MODIFIED_MARKER): title_new = title_cur.rstrip('*') else: title_new = title_cur self._tabs.setTabText(idx, title_new) def on_filename_modified(self, filename): title, tooltip = _tab_title_and_toolip(filename) idx_cur = self._tabs.currentIndex() self._tabs.setTabText(idx_cur, title) self._tabs.setTabToolTip(idx_cur, tooltip) def open_file_in_new_tab(self, filepath): """Open the existing file in a new tab in the editor :param filepath: A path to an existing file """ with open(filepath, 'r') as code_file: content = code_file.read() self.append_new_editor(content=content, filename=filepath) def plus_button_clicked(self, _): """Add a new tab when the plus button is clicked""" self.append_new_editor() def save_current_file(self): """Save the current file""" self.current_editor().save()