
MacOS treats windows and "controls" (i.e. widgets inside a window) as completely different things. Most other platforms treat them as essentially the same things. Which route should the CGA go? The situation seems to be something like this: + Widget everything that occupies space within some coordinate system. Has attributes like size. - Window A toplevel Window. There will be several variants of this, for document windows, dialog boxes etc. + Pane A widget "inside" a window (a window has panes - I've seen this name somewhere, feel free to suggest a more standard one) - Button - GridBox - Label - etc. "Container" could be a class outside this hierarchy, implemented by "Window", "GridBox" and a few other widgets. A Container would only contain Panes, never other widgets. The relationship between a window and a window-modal dialog window has nothing to do with the relationship between a window and a button inside that window, IMHO, so they shouldn't be expressed using the same parent/child relationship. Now perhaps we want a common datatype "Widget" with functions toWidget :: Widget w => w -> Widget and fromWidget :: Widget w => Widget -> Maybe w Perhaps we want a datatype "Pane" (or "Control" or whatever) with toPane :: Pane p => p -> Pane and so on. If I had a "Pane" datatype, I wouldn't really need the "Widget" anymore, but of course, one can never know. The "Pane" datatype would be the natural place to implement a lot of functionality at least on Mac OS, so if it's there, I'm happy, and I could then implement a Widget datatype as a simple discriminated union datatype. If there was no Pane datatype, the implementation for the widget datatype would become slightly ugly and inelegant on Mac OS... Cheers, Wolfgang

Wolfgang Thaller wrote:
Now perhaps we want a common datatype "Widget" with functions toWidget :: Widget w => w -> Widget and fromWidget :: Widget w => Widget -> Maybe w
Perhaps we want a datatype "Pane" (or "Control" or whatever) with toPane :: Pane p => p -> Pane and so on.
I don't see the point in this. If you have classes, then surely you don't need the types?
If I had a "Pane" datatype, I wouldn't really need the "Widget" anymore, but of course, one can never know. The "Pane" datatype would be the natural place to implement a lot of functionality at least on Mac OS, so if it's there, I'm happy, and I could then implement a Widget datatype as a simple discriminated union datatype. If there was no Pane datatype, the implementation for the widget datatype would become slightly ugly and inelegant on Mac OS...
The obvious solution seems to be to use classes. E.g. operations on
menu bars would operate on the MenuBar class, i.e.
doSomethingToMenuBar :: MenuBar w => w -> ...
For MacOS, a menu bar would be an instance of MenuBar. For Motif, it
would be an instance of both MenuBar and Widget. Code which is meant
to be portable would only use the MenuBar methods; Motif-specific code
could also use the Widget methods.
It seems to me that, as far as possible, the CGA should define (and
use) classes rather than types.
--
Glynn Clements

Glynn Clements wrote:
Wolfgang Thaller wrote:
Now perhaps we want a common datatype "Widget" with functions toWidget :: Widget w => w -> Widget and fromWidget :: Widget w => Widget -> Maybe w
Perhaps we want a datatype "Pane" (or "Control" or whatever) with toPane :: Pane p => p -> Pane and so on.
I don't see the point in this. If you have classes, then surely you don't need the types?
The example implementation already had a "Widget" type; a Widget type would be necessary when you want to have heterogenous lists of widgets. That's why somebody brought up the idea of having the toWidget and fromWidget functions; Nobody protested when the idea was first proposed, so I assumed that it was agreed. If such a generic type is used, that has certain implications for the implementation; the fltk-based example uses one generic type "Widget" along with specialized types "Window", "Button" and "Box", each of which is an instance of Widget (with methods toWidget and fromWidget). I was just pointing out that this specific choice of just one generic type for both Windows and other Widgets was impractical for the Mac OS implementation. Cheers, Wolfgang

Wolfgang Thaller
The example implementation already had a "Widget" type; a Widget type would be necessary when you want to have heterogenous lists of widgets. That's why somebody brought up the idea of having the toWidget and fromWidget functions; Nobody protested when the idea was first proposed, so I assumed that it was agreed.
By itself, there's no problem having a Widget type and to/fromWidget operations. After all, with _only_ those two operations, there's not that much you can do with them. But, the moment we add additional operations which act on objects of type Widget and not on objects in the Widget class, my gut tells me there will be problems. I was keeping quiet until such operations were proposed since it'd be easier to point out problems with concrete examples in front of us. -- Alastair Reid alastair@reid-consulting-uk.ltd.uk Reid Consulting (UK) Limited http://www.reid-consulting-uk.ltd.uk/alastair/

On Tue, Apr 29, 2003 at 09:43:18AM +0100, Alastair Reid wrote:
Wolfgang Thaller
writes: The example implementation already had a "Widget" type; a Widget type would be necessary when you want to have heterogenous lists of widgets. That's why somebody brought up the idea of having the toWidget and fromWidget functions; Nobody protested when the idea was first proposed, so I assumed that it was agreed.
By itself, there's no problem having a Widget type and to/fromWidget operations. After all, with _only_ those two operations, there's not that much you can do with them.
But, the moment we add additional operations which act on objects of type Widget and not on objects in the Widget class, my gut tells me there will be problems. I was keeping quiet until such operations were proposed since it'd be easier to point out problems with concrete examples in front of us.
IMHO the classes should just reflect the object hierarchy. If we have e.g. class Button b where... class ToggleButton b where... instance Button ToggleButton where... then we can have the simple function onActivate :: Button b => b -> IO () -> IO () and use it with ToggleButton as well (but not with a plain Widget or anything on the other branches of the tree). Having onActivate as a class member doesn't give any improvement in type safety, is less efficient (bigger dictionaries) and doesn't lend itself to automatic creation of this type hierarchy. Axel.

Axel Simon wrote:
IMHO the classes should just reflect the object hierarchy. If we have e.g.
class Button b where... class ToggleButton b where... instance Button ToggleButton where...
then we can have the simple function
onActivate :: Button b => b -> IO () -> IO ()
and use it with ToggleButton as well (but not with a plain Widget or anything on the other branches of the tree). Having onActivate as a class member doesn't give any improvement in type safety, is less efficient (bigger dictionaries) and doesn't lend itself to automatic creation of this type hierarchy.
Axel.
I'm no longer entirely sure what we are talking about. What is "the object hierarchy"? Could somebody who has an opinion on these issues knock up a concrete example; I'm not thinking of code, just some class declarations and signatures... Here's a summary of what's in the FLTK-based example that David Sankel posted a while ago. I've added some comments. class IsWidget a where toWidget :: a -> IO Widget data Widget instance IsWidget Widget class HasOnActivate a where doOnActivate :: a -> IO () -> IO( IO() ) class HasTitle w where title :: Attr w String -- This should be left to layout management: class HasPosition w where position :: Attr w (Int,Int) class HasSize w where size :: Attr w (Int,Int) -- The following two are not possible, because of some toolkits' inability to reparent widgets after creation: class HasAddChild a where addChild :: IsWidget b => a -> b -> IO () class HasChildren w ehere children :: Attr w [Widget] -- === data Window mkWindow :: [Prop Window] -> IO Window instance IsWidget window instance HasAddChild window instance HasChildren window visible :: Attr Window Bool -- === data Button mkButton :: [Prop Button] -> IO Button instance IsWidget Button instance HasTitle Button instance HasOnActivate Button instance HasPosition Button instance HasSize Button -- === -- The whole "Box" widget should probably be replaced by different kinds of boxes -- that do different kinds of layout. data Box mkBox :: [Prop Box] -> IO Box instance IsWidget Box instance HasTitle Box -- <= it seems that in fltk, a box is more than just a simple container.... instance HasPosition Box instance HasSize Box -- === Some more notes: If we want to implement a dynamic layout manager in Haskell for platforms that don't support that natively, we will need a way to query things like minimum size when we have just values of some general type (like Widget, though I already suggesting having a generic type that only includes all widgets _except_ windows). The current "CGA example" doesn't provide that. Also, the CGA example currently has completely separate code for the "HasSize" instances for Buttons and Boxes; but in the end, they both call the same virtual member function on a C++ object. There should be a way for backend implementors to avoid that needless duplication of work. Cheers, Wolfgang

Axel Simon wrote:
IMHO the classes should just reflect the object hierarchy. If we have e.g.
class Button b where... class ToggleButton b where... instance Button ToggleButton where...
Different toolkits have different hierarchies; e.g.:
Motif:
Core
XmPrimitive
XmLabel
XmPushButton
XmToggleButton
XmArrowButton
Athena:
Core
Simple
Label
Command (push-button)
Repeater
Toggle
Qt:
QWidget
QFrame
QLabel
QButton
QPushButton
QRadioButton
QCheckBox
QToolButton
Neither Motif nor Athena have separate classes for toggle buttons and
radio buttons.
Also, the situation for shell widgets (top-level windows) is a bit
messy for Motif. Motif's shell classes are all subclasses of Xt's
Shell class, which is a subclass of Xt's Composite class (containers).
However, all of Motif's normal (non-shell) containers are subclasses
of XmManager, while the shells aren't.
For Xt (Motif/Athena), the class hierachy doesn't matter much to
applications.
Widgets are mostly accessed through generic Xt functions which operate
upon any type of widget; e.g. setting/getting attribute values is done
with XtSetValues/XtGetValues, which work on any widget (accessing an
attribute which the widget doesn't possess is a run-time error).
Widget classes may provide functions which only operate upon a
specific type of widget (or a subclass), but such functions mostly
exist only for the more complex widget classes (lists, text fields)
which tend not to have subclasses.
The class hierarchy is most relevant when implementing new widget
classes; but that is likely to be well beyond the scope of the CGA.
OTOH, the class hierarchy may be more of an issue for Qt (or MFC, if
that is to be supported), but I'm not sufficiently familiar with Qt to
be able to comment on that.
Anyhow, to get back to the original issue, the key problem isn't the
relative positions of push-buttons etc, but the fact that top-level
windows are effectively subclasses of "widget" on some toolkits but
not on others. If you're assuming that "widget" is the topmost level
of the object hierarchy, this is likely to be a problem.
--
Glynn Clements

On Thu, May 01, 2003 at 03:11:28AM +0100, Glynn Clements wrote:
Axel Simon wrote:
IMHO the classes should just reflect the object hierarchy. If we have e.g.
class Button b where... class ToggleButton b where... instance Button ToggleButton where...
Different toolkits have different hierarchies; e.g.:
[..] Yes, I expected that. But perhaps we can create a (rather flat) object hierarchy which maps onto all backends.
Anyhow, to get back to the original issue, the key problem isn't the relative positions of push-buttons etc, but the fact that top-level windows are effectively subclasses of "widget" on some toolkits but not on others. If you're assuming that "widget" is the topmost level of the object hierarchy, this is likely to be a problem.
I would be happy with just a hierarchy for widgets. To answer Wolfgang's question:
Could somebody who has an opinion on these issues knock up a concrete example; I'm not thinking of code, just some class declarations and signatures...
This was my proposal: http://haskell.org/pipermail/gui/2003-April/000459.html As far as I can tell this is equivalent to the class IsWidget Button approach. Axel.

Axel Simon wrote:
Yes, I expected that. But perhaps we can create a (rather flat) object hierarchy which maps onto all backends.
If you say "object hierarchy", do you mean a set of types with upcast and downcast functions? Or are you referring to type classes? As to "rather flat", I'd say yes, I think that's what we have to do.
This was my proposal: http://haskell.org/pipermail/gui/2003-April/000459.html
OK so you're talking about upcast & downcast functions (as class members). At least that's how I would try to describe your approach. The question where to put the actual functions remains open. I think upcast and downcast functions become ugly as soon as the hierarchy doesn't match the underlying backend. What "upcasted" generic types do we need anyway? As I said, we need a data Pane = .... class PaneClass p where toPane :: p -> Pane fromPane :: Pane -> Maybe p ... at least in order to be able to implement dynamic layout in Haskell (for platforms that don't have it). I'm using the term Pane to mean any widget that can be placed inside a window; that is widgets minus windows and menu items. Do we need any other upcast/downcast functionality? Does anyone want to have a list of _any kind_ of widget, mixing windows and buttons and layout containers etc? Does anyone want to have a list of any kind of buttons, that may contain both push buttons and toggle buttons, but no edit text fields and labels? If the answer is no, then let's not include any more upcast/downcast functions, at least not in CGA 1.0, because they will never be conveniently implementable on all platforms at the same time. We could then just have classes that contain some functionality, and some plain functions where a certain functionality is only available at one widget. The next question we need to ask is, which widget type supports what functionality? I'd say we should only use single-function HasFoo classes where necessary; I prefer having logical groups of functionality, like "Container"; but I don't have a strong opinion on this issue. On the other hand, one-function-per-class-style is harder to screw up --- it will probably take a few tries until we have figured out what functionality we can group together in classes. Perhaps we should do a rough design first (which would have classes like HasOnActivate along with classes like Container), and then, when everything is basically there, go over it again and reorganize some classes. Below I've attached what I'm currently thinking of... could we have another quick "yes"/"no, because" vote and/or some counter-proposals? Cheers, Wolfgang ================== cut here ================== import Attributes -- from the CGA example (indirectly from HToolkit) data Pane = .... class PaneClass p where toPane :: p -> Pane fromPane :: Pane -> Maybe p -- plus any functions and attributes that are supported by all kinds of panes -- and not supported by any window or other object instance PaneClass Pane where toPane = id fromPane = Just -- ... class Container c where -- ... class HasOnActivate a where doOnActivate :: a -> IO () -> IO( IO() ) data Button = ... instance PaneClass Button where -- ... mkButton :: Container c => c -> [Prop Button] -> IO Button instance HasOnActivate Button where doOnActivate = ... data TextEntry = ... instance PaneClass TextEntry where -- ... mkTextEntry :: Container c => c -> [Prop TextEntry] -> IO TextEntry data GridBox = ... instance PaneClass GridBox where -- ... instance Container GridBox where -- ... column :: PaneClass c => Attr (GridBox,c) Int row :: PaneClass c => Attr (GridBox,c) Int columnspan :: PaneClass c => Attr (GridBox,c) Int rowspan :: PaneClass c => Attr (GridBox,c) Int -- plus some more data Window = ... -- no PaneClass instance for Window instance Container Window

On Fri, May 02, 2003 at 12:08:41AM +0200, Wolfgang Thaller wrote:
This was my proposal: http://haskell.org/pipermail/gui/2003-April/000459.html
OK so you're talking about upcast & downcast functions (as class members). At least that's how I would try to describe your approach. The question where to put the actual functions remains open.
I vote for not having them in classes at all. Overlap of function names of different types are minimal and lumping (some) functions together into categories is ad-hoc, hard to get right and impossible to change afterwards without breaking code.
I think upcast and downcast functions become ugly as soon as the hierarchy doesn't match the underlying backend.
That might be true. I don't see a good way to interface an agreed object hierarchy to the existing Gtk type classes in gtk2hs. Yet.
What "upcasted" generic types do we need anyway? As I said, we need a data Pane = .... class PaneClass p where toPane :: p -> Pane fromPane :: Pane -> Maybe p
... at least in order to be able to implement dynamic layout in Haskell (for platforms that don't have it). I'm using the term Pane to mean any widget that can be placed inside a window; that is widgets minus windows and menu items.
I don't mind the name, Pane or Widget is fine with me.
Do we need any other upcast/downcast functionality?
Dunno. Do you have anything in mind?
Does anyone want to have a list of _any kind_ of widget, mixing windows and buttons and layout containers etc?
I am currently assuming that I am able to compose layout containers into other layout containers. As such, I layout containers and widgets have to be in one class. But I think it's a good idea to separate top-level windows by giving them a different root class.
Does anyone want to have a list of any kind of buttons, that may contain both push buttons and toggle buttons, but no edit text fields and labels?
I thought about it and couldn't find a compelling "yes".
If the answer is no, then let's not include any more upcast/downcast functions, at least not in CGA 1.0, because they will never be conveniently implementable on all platforms at the same time.
We could then just have classes that contain some functionality, and some plain functions where a certain functionality is only available at one widget.
I don't understand what you mean with more upcast/downcast functions. Could you explain or give an example?
The next question we need to ask is, which widget type supports what functionality?
I guess we do that widget-by-widget. And don't make these functions part of the widget type class. I'd like to have setButtonLabel :: ButtonClass b => b -> String -> IO () setButtonLabel b name = ... (toButtonClass b) ...
Below I've attached what I'm currently thinking of... could we have another quick "yes"/"no, because" vote and/or some counter-proposals?
Cheers,
Wolfgang
================== cut here ==================
import Attributes -- from the CGA example (indirectly from HToolkit)
data Pane = .... class PaneClass p where toPane :: p -> Pane fromPane :: Pane -> Maybe p Yes.
-- plus any functions and attributes that are supported by all kinds of panes -- and not supported by any window or other object
No. Every function that is supported by all panes can be implemented like the setButtonLabel example. I don't see any advantage of making these functions memebers of the class. The disadvantage is large dictionaries which probably all contain the same virtual function for a specific entry.
instance PaneClass Pane where toPane = id fromPane = Just -- ...
Yes.
class Container c where -- ...
class HasOnActivate a where doOnActivate :: a -> IO () -> IO( IO() )
No. As above.
data Button = ... instance PaneClass Button where -- ...
I admit that I find it weird that the Button doesn't have a class. But if it is a leave then it's probably not necessary.
mkButton :: Container c => c -> [Prop Button] -> IO Button newButton. Please!?
[..] No unexpected change of opinion. Axel.

Axel Simon wrote:
I don't understand what you mean with more upcast/downcast functions. Could you explain or give an example?
Things like toButtonClass below...
I guess we do that widget-by-widget. And don't make these functions part of the widget type class.
I'd like to have
setButtonLabel :: ButtonClass b => b -> String -> IO () setButtonLabel b name = ... (toButtonClass b) ...
I'm a afraid of that toButtonClass function... toButtonClass is what I call an upcast function, and upcast functions can only be implemented conveniently if they mirror what the backend does. See also the discussion of HasOnActivate below.
import Attributes -- from the CGA example (indirectly from HToolkit)
data Pane = .... class PaneClass p where toPane :: p -> Pane fromPane :: Pane -> Maybe p Yes.
-- plus any functions and attributes that are supported by all kinds of panes -- and not supported by any window or other object
No. Every function that is supported by all panes can be implemented like the setButtonLabel example. I don't see any advantage of making these functions memebers of the class.
Ah yes. You're probably right. But I'd like to add the remark that this _would_ have been a problem if we wanted to do it for the "Widget" class which includes Windows and Panes, because not all backends have a common base type for windows and panes. To avoid similar problems, I'd like to avoid having more "upcasted" types like Pane, and use type classes for everything else.
class Container c where -- ...
class HasOnActivate a where doOnActivate :: a -> IO () -> IO( IO() )
No. As above.
Does that No refer to both Container and HasOnActivate? For Container, I think we do need them, because there are at least two independent types which would implement the Container class, Window and GridBox. A Window should contain only one child widget, because a Window doesn't have any layout algorithm (IIRC that's the same thing in GTK?). Windows and GridBoxes are two completely independent types which cannot share any implementation on MacOS, so implementing the functionality via some toContainer function wouldn't work well. That's why we should have a class for Container which contains all the functionality necessary for achieving dynamic layout. So I don't As far as HasOnActivate is concerned... We could have separate doOnButtonActivate and doOnToggleButtonToggle functions without any classes, but I don't think that has any advantages. Are you perhaps thinking of class ButtonClass b where toButton :: b -> Button instance ButtonClass Button instance ButtonClass ToggleButton doOnActivate :: ButtonClass b => b -> IO () -> IO ( IO () ) doOnActivate b io = ... (toButton b) ... I'm sceptical of that - is ToggleButton derived from Button in all backends? Button and ToggleButton are actually siblings in Carbon (but there are still some common functions to work with them, so it's not a real problem; it might be in other cases, or for other backends). That's why I'm in favour of relying on type classes that contain the actual functions rather than "toButton"-like functions for widgets; ... but as long as we're inside the "Pane" subtree, i.e. no Windows, I think that most problems can be worked around relatively easily. So my opinion isn't a very strong one in this case.
I admit that I find it weird that the Button doesn't have a class. But if it is a leave then it's probably not necessary.
My idea was to make all types inside the "Pane" hierarchy "leaves"; I wanted to represent all other relationships using different type classes. That way, we can't accidentally make assumptions about relationships between widget types that just don't hold true on all backends. On the other hand, a hierarchy with toFoo (upcast) functions could be perfectly viable _as long as_ we avoid the mistake of defining a hierarchy that "doesn't fit" with all backends.
mkButton :: Container c => c -> [Prop Button] -> IO Button newButton. Please!?
Now that you say it, yes!! (I was copying from the CGA example without thinking about the naming...) Cheers, Wolfgang

On Fri, May 02, 2003 at 09:15:48PM +0200, Wolfgang Thaller wrote:
I'd like to have
setButtonLabel :: ButtonClass b => b -> String -> IO () setButtonLabel b name = ... (toButtonClass b) ...
I'm a afraid of that toButtonClass function... toButtonClass is what I call an upcast function, and upcast functions can only be implemented conveniently if they mirror what the backend does.
Ok, possible.
See also the discussion of HasOnActivate below. [..] Ah yes. You're probably right. But I'd like to add the remark that this _would_ have been a problem if we wanted to do it for the "Widget" class which includes Windows and Panes, because not all backends have a common base type for windows and panes. To avoid similar problems, I'd like to avoid having more "upcasted" types like Pane, and use type classes for everything else.
class Container c where -- ...
class HasOnActivate a where doOnActivate :: a -> IO () -> IO( IO() )
No. As above.
Does that No refer to both Container and HasOnActivate?
For Container, I think we do need them, because there are at least two independent types which would implement the Container class, Window and GridBox. Oh, sorry. No, the container class is fine with me.
As far as HasOnActivate is concerned... We could have separate doOnButtonActivate and doOnToggleButtonToggle functions without any classes, but I don't think that has any advantages. My observation is that common functionality is rare between widgets. Using classes for every signal and function is overkill (and causes name space pollution and perhaps even slow turn-around times). And grouping them into related classes seems hard to get right. But we can try. (I am getting more aquainted to that thought.)
Are you perhaps thinking of
class ButtonClass b where toButton :: b -> Button instance ButtonClass Button instance ButtonClass ToggleButton doOnActivate :: ButtonClass b => b -> IO () -> IO ( IO () ) doOnActivate b io = ... (toButton b) ...
I'm sceptical of that - is ToggleButton derived from Button in all backends? Button and ToggleButton are actually siblings in Carbon (but there are still some common functions to work with them, so it's not a real problem; it might be in other cases, or for other backends). That's why I'm in favour of relying on type classes that contain the actual functions rather than "toButton"-like functions for widgets; ... but as long as we're inside the "Pane" subtree, i.e. no Windows, I think that most problems can be worked around relatively easily. So my opinion isn't a very strong one in this case.
I admit that I find it weird that the Button doesn't have a class. But if it is a leave then it's probably not necessary.
My idea was to make all types inside the "Pane" hierarchy "leaves"; I wanted to represent all other relationships using different type classes. That way, we can't accidentally make assumptions about relationships between widget types that just don't hold true on all backends. On the other hand, a hierarchy with toFoo (upcast) functions could be perfectly viable _as long as_ we avoid the mistake of defining a hierarchy that "doesn't fit" with all backends.
Ok, I think this is a good approach. Just a flat hierarchy to facilitate having a list of widgets. Axel.
participants (4)
-
Alastair Reid
-
Axel Simon
-
Glynn Clements
-
Wolfgang Thaller