-
Notifications
You must be signed in to change notification settings - Fork 0
Forms
To use this package import module Graphics.Forms
.
The forms plugin offers a description language for editors of simple data. It additionally provides a mechanism for shared perferences, which can be stored in simple config files and can be edited with a derived editor.
To describe a input form you can use
* VertBox were one element is below the other
* HoriBox were one element is right of the otherwise
* TabbedBox were one visible element hides the one behind
* Fields which describe a real data element with:
* Parameters which many times give Name and Synopsis for this field,
but which may as well contain gtk layout parameters
* Getter function
* Setter function
* Editor
We have some simple and composite editors, and it is possible to build your own editors by combining existing editors.
So here is an example with the description:
pluginConfDescr ∷ FieldDescriptionG PluginConfig
pluginConfDescr = VertBoxG defaultParams [
HoriBoxG defaultParams [
mkFieldG
(("Name",ParaString "Name of the config") `setPara` defaultParams)
cfName
(λ b a → a{cfName = b})
(stringEditor (const True) True)
, mkFieldG
(("Name", ParaString "Version") `setPara` defaultParams)
cfVersion
(λ b a → a{cfVersion = b})
versionEditor],
HoriBoxG (("VPack", ParaPack PackGrow) `setPara` defaultParams) [
mkFieldG
(("Name", ParaString "Plugin list") `setPara`
("Direction", ParaDir Vertical) `setPara`
("MinSize", ParaSize (-1,125)) `setPara` defaultParams)
(λ plugin → (cfPlugins plugin,cfChoices plugin))
(λ b a → a{cfPlugins = fst b, cfChoices = snd b})
(prerequisitesEditor (Just deleteHandler))
, mkFieldG
(("Name",ParaString "Synopsis") `setPara`
("Synopsis", ParaString "or call it comment") `setPara` defaultParams)
cfSynopsis
(λ b a → a{cfSynopsis = b})
multilineStringEditor]]
The Layout can be influenced by setting parameters. All this parameters have a standard setting. They are all passed to gtk+ in the end, so the gtk manual is the final reference for layout.
This has the disadvantage, that it can be really hard to get the layout you want. The advantage is that you have generally a consistent look (almost for free).
VBoxHomogeneous
~ Shall all box elements occupy the same space (Default: False)
~ Example:
replace: VertBoxG defaultParams [..]
with: VertBoxG (("VBoxHomogeneous", ParaBool True) ``setPara`` defaultParams) [..]
so that any element gets the same space
HBoxHomogeneous
~ Shall all box elements occupy the same space (Default: True)
VPack
~ How is an element packed in a vertical box (PackRepel, PackGrow, PackNatural Default)
~ Can be specified for every element
~ e.g. to make a certain HBox grow when the place gets bigger
~ HoriBoxG (("VPack", ParaPack PackGrow) ``setPara`` defaultParams) [..]
HPack
~ How is an element packed in a vertical box (PackRepel, PackGrow Default, PackNatural)
~ Can be specified for every element
~ If you want to specify a packing, regardless of the box it is packed into
~ specify both parameters:
~ (("HPack",ParaPack PackGrow) ``setPara`` ("VPack",ParaPack PackGrow)
setPara parameters)
Any field is wrapped in an outer alignment object with the parameters:
OuterAlignment
~ (xalign, yalign, xscale, yscale) Default: (ParaAlign (0.0, 0.0, 1, 1))
~ which means (place left top, and stretch)
OuterPadding
~ Defines a space in pixels for the outer alignment
~ (top, bottom, left, right) Default: (ParaPadding (5, 5, 5, 5))
Inside the padding is a frame with an optional label:
ShowLable
~ You want the label to be displayed
Name
~ Used for the label Default: (ParaString "")
LabelAlign
~ Where to place the label (Default: (ParaPos (0.5, 0.0))
~ meaning in the middle and at the top
Shadow
~ Make this frame visible with one of
~ ShadowNone (default), ShadowIn, ShadowOut, ShadowEtchedIn, ShadowEtchedOut
Synopsis
~ Used for tooltips for further info
~ Used as comments in config files
Inside the fame is another alignment:
InnerAlignment
~ (xalign, yalign, xscale, yscale) Default: (ParaAlign (0.0, 0.0, 1, 1))
~ which means (place left top, and stretch)
InnerPadding
~ Defines a space in pixels for the outer alignment
~ (top, bottom, left, right) Default: (ParaPadding (5, 5, 5, 5))
Inside the inner alignment is the editor widget:
MinSize ~ Specify a concrete minimum size for the widget ~ Default is ParaSize (-1,-1), which means no minimum size
Other parameters are:
StockId ~ Used for special editors to specify a stock item ~ Default: (ParaString "")
Direction ~ Used for composite editors to specify how to arrange components ~ Default: (ParaDir Horizontal) MultiSel ~ Used for some editors to specify if multiple selections should be possible ~ Default: (ParaBool True)
This is a collection of simple editors the plugin provides:
-- | Editor for a boolean value in the form of a check button
boolEditor ∷ Editor Bool
-- | Editor for a boolean value in the form of two radio buttons
boolEditor2 ∷ String → Editor Bool
-- | Editor for an enum value in the form of n radio buttons
enumEditor ∷ forall α . (Show α, Enum α, Bounded α) ⇒ [String] → Editor α
-- | Editor for a string in the form of a text entry
stringEditor ∷ (String → Bool) → Bool → Editor String
-- | Editor for an integer in the form of a spin entry
intEditor ∷ (Double,Double,Double) → Editor Int
-- | Editor for for any value which is an instance of Read and Show in the form of a
-- | text entry
genericEditor ∷ (Show β, Read β) ⇒ Editor β
-- | Editor for the selection of some element from a static list of elements in the
-- | form of a combo box
comboSelectionEditor ∷ Eq β ⇒ [β] → (β → String) → Editor β
-- | Editor for the selection of some elements from a list of elements in the
-- | form of a list box
multiselectionEditor ∷ (Show β, Eq β) ⇒ Editor [β]
-- | Editor for the selection of some elements from a static list of elements in the
-- | form of a list box with toggle elements
staticListMultiEditor ∷ (Eq β) ⇒ [β] → (β → String) → Editor [β]
-- | Editor for the selection of some elements from a static list of elements in the
-- | form of a list box
staticListEditor ∷ (Eq β) ⇒ [β] → (β → String) → Editor β
-- | Editor for the selection of a file path in the form of a text entry and a button,
-- | which opens a gtk file chooser
fileEditor ∷ Maybe FilePath → FileChooserAction → String → Editor FilePath
-- | Editor for a font selection
fontEditor ∷ Editor (Maybe String)
-- | An editor, which opens another editor
-- You have to inject a value before the button can be clicked.
otherEditor ∷ (α → String → IO (Maybe α)) → Editor α
-- | An Editor for nothing (which may report a click) in the form of a button
clickEditor ∷ Bool → Editor ()
-- | Editor for no value, it only emtis a clicked event and has the form of a check button
buttonEditor ∷ Editor ()
-- | An invisible editor without any effect
noEditor ∷ α → Editor α
-- | An Editor to display an image
imageEditor ∷ Editor StockId
And this is a collection of composite editors:
-- | An editor which composes two subeditors
pairEditor ∷ (Editor α, Parameters) → (Editor β, Parameters) → Editor (α,β)
tupel3Editor ∷ (Editor α, Parameters)
→ (Editor β, Parameters)
→ (Editor γ, Parameters)
→ Editor (α,β,γ)
-- | Like a pair editor, but with a moveable split
splitEditor ∷ (Editor α, Parameters) → (Editor β, Parameters) → Editor (α,β)
-- | An editor with a subeditor which gets active, when a checkbox is selected
-- or deselected (if the positive Argument is False)
maybeEditor ∷ Default β ⇒ (Editor β, Parameters) → Bool → String → Editor (Maybe β)
-- | An editor with a subeditor which gets active, when a checkbox is selected
-- or grayed out (if the positive Argument is False)
disableEditor ∷ Default β ⇒ (Editor β, Parameters) → Bool → String → Editor (Bool,β)
-- | An editor with a subeditor which gets active, when a checkbox is selected
-- or deselected (if the positive Argument is False)
eitherOrEditor ∷ (Default α, Default β) ⇒ (Editor α, Parameters) →
(Editor β, Parameters) → String → Editor (Either α β)
-- | An editor for a selection from some given elements
selectionEditor ∷ (Show α, Default α, Eq α, Typeable α) ⇒ ColumnDescr α
→ Maybe (α → α → Ordering) -- ^ The 'mbSort' arg, a sort function if desired
→ Maybe (α → α → Bool) -- ^ the test to ommit double insertions
→ Maybe ([α] → StateM())
→ Editor ([α],[α])
-- | An editor with a subeditor, of which a list of items can be selected
multisetEditor ∷ (Show α, Default α, Eq α) ⇒ ColumnDescr α
→ (Editor α, Parameters)
→ Maybe ([α] → [α]) -- ^ The 'mbSort' arg, a sort function if desired
→ Maybe (α → α → Bool) -- ^ The 'mbReplace' arg, a function which is a criteria for removing an
-- old entry when adding a new value
→ Editor [α]
-- a trivial example: (ColumnDescr False [("",(\row -> [cellText := show row]))])
-- and a nontrivial:
-- [("Package",\(Dependency str _) -> [cellText := str])
-- ,("Version",\(Dependency _ vers) -> [cellText := showVersionRange vers])])
data ColumnDescr row = ColumnDescr Bool [(String,(row → [AttrOp CellRendererText]),
Maybe (row → String → row))]
filesEditor ∷ Maybe FilePath → FileChooserAction → String → Editor [FilePath]
stringsEditor ∷ (String → Bool) → Bool → Editor [String]
dependencyEditor ∷ [PackageIdentifier] → Editor Dependency
versionRangeEditor ∷ Editor VersionRange
If you want a pane, which have just a form as contents, we have a standard function to build it:
-- | Returns a builder for a pane
-- Requires a forms description, an initial value and a FormPaneDescr
buildFormsPane ∷ Pane β ⇒ FieldDescriptionG α → α → FormPaneDescr α β
→ (PanePath → Notebook → Window → StateM (Maybe β, Connections))
data FormPaneDescr α β = FormPaneDescr {
fpGetPane ∷ Pane β ⇒ VBox → Injector α → Extractor α → β, -- ^ Construct the pane data type
fpSaveAction ∷ α → StateM (), -- ^ Called when the save button is hit
fpHasChanged ∷ α → α → Bool, -- ^ Judge if this qualify as a change
fpGuiHandlers ∷ [([GUIEventSelector],Handler GUIEvent)], -- ^ Handle GUI Events triggered
fpExtraButtons ∷ [(String,StateM ())]} -- ^ Add extra buttons with handlers
And here is an example how you can use it:
buildPluginPane ∷ PanePath → Notebook → Window → StateM (Maybe PluginPane, Connections)
buildPluginPane = λ pp nb w → makeValue ↠ λ initial →
(buildFormsPane pluginDescr initial formPaneDescr) pp nb w
where
makeValue = do
currentConfigPath ← getCurrentConfigPath
choices ← liftIO $ getPrereqChoices (dropFileName currentConfigPath)
return defaultPlugin{plChoices = choices}
formPaneDescr ∷ FormPaneDescr Plugin PluginPane = FormPaneDescr {
fpGetPane = λ top inj ext → PluginPane top inj ext,
fpSaveAction = λ v → do
currentConfigPath ← getCurrentConfigPath
liftIO $ writePluginDescr (dropFileName currentConfigPath
</> getPluginName v <.> ".lkshp") v
triggerPluginPane PluginDescrChanged
return (),
fpEqual = λ v1 v2 → v1{plChoices = []} ≡ v2{plChoices = []},
fpGuiHandlers = [],
fpExtraButtons = []}
So this is the code, to build the actual pane with the editor you have already seen.
Lets say our dummy plugin needs to store a String for configuration. First we register for the forms event channel and add the description of the preferences for our dummy plugin:
-- | Define the prefs you need
data DummyPrefs = DummyPrefs {
dummyArg ∷ String}
deriving (Eq,Typeable)
-- | Handle the registerPrefs event
formsEventHandler (RegisterPrefs prefs) = return (RegisterPrefs $ prefs ⊕
[("Dummy",GenF dummyPrefsDescr defaultDummyPrefs)])
formsEventHandler other = return other
-- | The description of the editor
dummyPrefsDescr ∷ FieldDescription DummyPrefs
dummyPrefsDescr =
VertBox defaultParams [
mkField
(("Name", ParaString "Dummy arg") <<< defaultParams)
stringPrinter
stringParser
dummyArg
(λb a → a{dummyArg = b})
(stringEditor (const True) True)
(λ_i → return ())]
-- | The default value
defaultDummyPrefs = DummyPrefs "Just a dummy"
The description is akin to the description of editors, but adds a printer and a parser for storing and retrieving the value(2. and 3. field), and an applicator(7. field), for applying the new prefs to the running plugin.
You can now use the getPrefs ∷ Typeable α ⇒ String → StateM α
function to get the
value for the prefs, in our case DummyPrefs configString <- getPrefs "Dummy"
.
In the preferences file, you will find the following entry:
[Dummy]
Dummy arg: Just a dummy
Here is the interface for the preferences module, but as a plugin writer you should assume that the application already handles reading and editing of preferences.
-- | Load preferences from filepath.
-- Pref descriptions needs to be registered before
loadPrefs ∷ FilePath → StateM ()
-- | Save preferences to filepath.
-- Pref descriptions needs to be registered before.
savePrefs ∷ FilePath → StateM ()
-- | Gets a preference value from a category
getPrefs ∷ Typeable α ⇒ String → StateM α
-- | Sets a preference value for a category
setPrefs ∷ (Eq α , Typeable α) ⇒ String → α → StateM ()
-- | opens up an editor for preferences
openPreferencesPane ∷ StateM ()