add some interface function

This commit is contained in:
Tan, Kian-ting 2021-06-05 04:09:42 +08:00
parent 3a3c892c0c
commit a12b7fa15b
6 changed files with 422 additions and 69 deletions

View file

@ -0,0 +1,175 @@
import sys
from PyQt5.QtWidgets import *
from PyQt5.Qsci import QsciScintilla
class FindReplace(QDialog):
def __init__(self, parent):
super(QDialog, self).__init__()
self.parent = parent
self.find_label = QLabel("Find:")
self.find_line_edit = QLineEdit()
self.find_label.setBuddy(self.find_line_edit)
self.replace_label = QLabel("Replace:")
self.replace_line_edit = QLineEdit()
self.replace_label.setBuddy(self.replace_line_edit)
self.top_grid_layout = QGridLayout()
self.top_grid_layout.addWidget(self.find_label,0,0)
self.top_grid_layout.addWidget(self.find_line_edit,0,1)
self.top_grid_layout.addWidget(self.replace_label,1,0)
self.top_grid_layout.addWidget(self.replace_line_edit,1,1)
self.regex_checkbox = QCheckBox('Using regex',self)
self.case_sensitive_checkbox = QCheckBox('Case sensitive',self)
self.match_whole_word_checkbox = QCheckBox('Match whole word',self)
self.find_next_botton = QPushButton("Find next")
self.find_prev_botton = QPushButton("Find prev.")
self.replace_botton = QPushButton("Replace")
self.replace_all_botton = QPushButton("Replace all")
self.botton_layout = QHBoxLayout()
self.botton_layout.addWidget(self.find_next_botton)
self.botton_layout.addWidget(self.find_prev_botton)
self.botton_layout.addWidget(self.replace_botton)
self.botton_layout.addWidget(self.replace_all_botton)
self.main_layout = QVBoxLayout()
self.main_layout.addLayout(self.top_grid_layout)
self.main_layout.addWidget(self.regex_checkbox)
self.main_layout.addWidget(self.case_sensitive_checkbox)
self.main_layout.addWidget(self.match_whole_word_checkbox)
self.main_layout.addLayout(self.botton_layout)
self.setLayout(self.main_layout)
self.setWindowTitle("Find and replace")
self.set_action()
def set_action(self):
self.find_next_botton.clicked.connect(self.find_next_call)
self.find_prev_botton.clicked.connect(self.find_prev_call)
self.replace_botton.clicked.connect(self.replace_call)
self.replace_all_botton.clicked.connect(self.replace_all_call)
def find_next_call(self):
is_first_checked = True
is_regex = False
is_case_sensitive = False
is_matched_whole_word = False
is_wrap_search = True
text = self.find_line_edit.text()
if self.regex_checkbox.isChecked() == True:
is_regex = True
if self.case_sensitive_checkbox.isChecked() == True:
is_case_sensitive = True
if self.match_whole_word_checkbox.isChecked() == True:
is_matched_whole_word = True
if is_first_checked == True:
self.parent.editor.findFirst(text, is_regex, is_case_sensitive, is_matched_whole_word, is_wrap_search)
is_first_checked = False
else:
self.parent.editor.findNext()
def find_prev_call(self):
total_line_number = self.parent.editor.SendScintilla(QsciScintilla.SCI_GETLINECOUNT)
is_first_checked = True
is_regex = False
is_case_sensitive = False
is_matched_whole_word = False
is_wrap_search = True
text = self.find_line_edit.text()
if self.regex_checkbox.isChecked() == True:
is_regex = True
if self.case_sensitive_checkbox.isChecked() == True:
is_case_sensitive = True
if self.match_whole_word_checkbox.isChecked() == True:
is_matched_whole_word = True
editor = self.parent.editor
editor.findFirst(text, is_regex, is_case_sensitive, is_matched_whole_word, is_wrap_search, forward = False,
line = editor.getSelection()[0], index=editor.getSelection()[1])
def replace_call(self):
editor = self.parent.editor
is_first_checked = True
is_regex = False
is_case_sensitive = False
is_matched_whole_word = False
is_wrap_search = True
text = self.find_line_edit.text()
if self.regex_checkbox.isChecked() == True:
is_regex = True
if self.case_sensitive_checkbox.isChecked() == True:
is_case_sensitive = True
if self.match_whole_word_checkbox.isChecked() == True:
is_matched_whole_word = True
text = self.find_line_edit.text()
replacing_text = self.replace_line_edit.text()
editor.replace(replacing_text)
def replace_all_call(self):
editor = self.parent.editor
is_first_checked = True
is_regex = False
is_case_sensitive = False
is_matched_whole_word = False
is_wrap_search = True
text = self.find_line_edit.text()
replacing_text = self.replace_line_edit.text()
if self.regex_checkbox.isChecked() == True:
is_regex = True
if self.case_sensitive_checkbox.isChecked() == True:
is_case_sensitive = True
if self.match_whole_word_checkbox.isChecked() == True:
is_matched_whole_word = True
while editor.findFirst(text, is_regex, is_case_sensitive, is_matched_whole_word, is_wrap_search,
line=editor.getSelection()[2], index=editor.getSelection()[3], forward = True):
editor.replace(replacing_text)

View file

BIN
src/example.pdf Normal file

Binary file not shown.

View file

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
#-*-coding:utf-8-*- #-*-coding:utf-8-*-
import json
import os import os
import re import re
import sys import sys
@ -9,14 +10,19 @@ from PyQt5.QtGui import *
from PyQt5 import QtWebEngineWidgets from PyQt5 import QtWebEngineWidgets
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from PyQt5.Qsci import QsciScintilla, QsciLexerPython, QsciLexerCustom from PyQt5.Qsci import QsciScintilla, QsciLexerPython, QsciLexerCustom
import qrc_resources
filename = "untitled" import qrc_resources
from EditorOther import FindReplace
filename = None
dirname = os.path.abspath(os.path.dirname(__file__)) #os.path.dirname('__file__') dirname = os.path.abspath(os.path.dirname(__file__)) #os.path.dirname('__file__')
PDFJS = os.path.join(dirname, 'thirdparty/pdfjs/web/viewer.html') PDFJS = os.path.join(dirname, '../thirdparty/pdfjs/web/viewer.html')
PDF = os.path.join(dirname, 'example.pdf') PDF = os.path.join(dirname, 'example.pdf')
tab_width = 4
font_family = 'Noto Sans Mono' font_family = 'Noto Sans Mono'
font_size = 11 font_size = 11
@ -37,14 +43,13 @@ class CustomQsciEditor(QsciScintilla):
def __init__(self, parent=None): def __init__(self, parent=None):
super(CustomQsciEditor, self).__init__(parent) super(CustomQsciEditor, self).__init__(parent)
lexer = ClochurLexer(self)
self.setLexer(lexer)
# Margin 0 for line numbers
font = QFont() font = QFont()
font.setFamily(font_family) font.setFamily(font_family)
font.setPointSize(font_size) font.setPointSize(font_size)
self.setFont(font)
self.setMarginsFont(font)
# Margin 0 for line numbers
fontMetrics = QFontMetrics(font) fontMetrics = QFontMetrics(font)
self.setMarginsFont(font) self.setMarginsFont(font)
self.setMarginWidth(0, fontMetrics.width("00") + 6) self.setMarginWidth(0, fontMetrics.width("00") + 6)
@ -65,12 +70,28 @@ class CustomQsciEditor(QsciScintilla):
# set encoding # set encoding
self.SendScintilla(QsciScintilla.SCI_SETCODEPAGE, QsciScintilla.SC_CP_UTF8) self.SendScintilla(QsciScintilla.SCI_SETCODEPAGE, QsciScintilla.SC_CP_UTF8)
self.setUtf8(True)
# set Auto indent
self.setAutoIndent(True)
self.setIndentationWidth(tab_width)
self.setIndentationsUseTabs(False)
self.setTabWidth(4)
self.setBackspaceUnindents(True)
class Window(QMainWindow): class Window(QMainWindow):
def __init__(self): def __init__(self):
super(QMainWindow, self).__init__() super(QMainWindow, self).__init__()
self.file = None
self.filename = None
self.tmp_folder = '/tmp'
self.tmp_file = 'clochur_tmp.json'
self.untitled_id = None
self.opened_file_dirname = os.path.expanduser("~")
self._createActions() self._createActions()
self._createMenuBar() self._createMenuBar()
self._createEditToolBar() self._createEditToolBar()
@ -78,16 +99,53 @@ class Window(QMainWindow):
def _createActions(self): def _createActions(self):
self.new_action = QAction(QIcon(":new.svg"), "&New", self) self.new_action = QAction(QIcon(":new.svg"), "&New", self)
self.new_action.setShortcut('Ctrl+N')
self.new_action.triggered.connect(self.new_call)
self.open_action = QAction(QIcon(":open.svg"), "&Open...", self) self.open_action = QAction(QIcon(":open.svg"), "&Open...", self)
self.open_action.setShortcut('Ctrl+O')
self.open_action.triggered.connect(self.open_call)
self.save_action = QAction(QIcon(":save.svg"), "&Save", self) self.save_action = QAction(QIcon(":save.svg"), "&Save", self)
self.save_action.setShortcut('Ctrl+S')
self.save_action.triggered.connect(self.save_call)
self.save_as_action = QAction(QIcon(":save-as.svg"), "Save as...", self) self.save_as_action = QAction(QIcon(":save-as.svg"), "Save as...", self)
self.save_as_action.triggered.connect(self.save_as_call)
self.exit_action = QAction("&Exit", self) self.exit_action = QAction("&Exit", self)
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.triggered.connect(self.exit_call)
self.undo_action = QAction(QIcon(":undo.svg"), "&Undo", self) self.undo_action = QAction(QIcon(":undo.svg"), "&Undo", self)
self.undo_action.setShortcut('Ctrl+Z')
self.undo_action.triggered.connect(self.undo_call)
self.redo_action = QAction(QIcon(":redo.svg"), "&Redo", self) self.redo_action = QAction(QIcon(":redo.svg"), "&Redo", self)
self.redo_action.setShortcut('Ctrl+Y')
self.redo_action.triggered.connect(self.redo_call)
self.copy_action = QAction(QIcon(":copy.svg"), "&Copy", self) self.copy_action = QAction(QIcon(":copy.svg"), "&Copy", self)
self.copy_action.setShortcut('Ctrl+C')
self.copy_action.triggered.connect(self.copy_call)
self.paste_action = QAction(QIcon(":paste.svg"), "&Paste", self) self.paste_action = QAction(QIcon(":paste.svg"), "&Paste", self)
self.paste_action.setShortcut('Ctrl+V')
self.paste_action.triggered.connect(self.paste_call)
self.cut_action = QAction(QIcon(":cut.svg"), "C&ut", self) self.cut_action = QAction(QIcon(":cut.svg"), "C&ut", self)
self.cut_action.setShortcut('Ctrl+X')
self.cut_action.triggered.connect(self.cut_call)
self.find_and_replace_action = QAction(QIcon(":find-replace.svg"), "&Find and replace" , self)
self.find_and_replace_action.setShortcut('Ctrl+F')
self.find_and_replace_action.triggered.connect(self.find_and_replace_call)
self.select_all_action = QAction( "Select &All" , self)
self.select_all_action.setShortcut('Ctrl+A')
self.select_all_action.triggered.connect(self.select_all_call)
self.convert_action = QAction(QIcon(":convert.svg"), "Con&vert", self) self.convert_action = QAction(QIcon(":convert.svg"), "Con&vert", self)
self.about_action = QAction("&About", self) self.about_action = QAction("&About", self)
@ -108,16 +166,25 @@ class Window(QMainWindow):
file_menu.addAction(self.exit_action) file_menu.addAction(self.exit_action)
edit_menu = menuBar.addMenu("&Edit") edit_menu = menuBar.addMenu("&Edit")
edit_menu.addAction(self.copy_action)
edit_menu.addAction(self.paste_action)
edit_menu.addAction(self.cut_action)
edit_menu.addSeparator()
edit_menu.addAction(self.undo_action) edit_menu.addAction(self.undo_action)
edit_menu.addAction(self.redo_action) edit_menu.addAction(self.redo_action)
edit_menu.addSeparator() edit_menu.addSeparator()
edit_menu.addAction(self.cut_action)
edit_menu.addAction(self.copy_action)
edit_menu.addAction(self.paste_action)
edit_menu.addSeparator()
edit_menu.addAction(self.select_all_action)
edit_menu.addSeparator()
edit_menu.addAction(self.find_and_replace_action)
edit_menu.addAction(self.convert_action) edit_menu.addAction(self.convert_action)
@ -132,6 +199,115 @@ class Window(QMainWindow):
help_menu = menuBar.addMenu("&Help") help_menu = menuBar.addMenu("&Help")
help_menu.addAction(self.about_action) help_menu.addAction(self.about_action)
def closeEvent(self, event):
self.exit_call()
event.ignore()
def new_call(self):
os.system('clochur')
def open_call(self):
file_path = QFileDialog.getOpenFileName(self, 'Open file...', self.opened_file_dirname, "CLC typesetting format (*.clc)")
if file_path[0] != '':
self.filename = os.path.basename(file_path[0])
self.opened_file_dirname = os.path.dirname(file_path[0])
self.file = open(file_path[0], 'r', encoding='utf-8')
editor.setText(self.file.read())
self.file.close()
def save_call(self):
if self.filename == None:
self.save_as_call()
self.editor.setModified(False)
else:
self.file = open(os.path.join(self.opened_file_dirname,self.filename), 'w', encoding='utf-8')
file_content = editor.text()
self.file.write(file_content)
self.file.close()
self.editor.setModified(False)
def removing_untitled_id(self):
if self.untitled_id != None:
with open(os.path.join(self.tmp_folder, self.tmp_file), 'r') as f:
data = json.load(f)
data["untitled"].remove(self.untitled_id)
with open(os.path.join(self.tmp_folder, self.tmp_file), 'w') as f:
json.dump(data, f, indent=4)
def save_as_call(self):
file_path = QFileDialog.getSaveFileName(self, 'Save file as...', self.opened_file_dirname, "CLC typesetting format (*.clc)")
if file_path[0] != '':
self.filename = os.path.basename(file_path[0])
self.opened_file_dirname = os.path.dirname(file_path[0])
self.file = open(file_path[0], 'w', encoding='utf-8')
file_content = editor.text()
self.file.write(file_content)
self.file.close()
self.editor.setModified(False)
self.removing_untitled_id()
self.setWindowTitle("Clochur - %s" % os.path.basename(file_path[0]))
pass
def exit_call(self):
#reply = QMessageBox.question(self,'','Do You want to save this file? The text has been modified', QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.No)
if self.editor.isModified():
reply = QMessageBox.question(self,'','Do You want to save this file? The text has been modified', QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel, QMessageBox.No)
if reply == QMessageBox.Yes:
file_path = QFileDialog.getSaveFileName(self, 'Save file as...', opened_file_dirname, "CLC typesetting format (*.clc)")
if file_path[0] != '':
self.file = open(file_path[0], 'w', encoding='utf-8')
file_content = editor.text()
self.file.write(file_content)
self.file.close()
self.removing_untitled_id()
elif reply == QMessageBox.No:
self.removing_untitled_id()
app.exit()
else:
pass
else:
self.removing_untitled_id()
app.exit()
def undo_call(self):
self.editor.undo()
def redo_call(self):
self.editor.redo()
def copy_call(self):
self.editor.copy()
def paste_call(self):
self.editor.paste()
def cut_call(self):
self.editor.cut()
def find_and_replace_call(self):
print(FindReplace)
find_replace_dialog = FindReplace.FindReplace(self)
type(find_replace_dialog)
find_replace_dialog.exec_()
def select_all_call(self):
self.editor.selectAll()
def _createEditToolBar(self): def _createEditToolBar(self):
editToolBar = QToolBar("Edit", self) editToolBar = QToolBar("Edit", self)
editToolBar.toolButtonStyle = Qt.ToolButtonTextOnly editToolBar.toolButtonStyle = Qt.ToolButtonTextOnly
@ -190,6 +366,36 @@ class Window(QMainWindow):
formatToolBar.addWidget(font_combo_box) formatToolBar.addWidget(font_combo_box)
formatToolBar.addWidget(font_button) formatToolBar.addWidget(font_button)
def generate_untitled_title(self):
json_file = os.path.join(self.tmp_folder, self.tmp_file)
self.untitled_id = None
if not os.path.isfile(json_file):
with open(json_file, 'w') as f:
content = '{"untitled" : [1]}'
f.write(content)
self.untitled_id = 1
else:
i = 1
with open(json_file, 'r') as f:
data = json.load(f)
if data["untitled"] == []:
i = 1
else:
while i in data["untitled"]:
i += 1
data["untitled"].append(i)
data["untitled"].sort()
with open(json_file, 'w') as f:
json.dump(data, f, indent=4)
self.untitled_id = i
return "Untitled %d" % self.untitled_id
class ClochurLexer(QsciLexerCustom): class ClochurLexer(QsciLexerCustom):
def __init__(self, parent=None): def __init__(self, parent=None):
@ -198,11 +404,11 @@ class ClochurLexer(QsciLexerCustom):
0: 'Default', 0: 'Default',
1: 'Keyword', 1: 'Keyword',
2: 'Comment', 2: 'Comment',
3: 'String', 3: 'String',
4: 'Rainbow0', 4: 'Rainbow0',
5: 'Rainbow1', 5: 'Rainbow1',
6: 'Rainbow2', 6: 'Rainbow2',
7: 'Rainbow3', 7: 'Rainbow3',
8: 'Rainbow4', 8: 'Rainbow4',
9: 'Rainbow5', 9: 'Rainbow5',
10: 'Rainbow6', 10: 'Rainbow6',
@ -216,8 +422,13 @@ class ClochurLexer(QsciLexerCustom):
self.PRIMARY = ['define', 'let' , '#t', '#f', 'lambda', '@', 'cond', 'if', 'docu'] self.PRIMARY = ['define', 'let' , '#t', '#f', 'lambda', '@', 'cond', 'if', 'docu']
#self.rainbow_state = 0 font = QFont()
font.setFamily(font_family)
font.setPointSize(font_size)
self.setDefaultFont(font)
# set indent style
self.setAutoIndentStyle(QsciScintilla.AiMaintain)
def language(self): def language(self):
return "Clochur" return "Clochur"
@ -226,7 +437,7 @@ class ClochurLexer(QsciLexerCustom):
ret = "Lexer for Clochur - a S-expression-like" + \ ret = "Lexer for Clochur - a S-expression-like" + \
"typesetting Language. %s, %s" % (style, self._styles.get(style, '')) "typesetting Language. %s, %s" % (style, self._styles.get(style, ''))
return ret return ret
def defaultColor(self, style): def defaultColor(self, style):
if style == self.Default: if style == self.Default:
return QColor("#000000") return QColor("#000000")
@ -250,12 +461,12 @@ class ClochurLexer(QsciLexerCustom):
return QColor("#0000ff") return QColor("#0000ff")
elif style == self.Rainbow6: elif style == self.Rainbow6:
return QColor("#aa00ff") return QColor("#aa00ff")
else: else:
return QsciLexerCustom.defaultColor(self, style) return QsciLexerCustom.defaultColor(self, style)
def defaultRainbowColor(self, index):
return QColor(self.rainbow_color[index])
def styleText(self, start, end): def styleText(self, start, end):
editor = self.editor() editor = self.editor()
@ -281,7 +492,7 @@ class ClochurLexer(QsciLexerCustom):
index = SCI(QsciScintilla.SCI_LINEFROMPOSITION, start) index = SCI(QsciScintilla.SCI_LINEFROMPOSITION, start)
for line in source.splitlines(True): for line in source.splitlines(True):
print("%s, %d" % (line, rainbow_state)) #print("%s, %d" % (line, rainbow_state))
length = len(line) length = len(line)
i = 0 i = 0
@ -297,7 +508,7 @@ class ClochurLexer(QsciLexerCustom):
line_utf8_splitted_len_pair = [{"str": item, "len" : len(bytearray(item, "utf-8"))} for item in line_utf8_splitted] line_utf8_splitted_len_pair = [{"str": item, "len" : len(bytearray(item, "utf-8"))} for item in line_utf8_splitted]
print(line_utf8_splitted_len_pair) #print(line_utf8_splitted_len_pair)
is_comment = False is_comment = False
@ -337,62 +548,20 @@ class ClochurLexer(QsciLexerCustom):
new_state = self.Default new_state = self.Default
SCI(QsciScintilla.SCI_SETLINESTATE, index, rainbow_state) SCI(QsciScintilla.SCI_SETLINESTATE, index, rainbow_state)
'''while i < length:
word_length = 1
if line[i:].startswith(b"\\"):
prev_is_slash = True
else:
# convert byte array to utf-8, and match comment to color it.
if line[i:].startswith(b'%') and prev_is_slash == False:
new_state = self.Comment
word_length = len(line[i:])
print(line[i:].decode('utf-8'))
#keywords_joined = "^(" + "|".join(self.PRIMARY) + ")$"
for keyword in self.PRIMARY:
if line[i:].startswith(bytearray(keyword, 'utf-8')):
#if re.match(keywords_joined , line[i:].decode('utf-8')):
#matched = re.match(keywords_joined , line[i:].decode('utf-8'))
#word_length = len(matched.group(0))
word_length = len(keyword)
new_state = self.Keyword
prev_is_slash = False
i += word_length
set_style(word_length, new_state)'''
index += 1 index += 1
if __name__ == '__main__': if __name__ == '__main__':
app = QApplication([]) app = QApplication([])
app.setApplicationName("Clochur - %s" % filename)
editor = CustomQsciEditor() editor = CustomQsciEditor()
editor.setMinimumWidth(200) editor.setMinimumWidth(200)
#editor.resize(QSize(500, 2000)) #editor.resize(QSize(500, 2000))
lexer = ClochurLexer(editor)
editor.setLexer(lexer)
pdf_viewer = PDFJSWidget() pdf_viewer = PDFJSWidget()
pdf_viewer.setMinimumWidth(200) pdf_viewer.setMinimumWidth(200)
#pdf_viewer.resize(QSize(500, 2000)) #pdf_viewer.resize(QSize(500, 2000))
@ -411,10 +580,19 @@ if __name__ == '__main__':
window = Window() window = Window()
window.editor = editor
untitled_title = window.generate_untitled_title()
if window.file != None:
app.setApplicationName("Clochur - %s" % os.path.basename(window.file))
else:
app.setApplicationName("Clochur - %s" % untitled_title)
main_widget = QWidget() main_widget = QWidget()
main_widget.setLayout(main_layout) main_widget.setLayout(main_layout)
window.setCentralWidget(main_widget) window.setCentralWidget(main_widget)
window.show() window.show()
app.exec_()
sys.exit(app.exec_())