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/textract.py
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import platform
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
import fitz # PyMuPDF
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import fitz # PyMuPDF
|
|
9
|
+
from PyQt5.QtCore import Qt, QTimer, pyqtSignal, QLine, QLineF, QRectF, QPointF
|
|
10
|
+
from PyQt5.QtGui import QPixmap, QImage, QFont, QPainter, QColor, QCursor, QIcon, QTransform, QPen
|
|
11
|
+
from PyQt5.QtWidgets import QMainWindow, QLabel, QSizePolicy, QApplication, QVBoxLayout, QWidget, QPushButton, \
|
|
12
|
+
QShortcut, QInputDialog, QTabBar, QToolBar, QHBoxLayout, QComboBox, QGraphicsView, QGraphicsScene, \
|
|
13
|
+
QGraphicsPixmapItem, QGraphicsItem, QGraphicsEllipseItem, \
|
|
14
|
+
QGraphicsRectItem, QGraphicsProxyWidget, QGraphicsLineItem
|
|
15
|
+
from pymupdf import Rect
|
|
16
|
+
from scipy.signal import savgol_filter
|
|
17
|
+
|
|
18
|
+
from spiceditor.utils import create_cursor_image
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Eraser(QGraphicsEllipseItem):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def smooth_with_savgol(points, window_size=10, poly_order=2):
|
|
26
|
+
if len(points) < window_size:
|
|
27
|
+
return points
|
|
28
|
+
x = [p[0] for p in points]
|
|
29
|
+
y = [p[1] for p in points]
|
|
30
|
+
smoothed_x = savgol_filter(x, window_size, poly_order)
|
|
31
|
+
smoothed_y = savgol_filter(y, window_size, poly_order)
|
|
32
|
+
return list(zip(smoothed_x, smoothed_y))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class GraphicsScene(QGraphicsScene):
|
|
36
|
+
NONE = 0
|
|
37
|
+
POINTER = 1
|
|
38
|
+
WRITING = 2
|
|
39
|
+
ERASING = 3
|
|
40
|
+
RECTANGLES = 4
|
|
41
|
+
ELLIPSES = 5
|
|
42
|
+
|
|
43
|
+
navigate = pyqtSignal(int)
|
|
44
|
+
|
|
45
|
+
def keyPressEvent(self, event):
|
|
46
|
+
super().keyPressEvent(event)
|
|
47
|
+
if event.key() == Qt.Key_E:
|
|
48
|
+
self.status = GraphicsScene.ERASING
|
|
49
|
+
elif event.key() == Qt.Key_W:
|
|
50
|
+
self.status = GraphicsScene.WRITING
|
|
51
|
+
elif event.key() in [Qt.Key_Right, Qt.Key_Down]:
|
|
52
|
+
self.navigate.emit(1)
|
|
53
|
+
elif event.key() in [Qt.Key_Left, Qt.Key_Up]:
|
|
54
|
+
self.navigate.emit(-1)
|
|
55
|
+
|
|
56
|
+
def __init__(self):
|
|
57
|
+
super().__init__()
|
|
58
|
+
self.handwriting = []
|
|
59
|
+
self.pixmap = QGraphicsPixmapItem()
|
|
60
|
+
self.pixmap.setTransformationMode(Qt.SmoothTransformation) # Enable smooth transformation for the pixmap item
|
|
61
|
+
|
|
62
|
+
self.addItem(self.pixmap)
|
|
63
|
+
self.image = None
|
|
64
|
+
self.start = None
|
|
65
|
+
self.page = 0
|
|
66
|
+
self.status = GraphicsScene.NONE
|
|
67
|
+
|
|
68
|
+
self.gum = Eraser(0, 0, 100, 100, self.pixmap)
|
|
69
|
+
self.gum.setBrush(QColor(255, 255, 255))
|
|
70
|
+
self.gum.setPen(QPen(QColor(0, 0, 0), 2))
|
|
71
|
+
self.gum.setVisible(False)
|
|
72
|
+
self.drawings = {}
|
|
73
|
+
self.rectangle = None
|
|
74
|
+
self.color = QPen(Qt.black, 2)
|
|
75
|
+
# make the ellipse movable
|
|
76
|
+
# self.gum.setFlag(QGraphicsItem.ItemIsMovable)
|
|
77
|
+
|
|
78
|
+
def set_image(self, image, page):
|
|
79
|
+
current = self.drawings.get(self.page, [])
|
|
80
|
+
for item in current:
|
|
81
|
+
self.removeItem(item)
|
|
82
|
+
|
|
83
|
+
self.image = image
|
|
84
|
+
self.page = page
|
|
85
|
+
# self.resize_image(size)
|
|
86
|
+
self.pixmap.setPixmap(self.image)
|
|
87
|
+
|
|
88
|
+
for item in self.drawings.get(self.page, []):
|
|
89
|
+
self.addItem(item)
|
|
90
|
+
|
|
91
|
+
def mousePressEvent(self, event):
|
|
92
|
+
event.accept()
|
|
93
|
+
super().mousePressEvent(event)
|
|
94
|
+
if event.button() == Qt.RightButton:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
self.start = event.scenePos()
|
|
98
|
+
|
|
99
|
+
# get objects at the click position
|
|
100
|
+
items = self.items(self.start)
|
|
101
|
+
for item in items:
|
|
102
|
+
if type(item) not in [QGraphicsPixmapItem, Eraser]:
|
|
103
|
+
self.start = None
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
if self.status == GraphicsScene.WRITING:
|
|
107
|
+
self.handwriting.clear()
|
|
108
|
+
|
|
109
|
+
elif self.status == GraphicsScene.RECTANGLES:
|
|
110
|
+
self.rectangle = self.addRect(QRectF(self.start, self.start), self.color)
|
|
111
|
+
|
|
112
|
+
elif self.status == GraphicsScene.ELLIPSES:
|
|
113
|
+
self.rectangle = self.addEllipse(QRectF(self.start, self.start), self.color)
|
|
114
|
+
|
|
115
|
+
if self.rectangle is not None:
|
|
116
|
+
self.rectangle.setFlag(QGraphicsItem.ItemIsMovable)
|
|
117
|
+
if self.drawings.get(self.page, None) is None:
|
|
118
|
+
self.drawings[self.page] = []
|
|
119
|
+
self.drawings[self.page].append(self.rectangle)
|
|
120
|
+
|
|
121
|
+
def mouseMoveEvent(self, event):
|
|
122
|
+
super().mouseMoveEvent(event)
|
|
123
|
+
if self.status == GraphicsScene.WRITING:
|
|
124
|
+
if self.start is None:
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
line = self.addLine(QLineF(self.start, event.scenePos()), self.color)
|
|
128
|
+
self.handwriting.append(line)
|
|
129
|
+
self.start = event.scenePos()
|
|
130
|
+
|
|
131
|
+
elif self.status == GraphicsScene.ERASING:
|
|
132
|
+
if self.start is not None:
|
|
133
|
+
self.gum.setPos(event.scenePos() - QLine(50, 50, 50, 50).p2())
|
|
134
|
+
colliding = self.gum.collidingItems()
|
|
135
|
+
for item in colliding:
|
|
136
|
+
if type(item) in [QGraphicsPixmapItem, QGraphicsProxyWidget]:
|
|
137
|
+
continue
|
|
138
|
+
if item in self.drawings.get(self.page, []):
|
|
139
|
+
self.drawings[self.page].remove(item)
|
|
140
|
+
self.removeItem(item)
|
|
141
|
+
|
|
142
|
+
elif self.status in [GraphicsScene.RECTANGLES, GraphicsScene.ELLIPSES]:
|
|
143
|
+
if self.start is None:
|
|
144
|
+
return
|
|
145
|
+
self.rectangle.setRect(QRectF(self.start, event.scenePos()))
|
|
146
|
+
|
|
147
|
+
def mouseReleaseEvent(self, event):
|
|
148
|
+
super().mouseReleaseEvent(event)
|
|
149
|
+
self.start = None
|
|
150
|
+
self.rectangle = None
|
|
151
|
+
|
|
152
|
+
if self.status == GraphicsScene.WRITING:
|
|
153
|
+
self.smooth_handwriting()
|
|
154
|
+
|
|
155
|
+
def smooth_handwriting(self):
|
|
156
|
+
points = []
|
|
157
|
+
for p in self.handwriting: # type: QGraphicsLineItem
|
|
158
|
+
points.append((p.line().x1(), p.line().y1()))
|
|
159
|
+
|
|
160
|
+
for line in self.handwriting:
|
|
161
|
+
self.removeItem(line)
|
|
162
|
+
|
|
163
|
+
smoothed = smooth_with_savgol(points, 20, 4)
|
|
164
|
+
|
|
165
|
+
if len(smoothed) > 0:
|
|
166
|
+
p1 = smoothed[0]
|
|
167
|
+
for p2 in smoothed[1:]:
|
|
168
|
+
line = self.addLine(QLineF(QPointF(*p1), QPointF(*p2)), self.color)
|
|
169
|
+
if self.drawings.get(self.page) is None:
|
|
170
|
+
self.drawings[self.page] = []
|
|
171
|
+
self.drawings[self.page].append(line)
|
|
172
|
+
p1 = p2
|
|
173
|
+
|
|
174
|
+
def erase_all(self):
|
|
175
|
+
for item in self.drawings.get(self.page, []):
|
|
176
|
+
self.removeItem(item)
|
|
177
|
+
self.drawings[self.page] = []
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class GraphicsView(QGraphicsView):
|
|
181
|
+
resized = pyqtSignal()
|
|
182
|
+
|
|
183
|
+
def __init__(self, a):
|
|
184
|
+
super().__init__(a)
|
|
185
|
+
self.setRenderHint(QPainter.SmoothPixmapTransform)
|
|
186
|
+
self.setRenderHint(QPainter.Antialiasing)
|
|
187
|
+
self.setRenderHint(QPainter.TextAntialiasing)
|
|
188
|
+
self.setRenderHint(QPainter.HighQualityAntialiasing)
|
|
189
|
+
# self.scence = None
|
|
190
|
+
|
|
191
|
+
def resizeEvent(self, event):
|
|
192
|
+
super().resizeEvent(event)
|
|
193
|
+
transf = QTransform()
|
|
194
|
+
ratio1 = self.viewport().size().width() / self.scene().image.width()
|
|
195
|
+
ratio2 = self.viewport().size().height() / self.scene().image.height()
|
|
196
|
+
ratio = min(ratio1, ratio2)
|
|
197
|
+
transf.scale(ratio, ratio)
|
|
198
|
+
self.setTransform(transf)
|
|
199
|
+
self.scene().setSceneRect(0, 0, self.scene().image.width(), self.scene().image.height())
|
|
200
|
+
self.resized.emit()
|
|
201
|
+
|
|
202
|
+
# if self.scence is not None:
|
|
203
|
+
# self.scene().removeItem(self.scence)
|
|
204
|
+
# self.scence = self.scene().addRect(self.sceneRect(), QColor(255, 0, 0))
|
|
205
|
+
|
|
206
|
+
def set_image(self, image, page):
|
|
207
|
+
self.scene().set_image(image, page)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class Slides(QWidget):
|
|
211
|
+
play_code = pyqtSignal(str)
|
|
212
|
+
|
|
213
|
+
def set_writing_mode(self, mode):
|
|
214
|
+
for i, elem in enumerate(self.group):
|
|
215
|
+
elem.blockSignals(True)
|
|
216
|
+
elem.setChecked(i == mode)
|
|
217
|
+
elem.blockSignals(False)
|
|
218
|
+
|
|
219
|
+
self.scene.status = mode
|
|
220
|
+
self.scene.gum.setVisible(mode == GraphicsScene.ERASING)
|
|
221
|
+
|
|
222
|
+
if mode == GraphicsScene.POINTER:
|
|
223
|
+
self.view.viewport().setCursor(QCursor(create_cursor_image()))
|
|
224
|
+
else:
|
|
225
|
+
self.view.viewport().setCursor(Qt.ArrowCursor)
|
|
226
|
+
|
|
227
|
+
def set_color(self, color):
|
|
228
|
+
for i, elem in enumerate(self.color_group):
|
|
229
|
+
elem.blockSignals(True)
|
|
230
|
+
elem.setChecked(i == color)
|
|
231
|
+
elem.blockSignals(False)
|
|
232
|
+
|
|
233
|
+
colors = [Qt.black, Qt.red, Qt.green, Qt.blue, Qt.yellow, Qt.magenta, Qt.cyan, Qt.gray]
|
|
234
|
+
self.scene.color = QPen(colors[color], self.scene.color.width())
|
|
235
|
+
if self.scene.status in [GraphicsScene.NONE, GraphicsScene.ERASING]:
|
|
236
|
+
self.set_writing_mode(GraphicsScene.WRITING)
|
|
237
|
+
|
|
238
|
+
def erase_all(self):
|
|
239
|
+
self.scene.erase_all()
|
|
240
|
+
|
|
241
|
+
def create_toolbar(self):
|
|
242
|
+
toolbar = QToolBar()
|
|
243
|
+
# toolbar.setMaximumHeight(35)
|
|
244
|
+
toolbar.show()
|
|
245
|
+
|
|
246
|
+
none = toolbar.addAction("", lambda: self.set_writing_mode(0))
|
|
247
|
+
none.setIcon(QIcon(":/icons/cursor.svg"))
|
|
248
|
+
none.setCheckable(True)
|
|
249
|
+
none.setChecked(True)
|
|
250
|
+
|
|
251
|
+
pointer = toolbar.addAction("Pointer", lambda: self.set_writing_mode(1))
|
|
252
|
+
pointer.setIcon(QIcon(":/icons/origin.svg"))
|
|
253
|
+
pointer.setCheckable(True)
|
|
254
|
+
|
|
255
|
+
write = toolbar.addAction("", lambda: self.set_writing_mode(2))
|
|
256
|
+
write.setIcon(QIcon(":/icons/edit.svg"))
|
|
257
|
+
write.setCheckable(True)
|
|
258
|
+
|
|
259
|
+
rectangle = toolbar.addAction(QIcon(":/icons/rectangle.svg"), "", lambda: self.set_writing_mode(4))
|
|
260
|
+
rectangle.setCheckable(True)
|
|
261
|
+
circle = toolbar.addAction(QIcon(":/icons/circle.svg"), "", lambda: self.set_writing_mode(5))
|
|
262
|
+
circle.setCheckable(True)
|
|
263
|
+
|
|
264
|
+
toolbar.addSeparator()
|
|
265
|
+
erase = toolbar.addAction("", lambda: self.set_writing_mode(3))
|
|
266
|
+
erase.setIcon(QIcon(":/icons/cancel.svg"))
|
|
267
|
+
erase.setCheckable(True)
|
|
268
|
+
toolbar.addSeparator()
|
|
269
|
+
|
|
270
|
+
self.group = [none, pointer, write, erase, rectangle, circle]
|
|
271
|
+
|
|
272
|
+
erase_all = toolbar.addAction("", lambda: self.erase_all())
|
|
273
|
+
erase_all.setIcon(QIcon(":/icons/bin.svg"))
|
|
274
|
+
|
|
275
|
+
toolbar.addSeparator()
|
|
276
|
+
t1 = toolbar.addAction(QIcon(":/icons/minus.svg"), "", lambda: self.set_thickness(0))
|
|
277
|
+
t1.setCheckable(True)
|
|
278
|
+
t1.setChecked(True)
|
|
279
|
+
t2 = toolbar.addAction(QIcon(":/icons/minus_med.svg"), "", lambda: self.set_thickness(1))
|
|
280
|
+
t2.setCheckable(True)
|
|
281
|
+
t3 = toolbar.addAction(QIcon(":/icons/minus_big.svg"), "", lambda: self.set_thickness(2))
|
|
282
|
+
t3.setCheckable(True)
|
|
283
|
+
|
|
284
|
+
self.thickness_group = [t1, t2, t3]
|
|
285
|
+
|
|
286
|
+
toolbar.addSeparator()
|
|
287
|
+
black = toolbar.addAction("", lambda: self.set_color(0))
|
|
288
|
+
black.setIcon(QIcon(":/icons/black.svg"))
|
|
289
|
+
red = toolbar.addAction("", lambda: self.set_color(1))
|
|
290
|
+
red.setIcon(QIcon(":/icons/red.svg"))
|
|
291
|
+
green = toolbar.addAction("", lambda: self.set_color(2))
|
|
292
|
+
green.setIcon(QIcon(":/icons/green.svg"))
|
|
293
|
+
blue = toolbar.addAction("", lambda: self.set_color(3))
|
|
294
|
+
blue.setIcon(QIcon(":/icons/blue.svg"))
|
|
295
|
+
|
|
296
|
+
self.color_group = [black, red, green, blue]
|
|
297
|
+
for elem in self.color_group:
|
|
298
|
+
elem.setCheckable(True)
|
|
299
|
+
black.setChecked(True)
|
|
300
|
+
|
|
301
|
+
toolbar.addSeparator()
|
|
302
|
+
|
|
303
|
+
prev = toolbar.addAction("✕", lambda: self.move_to(False))
|
|
304
|
+
prev.setIcon(QIcon(":/icons/arrow-left.svg"))
|
|
305
|
+
|
|
306
|
+
self.action_touchable = toolbar.addAction("Hold")
|
|
307
|
+
self.action_touchable.setCheckable(True)
|
|
308
|
+
self.action_touchable.setIcon(QIcon(":/icons/pan.svg"))
|
|
309
|
+
|
|
310
|
+
toolbar.addAction(QIcon(":/icons/plus.svg"), "", self.add_empty_page)
|
|
311
|
+
|
|
312
|
+
next1 = toolbar.addAction("⬇", lambda: self.move_to(True))
|
|
313
|
+
next1.setIcon(QIcon(":/icons/arrow-right.svg"))
|
|
314
|
+
return toolbar
|
|
315
|
+
|
|
316
|
+
def add_empty_page(self):
|
|
317
|
+
self.page += 1
|
|
318
|
+
self.pages_number.insert(self.page, None)
|
|
319
|
+
self.update_image()
|
|
320
|
+
|
|
321
|
+
def set_thickness(self, thickness):
|
|
322
|
+
for i, elem in enumerate(self.thickness_group):
|
|
323
|
+
elem.blockSignals(True)
|
|
324
|
+
elem.setChecked(i == thickness)
|
|
325
|
+
elem.blockSignals(False)
|
|
326
|
+
|
|
327
|
+
self.scene.color = QPen(self.scene.color.color(), 2 + thickness * 4)
|
|
328
|
+
if self.scene.status in [GraphicsScene.NONE, GraphicsScene.ERASING]:
|
|
329
|
+
self.set_writing_mode(GraphicsScene.WRITING)
|
|
330
|
+
|
|
331
|
+
def __init__(self, config, pdf_path, page):
|
|
332
|
+
super().__init__()
|
|
333
|
+
self.config = config
|
|
334
|
+
self.thickness = 2
|
|
335
|
+
self.action_touchable = None
|
|
336
|
+
self.group = []
|
|
337
|
+
self.color_group = []
|
|
338
|
+
self.touchable = True
|
|
339
|
+
self.program = ""
|
|
340
|
+
self.code_buttons = []
|
|
341
|
+
self.code_line_height = 0
|
|
342
|
+
self.resized_pixmap = None
|
|
343
|
+
self.doc = fitz.open(pdf_path)
|
|
344
|
+
self.pages_number = [i for i in range(len(self.doc))]
|
|
345
|
+
|
|
346
|
+
self.filename = pdf_path
|
|
347
|
+
self.page = page
|
|
348
|
+
self.base = None
|
|
349
|
+
|
|
350
|
+
# Create a QLabel to display the image
|
|
351
|
+
self.scene = GraphicsScene()
|
|
352
|
+
self.scene.navigate.connect(self.navigate)
|
|
353
|
+
self.view = GraphicsView(self.scene)
|
|
354
|
+
self.view.resized.connect(self.resized)
|
|
355
|
+
self.view.setAlignment(Qt.AlignCenter)
|
|
356
|
+
self.view.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
|
|
357
|
+
|
|
358
|
+
self.base = QGraphicsRectItem(0, 0, 35, 15, self.scene.pixmap)
|
|
359
|
+
self.base.setBrush(QColor(220, 220, 220))
|
|
360
|
+
self.base.setPen(Qt.transparent)
|
|
361
|
+
self.proxy = QGraphicsProxyWidget(self.base)
|
|
362
|
+
|
|
363
|
+
# add shortcut ctrl+n to number of page
|
|
364
|
+
def get_number_of_page():
|
|
365
|
+
a, b = QInputDialog.getInt(self, "Number of page", "Enter the number of page", self.page + 1, 1,
|
|
366
|
+
len(self.doc), 1, Qt.WindowFlags())
|
|
367
|
+
self.page = a - 1
|
|
368
|
+
self.update_image()
|
|
369
|
+
|
|
370
|
+
q = QShortcut("Ctrl+N", self)
|
|
371
|
+
q.activated.connect(get_number_of_page)
|
|
372
|
+
|
|
373
|
+
# Set up a layout
|
|
374
|
+
layout = QHBoxLayout()
|
|
375
|
+
layout.setSpacing(0)
|
|
376
|
+
layout.addWidget(self.view)
|
|
377
|
+
|
|
378
|
+
self.setLayout(layout)
|
|
379
|
+
self.update_image()
|
|
380
|
+
|
|
381
|
+
self.toolbar_float = None
|
|
382
|
+
self.toolbar = self.create_toolbar()
|
|
383
|
+
self.set_toolbar_float(True)
|
|
384
|
+
|
|
385
|
+
QTimer.singleShot(0, self.resize_image)
|
|
386
|
+
|
|
387
|
+
QApplication.instance().setStyleSheet("""
|
|
388
|
+
QToolTip {
|
|
389
|
+
font-family: 'Courier New', monospace;
|
|
390
|
+
font-size: 12pt;
|
|
391
|
+
color: #ffffff;
|
|
392
|
+
background-color: #333333;
|
|
393
|
+
border: 1px solid #ffffff;
|
|
394
|
+
}
|
|
395
|
+
""")
|
|
396
|
+
|
|
397
|
+
def is_toolbar_float(self):
|
|
398
|
+
return self.toolbar_float
|
|
399
|
+
|
|
400
|
+
def set_toolbar_float(self, value, parent=None):
|
|
401
|
+
|
|
402
|
+
if value:
|
|
403
|
+
self.toolbar.setParent(None)
|
|
404
|
+
self.toolbar.setOrientation(Qt.Horizontal)
|
|
405
|
+
self.proxy.setWidget(self.toolbar)
|
|
406
|
+
self.proxy.setPos(-2, 10)
|
|
407
|
+
self.proxy.setZValue(1)
|
|
408
|
+
self.base.setFlags(QGraphicsItem.ItemIgnoresTransformations | QGraphicsItem.ItemIsMovable)
|
|
409
|
+
self.proxy.show()
|
|
410
|
+
self.base.show()
|
|
411
|
+
self.toolbar.show()
|
|
412
|
+
|
|
413
|
+
else:
|
|
414
|
+
self.proxy.setWidget(None)
|
|
415
|
+
self.base.hide()
|
|
416
|
+
self.toolbar.setParent(parent)
|
|
417
|
+
self.toolbar.setOrientation(Qt.Horizontal)
|
|
418
|
+
self.toolbar.show()
|
|
419
|
+
|
|
420
|
+
self.toolbar_float = value
|
|
421
|
+
|
|
422
|
+
def resized(self):
|
|
423
|
+
if self.base is not None:
|
|
424
|
+
self.base.setPos(0, 0)
|
|
425
|
+
|
|
426
|
+
def get_toolbar(self):
|
|
427
|
+
return self.toolbar
|
|
428
|
+
|
|
429
|
+
def navigate(self, delta):
|
|
430
|
+
self.page = (self.page + delta) % len(self.doc)
|
|
431
|
+
self.update_image()
|
|
432
|
+
|
|
433
|
+
def play_program(self, program):
|
|
434
|
+
self.setFocus()
|
|
435
|
+
self.play_code.emit(program)
|
|
436
|
+
return
|
|
437
|
+
|
|
438
|
+
# Command to open a new terminal and execute the Python code
|
|
439
|
+
if platform.system() == "Linux":
|
|
440
|
+
subprocess.run([
|
|
441
|
+
"gnome-terminal",
|
|
442
|
+
"--",
|
|
443
|
+
"bash",
|
|
444
|
+
"-c",
|
|
445
|
+
f"python3 -c \"{self.program}\"; exec bash"
|
|
446
|
+
])
|
|
447
|
+
else:
|
|
448
|
+
# Command to open a new PowerShell window and execute Python code
|
|
449
|
+
subprocess.run([
|
|
450
|
+
"powershell",
|
|
451
|
+
"-NoExit",
|
|
452
|
+
"-Command",
|
|
453
|
+
f"python -c \"{self.program}\""
|
|
454
|
+
])
|
|
455
|
+
|
|
456
|
+
def mousePressEvent(self, a0):
|
|
457
|
+
super().mousePressEvent(a0)
|
|
458
|
+
if a0.button() == Qt.RightButton:
|
|
459
|
+
return
|
|
460
|
+
|
|
461
|
+
if self.scene.status != GraphicsScene.NONE:
|
|
462
|
+
return
|
|
463
|
+
|
|
464
|
+
if not self.action_touchable.isChecked():
|
|
465
|
+
right_side = a0.x() > self.view.width() // 2
|
|
466
|
+
self.move_to(right_side)
|
|
467
|
+
|
|
468
|
+
def move_to(self, right_side):
|
|
469
|
+
if right_side:
|
|
470
|
+
self.page = (self.page + 1) % len(self.doc)
|
|
471
|
+
else:
|
|
472
|
+
self.page = (self.page - 1) % len(self.doc)
|
|
473
|
+
|
|
474
|
+
self.update_image()
|
|
475
|
+
|
|
476
|
+
def toggle_cursor(self):
|
|
477
|
+
if self.view.cursor() == Qt.ArrowCursor:
|
|
478
|
+
self.set_custom_cursor(self.view)
|
|
479
|
+
else:
|
|
480
|
+
self.view.setCursor(Qt.ArrowCursor)
|
|
481
|
+
self.view.update()
|
|
482
|
+
|
|
483
|
+
def update_image(self):
|
|
484
|
+
# Clear buttons
|
|
485
|
+
for button, _ in self.code_buttons:
|
|
486
|
+
self.scene.removeItem(button)
|
|
487
|
+
self.code_buttons.clear()
|
|
488
|
+
|
|
489
|
+
# Get number of page
|
|
490
|
+
pdf_page = self.pages_number[self.page]
|
|
491
|
+
if pdf_page is None:
|
|
492
|
+
image = QPixmap(1920, 1080)
|
|
493
|
+
image.fill(Qt.white)
|
|
494
|
+
else:
|
|
495
|
+
page = self.doc[pdf_page]
|
|
496
|
+
pix = page.get_pixmap(matrix=fitz.Matrix(2, 2), alpha=False, annots=True)
|
|
497
|
+
image = QImage(pix.samples, pix.width, pix.height, pix.stride, QImage.Format_RGB888)
|
|
498
|
+
for d in page.get_drawings():
|
|
499
|
+
fill = d.get("fill")
|
|
500
|
+
type = d.get("type")
|
|
501
|
+
color = d.get("color")
|
|
502
|
+
|
|
503
|
+
if type != 'f' and color == (1.0, 0, 1.0):
|
|
504
|
+
rect = d.get("rect")
|
|
505
|
+
self.extract_text_and_fonts(rect)
|
|
506
|
+
|
|
507
|
+
self.update_button_pos()
|
|
508
|
+
|
|
509
|
+
self.pixmap = QPixmap(image)
|
|
510
|
+
self.resize_image()
|
|
511
|
+
self.view.set_image(self.pixmap, self.page)
|
|
512
|
+
|
|
513
|
+
def resizeEvent(self, event):
|
|
514
|
+
super().resizeEvent(event)
|
|
515
|
+
self.resize_image()
|
|
516
|
+
|
|
517
|
+
def resize_image(self):
|
|
518
|
+
# Resize the pixmap to fit the QLabel while maintaining aspect ratio
|
|
519
|
+
if not self.pixmap is None and not self.pixmap.isNull():
|
|
520
|
+
self.resized_pixmap = self.pixmap.scaled(
|
|
521
|
+
self.view.size(),
|
|
522
|
+
Qt.KeepAspectRatio,
|
|
523
|
+
Qt.SmoothTransformation
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
def extract_text_and_fonts(self, rect=None):
|
|
527
|
+
lines = []
|
|
528
|
+
try:
|
|
529
|
+
|
|
530
|
+
page_without_empty = 0
|
|
531
|
+
for i in range(self.page+1):
|
|
532
|
+
page_without_empty += 1 if self.pages_number[i] is not None else 0
|
|
533
|
+
|
|
534
|
+
page = self.doc[page_without_empty - 1]
|
|
535
|
+
|
|
536
|
+
# Extract blocks of text
|
|
537
|
+
blocks = page.get_text("dict", clip=rect)['blocks']
|
|
538
|
+
for block in blocks:
|
|
539
|
+
if 'lines' in block: # Ensure the block contains text
|
|
540
|
+
|
|
541
|
+
block_bbox = block['bbox'] # Get block position
|
|
542
|
+
for line in block['lines']:
|
|
543
|
+
line_text = ""
|
|
544
|
+
line_box = line["bbox"]
|
|
545
|
+
for span in line['spans']:
|
|
546
|
+
text = span['text']
|
|
547
|
+
font = span['font']
|
|
548
|
+
position = span['bbox'] # Get position of the span
|
|
549
|
+
line_text += text
|
|
550
|
+
|
|
551
|
+
lines.append((line_text, line_box))
|
|
552
|
+
|
|
553
|
+
except Exception as e:
|
|
554
|
+
print(f"An error occurred: {e}")
|
|
555
|
+
|
|
556
|
+
if len(lines) != 0:
|
|
557
|
+
rect: Rect
|
|
558
|
+
|
|
559
|
+
xs = set()
|
|
560
|
+
for text, pos in lines:
|
|
561
|
+
x1, y1, x2, y2 = pos
|
|
562
|
+
x1 = int(x1)
|
|
563
|
+
xs.add(x1)
|
|
564
|
+
xs = list(xs)
|
|
565
|
+
xs.sort()
|
|
566
|
+
|
|
567
|
+
program = str()
|
|
568
|
+
for text, pos in lines:
|
|
569
|
+
x1, y1, x2, y2 = pos
|
|
570
|
+
x1 = int(x1)
|
|
571
|
+
i = xs.index(x1) * 4
|
|
572
|
+
program += " " * i + text + "\n"
|
|
573
|
+
|
|
574
|
+
code_pos = (rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0)
|
|
575
|
+
|
|
576
|
+
play_button = QPushButton()
|
|
577
|
+
play_button.setFixedSize(45, 45)
|
|
578
|
+
play_button.setIcon(self.style().standardIcon(QApplication.style().SP_MediaPlay))
|
|
579
|
+
play_button.clicked.connect(lambda x=program, y=program: self.play_program(y))
|
|
580
|
+
|
|
581
|
+
proxy = QGraphicsProxyWidget(self.scene.pixmap)
|
|
582
|
+
proxy.setWidget(play_button)
|
|
583
|
+
# proxy.setFlags(QGraphicsItem.ItemIgnoresTransformations)
|
|
584
|
+
|
|
585
|
+
# Add the proxy widget to the scene
|
|
586
|
+
# self.scene.addItem(proxy)
|
|
587
|
+
|
|
588
|
+
self.code_buttons.append((proxy, code_pos))
|
|
589
|
+
play_button.setToolTip(self.program)
|
|
590
|
+
|
|
591
|
+
def update_button_pos(self):
|
|
592
|
+
|
|
593
|
+
for button, code_pos in self.code_buttons:
|
|
594
|
+
code_x, code_y, code_w, code_h = code_pos
|
|
595
|
+
button: QGraphicsRectItem
|
|
596
|
+
button.setPos((code_x + code_w) * 2 - button.sceneBoundingRect().width(),
|
|
597
|
+
(code_y + code_h) * 2 - button.sceneBoundingRect().height())
|
pyspice/utils.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from PyQt5.QtCore import Qt
|
|
2
|
+
from PyQt5.QtGui import QPixmap, QColor, QPainter
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def create_cursor_image(size=30):
|
|
6
|
+
size = 30 # Size of the cursor
|
|
7
|
+
cursor_image = QPixmap(size, size)
|
|
8
|
+
cursor_image.fill(Qt.transparent) # Fill the pixmap with transparency
|
|
9
|
+
|
|
10
|
+
# Create a QPainter to draw the red dot
|
|
11
|
+
painter = QPainter(cursor_image)
|
|
12
|
+
painter.setBrush(QColor(255, 0, 0, 128)) # Red color
|
|
13
|
+
painter.setPen(Qt.NoPen)
|
|
14
|
+
painter.drawEllipse(0, 0, size, size)
|
|
15
|
+
painter.end()
|
|
16
|
+
|
|
17
|
+
return cursor_image
|
|
18
|
+
|
|
19
|
+
def color(icon_path, color):
|
|
20
|
+
# Load the pixmap from the icon path
|
|
21
|
+
pixmap = QPixmap(icon_path)
|
|
22
|
+
|
|
23
|
+
# Create an empty QPixmap with the same size
|
|
24
|
+
colored_pixmap = QPixmap(pixmap.size())
|
|
25
|
+
colored_pixmap.fill(Qt.transparent)
|
|
26
|
+
|
|
27
|
+
# Paint the new color onto the QPixmap
|
|
28
|
+
painter = QPainter(colored_pixmap)
|
|
29
|
+
painter.drawPixmap(0, 0, pixmap)
|
|
30
|
+
painter.setCompositionMode(QPainter.CompositionMode_SourceIn)
|
|
31
|
+
painter.fillRect(colored_pixmap.rect(), QColor(color))
|
|
32
|
+
painter.end()
|
|
33
|
+
return colored_pixmap
|
spiceditor/__init__.py
ADDED
|
File without changes
|