changeset 173:a1ba9157510e

Enabled ServiceContentList to call its callbacks when its value changes. Tried to fix some other bugs, but this is not a very clean commit, due to wanting to make some big changes to enable better use of invariants next.
author Diggory Hardy <diggory.hardy@gmail.com>
date Sat, 08 Aug 2009 15:53:10 +0200
parents 0dd49f333189
children 3d58adc17d20
files codeDoc/ideas.txt codeDoc/jobs.txt examples/guiDemo.d mde/content/ServiceContent.d mde/gui/WMScreen.d mde/gui/WidgetManager.d mde/gui/widget/AParentWidget.d mde/gui/widget/Floating.d mde/gui/widget/ParentContent.d mde/setup/Init.d
diffstat 10 files changed, 173 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/ideas.txt	Wed Jul 29 20:28:22 2009 +0200
+++ b/codeDoc/ideas.txt	Sat Aug 08 15:53:10 2009 +0200
@@ -12,17 +12,26 @@
   > generalise concept of 2D widgets:
     > some other way of specifying a widget's dimensions
     > divide layout widget into a sub-widget handler and a placement component, such that placement can use a rectangle, hexagon or whatever component
+  > Menu widgets:
+    > Need BoolContentWidget with name in menu
+      > Special widget?
 ->  scripted widgets
 ->  decent rendering/theme system
 ->  events:
-    ->	Click events: widgets only receive clickEvent for left-button press, other button events handled alternatively from WidgetManager?
+    ?>	Click events: WidgetManager separates out actions:
+       >  left-click down:
+	 >  starts drag monitor, visually depresses buttons
+	   >  calls downClick, which can return codes to enable dragging, etc.
+	 >  if mouse moved more than dragStartDistance from click-position and dragging-from-widget available, start drag mode:
+	   >  visual feedback for dragging
+	   >  releasing does drag action; releasing on start widget cancels action
+	 >  otherwise, releasing does normal button effect (pass a eventClick)
+	*>  consequence: button click/release & dragging more managed by WM; possibly less flexible for widgets
+       >  dropContent(mouseClipboard): middle click paste action
+       >  ...: open context menu
+       >  eventScroll: scrolling event
+      ->  Issue: release-click for menu buttons needs to be passed separately
     ->	Click/drag handling:
-	->  on click:
-	    ->	clicked widget recieves depress event
-	    ->	create drag monitor, which records parent and optionally runs parent method on move (which can find the widget/drop-zone underneath)
-	->  on release:
-	    ->	method from either dragged or drop-zone widget called with reference to the other:
-		->  perhaps function in dragged widget called, which is passed reference of widget underneath, and can call getDropZoneAncestor on this
 	->  drag/drop-like possibilities:
 	    ->	magnifier which is dragged from icon, creates a magnified window, and disappears on release
 	    ->	colour-picker which is dragged from a widget and dropped to choose the colour under it
--- a/codeDoc/jobs.txt	Wed Jul 29 20:28:22 2009 +0200
+++ b/codeDoc/jobs.txt	Sat Aug 08 15:53:10 2009 +0200
@@ -3,11 +3,32 @@
 
 
 In progress:
+Made context menus display again.
+TODO: investigate why menu items don't show up.
 Having setContent recusively call on subWidgets is not quite right: where the addContent function was used to pass a different content, the content should not be reset by a content() call propegated from a parent.
+Why is ServiceContent.opCall() called in addition to call from CollapsibleWidget?
+Why is ServiceContentList.opCall() not called? Probably because it's callbacks are never called.
+
+Protection and invariants:
+    /** TODO: in progess - design of invariants
+     *
+     * Ideally this wants to be called after minXChange has finished, but not
+     * before/during. minXChange is called in 2 cases:
+     * 	recursively from a child
+     * 	from a change of content (setContent, keyEvent, content callbacks) - these can all be public
+     * 
+     * So minXChange should not be public? But then the invariant is never called on
+     * parent widgets affected by the size change, including many parent widgets
+     * where invariants would be really useful.
+     */
+So make sure draw() and a few others are public, and invariants get called at these points. Thus a lot more must have at least package protection.
+
+Implement a RootWidget moving functionality out of AWidgetManager, etc., now, or later?
 
 
 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent):
 Also search for FIXME/NOTE/BUG/WARNING comment marks.
+4   Move createWidget code out of WidgetManager.
 4   GUI: up-clicks get passed as events and activate objects
 3   Closing menus when release-click is not on menu or parent (ordinary & context).
 3   Dragging and dropping of editable data: should content immediately appear as being dragged?
@@ -17,6 +38,7 @@
 3   Content: setContent specialisations, opAssign should reject more values (particularly for BoolContent).
 3   Widget saving: how to deal with modifier functions, esp. when they discard parameters? Remove feature except for dimdata and handle gui editing separately?
 3   Windows compatibility - no registry support (useful to find path).
+2   Layout spacing is present between sub-widgets of zero size
 2   Optimise: memory/reuse of popupContext and dragContentDisplay (in WidgetManager).
 2   Check for unnecessary redraws (associated with mouse movement, not clicking).
 2   First glyph drawn incorrectly in release mode - ??
--- a/examples/guiDemo.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/examples/guiDemo.d	Sat Aug 08 15:53:10 2009 +0200
@@ -28,6 +28,7 @@
 import tango.core.Thread : Thread;	// Thread.sleep()
 import tango.time.Clock;                // Clock.now()
 import tango.util.log.Log : Log, Logger;
+import tango.core.stacktrace.TraceExceptions;
 
 int main(char[][] args)
 {
--- a/mde/content/ServiceContent.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/mde/content/ServiceContent.d	Sat Aug 08 15:53:10 2009 +0200
@@ -25,6 +25,14 @@
 
 import mde.content.AStringContent;
 
+debug {
+    import tango.util.log.Log : Log, Logger;
+    private Logger logger;
+    static this () {
+        logger = Log.getLogger ("mde.gui.content.ServiceContent");
+    }
+}
+
 /** Interface for ServiceContent and ServiceContentList. */
 interface IServiceContent : IContent {
     void setContent (Content cont);
@@ -46,11 +54,15 @@
      *
      * Stores the reference, because it won't be passed later. */
     override void setContent (Content cont) {
+	T oCont = activeCont;
 	activeCont = cast(T)cont;
+	if ((oCont !is null) != (activeCont !is null))
+	    endEvent;
     }
     
     override bool opCall () {
-	return activeCont is null;
+	debug logger.trace ("ServiceContent.opCall: {}", symbol);
+	return activeCont !is null;
     }
     // Doesn't support directly setting the value
     override void opAssign (bool val) {}
@@ -72,18 +84,19 @@
     
     void setContent (Content cont) {
 	foreach (child; list_) {
-	    debug assert (cast(IServiceContent)child !is null);
 	    (cast(IServiceContent)child).setContent (cont);
 	}
     }
     
+    override void append (Content x) {
+	assert (cast(IBoolContent) x, "Only IBoolContent children are allowed!");
+	list_ ~= x;
+	x.addCallback (&childChangeCB);
+    }
+    
     override bool opCall () {
-	foreach (child; list_) {
-	    debug assert (cast(IBoolContent)child !is null);
-	    if (!(cast(IBoolContent)child)())
-		return false;
-	}
-	return true;
+	debug logger.trace ("ServiceContentList.opCall");
+	return v;
     }
     // Doesn't support directly setting the value
     override void opAssign (bool val) {}
@@ -101,14 +114,14 @@
 	auto ret = new ServiceContentList (lName);
 	
 	// Many use callbacks on a generic class type. For this, we should be sure of the type.
-	(new AStringService(lName~".copy")).addCallback ((IContent c) {
+	(new AStringService(lName~".copy")).addCallback (delegate void(IContent c) {
 	    debug assert (cast(AStringService)c);
 	    with (cast(AStringService)c) {
 		if (activeCont !is null)
 		    clipboard = activeCont.toString(0);
 	    }
 	});
-	(new AStringService(lName~".paste")).addCallback ((IContent c) {
+	(new AStringService(lName~".paste")).addCallback (delegate void(IContent c) {
 	    debug assert (cast(AStringService)c);
 	    with (cast(AStringService)c) {
 		if (activeCont !is null)
@@ -117,14 +130,14 @@
 	});
 	
 	new ServiceContentList(lName~".calculator");
-	(new IntService(lName~".calculator.increment")).addCallback ((IContent c) {
+	(new IntService(lName~".calculator.increment")).addCallback (delegate void(IContent c) {
 	    debug assert (cast(IntService)c);
 	    with (cast(IntService)c) {
 		if (activeCont !is null)
 		    activeCont = activeCont()+1;
 	    }
 	});
-	(new IntService(lName~".calculator.decrement")).addCallback ((IContent c) {
+	(new IntService(lName~".calculator.decrement")).addCallback (delegate void(IContent c) {
 	    debug assert (cast(IntService)c);
 	    with (cast(IntService)c) {
 		if (activeCont !is null)
@@ -135,6 +148,27 @@
     }
     
 private:
+    void childChangeCB (IContent icont) {
+	if (v == false) {	// then value changes iff icont()
+	    debug assert (cast(IBoolContent) icont);
+	    if ((cast(IBoolContent) icont)()) {
+		v = true;
+		endEvent;
+	    }
+	} else {	// we must check all children
+	    v = false;
+	    foreach (child; list_) {
+		if ((cast(IBoolContent)child)()) {
+		    v = true;
+		    break;
+		}
+	    }
+	    if (!v)
+		endEvent;
+	}
+    }
+    
+    bool v = false;	// cache value so we can see when it changes
     static char[] clipboard;
     //TODO: lock on clipboard: static Mutex mutex;
 }
--- a/mde/gui/WMScreen.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/mde/gui/WMScreen.d	Sat Aug 08 15:53:10 2009 +0200
@@ -28,6 +28,7 @@
 import mde.setup.Screen;
 import mde.input.Input;
 
+import tango.io.Console;	// to print exception stack-trace
 import tango.util.log.Log : Log, Logger;
 
 private Logger logger;
@@ -89,7 +90,8 @@
 	    wmMouseClick (cast(wdabs) usx, cast(wdabs) usy, b, state);
         } catch (Exception e) {
             logger.error ("clickEvent: exception processing event: {}", e.msg);
-        }
+	    e.writeOut(delegate void(char[]s){ Cerr(s); });
+	}
     }
     
     /** For mouse motion events. */
--- a/mde/gui/WidgetManager.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/mde/gui/WidgetManager.d	Sat Aug 08 15:53:10 2009 +0200
@@ -46,6 +46,7 @@
 
 public import tango.core.sync.Mutex;
 import tango.util.log.Log : Log, Logger;
+import tango.io.Console;	// to print exception stack-trace
 import tango.util.container.SortedMap;
 
 private Logger logger;
@@ -93,9 +94,12 @@
     final void recursionCheck (widgetID, IContent) {}
     
     override void minWChange (IChildWidget widget, wdim nmw) {
-	if (widget !is childRoot)	// Probably because widget is a popup widget
+	if (widget !is childRoot) {	// Probably because widget is a popup widget
 	    // This may get called from a CTOR, hence we can't check widget is one of childContext, etc.
+	    if (widget.width < nmw)
+		widget.setWidth (nmw, -1);
 	    return;
+	}
         mw = nmw;
         if (w < nmw) {
             childRoot.setWidth (nmw, -1);
@@ -105,8 +109,11 @@
         requestRedraw;
     }
     override void minHChange (IChildWidget widget, wdim nmh) {
-	if (widget !is childRoot)
+	if (widget !is childRoot) {
+	    if (widget.height < nmh)
+		widget.setHeight (nmh, -1);
 	    return;
+	}
         mh = nmh;
         if (h < nmh) {
             childRoot.setHeight (nmh, -1);
@@ -118,7 +125,7 @@
     //END IParentWidget methods
     
     //BEGIN IWidget methods
-    override bool saveChanges () {
+    public override bool saveChanges () {
 	bool ret = childRoot.saveChanges;
 	ret |= childContext.saveChanges;
 	if (childDragged !is null)
@@ -147,12 +154,17 @@
         childIPPW = ippw;
     }
     override bool removeChildIPPW (IPopupParentWidget ippw) {
+	if (ippw is childContext && contextActive) {
+	    childContext.removedIPPW;
+	    contextActive = false;
+	    return true;
+	}
         if (childIPPW !is ippw) return false;
         childIPPW.removedIPPW;
         childIPPW = null;
         mAIPPW = MenuPosition.INACTIVE;
         requestRedraw;
-        return false;
+        return true;
     }
     
     override void menuActive (MenuPosition mA) {
@@ -180,6 +192,7 @@
 	    if (ret) return ret;
 	    if (closePopup) {
 		childContext.removedIPPW;
+		contextActive = false;
 		requestRedraw;
 	    }
         }
@@ -194,8 +207,10 @@
     }
     
     override void drawPopup () {
+	if (childIPPW)
+	    childIPPW.drawPopup;
 	if (contextActive)
-	    childContext.draw();
+	    childContext.drawPopup();
 	if (childDragged)
 	    childDragged.draw();
     }
@@ -231,7 +246,9 @@
             // Not returned a new widget:
             logger.error ("Bad widget type: {}; creating a debug widget instead",type);
         } catch (Exception e) {
-            logger.error ("Error creating widget: {}; creating a debug widget instead.", e.msg);
+            logger.error ("Error creating widget; creating a debug widget instead. Exception printed to stderr.");
+	    //TODO: find a standard way to output exceptions, and implement everywhere:
+	    e.writeOut(delegate void(char[]s){ Cerr(s); });
         }
     
         return new DebugWidget (this, parent, id, data, content);
@@ -294,7 +311,7 @@
         if (y < 0) y = 0;
         popup.setPosition (x, y);
 	debug if (Debug.logPopupPositioning())
-	    logger.trace ("Placed popup {}; output position: {}", popup, position);
+	    logger.trace ("Placed popup {} of size ({},{}) at ({},{}); output position: {}", popup, w,h, x,y, position);
 	return position;
     }
 
@@ -352,8 +369,6 @@
     final void wmDrawWidgets() {
 	if (childRoot)
 	    childRoot.draw;
-	if (childIPPW)
-	    childIPPW.drawPopup;
 	drawPopup;
     }
     
--- a/mde/gui/widget/AParentWidget.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/mde/gui/widget/AParentWidget.d	Sat Aug 08 15:53:10 2009 +0200
@@ -87,8 +87,15 @@
     }
     
     // Most parent widgets need to implement these, although not all
-    void minWChange (IChildWidget widget, wdim mw) {}
-    void minHChange (IChildWidget widget, wdim mh) {}
+    // They must at a minimum make sure widget's size is at least nmw by nmh.
+    override void minWChange (IChildWidget widget, wdim nmw) {
+	if (widget.width < nmw)
+	    widget.setWidth (nmw, -1);
+    }
+    override void minHChange (IChildWidget widget, wdim nmh) {
+	if (widget.height < nmh)
+	    widget.setHeight (nmh, -1);
+    }
     
     debug override void logWidgetSize () {
         super.logWidgetSize;
@@ -237,7 +244,8 @@
     /// Open a context menu
     void openMenu (IChildWidget underMouse, Content contextContent) {
 	if (mAIPPW != MenuPosition.INACTIVE) return;	// already in use
-	subWidgets[0].setContent = contextContent;
+	//NOTE: Disabled since it doesn't work correctly atm:
+	//subWidgets[0].setContent = contextContent;
 	parentIPPW.addChildIPPW (this);
 	// For context menus, don't set parentIPPW.menuActive like for clicked menus:
 	menuActive = mgr.positionPopup (underMouse, popup);
--- a/mde/gui/widget/Floating.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/mde/gui/widget/Floating.d	Sat Aug 08 15:53:10 2009 +0200
@@ -195,10 +195,10 @@
     }
     
     override int clickEvent (wdabs cx, wdabs cy, ubyte b, bool state) {
-	size_t event = getFloatingWidget (cx,cy, true);
-	if (event > subWidgets.length) return 0;
         if (b == 1 && state == true) {
-            active = event;
+	    size_t event = getFloatingWidget (cx,cy, true);
+	    if (event > subWidgets.length) return 0;
+	    active = event;
             with (sWData[active]) {
                 resizeType = border.getResize (cx - this.x - x, cy - this.y - y, w,h);
                 alias border.RESIZE RESIZE;
--- a/mde/gui/widget/ParentContent.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/mde/gui/widget/ParentContent.d	Sat Aug 08 15:53:10 2009 +0200
@@ -196,14 +196,22 @@
     }
     
     override void minWChange (IChildWidget widget, wdim nmw) {
-        if (widget !is currentW) return;
-        mw = nmw;
-        parent.minWChange (this, nmw);
+        if (widget is currentW) {
+	    mw = nmw;
+	    parent.minWChange (this, nmw);
+	} else {	// changes won't be seen, but we must follow function spec
+	    if (widget.width < nmw)
+		widget.setWidth (nmw, -1);
+	}
     }
     override void minHChange (IChildWidget widget, wdim nmh) {
-        if (widget !is currentW) return;
-        mh = nmh;
-        parent.minHChange (this, nmh);
+        if (widget is currentW) {
+	    mh = nmh;
+	    parent.minHChange (this, nmh);
+	} else {
+	    if (widget.height < nmh)
+		widget.setHeight (nmh, -1);
+	}
     }
     
     override bool isWSizable () {
@@ -273,14 +281,14 @@
         
         subWidgets = [mgr.makeWidget (this, data.strings[0], c)];
         
-        content_.addCallback (&collapse);
+        content_.addCallback (&cbDisplay);
     }
     
     override bool setup (uint n, uint flags) {
         bool r = super.setup (n, flags);
         if (r) {
-            collapsed = content_();
-            if (!collapsed) {
+            display = content_();
+            if (display) {
             	mw = subWidgets[0].minWidth;
             	mh = subWidgets[0].minHeight;
             	w = subWidgets[0].width;
@@ -306,7 +314,7 @@
 	    return;
 	}
 	content_ = cont;
-	collapse (content_);
+	cbDisplay (content_);
     }
     
     override void minWChange (IChildWidget widget, wdim nmw) {
@@ -330,50 +338,51 @@
     
     override void setWidth (wdim nw, int dir) {
 	w = (nw >= mw ? nw : mw);
-        if (!collapsed) subWidgets[0].setWidth (w, dir);
+        subWidgets[0].setWidth (w, dir);
     }
     override void setHeight (wdim nh, int dir) {
         h = (nh >= mh ? nh : mh);
-        if (!collapsed) subWidgets[0].setHeight (h, dir);
+        subWidgets[0].setHeight (h, dir);
     }
     
     override void setPosition (wdim nx, wdim ny) {
         x = nx;
         y = ny;
-        if (!collapsed) subWidgets[0].setPosition (nx,ny);
+	if (display) subWidgets[0].setPosition (nx,ny);
     }
     
     override IChildWidget getWidget (wdim cx, wdim cy) {
-        if (!collapsed)
+        if (display)
             return subWidgets[0].getWidget (cx, cy);
         else return this;
     }
     
     override void draw () {
-        if (!collapsed) subWidgets[0].draw;
+        if (display) subWidgets[0].draw;
     }
     
 protected:
     // callback on content_
-    void collapse (IContent) {
-	if (collapsed == content_()) return;
-        collapsed = content_();
-        if (collapsed) {
-            mw = mh = 0;
-        } else {
+    void cbDisplay (IContent) {
+	if (display == content_()) return;
+        display = content_();
+	logger.trace ("{}.cbDisplay ({})", id, display);
+        if (display) {
             mw = subWidgets[0].minWidth;
             mh = subWidgets[0].minHeight;
+        } else {
+            mw = mh = 0;
         }
         parent.minWChange (this, mw);
         parent.minHChange (this, mh);
-        if (collapsed) return;
+        if (!display) return;
     	// set incase parent didn't:
         subWidgets[0].setWidth (w, -1);
         subWidgets[0].setHeight (h, -1);
         subWidgets[0].setPosition (x,y);
     }
     
-    bool collapsed = false;
+    bool display = false;
     IBoolContent content_;
 }
 
@@ -435,12 +444,12 @@
     override void minWChange (IChildWidget widget, wdim nmw) {
         debug assert (widget is subWidgets[0]);
         mw = nmw + border.x1 + border.x2;
-        parent.minWChange (this, nmw);
+        parent.minWChange (this, mw);
     }
     override void minHChange (IChildWidget widget, wdim nmh) {
         debug assert (widget is subWidgets[0]);
         mh = nmh + border.y1 + border.y2;
-        parent.minHChange (this, nmh);
+        parent.minHChange (this, mh);
     }
     
     override void draw () {
@@ -458,6 +467,15 @@
     }
     
 protected:
+    invariant {
+	// this() calls makeWidget() which calls recursionCheck(); invariant is called at this point before this() finishes
+	if (subWidgets.length) {
+	    assert (subWidgets.length == 1);
+	    assert (mw == subWidgets[0].minWidth + border.x1 + border.x2);
+	    assert (mh == subWidgets[0].minHeight + border.y1 + border.y2);
+	}
+    }
+    
     alias IRenderer.Border.BTYPE BTYPE;
     alias IRenderer.Border.RESIZE RESIZE;
     BTYPE borderType;       // what type of border to put around the widget
--- a/mde/setup/Init.d	Wed Jul 29 20:28:22 2009 +0200
+++ b/mde/setup/Init.d	Sat Aug 08 15:53:10 2009 +0200
@@ -346,13 +346,15 @@
                     } catch (InitStageException e) {
                         debug logger.error ("({}) InitStage {}: failed: "~e.msg, threadNum, stage.name);
                         else logger.error ("InitStage {}: failed: "~e.msg, stage.name);
-                        stage.state = e.state;
+			e.writeOut(delegate void(char[]s){ Cerr(s); });
+			stage.state = e.state;
                         doneInit = STATE.ABORT;
                         break threadLoop;
                     } catch (Exception e) {
                         debug logger.error ("({}) InitStage {}: failed: "~e.msg, threadNum, stage.name);
                         else logger.error ("InitStage {}: failed: "~e.msg, stage.name);
-                        doneInit = STATE.ABORT;
+			e.writeOut(delegate void(char[]s){ Cerr(s); });
+			doneInit = STATE.ABORT;
                         break threadLoop;
                     }
                 }