spiceditor 0.0.5__tar.gz → 0.0.8__tar.gz
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.
- {spiceditor-0.0.5 → spiceditor-0.0.8}/PKG-INFO +2 -1
- {spiceditor-0.0.5 → spiceditor-0.0.8}/setup.py +4 -2
- spiceditor-0.0.8/src/spiceditor/bw_timer.py +147 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/dialogs.py +125 -4
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/editor_widget.py +33 -25
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/file_browser.py +27 -7
- spiceditor-0.0.8/src/spiceditor/highlighter.py +87 -0
- spiceditor-0.0.8/src/spiceditor/install.py +38 -0
- spiceditor-0.0.8/src/spiceditor/kakka.py +143 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/main_window.py +82 -53
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/spice_console.py +16 -12
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/spice_magic_editor.py +119 -42
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/splitter.py +1 -1
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/textract.py +34 -40
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor.egg-info/PKG-INFO +2 -1
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor.egg-info/SOURCES.txt +3 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor.egg-info/entry_points.txt +1 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor.egg-info/requires.txt +1 -0
- spiceditor-0.0.5/src/spiceditor/highlighter.py +0 -74
- {spiceditor-0.0.5 → spiceditor-0.0.8}/LICENSE +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/README.md +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/setup.cfg +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/__init__.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/line_number_text_edit.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/magic_scrollbar.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/resources.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/spice.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/spiceterm.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/term.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor/utils.py +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor.egg-info/dependency_links.txt +0 -0
- {spiceditor-0.0.5 → spiceditor-0.0.8}/src/spiceditor.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: spiceditor
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: Spice is a Python IDE for students
|
|
5
5
|
Home-page: https://github.com/dantard/coder
|
|
6
6
|
Author: Danilo Tardioli
|
|
@@ -18,6 +18,7 @@ Requires-Dist: scipy
|
|
|
18
18
|
Requires-Dist: qtconsole
|
|
19
19
|
Requires-Dist: termqt
|
|
20
20
|
Requires-Dist: easyconfig2
|
|
21
|
+
Requires-Dist: pyshortcuts
|
|
21
22
|
Dynamic: author
|
|
22
23
|
Dynamic: author-email
|
|
23
24
|
Dynamic: classifier
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='spiceditor',
|
|
5
|
-
version='0.0.
|
|
5
|
+
version='0.0.8',
|
|
6
6
|
packages=find_packages(where='src'), # Specify src directory
|
|
7
7
|
package_dir={'': 'src'}, # Tell setuptools that packages are under src
|
|
8
8
|
install_requires=[
|
|
@@ -12,7 +12,8 @@ setup(
|
|
|
12
12
|
'scipy',
|
|
13
13
|
'qtconsole',
|
|
14
14
|
'termqt',
|
|
15
|
-
'easyconfig2'
|
|
15
|
+
'easyconfig2',
|
|
16
|
+
'pyshortcuts'
|
|
16
17
|
],
|
|
17
18
|
author='Danilo Tardioli',
|
|
18
19
|
author_email='dantard@unizar.es',
|
|
@@ -30,6 +31,7 @@ setup(
|
|
|
30
31
|
'console_scripts': [
|
|
31
32
|
'spice=spiceditor.spice:main',
|
|
32
33
|
'spiceterm=spiceditor.spiceterm:main',
|
|
34
|
+
'spiceinstall=spiceditor.install:create_shortcuts'
|
|
33
35
|
],
|
|
34
36
|
}
|
|
35
37
|
)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import sys
|
|
3
|
+
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton, QHBoxLayout
|
|
4
|
+
from PyQt5.QtCore import QTimer, Qt
|
|
5
|
+
from PyQt5.QtGui import QFont
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CountdownTimer(QWidget):
|
|
9
|
+
def __init__(self, hours=0, minutes=0, seconds=0, auto_start=False, show_buttons=True):
|
|
10
|
+
super().__init__()
|
|
11
|
+
|
|
12
|
+
# Store initial time
|
|
13
|
+
self.initial_hours = hours
|
|
14
|
+
self.initial_minutes = minutes
|
|
15
|
+
self.initial_seconds = seconds
|
|
16
|
+
self.auto_start = auto_start
|
|
17
|
+
self.show_buttons = show_buttons
|
|
18
|
+
|
|
19
|
+
# Calculate total seconds
|
|
20
|
+
self.total_seconds = hours * 3600 + minutes * 60 + seconds
|
|
21
|
+
self.remaining_seconds = self.total_seconds
|
|
22
|
+
|
|
23
|
+
# Timer running state
|
|
24
|
+
self.is_running = False
|
|
25
|
+
self.setWindowFlags(self.windowFlags() | Qt.WindowStaysOnTopHint)
|
|
26
|
+
|
|
27
|
+
# Main layout
|
|
28
|
+
layout = QVBoxLayout()
|
|
29
|
+
|
|
30
|
+
# Time display label
|
|
31
|
+
self.time_label = QLabel()
|
|
32
|
+
self.time_label.setAlignment(Qt.AlignCenter)
|
|
33
|
+
self.time_label.setFont(QFont('Arial', 48, QFont.Bold))
|
|
34
|
+
layout.addWidget(self.time_label)
|
|
35
|
+
|
|
36
|
+
# Buttons layout
|
|
37
|
+
button_layout = QHBoxLayout()
|
|
38
|
+
|
|
39
|
+
# Start/Pause button
|
|
40
|
+
self.start_pause_btn = QPushButton('Start')
|
|
41
|
+
self.start_pause_btn.clicked.connect(self.toggle_timer)
|
|
42
|
+
button_layout.addWidget(self.start_pause_btn)
|
|
43
|
+
|
|
44
|
+
# Reset button
|
|
45
|
+
self.reset_btn = QPushButton('Reset')
|
|
46
|
+
self.reset_btn.clicked.connect(self.reset_timer)
|
|
47
|
+
button_layout.addWidget(self.reset_btn)
|
|
48
|
+
|
|
49
|
+
if self.show_buttons:
|
|
50
|
+
layout.addLayout(button_layout)
|
|
51
|
+
|
|
52
|
+
self.setLayout(layout)
|
|
53
|
+
|
|
54
|
+
# QTimer for countdown
|
|
55
|
+
self.timer = QTimer()
|
|
56
|
+
self.timer.timeout.connect(self.countdown)
|
|
57
|
+
|
|
58
|
+
if self.auto_start:
|
|
59
|
+
self.toggle_timer()
|
|
60
|
+
|
|
61
|
+
self.update_display()
|
|
62
|
+
|
|
63
|
+
def update_display(self):
|
|
64
|
+
"""Update the time display label"""
|
|
65
|
+
hours = self.remaining_seconds // 3600
|
|
66
|
+
minutes = (self.remaining_seconds % 3600) // 60
|
|
67
|
+
seconds = self.remaining_seconds % 60
|
|
68
|
+
|
|
69
|
+
time_str = f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
|
70
|
+
self.time_label.setText(time_str)
|
|
71
|
+
|
|
72
|
+
# Change color when time is running out
|
|
73
|
+
if 10 >= self.remaining_seconds > 0:
|
|
74
|
+
self.time_label.setStyleSheet("color: orange;")
|
|
75
|
+
elif self.remaining_seconds == 0:
|
|
76
|
+
self.time_label.setStyleSheet("color: red;")
|
|
77
|
+
else:
|
|
78
|
+
self.time_label.setStyleSheet("color: black;")
|
|
79
|
+
|
|
80
|
+
def countdown(self):
|
|
81
|
+
"""Decrease time by 1 second"""
|
|
82
|
+
if self.remaining_seconds > 0:
|
|
83
|
+
self.remaining_seconds -= 1
|
|
84
|
+
self.update_display()
|
|
85
|
+
else:
|
|
86
|
+
self.timer.stop()
|
|
87
|
+
self.is_running = False
|
|
88
|
+
self.start_pause_btn.setText('Start')
|
|
89
|
+
print("Timer finished!")
|
|
90
|
+
try:
|
|
91
|
+
self.play_alarm_sound()
|
|
92
|
+
except:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
def toggle_timer(self):
|
|
96
|
+
"""Start or pause the timer"""
|
|
97
|
+
if self.is_running:
|
|
98
|
+
self.timer.stop()
|
|
99
|
+
self.is_running = False
|
|
100
|
+
self.start_pause_btn.setText('Resume')
|
|
101
|
+
else:
|
|
102
|
+
if self.remaining_seconds > 0:
|
|
103
|
+
self.timer.start(1000) # Update every 1000ms (1 second)
|
|
104
|
+
self.is_running = True
|
|
105
|
+
self.start_pause_btn.setText('Pause')
|
|
106
|
+
|
|
107
|
+
def reset_timer(self):
|
|
108
|
+
"""Reset timer to initial value"""
|
|
109
|
+
self.timer.stop()
|
|
110
|
+
self.is_running = False
|
|
111
|
+
self.remaining_seconds = self.total_seconds
|
|
112
|
+
self.start_pause_btn.setText('Start')
|
|
113
|
+
self.update_display()
|
|
114
|
+
|
|
115
|
+
def play_alarm_sound(self):
|
|
116
|
+
"""Play a sound when timer finishes"""
|
|
117
|
+
try:
|
|
118
|
+
system = platform.system()
|
|
119
|
+
|
|
120
|
+
if system == "Windows":
|
|
121
|
+
# Windows: use winsound
|
|
122
|
+
import winsound
|
|
123
|
+
# Play beep 3 times
|
|
124
|
+
for i in range(3):
|
|
125
|
+
QTimer.singleShot(i * 600, lambda: winsound.Beep(1000, 400))
|
|
126
|
+
else:
|
|
127
|
+
# Fallback: just print bell character (works on most terminals)
|
|
128
|
+
for _ in range(3):
|
|
129
|
+
print('\a') # ASCII bell character
|
|
130
|
+
except Exception as e:
|
|
131
|
+
# Fallback: just print bell character (works on most terminals)
|
|
132
|
+
print(f"Sound method failed: {e}")
|
|
133
|
+
for _ in range(3):
|
|
134
|
+
print('\a') # ASCII bell character
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Example usage
|
|
138
|
+
if __name__ == '__main__':
|
|
139
|
+
app = QApplication(sys.argv)
|
|
140
|
+
|
|
141
|
+
# Create a timer widget with 0 hours, 5 minutes, 30 seconds
|
|
142
|
+
timer_widget = CountdownTimer(hours=0, minutes=0, seconds=3, auto_start=True, show_buttons=False)
|
|
143
|
+
timer_widget.setWindowTitle('Countdown Timer')
|
|
144
|
+
timer_widget.resize(400, 200)
|
|
145
|
+
timer_widget.show()
|
|
146
|
+
|
|
147
|
+
sys.exit(app.exec_())
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
from PyQt5.QtCore import Qt
|
|
2
|
-
from PyQt5.QtWidgets import QPushButton, QTextEdit, QVBoxLayout, QDialog
|
|
3
|
-
|
|
2
|
+
from PyQt5.QtWidgets import QPushButton, QTextEdit, QVBoxLayout, QDialog, QTabWidget, QWidget
|
|
4
3
|
|
|
5
4
|
|
|
6
5
|
class Author(QDialog):
|
|
7
6
|
def __init__(self):
|
|
8
7
|
super().__init__()
|
|
9
8
|
self.setLayout(QVBoxLayout())
|
|
10
|
-
self.setMinimumSize(
|
|
9
|
+
self.setMinimumSize(420, 370)
|
|
10
|
+
self.setMaximumSize(420, 420)
|
|
11
11
|
self.setWindowTitle("About")
|
|
12
|
+
self.tabs = QTabWidget()
|
|
13
|
+
self.layout().addWidget(self.tabs)
|
|
14
|
+
|
|
12
15
|
textEdit = QTextEdit()
|
|
13
16
|
textEdit.setReadOnly(True)
|
|
14
17
|
textEdit.setHtml("""<!DOCTYPE html>
|
|
@@ -94,7 +97,125 @@ class Author(QDialog):
|
|
|
94
97
|
</html>
|
|
95
98
|
|
|
96
99
|
""")
|
|
97
|
-
|
|
100
|
+
textEdit2 = QTextEdit()
|
|
101
|
+
textEdit2.setReadOnly(True)
|
|
102
|
+
textEdit2.setHtml("""<!DOCTYPE html>
|
|
103
|
+
<html lang="en">
|
|
104
|
+
<head>
|
|
105
|
+
<meta charset="UTF-8">
|
|
106
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
107
|
+
<title>SPICE - Shortcuts</title>
|
|
108
|
+
<style>
|
|
109
|
+
body {
|
|
110
|
+
font-family: 'Arial', sans-serif;
|
|
111
|
+
background-color: #f4f4f9;
|
|
112
|
+
color: #333;
|
|
113
|
+
margin: 0;
|
|
114
|
+
padding: 20px;
|
|
115
|
+
}
|
|
116
|
+
h1 {
|
|
117
|
+
font-size: 2.5em;
|
|
118
|
+
color: #2c3e50;
|
|
119
|
+
margin-bottom: 20px;
|
|
120
|
+
text-align: center;
|
|
121
|
+
}
|
|
122
|
+
table {
|
|
123
|
+
width: 70%;
|
|
124
|
+
margin: 0 auto 20px auto;
|
|
125
|
+
border-collapse: collapse;
|
|
126
|
+
border: 1px solid #ddd;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
th, td {
|
|
130
|
+
padding: 4px 10px;
|
|
131
|
+
line-height: 1.1;
|
|
132
|
+
text-align: left;
|
|
133
|
+
}
|
|
134
|
+
th {
|
|
135
|
+
background-color: #f2f2f2;
|
|
136
|
+
}
|
|
137
|
+
tr:hover {
|
|
138
|
+
background-color: #f1f1f1;
|
|
139
|
+
}
|
|
140
|
+
.footer {
|
|
141
|
+
font-size: 1em;
|
|
142
|
+
color: #7f8c8d;
|
|
143
|
+
text-align: center;
|
|
144
|
+
}
|
|
145
|
+
</style>
|
|
146
|
+
</head>
|
|
147
|
+
<body>
|
|
148
|
+
<table>
|
|
149
|
+
<tr>
|
|
150
|
+
<th>Action</th>
|
|
151
|
+
<th>Shortcut</th>
|
|
152
|
+
</tr>
|
|
153
|
+
<tr>
|
|
154
|
+
<td>Autocomplete</td>
|
|
155
|
+
<td>Tab</td>
|
|
156
|
+
</tr>
|
|
157
|
+
<tr>
|
|
158
|
+
<td>Execute Code</td>
|
|
159
|
+
<td>Ctrl + Enter</td>
|
|
160
|
+
</tr>
|
|
161
|
+
<tr>
|
|
162
|
+
<td>Execute Single Line</td>
|
|
163
|
+
<td>Ctrl + Shift + Enter</td>
|
|
164
|
+
</tr>
|
|
165
|
+
|
|
166
|
+
<tr>
|
|
167
|
+
<td>Save</td>
|
|
168
|
+
<td>Ctrl + S</td>
|
|
169
|
+
</tr>
|
|
170
|
+
<tr>
|
|
171
|
+
<td>Save As</td>
|
|
172
|
+
<td>Ctrl + Shift + S</td>
|
|
173
|
+
</tr>
|
|
174
|
+
|
|
175
|
+
<tr>
|
|
176
|
+
<td>New Editor Tab</td>
|
|
177
|
+
<td>Ctrl + E</td>
|
|
178
|
+
</tr>
|
|
179
|
+
<tr>
|
|
180
|
+
<td>Change Tab</td>
|
|
181
|
+
<td>F1 – F10</td>
|
|
182
|
+
</tr>
|
|
183
|
+
|
|
184
|
+
<tr>
|
|
185
|
+
<td>Execute whole code</td>
|
|
186
|
+
<td>F11</td>
|
|
187
|
+
</tr>
|
|
188
|
+
<tr>
|
|
189
|
+
<td>Execute line and Advance</td>
|
|
190
|
+
<td>F12</td>
|
|
191
|
+
</tr>
|
|
192
|
+
|
|
193
|
+
<tr>
|
|
194
|
+
<td>Fullscreen</td>
|
|
195
|
+
<td>Ctrl + L</td>
|
|
196
|
+
</tr>
|
|
197
|
+
|
|
198
|
+
<tr>
|
|
199
|
+
<td>Toggle Visualization</td>
|
|
200
|
+
<td>Ctrl + K</td>
|
|
201
|
+
</tr>
|
|
202
|
+
<tr>
|
|
203
|
+
<td>Toggle Dark Mode</td>
|
|
204
|
+
<td>Ctrl + M</td>
|
|
205
|
+
</tr>
|
|
206
|
+
<tr>
|
|
207
|
+
<td>Change Text Size</td>
|
|
208
|
+
<td>Ctrl + (+ / -)</td>
|
|
209
|
+
</tr>
|
|
210
|
+
|
|
211
|
+
</table>
|
|
212
|
+
</body>
|
|
213
|
+
</html>
|
|
214
|
+
""")
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
self.tabs.addTab(textEdit, "About")
|
|
218
|
+
self.tabs.addTab(textEdit2, "Shortcuts")
|
|
98
219
|
close_button = QPushButton("Close")
|
|
99
220
|
close_button.setMaximumWidth(100)
|
|
100
221
|
# center the button
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
from PyQt5.QtCore import Qt
|
|
4
|
-
from PyQt5.QtGui import QFont, QIcon
|
|
4
|
+
from PyQt5.QtGui import QFont, QIcon, QTextCursor
|
|
5
5
|
from PyQt5.QtWidgets import QVBoxLayout, QToolBar, QStatusBar, QWidget, QComboBox, QShortcut, QTabWidget, QFileDialog, \
|
|
6
6
|
QApplication, QDialog, QMessageBox
|
|
7
|
+
|
|
7
8
|
from spiceditor import utils
|
|
8
9
|
|
|
9
10
|
import spiceditor.resources # noqa
|
|
@@ -17,26 +18,21 @@ class EditorWidget(QWidget):
|
|
|
17
18
|
self.config = config
|
|
18
19
|
editor = config.root().getSubSection("editor", pretty="Editor")
|
|
19
20
|
self.cfg_keep_code = editor.getCheckBox("keep_code",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
self.cfg_show_all = editor.getCheckBox("show_all",
|
|
23
|
-
pretty="Show all Code on Open",
|
|
24
|
-
default=False)
|
|
21
|
+
pretty="Keep Code on Run",
|
|
22
|
+
default=False)
|
|
25
23
|
self.cfg_autocomplete = editor.getString("autocomplete",
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
pretty="Autocomplete",
|
|
25
|
+
default="")
|
|
28
26
|
self.cfg_delay = editor.getSlider("delay",
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
suffix=" ms")
|
|
27
|
+
pretty="Delay",
|
|
28
|
+
min=0, max=100,
|
|
29
|
+
default=25,
|
|
30
|
+
den=1,
|
|
31
|
+
show_value=True)
|
|
35
32
|
|
|
36
33
|
self.cfg_show_sb = editor.getCheckBox("show_tb",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
pretty="Show Toolbar",
|
|
35
|
+
default=False)
|
|
40
36
|
|
|
41
37
|
self.language_editor = language_editor
|
|
42
38
|
self.console = console
|
|
@@ -45,6 +41,7 @@ class EditorWidget(QWidget):
|
|
|
45
41
|
left_layout = QVBoxLayout()
|
|
46
42
|
|
|
47
43
|
self.language_editor.ctrl_enter.connect(self.execute_code)
|
|
44
|
+
self.language_editor.ctrl_shift_enter.connect(self.execute_single_line)
|
|
48
45
|
self.language_editor.info.connect(self.update_status_bar)
|
|
49
46
|
|
|
50
47
|
bar = QToolBar()
|
|
@@ -57,9 +54,9 @@ class EditorWidget(QWidget):
|
|
|
57
54
|
self.keep_banner.setCheckable(True)
|
|
58
55
|
self.keep_banner.setChecked(False)
|
|
59
56
|
|
|
60
|
-
self.show_all =
|
|
61
|
-
self.show_all.setIcon(QIcon(":/icons/radio-button.svg"))
|
|
62
|
-
self.show_all.setCheckable(True)
|
|
57
|
+
# self.show_all = bar.addAction("Show all Code on Load")
|
|
58
|
+
# self.show_all.setIcon(QIcon(":/icons/radio-button.svg"))
|
|
59
|
+
# self.show_all.setCheckable(True)
|
|
63
60
|
|
|
64
61
|
self.text_edit_group = [a1, a2, a3, self.keep_banner]
|
|
65
62
|
bar.addSeparator()
|
|
@@ -74,9 +71,12 @@ class EditorWidget(QWidget):
|
|
|
74
71
|
self.setLayout(left_layout)
|
|
75
72
|
self.setLayout(left_layout)
|
|
76
73
|
|
|
74
|
+
q = QShortcut("F5", self)
|
|
75
|
+
q.activated.connect(self.language_editor.format_code)
|
|
76
|
+
|
|
77
77
|
def update_config(self):
|
|
78
78
|
self.keep_banner.setChecked(self.cfg_keep_code.get())
|
|
79
|
-
self.show_all.setChecked(self.cfg_show_all.get())
|
|
79
|
+
# self.show_all.setChecked(self.cfg_show_all.get())
|
|
80
80
|
self.language_editor.append_autocomplete(self.cfg_autocomplete.get())
|
|
81
81
|
self.language_editor.set_delay(self.cfg_delay.get())
|
|
82
82
|
self.language_editor.set_font_size(self.config.root().get_node("font_size").get() + 10)
|
|
@@ -87,11 +87,11 @@ class EditorWidget(QWidget):
|
|
|
87
87
|
|
|
88
88
|
def load_program(self, path, show_all=False):
|
|
89
89
|
self.path = path
|
|
90
|
-
with open(path) as f:
|
|
90
|
+
with open(path, encoding="utf-8") as f:
|
|
91
91
|
self.language_editor.set_code(f.read())
|
|
92
92
|
self.console.clear()
|
|
93
93
|
|
|
94
|
-
if
|
|
94
|
+
if show_all:
|
|
95
95
|
self.show_all_code()
|
|
96
96
|
|
|
97
97
|
def save_program(self, path, save_as):
|
|
@@ -102,7 +102,7 @@ class EditorWidget(QWidget):
|
|
|
102
102
|
if not filename:
|
|
103
103
|
return
|
|
104
104
|
|
|
105
|
-
self.path = filename.replace(".py","") + ".py"
|
|
105
|
+
self.path = filename.replace(".py", "") + ".py"
|
|
106
106
|
|
|
107
107
|
with open(self.path, "w") as f:
|
|
108
108
|
f.write(self.language_editor.toPlainText())
|
|
@@ -118,9 +118,17 @@ class EditorWidget(QWidget):
|
|
|
118
118
|
# self.console_widget.clear()
|
|
119
119
|
|
|
120
120
|
def execute_code(self):
|
|
121
|
-
self.
|
|
121
|
+
if self.config.root().get_child("format_code_before_run").get_value():
|
|
122
|
+
self.language_editor.format_code()
|
|
123
|
+
|
|
122
124
|
self.console.execute(self.language_editor.toPlainText(), not self.keep_banner.isChecked())
|
|
123
125
|
|
|
126
|
+
def execute_single_line(self, advance=False):
|
|
127
|
+
line = self.language_editor.get_current_line()
|
|
128
|
+
self.console.execute(line, not self.keep_banner.isChecked())
|
|
129
|
+
if advance:
|
|
130
|
+
self.language_editor.moveCursor(QTextCursor.Down)
|
|
131
|
+
|
|
124
132
|
def set_dark_mode(self, dark):
|
|
125
133
|
self.language_editor.set_dark_mode(dark)
|
|
126
134
|
color = Qt.white if dark else Qt.black
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
3
5
|
|
|
4
6
|
from PyQt5 import QtGui
|
|
5
7
|
from PyQt5.QtCore import QObject, pyqtSignal, QDir, QItemSelectionModel, QModelIndex, Qt, QTimer
|
|
@@ -9,12 +11,21 @@ from PyQt5.QtWidgets import QWidget, QTreeView, QFileSystemModel, QVBoxLayout, Q
|
|
|
9
11
|
class Tree(QTreeView):
|
|
10
12
|
delete_requested = pyqtSignal(str)
|
|
11
13
|
|
|
12
|
-
def filter_rows(self):
|
|
14
|
+
def filter_rows(self, extensions=None):
|
|
15
|
+
|
|
16
|
+
extensions = extensions or [".txt", ".py", ".csv"]
|
|
17
|
+
|
|
13
18
|
for i in range(self.model().rowCount(self.rootIndex())):
|
|
14
19
|
child_index = self.model().index(i, 0, self.rootIndex()) # Get index of each row
|
|
15
20
|
filename = self.model().data(child_index) # Get file name
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
full_path = self.model().filePath(child_index)
|
|
22
|
+
if os.path.isdir(full_path):
|
|
23
|
+
if "__pycache__" in full_path:
|
|
24
|
+
self.setRowHidden(i, self.rootIndex(), True)
|
|
25
|
+
else:
|
|
26
|
+
extension = os.path.splitext(filename)[1]
|
|
27
|
+
if extension not in extensions:
|
|
28
|
+
self.setRowHidden(i, self.rootIndex(), True)
|
|
18
29
|
|
|
19
30
|
def contextMenuEvent(self, a0: QtGui.QContextMenuEvent) -> None:
|
|
20
31
|
super().contextMenuEvent(a0)
|
|
@@ -38,14 +49,12 @@ class FileBrowser(QWidget):
|
|
|
38
49
|
def __init__(self, path, filters=None, hide_details=True):
|
|
39
50
|
super().__init__()
|
|
40
51
|
self.current_files = []
|
|
41
|
-
if filters is None:
|
|
42
|
-
filters = ["*.pdf"]
|
|
43
52
|
self.signals = self.Signals()
|
|
44
53
|
self.path = path
|
|
45
54
|
self.treeview = Tree()
|
|
46
55
|
self.treeview.delete_requested.connect(self.delete_requested)
|
|
47
56
|
self.dirModel = QFileSystemModel()
|
|
48
|
-
self.dirModel.directoryLoaded.connect(self.treeview.filter_rows)
|
|
57
|
+
self.dirModel.directoryLoaded.connect(lambda:self.treeview.filter_rows(filters))
|
|
49
58
|
#self.dirModel.setNameFilters(filters)
|
|
50
59
|
self.dirModel.setNameFilterDisables(False)
|
|
51
60
|
|
|
@@ -85,7 +94,18 @@ class FileBrowser(QWidget):
|
|
|
85
94
|
path = self.dirModel.fileInfo(index).absoluteFilePath()
|
|
86
95
|
if os.path.isdir(path):
|
|
87
96
|
return
|
|
88
|
-
|
|
97
|
+
if os.path.isfile(path):
|
|
98
|
+
extension = os.path.splitext(path)[1]
|
|
99
|
+
if extension in [".txt", ".py", ".csv", ".yaml", ".json"]:
|
|
100
|
+
self.signals.file_selected.emit(path)
|
|
101
|
+
else:
|
|
102
|
+
# open with default application
|
|
103
|
+
if sys.platform.startswith("win"):
|
|
104
|
+
os.startfile(path)
|
|
105
|
+
elif sys.platform.startswith("darwin"):
|
|
106
|
+
subprocess.run(["open", path], check=False)
|
|
107
|
+
else:
|
|
108
|
+
subprocess.run(["xdg-open", path], check=False)
|
|
89
109
|
|
|
90
110
|
def btn_up_clicked(self):
|
|
91
111
|
index = self.treeview.rootIndex()
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from PyQt5.QtCore import Qt, QRegExp
|
|
2
|
+
from PyQt5.QtGui import QSyntaxHighlighter, QTextCharFormat, QColor, QFont
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Scheme:
|
|
6
|
+
def __init__(self, keywords, color1, color2):
|
|
7
|
+
self.keywords = keywords
|
|
8
|
+
self.color_light = color1
|
|
9
|
+
self.color_dark = color2
|
|
10
|
+
|
|
11
|
+
class SyntaxHighlighter(QSyntaxHighlighter):
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def __init__(self, *schemes):
|
|
16
|
+
super().__init__(None)
|
|
17
|
+
self.highlighting_rules = []
|
|
18
|
+
self.schemes = schemes
|
|
19
|
+
self.dark = False
|
|
20
|
+
self.keywords = []
|
|
21
|
+
self.apply_schemes()
|
|
22
|
+
|
|
23
|
+
def set_dark_mode(self, dark):
|
|
24
|
+
self.dark = dark
|
|
25
|
+
self.highlighting_rules.clear()
|
|
26
|
+
self.apply_schemes()
|
|
27
|
+
|
|
28
|
+
def apply_schemes(self):
|
|
29
|
+
for scheme in self.schemes: # Scheme
|
|
30
|
+
keyword_format = QTextCharFormat()
|
|
31
|
+
keyword_format.setForeground(scheme.color_light if not self.dark else scheme.color_dark)
|
|
32
|
+
keyword_format.setFontWeight(QFont.Bold)
|
|
33
|
+
|
|
34
|
+
self.highlighting_rules += [(f"\\b{k}\\b", keyword_format) for k in scheme.keywords]
|
|
35
|
+
self.keywords += scheme.keywords
|
|
36
|
+
|
|
37
|
+
string_format = QTextCharFormat()
|
|
38
|
+
string_format.setForeground(Qt.magenta)
|
|
39
|
+
self.highlighting_rules.append((r'".*"', string_format))
|
|
40
|
+
self.highlighting_rules.append((r"'.*'", string_format))
|
|
41
|
+
|
|
42
|
+
comment_format = QTextCharFormat()
|
|
43
|
+
comment_format.setForeground(QColor("green"))
|
|
44
|
+
comment_format.setFontItalic(True)
|
|
45
|
+
self.highlighting_rules.append((r"#.*", comment_format))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def highlightBlock(self, text):
|
|
49
|
+
for pattern, fmt in self.highlighting_rules:
|
|
50
|
+
expression = QRegExp(pattern)
|
|
51
|
+
index = expression.indexIn(text)
|
|
52
|
+
while index >= 0:
|
|
53
|
+
length = expression.matchedLength()
|
|
54
|
+
self.setFormat(index, length, fmt)
|
|
55
|
+
index = expression.indexIn(text, index + length)
|
|
56
|
+
|
|
57
|
+
def get_keywords(self):
|
|
58
|
+
return self.keywords
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class PythonHighlighter(SyntaxHighlighter):
|
|
62
|
+
def __init__(self, dark=False):
|
|
63
|
+
super().__init__(
|
|
64
|
+
Scheme(['return', 'nonlocal', 'elif', 'assert', 'or', 'yield', 'finally',
|
|
65
|
+
'from', 'global', 'del', 'print', 'None', 'pass', 'class', 'as',
|
|
66
|
+
'break', 'while', 'await', 'async', 'range', 'is', 'True', 'lambda',
|
|
67
|
+
'False', 'in', 'import', 'except', 'continue', 'and', 'raise', 'with',
|
|
68
|
+
'if', 'try', 'for', 'else', 'not', 'def', "input", "int", "float", "str",
|
|
69
|
+
"list", "dict", "input", "print", "open", "read", "write", "close", "split",], Qt.blue, Qt.cyan),
|
|
70
|
+
Scheme(['self'], Qt.darkMagenta, Qt.magenta))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PascalHighlighter(SyntaxHighlighter):
|
|
74
|
+
def __init__(self, dark=False):
|
|
75
|
+
super().__init__([([
|
|
76
|
+
"and", "array", "asm", "begin", "case", "const", "constructor", "destructor",
|
|
77
|
+
"div", "do", "downto", "else", "end", "file", "for", "function", "goto", "if",
|
|
78
|
+
"implementation", "in", "inherited", "inline", "interface", "label", "mod", "nil",
|
|
79
|
+
"not", "object", "of", "or", "packed", "procedure", "program", "record", "repeat",
|
|
80
|
+
"set", "shl", "shr", "string", "then", "to", "type", "unit", "until", "uses",
|
|
81
|
+
"var", "while", "with", "xor", "AND", "ARRAY", "ASM", "BEGIN", "CASE", "CONST", "CONSTRUCTOR", "DESTRUCTOR",
|
|
82
|
+
"DIV", "DO", "DOWNTO", "ELSE", "END", "FILE", "FOR", "FUNCTION", "GOTO", "IF",
|
|
83
|
+
"IMPLEMENTATION", "IN", "INHERITED", "INLINE", "INTERFACE", "LABEL", "MOD", "NIL",
|
|
84
|
+
"NOT", "OBJECT", "OF", "OR", "PACKED", "PROCEDURE", "PROGRAM", "RECORD", "REPEAT",
|
|
85
|
+
"SET", "SHL", "SHR", "STRING", "THEN", "TO", "TYPE", "UNIT", "UNTIL", "USES",
|
|
86
|
+
"VAR", "WHILE", "WITH", "XOR"
|
|
87
|
+
], Qt.blue, Qt.blue)])
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import shutil
|
|
3
|
+
from pyshortcuts import make_shortcut
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def create_shortcuts():
|
|
7
|
+
"""Create application shortcuts"""
|
|
8
|
+
# The command name from your entry_points
|
|
9
|
+
command_name = 'spice'
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
# Try to find it in PATH first
|
|
13
|
+
script_path = shutil.which(command_name)
|
|
14
|
+
|
|
15
|
+
if script_path is None:
|
|
16
|
+
print(f"Error: '{command_name}' not found in PATH.")
|
|
17
|
+
print(f"Make sure the package is installed and try again.")
|
|
18
|
+
print(f"Try running: pip install --force-reinstall your-package")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
print(f"Found script at: {script_path}")
|
|
22
|
+
|
|
23
|
+
make_shortcut(
|
|
24
|
+
script=script_path,
|
|
25
|
+
name='Spice',
|
|
26
|
+
description='The Spice Editor - A Python IDE for Students',
|
|
27
|
+
icon='path/to/icon.png',
|
|
28
|
+
terminal=False,
|
|
29
|
+
startmenu=True,
|
|
30
|
+
)
|
|
31
|
+
print("✓ Shortcuts created successfully!")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
print(f"Failed to create shortcuts: {e}")
|
|
34
|
+
sys.exit(1)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
if __name__ == '__main__':
|
|
38
|
+
create_shortcuts()
|