예제 #1
0
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()
예제 #2
0
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)