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.
Files changed (41) hide show
  1. pynotebook/__init__.py +1 -0
  2. pynotebook/cerealizerformat.py +77 -0
  3. pynotebook/clients.py +47 -0
  4. pynotebook/controlled.py +37 -0
  5. pynotebook/graphics.py +414 -0
  6. pynotebook/inspector.py +488 -0
  7. pynotebook/jsonformat.py +259 -0
  8. pynotebook/nbstream.py +50 -0
  9. pynotebook/nbtexels.py +305 -0
  10. pynotebook/nbview.py +1654 -0
  11. pynotebook/pyclient.py +487 -0
  12. pynotebook/textformat.py +110 -0
  13. pynotebook/textmodel/__init__.py +2 -0
  14. pynotebook/textmodel/iterators.py +64 -0
  15. pynotebook/textmodel/modelbase.py +91 -0
  16. pynotebook/textmodel/properties.py +46 -0
  17. pynotebook/textmodel/styles.py +348 -0
  18. pynotebook/textmodel/texeltree.py +1126 -0
  19. pynotebook/textmodel/textmodel.py +934 -0
  20. pynotebook/textmodel/utils.py +580 -0
  21. pynotebook/textmodel/viewbase.py +84 -0
  22. pynotebook/textmodel/weights.py +195 -0
  23. pynotebook/wxgraphics.py +227 -0
  24. pynotebook/wxtextview/__init__.py +1 -0
  25. pynotebook/wxtextview/boxes.py +834 -0
  26. pynotebook/wxtextview/builder.py +154 -0
  27. pynotebook/wxtextview/cache.py +23 -0
  28. pynotebook/wxtextview/editing_modes.py +54 -0
  29. pynotebook/wxtextview/linewrap.py +232 -0
  30. pynotebook/wxtextview/markbuffer.py +516 -0
  31. pynotebook/wxtextview/rect.py +44 -0
  32. pynotebook/wxtextview/simplelayout.py +358 -0
  33. pynotebook/wxtextview/testdevice.py +49 -0
  34. pynotebook/wxtextview/testing.py +30 -0
  35. pynotebook/wxtextview/textview.py +1103 -0
  36. pynotebook/wxtextview/wxdevice.py +186 -0
  37. pynotebook/wxtextview/wxtextview.py +541 -0
  38. pynotebook-0.4.0.dist-info/METADATA +114 -0
  39. pynotebook-0.4.0.dist-info/RECORD +41 -0
  40. pynotebook-0.4.0.dist-info/WHEEL +4 -0
  41. 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
+
@@ -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
+