every program does this, here is how

Thursday January 06, 2005
There is a pretty common thing that various applications do now, generally touted as a major feature, such as "tabbed browsing" or "single-window chat" or whatever. Basically applications start out like this:

MDI Windows

and then at some point later they do this:

MDI Notebook

I have been spending some of my spare time messing around with PyGTK, so here is some sample code in Python which does this thing.




import gtk

from zope.interface import Interface, implements, Attribute

class IMultiPlug(Interface):
"""Implements multi-plug stuff.
"""

def addPlug(plug, position=0):
"""
Add plug.

@param plug: implementor of IPlug

@param position: int - useful when using tabs or other visibly ordered
MPI interfaces
"""

def removePlug(plug=None):
"""
Remove a plug.

@param plug: implementor of IPlug
"""

def iterPlugs():
"""Return an iterator of all plugs
"""

class IPlug(Interface):
gtkwidget = Attribute('a gtk.Widget')
name = Attribute('a unicode name')
icon = Attribute('a gdk.Pixmap')

class Plug:
implements(IPlug)
def __init__(self, name, gtkwidget, icon=None):
self.name = name
self.gtkwidget = gtkwidget
if icon is None:
icon = gtk.icon_theme_get_default().load_icon(gtk.STOCK_YES,
64, 0)
self.icon = icon

class MultiWindow:
implements(IMultiPlug)

def __init__(self, prefix = ''):
self.windows = {}
self.plugs = []
self.prefix = prefix

def addPlug(self, plug, position=0):
win = gtk.Window()
win.set_title(self.prefix + plug.name)
self.windows[plug] = win
win.add(plug.gtkwidget)
win.show_all()
self.plugs.insert(position, plug)
def killit(ev):
self.removePlug(plug)
win.connect("delete_event", killit)
win.set_icon(plug.icon)

def iterPlugs(self):
return self.plugs[:]

def removePlug(self, plug):
for enum, oplug in enumerate(self.plugs):
if plug is oplug:
self.windows[plug].remove(plug.gtkwidget)
self.windows[plug].destroy()
self.plugs.pop(enum)
self.windows.pop(plug)
return

class MultiTab:
implements(IMultiPlug)

def __init__(self, notebook, window):
self.notebook = notebook
self.window = window
self.plugs = []

def labelFactory(self, plug):
hb = gtk.HBox()

# Icon box
icbox = gtk.Image()
scaled = plug.icon.scale_simple(16, 16, gtk.gdk.INTERP_BILINEAR)
icbox.set_from_pixbuf(scaled)
icbox.set_padding(2, 0)
hb.add(icbox)

# Label
hb.add(gtk.Label(plug.name))

# Close Button
cb = gtk.Button()
cb.set_relief(gtk.RELIEF_NONE)
im = gtk.Image()
im.set_from_pixbuf(gtk.icon_theme_get_default().load_icon(gtk.STOCK_CLOSE,
12, 0))
im.show_all()
cb.add(im)
hb.add(cb)
hb.set_child_packing(im, False, False, 0, gtk.PACK_START)

def killit(ev):
self.removePlug(plug)
cb.connect('clicked', killit)

return hb

def addPlug(self, plug, position=0):
lab = self.labelFactory(plug)
self.notebook.insert_page(plug.gtkwidget, lab, position)
self.plugs.insert(position, plug)
plug.gtkwidget.show_all()
lab.show_all()
self.window.show_all()

def removePlug(self, plug):
for enum, oplug in enumerate(self.plugs):
if oplug is plug:
self.plugs.pop(enum)
self.notebook.remove_page(enum)
break
if not self.plugs:
self.window.hide()

def iterPlugs(self):
return self.plugs[:]


class Cycler:

def __init__(self, mdi1, mdi2):
self.mdi1 = mdi1
self.mdi2 = mdi2
win = gtk.Window()
win.set_title("Cycler")
but = gtk.Button("cycle MDI")
but.set_size_request(200,200)
win.add(but)
win.connect('destroy', gtk.main_quit)
but.connect('clicked', self.swap)
win.show_all()

def swap(self, ev):
for plug in self.mdi1.iterPlugs():
self.mdi1.removePlug(plug)
self.mdi2.addPlug(plug)
self.mdi1, self.mdi2 = self.mdi2, self.mdi1

def test():
mdi1 = MultiWindow('MDI Demo: ')
n = gtk.Notebook()
nw = gtk.Window()
nw.set_title("MDI Notebook")
nw.add(n)
mdi2 = MultiTab(n, nw)

widgets = [gtk.Button('Button %d' % x) for x in range(4)]
import random
itheme = gtk.icon_theme_get_default()
for e, widget in enumerate(widgets):
widget.set_size_request(300,200)
mdi1.addPlug(Plug('Plug #%d'%e, widget, itheme.load_icon(random.choice(itheme.list_icons()),64,0) ))
Cycler(mdi1, mdi2)
gtk.main()


if __name__ == '__main__':
test()