1# vim: ts=8:sts=8:sw=8:noexpandtab 2# 3# This file is part of ReText 4# Copyright: 2012-2021 Dmitry Shachnev 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <http://www.gnu.org/licenses/>. 18 19import os 20import re 21import weakref 22 23from markups import MarkdownMarkup, ReStructuredTextMarkup, TextileMarkup 24from ReText import globalSettings, settings, tablemode 25 26from PyQt5.QtCore import pyqtSignal, QFileInfo, QPoint, QRect, QSize, Qt 27from PyQt5.QtGui import QColor, QImage, QKeyEvent, QMouseEvent, QPainter, \ 28QPalette, QTextCursor, QTextFormat, QWheelEvent, QGuiApplication 29from PyQt5.QtWidgets import QAction, QApplication, QFileDialog, QLabel, QTextEdit, QWidget 30 31try: 32 from ReText.fakevimeditor import ReTextFakeVimHandler 33except ImportError: 34 ReTextFakeVimHandler = None 35 36colors = { 37 # Editor 38 'marginLine': {'light': '#dcd2dc', 'dark': '#3daee9'}, 39 'currentLineHighlight': {'light': '#ffffc8', 'dark': '#31363b'}, 40 'infoArea': {'light': '#aaaaff55', 'dark': '#aa557f2a'}, 41 'statsArea': {'light': '#aaffaa55', 'dark': '#aa7f552a'}, 42 'lineNumberArea': {'light': '#00ffff', 'dark': '#31363b'}, 43 'lineNumberAreaText': {'light': '#008080', 'dark': '#bdc3c7'}, 44 # Highlighter 45 'htmlTags': {'light': '#800080', 'dark': '#d070d0'}, 46 'htmlSymbols': {'light': '#008080', 'dark': '#70d0a0'}, 47 'htmlStrings': {'light': '#808000', 'dark': '#d0d070'}, 48 'htmlComments': {'light': '#a0a0a4', 'dark': '#b0b0aa'}, 49 'codeSpans': {'light': '#505050', 'dark': '#afafaf'}, 50 'markdownLinks': {'light': '#000090', 'dark': '#8080ff'}, 51 'blockquotes': {'light': '#808080', 'dark': '#b0b0b0'}, 52 'restDirectives': {'light': '#800080', 'dark': '#d070d0'}, 53 'restRoles': {'light': '#800000', 'dark': '#d07070'}, 54 'whitespaceOnEnd': {'light': '#80e1e1a5', 'dark': '#8096966e'}, 55} 56 57colorValues = {} 58 59def updateColorScheme(settings=settings): 60 palette = QApplication.palette() 61 windowColor = palette.color(QPalette.ColorRole.Window) 62 themeVariant = 'light' if windowColor.lightness() > 150 else 'dark' 63 settings.beginGroup('ColorScheme') 64 for key in colors: 65 if settings.contains(key): 66 colorValues[key] = settings.value(key, type=QColor) 67 else: 68 colorValues[key] = QColor(colors[key][themeVariant]) 69 settings.endGroup() 70 71def getColor(colorName): 72 if not colorValues: 73 updateColorScheme() 74 return colorValues[colorName] 75 76def documentIndentMore(document, cursor, globalSettings=globalSettings): 77 if cursor.hasSelection(): 78 block = document.findBlock(cursor.selectionStart()) 79 end = document.findBlock(cursor.selectionEnd()).next() 80 cursor.beginEditBlock() 81 while block != end: 82 cursor.setPosition(block.position()) 83 if globalSettings.tabInsertsSpaces: 84 cursor.insertText(' ' * globalSettings.tabWidth) 85 else: 86 cursor.insertText('\t') 87 block = block.next() 88 cursor.endEditBlock() 89 else: 90 indent = globalSettings.tabWidth - (cursor.positionInBlock() 91 % globalSettings.tabWidth) 92 if globalSettings.tabInsertsSpaces: 93 cursor.insertText(' ' * indent) 94 else: 95 cursor.insertText('\t') 96 97def documentIndentLess(document, cursor, globalSettings=globalSettings): 98 if cursor.hasSelection(): 99 block = document.findBlock(cursor.selectionStart()) 100 end = document.findBlock(cursor.selectionEnd()).next() 101 else: 102 block = document.findBlock(cursor.position()) 103 end = block.next() 104 cursor.beginEditBlock() 105 while block != end: 106 cursor.setPosition(block.position()) 107 if document.characterAt(cursor.position()) == '\t': 108 cursor.deleteChar() 109 else: 110 pos = 0 111 while document.characterAt(cursor.position()) == ' ' \ 112 and pos < globalSettings.tabWidth: 113 pos += 1 114 cursor.deleteChar() 115 block = block.next() 116 cursor.endEditBlock() 117 118class ReTextEdit(QTextEdit): 119 resized = pyqtSignal(QRect) 120 scrollLimitReached = pyqtSignal(QWheelEvent) 121 returnBlockPattern = re.compile("^[\\s]*([*>-]|\\d+\\.) ") 122 orderedListPattern = re.compile("^([\\s]*)(\\d+)\\. $") 123 wordPattern = re.compile(r"\w+") 124 nonAlphaNumPattern = re.compile(r"\W") 125 surroundKeysSelfClose = [ 126 Qt.Key.Key_Underscore, 127 Qt.Key.Key_Asterisk, 128 Qt.Key.Key_QuoteDbl, 129 Qt.Key.Key_Apostrophe 130 ] 131 surroundKeysOtherClose = { 132 Qt.Key.Key_ParenLeft: ')', 133 Qt.Key.Key_BracketLeft: ']' 134 } 135 136 def __init__(self, parent, settings=globalSettings): 137 QTextEdit.__init__(self) 138 self.tab = weakref.proxy(parent) 139 self.parent = parent.p 140 self.undoRedoActive = False 141 self.tableModeEnabled = False 142 self.setAcceptRichText(False) 143 self.lineNumberArea = LineNumberArea(self) 144 self.infoArea = LineInfoArea(self) 145 self.statistics = (0, 0, 0) 146 self.statsArea = TextInfoArea(self) 147 self.updateFont() 148 self.setWrapModeAndWidth() 149 self.document().blockCountChanged.connect(self.updateLineNumberAreaWidth) 150 self.cursorPositionChanged.connect(self.highlightCurrentLine) 151 self.document().contentsChange.connect(self.contentsChange) 152 self.settings = settings 153 if globalSettings.useFakeVim: 154 self.installFakeVimHandler() 155 156 def setWrapModeAndWidth(self): 157 if globalSettings.rightMarginWrap and (self.rect().topRight().x() > self.marginx): 158 self.setLineWrapMode(QTextEdit.LineWrapMode.FixedPixelWidth) 159 self.setLineWrapColumnOrWidth(self.marginx) 160 else: 161 self.setLineWrapMode(QTextEdit.LineWrapMode.WidgetWidth) 162 163 def updateFont(self): 164 self.setFont(globalSettings.editorFont) 165 metrics = self.fontMetrics() 166 self.marginx = (int(self.document().documentMargin()) 167 + metrics.horizontalAdvance(' ' * globalSettings.rightMargin)) 168 self.setTabStopDistance(globalSettings.tabWidth * metrics.horizontalAdvance(' ')) 169 self.updateLineNumberAreaWidth() 170 self.infoArea.updateTextAndGeometry() 171 self.updateTextStatistics() 172 self.statsArea.updateTextAndGeometry() 173 if globalSettings.wideCursor: 174 self.setCursorWidth(metrics.averageCharWidth()) 175 176 def paintEvent(self, event): 177 if not globalSettings.rightMargin: 178 return super().paintEvent(event) 179 painter = QPainter(self.viewport()) 180 painter.setPen(getColor('marginLine')) 181 y1 = self.rect().topLeft().y() 182 y2 = self.rect().bottomLeft().y() 183 painter.drawLine(self.marginx, y1, self.marginx, y2) 184 super().paintEvent(event) 185 186 def wheelEvent(self, event): 187 modifiers = QGuiApplication.keyboardModifiers() 188 if modifiers == Qt.KeyboardModifier.ControlModifier: 189 font = globalSettings.editorFont 190 size = font.pointSize() 191 scroll = event.angleDelta().y() 192 if scroll > 0: 193 size += 1 194 elif scroll < 0: 195 size -= 1 196 else: 197 return 198 font.setPointSize(size) 199 self.parent.setEditorFont(font) 200 else: 201 super().wheelEvent(event) 202 203 if event.angleDelta().y() < 0: 204 scrollBarLimit = self.verticalScrollBar().maximum() 205 else: 206 scrollBarLimit = self.verticalScrollBar().minimum() 207 208 if self.verticalScrollBar().value() == scrollBarLimit: 209 self.scrollLimitReached.emit(event) 210 211 def scrollContentsBy(self, dx, dy): 212 super().scrollContentsBy(dx, dy) 213 self.lineNumberArea.update() 214 215 def contextMenuEvent(self, event): 216 # Create base menu 217 menu = self.createStandardContextMenu() 218 if self.parent.actionPasteImage.isEnabled(): 219 actions = menu.actions() 220 actionPaste = menu.findChild(QAction, "edit-paste") 221 actionNextAfterPaste = actions[actions.index(actionPaste) + 1] 222 menu.insertAction(actionNextAfterPaste, self.parent.actionPasteImage) 223 224 text = self.toPlainText() 225 if not text: 226 menu.exec(event.globalPos()) 227 return 228 229 # Check word under the cursor 230 oldcursor = self.textCursor() 231 cursor = self.cursorForPosition(event.pos()) 232 curchar = self.document().characterAt(cursor.position()) 233 isalpha = curchar.isalpha() 234 dictionary = self.tab.highlighter.dictionary 235 word = None 236 if isalpha and not (oldcursor.hasSelection() and oldcursor.selectedText() != cursor.selectedText()): 237 cursor.select(QTextCursor.SelectionType.WordUnderCursor) 238 word = cursor.selectedText() 239 240 if word is not None and dictionary and not dictionary.check(word): 241 self.setTextCursor(cursor) 242 suggestions = dictionary.suggest(word) 243 actions = [self.parent.act(sug, trig=self.fixWord(sug)) for sug in suggestions] 244 menu.insertSeparator(menu.actions()[0]) 245 for action in actions[::-1]: 246 menu.insertAction(menu.actions()[0], action) 247 menu.insertSeparator(menu.actions()[0]) 248 menu.insertAction(menu.actions()[0], self.parent.act(self.tr('Add to dictionary'), trig=self.learnWord(word))) 249 250 menu.addSeparator() 251 menu.addAction(self.parent.actionMoveUp) 252 menu.addAction(self.parent.actionMoveDown) 253 254 menu.exec(event.globalPos()) 255 256 def fixWord(self, correctword): 257 return lambda: self.insertPlainText(correctword) 258 259 def learnWord(self, newword): 260 return lambda: self.addNewWord(newword) 261 262 def addNewWord(self, newword): 263 cursor = self.textCursor() 264 block = cursor.block() 265 cursor.clearSelection() 266 self.setTextCursor(cursor) 267 dictionary = self.tab.highlighter.dictionary 268 if (dictionary is None) or not newword: 269 return 270 dictionary.add(newword) 271 self.tab.highlighter.rehighlightBlock(block) 272 273 def isSurroundKey(self, key): 274 return key in self.surroundKeysSelfClose or key in self.surroundKeysOtherClose 275 276 def getCloseKey(self, event, key): 277 if key in self.surroundKeysSelfClose: 278 return event.text() 279 280 if key in self.surroundKeysOtherClose: 281 return self.surroundKeysOtherClose[key] 282 283 def surroundText(self, cursor, event, key): 284 text = cursor.selectedText() 285 keyStr = event.text() 286 keyClose = self.getCloseKey(event, key) 287 288 cursor.insertText(keyStr + text + keyClose) 289 290 def keyPressEvent(self, event): 291 key = event.key() 292 cursor = self.textCursor() 293 if key == Qt.Key.Key_Backspace and event.modifiers() & Qt.KeyboardModifier.GroupSwitchModifier: 294 # Workaround for https://bugreports.qt.io/browse/QTBUG-49771 295 event = QKeyEvent(event.type(), event.key(), 296 event.modifiers() ^ Qt.KeyboardModifier.GroupSwitchModifier) 297 if key == Qt.Key.Key_Tab: 298 documentIndentMore(self.document(), cursor) 299 elif key == Qt.Key.Key_Backtab: 300 documentIndentLess(self.document(), cursor) 301 elif key == Qt.Key.Key_Return: 302 markupClass = self.tab.getActiveMarkupClass() 303 if event.modifiers() & Qt.KeyboardModifier.ControlModifier: 304 cursor.insertText('\n') 305 self.ensureCursorVisible() 306 elif self.tableModeEnabled and tablemode.handleReturn(cursor, markupClass, 307 newRow=(event.modifiers() & Qt.KeyboardModifier.ShiftModifier)): 308 self.setTextCursor(cursor) 309 self.ensureCursorVisible() 310 else: 311 if event.modifiers() & Qt.KeyboardModifier.ShiftModifier and markupClass == MarkdownMarkup: 312 # Insert Markdown-style line break 313 cursor.insertText(' ') 314 self.handleReturn(cursor) 315 elif cursor.selectedText() and self.isSurroundKey(key): 316 self.surroundText(cursor, event, key) 317 else: 318 if event.text() and self.tableModeEnabled: 319 cursor.beginEditBlock() 320 super().keyPressEvent(event) 321 if event.text() and self.tableModeEnabled: 322 cursor.endEditBlock() 323 324 def handleReturn(self, cursor): 325 # Select text between the cursor and the line start 326 cursor.movePosition(QTextCursor.MoveOperation.StartOfBlock, QTextCursor.MoveMode.KeepAnchor) 327 text = cursor.selectedText() 328 length = len(text) 329 match = self.returnBlockPattern.search(text) 330 if match is not None: 331 matchedText = match.group(0) 332 if len(matchedText) == length: 333 cursor.removeSelectedText() 334 matchedText = '' 335 else: 336 matchOL = self.orderedListPattern.match(matchedText) 337 if matchOL is not None: 338 matchedPrefix = matchOL.group(1) 339 matchedNumber = int(matchOL.group(2)) 340 nextNumber = matchedNumber if self.settings.orderedListMode == 'repeat' else matchedNumber + 1 341 matchedText = matchedPrefix + str(nextNumber) + ". " 342 else: 343 matchedText = '' 344 # Reset the cursor 345 cursor = self.textCursor() 346 cursor.insertText('\n' + matchedText) 347 self.ensureCursorVisible() 348 349 def moveLineUp(self): 350 self.moveLine(QTextCursor.MoveOperation.PreviousBlock) 351 352 def moveLineDown(self): 353 self.moveLine(QTextCursor.MoveOperation.NextBlock) 354 355 def moveLine(self, direction): 356 cursor = self.textCursor() 357 # Select the current block 358 cursor.movePosition(QTextCursor.MoveOperation.StartOfBlock, QTextCursor.MoveMode.MoveAnchor) 359 cursor.movePosition(QTextCursor.MoveOperation.NextBlock, QTextCursor.MoveMode.KeepAnchor) 360 text = cursor.selectedText() 361 # Remove it 362 cursor.removeSelectedText() 363 # Move to the wanted block 364 cursor.movePosition(direction, QTextCursor.MoveMode.MoveAnchor) 365 # Paste the line 366 cursor.insertText(text) 367 # Move to the pasted block 368 cursor.movePosition(QTextCursor.MoveOperation.PreviousBlock, QTextCursor.MoveMode.MoveAnchor) 369 # Update cursor 370 self.setTextCursor(cursor) 371 372 def lineNumberAreaWidth(self): 373 if not globalSettings.lineNumbersEnabled: 374 return 0 375 cursor = QTextCursor(self.document()) 376 cursor.movePosition(QTextCursor.MoveOperation.End) 377 if globalSettings.relativeLineNumbers: 378 digits = len(str(cursor.blockNumber())) + 1 379 else: 380 digits = len(str(cursor.blockNumber() + 1)) 381 return 5 + self.fontMetrics().horizontalAdvance('9') * digits 382 383 def updateLineNumberAreaWidth(self, blockcount=0): 384 self.setViewportMargins(self.lineNumberAreaWidth(), 0, 0, 0) 385 386 def resizeEvent(self, event): 387 super().resizeEvent(event) 388 rect = self.contentsRect() 389 self.resized.emit(rect) 390 self.lineNumberArea.setGeometry(rect.left(), rect.top(), 391 self.lineNumberAreaWidth(), rect.height()) 392 self.infoArea.updateTextAndGeometry() 393 self.statsArea.updateTextAndGeometry() 394 self.setWrapModeAndWidth() 395 self.ensureCursorVisible() 396 397 def highlightCurrentLine(self): 398 if globalSettings.relativeLineNumbers: 399 self.lineNumberArea.update() 400 if globalSettings.highlightCurrentLine == 'disabled': 401 return self.setExtraSelections([]) 402 selection = QTextEdit.ExtraSelection() 403 selection.format.setBackground(getColor('currentLineHighlight')) 404 selection.format.setProperty(QTextFormat.Property.FullWidthSelection, True) 405 selection.cursor = self.textCursor() 406 selection.cursor.clearSelection() 407 selections = [selection] 408 if globalSettings.highlightCurrentLine == 'wrapped-line': 409 selections.append(QTextEdit.ExtraSelection()) 410 selections[0].cursor.movePosition(QTextCursor.MoveOperation.StartOfBlock) 411 selections[0].cursor.movePosition(QTextCursor.MoveOperation.EndOfBlock, QTextCursor.MoveMode.KeepAnchor) 412 selections[1].format.setBackground(getColor('currentLineHighlight')) 413 selections[1].format.setProperty(QTextFormat.Property.FullWidthSelection, True) 414 selections[1].cursor = self.textCursor() 415 selections[1].cursor.movePosition(QTextCursor.MoveOperation.EndOfBlock) 416 elif selection.cursor.block().textDirection() == Qt.LayoutDirection.RightToLeft: 417 # FullWidthSelection does not work correctly for RTL direction 418 selection.cursor.movePosition(QTextCursor.MoveOperation.StartOfLine) 419 selection.cursor.movePosition(QTextCursor.MoveOperation.EndOfLine, QTextCursor.MoveMode.KeepAnchor) 420 self.setExtraSelections(selections) 421 422 def enableTableMode(self, enable): 423 self.tableModeEnabled = enable 424 425 def backupCursorPositionOnLine(self): 426 return self.textCursor().positionInBlock() 427 428 def restoreCursorPositionOnLine(self, positionOnLine): 429 cursor = self.textCursor() 430 cursor.setPosition(cursor.block().position() + positionOnLine) 431 self.setTextCursor(cursor) 432 433 def contentsChange(self, pos, removed, added): 434 if self.tableModeEnabled: 435 markupClass = self.tab.getActiveMarkupClass() 436 437 cursorPosition = self.backupCursorPositionOnLine() 438 tablemode.adjustTableToChanges(self.document(), pos, added - removed, markupClass) 439 self.restoreCursorPositionOnLine(cursorPosition) 440 self.lineNumberArea.update() 441 self.updateTextStatistics() 442 443 def findNextImageName(self, filenames): 444 highestNumber = 0 445 for filename in filenames: 446 m = re.match(r'image(\d+).png', filename, re.IGNORECASE) 447 if m: 448 number = int(m.group(1)) 449 highestNumber = max(number, highestNumber) 450 return 'image%04d.png' % (highestNumber + 1) 451 452 def getImageFilename(self): 453 if self.tab.fileName: 454 saveDir = os.path.dirname(self.tab.fileName) 455 else: 456 saveDir = os.getcwd() 457 458 imageFileName = self.findNextImageName(os.listdir(saveDir)) 459 460 return QFileDialog.getSaveFileName(self, 461 self.tr('Save image'), 462 os.path.join(saveDir, imageFileName), 463 self.tr('Images (*.png *.jpg)'))[0] 464 465 def makeFileNameRelative(self, fileName): 466 """Tries to make the given fileName relative. If the document is 467 not saved, or the fileName is on a different root, returns the 468 original fileName. 469 """ 470 if self.tab.fileName: 471 currentDir = os.path.dirname(self.tab.fileName) 472 try: 473 return os.path.relpath(fileName, currentDir) 474 except ValueError: # different roots 475 return fileName 476 return fileName 477 478 def getImageMarkup(self, fileName): 479 """Returns markup for image in the current markup language. 480 481 This method is also accessed in ReTextWindow.insertImage. 482 """ 483 link = self.makeFileNameRelative(fileName) 484 markupClass = self.tab.getActiveMarkupClass() 485 if markupClass == MarkdownMarkup: 486 return '![%s](%s)' % (QFileInfo(link).baseName(), link) 487 elif markupClass == ReStructuredTextMarkup: 488 return '.. image:: %s' % link 489 elif markupClass == TextileMarkup: 490 return '!%s!' % link 491 492 def pasteImage(self): 493 mimeData = QApplication.instance().clipboard().mimeData() 494 fileName = self.getImageFilename() 495 if not fileName or not mimeData.hasImage(): 496 return 497 image = QImage(mimeData.imageData()) 498 image.save(fileName) 499 500 imageText = self.getImageMarkup(fileName) 501 502 self.textCursor().insertText(imageText) 503 504 def installFakeVimHandler(self): 505 if ReTextFakeVimHandler: 506 fakeVimEditor = ReTextFakeVimHandler(self, self.parent) 507 fakeVimEditor.setSaveAction(self.parent.actionSave) 508 fakeVimEditor.setQuitAction(self.parent.actionQuit) 509 self.parent.actionFakeVimMode.triggered.connect(fakeVimEditor.remove) 510 511 def updateTextStatistics(self): 512 if not globalSettings.documentStatsEnabled: 513 return 514 text = self.toPlainText() 515 wordCount = len(self.wordPattern.findall(text)) 516 alphaNums = self.nonAlphaNumPattern.sub('', text) 517 alphaNumCount = len(alphaNums) 518 characterCount = len(text) 519 self.statistics = (wordCount, alphaNumCount, characterCount) 520 521 522class LineNumberArea(QWidget): 523 def __init__(self, editor): 524 QWidget.__init__(self, editor) 525 self.editor = editor 526 527 def sizeHint(self): 528 return QSize(self.editor.lineNumberAreaWidth(), 0) 529 530 def paintEvent(self, event): 531 if not globalSettings.lineNumbersEnabled: 532 return super().paintEvent(event) 533 painter = QPainter(self) 534 painter.fillRect(event.rect(), getColor('lineNumberArea')) 535 painter.setPen(getColor('lineNumberAreaText')) 536 cursor = self.editor.cursorForPosition(QPoint(0, 0)) 537 atEnd = False 538 fontHeight = self.fontMetrics().height() 539 height = self.editor.height() 540 if globalSettings.relativeLineNumbers: 541 relativeTo = self.editor.textCursor().blockNumber() 542 else: 543 relativeTo = -1 544 while not atEnd: 545 rect = self.editor.cursorRect(cursor) 546 if rect.top() >= height: 547 break 548 number = str(cursor.blockNumber() - relativeTo).replace('-', '−') 549 painter.drawText(0, rect.top(), self.width() - 2, 550 fontHeight, Qt.AlignmentFlag.AlignRight, number) 551 cursor.movePosition(QTextCursor.MoveOperation.EndOfBlock) 552 atEnd = cursor.atEnd() 553 if not atEnd: 554 cursor.movePosition(QTextCursor.MoveOperation.NextBlock) 555 556class InfoArea(QLabel): 557 def __init__(self, editor, baseColor): 558 QWidget.__init__(self, editor) 559 self.editor = editor 560 self.editor.cursorPositionChanged.connect(self.updateTextAndGeometry) 561 self.updateTextAndGeometry() 562 self.setAutoFillBackground(True) 563 self.baseColor = baseColor 564 palette = self.palette() 565 palette.setColor(QPalette.ColorRole.Window, self.baseColor) 566 self.setPalette(palette) 567 self.setCursor(Qt.CursorShape.IBeamCursor) 568 569 def updateTextAndGeometry(self): 570 text = self.getText() 571 (w, h) = self.getAreaSize(text) 572 (x, y) = self.getAreaPosition(w, h) 573 self.setText(text) 574 self.resize(w, h) 575 self.move(x, y) 576 self.setVisible(not globalSettings.useFakeVim) 577 578 def getAreaSize(self, text): 579 metrics = self.fontMetrics() 580 width = metrics.horizontalAdvance(text) 581 height = metrics.height() 582 return width, height 583 584 def getAreaPosition(self, width, height): 585 return 0, 0 586 587 def getText(self): 588 return "" 589 590 def enterEvent(self, event): 591 palette = self.palette() 592 windowColor = QColor(self.baseColor) 593 windowColor.setAlpha(0x20) 594 palette.setColor(QPalette.ColorRole.Window, windowColor) 595 textColor = palette.color(QPalette.ColorRole.WindowText) 596 textColor.setAlpha(0x20) 597 palette.setColor(QPalette.ColorRole.WindowText, textColor) 598 self.setPalette(palette) 599 600 def leaveEvent(self, event): 601 palette = self.palette() 602 palette.setColor(QPalette.ColorRole.Window, self.baseColor) 603 palette.setColor(QPalette.ColorRole.WindowText, 604 self.editor.palette().color(QPalette.ColorRole.WindowText)) 605 self.setPalette(palette) 606 607 def mousePressEvent(self, event): 608 pos = self.mapToParent(event.pos()) 609 pos.setX(pos.x() - self.editor.lineNumberAreaWidth()) 610 newEvent = QMouseEvent(event.type(), pos, 611 event.button(), event.buttons(), 612 event.modifiers()) 613 self.editor.mousePressEvent(newEvent) 614 615 mouseReleaseEvent = mousePressEvent 616 mouseDoubleClickEvent = mousePressEvent 617 mouseMoveEvent = mousePressEvent 618 619class LineInfoArea(InfoArea): 620 def __init__(self, editor): 621 InfoArea.__init__(self, editor, getColor('infoArea')) 622 623 def getAreaPosition(self, width, height): 624 viewport = self.editor.viewport() 625 rightSide = viewport.width() + self.editor.lineNumberAreaWidth() 626 if globalSettings.documentStatsEnabled: 627 return rightSide - width, viewport.height() - (2 * height) 628 else: 629 return rightSide - width, viewport.height() - height 630 631 def getText(self): 632 template = '%d : %d' 633 cursor = self.editor.textCursor() 634 block = cursor.blockNumber() + 1 635 position = cursor.positionInBlock() 636 return template % (block, position) 637 638 639class TextInfoArea(InfoArea): 640 def __init__(self, editor): 641 InfoArea.__init__(self, editor, getColor('statsArea')) 642 643 def getAreaPosition(self, width, height): 644 viewport = self.editor.viewport() 645 rightSide = viewport.width() + self.editor.lineNumberAreaWidth() 646 return rightSide - width, viewport.height() - height 647 648 def getText(self): 649 if not globalSettings.documentStatsEnabled: 650 return 651 template = self.tr('%d w | %d a | %d c', 652 'count of words, alphanumeric characters, all characters') 653 words, alphaNums, characters = self.editor.statistics 654 return template % (words, alphaNums, characters) 655