# -*- coding: utf-8 -*-
#
# This file is part of the qpageview package.
#
# Copyright (c) 2019 - 2019 by Wilbert Berendsen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# See http://www.gnu.org/licenses/ for more information.
"""
Generic Link class and handling of links (clickable areas on a Page).
The link area is in coordinates between 0.0 and 1.0, like Poppler does it.
This way we can easily compute where the link area is on a page in different
sizes or rotations.
"""
import collections
from PyQt5.QtCore import pyqtSignal, QEvent, QRectF, Qt
from . import page
from . import rectangles
Area = collections.namedtuple("Area", "left top right bottom")
[docs]class Link:
url = ""
tooltip = ""
area = Area(0, 0, 0, 0)
def __init__(self, left, top, right, bottom, url=None, tooltip=None):
self.area = Area(left, top, right, bottom)
if url:
self.url = url
if tooltip:
self.tooltip = tooltip
[docs] def rect(self):
"""Return the area attribute as a QRectF()."""
r = QRectF()
r.setCoords(*self.area)
return r
[docs]class Links(rectangles.Rectangles):
"""Manages a list of Link objects.
See the rectangles documentation for how to access the links.
"""
[docs] def get_coords(self, link):
return link.area
[docs]class LinkViewMixin:
"""Mixin class to enhance view.View with link capabilities."""
#: (page, link) emitted when the user hovers a link
linkHovered = pyqtSignal(page.AbstractPage, Link)
#: (no args) emitted when the user does not hover a link anymore
linkLeft = pyqtSignal()
#: (event, page, link) emitted when the user clicks a link
linkClicked = pyqtSignal(QEvent, page.AbstractPage, Link)
#: (event, page, link) emitted when a What's This or Toolip is requested.
#: The event's type determines the type of this help event.
linkHelpRequested = pyqtSignal(QEvent, page.AbstractPage, Link)
#: whether to actually enable Link handling
linksEnabled = True
def __init__(self, parent=None, **kwds):
self._currentLinkId = None
self._linkHighlighter = None
super().__init__(parent, **kwds)
[docs] def setLinkHighlighter(self, highlighter):
"""Sets a Highlighter (see highlight.py) to highlight a link on hover.
Use None to remove an active Highlighter. By default no highlighter is
set to highlight links on hover.
To be able to actually *use* highlighting, be sure to also mix in the
HighlightViewMixin class from the highlight module.
"""
self._linkHighlighter = highlighter
[docs] def linkHighlighter(self):
"""Return the currently set Highlighter, if any.
By default no highlighter is set to highlight links on hover, and None
is returned in that case.
"""
return self._linkHighlighter
[docs] def adjustCursor(self, pos):
"""Adjust the cursor if pos is on a link (and linksEnabled is True).
Also emits signals when the cursor enters or leaves a link.
"""
if self.linksEnabled:
page, link = self.linkAt(pos)
if link:
lid = id(link)
else:
lid = None
if lid != self._currentLinkId:
if self._currentLinkId is not None:
self.linkHoverLeave()
self._currentLinkId = lid
if lid is not None:
self.linkHoverEnter(page, link)
if link:
return # do not call super() if we are on a link
super().adjustCursor(pos)
[docs] def linkAt(self, pos):
"""If the pos (in the viewport) is over a link, return a (page, link) tuple.
Otherwise returns (None, None).
"""
pos = pos - self.layoutPosition()
page = self._pageLayout.pageAt(pos)
if page:
links = page.linksAt(pos - page.pos())
if links:
return page, links[0]
return None, None
[docs] def linkHoverEnter(self, page, link):
"""Called when the mouse hovers over a link.
The default implementation emits the linkHovered(page, link) signal,
sets a pointing hand mouse cursor, and, if a Highlighter was set using
setLinkHighlighter(), highlights the link. You can reimplement this
method to do something different.
"""
self.setCursor(Qt.PointingHandCursor)
self.linkHovered.emit(page, link)
if self._linkHighlighter:
self.highlight({page: [link.rect()]}, self._linkHighlighter, 3000)
[docs] def linkHoverLeave(self):
"""Called when the mouse does not hover a link anymore.
The default implementation emits the linkLeft() signal, sets a default
mouse cursor, and, if a Highlighter was set using setLinkHighlighter(),
removes the highlighting of the current link. You can reimplement this
method to do something different.
"""
self.unsetCursor()
self.linkLeft.emit()
if self._linkHighlighter:
self.clearHighlight(self._linkHighlighter)
[docs] def linkClickEvent(self, ev, page, link):
"""Called when a link is clicked.
The default implementation emits the linkClicked(event, page, link)
signal. The event can be used for things like determining which button
was used, and which keyboard modifiers were in effect.
"""
self.linkClicked.emit(ev, page, link)
[docs] def linkHelpEvent(self, ev, page, link):
"""Called when a ToolTip or WhatsThis wants to appear.
The default implementation emits the linkHelpRequested(event, page, link)
signal. Using the event you can find the position, and the type of the
help event.
"""
self.linkHelpRequested.emit(ev, page, link)
[docs] def event(self, ev):
"""Reimplemented to handle HelpEvent for links."""
if self.linksEnabled and ev.type() in (QEvent.ToolTip, QEvent.WhatsThis):
page, link = self.linkAt(ev.pos())
if link:
self.linkHelpEvent(ev, page, link)
return True
return super().event(ev)
[docs] def mousePressEvent(self, ev):
"""Implemented to detect clicking a link and calling linkClickEvent()."""
if self.linksEnabled:
page, link = self.linkAt(ev.pos())
if link:
self.linkClickEvent(ev, page, link)
return
super().mousePressEvent(ev)
[docs] def leaveEvent(self, ev):
"""Implemented to leave a link, might there still be one hovered."""
if self.linksEnabled and self._currentLinkId is not None:
self.linkHoverLeave()
self._currentLinkId = None
super().leaveEvent(ev)