chiark / gitweb /
Do not crash on malformed ini files.
[cura.git] / Cura / gui / util / openglGui.py
1 from __future__ import absolute_import
2 from __future__ import division
3
4 import wx
5 from wx import glcanvas
6 import OpenGL
7 OpenGL.ERROR_CHECKING = False
8 from OpenGL.GL import *
9
10 from Cura.gui.util import opengl
11
12 class glGuiControl(object):
13         def __init__(self, parent, pos):
14                 self._parent = parent
15                 self._base = parent._base
16                 self._pos = pos
17                 self._size = (0,0, 1, 1)
18                 self._parent.add(self)
19
20         def setSize(self, x, y, w, h):
21                 self._size = (x, y, w, h)
22
23         def getSize(self):
24                 return self._size
25
26         def getMinSize(self):
27                 return 1, 1
28
29         def updateLayout(self):
30                 pass
31
32         def focusNext(self):
33                 for n in xrange(self._parent._glGuiControlList.index(self) + 1, len(self._parent._glGuiControlList)):
34                         if self._parent._glGuiControlList[n].setFocus():
35                                 return
36                 for n in xrange(0, self._parent._glGuiControlList.index(self)):
37                         if self._parent._glGuiControlList[n].setFocus():
38                                 return
39
40         def focusPrevious(self):
41                 for n in xrange(self._parent._glGuiControlList.index(self) -1, -1, -1):
42                         if self._parent._glGuiControlList[n].setFocus():
43                                 return
44                 for n in xrange(len(self._parent._glGuiControlList) - 1, self._parent._glGuiControlList.index(self), -1):
45                         if self._parent._glGuiControlList[n].setFocus():
46                                 return
47
48         def setFocus(self):
49                 return False
50
51 class glGuiContainer(glGuiControl):
52         def __init__(self, parent, pos):
53                 self._glGuiControlList = []
54                 glGuiLayoutButtons(self)
55                 super(glGuiContainer, self).__init__(parent, pos)
56
57         def add(self, ctrl):
58                 self._glGuiControlList.append(ctrl)
59                 self.updateLayout()
60
61         def OnMouseDown(self, x, y):
62                 for ctrl in self._glGuiControlList:
63                         if ctrl.OnMouseDown(x, y):
64                                 return True
65                 return False
66
67         def OnMouseMotion(self, x, y):
68                 handled = False
69                 for ctrl in self._glGuiControlList:
70                         if ctrl.OnMouseMotion(x, y):
71                                 handled = True
72                 return handled
73
74         def draw(self):
75                 for ctrl in self._glGuiControlList:
76                         ctrl.draw()
77
78         def updateLayout(self):
79                 self._layout.update()
80                 for ctrl in self._glGuiControlList:
81                         ctrl.updateLayout()
82
83 class glGuiPanel(glcanvas.GLCanvas):
84         def __init__(self, parent):
85                 attribList = (glcanvas.WX_GL_RGBA, glcanvas.WX_GL_DOUBLEBUFFER, glcanvas.WX_GL_DEPTH_SIZE, 24, glcanvas.WX_GL_STENCIL_SIZE, 8)
86                 glcanvas.GLCanvas.__init__(self, parent, style=wx.WANTS_CHARS, attribList = attribList)
87                 self._base = self
88                 self._focus = None
89                 self._container = None
90                 self._container = glGuiContainer(self, (0,0))
91
92                 self._context = glcanvas.GLContext(self)
93                 self._glButtonsTexture = None
94                 self._buttonSize = 64
95
96                 wx.EVT_PAINT(self, self._OnGuiPaint)
97                 wx.EVT_SIZE(self, self._OnSize)
98                 wx.EVT_ERASE_BACKGROUND(self, self._OnEraseBackground)
99                 wx.EVT_LEFT_DOWN(self, self._OnGuiMouseLeftDown)
100                 wx.EVT_MOTION(self, self._OnGuiMouseMotion)
101                 wx.EVT_CHAR(self, self.OnKeyChar)
102                 wx.EVT_KILL_FOCUS(self, self.OnFocusLost)
103
104         def OnKeyChar(self, e):
105                 if self._focus is not None:
106                         self._focus.OnKeyChar(e.GetKeyCode())
107                         self.Refresh()
108
109         def OnFocusLost(self, e):
110                 self._focus = None
111                 self.Refresh()
112
113         def _OnGuiMouseLeftDown(self,e):
114                 self.SetFocus()
115                 if self._container.OnMouseDown(e.GetX(), e.GetY()):
116                         self.Refresh()
117                         return
118                 self.OnMouseLeftDown(e)
119
120         def _OnGuiMouseMotion(self,e):
121                 self.Refresh()
122                 if not self._container.OnMouseMotion(e.GetX(), e.GetY()):
123                         self.OnMouseMotion(e)
124
125         def _OnGuiPaint(self, e):
126                 h = self.GetSize().GetHeight()
127                 w = self.GetSize().GetWidth()
128                 oldButtonSize = self._buttonSize
129                 if h / 3 > w / 4:
130                         w = h * 4 / 3
131                 if w < 64 * 10:
132                         self._buttonSize = 48
133                 elif w < 64 * 15:
134                         self._buttonSize = 64
135                 elif w < 64 * 20:
136                         self._buttonSize = 80
137                 else:
138                         self._buttonSize = 96
139                 if self._buttonSize != oldButtonSize:
140                         self._container.updateLayout()
141
142                 dc = wx.PaintDC(self)
143                 self.SetCurrent(self._context)
144                 self.OnPaint(e)
145                 self._drawGui()
146                 glFlush()
147                 self.SwapBuffers()
148
149         def _drawGui(self):
150                 if self._glButtonsTexture is None:
151                         self._glButtonsTexture = opengl.loadGLTexture('glButtons.png')
152
153                 glDisable(GL_DEPTH_TEST)
154                 glEnable(GL_BLEND)
155                 glDisable(GL_LIGHTING)
156                 glColor4ub(255,255,255,255)
157
158                 glMatrixMode(GL_PROJECTION)
159                 glLoadIdentity()
160                 size = self.GetSize()
161                 glOrtho(0, size.GetWidth()-1, size.GetHeight()-1, 0, -1000.0, 1000.0)
162                 glMatrixMode(GL_MODELVIEW)
163                 glLoadIdentity()
164
165                 self._container.draw()
166
167         def _OnEraseBackground(self,event):
168                 #Workaround for windows background redraw flicker.
169                 pass
170
171         def _OnSize(self,e):
172                 self._container.setSize(0, 0, self.GetSize().GetWidth(), self.GetSize().GetHeight())
173                 self._container.updateLayout()
174                 self.Refresh()
175
176         def OnMouseLeftDown(self,e):
177                 pass
178         def OnMouseMotion(self, e):
179                 pass
180         def OnPaint(self, e):
181                 pass
182
183         def add(self, ctrl):
184                 if self._container is not None:
185                         self._container.add(ctrl)
186
187 class glGuiLayoutButtons(object):
188         def __init__(self, parent):
189                 self._parent = parent
190                 self._parent._layout = self
191
192         def update(self):
193                 bs = self._parent._base._buttonSize
194                 x0, y0, w, h = self._parent.getSize()
195                 gridSize = bs * 1.3
196                 for ctrl in self._parent._glGuiControlList:
197                         pos = ctrl._pos
198                         if pos[0] < 0:
199                                 x = w + pos[0] * gridSize - bs * 0.2
200                         else:
201                                 x = pos[0] * gridSize + bs * 0.2
202                         if pos[1] < 0:
203                                 y = h + pos[1] * gridSize - bs * 0.2
204                         else:
205                                 y = pos[1] * gridSize + bs * 0.2
206                         ctrl.setSize(x, y, gridSize, gridSize)
207
208         def getLayoutSize(self):
209                 _, _, w, h = self._parent.getSize()
210                 return w, h
211
212 class glGuiLayoutGrid(object):
213         def __init__(self, parent):
214                 self._parent = parent
215                 self._parent._layout = self
216                 self._size = 0,0
217
218         def update(self):
219                 borderSize = self._parent._base._buttonSize * 0.2
220                 x0, y0, w, h = self._parent.getSize()
221                 x0 += borderSize
222                 y0 += borderSize
223                 widths = {}
224                 heights = {}
225                 for ctrl in self._parent._glGuiControlList:
226                         x, y = ctrl._pos
227                         w, h = ctrl.getMinSize()
228                         if not x in widths:
229                                 widths[x] = w
230                         else:
231                                 widths[x] = max(widths[x], w)
232                         if not y in heights:
233                                 heights[y] = h
234                         else:
235                                 heights[y] = max(heights[y], h)
236                 for ctrl in self._parent._glGuiControlList:
237                         x, y = ctrl._pos
238                         x1 = x0
239                         y1 = y0
240                         for n in xrange(0, x):
241                                 if not n in widths:
242                                         widths[n] = 3
243                                 x1 += widths[n]
244                         for n in xrange(0, y):
245                                 if not n in heights:
246                                         heights[n] = 3
247                                 y1 += heights[n]
248                         ctrl.setSize(x1, y1, widths[x], heights[y])
249                 self._size = sum(widths.values()) + borderSize * 2, sum(heights.values()) + borderSize * 2
250
251         def getLayoutSize(self):
252                 return self._size
253
254 class glButton(glGuiControl):
255         def __init__(self, parent, imageID, tooltip, pos, callback):
256                 super(glButton, self).__init__(parent, pos)
257                 self._tooltip = tooltip
258                 self._parent = parent
259                 self._imageID = imageID
260                 self._callback = callback
261                 self._selected = False
262                 self._focus = False
263                 self._hidden = False
264                 self._disabled = False
265
266         def setSelected(self, value):
267                 self._selected = value
268
269         def setHidden(self, value):
270                 self._hidden = value
271
272         def setDisabled(self, value):
273                 self._disabled = value
274
275         def getSelected(self):
276                 return self._selected
277
278         def getMinSize(self):
279                 return self._base._buttonSize, self._base._buttonSize
280
281         def _getPixelPos(self):
282                 x0, y0, w, h = self.getSize()
283                 return x0 + w / 2, y0 + h / 2
284
285         def draw(self):
286                 if self._hidden:
287                         return
288
289                 cx = (self._imageID % 4) / 4
290                 cy = int(self._imageID / 4) / 4
291                 bs = self._base._buttonSize
292                 pos = self._getPixelPos()
293
294                 glPushMatrix()
295                 glTranslatef(pos[0], pos[1], 0)
296                 glBindTexture(GL_TEXTURE_2D, self._base._glButtonsTexture)
297                 glEnable(GL_TEXTURE_2D)
298                 scale = 0.8
299                 if self._selected:
300                         scale = 1.0
301                 elif self._focus:
302                         scale = 0.9
303                 glScalef(bs * scale, bs * scale, bs * scale)
304                 if self._disabled:
305                         glColor4ub(128,128,128,128)
306                 else:
307                         glColor4ub(255,255,255,255)
308                 glBegin(GL_QUADS)
309                 glTexCoord2f(cx+0.25, cy)
310                 glVertex2f( 0.5,-0.5)
311                 glTexCoord2f(cx, cy)
312                 glVertex2f(-0.5,-0.5)
313                 glTexCoord2f(cx, cy+0.25)
314                 glVertex2f(-0.5, 0.5)
315                 glTexCoord2f(cx+0.25, cy+0.25)
316                 glVertex2f( 0.5, 0.5)
317                 glEnd()
318                 glDisable(GL_TEXTURE_2D)
319                 if self._focus:
320                         glColor4ub(0,0,0,255)
321                         glTranslatef(0, -0.55, 0)
322                         opengl.glDrawStringCenter(self._tooltip)
323                 glPopMatrix()
324
325         def _checkHit(self, x, y):
326                 if self._hidden or self._disabled:
327                         return False
328                 bs = self.getMinSize()[0]
329                 pos = self._getPixelPos()
330                 return -bs * 0.5 <= x - pos[0] <= bs * 0.5 and -bs * 0.5 <= y - pos[1] <= bs * 0.5
331
332         def OnMouseMotion(self, x, y):
333                 if self._checkHit(x, y):
334                         self._focus = True
335                         return True
336                 self._focus = False
337                 return False
338
339         def OnMouseDown(self, x, y):
340                 if self._checkHit(x, y):
341                         self._callback()
342                         return True
343                 return False
344
345 class glRadioButton(glButton):
346         def __init__(self, parent, imageID, tooltip, pos, group, callback):
347                 super(glRadioButton, self).__init__(parent, imageID, tooltip, pos, self._onRadioSelect)
348                 self._group = group
349                 self._radioCallback = callback
350                 self._group.append(self)
351
352         def setSelected(self, value):
353                 self._selected = value
354
355         def _onRadioSelect(self):
356                 for ctrl in self._group:
357                         if ctrl != self:
358                                 ctrl.setSelected(False)
359                 if self.getSelected():
360                         self.setSelected(False)
361                 else:
362                         self.setSelected(True)
363                 self._radioCallback()
364
365 class glFrame(glGuiContainer):
366         def __init__(self, parent, pos):
367                 super(glFrame, self).__init__(parent, pos)
368                 self._selected = False
369                 self._focus = False
370                 self._hidden = False
371
372         def setSelected(self, value):
373                 self._selected = value
374
375         def setHidden(self, value):
376                 self._hidden = value
377
378         def getSelected(self):
379                 return self._selected
380
381         def getMinSize(self):
382                 return self._base._buttonSize, self._base._buttonSize
383
384         def _getPixelPos(self):
385                 x0, y0, w, h = self.getSize()
386                 return x0, y0
387
388         def draw(self):
389                 if self._hidden:
390                         return
391
392                 bs = self._parent._buttonSize
393                 pos = self._getPixelPos()
394
395                 glPushMatrix()
396                 glTranslatef(pos[0], pos[1], 0)
397                 glBindTexture(GL_TEXTURE_2D, self._parent._glButtonsTexture)
398                 glEnable(GL_TEXTURE_2D)
399
400                 size = self._layout.getLayoutSize()
401                 glColor4ub(255,255,255,128)
402                 glBegin(GL_QUADS)
403                 glTexCoord2f(1, 0)
404                 glVertex2f( size[0], 0)
405                 glTexCoord2f(0, 0)
406                 glVertex2f( 0, 0)
407                 glTexCoord2f(0, 1)
408                 glVertex2f( 0, size[1])
409                 glTexCoord2f(1, 1)
410                 glVertex2f( size[0], size[1])
411                 glEnd()
412                 glDisable(GL_TEXTURE_2D)
413                 glPopMatrix()
414                 #Draw the controls on the frame
415                 super(glFrame, self).draw()
416
417         def _checkHit(self, x, y):
418                 if self._hidden:
419                         return False
420                 pos = self._getPixelPos()
421                 w, h = self._layout.getLayoutSize()
422                 return 0 <= x - pos[0] <= w and 0 <= y - pos[1] <= h
423
424         def OnMouseMotion(self, x, y):
425                 super(glFrame, self).OnMouseMotion(x, y)
426                 if self._checkHit(x, y):
427                         self._focus = True
428                         return True
429                 self._focus = False
430                 return False
431
432         def OnMouseDown(self, x, y):
433                 if self._checkHit(x, y):
434                         super(glFrame, self).OnMouseDown(x, y)
435                         return True
436                 return False
437
438 class glLabel(glGuiControl):
439         def __init__(self, parent, label, pos):
440                 self._label = label
441                 super(glLabel, self).__init__(parent, pos)
442
443         def getMinSize(self):
444                 w, h = opengl.glGetStringSize(self._label)
445                 return w + 10, h + 4
446
447         def _getPixelPos(self):
448                 x0, y0, w, h = self.getSize()
449                 return x0, y0
450
451         def draw(self):
452                 x, y, w, h = self.getSize()
453
454                 glPushMatrix()
455                 glTranslatef(x, y, 0)
456
457                 glColor4ub(255,255,255,128)
458                 glBegin(GL_QUADS)
459                 glTexCoord2f(1, 0)
460                 glVertex2f( w, 0)
461                 glTexCoord2f(0, 0)
462                 glVertex2f( 0, 0)
463                 glTexCoord2f(0, 1)
464                 glVertex2f( 0, h)
465                 glTexCoord2f(1, 1)
466                 glVertex2f( w, h)
467                 glEnd()
468
469                 glTranslate(5, h - 5, 0)
470                 glColor4ub(0,0,0,255)
471                 opengl.glDrawStringLeft(self._label)
472                 glPopMatrix()
473
474         def _checkHit(self, x, y):
475                 return False
476
477         def OnMouseMotion(self, x, y):
478                 return False
479
480         def OnMouseDown(self, x, y):
481                 return False
482
483 class glNumberCtrl(glGuiControl):
484         def __init__(self, parent, value, pos, callback):
485                 self._callback = callback
486                 self._value = str(value)
487                 self._selectPos = 0
488                 self._maxLen = 6
489                 self._inCallback = False
490                 super(glNumberCtrl, self).__init__(parent, pos)
491
492         def setValue(self, value):
493                 if self._inCallback:
494                         return
495                 self._value = str(value)
496
497         def getMinSize(self):
498                 w, h = opengl.glGetStringSize("VALUES")
499                 return w + 10, h + 4
500
501         def _getPixelPos(self):
502                 x0, y0, w, h = self.getSize()
503                 return x0, y0
504
505         def draw(self):
506                 x, y, w, h = self.getSize()
507
508                 glPushMatrix()
509                 glTranslatef(x, y, 0)
510
511                 if self._base._focus == self:
512                         glColor4ub(255,255,255,255)
513                 else:
514                         glColor4ub(255,255,255,192)
515                 glBegin(GL_QUADS)
516                 glTexCoord2f(1, 0)
517                 glVertex2f( w, 0)
518                 glTexCoord2f(0, 0)
519                 glVertex2f( 0, 0)
520                 glTexCoord2f(0, 1)
521                 glVertex2f( 0, h)
522                 glTexCoord2f(1, 1)
523                 glVertex2f( w, h)
524                 glEnd()
525
526                 glTranslate(5, h - 5, 0)
527                 glColor4ub(0,0,0,255)
528                 opengl.glDrawStringLeft(self._value)
529                 if self._base._focus == self:
530                         glTranslate(opengl.glGetStringSize(self._value[0:self._selectPos])[0] - 2, -1, 0)
531                         opengl.glDrawStringLeft('|')
532                 glPopMatrix()
533
534         def _checkHit(self, x, y):
535                 x1, y1, w, h = self.getSize()
536                 return 0 <= x - x1 <= w and 0 <= y - y1 <= h
537
538         def OnMouseMotion(self, x, y):
539                 return False
540
541         def OnMouseDown(self, x, y):
542                 if self._checkHit(x, y):
543                         self.setFocus()
544                         return True
545                 return False
546
547         def OnKeyChar(self, c):
548                 self._inCallback = True
549                 if c == wx.WXK_LEFT:
550                         self._selectPos -= 1
551                         self._selectPos = max(0, self._selectPos)
552                 if c == wx.WXK_RIGHT:
553                         self._selectPos += 1
554                         self._selectPos = min(self._selectPos, len(self._value))
555                 if c == wx.WXK_UP:
556                         try:
557                                 value = float(self._value)
558                         except:
559                                 pass
560                         else:
561                                 value += 0.1
562                                 self._value = str(value)
563                                 self._callback(self._value)
564                 if c == wx.WXK_DOWN:
565                         try:
566                                 value = float(self._value)
567                         except:
568                                 pass
569                         else:
570                                 value -= 0.1
571                                 if value > 0:
572                                         self._value = str(value)
573                                         self._callback(self._value)
574                 if c == wx.WXK_BACK and self._selectPos > 0:
575                         self._value = self._value[0:self._selectPos - 1] + self._value[self._selectPos:]
576                         self._selectPos -= 1
577                         self._callback(self._value)
578                 if c == wx.WXK_DELETE:
579                         self._value = self._value[0:self._selectPos] + self._value[self._selectPos + 1:]
580                         self._callback(self._value)
581                 if c == wx.WXK_TAB:
582                         if wx.GetKeyState(wx.WXK_SHIFT):
583                                 self.focusPrevious()
584                         else:
585                                 self.focusNext()
586                 if (ord('0') <= c <= ord('9') or c == ord('.')) and len(self._value) < self._maxLen:
587                         self._value = self._value[0:self._selectPos] + chr(c) + self._value[self._selectPos:]
588                         self._selectPos += 1
589                         self._callback(self._value)
590                 self._inCallback = False
591
592         def setFocus(self):
593                 self._base._focus = self
594                 self._selectPos = len(self._value)
595                 return True
596
597 class glCheckbox(glGuiControl):
598         def __init__(self, parent, value, pos, callback):
599                 self._callback = callback
600                 self._value = value
601                 self._selectPos = 0
602                 self._maxLen = 6
603                 self._inCallback = False
604                 super(glCheckbox, self).__init__(parent, pos)
605
606         def setValue(self, value):
607                 if self._inCallback:
608                         return
609                 self._value = str(value)
610
611         def getValue(self):
612                 return self._value
613
614         def getMinSize(self):
615                 return 20, 20
616
617         def _getPixelPos(self):
618                 x0, y0, w, h = self.getSize()
619                 return x0, y0
620
621         def draw(self):
622                 x, y, w, h = self.getSize()
623
624                 glPushMatrix()
625                 glTranslatef(x, y, 0)
626
627                 if self._value:
628                         glColor4ub(0,255,0,255)
629                 else:
630                         glColor4ub(255,0,0,255)
631                 glBegin(GL_QUADS)
632                 glTexCoord2f(1, 0)
633                 glVertex2f( w, 0)
634                 glTexCoord2f(0, 0)
635                 glVertex2f( 0, 0)
636                 glTexCoord2f(0, 1)
637                 glVertex2f( 0, h)
638                 glTexCoord2f(1, 1)
639                 glVertex2f( w, h)
640                 glEnd()
641
642                 glPopMatrix()
643
644         def _checkHit(self, x, y):
645                 x1, y1, w, h = self.getSize()
646                 return 0 <= x - x1 <= w and 0 <= y - y1 <= h
647
648         def OnMouseMotion(self, x, y):
649                 return False
650
651         def OnMouseDown(self, x, y):
652                 if self._checkHit(x, y):
653                         self._value = not self._value
654                         return True
655                 return False