Archive for the 'Development' Category

Start of OpenGL binding

Friday, November 4th, 2005

Here’s the early results of an OpenGL binding for Gtk2Hs:
[img]
You can’t see from the screenshot but in the live program the cube spins about.

I’m using the OpenGL extension for Gtk+ called GtkGlExt. This only takes the place of the GLUT, we still use the existing HOpenGL binding for all the actual OpenGL stuff.

The code for the above example was adapted from Kenneth Hoste’s RotatingCube example (which he adapted from somewhere else).

The ‘gtkglext’ package is now included the development version of Gtk2Hs if you want to try it out.

darcs get http://haskell.org/gtk2hs/darcs/gtk2hs/

Implementing tree/list data models in Haskell

Wednesday, July 27th, 2005

Another idea for improving the tree/list stuff would be to allow the data model to be defined in Haskell. That way one would not have to make do with the two models provided by Gtk+, the TreeStore and the ListStore. These are rather cumbersome to use from Haskell since it all has to be done in the IO monad. Also it doesn’t feel ideal since you’re having to stuff your data into a different structure rather than just providing an adaptor for your existing data model.

Now the Gtk+ tree/list system is designed to follow the model/view/controller pattern so it does allow you to create your own models that implement the GtkTreeModel interface. However Gtk2Hs does not provide a way to implement the GtkTreeModel interface using Haskell. So my suggestion is to do just that.

I’m not quite sure how this would pan out but I think it’s worth a go. It can’t be worse than the existing system! :-)

This would be implemented by writing in C a HaskellTreeStore that implementes the GtkTreeModel interface and deligates its implementation to a Haskell implementation. The Haskell implementation would probably be an instance of some new type class, lets call it TreeModelInterface.

So suppose you have some data and you want to connect it up to a list widget.

data MyDataset = ...

Then you would need to make it an instance of the TreeModelInterface

instance TreeModelInterface MyDataset where
  getNumberOfRows mydataset = ...
  getValue mydataset row = ...
  ...

Then if you’ve got a value of your MyDataset type then you could construct a TreeModel:

treeModelNew :: TreeModelInterface model => -> TreeModel

So you would then connect this new model to a view in much the same way as one currently does with the TreeStore and the ListStore models.

These names are not the greatest, just the first thing I thought of, suggestions welcome.

Initial Thoughts

Wednesday, July 27th, 2005

Duncan has pointed out that we’d like to re-implement my Mogul stuff for creating tree/lists widgets, hopefully using a more Attr-like style. The problem with the current Mogul stuff is that it provides constructors that return functions to read and write the cell values. Each pair of read-write function has a type that corresponds to the data stored at the specific column. Although this is very object-oriented, it means you have to keep two functions around for each column in the tree model. This is a pain since the read and write functions have to be passed to every function that manipulates the list/tree. It would be more convenient if phantom types or type classes could be used to ensure type-correct access. This is very appealing since the (type of the) columns of a tree model must be static anyway.
(Note that this does not mean that the set of columns that are shown is static; what columns are visible is determined by the view, not the model.)

Here is the framework:

  • a visible column is drawn by a cell renderer
  • each cell renderer has several attributes, e.g. the text of the cell and the foreground colour
  • while the foreground colour attribute can be the same for all rows, some attributes (like the text) should be different for each row; these changing attributes are retrieved from the model
  • the model contains a column for each of the changing attributes, and several rows that make up the actual data of the list or tree

What we aim for is a type-safe interface to access the different columns in the model. As mentioned above, the set of columns in a tree model are determined at creation time. One idea I had was to use a nested phantom type to represent the types of the columns. For example, a model that contains two String columns and one Pixbuf column could have the type

TreeModel (Column String (Column String (Column Pixbuf ())))

where Column is some empty newtype constructor. One problem I see with this approach is that accessing the nth column cannot be expressed. Instead one would have to define function to read or write a specific column, e.g.

readFirst :: TreeModel (Column val a) -> IO val

readSecond :: TreeModel (Column a (Column val b)) -> IO val

which obviously imposes a hard limit on the number of columns a program could defined. Suggestions welcome.

API changes between Gtk2Hs 0.9.7 and 0.9.8

Monday, June 20th, 2005

We are trying to make fixes and imprevements to the Gtk2Hs API before we reach version 1.0. For the the 1.0.x series the aim is for the API to remain stable and not contain backwards incompatbile changes between releases.

1. Significant changes

These API changes are will require minor changes in your code if you use any of the functions listed here.

1.1. Type changes

1.1.1. Added ‘Maybe’ types

These changes involve wrapping a function parameter or result in a Maybe type. This is usually because there is a meaningful ‘Nothing’ case that would otherwise be missed. (In previous Gtk2Hs versions the situations that now return ‘Nothing’ may have instead resulted in a segfault.)

binGetChild changed from

     :: BinClass bin => bin -> IO Widget
  to :: BinClass self => self -> IO (Maybe Widget)

panedGetChild1 changed from

     :: PanedClass p => p -> IO Widget
  to :: PanedClass self => self -> IO (Maybe Widget)

panedGetChild2 changed from

     :: PanedClass p => p -> IO Widget
  to :: PanedClass self => self -> IO (Maybe Widget)

entryCompletionGetModel changed from

     :: EntryCompletion -> IO TreeModel
  to :: EntryCompletion -> IO (Maybe TreeModel)

entryCompletionSetModel changed from

     :: EntryCompletion -> TreeModel -> IO ()
  to :: TreeModelClass model => EntryCompletion -> Maybe model -> IO ()

comboBoxSetModel changed from

     :: ComboBoxClass combo => combo -> TreeModel -> IO ()
  to :: (ComboBoxClass self, TreeModelClass model) => self -> Maybe model -> IO ()

treeViewGetCursor changed from

     :: TreeViewClass tv => tv -> IO (Maybe TreePath, Maybe TreeViewColumn)
  to :: TreeViewClass self => self -> IO (TreePath, Maybe TreeViewColumn)

1.1.2. Miscelanious type changes

miscSetAlignment changed from

     :: MiscClass m => m -> Double -> Double -> IO ()
  to :: MiscClass self => self -> Float -> Float -> IO ()

This function now takes Float rather than Double parameters which more accurately reflects the underlying C API.

frameGetLabelAlign changed from

     :: FrameClass f => f -> IO Float
  to :: FrameClass self => self -> IO (Float, Float)

frameSetLabelAlign changed from

     :: FrameClass f => f -> Float -> IO ()
  to :: FrameClass self => self -> Float -> Float -> IO ()

frameSetLabelAlign now takes an extra Float argument and frameGetLabelAlign now returns an extra Float argument. This extra value is the Y alignment.

Pass 0.0 for the Y alignment (the second argument) to frameSetLabelAlign to get the previous behavour of this function.

These two functions previosuly only set and returned the X alignment of the frame’s label. They now act on both theX and Y alignment.

1.1.3. Swapped parameter order

For some reason these functions had their argumets the wrong way round. The object being acted upon should be the first argument.

toggleButtonSetActive changed from

     :: ToggleButtonClass tb => Bool -> tb -> IO ()
  to :: ToggleButtonClass self => self -> Bool -> IO ()

toggleButtonSetInconsistent changed from

     :: ToggleButtonClass tb => Bool -> tb -> IO ()
  to :: ToggleButtonClass self => self -> Bool -> IO ()

textBufferGetIterAtLine changed from

     :: TextBufferClass tb => Int -> tb -> IO TextIter
  to :: TextBufferClass self => self -> Int -> IO TextIter

textViewForwardDisplayLineEnd changed from

     :: TextViewClass tv => TextIter -> tv -> IO Bool
  to :: TextViewClass self => self -> TextIter -> IO Bool

treeViewExpandRow changed from

     :: TreeViewClass tv => TreePath -> Bool -> tv -> IO Bool
  to :: TreeViewClass self => self -> TreePath -> Bool -> IO Bool

1.2. Name Changes

treeViewGetHadjustment has been renamed to treeViewGetHAdjustment

treeViewGetVadjustment has been renamed to treeViewGetVAdjustment

treeViewSetHadjustment has been renamed to treeViewSetHadjustment

treeViewSetVadjustment has been renamed to treeViewSetVAdjustment

The names of the adjustment objects are VAdjustment and HAdjustment, not Vadjustment and Hadjustment. Also, for some reason the “Set” variants of these functions had their argumets the wrong way round. The object being acted upon should be the first argument.

fileChooserlistShortcutFolders has been renamed to fileChooserListShortcutFolders

1.2.1. Priority constants

Changed the meaning of the priority contants used by idleAdd and timeoutAdd.

If you use these functions you should change uses of priorityDefault to priorityDefaultIdle and priorityHigh to priorityHighIdle. This will give the same behaviour as before - which is almost certainly what you want.

Previously priorityDefault had actually been the value of G_PRIORITY_DEFAULT_IDLE rather than G_PRIORITY_DEFAULT and similarly for priorityHigh.

So now priorityDefault refers to G_PRIORITY_DEFAULT and there is a new value priorityDefaultIdle that refers to G_PRIORITY_DEFAULT_IDLE.

1.3. Removed functions and values

The event type testing functions: hasButLeft, hasButMiddle, hasButRight, hasControl, hasLock, hasMod1, hasMod2, hasMod3, hasMod4, hasMod5, hasShift have been removed. The Modifier data type is not opaque anymore so you can use the Modifier data constructors instead.

textViewBackwardDisplayLineEnd and textViewForwardDisplayLineStart have been removed. In a sense they never really existed! They were incorrectly implemented as aliases to other functions. If you were using textViewBackwardDisplayLineEnd you were actually using textViewBackwardDisplayLineStart and similarly if you were using textViewForwardDisplayLineStart you were actually using textViewForwardDisplayLineStart. So you can fix your code by doing the appropriate renaming.

2. Insignificant changes

These changes will probably not require any changes in your code, even if you use the functions listed in this section.

2.1. Generalised types

These changes are where a function parameter that was a specific concrete type has been replaced by a type class of which the original concrete type was a member. So for example, instead of requiring a TreeModel we allow any type in the TreeModelClass. Such a change will not break programs except in the unlikely circumstance that it introduces a type ambiguity.

In the OOP model this change means allowing subclasses to be passed as well.

comboBoxNewWithModel changed from

     :: TreeModel -> IO ComboBox
  to :: TreeModelClass model => model -> IO ComboBox

comboBoxEntryNewWithModel changed from

     :: TreeModel -> Int -> IO ComboBoxEntry
  to :: TreeModelClass model => model -> Int -> IO ComboBoxEntry

toolbarSetDropHighlightItem changed from

     :: ToolbarClass tb => tb -> Maybe ToolItem -> Int -> IO ()
  to :: (ToolbarClass self, ToolItemClass toolItem) => self -> Maybe toolItem -> Int -> IO ()

textBufferApplyTag changed from

     :: TextBufferClass tb => tb -> TextTag -> TextIter -> TextIter -> IO ()
  to :: (TextBufferClass self, TextTagClass tag) => self -> tag -> TextIter -> TextIter -> IO ()

textBufferDeleteMark changed from

     :: TextBufferClass tb => tb -> TextMark -> IO ()
  to :: (TextBufferClass self, TextMarkClass mark) => self -> mark -> IO ()

textBufferGetIterAtMark changed from

     :: TextBufferClass tb => tb -> TextMark -> IO TextIter
  to :: (TextBufferClass self, TextMarkClass mark) => self -> mark -> IO TextIter

textBufferMoveMark changed from

     :: TextBufferClass tb => tb -> TextMark -> TextIter -> IO ()
  to :: (TextBufferClass self, TextMarkClass mark) => self -> mark -> TextIter -> IO ()

textBufferNew changed from

     :: Maybe TextTagTable -> IO TextBuffer
  to :: TextTagTableClass table => Maybe table -> IO TextBuffer

textBufferRemoveTag changed from

     :: TextBufferClass tb => tb -> TextTag -> TextIter -> TextIter -> IO ()
  to :: (TextBufferClass self, TextTagClass tag) => self -> tag -> TextIter -> TextIter -> IO ()

textTagTableAdd changed from

     :: TextTagTableClass obj => obj -> TextTag -> IO ()
  to :: (TextTagTableClass self, TextTagClass tag) => self -> tag -> IO ()

textTagTableRemove changed from

     :: TextTagTableClass obj => obj -> TextTag -> IO ()
  to :: (TextTagTableClass self, TextTagClass tag) => self -> tag -> IO ()

textViewMoveMarkOnscreen changed from

     :: TextViewClass tv => tv -> TextMark -> IO Bool
  to :: (TextViewClass self, TextMarkClass mark) => self -> mark -> IO Bool

textViewNewWithBuffer changed from

     :: TextBuffer -> IO TextView
  to :: TextBufferClass buffer => buffer -> IO TextView

textViewScrollMarkOnscreen changed from

     :: TextViewClass tv => tv -> TextMark -> IO ()
  to :: (TextViewClass self, TextMarkClass mark) => self -> mark -> IO ()

textViewScrollToMark changed from

     :: TextViewClass tv => tv -> TextMark -> Double -> Maybe (Double, Double) -> IO ()
  to :: (TextViewClass self, TextMarkClass mark) => self -> mark -> Double -> Maybe (Double, Double) -> IO ()

textViewSetBuffer changed from

     :: TextViewClass tv => tv -> TextBuffer -> IO ()
  to :: (TextViewClass self, TextBufferClass buffer) => self -> buffer -> IO ()

treeViewSetCursorOnCell changed from

     :: TreeViewClass tv => tv -> TreePath -> TreeViewColumn -> CellRenderer -> Bool -> IO ()
  to :: (TreeViewClass self, CellRendererClass focusCell) => self -> TreePath -> TreeViewColumn -> focusCell -> Bool -> IO ()

2.2. Specialised types

This is the opposite change to those above. It replaces a polymorphic parameter constrained by a type class with a concrete member of that class, or by another type class that inherits from the original one. These examples should not break any code since the concrete type was the only member of the type class.

In the OOP model this amounts to making the object type closed, so it cannot be inherited from.

containerSetFocusHAdjustment changed from

     :: (ContainerClass c, AdjustmentClass a) => c -> a -> IO ()
  to :: ContainerClass self => self -> Adjustment -> IO ()

containerSetFocusVAdjustment changed from

     :: (ContainerClass c, AdjustmentClass a) => c -> a -> IO ()
  to :: ContainerClass self => self -> Adjustment -> IO ()

afterToggled changed from

     :: ButtonClass b => b -> IO () -> IO (ConnectId b)
  to :: ToggleButtonClass self => self -> IO () -> IO (ConnectId self)

onToggled changed from

     :: ButtonClass b => b -> IO () -> IO (ConnectId b)
  to :: ToggleButtonClass self => self -> IO () -> IO (ConnectId self)

every TreeViewColumn function changed from

     :: TreeViewColumnClass tvc => tvc -> ...
  to :: TreeViewColumn -> ...