#!/usr/local/bin/python2.3

# 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 Uoti Urpala and Matt Chisholm

from __future__ import division

from BitTorrent.platform import install_translation
install_translation()

import sys

assert sys.version_info >= (2, 3), _("Install Python 2.3 or greater")

import itertools
import math
import os
import threading
import datetime
import random
import gtk
import pango
import gobject
import webbrowser
import atexit

assert gtk.pygtk_version >= (2, 4), _("PyGTK 2.4 or newer required")

from BitTorrent import HELP_URL, DONATE_URL, SEARCH_URL, version, languages, branch
from BitTorrent import zurllib
from BitTorrent import configfile
from BitTorrent.parseargs import makeHelp
from BitTorrent.defaultargs import get_defaults
from BitTorrent import TorrentQueue
from BitTorrent.TorrentQueue import RUNNING, RUN_QUEUED, QUEUED, KNOWN, ASKING_LOCATION
from BitTorrent.controlsocket import ControlSocket
from BitTorrent import BTFailure, INFO, WARNING, ERROR, CRITICAL
from BitTorrent.prefs import Preferences
from BitTorrent import LaunchPath
from BitTorrent import Desktop
from BitTorrent import ClientIdentifier
from BitTorrent import GetTorrent
from BitTorrent import NewVersion
from BitTorrent.platform import doc_root, spawn, path_wrap, os_version, is_frozen_exe
from BitTorrent.GUI import *

defaults = get_defaults('bittorrent')
defaults.extend((('donated' , '', ''), # the version that the user last donated for
                 ('notified', '', ''), # the version that the user was last notified of
                 ))

NAG_FREQUENCY = 3
PORT_RANGE = 5

defconfig = dict([(name, value) for (name, value, doc) in defaults])
del name, value, doc

ui_options = [
    'max_upload_rate'       ,
    'minport'               ,
    'maxport'               ,
    'next_torrent_time'     ,
    'next_torrent_ratio'    ,
    'last_torrent_ratio'    ,
    'seed_forever'          ,
    'seed_last_forever'     ,
    'ask_for_save'          ,
    'save_in'               ,
    'open_from'             ,
    'ip'                    ,
    'start_torrent_behavior',
    ]
advanced_ui_options_index = len(ui_options)
ui_options.extend([
    'min_uploads'     ,
    'max_uploads'     ,
    'max_initiate'    ,
    'max_allow_in'    ,
    'max_files_open'  ,
    'forwarded_port'  ,
    'display_interval',
    'donated'         ,
    'notified'        ,
    ])


if is_frozen_exe:
    ui_options.append('progressbar_hack')
    defproghack = 0
    if os_version == 'XP':
        # turn on progress bar hack by default for Win XP 
        defproghack = 1
    defaults.extend((('progressbar_hack' , defproghack, ''),)) 
                     

def find_dir(path):
    if os.path.isdir(path):
        return path
    directory, garbage = os.path.split(path)
    while directory:
        if os.access(directory, os.F_OK) and os.access(directory, os.W_OK):
            return directory
        directory, garbage = os.path.split(directory)
        if garbage == '':
            break        
    return None

def smart_dir(path):
    path = find_dir(path)
    if path is None:
        path = Desktop.desktop
    return path

class MenuItem(gtk.MenuItem): 
    def __init__(self, label, accel_group=None, func=None):
        gtk.MenuItem.__init__(self, label)
        if func is not None:
            self.connect("activate", func)
        else:
            self.set_sensitive(False)

        if accel_group is not None:
            label = label.decode('utf-8')
            accel_index = label.find('_')
            if -1 < accel_index < len(label) - 1:
                accel_char = long(ord(label[accel_index+1]))
                accel_key  = gtk.gdk.unicode_to_keyval(accel_char)
                if accel_key != accel_char | 0x01000000:
                    self.add_accelerator("activate", accel_group, accel_key,
                                         gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
        self.show()


def build_menu(menu_items, accel_group=None):
    menu = gtk.Menu()
    for label,func in menu_items:
        if label == '----':
            s = gtk.SeparatorMenuItem()
            s.show()
            menu.add(s)
        else:
            item = MenuItem(label, accel_group=accel_group, func=func)
            item.show()
            menu.add(item)
    return menu


class Validator(gtk.Entry):
    valid_chars = '1234567890'
    minimum = None
    maximum = None
    cast = int
    
    def __init__(self, option_name, config, setfunc):
        gtk.Entry.__init__(self)
        self.option_name = option_name
        self.config      = config
        self.setfunc     = setfunc

        self.set_text(str(config[option_name]))
            
        self.set_size_request(self.width,-1)
        
        self.connect('insert-text', self.text_inserted)
        self.connect('focus-out-event', self.focus_out)

    def get_value(self):
        value = None
        try:
            value = self.cast(self.get_text())
        except ValueError:
            pass
        return value

    def set_value(self, value):
        self.set_text(str(value))
        self.setfunc(self.option_name, value)        
        
    def focus_out(self, entry, widget):
        value = self.get_value()

        if value is None:
            return

        if (self.minimum is not None) and (value < self.minimum):
            value = self.minimum
        if (self.maximum is not None) and (value > self.maximum):
            value = self.maximum

        self.set_value(value)

    def text_inserted(self, entry, input, position, user_data):
        for i in input:
            if (self.valid_chars is not None) and (i not in self.valid_chars):
                self.emit_stop_by_name('insert-text')
                return True
        return False

class IPValidator(Validator):
    valid_chars = '1234567890.'
    width = 128
    cast = str

class PortValidator(Validator):
    width = 64
    minimum = 0
    maximum = 65535

    def add_end(self, end_name):
        self.end_option_name = end_name

    def set_value(self, value):
        self.set_text(str(value))
        self.setfunc(self.option_name, value)
        self.setfunc(self.end_option_name, value+PORT_RANGE)


class PercentValidator(Validator):
    width = 48
    minimum = 0

class MinutesValidator(Validator):
    width = 48
    minimum = 1

class EnterUrlDialog(MessageDialog):
    flags = gtk.DIALOG_DESTROY_WITH_PARENT
    def __init__(self, parent):
        self.entry = gtk.Entry()
        self.entry.show()
        self.main = parent
        MessageDialog.__init__(self, parent.mainwindow,
                               _("Enter torrent URL"),
                               _("Enter the URL of a torrent file to open:"),
                               type=gtk.MESSAGE_QUESTION,
                               buttons=gtk.BUTTONS_OK_CANCEL,
                               yesfunc=lambda *args: parent.open_url(self.entry.get_text()),
                               default=gtk.RESPONSE_OK
                               )
        hbox = gtk.HBox()
        hbox.pack_start(self.entry, padding=SPACING)
        hbox.show()
        self.entry.set_activates_default(True)
        self.entry.set_flags(gtk.CAN_FOCUS)
        self.vbox.pack_start(hbox)
        self.entry.grab_focus()

    def close(self, *args):
        self.destroy()

    def destroy(self):
        MessageDialog.destroy(self)
        self.main.window_closed('enterurl')


class RateSliderBox(gtk.VBox):
    base = 10
    multiplier = 4
    max_exponent = 3.3

    def __init__(self, config, torrentqueue):
        gtk.VBox.__init__(self, homogeneous=False)
        self.config = config
        self.torrentqueue = torrentqueue

        if self.config['max_upload_rate'] < self.slider_to_rate(0):
            self.config['max_upload_rate'] = self.slider_to_rate(0)

        self.speed_classes = {
            (   4,    5):_("dialup"           ),
            (   6,   14):_("DSL/cable 128k up"),
            (  15,   29):_("DSL/cable 256k up"),
            (  30,   91):_("DSL 768k up"      ),
            (  92,  137):_("T1"               ),
            ( 138,  182):_("T1/E1"            ),
            ( 183,  249):_("E1"               ),
            ( 250, 5446):_("T3"               ),
            (5447,18871):_("OC3"              ),
            }

        biggest_size = 0
        for v in self.speed_classes.values():
            width = gtk.Label(v).size_request()[0]
            if width > biggest_size:
                biggest_size = width

        self.rate_slider_label_box = gtk.HBox(spacing=SPACING,
                                              homogeneous=True)
        
        self.rate_slider_label = gtk.Label(_("Maximum upload rate:"))
        if gtk.pygtk_version >= (2, 6):
            self.rate_slider_label.set_ellipsize(pango.ELLIPSIZE_START)
        self.rate_slider_label.set_alignment(1, 0.5)
        self.rate_slider_label_box.pack_start(self.rate_slider_label,
                                              expand=True, fill=True)

        self.rate_slider_value = gtk.Label(
            self.value_to_label(self.config['max_upload_rate']))
        self.rate_slider_value.set_alignment(0, 0.5)
        self.rate_slider_value.set_size_request(biggest_size, -1)

        self.rate_slider_label_box.pack_start(self.rate_slider_value,
                                              expand=True, fill=True)

        self.rate_slider_adj = gtk.Adjustment(
            self.rate_to_slider(self.config['max_upload_rate']), 0,
            self.max_exponent, 0.01, 0.1)
        
        self.rate_slider = gtk.HScale(self.rate_slider_adj)
        self.rate_slider.set_draw_value(False)
        self.rate_slider_adj.connect('value_changed', self.set_max_upload_rate)

        self.pack_start(self.rate_slider       , expand=False, fill=False)
        self.pack_start(self.rate_slider_label_box , expand=False, fill=False)

        if False: # this shows the legend for the slider
            self.rate_slider_legend = gtk.HBox(homogeneous=True)
            for i in range(int(self.max_exponent+1)):
                label = gtk.Label(str(self.slider_to_rate(i)))
                alabel = halign(label, i/self.max_exponent)
                self.rate_slider_legend.pack_start(alabel,
                                                   expand=True, fill=True)
            self.pack_start(self.rate_slider_legend, expand=False, fill=False)


    def start(self):
        self.set_max_upload_rate(self.rate_slider_adj)

    def rate_to_slider(self, value):
        return math.log(value/self.multiplier, self.base)

    def slider_to_rate(self, value):
        return int(round(self.base**value * self.multiplier))

    def value_to_label(self, value):
        conn_type = ''
        for key, conn in self.speed_classes.items():
            min_v, max_v = key
            if min_v <= value <= max_v:
                conn_type = ' (%s)'%conn
                break
        label = str(Rate(value*1024)) + conn_type
        return label

    def set_max_upload_rate(self, adj):
        option = 'max_upload_rate'
        value = self.slider_to_rate(adj.get_value())
        self.config[option] = value
        self.torrentqueue.set_config(option, value)
        self.rate_slider_value.set_text(self.value_to_label(int(value)))


class StopStartButton(gtk.Button):
    stop_tip  = _("Temporarily stop all running torrents")
    start_tip = _("Resume downloading")

    def __init__(self, main):
        gtk.Button.__init__(self)
        self.main = main
        self.connect('clicked', self.toggle)

        self.stop_image = gtk.Image()
        self.stop_image.set_from_stock('bt-pause', gtk.ICON_SIZE_BUTTON)
        self.stop_image.show()

        self.start_image = gtk.Image()
        self.start_image.set_from_stock('bt-play', gtk.ICON_SIZE_BUTTON)
        self.start_image.show()

    def toggle(self, widget):
        self.set_paused(not self.main.config['pause'])

    def set_paused(self, paused):
        image = self.get_child()
        if paused:
            if image == self.stop_image:
                self.remove(self.stop_image)
            if image != self.start_image:
                self.add(self.start_image)
            self.main.tooltips.set_tip(self, self.start_tip)
            self.main.stop_queue()
        else:
            if image == self.start_image:
                self.remove(self.start_image)
            if image != self.stop_image:
                self.add(self.stop_image)
            self.main.tooltips.set_tip(self, self.stop_tip )
            self.main.restart_queue()


class StatusLight(gtk.EventBox):

    states = {'stopped': ('bt-status-stopped',
                          _("Paused")),
              'empty'  : ('bt-status-stopped',
                          _("No torrents")),
              'running': ('bt-status-running',
                          _("Running normally")),
              'natted' : ('bt-status-natted',
                          _("Firewalled/NATted"))
              }
    
    def __init__(self, main):
        gtk.EventBox.__init__(self)
        self.main = main
        self.image = None
        self.images = {}
        self.mystate = None
        for k,(s,t) in self.states.items():
            i = gtk.Image()
            load_large_toolbar_image(i, s)
            i.show()
            self.images[k] = i
        
        self.set_size_request(24,24)
        self.main.tooltips.set_tip(self, 'tooltip')
        self.change_state('natted')

    def change_state(self, state):
        if self.mystate == state:
            return
        self.mystate = state
        assert self.states.has_key(state)
        if self.image is not None:
            self.remove(self.image)
        self.image = self.images[state]
        self.add(self.image)
        
        stock, tooltip = self.states[state]
        self.main.tooltips.set_tip(self, tooltip)
       

class VersionWindow(Window):
    def __init__(self, main, newversion, download_url):
        Window.__init__(self)
        self.set_title(_("New %s version available")%app_name)
        self.set_border_width(SPACING)
        self.set_resizable(False)
        self.main = main
        self.newversion = newversion
        self.download_url = download_url
        self.connect('destroy', lambda w: self.main.window_closed('version'))
        self.vbox = gtk.VBox(spacing=SPACING)
        self.hbox = gtk.HBox(spacing=SPACING)
        self.image = gtk.Image()
        self.image.set_from_stock(gtk.STOCK_DIALOG_INFO, gtk.ICON_SIZE_DIALOG)
        self.hbox.pack_start(self.image)
        
        self.label = gtk.Label()
        self.label.set_markup(
            (_("A newer version of %s is available.\n") % app_name) +
            (_("You are using %s, and the new version is %s.\n") % (version, newversion)) +
            (_("You can always get the latest version from \n%s") % self.download_url)
            ) 
        self.label.set_selectable(True)
        self.hbox.pack_start(self.label)
        self.vbox.pack_start(self.hbox)
        self.bbox = gtk.HBox(spacing=SPACING)

        self.closebutton = gtk.Button(_("Download _later"))
        self.closebutton.connect('clicked', self.close)

        self.newversionbutton = gtk.Button(_("Download _now"))
        self.newversionbutton.connect('clicked', self.get_newversion)

        self.bbox.pack_end(self.newversionbutton, expand=False, fill=False)
        self.bbox.pack_end(self.closebutton     , expand=False, fill=False)

        self.checkbox = gtk.CheckButton(_("_Remind me later"))
        self.checkbox.set_active(True)
        self.checkbox.connect('toggled', self.remind_toggle)
        
        self.bbox.pack_start(self.checkbox, expand=False, fill=False)

        self.vbox.pack_start(self.bbox)
        
        self.add(self.vbox)
        self.show_all()

    def remind_toggle(self, widget):
        v = self.checkbox.get_active()
        notified = ''
        if v:
            notified = ''
        else:
            notified = self.newversion
        self.main.set_config('notified', str(notified))

    def close(self, widget):
        self.destroy()

    def get_newversion(self, widget):
        if self.main.updater.can_install():
            if self.main.updater.torrentfile is None:
                self.main.visit_url(self.download_url)
            else:
                self.main.start_auto_update()
        else:
            self.main.visit_url(self.download_url)
        self.destroy()


class AboutWindow(object):

    def __init__(self, main, donatefunc):
        self.win = Window()
        self.win.set_title(_("About %s")%app_name)
        self.win.set_size_request(300,400)
        self.win.set_border_width(SPACING)
        self.win.set_resizable(False)
        self.win.connect('destroy', lambda w: main.window_closed('about'))
        self.scroll = gtk.ScrolledWindow()
        self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
        self.scroll.set_shadow_type(gtk.SHADOW_IN)

        self.outervbox = gtk.VBox()

        self.outervbox.pack_start(get_logo(96), expand=False, fill=False)

        version_str = version
        if int(version_str[2]) % 2:
            version_str = version_str + ' ' + _("Beta")

        self.outervbox.pack_start(gtk.Label(_("Version %s")%version_str), expand=False, fill=False)

        if branch is not None:
            blabel = gtk.Label('cdv client dir: %s' % branch)
            self.outervbox.pack_start(blabel, expand=False, fill=False)

        self.vbox = gtk.VBox()
        self.vbox.set_size_request(250, -1)

        for i, fn in enumerate(('credits', 'credits-l10n')):
            if i != 0:
                self.vbox.pack_start(gtk.HSeparator(), padding=SPACING,
                                     expand=False, fill=False)
            filename = os.path.join(doc_root, fn+'.txt')
            l = ''
            if not os.access(filename, os.F_OK|os.R_OK):
                l = _("Couldn't open %s") % filename
            else:
                credits_f = file(filename)
                l = credits_f.read()
                credits_f.close()
            if os.name == 'nt':
                # gtk ignores blank lines on win98
                l = l.replace('\n\n', '\n\t\n')
            label = gtk.Label(l.strip())
            label.set_line_wrap(True)
            label.set_selectable(True)
            label.set_justify(gtk.JUSTIFY_CENTER)
            label.set_size_request(250,-1)
            self.vbox.pack_start(label, expand=False, fill=False)

        self.scroll.add_with_viewport(self.vbox)

        self.outervbox.pack_start(self.scroll, padding=SPACING)

        self.donatebutton = gtk.Button(_("Donate"))
        self.donatebutton.connect('clicked', donatefunc)
        self.donatebuttonbox = gtk.HButtonBox()
        self.donatebuttonbox.pack_start(self.donatebutton,
                                        expand=False, fill=False)
        self.outervbox.pack_end(self.donatebuttonbox, expand=False, fill=False)

        self.win.add(self.outervbox)

        self.win.show_all()

    def close(self, widget):
        self.win.destroy()    


class LogWindow(object):
    def __init__(self, main, logbuffer, config):
        self.config = config
        self.main = main
        self.win = Window()
        self.win.set_title(_("%s Activity Log")%app_name)
        self.win.set_default_size(600, 200)
        self.win.set_border_width(SPACING)
            
        self.buffer = logbuffer
        self.text = gtk.TextView(self.buffer)
        self.text.set_editable(False)
        self.text.set_cursor_visible(False)
        self.text.set_wrap_mode(gtk.WRAP_WORD)

        self.scroll = gtk.ScrolledWindow()
        self.scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
        self.scroll.set_shadow_type(gtk.SHADOW_IN)
        self.scroll.add(self.text)

        self.vbox = gtk.VBox(spacing=SPACING)
        self.vbox.pack_start(self.scroll)

        self.buttonbox = gtk.HButtonBox()
        self.buttonbox.set_spacing(SPACING)
        
        self.closebutton = gtk.Button(stock='gtk-close')
        self.closebutton.connect('clicked', self.close)
        
        self.savebutton = gtk.Button(stock='gtk-save')
        self.savebutton.connect('clicked', self.save_log_file_selection)

        self.clearbutton = gtk.Button(stock='gtk-clear')
        self.clearbutton.connect('clicked', self.clear_log)

        self.buttonbox.pack_start(self.savebutton)
        self.buttonbox.pack_start(self.closebutton)

        self.hbox2 = gtk.HBox(homogeneous=False)

        self.hbox2.pack_end(self.buttonbox, expand=False, fill=False)

        bb = gtk.HButtonBox()
        bb.pack_start(self.clearbutton)
        self.hbox2.pack_start(bb, expand=False, fill=True)

        self.vbox.pack_end(self.hbox2, expand=False, fill=True)

        self.win.add(self.vbox)        
        self.win.connect("destroy", lambda w: self.main.window_closed('log'))
        self.scroll_to_end()
        self.win.show_all()

    def scroll_to_end(self):
        mark = self.buffer.create_mark(None, self.buffer.get_end_iter())
        self.text.scroll_mark_onscreen(mark)

    def save_log_file_selection(self, *args):
        name = 'bittorrent.log'
        path = smart_dir(self.config['save_in'])
        fullname = os.path.join(path, name)
        self.main.open_window('savefile',
                              title=_("Save log in:"),
                              fullname=fullname,
                              got_location_func=self.save_log,
                              no_location_func=lambda: self.main.window_closed('savefile'))


    def save_log(self, saveas):
        self.main.window_closed('savefile')
        f = file(saveas, 'w')
        f.write(self.buffer.get_text(self.buffer.get_start_iter(),
                                     self.buffer.get_end_iter()))
        save_message = self.buffer.log_text(_("log saved"), None)
        f.write(save_message)
        f.close()

    def clear_log(self, *args):
        self.buffer.clear_log()

    def close(self, widget):
        self.win.destroy()


class LogBuffer(gtk.TextBuffer):
    h = { CRITICAL:'critical',
          ERROR   :'error'   ,
          WARNING :'warning' ,
          INFO    :'info'    , } 

    def __init__(self):
        gtk.TextBuffer.__init__(self)

        tt = self.get_tag_table()

        size_tag = gtk.TextTag('small')
        size_tag.set_property('size-points', 10)
        tt.add(size_tag)

        info_tag = gtk.TextTag('info')
        info_tag.set_property('foreground', '#00a040')
        tt.add(info_tag)

        warning_tag = gtk.TextTag('warning')
        warning_tag.set_property('foreground', '#a09000')
        tt.add(warning_tag)

        error_tag = gtk.TextTag('error')
        error_tag.set_property('foreground', '#b00000')
        tt.add(error_tag)

        critical_tag = gtk.TextTag('critical')
        critical_tag.set_property('foreground', '#b00000')
        critical_tag.set_property('weight', pango.WEIGHT_BOLD)
        tt.add(critical_tag)


    def log_text(self, text, severity=CRITICAL):
        now_str = datetime.datetime.strftime(datetime.datetime.now(),
                                             '[%Y-%m-%d %H:%M:%S] ')
        self.insert_with_tags_by_name(self.get_end_iter(), now_str, 'small')
        if severity is not None:
            self.insert_with_tags_by_name(self.get_end_iter(), '%s\n'%text,
                                          'small', self.h[severity])
        else:
            self.insert_with_tags_by_name(self.get_end_iter(),
                                          ' -- %s -- \n'%text, 'small')
            
        return now_str+text+'\n'

    def clear_log(self):
        self.set_text('')
        self.log_text(_("log cleared"), None)


class CheckButton(gtk.CheckButton): 
    def __init__(self, label, main, option_name, initial_value,
                 extra_callback=None):
        gtk.CheckButton.__init__(self, label)
        self.main = main
        self.option_name = option_name
        self.option_type = type(initial_value)
        self.set_active(bool(initial_value))
        self.extra_callback = extra_callback
        self.connect('toggled', self.callback)

    def callback(self, *args):
        self.main.config[self.option_name] = \
            self.option_type(not self.main.config[self.option_name])
        self.main.setfunc(self.option_name, self.main.config[self.option_name])
        if self.extra_callback is not None:
            self.extra_callback()


class SettingsWindow(object):

    def __init__(self, main, config, setfunc):
        self.main = main
        self.setfunc = setfunc
        self.config = config
        self.win = Window()
        self.win.connect("destroy", lambda w: main.window_closed('settings'))
        self.win.set_title(_("%s Settings")%app_name)
        self.win.set_border_width(SPACING)

        self.notebook = gtk.Notebook()

        self.vbox = gtk.VBox(spacing=SPACING)
        self.vbox.pack_start(self.notebook, expand=False, fill=False)

        # Saving tab
        self.saving_box = gtk.VBox(spacing=SPACING)
        self.saving_box.set_border_width(SPACING)
        self.notebook.append_page(self.saving_box, gtk.Label(_("Saving")))

        self.dl_frame = gtk.Frame(_("Save new downloads in:"))
        self.saving_box.pack_start(self.dl_frame, expand=False, fill=False)

        self.dl_box = gtk.VBox(spacing=SPACING)
        self.dl_box.set_border_width(SPACING)
        self.dl_frame.add(self.dl_box)
        self.save_in_box = gtk.HBox(spacing=SPACING)

        self.dl_save_in = gtk.Entry()
        self.dl_save_in.set_editable(False)
        self.set_save_in(self.config['save_in'])
        self.save_in_box.pack_start(self.dl_save_in, expand=True, fill=True)

        self.dl_save_in_button = gtk.Button(_("Change..."))
        self.dl_save_in_button.connect('clicked', self.get_save_in)
        self.save_in_box.pack_start(self.dl_save_in_button, expand=False, fill=False)
        self.dl_box.pack_start(self.save_in_box, expand=False, fill=False)

        self.dl_ask_checkbutton = CheckButton(
            _("Ask where to save each new download"), self,
            'ask_for_save', self.config['ask_for_save'])

        self.dl_box.pack_start(self.dl_ask_checkbutton, expand=False, fill=False)
        # end Saving tab

        # Downloading tab
        self.downloading_box = gtk.VBox(spacing=SPACING)
        self.downloading_box.set_border_width(SPACING)
        self.notebook.append_page(self.downloading_box, gtk.Label(_("Downloading")))

        self.dnd_frame = gtk.Frame(_("Starting additional torrents manually:"))
        self.dnd_box = gtk.VBox(spacing=SPACING, homogeneous=True)
        self.dnd_box.set_border_width(SPACING)

        self.dnd_states = ['replace','add','ask']
        self.dnd_original_state = self.config['start_torrent_behavior']
        
        self.always_replace_radio = gtk.RadioButton(
            group=None,
            label=_("Always stops the _last running torrent"))
        self.dnd_box.pack_start(self.always_replace_radio)
        self.always_replace_radio.state_name = self.dnd_states[0]
        
        self.always_add_radio = gtk.RadioButton(
            group=self.always_replace_radio,
            label=_("Always starts the torrent in _parallel"))
        self.dnd_box.pack_start(self.always_add_radio)
        self.always_add_radio.state_name = self.dnd_states[1]

        self.always_ask_radio = gtk.RadioButton(
            group=self.always_replace_radio,
            label=_("_Asks each time")
            )
        self.dnd_box.pack_start(self.always_ask_radio)
        self.always_ask_radio.state_name = self.dnd_states[2]

        self.dnd_group = self.always_replace_radio.get_group()
        for r in self.dnd_group:
            r.connect('toggled', self.start_torrent_behavior_changed)

        self.set_start_torrent_behavior(self.config['start_torrent_behavior'])
        
        self.dnd_frame.add(self.dnd_box)
        self.downloading_box.pack_start(self.dnd_frame, expand=False, fill=False)

        # Seeding tab
        self.seeding_box = gtk.VBox(spacing=SPACING)
        self.seeding_box.set_border_width(SPACING)
        self.notebook.append_page(self.seeding_box, gtk.Label(_("Seeding")))

        def colon_split(framestr):
            COLONS = (':', u'\uff1a')
            for colon in COLONS:
                if colon in framestr:
                    return framestr.split(colon)
            return '', framestr
        
        nt_framestr = _("Seed completed torrents: until share ratio reaches [_] percent, or for [_] minutes, whichever comes first.")
        nt_title, nt_rem = colon_split(nt_framestr)
        nt_msg1, nt_msg2, nt_msg4 = nt_rem.split('[_]')
        nt_msg3 = ''
        if ',' in nt_msg2:
            nt_msg2, nt_msg3 = nt_msg2.split(',')
            nt_msg2 += ','

        self.next_torrent_frame = gtk.Frame(nt_title+':')
        self.next_torrent_box   = gtk.VBox(spacing=SPACING, homogeneous=True)
        self.next_torrent_box.set_border_width(SPACING) 
        
        self.next_torrent_frame.add(self.next_torrent_box)


        self.next_torrent_ratio_box = gtk.HBox()
        self.next_torrent_ratio_box.pack_start(gtk.Label(nt_msg1),
                                               fill=False, expand=False)
        self.next_torrent_ratio_field = PercentValidator('next_torrent_ratio',
                                                         self.config, self.setfunc)
        self.next_torrent_ratio_box.pack_start(self.next_torrent_ratio_field,
                                               fill=False, expand=False)
        self.next_torrent_ratio_box.pack_start(gtk.Label(nt_msg2),
                                               fill=False, expand=False)
        self.next_torrent_box.pack_start(self.next_torrent_ratio_box)


        self.next_torrent_time_box = gtk.HBox()
        self.next_torrent_time_box.pack_start(gtk.Label(nt_msg3),
                                              fill=False, expand=False)
        self.next_torrent_time_field = MinutesValidator('next_torrent_time',
                                                        self.config, self.setfunc)
        self.next_torrent_time_box.pack_start(self.next_torrent_time_field,
                                              fill=False, expand=False)
        self.next_torrent_time_box.pack_start(gtk.Label(nt_msg4),
                                              fill=False, expand=False)
        self.next_torrent_box.pack_start(self.next_torrent_time_box)

        def seed_forever_extra():
            for field in (self.next_torrent_ratio_field,
                          self.next_torrent_time_field):
                field.set_sensitive(not self.config['seed_forever'])

        seed_forever_extra()
        self.seed_forever = CheckButton( _("Seed indefinitely"), self,
                                         'seed_forever',
                                         self.config['seed_forever'],
                                         seed_forever_extra)
        self.next_torrent_box.pack_start(self.seed_forever)
        # end next torrent seed behavior

        # begin last torrent seed behavior
        lt_framestr = _("Seed last completed torrent: until share ratio reaches [_] percent.")
        lt_title, lt_rem = colon_split(lt_framestr)
        lt_msg1, lt_msg2 = lt_rem.split('[_]')
        
        self.seeding_box.pack_start(self.next_torrent_frame, expand=False, fill=False)

        self.last_torrent_frame = gtk.Frame(lt_title+':')
        self.last_torrent_vbox = gtk.VBox(spacing=SPACING)
        self.last_torrent_vbox.set_border_width(SPACING)
        self.last_torrent_box = gtk.HBox()
        self.last_torrent_box.pack_start(gtk.Label(lt_msg1),
                                         expand=False, fill=False)
        self.last_torrent_ratio_field = PercentValidator('last_torrent_ratio',
                                                         self.config, self.setfunc)
        self.last_torrent_box.pack_start(self.last_torrent_ratio_field,
                                         fill=False, expand=False)
        self.last_torrent_box.pack_start(gtk.Label(lt_msg2),
                                         fill=False, expand=False)
        self.last_torrent_vbox.pack_start(self.last_torrent_box)
        
        def seed_last_forever_extra():
            self.last_torrent_ratio_field.set_sensitive(
                not self.config['seed_last_forever'])

        seed_last_forever_extra()

        self.seed_last_forever = CheckButton(_("Seed indefinitely"), self,
                                             'seed_last_forever',
                                             self.config['seed_last_forever'],
                                             seed_last_forever_extra)
        self.last_torrent_vbox.pack_start(self.seed_last_forever)

        self.last_torrent_frame.add(self.last_torrent_vbox)
        self.seeding_box.pack_start(self.last_torrent_frame, expand=False, fill=False)

        # Network tab
        self.network_box = gtk.VBox(spacing=SPACING)
        self.network_box.set_border_width(SPACING)
        self.notebook.append_page(self.network_box, gtk.Label(_("Network")))

        self.port_range_frame = gtk.Frame(_("Look for available port:"))        
        self.port_range = gtk.HBox()
        self.port_range.set_border_width(SPACING)
        self.port_range.pack_start(gtk.Label(_("starting at port: ")),
                                   expand=False, fill=False)
        self.minport_field = PortValidator('minport', self.config, self.setfunc)
        self.minport_field.add_end('maxport')
        self.port_range.pack_start(self.minport_field, expand=False, fill=False)
        self.minport_field.settingswindow = self
        self.port_range.pack_start(gtk.Label(' (0-65535)'),
                                   expand=False, fill=False)

        self.port_range_frame.add(self.port_range)
        self.network_box.pack_start(self.port_range_frame, expand=False, fill=False)


        self.ip_frame = gtk.Frame(_("IP to report to the tracker:"))
        self.ip_box = gtk.VBox()
        self.ip_box.set_border_width(SPACING)
        self.ip_field = IPValidator('ip', self.config, self.setfunc)
        self.ip_box.pack_start(self.ip_field, expand=False, fill=False)
        label = gtk.Label(_("(Has no effect unless you are on the\nsame local network as the tracker)"))
        label.set_line_wrap(True)
        self.ip_box.pack_start(lalign(label), expand=False, fill=False)
        self.ip_frame.add(self.ip_box)
        self.network_box.pack_start(self.ip_frame, expand=False, fill=False)

        # end Network tab        

        # Language tab
        self.languagechooser = LanguageChooser()
        self.notebook.append_page(self.languagechooser, gtk.Label("Language"))
        # end Language tab

        # Misc tab
        if is_frozen_exe:
            # allow the user to set the progress bar text to all black
            self.progressbar_hack = CheckButton(
                _("Progress bar text is always black\n(requires restart)"),
                self, 'progressbar_hack', self.config['progressbar_hack'])

            self.misc_box = gtk.VBox(spacing=SPACING)
            self.misc_box.set_border_width(SPACING)
            self.misc_box.pack_start(self.progressbar_hack, expand=False, fill=False)
            self.notebook.append_page(self.misc_box, gtk.Label(_("Misc")))
        # end Misc tab

        # Advanced tab
        if advanced_ui:
            self.advanced_box = gtk.VBox(spacing=SPACING)
            self.advanced_box.set_border_width(SPACING)
            hint = gtk.Label(_("WARNING: Changing these settings can\nprevent %s from functioning correctly.")%app_name)
            self.advanced_box.pack_start(lalign(hint), expand=False, fill=False)
            self.store = gtk.ListStore(*[gobject.TYPE_STRING] * 2)
            for option in ui_options[advanced_ui_options_index:]:
                self.store.append((option, str(self.config[option])))

            self.treeview = gtk.TreeView(self.store)
            r = gtk.CellRendererText()
            column = gtk.TreeViewColumn(_("Option"), r, text=0)
            self.treeview.append_column(column)
            r = gtk.CellRendererText()
            r.set_property('editable', True)
            r.connect('edited', self.store_value_edited)
            column = gtk.TreeViewColumn(_("Value"), r, text=1)
            self.treeview.append_column(column)
            self.advanced_frame = gtk.Frame()
            self.advanced_frame.set_shadow_type(gtk.SHADOW_IN)
            self.advanced_frame.add(self.treeview)

            self.advanced_box.pack_start(self.advanced_frame, expand=False, fill=False)
            self.notebook.append_page(self.advanced_box, gtk.Label(_("Advanced")))
        

        self.win.add(self.vbox)
        self.win.show_all()


    def get_save_in(self, widget=None):
        self.file_selection = self.main.open_window('choosefolder',
                                                    title=_("Choose default download directory"),
                                                    fullname=self.config['save_in'],
                                                    got_location_func=self.set_save_in,
                                                    no_location_func=lambda: self.main.window_closed('choosefolder'))

    def set_save_in(self, save_location):
        self.main.window_closed('choosefolder')
        if os.path.isdir(save_location):
            if save_location[-1] != os.sep:
                save_location += os.sep
            self.config['save_in'] = save_location
            save_in = path_wrap(self.config['save_in'])
            self.dl_save_in.set_text(save_in)
            self.setfunc('save_in', self.config['save_in'])

    def set_start_torrent_behavior(self, state_name):
        if state_name in self.dnd_states:
            for r in self.dnd_group:
                if r.state_name == state_name:
                    r.set_active(True)
                else:
                    r.set_active(False)
        else:
            self.always_replace_radio.set_active(True)        

    def start_torrent_behavior_changed(self, radiobutton):
        if radiobutton.get_active():
            self.setfunc('start_torrent_behavior', radiobutton.state_name)

    def store_value_edited(self, cell, row, new_text):
        it = self.store.get_iter_from_string(row)
        option = ui_options[int(row)+advanced_ui_options_index]
        t = type(defconfig[option])
        try:
            if t is type(None) or t is str:
                value = new_text
            elif t is int or t is long:
                value = int(new_text)
            elif t is float:
                value = float(new_text)
            elif t is bool:
                value = value == 'True'
            else:
                raise TypeError, str(t)
        except ValueError:
            return
        self.setfunc(option, value)
        self.store.set(it, 1, str(value))

    def close(self, widget):
        self.win.destroy()


class FileListWindow(object):

    def __init__(self, metainfo, closefunc):
        self.metainfo = metainfo
        self.setfunc = None
        self.allocfunc = None
        priorities = [0, 0]
        self.win = Window()
        self.win.set_title(_('Files in "%s"') % self.metainfo.name)
        self.win.connect("destroy", closefunc)

        self.box1 = gtk.VBox()

        size_request = [0,0]
        
        if advanced_ui and False:
            self.toolbar = gtk.Toolbar()
            for label, stockicon, method, arg in ((_("Apply")         , gtk.STOCK_APPLY  , self.set_priorities, None ),
                                                  (_("Allocate")      , gtk.STOCK_SAVE   , self.dosomething, 'alloc',),
                                                  (_("Never download"), gtk.STOCK_DELETE , self.dosomething, 'never',),
                                                  (_("Decrease")      , gtk.STOCK_GO_DOWN, self.dosomething, -1     ,),
                                                  (_("Increase")      , gtk.STOCK_GO_UP  , self.dosomething, +1     ,),):
                self.make_tool_item(label, stockicon, method, arg)
            self.box1.pack_start(self.toolbar, False)
            size_request = [450,54]
            
        self.sw = gtk.ScrolledWindow()
        self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self.box1.pack_start(self.sw)
        self.win.add(self.box1)

        columns = [_("Filename"),_("Length"),_('%')]
        pre_size_list = ['MMMMMMMMMMMMMMMMMMMMMMMM', '6666 MB', '100.0']
        if advanced_ui:
            columns += ['A','Order']
            pre_size_list += ['*','None','black']
        num_columns = len(pre_size_list)

        self.store = gtk.ListStore(*[gobject.TYPE_STRING] * num_columns)
        self.store.append(pre_size_list)
        self.treeview = gtk.TreeView(self.store)
        cs = []
        for i, name in enumerate(columns):
            r = gtk.CellRendererText()
            r.set_property('xalign', (0, 1, 1, .5, 1)[i])
            if i != 4:
                column = gtk.TreeViewColumn(name, r, text = i)
            else:
                column = gtk.TreeViewColumn(name, r, text = i, foreground = i + 1)
            column.set_resizable(True)
            self.treeview.append_column(column)
            cs.append(column)

        self.sw.add(self.treeview)
        self.treeview.set_headers_visible(False)
        self.treeview.columns_autosize()
        self.box1.show_all()
        self.treeview.realize()

        for column in cs:
            column.set_fixed_width(max(5,column.get_width()))
            column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        self.treeview.set_headers_visible(True)
        self.store.clear()
        self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        self.piecelen = self.metainfo.piece_length
        self.lengths = self.metainfo.sizes
        self.initialize_file_priorities(priorities)
        for name, size, priority in itertools.izip(self.metainfo.orig_files,
                                        self.metainfo.sizes, self.priorities):
            row = [name, Size(size), '?',]
            if advanced_ui:
                row += ['', priority == 255 and 'None' or str(priority), 'black']
            self.store.append(row)

        tvsr = self.treeview.size_request()
        vertical_padding = 18 
        size_request = [max(size_request[0],tvsr[0]),
                        (size_request[1] + tvsr[1] ) + vertical_padding]
        maximum_height = 300
        if size_request[1] > maximum_height - SCROLLBAR_WIDTH:
            size_request[1] = maximum_height
            size_request[0] = size_request[0] + SCROLLBAR_WIDTH
        self.win.set_default_size(*size_request)
                                  
        self.win.show_all()

    def make_tool_item_24(self, label, stockicon, method, arg): # for pygtk 2.4
        icon = gtk.Image()
        icon.set_from_stock(stockicon, gtk.ICON_SIZE_SMALL_TOOLBAR)
        item = gtk.ToolButton(icon_widget=icon, label=label)
        item.set_homogeneous(True)
        if arg is not None:
            item.connect('clicked', method, arg)
        else:
            item.connect('clicked', method)
        self.toolbar.insert(item, 0)

    def make_tool_item_22(self, label, stockicon, method, arg): # for pygtk 2.2
        icon = gtk.Image()
        icon.set_from_stock(stockicon, gtk.ICON_SIZE_SMALL_TOOLBAR)
        self.toolbar.prepend_item(label, None, None, icon, method, user_data=arg)

    if gtk.pygtk_version >= (2, 4):
        make_tool_item = make_tool_item_24
    else:
        make_tool_item = make_tool_item_22
        
    def set_priorities(self, widget):
        r = []
        piece = 0
        pos = 0
        curprio = prevprio = 1000
        for priority, length in itertools.izip(self.priorities, self.lengths):
            pos += length
            curprio = min(priority, curprio)
            while pos >= (piece + 1) * self.piecelen:
                if curprio != prevprio:
                    r.extend((piece, curprio))
                prevprio = curprio
                if curprio == priority:
                    piece = pos // self.piecelen
                else:
                    piece += 1
                if pos == piece * self.piecelen:
                    curprio = 1000
                else:
                    curprio = priority
        if curprio != prevprio:
            r.extend((piece, curprio))
        self.setfunc(r)
        it = self.store.get_iter_first()
        for i in xrange(len(self.priorities)):
            self.store.set_value(it, 5, "black")
            it = self.store.iter_next(it)
        self.origpriorities = list(self.priorities)

    def initialize_file_priorities(self, piecepriorities):
        self.priorities = []
        piecepriorities = piecepriorities + [999999999]
        it = iter(piecepriorities)
        assert it.next() == 0
        pos = piece = curprio = 0
        for length in self.lengths:
            pos += length
            priority = curprio
            while pos >= piece * self.piecelen:
                curprio = it.next()
                if pos > piece * self.piecelen:
                    priority = max(priority, curprio)
                piece = it.next()
            self.priorities.append(priority)
        self.origpriorities = list(self.priorities)

    def dosomething(self, widget, dowhat):
        self.treeview.get_selection().selected_foreach(self.adjustfile, dowhat)

    def adjustfile(self, treemodel, path, it, dowhat):
        row = path[0]
        if dowhat == "alloc":
            self.allocfunc(row)
            return
        if self.priorities[row] == 255:
            return
        if dowhat == 'never':
            self.priorities[row] = 255
        else:
            if self.priorities[row] == 0 and dowhat < 0:
                return
            self.priorities[row] += dowhat
        treemodel.set_value(it, 4, self.priorities[row] == 255 and 'None' or str(self.priorities[row]))
        treemodel.set_value(it, 5, self.priorities[row] == self.origpriorities[row] and 'black' or 'red')

    def update(self, left, allocated):
        it = self.store.get_iter_first()
        for left, total, alloc in itertools.izip(left, self.lengths,
                                                 allocated):
            if total == 0:
                p = 1
            else:
                p = (total - left) / total
            self.store.set_value(it, 2, "%.1f" % (int(p * 1000)/10))
            if advanced_ui:
                self.store.set_value(it, 3, '*' * alloc)
            it = self.store.iter_next(it)

    def close(self):
        self.win.destroy()


class PeerListWindow(object):

    def __init__(self, torrent_name, closefunc):
        self.win = Window()
        self.win.connect("destroy", closefunc)
        self.win.set_title( _('Peers for "%s"')%torrent_name)
        self.sw = gtk.ScrolledWindow()
        self.sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
        self.sw.set_shadow_type(gtk.SHADOW_IN)
        self.win.add(self.sw)

        column_header = [_("IP address"), _("Client"), _("Connection"), _("KB/s down"), _("KB/s up"), _("MB downloaded"), _("MB uploaded"), _("% complete"), _("KB/s est. peer download")]
        pre_size_list = ['666.666.666.666', 'TorrentStorm 1.3', 'bad peer', 66666, 66666, '1666.66', '1666.66', '100.0', 6666]
        numeric_cols = [3,4,5,6,7,8]
        store_types = [gobject.TYPE_STRING]*3  + [gobject.TYPE_INT]*2 + [gobject.TYPE_STRING]*3 + [gobject.TYPE_INT]
        
        if advanced_ui:
            column_header[2:2] = [_("Peer ID")]
            pre_size_list[2:2] = ['-AZ2104-']
            store_types[2:2]   = [gobject.TYPE_STRING]
            column_header[5:5] = [_("Interested"),_("Choked"),_("Snubbed")]
            pre_size_list[5:5] = ['*','*','*']
            store_types[5:5]   = [gobject.TYPE_STRING]*3
            column_header[9:9] = [_("Interested"),_("Choked"),_("Optimistic upload")]
            pre_size_list[9:9] = ['*','*','*']
            store_types[9:9]   = [gobject.TYPE_STRING]*3
            numeric_cols = [4,8,12,13,14,15]

        num_columns = len(column_header)
        self.store = gtk.ListStore(*store_types)
        self.store.append(pre_size_list)

        def makesortfunc(sort_func):
            def sortfunc(treemodel, iter1, iter2, column):
                a_str = treemodel.get_value(iter1, column)
                b_str = treemodel.get_value(iter2, column)
                if a_str is not None and b_str is not None:
                    return sort_func(a_str,b_str)
                else:
                    return 0
            return sortfunc

        def ip_sort(a_str,b_str):
            for a,b in zip(a_str.split('.'), b_str.split('.')):
                if a == b:
                    continue
                if len(a) == len(b):
                    return cmp(a,b)
                return cmp(int(a), int(b))
            return 0

        def float_sort(a_str,b_str):
            a,b = 0,0
            try: a = float(a_str)
            except ValueError: pass
            try: b = float(b_str)
            except ValueError: pass
            return cmp(a,b)

        self.store.set_sort_func(0, makesortfunc(ip_sort), 0)
        for i in range(2,5):
            self.store.set_sort_func(num_columns-i, makesortfunc(float_sort), num_columns-i)
        
        self.treeview = gtk.TreeView(self.store)
        cs = []
        for i, name in enumerate(column_header):
            r = gtk.CellRendererText()
            if i in numeric_cols:
                r.set_property('xalign', 1)
            column = gtk.TreeViewColumn(name, r, text = i)
            column.set_resizable(True)
            column.set_min_width(5)
            column.set_sort_column_id(i)
            self.treeview.append_column(column)
            cs.append(column)
        self.treeview.set_rules_hint(True)
        self.sw.add(self.treeview)
        self.treeview.set_headers_visible(False)
        self.treeview.columns_autosize()
        self.sw.show_all()
        self.treeview.realize()
        for column in cs:
            column.set_fixed_width(column.get_width())
            column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
        self.treeview.set_headers_visible(True)
        self.store.clear()
        self.treeview.get_selection().set_mode(gtk.SELECTION_NONE)
        width = self.treeview.size_request()[0]
        self.win.set_default_size(width+SCROLLBAR_WIDTH, 300)
        self.win.show_all()
        self.prev = []


    def update(self, peers, bad_peers):
        fields = []

        def p_bool(value): return value and '*' or ''

        for peer in peers:
            field = []
            field.append(peer['ip']) 

            client, version = ClientIdentifier.identify_client(peer['id']) 
            field.append(client + ' ' + version)

            if advanced_ui:
                field.append(zurllib.quote(peer['id'])) 
                
            field.append(peer['initiation'] == 'R' and _("remote") or _("local"))
            dl = peer['download']
            ul = peer['upload']

            for l in (dl, ul):
                rate = l[1]
                if rate > 100:
                    field.append(int(round(rate/(2**10)))) 
                else:
                    field.append(0)
                if advanced_ui:
                    field.append(p_bool(l[2]))
                    field.append(p_bool(l[3]))
                    if len(l) > 4:
                        field.append(p_bool(l[4]))
                    else:
                        field.append(p_bool(peer['is_optimistic_unchoke']))

            field.append('%.2f'%round(dl[0] / 2**20, 2))
            field.append('%.2f'%round(ul[0] / 2**20, 2))
            field.append('%.1f'%round(int(peer['completed']*1000)/10, 1))

            field.append(int(peer['speed']//(2**10)))

            fields.append(field)

        for (ip, (is_banned, stats)) in bad_peers.iteritems():
            field = []
            field.append(ip)

            client, version = ClientIdentifier.identify_client(stats.peerid)
            field.append(client + ' ' + version)

            if advanced_ui:
                field.append(zurllib.quote(stats.peerid))

            field.append(_("bad peer"))

            # the sortable peer list won't take strings in these fields
            field.append(0) 

            if advanced_ui:
                field.extend([0] * 7) # upRate, * fields
            else:
                field.extend([0] * 1) # upRate
                
            field.append(_("%d ok") % stats.numgood)
            field.append(_("%d bad") % len(stats.bad))
            if is_banned: # completion
                field.append(_("banned"))
            else:
                field.append(_("ok"))
            field.append(0) # peer dl rate
            fields.append(field)

        if self.store.get_sort_column_id() < 0:
            # ListStore is unsorted, it might be faster to set only modified fields
            it = self.store.get_iter_first()
            for old, new in itertools.izip(self.prev, fields):
                if old != new:
                    for i, value in enumerate(new):
                        if value != old[i]:
                            self.store.set_value(it, i, value)
                it = self.store.iter_next(it)
            for i in range(len(fields), len(self.prev)):
                self.store.remove(it)
            for i in range(len(self.prev), len(fields)):
                self.store.append(fields[i])
            self.prev = fields
        else:
            # ListStore is sorted, no reason not to to reset all fields
            self.store.clear()
            for field in fields:
                self.store.append(field)
            
        

    def close(self):
        self.win.destroy()


class TorrentInfoWindow(object):

    def __init__(self, torrent_box, closefunc):
        self.win = Window()
        self.torrent_box = torrent_box
        name = self.torrent_box.metainfo.name
        self.win.set_title(_('Info for "%s"')%name)
        self.win.set_size_request(-1,-1)
        self.win.set_border_width(SPACING)
        self.win.set_resizable(False)
        self.win.connect('destroy', closefunc)
        self.vbox = gtk.VBox(spacing=SPACING)

        self.table = gtk.Table(rows=4, columns=3, homogeneous=False)
        self.table.set_row_spacings(SPACING)
        self.table.set_col_spacings(SPACING)
        y = 0

        def add_item(key, val, y):
            self.table.attach(ralign(gtk.Label(key)), 0, 1, y, y+1)
            v = gtk.Label(val)
            v.set_selectable(True)
            self.table.attach(lalign(v), 1, 2, y, y+1)

        add_item(_("Torrent name:"), name, y)
        y+=1

        announce = ''
        if self.torrent_box.metainfo.is_trackerless:
            announce = _("(trackerless torrent)")
        else:
            announce = self.torrent_box.metainfo.announce
        add_item(_("Announce url:"), announce, y)
        y+=1

        size = Size(self.torrent_box.metainfo.total_bytes)
        num_files = _(", in one file")
        if self.torrent_box.is_batch:
            num_files = _(", in %d files") % len(self.torrent_box.metainfo.sizes)
        add_item(_("Total size:"),  str(size)+num_files, y)
        y+=1

        if advanced_ui:
            pl = self.torrent_box.metainfo.piece_length
            count, lastlen = divmod(size, pl)
            sizedetail = '%d x %d + %d = %d' % (count, pl, lastlen, int(size))
            add_item(_("Pieces:"), sizedetail, y)
            y+=1
            add_item(_("Info hash:"), self.torrent_box.infohash.encode('hex'), y)
            y+=1

        path = self.torrent_box.dlpath 
        filename = ''
        if not self.torrent_box.is_batch:
            path,filename = os.path.split(self.torrent_box.dlpath)
        if path[-1] != os.sep:
            path += os.sep
        path = path_wrap(path)
        add_item(_("Save in:"), path, y)
        y+=1

        if not self.torrent_box.is_batch:
            add_item(_("File name:"), path_wrap(filename), y)
            y+=1

        self.vbox.pack_start(self.table)        

        if self.torrent_box.metainfo.comment not in (None, ''):
            commentbuffer = gtk.TextBuffer()
            commentbuffer.set_text(self.torrent_box.metainfo.comment)
            commenttext = gtk.TextView(commentbuffer)
            commenttext.set_editable(False)
            commenttext.set_cursor_visible(False)
            commenttext.set_wrap_mode(gtk.WRAP_WORD)
            commentscroll = gtk.ScrolledWindow()
            commentscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
            commentscroll.set_shadow_type(gtk.SHADOW_IN)
            commentscroll.add(commenttext)
            self.vbox.pack_start(commentscroll)

        self.vbox.pack_start(gtk.HSeparator(), expand=False, fill=False)

        self.hbox = gtk.HBox(spacing=SPACING)
        lbbox = gtk.HButtonBox()
        rbbox = gtk.HButtonBox()
        lbbox.set_spacing(SPACING)

        if LaunchPath.can_launch_files:
            opendirbutton = IconButton(_("Open directory"), stock=gtk.STOCK_OPEN)
            opendirbutton.connect('clicked', self.torrent_box.open_dir)
            lbbox.pack_start(opendirbutton, expand=False, fill=False)
            opendirbutton.set_sensitive(self.torrent_box.can_open_dir())

        filelistbutton = IconButton(_("Show file list"), stock='gtk-index')
        if self.torrent_box.is_batch:
            filelistbutton.connect('clicked', self.torrent_box.open_filelist)
        else:
            filelistbutton.set_sensitive(False)
        lbbox.pack_start(filelistbutton, expand=False, fill=False)

        closebutton = gtk.Button(stock='gtk-close')
        closebutton.connect('clicked', lambda w: self.close())
        rbbox.pack_end(closebutton, expand=False, fill=False)

        self.hbox.pack_start(lbbox, expand=False, fill=False)
        self.hbox.pack_end(  rbbox, expand=False, fill=False)

        self.vbox.pack_end(self.hbox, expand=False, fill=False)

        self.win.add(self.vbox)
        
        self.win.show_all()

    def close(self):
        self.win.destroy()


class TorrentBox(gtk.EventBox):
    torrent_tip_format = '%s:\n %s\n %s'
    
    def __init__(self, infohash, metainfo, dlpath, completion, main):
        gtk.EventBox.__init__(self)
        self.infohash = infohash
        self.metainfo = metainfo
        self.completion = completion
        self.main = main

        self.main_torrent_dnd_tip = _("drag to reorder")
        self.torrent_menu_tip = _("right-click for menu")

        self.set_save_location(dlpath)

        self.uptotal   = self.main.torrents[self.infohash].uptotal
        self.downtotal = self.main.torrents[self.infohash].downtotal
        if self.downtotal > 0:
            self.up_down_ratio = self.uptotal / self.metainfo.total_bytes
        else:
            self.up_down_ratio = None

        self.infowindow = None
        self.filelistwindow = None
        self.is_batch = metainfo.is_batch
        self.menu = None
        self.menu_handler = None

        self.vbox = gtk.VBox(homogeneous=False, spacing=SPACING)
        self.label = gtk.Label()
        self.set_name()
        
        self.vbox.pack_start(lalign(self.label), expand=False, fill=False)

        self.hbox = gtk.HBox(homogeneous=False, spacing=SPACING)

        self.icon = gtk.Image()
        self.icon.set_size_request(-1, 29)

        self.iconbox = gtk.VBox()
        self.iconevbox = gtk.EventBox()        
        self.iconevbox.add(self.icon)
        self.iconbox.pack_start(self.iconevbox, expand=False, fill=False)
        self.hbox.pack_start(self.iconbox, expand=False, fill=False)
        
        self.vbox.pack_start(self.hbox)
        
        self.infobox = gtk.VBox(homogeneous=False)

        self.progressbarbox = gtk.HBox(homogeneous=False, spacing=SPACING)
        self.progressbar = gtk.ProgressBar()

        self.reset_progressbar_color()
        
        if self.completion is not None:
            self.progressbar.set_fraction(self.completion)
            if self.completion >= 1:
                done_label = self.make_done_label()
                self.progressbar.set_text(done_label)
            else:
                self.progressbar.set_text('%.1f%%'%(self.completion*100))
        else:
            self.progressbar.set_text('?')
            
        self.progressbarbox.pack_start(self.progressbar,
                                       expand=True, fill=True)

        self.buttonevbox = gtk.EventBox()
        self.buttonbox = gtk.HBox(homogeneous=True, spacing=SPACING)

        self.infobutton = gtk.Button()
        self.infoimage = gtk.Image()
        self.infoimage.set_from_stock('bt-info', gtk.ICON_SIZE_BUTTON)
        self.infobutton.add(self.infoimage)
        self.infobutton.connect('clicked', self.open_info)
        self.main.tooltips.set_tip(self.infobutton,
                                   _("Torrent info"))

        self.buttonbox.pack_start(self.infobutton, expand=True)

        self.cancelbutton = gtk.Button()
        self.cancelimage = gtk.Image()
        if self.completion is not None and self.completion >= 1:
            self.cancelimage.set_from_stock('bt-remove', gtk.ICON_SIZE_BUTTON)
            self.main.tooltips.set_tip(self.cancelbutton,
                                       _("Remove torrent"))
        else:
            self.cancelimage.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)
            self.main.tooltips.set_tip(self.cancelbutton,
                                       _("Abort torrent"))
            
        self.cancelbutton.add(self.cancelimage)
        # not using 'clicked' because we want to check for CTRL key
        self.cancelbutton.connect('button-release-event', self.confirm_remove)
        
        self.buttonbox.pack_start(self.cancelbutton, expand=True, fill=False)
        self.buttonevbox.add(self.buttonbox)

        vbuttonbox = gtk.VBox(homogeneous=False)
        vbuttonbox.pack_start(self.buttonevbox, expand=False, fill=False)
        self.hbox.pack_end(vbuttonbox, expand=False, fill=False)

        self.infobox.pack_start(self.progressbarbox, expand=False, fill=False)

        self.hbox.pack_start(self.infobox, expand=True, fill=True)
        self.add( self.vbox )

        self.drag_source_set(gtk.gdk.BUTTON1_MASK,
                             TARGET_ALL,
                             gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)
        self.connect('drag_data_get', self.drag_data_get)

        self.connect('drag_begin' , self.drag_begin )
        self.connect('drag_end'   , self.drag_end   )
        self.cursor_handler_id = self.connect('enter_notify_event', self.change_cursors)


    def set_save_location(self, dlpath):
        self.dlpath = dlpath
        updater_infohash = self.main.updater.infohash
        if updater_infohash == self.infohash:
            my_installer_dir = os.path.split(self.dlpath)[0]
            if self.main.updater.installer_dir != my_installer_dir:
                self.main.updater.set_installer_dir(my_installer_dir)


    def reset_progressbar_color(self):
        # Hack around broken GTK-Wimp theme:
        # make progress bar text always black
        # see task #694
        if is_frozen_exe and self.main.config['progressbar_hack']:
            style = self.progressbar.get_style().copy()
            black = style.black
            self.progressbar.modify_fg(gtk.STATE_PRELIGHT, black)
        

    def change_cursors(self, *args):
        # BUG: this is in a handler that is disconnected because the
        # window attributes are None until after show_all() is called
        self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
        self.buttonevbox.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.LEFT_PTR))
        self.disconnect(self.cursor_handler_id)
        

    def drag_data_get(self, widget, context, selection, targetType, eventTime):
        selection.set(selection.target, 8, self.infohash)

    def drag_begin(self, *args):
        pass

    def drag_end(self, *args):
        self.main.drag_end()

    def make_done_label(self, statistics=None):
        s = ''
        if statistics and statistics['timeEst'] is not None:
            s = _(", will seed for %s") % Duration(statistics['timeEst'])
        elif statistics:
            s = _(", will seed indefinitely.")

        if self.up_down_ratio is not None:
            done_label = _("Done, share ratio: %d%%") % \
                         (self.up_down_ratio*100) + s
        elif statistics is not None:
            done_label = _("Done, %s uploaded") % \
                         Size(statistics['upTotal']) + s
        else:
            done_label = _("Done")

        return done_label
        

    def set_name(self):
        self.label.set_text(self.metainfo.name)
        if gtk.pygtk_version < (2, 6):
            max_title_width = 560
            if self.label.size_request()[0] > max_title_width:
                self.label.set_size_request(max_title_width, -1)
        else:
            self.label.set_ellipsize(pango.ELLIPSIZE_END)

    def make_menu(self, extra_menu_items=[]):
        if self.menu_handler:
            self.disconnect(self.menu_handler)

        ## Basic Info
        menu_items = [ MenuItem(_("Torrent _info"   ), func=self.open_info), ]
        open_dir_func = None
        if LaunchPath.can_launch_files and self.can_open_dir():
            open_dir_func = self.open_dir
        menu_items.append( MenuItem(_("_Open directory" ), func=open_dir_func) )
        filelistfunc = None
        if self.is_batch:
            filelistfunc = self.open_filelist
        menu_items.append(MenuItem(_("_File list"), func=filelistfunc))
        if self.torrent_state == RUNNING:
            menu_items.append(MenuItem(_("_Peer list"), func=self.open_peerlist))
        ## end Basic Info

        menu_items.append(gtk.SeparatorMenuItem())

        ## Settings
        # change save location
        change_save_location_func = None
        if self.torrent_state != RUNNING and self.completion <= 0:
            change_save_location_func = self.change_save_location
        menu_items.append(MenuItem(_("_Change location"),
                                   func=change_save_location_func))
        # seed forever item
        self.seed_forever_item = gtk.CheckMenuItem(_("_Seed indefinitely"))
        self.reset_seed_forever()
        def sft(widget, *args):
            active = widget.get_active()
            infohash = self.infohash
            for option in ('seed_forever', 'seed_last_forever'):
                self.main.torrentqueue.set_config(option, active, infohash)
                self.main.torrentqueue.set_config(option, active, infohash)
        self.seed_forever_item.connect('toggled', sft)
        menu_items.append(self.seed_forever_item)
        ## end Settings

        menu_items.append(gtk.SeparatorMenuItem())

        ## Queue state dependent items
        if self.torrent_state == KNOWN:
            menu_items.append( MenuItem(_("Re_start"), func=self.move_to_end   ))
        elif self.torrent_state == QUEUED:
            #Here's where we'll put the "Start hash check" menu item
            menu_items.append(MenuItem(_("Download _now"), func=self.start))            
        elif self.torrent_state in (RUNNING, RUN_QUEUED):
            # no items for here
            pass

        ## Completion dependent items
        if self.completion is not None and self.completion >= 1:
            if self.torrent_state != KNOWN:
                menu_items.append(MenuItem(_("_Finish"), func=self.finish))
            menu_items.append( MenuItem(_("_Remove" ), func=self.confirm_remove))
        else:
            if self.torrent_state in (RUNNING, RUN_QUEUED):
                menu_items.append(MenuItem(_("Download _later"), func=self.move_to_end))
            else:
                #Here's where we'll put the "Seed _later" menu item
                pass
            menu_items.append(MenuItem(_("_Abort" ), func=self.confirm_remove))

        ## build the menu
        self.menu = gtk.Menu()

        for i in menu_items:
            i.show()
            self.menu.add(i)

        self.menu_handler = self.connect_object("event", self.show_menu, self.menu)

    def reset_seed_forever(self):
        sfb = False
        d = self.main.torrents[self.infohash].config.getDict()
        if d.has_key('seed_forever'):
            sfb = d['seed_forever']
        self.seed_forever_item.set_active(bool(sfb))        

    def change_save_location(self, widget=None):
        self.main.change_save_location(self.infohash)

    def open_info(self, widget=None):
        if self.infowindow is None:
            self.infowindow = TorrentInfoWindow(self, self.infoclosed)
    
    def infoclosed(self, widget=None):
        self.infowindow = None

    def close_info(self):
        if self.infowindow is not None:
            self.infowindow.close()

    def open_filelist(self, widget):
        if not self.is_batch:
            return
        if self.filelistwindow is None:
            self.filelistwindow = FileListWindow(self.metainfo,
                                                 self.filelistclosed)
            self.main.torrentqueue.check_completion(self.infohash, True)

    def filelistclosed(self, widget):
        self.filelistwindow = None

    def close_filelist(self):
        if self.filelistwindow is not None:
            self.filelistwindow.close()

    def close_child_windows(self):
        self.close_info()
        self.close_filelist()

    def destroy(self):
        if self.menu is not None:
            self.menu.destroy()
        self.menu = None
        gtk.EventBox.destroy(self)

    def show_menu(self, widget, event):
        if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
            widget.popup(None, None, None, event.button, event.time)
            return True
        return False

    def _short_path(self, dlpath):
        path_length = 40
        sep = '...'
        ret = os.path.split(dlpath)[0]
        if len(ret) > path_length+len(sep):
            return ret[:int(path_length/2)]+sep+ret[-int(path_length/2):]
        else:
            return ret

    def get_path_to_open(self):
        path = self.dlpath
        if not self.is_batch:
            path = os.path.split(self.dlpath)[0]
        return path

    def can_open_dir(self):
        return os.access(self.get_path_to_open(), os.F_OK|os.R_OK)
        
    def open_dir(self, widget):
        LaunchPath.launchdir(self.get_path_to_open())

    def confirm_remove(self, widget, event=None):
        if event is not None and event.get_state() & gtk.gdk.CONTROL_MASK:
            self.remove()
        else:
            message = _('Are you sure you want to remove "%s"?') % self.metainfo.name
            if self.completion >= 1:
                if self.up_down_ratio is not None:
                    message = _("Your share ratio for this torrent is %d%%. ")%(self.up_down_ratio*100) + message
                else:
                    message = _("You have uploaded %s to this torrent. ")%(Size(self.uptotal)) + message

            d = MessageDialog(self.main.mainwindow,
                              _("Remove this torrent?"),
                              message, 
                              type=gtk.MESSAGE_QUESTION,
                              buttons=gtk.BUTTONS_OK_CANCEL,
                              yesfunc=self.remove,
                              default=gtk.RESPONSE_OK,
                              )

    def remove(self):
        self.main.torrentqueue.remove_torrent(self.infohash)


class KnownTorrentBox(TorrentBox):

    torrent_state = KNOWN

    def __init__(self, infohash, metainfo, dlpath, completion, main):
        TorrentBox.__init__(self, infohash, metainfo, dlpath, completion, main)

        status_tip = ''
        if completion >= 1:
            load_large_toolbar_image(self.icon, 'bt-finished')
            status_tip = _("Finished")
            known_torrent_dnd_tip = _("drag into list to seed")
        else:
            load_large_toolbar_image(self.icon, 'bt-broken')
            status_tip = _("Failed")
            known_torrent_dnd_tip = _("drag into list to resume")

        self.main.tooltips.set_tip(self.iconevbox,
                                   self.torrent_tip_format % (status_tip,
                                                              known_torrent_dnd_tip,
                                                              self.torrent_menu_tip))
        self.make_menu()
        self.show_all()

    def move_to_end(self, widget):
        self.main.change_torrent_state(self.infohash, QUEUED)
        

class DroppableTorrentBox(TorrentBox):

    def __init__(self, infohash, metainfo, dlpath, completion, main):
        TorrentBox.__init__(self, infohash, metainfo, dlpath, completion, main)

        self.drag_dest_set(gtk.DEST_DEFAULT_DROP,
                           TARGET_ALL,
                           gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)

        self.connect('drag_data_received', self.drag_data_received)
        self.connect('drag_motion', self.drag_motion)
        self.index = None

    def drag_data_received(self, widget, context, x, y, selection, targetType, time):
        if targetType == BT_TARGET_TYPE:
            half_height = self.size_request()[1] // 2
            where = cmp(y, half_height)
            if where == 0: where = 1
            self.parent.put_infohash_at_child(selection.data, self, where)
        else:
            self.main.accept_dropped_file(widget, context, x, y, selection, targetType, time)
        
    def drag_motion(self, widget, context, x, y, time):
        self.get_current_index()
        half_height = self.size_request()[1] // 2
        if y < half_height: 
            self.parent.highlight_before_index(self.index)
        else:
            self.parent.highlight_after_index(self.index)
        return False

    def drag_end(self, *args):
        self.parent.highlight_child()
        TorrentBox.drag_end(self, *args)

    def get_current_index(self):
        self.index = self.parent.get_index_from_child(self)


class QueuedTorrentBox(DroppableTorrentBox):
    icon_name = 'bt-queued'
    torrent_state = QUEUED

    def __init__(self, infohash, metainfo, dlpath, completion, main):
        DroppableTorrentBox.__init__(self, infohash, metainfo, dlpath, completion, main)

        self.state_name = _("Waiting")
        self.main.tooltips.set_tip(self.iconevbox,
                                   self.torrent_tip_format % (self.state_name,
                                                              self.main_torrent_dnd_tip, 
                                                              self.torrent_menu_tip))

        load_large_toolbar_image(self.icon, self.icon_name)            
        self.make_menu()
        self.show_all()

    def start(self, widget):
        self.main.runbox.put_infohash_last(self.infohash)

    def finish(self, widget):
        self.main.change_torrent_state(self.infohash, KNOWN)


class PausedTorrentBox(DroppableTorrentBox):
    icon_name = 'bt-paused'
    torrent_state = RUN_QUEUED

    def __init__(self, infohash, metainfo, dlpath, completion, main):
        DroppableTorrentBox.__init__(self, infohash, metainfo, dlpath, completion, main)

        self.state_name = _("Paused")
        self.main.tooltips.set_tip(self.iconevbox,
                                   self.torrent_tip_format % (self.state_name,
                                                              self.main_torrent_dnd_tip,
                                                              self.torrent_menu_tip))

        load_large_toolbar_image(self.icon, self.icon_name)
        self.make_menu()
        self.show_all()

    def move_to_end(self, widget):
        self.main.change_torrent_state(self.infohash, QUEUED)

    def finish(self, widget):
        self.main.change_torrent_state(self.infohash, KNOWN)

    def update_status(self, statistics):
        # in case the TorrentQueue thread calls widget.update_status()
        # before the GUI has changed the torrent widget to a
        # RunningTorrentBox
        pass


class RunningTorrentBox(PausedTorrentBox):
    torrent_state = RUNNING

    def __init__(self, infohash, metainfo, dlpath, completion, main):
        DroppableTorrentBox.__init__(self, infohash, metainfo, dlpath, completion, main)

        self.main.tooltips.set_tip(self.iconevbox,
                                   self.torrent_tip_format % (_("Running"),
                                                              self.main_torrent_dnd_tip,
                                                              self.torrent_menu_tip))

        self.seed = False
        self.peerlistwindow = None
        self.update_peer_list_flag = 0

        load_large_toolbar_image(self.icon, 'bt-running')

        self.rate_label_box = gtk.HBox(homogeneous=True)

        self.up_rate   = gtk.Label()
        self.down_rate = gtk.Label()
        self.rate_label_box.pack_start(lalign(self.up_rate  ),
                                       expand=True, fill=True)
        self.rate_label_box.pack_start(lalign(self.down_rate),
                                       expand=True, fill=True)

        self.infobox.pack_start(self.rate_label_box)        

        if advanced_ui:
            self.extrabox = gtk.VBox(homogeneous=False)
            #self.extrabox = self.vbox
            
            self.up_curr    = FancyLabel(_("Current up: %s"  ), 0)
            self.down_curr  = FancyLabel(_("Current down: %s"), 0)
            self.curr_box   = gtk.HBox(homogeneous=True)
            self.curr_box.pack_start(lalign(self.up_curr  ), expand=True, fill=True)
            self.curr_box.pack_start(lalign(self.down_curr), expand=True, fill=True)
            self.extrabox.pack_start(self.curr_box)
            
            self.up_prev    = FancyLabel(_("Previous up: %s"  ), 0)
            self.down_prev  = FancyLabel(_("Previous down: %s"), 0)
            self.prev_box   = gtk.HBox(homogeneous=True)
            self.prev_box.pack_start(lalign(self.up_prev  ), expand=True, fill=True)
            self.prev_box.pack_start(lalign(self.down_prev), expand=True, fill=True)
            self.extrabox.pack_start(self.prev_box)

            self.share_ratio = FancyLabel(_("Share ratio: %0.02f%%"), 0)
            self.extrabox.pack_start(lalign(self.share_ratio))

            self.peer_info = FancyLabel(_("%s peers, %s seeds. Totals from "
                                          "tracker: %s"), 0, 0, 'NA')
            self.extrabox.pack_start(lalign(self.peer_info))

            self.dist_copies = FancyLabel(_("Distributed copies: %d; Next: %s"), 0, '')
            self.extrabox.pack_start(lalign(self.dist_copies))

            self.piece_info = FancyLabel(_("Pieces: %d total, %d complete, "
                                           "%d partial, %d active (%d empty)"), *(0,)*5)
            self.extrabox.pack_start(lalign(self.piece_info))

            self.bad_info = FancyLabel(_("%d bad pieces + %s in discarded requests"), 0, 0)
            self.extrabox.pack_start(lalign(self.bad_info))

            # extra info
            
            pl = self.metainfo.piece_length
            tl = self.metainfo.total_bytes
            count, lastlen = divmod(tl, pl)
            self.piece_count = count + (lastlen > 0)

            self.infobox.pack_end(self.extrabox, expand=False, fill=False)

        self.make_menu()
        self.show_all() 


    def change_to_completed(self):
        self.completion = 1.0
        self.cancelimage.set_from_stock('bt-remove', gtk.ICON_SIZE_BUTTON)
        self.main.tooltips.set_tip(self.cancelbutton,
                                   _("Remove torrent"))

        updater_infohash = self.main.updater.infohash
        if updater_infohash == self.infohash:
            self.main.updater.start_install()

        self.make_menu()

    def close_child_windows(self):
        TorrentBox.close_child_windows(self)
        self.close_peerlist()

    def open_filelist(self, widget):
        if not self.is_batch:
            return
        if self.filelistwindow is None:
            self.filelistwindow = FileListWindow(self.metainfo,
                                                 self.filelistclosed)
            self.main.make_statusrequest()

    def open_peerlist(self, widget):
        if self.peerlistwindow is None:
            self.peerlistwindow = PeerListWindow(self.metainfo.name,
                                                 self.peerlistclosed)
            self.main.make_statusrequest()

    def peerlistclosed(self, widget):
        self.peerlistwindow = None
        self.update_peer_list_flag = 0

    def close_peerlist(self):
        if self.peerlistwindow is not None:
            self.peerlistwindow.close()

    rate_label = ': %s'

    def update_status(self, statistics):
        fractionDone = statistics.get('fractionDone')
        activity = statistics.get('activity')

        self.main.set_title(torrentName=self.metainfo.name,
                            fractionDone=fractionDone)

        dt = self.downtotal
        if statistics.has_key('downTotal'):
            dt += statistics['downTotal']

        ut = self.uptotal
        if statistics.has_key('upTotal'):
            ut += statistics['upTotal']

        if dt > 0:
            self.up_down_ratio = ut / self.metainfo.total_bytes
        
        eta_label = '?'
        done_label = _("Done")
        if 'numPeers' in statistics:
            eta = statistics.get('timeEst')
            if eta is not None:
                eta_label = Duration(eta)
            if fractionDone == 1:
                done_label = self.make_done_label(statistics)

        if fractionDone == 1:
            self.progressbar.set_fraction(1)
            self.progressbar.set_text(done_label)
            self.reset_seed_forever()
            if not self.completion >= 1:
                self.change_to_completed()
        else:
            self.progressbar.set_fraction(fractionDone)
            progress_bar_label = _("%.1f%% done, %s remaining") % \
                                 (int(fractionDone*1000)/10, eta_label) 
            self.progressbar.set_text(progress_bar_label)
            

        if 'numPeers' not in statistics:
            return

        self.down_rate.set_text(_("Download rate")+self.rate_label %
                                Rate(statistics['downRate']))
        self.up_rate.set_text  (_("Upload rate"  )+self.rate_label %
                                Rate(statistics['upRate']))

        if advanced_ui:
            if self.up_down_ratio is not None:
                self.share_ratio.set_value(self.up_down_ratio*100)

            num_seeds = statistics['numSeeds']
            if self.seed:
                num_seeds = statistics['numOldSeeds'] = 0 # !@# XXX

            if statistics['trackerPeers'] is not None:
                totals = '%d/%d' % (statistics['trackerPeers'],
                                    statistics['trackerSeeds'])
            else:
                totals = _("NA")
            self.peer_info.set_value(statistics['numPeers'], num_seeds, totals)

            self.up_curr.set_value(str(Size(statistics['upTotal'])))
            self.down_curr.set_value(str(Size(statistics['downTotal'])))

            self.up_prev.set_value(str(Size(self.uptotal)))
            self.down_prev.set_value(str(Size(self.downtotal)))

            # refresh extra info
            self.piece_info.set_value(self.piece_count,
                                      statistics['storage_numcomplete'],
                                      statistics['storage_dirty'],
                                      statistics['storage_active'],
                                      statistics['storage_new'] )

            self.dist_copies.set_value( statistics['numCopies'], ', '.join(["%d:%.1f%%" % (a, int(b*1000)/10) for a, b in zip(itertools.count(int(statistics['numCopies']+1)), statistics['numCopyList'])]))

            self.bad_info.set_value(statistics['storage_numflunked'], Size(statistics['discarded']))
        
        if self.peerlistwindow is not None:
            if self.update_peer_list_flag == 0:
                spew = statistics.get('spew')
                if spew is not None:
                    self.peerlistwindow.update(spew, statistics['bad_peers'])
            self.update_peer_list_flag = (self.update_peer_list_flag + 1) % 4
        if self.filelistwindow is not None:
            if 'files_left' in statistics:
                self.filelistwindow.update(statistics['files_left'],
                                           statistics['files_allocated'])


class DroppableHSeparator(PaddedHSeparator):

    def __init__(self, box, spacing=SPACING):
        PaddedHSeparator.__init__(self, spacing)
        self.box = box
        self.main = box.main

        self.drag_dest_set(gtk.DEST_DEFAULT_DROP,
                           TARGET_ALL,
                           gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)

        self.connect('drag_data_received', self.drag_data_received)
        self.connect('drag_motion'       , self.drag_motion       )

    def drag_highlight(self):
        self.sep.drag_highlight()
        self.main.add_unhighlight_handle()

    def drag_unhighlight(self):
        self.sep.drag_unhighlight()

    def drag_data_received(self, widget, context, x, y, selection, targetType, time):
        if targetType == BT_TARGET_TYPE:
            self.box.drop_on_separator(self, selection.data)
        else:
            self.main.accept_dropped_file(widget, context, x, y, selection, targetType, time)

    def drag_motion(self, wid, context, x, y, time):
        self.drag_highlight()
        return False


class DroppableBox(HSeparatedBox):
    def __init__(self, main, spacing=0):
        HSeparatedBox.__init__(self, spacing=spacing)
        self.main = main
        self.drag_dest_set(gtk.DEST_DEFAULT_DROP,
                           TARGET_ALL,
                           gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)
        self.connect('drag_data_received', self.drag_data_received)
        self.connect('drag_motion', self.drag_motion)

    def drag_motion(self, widget, context, x, y, time):
        return False

    def drag_data_received(self, widget, context, x, y, selection, targetType, time):
        pass


class KnownBox(DroppableBox):

    def __init__(self, main, spacing=0):
        DroppableBox.__init__(self, main, spacing=spacing)
        self.drag_dest_set(gtk.DEST_DEFAULT_DROP,
                           TARGET_ALL,
                           gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)

    def pack_start(self, widget, *args, **kwargs):
        old_len = len(self.get_children())
        DroppableBox.pack_start(self, widget, *args, **kwargs)
        if old_len <= 0:
            self.main.maximize_known_pane()
        self.main.knownscroll.scroll_to_bottom()

    def remove(self, widget):
        DroppableBox.remove(self, widget)
        new_len = len(self.get_children())
        if new_len == 0:
            self.main.maximize_known_pane()

    def drag_data_received(self, widget, context, x, y, selection, targetType, time):
        if targetType == BT_TARGET_TYPE:
            infohash = selection.data
            self.main.finish(infohash)
        else:
            self.main.accept_dropped_file(widget, context, x, y, selection, targetType, time)

    def drag_motion(self, widget, context, x, y, time):
        self.main.drag_highlight(widget=self)
    
    def drag_highlight(self):
        self.main.knownscroll.drag_highlight()
        self.main.add_unhighlight_handle()

    def drag_unhighlight(self):
        self.main.knownscroll.drag_unhighlight()


class RunningAndQueueBox(gtk.VBox):

    def __init__(self, main, **kwargs):
        gtk.VBox.__init__(self, **kwargs)
        self.main = main

    def drop_on_separator(self, sep, infohash):
        self.main.change_torrent_state(infohash, QUEUED, 0)

    def highlight_between(self):
        self.drag_highlight()

    def drag_highlight(self):
        self.get_children()[1].drag_highlight()

    def drag_unhighlight(self):
        self.get_children()[1].drag_unhighlight()
        

class SpacerBox(DroppableBox):
    
    def drag_data_received(self, widget, context, x, y, selection, targetType, time):
        if targetType == BT_TARGET_TYPE:
            infohash = selection.data
            self.main.queuebox.put_infohash_last(infohash)
        else:
            self.main.accept_dropped_file(widget, context, x, y, selection, targetType, time)
            
        return True

BEFORE = -1
AFTER  =  1

class ReorderableBox(DroppableBox):

    def new_separator(self):
        return DroppableHSeparator(self)

    def __init__(self, main):
        DroppableBox.__init__(self, main)
        self.main = main

        self.drag_dest_set(gtk.DEST_DEFAULT_DROP,
                           TARGET_ALL,
                           gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)

        self.connect('drag_data_received', self.drag_data_received)
        self.connect('drag_motion'       , self.drag_motion)

    def drag_data_received(self, widget, context, x, y, selection, targetType, time):
        
        if targetType == BT_TARGET_TYPE:
            half_height = self.size_request()[1] // 2
            if y < half_height:
                self.put_infohash_first(selection.data)
            else:
                self.put_infohash_last(selection.data)
        else:
            self.main.accept_dropped_file(widget, context, x, y, selection, targetType, time)
        return True

    def drag_motion(self, widget, context, x, y, time):
        return False

    def drag_highlight(self):
        final = self.get_children()[-1]
        final.drag_highlight()
        self.main.add_unhighlight_handle()

    def drag_unhighlight(self): 
        self.highlight_child(index=None)
        self.parent.drag_unhighlight()

    def highlight_before_index(self, index):
        self.drag_unhighlight()
        children = self._get_children()
        if index > 0:
            children[index*2 - 1].drag_highlight()
        else:
            self.highlight_at_top()

    def highlight_after_index(self, index):
        self.drag_unhighlight()
        children = self._get_children()
        if index*2 < len(children)-1:
            children[index*2 + 1].drag_highlight()
        else:
            self.highlight_at_bottom()

    def highlight_child(self, index=None):
        for i, child in enumerate(self._get_children()):
            if index is not None and i == index*2:
                child.drag_highlight()
            else:
                child.drag_unhighlight()


    def drop_on_separator(self, sep, infohash):
        children = self._get_children()
        for i, child in enumerate(children):
            if child == sep:
                reference_child = children[i-1]
                self.put_infohash_at_child(infohash, reference_child, AFTER)
                break


    def get_queue(self):
        queue = []
        c = self.get_children()
        for t in c:
            queue.append(t.infohash)
        return queue

    def put_infohash_first(self, infohash):
        self.highlight_child()
        children = self.get_children()
        if len(children) > 1 and infohash == children[0].infohash:
            return
        
        self.put_infohash_at_index(infohash, 0)

    def put_infohash_last(self, infohash):
        self.highlight_child()
        children = self.get_children()
        end = len(children)
        if len(children) > 1 and infohash == children[end-1].infohash:
            return

        self.put_infohash_at_index(infohash, end)

    def put_infohash_at_child(self, infohash, reference_child, where):
        self.highlight_child()
        if infohash == reference_child.infohash:
            return
        
        target_index = self.get_index_from_child(reference_child)
        if where == AFTER:
            target_index += 1
        self.put_infohash_at_index(infohash, target_index)

    def get_index_from_child(self, child):
        c = self.get_children()
        ret = -1
        try:
            ret = c.index(child)
        except ValueError:
            pass
        return ret

    def highlight_at_top(self):
        raise NotImplementedError

    def highlight_at_bottom(self):
        raise NotImplementedError

    def put_infohash_at_index(self, infohash, end):
        raise NotImplementedError

class RunningBox(ReorderableBox):

    def put_infohash_at_index(self, infohash, target_index):
        #print 'RunningBox.put_infohash_at_index', infohash.encode('hex')[:8], target_index

        l = self.get_queue()
        replaced = None
        if l:
            replaced = l[-1]
        self.main.confirm_replace_running_torrent(infohash, replaced,
                                                  target_index)

    def highlight_at_top(self):
        pass
        # BUG: Don't know how I will indicate in the UI that the top of the list is highlighted

    def highlight_at_bottom(self):
        self.parent.highlight_between()


class QueuedBox(ReorderableBox):

    def put_infohash_at_index(self, infohash, target_index):
        #print 'want to put', infohash.encode('hex'), 'at', target_index
        self.main.change_torrent_state(infohash, QUEUED, target_index)

    def highlight_at_top(self):
        self.parent.highlight_between()

    def highlight_at_bottom(self):
        pass
        # BUG: Don't know how I will indicate in the UI that the bottom of the list is highlighted



class Struct(object):
    pass


class SearchField(gtk.Entry):
    def __init__(self, default_text, visit_url_func):
        gtk.Entry.__init__(self)
        self.default_text = default_text
        self.visit_url_func = visit_url_func
        self.set_text(self.default_text)
        self.set_size_request(150, -1)

        # default gtk Entry dnd processing is broken on linux!
        #  - default Motion handling causes asyncs
        #  - there's no way to filter the default text dnd
        # see the parent window for a very painful work-around
        self.drag_dest_unset()
        
        self.connect('key-press-event', self.check_for_enter)
        self.connect('button-press-event', self.begin_edit)
        self.search_completion = gtk.EntryCompletion()
        self.search_completion.set_text_column(0)
        self.search_store = gtk.ListStore(gobject.TYPE_STRING)
        self.search_completion.set_model(self.search_store)
        self.set_completion(self.search_completion)
        self.reset_text()
        self.timeout_id = None

    def begin_edit(self, *args):
        if self.get_text() == self.default_text:
            self.set_text('')

    def check_for_enter(self, widget, event):
        if event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
            self.search()

    def reset_text(self):
        self.set_text(self.default_text)

    def search(self, *args):
        search_term = self.get_text()
        if search_term and search_term != self.default_text:
            self.search_store.append([search_term])
            search_url = SEARCH_URL % {'query' :zurllib.quote(search_term),
                                       'client':'M-%s'%version.replace('.','-')}
            
            self.timeout_id = gobject.timeout_add(2000, self.resensitize)
            self.set_sensitive(False)
            self.visit_url_func(search_url, callback=self.resensitize)
        else:
            self.reset_text()
            self.select_region(0, -1)
            self.grab_focus()

    def resensitize(self):
        self.set_sensitive(True)
        self.reset_text()
        if self.timeout_id is not None:
            gobject.source_remove(self.timeout_id)
            self.timeout_id = None


class DownloadInfoFrame(object):

    def __init__(self, config, torrentqueue):
        self.config = config
        if self.config['save_in'] == '':
           self.config['save_in'] = smart_dir('')
        
        self.torrentqueue = torrentqueue
        self.torrents = {}
        self.running_torrents = {}
        self.lists = {}
        self.update_handle = None
        self.unhighlight_handle = None
        self.custom_size = False
        self.child_windows = {}
        self.postponed_save_windows = []
        self.helpwindow     = None
        self.errordialog    = None

        gtk.threads_enter()

        self.mainwindow = Window(gtk.WINDOW_TOPLEVEL)
        self.mainwindow.set_border_width(0)

        self.set_seen_remote_connections(False)

        self.mainwindow.drag_dest_set(gtk.DEST_DEFAULT_ALL,
                                      TARGET_EXTERNAL,
                                      gtk.gdk.ACTION_MOVE|gtk.gdk.ACTION_COPY)

        self.mainwindow.connect('drag_leave'        , self.drag_leave         )
        self.mainwindow.connect('drag_data_received', self.accept_dropped_file)

        self.mainwindow.set_size_request(WINDOW_WIDTH, -1)

        self.mainwindow.connect('destroy', self.cancel)

        self.mainwindow.connect('size-allocate', self.size_was_allocated)

        self.accel_group = gtk.AccelGroup()

        self.mainwindow.add_accel_group(self.accel_group)

        #self.accel_group.connect(ord('W'), gtk.gdk.CONTROL_MASK, gtk.ACCEL_LOCKED,
        #                         lambda *args: self.mainwindow.destroy())

        self.tooltips = gtk.Tooltips()

        self.logbuffer = LogBuffer()
        self.log_text(_("%s started")%app_name, severity=None)

        self.box1 = gtk.VBox(homogeneous=False, spacing=0)

        self.box2 = gtk.VBox(homogeneous=False, spacing=0)
        self.box2.set_border_width(SPACING)

        self.menubar = gtk.MenuBar()
        self.box1.pack_start(self.menubar, expand=False, fill=False)

        self.ssbutton = StopStartButton(self)

        # keystrokes used: A D F H L N O P Q S U (E)
        file_menu_items = ((_("_Open torrent file"), self.select_torrent_to_open),
                           (_("Open torrent _URL"), self.enter_url_to_open),
                           (_("Make _new torrent" ), self.make_new_torrent),

                           ('----'          , None),
                           (_("_Pause/Play"), self.ssbutton.toggle),
                           ('----'          , None),
                           (_("_Quit")      , lambda w: self.mainwindow.destroy()),
                           )
        view_menu_items = ((_("Show/Hide _finished torrents"), self.toggle_known),
                           # BUG: if you reorder this menu, see def set_custom_size() first
                           (_("_Resize window to fit"), lambda w: self.resize_to_fit()),
                           ('----'             , None),
                           (_("_Log")          , lambda w: self.open_window('log')),
                           # 'View log of all download activity',
                           #('----'            , None),
                           (_("_Settings")     , lambda w: self.open_window('settings')),
                           #'Change download behavior and network settings',
                           )
        help_menu_items = ((_("_Help")         , self.open_help),
                           #(_("_Help Window") , lambda w: self.open_window('help')),
                           (_("_About")         , lambda w: self.open_window('about')),
                           (_("_Donate")        , lambda w: self.donate()),
                           #(_("Rais_e")        , lambda w: self.raiseerror()), 
                           )
        
        self.filemenu = gtk.MenuItem(_("_File"))

        self.filemenu.set_submenu(build_menu(file_menu_items, self.accel_group))
        self.filemenu.show()

        self.viewmenu = gtk.MenuItem(_("_View"))
        self.viewmenu.set_submenu(build_menu(view_menu_items, self.accel_group))
        self.viewmenu.show()

        self.helpmenu = gtk.MenuItem(_("_Help"))
        self.helpmenu.set_submenu(build_menu(help_menu_items, self.accel_group))
        self.helpmenu.show()

        if os.name != 'nt':
            self.helpmenu.set_right_justified(True)

        self.menubar.append(self.filemenu)
        self.menubar.append(self.viewmenu)
        self.menubar.append(self.helpmenu)
        
        self.menubar.show()

        self.header = gtk.HBox(homogeneous=False)

        self.box1.pack_start(self.box2, expand=False, fill=False)

        # control box: rate slider, start-stop button, search widget, status light

        self.controlbox = gtk.HBox(homogeneous=False)

        controlbox_padding = SPACING//2

        # stop-start button
        self.controlbox.pack_start(malign(self.ssbutton),
                                   expand=False, fill=False)

        # rate slider
        self.rate_slider_box = RateSliderBox(self.config, self.torrentqueue)
        self.controlbox.pack_start(self.rate_slider_box,
                                   expand=True, fill=True,
                                   padding=controlbox_padding)

        self.controlbox.pack_start(gtk.VSeparator(), expand=False, fill=False,
                                   padding=controlbox_padding)

        # search box 
        self.search_field = SearchField(_("Search for torrents"), self.visit_url)
        sfa = gtk.Alignment(xalign=0, yalign=0.5, xscale=1, yscale=0)
        sfa.add(self.search_field)
        self.controlbox.pack_start(sfa,
                                   expand=False, fill=False, padding=controlbox_padding)

        # separator
        self.controlbox.pack_start(gtk.VSeparator(), expand=False, fill=False,
                                   padding=controlbox_padding)

        # status light
        self.status_light = StatusLight(self)

        self.controlbox.pack_start(malign(self.status_light),
                                   expand=False, fill=False)

        self.box2.pack_start(self.controlbox,
                             expand=False, fill=False, padding=0)
        # end control box

        self.paned = gtk.VPaned()

        self.knownscroll = ScrolledWindow()
        self.knownscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
        self.knownscroll.set_shadow_type(gtk.SHADOW_NONE)
        self.knownscroll.set_size_request(-1, SPACING)

        self.knownbox = KnownBox(self)
        self.knownbox.set_border_width(SPACING)

        self.knownscroll.add_with_viewport(self.knownbox)
        self.paned.pack1(self.knownscroll, resize=False, shrink=True)

        
        self.mainscroll = AutoScrollingWindow()
        self.mainscroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
        self.mainscroll.set_shadow_type(gtk.SHADOW_NONE)
        self.mainscroll.set_size_request(-1, SPACING)

        self.scrollbox = RunningAndQueueBox(self, homogeneous=False)
        self.scrollbox.set_border_width(SPACING)
        
        self.runbox = RunningBox(self)
        self.scrollbox.pack_start(self.runbox, expand=False, fill=False)

        self.scrollbox.pack_start(DroppableHSeparator(self.scrollbox), expand=False, fill=False)

        self.queuebox = QueuedBox(self)
        self.scrollbox.pack_start(self.queuebox, expand=False, fill=False)

        self.scrollbox.pack_start(SpacerBox(self), expand=True, fill=True) 

        self.mainscroll.add_with_viewport(self.scrollbox)

        self.paned.pack2(self.mainscroll, resize=True, shrink=False)

        self.box1.pack_start(self.paned)

        self.box1.show_all()

        self.mainwindow.add(self.box1)

        self.set_title()
        self.set_size()
        self.mainwindow.show()
        self.paned.set_position(0)
        self.search_field.grab_focus()

        self.updater = NewVersion.Updater(
            gtk_wrap, 
            self.new_version, 
            self.torrentqueue.start_new_torrent,
            self.confirm_install_new_version   ,
            self.global_error                  )

        self.nag()
        
        gtk.threads_leave()

    def drag_leave(self, *args):
        self.drag_end()

    def make_new_torrent(self, widget=None):
        #raise self.torrentqueue.wrapped.controlsocket
        spawn(self.torrentqueue, 'maketorrent')

    def accept_dropped_file(self, widget, context, x, y, selection,
                            targetType, time):
        if targetType == EXTERNAL_FILE_TYPE:
            d = selection.data.strip()
            file_uris = d.split('\r\n')
            for file_uri in file_uris:
                # this catches non-url entries, I've seen "\x00" at the end of lists
                if file_uri.find(':/') != -1:
                    file_name = zurllib.url2pathname(file_uri)
                    file_name = file_name[7:]
                    if os.name == 'nt':
                        file_name = file_name.strip('\\')
                    self.open_torrent( file_name )
        elif targetType == EXTERNAL_STRING_TYPE:

            data = selection.data.strip()

            # size must be > 0,0 for the intersection code to register it
            drop_rect = gtk.gdk.Rectangle(x, y, 1, 1)
            if ((self.search_field.intersect(drop_rect) is not None) and
                (not data.lower().endswith(".torrent"))):

                client_point = self.mainwindow.translate_coordinates(self.search_field, x, y)
                layout_offset = self.search_field.get_layout_offsets()
                point = []
                # subtract (not add) the offset, because we're hit-testing the layout, not the widget
                point.append(client_point[0] - layout_offset[0])
                point.append(client_point[1] - layout_offset[1])
                # ha ha ha. pango is so ridiculous
                point[0] *= pango.SCALE
                point[1] *= pango.SCALE
                layout = self.search_field.get_layout()
                position = layout.xy_to_index(*point)
                self.search_field.insert_text(data, position[0])
            else:                    
                self.open_url(data)

    def drag_highlight(self, widget=None):
        widgets = (self.knownbox, self.runbox, self.queuebox) 
        for w in widgets:
            if w != widget:
                w.drag_unhighlight()
        for w in widgets:
            if w == widget:
                w.drag_highlight()
                self.add_unhighlight_handle()

    def drag_end(self):
        self.drag_highlight(widget=None)
        self.mainscroll.stop_scrolling()

    def set_title(self, torrentName=None, fractionDone=None):
        title = app_name
        trunc = '...'
        sep = ': '

        if self.config['pause']:
            title += sep+_("(stopped)")
        elif len(self.running_torrents) == 1 and torrentName and \
               fractionDone is not None:
            maxlen = WINDOW_TITLE_LENGTH - len(app_name) - len(trunc) - len(sep)
            if len(torrentName) > maxlen:
                torrentName = torrentName[:maxlen] + trunc
            title = '%s%s%0.1f%%%s%s'% (app_name,
                                            sep,
                                            (int(fractionDone*1000)/10),
                                            sep,
                                            torrentName)
        elif len(self.running_torrents) > 1:
            title += sep+_("(multiple)")

        self.mainwindow.set_title(title)

    def _guess_size(self):
        paned_height = self.scrollbox.size_request()[1]
        if hasattr(self.paned, 'style_get_property'):
            paned_height += self.paned.style_get_property('handle-size')
        else:
            paned_height += 5
        paned_height += self.paned.get_position()
        paned_height += 4 # fudge factor, probably from scrolled window beveling ?
        paned_height = max(paned_height, MIN_MULTI_PANE_HEIGHT)

        new_height = self.menubar.size_request()[1] + \
                     self.box2.size_request()[1] + \
                     paned_height
        new_height = min(new_height, MAX_WINDOW_HEIGHT)
        new_width = max(self.scrollbox.size_request()[0] + SCROLLBAR_WIDTH, WINDOW_WIDTH)
        return new_width, new_height

    def set_size(self):
        if not self.custom_size:
            self.mainwindow.resize(*self._guess_size())

    def size_was_allocated(self, *args):
        current_size = self.mainwindow.get_size()
        target_size = self._guess_size()
        if current_size == target_size:
            self.set_custom_size(False)
        else:
            self.set_custom_size(True)

    def resize_to_fit(self):
        self.set_custom_size(False)
        self.set_size()

    def set_custom_size(self, val):
        self.custom_size = val
        # BUG this is a hack:
        self.viewmenu.get_submenu().get_children()[1].set_sensitive(val)

    # BUG need to add handler on resize event to keep track of
    # old_position when pane is hidden manually
    def split_pane(self):
        pos = self.paned.get_position()
        if pos > 0:
            self.paned.old_position = pos
            self.paned.set_position(0)
        else:
            if hasattr(self.paned, 'old_position'):
                self.paned.set_position(self.paned.old_position)
            else:
                self.maximize_known_pane()

    def maximize_known_pane(self):
        self.set_pane_position(self.knownbox.size_request()[1])        

    def set_pane_position(self, pane_position):
            pane_position = min(MAX_WINDOW_HEIGHT//2, pane_position)
            self.paned.set_position(pane_position)

    def toggle_known(self, widget=None):
        self.split_pane()

    def open_window(self, window_name, *args, **kwargs):
        if os.name == 'nt':
            self.mainwindow.present()
        savewidget = SaveFileSelection
        if window_name == 'savedir':
            savewidget = CreateFolderSelection
            window_name = 'savefile'
        if self.child_windows.has_key(window_name):
            if window_name == 'savefile':
                kwargs['show'] = False
                self.postponed_save_windows.append(savewidget(self, **kwargs))
            return

        if window_name == 'log'       :
            self.child_windows[window_name] = LogWindow(self, self.logbuffer, self.config)
        elif window_name == 'about'   :
            self.child_windows[window_name] = AboutWindow(self, lambda w: self.donate())
        elif window_name == 'help'    :
            self.child_windows[window_name] = HelpWindow(self, makeHelp('bittorrent', defaults))
        elif window_name == 'settings':
            self.child_windows[window_name] = SettingsWindow(self, self.config, self.set_config)
        elif window_name == 'version' :
            self.child_windows[window_name] = VersionWindow(self, *args)
        elif window_name == 'openfile':
            self.child_windows[window_name] = OpenFileSelection(self, **kwargs)
        elif window_name == 'savefile':
            self.child_windows[window_name] = savewidget(self, **kwargs)
        elif window_name == 'choosefolder':
            self.child_windows[window_name] = ChooseFolderSelection(self, **kwargs)            
        elif window_name == 'enterurl':
            self.child_windows[window_name] = EnterUrlDialog(self, **kwargs)

        return self.child_windows[window_name]

    def window_closed(self, window_name):
        if self.child_windows.has_key(window_name):
            del self.child_windows[window_name]
        if window_name == 'savefile' and self.postponed_save_windows:
            newwin = self.postponed_save_windows.pop(-1)
            newwin.show()
            self.child_windows['savefile'] = newwin
    
    def close_window(self, window_name):
        self.child_windows[window_name].close(None)

    def new_version(self, newversion, download_url):
        if not self.config['notified'] or \
               newversion != NewVersion.Version.from_str(self.config['notified']):
            if not self.torrents.has_key(self.updater.infohash):
                self.open_window('version', newversion, download_url)
            else:
                dlpath = os.path.split(self.torrents[self.updater.infohash].dlpath)[0]
                self.updater.set_installer_dir(dlpath)
                self.updater.start_install()

    def check_version(self):
        self.updater.check() 

    def start_auto_update(self):
        if not self.torrents.has_key(self.updater.infohash):
            self.updater.download()
        else:
            self.global_error(INFO, _("Already downloading %s installer") % self.updater.version)

    def confirm_install_new_version(self):
        MessageDialog(self.mainwindow,
                      _("Install new %s now?")%app_name,
                      _("Do you want to quit %s and install the new version, "
                        "%s, now?")%(app_name,self.updater.version),
                      type=gtk.MESSAGE_QUESTION,
                      buttons=gtk.BUTTONS_YES_NO,
                      yesfunc=self.install_new_version,
                      nofunc=None,
                      default=gtk.RESPONSE_YES
                      )

    def install_new_version(self):
        self.updater.launch_installer()
        self.cancel()
        

    def open_help(self,widget):
        if self.helpwindow is None:
            msg = (_("%s help is at \n%s\nWould you like to go there now?")%
                  (app_name, HELP_URL))
            self.helpwindow = MessageDialog(self.mainwindow,
                                            _("Visit help web page?"),
                                            msg,
                                            type=gtk.MESSAGE_QUESTION,
                                            buttons=gtk.BUTTONS_OK_CANCEL,
                                            yesfunc=self.visit_help,
                                            nofunc =self.help_closed,
                                            default=gtk.RESPONSE_OK
                                            )

    def visit_help(self):
        self.visit_url(HELP_URL)
        self.help_closed()
        
    def close_help(self):
        self.helpwindow.close()

    def help_closed(self, widget=None):
        self.helpwindow = None


    def set_config(self, option, value):
        self.config[option] = value
        if option == 'display_interval':
            self.init_updates()
        self.torrentqueue.set_config(option, value)


    def confirm_remove_finished_torrents(self,widget):
        count = 0
        for infohash, t in self.torrents.iteritems():
            if t.state == KNOWN and t.completion >= 1:
                count += 1
        if count:
            if self.paned.get_position() == 0:
                self.toggle_known()
            msg = ''
            if count == 1:
                msg = _("There is one finished torrent in the list. ") + \
                      _("Do you want to remove it?")
            else:
                msg = _("There are %d finished torrents in the list. ") % count +\
                      _("Do you want to remove all of them?")
            MessageDialog(self.mainwindow,
                          _("Remove all finished torrents?"),
                          msg,
                          type=gtk.MESSAGE_QUESTION,
                          buttons=gtk.BUTTONS_OK_CANCEL,
                          yesfunc=self.remove_finished_torrents,
                          default=gtk.RESPONSE_OK)
        else:
            MessageDialog(self.mainwindow,
                          _("No finished torrents"),
                          _("There are no finished torrents to remove."),
                          type=gtk.MESSAGE_INFO,
                          default=gtk.RESPONSE_OK)

    def remove_finished_torrents(self):
        for infohash, t in self.torrents.iteritems():
            if t.state == KNOWN and t.completion >= 1:
                self.torrentqueue.remove_torrent(infohash)
        if self.paned.get_position() > 0:
            self.toggle_known()

    def cancel(self, widget=None):
        for window_name in self.child_windows.keys():
            self.close_window(window_name)
        
        if self.errordialog is not None:
            self.errordialog.destroy()
            self.errors_closed()

        for t in self.torrents.itervalues():
            if t.widget is not None:
                t.widget.close_child_windows()

        self.torrentqueue.set_done()
        gtk.main_quit()

    # Currently called if the user started bittorrent from a terminal
    # and presses ctrl-c there
    def quit(self):
        self.mainwindow.destroy()

    def make_statusrequest(self):
        if self.config['pause']:
            return True
        for infohash, t in self.running_torrents.iteritems():
            self.torrentqueue.request_status(infohash, t.widget.peerlistwindow
                             is not None, t.widget.filelistwindow is not None)
        return True

    def enter_url_to_open(self, widget): 
        self.open_window('enterurl')

    def open_url(self, url):
        data, errors = GetTorrent.get_url(url)
        if data:
            self.torrentqueue.start_new_torrent(data)
        if errors:
            for e in errors:
                self.global_error(ERROR, e)

    def select_torrent_to_open(self, widget):
        open_location = self.config['open_from']
        if not open_location:
            open_location = self.config['save_in']
        path = smart_dir(open_location) 
        self.open_window('openfile',
                         title=_("Open torrent:"),
                         fullname=path,
                         got_location_func=self.open_torrent,
                         no_location_func=lambda: self.window_closed('openfile'))


    def open_torrent(self, name):
        self.window_closed('openfile')
        open_location = os.path.split(name)[0]
        if open_location[-1] != os.sep:
            open_location += os.sep
        self.set_config('open_from',  open_location)

        data, errors = GetTorrent.get_file(name)
        if data:
            self.torrentqueue.start_new_torrent(data)

        if errors:
            for e in errors:
                self.global_error(ERROR, e)


    def change_save_location(self, infohash):
        def no_location():
            self.window_closed('savefile')

        t = self.torrents[infohash]
        metainfo = t.metainfo
        
        selector = self.open_window(metainfo.is_batch and 'savedir' or \
                                                          'savefile',
                      title=_("Change save location for ") + metainfo.name,
                      fullname=t.dlpath,
                      got_location_func = \
                            lambda fn: self.got_changed_location(infohash, fn),
                      no_location_func=no_location)

    def got_changed_location(self, infohash, fullpath):
        self.window_closed('savefile')
        self.torrentqueue.set_save_location(infohash, fullpath)

    def save_location(self, infohash, metainfo):
        name = metainfo.name_fs

        if self.config['save_as'] and \
               os.access(os.path.split(self.config['save_as'])[0], os.W_OK):
            path = self.config['save_as']
            self.got_location(infohash, path, store_in_config=False)
            self.config['save_as'] = ''
            return

        path = smart_dir(self.config['save_in'])

        fullname = os.path.join(path, name)

        if not self.config['ask_for_save']:
            if os.access(fullname, os.F_OK):
                message = MessageDialog(self.mainwindow,
                                        _("File exists!"),
                                        _('"%s" already exists. '
                                          "Do you want to choose a different file name?") % path_wrap(name),
                                        buttons=gtk.BUTTONS_YES_NO,
                                        nofunc= lambda : self.got_location(infohash, fullname),
                                        yesfunc=lambda : self.get_save_location(infohash, metainfo, fullname),
                                        default=gtk.RESPONSE_NO)

            else:
                self.got_location(infohash, fullname)
        else:
            self.get_save_location(infohash, metainfo, fullname)

    def get_save_location(self, infohash, metainfo, fullname):
        def no_location():
            self.window_closed('savefile')
            self.torrentqueue.remove_torrent(infohash)

        selector = self.open_window(metainfo.is_batch and 'savedir' or \
                                                          'savefile',
                                    title=_("Save location for ") + metainfo.name,
                                    fullname=fullname,
                                    got_location_func = lambda fn: \
                                              self.got_location(infohash, fn),
                                    no_location_func=no_location)
        
        self.torrents[infohash].widget = selector

    def got_location(self, infohash, fullpath, store_in_config=True):
        self.window_closed('savefile')
        self.torrents[infohash].widget = None
        save_in = os.path.split(fullpath)[0]

        metainfo = self.torrents[infohash].metainfo
        if metainfo.is_batch:
            bottom_dirs, top_dir_name = os.path.split(save_in)
            if metainfo.name_fs == top_dir_name:

                message = MessageDialog(self.mainwindow, _("Directory exists!"),
                                        _('"%s" already exists.'\
                                          " Do you intend to create an identical,"\
                                          " duplicate directory inside the existing"\
                                          " directory?")%path_wrap(save_in),
                                        buttons=gtk.BUTTONS_YES_NO,
                                        nofunc =lambda : self.got_location(infohash, save_in ),
                                        yesfunc=lambda : self._got_location(infohash, save_in, fullpath, store_in_config=store_in_config),
                                        default=gtk.RESPONSE_NO,
                                        )
                return
        self._got_location(infohash, save_in, fullpath, store_in_config=store_in_config)

    def _got_location(self, infohash, save_in, fullpath, store_in_config=True):
        if store_in_config:
            if save_in[-1] != os.sep:
                save_in += os.sep
            self.set_config('save_in', save_in)
        self.torrents[infohash].dlpath = fullpath
        self.torrentqueue.set_save_location(infohash, fullpath)

    def add_unhighlight_handle(self):
        if self.unhighlight_handle is not None:
            gobject.source_remove(self.unhighlight_handle)
            
        self.unhighlight_handle = gobject.timeout_add(2000,
                                                      self.unhighlight_after_a_while,
                                                      priority=gobject.PRIORITY_LOW)

    def unhighlight_after_a_while(self):
        self.drag_highlight()
        gobject.source_remove(self.unhighlight_handle)
        self.unhighlight_handle = None
        return False

    def init_updates(self):
        if self.update_handle is not None:
            gobject.source_remove(self.update_handle)
        self.update_handle = gobject.timeout_add(
            int(self.config['display_interval'] * 1000),
            self.make_statusrequest)

    def remove_torrent_widget(self, infohash):
        t = self.torrents[infohash]
        self.lists[t.state].remove(infohash)
        if t.state == RUNNING:
            del self.running_torrents[infohash]
            self.set_title()
        if t.state == ASKING_LOCATION:
            if t.widget is not None:
                t.widget.destroy()
            return

        if t.state in (KNOWN, RUNNING, QUEUED):
            t.widget.close_child_windows()

        if t.state == RUNNING:
            self.runbox.remove(t.widget)
        elif t.state == QUEUED:
            self.queuebox.remove(t.widget)
        elif t.state == KNOWN:
            self.knownbox.remove(t.widget)
            
        t.widget.destroy()

        self.set_size()

    def create_torrent_widget(self, infohash, queuepos=None):
        t = self.torrents[infohash]
        l = self.lists.setdefault(t.state, [])
        if queuepos is None:
            l.append(infohash)
        else:
            l.insert(queuepos, infohash)
        if t.state == ASKING_LOCATION:
            self.save_location(infohash, t.metainfo)
            self.nag()
            return
        elif t.state == RUNNING:
            self.running_torrents[infohash] = t
            if not self.config['pause']:
                t.widget = RunningTorrentBox(infohash, t.metainfo, t.dlpath,
                                             t.completion, self)
            else:
                t.widget = PausedTorrentBox(infohash, t.metainfo, t.dlpath,
                                             t.completion, self)
            box = self.runbox
        elif t.state == QUEUED:
            t.widget = QueuedTorrentBox(infohash, t.metainfo, t.dlpath,
                                        t.completion, self)
            box = self.queuebox
        elif t.state == KNOWN:
            t.widget = KnownTorrentBox(infohash, t.metainfo, t.dlpath,
                                       t.completion, self)
            box = self.knownbox
        box.pack_start(t.widget, expand=False, fill=False)
        if queuepos is not None:
            box.reorder_child(t.widget, queuepos)

        self.set_size()

    def log_text(self, text, severity=ERROR):
        self.logbuffer.log_text(text, severity)
        if self.child_windows.has_key('log'):
            self.child_windows['log'].scroll_to_end()

    def _error(self, severity, err_str):
        err_str = err_str.decode('utf-8', 'replace').encode('utf-8')
        if severity >= ERROR:
            self.error_modal(err_str)
        self.log_text(err_str, severity)

    def error(self, infohash, severity, text):
        if self.torrents.has_key(infohash):
            name = self.torrents[infohash].metainfo.name
            err_str = '"%s" : %s'%(name,text)
            self._error(severity, err_str)
        else:
            ihex = infohash.encode('hex')
            err_str = '"%s" : %s'%(ihex,text)
            self._error(severity, err_str)
            self._error(WARNING, 'Previous error raised for invalid infohash: "%s"' % ihex)

    def global_error(self, severity, text):
        err_str = _("(global message) : %s")%text
        self._error(severity, err_str)

    def error_modal(self, text):
        if self.child_windows.has_key('log'):
            return

        title = _("%s Error") % app_name
        
        if self.errordialog is not None:
            if not self.errordialog.multi:
                self.errordialog.destroy()
                self.errordialog = MessageDialog(self.mainwindow, title, 
                                                 _("Multiple errors have occurred. "
                                                   "Click OK to view the error log."),
                                                 buttons=gtk.BUTTONS_OK_CANCEL,
                                                 yesfunc=self.multiple_errors_yes,
                                                 nofunc=self.errors_closed,
                                                 default=gtk.RESPONSE_OK
                                                 )
                self.errordialog.multi = True
            else:
                # already showing the multi error dialog, so do nothing
                pass
        else:
            self.errordialog = MessageDialog(self.mainwindow, title, text,
                                             yesfunc=self.errors_closed,
                                             default=gtk.RESPONSE_OK)
            self.errordialog.multi = False


    def multiple_errors_yes(self):
        self.errors_closed()
        self.open_window('log')

    def errors_closed(self):
        self.errordialog = None

    def stop_queue(self):
        self.set_config('pause', True)
        self.set_title()
        self.status_light.change_state('stopped')
        self.set_seen_remote_connections(False)
        q = list(self.runbox.get_queue())
        for infohash in q:
            t = self.torrents[infohash]
            self.remove_torrent_widget(infohash)
            self.create_torrent_widget(infohash)

    def restart_queue(self):
        self.set_config('pause', False)
        q = list(self.runbox.get_queue())
        for infohash in q:
            t = self.torrents[infohash]
            self.remove_torrent_widget(infohash)
            self.create_torrent_widget(infohash)
        self.start_status_light()

    def start_status_light(self):
        if len(self.running_torrents):
            self.status_light.change_state('natted')
        else:
            self.status_light.change_state('empty')

    def update_status(self, torrent, statistics):
        if self.config['pause']:
            self.status_light.change_state('stopped')
            return

        if self.seen_remote_connections:
            self.status_light.change_state('running')
        else:
            self.start_status_light()
        
        self.running_torrents[torrent].widget.update_status(statistics)
        if (not self.seen_remote_connections and
            statistics.get('ever_got_incoming')):
            self.set_seen_remote_connections(seen=True)
        if self.updater is not None:
            updater_infohash = self.updater.infohash
            if self.torrents.has_key(updater_infohash):
                updater_torrent = self.torrents[updater_infohash]
                if updater_torrent.state == QUEUED:
                    self.change_torrent_state(updater_infohash, RUNNING,
                                              index=0, replaced=0,
                                              force_running=True)

    def set_seen_remote_connections(self, seen=False):
        if seen:
            self.status_light.change_state('running')
        self.seen_remote_connections = seen

    def new_displayed_torrent(self, infohash, metainfo, dlpath, state, config,
                              completion=None, uptotal=0, downtotal=0):
        t = Struct()
        t.metainfo = metainfo
        t.dlpath = dlpath
        t.state = state
        t.config = config
        t.completion = completion
        t.uptotal = uptotal
        t.downtotal = downtotal
        self.torrents[infohash] = t
        self.create_torrent_widget(infohash)

    def torrent_state_changed(self, infohash, dlpath, state, completion,
                              uptotal, downtotal, queuepos=None):
        t = self.torrents[infohash]
        self.remove_torrent_widget(infohash)
        t.dlpath = dlpath
        t.state = state
        t.completion = completion
        t.uptotal = uptotal
        t.downtotal = downtotal
        self.create_torrent_widget(infohash, queuepos)

    def reorder_torrent(self, infohash, queuepos):
        self.remove_torrent_widget(infohash)
        self.create_torrent_widget(infohash, queuepos)

    def update_completion(self, infohash, completion, files_left=None,
                          files_allocated=None):
        t = self.torrents[infohash]
        if files_left is not None and t.widget.filelistwindow is not None:
            t.widget.filelistwindow.update(files_left, files_allocated)

    def removed_torrent(self, infohash):
        self.remove_torrent_widget(infohash)
        del self.torrents[infohash]

    def change_torrent_state(self, infohash, newstate, index=None,
                             replaced=None, force_running=False):
        t = self.torrents[infohash]
        pred = succ = None
        if index is not None:
            l = self.lists.setdefault(newstate, [])
            if index > 0:
                pred = l[index - 1]
            if index < len(l):
                succ = l[index]
        self.torrentqueue.change_torrent_state(infohash, t.state, newstate,
                                         pred, succ, replaced, force_running)

    def finish(self, infohash):
        t = self.torrents[infohash]
        if t is None or t.state == KNOWN:
            return
        self.change_torrent_state(infohash, KNOWN)

    def confirm_replace_running_torrent(self, infohash, replaced, index):
        replace_func = lambda *args: self.change_torrent_state(infohash,
                                RUNNING, index, replaced)
        add_func     = lambda *args: self.change_torrent_state(infohash,
                                RUNNING, index, force_running=True)
        moved_torrent = self.torrents[infohash]

        if moved_torrent.state == RUNNING:
            self.change_torrent_state(infohash, RUNNING, index)
            return

        if self.config['start_torrent_behavior'] == 'replace':
            replace_func()
            return
        elif self.config['start_torrent_behavior'] == 'add':
            add_func()
            return
        
        moved_torrent_name = moved_torrent.metainfo.name
        confirm = MessageDialog(self.mainwindow,
                                _("Stop running torrent?"),
                                _('You are about to start "%s". Do you want to stop the last running torrent as well?')%(moved_torrent_name),
                                type=gtk.MESSAGE_QUESTION,
                                buttons=gtk.BUTTONS_YES_NO,
                                yesfunc=replace_func,
                                nofunc=add_func,
                                default=gtk.RESPONSE_YES)

    def nag(self):
        if ((self.config['donated'] != version) and
            #(random.random() * NAG_FREQUENCY) < 1) and
            False):
            title = _("Have you donated?")
            message = _("Welcome to the new version of %s. Have you donated?")%app_name
            self.nagwindow = MessageDialog(self.mainwindow,
                                           title,
                                           message,
                                           type=gtk.MESSAGE_QUESTION,
                                           buttons=gtk.BUTTONS_YES_NO,
                                           yesfunc=self.nag_yes, nofunc=self.nag_no,
                                           default=gtk.RESPONSE_NO)
            
    def nag_no(self):
        self.donate()

    def nag_yes(self):
        self.set_config('donated', version)
        MessageDialog(self.mainwindow,
                      _("Thanks!"),
                      _("Thanks for donating! To donate again, "
                        'select "Donate" from the "Help" menu.'),
                      type=gtk.MESSAGE_INFO,
                      default=gtk.RESPONSE_OK
                      )

    def donate(self):
        self.visit_url(DONATE_URL)


    def visit_url(self, url, callback=None):
        t = threading.Thread(target=self._visit_url,
                             args=(url,callback))
        t.start()

    def _visit_url(self, url, callback=None):
        webbrowser.open(url)
        if callback:
            gtk_wrap(callback)


    def raiseerror(self, *args):
        raise ValueError('test traceback behavior')

#this class provides a thin layer around the loop so that the main window
#doesn't have to run it. It protects againstexceptions in mainwindow creation
#preventing the loop from starting (and causing "The grey screen of BT")
class MainLoop:
    def __init__(self):
        self.mainwindow = None
        self.started = 0
        
        gtk.threads_init()

    def set_mainwindow(self, mainwindow):
        self.mainwindow = mainwindow

    def run(self):
        gtk.threads_enter()

        if self.mainwindow:
            self.mainwindow.ssbutton.set_paused(self.mainwindow.config['pause'])
            self.mainwindow.rate_slider_box.start()
            self.mainwindow.init_updates()

        try:
            #the main loop has been started
            self.started = 1
            gtk.main() 
        except KeyboardInterrupt:
            gtk.threads_leave()
            if self.mainwindow:
                self.mainwindow.torrentqueue.set_done()
            raise
        
        gtk.threads_leave()

    def quit(self):
        if self.mainwindow: 
            self.mainwindow.quit()
        

def btgui_exit_gtk(mainloop):
    # if the main loop has never run, we have to run it to flush blocking threads
    # if it has run, running it a second time will cause duplicate-destruction problems
    if not mainloop.started:
        # queue up a command to close the gui
        gobject.idle_add(lock_wrap, mainloop.quit)
        # run the main loop so we process all queued commands, then quit
        # ignore errors, since we've already logged what caused us to leave early
        try: 
            mainloop.run()
        except:
            pass

def btgui_exit(controlsocket):
    if sys.platform.startswith('win'):
        controlsocket.close_sic_socket()

if __name__ == '__main__':

    try:
        config, args = configfile.parse_configuration_and_args(defaults,
                                        'bittorrent', sys.argv[1:], 0, None)
    except BTFailure, e:
        print str(e)
        sys.exit(1)

    config = Preferences().initWithDict(config)
    advanced_ui = config['advanced']

    newtorrents = args
    for opt in ('responsefile', 'url'):
        if config[opt]:
            print '"--%s"' % opt, _("deprecated, do not use")
            newtorrents.append(config[opt])
    
    controlsocket = ControlSocket(config)

    got_control_socket = True
    try:
        controlsocket.create_socket()
    except BTFailure:
        got_control_socket = False

        try:
            # windows needs to discover which socket to use, since it could be different
            # for different users. errors should be treated just like no-op failures
            if os.name == 'nt':
                controlsocket.discover_sic_socket()
                
            controlsocket.send_command('no-op')
        except BTFailure:
            sys.stderr.write(_("Failed to create or send command "
                               "through existing control socket.") + 
                             _(" Closing all %s windows may fix the problem.")
                             % app_name)
            sys.exit(1)

    # make sure we clean up the controlsocket when we close
    atexit.register(btgui_exit, controlsocket)

    datas = []
    errors = []
    if newtorrents:
        for arg in newtorrents:
            newdata, newerrors = GetTorrent.get_quietly(arg)
            if newdata:
                datas.append(newdata)
            errors.extend(newerrors)
        # Not sure if anything really useful could be done if
        # these send_command calls fail
        if not got_control_socket:
            for data in datas:
                controlsocket.send_command('start_torrent', data, config['save_as'])
            for error in errors:
                controlsocket.send_command('show_error', error)
            sys.exit(0)
    elif not got_control_socket:
        try:
            controlsocket.send_command('show_error', _("%s already running")%app_name)
        except BTFailure:
            sys.stderr.write(_("Failed to send command through "
                               "existing control socket.") +
                             _(" Closing all %s windows may fix the problem.")
                             % app_name)
        sys.exit(1)

    mainloop = MainLoop()

    # make sure we start the gtk loop once before we close
    atexit.register(btgui_exit_gtk, mainloop)

    torrentqueue = TorrentQueue.TorrentQueue(config, ui_options, controlsocket)
    d = DownloadInfoFrame(config,TorrentQueue.ThreadWrappedQueue(torrentqueue))

    mainloop.set_mainwindow(d)
    
    startflag = threading.Event()
    dlthread = threading.Thread(target = torrentqueue.run,
                                args = (d, gtk_wrap, startflag))
    dlthread.setDaemon(False)
    dlthread.start()
    startflag.wait()
    # the wait may have been terminated because of an error
    if torrentqueue.initialized == -1:
        raise BTFailure(_("Could not start the TorrentQueue, see above for errors."))
    
    torrentqueue.rawserver.install_sigint_handler()
    for data in datas:
        d.torrentqueue.start_new_torrent(data)
    for error in errors:
        d.global_error(ERROR, error)

    try:
        mainloop.run()
    except KeyboardInterrupt:
        # the gtk main loop is closed in MainLoop
        sys.exit(1)
