changes to work with latest rspec versions, try create at aggregates for protogeni...
[sface.git] / sface / mainwindow.py
1 import os, os.path
2 import sys
3 import time
4 import traceback
5
6 from PyQt4.QtCore import *
7 from PyQt4.QtGui import *
8
9 from distutils.version import LooseVersion
10 from sfa.util.version import version_core
11
12 import sface.screens
13 from sface.config import config
14 from sface.logwindow import LogWindow
15 from sface.rspecwindow import RSpecWindow, ResourcesWindow
16 from sface.screens.sfascreen import SfaScreen
17 from sface.xmlrpcwindow import get_tracker, init_tracker
18
19 MINIMUM_SFA_VERSION = "1.0-37"
20
21 # depending on the platform..
22 # could probably use Qt's resource system but looks overkill for just one file...
23 def locate_image_file (filename):
24     for dir in [ '/usr/share', '/Applications/sface.app/Contents/Resources/sface' ] :
25         for suffix in ['png','jpg']:
26             attempt=os.path.join(dir,'images',"%s.%s"%(filename,suffix))
27             if os.path.isfile(attempt) : return attempt
28     return os.path.join('/could/not/locate/image/file',filename)
29
30 def load_screens(dirname):
31     modnames = []
32     for fn in os.listdir(dirname):
33         if not fn.endswith(".py"):
34             continue
35         modname = fn.rsplit(".py",1)[0]
36         if modname == "sfascreen":
37             # ignore this, it's the base class, not a screen
38             continue
39         modnames.append(modname)
40
41     # we want the stock screens to show up in a specific order. plugins can
42     # show up in any order afterward.
43
44     sort_order = ["mainscreen", "statusscreen", "userscreen", "configscreen", "helpscreen"]
45     sort_order.reverse()
46     for modname in sort_order:
47         if modname in modnames:
48             modnames.remove(modname)
49             modnames.insert(0,modname)
50
51     # import each module and find whatever class(es) is descendant from
52     # SfaScreen within the module. Might be a better idea to just define a
53     # screens=[] variable in each module
54
55     screens = []
56     for modname in modnames:
57         try:
58             mod = __import__("sface.screens." + modname, fromlist=["modname"])
59         except:
60             print "Exception while importing screen", modname
61             traceback.print_exc()
62             continue
63
64         for object in dir(mod):
65             object = getattr(mod, object)
66             if hasattr(object, "__bases__") and sface.screens.sfascreen.SfaScreen in object.__bases__:
67                 screens.append(object)
68
69     return screens
70
71 def check_version():
72     sfa_version = version_core()['code_tag']
73     if (LooseVersion(sfa_version) < LooseVersion(MINIMUM_SFA_VERSION)):
74         QMessageBox.warning(None, "Old SFA Version", "sfa version %s is required. "
75                                                          "Your installed version is %s. "
76                                                          "Please upgrade your sfa and sfa-client packages."
77                                                          % (MINIMUM_SFA_VERSION, sfa_version) )
78         sys.exit(-1)
79
80 class Nav(QWidget):
81     def __init__(self, screens, parent=None):
82         QWidget.__init__(self, parent)
83
84         self.title = QLabel("", self)
85         scene=QGraphicsScene()
86         pixmap = QPixmap(locate_image_file('graphic-sfa64'))
87         logolabel=QLabel("",self)
88         logolabel.setPixmap(pixmap)
89
90         hlayout = QHBoxLayout()
91         hlayout.addWidget(logolabel)
92         hlayout.addWidget(self.title)
93         hlayout.addStretch()
94         gotolabel=QLabel("Go to: ", self)
95         gotolabel.setAlignment(Qt.AlignRight)
96         hlayout.addWidget(gotolabel)
97
98         self.screenLabels = []
99         for screen in screens:
100             label = QLabel(screen.getLinkText(), self)
101             label.setAlignment(Qt.AlignRight)
102             self.screenLabels.append(label)
103             hlayout.addWidget(label)
104
105         self.setLayout(hlayout)
106
107     def setTitle(self, title):
108         self.title.setText(title)
109
110
111 class Status(QLabel):
112     def __init__(self, parent=None):
113         QLabel.__init__(self, "", parent)
114         self.setMaximumWidth(640)
115         self.sliceUpdateDate()
116
117     def set(self, msg, timeout):
118         self.setText(msg)
119         if timeout:
120             QTimer.singleShot(timeout, self.reset)
121
122     def sliceUpdateDate(self):
123         rspec_file = config.getSliceRSpecFile()
124         if not os.path.exists(rspec_file):
125             return
126
127         creation_time = os.stat(rspec_file).st_ctime
128         last_update = time.ctime(creation_time)
129         self.set("Slice data last refreshed on %s" % last_update, timeout=None)
130
131     def reset(self):
132         self.setText("")
133         QTimer.singleShot(1500, self.sliceUpdateDate)
134
135
136 class MainWindow(QWidget):
137     def __init__(self, parent=None):
138         QWidget.__init__(self, parent)
139
140         check_version()
141
142         # These are top-level windows and should be initialized with parent set
143         # to our parent. Otherwise, getting a segfault on exit in Ubuntu.
144         self.logWindow = LogWindow(parent)
145         self.rspecWindow = RSpecWindow(parent)
146         self.resourcesWindow = ResourcesWindow(parent)
147         self.trackerWindow = init_tracker(parent)
148
149         self.pix = QLabel(self)
150
151         screenClasses = load_screens(os.path.dirname(sface.screens.__file__))
152         self.screenWidgets = []
153
154         self.screens = QStackedWidget(self)
155         for screen in screenClasses:
156             # use a try/catch block to isolate the screen. Third-party plugins
157             # could be buggy.
158             try:
159                 screenWidget = screen(self)
160                 self.screenWidgets.append(screenWidget)
161                 self.screens.addWidget(screenWidget)
162             except:
163                 print "Exception while creating screen", screen.__name__
164                 traceback.print_exc()
165
166         self.screens.addWidget(self.pix)
167         self.next_screen = None
168
169         self.nav = Nav(self.screenWidgets, self)
170
171         if self.screenWidgets:
172             self.nav.setTitle(self.screenWidgets[0].getTitleText())
173
174         self.status = Status(self)
175         self.tracker = QLabel("<a href='showtracker'>Xmlrpc</a>", self)
176         self.log = QLabel("<a href='showlog'>Log</a>", self)
177         self.rspec = QLabel("<a href='showlog'>RSpec</a>", self)
178         self.resources = QLabel("<a href='showlog'>Resources</a>", self)
179
180         hlayout = QHBoxLayout()
181         hlayout.addWidget(self.status)
182         hlayout.addStretch()
183         hlayout.addWidget(self.tracker)
184         hlayout.addWidget(self.rspec)
185         hlayout.addWidget(self.resources)
186         hlayout.addWidget(self.log)
187
188         layout = QVBoxLayout()
189         layout.addWidget(self.nav)
190         layout.addWidget(self.screens)
191         layout.addLayout(hlayout)
192         self.setLayout(layout)
193         self.resize(800, 500)
194
195         for link in self.nav.screenLabels:
196             self.connect(link, SIGNAL('linkActivated(QString)'),
197                          self.animateToScreen)
198
199         self.connect(self.tracker, SIGNAL('linkActivated(QString)'),
200                      self.showTrackerWindow)
201         self.connect(self.log, SIGNAL('linkActivated(QString)'),
202                      self.showLogWindow)
203         self.connect(self.rspec, SIGNAL('linkActivated(QString)'),
204                      self.showRSpecWindow)
205         self.connect(self.resources, SIGNAL('linkActivated(QString)'),
206                      self.showResourcesWindow)
207
208     def redirectOutputToLog(self):
209         self.logWindow.redirectOutput()
210
211     def showTrackerWindow(self):
212         tracker = get_tracker()
213         tracker.show()
214         tracker.resize(500, 640)
215         tracker.raise_()
216         tracker.activateWindow()
217
218     def showLogWindow(self, link):
219         self.logWindow.show()
220         self.logWindow.resize(800, 200)
221         self.logWindow.raise_()
222         self.logWindow.activateWindow()
223
224     def showRSpecWindow(self, link):
225         self.rspecWindow.show()
226         self.rspecWindow.resize(500, 640)
227         self.rspecWindow.raise_()
228         self.rspecWindow.activateWindow()
229
230     def showResourcesWindow(self, link):
231         self.resourcesWindow.show()
232         self.resourcesWindow.resize(500, 640)
233         self.resourcesWindow.raise_()
234         self.resourcesWindow.activateWindow()
235
236     def animatePixmap(self, y):
237         self.pix.move(0, y)
238
239     def animateToScreen(self, link):
240         for screen in self.screenWidgets:
241             if link == screen.name:
242                 self.next_screen = screen
243
244         curr_screen = self.screens.currentWidget()
245
246         if self.next_screen == curr_screen:
247             self.setStatus("Already showing %s" % curr_screen.getTitleText(), timeout=1000)
248             return
249
250         # This is an optimization to have a smoother animation. We
251         # render the widget into a pixmap and animate that instead of
252         # moving the whole widget around.
253         pixmap = QPixmap(self.screens.size())
254         curr_screen.render(pixmap)
255         self.screens.setCurrentWidget(self.pix)
256         self.pix.setPixmap(pixmap)
257
258         timeLine = QTimeLine(500, self)
259         timeLine.setFrameRange(0, self.screens.height());
260         self.connect(timeLine, SIGNAL('frameChanged(int)'), self.animatePixmap)
261         self.connect(timeLine, SIGNAL('finished()'), self.toNextScreen)
262         timeLine.start()
263
264     def toNextScreen(self):
265         self.screens.setCurrentWidget(self.next_screen)
266         self.nav.setTitle(self.next_screen.getTitleText())
267
268     def setStatus(self, msg, timeout):
269         self.status.set(msg, timeout)
270
271     def nodeSelectionChanged(self, hostname):
272         if self.rspecWindow.isVisible():
273             self.rspecWindow.showNode(hostname)
274
275     def closeEvent(self, event):
276         # give the screens an opportunity to veto the close
277         for screen in self.screenWidgets:
278             if not screen.canClose():
279                 event.ignore()
280                 return
281
282         # give the screens an opportunity to close gracefully
283         for screen in self.screenWidgets:
284             screen.mainWindowClose()
285
286         QWidget.closeEvent(self, event)
287