-- 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.PdfViewer.PdfView where import Control.Applicative import Control.Arrow import Control.Concurrent.STM import Control.Monad.State import Data.Map (Map) import Data.Text.Lazy (Text) import Data.Typeable import Graphics.Rendering.Cairo import Graphics.UI.Gtk hiding (Statusbar, statusbarNew, get) import Graphics.UI.Gtk.Gdk.SerializedEvent import Graphics.UI.Gtk.Poppler.Document import Graphics.UI.Gtk.Poppler.Page import Manatee.Core.PageView import Manatee.Core.Types import Manatee.Extension.PdfViewer.PdfBuffer import Manatee.Toolkit.General.Basic import Manatee.Toolkit.General.STM import Manatee.Toolkit.Gtk.Event import Manatee.Toolkit.Gtk.Gtk import Manatee.Toolkit.Gtk.ScrolledWindow import qualified Data.Map as M data PdfView = PdfView {pdfViewPlugId :: TVar PagePlugId ,pdfViewScrolledWindow :: ScrolledWindow ,pdfViewView :: DrawingArea ,pdfViewBuffer :: PdfBuffer ,pdfViewPageIndex :: TVar Int ,pdfViewScale :: TVar Double } deriving Typeable instance PageBuffer PdfBuffer where pageBufferGetName = readTVarIO . pdfBufferPath pageBufferSetName a = writeTVarIO (pdfBufferPath a) pageBufferClient = pdfBufferClient pageBufferCreateView a pId = PageViewWrap <$> pdfViewNew a pId pageBufferMode = pdfBufferMode instance PageView PdfView where pageViewBuffer = PageBufferWrap . pdfViewBuffer pageViewPlugId = pdfViewPlugId pageViewFocus = widgetGrabFocus . pdfViewView pageViewScrolledWindow = pdfViewScrolledWindow pageViewHandleKeyAction = pdfViewHandleKeyAction -- | New pdf viewer view. pdfViewNew :: PdfBuffer -> PagePlugId -> IO PdfView pdfViewNew buffer plugId = do -- Create plug id. pId <- newTVarIO plugId -- Create UI frame. scrolledWindow <- scrolledWindowNew_ -- Create view. view <- drawingAreaNew scrolledWindowAddWithViewport scrolledWindow view pageIndex <- newTVarIO 0 scale <- newTVarIO 0 let viewer = PdfView pId scrolledWindow view buffer pageIndex scale -- Redraw signal. view `on` exposeEvent $ tryEvent $ pdfViewHandleExpose viewer return viewer -- | Handle expose signal. pdfViewHandleExpose :: PdfView -> EventM EExpose () pdfViewHandleExpose view@(PdfView {pdfViewBuffer = buffer ,pdfViewScale = viewScale }) = do (winWidth, _) <- eventWindowSize liftIO $ do let (pageWidth, pageHeight) = pdfBufferPageSize buffer area = pdfViewView view -- Init scale and size request. readTVarIO viewScale >>= \ value -> -- Update scale value and area size request when haven't set. when (value == 0) $ do -- Update scale value. let scaleValue = (winWidth / pageWidth) writeTVarIO viewScale scaleValue -- Update size request. widgetSetSizeRequest area (truncate winWidth) (truncate (scaleValue * pageHeight * i2d (pdfBufferNPages buffer))) -- Draw. pdfViewDraw view -- | Draw for viewer. pdfViewDraw :: PdfView -> IO () pdfViewDraw view@(PdfView {pdfViewBuffer = buffer ,pdfViewScale = viewScale ,pdfViewPageIndex = pageIndex }) = do -- Init. let (_, pageHeight) = pdfBufferPageSize buffer doc = pdfBufferDocument buffer area = pdfViewView view -- Get render index range and scale value. (indexRange, currentIndex) <- pdfViewGetIndexRange view scaleValue <- readTVarIO viewScale -- Update page information. writeTVarIO pageIndex currentIndex pageViewUpdateInfoStatus view "Page" ("Page (" ++ show (currentIndex + 1) ++ "/" ++ show (pdfBufferNPages buffer) ++ ")") -- Render page. forM_ indexRange $ \ index -> do page <- documentGetPage doc index drawWindow <- widgetGetDrawWindow area renderWithDrawable drawWindow $ do setSourceRGB 1.0 1.0 1.0 translate 0 (scaleValue * pageHeight * i2d index) scale scaleValue scaleValue pageRender page -- | Handle key action. pdfViewHandleKeyAction :: PdfView -> Text -> SerializedEvent -> IO () pdfViewHandleKeyAction view keystoke sEvent = case M.lookup keystoke pdfViewKeymap of Just action -> action view Nothing -> widgetPropagateEvent (pdfViewView view) sEvent -- | Update adjustment. pdfViewUpdateAdjustment :: PdfView -> (Double, Double) -> IO () pdfViewUpdateAdjustment view (width, height) = do let sWin = pdfViewScrolledWindow view hAdj <- scrolledWindowGetHAdjustment sWin vAdj <- scrolledWindowGetVAdjustment sWin adjustmentSetUpper hAdj width adjustmentSetUpper vAdj height -- | Keymap. pdfViewKeymap :: Map Text (PdfView -> IO ()) pdfViewKeymap = M.fromList [("j", pageViewScrollVerticalStep True) ,("k", pageViewScrollVerticalStep False) ,("Down", pageViewScrollVerticalStep True) ,("Up", pageViewScrollVerticalStep False) ,(" ", pageViewScrollVerticalPage True) ,("b", pageViewScrollVerticalPage False) ,("J", pageViewScrollToBottom) ,("K", pageViewScrollToTop) ,("n", pdfViewNextPage) ,("p", pdfViewPrevPage) ,("N", pdfViewLastPage) ,("P", pdfViewFirstPage) ] -- | Get index range to render visible are. pdfViewGetIndexRange :: PdfView -> IO ([Int], Int) pdfViewGetIndexRange PdfView {pdfViewBuffer = buffer ,pdfViewScale = scale ,pdfViewScrolledWindow = scrolledWindow} = do vAdj <- scrolledWindowGetVAdjustment scrolledWindow vAdjTop <- adjustmentGetValue vAdj vAdjSize <- adjustmentGetPageSize vAdj scaleValue <- readTVarIO scale let vAdjBottom = vAdjTop + vAdjSize (_, height) = ((*) scaleValue *** (*) scaleValue) $ pdfBufferPageSize buffer nPages = pdfBufferNPages buffer topIndex = truncate $ vAdjTop / height bottonIndex = truncate $ vAdjBottom / height startIndex = (\x -> if x < 0 then 0 else x) topIndex endIndex = (\x -> if x >= nPages then nPages - 1 else x) $ if vAdjBottom - i2d bottonIndex * height == 0.0 then bottonIndex - 1 else bottonIndex -- Use point at half of visible area avoid bound value problem. currIndex = truncate $ (vAdjTop + vAdjSize / 2) / height return ([startIndex..endIndex], currIndex) -- | View next page. pdfViewNextPage :: PdfView -> IO () pdfViewNextPage PdfView {pdfViewBuffer = buffer ,pdfViewPageIndex = pageIndex ,pdfViewScale = viewScale ,pdfViewScrolledWindow = scrolledWindow} = do currentIndex <- readTVarIO pageIndex scaleValue <- readTVarIO viewScale let (_, height) = pdfBufferPageSize buffer maxIndex = pdfBufferNPages buffer - 1 nextIndex | currentIndex >= maxIndex = maxIndex | otherwise = currentIndex + 1 vAdj <- scrolledWindowGetVAdjustment scrolledWindow adjustmentSetValue vAdj (i2d nextIndex * height * scaleValue) -- | View prev page. pdfViewPrevPage :: PdfView -> IO () pdfViewPrevPage PdfView {pdfViewBuffer = buffer ,pdfViewPageIndex = pageIndex ,pdfViewScale = viewScale ,pdfViewScrolledWindow = scrolledWindow} = do currentIndex <- readTVarIO pageIndex scaleValue <- readTVarIO viewScale let (_, height) = pdfBufferPageSize buffer prevIndex | currentIndex <= 0 = 0 | otherwise = currentIndex - 1 vAdj <- scrolledWindowGetVAdjustment scrolledWindow adjustmentSetValue vAdj (i2d prevIndex * height * scaleValue) -- | First page. pdfViewFirstPage :: PdfView -> IO () pdfViewFirstPage = pageViewScrollToTop -- | Last page. pdfViewLastPage :: PdfView -> IO () pdfViewLastPage PdfView {pdfViewBuffer = buffer ,pdfViewScale = viewScale ,pdfViewScrolledWindow = scrolledWindow} = do let (_, height) = pdfBufferPageSize buffer maxIndex = pdfBufferNPages buffer - 1 scaleValue <- readTVarIO viewScale vAdj <- scrolledWindowGetVAdjustment scrolledWindow adjustmentSetValue vAdj (i2d maxIndex * height * scaleValue)