1 """GNUmed onscreen Snellen Chart emulator.
2
3 FIXME: store screen size
4 """
5
6
7
8 __version__ = "$Revision: 1.6 $"
9 __author__ = "Ian Haywood, Karsten Hilbert <Karsten.Hilbert@gmx.net>"
10 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
11
12 import math, random, sys, logging
13
14
15 import wx
16
17
18 if __name__ == '__main__':
19 sys.path.insert(0, '../../')
20 from Gnumed.pycommon import gmI18N
21
22 ID_SNELLENMENU = wx.NewId()
23 ID_SNELLENBUTTON = wx.NewId()
24
25
27
29 """Converts a pair of co-ordinates from block co-ords to real.
30
31 X, Y -- define top-left corner of current character
32 """
33 if self.mirror:
34 X = 5-X
35 return wx.Point(
36 int ((X * self.blockX) + self.startX),
37 int ((Y * self.blockY) + self.startY)
38 )
39
41 """
42 Draws the letter O
43 """
44 self._draw_arc (2.5, 2.5, 2.5, 0, 360)
45
47 self.O()
48 self._draw_line (2.6, 3, 4, 5)
49
51 if self.mirror:
52 self._draw_arc (2.5, 2.5, 2.5, 140, -140)
53 else:
54 self._draw_arc (2.5, 2.5, 2.5, 40, 320)
55
57 if self.mirror:
58 self._draw_arc (2.5, 2.5, 2.5, 140, -150)
59 else:
60 self._draw_arc (2.5, 2.5, 2.5, 40, 330)
61 self._draw_rect (2.5, 2.7, 5, 3.7)
62 self._draw_rect (4, 2.7, 5, 5)
63
65 self._draw_line (0, 0, 1, 5)
66 self._draw_line (2, 0, 1, 5)
67 self._draw_line (2, 0, 3, 5)
68 self._draw_line (4, 0, 3, 5)
69
71 self._draw_line (0, 0, 2, 5)
72 self._draw_line (4, 0, 2, 5)
73
75 self._draw_rect (0, 0, 5, 1)
76 self._draw_rect (2, 1, 3, 5)
77
79 self._draw_rect (2, 0, 3, 5)
80
82 self._draw_line (2, 0, 0, 5)
83 self._draw_line (2, 0, 4, 5)
84 self._draw_rect (1.4, 2.5, 3.6, 3.5)
85
87 self._draw_rect (0, 0, 1, 5)
88 self._draw_rect (1, 0, 5, 1)
89 self._draw_rect (1, 2, 5, 3)
90
92 self._draw_rect (0, 0, 1, 5)
93 self._draw_rect (0, 0, 5, 1)
94 self._draw_rect (0, 2, 5, 3)
95 self._draw_rect (0, 4, 5, 5)
96
98 self._draw_rect (4, 0, 5, 5)
99 self._draw_rect (0, 0, 5, 1)
100 self._draw_rect (0, 2, 5, 3)
101 self._draw_rect (0, 4, 5, 5)
102
104 self._draw_rect (0, 4, 5, 5)
105 self._draw_rect (0, 0, 1, 5)
106 self._draw_rect (2, 0, 3, 5)
107 self._draw_rect (4, 0, 5, 5)
108
110 self._draw_rect (0, 0, 5, 1)
111 self._draw_rect (0, 0, 1, 5)
112 self._draw_rect (2, 0, 3, 5)
113 self._draw_rect (4, 0, 5, 5)
114
116 self._draw_rect (0, 0, 1, 5)
117 self._draw_rect (4, 0, 5, 5)
118 self._draw_rect (1, 2, 4, 3)
119
121 self._draw_rect (0, 0, 1, 5)
122 self._draw_line (3.5, 0, 0.5, 2.5, width = 1.5)
123 self._draw_line (0.5, 2.5, 3.5, 5, width = 1.5)
124
126 self._draw_rect (0, 0, 1, 5)
127 self._draw_rect (1, 4, 5, 5)
128
130 self._draw_rect (0, 0, 5, 1)
131 self._draw_rect (0, 4, 5, 5)
132 self._draw_line (3.5, 1, 0, 4, width = 1.5)
133
135 self._draw_line (4, 0, 0, 5)
136 self._draw_line (0, 0, 4, 5)
137
139 """Sidebars common to N and M
140 """
141 self._draw_rect (0, 0, 1, 5)
142 self._draw_rect (4, 0, 5, 5)
143
145 self.NM ()
146 self._draw_line (0, 0, 4, 5)
147
149 self.NM ()
150 self._draw_line (4, 0, 0, 5)
151
153 self.NM ()
154 self._draw_line (0, 0, 2, 5)
155 self._draw_line (4, 0, 2, 5)
156
158 self._draw_rect (0, 0, 5, 1)
159 self._draw_rect (0, 0, 1, 5)
160
162 self._draw_line (2, 0, 0, 5)
163 self._draw_line (2, 0, 4, 5)
164 self._draw_rect (0.5, 4, 4.5, 5)
165
167 self._draw_rect (0, 0, 5, 1)
168 self._draw_rect (0, 0, 1, 5)
169 self._draw_rect (4, 0, 5, 5)
170
172 self._draw_rect (2, 0, 3, 5)
173 self._draw_rect (0, 2, 5, 3)
174
176 self._draw_rect (0, 0, 5, 1)
177 self._draw_rect (0, 4, 5, 5)
178 self._draw_rect (0, 1, 1, 4)
179 self._draw_rect (4, 1, 5, 4)
180
182 """
183 Star of 5 points
184 """
185 n = 5
186 list = []
187 for i in range (0, n):
188 theta = (i+0.00001)/n*2*math.pi
189 x = 2.5 + 2.5*math.sin (theta)
190 y = 2.5 - 2.5*math.cos (theta)
191 list.append (self.convert (x, y))
192 theta = (i+0.5)/n*2*math.pi
193 x = 2.5 + math.sin (theta)
194 y = 2.5 - math.cos (theta)
195 list.append (self.convert (x, y))
196 self.dc.DrawPolygon (list, fill_style = wx.WINDING_RULE)
197
198 latin = [A, C,
199 C, E, F, G,
200 H, I, K, L, M,
201 N, O, Q, T, V,
202 W, X, Z]
203
204 fourE = [E, UpE, DownE, BackE]
205
206 greek = [A, gamma, delta, E,
207 Z, H, I, K, M,
208 N, O, pi, T, X]
209
210 cyrillic = [A, delta, E, BackN,
211 K, M, H, O, pi,
212 T, C, X]
213
214 symbol = [O, cross, star, box]
215
216 alphabets = {
217 _("Latin"): latin,
218 _("Greek"): greek,
219 _("Cyrillic"): cyrillic,
220 _("Four Es"): fourE,
221 _("Symbol"): symbol
222 }
223
224 - def __init__(self, width, height, alpha = symbol, mirr = 0, parent = None):
225 """
226 Initialise. width and height define the physical size of the
227 CRT in cm.
228 """
229 wx.Frame.__init__ (self, parent, -1, _("Snellen Chart"))
230
231
232
233
234 self.screen_width_cm = width
235 self.screen_height_cm = height
236
237 self.screen_width_pixel = 0
238 self.screen_height_pixel = 0
239
240 self.standard_patient_chart_distances = [3, 5, 6, 7.5, 9, 12, 15, 18, 24, 30, 48, 60]
241 self.mirror = mirr
242 self.alphabet = alpha
243
244 wx.EVT_CLOSE (self, self.OnClose)
245 wx.EVT_KEY_DOWN (self, self.OnKeyUp)
246 wx.EVT_LEFT_UP (self, self.OnLeftDown)
247 wx.EVT_RIGHT_UP (self, self.OnRightDown)
248 wx.EVT_LEFT_DCLICK (self, self.OnDClick)
249 wx.EVT_PAINT (self, self.OnPaint)
250
251
252 self.ShowFullScreen(1)
253
254
255
256
257
258
260 self.dc = wx.PaintDC(self)
261 if self.screen_width_pixel == 0:
262 self.screen_width_pixel, self.screen_height_pixel = self.GetClientSize()
263 self.set_distance(2)
264
265
266
267 self.dc.SetFont(wx.Font (36, wx.ROMAN, wx.NORMAL, wx.NORMAL))
268 self.dc.SetBrush(wx.BLACK_BRUSH)
269 self.dc.SetBackground(wx.WHITE_BRUSH)
270 self.dc.SetPen(wx.TRANSPARENT_PEN)
271
272
273
274 self._swap_fg_bg_col()
275 self.dc.DrawRectangle(0, 0, self.screen_width_pixel, self.screen_height_pixel)
276 self._swap_fg_bg_col ()
277
278 self.dc.DrawText (str(self.standard_patient_chart_distances[self.distance]), 20, 20)
279 self.startX = self.spacing
280 for i in self.choices:
281 i (self)
282 self.startX += self.blockX*5
283 self.startX += self.spacing
284
285 self.dc = None
286
288 if key.GetKeyCode() == wx.WXK_UP and self.distance < len (self.standard_patient_chart_distances)-1:
289 self.set_distance (self.distance+1)
290 if key.GetKeyCode() == wx.WXK_DOWN and self.distance > 0:
291 self.set_distance (self.distance-1)
292 if key.GetKeyCode() == wx.WXK_ESCAPE:
293 self.Destroy ()
294
296 if self.distance > 0:
297 self.set_distance (self.distance-1)
298 self.Refresh ()
299
301 if self.distance < len(self.standard_patient_chart_distances)-1:
302 self.set_distance (self.distance+1)
303 self.Refresh()
304
307
310
312 import sys
313 sys.exit (0)
314
315
316
317
319 """Swap fore- and background pens."""
320 background = self.dc.GetBackground()
321 foreground = self.dc.GetBrush()
322 self.dc.SetBrush(background)
323 self.dc.SetBackground(foreground)
324
345
346 - def _draw_arc (self, x, y, arm, start, end):
347 """
348 Draws an arc-stroke, 1 unit wide, subtending (x, y), the outer
349 distance is arm, between start and end angle
350 """
351 topx, topy = self.convert (x-arm, y-arm)
352 botx, boty = self.convert (x+arm, y+arm)
353 width = botx - topx
354 if width < 0:
355 width = -width
356 t = botx
357 botx = topx
358 topx = t
359 height = boty - topy
360 self.dc.DrawEllipticArc(topx, topy, width, height, start, end)
361
362 arm -= 1
363 self._swap_fg_bg_col()
364 topx, topy = self.convert (x-arm, y-arm)
365 botx, boty = self.convert (x+arm, y+arm)
366 width = botx - topx
367 if width < 0:
368 width = -width
369 t = botx
370 botx = topx
371 topx = t
372 height = boty- topy
373 self.dc.DrawEllipticArc(topx, topy, width, height, start, end)
374 self._swap_fg_bg_col()
375
377 """Draws straight descending letter-stroke.
378
379 (x1, y1) is top-left,
380 (x2, y2) is bottom left point
381 """
382 coords = [
383 self.convert (x1, y1),
384 self.convert (x1+width, y1),
385 self.convert (x2+width, y2),
386 self.convert (x2, y2)
387 ]
388 self.dc.DrawPolygon(coords)
389
390
392 """
393 Sets standard viewing distance, against which patient is
394 compared. n is an index to the list self.standard_patient_chart_distances
395 """
396 self.distance = n
397
398
399
400
401 one_minute = (math.pi / 180) / 60
402 blocksize = (self.standard_patient_chart_distances[n] * 100) * math.atan(one_minute)
403
404 self.blockX = int (blocksize / self.screen_width_cm * self.screen_width_pixel)
405 self.blockY = int (blocksize / self.screen_height_cm * self.screen_height_pixel)
406
407 chars = int (self.screen_width_pixel / (self.blockX*5)) - 1
408 if chars < 1:
409 chars = 1
410 if chars > 7:
411 chars = 7
412 if chars < len (self.alphabet):
413 self.choices = []
414 while len (self.choices) < chars:
415 c = random.choice (self.alphabet)
416 if not c in self.choices:
417 self.choices.append (c)
418 else:
419 self.choices = [ random.choice(self.alphabet) for i in range(1, chars) ]
420 self.spacing = int ((self.screen_width_pixel -
421 (chars*self.blockX*5))/(chars+1))
422 if self.spacing < 0:
423 self.spacing = 0
424 self.startY = int ((self.screen_height_pixel-(self.blockY*5))/2)
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
451 """
452 Dialogue class to get Snellen chart settings.
453 """
455 wx.Dialog.__init__(
456 self,
457 None,
458 -1,
459 _("Snellen Chart Setup"),
460 wx.DefaultPosition,
461 wx.Size(350, 200)
462 )
463
464
465
466
467 vbox = wx.BoxSizer (wx.VERTICAL)
468 hbox1 = wx.BoxSizer (wx.HORIZONTAL)
469 hbox1.Add (wx.StaticText(self, -1, _("Screen Height (cm): ")), 0, wx.ALL, 15)
470 self.height_ctrl = wx.SpinCtrl (self, -1, value = "25", min = 10, max = 100)
471 hbox1.Add (self.height_ctrl, 1, wx.TOP, 15)
472 vbox.Add (hbox1, 1, wx.EXPAND)
473 hbox2 = wx.BoxSizer (wx.HORIZONTAL)
474 hbox2.Add (wx.StaticText(self, -1, _("Screen Width (cm): ")), 0, wx.ALL, 15)
475 self.width_ctrl = wx.SpinCtrl (self, -1, value = "30", min = 10, max = 100)
476 hbox2.Add (self.width_ctrl, 1, wx.TOP, 15)
477 vbox.Add (hbox2, 1, wx.EXPAND)
478 hbox3 = wx.BoxSizer (wx.HORIZONTAL)
479 hbox3.Add (wx.StaticText(self, -1, _("Alphabet: ")), 0, wx.ALL, 15)
480 self.alpha_ctrl = wx.Choice (self, -1, choices = cSnellenChart.alphabets.keys ())
481 hbox3.Add (self.alpha_ctrl, 1, wx.TOP, 15)
482 vbox.Add (hbox3, 1, wx.EXPAND)
483 self.mirror_ctrl = wx.CheckBox (self, -1, label = _("Mirror"))
484 vbox.Add (self.mirror_ctrl, 0, wx.ALL, 15)
485 vbox.Add (wx.StaticText (self, -1,
486 _("""Control Snellen chart using mouse:
487 left-click increases text
488 right-click decreases text
489 double-click ends""")), 0, wx.ALL, 15)
490 hbox5 = wx.BoxSizer (wx.HORIZONTAL)
491 ok = wx.Button(self, wx.ID_OK, _(" OK "), size=wx.DefaultSize)
492 cancel = wx.Button (self, wx.ID_CANCEL, _(" Cancel "),
493 size=wx.DefaultSize)
494 hbox5.Add (ok, 1, wx.TOP, 15)
495 hbox5.Add (cancel, 1, wx.TOP, 15)
496 vbox.Add (hbox5, 1, wx.EXPAND)
497 self.SetSizer (vbox)
498 self.SetAutoLayout (1)
499 vbox.Fit (self)
500
501 wx.EVT_BUTTON (ok, wx.ID_OK, self.OnOK)
502 wx.EVT_BUTTON (cancel, wx.ID_CANCEL, self.OnCancel)
503 wx.EVT_CLOSE (self, self.OnClose )
504
505
506
508 self.EndModal (wx.ID_CANCEL)
509
511 self.EndModal (wx.ID_CANCEL)
512
513 - def OnOK (self, event):
514
515 selected_alpha_string = self.alpha_ctrl.GetStringSelection()
516 if selected_alpha_string is None or len (selected_alpha_string) < 2:
517 alpha = cSnellenChart.latin
518 else:
519 alpha = cSnellenChart.alphabets[selected_alpha_string]
520 height = self.height_ctrl.GetValue ()
521 width = self.width_ctrl.GetValue ()
522 mirr = self.mirror_ctrl.GetValue()
523 self.vals = (width, height, alpha, mirr)
524 self.EndModal(wx.ID_OK)
525
526
527
528
529
530
531 if __name__ == '__main__':
532
533 gmI18N.activate_locale()
534 gmI18N.install_domain('gnumed')
535
538 cfg = cSnellenCfgDlg()
539 if cfg.ShowModal () == 0:
540 frame = cSnellenChart (
541 width = cfg.vals[0],
542 height = cfg.vals[1],
543 alpha = cfg.vals[2],
544 mirr = cfg.vals[3],
545 parent = None
546 )
547 frame.CentreOnScreen(wx.BOTH)
548 self.SetTopWindow(frame)
549 frame.Destroy = frame.DestroyWhenApp
550 frame.Show(True)
551 return 1
552
554 app = TestApp ()
555 app.MainLoop ()
556
557 main()
558
559