diff -r 0a2f117c2fb8 -r 61cde5384831 MultiColumnPanelView.st --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MultiColumnPanelView.st Mon Dec 17 12:10:53 2007 +0100 @@ -0,0 +1,675 @@ +" + COPYRIGHT (c) 1989 by Claus Gittinger + All Rights Reserved + + This software is furnished under a license and may be used + only in accordance with the terms of that license and with the + inclusion of the above copyright notice. This software may not + be provided or otherwise made available to, or used by, any + other person. No title to or ownership of the software is + hereby transferred. +" +"{ Package: 'stx:libwidg' }" + +PanelView subclass:#MultiColumnPanelView + instanceVariableNames:'columnWidths' + classVariableNames:'' + poolDictionaries:'' + category:'Views-Layout' +! + +!MultiColumnPanelView class methodsFor:'documentation'! + +copyright +" + COPYRIGHT (c) 1989 by Claus Gittinger + All Rights Reserved + + This software is furnished under a license and may be used + only in accordance with the terms of that license and with the + inclusion of the above copyright notice. This software may not + be provided or otherwise made available to, or used by, any + other person. No title to or ownership of the software is + hereby transferred. +" +! + +documentation +" + a View which arranges its child-views in a vertical column. + All real work is done in PanelView - except the layout computation is + redefined here. + + The layout is controlled by the instance variables: + horizontalLayout and verticalLayout + in addition to + horizontalSpace and verticalSpace. + + The vertical layout can be any of: + + #top arrange elements at the top + #topSpace arrange elements at the top, start with spacing + #bottom arrange elements at the bottom + #bottomSpace arrange elements at the bottom, start with spacing + #center arrange elements in the center; ignore verticalSpace + #spread spread elements evenly; ignore verticalSpace + #spreadSpace spread elements evenly with spacing at ends; ignore verticalSpace + #fit like #spread, but resize elements for tight packing; ignore verticalSpace + #fitSpace like #fit, with spacing; ignore verticalSpace + #topFit like #top, but resize the last element to fit + #topSpaceFit like #topSpace, but resize the last element to fit + #bottomFit like #bottom, but resize the first element to fit + #bottomSpaceFit like #bottomSpace, but resize the first element to fit + + the horizontal layout can be: + + #left place element at the left + #leftSpace place element at the left, offset by horizontalSpace + #center place elements horizontally centered; ignore horizontalSpace + #right place it at the right + #rightSpace place it at the right, offset by horizontalSpace + #fit resize elements horizontally to fit this panel; ignore horizontalSpace + #fitSpace like #fit, but add spacing; ignore horizontalSpace + + #leftMax like #left, but resize elements to max of them + #leftSpaceMax like #leftSpace, but resize elements + #centerMax like #center, but resize elements + #rightMax like #right, but resize elements to max of them + #rightSpaceMax like #rightSpace, but resize elements + + The defaults is #center for both directions. + + The layout is changed by the messages #verticalLayout: and #horizontalLayout:. + For backward compatibility (to times, where only vLayout existed), the simple + #layout: does the same as #verticalLayout:. Do not use this old method. + + The panel assumes, that the elements do not resize themselfes, after it + became visible. This is not true for all widgets (buttons or labels may + like to change). If you have changing elements, tell this to the panel + with 'aPanel elementsChangeSize:true'. In that case, the panel will react + to size changes, and reorganize things. + + If none of these layout/space combinations is exactly what you need in + your application, create a subclass, and redefine the setChildPositions method. + + CAVEAT: this class started with #top and no horizontal alignments; + as time went by, more layouts were added and the setup should be changed + to use different selectors for space, max-resize and alignment + (i.e. having more and more layout symbols makes things a bit confusing ...) + + [see also:] + HorizontalPanelView + VariableVerticalPanel VariableHorizontalPanel + Label + + [author:] + Claus Gittinger +" +! + +examples +" + These examples demonstrate the effect of different layout + settings. + You should try more examples, combining spacing and different + verticalLayout:/horizontalLayout: combinations. + + + example: default layout (centered) + [exBegin] + |v p b1 b2 b3 l1 l2 l3| + + v := StandardSystemView new. + v label:'center (default)'. + p := MultiColumnPanelView in:v. + p origin:(0.0 @ 0.0) corner:(1.0 @ 1.0). + p columnWidths:#(0.3 0.7). + l1 := Label label:'label1' in:p. + b1 := Button label:'button1' in:p. + l2 := Label label:'l2' in:p. + b2 := Button label:'b2' in:p. + l3 := Label label:'lab3' in:p. + b3 := Button label:'butt3' in:p. + v extent:100 @ 300. + v open + [exEnd] +" +! ! + +!MultiColumnPanelView methodsFor:'accessing'! + +columnWidths:something + something ~= columnWidths ifTrue:[ + columnWidths := something. + self layoutChanged + ]. +! + +horizontalLayout + "return the horizontal layout as symbol. + the returned value is one of + #left place element at the left + #leftSpace place element at the left, offset by horizontalSpace + #center place elements horizontally centered; ignore horizontalSpace + #right place it at the right + #rightSpace place it at the right, offset by horizontalSpace + #fit resize elements horizontally to fit this panel; ignore horizontalSpace + #fitSpace like #fit, but add spacing; ignore horizontalSpace + + #leftMax like #left, but resize elements to max of them + #leftSpaceMax like #leftSpace, but resize elements + #centerMax like #center, but resize elements + #rightMax like #right, but resize elements to max of them + #rightSpaceMax like #rightSpace, but resize elements + the default is #centered + " + + ^ hLayout +! + +horizontalLayout:aSymbol + "change the horizontal layout as symbol. + The argument, aSymbol must be one of: + #left place element at the left + #leftSpace place element at the left, offset by horizontalSpace + #center place elements horizontally centered; ignore horizontalSpace + #right place it at the right + #rightSpace place it at the right, offset by horizontalSpace + #fit resize elements horizontally to fit this panel; ignore horizontalSpace + #fitSpace like #fit, but add spacing; ignore horizontalSpace + + #leftMax like #left, but resize elements to max of them + #leftSpaceMax like #leftSpace, but resize elements + #centerMax like #center, but resize elements + #rightMax like #right, but resize elements to max of them + #rightSpaceMax like #rightSpace, but resize elements + the default (if never changed) is #centered + " + + (hLayout ~~ aSymbol) ifTrue:[ + hLayout := aSymbol. + self layoutChanged + ] +! + +layout:something + "OBSOLETE compatibility interface. Will vanish. + leftover for historic reasons - do not use any more. + In the meantime, try to figure out what is meant ... a kludge" + + + + something isLayout ifTrue:[^ super layout:something]. + + self obsoleteMethodWarning:'use #verticalLayout:'. + ^ self verticalLayout:something + + "Modified: 31.8.1995 / 23:08:54 / claus" +! + +verticalLayout + "return the vertical layout as a symbol. + the returned value is one of + #top arrange elements at the top + #topSpace arrange elements at the top, start with spacing + #bottom arrange elements at the bottom + #bottomSpace arrange elements at the bottom, start with spacing + #center arrange elements in the center; ignore verticalSpace + #spread spread elements evenly; ignore verticalSpace + #spreadSpace spread elements evenly with spacing at ends; ignore verticalSpace + #fit like #spread, but resize elements for tight packing; ignore verticalSpace + #fitSpace like #fit, with spacing; ignore verticalSpace + #topFit like #top, but resize the last element to fit + #topSpaceFit like #topSpace, but resize the last element to fit + #bottomFit like #bottom, but resize the first element to fit + #bottomSpaceFit like #bottomSpace, but extend the first element to fit + the default is #centered + " + + ^ vLayout + + "Modified: 17.8.1997 / 15:20:13 / cg" +! + +verticalLayout:aSymbol + "change the vertical layout as a symbol. + The argument, aSymbol must be one of: + #top arrange elements at the top + #topSpace arrange elements at the top, start with spacing + #bottom arrange elements at the bottom + #bottomSpace arrange elements at the bottom, start with spacing + #center arrange elements in the center; ignore verticalSpace + #spread spread elements evenly; ignore verticalSpace + #spreadSpace spread elements evenly with spacing at ends; ignore verticalSpace + #fit like #spread, but resize elements for tight packing; ignore verticalSpace + #fitSpace like #fit, with spacing; ignore verticalSpace + #topFit like #top, but resize the last element to fit + #topSpaceFit like #topSpace, but resize the last element to fit + #bottomFit like #bottom, but resize the first element to fit + #bottomSpaceFit like #bottomSpace, but extend the first element to fit + the default (if never changed) is #centered + " + + (vLayout ~~ aSymbol) ifTrue:[ + vLayout := aSymbol. + self layoutChanged + ] + + "Modified: 17.8.1997 / 15:19:58 / cg" +! ! + +!MultiColumnPanelView methodsFor:'initialization'! + +initialize + super initialize. + + hLayout := #fit. + vLayout := #top. + columnWidths := #(0.5 0.5). +! ! + +!MultiColumnPanelView methodsFor:'layout'! + +setChildPositions + "(re)compute position of every child" + + |xpos ypos space sumOfHeights numChilds l hEach hInside hL vL + maxWidth maxHeight resizeToMaxV resizeToMaxH m2 subViews restHeight + rowsPerCol maxWidthPerCol col numCols cX cY| + + subViews := self subViewsToConsider. + subViews size == 0 ifTrue:[^ self]. + +"/ self extentChangedFlag ifTrue:[ +"/ ext := self computeExtent. +"/ width := ext x. +"/ height := ext y. +"/ ]. + + space := verticalSpace. + numChilds := subViews size. + numCols := columnWidths size max:1. + + m2 := margin * 2. + hInside := height - m2 + (borderWidth*2) - subViews last borderWidth. + + hL := hLayout. + vL := vLayout. + + rowsPerCol := Array new:numCols withAll:0. + maxWidthPerCol := Array new:numCols withAll:0. + + maxHeight := 0. + col := 1. + subViews do:[:child | + |childsW childsH| + + childsW := child widthIncludingBorder. + childsH := child heightIncludingBorder. + maxHeight := maxHeight max:childsH. + maxWidthPerCol at:col put:((maxWidthPerCol at:col) max:childsW). + rowsPerCol at:col put:(rowsPerCol at:col)+1. + col := col + 1. + col > numCols ifTrue:[ col := 1 ]. + ]. +"/ new + xpos := 0. + ypos := 0. + col := 1. + subViews do:[:child | + col == numCols ifTrue:[ + cX := width. + ] ifFalse:[ + cX := xpos + (width // numCols). + ]. + cY := ypos + maxHeight. + + child origin:(xpos@ypos)"corner:(cX @ cY)". + hLayout == #fit ifTrue:[ + child width:(cX - xpos + 1). + ]. + col := col + 1. + col > numCols ifTrue:[ + col := 1. + xpos := 0. + ypos := cY + verticalSpace. + ] ifFalse:[ + xpos := cX + horizontalSpace. + ]. + ]. + ^ self. + +"/ old + + resizeToMaxV := false. + (vL endsWith:'Max') ifTrue:[ + resizeToMaxV := true. + hEach := maxHeight := subViews inject:0 into:[:maxSoFar :child | maxSoFar max:child heightIncludingBorder]. + vL := (vL copyWithoutLast:3) asSymbol. + ]. + + numChilds == 1 ifTrue:[ + (vL == #topFit or:[vL == #bottomFit]) ifTrue:[ + vL := #fit + ]. + (vL == #topSpaceFit or:[vL == #bottomSpaceFit]) ifTrue:[ + vL := #fitSpace + ]. + ]. + + vL == #fitSpace ifTrue:[ + " + adjust childs extents and set origins. + Be careful to avoid accumulation of rounding errors + " + hEach := (hInside - ((numChilds + 1) * space)) / numChilds. + ypos := space + margin - borderWidth. + ] ifFalse:[ + vL == #fit ifTrue:[ + " + adjust childs extents and set origins. + Be careful to avoid accumulation of rounding errors + " + hEach := (hInside - ((numChilds - 1) * space)) / numChilds. + ypos := margin - borderWidth. + ] ifFalse:[ + l := vL. + + " + compute net height needed + " + resizeToMaxV ifTrue:[ + sumOfHeights := subViews inject:0 into:[:sumSoFar :child | sumSoFar + maxHeight + (child borderWidth*2)]. + ] ifFalse:[ + sumOfHeights := subViews inject:0 into:[:sumSoFar :child | sumSoFar + child heightIncludingBorder]. + + "/ adjust - do not include height of last(first) element if doing a fit + (vL == #topFit or:[vL == #topSpaceFit]) ifTrue:[ + sumOfHeights := sumOfHeights - subViews last heightIncludingBorder. + ] ifFalse:[ + (vL == #bottomFit or:[vL == #bottomSpaceFit]) ifTrue:[ + sumOfHeights := sumOfHeights - subViews first heightIncludingBorder. + ] + ]. + ]. + + restHeight := height - sumOfHeights - ((numChilds-1)*space). + + ((l == #center) and:[numChilds == 1]) ifTrue:[l := #spread]. + (l == #spread and:[numChilds == 1]) ifTrue:[l := #spreadSpace]. + + " + compute position of topmost subview and space between them; + if they do hardly fit, leave no space between them + " + ((sumOfHeights >= (height - m2)) + and:[l ~~ #fixTopSpace and:[l ~~ #fixTop]]) ifTrue:[ + " + if we have not enough space for all the elements, + fill them tight, and show what can be shown (at least) + " + ypos := margin. + space := 0 + ] ifFalse:[ + l == #fixTopSpace ifTrue:[ + l := #topSpace + ] ifFalse:[ + l == #fixTop ifTrue:[ + l := #top + ] + ]. + ((l == #bottom) or:[l == #bottomSpace + or:[l == #bottomFit or:[l == #bottomSpaceFit]]]) ifTrue:[ + ypos := restHeight - (space * (numChilds - 1)). +"/ +"/ borderWidth == 0 ifTrue:[ +"/ ypos := ypos + space +"/ ]. +"/ + (l == #bottomSpace + or:[l == #bottomSpaceFit]) ifTrue:[ + ypos >= space ifTrue:[ + ypos := ypos - space + ] + ]. + ypos := ypos - margin. + + ypos < 0 ifTrue:[ + space := space min:(restHeight // (numChilds + 1)). + ypos := restHeight - (space * numChilds). + ] + ] ifFalse: [ + (l == #spread) ifTrue:[ + space := (restHeight - m2) // (numChilds - 1). + ypos := margin. + (space == 0) ifTrue:[ + ypos := restHeight // 2 + ] + ] ifFalse: [ + (l == #spreadSpace) ifTrue:[ + space := (restHeight - m2) // (numChilds + 1). + ypos := space + margin. + (space == 0) ifTrue:[ + ypos := restHeight // 2 + ] + ] ifFalse: [ + ((l == #top) or:[l == #topSpace + or:[l == #topFit or:[l == #topSpaceFit]]]) ifTrue:[ + space := space min:(restHeight - m2) // (numChilds + 1). + (vL == #fixTop or:[vL == #fixTopSpace]) ifTrue:[ + space := space max:verticalSpace. + ] ifFalse:[ + space := space max:0. + ]. + (l == #topSpace or:[l == #topSpaceFit]) ifTrue:[ + ypos := space + margin. + ] ifFalse:[ + "/ + "/ if the very first view has a 0-level AND + "/ my level is non-zero, begin with margin + "/ + true "(margin ~~ 0 and:[subViews first level == 0])" ifTrue:[ + ypos := margin + ] ifFalse:[ + ypos := 0 + ] + ] + ] ifFalse:[ + "center" + ypos := (restHeight - ((numChilds - 1) * space)) // 2. + ypos < 0 ifTrue:[ + space := restHeight // (numChilds + 1). + ypos := (restHeight - ((numChilds - 1) * space)) // 2. + ] + ] + ] + ] + ] + ]. + ]. + ]. + + resizeToMaxH := false. + (hL endsWith:'Max') ifTrue:[ + resizeToMaxH := true. + maxWidth := subViews inject:0 into:[:maxSoFar :child | maxSoFar max:child widthIncludingBorder]. + hL := (hL copyWithoutLast:3) asSymbol. + ]. + + " + now set positions + " + subViews keysAndValuesDo:[:index :child | + |xpos advance bwChild wChild newWChild x2| + + wChild := child widthIncludingBorder. + bwChild := child borderWidth. + + elementsChangeSize ifTrue:[ + "to avoid a recursion when we change the elements size" + child removeDependent:self. + ]. + resizeToMaxH ifTrue:[ + child width:(wChild := maxWidth - (bwChild * 2)). + ]. + + hL == #left ifTrue:[ + xpos := 0 - borderWidth + margin. + ] ifFalse:[ + hL == #leftSpace ifTrue:[ + xpos := horizontalSpace + margin + ] ifFalse:[ + hL == #right ifTrue:[ + xpos := width - wChild - margin + ] ifFalse:[ + hL == #rightSpace ifTrue:[ + xpos := width - horizontalSpace - wChild - margin. + ] ifFalse:[ + hL == #fitSpace ifTrue:[ + xpos := horizontalSpace + margin. + newWChild := width - m2 - ((horizontalSpace + bwChild) * 2) + ] ifFalse:[ + hL == #fit ifTrue:[ + newWChild := width "- (bwChild * 2)". + borderWidth == 0 ifTrue:[ + newWChild := newWChild - (bwChild * 2) + ]. + true "child level == 0" ifTrue:[ + xpos := margin - borderWidth. + newWChild := newWChild - m2 + ] ifFalse:[ + xpos := 0 - borderWidth. + ]. + ] ifFalse:[ + "centered" + xpos := margin + ((width - m2 - wChild) // 2). + ] + ] + ] + ] + ] + ]. + newWChild notNil ifTrue:[ + child width:newWChild + ]. + +"/ (xpos < 0) ifTrue:[ xpos := 0 ]. + + x2 := xpos + child width - 1. + + (vL == #fit + or:[vL == #fitSpace + or:[resizeToMaxV]]) ifTrue:[ + child origin:(xpos @ (ypos rounded)) + corner:(x2 @ (ypos + hEach - bwChild - 1) rounded). + advance := hEach + ] ifFalse:[ + child origin:(xpos@ypos). + advance := child heightIncludingBorder + ]. + + index == numChilds ifTrue:[ + |y| + + (vL == #topFit or:[vL == #topSpaceFit]) ifTrue:[ + y := height - margin - 1. + vL == #topSpaceFit ifTrue:[ + y := y - space + ]. + child corner:x2 @ y + ]. + ]. + index == 1 ifTrue:[ + (vL == #bottomFit or:[vL == #bottomSpaceFit]) ifTrue:[ + ypos := margin + 0 + (child borderWidth * 2) - borderWidth. + vL == #bottomSpaceFit ifTrue:[ + ypos := ypos + space + ]. + advance := restHeight. + child origin:((child origin x) @ ypos) + corner:((child corner x) @ (ypos+advance)) + ]. + ]. + + ypos := ypos + advance + space. + elementsChangeSize ifTrue:[ + "reinstall dependency that we removed above" + child addDependent:self. + ]. + ] + + "Modified: / 04-09-1995 / 18:43:29 / claus" + "Modified: / 10-10-2007 / 13:47:56 / cg" +! ! + +!MultiColumnPanelView methodsFor:'queries'! + +preferredExtent + "return a good extent, one that makes subviews fit" + + |maxHeight maxWidth maxWidthPerCol w m2 subViews col numCols rowsPerCol| + + "/ If I have an explicit preferredExtent .. + + preferredExtent notNil ifTrue:[ + ^ preferredExtent + ]. + + subViews := self subViewsToConsider. + (subViews size == 0) ifTrue:[ + ^ super preferredExtent. + ]. + + "compute net height needed" + numCols := columnWidths size. + rowsPerCol := Array new:numCols withAll:0. + maxWidthPerCol := Array new:numCols withAll:0. + + maxHeight := 0. + col := 1. + subViews do:[:child | + |childsPreference| + + childsPreference := child preferredExtent. + maxHeight := maxHeight max:childsPreference y. + maxWidthPerCol at:col put:((maxWidthPerCol at:col) max:childsPreference y). + rowsPerCol at:col put:(rowsPerCol at:col)+1. + col := col + 1. + col > numCols ifTrue:[ col := 1 ]. + ]. + + borderWidth ~~ 0 ifTrue:[ + maxWidth := maxWidth + (horizontalSpace * 2). + ]. + +"/ sumOfHeights := sumOfHeights + ((subViews size - 1) * verticalSpace). +"/ ((vLayout == #topSpace) or:[vLayout == #bottomSpace]) ifTrue:[ +"/ sumOfHeights := sumOfHeights + verticalSpace +"/ ] ifFalse:[ +"/ ((vLayout == #center) or:[vLayout == #spread]) ifTrue:[ +"/ sumOfHeights := sumOfHeights + (verticalSpace * 2) +"/ ] +"/ ]. + +"/ ((hLayout == #leftSpace) or:[hLayout == #rightSpace]) ifTrue:[ +"/ maxWidth := maxWidth + horizontalSpace +"/ ] ifFalse:[ +"/ ((hLayout == #fitSpace) or:[hLayout == #center]) ifTrue:[ +"/ maxWidth := maxWidth + (horizontalSpace * 2) +"/ ] +"/ ]. + m2 := margin * 2. +"/ ^ (maxWidth + m2) @ (sumOfHeights + m2) + + w := maxWidthPerCol inject:0 into:[:sumSoFar :thisWidth | sumSoFar + thisWidth]. + + ^ (w+m2) @ (rowsPerCol max * maxHeight) + m2 + + "Modified: / 17.1.1998 / 00:18:16 / cg" +! ! + +!MultiColumnPanelView class methodsFor:'documentation'! + +version + ^ '$Header: /cvs/stx/stx/libwidg/MultiColumnPanelView.st,v 1.1 2007-12-17 11:10:53 cg Exp $' +! !