1 """
2 """
3
4 from PyQt4.QtGui import *
5 from PyQt4.QtCore import *
6
7 import Constants
8 import sys
9 import re
10
12 """
13 """
14
16 """
17 Overview
18 ========
19 The bridge controls many "global" functionality, and in some cases may want to alter
20 something in the main window or application.
21
22 Also, and probably most importantly, the bridge serves as a universal interconnection
23 between any class you want. It makes it incredibly easy for the open file menu to send a
24 command to the ipython shell which is going to perform a command and then update the
25 region and variable viewers.
26
27 @param app: This gives the bridge a reference to the underlying application, allowing
28 it to change its fundamental properties if necessary.
29 @type app: QApplication
30
31 @param win: This gives the bridge a reference to the underlying main window, allowing
32 it to change its fundamental properties if necessary.
33 @type win: QMainWindow
34 """
35
36 QObject.__init__ (self)
37 self._app = app
38 self._mainWindow = win
39
40 self._connected = False
41 self._parallel = False
42 self._drugItem = None
43
44 self._host = None
45 self._port = None
46 self._user = None
47 self._nodes = []
48 self._numNodes = 0
49
50
51 self._importPylab = False
52 self._importNumpy = False
53 self._importScipy = False
54
55 try:
56 import visit
57 self._importVisIt = True
58 except (ImportError):
59 self._importVisIt = False
60
61
62
63 self._inPlace = False
64 self._autoGuess = True
65 self._limitOut = True
66 self._dataPath = None
67
68 self._pythonVersion = sys.version_info
69
70 self._visitModuleStarted = False
71
72
73 self._lastUpdated = 0
74 self.startTimer (3000)
75
76 self.loadSettings ()
77
79 """
80 Overview
81 ========
82 This method will load the contents of the configuration file: ~/.basin/client/general.conf
83
84 These correspond to the general preferences found within the BASIN_preferences.Preferences class.
85
86 This method is called upon the instantiation of the bridge as well as whenever the general.conf
87 is overwritten by the preferences menu.
88 """
89
90 file = QFile (str (QDir.home ().path ()) + "/.basin/client/general.conf")
91 if file.exists ():
92 file.open (QIODevice.ReadOnly | QIODevice.Text)
93 stream = QTextStream (file)
94 reInPlace = re.compile ("\s*INPLACE\s*:\s*.*\s*")
95 reAutoGuess = re.compile ("\s*AUTOGUESS\s*:\s*.*\s*")
96 reLimitOut = re.compile ("\s*LIMITOUT\s*:\s*.*\s*")
97 reDataPath = re.compile ("\s*DATAPATH\s*:\s*.*\s*")
98 while not stream.atEnd ():
99 line = str (stream.readLine ())
100 val = line[line.find (':') + 1:]
101 if val == "False":
102 val = False
103 elif val == "True":
104 val = True
105 elif val == "None":
106 val = None
107
108 if reInPlace.match (line):
109 self._inPlace = val
110 elif reAutoGuess.match (line):
111 self._autoGuess = val
112 elif reLimitOut.match (line):
113 self._limitOut= val
114 elif reDataPath.match (line):
115 self._dataPath = val
116 file.close ()
117
119 """
120 Overview
121 ========
122 This gives the bridge a reference to the menubar. This isn't done during initialization
123 since the bridge is created before the menu bar exists.
124
125 @param menuBar: This is the reference to the menu bar that the Bridge requires.
126 @type menuBar: MenuBar
127 """
128
129 self._menuBar = menuBar
130
143
145 """
146 Overview
147 ========
148 This gives the bridge a reference to the actions. This isn't done during initialization
149 since the bridge is created before the actions exists.
150
151 @param actions:
152 @type actions:
153 """
154
155 self._actions = actions
156
158 """
159 Overview
160 ========
161 This gives the bridge a reference to the region viewer. This isn't done during initialization
162 since the bridge is created before the region viewer exists.
163
164 @param regionViewer:
165 @type regionViewer:
166 """
167
168 self._regionViewer = regionViewer
169 QObject.connect (self, SIGNAL ("updateBasin (PyQt_PyObject)"), self._regionViewer.Update)
170
172 """
173 Overview
174 ========
175 This gives the bridge a reference to the variable viewer. This isn't done during initialization
176 since the bridge is created before the variable viewer exists.
177 """
178
179 self._varViewer = varViewer
180 QObject.connect (self, SIGNAL ("updateWorld (PyQt_PyObject)"), self._varViewer.Update)
181
183 """
184 Overview
185 ========
186 This gives the bridge a reference to the dock. This isn't done during initialization since
187 the bridge is created before the dock exists.
188
189 @param dock:
190 @type dock: Dock
191 """
192
193 self._dock = dock
194
196 """
197 Overview
198 ========
199 This gives the bridge a reference to the status bar. This isn't done during initialization
200 since the bridge is created before the status bar exists.
201 """
202
203 self._statusBar = statusBar
204
206 """
207 Overview
208 ========
209 This gives the bridge a reference to the IPython Shell. This isn't done during initialization
210 since the bridge is created before the IPython Shell exists.
211 """
212
213 self._ipyShell = ipyShell
214 self.pythonStatus ()
215
217 """
218 Overview
219 ========
220 This method returns the cursor to its former state. This means that you shouldn't change
221 a cursor until it has been returned to its original state, because returning it twice has no effect.
222 """
223
224 self._app.restoreOverrideCursor ()
225
227 """
228 Bridge.CursorBusy (self)
229
230 This method changes the cursor to a busy cursor. This
231 should really only be called if the cursor is in a
232 nromal state to begin with.
233 """
234 self._app.setOverrideCursor (QCursor (Qt.WaitCursor))
235
237 """
238 Bridge.CursorBusy (self)
239
240 This method changes the cursor to an open hand cursor.
241 This should really only be called if the cursor is in a
242 nromal state to begin with.
243 """
244 self._app.setOverrideCursor (QCursor (Qt.OpenHandCursor))
245
247 """
248 Bridge.CursorBusy (self)
249
250 This method changes the cursor to a closed hand cursor.
251 This should really only be called if the cursor is in a
252 nromal state to begin with.
253 """
254 self._app.setOverrideCursor (QCursor (Qt.ClosedHandCursor))
255
256
258 """
259 Bridge.Update (self)
260
261 This is the updating function that should be used to perform
262 an update across the entie basin_client. While the individual
263 region viewer and free variable viewer have update methods,
264 this should be the only point at which they are called.
265
266 That being said, the roder in which they are called is of
267 great importance. The Region Viewer must be updated first,
268 then the free variable viewer. This is because the variable
269 viewer has to check for aliases after it sets its cotnents up,
270 and if the region viewer is not already updated, then it
271 would be providing possibly false, and certainly old, results.
272
273 As this operation can take some noticeable time, the cursor
274 is changed to busy during this oepration.
275
276 After the update is complete, the _lastUpdated member is also
277 updated to reflect the ipython line number at which the client
278 is updated to. This gets used because every few seconds a
279 timer checks if an update is needed, by comparing this _lastUpdated
280 member to the current line.
281 """
282 if (not self._connected) and (not self._parallel):
283 return
284 self.CursorBusy ()
285 self.emit (SIGNAL ("updateBasin (PyQt_PyObject)"), self._ipyShell.getTopLevelRegions ())
286 self.emit (SIGNAL ("updateWorld (PyQt_PyObject)"), self._ipyShell.getFreeVariables ())
287 discrete, discreteWindows = self._ipyShell.getVisItExposeDiscrete ()
288 continuous, continuousWindows = self._ipyShell.getVisItExposeContinuous ()
289 self.emit (SIGNAL ("updateVisIt (PyQt_PyObject, PyQt_PyObject, PyQt_PyObject, PyQt_PyObject)"), discrete, discreteWindows, continuous, continuousWindows)
290 self.CursorNorm ()
291 self._ipyShell._commandLine._update == False
292 result = self._ipyShell.SilentCommand ("print \"\\n\" + BASIN_UPDATE").split ("\n")
293 lineNumber = result[1][result[1].find ("[", 1) + \
294 1:result[1].find ("]", result[1].find ("[", 1))]
295 self._lastUpdated = int (lineNumber)
296
298 """
299 Bridge.PopDrugItem (self)
300
301 This will retrieve an item tacked onto the bridge from a drag
302 operation. On top of that, it also erases from the bridge in
303 a traditional "pop" fashion.
304 """
305 temp = self._drugItem
306 self._drugItem = None
307 return temp
308
310 """
311 Bridge.SaveIPythonSession (self)
312
313 This method will create a File Saving dialog designed to save
314 the contents of the IPython Shell's History to a log file. If
315 the dialog is successful in getting a file name, it will save
316 the entire contents of the history in clear text format, dis-
317 regarding any formating that is actually seen in the IPython
318 History.
319 """
320 dialog = QFileDialog (None, QString ("Save IPython Session Log"),
321 QString (str (QDir.home ().path ())),
322 QString ("Text (*.log)"))
323 dialog.setWindowIcon (QIcon (QString (ICON_DIR + "/file/python.png")))
324 dialog.setViewMode (QFileDialog.Detail)
325 dialog.setFileMode (QFileDialog.AnyFile)
326 dialog.setAcceptMode (QFileDialog.AcceptSave)
327 dialog.exec_ ()
328
329 files = dialog.selectedFiles ()
330 if not files.isEmpty ():
331 fileName = str (files.takeFirst ())
332 file = QFile ()
333 file.setFileName (fileName)
334 file.setPermissions (QFile.WriteOwner | QFile.ReadOwner | QFile.ReadUser \
335 | QFile.WriteUser | QFile.ReadGroup | QFile.WriteGroup | QFile.ReadOther)
336 if dialog.selectedFilter () == "Text (*.log)":
337 file.open (QIODevice.WriteOnly | QIODevice.Text)
338 file.writeData (str (self._ipyShell._commandHistory.toPlainText ()))
339 file.close ()
340
342 """
343 Bridge.Connect (self)
344
345 This method should be called whenever the user makes a
346 successful connection. This is done automatically through
347 the Connection module. It will enable the command line
348 for ipython, make everything aware that it is connected, and
349 set the menu actions to reflect connectivity.
350
351 The only real way to connect is via the Connection module,
352 so there is little need for this method to be called
353 anywhere else.
354 """
355 self._ipyShell._commandLine.setReadOnly (False)
356 self._ipyShell._commandStatus.toggleButton.setEnabled (True)
357 self._connected = True
358 self.pythonStatus ()
359 self._actions.menuConnect ()
360
362 """
363 Bridge.Disconnect (self)
364
365 This method should be called whenever the user disconnects
366 from the basin session. This can either happen manually by
367 the user, or if the bridge's method Error deems it necessary
368 to automatically kill the session.
369
370 Generally speaking it looks for specific types of connectivity
371 errors to decide if it should pull the plug on the session
372 or if it will be ok. Manually killing the basin_start.sh
373 session, along with segfaults within the C-code will
374 result in this type of disconnection.
375
376 This method really only disables the ipython command line
377 along with clearing the region/variable viewers, and changing
378 menu's to reflect a disconnected status.
379 """
380 self._ipyShell._commandLine.setReadOnly (True)
381 self._ipyShell._initialized = False
382 self._ipyShell._scriptPath = ""
383 self._ipyShell._commandStatus.toggleButton.setEnabled (False)
384 self._regionViewer.clear ()
385 self._varViewer.clear ()
386 self._connected = False
387 self.pythonStatus ()
388 self._actions.menuDisconnect ()
389
391 """
392 Bridge.parallelOn (self)
393
394 This method simply makes the data viewers available as well
395 as making the application aware that it is in parallel mode.
396
397 This is not meant to change any behaviour, but rather reflect
398 that state of the program.
399 """
400 self._regionViewer.setEnabled (True)
401 self._varViewer.setEnabled (True)
402 self._parallel = True
403 self.pythonStatus ()
404
406 """
407 Bridge.parallelOff (self)
408
409 This method simply makes the data viewers disabled as well
410 as informing the application as to its non-parallel status.
411
412 This method will not affect any real behaviour, but rather
413 reflects the state of the program.
414 """
415 self._regionViewer.setEnabled (False)
416 self._varViewer.setEnabled (False)
417 self._parallel = False
418 self.pythonStatus ()
419
421 """
422 Bridge.pythonStatus (self)
423
424 This method will update the IPython status, which is displayed
425 in between the command history and command lines of the shell.
426
427 The status string is dependant on the status of basin_client's
428 connectivity as well as its parallel status. Assuming it is
429 both conencted and parallel, it will also inform the user of the
430 number of nodes that it is currently working with in the session.
431 """
432 if self._parallel and self._connected:
433 self._ipyShell.showStatus ("Connected, Parallel Mode: On, " +
434 str (len (self._nodes)) + " nodes")
435 elif self._parallel and not self._connected:
436 self._ipyShell.showStatus ("Disconnected")
437 elif not self._parallel and self._connected:
438 self._ipyShell.showStatus ("Connected, Parallel Mode: Off")
439 elif not self._parallel and not self._connected:
440 self._ipyShell.showStatus ("Disconnected")
441
443 """
444 Bridge.AddToDock (self, item)
445
446 This method is generally called from the region or
447 free variable viwer. It will send an item that it
448 wants to send to the dock here first, since the viewers
449 don't directly know which widget is currently docked
450 (even though the user can obviously tell this).
451
452 It will determine the proper widget in the dock to send
453 the item to. This does not imply that the dock can
454 actually do anythung with the item, that is up to the
455 dock widget to decide.
456 """
457 if item != None:
458 currentWidget = self._dock.GetCurrentWidget ()
459 if currentWidget != None:
460 currentWidget.DoubleClicked (item)
461
463 """
464 Bridge.makeParallel (self)
465
466 This is just a method from the bridge to force the client into
467 a parallel mode. If it already is parallel, it does nothing.
468 """
469 if self._parallel == False:
470 self._ipyShell.toggleSilentAutopx ()
471
473 """
474 Bridge.makeParallel (self)
475
476 This is just a method from the bridge to force the client into
477 a non-parallel mode. If it isn't parallel currently, it does nothing.
478 """
479 if self._parallel == True:
480 self._ipyShell.toggleSilentAutopx ()
481
483 """
484 Bridge.timerEvent (self, ev)
485
486 In the Bridge's initialization it sets a timer event, currently
487 for every 3 seconds. Every 3 seconds, this method is called.
488
489 If the client is connected, parallel, and the user has nothing
490 currently typed on the command line, it will go ahead and see
491 if it needs to bother updating.
492
493 In the bridge, there is a variable keeping track of the last
494 updated line in the ipython shell. It will check that against
495 a variable that keeps track of the latest relevant update line
496 in ipython. This assumes that code in the ipython shell knows
497 when it is and isn't relevant to updates.
498
499 If it finds that its update line is less than the ipython's
500 last relevant update line, then it will go ahead and perform
501 an Update, as well as setting its newly updated lastUpdated
502 variable.
503 """
504
505
506 if self._parallel and self._connected and self._ipyShell.isClear () \
507 and self._ipyShell._commandLine._singleLine:
508
509 result = self._ipyShell.SilentCommand ("print \"\\n\" + BASIN_UPDATE")
510 if result.find ("rror") != -1:
511 self.Error ("BASIN_bridge.py", "Bridge", "timerEvent", result)
512
513
514 self.makeLocal ()
515 self.Disconnect ()
516 return
517 val = result.split ("\n")[3]
518
519 if self._lastUpdated < int (val):
520
521 self._lastUpdated = int (val)
522 self.Update ()
523
524 - def Error (self, _file, _class, _function, _message = "", silent = False):
525 """
526 Bridge.Error (self, _file, _class, _function, _message = "", silent)
527
528 This error method should be called whenever ipython makes a boo-boo.
529 It is up the the coder to determine if an error has occurred, I lazily
530 do this by doing a quick search through the return string for error.
531
532 All of the arguments are for the purpose of reporting where in this
533 basin_client code that the error occurred in. Of course the error
534 may have nothing to do with the client code, however, it is possible
535 that the error occurred from a mal-constructed ipython command being
536 sent to the ipython shell, in which case it is important. _file refers
537 to the actual scrip file, _class to the python class it occurred in,
538 _function for the method in the class it occurred. _message should be
539 a copy of the ipython error received. Silent hasn't been implemented
540 yet, but was intended to be a way of suppressing error messages to the
541 user, while still logging them in ~/.basin/client/error.log
542 """
543 self.CursorNorm ()
544
545
546 _date = QDate ().currentDate ().toString ("MM/dd/yyyy")
547 _time = QTime ().currentTime ().toString ("H:mm:ss")
548
549
550 path = QDir.home ()
551 if not path.exists (".basin"):
552 path.mkdir (".basin")
553 path.cd (".basin")
554 if not path.exists ("client"):
555 path.mkdir ("client")
556 path.cd ("client")
557 logPath = path.path () + "/error.log"
558 file = open (logPath, "a")
559 file.write ("Date: " + str (_date) + "\n")
560 file.write ("Time: " + str (_time) + "\n")
561 file.write ("File: " + str (_file) + "\n")
562 file.write ("Class: " + str (_class) + "\n")
563 file.write ("Function: " + str (_function) + "\n\n")
564 file.write ("***************************************************************************\n")
565 file.write ("*******************************Error Message*******************************\n")
566 file.write ("***************************************************************************\n")
567 file.write (_message + "\n")
568 file.write ("***************************************************************************\n\n\n\n\n")
569 file.close ()
570
571 if silent:
572 return
573
574
575 dialog = QDialog (self._mainWindow)
576 dialog.setWindowTitle ("Error!")
577 layout = QGridLayout ()
578 dialog.setLayout (layout)
579 acceptButton = QPushButton ("Ok")
580 QObject.connect (acceptButton, SIGNAL ("clicked ()"), dialog.accept)
581 layout.addWidget (QLabel (QString ("IPython Error!")), 0, 0, 1, 2, Qt.AlignHCenter)
582 layout.addWidget (QLabel (QString ("Date:")), 1, 0, 2, 1, Qt.AlignRight)
583 layout.addWidget (QLabel (QString (str (_date))), 1, 1, 2, 1, Qt.AlignLeft)
584 layout.addWidget (QLabel (QString ("Time:")), 3, 0, 2, 1, Qt.AlignRight)
585 layout.addWidget (QLabel (QString (str (_time))), 3, 1, 2, 1, Qt.AlignLeft)
586 layout.addWidget (QLabel (QString ("File:")), 5, 0, Qt.AlignRight)
587 layout.addWidget (QLabel (QString (str (_file))), 5, 1, Qt.AlignLeft)
588 layout.addWidget (QLabel (QString ("Class:")), 6, 0, Qt.AlignRight)
589 layout.addWidget (QLabel (QString (str (_class))), 6, 1, Qt.AlignLeft)
590 layout.addWidget (QLabel (QString ("Function:")), 7, 0, Qt.AlignRight)
591 layout.addWidget (QLabel (QString (str (_function))), 7, 1, Qt.AlignLeft)
592 layout.addWidget (QLabel (QString ("Log available in " + logPath)), \
593 8, 0, 1, 2, Qt.AlignHCenter)
594 layout.addWidget (acceptButton, 9, 0, 1, 2, Qt.AlignHCenter)
595 dialog.exec_ ()
596