class EditableTabBar(QTabBar, object): """ Basic implementation of an editable tab bar """ addTabClicked = Signal() tabRenamed = Signal(object, object, object) def __init__(self, parent=None): super(EditableTabBar, self).__init__(parent=parent) self._is_editable = True self._editor = QLineEdit(self) self._editor.setWindowFlags(Qt.Popup) self._editor.setFocusProxy(self) self._editor.editingFinished.connect(self.handle_editing_finished) self._editor.installEventFilter(self) self.add_tab_btn = EditableAddButton(parent=self) self._move_add_tab_btn() self.setDrawBase(False) self.add_tab_btn.clicked.connect(self.addTabClicked.emit) def is_editable(self): """ Returns whether the tab bar enables rename mode when the user double clicks on the tab :return: bool """ return self._is_editable def set_is_editable(self, flag): """ Sets whether the tabs are editable or not :param flag: bool """ self._is_editable = bool(flag) def edit_tab(self, index): """ Function that is called when the tab is going to be edited :param index: :return: """ if not self._is_editable: return rect = self.tabRect(index) self._editor.setFixedSize(rect.size()) self._editor.move(self.parent().mapToGlobal(rect.topLeft())) self._editor.setText(self.tabText(index)) self._editor.selectAll() if not self._editor.isVisible(): self._editor.show() def handle_editing_finished(self): """ Function that is called when the tab edit has been completed """ index = self.currentIndex() if index >= 0: self._editor.hide() for i in range(self.count()): if self.tabText(i) == self._editor.text(): LOGGER.warning( 'Impossible to rename category because exists a tab with the same name!' ) return old_name = self.tabText(index) self.setTabText(index, self._editor.text()) self.tabRenamed.emit(index, self.tabText(index), old_name) def sizeHint(self): """ Return the size of the tab bar with increased width for the add tab button :return: QSize, size of the tab bar """ size_hint = super(EditableTabBar, self).sizeHint() return QSize(size_hint.width() + 25, size_hint.height()) def resizeEvent(self, event): """ Resize the widget and make sure the add tab button is in the correct location """ super(EditableTabBar, self).resizeEvent(event) self._move_add_tab_btn() def tabLayoutChange(self): """ This virtual handler is called whenever the tab layout changes. If anything changes make sure the add char btn is in the correct location """ super(EditableTabBar, self).tabLayoutChange() self._move_add_tab_btn() def eventFilter(self, widget, event): if ((event.type() == QEvent.MouseButtonPress and not self._editor.geometry().contains(event.globalPos())) or (event.type() == QEvent.KeyPress and event.key() == Qt.Key_Escape)): self._editor.hide() return True return QTabBar.eventFilter(self, widget, event) def mouseDoubleClickEvent(self, event): index = self.tabAt(event.pos()) if index >= 0: if not self._is_editable: return self.edit_tab(index) def _move_add_tab_btn(self): """ Move the add tab button to the correct location """ # Find the width of all of the tabs size = sum([self.tabRect(i).width() for i in range(self.count())]) # Set the add tab button location in a visible area h = self.geometry().top() w = self.width() if size > w: self.add_tab_btn.move(w - 50, h) else: self.add_tab_btn.move(size, h)
class DockTitleBar(QWidget, object): def __init__(self, dock_widget, renamable=False): super(DockTitleBar, self).__init__(dock_widget) self._renamable = renamable main_layout = QHBoxLayout() main_layout.setContentsMargins(0, 0, 0, 1) self.setLayout(main_layout) self._buttons_box = QGroupBox('') self._buttons_box.setObjectName('Docked') self._buttons_layout = QHBoxLayout() self._buttons_layout.setSpacing(1) self._buttons_layout.setMargin(2) self._buttons_box.setLayout(self._buttons_layout) main_layout.addWidget(self._buttons_box) self._title_label = QLabel(self) self._title_label.setStyleSheet('background: transparent') self._title_edit = QLineEdit(self) self._title_edit.setVisible(False) self._button_size = QSize(14, 14) self._dock_btn = QToolButton(self) self._dock_btn.setIcon(resources.icon('restore_window', theme='color')) self._dock_btn.setMaximumSize(self._button_size) self._dock_btn.setAutoRaise(True) self._close_btn = QToolButton(self) self._close_btn.setIcon(resources.icon('close_window', theme='color')) self._close_btn.setMaximumSize(self._button_size) self._close_btn.setAutoRaise(True) self._buttons_layout.addSpacing(2) self._buttons_layout.addWidget(self._title_label) self._buttons_layout.addWidget(self._title_edit) self._buttons_layout.addStretch() self._buttons_layout.addSpacing(5) self._buttons_layout.addWidget(self._dock_btn) self._buttons_layout.addWidget(self._close_btn) self._buttons_box.mouseDoubleClickEvent = self.mouseDoubleClickEvent self._buttons_box.mousePressEvent = self.mousePressEvent self._buttons_box.mouseMoveEvent = self.mouseMoveEvent self._buttons_box.mouseReleaseEvent = self.mouseReleaseEvent dock_widget.featuresChanged.connect(self._on_dock_features_changed) self._title_edit.editingFinished.connect(self._on_finish_edit) self._dock_btn.clicked.connect(self._on_dock_btn_clicked) self._close_btn.clicked.connect(self._on_close_btn_clicked) self._on_dock_features_changed(dock_widget.features()) self.set_title(dock_widget.windowTitle()) dock_widget.installEventFilter(self) dock_widget.topLevelChanged.connect(self._on_change_floating_style) @property def renamable(self): return self._renamable @renamable.setter def renamable(self, flag): self._renamable = flag def eventFilter(self, obj, event): if event.type() == QEvent.WindowTitleChange: self.set_title(obj.windowTitle()) return super(DockTitleBar, self).eventFilter(obj, event) def mouseMoveEvent(self, event): event.ignore() def mousePressEvent(self, event): event.ignore() def mouseReleaseEvent(self, event): event.ignore() def mouseDoubleClickEvent(self, event): if event.pos().x() <= self._title_label.width() and self._renamable: self._start_edit() else: super(DockTitleBar, self).mouseDoubleClickEvent(event) def update(self, *args, **kwargs): self._on_change_floating_style(self.parent().isFloating()) super(DockTitleBar, self).update(*args, **kwargs) def set_title(self, title): self._title_label.setText(title) self._title_edit.setText(title) def add_button(self, button): button.setAutoRaise(True) button.setMaximumSize(self._button_size) self._buttons_layout.insertWidget(5, button) def _start_edit(self): self._title_label.hide() self._title_edit.show() self._title_edit.setFocus() def _finish_edit(self): self._title_edit.hide() self._title_label.show() self.parent().setWindowTitle(self._title_edit.text()) def _on_dock_features_changed(self, features): if not features & QDockWidget.DockWidgetVerticalTitleBar: self._close_btn.setVisible(features & QDockWidget.DockWidgetClosable) self._dock_btn.setVisible(features & QDockWidget.DockWidgetFloatable) else: raise ValueError('Vertical title bar is not supported!') def _on_finish_edit(self): self._finish_edit() def _on_dock_btn_clicked(self): self.parent().setFloating(not self.parent().isFloating()) def _on_close_btn_clicked(self): self.parent().toggleViewAction().setChecked(False) self.parent().close() def _on_change_floating_style(self, state): pass
class EditableTearOffTabBar(TearOffTabBar, object): """ Extended implementation of an editable tab bar with: - Rename functionality - Tear off functionality """ tab_label_renamed = Signal(str, str) request_remove = Signal(int) tab_changed = Signal(int) def __init__(self, parent=None): super(EditableTearOffTabBar, self).__init__(parent=parent) self._editor = QLineEdit(self) self._editor.setWindowFlags(Qt.Popup) self._editor.setFocusProxy(self) self._editor.setFocusPolicy(Qt.StrongFocus) self._editor.editingFinished.connect(self.handle_editing_finished) self._editor.installEventFilter(self) self._editor.setValidator(QRegExpValidator(nameRegExp)) self._edit_index = -1 def edit_tab(self, index): """ This set the tab in edit mode This method is called when double click on the tab :param index: int, Index of the tab to be renamed """ # Show the editable line and position it on top of the selected tab rect = self.tabRect(index) self._editor.setFixedSize(rect.size()) self._editor.move(self.parent().mapToGlobal(rect.topLeft())) self._editor.setText(self.tabText(index)) if not self._editor.isVisible(): self._editor.show() self._edit_index = index def handle_editing_finished(self): """ This finish the edit of the tab name """ # This method only works of we are editing any tab if self._edit_index >= 0: # Hide the text and update tab text self._editor.hide() old_text = self.tabText(self.__editIndex) new_text = self._editor.text() if old_text != new_text: names = [self.tabText(i) for i in range(self.count())] new_text = naming.get_numeric_name(new_text, names) self.setTabText(self._edit_index, new_text) self.tab_label_renamed.emit(old_text, new_text) self._edit_index = -1 def eventFilter(self, widget, event): """ If we click (with mouse or keyboard) on registered widgets we hide the editor """ if event.type( ) == QEvent.MouseButtonPress and not self._editor.geometry().contains( event.globalPos()) or event.type( ) == QEvent.KeyPress and event.key() == Qt.Key_Escape: self._editor.hide() return False return QTabBar.eventFilter(self, widget, event)