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 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)