-- Author: Andy Stewart -- Maintainer: Andy Stewart -- -- Copyright (C) 2010 Andy Stewart, all rights reserved. -- -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU General Public License as published by -- the Free Software Foundation, either version 3 of the License, or -- any later version. -- -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU General Public License for more details. -- -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . {-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-} module Manatee.Extension.Mplayer.PlaylistView where import Control.Applicative import Control.Concurrent.STM import Control.Monad import Data.Map (Map) import Data.Text.Lazy (Text) import Data.Typeable import Graphics.UI.Gtk hiding (Statusbar, statusbarNew, get) import Graphics.UI.Gtk.Gdk.SerializedEvent import Manatee.Core.PageView import Manatee.Core.Types import Manatee.Extension.Mplayer.DBus import Manatee.Extension.Mplayer.PlaylistBuffer import Manatee.Toolkit.General.Basic import Manatee.Toolkit.General.Functor import Manatee.Toolkit.General.Maybe import Manatee.Toolkit.General.STM import Manatee.Toolkit.Gio.Gio import Manatee.Toolkit.Gtk.Gtk import Manatee.Toolkit.Gtk.ModelView import Manatee.Toolkit.Gtk.ScrolledWindow import System.GIO.File.ContentType import System.Posix.Process import System.Random import qualified Data.Map as M data PlaylistView = PlaylistView {playlistViewPlugId :: TVar PagePlugId ,playlistViewScrolledWindow :: ScrolledWindow ,playlistViewBuffer :: PlaylistBuffer ,playlistViewTreeView :: TreeView ,playlistViewListStore :: ListStore MultimediaInfo ,playlistViewSortModel :: TypedTreeModelSort MultimediaInfo ,playlistViewCurrentPlayPath :: TVar TreePath ,playlistViewPlayMode :: TVar PlayMode} deriving Typeable instance PageBuffer PlaylistBuffer where pageBufferGetName = readTVarIO . playlistBufferName pageBufferSetName a = writeTVarIO (playlistBufferName a) pageBufferClient = playlistBufferClient pageBufferCreateView a pId = PageViewWrap <$> playlistViewNew a pId pageBufferMode = playlistBufferMode instance PageView PlaylistView where pageViewBuffer = PageBufferWrap . playlistViewBuffer pageViewPlugId = playlistViewPlugId pageViewFocus = treeViewFocus . playlistViewTreeView pageViewScrolledWindow = playlistViewScrolledWindow pageViewHandleKeyAction = playlistViewHandleKeyAction pageViewScrollToTop = playlistViewScrollToTop pageViewScrollToBottom = playlistViewScrollToBottom pageViewScrollVerticalPage = playlistViewScrollVerticalPage pageViewScrollVerticalStep = playlistViewScrollVerticalStep data PlayMode = SingleMode | ListMode | RandomMode deriving (Show, Eq, Ord) -- | Internal new function. playlistViewNew :: PlaylistBuffer -> PagePlugId -> IO PlaylistView playlistViewNew buffer plugId = do -- Create plug id. pId <- newTVarIO plugId -- Create UI frame. scrolledWindow <- scrolledWindowNew_ -- Tree view. treeView <- treeViewNew treeViewSetEnableTreeLines treeView True scrolledWindow `containerAdd` treeView -- List store. listStore <- listStoreNew [] -- Sort model. sortModel <- treeModelSortNewWithModel listStore -- Current play path. path <- newTVarIO [0] -- Play mode. playMode <- newTVarIO ListMode -- Playlist view. let playlistView = PlaylistView pId scrolledWindow buffer treeView listStore sortModel path playMode -- Build match rule listen DBus message. playlistViewBuildMatchRule playlistView -- Draw view. playlistViewDraw playlistView return playlistView -- | Build match rule. playlistViewBuildMatchRule :: PlaylistView -> IO () playlistViewBuildMatchRule view = mkMplayerClientMatchRule (pageViewClient view) (PlayFinished, \ _ -> do mode <- readTVarIO (playlistViewPlayMode view) case mode of SingleMode -> playlistViewPlayCurrent view ListMode -> playlistViewPlayNext view RandomMode -> playlistViewPlayRandom view) -- | Switch play mode. playlistViewSwitchPlayMode :: PlaylistView -> IO () playlistViewSwitchPlayMode view = do mode <- readTVarIO (playlistViewPlayMode view) let newMode = case mode of SingleMode -> ListMode ListMode -> RandomMode RandomMode -> SingleMode writeTVarIO (playlistViewPlayMode view) newMode pageViewUpdateInfoStatus view "PlayMode" (" PlayMode (" ++ show newMode ++ ")") -- | Play current track. playlistViewPlayCurrent :: PlaylistView -> IO () playlistViewPlayCurrent view = do currentPath <- readTVarIO $ playlistViewCurrentPlayPath view playlistViewPlayInternal view currentPath -- | Draw playlist view. playlistViewDraw :: PlaylistView -> IO () playlistViewDraw view = do -- Get value. let buffer = playlistViewBuffer view treeView = playlistViewTreeView view store = playlistViewListStore view model = playlistViewSortModel view infos <- readTVarIO $ playlistBufferInfos buffer -- Append to list store. listStoreClear store forM_ infos (listStoreAppend store) -- Set tree view model. treeViewSetModel treeView model -- Clean tree view. treeViewRemoveColumns treeView -- Add icon column. playlistViewAddIconColumn treeView store -- Add column file info to tree view. forM_ (playlistBufferOptions buffer) (playlistViewAddColumn treeView store model) -- Set default sort rule. sortStatus <- readTVarIO $ playlistBufferSortStatus buffer playlistViewSortInternal view sortStatus return () -- | Internal sort function. playlistViewSortInternal :: PlaylistView -> (MultimediaOption, SortType) -> IO () playlistViewSortInternal view (option, sortType) = do let options = playlistBufferOptions $ playlistViewBuffer view lookup option options ?>= \x -> treeSortableSetSortColumnId (playlistViewSortModel view) x sortType -- | Add icon column. playlistViewAddIconColumn :: (TreeViewClass self ,TreeModelClass (model row) ,TypedTreeModelClass model) => self -> model row -> IO () playlistViewAddIconColumn treeView store = do -- Any music format can work, we just need music icon pixbuf. pixbuf <- getIconPixbuf $ contentTypeGetIcon "audio/mp3" tvc <- treeViewColumnNew set tvc [treeViewColumnTitle := ""] treeViewAppendColumn treeView tvc icon <- cellRendererPixbufNew treeViewColumnPackStart tvc icon True cellLayoutSetAttributes tvc icon store $ \_ -> [cellPixbuf := pixbuf] -- | Add column. playlistViewAddColumn :: (MultimediaInfoClass t ,TreeViewClass self1 ,TreeModelClass self ,TreeModelSortClass self ,TypedTreeModelClass model ,TreeSortableClass self) => self1 -> model MultimediaInfo -> self -> (t, SortColumnId) -> IO () playlistViewAddColumn treeView model sortModel option@(info,sortId) = do playlistViewSetSortFunc model sortModel option let name = getColumnTitle info maxWidth = getColumnMaxWidth info tvc <- treeViewAddColumnWithTitle treeView name sortId maxWidth ?>= \width -> treeViewColumnSetMaxWidth tvc width cell <- cellRendererTextNew treeViewColumnPackStart tvc cell True playlistViewSetCellText tvc cell model sortModel info -- | Set sort function. playlistViewSetSortFunc :: (TreeSortableClass self, TypedTreeModelClass model, MultimediaInfoClass a) => model MultimediaInfo -> self -> (a, SortColumnId) -> IO () playlistViewSetSortFunc model sortModel (info, sortId) = treeSortableSetSortFunc sortModel sortId $ \iter1 iter2 -> do row1 <- treeModelGetRow model iter1 row2 <- treeModelGetRow model iter2 compareRow info row1 row2 -- | Set cell text. playlistViewSetCellText :: (CellLayoutClass self, CellRendererTextClass cell, TreeModelClass model, TreeModelSortClass model, TypedTreeModelClass model1, MultimediaInfoClass a) => self -> cell -> model1 MultimediaInfo -> model -> a -> IO () playlistViewSetCellText tvc cell model sortModel info = cellLayoutSetAttributeFunc tvc cell sortModel $ \iter -> do row <- treeModelSortGetRow model sortModel iter set cell [cellText := getCellText info row ,cellXAlign := getCellXAlign info] -- | Next node. playlistViewNextNode :: PlaylistView -> IO () playlistViewNextNode = treeViewFocusNextToplevelNode . playlistViewTreeView -- | Previous node. playlistViewPrevNode :: PlaylistView -> IO () playlistViewPrevNode = treeViewFocusPrevToplevelNode . playlistViewTreeView -- | Keymap. playlistViewKeymap :: Map Text (PlaylistView -> IO ()) playlistViewKeymap = M.fromList [("j", playlistViewNextNode) ,("k", playlistViewPrevNode) ,("Down", playlistViewNextNode) ,("Up", playlistViewPrevNode) ,("J", playlistViewScrollToBottom) ,("K", playlistViewScrollToTop) ,(" ", playlistViewScrollVerticalPage True) ,("b", playlistViewScrollVerticalPage False) ,("PageDown", playlistViewScrollVerticalPage True) ,("PageUp", playlistViewScrollVerticalPage False) ,("m", playlistViewPlay) ,("Return", playlistViewPlay) ,("M", playlistViewPause) ,("z", playlistViewStop) ,("h", playlistViewBackward) ,("l", playlistViewForward) ,("Right", playlistViewBackward) ,("Left", playlistViewForward) ,(",", playlistViewVolumeDec) ,(".", playlistViewVolumeInc) ,("-", playlistViewVolumeDec) ,("=", playlistViewVolumeInc) ,("n", playlistViewPlayNext) ,("p", playlistViewPlayPrev) ,("N", playlistViewPlayRandom) ,("P", playlistViewSwitchPlayMode) ,("1", playlistViewSortByTitle) ,("2", playlistViewSortByAlbum) ,("3", playlistViewSortByArtist) ,("4", playlistViewSortByYear) ,("5", playlistViewSortByTrack) ,("6", playlistViewSortByBitRate) ,("7", playlistViewSortByDuration) ] -- | Sort by title. playlistViewSortByTitle :: PlaylistView -> IO () playlistViewSortByTitle view = playlistViewSort view MOTitle -- | Sort by album. playlistViewSortByAlbum :: PlaylistView -> IO () playlistViewSortByAlbum view = playlistViewSort view MOAlbum -- | Sort by artist. playlistViewSortByArtist :: PlaylistView -> IO () playlistViewSortByArtist view = playlistViewSort view MOArtist -- | Sort by year. playlistViewSortByYear :: PlaylistView -> IO () playlistViewSortByYear view = playlistViewSort view MOYear -- | Sort by track. playlistViewSortByTrack :: PlaylistView -> IO () playlistViewSortByTrack view = playlistViewSort view MOTrack -- | Sort by BitRate. playlistViewSortByBitRate :: PlaylistView -> IO () playlistViewSortByBitRate view = playlistViewSort view MOBitRate -- | Sort by duration. playlistViewSortByDuration :: PlaylistView -> IO () playlistViewSortByDuration view = playlistViewSort view MODuration -- | Sort column. playlistViewSort :: PlaylistView -> MultimediaOption -> IO () playlistViewSort view option = do -- Get model and options. let model = playlistViewSortModel view buffer = playlistViewBuffer view options = playlistBufferOptions buffer -- Get current sortType and columnId. (curSortType, _, curSortColumnId) <- treeSortableGetSortColumnId model lookup option options ?>= \id -> do treeSortableSetSortColumnId model id $ if id == curSortColumnId -- Just change sort order when sort column id is same. then -- Just change sort order. case curSortType of SortAscending -> SortDescending SortDescending -> SortAscending -- Otherwise sort ascending. else SortAscending -- Get new sort type. (newSortType, _, _) <- treeSortableGetSortColumnId model -- Update sort status of buffer. writeTVarIO (playlistBufferSortStatus $ playlistViewBuffer view) (option, newSortType) -- Focus to cell. treeViewFocus (playlistViewTreeView view) -- | Play current selected track. playlistViewPlay :: PlaylistView -> IO () playlistViewPlay view = do let treeView = playlistViewTreeView view treeViewGetSelectedPath treeView >?>= \ path -> do -- Convert sorted path to unsorted model path. currentPath <- treeModelSortConvertPathToChildPath (playlistViewSortModel view) path playlistViewPlayInternal view currentPath -- | Play next track randomly. playlistViewPlayRandom :: PlaylistView -> IO () playlistViewPlayRandom view = do size <- treeViewGetToplevelNodeCount (playlistViewTreeView view) randomIndex <- randomRIO (0, size - 1) playlistViewPlayInternal view [randomIndex] -- | Play next track. playlistViewPlayNext :: PlaylistView -> IO () playlistViewPlayNext view = do currentPath <- readTVarIO $ playlistViewCurrentPlayPath view nextSortPath <- treeViewNextSortPath (playlistViewTreeView view) (playlistViewSortModel view) currentPath playlistViewPlayInternal view nextSortPath -- | Play previous track. playlistViewPlayPrev :: PlaylistView -> IO () playlistViewPlayPrev view = do currentPath <- readTVarIO $ playlistViewCurrentPlayPath view prevSortPath <- treeViewPrevSortPath (playlistViewTreeView view) (playlistViewSortModel view) currentPath playlistViewPlayInternal view prevSortPath -- | The internal function of `playlistViewPlay'. playlistViewPlayInternal :: PlaylistView -> TreePath -> IO () playlistViewPlayInternal view path = do -- Get file path and update play path status. filePath <- liftM miFilePath $ listStoreGetValue (playlistViewListStore view) (head path) let displayPath = filepathGetDisplayName filePath writeTVarIO (playlistViewCurrentPlayPath view) path -- Update play mode status. mode <- readTVarIO (playlistViewPlayMode view) pageViewUpdateInfoStatus view "PlayMode" (" PlayMode (" ++ show mode ++ ")") -- Update playing status. pageViewUpdateInfoStatus view "Playing" (" Playing : " ++ displayPath) -- Send daemon signal to play. processId <- getProcessID mkMplayerDaemonSignal (pageViewClient view) Play (PlayArgs filePath processId) -- | Stop play. playlistViewStop :: PlaylistView -> IO () playlistViewStop view = mkMplayerDaemonSignal (pageViewClient view) Stop StopArgs -- | Pause or continue play. playlistViewPause :: PlaylistView -> IO () playlistViewPause view = mkMplayerDaemonSignal (pageViewClient view) Pause PauseArgs -- | Forward current track. playlistViewForward :: PlaylistView -> IO () playlistViewForward view = mkMplayerDaemonSignal (pageViewClient view) Forward (ForwardArgs 10) -- | Backward current track. playlistViewBackward :: PlaylistView -> IO () playlistViewBackward view = mkMplayerDaemonSignal (pageViewClient view) Backward (BackwardArgs 10) -- | Increase volume. playlistViewVolumeInc :: PlaylistView -> IO () playlistViewVolumeInc view = mkMplayerDaemonSignal (pageViewClient view) VolumeInc (VolumeIncArgs 10) -- | Decrease volume. playlistViewVolumeDec :: PlaylistView -> IO () playlistViewVolumeDec view = mkMplayerDaemonSignal (pageViewClient view) VolumeDec (VolumeDecArgs 10) -- | Begin. playlistViewScrollToTop :: PlaylistView -> IO () playlistViewScrollToTop = treeViewFocusFirstToplevelNode . playlistViewTreeView -- | End. playlistViewScrollToBottom :: PlaylistView -> IO () playlistViewScrollToBottom = treeViewFocusLastToplevelNode . playlistViewTreeView -- | Scroll page vertically. playlistViewScrollVerticalPage :: Bool -> PlaylistView -> IO () playlistViewScrollVerticalPage isDown a = do let sw = playlistViewScrolledWindow a tv = playlistViewTreeView a pageInc <- (<=<) adjustmentGetPageIncrement scrolledWindowGetVAdjustment sw treeViewScrollVertical tv sw (if isDown then pageInc else (- pageInc)) -- | Scroll step vertically. playlistViewScrollVerticalStep :: Bool -> PlaylistView -> IO () playlistViewScrollVerticalStep isDown a = do let sw = playlistViewScrolledWindow a tv = playlistViewTreeView a stepInc <- (<<<=) i2d treeViewGetSelectedCellHeight tv treeViewScrollVertical tv sw (if isDown then stepInc else (- stepInc)) -- | Handle key action. playlistViewHandleKeyAction :: PlaylistView -> Text -> SerializedEvent -> IO () playlistViewHandleKeyAction view keystoke sEvent = case M.lookup keystoke playlistViewKeymap of Just action -> action view Nothing -> widgetPropagateEvent (playlistViewTreeView view) sEvent