def __init__(self): """ Creates the main window. """ # Gtk builder self.builder = gtk.Builder() self.builder.add_from_file(MAIN_GUI_FILE) self.builder.connect_signals(self) # Config and Marks self.config = Config() self.marks = Marks() # Settings window self.settings = Settings(self.config) # Loading the API cache_dir = self.config.get_key("cache_dir") if cache_dir[-1] == os.sep: cache_dir = cache_dir[:-1] self.pycavane = pycavane.Pycavane(cache_dir=cache_dir) # Load the player self.player = Player(self, self.config) # Attributes self.current_show = None self.current_seasson = None self.current_movies = {} # Getting the used widgets self.main_window = self.builder.get_object("mainWindow") self.statusbar_label = self.builder.get_object("statusbarLabel") self.progress_box = self.builder.get_object("progressBox") self.progress = self.builder.get_object("progress") self.progress_label = self.builder.get_object("progressLabel") self.name_filter = self.builder.get_object("nameFilter") self.name_filter_clear = self.builder.get_object("nameFilterClear") self.name_list = self.builder.get_object("nameList") self.name_model = self.name_list.get_model() self.file_viewer = self.builder.get_object("fileViewer") self.file_model = self.file_viewer.get_model() self.mode_combo = self.builder.get_object("modeCombo") self.search_entry = self.builder.get_object("searchEntry") self.search_button = self.builder.get_object("searchButton") self.search_clear = self.builder.get_object("searchClear") self.sidebar = self.builder.get_object("sidebarVbox") self.path_label = self.builder.get_object("pathLabel") self.info_window = self.builder.get_object("infoWindow") self.info_title = self.builder.get_object("infoTitle") self.info_label = self.builder.get_object("infoLabel") self.info_image = self.builder.get_object("infoImage") # Creating a new filter model to allow the user filter the # shows and movies by typing on an entry box self.name_model_filter = self.name_model.filter_new() self.name_model_filter.set_visible_func(generic_visible_func, (self.name_filter, NAME_COLUMN_TEXT)) self.name_list.set_model(self.name_model_filter) # Keyboard shortcuts accel_group = gtk.AccelGroup() key, modifier = gtk.accelerator_parse("<Ctrl>W") accel_group.connect_group(key, modifier, gtk.ACCEL_VISIBLE, lambda a, b, c, d: gtk.main_quit()) key, modifier = gtk.accelerator_parse("<Ctrl>Q") accel_group.connect_group(key, modifier, gtk.ACCEL_VISIBLE, lambda a, b, c, d: gtk.main_quit()) self.main_window.add_accel_group(accel_group) # Now we show the window self.main_window.show_all() # Do the login if necesary user = self.config.get_key("cuevana_user") passwd = self.config.get_key("cuevana_pass") if user and passwd: self.background_task(self.pycavane.login, self.on_login_done, user, passwd, status_message="Login in...") # Setup the basic stuff self.setup()
class GUIHandler: """ Main class, loads the gui and handles all events. """ def __init__(self): """ Creates the main window. """ # Gtk builder self.builder = gtk.Builder() self.builder.add_from_file(MAIN_GUI_FILE) self.builder.connect_signals(self) # Config and Marks self.config = Config() self.marks = Marks() # Settings window self.settings = Settings(self.config) # Loading the API cache_dir = self.config.get_key("cache_dir") if cache_dir[-1] == os.sep: cache_dir = cache_dir[:-1] self.pycavane = pycavane.Pycavane(cache_dir=cache_dir) # Load the player self.player = Player(self, self.config) # Attributes self.current_show = None self.current_seasson = None self.current_movies = {} # Getting the used widgets self.main_window = self.builder.get_object("mainWindow") self.statusbar_label = self.builder.get_object("statusbarLabel") self.progress_box = self.builder.get_object("progressBox") self.progress = self.builder.get_object("progress") self.progress_label = self.builder.get_object("progressLabel") self.name_filter = self.builder.get_object("nameFilter") self.name_filter_clear = self.builder.get_object("nameFilterClear") self.name_list = self.builder.get_object("nameList") self.name_model = self.name_list.get_model() self.file_viewer = self.builder.get_object("fileViewer") self.file_model = self.file_viewer.get_model() self.mode_combo = self.builder.get_object("modeCombo") self.search_entry = self.builder.get_object("searchEntry") self.search_button = self.builder.get_object("searchButton") self.search_clear = self.builder.get_object("searchClear") self.sidebar = self.builder.get_object("sidebarVbox") self.path_label = self.builder.get_object("pathLabel") self.info_window = self.builder.get_object("infoWindow") self.info_title = self.builder.get_object("infoTitle") self.info_label = self.builder.get_object("infoLabel") self.info_image = self.builder.get_object("infoImage") # Creating a new filter model to allow the user filter the # shows and movies by typing on an entry box self.name_model_filter = self.name_model.filter_new() self.name_model_filter.set_visible_func(generic_visible_func, (self.name_filter, NAME_COLUMN_TEXT)) self.name_list.set_model(self.name_model_filter) # Keyboard shortcuts accel_group = gtk.AccelGroup() key, modifier = gtk.accelerator_parse("<Ctrl>W") accel_group.connect_group(key, modifier, gtk.ACCEL_VISIBLE, lambda a, b, c, d: gtk.main_quit()) key, modifier = gtk.accelerator_parse("<Ctrl>Q") accel_group.connect_group(key, modifier, gtk.ACCEL_VISIBLE, lambda a, b, c, d: gtk.main_quit()) self.main_window.add_accel_group(accel_group) # Now we show the window self.main_window.show_all() # Do the login if necesary user = self.config.get_key("cuevana_user") passwd = self.config.get_key("cuevana_pass") if user and passwd: self.background_task(self.pycavane.login, self.on_login_done, user, passwd, status_message="Login in...") # Setup the basic stuff self.setup() def setup(self): """ Sets the default things. """ self.current_seasson = None # Get and set the last used mode last_mode = self.config.get_key("last_mode") if last_mode not in MODES: last_mode = MODE_SHOWS # Set the combobox in the right mode self.mode_combo.set_active(MODES.index(last_mode)) last_mode = last_mode.lower() getattr(self, "set_mode_%s" % last_mode)() def freeze(self, status_message="Loading..."): """ Freezes the gui so the user can't interact with it. """ self.mode_combo.set_sensitive(False) self.name_list.set_sensitive(False) self.name_filter.set_sensitive(False) self.name_filter_clear.set_sensitive(False) self.file_viewer.set_sensitive(False) self.search_entry.set_sensitive(False) self.search_clear.set_sensitive(False) self.search_button.set_sensitive(False) self.set_status_message(status_message) def _unfreeze(self): """ Sets the widgets to be usable. Usually this function shouldn't be called directly but using the decorator @unfreeze but is not completly wrong to use it. """ self.set_status_message("") self.mode_combo.set_sensitive(True) self.name_list.set_sensitive(True) self.name_filter.set_sensitive(True) self.name_filter_clear.set_sensitive(True) self.file_viewer.set_sensitive(True) self.search_entry.set_sensitive(True) self.search_clear.set_sensitive(True) self.search_button.set_sensitive(True) def unfreeze(func): """ Decorator that calls the decorated function and then unfreezes the gui so the user can interact with it. """ def decorate(self, *args, **kwargs): self._unfreeze() args = [self] + list(args) # func is a method so it needs self func(*args, **kwargs) return decorate def set_status_message(self, message): """ Sets the message shown in the statusbar. """ self.statusbar_label.set_label(message) def background_task(self, func, callback, *args, **kwargs): """ Freezes the gui, starts a thread with func and when it's over calls callback with the result. """ status_message = "Loading..." if "status_message" in kwargs: status_message = kwargs["status_message"] del kwargs["status_message"] self.freeze(status_message) GtkThreadRunner(callback, func, *args, **kwargs) @unfreeze def on_login_done(self, err): if err: print err else: # Update the favorites self.background_task(self.pycavane.get_favorite_series, self.update_favorites, status_message="Updating favorites...") def _on_destroy(self, *args): """ Called when the window closes. """ self.save_config() # We kill gtk gtk.main_quit() def _on_mode_change(self, *args): """ Called when the 'mode' combobox changes value. """ mode = self.get_mode() self.config.set_key("last_mode", mode) mode = mode.lower() self.file_model.clear() # Call the corresponding set_mode method getattr(self, "set_mode_%s" % mode)() def _on_name_change(self, *args): """ Called when the user selects a show from the 'name list'. """ selected_text = get_selected_text(self.name_list, NAME_COLUMN_TEXT) self.current_show = selected_text self.path_label.set_text(self.current_show) self.file_model.clear() self.background_task(self.pycavane.seasson_by_show, self.show_seassons, selected_text, status_message="Loading show %s..." % selected_text) def _on_name_filter_change(self, *args): """ Called when the textbox to filter names changes. """ self.name_model_filter.refilter() def _on_name_filter_clear_clicked(self, *args): """ Clears the name filter input. """ self.name_filter.set_text("") def _on_name_button_press(self, view, event): """ Called when the user press any mouse button on the name list. """ if event.button == 3: # 3 it's right click if self.get_mode() == MODE_FAVORITES: popup_menu = self.builder.get_object("nameFavoritesMenu") else: popup_menu = self.builder.get_object("nameShowsMenu") popup_menu.popup(None, None, None, event.button, event.time) def _on_name_add_favorite(self, *args): """ Adds the selected show from favorites. """ selected = get_selected_text(self.name_list, NAME_COLUMN_TEXT) if selected not in self.config.get_key("favorites"): self.config.append_key("favorites", selected) self.background_task(self.pycavane.add_favorite, self.on_finish_favorite, selected, False) def _on_name_remove_favorite(self, *args): """ Removes the selected show from favorites. """ selected = get_selected_text(self.name_list, NAME_COLUMN_TEXT) if selected in self.config.get_key("favorites"): self.config.remove_key("favorites", selected) self.set_mode_favorites() self.background_task(self.pycavane.del_favorite, self.on_finish_favorite, selected, False) @unfreeze def on_finish_favorite(self, *args): pass def _on_file_button_press(self, view, event): """ Called when the user press any mouse button on the file viewer. """ if event.button == 3: popup_menu = self.builder.get_object("fileViewerMenu") selected_text = get_selected_text(self.file_viewer, FILE_VIEW_COLUMN_TEXT) if not selected_text.startswith("Temporada") and \ not selected_text == "..": popup_menu.popup(None, None, None, event.button, event.time) def _on_play_clicked(self, *args): """ Called when the user click on the play context menu item. """ selected_text = get_selected_text(self.file_viewer, FILE_VIEW_COLUMN_TEXT) if selected_text.count(" - "): # It's an episode. I know this is ugly self.open_show(selected_text) else: self.open_movie(selected_text) def _on_download_only_clicked(self, *args): """ Called when the user click on the download only context menu item. """ self._on_download_clicked(None, True) def _on_download_clicked(self, widget, download_only=False): """ Called when the user click on the download and play context menu item. """ chooser = gtk.FileChooserDialog(title="Dowload to...", parent=self.main_window, action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, gtk.RESPONSE_OK)) last_download_dir = self.config.get_key("last_download_directory") chooser.set_current_folder(last_download_dir) response = chooser.run() if response == gtk.RESPONSE_OK: save_to = chooser.get_filename() self.config.set_key("last_download_directory", save_to) selected_text = get_selected_text(self.file_viewer, FILE_VIEW_COLUMN_TEXT) if self.get_mode() == MODE_MOVIES: self.open_movie(selected_text, file_path=save_to, download_only=download_only) else: self.open_show(selected_text, file_path=save_to, download_only=download_only) chooser.destroy() def _on_name_key_press(self, treeview, event): """ Called when the users presses a key on the name filter list. """ acceptedchars = map(chr, range(97, 123)) + map(chr, range(65, 91)) \ + ['0','1','2','3','4','5','6','7','8','9'] key = gtk.gdk.keyval_name(event.keyval) if key in acceptedchars: self.name_filter.set_text(key) self.name_filter.grab_focus() self.name_filter.set_position(len(self.name_filter.get_text())) def save_config(self): """ Saves the config to disk. """ self.config.save() def mark_selected(self, *args): """ Called when the user clicks on Mark item in the context menu. """ selection = self.file_viewer.get_selection() model, iteration = selection.get_selected() selected_text = model.get_value(iteration, FILE_VIEW_COLUMN_TEXT) model.set_value(iteration, FILE_VIEW_COLUMN_PIXBUF, ICON_FILE_MOVIE_MARK) self.marks.add(selected_text) def unmark_selected(self, *args): """ Called when the user clicks on Mark item in the context menu. """ marks = self.marks.get_all() selection = self.file_viewer.get_selection() model, iteration = selection.get_selected() selected_text = model.get_value(iteration, FILE_VIEW_COLUMN_TEXT) if selected_text in marks: model.set_value(iteration, FILE_VIEW_COLUMN_PIXBUF, ICON_FILE_MOVIE) self.marks.remove(selected_text) def open_in_cuevana(self, *args): """ Open selected episode or movie on cuevana website. """ selected_text = get_selected_text(self.file_viewer, FILE_VIEW_COLUMN_TEXT) if selected_text.count(" - "): # It's a serie link = "http://www.cuevana.tv/series/%s/%s/%s/" show = self.current_show season = self.current_seasson try: selected_episode = selected_text.split(" - ", 1)[1] except IndexError: return data = self.pycavane.episode_by_name(selected_episode, show, season) assert data != None show = normalize_string(show) episode = normalize_string(data[2]) webbrowser.open(link % (data[0], show, episode)) else: link = "http://www.cuevana.tv/peliculas/%s/%s/" data = self.pycavane.movie_by_name(selected_text) print data[1] movie = normalize_string(data[1]) print movie webbrowser.open(link % (data[0], movie)) def _on_search_clear_clicked(self, *args): """ Clears the search input. """ self.search_entry.set_text("") def _on_search_activate(self, *args): """ Called when the user does a search. """ # Sets the correct mode self.set_mode_movies() self.mode_combo.set_active(MODES.index(MODE_MOVIES)) query = self.search_entry.get_text() self.current_movies = {} self.background_task(self.pycavane.search_title, self.show_search, query, status_message="Searching movies with title %s..." % query) def _on_open_file(self, widget, path, *args): """ Called when the user double clicks on a file inside the file viewer. """ selected_text = self.file_model[path][1] mode = self.get_mode() if mode == MODE_SHOWS or mode == MODE_FAVORITES: if selected_text.startswith("Temporada"): self.open_seasson(selected_text) else: self.open_show(selected_text) elif mode == MODE_MOVIES: self.open_movie(selected_text) def _on_about_clicked(self, *args): """ Opens the about dialog. """ help_dialog = self.builder.get_object("aboutDialog") help_dialog.run() help_dialog.hide() def _on_open_settings(self, *args): """ Called when the user opens the preferences from the menu. """ self.settings.show() def _on_info_clicked(self, *args): """ Called when click on the context menu info item. """ selected_text = get_selected_text(self.file_viewer, FILE_VIEW_COLUMN_TEXT) if selected_text.startswith("Temporada"): return try: selected_episode = selected_text.split(" - ", 1)[1] except IndexError: return show = self.current_show seasson = self.current_seasson episode_found = self.pycavane.episode_by_name(selected_episode, show, seasson) if not episode_found: return self.background_task(self.pycavane.get_episode_info, self.open_info_window, episode_found) def _on_info_window_close(self, *args): """ Called when the info window is closed. """ self.info_window.hide() @unfreeze def open_info_window(self, info): """ Opens the info window and loads the info. """ title = "%s: %s" % (self.current_show, info[1]) desc = info[2] image_link = info[0] empty_case = gtk.gdk.pixbuf_new_from_file(IMAGE_CASE_EMPTY) self.info_image.set_from_pixbuf(empty_case) self.background_task(self.download_show_image, self.set_info_image, image_link) self.info_title.set_label(title) self.info_label.set_label(desc) self.info_window.show() def download_show_image(self, link): """ Downloads the show image from `link`. """ images_dir = self.config.get_key("images_dir") show = self.current_show.lower() show_file = show.replace(" ", "_") + ".jpg" image_path = images_dir + os.sep + show_file if not os.path.exists(image_path): url_open = urllib.urlopen(link) img = open(image_path, "wb") img.write(url_open.read()) img.close() url_open.close() return image_path @unfreeze def set_info_image(self, image_path): """ Sets the image of the current episode. """ pixbuf = gtk.gdk.pixbuf_new_from_file(image_path) case = gtk.gdk.pixbuf_new_from_file(IMAGE_CASE) width = pixbuf.props.width height = pixbuf.props.height case = case.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR) case.composite(pixbuf, 0, 0, width, height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_HYPER, 255) self.info_image.set_from_pixbuf(pixbuf) @unfreeze def show_shows(self, shows): """ Adds all the shows to the list. """ self.file_model.clear() for _, show_name in shows: self.name_model.append([show_name]) @unfreeze def show_seassons(self, seassons): """ Fills the file viewer with the seassons. """ self.file_model.clear() for _, seasson_name in seassons: self.file_model.append((ICON_FOLDER, seasson_name)) @unfreeze def show_episodes(self, episodes): """ Fills the file viewer with the episodes. """ self.file_model.clear() marks = self.marks.get_all() self.file_model.append((ICON_FOLDER, "..")) for _, episode_number, episode_name in episodes: try: episode_name = "%.2d - %s" % (int(episode_number), episode_name) except ValueError: episode_name = "%s - %s" % (episode_number, episode_name) icon = ICON_FILE_MOVIE if episode_name in marks: icon = ICON_FILE_MOVIE_MARK self.file_model.append((icon, episode_name)) @unfreeze def show_search(self, search_result): """ Fills the file viewer with the movies from the search results. """ self.file_model.clear() marks = self.marks.get_all() search_list, maybe_meant = search_result if maybe_meant: self.set_status_message("Maybe you meant: %s" % maybe_meant) if not search_list: return for result_id, result_name, is_movie in search_list: if is_movie: icon = ICON_FILE_MOVIE if result_name in marks: icon = ICON_FILE_MOVIE_MARK self.current_movies[result_name] = result_id self.file_model.append((icon, result_name)) def open_seasson(self, seasson_text): """ Fills the file viewer with the episodes from the seasson. """ show = self.current_show self.current_seasson = seasson_text self.path_label.set_text(show + " / " + self.current_seasson) self.background_task(self.pycavane.episodes_by_season, self.show_episodes, show, seasson_text) def open_show(self, episode_text, file_path=None, download_only=False): """ Starts the download of the given episode. """ if episode_text == "..": self.background_task(self.pycavane.seasson_by_show, self.show_seassons, self.current_show) return selected_episode = episode_text.split(" - ", 1)[1] show = self.current_show seasson = self.current_seasson episode_found = self.pycavane.episode_by_name(selected_episode, show, seasson) self.background_task(self.player.play, self.on_player_finish, episode_found, file_path=file_path, download_only=download_only) def open_movie(self, movie_text, file_path=None, download_only=False): """ Starts the download of the given movie. """ movie = (self.current_movies[movie_text], movie_text) self.background_task(self.player.play, self.on_player_finish, movie, is_movie=True, file_path=file_path, download_only=download_only) def on_player_finish(self, error): """ Called when the user closes the player. """ self._unfreeze() if error: self.set_status_message(str(error)) def set_mode_shows(self, *args): """ Sets the current mode to shows. """ self.sidebar.show() self.search_entry.set_text("") self.name_filter.set_text("") self.path_label.set_text("") self.name_model.clear() self.background_task(self.pycavane.get_shows, self.show_shows, status_message="Obtaining shows list") def set_mode_movies(self): """ Sets the current mode to movies. """ self.name_model.clear() self.search_entry.grab_focus() self.sidebar.hide() self.path_label.set_text("") self.name_filter.set_text("") def set_mode_favorites(self): """ Sets the current mode to favorites. """ self.sidebar.show() self.search_entry.set_text("") self.path_label.set_text("") self.name_filter.set_text("") self.name_model.clear() for favorite in self.config.get_key("favorites"): self.name_model.append([favorite]) @unfreeze def update_favorites(self, favorites): for fav in favorites: if fav not in self.config.get_key("favorites"): self.config.append_key("favorites", fav) if self.get_mode() == MODE_FAVORITES: self.set_mode_favorites() def get_mode(self): """ Returns the current mode. i.e the value of the mode combobox. The result will be the constant MODE_* (see constants definitions). """ model = self.mode_combo.get_model() active = self.mode_combo.get_active() mode_text = model[active][0] # Poscondition assert mode_text in MODES return mode_text