# HG changeset patch # User Diggory Hardy # Date 1249739590 -7200 # Node ID a1ba9157510e196ead2bb2d8ae675fde65192478 # Parent 0dd49f3331898e0a7dae075afd6e5605eeebe268 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. diff -r 0dd49f333189 -r a1ba9157510e codeDoc/ideas.txt --- 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 diff -r 0dd49f333189 -r a1ba9157510e codeDoc/jobs.txt --- 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 - ?? diff -r 0dd49f333189 -r a1ba9157510e examples/guiDemo.d --- 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) { diff -r 0dd49f333189 -r a1ba9157510e mde/content/ServiceContent.d --- 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; } diff -r 0dd49f333189 -r a1ba9157510e mde/gui/WMScreen.d --- 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. */ diff -r 0dd49f333189 -r a1ba9157510e mde/gui/WidgetManager.d --- 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; } diff -r 0dd49f333189 -r a1ba9157510e mde/gui/widget/AParentWidget.d --- 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); diff -r 0dd49f333189 -r a1ba9157510e mde/gui/widget/Floating.d --- 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; diff -r 0dd49f333189 -r a1ba9157510e mde/gui/widget/ParentContent.d --- 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 diff -r 0dd49f333189 -r a1ba9157510e mde/setup/Init.d --- 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; } }