1  /*
     2   Copyright aswing.org, see the LICENCE.txt.
     3  */
     4  
     5  import org.aswing.*;
     6  import org.aswing.graphices.*;
     7  import org.aswing.geom.*;
     8  import org.aswing.border.*;
     9  import org.aswing.plaf.*;
    10  import org.aswing.utils.*;
    11   
    12  /**
    13   * Basic Button implementation.
    14   * To implement a Diff button UI, generally you should:
    15   * <ul>
    16   * 	<li>override <code>paintBackGround</code> to paint different bg for you button
    17   * 	<li>implement a diff Border for it, and put it into UI defaults with "Button.border" as key.
    18   * 	<li>initialize differnt UI defaults for buttons in your LAF class, for example:  
    19   * 	Button.font, ToggleButton.backgound XXXButton.foreground...
    20   * </ul>
    21   * @author iiley
    22   */
    23  class org.aswing.plaf.basic.BasicButtonUI extends ButtonUI{
    24  
    25      // Shared UI object
    26      private static var buttonUI:BasicButtonUI;
    27      
    28      // Offset controlled by set method 
    29      private var shiftOffset:Number;
    30      private var defaultTextShiftOffset:Number;
    31  
    32      // Has the shared instance defaults been initialized?
    33      private var defaults_initialized:Boolean;
    34      
    35      //map to recored the buttons -> listeners
    36     	private var listenerMap:HashMap;
    37     	//map to recored the buttons -> last painted icon
    38     	private var lastPaintedIconMap:HashMap;
    39     	//map to recored the buttons -> topTextField
    40     	private var topTextFieldMap:HashMap;
    41     	//map to recored the buttons -> bottomTextField
    42     	private var bottomTextFieldMap:HashMap;
    43  
    44      private static var propertyPrefix:String = "Button" + ".";
    45  
    46      // ********************************
    47      //          Create PLAF
    48      // ********************************
    49      public static function createInstance(c:Component):ComponentUI {
    50      	if(buttonUI == null){
    51      		buttonUI = new BasicButtonUI();
    52      	}
    53          return buttonUI;
    54      }
    55  
    56      private function getPropertyPrefix():String {
    57          return propertyPrefix;
    58      }
    59      
    60      public function BasicButtonUI() {
    61      	shiftOffset = 0;
    62      	defaultTextShiftOffset = 0;
    63      	defaults_initialized = false;
    64      	listenerMap = new HashMap();
    65      	lastPaintedIconMap = new HashMap();
    66      	topTextFieldMap = new HashMap();
    67      	bottomTextFieldMap = new HashMap();
    68      	checkRectsForCountLayout();
    69      }
    70  
    71      public function installUI(c:Component):Void{
    72      	var b:AbstractButton = AbstractButton(c);
    73          installDefaults(b);
    74          installListeners(b);
    75      }
    76      
    77      private var defaultForeground:ASColor;
    78      private var defaultBackground:ASColor;
    79      private var defaultTextFormat:TextFormat;
    80      private var defaultBorder:Border;    
    81  
    82  	private function installDefaults(b:AbstractButton):Void{
    83          // load shared instance defaults
    84          var pp:String = getPropertyPrefix();
    85          if(!defaults_initialized) {
    86              defaultTextShiftOffset = Number(UIManager.get(pp + "textShiftOffset"));
    87          	defaults_initialized = true;
    88          }
    89          
    90          if(b.getMargin() === undefined || (b.getMargin() instanceof UIResource)) {
    91              b.setMargin(UIManager.getInsets(pp + "margin"));
    92          }
    93          
    94          LookAndFeel.installColorsAndFont(b, pp + "background", pp + "foreground", pp + "font");
    95          LookAndFeel.installBorder(b, pp + "border");
    96          LookAndFeel.installBasicProperties(b, pp);
    97          setTextShiftOffset();
    98  	}
    99  	
   100  	private function installListeners(b:AbstractButton):Void{
   101  		var l:Object = createButtonListener(b);
   102  		if(l != null){
   103  			listenerMap.put(b.getID(), l);
   104  			b.addEventListener(l);
   105  		}
   106  	}
   107  	
   108  	private function createButtonListener(b:AbstractButton):Object{
   109  		var l:Object = new Object();
   110  		l[AbstractButton.ON_STATE_CHANGED] =Delegate.create(this,onStateChanged);
   111  		l[AbstractButton.ON_SET_FOCUS] =Delegate.create(this,onDrawFocus);
   112  		l[AbstractButton.ON_KILL_FOCUS] =Delegate.create(this,onUnDrawFocus);
   113  		return l;
   114  	}
   115  	
   116  	private function onUnDrawFocus(eventObj:Event):Void{
   117  		var temp:Component=Component(eventObj.getTarget()); 
   118      	//temp.getFocusClip().clear();
   119  	}
   120  	
   121  	private function onDrawFocus(eventObj:Event):Void{
   122  	  	/*
   123  	  	var temp:Component=Component(eventObj.getSource()); 
   124  		var tempColor:ASColor=new ASColor(0xFF0000,100);
   125  		var tempPen:Pen=new Pen(tempColor,2);
   126  		var g:Graphics = new Graphics(temp.getFocusClip());
   127  		g.drawRectangle(tempPen,0,0,temp.getWidth(),temp.getHeight());
   128  		*/
   129  		    
   130  	}
   131  	
   132  	private function onStateChanged(eventObj:Event):Void{
   133  		eventObj.getSource().paintImmediately();
   134  	}
   135  	
   136  	
   137      public function uninstallUI(c:Component):Void{
   138      	var b:AbstractButton = AbstractButton(c);
   139          uninstallDefaults(b);
   140          uninstallListeners(b);
   141          removeMCs(b);
   142      	listenerMap.remove(c.getID());
   143      	lastPaintedIconMap.remove(c.getID());
   144      	topTextFieldMap.remove(c.getID());
   145      	bottomTextFieldMap.remove(c.getID());
   146      }
   147      
   148      private function uninstallDefaults(b:AbstractButton):Void{
   149      	LookAndFeel.uninstallBorder(b);
   150      }
   151      
   152      private function uninstallListeners(b:AbstractButton):Void{
   153      	b.removeEventListener(listenerMap.remove(b.getID()));
   154      }
   155          
   156      private function removeMCs(c:Component):Void{
   157      	getTopTextField(c).removeTextField();
   158      	getBottomTextField(c).removeTextField();
   159      }
   160      
   161      public function create(c:Component):Void{
   162      	var topText:TextField = c.createTextField("t_text");
   163      	var bottomText:TextField = c.createTextField("b_text");
   164      	topTextFieldMap.put(c.getID(), topText);
   165      	bottomTextFieldMap.put(c.getID(), bottomText);
   166      }
   167      
   168      private function getTopTextField(c:Component):TextField{
   169      	return TextField(topTextFieldMap.get(c.getID()));
   170      }
   171      
   172      private function getBottomTextField(c:Component):TextField{
   173      	return TextField(bottomTextFieldMap.get(c.getID()));
   174      }
   175      
   176      /* These rectangles/insets are allocated once for all 
   177       * ButtonUI.paint() calls.  Re-using rectangles rather than 
   178       * allocating them in each paint call substantially reduced the time
   179       * it took paint to run.  Obviously, this method can't be re-entered.
   180       */
   181  	private static var viewRect:Rectangle;
   182      private static var textRect:Rectangle;
   183      private static var iconRect:Rectangle;
   184         
   185      private static function checkRectsForCountLayout():Void{
   186      	if(viewRect == null){
   187  			viewRect = new Rectangle();
   188      		textRect = new Rectangle();
   189      		iconRect = new Rectangle();
   190      	}
   191      }
   192         
   193      public function paint(c:Component, g:Graphics, r:Rectangle):Void{
   194      	super.paint(c, g, r);
   195      	var b:AbstractButton = AbstractButton(c);
   196      	
   197      	var insets:Insets = b.getMargin();
   198      	if(insets != null){
   199      		r = insets.getInsideBounds(r);
   200      	}
   201      	viewRect.setRect(r);
   202      	
   203      	textRect.x = textRect.y = textRect.width = textRect.height = 0;
   204          iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
   205  
   206          // layout the text and icon
   207          var text:String = ASWingUtils.layoutCompoundLabel(
   208              c.getFont(), b.getText(), b.getIcon(), 
   209              b.getVerticalAlignment(), b.getHorizontalAlignment(),
   210              b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
   211              viewRect, iconRect, textRect, 
   212  	    	b.getText() == null ? 0 : b.getIconTextGap());
   213  	   	
   214      	
   215      	paintIcon(b, g, iconRect);
   216      	
   217          if (text != null && text != ""){
   218          	if(b.getModel().isPressed()){
   219          		textRect.x += getTextShiftOffset();
   220          		textRect.y += getTextShiftOffset();
   221          	}
   222  			paintText(b, textRect, text);
   223          }else{
   224          	getTopTextField(b).text = "";
   225          	getBottomTextField(b).text = "";
   226          }
   227          
   228          //Check for Focus and call Focus Draw
   229      }
   230      
   231      public function paintFocus(c:Component, g:Graphics, r:Rectangle):Void{
   232      	
   233      }
   234      
   235      /**
   236       * paint the text to specified button with specified bounds
   237       */
   238      private function paintText(b:AbstractButton, textRect:Rectangle, text:String):Void{
   239      	
   240      	var model:ButtonModel = b.getModel();
   241      	var font:ASFont = b.getFont();
   242      	if(model.isEnabled()){
   243      		paintTextField(textRect, getTopTextField(b), text, font, b.getForeground());
   244      		getBottomTextField(b)._visible = false;
   245      	}else{
   246      		getBottomTextField(b)._visible = true;
   247      		paintTextField(textRect, getTopTextField(b), text, font, b.getBackground().darker());
   248      		var rbRectangle:Rectangle = new Rectangle(textRect);
   249      		rbRectangle.x++;
   250      		rbRectangle.y++;
   251      		paintTextField(rbRectangle, getBottomTextField(b), text, font, b.getBackground().brighter());
   252      	}
   253      }
   254      
   255      private function paintTextField(tRect:Rectangle, textField:TextField, text:String, font:ASFont, color:ASColor):Void{
   256  		textField.text = text;
   257      	ASWingUtils.applyTextFontAndColor(textField, font, color);
   258  		//ASWingUtils.boundsTextField(textField, tRect); //This method has some problem cause text shake
   259  		textField._x = tRect.x;
   260  		textField._y = tRect.y;
   261      }
   262      
   263      /**
   264       * paint the icon to specified button's mc with specified bounds
   265       */
   266      private function paintIcon(b:AbstractButton, g:Graphics, iconRect:Rectangle):Void{
   267          var model:ButtonModel = b.getModel();
   268          var icon:Icon = b.getIcon();
   269          var tmpIcon:Icon = null;
   270  	    if(icon == null) {
   271  			unistallLastPaintIcon(b, icon);
   272  	    	return;
   273  	    }
   274  
   275          if(!model.isEnabled()) {
   276  			if(model.isSelected()) {
   277              	tmpIcon = b.getDisabledSelectedIcon();
   278  			} else {
   279              	tmpIcon = b.getDisabledIcon();
   280  			}
   281          }else if(model.isPressed()) {
   282              tmpIcon = b.getPressedIcon();
   283              if(tmpIcon != null) {
   284                  // revert back to 0 offset
   285                  //clearTextShiftOffset();
   286              }
   287          }else if(model.isRollOver() && b.isRollOverEnabled()) {
   288  			if(model.isSelected()) {
   289              	tmpIcon = b.getRollOverSelectedIcon();
   290  			} else {
   291              	tmpIcon = b.getRollOverIcon();
   292  			}
   293          }else if(model.isSelected()) {
   294              tmpIcon = b.getSelectedIcon();
   295  	    }
   296                
   297  	    if(tmpIcon != null) {
   298  	        icon = tmpIcon;
   299  	    }
   300                 
   301  		unistallLastPaintIcon(b, icon);
   302  		
   303          if(model.isPressed()) {
   304              icon.paintIcon(b, g, iconRect.x + getTextShiftOffset(),
   305                      iconRect.y + getTextShiftOffset());
   306          } else {
   307              icon.paintIcon(b, g, iconRect.x, iconRect.y);
   308          }
   309          setJustPaintedIcon(b, icon);
   310      }
   311      
   312  	private function paintBackGround(com:Component, g:Graphics, b:Rectangle):Void{
   313  		super.paintBackGround(com, g, b);
   314  	}    
   315      
   316      private function unistallLastPaintIcon(b:AbstractButton, currentIcon:Icon):Void{
   317      	var lastPaintedIcon:Icon = Icon(lastPaintedIconMap.get(b.getID()));
   318          if(lastPaintedIcon != currentIcon){
   319          	lastPaintedIcon.uninstallIcon(b);
   320          }
   321      }
   322      
   323      private function setJustPaintedIcon(b:AbstractButton, ic:Icon):Void{
   324      	lastPaintedIconMap.put(b.getID(), ic);
   325      }
   326      
   327      private function clearTextShiftOffset():Void{
   328          shiftOffset = 0;
   329      }
   330      
   331      private function setTextShiftOffset():Void{
   332          shiftOffset = defaultTextShiftOffset;
   333      }
   334      
   335      private function getTextShiftOffset():Number{
   336      	return shiftOffset;
   337      }
   338      
   339      /**
   340       * Returns the a button's preferred size with specified icon and text.
   341       */
   342      private function getButtonPreferredSize(b:AbstractButton, icon:Icon, text:String):Dimension{
   343      	viewRect.setRect(0, 0, 100000, 100000);
   344      	textRect.x = textRect.y = textRect.width = textRect.height = 0;
   345          iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
   346          
   347          ASWingUtils.layoutCompoundLabel(
   348              b.getFont(), text, icon, 
   349              b.getVerticalAlignment(), b.getHorizontalAlignment(),
   350              b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
   351              viewRect, iconRect, textRect, 
   352  	    	b.getText() == null ? 0 : b.getIconTextGap()
   353          );
   354          /* The preferred size of the button is the size of 
   355           * the text and icon rectangles plus the buttons insets.
   356           */
   357          var size:Dimension;
   358          if(icon == null){
   359          	size = textRect.getSize();
   360          }else if(b.getText()==null || b.getText()==""){
   361          	size = iconRect.getSize();
   362          }else{
   363          	var r:Rectangle = iconRect.union(textRect);
   364          	size = r.getSize();
   365          }
   366          size = b.getInsets().roundsSize(size);
   367  		if(b.getMargin() != null)
   368          	size = b.getMargin().roundsSize(size);
   369          return size;
   370      }
   371      /**
   372       * Returns the a button's minimum size with specified icon and text.
   373       */    
   374      private function getButtonMinimumSize(b:AbstractButton, icon:Icon, text:String):Dimension{
   375          var size:Dimension = b.getInsets().roundsSize();
   376  		if(b.getMargin() != null)
   377          	size = b.getMargin().roundsSize(size);
   378  		return size;
   379      }    
   380      
   381      public function getPreferredSize(c:Component):Dimension{
   382      	var b:AbstractButton = AbstractButton(c);
   383      	var icon:Icon = b.getIcon();
   384      	var text:String = b.getText();
   385      	return getButtonPreferredSize(b, icon, text);
   386      }
   387  
   388      public function getMinimumSize(c:Component):Dimension{
   389      	var b:AbstractButton = AbstractButton(c);
   390      	var icon:Icon = b.getIcon();
   391      	var text:String = b.getText();
   392      	return getButtonMinimumSize(b, icon, text);
   393      }
   394  
   395      public function getMaximumSize(c:Component):Dimension{
   396  		return new Dimension(Number.MAX_VALUE, Number.MAX_VALUE);
   397      }    
   398  }
   399