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}