Advertisement
Neelo

Trackma-GTK (AppIndicator version)

Aug 2nd, 2015
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 81.05 KB | None | 0 0
  1. # This file is part of Trackma.
  2. #
  3. # This program is free software: you can redistribute it and/or modify
  4. # it under the terms of the GNU General Public License as published by
  5. # the Free Software Foundation, either version 3 of the License, or
  6. # (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15. #
  16.  
  17. import sys
  18. try:
  19.     import gobject
  20.     import pygtk
  21.     pygtk.require('2.0')
  22.     import gtk
  23.     import pango
  24. except ImportError:
  25.     print ("Couldn't import GTK dependencies. Make sure you "
  26.            "installed the PyGTK package.")
  27.     sys.exit(-1)
  28.  
  29. gtk.gdk.threads_init() # We'll use threads
  30.  
  31. import os
  32. import cgi
  33. import time
  34. import threading
  35. import webbrowser
  36. import urllib2
  37. from cStringIO import StringIO
  38. import appindicator
  39.  
  40. try:
  41.     import Image
  42.     imaging_available = True
  43. except ImportError:
  44.     try:
  45.         from PIL import Image
  46.         imaging_available = True
  47.     except ImportError:
  48.         print "Warning: PIL or Pillow isn't available. Preview images will be disabled."
  49.         imaging_available = False
  50.  
  51. import trackma.messenger as messenger
  52. import trackma.utils as utils
  53.  
  54. from trackma.engine import Engine
  55. from trackma.accounts import AccountManager
  56.  
  57. class Trackma_gtk(object):
  58.     engine = None
  59.     config = None
  60.     show_lists = dict()
  61.     image_thread = None
  62.     close_thread = None
  63.     hidden = False
  64.     quit = False
  65.  
  66.     def main(self):
  67.         """Start the Account Selector"""
  68.         self.configfile = utils.get_root_filename('ui-gtk.json')
  69.         self.config = utils.parse_config(self.configfile, utils.gtk_defaults)
  70.  
  71.         manager = AccountManager()
  72.  
  73.         # Use the remembered account if there's one
  74.         if manager.get_default():
  75.             self.start(manager.get_default())
  76.         else:
  77.             self.accountsel = AccountSelect(manager)
  78.             self.accountsel.use_button.connect("clicked", self.use_account)
  79.             self.accountsel.create()
  80.  
  81.         gtk.main()
  82.  
  83.     def do_switch_account(self, widget, switch=True):
  84.         manager = AccountManager()
  85.         self.accountsel = AccountSelect(manager = AccountManager(), switch=switch)
  86.         self.accountsel.use_button.connect("clicked", self.use_account)
  87.         self.accountsel.create()
  88.  
  89.     def use_account(self, widget):
  90.         """Start the main application with the following account"""
  91.         accountid = self.accountsel.get_selected_id()
  92.         account = self.accountsel.manager.get_account(accountid)
  93.         # If remember box is checked, set as default account
  94.         if self.accountsel.is_remember():
  95.             self.accountsel.manager.set_default(accountid)
  96.         else:
  97.             self.accountsel.manager.set_default(None)
  98.  
  99.         self.accountsel.destroy()
  100.  
  101.         # Reload the engine if already started,
  102.         # start it otherwise
  103.         if self.engine and self.engine.loaded:
  104.             self.do_reload(None, account, None)
  105.         else:
  106.             self.start(account)
  107.  
  108.     def start(self, account):
  109.         """Create the main window"""
  110.         # Create engine
  111.         self.account = account
  112.         self.engine = Engine(account)
  113.  
  114.         self.main = gtk.Window(gtk.WINDOW_TOPLEVEL)
  115.         self.main.set_position(gtk.WIN_POS_CENTER)
  116.         self.main.connect('delete_event', self.delete_event)
  117.         self.main.connect('destroy', self.on_destroy)
  118.         self.main.set_title('Trackma-gtk ' + utils.VERSION)
  119.         gtk.window_set_default_icon_from_file(utils.datadir + '/data/icon.png')
  120.  
  121.         # Menus
  122.         mb_show = gtk.Menu()
  123.         self.mb_play = gtk.ImageMenuItem(gtk.STOCK_MEDIA_PLAY)
  124.         self.mb_play.connect("activate", self.do_play, True)
  125.         mb_neweps = gtk.MenuItem('Scan new episodes')
  126.         mb_neweps.connect("activate", self.do_neweps)
  127.         self.mb_info = gtk.MenuItem('Show details...')
  128.         self.mb_info.connect("activate", self.do_info)
  129.         mb_web = gtk.MenuItem("Open web site")
  130.         mb_web.connect("activate", self.do_web)
  131.         mb_copy = gtk.MenuItem("Copy title to clipboard")
  132.         mb_copy.connect("activate", self.do_copytoclip)
  133.         mb_alt_title = gtk.MenuItem("Set alternate title...")
  134.         mb_alt_title.connect("activate", self.do_altname)
  135.         self.mb_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE)
  136.         self.mb_delete.connect("activate", self.do_delete)
  137.         self.mb_exit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
  138.         self.mb_exit.connect("activate", self.do_quit, None)
  139.         gtk.stock_add([(gtk.STOCK_ADD, "Add/Search Shows", 0, 0, "")])
  140.         self.mb_addsearch = gtk.ImageMenuItem(gtk.STOCK_ADD)
  141.         self.mb_addsearch.connect("activate", self.do_addsearch)
  142.  
  143.         mb_show.append(self.mb_addsearch)
  144.         mb_show.append(mb_neweps)
  145.         mb_show.append(gtk.SeparatorMenuItem())
  146.         mb_show.append(self.mb_play)
  147.         mb_show.append(self.mb_info)
  148.         mb_show.append(mb_web)
  149.         mb_show.append(gtk.SeparatorMenuItem())
  150.         mb_show.append(mb_copy)
  151.         mb_show.append(mb_alt_title)
  152.         mb_show.append(gtk.SeparatorMenuItem())
  153.         mb_show.append(self.mb_delete)
  154.         mb_show.append(gtk.SeparatorMenuItem())
  155.         mb_show.append(self.mb_exit)
  156.  
  157.         mb_list = gtk.Menu()
  158.         gtk.stock_add([(gtk.STOCK_REFRESH, "Sync", 0, 0, "")])
  159.         self.mb_sync = gtk.ImageMenuItem(gtk.STOCK_REFRESH)
  160.         self.mb_sync.connect("activate", self.do_sync)
  161.         self.mb_retrieve = gtk.ImageMenuItem("Retrieve list")
  162.         self.mb_retrieve.connect("activate", self.do_retrieve_ask)
  163.         self.mb_send = gtk.MenuItem('Send changes')
  164.         self.mb_send.connect("activate", self.do_send)
  165.  
  166.         mb_list.append(self.mb_sync)
  167.         mb_list.append(gtk.SeparatorMenuItem())
  168.         mb_list.append(self.mb_retrieve)
  169.         mb_list.append(self.mb_send)
  170.  
  171.         mb_options = gtk.Menu()
  172.         self.mb_switch_account = gtk.MenuItem('Switch Account...')
  173.         self.mb_switch_account.connect("activate", self.do_switch_account)
  174.         self.mb_settings = gtk.MenuItem('Global Settings...')
  175.         self.mb_settings.connect("activate", self.do_settings)
  176.  
  177.         mb_options.append(self.mb_switch_account)
  178.         mb_options.append(gtk.SeparatorMenuItem())
  179.         mb_options.append(self.mb_settings)
  180.  
  181.         self.mb_mediatype_menu = gtk.Menu()
  182.  
  183.         mb_help = gtk.Menu()
  184.         mb_about = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
  185.         mb_about.connect("activate", self.on_about)
  186.         mb_help.append(mb_about)
  187.  
  188.         # Root menubar
  189.         root_menu1 = gtk.MenuItem("Show")
  190.         root_menu1.set_submenu(mb_show)
  191.         root_list = gtk.MenuItem("List")
  192.         root_list.set_submenu(mb_list)
  193.         root_options = gtk.MenuItem("Options")
  194.         root_options.set_submenu(mb_options)
  195.         mb_mediatype = gtk.MenuItem("Mediatype")
  196.         mb_mediatype.set_submenu(self.mb_mediatype_menu)
  197.         root_menu2 = gtk.MenuItem("Help")
  198.         root_menu2.set_submenu(mb_help)
  199.  
  200.         mb = gtk.MenuBar()
  201.         mb.append(root_menu1)
  202.         mb.append(root_list)
  203.         mb.append(mb_mediatype)
  204.         mb.append(root_options)
  205.         mb.append(root_menu2)
  206.  
  207.         # Create vertical box
  208.         vbox = gtk.VBox(False, 0)
  209.         self.main.add(vbox)
  210.  
  211.         vbox.pack_start(mb, False, False, 0)
  212.  
  213.         self.top_hbox = gtk.HBox(False, 10)
  214.         self.top_hbox.set_border_width(5)
  215.  
  216.         self.show_image = ImageView(100, 149)
  217.         self.top_hbox.pack_start(self.show_image, False, False, 0)
  218.  
  219.         # Right box
  220.         top_right_box = gtk.VBox(False, 0)
  221.  
  222.         # Line 1: Title
  223.         line1 = gtk.HBox(False, 5)
  224.         self.show_title = gtk.Label()
  225.         self.show_title.set_use_markup(True)
  226.         self.show_title.set_alignment(0, 0.5)
  227.         self.show_title.set_ellipsize(pango.ELLIPSIZE_END)
  228.  
  229.         line1.pack_start(self.show_title, True, True, 0)
  230.  
  231.         # API info
  232.         api_hbox = gtk.HBox(False, 5)
  233.         self.api_icon = gtk.Image()
  234.         self.api_user = gtk.Label()
  235.         api_hbox.pack_start(self.api_icon)
  236.         api_hbox.pack_start(self.api_user)
  237.  
  238.         alignment1 = gtk.Alignment(xalign=1, yalign=0)
  239.         alignment1.add(api_hbox)
  240.         line1.pack_start(alignment1, False, False, 0)
  241.  
  242.         top_right_box.pack_start(line1, True, True, 0)
  243.  
  244.         # Line 2: Episode
  245.         line2 = gtk.HBox(False, 5)
  246.         line2_t = gtk.Label('  Progress')
  247.         line2_t.set_size_request(70, -1)
  248.         line2_t.set_alignment(0, 0.5)
  249.         line2.pack_start(line2_t, False, False, 0)
  250.         self.show_ep_num = gtk.SpinButton()
  251.         self.show_ep_num.set_sensitive(False)
  252.         self.show_ep_num.connect("activate", self.do_update)
  253.         #self.show_ep_num.connect("value_changed", self.do_update)
  254.         line2.pack_start(self.show_ep_num, False, False, 0)
  255.  
  256.         # Buttons
  257.         top_buttons = gtk.HBox(False, 5)
  258.  
  259.         self.add_epp_button = gtk.Button('+')
  260.         self.add_epp_button.connect("clicked", self.do_add_epp)
  261.         self.add_epp_button.set_sensitive(False)
  262.         line2.pack_start(self.add_epp_button, False, False, 0)
  263.  
  264.         self.rem_epp_button = gtk.Button('-')
  265.         self.rem_epp_button.connect("clicked", self.do_rem_epp)
  266.         self.rem_epp_button.set_sensitive(False)
  267.         line2.pack_start(self.rem_epp_button, False, False, 0)
  268.  
  269.         self.update_button = gtk.Button('Update')
  270.         self.update_button.connect("clicked", self.do_update)
  271.         self.update_button.set_sensitive(False)
  272.         line2.pack_start(self.update_button, False, False, 0)
  273.  
  274.         self.play_button = gtk.Button('Play')
  275.         self.play_button.connect("clicked", self.do_play, False)
  276.         self.play_button.set_sensitive(False)
  277.         line2.pack_start(self.play_button, False, False, 0)
  278.  
  279.         self.play_next_button = gtk.Button('Play Next')
  280.         self.play_next_button.connect("clicked", self.do_play, True)
  281.         self.play_next_button.set_sensitive(False)
  282.         line2.pack_start(self.play_next_button, False, False, 0)
  283.  
  284.         top_right_box.pack_start(line2, True, False, 0)
  285.  
  286.         # Line 3: Score
  287.         line3 = gtk.HBox(False, 5)
  288.         line3_t = gtk.Label('  Score')
  289.         line3_t.set_size_request(70, -1)
  290.         line3_t.set_alignment(0, 0.5)
  291.         line3.pack_start(line3_t, False, False, 0)
  292.         self.show_score = gtk.SpinButton()
  293.         self.show_score.set_adjustment(gtk.Adjustment(upper=10, step_incr=1))
  294.         self.show_score.set_sensitive(False)
  295.         self.show_score.connect("activate", self.do_score)
  296.         line3.pack_start(self.show_score, False, False, 0)
  297.  
  298.         self.scoreset_button = gtk.Button('Set')
  299.         self.scoreset_button.connect("clicked", self.do_score)
  300.         self.scoreset_button.set_sensitive(False)
  301.         line3.pack_start(self.scoreset_button, False, False, 0)
  302.  
  303.         top_right_box.pack_start(line3, True, False, 0)
  304.  
  305.         # Line 4: Status
  306.         line4 = gtk.HBox(False, 5)
  307.         line4_t = gtk.Label('  Status')
  308.         line4_t.set_size_request(70, -1)
  309.         line4_t.set_alignment(0, 0.5)
  310.         line4.pack_start(line4_t, False, False, 0)
  311.  
  312.         self.statusmodel = gtk.ListStore(str, str)
  313.  
  314.         self.statusbox = gtk.ComboBox(self.statusmodel)
  315.         cell = gtk.CellRendererText()
  316.         self.statusbox.pack_start(cell, True)
  317.         self.statusbox.add_attribute(cell, 'text', 1)
  318.         self.statusbox_handler = self.statusbox.connect("changed", self.do_status)
  319.         self.statusbox.set_sensitive(False)
  320.  
  321.         alignment = gtk.Alignment(xalign=0, yalign=0.5)
  322.         alignment.add(self.statusbox)
  323.  
  324.         line4.pack_start(alignment, True, True, 0)
  325.  
  326.         top_right_box.pack_start(line4, True, False, 0)
  327.  
  328.         self.top_hbox.pack_start(top_right_box, True, True, 0)
  329.         vbox.pack_start(self.top_hbox, False, False, 0)
  330.  
  331.         # Notebook for lists
  332.         self.notebook = gtk.Notebook()
  333.         self.notebook.set_tab_pos(gtk.POS_TOP)
  334.         self.notebook.set_scrollable(True)
  335.         self.notebook.set_border_width(3)
  336.  
  337.         sw = gtk.ScrolledWindow()
  338.         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  339.         sw.set_size_request(550, 300)
  340.         sw.set_border_width(5)
  341.         self.notebook.append_page(sw, gtk.Label("Status"))
  342.  
  343.         vbox.pack_start(self.notebook, True, True, 0)
  344.  
  345.         self.statusbar = gtk.Statusbar()
  346.         self.statusbar.push(0, 'Trackma-gtk ' + utils.VERSION)
  347.         vbox.pack_start(self.statusbar, False, False, 0)
  348.  
  349.         vbox.show_all()
  350.  
  351.         # Accelerators
  352.         accelgrp = gtk.AccelGroup()
  353.  
  354.         key, mod = gtk.accelerator_parse("<Control>N")
  355.         self.mb_play.add_accelerator("activate", accelgrp, key, mod, gtk.ACCEL_VISIBLE)
  356.         key, mod = gtk.accelerator_parse("<Control>A")
  357.         self.mb_addsearch.add_accelerator("activate", accelgrp, key, mod, gtk.ACCEL_VISIBLE)
  358.         key, mod = gtk.accelerator_parse("<Control>S")
  359.         self.mb_sync.add_accelerator("activate", accelgrp, key, mod, gtk.ACCEL_VISIBLE)
  360.         key, mod = gtk.accelerator_parse("<Control>E")
  361.         self.mb_send.add_accelerator("activate", accelgrp, key, mod, gtk.ACCEL_VISIBLE)
  362.         key, mod = gtk.accelerator_parse("<Control>R")
  363.         self.mb_retrieve.add_accelerator("activate", accelgrp, key, mod, gtk.ACCEL_VISIBLE)
  364.         key, mod = gtk.accelerator_parse("<Control>Right")
  365.         self.add_epp_button.add_accelerator("activate", accelgrp, key, mod, gtk.ACCEL_VISIBLE)
  366.         self.add_epp_button.set_tooltip_text("Ctrl+Right")
  367.         key, mod = gtk.accelerator_parse("<Control>Left")
  368.         self.rem_epp_button.add_accelerator("activate", accelgrp, key, mod, gtk.ACCEL_VISIBLE)
  369.         self.rem_epp_button.set_tooltip_text("Ctrl+Left")
  370.  
  371.         self.main.add_accel_group(accelgrp)
  372.  
  373.         # Status icon
  374.         self.indicator = appindicator.Indicator('trackma-gtk', utils.datadir + '/data/icon.png', appindicator.CATEGORY_APPLICATION_STATUS)
  375.  
  376.         self.indicatormenu = gtk.Menu()
  377.         mb_version = gtk.MenuItem('Trackma-gtk ' + utils.VERSION)
  378.         mb_show = gtk.MenuItem("Show/Hide")
  379.         mb_about = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
  380.         mb_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT)
  381.  
  382.         mb_version.set_sensitive(False)
  383.         mb_show.connect("activate", self.status_event)
  384.         mb_about.connect("activate", self.on_about)
  385.         mb_quit.connect("activate", self.do_quit)
  386.  
  387.         self.indicatormenu.append(mb_version)
  388.         self.indicatormenu.append(gtk.SeparatorMenuItem())
  389.         self.indicatormenu.append(mb_show)
  390.         self.indicatormenu.append(mb_about)
  391.         self.indicatormenu.append(gtk.SeparatorMenuItem())
  392.         self.indicatormenu.append(mb_quit)
  393.         self.indicatormenu.show_all()
  394.  
  395.         self.indicator.set_menu(self.indicatormenu)
  396.  
  397.         if self.config['show_tray']:
  398.             self.indicator.set_status(appindicator.STATUS_ACTIVE)
  399.         else:
  400.             self.indicator.set_visible(appindicator.STATUS_PASSIVE)
  401.  
  402.         # Engine configuration
  403.         self.engine.set_message_handler(self.message_handler)
  404.         self.engine.connect_signal('episode_changed', self.changed_show)
  405.         self.engine.connect_signal('score_changed', self.changed_show)
  406.         self.engine.connect_signal('status_changed', self.changed_show_status)
  407.         self.engine.connect_signal('playing', self.playing_show)
  408.         self.engine.connect_signal('show_added', self.changed_show_status)
  409.         self.engine.connect_signal('show_deleted', self.changed_show_status)
  410.  
  411.         self.selected_show = 0
  412.  
  413.         if not imaging_available:
  414.             self.show_image.pholder_show("PIL library\nnot available")
  415.  
  416.         self.allow_buttons(False)
  417.  
  418.         # Don't show the main dialog if start in tray option is set
  419.         if self.config['show_tray'] and self.config['start_in_tray']:
  420.             self.hidden = True
  421.         else:
  422.             self.main.show()
  423.  
  424.         self.start_engine()
  425.  
  426.     def _clear_gui(self):
  427.         self.show_title.set_text('<span size="14000"><b>Trackma</b></span>')
  428.         self.show_title.set_use_markup(True)
  429.         self.show_image.pholder_show("Trackma")
  430.  
  431.         current_api = utils.available_libs[self.account['api']]
  432.         api_iconfile = current_api[1]
  433.  
  434.         self.main.set_title('Trackma-gtk %s [%s (%s)]' % (
  435.             utils.VERSION,
  436.             self.engine.api_info['name'],
  437.             self.engine.api_info['mediatype']))
  438.         self.api_icon.set_from_file(api_iconfile)
  439.         self.api_user.set_text("%s (%s)" % (
  440.             self.engine.get_userconfig('username'),
  441.             self.engine.api_info['mediatype']))
  442.  
  443.         self.score_decimal_places = 0
  444.         if isinstance( self.engine.mediainfo['score_step'], float ):
  445.             self.score_decimal_places = len(str(self.engine.mediainfo['score_step']).split('.')[1])
  446.  
  447.         self.show_score.set_value(0)
  448.         self.show_score.set_digits(self.score_decimal_places)
  449.         self.show_score.set_range(0, self.engine.mediainfo['score_max'])
  450.         self.show_score.get_adjustment().set_step_increment(self.engine.mediainfo['score_step'])
  451.  
  452.         can_play = self.engine.mediainfo['can_play']
  453.         can_update = self.engine.mediainfo['can_update']
  454.  
  455.         self.play_button.set_sensitive(can_play)
  456.         self.play_next_button.set_sensitive(can_play)
  457.  
  458.         self.update_button.set_sensitive(can_update)
  459.         self.show_ep_num.set_sensitive(can_update)
  460.         self.add_epp_button.set_sensitive(can_update)
  461.  
  462.     def _create_lists(self):
  463.         statuses_nums = self.engine.mediainfo['statuses']
  464.         statuses_names = self.engine.mediainfo['statuses_dict']
  465.  
  466.         # Statusbox
  467.         self.statusmodel.clear()
  468.         for status in statuses_nums:
  469.             self.statusmodel.append([status, statuses_names[status]])
  470.         self.statusbox.set_model(self.statusmodel)
  471.         self.statusbox.show_all()
  472.  
  473.         # Clear notebook
  474.         for i in xrange(self.notebook.get_n_pages()):
  475.             self.notebook.remove_page(-1)
  476.  
  477.         # Insert pages
  478.         for status in statuses_nums:
  479.             name = statuses_names[status]
  480.  
  481.             sw = gtk.ScrolledWindow()
  482.             sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  483.             sw.set_size_request(550, 300)
  484.             sw.set_border_width(5)
  485.  
  486.             self.show_lists[status] = ShowView(
  487.                     status,
  488.                     self.engine.mediainfo['has_progress'],
  489.                     self.score_decimal_places)
  490.             self.show_lists[status].get_selection().connect("changed", self.select_show)
  491.             self.show_lists[status].connect("row-activated", self.do_info)
  492.             self.show_lists[status].connect("button-press-event", self.showview_context_menu)
  493.             self.show_lists[status].pagenumber = self.notebook.get_n_pages()
  494.             sw.add(self.show_lists[status])
  495.  
  496.             self.notebook.append_page(sw, gtk.Label(name))
  497.             self.notebook.show_all()
  498.  
  499.         self.notebook.connect("switch-page", self.select_show)
  500.  
  501.     def idle_destroy(self):
  502.         gobject.idle_add(self.idle_destroy_push)
  503.  
  504.     def idle_destroy_push(self):
  505.         self.quit = True
  506.         self.main.destroy()
  507.  
  508.     def idle_restart(self):
  509.         gobject.idle_add(self.idle_restart_push)
  510.  
  511.     def idle_restart_push(self):
  512.         self.quit = False
  513.         self.main.destroy()
  514.         self.do_switch_account(None, False)
  515.  
  516.     def on_destroy(self, widget):
  517.         if self.quit:
  518.             gtk.main_quit()
  519.  
  520.     def status_event(self, widget):
  521.         # Called when the Show/Hide is clicked on the tray icon menu
  522.         if self.hidden:
  523.             self.main.show()
  524.             self.hidden = False
  525.         else:
  526.             self.main.hide()
  527.             self.hidden = True
  528.  
  529.     def delete_event(self, widget, event, data=None):
  530.         if self.indicator.get_status() == appindicator.STATUS_ACTIVE and self.config['close_to_tray']:
  531.             self.hidden = True
  532.             self.main.hide()
  533.         else:
  534.             self.do_quit()
  535.         return True
  536.  
  537.     def do_quit(self, widget=None, event=None, data=None):
  538.         if self.close_thread is None:
  539.             self.close_thread = threading.Thread(target=self.task_unload).start()
  540.  
  541.     def do_addsearch(self, widget):
  542.         win = ShowSearch(self.engine)
  543.         win.show_all()
  544.  
  545.     def do_settings(self, widget):
  546.         win = Settings(self.engine, self.config, self.configfile)
  547.         win.show_all()
  548.  
  549.     def do_reload(self, widget, account, mediatype):
  550.         self.selected_show = 0
  551.  
  552.         threading.Thread(target=self.task_reload, args=[account, mediatype]).start()
  553.  
  554.     def do_play(self, widget, playnext):
  555.         threading.Thread(target=self.task_play, args=(playnext,)).start()
  556.  
  557.     def do_neweps(self, widget):
  558.         threading.Thread(target=self.task_neweps).start()
  559.  
  560.     def do_delete(self, widget):
  561.         try:
  562.             show = self.engine.get_show_info(self.selected_show)
  563.             self.engine.delete_show(show)
  564.         except utils.TrackmaError, e:
  565.             self.error(e.message)
  566.  
  567.     def do_info(self, widget, d1=None, d2=None):
  568.         show = self.engine.get_show_info(self.selected_show)
  569.         win = InfoDialog(self.engine, show)
  570.  
  571.     def do_add_epp(self, widget):
  572.         ep = self.show_ep_num.get_value_as_int()
  573.         try:
  574.             show = self.engine.set_episode(self.selected_show, ep + 1)
  575.             self.show_ep_num.set_value(show['my_progress'])
  576.         except utils.TrackmaError, e:
  577.             self.error(e.message)
  578.  
  579.     def do_rem_epp(self, widget):
  580.         ep = self.show_ep_num.get_value_as_int()
  581.         try:
  582.             if(ep > 0):
  583.                     show = self.engine.set_episode(self.selected_show, ep - 1)
  584.                     self.show_ep_num.set_value(show['my_progress'])
  585.  
  586.         except utils.TrackmaError, e:
  587.             self.error(e.message)
  588.  
  589.     def do_update(self, widget):
  590.         self.show_ep_num.update()
  591.         ep = self.show_ep_num.get_value_as_int()
  592.         try:
  593.             show = self.engine.set_episode(self.selected_show, ep)
  594.         except utils.TrackmaError, e:
  595.             self.error(e.message)
  596.  
  597.     def do_score(self, widget):
  598.         self.show_score.update()
  599.         score = self.show_score.get_value()
  600.         try:
  601.             show = self.engine.set_score(self.selected_show, score)
  602.         except utils.TrackmaError, e:
  603.             self.error(e.message)
  604.  
  605.     def do_status(self, widget):
  606.         statusiter = self.statusbox.get_active_iter()
  607.         status = self.statusmodel.get(statusiter, 0)[0]
  608.  
  609.         try:
  610.             show = self.engine.set_status(self.selected_show, status)
  611.         except utils.TrackmaError, e:
  612.             self.error(e.message)
  613.  
  614.     def do_update_next(self, show, played_ep):
  615.         # Thread safe
  616.         gobject.idle_add(self.task_update_next, show, played_ep)
  617.  
  618.     def changed_show(self, show):
  619.         status = show['my_status']
  620.         self.show_lists[status].update(show)
  621.  
  622.     def changed_show_title(self, show, altname):
  623.         status = show['my_status']
  624.         self.show_lists[status].update_title(show, altname)
  625.  
  626.     def changed_show_status(self, show, old_status=None):
  627.         # Rebuild lists
  628.         status = show['my_status']
  629.  
  630.         self.build_list(status)
  631.         if old_status:
  632.             self.build_list(old_status)
  633.  
  634.         pagenumber = self.show_lists[status].pagenumber
  635.         self.notebook.set_current_page(pagenumber)
  636.  
  637.         self.show_lists[status].select(show)
  638.  
  639.     def playing_show(self, show, is_playing, episode):
  640.         status = show['my_status']
  641.         self.show_lists[status].playing(show, is_playing)
  642.  
  643.     def task_update_next(self, show, played_ep):
  644.         dialog = gtk.MessageDialog(self.main,
  645.                     gtk.DIALOG_MODAL,
  646.                     gtk.MESSAGE_QUESTION,
  647.                     gtk.BUTTONS_YES_NO,
  648.                     "Update %s to episode %d?" % (show['title'], played_ep))
  649.         dialog.show_all()
  650.         dialog.connect("response", self.task_update_next_response, show, played_ep)
  651.  
  652.     def task_update_next_response(self, widget, response, show, played_ep):
  653.         widget.destroy()
  654.         # Update show to the played episode
  655.         if response == gtk.RESPONSE_YES:
  656.             try:
  657.                 show = self.engine.set_episode(show['id'], played_ep)
  658.                 status = show['my_status']
  659.                 self.show_lists[status].update(show)
  660.             except utils.TrackmaError, e:
  661.                 self.error(e.message)
  662.  
  663.     def task_play(self, playnext):
  664.         self.allow_buttons(False)
  665.  
  666.         show = self.engine.get_show_info(self.selected_show)
  667.  
  668.         try:
  669.             if playnext:
  670.                 played_ep = self.engine.play_episode(show)
  671.             else:
  672.                 ep = self.show_ep_num.get_value_as_int()
  673.                 played_ep = self.engine.play_episode(show, ep)
  674.  
  675.             # Ask if we should update to the next episode
  676.             if played_ep == (show['my_progress'] + 1):
  677.                 self.do_update_next(show, played_ep)
  678.         except utils.TrackmaError, e:
  679.             self.error(e.message)
  680.             print e.message
  681.  
  682.         self.status("Ready.")
  683.         self.allow_buttons(True)
  684.  
  685.     def task_neweps(self):
  686.         self.allow_buttons(False)
  687.         _filter = self.engine.mediainfo['status_start']
  688.         filtered = self.engine.filter_list(_filter)
  689.  
  690.         try:
  691.             result = self.engine.get_new_episodes(filtered)
  692.             for show in result:
  693.                 self.changed_show(show)
  694.         except utils.TrackmaError, e:
  695.             self.error(e.message)
  696.  
  697.         self.status("Ready.")
  698.         self.allow_buttons(True)
  699.  
  700.     def task_unload(self):
  701.         self.allow_buttons(False)
  702.         self.engine.unload()
  703.  
  704.         self.idle_destroy()
  705.  
  706.     def do_retrieve_ask(self, widget):
  707.         queue = self.engine.get_queue()
  708.  
  709.         if len(queue) > 0:
  710.             dialog = gtk.MessageDialog(self.main,
  711.                 gtk.DIALOG_MODAL,
  712.                 gtk.MESSAGE_QUESTION,
  713.                 gtk.BUTTONS_YES_NO,
  714.                 "There are %d queued changes in your list. If you retrieve the remote list now you will lose your queued changes. Are you sure you want to continue?" % len(queue))
  715.             dialog.show_all()
  716.             dialog.connect("response", self.do_retrieve)
  717.         else:
  718.             # If the user doesn't have any queued changes
  719.             # just go ahead
  720.             self.do_retrieve()
  721.  
  722.     def do_retrieve(self, widget=None, response=gtk.RESPONSE_YES):
  723.         if widget:
  724.             widget.destroy()
  725.  
  726.         if response == gtk.RESPONSE_YES:
  727.             threading.Thread(target=self.task_sync, args=(False,True)).start()
  728.  
  729.     def do_send(self, widget):
  730.         threading.Thread(target=self.task_sync, args=(True,False)).start()
  731.  
  732.     def do_sync(self, widget):
  733.         threading.Thread(target=self.task_sync, args=(True,True)).start()
  734.  
  735.     def task_sync(self, send, retrieve):
  736.         self.allow_buttons(False)
  737.  
  738.         if send:
  739.             self.engine.list_upload()
  740.         if retrieve:
  741.             self.engine.list_download()
  742.  
  743.         gtk.threads_enter()
  744.         self.build_all_lists()
  745.         gtk.threads_leave()
  746.  
  747.         self.status("Ready.")
  748.         self.allow_buttons(True)
  749.  
  750.     def start_engine(self):
  751.         threading.Thread(target=self.task_start_engine).start()
  752.  
  753.     def task_start_engine(self):
  754.         if not self.engine.loaded:
  755.             try:
  756.                 self.engine.start()
  757.             except utils.TrackmaFatal, e:
  758.                 print("Fatal engine error: %s" % e.message)
  759.                 self.idle_restart()
  760.                 self.error("Fatal engine error: %s" % e.message)
  761.                 return
  762.  
  763.         gtk.threads_enter()
  764.         self.statusbox.handler_block(self.statusbox_handler)
  765.         self._clear_gui()
  766.         self._create_lists()
  767.         self.build_all_lists()
  768.  
  769.         # Clear and build API and mediatypes menus
  770.         for i in self.mb_mediatype_menu.get_children():
  771.             self.mb_mediatype_menu.remove(i)
  772.  
  773.         for mediatype in self.engine.api_info['supported_mediatypes']:
  774.             item = gtk.RadioMenuItem(None, mediatype)
  775.             if mediatype == self.engine.api_info['mediatype']:
  776.                 item.set_active(True)
  777.             item.connect("activate", self.do_reload, None, mediatype)
  778.             self.mb_mediatype_menu.append(item)
  779.             item.show()
  780.  
  781.         self.statusbox.handler_unblock(self.statusbox_handler)
  782.         gtk.threads_leave()
  783.  
  784.         self.status("Ready.")
  785.         self.allow_buttons(True)
  786.  
  787.     def task_reload(self, account, mediatype):
  788.         try:
  789.             self.engine.reload(account, mediatype)
  790.         except utils.TrackmaError, e:
  791.             self.error(e.message)
  792.         except utils.TrackmaFatal, e:
  793.             print("Fatal engine error: %s" % e.message)
  794.             self.idle_restart()
  795.             self.error("Fatal engine error: %s" % e.message)
  796.             return
  797.  
  798.         if account:
  799.             self.account = account
  800.  
  801.         # Refresh the GUI
  802.         self.task_start_engine()
  803.  
  804.     def select_show(self, widget, page=None, page_num=None):
  805.         page = page_num
  806.  
  807.         if page is None:
  808.             page = self.notebook.get_current_page()
  809.  
  810.         selection = self.notebook.get_nth_page(page).get_child().get_selection()
  811.  
  812.         (tree_model, tree_iter) = selection.get_selected()
  813.         if not tree_iter:
  814.             self.allow_buttons_push(False, lists_too=False)
  815.             return
  816.  
  817.         try:
  818.             self.selected_show = int(tree_model.get(tree_iter, 0)[0])
  819.         except ValueError:
  820.             self.selected_show = tree_model.get(tree_iter, 0)[0]
  821.  
  822.         self.allow_buttons_push(True, lists_too=False)
  823.  
  824.         show = self.engine.get_show_info(self.selected_show)
  825.  
  826.         # Block handlers
  827.         self.statusbox.handler_block(self.statusbox_handler)
  828.  
  829.         if self.image_thread is not None:
  830.             self.image_thread.cancel()
  831.  
  832.         self.show_title.set_text('<span size="14000"><b>{0}</b></span>'.format(cgi.escape(show['title'])))
  833.         self.show_title.set_use_markup(True)
  834.  
  835.         # Episode selector
  836.         if show['total']:
  837.             adjustment = gtk.Adjustment(upper=show['total'], step_incr=1)
  838.         else:
  839.             adjustment = gtk.Adjustment(upper=1000, step_incr=1)
  840.  
  841.         self.show_ep_num.set_adjustment(adjustment)
  842.         self.show_ep_num.set_value(show['my_progress'])
  843.  
  844.         # Status selector
  845.         for i in self.statusmodel:
  846.             if i[0] == str(show['my_status']):
  847.                 self.statusbox.set_active_iter(i.iter)
  848.                 break
  849.  
  850.         # Score selector
  851.         self.show_score.set_value(show['my_score'])
  852.  
  853.         # Image
  854.         if show.get('image_thumb') or show.get('image'):
  855.             utils.make_dir('cache')
  856.             filename = utils.get_filename('cache', "%s.jpg" % (show['id']))
  857.  
  858.             if os.path.isfile(filename):
  859.                 self.show_image.image_show(filename)
  860.             else:
  861.                 if imaging_available:
  862.                     self.show_image.pholder_show('Loading...')
  863.                     self.image_thread = ImageTask(self.show_image, show.get('image_thumb') or show['image'], filename, (100, 149))
  864.                     self.image_thread.start()
  865.                 else:
  866.                     self.show_image.pholder_show("PIL library\nnot available")
  867.  
  868.         # Unblock handlers
  869.         self.statusbox.handler_unblock(self.statusbox_handler)
  870.  
  871.     def build_all_lists(self):
  872.         for status in self.show_lists.iterkeys():
  873.             self.build_list(status)
  874.  
  875.     def build_list(self, status):
  876.         widget = self.show_lists[status]
  877.         widget.append_start()
  878.         for show in self.engine.filter_list(widget.status_filter):
  879.             widget.append(show, self.engine.altname(show['id']))
  880.         widget.append_finish()
  881.  
  882.     def on_about(self, widget):
  883.         about = gtk.AboutDialog()
  884.         about.set_program_name("Trackma-gtk")
  885.         about.set_version(utils.VERSION)
  886.         about.set_comments("Trackma is an open source client for media tracking websites.")
  887.         about.set_website("http://github.com/z411/trackma")
  888.         about.set_copyright("(c) z411 - Icon by shuuichi")
  889.         about.run()
  890.         about.destroy()
  891.  
  892.     def message_handler(self, classname, msgtype, msg):
  893.         # Thread safe
  894.         print "%s: %s" % (classname, msg)
  895.         if msgtype == messenger.TYPE_WARN:
  896.             gobject.idle_add(self.status_push, "%s warning: %s" % (classname, msg))
  897.         elif msgtype != messenger.TYPE_DEBUG:
  898.             gobject.idle_add(self.status_push, "%s: %s" % (classname, msg))
  899.  
  900.     def error(self, msg, icon=gtk.MESSAGE_ERROR):
  901.         # Thread safe
  902.         gobject.idle_add(self.error_push, msg, icon)
  903.  
  904.     def error_push(self, msg, icon=gtk.MESSAGE_ERROR):
  905.         dialog = gtk.MessageDialog(self.main, gtk.DIALOG_MODAL, icon, gtk.BUTTONS_OK, msg)
  906.         dialog.show_all()
  907.         dialog.connect("response", self.modal_close)
  908.  
  909.     def modal_close(self, widget, response_id):
  910.         widget.destroy()
  911.  
  912.     def status(self, msg):
  913.         # Thread safe
  914.         gobject.idle_add(self.status_push, msg)
  915.  
  916.     def status_push(self, msg):
  917.         self.statusbar.push(0, msg)
  918.  
  919.     def allow_buttons(self, boolean):
  920.         # Thread safe
  921.         gobject.idle_add(self.allow_buttons_push, boolean)
  922.  
  923.     def allow_buttons_push(self, boolean, lists_too=True):
  924.         if lists_too:
  925.             for widget in self.show_lists.itervalues():
  926.                 widget.set_sensitive(boolean)
  927.  
  928.         if self.selected_show or not boolean:
  929.             if self.engine.mediainfo['can_play']:
  930.                 self.play_button.set_sensitive(boolean)
  931.                 self.play_next_button.set_sensitive(boolean)
  932.  
  933.             if self.engine.mediainfo['can_update']:
  934.                 self.update_button.set_sensitive(boolean)
  935.                 self.show_ep_num.set_sensitive(boolean)
  936.                 self.add_epp_button.set_sensitive(boolean)
  937.                 self.rem_epp_button.set_sensitive(boolean)
  938.  
  939.             self.scoreset_button.set_sensitive(boolean)
  940.             self.show_score.set_sensitive(boolean)
  941.             self.statusbox.set_sensitive(boolean)
  942.  
  943.     def do_copytoclip(self, widget):
  944.         # Copy selected show title to clipboard
  945.         show = self.engine.get_show_info(self.selected_show)
  946.  
  947.         clipboard = gtk.clipboard_get()
  948.         clipboard.set_text(show['title'])
  949.  
  950.         self.status('Title copied to clipboard.')
  951.  
  952.     def do_altname(self,widget):
  953.         show = self.engine.get_show_info(self.selected_show)
  954.         current_altname = self.engine.altname(self.selected_show)
  955.  
  956.         dialog = gtk.MessageDialog(
  957.             None,
  958.             gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
  959.             gtk.MESSAGE_QUESTION,
  960.             gtk.BUTTONS_OK_CANCEL,
  961.             None)
  962.         dialog.set_markup('Set the <b>alternate title</b> for the show.')
  963.         entry = gtk.Entry()
  964.         entry.set_text(current_altname)
  965.         entry.connect("activate", self.altname_response, dialog, gtk.RESPONSE_OK)
  966.         hbox = gtk.HBox()
  967.         hbox.pack_start(gtk.Label("Alternate Title:"), False, 5, 5)
  968.         hbox.pack_end(entry)
  969.         dialog.format_secondary_markup("Use this if the tracker is unable to find this show. Leave blank to disable.")
  970.         dialog.vbox.pack_end(hbox, True, True, 0)
  971.         dialog.show_all()
  972.         retval = dialog.run()
  973.  
  974.         if retval == gtk.RESPONSE_OK:
  975.             text = entry.get_text()
  976.             self.engine.altname(self.selected_show, text)
  977.             self.changed_show_title(show, text)
  978.  
  979.         dialog.destroy()
  980.  
  981.     def altname_response(self, entry, dialog, response):
  982.         dialog.response(response)
  983.  
  984.     def do_web(self, widget):
  985.         show = self.engine.get_show_info(self.selected_show)
  986.         if show['url']:
  987.             webbrowser.open(show['url'], 2, True)
  988.  
  989.     def showview_context_menu(self, treeview, event):
  990.         if event.button == 3:
  991.             x = int(event.x)
  992.             y = int(event.y)
  993.             pthinfo = treeview.get_path_at_pos(x, y)
  994.             if pthinfo is not None:
  995.                 path, col, cellx, celly = pthinfo
  996.                 treeview.grab_focus()
  997.                 treeview.set_cursor(path, col, 0)
  998.  
  999.                 menu = gtk.Menu()
  1000.                 mb_play = gtk.ImageMenuItem(gtk.STOCK_MEDIA_PLAY)
  1001.                 mb_play.connect("activate", self.do_play, True)
  1002.                 mb_info = gtk.MenuItem("Show details...")
  1003.                 mb_info.connect("activate", self.do_info)
  1004.                 mb_web = gtk.MenuItem("Open web site")
  1005.                 mb_web.connect("activate", self.do_web)
  1006.                 mb_copy = gtk.MenuItem("Copy title to clipboard")
  1007.                 mb_copy.connect("activate", self.do_copytoclip)
  1008.                 mb_alt_title = gtk.MenuItem("Set alternate title...")
  1009.                 mb_alt_title.connect("activate", self.do_altname)
  1010.                 mb_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE)
  1011.                 mb_delete.connect("activate", self.do_delete)
  1012.  
  1013.                 menu.append(mb_play)
  1014.                 menu.append(mb_info)
  1015.                 menu.append(mb_web)
  1016.                 menu.append(gtk.SeparatorMenuItem())
  1017.                 menu.append(mb_copy)
  1018.                 menu.append(mb_alt_title)
  1019.                 menu.append(gtk.SeparatorMenuItem())
  1020.                 menu.append(mb_delete)
  1021.  
  1022.                 menu.show_all()
  1023.                 menu.popup(None, None, None, event.button, event.time)
  1024.  
  1025. class ImageTask(threading.Thread):
  1026.     cancelled = False
  1027.  
  1028.     def __init__(self, show_image, remote, local, size=None):
  1029.         self.show_image = show_image
  1030.         self.remote = remote
  1031.         self.local = local
  1032.         self.size = size
  1033.         threading.Thread.__init__(self)
  1034.  
  1035.     def run(self):
  1036.         self.cancelled = False
  1037.  
  1038.         time.sleep(1)
  1039.  
  1040.         if self.cancelled:
  1041.             return
  1042.  
  1043.         # If there's a better solution for this please tell me/implement it.
  1044.  
  1045.         # If there's a size specified, thumbnail with PIL library
  1046.         # otherwise download and save it as it is
  1047.         req = urllib2.Request(self.remote)
  1048.         req.add_header("User-agent", "TrackmaImage/{}".format(utils.VERSION))
  1049.         img_file = StringIO(urllib2.urlopen(req).read())
  1050.         if self.size:
  1051.             im = Image.open(img_file)
  1052.             im.thumbnail((self.size[0], self.size[1]), Image.ANTIALIAS)
  1053.             im.save(self.local)
  1054.         else:
  1055.             with open(self.local, 'wb') as f:
  1056.                 f.write(img_file.read())
  1057.  
  1058.         if self.cancelled:
  1059.             return
  1060.  
  1061.         gtk.threads_enter()
  1062.         self.show_image.image_show(self.local)
  1063.         gtk.threads_leave()
  1064.  
  1065.     def cancel(self):
  1066.         self.cancelled = True
  1067.  
  1068. class ImageView(gtk.HBox):
  1069.     def __init__(self, w, h):
  1070.         gtk.HBox.__init__(self)
  1071.  
  1072.         self.showing_pholder = False
  1073.  
  1074.         self.w_image = gtk.Image()
  1075.         self.w_image.set_size_request(w, h)
  1076.  
  1077.         self.w_pholder = gtk.Label()
  1078.         self.w_pholder.set_size_request(w, h)
  1079.  
  1080.         self.pack_start(self.w_image, False, False, 0)
  1081.  
  1082.     def image_show(self, filename):
  1083.         if self.showing_pholder:
  1084.             self.remove(self.w_pholder)
  1085.             self.pack_start(self.w_image, False, False, 0)
  1086.             self.w_image.show()
  1087.             self.showing_pholder = False
  1088.  
  1089.         self.w_image.set_from_file(filename)
  1090.  
  1091.     def pholder_show(self, msg):
  1092.         if not self.showing_pholder:
  1093.             self.pack_end(self.w_pholder, False, False, 0)
  1094.             self.remove(self.w_image)
  1095.             self.w_pholder.show()
  1096.             self.showing_pholder = True
  1097.  
  1098.         self.w_pholder.set_text(msg)
  1099.  
  1100. class ShowView(gtk.TreeView):
  1101.     def __init__(self, status, has_progress=True, decimals=0):
  1102.         gtk.TreeView.__init__(self)
  1103.  
  1104.         self.has_progress = has_progress
  1105.         self.decimals = decimals
  1106.         self.status_filter = status
  1107.  
  1108.         self.set_enable_search(True)
  1109.         self.set_search_column(1)
  1110.  
  1111.         self.cols = dict()
  1112.         if has_progress:
  1113.             columns = (('Title', 1), ('Progress', 2), ('Score', 3), ('Percent', 4))
  1114.         else:
  1115.             columns = (('Title', 1), ('Score', 3))
  1116.  
  1117.         for (name, sort) in columns:
  1118.             self.cols[name] = gtk.TreeViewColumn(name)
  1119.             self.cols[name].set_sort_column_id(sort)
  1120.             self.append_column(self.cols[name])
  1121.  
  1122.         #renderer_id = gtk.CellRendererText()
  1123.         #self.cols['ID'].pack_start(renderer_id, False)
  1124.         #self.cols['ID'].set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  1125.         #self.cols['ID'].set_expand(False)
  1126.         #self.cols['ID'].add_attribute(renderer_id, 'text', 0)
  1127.  
  1128.         renderer_title = gtk.CellRendererText()
  1129.         self.cols['Title'].pack_start(renderer_title, False)
  1130.         self.cols['Title'].set_resizable(True)
  1131.         self.cols['Title'].set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
  1132.         self.cols['Title'].set_expand(True)
  1133.         self.cols['Title'].add_attribute(renderer_title, 'text', 1)
  1134.         self.cols['Title'].add_attribute(renderer_title, 'foreground', 7)
  1135.  
  1136.         if has_progress:
  1137.             renderer_progress = gtk.CellRendererText()
  1138.             self.cols['Progress'].pack_start(renderer_progress, False)
  1139.             self.cols['Progress'].add_attribute(renderer_progress, 'text', 4)
  1140.             self.cols['Progress'].set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  1141.             self.cols['Progress'].set_expand(False)
  1142.  
  1143.             renderer_percent = gtk.CellRendererProgress()
  1144.             self.cols['Percent'].pack_start(renderer_percent, False)
  1145.             self.cols['Percent'].add_attribute(renderer_percent, 'value', 6)
  1146.             renderer_percent.set_fixed_size(100, -1)
  1147.  
  1148.         renderer_score = gtk.CellRendererText()
  1149.         self.cols['Score'].pack_start(renderer_score, False)
  1150.         self.cols['Score'].add_attribute(renderer_score, 'text', 5)
  1151.         self.cols['Score'].set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
  1152.         self.cols['Score'].set_expand(False)
  1153.  
  1154.         # ID, Title, Episodes, Score, Episodes_str, Score_str, Progress, Color
  1155.         self.store = gtk.ListStore(str, str, int, float, str, str, int, str)
  1156.         self.set_model(self.store)
  1157.  
  1158.     def _get_color(self, show):
  1159.         if show.get('queued'):
  1160.             return '#54C571'
  1161.         elif show.get('neweps'):
  1162.             return '#FBB917'
  1163.         elif show['status'] == 1:
  1164.             return '#0099cc'
  1165.         elif show['status'] == 3:
  1166.             return '#999900'
  1167.         else:
  1168.             return None
  1169.  
  1170.     def append_start(self):
  1171.         self.freeze_child_notify()
  1172.         self.store.clear()
  1173.  
  1174.     def append(self, show, altname=None):
  1175.         if self.has_progress:
  1176.             if show['total'] and show['my_progress'] <= show['total']:
  1177.                 progress = (float(show['my_progress']) / show['total']) * 100
  1178.             else:
  1179.                 progress = 0
  1180.             episodes_str = "%d / %d" % (show['my_progress'], show['total'])
  1181.         else:
  1182.             episodes_str = ''
  1183.             progress = 0
  1184.  
  1185.         title_str = show['title']
  1186.         if altname:
  1187.             title_str += " [%s]" % altname
  1188.  
  1189.         score_str = "%0.*f" % (self.decimals, show['my_score'])
  1190.  
  1191.         row = [show['id'], title_str, show['my_progress'], show['my_score'], episodes_str, score_str, progress, self._get_color(show)]
  1192.         self.store.append(row)
  1193.  
  1194.     def append_finish(self):
  1195.         self.thaw_child_notify()
  1196.         self.store.set_sort_column_id(1, gtk.SORT_ASCENDING)
  1197.  
  1198.     def get_showid(self):
  1199.         selection = self.get_selection()
  1200.         if selection is not None:
  1201.             selection.set_mode(gtk.SELECTION_SINGLE)
  1202.             (tree_model, tree_iter) = selection.get_selected()
  1203.             return tree_model.get(tree_iter, 0)[0]
  1204.  
  1205.     def update(self, show):
  1206.         for row in self.store:
  1207.             if int(row[0]) == show['id']:
  1208.                 if self.has_progress:
  1209.                     if show['total']:
  1210.                         progress = (float(show['my_progress']) / show['total']) * 100
  1211.                     else:
  1212.                         progress = 0
  1213.                     episodes_str = "%d / %d" % (show['my_progress'], show['total'])
  1214.                     row[2] = show['my_progress']
  1215.                     row[4] = episodes_str
  1216.                     row[6] = progress
  1217.  
  1218.                 score_str = "%0.*f" % (self.decimals, show['my_score'])
  1219.  
  1220.                 row[3] = show['my_score']
  1221.                 row[5] = score_str
  1222.                 row[7] = self._get_color(show)
  1223.                 return
  1224.  
  1225.         #print "Warning: Show ID not found in ShowView (%d)" % show['id']
  1226.  
  1227.     def update_title(self, show, altname=None):
  1228.         for row in self.store:
  1229.             if int(row[0]) == show['id']:
  1230.                 if altname:
  1231.                     title_str = "%s [%s]" % (show['title'], altname)
  1232.                 else:
  1233.                     title_str = show['title']
  1234.  
  1235.                 row[1] = title_str
  1236.                 return
  1237.  
  1238.     def playing(self, show, is_playing):
  1239.         # Change the color if the show is currently playing
  1240.         for row in self.store:
  1241.             if int(row[0]) == show['id']:
  1242.                 if is_playing:
  1243.                     row[7] = '#6C2DC7'
  1244.                 else:
  1245.                     row[7] = self._get_color(show)
  1246.                 return
  1247.  
  1248.     def select(self, show):
  1249.         """Select specified row"""
  1250.         for row in self.store:
  1251.             if int(row[0]) == show['id']:
  1252.                 selection = self.get_selection()
  1253.                 selection.select_iter(row.iter)
  1254.                 break
  1255.  
  1256. class AccountSelect(gtk.Window):
  1257.     default = None
  1258.  
  1259.     def __init__(self, manager, switch=False):
  1260.         gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
  1261.         self.use_button = gtk.Button('Switch')
  1262.         self.use_button.set_sensitive(False)
  1263.  
  1264.         self.manager = manager
  1265.         self.switch = switch
  1266.  
  1267.     def create(self):
  1268.         self.pixbufs = {}
  1269.         for (libname, lib) in utils.available_libs.iteritems():
  1270.             self.pixbufs[libname] = gtk.gdk.pixbuf_new_from_file(lib[1])
  1271.  
  1272.         self.set_position(gtk.WIN_POS_CENTER)
  1273.         self.set_title('Select Account')
  1274.         self.set_border_width(10)
  1275.         self.connect('delete-event', self.on_delete)
  1276.  
  1277.         vbox = gtk.VBox(False, 10)
  1278.  
  1279.         # Treeview
  1280.         sw = gtk.ScrolledWindow()
  1281.         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  1282.         sw.set_size_request(400, 200)
  1283.  
  1284.         self.accountlist = gtk.TreeView()
  1285.  
  1286.         col_user = gtk.TreeViewColumn('Username')
  1287.         col_user.set_expand(True)
  1288.         self.accountlist.append_column(col_user)
  1289.         col_site = gtk.TreeViewColumn('Site')
  1290.         self.accountlist.append_column(col_site)
  1291.  
  1292.         renderer_user = gtk.CellRendererText()
  1293.         col_user.pack_start(renderer_user, False)
  1294.         col_user.add_attribute(renderer_user, 'text', 1)
  1295.         renderer_icon = gtk.CellRendererPixbuf()
  1296.         col_site.pack_start(renderer_icon, False)
  1297.         col_site.add_attribute(renderer_icon, 'pixbuf', 3)
  1298.         renderer_site = gtk.CellRendererText()
  1299.         col_site.pack_start(renderer_site, False)
  1300.         col_site.add_attribute(renderer_site, 'text', 2)
  1301.  
  1302.         self.store = gtk.ListStore(int, str, str, gtk.gdk.Pixbuf, bool)
  1303.         self.accountlist.set_model(self.store)
  1304.  
  1305.         self.accountlist.get_selection().connect("changed", self.on_account_changed)
  1306.         self.accountlist.connect("row-activated", self.on_row_activated)
  1307.  
  1308.         # Bottom buttons
  1309.         alignment = gtk.Alignment(xalign=1.0)
  1310.         bottombar = gtk.HBox(False, 5)
  1311.  
  1312.         self.remember = gtk.CheckButton('Remember')
  1313.         if self.manager.get_default() is not None:
  1314.             self.remember.set_active(True)
  1315.         gtk.stock_add([(gtk.STOCK_APPLY, "Add", 0, 0, "")])
  1316.         add_button = gtk.Button(stock=gtk.STOCK_APPLY)
  1317.         add_button.connect("clicked", self.do_add)
  1318.         self.delete_button = gtk.Button(stock=gtk.STOCK_DELETE)
  1319.         self.delete_button.set_sensitive(False)
  1320.         self.delete_button.connect("clicked", self.do_delete)
  1321.         close_button = gtk.Button(stock=gtk.STOCK_CLOSE)
  1322.         close_button.connect("clicked", self.do_close)
  1323.  
  1324.         bottombar.pack_start(self.remember, False, False, 0)
  1325.         bottombar.pack_start(self.use_button, False, False, 0)
  1326.         bottombar.pack_start(add_button, False, False, 0)
  1327.         bottombar.pack_start(self.delete_button, False, False, 0)
  1328.         bottombar.pack_start(close_button, False, False, 0)
  1329.         alignment.add(bottombar)
  1330.  
  1331.         sw.add(self.accountlist)
  1332.  
  1333.         vbox.pack_start(sw, True, True, 0)
  1334.         vbox.pack_start(alignment, False, False, 0)
  1335.         self.add(vbox)
  1336.  
  1337.         self._refresh_list()
  1338.         self.show_all()
  1339.  
  1340.     def _refresh_list(self):
  1341.         self.store.clear()
  1342.         for k, account in self.manager.get_accounts():
  1343.             libname = account['api']
  1344.             try:
  1345.                 api = utils.available_libs[libname]
  1346.                 self.store.append([k, account['username'], api[0], self.pixbufs[libname], True])
  1347.             except KeyError:
  1348.                 # Invalid API
  1349.                 self.store.append([k, account['username'], 'N/A', None, False])
  1350.  
  1351.  
  1352.     def is_remember(self):
  1353.         # Return the state of the checkbutton if there's no default account
  1354.         if self.default is None:
  1355.             return self.remember.get_active()
  1356.         else:
  1357.             return True
  1358.  
  1359.     def get_selected(self):
  1360.         selection = self.accountlist.get_selection()
  1361.         selection.set_mode(gtk.SELECTION_SINGLE)
  1362.         return selection.get_selected()
  1363.  
  1364.     def get_selected_id(self):
  1365.         if self.default is not None:
  1366.             return self.default
  1367.         else:
  1368.             tree_model, tree_iter = self.get_selected()
  1369.             return tree_model.get_value(tree_iter, 0)
  1370.  
  1371.     def on_account_changed(self, widget):
  1372.         tree_model, tree_iter = self.get_selected()
  1373.         if tree_iter:
  1374.             is_selectable = tree_model.get_value(tree_iter, 4)
  1375.         else:
  1376.             is_selectable = False
  1377.  
  1378.         self.use_button.set_sensitive(is_selectable)
  1379.         self.delete_button.set_sensitive(True)
  1380.  
  1381.     def on_row_activated(self, treeview, iter, path):
  1382.         self.use_button.emit("clicked")
  1383.  
  1384.     def do_add(self, widget):
  1385.         """Create Add Account window"""
  1386.         self.add_win = AccountSelectAdd(self.pixbufs)
  1387.         self.add_win.add_button.connect("clicked", self.add_account)
  1388.  
  1389.     def add_account(self, widget):
  1390.         """Closes Add Account window and tells the manager to add
  1391.        the account to the database"""
  1392.         username =  self.add_win.txt_user.get_text().strip()
  1393.         password = self.add_win.txt_passwd.get_text()
  1394.         apiiter = self.add_win.cmb_api.get_active_iter()
  1395.  
  1396.         if not username:
  1397.             self.error('Please enter a username.')
  1398.             return
  1399.         if not password:
  1400.             self.error('Please enter a password.')
  1401.             return
  1402.         if not apiiter:
  1403.             self.error('Please select a website.')
  1404.             return
  1405.  
  1406.         api = self.add_win.model_api.get(apiiter, 0)[0]
  1407.         self.add_win.destroy()
  1408.  
  1409.         self.manager.add_account(username, password, api)
  1410.         self._refresh_list()
  1411.  
  1412.     def do_delete(self, widget):
  1413.         selectedid = self.get_selected_id()
  1414.         dele = self.manager.delete_account(selectedid)
  1415.  
  1416.         self._refresh_list()
  1417.  
  1418.     def error(self, msg):
  1419.         md = gtk.MessageDialog(None,
  1420.             gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,
  1421.             gtk.BUTTONS_CLOSE, msg)
  1422.         md.run()
  1423.         md.destroy()
  1424.  
  1425.     def modal_close(self, widget, response_id):
  1426.         widget.destroy()
  1427.  
  1428.     def do_close(self, widget):
  1429.         self.destroy()
  1430.         if not self.switch:
  1431.             gtk.main_quit()
  1432.  
  1433.     def on_delete(self, widget, data):
  1434.         self.do_close(None)
  1435.         return False
  1436.  
  1437. class InfoDialog(gtk.Window):
  1438.     def __init__(self, engine, show):
  1439.         self.engine = engine
  1440.         self.show = show
  1441.  
  1442.         gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
  1443.         self.set_position(gtk.WIN_POS_CENTER)
  1444.         self.set_title('Show Details')
  1445.         self.set_border_width(10)
  1446.  
  1447.         fullbox = gtk.VBox()
  1448.  
  1449.         # Info box
  1450.         info = InfoWidget(engine)
  1451.         info.set_size(600, 500)
  1452.  
  1453.         # Bottom line (buttons)
  1454.         alignment = gtk.Alignment(xalign=1.0)
  1455.         bottombar = gtk.HBox(False, 5)
  1456.  
  1457.         web_button = gtk.Button('Open web')
  1458.         web_button.connect("clicked", self.do_web)
  1459.         close_button = gtk.Button(stock=gtk.STOCK_CLOSE)
  1460.         close_button.connect("clicked", self.do_close)
  1461.  
  1462.         bottombar.pack_start(web_button, False, False, 0)
  1463.         bottombar.pack_start(close_button, False, False, 0)
  1464.         alignment.add(bottombar)
  1465.  
  1466.         fullbox.pack_start(info)
  1467.         fullbox.pack_start(alignment)
  1468.  
  1469.         self.add(fullbox)
  1470.         self.show_all()
  1471.  
  1472.         info.load(show)
  1473.  
  1474.     def do_close(self, widget):
  1475.         self.destroy()
  1476.  
  1477.     def do_web(self, widget):
  1478.         if self.show['url']:
  1479.             webbrowser.open(self.show['url'], 2, True)
  1480.  
  1481.  
  1482. class InfoWidget(gtk.VBox):
  1483.     def __init__(self, engine):
  1484.         gtk.VBox.__init__(self)
  1485.  
  1486.         self.engine = engine
  1487.  
  1488.         # Title line
  1489.         self.w_title = gtk.Label('')
  1490.         self.w_title.set_ellipsize(pango.ELLIPSIZE_END)
  1491.  
  1492.         # Middle line (sidebox)
  1493.         eventbox_sidebox = gtk.EventBox()
  1494.         self.scrolled_sidebox = gtk.ScrolledWindow()
  1495.         self.scrolled_sidebox.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  1496.         sidebox = gtk.HBox()
  1497.  
  1498.         alignment_image = gtk.Alignment(yalign=0.0)
  1499.         self.w_image = ImageView(225, 350)
  1500.         alignment_image.add(self.w_image)
  1501.  
  1502.         self.w_content = gtk.Label()
  1503.  
  1504.         sidebox.pack_start(alignment_image, padding=5)
  1505.         sidebox.pack_start(self.w_content, padding=5)
  1506.  
  1507.         eventbox_sidebox.add(sidebox)
  1508.  
  1509.         self.scrolled_sidebox.add_with_viewport(eventbox_sidebox)
  1510.  
  1511.         self.pack_start(self.w_title, False, False)
  1512.         self.pack_start(self.scrolled_sidebox, padding=5)
  1513.  
  1514.     def set_size(self, w, h):
  1515.         self.scrolled_sidebox.set_size_request(w, h)
  1516.  
  1517.     def load(self, show):
  1518.         self.show = show
  1519.  
  1520.         # Load image
  1521.         imagefile = utils.get_filename('cache', "f_%d.jpg" % show['id'])
  1522.  
  1523.         if os.path.isfile(imagefile):
  1524.             self.w_image.image_show(imagefile)
  1525.         else:
  1526.             self.w_image.pholder_show('Loading...')
  1527.             self.image_thread = ImageTask(self.w_image, show['image'], imagefile)
  1528.             self.image_thread.start()
  1529.  
  1530.         # Start info loading thread
  1531.         threading.Thread(target=self.task_load).start()
  1532.  
  1533.     def task_load(self):
  1534.         # Thread to ask the engine for show details
  1535.  
  1536.         try:
  1537.             self.details = self.engine.get_show_details(self.show)
  1538.         except utils.TrackmaError, e:
  1539.             self.details = None
  1540.             self.details_e = e
  1541.  
  1542.         gobject.idle_add(self._done)
  1543.  
  1544.     def _done(self):
  1545.         if self.details:
  1546.             # Put the returned details into the lines VBox
  1547.             self.w_title.set_text('<span size="14000"><b>{0}</b></span>'.format(cgi.escape(self.details['title'])))
  1548.             self.w_title.set_use_markup(True)
  1549.  
  1550.             detail = list()
  1551.             for line in self.details['extra']:
  1552.                 if line[0] and line[1]:
  1553.                     detail.append("<b>%s</b>\n%s" % (cgi.escape(str(line[0])), cgi.escape(str(line[1]))))
  1554.  
  1555.             self.w_content.set_text("\n\n".join(detail))
  1556.             self.w_content.set_use_markup(True)
  1557.             self.w_content.set_size_request(340, -1)
  1558.  
  1559.             self.show_all()
  1560.         else:
  1561.             self.w_title.set_text('Error while getting details.')
  1562.             if self.details_e:
  1563.                 self.w_content.set_text(self.details_e.message)
  1564.  
  1565.         self.w_content.set_alignment(0, 0)
  1566.         self.w_content.set_line_wrap(True)
  1567.         self.w_content.set_size_request(340, -1)
  1568.  
  1569. class Settings(gtk.Window):
  1570.     def __init__(self, engine, config, configfile):
  1571.         self.engine = engine
  1572.  
  1573.         self.config = config
  1574.         self.configfile = configfile
  1575.  
  1576.         gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
  1577.  
  1578.         self.set_position(gtk.WIN_POS_CENTER)
  1579.         self.set_title('Global Settings')
  1580.         self.set_border_width(10)
  1581.  
  1582.         ### Play Next ###
  1583.         lbl_player = gtk.Label('Media Player')
  1584.         lbl_player.set_size_request(120, -1)
  1585.         self.txt_player = gtk.Entry(4096)
  1586.         playerbrowse_button = gtk.Button('Browse...')
  1587.         playerbrowse_button.connect("clicked", self.do_browse, 'Select player', self.txt_player)
  1588.  
  1589.         header0 = gtk.Label()
  1590.         header0.set_text('<span size="10000"><b>Play Next</b></span>')
  1591.         header0.set_use_markup(True)
  1592.  
  1593.         line0 = gtk.HBox(False, 5)
  1594.         line0.pack_start(lbl_player, False, False, 0)
  1595.         line0.pack_start(self.txt_player, False, False, 0)
  1596.         line0.pack_start(playerbrowse_button, False, False, 0)
  1597.  
  1598.         ### Tracker ###
  1599.  
  1600.         # Labels
  1601.         lbl_process = gtk.Label('Process Name')
  1602.         lbl_process.set_size_request(120, -1)
  1603.         lbl_searchdir = gtk.Label('Search Directory')
  1604.         lbl_searchdir.set_size_request(120, -1)
  1605.         lbl_tracker_enabled = gtk.Label('Enable Tracker')
  1606.         lbl_tracker_enabled.set_size_request(120, -1)
  1607.         lbl_tracker_plex_host_port = gtk.Label('Host and Port')
  1608.         lbl_tracker_plex_host_port.set_size_request(120, -1)
  1609.  
  1610.         # Entries
  1611.         self.txt_process = gtk.Entry(4096)
  1612.         self.txt_searchdir = gtk.Entry(4096)
  1613.         self.browse_button = gtk.Button('Browse...')
  1614.         self.browse_button.connect("clicked", self.do_browse, 'Select search directory', self.txt_searchdir, True)
  1615.         self.chk_tracker_enabled = gtk.CheckButton()
  1616.         self.txt_plex_host = gtk.Entry(4096)
  1617.         self.txt_plex_port = gtk.Entry(5)
  1618.         self.txt_plex_port.set_width_chars(5)
  1619.         self.chk_tracker_enabled.connect("toggled", self.tracker_type_sensitive)
  1620.  
  1621.         # Radio buttons
  1622.         self.rbtn_tracker_local = gtk.RadioButton(None, 'Local')
  1623.         self.rbtn_tracker_plex = gtk.RadioButton(self.rbtn_tracker_local, 'Plex Media Server')
  1624.         self.rbtn_tracker_plex.connect("toggled", self.tracker_type_sensitive)
  1625.         self.rbtn_tracker_local.connect("toggled", self.tracker_type_sensitive)
  1626.  
  1627.         # Buttons
  1628.         alignment = gtk.Alignment(xalign=0.5)
  1629.         bottombar = gtk.HBox(False, 5)
  1630.         self.apply_button = gtk.Button(stock=gtk.STOCK_APPLY)
  1631.         self.apply_button.connect("clicked", self.do_apply)
  1632.         close_button = gtk.Button(stock=gtk.STOCK_CANCEL)
  1633.         close_button.connect("clicked", self.do_close)
  1634.         bottombar.pack_start(self.apply_button, False, False, 0)
  1635.         bottombar.pack_start(close_button, False, False, 0)
  1636.         alignment.add(bottombar)
  1637.  
  1638.         # HBoxes
  1639.         header1 = gtk.Label()
  1640.         header1.set_text('<span size="10000"><b>Tracker Options</b></span>')
  1641.         header1.set_use_markup(True)
  1642.  
  1643.         line1 = gtk.HBox(False, 5)
  1644.         line1.pack_start(lbl_process, False, False, 0)
  1645.         line1.pack_start(self.txt_process, True, True, 0)
  1646.  
  1647.         line2 = gtk.HBox(False, 5)
  1648.         line2.pack_start(lbl_searchdir, False, False, 0)
  1649.         line2.pack_start(self.txt_searchdir, True, True, 0)
  1650.         line2.pack_start(self.browse_button, False, False, 0)
  1651.  
  1652.         line7 = gtk.HBox(False, 5)
  1653.         line7.pack_start(lbl_tracker_plex_host_port, False, False, 0)
  1654.         line7.pack_start(self.txt_plex_host, True, True, 0)
  1655.         line7.pack_start(self.txt_plex_port, True, True, 0)
  1656.  
  1657.         line3 = gtk.HBox(False, 5)
  1658.         line3.pack_start(lbl_tracker_enabled, False, False, 0)
  1659.         line3.pack_start(self.chk_tracker_enabled, False, False, 0)
  1660.         line3.pack_start(self.rbtn_tracker_local, False, False, 0)
  1661.         line3.pack_start(self.rbtn_tracker_plex, False, False, 0)
  1662.  
  1663.         ### Auto-retrieve ###
  1664.         header2 = gtk.Label()
  1665.         header2.set_text('<span size="10000"><b>Auto-retrieve</b></span>')
  1666.         header2.set_use_markup(True)
  1667.  
  1668.         # Radio buttons
  1669.         self.rbtn_autoret_off = gtk.RadioButton(None, 'Disabled')
  1670.         self.rbtn_autoret_always = gtk.RadioButton(self.rbtn_autoret_off, 'Always at start')
  1671.  
  1672.         self.rbtn_autoret_days = gtk.RadioButton(self.rbtn_autoret_off, 'After')
  1673.         self.spin_autoret_days = gtk.SpinButton(gtk.Adjustment(value=3, lower=1, upper=100, step_incr=1, page_incr=10))
  1674.         self.spin_autoret_days.set_sensitive(False)
  1675.         self.rbtn_autoret_days.connect("toggled", self.radio_toggled, self.spin_autoret_days)
  1676.         lbl_autoret_days = gtk.Label('days')
  1677.         line_autoret_days = gtk.HBox(False, 5)
  1678.         line_autoret_days.pack_start(self.rbtn_autoret_days, False, False, 0)
  1679.         line_autoret_days.pack_start(self.spin_autoret_days, False, False, 0)
  1680.         line_autoret_days.pack_start(lbl_autoret_days, False, False, 0)
  1681.  
  1682.         line4 = gtk.VBox(False, 5)
  1683.         line4.pack_start(self.rbtn_autoret_off, False, False, 0)
  1684.         line4.pack_start(self.rbtn_autoret_always, False, False, 0)
  1685.         line4.pack_start(line_autoret_days, False, False, 0)
  1686.  
  1687.         ### Auto-send ###
  1688.         header3 = gtk.Label()
  1689.         header3.set_text('<span size="10000"><b>Auto-send</b></span>')
  1690.         header3.set_use_markup(True)
  1691.  
  1692.         # Radio buttons
  1693.         self.rbtn_autosend_off = gtk.RadioButton(None, 'Disabled')
  1694.         self.rbtn_autosend_always = gtk.RadioButton(self.rbtn_autosend_off, 'After every change')
  1695.         self.rbtn_autosend_at_exit = gtk.CheckButton('Auto-send at exit')
  1696.  
  1697.         self.rbtn_autosend_hours = gtk.RadioButton(self.rbtn_autosend_off, 'After')
  1698.         self.spin_autosend_hours = gtk.SpinButton(gtk.Adjustment(value=5, lower=1, upper=1000, step_incr=1, page_incr=10))
  1699.         self.spin_autosend_hours.set_sensitive(False)
  1700.         self.rbtn_autosend_hours.connect("toggled", self.radio_toggled, self.spin_autosend_hours)
  1701.         lbl_autosend_hours = gtk.Label('hours')
  1702.         line_autosend_hours = gtk.HBox(False, 5)
  1703.         line_autosend_hours.pack_start(self.rbtn_autosend_hours, False, False, 0)
  1704.         line_autosend_hours.pack_start(self.spin_autosend_hours, False, False, 0)
  1705.         line_autosend_hours.pack_start(lbl_autosend_hours, False, False, 0)
  1706.  
  1707.         self.rbtn_autosend_size = gtk.RadioButton(self.rbtn_autosend_off, 'After the queue is larger than')
  1708.         self.spin_autosend_size = gtk.SpinButton(gtk.Adjustment(value=5, lower=1, upper=1000, step_incr=1, page_incr=10))
  1709.         self.spin_autosend_size.set_sensitive(False)
  1710.         self.rbtn_autosend_size.connect("toggled", self.radio_toggled, self.spin_autosend_size)
  1711.         lbl_autosend_size = gtk.Label('entries')
  1712.         line_autosend_size = gtk.HBox(False, 5)
  1713.         line_autosend_size.pack_start(self.rbtn_autosend_size, False, False, 0)
  1714.         line_autosend_size.pack_start(self.spin_autosend_size, False, False, 0)
  1715.         line_autosend_size.pack_start(lbl_autosend_size, False, False, 0)
  1716.  
  1717.         line5 = gtk.VBox(False, 5)
  1718.         line5.pack_start(self.rbtn_autosend_off, False, False, 0)
  1719.         line5.pack_start(self.rbtn_autosend_always, False, False, 0)
  1720.         line5.pack_start(line_autosend_hours, False, False, 0)
  1721.         line5.pack_start(line_autosend_size, False, False, 0)
  1722.         line5.pack_start(self.rbtn_autosend_at_exit, False, False, 0)
  1723.  
  1724.  
  1725.         ### GTK Interface ###
  1726.         header4 = gtk.Label()
  1727.         header4.set_text('<span size="10000"><b>GTK Interface</b></span>')
  1728.         header4.set_use_markup(True)
  1729.  
  1730.         self.chk_show_tray = gtk.CheckButton('Show Tray Icon')
  1731.         self.chk_close_to_tray = gtk.CheckButton('Close to Tray')
  1732.         self.chk_start_in_tray = gtk.CheckButton('Start Minimized to Tray')
  1733.         self.chk_close_to_tray.set_sensitive(False)
  1734.         self.chk_start_in_tray.set_sensitive(False)
  1735.         self.chk_show_tray.connect("toggled", self.radio_toggled, self.chk_close_to_tray)
  1736.         self.chk_show_tray.connect("toggled", self.radio_toggled, self.chk_start_in_tray)
  1737.         line6 = gtk.VBox(False, 5)
  1738.         line6.pack_start(self.chk_show_tray, False, False, 0)
  1739.         line6.pack_start(self.chk_close_to_tray, False, False, 0)
  1740.         line6.pack_start(self.chk_start_in_tray, False, False, 0)
  1741.  
  1742.         # Join HBoxes
  1743.         vbox = gtk.VBox(False, 10)
  1744.         vbox.pack_start(header0, False, False, 0)
  1745.         vbox.pack_start(line0, False, False, 0)
  1746.         vbox.pack_start(header1, False, False, 0)
  1747.         vbox.pack_start(line3, False, False, 0)
  1748.         vbox.pack_start(line1, False, False, 0)
  1749.         vbox.pack_start(line2, False, False, 0)
  1750.         vbox.pack_start(line7, False, False, 0)
  1751.         vbox.pack_start(header2, False, False, 0)
  1752.         vbox.pack_start(line4, False, False, 0)
  1753.         vbox.pack_start(header3, False, False, 0)
  1754.         vbox.pack_start(line5, False, False, 0)
  1755.         vbox.pack_start(header4, False, False, 0)
  1756.         vbox.pack_start(line6, False, False, 0)
  1757.         vbox.pack_start(alignment, False, False, 0)
  1758.  
  1759.         self.add(vbox)
  1760.         self.load_config()
  1761.  
  1762.     def load_config(self):
  1763.         """Engine Configuration"""
  1764.         self.txt_player.set_text(self.engine.get_config('player'))
  1765.         self.txt_process.set_text(self.engine.get_config('tracker_process'))
  1766.         self.txt_searchdir.set_text(self.engine.get_config('searchdir'))
  1767.         self.txt_plex_host.set_text(self.engine.get_config('plex_host'))
  1768.         self.txt_plex_port.set_text(self.engine.get_config('plex_port'))
  1769.         self.chk_tracker_enabled.set_active(self.engine.get_config('tracker_enabled'))
  1770.         self.rbtn_autosend_at_exit.set_active(self.engine.get_config('autosend_at_exit'))
  1771.  
  1772.         if self.engine.get_config('tracker_type') == 'local':
  1773.             self.rbtn_tracker_local.set_active(True)
  1774.             self.txt_plex_host.set_sensitive(False)
  1775.             self.txt_plex_port.set_sensitive(False)
  1776.         elif self.engine.get_config('tracker_type') == 'plex':
  1777.             self.rbtn_tracker_plex.set_active(True)
  1778.             self.txt_process.set_sensitive(False)
  1779.  
  1780.         if self.engine.get_config('autoretrieve') == 'always':
  1781.             self.rbtn_autoret_always.set_active(True)
  1782.         elif self.engine.get_config('autoretrieve') == 'days':
  1783.             self.rbtn_autoret_days.set_active(True)
  1784.  
  1785.         if self.engine.get_config('autosend') == 'always':
  1786.             self.rbtn_autosend_always.set_active(True)
  1787.         elif self.engine.get_config('autosend') == 'hours':
  1788.             self.rbtn_autosend_hours.set_active(True)
  1789.         elif self.engine.get_config('autosend') == 'size':
  1790.             self.rbtn_autosend_size.set_active(True)
  1791.  
  1792.         self.spin_autoret_days.set_value(self.engine.get_config('autoretrieve_days'))
  1793.         self.spin_autosend_hours.set_value(self.engine.get_config('autosend_hours'))
  1794.         self.spin_autosend_size.set_value(self.engine.get_config('autosend_size'))
  1795.  
  1796.         """GTK Interface Configuration"""
  1797.         self.chk_show_tray.set_active(self.config['show_tray'])
  1798.         self.chk_close_to_tray.set_active(self.config['close_to_tray'])
  1799.         self.chk_start_in_tray.set_active(self.config['start_in_tray'])
  1800.  
  1801.     def save_config(self):
  1802.         """Engine Configuration"""
  1803.         self.engine.set_config('player', self.txt_player.get_text())
  1804.         self.engine.set_config('tracker_process', self.txt_process.get_text())
  1805.         self.engine.set_config('searchdir', self.txt_searchdir.get_text())
  1806.         self.engine.set_config('plex_host', self.txt_plex_host.get_text())
  1807.         self.engine.set_config('plex_port', self.txt_plex_port.get_text())
  1808.         self.engine.set_config('tracker_enabled', self.chk_tracker_enabled.get_active())
  1809.         self.engine.set_config('autosend_at_exit', self.rbtn_autosend_at_exit.get_active())
  1810.  
  1811.         # Tracker type
  1812.         if self.rbtn_tracker_local.get_active():
  1813.             self.engine.set_config('tracker_type', 'local')
  1814.         elif self.rbtn_tracker_plex.get_active():
  1815.             self.engine.set_config('tracker_type', 'plex')
  1816.  
  1817.         # Auto-retrieve
  1818.         if self.rbtn_autoret_always.get_active():
  1819.             self.engine.set_config('autoretrieve', 'always')
  1820.         elif self.rbtn_autoret_days.get_active():
  1821.             self.engine.set_config('autoretrieve', 'days')
  1822.         else:
  1823.             self.engine.set_config('autoretrieve', 'off')
  1824.  
  1825.         # Auto-send
  1826.         if self.rbtn_autosend_always.get_active():
  1827.             self.engine.set_config('autosend', 'always')
  1828.         elif self.rbtn_autosend_hours.get_active():
  1829.             self.engine.set_config('autosend', 'hours')
  1830.         elif self.rbtn_autosend_size.get_active():
  1831.             self.engine.set_config('autosend', 'size')
  1832.         else:
  1833.             self.engine.set_config('autosend', 'off')
  1834.  
  1835.         self.engine.set_config('autoretrieve_days', self.spin_autoret_days.get_value_as_int())
  1836.         self.engine.set_config('autosend_hours', self.spin_autosend_hours.get_value_as_int())
  1837.         self.engine.set_config('autosend_size', self.spin_autosend_size.get_value_as_int())
  1838.  
  1839.         self.engine.save_config()
  1840.  
  1841.         """GTK Interface configuration"""
  1842.         self.config['show_tray'] = self.chk_show_tray.get_active()
  1843.  
  1844.         if self.chk_show_tray.get_active():
  1845.             self.config['close_to_tray'] = self.chk_close_to_tray.get_active()
  1846.             self.config['start_in_tray'] = self.chk_start_in_tray.get_active()
  1847.         else:
  1848.             self.config['close_to_tray'] = False
  1849.             self.config['start_in_tray'] = False
  1850.  
  1851.         utils.save_config(self.config, self.configfile)
  1852.  
  1853.     def radio_toggled(self, widget, spin):
  1854.         spin.set_sensitive(widget.get_active())
  1855.  
  1856.     def tracker_type_sensitive(self, widget):
  1857.         if self.chk_tracker_enabled.get_active():
  1858.             if self.rbtn_tracker_local.get_active():
  1859.                 self.txt_process.set_sensitive(True)
  1860.                 self.txt_plex_host.set_sensitive(False)
  1861.                 self.txt_plex_port.set_sensitive(False)
  1862.             elif self.rbtn_tracker_plex.get_active():
  1863.                 self.txt_plex_host.set_sensitive(True)
  1864.                 self.txt_plex_port.set_sensitive(True)
  1865.                 self.txt_process.set_sensitive(False)
  1866.         else:
  1867.             self.txt_process.set_sensitive(False)
  1868.             self.txt_plex_host.set_sensitive(False)
  1869.             self.txt_plex_port.set_sensitive(False)
  1870.  
  1871.     def do_browse(self, widget, title, entry, dironly=False):
  1872.         browsew = gtk.FileChooserDialog(title,
  1873.                                         None,
  1874.                                         gtk.FILE_CHOOSER_ACTION_OPEN,
  1875.                                         (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
  1876.                                         gtk.STOCK_OPEN, gtk.RESPONSE_OK))
  1877.         browsew.set_default_response(gtk.RESPONSE_OK)
  1878.  
  1879.         if dironly:
  1880.             browsew.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
  1881.  
  1882.         response = browsew.run()
  1883.         if response == gtk.RESPONSE_OK:
  1884.             entry.set_text(browsew.get_filename())
  1885.         browsew.destroy()
  1886.  
  1887.     def do_apply(self, widget):
  1888.         self.save_config()
  1889.         self.destroy()
  1890.  
  1891.     def do_close(self, widget):
  1892.         self.destroy()
  1893.  
  1894. class AccountSelectAdd(gtk.Window):
  1895.     def __init__(self, pixbufs):
  1896.         gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
  1897.  
  1898.         self.set_position(gtk.WIN_POS_CENTER)
  1899.         self.set_title('Create Account')
  1900.         self.set_border_width(10)
  1901.  
  1902.         # Labels
  1903.         self.lbl_user = gtk.Label('Username')
  1904.         self.lbl_user.set_size_request(70, -1)
  1905.         self.lbl_passwd = gtk.Label('Password')
  1906.         self.lbl_passwd.set_size_request(70, -1)
  1907.         lbl_api = gtk.Label('Website')
  1908.         lbl_api.set_size_request(70, -1)
  1909.  
  1910.         # Entries
  1911.         self.txt_user = gtk.Entry(128)
  1912.         self.txt_passwd = gtk.Entry(128)
  1913.         self.txt_passwd.set_visibility(False)
  1914.  
  1915.         # Combobox
  1916.         self.model_api = gtk.ListStore(str, str, gtk.gdk.Pixbuf)
  1917.  
  1918.         for (libname, lib) in sorted(utils.available_libs.iteritems()):
  1919.             self.model_api.append([libname, lib[0], pixbufs[libname]])
  1920.  
  1921.         self.cmb_api = gtk.ComboBox(self.model_api)
  1922.         cell_icon = gtk.CellRendererPixbuf()
  1923.         cell_name = gtk.CellRendererText()
  1924.         self.cmb_api.pack_start(cell_icon, False)
  1925.         self.cmb_api.pack_start(cell_name, True)
  1926.         self.cmb_api.add_attribute(cell_icon, 'pixbuf', 2)
  1927.         self.cmb_api.add_attribute(cell_name, 'text', 1)
  1928.         self.cmb_api.connect("changed", self._refresh)
  1929.  
  1930.         # Buttons
  1931.         self.btn_auth = gtk.Button("Request PIN")
  1932.         self.btn_auth.connect("clicked", self.do_auth)
  1933.  
  1934.         alignment = gtk.Alignment(xalign=0.5)
  1935.         bottombar = gtk.HBox(False, 5)
  1936.         self.add_button = gtk.Button(stock=gtk.STOCK_APPLY)
  1937.         close_button = gtk.Button(stock=gtk.STOCK_CLOSE)
  1938.         close_button.connect("clicked", self.do_close)
  1939.         bottombar.pack_start(self.add_button, False, False, 0)
  1940.         bottombar.pack_start(close_button, False, False, 0)
  1941.         alignment.add(bottombar)
  1942.  
  1943.         # HBoxes
  1944.         line1 = gtk.HBox(False, 5)
  1945.         line1.pack_start(self.lbl_user, False, False, 0)
  1946.         line1.pack_start(self.txt_user, True, True, 0)
  1947.  
  1948.         line2 = gtk.HBox(False, 5)
  1949.         line2.pack_start(self.lbl_passwd, False, False, 0)
  1950.         line2.pack_start(self.txt_passwd, True, True, 0)
  1951.         line2.pack_start(self.btn_auth, False, False, 0)
  1952.  
  1953.         line3 = gtk.HBox(False, 5)
  1954.         line3.pack_start(lbl_api, False, False, 0)
  1955.         line3.pack_start(self.cmb_api, True, True, 0)
  1956.  
  1957.         # Join HBoxes
  1958.         vbox = gtk.VBox(False, 10)
  1959.         vbox.pack_start(line3, False, False, 0)
  1960.         vbox.pack_start(line1, False, False, 0)
  1961.         vbox.pack_start(line2, False, False, 0)
  1962.         vbox.pack_start(alignment, False, False, 0)
  1963.  
  1964.         self.add(vbox)
  1965.         self.show_all()
  1966.         self.btn_auth.hide()
  1967.  
  1968.     def _refresh(self, widget):
  1969.         self.txt_user.set_text("")
  1970.         self.txt_passwd.set_text("")
  1971.  
  1972.         apiiter = self.cmb_api.get_active_iter()
  1973.         api = self.model_api.get(apiiter, 0)[0]
  1974.         if utils.available_libs[api][2] == utils.LOGIN_OAUTH:
  1975.             self.lbl_user.set_text("Name")
  1976.             self.lbl_passwd.set_text("PIN")
  1977.             self.txt_passwd.set_visibility(True)
  1978.             self.btn_auth.show()
  1979.         else:
  1980.             self.lbl_user.set_text("Username")
  1981.             self.lbl_passwd.set_text("Password")
  1982.             self.txt_passwd.set_visibility(False)
  1983.             self.btn_auth.hide()
  1984.  
  1985.     def do_auth(self, widget):
  1986.         apiiter = self.cmb_api.get_active_iter()
  1987.         api = self.model_api.get(apiiter, 0)[0]
  1988.         url = utils.available_libs[api][4]
  1989.  
  1990.         webbrowser.open(url, 2, True)
  1991.  
  1992.     def do_close(self, widget):
  1993.         self.destroy()
  1994.  
  1995.  
  1996. class ShowSearch(gtk.Window):
  1997.     def __init__(self, engine):
  1998.         gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
  1999.  
  2000.         self.engine = engine
  2001.  
  2002.         fullbox = gtk.HPaned()
  2003.  
  2004.         self.set_position(gtk.WIN_POS_CENTER)
  2005.         self.set_title('Search')
  2006.         self.set_border_width(10)
  2007.  
  2008.         vbox = gtk.VBox(False, 10)
  2009.  
  2010.         searchbar = gtk.HBox(False, 5)
  2011.         searchbar.pack_start(gtk.Label('Search'), False, False, 0)
  2012.         self.searchtext = gtk.Entry(100)
  2013.         self.searchtext.connect("activate", self.do_search)
  2014.         searchbar.pack_start(self.searchtext, True, True, 0)
  2015.         self.search_button = gtk.Button('Search')
  2016.         self.search_button.connect("clicked", self.do_search)
  2017.         searchbar.pack_start(self.search_button, False, False, 0)
  2018.  
  2019.         sw = gtk.ScrolledWindow()
  2020.         sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  2021.         sw.set_size_request(450, 350)
  2022.  
  2023.         alignment = gtk.Alignment(xalign=1.0)
  2024.         bottombar = gtk.HBox(False, 5)
  2025.         gtk.stock_add([(gtk.STOCK_APPLY, "Add", 0, 0, "")])
  2026.         self.add_button = gtk.Button(stock=gtk.STOCK_APPLY)
  2027.         self.add_button.connect("clicked", self.do_add)
  2028.         self.add_button.set_sensitive(False)
  2029.         close_button = gtk.Button(stock=gtk.STOCK_CLOSE)
  2030.         close_button.connect("clicked", self.do_close)
  2031.         bottombar.pack_start(self.add_button, False, False, 0)
  2032.         bottombar.pack_start(close_button, False, False, 0)
  2033.         alignment.add(bottombar)
  2034.  
  2035.         self.showlist = ShowSearchView()
  2036.         self.showlist.get_selection().connect("changed", self.select_show)
  2037.  
  2038.         sw.add(self.showlist)
  2039.  
  2040.         self.info = InfoWidget(engine)
  2041.         self.info.set_size(400, 350)
  2042.  
  2043.         vbox.pack_start(searchbar, False, False, 0)
  2044.         vbox.pack_start(sw, True, True, 0)
  2045.         vbox.pack_start(alignment, False, False, 0)
  2046.         fullbox.pack1(vbox)
  2047.         fullbox.pack2(self.info)
  2048.         self.add(fullbox)
  2049.  
  2050.     def do_add(self, widget, path=None, view_column=None):
  2051.         # Get show dictionary
  2052.         show = None
  2053.         for item in self.entries:
  2054.             if item['id'] == self.selected_show:
  2055.                 show = item
  2056.                 break
  2057.  
  2058.         if show is not None:
  2059.             try:
  2060.                 self.engine.add_show(show)
  2061.                 #self.do_close()
  2062.             except utils.TrackmaError, e:
  2063.                 self.error_push(e.message)
  2064.  
  2065.     def do_search(self, widget):
  2066.         threading.Thread(target=self.task_search).start()
  2067.  
  2068.     def do_close(self, widget=None):
  2069.         self.destroy()
  2070.  
  2071.     def select_show(self, widget):
  2072.         # Get selected show ID
  2073.         (tree_model, tree_iter) = widget.get_selected()
  2074.         if not tree_iter:
  2075.             self.allow_buttons_push(False, lists_too=False)
  2076.             return
  2077.  
  2078.         self.selected_show = int(tree_model.get(tree_iter, 0)[0])
  2079.         self.info.load(self.showdict[self.selected_show])
  2080.         self.add_button.set_sensitive(True)
  2081.  
  2082.     def task_search(self):
  2083.         self.allow_buttons(False)
  2084.  
  2085.         try:
  2086.             self.entries = self.engine.search(self.searchtext.get_text())
  2087.         except utils.TrackmaError, e:
  2088.             self.entries = []
  2089.             self.error(e.message)
  2090.  
  2091.         self.showdict = dict()
  2092.  
  2093.         gtk.threads_enter()
  2094.         self.showlist.append_start()
  2095.         for show in self.entries:
  2096.             self.showdict[show['id']] = show
  2097.             self.showlist.append(show)
  2098.         self.showlist.append_finish()
  2099.         gtk.threads_leave()
  2100.  
  2101.         self.allow_buttons(True)
  2102.         self.add_button.set_sensitive(False)
  2103.  
  2104.     def allow_buttons_push(self, boolean):
  2105.         self.search_button.set_sensitive(boolean)
  2106.  
  2107.     def allow_buttons(self, boolean):
  2108.         # Thread safe
  2109.         gobject.idle_add(self.allow_buttons_push, boolean)
  2110.  
  2111.     def error(self, msg):
  2112.         # Thread safe
  2113.         gobject.idle_add(self.error_push, msg)
  2114.  
  2115.     def error_push(self, msg):
  2116.         dialog = gtk.MessageDialog(self, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg)
  2117.         dialog.show_all()
  2118.         dialog.connect("response", self.modal_close)
  2119.  
  2120.     def modal_close(self, widget, response_id):
  2121.         widget.destroy()
  2122.  
  2123.  
  2124. class ShowSearchView(gtk.TreeView):
  2125.     def __init__(self):
  2126.         gtk.TreeView.__init__(self)
  2127.  
  2128.         self.cols = dict()
  2129.         i = 0
  2130.         for name in ('Title', 'Type', 'Total'):
  2131.             self.cols[name] = gtk.TreeViewColumn(name)
  2132.             self.cols[name].set_sort_column_id(i)
  2133.             self.append_column(self.cols[name])
  2134.             i += 1
  2135.  
  2136.         #renderer_id = gtk.CellRendererText()
  2137.         #self.cols['ID'].pack_start(renderer_id, False)
  2138.         #self.cols['ID'].add_attribute(renderer_id, 'text', 0)
  2139.  
  2140.         renderer_title = gtk.CellRendererText()
  2141.         self.cols['Title'].pack_start(renderer_title, False)
  2142.         self.cols['Title'].set_resizable(True)
  2143.         #self.cols['Title'].set_expand(True)
  2144.         self.cols['Title'].add_attribute(renderer_title, 'text', 1)
  2145.         self.cols['Title'].add_attribute(renderer_title, 'foreground', 4)
  2146.  
  2147.         renderer_type = gtk.CellRendererText()
  2148.         self.cols['Type'].pack_start(renderer_type, False)
  2149.         self.cols['Type'].add_attribute(renderer_type, 'text', 2)
  2150.  
  2151.         renderer_total = gtk.CellRendererText()
  2152.         self.cols['Total'].pack_start(renderer_total, False)
  2153.         self.cols['Total'].add_attribute(renderer_total, 'text', 3)
  2154.  
  2155.         self.store = gtk.ListStore(str, str, str, str, str)
  2156.         self.set_model(self.store)
  2157.  
  2158.     def append_start(self):
  2159.         self.freeze_child_notify()
  2160.         self.store.clear()
  2161.  
  2162.     def append(self, show):
  2163.         if show['status'] == 1:
  2164.             color = '#0099cc'
  2165.         elif show['status'] == 3:
  2166.             color = '#999900'
  2167.         else:
  2168.             color = None
  2169.  
  2170.         row = [show['id'], show['title'], show['type'], show['total'], color]
  2171.         self.store.append(row)
  2172.  
  2173.     def append_finish(self):
  2174.         self.thaw_child_notify()
  2175.         self.store.set_sort_column_id(1, gtk.SORT_ASCENDING)
  2176.  
  2177. def main():
  2178.     app = Trackma_gtk()
  2179.     try:
  2180.         gtk.gdk.threads_enter()
  2181.         app.main()
  2182.     except utils.TrackmaFatal, e:
  2183.         md = gtk.MessageDialog(None,
  2184.             gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR,
  2185.             gtk.BUTTONS_CLOSE, e.message)
  2186.         md.run()
  2187.         md.destroy()
  2188.     finally:
  2189.         gtk.gdk.threads_leave()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement