def combos_config(datos_conexion, objeto, tabla, cod): if tabla == "ajustemotivos": lista = ListStore(int, str, int) elif tabla == "impuestos": lista = ListStore(int, str, float) elif tabla == "monedas_s": lista = ListStore(int, str, int, str, str) elif tabla == "turnos": lista = ListStore(int, str, str, str) else: tipo = str if tabla in ("generos", "tipodocumentos", "unidadmedidas") else int lista = ListStore(tipo, str) objeto.set_model(lista) cell = CellRendererText() objeto.pack_start(cell, True) objeto.add_attribute(cell, 'text', 1) # Mostrar segunda columna conexion = conectar(datos_conexion) cursor = consultar(conexion, "*", tabla, " ORDER BY " + cod) datos = cursor.fetchall() cant = cursor.rowcount conexion.close() lista.clear() for i in range(0, cant): listafila = [datos[i][0], datos[i][1]] if tabla in ("ajustemotivos", "impuestos"): listafila.append(datos[i][2]) elif tabla == "monedas_s": listafila.extend([datos[i][2], datos[i][3], datos[i][4]]) elif tabla == "turnos": listafila.extend([str(datos[i][2]), str(datos[i][3])]) lista.append(listafila)
def getHoursModel(): global HOURS if HOURS is None: HOURS = ListStore(str) HOURS.set_sort_column_id(0, SortType.ASCENDING) for i in range(24): HOURS.append((str(i).zfill(2) + ':00', )) return HOURS
def config_combo_total(self): lista = ListStore(int, str) self.obj("cmb_total").set_model(lista) cell = CellRendererText() self.obj("cmb_total").pack_start(cell, True) self.obj("cmb_total").add_attribute(cell, 'text', 1) lista.append([1, "Entre"]) lista.append([2, "Mayor que..."]) lista.append([3, "Mayor o igual que..."]) lista.append([4, "Menor que..."]) lista.append([5, "Menor o igual que..."])
def create_combo_box(model: Gtk.ListStore = None, labels: List[str] = None) -> Gtk.ComboBox: if model is None: model = Gtk.ListStore(str) if labels is not None: for i in range(len(labels)): model.append([labels[i]]) combo_box = Gtk.ComboBox.new_with_model(model) renderer_text = Gtk.CellRendererText() combo_box.pack_start(renderer_text, True) combo_box.add_attribute(renderer_text, "text", 0) combo_box.set_active(0) return combo_box
def copy_childrens(from_model: Gtk.TreeStore, to_model: Gtk.ListStore, iter_: Gtk.TreeIter, column: int) -> None: childrens = from_model.iter_n_children(iter_) if childrens: for index in range(childrens): children_iter = from_model.iter_nth_child(iter_, index) value = from_model.get_value(children_iter, column) if value: to_model.append([value]) else: log.debug( _("Ignoring value from %s on column %s item %s because value is empty"), children_iter, column, index ) else: value = from_model.get_value(iter_, column) to_model.append([value])
def cmb_it_02_config(self): lista = ListStore(int, str, float) objeto.set_model(lista) cell = CellRendererText() objeto.pack_start(cell, True) objeto.add_attribute(cell, 'text', 2) conexion = Op.conectar(self.nav.datos_conexion) cursor = Op.consultar(conexion, "*", tabla, " ORDER BY " + cod) datos = cursor.fetchall() cant = cursor.rowcount conexion.close() lista.clear() for i in range(0, cant): listafila = [datos[i][0], datos[i][1]] if tabla in ("ajustemotivos", "impuestos"): listafila.append(datos[i][2]) if tabla == "turnos": listafila.extend([str(datos[i][2]), str(datos[i][3])]) lista.append(listafila)
def configurar_combo_pagos(self): lista = ListStore(str) self.obj("cmb_pago").set_model(lista) cell = CellRendererText() self.obj("cmb_pago").pack_start(cell, True) self.obj("cmb_pago").add_attribute(cell, 'text', 0) lista.clear() lista.append(["Mensual"]) lista.append(["Quincenal"]) lista.append(["Semanal"])
def dummy_entries(): store = ListStore(str) store.append(('First entry',)) store.append(('Second entry',)) store.append(('Third entry',)) return store
class PeersTab(Tab): def __init__(self): super(PeersTab, self).__init__('Peers', 'peers_tab', 'peers_tab_label') self.peer_menu = self.main_builder.get_object('menu_peer_tab') component.get('MainWindow').connect_signals(self) self.listview = self.main_builder.get_object('peers_listview') self.listview.props.has_tooltip = True self.listview.connect('button-press-event', self._on_button_press_event) self.listview.connect('query-tooltip', self._on_query_tooltip) # flag, ip, client, downspd, upspd, country code, int_ip, seed/peer icon, progress self.liststore = ListStore(Pixbuf, str, str, int, int, str, float, Pixbuf, float) self.cached_flag_pixbufs = {} self.seed_pixbuf = icon_seeding self.peer_pixbuf = icon_downloading # key is ip address, item is row iter self.peers = {} # Country column column = TreeViewColumn() render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 0) column.set_sort_column_id(5) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(20) column.set_reorderable(True) self.listview.append_column(column) # Address column column = TreeViewColumn(_('Address')) render = CellRendererPixbuf() column.pack_start(render, False) column.add_attribute(render, 'pixbuf', 7) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 1) column.set_sort_column_id(6) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Client column column = TreeViewColumn(_('Client')) render = CellRendererText() column.pack_start(render, False) column.add_attribute(render, 'text', 2) column.set_sort_column_id(2) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Progress column column = TreeViewColumn(_('Progress')) render = CellRendererProgress() column.pack_start(render, True) column.set_cell_data_func(render, cell_data_peer_progress, 8) column.set_sort_column_id(8) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(100) column.set_reorderable(True) self.listview.append_column(column) # Down Speed column column = TreeViewColumn(_('Down Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_down, 3) column.set_sort_column_id(3) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) column.set_reorderable(True) self.listview.append_column(column) # Up Speed column column = TreeViewColumn(_('Up Speed')) render = CellRendererText() column.pack_start(render, False) column.set_cell_data_func(render, cell_data_speed_up, 4) column.set_sort_column_id(4) column.set_clickable(True) column.set_resizable(True) column.set_expand(False) column.set_min_width(50) # Bugfix: Last column needs max_width set to stop scrollbar appearing column.set_max_width(150) column.set_reorderable(True) self.listview.append_column(column) self.listview.set_model(self.liststore) self.load_state() self.torrent_id = None def save_state(self): # Get the current sort order of the view column_id, sort_order = self.liststore.get_sort_column_id() # Setup state dict state = { 'columns': {}, 'sort_id': column_id, 'sort_order': int(sort_order) if sort_order else None, } for index, column in enumerate(self.listview.get_columns()): state['columns'][column.get_title()] = { 'position': index, 'width': column.get_width(), } save_pickled_state_file('peers_tab.state', state) def load_state(self): state = load_pickled_state_file('peers_tab.state') if state is None: return if len(state['columns']) != len(self.listview.get_columns()): log.warning('peers_tab.state is not compatible! rejecting..') return if state['sort_id'] and state['sort_order'] is not None: self.liststore.set_sort_column_id(state['sort_id'], state['sort_order']) for (index, column) in enumerate(self.listview.get_columns()): cname = column.get_title() if cname in state['columns']: cstate = state['columns'][cname] column.set_sizing(TreeViewColumnSizing.FIXED) column.set_fixed_width( cstate['width'] if cstate['width'] > 0 else 10) if state['sort_id'] == index and state[ 'sort_order'] is not None: column.set_sort_indicator(True) column.set_sort_order(state['sort_order']) if cstate['position'] != index: # Column is in wrong position if cstate['position'] == 0: self.listview.move_column_after(column, None) elif (self.listview.get_columns()[cstate['position'] - 1].get_title() != cname): self.listview.move_column_after( column, self.listview.get_columns()[cstate['position'] - 1]) def update(self): # Get the first selected torrent torrent_id = component.get('TorrentView').get_selected_torrents() # Only use the first torrent in the list or return if None selected if len(torrent_id) != 0: torrent_id = torrent_id[0] else: # No torrent is selected in the torrentview self.liststore.clear() return if torrent_id != self.torrent_id: # We only want to do this if the torrent_id has changed self.liststore.clear() self.peers = {} self.torrent_id = torrent_id component.get('SessionProxy').get_torrent_status( torrent_id, ['peers']).addCallback(self._on_get_torrent_status) def get_flag_pixbuf(self, country): if not country.strip(): return None if country not in self.cached_flag_pixbufs: # We haven't created a pixbuf for this country yet try: self.cached_flag_pixbufs[country] = Pixbuf.new_from_file( deluge.common.resource_filename( 'deluge', os.path.join('ui', 'data', 'pixmaps', 'flags', country.lower() + '.png'), )) except Exception as ex: log.debug('Unable to load flag: %s', ex) return None return self.cached_flag_pixbufs[country] def _on_get_torrent_status(self, status): new_ips = set() for peer in status['peers']: new_ips.add(peer['ip']) if peer['ip'] in self.peers: # We already have this peer in our list, so lets just update it row = self.peers[peer['ip']] if not self.liststore.iter_is_valid(row): # This iter is invalid, delete it and continue to next iteration del self.peers[peer['ip']] continue values = self.liststore.get(row, 3, 4, 5, 7, 8) if peer['down_speed'] != values[0]: self.liststore.set_value(row, 3, peer['down_speed']) if peer['up_speed'] != values[1]: self.liststore.set_value(row, 4, peer['up_speed']) if peer['country'] != values[2]: self.liststore.set_value(row, 5, peer['country']) self.liststore.set_value( row, 0, self.get_flag_pixbuf(peer['country'])) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf if icon != values[3]: self.liststore.set_value(row, 7, icon) if peer['progress'] != values[4]: self.liststore.set_value(row, 8, peer['progress']) else: # Peer is not in list so we need to add it # Create an int IP address for sorting purposes if peer['ip'].count(':') == 1: # This is an IPv4 address ip_int = sum( int(byte) << shift for byte, shift in zip( peer['ip'].split(':')[0].split('.'), (24, 16, 8, 0))) peer_ip = peer['ip'] else: # This is an IPv6 address import socket import binascii # Split out the :port ip = ':'.join(peer['ip'].split(':')[:-1]) ip_int = int( binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16) peer_ip = '[%s]:%s' % (ip, peer['ip'].split(':')[-1]) if peer['seed']: icon = self.seed_pixbuf else: icon = self.peer_pixbuf row = self.liststore.append([ self.get_flag_pixbuf(peer['country']), peer_ip, peer['client'], peer['down_speed'], peer['up_speed'], peer['country'], float(ip_int), icon, peer['progress'], ]) self.peers[peer['ip']] = row # Now we need to remove any ips that were not in status["peers"] list for ip in set(self.peers).difference(new_ips): self.liststore.remove(self.peers[ip]) del self.peers[ip] def clear(self): self.liststore.clear() def _on_button_press_event(self, widget, event): """This is a callback for showing the right-click context menu.""" log.debug('on_button_press_event') # We only care about right-clicks if self.torrent_id and event.button == 3: self.peer_menu.popup(None, None, None, None, event.button, event.time) return True def _on_query_tooltip(self, widget, x, y, keyboard_tip, tooltip): is_tooltip, x, y, model, path, _iter = widget.get_tooltip_context( x, y, keyboard_tip) if is_tooltip: country_code = model.get(_iter, 5)[0] if country_code != ' ' and country_code in COUNTRIES: tooltip.set_text(COUNTRIES[country_code]) # widget here is self.listview widget.set_tooltip_cell(tooltip, path, widget.get_column(0), None) return True return False def on_menuitem_add_peer_activate(self, menuitem): """This is a callback for manually adding a peer""" log.debug('on_menuitem_add_peer') builder = Builder() builder.add_from_file( deluge.common.resource_filename( __package__, os.path.join('glade', 'connect_peer_dialog.ui'))) peer_dialog = builder.get_object('connect_peer_dialog') txt_ip = builder.get_object('txt_ip') response = peer_dialog.run() if response: value = txt_ip.get_text() ip, port = parse_ip_port(value) if ip and port: log.info('Adding peer IP: %s port: %s to %s', ip, port, self.torrent_id) client.core.connect_peer(self.torrent_id, ip, port) else: log.error('Error parsing peer "%s"', value) peer_dialog.destroy() return True
class Playlist: def __init__(self, extensionsloader): self.extensions = extensionsloader self.module = {'name': 'Playlist'} self.functions = Functions() self.userconf = ConfigLoader() self.config = {} rpt = self.userconf.config['Playlist']['repeat'] self.config['repeat'] = bool(int(rpt)) shf = self.userconf.config['Playlist']['shuffle'] self.config['shuffle'] = bool(int(shf)) shm = self.userconf.config['Playlist']['shuffle_mode'] self.config['shuffle_mode'] = shm self.lastfm = LastFm() self.playlists_mgmnt = Playlists() self.playlist_content = {} self.playlist_identifier = 0 self.playlist_current = None self.current_playlist_id = 0 self.user_playlists = {} self.last_user_playlist = None self.similar_artists = [] self.is_in_similar_thread = False # Create the Playlist view def launch_playlist(wdg): self.widgets = wdg[1] self.playlist_label = self.widgets.get_object('label_playlist') self.playlist_repeat = self.widgets.get_object('tool-repeat') self.playlist_shuffle = self.widgets.get_object('tool-shuffle') self.playlist_clean = self.widgets.get_object('tool-clean') self.playlist_combo = self.widgets.get_object('combo-playlist') self.playlist_save = self.widgets.get_object('tool-saveplaylist') self.playlist_lyrics = self.widgets.get_object('tool-lyrics') self.playlist_tree = self.widgets.get_object('treeview_playlist') self.playlist_tree.set_headers_visible(False) self.playlist_tree.props.reorderable = True self.playlist_tree.connect('key_press_event', self.key_pressed) self.playlist_tree.connect('row_activated', self.row_activated) self.liststore = ListStore(str, str, str, str, int) self.playlist_tree.set_model(self.liststore) renderer_pix = CellRendererPixbuf() column_pixbuf = TreeViewColumn('1', renderer_pix, icon_name=0) column_pixbuf.set_fixed_width(18) self.playlist_tree.append_column(column_pixbuf) renderer_text = CellRendererText() column_text = TreeViewColumn('2', renderer_text, markup=1) column_text.props.expand = True column_text.props.max_width = 192 self.playlist_tree.append_column(column_text) column_text = TreeViewColumn('3', renderer_text, markup=2) column_text.set_fixed_width(40) self.playlist_tree.append_column(column_text) self.repeat_btn = self.widgets.get_object('tool-repeat') if self.config['repeat']: self.repeat_btn.set_active(True) self.repeat_btn.connect('clicked', self.toggle, 'repeat') self.shuffle_btn = self.widgets.get_object('tool-shuffle') if self.config['shuffle']: self.shuffle_btn.set_active(True) self.shuffle_btn.connect('clicked', self.toggle, 'shuffle') def clean_wdg(widget): # Clean playlist self.clean() # Show popover if self.current_playlist_id > 3: self.clean_btn.set_sensitive(True) self.clean_pop.show_all() self.clean_btn = self.widgets.get_object('tool-clean') self.clean_btn.connect('clicked', clean_wdg) self.clean_pop = Popover.new(self.clean_btn) self.clean_pop.set_size_request(100, 30) gtkpla = join(self.functions.datadir, 'glade', 'plist-del-pop.ui') win = gtk_builder() win.set_translation_domain('bluemindo') win.add_from_file(gtkpla) hbox = win.get_object('box-playlist') lbl = win.get_object('label') lbl.set_text(_('Do you also want to remove the playlist?')) btn = win.get_object('del-btn') btn.set_label(_('Delete')) btn.connect('clicked', self.delete_playlist) self.clean_pop.add(hbox) # Populate combobox self.combolist = ListStore(int, str) self.combobox = self.widgets.get_object('combobox') self.combobox.set_model(self.combolist) self.combobox.set_popup_fixed_width(False) self.combobox.props.expand = False renderer_text = CellRendererText() renderer_text.props.ellipsize = EllipsizeMode.END self.combobox.pack_start(renderer_text, True) self.combobox.set_entry_text_column(1) self.combobox.add_attribute(renderer_text, 'text', 1) self.combolist.append([0, _('Current')]) self.combolist.append([1, _('Top 50 songs')]) self.combolist.append([2, _('Top 10 albums')]) playlists = self.playlists_mgmnt.get_playlists() if len(playlists) > 0: self.combolist.append([3, '']) item_id = 3 playlists.sort(key=lambda it: it[1]) for item in playlists: item_id += 1 self.combolist.append([item_id, item]) self.user_playlists[item_id] = item self.last_user_playlist = item_id def combo_sep(model, iter): if model[iter][0] == 3: return True self.combobox.set_row_separator_func(combo_sep) def on_combo_changed(widget): path = widget.get_active() item_id = self.combolist[path][0] # First, clean the playlist self.clean(None) # Second, populate the playlist if item_id > 0: self.populate(item_id) # Then, update playlist identifier self.current_playlist_id = item_id # Show delete/remove button if the playlist is from the user if item_id > 3: self.clean_btn.set_sensitive(True) self.combobox.set_active(0) self.combobox.connect('changed', on_combo_changed) self.tool_save = self.widgets.get_object('tool-saveplaylist') self.tool_save.connect('clicked', self.save_playlist) self.save_pop = Popover.new(self.tool_save) self.save_pop.set_size_request(100, 30) gtkpla = join(self.functions.datadir, 'glade', 'plist-add-pop.ui') win = gtk_builder() win.set_translation_domain('bluemindo') win.add_from_file(gtkpla) hbox = win.get_object('box-playlist') self.save_pop.add(hbox) self.save_entry = win.get_object('save-entry') self.save_entry.connect('key_press_event', self.save_playlist_key) self.save_btn = win.get_object('save-btn') self.save_btn.connect('clicked', self.save_playlist_button) self.save_btn.set_label(_('Save')) self.clean_btn.set_sensitive(False) self.tool_save.set_sensitive(False) # Acquire the songs tree def acquire_tree(st): self.songs_tree = st self.extensions.connect('OnBluemindoStarted', launch_playlist) self.extensions.connect('OnSongsTreeCreated', acquire_tree) self.extensions.connect('OnSongQueued', self.on_new_song_queued) self.extensions.connect('OnAlbumQueued', self.on_new_album_queued) self.extensions.connect('AskPreviousSong', self.ask_previous_song) self.extensions.connect('AskNextSong', self.ask_next_song) self.extensions.connect('HasStartedSong', self.song_started) def toggle(self, widget, action): if action == 'repeat' and self.config['repeat']: self.config['repeat'] = False self.repeat_btn.set_active(False) self.userconf.update_key('Playlist', 'repeat', str(int(False))) elif action == 'repeat' and not self.config['repeat']: self.config['repeat'] = True self.repeat_btn.set_active(True) self.userconf.update_key('Playlist', 'repeat', str(int(True))) elif action == 'shuffle' and self.config['shuffle']: self.config['shuffle'] = False self.shuffle_btn.set_active(False) self.userconf.update_key('Playlist', 'shuffle', str(int(False))) elif action == 'shuffle' and not self.config['shuffle']: self.config['shuffle'] = True self.shuffle_btn.set_active(True) self.userconf.update_key('Playlist', 'shuffle', str(int(True))) def row_activated(self, widget, path, column): item_iter = self.liststore.get_iter(path) # Get the founded element. item_identifier = self.liststore.get_value(item_iter, 4) current_item = self.playlist_content[item_identifier] # The element is a song. if self.playlist_content[item_identifier].kind == 'song': self.playlist_current = [item_iter, item_identifier, None] self.extensions.load_event('OnPlayNewSong', current_item) # The element is an album. else: self.extensions.load_event('OnAbortPlayback') sng = self.playlist_content[item_identifier].tracks[0] self.playlist_current = [item_iter, item_identifier, 0] self.extensions.load_event('OnPlayNewSong', sng) def key_pressed(self, widget, eventkey): if eventkey.get_keyval()[1] == KEY_Delete: # Delete an item from the playlist selection = self.playlist_tree.get_selection() selected = selection.get_selected_rows() liststore = selected[0] listpath = selected[1] if len(listpath) > 0: selpath = listpath[0] playlist_identifier = liststore[selpath][4] # Are we removing the currently playing item? if self.playlist_current is not None: item_iter, item_path, item_in_album = self.playlist_current if selpath == TreePath.new_from_string(str(item_path)): self.playlist_current = None # Removal del self.playlist_content[playlist_identifier] del liststore[selpath] def clean(self, data=None): self.playlist_content = {} self.playlist_identifier = 0 self.playlist_current = None self.liststore.clear() # Update GUI self.clean_btn.set_sensitive(False) self.tool_save.set_sensitive(False) def populate(self, playlist_id): if playlist_id in (1, 2): # Automatic playlists based on listening stats if playlist_id == 1: tb = 'stats_songs' lm = 50 elif playlist_id == 2: tb = 'stats_albums' lm = 10 result = [] txt = ('select * from %s order by tracks desc limit %u' % (tb, lm)) sql = SQLite() cur = sql.execute(txt) for sg in cur: result.append(sg) sql.close() for item in result: if playlist_id == 1: sng = Song(filename=item[0]) if hasattr(sng, 'title'): self.on_new_song_queued(sng) elif playlist_id == 2: album_name = item[0] for it in self.songs_tree: if album_name in self.songs_tree[it]: self.on_new_album_queued(Album(it, album_name, self.songs_tree)) break elif playlist_id > 3: # User-created playlists user_plist = self.user_playlists[playlist_id] plist = self.playlists_mgmnt.load_playlist(user_plist) for item in plist: sng = Song(filename=item) if hasattr(sng, 'title'): self.on_new_song_queued(sng) def delete_playlist(self, widget): if self.current_playlist_id > 3: user_plist = self.user_playlists[self.current_playlist_id] self.playlists_mgmnt.delete_playlist(user_plist) # Delete the playlist from the list del self.user_playlists[self.current_playlist_id] cblid = 0 for item in self.combolist: if item[0] == self.current_playlist_id: del self.combolist[cblid] break cblid += 1 # Move back to "Current" playlist self.combobox.set_active(0) def save_playlist_key(self, widget, eventkey): if eventkey.get_keyval()[1] == KEY_Return: self.save_playlist_button(None) def save_playlist_button(self, widget): user_entry = self.save_entry.get_text() user_entry = user_entry.replace('\\', '-') user_entry = user_entry.replace('/', '-') user_entry = user_entry.replace('*', '-') user_entry = user_entry.replace('|', '-') if len(user_entry) > 0: rtn_value = self.playlists_mgmnt.create_new_playlist(user_entry) if rtn_value: self.save_pop.hide() # Add playlist to GUI if self.last_user_playlist is None: self.combolist.append([3, '']) self.last_user_playlist = 3 self.last_user_playlist += 1 self.combolist.append([self.last_user_playlist, user_entry]) self.user_playlists[self.last_user_playlist] = user_entry # Write the playlist self.write_playlist(user_entry) # Select the new playlist self.combobox.set_active(self.last_user_playlist) def save_playlist(self, widget): if self.current_playlist_id < 3: # Create a new playlist self.save_entry.set_text('') self.save_pop.show_all() elif self.current_playlist_id > 3: # Update an existing playlist user_plist = self.user_playlists[self.current_playlist_id] self.write_playlist(user_plist) def write_playlist(self, user_plist): plist_content = [] for item in self.liststore: item_id = item[4] plist_content.append(self.playlist_content[item_id]) self.playlists_mgmnt.write_playlist(user_plist, plist_content) def on_new_song_queued(self, song_info): title = song_info.title artist = song_info.artist album = song_info.album filename = song_info.filename length = self.functions.human_length(song_info.length) self.liststore.append(('audio-x-generic-symbolic', '<b>' + self.functions.view_encode(title, 99) + '</b>\n' + self.functions.view_encode(artist), '<span foreground="grey">' + length + '</span>', filename, self.playlist_identifier)) self.playlist_content[self.playlist_identifier] = song_info self.playlist_identifier += 1 # Update GUI self.clean_btn.set_sensitive(True) self.tool_save.set_sensitive(True) def on_new_album_queued(self, album_info): artist = album_info.artist album = album_info.name songs_count = str(len(album_info.tracks)) + ' ♫' self.liststore.append(('media-optical-symbolic', '<b>' + self.functions.view_encode(album, 99) + '</b>\n' + self.functions.view_encode(artist), '<span foreground="grey">' + songs_count + '</span>', '[album]', self.playlist_identifier)) self.playlist_content[self.playlist_identifier] = album_info self.playlist_identifier += 1 # Update GUI self.clean_btn.set_sensitive(True) self.tool_save.set_sensitive(True) def ask_next_song(self, current_song): self.ask_for_a_song(True, current_song) def ask_previous_song(self, current_song): self.ask_for_a_song(False, current_song) def ask_for_a_song(self, next=True, current_song=None): def walk_in_playlist(item_iter, next=True): base_item_iter = item_iter if item_iter is None: # Find first song. item_iter = self.liststore.get_iter_first() elif next: # Find next song. path = self.liststore.get_path(self.playlist_current[0]) path_int = int(path.to_string()) max_id = len(self.playlist_content) - 1 if (path_int + 1 <= max_id): # There is a song to launch! item_iter = self.liststore.get_iter(path_int + 1) else: # There is no song to launch! if not self.config['repeat']: self.extensions.load_event('OnAbortPlayback') return else: item_iter = self.liststore.get_iter_first() elif not next: # Find previous song. path = self.liststore.get_path(self.playlist_current[0]) path_int = int(path.to_string()) max_id = len(self.playlist_content) - 1 if (path_int -1 >= 0): # There is a song to launch. item_iter = self.liststore.get_iter(path_int - 1) else: # There is no song to launch! if not self.config['repeat']: self.extensions.load_event('OnAbortPlayback') return else: item_iter = self.liststore.get_iter_from_string(str(max_id)) # Get the founded element. item_identifier = self.liststore.get_value(item_iter, 4) current_item = self.playlist_content[item_identifier] def launch_founded_item(item_identifier, item_iter, current_item): # The element is a song. if self.playlist_content[item_identifier].kind == 'song': self.playlist_current = [item_iter, item_identifier, None] self.extensions.load_event('OnPlayNewSong', current_item) # The element is an album. else: self.extensions.load_event('OnAbortPlayback') sng = self.playlist_content[item_identifier].tracks[0] sng.rg_mode_guess = 'album' self.playlist_current = [item_iter, item_identifier, 0] self.extensions.load_event('OnPlayNewSong', sng) # Are we currently listening from an album? if base_item_iter is not None: kind = self.playlist_content[self.playlist_current[1]].kind if kind == 'album': base_item_identifier = self.liststore.get_value(base_item_iter, 4) tracks = self.playlist_content[self.playlist_current[1]].tracks max_sng = len(tracks) - 1 if next: if self.playlist_current[2] < max_sng: item_in_album = self.playlist_current[2] + 1 else: return launch_founded_item(item_identifier, item_iter, current_item) elif not next: if self.playlist_current[2] - 1 > -1: item_in_album = self.playlist_current[2] - 1 else: return launch_founded_item(item_identifier, item_iter, current_item) sng = self.playlist_content[base_item_identifier].tracks[item_in_album] sng.rg_mode_guess = 'album' self.playlist_current = [base_item_iter, base_item_identifier, item_in_album] self.extensions.load_event('OnPlayNewSong', sng) return launch_founded_item(item_identifier, item_iter, current_item) if len(self.playlist_content) == 0: # Playlist is empty. if not self.config['shuffle']: # Shuffle disabled: abort playback. self.extensions.load_event('OnAbortPlayback') else: # Shuffle enabled. if self.config['shuffle_mode'] == 'random': # Random mode: seek for shuffle song. self.extensions.load_event('AskShuffleSong') elif self.config['shuffle_mode'] == 'similar': # Similar mode: seek for a similar song. if len(self.similar_artists) == 0: # No similar song founded: seek for any one. self.extensions.load_event('AskShuffleSong') return # Choose one song in the list of similar artists index = randrange(len(self.similar_artists)) artist = self.similar_artists[index] mdb = MusicDatabase(None) songs_list = mdb.load_from_artist(artist) if len(songs_list) > 1: index = randrange(len(songs_list)) song = songs_list[index] sng = Song(filename=song[8]) print ('[SIMILAR] Playing a song from ' + sng.artist) self.extensions.load_event('OnPlayNewSong', sng) else: self.extensions.load_event('AskShuffleSong') else: # Playlist is not empty, walk in it! if self.playlist_current is None: # Currently no current item in playlist, choose the first one! walk_in_playlist(None) else: # The current playling song is in the playlist! if next: walk_in_playlist(self.playlist_current[0]) else: walk_in_playlist(self.playlist_current[0], False) def song_started(self, song): # First, try to download a few similar artists names if self.config['shuffle_mode'] == 'similar': def download_similars(): threads_enter() art = self.lastfm.get_similar_artists(song.artist) self.similar_artists = [] mdb = MusicDatabase(None) for artist in art: if mdb.artist_exists(artist): self.similar_artists.append(artist) self.is_in_similar_thread = False threads_leave() if not self.is_in_similar_thread: self.is_in_similar_thread = True thread = Thread(group=None, target=download_similars, name='similars', args=()) thread.start() # Second, update statistics for this song song.increment_statistics() alb = Album(song.artist, song.album, self.songs_tree) alb.increment_statistics() # Then, highlight currently playing song/album if it's in playlist if self.playlist_current is not None: # Currently playing item is in the playlist item_iter, item_path, item_in_album = self.playlist_current # Remove marker of all items for item in self.liststore: current_label = item[1] if current_label[:2] == '◎ ': item[1] = current_label[2:] # Add marker on one item current_label = self.liststore[item_iter][1] if current_label[:2] != '◎ ': self.liststore[item_iter][1] = '◎ ' + current_label
class AlbumsView: def __init__(self, widgets, extensions): self.widgets = widgets self.extensions = extensions self.functions = Functions() self.userconf = ConfigLoader() self.dblclick = None # Create the IconView self.albumview = self.widgets[1].get_object('albumview') self.albummodel = ListStore(Pixbuf, str, str, str, int, str, str) self.albumview.set_pixbuf_column(0) self.albumview.set_markup_column(1) self.albumview.set_column_spacing(0) self.albumview.set_spacing(0) self.albumview.set_item_width(100) self.albumview.set_property('activate-on-single-click', False) # Add a filter to the ListStore model self.albumfilter = self.albummodel.filter_new(None) self.remove_filter_data() self.matched = False def filter_visible(model, iter, data): usrf = self.widgets[1].get_object('searchentry').get_text() search_a = model.get_value(iter, 3) search_b = model.get_value(iter, 2) pre_result = False if self.matched: usrf_a = self.matched[0] usrf_b = self.matched[1] if usrf_b == 'blm.!ARTIST!': if usrf_a.lower() == search_b.lower(): # Matched an artist pre_result = True else: if (usrf_a.lower() == search_a.lower() and usrf_b.lower() == search_b.lower()): # Matched an album pre_result = True else: if len(model) > 0: if (usrf.lower() in search_a.lower() or usrf.lower() in search_b.lower()): # Found an element (artist or album name is close) pre_result = True else: # No element founded at all, return False anyway return False # Apply filters fdg = self.filter_data['genre'] fdy = self.filter_data['year'] # Filter results by genres if fdg is not None and fdg != model.get_value(iter, 5): pre_result = False # Filter results by years if fdy is not None and fdy != model.get_value(iter, 6): pre_result = False # Return the final result return pre_result self.albumfilter.set_visible_func(filter_visible) self.albumview.set_model(self.albumfilter) # Connect to signals def grab_entry_focus(widget, event): key = event.string if (key.lower() in 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,' 'x,y,z,0,1,2,3,4,5,6,7,8,9'.split(',')): self.widgets[1].get_object('searchentry').grab_focus() self.widgets[1].get_object('searchentry').set_text(key) self.widgets[1].get_object('searchentry').set_position(-1) self.albumview.connect('key-press-event', grab_entry_focus) self.albumview.connect('selection_changed', self.on_selection_changed) def populate_albums(self, albums_tree, albums, songs_tree, search): self.albums_tree = albums_tree self.albums = albums self.songs_tree = songs_tree self.search_entry = search.searchentry # Clear the tree model first self.albummodel.clear() # Show albums in the main explorer view self.album_nf = [] album_id = 0 for alb in self.albums_tree: bdir = join(self.userconf.datadir, 'modules', 'player', 'covers') album = alb['album'] artist = alb['artist'] cover = join(bdir, self.functions.get_hash(album, artist)) if isfile(cover): cover_px = Pixbuf.new_from_file_at_scale(cover, 150, 150, True) else: cover_px = Pixbuf.new_from_file(join(self.functions.datadir, 'image', 'logo_head_big.png')) self.albums[album_id] = Album(artist, album, self.songs_tree) ap = self.albummodel.append([cover_px, '<b>' + self.functions.view_encode(album) + '</b>\n<span foreground="grey">' + self.functions.view_encode(artist) + '</span>', artist, album, album_id, '', '']) album_id += 1 if not isfile(cover): self.album_nf.append([cover, ap, None]) # Check if we have to regenerate thumbnail (cover not found at startup) if len(self.album_nf) > 0: def regenerate_thumb(): new_album = [] for alb in self.album_nf: if isfile(alb[0]): item_iter = alb[1] cover_md5 = md5(open(alb[0], 'rb').read()).hexdigest() if alb[2] == None or alb[2] != cover_md5: cover_px = Pixbuf.new_from_file_at_scale(alb[0], 150, 150, True) self.albummodel.set_value(item_iter, 0, cover_px) alb[2] = cover_md5 new_album.append(alb) else: new_album.append(alb) if len(new_album) > 0: self.album_nf = new_album return True timeout_add(15000, regenerate_thumb) def on_album_matched(self, album): for item in self.albummodel: if item[2] == album.artist and item[3] == album.name: self.on_selection_changed(self.albumview, album) def on_selection_changed(self, icon_view, album=None): popup = Popover.new(self.albumview) popup.set_size_request(810, 240) if album is None: selection = icon_view.get_selected_items() if len(selection) != 1: return path = selection[0] treeiter = self.albumfilter.get_iter(path) isset, path, cell = icon_view.get_cursor() isset, rect = icon_view.get_cell_rect(path, cell) popup.set_pointing_to(rect) album_id = self.albumfilter.get_value(treeiter, 4) album_obj = self.albums[album_id] else: album_obj = album popup.set_relative_to(self.search_entry) # Handle double clicks def empty_dblclick(): self.dblclick = None if self.dblclick is None: self.dblclick = album_obj timeout_add(1000, empty_dblclick) elif self.dblclick == album_obj: self.play(album_obj) return album = album_obj.name artist = album_obj.artist glade_album = join(self.functions.datadir, 'glade', 'albumview.ui') box = gtk_builder() box.set_translation_domain('bluemindo') box.add_from_file(glade_album) popup.add(box.get_object('box1')) box.get_object('label_album').set_text(album) box.get_object('label_artist').set_text(artist) bdir = join(self.userconf.datadir, 'modules', 'player', 'covers') cover = join(bdir, self.functions.get_hash(album, artist)) if isfile(cover): cover_px = Pixbuf.new_from_file_at_scale(cover, 180, 180, True) else: cover_px = Pixbuf.new_from_file(join(self.functions.datadir, 'image', 'logo_head_big.png')) box.get_object('album_cover').set_from_pixbuf(cover_px) def play_album(wdg, album): self.play(album) def queue_album(wdg, album): self.queue(album) def change_cover(wdg, ka, album): artist_name = album.artist album_name = album.name fcdialog = FileChooserDialog( title=_('Change the cover picture for this album'), buttons=(_('Select'), ResponseType.OK)) fcdialog.set_transient_for(self.widgets[0][11]) response = fcdialog.run() if response == ResponseType.OK: filename = fcdialog.get_filename() datadir = self.userconf.datadir hash_a = self.functions.get_hash(album_name, artist_name) pictures_dir = join(datadir, 'modules', 'player', 'covers') album_file = join(pictures_dir, hash_a) copyfile(filename, album_file) new = Pixbuf.new_from_file_at_scale(album_file, 180, 180, True) box.get_object('album_cover').set_from_pixbuf(new) fcdialog.destroy() box.get_object('button_play').connect('clicked', play_album, album_obj) box.get_object('button_add').connect('clicked', queue_album, album_obj) box.get_object('coverevent').connect('button-press-event', change_cover, album_obj) i = 0 a = -1 previous_column = 0 grid_songs = box.get_object('grid_songs') grid_songs.set_size_request(-1, 200) grid_songs.set_column_spacing(5) try: kids = grid_songs.get_children() for kid in kids: grid_songs.remove(kid) except IndexError: pass for song in album_obj.tracks: i += 1 a += 1 def queue(wdg, song): self.queue(song) def play(wdg, song): self.play(song) song_wdg = Box(spacing=0) song_btr = Button() song_btr.connect('clicked', play, song) song_btr.set_relief(ReliefStyle.NONE) song_btr_content = Box(spacing=0) song_btr.add(song_btr_content) song_tr = Label() song_tr.set_markup('<span foreground="grey">' + str(song.track) + '</span>') song_tr.set_width_chars(3) song_btr_content.pack_start(song_tr, False, True, 0) song_ti = Label() song_ti.set_markup('<b>' + self.functions.view_encode(song.title, 22) + '</b>') song_ti.set_alignment(0.0, 0.5) song_ti.set_size_request(190, -1) song_btr_content.pack_start(song_ti, False, False, 0) length = self.functions.human_length(song.length) song_le = Label() song_le.set_markup('<span foreground="grey">' + length + '</span>') song_le.set_width_chars(5) song_btr_content.pack_start(song_le, False, True, 0) song_wdg.pack_start(song_btr, False, False, 0) song_add = Button.new_from_icon_name('list-add-symbolic', 0) song_add.set_property('relief', 2) song_add.set_size_request(14, 14) song_add.connect('clicked', queue, song) song_wdg.pack_start(song_add, False, False, 0) if i <= len(album_obj.tracks)/2: column = 0 previous_column = 0 row = a else: if previous_column == 0: a = 0 column = 1 previous_column = 1 row = a grid_songs.attach(song_wdg, column, row, 1, 1) popup.show_all() def play(self, usrobject): kind = usrobject.kind if kind == 'album': self.extensions.load_event('OnPlayNewAlbum', usrobject) else: self.extensions.load_event('OnPlayNewSong', usrobject) def queue(self, usrobject): kind = usrobject.kind if kind == 'album': self.extensions.load_event('OnAlbumQueued', usrobject) else: self.extensions.load_event('OnSongQueued', usrobject) def generate_filter_data(self, album_data): for element in self.albummodel: artist = element[2] album = element[3] datalb = album_data[artist][album] datalb_genre = datalb['genre'] datalb_year = datalb['year'] element[5] = datalb_genre element[6] = datalb_year def add_filter_data(self, value, field): self.filter_data[field] = value self.albumfilter.refilter() def remove_filter_data(self, cmb=None): if cmb is None: self.filter_data = {'genre': None, 'year': None} else: self.filter_data[cmb] = None self.albumfilter.refilter()
class QueuedTorrents(component.Component): def __init__(self): component.Component.__init__( self, 'QueuedTorrents', depend=['StatusBar', 'AddTorrentDialog'] ) self.queue = [] self.status_item = None self.config = ConfigManager('gtk3ui.conf') self.builder = Builder() self.builder.add_from_file( deluge.common.resource_filename( __package__, os.path.join('glade', 'queuedtorrents.ui') ) ) self.builder.get_object('chk_autoadd').set_active(self.config['autoadd_queued']) self.dialog = self.builder.get_object('queued_torrents_dialog') self.dialog.set_icon(get_logo(32)) self.builder.connect_signals(self) self.treeview = self.builder.get_object('treeview') self.treeview.append_column( TreeViewColumn(_('Torrent'), CellRendererText(), text=0) ) self.liststore = ListStore(str, str) self.treeview.set_model(self.liststore) self.treeview.set_tooltip_column(1) def run(self): self.dialog.set_transient_for(component.get('MainWindow').window) self.dialog.show() def start(self): if len(self.queue) == 0: return # Make sure status bar info is showing self.update_status_bar() # We only want the add button sensitive if we're connected to a host self.builder.get_object('button_add').set_sensitive(True) if self.config['autoadd_queued'] or self.config['standalone']: self.on_button_add_clicked(None) else: self.run() def stop(self): # We only want the add button sensitive if we're connected to a host self.builder.get_object('button_add').set_sensitive(False) self.update_status_bar() def add_to_queue(self, torrents): """Adds the list of torrents to the queue""" # Add to the queue while removing duplicates self.queue = list(set(self.queue + torrents)) # Update the liststore self.liststore.clear() for torrent in self.queue: if deluge.common.is_magnet(torrent): magnet = deluge.common.get_magnet_info(torrent) self.liststore.append([magnet['name'], torrent]) else: self.liststore.append([os.path.split(torrent)[1], torrent]) # Update the status bar self.update_status_bar() def update_status_bar(self): """Attempts to update status bar""" # If there are no queued torrents.. remove statusbar widgets and return if len(self.queue) == 0: if self.status_item is not None: component.get('StatusBar').remove_item(self.status_item) self.status_item = None return False try: component.get('StatusBar') except Exception: # The statusbar hasn't been loaded yet, so we'll add a timer to # update it later. timeout_add(100, self.update_status_bar) return False # Set the label text for statusbar if len(self.queue) > 1: label = str(len(self.queue)) + _(' Torrents Queued') else: label = str(len(self.queue)) + _(' Torrent Queued') # Add the statusbar items if needed, or just modify the label if they # have already been added. if self.status_item is None: self.status_item = component.get('StatusBar').add_item( icon='view-sort-descending', text=label, callback=self.on_statusbar_click, ) else: self.status_item.set_text(label) # We return False so the timer stops return False def on_statusbar_click(self, widget, event): log.debug('on_statusbar_click') self.run() def on_button_remove_clicked(self, widget): selected = self.treeview.get_selection().get_selected()[1] if selected is not None: path = self.liststore.get_value(selected, 1) self.liststore.remove(selected) self.queue.remove(path) self.update_status_bar() def on_button_clear_clicked(self, widget): self.liststore.clear() del self.queue[:] self.update_status_bar() def on_button_close_clicked(self, widget): self.dialog.hide() def on_button_add_clicked(self, widget): # Add all the torrents in the liststore def add_torrent(model, path, _iter, data): torrent_path = deluge.common.decode_bytes(model.get_value(_iter, 1)) process_args([torrent_path]) self.liststore.foreach(add_torrent, None) del self.queue[:] self.dialog.hide() self.update_status_bar() def on_chk_autoadd_toggled(self, widget): self.config['autoadd_queued'] = widget.get_active()
class Filter: def __init__(self, widgets, aview): self.widgets = widgets self.functions = Functions() self.userconf = ConfigLoader() # GUI self.filter_button = self.widgets[1].get_object('tool-filter') self.filter_button.connect('clicked', self.on_button_clicked) gladefile = join(self.functions.datadir, 'glade', 'filterbar.ui') self.fbox = gtk_builder() self.fbox.set_translation_domain('bluemindo') self.fbox.add_from_file(gladefile) self.filter_box = self.fbox.get_object('infobar') wdg_place = self.widgets[1].get_object('filter-emplacement') wdg_place.add(self.filter_box) self.fbox.get_object('label_filter').set_text(_('Filter the results:')) # Create ComboBoxes self.genre_fstore = ListStore(int, str) self.genre_fcombo = self.fbox.get_object('combobox-genre') self.genre_fcombo.set_model(self.genre_fstore) renderer_text = CellRendererText() self.genre_fcombo.pack_start(renderer_text, True) self.genre_fcombo.set_entry_text_column(1) self.genre_fcombo.add_attribute(renderer_text, 'text', 1) self.year_fstore = ListStore(int, str) self.year_fcombo = self.fbox.get_object('combobox-year') self.year_fcombo.set_model(self.year_fstore) renderer_text = CellRendererText() self.year_fcombo.pack_start(renderer_text, True) self.year_fcombo.set_entry_text_column(1) self.year_fcombo.add_attribute(renderer_text, 'text', 1) def on_button_clicked(self, widget): if self.filter_box.props.visible is True: self.filter_box.hide() else: self.filter_box.show_all() # Reset filters self.genre_fcombo.set_active(0) self.year_fcombo.set_active(0) def launch(self, albums_tree, songs_tree, aview): self.albums_tree = albums_tree self.songs_tree = songs_tree self.aview = aview album_data = {} data_genre = [] data_year = [] # Gather data for item in self.albums_tree: item_artist = item['artist'] item_album = item['album'] if item_artist not in album_data.keys(): album_data[item_artist] = {} album_data[item_artist][item_album] = {} album = Album(item_artist, item_album, self.songs_tree) album_genre = '' album_year = '' for sng in album.tracks: if album_genre == '': album_genre = sng.genre if album_year == '': album_year = sng.year if album_genre != '' and album_genre not in data_genre: data_genre.append(album_genre) if album_year != '' and album_year not in data_year: data_year.append(album_year) album_data[item_artist][item_album]['genre'] = album_genre album_data[item_artist][item_album]['year'] = album_year # Populate combobox self.genre_fstore.clear() self.genre_fstore.append([-2, _('All genres')]) self.genre_fstore.append([-1, '']) data_genre.sort() i = 0 for genre in data_genre: self.genre_fstore.append([i, genre]) i += 1 self.year_fstore.clear() self.year_fstore.append([-2, _('All years')]) self.year_fstore.append([-1, '']) data_year.sort() i = 0 for year in data_year: self.year_fstore.append([i, year]) i += 1 def combo_sep(model, iter): if model[iter][0] == -1: return True self.year_fcombo.set_row_separator_func(combo_sep) self.genre_fcombo.set_row_separator_func(combo_sep) self.aview.generate_filter_data(album_data) self.genre_fcombo.connect('changed', self.on_fcombo_changed, 'genre') self.year_fcombo.connect('changed', self.on_fcombo_changed, 'year') # Hide filters self.filter_box.hide() self.filter_button.set_active(False) def on_fcombo_changed(self, widget, cmb): path = widget.get_active() try: if cmb == 'genre': item_value = self.genre_fstore[path][1] item_key = self.genre_fstore[path][0] else: item_value = self.year_fstore[path][1] item_key = self.year_fstore[path][0] except IndexError: item_key = -42 if item_key >= 0: self.aview.add_filter_data(item_value, cmb) else: self.aview.remove_filter_data(cmb)
class Search: def __init__(self, widgets, aview): self.widgets = widgets self.aview = aview self.albumview = aview.albumview self.albumfilter = aview.albumfilter self.matched = aview.matched self.functions = Functions() self.userconf = ConfigLoader() # Create the autocompletion columns self.completion_model = ListStore(Pixbuf, str, str, str, str, str, str, str) ecomplet = EntryCompletion() ecomplet.set_model(self.completion_model) pixbufcell = CellRendererPixbuf() ecomplet.pack_start(pixbufcell, False) ecomplet.add_attribute(pixbufcell, 'pixbuf', 0) markupcell = CellRendererText() markupcell.props.xpad = 10 ecomplet.pack_start(markupcell, True) ecomplet.add_attribute(markupcell, 'markup', 1) markupcell = CellRendererText() markupcell.props.xpad = 5 ecomplet.pack_start(markupcell, False) ecomplet.add_attribute(markupcell, 'markup', 2) pixbufcell = CellRendererPixbuf() ecomplet.pack_start(pixbufcell, False) ecomplet.add_attribute(pixbufcell, 'icon_name', 3) ecomplet.props.text_column = 4 def matched(widget, model, iter): item = model[iter] data_a = item[4] data_b = item[5] self.aview.matched = [data_a, data_b] self.albumfilter.refilter() if data_b == 'blm.!ARTIST!': # Matched an artist: show albums return False elif exists(data_b): # Matched a song: queue to playlist sng = Song(filename=item[5]) self.aview.queue(sng) # Go back to empty search self.aview.matched = False searchentry = widget.get_entry() searchentry.set_text('') return True #elif len(self.albumfilter) == 1: else: # Matched an album: load it in a panel album = Album(data_b, data_a, self.songs_tree) if hasattr(album, 'name'): self.aview.on_album_matched(album) # Go back to empty search self.aview.matched = False searchentry = widget.get_entry() searchentry.set_text('') return True ecomplet.connect('match-selected', matched) searchentry = self.widgets[1].get_object('searchentry') searchentry.set_completion(ecomplet) searchentry.grab_focus() def do_filter(widget): self.albumfilter.refilter() self.aview.matched = False searchentry.connect('changed', do_filter) self.searchentry = searchentry def generate_autocompletion(self, artists, albums, songs_tree): self.songs_tree = songs_tree albums_without_cover = [] artists_without_picture = [] # Launch autocompletion now that the songs tree is generated def append_autocompletion(name, kind): fnf = join(self.functions.datadir, 'image', 'logo_head_big.png') if kind == 1: # Artist icon = 'face-smile-symbolic' pic = join(self.userconf.datadir, 'modules', 'explorer', 'artists', self.functions.get_hash(name, 'picture')) try: if isfile(pic): pxbf = Pixbuf.new_from_file_at_scale(pic, 70, 70, True) else: pxbf = Pixbuf.new_from_file_at_scale(fnf, 70, 70, True) artists_without_picture.append(name) except GLIBError: pxbf = Pixbuf.new_from_file_at_scale(fnf, 70, 70, True) dname = '<b>' + self.functions.view_encode(name, 99) + '</b>' infos = ('<b>' + _('Artist') + '</b>\n' + _('%s albums in collection.' % ('<b>' + str(len(self.songs_tree[name])) + '</b>'))) add = 'blm.!ARTIST!' add_a = '' add_b = '' elif kind == 2: # Album icon = 'media-optical-symbolic' artist = name[0] name = name[1] cover = join(self.userconf.datadir, 'modules', 'player', 'covers', self.functions.get_hash(name, artist)) if isfile(cover): pxbf = Pixbuf.new_from_file_at_scale(cover, 70, 70, True) else: pxbf = Pixbuf.new_from_file_at_scale(fnf, 70, 70, True) albums_without_cover.append([artist, name]) dname = ('<b>' + self.functions.view_encode(name, 99) + '</b>\n<i>' + self.functions.view_encode(artist, 99) + '</i>') length = 0 songs = 0 for song in self.songs_tree[artist][name]: songs += 1 length += song[7] hlgth = self.functions.human_length(length) infos = (_('%s songs' % ('<b>' + str(songs) + '</b>')) + '\n' + _('Total playing time: %s.' % ('<i>' + hlgth + '</i>'))) self.cur_pxbf = pxbf add = artist add_a = '' add_b = '' elif kind == 3: # Song icon = 'media-record-symbolic' artist = name[1] album = name[2] add = name[8] add_a = artist add_b = album name = name[0] dname = ('<b>' + self.functions.view_encode(name, 99) + '</b>\n<i>' + self.functions.view_encode(artist, 99) + ' - ' + self.functions.view_encode(album, 99) + '</i>') infos = '<b>' + _('Song') + '</b>' pxbf = self.cur_pxbf self.completion_model.append( [pxbf, dname, infos, icon, name, add, add_a, add_b]) self.completion_model.clear() for a in artists: append_autocompletion(a, 1) for a in albums: append_autocompletion(a, 2) for sng in self.songs_tree[a[0]][a[1]]: append_autocompletion(sng, 3) # Retrieve album covers lastfm = LastFm() thread = Thread(group=None, target=lastfm.get_albums_pictures, name='coverart', kwargs={'albums': albums_without_cover}) thread.start() # Retrieve artist pictures thread = Thread(group=None, target=lastfm.get_artists_pictures, name='coverart', kwargs={'artists': artists_without_picture}) thread.start()
class CardsGenerator(Window): def __init__(self, parent: Window, app: Application, col_filename: Filename, vid_filename: Filename, sub_filename: Filename, opt_sub_filename: OptFilename, deck_name: str): super().__init__(title='Asts - Anki Card Generator', application=app, transient_for=parent) self.set_default_size(width=1000, height=700) self.set_keep_above(True) self.set_modal(True) self.set_resizable(False) setBgColor(widget=self, alpha=0.93) self._main_box: Box = Box() self._main_box.set_orientation(Orientation.VERTICAL) setMargin(self._main_box, 10) self.add(self._main_box) self._subtitles_grid: Grid = Grid() setMargin(self._subtitles_grid, 5) # box.pack_(expand, fill, padding) self._main_box.pack_start(self._subtitles_grid, False, True, 0) self._collection_filename: Filename = col_filename self._video_filename: Filename = vid_filename self._subtitles_filename: Filename = sub_filename self._opt_subtitles_filename: OptFilename = opt_sub_filename self._deck_name: str = deck_name self._any_media_toggled: bool = False self._dict_any_media: Dict[str, bool] self._dict_any_change_front: Dict[str, bytes] self._dict_any_change_back: Dict[str, bytes] self._textview_front: TextView self._textview_back: TextView self._textbuffer_front: TextBuffer self._textbuffer_back: TextBuffer self._subtitles_liststore: ListStore self._subtitles_liststore_back: ListStore self._subtitles_treeview: TreeView self._selected_row: TreeSelection self._progress_bar: ProgressBar self._cancel_btn: Button self._generate_btn: Button self._cur_progress: int self._max_tasks: int self._cancel_task: bool self._list_of_sentences: ListSentences self._list_info_medias: List[List[Info]] self._color_tag_names: List[str] # TheadingHandler will utilize these # updating the status for each task tasks # also the sensitive and progress of the progress bar # depends on these. self._futures_list: List[Future] def showAll(self) -> None: """ Draws the cards generator window and it's respective widgets. :return: """ # subtitles tree view self._setSubtitleTreeView() # indice and dialogue cells self._setDialogCells() # start and end timer cells self._setTimerCells() # video, audio and image cells self._setMediasCells() # fills both tree view with the subtitles self._populateListStore() # setting the model after and initializing _selected_row and _dict_any_media # after the liststore being complete initialized self._subtitles_treeview.set_model(self._subtitles_liststore) self._selected_row = self._subtitles_treeview.get_selection() self._selected_row.connect('changed', self._itemSelected) self._dict_any_media = { str(key): False for key in enumerate(self._subtitles_liststore) } # search entry self._setSearchEntry() # sets up the sentence editing related (e.g toolbar, tagging, etc) self._setSentenceRelated() # all color tags are named as it's respective values self._color_tag_names = [ '#9999c1c1f1f1', '#6262a0a0eaea', '#35358484e4e4', '#1c1c7171d8d8', '#1a1a5f5fb4b4', '#8f8ff0f0a4a4', '#5757e3e38989', '#3333d1d17a7a', '#2e2ec2c27e7e', '#2626a2a26969', '#f9f9f0f06b6b', '#f8f8e4e45c5c', '#f6f6d3d32d2d', '#f5f5c2c21111', '#e5e5a5a50a0a', '#ffffbebe6f6f', '#ffffa3a34848', '#ffff78780000', '#e6e661610000', '#c6c646460000', '#f6f661615151', '#eded33333b3b', '#e0e01b1b2424', '#c0c01c1c2828', '#a5a51d1d2d2d', '#dcdc8a8adddd', '#c0c06161cbcb', '#91914141acac', '#81813d3d9c9c', '#616135358383', '#cdcdabab8f8f', '#b5b583835a5a', '#98986a6a4444', '#86865e5e3c3c', '#636345452c2c', '#ffffffffffff', '#f6f6f5f5f4f4', '#dededddddada', '#c0c0bfbfbcbc', '#9a9a99999696', '#777776767b7b', '#5e5e5c5c6464', '#3d3d38384646', '#24241f1f3131', '#000000000000', ] # sets up dictionary used to track the tags used self._initDictionariesTag() # sets up the buttons to select all sentences self._setSelectAll() # sets up the progress bar self._setProgressBar() # cancel and generate buttonsj self._resetFuturesLists() self._setButtons() self.show_all() def _resetFuturesLists(self) -> None: """ Assign a empty list of both lists of futures (futures_setences and futures_medias). :return: """ self._futures_list = [] def _setSearchEntry(self) -> None: """ Connect the changed event for the search_entry object. :return: """ search_entry: SearchEntry = SearchEntry() search_entry.set_halign(Align.END) setMargin(search_entry, 0, 5, 0, 5) self._subtitles_grid.attach(search_entry, 0, 0, 1, 1) search_entry.connect('changed', self.searchIt) def searchIt(self, search_entry: SearchEntry) -> None: """ Searchs over the _subtitles_liststore. :return: """ term_searched: str = search_entry.get_text() for i, term in enumerate(self._subtitles_liststore): if term_searched and term_searched in term[1].lower(): self._subtitles_treeview.set_cursor(i) break def _setSelectAll(self) -> None: """ Sets up widgets to select all sentences. :return: """ grid: Grid = Grid() grid.set_halign(Align.END) self._subtitles_grid.attach(grid, 0, 2, 1, 1) lbl: Label = Label(label='Select all') setMargin(lbl, 5) grid.attach(lbl, 0, 0, 1, 1) all_vid_toggle: CheckButton = CheckButton() all_vid_toggle.set_halign(Align.CENTER) all_vid_toggle.connect('toggled', self._onAllVideosToggled) setMargin(all_vid_toggle, 5) grid.attach(all_vid_toggle, 1, 0, 1, 1) lbl2: Label = Label(label='Videos') setMargin(lbl2, 5) grid.attach(lbl2, 1, 1, 1, 1) all_audio_toggle: CheckButton = CheckButton() all_audio_toggle.set_halign(Align.CENTER) all_audio_toggle.connect('toggled', self._onAllAudiosToggled, all_vid_toggle) setMargin(all_audio_toggle, 5) grid.attach(all_audio_toggle, 2, 0, 1, 1) lbl3: Label = Label(label='Audios') setMargin(lbl3, 5) grid.attach(lbl3, 2, 1, 1, 1) all_img_toggle: CheckButton = CheckButton() all_img_toggle.set_halign(Align.CENTER) all_img_toggle.connect('toggled', self._onAllImagesToggled) setMargin(all_img_toggle, 5) grid.attach(all_img_toggle, 3, 0, 1, 1) lbl4: Label = Label(label='Snapshot') setMargin(lbl4, 5) grid.attach(lbl4, 3, 1, 1, 1) def _onAllVideosToggled(self, _) -> None: """ Handle the toggled event for the ToggleButton object. :param widget: ToggleButton object. :return: """ for i in range(len(self._subtitles_liststore)): if self._subtitles_liststore[i][5]: self._subtitles_liststore[i][ 5] = not self._subtitles_liststore[i][5] self._subtitles_liststore[i][ 4] = not self._subtitles_liststore[i][4] self._dict_any_media[str(i)] = self._subtitles_liststore[i][4] elif self._subtitles_liststore[i][6]: self._subtitles_liststore[i][ 6] = not self._subtitles_liststore[i][6] self._subtitles_liststore[i][ 4] = not self._subtitles_liststore[i][4] self._dict_any_media[str(i)] = self._subtitles_liststore[i][4] else: self._subtitles_liststore[i][ 4] = not self._subtitles_liststore[i][4] self._dict_any_media[str(i)] = self._subtitles_liststore[i][4] if True in self._dict_any_media.values(): self._any_media_toggled = True else: self._any_media_toggled = False def _onAllAudiosToggled(self, _) -> None: """ Handle the toggled event for the ToggleButton object. :param widget: ToggleButton object. :return: """ for i in range(len(self._subtitles_liststore)): if self._subtitles_liststore[i][4]: self._subtitles_liststore[i][ 4] = not self._subtitles_liststore[i][4] self._subtitles_liststore[i][ 5] = not self._subtitles_liststore[i][5] self._dict_any_media[str(i)] = self._subtitles_liststore[i][5] elif self._subtitles_liststore[i][5] and self._subtitles_liststore[ i][6]: self._subtitles_liststore[i][ 5] = not self._subtitles_liststore[i][5] self._dict_any_media[str(i)] = self._subtitles_liststore[i][6] else: self._subtitles_liststore[i][ 5] = not self._subtitles_liststore[i][5] self._dict_any_media[str(i)] = self._subtitles_liststore[i][5] if True in self._dict_any_media.values(): self._any_media_toggled = True else: self._any_media_toggled = False def _onAllImagesToggled(self, _) -> None: """ Handle the toggled event for the ToggleButton object. :param widget: ToggleButton object. :return: """ for i in range(len(self._subtitles_liststore)): if self._subtitles_liststore[i][4]: self._subtitles_liststore[i][ 4] = not self._subtitles_liststore[i][4] self._subtitles_liststore[i][ 6] = not self._subtitles_liststore[i][6] self._dict_any_media[str(i)] = self._subtitles_liststore[i][6] else: self._subtitles_liststore[i][ 6] = not self._subtitles_liststore[i][6] self._dict_any_media[str(i)] = self._subtitles_liststore[i][6] if True in self._dict_any_media.values(): self._any_media_toggled = True else: self._any_media_toggled = False def _initDictionariesTag(self) -> None: """ Init the default values for the used tags. :return: """ # dictionaries to track the tags self._dict_any_change_front = ({ str(key): serializeIt(text_buffer=self._textbuffer_front, tmp_string=value[1]) for key, value in enumerate(self._subtitles_liststore) }) self._dict_any_change_back = ({ str(key): serializeIt(text_buffer=self._textbuffer_back, tmp_string=value[1]) for key, value in enumerate(self._subtitles_liststore_back) }) def _populateListStore(self) -> None: """ Fills both list store (front and back) with subtitles. :return: """ self._subtitles_liststore = ListStore( int, # indice str, # dialogue str, # start timer str, # end timer bool, # whether video is selected bool, # whether audio is selected bool # whether image is selected ) # only the first two values are important here self._subtitles_liststore_back = ListStore(int, str, str, str, bool, bool, bool) dialogues_list: List[List[Info]] = extractAllDialogues( self._subtitles_filename) for dialogue in dialogues_list: self._subtitles_liststore.append(dialogue) if self._opt_subtitles_filename: opt_dialogues_list: List[List[Info]] = extractAllDialogues( self._opt_subtitles_filename) # the subtitles and their respective translations # may or may not be of same lenght # in that case fill the list with dummy values for i in range(len(dialogues_list)): try: self._subtitles_liststore_back.append( opt_dialogues_list[i]) except IndexError: self._subtitles_liststore_back.append( (i, '', '', '', False, False, False)) else: # in case no subtitles was selected for the back list store # fill it with dummy values for i in range(len(dialogues_list)): self._subtitles_liststore_back.append( (i, '', '', '', False, False, False)) def _setTimerCells(self) -> None: """ Arrange the start and end timer cells. :return: """ # Making some cells editable 'Start' and 'End' respectivily editable_start_field: CellRendererText = CellRendererText() editable_end_field: CellRendererText = CellRendererText() editable_start_field.set_property('editable', True) editable_end_field.set_property('editable', True) self._subtitles_treeview.append_column( TreeViewColumn(title='Start', cell_renderer=editable_start_field, text=2)) self._subtitles_treeview.append_column( TreeViewColumn(title='End', cell_renderer=editable_end_field, text=3)) editable_start_field.connect('edited', self._startFieldEdited) editable_end_field.connect('edited', self._endFieldEdited) def _startFieldEdited(self, _, path: TreePath, text: str) -> None: """ Handle the edited event for the start timer field cell. :widget: CellRendererText object. :path: TreePath object. :text: A string to be assigned to subtitles_liststore. :return: """ from re import compile, Pattern regex_timer: Pattern[str] = compile( r'([0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9])') result = regex_timer.findall(text) if result: self._subtitles_liststore[path][2] = result[0] def _endFieldEdited(self, _, path: TreePath, text: str) -> None: """ Handle the edited event for the end timer field cell. :widget: CellRendererText object. :path: TreePath object. :text: A string to be assigned to subtitles_liststore. :return: """ from re import compile, Pattern regex_timer: Pattern[str] = compile( r'([0-9]?[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9])') result: List[str] = regex_timer.findall(text) if result: self._subtitles_liststore[path][3] = result[0] def _setDialogCells(self) -> None: """ Arrange the dialogue and indice cell at the treeview. :return: """ for i, title in enumerate(['Indice', 'Dialog']): renderer: CellRendererText = CellRendererText() path_column: TreeViewColumn = TreeViewColumn( title=title, cell_renderer=renderer, text=i) if title == 'Dialog': path_column.set_sizing(TreeViewColumnSizing.FIXED) path_column.set_fixed_width(520) path_column.set_min_width(520) self._subtitles_treeview.append_column(path_column) def _setMediasCells(self) -> None: """ Arrange the video, audio and snapshot cells. :return: """ # cell video, audio and snapshot to toggle renderer_video_toggle: CellRendererToggle = CellRendererToggle() column_toggle = TreeViewColumn(title='Video', cell_renderer=renderer_video_toggle, active=4) self._subtitles_treeview.append_column(column_toggle) renderer_video_toggle.connect("toggled", self._onCellVideoToggled) renderer_audio_toggle: CellRendererToggle = CellRendererToggle() column_toggle = TreeViewColumn(title='Audio', cell_renderer=renderer_audio_toggle, active=5) self._subtitles_treeview.append_column(column_toggle) renderer_audio_toggle.connect("toggled", self._onCellAudioToggled) renderer_snapshot_toggle: CellRendererToggle = CellRendererToggle() column_toggle = TreeViewColumn(title='Snapshot', cell_renderer=renderer_snapshot_toggle, active=6) self._subtitles_treeview.append_column(column_toggle) renderer_snapshot_toggle.connect("toggled", self._onCellImageToggled) def _onCellVideoToggled(self, _, path) -> None: """ Handles the toggled event for the CellRendererToggle object. :param widget: CellRendererToggle object. :path path: TreePath object. :return: """ if self._subtitles_liststore[path][5]: self._subtitles_liststore[path][ 5] = not self._subtitles_liststore[path][5] self._subtitles_liststore[path][ 4] = not self._subtitles_liststore[path][4] self._dict_any_media[path] = self._subtitles_liststore[path][4] elif self._subtitles_liststore[path][6]: self._subtitles_liststore[path][ 6] = not self._subtitles_liststore[path][6] self._subtitles_liststore[path][ 4] = not self._subtitles_liststore[path][4] self._dict_any_media[path] = self._subtitles_liststore[path][4] else: self._subtitles_liststore[path][ 4] = not self._subtitles_liststore[path][4] self._dict_any_media[path] = self._subtitles_liststore[path][4] if True in self._dict_any_media.values(): self._any_media_toggled = True else: self._any_media_toggled = False def _onCellAudioToggled(self, _, path: str) -> None: """ Handles the toggled event for the CellRendererToggle object. :param widget: CellRendererToggle object. :path path: TreePath object. :return: """ if self._subtitles_liststore[path][4]: self._subtitles_liststore[path][ 4] = not self._subtitles_liststore[path][4] self._subtitles_liststore[path][ 5] = not self._subtitles_liststore[path][5] self._dict_any_media[path] = self._subtitles_liststore[path][5] elif self._subtitles_liststore[path][5] and self._subtitles_liststore[ path][6]: self._subtitles_liststore[path][ 5] = not self._subtitles_liststore[path][5] self._dict_any_media[path] = self._subtitles_liststore[path][6] else: self._subtitles_liststore[path][ 5] = not self._subtitles_liststore[path][5] self._dict_any_media[path] = self._subtitles_liststore[path][5] if True in self._dict_any_media.values(): self._any_media_toggled = True else: self._any_media_toggled = False def _onCellImageToggled(self, _, path: str) -> None: """ Handles the toggled event for the CellRendererToggle object. :param widget: CellRendererToggle object. :path path: TreePath object. :return: """ if self._subtitles_liststore[path][4]: self._subtitles_liststore[path][ 4] = not self._subtitles_liststore[path][4] self._subtitles_liststore[path][ 6] = not self._subtitles_liststore[path][6] self._dict_any_media[path] = self._subtitles_liststore[path][6] elif self._subtitles_liststore[path][6] and self._subtitles_liststore[ path][5]: self._subtitles_liststore[path][ 6] = not self._subtitles_liststore[path][6] self._dict_any_media[path] = self._subtitles_liststore[path][5] else: self._subtitles_liststore[path][ 6] = not self._subtitles_liststore[path][6] self._dict_any_media[path] = self._subtitles_liststore[path][6] if True in self._dict_any_media.values(): self._any_media_toggled = True else: self._any_media_toggled = False def _setSubtitleTreeView(self) -> None: """ Sets the scrolled window and a tree view for subtitles info. :return: """ self._subtitles_treeview = TreeView() self._subtitles_treeview.set_grid_lines(TreeViewGridLines.BOTH) scrl_wnd: ScrolledWindow = ScrolledWindow() scrl_wnd.set_hexpand(True) scrl_wnd.set_vexpand(True) scrl_wnd.add(self._subtitles_treeview) self._subtitles_grid.attach(scrl_wnd, 0, 1, 1, 1) def _itemSelected(self, _) -> None: """ Keeps tracks of selections change at the treeview object. :return: """ path: str = self._selected_row.get_selected_rows()[1][0].to_string() deserializeIt(self._textbuffer_front, self._dict_any_change_front[path]) deserializeIt(self._textbuffer_back, self._dict_any_change_back[path]) self._textbuffer_front.connect('changed', self._editingCard) self._textbuffer_back.connect('changed', self._editingCardBack) def _editingCard(self, textbuffer_front: TextBuffer) -> None: """ Keeps track of changes at the text_buffer_front. :param text_buffer_front: TextBuffer object. :return: """ path: TreePath = self._selected_row.get_selected_rows()[1][0] start_iter_front: TextIter = textbuffer_front.get_start_iter() end_iter_front: TextIter = textbuffer_front.get_end_iter() self._subtitles_liststore[path][1] = textbuffer_front.get_text( start_iter_front, end_iter_front, True) self._dict_any_change_front[path.to_string()] = serializeIt( text_buffer=textbuffer_front) def _editingCardBack(self, textbuffer_back: TextBuffer) -> None: """ Keeps track of changes at the text_buffer_back. :param text_buffer_back: TextBuffer object. :return: """ path: TreePath = self._selected_row.get_selected_rows()[1][0] start_iter_back: TextIter = textbuffer_back.get_start_iter() end_iter_back: TextIter = textbuffer_back.get_end_iter() self._subtitles_liststore_back[path][1] = textbuffer_back.get_text( start_iter_back, end_iter_back, True) self._dict_any_change_back[path.to_string()] = serializeIt( text_buffer=textbuffer_back) def _setSentenceRelated(self) -> None: """ Sets up the sentence editing widgets related. Also initialize both text buffers. :return: """ box: Box = Box() self._main_box.pack_start(box, False, True, 0) box.set_orientation(Orientation.VERTICAL) setMargin(box, 5) toolbar: Toolbar = Toolbar() box.pack_start(toolbar, False, True, 0) toolbar.set_halign(Align.END) setMargin(toolbar, 5) lbl: Label = Label() lbl.set_markup('<i><b>Front</b></i>') box.pack_start(lbl, False, True, 0) lbl.set_halign(Align.START) setMargin(lbl, 5) scrl_wnd: ScrolledWindow = ScrolledWindow() scrl_wnd.set_hexpand(True) scrl_wnd.set_vexpand(True) textview: TextView = TextView() scrl_wnd.add(textview) box.pack_start(scrl_wnd, False, True, 0) self._textbuffer_front = textview.get_buffer() lbl2: Label = Label() lbl2.set_halign(Align.START) lbl2.set_markup('<i><b>Back</b></i>') box.pack_start(lbl2, False, True, 0) setMargin(lbl2, 5) scrl_wnd2: ScrolledWindow = ScrolledWindow() scrl_wnd2.set_hexpand(True) scrl_wnd2.set_vexpand(True) textview2: TextView = TextView() scrl_wnd2.add(textview2) box.pack_end(scrl_wnd2, False, True, 0) self._textbuffer_back = textview2.get_buffer() # this depends on the text buffer to be initialized self._setToolbarColorButton(toolbar) toolbar.insert(SeparatorToolItem(), 3) self._setToolbarUnderlineButton(toolbar) self._setToolbarBoldButton(toolbar) self._setToolbarItalicButton(toolbar) toolbar.insert(SeparatorToolItem(), 7) self._setToolbarTagRemoverButton(toolbar) def _setToolbarColorButton(self, toolbar: Toolbar) -> None: """ Sets up the color button from the toolbar. :param toolbar: Toolbar object :return: """ set_color_button: ToolButton = ToolButton() set_color_button.set_icon_name('gtk-select-color') toolbar.insert(set_color_button, 1) tool_item_color_button: ToolItem = ToolItem() color_button = ColorButton() tool_item_color_button.add(color_button) toolbar.insert(tool_item_color_button, 2) set_color_button.connect('clicked', self._onToolbarColorBtnClicked, color_button) def _setToolbarUnderlineButton(self, toolbar: Toolbar) -> None: """ Sets up the underline button from the toolbar. :param toolbar: Toolbar object :return: """ tag_underline_front: TextTag = self._textbuffer_front.create_tag( 'underline', underline=Underline.SINGLE) tag_underline_back: TextTag = self._textbuffer_back.create_tag( 'underline', underline=Underline.SINGLE) button_underline: ToolButton = ToolButton() button_underline.set_icon_name('format-text-underline-symbolic') toolbar.insert(button_underline, 4) button_underline.connect('clicked', self._onToolbarTagBtnClicked, tag_underline_front, tag_underline_back) def _setToolbarBoldButton(self, toolbar: Toolbar) -> None: """ Sets up the bold button from the toolbar. :param toolbar: Toolbar object :return: """ tag_bold_front: TextTag = self._textbuffer_front.create_tag( 'bold', weight=Weight.BOLD) tag_bold_back: TextTag = self._textbuffer_back.create_tag( 'bold', weight=Weight.BOLD) button_bold: ToolButton = ToolButton() button_bold.set_icon_name('format-text-bold-symbolic') toolbar.insert(button_bold, 5) button_bold.connect('clicked', self._onToolbarTagBtnClicked, tag_bold_front, tag_bold_back) def _setToolbarItalicButton(self, toolbar: Toolbar) -> None: """ Sets up the italic button from the toolbar. :param toolbar: Toolbar object :return: """ tag_italic_front: TextTag = self._textbuffer_front.create_tag( 'italic', style=Style.ITALIC) tag_italic_back: TextTag = self._textbuffer_back.create_tag( 'italic', style=Style.ITALIC) button_italic: ToolButton = ToolButton() button_italic.set_icon_name('format-text-italic-symbolic') toolbar.insert(button_italic, 6) button_italic.connect('clicked', self._onToolbarTagBtnClicked, tag_italic_front, tag_italic_back) def _setToolbarTagRemoverButton(self, toolbar: Toolbar) -> None: """ Sets up the tag remover button from the toolbar. :param toolbar: Toolbar object. :return: """ button_remove_all_tags: ToolButton = ToolButton() button_remove_all_tags.set_icon_name('edit-clear-symbolic') toolbar.insert(button_remove_all_tags, 8) button_remove_all_tags.connect( 'clicked', lambda _: self._removeAllTagsFromSelection()) def _getBounds(self) -> Tuple[TextMark, TextMark, Optional[str]]: """ Returns the selection of the text in the text buffer. :return: A tuple with the textiter of the selection and the path string. """ path: Optional[str] # if no row is selected # a IndexError will be raised try: path = self._selected_row.get_selected_rows()[1][0].to_string() except IndexError: path = None bounds_front: TextMark = self._textbuffer_front.get_selection_bounds() bounds_back: TextMark = self._textbuffer_back.get_selection_bounds() return (bounds_front, bounds_back, path) def _onToolbarColorBtnClicked(self, _, color_button: ColorButton) -> None: """ Handles the clicked event for the tool_item_color_button. :param set_color_button: ToolButton object. :param color_button: ColorButton object. :return: """ start: TextIter end: TextIter bounds_front: TextMark bounds_back: TextMark path: Optional[str] color: str = color_button.get_color().to_string() tag_table_front: TextTagTable = self._textbuffer_front.get_tag_table() tag_table_back: TextTagTable = self._textbuffer_back.get_tag_table() (bounds_front, bounds_back, path) = self._getBounds() # no selected row so there's nothing to do if not path: return ##### FRONT if bounds_front: (start, end) = bounds_front # only the first color applied to the selection # will be present at the final card # so remove all color previously applied to the current selected text. self._removeAllTagsFromSelection(color_tags=True) if not tag_table_front.lookup(color): tag_front: TextTag = self._textbuffer_front.create_tag( color, foreground=color) self._textbuffer_front.apply_tag(tag_front, start, end) else: self._textbuffer_front.apply_tag_by_name(color, start, end) self._dict_any_change_front[path] = serializeIt( text_buffer=self._textbuffer_front) ###### BACK if bounds_back: (start, end) = bounds_back # only the first color applied to the selected text # will be present at the final card # so remove all color previously applied to the current selected text. self._removeAllTagsFromSelection(color_tags=True) if not tag_table_back.lookup(color): tag_back = self._textbuffer_back.create_tag(color, foreground=color) self._textbuffer_back.apply_tag(tag_back, start, end) else: self._textbuffer_back.apply_tag_by_name(color, start, end) self._dict_any_change_back[path] = serializeIt( text_buffer=self._textbuffer_back) def _onToolbarTagBtnClicked(self, _, tag_front: TextTag, tag_back: TextTag) -> None: """ Handles the clicked event for the tool button. :param widget: ToolButton object. :param tag_front: TextTag object. :param tag_back: TextTag object. :return: """ start: TextIter end: TextIter bounds_front: TextMark bounds_back: TextMark path: Optional[str] (bounds_front, bounds_back, path) = self._getBounds() # no selected row so there's nothing to do if not path: return ##### FRONT if bounds_front: (start, end) = bounds_front self._textbuffer_front.apply_tag(tag_front, start, end) self._dict_any_change_front[path] = serializeIt( text_buffer=self._textbuffer_front) ###### BACK if bounds_back: (start, end) = bounds_back self._textbuffer_back.apply_tag(tag_back, start, end) self._dict_any_change_back[path] = serializeIt( text_buffer=self._textbuffer_back) def _removeAllTagsFromSelection(self, color_tags: bool = False) -> None: """ Remove all tags from the current selected text. :param color_tags: If true only removes color tags. :return: """ start: TextIter end: TextIter bounds_front: TextMark bounds_back: TextMark path: Optional[str] tag_table_front: TextTagTable = self._textbuffer_front.get_tag_table() tag_table_back: TextTagTable = self._textbuffer_back.get_tag_table() (bounds_front, bounds_back, path) = self._getBounds() # no selected row so there's nothing to do if not path: return ### FRONT if bounds_front: (start, end) = bounds_front if color_tags: for c in self._color_tag_names: if tag_table_front.lookup(c): self._textbuffer_front.remove_tag_by_name( c, start, end) else: self._textbuffer_front.remove_all_tags(start, end) self._dict_any_change_front[path] = serializeIt( text_buffer=self._textbuffer_front) ### BACK if bounds_back: (start, end) = bounds_back if color_tags: for c in self._color_tag_names: if tag_table_back.lookup(c): self._textbuffer_back.remove_tag_by_name(c, start, end) else: self._textbuffer_back.remove_all_tags(start, end) self._dict_any_change_back[path] = serializeIt( text_buffer=self._textbuffer_back) def _setProgressBar(self) -> None: """ Sets up the progress bar. :return: """ self._cur_progress = 0 self._progress_bar = ProgressBar() setMargin(self._progress_bar, 5) self._main_box.pack_start(self._progress_bar, False, True, 0) def _setButtons(self) -> None: """ Sets up the cancel and generate buttons. :return: """ box: Box = Box() self._main_box.pack_end(box, False, True, 0) box.set_halign(Align.CENTER) box.set_orientation(Orientation.HORIZONTAL) setMargin(box, 5) self._cancel_btn = Button(label='Cancel') box.pack_start(self._cancel_btn, False, True, 0) setMargin(self._cancel_btn, 5, 5, 100, 5) self._cancel_btn.connect('clicked', self._onCancelBtnClicked) self._generate_btn = Button(label='Generate') box.pack_end(self._generate_btn, False, True, 0) setMargin(self._generate_btn, 100, 5, 5, 5) self._generate_btn.connect('clicked', self._onGenerateBtnClicked) timeout_add(300, self._setSensitiveGenerateBtn) def _setSensitiveGenerateBtn(self) -> bool: """ Set the senstive for the generate_btn. :return: A boolean to signal whether idle_add should remove it from list event. """ if self._cur_progress or not self._allFuturesDone(): self._generate_btn.set_sensitive(False) elif not self._any_media_toggled: self._generate_btn.set_sensitive(False) else: self._generate_btn.set_sensitive(True) return True def _allFuturesDone(self) -> bool: """ Check for the status of futures. :return: Return true if all futures are done. """ for f in self._futures_list: if not f.done(): return False return True def _updateProgress(self) -> bool: """ Keep track of the objects yet to be completed. Updates the progress bar. :param future: Parameter passed by add_done_callback. :return: a boolean to signal whether idle_add should remove it from list event. """ if not self.getCancelTaskStatus(): self._cur_progress += 1 self._progress_bar.set_fraction(self._cur_progress / self._max_tasks) self._progress_bar.set_text(None) self._progress_bar.set_show_text(True) if self._cur_progress == self._max_tasks: self._cur_progress = 0 self._progress_bar.set_text('Done!') self._progress_bar.set_show_text(True) return False def resetProgressbar(self) -> None: """ Resets the progress bar back to zero. :return: """ self._cur_progress = 0 self._progress_bar.set_fraction(self._cur_progress) self._progress_bar.set_show_text(False) def idleaddUpdateProgress(self, _) -> None: """ Call idle_add to call updateProgress. :param future: Optional future object. :return: """ idle_add(self._updateProgress) def getCancelTaskStatus(self) -> bool: """ Get the status for the cancel_task. :return: Return true if the task should be cancelled. """ return self._cancel_task def setCancelTaskStatus(self, status: bool) -> None: """ Set the status for the cancel_task. :return: """ self._cancel_task = status def _idleaddUpdateProgress(self, _) -> None: """ Call idle_add to call updateProgress. :param future: Optional future object. :return: """ idle_add(self._updateProgress) def _setSensitiveCancelBtn(self) -> bool: """ Set the sensitive for snd_cancel_button. :return: """ if self._allFuturesDone(): self._progress_bar.set_text('Canceled!') self._cancel_btn.set_sensitive(True) return False else: self._progress_bar.set_text('Cancelling please wait...') self._cancel_btn.set_sensitive(False) return True def _onCancelBtnClicked(self, _) -> None: """ Handle the clicked event for the second_cancel_button button. :param widget: Button object. :return: """ if not self._cur_progress: self._generate_btn.set_sensitive(True) self.close() else: self.setCancelTaskStatus(True) self._cur_progress = 0 self._progress_bar.set_fraction(self._cur_progress) self._progress_bar.set_show_text(True) timeout_add(300, self._setSensitiveCancelBtn) self._cur_progress = 0 self._progress_bar.set_fraction(self._cur_progress) def _onGenerateBtnClicked(self, _) -> None: """ Handle the click event for the generate_btn. :return: """ from asts.Threading import ThreadingHandler self._listMediasSentences() ThreadingHandler(self) def _listMediasSentences(self) -> None: """ Create two lists and fill them with filenames and sentences. :return: """ from uuid import uuid1 from asts.Utils import PangoToHtml # case other tasks already had been scheduled self._resetFuturesLists() self._list_of_sentences = [] self._list_info_medias = [] p: PangoToHtml = PangoToHtml() for i in range(len(self._subtitles_liststore)): if self._subtitles_liststore[i][4] or self._subtitles_liststore[i][ 5] or self._subtitles_liststore[i][6]: # a unique id for each media, some images will conflict if it has the same name as a image # on anki media collection uuid_media = uuid1().int text_front: str = p.feed(self._dict_any_change_front[str(i)]) text_back: str = p.feed(self._dict_any_change_back[str(i)]) self._list_info_medias.append( [str(uuid_media)] + (self._subtitles_liststore[i][1:])) if self._subtitles_liststore[i][ 4] and not self._subtitles_liststore[i][6]: self._list_of_sentences.append( (text_front, text_back, f'{uuid_media}.mp4', None, None)) elif self._subtitles_liststore[i][ 5] and self._subtitles_liststore[i][6]: self._list_of_sentences.append( (text_front, text_back, None, f'{uuid_media}.mp3', f'{uuid_media}.bmp')) elif self._subtitles_liststore[i][ 5] and not self._subtitles_liststore[i][6]: self._list_of_sentences.append( (text_front, text_back, None, f'{uuid_media}.mp3', None)) else: self._list_of_sentences.append( (text_front, text_back, None, None, f'{uuid_media}.bmp')) self._max_tasks = len(self._list_info_medias) + len( self._list_of_sentences) def getCollection(self) -> Filename: """ Returns the filename for the anki2.collection. :return: Filename of the anki2.collection. """ return self._collection_filename def getDeckName(self) -> str: """ Returns the deck name. :return: Deck name. """ return self._deck_name def getVideoFilename(self) -> Filename: """ Returns the name of the video file. :return: Video filename. """ return self._video_filename def getListInfoMedias(self) -> List[List[Info]]: """ Returns a list with information about each media to be used at creating cards. :return: A list with information about each media. """ return self._list_info_medias def getListOfSentences(self) -> ListSentences: """ Returns a List with information about each sentence to be used at creating cards. :return: A list with information about each sentence. """ return self._list_of_sentences def appendFuture(self, future: Future) -> None: """ Append the future to _futures_list. :return: """ self._futures_list.append(future)
class Model: """ Holds the application's data, as well as operations on them. The Model consists of four sub-models that correspond to the four main window parts: Flow Schedule, Zones, Zone Inspector and Playlists. """ def __init__(self): """ Initialize sub-models. Do not initialize the zoneInspector sub-model, because no zone exists on application startup. """ # Weekly Schedule Model self.schedule = {} for dayIndex in range(7): self.schedule[dayIndex] = ListStore(str, str) self.schedule[dayIndex].set_sort_column_id(0, SortType.ASCENDING) # Zone Model self.zones = ListStore(str, str, str, str) self.zones.set_sort_column_id(0, SortType.ASCENDING) # Zone Inspector Model self.zoneInspector = {} # Playlist Model self.playlists = ListStore(str, str) self.playlists.set_sort_column_id(0, SortType.ASCENDING) # Public methods def addZoneToDatabase(self, zoneName, zoneMaintainers='', zoneDescription='', zoneComments=''): """ Add a zone to the database. Subsequently, initialize its inspector. """ self.zones.append( (zoneName, zoneDescription, zoneMaintainers, zoneComments)) self.initZoneInspector(zoneName) def removeZoneFromDatabase(self, zoneRow): """ Remove a zone from the database. Subsequently, remove every occurrence of it in the Flow Schedule. Also remove its inspector. """ zoneName = self.zones[zoneRow][0] del self.zones[zoneRow] for dayIndex in range(7): while True: scheduleRow = self.getRowOfItemInColumnOfModel( zoneName, 1, self.schedule[dayIndex]) if scheduleRow is not None: del self.schedule[dayIndex][scheduleRow] else: break self.zoneInspector[zoneName].clear() del self.zoneInspector[zoneName] def editZoneNameInDatabase(self, oldZoneName, newZoneName): """ Edit a zone's name in the database. Subsequently, rename every occurrence of it in the Flow Schedule. Also rename its inspector. """ zoneRow = self.getZoneRow(oldZoneName) self.zones[zoneRow][0] = newZoneName for dayIndex in range(7): while True: scheduleRow = self.getRowOfItemInColumnOfModel( oldZoneName, 1, self.schedule[dayIndex]) if scheduleRow is not None: self.schedule[dayIndex][scheduleRow][1] = newZoneName else: break self.initZoneInspector(newZoneName) self.zoneInspector[newZoneName] = self.zoneInspector[oldZoneName] del self.zoneInspector[oldZoneName] def addPlaylistToDatabase(self, playlistPath): """ Add a playlist to the database. """ playlistName = getPlaylistNameFromPath(playlistPath) self.playlists.append((playlistName, playlistPath)) def removePlaylistFromDatabase(self, playlistRow): """ Remove a playlist from the database. Subsequently, remove it from every zone in the database. """ playlistName = self.playlists[playlistRow][0] del self.playlists[playlistRow] for zone in self.zones: zoneName = zone[0] while True: zoneInspectorRow = self.getRowOfItemInColumnOfModel( playlistName, 0, self.zoneInspector[zoneName]) if zoneInspectorRow is not None: del self.zoneInspector[zoneName][zoneInspectorRow] else: break def addZoneToSchedule(self, dayIndex, zoneName, zoneStartTime='00:00'): """ Add zoneName to the day that corresponds to dayIndex in Flow Schedule. """ self.schedule[dayIndex].append((zoneStartTime, zoneName)) def removeZoneFromSchedule(self, dayIndex, scheduleRow): """ Remove a zone from the day that corresponds to dayIndex in Flow Schedule. """ del self.schedule[dayIndex][scheduleRow] def addPlaylistToZone(self, zoneName, playlist): """ Add playlist to zoneName. """ self.zoneInspector[zoneName].append( (playlist.name, playlist.type, playlist.shuffle, playlist.schedIntervalMins, playlist.numSchedItems, playlist.fadeInSecs, playlist.fadeOutSecs, playlist.minLevel, playlist.maxLevel)) def removePlaylistFromZone(self, zoneName, zoneInspectorRow): """ Remove the playlist located in zoneInspectorRow from zoneName. """ del self.zoneInspector[zoneName][zoneInspectorRow] def zoneExistsInDatabase(self, zoneName): """ Return true if zoneName exists in database. """ return self.itemExistsInColumnOfModel(zoneName, 0, self.zones) def playlistExistsInDatabase(self, playlistName): """ Return true if playlistName exists in database. """ return self.itemExistsInColumnOfModel(playlistName, 0, self.playlists) def zoneHasMainPlaylist(self, zoneName): """ Return true if zoneName has a Main playlist. """ return self.itemExistsInColumnOfModel('Main', 1, self.zoneInspector[zoneName]) def getZoneRow(self, zoneName): """ Return the zoneName's row in Zones. """ return self.getRowOfItemInColumnOfModel(zoneName, 0, self.zones) def getPlaylistRow(self, playlistName): """ Return the playlistName's row in Playlists. """ return self.getRowOfItemInColumnOfModel(playlistName, 0, self.playlists) def getMainPlaylistRow(self, zoneName): """ Return the Main playlist's row of zoneName in zoneInspector. """ return self.getRowOfItemInColumnOfModel('Main', 1, self.zoneInspector[zoneName]) def getFallbackPlaylistRow(self, zoneName): """ Return the Fallback playlist's row of zoneName in zoneInspector. """ return self.getRowOfItemInColumnOfModel('Fallback', 1, self.zoneInspector[zoneName]) def attemptToAddDefaultPlaylistsToZone(self, zoneName): """ Add default playlists to zoneName, if they exist in database. """ if self.playlistExistsInDatabase('fallback'): playlist = Playlist('fallback', 'Fallback', True, '', '', '2', '2', '0', '1') self.addPlaylistToZone(zoneName, playlist) if self.playlistExistsInDatabase('Spots'): playlist = Playlist('Spots', 'Intermediate', True, '70', '1', '', '', '', '') self.addPlaylistToZone(zoneName, playlist) if self.playlistExistsInDatabase('Jingles'): playlist = Playlist('Jingles', 'Intermediate', True, '40', '1', '', '', '', '') self.addPlaylistToZone(zoneName, playlist) # Private methods def itemExistsInColumnOfModel(self, item, column, model): """ If item exists in model's column, return true. """ return any((row[column] == item for row in model)) def getRowOfItemInColumnOfModel(self, item, column, model): """ If item exists in model's column, return its row. """ for i in range(len(model)): treeiter = model.get_iter(TreePath(i)) if model[treeiter][column] == item: return treeiter return None def initZoneInspector(self, zoneName): """ Initialize zoneName's inspector. """ self.zoneInspector[zoneName] = ListStore(str, str, bool, str, str, str, str, str, str) self.zoneInspector[zoneName].set_sort_column_id(1, SortType.DESCENDING)