# Gcode display / edit widget for QT_VCP
# Copyright 2016 Chris Morley
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# This was based on
# QScintilla sample with PyQt
# Eli Bendersky (eliben@gmail.com)
# Which is code in the public domain
#
# See also:
# http://pyqt.sourceforge.net/Docs/QScintilla2/index.html
# https://qscintilla.com/
import sys
import os
from PySide6.QtCore import Property, QObject, Slot, QFile, QFileInfo, QTextStream, Signal, QTimer
from PySide6.QtGui import QFont, QFontMetrics, QColor
from PySide6.QtWidgets import QInputDialog, QLineEdit, QDialog, QHBoxLayout, QVBoxLayout, QLabel, QPushButton, QCheckBox
from qtpyvcp.utilities import logger
from qtpyvcp.plugins import getPlugin
from qtpyvcp.utilities.info import Info
from qtpyvcp.utilities.qt_safety import safe_qt_callback
LOG = logger.getLogger(__name__)
try:
from PySide6.Qsci import QsciScintilla, QsciLexerCustom
except ImportError as e:
LOG.critical(
"Can't import PySide6.Qsci (QsciScintilla). Install python3-pyside6.qsci (or python3-pyqt5.qsci).",
exc_info=e,
)
sys.exit(1)
IN_DESIGNER = os.getenv('DESIGNER', False)
if not IN_DESIGNER:
STATUS = getPlugin('status')
INFO = Info()
# ==============================================================================
# Simple custom lexer for Gcode
# ==============================================================================
[docs]
class GcodeLexer(QsciLexerCustom):
def __init__(self, parent=None, standalone=False):
super(GcodeLexer, self).__init__(parent)
# This prevents doing unneeded initialization
# when QtDesginer loads the plugin.
if parent is None and not standalone:
return
self._styles = {
0: 'Default',
1: 'Comment',
2: 'Key',
3: 'Assignment',
4: 'Value',
}
for key, value in self._styles.items():
setattr(self, value, key)
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
font.setBold(True)
self.setFont(font, 2)
# Paper sets the background color of each style of text
def setPaperBackground(self, color, style=None):
if style is None:
for i in range(0, 5):
self.setPaper(color, i)
else:
self.setPaper(color, style)
def description(self, style):
return self._styles.get(style, '')
def defaultColor(self, style):
if style == self.Default:
return QColor('#000000') # black
elif style == self.Comment:
return QColor('#000000') # black
elif style == self.Key:
return QColor('#0000CC') # blue
elif style == self.Assignment:
return QColor('#CC0000') # red
elif style == self.Value:
return QColor('#00CC00') # green
return QsciLexerCustom.defaultColor(self, style)
def styleText(self, start, end):
editor = self.editor()
if editor is None:
return
# scintilla works with encoded bytes, not decoded characters.
# this matters if the source contains non-ascii characters and
# a multi-byte encoding is used (e.g. utf-8)
source = ''
if end > editor.length():
end = editor.length()
if end > start:
source = bytearray(end - start)
editor.SendScintilla(
editor.SCI_GETTEXTRANGE, start, end, source)
if not source:
return
# the line index will also be needed to implement folding
index = editor.SendScintilla(editor.SCI_LINEFROMPOSITION, start)
if index > 0:
# the previous state may be needed for multi-line styling
pos = editor.SendScintilla(
editor.SCI_GETLINEENDPOSITION, index - 1)
state = editor.SendScintilla(editor.SCI_GETSTYLEAT, pos)
else:
state = self.Default
set_style = self.setStyling
self.startStyling(start, 0x1f)
# scintilla always asks to style whole lines
for line in source.splitlines(True):
# print(line)
length = len(line)
graymode = False
msg = ('msg'.encode('utf-8') in line.lower() or 'debug'.encode('utf-8') in line.lower())
for char in str(line):
# print(char)
if char == '(':
graymode = True
set_style(1, self.Comment)
continue
elif char == ')':
graymode = False
set_style(1, self.Comment)
continue
elif graymode:
if msg and char.lower() in ('m', 's', 'g', ',', 'd', 'e', 'b', 'u'):
set_style(1, self.Assignment)
if char == ',': msg = False
else:
set_style(1, self.Comment)
continue
elif char in ('%', '<', '>', '#', '='):
state = self.Assignment
elif char in ('[', ']'):
state = self.Value
elif char.isalpha():
state = self.Key
elif char.isdigit():
state = self.Default
else:
state = self.Default
set_style(1, state)
# folding implementation goes here
index += 1
# ==============================================================================
# Base editor class
# ==============================================================================
[docs]
class EditorBase(QsciScintilla):
ARROW_MARKER_NUM = 8
def __init__(self, parent=None):
super(EditorBase, self).__init__(parent)
# linuxcnc defaults
self.idle_line_reset = False
# don't allow editing by default
self.setReadOnly(True)
# Set the default font
font = QFont()
font.setFamily('Courier')
font.setFixedPitch(True)
font.setPointSize(10)
self.setFont(font)
self.setMarginsFont(font)
# Margin 0 is used for line numbers
fontmetrics = QFontMetrics(font)
self.setMarginsFont(font)
self.setMarginWidth(0, fontmetrics.width("0000") + 6)
self.setMarginLineNumbers(0, True)
self.setMarginsBackgroundColor(QColor("#cccccc"))
# Clickable margin 1 for showing markers
self.setMarginSensitivity(1, True)
# setting marker margin width to zero make the marker highlight line
self.setMarginWidth(1, 10)
self.marginClicked.connect(self.on_margin_clicked)
self.markerDefine(QsciScintilla.RightArrow,
self.ARROW_MARKER_NUM)
self.setMarkerBackgroundColor(QColor("#ffe4e4"),
self.ARROW_MARKER_NUM)
# Brace matching: enable for a brace immediately before or after
# the current position
#
self.setBraceMatching(QsciScintilla.SloppyBraceMatch)
# Current line visible with special background color
self.setCaretLineVisible(True)
self.setCaretLineBackgroundColor(QColor("#ffe4e4"))
# Set custom gcode lexer
self.lexer = GcodeLexer(self)
self.lexer.setDefaultFont(font)
self.setLexer(self.lexer)
# default gray background
self.set_background_color('#C0C0C0')
self.highlit = None
# not too small
# self.setMinimumSize(200, 100)
[docs]
def find_text_occurences(self, text):
"""Return byte positions of start and end of all 'text' occurences in the document"""
text_len = len(text)
end_pos = self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
self.SendScintilla(QsciScintilla.SCI_SETTARGETSTART, 0)
self.SendScintilla(QsciScintilla.SCI_SETTARGETEND, end_pos)
occurences = []
match = self.SendScintilla(QsciScintilla.SCI_SEARCHINTARGET, text_len, text)
print(match)
while match != -1:
match_end = self.SendScintilla(QsciScintilla.SCI_GETTARGETEND)
occurences.append((match, match_end))
# -- if there's a match, the target is modified so we shift its start
# -- and restore its end --
self.SendScintilla(QsciScintilla.SCI_SETTARGETSTART, match_end)
self.SendScintilla(QsciScintilla.SCI_SETTARGETEND, end_pos)
# -- find it again in the new (reduced) target --
match = self.SendScintilla(QsciScintilla.SCI_SEARCHINTARGET, text_len, text)
return occurences
def highlight_occurences(self, text):
occurences = self.find_text_occurences(text)
text_len = len(text)
self.SendScintilla(QsciScintilla.SCI_SETSTYLEBITS, 8)
for occs in occurences:
self.SendScintilla(QsciScintilla.SCI_SETINDICATORCURRENT, 0)
self.SendScintilla(QsciScintilla.SCI_INDICATORFILLRANGE,
occs[0], text_len)
# -- this is somewhat buggy : it was meant to change the color
# -- but somewhy the colouring suddenly changes colour.
# self.SendScintilla(Qsci.QsciScintilla.SCI_STARTSTYLING, occs[0], 0xFF)
# self.SendScintilla(Qsci.QsciScintilla.SCI_SETSTYLING,
# textLen,
# styles["HIGHLIGHT"][0])
self.highlit = occurences
def clear_highlights(self):
if self.highlit is None:
return
for occs in self.highlit:
self.SendScintilla(QsciScintilla.SCI_SETINDICATORCURRENT, 0)
self.SendScintilla(QsciScintilla.SCI_INDICATORCLEARRANGE,
occs[0], occs[1] - occs[0])
self.highlit = None
def text_search(self, text, from_start, highlight_all, re=False,
cs=True, wo=False, wrap=True, forward=True,
line=-1, index=-1, show=True):
if text is not None:
if highlight_all:
self.clear_highlights()
self.highlight_occurences(text)
if from_start:
self.setCursorPosition(0, 0)
match = self.findFirst(text, re, cs, wo, wrap, forward, line, index, show)
def text_replace(self, text, sub, from_start, re=False,
cs=True, wo=False, wrap=True, forward=True,
line=-1, index=-1, show=True):
if text is not None and sub is not None:
self.clear_highlights()
self.highlight_occurences(text)
if from_start:
self.setCursorPosition(0, 0)
match = self.findFirst(text, re, cs, wo, wrap, forward, line, index, show)
if match:
self.replace(sub)
def text_replace_all(self, text, sub, from_start, re=False,
cs=True, wo=False, wrap=True, forward=True,
line=-1, index=-1, show=True):
if text is not None and sub is not None:
self.clear_highlights()
self.SendScintilla(QsciScintilla.SCI_SETTARGETSTART, 0)
end_pos = self.SendScintilla(QsciScintilla.SCI_GETLENGTH)
self.SendScintilla(QsciScintilla.SCI_SETTARGETEND, end_pos)
print((self.SendScintilla(QsciScintilla.SCI_SEARCHINTARGET, len(text), text)))
# match = self.findFirst(text, re, cs, wo, wrap, forward, line, index, show)
# if match:
# self.replace(sub)
# must set lexer paper background color _and_ editor background color it seems
def set_background_color(self, color):
self.SendScintilla(QsciScintilla.SCI_STYLESETBACK, QsciScintilla.STYLE_DEFAULT, QColor(color))
self.lexer.setPaperBackground(QColor(color))
def set_margin_background_color(self, color):
self.setMarginsBackgroundColor(QColor(color))
def on_margin_clicked(self, nmargin, nline, modifiers):
# Toggle marker for the line the margin was clicked on
if self.markersAtLine(nline) != 0:
self.markerDelete(nline, self.ARROW_MARKER_NUM)
else:
self.markerAdd(nline, self.ARROW_MARKER_NUM)
# ==============================================================================
# Gcode widget
# ==============================================================================
[docs]
class GcodeEditor(EditorBase, QObject):
ARROW_MARKER_NUM = 8
SCI_GETDOCPOINTER = getattr(QsciScintilla, 'SCI_GETDOCPOINTER', 2357)
SCI_SETDOCPOINTER = getattr(QsciScintilla, 'SCI_SETDOCPOINTER', 2358)
SCI_ADDREFDOCUMENT = getattr(QsciScintilla, 'SCI_ADDREFDOCUMENT', 2376)
SCI_RELEASEDOCUMENT = getattr(QsciScintilla, 'SCI_RELEASEDOCUMENT', 2377)
somethingHasChanged = Signal(bool)
def __init__(self, parent=None):
super(GcodeEditor, self).__init__(parent)
self.filename = ""
self._last_filename = None
self.auto_show_mdi = True
self.last_line = None
# self.setEolVisibility(True)
self.is_editor = False
self.text_before_edit = ''
self._shared_doc_ptr = None
self._applying_external_program_selection = False
self.dialog = FindReplaceDialog(parent=self)
# QSS Hack
self.backgroundcolor = ''
self.marginbackgroundcolor = ''
# register with the status:task_mode channel to
# drive the mdi auto show behaviour
#STATUS.task_mode.notify(self.onMdiChanged)
#self.prev_taskmode = STATUS.task_mode
#self.cursorPositionChanged.connect(self.line_changed)
self.cursorPositionChanged.connect(self._on_cursor_position_changed)
self.selectionChanged.connect(self._on_selection_changed)
self._install_external_selected_line_sync()
self.somethingHasChanged.emit(False)
def _install_external_selected_line_sync(self):
status_obj = globals().get('STATUS', None)
if status_obj is None:
return
selected_line_channel = getattr(status_obj, 'selected_program_line', None)
selected_line_notify = getattr(selected_line_channel, 'notify', None)
if callable(selected_line_notify):
selected_line_notify(safe_qt_callback(self, self._on_external_selected_program_line_changed))
selected_lines_channel = getattr(status_obj, 'selected_program_lines', None)
selected_lines_notify = getattr(selected_lines_channel, 'notify', None)
if callable(selected_lines_notify):
selected_lines_notify(safe_qt_callback(self, self._on_external_selected_program_line_changed))
def _on_external_selected_program_line_changed(self, *_args):
if self._applying_external_program_selection:
return
if not self._last_filename:
return
selected_lines = self._selected_program_line_values_from_status()
if not selected_lines:
return
self._apply_selected_program_lines_from_status(selected_lines)
def _selected_program_line_values_from_status(self):
status_obj = globals().get('STATUS', None)
if status_obj is None:
return []
selected_lines_channel = getattr(status_obj, 'selected_program_lines', None)
selected_lines_raw = getattr(selected_lines_channel, 'value', None)
if isinstance(selected_lines_raw, (list, tuple, set)):
selected_lines = self._normalize_selected_lines(selected_lines_raw)
if selected_lines:
return selected_lines
selected_line_channel = getattr(status_obj, 'selected_program_line', None)
selected_line_raw = getattr(selected_line_channel, 'value', None)
return self._normalize_selected_lines([selected_line_raw])
def _apply_selected_program_lines_from_status(self, line_numbers):
selected_lines = self._normalize_selected_lines(line_numbers)
if not selected_lines:
return
start_line = selected_lines[0] - 1
end_line = selected_lines[-1] - 1
if start_line < 0 or end_line < start_line:
return
self._applying_external_program_selection = True
try:
end_col = 0
try:
line_text = self.text(end_line)
if isinstance(line_text, str):
end_col = len(line_text)
except Exception:
end_col = 0
try:
self.setSelection(start_line, 0, end_line, end_col)
except Exception:
pass
self.setCursorPosition(start_line, 0)
ensure_line_visible = getattr(self, 'ensureLineVisible', None)
if callable(ensure_line_visible):
ensure_line_visible(start_line)
self.ensureCursorVisible()
self.SendScintilla(QsciScintilla.SCI_VERTICALCENTRECARET)
finally:
self._applying_external_program_selection = False
def _get_doc_pointer(self):
try:
return int(self.SendScintilla(self.SCI_GETDOCPOINTER))
except Exception:
return 0
def _release_shared_document_ref(self):
if not self._shared_doc_ptr:
return
try:
self.SendScintilla(self.SCI_RELEASEDOCUMENT, 0, self._shared_doc_ptr)
except Exception:
pass
finally:
self._shared_doc_ptr = None
def use_shared_document_from(self, source_editor):
if source_editor is None or source_editor is self:
return False
source_ptr = 0
try:
source_ptr = int(source_editor._get_doc_pointer())
except Exception:
return False
if not source_ptr:
return False
current_ptr = self._get_doc_pointer()
if current_ptr == source_ptr:
return True
self._release_shared_document_ref()
try:
self.SendScintilla(self.SCI_ADDREFDOCUMENT, 0, source_ptr)
self.SendScintilla(self.SCI_SETDOCPOINTER, 0, source_ptr)
self._shared_doc_ptr = source_ptr
self.SendScintilla(QsciScintilla.SCI_GOTOPOS, 0)
return True
except Exception:
self._release_shared_document_ref()
return False
@Slot(bool)
def setEditable(self, state):
if state:
self.setReadOnly(False)
self.setCaretLineVisible(True)
if self.text_before_edit != '':
self.text_before_edit = self.text()
self.somethingHasChanged.emit(False)
else:
self.setReadOnly(True)
self.setCaretLineVisible(False)
self.somethingHasChanged.emit(self.text_before_edit != self.text())
@Slot(bool)
def EditorReadWrite(self, state):
self.setEditable(state)
@Slot(str)
def setFilename(self, path):
self.filename = path
@Slot()
def save(self):
save_file = QFile(str(STATUS.file))
result = save_file.open(QFile.WriteOnly)
if result:
LOG.debug("---self.text(): {}".format(self.text()))
save_stream = QTextStream(save_file)
save_stream << self.text()
save_file.close()
self.text_before_edit = ''
self.somethingHasChanged.emit(False)
else:
LOG.debug("---save error")
@Slot()
def saveFile(self):
self.save()
@Slot()
def saveAs(self):
file_name = self.save_as_dialog(self.filename)
if file_name is False:
print("saveAs file name error")
return
self.filename = str(STATUS.file)
original_file = QFileInfo(self.filename)
path = original_file.path()
new_absolute_path = os.path.join(path, file_name)
new_file = QFile(new_absolute_path)
result = new_file.open(QFile.WriteOnly)
if result:
save_stream = QTextStream(new_file)
save_stream << self.text()
new_file.close()
self.text_before_edit = ''
self.somethingHasChanged.emit(False)
@Slot()
def saveFileAs(self):
self.saveAs()
@Slot()
def find_replace(self):
self.dialog.show()
@Slot()
def findDialog(self):
self.find_replace()
def search_text(self, find_text, highlight_all):
from_start = False
if find_text != "":
self.text_search(find_text, from_start, highlight_all)
def replace_text(self, find_text, replace_text):
from_start = False
if find_text != "" and replace_text != "":
self.text_replace(find_text, replace_text, from_start)
def replace_all_text(self, find_text, replace_text):
from_start = True
if find_text != "" and replace_text != "":
self.text_replace_all(find_text, find_text, from_start)
@Property(bool)
def is_editor(self):
return self._is_editor
@is_editor.setter
def is_editor(self, enabled):
self._is_editor = enabled
if not self._is_editor:
STATUS.file.notify(safe_qt_callback(self, self.load_program))
STATUS.motion_line.onValueChanged(safe_qt_callback(self, self.highlight_line))
# STATUS.connect('line-changed', self.highlight_line)
# if self.idle_line_reset:
# STATUS.connect('interp_idle', lambda w: self.set_line_number(None, 0))
@Property(str)
def backgroundcolor(self):
"""Property to set the background color of the GCodeEditor (str).
sets the background color of the GCodeEditor
"""
return self._backgroundcolor
@backgroundcolor.setter
def backgroundcolor(self, color):
self._backgroundcolor = color
self.set_background_color(color)
@Property(str)
def marginbackgroundcolor(self):
"""Property to set the background color of the GCodeEditor margin (str).
sets the background color of the GCodeEditor margin
"""
return self._marginbackgroundcolor
@marginbackgroundcolor.setter
def marginbackgroundcolor(self, color):
self._marginbackgroundcolor = color
self.set_margin_background_color(color)
def load_program(self, fname=None):
if fname is None:
fname = self._last_filename
else:
self._last_filename = fname
self.load_text(fname)
# self.zoomTo(6)
self.setCursorPosition(0, 0)
self._set_selected_program_line_channels([1])
def _selected_line_sync_allowed(self):
interp_state_channel = getattr(STATUS, 'interp_state', None)
interp_state_text = str(interp_state_channel).strip().lower() if interp_state_channel is not None else ''
return interp_state_text in ('idle', 'paused')
@staticmethod
def _normalize_selected_lines(lines):
normalized = []
seen = set()
for raw in lines:
try:
line_no = int(raw)
except Exception:
continue
if line_no <= 0 or line_no in seen:
continue
seen.add(line_no)
normalized.append(line_no)
normalized.sort()
return normalized
def _set_selected_program_line_channels(self, line_numbers):
if not self._selected_line_sync_allowed():
return
normalized_lines = self._normalize_selected_lines(line_numbers)
if not normalized_lines:
return
selected_lines_channel = getattr(STATUS, 'selected_program_lines', None)
set_lines = getattr(selected_lines_channel, 'setValue', None)
if callable(set_lines):
set_lines(normalized_lines)
selected_channel = getattr(STATUS, 'selected_program_line', None)
set_value = getattr(selected_channel, 'setValue', None)
if callable(set_value):
set_value(normalized_lines[0])
def _publish_selected_program_line(self, zero_based_line):
line_no = int(zero_based_line) + 1
if line_no < 1:
line_no = 1
self._set_selected_program_line_channels([line_no])
def _publish_selected_program_lines_from_selection(self):
try:
from_line, from_index, to_line, to_index = self.getSelection()
except Exception:
line, _index = self.getCursorPosition()
self._publish_selected_program_line(line)
return
if from_line < 0 or to_line < 0:
line, _index = self.getCursorPosition()
self._publish_selected_program_line(line)
return
start_line = min(from_line, to_line)
end_line = max(from_line, to_line)
if to_index == 0 and end_line > start_line:
end_line -= 1
self._set_selected_program_line_channels(range(start_line + 1, end_line + 2))
def _on_cursor_position_changed(self, line, _index):
if self._applying_external_program_selection:
return
if not self._last_filename:
return
if self.hasSelectedText():
self._publish_selected_program_lines_from_selection()
return
self._publish_selected_program_line(line)
def _on_selection_changed(self):
if self._applying_external_program_selection:
return
if not self._last_filename:
return
if self.hasSelectedText():
self._publish_selected_program_lines_from_selection()
return
line, _index = self.getCursorPosition()
self._publish_selected_program_line(line)
def set_text_fast(self, text, defer_lexer=True):
lexer = getattr(self, 'lexer', None)
lexer_detached = False
self._release_shared_document_ref()
if isinstance(text, (bytes, bytearray, memoryview)):
payload = bytes(text)
else:
payload = str(text).encode('utf-8', errors='replace')
self.setUpdatesEnabled(False)
try:
if defer_lexer and lexer is not None:
self.setLexer(None)
lexer_detached = True
self.SendScintilla(QsciScintilla.SCI_SETUNDOCOLLECTION, 0)
self.SendScintilla(QsciScintilla.SCI_SETTEXT, payload)
self.SendScintilla(QsciScintilla.SCI_EMPTYUNDOBUFFER)
self.SendScintilla(QsciScintilla.SCI_SETUNDOCOLLECTION, 1)
self.SendScintilla(QsciScintilla.SCI_GOTOPOS, 0)
finally:
self.setUpdatesEnabled(True)
if lexer_detached:
def _restore_lexer():
self.setLexer(lexer)
self.recolor()
QTimer.singleShot(0, _restore_lexer)
def closeEvent(self, event):
self._release_shared_document_ref()
super(GcodeEditor, self).closeEvent(event)
def load_text(self, fname):
try:
fp = os.path.expanduser(fname)
with open(fp, 'rb') as handle:
self.set_text_fast(handle.read())
except:
LOG.error('File path is not valid: {}'.format(fname))
self.setText('')
return
self.last_line = None
self.ensureCursorVisible()
self.SendScintilla(QsciScintilla.SCI_VERTICALCENTRECARET)
def highlight_line(self, line):
# if STATUS.is_auto_running():
# if not STATUS.old['file'] == self._last_filename:
# LOG.debug('should reload the display')
# self.load_text(STATUS.old['file'])
# self._last_filename = STATUS.old['file']
self.markerAdd(line, self.ARROW_MARKER_NUM)
if self.last_line:
self.markerDelete(self.last_line, self.ARROW_MARKER_NUM)
self.setCursorPosition(line, 0)
self.ensureCursorVisible()
self.SendScintilla(QsciScintilla.SCI_VERTICALCENTRECARET)
self.last_line = line
self._publish_selected_program_line(line)
def set_line_number(self, line):
pass
def select_lineup(self):
line, col = self.getCursorPosition()
LOG.debug(line)
self.setCursorPosition(line - 1, 0)
self.highlight_line(line - 1)
def select_linedown(self):
line, col = self.getCursorPosition()
LOG.debug(line)
self.setCursorPosition(line + 1, 0)
self.highlight_line(line + 1)
# simple input dialog for save as
def save_as_dialog(self, filename):
text, ok_pressed = QInputDialog.getText(self, "Save as", "New name:", QLineEdit.EchoMode.Normal, filename)
if ok_pressed and text != '':
return text
else:
return False
# more complex dialog required by find replace
[docs]
class FindReplaceDialog(QDialog):
def __init__(self, parent):
super(FindReplaceDialog, self).__init__(parent)
self.parent = parent
self.setWindowTitle("Find Replace")
self.setFixedSize(400, 200)
main_layout = QVBoxLayout()
find_layout = QHBoxLayout()
replace_layout = QHBoxLayout()
options_layout = QHBoxLayout()
buttons_layout = QHBoxLayout()
find_label = QLabel()
find_label.setText("Find:")
self.find_input = QLineEdit()
find_layout.addWidget(find_label)
find_layout.addWidget(self.find_input)
replace_label = QLabel()
replace_label.setText("Replace:")
self.replace_input = QLineEdit()
replace_layout.addWidget(replace_label)
replace_layout.addWidget(self.replace_input)
self.close_button = QPushButton()
self.close_button.setText("Close")
self.find_button = QPushButton()
self.find_button.setText("Find")
self.replace_button = QPushButton()
self.replace_button.setText("Replace")
self.all_button = QPushButton()
self.all_button.setText("Replace All")
buttons_layout.addWidget(self.close_button)
buttons_layout.addWidget(self.find_button)
buttons_layout.addWidget(self.replace_button)
buttons_layout.addWidget(self.all_button)
self.highlight_result = QCheckBox()
self.highlight_result.setText("highlight results")
options_layout.addWidget(self.highlight_result)
main_layout.addLayout(find_layout)
main_layout.addLayout(replace_layout)
main_layout.addLayout(options_layout)
main_layout.addLayout(buttons_layout)
self.setLayout(main_layout)
self.find_button.clicked.connect(self.find_text)
self.replace_button.clicked.connect(self.replace_text)
self.all_button.clicked.connect(self.replace_all_text)
self.close_button.clicked.connect(self.hide_dialog)
def find_text(self):
find_text = self.find_input.text()
highlight = self.highlight_result.isChecked()
self.parent.search_text(find_text, highlight)
def replace_text(self):
find_text = self.find_input.text()
replace_text = self.replace_input.text()
self.parent.replace_text(find_text, replace_text)
def replace_all_text(self):
find_text = self.find_input.text()
replace_text = self.replace_input.text()
if find_text == "":
return
self.parent.replace_all_text(find_text, replace_text)
def hide_dialog(self):
self.hide()
# ==============================================================================
# For testing
# ==============================================================================
# if __name__ == "__main__":
# from PySide6.QtGui import QApplication
#
# app = QApplication(sys.argv)
# editor = GcodeEditor(standalone=True)
# editor.show()
#
# editor.setText(open(sys.argv[0]).read())
# app.exec_()