30 Commits

Author SHA1 Message Date
a452b44cfe Blub 2016-04-26 12:23:47 +02:00
8bcdb84efd LIB: add mkdirP 2016-04-26 01:45:24 +02:00
746daf9ba6 LIB: first try of bookmarks 2016-04-25 02:08:44 +02:00
251a20e881 GTK: minor fixes 2016-04-24 20:01:22 +02:00
c29693fbd0 GTK: allow to open terminal at current directory
TODO: terminal needs to be configurable
2016-04-24 20:00:34 +02:00
9420af15a1 README: update image 2016-04-24 18:48:13 +02:00
3008e4463b GTK: implement tabs wrt #45
This also restructures the meaning of MyGUI and MyView.
They are now more strictly a hierarchy and everything that may
be specific to a view (like urlBar) has been moved into the MyView
context.

In addition, this also fixes #42
2016-04-24 18:38:47 +02:00
hasufell
44fc047223 Minor addition in HACKING.md 2016-04-21 02:50:10 +02:00
hasufell
8348f34a4a Improve HACKING.md 2016-04-21 02:45:23 +02:00
a4c8995299 GTK: formatting and comments 2016-04-20 17:59:55 +02:00
0ff24002e5 GTK: avoid subsequent duplicate entries in history 2016-04-20 17:27:47 +02:00
7608d838aa GTK: improve history navigation
* increase maximum numbers of items in the history
* empty the "forward" history if we enter a directory
  via normal navigation
2016-04-20 17:20:04 +02:00
d432c2146b GTK: improve file property dialog
Adds the following fields:
* file type
* permissions
* link destination of symlink (if applicable)
2016-04-20 17:16:58 +02:00
064d5a1032 LIB/GTK: use strict data types where possible 2016-04-20 16:34:26 +02:00
39bc0cba24 GTK: tweak GUI appearance 2016-04-20 16:33:39 +02:00
07c5fa2d62 GTK: fix imports 2016-04-20 16:33:18 +02:00
5c57551438 GTK: restructure Callbacks.hs to make it more readable 2016-04-20 01:25:40 +02:00
3c6aca04b4 GTK: fix history when clicking on HOME button 2016-04-20 00:48:34 +02:00
9d572c8a6e GTK: remove obsolete void usage 2016-04-20 00:39:53 +02:00
680a75f5be GTK: implement rudimentary history support wrt #21
5 items back and forth only. Implemented via a simple
TVar ([], []). Might be improved in the future.
2016-04-20 00:38:22 +02:00
4b0e3ba89a Add symbolic link to hacking/HACKING.md 2016-04-19 21:17:17 +02:00
02f04d92f3 Fix missing whitespace in hsimport.hs prettyPrint String 2016-04-19 21:05:52 +02:00
a61b409486 GTK: implement fileinfo dialog wrt #32 2016-04-19 21:05:29 +02:00
a7ba20ae00 README: fix installation instructions 2016-04-18 01:59:46 +02:00
9b43814846 GTK: clear move buffer after move
Doesn't make sense to keep it since the buffer
will probably point to a non-existing file afterwards.
2016-04-18 01:02:18 +02:00
8b8c9a669f GTK: implement file drag and drop
Still doesn't work on multiple rows.

Also see #14
2016-04-18 00:51:45 +02:00
7f538f4fae Update hpath submodule 2016-04-18 00:28:10 +02:00
1d2bf37a44 GTK: implement creating new directories
And moving it with creating new files to a submenuitem.
2016-04-17 03:12:34 +02:00
2e16e0ae48 HACKING: add note about absolute paths 2016-04-17 01:44:53 +02:00
260e7ea01c HACKING: fix newlines in hsimport.hs 2016-04-17 01:41:36 +02:00
18 changed files with 1473 additions and 598 deletions

2
3rdparty/hpath vendored

1
HACKING.md Symbolic link
View File

@@ -0,0 +1 @@
hacking/HACKING.md

View File

@@ -15,12 +15,21 @@ Design goals:
Screenshots Screenshots
----------- -----------
![Image missing](https://cloud.githubusercontent.com/assets/1241845/14584163/6dbef950-0439-11e6-8a6e-2352c048775e.png "hsfm-gtk") ![hsfm](https://cloud.githubusercontent.com/assets/1241845/14768900/06efd43c-0a4d-11e6-939e-6b067bdb47ce.png "hsfm-gtk")
Installation Installation
------------ ------------
``` ```
git submodule update --init --recursive
cabal sandbox init
cabal sandbox add-source 3rdparty/hinotify
cabal sandbox add-source 3rdparty/hpath
cabal sandbox add-source 3rdparty/hpath/3rdparty/posix-paths
cabal sandbox add-source 3rdparty/simple-sendfile
cabal install alex happy
export PATH="$(pwd)/.cabal-sandbox/bin:$PATH"
cabal install gtk2hs-buildtools
cabal install cabal install
``` ```

View File

@@ -2,113 +2,281 @@
<!-- Generated with glade 3.18.3 --> <!-- Generated with glade 3.18.3 -->
<interface> <interface>
<requires lib="gtk+" version="3.16"/> <requires lib="gtk+" version="3.16"/>
<object class="GtkGrid" id="fpropGrid">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">2</property>
<property name="column_spacing">2</property>
<property name="row_homogeneous">True</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">File Name:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropFnEntry">
<property name="width_request">350</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">Location:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">Total Size:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropLocEntry">
<property name="width_request">350</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropTsEntry">
<property name="width_request">350</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">Accessed:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">Modified:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropModEntry">
<property name="width_request">350</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropAcEntry">
<property name="width_request">350</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">7</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">File Type:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropFTEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropPermEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">Link Destination:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_left">5</property>
<property name="label" translatable="yes">Permissions:</property>
<attributes>
<attribute name="weight" value="bold"/>
</attributes>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="fpropLDEntry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="margin_left">5</property>
<property name="margin_right">5</property>
<property name="margin_top">2</property>
<property name="margin_bottom">2</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">5</property>
</packing>
</child>
</object>
<object class="GtkImage" id="image1"> <object class="GtkImage" id="image1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="stock">gtk-edit</property> <property name="stock">gtk-edit</property>
</object> </object>
<object class="GtkMenu" id="rcMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="rcFileOpen">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileExecute">
<property name="label">gtk-execute</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileNew">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileCut">
<property name="label">gtk-cut</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileCopy">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileRename">
<property name="label">Rename</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image1</property>
<property name="use_stock">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFilePaste">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileDelete">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
<object class="GtkImage" id="image2"> <object class="GtkImage" id="image2">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="stock">gtk-edit</property> <property name="stock">gtk-open</property>
</object> </object>
<object class="GtkImage" id="image3"> <object class="GtkImage" id="image3">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<property name="stock">gtk-cancel</property> <property name="stock">gtk-cancel</property>
</object> </object>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-zoom-fit</property>
</object>
<object class="GtkImage" id="image5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-zoom-fit</property>
</object>
<object class="GtkApplicationWindow" id="rootWin"> <object class="GtkApplicationWindow" id="rootWin">
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child> <child>
@@ -130,33 +298,6 @@
<object class="GtkMenu" id="menu1"> <object class="GtkMenu" id="menu1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="menubarFileOpen">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menubarFileExecute">
<property name="label">gtk-execute</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menubarFileNew">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child> <child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem1"> <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property> <property name="visible">True</property>
@@ -176,65 +317,6 @@
</child> </child>
</object> </object>
</child> </child>
<child>
<object class="GtkMenuItem" id="menubarEdit">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Edit</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="menubarEditCut">
<property name="label">gtk-cut</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menubarEditCopy">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menubarEditRename">
<property name="label">Move</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image2</property>
<property name="use_stock">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menubarEditPaste">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menubarEditDelete">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child> <child>
<object class="GtkMenuItem" id="menubarView"> <object class="GtkMenuItem" id="menubarView">
<property name="visible">True</property> <property name="visible">True</property>
@@ -244,24 +326,6 @@
<object class="GtkMenu" id="menu5"> <object class="GtkMenu" id="menu5">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">False</property> <property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="menubarViewTree">
<property name="label">Tree View</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image4</property>
<property name="use_stock">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menubarViewIcon">
<property name="label">Icon view</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image5</property>
<property name="use_stock">False</property>
</object>
</child>
</object> </object>
</child> </child>
</object> </object>
@@ -297,82 +361,27 @@
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox" id="box2"> <object class="GtkNotebook" id="notebook">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="urlBar">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">url</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="upViewB">
<property name="label">gtk-go-up</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="homeViewB">
<property name="label">gtk-home</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="refreshViewB">
<property name="label">gtk-refresh</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="mainScroll">
<property name="width_request">300</property>
<property name="height_request">500</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child> <child>
<placeholder/> <placeholder/>
</child> </child>
<child type="tab">
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child type="tab">
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child type="tab">
<placeholder/>
</child>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">True</property>
@@ -430,4 +439,267 @@
</object> </object>
</child> </child>
</object> </object>
<object class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-zoom-in</property>
</object>
<object class="GtkImage" id="image5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-zoom-out</property>
</object>
<object class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-directory</property>
</object>
<object class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-zoom-fit</property>
</object>
<object class="GtkMenu" id="rcMenu">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="rcFileOpen">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileExecute">
<property name="label">gtk-execute</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileNew">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="rcFileNewRegFile">
<property name="label">gtk-file</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileNewDir">
<property name="label" translatable="yes">directory</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image6</property>
<property name="use_stock">False</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem2">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileCut">
<property name="label">gtk-cut</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileCopy">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileRename">
<property name="label">Rename</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image1</property>
<property name="use_stock">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFilePaste">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileDelete">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileProperty">
<property name="label">gtk-properties</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileView">
<property name="label">View</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image7</property>
<property name="use_stock">False</property>
<child type="submenu">
<object class="GtkMenu" id="menu2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="rcFileIconView">
<property name="label">icon view</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image4</property>
<property name="use_stock">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="rcFileTreeView">
<property name="label" translatable="yes">tree view</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="image">image5</property>
<property name="use_stock">False</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<object class="GtkBox" id="viewBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="box2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkEntry" id="urlBar">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="input_purpose">url</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="upViewB">
<property name="label">gtk-go-up</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="homeViewB">
<property name="label">gtk-home</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="refreshViewB">
<property name="label">gtk-refresh</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="padding">2</property>
<property name="position">3</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="mainScroll">
<property name="width_request">300</property>
<property name="height_request">500</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</interface> </interface>

View File

@@ -1,25 +1,23 @@
HACKING # HACKING
=======
Check out the [issue tracker](https://github.com/hasufell/hsfm/issues) Check out the [issue tracker](https://github.com/hasufell/hsfm/issues)
if you don't know yet what you want to hack on. if you don't know yet what you want to hack on.
Coding style ## Coding style
------------
- match the sorroundings - match the sorroundings
- no overcomplicated pointfree style - no overcomplicated pointfree style
- normal indenting 2 whitespaces - normal indenting 2 whitespaces
- just make things pretty and readable - just make things pretty and readable
- use the provided [hsimport.hs](hsimport.hs) - you can use the provided [hsimport.hs](hsimport.hs)
Documentation ## Documentation
-------------
__Everything__ must be documented. :) __Everything__ must be documented. :)
Don't assume people know what you mean. Type signatures are not sufficient
documentation.
Hacking Guide ## Hacking Overview
-------------
The main data structure for the IO related File type is in The main data structure for the IO related File type is in
[HSFM.FileSystem.FileType](./../src/HSFM/FileSystem/FileType.hs#L93), which [HSFM.FileSystem.FileType](./../src/HSFM/FileSystem/FileType.hs#L93), which
@@ -28,6 +26,8 @@ should be seen as a library. This is the entry point where
and the File type in general [is constructed](./../src/HSFM/FileSystem/FileType.hs#L302). and the File type in general [is constructed](./../src/HSFM/FileSystem/FileType.hs#L302).
The File type uses a safe Path type under the hood instead of Strings, The File type uses a safe Path type under the hood instead of Strings,
utilizing the [hpath](https://github.com/hasufell/hpath) library. utilizing the [hpath](https://github.com/hasufell/hpath) library.
Note that mostly only absolute paths are allowed on type level to improve
path and thread safety.
File operations (like copy, delete etc) are defined at File operations (like copy, delete etc) are defined at
[HSFM.FileSystem.FileOperation](./../src/HSFM/FileSystem/FileOperations.hs) [HSFM.FileSystem.FileOperation](./../src/HSFM/FileSystem/FileOperations.hs)
@@ -52,3 +52,73 @@ following files:
* [HSFM.GUI.Gtk.MyGUI](./../src/HSFM/GUI/Gtk/MyGUI.hs): add initializers for the GUI buttons to be fetched from the GTK builder.xml file * [HSFM.GUI.Gtk.MyGUI](./../src/HSFM/GUI/Gtk/MyGUI.hs): add initializers for the GUI buttons to be fetched from the GTK builder.xml file
* [HSFM.GUI.Gtk.Callbacks](./../src/HSFM/GUI/Gtk/Callbacks.hs): define the callbacks and the actual functionality here * [HSFM.GUI.Gtk.Callbacks](./../src/HSFM/GUI/Gtk/Callbacks.hs): define the callbacks and the actual functionality here
## Concepts
### Path safety
Paths are usually represented in haskell libraries as `type FilePath = String`.
This is bad, because of a number of reasons:
* encoding issues, since the low-level representation of filepaths is in fact an array of C chars
* weak typing... we could pass arbitrary invalid/malicious filepaths or other random strings
* no information about any property at type level (e.g. is it an absolute path?)
* no filepath constructors that do sanity checks and proper parsing
* no guarantee whether the filepath is normalised or not or even valid
Because of that, the solution is:
* use `ByteString` under the hood
* wrap it inside `Path t` where `t` can be either `Abs` (for absolute), `Rel` (for relative) or `Fn` (for filename)
* construct filepaths via smart constructors only that reject certain paths (like `.` or `..`) and normalise the path
This leads to the following benefits:
* we have guarantees about whether a path is absolute or not, which is important for runtime safety in general, predictable behavior and thread safety
* we don't mess with the filepath representation we get from low-level posix functions, so encoding issues are pretty much out
* we can reason about filepaths and rely on them to be valid (don't confuse that with "they exist")
* filepath functions like `(</>)` are now predictable and safe in contrast to the version from the `filepath` package
The only problem with this approach is that most libraries are still String
based. Some provide dedicated `Foo.ByteString` modules though, but it
might be necessary to fork libraries.
We also need to keep track of the [Abstract FilePath proposal](https://ghc.haskell.org/trac/ghc/wiki/Proposal/AbstractFilePath).
Almost all paths in HSFM are only allowed to be absolute (`Path Abs`), unless
they are filenames (`Path Fn`) and processed for GUI purposes. This is as
already mentioned for the purpose of runtime safety, predictability and
thread safety.
### File IO safety
This is a pretty difficult problem. One thing to ensure safety on IO level
is simply the strong haskell type system, since we push everything
into our `File a` type and can then pattern match easily against the different
types of files.
The only problem with this approach is that we are examining a file at point
`a` in time, safe the information and then use that information further down
the call stack at point `b` in time, when the file information in memory
could already be out of date. There are two approaches to make this less
sucky:
* use the hinotify library on GUI level to refresh the view (and the File representation in memory) whenever the contents of a directory changes
* when we stuff something into the copy buffer, it is not saved as type `File a`, but as `Path Abs`... when the operation is finalized via `runFileOp`, then the file at the given path is read and the copy/move/whatnot function carried out immediately
This means we should only interact with the `HSFM.FileSystem.FileOperation`
module via the operation data types `FileOperation`, `Copy` and `Move` and
the `runFileOp` function. This doesn't completely solve the problem, but for
the rest we have to trust the posix functions to throw the proper exceptions.
In addition, we don't use the `directory` package, which is dangerous
and broken. Instead, we implement our own low-level wrappers around
the posix functions, so we have proper control over the internals
and know the possible exceptions.
### Exception handling
Exceptions are good. We don't want to wrap everything in Maybe/Either types
unless we want to handle failure immediately. Otherwise we need to make
sure that at least at some point IOExceptions are caught and visualized
to the user. This is often done via e.g. `withErrorDialog` which catches
`IOException` and `FmIOException`.
It's also important to clean up stuff like filedescriptors via
functions like `bracket` directly in our low-level code in case
something goes wrong.

View File

@@ -8,7 +8,7 @@ main = hsimport $ defaultConfig { prettyPrint = prettyPrint
where where
prettyPrint :: HS.ImportDecl -> String prettyPrint :: HS.ImportDecl -> String
prettyPrint (HS.ImportDecl sloc modname qual _ _ mpkg mas mspec) = prettyPrint (HS.ImportDecl sloc modname qual _ _ mpkg mas mspec) =
"import " ++ (ifStr qual "qualified") ++ "import " ++ (ifStr qual "qualified ") ++
(maybe "" (\pkg -> " \"" ++ pkg ++ "\" ") mpkg) ++ (maybe "" (\pkg -> " \"" ++ pkg ++ "\" ") mpkg) ++
getMN modname ++ (maybe "" (\name -> " as " ++ getMN name) $ mas) ++ getMN modname ++ (maybe "" (\name -> " as " ++ getMN name) $ mas) ++
specprint mspec specprint mspec
@@ -16,9 +16,9 @@ main = hsimport $ defaultConfig { prettyPrint = prettyPrint
specprint :: Maybe (Bool, [HS.ImportSpec]) -> String specprint :: Maybe (Bool, [HS.ImportSpec]) -> String
specprint Nothing = "" specprint Nothing = ""
specprint (Just (False, xs)) specprint (Just (False, xs))
= "\n (\n" ++ printImportSpecs xs ++ "\n )" = "\n (\n" ++ printImportSpecs xs ++ " )"
specprint (Just (True, xs)) specprint (Just (True, xs))
= "\n hiding (\n" ++ printImportSpecs xs ++ "\n )" = "\n hiding (\n" ++ printImportSpecs xs ++ " )"
printImportSpecs :: [HS.ImportSpec] -> String printImportSpecs :: [HS.ImportSpec] -> String
printImportSpecs ins printImportSpecs ins
@@ -26,7 +26,7 @@ main = hsimport $ defaultConfig { prettyPrint = prettyPrint
in " " ++ printSpec x ++ "\n" ++ go xs in " " ++ printSpec x ++ "\n" ++ go xs
where where
go [] = "" go [] = ""
go [x'] = " , " ++ printSpec x' go [x'] = " , " ++ printSpec x' ++ "\n"
go (x':xs') = " , " ++ printSpec x' ++ "\n" ++ go xs' go (x':xs') = " , " ++ printSpec x' ++ "\n" ++ go xs'
printSpec :: HS.ImportSpec -> String printSpec :: HS.ImportSpec -> String
printSpec = HS.prettyPrint printSpec = HS.prettyPrint

View File

@@ -27,17 +27,21 @@ library
HSFM.FileSystem.Errors HSFM.FileSystem.Errors
HSFM.FileSystem.FileOperations HSFM.FileSystem.FileOperations
HSFM.FileSystem.FileType HSFM.FileSystem.FileType
HSFM.Settings.Bookmarks
HSFM.Utils.IO HSFM.Utils.IO
HSFM.Utils.MyPrelude HSFM.Utils.MyPrelude
build-depends: build-depends:
attoparsec,
base >= 4.7, base >= 4.7,
bytestring, bytestring,
containers, containers,
data-default, data-default,
errors,
filepath >= 1.3.0.0, filepath >= 1.3.0.0,
hinotify, hinotify,
hpath, hpath,
monad-loops,
mtl >= 2.2, mtl >= 2.2,
old-locale >= 1, old-locale >= 1,
posix-paths, posix-paths,
@@ -48,7 +52,8 @@ library
time >= 1.4.2, time >= 1.4.2,
unix, unix,
unix-bytestring, unix-bytestring,
utf8-string utf8-string,
word8
hs-source-dirs: src hs-source-dirs: src
default-language: Haskell2010 default-language: Haskell2010
Default-Extensions: RecordWildCards Default-Extensions: RecordWildCards

View File

@@ -37,10 +37,15 @@ import Control.Exception
) )
import Control.Monad import Control.Monad
( (
unless forM_
, unless
, void , void
, when , when
) )
import Control.Monad.Loops
(
dropWhileM
)
import Data.ByteString import Data.ByteString
( (
ByteString ByteString
@@ -81,7 +86,7 @@ import qualified HPath as P
import HSFM.FileSystem.Errors import HSFM.FileSystem.Errors
import HSFM.FileSystem.FileType import HSFM.FileSystem.FileType
import HSFM.Utils.IO import HSFM.Utils.IO
import Prelude hiding (readFile) import Prelude hiding (readFile, writeFile)
import System.Posix.Directory.ByteString import System.Posix.Directory.ByteString
( (
createDirectory createDirectory
@@ -117,9 +122,10 @@ import System.Posix.IO.Sendfile.ByteString
import qualified System.Posix.Process.ByteString as SPP import qualified System.Posix.Process.ByteString as SPP
import System.Posix.Types import System.Posix.Types
( (
FileMode ByteCount
, ProcessID
, Fd , Fd
, FileMode
, ProcessID
) )
@@ -408,6 +414,31 @@ easyCopy cm from@Dir{}
easyCopy _ _ _ = throw $ InvalidOperation "wrong input type" easyCopy _ _ _ = throw $ InvalidOperation "wrong input type"
-- |Write a ByteString to a file, overwriting the file. Follows
-- symbolic links.
writeFile :: File a -> ByteString -> IO ByteCount
writeFile RegFile { path = fp } bs = P.withAbsPath fp $ \p ->
bracket (SPI.openFd p SPI.WriteOnly (Just PF.stdFileMode)
SPI.defaultFileFlags)
SPI.closeFd
$ \fd -> SPB.fdWrite fd bs
writeFile SymLink { sdest = file@RegFile{} } bs =
writeFile file bs
writeFile _ _ = throw $ InvalidOperation "wrong input type"
readFileContents :: File FileInfo -> IO ByteString
readFileContents RegFile { path = fp } =
P.withAbsPath fp $ \p ->
bracket (SPI.openFd p SPI.ReadOnly Nothing SPI.defaultFileFlags)
SPI.closeFd
$ \fd -> do
fs <- PF.getFdStatus fd
SPB.fdRead fd (fromIntegral $ PF.fileSize fs)
readFileContents SymLink { sdest = file@RegFile{} } =
readFileContents file
readFileContents _ = throw $ InvalidOperation "wrong input type"
@@ -525,10 +556,18 @@ createDir :: File FileInfo -> Path Fn -> IO ()
createDir (DirOrSym td) fn = do createDir (DirOrSym td) fn = do
let fullp = path td P.</> fn let fullp = path td P.</> fn
throwDirDoesExist fullp throwDirDoesExist fullp
createDirectory (P.fromAbs fullp) newFilePerms createDirectory (P.fromAbs fullp) newDirPerms
createDir _ _ = throw $ InvalidOperation "wrong input type" createDir _ _ = throw $ InvalidOperation "wrong input type"
-- |Create a directory at the given path, creating all parents if
-- necessary.
mkdirP :: Path Abs -> IO ()
mkdirP p = do
mkps <- dropWhileM canOpenDirectory (reverse $ p : P.getAllParents p)
forM_ mkps $ \mkp -> createDirectory (P.fromAbs mkp) newDirPerms
---------------------------- ----------------------------

View File

@@ -97,58 +97,58 @@ import System.Posix.Types
-- can be converted to a String with 'show'. -- can be converted to a String with 'show'.
data File a = data File a =
Failed { Failed {
path :: Path Abs path :: !(Path Abs)
, err :: IOError , err :: IOError
} }
| Dir { | Dir {
path :: Path Abs path :: !(Path Abs)
, fvar :: a , fvar :: a
} }
| RegFile { | RegFile {
path :: Path Abs path :: !(Path Abs)
, fvar :: a , fvar :: a
} }
| SymLink { | SymLink {
path :: Path Abs path :: !(Path Abs)
, fvar :: a , fvar :: a
, sdest :: File a -- ^ symlink madness, , sdest :: File a -- ^ symlink madness,
-- we need to know where it points to -- we need to know where it points to
, rawdest :: ByteString , rawdest :: !ByteString
} }
| BlockDev { | BlockDev {
path :: Path Abs path :: !(Path Abs)
, fvar :: a , fvar :: a
} }
| CharDev { | CharDev {
path :: Path Abs path :: !(Path Abs)
, fvar :: a , fvar :: a
} }
| NamedPipe { | NamedPipe {
path :: Path Abs path :: !(Path Abs)
, fvar :: a , fvar :: a
} }
| Socket { | Socket {
path :: Path Abs path :: !(Path Abs)
, fvar :: a , fvar :: a
} deriving (Show, Eq) } deriving (Show, Eq)
-- |Low-level file information. -- |Low-level file information.
data FileInfo = FileInfo { data FileInfo = FileInfo {
deviceID :: DeviceID deviceID :: !DeviceID
, fileID :: FileID , fileID :: !FileID
, fileMode :: FileMode , fileMode :: !FileMode
, linkCount :: LinkCount , linkCount :: !LinkCount
, fileOwner :: UserID , fileOwner :: !UserID
, fileGroup :: GroupID , fileGroup :: !GroupID
, specialDeviceID :: DeviceID , specialDeviceID :: !DeviceID
, fileSize :: FileOffset , fileSize :: !FileOffset
, accessTime :: EpochTime , accessTime :: !EpochTime
, modificationTime :: EpochTime , modificationTime :: !EpochTime
, statusChangeTime :: EpochTime , statusChangeTime :: !EpochTime
, accessTimeHiRes :: POSIXTime , accessTimeHiRes :: !POSIXTime
, modificationTimeHiRes :: POSIXTime , modificationTimeHiRes :: !POSIXTime
, statusChangeTimeHiRes :: POSIXTime , statusChangeTimeHiRes :: !POSIXTime
} deriving (Show, Eq, Ord) } deriving (Show, Eq, Ord)
@@ -563,9 +563,17 @@ getFreeVar _ = Nothing
-- |Pack the modification time into a string. -- |Pack the modification time into a string.
packModTime :: File FileInfo packModTime :: File FileInfo
-> String -> String
packModTime = packModTime = fromFreeVar $ epochToString . modificationTime
fromFreeVar
$ show . posixSecondsToUTCTime . realToFrac . modificationTime
-- |Pack the modification time into a string.
packAccessTime :: File FileInfo
-> String
packAccessTime = fromFreeVar $ epochToString . accessTime
epochToString :: EpochTime -> String
epochToString = show . posixSecondsToUTCTime . realToFrac
-- |Pack the permissions into a string, similar to what "ls -l" does. -- |Pack the permissions into a string, similar to what "ls -l" does.
@@ -599,3 +607,21 @@ packPermissions dt = fromFreeVar (pStr . fileMode) dt
| otherwise = "-" | otherwise = "-"
hasFM fm = ffm `PF.intersectFileModes` fm == fm hasFM fm = ffm `PF.intersectFileModes` fm == fm
packFileType :: File a -> String
packFileType file = case file of
Dir {} -> "Directory"
RegFile {} -> "Regular File"
SymLink {} -> "Symbolic Link"
BlockDev {} -> "Block Device"
CharDev {} -> "Char Device"
NamedPipe {} -> "Named Pipe"
Socket {} -> "Socket"
_ -> "Unknown"
packLinkDestination :: File a -> Maybe ByteString
packLinkDestination file = case file of
SymLink { rawdest = dest } -> Just dest
_ -> Nothing

View File

@@ -29,6 +29,7 @@ import Data.Maybe
) )
import Graphics.UI.Gtk import Graphics.UI.Gtk
import qualified HPath as P import qualified HPath as P
import HSFM.GUI.Gtk.Callbacks
import HSFM.GUI.Gtk.Data import HSFM.GUI.Gtk.Data
import HSFM.GUI.Gtk.MyGUI import HSFM.GUI.Gtk.MyGUI
import HSFM.GUI.Gtk.MyView import HSFM.GUI.Gtk.MyView
@@ -44,14 +45,13 @@ main = do
_ <- initGUI _ <- initGUI
args <- SPE.getArgs args <- SPE.getArgs
mygui <- createMyGUI
myview <- createMyView mygui createTreeView
let mdir = fromMaybe (fromJust $ P.parseAbs "/") let mdir = fromMaybe (fromJust $ P.parseAbs "/")
(P.parseAbs . headDef "/" $ args) (P.parseAbs . headDef "/" $ args)
refreshView mygui myview (Just $ mdir)
mygui <- createMyGUI
_ <- newTab mygui createTreeView mdir
setGUICallbacks mygui
widgetShowAll (rootWin mygui) widgetShowAll (rootWin mygui)

View File

@@ -32,8 +32,9 @@ import Control.Exception
) )
import Control.Monad import Control.Monad
( (
void forM_
, forM_ , void
, when
) )
import Control.Monad.IO.Class import Control.Monad.IO.Class
( (
@@ -67,6 +68,12 @@ import System.Posix.Env.ByteString
( (
getEnv getEnv
) )
import qualified System.Posix.Process.ByteString as SPP
import System.Posix.Types
(
ProcessID
)
@@ -76,14 +83,75 @@ import System.Posix.Env.ByteString
----------------- -----------------
-- |Set callbacks, on hotkeys, events and stuff.
setCallbacks :: MyGUI -> MyView -> IO ()
setCallbacks mygui myview = do ---- MAIN CALLBACK ENTRYPOINT ----
-- |Set callbacks for the whole gui, on hotkeys, events and stuff.
setGUICallbacks :: MyGUI -> IO ()
setGUICallbacks mygui = do
_ <- clearStatusBar mygui `on` buttonActivated $ do
popStatusbar mygui
writeTVarIO (operationBuffer mygui) None
-- menubar-file
_ <- (menubarFileQuit . menubar) mygui `on` menuItemActivated $
mainQuit
-- menubar-help
_ <- (menubarHelpAbout . menubar) mygui `on` menuItemActivated $
liftIO showAboutDialog
return ()
-- key events
_ <- rootWin mygui `on` keyPressEvent $ tryEvent $ do
[Control] <- eventModifier
"q" <- fmap glibToString eventKeyName
liftIO mainQuit
return ()
-- |Set callbacks specific to a given view, on hotkeys, events and stuff.
setViewCallbacks :: MyGUI -> MyView -> IO ()
setViewCallbacks mygui myview = do
view' <- readTVarIO $ view myview view' <- readTVarIO $ view myview
case view' of case view' of
fmv@(FMTreeView treeView) -> do fmv@(FMTreeView treeView) -> do
_ <- treeView `on` rowActivated _ <- treeView `on` rowActivated
$ (\_ _ -> withItems mygui myview open) $ (\_ _ -> withItems mygui myview open)
-- drag events
_ <- treeView `on` dragBegin $
\_ -> withItems mygui myview moveInit
_ <- treeView `on` dragDrop $
\dc p ts -> do
p' <- treeViewConvertWidgetToTreeCoords treeView p
mpath <- treeViewGetPathAtPos treeView p'
case mpath of
Nothing -> do
dragFinish dc False False ts
return False
Just _ -> do
atom <- atomNew ("HSFM" :: String)
dragGetData treeView dc atom ts
return True
_ <- treeView `on` dragDataReceived $
\dc p _ ts ->
liftIO $ do
signalStopEmission treeView "drag_data_received"
p' <- treeViewConvertWidgetToTreeCoords treeView p
mpath <- treeViewGetPathAtPos treeView p'
case mpath of
Nothing -> dragFinish dc False False ts
Just (tp, _, _) -> do
mitem <- rawPathToItem myview tp
forM_ mitem $ \item ->
operationFinal mygui myview (Just item)
dragFinish dc True False ts
commonGuiEvents fmv commonGuiEvents fmv
return () return ()
fmv@(FMIconView iconView) -> do fmv@(FMIconView iconView) -> do
@@ -91,73 +159,40 @@ setCallbacks mygui myview = do
$ (\_ -> withItems mygui myview open) $ (\_ -> withItems mygui myview open)
commonGuiEvents fmv commonGuiEvents fmv
return () return ()
menubarCallbacks
where where
menubarCallbacks = do
-- menubar-file
_ <- menubarFileQuit mygui `on` menuItemActivated $ mainQuit
_ <- menubarFileOpen mygui `on` menuItemActivated $
liftIO $ withItems mygui myview open
_ <- menubarFileExecute mygui `on` menuItemActivated $
liftIO $ withItems mygui myview execute
_ <- menubarFileNew mygui `on` menuItemActivated $
liftIO $ newFile mygui myview
-- menubar-edit
_ <- menubarEditCut mygui `on` menuItemActivated $
liftIO $ withItems mygui myview moveInit
_ <- menubarEditCopy mygui `on` menuItemActivated $
liftIO $ withItems mygui myview copyInit
_ <- menubarEditRename mygui `on` menuItemActivated $
liftIO $ withItems mygui myview renameF
_ <- menubarEditPaste mygui `on` menuItemActivated $
liftIO $ operationFinal mygui myview
_ <- menubarEditDelete mygui `on` menuItemActivated $
liftIO $ withItems mygui myview del
-- mewnubar-view
_ <- menubarViewIcon mygui `on` menuItemActivated $
liftIO $ switchView mygui myview createIconView
_ <- menubarViewTree mygui `on` menuItemActivated $
liftIO $ switchView mygui myview createTreeView
-- menubar-help
_ <- menubarHelpAbout mygui `on` menuItemActivated $
liftIO showAboutDialog
return ()
commonGuiEvents fmv = do commonGuiEvents fmv = do
let view = fmViewToContainer fmv let view = fmViewToContainer fmv
-- GUI events -- GUI events
_ <- urlBar mygui `on` entryActivated $ urlGoTo mygui myview _ <- urlBar myview `on` entryActivated $ urlGoTo mygui myview
_ <- upViewB myview `on` buttonActivated $
_ <- upViewB mygui `on` buttonActivated $
upDir mygui myview upDir mygui myview
_ <- homeViewB mygui `on` buttonActivated $ _ <- homeViewB myview `on` buttonActivated $
goHome mygui myview goHome mygui myview
_ <- refreshViewB mygui `on` buttonActivated $ do _ <- refreshViewB myview `on` buttonActivated $ do
cdir <- liftIO $ getCurrentDir myview cdir <- liftIO $ getCurrentDir myview
refreshView' mygui myview cdir refreshView' mygui myview cdir
_ <- clearStatusBar mygui `on` buttonActivated $ do
popStatusbar mygui
writeTVarIO (operationBuffer myview) None
-- key events -- key events
_ <- rootWin mygui `on` keyPressEvent $ tryEvent $ do _ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
[Control] <- eventModifier
"q" <- fmap glibToString eventKeyName
liftIO mainQuit
_ <- view `on` keyPressEvent $ tryEvent $ do
[Control] <- eventModifier [Control] <- eventModifier
"h" <- fmap glibToString eventKeyName "h" <- fmap glibToString eventKeyName
cdir <- liftIO $ getCurrentDir myview cdir <- liftIO $ getCurrentDir myview
liftIO $ modifyTVarIO (settings mygui) liftIO $ modifyTVarIO (settings mygui)
(\x -> x { showHidden = not . showHidden $ x}) (\x -> x { showHidden = not . showHidden $ x})
>> refreshView' mygui myview cdir >> refreshView' mygui myview cdir
_ <- view `on` keyPressEvent $ tryEvent $ do _ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
[Alt] <- eventModifier [Alt] <- eventModifier
"Up" <- fmap glibToString eventKeyName "Up" <- fmap glibToString eventKeyName
liftIO $ upDir mygui myview liftIO $ upDir mygui myview
_ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
[Alt] <- eventModifier
"Left" <- fmap glibToString eventKeyName
liftIO $ goHistoryPrev mygui myview
_ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
[Alt] <- eventModifier
"Right" <- fmap glibToString eventKeyName
liftIO $ goHistoryNext mygui myview
_ <- view `on` keyPressEvent $ tryEvent $ do _ <- view `on` keyPressEvent $ tryEvent $ do
"Delete" <- fmap glibToString eventKeyName "Delete" <- fmap glibToString eventKeyName
liftIO $ withItems mygui myview del liftIO $ withItems mygui myview del
@@ -173,10 +208,23 @@ setCallbacks mygui myview = do
[Control] <- eventModifier [Control] <- eventModifier
"x" <- fmap glibToString eventKeyName "x" <- fmap glibToString eventKeyName
liftIO $ withItems mygui myview moveInit liftIO $ withItems mygui myview moveInit
_ <- view `on` keyPressEvent $ tryEvent $ do _ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
[Control] <- eventModifier [Control] <- eventModifier
"v" <- fmap glibToString eventKeyName "v" <- fmap glibToString eventKeyName
liftIO $ operationFinal mygui myview liftIO $ operationFinal mygui myview Nothing
_ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
[Control] <- eventModifier
"t" <- fmap glibToString eventKeyName
liftIO $ void $ do
cwd <- getCurrentDir myview
newTab mygui createTreeView (path cwd)
_ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
[Control] <- eventModifier
"w" <- fmap glibToString eventKeyName
liftIO $ void $ closeTab mygui myview
_ <- viewBox myview `on` keyPressEvent $ tryEvent $ do
"F4" <- fmap glibToString eventKeyName
liftIO $ void $ openTerminalHere myview
-- righ-click -- righ-click
_ <- view `on` buttonPressEvent $ do _ <- view `on` buttonPressEvent $ do
@@ -184,7 +232,7 @@ setCallbacks mygui myview = do
t <- eventTime t <- eventTime
case eb of case eb of
RightButton -> do RightButton -> do
_ <- liftIO $ menuPopup (rcMenu mygui) _ <- liftIO $ menuPopup (rcMenu . rcmenu $ myview)
$ Just (RightButton, t) $ Just (RightButton, t)
-- this is just to not screw with current selection -- this is just to not screw with current selection
-- on right-click -- on right-click
@@ -200,25 +248,42 @@ setCallbacks mygui myview = do
return $ elem tp selectedTps return $ elem tp selectedTps
-- no item under the cursor, pass on the signal -- no item under the cursor, pass on the signal
Nothing -> return False Nothing -> return False
OtherButton 8 -> do
liftIO $ goHistoryPrev mygui myview
return False
OtherButton 9 -> do
liftIO $ goHistoryNext mygui myview
return False
-- not right-click, so pass on the signal -- not right-click, so pass on the signal
_ -> return False _ -> return False
_ <- rcFileOpen mygui `on` menuItemActivated $
-- right click menu
_ <- (rcFileOpen . rcmenu) myview `on` menuItemActivated $
liftIO $ withItems mygui myview open liftIO $ withItems mygui myview open
_ <- rcFileExecute mygui `on` menuItemActivated $ _ <- (rcFileExecute . rcmenu) myview `on` menuItemActivated $
liftIO $ withItems mygui myview execute liftIO $ withItems mygui myview execute
_ <- rcFileNew mygui `on` menuItemActivated $ _ <- (rcFileNewRegFile . rcmenu) myview `on` menuItemActivated $
liftIO $ newFile mygui myview liftIO $ newFile mygui myview
_ <- rcFileCopy mygui `on` menuItemActivated $ _ <- (rcFileNewDir . rcmenu) myview `on` menuItemActivated $
liftIO $ newDir mygui myview
_ <- (rcFileCopy . rcmenu) myview `on` menuItemActivated $
liftIO $ withItems mygui myview copyInit liftIO $ withItems mygui myview copyInit
_ <- rcFileRename mygui `on` menuItemActivated $ _ <- (rcFileRename . rcmenu) myview `on` menuItemActivated $
liftIO $ withItems mygui myview renameF liftIO $ withItems mygui myview renameF
_ <- rcFilePaste mygui `on` menuItemActivated $ _ <- (rcFilePaste . rcmenu) myview `on` menuItemActivated $
liftIO $ operationFinal mygui myview liftIO $ operationFinal mygui myview Nothing
_ <- rcFileDelete mygui `on` menuItemActivated $ _ <- (rcFileDelete . rcmenu) myview `on` menuItemActivated $
liftIO $ withItems mygui myview del liftIO $ withItems mygui myview del
_ <- rcFileCut mygui `on` menuItemActivated $ _ <- (rcFileProperty . rcmenu) myview `on` menuItemActivated $
liftIO $ withItems mygui myview showFilePropertyDialog
_ <- (rcFileCut . rcmenu) myview `on` menuItemActivated $
liftIO $ withItems mygui myview moveInit liftIO $ withItems mygui myview moveInit
_ <- (rcFileIconView . rcmenu) myview `on` menuItemActivated $
liftIO $ switchView mygui myview createIconView
_ <- (rcFileTreeView . rcmenu) myview `on` menuItemActivated $
liftIO $ switchView mygui myview createTreeView
return () return ()
getPathAtPos fmv (x, y) = getPathAtPos fmv (x, y) =
case fmv of case fmv of
FMTreeView treeView -> do FMTreeView treeView -> do
@@ -230,47 +295,31 @@ setCallbacks mygui myview = do
-- |Go to the url given at the 'urlBar' and visualize it in the given
-- treeView. ---- OTHER ----
--
-- If the url is invalid, does nothing.
urlGoTo :: MyGUI -> MyView -> IO ()
urlGoTo mygui myview = withErrorDialog $ do
fp <- entryGetText (urlBar mygui)
forM_ (P.parseAbs fp :: Maybe (Path Abs)) $ \fp' ->
refreshView mygui myview (Just fp')
goHome :: MyGUI -> MyView -> IO () openTerminalHere :: MyView -> IO ProcessID
goHome mygui myview = withErrorDialog $ do openTerminalHere myview = do
mhomedir <- getEnv "HOME" cwd <- (P.fromAbs . path) <$> getCurrentDir myview
refreshView mygui myview (P.parseAbs =<< mhomedir) -- TODO: make terminal configurable
SPP.forkProcess $ SPP.executeFile "sakura" True ["-d", cwd] Nothing
-- |Supposed to be used with 'withRows'. Opens a file or directory.
open :: [Item] -> MyGUI -> MyView -> IO ()
open [item] mygui myview = withErrorDialog $
case item of
DirOrSym r -> do
nv <- readFile getFileInfo $ path r
refreshView' mygui myview nv
r ->
void $ openFile r
-- this throws on the first error that occurs
open (FileLikeList fs) _ _ = withErrorDialog $
forM_ fs $ \f -> void $ openFile f
open _ _ _ = withErrorDialog
. throw $ InvalidOperation
"Operation not supported on multiple files"
-- |Execute a given file. ---- TAB OPERATIONS ----
execute :: [Item] -> MyGUI -> MyView -> IO ()
execute [item] _ _ = withErrorDialog $
void $ executeFile item [] -- |Closes the current tab, but only if there is more than one tab.
execute _ _ _ = withErrorDialog closeTab :: MyGUI -> MyView -> IO ()
. throw $ InvalidOperation closeTab mygui myview = do
"Operation not supported on multiple files" n <- notebookGetNPages (notebook mygui)
when (n > 1) $ void $ destroyView mygui myview
---- FILE OPERATION CALLBACKS (COPY, MOVE, ...) ----
-- |Supposed to be used with 'withRows'. Deletes a file or directory. -- |Supposed to be used with 'withRows'. Deletes a file or directory.
@@ -291,8 +340,8 @@ del _ _ _ = withErrorDialog
-- |Initializes a file move operation. -- |Initializes a file move operation.
moveInit :: [Item] -> MyGUI -> MyView -> IO () moveInit :: [Item] -> MyGUI -> MyView -> IO ()
moveInit items@(_:_) mygui myview = do moveInit items@(_:_) mygui _ = do
writeTVarIO (operationBuffer myview) (FMove . MP1 . map path $ items) writeTVarIO (operationBuffer mygui) (FMove . MP1 . map path $ items)
let sbmsg = case items of let sbmsg = case items of
(item:[]) -> "Move buffer: " ++ getFPasStr item (item:[]) -> "Move buffer: " ++ getFPasStr item
_ -> "Move buffer: " ++ (show . length $ items) _ -> "Move buffer: " ++ (show . length $ items)
@@ -305,8 +354,8 @@ moveInit _ _ _ = withErrorDialog
-- |Supposed to be used with 'withRows'. Initializes a file copy operation. -- |Supposed to be used with 'withRows'. Initializes a file copy operation.
copyInit :: [Item] -> MyGUI -> MyView -> IO () copyInit :: [Item] -> MyGUI -> MyView -> IO ()
copyInit items@(_:_) mygui myview = do copyInit items@(_:_) mygui _ = do
writeTVarIO (operationBuffer myview) (FCopy . CP1 . map path $ items) writeTVarIO (operationBuffer mygui) (FCopy . CP1 . map path $ items)
let sbmsg = case items of let sbmsg = case items of
(item:[]) -> "Copy buffer: " ++ getFPasStr item (item:[]) -> "Copy buffer: " ++ getFPasStr item
_ -> "Copy buffer: " ++ (show . length $ items) _ -> "Copy buffer: " ++ (show . length $ items)
@@ -319,25 +368,28 @@ copyInit _ _ _ = withErrorDialog
-- |Finalizes a file operation, such as copy or move. -- |Finalizes a file operation, such as copy or move.
operationFinal :: MyGUI -> MyView -> IO () operationFinal :: MyGUI -> MyView -> Maybe Item -> IO ()
operationFinal _ myview = withErrorDialog $ do operationFinal mygui myview mitem = withErrorDialog $ do
op <- readTVarIO (operationBuffer myview) op <- readTVarIO (operationBuffer mygui)
cdir <- path <$> getCurrentDir myview cdir <- case mitem of
Nothing -> path <$> getCurrentDir myview
Just x -> return $ path x
case op of case op of
FMove (MP1 s) -> do FMove (MP1 s) -> do
let cmsg = "Really move " ++ imsg s let cmsg = "Really move " ++ imsg s
++ " to \"" ++ P.fpToString (P.fromAbs cdir) ++ " to \"" ++ P.fpToString (P.fromAbs cdir)
++ "\"?" ++ "\"?"
withConfirmationDialog cmsg . withCopyModeDialog withConfirmationDialog cmsg . withCopyModeDialog
$ \cm -> void $ runFileOp (FMove . MC s cdir $ cm) $ \cm -> do
return () void $ runFileOp (FMove . MC s cdir $ cm)
popStatusbar mygui
writeTVarIO (operationBuffer mygui) None
FCopy (CP1 s) -> do FCopy (CP1 s) -> do
let cmsg = "Really copy " ++ imsg s let cmsg = "Really copy " ++ imsg s
++ " to \"" ++ P.fpToString (P.fromAbs cdir) ++ " to \"" ++ P.fpToString (P.fromAbs cdir)
++ "\"?" ++ "\"?"
withConfirmationDialog cmsg . withCopyModeDialog withConfirmationDialog cmsg . withCopyModeDialog
$ \cm -> void $ runFileOp (FCopy . CC s cdir $ cm) $ \cm -> void $ runFileOp (FCopy . CC s cdir $ cm)
return ()
_ -> return () _ -> return ()
where where
imsg s = case s of imsg s = case s of
@@ -345,15 +397,7 @@ operationFinal _ myview = withErrorDialog $ do
items -> (show . length $ items) ++ " items" items -> (show . length $ items) ++ " items"
-- |Go up one directory and visualize it in the treeView. -- |Create a new file.
upDir :: MyGUI -> MyView -> IO ()
upDir mygui myview = withErrorDialog $ do
cdir <- getCurrentDir myview
nv <- goUp cdir
refreshView' mygui myview nv
-- |Go up one directory and visualize it in the treeView.
newFile :: MyGUI -> MyView -> IO () newFile :: MyGUI -> MyView -> IO ()
newFile _ myview = withErrorDialog $ do newFile _ myview = withErrorDialog $ do
mfn <- textInputDialog "Enter file name" mfn <- textInputDialog "Enter file name"
@@ -363,6 +407,16 @@ newFile _ myview = withErrorDialog $ do
createFile cdir fn createFile cdir fn
-- |Create a new directory.
newDir :: MyGUI -> MyView -> IO ()
newDir _ myview = withErrorDialog $ do
mfn <- textInputDialog "Enter directory name"
let pmfn = P.parseFn =<< P.userStringToFP <$> mfn
for_ pmfn $ \fn -> do
cdir <- getCurrentDir myview
createDir cdir fn
renameF :: [Item] -> MyGUI -> MyView -> IO () renameF :: [Item] -> MyGUI -> MyView -> IO ()
renameF [item] _ _ = withErrorDialog $ do renameF [item] _ _ = withErrorDialog $ do
mfn <- textInputDialog "Enter new file name" mfn <- textInputDialog "Enter new file name"
@@ -377,3 +431,100 @@ renameF [item] _ _ = withErrorDialog $ do
renameF _ _ _ = withErrorDialog renameF _ _ _ = withErrorDialog
. throw $ InvalidOperation . throw $ InvalidOperation
"Operation not supported on multiple files" "Operation not supported on multiple files"
---- DIRECTORY TRAVERSAL AND FILE OPENING CALLBACKS ----
-- |Go to the url given at the 'urlBar' and visualize it in the given
-- treeView.
--
-- If the url is invalid, does nothing.
urlGoTo :: MyGUI -> MyView -> IO ()
urlGoTo mygui myview = withErrorDialog $ do
fp <- entryGetText (urlBar myview)
forM_ (P.parseAbs fp :: Maybe (Path Abs)) $ \fp' ->
whenM (canOpenDirectory fp')
(goDir mygui myview =<< (readFile getFileInfo $ fp'))
goHome :: MyGUI -> MyView -> IO ()
goHome mygui myview = withErrorDialog $ do
mhomedir <- getEnv "HOME"
forM_ (P.parseAbs =<< mhomedir :: Maybe (Path Abs)) $ \fp' ->
whenM (canOpenDirectory fp')
(goDir mygui myview =<< (readFile getFileInfo $ fp'))
-- |Execute a given file.
execute :: [Item] -> MyGUI -> MyView -> IO ()
execute [item] _ _ = withErrorDialog $
void $ executeFile item []
execute _ _ _ = withErrorDialog
. throw $ InvalidOperation
"Operation not supported on multiple files"
-- |Supposed to be used with 'withRows'. Opens a file or directory.
open :: [Item] -> MyGUI -> MyView -> IO ()
open [item] mygui myview = withErrorDialog $
case item of
DirOrSym r -> do
nv <- readFile getFileInfo $ path r
goDir mygui myview nv
r ->
void $ openFile r
-- this throws on the first error that occurs
open (FileLikeList fs) _ _ = withErrorDialog $
forM_ fs $ \f -> void $ openFile f
open _ _ _ = withErrorDialog
. throw $ InvalidOperation
"Operation not supported on multiple files"
-- |Go up one directory and visualize it in the treeView.
upDir :: MyGUI -> MyView -> IO ()
upDir mygui myview = withErrorDialog $ do
cdir <- getCurrentDir myview
nv <- goUp cdir
goDir mygui myview nv
-- |Helper that is invoked for any directory change operations.
goDir :: MyGUI -> MyView -> Item -> IO ()
goDir mygui myview item = do
cdir <- getCurrentDir myview
modifyTVarIO (history myview)
(\(p, _) -> (path cdir `addHistory` p, []))
refreshView' mygui myview item
-- |Go "back" in the history.
goHistoryPrev :: MyGUI -> MyView -> IO ()
goHistoryPrev mygui myview = do
hs <- readTVarIO (history myview)
case hs of
([], _) -> return ()
(x:xs, _) -> do
cdir <- getCurrentDir myview
nv <- readFile getFileInfo $ x
modifyTVarIO (history myview)
(\(_, n) -> (xs, path cdir `addHistory` n))
refreshView' mygui myview nv
-- |Go "forth" in the history.
goHistoryNext :: MyGUI -> MyView -> IO ()
goHistoryNext mygui myview = do
hs <- readTVarIO (history myview)
case hs of
(_, []) -> return ()
(_, x:xs) -> do
cdir <- getCurrentDir myview
nv <- readFile getFileInfo $ x
modifyTVarIO (history myview)
(\(p, _) -> (path cdir `addHistory` p, xs))
refreshView' mygui myview nv

View File

@@ -22,4 +22,4 @@ module HSFM.GUI.Gtk.Callbacks where
import HSFM.GUI.Gtk.Data import HSFM.GUI.Gtk.Data
setCallbacks :: MyGUI -> MyView -> IO () setViewCallbacks :: MyGUI -> MyView -> IO ()

View File

@@ -29,7 +29,12 @@ import Control.Concurrent.STM
( (
TVar TVar
) )
import Graphics.UI.Gtk import Graphics.UI.Gtk hiding (MenuBar)
import HPath
(
Abs
, Path
)
import HSFM.FileSystem.FileOperations import HSFM.FileSystem.FileOperations
import HSFM.FileSystem.FileType import HSFM.FileSystem.FileType
import System.INotify.ByteString import System.INotify.ByteString
@@ -50,65 +55,96 @@ import System.INotify.ByteString
-- runtime. -- runtime.
data MyGUI = MkMyGUI { data MyGUI = MkMyGUI {
-- |main Window -- |main Window
rootWin :: Window rootWin :: !Window
, menubarFileQuit :: ImageMenuItem
, menubarFileOpen :: ImageMenuItem -- widgets on the main window
, menubarFileExecute :: ImageMenuItem , menubar :: !MenuBar
, menubarFileNew :: ImageMenuItem , statusBar :: !Statusbar
, menubarEditCut :: ImageMenuItem , clearStatusBar :: !Button
, menubarEditCopy :: ImageMenuItem , notebook :: !Notebook
, menubarEditRename :: ImageMenuItem
, menubarEditPaste :: ImageMenuItem -- other
, menubarEditDelete :: ImageMenuItem , fprop :: !FilePropertyGrid
, menubarViewTree :: ImageMenuItem , settings :: !(TVar FMSettings)
, menubarViewIcon :: ImageMenuItem
, menubarHelpAbout :: ImageMenuItem , operationBuffer :: !(TVar FileOperation)
, rcMenu :: Menu }
, rcFileOpen :: ImageMenuItem
, rcFileExecute :: ImageMenuItem
, rcFileNew :: ImageMenuItem -- |This describes the contents of the current view and is separated from MyGUI,
, rcFileCut :: ImageMenuItem -- because we might want to have multiple views.
, rcFileCopy :: ImageMenuItem data MyView = MkMyView {
, rcFileRename :: ImageMenuItem view :: !(TVar FMView)
, rcFilePaste :: ImageMenuItem , cwd :: !(MVar Item)
, rcFileDelete :: ImageMenuItem , rawModel :: !(TVar (ListStore Item))
, upViewB :: Button , sortedModel :: !(TVar (TypedTreeModelSort Item))
, homeViewB :: Button , filteredModel :: !(TVar (TypedTreeModelFilter Item))
, refreshViewB :: Button , inotify :: !(MVar INotify)
, urlBar :: Entry
, statusBar :: Statusbar -- the first part of the tuple represents the "go back"
, clearStatusBar :: Button -- the second part the "go forth" in the history
, settings :: TVar FMSettings , history :: !(TVar ([Path Abs], [Path Abs]))
, scroll :: ScrolledWindow
-- sub-widgets
, scroll :: !ScrolledWindow
, viewBox :: !Box
, rcmenu :: !RightClickMenu
, upViewB :: !Button
, homeViewB :: !Button
, refreshViewB :: !Button
, urlBar :: !Entry
}
data MenuBar = MkMenuBar {
menubarFileQuit :: !ImageMenuItem
, menubarHelpAbout :: !ImageMenuItem
}
data RightClickMenu = MkRightClickMenu {
rcMenu :: !Menu
, rcFileOpen :: !ImageMenuItem
, rcFileExecute :: !ImageMenuItem
, rcFileNewRegFile :: !ImageMenuItem
, rcFileNewDir :: !ImageMenuItem
, rcFileCut :: !ImageMenuItem
, rcFileCopy :: !ImageMenuItem
, rcFileRename :: !ImageMenuItem
, rcFilePaste :: !ImageMenuItem
, rcFileDelete :: !ImageMenuItem
, rcFileProperty :: !ImageMenuItem
, rcFileIconView :: !ImageMenuItem
, rcFileTreeView :: !ImageMenuItem
}
data FilePropertyGrid = MkFilePropertyGrid {
fpropGrid :: !Grid
, fpropFnEntry :: !Entry
, fpropLocEntry :: !Entry
, fpropTsEntry :: !Entry
, fpropModEntry :: !Entry
, fpropAcEntry :: !Entry
, fpropFTEntry :: !Entry
, fpropPermEntry :: !Entry
, fpropLDEntry :: !Entry
} }
-- |FM-wide settings. -- |FM-wide settings.
data FMSettings = MkFMSettings { data FMSettings = MkFMSettings {
showHidden :: Bool showHidden :: !Bool
, isLazy :: Bool , isLazy :: !Bool
, iconSize :: Int , iconSize :: !Int
} }
data FMView = FMTreeView TreeView data FMView = FMTreeView !TreeView
| FMIconView IconView | FMIconView !IconView
type Item = File FileInfo type Item = File FileInfo
-- |This describes the contents of the current vie and is separated from MyGUI,
-- because we might want to have multiple views.
data MyView = MkMyView {
view :: TVar FMView
, cwd :: MVar Item
, rawModel :: TVar (ListStore Item)
, sortedModel :: TVar (TypedTreeModelSort Item)
, filteredModel :: TVar (TypedTreeModelFilter Item)
, operationBuffer :: TVar FileOperation
, inotify :: MVar INotify
}
fmViewToContainer :: FMView -> Container fmViewToContainer :: FMView -> Container
fmViewToContainer (FMTreeView x) = castToContainer . toGObject $ x fmViewToContainer (FMTreeView x) = castToContainer . toGObject $ x
fmViewToContainer (FMIconView x) = castToContainer . toGObject $ x fmViewToContainer (FMIconView x) = castToContainer . toGObject $ x

View File

@@ -36,6 +36,7 @@ import Control.Monad
, when , when
, void , void
) )
import qualified Data.ByteString as BS
import Data.Version import Data.Version
( (
showVersion showVersion
@@ -62,6 +63,9 @@ import Graphics.UI.Gtk
import qualified HPath as P import qualified HPath as P
import HSFM.FileSystem.Errors import HSFM.FileSystem.Errors
import HSFM.FileSystem.FileOperations import HSFM.FileSystem.FileOperations
import HSFM.FileSystem.FileType
import HSFM.GUI.Glib.GlibString()
import HSFM.GUI.Gtk.Data
import HSFM.GUI.Gtk.Errors import HSFM.GUI.Gtk.Errors
import Paths_hsfm import Paths_hsfm
( (
@@ -180,7 +184,7 @@ withCopyModeDialog fa =
showAboutDialog :: IO () showAboutDialog :: IO ()
showAboutDialog = do showAboutDialog = do
ad <- aboutDialogNew ad <- aboutDialogNew
lstr <- readFile =<< getDataFileName "LICENSE" lstr <- Prelude.readFile =<< getDataFileName "LICENSE"
hsfmicon <- pixbufNewFromFile =<< getDataFileName "data/Gtk/icons/hsfm.png" hsfmicon <- pixbufNewFromFile =<< getDataFileName "data/Gtk/icons/hsfm.png"
pdesc <- fmap packageDescription pdesc <- fmap packageDescription
(readPackageDescription silent (readPackageDescription silent
@@ -244,3 +248,50 @@ textInputDialog title = do
_ -> throw UnknownDialogButton _ -> throw UnknownDialogButton
widgetDestroy chooserDialog widgetDestroy chooserDialog
return ret return ret
showFilePropertyDialog :: [Item] -> MyGUI -> MyView -> IO ()
showFilePropertyDialog [item] mygui _ = do
dialog <- messageDialogNew Nothing
[DialogDestroyWithParent]
MessageInfo
ButtonsNone
"File Properties"
let fprop' = fprop mygui
grid = fpropGrid fprop'
entrySetText (fpropFnEntry fprop') (maybe BS.empty P.fromRel
$ P.basename . path $ item)
entrySetText (fpropLocEntry fprop') (P.fromAbs . P.dirname . path $ item)
entrySetText (fpropTsEntry fprop') (fromFreeVar (show . fileSize) item)
entrySetText (fpropModEntry fprop') (packModTime item)
entrySetText (fpropAcEntry fprop') (packAccessTime item)
entrySetText (fpropFTEntry fprop') (packFileType item)
entrySetText (fpropPermEntry fprop')
(tail $ packPermissions item) -- throw away the filetype part
case packLinkDestination item of
(Just dest) -> do
widgetSetSensitive (fpropLDEntry fprop') True
entrySetText (fpropLDEntry fprop') dest
Nothing -> do
widgetSetSensitive (fpropLDEntry fprop') False
entrySetText (fpropLDEntry fprop') "( Not a symlink )"
cbox <- dialogGetActionArea dialog
_ <- dialogAddButton dialog "Ok" (ResponseUser 0)
_ <- dialogAddButton dialog "Cancel" (ResponseUser 1)
boxPackStart (castToBox cbox) grid PackNatural 5
widgetShowAll dialog
_ <- dialogRun dialog
-- make sure our grid does not get destroyed
containerRemove (castToBox cbox) grid
widgetDestroy dialog
return ()
showFilePropertyDialog _ _ _ = return ()

View File

@@ -16,6 +16,7 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
--} --}
{-# LANGUAGE RecordWildCards #-}
{-# OPTIONS_HADDOCK ignore-exports #-} {-# OPTIONS_HADDOCK ignore-exports #-}
module HSFM.GUI.Gtk.MyGUI where module HSFM.GUI.Gtk.MyGUI where
@@ -26,6 +27,7 @@ import Control.Concurrent.STM
newTVarIO newTVarIO
) )
import Graphics.UI.Gtk import Graphics.UI.Gtk
import HSFM.FileSystem.FileOperations
import HSFM.GUI.Gtk.Data import HSFM.GUI.Gtk.Data
import Paths_hsfm import Paths_hsfm
( (
@@ -46,6 +48,7 @@ createMyGUI = do
let settings' = MkFMSettings False True 24 let settings' = MkFMSettings False True 24
settings <- newTVarIO settings' settings <- newTVarIO settings'
operationBuffer <- newTVarIO None
builder <- builderNew builder <- builderNew
builderAddFromFile builder =<< getDataFileName "data/Gtk/builder.xml" builderAddFromFile builder =<< getDataFileName "data/Gtk/builder.xml"
@@ -53,64 +56,38 @@ createMyGUI = do
-- get the pre-defined gui widgets -- get the pre-defined gui widgets
rootWin <- builderGetObject builder castToWindow rootWin <- builderGetObject builder castToWindow
"rootWin" "rootWin"
scroll <- builderGetObject builder castToScrolledWindow
"mainScroll"
menubarFileQuit <- builderGetObject builder castToImageMenuItem menubarFileQuit <- builderGetObject builder castToImageMenuItem
"menubarFileQuit" "menubarFileQuit"
menubarFileOpen <- builderGetObject builder castToImageMenuItem
"menubarFileOpen"
menubarFileExecute <- builderGetObject builder castToImageMenuItem
"menubarFileExecute"
menubarFileNew <- builderGetObject builder castToImageMenuItem
"menubarFileNew"
menubarEditCut <- builderGetObject builder castToImageMenuItem
"menubarEditCut"
menubarEditCopy <- builderGetObject builder castToImageMenuItem
"menubarEditCopy"
menubarEditRename <- builderGetObject builder castToImageMenuItem
"menubarEditRename"
menubarEditPaste <- builderGetObject builder castToImageMenuItem
"menubarEditPaste"
menubarEditDelete <- builderGetObject builder castToImageMenuItem
"menubarEditDelete"
menubarHelpAbout <- builderGetObject builder castToImageMenuItem menubarHelpAbout <- builderGetObject builder castToImageMenuItem
"menubarHelpAbout" "menubarHelpAbout"
urlBar <- builderGetObject builder castToEntry
"urlBar"
statusBar <- builderGetObject builder castToStatusbar statusBar <- builderGetObject builder castToStatusbar
"statusBar" "statusBar"
clearStatusBar <- builderGetObject builder castToButton clearStatusBar <- builderGetObject builder castToButton
"clearStatusBar" "clearStatusBar"
rcMenu <- builderGetObject builder castToMenu fpropGrid <- builderGetObject builder castToGrid
"rcMenu" "fpropGrid"
rcFileOpen <- builderGetObject builder castToImageMenuItem fpropFnEntry <- builderGetObject builder castToEntry
"rcFileOpen" "fpropFnEntry"
rcFileExecute <- builderGetObject builder castToImageMenuItem fpropLocEntry <- builderGetObject builder castToEntry
"rcFileExecute" "fpropLocEntry"
rcFileNew <- builderGetObject builder castToImageMenuItem fpropTsEntry <- builderGetObject builder castToEntry
"rcFileNew" "fpropTsEntry"
rcFileCut <- builderGetObject builder castToImageMenuItem fpropModEntry <- builderGetObject builder castToEntry
"rcFileCut" "fpropModEntry"
rcFileCopy <- builderGetObject builder castToImageMenuItem fpropAcEntry <- builderGetObject builder castToEntry
"rcFileCopy" "fpropAcEntry"
rcFileRename <- builderGetObject builder castToImageMenuItem fpropFTEntry <- builderGetObject builder castToEntry
"rcFileRename" "fpropFTEntry"
rcFilePaste <- builderGetObject builder castToImageMenuItem fpropPermEntry <- builderGetObject builder castToEntry
"rcFilePaste" "fpropPermEntry"
rcFileDelete <- builderGetObject builder castToImageMenuItem fpropLDEntry <- builderGetObject builder castToEntry
"rcFileDelete" "fpropLDEntry"
upViewB <- builderGetObject builder castToButton notebook <- builderGetObject builder castToNotebook
"upViewB" "notebook"
homeViewB <- builderGetObject builder castToButton
"homeViewB"
refreshViewB <- builderGetObject builder castToButton
"refreshViewB"
menubarViewTree <- builderGetObject builder castToImageMenuItem
"menubarViewTree"
menubarViewIcon <- builderGetObject builder castToImageMenuItem
"menubarViewIcon"
-- construct the gui object -- construct the gui object
let menubar = MkMenuBar {..}
let fprop = MkFilePropertyGrid {..}
let mygui = MkMyGUI {..} let mygui = MkMyGUI {..}
-- sets the default icon -- sets the default icon

View File

@@ -16,7 +16,6 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
--} --}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK ignore-exports #-} {-# OPTIONS_HADDOCK ignore-exports #-}
@@ -48,21 +47,28 @@ import Data.Maybe
catMaybes catMaybes
, fromJust , fromJust
) )
import HSFM.FileSystem.Errors
(
canOpenDirectory
)
import Graphics.UI.Gtk import Graphics.UI.Gtk
import {-# SOURCE #-} HSFM.GUI.Gtk.Callbacks (setCallbacks) import {-# SOURCE #-} HSFM.GUI.Gtk.Callbacks (setViewCallbacks)
import HPath import HPath
( (
Path Path
, Abs , Abs
) )
import qualified HPath as P import qualified HPath as P
import HSFM.FileSystem.FileOperations
import HSFM.FileSystem.FileType import HSFM.FileSystem.FileType
import HSFM.GUI.Glib.GlibString() import HSFM.GUI.Glib.GlibString()
import HSFM.GUI.Gtk.Data import HSFM.GUI.Gtk.Data
import HSFM.GUI.Gtk.Icons import HSFM.GUI.Gtk.Icons
import HSFM.GUI.Gtk.Utils import HSFM.GUI.Gtk.Utils
import HSFM.Utils.IO import HSFM.Utils.IO
import Paths_hsfm
(
getDataFileName
)
import Prelude hiding(readFile) import Prelude hiding(readFile)
import System.INotify.ByteString import System.INotify.ByteString
( (
@@ -71,13 +77,18 @@ import System.INotify.ByteString
, killINotify , killINotify
, EventVariety(..) , EventVariety(..)
) )
import System.IO.Error
(
tryIOError
)
-- |Creates a new tab with its own view and refreshes the view.
newTab :: MyGUI -> IO FMView -> Path Abs -> IO MyView
newTab mygui iofmv path = do
myview <- createMyView mygui iofmv
_ <- notebookAppendPage (notebook mygui) (viewBox myview)
(maybe (P.fromAbs path) P.fromRel $ P.basename path)
refreshView mygui myview (Just path)
return myview
-- |Constructs the initial MyView object with a few dummy models. -- |Constructs the initial MyView object with a few dummy models.
-- It also initializes the callbacks. -- It also initializes the callbacks.
@@ -85,9 +96,11 @@ createMyView :: MyGUI
-> IO FMView -> IO FMView
-> IO MyView -> IO MyView
createMyView mygui iofmv = do createMyView mygui iofmv = do
operationBuffer <- newTVarIO None
inotify <- newEmptyMVar inotify <- newEmptyMVar
history <- newTVarIO ([],[])
builder <- builderNew
builderAddFromFile builder =<< getDataFileName "data/Gtk/builder.xml"
-- create dummy models, so we don't have to use MVar -- create dummy models, so we don't have to use MVar
rawModel <- newTVarIO =<< listStoreNew [] rawModel <- newTVarIO =<< listStoreNew []
@@ -99,14 +112,56 @@ createMyView mygui iofmv = do
view' <- iofmv view' <- iofmv
view <- newTVarIO view' view <- newTVarIO view'
urlBar <- builderGetObject builder castToEntry
"urlBar"
rcMenu <- builderGetObject builder castToMenu
"rcMenu"
rcFileOpen <- builderGetObject builder castToImageMenuItem
"rcFileOpen"
rcFileExecute <- builderGetObject builder castToImageMenuItem
"rcFileExecute"
rcFileNewRegFile <- builderGetObject builder castToImageMenuItem
"rcFileNewRegFile"
rcFileNewDir <- builderGetObject builder castToImageMenuItem
"rcFileNewDir"
rcFileCut <- builderGetObject builder castToImageMenuItem
"rcFileCut"
rcFileCopy <- builderGetObject builder castToImageMenuItem
"rcFileCopy"
rcFileRename <- builderGetObject builder castToImageMenuItem
"rcFileRename"
rcFilePaste <- builderGetObject builder castToImageMenuItem
"rcFilePaste"
rcFileDelete <- builderGetObject builder castToImageMenuItem
"rcFileDelete"
rcFileProperty <- builderGetObject builder castToImageMenuItem
"rcFileProperty"
rcFileIconView <- builderGetObject builder castToImageMenuItem
"rcFileIconView"
rcFileTreeView <- builderGetObject builder castToImageMenuItem
"rcFileTreeView"
upViewB <- builderGetObject builder castToButton
"upViewB"
homeViewB <- builderGetObject builder castToButton
"homeViewB"
refreshViewB <- builderGetObject builder castToButton
"refreshViewB"
scroll <- builderGetObject builder castToScrolledWindow
"mainScroll"
viewBox <- builderGetObject builder castToBox
"viewBox"
let rcmenu = MkRightClickMenu {..}
let myview = MkMyView {..} let myview = MkMyView {..}
-- set the bindings -- set the bindings
setCallbacks mygui myview setViewCallbacks mygui myview
-- add the treeview to the scroll container -- add the treeview to the scroll container
let oview = fmViewToContainer view' let oview = fmViewToContainer view'
containerAdd (scroll mygui) oview containerAdd scroll oview
widgetShowAll viewBox
return myview return myview
@@ -115,22 +170,41 @@ createMyView mygui iofmv = do
-- io action returns. -- io action returns.
switchView :: MyGUI -> MyView -> IO FMView -> IO () switchView :: MyGUI -> MyView -> IO FMView -> IO ()
switchView mygui myview iofmv = do switchView mygui myview iofmv = do
cwd <- getCurrentDir myview
oldpage <- destroyView mygui myview
-- create new view and tab page where the previous one was
nview <- createMyView mygui iofmv
newpage <- notebookInsertPage (notebook mygui) (viewBox nview)
(maybe (P.fromAbs $ path cwd) P.fromRel
$ P.basename . path $ cwd) oldpage
notebookSetCurrentPage (notebook mygui) newpage
refreshView' mygui nview cwd
-- |Destroys the current view by disconnecting the watcher
-- and destroying the active FMView container.
--
-- Everything that needs to be done in order to forget about a
-- view needs to be done here.
--
-- Returns the page in the tab list this view corresponds to.
destroyView :: MyGUI -> MyView -> IO Int
destroyView mygui myview = do
-- disconnect watcher
mi <- tryTakeMVar (inotify myview)
for_ mi $ \i -> killINotify i
page <- notebookGetCurrentPage (notebook mygui)
-- destroy old view and tab page
view' <- readTVarIO $ view myview view' <- readTVarIO $ view myview
let oview = fmViewToContainer view' widgetDestroy (fmViewToContainer view')
notebookRemovePage (notebook mygui) page
widgetDestroy oview return page
nview' <- iofmv
let nview = fmViewToContainer nview'
writeTVarIO (view myview) nview'
setCallbacks mygui myview
containerAdd (scroll mygui) nview
widgetShow nview
refreshView mygui myview Nothing
-- |Createss an IconView. -- |Createss an IconView.
@@ -156,6 +230,13 @@ createTreeView = do
tvs <- treeViewGetSelection treeView tvs <- treeViewGetSelection treeView
treeSelectionSetMode tvs SelectionMultiple treeSelectionSetMode tvs SelectionMultiple
-- set drag and drop
tl <- targetListNew
atom <- atomNew ("HSFM" :: String)
targetListAdd tl atom [TargetSameApp] 0
treeViewEnableModelDragDest treeView tl [ActionCopy]
treeViewEnableModelDragSource treeView [Button1] tl [ActionCopy]
-- create final tree model columns -- create final tree model columns
renderTxt <- cellRendererTextNew renderTxt <- cellRendererTextNew
renderPix <- cellRendererPixbufNew renderPix <- cellRendererPixbufNew
@@ -212,16 +293,10 @@ refreshView :: MyGUI
refreshView mygui myview mfp = refreshView mygui myview mfp =
case mfp of case mfp of
Just fp -> do Just fp -> do
-- readFileWithFileInfo can just outright fail... canopen <- canOpenDirectory fp
ecdir <- tryIOError (readFile getFileInfo fp) if canopen
case ecdir of then refreshView' mygui myview =<< readFile getFileInfo fp
Right cdir -> else refreshView mygui myview =<< getAlternativeDir
-- ...or return an `AnchordFile` with a Failed constructor,
-- both of which need to be handled here
if (failed cdir)
then refreshView mygui myview =<< getAlternativeDir
else refreshView' mygui myview cdir
Left _ -> refreshView mygui myview =<< getAlternativeDir
Nothing -> refreshView mygui myview =<< getAlternativeDir Nothing -> refreshView mygui myview =<< getAlternativeDir
where where
getAlternativeDir = do getAlternativeDir = do
@@ -229,7 +304,7 @@ refreshView mygui myview mfp =
Item) Item)
case ecd of case ecd of
Right dir -> return (Just $ path dir) Right dir -> return (Just $ path dir)
Left _ -> return (P.parseAbs "/") Left _ -> return (P.parseAbs P.pathSeparator')
-- |Refreshes the View based on the given directory. -- |Refreshes the View based on the given directory.
@@ -240,14 +315,14 @@ refreshView' :: MyGUI
-> MyView -> MyView
-> Item -> Item
-> IO () -> IO ()
refreshView' mygui myview dt@(DirOrSym _) = do refreshView' mygui myview item@(DirOrSym _) = do
newRawModel <- fileListStore dt myview newRawModel <- fileListStore item myview
writeTVarIO (rawModel myview) newRawModel writeTVarIO (rawModel myview) newRawModel
view' <- readTVarIO $ view myview view' <- readTVarIO $ view myview
_ <- tryTakeMVar (cwd myview) _ <- tryTakeMVar (cwd myview)
putMVar (cwd myview) dt putMVar (cwd myview) item
-- get selected items -- get selected items
tps <- getSelectedTreePaths mygui myview tps <- getSelectedTreePaths mygui myview
@@ -255,6 +330,12 @@ refreshView' mygui myview dt@(DirOrSym _) = do
constructView mygui myview constructView mygui myview
-- set notebook tab label
page <- notebookGetCurrentPage (notebook mygui)
child <- fromJust <$> notebookGetNthPage (notebook mygui) page
notebookSetTabLabelText (notebook mygui) child
(maybe (P.fromAbs $ path item) P.fromRel $ P.basename . path $ item)
-- reselect selected items -- reselect selected items
-- TODO: not implemented for icon view yet -- TODO: not implemented for icon view yet
case view' of case view' of
@@ -299,7 +380,7 @@ constructView mygui myview = do
cdirp <- path <$> getCurrentDir myview cdirp <- path <$> getCurrentDir myview
-- update urlBar -- update urlBar
entrySetText (urlBar mygui) (P.fromAbs cdirp) entrySetText (urlBar myview) (P.fromAbs cdirp)
rawModel' <- readTVarIO $ rawModel myview rawModel' <- readTVarIO $ rawModel myview

View File

@@ -67,24 +67,7 @@ getSelectedItems :: MyGUI
-> IO [Item] -> IO [Item]
getSelectedItems mygui myview = do getSelectedItems mygui myview = do
tps <- getSelectedTreePaths mygui myview tps <- getSelectedTreePaths mygui myview
getSelectedItems' mygui myview tps catMaybes <$> mapM (rawPathToItem myview) tps
getSelectedItems' :: MyGUI
-> MyView
-> [TreePath]
-> IO [Item]
getSelectedItems' _ myview tps = do
rawModel' <- readTVarIO $ rawModel myview
sortedModel' <- readTVarIO $ sortedModel myview
filteredModel' <- readTVarIO $ filteredModel myview
iters <- catMaybes <$> mapM (treeModelGetIter sortedModel') tps
forM iters $ \iter -> do
cIter' <- treeModelSortConvertIterToChildIter sortedModel' iter
cIter <- treeModelFilterConvertIterToChildIter filteredModel' cIter'
treeModelGetRow rawModel' cIter
-- |Carry out an action on the currently selected item. -- |Carry out an action on the currently selected item.
@@ -124,13 +107,14 @@ getFirstItem myview = do
-- |Reads the current directory from MyView. -- |Reads the current directory from MyView.
--
-- This reads the MVar and may block the main thread if it's
-- empty.
getCurrentDir :: MyView getCurrentDir :: MyView
-> IO Item -> IO Item
getCurrentDir myview = readMVar (cwd myview) getCurrentDir myview = readMVar (cwd myview)
-- |Push a message to the status bar. -- |Push a message to the status bar.
pushStatusBar :: MyGUI -> String -> IO (ContextId, MessageId) pushStatusBar :: MyGUI -> String -> IO (ContextId, MessageId)
pushStatusBar mygui str = do pushStatusBar mygui str = do
@@ -146,3 +130,37 @@ popStatusbar mygui = do
let sb = statusBar mygui let sb = statusBar mygui
cid <- statusbarGetContextId sb "FM Status" cid <- statusbarGetContextId sb "FM Status"
statusbarPop sb cid statusbarPop sb cid
-- |Turn a path on the rawModel into a path that we can
-- use at the outermost model layer.
rawPathToIter :: MyView -> TreePath -> IO (Maybe TreeIter)
rawPathToIter myview tp = do
fmodel <- readTVarIO (filteredModel myview)
smodel <- readTVarIO (sortedModel myview)
msiter <- treeModelGetIter smodel tp
forM msiter $ \siter -> do
cIter <- treeModelSortConvertIterToChildIter smodel siter
treeModelFilterConvertIterToChildIter fmodel cIter
-- |Turn a path on the rawModel into the corresponding item
-- that we can use at the outermost model layer.
rawPathToItem :: MyView -> TreePath -> IO (Maybe Item)
rawPathToItem myview tp = do
rawModel' <- readTVarIO $ rawModel myview
miter <- rawPathToIter myview tp
forM miter $ \iter -> treeModelGetRow rawModel' iter
-- |Makes sure the list is max 5. This is probably not very efficient
-- but we don't care, since it's a small list anyway.
addHistory :: Eq a => a -> [a] -> [a]
addHistory i [] = [i]
addHistory i xs@(x:_)
| i == x = xs
| length xs == maxLength = i : take (maxLength - 1) xs
| otherwise = i : xs
where
maxLength = 10

View File

@@ -0,0 +1,139 @@
{--
HSFM, a filemanager written in Haskell.
Copyright (C) 2016 Julian Ospald
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
--}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_HADDOCK ignore-exports #-}
module HSFM.Settings.Bookmarks where
import Control.Monad
(
void
)
import Data.Attoparsec.ByteString
import qualified Data.ByteString as BS
import Data.ByteString
(
ByteString
)
import Data.Maybe
(
catMaybes
, fromJust
)
import Data.Word8
(
_nul
)
import qualified HPath as P
import HPath
(
Abs
, Fn
, Path
)
import HSFM.FileSystem.FileOperations
import HSFM.FileSystem.FileType
import Prelude hiding (readFile, writeFile)
import System.Posix.Env.ByteString
(
getEnv
)
-- |A bookmark. `bkName` is principally a description of the bookmark
-- but must satisfy the constraints of a filename.
data Bookmark = MkBookmark {
bkName :: Path Fn
, bkPath :: Path Abs
} deriving (Show)
-- |Parses bookmarks from a ByteString that has pairs of
-- name and path. Name and path are separated by one null character
-- and the pairs itself are separated by two null characters from
-- each other.
bkParser :: Parser [Bookmark]
bkParser =
fmap catMaybes $ many' (fmap toBm $ bookmark <* word8 _nul <* word8 _nul)
where
toBm :: (ByteString, ByteString) -> Maybe Bookmark
toBm (name, path) = MkBookmark <$> P.parseFn name
<*> P.parseAbs path
bookmark :: Parser (ByteString, ByteString)
bookmark =
(\x y -> (BS.pack x, BS.pack y))
<$> many1' char
<* (word8 _nul)
<*> many1' char
char = satisfy (`notElem` [_nul])
-- |Writes bookmarks to a given file.
writeBookmarks :: [Bookmark] -> IO ()
writeBookmarks [] = return ()
writeBookmarks bs = do
bf <- bookmarksFile
bfd <- bookmarksDir
mkdirP bfd
readFile getFileInfo bfd >>= (\x -> createFile x bookmarksFileName)
let str = foldr1 (\x y -> x `BS.append` BS.pack [_nul, _nul]
`BS.append`
y `BS.append` BS.pack [_nul, _nul])
(fmap toByteString bs)
file <- readFile getFileInfo bf
void $ writeFile file str
where
toByteString :: Bookmark -> ByteString
toByteString b = (P.fromRel $ bkName b)
`BS.append` BS.singleton _nul
`BS.append` (P.fromAbs $ bkPath b)
-- |Reads bookmarks from a given file.
readBookmarks :: IO [Bookmark]
readBookmarks = do
p <- bookmarksFile
file <- readFile getFileInfo p
c <- readFileContents file
case parseOnly bkParser c of
Left _ -> return []
Right x -> return x
bookmarksDir :: IO (Path Abs)
bookmarksDir = do
mhomedir <- getEnv "HOME"
case mhomedir of
Nothing -> ioError (userError "No valid homedir?!")
Just home -> do
phome <- P.parseAbs home
reldir <- P.parseRel ".config/hsfm"
return $ phome P.</> reldir
bookmarksFile :: IO (Path Abs)
bookmarksFile = do
path <- bookmarksDir
return $ path P.</> bookmarksFileName
bookmarksFileName :: Path Fn
bookmarksFileName = fromJust $ P.parseFn "bookmarks"