/ src / bitmessageqt / messageview.py
messageview.py
  1  """
  2  Custom message viewer with support for switching between HTML and plain
  3  text rendering, HTML sanitization, lazy rendering (as you scroll down),
  4  zoom and URL click warning popup
  5  
  6  """
  7  
  8  from PyQt4 import QtCore, QtGui
  9  
 10  from .safehtmlparser import SafeHTMLParser
 11  from tr import _translate
 12  
 13  
 14  class MessageView(QtGui.QTextBrowser):
 15      """Message content viewer class, can switch between plaintext and HTML"""
 16      MODE_PLAIN = 0
 17      MODE_HTML = 1
 18  
 19      def __init__(self, parent=0):
 20          super(MessageView, self).__init__(parent)
 21          self.mode = MessageView.MODE_PLAIN
 22          self.html = None
 23          self.setOpenExternalLinks(False)
 24          self.setOpenLinks(False)
 25          self.anchorClicked.connect(self.confirmURL)
 26          self.out = ""
 27          self.outpos = 0
 28          self.document().setUndoRedoEnabled(False)
 29          self.rendering = False
 30          self.defaultFontPointSize = self.currentFont().pointSize()
 31          self.verticalScrollBar().valueChanged.connect(self.lazyRender)
 32          self.setWrappingWidth()
 33  
 34      def resizeEvent(self, event):
 35          """View resize event handler"""
 36          super(MessageView, self).resizeEvent(event)
 37          self.setWrappingWidth(event.size().width())
 38  
 39      def mousePressEvent(self, event):
 40          """Mouse press button event handler"""
 41          if event.button() == QtCore.Qt.LeftButton and self.html and self.html.has_html and self.cursorForPosition(
 42                  event.pos()).block().blockNumber() == 0:
 43              if self.mode == MessageView.MODE_PLAIN:
 44                  self.showHTML()
 45              else:
 46                  self.showPlain()
 47          else:
 48              super(MessageView, self).mousePressEvent(event)
 49  
 50      def wheelEvent(self, event):
 51          """Mouse wheel scroll event handler"""
 52          # super will actually automatically take care of zooming
 53          super(MessageView, self).wheelEvent(event)
 54          if (
 55              QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ControlModifier
 56          ) == QtCore.Qt.ControlModifier and event.orientation() == QtCore.Qt.Vertical:
 57              zoom = self.currentFont().pointSize() * 100 / self.defaultFontPointSize
 58              QtGui.QApplication.activeWindow().statusBar().showMessage(_translate(
 59                  "MainWindow", "Zoom level %1%").arg(str(zoom)))
 60  
 61      def setWrappingWidth(self, width=None):
 62          """Set word-wrapping width"""
 63          self.setLineWrapMode(QtGui.QTextEdit.FixedPixelWidth)
 64          if width is None:
 65              width = self.width()
 66          self.setLineWrapColumnOrWidth(width)
 67  
 68      def confirmURL(self, link):
 69          """Show a dialog requesting URL opening confirmation"""
 70          if link.scheme() == "mailto":
 71              window = QtGui.QApplication.activeWindow()
 72              window.ui.lineEditTo.setText(link.path())
 73              if link.hasQueryItem("subject"):
 74                  window.ui.lineEditSubject.setText(
 75                      link.queryItemValue("subject"))
 76              if link.hasQueryItem("body"):
 77                  window.ui.textEditMessage.setText(
 78                      link.queryItemValue("body"))
 79              window.setSendFromComboBox()
 80              window.ui.tabWidgetSend.setCurrentIndex(0)
 81              window.ui.tabWidget.setCurrentIndex(
 82                  window.ui.tabWidget.indexOf(window.ui.send)
 83              )
 84              window.ui.textEditMessage.setFocus()
 85              return
 86          reply = QtGui.QMessageBox.warning(
 87              self,
 88              QtGui.QApplication.translate(
 89                  "MessageView",
 90                  "Follow external link"),
 91              QtGui.QApplication.translate(
 92                  "MessageView",
 93                  "The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you"
 94                  " or download malicious data. Are you sure?").arg(str(link.toString())),
 95              QtGui.QMessageBox.Yes,
 96              QtGui.QMessageBox.No)
 97          if reply == QtGui.QMessageBox.Yes:
 98              QtGui.QDesktopServices.openUrl(link)
 99  
100      def loadResource(self, restype, name):
101          """
102          Callback for loading referenced objects, such as an image. For security reasons at the moment doesn't do
103          anything)
104          """
105          pass
106  
107      def lazyRender(self):
108          """
109          Partially render a message. This is to avoid UI freezing when loading huge messages. It continues loading as
110          you scroll down.
111          """
112          if self.rendering:
113              return
114          self.rendering = True
115          position = self.verticalScrollBar().value()
116          cursor = QtGui.QTextCursor(self.document())
117          while self.outpos < len(self.out) and self.verticalScrollBar().value(
118          ) >= self.document().size().height() - 2 * self.size().height():
119              startpos = self.outpos
120              self.outpos += 10240
121              # find next end of tag
122              if self.mode == MessageView.MODE_HTML:
123                  pos = self.out.find(">", self.outpos)
124                  if pos > self.outpos:
125                      self.outpos = pos + 1
126              cursor.movePosition(QtGui.QTextCursor.End, QtGui.QTextCursor.MoveAnchor)
127              cursor.insertHtml(QtCore.QString(self.out[startpos:self.outpos]))
128          self.verticalScrollBar().setValue(position)
129          self.rendering = False
130  
131      def showPlain(self):
132          """Render message as plain text."""
133          self.mode = MessageView.MODE_PLAIN
134          out = self.html.raw
135          if self.html.has_html:
136              out = "<div align=\"center\" style=\"text-decoration: underline;\"><b>" + str(
137                  QtGui.QApplication.translate(
138                      "MessageView", "HTML detected, click here to display")) + "</b></div><br/>" + out
139          self.out = out
140          self.outpos = 0
141          self.setHtml("")
142          self.lazyRender()
143  
144      def showHTML(self):
145          """Render message as HTML"""
146          self.mode = MessageView.MODE_HTML
147          out = self.html.sanitised
148          out = "<div align=\"center\" style=\"text-decoration: underline;\"><b>" + str(
149              QtGui.QApplication.translate("MessageView", "Click here to disable HTML")) + "</b></div><br/>" + out
150          self.out = out
151          self.outpos = 0
152          self.setHtml("")
153          self.lazyRender()
154  
155      def setContent(self, data):
156          """Set message content from argument"""
157          self.html = SafeHTMLParser()
158          self.html.reset()
159          self.html.reset_safe()
160          self.html.allow_picture = True
161          self.html.feed(data)
162          self.html.close()
163          self.showPlain()