1# This file is part of the qpageview package. 2# 3# Copyright (c) 2019 - 2019 by Wilbert Berendsen 4# 5# This program is free software; you can redistribute it and/or 6# modify it under the terms of the GNU General Public License 7# as published by the Free Software Foundation; either version 2 8# of the License, or (at your option) any later version. 9# 10# This program is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13# GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License 16# along with this program; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18# See http://www.gnu.org/licenses/ for more information. 19 20 21""" 22Generic Link class and handling of links (clickable areas on a Page). 23 24The link area is in coordinates between 0.0 and 1.0, like Poppler does it. 25This way we can easily compute where the link area is on a page in different 26sizes or rotations. 27 28""" 29 30import collections 31 32from PyQt5.QtCore import pyqtSignal, QEvent, QRectF, Qt 33 34from . import page 35from . import rectangles 36 37Area = collections.namedtuple("Area", "left top right bottom") 38 39 40class Link: 41 url = "" 42 tooltip = "" 43 area = Area(0, 0, 0, 0) 44 45 def __init__(self, left, top, right, bottom, url=None, tooltip=None): 46 self.area = Area(left, top, right, bottom) 47 if url: 48 self.url = url 49 if tooltip: 50 self.tooltip = tooltip 51 52 def rect(self): 53 """Return the area attribute as a QRectF().""" 54 r = QRectF() 55 r.setCoords(*self.area) 56 return r 57 58 59class Links(rectangles.Rectangles): 60 """Manages a list of Link objects. 61 62 See the rectangles documentation for how to access the links. 63 64 """ 65 def get_coords(self, link): 66 return link.area 67 68 69class LinkViewMixin: 70 """Mixin class to enhance view.View with link capabilities.""" 71 72 linkHovered = pyqtSignal(page.AbstractPage, Link) 73 linkLeft = pyqtSignal() 74 linkClicked = pyqtSignal(QEvent, page.AbstractPage, Link) 75 linkHelpRequested = pyqtSignal(QEvent, page.AbstractPage, Link) 76 77 linksEnabled = True 78 79 def __init__(self, parent=None, **kwds): 80 self._currentLinkId = None 81 self._linkHighlighter = None 82 super().__init__(parent, **kwds) 83 84 def setLinkHighlighter(self, highlighter): 85 """Sets a Highlighter (see highlight.py) to highlight a link on hover. 86 87 Use None to remove an active Highlighter. By default no highlighter is 88 set to highlight links on hover. 89 90 To be able to actually *use* highlighting, be sure to also mix in the 91 HighlightViewMixin class from the highlight module. 92 93 """ 94 self._linkHighlighter = highlighter 95 96 def linkHighlighter(self): 97 """Return the currently set Highlighter, if any. 98 99 By default no highlighter is set to highlight links on hover, and None 100 is returned in that case. 101 102 """ 103 return self._linkHighlighter 104 105 def adjustCursor(self, pos): 106 """Adjust the cursor if pos is on a link (and linksEnabled is True). 107 108 Also emits signals when the cursor enters or leaves a link. 109 110 """ 111 if self.linksEnabled: 112 page, link = self.linkAt(pos) 113 if link: 114 lid = id(link) 115 else: 116 lid = None 117 if lid != self._currentLinkId: 118 if self._currentLinkId is not None: 119 self.linkHoverLeave() 120 self._currentLinkId = lid 121 if lid is not None: 122 self.linkHoverEnter(page, link) 123 if link: 124 return # do not call super() if we are on a link 125 super().adjustCursor(pos) 126 127 def linkAt(self, pos): 128 """If the pos (in the viewport) is over a link, return a (page, link) tuple. 129 130 Otherwise returns (None, None). 131 132 """ 133 pos = pos - self.layoutPosition() 134 page = self._pageLayout.pageAt(pos) 135 if page: 136 links = page.linksAt(pos - page.pos()) 137 if links: 138 return page, links[0] 139 return None, None 140 141 def linkHoverEnter(self, page, link): 142 """Called when the mouse hovers over a link. 143 144 The default implementation emits the linkHovered(page, link) signal, 145 sets a pointing hand mouse cursor, and, if a Highlighter was set using 146 setLinkHighlighter(), highlights the link. You can reimplement this 147 method to do something different. 148 149 """ 150 self.setCursor(Qt.PointingHandCursor) 151 self.linkHovered.emit(page, link) 152 if self._linkHighlighter: 153 self.highlight({page: [link.rect()]}, self._linkHighlighter, 3000) 154 155 def linkHoverLeave(self): 156 """Called when the mouse does not hover a link anymore. 157 158 The default implementation emits the linkLeft() signal, sets a default 159 mouse cursor, and, if a Highlighter was set using setLinkHighlighter(), 160 removes the highlighting of the current link. You can reimplement this 161 method to do something different. 162 163 """ 164 self.unsetCursor() 165 self.linkLeft.emit() 166 if self._linkHighlighter: 167 self.clearHighlight(self._linkHighlighter) 168 169 def linkClickEvent(self, ev, page, link): 170 """Called when a link is clicked. 171 172 The default implementation emits the linkClicked(event, page, link) 173 signal. The event can be used for things like determining which button 174 was used, and which keyboard modifiers were in effect. 175 176 """ 177 self.linkClicked.emit(ev, page, link) 178 179 def linkHelpEvent(self, ev, page, link): 180 """Called when a ToolTip or WhatsThis wants to appear. 181 182 The default implementation emits the linkHelpRequested(event, page, link) 183 signal. Using the event you can find the position, and the type of the 184 help event. 185 186 """ 187 self.linkHelpRequested.emit(ev, page, link) 188 189 def event(self, ev): 190 """Reimplemented to handle HelpEvent for links.""" 191 if self.linksEnabled and ev.type() in (QEvent.ToolTip, QEvent.WhatsThis): 192 page, link = self.linkAt(ev.pos()) 193 if link: 194 self.linkHelpEvent(ev, page, link) 195 return True 196 return super().event(ev) 197 198 def mousePressEvent(self, ev): 199 """Implemented to detect clicking a link and calling linkClickEvent().""" 200 if self.linksEnabled: 201 page, link = self.linkAt(ev.pos()) 202 if link: 203 self.linkClickEvent(ev, page, link) 204 return 205 super().mousePressEvent(ev) 206 207 def leaveEvent(self, ev): 208 """Implemented to leave a link, might there still be one hovered.""" 209 if self.linksEnabled and self._currentLinkId is not None: 210 self.linkHoverLeave() 211 self._currentLinkId = None 212 super().leaveEvent(ev) 213 214 215