Java tutorial
/** * This file is part of pwt. * * pwt is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * pwt is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser * General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with pwt. If not, * see <http://www.gnu.org/licenses/>. */ package fr.putnami.pwt.core.widget.client; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Node; import com.google.gwt.event.dom.client.ScrollEvent; import com.google.gwt.event.dom.client.ScrollHandler; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.IsWidget; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.Widget; import java.util.List; import java.util.Map; import fr.putnami.pwt.core.model.client.base.HasDrawable; import fr.putnami.pwt.core.theme.client.CssStyle; import fr.putnami.pwt.core.widget.client.base.AbstractComposite; import fr.putnami.pwt.core.widget.client.base.SimpleStyle; import fr.putnami.pwt.core.widget.client.util.StyleUtils; public class NavSpy extends AbstractComposite implements HasDrawable { private static final CssStyle STYLE_NAV_SPY = new SimpleStyle("nav-spy"); private class NavWidget extends NavLink implements ScheduledCommand { private final NavWidget parentNav; private final int level; private Nav subNavContainer; NavWidget(NavWidget parentNav, int level) { this.parentNav = parentNav; this.level = level; } NavWidget(NavWidget parentNav, final Element heading) { super(heading.getInnerHTML(), new ScheduledCommand() { @Override public void execute() { int top = NavSpy.this.getElementTop(heading) - NavSpy.this.spyOffset; if (NavSpy.this.isBodyScrollWidget()) { Window.scrollTo(Document.get().getScrollLeft(), top); } else { NavSpy.this.scrollWidget.getElement().setScrollTop(top); } } }); this.parentNav = parentNav; this.level = NavSpy.this.getLevel(heading); } private NavWidget addToSubNav(NavWidget subNav) { this.getSubNavContainer().append(subNav); return subNav; } public Nav getSubNavContainer() { if (this.subNavContainer == null) { this.subNavContainer = new Nav(); this.append(this.subNavContainer); } return this.subNavContainer; } @Override public void execute() { // NoOp } } private final SimplePanel content = new SimplePanel(); private final List<Element> headings = Lists.newArrayList(); private final Map<Element, NavWidget> navs = Maps.newHashMap(); private boolean refreshing = false; private HandlerRegistration scrollRegistration; private Widget scrollWidget; private int spyOffset; private Widget headingContainer; private String spyName; private final RepeatingCommand refreshCommand = new RepeatingCommand() { @Override public boolean execute() { NavSpy.this.refreshActive(); return false; } }; public NavSpy() { this.initWidget(this.content); StyleUtils.addStyle(this, NavSpy.STYLE_NAV_SPY); } protected NavSpy(NavSpy source) { super(source); this.initWidget(this.content); } @Override public IsWidget cloneWidget() { return new NavSpy(this); } @Override protected void onLoad() { super.onLoad(); this.registerScrollHandler(); } @Override protected void onUnload() { super.onUnload(); if (this.scrollRegistration != null) { this.scrollRegistration.removeHandler(); } } public Widget getHeadingContainer() { return this.headingContainer; } public void setHeadingContainer(Widget target) { this.headingContainer = target; } public String getSpyName() { return this.spyName; } public void setSpyName(String spyName) { this.spyName = spyName; } public void setScrollWidget(IsWidget scrollWidget) { if (scrollWidget != null) { this.scrollWidget = scrollWidget.asWidget(); } else { this.scrollWidget = null; } this.registerScrollHandler(); } public void setSpyOffset(int spyOffset) { this.spyOffset = spyOffset; } @Override public void redraw() { if (this.headingContainer == null) { this.headingContainer = RootPanel.get(); } this.headings.clear(); this.collectHeadings(this.headingContainer.getElement(), this.headings); int lowestNavLevel = 6; this.navs.clear(); this.content.clear(); for (Element heading : this.headings) { int level = this.getLevel(heading); if (level < lowestNavLevel) { lowestNavLevel = level; } } NavWidget currentNav = new NavWidget(null, lowestNavLevel - 1); this.content.add(currentNav.getSubNavContainer()); for (Element heading : this.headings) { int level = this.getLevel(heading); while (currentNav.level >= level && currentNav.parentNav != null) { currentNav = currentNav.parentNav; } if (currentNav.level < level - 1) { for (int i = currentNav.level; i < level; i++) { NavWidget newNav = new NavWidget(currentNav, i); currentNav.append(newNav); currentNav = newNav; } } currentNav = currentNav.addToSubNav(new NavWidget(currentNav, heading)); this.navs.put(heading, currentNav); } } private void registerScrollHandler() { if (this.scrollRegistration != null) { this.scrollRegistration.removeHandler(); } if (this.isBodyScrollWidget()) { this.scrollRegistration = Window.addWindowScrollHandler(new Window.ScrollHandler() { @Override public void onWindowScroll(Window.ScrollEvent event) { NavSpy.this.scheduleRefresh(); } }); } else { this.scrollRegistration = this.scrollWidget.addDomHandler(new ScrollHandler() { @Override public void onScroll(ScrollEvent event) { NavSpy.this.scheduleRefresh(); } }, ScrollEvent.getType()); } } private void scheduleRefresh() { if (!this.refreshing) { this.refreshing = true; Scheduler.get().scheduleFixedDelay(this.refreshCommand, 250); } } private int getElementTop(Element heading) { if (this.isBodyScrollWidget()) { return heading.getAbsoluteTop(); } return heading.getOffsetTop() - this.scrollWidget.getElement().getOffsetTop(); } private boolean isBodyScrollWidget() { return this.scrollWidget == null; } private void refreshActive() { if (this.navs.isEmpty()) { // Not displayed NavSpy return; } int scrollTop, scrollHeight, maxScroll; if (this.isBodyScrollWidget()) { scrollTop = Document.get().getScrollTop() + this.spyOffset; scrollHeight = Document.get().getScrollHeight(); maxScroll = scrollHeight - Document.get().getClientHeight(); } else { scrollTop = this.scrollWidget.getElement().getScrollTop() + this.spyOffset; scrollHeight = this.scrollWidget.getElement().getScrollHeight(); maxScroll = scrollHeight - this.scrollWidget.getElement().getClientHeight(); } Element activeHeading = null; if (scrollTop >= maxScroll && !this.headings.isEmpty()) { activeHeading = this.headings.get(this.headings.size() - 1); } else { for (Element heading : this.headings) { int top = this.getElementTop(heading) - scrollTop; if (activeHeading == null || top <= 0) { activeHeading = heading; } if (top > 0) { break; } } } for (NavWidget nav : this.navs.values()) { nav.setActive(false); } if (activeHeading != null) { NavWidget navActive = this.navs.get(activeHeading); navActive.setActive(true); while (navActive.parentNav != null) { navActive = navActive.parentNav; navActive.setActive(true); } } this.refreshing = false; } private int getLevel(Element element) { return Heading.HEADING_TAGS.indexOf(element.getTagName().toLowerCase()) + 1; } private void collectHeadings(Element element, List<Element> headings) { for (int i = 0; i < element.getChildCount(); i++) { Node node = element.getChild(i); if (node instanceof Element) { Element child = (Element) node; String tagName = child.getTagName(); if (tagName != null && Heading.HEADING_TAGS.contains(tagName.toLowerCase())) { if (this.spyName != null && this.spyName.equals(child.getAttribute(Heading.ATTRIBUTE_DATA_SUMMARY))) { headings.add(child); } } else { this.collectHeadings(child, headings); } } } } }