Python Forum
RichTextEditor - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: General (https://python-forum.io/forum-1.html)
+--- Forum: Code sharing (https://python-forum.io/forum-5.html)
+--- Thread: RichTextEditor (/thread-7419.html)

Pages: 1 2


RichTextEditor - Axel_Erfurt - Jan-09-2018

based on the PyQt5 'RichText' example.
I added a lot of features.
(you can also use it as an HTML editor)

code on github

#!/usr/bin/python3
# -- coding: utf-8 --
from PyQt5.QtWidgets import QTextEdit, QWidget, QVBoxLayout, QApplication, QFileDialog, QMessageBox, QHBoxLayout, \
						 QToolBar, QComboBox, QAction, QLineEdit, QMenu, QMainWindow, QActionGroup, \
						QFontComboBox, QColorDialog, QInputDialog, QPushButton
from PyQt5.QtGui import QIcon, QPainter, QTextFormat, QColor, QTextCursor, QKeySequence, QClipboard, \
						QTextCharFormat, QTextCharFormat, QFont, QPixmap, QFontDatabase, QFontInfo, QTextDocumentWriter, \
						QImage, QTextListFormat, QTextBlockFormat, QTextDocumentFragment
from PyQt5.QtCore import Qt, QDir, QFile, QFileInfo, QTextStream, QSettings, QTextCodec, QSize, QMimeData, QUrl, QSysInfo
from PyQt5 import QtPrintSupport
import sys, os, webbrowser

tab = "\t"
eof = "\n"
tableheader2 = "<table><tr><th>	Header 1	</th><th>	Header 2	</th> \
</tr><tr><td>	Line 1	</td><td>	Line 1	</td></tr></table>"
tableheader3 = "<table><tr><th>	Header 1	</th><th>	Header 2	</th><th>	Header 3	</th> \
</tr><tr><td>	Line 1	</td><td>	Line 1	</td><td>	Line 1	</td></tr></table>"

class myEditor(QMainWindow):
	def __init__(self, parent = None):
		super(myEditor, self).__init__(parent)
		self.MaxRecentFiles = 5
		self.windowList = []
		self.recentFileActs = []
		self.mainText = " "
		self.settings = QSettings('Axel Schneider', 'RichTextEdit')
		self.setAttribute(Qt.WA_DeleteOnClose)

		# Editor Widget ...
#		QIcon.setThemeName('gnome')
		self.editor = QTextEdit() 
		self.editor.setStyleSheet(myStyleSheet(self))
		self.editor.setTabStopWidth(14)
		self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
		self.editor.customContextMenuRequested.connect(self.contextMenuRequested)
		
		self.createActions()
		self.createTollbarActions()
		self.createToolbar()
		self.createMenubar()

	def createTollbarActions(self):
		self.newAct = QAction("&New", self, shortcut=QKeySequence.New,statusTip="create a new file", triggered=self.newFile)
		self.newAct.setIcon(QIcon.fromTheme("gtk-new"))
		self.openAct = QAction("&Open", self, shortcut=QKeySequence.Open,statusTip="open file", triggered=self.openFile)
		self.openAct.setIcon(QIcon.fromTheme("gtk-open"))
		self.importRTFAct = QAction(QIcon.fromTheme("gnome-mime-application-rtf"), "import RTF", self, statusTip="import RTF File", triggered=self.importRTF)
		self.saveAct = QAction("&Save", self, shortcut=QKeySequence.Save,statusTip="save file", triggered=self.fileSave)
		self.saveAct.setIcon(QIcon.fromTheme("gtk-save"))
		self.saveAsAct = QAction("&Save as ...", self, shortcut=QKeySequence.SaveAs,statusTip="save file as ...", triggered=self.fileSaveAs)
		self.saveAsAct.setIcon(QIcon.fromTheme("gtk-save-as"))
		self.saveAsODFAct = QAction("&Save as OpenOffice Document", self, shortcut="Ctrl+Shift+e",statusTip="save file as OpenOffice Document", triggered=self.fileSaveAsODF)
		self.saveAsODFAct.setIcon(QIcon.fromTheme("libreoffice-writer"))
		self.pdfAct = QAction("export PDF", self, statusTip="save file as PDF",  triggered=self.exportPDF)
		self.pdfAct.setIcon(QIcon.fromTheme("application-pdf"))
				### print preview
		self.printPreviewAct = QAction("preview", self, shortcut=QKeySequence.Print,statusTip="Preview Document", triggered=self.handlePrintPreview)
		self.printPreviewAct.setIcon(QIcon.fromTheme("gtk-print-preview"))
		### print
		self.printAct = QAction("print", self, shortcut=QKeySequence.Print,statusTip="Print Document", triggered=self.handlePrint)
		self.printAct.setIcon(QIcon.fromTheme("gtk-print"))
		### show in Browser
		self.browserAct = QAction("preview in Browser", self, shortcut="F5",statusTip="preview in Browser", triggered=self.handleBrowser)
		self.browserAct.setIcon(QIcon.fromTheme("browser"))
		self.exitAct = QAction("Exit", self, shortcut=QKeySequence.Quit,statusTip="Exit", triggered=self.handleQuit)
		self.exitAct.setIcon(QIcon.fromTheme("application-exit"))
		self.repAllAct = QPushButton("replace all") 
		self.repAllAct.setIcon(QIcon.fromTheme("gtk-find-and-replace"))
		self.repAllAct.setStatusTip("replace all")
		self.repAllAct.clicked.connect(self.replaceAll)
		self.bgAct = QAction(QIcon.fromTheme("preferences-color-symbolic"),"change Background Color", \
						statusTip="change Background Color", triggered=self.changeBGColor)

	def createToolbar(self):		
			### begin toolbar
		self.file_tb = QToolBar(self)
		self.file_tb.setWindowTitle("File Toolbar")		
		self.file_tb.addAction(self.newAct)
		self.file_tb.addAction(self.openAct)
		self.file_tb.addSeparator() 
		self.file_tb.addAction(self.saveAct)
		self.file_tb.addAction(self.saveAsAct)
		self.file_tb.addSeparator() 
		self.file_tb.addAction(self.saveAsODFAct)
		self.file_tb.addSeparator()
		self.file_tb.addAction(self.pdfAct)
		self.file_tb.addSeparator()
		self.file_tb.addAction(self.printPreviewAct)
		self.file_tb.addAction(self.printAct) 
		self.file_tb.addSeparator()
		self.file_tb.addAction(self.browserAct) 
		self.file_tb.addSeparator()
		self.file_tb.addAction(QAction(QIcon.fromTheme('image'), "insert Image", self, statusTip="insert an image", triggered = self.insertImage))		
		### find / replace toolbar
		self.edit_tb = QToolBar(self)
		self.edit_tb.setWindowTitle("Find Toolbar")   
		self.findfield = QLineEdit()
		self.findfield.addAction(QIcon.fromTheme("gtk-find"), 0)
		self.findfield.setClearButtonEnabled(True)
		self.findfield.setFixedWidth(200)
		self.findfield.setPlaceholderText("find")
		self.findfield.setStatusTip("press RETURN to find")
		self.findfield.setText("")
		self.findfield.returnPressed.connect(self.findText)
		self.edit_tb.addWidget(self.findfield)
		self.replacefield = QLineEdit()
		self.replacefield.addAction(QIcon.fromTheme("gtk-find-and-replace"), 0)
		self.replacefield.setClearButtonEnabled(True)
		self.replacefield.setFixedWidth(200)
		self.replacefield.setPlaceholderText("replace with")
		self.replacefield.setStatusTip("press RETURN to replace the first")
		self.replacefield.returnPressed.connect(self.replaceOne)
		self.edit_tb.addSeparator() 
		self.edit_tb.addWidget(self.replacefield)
		self.edit_tb.addSeparator()
		self.edit_tb.addWidget(self.repAllAct)
		self.edit_tb.addSeparator()
		self.edit_tb.addAction(self.bgAct)
		### Format Toolbar
		self.format_tb = QToolBar(self)
		self.format_tb.setWindowTitle("Format Toolbar")
		
		self.actionTextBold = QAction(QIcon.fromTheme('format-text-bold-symbolic'), "&Bold", self, priority=QAction.LowPriority,
				shortcut=Qt.CTRL + Qt.Key_B, triggered=self.textBold, checkable=True)
		self.actionTextBold.setStatusTip("bold")
		bold = QFont()
		bold.setBold(True)
		self.actionTextBold.setFont(bold)
		self.format_tb.addAction(self.actionTextBold)	

		self.actionTextItalic = QAction(QIcon.fromTheme('format-text-italic-symbolic'), "&Italic", self, priority=QAction.LowPriority,
				shortcut=Qt.CTRL + Qt.Key_I, triggered=self.textItalic, checkable=True)
		italic = QFont()
		italic.setItalic(True)
		self.actionTextItalic.setFont(italic)
		self.format_tb.addAction(self.actionTextItalic)
		
		self.actionTextUnderline = QAction(QIcon.fromTheme('format-text-underline-symbolic'), "&Underline", self, priority=QAction.LowPriority,
				shortcut=Qt.CTRL + Qt.Key_U, triggered=self.textUnderline, checkable=True)
		underline = QFont()
		underline.setUnderline(True)
		self.actionTextUnderline.setFont(underline)
		self.format_tb.addAction(self.actionTextUnderline)
		
		self.format_tb.addSeparator()
		
		self.grp = QActionGroup(self, triggered=self.textAlign)

		if QApplication.isLeftToRight():
			self.actionAlignLeft = QAction(QIcon.fromTheme('format-justify-left-symbolic'),"&Left", self.grp)
			self.actionAlignCenter = QAction(QIcon.fromTheme('format-justify-center-symbolic'),"C&enter", self.grp)
			self.actionAlignRight = QAction(QIcon.fromTheme('format-justify-right-symbolic'),"&Right", self.grp)
		else:
			self.actionAlignRight = QAction(QIcon.fromTheme('gtk-justify-right-symbolic'),"&Right", self.grp)
			self.actionAlignCenter = QAction(QIcon.fromTheme('gtk-justify-center-symbolic'),"C&enter", self.grp)
			self.actionAlignLeft = QAction(QIcon.fromTheme('format-justify-left-symbolic'),"&Left", self.grp)
 
		self.actionAlignJustify = QAction(QIcon.fromTheme('format-justify-fill-symbolic'),"&Justify", self.grp)

		self.actionAlignLeft.setShortcut(Qt.CTRL + Qt.Key_L)
		self.actionAlignLeft.setCheckable(True)
		self.actionAlignLeft.setPriority(QAction.LowPriority)

		self.actionAlignCenter.setShortcut(Qt.CTRL + Qt.Key_E)
		self.actionAlignCenter.setCheckable(True)
		self.actionAlignCenter.setPriority(QAction.LowPriority)

		self.actionAlignRight.setShortcut(Qt.CTRL + Qt.Key_R)
		self.actionAlignRight.setCheckable(True)
		self.actionAlignRight.setPriority(QAction.LowPriority)

		self.actionAlignJustify.setShortcut(Qt.CTRL + Qt.Key_J)
		self.actionAlignJustify.setCheckable(True)
		self.actionAlignJustify.setPriority(QAction.LowPriority)

		self.format_tb.addActions(self.grp.actions())

		#self.indentAct = QAction(QIcon.fromTheme("format-indent-more-symbolic"), "indent more", self, triggered = self.indentLine, shortcut = "F8")
#		self.indentLessAct = QAction(QIcon.fromTheme("format-indent-less-symbolic"), "indent less", self, triggered = self.indentLessLine, shortcut = "F9")
#		self.format_tb.addAction(self.indentAct)
#		self.format_tb.addAction(self.indentLessAct)
		
		pix = QPixmap(16, 16)
		pix.fill(Qt.black)
		self.actionTextColor = QAction(QIcon(pix), "TextColor...", self,
				triggered=self.textColor)
		self.format_tb.addSeparator()
		self.format_tb.addAction(self.actionTextColor)
		
		self.font_tb = QToolBar(self)
		self.font_tb.setAllowedAreas(Qt.TopToolBarArea | Qt.BottomToolBarArea)
		self.font_tb.setWindowTitle("Font Toolbar")
		
		self.comboStyle = QComboBox(self.font_tb)
		self.font_tb.addWidget(self.comboStyle)
		self.comboStyle.addItem("Standard")
		self.comboStyle.addItem("Bullet List (Disc)")
		self.comboStyle.addItem("Bullet List (Circle)")
		self.comboStyle.addItem("Bullet List (Square)")
		self.comboStyle.addItem("Ordered List (Decimal)")
		self.comboStyle.addItem("Ordered List (Alpha lower)")
		self.comboStyle.addItem("Ordered List (Alpha upper)")
		self.comboStyle.addItem("Ordered List (Roman lower)")
		self.comboStyle.addItem("Ordered List (Roman upper)")
		self.comboStyle.activated.connect(self.textStyle)

		self.comboFont = QFontComboBox(self.font_tb)
		self.font_tb.addSeparator()
		self.font_tb.addWidget(self.comboFont)
		self.comboFont.activated[str].connect(self.textFamily)

		self.comboSize = QComboBox(self.font_tb)
		self.font_tb.addSeparator()
		self.comboSize.setObjectName("comboSize")
		self.font_tb.addWidget(self.comboSize)
		self.comboSize.setEditable(True)

		db = QFontDatabase()
		for size in db.standardSizes():
			self.comboSize.addItem("%s" % (size))
		self.comboSize.addItem("%s" % (90))
		self.comboSize.addItem("%s" % (100))
		self.comboSize.addItem("%s" % (160))
		self.comboSize.activated[str].connect(self.textSize)
		self.comboSize.setCurrentIndex(
				self.comboSize.findText(
						"%s" % (QApplication.font().pointSize())))	
		self.addToolBar(self.file_tb)	
		self.addToolBar(self.format_tb)	
#		self.addToolBarBreak(Qt.TopToolBarArea)
		self.addToolBar(self.font_tb)

	def importRTF(self):
		path, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.homePath() + "/Documents/",
					"RTF Files (*.rtf)")
		if path:
			inFile = QFile(path)
			if path.endswith(".rtf"):
				os.system("cd /tmp;libreoffice --headless --convert-to html '" + path + "'")
				newfile = "/tmp/" + self.strippedName(path).replace(".rtf", ".html")
				self.openFileOnStart(newfile)
				self.statusBar().showMessage("File is in '/tmp' *** please use 'save as ...'")

	def msgbox(self,title, message):
		QMessageBox.warning(self, title, message)

	def indentLine(self):
		if not self.editor.textCursor().selectedText() == "":
			ot = self.editor.textCursor().selection().toHtml()
			self.msgbox("HTML", str(ot))
#			self.editor.textCursor().insertText(tab)
#			self.editor.textCursor().insertHtml(QTextDocumentFragment.toHtml(ot))
#			self.setModified(True)
		
	def indentLessLine(self):
		if not self.editor.textCursor().selectedText() == "":
			newline = u"\u2029"
			list = []
			ot = self.editor.textCursor().selectedText()
			theList  = ot.splitlines()
			linecount = ot.count(newline)
			for i in range(linecount + 1):
				list.insert(i, (theList[i]).replace(tab, "", 1))
			self.editor.textCursor().insertText(newline.join(list))
			self.setModified(True)	

	def createMenubar(self):		
		bar=self.menuBar()
		
		self.filemenu=bar.addMenu("File")
		self.filemenu.addAction(QIcon.fromTheme("dialog-question"),"about PyEdit", self.about, shortcut = "Ctrl+i")
		self.separatorAct = self.filemenu.addSeparator()
		self.filemenu.addAction(self.newAct)
		self.filemenu.addAction(self.openAct)
		self.filemenu.addAction(self.importRTFAct)
		self.filemenu.addAction(self.saveAct)
		self.filemenu.addAction(self.saveAsAct)
		self.filemenu.addSeparator()
		self.filemenu.addAction(self.saveAsODFAct)
		self.filemenu.addSeparator()
		self.filemenu.addAction(QIcon.fromTheme("application-pdf"),"export PDF", self.exportPDF)
		self.filemenu.addSeparator()
		for i in range(self.MaxRecentFiles):
			self.filemenu.addAction(self.recentFileActs[i])
		self.updateRecentFileActions()
		self.filemenu.addSeparator()
		self.clearRecentAct = QAction("clear Recent Files List", self, triggered=self.clearRecentFiles)
		self.clearRecentAct.setIcon(QIcon.fromTheme("edit-clear"))
		self.filemenu.addAction(self.clearRecentAct)
		self.filemenu.addSeparator()
		self.filemenu.addAction(QAction(QIcon.fromTheme('html'), "get HTML (Document)", self, triggered = self.getHTML))
		self.filemenu.addSeparator()
		self.filemenu.addAction(self.exitAct)
#		bar.setStyleSheet(myStyleSheet(self))
		editmenu = bar.addMenu("Edit")
		editmenu.addAction(QAction(QIcon.fromTheme('edit-undo'), "Undo", self, triggered = self.editor.undo, shortcut = "Ctrl+u"))
		editmenu.addAction(QAction(QIcon.fromTheme('edit-redo'), "Redo", self, triggered = self.editor.redo, shortcut = "Shift+Ctrl+u"))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('edit-copy'), "Copy", self, triggered = self.editor.copy, shortcut = QKeySequence.Copy))
		editmenu.addAction(QAction(QIcon.fromTheme('edit-cut'), "Cut", self, triggered = self.editor.cut, shortcut = QKeySequence.Cut))
		editmenu.addAction(QAction(QIcon.fromTheme('edit-paste'), "Paste", self, triggered = self.editor.paste, shortcut = QKeySequence.Paste))
		editmenu.addAction(QAction(QIcon.fromTheme('edit-delete'), "Delete", self, triggered = self.editor.cut, shortcut = QKeySequence.Delete))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('edit-select-all'), "Select All", self, triggered = self.editor.selectAll, shortcut = QKeySequence.SelectAll))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('edit-copy'), "grab selected line", self, triggered = self.grabLine))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('image'), "insert Image", self, triggered = self.insertImage))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('input-tablet'), "insert Table (2 Column)", self, triggered = self.insertTable))
		editmenu.addAction(QAction(QIcon.fromTheme('input-tablet'), "insert Table (3 Column)", self, triggered = self.insertTable3))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('text-html'), "convert from HTML", self, triggered = self.convertfromHTML, shortcut ="F10"))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('browser'), "insert Link", self, triggered = self.insertLink))
		editmenu.addAction(QAction(QIcon.fromTheme('browser'), "edit Link", self, triggered = self.editLink))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('input-tablet'), "edit body style", self, triggered = self.editBody))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('input-tablet'), "edit HTML (selected Text)", self, triggered = self.editHTML))
		editmenu.addSeparator()
		editmenu.addAction(QAction(QIcon.fromTheme('stock_calendar'), "insert Date", self, triggered = self.insertDate))
		editmenu.addAction(QAction(QIcon.fromTheme('stock_calendar'), "insert Time", self, triggered = self.insertTime))
		editmenu.addAction(QAction(QIcon.fromTheme('stock_calendar'), "insert Date && Time", self, triggered = self.insertDateTime))
		editmenu.addSeparator()
		editmenu.addAction(self.bgAct)

		self.formatMenu = QMenu("F&ormat", self)
		self.formatMenu.addAction(self.actionTextBold)
		self.formatMenu.addAction(self.actionTextItalic)
		self.formatMenu.addAction(self.actionTextUnderline)
		self.formatMenu.addSeparator()
		self.formatMenu.addActions(self.grp.actions())
		self.formatMenu.addSeparator()
		self.formatMenu.addAction(self.actionTextColor)
		bar.addMenu(self.formatMenu)
		# Laying out...
		layoutV = QVBoxLayout()
		layoutV.addWidget(self.edit_tb)
		layoutV.addWidget(self.editor)
		### main window
		mq = QWidget(self)
		mq.setLayout(layoutV)
		self.setCentralWidget(mq)
		self.statusBar().showMessage("Welcome to RichTextEdit * " \
							+ QSysInfo.prettyProductName() + " * "  + \
							QSysInfo.kernelVersion() + " * " + QSysInfo.machineHostName() + " * " + \
							QSysInfo.currentCpuArchitecture() + " * " + QSysInfo.buildAbi())
		# Event Filter ...
		self.installEventFilter(self)
		self.cursor = QTextCursor()
		self.editor.setTextCursor(self.cursor)
		
		self.editor.setPlainText(self.mainText)
		self.editor.moveCursor(self.cursor.End)
		self.editor.textCursor().deletePreviousChar()
		self.editor.document().modificationChanged.connect(self.setWindowModified)
		self.extra_selections = []
		self.fname = ""
		self.filename = ""
		self.editor.setFocus()
		self.setModified(False)
		self.fontChanged(self.editor.font())
		self.colorChanged(self.editor.textColor())
		self.alignmentChanged(self.editor.alignment())
		self.editor.document().modificationChanged.connect(
				self.setWindowModified)

		self.setWindowModified(self.editor.document().isModified())
		self.editor.setAcceptRichText(True)
		self.editor.currentCharFormatChanged.connect(
				self.currentCharFormatChanged)
		self.editor.cursorPositionChanged.connect(self.cursorPositionChanged)
#		QApplication.clipboard().dataChanged.connect(self.clipboardDataChanged)

	def insertDate(self):
		import time
		from datetime import date
		today = date.today().strftime("%A, %d.%B %Y")
		self.editor.textCursor().insertText(today)

	def insertTime(self):
		import time
		from datetime import date
		today = time.strftime("%H:%M Uhr")
		self.editor.textCursor().insertText(today)

	def insertDateTime(self):
		self.insertDate()
		self.editor.textCursor().insertText(eof)
		self.insertTime()
		self.editor.textCursor().insertText(eof)

	def changeBGColor(self):
		all = self.editor.document().toHtml()
		bgcolor = all.partition("<body style=")[2].partition(">")[0].partition('bgcolor="')[2].partition('"')[0]
		if not bgcolor == "":
			col = QColorDialog.getColor(QColor(bgcolor), self)
			if not col.isValid():
				return
			else:
				colorname = col.name()
				new = all.replace("bgcolor=" + '"' + bgcolor + '"', "bgcolor=" + '"' + colorname + '"')
				self.editor.document().setHtml(new)
		else:
			col = QColorDialog.getColor(QColor("#FFFFFF"), self)
			if not col.isValid():
				return
			else:
				all = self.editor.document().toHtml()
				body = all.partition("<body style=")[2].partition(">")[0]
				newbody = body + "bgcolor=" + '"' + col.name() + '"'
				new = all.replace(body, newbody)	
				self.editor.document().setHtml(new)

	def getHTML(self):
		all = self.editor.document().toHtml()
		clipboard = QApplication.clipboard()
		clipboard.setText(all)
		self.statusBar().showMessage("HTML is in clipboard")

	def editHTML(self):
		all = self.editor.textCursor().selection().toHtml()
		dlg = QInputDialog(self, Qt.Window)
		dlg.setGeometry(50, 50, 700, 400)
		dlg.setOption(QInputDialog.UsePlainTextEditForTextInput)
		new, ok = dlg.getMultiLineText(self, 'change HTML', "edit HTML", all)
		if ok:
			self.editor.textCursor().insertHtml(new)
			self.statusBar().showMessage("HTML changed")
		else:
			self.statusBar().showMessage("HTML not changed")

	def editBody(self):
		all = self.editor.document().toHtml()
		body = all.partition("<body style=")[2].partition(">")[0]
		dlg = QInputDialog()
		mybody, ok = dlg.getText(self, 'change body style', "", QLineEdit.Normal, body, Qt.Dialog)
		if ok:
			new = all.replace(body, mybody)	
			self.editor.document().setHtml(new)
			self.statusBar().showMessage("body style changed")
		else:
			self.statusBar().showMessage("body style not changed")
		
	def insertTable(self):
		self.editor.append(tableheader2)

	def insertTable3(self):
		self.editor.append(tableheader3)

	def handleBrowser(self):
		if self.editor.toPlainText() == "":
			self.statusBar().showMessage("no text")
		else:
			if not self.editor.document().isModified() == True:
				webbrowser.open(self.filename, new=0, autoraise=True)
			else:
				myfilename = "/tmp/browser.html"
				writer = QTextDocumentWriter(myfilename)
				success = writer.write(self.editor.document())
				if success:
					webbrowser.open(myfilename, new=0, autoraise=True)
				return success

	def contextMenuRequested(self,point):
		cmenu = QMenu()
		cmenu = self.editor.createStandardContextMenu()
		cmenu.addSeparator()
		cmenu.addAction(QAction(QIcon.fromTheme('edit-copy'), "grab this line", self, triggered = self.grabLine))
		cmenu.addSeparator()
		cmenu.addAction(QAction(QIcon.fromTheme('image-x-generic'), "insert Image", self, triggered = self.insertImage))
		cmenu.addSeparator()
		cmenu.addAction(QAction(QIcon.fromTheme('input-tablet'), "insert Table (2 Column)", self, triggered = self.insertTable))
		cmenu.addAction(QAction(QIcon.fromTheme('input-tablet'), "insert Table (3 Column)", self, triggered = self.insertTable3))#
		cmenu.addSeparator()
		cmenu.addAction(QAction(QIcon.fromTheme('text-html'), "convert from HTML", self, triggered = self.convertfromHTML))
		cmenu.addSeparator()
		cmenu.addAction(QAction(QIcon.fromTheme('text-plain'), "convert to Text", self, triggered = self.convertToHTML))
		cmenu.addSeparator()
		cmenu.addAction(QAction(QIcon.fromTheme('browser'), "insert Link", self, triggered = self.insertLink))
		cmenu.addAction(QAction(QIcon.fromTheme('browser'), "edit Link", self, triggered = self.editLink))
		cmenu.addSeparator()
		cmenu.addAction(QAction(QIcon.fromTheme('input-tablet'), "edit HTML (selected Text)", self, triggered = self.editHTML))
		cmenu.addSeparator()
		cmenu.addAction(self.bgAct)
		cmenu.exec_(self.editor.mapToGlobal(point))

	def editLink(self):
		if not self.editor.textCursor().selectedText() == "":
			mt = self.editor.textCursor().selectedText()
			mytext = QTextDocumentFragment.toHtml(self.editor.textCursor().selection())
			myurl = mytext.partition('<a href="')[2].partition('">')[0]
			dlg = QInputDialog()
			dlg.setOkButtonText("Change")
			mylink, ok = dlg.getText(self, 'change URL', "", QLineEdit.Normal, str(myurl), Qt.Dialog)
			if ok:
				if mylink.startswith("http"):
					self.editor.textCursor().insertHtml("<a href='" + mylink + "' target='_blank'>" + mt + "</a>")
					self.statusBar().showMessage("link added")
				else:
					self.statusBar().showMessage("this is no link")
			else:
				self.statusBar().showMessage("not changed")
		else:
			self.statusBar().showMessage("no text selected")

	def insertLink(self):
		if not self.editor.textCursor().selectedText() == "":
			mytext = self.editor.textCursor().selectedText()
			dlg = QInputDialog()
			mylink, ok = dlg.getText(self, 'insert URL', "", QLineEdit.Normal, "", Qt.Dialog)
			if ok:
				if str(mylink).startswith("http"):
					self.editor.textCursor().insertHtml("<a href='" + mylink + "' target='_blank'>" + mytext + "</a>")
					self.statusBar().showMessage("link added")
				else:
					self.statusBar().showMessage("this is no link")
			else:
				self.statusBar().showMessage("no link added")
		else:
			self.statusBar().showMessage("no text selected")

	def convertfromHTML(self):
		oldtext = self.editor.textCursor().selectedText()
		self.editor.textCursor().insertHtml(oldtext)
		self.statusBar().showMessage("converted to html")

	def convertToHTML(self):
		oldtext = QTextDocumentFragment.fromHtml(self.editor.textCursor().selectedText())
		self.editor.textCursor().insertText(oldtext.toPlainText())
		self.statusBar().showMessage("converted to plain text")

	def insertImage(self):
		path, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.homePath() + "/Pictures/",
					"Images (*.png *.PNG *.jpg *.JPG *.bmp *.BMP *.xpm *.gif *.eps)")
		if path:
			#self.editor.append("<img src='" + path + "'/>")
			self.editor.textCursor().insertImage("file://" + path)
			self.statusBar().showMessage("'" + path + "' inserted")
		else:
			self.statusBar().showMessage("no image")
	
	def grabLine(self):
		text = self.editor.textCursor().block().text()
		clipboard = QApplication.clipboard()
		clipboard.setText(text)
		
	def about(self):
		link = "<p><a title='Axel Schneider' href='http://goodoldsongs.jimdo.com' target='_blank'>Axel Schneider</a></p>"
		title = "about RichTextEdit"
		message =  "<span style='color: #1F9CDD; font-size: 24pt;font-weight: bold;'\
					>RichTextEdit 1.0</strong></span></p><br>created by<h3>" + link + "</h3> with PyQt5<br>" \
					+ "<br>Copyright © 2017 The Qt Company Ltd and other contributors." \
					+ "<br>Qt and the Qt logo are trademarks of The Qt Company Ltd."
		msg = QMessageBox(QMessageBox.Information, title, message, QMessageBox.NoButton, self, Qt.Dialog|Qt.NoDropShadowWindowHint).show()
		
	def createActions(self):
		for i in range(self.MaxRecentFiles):
			self.recentFileActs.append(
				   QAction(self, visible=False,
							triggered=self.openRecentFile))
			
	def openRecentFile(self):
		action = self.sender()
		if action:
			if (self.maybeSave()):
				self.openFileOnStart(action.data())
			
		### New File
	def newFile(self):
		if self.maybeSave():
			self.editor.clear()
			self.editor.setPlainText(self.mainText)
			self.filename = ""
			
			self.editor.moveCursor(self.cursor.End)
			self.editor.textCursor().deletePreviousChar()
			self.setWindowTitle("New[*]")
			self.setModified(False)
			
	   ### open File
	def openFileOnStart(self, path=None):
		if path:
			inFile = QFile(path)
			if inFile.open(QFile.ReadWrite | QFile.Text):
				data = inFile.readAll()
				codec = QTextCodec.codecForHtml(data)
				unistr = codec.toUnicode(data)

				if Qt.mightBeRichText(unistr):
					self.editor.setHtml(unistr)
				else:
					self.editor.setPlainText(unistr)
				self.filename = path
				self.setModified(False)
				self.fname = QFileInfo(path).fileName() 
				self.document = self.editor.document()
				self.setCurrentFile(path)
				self.statusBar().showMessage("loaded file '" + path + "'")
		
		### open File
	def openFile(self, path=None):
		if self.maybeSave():
			if not path:
				path, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.homePath() + "/Documents/",
					"RichText Files (*.htm *.html);; Text Files (*.txt *.csv *.py);;All Files (*.*)")
			if path:
				inFile = QFile(path)
				self.openFileOnStart(path)

	def exportPDF(self):
		if self.editor.toPlainText() == "":
			self.statusBar().showMessage("no text")
		else:
			newname = self.strippedName(self.filename).replace(".html", ".pdf")
			fn, _ = QFileDialog.getSaveFileName(self,
					"PDF files (*.pdf);;All Files (*)", (QDir.homePath() + "/PDF/" + newname))
			printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
			printer.setOutputFormat(QtPrintSupport.QPrinter.PdfFormat)
			printer.setOutputFileName(fn)
			self.editor.document().print_(printer)

			### save
	def fileSave(self):
		if not self.filename:
			return self.fileSaveAs()
		
		if self.isModified():
			writer = QTextDocumentWriter(self.filename)
			success = writer.write(self.editor.document())
			if success:
				self.editor.document().setModified(False)
				self.setCurrentFile(self.filename)
				self.statusBar().showMessage("saved file '" + self.filename + "'")
			return success
		else:
			self.statusBar().showMessage("already saved")

	def fileSaveODF(self, fn):
		writer = QTextDocumentWriter(fn)
#		writer.setFormat("ODF")
		success = writer.write(self.editor.document())
		if success:
			self.statusBar().showMessage("saved file '" + fn + "'")
		return success

	def fileSaveAs(self):
		if self.editor.toPlainText() == "":
			self.statusBar().showMessage("no text")
		else:
			fn, _ = QFileDialog.getSaveFileName(self, "Save as...", self.filename,
				"HTML-Files (*.html *.htm)")

			if not fn:
				return False

			lfn = fn.lower()
			if not lfn.endswith(('.htm', '.html')):
				fn += '.html'
			self.filename = fn
			return self.fileSave()

	def fileSaveAsODF(self):
		if self.editor.toPlainText() == "":
			self.statusBar().showMessage("no text")
		else:
			fn, _ = QFileDialog.getSaveFileName(self, "Save as...", self.strippedName(self.filename).replace(".html",""),
				"OpenOffice-Files (*.odt)")

			if not fn:
				return False

			lfn = fn.lower()
			if not lfn.endswith(('.odt')):
				fn += '.odt'
			return self.fileSaveODF(fn)

	def closeEvent(self, e):
		if self.maybeSave():
			e.accept()
		else:
			e.ignore()
		
		### ask to save
	def maybeSave(self):
		if not self.isModified():
			return True

		if self.filename.startswith(':/'):
			return True

		ret = QMessageBox.question(self, "Message",
				"<h4><p>The document was modified.</p>\n" \
				"<p>Do you want to save changes?</p></h4>",
				QMessageBox.Yes | QMessageBox.Discard | QMessageBox.Cancel)

		if ret == QMessageBox.Yes:
			if self.filename == "":
				self.fileSaveAs()
				return False
			else:
				self.fileSave()
				return True

		if ret == QMessageBox.Cancel:
			return False

		return True  
	
	def findText(self):
		word = self.findfield.text()
		if self.editor.find(word):
			return
		else:
			self.editor.moveCursor(QTextCursor.Start)			
			if self.editor.find(word):
				return
		
	def handleQuit(self):
		print("Goodbye ...")
		app.quit()

	def document(self):
		return self.editor.document
		
	def isModified(self):
		return self.editor.document().isModified()

	def setModified(self, modified):
		self.editor.document().setModified(modified)

	def setLineWrapMode(self, mode):
		self.editor.setLineWrapMode(mode)

	def clear(self):
		self.editor.clear()

	def setPlainText(self, *args, **kwargs):
		self.editor.setPlainText(*args, **kwargs)

	def setDocumentTitle(self, *args, **kwargs):
		self.editor.setDocumentTitle(*args, **kwargs)

	def set_number_bar_visible(self, value):
		self.numbers.setVisible(value)
		
	def replaceAll(self):
		oldtext = self.findfield.text()
		newtext = self.replacefield.text()
		if not oldtext == "":
			h = self.editor.toHtml().replace(oldtext, newtext)
			self.editor.setText(h)
			self.setModified(True)
			self.statusBar().showMessage("all replaced")
		else:
			self.statusBar().showMessage("nothing to replace")
		
	def replaceOne(self):
		oldtext = self.findfield.text()
		newtext = self.replacefield.text()
		if not oldtext == "":
			h = self.editor.toHtml().replace(oldtext, newtext, 1)
			self.editor.setText(h)
			self.setModified(True)
			self.statusBar().showMessage("one replaced")
		else:
			self.statusBar().showMessage("nothing to replace")
		
	def setCurrentFile(self, fileName):
		self.filename = fileName
		if self.filename:
			self.setWindowTitle(self.strippedName(self.filename) + "[*]")
		else:
			self.setWindowTitle("no File")	  
		
		files = self.settings.value('recentFileList', [])

		try:
			files.remove(fileName)
		except ValueError:
			pass

		files.insert(0, fileName)
		del files[self.MaxRecentFiles:]

		self.settings.setValue('recentFileList', files)

		for widget in QApplication.topLevelWidgets():
			if isinstance(widget, myEditor):
				widget.updateRecentFileActions()

	def updateRecentFileActions(self):
		mytext = ""
		files = self.settings.value('recentFileList', [])
		numRecentFiles = min(len(files), self.MaxRecentFiles)

		for i in range(numRecentFiles):
			text = "&%d %s" % (i + 1, self.strippedName(files[i]))
			self.recentFileActs[i].setText(text)
			self.recentFileActs[i].setData(files[i])
			self.recentFileActs[i].setVisible(True)
			self.recentFileActs[i].setIcon(QIcon.fromTheme("gnome-mime-text-x"))
			
		for j in range(numRecentFiles, self.MaxRecentFiles):
			self.recentFileActs[j].setVisible(False)

		self.separatorAct.setVisible((numRecentFiles > 0))
			
	def clearRecentFiles(self, fileName):
		self.settings.clear()
		self.updateRecentFileActions()
		
	def strippedName(self, fullFileName):
		return QFileInfo(fullFileName).fileName()

	def textBold(self):
		fmt = QTextCharFormat()
		fmt.setFontWeight(self.actionTextBold.isChecked() and QFont.Bold or QFont.Normal)
		self.mergeFormatOnWordOrSelection(fmt)

	def textUnderline(self):
		fmt = QTextCharFormat()
		fmt.setFontUnderline(self.actionTextUnderline.isChecked())
		self.mergeFormatOnWordOrSelection(fmt)

	def textItalic(self):
		fmt = QTextCharFormat()
		fmt.setFontItalic(self.actionTextItalic.isChecked())
		self.mergeFormatOnWordOrSelection(fmt)

	def textFamily(self, family):
		fmt = QTextCharFormat()
		fmt.setFontFamily(family)
		self.mergeFormatOnWordOrSelection(fmt)

	def textSize(self, pointSize):
		pointSize = float(self.comboSize.currentText())
		if pointSize > 0:
			fmt = QTextCharFormat()
			fmt.setFontPointSize(pointSize)
			self.mergeFormatOnWordOrSelection(fmt)

	def textStyle(self, styleIndex):
		cursor = self.editor.textCursor()
		if styleIndex:
			styleDict = {
				1: QTextListFormat.ListDisc,
				2: QTextListFormat.ListCircle,
				3: QTextListFormat.ListSquare,
				4: QTextListFormat.ListDecimal,
				5: QTextListFormat.ListLowerAlpha,
				6: QTextListFormat.ListUpperAlpha,
				7: QTextListFormat.ListLowerRoman,
				8: QTextListFormat.ListUpperRoman,
						}

			style = styleDict.get(styleIndex, QTextListFormat.ListDisc)
			cursor.beginEditBlock()
			blockFmt = cursor.blockFormat()
			listFmt = QTextListFormat()

			if cursor.currentList():
				listFmt = cursor.currentList().format()
			else:
				listFmt.setIndent(1)
				blockFmt.setIndent(0)
				cursor.setBlockFormat(blockFmt)

			listFmt.setStyle(style)
			cursor.createList(listFmt)
			cursor.endEditBlock()
		else:
			bfmt = QTextBlockFormat()
			bfmt.setObjectIndex(-1)
			cursor.mergeBlockFormat(bfmt)

	def textColor(self):
		col = QColorDialog.getColor(self.editor.textColor(), self)
		if not col.isValid():
			return

		fmt = QTextCharFormat()
		fmt.setForeground(col)
		self.mergeFormatOnWordOrSelection(fmt)
		self.colorChanged(col)

	def textAlign(self, action):
		if action == self.actionAlignLeft:
			self.editor.setAlignment(Qt.AlignLeft | Qt.AlignAbsolute)
		elif action == self.actionAlignCenter:
			self.editor.setAlignment(Qt.AlignHCenter)
		elif action == self.actionAlignRight:
			self.editor.setAlignment(Qt.AlignRight | Qt.AlignAbsolute)
		elif action == self.actionAlignJustify:
			self.editor.setAlignment(Qt.AlignJustify)

	def currentCharFormatChanged(self, format):
		self.fontChanged(format.font())
		self.colorChanged(format.foreground().color())

	def cursorPositionChanged(self):
		self.alignmentChanged(self.editor.alignment())

	def clipboardDataChanged(self):
		self.actionPaste.setEnabled(len(QApplication.clipboard().text()) != 0)

	def mergeFormatOnWordOrSelection(self, format):
		cursor = self.editor.textCursor()
		if not cursor.hasSelection():
			cursor.select(QTextCursor.WordUnderCursor)

		cursor.mergeCharFormat(format)
		self.editor.mergeCurrentCharFormat(format)

	def fontChanged(self, font):
		self.comboFont.setCurrentIndex(
				self.comboFont.findText(QFontInfo(font).family()))
		self.comboSize.setCurrentIndex(
				self.comboSize.findText("%s" % font.pointSize()))
		self.actionTextBold.setChecked(font.bold())
		self.actionTextItalic.setChecked(font.italic())
		self.actionTextUnderline.setChecked(font.underline())

	def colorChanged(self, color):
		pix = QPixmap(26, 20)
		pix.fill(color)
		self.actionTextColor.setIcon(QIcon(pix))

	def alignmentChanged(self, alignment):
		if alignment & Qt.AlignLeft:
			self.actionAlignLeft.setChecked(True)
		elif alignment & Qt.AlignHCenter:
			self.actionAlignCenter.setChecked(True)
		elif alignment & Qt.AlignRight:
			self.actionAlignRight.setChecked(True)
		elif alignment & Qt.AlignJustify:
			self.actionAlignJustify.setChecked(True)

	def handlePrint(self):
		if self.editor.toPlainText() == "":
			self.statusBar().showMessage("no text")
		else:
			dialog = QtPrintSupport.QPrintDialog()
			if dialog.exec_() == QDialog.Accepted:
				self.handlePaintRequest(dialog.printer())
				self.statusBar().showMessage("Document printed")
			
	def handlePrintPreview(self):
		if self.editor.toPlainText() == "":
			self.statusBar().showMessage("no text")
		else:
			dialog = QtPrintSupport.QPrintPreviewDialog()
			dialog.setGeometry(30, 0, self.width() - 60, self.height() - 60)
			dialog.paintRequested.connect(self.handlePaintRequest)
			dialog.exec_()
			self.statusBar().showMessage("Print Preview closed")

	def handlePaintRequest(self, printer):
		printer.setDocName(self.filename)
		document = self.editor.document()
		document.print_(printer)
  
def myStyleSheet(self):
	return """
QTextEdit
{
background: #fafafa;
color: #202020;
border: 1px solid #1EAE3D;
selection-background-color: #729fcf;
selection-color: #ffffff;
}
	"""	   

if __name__ == '__main__':
	app = QApplication(sys.argv)
	win = myEditor()
	win.setWindowIcon(QIcon.fromTheme("gnome-mime-application-rtf"))
	win.setWindowTitle("RichTextEdit" + "[*]")
	win.setMinimumSize(640,250)
	win.showMaximized()
	if len(sys.argv) > 1:
		print(sys.argv[1])
		win.openFileOnStart(sys.argv[1])
	app.exec_()



RE: RichTextEditor - Gribouillis - Jan-09-2018

Impressive effort, but I get
Traceback (most recent call last):
  File "RichTextEdit.py", line 12, in <module>
    import mycalendar
ImportError: No module named 'mycalendar'



RE: RichTextEditor - Axel_Erfurt - Jan-09-2018

sorry , remove the line, it was for a test.

I've updated the code in first post.


RE: RichTextEditor - Gribouillis - Jan-09-2018

Traceback (most recent call last):
  File "RichTextEdit.py", line 979, in <module>
    win = myEditor()
  File "RichTextEdit.py", line 39, in __init__
    self.createTollbarActions()
  File "RichTextEdit.py", line 73, in createTollbarActions
    statusTip="change Background Color", triggered=self.changeBGColor)
TypeError: arguments did not match any overloaded call:
  QAction(QObject): argument 1 has unexpected type 'QIcon'
  QAction(str, QObject): argument 1 has unexpected type 'QIcon'
  QAction(QIcon, str, QObject): not enough arguments
I'm using the Github code with python 3.5.2.


RE: RichTextEditor - Axel_Erfurt - Jan-09-2018

I get the same error on python 2, but not in python 3.5.2 on Linux


RE: RichTextEditor - Gribouillis - Jan-09-2018

Well I'm in Kubuntu 16.04.3 with python 3.5.2. I have the package python3-pyqt5 installed. All the packages are up to date, and I get this error. You could perhaps test it in a virtual host.

λ sudo dpkg -l | grep python3-pyqt5
ii  python3-pyqt5                                   5.5.1+dfsg-3ubuntu4                                      amd64        Python 3 bindings for Qt5



RE: RichTextEditor - Axel_Erfurt - Jan-09-2018

I get his:

[inline]sudo dpkg -l | grep python3-pyqt5
ii python3-pyqt5 5.5.1+dfsg-3ubuntu4 amd64 Python 3 bindings for Qt5
ii python3-pyqt5.qtmultimedia 5.5.1+dfsg-3ubuntu4 amd64 Python 3 bindings for Qt5's Multimedia module
ii python3-pyqt5.qtwebkit 5.5.1+dfsg-3ubuntu4 amd64 Python 3 bindings for Qt5's WebKit module[/inline]

are you sure using python 3?

python3 /path/to/RichTextEdit.py


RE: RichTextEditor - Gribouillis - Jan-09-2018

I'm trying to show a screenshot:
[Image: bMYku6]


RE: RichTextEditor - Axel_Erfurt - Jan-09-2018

replace the line self.bgAct = .... with:

		self.bgAct = QAction("change Background Color",self,  triggered=self.changeBGColor)
		self.bgAct.setStatusTip("change Background Color")
		self.bgAct.setIcon(QIcon.fromTheme("preferences-color-symbolic"))
that makes it working on python 2 for me.


RE: RichTextEditor - Gribouillis - Jan-09-2018

I finally got it working. I had to remove most of the welcome message because of attributes errors on QSysInfo around line 350.

Traceback (most recent call last):
  File "RichTextEdit.py", line 982, in <module>
    win = myEditor()
  File "RichTextEdit.py", line 41, in __init__
    self.createMenubar()
  File "RichTextEdit.py", line 352, in createMenubar
    QSysInfo.currentCpuArchitecture() + " * " + QSysInfo.buildAbi())
AttributeError: type object 'QSysInfo' has no attribute 'prettyProductName'
What? you're indenting python code with TAB characters instead of 4 SPACES! That's a very bad habit because most people use four spaces. You can configure your editor to insert 4 spaces when you hit the TAB key.

I had to reindent the code with the old Tim Peters' reindent script to replace the tabs and implement the changes you suggested (he is the guy who authored the Zen of python!).

The editor window looks quite usable. I'm missing a shortcut to insert titles.

Remark: you are using a lot of line continuation characters \ . You never need them in python. Using parentheses, one can easily write multiline statements such as
from foo import (a, b, c, d
    e, f, g, h,
    i, j, k, l,
)