add hooks for graceful screen cleanup
[sface.git] / sface / mainwindow.py
index fcedbd0..436756a 100644 (file)
@@ -1,34 +1,97 @@
-
-import os
+import os, os.path
+import sys
 import time
+import traceback
 
 from PyQt4.QtCore import *
 from PyQt4.QtGui import *
 
+import sface.screens
 from sface.config import config
-from sface.screens.configscreen import ConfigScreen
-from sface.screens.mainscreen import MainScreen
+from sface.logwindow import LogWindow
+from sface.rspecwindow import RSpecWindow
+from sface.screens.sfascreen import SfaScreen
+
+# depending on the platform..
+# could probably use Qt's resource system but looks overkill for just one file...
+def locate_image_file (filename):
+    for dir in [ '/usr/share', '/Applications/sface.app/Contents/Resources/sface' ] :
+        for suffix in ['png','jpg']:
+            attempt=os.path.join(dir,'images',"%s.%s"%(filename,suffix))
+            if os.path.isfile(attempt) : return attempt
+    return os.path.join('/could/not/locate/image/file',filename)
+
+def load_screens(dirname):
+    modnames = []
+    for fn in os.listdir(dirname):
+        if not fn.endswith(".py"):
+            continue
+        modname = fn.rsplit(".py",1)[0]
+        if modname == "sfascreen":
+            # ignore this, it's the base class, not a screen
+            continue
+        modnames.append(modname)
+
+    # we want the stock screens to show up in a specific order. plugins can
+    # show up in any order afterward.
+
+    sort_order = ["mainscreen", "configscreen", "helpscreen"]
+    sort_order.reverse()
+    for modname in sort_order:
+        if modname in modnames:
+            modnames.remove(modname)
+            modnames.insert(0,modname)
+
+    # import each module and find whatever class(es) is descendant from
+    # SfaScreen within the module. Might be a better idea to just define a
+    # screens=[] variable in each module
+
+    screens = []
+    for modname in modnames:
+        try:
+            mod = __import__("sface.screens." + modname, fromlist=["modname"])
+        except:
+            print "Exception while importing screen", modname
+            traceback.print_exc()
+            continue
+
+        for object in dir(mod):
+            object = getattr(mod, object)
+            if hasattr(object, "__bases__") and sface.screens.sfascreen.SfaScreen in object.__bases__:
+                screens.append(object)
+
+    return screens
 
 class Nav(QWidget):
-    def __init__(self, parent=None):
+    def __init__(self, screens, parent=None):
         QWidget.__init__(self, parent)
-        
+
         self.title = QLabel("", self)
-        self.link = QLabel("", self)
-        self.link.setAlignment(Qt.AlignRight)
-        
+        scene=QGraphicsScene()
+        pixmap = QPixmap(locate_image_file('graphic-sfa64'))
+        logolabel=QLabel("",self)
+        logolabel.setPixmap(pixmap)
+
         hlayout = QHBoxLayout()
+        hlayout.addWidget(logolabel)
         hlayout.addWidget(self.title)
         hlayout.addStretch()
-        hlayout.addWidget(self.link)
+        gotolabel=QLabel("Go to: ", self)
+        gotolabel.setAlignment(Qt.AlignRight)
+        hlayout.addWidget(gotolabel)
+
+        self.screenLabels = []
+        for screen in screens:
+            label = QLabel(screen.getLinkText(), self)
+            label.setAlignment(Qt.AlignRight)
+            self.screenLabels.append(label)
+            hlayout.addWidget(label)
+
         self.setLayout(hlayout)
 
     def setTitle(self, title):
         self.title.setText(title)
 
-    def setLink(self, link):
-        self.link.setText(link)
-
 
 class Status(QLabel):
     def __init__(self, parent=None):
@@ -58,44 +121,126 @@ class MainWindow(QWidget):
     def __init__(self, parent=None):
         QWidget.__init__(self, parent)
 
-        self.config_screen = ConfigScreen(self)
-        self.main_screen = MainScreen(self)
+        self.logWindow = LogWindow(self)
+        self.rspecWindow = RSpecWindow(self)
+
+        self.pix = QLabel(self)
+
+        screenClasses = load_screens(os.path.dirname(sface.screens.__file__))
+        self.screenWidgets = []
 
         self.screens = QStackedWidget(self)
-        self.screens.addWidget(self.main_screen)
-        self.screens.addWidget(self.config_screen)
+        for screen in screenClasses:
+            # use a try/catch block to isolate the screen. Third-party plugins
+            # could be buggy.
+            try:
+                screenWidget = screen(self)
+                self.screenWidgets.append(screenWidget)
+                self.screens.addWidget(screenWidget)
+            except:
+                print "Exception while creating screen", screen.__name__
+                traceback.print_exc()
+
+        self.screens.addWidget(self.pix)
+        self.next_screen = None
 
-        self.nav = Nav(self)
-        self.nav.setTitle(self.main_screen.getTitleText())
-        self.nav.setLink(self.config_screen.getLinkText())
+        self.nav = Nav(self.screenWidgets, self)
+
+        if self.screenWidgets:
+            self.nav.setTitle(self.screenWidgets[0].getTitleText())
 
         self.status = Status(self)
+        self.log = QLabel("<a href='showlog'>Show Log</a>", self)
+        self.rspec = QLabel("<a href='showlog'>Show RSpec</a>", self)
+
+        hlayout = QHBoxLayout()
+        hlayout.addWidget(self.status)
+        hlayout.addStretch()
+        hlayout.addWidget(self.rspec)
+        hlayout.addWidget(self.log)
 
         layout = QVBoxLayout()
         layout.addWidget(self.nav)
         layout.addWidget(self.screens)
-        layout.addWidget(self.status)
+        layout.addLayout(hlayout)
         self.setLayout(layout)
-        self.resize(800, 600)
+        self.resize(800, 500)
+
+        for link in self.nav.screenLabels:
+            self.connect(link, SIGNAL('linkActivated(QString)'),
+                         self.animateToScreen)
 
-        self.connect(self.nav.link, SIGNAL('linkActivated(QString)'),
-                     self.toScreen)
+        self.connect(self.log, SIGNAL('linkActivated(QString)'),
+                     self.showLogWindow)
+        self.connect(self.rspec, SIGNAL('linkActivated(QString)'),
+                     self.showRSpecWindow)
 
-    def toScreen(self, link):
-        if link == self.config_screen.name:
-            self.toConfigScreen()
-        elif link == self.main_screen.name:
-            self.toMainScreen()
+    def redirectOutputToLog(self):
+        self.logWindow.redirectOutput()
 
-    def toConfigScreen(self):
-        self.screens.setCurrentWidget(self.config_screen)
-        self.nav.setLink(self.main_screen.getLinkText())
-        self.nav.setTitle(self.config_screen.getTitleText())
+    def showLogWindow(self, link):
+        self.logWindow.show()
+        self.logWindow.resize(800, 200)
+        self.logWindow.raise_()
+        self.logWindow.activateWindow()
 
-    def toMainScreen(self):
-        self.screens.setCurrentWidget(self.main_screen)
-        self.nav.setLink(self.config_screen.getLinkText())
-        self.nav.setTitle(self.main_screen.getTitleText())
+    def showRSpecWindow(self, link):
+        self.rspecWindow.show()
+        self.rspecWindow.resize(500, 640)
+        self.rspecWindow.raise_()
+        self.rspecWindow.activateWindow()
+
+
+    def animatePixmap(self, y):
+        self.pix.move(0, y)
+
+    def animateToScreen(self, link):
+        for screen in self.screenWidgets:
+            if link == screen.name:
+                self.next_screen = screen
+
+        curr_screen = self.screens.currentWidget()
+
+        if self.next_screen == curr_screen:
+            self.setStatus("Already showing %s" % curr_screen.getTitleText(), timeout=1000)
+            return
+
+        # This is an optimization to have a smoother animation. We
+        # render the widget into a pixmap and animate that instead of
+        # moving the whole widget around.
+        pixmap = QPixmap(self.screens.size())
+        curr_screen.render(pixmap)
+        self.screens.setCurrentWidget(self.pix)
+        self.pix.setPixmap(pixmap)
+
+        timeLine = QTimeLine(500, self)
+        timeLine.setFrameRange(0, self.screens.height());
+        self.connect(timeLine, SIGNAL('frameChanged(int)'), self.animatePixmap)
+        self.connect(timeLine, SIGNAL('finished()'), self.toNextScreen)
+        timeLine.start()
+
+
+    def toNextScreen(self):
+        self.screens.setCurrentWidget(self.next_screen)
+        self.nav.setTitle(self.next_screen.getTitleText())
 
     def setStatus(self, msg, timeout):
         self.status.set(msg, timeout)
+
+    def nodeSelectionChanged(self, hostname):
+        if self.rspecWindow.isVisible():
+            self.rspecWindow.showNode(hostname)
+
+    def closeEvent(self, event):
+        # give the screens an opportunity to veto the close
+        for screen in self.screenWidgets:
+            if not screen.canClose():
+                event.ignore()
+                return
+
+        # give the screens an opportunity to close gracefully
+        for screen in self.screenWidgets:
+            screen.mainWindowClose()
+
+        QWidget.closeEvent(self, event)
+