home *** CD-ROM | disk | FTP | other *** search
/ Chip 2006 June / CHIP 2006-06.2.iso / program / freeware / Democracy-0.8.2.exe / xulrunner / python / BitTorrent / GUI.py < prev    next >
Encoding:
Python Source  |  2006-04-10  |  16.3 KB  |  512 lines

  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.0 (the License).  You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License.  You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10.  
  11. # written by Matt Chisholm
  12.  
  13. from __future__ import division
  14.  
  15. import gtk
  16. import pango
  17. import gobject
  18. import os.path
  19. import threading
  20.  
  21. from __init__ import image_root, app_name, FAQ_URL
  22.  
  23. SPACING = 8
  24. WINDOW_TITLE_LENGTH = 128 # do we need this?
  25. WINDOW_WIDTH = 600
  26. MAX_WINDOW_HEIGHT = 600 # BUG: can we get this from the user's screen size?
  27. MAX_WINDOW_WIDTH  = 600 # BUG: can we get this from the user's screen size?
  28. MIN_MULTI_PANE_HEIGHT = 160
  29.  
  30. BT_TARGET_TYPE = 0
  31. EXTERNAL_TARGET_TYPE = 1
  32.  
  33. BT_TARGET       = ("application/x-bittorrent", gtk.TARGET_SAME_APP, BT_TARGET_TYPE      )
  34. EXTERNAL_TARGET = ("text/uri-list"           , 0                  , EXTERNAL_TARGET_TYPE)
  35.  
  36. # a slightly hackish but very reliable way to get OS scrollbar width
  37. sw = gtk.ScrolledWindow()
  38. SCROLLBAR_WIDTH = sw.size_request()[0] - 48
  39. del sw
  40.  
  41. def align(obj,x,y):
  42.     a = gtk.Alignment(x,y,0,0)
  43.     a.add(obj)
  44.     return a
  45.     
  46. def halign(obj, amt):
  47.     return align(obj,amt,0.5)
  48.  
  49. def lalign(obj):
  50.     return halign(obj,0)
  51.  
  52. def ralign(obj):
  53.     return halign(obj,1)
  54.  
  55. def valign(obj, amt):
  56.     return align(obj,0.5,amt)
  57.  
  58. factory = gtk.IconFactory()
  59.  
  60. # these don't seem to be documented anywhere:
  61. # ICON_SIZE_BUTTON        = 20x20
  62. # ICON_SIZE_LARGE_TOOLBAR = 24x24
  63.  
  64. for n in 'broken finished info pause paused play queued running remove'.split():
  65.     fn = os.path.join(image_root, ("%s.png"%n))
  66.  
  67.     pixbuf = gtk.gdk.pixbuf_new_from_file(fn)
  68.     
  69.     set = gtk.IconSet(pixbuf)
  70.  
  71.     factory.add('bt-%s'%n, set)
  72.  
  73. factory.add_default()
  74.  
  75. def get_logo(size=32):
  76.     fn = os.path.join(image_root, 'logo', 'bittorrent_%d.png'%size)
  77.     logo = gtk.Image()
  78.     logo.set_from_file(fn)
  79.     return logo
  80.  
  81. class Size(long):
  82.     """displays size in human-readable format"""
  83.     size_labels = ['','K','M','G','T','P','E','Z','Y']    
  84.     radix = 2**10
  85.  
  86.     def __new__(cls, value, precision=None):
  87.         self = long.__new__(cls, value)
  88.         return self
  89.  
  90.     def __init__(self, value, precision=0):
  91.         long.__init__(self, value)
  92.         self.precision = precision
  93.  
  94.     def __str__(self, precision=None):
  95.         if precision is None:
  96.             precision = self.precision
  97.         value = self
  98.         for unitname in self.size_labels:
  99.             if value < self.radix and precision < self.radix:
  100.                 break
  101.             value /= self.radix
  102.             precision /= self.radix
  103.         if unitname and value < 10 and precision < 1:
  104.             return '%.1f %sB' % (value, unitname)
  105.         else:
  106.             return '%.0f %sB' % (value, unitname)
  107.  
  108.  
  109. class Rate(Size):
  110.     """displays rate in human-readable format"""
  111.     def __init__(self, value, precision=2**10):
  112.         Size.__init__(self, value, precision)
  113.  
  114.     def __str__(self, precision=None):
  115.         return '%s/s'% Size.__str__(self, precision=None)
  116.  
  117.  
  118. class Duration(float):
  119.     """displays duration in human-readable format"""
  120.     def __str__(value):
  121.         if value > 365 * 24 * 60 * 60:
  122.             return '?'
  123.         elif value >= 172800:
  124.             return '%d days' % (value//86400) # 2 days or longer
  125.         elif value >= 86400:
  126.             return '1 day %d hours' % ((value-86400)//3600) # 1-2 days
  127.         elif value >= 3600:
  128.             return '%d:%02d hours' % (value//3600, (value%3600)//60) # 1 h - 1 day
  129.         elif value >= 60:
  130.             return '%d:%02d minutes' % (value//60, value%60) # 1 minute to 1 hour
  131.         elif value >= 0:
  132.             return '%d seconds' % int(value)
  133.         else:
  134.             return '0 seconds'
  135.  
  136. class IconButton(gtk.Button):
  137.     def __init__(self, label, iconpath=None, stock=None):
  138.         gtk.Button.__init__(self)
  139.  
  140.         self.hbox = gtk.HBox(spacing=5)
  141.         
  142.         self.icon = gtk.Image()
  143.         if stock is not None:
  144.             self.icon.set_from_stock(stock, gtk.ICON_SIZE_BUTTON)
  145.         elif iconpath is not None:
  146.             self.icon.set_from_file(iconpath)
  147.         else:
  148.             raise TypeError, "IconButton needs iconpath or stock"
  149.         self.hbox.pack_start(self.icon)
  150.  
  151.         self.label = gtk.Label(label)
  152.         self.hbox.pack_start(self.label)
  153.  
  154.         self.add(halign(self.hbox, 0.5))
  155.  
  156.  
  157. class Window(gtk.Window):
  158.     def __init__(self, *args):
  159.         apply(gtk.Window.__init__, (self,)+args)
  160.         self.set_icon_from_file(os.path.join(image_root,'bittorrent.ico'))
  161.  
  162.  
  163. class HelpWindow(Window):
  164.     def __init__(self, main, helptext):
  165.         Window.__init__(self)
  166.         self.set_title('%s Help'%app_name)
  167.         self.main = main
  168.         self.set_border_width(SPACING)
  169.  
  170.         self.vbox = gtk.VBox(spacing=SPACING)
  171.         
  172.         self.faq_box = gtk.HBox(spacing=SPACING)
  173.         self.faq_box.pack_start(gtk.Label("Frequently Asked Questions:"), expand=False, fill=False)
  174.         self.faq_url = gtk.Entry()
  175.         self.faq_url.set_text(FAQ_URL)
  176.         self.faq_url.set_editable(False)
  177.         self.faq_box.pack_start(self.faq_url, expand=True, fill=True)
  178.         self.faq_button = gtk.Button('Go')
  179.         self.faq_button.connect('clicked', lambda w: self.main.visit_url(FAQ_URL) )
  180.         self.faq_box.pack_start(self.faq_button, expand=False, fill=False)
  181.         self.vbox.pack_start(self.faq_box, expand=False, fill=False)
  182.  
  183.         self.cmdline_args = gtk.Label(helptext)
  184.  
  185.         self.cmdline_sw = ScrolledWindow()
  186.         self.cmdline_sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
  187.         self.cmdline_sw.add_with_viewport(self.cmdline_args)
  188.  
  189.         self.cmdline_sw.set_size_request(self.cmdline_args.size_request()[0]+SCROLLBAR_WIDTH, 200)
  190.  
  191.         self.vbox.pack_start(self.cmdline_sw)
  192.         
  193.         self.add(self.vbox)
  194.  
  195.         self.show_all()
  196.         
  197.         if self.main is not None:
  198.             self.connect('destroy', lambda w: self.main.window_closed('help'))
  199.         else:
  200.             self.connect('destroy', lambda w: gtk.main_quit())
  201.             gtk.main()
  202.                         
  203.  
  204.  
  205.     def close(self, widget=None):
  206.         self.destroy()    
  207.  
  208.  
  209. class ScrolledWindow(gtk.ScrolledWindow):
  210.     def scroll_to_bottom(self):
  211.         child_height = self.child.child.size_request()[1]
  212.         self.scroll_to(0, child_height)
  213.         
  214.     def scroll_by(self, dx=0, dy=0):
  215.         v = self.get_vadjustment()
  216.         new_y = min(v.upper, v.value + dy)
  217.         self.scroll_to(0, new_y)
  218.  
  219.     def scroll_to(self, x=0, y=0):
  220.         v = self.get_vadjustment()
  221.         child_height = self.child.child.size_request()[1]
  222.         new_adj = gtk.Adjustment(y, 0, child_height)
  223.         self.set_vadjustment(new_adj)
  224.  
  225.  
  226. class AutoScrollingWindow(ScrolledWindow):
  227.     def __init__(self):
  228.         ScrolledWindow.__init__(self)
  229.         self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
  230.                            gtk.DEST_DEFAULT_DROP,
  231.                            [( "application/x-bittorrent",  gtk.TARGET_SAME_APP, BT_TARGET_TYPE )],
  232.                            gtk.gdk.ACTION_MOVE)
  233.         self.connect('drag_motion'       , self.drag_motion       )
  234. #        self.connect('drag_data_received', self.drag_data_received)
  235.         self.vscrolltimeout = None
  236.  
  237. #    def drag_data_received(self, widget, context, x, y, selection, targetType, time):
  238. #        print 'AutoScrollingWindow.drag_data_received(', widget
  239.  
  240.     def drag_motion(self, widget, context, x, y, time):
  241.         v = self.get_vadjustment()
  242.         if v.page_size - y <= 10:
  243.             amount = (10 - int(v.page_size - y)) * 2
  244.             self.start_scrolling(amount)
  245.         elif y <= 10:
  246.             amount = (y - 10) * 2
  247.             self.start_scrolling(amount)
  248.         else:
  249.             self.stop_scrolling()
  250.  
  251.     def scroll_and_wait(self, amount, lock_held):
  252.         if not lock_held:
  253.             gtk.threads_enter()
  254.         self.scroll_by(0, amount)
  255.         if not lock_held:
  256.             gtk.threads_leave()
  257.         if self.vscrolltimeout is not None:
  258.             gobject.source_remove(self.vscrolltimeout)
  259.         self.vscrolltimeout = gobject.timeout_add(100, self.scroll_and_wait, amount, False)
  260.         #print "adding timeout", self.vscrolltimeout, amount
  261.  
  262.     def start_scrolling(self, amount):
  263.         if self.vscrolltimeout is not None:
  264.             gobject.source_remove(self.vscrolltimeout)            
  265.         self.scroll_and_wait(amount, True)
  266.         
  267.     def stop_scrolling(self):
  268.         if self.vscrolltimeout is not None:
  269.             #print "removing timeout", self.vscrolltimeout
  270.             gobject.source_remove(self.vscrolltimeout)
  271.             self.vscrolltimeout = None
  272.  
  273. class MessageDialog(gtk.MessageDialog):
  274.     flags = gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT
  275.     
  276.     def __init__(self, parent, title, message,
  277.                  type=gtk.MESSAGE_ERROR,
  278.                  buttons=gtk.BUTTONS_OK,
  279.                  yesfunc=None, nofunc=None,
  280.                  default=None
  281.                  ):
  282.         gtk.MessageDialog.__init__(self, parent,
  283.                                    self.flags,
  284.                                    type, buttons, message)
  285.  
  286.         self.set_size_request(-1, -1)
  287.         self.set_resizable(gtk.FALSE)
  288.         self.set_title(title)
  289.         if default is not None:
  290.             self.set_default_response(default)
  291.         
  292.         self.label.set_line_wrap(gtk.TRUE)
  293.  
  294.         self.connect('response', self.callback)
  295.  
  296.         self.yesfunc = yesfunc
  297.         self.nofunc = nofunc
  298.         self.show_all()
  299.  
  300.     def callback(self, widget, response_id, *args):
  301.         if ((response_id == gtk.RESPONSE_OK or
  302.              response_id == gtk.RESPONSE_YES) and
  303.             self.yesfunc is not None):
  304.             self.yesfunc()
  305.         if ((response_id == gtk.RESPONSE_CANCEL or
  306.              response_id == gtk.RESPONSE_NO )
  307.             and self.nofunc is not None):
  308.             self.nofunc()
  309.         self.destroy()
  310.  
  311. class ErrorMessageDialog(MessageDialog):
  312.     flags = gtk.DIALOG_DESTROY_WITH_PARENT
  313.  
  314.  
  315. class FileSelection(gtk.FileSelection):
  316.  
  317.     def __init__(self, main, title='', fullname='', got_location_func=None, no_location_func=None, got_multiple_location_func=None, show=True):
  318.         gtk.FileSelection.__init__(self)
  319.         self.main = main
  320.         self.set_modal(gtk.TRUE)
  321.         self.set_destroy_with_parent(gtk.TRUE)
  322.         self.set_title(title)
  323.         if (got_location_func is None and
  324.             got_multiple_location_func is not None):
  325.             self.set_select_multiple(True)
  326.         self.got_location_func = got_location_func
  327.         self.no_location_func = no_location_func
  328.         self.got_multiple_location_func = got_multiple_location_func
  329.         self.cancel_button.connect("clicked", self.destroy)
  330.         self.d_handle = self.connect('destroy', self.no_location)
  331.         self.ok_button.connect("clicked", self.done)
  332.         self.set_filename(fullname)
  333.         if show:
  334.             self.show()
  335.  
  336.     def no_location(self, widget=None):
  337.         if self.no_location_func is not None:
  338.             self.no_location_func()
  339.  
  340.     def done(self, widget=None):
  341.         if self.get_select_multiple():
  342.             self.got_multiple_location()
  343.         else:
  344.             self.got_location()
  345.         self.disconnect(self.d_handle)
  346.         self.destroy()
  347.  
  348.     def got_location(self):
  349.         if self.got_location_func is not None:
  350.             name = self.get_filename()
  351.             self.got_location_func(name)
  352.  
  353.     def got_multiple_location(self):
  354.         if self.got_multiple_location_func is not None:
  355.             names = self.get_selections()
  356.             self.got_multiple_location_func(names)
  357.             
  358.     def destroy(self, widget=None):
  359.         gtk.FileSelection.destroy(self)
  360.  
  361.     def close_child_windows(self):
  362.         self.no_location()
  363.  
  364.     def close(self, widget=None):
  365.         self.destroy()
  366.  
  367. class OpenFileSelection(FileSelection):
  368.     pass
  369.  
  370. class SaveFileSelection(FileSelection):
  371.     pass
  372.  
  373. class ChooseFolderSelection(FileSelection):
  374.     pass
  375.  
  376. if os.name == 'nt':
  377.     from venster import comdlg
  378.     class BaseFileSelection:
  379.         _klass = None
  380.         _flags = 0
  381.         _filter = ''
  382.         def __init__(self, main, title='', fullname='',
  383.                      got_location_func=None,
  384.                      no_location_func=None,
  385.                      got_multiple_location_func=None,
  386.                      show=True):
  387.             self.main = main
  388.             self.bfs = self._klass()
  389.             self.bfs.Flags = self._flags
  390.             self.bfs.filter = self._filter
  391.  
  392.             self.got_location_func = got_location_func
  393.             self.no_location_func  = no_location_func
  394.             self.got_multiple_location_func = got_multiple_location_func
  395.             
  396.  
  397.             if (got_location_func is None and
  398.                 got_multiple_location_func is not None):
  399.                 self.bfs.Flags |= comdlg.OFN_ALLOWMULTISELECT
  400.  
  401.             path, filename = os.path.split(fullname)
  402.             self.bfs.lpstrInitialDir = path
  403.             self.bfs.lpstrFile = filename
  404.             self.bfs.lpstrTitle = title
  405.  
  406.             self.thread = threading.Thread(target=self.run, \
  407.                                            name="Bittorrent GUI")
  408.             if show:
  409.                 self.show()
  410.             
  411.         def show(self):
  412.             self.thread.start()
  413.  
  414.         def run(self):
  415.             result = self.bfs.DoModal(parent=self.main.mainwindow.window.handle)
  416.             self.done(result)
  417.  
  418.         def done(self, result):
  419.             if result is not None:
  420.                 if self.got_location_func is not None:
  421.                     self.got_location_func(result)
  422.                 elif self.got_multiple_location_func is not None:
  423.                     print result, type(result) # this is broken
  424.                     self.got_multiple_location_func((result,))
  425.             else:
  426.                 if self.no_location_func is not None:
  427.                     self.no_location_func()
  428.             
  429.  
  430.         def close_child_windows(self):
  431.             self.destroy()
  432.  
  433.         def destroy(self):
  434.             pass
  435.  
  436.         def close(self):
  437.             self.destroy()
  438.         
  439.     class OpenFileSelection(BaseFileSelection):
  440.         _klass = comdlg.OpenFileDialog
  441.         _flags = comdlg.OFN_FILEMUSTEXIST|comdlg.OFN_PATHMUSTEXIST
  442.         _filter = "Torrent files|*.torrent|All files (*.*)|*.*"
  443.  
  444.     class SaveFileSelection(BaseFileSelection):
  445.         _klass = comdlg.SaveFileDialog
  446.         _filter = "All files (*.*)|*.*"
  447.  
  448.  
  449. class PaddedHSeparator(gtk.VBox):
  450.     def __init__(self, spacing=SPACING):
  451.         gtk.VBox.__init__(self)
  452.         self.sep = gtk.HSeparator()
  453.         self.pack_start(self.sep, expand=False, fill=False, padding=spacing)
  454.         self.show_all()
  455.         
  456.  
  457. class HSeparatedBox(gtk.VBox):
  458.  
  459.     def new_separator(self):
  460.         return PaddedHSeparator()
  461.  
  462.     def _get_children(self):
  463.         return gtk.VBox.get_children(self)
  464.  
  465.     def get_children(self):
  466.         return self._get_children()[0::2]
  467.  
  468.     def _reorder_child(self, child, index):
  469.         gtk.VBox.reorder_child(self, child, index)
  470.  
  471.     def reorder_child(self, child, index):
  472.         children = self._get_children()
  473.         oldindex = children.index(child)
  474.         sep = None
  475.         if oldindex == len(children) - 1:
  476.             sep = children[oldindex-1]
  477.         else:
  478.             sep = children[oldindex+1]
  479.  
  480.         newindex = index*2
  481.         if newindex == len(children) -1:
  482.             self._reorder_child(sep, newindex-1)
  483.             self._reorder_child(child, newindex)
  484.         else:
  485.             self._reorder_child(child, newindex)
  486.             self._reorder_child(sep, newindex+1)
  487.  
  488.     def pack_start(self, widget, *args, **kwargs):
  489.         if len(self._get_children()):
  490.             s = self.new_separator()
  491.             gtk.VBox.pack_start(self, s, *args, **kwargs)
  492.             s.show()
  493.         gtk.VBox.pack_start(self, widget, *args, **kwargs)
  494.  
  495.     def pack_end(self, widget, *args, **kwargs):
  496.         if len(self._get_children()):
  497.             s = self.new_separator()
  498.             gtk.VBox.pack_start(self, s, *args, **kwargs)
  499.             s.show()
  500.         gtk.VBox.pack_end(self, widget, *args, **kwargs)
  501.  
  502.     def remove(self, widget):
  503.         children = self._get_children()
  504.         if len(children) > 1:
  505.             index = children.index(widget)
  506.             if index == 0:
  507.                 sep = children[index+1]
  508.             else:
  509.                 sep = children[index-1]
  510.             sep.destroy()
  511.         gtk.VBox.remove(self, widget)
  512.