chiark / gitweb /
bdf6f9cdeae503a217dae5d479851c29bcf578b3
[cura.git] / Cura / gui / tools / youmagineGui.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import wx
5 import threading
6 import time
7 import re
8 import os
9 import types
10 import webbrowser
11 import cStringIO as StringIO
12
13 from Cura.util import profile
14 from Cura.util import youmagine
15 from Cura.util.meshLoaders import stl
16 from Cura.util.meshLoaders import amf
17 from Cura.util.resources import getPathForImage
18
19 from Cura.gui.util import webcam
20
21 DESIGN_FILE_EXT = ['.scad', '.blend', '.max', '.stp', '.step', '.igs', '.iges', '.sldasm', '.sldprt', '.skp', '.iam', '.prt', '.x_t', '.ipt', '.dwg', '.123d', '.wings', '.fcstd', '.top']
22
23 def getClipboardText():
24         ret = ''
25         try:
26                 if not wx.TheClipboard.IsOpened():
27                         wx.TheClipboard.Open()
28                         do = wx.TextDataObject()
29                         if wx.TheClipboard.GetData(do):
30                                 ret = do.GetText()
31                         wx.TheClipboard.Close()
32                 return ret
33         except:
34                 return ret
35
36 def getAdditionalFiles(objects, onlyExtChanged):
37         names = set()
38         for obj in objects:
39                 name = obj.getOriginFilename()
40                 if name is None:
41                         continue
42                 names.add(name[:name.rfind('.')])
43         ret = []
44         for name in names:
45                 for ext in DESIGN_FILE_EXT:
46                         if os.path.isfile(name + ext):
47                                 ret.append(name + ext)
48         if onlyExtChanged:
49                 return ret
50         onlyExtList = ret
51         ret = []
52         for name in names:
53                 for ext in DESIGN_FILE_EXT:
54                         for filename in os.listdir(os.path.dirname(name)):
55                                 filename = os.path.join(os.path.dirname(name), filename)
56                                 if filename.endswith(ext) and filename not in ret and filename not in onlyExtList:
57                                         ret.append(filename)
58         return ret
59
60 class youmagineManager(object):
61         def __init__(self, parent, objectScene):
62                 self._mainWindow = parent
63                 self._scene = objectScene
64                 self._ym = youmagine.Youmagine(profile.getPreference('youmagine_token'), self._progressCallback)
65
66                 self._indicatorWindow = workingIndicatorWindow(self._mainWindow)
67                 self._getAuthorizationWindow = getAuthorizationWindow(self._mainWindow, self._ym)
68                 self._newDesignWindow = newDesignWindow(self._mainWindow, self, self._ym)
69
70                 thread = threading.Thread(target=self.checkAuthorizationThread)
71                 thread.daemon = True
72                 thread.start()
73
74         def _progressCallback(self, progress):
75                 self._indicatorWindow.progress(progress)
76
77         #Do all the youmagine communication in a background thread, because it can take a while and block the UI thread otherwise
78         def checkAuthorizationThread(self):
79                 wx.CallAfter(self._indicatorWindow.showBusy, _("Checking token"))
80                 if not self._ym.isAuthorized():
81                         wx.CallAfter(self._indicatorWindow.Hide)
82                         if not self._ym.isHostReachable():
83                                 wx.MessageBox(_("Failed to contact YouMagine.com"), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
84                                 return
85                         wx.CallAfter(self._getAuthorizationWindow.Show)
86                         lastTriedClipboard = ''
87                         while not self._ym.isAuthorized():
88                                 time.sleep(0.1)
89                                 if self._getAuthorizationWindow.abort:
90                                         wx.CallAfter(self._getAuthorizationWindow.Destroy)
91                                         return
92                                 clipboard = getClipboardText()
93                                 if len(clipboard) == 20:
94                                         if clipboard != lastTriedClipboard and re.match('[a-zA-Z0-9]*', clipboard):
95                                                 lastTriedClipboard = clipboard
96                                                 self._ym.setAuthToken(clipboard)
97                         profile.putPreference('youmagine_token', self._ym.getAuthToken())
98                         wx.CallAfter(self._getAuthorizationWindow.Hide)
99                         wx.CallAfter(self._getAuthorizationWindow.Destroy)
100                         wx.MessageBox(_("Cura is now authorized to share on YouMagine"), _("YouMagine."), wx.OK | wx.ICON_INFORMATION)
101                 wx.CallAfter(self._indicatorWindow.Hide)
102
103                 #TODO: Would you like to create a new design or add the model to an existing design?
104                 wx.CallAfter(self._newDesignWindow.Show)
105
106         def createNewDesign(self, name, description, category, license, imageList, extraFileList, publish):
107                 thread = threading.Thread(target=self.createNewDesignThread, args=(name, description, category, license, imageList, extraFileList, publish))
108                 thread.daemon = True
109                 thread.start()
110
111         def createNewDesignThread(self, name, description, category, license, imageList, extraFileList, publish):
112                 wx.CallAfter(self._indicatorWindow.showBusy, _("Creating new design on YouMagine..."))
113                 id = self._ym.createDesign(name, description, category, license)
114                 wx.CallAfter(self._indicatorWindow.Hide)
115                 if id is None:
116                         wx.MessageBox(_("Failed to create a design, nothing uploaded!"), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
117                         return
118
119                 for obj in self._scene.objects():
120                         wx.CallAfter(self._indicatorWindow.showBusy, _("Building model %s...") % (obj.getName()))
121                         time.sleep(0.1)
122                         s = StringIO.StringIO()
123                         filename = obj.getName()
124                         if obj.canStoreAsSTL():
125                                 stl.saveSceneStream(s, [obj])
126                                 filename += '.stl'
127                         else:
128                                 amf.saveSceneStream(s, filename, [obj])
129                                 filename += '.amf'
130
131                         wx.CallAfter(self._indicatorWindow.showBusy, _("Uploading model %s...") % (filename))
132                         if self._ym.createDocument(id, filename, s.getvalue()) is None:
133                                 wx.MessageBox(_("Failed to upload %s!") % (filename), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
134                         s.close()
135
136                 for extra in extraFileList:
137                         wx.CallAfter(self._indicatorWindow.showBusy, _("Uploading file %s...") % (os.path.basename(extra)))
138                         with open(extra, "rb") as f:
139                                 if self._ym.createDocument(id, os.path.basename(extra), f.read()) is None:
140                                         wx.MessageBox(_("Failed to upload %s!") % (os.path.basename(extra)), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
141
142                 for image in imageList:
143                         if type(image) in types.StringTypes:
144                                 filename = os.path.basename(image)
145                                 wx.CallAfter(self._indicatorWindow.showBusy, _("Uploading image %s...") % (filename))
146                                 with open(image, "rb") as f:
147                                         if self._ym.createImage(id, filename, f.read()) is None:
148                                                 wx.MessageBox(_("Failed to upload %s!") % (filename), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
149                         elif type(image) is wx.Bitmap:
150                                 s = StringIO.StringIO()
151                                 if wx.ImageFromBitmap(image).SaveStream(s, wx.BITMAP_TYPE_JPEG):
152                                         if self._ym.createImage(id, "snapshot.jpg", s.getvalue()) is None:
153                                                 wx.MessageBox(_("Failed to upload snapshot!"), _("YouMagine error."), wx.OK | wx.ICON_ERROR)
154                         else:
155                                 print type(image)
156
157                 if publish:
158                         wx.CallAfter(self._indicatorWindow.showBusy, _("Publishing design..."))
159                         if not self._ym.publishDesign(id):
160                                 #If publishing failed try again after 1 second, this might help when you need to wait for the renderer. But does not always work.
161                                 time.sleep(1)
162                                 self._ym.publishDesign(id)
163                 wx.CallAfter(self._indicatorWindow.Hide)
164
165                 webbrowser.open(self._ym.viewUrlForDesign(id))
166
167
168 class workingIndicatorWindow(wx.Frame):
169         def __init__(self, parent):
170                 super(workingIndicatorWindow, self).__init__(parent, title='YouMagine', style=wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.CAPTION)
171                 self._panel = wx.Panel(self)
172                 self.SetSizer(wx.BoxSizer())
173                 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
174
175                 self._busyBitmaps = [
176                         wx.Bitmap(getPathForImage('busy-0.png')),
177                         wx.Bitmap(getPathForImage('busy-1.png')),
178                         wx.Bitmap(getPathForImage('busy-2.png')),
179                         wx.Bitmap(getPathForImage('busy-3.png'))
180                 ]
181
182                 self._indicatorBitmap = wx.StaticBitmap(self._panel, -1, wx.EmptyBitmapRGBA(24, 24, red=255, green=255, blue=255, alpha=1))
183                 self._statusText = wx.StaticText(self._panel, -1, '...')
184                 self._progress = wx.Gauge(self._panel, -1)
185                 self._progress.SetRange(1000)
186                 self._progress.SetMinSize((250, 30))
187
188                 self._panel._sizer = wx.GridBagSizer(2, 2)
189                 self._panel.SetSizer(self._panel._sizer)
190                 self._panel._sizer.Add(self._indicatorBitmap, (0, 0), flag=wx.ALL, border=5)
191                 self._panel._sizer.Add(self._statusText, (0, 1), flag=wx.ALIGN_CENTER_VERTICAL|wx.ALL, border=5)
192                 self._panel._sizer.Add(self._progress, (1, 0), span=(1,2), flag=wx.EXPAND|wx.ALL, border=5)
193
194                 self._busyState = 0
195                 self._busyTimer = wx.Timer(self)
196                 self.Bind(wx.EVT_TIMER, self._busyUpdate, self._busyTimer)
197                 self._busyTimer.Start(100)
198
199         def _busyUpdate(self, e):
200                 if self._busyState is None:
201                         return
202                 self._busyState += 1
203                 if self._busyState >= len(self._busyBitmaps):
204                         self._busyState = 0
205                 self._indicatorBitmap.SetBitmap(self._busyBitmaps[self._busyState])
206
207         def progress(self, progressAmount):
208                 wx.CallAfter(self._progress.Show)
209                 wx.CallAfter(self._progress.SetValue, progressAmount*1000)
210                 wx.CallAfter(self.Layout)
211                 wx.CallAfter(self.Fit)
212
213         def showBusy(self, text):
214                 self._statusText.SetLabel(text)
215                 self._progress.Hide()
216                 self.Layout()
217                 self.Fit()
218                 self.Centre()
219                 self.Show()
220
221 class getAuthorizationWindow(wx.Frame):
222         def __init__(self, parent, ym):
223                 super(getAuthorizationWindow, self).__init__(parent, title='YouMagine')
224                 self._panel = wx.Panel(self)
225                 self.SetSizer(wx.BoxSizer())
226                 self.GetSizer().Add(self._panel, 1, wx.EXPAND)
227                 self._ym = ym
228                 self.abort = False
229
230                 self._requestButton = wx.Button(self._panel, -1, _("Request authorization from YouMagine"))
231                 self._authToken = wx.TextCtrl(self._panel, -1, _("Paste token here"))
232
233                 self._panel._sizer = wx.GridBagSizer(5, 5)
234                 self._panel.SetSizer(self._panel._sizer)
235
236                 self._panel._sizer.Add(wx.StaticBitmap(self._panel, -1, wx.Bitmap(getPathForImage('youmagine-text.png'))), (0,0), span=(1,4), flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
237                 self._panel._sizer.Add(wx.StaticText(self._panel, -1, _("To share your designs on YouMagine\nyou need an account on YouMagine.com\nand authorize Cura to access your account.")), (1, 1))
238                 self._panel._sizer.Add(self._requestButton, (2, 1), flag=wx.ALL)
239                 self._panel._sizer.Add(wx.StaticText(self._panel, -1, _("This will open a browser window where you can\nauthorize Cura to access your YouMagine account.\nYou can revoke access at any time\nfrom YouMagine.com")), (3, 1), flag=wx.ALL)
240                 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (4,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
241                 self._panel._sizer.Add(self._authToken, (5, 1), flag=wx.EXPAND | wx.ALL)
242                 self._panel._sizer.Add(wx.StaticLine(self._panel, -1), (6,0), span=(1,4), flag=wx.EXPAND | wx.ALL)
243
244                 self.Bind(wx.EVT_BUTTON, self.OnRequestAuthorization, self._requestButton)
245                 self.Bind(wx.EVT_TEXT, self.OnEnterToken, self._authToken)
246                 self.Bind(wx.EVT_CLOSE, self.OnClose)
247
248                 self.Fit()
249                 self.Centre()
250
251                 self._authToken.SetFocus()
252                 self._authToken.SelectAll()
253
254         def OnRequestAuthorization(self, e):
255                 webbrowser.open(self._ym.getAuthorizationUrl())
256
257         def OnEnterToken(self, e):
258                 self._ym.setAuthToken(self._authToken.GetValue())
259
260         def OnClose(self, e):
261                 self.abort = True
262
263 class newDesignWindow(wx.Frame):
264         def __init__(self, parent, manager, ym):
265                 super(newDesignWindow, self).__init__(parent, title='Share on YouMagine')
266                 p = wx.Panel(self)
267                 self.SetSizer(wx.BoxSizer())
268                 self.GetSizer().Add(p, 1, wx.EXPAND)
269                 self._manager = manager
270                 self._ym = ym
271
272                 categoryOptions = ym.getCategories()
273                 licenseOptions = ym.getLicenses()
274                 self._designName = wx.TextCtrl(p, -1, _("Design name"))
275                 self._designDescription = wx.TextCtrl(p, -1, '', size=(1, 150), style = wx.TE_MULTILINE)
276                 self._designLicense = wx.ComboBox(p, -1, licenseOptions[0], choices=licenseOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
277                 self._category = wx.ComboBox(p, -1, categoryOptions[-1], choices=categoryOptions, style=wx.CB_DROPDOWN|wx.CB_READONLY)
278                 self._publish = wx.CheckBox(p, -1, _("Publish after upload"))
279                 self._shareButton = wx.Button(p, -1, _("Share!"))
280                 self._imageScroll = wx.lib.scrolledpanel.ScrolledPanel(p)
281                 self._additionalFiles = wx.CheckListBox(p, -1)
282                 self._additionalFiles.InsertItems(getAdditionalFiles(self._manager._scene.objects(), True), 0)
283                 self._additionalFiles.SetChecked(range(0, self._additionalFiles.GetCount()))
284                 self._additionalFiles.InsertItems(getAdditionalFiles(self._manager._scene.objects(), False), self._additionalFiles.GetCount())
285
286                 self._imageScroll.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
287                 self._addImageButton = wx.Button(self._imageScroll, -1, _("Add..."), size=(70,52))
288                 self._imageScroll.GetSizer().Add(self._addImageButton)
289                 self._snapshotButton = wx.Button(self._imageScroll, -1, _("Webcam..."), size=(70,52))
290                 self._imageScroll.GetSizer().Add(self._snapshotButton)
291                 self._imageScroll.Fit()
292                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
293                 self._imageScroll.SetMinSize((20, self._imageScroll.GetSize()[1] + wx.SystemSettings_GetMetric(wx.SYS_HSCROLL_Y)))
294
295                 self._publish.SetValue(True)
296                 self._publish.SetToolTipString(
297                         _("Directly publish the design after uploading.\nWithout this check the design will not be public\nuntil you publish it yourself on YouMagine.com"))
298
299                 s = wx.GridBagSizer(5, 5)
300                 p.SetSizer(s)
301
302                 s.Add(wx.StaticBitmap(p, -1, wx.Bitmap(getPathForImage('youmagine-text.png'))), (0,0), span=(1,3), flag=wx.ALIGN_CENTRE | wx.ALL, border=5)
303                 s.Add(wx.StaticText(p, -1, _("Design name:")), (1, 0), flag=wx.LEFT|wx.TOP, border=5)
304                 s.Add(self._designName, (1, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
305                 s.Add(wx.StaticText(p, -1, _("Description:")), (2, 0), flag=wx.LEFT|wx.TOP, border=5)
306                 s.Add(self._designDescription, (2, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
307                 s.Add(wx.StaticText(p, -1, _("Category:")), (3, 0), flag=wx.LEFT|wx.TOP, border=5)
308                 s.Add(self._category, (3, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
309                 s.Add(wx.StaticText(p, -1, _("License:")), (4, 0), flag=wx.LEFT|wx.TOP, border=5)
310                 s.Add(self._designLicense, (4, 1), span=(1,2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
311                 s.Add(wx.StaticLine(p, -1), (5,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
312                 s.Add(wx.StaticText(p, -1, _("Images:")), (6, 0), flag=wx.LEFT|wx.TOP, border=5)
313                 s.Add(self._imageScroll, (6, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
314                 s.Add(wx.StaticLine(p, -1), (7,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
315                 s.Add(wx.StaticText(p, -1, _("Related design files:")), (8, 0), flag=wx.LEFT|wx.TOP, border=5)
316
317                 s.Add(self._additionalFiles, (8, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
318                 s.Add(wx.StaticLine(p, -1), (9,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
319                 s.Add(self._shareButton, (10, 1), flag=wx.BOTTOM, border=15)
320                 s.Add(self._publish, (10, 2), flag=wx.BOTTOM|wx.ALIGN_CENTER_VERTICAL, border=15)
321
322                 s.AddGrowableRow(2)
323                 s.AddGrowableCol(2)
324
325                 self.Bind(wx.EVT_BUTTON, self.OnShare, self._shareButton)
326                 self.Bind(wx.EVT_BUTTON, self.OnAddImage, self._addImageButton)
327                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._snapshotButton)
328
329                 self.Fit()
330                 self.Centre()
331
332                 self._designDescription.SetMinSize((1,1))
333                 self._designName.SetFocus()
334                 self._designName.SelectAll()
335
336         def OnShare(self, e):
337                 if self._designName.GetValue() == '':
338                         wx.MessageBox(_("The name cannot be empty"), _("New design error."), wx.OK | wx.ICON_ERROR)
339                         self._designName.SetFocus()
340                         return
341                 if self._designDescription.GetValue() == '':
342                         wx.MessageBox(_("The description cannot be empty"), _("New design error."), wx.OK | wx.ICON_ERROR)
343                         self._designDescription.SetFocus()
344                         return
345                 imageList = []
346                 for child in self._imageScroll.GetChildren():
347                         if hasattr(child, 'imageFilename'):
348                                 imageList.append(child.imageFilename)
349                         if hasattr(child, 'imageData'):
350                                 imageList.append(child.imageData)
351                 self._manager.createNewDesign(self._designName.GetValue(), self._designDescription.GetValue(), self._category.GetValue(), self._designLicense.GetValue(), imageList, self._additionalFiles.GetCheckedStrings(), self._publish.GetValue())
352                 self.Destroy()
353
354         def OnAddImage(self, e):
355                 dlg=wx.FileDialog(self, "Select image file...", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
356                 dlg.SetWildcard("Image files (*.jpg,*.jpeg,*.png)|*.jpg;*.jpeg;*.png")
357                 if dlg.ShowModal() == wx.ID_OK:
358                         for filename in dlg.GetPaths():
359                                 self._addImage(filename)
360                 dlg.Destroy()
361
362         def OnTakeImage(self, e):
363                 webcamPhotoWindow(self).Show()
364
365         def _addImage(self, image):
366                 wxImage = None
367                 if type(image) in types.StringTypes:
368                         try:
369                                 wxImage = wx.ImageFromBitmap(wx.Bitmap(image))
370                         except:
371                                 pass
372                 else:
373                         wxImage = wx.ImageFromBitmap(image)
374                 if wxImage is None:
375                         return
376
377                 width, height = wxImage.GetSize()
378                 if width > 70:
379                         height = height*70/width
380                         width = 70
381                 if height > 52:
382                         width = width*52/height
383                         height = 52
384                 wxImage.Rescale(width, height, wx.IMAGE_QUALITY_NORMAL)
385                 wxImage.Resize((70, 52), ((70-width)/2, (52-height)/2))
386                 ctrl = wx.StaticBitmap(self._imageScroll, -1, wx.BitmapFromImage(wxImage))
387                 if type(image) in types.StringTypes:
388                         ctrl.imageFilename = image
389                 else:
390                         ctrl.imageData = image
391
392                 delButton = wx.Button(ctrl, -1, 'X', style=wx.BU_EXACTFIT)
393                 self.Bind(wx.EVT_BUTTON, self.OnDeleteImage, delButton)
394
395                 self._imageScroll.GetSizer().Insert(len(self._imageScroll.GetChildren())-3, ctrl)
396                 self._imageScroll.Layout()
397                 self._imageScroll.Refresh()
398                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
399
400         def OnDeleteImage(self, e):
401                 ctrl = e.GetEventObject().GetParent()
402                 self._imageScroll.GetSizer().Detach(ctrl)
403                 ctrl.Destroy()
404
405                 self._imageScroll.Layout()
406                 self._imageScroll.Refresh()
407                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
408
409 class webcamPhotoWindow(wx.Frame):
410         def __init__(self, parent):
411                 super(webcamPhotoWindow, self).__init__(parent, title='YouMagine')
412                 p = wx.Panel(self)
413                 self.panel = p
414                 self.SetSizer(wx.BoxSizer())
415                 self.GetSizer().Add(p, 1, wx.EXPAND)
416
417                 self._cam = webcam.webcam()
418                 self._cam.takeNewImage(False)
419
420                 s = wx.GridBagSizer(3, 3)
421                 p.SetSizer(s)
422
423                 self._preview = wx.Panel(p)
424                 self._cameraSelect = wx.ComboBox(p, -1, self._cam.listCameras()[0], choices=self._cam.listCameras(), style=wx.CB_DROPDOWN|wx.CB_READONLY)
425                 self._takeImageButton = wx.Button(p, -1, 'Snap image')
426                 self._takeImageTimer = wx.Timer(self)
427
428                 s.Add(self._takeImageButton, pos=(1, 0), flag=wx.ALL, border=5)
429                 s.Add(self._cameraSelect, pos=(1, 1), flag=wx.ALL, border=5)
430                 s.Add(self._preview, pos=(0, 0), span=(1, 2), flag=wx.EXPAND|wx.ALL, border=5)
431
432                 if self._cam.getLastImage() is not None:
433                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
434                 else:
435                         self._preview.SetMinSize((640, 480))
436
437                 self._preview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
438                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._takeImageButton)
439                 self.Bind(wx.EVT_TIMER, self.OnTakeImageTimer, self._takeImageTimer)
440                 self.Bind(wx.EVT_COMBOBOX, self.OnCameraChange, self._cameraSelect)
441
442                 self.Fit()
443                 self.Centre()
444
445                 self._takeImageTimer.Start(200)
446
447         def OnCameraChange(self, e):
448                 self._cam.setActiveCamera(self._cameraSelect.GetSelection())
449
450         def OnTakeImage(self, e):
451                 self.GetParent()._addImage(self._cam.getLastImage())
452                 self.Destroy()
453
454         def OnTakeImageTimer(self, e):
455                 self._cam.takeNewImage(False)
456                 self.Refresh()
457
458         def OnCameraEraseBackground(self, e):
459                 dc = e.GetDC()
460                 if not dc:
461                         dc = wx.ClientDC(self)
462                         rect = self.GetUpdateRegion().GetBox()
463                         dc.SetClippingRect(rect)
464                 dc.SetBackground(wx.Brush(self._preview.GetBackgroundColour(), wx.SOLID))
465                 if self._cam.getLastImage() is not None:
466                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
467                         self.panel.Fit()
468                         dc.DrawBitmap(self._cam.getLastImage(), 0, 0)
469                 else:
470                         dc.Clear()