View Javadoc
1   /**
2    * This file Copyright (c) 2010-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.vaadin.gwt.client.tabsheet.widget;
35  
36  import info.magnolia.ui.vaadin.gwt.client.tabsheet.event.ActiveTabChangedEvent;
37  import info.magnolia.ui.vaadin.gwt.client.tabsheet.event.ShowAllTabsEvent;
38  import info.magnolia.ui.vaadin.gwt.client.tabsheet.event.ShowAllTabsHandler;
39  import info.magnolia.ui.vaadin.gwt.client.tabsheet.event.TabCloseEvent;
40  import info.magnolia.ui.vaadin.gwt.client.tabsheet.event.TabCloseEventHandler;
41  import info.magnolia.ui.vaadin.gwt.client.tabsheet.tab.widget.MagnoliaTabLabel;
42  import info.magnolia.ui.vaadin.gwt.client.tabsheet.tab.widget.MagnoliaTabWidget;
43  import info.magnolia.ui.vaadin.gwt.client.tabsheet.util.CollectionUtil;
44  
45  import java.util.Collections;
46  import java.util.HashMap;
47  import java.util.Iterator;
48  import java.util.LinkedList;
49  import java.util.List;
50  import java.util.Map;
51  
52  import com.google.gwt.core.client.Scheduler.ScheduledCommand;
53  import com.google.gwt.dom.client.NativeEvent;
54  import com.google.gwt.event.dom.client.ClickEvent;
55  import com.google.gwt.event.dom.client.ClickHandler;
56  import com.google.gwt.user.client.DOM;
57  import com.google.gwt.user.client.Element;
58  import com.google.gwt.user.client.ui.ComplexPanel;
59  import com.google.gwt.user.client.ui.DialogBox;
60  import com.google.gwt.user.client.ui.MenuBar;
61  import com.google.gwt.user.client.ui.MenuItem;
62  import com.google.gwt.user.client.ui.SimplePanel;
63  import com.google.gwt.user.client.ui.Widget;
64  import com.google.web.bindery.event.shared.EventBus;
65  import com.vaadin.client.ui.VButton;
66  
67  /**
68   * A bar that contains the tab labels and controls the switching between tabs.
69   *
70   * @deprecated Since 6.1, Use Vaadin TabSheet component
71   */
72  @Deprecated
73  public class TabBarWidget extends ComplexPanel {
74  
75      private static final String SINGLE_TAB_CLASS_NAME = "single-tab";
76  
77      private final List<MagnoliaTabLabel> tabLabels = new LinkedList<>();
78  
79      private final Element tabContainer = DOM.createElement("ul");
80  
81      private final EventBus eventBus;
82  
83      private VShellShowAllTabLabel showAllTab;
84  
85      private final HiddenTabsPopup hiddenTabsPopup;
86  
87      private MagnoliaTabLabel activeTab;
88  
89      public TabBarWidget(EventBus eventBus) {
90          this.eventBus = eventBus;
91          setElement(tabContainer);
92          setStyleName("nav");
93          addStyleDependentName("tabs");
94  
95          hiddenTabsPopup = new HiddenTabsPopup(eventBus);
96          add(hiddenTabsPopup, tabContainer);
97          bindHandlers();
98      }
99  
100     public void reArrangeTabVisibility() {
101         if (tabLabels.isEmpty()) {
102             return;
103         }
104         // Is this ever possible?
105         if (activeTab == null) {
106             activeTab = tabLabels.get(0);
107         }
108 
109         // Reset hidden-tabs popup
110         hiddenTabsPopup.setVisible(true);
111         int toggleWidth = hiddenTabsPopup.getOffsetWidth();
112         int availableWidth = tabContainer.getOffsetWidth();
113 
114         // 1. Active tab is always visible, count its width first
115         activeTab.setVisible(true);
116         availableWidth -= activeTab.getOffsetWidth();
117 
118         // 2. Account for show-all tab if needed
119         if (showAllTab != null) {
120             availableWidth -= showAllTab.getOffsetWidth();
121         }
122 
123         // Collect every tab's width (we'll have to know anyway)
124         Map<MagnoliaTabLabel, Integer> tabWidths = new HashMap<>();
125         for (MagnoliaTabLabel tab : tabLabels) {
126             if (tab == activeTab) {
127                 continue;
128             }
129             tab.setVisible(true);
130             tabWidths.put(tab, tab.getOffsetWidth());
131         }
132 
133         // 3. Squeeze all other tabs from start to end, as many as width allows
134         Iterator<MagnoliaTabLabel> it = tabLabels.iterator();
135         boolean outOfSpace = false;
136         while (it.hasNext()) {
137             MagnoliaTabLabel tab = it.next();
138             // Active tab is already accounted for, don't hide it
139             if (tab == activeTab) {
140                 continue;
141             }
142             if (!outOfSpace) {
143                 int width = tabWidths.get(tab);
144                 int maxWidth = Collections.max(tabWidths.values());
145                 // Either I have enough space for the next tab and it's the last one
146                 if ((!it.hasNext() && availableWidth >= width)
147                         // Either I need enough space for the widest of the next tabs, plus the toggle
148                         // Why widest? The tab bar may have to accommodate it, not necessarily now, but maybe when switching.
149                         || (maxWidth + toggleWidth <= availableWidth)) {
150                     tabWidths.remove(tab);
151                     availableWidth -= width;
152                     continue;
153                 } else {
154                     outOfSpace = true;
155                 }
156             }
157             // If we're there we've run out of space
158             tab.setVisible(false);
159         }
160 
161         // 4. Compute content of the hidden tabs popup
162         hiddenTabsPopup.hide();
163         hiddenTabsPopup.menubar.clearItems();
164         it = tabLabels.iterator();
165         while (it.hasNext()) {
166             MagnoliaTabLabel tab = it.next();
167             if (!tab.isVisible()) {
168                 hiddenTabsPopup.addTabLabel(tab);
169             }
170         }
171         hiddenTabsPopup.showControlIfNeeded();
172     }
173 
174     private void bindHandlers() {
175         eventBus.addHandler(ActiveTabChangedEvent.TYPE, new ActiveTabChangedEvent.Handler() {
176             @Override
177             public void onActiveTabChanged(final ActiveTabChangedEvent event) {
178                 if (event.getTab() == null) return;
179                 final MagnoliaTabWidget tab = event.getTab();
180                 final MagnoliaTabLabel label = tab.getLabel();
181                 if (label != null) {
182                     for (final MagnoliaTabLabel tabLabel : tabLabels) {
183                         tabLabel.removeStyleName("active");
184                     }
185                     label.addStyleName("active");
186                     showAll(false);
187                     activeTab = label;
188                     hiddenTabsPopup.menuWrapper.hide();
189                     if (!label.isVisible()) {
190                         reArrangeTabVisibility();
191                     }
192                 }
193             }
194         });
195 
196         eventBus.addHandler(TabCloseEvent.TYPE, new TabCloseEventHandler() {
197             @Override
198             public void onTabClosed(TabCloseEvent event) {
199                 final MagnoliaTabLabel tabLabel = event.getTab().getLabel();
200                 boolean wasActive = tabLabel.getStyleName().contains("active");
201                 if (wasActive) {
202                     final MagnoliaTabLabel nextLabel = getNextLabel(tabLabel);
203                     if (nextLabel != null) {
204                         nextLabel.addStyleName("active");
205                         activeTab = nextLabel;
206                     }
207                 }
208                 tabLabels.remove(tabLabel);
209                 remove(tabLabel);
210                 updateSingleTabStyle();
211                 reArrangeTabVisibility();
212             }
213         });
214 
215         eventBus.addHandler(ShowAllTabsEvent.TYPE, new ShowAllTabsHandler() {
216 
217             @Override
218             public void onShowAllTabs(ShowAllTabsEvent event) {
219                 for (final MagnoliaTabLabel tabLabel : tabLabels) {
220                     tabLabel.removeStyleName("active");
221                 }
222                 showAll(true);
223             }
224         });
225     }
226 
227     protected MagnoliaTabLabelnt/tabsheet/tab/widget/MagnoliaTabLabel.html#MagnoliaTabLabel">MagnoliaTabLabel getNextLabel(final MagnoliaTabLabel label) {
228         return CollectionUtil.getNext(tabLabels, label);
229     }
230 
231     public void addTabLabel(MagnoliaTabLabel label) {
232         label.setEventBus(eventBus);
233         if (!tabLabels.contains(label)) {
234             tabLabels.add(label);
235             // Keep hidden-tabs toggle and show-all button last in the DOM when inserting labels.
236             insert(label, tabContainer, tabLabels.size() - 1, true);
237             updateSingleTabStyle();
238         }
239     }
240 
241     public void updateSingleTabStyle() {
242         if (tabLabels.size() <= 1) {
243             tabContainer.addClassName(SINGLE_TAB_CLASS_NAME);
244         } else {
245             tabContainer.removeClassName(SINGLE_TAB_CLASS_NAME);
246         }
247     }
248 
249     public void addShowAllTab(boolean showAll, String label) {
250         if (showAll && showAllTab == null) {
251             showAllTab = new VShellShowAllTabLabel(label);
252             add(showAllTab, getElement());
253         } else if (!showAll && showAllTab != null) {
254             remove(showAllTab);
255             showAllTab = null;
256         }
257         reArrangeTabVisibility();
258     }
259 
260     private class VShellShowAllTabLabel extends SimplePanel {
261 
262         private final VButton textWrapper = new VButton();
263 
264         public VShellShowAllTabLabel(String label) {
265             super(DOM.createElement("li"));
266             addStyleName("show-all");
267             textWrapper.getElement().setInnerHTML(label);
268             textWrapper.getElement().setClassName("tab-title");
269             this.add(textWrapper);
270         }
271 
272         @Override
273         protected void onLoad() {
274             super.onLoad();
275             bindHandlers();
276         }
277 
278         private void bindHandlers() {
279             addDomHandler(new ClickHandler() {
280                 @Override
281                 public void onClick(ClickEvent event) {
282                     onClickGeneric(event.getNativeEvent());
283                 }
284             }, ClickEvent.getType());
285 
286             textWrapper.addClickHandler(new ClickHandler() {
287                 @Override
288                 public void onClick(ClickEvent event) {
289                     textWrapper.setFocus(false);
290                     onClickGeneric(event.getNativeEvent());
291                 }
292             });
293         }
294 
295         private void onClickGeneric(NativeEvent nativeEvent) {
296             eventBus.fireEvent(new ShowAllTabsEvent());
297             nativeEvent.stopPropagation();
298         }
299 
300     }
301 
302     /**
303      * The toggle display the hidden-tabs popup upon click.
304      */
305     static class HiddenTabsPopup extends Widget {
306 
307         private final DialogBox menuWrapper = new DialogBox(true);
308         private final HiddenTabsMenuBar menubar = new HiddenTabsMenuBar();
309         private final EventBus eventBus;
310 
311         public HiddenTabsPopup(EventBus eventBus) {
312             this.eventBus = eventBus;
313 
314             setElement(DOM.createElement("li"));
315             addStyleName("icon-arrow2_e");
316             addStyleName("hidden-tabs-popup-button");
317             menuWrapper.add(menubar);
318             menuWrapper.setStyleName("context-menu-wrapper");
319 
320             // Initially hide the component
321             setVisible(false);
322         }
323 
324         @Override
325         protected void onLoad() {
326             super.onLoad();
327             bindHandlers();
328         }
329 
330         public void addTabLabel(final MagnoliaTabLabel label) {
331             final MenuItem item = menubar.addItem(label.getCaption(), new ScheduledCommand() {
332                 @Override
333                 public void execute() {
334                     menuWrapper.hide();
335                     eventBus.fireEvent(new ActiveTabChangedEvent(label.getTab()));
336                 }
337             });
338             item.addStyleName("menu-item");
339         }
340 
341         private void bindHandlers() {
342             addDomHandler(new ClickHandler() {
343                 @Override
344                 public void onClick(ClickEvent event) {
345                     event.preventDefault();
346                     event.stopPropagation();
347                     menuWrapper.setPopupPosition(getAbsoluteLeft() + getOffsetWidth(), getAbsoluteTop());
348                     menuWrapper.show();
349                 }
350 
351             }, ClickEvent.getType());
352         }
353 
354         public void hide() {
355             menuWrapper.hide();
356             setVisible(false);
357         }
358 
359         public void showControlIfNeeded() {
360             setVisible(!menubar.isEmpty());
361         }
362 
363         private class HiddenTabsMenuBar extends MenuBar {
364 
365             public HiddenTabsMenuBar() {
366                 super(true);
367                 setStyleName("context-menu");
368                 addStyleName("hidden-tabs-menu");
369             }
370 
371             public boolean isEmpty() {
372                 return super.getItems().isEmpty();
373             }
374         }
375     }
376 
377     public void showAll(boolean showAll) {
378         if (showAllTab != null) {
379             if (showAll) {
380                 showAllTab.addStyleName("active");
381             } else {
382                 if (showAllTab.getStyleName().contains("active")) {
383                     showAllTab.removeStyleName("active");
384                 }
385             }
386         }
387     }
388 
389 }