diff --git a/src/EditorOther/FindReplace.py b/src/EditorOther/FindReplace.py new file mode 100644 index 0000000..5caab70 --- /dev/null +++ b/src/EditorOther/FindReplace.py @@ -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) + + + + + + + + + + diff --git a/src/EditorOther/__init__.py b/src/EditorOther/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/example.pdf b/src/example.pdf new file mode 100644 index 0000000..eb3cd18 Binary files /dev/null and b/src/example.pdf differ diff --git a/main.py b/src/main.py similarity index 60% rename from main.py rename to src/main.py index cc79feb..92c86ef 100755 --- a/main.py +++ b/src/main.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 #-*-coding:utf-8-*- +import json import os import re import sys @@ -9,14 +10,19 @@ from PyQt5.QtGui import * from PyQt5 import QtWebEngineWidgets from PyQt5.QtWidgets import * 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__') -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') +tab_width = 4 + font_family = 'Noto Sans Mono' font_size = 11 @@ -37,14 +43,13 @@ class CustomQsciEditor(QsciScintilla): def __init__(self, parent=None): super(CustomQsciEditor, self).__init__(parent) + lexer = ClochurLexer(self) + self.setLexer(lexer) + + # Margin 0 for line numbers font = QFont() font.setFamily(font_family) font.setPointSize(font_size) - self.setFont(font) - self.setMarginsFont(font) - - # Margin 0 for line numbers - fontMetrics = QFontMetrics(font) self.setMarginsFont(font) self.setMarginWidth(0, fontMetrics.width("00") + 6) @@ -65,12 +70,28 @@ class CustomQsciEditor(QsciScintilla): # set encoding 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): def __init__(self): 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._createMenuBar() self._createEditToolBar() @@ -78,16 +99,53 @@ class Window(QMainWindow): def _createActions(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.setShortcut('Ctrl+O') + self.open_action.triggered.connect(self.open_call) + 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.triggered.connect(self.save_as_call) 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.setShortcut('Ctrl+Z') + self.undo_action.triggered.connect(self.undo_call) + 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.setShortcut('Ctrl+C') + self.copy_action.triggered.connect(self.copy_call) + 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.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.about_action = QAction("&About", self) @@ -108,16 +166,25 @@ class Window(QMainWindow): file_menu.addAction(self.exit_action) 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.redo_action) 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) @@ -132,6 +199,115 @@ class Window(QMainWindow): help_menu = menuBar.addMenu("&Help") 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): editToolBar = QToolBar("Edit", self) editToolBar.toolButtonStyle = Qt.ToolButtonTextOnly @@ -190,6 +366,36 @@ class Window(QMainWindow): formatToolBar.addWidget(font_combo_box) 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): def __init__(self, parent=None): @@ -198,11 +404,11 @@ class ClochurLexer(QsciLexerCustom): 0: 'Default', 1: 'Keyword', 2: 'Comment', - 3: 'String', + 3: 'String', 4: 'Rainbow0', 5: 'Rainbow1', - 6: 'Rainbow2', - 7: 'Rainbow3', + 6: 'Rainbow2', + 7: 'Rainbow3', 8: 'Rainbow4', 9: 'Rainbow5', 10: 'Rainbow6', @@ -216,8 +422,13 @@ class ClochurLexer(QsciLexerCustom): 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): return "Clochur" @@ -226,7 +437,7 @@ class ClochurLexer(QsciLexerCustom): ret = "Lexer for Clochur - a S-expression-like" + \ "typesetting Language. %s, %s" % (style, self._styles.get(style, '')) return ret - + def defaultColor(self, style): if style == self.Default: return QColor("#000000") @@ -250,12 +461,12 @@ class ClochurLexer(QsciLexerCustom): return QColor("#0000ff") elif style == self.Rainbow6: return QColor("#aa00ff") - + else: return QsciLexerCustom.defaultColor(self, style) - def defaultRainbowColor(self, index): - return QColor(self.rainbow_color[index]) + + def styleText(self, start, end): editor = self.editor() @@ -281,7 +492,7 @@ class ClochurLexer(QsciLexerCustom): index = SCI(QsciScintilla.SCI_LINEFROMPOSITION, start) for line in source.splitlines(True): - print("%s, %d" % (line, rainbow_state)) + #print("%s, %d" % (line, rainbow_state)) length = len(line) 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] - print(line_utf8_splitted_len_pair) + #print(line_utf8_splitted_len_pair) is_comment = False @@ -337,62 +548,20 @@ class ClochurLexer(QsciLexerCustom): new_state = self.Default 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 + + + if __name__ == '__main__': app = QApplication([]) - app.setApplicationName("Clochur - %s" % filename) editor = CustomQsciEditor() editor.setMinimumWidth(200) #editor.resize(QSize(500, 2000)) - lexer = ClochurLexer(editor) - editor.setLexer(lexer) - pdf_viewer = PDFJSWidget() pdf_viewer.setMinimumWidth(200) #pdf_viewer.resize(QSize(500, 2000)) @@ -411,10 +580,19 @@ if __name__ == '__main__': 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.setLayout(main_layout) window.setCentralWidget(main_widget) window.show() - app.exec_() \ No newline at end of file + + sys.exit(app.exec_()) \ No newline at end of file diff --git a/qrc_resources.py b/src/qrc_resources.py similarity index 100% rename from qrc_resources.py rename to src/qrc_resources.py diff --git a/resources.qrc b/src/resources.qrc similarity index 100% rename from resources.qrc rename to src/resources.qrc