-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFilesInConflict.hs
More file actions
298 lines (267 loc) · 13.2 KB
/
FilesInConflict.hs
File metadata and controls
298 lines (267 loc) · 13.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
-----------------------------------------------------------------------------
--
-- Module : VCSGui.Common.FilesInConflict
-- Copyright : 2011 Stephan Fortelny, Harald Jagenteufel
-- License : GPL
--
-- Maintainer : stephanfortelny at gmail.com, h.jagenteufel at gmail.com
-- Stability :
-- Portability :
--
-- | Provides a GUI to show and resolve conflicts in a VCS.
--
-----------------------------------------------------------------------------
module VCSGui.Common.FilesInConflict (
showFilesInConflictGUI
) where
import qualified VCSWrapper.Common as Wrapper
import qualified VCSGui.Common.GtkHelper as H
import qualified VCSGui.Common.Commit as Commit
import qualified VCSGui.Common.MergeTool as Merge
import qualified VCSGui.Common.Process as Process
import qualified VCSGui.Common.ConflictsResolved as ConflictsResolvedGUI
import qualified VCSGui.Common.Error as Error
import Control.Monad.Trans(liftIO)
import Control.Monad
import Control.Monad.Reader
import Paths_vcsgui(getDataFileName)
import Data.Text (Text)
import qualified Data.Text as T (unpack, pack)
import GI.Gtk.Objects.TreeView (treeViewSetModel, TreeView(..))
import GI.Gtk.Objects.Action (onActionActivate)
import GI.Gtk.Enums (ResponseType(..), FileChooserAction(..))
import GI.Gtk.Objects.Widget (widgetDestroy, widgetShowAll)
import GI.Gtk.Objects.CellRendererText (cellRendererTextNew)
import GI.Gtk.Objects.CellRendererToggle
(onCellRendererToggleToggled, cellRendererToggleNew)
import GI.Gtk.Interfaces.TreeModel (treeModelGetIterFromString)
import GI.Gtk.Objects.Builder (builderGetObject, Builder(..))
import Data.GI.Base.BasicTypes
(ManagedPtr(..), GObject)
import Data.GI.Base.ManagedPtr (unsafeCastTo)
import Data.GI.Gtk.ModelView.SeqStore
(seqStoreAppend, seqStoreClear, seqStoreToList,
seqStoreSetValue, seqStoreIterToIndex, seqStoreGetValue,
seqStoreNew, SeqStore(..))
import GI.Gtk.Objects.Window
(setWindowTransientFor, setWindowTitle, Window(..))
import Data.GI.Base.GObject (new')
import GI.Gtk.Objects.FileChooserDialog (FileChooserDialog(..))
import GI.Gtk.Objects.Dialog (dialogRun, dialogAddButton)
import GI.Gtk.Interfaces.FileChooser
(fileChooserGetFilename, setFileChooserAction)
import Data.Maybe (fromJust)
import GI.Gtk
(setCellRendererToggleActive, setCellRendererTextText)
--
-- glade path and object accessors
--
getGladepath = getDataFileName "data/guiCommonFilesInConflict.glade"
accessorWindowFilesInConflict = "windowFilesInConflict"
accessorTreeViewFiles = "treeViewFiles"
accessorActResolved = "actResolved"
accessorActCancel = "actCancel"
accessorActBrowsePath = "actBrowsePath"
accessorEntPath = "entPath"
--
-- types
--
-- | Handler being called after all files have been resolved and resolved button is pressed
type Handler = Wrapper.Ctx()
-- fn to set seqStore model for treeview
type TreeViewSetter = (Maybe FilePath) -- ^ Maybe cwd
-> [FilePath] -- ^ conflicting files
-> (FilePath -> Wrapper.Ctx [FilePath]) -- ^ fn receiving a path to a conflicting file and returning all conflicting files involved in the conflict (max 4)
-> (FilePath -> Wrapper.Ctx ()) -- ^ fn to mark files as resolved in VCS
-> (Either Merge.MergeTool Merge.MergeToolSetter) -- ^ either a mergetool or fn to set one
-> H.TextEntryItem -- ^ the entry to get the path to the mergetool from
-> TreeView -- ^ the treeview to set the model to
-> Wrapper.Ctx (SeqStore SCFile)
-- GUI storing accessible elements
data GUI = GUI {
windowFilesInConflict :: H.WindowItem
, treeViewFiles :: H.TreeViewItem SCFile
, actResolved :: H.ActionItem
, actCancel :: H.ActionItem
, actBrowsePath :: H.ActionItem
, entPath :: H.TextEntryItem
}
--model for treestore
data SCFile = SCFile {
filePath :: FilePath
, isResolved :: Bool
}
deriving (Show)
-- | Shows a GUI showing conflicting files and providing means to resolve the conflicts.
showFilesInConflictGUI :: (Maybe TreeViewSetter) -- ^ fn to set seqStore model for treeview, Nothing for default
-> [FilePath] -- ^ conflicting files
-> (FilePath -> Wrapper.Ctx [FilePath]) -- ^ fn receiving a path to a conflicting file and returning all conflicting files involved in the conflict (max 4)
-> (FilePath -> Wrapper.Ctx ()) -- ^ fn to mark files as resolved in VCS
-> (Either Merge.MergeTool Merge.MergeToolSetter) -- ^ 'MergeTool' is used for any possible conflicts. If not present user will be asked to provide 'MergeTool' on conflicts. 'MergeToolSetter' will be called for response.
-> Handler -- ^ 'Handler' for action resolved
-> Wrapper.Ctx ()
showFilesInConflictGUI Nothing f g m e a =
showFilesInConflictGUI (Just defaultSetUpTreeView) f g m e a
showFilesInConflictGUI (Just setUpTreeView) filesInConflict filesToResolveGetter resolveMarker eMergeToolSetter actResolvedHandler = do
liftIO $ putStrLn "Starting files in conflict gui ..."
config <- ask
let cwd = (Wrapper.configCwd config)
gui <- loadGUI $ setUpTreeView cwd filesInConflict filesToResolveGetter resolveMarker eMergeToolSetter
mbMergeToolSetter <- case eMergeToolSetter of
Left (Merge.MergeTool path) -> do
liftIO $ H.set (entPath gui) $ T.pack path
return Nothing
Right setter -> return $ Just setter
-- connect actions
liftIO $ H.registerClose $ windowFilesInConflict gui
liftIO $ H.registerCloseAction (actCancel gui) (windowFilesInConflict gui)
config <- ask
liftIO $ onActionActivate (H.getItem (actResolved gui)) $ do
--TODO check if all files have been resolved
Wrapper.runVcs config $ actResolvedHandler
H.closeWin (windowFilesInConflict gui)
liftIO $ onActionActivate (H.getItem (actBrowsePath gui)) $ do
mbPath <- showFolderChooserDialog "Select executable" (H.getItem $ windowFilesInConflict gui) FileChooserActionOpen
case mbPath of
Nothing -> return ()
Just path -> do
-- update gui
H.set (entPath gui) $ T.pack path
-- call setter
case mbMergeToolSetter of
Nothing -> return ()
Just setter -> setter (Merge.MergeTool path)
return ()
-- present window
liftIO $ widgetShowAll $ H.getItem $ windowFilesInConflict gui
return ()
loadGUI :: (H.TextEntryItem -> TreeView -> Wrapper.Ctx (SeqStore SCFile))
-- ^ (The entry to get the path to the mergetool from. , treeview to setup, fn to set seqStore model for treeview
-> Wrapper.Ctx GUI
loadGUI setUpTreeView = do
gladepath <- liftIO getGladepath
builder <- liftIO $ H.openGladeFile gladepath
win <- liftIO $ H.getWindowFromGlade builder accessorWindowFilesInConflict
entPath <- liftIO $ H.getTextEntryFromGlade builder accessorEntPath
treeViewFiles <- getTreeViewFromGladeCustomStore builder accessorTreeViewFiles (setUpTreeView entPath)
actResolved <- liftIO $ H.getActionFromGlade builder accessorActResolved
actCancel <- liftIO $ H.getActionFromGlade builder accessorActCancel
actBrowsePath <- liftIO $ H.getActionFromGlade builder accessorActBrowsePath
return $ GUI win treeViewFiles actResolved actCancel actBrowsePath entPath
defaultSetUpTreeView :: TreeViewSetter
defaultSetUpTreeView mbcwd conflictingFiles filesToResolveGetter resolveMarker eMergeToolSetter entPath listView = do
config <- ask
liftIO $ do
-- create model
seqStore <- seqStoreNew [
(SCFile fileName
(False))
| fileName <- conflictingFiles]
treeViewSetModel listView (Just seqStore)
let treeViewItem = (seqStore, listView)
renderer <- cellRendererTextNew
H.addColumnToTreeView' treeViewItem
renderer
"File"
$ \cell scf -> setCellRendererTextText cell . T.pack $ filePath scf
renderer <- cellRendererToggleNew
H.addColumnToTreeView' treeViewItem
renderer
"Resolved"
$ \cell scf -> setCellRendererToggleActive cell $ isResolved scf
-- connect select action
onCellRendererToggleToggled renderer $ \(columnId :: Text) -> do
putStrLn $ "Checkbutton clicked at column " ++ (show columnId)
--TODO only call tool if button is not checked, move this code to being called if a click on row is received
let callTool' = (\path -> Wrapper.runVcs config $ callTool columnId seqStore path)
mbPath <- H.get entPath
case mbPath of
Nothing -> Error.showErrorGUI "MergeTool not set. Set MergeTool first."
Just path -> callTool' path
return ()
return seqStore
where
callTool columnId seqStore pathToTool = do
config <- ask
(True, treeIter) <- liftIO $ treeModelGetIterFromString seqStore columnId
value <- liftIO $ seqStoreGetValue seqStore =<< seqStoreIterToIndex treeIter
filesToResolve <- filesToResolveGetter $ filePath value
resolvedByTool <- liftIO $ Process.exec mbcwd pathToTool $ map T.pack filesToResolve
let setResolved' = setResolved seqStore treeIter value
case resolvedByTool of
False -> ConflictsResolvedGUI.showConflictsResolvedGUI
(\resolved -> setResolved' resolved)
True -> setResolved seqStore treeIter value True
return()
setResolved seqStore treeIter oldValue isResolved = do
let fp = filePath oldValue
case isResolved of
False -> return ()
True -> resolveMarker fp
let newValue = (\(SCFile fp b) -> SCFile fp isResolved)
oldValue
n <- seqStoreIterToIndex treeIter
liftIO $ seqStoreSetValue seqStore n newValue
return ()
----
---- HELPERS
----
getTreeViewFromGladeCustomStore :: Builder
-> Text
-> (TreeView -> Wrapper.Ctx (SeqStore SCFile)) -- ^ fn defining how to setup the liststore
-> Wrapper.Ctx (H.TreeViewItem SCFile)
getTreeViewFromGladeCustomStore builder name setupSeqStore = do
(_, tView) <- liftIO $ wrapWidget builder TreeView name
store <- setupSeqStore tView
let getter = getFromSeqStore (store, tView)
setter = setToSeqStore (store, tView)
return (name, (store, tView), (getter, setter))
---
--- same as gtkhelper, but avoiding exposing it
---
wrapWidget :: GObject objClass =>
Builder
-> (ManagedPtr objClass -> objClass)
-> Text -> IO (Text, objClass)
wrapWidget builder constructor name = do
putStrLn $ " cast " ++ T.unpack name
gobj <- builderGetObject builder name >>= unsafeCastTo constructor . fromJust
return (name, gobj)
getFromSeqStore :: (SeqStore a, TreeView)
-> IO (Maybe [a])
getFromSeqStore (store, _) = do
list <- seqStoreToList store
if null list
then return Nothing
else return $ Just list
setToSeqStore :: (SeqStore a, TreeView)
-> [a]
-> IO ()
setToSeqStore (store, view) newList = do
seqStoreClear store
mapM_ (seqStoreAppend store) newList
return ()
-- HELPER
-- | shows a dialog to choose a folder, returns Just FilePath to folder if succesfull, Nothing if cancelled
showFolderChooserDialog :: Text -- ^ title of the window
-> Window -- ^ parent window
-> FileChooserAction
-> IO (Maybe FilePath)
showFolderChooserDialog title parent fcAction = do
dialog <- new' FileChooserDialog []
setWindowTitle dialog title
dialogAddButton dialog "gtk-cancel" (fromIntegral $ fromEnum ResponseTypeCancel)
dialogAddButton dialog "Select" (fromIntegral $ fromEnum ResponseTypeAccept)
setWindowTransientFor dialog parent
setFileChooserAction dialog fcAction
response <- dialogRun dialog
case toEnum $ fromIntegral response of
ResponseTypeCancel -> widgetDestroy dialog >> return Nothing
ResponseTypeDeleteEvent -> widgetDestroy dialog >> return Nothing
ResponseTypeAccept -> do
f <- fileChooserGetFilename dialog
widgetDestroy dialog
return f