pqSMTKNewResourceBehavior.cxx 12.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#include "smtk/extension/paraview/appcomponents/pqSMTKNewResourceBehavior.h"

// Client side
#include "pqActiveObjects.h"
#include "pqApplicationCore.h"
#include "pqCoreUtilities.h"
#include "pqFileDialog.h"
#include "pqObjectBuilder.h"
18
#include "pqPropertiesPanel.h"
19
#include "pqServer.h"
20
#include "vtkSMPVRepresentationProxy.h"
21 22 23 24 25 26 27 28 29 30
#include "vtkSMPropertyHelper.h"
#include "vtkSMProxy.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/json/jsonResource.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKOperationPanel.h"
31 32
#include "smtk/extension/paraview/appcomponents/pqSMTKRenderResourceBehavior.h"
#include "smtk/extension/paraview/appcomponents/pqSMTKResource.h"
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
#include "smtk/extension/paraview/appcomponents/pqSMTKWrapper.h"
#include "smtk/extension/qt/qtOperationView.h"
#include "smtk/extension/qt/qtUIManager.h"
#include "smtk/operation/Manager.h"
#include "smtk/operation/groups/CreatorGroup.h"

#include <QAction>
#include <QApplication>
#include <QDialog>
#include <QMainWindow>
#include <QMenu>
#include <QMenuBar>
#include <QObject>
#include <QPushButton>
#include <QSharedPointer>

#include "nlohmann/json.hpp"

using json = nlohmann::json;

pqNewResourceReaction::pqNewResourceReaction(
  const std::string& operationName, QAction* parentObject)
  : Superclass(parentObject)
  , m_operationName(operationName)
{
}

void pqNewResourceReaction::newResource()
{
  // Access the active server
  pqServer* server = pqActiveObjects::instance().activeServer();
  pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server);

  // Access the unique name associated with the operation.
  auto createOp = wrapper->smtkOperationManager()->create(m_operationName);

  // Construct a modal dialog for the operation.
  QSharedPointer<QDialog> createDialog = QSharedPointer<QDialog>(new QDialog());
  createDialog->setObjectName("CreateResourceDialog");
72 73
  createDialog->setWindowTitle(
    QString::fromStdString(createOp->parameters()->definition()->label()));
74 75 76 77 78 79 80 81
  createDialog->setLayout(new QVBoxLayout(createDialog.data()));

  // Create a new UI for the dialog.
  QSharedPointer<smtk::extension::qtUIManager> uiManager =
    QSharedPointer<smtk::extension::qtUIManager>(new smtk::extension::qtUIManager(createOp));

  // Create an operation view for the operation.
  smtk::view::ViewPtr view = uiManager->findOrCreateOperationView();
82 83 84 85 86 87 88 89 90 91 92 93

  // Currently, creation operators all fallow the pattern of optionally creating
  // an entity within an extant resource. Since this functionality doesn't make
  // sense for a "New Resource" menu option, we flag the input items involved
  // with that choice as advanced and disable the advanced items here. Since the
  // choice of filtering by advance level is persistent for the operation, we
  // unset the flag after the operation window returns.
  std::string originalFilterByAdvanceLevel;
  if (view->details().attribute("FilterByAdvanceLevel", originalFilterByAdvanceLevel))
  {
    view->details().setAttribute("FilterByAdvanceLevel", "false");
  }
94 95 96 97 98 99 100 101
  smtk::extension::qtOperationView* opView = dynamic_cast<smtk::extension::qtOperationView*>(
    uiManager->setSMTKView(view, createDialog.data()));

  // Remove all connections from the operation view's apply button. We are going
  // to intercept the client-side operation parameters and execute them on the
  // server.
  opView->applyButton()->disconnect();

102
  // Retrieve the operation parameters and close the modal dialog when the
103 104
  // apply button is clicked.
  std::string typeName;
105
  std::string parameters;
106 107 108
  QObject::connect(opView->applyButton(), &QPushButton::clicked, [&]() {
    typeName = opView->operation()->typeName();
    json j;
109 110
    smtk::attribute::to_json(j, opView->operation()->parameters());
    parameters = j.dump();
111 112 113 114 115 116
    createDialog->done(QDialog::Accepted);
  });

  // Launch the modal dialog and wait for the operation to succeed.
  createDialog->exec();

117
  if (!typeName.empty())
118
  {
119 120 121
    auto pqCore = pqApplicationCore::instance();
    auto builder = pqCore->getObjectBuilder();

122
    pqSMTKResource* src =
123
      static_cast<pqSMTKResource*>(builder->createSource("sources", "SMTKResourceCreator", server));
124 125
    vtkSMPropertyHelper(src->getProxy(), "TypeName").Set(typeName.c_str());
    vtkSMPropertyHelper(src->getProxy(), "Parameters").Set(parameters.c_str());
126 127

    pqSMTKRenderResourceBehavior::instance()->renderPipelineSource(src);
128
  }
129 130 131 132 133 134 135

  // Restore the original choice for filtering by advance level so it will be
  // present when the operation is called from another code path.
  if (view->details().attribute("FilterByAdvanceLevel"))
  {
    view->details().setAttribute("FilterByAdvanceLevel", originalFilterByAdvanceLevel);
  }
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
}

namespace
{
QAction* findSaveResourceAction(QMenu* menu)
{
  foreach (QAction* action, menu->actions())
  {
    if (action->text().contains("save resource", Qt::CaseInsensitive))
    {
      return action;
    }
  }
  return NULL;
}
}

static pqSMTKNewResourceBehavior* g_instance = nullptr;

pqSMTKNewResourceBehavior::pqSMTKNewResourceBehavior(QObject* parent)
  : Superclass(parent)
  , m_newMenu(nullptr)
{
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
  // Wait until the event loop starts, ensuring that the main window will be
  // accessible.
  QTimer::singleShot(0, this, [this]() {
    auto pqCore = pqApplicationCore::instance();
    if (pqCore)
    {
      QMenu* fileMenu = this->fileMenu();

      // We want to defer the creation of the menu actions as much as possible
      // so the File menu will already be populated by the time we add our
      // custom actions. If our actions are inserted first, there is no way to
      // control where in the list of actions they go, and they end up awkwardly
      // sitting at the top of the menu. By using a single-shot connection to
      // load our actions, we ensure that extant Save methods are in place; we
      // key off of their location to make the menu look better.
      QMetaObject::Connection* connection = new QMetaObject::Connection;
      *connection = QObject::connect(fileMenu, &QMenu::aboutToShow, [this, connection, fileMenu]() {
        QAction* saveAction = findSaveResourceAction(fileMenu);

        this->setNewMenu(qobject_cast<QMenu*>(
          fileMenu->insertMenu(saveAction, new QMenu("New Resource"))->parent()));
        this->updateNewMenu();

        // Remove this connection.
        QObject::disconnect(*connection);
        delete connection;
      });

      pqActiveObjects* activeObjects = &pqActiveObjects::instance();
      QObject::connect(
        activeObjects, SIGNAL(serverChanged(pqServer*)), this, SLOT(updateNewMenu()));
190 191 192 193 194

      pqServer* server = pqActiveObjects::instance().activeServer();
      pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server);
      if (wrapper != nullptr)
      {
195
        m_key = wrapper->smtkOperationManager()->groupObservers().insert(
196
          [this](const smtk::operation::Operation::Index&, const std::string& groupName, bool) {
197
            if (g_instance != nullptr && groupName == smtk::operation::CreatorGroup::type_name)
198 199 200 201 202 203 204
            {
              this->updateNewMenu();
            }
          });
        // Access the creator group.
        auto creatorGroup = smtk::operation::CreatorGroup(wrapper->smtkOperationManager());
      }
205 206
    }
  });
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
}

QMenu* pqSMTKNewResourceBehavior::fileMenu()
{
  QMainWindow* mainWindow = qobject_cast<QMainWindow*>(pqCoreUtilities::mainWidget());

  QList<QAction*> menuBarActions = mainWindow->menuBar()->actions();

  QMenu* menu = NULL;
  foreach (QAction* existingMenuAction, menuBarActions)
  {
    QString menuName = existingMenuAction->text();
    menuName.remove('&');
    if (menuName == "File")
    {
      return existingMenuAction->menu();
    }
  }

  if (menu == nullptr)
  {
    // Create new menu.
    menu = new QMenu("File", mainWindow);
    menu->setObjectName("File");
  }

  return menu;
}

void pqSMTKNewResourceBehavior::setNewMenu(QMenu* menu)
{
  this->m_newMenu = menu;
}

void pqSMTKNewResourceBehavior::updateNewMenu()
{
  if (m_newMenu != nullptr)
  {
    m_newMenu->clear();
246
    m_newMenu->setObjectName("newResourceMenu");
247 248 249 250 251 252 253

    bool visible = false;

    pqServer* server = pqActiveObjects::instance().activeServer();
    pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server);
    if (wrapper != nullptr)
    {
254
      // Access the creator group.
255 256
      auto creatorGroup = smtk::operation::CreatorGroup(wrapper->smtkOperationManager());

257 258 259 260 261 262
      // Access all resources that can be created by operations in the creator
      // group.
      auto resourceNames = creatorGroup.supportedResources();
      visible = !resourceNames.empty();

      for (auto& resourceName : resourceNames)
263
      {
264
        // To acquire a human-readable resource name, we split on the c++
265
        // double-colon separator and select the penultimate token as the
266
        // short-form for the resource name. We then capitalize the name.
267 268 269
        // This is hack-ish, but it should serve as a stopgap until a
        // standardized convention for assigning human-readable labels to
        // resources is in place.
270
        QString label = QString::fromStdString(resourceName);
271 272 273 274
        QStringList splitLabel = label.split("::");
        label = *(++splitLabel.rbegin());
        label[0] = label[0].toUpper();

275 276 277 278 279 280 281
        auto operationIndices = creatorGroup.operationsForResource(resourceName);

        // If there is only one Create operation associated with this resource,
        // then there is no need to display the Create operation's label.
        if (operationIndices.size() == 1)
        {
          QAction* newResourceAction = new QAction(label, m_newMenu);
282
          newResourceAction->setObjectName(QString::fromStdString(resourceName));
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
          m_newMenu->addAction(newResourceAction);

          std::string operationName = creatorGroup.operationName(*operationIndices.begin());
          new pqNewResourceReaction(operationName, newResourceAction);
        }
        else
        {
          // If there is more than one Create operation associated with this
          // resource, then we promote the resource from an action to a menu and
          // populate the menu with the operations that can create the resource.
          QMenu* resourceMenu = new QMenu(label, m_newMenu);
          m_newMenu->addMenu(resourceMenu);
          resourceMenu->setObjectName(label);

          for (auto& index : operationIndices)
          {
            std::string operationName = creatorGroup.operationName(index);

            QString opLabel = QString::fromStdString(creatorGroup.operationLabel(index));
            // The pattern for the labels of create operations is "Model - XXX".
            // That seems superfluous, so let's strip out the first part.
            QStringList splitOpLabel = opLabel.split(" - ");
            opLabel = *splitOpLabel.rbegin();

            QAction* newResourceAction = new QAction(opLabel, resourceMenu);
308
            newResourceAction->setObjectName(QString::fromStdString(operationName));
309 310 311 312 313
            resourceMenu->addAction(newResourceAction);

            new pqNewResourceReaction(operationName, newResourceAction);
          }
        }
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
      }
    }
    m_newMenu->menuAction()->setVisible(visible);
  }
}

pqSMTKNewResourceBehavior* pqSMTKNewResourceBehavior::instance(QObject* parent)
{
  if (!g_instance)
  {
    g_instance = new pqSMTKNewResourceBehavior(parent);
  }

  if (g_instance->parent() == nullptr && parent)
  {
    g_instance->setParent(parent);
  }

  return g_instance;
}

pqSMTKNewResourceBehavior::~pqSMTKNewResourceBehavior()
{
  if (g_instance == this)
  {
339 340 341 342 343 344 345
    pqServer* server = pqActiveObjects::instance().activeServer();
    pqSMTKWrapper* wrapper = pqSMTKBehavior::instance()->resourceManagerForServer(server);
    if (wrapper != nullptr)
    {
      wrapper->smtkOperationManager()->groupObservers().erase(m_key);
    }

346 347 348 349 350
    g_instance = nullptr;
  }

  QObject::disconnect(this);
}