gladereactor.py   [plain text]


# Twisted, the Framework of Your Internet
# Copyright (C) 2001 Matthew W. Lefkowitz
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of version 2.1 of the GNU Lesser General Public
# License as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

"""
A modified gtk2 reactor with a Glade dialog in-process that allows you to stop,
suspend, resume and inspect transports interactively.
"""

__all__ = ['install']

# Twisted Imports
from twisted.python import log, threadable, runtime, failure, util, reflect, components
from twisted.internet.gtk2reactor import Gtk2Reactor as sup

import gtk
import gobject
import gtk.glade

COLUMN_DESCRIPTION = 0
COLUMN_TRANSPORT = 1
COLUMN_READING = 2
COLUMN_WRITING = 3


class GladeReactor(sup):
    """GTK+-2 event loop reactor with GUI.
    """

    def listenTCP(self, port, factory, backlog=5, interface=''):
        from _inspectro import LoggingFactory
        factory = LoggingFactory(factory)
        return sup.listenTCP(self, port, factory, backlog, interface)
    
    def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
        from _inspectro import LoggingFactory
        factory = LoggingFactory(factory)
        return sup.connectTCP(self, host, port, factory, timeout, bindAddress)

    def listenSSL(self, port, factory, contextFactory, backlog=5, interface=''):
        from _inspectro import LoggingFactory
        factory = LoggingFactory(factory)
        return sup.listenSSL(self, port, factory, contextFactory, backlog, interface)

    def connectSSL(self, host, port, factory, contextFactory, timeout=30, bindAddress=None):
        from _inspectro import LoggingFactory
        factory = LoggingFactory(factory)
        return sup.connectSSL(self, host, port, factory, contextFactory, timeout, bindAddress)

    def connectUNIX(self, address, factory, timeout=30):
        from _inspectro import LoggingFactory
        factory = LoggingFactory(factory)
        return sup.connectUNIX(self, address, factory, timeout)

    def listenUNIX(self, address, factory, backlog=5, mode=0666):
        from _inspectro import LoggingFactory
        factory = LoggingFactory(factory)
        return sup.listenUNIX(self, address, factory, backlog, mode)

    def on_disconnect_clicked(self, w):
        store, iter = self.servers.get_selection().get_selected()
        store[iter][COLUMN_TRANSPORT].loseConnection()

    def on_viewlog_clicked(self, w):
        store, iter = self.servers.get_selection().get_selected()
        data = store[iter][1]
        from _inspectro import LogViewer
        if hasattr(data, "protocol") and not data.protocol.logViewer:
            LogViewer(data.protocol)
    
    def on_inspect_clicked(self, w):
        store, iter = self.servers.get_selection().get_selected()
        data = store[iter]
        from _inspectro import Inspectro
        Inspectro(data[1])

    def on_suspend_clicked(self, w):
        store, iter = self.servers.get_selection().get_selected()
        data = store[iter]
        sup.removeReader(self, data[1])
        sup.removeWriter(self, data[1])
        if data[COLUMN_DESCRIPTION].endswith('(suspended)'):
            if data[COLUMN_READING]:
                sup.addReader(self, data[COLUMN_TRANSPORT])
            if data[COLUMN_WRITING]:
                sup.addWriter(self, data[COLUMN_TRANSPORT])
            data[COLUMN_DESCRIPTION] = str(data[COLUMN_TRANSPORT])
            self.toggle_suspend(1)
        else:
            data[0] += ' (suspended)'
            self.toggle_suspend(0)

    def toggle_suspend(self, suspending=0):
        stock, nonstock = [('gtk-redo', 'Resume'),
                           ('gtk-undo', 'Suspend')][suspending]
        b = self.xml.get_widget("suspend")
        b.set_use_stock(1)
        b.set_label(stock)
        b.get_child().get_child().get_children()[1].set_label(nonstock)

    def servers_selection_changed(self, w):
        store, iter = w.get_selected()
        if iter is None:
            self.xml.get_widget("suspend").set_sensitive(0)
            self.xml.get_widget('disconnect').set_sensitive(0)
        else:
            data = store[iter]
            self.toggle_suspend(not 
                data[COLUMN_DESCRIPTION].endswith('(suspended)'))
            self.xml.get_widget("suspend").set_sensitive(1)
            self.xml.get_widget('disconnect').set_sensitive(1)

    def on_quit_clicked(self, w):
        self.stop()

    def __init__(self):
        self.xml = gtk.glade.XML(util.sibpath(__file__,"gladereactor.glade"))
        d = {}
        for m in reflect.prefixedMethods(self, "on_"):
            d[m.im_func.__name__] = m
        self.xml.signal_autoconnect(d)
        self.xml.get_widget('window1').connect('destroy',
                                               lambda w: self.stop())
        self.servers = self.xml.get_widget("servertree")
        sel = self.servers.get_selection()
        sel.set_mode(gtk.SELECTION_SINGLE)
        sel.connect("changed",
                    self.servers_selection_changed)
        ## argh coredump: self.servers_selection_changed(sel)
        self.xml.get_widget('suspend').set_sensitive(0)
        self.xml.get_widget('disconnect').set_sensitive(0)
        # setup model, connect it to my treeview
        self.model = gtk.ListStore(str, object, gobject.TYPE_BOOLEAN,
                                   gobject.TYPE_BOOLEAN)
        self.servers.set_model(self.model)
        self.servers.set_reorderable(1)
        self.servers.set_headers_clickable(1)
        # self.servers.set_headers_draggable(1)
        # add a column
        for col in [
            gtk.TreeViewColumn('Server',
                               gtk.CellRendererText(),
                               text=0),
            gtk.TreeViewColumn('Reading',
                               gtk.CellRendererToggle(),
                               active=2),
            gtk.TreeViewColumn('Writing',
                               gtk.CellRendererToggle(),
                               active=3)]:
            
            self.servers.append_column(col)
            col.set_resizable(1)
        sup.__init__(self)

    def addReader(self, reader):
        sup.addReader(self, reader)
##      gtk docs suggest this - but it's stupid
##         self.model.set(self.model.append(),
##                        0, str(reader),
##                        1, reader)
        self._maybeAddServer(reader, read=1)

    def _goAway(self,reader):
        for p in range(len(self.model)):
            if self.model[p][1] == reader:
                self.model.remove(self.model.get_iter_from_string(str(p)))
                return


    def _maybeAddServer(self, reader, read=0, write=0):
        p = 0
        for x in self.model:
            if x[1] == reader:
                if reader == 0:
                    reader += 1
                x[2] += read
                x[3] += write
                x[2] = max(x[2],0)
                x[3] = max(x[3],0)
                
                if not (x[2] or x[3]):
                    x[0] = x[0] + '(disconnected)'
                    self.callLater(5, self._goAway, reader)
                return
            p += 1
        else:
            read = max(read,0)
            write = max(write, 0)
            if read or write:
                self.model.append((reader,reader,read,write))

    def addWriter(self, writer):
        sup.addWriter(self, writer)
        self._maybeAddServer(writer, write=1)

    def removeReader(self, reader):
        sup.removeReader(self, reader)
        self._maybeAddServer(reader, read=-1)

    def removeWriter(self, writer):
        sup.removeWriter(self, writer)
        self._maybeAddServer(writer, write=-1)

    def crash(self):
        gtk.main_quit()

    def run(self, installSignalHandlers=1):
        self.startRunning(installSignalHandlers=installSignalHandlers)
        self.simulate()
        gtk.main()


def install():
    """Configure the twisted mainloop to be run inside the gtk mainloop.
    """
    reactor = GladeReactor()
    from twisted.internet.main import installReactor
    installReactor(reactor)
    return reactor