/*=========================================================================

   Program: ParaView
   Module:  pqTabbedMultiViewWidget.cxx

   Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc.
   All rights reserved.

   ParaView is a free software; you can redistribute it and/or modify it
   under the terms of the ParaView license version 1.2.

   See License_v1.2.txt for the full ParaView license.
   A copy of this license can be obtained by contacting
   Kitware Inc.
   28 Corporate Drive
   Clifton Park, NY 12065
   USA

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

========================================================================*/
#include "pqTabbedMultiViewWidget.h"

#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqMultiViewWidget.h"
#include "pqObjectBuilder.h"
#include "pqServer.h"
#include "pqServerManagerModel.h"
#include "pqServerManagerObserver.h"
#include "pqUndoStack.h"
#include "pqView.h"
#include "vtkCommand.h"
#include "vtkErrorCode.h"
#include "vtkImageData.h"
#include "vtkNew.h"
#include "vtkSMParaViewPipelineControllerWithRendering.h"
#include "vtkSMProperty.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxyManager.h"
#include "vtkSMSaveScreenshotProxy.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMTrace.h"
#include "vtkSMUtilities.h"
#include "vtkSMViewLayoutProxy.h"

#include <QEvent>
#include <QGridLayout>
#include <QInputDialog>
#include <QLabel>
#include <QMap>
#include <QMenu>
#include <QMouseEvent>
#include <QPointer>
#include <QShortcut>
#include <QStyle>
#include <QTabBar>
#include <QTabWidget>
#include <QtDebug>

#include <cassert>

static const int PQTABBED_WIDGET_PIXMAP_SIZE = 16;
//-----------------------------------------------------------------------------
// ******************** pqTabWidget **********************
//-----------------------------------------------------------------------------
pqTabbedMultiViewWidget::pqTabWidget::pqTabWidget(QWidget* parentObject)
  : Superclass(parentObject)
  , ReadOnly(false)
  , TabBarVisibility(true)
{
  // If annotation filters are enabled, it is possible to get into a situation
  // where no layouts are visible. This means the new-layout tab ("+") is both
  // visible and active – users cannot switch to it to create a new layout with
  // a view-selector because it is already active. In that case, the connection
  // below allows us to explicitly create a new layout.
  QObject::connect(
    this, &QTabWidget::tabBarClicked, this, &pqTabWidget::createViewSelectorTabIfNeeded);
}

//-----------------------------------------------------------------------------
pqTabbedMultiViewWidget::pqTabWidget::~pqTabWidget() = default;

//-----------------------------------------------------------------------------
int pqTabbedMultiViewWidget::pqTabWidget::tabButtonIndex(
  QWidget* wdg, QTabBar::ButtonPosition position) const
{
  for (int cc = 0; cc < this->count(); cc++)
  {
    if (this->tabBar()->tabButton(cc, position) == wdg)
    {
      return cc;
    }
  }
  return -1;
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::pqTabWidget::setTabButton(
  int index, QTabBar::ButtonPosition position, QWidget* wdg)
{
  this->tabBar()->setTabButton(index, position, wdg);
}

//-----------------------------------------------------------------------------
int pqTabbedMultiViewWidget::pqTabWidget::addAsTab(
  pqMultiViewWidget* wdg, pqTabbedMultiViewWidget* self)
{
  int tab_count = this->count();
  if (this->ReadOnly)
  {
    // Add one for the missing '+' button which is not present in ReadOnly mode.
    // The logic later on assumes an additional tab.
    tab_count++;
  }
  vtkSMViewLayoutProxy* proxy = wdg->layoutManager();
  pqServerManagerModel* smmodel = pqApplicationCore::instance()->getServerManagerModel();
  pqProxy* item = smmodel->findItem<pqProxy*>(proxy);
  int tab_index = this->insertTab(tab_count - 1, wdg, item->getSMName());
  this->connect(item, SIGNAL(nameChanged(pqServerManagerModelItem*)), self,
    SLOT(onLayoutNameChanged(pqServerManagerModelItem*)));

  QLabel* label = new QLabel();
  label->setObjectName("popout");
  label->setToolTip(pqTabWidget::popoutLabelText(false));
  label->setStatusTip(pqTabWidget::popoutLabelText(false));
  label->setPixmap(label->style()
                     ->standardIcon(pqTabWidget::popoutLabelPixmap(false))
                     .pixmap(PQTABBED_WIDGET_PIXMAP_SIZE, PQTABBED_WIDGET_PIXMAP_SIZE));
  this->setTabButton(tab_index, QTabBar::LeftSide, label);
  label->installEventFilter(self);

  label = new QLabel();
  label->setObjectName("close");
  label->setToolTip(tr("Close layout"));
  label->setStatusTip(tr("Close layout"));
  label->setPixmap(label->style()
                     ->standardIcon(QStyle::SP_TitleBarCloseButton)
                     .pixmap(PQTABBED_WIDGET_PIXMAP_SIZE, PQTABBED_WIDGET_PIXMAP_SIZE));
  this->setTabButton(tab_index, QTabBar::RightSide, label);
  label->installEventFilter(self);
  label->setVisible(!this->ReadOnly);
  return tab_index;
}

//-----------------------------------------------------------------------------
QString pqTabbedMultiViewWidget::pqTabWidget::popoutLabelText(bool popped_out)
{
  return popped_out ? tr("Bring popped out window back to the frame")
                    : tr("Pop out layout in separate window");
}

//-----------------------------------------------------------------------------
QStyle::StandardPixmap pqTabbedMultiViewWidget::pqTabWidget::popoutLabelPixmap(bool popped_out)
{
  return popped_out ? QStyle::SP_TitleBarNormalButton : QStyle::SP_TitleBarMaxButton;
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::pqTabWidget::setReadOnly(bool val)
{
  if (this->ReadOnly == val)
  {
    return;
  }

  this->ReadOnly = val;
  QList<QLabel*> labels = this->findChildren<QLabel*>("close");
  Q_FOREACH (QLabel* label, labels)
  {
    label->setVisible(!val);
  }
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::pqTabWidget::setTabBarVisibility(bool val)
{
  this->TabBarVisibility = val;
  this->tabBar()->setVisible(val);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::pqTabWidget::createViewSelectorTabIfNeeded(int tabIndex)
{
  (void)tabIndex;
  if (this->currentIndex() == 0 && this->count() == 1)
  {
    pqServer* server = pqActiveObjects::instance().activeServer();
    auto* tmv = qobject_cast<pqTabbedMultiViewWidget*>(this->parent());
    if (tmv)
    {
      tmv->createTab(server);
    }
  }
}

//-----------------------------------------------------------------------------
QSize pqTabbedMultiViewWidget::pqTabWidget::preview(const QSize& nsize)
{
  // NOTE: do for all widgets? Currently, we'll only do this for the current
  // widget.
  if (pqMultiViewWidget* widget = qobject_cast<pqMultiViewWidget*>(this->currentWidget()))
  {
    return widget->preview(nsize);
  }
  return QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX);
}

//-----------------------------------------------------------------------------
// ****************     pqTabbedMultiViewWidget   **********************
//-----------------------------------------------------------------------------
class pqTabbedMultiViewWidget::pqInternals
{
  bool DecorationsVisibility = true;

  // keeps tracks of pqMultiViewWidget instances.
  QMap<pqServer*, QList<QPointer<pqMultiViewWidget>>> TabWidgets;

  QString FilterAnnotationKey;
  bool FilterAnnotationMatching = true;

public:
  QPointer<pqTabWidget> TabWidget;

  QPointer<QWidget> FullScreenWindow;
  QPointer<QWidget> NewTabWidget;

  void addNewTabWidget()
  {
    if (!this->NewTabWidget)
    {
      this->NewTabWidget = new QWidget(this->TabWidget);
      this->TabWidget->addTab(this->NewTabWidget, "+");
    }
  }
  void removeNewTabWidget()
  {
    if (this->NewTabWidget)
    {
      this->TabWidget->removeTab(this->TabWidget->indexOf(this->NewTabWidget));
      delete this->NewTabWidget;
    }
  }

  void setDecorationsVisibility(bool val)
  {
    this->TabWidget->setTabBarVisibility(val);
    this->DecorationsVisibility = val;
    for (int cc = 0, max = this->TabWidget->count(); cc < max; ++cc)
    {
      if (auto mvwidget = qobject_cast<pqMultiViewWidget*>(this->TabWidget->widget(cc)))
      {
        mvwidget->setDecorationsVisibility(val);
      }
    }
  }

  bool decorationsVisibility() const { return this->DecorationsVisibility; }

  /// adds a vtkSMViewLayoutProxy to a new tab.
  int addTab(vtkSMViewLayoutProxy* vlayout, pqTabbedMultiViewWidget* self)
  {
    const int count = this->TabWidget->count();
    auto widget = new pqMultiViewWidget();
    widget->setObjectName(QString("MultiViewWidget%1").arg(count));
    widget->setLayoutManager(vlayout);
    widget->setDecorationsVisibility(this->decorationsVisibility());

    // ensure that the tab current when the pqMultiViewWidget becomes
    // active.
    QObject::connect(widget, &pqMultiViewWidget::frameActivated,
      [this, widget]() { this->TabWidget->setCurrentWidget(widget); });

    auto server =
      pqApplicationCore::instance()->getServerManagerModel()->findServer(vlayout->GetSession());

    this->TabWidgets[server].push_back(widget);

    const int tab_index = this->isVisible(vlayout) ? this->TabWidget->addAsTab(widget, self) : -1;
    return tab_index;
  }

  /**
   * this will return -1 if vlayout is hidden.
   */
  int tabIndex(vtkSMProxy* vlayout) const
  {
    if (auto widget = this->findWidget(vlayout))
    {
      return this->TabWidget->indexOf(widget);
    }
    return -1;
  }

  void setCurrentTab(int index)
  {
    if (index >= 0 && index < this->TabWidget->count())
    {
      this->TabWidget->setCurrentIndex(index);
    }
  }

  /**
   * Returns pqMultiViewWidget instance given a vlayout, if any. Else
   * nullptr.
   */
  pqMultiViewWidget* findWidget(vtkSMProxy* vlayout) const
  {
    for (const auto& pqmvwidget : this->widgets())
    {
      if (pqmvwidget && pqmvwidget->layoutManager() == vlayout)
      {
        return pqmvwidget;
      }
    }
    return nullptr;
  }

  void removeTab(vtkSMProxy* vlayout)
  {
    auto widget = this->findWidget(vlayout);
    if (widget == nullptr)
    {
      return;
    }

    const auto index = this->TabWidget->indexOf(widget);
    if (index != -1)
    {
      if (index == this->TabWidget->currentIndex())
      {
        this->TabWidget->setCurrentIndex((index - 1) > 0 ? (index - 1) : 0);
      }
      this->TabWidget->removeTab(index);
    }

    auto server =
      pqApplicationCore::instance()->getServerManagerModel()->findServer(vlayout->GetSession());
    this->TabWidgets[server].removeOne(widget);
    delete widget;
  }

  // removes all tabs associated with a server.
  void removeTabs(pqServer* server)
  {
    auto wdgs = this->TabWidgets[server];
    for (const auto& widget : wdgs)
    {
      if (widget)
      {
        this->removeTab(widget->layoutManager());
      }
    }
    this->TabWidgets.remove(server);
  }

  QList<QPointer<pqMultiViewWidget>> widgets() const
  {
    QList<QPointer<pqMultiViewWidget>> wgs;
    for (auto& list : this->TabWidgets)
    {
      wgs += list;
    }
    return wgs;
  }

  bool isVisible(vtkSMViewLayoutProxy* vlayout) const
  {
    Q_ASSERT(vlayout != nullptr);
    if (this->FilterAnnotationKey.isEmpty())
    {
      return true;
    }
    else
    {
      const bool hasAnnotation = vlayout->HasAnnotation(this->FilterAnnotationKey.toUtf8().data());
      return this->FilterAnnotationMatching ? hasAnnotation : !hasAnnotation;
    }
  }

  void updateVisibleTabs()
  {
    // build a list of visible tabs and if they are different,
    // than what's shown, update the view.
    QList<QWidget*> visibleWidgets;
    for (const auto& widget : this->widgets())
    {
      if (widget &&
        (this->isVisible(widget->layoutManager()) || widget->layoutManager()->GetViews().empty()))
      {
        visibleWidgets.push_back(widget);
      }
    }

    if (this->NewTabWidget)
    {
      visibleWidgets.push_back(this->NewTabWidget);
    }

    QList<QWidget*> tabs;
    for (int cc = 0, max = this->TabWidget->count(); cc < max; ++cc)
    {
      tabs.push_back(this->TabWidget->widget(cc));
    }

    if (tabs != visibleWidgets)
    {
      this->TabWidget->clear();
      for (auto wdg : visibleWidgets)
      {
        this->TabWidget->addTab(wdg, this->tabLabel(wdg));
      }
    }
  }

  QString tabLabel(QWidget* wdg) const
  {
    if (wdg == this->NewTabWidget)
    {
      return "+";
    }
    else if (auto mvwidget = qobject_cast<pqMultiViewWidget*>(wdg))
    {
      auto vlayout = mvwidget->layoutManager();
      auto pxm = vlayout->GetSessionProxyManager();
      return pxm->GetProxyName("layouts", vlayout);
    }
    return "?";
  }

  void enableAnnotationFilter(const QString& annotationKey)
  {
    if (this->FilterAnnotationKey != annotationKey)
    {
      this->FilterAnnotationKey = annotationKey;
      this->updateVisibleTabs();
    }
  }

  void disableAnnotationFilter()
  {
    if (!this->FilterAnnotationKey.isEmpty())
    {
      this->FilterAnnotationKey.clear();
      this->updateVisibleTabs();
    }
  }

  void setAnnotationFilterMatching(bool matching)
  {
    if (this->FilterAnnotationMatching != matching)
    {
      this->FilterAnnotationMatching = matching;
      if (!this->FilterAnnotationKey.isEmpty())
      {
        this->updateVisibleTabs();
      }
    }
  }
};

//-----------------------------------------------------------------------------
pqTabbedMultiViewWidget::pqTabbedMultiViewWidget(QWidget* parentObject)
  : Superclass(parentObject)
  , Internals(new pqInternals())
{
  this->Internals->TabWidget = new pqTabWidget(this);
  this->Internals->TabWidget->setObjectName("CoreWidget");

  this->Internals->TabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu);
  connect(this->Internals->TabWidget->tabBar(), SIGNAL(customContextMenuRequested(const QPoint&)),
    this, SLOT(contextMenuRequested(const QPoint&)));

  QGridLayout* glayout = new QGridLayout(this);
  glayout->setContentsMargins(0, 0, 0, 0);
  glayout->setSpacing(0);
  glayout->addWidget(this->Internals->TabWidget, 0, 0);

  pqApplicationCore* core = pqApplicationCore::instance();

  core->registerManager("MULTIVIEW_WIDGET", this);

  pqServerManagerModel* smmodel = core->getServerManagerModel();

  QObject::connect(smmodel, SIGNAL(proxyAdded(pqProxy*)), this, SLOT(proxyAdded(pqProxy*)));
  QObject::connect(smmodel, SIGNAL(proxyRemoved(pqProxy*)), this, SLOT(proxyRemoved(pqProxy*)));
  QObject::connect(
    smmodel, SIGNAL(preServerRemoved(pqServer*)), this, SLOT(serverRemoved(pqServer*)));

  this->Internals->addNewTabWidget();
  QObject::connect(
    this->Internals->TabWidget, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));

  // we need to ensure that all views loaded from the state do get assigned to
  // some layout correctly.
  QObject::connect(
    core, SIGNAL(stateLoaded(vtkPVXMLElement*, vtkSMProxyLocator*)), this, SLOT(onStateLoaded()));
}

//-----------------------------------------------------------------------------
pqTabbedMultiViewWidget::~pqTabbedMultiViewWidget()
{
  delete this->Internals;
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::setReadOnly(bool val)
{
  if (val != this->readOnly())
  {
    this->Internals->TabWidget->setReadOnly(val);
    if (val)
    {
      this->Internals->removeNewTabWidget();
    }
    else
    {
      this->Internals->addNewTabWidget();
    }
  }
}

//-----------------------------------------------------------------------------
bool pqTabbedMultiViewWidget::readOnly() const
{
  return this->Internals->TabWidget->readOnly();
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::setTabVisibility(bool visible)
{
  this->Internals->TabWidget->setTabBarVisibility(visible);
}

//-----------------------------------------------------------------------------
bool pqTabbedMultiViewWidget::tabVisibility() const
{
  return this->Internals->TabWidget->tabBarVisibility();
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::toggleFullScreen()
{
  auto& internals = (*this->Internals);
  if (this->Internals->FullScreenWindow)
  {
    internals.FullScreenWindow->layout()->removeWidget(this->Internals->TabWidget);
    this->layout()->addWidget(internals.TabWidget);
    delete internals.FullScreenWindow;
  }
  else
  {
    QWidget* fullScreenWindow = new QWidget(this, Qt::Window);
    internals.FullScreenWindow = fullScreenWindow;
    fullScreenWindow->setObjectName("FullScreenWindow");
    this->layout()->removeWidget(internals.TabWidget);

    QGridLayout* glayout = new QGridLayout(fullScreenWindow);
    glayout->setSpacing(0);
    glayout->setContentsMargins(0, 0, 0, 0);
    glayout->addWidget(internals.TabWidget, 0, 0);
    fullScreenWindow->showFullScreen();
    fullScreenWindow->show();

    QShortcut* esc = new QShortcut(Qt::Key_Escape, fullScreenWindow);
    QObject::connect(esc, SIGNAL(activated()), this, SLOT(toggleFullScreen()));
    QShortcut* f11 = new QShortcut(Qt::Key_F11, fullScreenWindow);
    QObject::connect(f11, SIGNAL(activated()), this, SLOT(toggleFullScreen()));
  }

  // when we enter full screen, let's hide decorations by default.
  internals.setDecorationsVisibility(internals.FullScreenWindow == nullptr);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::proxyAdded(pqProxy* proxy)
{
  if (proxy->getSMGroup() == "layouts" && proxy->getProxy()->IsA("vtkSMViewLayoutProxy"))
  {
    auto vlayout = vtkSMViewLayoutProxy::SafeDownCast(proxy->getProxy());
    this->createTab(vlayout);
  }
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::proxyRemoved(pqProxy* proxy)
{
  auto& internals = (*this->Internals);
  if (proxy->getSMGroup() == "layouts" && proxy->getProxy()->IsA("vtkSMViewLayoutProxy"))
  {
    internals.removeTab(proxy->getProxy());
  }
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::serverRemoved(pqServer* server)
{
  // remove all tabs corresponding to the closed session.
  auto& internals = (*this->Internals);
  internals.removeTabs(server);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::currentTabChanged(int /* index*/)
{
  QWidget* currentWidget = this->Internals->TabWidget->currentWidget();
  if (pqMultiViewWidget* frame = qobject_cast<pqMultiViewWidget*>(currentWidget))
  {
    frame->makeFrameActive();
  }
  else if (currentWidget == this->Internals->NewTabWidget &&
    this->Internals->TabWidget->count() > 1)
  {
    // count() > 1 check keeps this widget from creating new tabs as the tabs are
    // being removed.
    this->Internals->setCurrentTab(this->createTab());
  }
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::closeTab(int index)
{
  pqMultiViewWidget* widget =
    qobject_cast<pqMultiViewWidget*>(this->Internals->TabWidget->widget(index));
  vtkSMProxy* vlayout = widget ? widget->layoutManager() : nullptr;
  if (vlayout)
  {
    pqServerManagerModel* smmodel = pqApplicationCore::instance()->getServerManagerModel();
    pqObjectBuilder* builder = pqApplicationCore::instance()->getObjectBuilder();

    BEGIN_UNDO_SET(tr("Remove View Tab"));
    // first remove each of the views in the tab layout.
    widget->destroyAllViews();

    SM_SCOPED_TRACE(CallFunction).arg("RemoveLayout").arg(vlayout);

    builder->destroy(smmodel->findItem<pqProxy*>(vlayout));
    END_UNDO_SET();
  }

  // The '+' tab is missing if in readOnly mode. Account for that here.
  int minimumNumberOfTabs = this->readOnly() ? 0 : 1;
  if (this->Internals->TabWidget->count() == minimumNumberOfTabs)
  {
    this->Internals->setCurrentTab(this->createTab());
  }
}

//-----------------------------------------------------------------------------
int pqTabbedMultiViewWidget::createTab()
{
  pqServer* server = pqActiveObjects::instance().activeServer();
  return server ? this->createTab(server) : -1;
}

//-----------------------------------------------------------------------------
int pqTabbedMultiViewWidget::createTab(pqServer* server)
{
  if (server)
  {
    BEGIN_UNDO_SET(tr("Add View Tab"));
    auto pxm = server->proxyManager();
    auto vlayout = pxm->NewProxy("misc", "ViewLayout");
    Q_ASSERT(vlayout != nullptr);

    vtkNew<vtkSMParaViewPipelineControllerWithRendering> controller;
    controller->InitializeProxy(vlayout);
    controller->RegisterLayoutProxy(vlayout);
    vlayout->FastDelete();
    END_UNDO_SET();

    this->updateVisibleTabs();
    return this->Internals->tabIndex(vlayout);
  }
  return -1;
}

//-----------------------------------------------------------------------------
int pqTabbedMultiViewWidget::createTab(vtkSMViewLayoutProxy* vlayout)
{
  auto& internals = (*this->Internals);
  return internals.addTab(vlayout, this);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::setCurrentTab(int index)
{
  this->Internals->setCurrentTab(index);
}

//-----------------------------------------------------------------------------
bool pqTabbedMultiViewWidget::eventFilter(QObject* obj, QEvent* evt)
{
  // filtering events on the QLabel added as the tabButton to the tabbar to
  // close the tabs. If clicked, we close the tab.
  if (evt->type() == QEvent::MouseButtonRelease && qobject_cast<QLabel*>(obj))
  {
    QMouseEvent* mouseEvent = dynamic_cast<QMouseEvent*>(evt);
    if (mouseEvent->button() == Qt::LeftButton)
    {
      int index =
        this->Internals->TabWidget->tabButtonIndex(qobject_cast<QWidget*>(obj), QTabBar::RightSide);
      if (index != -1)
      {
        BEGIN_UNDO_SET(tr("Close Tab"));
        this->closeTab(index);
        END_UNDO_SET();
        return true;
      }

      // user clicked on the popout label. We pop the frame out (or back in).
      index =
        this->Internals->TabWidget->tabButtonIndex(qobject_cast<QWidget*>(obj), QTabBar::LeftSide);
      if (index != -1)
      {
        // Pop out tab in a separate window.
        pqMultiViewWidget* tabPage =
          qobject_cast<pqMultiViewWidget*>(this->Internals->TabWidget->widget(index));
        if (tabPage)
        {
          QLabel* label = qobject_cast<QLabel*>(obj);
          bool popped_out = tabPage->togglePopout();
          label->setPixmap(label->style()
                             ->standardIcon(pqTabWidget::popoutLabelPixmap(popped_out))
                             .pixmap(PQTABBED_WIDGET_PIXMAP_SIZE, PQTABBED_WIDGET_PIXMAP_SIZE));
          label->setToolTip(pqTabWidget::popoutLabelText(popped_out));
          label->setStatusTip(pqTabWidget::popoutLabelText(popped_out));
        }
        return true;
      }
    }
  }

  return this->Superclass::eventFilter(obj, evt);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::toggleWidgetDecoration()
{
  auto& internals = (*this->Internals);
  this->setDecorationsVisibility(!internals.decorationsVisibility());
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::setDecorationsVisibility(bool val)
{
  auto& internals = (*this->Internals);
  internals.setDecorationsVisibility(val);
}

//-----------------------------------------------------------------------------
bool pqTabbedMultiViewWidget::decorationsVisibility() const
{
  auto& internals = (*this->Internals);
  return internals.decorationsVisibility();
}

//-----------------------------------------------------------------------------
QSize pqTabbedMultiViewWidget::clientSize() const
{
  if (this->Internals->TabWidget->currentWidget())
  {
    return this->Internals->TabWidget->currentWidget()->size();
  }

  return this->size();
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::lockViewSize(const QSize& viewSize)
{
  QList<QPointer<pqMultiViewWidget>> widgets = this->Internals->widgets();
  Q_FOREACH (QPointer<pqMultiViewWidget> widget, widgets)
  {
    if (widget)
    {
      widget->lockViewSize(viewSize);
    }
  }

  Q_EMIT this->viewSizeLocked(!viewSize.isEmpty());
}

//-----------------------------------------------------------------------------
QSize pqTabbedMultiViewWidget::preview(const QSize& nsize)
{
  return this->Internals->TabWidget->preview(nsize);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::reset()
{
  for (int cc = this->Internals->TabWidget->count() - 1; cc > 1; --cc)
  {
    this->closeTab(cc - 1);
  }

  pqMultiViewWidget* widget =
    qobject_cast<pqMultiViewWidget*>(this->Internals->TabWidget->currentWidget());
  if (widget)
  {
    widget->reset();
  }
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::onStateLoaded()
{
// FIXME: remove
// maybe warn there are views in state file not assigned to layout
#if 0
  QSet<vtkSMViewProxy*> proxies;
  Q_FOREACH (pqMultiViewWidget* wdg, this->Internals->TabWidgets.values())
  {
    if (wdg)
    {
      proxies.unite(wdg->viewProxies().toSet());
    }
  }

  // check that all views are assigned to some frame or other.
  QList<pqView*> views =
    pqApplicationCore::instance()->getServerManagerModel()->findItems<pqView*>();

  Q_FOREACH (pqView* view, views)
  {
    if (!proxies.contains(view->getViewProxy()))
    {
      this->assignToFrame(view, false);
    }
  }
#endif
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::contextMenuRequested(const QPoint& point)
{
  this->setFocus(Qt::OtherFocusReason);

  int tabIndex = this->Internals->TabWidget->tabBar()->tabAt(point);
  pqMultiViewWidget* widget =
    qobject_cast<pqMultiViewWidget*>(this->Internals->TabWidget->widget(tabIndex));
  auto vlayout = widget ? vtkSMViewLayoutProxy::SafeDownCast(widget->layoutManager()) : nullptr;
  if (!vlayout)
  {
    return;
  }
  pqServerManagerModel* smmodel = pqApplicationCore::instance()->getServerManagerModel();
  pqProxy* proxy = smmodel->findItem<pqProxy*>(vlayout);

  QMenu* menu = new QMenu(this);
  QAction* renameAction = menu->addAction(tr("Rename"));
  QAction* closeAction = menu->addAction(tr("Close layout"));
  auto rearrangeMenu = menu->addMenu(tr("Rearrange Views"));
  QAction* horizontalAction = rearrangeMenu->addAction(tr("Horizontally"));
  rearrangeMenu->addAction(tr("Vertically"));
  QAction* gridAction = rearrangeMenu->addAction(tr("Grid"));

  QAction* action = menu->exec(this->Internals->TabWidget->tabBar()->mapToGlobal(point));
  if (action == closeAction)
  {
    BEGIN_UNDO_SET(tr("Close Tab"));
    this->closeTab(tabIndex);
    END_UNDO_SET();
  }
  else if (action == renameAction)
  {
    bool ok;
    QString oldName = proxy->getSMName();
    QString newName = QInputDialog::getText(
      this, tr("Rename Layout..."), tr("New name:"), QLineEdit::Normal, oldName, &ok);
    if (ok && !newName.isEmpty() && newName != oldName)
    {
      SM_SCOPED_TRACE(CallFunction)
        .arg("RenameLayout")
        .arg(newName.toUtf8().data())
        .arg((vtkObject*)proxy->getProxy());

      proxy->rename(newName);
    }
  }
  else if (action == gridAction)
  {
    vlayout->RearrangeViews();
  }
  else if (action)
  {
    vlayout->RearrangeViews(action == horizontalAction ? vtkSMViewLayoutProxy::HORIZONTAL
                                                       : vtkSMViewLayoutProxy::VERTICAL);
  }

  delete menu;
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::onLayoutNameChanged(pqServerManagerModelItem* item)
{
  pqProxy* proxy = dynamic_cast<pqProxy*>(item);
  for (int i = 0; i < this->Internals->TabWidget->count(); i++)
  {
    pqMultiViewWidget* wdg =
      dynamic_cast<pqMultiViewWidget*>(this->Internals->TabWidget->widget(i));
    if (wdg && wdg->layoutManager() == proxy->getProxy())
    {
      this->Internals->TabWidget->setTabText(i, proxy->getSMName());
      return;
    }
  }
}

//-----------------------------------------------------------------------------
vtkSMViewLayoutProxy* pqTabbedMultiViewWidget::layoutProxy() const
{
  if (auto widget = qobject_cast<pqMultiViewWidget*>(this->Internals->TabWidget->currentWidget()))
  {
    return widget->layoutManager();
  }
  return nullptr;
}

//-----------------------------------------------------------------------------
pqMultiViewWidget* pqTabbedMultiViewWidget::findTab(vtkSMViewLayoutProxy* layoutManager) const
{
  const auto& internals = (*this->Internals);
  return internals.findWidget(layoutManager);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::enableAnnotationFilter(const QString& annotationKey)
{
  auto& internals = (*this->Internals);
  internals.enableAnnotationFilter(annotationKey);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::disableAnnotationFilter()
{
  auto& internals = (*this->Internals);
  internals.disableAnnotationFilter();
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::setAnnotationFilterMatching(bool matching)
{
  auto& internals = (*this->Internals);
  internals.setAnnotationFilterMatching(matching);
}

//-----------------------------------------------------------------------------
void pqTabbedMultiViewWidget::updateVisibleTabs()
{
  auto& internals = (*this->Internals);
  internals.updateVisibleTabs();
}

//-----------------------------------------------------------------------------
QList<QString> pqTabbedMultiViewWidget::visibleTabLabels() const
{
  QList<QString> result;
  auto& internals = (*this->Internals);
  auto tabWidget = internals.TabWidget;
  for (int cc = 0, max = tabWidget->count(); cc < max; ++cc)
  {
    result.push_back(tabWidget->tabText(cc));
  }
  return result;
}
