<commit_before>/*
 * DocTabLayoutPanel.java
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
package org.rstudio.core.client.theme;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.*;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Float;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.*;

import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.dom.DomUtils.NodePredicate;
import org.rstudio.core.client.events.*;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.theme.res.ThemeStyles;


/**
 * A tab panel that is styled for document tabs.
 */
public class DocTabLayoutPanel
      extends TabLayoutPanel
      implements HasTabClosingHandlers,
                 HasTabCloseHandlers,
                 HasTabClosedHandlers,
                 HasTabReorderHandlers
{
   public interface TabCloseObserver
   {
      public void onTabClose();
   }
   
   public interface TabMoveObserver
   {
      public void onTabMove(Widget tab, int oldPos, int newPos);
   }

   public DocTabLayoutPanel(boolean closeableTabs,
                            int padding,
                            int rightMargin)
   {
      super(BAR_HEIGHT, Style.Unit.PX);
      closeableTabs_ = closeableTabs;
      padding_ = padding;
      rightMargin_ = rightMargin;
      styles_ = ThemeResources.INSTANCE.themeStyles();
      addStyleName(styles_.docTabPanel());
      addStyleName(styles_.moduleTabPanel());
   }

   @Override
   public void add(final Widget child, String text)
   {
      add(child, null, text, null);
   }
   
   public void add(final Widget child,
                   ImageResource icon,
                   final String text,
                   String tooltip)
   {
      if (closeableTabs_)
      {
         DocTab tab = new DocTab(icon, text, tooltip, 
         new TabCloseObserver()
         {
            public void onTabClose()
            {
               int index = getWidgetIndex(child);
               if (index >= 0)
               {
                  tryCloseTab(index, null);
               }
            }
         }, 
         new TabMoveObserver()
         {
            @Override
            public void onTabMove(Widget tab, int oldPos, int newPos)
            {
               TabReorderEvent event = new TabReorderEvent(oldPos, newPos);
               fireEvent(event);
            }
         });
         super.add(child, tab);
      }
      else
      {
         super.add(child, text);
      }
   }

   public boolean tryCloseTab(int index, Command onClosed)
   {
      TabClosingEvent event = new TabClosingEvent(index);
      fireEvent(event);
      if (event.isCancelled())
         return false;

      closeTab(index, onClosed);
      return true;
   }

   public void closeTab(int index, Command onClosed)
   {
      if (remove(index))
      {
         if (onClosed != null)
            onClosed.execute();
      }
   }

   @Override
   public void selectTab(int index)
   {
      super.selectTab(index);
      ensureSelectedTabIsVisible(true);
   }

   public void ensureSelectedTabIsVisible(boolean animate)
   {
      if (currentAnimation_ != null)
      {
         currentAnimation_.cancel();
         currentAnimation_ = null;
      }

      Element selectedTab = (Element) DomUtils.findNode(
            getElement(),
            true,
            false,
            new NodePredicate()
            {
               public boolean test(Node n)
               {
                  if (n.getNodeType() != Node.ELEMENT_NODE)
                     return false;
                  return ((Element) n).getClassName()
                        .contains("gwt-TabLayoutPanelTab-selected");
               }
            });
      if (selectedTab == null)
      {
         return;
      }
      selectedTab = selectedTab.getFirstChildElement()
                               .getFirstChildElement();

      Element tabBar = getTabBarElement();

      if (!isVisible() || !isAttached() || tabBar.getOffsetWidth() == 0)
         return; // not yet loaded

      final Element tabBarParent = tabBar.getParentElement();

      final int start = tabBarParent.getScrollLeft();
      int end = DomUtils.ensureVisibleHoriz(tabBarParent,
                                                  selectedTab,
                                                  padding_,
                                                  padding_ + rightMargin_,
                                                  true);

      // When tabs are closed, the overall width shrinks, and this can lead
      // to cases where there's too much empty space on the screen
      Node lastTab = getLastChildElement(tabBar);
      if (lastTab == null || lastTab.getNodeType() != Node.ELEMENT_NODE)
         return;
      int edge = DomUtils.getRelativePosition(tabBarParent, Element.as(lastTab)).x 
            + Element.as(lastTab).getOffsetWidth();
      end = Math.min(end,
                     Math.max(0,
                              edge - (tabBarParent.getOffsetWidth() - rightMargin_)));

      if (edge <= tabBarParent.getOffsetWidth() - rightMargin_)
         end = 0;

      if (start != end)
      {
         if (!animate)
         {
            tabBarParent.setScrollLeft(end);
         }
         else
         {
            final int finalEnd = end;
            currentAnimation_ = new Animation() {
               @Override
               protected void onUpdate(double progress)
               {
                  double delta = (finalEnd - start) * progress;
                  tabBarParent.setScrollLeft((int) (start + delta));
               }

               @Override
               protected void onComplete()
               {
                  if (this == currentAnimation_)
                  {
                     tabBarParent.setScrollLeft(finalEnd);
                     currentAnimation_ = null;
                  }
               }
            };
            currentAnimation_.run(Math.max(200,
                                           Math.min(1500,
                                                    Math.abs(end - start)*2)));
         }
      }
   }

   @Override
   public void onResize()
   {
      super.onResize();
      ensureSelectedTabIsVisible(false);
   }

  
   @Override
   public boolean remove(int index)
   {
      if ((index < 0) || (index >= getWidgetCount())) {
        return false;
      }

      fireEvent(new TabCloseEvent(index));

      if (getSelectedIndex() == index)
      {
         boolean closingLastTab = index == getWidgetCount() - 1;
         int indexToSelect = closingLastTab
                             ? index - 1
                             : index + 1;
         if (indexToSelect >= 0)
            selectTab(indexToSelect);
      }

      if (!super.remove(index))
         return false;

      fireEvent(new TabClosedEvent(index));
      ensureSelectedTabIsVisible(true);
      return true;
   }

   @Override
   public void add(Widget child, String text, boolean asHtml)
   {
      if (asHtml)
         throw new UnsupportedOperationException("HTML tab names not supported");

      add(child, text);
   }

   @Override
   public void add(Widget child, Widget tab)
   {
      throw new UnsupportedOperationException("Not supported");
   }
   
   private class DocTab extends Composite
   {

      private DocTab(ImageResource icon,
                     String title,
                     String tooltip,
                     TabCloseObserver closeHandler,
                     TabMoveObserver moveHandler)
      {
         HorizontalPanel layoutPanel = new HorizontalPanel();
         layoutPanel.setStylePrimaryName(styles_.tabLayout());
         layoutPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_BOTTOM);

         HTML left = new HTML();
         left.setStylePrimaryName(styles_.tabLayoutLeft());
         layoutPanel.add(left);

         contentPanel_ = new HorizontalPanel();
         contentPanel_.setTitle(tooltip);
         contentPanel_.setStylePrimaryName(styles_.tabLayoutCenter());
         contentPanel_.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);

         if (icon != null)
            contentPanel_.add(imageForIcon(icon));

         label_ = new Label(title, false);
         label_.addStyleName(styles_.docTabLabel());
         contentPanel_.add(label_);

         appendDirtyMarker();

         Image img = new Image(ThemeResources.INSTANCE.closeTab());
         img.setStylePrimaryName(styles_.closeTabButton());
         img.addStyleName(ThemeStyles.INSTANCE.handCursor());
         contentPanel_.add(img);

         layoutPanel.add(contentPanel_);

         HTML right = new HTML();
         right.setStylePrimaryName(styles_.tabLayoutRight());
         layoutPanel.add(right);

         initWidget(layoutPanel);

         this.sinkEvents(Event.ONMOUSEDOWN | 
                        Event.ONMOUSEMOVE | 
                        Event.ONMOUSEUP |
                        Event.ONLOSECAPTURE);
         closeHandler_ = closeHandler;
         moveHandler_ = moveHandler;
         closeElement_ = img.getElement();
      }
      
      private void appendDirtyMarker()
      {
         Label marker = new Label("*");
         marker.setStyleName(styles_.dirtyTabIndicator());
         contentPanel_.insert(marker, 2);
      }

      public void replaceTitle(String title)
      {
         label_.setText(title);
      }

      public void replaceTooltip(String tooltip)
      {
         contentPanel_.setTitle(tooltip);
      }

      public void replaceIcon(ImageResource icon)
      {
         if (contentPanel_.getWidget(0) instanceof Image)
            contentPanel_.remove(0);
         contentPanel_.insert(imageForIcon(icon), 0);
      }
      
      private Image imageForIcon(ImageResource icon)
      {
         Image image = new Image(icon);
         image.setStylePrimaryName(styles_.docTabIcon());
         return image;
      }

      @Override
      public void onBrowserEvent(Event event) 
      {  
         switch(DOM.eventGetType(event))
         {
            case Event.ONMOUSEDOWN: 
            {
               // handle mousedowns as drag initiations (unless user is reaching
               // for the close button)
               if (event.getButton() == Event.BUTTON_LEFT && 
                     Element.as(event.getEventTarget()) != closeElement_)
               {
                  beginDrag(event);
                  event.preventDefault();
                  event.stopPropagation();
               }
               break;
            }
           
            case Event.ONMOUSEMOVE: 
            {
               if (dragging_) 
               {
                  drag(event);
                  event.preventDefault();
                  event.stopPropagation();
               }
               break;
            }
           
            case Event.ONMOUSEUP:
            {
               // middlemouse should close a tab
               if (event.getButton() == Event.BUTTON_MIDDLE)
               {
                  event.stopPropagation();
                  event.preventDefault();
                  closeHandler_.onTabClose();
                  break;
               }
               
               // note: fallthrough
            }
            case Event.ONLOSECAPTURE: 
            {
               if (Element.as(event.getEventTarget()) == closeElement_)
               {
                  // handle click on close button
                  closeHandler_.onTabClose();
               }
               else if (dragging_)
               {
                  // complete dragging
                  endDrag(event);
               }
               event.preventDefault();
               event.stopPropagation();
               break;   
            }
         }
         super.onBrowserEvent(event);
      }

      private void beginDrag(Event evt)
      {
         // set drag element state
         dragging_ = true;
         dragElement_ = getElement().getParentElement().getParentElement();
         dragTabsHost_ = dragElement_.getParentElement();
         dragScrollHost_ = dragTabsHost_.getParentElement();
         outOfBounds_ = 0;
         candidatePos_ = 0;

         // find the current position of this tab among its siblings--we'll use
         // this later to determine whether to shift siblings left or right
         for (int i = 0; i < dragTabsHost_.getChildCount(); i++)
         {
            Node node = dragTabsHost_.getChild(i);
            if (node.getNodeType() == Node.ELEMENT_NODE)
            {
               if (Element.as(node) == dragElement_)
               {
                  candidatePos_ = i;
                  destPos_ = i;
               }
               // the relative position of the last node determines how far we
               // can drag--add 10px so it stretches a little
               dragMax_ = DomUtils.leftRelativeTo(dragTabsHost_, Element.as(node)) 
                     + (Element.as(node).getClientWidth() - dragElement_.getClientWidth())
                     + 10;
            }
         }
         startPos_ = candidatePos_;

         // snap the element out of the tabset
         lastElementX_ = DomUtils.leftRelativeTo(dragTabsHost_, dragElement_);
         lastCursorX_= evt.getClientX();
         dragElement_.getStyle().setPosition(Position.ABSOLUTE);
         dragElement_.getStyle().setLeft(lastElementX_, Unit.PX);
         dragElement_.getStyle().setZIndex(100);
         DOM.setCapture(getElement());

         // create the placeholder that shows where this tab will go when the
         // mouse is released
         dragPlaceholder_ = Document.get().createDivElement();
         dragPlaceholder_.getStyle().setWidth(dragElement_.getClientWidth(), 
               Unit.PX);
         dragPlaceholder_.getStyle().setHeight(2, Unit.PX);
         dragPlaceholder_.getStyle().setDisplay(Display.INLINE_BLOCK);
         dragPlaceholder_.getStyle().setPosition(Position.RELATIVE);
         dragPlaceholder_.getStyle().setFloat(Float.LEFT);
         dragTabsHost_.insertAfter(dragPlaceholder_, dragElement_);
      }
      
      private void endDrag(final Event evt)
      {
         // remove the properties used to position for dragging
         dragElement_.getStyle().clearLeft();
         dragElement_.getStyle().clearPosition();
         dragElement_.getStyle().clearZIndex();
         
         // insert this tab where the placeholder landed
         dragTabsHost_.removeChild(dragElement_);
         dragTabsHost_.insertAfter(dragElement_, dragPlaceholder_);
         dragTabsHost_.removeChild(dragPlaceholder_);

         // finish dragging
         DOM.releaseCapture(getElement());
         dragging_ = false;
         
         // unsink mousedown/mouseup and simulate a click to activate the tab
         simulateClick(evt);
         
         // let observer know we moved; adjust the destination position one to
         // the left if we're right of the start position to account for the
         // position of the tab prior to movement
         if (startPos_ != destPos_)
         {
            moveHandler_.onTabMove(this, startPos_, destPos_);
         }
      }
      
      private void simulateClick(Event evt)
      {
         this.unsinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP);
         DomEvent.fireNativeEvent(Document.get().createClickEvent(0,
               evt.getScreenX(), evt.getScreenY(), 
               evt.getClientX(), evt.getClientY(), 
               evt.getCtrlKey(), evt.getAltKey(), 
               evt.getShiftKey(), evt.getMetaKey()), this.getParent());
         this.sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP);
         
      }
      
      private boolean autoScroll(int dir)
      {
         // move while the mouse is still out of bounds 
         if (dragging_ && outOfBounds_ != 0)
         {
            // if mouse is far out of bounds, use it to accelerate movement
            if (Math.abs(outOfBounds_) > 100)
            {
               dir *= 1 + Math.round(Math.abs(outOfBounds_) / 75);
            }
            // move if there's moving to be done
            if (dir < 0 && lastElementX_ > 0 ||
                dir > 0 && lastElementX_ < dragMax_)
            {
               commitPosition(lastElementX_ + dir);

               // scroll if there's scrolling to be done
               int left = dragScrollHost_.getScrollLeft();
               if ((dir < 0 && left > 0) || 
                   (dir > 0 && left < dragScrollHost_.getScrollWidth() - 
                        dragScrollHost_.getClientWidth()))
               {
                  dragScrollHost_.setScrollLeft(left + dir);
               }
            }
            return true;
         }
         return false;
      }
      
      private void drag(Event evt) 
      {
         int offset = evt.getClientX() - lastCursorX_;
         lastCursorX_ = evt.getClientX();
         // cursor is outside the tab area
         if (outOfBounds_ != 0)
         {
            // did the cursor move back in bounds? 
            if (outOfBounds_ + offset > 0 != outOfBounds_ > 0)
            {
               outOfBounds_ = 0;
               offset = outOfBounds_ + offset;
            }
            else <commit_msg>add html5 drag event handlers to document tab and its host<commit_after>/*
 * DocTabLayoutPanel.java
 *
 * Copyright (C) 2009-12 by RStudio, Inc.
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */
package org.rstudio.core.client.theme;

import com.google.gwt.animation.client.Animation;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.*;
import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.dom.client.Style.Float;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.DomEvent;
import com.google.gwt.event.dom.client.DragEvent;
import com.google.gwt.event.dom.client.DragHandler;
import com.google.gwt.event.dom.client.DragStartEvent;
import com.google.gwt.event.dom.client.DragStartHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.*;

import org.rstudio.core.client.Debug;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.dom.DomUtils.NodePredicate;
import org.rstudio.core.client.events.*;
import org.rstudio.core.client.js.JsObject;
import org.rstudio.core.client.theme.res.ThemeResources;
import org.rstudio.core.client.theme.res.ThemeStyles;


/**
 * A tab panel that is styled for document tabs.
 */
public class DocTabLayoutPanel
      extends TabLayoutPanel
      implements HasTabClosingHandlers,
                 HasTabCloseHandlers,
                 HasTabClosedHandlers,
                 HasTabReorderHandlers
{
   public interface TabCloseObserver
   {
      public void onTabClose();
   }
   
   public interface TabMoveObserver
   {
      public void onTabMove(Widget tab, int oldPos, int newPos);
   }

   public DocTabLayoutPanel(boolean closeableTabs,
                            int padding,
                            int rightMargin)
   {
      super(BAR_HEIGHT, Style.Unit.PX);
      closeableTabs_ = closeableTabs;
      padding_ = padding;
      rightMargin_ = rightMargin;
      styles_ = ThemeResources.INSTANCE.themeStyles();
      addStyleName(styles_.docTabPanel());
      addStyleName(styles_.moduleTabPanel());

      addDomHandler(new DragStartHandler()
      {
         
         @Override
         public void onDragStart(DragStartEvent arg0)
         {
            Debug.devlog("drag start - panel");
            
         }
      }, DragStartEvent.getType());

      addDomHandler(new DragHandler()
      {
         
         @Override
         public void onDrag(DragEvent arg0)
         {
            Debug.devlog("dragging - " + arg0.getNativeEvent().getClientX());
         }
      }, DragEvent.getType());
   }

   @Override
   public void add(final Widget child, String text)
   {
      add(child, null, text, null);
   }
   
   public void add(final Widget child,
                   ImageResource icon,
                   final String text,
                   String tooltip)
   {
      if (closeableTabs_)
      {
         DocTab tab = new DocTab(icon, text, tooltip, 
         new TabCloseObserver()
         {
            public void onTabClose()
            {
               int index = getWidgetIndex(child);
               if (index >= 0)
               {
                  tryCloseTab(index, null);
               }
            }
         }, 
         new TabMoveObserver()
         {
            @Override
            public void onTabMove(Widget tab, int oldPos, int newPos)
            {
               TabReorderEvent event = new TabReorderEvent(oldPos, newPos);
               fireEvent(event);
            }
         });
         super.add(child, tab);
      }
      else
      {
         super.add(child, text);
      }
   }

   public boolean tryCloseTab(int index, Command onClosed)
   {
      TabClosingEvent event = new TabClosingEvent(index);
      fireEvent(event);
      if (event.isCancelled())
         return false;

      closeTab(index, onClosed);
      return true;
   }

   public void closeTab(int index, Command onClosed)
   {
      if (remove(index))
      {
         if (onClosed != null)
            onClosed.execute();
      }
   }

   @Override
   public void selectTab(int index)
   {
      super.selectTab(index);
      ensureSelectedTabIsVisible(true);
   }

   public void ensureSelectedTabIsVisible(boolean animate)
   {
      if (currentAnimation_ != null)
      {
         currentAnimation_.cancel();
         currentAnimation_ = null;
      }

      Element selectedTab = (Element) DomUtils.findNode(
            getElement(),
            true,
            false,
            new NodePredicate()
            {
               public boolean test(Node n)
               {
                  if (n.getNodeType() != Node.ELEMENT_NODE)
                     return false;
                  return ((Element) n).getClassName()
                        .contains("gwt-TabLayoutPanelTab-selected");
               }
            });
      if (selectedTab == null)
      {
         return;
      }
      selectedTab = selectedTab.getFirstChildElement()
                               .getFirstChildElement();

      Element tabBar = getTabBarElement();

      if (!isVisible() || !isAttached() || tabBar.getOffsetWidth() == 0)
         return; // not yet loaded

      final Element tabBarParent = tabBar.getParentElement();

      final int start = tabBarParent.getScrollLeft();
      int end = DomUtils.ensureVisibleHoriz(tabBarParent,
                                                  selectedTab,
                                                  padding_,
                                                  padding_ + rightMargin_,
                                                  true);

      // When tabs are closed, the overall width shrinks, and this can lead
      // to cases where there's too much empty space on the screen
      Node lastTab = getLastChildElement(tabBar);
      if (lastTab == null || lastTab.getNodeType() != Node.ELEMENT_NODE)
         return;
      int edge = DomUtils.getRelativePosition(tabBarParent, Element.as(lastTab)).x 
            + Element.as(lastTab).getOffsetWidth();
      end = Math.min(end,
                     Math.max(0,
                              edge - (tabBarParent.getOffsetWidth() - rightMargin_)));

      if (edge <= tabBarParent.getOffsetWidth() - rightMargin_)
         end = 0;

      if (start != end)
      {
         if (!animate)
         {
            tabBarParent.setScrollLeft(end);
         }
         else
         {
            final int finalEnd = end;
            currentAnimation_ = new Animation() {
               @Override
               protected void onUpdate(double progress)
               {
                  double delta = (finalEnd - start) * progress;
                  tabBarParent.setScrollLeft((int) (start + delta));
               }

               @Override
               protected void onComplete()
               {
                  if (this == currentAnimation_)
                  {
                     tabBarParent.setScrollLeft(finalEnd);
                     currentAnimation_ = null;
                  }
               }
            };
            currentAnimation_.run(Math.max(200,
                                           Math.min(1500,
                                                    Math.abs(end - start)*2)));
         }
      }
   }

   @Override
   public void onResize()
   {
      super.onResize();
      ensureSelectedTabIsVisible(false);
   }

  
   @Override
   public boolean remove(int index)
   {
      if ((index < 0) || (index >= getWidgetCount())) {
        return false;
      }

      fireEvent(new TabCloseEvent(index));

      if (getSelectedIndex() == index)
      {
         boolean closingLastTab = index == getWidgetCount() - 1;
         int indexToSelect = closingLastTab
                             ? index - 1
                             : index + 1;
         if (indexToSelect >= 0)
            selectTab(indexToSelect);
      }

      if (!super.remove(index))
         return false;

      fireEvent(new TabClosedEvent(index));
      ensureSelectedTabIsVisible(true);
      return true;
   }

   @Override
   public void add(Widget child, String text, boolean asHtml)
   {
      if (asHtml)
         throw new UnsupportedOperationException("HTML tab names not supported");

      add(child, text);
   }

   @Override
   public void add(Widget child, Widget tab)
   {
      throw new UnsupportedOperationException("Not supported");
   }
   
   private class DocTab extends Composite
   {

      private DocTab(ImageResource icon,
                     String title,
                     String tooltip,
                     TabCloseObserver closeHandler,
                     TabMoveObserver moveHandler)
      {
         HorizontalPanel layoutPanel = new HorizontalPanel();
         layoutPanel.setStylePrimaryName(styles_.tabLayout());
         layoutPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_BOTTOM);
         layoutPanel.getElement().setDraggable("true");
         layoutPanel.addDomHandler(new DragStartHandler()
         {
            
            @Override
            public void onDragStart(DragStartEvent evt)
            {
               evt.setData("text", "foo");
               JsObject jso = (JsObject)
                     evt.getNativeEvent().getDataTransfer().cast();
               jso.setString("effectAllowed", "move");

               beginDrag(evt);
            }
         }, DragStartEvent.getType());
         layoutPanel.addDomHandler(new DragHandler()
         {
            
            @Override
            public void onDrag(DragEvent evt)
            {
               drag(evt);
            }
         }, DragEvent.getType());

         HTML left = new HTML();
         left.setStylePrimaryName(styles_.tabLayoutLeft());
         layoutPanel.add(left);

         contentPanel_ = new HorizontalPanel();
         contentPanel_.setTitle(tooltip);
         contentPanel_.setStylePrimaryName(styles_.tabLayoutCenter());
         contentPanel_.setVerticalAlignment(HasVerticalAlignment.ALIGN_MIDDLE);

         if (icon != null)
            contentPanel_.add(imageForIcon(icon));

         label_ = new Label(title, false);
         label_.addStyleName(styles_.docTabLabel());
         contentPanel_.add(label_);

         appendDirtyMarker();

         Image img = new Image(ThemeResources.INSTANCE.closeTab());
         img.setStylePrimaryName(styles_.closeTabButton());
         img.addStyleName(ThemeStyles.INSTANCE.handCursor());
         contentPanel_.add(img);

         layoutPanel.add(contentPanel_);

         HTML right = new HTML();
         right.setStylePrimaryName(styles_.tabLayoutRight());
         layoutPanel.add(right);

         initWidget(layoutPanel);

         this.sinkEvents(Event.ONMOUSEMOVE |
               Event.ONMOUSEUP |
               Event.ONLOSECAPTURE);
         closeHandler_ = closeHandler;
         moveHandler_ = moveHandler;
         closeElement_ = img.getElement();
      }
      
      private void appendDirtyMarker()
      {
         Label marker = new Label("*");
         marker.setStyleName(styles_.dirtyTabIndicator());
         contentPanel_.insert(marker, 2);
      }

      public void replaceTitle(String title)
      {
         label_.setText(title);
      }

      public void replaceTooltip(String tooltip)
      {
         contentPanel_.setTitle(tooltip);
      }

      public void replaceIcon(ImageResource icon)
      {
         if (contentPanel_.getWidget(0) instanceof Image)
            contentPanel_.remove(0);
         contentPanel_.insert(imageForIcon(icon), 0);
      }
      
      private Image imageForIcon(ImageResource icon)
      {
         Image image = new Image(icon);
         image.setStylePrimaryName(styles_.docTabIcon());
         return image;
      }

      @Override
      public void onBrowserEvent(Event event) 
      {  
         switch(DOM.eventGetType(event))
         {
            case Event.ONMOUSEUP:
            {
               // middlemouse should close a tab
               if (event.getButton() == Event.BUTTON_MIDDLE)
               {
                  event.stopPropagation();
                  event.preventDefault();
                  closeHandler_.onTabClose();
                  break;
               }
               
               // note: fallthrough
            }
            case Event.ONLOSECAPTURE: 
            {
               if (Element.as(event.getEventTarget()) == closeElement_)
               {
                  // handle click on close button
                  closeHandler_.onTabClose();
               }
               else if (dragging_)
               {
                  // complete dragging
                  endDrag(event);
               }
               event.preventDefault();
               event.stopPropagation();
               break;   
            }
         }
         super.onBrowserEvent(event);
      }

      public void beginDrag(DragStartEvent evt)
      {
         // set drag element state
         dragging_ = true;
         dragElement_ = getElement().getParentElement().getParentElement();
         dragTabsHost_ = dragElement_.getParentElement();
         dragScrollHost_ = dragTabsHost_.getParentElement();
         outOfBounds_ = 0;
         candidatePos_ = 0;

         // find the current position of this tab among its siblings--we'll use
         // this later to determine whether to shift siblings left or right
         for (int i = 0; i < dragTabsHost_.getChildCount(); i++)
         {
            Node node = dragTabsHost_.getChild(i);
            if (node.getNodeType() == Node.ELEMENT_NODE)
            {
               if (Element.as(node) == dragElement_)
               {
                  candidatePos_ = i;
                  destPos_ = i;
               }
               // the relative position of the last node determines how far we
               // can drag--add 10px so it stretches a little
               dragMax_ = DomUtils.leftRelativeTo(dragTabsHost_, Element.as(node)) 
                     + (Element.as(node).getClientWidth() - dragElement_.getClientWidth())
                     + 10;
            }
         }
         startPos_ = candidatePos_;

         // snap the element out of the tabset
         lastElementX_ = DomUtils.leftRelativeTo(dragTabsHost_, dragElement_);
         lastCursorX_= evt.getNativeEvent().getClientX();
         dragElement_.getStyle().setPosition(Position.ABSOLUTE);
         dragElement_.getStyle().setLeft(lastElementX_, Unit.PX);
         dragElement_.getStyle().setZIndex(100);
         DOM.setCapture(getElement());

         // create the placeholder that shows where this tab will go when the
         // mouse is released
         dragPlaceholder_ = Document.get().createDivElement();
         dragPlaceholder_.getStyle().setWidth(dragElement_.getClientWidth(), 
               Unit.PX);
         dragPlaceholder_.getStyle().setHeight(2, Unit.PX);
         dragPlaceholder_.getStyle().setDisplay(Display.INLINE_BLOCK);
         dragPlaceholder_.getStyle().setPosition(Position.RELATIVE);
         dragPlaceholder_.getStyle().setFloat(Float.LEFT);
         dragTabsHost_.insertAfter(dragPlaceholder_, dragElement_);
      }
      
      private void endDrag(final Event evt)
      {
         // remove the properties used to position for dragging
         dragElement_.getStyle().clearLeft();
         dragElement_.getStyle().clearPosition();
         dragElement_.getStyle().clearZIndex();
         
         // insert this tab where the placeholder landed
         dragTabsHost_.removeChild(dragElement_);
         dragTabsHost_.insertAfter(dragElement_, dragPlaceholder_);
         dragTabsHost_.removeChild(dragPlaceholder_);

         // finish dragging
         DOM.releaseCapture(getElement());
         dragging_ = false;
         
         // unsink mousedown/mouseup and simulate a click to activate the tab
         simulateClick(evt);
         
         // let observer know we moved; adjust the destination position one to
         // the left if we're right of the start position to account for the
         // position of the tab prior to movement
         if (startPos_ != destPos_)
         {
            moveHandler_.onTabMove(this, startPos_, destPos_);
         }
      }
      
      private void simulateClick(Event evt)
      {
         this.unsinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP);
         DomEvent.fireNativeEvent(Document.get().createClickEvent(0,
               evt.getScreenX(), evt.getScreenY(), 
               evt.getClientX(), evt.getClientY(), 
               evt.getCtrlKey(), evt.getAltKey(), 
               evt.getShiftKey(), evt.getMetaKey()), this.getParent());
         this.sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEUP);
         
      }
      
      private boolean autoScroll(int dir)
      {
         // move while the mouse is still out of bounds 
         if (dragging_ && outOfBounds_ != 0)
         {
            // if mouse is far out of bounds, use it to accelerate movement
            if (Math.abs(outOfBounds_) > 100)
            {
               dir *= 1 + Math.round(Math.abs(outOfBounds_) / 75);
            }
            // move if there's moving to be done
            if (dir < 0 && lastElementX_ > 0 ||
                dir > 0 && lastElementX_ < dragMax_)
            {
               commitPosition(lastElementX_ + dir);

               // scroll if there's scrolling to be done
               int left = dragScrollHost_.getScrollLeft();
               if ((dir < 0 && left > 0) || 
                   (dir > 0 && left < dragScrollHost_.getScrollWidth() - 
                        dragScrollHost_.getClientWidth()))
               {
                  dragScrollHost_.setScrollLeft(left + dir);
               }
            }
            return true;
         }
         return false;
      }
      
      private void drag(DragEvent evt) 
      {
         int offset = evt.getNativeEvent().getClientX() - lastCursorX_;
         lastCursorX_ = evt.getNativeEvent().getClientX();
         // cursor is outside the tab area
         if (outOfBounds_ != 0)
         {
            // did the cursor move back in bounds? 
            if (outOfBounds_ + offset > 0 != outOfBounds_ > 0)
            {
               outOfBounds_ = 0;
               offset = outOfBounds_ + offset;
            }
            else 
{'fraction_non_alphanumeric': 0.0905972828853935, 'fraction_numerical': 0.003269112248452531, 'mean_word_length': 1.6758442675844267, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 24, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '137968', 'n_tokens_mistral': 9759, 'n_tokens_neox': 9488, 'n_words': 2139}