Skip to content

Commit f278a6c

Browse files
committed
Initial revision
* ReText is a simple HTML editor
0 parents  commit f278a6c

File tree

2 files changed

+535
-0
lines changed

2 files changed

+535
-0
lines changed

retext.py

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
import sys
5+
from PyQt4.Qt import *
6+
from PyQt4.QtCore import *
7+
from PyQt4.QtGui import *
8+
9+
app_name = "ReText"
10+
app_version = "0.1.0 alpha"
11+
12+
class HtmlHighlighter(QSyntaxHighlighter):
13+
def __init__(self, parent):
14+
QSyntaxHighlighter.__init__(self, parent)
15+
16+
def highlightBlock(self, text):
17+
charFormat = QTextCharFormat()
18+
charFormat.setFontWeight(QFont.Bold)
19+
patterns = ("<[^>]*>", "&[^;]*;", "\"[^\"]*\"")
20+
foregrounds = [Qt.darkMagenta, Qt.darkCyan, Qt.darkYellow]
21+
for i in range(3):
22+
expression = QRegExp(patterns[i])
23+
index = expression.indexIn(text)
24+
charFormat.setForeground(foregrounds[i]);
25+
while (index >= 0):
26+
length = expression.matchedLength()
27+
self.setFormat(index, length, charFormat)
28+
index = expression.indexIn(text, index + length)
29+
30+
class MainWindow(QMainWindow):
31+
def __init__(self):
32+
QMainWindow.__init__(self)
33+
self.resize(800, 600)
34+
screen = QDesktopWidget().screenGeometry()
35+
size = self.geometry()
36+
self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2)
37+
self.setWindowTitle(self.tr('New document') + ' - '+app_name)
38+
self.setWindowIcon(QIcon.fromTheme('accessories-text-editor'))
39+
self.centralwidget = QWidget(self)
40+
self.verticalLayout = QVBoxLayout(self.centralwidget)
41+
self.previewBox = QTextEdit(self.centralwidget)
42+
self.previewBox.setVisible(False)
43+
self.previewBox.setReadOnly(True)
44+
self.verticalLayout.addWidget(self.previewBox)
45+
self.editBox = QTextEdit(self.centralwidget)
46+
self.editBox.setAcceptRichText(False)
47+
self.verticalLayout.addWidget(self.editBox)
48+
self.setCentralWidget(self.centralwidget)
49+
self.syntaxHighlighter = HtmlHighlighter(self.editBox.document())
50+
self.toolBar = QToolBar(self.tr('File toolbar'), self)
51+
self.addToolBar(Qt.TopToolBarArea, self.toolBar)
52+
self.editBar = QToolBar(self.tr('Edit toolbar'), self)
53+
self.addToolBar(Qt.TopToolBarArea, self.editBar)
54+
self.actionOpen = QAction(QIcon.fromTheme('document-open'), self.tr('Open'), self)
55+
self.actionOpen.setShortcut(QKeySequence.Open)
56+
self.connect(self.actionOpen, SIGNAL('triggered()'), self.openFile)
57+
self.actionSave = QAction(QIcon.fromTheme('document-save'), self.tr('Save'), self)
58+
self.actionSave.setEnabled(False)
59+
self.actionSave.setShortcut(QKeySequence.Save)
60+
self.connect(self.editBox.document(), SIGNAL('modificationChanged(bool)'), self.modificationChanged)
61+
self.connect(self.actionSave, SIGNAL('triggered()'), self.saveFile)
62+
self.actionSaveAs = QAction(QIcon.fromTheme('document-save-as'), self.tr('Save as'), self)
63+
self.actionSaveAs.setShortcut(QKeySequence.SaveAs)
64+
self.connect(self.actionSaveAs, SIGNAL('triggered()'), self.saveFileAs)
65+
self.actionPrint = QAction(QIcon.fromTheme('document-print'), self.tr('Print'), self)
66+
self.actionPrint.setShortcut(QKeySequence.Print)
67+
self.connect(self.actionPrint, SIGNAL('triggered()'), self.printFile)
68+
self.actionPreview = QAction(QIcon.fromTheme('x-office-document'), self.tr('Preview'), self)
69+
self.actionPreview.setCheckable(True)
70+
self.connect(self.actionPreview, SIGNAL('triggered(bool)'), self.preview)
71+
self.actionPerfectHtml = QAction(QIcon.fromTheme('text-html'), 'HTML', self)
72+
self.connect(self.actionPerfectHtml, SIGNAL('triggered()'), self.saveFilePerfect)
73+
self.actionPdf = QAction(QIcon.fromTheme('application-pdf'), 'PDF', self)
74+
self.connect(self.actionPdf, SIGNAL('triggered()'), self.savePdf)
75+
self.actionQuit = QAction(QIcon.fromTheme('application-exit'), self.tr('Quit'), self)
76+
self.actionQuit.setShortcut(QKeySequence.Quit)
77+
self.connect(self.actionQuit, SIGNAL('triggered()'), qApp, SLOT('quit()'))
78+
self.actionUndo = QAction(QIcon.fromTheme('edit-undo'), self.tr('Undo'), self)
79+
self.actionUndo.setShortcut(QKeySequence.Undo)
80+
self.actionRedo = QAction(QIcon.fromTheme('edit-redo'), self.tr('Redo'), self)
81+
self.actionRedo.setShortcut(QKeySequence.Redo)
82+
self.connect(self.actionUndo, SIGNAL('triggered()'), self.editBox, SLOT('undo()'))
83+
self.connect(self.actionRedo, SIGNAL('triggered()'), self.editBox, SLOT('redo()'))
84+
self.actionUndo.setEnabled(False)
85+
self.actionRedo.setEnabled(False)
86+
self.connect(self.editBox.document(), SIGNAL('undoAvailable(bool)'), self.actionUndo, SLOT('setEnabled(bool)'))
87+
self.connect(self.editBox.document(), SIGNAL('redoAvailable(bool)'), self.actionRedo, SLOT('setEnabled(bool)'))
88+
self.actionCopy = QAction(QIcon.fromTheme('edit-copy'), self.tr('Copy'), self)
89+
self.actionCopy.setShortcut(QKeySequence.Copy)
90+
self.actionCopy.setEnabled(False)
91+
self.actionCut = QAction(QIcon.fromTheme('edit-cut'), self.tr('Cut'), self)
92+
self.actionCut.setShortcut(QKeySequence.Cut)
93+
self.actionCut.setEnabled(False)
94+
self.actionPaste = QAction(QIcon.fromTheme('edit-paste'), self.tr('Paste'), self)
95+
self.actionPaste.setShortcut(QKeySequence.Paste)
96+
self.connect(self.actionCut, SIGNAL('triggered()'), self.editBox, SLOT('cut()'))
97+
self.connect(self.actionCopy, SIGNAL('triggered()'), self.editBox, SLOT('copy()'))
98+
self.connect(self.actionPaste, SIGNAL('triggered()'), self.editBox, SLOT('paste()'))
99+
self.connect(qApp.clipboard(), SIGNAL('dataChanged()'), self.clipboardDataChanged)
100+
self.clipboardDataChanged()
101+
self.actionEnableMarkup = QAction(self.tr('Enable markup'), self)
102+
self.actionEnableMarkup.setCheckable(True)
103+
self.actionEnableMarkup.setChecked(True)
104+
self.connect(self.actionEnableMarkup, SIGNAL('triggered(bool)'), self.enableMarkup)
105+
self.actionAbout = QAction(QIcon.fromTheme('help-about'), self.tr('About %1').arg(app_name), self)
106+
self.connect(self.actionAbout, SIGNAL('triggered()'), self.aboutDialog)
107+
self.actionAboutQt = QAction(self.tr('About Qt'), self)
108+
self.connect(self.actionAboutQt, SIGNAL('triggered()'), qApp, SLOT('aboutQt()'))
109+
self.usefulTags = ('a', 'b', 'center', 'h1', 'h2', 'h3', 'i', 'img', 's', 'span', 'table', 'td', 'tr', 'u')
110+
self.usefulChars = ('amp', 'gt', 'laquo', 'lt', 'minus', 'mdash', 'nbsp', 'ndash', 'quot', 'raquo')
111+
self.tagsBox = QComboBox(self.editBar)
112+
self.tagsBox.addItem(self.tr('Tags'))
113+
self.tagsBox.addItems(self.usefulTags)
114+
self.connect(self.tagsBox, SIGNAL('activated(int)'), self.insertTag)
115+
self.symbolBox = QComboBox(self.editBar)
116+
self.symbolBox.addItem(self.tr('Symbols'))
117+
self.symbolBox.addItems(self.usefulChars)
118+
self.connect(self.symbolBox, SIGNAL('activated(int)'), self.insertSymbol)
119+
self.menubar = QMenuBar(self)
120+
self.menubar.setGeometry(QRect(0, 0, 800, 25))
121+
self.setMenuBar(self.menubar)
122+
self.menuFile = self.menubar.addMenu(self.tr('File'))
123+
self.menuEdit = self.menubar.addMenu(self.tr('Edit'))
124+
self.menuHelp = self.menubar.addMenu(self.tr('Help'))
125+
self.menuFile.addAction(self.actionOpen)
126+
self.menuFile.addAction(self.actionSave)
127+
self.menuFile.addAction(self.actionSaveAs)
128+
self.menuFile.addSeparator()
129+
self.menuExport = self.menuFile.addMenu(self.tr('Export'))
130+
self.menuExport.addAction(self.actionPerfectHtml)
131+
self.menuExport.addAction(self.actionPdf)
132+
self.menuFile.addAction(self.actionPrint)
133+
self.menuFile.addSeparator()
134+
self.menuFile.addAction(self.actionQuit)
135+
self.menuEdit.addAction(self.actionUndo)
136+
self.menuEdit.addAction(self.actionRedo)
137+
self.menuEdit.addSeparator()
138+
self.menuEdit.addAction(self.actionCut)
139+
self.menuEdit.addAction(self.actionCopy)
140+
self.menuEdit.addAction(self.actionPaste)
141+
self.menuEdit.addSeparator()
142+
self.menuEdit.addAction(self.actionEnableMarkup)
143+
self.menuEdit.addAction(self.actionPreview)
144+
self.menuHelp.addAction(self.actionAbout)
145+
self.menuHelp.addAction(self.actionAboutQt)
146+
self.menubar.addMenu(self.menuFile)
147+
self.menubar.addMenu(self.menuEdit)
148+
self.menubar.addMenu(self.menuHelp)
149+
self.toolBar.addAction(self.actionOpen)
150+
self.toolBar.addAction(self.actionSave)
151+
self.toolBar.addAction(self.actionPrint)
152+
self.toolBar.addSeparator()
153+
self.toolBar.addAction(self.actionPreview)
154+
self.editBar.addAction(self.actionUndo)
155+
self.editBar.addAction(self.actionRedo)
156+
self.editBar.addSeparator()
157+
self.editBar.addAction(self.actionCut)
158+
self.editBar.addAction(self.actionCopy)
159+
self.editBar.addAction(self.actionPaste)
160+
self.editBar.addWidget(self.tagsBox)
161+
self.editBar.addWidget(self.symbolBox)
162+
self.useMarkup = True
163+
self.fileName = None
164+
165+
def preview(self, viewmode):
166+
self.editBar.setEnabled(not viewmode)
167+
self.editBox.setVisible(not viewmode)
168+
self.previewBox.setVisible(viewmode)
169+
if viewmode:
170+
self.previewBox.setHtml(self.parseText())
171+
172+
def openFile(self):
173+
filename = QFileDialog.getOpenFileName(self, self.tr("Open file"), "", self.tr("ReText files (*.re *.txt)"))
174+
if filename:
175+
openfile = QFile(filename)
176+
openfile.open(QIODevice.ReadOnly)
177+
openstream = QTextStream(openfile)
178+
html = openstream.readAll()
179+
openfile.close
180+
self.editBox.setPlainText(html)
181+
182+
def saveFile(self):
183+
self.saveFileMain(False)
184+
185+
def saveFileAs(self):
186+
self.saveFileMain(True)
187+
188+
def saveFileMain(self, dlg):
189+
if (not self.fileName) or dlg:
190+
self.fileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", self.tr("ReText files (*.re *.txt)"))
191+
if self.fileName:
192+
if QFileInfo(self.fileName).suffix().isEmpty():
193+
self.fileName.append(".re")
194+
savefile = QFile(self.fileName)
195+
savefile.open(QIODevice.WriteOnly)
196+
savestream = QTextStream(savefile)
197+
savestream.__lshift__(self.parseText())
198+
savefile.close()
199+
self.editBox.document().setModified(False)
200+
201+
def saveFilePerfect(self):
202+
if not self.fileName:
203+
self.fileName = QFileDialog.getSaveFileName(self, self.tr("Save file"), "", self.tr("HTML files (*.html *.htm)"))
204+
if self.fileName:
205+
if QFileInfo(self.fileName).suffix().isEmpty():
206+
self.fileName.append(".html")
207+
td = QTextDocument()
208+
td.setHtml(self.parseText())
209+
writer = QTextDocumentWriter(self.fileName)
210+
writer.write(td)
211+
212+
def savePdf(self):
213+
fileName = QFileDialog.getSaveFileName(self, self.tr("Export document to PDF"), "", self.tr("PDF files (*.pdf)"));
214+
if fileName:
215+
if QFileInfo(fileName).suffix().isEmpty():
216+
fileName.append(".pdf")
217+
printer = QPrinter(QPrinter.HighResolution)
218+
printer.setOutputFormat(QPrinter.PdfFormat)
219+
printer.setOutputFileName(fileName)
220+
printer.setCreator(app_name+" "+app_version)
221+
td = QTextDocument()
222+
td.setHtml(self.parseText())
223+
td.print_(printer)
224+
225+
def printFile(self):
226+
printer = QPrinter(QPrinter.HighResolution)
227+
printer.setCreator(app_name+" "+app_version)
228+
dlg = QPrintDialog(printer, self)
229+
dlg.setWindowTitle(self.tr("Print Document"))
230+
if (dlg.exec_() == QDialog.Accepted):
231+
td = QTextDocument()
232+
td.setHtml(self.parseText())
233+
td.print_(printer)
234+
235+
def getDocumentTitle(self):
236+
if self.fileName:
237+
return QFileInfo(self.fileName).fileName()
238+
else:
239+
return self.tr("New document")
240+
241+
def modificationChanged(self, changed):
242+
self.actionSave.setEnabled(changed)
243+
if changed:
244+
self.setWindowTitle(self.getDocumentTitle()+"* - "+app_name)
245+
else:
246+
self.setWindowTitle(self.getDocumentTitle()+" - "+app_name)
247+
248+
def clipboardDataChanged(self):
249+
self.actionPaste.setEnabled(qApp.clipboard().mimeData().hasText())
250+
251+
def insertTag(self, num):
252+
if num:
253+
ut = self.usefulTags[num-1]
254+
hc = not ut in ('img', 'td', 'tr')
255+
arg = ''
256+
if ut == 'a':
257+
arg = ' href=""'
258+
if ut == 'img':
259+
arg = ' src=""'
260+
if ut == 'img':
261+
arg = ' style=""'
262+
tc = self.editBox.textCursor()
263+
if hc:
264+
toinsert = '<'+ut+arg+'>'+tc.selectedText()+'</'+ut+'>'
265+
tc.removeSelectedText
266+
tc.insertText(toinsert)
267+
else:
268+
tc.insertText('<'+ut+arg+'>'+tc.selectedText())
269+
self.tagsBox.setCurrentIndex(0)
270+
271+
def insertSymbol(self, num):
272+
if num:
273+
self.editBox.insertPlainText('&'+self.usefulChars[num-1]+';')
274+
self.symbolBox.setCurrentIndex(0)
275+
276+
def maybeSave(self):
277+
if not self.editBox.document().isModified():
278+
return True
279+
ret = QMessageBox.warning(self, app_name, self.tr("The document has been modified.\nDo you want to save your changes?"), \
280+
QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)
281+
if ret == QMessageBox.Save:
282+
self.saveFileMain(False)
283+
return True
284+
elif ret == QMessageBox.Cancel:
285+
return False
286+
return True
287+
288+
def closeEvent(self, closeevent):
289+
if self.maybeSave():
290+
closeevent.accept()
291+
else:
292+
closeevent.ignore()
293+
294+
def aboutDialog(self):
295+
QMessageBox.about(self, self.tr('About %1').arg(app_name), self.tr('This is %1, version %2\nAuthor: Dmitry Shachnev, 2011').arg(app_name, app_version))
296+
297+
def enableMarkup(self, markup):
298+
self.useMarkup = markup
299+
300+
def parseText(self):
301+
htmltext = self.editBox.toPlainText()
302+
document = self.editBox.document()
303+
linecount = document.lineCount()
304+
if self.useMarkup:
305+
toinsert = ""
306+
ptagopened = False
307+
prevnotempty = False
308+
currentlist = 0
309+
for i in range(linecount):
310+
islist = 0
311+
line = document.findBlockByLineNumber(i).text()
312+
header = line.startsWith("<h")
313+
if not (line.isEmpty() or ptagopened or header):
314+
toinsert += "<p>"
315+
ptagopened = True
316+
if line.startsWith("* "):
317+
islist=1
318+
if line.startsWith("# "):
319+
islist=2
320+
if currentlist != islist:
321+
if currentlist == 1:
322+
toinsert += "</ul>"
323+
if currentlist == 2:
324+
toinsert += "</ol>"
325+
if islist == 1:
326+
toinsert += "<ul>"
327+
if islist == 2:
328+
toinsert += "<ol>"
329+
if line.isEmpty():
330+
if (ptagopened):
331+
toinsert += "</p>"
332+
ptagopened = False
333+
elif (currentlist==0 and islist==0 and prevnotempty and not header):
334+
toinsert += "<br>"
335+
currentlist = islist
336+
prevnotempty = not (line.isEmpty() or header)
337+
if islist:
338+
toinsert += QString("<li>%1</li>").arg(line.right(line.length()-2))
339+
else:
340+
toinsert += line
341+
if (currentlist==1):
342+
toinsert+="</ul>"
343+
if (currentlist==2):
344+
toinsert+="</ul>"
345+
if ptagopened:
346+
toinsert += "</p>"
347+
else:
348+
toinsert = htmltext
349+
return toinsert
350+
351+
def main():
352+
app = QApplication(sys.argv)
353+
RtTranslator = QTranslator()
354+
RtTranslator.load("retext_"+QLocale.system().name())
355+
QtTranslator = QTranslator()
356+
QtTranslator.load("qt_"+QLocale.system().name(), QLibraryInfo.location(QLibraryInfo.TranslationsPath))
357+
app.installTranslator(RtTranslator)
358+
app.installTranslator(QtTranslator)
359+
main = MainWindow()
360+
main.show()
361+
sys.exit(app.exec_())
362+
363+
if __name__ == '__main__':
364+
main()

0 commit comments

Comments
 (0)