/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef nsTreeBodyFrame_h
#define nsTreeBodyFrame_h

#include "mozilla/Attributes.h"

#include "nsLeafBoxFrame.h"
#include "nsITreeView.h"
#include "nsICSSPseudoComparator.h"
#include "nsIScrollbarMediator.h"
#include "nsITimer.h"
#include "nsIReflowCallback.h"
#include "nsTArray.h"
#include "nsTreeStyleCache.h"
#include "nsTreeColumns.h"
#include "nsAutoPtr.h"
#include "nsDataHashtable.h"
#include "imgIRequest.h"
#include "imgIDecoderObserver.h"
#include "nsScrollbarFrame.h"
#include "nsThreadUtils.h"
#include "mozilla/LookAndFeel.h"

class nsOverflowChecker;
class nsTreeImageListener;

// An entry in the tree's image cache
struct nsTreeImageCacheEntry
{
  nsTreeImageCacheEntry() {}
  nsTreeImageCacheEntry(imgIRequest *aRequest, imgIDecoderObserver *aListener)
    : request(aRequest), listener(aListener) {}

  nsCOMPtr<imgIRequest> request;
  nsCOMPtr<imgIDecoderObserver> listener;
};

// The actual frame that paints the cells and rows.
class nsTreeBodyFrame MOZ_FINAL
  : public nsLeafBoxFrame
  , public nsICSSPseudoComparator
  , public nsIScrollbarMediator
  , public nsIReflowCallback
{
public:
  nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
  ~nsTreeBodyFrame();

  NS_DECL_QUERYFRAME_TARGET(nsTreeBodyFrame)
  NS_DECL_QUERYFRAME
  NS_DECL_FRAMEARENA_HELPERS

  // Callback handler methods for refresh driver based animations.
  // Calls to these functions are forwarded from nsTreeImageListener. These
  // mirror how nsImageFrame works.
  nsresult OnImageIsAnimated(imgIRequest* aRequest);

  // non-virtual signatures like nsITreeBodyFrame
  nsresult GetColumns(nsITreeColumns **aColumns);
  nsresult GetView(nsITreeView **aView);
  nsresult SetView(nsITreeView *aView);
  nsresult GetFocused(bool *aFocused);
  nsresult SetFocused(bool aFocused);
  nsresult GetTreeBody(nsIDOMElement **aElement);
  nsresult GetRowHeight(int32_t *aValue);
  nsresult GetRowWidth(int32_t *aValue);
  nsresult GetHorizontalPosition(int32_t *aValue);
  nsresult GetSelectionRegion(nsIScriptableRegion **aRegion);
  nsresult GetFirstVisibleRow(int32_t *aValue);
  nsresult GetLastVisibleRow(int32_t *aValue);
  nsresult GetPageLength(int32_t *aValue);
  nsresult EnsureRowIsVisible(int32_t aRow);
  nsresult EnsureCellIsVisible(int32_t aRow, nsITreeColumn *aCol);
  nsresult ScrollToRow(int32_t aRow);
  nsresult ScrollByLines(int32_t aNumLines);
  nsresult ScrollByPages(int32_t aNumPages);
  nsresult ScrollToCell(int32_t aRow, nsITreeColumn *aCol);
  nsresult ScrollToColumn(nsITreeColumn *aCol);
  nsresult ScrollToHorizontalPosition(int32_t aValue);
  nsresult Invalidate();
  nsresult InvalidateColumn(nsITreeColumn *aCol);
  nsresult InvalidateRow(int32_t aRow);
  nsresult InvalidateCell(int32_t aRow, nsITreeColumn *aCol);
  nsresult InvalidateRange(int32_t aStart, int32_t aEnd);
  nsresult InvalidateColumnRange(int32_t aStart, int32_t aEnd,
                                 nsITreeColumn *aCol);
  nsresult GetRowAt(int32_t aX, int32_t aY, int32_t *aValue);
  nsresult GetCellAt(int32_t aX, int32_t aY, int32_t *aRow,
                     nsITreeColumn **aCol, nsACString &aChildElt);
  nsresult GetCoordsForCellItem(int32_t aRow, nsITreeColumn *aCol,
                                const nsACString &aElt,
                                int32_t *aX, int32_t *aY,
                                int32_t *aWidth, int32_t *aHeight);
  nsresult IsCellCropped(int32_t aRow, nsITreeColumn *aCol, bool *aResult);
  nsresult RowCountChanged(int32_t aIndex, int32_t aCount);
  nsresult BeginUpdateBatch();
  nsresult EndUpdateBatch();
  nsresult ClearStyleAndImageCaches();

  virtual nsSize GetMinSize(nsBoxLayoutState& aBoxLayoutState);
  virtual void SetBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
                         bool aRemoveOverflowArea = false);

  // nsIReflowCallback
  virtual bool ReflowFinished() MOZ_OVERRIDE;
  virtual void ReflowCallbackCanceled() MOZ_OVERRIDE;

  // nsICSSPseudoComparator
  virtual bool PseudoMatches(nsCSSSelector* aSelector) MOZ_OVERRIDE;

  // nsIScrollbarMediator
  NS_IMETHOD PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex);
  NS_IMETHOD ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) MOZ_OVERRIDE;
  NS_IMETHOD VisibilityChanged(bool aVisible) MOZ_OVERRIDE { Invalidate(); return NS_OK; }

  // Overridden from nsIFrame to cache our pres context.
  NS_IMETHOD Init(nsIContent*     aContent,
                  nsIFrame*       aParent,
                  nsIFrame*       aPrevInFlow) MOZ_OVERRIDE;
  virtual void DestroyFrom(nsIFrame* aDestructRoot);

  NS_IMETHOD GetCursor(const nsPoint& aPoint,
                       nsIFrame::Cursor& aCursor);

  NS_IMETHOD HandleEvent(nsPresContext* aPresContext,
                         nsGUIEvent* aEvent,
                         nsEventStatus* aEventStatus);

  NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                              const nsRect&           aDirtyRect,
                              const nsDisplayListSet& aLists) MOZ_OVERRIDE;

  virtual void DidSetStyleContext(nsStyleContext* aOldStyleContext);

  friend nsIFrame* NS_NewTreeBodyFrame(nsIPresShell* aPresShell);
  friend class nsTreeColumn;

  struct ScrollParts {
    nsScrollbarFrame*    mVScrollbar;
    nsCOMPtr<nsIContent> mVScrollbarContent;
    nsScrollbarFrame*    mHScrollbar;
    nsCOMPtr<nsIContent> mHScrollbarContent;
    nsIFrame*            mColumnsFrame;
    nsIScrollableFrame*  mColumnsScrollFrame;
  };

  void PaintTreeBody(nsRenderingContext& aRenderingContext,
                     const nsRect& aDirtyRect, nsPoint aPt);

  nsITreeBoxObject* GetTreeBoxObject() const { return mTreeBoxObject; }

  bool GetVerticalOverflow() const { return mVerticalOverflow; }
  bool GetHorizontalOverflow() const {return mHorizontalOverflow; }

protected:
  friend class nsOverflowChecker;

  // This method paints a specific column background of the tree.
  void PaintColumn(nsTreeColumn*        aColumn,
                   const nsRect&        aColumnRect,
                   nsPresContext*      aPresContext,
                   nsRenderingContext& aRenderingContext,
                   const nsRect&        aDirtyRect);

  // This method paints a single row in the tree.
  void PaintRow(int32_t              aRowIndex,
                const nsRect&        aRowRect,
                nsPresContext*       aPresContext,
                nsRenderingContext& aRenderingContext,
                const nsRect&        aDirtyRect,
                nsPoint              aPt);

  // This method paints a single separator in the tree.
  void PaintSeparator(int32_t              aRowIndex,
                      const nsRect&        aSeparatorRect,
                      nsPresContext*      aPresContext,
                      nsRenderingContext& aRenderingContext,
                      const nsRect&        aDirtyRect);

  // This method paints a specific cell in a given row of the tree.
  void PaintCell(int32_t              aRowIndex, 
                 nsTreeColumn*        aColumn,
                 const nsRect&        aCellRect,
                 nsPresContext*       aPresContext,
                 nsRenderingContext& aRenderingContext,
                 const nsRect&        aDirtyRect,
                 nscoord&             aCurrX,
                 nsPoint              aPt);

  // This method paints the twisty inside a cell in the primary column of an tree.
  void PaintTwisty(int32_t              aRowIndex,
                   nsTreeColumn*        aColumn,
                   const nsRect&        aTwistyRect,
                   nsPresContext*      aPresContext,
                   nsRenderingContext& aRenderingContext,
                   const nsRect&        aDirtyRect,
                   nscoord&             aRemainingWidth,
                   nscoord&             aCurrX);

  // This method paints the image inside the cell of an tree.
  void PaintImage(int32_t              aRowIndex,
                  nsTreeColumn*        aColumn,
                  const nsRect&        aImageRect,
                  nsPresContext*      aPresContext,
                  nsRenderingContext& aRenderingContext,
                  const nsRect&        aDirtyRect,
                  nscoord&             aRemainingWidth,
                  nscoord&             aCurrX);

  // This method paints the text string inside a particular cell of the tree.
  void PaintText(int32_t              aRowIndex, 
                 nsTreeColumn*        aColumn,
                 const nsRect&        aTextRect,
                 nsPresContext*      aPresContext,
                 nsRenderingContext& aRenderingContext,
                 const nsRect&        aDirtyRect,
                 nscoord&             aCurrX,
                 bool                 aTextRTL);

  // This method paints the checkbox inside a particular cell of the tree.
  void PaintCheckbox(int32_t              aRowIndex, 
                     nsTreeColumn*        aColumn,
                     const nsRect&        aCheckboxRect,
                     nsPresContext*      aPresContext,
                     nsRenderingContext& aRenderingContext,
                     const nsRect&        aDirtyRect);

  // This method paints the progress meter inside a particular cell of the tree.
  void PaintProgressMeter(int32_t              aRowIndex, 
                          nsTreeColumn*        aColumn,
                          const nsRect&        aProgressMeterRect,
                          nsPresContext*      aPresContext,
                          nsRenderingContext& aRenderingContext,
                          const nsRect&        aDirtyRect);

  // This method paints a drop feedback of the tree.
  void PaintDropFeedback(const nsRect&        aDropFeedbackRect, 
                         nsPresContext*      aPresContext,
                         nsRenderingContext& aRenderingContext,
                         const nsRect&        aDirtyRect,
                         nsPoint              aPt);

  // This method is called with a specific style context and rect to
  // paint the background rect as if it were a full-blown frame.
  void PaintBackgroundLayer(nsStyleContext*      aStyleContext,
                            nsPresContext*      aPresContext, 
                            nsRenderingContext& aRenderingContext, 
                            const nsRect&        aRect,
                            const nsRect&        aDirtyRect);


  int32_t GetLastVisibleRow() {
    return mTopRowIndex + mPageLength;
  }

  // An internal hit test.  aX and aY are expected to be in twips in the
  // coordinate system of this frame.
  int32_t GetRowAt(nscoord aX, nscoord aY);

  // Check for bidi characters in the text, and if there are any, ensure
  // that the prescontext is in bidi mode.
  void CheckTextForBidi(nsAutoString& aText);

  void AdjustForCellText(nsAutoString& aText,
                         int32_t aRowIndex,  nsTreeColumn* aColumn,
                         nsRenderingContext& aRenderingContext,
                         nsRect& aTextRect);

  // A helper used when hit testing.
  nsIAtom* GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
                               int32_t aRowIndex, nsTreeColumn* aColumn);

  // An internal hit test.  aX and aY are expected to be in twips in the
  // coordinate system of this frame.
  void GetCellAt(nscoord aX, nscoord aY, int32_t* aRow, nsTreeColumn** aCol,
                 nsIAtom** aChildElt);

  // Retrieve the area for the twisty for a cell.
  nsITheme* GetTwistyRect(int32_t aRowIndex,
                          nsTreeColumn* aColumn,
                          nsRect& aImageRect,
                          nsRect& aTwistyRect,
                          nsPresContext* aPresContext,
                          nsRenderingContext& aRenderingContext,
                          nsStyleContext* aTwistyContext);

  // Fetch an image from the image cache.
  nsresult GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
                    nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult);

  // Returns the size of a given image.   This size *includes* border and
  // padding.  It does not include margins.
  nsRect GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, nsStyleContext* aStyleContext);

  // Returns the destination size of the image, not including borders and padding.
  nsSize GetImageDestSize(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image);

  // Returns the source rectangle of the image to be displayed.
  nsRect GetImageSourceRect(nsStyleContext* aStyleContext, bool useImageRegion, imgIContainer* image);

  // Returns the height of rows in the tree.
  int32_t GetRowHeight();

  // Returns our indentation width.
  int32_t GetIndentation();

  // Calculates our width/height once border and padding have been removed.
  void CalcInnerBox();

  // Calculate the total width of our scrollable portion
  nscoord CalcHorzWidth(const ScrollParts& aParts);

  // Looks up a style context in the style cache.  On a cache miss we resolve
  // the pseudo-styles passed in and place them into the cache.
  nsStyleContext* GetPseudoStyleContext(nsIAtom* aPseudoElement);

  // Retrieves the scrollbars and scrollview relevant to this treebody. We
  // traverse the frame tree under our base element, in frame order, looking
  // for the first relevant vertical scrollbar, horizontal scrollbar, and
  // scrollable frame (with associated content and scrollable view). These
  // are all volatile and should not be retained.
  ScrollParts GetScrollParts();

  // Update the curpos of the scrollbar.
  void UpdateScrollbars(const ScrollParts& aParts);

  // Update the maxpos of the scrollbar.
  void InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame);

  // Check overflow and generate events.
  void CheckOverflow(const ScrollParts& aParts);

  // Calls UpdateScrollbars, Invalidate aNeedsFullInvalidation if true,
  // InvalidateScrollbars and finally CheckOverflow.
  // returns true if the frame is still alive after the method call.
  bool FullScrollbarsUpdate(bool aNeedsFullInvalidation);

  // Use to auto-fill some of the common properties without the view having to do it.
  // Examples include container, open, selected, and focus.
  void PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol);

  // Our internal scroll method, used by all the public scroll methods.
  nsresult ScrollInternal(const ScrollParts& aParts, int32_t aRow);
  nsresult ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow);
  nsresult ScrollToColumnInternal(const ScrollParts& aParts, nsITreeColumn* aCol);
  nsresult ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition);
  nsresult EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow);

  // Convert client pixels into appunits in our coordinate space.
  nsPoint AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY);

  // Cache the box object
  void EnsureBoxObject();

  void EnsureView();

  // Get the base element, <tree> or <select>
  nsIContent* GetBaseElement();

  nsresult GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
                        nsRenderingContext* aRenderingContext,
                        nscoord& aDesiredSize, nscoord& aCurrentSize);
  nscoord CalcMaxRowWidth();

  // Translate the given rect horizontally from tree coordinates into the
  // coordinate system of our nsTreeBodyFrame.  If clip is true, then clip the
  // rect to its intersection with mInnerBox in the horizontal direction.
  // Return whether the result has a nonempty intersection with mInnerBox
  // after projecting both onto the horizontal coordinate axis.
  bool OffsetForHorzScroll(nsRect& rect, bool clip);

  bool CanAutoScroll(int32_t aRowIndex);

  // Calc the row and above/below/on status given where the mouse currently is hovering.
  // Also calc if we're in the region in which we want to auto-scroll the tree.
  // A positive value of |aScrollLines| means scroll down, a negative value
  // means scroll up, a zero value means that we aren't in drag scroll region.
  void ComputeDropPosition(nsGUIEvent* aEvent, int32_t* aRow, int16_t* aOrient,
                           int16_t* aScrollLines);

  // Mark ourselves dirty if we're a select widget
  void MarkDirtyIfSelect();

  void InvalidateDropFeedback(int32_t aRow, int16_t aOrientation) {
    InvalidateRow(aRow);
    if (aOrientation != nsITreeView::DROP_ON)
      InvalidateRow(aRow + aOrientation);
  }

public:
  static
  already_AddRefed<nsTreeColumn> GetColumnImpl(nsITreeColumn* aUnknownCol) {
    if (!aUnknownCol)
      return nullptr;

    nsTreeColumn* col;
    aUnknownCol->QueryInterface(NS_GET_IID(nsTreeColumn), (void**)&col);
    return col;
  }

  /**
   * Remove an nsITreeImageListener from being tracked by this frame. Only tree
   * image listeners that are created by this frame are tracked.
   *
   * @param aListener A pointer to an nsTreeImageListener to no longer
   *        track.
   */
  void RemoveTreeImageListener(nsTreeImageListener* aListener);

protected:

  // Create a new timer. This method is used to delay various actions like
  // opening/closing folders or tree scrolling.
  // aID is type of the action, aFunc is the function to be called when
  // the timer fires and aType is type of timer - one shot or repeating.
  nsresult CreateTimer(const mozilla::LookAndFeel::IntID aID,
                       nsTimerCallbackFunc aFunc, int32_t aType,
                       nsITimer** aTimer);

  static void OpenCallback(nsITimer *aTimer, void *aClosure);

  static void CloseCallback(nsITimer *aTimer, void *aClosure);

  static void LazyScrollCallback(nsITimer *aTimer, void *aClosure);

  static void ScrollCallback(nsITimer *aTimer, void *aClosure);

  class ScrollEvent : public nsRunnable {
  public:
    NS_DECL_NSIRUNNABLE
    ScrollEvent(nsTreeBodyFrame *aInner) : mInner(aInner) {}
    void Revoke() { mInner = nullptr; }
  private:
    nsTreeBodyFrame* mInner;
  };

  void PostScrollEvent();
  void FireScrollEvent();

  /**
   * Clear the pointer to this frame for all nsTreeImageListeners that were
   * created by this frame.
   */
  void DetachImageListeners();

#ifdef ACCESSIBILITY
  /**
   * Fires 'treeRowCountChanged' event asynchronously. The event supports
   * nsIDOMDataContainerEvent interface that is used to expose the following
   * information structures.
   *
   * @param aIndex  the row index rows are added/removed from
   * @param aCount  the number of added/removed rows (the sign points to
   *                an operation, plus - addition, minus - removing)
   */
  void FireRowCountChangedEvent(int32_t aIndex, int32_t aCount);

  /**
   * Fires 'treeInvalidated' event asynchronously. The event supports
   * nsIDOMDataContainerEvent interface that is used to expose the information
   * structures described by method arguments.
   *
   * @param aStartRow  the start index of invalidated rows, -1 means that
   *                   columns have been invalidated only
   * @param aEndRow    the end index of invalidated rows, -1 means that columns
   *                   have been invalidated only
   * @param aStartCol  the start invalidated column, nullptr means that only rows
   *                   have been invalidated
   * @param aEndCol    the end invalidated column, nullptr means that rows have
   *                   been invalidated only
   */
  void FireInvalidateEvent(int32_t aStartRow, int32_t aEndRow,
                           nsITreeColumn *aStartCol, nsITreeColumn *aEndCol);
#endif

protected: // Data Members

  class Slots {
    public:
      Slots() {
      }

      ~Slots() {
        if (mTimer)
          mTimer->Cancel();
      }

      friend class nsTreeBodyFrame;

    protected:
      // If the drop is actually allowed here or not.
      bool                     mDropAllowed;

      // True while dragging over the tree.
      bool mIsDragging;

      // The row the mouse is hovering over during a drop.
      int32_t                  mDropRow;

      // Where we want to draw feedback (above/on this row/below) if allowed.
      int16_t                  mDropOrient;

      // Number of lines to be scrolled.
      int16_t                  mScrollLines;

      // The drag action that was received for this slot
      uint32_t                 mDragAction;

      // Timer for opening/closing spring loaded folders or scrolling the tree.
      nsCOMPtr<nsITimer>       mTimer;

      // An array used to keep track of all spring loaded folders.
      nsTArray<int32_t>        mArray;
  };

  Slots* mSlots;

  nsRevocableEventPtr<ScrollEvent> mScrollEvent;

  // The cached box object parent.
  nsCOMPtr<nsITreeBoxObject> mTreeBoxObject;

  // Cached column information.
  nsRefPtr<nsTreeColumns> mColumns;

  // The current view for this tree widget.  We get all of our row and cell data
  // from the view.
  nsCOMPtr<nsITreeView> mView;

  // A cache of all the style contexts we have seen for rows and cells of the tree.  This is a mapping from
  // a list of atoms to a corresponding style context.  This cache stores every combination that
  // occurs in the tree, so for n distinct properties, this cache could have 2 to the n entries
  // (the power set of all row properties).
  nsTreeStyleCache mStyleCache;

  // A hashtable that maps from URLs to image request/listener pairs.  The URL
  // is provided by the view or by the style context. The style context
  // represents a resolved :-moz-tree-cell-image (or twisty) pseudo-element.
  // It maps directly to an imgIRequest.
  nsDataHashtable<nsStringHashKey, nsTreeImageCacheEntry> mImageCache;

  // A scratch array used when looking up cached style contexts.
  nsCOMPtr<nsISupportsArray> mScratchArray;

  // The index of the first visible row and the # of rows visible onscreen.  
  // The tree only examines onscreen rows, starting from
  // this index and going up to index+pageLength.
  int32_t mTopRowIndex;
  int32_t mPageLength;

  // The horizontal scroll position
  nscoord mHorzPosition;
  // Our desired horizontal width (the width for which we actually have tree
  // columns).
  nscoord mHorzWidth;
  // The amount by which to adjust the width of the last cell.
  // This depends on whether or not the columnpicker and scrollbars are present.
  nscoord mAdjustWidth;

  // Cached heights and indent info.
  nsRect mInnerBox; // 4-byte aligned
  int32_t mRowHeight;
  int32_t mIndentation;
  nscoord mStringWidth;

  int32_t mUpdateBatchNest;

  // Cached row count.
  int32_t mRowCount;

  // The row the mouse is hovering over.
  int32_t mMouseOverRow;

  // Whether or not we're currently focused.
  bool mFocused;

  // Do we have a fixed number of onscreen rows?
  bool mHasFixedRowCount;

  bool mVerticalOverflow;
  bool mHorizontalOverflow;

  bool mReflowCallbackPosted;

  // Hash table to keep track of which listeners we created and thus
  // have pointers to us.
  nsTHashtable<nsPtrHashKey<nsTreeImageListener> > mCreatedListeners;

}; // class nsTreeBodyFrame

#endif
