pynotebook 0.4.0__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.
- pynotebook/__init__.py +1 -0
- pynotebook/cerealizerformat.py +77 -0
- pynotebook/clients.py +47 -0
- pynotebook/controlled.py +37 -0
- pynotebook/graphics.py +414 -0
- pynotebook/inspector.py +488 -0
- pynotebook/jsonformat.py +259 -0
- pynotebook/nbstream.py +50 -0
- pynotebook/nbtexels.py +305 -0
- pynotebook/nbview.py +1654 -0
- pynotebook/pyclient.py +487 -0
- pynotebook/textformat.py +110 -0
- pynotebook/textmodel/__init__.py +2 -0
- pynotebook/textmodel/iterators.py +64 -0
- pynotebook/textmodel/modelbase.py +91 -0
- pynotebook/textmodel/properties.py +46 -0
- pynotebook/textmodel/styles.py +348 -0
- pynotebook/textmodel/texeltree.py +1126 -0
- pynotebook/textmodel/textmodel.py +934 -0
- pynotebook/textmodel/utils.py +580 -0
- pynotebook/textmodel/viewbase.py +84 -0
- pynotebook/textmodel/weights.py +195 -0
- pynotebook/wxgraphics.py +227 -0
- pynotebook/wxtextview/__init__.py +1 -0
- pynotebook/wxtextview/boxes.py +834 -0
- pynotebook/wxtextview/builder.py +154 -0
- pynotebook/wxtextview/cache.py +23 -0
- pynotebook/wxtextview/editing_modes.py +54 -0
- pynotebook/wxtextview/linewrap.py +232 -0
- pynotebook/wxtextview/markbuffer.py +516 -0
- pynotebook/wxtextview/rect.py +44 -0
- pynotebook/wxtextview/simplelayout.py +358 -0
- pynotebook/wxtextview/testdevice.py +49 -0
- pynotebook/wxtextview/testing.py +30 -0
- pynotebook/wxtextview/textview.py +1103 -0
- pynotebook/wxtextview/wxdevice.py +186 -0
- pynotebook/wxtextview/wxtextview.py +541 -0
- pynotebook-0.4.0.dist-info/METADATA +114 -0
- pynotebook-0.4.0.dist-info/RECORD +41 -0
- pynotebook-0.4.0.dist-info/WHEEL +4 -0
- pynotebook-0.4.0.dist-info/licenses/LICENSE +38 -0
pynotebook/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
# cerealizerformat.py
|
|
4
|
+
#
|
|
5
|
+
# The former file format, based on cerealizer (a secure form of
|
|
6
|
+
# pickling). Kept to load older files; jsonformat is the default. The
|
|
7
|
+
# classes are those registered in jsonformat.
|
|
8
|
+
|
|
9
|
+
from . import nbtexels, jsonformat
|
|
10
|
+
from .textmodel import textmodel
|
|
11
|
+
from .textmodel import texeltree, styles
|
|
12
|
+
from .nbtexels import mk_textmodel
|
|
13
|
+
|
|
14
|
+
import cerealizer
|
|
15
|
+
from io import BytesIO
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
magic = b'pynotebook0'
|
|
19
|
+
cmagic =b'cereal1'
|
|
20
|
+
|
|
21
|
+
def _register_classes():
|
|
22
|
+
# Classes are registered by their short name (cerealizer's default
|
|
23
|
+
# is module + name), because classes may move to other modules.
|
|
24
|
+
for classname, Class in jsonformat.get_registered():
|
|
25
|
+
try:
|
|
26
|
+
cerealizer.register(Class, None, classname)
|
|
27
|
+
except ValueError: # already registered
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def dumps(obj):
|
|
32
|
+
_register_classes()
|
|
33
|
+
s = BytesIO()
|
|
34
|
+
cerealizer.Dumper().dump(obj, s)
|
|
35
|
+
c = s.getvalue()
|
|
36
|
+
assert c.startswith(cmagic)
|
|
37
|
+
return magic+c[len(cmagic):]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _replace_styles(texel, table):
|
|
41
|
+
if texel.is_group or texel.is_container:
|
|
42
|
+
for child in texel.childs:
|
|
43
|
+
_replace_styles(child, table)
|
|
44
|
+
else:
|
|
45
|
+
if 'style' in texel.__dict__:
|
|
46
|
+
sid = id(texel.style)
|
|
47
|
+
if not sid in table:
|
|
48
|
+
table[sid] = styles.create_style(**texel.style)
|
|
49
|
+
texel.style = table[sid]
|
|
50
|
+
if isinstance(texel, texeltree.NewLine) and 'parstyle' in texel.__dict__:
|
|
51
|
+
sid = id(texel.parstyle)
|
|
52
|
+
if not sid in table:
|
|
53
|
+
table[sid] = texel.parstyle = styles.create_style(**texel.parstyle)
|
|
54
|
+
texel.parstyle = table[sid]
|
|
55
|
+
|
|
56
|
+
def loads(s):
|
|
57
|
+
assert s.startswith(magic)
|
|
58
|
+
_register_classes()
|
|
59
|
+
s = cmagic+s[len(magic):]
|
|
60
|
+
|
|
61
|
+
model = cerealizer.Dumper().undump(BytesIO(s))
|
|
62
|
+
_replace_styles(model.texel, {})
|
|
63
|
+
return model
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_00():
|
|
68
|
+
model1 = textmodel.TextModel()
|
|
69
|
+
tmp = textmodel.TextModel(u'for a in range(5):\n print a')
|
|
70
|
+
cell = nbtexels.ScriptingCell(tmp.texel, texeltree.NULL_TEXEL)
|
|
71
|
+
model1.insert(len(model1), mk_textmodel(cell))
|
|
72
|
+
|
|
73
|
+
s = dumps(model1)
|
|
74
|
+
model2 = loads(s)
|
|
75
|
+
assert str(model1.texel) == str(model2.texel)
|
|
76
|
+
assert model1.get_style(5) is model2.get_style(5)
|
|
77
|
+
assert model1.get_parstyle(5) is model2.get_parstyle(5)
|
pynotebook/clients.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Aborted(Exception):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Client:
|
|
11
|
+
name = 'unnamed' # each client should have a unique name
|
|
12
|
+
counter = 0
|
|
13
|
+
can_abort = False # not all clients can abort
|
|
14
|
+
|
|
15
|
+
def abort(self):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def execute(self, texel):
|
|
19
|
+
"""Returns a tuple of completions. """
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def complete(self, word, nmax=None):
|
|
23
|
+
"""Returns a tuple of completions. """
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
def colorize(self, texel, styles=None, bgcolor='white'):
|
|
27
|
+
"""Colorizes $texel$ to make it more readable. """
|
|
28
|
+
return texel
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ClientPool:
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self.clients = {}
|
|
35
|
+
|
|
36
|
+
def add(self, client, name):
|
|
37
|
+
self.clients[name] = client
|
|
38
|
+
|
|
39
|
+
def register(self, client):
|
|
40
|
+
self.add(client, client.name)
|
|
41
|
+
|
|
42
|
+
def get(self, name):
|
|
43
|
+
return self.clients[name]
|
|
44
|
+
|
|
45
|
+
def get_matching(self, texel):
|
|
46
|
+
return self.get(texel.client_name)
|
|
47
|
+
|
pynotebook/controlled.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# log method calls which lead to exceptions
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import traceback
|
|
7
|
+
import cPickle
|
|
8
|
+
from tempfile import mkdtemp
|
|
9
|
+
|
|
10
|
+
bk_stderr = sys.stderr
|
|
11
|
+
|
|
12
|
+
def store_exception(obj, name, args, kwds):
|
|
13
|
+
dirname = mkdtemp(prefix="pnb_tmp")
|
|
14
|
+
tb = traceback.format_exc()
|
|
15
|
+
f = open(os.path.join(dirname, 'exception.txt'), 'wb')
|
|
16
|
+
print >>f, tb
|
|
17
|
+
f = open(os.path.join(dirname, 'call.pickle'), 'wb')
|
|
18
|
+
l = (obj, name, args, kwds)
|
|
19
|
+
cPickle.dump(l, f)
|
|
20
|
+
print >>bk_stderr, tb
|
|
21
|
+
print >>bk_stderr, "Stored details to file %s" % repr(dirname)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def controlled(f):
|
|
26
|
+
def new_f(self, *args, **kwds):
|
|
27
|
+
try:
|
|
28
|
+
return f(self, *args, **kwds)
|
|
29
|
+
except:
|
|
30
|
+
store_exception(self.model, f.__name__, args, kwds)
|
|
31
|
+
raise
|
|
32
|
+
return new_f
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
pynotebook/graphics.py
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# graphics.py
|
|
5
|
+
#
|
|
6
|
+
#
|
|
7
|
+
# This module defines graphics operations which are used in connection
|
|
8
|
+
# with the 'Graphics'-Texel. Each instance of an operations definies a
|
|
9
|
+
# certain graphical object (e.g. a line or circle) or sets certain
|
|
10
|
+
# drawing properties (.e.g. line color or fill color).
|
|
11
|
+
#
|
|
12
|
+
# Lists of graphic operations represent a vector graphic. They can be
|
|
13
|
+
# saved, loaded or displayed.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# XXX missing:
|
|
17
|
+
# - SetLogicalFunction
|
|
18
|
+
# - fill styles
|
|
19
|
+
# - gradient fills
|
|
20
|
+
# - rounded rectangle
|
|
21
|
+
# - clipping
|
|
22
|
+
# - spline
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
import wx
|
|
26
|
+
from .nbtexels import Graphics
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _normalize_color(color):
|
|
31
|
+
if isinstance(color, wx.Colour):
|
|
32
|
+
return color.asTuple()+(color.Alpha(), )
|
|
33
|
+
elif isinstance(color, str):
|
|
34
|
+
c = wx.Colour(color)
|
|
35
|
+
return tuple(c)
|
|
36
|
+
elif type(color) in (list, tuple) and len(color) in (3, 4):
|
|
37
|
+
return tuple(color)
|
|
38
|
+
else:
|
|
39
|
+
raise ValueError('Not a valid color specification: %s' % repr(color))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Some versions of wx (accidentally) use degrees instead of
|
|
43
|
+
# radians. The following factor is used to correct this. It has the
|
|
44
|
+
# value None and is set later to either 1.0 or pi/180.0
|
|
45
|
+
#
|
|
46
|
+
# Calculation is could be done here in principle, using a
|
|
47
|
+
# MeasuringContext. However, this always gives memory violations.
|
|
48
|
+
_rotation_correction = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LineColor:
|
|
53
|
+
def __init__(self, color):
|
|
54
|
+
self.color = _normalize_color(color)
|
|
55
|
+
|
|
56
|
+
def draw(self, gc, state):
|
|
57
|
+
pen = state["pen"]
|
|
58
|
+
pen.SetColour(self.color)
|
|
59
|
+
gc.SetPen(pen)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class LineWidth:
|
|
63
|
+
def __init__(self, width):
|
|
64
|
+
self.width = width
|
|
65
|
+
|
|
66
|
+
def draw(self, gc, state):
|
|
67
|
+
pen = state["pen"]
|
|
68
|
+
pen.SetWidth(self.width)
|
|
69
|
+
gc.SetPen(pen)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LineDashes:
|
|
73
|
+
# Dashes is a list of length values. Example: [5, 5] will draw a
|
|
74
|
+
# line of length 5, a space of length 5, a line of length 5, ...
|
|
75
|
+
def __init__(self, dashes):
|
|
76
|
+
self.dashes = dashes
|
|
77
|
+
|
|
78
|
+
def draw(self, gc, state):
|
|
79
|
+
pen = state["pen"]
|
|
80
|
+
pen.SetDashes(self.dashes)
|
|
81
|
+
gc.SetPen(pen)
|
|
82
|
+
|
|
83
|
+
line_joins = dict(
|
|
84
|
+
bevel=wx.JOIN_BEVEL, miter=wx.JOIN_MITER, round=wx.JOIN_ROUND)
|
|
85
|
+
|
|
86
|
+
class LineJoin:
|
|
87
|
+
def __init__(self, join_style):
|
|
88
|
+
if not join_style in line_joins:
|
|
89
|
+
raise ValueError("join_style mus be 'bevel, 'miter or 'round'")
|
|
90
|
+
self.join_style = join_style
|
|
91
|
+
|
|
92
|
+
def draw(self, gc, state):
|
|
93
|
+
pen = state["pen"]
|
|
94
|
+
pen.SetJoin(line_joins[self.join_style])
|
|
95
|
+
gc.SetPen(pen)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
line_caps = dict(
|
|
99
|
+
round=wx.CAP_ROUND, projecting=wx.CAP_PROJECTING, butt=wx.CAP_BUTT)
|
|
100
|
+
|
|
101
|
+
class LineCap:
|
|
102
|
+
def __init__(self, cap_style):
|
|
103
|
+
if not cap_style in line_caps:
|
|
104
|
+
raise ValueError("cap_style mus be 'round', 'projecting' or 'butt'")
|
|
105
|
+
self.cap_style = cap_style
|
|
106
|
+
|
|
107
|
+
def draw(self, gc, state):
|
|
108
|
+
pen = state["pen"]
|
|
109
|
+
pen.SetCap(line_caps[self.cap_style])
|
|
110
|
+
gc.SetPen(pen)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class FillColor:
|
|
114
|
+
def __init__(self, color):
|
|
115
|
+
self.color = _normalize_color(color)
|
|
116
|
+
|
|
117
|
+
def draw(self, gc, state):
|
|
118
|
+
brush = state["brush"]
|
|
119
|
+
brush.SetColour(self.color)
|
|
120
|
+
brush.SetStyle(wx.SOLID)
|
|
121
|
+
gc.SetBrush(brush)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Line:
|
|
125
|
+
def __init__(self, *points):
|
|
126
|
+
self.points = tuple(points)
|
|
127
|
+
|
|
128
|
+
def draw(self, gc, state):
|
|
129
|
+
points = self.points
|
|
130
|
+
if len(points)<2:
|
|
131
|
+
return
|
|
132
|
+
p = gc.CreatePath()
|
|
133
|
+
for point in points:
|
|
134
|
+
p.AddLineToPoint(point)
|
|
135
|
+
p.Transform(state['matrix'])
|
|
136
|
+
gc.DrawPath(p)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# class Spline:
|
|
140
|
+
# def __init__(self, *points):
|
|
141
|
+
# self.points = tuple(points)
|
|
142
|
+
#
|
|
143
|
+
# def draw(self, gc, state):
|
|
144
|
+
# gc.DrawSpline(self.points)
|
|
145
|
+
# # XXX TODO
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class Polygon:
|
|
149
|
+
def __init__(self, *points):
|
|
150
|
+
self.points = tuple(points)
|
|
151
|
+
# XXX should we be able to set the fill rule?
|
|
152
|
+
|
|
153
|
+
def draw(self, gc, state):
|
|
154
|
+
points = self.points
|
|
155
|
+
if len(points)<2:
|
|
156
|
+
return
|
|
157
|
+
p = gc.CreatePath()
|
|
158
|
+
for point in points:
|
|
159
|
+
p.AddLineToPoint(point)
|
|
160
|
+
p.CloseSubpath()
|
|
161
|
+
p.Transform(state['matrix'])
|
|
162
|
+
gc.DrawPath(p)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class Path:
|
|
167
|
+
"""A path represents shapes like lines, curves, arcs and more.
|
|
168
|
+
|
|
169
|
+
*data* ist a list of drawing commands which are each specified as
|
|
170
|
+
tuples with coordinate values. Currently there are four commands:
|
|
171
|
+
- M (move to point)
|
|
172
|
+
- L (line to point)
|
|
173
|
+
- Q (Bezier curve to point)
|
|
174
|
+
- Z (close path)
|
|
175
|
+
"""
|
|
176
|
+
def __init__(self, *data):
|
|
177
|
+
self.data = data
|
|
178
|
+
|
|
179
|
+
def draw(self, gc, state):
|
|
180
|
+
wxpath = gc.CreatePath()
|
|
181
|
+
x = y = 0
|
|
182
|
+
for item in self.data:
|
|
183
|
+
cmd = item[0]
|
|
184
|
+
points = item[1:]
|
|
185
|
+
if cmd == 'M':
|
|
186
|
+
x, y = points
|
|
187
|
+
wxpath.MoveToPoint(x, y)
|
|
188
|
+
elif cmd == 'L':
|
|
189
|
+
x, y = points
|
|
190
|
+
wxpath.AddLineToPoint(x, y)
|
|
191
|
+
elif cmd == 'Q':
|
|
192
|
+
cx, cy, x, y = points
|
|
193
|
+
wxpath.AddQuadCurveToPoint(cx, cy, x, y)
|
|
194
|
+
elif cmd == 'Z':
|
|
195
|
+
wxpath.CloseSubpath()
|
|
196
|
+
else:
|
|
197
|
+
raise Exception("Unknown path command: '%s'" % cmd)
|
|
198
|
+
wxpath.Transform(state['matrix'])
|
|
199
|
+
gc.DrawPath(wxpath)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class Circle:
|
|
205
|
+
def __init__(self, xxx_todo_changeme1, r):
|
|
206
|
+
(x, y) = xxx_todo_changeme1
|
|
207
|
+
self.x = x
|
|
208
|
+
self.y = y
|
|
209
|
+
self.r = r
|
|
210
|
+
|
|
211
|
+
def draw(self, gc, state):
|
|
212
|
+
p = gc.CreatePath()
|
|
213
|
+
p.AddCircle(self.x, self.y, self.r)
|
|
214
|
+
p.Transform(state['matrix'])
|
|
215
|
+
gc.DrawPath(p)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class Ellipse:
|
|
219
|
+
def __init__(self, xxx_todo_changeme2, r1, r2):
|
|
220
|
+
(x, y) = xxx_todo_changeme2
|
|
221
|
+
self.x = x
|
|
222
|
+
self.y = y
|
|
223
|
+
self.r1 = r1
|
|
224
|
+
self.r2 = r2
|
|
225
|
+
|
|
226
|
+
def draw(self, gc, state):
|
|
227
|
+
p = gc.CreatePath()
|
|
228
|
+
p.AddEllipse(self.x, self.y, self.r1, self.r2)
|
|
229
|
+
p.Transform(state['matrix'])
|
|
230
|
+
gc.DrawPath(p)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class Arc:
|
|
235
|
+
def __init__(self, xxx_todo_changeme3, r, start, end):
|
|
236
|
+
(x, y) = xxx_todo_changeme3
|
|
237
|
+
self.x = x
|
|
238
|
+
self.y = y
|
|
239
|
+
self.r = r
|
|
240
|
+
self.start = start
|
|
241
|
+
self.end = end
|
|
242
|
+
# XXX do we need clockwise?
|
|
243
|
+
|
|
244
|
+
def draw(self, gc, state):
|
|
245
|
+
p = gc.CreatePath()
|
|
246
|
+
p.AddArc(self.x, self.y, self.r, self.start, self.end, True)
|
|
247
|
+
p.Transform(state['matrix'])
|
|
248
|
+
gc.DrawPath(p)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class Rectangle:
|
|
253
|
+
def __init__(self, xxx_todo_changeme4, xxx_todo_changeme5):
|
|
254
|
+
(x1, y1) = xxx_todo_changeme4
|
|
255
|
+
(x2, y2) = xxx_todo_changeme5
|
|
256
|
+
self.x1 = x1
|
|
257
|
+
self.y1 = y1
|
|
258
|
+
self.x2 = x2
|
|
259
|
+
self.y2 = y2
|
|
260
|
+
|
|
261
|
+
def draw(self, gc, state):
|
|
262
|
+
x = min(self.x1, self.x2)
|
|
263
|
+
w = max(self.x1, self.x2)-x
|
|
264
|
+
y = min(self.y1, self.y2)
|
|
265
|
+
h = max(self.y1, self.y2)-y
|
|
266
|
+
p = gc.CreatePath()
|
|
267
|
+
p.AddRectangle(x, y, w, h)
|
|
268
|
+
p.Transform(state['matrix'])
|
|
269
|
+
gc.DrawPath(p)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class Font:
|
|
274
|
+
def __init__(self, size, family, style, weight,
|
|
275
|
+
underline=False, face="",
|
|
276
|
+
encoding=wx.FONTENCODING_DEFAULT):
|
|
277
|
+
self.size = size
|
|
278
|
+
self.family = family
|
|
279
|
+
self.style = style
|
|
280
|
+
self.weight = weight
|
|
281
|
+
self.underline = underline
|
|
282
|
+
self.face = face
|
|
283
|
+
self.encoding = encoding
|
|
284
|
+
|
|
285
|
+
def draw(self, gc, state):
|
|
286
|
+
font = wx.Font(self.size, self.family, self.style,
|
|
287
|
+
self.weight, self.underline,
|
|
288
|
+
self.face, self.encoding)
|
|
289
|
+
gc.SetFont(font)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class GraphicsText:
|
|
293
|
+
def __init__(self, text, point, align=(0, 0)):
|
|
294
|
+
self.text = text
|
|
295
|
+
self.point = point
|
|
296
|
+
self.align = align
|
|
297
|
+
|
|
298
|
+
def draw(self, gc, state):
|
|
299
|
+
x, y = self.point
|
|
300
|
+
w, h = gc.GetTextExtent(self.text)
|
|
301
|
+
dx = 0.5*w*(self.align[0]-1)
|
|
302
|
+
dy = 0.5*h*(self.align[1]-1)
|
|
303
|
+
gc.SetTransform(state['matrix'])
|
|
304
|
+
gc.DrawText(self.text, x+dx, y+dy)
|
|
305
|
+
gc.SetTransform(state['trafo'])
|
|
306
|
+
|
|
307
|
+
Text = GraphicsText # alias
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class Translate:
|
|
311
|
+
def __init__(self, offset):
|
|
312
|
+
self.offset = offset
|
|
313
|
+
|
|
314
|
+
def draw(self, gc, state):
|
|
315
|
+
dx, dy = self.offset
|
|
316
|
+
state['matrix'].Translate(dx, dy)
|
|
317
|
+
#gc.Translate(dx, dy)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class Rotate:
|
|
321
|
+
def __init__(self, angle):
|
|
322
|
+
self.angle = angle
|
|
323
|
+
|
|
324
|
+
def draw(self, gc, state):
|
|
325
|
+
global _rotation_correction
|
|
326
|
+
if _rotation_correction is None:
|
|
327
|
+
# Determine rotation correction factor.
|
|
328
|
+
m = gc.CreateMatrix()
|
|
329
|
+
m.Rotate(360)
|
|
330
|
+
if m.Get()[0]<-0.28:
|
|
331
|
+
# radians
|
|
332
|
+
_rotation_correction = 1
|
|
333
|
+
else:
|
|
334
|
+
# degrees
|
|
335
|
+
import math
|
|
336
|
+
_rotation_correction = math.pi/180
|
|
337
|
+
else:
|
|
338
|
+
_rotation_correction = 1
|
|
339
|
+
|
|
340
|
+
state['matrix'].Rotate(self.angle*_rotation_correction)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class Scale:
|
|
344
|
+
def __init__(self, fx, fy=None):
|
|
345
|
+
if fy is None:
|
|
346
|
+
fy = fx
|
|
347
|
+
self.fx = fx
|
|
348
|
+
self.fy = fy
|
|
349
|
+
|
|
350
|
+
def draw(self, gc, state):
|
|
351
|
+
state['matrix'].Scale(self.fx, self.fy)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _register_graphics_classes():
|
|
356
|
+
from .jsonformat import register
|
|
357
|
+
for value in list(globals().values()):
|
|
358
|
+
if isinstance(value, type) and value.__module__ == __name__:
|
|
359
|
+
register(value)
|
|
360
|
+
|
|
361
|
+
_register_graphics_classes()
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def register_classes():
|
|
365
|
+
pass # registration happens automatically on import
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def init_testing(redirect=True):
|
|
369
|
+
import wx
|
|
370
|
+
from .nbview import TextModel, NBView
|
|
371
|
+
app = wx.App(redirect=redirect)
|
|
372
|
+
model = TextModel('')
|
|
373
|
+
|
|
374
|
+
frame = wx.Frame(None)
|
|
375
|
+
win = wx.Panel(frame, -1)
|
|
376
|
+
view = NBView(win, -1, style=wx.SUNKEN_BORDER)
|
|
377
|
+
view.model = model
|
|
378
|
+
box = wx.BoxSizer(wx.VERTICAL)
|
|
379
|
+
box.Add(view, 1, wx.ALL|wx.GROW, 1)
|
|
380
|
+
win.SetSizer(box)
|
|
381
|
+
win.SetAutoLayout(True)
|
|
382
|
+
|
|
383
|
+
frame.Show()
|
|
384
|
+
return locals()
|
|
385
|
+
|
|
386
|
+
def test_00():
|
|
387
|
+
"Graphics"
|
|
388
|
+
from .nbview import mk_textmodel
|
|
389
|
+
ns = init_testing(False)
|
|
390
|
+
model = ns['model']
|
|
391
|
+
model.insert(len(model), mk_textmodel(Graphics([Circle((0, 0), 5)])))
|
|
392
|
+
segments = [
|
|
393
|
+
(0, 0), (0, 10), (10, 10), (10, 0)
|
|
394
|
+
]
|
|
395
|
+
l = Line(*segments)
|
|
396
|
+
model.insert(len(model), mk_textmodel(Graphics([l])))
|
|
397
|
+
return ns
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def demo_00():
|
|
402
|
+
import wx
|
|
403
|
+
from .wxtextview import testing
|
|
404
|
+
ns = test_00()
|
|
405
|
+
testing.pyshell(ns)
|
|
406
|
+
py = ns['view']._clients._clients['direct python']
|
|
407
|
+
py.namespace.update(
|
|
408
|
+
dict(Graphics=Graphics, Dot=Dot, Line=Line))
|
|
409
|
+
ns['app'].MainLoop()
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
|