spiceditor 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of spiceditor might be problematic. Click here for more details.
- pyspice/__init__.py +0 -0
- pyspice/dialogs.py +103 -0
- pyspice/editor_widget.py +196 -0
- pyspice/file_browser.py +143 -0
- pyspice/highlighter.py +74 -0
- pyspice/line_number_text_edit.py +54 -0
- pyspice/magic_scrollbar.py +11 -0
- pyspice/main.py +435 -0
- pyspice/resources.py +1120 -0
- pyspice/spice_console.py +282 -0
- pyspice/spice_magic_editor.py +389 -0
- pyspice/splitter.py +118 -0
- pyspice/term.py +63 -0
- pyspice/textract.py +597 -0
- pyspice/utils.py +33 -0
- spiceditor/__init__.py +0 -0
- spiceditor/dialogs.py +103 -0
- spiceditor/editor_widget.py +196 -0
- spiceditor/file_browser.py +143 -0
- spiceditor/highlighter.py +74 -0
- spiceditor/line_number_text_edit.py +54 -0
- spiceditor/magic_scrollbar.py +11 -0
- spiceditor/main.py +435 -0
- spiceditor/resources.py +1120 -0
- spiceditor/spice_console.py +282 -0
- spiceditor/spice_magic_editor.py +389 -0
- spiceditor/splitter.py +118 -0
- spiceditor/term.py +63 -0
- spiceditor/textract.py +597 -0
- spiceditor/utils.py +33 -0
- spiceditor-0.0.4.dist-info/LICENSE +674 -0
- spiceditor-0.0.4.dist-info/METADATA +31 -0
- spiceditor-0.0.4.dist-info/RECORD +51 -0
- spiceditor-0.0.4.dist-info/WHEEL +5 -0
- spiceditor-0.0.4.dist-info/entry_points.txt +2 -0
- spiceditor-0.0.4.dist-info/top_level.txt +1 -0
- spyce/__init__.py +0 -0
- spyce/dialogs.py +103 -0
- spyce/editor_widget.py +196 -0
- spyce/file_browser.py +143 -0
- spyce/highlighter.py +74 -0
- spyce/line_number_text_edit.py +54 -0
- spyce/magic_scrollbar.py +11 -0
- spyce/main.py +435 -0
- spyce/resources.py +1120 -0
- spyce/spice_console.py +282 -0
- spyce/spice_magic_editor.py +389 -0
- spyce/splitter.py +118 -0
- spyce/term.py +63 -0
- spyce/textract.py +597 -0
- spyce/utils.py +33 -0
pyspice/__init__.py
ADDED
|
File without changes
|
pyspice/dialogs.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from PyQt5.QtCore import Qt
|
|
2
|
+
from PyQt5.QtWidgets import QPushButton, QTextEdit, QVBoxLayout, QDialog
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Author(QDialog):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__()
|
|
9
|
+
self.setLayout(QVBoxLayout())
|
|
10
|
+
self.setMinimumSize(400, 350)
|
|
11
|
+
self.setWindowTitle("About")
|
|
12
|
+
textEdit = QTextEdit()
|
|
13
|
+
textEdit.setReadOnly(True)
|
|
14
|
+
textEdit.setHtml("""<!DOCTYPE html>
|
|
15
|
+
<html lang="en">
|
|
16
|
+
<head>
|
|
17
|
+
<meta charset="UTF-8">
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
<title>SPICE - About</title>
|
|
20
|
+
<style>
|
|
21
|
+
body {
|
|
22
|
+
font-family: 'Arial', sans-serif;
|
|
23
|
+
background-color: #f4f4f9;
|
|
24
|
+
color: #333;
|
|
25
|
+
margin: 0;
|
|
26
|
+
padding: 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
justify-content: center;
|
|
29
|
+
align-items: center;
|
|
30
|
+
height: 100vh;
|
|
31
|
+
text-align: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.container {
|
|
35
|
+
background-color: #ffffff;
|
|
36
|
+
padding: 20px;
|
|
37
|
+
border-radius: 8px;
|
|
38
|
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
39
|
+
width: 80%;
|
|
40
|
+
max-width: 600px;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
h1 {
|
|
44
|
+
font-size: 3em;
|
|
45
|
+
color: #2c3e50;
|
|
46
|
+
margin-bottom: 20px;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
p {
|
|
50
|
+
font-size: 1.2em;
|
|
51
|
+
line-height: 1.6;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
ul {
|
|
55
|
+
list-style: none;
|
|
56
|
+
padding: 0;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
ul li {
|
|
60
|
+
font-size: 1.1em;
|
|
61
|
+
margin: 5px 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.footer {
|
|
65
|
+
font-size: 1em;
|
|
66
|
+
color: #7f8c8d;
|
|
67
|
+
margin-top: 20px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.footer p {
|
|
71
|
+
margin: 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
</style>
|
|
75
|
+
</head>
|
|
76
|
+
<body>
|
|
77
|
+
|
|
78
|
+
<div class="container">
|
|
79
|
+
<h1>Spice</h1>
|
|
80
|
+
<h2><strong>Slides and Python for Interactive and Creative Education</strong></h2>
|
|
81
|
+
|
|
82
|
+
<h4><strong>Developed by:</strong></h4>
|
|
83
|
+
<h2>Danilo Tardioli</h2>
|
|
84
|
+
<h3>Email: <a href="mailto:dantard@unizar.es">dantard@unizar.es</a></h3>
|
|
85
|
+
|
|
86
|
+
<p><strong>Year:</strong> 2024</p>
|
|
87
|
+
|
|
88
|
+
<div class="footer">
|
|
89
|
+
<p><strong>Learn more at:</strong> <a href="https://github.com/dantard/coder">https://github.com/dantard/coder</a></p>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
|
|
96
|
+
""")
|
|
97
|
+
self.layout().addWidget(textEdit)
|
|
98
|
+
close_button = QPushButton("Close")
|
|
99
|
+
close_button.setMaximumWidth(100)
|
|
100
|
+
# center the button
|
|
101
|
+
self.layout().addWidget(close_button)
|
|
102
|
+
self.layout().setAlignment(close_button, Qt.AlignRight)
|
|
103
|
+
close_button.clicked.connect(self.close)
|
pyspice/editor_widget.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PyQt5.QtCore import Qt
|
|
4
|
+
from PyQt5.QtGui import QFont, QIcon
|
|
5
|
+
from PyQt5.QtWidgets import QVBoxLayout, QToolBar, QStatusBar, QWidget, QComboBox, QShortcut, QTabWidget, QFileDialog, \
|
|
6
|
+
QApplication, QDialog, QMessageBox
|
|
7
|
+
from spiceditor import utils
|
|
8
|
+
|
|
9
|
+
import spiceditor.resources # noqa
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DynamicComboBox(QComboBox):
|
|
13
|
+
def __init__(self, folder=None, parent=None):
|
|
14
|
+
super().__init__(parent)
|
|
15
|
+
self.folder = folder
|
|
16
|
+
self.populate()
|
|
17
|
+
|
|
18
|
+
def set_folder(self, folder):
|
|
19
|
+
self.folder = folder
|
|
20
|
+
self.populate()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def populate(self, item=None):
|
|
24
|
+
self.blockSignals(True)
|
|
25
|
+
self.clear() # Clear the current items
|
|
26
|
+
self.addItem("[Free Coding]")
|
|
27
|
+
if self.folder is not None and os.path.exists(self.folder):
|
|
28
|
+
files = list(x for x in os.listdir(self.folder) if x.endswith(".py"))
|
|
29
|
+
|
|
30
|
+
files = [f for f in files if os.path.isfile(os.path.join(self.folder, f))]
|
|
31
|
+
files.sort()
|
|
32
|
+
self.addItems(files)
|
|
33
|
+
if item is not None:
|
|
34
|
+
index = self.findText(item)
|
|
35
|
+
if index >=0:
|
|
36
|
+
self.setCurrentIndex(index)
|
|
37
|
+
self.blockSignals(False)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class EditorWidget(QWidget):
|
|
42
|
+
|
|
43
|
+
def __init__(self, language_editor, console, config):
|
|
44
|
+
super().__init__()
|
|
45
|
+
self.path = None
|
|
46
|
+
self.config = config
|
|
47
|
+
editor = config.root().getSubSection("editor", pretty="Editor")
|
|
48
|
+
self.cfg_keep_code = editor.getCheckBox("keep_code",
|
|
49
|
+
pretty="Keep Code on Run",
|
|
50
|
+
default=False)
|
|
51
|
+
self.cfg_show_all = editor.getCheckBox("show_all",
|
|
52
|
+
pretty="Show all Code on Open",
|
|
53
|
+
default=False)
|
|
54
|
+
self.cfg_autocomplete = editor.getString("autocomplete",
|
|
55
|
+
pretty="Autocomplete",
|
|
56
|
+
default="")
|
|
57
|
+
self.cfg_delay = editor.getSlider("delay",
|
|
58
|
+
pretty="Delay",
|
|
59
|
+
min=0, max=100,
|
|
60
|
+
default=25,
|
|
61
|
+
den=1,
|
|
62
|
+
show_value=True,
|
|
63
|
+
suffix=" ms")
|
|
64
|
+
|
|
65
|
+
self.cfg_show_sb = editor.getCheckBox("show_tb",
|
|
66
|
+
pretty="Show Toolbar",
|
|
67
|
+
default=False)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
self.language_editor = language_editor
|
|
71
|
+
self.console = console
|
|
72
|
+
|
|
73
|
+
# Left side layout
|
|
74
|
+
left_layout = QVBoxLayout()
|
|
75
|
+
|
|
76
|
+
self.language_editor.ctrl_enter.connect(self.execute_code)
|
|
77
|
+
self.language_editor.info.connect(self.update_status_bar)
|
|
78
|
+
|
|
79
|
+
bar = QToolBar()
|
|
80
|
+
|
|
81
|
+
a1 = bar.addAction("Play", self.execute_code)
|
|
82
|
+
a2 = bar.addAction("Clear", self.clear)
|
|
83
|
+
a3 = bar.addAction("Show", self.language_editor.show_code)
|
|
84
|
+
|
|
85
|
+
self.keep_banner = bar.addAction("Keep Code on Console")
|
|
86
|
+
self.keep_banner.setCheckable(True)
|
|
87
|
+
self.keep_banner.setChecked(False)
|
|
88
|
+
|
|
89
|
+
self.show_all = bar.addAction("Show all Code on Load")
|
|
90
|
+
self.show_all.setIcon(QIcon(":/icons/radio-button.svg"))
|
|
91
|
+
self.show_all.setCheckable(True)
|
|
92
|
+
|
|
93
|
+
self.text_edit_group = [a1, a2, a3, self.keep_banner]
|
|
94
|
+
bar.addSeparator()
|
|
95
|
+
|
|
96
|
+
left_layout.addWidget(bar)
|
|
97
|
+
left_layout.addWidget(self.language_editor)
|
|
98
|
+
|
|
99
|
+
self.sb = QStatusBar()
|
|
100
|
+
left_layout.addWidget(self.sb)
|
|
101
|
+
left_layout.setSpacing(0)
|
|
102
|
+
|
|
103
|
+
self.setLayout(left_layout)
|
|
104
|
+
self.setLayout(left_layout)
|
|
105
|
+
|
|
106
|
+
def update_config(self):
|
|
107
|
+
self.keep_banner.setChecked(self.cfg_keep_code.get())
|
|
108
|
+
self.show_all.setChecked(self.cfg_show_all.get())
|
|
109
|
+
self.language_editor.append_autocomplete(self.cfg_autocomplete.get())
|
|
110
|
+
self.language_editor.set_delay(self.cfg_delay.get())
|
|
111
|
+
self.language_editor.set_font_size(self.config.font_size.get() + 10)
|
|
112
|
+
|
|
113
|
+
def set_font_size(self, font_size):
|
|
114
|
+
self.language_editor.set_font_size(font_size)
|
|
115
|
+
self.console.set_font_size(font_size)
|
|
116
|
+
|
|
117
|
+
def load_program(self, path, show_all=False):
|
|
118
|
+
self.path = path
|
|
119
|
+
with open(path) as f:
|
|
120
|
+
self.language_editor.set_code(f.read())
|
|
121
|
+
self.console.clear()
|
|
122
|
+
|
|
123
|
+
if self.show_all.isChecked() or show_all:
|
|
124
|
+
self.show_all_code()
|
|
125
|
+
|
|
126
|
+
def save_program(self, path, save_as):
|
|
127
|
+
if self.path is None or save_as:
|
|
128
|
+
ext = self.console.get_file_extension()
|
|
129
|
+
filename, ok = QFileDialog.getSaveFileName(self, "Save code", filter="Language files (*" + ext + ")",
|
|
130
|
+
directory=path)
|
|
131
|
+
if not filename:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
self.path = filename.replace(".py","") + ".py"
|
|
135
|
+
|
|
136
|
+
with open(self.path, "w") as f:
|
|
137
|
+
f.write(self.language_editor.toPlainText())
|
|
138
|
+
|
|
139
|
+
name = os.path.basename(self.path)
|
|
140
|
+
tab_wiget: QTabWidget = self.parent().parent() # noqa
|
|
141
|
+
index = tab_wiget.indexOf(self)
|
|
142
|
+
tab_wiget.setTabText(index, name)
|
|
143
|
+
self.sb.showMessage("Saved in " + self.path, 2000)
|
|
144
|
+
|
|
145
|
+
def clear(self):
|
|
146
|
+
self.language_editor.clear()
|
|
147
|
+
# self.console_widget.clear()
|
|
148
|
+
|
|
149
|
+
def execute_code(self):
|
|
150
|
+
self.language_editor.format_code()
|
|
151
|
+
self.console.execute(self.language_editor.toPlainText(), not self.keep_banner.isChecked())
|
|
152
|
+
|
|
153
|
+
def set_dark_mode(self, dark):
|
|
154
|
+
self.language_editor.set_dark_mode(dark)
|
|
155
|
+
color = Qt.white if dark else Qt.black
|
|
156
|
+
a1, a2, a3, a4 = self.text_edit_group
|
|
157
|
+
a1.setIcon(QIcon(utils.color(":/icons/play.svg", color)))
|
|
158
|
+
a2.setIcon(QIcon(utils.color(":/icons/refresh.svg", color)))
|
|
159
|
+
a3.setIcon(QIcon(utils.color(":/icons/download.svg", color)))
|
|
160
|
+
a4.setIcon(QIcon(utils.color(":/icons/hash.svg", color)))
|
|
161
|
+
|
|
162
|
+
def get_text(self):
|
|
163
|
+
self.language_editor.format_code()
|
|
164
|
+
return self.language_editor.text_edit.toPlainText()
|
|
165
|
+
|
|
166
|
+
def get_font_size(self):
|
|
167
|
+
return self.language_editor.font().pixelSize()
|
|
168
|
+
|
|
169
|
+
def append_autocomplete(self, value, val):
|
|
170
|
+
self.language_editor.append_autocomplete(value, val)
|
|
171
|
+
|
|
172
|
+
def set_delay(self, value):
|
|
173
|
+
self.language_editor.set_delay(value)
|
|
174
|
+
|
|
175
|
+
def show_all_code(self):
|
|
176
|
+
self.language_editor.show_all_code()
|
|
177
|
+
|
|
178
|
+
def set_progs_path(self, path):
|
|
179
|
+
value = self.prog_cb.currentText()
|
|
180
|
+
self.prog_cb.set_folder(path)
|
|
181
|
+
self.populate_progs()
|
|
182
|
+
self.prog_cb.setCurrentIndex(self.prog_cb.findText(value))
|
|
183
|
+
|
|
184
|
+
def update_status_bar(self, x, diff, timeout):
|
|
185
|
+
if self.cfg_show_sb.get_value():
|
|
186
|
+
if timeout != 0:
|
|
187
|
+
x = "{:5d} | {}".format(diff, x)
|
|
188
|
+
self.sb.showMessage(x, 1000)
|
|
189
|
+
else:
|
|
190
|
+
self.sb.showMessage(x)
|
|
191
|
+
|
|
192
|
+
# red if diff is negative
|
|
193
|
+
if diff < 10:
|
|
194
|
+
self.sb.setStyleSheet("color: red")
|
|
195
|
+
else:
|
|
196
|
+
self.sb.setStyleSheet("color: black")
|
pyspice/file_browser.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
from PyQt5 import QtGui
|
|
5
|
+
from PyQt5.QtCore import QObject, pyqtSignal, QDir, QItemSelectionModel, QModelIndex, Qt
|
|
6
|
+
from PyQt5.QtWidgets import QWidget, QTreeView, QFileSystemModel, QVBoxLayout, QPushButton, QHBoxLayout, QLabel, QMenu, QMessageBox, QToolBar, QInputDialog
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Tree(QTreeView):
|
|
10
|
+
delete_requested = pyqtSignal(str)
|
|
11
|
+
|
|
12
|
+
def filter_rows(self):
|
|
13
|
+
for i in range(self.model().rowCount(self.rootIndex())):
|
|
14
|
+
child_index = self.model().index(i, 0, self.rootIndex()) # Get index of each row
|
|
15
|
+
filename = self.model().data(child_index) # Get file name
|
|
16
|
+
if filename == "__pycache__":
|
|
17
|
+
self.setRowHidden(i, self.rootIndex(), True)
|
|
18
|
+
|
|
19
|
+
def contextMenuEvent(self, a0: QtGui.QContextMenuEvent) -> None:
|
|
20
|
+
super().contextMenuEvent(a0)
|
|
21
|
+
indexes = self.selectedIndexes()
|
|
22
|
+
if indexes:
|
|
23
|
+
index = self.indexAt(a0.pos())
|
|
24
|
+
if index.isValid():
|
|
25
|
+
dirModel = self.model()
|
|
26
|
+
path = dirModel.fileInfo(index).absoluteFilePath()
|
|
27
|
+
menu = QMenu()
|
|
28
|
+
delete = menu.addAction("Delete")
|
|
29
|
+
res = menu.exec_(self.viewport().mapToGlobal(a0.pos()))
|
|
30
|
+
if res == delete:
|
|
31
|
+
self.delete_requested.emit(path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FileBrowser(QWidget):
|
|
35
|
+
class Signals(QObject):
|
|
36
|
+
file_selected = pyqtSignal(str)
|
|
37
|
+
|
|
38
|
+
def __init__(self, path, filters=None, hide_details=True):
|
|
39
|
+
super().__init__()
|
|
40
|
+
if filters is None:
|
|
41
|
+
filters = ["*.pdf"]
|
|
42
|
+
self.signals = self.Signals()
|
|
43
|
+
self.path = path
|
|
44
|
+
self.treeview = Tree()
|
|
45
|
+
self.treeview.delete_requested.connect(self.delete_requested)
|
|
46
|
+
self.dirModel = QFileSystemModel()
|
|
47
|
+
self.dirModel.directoryLoaded.connect(self.treeview.filter_rows)
|
|
48
|
+
self.dirModel.setNameFilters(filters)
|
|
49
|
+
self.dirModel.setNameFilterDisables(False)
|
|
50
|
+
|
|
51
|
+
self.treeview.setModel(self.dirModel)
|
|
52
|
+
self.treeview.setRootIndex(self.dirModel.setRootPath(path))
|
|
53
|
+
|
|
54
|
+
vlayout = QVBoxLayout(self)
|
|
55
|
+
vlayout.setSpacing(0)
|
|
56
|
+
vlayout.setContentsMargins(0, 0, 0, 0)
|
|
57
|
+
self.setLayout(vlayout)
|
|
58
|
+
#tb = QToolBar()
|
|
59
|
+
#tb.addAction("🗀", self.refresh)
|
|
60
|
+
|
|
61
|
+
#vlayout.addWidget(tb)
|
|
62
|
+
|
|
63
|
+
self.layout().addWidget(self.treeview)
|
|
64
|
+
self.treeview.selectionModel().selectionChanged.connect(self.on_current_changed)
|
|
65
|
+
self.treeview.doubleClicked.connect(self.on_double_clicked)
|
|
66
|
+
|
|
67
|
+
if hide_details:
|
|
68
|
+
for i in range(1, self.treeview.model().columnCount()):
|
|
69
|
+
self.treeview.header().hideSection(i)
|
|
70
|
+
|
|
71
|
+
def delete_requested(self, path):
|
|
72
|
+
if QMessageBox.question(self, "Delete", f"Are you sure you want to delete {path}?", QMessageBox.Yes | QMessageBox.No) == QMessageBox.No:
|
|
73
|
+
return
|
|
74
|
+
if os.path.isdir(path):
|
|
75
|
+
shutil.rmtree(path)
|
|
76
|
+
else:
|
|
77
|
+
os.remove(path)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def on_double_clicked(self, index):
|
|
81
|
+
# Map the proxy index to the source model index
|
|
82
|
+
path = self.dirModel.fileInfo(index).absoluteFilePath()
|
|
83
|
+
if os.path.isdir(path):
|
|
84
|
+
return
|
|
85
|
+
self.signals.file_selected.emit(path)
|
|
86
|
+
|
|
87
|
+
def btn_up_clicked(self):
|
|
88
|
+
index = self.treeview.rootIndex()
|
|
89
|
+
if index.isValid():
|
|
90
|
+
index = index.parent()
|
|
91
|
+
self.set_root_index(index)
|
|
92
|
+
|
|
93
|
+
def set_root(self, path):
|
|
94
|
+
self.treeview.setRootIndex(self.dirModel.setRootPath(path))
|
|
95
|
+
|
|
96
|
+
def set_root_index(self, index):
|
|
97
|
+
self.treeview.setRootIndex(index)
|
|
98
|
+
path = self.dirModel.fileInfo(index).absoluteFilePath()
|
|
99
|
+
self.label.setText(path)
|
|
100
|
+
|
|
101
|
+
def select(self, filename, emit=True):
|
|
102
|
+
if not emit:
|
|
103
|
+
self.treeview.selectionModel().blockSignals(True)
|
|
104
|
+
index = self.dirModel.index(filename)
|
|
105
|
+
indices = []
|
|
106
|
+
while index.isValid():
|
|
107
|
+
indices.append(index)
|
|
108
|
+
index = index.parent()
|
|
109
|
+
|
|
110
|
+
for index in reversed(indices):
|
|
111
|
+
self.treeview.setExpanded(index, True)
|
|
112
|
+
|
|
113
|
+
self.treeview.setCurrentIndex(index)
|
|
114
|
+
self.treeview.selectionModel().blockSignals(False)
|
|
115
|
+
|
|
116
|
+
def on_current_changed(self, selected, deselected):
|
|
117
|
+
pass
|
|
118
|
+
# if deselected.indexes():
|
|
119
|
+
# print("deselected1", deselected.indexes())
|
|
120
|
+
# # Check if the deselected index is valid
|
|
121
|
+
# for index in deselected.indexes():
|
|
122
|
+
# if not os.path.isfile(self.dirModel.filePath(index)):
|
|
123
|
+
# self.treeview.clearSelection()
|
|
124
|
+
# return
|
|
125
|
+
#
|
|
126
|
+
# if selected is None or len(selected.indexes()) < 1:
|
|
127
|
+
# return
|
|
128
|
+
# path = self.dirModel.fileInfo(selected.indexes()[0]).absoluteFilePath()
|
|
129
|
+
# # self.listview.setRootIndex(self.fileModel.setRootPath(path))
|
|
130
|
+
# if os.path.isfile(path):
|
|
131
|
+
# self.signals.file_selected.emit(path)
|
|
132
|
+
|
|
133
|
+
def new_folder(self):
|
|
134
|
+
current_path = self.dirModel.rootPath()
|
|
135
|
+
|
|
136
|
+
# If the toolbar refresh button was clicked with no arguments,
|
|
137
|
+
# show the folder creation dialog
|
|
138
|
+
folder_name, ok = QInputDialog.getText(self, "Folder Name", "Enter the folder name")
|
|
139
|
+
if ok and folder_name:
|
|
140
|
+
new_folder_path = os.path.join(self.path, folder_name)
|
|
141
|
+
os.makedirs(new_folder_path, exist_ok=True)
|
|
142
|
+
self.dirModel.setRootPath("")
|
|
143
|
+
self.dirModel.setRootPath(current_path)
|
pyspice/highlighter.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from PyQt5.QtCore import Qt, QRegExp
|
|
2
|
+
from PyQt5.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QFont
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SyntaxHighlighter(QSyntaxHighlighter):
|
|
6
|
+
|
|
7
|
+
def __init__(self, keywords):
|
|
8
|
+
super().__init__(None)
|
|
9
|
+
self.highlighting_rules = []
|
|
10
|
+
self.keywords = keywords
|
|
11
|
+
self.keyword_color = Qt.blue
|
|
12
|
+
self.apply_scheme()
|
|
13
|
+
|
|
14
|
+
def set_dark_mode(self, dark):
|
|
15
|
+
self.keyword_color = Qt.cyan if dark else Qt.blue
|
|
16
|
+
self.highlighting_rules.clear()
|
|
17
|
+
self.apply_scheme()
|
|
18
|
+
|
|
19
|
+
def apply_scheme(self):
|
|
20
|
+
keyword_format = QTextCharFormat()
|
|
21
|
+
keyword_format.setForeground(self.keyword_color)
|
|
22
|
+
keyword_format.setFontWeight(QFont.Bold)
|
|
23
|
+
|
|
24
|
+
self.highlighting_rules += [(f"\\b{k}\\b", keyword_format) for k in self.keywords]
|
|
25
|
+
|
|
26
|
+
string_format = QTextCharFormat()
|
|
27
|
+
string_format.setForeground(Qt.magenta)
|
|
28
|
+
self.highlighting_rules.append((r'".*"', string_format))
|
|
29
|
+
self.highlighting_rules.append((r"'.*'", string_format))
|
|
30
|
+
|
|
31
|
+
comment_format = QTextCharFormat()
|
|
32
|
+
comment_format.setForeground(QColor("green"))
|
|
33
|
+
comment_format.setFontItalic(True)
|
|
34
|
+
self.highlighting_rules.append((r"#.*", comment_format))
|
|
35
|
+
|
|
36
|
+
def highlightBlock(self, text):
|
|
37
|
+
for pattern, fmt in self.highlighting_rules:
|
|
38
|
+
expression = QRegExp(pattern)
|
|
39
|
+
index = expression.indexIn(text)
|
|
40
|
+
while index >= 0:
|
|
41
|
+
length = expression.matchedLength()
|
|
42
|
+
self.setFormat(index, length, fmt)
|
|
43
|
+
index = expression.indexIn(text, index + length)
|
|
44
|
+
|
|
45
|
+
def get_keywords(self):
|
|
46
|
+
return self.keywords
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PythonHighlighter(SyntaxHighlighter):
|
|
50
|
+
def __init__(self, dark=False):
|
|
51
|
+
super().__init__(['return', 'nonlocal', 'elif', 'assert', 'or', 'yield', 'finally',
|
|
52
|
+
'from', 'global', 'del', 'print', 'None', 'pass', 'class', 'as',
|
|
53
|
+
'break', 'while', 'await', 'async', 'range', 'is', 'True', 'lambda',
|
|
54
|
+
'False', 'in', 'import', 'except', 'continue', 'and', 'raise', 'with',
|
|
55
|
+
'if', 'try', 'for', 'else', 'not', 'def', "input", "int", "float", "str",
|
|
56
|
+
"list", "dict", "input", "print", "open", "read", "write", "close", "split",
|
|
57
|
+
])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PascalHighlighter(SyntaxHighlighter):
|
|
61
|
+
def __init__(self, dark=False):
|
|
62
|
+
super().__init__([
|
|
63
|
+
"and", "array", "asm", "begin", "case", "const", "constructor", "destructor",
|
|
64
|
+
"div", "do", "downto", "else", "end", "file", "for", "function", "goto", "if",
|
|
65
|
+
"implementation", "in", "inherited", "inline", "interface", "label", "mod", "nil",
|
|
66
|
+
"not", "object", "of", "or", "packed", "procedure", "program", "record", "repeat",
|
|
67
|
+
"set", "shl", "shr", "string", "then", "to", "type", "unit", "until", "uses",
|
|
68
|
+
"var", "while", "with", "xor", "AND", "ARRAY", "ASM", "BEGIN", "CASE", "CONST", "CONSTRUCTOR", "DESTRUCTOR",
|
|
69
|
+
"DIV", "DO", "DOWNTO", "ELSE", "END", "FILE", "FOR", "FUNCTION", "GOTO", "IF",
|
|
70
|
+
"IMPLEMENTATION", "IN", "INHERITED", "INLINE", "INTERFACE", "LABEL", "MOD", "NIL",
|
|
71
|
+
"NOT", "OBJECT", "OF", "OR", "PACKED", "PROCEDURE", "PROGRAM", "RECORD", "REPEAT",
|
|
72
|
+
"SET", "SHL", "SHR", "STRING", "THEN", "TO", "TYPE", "UNIT", "UNTIL", "USES",
|
|
73
|
+
"VAR", "WHILE", "WITH", "XOR"
|
|
74
|
+
])
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from PyQt5.QtCore import Qt
|
|
2
|
+
from PyQt5.QtGui import QTextCursor, QTextCharFormat, QColor, QTextBlockFormat
|
|
3
|
+
from PyQt5.QtWidgets import QTextEdit
|
|
4
|
+
|
|
5
|
+
from spiceditor.magic_scrollbar import MagicScrollBar
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LineNumberTextEdit(QTextEdit):
|
|
9
|
+
def __init__(self, parent=None):
|
|
10
|
+
super().__init__(parent)
|
|
11
|
+
self.line_highlighter_color = QColor(255, 255, 255, 100)
|
|
12
|
+
self.setReadOnly(True)
|
|
13
|
+
self.setLineWrapMode(QTextEdit.NoWrap)
|
|
14
|
+
self.setHorizontalScrollBar(MagicScrollBar())
|
|
15
|
+
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
|
16
|
+
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def highlight_line(self, line_number):
|
|
20
|
+
cursor = self.textCursor()
|
|
21
|
+
position = cursor.position()
|
|
22
|
+
cursor.movePosition(QTextCursor.Start)
|
|
23
|
+
|
|
24
|
+
# Select all text and reset formatting
|
|
25
|
+
cursor.select(QTextCursor.Document)
|
|
26
|
+
default_format = QTextCharFormat() # Default format (no highlights)
|
|
27
|
+
cursor.setCharFormat(default_format)
|
|
28
|
+
|
|
29
|
+
cursor.movePosition(QTextCursor.Start)
|
|
30
|
+
for _ in range(line_number):
|
|
31
|
+
cursor.movePosition(QTextCursor.Down)
|
|
32
|
+
|
|
33
|
+
cursor.select(QTextCursor.LineUnderCursor)
|
|
34
|
+
|
|
35
|
+
highlight_format = QTextCharFormat()
|
|
36
|
+
highlight_format.setBackground(self.line_highlighter_color)
|
|
37
|
+
cursor.setCharFormat(highlight_format)
|
|
38
|
+
cursor.setPosition(position)
|
|
39
|
+
|
|
40
|
+
# blockFmt = QTextBlockFormat()
|
|
41
|
+
# blockFmt.setLineHeight(40, QTextBlockFormat.FixedHeight)
|
|
42
|
+
#
|
|
43
|
+
# theCursor = self.textCursor()
|
|
44
|
+
# theCursor.clearSelection()
|
|
45
|
+
# theCursor.select(QTextCursor.Document)
|
|
46
|
+
# theCursor.mergeBlockFormat(blockFmt)
|
|
47
|
+
|
|
48
|
+
def set_line_highlighter_color(self, color):
|
|
49
|
+
self.line_highlighter_color = color
|
|
50
|
+
self.update()
|
|
51
|
+
|
|
52
|
+
def set_dark_mode(self, dark):
|
|
53
|
+
self.set_line_highlighter_color(
|
|
54
|
+
QColor(0, 0, 0, 50) if not dark else QColor(255, 255, 255, 100))
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from PyQt5.QtGui import QPainter
|
|
2
|
+
from PyQt5.QtWidgets import QScrollBar
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MagicScrollBar(QScrollBar):
|
|
6
|
+
def paintEvent(self, a0) -> None:
|
|
7
|
+
super().paintEvent(a0)
|
|
8
|
+
if self.maximum() == 0:
|
|
9
|
+
p = QPainter(self)
|
|
10
|
+
p.fillRect(self.rect(), self.parent().palette().base().color())
|
|
11
|
+
|