1 | ############################################################################ |
---|
2 | # |
---|
3 | # SAGE UI - A Graphical User Interface for SAGE |
---|
4 | # Copyright (C) 2005 Electronic Visualization Laboratory, |
---|
5 | # University of Illinois at Chicago |
---|
6 | # |
---|
7 | # All rights reserved. |
---|
8 | # |
---|
9 | # Redistribution and use in source and binary forms, with or without |
---|
10 | # modification, are permitted provided that the following conditions are met: |
---|
11 | # |
---|
12 | # * Redistributions of source code must retain the above copyright |
---|
13 | # notice, this list of conditions and the following disclaimer. |
---|
14 | # * Redistributions in binary form must reproduce the above |
---|
15 | # copyright notice, this list of conditions and the following disclaimer |
---|
16 | # in the documentation and/or other materials provided with the distribution. |
---|
17 | # * Neither the name of the University of Illinois at Chicago nor |
---|
18 | # the names of its contributors may be used to endorse or promote |
---|
19 | # products derived from this software without specific prior written permission. |
---|
20 | # |
---|
21 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
---|
22 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
---|
23 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
---|
24 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
---|
25 | # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
---|
26 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
---|
27 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
---|
28 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
---|
29 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
---|
30 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
---|
31 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
---|
32 | # |
---|
33 | # Direct questions, comments etc about SAGE UI to www.evl.uic.edu/cavern/forum |
---|
34 | # |
---|
35 | # Author: Ratko Jagodic |
---|
36 | # |
---|
37 | ############################################################################ |
---|
38 | |
---|
39 | |
---|
40 | import xmlrpclib, base64, sys, cStringIO, socket, pickle, copy, os.path, os |
---|
41 | from httplib import HTTPException |
---|
42 | import wx.lib.scrolledpanel |
---|
43 | from wx import ProgressDialog |
---|
44 | from globals import ConvertPath, Message, ChoiceDialog, appPanelColor, dialogColor |
---|
45 | import help #my own |
---|
46 | import preferences as prefs #my own |
---|
47 | from math import ceil |
---|
48 | from threading import * |
---|
49 | from misc.imsize import imagesize # reads the image header and gets the size from it |
---|
50 | import wx.lib.throbber as throb |
---|
51 | import urllib |
---|
52 | |
---|
53 | XMLRPC_PORT = "8800" |
---|
54 | FILE_GRABBER_PORT = "8801" |
---|
55 | FILE_SERVER_BIN_PORT = 8802 |
---|
56 | PREVIEW_SIZE = (150,150) |
---|
57 | DEFAULT_TIMEOUT = None # no timeout because server side processing might take a while |
---|
58 | |
---|
59 | # used so that we don't create two connections to the same server... no reason |
---|
60 | # keyed by IP address of the server (host) |
---|
61 | fileServers = {} |
---|
62 | def GetFileServer(host, port): |
---|
63 | global fileServers |
---|
64 | if fileServers.has_key(host): |
---|
65 | return fileServers[host] |
---|
66 | else: |
---|
67 | fs = FileServer(host, port) |
---|
68 | fileServers[host] = fs |
---|
69 | return fileServers[host] |
---|
70 | |
---|
71 | |
---|
72 | |
---|
73 | ### stuff needed in order to customize the sending... |
---|
74 | class MyConnection: |
---|
75 | def __init__(self, conn): |
---|
76 | self.conn = conn |
---|
77 | |
---|
78 | ### if data is bigger (>100KB), show the progress bar |
---|
79 | def send(self, data): |
---|
80 | dataLength = len(data) |
---|
81 | if dataLength > 100000: |
---|
82 | try: |
---|
83 | dlg = wx.ProgressDialog("Uploading File", "Uploading... (0/%.2f)"%(dataLength/1048576), |
---|
84 | style=wx.PD_SMOOTH | wx.PD_CAN_ABORT | |
---|
85 | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_AUTO_HIDE) |
---|
86 | chunkSize = 8192 |
---|
87 | for i in range (0, dataLength, chunkSize): #send chunks of 8192 bytes |
---|
88 | self.conn.send(data[i:i+chunkSize]) |
---|
89 | if not dlg.Update(ceil((float(i)/dataLength)*100), "Uploading... (%.2f/%.2f MB)"% |
---|
90 | (float(i)/1048576, float(dataLength)/1048576)): |
---|
91 | self.conn.close() |
---|
92 | break |
---|
93 | dlg.Destroy() |
---|
94 | except socket.error: |
---|
95 | dlg.Destroy() |
---|
96 | Message("Error while uploading file. This is most likely a server-side error and it should be possible to continue.", "Upload Failed") |
---|
97 | else: |
---|
98 | self.conn.send(data) |
---|
99 | |
---|
100 | ### overridden so that we can catch the exception when we press cancel during upload |
---|
101 | def getreply(self): |
---|
102 | ret = (None, None, None) |
---|
103 | try: |
---|
104 | ret = self.conn.getreply() |
---|
105 | except HTTPException: |
---|
106 | pass |
---|
107 | return ret |
---|
108 | |
---|
109 | |
---|
110 | def __getattr__(self, key): |
---|
111 | return getattr(self.conn, key) |
---|
112 | |
---|
113 | |
---|
114 | class MyTransport(xmlrpclib.Transport): |
---|
115 | def make_connection(self, host): |
---|
116 | conn = xmlrpclib.Transport.make_connection(self, host) |
---|
117 | return MyConnection(conn) |
---|
118 | |
---|
119 | |
---|
120 | |
---|
121 | |
---|
122 | class FileServer: |
---|
123 | |
---|
124 | def __init__(self, host, port): |
---|
125 | self.host = host #where the FileLibrary is running |
---|
126 | self.port = port |
---|
127 | self.connected = False |
---|
128 | self.Connect() |
---|
129 | |
---|
130 | |
---|
131 | # try to connect to the server |
---|
132 | # if we failed, set the flag |
---|
133 | # if we succeed, get the path where the images are stored so that we can send it to the app |
---|
134 | def Connect(self): |
---|
135 | if self.connected: return True |
---|
136 | |
---|
137 | print "\nConnecting to XMLRPC server at: http://"+str(self.host)+":"+self.port |
---|
138 | socket.setdefaulttimeout(DEFAULT_TIMEOUT) |
---|
139 | self.server = xmlrpclib.ServerProxy("http://"+str(self.host)+":"+self.port, transport=MyTransport()) |
---|
140 | #socket.setdefaulttimeout(3) |
---|
141 | try: |
---|
142 | self.connected = True |
---|
143 | global FILE_GRABBER_PORT |
---|
144 | FILE_GRABBER_PORT = self.server.TestConnection()[1] #just see if the connection opened correctly |
---|
145 | except: |
---|
146 | print "Could not connect to the file server at: "+str(self.host)+":"+self.port |
---|
147 | self.connected = False |
---|
148 | return False |
---|
149 | else: |
---|
150 | print "Connected to the XMLRPC server at: http://"+str(self.host)+":"+self.port |
---|
151 | return True |
---|
152 | |
---|
153 | |
---|
154 | def IsConnected(self): |
---|
155 | return self.Connect() |
---|
156 | |
---|
157 | |
---|
158 | # uploads the file to the file library and returns information about the file |
---|
159 | def UploadFile(self, fullPath): |
---|
160 | try: |
---|
161 | |
---|
162 | # get the file info first |
---|
163 | fileInfo = self.GetNewFileInfo(fullPath) |
---|
164 | if not fileInfo: return False # file type not supported |
---|
165 | else: (fileType, appName, fullRemotePath, size, params, fileExists) = fileInfo |
---|
166 | |
---|
167 | if fileExists: # if the file exists on the server, just show it (ie. dont send it) |
---|
168 | return (fileType, appName, fullRemotePath, size, params, fileExists) #no need to upload |
---|
169 | else: |
---|
170 | if self.__SendFile(fullPath): # did upload fail for some reason? |
---|
171 | return (fileType, appName, fullRemotePath, size, params, fileExists) |
---|
172 | else: |
---|
173 | return False # upload failed so whatever you are doing after this (ShowFile maybe), don't do it |
---|
174 | |
---|
175 | except socket.error: |
---|
176 | Message("Unable to upload file. There is no connection with the File Server.", "No connection") |
---|
177 | return False |
---|
178 | except xmlrpclib.ProtocolError: |
---|
179 | return False |
---|
180 | |
---|
181 | |
---|
182 | # gets the information about the file and the supporting app from the file library |
---|
183 | def GetNewFileInfo(self, fullPath): |
---|
184 | #c = wx.BusyCursor() |
---|
185 | (path, filename) = os.path.split(fullPath) |
---|
186 | filename = "_".join(filename.split()) |
---|
187 | |
---|
188 | # check if the file type is supported |
---|
189 | # if it is, it will get some data about the file (type, viewerApp ...) |
---|
190 | fileSize = os.stat(fullPath).st_size #get the size in bytes |
---|
191 | fileInfo = self.server.GetFileInfo( filename, fileSize ) |
---|
192 | if not fileInfo: |
---|
193 | #del c |
---|
194 | extension = os.path.splitext(filename)[1].lower() |
---|
195 | Message("File type <"+extension+"> not supported.", "Invalid File Type") |
---|
196 | return False |
---|
197 | |
---|
198 | (fileType, size, fullRemotePath, appName, params, fileExists) = fileInfo |
---|
199 | |
---|
200 | # prepare for showing the file (filename, path, size if necessary...) |
---|
201 | if fileType == "image": |
---|
202 | try: |
---|
203 | imsize = imagesize(fullPath) |
---|
204 | size = (imsize[1], imsize[0]) |
---|
205 | except: |
---|
206 | size = (-1,-1) |
---|
207 | #del c |
---|
208 | return (fileType, appName, fullRemotePath, size, params, fileExists) |
---|
209 | |
---|
210 | |
---|
211 | def __SendFile(self, fullPath): |
---|
212 | try: |
---|
213 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
---|
214 | s.connect((self.host, FILE_SERVER_BIN_PORT)) |
---|
215 | |
---|
216 | #convert to a filename with _ instead of empty spaces |
---|
217 | convertedFilename = "_".join(os.path.basename(fullPath).split()) |
---|
218 | |
---|
219 | # send the header first |
---|
220 | fileSize = os.path.getsize(fullPath) |
---|
221 | header = convertedFilename + " " + str(PREVIEW_SIZE[0]) + " " + str(PREVIEW_SIZE[1]) + \ |
---|
222 | " " + str(fileSize) + "\n" |
---|
223 | s.sendall(header) |
---|
224 | |
---|
225 | # show the progress dialog if file is bigger |
---|
226 | doDlg = False |
---|
227 | if fileSize > 100000: |
---|
228 | dlg = wx.ProgressDialog("Uploading File", "Uploading... (0/%.2f)"%(fileSize/1048576), |
---|
229 | style=wx.PD_SMOOTH | wx.PD_CAN_ABORT | |
---|
230 | wx.PD_ELAPSED_TIME | wx.PD_REMAINING_TIME | wx.PD_AUTO_HIDE) |
---|
231 | doDlg = True |
---|
232 | |
---|
233 | # send the file data |
---|
234 | f=open(fullPath, "rb") |
---|
235 | t = 0 |
---|
236 | for line in f: |
---|
237 | s.sendall(line) |
---|
238 | t += len(line) |
---|
239 | |
---|
240 | # update the dialog if needed |
---|
241 | if doDlg and not dlg.Update(ceil((float(t)/fileSize)*100), "Uploading... (%.2f/%.2f MB)"% |
---|
242 | (float(t)/1048576, float(fileSize)/1048576)): |
---|
243 | s.close() |
---|
244 | f.close() |
---|
245 | dlg.Destroy() |
---|
246 | return False |
---|
247 | |
---|
248 | if doDlg: dlg.Destroy() |
---|
249 | |
---|
250 | s.sendall("\n") # because the other side is reading lines |
---|
251 | f.close() |
---|
252 | s.recv(1) |
---|
253 | except: |
---|
254 | print "Error sending file to File Server: ", sys.exc_info()[0], sys.exc_info()[1] |
---|
255 | if doDlg: dlg.Destroy() |
---|
256 | Message("Could not upload file "+str(os.path.basename(fullPath)), "Error") |
---|
257 | return False |
---|
258 | |
---|
259 | return True |
---|
260 | |
---|
261 | #return self.server.UploadFile( convertedFilename, data, PREVIEW_SIZE ) |
---|
262 | |
---|
263 | |
---|
264 | |
---|
265 | ### the base class for accepting the dropped files |
---|
266 | class FileDropTarget(wx.PyDropTarget): |
---|
267 | def __init__(self, host=None, port=None): |
---|
268 | |
---|
269 | # set up data objects (Composite works correctly on Windows) |
---|
270 | if "__WXMSW__" in wx.PlatformInfo: |
---|
271 | self.do = wx.DataObjectComposite() |
---|
272 | self.filedo = wx.FileDataObject() |
---|
273 | self.textdo = wx.TextDataObject() |
---|
274 | self.do.Add(self.textdo) |
---|
275 | self.do.Add(self.filedo, True) |
---|
276 | else: |
---|
277 | self.do = wx.FileDataObject() |
---|
278 | |
---|
279 | wx.PyDropTarget.__init__(self, self.do) |
---|
280 | |
---|
281 | # delayed server object creation for the CanvasDropTarget |
---|
282 | # when a file is dropped, we dont know which library to upload the file to so |
---|
283 | # that's when we decide which library to connect to |
---|
284 | if host!=None and port!=None: |
---|
285 | self.serverObj = GetFileServer(host, port) |
---|
286 | self.server = self.serverObj.server |
---|
287 | |
---|
288 | |
---|
289 | # this must be called if you passed in None to the constructor |
---|
290 | def SetLibrary(self, host, port): |
---|
291 | self.serverObj = GetFileServer(host, port) |
---|
292 | self.server = self.serverObj.server |
---|
293 | |
---|
294 | |
---|
295 | def GetServer(self): |
---|
296 | return self.server |
---|
297 | |
---|
298 | |
---|
299 | def OnData(self, x, y, d): |
---|
300 | if not self.serverObj.IsConnected(): |
---|
301 | Message("Unable to upload/show file. There is no connection with the File Server.", "No connection") |
---|
302 | return False |
---|
303 | |
---|
304 | self.GetData() #get the dropped data |
---|
305 | |
---|
306 | if "__WXMSW__" in wx.PlatformInfo: |
---|
307 | if self.textdo.GetTextLength() > 1: # empty text has length = 1 |
---|
308 | droppedText = self.textdo.GetText() |
---|
309 | self.textdo.SetText("") #reset the text |
---|
310 | return self.__DownloadURL(droppedText) |
---|
311 | else: |
---|
312 | files = self.filedo.GetFilenames()[0] #extract the filenames from the dropped data |
---|
313 | #files = unicode(files, "utf_8") |
---|
314 | if os.path.isdir(files): |
---|
315 | Message("You cannot upload directories. Only individual files.", "Upload Failed") |
---|
316 | return False |
---|
317 | else: |
---|
318 | return files |
---|
319 | else: |
---|
320 | filenames = self.do.GetFilenames() |
---|
321 | if not filenames: return False |
---|
322 | files = filenames[0] #extract the filenames from the dropped data |
---|
323 | if os.path.isdir(files): |
---|
324 | Message("You cannot upload directories. Only individual files.", "Upload Failed") |
---|
325 | return False |
---|
326 | else: |
---|
327 | return files |
---|
328 | |
---|
329 | |
---|
330 | # checks if the file the url is pointing to is valid, |
---|
331 | # if so, it downloads it and returns the local path to it |
---|
332 | def __DownloadURL(self, url): |
---|
333 | if os.path.isfile(url): # if it's a local file so just return it's path |
---|
334 | return url |
---|
335 | elif url.startswith("http://") or url.startswith("ftp://"): #if it's a url |
---|
336 | filename = os.path.basename(url) |
---|
337 | if self.server.GetFileType(filename): # is the file type even supported? |
---|
338 | urllib.urlcleanup() #cleanup the cache |
---|
339 | urllib.urlretrieve(url, filename) |
---|
340 | return os.path.abspath(filename) |
---|
341 | else: # file type not supported |
---|
342 | extension = os.path.splitext(filename)[1].lower() |
---|
343 | Message("File type <"+extension+"> not supported.", "Invalid File Type") |
---|
344 | return False |
---|
345 | else: |
---|
346 | return False |
---|
347 | |
---|
348 | |
---|
349 | |
---|
350 | class CanvasDropTarget(FileDropTarget): |
---|
351 | def __init__(self, canvas): |
---|
352 | self.canvas = canvas |
---|
353 | self.sageGate = self.canvas.sageGate |
---|
354 | self.sageHost = self.sageGate.sageHost #for the filegrabber |
---|
355 | self.lastX = 0 |
---|
356 | self.lastY = 0 |
---|
357 | |
---|
358 | FileDropTarget.__init__(self) # dont provide library location right away since we will let the user choose that each time |
---|
359 | |
---|
360 | |
---|
361 | def OnDrop(self, x, y): |
---|
362 | self.lastX = self.canvas.ToSAGECoordsX(x, 0) |
---|
363 | self.lastY = self.canvas.ToSAGECoordsY(y, 0) |
---|
364 | return True |
---|
365 | |
---|
366 | |
---|
367 | def OnData(self, x, y, d): # just upload the file to the libary on the machine we are connected to |
---|
368 | self.SetLibrary(self.sageHost, XMLRPC_PORT) |
---|
369 | filePath = FileDropTarget.OnData(self,x,y,d) |
---|
370 | if filePath: |
---|
371 | uploadResult = self.serverObj.UploadFile(filePath) |
---|
372 | else: return False |
---|
373 | |
---|
374 | if uploadResult: #if upload succeeded |
---|
375 | (fileType, appName, fullRemotePath, size, params, fileExists) = uploadResult |
---|
376 | self.ShowFile(fileType, appName, fullRemotePath, size, params, True) |
---|
377 | return d |
---|
378 | |
---|
379 | |
---|
380 | # run the show file in a thread |
---|
381 | def ShowFile(self, fileType, fullPath, appName, params, size, doPrep=True): |
---|
382 | t = Thread(target=self.DoShowFile, args=(fileType, fullPath, appName, params, size, doPrep)) |
---|
383 | t.start() |
---|
384 | |
---|
385 | |
---|
386 | def DoShowFile(self, fileType, appName, fullRemotePath, size, params, doPrep=True): |
---|
387 | # first, make sure that the file exists |
---|
388 | if doPrep: |
---|
389 | prepResult = self.server.PrepareFile(fullRemotePath, self.sageHost) |
---|
390 | if not prepResult: |
---|
391 | Message("Unable to show file because the transfer of file from the FileServer to the SAGE machine failed.", "Error") |
---|
392 | return |
---|
393 | else: |
---|
394 | fullPath = prepResult #the path was returned by the FileGrabber |
---|
395 | |
---|
396 | totalDisplayWidth = self.canvas.tiledDisplayWidth |
---|
397 | totalDisplayHeight = self.canvas.tiledDisplayHeight |
---|
398 | |
---|
399 | if fileType == "image": |
---|
400 | imageWidth = size[0] |
---|
401 | imageHeight = size[1] |
---|
402 | imageAspectRatio = float( imageWidth/imageHeight ) |
---|
403 | |
---|
404 | # figure out if the image is going to fit, if not, reposition and resize it as necessary |
---|
405 | # first resize if necessary |
---|
406 | widthRatio = float(totalDisplayWidth / float(imageWidth)) |
---|
407 | heightRatio = float(totalDisplayHeight / float(imageHeight)) |
---|
408 | if widthRatio < 1 or heightRatio < 1: |
---|
409 | if widthRatio > heightRatio: #we'll resize based on height |
---|
410 | resizeRatio = heightRatio |
---|
411 | else: |
---|
412 | resizeRatio = widthRatio #we'll resize based on width |
---|
413 | imageWidth = int(imageWidth * resizeRatio) |
---|
414 | imageHeight = int(imageHeight * resizeRatio) |
---|
415 | |
---|
416 | # now reposition |
---|
417 | cornerX = self.lastX - int(imageWidth/2) |
---|
418 | cornerY = self.lastY - int(imageHeight/2) |
---|
419 | |
---|
420 | # is the image off the screen up or down? |
---|
421 | if (totalDisplayHeight - cornerY) < imageHeight: |
---|
422 | cornerY = int(totalDisplayHeight - imageHeight) |
---|
423 | elif cornerY < 0: |
---|
424 | cornerY = 0 |
---|
425 | |
---|
426 | # is the image off the screen left or right? |
---|
427 | if (totalDisplayWidth - cornerX) < imageWidth: |
---|
428 | cornerX = int(totalDisplayWidth - imageWidth) |
---|
429 | elif cornerX < 0: |
---|
430 | cornerX = 0 |
---|
431 | res = self.sageGate.executeApp(appName, pos=(cornerX, cornerY), size=(imageWidth,imageHeight), optionalArgs = fullRemotePath+" "+str(imageWidth)+" "+str(imageHeight)+" "+params) |
---|
432 | |
---|
433 | elif fileType == "video" or fileType == "pdf": |
---|
434 | res = self.sageGate.executeApp(appName, pos=(self.lastX, self.lastY), optionalArgs=params+" "+fullRemotePath) |
---|
435 | |
---|
436 | else: #for other types |
---|
437 | res = self.sageGate.executeApp(appName, optionalArgs=fullRemotePath+" "+params) |
---|
438 | |
---|
439 | if res == -1: |
---|
440 | Message("Application not started. Either application failed, the application launcher is not running or the application <<"+appName+">> is not configured in application launcher.", "Application Launch Failed") |
---|
441 | |
---|
442 | |
---|
443 | |
---|
444 | |
---|
445 | class LibraryDropTarget(FileDropTarget): |
---|
446 | def __init__(self, parent, host, port=XMLRPC_PORT): |
---|
447 | self.parent = parent |
---|
448 | FileDropTarget.__init__(self, host, port) |
---|
449 | |
---|
450 | |
---|
451 | def OnData(self, x, y, d): |
---|
452 | filePath = FileDropTarget.OnData(self,x,y,d) |
---|
453 | if not filePath: return |
---|
454 | uploadResult = self.serverObj.UploadFile(filePath) |
---|
455 | if uploadResult: #if upload succeeded |
---|
456 | (fileType, appName, fullRemotePath, size, params, fileExists) = uploadResult |
---|
457 | if not fileExists: #only add a file to the tree if the file doesnt exist yet |
---|
458 | self.parent.OnNewFile(fullRemotePath, fileType) |
---|
459 | else: |
---|
460 | Message("File already exists in the file library.", "Duplicate Found") |
---|
461 | |
---|
462 | |
---|
463 | |
---|
464 | dirTextColor = wx.Colour(204, 153, 102)#250,50,50) |
---|
465 | |
---|
466 | class FileLibrary(wx.Frame): |
---|
467 | def __init__(self, parent, parentPos): |
---|
468 | self.canvas = parent |
---|
469 | self.sageGate = self.canvas.sageGate |
---|
470 | self.sageHost = self.sageGate.sageHost #for the filegrabber |
---|
471 | |
---|
472 | res = LibraryChoiceDialog(parent).ShowDialog() |
---|
473 | if not res: |
---|
474 | return #user pressed cancel when choosing the library |
---|
475 | else: |
---|
476 | (self.libName, self.libHost) = res |
---|
477 | |
---|
478 | |
---|
479 | self.dt = LibraryDropTarget(self, self.libHost, XMLRPC_PORT) |
---|
480 | self.server = self.dt.GetServer() |
---|
481 | self.serverObj = self.dt.serverObj |
---|
482 | |
---|
483 | # we must be connected in order to see the library |
---|
484 | if not self.serverObj.IsConnected(): |
---|
485 | Message("Unable to access file library. There is no connection with the File Server.", "No connection") |
---|
486 | else: |
---|
487 | x,y=parentPos |
---|
488 | wx.Frame.__init__(self, parent, help.FILE_LIBRARY, "File Library - "+self.libName, pos=(x+200, y+200), size=(550,550)) |
---|
489 | self.previewSize = (150,150) |
---|
490 | self.panel = wx.lib.scrolledpanel.ScrolledPanel(self, wx.ID_ANY) |
---|
491 | self.__SetupLayout() |
---|
492 | |
---|
493 | # so that wherever we press F1 key it will bring up help for that window |
---|
494 | # the help.ShowHelp function will check for the correct key press |
---|
495 | self.Bind(wx.EVT_CHAR, help.ShowHelp) |
---|
496 | |
---|
497 | # make the tree |
---|
498 | self.tree = wx.TreeCtrl(self.panel, wx.ID_ANY, style=wx.TR_NO_LINES | wx.TR_SINGLE | wx.TR_HAS_BUTTONS) |
---|
499 | self.tree.SetDropTarget(self.dt) |
---|
500 | self.root = self.tree.AddRoot("Files") |
---|
501 | self.imList = wx.ImageList(16,16) |
---|
502 | self.tree.SetImageList(self.imList) #image list must be created in order to have DnD capabiliy... weird |
---|
503 | self.MakeTree() |
---|
504 | self.tree.Bind(wx.EVT_RIGHT_DOWN, self.OnRightClick) |
---|
505 | self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelChanged) |
---|
506 | self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnShowButton) |
---|
507 | self.tree.Bind(wx.EVT_TREE_BEGIN_DRAG, self.OnBeginDrag) |
---|
508 | self.tree.Bind(wx.EVT_TREE_END_DRAG, self.OnEndDrag) |
---|
509 | self.tree.Bind(wx.EVT_ENTER_WINDOW, self.OnMouseEnter) |
---|
510 | self.tree.Bind(wx.EVT_LEAVE_WINDOW, self.OnMouseLeave) |
---|
511 | self.tree.Bind(wx.EVT_MOTION, self.OnMouseMove) |
---|
512 | self.currentItemId = None |
---|
513 | |
---|
514 | # set the colors |
---|
515 | self.tree.SetBackgroundColour(appPanelColor) |
---|
516 | self.tree.SetForegroundColour(wx.WHITE) |
---|
517 | self.panel.SetBackgroundStyle(wx.BG_STYLE_COLOUR) |
---|
518 | self.panel.SetBackgroundColour(dialogColor) |
---|
519 | self.SetForegroundColour(wx.WHITE) |
---|
520 | |
---|
521 | # add main things to the sizer |
---|
522 | self.mainSizer.Add(self.tree, 1, wx.EXPAND) |
---|
523 | self.mainSizer.Add(self.vertLine, 0, wx.EXPAND ) |
---|
524 | self.mainSizer.Add(self.vertSizer, 0, wx.EXPAND) |
---|
525 | |
---|
526 | # attach the sizer to the panel |
---|
527 | self.panel.SetSizer(self.mainSizer) |
---|
528 | self.panel.SetAutoLayout(1) |
---|
529 | self.panel.SetupScrolling() |
---|
530 | |
---|
531 | wx.Frame.Show(self) |
---|
532 | |
---|
533 | |
---|
534 | def __SetupLayout(self): |
---|
535 | self.mainSizer = wx.BoxSizer(wx.HORIZONTAL) |
---|
536 | self.vertSizer = wx.BoxSizer(wx.VERTICAL) |
---|
537 | self.horSizer = wx.BoxSizer(wx.HORIZONTAL) |
---|
538 | |
---|
539 | self.vertLine = wx.StaticLine(self.panel, style=wx.LI_VERTICAL) |
---|
540 | |
---|
541 | self.showBtn = wx.Button(self.panel, wx.ID_ANY, "Show") |
---|
542 | self.showBtn.Bind(wx.EVT_BUTTON, self.OnShowButton) |
---|
543 | self.showBtn.Disable() |
---|
544 | self.__SetupButton(self.showBtn) |
---|
545 | |
---|
546 | self.shareCheckBox = wx.CheckBox(self.panel, wx.ID_ANY, "Shareable app? (uses sageBridge)") |
---|
547 | self.shareCheckBox.SetForegroundColour(wx.WHITE) |
---|
548 | self.shareCheckBox.SetBackgroundStyle(wx.BG_STYLE_COLOUR) |
---|
549 | self.shareCheckBox.SetBackgroundColour(dialogColor) |
---|
550 | |
---|
551 | self.dxtCheckBox = wx.CheckBox(self.panel, wx.ID_ANY, "Use DXT (faster, lower quality)") |
---|
552 | self.dxtCheckBox.SetValue(True) |
---|
553 | self.dxtCheckBox.SetForegroundColour(wx.WHITE) |
---|
554 | self.dxtCheckBox.SetBackgroundStyle(wx.BG_STYLE_COLOUR) |
---|
555 | self.dxtCheckBox.SetBackgroundColour(dialogColor) |
---|
556 | |
---|
557 | self.refreshBtn = wx.Button(self.panel, wx.ID_ANY, "Refresh") |
---|
558 | self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshTree) |
---|
559 | self.__SetupButton(self.refreshBtn, smaller=True) |
---|
560 | |
---|
561 | self.browseBtn = wx.Button(self.panel, wx.ID_ANY, "Browse") |
---|
562 | self.browseBtn.Bind(wx.EVT_BUTTON, self.BrowseFiles) |
---|
563 | self.__SetupButton(self.browseBtn, smaller=True) |
---|
564 | |
---|
565 | self.helpBtn = wx.Button(self.panel, help.FILE_LIBRARY, "Help") |
---|
566 | self.helpBtn.Bind(wx.EVT_BUTTON, help.ShowHelp) |
---|
567 | self.__SetupButton(self.helpBtn, smaller=True) |
---|
568 | |
---|
569 | self.searchField = wx.TextCtrl(self.panel, wx.ID_ANY, style=wx.TE_PROCESS_ENTER) |
---|
570 | self.searchField.Bind(wx.EVT_TEXT_ENTER, self.OnSearch) |
---|
571 | self.searchField.Bind(wx.EVT_TEXT, self.OnSearch) |
---|
572 | tTip = wx.ToolTip("Search as you type...\npress ENTER to search again") |
---|
573 | tTip.SetDelay(200) |
---|
574 | self.searchMsg = wx.StaticText(self.panel, wx.ID_ANY, "", style=wx.ALIGN_CENTRE) |
---|
575 | self.searchMsg.SetForegroundColour(wx.WHITE) |
---|
576 | self.searchField.SetToolTip(tTip) |
---|
577 | self.searchBtn = wx.Button(self.panel, wx.ID_ANY, "Find Next") |
---|
578 | self.searchBtn.Bind(wx.EVT_BUTTON, self.OnSearch) |
---|
579 | self.__SetupButton(self.searchBtn) |
---|
580 | tTip = wx.ToolTip("Same as pressing ENTER in the search field") |
---|
581 | tTip.SetDelay(200) |
---|
582 | self.searchBtn.SetToolTip(tTip) |
---|
583 | |
---|
584 | self.horSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5) |
---|
585 | self.horSizer.Add(self.browseBtn, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, border=10) |
---|
586 | self.horSizer.Add(self.helpBtn, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5) |
---|
587 | |
---|
588 | self.no_preview = wx.Image(ConvertPath("images/no_preview.png")).ConvertToBitmap() |
---|
589 | self.retrieving_preview = wx.Image(ConvertPath("images/retrieving_preview.png")).ConvertToBitmap() |
---|
590 | self.currentImage = wx.StaticBitmap(self.panel, wx.ID_ANY, self.no_preview, size=self.previewSize) |
---|
591 | |
---|
592 | self.vertSizer.Add(self.horSizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_TOP | wx.TOP | wx.EXPAND, border=10) |
---|
593 | self.vertSizer.Add(wx.StaticLine(self.panel, style=wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.ALIGN_TOP | wx.TOP, border=5) |
---|
594 | self.vertSizer.AddSpacer((1,1),1) |
---|
595 | self.vertSizer.Add(self.currentImage, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP, border=20) |
---|
596 | self.vertSizer.Add(self.showBtn, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5) |
---|
597 | self.vertSizer.Add(self.shareCheckBox, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5) |
---|
598 | self.vertSizer.Add(self.dxtCheckBox, 0, wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5) |
---|
599 | self.vertSizer.AddSpacer((1,1),1) |
---|
600 | self.vertSizer.Add(wx.StaticLine(self.panel, style=wx.LI_HORIZONTAL), 0, wx.EXPAND | wx.ALIGN_BOTTOM | wx.BOTTOM, border=10) |
---|
601 | self.vertSizer.Add(self.searchField, 0, wx.ALIGN_BOTTOM | wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5) |
---|
602 | self.vertSizer.Add(self.searchMsg, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) |
---|
603 | self.vertSizer.Add(self.searchBtn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, border=10) |
---|
604 | |
---|
605 | |
---|
606 | def __SetupButton(self, btn, smaller=False): |
---|
607 | if "__WXMAC__" not in wx.PlatformInfo: |
---|
608 | if smaller: |
---|
609 | sz = btn.GetSize() |
---|
610 | btn.SetMaxSize((sz[0], sz[1]-5)) |
---|
611 | btn.SetBackgroundColour(wx.Colour(153, 255, 255)) |
---|
612 | btn.SetForegroundColour(wx.BLACK) |
---|
613 | |
---|
614 | |
---|
615 | def MakeTree(self): |
---|
616 | self._alreadyFound = [] # a list of already found itemIds |
---|
617 | self._searchString = "" # a current search string |
---|
618 | self.branches = {} #treeItemIds keyed by file types |
---|
619 | self.favoritesBranches = {} #treeItemIds keyed by file types |
---|
620 | |
---|
621 | # get the newest list of files from the File Server |
---|
622 | try: |
---|
623 | fileHash = self.server.GetFiles() |
---|
624 | except socket.error: |
---|
625 | Message("Unable to retrieve the file list. No connection with the File Server.", "No Connection") |
---|
626 | return |
---|
627 | |
---|
628 | # make the parents (main folders) |
---|
629 | for fType, tpl in fileHash.iteritems(): |
---|
630 | tpl[2].sort(lambda x, y: cmp(x.lower(), y.lower())) |
---|
631 | self.MakeBranch(fType, tpl[0]) #include the dir path |
---|
632 | self.MakeNodes(self.branches[fType], tpl) |
---|
633 | |
---|
634 | # make the favorites folder |
---|
635 | self.MakeBranch("favorites", None) #None dirPath (virtual directory) |
---|
636 | for fType in self.branches.iterkeys(): |
---|
637 | if fType != "favorites": |
---|
638 | self.MakeFavoritesBranch(fType) |
---|
639 | |
---|
640 | self.LoadFavorites() |
---|
641 | |
---|
642 | # bind events and finish the tree |
---|
643 | self.tree.Expand(self.root) |
---|
644 | |
---|
645 | |
---|
646 | ### recursively go through the data retrieved from server.GetFiles |
---|
647 | ### and recreate the directory structure as a tree |
---|
648 | def MakeNodes(self, parentId, tpl): |
---|
649 | (dirPath, dirs, files, fileType) = tpl |
---|
650 | sortedDirs = dirs.keys() |
---|
651 | sortedDirs.sort(lambda x, y: cmp(x.lower(), y.lower())) |
---|
652 | |
---|
653 | for dirName in sortedDirs: |
---|
654 | dirTpl = dirs[dirName] |
---|
655 | dirId = self.tree.AppendItem(parentId, dirName) |
---|
656 | dirObj = Node(dirName, fileType, dirPath, False, False) |
---|
657 | self.tree.SetItemTextColour(dirId, dirTextColor) |
---|
658 | self.tree.SetPyData(dirId, dirObj) |
---|
659 | self.MakeNodes(dirId, dirTpl) |
---|
660 | |
---|
661 | files.sort(lambda x, y: cmp(x.lower(), y.lower())) |
---|
662 | for f in files: |
---|
663 | fileObj = Node(f, fileType, dirPath, True, False) |
---|
664 | fId = self.tree.AppendItem(parentId, f) |
---|
665 | self.tree.SetPyData(fId, fileObj) |
---|
666 | |
---|
667 | |
---|
668 | ### opens a file browser for your local machine so that you can add files to the library that way |
---|
669 | def BrowseFiles(self, event=None): |
---|
670 | dlg = wx.FileDialog(self, str("Add to the file library"), defaultDir=os.getcwd(), style=wx.OPEN | wx.FILE_MUST_EXIST) |
---|
671 | if dlg.ShowModal() == wx.ID_OK: |
---|
672 | uploadResult = self.serverObj.UploadFile(dlg.GetPath()) # do the checks, upload the file... |
---|
673 | if uploadResult: #if upload succeeded |
---|
674 | (fileType, appName, fullRemotePath, size, params, fileExists) = uploadResult |
---|
675 | if not fileExists: #only add a file to the tree if the file doesnt exist yet |
---|
676 | self.OnNewFile(fullRemotePath, fileType) |
---|
677 | else: |
---|
678 | Message("File already exists in the file library.", "Duplicate Found") |
---|
679 | dlg.Destroy() |
---|
680 | |
---|
681 | |
---|
682 | # called when a new file is dropped on the file library |
---|
683 | def OnNewFile(self, fullRemotePath, fileType): |
---|
684 | def AlreadyThere(filename, type): |
---|
685 | (siblingId, cookie) = self.tree.GetFirstChild(self.branches[type]) |
---|
686 | while siblingId.IsOk(): |
---|
687 | if self.tree.GetPyData(siblingId).GetName() == filename: |
---|
688 | return True # no need to add to favorites since it already exists in there |
---|
689 | siblingId = self.tree.GetNextSibling(siblingId) |
---|
690 | return False #if we got here that means that there are no files with the same name already |
---|
691 | |
---|
692 | |
---|
693 | (path, filename) = os.path.split(fullRemotePath) |
---|
694 | if AlreadyThere(filename, fileType): return # check if the file already exists in the tree... if it does no need to update it |
---|
695 | itemId = self.tree.AppendItem(self.branches[fileType], filename) |
---|
696 | self.tree.SetPyData(itemId, Node(filename, fileType, path, True, False)) |
---|
697 | |
---|
698 | |
---|
699 | def MakeBranch(self, fType, path): |
---|
700 | if not self.branches.has_key(fType): |
---|
701 | self.branches[fType] = self.tree.AppendItem(self.root, fType) |
---|
702 | self.tree.SetItemTextColour(self.branches[fType], dirTextColor) |
---|
703 | self.tree.SetPyData(self.branches[fType], Node("", fType, path, False, fType=="favorites", True)) |
---|
704 | |
---|
705 | |
---|
706 | def MakeFavoritesBranch(self, fType): |
---|
707 | if not self.favoritesBranches.has_key(fType): |
---|
708 | self.favoritesBranches[fType] = self.tree.AppendItem(self.branches["favorites"], fType) |
---|
709 | self.tree.SetItemTextColour(self.favoritesBranches[fType], dirTextColor) |
---|
710 | self.tree.SetPyData(self.favoritesBranches[fType], Node(fType, fType, "", False, True, True)) |
---|
711 | |
---|
712 | |
---|
713 | def RefreshTree(self, event=None): #does not refresh favorites |
---|
714 | self.tree.CollapseAndReset(self.root) |
---|
715 | self.MakeTree() |
---|
716 | self.currentImage.SetBitmap(self.no_preview) |
---|
717 | |
---|
718 | |
---|
719 | def OnSearch(self, event): |
---|
720 | self.searchMsg.SetLabel("") |
---|
721 | if event.GetEventType() == wx.EVT_TEXT_ENTER.evtType[0] or event.GetEventType() == wx.EVT_BUTTON.evtType[0]: |
---|
722 | selectedItemId = self.tree.GetSelection() |
---|
723 | self._alreadyFound.append(selectedItemId) |
---|
724 | elif event.GetEventType() == wx.EVT_TEXT.evtType[0]: |
---|
725 | self._alreadyFound = [] #if the user changed the search string, restart the search |
---|
726 | |
---|
727 | # start the search |
---|
728 | resultItemId = self.SearchAndSelect(self.root, self.searchField.GetValue()) |
---|
729 | if resultItemId != None and resultItemId.IsOk(): |
---|
730 | self.tree.SelectItem(resultItemId, True) |
---|
731 | else: |
---|
732 | if resultItemId == None: |
---|
733 | self.searchMsg.SetLabel("") # display nothing for the empty string |
---|
734 | else: |
---|
735 | self.searchMsg.SetLabel("Nothing Found.") |
---|
736 | |
---|
737 | |
---|
738 | # search through a tree recursively |
---|
739 | def SearchAndSelect(self, root, searchString): |
---|
740 | if len(searchString) == 0: |
---|
741 | self._alreadyFound = [] |
---|
742 | return #dont search for an empty string |
---|
743 | |
---|
744 | (itemId, cookie) = self.tree.GetFirstChild( root ) |
---|
745 | while itemId.IsOk(): |
---|
746 | itemData = self.tree.GetPyData(itemId) |
---|
747 | if itemData.GetType() == "favorites": #skip the favorites |
---|
748 | (itemId, cookie) = self.tree.GetNextChild( root, cookie ) |
---|
749 | continue |
---|
750 | if searchString.lower() in itemData.GetName().lower(): |
---|
751 | if not itemId in self._alreadyFound: |
---|
752 | self._alreadyFound.append(itemId) |
---|
753 | return itemId |
---|
754 | if self.tree.ItemHasChildren( itemId ): |
---|
755 | itemId = self.SearchAndSelect( itemId, searchString ) |
---|
756 | if itemId.IsOk(): |
---|
757 | return itemId |
---|
758 | (itemId, cookie) = self.tree.GetNextChild( root, cookie ) |
---|
759 | |
---|
760 | return itemId |
---|
761 | |
---|
762 | |
---|
763 | def OnBeginDrag(self, evt): |
---|
764 | self.draggedItemId = evt.GetItem() |
---|
765 | itemData = self.tree.GetPyData(self.draggedItemId) |
---|
766 | if itemData != None and itemData.IsFile() and not itemData.IsFavorite(): |
---|
767 | evt.Allow() |
---|
768 | |
---|
769 | |
---|
770 | def OnEndDrag(self, evt): |
---|
771 | onItemId = evt.GetItem() #, flags = self.tree.HitTest(evt.GetPoint()) |
---|
772 | if onItemId.IsOk(): #make sure that we dropped it on a legal spot |
---|
773 | onItemData = self.tree.GetPyData(onItemId) |
---|
774 | draggedItemData = self.tree.GetPyData(self.draggedItemId) |
---|
775 | draggedFilename = draggedItemData.GetName() |
---|
776 | draggedType = draggedItemData.GetType() |
---|
777 | |
---|
778 | if onItemData == None: # files can't be dropped on the root nor can we drag favorites |
---|
779 | return |
---|
780 | |
---|
781 | elif onItemData.IsFavorite(): # we dropped it in the "favorites" part |
---|
782 | if prefs.favorite_files.AlreadyFavorite(draggedItemData): return # check if the file already exists in favs |
---|
783 | favItemId = self.tree.AppendItem(self.favoritesBranches[draggedType], draggedFilename) |
---|
784 | favItemData = copy.copy(draggedItemData) |
---|
785 | favItemData.SetFavorite(True) #we use the same data from the original file but mark it as a favorite |
---|
786 | self.tree.SetPyData(favItemId, favItemData) |
---|
787 | prefs.favorite_files.AddFavorite(favItemData) |
---|
788 | |
---|
789 | elif onItemData.GetType() == draggedType: # dropped it in a non-favorites area |
---|
790 | parentId = onItemId |
---|
791 | newFullPath = os.path.join(onItemData.GetFullPath(), draggedFilename) |
---|
792 | oldFullPath = draggedItemData.GetFullPath() |
---|
793 | if onItemData.IsFile(): #if we dropped it on a file pretend it was the directory above |
---|
794 | parentId = self.tree.GetItemParent(onItemId) |
---|
795 | parentData = self.tree.GetPyData(parentId) |
---|
796 | newFullPath = os.path.join( parentData.GetFullPath(), draggedFilename) |
---|
797 | if newFullPath == oldFullPath: #if the file was dropped within the same dir, do nothing |
---|
798 | return |
---|
799 | try: |
---|
800 | res = self.server.MoveFile(oldFullPath, newFullPath) |
---|
801 | except socket.error: |
---|
802 | Message("Error moving file. No connection with the File Server.", "No Connection") |
---|
803 | else: |
---|
804 | if res != True: |
---|
805 | Message("Error occured while moving the file. Does the file exist?", "Move Unsuccessful") |
---|
806 | self.RefreshTree() |
---|
807 | else: # move successful, rearrange the tree |
---|
808 | self.MoveNode(parentId, self.draggedItemId, newFullPath, draggedType) |
---|
809 | |
---|
810 | |
---|
811 | def MoveNode(self, parentId, oldItemId, newFullPath, fileType): |
---|
812 | self.tree.Delete(oldItemId) #first delete the old node |
---|
813 | (path, filename) = os.path.split(newFullPath) |
---|
814 | itemId = self.tree.AppendItem(parentId, filename) |
---|
815 | self.tree.SetPyData(itemId, Node(filename, fileType, path, True, False)) |
---|
816 | |
---|
817 | |
---|
818 | def LoadFavorites(self): #must be done after the favorites branches have been built already |
---|
819 | favoriteFilesData = prefs.favorite_files.GetFavorites() |
---|
820 | if not favoriteFilesData: return |
---|
821 | |
---|
822 | # insert the nodes into the tree |
---|
823 | for type, items in favoriteFilesData.iteritems(): |
---|
824 | if self.favoritesBranches.has_key(type): |
---|
825 | for itemData in items: |
---|
826 | itemId = self.tree.AppendItem(self.favoritesBranches[type], itemData.GetName()) |
---|
827 | self.tree.SetPyData(itemId, itemData) |
---|
828 | |
---|
829 | |
---|
830 | def OnMouseEnter(self, evt): |
---|
831 | if evt.Dragging(): |
---|
832 | self.__DestroyPopup() |
---|
833 | evt.Skip() |
---|
834 | |
---|
835 | |
---|
836 | def OnMouseLeave(self, evt): |
---|
837 | self.__DestroyPopup() |
---|
838 | |
---|
839 | |
---|
840 | def __DestroyPopup(self): |
---|
841 | if hasattr(self, "pd"): self.pd.Destroy(); del self.pd |
---|
842 | self.currentItemId = None |
---|
843 | |
---|
844 | |
---|
845 | # when we move a mouse over a file, display its metadata |
---|
846 | def OnMouseMove(self, evt): |
---|
847 | if evt.Dragging(): |
---|
848 | evt.Skip() |
---|
849 | pt = evt.GetPosition() |
---|
850 | itemId, flags = self.tree.HitTest(pt) |
---|
851 | if not itemId.IsOk(): |
---|
852 | self.__DestroyPopup() |
---|
853 | return |
---|
854 | self.ShowMetadata(itemId) |
---|
855 | |
---|
856 | # shows the metadata for a given itemId inside a popup |
---|
857 | def ShowMetadata(self, itemId): |
---|
858 | itemData = self.tree.GetPyData(itemId) |
---|
859 | |
---|
860 | if itemData == None: |
---|
861 | self.__DestroyPopup() |
---|
862 | return #don't do anything if the user moved over the root |
---|
863 | |
---|
864 | # get the metadata now if it's a file we are over |
---|
865 | if itemData.IsFile(): |
---|
866 | if self.currentItemId == itemId: |
---|
867 | return #we already have a tooltip for this one |
---|
868 | self.__DestroyPopup() |
---|
869 | self.currentItemId = itemId |
---|
870 | metadata = self.server.GetMetadata(itemData.GetFullPath()) |
---|
871 | if metadata: |
---|
872 | md = itemData.GetName()+"\n"+metadata |
---|
873 | self.pd = PopupDialog(self.tree)#popup.PopupControl(self.tree)#, metadata) |
---|
874 | text = wx.StaticText(self.pd, wx.ID_ANY, md, pos=(2,2))# etadata.lstrip()) |
---|
875 | self.pd.SetContent(text) |
---|
876 | self.pd.Display() |
---|
877 | else: |
---|
878 | self.__DestroyPopup() |
---|
879 | |
---|
880 | |
---|
881 | def OnRightClick(self, evt): |
---|
882 | pt = evt.GetPosition() |
---|
883 | itemId, flags = self.tree.HitTest(pt) |
---|
884 | itemData = self.tree.GetPyData(itemId) |
---|
885 | self.tree.SelectItem(itemId) |
---|
886 | |
---|
887 | if itemData == None: |
---|
888 | return #don't do anything if the user right-clicked on the root |
---|
889 | |
---|
890 | # show the menu now |
---|
891 | menu = wx.Menu() |
---|
892 | if itemData.IsFile(): # FILES |
---|
893 | if itemData.IsFavorite(): |
---|
894 | delMenuItem = menu.Append(1, "Remove From Favorites") |
---|
895 | menu.Bind(wx.EVT_MENU, self.OnRemoveFavoritesFile, delMenuItem) |
---|
896 | else: |
---|
897 | delMenuItem = menu.Append(1, "Delete File") |
---|
898 | menu.Bind(wx.EVT_MENU, self.OnDeleteFile, delMenuItem) |
---|
899 | elif not itemData.IsFile() and not itemData.IsFavorite(): # FOLDERS |
---|
900 | newMenuItem = menu.Append(1, "New Folder") |
---|
901 | menu.Bind(wx.EVT_MENU, self.OnNewFolder, newMenuItem) |
---|
902 | if not itemData.IsBranch(): # you cant delete branches |
---|
903 | delMenuItem = menu.Append(2, "Delete Folder") |
---|
904 | menu.Bind(wx.EVT_MENU, self.OnDeleteFolder, delMenuItem) |
---|
905 | |
---|
906 | self.tree.PopupMenu(menu) |
---|
907 | |
---|
908 | |
---|
909 | def OnNewFolder(self, evt): |
---|
910 | newFolderName = wx.GetTextFromUser("New Folder Name: ", "New Folder") |
---|
911 | newFolderName = "_".join(newFolderName.split()) |
---|
912 | if newFolderName == "": |
---|
913 | return |
---|
914 | itemId = self.tree.GetSelection() |
---|
915 | itemData = self.tree.GetPyData(itemId) |
---|
916 | itemType = itemData.GetType() |
---|
917 | newFullPath = os.path.join( itemData.GetFullPath(), newFolderName) |
---|
918 | try: |
---|
919 | res = self.server.NewFolder(newFullPath) |
---|
920 | except socket.error: |
---|
921 | Message("Error creating the directory. No connection with the File Server.", "No Connection") |
---|
922 | else: |
---|
923 | if res != True: |
---|
924 | Message("Error occured while creating the directory. Does the path leading to it still exist?", "Directory Creation Unsuccessful") |
---|
925 | self.RefreshTree() |
---|
926 | else: # make successful, rearrange the tree |
---|
927 | self.MakeFolder(itemId, newFullPath, itemType) |
---|
928 | |
---|
929 | |
---|
930 | def MakeFolder(self, parentId, newFullPath, fileType): |
---|
931 | (path, name) = os.path.split(newFullPath) |
---|
932 | itemId = self.tree.AppendItem(parentId, name) |
---|
933 | self.tree.SetItemTextColour(itemId, dirTextColor) |
---|
934 | self.tree.SetPyData(itemId, Node(name, fileType, path, False, False)) |
---|
935 | self.tree.SelectItem(itemId) |
---|
936 | |
---|
937 | |
---|
938 | def OnDeleteFolder(self, evt): |
---|
939 | itemId = self.tree.GetSelection() |
---|
940 | itemData = self.tree.GetPyData(itemId) |
---|
941 | fullPath = itemData.GetFullPath() |
---|
942 | if ChoiceDialog("This will delete the DIR and all the FILES in it permanently from the File Server which means that no " \ |
---|
943 | "user will be able to access it/them anymore.\nAre you sure you want to continue?", \ |
---|
944 | "Deletion Confirmation"): |
---|
945 | try: |
---|
946 | res = self.server.DeleteFolder(fullPath) |
---|
947 | except socket.error: |
---|
948 | Message("Error deleting the directory. No connection with the File Server.", "No Connection") |
---|
949 | else: |
---|
950 | if res != True: |
---|
951 | Message("Error occured while deleting the directory. Are the permissions correct?", "Directory Deletion Unsuccessful") |
---|
952 | self.RefreshTree() |
---|
953 | else: # delete successful, rearrange the tree |
---|
954 | self.tree.Delete(itemId) |
---|
955 | |
---|
956 | |
---|
957 | def OnDeleteFile(self, evt): |
---|
958 | itemId = self.tree.GetSelection() |
---|
959 | itemData = self.tree.GetPyData(itemId) |
---|
960 | if itemData == None: |
---|
961 | return #the user clicked on one of the root folders which are not modifiable |
---|
962 | if ChoiceDialog("This will delete the file permanently from the File Server which means that no " \ |
---|
963 | "user will be able to access it anymore.\nAre you sure you want to continue?", \ |
---|
964 | "Deletion Confirmation"): |
---|
965 | try: |
---|
966 | res = self.server.DeleteFile(itemData.GetFullPath()) |
---|
967 | except socket.error: |
---|
968 | Message("Error deleting file. No connection with the File Server.", "No Connection") |
---|
969 | else: |
---|
970 | if res != True: |
---|
971 | Message("Error occured while deleting file. Does the file exist?", "Delete Unsuccessful") |
---|
972 | self.RefreshTree() |
---|
973 | else: # delete that node from the tree |
---|
974 | self.tree.Delete(itemId) |
---|
975 | self.currentImage.SetBitmap(self.no_preview) |
---|
976 | |
---|
977 | |
---|
978 | def OnRemoveFavoritesFile(self, event): |
---|
979 | itemId = self.tree.GetSelection() |
---|
980 | prefs.favorite_files.RemoveFavorite(self.tree.GetPyData(itemId)) # you must do this before deleting the item |
---|
981 | self.tree.Delete(itemId) |
---|
982 | #self.SaveFavorites() |
---|
983 | |
---|
984 | |
---|
985 | |
---|
986 | def OnShowButton(self, evt): |
---|
987 | itemId = self.tree.GetSelection() |
---|
988 | if not itemId.IsOk(): |
---|
989 | return |
---|
990 | itemData = self.tree.GetPyData( itemId ) |
---|
991 | |
---|
992 | |
---|
993 | # check if the user double clicked on a parent and not a leaf |
---|
994 | # if so, expand/collapse the parent |
---|
995 | if itemData == None or not itemData.IsFile(): |
---|
996 | if self.tree.IsExpanded(itemId): |
---|
997 | self.tree.Collapse(itemId) |
---|
998 | else: |
---|
999 | self.tree.Expand(itemId) |
---|
1000 | return |
---|
1001 | |
---|
1002 | # show the file (check if it exists first) |
---|
1003 | try: |
---|
1004 | c = wx.BusyCursor() |
---|
1005 | fileInfo = self.server.GetFileInfo(itemData.GetName(), 0, itemData.GetPath()) |
---|
1006 | del c |
---|
1007 | if not fileInfo: #file not supported |
---|
1008 | Message("File type <"+os.path.splitext(itemData.GetName())[1]+"> not supported.", "Invalid File Type") |
---|
1009 | return |
---|
1010 | (fileType, size, fullPath, appName, params, fileExists) = fileInfo |
---|
1011 | if not fileExists: #file doesnt exist |
---|
1012 | Message("File <"+itemData.GetName()+"> does not seem to exist anymore.\nTry closing the library and opening it again in order to refresh the file list.", "Invalid Filename") |
---|
1013 | return |
---|
1014 | self.ShowFile(fileType, fullPath, appName, params, size) |
---|
1015 | except socket.error: |
---|
1016 | del c |
---|
1017 | Message("Unable to show file. There is no connection with the File Library.", "No connection") |
---|
1018 | |
---|
1019 | |
---|
1020 | def OnSelChanged(self, evt): |
---|
1021 | class PreviewTimer(wx.Timer): |
---|
1022 | def __init__(self, action, itemId): |
---|
1023 | self.action = action |
---|
1024 | self.itemId = itemId |
---|
1025 | wx.Timer.__init__(self) |
---|
1026 | def Notify(self): |
---|
1027 | c = wx.BusyCursor() |
---|
1028 | try: |
---|
1029 | self.action(self.itemId) |
---|
1030 | except socket.error, xmlrpclib.ProtocolError: |
---|
1031 | pass # no preview retrieved... oh well |
---|
1032 | del c |
---|
1033 | |
---|
1034 | def GetPreview(itemId): |
---|
1035 | itemData = self.tree.GetPyData(itemId) |
---|
1036 | if itemData == None or not itemData.IsFile(): |
---|
1037 | self.showBtn.Disable() |
---|
1038 | return # do nothing if a leaf wasn't selected |
---|
1039 | self.showBtn.Enable() |
---|
1040 | |
---|
1041 | # no preview if we didnt select an image |
---|
1042 | #if itemData.GetType() != "image": |
---|
1043 | # self.currentImage.SetBitmap(self.no_preview) |
---|
1044 | # return |
---|
1045 | |
---|
1046 | # get the preview from the server (and get other info like the size of the image) |
---|
1047 | self.currentImage.SetBitmap(self.retrieving_preview) |
---|
1048 | try: |
---|
1049 | preview = self.server.GetPreview(itemData.GetFullPath(), self.previewSize) |
---|
1050 | except: |
---|
1051 | self.currentImage.SetBitmap(self.no_preview) |
---|
1052 | return |
---|
1053 | if not preview: #preview retrieval failed for some reason |
---|
1054 | self.currentImage.SetBitmap(self.no_preview) |
---|
1055 | else: |
---|
1056 | (previewData, isBinary) = preview |
---|
1057 | if isBinary: |
---|
1058 | stream=wx.InputStream(cStringIO.StringIO(base64.decodestring(previewData))) #make a stream out of the image |
---|
1059 | im = wx.ImageFromStream(stream) |
---|
1060 | im.Rescale(PREVIEW_SIZE[0], PREVIEW_SIZE[1]) |
---|
1061 | else: |
---|
1062 | im = wx.EmptyImage(PREVIEW_SIZE[0], PREVIEW_SIZE[1]) |
---|
1063 | im.SetData(base64.decodestring(previewData)) |
---|
1064 | |
---|
1065 | if im.Ok(): |
---|
1066 | self.currentImage.SetBitmap(im.ConvertToBitmap()) |
---|
1067 | else: |
---|
1068 | self.currentImage.SetBitmap(self.no_preview) |
---|
1069 | |
---|
1070 | # show the preview on a timer (done so that search-as-you-type is faster) |
---|
1071 | if hasattr(self, "_timer"): |
---|
1072 | if self._timer.IsRunning(): |
---|
1073 | self._timer.Stop() |
---|
1074 | self._timer = PreviewTimer(GetPreview, evt.GetItem()) |
---|
1075 | self._timer.Start(300, True) |
---|
1076 | |
---|
1077 | |
---|
1078 | # run the show file in a thread |
---|
1079 | def ShowFile(self, fileType, fullPath, appName, params, size): |
---|
1080 | t = Thread(target=self.DoShowFile, args=(fileType, fullPath, appName, params, size)) |
---|
1081 | t.start() |
---|
1082 | |
---|
1083 | |
---|
1084 | def DoShowFile(self, fileType, fullPath, appName, params, size): |
---|
1085 | prepResult = self.server.PrepareFile(fullPath, self.sageHost) |
---|
1086 | bridge = self.shareCheckBox.GetValue() |
---|
1087 | |
---|
1088 | if self.dxtCheckBox.GetValue(): |
---|
1089 | useDXT = "" |
---|
1090 | else: |
---|
1091 | useDXT = "-show_original" |
---|
1092 | |
---|
1093 | if not prepResult: |
---|
1094 | Message("Unable to show file because the transfer of file from the FileServer to the SAGE machine failed.", "Error") |
---|
1095 | return |
---|
1096 | else: |
---|
1097 | fullPath = prepResult #the path was returned by the FileGrabber |
---|
1098 | |
---|
1099 | if fileType == "image": |
---|
1100 | totalDisplayWidth = self.canvas.tiledDisplayWidth |
---|
1101 | totalDisplayHeight = self.canvas.tiledDisplayHeight |
---|
1102 | imageWidth = size[0] |
---|
1103 | imageHeight = size[1] |
---|
1104 | imageAspectRatio = float( imageWidth/imageHeight ) |
---|
1105 | |
---|
1106 | # figure out if the image is going to fit, if not, reposition and resize it as necessary |
---|
1107 | # first resize if necessary |
---|
1108 | widthRatio = float(totalDisplayWidth / float(imageWidth)) |
---|
1109 | heightRatio = float(totalDisplayHeight / float(imageHeight)) |
---|
1110 | if widthRatio < 1 or heightRatio < 1: |
---|
1111 | if widthRatio > heightRatio: #we'll resize based on height |
---|
1112 | resizeRatio = heightRatio |
---|
1113 | else: |
---|
1114 | resizeRatio = widthRatio #we'll resize based on width |
---|
1115 | imageWidth = int(imageWidth * resizeRatio) |
---|
1116 | imageHeight = int(imageHeight * resizeRatio) |
---|
1117 | |
---|
1118 | # now reposition |
---|
1119 | cornerX = 0 |
---|
1120 | cornerY = 0 |
---|
1121 | |
---|
1122 | res = self.sageGate.executeApp(appName ,pos=(cornerX, cornerY), size=(imageWidth,imageHeight), optionalArgs = fullPath+" "+str(imageWidth)+" "+str(imageHeight)+" "+useDXT+" "+params, useBridge=bridge) |
---|
1123 | elif fileType == "video" or fileType=="pdf": |
---|
1124 | res = self.sageGate.executeApp(appName, optionalArgs=params+" "+fullPath, useBridge=bridge) |
---|
1125 | else: #for other types |
---|
1126 | res = self.sageGate.executeApp(appName, optionalArgs=fullPath+" "+params, useBridge=bridge) |
---|
1127 | |
---|
1128 | if res == -1: |
---|
1129 | Message("Application not started. Either application failed, the application launcher is not running or the application <<"+appName+">> is not configured in application launcher.", "Application Launch Failed") |
---|
1130 | |
---|
1131 | |
---|
1132 | |
---|
1133 | ### shows the dialog with a list of file libraries that you can |
---|
1134 | ### connect to. ShowDialog returns the selected library (Actually it's |
---|
1135 | ### IP address) |
---|
1136 | class LibraryChoiceDialog(wx.Dialog): |
---|
1137 | def __init__(self, parent): |
---|
1138 | wx.Dialog.__init__(self, parent, wx.ID_ANY, "Choose File Library", style=wx.CAPTION | wx.CLOSE_BOX | wx.RESIZE_BORDER) |
---|
1139 | self.SetBackgroundColour(dialogColor) |
---|
1140 | self.SetForegroundColour(wx.WHITE) |
---|
1141 | self.mainSizer = wx.BoxSizer(wx.VERTICAL) |
---|
1142 | self.horSizer1 = wx.BoxSizer(wx.HORIZONTAL) |
---|
1143 | self.SetMinSize((100, 200)) |
---|
1144 | self.__MakeControls() |
---|
1145 | |
---|
1146 | |
---|
1147 | |
---|
1148 | def __MakeControls(self): |
---|
1149 | self.ID_ADD = 500 |
---|
1150 | self.ID_DEL = 501 |
---|
1151 | self.ID_UPLOAD = 502 |
---|
1152 | self.ID_SHOW = 503 |
---|
1153 | |
---|
1154 | self.libList = wx.ListBox(self, wx.ID_ANY, choices=prefs.fileLib.GetLibraryList(), style=wx.LB_SINGLE) |
---|
1155 | self.libList.SetStringSelection( prefs.fileLib.GetDefault()[0] ) |
---|
1156 | self.libList.Bind(wx.EVT_LISTBOX_DCLICK, self.__OnDoubleClick) |
---|
1157 | #self.libList.SetTextColour(wx.WHITE) |
---|
1158 | #self.libList.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) |
---|
1159 | #self.libList.SetBackgroundColour(appPanelColor) |
---|
1160 | #self.libList.SetBackgroundColour(appPanelColor) |
---|
1161 | #self.libList.SetForegroundColour(wx.WHITE) |
---|
1162 | |
---|
1163 | addBtn = wx.Button(self, self.ID_ADD, "Add", size=(60,20)) |
---|
1164 | smallFont = addBtn.GetFont() |
---|
1165 | smallFont.SetPointSize(smallFont.GetPointSize() - smallFont.GetPointSize()/5) |
---|
1166 | addBtn.SetFont(smallFont) |
---|
1167 | delBtn = wx.Button(self, self.ID_DEL, "Remove", size=(60,20)) |
---|
1168 | delBtn.SetFont(smallFont) |
---|
1169 | |
---|
1170 | self.horSizer1.Add(addBtn, 0, wx.ALIGN_CENTER | wx.RIGHT, border=10) |
---|
1171 | self.horSizer1.Add(delBtn, 0, wx.ALIGN_CENTER ) |
---|
1172 | self.mainSizer.Add(self.libList, 1, wx.ALIGN_CENTER | wx.ALIGN_TOP | wx.ALL | wx.EXPAND, border=20) |
---|
1173 | self.mainSizer.Add(self.horSizer1, 0, wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, border=5) |
---|
1174 | self.mainSizer.Add(self.CreateButtonSizer(wx.OK | wx.CANCEL), 0, wx.ALIGN_CENTER | wx.ALL, border=15) |
---|
1175 | self.SetSizer(self.mainSizer) |
---|
1176 | self.Fit() |
---|
1177 | |
---|
1178 | self.Bind(wx.EVT_BUTTON, self.__OnAdd, id=self.ID_ADD) |
---|
1179 | self.Bind(wx.EVT_BUTTON, self.__OnRemove, id=self.ID_DEL) |
---|
1180 | self.Bind(wx.EVT_BUTTON, self.__OnUpload, id=self.ID_UPLOAD) |
---|
1181 | self.Bind(wx.EVT_BUTTON, self.__OnShow, id=self.ID_SHOW) |
---|
1182 | |
---|
1183 | |
---|
1184 | def ShowDialog(self): |
---|
1185 | res = self.ShowModal() |
---|
1186 | if res==wx.ID_OK or res==self.ID_UPLOAD: |
---|
1187 | libName = self.libList.GetStringSelection() |
---|
1188 | libHost = prefs.fileLib.GetLibraryIP( libName ) |
---|
1189 | prefs.fileLib.SetDefault( libName, libHost ) |
---|
1190 | self.Destroy() |
---|
1191 | return (libName, libHost) |
---|
1192 | else: |
---|
1193 | self.Destroy() |
---|
1194 | return False |
---|
1195 | |
---|
1196 | |
---|
1197 | def __OnDoubleClick(self, evt): |
---|
1198 | self.EndModal(self.ID_UPLOAD) |
---|
1199 | |
---|
1200 | |
---|
1201 | def __OnUpload(self, evt): |
---|
1202 | self.EndModal(self.ID_UPLOAD) |
---|
1203 | |
---|
1204 | |
---|
1205 | def __OnShow(self, evt): |
---|
1206 | self.EndModal(self.ID_SHOW) |
---|
1207 | |
---|
1208 | |
---|
1209 | def __OnAdd(self, evt): |
---|
1210 | dlg = wx.Dialog(self, wx.ID_ANY, "Add New Library") |
---|
1211 | sizer = wx.BoxSizer(wx.VERTICAL) |
---|
1212 | nameLabel = wx.StaticText(dlg, wx.ID_ANY, "Library name (label):") |
---|
1213 | nameField = wx.TextCtrl(dlg, wx.ID_ANY) |
---|
1214 | ipLabel = wx.StaticText(dlg, wx.ID_ANY, "Library IP address (or hostname):") |
---|
1215 | ipField = wx.TextCtrl(dlg, wx.ID_ANY) |
---|
1216 | |
---|
1217 | sizer.Add(nameLabel, 0, wx.ALIGN_LEFT | wx.ALL, border = 10) |
---|
1218 | sizer.Add(nameField, 0, wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 10) |
---|
1219 | sizer.Add(ipLabel, 0, wx.ALIGN_LEFT | wx.ALL, border = 10) |
---|
1220 | sizer.Add(ipField, 0, wx.ALIGN_LEFT | wx.LEFT | wx.BOTTOM | wx.RIGHT | wx.EXPAND, border = 10) |
---|
1221 | sizer.Add(dlg.CreateButtonSizer(wx.OK|wx.CANCEL), 0, wx.ALIGN_CENTER|wx.ALL, border=10) |
---|
1222 | dlg.SetSizer(sizer) |
---|
1223 | dlg.Fit() |
---|
1224 | if dlg.ShowModal() == wx.ID_OK: |
---|
1225 | prefs.fileLib.AddLibrary(ipField.GetValue(), nameField.GetValue()) |
---|
1226 | self.libList.Set(prefs.fileLib.GetLibraryList()) |
---|
1227 | self.libList.SetStringSelection(nameField.GetValue()) |
---|
1228 | dlg.Destroy() |
---|
1229 | else: |
---|
1230 | dlg.Destroy() |
---|
1231 | |
---|
1232 | |
---|
1233 | def __OnRemove(self, evt): |
---|
1234 | selection = self.libList.GetStringSelection() |
---|
1235 | if selection != "": |
---|
1236 | prefs.fileLib.RemoveLibrary(selection) |
---|
1237 | self.libList.Set(prefs.fileLib.GetLibraryList()) |
---|
1238 | return |
---|
1239 | |
---|
1240 | |
---|
1241 | |
---|
1242 | class PopupDialog(wx.Dialog): |
---|
1243 | def __init__(self,parent,content = None): |
---|
1244 | wx.Dialog.__init__(self,parent,wx.ID_ANY,'', style = wx.NO_BORDER | wx.STAY_ON_TOP) |
---|
1245 | |
---|
1246 | self.ctrl = parent |
---|
1247 | self.win = wx.Window(self,-1,pos = (0,0),style = wx.NO_BORDER) |
---|
1248 | |
---|
1249 | if content: |
---|
1250 | self.SetContent(content) |
---|
1251 | |
---|
1252 | |
---|
1253 | def SetContent(self,content): |
---|
1254 | self.content = content |
---|
1255 | self.content.Reparent(self.win) |
---|
1256 | self.win.SetBackgroundColour(dialogColor)#102, 153, 153))#appPanelColor) |
---|
1257 | self.content.SetForegroundColour(wx.WHITE) |
---|
1258 | self.content.Show(True) |
---|
1259 | sz = self.content.GetSize() |
---|
1260 | self.win.SetClientSize((sz[0]+6, sz[1]+6)) |
---|
1261 | self.SetSize(self.win.GetSize()) |
---|
1262 | |
---|
1263 | |
---|
1264 | def Display(self): |
---|
1265 | class InfoTimer(wx.Timer): |
---|
1266 | def __init__(self, action): |
---|
1267 | self.action = action |
---|
1268 | wx.Timer.__init__(self) |
---|
1269 | def Notify(self): |
---|
1270 | try: |
---|
1271 | self.action() |
---|
1272 | except socket.error, xmlrpclib.ProtocolError: |
---|
1273 | pass # no info retrieved... oh well |
---|
1274 | |
---|
1275 | def __ActuallyShow(): |
---|
1276 | pos = wx.GetMousePosition() |
---|
1277 | posX = pos[0]-(30+self.GetSize()[0]) |
---|
1278 | posY = pos[1] |
---|
1279 | self.Move((posX, posY)) |
---|
1280 | self.Show() |
---|
1281 | |
---|
1282 | # show the info on a timer |
---|
1283 | if hasattr(self, "_timer"): |
---|
1284 | if self._timer.IsRunning(): |
---|
1285 | self._timer.Stop() |
---|
1286 | self._timer = InfoTimer(__ActuallyShow) |
---|
1287 | self._timer.Start(500, True) |
---|
1288 | |
---|
1289 | |
---|
1290 | |
---|
1291 | |
---|
1292 | |
---|
1293 | class Node: |
---|
1294 | def __init__(self, name, fileType, path, isFile, isFavorite, isBranch=False): |
---|
1295 | self.name = name |
---|
1296 | self.fileType = fileType |
---|
1297 | self.path = path |
---|
1298 | self.isFavorite = isFavorite |
---|
1299 | self.isFile = isFile |
---|
1300 | self.isBranch = isBranch |
---|
1301 | |
---|
1302 | def GetName(self): |
---|
1303 | return self.name |
---|
1304 | |
---|
1305 | def GetPath(self): |
---|
1306 | return self.path |
---|
1307 | |
---|
1308 | def GetFullPath(self): |
---|
1309 | return os.path.join(self.path, self.name) |
---|
1310 | |
---|
1311 | def GetType(self): |
---|
1312 | return self.fileType |
---|
1313 | |
---|
1314 | def IsFavorite(self): |
---|
1315 | return self.isFavorite |
---|
1316 | |
---|
1317 | def SetFavorite(self, fav): |
---|
1318 | self.isFavorite = fav |
---|
1319 | |
---|
1320 | def IsFile(self): |
---|
1321 | return self.isFile |
---|
1322 | |
---|
1323 | def IsBranch(self): |
---|
1324 | return self.isBranch |
---|
1325 | |
---|
1326 | def __eq__(self, other): |
---|
1327 | if hasattr(other, "GetName"): |
---|
1328 | if self.GetName() == other.GetName(): |
---|
1329 | return True |
---|
1330 | else: |
---|
1331 | return False |
---|
1332 | return False |
---|
1333 | |
---|
1334 | def __ne__(self, other): |
---|
1335 | if hasattr(other, "GetName"): |
---|
1336 | if self.GetName() != other.GetName(): |
---|
1337 | return True |
---|
1338 | else: |
---|
1339 | return False |
---|
1340 | return True |
---|
1341 | |
---|
1342 | |
---|