class BookmarksToolbar(QWidget): def __init__(self, window, parent=None): ''' @param: window BrowserWindow @param: parent QWidget ''' super().__init__(parent) self._window = window self._bookmarks = gVar.app.bookmarks() # Bookmarks self._clickedBookmark = None # BookmarkItem self._layout = None # QHBoxLayout self._updateTimer = None # QTimer self._actShowOnlyIcons = None # QAction self._actShowOnlyText = None # QAction self._dropRow = -1 self._dropPos = QPoint() self.setObjectName('bookmarksbar') self.setAcceptDrops(True) self.setContextMenuPolicy(Qt.CustomContextMenu) self._layout = QHBoxLayout(self) margin = self.style().pixelMetric(QStyle.PM_ToolBarItemMargin, None, self) + \ self.style().pixelMetric(QStyle.PM_ToolBarFrameWidth, None, self) self._layout.setContentsMargins(margin, margin, margin, margin) self._layout.setSpacing(self.style().pixelMetric( QStyle.PM_ToolBarItemSpacing, None, self)) self.setLayout(self._layout) self._updateTimer = QTimer(self) self._updateTimer.setInterval(300) self._updateTimer.setSingleShot(True) self._updateTimer.timeout.connect(self._refresh) self._bookmarks.bookmarkAdded.connect(self._bookmarksChanged) self._bookmarks.bookmarkRemoved.connect(self._bookmarksChanged) self._bookmarks.bookmarkChanged.connect(self._bookmarksChanged) self._bookmarks.showOnlyIconsInToolbarChanged.connect( self._showOnlyIconsChanged) self._bookmarks.showOnlyTextInToolbarChanged.connect( self._showOnlyTextChanged) self.customContextMenuRequested.connect(self._contextMenuRequested) self._refresh() # private Q_SLOTS: def _contextMenuRequested(self, pos): ''' @param: pos QPoint ''' # BookmarksToolbarButton button = self._buttonAt(pos) if button: self._clickedBookmark = button.bookmark() else: self._clickedBookmark = None menu = QMenu() actNewTab = menu.addAction(IconProvider.newTabIcon(), _('Open in new tab')) actNewWindow = menu.addAction(IconProvider.newWindowIcon(), _('Open in new window')) actNewPrivateWindow = menu.addAction( IconProvider.privateBrowsingIcon(), _('Open in new private window')) menu.addSeparator() actEdit = menu.addAction(_('Edit')) actDelete = menu.addAction(QIcon.fromTheme('edit-delete'), _('Delete')) menu.addSeparator() self._actShowOnlyIcons = menu.addAction(_('Show Only Icons')) self._actShowOnlyIcons.setCheckable(True) self._actShowOnlyIcons.setChecked( self._bookmarks.showOnlyIconsInToolbar()) self._actShowOnlyIcons.toggled.connect(self._showOnlyIconsChanged) self._actShowOnlyText = menu.addAction(_('Show Only Text')) self._actShowOnlyText.setCheckable(True) self._actShowOnlyText.setChecked( self._bookmarks.showOnlyTextInToolbar()) self._actShowOnlyText.toggled.connect(self._showOnlyTextChanged) actNewTab.triggered.connect(self._openBookmarkInNewTab) actNewWindow.triggered.connect(self._openBookmarkInNewWindow) actNewPrivateWindow.triggered.connect( self._openBookmarkInNewPrivateWindow) actEdit.triggered.connect(self._editBookmark) actDelete.triggered.connect(self._deleteBookmark) canBeModify = self._clickedBookmark and self._bookmarks.canBeModified( self._clickedBookmark) actEdit.setEnabled(canBeModify) actDelete.setEnabled(canBeModify) canOpen = self._clickedBookmark and self._clickedBookmark.isUrl() actNewTab.setEnabled(canOpen) actNewWindow.setEnabled(canOpen) actNewPrivateWindow.setEnabled(canOpen) menu.exec_(self.mapToGlobal(pos)) if button: # Clear mouse over state after closing menu button.update() self._clickedBookmark = None self._actShowOnlyIcons = None self._actShowOnlyText = None def _refresh(self): self._clear() folder = gVar.app.bookmarks().toolbarFolder() for child in folder.children(): self._addItem(child) self._layout.addStretch() def _bookmarksChanged(self): self._updateTimer.start() def _showOnlyIconsChanged(self, state): if state and self._actShowOnlyText: self._actShowOnlyText.setChecked(False) for idx in range(self._layout.count()): # BookmarksToolbarButton btn = self._layout.itemAt(idx).widget() if isinstance(btn, BookmarksToolbarButton): btn.setShowOnlyIcon(state) def _showOnlyTextChanged(self, state): if state and self._actShowOnlyIcons: self._actShowOnlyIcons.setChecked(False) for idx in range(self._layout.count()): # BookmarksToolbarButton btn = self._layout.itemAt(idx).widget() if isinstance(btn, BookmarksToolbarButton): btn.setShowOnlyText(state) def _openBookmarkInNewTab(self): if self._clickedBookmark: BookmarksTools.openBookmarkInNewTab(self._window, self._clickedBookmark) def _openBookmarkInNewWindow(self): if self._clickedBookmark: BookmarksTools.openBookmarkInNewWindow(self._clickedBookmark) def _openBookmarkInNewPrivateWindow(self): if self._clickedBookmark: BookmarksTools.openBookmarkInNewPrivateWindow( self._clickedBookmark) def _editBookmark(self): if self._clickedBookmark: BookmarksTools.editBookmarkDialog(self, self._clickedBookmark) self._bookmarks.changeBookmark(self._clickedBookmark) def _deleteBookmark(self): if self._clickedBookmark: self._bookmarks.removeBookmark(self._clickedBookmark) # private: def _clear(self): for idx in range(self._layout.count()): item = self._layout.takeAt(0) widget = item.widget() if widget: widget.setParent(None) widget.deleteLater() assert (self._layout.isEmpty()) def _addItem(self, item): ''' @param: BookmarkItem ''' assert (item) button = BookmarksToolbarButton(item, self) button.setMainWindow(self._window) button.setShowOnlyIcon(self._bookmarks.showOnlyIconsInToolbar()) button.setShowOnlyText(self._bookmarks.showOnlyTextInToolbar()) self._layout.addWidget(button) def _buttonAt(self, pos): ''' @param: pos QPoint @return: BookmarksToolbarButton ''' button = QApplication.widgetAt(self.mapToGlobal(pos)) if not isinstance(button, BookmarksToolbarButton): button = None return button # override def minimumSizeHint(self): size = super().minimumSizeHint() size.setHeight(max(20, size.height())) return size # override def dropEvent(self, event): ''' @param: event QDropEvent ''' row = self._dropRow self._clearDropIndicator() mime = event.mimeData() if not mime.hasUrls() and not mime.hasFormat( BookmarksButtonMimeData.mimeType()): super().dropEvent(event) return # BookmarkItem parent = self._bookmarks.toolbarFolder() bookmark = None if mime.hasFormat(BookmarksButtonMimeData.mimeType()): bookmarkMime = mime assert (isinstance(bookmarkMime, BookmarksButtonMimeData)) bookmark = bookmarkMime.item() initialIndex = bookmark.parent().children().index(bookmark) current = self._buttonAt(self._dropPos) if initialIndex < self._layout.indexOf(current): row -= 1 else: url = mime.urls()[0] if mime.hasText(): title = mime.text() else: title = url.toEncoded(QUrl.RemoveScheme) bookmark = BookmarkItem(BookmarkItem.Url) bookmark.setTitle(title) bookmark.setUrl(url) if row >= 0: self._bookmarks.insertBookmark(parent, row, bookmark) else: self._bookmarks.addBookmark(parent, bookmark) # override def dragEnterEvent(self, event): ''' @param: event QDragEnterEvent ''' mime = event.mimeData() if (mime.hasUrls() and mime.hasText()) or mime.hasFormat( BookmarksButtonMimeData.mimeType()): event.acceptProposedAction() return super().dragEnterEvent(event) # override def dragMoveEvent(self, event): ''' @param: event QDragMoveEvent ''' eventX = event.pos().x() # BoomarksToolbarButton button = self._buttonAt(event.pos()) self._dropPos = event.pos() self._dropRow = self._layout.indexOf(button) if button: if eventX - button.x() >= button.x() + button.width() - eventX: self._dropRow + 1 else: self._dropRow = -1 self.update() # override def dragLeaveEvent(self, event): ''' @param: event QDragLeaveEvent ''' self._clearDropIndicator() # override def paintEvent(self, event): ''' @param: event QPaintEvent ''' super().paintEvent(event) # Draw drop indicator if self._dropRow != -1: # BookmarksToolbarButton button = self._buttonAt(self._dropPos) if button: if button.bookmark().isFolder(): return tmpRect = QRect(button.x(), 0, button.width(), self.height()) rect = QRect() if self._dropRow == self._layout.indexOf(button): rect = QRect(max(0, tmpRect.left() - 2), tmpRect.top(), 3, tmpRect.height()) else: rect = QRect(tmpRect.right() + 0, tmpRect.top(), 3, tmpRect.height()) gVar.appTools.paintDropIndicator(self, rect) def _clearDropIndicator(self): self._dropRow = -1 self.update()
class LocationCompleterView(QWidget): def __init__(self): super().__init__(None) self._view = None # QListView self._delegate = None # LocationCompleterDelegate self._searchEnginesLayout = None # QHBoxLayout self._resizeHeight = -1 self._resizeTimer = None # QTimer self._forceResize = True self.setAttribute(Qt.WA_ShowWithoutActivating) self.setAttribute(Qt.WA_X11NetWmWindowTypeCombo) if gVar.app.platformName() == 'xcb': self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint | Qt.BypassWindowManagerHint) else: self.setWindowFlags(Qt.Popup) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) self._view = QListView(self) layout.addWidget(self._view) self._view.setUniformItemSizes(True) self._view.setEditTriggers(QAbstractItemView.NoEditTriggers) self._view.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self._view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self._view.setSelectionBehavior(QAbstractItemView.SelectRows) self._view.setSelectionMode(QAbstractItemView.SingleSelection) self._view.setMouseTracking(True) gVar.app.installEventFilter(self) self._delegate = LocationCompleterDelegate(self) self._view.setItemDelegate(self._delegate) searchFrame = QFrame(self) searchFrame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised) searchLayout = QHBoxLayout(searchFrame) searchLayout.setContentsMargins(10, 4, 4, 4) searchSettingsButton = ToolButton(self) searchSettingsButton.setIcon(IconProvider.settingsIcon()) searchSettingsButton.setToolTip(_('Manage Search Engines')) searchSettingsButton.setAutoRaise(True) searchSettingsButton.setIconSize(QSize(16, 16)) searchSettingsButton.clicked.connect(self.searchEnginesDialogRequested) searchLabel = QLabel(_('Search with:')) self._searchEnginesLayout = QHBoxLayout() self._setupSearchEngines() gVar.app.searchEnginesManager().enginesChanged.connect( self._setupSearchEngines) searchLayout.addWidget(searchLabel) searchLayout.addLayout(self._searchEnginesLayout) searchLayout.addStretch() searchLayout.addWidget(searchSettingsButton) layout.addWidget(searchFrame) def model(self): ''' @return: QAbstractItemModel ''' return self._view.model() def setModel(self, model): ''' @param model QAbstractItemModel ''' self._view.setModel(model) def selectionModel(self): ''' @return: QItemSelectionModel ''' return self._view.selectionModel() def currentIndex(self): ''' @return: QModelIndex ''' return self._view.currentIndex() def setCurrentIndex(self, index): ''' @param index QModelIndex ''' self._view.setCurrentIndex(index) def adjustSize(self): maxItemsCount = 12 newHeight = self._view.sizeHintForRow(0) * min(maxItemsCount, self.model().rowCount()) if not self._resizeTimer: self._resizeTimer = QTimer(self) self._resizeTimer.setInterval(200) def func(): if self._resizeHeight > 0: self._view.setFixedHeight(self._resizeHeight) self.setFixedHeight(self.sizeHint().height()) self._resizeHeight = -1 self._resizeTimer.timeout.connect(func) if not self._forceResize: if newHeight == self._resizeHeight: return elif newHeight == self._view.height(): self._resizeHeight = -1 return elif newHeight < self._view.height(): self._resizeHeight = newHeight self._resizeTimer.start() return self._resizeHeight = -1 self._forceResize = False self._view.setFixedHeight(newHeight) self.setFixedHeight(self.sizeHint().height()) # override def eventFilter(self, obj, event): # noqa C901 ''' @param obj QObject @param event QEvent @return: bool ''' # Event filter based on QCompleter::eventFilter from qcompleter.cpp if obj == self or obj == self._view or not self.isVisible(): return False evtType = event.type() if obj == self._view.viewport(): if evtType == QEvent.MouseButtonRelease: # QMouseEvent e = event index = self._view.indexAt(e.pos()) if not index.isValid(): return False # Qt::MouseButton button = e.button() # Qt::KeyboardModifiers modifiers = e.modifiers() if button == Qt.LeftButton and modifiers == Qt.NoModifier: self.indexActivated.emit(index) return True if button == Qt.MiddleButton or (button == Qt.LeftButton and modifiers == Qt.ControlModifier): self.indexCtrlActivated.emit(index) return True if button == Qt.LeftButton and modifiers == Qt.ShiftModifier: self.indexShiftActivated.emit(index) return True return False if evtType == QEvent.KeyPress: # QKeyEvent keyEvent = event evtKey = keyEvent.key() modifiers = keyEvent.modifiers() index = self._view.currentIndex() item = self.model().index(0, 0) if item.data(LocationCompleterModel.VisitSearchItemRole): visitSearchIndex = item else: visitSearchIndex = QModelIndex() if (evtKey == Qt.Key_Up or evtKey == Qt.Key_Down) and \ self._view.currentIndex() != index: self._view.setCurrentIndex(index) # TODO: ? if evtKey in (Qt.Key_Return, Qt.Key_Enter): if index.isValid(): if modifiers == Qt.NoModifier or modifiers == Qt.KeypadModifier: self.indexActivated.emit(index) return True if modifiers == Qt.ControlModifier: self.indexCtrlActivated.emit(index) return True if modifiers == Qt.ShiftModifier: self.indexShiftActivated.emit(index) return True elif evtKey == Qt.Key_End: if modifiers & Qt.ControlModifier: self._view.setCurrentIndex(self.model().index( self.model().rowCount() - 1, 0)) return True else: self.close() elif evtKey == Qt.Key_Home: if modifiers & Qt.ControlModifier: self._view.setCurrentIndex(self.model().index(0, 0)) self._view.scrollToTop() return True else: self.close() elif evtKey == Qt.Key_Escape: self.close() return True elif evtKey == Qt.Key_F4: if modifiers == Qt.AltModifier: self.close() return False elif evtKey in (Qt.Key_Tab, Qt.Key_Backtab): if modifiers != Qt.NoModifier and modifiers != Qt.ShiftModifier: return False isBack = evtKey == Qt.Key_Backtab if evtKey == Qt.Key_Tab and modifiers == Qt.ShiftModifier: isBack = True ev = QKeyEvent(QKeyEvent.KeyPress, isBack and Qt.Key_Up or Qt.Key_Down, Qt.NoModifier) QApplication.sendEvent(self.focusProxy(), ev) return True elif evtKey in (Qt.Key_Up, Qt.Key_PageUp): if modifiers != Qt.NoModifier: return False step = evtKey == Qt.Key_PageUp and 5 or 1 if not index.isValid() or index == visitSearchIndex: rowCount = self.model().rowCount() lastIndex = self.model().index(rowCount - 1, 0) self._view.setCurrentIndex(lastIndex) elif index.row() == 0: self._view.setCurrentIndex(QModelIndex()) else: row = max(0, index.row() - step) self._view.setCurrentIndex(self.model().index(row, 0)) return True elif evtKey in (Qt.Key_Down, Qt.Key_PageDown): if modifiers != Qt.NoModifier: return False step = evtKey == Qt.Key_PageDown and 5 or 1 if not index.isValid(): firstIndex = self.model().index(0, 0) self._view.setCurrentIndex(firstIndex) elif index != visitSearchIndex and index.row( ) == self.model().rowCount() - 1: self._view.setCurrentIndex(visitSearchIndex) self._view.scrollToTop() else: row = min(self.model().rowCount() - 1, index.row() + step) self._view.setCurrentIndex(self.model().index(row, 0)) return True elif evtKey == Qt.Key_Delete: if index != visitSearchIndex and self._view.viewport().rect( ).contains(self._view.visualRect(index)): self.indexDeleteRequested.emit(index) return True elif evtKey == Qt.Key_Shift: self._delegate.setForceVisitItem(True) self._view.viewport().update() # end of switch evtKey if self.focusProxy(): self.focusProxy().event(keyEvent) return True elif evtType == QEvent.KeyRelease: if event.key() == Qt.Key_Shift: self._delegate.setForceVisitItem(False) self._view.viewport().update() return True elif evtType in (QEvent.Wheel, QEvent.MouseButtonPress): if not self.underMouse(): self.close() return False elif evtType == QEvent.FocusOut: # QFocusEvent focusEvent = event reason = focusEvent.reason() if reason != Qt.PopupFocusReason and reason != Qt.MouseFocusReason: self.close() elif evtType in (QEvent.Move, QEvent.Resize): w = obj if isinstance(w, QWidget) and w.isWindow() and self.focusProxy( ) and w == self.focusProxy().window(): self.close() # end of switch evtType return False # Q_SIGNALS closed = pyqtSignal() searchEnginesDialogRequested = pyqtSignal() loadRequested = pyqtSignal(LoadRequest) indexActivated = pyqtSignal(QModelIndex) indexCtrlActivated = pyqtSignal(QModelIndex) indexShiftActivated = pyqtSignal(QModelIndex) indexDeleteRequested = pyqtSignal(QModelIndex) # public Q_SLOTS: def close(self): self.hide() self._view.verticalScrollBar().setValue(0) self._delegate.setForceVisitItem(False) self._forceResize = True self.closed.emit() # private: def _setupSearchEngines(self): for idx in (range(self._searchEnginesLayout.count())): item = self._searchEnginesLayout.takeAt(0) item.deleteLater() engines = gVar.app.searchEnginesManager().allEngines() for engine in engines: button = ToolButton(self) button.setIcon(engine.icon) button.setToolTip(engine.name) button.setAutoRaise(True) button.setIconSize(QSize(16, 16)) def func(): text = self.model().index(0, 0).data( LocationCompleterModel.SearchStringRole) self.loadRequested.emit( gVar.app.searchEngineManager().searchResult(engine, text)) button.clicked.connect(func) self._searchEnginesLayout.addWidget(button)