mirror of
https://gitlab.com/sheaf/metabrush.git
synced 2024-11-05 14:53:37 +00:00
add saving/loading
This commit is contained in:
parent
db4115c634
commit
5f3bbc891a
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@ assets/*.svg
|
||||||
assets/*/
|
assets/*/
|
||||||
refs/
|
refs/
|
||||||
img/examples
|
img/examples
|
||||||
|
files/
|
||||||
|
|
||||||
*.txt
|
*.txt
|
||||||
*.md
|
*.md
|
||||||
|
|
|
@ -132,10 +132,14 @@ executable MetaBrush
|
||||||
|
|
||||||
build-depends:
|
build-depends:
|
||||||
MetaBrush
|
MetaBrush
|
||||||
|
, atomic-file-ops
|
||||||
|
^>= 0.3.0.0
|
||||||
, bytestring
|
, bytestring
|
||||||
^>= 0.10.10.1
|
^>= 0.10.10.1
|
||||||
, directory
|
, directory
|
||||||
>= 1.3.4.0 && < 1.4
|
>= 1.3.4.0 && < 1.4
|
||||||
|
, filepath
|
||||||
|
^>= 1.4.2.1
|
||||||
, gi-gdk
|
, gi-gdk
|
||||||
>= 3.0.22 && < 3.1
|
>= 3.0.22 && < 3.1
|
||||||
, gi-gio
|
, gi-gio
|
||||||
|
|
|
@ -6,29 +6,20 @@
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE MultiParamTypeClasses #-}
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE TypeApplications #-}
|
||||||
{-# LANGUAGE UndecidableInstances #-}
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
module MetaBrush.Action
|
module MetaBrush.Action where
|
||||||
( HandleAction(..)
|
|
||||||
, quitEverything
|
|
||||||
, NewFile(..), OpenFile(..), Save(..), SaveAs(..), Close(..), Quit(..)
|
|
||||||
, Undo(..), Redo(..), Cut(..), Copy(..), Paste(..), Duplicate(..), Delete(..)
|
|
||||||
, Confirm(..)
|
|
||||||
, About(..)
|
|
||||||
, MouseMove(..), MouseClick(..), MouseRelease(..)
|
|
||||||
, Scroll(..), KeyboardPress(..), KeyboardRelease(..)
|
|
||||||
)
|
|
||||||
where
|
|
||||||
|
|
||||||
-- base
|
-- base
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
( guard, unless, void )
|
( guard, when, unless, void )
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
( for_ )
|
( for_ )
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
( catMaybes )
|
( catMaybes, listToMaybe)
|
||||||
import Data.Word
|
import Data.Word
|
||||||
( Word32 )
|
( Word32 )
|
||||||
|
|
||||||
|
@ -41,6 +32,8 @@ import Data.Act
|
||||||
)
|
)
|
||||||
|
|
||||||
-- containers
|
-- containers
|
||||||
|
import qualified Data.Map as Map
|
||||||
|
( insert )
|
||||||
import Data.Sequence
|
import Data.Sequence
|
||||||
( Seq(..) )
|
( Seq(..) )
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
|
@ -48,6 +41,14 @@ import qualified Data.Sequence as Seq
|
||||||
import qualified Data.Set as Set
|
import qualified Data.Set as Set
|
||||||
( insert, delete )
|
( insert, delete )
|
||||||
|
|
||||||
|
-- directory
|
||||||
|
import System.Directory
|
||||||
|
( doesDirectoryExist, listDirectory )
|
||||||
|
|
||||||
|
-- filepath
|
||||||
|
import System.FilePath
|
||||||
|
( (</>), (<.>), takeExtension )
|
||||||
|
|
||||||
-- gi-gdk
|
-- gi-gdk
|
||||||
import qualified GI.Gdk as GDK
|
import qualified GI.Gdk as GDK
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ import MetaBrush.Context
|
||||||
( UIElements(..), Variables(..)
|
( UIElements(..), Variables(..)
|
||||||
, Modifier(..), modifierKey
|
, Modifier(..), modifierKey
|
||||||
, HoldAction(..), PartialPath(..)
|
, HoldAction(..), PartialPath(..)
|
||||||
, withCurrentDocument, modifyingCurrentDocument
|
, currentDocument, withCurrentDocument, modifyingCurrentDocument
|
||||||
)
|
)
|
||||||
import MetaBrush.Document
|
import MetaBrush.Document
|
||||||
( Document(..)
|
( Document(..)
|
||||||
|
@ -86,6 +87,8 @@ import MetaBrush.Document.Selection
|
||||||
, translateSelection
|
, translateSelection
|
||||||
, deleteSelected
|
, deleteSelected
|
||||||
)
|
)
|
||||||
|
import MetaBrush.Document.Serialise
|
||||||
|
( saveDocument, loadDocument )
|
||||||
import MetaBrush.UI.Coordinates
|
import MetaBrush.UI.Coordinates
|
||||||
( toViewportCoordinates )
|
( toViewportCoordinates )
|
||||||
import MetaBrush.UI.InfoBar
|
import MetaBrush.UI.InfoBar
|
||||||
|
@ -125,7 +128,56 @@ data OpenFile = OpenFile TabLocation
|
||||||
deriving stock Show
|
deriving stock Show
|
||||||
|
|
||||||
instance HandleAction OpenFile where
|
instance HandleAction OpenFile where
|
||||||
handleAction _ _ _ = pure ()
|
handleAction uiElts@( UIElements { .. } ) vars@( Variables { .. } ) ( OpenFile tabLoc ) = do
|
||||||
|
fileChooser <-
|
||||||
|
GTK.fileChooserNativeNew ( Just "Open MetaBrush document..." ) ( Just window )
|
||||||
|
GTK.FileChooserActionOpen
|
||||||
|
( Just "Open" )
|
||||||
|
( Just "Cancel" )
|
||||||
|
GTK.fileChooserSetSelectMultiple fileChooser True
|
||||||
|
GTK.nativeDialogSetModal fileChooser True
|
||||||
|
fileFilter <- GTK.fileFilterNew
|
||||||
|
GTK.fileFilterSetName fileFilter ( Just "MetaBrush document" )
|
||||||
|
GTK.fileFilterAddPattern fileFilter "*.mb"
|
||||||
|
GTK.fileChooserAddFilter fileChooser fileFilter
|
||||||
|
void $ GTK.nativeDialogRun fileChooser
|
||||||
|
filePaths <- GTK.fileChooserGetFilenames fileChooser
|
||||||
|
for_ filePaths \ filePath -> do
|
||||||
|
mbDoc <- loadDocument uniqueSupply filePath
|
||||||
|
case mbDoc of
|
||||||
|
Left _errMessage -> pure () -- TODO: show warning dialog?
|
||||||
|
Right doc -> do
|
||||||
|
newFileTab vars uiElts ( Just doc ) tabLoc
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
-- Open folder --
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
data OpenFolder = OpenFolder TabLocation
|
||||||
|
deriving stock Show
|
||||||
|
|
||||||
|
instance HandleAction OpenFolder where
|
||||||
|
handleAction uiElts@( UIElements { .. } ) vars@( Variables { .. } ) ( OpenFolder tabLoc ) = do
|
||||||
|
fileChooser <-
|
||||||
|
GTK.fileChooserNativeNew ( Just "Select folder..." ) ( Just window )
|
||||||
|
GTK.FileChooserActionSelectFolder
|
||||||
|
( Just "Select Folder" )
|
||||||
|
( Just "Cancel" )
|
||||||
|
GTK.fileChooserSetSelectMultiple fileChooser True
|
||||||
|
GTK.nativeDialogSetModal fileChooser True
|
||||||
|
void $ GTK.nativeDialogRun fileChooser
|
||||||
|
folderPaths <- GTK.fileChooserGetFilenames fileChooser
|
||||||
|
for_ folderPaths \ folderPath -> do
|
||||||
|
exists <- doesDirectoryExist folderPath
|
||||||
|
when exists do
|
||||||
|
filePaths <- listDirectory folderPath
|
||||||
|
for_ filePaths \ filePath -> when ( takeExtension filePath == ".mb" ) do
|
||||||
|
mbDoc <- loadDocument uniqueSupply ( folderPath </> filePath )
|
||||||
|
case mbDoc of
|
||||||
|
Left _errMessage -> pure () -- TODO: show warning dialog?
|
||||||
|
Right doc -> do
|
||||||
|
newFileTab vars uiElts ( Just doc ) tabLoc
|
||||||
|
pure ()
|
||||||
|
|
||||||
---------------
|
---------------
|
||||||
-- Save file --
|
-- Save file --
|
||||||
|
@ -134,9 +186,24 @@ instance HandleAction OpenFile where
|
||||||
data Save = Save
|
data Save = Save
|
||||||
deriving stock Show
|
deriving stock Show
|
||||||
|
|
||||||
-- TODO
|
|
||||||
instance HandleAction Save where
|
instance HandleAction Save where
|
||||||
handleAction _ _ _ = pure ()
|
handleAction uiElts vars@( Variables { .. } ) _ = do
|
||||||
|
mbDoc <- STM.atomically $ currentDocument vars
|
||||||
|
case mbDoc of
|
||||||
|
Nothing -> pure () -- could show a warning message
|
||||||
|
Just doc@( Document { mbFilePath, unsavedChanges } )
|
||||||
|
| Nothing <- mbFilePath
|
||||||
|
-> saveAs uiElts vars doc
|
||||||
|
| False <- unsavedChanges
|
||||||
|
-> pure ()
|
||||||
|
| Just filePath <- mbFilePath
|
||||||
|
-> do
|
||||||
|
saveDocument filePath doc
|
||||||
|
STM.atomically $
|
||||||
|
STM.modifyTVar' openDocumentsTVar
|
||||||
|
( Map.insert ( documentUnique doc )
|
||||||
|
( doc { unsavedChanges = False } )
|
||||||
|
)
|
||||||
|
|
||||||
-------------
|
-------------
|
||||||
-- Save as --
|
-- Save as --
|
||||||
|
@ -145,9 +212,43 @@ instance HandleAction Save where
|
||||||
data SaveAs = SaveAs
|
data SaveAs = SaveAs
|
||||||
deriving stock Show
|
deriving stock Show
|
||||||
|
|
||||||
-- TODO
|
|
||||||
instance HandleAction SaveAs where
|
instance HandleAction SaveAs where
|
||||||
handleAction _ _ _ = pure ()
|
handleAction uiElts vars _ = do
|
||||||
|
mbDoc <- STM.atomically $ currentDocument vars
|
||||||
|
case mbDoc of
|
||||||
|
Nothing -> pure () -- could show a warning message
|
||||||
|
Just doc -> saveAs uiElts vars doc
|
||||||
|
|
||||||
|
saveAs :: UIElements -> Variables -> Document -> IO ()
|
||||||
|
saveAs ( UIElements { window } ) ( Variables { openDocumentsTVar } ) doc = do
|
||||||
|
fileChooser <-
|
||||||
|
GTK.fileChooserNativeNew ( Just "Save MetaBrush document..." ) ( Just window )
|
||||||
|
GTK.FileChooserActionSave
|
||||||
|
( Just "Save" )
|
||||||
|
( Just "Cancel" )
|
||||||
|
GTK.nativeDialogSetModal fileChooser True
|
||||||
|
GTK.fileChooserSetDoOverwriteConfirmation fileChooser True
|
||||||
|
fileFilter <- GTK.fileFilterNew
|
||||||
|
GTK.fileFilterSetName fileFilter ( Just "MetaBrush document" )
|
||||||
|
GTK.fileFilterAddPattern fileFilter "*.mb"
|
||||||
|
GTK.fileChooserAddFilter fileChooser fileFilter
|
||||||
|
void $ GTK.nativeDialogRun fileChooser
|
||||||
|
mbFilePath <- listToMaybe <$> GTK.fileChooserGetFilenames fileChooser
|
||||||
|
for_ mbFilePath \ filePath -> do
|
||||||
|
-- set the document's new file path, and modify to indicate "no unsaved changes"
|
||||||
|
let
|
||||||
|
fullFilePath :: FilePath
|
||||||
|
fullFilePath
|
||||||
|
| ".mb" <- takeExtension filePath
|
||||||
|
= filePath
|
||||||
|
| otherwise
|
||||||
|
= filePath <.> "mb"
|
||||||
|
saveDocument fullFilePath doc
|
||||||
|
STM.atomically $
|
||||||
|
STM.modifyTVar' openDocumentsTVar
|
||||||
|
( Map.insert ( documentUnique doc )
|
||||||
|
( doc { mbFilePath = Just fullFilePath, unsavedChanges = False } )
|
||||||
|
)
|
||||||
|
|
||||||
-----------
|
-----------
|
||||||
-- Close --
|
-- Close --
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
module MetaBrush.Document.Serialise
|
module MetaBrush.Document.Serialise
|
||||||
( documentToJSON, documentFromJSON )
|
( documentToJSON, documentFromJSON
|
||||||
|
, saveDocument, loadDocument
|
||||||
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
-- base
|
-- base
|
||||||
|
import Control.Monad
|
||||||
|
( unless )
|
||||||
import qualified Data.Bifunctor as Bifunctor
|
import qualified Data.Bifunctor as Bifunctor
|
||||||
( first )
|
( first )
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
|
@ -20,9 +24,15 @@ import Data.Functor.Identity
|
||||||
import Unsafe.Coerce
|
import Unsafe.Coerce
|
||||||
( unsafeCoerce ) -- Tony Morris special
|
( unsafeCoerce ) -- Tony Morris special
|
||||||
|
|
||||||
|
-- atomic-file-ops
|
||||||
|
import System.IO.AtomicFileOps
|
||||||
|
( atomicReplaceFile )
|
||||||
|
|
||||||
-- bytestring
|
-- bytestring
|
||||||
import qualified Data.ByteString as Strict
|
import qualified Data.ByteString as Strict
|
||||||
( ByteString )
|
( ByteString )
|
||||||
|
import qualified Data.ByteString as Strict.ByteString
|
||||||
|
( readFile )
|
||||||
import qualified Data.ByteString.Lazy as Lazy
|
import qualified Data.ByteString.Lazy as Lazy
|
||||||
( ByteString )
|
( ByteString )
|
||||||
import qualified Data.ByteString.Lazy.Builder as Lazy.ByteString.Builder
|
import qualified Data.ByteString.Lazy.Builder as Lazy.ByteString.Builder
|
||||||
|
@ -34,6 +44,14 @@ import Data.Sequence
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
( fromList )
|
( fromList )
|
||||||
|
|
||||||
|
-- directory
|
||||||
|
import System.Directory
|
||||||
|
( canonicalizePath, createDirectoryIfMissing, doesFileExist )
|
||||||
|
|
||||||
|
-- filepath
|
||||||
|
import System.FilePath
|
||||||
|
( takeDirectory )
|
||||||
|
|
||||||
-- scientific
|
-- scientific
|
||||||
import qualified Data.Scientific as Scientific
|
import qualified Data.Scientific as Scientific
|
||||||
( fromFloatDigits, toRealFloat )
|
( fromFloatDigits, toRealFloat )
|
||||||
|
@ -119,6 +137,29 @@ documentFromJSON uniqueSupply mfp
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- | Save a MetaBrush document to a file (in JSON format).
|
||||||
|
saveDocument :: FilePath -> Document -> IO ()
|
||||||
|
saveDocument path doc = do
|
||||||
|
path' <- canonicalizePath path
|
||||||
|
let
|
||||||
|
dir :: FilePath
|
||||||
|
dir = takeDirectory path'
|
||||||
|
createDirectoryIfMissing True dir
|
||||||
|
exists <- doesFileExist path
|
||||||
|
unless exists do
|
||||||
|
writeFile path ""
|
||||||
|
atomicReplaceFile Nothing path' ( documentToJSON doc )
|
||||||
|
|
||||||
|
-- | Load a MetaBrush document.
|
||||||
|
loadDocument :: UniqueSupply -> FilePath -> IO ( Either String Document )
|
||||||
|
loadDocument uniqueSupply fp = do
|
||||||
|
exists <- doesFileExist fp
|
||||||
|
if exists
|
||||||
|
then Bifunctor.first show <$> ( documentFromJSON uniqueSupply ( Just fp ) =<< Strict.ByteString.readFile fp )
|
||||||
|
else pure ( Left $ "No file at " <> fp )
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
encodeDouble :: Applicative f => JSON.Encoder f Double
|
encodeDouble :: Applicative f => JSON.Encoder f Double
|
||||||
encodeDouble = contramap Scientific.fromFloatDigits JSON.Encoder.scientific
|
encodeDouble = contramap Scientific.fromFloatDigits JSON.Encoder.scientific
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
|
||||||
{-# LANGUAGE TypeOperators #-}
|
|
||||||
|
|
||||||
module MetaBrush.Event
|
module MetaBrush.Event
|
||||||
( handleEvents )
|
( handleEvents )
|
||||||
|
|
|
@ -90,7 +90,7 @@ data instance MenuItem action submenu Description
|
||||||
, menuItemAccel :: !( Maybe ( Word32, [ Modifier ] ) )
|
, menuItemAccel :: !( Maybe ( Word32, [ Modifier ] ) )
|
||||||
, submenuDescription :: !( submenu Description )
|
, submenuDescription :: !( submenu Description )
|
||||||
}
|
}
|
||||||
data instance MenuItem action submenu Object
|
data instance MenuItem action submenu Object
|
||||||
= MenuItem
|
= MenuItem
|
||||||
{ menuItem :: !GTK.MenuItem
|
{ menuItem :: !GTK.MenuItem
|
||||||
, menuItemSubmenu :: !( submenu Object )
|
, menuItemSubmenu :: !( submenu Object )
|
||||||
|
@ -122,12 +122,13 @@ data Menu ( rt :: ResourceType )
|
||||||
|
|
||||||
data FileMenu ( rt :: ResourceType )
|
data FileMenu ( rt :: ResourceType )
|
||||||
= FileMenu
|
= FileMenu
|
||||||
{ new :: !( MenuItem NewFile NoSubresource rt )
|
{ new :: !( MenuItem NewFile NoSubresource rt )
|
||||||
, open :: !( MenuItem OpenFile NoSubresource rt )
|
, openFile :: !( MenuItem OpenFile NoSubresource rt )
|
||||||
, save :: !( MenuItem Save NoSubresource rt )
|
, openFolder :: !( MenuItem OpenFolder NoSubresource rt )
|
||||||
, saveAs :: !( MenuItem SaveAs NoSubresource rt )
|
, save :: !( MenuItem Save NoSubresource rt )
|
||||||
, close :: !( MenuItem Close NoSubresource rt )
|
, saveAs :: !( MenuItem SaveAs NoSubresource rt )
|
||||||
, quit :: !( MenuItem Quit NoSubresource rt )
|
, close :: !( MenuItem Close NoSubresource rt )
|
||||||
|
, quit :: !( MenuItem Quit NoSubresource rt )
|
||||||
}
|
}
|
||||||
deriving stock Generic
|
deriving stock Generic
|
||||||
|
|
||||||
|
@ -178,12 +179,13 @@ menuDescription
|
||||||
fileMenuDescription :: FileMenu Description
|
fileMenuDescription :: FileMenu Description
|
||||||
fileMenuDescription
|
fileMenuDescription
|
||||||
= FileMenu
|
= FileMenu
|
||||||
{ new = MenuItemDescription "New" [ "submenuItem" ] ( NewFile AfterCurrentTab ) ( Just ( GDK.KEY_N, [ Control L ] ) ) NoSubresource
|
{ new = MenuItemDescription "New" [ "submenuItem" ] ( NewFile AfterCurrentTab ) ( Just ( GDK.KEY_N, [ Control L ] ) ) NoSubresource
|
||||||
, open = MenuItemDescription "Open" [ "submenuItem" ] ( OpenFile AfterCurrentTab ) ( Just ( GDK.KEY_O, [ Control L ] ) ) NoSubresource
|
, openFile = MenuItemDescription "Open file" [ "submenuItem" ] ( OpenFile AfterCurrentTab ) ( Just ( GDK.KEY_O, [ Control L ] ) ) NoSubresource
|
||||||
, save = MenuItemDescription "Save" [ "submenuItem" ] Save ( Just ( GDK.KEY_S, [ Control L ] ) ) NoSubresource
|
, openFolder = MenuItemDescription "Open folder" [ "submenuItem" ] ( OpenFolder AfterCurrentTab ) ( Just ( GDK.KEY_O, [ Control L, Shift L ] ) ) NoSubresource
|
||||||
, saveAs = MenuItemDescription "Save as" [ "submenuItem" ] SaveAs ( Just ( GDK.KEY_S, [ Control L, Shift L ] ) ) NoSubresource
|
, save = MenuItemDescription "Save" [ "submenuItem" ] Save ( Just ( GDK.KEY_S, [ Control L ] ) ) NoSubresource
|
||||||
, close = MenuItemDescription "Close" [ "submenuItem" ] Close ( Just ( GDK.KEY_W, [ Control L ] ) ) NoSubresource
|
, saveAs = MenuItemDescription "Save as" [ "submenuItem" ] SaveAs ( Just ( GDK.KEY_S, [ Control L, Shift L ] ) ) NoSubresource
|
||||||
, quit = MenuItemDescription "Quit" [ "submenuItem" ] Quit ( Just ( GDK.KEY_Q, [ Control L ] ) ) NoSubresource
|
, close = MenuItemDescription "Close" [ "submenuItem" ] Close ( Just ( GDK.KEY_W, [ Control L ] ) ) NoSubresource
|
||||||
|
, quit = MenuItemDescription "Quit" [ "submenuItem" ] Quit ( Just ( GDK.KEY_Q, [ Control L ] ) ) NoSubresource
|
||||||
}
|
}
|
||||||
|
|
||||||
editMenuDescription :: EditMenu Description
|
editMenuDescription :: EditMenu Description
|
||||||
|
|
Loading…
Reference in a new issue