class Controller: """This Class Handles the interactions between the GUI(View) and the Model""" folder = '' folders = [] cacheDir = cacheDir def __init__(self, model): self.model = model self.settings = settings self.playlist = self.lastPlaylist() self.playerThread = PlayerThread(self.playlist, self) self.position = 0 self.duration = 0 pynotify.init('label') self.notification = pynotify.Notification(' ',' ') self.folder = self.model.directory if self.folder == '': self.folder = os.environ.get('HOME', None) if not os.path.exists(self.cacheDir): os.makedirs(self.cacheDir) try: from MediaKeysHandler import MediaKeys MediaKeys(self) except: pass def registerView(self,view): """Connects the View to the Controller""" self.view = view def refreshColumnsVisibility(self): """Sets the visibility property of the file browser columns according to the settings""" self.view.filesTree.setColumnsVisibility() def refreshStatusIcon(self): """Refresh the status icon according to the settings""" self.view.setStatusIcon() def openFolder(self, o): """Creates the dialog window that permits to choose the folder(s) to scan""" old = self.folders folderChooser = gtk.FileChooserDialog(_('Select Folder...'), None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK)) folderChooser.set_current_folder(self.folder) folderChooser.set_default_response(gtk.RESPONSE_OK) folderChooser.set_select_multiple(True) response = folderChooser.run() if response == gtk.RESPONSE_OK: folderChooser.hide() gtkTrick() Global.PBcount = 0 self.view.vbox.pack_start(self.view.progressBar, False) self.view.progressBar.set_fraction(0.0) if self.settings['view'] != 3: self.view.progressBar.show() gtkTrick() self.folders = folderChooser.get_filenames() self.folder = folderChooser.get_current_folder() if old != self.folders: #if False not in [True for f in self.folders if os.stat(f).st_uid == os.getuid()]: self.__reBuildViewTree() else: self.view.vbox.remove(self.view.progressBar) self.refreshTree(update = False) folderChooser.hide() else: folderChooser.destroy() gtkTrick() def __reBuildViewTree(self): """Creates a new Model using the current folder""" self.view.filesTree.buttons.set_sensitive(False) if len(self.folders) == 1: self.model = Model([self.folders[0]], self.view.progressBar) else: self.model = Model(self.folders, self.view.progressBar, group=True) self.saveCache() self.__refreshViewTree() self.view.vbox.remove(self.view.progressBar) self.model.lastUpdate = time.time() self.view.filesTree.buttons.set_sensitive(True) def loadLibrary(self): Global.PBcount = 0 self.folder = self.settings['libraryFolder'] self.folders = [self.settings['libraryFolder']] self.view.vbox.pack_start(self.view.progressBar, False) self.view.progressBar.set_fraction(0.0) self.view.progressBar.show() gtkTrick() self.__reBuildViewTree() def saveLibrary(self): """Saves the library in a cache file, using serialization""" fname = 'library' dir = self.cacheDir cachefile = os.path.join(dir, fname) FILE = open(cachefile,'w') cerealizer.dump(self.model.getAudioFileList(), FILE) FILE.close() def saveCache(self): """Saves the model in a cache file, using serialization""" if self.settings['libraryMode']: return else: fname = 'cache' dir = self.cacheDir cachefile = os.path.join(dir, fname) FILE = open(cachefile,'w') if self.settings['foldercache']: cerealizer.dump(self.model.getAudioFileList(), FILE) else: cerealizer.dump([], FILE) FILE.close() def saveWinSize(self, width, height, pos, volume): """Stores in the settings dictionary the dimensions of the main window""" self.settings['width'] = width self.settings['height'] = height self.settings['pos'] = pos self.settings['volume'] = volume def readWinSize(self): """Returns the dimensions of the main window""" s = self.settings return s['width'], s['height'], s['pos'], s['volume'] def saveLastPlaylist(self): """Saves the current playlists""" dir = self.cacheDir if not os.path.exists(dir): os.makedirs(dir) playlistFile = os.path.join(dir, 'lastplaylist') FILE = open(playlistFile,'w') cerealizer.dump(self.playlist, FILE) FILE.close() def lastPlaylist(self): """Loads the playlist saved at last shutdown""" if self.model.playlist != None: return self.model.playlist try: if self.settings['lastplaylist']: dir = self.cacheDir playlistFile = os.path.join(dir, 'lastplaylist') FILE = open(playlistFile,'r') files = cerealizer.load(FILE) FILE.close() return files else: return [] except: return [] def __expFunc(self, tree, path): """Stores the currently expanded rows""" model = tree.get_model() iter = model.get_iter(path) folderName = model.get_value(iter, 0) self.expandedList.append(folderName) def __restoreExpanded(self, model, path, iter): """Re-expands the previously expanded rows (to use after a treeView refresh)""" iter = model.get_iter(path) folderName = model.get_value(iter, 0) if folderName in self.expandedList: self.view.filesTree.treeview.expand_row(path, False) def __refreshViewTree(self): """Refreshes the treeview""" self.expandedList = [] self.view.filesTree.treeview.map_expanded_rows(self.__expFunc) self.view.filesTree.setModel(self.model) self.view.filesTree.searchBox.setListStore(self.view.filesTree.listStore) self.view.filesTree.treeStore.foreach(self.__restoreExpanded) def refreshTree(self, widget = None, data = None, update = True): """Refreshes the Model and the file browser treeView""" self.view.vbox.pack_start(self.view.progressBar, False) #self.view.progressBar.pulse() self.view.statusbar.push(0, 'Updating library...') gtkTrick() if update: self.model.updateModel() if self.model.changed: self.__refreshViewTree() if self.settings['libraryMode']: self.saveLibrary() else: self.saveCache() self.view.statusbar.pop(0) self.view.vbox.remove(self.view.progressBar) def toggle(self, cell, path, rowModel): """Adds the selected files to the playlist and updates the treeview""" print row = rowModel[path] self.__addTrack(row) if type(rowModel).__name__ == 'TreeStore': self.__recursiveToggle(path, rowModel) self.updatePlaylist() def __recursiveToggle(self, path, rowModel): """Recursively adds the selected files to the playlist and updates the treeview""" i=0 rowexists = True while True: try: row = rowModel[path + (":%d" % (i))] self.__addTrack(row) self.__recursiveToggle((path + (":%d" % (i))), rowModel) i+=1 except: rowexists = False #print sys.exc_info() if not rowexists: break def addAll(self, widget, add): """Adds to the playlist all the files of the current folder""" rowModel = self.view.filesTree.treeview.get_model() rowModel.foreach(self.__add) self.updatePlaylist() def __add(self, model, path, iter, add = None): row = model[path] self.__addTrack(row) def __addTrack(self, row): """Handles the addition of the files in the playlist""" pt = self.playerThread lstore = self.view.playlistFrame.listStore append = lstore.append cfname = row[8] fname = row[0] title = row[2] album = row[4] artist = row[3] if cfname != '': self.playlist.append((cfname, title, album, artist)) icon = None info = [title, album, artist] for tag in info: new = tag.replace('<', '') new = new.replace('>', '') info[info.index(tag)] = new text = '<b>%s</b>\n%s <i>%s</i> %s <i>%s</i>' % (info[0], _('from'), info[1], _('by'), info[2]) text = text.replace('&', '&') if info[0] != '' and info[0] != ' ': append([icon, text]) else: f = '<b>%s</b>\n' % fname append([icon, f]) if len(self.playlist) == 1 and pt.trackNum != -1: pt.trackNum = -1 self.nextTrack() pt.pause() self.__extendShuffleList(len(self.playlist)-1) def addTrack(self, cfname): """Handles the addition of the files in the playlist""" pt = self.playerThread lstore = self.view.playlistFrame.listStore append = lstore.append if cfname != '': tags = self.extractTags(cfname) self.playlist.append((cfname, tags['title'], tags['album'], tags['artist'])) icon = None info = [tags['title'], tags['album'], tags['artist']] for tag in info: new = tag.replace('<', '') new = new.replace('>', '') info[info.index(tag)] = new text = '<b>%s</b>\n%s <i>%s</i> %s <i>%s</i>' % (info[0], _('from'), info[1], _('by'), info[2]) text = text.replace('&', '&') if info[0] != '' and info[0] != ' ': append([icon, text]) else: f = '<b>%s</b>\n' % tags['filename'] append([icon, f]) if len(self.playlist) == 1 and pt.trackNum != -1: pt.trackNum = -1 self.nextTrack() pt.pause() self.__extendShuffleList(len(self.playlist)-1) def __extendShuffleList(self, num): """Extends the shuffleList to handle the insertion in the playlist of a new track""" r = range(self.playerThread.trackNum+1,num) if len(r) > 0: i = random.choice(r) self.playerThread.shuffleList.insert(i, num) else: self.playerThread.shuffleList.append(num) def doubleClickSelect(self, tree, event): """Detects double click on the treeview and updates the selection""" rowList = tree.get_selection().get_selected_rows()[1] model = tree.get_model() pt = self.playerThread try: path, x, y = self.__detectPath(tree, event) rectangle = tuple(tree.get_cell_area(path, tree.get_column(7))) max, min = rectangle[0] + rectangle[2], rectangle[0] if event.type == gtk.gdk._2BUTTON_PRESS and x < min: path, x, y = self.__detectPath(tree, event) iter = model.get_iter(path) if model.get_value(iter, 8) != '': self.toggle(None, path, model) if pt.shuffle: i = pt.shuffleList.index(len(self.playlist)-1) pt.trackNum = i - 1 else: pt.trackNum = len(self.playlist) - 2 self.nextTrack() else: if tree.row_expanded(path): tree.collapse_row(path) else: tree.expand_row(path, False) if not self.view.slider.get_sensitive(): self.view.slider.set_sensitive(True) elif event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: if max > x > min: if len(rowList) <= 1: self.toggle(None, path, model) elif len(rowList) > 1: for row in rowList: try: path = str(row[0]) + ':' + str(row[1]) except: path = str(row[0]) self.toggle(None, path, model) except: #import sys #print sys.exc_info() return def dbusPlay(self): """Play command to be called from the dbus service""" pt = self.playerThread if pt.shuffle: i = pt.shuffleList.index(len(self.playlist)-1) pt.trackNum = i - 1 else: pt.trackNum = len(self.playlist) - 2 self.nextTrack() if not self.view.slider.get_sensitive(): self.view.slider.set_sensitive(True) def dbusAddTrack(self, cfname): """AddTrack command to be called from the dbus service""" self.addTrack(cfname) def doubleClickPlay(self, tree, event): """Detects double click on the playlist and play the selected track""" try: if event.type == gtk.gdk._2BUTTON_PRESS: path, x, y = self.__detectPath(tree, event) if not self.view.slider.get_sensitive(): self.view.slider.set_sensitive(True) i = int(path) if self.playerThread.shuffle: num = int(path) i = self.playerThread.shuffleList.index(int(path)) self.playerThread.trackNum = i - 1 self.nextTrack() except: return def rightClick(self, tree, event, openMenu): """Opens the rx click menu""" if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: path, x, y = self.__detectPath(tree, event) openMenu(event.time, path) def __detectPath(self, tree, event): """Determines the path corresponding to the area of the double click""" x = int(event.get_coords()[0]) y = int(event.get_coords()[1]) pathinfo = tree.get_path_at_pos(x, y) path = '' for i in range(len(pathinfo[0])): if i == 0: sep = '' else: sep = ':' path = path + sep + str(pathinfo[0][i]) return path, x, y def dragBegin(self, widget, context, selection): """Starts the D&D process""" items = selection.get_selected_rows() if len(items[1]) > 1: icon = gtk.STOCK_DND_MULTIPLE else: icon = gtk.STOCK_DND context.set_icon_stock(icon, 0, 0) def drag(self, treeview, context, selection, target_id, etime): """Starts DnD removing the selected file from the playlist""" try: treeselection = treeview.get_selection() model, rows = treeselection.get_selected_rows() self.current = self.playlist[self.playerThread.trackNum] self.startIndex = rows[0][0] self.movedTracks = [] count = 0 for tuple in rows: path = tuple[0] - count self.movedTracks.append(self.playlist[path]) del self.playlist[path] count+=1 except: return def drop(self, treeview, context, x, y, selection, info, etime): """Starts DnD inserting the selected file in the playlist""" try: drop_info = treeview.get_dest_row_at_pos(x, y) count = 0 for row in self.movedTracks: if drop_info: path, position = drop_info if position == gtk.TREE_VIEW_DROP_BEFORE or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE: if self.startIndex > path[0]: i = 0 else: i = -1 elif position == gtk.TREE_VIEW_DROP_AFTER or position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER: if self.startIndex > path[0]: i = 1 else: i = 0 else: i = 0 index = path[0] + i + count self.playlist.insert(index, row) else: self.playlist.append(row) count += 1 context.finish(True, True, etime) self.playerThread.trackNum = self.playlist.index(self.current) except: return def dragEnd(self, widget, context): """Ends the D&D process""" self.createPlaylist() def toggleFilter(self, data): """Enables/disables mp3 filtering""" for type in audioTypes: self.view.getFormatDict()[type[1:]] = self.view.actiongroup.get_action(type[1:].capitalize()).get_active() self.__refreshViewTree() return def playStopSelected(self, obj = None): """Handles the click on the Play/Pause button""" if self.view.actiongroup.get_action('Play/Stop').get_stock_id() == gtk.STOCK_MEDIA_PLAY: if len(self.playlist) > 0: if not self.playerThread.isStarted(): self.playerThread.setPlaylist(self.playlist) self.view.slider.set_sensitive(True) self.playerThread.go() #self.playerThread.join(0.1) else: if self.playerThread.trackNum == 0 and self.view.slider.get_value() == 0: self.playerThread.updateGUI() if not self.view.slider.get_sensitive(): self.view.slider.set_sensitive(True) self.playerThread.play() elif self.view.actiongroup.get_action('Play/Stop').get_stock_id() == gtk.STOCK_MEDIA_PAUSE: self.playerThread.pause() self.view.image.updateImage() def nextTrack(self, obj = None): """Handles the click on the Next button""" if self.playerThread.started: self.playerThread.next() else: self.playerThread.go() def previousTrack(self, obj = None): """Handles the click on the Previous button""" self.playerThread.previous() def updateLabel(self, (cfname, title, album, artist), notify = True):