1  /*
     2   Copyright aswing.org, see the LICENCE.txt.
     3  */
     4  import org.aswing.Component;
     5  import org.aswing.Container;
     6  import org.aswing.Event;
     7  import org.aswing.geom.Dimension;
     8  import org.aswing.Insets;
     9  import org.aswing.JAccordion;
    10  import org.aswing.LookAndFeel;
    11  import org.aswing.plaf.AccordionUI;
    12  import org.aswing.plaf.basic.accordion.AccordionHeader;
    13  import org.aswing.plaf.basic.accordion.BasicAccordionHeader;
    14  import org.aswing.utils.Timer;
    15  
    16  /**
    17   *
    18   * @author iiley
    19   */
    20  class org.aswing.plaf.basic.BasicAccordionUI extends AccordionUI {
    21  	
    22  	private static var MOTION_SPEED:Number = 50;
    23  	private static var MOTION_CYCLE:Number = 40;  
    24  	
    25  	private var accordion:JAccordion;
    26  	private var headers:Array;
    27  	private var stateListener:Object;
    28  	private var motionTimer:Timer;
    29  	private var headerDestinations:Array;
    30  	private var childrenDestinations:Array;
    31  	
    32  	public function BasicAccordionUI() {
    33  		super();
    34  	}
    35      	
    36      public function installUI(c:Component):Void{
    37      	headers = new Array();
    38  		accordion = JAccordion(c);
    39  		installDefaults();
    40  		installComponents();
    41  		installListeners();
    42      }
    43      
    44  	public function uninstallUI(c:Component):Void{
    45  		accordion = JAccordion(c);
    46  		uninstallDefaults();
    47  		uninstallComponents();
    48  		uninstallListeners();
    49      }
    50  	
    51  	private function installDefaults():Void{
    52  		var pp:String = "Accordion.";
    53          LookAndFeel.installBorder(accordion, pp + "border");
    54          LookAndFeel.installColorsAndFont(accordion, pp + "background", pp + "foreground", pp + "font");
    55  	}
    56      
    57      private function uninstallDefaults():Void{
    58      	LookAndFeel.uninstallBorder(accordion);
    59      }
    60      
    61  	private function installComponents():Void{
    62  		__synTabs();
    63      }
    64  	private function uninstallComponents():Void{
    65  		for(var i:Number=0; i<headers.length; i++){
    66  			getHeader(i).getComponent().destroy();
    67  		}
    68  		headers.splice(0);
    69      }
    70  	
    71  	private function installListeners():Void{
    72  		stateListener = accordion.addEventListener(Component.ON_STATE_CHANGED, __selectionChanged, this);
    73  		motionTimer = new Timer(MOTION_CYCLE);
    74  		motionTimer.addActionListener(__motion, this);
    75  	}
    76      
    77      private function uninstallListeners():Void{
    78      	accordion.removeEventListener(stateListener);
    79      	motionTimer.stop();
    80      }
    81      
    82      /**
    83       * Just override this method if you want other LAF headers.
    84       */
    85      private function createNewHeader():AccordionHeader{
    86      	return new BasicAccordionHeader();
    87      }
    88      
    89      private function __synTabs():Void{
    90      	var comCount:Number = accordion.getComponentCount();
    91      	if(comCount != headers.length){
    92      		if(comCount > headers.length){
    93      			for(var i:Number = headers.length; i<comCount; i++){
    94      				var header:AccordionHeader = createNewHeader();
    95      				header.setTextAndIcon(accordion.getTitleAt(i), accordion.getIconAt(i));
    96      				header.getComponent().setToolTipText(accordion.getTipAt(i));
    97      				header.getComponent().addEventListener(Component.ON_RELEASE, __tabPressed, this);
    98      				header.getComponent().addTo(accordion);
    99      				headers.push(header);
   100      			}
   101      		}else{
   102      			for(var i:Number = headers.length-comCount; i>0; i--){
   103      				var header:AccordionHeader = AccordionHeader(headers.pop());
   104      				header.getComponent().destroy();
   105      			}
   106      		}
   107      	}
   108      }
   109      
   110      private function __ensureHeadersDisplayable():Void{
   111      	for(var i:Number=0; i<headers.length; i++){
   112      		var header:AccordionHeader = getHeader(i);
   113      		header.setTextAndIcon(accordion.getTitleAt(i), accordion.getIconAt(i));
   114      		header.getComponent().setToolTipText(accordion.getTipAt(i));
   115      		if(!header.getComponent().isDisplayable()){
   116      			header.getComponent().addTo(accordion);
   117      		}
   118      	}
   119      }
   120      
   121      private function __ensureHeadersOnTopDepths():Void{
   122      	if(accordion.getComponentCount()<=0){
   123      		return;
   124      	}
   125      	var firstComDepth:Number = accordion.getComponent(0).getDepth();
   126      	if(firstComDepth == undefined){
   127      		return;
   128      	}
   129      	
   130      	for(var i:Number=0; i<headers.length; i++){
   131      		var header:AccordionHeader = getHeader(i);
   132      		//move all header above accordion child components
   133      		if(header.getComponent().isDisplayable()){
   134      			var needDepth:Number = firstComDepth + headers.length + i;
   135  	    		if(header.getComponent().getDepth() != needDepth){
   136  	    			header.getComponent().swapDepths(needDepth);
   137  	    			//trace("header depth to : " + needDepth);
   138  	    		}
   139      		}
   140      	}
   141      }
   142      
   143      public function create(c:Component):Void{
   144      	__ensureHeadersDisplayable();
   145      	__ensureHeadersOnTopDepths();
   146      }
   147          
   148      private function getHeader(i:Number):AccordionHeader{
   149      	return AccordionHeader(headers[i]);
   150      }
   151      
   152      private function indexOfHeaderComponent(tab:Component):Number{
   153      	for(var i:Number=0; i<headers.length; i++){
   154      		if(getHeader(i).getComponent() == tab){
   155      			return i;
   156      		}
   157      	}
   158      	return -1;
   159      }
   160      
   161      private function __selectionChanged(event:Event):Void{
   162      	accordion.revalidate();
   163      }
   164      
   165      private function __tabPressed(event:Event):Void{
   166      	accordion.setSelectedIndex(indexOfHeaderComponent(Component(event.getSource())));
   167      }
   168  	
   169  	//---------------------------------------------------------------
   170  
   171      /**
   172       * may need override in subclass
   173       */
   174      public function addLayoutComponent(comp:Component, constraints:Object):Void{
   175      	__synTabs();
   176      }
   177  
   178      /**
   179       * may need override in subclass
   180       */
   181      public function removeLayoutComponent(comp:Component):Void{
   182      	__synTabs();
   183      }
   184  	
   185  	/**
   186       * may need override in subclass
   187  	 */
   188      public function preferredLayoutSize(target:Container):Dimension{
   189      	if(target === accordion){
   190  	    	var insets:Insets = accordion.getInsets();
   191  	    	
   192  	    	var w:Number = 0;
   193  	    	var h:Number = 0;
   194  	    	
   195  	    	for(var i:Number=accordion.getComponentCount()-1; i>=0; i--){
   196  	    		var size:Dimension = accordion.getComponent(i).getPreferredSize();
   197  	    		w = Math.max(w, size.width);
   198  	    		h = Math.max(h, size.height);
   199  	    	}
   200  	    	
   201  	    	for(var i:Number=accordion.getComponentCount()-1; i>=0; i--){
   202  	    		var size:Dimension = getHeader(i).getComponent().getPreferredSize();
   203  	    		w = Math.max(w, size.width);
   204  	    		h += size.height;
   205  	    	}
   206  	    	
   207  	    	return insets.roundsSize(new Dimension(w, h));
   208      	}
   209      	return null;
   210      }
   211  
   212  	/**
   213       * may need override in subclass
   214  	 */
   215      public function minimumLayoutSize(target:Container):Dimension{
   216      	if(target === accordion){
   217  	    	var insets:Insets = accordion.getInsets();
   218  	    	
   219  	    	var w:Number = 0;
   220  	    	var h:Number = 0;
   221  	    	
   222  	    	for(var i:Number=accordion.getComponentCount()-1; i>=0; i--){
   223  	    		var size:Dimension = accordion.getComponent(i).getMinimumSize();
   224  	    		w = Math.max(w, size.width);
   225  	    	}
   226  	    	
   227  	    	for(var i:Number=accordion.getComponentCount()-1; i>=0; i--){
   228  	    		var size:Dimension = getHeader(i).getComponent().getMinimumSize();
   229  	    		w = Math.max(w, size.width);
   230  	    		h += size.height;
   231  	    	}
   232  	    	
   233  	    	return insets.roundsSize(new Dimension(w, h));
   234      	}
   235      	return null;
   236      }
   237      
   238      private function __motion():Void{
   239      	var isFinished:Boolean = true;
   240      	var n:Number = headerDestinations.length;
   241      	var selected:Number = accordion.getSelectedIndex();
   242      	
   243      	for(var i:Number=0; i<n; i++){
   244      		var header:AccordionHeader = getHeader(i);
   245      		var tab:Component = header.getComponent();
   246      		var curY:Number = tab.getY();
   247      		var desY:Number = headerDestinations[i];
   248      		var toY:Number;
   249      		if(Math.abs(desY - curY) <= MOTION_SPEED){
   250      			toY = desY;
   251      		}else{
   252      			if(desY > curY){
   253      				toY = curY + MOTION_SPEED;
   254      			}else{
   255      				toY = curY - MOTION_SPEED;
   256      			}
   257      			isFinished = false;
   258      		}
   259      		tab.setLocation(tab.getX(), toY);
   260      		tab.validate();
   261      		var child:Component = accordion.getComponent(i);
   262      		child.setLocation(child.getX(), toY + tab.getHeight());
   263      		child.validate();
   264      	}
   265      	
   266      	if(isFinished){
   267      		motionTimer.stop();
   268      		for(var i:Number=0; i<n; i++){
   269  	    		var child:Component = accordion.getComponent(i);
   270  	    		if(selected == i){
   271  	    			child.setVisible(true);
   272  	    		}else{
   273  	    			child.setVisible(false);
   274  	    		}
   275      		}
   276      	}
   277      	updateAfterEvent();
   278      }
   279      
   280      /**
   281       * may need override in subclass
   282       */
   283      public function layoutContainer(target:Container):Void{
   284      	var insets:Insets = accordion.getInsets();
   285      	var x:Number = insets.left;
   286      	var y:Number = insets.top;
   287      	var w:Number = accordion.getWidth() - x - insets.right;
   288      	var h:Number = accordion.getHeight() - y - insets.bottom;
   289      	
   290      	var count:Number = accordion.getComponentCount();
   291      	var selected:Number = accordion.getSelectedIndex();
   292      	if(selected < 0){
   293      		return;
   294      	}
   295      	
   296      	headerDestinations = new Array(count);
   297      	
   298      	var vX:Number, vY:Number, vWidth:Number, vHeight:Number;
   299      	vHeight = h;
   300      	vWidth = w;
   301      	vX = x;
   302      	for(var i:Number=0; i<=selected; i++){
   303      		var header:AccordionHeader = getHeader(i);
   304      		var tab:Component = header.getComponent();
   305      		var size:Dimension = tab.getPreferredSize();
   306      		tab.setSize(w, size.height);
   307      		tab.setLocation(x, tab.getY());
   308      		accordion.getComponent(i).setLocation(x, tab.getY()+size.height);
   309      		headerDestinations[i] = y;
   310      		y += size.height;
   311      		vHeight -= size.height;
   312      		if(i == selected){
   313      			header.setSelected(true);
   314      			accordion.getComponent(i).setVisible(true);
   315      		}else{
   316      			header.setSelected(false);
   317      		}
   318      		tab.validate();
   319      	}
   320      	vY = y;
   321      	
   322      	y = accordion.getHeight() - insets.bottom;
   323      	for(var i:Number=count-1; i>selected; i--){
   324      		var header:AccordionHeader = getHeader(i);
   325      		var tab:Component = header.getComponent();
   326      		var size:Dimension = tab.getPreferredSize();
   327      		y -= size.height;
   328      		headerDestinations[i] = y;
   329      		tab.setSize(w, size.height);
   330      		tab.setLocation(x, tab.getY());
   331      		accordion.getComponent(i).setLocation(x, tab.getY()+size.height);
   332      		header.setSelected(false);
   333      		vHeight -= size.height;
   334      		tab.validate();
   335      	}
   336      	for(var i:Number=0; i<count; i++){
   337      		accordion.getComponent(i).setSize(vWidth, vHeight);
   338      	}
   339      	motionTimer.restart();
   340      	__ensureHeadersOnTopDepths();
   341      }
   342          
   343      //-------------------------------------------------------------	
   344  }
   345