changeset 123:d3b2cefd46c9

minSizeChange() allows run-time changes to widgets' minimal size (except for shrinking in a GridLayoutWidget). FloatingAreaWidget: correct resize limiters AStringContent: allows space and ignores modifier keys
author Diggory Hardy <diggory.hardy@gmail.com>
date Sun, 04 Jan 2009 17:35:15 +0000
parents f96e8d18c00a
children a2ef6b549101
files codeDoc/ideas.txt codeDoc/jobs.txt data/conf/guiDemo.mtt mde/content/AStringContent.d mde/content/Items.d mde/content/miscContent.d mde/gui/WidgetManager.d mde/gui/renderer/IRenderer.d mde/gui/widget/Floating.d mde/gui/widget/Ifaces.d mde/gui/widget/TextWidget.d mde/gui/widget/Widget.d mde/gui/widget/layout.d mde/imde.d
diffstat 14 files changed, 179 insertions(+), 41 deletions(-) [+]
line wrap: on
line diff
--- a/codeDoc/ideas.txt	Fri Jan 02 18:10:14 2009 +0000
+++ b/codeDoc/ideas.txt	Sun Jan 04 17:35:15 2009 +0000
@@ -29,3 +29,6 @@
             ->	with complicated substructures, may not be very intuitive
 	->  limit to popup menus?
     ->  these keyboard events only passed if activated by code outside the WidgetManager and no text input callback is active?
+
+Content:
+->  Per-content undo support?
--- a/codeDoc/jobs.txt	Fri Jan 02 18:10:14 2009 +0000
+++ b/codeDoc/jobs.txt	Sun Jan 04 17:35:15 2009 +0000
@@ -3,15 +3,13 @@
 
 
 In progress:
+Dynamic minimal size change: can only increase minimal size in layouts.
 
 
 
 To do (importance 0-5: 0 pointless, 1 no obvious impact now, 2 todo sometime, 3 useful, 4 important, 5 urgent):
 Also see todo.txt and FIXME/NOTE comment marks.
-5   Protection on recursion of widgets; can cause sigsegv (enum using ContentListWidget & optVal refering to optBox). Only allow non-content widgets to be instanced once?
-4   Make popups use ordinary widgets with int to determine type like other widgets.
 4   Close popup menu on button activation/click.
-4   When child widgets are resized: must tell parent (bug);
 3   Make EnumContent a derivative of BoolContent to use to solve callback problems.
 3   Synchronization of IContent with gui (e.g. multiple edit widgets): worth adding (temporary) callbacks?
 3   Widget saving: how to deal with modifier functions, esp. when they discard parameters? Remove feature except for dimdata and handle gui editing separately?
--- a/data/conf/guiDemo.mtt	Fri Jan 02 18:10:14 2009 +0000
+++ b/data/conf/guiDemo.mtt	Sun Jan 04 17:35:15 2009 +0000
@@ -2,8 +2,10 @@
 <char[]|Renderer="Simple">
 <char[]|Design="Working">
 {Working}
-<WidgetData|root={0:[0x4100,0,3,1],1:["bar","optaC","bar"]}>
-<WidgetData|bar={0:[0x4100,0,1,3],1:["menu","blank","menu"]}>
+<WidgetData|root={0:[0x4100,0,3,1],1:["bar","fC","bar"]}>
+<WidgetData|fC={0:[0x2031],1:["Options.MiscOptions.logOutput","float"]}>
+<WidgetData|float={0:[0x4200,14],1:["optBox"]}>
+<WidgetData|bar={0:[0x4100,14,1,3],1:["menu","blank","menu"]}>
 <WidgetData|menu={0:[0x2031],1:["imde.menu","menu0"]}>
 <WidgetData|menu0={0:[0x4011,0],1:["menu1"]}>
 <WidgetData|menu1={0:[0xE032,0],1:["menu2"]}>
@@ -14,7 +16,8 @@
 <WidgetData|opts={0:[0x4100,4,2,1],1:["optName","optSecs"]}>
 <WidgetData|optSecs={0:[0xC110,4],1:["optSec"]}>
 <WidgetData|optSec={0:[0x4100,0,2,1],1:["optName","optVars"]}>
-<WidgetData|optVars={0:[0xC110,0],1:["optDBox"]}>
+!{use optBox for no description, optDBox for descriptions under entries}
+<WidgetData|optVars={0:[0xC110,0],1:["optBox"]}>
 <WidgetData|optDBox={0:[0x4100,1,2,1],1:["optBox","optDesc"]}>
 <WidgetData|optBox={0:[0x4100,1,1,3],1:["optName","optSep","optVal"]}>
 <WidgetData|optName={0:[0x4020, 1, 0xffffff]}>
--- a/mde/content/AStringContent.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/content/AStringContent.d	Sun Jan 04 17:35:15 2009 +0000
@@ -75,7 +75,7 @@
     char[] keyStroke (ushort sym, char[] i) {
 	debug assert (i.length, "StringContent.keyStroke: no value (??)");	// impossible?
 	char k = *i;
-	if (k > 0x20) {
+	if (k >= 0x20) {
 	    if (k == 0x7f) {		// delete
 		size_t p = pos;
 		if (p < sv.length) ++p;
@@ -91,7 +91,9 @@
 		pos = npos;
 	    }
 	} else {			// use sym; many keys output 0
-	    if (sym == SDLK_BACKSPACE) {	// backspace; k == 0x8
+            if (sym >= SDLK_RSHIFT &&  (sym <= SDLK_COMPOSE || sym == SDLK_MENU)) {
+            }	// all modifier keys and contect menu key; should be ignored
+	    else if (sym == SDLK_BACKSPACE) {	// backspace; k == 0x8
 		char[] tail = sv[pos..$];
 		if (pos) --pos;
 		while (pos && (sv[pos] & 0x80) && !(sv[pos] & 0x40))
@@ -231,7 +233,12 @@
     alias opCall opCast;
     
     override char[] endEdit () {
-	v = Int.toInt (sv);
+	try {
+            v = Int.toInt (sv);
+        } catch (Exception e) {
+            logger.warn (e.msg);
+            sv = Int.toString (v);
+        }
 	endEvent;
 	return sv;
     }
@@ -264,7 +271,12 @@
     alias opCall opCast;
     
     override char[] endEdit () {
-	v = Float.toFloat (sv);
+        try {
+            v = Float.toFloat (sv);
+        } catch (Exception e) {
+            logger.warn (e.msg);
+            sv = Float.toString (v);
+        }
 	endEvent;
 	return sv;
     }
--- a/mde/content/Items.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/content/Items.d	Sun Jan 04 17:35:15 2009 +0000
@@ -64,7 +64,7 @@
 	    else if (h == "quit" && item is null)
 		return imde.quit;
 	}
-	throw new ContentItemException (h);
+	return new ErrorContent ("Error: bad content specifier",h);
     }
     
     /** Creates some content on first run (required by get()).
--- a/mde/content/miscContent.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/content/miscContent.d	Sun Jan 04 17:35:15 2009 +0000
@@ -52,15 +52,16 @@
 }
 
 /** Created on errors to display and log a message. */
-class ErrorContent : IContent
+class ErrorContent : Content
 {
-    this (char[] msg) {
-	this.msg = msg;
+    this (char[] name, char[] msg) {
+	super (name);
+        this.msg = msg;
     }
     
     override char[] toString (uint i) {
 	return i == 0 ? msg
-	     : i == 1 ? "Error"
+	     : i == 1 ? name_
 	     : null;
     }
     
@@ -71,4 +72,8 @@
 /** A Content with no value but able to pass on an event.
 *
 * The point being that a button can be tied to one of these. */
-alias Content EventContent;
+class EventContent : Content {
+    this (char[] symbol) {
+        super (symbol);
+    }
+}
--- a/mde/gui/WidgetManager.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/gui/WidgetManager.d	Sun Jan 04 17:35:15 2009 +0000
@@ -242,6 +242,8 @@
     // If call reaches the widget manager there isn't any recursion.
     //NOTE: should be override
     final void recursionCheck (widgetID) {}
+    //FIXME: implement
+    override void minSizeChange (IChildWidget widget, wdim mw, wdim mh) {}
     //END IParentWidget methods
     
     //BEGIN IWidgetManager methods
@@ -404,7 +406,7 @@
     GridLayout		= TAKES_CONTENT | 0x100,
     ContentList		= TAKES_CONTENT | SAFE_RECURSION | 0x110,
     
-    FloatingArea	= 0x200,
+    FloatingArea	= TAKES_CONTENT | 0x200,
 }
 
 // Only used for binarySearch algorithm generation; must be ordered by numerical values.
@@ -413,7 +415,6 @@
         "SizableBlank",
         "Debug",
 	"TextLabel",
-	"FloatingArea",
 	"addContent",
 	"PopupMenu",
 	"SubMenu",
@@ -424,6 +425,7 @@
 	"ButtonContent",
 	"MenuButtonContent",
 	"GridLayout",
+	"FloatingArea",
 	"subMenuContent",
 	"ContentList",
 	"editContent",
--- a/mde/gui/renderer/IRenderer.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/gui/renderer/IRenderer.d	Sun Jan 04 17:35:15 2009 +0000
@@ -139,8 +139,9 @@
     //BEGIN Methods
     /** Returns a Border containing dimensions.
      *
-     * The dimensions may depend on whether the widget is resizable (horizontally and vertically).
-     */
+     * Params:
+     *	type	= Flags from BTYPE describing whether the border allows resizing and moving and
+     *		its size. */
     Border getBorder (Border.BTYPE type, bool wSizable, bool hSizable);
     
     /** Return the renderer's between-widget spacing (for layout widgets). */
--- a/mde/gui/widget/Floating.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/gui/widget/Floating.d	Sun Jan 04 17:35:15 2009 +0000
@@ -18,6 +18,7 @@
 
 import mde.gui.widget.Widget;
 import mde.gui.exception;
+import mde.content.Content;
 
 import tango.util.log.Log : Log, Logger;
 
@@ -32,13 +33,12 @@
  * Rationale: parents' need to set subwidgets' positions when its position is set, so it needs to
  * know their positions.
  *
- * Data: Each string item is interpreted as a subwidget widgetID.
- * Ints supplied may consist of just the widget type or
- * additionally an (x,y) coordinate for each subwidget (all x coords first, then all y coords).
- */
+ * Data: Each string item is interpreted as a sub-widget widgetID to add as a floating window.
+ * Ints consist of widget type followed by a border type (flags from IRenderer.Border.BTYPE) for
+ * each sub-widget. */
 class FloatingAreaWidget : AParentWidget
 {
-    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data) {
+    this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent content) {
         if (data.ints.length != 1 + data.strings.length)
             throw new WidgetDataException (this);
         super (mgr, parent, id);
@@ -47,7 +47,7 @@
         sWOrder.length = subWidgets.length;
         sWData.length = subWidgets.length;
         foreach (i,s; data.strings) {
-            subWidgets[i] = mgr.makeWidget (this, s);
+            subWidgets[i] = mgr.makeWidget (this, s, content);
             sWOrder[i] = i;
             sWData[i].borderType = cast(BTYPE) data.ints[i+1];
         }
@@ -116,6 +116,39 @@
             subWidgets[i].setPosition (x + d.x + d.border.x1, y + d.y + d.border.y1);
     }
     
+    override void minSizeChange (IChildWidget widg, wdim nmw, wdim nmh) {
+        size_t i = 0;
+        while (i < subWidgets.length) {
+            if (subWidgets[i] is widg)
+                goto gotI;
+            ++i;
+        }
+        debug logger.error ("minSizeChange: widget is not my child (code error)");
+        return;
+        
+        gotI:
+        with (sWData[i]) {
+            mw = nmw + border.x1 + border.x2;
+            mh = nmh + border.y1 + border.y2;
+            
+            if (mw > w || !widg.isWSizable) {
+                w = mw;
+                widg.setWidth  (w - border.x1 - border.x2, -1);
+            }
+            if (mh > h || !widg.isHSizable) {
+                h = mh;
+                widg.setHeight (h - border.y1 - border.y2, -1);
+            }
+            
+            if (x + w > this.w)
+                x = (w > this.w) ? 0 : this.w - w;
+            if (y + h > this.h)
+                y = (h > this.h) ? 0 : this.h - h;
+            
+            mgr.requestRedraw;
+        }
+    }
+    
     override void draw () {
         super.draw;
         
@@ -210,14 +243,20 @@
         with (sWData[active]) {
             if (resizeType & RESIZE.X1) {
                 wdim ow = w;
-                w = xDrag - cx;
-                if (w < mw) w = mw;
-                x += ow - w;
+                w = xDrag - cx;		// new width
+                if (w < mw) w = mw;	// limit to min width
+                x += ow - w;		// move by difference
+                if (x < 0) {		// limit to left edge of area
+                    w += x;
+                    x = 0;
+                }
                 subWidgets[active].setWidth  (w - border.x1 - border.x2, 1);
             }
             else if (resizeType & RESIZE.X2) {
-                w = xDrag + cx;
-                if (w < mw) w = mw;
+                w = xDrag + cx;		// new width
+                if (w < mw) w = mw;	// limit to min width
+                if (x + w > this.w)	// limit to right edge of area
+                    w = this.w - x;
                 subWidgets[active].setWidth  (w - border.x1 - border.x2, -1);
             }
             if (resizeType & RESIZE.Y1) {
@@ -225,11 +264,17 @@
                 h = yDrag - cy;
                 if (h < mh) h = mh;
                 y += oh - h;
+                if (y < 0) {
+                    h += y;
+                    y = 0;
+                }
                 subWidgets[active].setHeight (h - border.y1 - border.y2, 1);
             }
             else if (resizeType & RESIZE.Y2) {
                 h = yDrag + cy;
                 if (h < mh) h = mh;
+                if (y + h > this.h)
+                    h = this.h - y;
                 subWidgets[active].setHeight (h - border.y1 - border.y2, -1);
             }
             // Reposition widget and sub-widgets:
--- a/mde/gui/widget/Ifaces.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/gui/widget/Ifaces.d	Sun Jan 04 17:35:15 2009 +0000
@@ -31,6 +31,8 @@
 /*************************************************************************************************
  * Interface for parent widgets, including IWidgetManager.
  *
+ * All widgets implement this via AWidget to make things simpler (code sharing).
+ *
  * Notation:
  *  Positive/negative direction: along the x/y axis in this direction.
  *  Layout widget: a widget containing multiple sub-widges (which hence controls how they are
@@ -40,6 +42,18 @@
 {
     /** Checks for recursion of unsafe widgets to prevent infinite recursion. */
     void recursionCheck (widgetID);
+    
+    /** Child widgets should call this on their parent if their minimal size changes, since they
+     * cannot resize themselves.
+     * 
+     * Parents should resize children calling this when the size is increased, and possibly when
+     * decreased and the widget is not resizable (but should not do so when it is).
+     * 
+     * Params:
+     *	widget	= The child widget calling the function
+     *	mw	= New minimal width
+     *	mh	= New minimal height */
+    void minSizeChange (IChildWidget widget, wdim mw, wdim mh);
 }
 
 
--- a/mde/gui/widget/TextWidget.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/gui/widget/TextWidget.d	Sun Jan 04 17:35:15 2009 +0000
@@ -145,7 +145,8 @@
     override void keyEvent (ushort s, char[] i) {
 	adapter.text = content.keyStroke (s, i);
 	adapter.index = content.editIndex;
-	adapter.getDimensions (mw, mh);	// NOTE: only passively change size: next resize will see new minimal size
+	adapter.getDimensions (mw, mh);
+        parent.minSizeChange (this, mw, mh);
 	mgr.requestRedraw;
     }
     override void keyFocusLost () {
--- a/mde/gui/widget/Widget.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/gui/widget/Widget.d	Sun Jan 04 17:35:15 2009 +0000
@@ -56,6 +56,9 @@
             throw new GuiException ("Infite recursion of "~a);
         parent.recursionCheck (a);
     }
+    
+    // Parent widgets need to implement this.
+    override void minSizeChange (IChildWidget widget, wdim mw, wdim mh) {}
 //END IParentWidget methods
     
 //BEGIN Load and save
--- a/mde/gui/widget/layout.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/gui/widget/layout.d	Sun Jan 04 17:35:15 2009 +0000
@@ -100,7 +100,6 @@
     this (IWidgetManager mgr, IParentWidget parent, widgetID id, WidgetData data, IContent content) {
 	cList = cast(IContentList) content;
 	WDCCheck (data, 2, 1, cList);
-        logger.trace ("Got data: {} and {}", data.ints, data.strings);
         cols = 1;
         rows = cList.list.length;
         subWidgets.length = rows;
@@ -116,7 +115,8 @@
             }
         } else {
             rows = cols = 1;
-            subWidgets = [mgr.makeWidget (this, data.strings[0], new ErrorContent ("<empty list>"))];
+            subWidgets = [mgr.makeWidget (this, data.strings[0],
+                                          new ErrorContent ("<empty list>",null)) ];
         }
     }
     
@@ -142,6 +142,7 @@
  *
  * The grid has no border but has spacing between widgets.
  *************************************************************************************************/
+// Note: mw, mh inherited from AWidget are not used; use col.mw, row.mw instead.
 abstract class GridWidget : AParentWidget
 {
     //BEGIN Creation & saving
@@ -192,8 +193,6 @@
 	}
 	initWidths = null;  // free
 	
-	mw = col.mw;
-	mh = row.mw;
 	w = col.w;
 	h = row.w;
 	
@@ -215,6 +214,14 @@
         return row.firstSizable >= 0;
     }
     
+    // mw, mh not used
+    override wdim minWidth () {
+        return col.mw;
+    }
+    override wdim minHeight () {
+        return row.mw;
+    }
+
     override void setWidth (wdim nw, int dir) {
         w = col.resizeWidth (nw, dir);
         // Note: setPosition must be called after!
@@ -232,6 +239,26 @@
         foreach (i,widget; subWidgets)
             widget.setPosition (x + col.pos[i % cols], y + row.pos[i / cols]);
     }
+    
+    override void minSizeChange (IChildWidget widg, wdim nmw, wdim nmh) {
+        size_t c = 0;
+        while (c < subWidgets.length) {
+            if (subWidgets[c] is widg)
+                goto gotCell;
+            ++c;
+        }
+        debug logger.error ("minSizeChange: widget is not my child (code error)");
+        return;
+        
+        gotCell:
+        if (col.newMinWidth (c % cols, nmw) ||
+            row.newMinWidth (c / cols, nmh)) {
+            parent.minSizeChange (this, col.mw, row.mw);
+            w = col.w;
+            h = row.w;
+        }
+        mgr.requestRedraw;
+    }
     //END Size & position
     
     
@@ -545,6 +572,7 @@
             diff = adjustCellSizes (diff, minWidth.length-1, -1);
         else
             diff = adjustCellSizes (diff, (dir == -1 ? lastSizable : firstSizable), dir);
+        genPositions;
         
         debug if (nw != w) {
             logger.trace ("resizeWidth on {} to {} failed, new width: {}, diff {}, firstSizable {}, columns {}",cast(void*)this, nw,w, diff, firstSizable, minWidth.length);
@@ -596,6 +624,29 @@
             diff = -adjustCellSizes (diff, resizeD, -1);
             adjustCellSizes (diff, resizeU, 1);
         }
+        genPositions;
+    }
+    
+    /** Called when one of the cells in column col now has minimal width nmw.
+     *
+     * Enlarges column minimal width if necessary; tries to keep total width the same but returns
+     * true if it cannot. */
+    bool newMinWidth (myDiff col, wdim nmw) {
+        bool r = false;
+        if (minWidth[col] < nmw) {
+            minWidth[col] = nmw;
+            wdim d = nmw - width[col];
+            if (d > 0 || (d < 0 && !sizable[col])) {
+                d = -adjustCellSizes (d, col, -1);
+                if (d != adjustCellSizes (d, minWidth.length-1, -1))
+                    r = true;	// if unable to keep overall size the same
+                genPositions;
+            }
+            mw = spacing * cast(wdim)(minWidth.length - 1);
+            foreach (imw; minWidth)
+                mw += imw;
+        }
+        return r;
     }
     
     /* Generate position infomation for each column and set w. */
@@ -666,7 +717,6 @@
         }
         // else no adjustment needed (diff == 0)
         
-        genPositions;
         return diff;
     }
     
@@ -687,11 +737,11 @@
      * Treat as READ ONLY outside this class! */
     wdim[]  width;              // only adjusted within the class
     wdim[]  pos;                /// ditto
+    wdim    spacing;            // used by genPositions (which cannot access the layout class's data)
+    wdim    w,mw;               // current & minimal widths
 protected:
     myDiff  resizeD,            // resizeCols works down from this index (<0 if not resizing)
             resizeU;            // and up from this index
-    wdim    spacing;            // used by genPositions (which cannot access the layout class's data)
-    wdim    w,mw;               // current & minimal widths
     /* indicies of the first/last resizable column (negative if none are resizable). */
     myDiff  firstSizable = -1, lastSizable = -1;  // set by calcFLSbl
     // Callbacks used to actually adjust a column's width:
--- a/mde/imde.d	Fri Jan 02 18:10:14 2009 +0000
+++ b/mde/imde.d	Sun Jan 04 17:35:15 2009 +0000
@@ -26,10 +26,11 @@
     input = new Input();
     mainSchedule = new Scheduler;
     
-    quit = (new EventContent("quit")).addCallback ((Content){
+    quit = new EventContent("quit");
+    quit.addCallback ((Content){
 	run = false;
     });
-    menu = new ContentList ("menu",[quit,
+    menu = new ContentList ("menu",[cast(Content) quit,
                             new EventContent("a"),
                             new ContentList("subMenu",[
                                            new EventContent("b"),