more NEWS items
[buildbot.git] / buildbot / clients / gtkPanes.py
blob468c5344f1f0863b943f624ab5e96d53b59c4c3c
1 #! /usr/bin/python
3 from twisted.internet import gtk2reactor
4 gtk2reactor.install()
6 from twisted.internet import reactor
8 import sys, time
10 import pygtk
11 pygtk.require("2.0")
12 import gobject, gtk
13 assert(gtk.Window) # in gtk1 it's gtk.GtkWindow
15 from twisted.spread import pb
17 #from buildbot.clients.base import Builder, Client
18 from buildbot.clients.base import TextClient
19 from buildbot.util import now
21 '''
22 class Pane:
23 def __init__(self):
24 pass
26 class OneRow(Pane):
27 """This is a one-row status bar. It has one square per Builder, and that
28 square is either red, yellow, or green. """
30 def __init__(self):
31 Pane.__init__(self)
32 self.widget = gtk.VBox(gtk.FALSE, 2)
33 self.nameBox = gtk.HBox(gtk.TRUE)
34 self.statusBox = gtk.HBox(gtk.TRUE)
35 self.widget.add(self.nameBox)
36 self.widget.add(self.statusBox)
37 self.widget.show_all()
38 self.builders = []
40 def getWidget(self):
41 return self.widget
42 def addBuilder(self, builder):
43 print "OneRow.addBuilder"
44 # todo: ordering. Should follow the order in which they were added
45 # to the original BotMaster
46 self.builders.append(builder)
47 # add the name to the left column, and a label (with background) to
48 # the right
49 name = gtk.Label(builder.name)
50 status = gtk.Label('??')
51 status.set_size_request(64,64)
52 box = gtk.EventBox()
53 box.add(status)
54 name.show()
55 box.show_all()
56 self.nameBox.add(name)
57 self.statusBox.add(box)
58 builder.haveSomeWidgets([name, status, box])
60 class R2Builder(Builder):
61 def start(self):
62 self.nameSquare.set_text(self.name)
63 self.statusSquare.set_text("???")
64 self.subscribe()
65 def haveSomeWidgets(self, widgets):
66 self.nameSquare, self.statusSquare, self.statusBox = widgets
68 def remote_newLastBuildStatus(self, event):
69 color = None
70 if event:
71 text = "\n".join(event.text)
72 color = event.color
73 else:
74 text = "none"
75 self.statusSquare.set_text(text)
76 if color:
77 print "color", color
78 self.statusBox.modify_bg(gtk.STATE_NORMAL,
79 gtk.gdk.color_parse(color))
81 def remote_currentlyOffline(self):
82 self.statusSquare.set_text("offline")
83 def remote_currentlyIdle(self):
84 self.statusSquare.set_text("idle")
85 def remote_currentlyWaiting(self, seconds):
86 self.statusSquare.set_text("waiting")
87 def remote_currentlyInterlocked(self):
88 self.statusSquare.set_text("interlocked")
89 def remote_currentlyBuilding(self, eta):
90 self.statusSquare.set_text("building")
93 class CompactRow(Pane):
94 def __init__(self):
95 Pane.__init__(self)
96 self.widget = gtk.VBox(gtk.FALSE, 3)
97 self.nameBox = gtk.HBox(gtk.TRUE, 2)
98 self.lastBuildBox = gtk.HBox(gtk.TRUE, 2)
99 self.statusBox = gtk.HBox(gtk.TRUE, 2)
100 self.widget.add(self.nameBox)
101 self.widget.add(self.lastBuildBox)
102 self.widget.add(self.statusBox)
103 self.widget.show_all()
104 self.builders = []
106 def getWidget(self):
107 return self.widget
109 def addBuilder(self, builder):
110 self.builders.append(builder)
112 name = gtk.Label(builder.name)
113 name.show()
114 self.nameBox.add(name)
116 last = gtk.Label('??')
117 last.set_size_request(64,64)
118 lastbox = gtk.EventBox()
119 lastbox.add(last)
120 lastbox.show_all()
121 self.lastBuildBox.add(lastbox)
123 status = gtk.Label('??')
124 status.set_size_request(64,64)
125 statusbox = gtk.EventBox()
126 statusbox.add(status)
127 statusbox.show_all()
128 self.statusBox.add(statusbox)
130 builder.haveSomeWidgets([name, last, lastbox, status, statusbox])
132 def removeBuilder(self, name, builder):
133 self.nameBox.remove(builder.nameSquare)
134 self.lastBuildBox.remove(builder.lastBuildBox)
135 self.statusBox.remove(builder.statusBox)
136 self.builders.remove(builder)
138 class CompactBuilder(Builder):
139 def setup(self):
140 self.timer = None
141 self.text = []
142 self.eta = None
143 def start(self):
144 self.nameSquare.set_text(self.name)
145 self.statusSquare.set_text("???")
146 self.subscribe()
147 def haveSomeWidgets(self, widgets):
148 (self.nameSquare,
149 self.lastBuildSquare, self.lastBuildBox,
150 self.statusSquare, self.statusBox) = widgets
152 def remote_currentlyOffline(self):
153 self.eta = None
154 self.stopTimer()
155 self.statusSquare.set_text("offline")
156 self.statusBox.modify_bg(gtk.STATE_NORMAL,
157 gtk.gdk.color_parse("red"))
158 def remote_currentlyIdle(self):
159 self.eta = None
160 self.stopTimer()
161 self.statusSquare.set_text("idle")
162 def remote_currentlyWaiting(self, seconds):
163 self.nextBuild = now() + seconds
164 self.startTimer(self.updateWaiting)
165 def remote_currentlyInterlocked(self):
166 self.stopTimer()
167 self.statusSquare.set_text("interlocked")
168 def startTimer(self, func):
169 # the func must clear self.timer and return gtk.FALSE when the event
170 # has arrived
171 self.stopTimer()
172 self.timer = gtk.timeout_add(1000, func)
173 func()
174 def stopTimer(self):
175 if self.timer:
176 gtk.timeout_remove(self.timer)
177 self.timer = None
178 def updateWaiting(self):
179 when = self.nextBuild
180 if now() < when:
181 next = time.strftime("%H:%M:%S", time.localtime(when))
182 secs = "[%d seconds]" % (when - now())
183 self.statusSquare.set_text("waiting\n%s\n%s" % (next, secs))
184 return gtk.TRUE # restart timer
185 else:
186 # done
187 self.statusSquare.set_text("waiting\n[RSN]")
188 self.timer = None
189 return gtk.FALSE
191 def remote_currentlyBuilding(self, eta):
192 self.stopTimer()
193 self.statusSquare.set_text("building")
194 if eta:
195 d = eta.callRemote("subscribe", self, 5)
197 def remote_newLastBuildStatus(self, event):
198 color = None
199 if event:
200 text = "\n".join(event.text)
201 color = event.color
202 else:
203 text = "none"
204 if not color: color = "gray"
205 self.lastBuildSquare.set_text(text)
206 self.lastBuildBox.modify_bg(gtk.STATE_NORMAL,
207 gtk.gdk.color_parse(color))
209 def remote_newEvent(self, event):
210 assert(event.__class__ == GtkUpdatingEvent)
211 self.current = event
212 event.builder = self
213 self.text = event.text
214 if not self.text: self.text = ["idle"]
215 self.eta = None
216 self.stopTimer()
217 self.updateText()
218 color = event.color
219 if not color: color = "gray"
220 self.statusBox.modify_bg(gtk.STATE_NORMAL,
221 gtk.gdk.color_parse(color))
223 def updateCurrent(self):
224 text = self.current.text
225 if text:
226 self.text = text
227 self.updateText()
228 color = self.current.color
229 if color:
230 self.statusBox.modify_bg(gtk.STATE_NORMAL,
231 gtk.gdk.color_parse(color))
232 def updateText(self):
233 etatext = []
234 if self.eta:
235 etatext = [time.strftime("%H:%M:%S", time.localtime(self.eta))]
236 if now() > self.eta:
237 etatext += ["RSN"]
238 else:
239 seconds = self.eta - now()
240 etatext += ["[%d secs]" % seconds]
241 text = "\n".join(self.text + etatext)
242 self.statusSquare.set_text(text)
243 def updateTextTimer(self):
244 self.updateText()
245 return gtk.TRUE # restart timer
247 def remote_progress(self, seconds):
248 if seconds == None:
249 self.eta = None
250 else:
251 self.eta = now() + seconds
252 self.startTimer(self.updateTextTimer)
253 self.updateText()
254 def remote_finished(self, eta):
255 self.eta = None
256 self.stopTimer()
257 self.updateText()
258 eta.callRemote("unsubscribe", self)
261 class Box:
262 def __init__(self, text="?"):
263 self.text = text
264 self.box = gtk.EventBox()
265 self.label = gtk.Label(text)
266 self.box.add(self.label)
267 self.box.set_size_request(64,64)
268 self.timer = None
270 def getBox(self):
271 return self.box
273 def setText(self, text):
274 self.text = text
275 self.label.set_text(text)
277 def setColor(self, color):
278 if not color:
279 return
280 self.box.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(color))
282 def setETA(self, eta):
283 if eta:
284 self.when = now() + eta
285 self.startTimer()
286 else:
287 self.stopTimer()
289 def startTimer(self):
290 self.stopTimer()
291 self.timer = gobject.timeout_add(1000, self.update)
292 self.update()
294 def stopTimer(self):
295 if self.timer:
296 gobject.source_remove(self.timer)
297 self.timer = None
298 self.label.set_text(self.text)
300 def update(self):
301 if now() < self.when:
302 next = time.strftime("%H:%M:%S", time.localtime(self.when))
303 secs = "[%d secs]" % (self.when - now())
304 self.label.set_text("%s\n%s\n%s" % (self.text, next, secs))
305 return True # restart timer
306 else:
307 # done
308 self.label.set_text("%s\n[soon]\n[overdue]" % (self.text,))
309 self.timer = None
310 return False
314 class ThreeRowBuilder:
315 def __init__(self, name, ref):
316 self.name = name
318 self.last = Box()
319 self.current = Box()
320 self.step = Box("idle")
321 self.step.setColor("white")
323 self.ref = ref
325 def getBoxes(self):
326 return self.last.getBox(), self.current.getBox(), self.step.getBox()
328 def getLastBuild(self):
329 d = self.ref.callRemote("getLastFinishedBuild")
330 d.addCallback(self.gotLastBuild)
331 def gotLastBuild(self, build):
332 if build:
333 build.callRemote("getText").addCallback(self.gotLastText)
334 build.callRemote("getColor").addCallback(self.gotLastColor)
336 def gotLastText(self, text):
337 self.last.setText("\n".join(text))
338 def gotLastColor(self, color):
339 self.last.setColor(color)
341 def getState(self):
342 self.ref.callRemote("getState").addCallback(self.gotState)
343 def gotState(self, res):
344 state, ETA, builds = res
345 # state is one of: offline, idle, waiting, interlocked, building
346 # TODO: ETA is going away, you have to look inside the builds to get
347 # that value
348 currentmap = {"offline": "red",
349 "idle": "white",
350 "waiting": "yellow",
351 "interlocked": "yellow",
352 "building": "yellow",}
353 text = state
354 self.current.setColor(currentmap[state])
355 if ETA is not None:
356 text += "\nETA=%s secs" % ETA
357 self.current.setText(state)
359 def buildStarted(self, build):
360 print "[%s] buildStarted" % (self.name,)
361 self.current.setColor("yellow")
363 def buildFinished(self, build, results):
364 print "[%s] buildFinished: %s" % (self.name, results)
365 self.gotLastBuild(build)
366 self.current.setColor("white")
367 self.current.stopTimer()
369 def buildETAUpdate(self, eta):
370 print "[%s] buildETAUpdate: %s" % (self.name, eta)
371 self.current.setETA(eta)
374 def stepStarted(self, stepname, step):
375 print "[%s] stepStarted: %s" % (self.name, stepname)
376 self.step.setText(stepname)
377 self.step.setColor("yellow")
378 def stepFinished(self, stepname, step, results):
379 print "[%s] stepFinished: %s %s" % (self.name, stepname, results)
380 self.step.setText("idle")
381 self.step.setColor("white")
382 self.step.stopTimer()
383 def stepETAUpdate(self, stepname, eta):
384 print "[%s] stepETAUpdate: %s %s" % (self.name, stepname, eta)
385 self.step.setETA(eta)
388 class ThreeRowClient(pb.Referenceable):
389 def __init__(self, window):
390 self.window = window
391 self.buildernames = []
392 self.builders = {}
394 def connected(self, ref):
395 print "connected"
396 self.ref = ref
397 self.pane = gtk.VBox(False, 2)
398 self.table = gtk.Table(1+3, 1)
399 self.pane.add(self.table)
400 self.window.vb.add(self.pane)
401 self.pane.show_all()
402 ref.callRemote("subscribe", "logs", 5, self)
404 def removeTable(self):
405 for child in self.table.get_children():
406 self.table.remove(child)
407 self.pane.remove(self.table)
409 def makeTable(self):
410 columns = len(self.builders)
411 self.table = gtk.Table(2, columns)
412 self.pane.add(self.table)
413 for i in range(len(self.buildernames)):
414 name = self.buildernames[i]
415 b = self.builders[name]
416 last,current,step = b.getBoxes()
417 self.table.attach(gtk.Label(name), i, i+1, 0, 1)
418 self.table.attach(last, i, i+1, 1, 2,
419 xpadding=1, ypadding=1)
420 self.table.attach(current, i, i+1, 2, 3,
421 xpadding=1, ypadding=1)
422 self.table.attach(step, i, i+1, 3, 4,
423 xpadding=1, ypadding=1)
424 self.table.show_all()
426 def rebuildTable(self):
427 self.removeTable()
428 self.makeTable()
430 def remote_builderAdded(self, buildername, builder):
431 print "builderAdded", buildername
432 assert buildername not in self.buildernames
433 self.buildernames.append(buildername)
435 b = ThreeRowBuilder(buildername, builder)
436 self.builders[buildername] = b
437 self.rebuildTable()
438 b.getLastBuild()
439 b.getState()
441 def remote_builderRemoved(self, buildername):
442 del self.builders[buildername]
443 self.buildernames.remove(buildername)
444 self.rebuildTable()
446 def remote_builderChangedState(self, name, state, eta):
447 self.builders[name].gotState((state, eta, None))
448 def remote_buildStarted(self, name, build):
449 self.builders[name].buildStarted(build)
450 def remote_buildFinished(self, name, build, results):
451 self.builders[name].buildFinished(build, results)
453 def remote_buildETAUpdate(self, name, build, eta):
454 self.builders[name].buildETAUpdate(eta)
455 def remote_stepStarted(self, name, build, stepname, step):
456 self.builders[name].stepStarted(stepname, step)
457 def remote_stepFinished(self, name, build, stepname, step, results):
458 self.builders[name].stepFinished(stepname, step, results)
460 def remote_stepETAUpdate(self, name, build, stepname, step,
461 eta, expectations):
462 # expectations is a list of (metricname, current_value,
463 # expected_value) tuples, so that we could show individual progress
464 # meters for each metric
465 self.builders[name].stepETAUpdate(stepname, eta)
467 def remote_logStarted(self, buildername, build, stepname, step,
468 logname, log):
469 pass
471 def remote_logFinished(self, buildername, build, stepname, step,
472 logname, log):
473 pass
476 class GtkClient(TextClient):
477 ClientClass = ThreeRowClient
479 def __init__(self, master):
480 self.master = master
482 w = gtk.Window()
483 self.w = w
484 #w.set_size_request(64,64)
485 w.connect('destroy', lambda win: gtk.main_quit())
486 self.vb = gtk.VBox(False, 2)
487 self.status = gtk.Label("unconnected")
488 self.vb.add(self.status)
489 self.listener = self.ClientClass(self)
490 w.add(self.vb)
491 w.show_all()
493 def connected(self, ref):
494 self.status.set_text("connected")
495 TextClient.connected(self, ref)
498 def addBuilder(self, name, builder):
499 Client.addBuilder(self, name, builder)
500 self.pane.addBuilder(builder)
501 def removeBuilder(self, name):
502 self.pane.removeBuilder(name, self.builders[name])
503 Client.removeBuilder(self, name)
505 def startConnecting(self, master):
506 self.master = master
507 Client.startConnecting(self, master)
508 self.status.set_text("connecting to %s.." % master)
509 def connected(self, remote):
510 Client.connected(self, remote)
511 self.status.set_text(self.master)
512 remote.notifyOnDisconnect(self.disconnected)
513 def disconnected(self, remote):
514 self.status.set_text("disconnected, will retry")
517 def main():
518 master = "localhost:8007"
519 if len(sys.argv) > 1:
520 master = sys.argv[1]
521 c = GtkClient(master)
522 c.run()
524 if __name__ == '__main__':
525 main()