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.

Files changed (51) hide show
  1. pyspice/__init__.py +0 -0
  2. pyspice/dialogs.py +103 -0
  3. pyspice/editor_widget.py +196 -0
  4. pyspice/file_browser.py +143 -0
  5. pyspice/highlighter.py +74 -0
  6. pyspice/line_number_text_edit.py +54 -0
  7. pyspice/magic_scrollbar.py +11 -0
  8. pyspice/main.py +435 -0
  9. pyspice/resources.py +1120 -0
  10. pyspice/spice_console.py +282 -0
  11. pyspice/spice_magic_editor.py +389 -0
  12. pyspice/splitter.py +118 -0
  13. pyspice/term.py +63 -0
  14. pyspice/textract.py +597 -0
  15. pyspice/utils.py +33 -0
  16. spiceditor/__init__.py +0 -0
  17. spiceditor/dialogs.py +103 -0
  18. spiceditor/editor_widget.py +196 -0
  19. spiceditor/file_browser.py +143 -0
  20. spiceditor/highlighter.py +74 -0
  21. spiceditor/line_number_text_edit.py +54 -0
  22. spiceditor/magic_scrollbar.py +11 -0
  23. spiceditor/main.py +435 -0
  24. spiceditor/resources.py +1120 -0
  25. spiceditor/spice_console.py +282 -0
  26. spiceditor/spice_magic_editor.py +389 -0
  27. spiceditor/splitter.py +118 -0
  28. spiceditor/term.py +63 -0
  29. spiceditor/textract.py +597 -0
  30. spiceditor/utils.py +33 -0
  31. spiceditor-0.0.4.dist-info/LICENSE +674 -0
  32. spiceditor-0.0.4.dist-info/METADATA +31 -0
  33. spiceditor-0.0.4.dist-info/RECORD +51 -0
  34. spiceditor-0.0.4.dist-info/WHEEL +5 -0
  35. spiceditor-0.0.4.dist-info/entry_points.txt +2 -0
  36. spiceditor-0.0.4.dist-info/top_level.txt +1 -0
  37. spyce/__init__.py +0 -0
  38. spyce/dialogs.py +103 -0
  39. spyce/editor_widget.py +196 -0
  40. spyce/file_browser.py +143 -0
  41. spyce/highlighter.py +74 -0
  42. spyce/line_number_text_edit.py +54 -0
  43. spyce/magic_scrollbar.py +11 -0
  44. spyce/main.py +435 -0
  45. spyce/resources.py +1120 -0
  46. spyce/spice_console.py +282 -0
  47. spyce/spice_magic_editor.py +389 -0
  48. spyce/splitter.py +118 -0
  49. spyce/term.py +63 -0
  50. spyce/textract.py +597 -0
  51. spyce/utils.py +33 -0
@@ -0,0 +1,31 @@
1
+ Metadata-Version: 2.2
2
+ Name: spiceditor
3
+ Version: 0.0.4
4
+ Summary: Spice is a Python IDE for students
5
+ Home-page: https://github.com/dantard/coder
6
+ Author: Danilo Tardioli
7
+ Author-email: dantard@unizar.es
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pyqt5
15
+ Requires-Dist: pymupdf>=1.18.17
16
+ Requires-Dist: autopep8
17
+ Requires-Dist: scipy
18
+ Requires-Dist: qtconsole
19
+ Requires-Dist: termqt
20
+ Requires-Dist: easyconfig2
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: requires-dist
28
+ Dynamic: requires-python
29
+ Dynamic: summary
30
+
31
+ # coder
@@ -0,0 +1,51 @@
1
+ pyspice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ pyspice/dialogs.py,sha256=JBDQHtIRbZTy_Wn3Pw-0Jz9Yqm7ogpLehN62Q19papo,2594
3
+ pyspice/editor_widget.py,sha256=kjP7sOObIgfXr-QfTc9RfoLOJYzGXhc5FjNUccXgahw,7136
4
+ pyspice/file_browser.py,sha256=BFLySZLCOiaHa4R3wd-YfO0KZaoIscXDMurutR62WoI,5445
5
+ pyspice/highlighter.py,sha256=YCvi766plweJFAkQy13v_C1_eMf473lszmaUTSTeDJU,3456
6
+ pyspice/line_number_text_edit.py,sha256=Seu2UJMViSuVsC4BcVOmgMacu3HtToLjtA880HsWQDM,1957
7
+ pyspice/magic_scrollbar.py,sha256=keB1CHyL84AuT6wK6USw4O330QtRJT3SqiVAR4Yw92I,317
8
+ pyspice/main.py,sha256=lFG2SYqkGIq3OG2TThp8jemocPaS0w4XCBOPInQ8rbU,16555
9
+ pyspice/resources.py,sha256=xKoh41jZmnzaFkc1nqBIkvTGzUVVHGNQp7mO7np7Jcw,62386
10
+ pyspice/spice_console.py,sha256=fY226U4hvKhbfz-gMq0dZvmsK5jL1novXyuYr_vqkn8,8705
11
+ pyspice/spice_magic_editor.py,sha256=lgZjaFQyLgkdDVQj84l3DHLvihhOQbCJP4qQoUcWOVY,13414
12
+ pyspice/splitter.py,sha256=nNkkbjdZBUbe2BgYRVMo-lJ4LCxtovbTUB9rYyvxGR0,3959
13
+ pyspice/term.py,sha256=nf-FK7ubYbd5kPM-iW8r82dKl_5bxPTw8isEqT5NqlM,2170
14
+ pyspice/textract.py,sha256=G0J_c1Rh-ZNrn63rmqAIPv4YQn3A2U9Zt5KYelAKgxQ,20847
15
+ pyspice/utils.py,sha256=oTgvP42KzMaEiu8HwpuM34A6TdBzs_IimkfAm8Kdqy0,1043
16
+ spiceditor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
+ spiceditor/dialogs.py,sha256=JBDQHtIRbZTy_Wn3Pw-0Jz9Yqm7ogpLehN62Q19papo,2594
18
+ spiceditor/editor_widget.py,sha256=kjP7sOObIgfXr-QfTc9RfoLOJYzGXhc5FjNUccXgahw,7136
19
+ spiceditor/file_browser.py,sha256=BFLySZLCOiaHa4R3wd-YfO0KZaoIscXDMurutR62WoI,5445
20
+ spiceditor/highlighter.py,sha256=YCvi766plweJFAkQy13v_C1_eMf473lszmaUTSTeDJU,3456
21
+ spiceditor/line_number_text_edit.py,sha256=Seu2UJMViSuVsC4BcVOmgMacu3HtToLjtA880HsWQDM,1957
22
+ spiceditor/magic_scrollbar.py,sha256=keB1CHyL84AuT6wK6USw4O330QtRJT3SqiVAR4Yw92I,317
23
+ spiceditor/main.py,sha256=lFG2SYqkGIq3OG2TThp8jemocPaS0w4XCBOPInQ8rbU,16555
24
+ spiceditor/resources.py,sha256=xKoh41jZmnzaFkc1nqBIkvTGzUVVHGNQp7mO7np7Jcw,62386
25
+ spiceditor/spice_console.py,sha256=fY226U4hvKhbfz-gMq0dZvmsK5jL1novXyuYr_vqkn8,8705
26
+ spiceditor/spice_magic_editor.py,sha256=lgZjaFQyLgkdDVQj84l3DHLvihhOQbCJP4qQoUcWOVY,13414
27
+ spiceditor/splitter.py,sha256=nNkkbjdZBUbe2BgYRVMo-lJ4LCxtovbTUB9rYyvxGR0,3959
28
+ spiceditor/term.py,sha256=nf-FK7ubYbd5kPM-iW8r82dKl_5bxPTw8isEqT5NqlM,2170
29
+ spiceditor/textract.py,sha256=G0J_c1Rh-ZNrn63rmqAIPv4YQn3A2U9Zt5KYelAKgxQ,20847
30
+ spiceditor/utils.py,sha256=oTgvP42KzMaEiu8HwpuM34A6TdBzs_IimkfAm8Kdqy0,1043
31
+ spyce/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ spyce/dialogs.py,sha256=JBDQHtIRbZTy_Wn3Pw-0Jz9Yqm7ogpLehN62Q19papo,2594
33
+ spyce/editor_widget.py,sha256=kjP7sOObIgfXr-QfTc9RfoLOJYzGXhc5FjNUccXgahw,7136
34
+ spyce/file_browser.py,sha256=BFLySZLCOiaHa4R3wd-YfO0KZaoIscXDMurutR62WoI,5445
35
+ spyce/highlighter.py,sha256=YCvi766plweJFAkQy13v_C1_eMf473lszmaUTSTeDJU,3456
36
+ spyce/line_number_text_edit.py,sha256=Seu2UJMViSuVsC4BcVOmgMacu3HtToLjtA880HsWQDM,1957
37
+ spyce/magic_scrollbar.py,sha256=keB1CHyL84AuT6wK6USw4O330QtRJT3SqiVAR4Yw92I,317
38
+ spyce/main.py,sha256=lFG2SYqkGIq3OG2TThp8jemocPaS0w4XCBOPInQ8rbU,16555
39
+ spyce/resources.py,sha256=xKoh41jZmnzaFkc1nqBIkvTGzUVVHGNQp7mO7np7Jcw,62386
40
+ spyce/spice_console.py,sha256=fY226U4hvKhbfz-gMq0dZvmsK5jL1novXyuYr_vqkn8,8705
41
+ spyce/spice_magic_editor.py,sha256=lgZjaFQyLgkdDVQj84l3DHLvihhOQbCJP4qQoUcWOVY,13414
42
+ spyce/splitter.py,sha256=nNkkbjdZBUbe2BgYRVMo-lJ4LCxtovbTUB9rYyvxGR0,3959
43
+ spyce/term.py,sha256=nf-FK7ubYbd5kPM-iW8r82dKl_5bxPTw8isEqT5NqlM,2170
44
+ spyce/textract.py,sha256=G0J_c1Rh-ZNrn63rmqAIPv4YQn3A2U9Zt5KYelAKgxQ,20847
45
+ spyce/utils.py,sha256=oTgvP42KzMaEiu8HwpuM34A6TdBzs_IimkfAm8Kdqy0,1043
46
+ spiceditor-0.0.4.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
47
+ spiceditor-0.0.4.dist-info/METADATA,sha256=3260QlOYYk4kXBtyRhpwBWZ4pS1TQZhgj2sjBCq7Mnk,808
48
+ spiceditor-0.0.4.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
49
+ spiceditor-0.0.4.dist-info/entry_points.txt,sha256=k_0k6GoBCxu8EvSVHUfvYHaZg_PTLWLSVSIos9gSI2E,52
50
+ spiceditor-0.0.4.dist-info/top_level.txt,sha256=V4z_TZf9P3ZulfhkBfljhrwZp6CkBDnCO5-lWkYvGm8,11
51
+ spiceditor-0.0.4.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.2)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ spiceditor = spiceditor.main:main
@@ -0,0 +1 @@
1
+ spiceditor
spyce/__init__.py ADDED
File without changes
spyce/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)
spyce/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")
spyce/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)
spyce/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
+