This repository has been archived on 2024-05-09. You can view files and clone it, but cannot push or open issues/pull-requests.
ipodderx-core/BitTorrent/GUI.py

774 lines
28 KiB
Python

# The contents of this file are subject to the BitTorrent Open Source License
# Version 1.1 (the License). You may not copy or use this file, in either
# source code or executable form, except in compliance with the License. You
# may obtain a copy of the License at http://www.bittorrent.com/license/.
#
# Software distributed under the License is distributed on an AS IS basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
# written by Matt Chisholm
from __future__ import division
import gtk
import pango
import gobject
import os
import threading
assert gtk.gtk_version >= (2, 2), "GTK 2.2 or newer required"
assert gtk.pygtk_version >= (2, 2), "PyGTK 2.2 or newer required"
from BitTorrent import app_name, FAQ_URL, languages, language_names
from BitTorrent.platform import image_root, read_language_file, write_language_file
def lock_wrap(function, *args):
gtk.threads_enter()
function(*args)
gtk.threads_leave()
def gtk_wrap(function, *args):
gobject.idle_add(lock_wrap, function, *args)
SPACING = 8
WINDOW_TITLE_LENGTH = 128 # do we need this?
WINDOW_WIDTH = 600
# get screen size from GTK
d = gtk.gdk.display_get_default()
s = d.get_default_screen()
MAX_WINDOW_HEIGHT = s.get_height()
MAX_WINDOW_WIDTH = s.get_width()
if os.name == 'nt':
MAX_WINDOW_HEIGHT -= 32 # leave room for start bar (exact)
MAX_WINDOW_HEIGHT -= 32 # and window decorations (depends on windows theme)
else:
MAX_WINDOW_HEIGHT -= 32 # leave room for window decorations (could be any size)
MIN_MULTI_PANE_HEIGHT = 107
BT_TARGET_TYPE = 0
EXTERNAL_FILE_TYPE = 1
EXTERNAL_STRING_TYPE = 2
BT_TARGET = ("application/x-bittorrent" , gtk.TARGET_SAME_APP, BT_TARGET_TYPE )
EXTERNAL_FILE = ("text/uri-list" , 0 , EXTERNAL_FILE_TYPE )
#gtk(gdk actually) is totally unable to receive text drags
#of any sort in windows because they're too lazy to use OLE.
#this list is all the atoms I could possibly find so that
#url dnd works on linux from any browser.
EXTERNAL_TEXTPLAIN = ("text/plain" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_TEXT = ("TEXT" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_COMPOUND_TEXT = ("COMPOUND_TEXT" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_MOZILLA = ("text/x-moz-url" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_NETSCAPE = ("_NETSCAPE_URL" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_HTML = ("text/html" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_UNICODE = ("text/unicode" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_UTF8 = ("text/plain;charset=utf-8" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_UTF8_STRING = ("UTF8_STRING" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_STRING = ("STRING" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_OLE2_DND = ("OLE2_DND" , 0 , EXTERNAL_STRING_TYPE)
EXTERNAL_RTF = ("Rich Text Format" , 0 , EXTERNAL_STRING_TYPE)
#there should alse be text/plain;charset={current charset}
TARGET_EXTERNAL = [EXTERNAL_FILE,
EXTERNAL_TEXTPLAIN,
EXTERNAL_TEXT,
EXTERNAL_COMPOUND_TEXT,
EXTERNAL_MOZILLA,
EXTERNAL_NETSCAPE,
EXTERNAL_HTML,
EXTERNAL_UNICODE,
EXTERNAL_UTF8,
EXTERNAL_UTF8_STRING,
EXTERNAL_STRING,
EXTERNAL_OLE2_DND,
EXTERNAL_RTF]
TARGET_ALL = [BT_TARGET,
EXTERNAL_FILE,
EXTERNAL_TEXTPLAIN,
EXTERNAL_TEXT,
EXTERNAL_COMPOUND_TEXT,
EXTERNAL_MOZILLA,
EXTERNAL_NETSCAPE,
EXTERNAL_HTML,
EXTERNAL_UNICODE,
EXTERNAL_UTF8,
EXTERNAL_UTF8_STRING,
EXTERNAL_STRING,
EXTERNAL_OLE2_DND,
EXTERNAL_RTF]
# a slightly hackish but very reliable way to get OS scrollbar width
sw = gtk.ScrolledWindow()
SCROLLBAR_WIDTH = sw.size_request()[0] - 48
del sw
def align(obj,x,y):
if type(obj) is gtk.Label:
obj.set_alignment(x,y)
return obj
else:
a = gtk.Alignment(x,y,0,0)
a.add(obj)
return a
def halign(obj, amt):
return align(obj,amt,0.5)
def lalign(obj):
return halign(obj,0)
def ralign(obj):
return halign(obj,1)
def valign(obj, amt):
return align(obj,0.5,amt)
def malign(obj):
return valign(obj, 0.5)
factory = gtk.IconFactory()
# these don't seem to be documented anywhere:
# ICON_SIZE_BUTTON = 20x20
# ICON_SIZE_LARGE_TOOLBAR = 24x24
for n in 'broken finished info pause paused play queued running remove status-running status-natted status-stopped'.split():
fn = os.path.join(image_root, ("%s.png"%n))
pixbuf = gtk.gdk.pixbuf_new_from_file(fn)
set = gtk.IconSet(pixbuf)
factory.add('bt-%s'%n, set)
factory.add_default()
def load_large_toolbar_image(image, stockname):
# This is a hack to work around a bug in GTK 2.4 that causes
# gtk.ICON_SIZE_LARGE_TOOLBAR icons to be drawn at 18x18 instead
# of 24x24 under GTK 2.4 & win32
if os.name == 'nt' and gtk.gtk_version < (2, 6):
image.set_from_file(os.path.join(image_root, stockname[3:]+'.png'))
else:
image.set_from_stock(stockname, gtk.ICON_SIZE_LARGE_TOOLBAR)
def get_logo(size=32):
fn = os.path.join(image_root, 'logo', 'bittorrent_%d.png'%size)
logo = gtk.Image()
logo.set_from_file(fn)
return logo
class Size(long):
"""displays size in human-readable format"""
size_labels = ['','K','M','G','T','P','E','Z','Y']
radix = 2**10
def __new__(cls, value, precision=None):
self = long.__new__(cls, value)
return self
def __init__(self, value, precision=0):
long.__init__(self, value)
self.precision = precision
def __str__(self, precision=None):
if precision is None:
precision = self.precision
value = self
for unitname in self.size_labels:
if value < self.radix and precision < self.radix:
break
value /= self.radix
precision /= self.radix
if unitname and value < 10 and precision < 1:
return '%.1f %sB' % (value, unitname)
else:
return '%.0f %sB' % (value, unitname)
class Rate(Size):
"""displays rate in human-readable format"""
def __init__(self, value, precision=2**10):
Size.__init__(self, value, precision)
def __str__(self, precision=None):
return '%s/s'% Size.__str__(self, precision=None)
class Duration(float):
"""displays duration in human-readable format"""
def __str__(self):
if self > 365 * 24 * 60 * 60:
return '?'
elif self >= 172800:
return _("%d days") % (self//86400) # 2 days or longer
elif self >= 86400:
return _("1 day %d hours") % ((self-86400)//3600) # 1-2 days
elif self >= 3600:
return _("%d:%02d hours") % (self//3600, (self%3600)//60) # 1 h - 1 day
elif self >= 60:
return _("%d:%02d minutes") % (self//60, self%60) # 1 minute to 1 hour
elif self >= 0:
return _("%d seconds") % int(self)
else:
return _("0 seconds")
class FancyLabel(gtk.Label):
def __init__(self, label_string, *values):
self.label_string = label_string
gtk.Label.__init__(self, label_string%values)
def set_value(self, *values):
self.set_text(self.label_string%values)
class IconButton(gtk.Button):
def __init__(self, label, iconpath=None, stock=None):
gtk.Button.__init__(self)
self.hbox = gtk.HBox(spacing=5)
self.icon = gtk.Image()
if stock is not None:
self.icon.set_from_stock(stock, gtk.ICON_SIZE_BUTTON)
elif iconpath is not None:
self.icon.set_from_file(iconpath)
else:
raise TypeError, 'IconButton needs iconpath or stock'
self.hbox.pack_start(self.icon)
self.label = gtk.Label(label)
self.hbox.pack_start(self.label)
self.add(halign(self.hbox, 0.5))
class LanguageChooser(gtk.Frame):
def __init__(self):
gtk.Frame.__init__(self, "Translate %s into:" % app_name)
self.set_border_width(SPACING)
model = gtk.ListStore(*[gobject.TYPE_STRING] * 2)
default = model.append(("System default", ''))
lang = read_language_file()
for l in languages:
it = model.append((language_names[l].encode('utf8'), l))
if l == lang:
default = it
self.combo = gtk.ComboBox(model)
cell = gtk.CellRendererText()
self.combo.pack_start(cell, True)
self.combo.add_attribute(cell, 'text', 0)
if default is not None:
self.combo.set_active_iter(default)
self.combo.connect('changed', self.changed)
box = gtk.VBox(spacing=SPACING)
box.set_border_width(SPACING)
box.pack_start(self.combo, expand=False, fill=False)
l = gtk.Label("You must restart %s for the\nlanguage "
"setting to take effect." % app_name)
l.set_alignment(0,1)
l.set_line_wrap(True)
box.pack_start(l, expand=False, fill=False)
self.add(box)
def changed(self, *a):
it = self.combo.get_active_iter()
model = self.combo.get_model()
code = model.get(it, 1)[0]
write_language_file(code)
class Window(gtk.Window):
def __init__(self, *args):
apply(gtk.Window.__init__, (self,)+args)
iconname = os.path.join(image_root,'bittorrent.ico')
icon16 = gtk.gdk.pixbuf_new_from_file_at_size(iconname, 16, 16)
icon32 = gtk.gdk.pixbuf_new_from_file_at_size(iconname, 32, 32)
self.set_icon_list(icon16, icon32)
class HelpWindow(Window):
def __init__(self, main, helptext):
Window.__init__(self)
self.set_title(_("%s Help")%app_name)
self.main = main
self.set_border_width(SPACING)
self.vbox = gtk.VBox(spacing=SPACING)
self.faq_box = gtk.HBox(spacing=SPACING)
self.faq_box.pack_start(gtk.Label(_("Frequently Asked Questions:")), expand=False, fill=False)
self.faq_url = gtk.Entry()
self.faq_url.set_text(FAQ_URL)
self.faq_url.set_editable(False)
self.faq_box.pack_start(self.faq_url, expand=True, fill=True)
self.faq_button = gtk.Button(_("Go"))
self.faq_button.connect('clicked', lambda w: self.main.visit_url(FAQ_URL) )
self.faq_box.pack_start(self.faq_button, expand=False, fill=False)
self.vbox.pack_start(self.faq_box, expand=False, fill=False)
self.cmdline_args = gtk.Label(helptext)
self.cmdline_sw = ScrolledWindow()
self.cmdline_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
self.cmdline_sw.add_with_viewport(self.cmdline_args)
self.cmdline_sw.set_size_request(self.cmdline_args.size_request()[0]+SCROLLBAR_WIDTH, 200)
self.vbox.pack_start(self.cmdline_sw)
self.add(self.vbox)
self.show_all()
if self.main is not None:
self.connect('destroy', lambda w: self.main.window_closed('help'))
else:
self.connect('destroy', lambda w: gtk.main_quit())
gtk.main()
def close(self, widget=None):
self.destroy()
class ScrolledWindow(gtk.ScrolledWindow):
def scroll_to_bottom(self):
child_height = self.child.child.size_request()[1]
self.scroll_to(0, child_height)
def scroll_by(self, dx=0, dy=0):
v = self.get_vadjustment()
new_y = min(v.upper, v.value + dy)
self.scroll_to(0, new_y)
def scroll_to(self, x=0, y=0):
v = self.get_vadjustment()
child_height = self.child.child.size_request()[1]
new_adj = gtk.Adjustment(y, 0, child_height)
self.set_vadjustment(new_adj)
class AutoScrollingWindow(ScrolledWindow):
def __init__(self):
ScrolledWindow.__init__(self)
self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
gtk.DEST_DEFAULT_DROP,
TARGET_ALL,
gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)
self.connect('drag_motion' , self.drag_motion )
# self.connect('drag_data_received', self.drag_data_received)
self.vscrolltimeout = None
# def drag_data_received(self, widget, context, x, y, selection, targetType, time):
# print _("AutoScrollingWindow.drag_data_received("), widget
def drag_motion(self, widget, context, x, y, time):
v = self.get_vadjustment()
if v.page_size - y <= 10:
amount = (10 - int(v.page_size - y)) * 2
self.start_scrolling(amount)
elif y <= 10:
amount = (y - 10) * 2
self.start_scrolling(amount)
else:
self.stop_scrolling()
return True
def scroll_and_wait(self, amount, lock_held):
if not lock_held:
gtk.threads_enter()
self.scroll_by(0, amount)
if not lock_held:
gtk.threads_leave()
if self.vscrolltimeout is not None:
gobject.source_remove(self.vscrolltimeout)
self.vscrolltimeout = gobject.timeout_add(100, self.scroll_and_wait, amount, False)
#print "adding timeout", self.vscrolltimeout, amount
def start_scrolling(self, amount):
if self.vscrolltimeout is not None:
gobject.source_remove(self.vscrolltimeout)
self.scroll_and_wait(amount, True)
def stop_scrolling(self):
if self.vscrolltimeout is not None:
#print "removing timeout", self.vscrolltimeout
gobject.source_remove(self.vscrolltimeout)
self.vscrolltimeout = None
class MessageDialog(gtk.MessageDialog):
flags = gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT
def __init__(self, parent, title, message,
type=gtk.MESSAGE_ERROR,
buttons=gtk.BUTTONS_OK,
yesfunc=None, nofunc=None,
default=gtk.RESPONSE_OK
):
gtk.MessageDialog.__init__(self, parent,
self.flags,
type, buttons, message)
self.set_size_request(-1, -1)
self.set_resizable(False)
self.set_title(title)
if default is not None:
self.set_default_response(default)
self.label.set_line_wrap(True)
self.connect('response', self.callback)
self.yesfunc = yesfunc
self.nofunc = nofunc
if os.name == 'nt':
parent.present()
self.show_all()
def callback(self, widget, response_id, *args):
if ((response_id == gtk.RESPONSE_OK or
response_id == gtk.RESPONSE_YES) and
self.yesfunc is not None):
self.yesfunc()
if ((response_id == gtk.RESPONSE_CANCEL or
response_id == gtk.RESPONSE_NO )
and self.nofunc is not None):
self.nofunc()
self.destroy()
class ErrorMessageDialog(MessageDialog):
flags = gtk.DIALOG_DESTROY_WITH_PARENT
if gtk.pygtk_version < (2, 4, 1):
class FileSelection(gtk.FileSelection):
def __init__(self, main, title='', fullname='', got_location_func=None, no_location_func=None, got_multiple_location_func=None, show=True):
gtk.FileSelection.__init__(self)
from BitTorrent.ConvertedMetainfo import filesystem_encoding
self.fsenc = filesystem_encoding
try:
fullname.decode('utf8')
except:
fullname = fullname.decode(self.fsenc)
self.main = main
self.set_modal(True)
self.set_destroy_with_parent(True)
self.set_title(title)
if (got_location_func is None and
got_multiple_location_func is not None):
self.set_select_multiple(True)
self.got_location_func = got_location_func
self.no_location_func = no_location_func
self.got_multiple_location_func = got_multiple_location_func
self.cancel_button.connect("clicked", self.destroy)
self.d_handle = self.connect('destroy', self.no_location)
self.ok_button.connect("clicked", self.done)
self.set_filename(fullname)
if show:
self.show()
def no_location(self, widget=None):
if self.no_location_func is not None:
self.no_location_func()
def done(self, widget=None):
if self.get_select_multiple():
self.got_multiple_location()
else:
self.got_location()
self.disconnect(self.d_handle)
self.destroy()
def got_location(self):
if self.got_location_func is not None:
name = self.get_filename()
self.got_location_func(name)
def got_multiple_location(self):
if self.got_multiple_location_func is not None:
names = self.get_selections()
self.got_multiple_location_func(names)
def destroy(self, widget=None):
gtk.FileSelection.destroy(self)
def close_child_windows(self):
self.no_location()
def close(self, widget=None):
self.destroy()
class OpenFileSelection(FileSelection):
pass
class SaveFileSelection(FileSelection):
pass
class ChooseFolderSelection(FileSelection):
pass
class CreateFolderSelection(FileSelection):
pass
class FileOrFolderSelection(FileSelection):
pass
else:
class FileSelection(gtk.FileChooserDialog):
def __init__(self, action, main, title='', fullname='',
got_location_func=None, no_location_func=None,
got_multiple_location_func=None, show=True):
gtk.FileChooserDialog.__init__(self, action=action, title=title,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
from BitTorrent.ConvertedMetainfo import filesystem_encoding
self.fsenc = filesystem_encoding
try:
fullname.decode('utf8')
except:
fullname = fullname.decode(self.fsenc)
self.set_default_response(gtk.RESPONSE_OK)
if action == gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER:
self.convert_button_box = gtk.HBox()
self.convert_button = gtk.Button(_("Choose an existing folder..."))
self.convert_button.connect('clicked', self.change_action)
self.convert_button_box.pack_end(self.convert_button,
expand=False,
fill=False)
self.convert_button_box.show_all()
self.set_extra_widget(self.convert_button_box)
elif action == gtk.FILE_CHOOSER_ACTION_OPEN:
self.all_filter = gtk.FileFilter()
self.all_filter.add_pattern('*')
self.all_filter.set_name(_("All Files"))
self.add_filter(self.all_filter)
self.torrent_filter = gtk.FileFilter()
self.torrent_filter.add_pattern('*.torrent')
self.torrent_filter.add_mime_type('application/x-bittorrent')
self.torrent_filter.set_name(_("Torrents"))
self.add_filter(self.torrent_filter)
self.set_filter(self.torrent_filter)
self.main = main
self.set_modal(True)
self.set_destroy_with_parent(True)
if fullname:
if action == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER:
if gtk.gtk_version < (2,6):
fullname = fullname.encode(self.fsenc)
self.set_filename(fullname)
elif action == gtk.FILE_CHOOSER_ACTION_OPEN:
if fullname[-1] != os.sep:
fullname = fullname + os.sep
path, filename = os.path.split(fullname)
if gtk.gtk_version < (2,6):
path = path.encode(self.fsenc)
self.set_current_folder(path)
else:
if fullname[-1] == os.sep:
fullname = fullname[:-1]
path, filename = os.path.split(fullname)
if gtk.gtk_version < (2,8):
path = path.encode(self.fsenc)
self.set_current_folder(path)
self.set_current_name(filename)
if got_multiple_location_func is not None:
self.got_multiple_location_func = got_multiple_location_func
self.set_select_multiple(True)
self.got_location_func = got_location_func
self.no_location_func = no_location_func
self.connect('response', self.got_response)
self.d_handle = self.connect('destroy', self.got_response,
gtk.RESPONSE_CANCEL)
if show:
self.show()
def change_action(self, widget):
if self.get_action() == gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER:
self.convert_button.set_label(_("Create a new folder..."))
self.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
elif self.get_action() == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER:
self.convert_button.set_label(_("Choose an existing folder..."))
self.set_action(gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER)
def got_response(self, widget, response):
if response == gtk.RESPONSE_OK:
if self.get_select_multiple():
if self.got_multiple_location_func is not None:
self.got_multiple_location_func(self.get_filenames())
elif self.got_location_func is not None:
fn = self.get_filename()
if fn:
self.got_location_func(fn)
else:
self.no_location_func()
else:
if self.no_location_func is not None:
self.no_location_func()
self.disconnect(self.d_handle)
self.destroy()
def done(self, widget=None):
if self.get_select_multiple():
self.got_multiple_location()
else:
self.got_location()
self.disconnect(self.d_handle)
self.destroy()
def close_child_windows(self):
self.destroy()
def close(self, widget=None):
self.destroy()
class OpenFileSelection(FileSelection):
def __init__(self, *args, **kwargs):
FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, *args,
**kwargs)
class SaveFileSelection(FileSelection):
def __init__(self, *args, **kwargs):
FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_SAVE, *args,
**kwargs)
class ChooseFolderSelection(FileSelection):
def __init__(self, *args, **kwargs):
FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
*args, **kwargs)
class CreateFolderSelection(FileSelection):
def __init__(self, *args, **kwargs):
FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_CREATE_FOLDER,
*args, **kwargs)
class FileOrFolderSelection(FileSelection):
def __init__(self, *args, **kwargs):
FileSelection.__init__(self, gtk.FILE_CHOOSER_ACTION_OPEN, *args,
**kwargs)
self.select_file = _("Select a file" )
self.select_folder = _("Select a folder")
self.convert_button_box = gtk.HBox()
self.convert_button = gtk.Button(self.select_folder)
self.convert_button.connect('clicked', self.change_action)
self.convert_button_box.pack_end(self.convert_button,
expand=False,
fill=False)
self.convert_button_box.show_all()
self.set_extra_widget(self.convert_button_box)
self.reset_by_action()
self.set_filter(self.all_filter)
def change_action(self, widget):
if self.get_action() == gtk.FILE_CHOOSER_ACTION_OPEN:
self.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
elif self.get_action() == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER:
self.set_action(gtk.FILE_CHOOSER_ACTION_OPEN)
self.reset_by_action()
def reset_by_action(self):
if self.get_action() == gtk.FILE_CHOOSER_ACTION_OPEN:
self.convert_button.set_label(self.select_folder)
self.set_title(self.select_file)
elif self.get_action() == gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER:
self.convert_button.set_label(self.select_file)
self.set_title(self.select_folder)
def set_title(self, title):
mytitle = title + ':'
FileSelection.set_title(self, mytitle)
class PaddedHSeparator(gtk.VBox):
def __init__(self, spacing=SPACING):
gtk.VBox.__init__(self)
self.sep = gtk.HSeparator()
self.pack_start(self.sep, expand=False, fill=False, padding=spacing)
self.show_all()
class HSeparatedBox(gtk.VBox):
def new_separator(self):
return PaddedHSeparator()
def _get_children(self):
return gtk.VBox.get_children(self)
def get_children(self):
return self._get_children()[0::2]
def _reorder_child(self, child, index):
gtk.VBox.reorder_child(self, child, index)
def reorder_child(self, child, index):
children = self._get_children()
oldindex = children.index(child)
sep = None
if oldindex == len(children) - 1:
sep = children[oldindex-1]
else:
sep = children[oldindex+1]
newindex = index*2
if newindex == len(children) -1:
self._reorder_child(sep, newindex-1)
self._reorder_child(child, newindex)
else:
self._reorder_child(child, newindex)
self._reorder_child(sep, newindex+1)
def pack_start(self, widget, *args, **kwargs):
if len(self._get_children()):
s = self.new_separator()
gtk.VBox.pack_start(self, s, *args, **kwargs)
s.show()
gtk.VBox.pack_start(self, widget, *args, **kwargs)
def pack_end(self, widget, *args, **kwargs):
if len(self._get_children()):
s = self.new_separator()
gtk.VBox.pack_start(self, s, *args, **kwargs)
s.show()
gtk.VBox.pack_end(self, widget, *args, **kwargs)
def remove(self, widget):
children = self._get_children()
if len(children) > 1:
index = children.index(widget)
if index == 0:
sep = children[index+1]
else:
sep = children[index-1]
sep.destroy()
gtk.VBox.remove(self, widget)