chiark / gitweb /
Merge pull request #557 from GreatFruitOmsk/i18n
[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', '.ipt', '.dwg', '.123d', '.wings']
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='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, _("Upload"))
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, _("Design files:")), (8, 0), flag=wx.LEFT|wx.TOP, border=5)
316                 s.Add(self._additionalFiles, (8, 1), span=(1, 2), flag=wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, border=5)
317                 s.Add(wx.StaticLine(p, -1), (9,0), span=(1,3), flag=wx.EXPAND|wx.ALL)
318                 s.Add(self._shareButton, (10, 1), flag=wx.BOTTOM, border=15)
319                 s.Add(self._publish, (10, 2), flag=wx.BOTTOM|wx.ALIGN_CENTER_VERTICAL, border=15)
320
321                 s.AddGrowableRow(2)
322                 s.AddGrowableCol(2)
323
324                 self.Bind(wx.EVT_BUTTON, self.OnShare, self._shareButton)
325                 self.Bind(wx.EVT_BUTTON, self.OnAddImage, self._addImageButton)
326                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._snapshotButton)
327
328                 self.Fit()
329                 self.Centre()
330
331                 self._designDescription.SetMinSize((1,1))
332                 self._designName.SetFocus()
333                 self._designName.SelectAll()
334
335         def OnShare(self, e):
336                 if self._designName.GetValue() == '':
337                         wx.MessageBox(_("The name cannot be empty"), _("New design error."), wx.OK | wx.ICON_ERROR)
338                         self._designName.SetFocus()
339                         return
340                 if self._designDescription.GetValue() == '':
341                         wx.MessageBox(_("The description cannot be empty"), _("New design error."), wx.OK | wx.ICON_ERROR)
342                         self._designDescription.SetFocus()
343                         return
344                 imageList = []
345                 for child in self._imageScroll.GetChildren():
346                         if hasattr(child, 'imageFilename'):
347                                 imageList.append(child.imageFilename)
348                         if hasattr(child, 'imageData'):
349                                 imageList.append(child.imageData)
350                 self._manager.createNewDesign(self._designName.GetValue(), self._designDescription.GetValue(), self._category.GetValue(), self._designLicense.GetValue(), imageList, self._additionalFiles.GetCheckedStrings(), self._publish.GetValue())
351                 self.Destroy()
352
353         def OnAddImage(self, e):
354                 dlg=wx.FileDialog(self, "Select image file...", style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST|wx.FD_MULTIPLE)
355                 dlg.SetWildcard("Image files (*.jpg,*.jpeg,*.png)|*.jpg;*.jpeg;*.png")
356                 if dlg.ShowModal() == wx.ID_OK:
357                         for filename in dlg.GetPaths():
358                                 self._addImage(filename)
359                 dlg.Destroy()
360
361         def OnTakeImage(self, e):
362                 webcamPhotoWindow(self).Show()
363
364         def _addImage(self, image):
365                 wxImage = None
366                 if type(image) in types.StringTypes:
367                         try:
368                                 wxImage = wx.ImageFromBitmap(wx.Bitmap(image))
369                         except:
370                                 pass
371                 else:
372                         wxImage = wx.ImageFromBitmap(image)
373                 if wxImage is None:
374                         return
375
376                 width, height = wxImage.GetSize()
377                 if width > 70:
378                         height = height*70/width
379                         width = 70
380                 if height > 52:
381                         width = width*52/height
382                         height = 52
383                 wxImage.Rescale(width, height, wx.IMAGE_QUALITY_NORMAL)
384                 wxImage.Resize((70, 52), ((70-width)/2, (52-height)/2))
385                 ctrl = wx.StaticBitmap(self._imageScroll, -1, wx.BitmapFromImage(wxImage))
386                 if type(image) in types.StringTypes:
387                         ctrl.imageFilename = image
388                 else:
389                         ctrl.imageData = image
390
391                 delButton = wx.Button(ctrl, -1, 'X', style=wx.BU_EXACTFIT)
392                 self.Bind(wx.EVT_BUTTON, self.OnDeleteImage, delButton)
393
394                 self._imageScroll.GetSizer().Insert(len(self._imageScroll.GetChildren())-3, ctrl)
395                 self._imageScroll.Layout()
396                 self._imageScroll.Refresh()
397                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
398
399         def OnDeleteImage(self, e):
400                 ctrl = e.GetEventObject().GetParent()
401                 self._imageScroll.GetSizer().Detach(ctrl)
402                 ctrl.Destroy()
403
404                 self._imageScroll.Layout()
405                 self._imageScroll.Refresh()
406                 self._imageScroll.SetupScrolling(scroll_x=True, scroll_y=False)
407
408 class webcamPhotoWindow(wx.Frame):
409         def __init__(self, parent):
410                 super(webcamPhotoWindow, self).__init__(parent, title='YouMagine')
411                 p = wx.Panel(self)
412                 self.panel = p
413                 self.SetSizer(wx.BoxSizer())
414                 self.GetSizer().Add(p, 1, wx.EXPAND)
415
416                 self._cam = webcam.webcam()
417                 self._cam.takeNewImage(False)
418
419                 s = wx.GridBagSizer(3, 3)
420                 p.SetSizer(s)
421
422                 self._preview = wx.Panel(p)
423                 self._cameraSelect = wx.ComboBox(p, -1, self._cam.listCameras()[0], choices=self._cam.listCameras(), style=wx.CB_DROPDOWN|wx.CB_READONLY)
424                 self._takeImageButton = wx.Button(p, -1, 'Snap image')
425                 self._takeImageTimer = wx.Timer(self)
426
427                 s.Add(self._takeImageButton, pos=(1, 0), flag=wx.ALL, border=5)
428                 s.Add(self._cameraSelect, pos=(1, 1), flag=wx.ALL, border=5)
429                 s.Add(self._preview, pos=(0, 0), span=(1, 2), flag=wx.EXPAND|wx.ALL, border=5)
430
431                 if self._cam.getLastImage() is not None:
432                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
433                 else:
434                         self._preview.SetMinSize((640, 480))
435
436                 self._preview.Bind(wx.EVT_ERASE_BACKGROUND, self.OnCameraEraseBackground)
437                 self.Bind(wx.EVT_BUTTON, self.OnTakeImage, self._takeImageButton)
438                 self.Bind(wx.EVT_TIMER, self.OnTakeImageTimer, self._takeImageTimer)
439                 self.Bind(wx.EVT_COMBOBOX, self.OnCameraChange, self._cameraSelect)
440
441                 self.Fit()
442                 self.Centre()
443
444                 self._takeImageTimer.Start(200)
445
446         def OnCameraChange(self, e):
447                 self._cam.setActiveCamera(self._cameraSelect.GetSelection())
448
449         def OnTakeImage(self, e):
450                 self.GetParent()._addImage(self._cam.getLastImage())
451                 self.Destroy()
452
453         def OnTakeImageTimer(self, e):
454                 self._cam.takeNewImage(False)
455                 self.Refresh()
456
457         def OnCameraEraseBackground(self, e):
458                 dc = e.GetDC()
459                 if not dc:
460                         dc = wx.ClientDC(self)
461                         rect = self.GetUpdateRegion().GetBox()
462                         dc.SetClippingRect(rect)
463                 dc.SetBackground(wx.Brush(self._preview.GetBackgroundColour(), wx.SOLID))
464                 if self._cam.getLastImage() is not None:
465                         self._preview.SetMinSize((self._cam.getLastImage().GetWidth(), self._cam.getLastImage().GetHeight()))
466                         self.panel.Fit()
467                         dc.DrawBitmap(self._cam.getLastImage(), 0, 0)
468                 else:
469                         dc.Clear()