    001package org.vishia.gral.widget;
    002
    003import java.util.ArrayList;
    004import java.util.List;
    005
    006import org.vishia.gral.base.GralMenu;
    007import org.vishia.gral.base.GralMng;
    008import org.vishia.gral.base.GralWidgImpl_ifc;
    009import org.vishia.gral.base.GralWidget;
    010import org.vishia.gral.ifc.GralColor;
    011import org.vishia.gral.ifc.GralMngBuild_ifc;
    012import org.vishia.gral.ifc.GralUserAction;
    013import org.vishia.gral.ifc.GralWidget_ifc;
    014import org.vishia.util.KeyCode;
    015
    016/**This widget is a selector with tabs. It can be placed like a text field.
    017 * Selecting a tab the {@link GralWidget#setActionChange(GralUserAction)} is called with the associated
    018 * UserData of the tab is third parameter of {@link GralUserAction#exec(int, GralWidget_ifc, Object...)}
    019 * with {@link KeyCode#activated}.
    020 * That can focus, show or select some widgets, panels etc. With them a tab-panel is able to build,
    021 * but in a more possibilities than usual implementations of such tab-panels.
    022 * <br><br>
    023 * The tabs are shift to left and right if the spaces is to less.
    024 * <br><br>
    025 * Tabs can be closed with right-mouse context menu. Then the {@link GralUserAction#exec(int, GralWidget_ifc, Object...)}
    026 * will be called a last time for the barely known UserData the last time with {@link KeyCode#removed}.
    027 * If the current selected tab will be removed, the tab left of them or the next if it was the first
    028 * will be activated with calling of {@link GralUserAction#exec(int, GralWidget_ifc, Object...)}
    029 * for the yet current selected tab if there is any one yet.
    030 * <br><br>
    031 * @author Hartmut Schorrig
    032 *
    033 */
    034public class GralHorizontalSelector<UserData> extends GralWidget
    035{
    036  /**Version, history and copyright/copyleft.
    037   * <ul>
    038   * <li>2013-06-18 Hartmut created, new idea.
    039   * </ul>
    040   *
    041   * <b>Copyright/Copyleft</b>:<br>
    042   * For this source the LGPL Lesser General Public License,
    043   * published by the Free Software Foundation is valid.
    044   * It means:
    045   * <ol>
    046   * <li> You can use this source without any restriction for any desired purpose.
    047   * <li> You can redistribute copies of this source to everybody.
    048   * <li> Every user of this source, also the user of redistribute copies
    049   *    with or without payment, must accept this license for further using.
    050   * <li> But the LPGL is not appropriate for a whole software product,
    051   *    if this source is only a part of them. It means, the user
    052   *    must publish this part of source,
    053   *    but doesn't need to publish the whole source of the own product.
    054   * <li> You can study and modify (improve) this source
    055   *    for own using or for redistribution, but you have to license the
    056   *    modified sources likewise under this LGPL Lesser General Public License.
    057   *    You mustn't delete this Copyright/Copyleft inscription in this source file.
    058   * </ol>
    059   * If you intent to use this source without publishing its usage, you can get
    060   * a second license subscribing a special contract with the author.
    061   *
    062   * @author Hartmut Schorrig = hartmut.schorrig@vishia.de
    063   */
    064  @SuppressWarnings("hiding")
    065  public static final int version = 20130618;
    066
    067
    068  protected List<Item<UserData>> items = new ArrayList<Item<UserData>>();
    069
    070  protected Item<UserData> actItem = null;
    071
    072  /**The item (index in {@link items} which is the current or actual used one. */
    073  protected int ixActItem = 0;
    074
    075  /**The item which is selected while the mouse button is pressed and hold, not released yet. */
    076  protected int ixDstItem = 0;
    077
    078  /**The item which is shown as left in the graphic. */
    079  protected int ixLeftItem = 0;
    080
    081  protected int minSize;
    082
    083  public GralColor colorText, colorSelect, colorBack, colorLine;
    084
    085
    086  /**The constructor creates the instance but does nothing with the graphic appearance.
    087   * @param name
    088   * @param mng
    089   */
    090  public GralHorizontalSelector(String name, GralUserAction actionOnSelect){
    091    super(name, 'n');
    092    colorText = GralColor.getColor("bk");
    093    colorSelect = GralColor.getColor("rd");
    094    colorBack = GralColor.getColor("wh");
    095    colorLine = GralColor.getColor("bk");
    096
    097    setActionChange(actionOnSelect);
    098  }
    099
    100
    101  /**Adds a item to show.
    102   * @param text
    103   * @param position
    104   * @param data
    105   */
    106  public void addItem(String text, int position, UserData data){
    107    Item<UserData> item = new Item<UserData>();
    108    item.text = text;
    109    item.xSize = 0;
    110    item.data = data;
    111    int pos1;
    112    if(position < 0 || position > items.size()){ pos1 = items.size(); }
    113    else{ pos1 = position; }
    114    items.add(pos1, item);
    115    ixActItem = ixDstItem = pos1;
    116    actItem = item;
    117  }
    118
    119
    120
    121  public boolean setActItem(String name){
    122    int ixItem = 0;
    123    for(Item<UserData> item: items){
    124      if(item.text.equals(name)){
    125        actItem = item;
    126        ixActItem = ixItem;
    127        ixDstItem = ixItem;
    128        return true;
    129      }
    130      ixItem +=1;
    131    }
    132    return false;  //not found.
    133  }
    134
    135  /**Remove a item.
    136   * @param text
    137   */
    138  public void removeItem(String text){
    139    for(Item<UserData> item: items){
    140      if(item.text.equals(text)){
    141        items.remove(item);
    142      }
    143    }
    144  }
    145
    146
    147  protected void setDstToActItem(){
    148    if(ixDstItem >=0){
    149      ixActItem = ixDstItem;
    150      actItem = items.get(ixActItem);
    151      GralWidget_ifc.ActionChange action = getActionChange(GralWidget_ifc.ActionChangeWhen.onEnter);
    152      if(action !=null){
    153        Object[] args = action.args();
    154        if(args == null){ action.action().exec(KeyCode.activated, GralHorizontalSelector.this, actItem.data); }
    155        else { action.action().exec(KeyCode.activated, GralHorizontalSelector.this, args, actItem.data); }
    156      }
    157    }
    158  }
    159
    160
    161
    162
    163  /**Called on mouse action in context menu.
    164   *
    165   */
    166  protected void removeTab(){
    167    boolean actItemRemoved = ixDstItem == ixActItem;
    168    Item<UserData> removed = items.remove(ixDstItem);
    169    GralWidget_ifc.ActionChange action = getActionChange(GralWidget_ifc.ActionChangeWhen.onEnter);
    170    /* un-necessary for remove:
    171    if(action !=null){
    172      Object[] args = action.args();
    173      if(args == null){ action.action().exec(KeyCode.activated, GralHorizontalSelector.this, actItem.data); }
    174      else { action.action().exec(KeyCode.removed, GralHorizontalSelector.this, args, actItem.data); }
    175    }
    176    */
    177    if(ixDstItem < ixActItem){ ixActItem -=1; }
    178    if(actItemRemoved){
    179      if(ixActItem >= items.size()){
    180        ixDstItem = ixActItem-1;
    181      }
    182      setDstToActItem();  //calls activation of the yet actual item.
    183    } else {
    184      ixDstItem = ixActItem; //unchanged
    185    }
    186    repaint(100, 300);
    187  }
    188
    189
    190
    191
    192  public static class Item<UserData>
    193  {
    194    public String text;
    195    public int xSize;
    196    protected UserData data;
    197
    198    protected boolean removeIfNotUsed;
    199  }
    200
    201
    202  GralUserAction actionRemoveTab = new GralUserAction("actionRemoveTab"){
    203    @Override public boolean exec(int actionCode, GralWidget_ifc widgd, Object... params) {
    204      if(KeyCode.isControlFunctionMouseUpOrMenu(actionCode)){ removeTab(); }
    205      return true;
    206    }
    207
    208  };
    209
    210
    211
    212
    213
    214  /**This class is not intent to use from an application, it is the super class for the implementation layer
    215   * to access all necessary data and methods with protected access rights.
    216   * The methods are protected because an application should not use it. This class is public because
    217   * it should be visible from the graphic implementation which is located in another package.
    218   */
    219  public static abstract class GraphicImplAccess<UserData>
    220  extends GralWidget.ImplAccess //access to GralWidget
    221  implements GralWidgImpl_ifc
    222  {
    223
    224    //public void setWidgetImpl(GralWidgImpl_ifc widg){ wdgImpl = widg; }
    225
    226    protected final GralHorizontalSelector<UserData> outer;
    227
    228    protected GraphicImplAccess(GralHorizontalSelector<UserData> widgg, GralMng mng){
    229      super(widgg, mng);
    230      outer = widgg;
    231    }
    232
    233    protected List<Item<UserData>> items(){ return outer.items; }
    234
    235    protected Item<?> actItem(){ return outer.actItem; }
    236
    237    protected Item<?> tab(int ix){ return outer.items.get(ix); }
    238
    239    protected int nrItem(){ return outer.ixDstItem; }
    240
    241    protected int nrofTabs(){ return outer.items.size(); }
    242
    243    protected void calcLeftTab(int gwidth, int xArrow){
    244      int xBefore = 0;
    245      int ixItem = outer.ixDstItem;
    246      //
    247      //search what tab should be shown left as first:
    248      //
    249      if(outer.items.size() >0){
    250        outer.ixLeftItem = 0;
    251        while(outer.ixLeftItem ==0 && ixItem >=0){
    252          GralHorizontalSelector.Item<UserData> item = outer.items.get(ixItem);
    253          if(item.xSize == 0){
    254            //item.xSize = 50; //TODO
    255          }
    256          if(xArrow + xBefore + item.xSize + xArrow +4 > gwidth){  //to much yet
    257            outer.ixLeftItem = ixItem +1;
    258          } else{
    259            xBefore += item.xSize;
    260            ixItem -=1;
    261          }
    262        }
    263      } else {
    264        outer.ixLeftItem = -1;  //not given
    265      }
    266    }
    267
    268
    269    /**Searches the tab which is shown in the xMouse region.
    270     * @param xMouse x-position from mouse button pressed in the implementation widget area.
    271     */
    272    protected void findTab(int xMouse){
    273      int ixTab = outer.ixLeftItem;
    274      int xPos = (ixTab == 0) ? 2: 22;
    275      boolean found = false;
    276      int zTabs = outer.items.size();
    277      GralHorizontalSelector.Item<?> tab;
    278      do {
    279        tab = tab(ixTab);
    280        if(xPos + tab.xSize > xMouse){
    281          found = true;
    282        } else {
    283          xPos += tab.xSize;
    284          ixTab +=1;
    285        }
    286      } while(ixTab < zTabs && !found);
    287      if(found){
    288        outer.ixDstItem = ixTab;
    289        //setActItem(tab.text);
    290      }
    291    }
    292
    293    /**Sets a chosen item to the current one, because the mouse was released inside this item. */
    294    protected void setDstToActItem(){ outer.setDstToActItem(); }
    295
    296    /**Removes a different choice of a destination item, because the mouse was released
    297     * outside of the area where it is pressed and outside of the widget. */
    298    protected void clearDstItem(){ outer.ixDstItem = outer.ixActItem; }
    299
    300    protected void removeDstItem(){
    301      outer.items.remove(outer.ixDstItem);
    302      if(outer.ixDstItem < outer.ixActItem){ outer.ixActItem -=1; }
    303      clearDstItem();
    304    }
    305
    306    protected void execAfterCreationImplWidget(){
    307      GralMenu menu = outer.getContextMenu();
    308      menu.addMenuItem("&Close tab", outer.actionRemoveTab);
    309    }
    310
    311
    312    protected int nrLeftTab(){ return outer.ixLeftItem; }
    313
    314
    315
    316  }
    317
    318}