/** * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.waveprotocol.wave.client.widget.overflowpanel; import org.waveprotocol.wave.client.scheduler.Scheduler.Priority; import org.waveprotocol.wave.client.scheduler.Scheduler.Task; import org.waveprotocol.wave.client.scheduler.SchedulerInstance; /** * Decorator which can be attached to an OverflowPanel, and makes sure that overflowing widgets * are added to a dropdown. * * @author patcoleman@google.com (Pat Coleman) */ public class OverflowPanelUpdater { /** * Required interface for anything this decorator is to control. */ public interface OverflowPanel { /** * Called at the start/end of the calculation to reset state. * Note that other method calls should occur between these two, in particular isVisible * and hasOverflowed are only required to be valid between calls. */ void onBeginOverflowLayout(); void onEndOverflowLayout(); /** * Shows the more button. On {@link OverflowPanelUpdater#updateState} this * will be called (if at all) before moving any items to the overflow * bucket. */ void showMoreButton(); /** Moves an indexed widget into the overflow bucket. */ void moveToOverflowBucket(int index); /** Checks whether an indexed widget on the panel would be visible (if not overflowing). */ boolean isVisible(int index); /** Checks whether an indexed widget on the panel has overflowed. */ boolean hasOverflowed(int index); /** Gets the number of widgets in the panel. */ int getWidgetCount(); } /** Panel this decorates */ private final OverflowPanel panel; /** Task for updating state of toolbar buttons */ private final Task stateUpdater = new Task() { @Override public void execute() { updateState(); } }; /** * Create the decorator to wrap an overflow panel. * @param panel The overflow-aware panel this decorator wraps */ public OverflowPanelUpdater(OverflowPanel panel) { this.panel = panel; } /** * Updates the overflow state - runs backwards through the contained widgets until a visible one * is found on the top row, then moves everything after it into the overflow bucket. */ public void updateState() { panel.onBeginOverflowLayout(); // run backwards through the items until we find one on the top line int numChildren = panel.getWidgetCount(); int lastOnTop; boolean itemHasOverflowed = false; for (lastOnTop = numChildren - 1; lastOnTop >= 0; lastOnTop--) { if (!panel.isVisible(lastOnTop)) { continue; // skip invisible widgets } if (!panel.hasOverflowed(lastOnTop)) { break; // stop once we hit widgets that haven't overflowed. } itemHasOverflowed = true; } // Show the button if any buttons have overflowed; this will change the // layout, so continue calculating which buttons have overflowed. if (itemHasOverflowed) { panel.showMoreButton(); } for (; lastOnTop >= 0; lastOnTop--) { if (!panel.isVisible(lastOnTop)) { continue; } if (!panel.hasOverflowed(lastOnTop)) { break; } } // Move all the overflowed buttons to the overflow panel. while (++lastOnTop < numChildren) { panel.moveToOverflowBucket(lastOnTop); } panel.onEndOverflowLayout(); } /** * Schedules a deferred call to updateState, if such a call is not already scheduled. This update * is deferred because it is dependent on layout, which depends on StyleInjector, which defers * things by default. */ public void updateStateEventually() { if (!SchedulerInstance.get().isScheduled(stateUpdater)) { SchedulerInstance.get().schedule(Priority.LOW, stateUpdater); } } }