-- 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 OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} module Manatee.Extension.Curl.CurlView where import Control.Applicative import Control.Concurrent.STM import Control.Monad import Data.List 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.Config import Manatee.Core.DBus import Manatee.Core.FileOpenRule import Manatee.Core.PageView import Manatee.Core.Types import Manatee.Extension.Curl.CurlBuffer import Manatee.Extension.Curl.Types 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.Concurrent import Manatee.Toolkit.Gtk.Gtk import Manatee.Toolkit.Gtk.ModelView import Manatee.Toolkit.Gtk.ScrolledWindow import System.Directory import System.FilePath import System.GIO import Text.Printf import qualified Data.Map as M data CurlView = CurlView {curlViewPlugId :: TVar PagePlugId ,curlViewScrolledWindow :: ScrolledWindow ,curlViewBuffer :: CurlBuffer ,curlViewTreeView :: TreeView ,curlViewListStore :: ListStore DownloadFile ,curlViewSortModel :: TypedTreeModelSort DownloadFile ,curlViewBroadcastChannel :: ViewChannel CurlTChanSignal } deriving Typeable instance PageBuffer CurlBuffer where pageBufferGetName = return . curlBufferName pageBufferSetName _ _ = return () pageBufferClient = curlBufferClient pageBufferCreateView a pId = PageViewWrap <$> curlViewNew a pId pageBufferMode = curlBufferMode instance PageView CurlView where pageViewBuffer = PageBufferWrap . curlViewBuffer pageViewPlugId = curlViewPlugId pageViewFocus = treeViewFocus . curlViewTreeView pageViewScrolledWindow = curlViewScrolledWindow pageViewHandleKeyAction = curlViewHandleKeyAction pageViewScrollToTop = curlViewScrollToTop pageViewScrollToBottom = curlViewScrollToBottom pageViewScrollVerticalPage = curlViewScrollVerticalPage pageViewScrollVerticalStep = curlViewScrollVerticalStep -- | New. curlViewNew :: CurlBuffer -> PagePlugId -> IO CurlView curlViewNew 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 -- Channel. channel <- createViewChannel (curlBufferBroadcastChannel buffer) treeView -- Curl view. let curlView = CurlView pId scrolledWindow buffer treeView listStore sortModel channel -- Read channel. curlViewListenChannel curlView -- Draw view. curlViewDraw curlView -- Display file name when cursor change. treeView `on` cursorChanged $ curlViewDisplayFilenameStatus curlView return curlView -- | Listen broadcast channel for draw view synchronous. curlViewListenChannel :: CurlView -> IO () curlViewListenChannel view = listenViewChannel (curlViewBroadcastChannel view) $ \ signal -> case signal of AddDownload downloadFile -> do listStoreAppend (curlViewListStore view) downloadFile return () UpdateStatus -> widgetQueueDraw (curlViewTreeView view) DeleteDownload url -> do list <- listStoreToList (curlViewListStore view) findIndex (\x -> dfURL x == url) list ?>= \ i -> listStoreRemove (curlViewListStore view) i -- | Draw curl view. curlViewDraw :: CurlView -> IO () curlViewDraw view = do -- Get value. let buffer = curlViewBuffer view fileInfos <- readTVarIO $ curlBufferFileInfos buffer let treeView = curlViewTreeView view store = curlViewListStore view model = curlViewSortModel view -- Append to list store. listStoreClear store forM_ fileInfos (listStoreAppend store) -- Set tree view model. treeViewSetModel treeView model -- Clean tree view. treeViewRemoveColumns treeView -- Add status icon column. curlViewAddStatusIconColumn treeView store (curlBufferDownloadStatusPixbuf buffer) -- Add type icon column. curlViewAddTypeIconColumn treeView store (curlBufferIconPixbufDatabase buffer) -- Get option index. let options = curlBufferFileInfoOptions buffer splitIndex = case findIndex (\x -> fst x == DFSize) options of Just i -> i + 1 Nothing -> length options (leftOptions, rightOptions) = splitAt splitIndex options -- Add column at left of progress. forM_ leftOptions (curlViewAddColumn treeView store model) -- Add progress column. curlViewAddProgressColumn treeView store -- Add column at right of progress. forM_ rightOptions (curlViewAddColumn treeView store model) -- Sort column. sortStatus <- readTVarIO $ curlBufferSortStatus buffer curlViewSortInternal view sortStatus return () -- | Add type icon. curlViewAddStatusIconColumn :: TreeViewClass tv => tv -> ListStore DownloadFile -> DownloadStatusPixbuf -> IO () curlViewAddStatusIconColumn treeView store (DownloadStatusPixbuf {dspPause = pausePixbuf ,dspRunning = runningPixbuf ,dspFinish = finishPixbuf ,dspFailed = failedPixbuf}) = do tvc <- treeViewColumnNew set tvc [treeViewColumnTitle := "Status"] treeViewAppendColumn treeView tvc icon <- cellRendererPixbufNew treeViewColumnPackStart tvc icon True cellLayoutSetAttributes tvc icon store $ \DownloadFile {dfDownloadStatus = statusTVar} -> [cellPixbuf :=> do status <- readTVarIO statusTVar return $ case status of Pause -> pausePixbuf Running -> runningPixbuf Finish -> finishPixbuf Failed -> failedPixbuf] -- | Add type icon. curlViewAddTypeIconColumn :: TreeViewClass tv => tv -> ListStore DownloadFile -> TVar FileIconPixbufDatabase -> IO () curlViewAddTypeIconColumn treeView store databaseTVar = do tvc <- treeViewColumnNew set tvc [treeViewColumnTitle := "Type"] treeViewAppendColumn treeView tvc icon <- cellRendererPixbufNew treeViewColumnPackStart tvc icon True cellLayoutSetAttributes tvc icon store $ \DownloadFile {dfName = nameTVar} -> [cellPixbuf :=> do name <- readTVarIO nameTVar (_, fMime) <- contentTypeGuess name "" 0 modifyTVarIOM databaseTVar (updateFileIconPixbufDatabaseWithFilePath name) database <- readTVarIO databaseTVar return $ maybeError (findIconPixbuf database fMime) ("curlViewAddTypeIconColumn: can't find pixbuf match in database : " ++ show fMime)] -- | Add progress. curlViewAddProgressColumn :: TreeViewClass tv => tv -> ListStore DownloadFile -> IO () curlViewAddProgressColumn treeView store = do tvc <- treeViewColumnNew set tvc [treeViewColumnTitle := "Progress" ,treeViewColumnMinWidth := 100] treeViewAppendColumn treeView tvc progress <- cellRendererProgressNew treeViewColumnPackStart tvc progress True cellLayoutSetAttributes tvc progress store $ \DownloadFile {dfProgress = progressTVar} -> [cellProgressValue :=> do progressValue <- readTVarIO progressTVar return $ floor progressValue ,cellProgressText :=> do progressValue <- readTVarIO progressTVar return $ Just (printf "%.2f" progressValue ++ " %")] -- | Add column. curlViewAddColumn :: (CurlFileInfoClass t, TreeViewClass self1, TreeModelClass self, TreeModelSortClass self, TypedTreeModelClass model, TreeSortableClass self) => self1 -> model DownloadFile -> self -> (t, SortColumnId) -> IO () curlViewAddColumn treeView model sortModel option@(info,sortId) = do curlViewSetSortFunc model sortModel option let name = getColumnTitle info tvc <- treeViewAddColumnWithTitle treeView name sortId getCellMinWidth info ?>= \width -> set tvc [treeViewColumnMinWidth := width] getCellMaxWidth info ?>= \width -> set tvc [treeViewColumnMaxWidth := width] cell <- cellRendererTextNew treeViewColumnPackStart tvc cell True curlViewSetCellText tvc cell model sortModel info -- | Set sort function. curlViewSetSortFunc :: (TreeSortableClass self, TypedTreeModelClass model, CurlFileInfoClass a) => model DownloadFile -> self -> (a, SortColumnId) -> IO () curlViewSetSortFunc 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. curlViewSetCellText :: (CellLayoutClass self, CellRendererTextClass cell, TreeModelClass model, TreeModelSortClass model, TypedTreeModelClass model1, CurlFileInfoClass a) => self -> cell -> model1 DownloadFile -> model -> a -> IO () curlViewSetCellText 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] -- | Internal sort function. curlViewSortInternal :: CurlView -> (DownloadFileOption, SortType) -> IO () curlViewSortInternal view (option, sortType) = do let options = curlBufferFileInfoOptions $ curlViewBuffer view lookup option options ?>= \x -> treeSortableSetSortColumnId (curlViewSortModel view) x sortType -- | Begin. curlViewScrollToTop :: CurlView -> IO () curlViewScrollToTop = treeViewFocusFirstToplevelNode . curlViewTreeView -- | End. curlViewScrollToBottom :: CurlView -> IO () curlViewScrollToBottom = treeViewFocusLastToplevelNode . curlViewTreeView -- | Scroll page vertically. curlViewScrollVerticalPage :: Bool -> CurlView -> IO () curlViewScrollVerticalPage isDown a = do let sw = curlViewScrolledWindow a tv = curlViewTreeView a pageInc <- (<=<) adjustmentGetPageIncrement scrolledWindowGetVAdjustment sw treeViewScrollVertical tv sw (if isDown then pageInc else (- pageInc)) -- | Scroll step vertically. curlViewScrollVerticalStep :: Bool -> CurlView -> IO () curlViewScrollVerticalStep isDown a = do let sw = curlViewScrolledWindow a tv = curlViewTreeView a stepInc <- (<<<=) i2d treeViewGetSelectedCellHeight tv treeViewScrollVertical tv sw (if isDown then stepInc else (- stepInc)) -- | Handle key action. curlViewHandleKeyAction :: CurlView -> Text -> SerializedEvent -> IO () curlViewHandleKeyAction view keystoke sEvent = case M.lookup keystoke curlViewKeymap of Just action -> action view Nothing -> widgetPropagateEvent (curlViewTreeView view) sEvent -- | Next node. curlViewNextNode :: CurlView -> IO () curlViewNextNode = treeViewFocusNextToplevelNode . curlViewTreeView -- | Previous node. curlViewPrevNode :: CurlView -> IO () curlViewPrevNode = treeViewFocusPrevToplevelNode . curlViewTreeView -- | Display file name status. curlViewDisplayFilenameStatus :: CurlView -> IO () curlViewDisplayFilenameStatus view = treeViewGetSelectedValue (curlViewTreeView view) (curlViewSortModel view) (curlViewListStore view) >?>= \ downloadFile -> do name <- readTVarIO (dfName downloadFile) pageViewUpdateInfoStatus view "Filename" ("Filename : " ++ name) -- | Sort column. curlViewSort :: CurlView -> DownloadFileOption -> IO () curlViewSort view option = do -- Get model and options. let model = curlViewSortModel view buffer = curlViewBuffer view options = curlBufferFileInfoOptions 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 (curlBufferSortStatus $ curlViewBuffer view) (option, newSortType) -- Focus to cell. treeViewFocus (curlViewTreeView view) -- | Sort by name. curlViewSortByName :: CurlView -> IO () curlViewSortByName view = curlViewSort view DFName -- | Sort by size. curlViewSortBySize :: CurlView -> IO () curlViewSortBySize view = curlViewSort view DFSize -- | Sort by download size. curlViewSortByDownloadSize :: CurlView -> IO () curlViewSortByDownloadSize view = curlViewSort view DFDownloadSize -- | Sort by rest size. curlViewSortByRestSize :: CurlView -> IO () curlViewSortByRestSize view = curlViewSort view DFRestSize -- | Sort by speed. curlViewSortBySpeed :: CurlView -> IO () curlViewSortBySpeed view = curlViewSort view DFSpeed -- | Sort by rest time. curlViewSortByRestTime :: CurlView -> IO () curlViewSortByRestTime view = curlViewSort view DFRestTime -- | Sort by thread number. curlViewSortByThread :: CurlView -> IO () curlViewSortByThread view = curlViewSort view DFThread -- | Sort by URL. curlViewSortByUrl :: CurlView -> IO () curlViewSortByUrl view = curlViewSort view DFURL -- | Add download. curlViewAddDownload :: CurlView -> IO () curlViewAddDownload view@(CurlView {curlViewBuffer = buffer}) = localInteractive view "sURL : " $ \ [url] -> do files <- readTVarIO $ curlBufferFileInfos buffer if url `elem` map dfURL files then pageViewUpdateOutputStatus view ("Has exist " ++ url) Nothing else curlBufferAddDownload buffer url -- | Pause curlViewPause :: CurlView -> IO () curlViewPause view = treeViewGetSelectedValue (curlViewTreeView view) (curlViewSortModel view) (curlViewListStore view) >?>= curlBufferPause -- | Pause all. curlViewPauseAll :: CurlView -> IO () curlViewPauseAll view = listStoreToList (curlViewListStore view) >>= \list -> forM_ list curlBufferPause -- | Continue. curlViewContinue :: CurlView -> IO () curlViewContinue view@(CurlView {curlViewBuffer = buffer}) = treeViewGetSelectedValue (curlViewTreeView view) (curlViewSortModel view) (curlViewListStore view) >?>= curlBufferContinue buffer -- | Continue all. curlViewContinueAll :: CurlView -> IO () curlViewContinueAll view@(CurlView {curlViewBuffer = buffer}) = listStoreToList (curlViewListStore view) >>= \list -> forM_ list (curlBufferContinue buffer) -- | Delete download. curlViewDeleteDownload :: CurlView -> IO () curlViewDeleteDownload view@(CurlView {curlViewBuffer = buffer}) = treeViewGetSelectedValue (curlViewTreeView view) (curlViewSortModel view) (curlViewListStore view) >?>= curlBufferDeleteDownload buffer -- | Jump to file. curlViewJumpToFile :: CurlView -> IO () curlViewJumpToFile view = treeViewGetSelectedValue (curlViewTreeView view) (curlViewSortModel view) (curlViewListStore view) >?>= \ downloadFile -> do downloadStatus <- readTVarIO (dfDownloadStatus downloadFile) case downloadStatus of Failed -> pageViewUpdateOutputStatus view "No file." Nothing Finish -> do downloadDir <- fmap ( defaultDownloadDir) getConfigDirectory mkDaemonSignal (pageViewClient view) NewTab (NewTabArgs "PageFileManager" downloadDir []) _ -> do cacheDir <- fmap ( defaultCacheDir) getConfigDirectory mkDaemonSignal (pageViewClient view) NewTab (NewTabArgs "PageFileManager" cacheDir []) -- | Open file. curlViewOpenFile :: CurlView -> IO () curlViewOpenFile view = treeViewGetSelectedValue (curlViewTreeView view) (curlViewSortModel view) (curlViewListStore view) >?>= \ downloadFile -> do downloadStatus <- readTVarIO (dfDownloadStatus downloadFile) if downloadStatus == Finish -- Just try when file download complete. then do -- Init. downloadDir <- fmap ( defaultDownloadDir) getConfigDirectory name <- readTVarIO (dfName downloadFile) let filePath = downloadDir name isExist <- doesFileExist filePath if isExist -- Try open file. then do (_, fileType) <- contentTypeGuess filePath "" 0 openRule <- fileOpenRule filePath fileType if null openRule then pageViewUpdateOutputStatus view ("Don't know how to open file : " ++ filePath) Nothing else do let rule = snd $ head openRule -- use default open rule. rule (pageViewClient view) -- Otherwise print information. else pageViewUpdateOutputStatus view ("Can't find " ++ filePath) Nothing -- Otherwise print information. else pageViewUpdateOutputStatus view "Haven't download complete." Nothing -- | Keymap. curlViewKeymap :: Map Text (CurlView -> IO ()) curlViewKeymap = M.fromList [("j", curlViewNextNode) ,("k", curlViewPrevNode) ,("Down", curlViewNextNode) ,("Up", curlViewPrevNode) ,("J", curlViewScrollToBottom) ,("K", curlViewScrollToTop) ,(" ", curlViewScrollVerticalPage True) ,("b", curlViewScrollVerticalPage False) ,("PageDown", curlViewScrollVerticalPage True) ,("PageUp", curlViewScrollVerticalPage False) ,("a", curlViewAddDownload) ,("d", curlViewDeleteDownload) ,("p", curlViewPause) ,("P", curlViewPauseAll) ,("n", curlViewContinue) ,("N", curlViewContinueAll) ,("f", curlViewJumpToFile) ,("m", curlViewOpenFile) ,("3", curlViewSortByName) ,("4", curlViewSortBySize) ,("6", curlViewSortBySpeed) ,("7", curlViewSortByRestTime) ,("8", curlViewSortByThread) ,("9", curlViewSortByUrl) ,("t", curlViewTest) ] curlViewTest :: CurlView -> IO () curlViewTest view@(CurlView {curlViewBuffer = buffer}) = do let urls = ["http://3c.media.v4.skyrock.net/music/3c2/316/3c2316b00657d412725ce07c1c7a3900.mp3"] forM_ urls $ \ url -> do files <- readTVarIO $ curlBufferFileInfos buffer if url `elem` map dfURL files then pageViewUpdateOutputStatus view ("Has exist " ++ url) Nothing else curlBufferAddDownload buffer url