001package org.vishia.gral.base;
002
003import java.lang.ref.Reference;
004
005import org.vishia.gral.ifc.GralColor;
006import org.vishia.gral.ifc.GralImageBase;
007import org.vishia.gral.ifc.GralUserAction;
008import org.vishia.gral.ifc.GralWidget_ifc;
009import org.vishia.util.KeyCode;
010
011public class GralButton extends GralWidget
012{
013  /**Version, history and license.
014   * <ul>
015   * <li>2016-05-03 Hartmut chg: Now supports {@link GralWidget#setVisible(boolean)}
016   * <li>2015-06-21 Hartmut chg: {@link #setSwitchMode(GralColor, GralColor, GralColor)} with null as 3. color
017   *   sets to 2-times switch like {@link #setSwitchMode(GralColor, GralColor)}. 
018   * <li>2015-05-31 Hartmut new: Now a key listener and a traverse listener was added for the SWT implementation.
019   *   This base class provides the {@link #activate()} method which should be invoked on ENTER-key.
020   * <li>2013-12-22 Hartmut chg: Now {@link GralButton} uses the new concept of instantiation: It is not
021   *   the super class of the implementation class. But it provides {@link GralButton.GraphicImplAccess}
022   *   as the super class. 
023   * <li>2013-05-23 Hartmut new color of text able to change (not only black)
024   * <li>2013-05-23 Hartmut chg: {@link #setState(State)} with an enum, not int, used for showing active/inactive
025   * <li>2012-08-22 Hartmut new implements {@link #setBackColor(GralColor, int)}
026   * <li>2012-03-09 Hartmut new Some functionality from SwtButton moved to this, some enhancement of 
027   *   functionality: Three states, 
028   * <li>2011 Hartmut created
029   * </ul>
030   * 
031   * <b>Copyright/Copyleft</b>:
032   * For this source the LGPL Lesser General Public License,
033   * published by the Free Software Foundation is valid.
034   * It means:
035   * <ol>
036   * <li> You can use this source without any restriction for any desired purpose.
037   * <li> You can redistribute copies of this source to everybody.
038   * <li> Every user of this source, also the user of redistribute copies
039   *    with or without payment, must accept this license for further using.
040   * <li> But the LPGL ist not appropriate for a whole software product,
041   *    if this source is only a part of them. It means, the user
042   *    must publish this part of source,
043   *    but don't need to publish the whole source of the own product.
044   * <li> You can study and modify (improve) this source
045   *    for own using or for redistribution, but you have to license the
046   *    modified sources likewise under this LGPL Lesser General Public License.
047   *    You mustn't delete this Copyright/Copyleft inscription in this source file.
048   * </ol>
049   * If you are intent to use this sources without publishing its usage, you can get
050   * a second license subscribing a special contract with the author. 
051   * 
052   * @author Hartmut Schorrig = hartmut.schorrig@vishia.de
053   * 
054   * 
055   */
056  public final static int version = 0x20120303;
057
058  
059  //final GralWindowMng_ifc mainWindow;
060  
061  protected boolean pressed;
062  
063  /**The state of switch and check button
064   * 1=on 2=off 3=disabled. 0=initial, see {@link #kOn} etc.
065   */
066  //protected int switchState;
067  //protected boolean switchedOn;
068
069  
070  public enum State{ On, Off, Disabled}
071  
072  protected State switchState;
073  
074  //public static final int kOn = 1, kOff=2, kDisabled=3;
075  
076  protected String sButtonTextOff, sButtonTextOn, sButtonTextDisabled;
077  
078  protected GralImageBase imageOff, imageOn, imageDisabled;
079  
080  /**Color of button. If colorOff is null, then it is not a switch button. */
081  protected GralColor colorBackOn, colorBackDisabled = GralColor.getColor("gr");
082  
083  /**Color of button. If colorOff is null, then it is not a switch button. */
084  protected GralColor colorLineOff = GralColor.getColor("bk"), colorLineOn = GralColor.getColor("bk"), colorLineDisabled = GralColor.getColor("lgr");
085  
086  /**Currently pressed, show it in graphic. */
087  protected boolean isPressed;
088  
089  
090  /**True if it is a switch or a check button. */
091  protected boolean shouldSwitched;
092  
093  /**True if the switch is in disable state. Show it in graphic. */
094  //protected boolean isDisabled;
095  
096  /**True if the button has three states: on, off, disabled. */
097  protected boolean bThreeStateSwitch;
098  
099  public GralButton(String sName)
100  {
101    super(sName, 'B');  //GralWidget
102  }
103  
104  /**Creates a button
105   * @param sPosName If given with "@pos = name" then it is a position with name, see {@link GralWidget#GralWidget(String, char)}
106   * @param sText The button text
107   * @param action The action on release mouse
108   */
109  public GralButton(String sPosName, String sText, GralUserAction action)
110  { super(sPosName, 'B');
111    setText(sText);
112    specifyActionChange(null, action, null);
113  }
114    
115  @Deprecated public GralButton(String position, String sName, String sText, GralUserAction action)
116  {
117    super(position, sName, 'B');  //GralWidget
118    setText(sText);
119    setActionChange(action);
120  }
121  
122  /**Declares the button to a switch button (switching on/off) with the given texts.
123   * Different colors for on and off may be set with {@link #setSwitchMode(GralColor, GralColor)}.
124   * See {@link #setSwitchMode(GralImageBase, GralImageBase)}.
125   * @param sButtonTextOff
126   * @param sButtonTextOn
127   */
128  public void setSwitchMode(String sButtonTextOff, String sButtonTextOn){ 
129    this.sButtonTextOn = sButtonTextOn;
130    this.sButtonTextOff = sButtonTextOff;
131    shouldSwitched = true;
132    bThreeStateSwitch = false;
133  }
134  
135  
136  /**Declares the button to a check button (switching on/off/disable) with the given texts.
137   * Different colors for on, off and disable may be set with {@link #setSwitchMode(GralColor, GralColor, GralColor)}.
138   * See {@link #setSwitchMode(GralImageBase, GralImageBase, GralImageBase)}.
139   * @param sButtonTextOff
140   * @param sButtonTextOn
141   * @param sDisabled
142   */
143  public void setSwitchMode(String sButtonTextOff, String sButtonTextOn, String sDisabled){ 
144    this.sButtonTextOn = sButtonTextOn;
145    this.sButtonTextOff = sButtonTextOff;
146    this.sButtonTextDisabled = sDisabled;
147    shouldSwitched = true; 
148    bThreeStateSwitch = true;
149  }
150  
151  
152  /**Declares the button to a switch button (switching on/off) with the given colors.
153   * Different texts for on and off may be set with {@link #setSwitchMode(String, String)}.
154   * See {@link #setSwitchMode(GralImageBase, GralImageBase)}.
155   * @param colorOff
156   * @param colorOn
157   */
158  public void setSwitchMode(GralColor colorOff, GralColor colorOn){ 
159    this.colorBackOn = colorOn;
160    dyda.backColor = colorOff;
161    bThreeStateSwitch = false;
162    shouldSwitched = true; 
163  }
164  
165  
166  /**Declares the button to a check button (switching on/off/disable) with the given texts.
167   * Different texts for on and off may be set with {@link #setSwitchMode(String, String, String)}.
168   * See {@link #setSwitchMode(GralImageBase, GralImageBase, GralImageBase)}.
169   * @param colorOff
170   * @param colorOn
171   */
172  public void setSwitchMode(GralColor colorOff, GralColor colorOn, GralColor colorDisabled){ 
173    this.colorBackOn = colorOn;
174    dyda.backColor = colorOff;
175    this.colorBackDisabled = colorDisabled;
176    bThreeStateSwitch = colorBackDisabled !=null;
177    shouldSwitched = true; 
178  }
179  
180  
181  /**Declares the button to a switch button (switching on/off) with the given images.
182   * see {@link #setSwitchMode(String, String)}.
183   * @param imageOff
184   * @param imageOn
185   */
186  public void setSwitchMode(GralImageBase imageOff, GralImageBase imageOn){ 
187    this.imageOn = imageOn;
188    this.imageOff = imageOff;
189    bThreeStateSwitch = false;
190    shouldSwitched = true; 
191  }
192  
193  
194  /**Declares the button to a check button (switching on/off/disable) with the given texts.
195   * see {@link #setSwitchMode(String, String, String)}.
196   * @param imageOff
197   * @param imageOn
198   */
199  public void setSwitchMode(GralImageBase imageOff, GralImageBase imageOn, GralImageBase imageDisabled){ 
200    this.imageOn = imageOn;
201    this.imageOff = imageOff;
202    this.imageDisabled = imageDisabled;
203    bThreeStateSwitch = true;
204    shouldSwitched = true; 
205  }
206  
207  
208  
209  
210  /**Implementing for 
211   * @see org.vishia.gral.base.GralWidget#setBackColor(org.vishia.gral.ifc.GralColor, int)
212   * @param ix=0: off-color ix=1: on-color, ix=2 or ix=-1: disable color.
213   */
214  @Override public void setBackColor(GralColor color, int ix){ 
215    GralColor[] ref = new GralColor[1];
216    boolean bChanged;
217    switch(ix){
218      case 0:          bChanged = !color.equals(dyda.backColor);     if(bChanged){ dyda.backColor = color; } break;
219      case 1:          bChanged = !color.equals(colorBackOn);        if(bChanged){ colorBackOn = color; } break;
220      case -1: case 2: bChanged = !color.equals(colorBackDisabled);  if(bChanged){ colorBackDisabled = color; } break;
221      default: throw new IllegalArgumentException("fault ix, allowed -1, 0, 1, 2, ix=" + ix);
222    }//switch
223    if(bChanged){
224      dyda.setChanged(ImplAccess.chgColorBack); 
225      repaint();
226    }
227  }
228
229  
230  /**Sets the disable text and color. A Button can be shown disabled anytime, independent of its role
231   * of switch button or check button.
232   * @param color
233   * @param text
234   */
235  public void setDisableColorText(GralColor color, String text){
236    this.colorBackDisabled = color;
237    this.sButtonTextDisabled = text;
238  }
239  
240  
241  /**Sets the text of the button.
242   * The text can be changed in any thread any time.
243   * A repaint is forced automatically after a short time (100 ms).
244   * @param sButtonText
245   */
246  public void setText(String sButtonText){ 
247    this.sButtonTextOff = sButtonText;
248    sButtonTextDisabled = sButtonText;
249    sButtonTextOff = sButtonText;
250    
251    if(sButtonTextDisabled == null){ sButtonTextDisabled = sButtonText; }
252    if(sButtonTextOn == null){ sButtonTextOn = sButtonText; }
253    repaint(100, 100);
254  }
255  
256  /**Show the button in an activated state. This method is called especially 
257   * in its mouse press and release events. 
258   * In the activated state the button looks like pressed.*/
259  protected void setActivated(boolean value){ 
260    isPressed = value;
261    repaint(100, 100);
262  }
263  
264  
265  /**This is the same action like release the left mouse button. It can be called from the application
266   * to activate the function of the button. Especially it is used for [Enter] on the focused button.
267   * It invokes the {@link GralWidget#specifyActionChange(String, GralUserAction, String[], org.vishia.gral.ifc.GralWidget_ifc.ActionChangeWhen...)}
268   * with {@link GralWidget_ifc.ActionChangeWhen#onMouse1Up}
269   */
270  public void activate() {
271    if(shouldSwitched){
272      if( switchState == State.On) { switchState = State.Off; }
273      else if(bThreeStateSwitch && switchState == State.Off){ switchState = State.Disabled; }
274      else { switchState = State.On; }
275    }
276    GralWidget_ifc.ActionChange action = getActionChange(GralWidget_ifc.ActionChangeWhen.onMouse1Up);
277    if(action !=null){
278      action.action().exec(KeyCode.enter, this, action.args());
279    }
280    repaint();
281  }
282  
283  
284  
285  @Override public void setValue(int cmd, int ident, Object visibleInfo, Object userData){
286    if(visibleInfo instanceof Integer){
287      int value = ((Integer)visibleInfo).intValue();
288      if(shouldSwitched){
289        switch(value){
290          case 0: switchState = State.Off; break;
291          case 1: switchState = State.On; break;
292          default: switchState = State.Disabled; break;
293        }
294      }
295    }
296  }
297
298  
299  
300  public void XXXsetDisabled(boolean value){
301    this.switchState = value ? State.Disabled: State.On; //kDisabled : kOn;
302    repaint(100, 100);
303  }
304  
305  
306  public boolean isDisabled(){ return switchState == State.Disabled; }
307  
308  
309  public boolean isOn(){ return switchState == State.On; }
310
311  /**Sets the appearance of the button
312   * <ul> 
313   * <li>val instance of GralColor, change the color appropriate to the current state.
314   * <li>val instance of Text, changes the switch state.
315   *   <ul>
316   *   <li>"1", "true", "on"; switches to on
317   *   <li>"0", "false", "off"; switches to off
318   *   </ul>    
319   * </ul>
320   * <li>val instance of Integer:
321   *   <ul>
322   *   <li>0 switches off.
323   *   <li>not 0 switches on
324   *   </ul>
325   * </ul> 
326   *   instance
327   * @param val 
328   */
329  public void setState(Object val)
330  {
331    if(val instanceof GralColor){
332      if(switchState == State.On){
333        colorBackOn = (GralColor)val;
334      } else {
335        dyda.backColor = (GralColor)val;
336      }
337    } else {
338      String sVal = (val instanceof String) ? (String)val : null;
339      int nVal = val instanceof Integer ? ((Integer)val).intValue(): -1;
340      if(sVal !=null && (sVal.equals("1") || sVal.equals("true") || sVal.equals("on"))
341        || sVal == null && nVal !=0){
342        switchState = State.On;
343      } else if(sVal !=null && (sVal.equals("0") || sVal.equals("false") || sVal.equals("off"))
344          || nVal == 0){
345        switchState = State.Off;
346      }
347    }
348    repaint(100,100);
349  }
350  
351
352  
353  /**Sets the state of the button
354   * @param state one of {@link #kOn}, {@value #kOff}, {@value #kDisabled}.
355   * @throws IllegalArgumentException if the state is faulty.
356   */
357  public void setState(State state){
358    //if(state == kOn || state == kOff || state ==kDisabled){
359      switchState = state;
360      repaint(100,100);
361    //} else {
362    //  throw new IllegalArgumentException("faulty state: " + state);
363    //}
364  }
365  
366  
367  public State getState(){ return switchState; }
368  
369  
370  /**Inner class to implement the mouse actions called from the gral implementing layer.
371   * If the mouse was moved from the widget's area while a button is pressed, the mouse action on release
372   * is not invoked.
373   */
374  class MouseActionButton implements GralMouseWidgetAction_ifc
375  {
376
377    @Override public boolean mouseMoved(int xMousePixel, int yMousePixel, int sizex, int sizey){
378      if(  xMousePixel < 0 || xMousePixel > sizex
379          || yMousePixel < 0 || yMousePixel > sizey
380          ){ //the mouse cursor has left the area of the widget:
381        setActivated(false);
382        return false;
383      }
384      else return true;
385    }
386
387
388    @Override public void mouse1Down(int keyCode, int xMousePixel, int yMousePixel, int xWidgetSizePixel, int yWidgetSizePixel, GralWidget widgg)
389    {
390      setActivated(true);
391      /*
392      GralWidget_ifc.ActionChange action = widgg.getActionChange(GralWidget_ifc.ActionChangeWhen.onMouse1Dn); 
393      if(action !=null){
394        Object[] args = action.args();
395        if(args == null){ action.action().exec(keyCode, widgg, new Integer(xMousePixel), new Integer(yMousePixel)); }
396        else { 
397          //additional 2 arguments: copy in one args2.
398          Object[] args2 = new Object[args.length +2];
399          System.arraycopy(args, 0, args2, 0, args.length);
400          args2[args.length] = new Integer(xMousePixel);
401          args2[args.length+1] = new Integer(yMousePixel);
402          action.action().exec(keyCode, widgg, args2); 
403        }
404      }
405      */
406    }   
407      
408   
409
410    /**It will be called only if the mouse up is pressed inside the button's area.
411     * @see org.vishia.gral.base.GralMouseWidgetAction_ifc#mouse1Up(int, int, int, int, int, org.vishia.gral.base.GralWidget)
412     */
413    @Override public void mouse1Up(int keyCode, int xMousePixel, int yMousePixel, int xWidgetSizePixel, int yWidgetSizePixel, GralWidget widgg)
414    {
415      setActivated(false);
416      //On -> Off -> Disabled -> On
417      if(shouldSwitched){
418        if( switchState == State.On) { switchState = State.Off; }
419        else if(bThreeStateSwitch && switchState == State.Off){ switchState = State.Disabled; }
420        else { switchState = State.On; }
421      }
422      /*
423      GralWidget_ifc.ActionChange action = getActionChange(GralWidget_ifc.ActionChangeWhen.onMouse1Up);
424      if(action !=null){
425
426        Object[] args = action.args();
427        if(args == null){ action.action().exec(keyCode, widgg, new Integer(xMousePixel), new Integer(yMousePixel)); }
428        else { 
429          //additional 2 arguments: copy in one args2.
430          Object[] args2 = new Object[args.length +2];
431          System.arraycopy(args, 0, args2, 0, args.length);
432          args2[args.length] = new Integer(xMousePixel);
433          args2[args.length+1] = new Integer(yMousePixel);
434          action.action().exec(keyCode, widgg, args2); 
435        }
436      }*/
437      widgg.repaint(); //100, 200);
438    }
439
440
441    @Override public void mouse2Down(int keyCode, int xMousePixel, int yMousePixel, int xWidgetSizePixel, int yWidgetSizePixel, GralWidget widgg)
442    {
443    }
444
445
446    @Override public void mouse2Up(int keyCode, int xMousePixel, int yMousePixel, int xWidgetSizePixel, int yWidgetSizePixel, GralWidget widgg)
447    {
448      /*
449      GralWidget_ifc.ActionChange action = getActionChange(GralWidget_ifc.ActionChangeWhen.onMouse2Up);
450      
451      if(action !=null){
452        Object[] args = action.args();
453        if(args == null){ action.action().exec(keyCode, widgg, new Integer(xMousePixel), new Integer(yMousePixel)); }
454        else { 
455          //additional 2 arguments: copy in one args2.
456          Object[] args2 = new Object[args.length +2];
457          System.arraycopy(args, 0, args2, 0, args.length);
458          args2[args.length] = new Integer(xMousePixel);
459          args2[args.length+1] = new Integer(yMousePixel);
460          action.action().exec(keyCode, widgg, args2); 
461        }
462      }
463      */
464    }
465
466    @Override public void mouse1Double(int keyCode, int xMousePixel, int yMousePixel, int xWidgetSizePixel, int yWidgetSizePixel, GralWidget widgg)
467    {
468      /*
469      GralWidget_ifc.ActionChange action = getActionChange(GralWidget_ifc.ActionChangeWhen.onMouse1Doublc);
470      if(action !=null){
471        Object[] args = action.args();
472        if(args == null){ action.action().exec(keyCode, widgg, new Integer(xMousePixel), new Integer(yMousePixel)); }
473        else { 
474          //additional 2 arguments: copy in one args2.
475          Object[] args2 = new Object[args.length +2];
476          System.arraycopy(args, 0, args2, 0, args.length);
477          args2[args.length] = new Integer(xMousePixel);
478          args2[args.length+1] = new Integer(yMousePixel);
479          action.action().exec(keyCode, widgg, args2); 
480        }
481      }
482      */
483    }
484    
485    
486    
487  }
488  
489  
490  
491  public abstract class GraphicImplAccess extends GralWidget.ImplAccess
492  implements GralWidgImpl_ifc
493  {
494
495    /**That action effects that the internal variable {@link #switchedOn} is set for a switching button.
496     * The user action will be called by the standard mouse action of the implementation graphic
497     * AWT: {@link org.vishia.gral.awt.AwtGralMouseListener.MouseListenerUserAction}.
498     */
499    final protected GralMouseWidgetAction_ifc mouseWidgetAction = new MouseActionButton();
500    
501
502    /**Set in {@link #paint1()}, used for paint routine. */
503    protected String sButtonText;
504    /**Set in {@link #paint1()}, used for paint routine. */
505    protected GralColor colorgback, colorgline;
506
507    /**Covers the GralWidget#widgg, but it is the same reference. This is the necessary type. */
508    protected GralButton widgg;
509    
510    protected GraphicImplAccess(GralButton widgg, GralMng mng)
511    { super(widgg);
512      this.widgg = widgg;
513    }
514    
515    
516    protected boolean isPressed(){ return GralButton.this.isPressed; }
517    
518    
519    protected void prepareWidget(){
520      int state1 = -1;
521      int chg = dyda().getChanged();  //don't handle a bit twice if re-read.
522      if((chg & chgVisibleInfo) !=0 && dyda().visibleInfo !=null){ 
523        if(dyda().visibleInfo instanceof Integer){
524          state1 = ((Integer)dyda().visibleInfo).intValue();
525        }
526      }
527      if((chg & chgIntg) !=0 ){ 
528        state1 = 0; //TODO dyda.intValue
529      }
530      dyda.acknChanged(chg & (chgVisibleInfo | chgIntg));
531      switch(state1){
532        case 0: switchState = State.Off; break;
533        case 1: switchState = State.On; break;
534        case 2: switchState = State.Disabled; break;
535        default: ; //don't change state.
536      }
537    }
538
539    
540    
541    protected void paint1(){
542      if(switchState == State.On){ 
543        sButtonText = sButtonTextOn != null ? sButtonTextOn : sButtonTextOff;
544        colorgback = colorBackOn !=null ? colorBackOn: dyda().backColor;
545        colorgline = colorLineOn !=null ? colorLineOn: colorLineOff;
546      } else if(switchState == State.Disabled){ 
547        sButtonText = sButtonTextDisabled;
548        colorgback = colorBackDisabled;
549        colorgline = colorLineDisabled;
550      } else { 
551        sButtonText = sButtonTextOff;
552        colorgback = dyda().backColor;
553        colorgline = colorLineOff;
554      }
555
556    }
557    
558    
559  }
560  
561  
562
563  
564}