Salome HOME
updated copyright message
[modules/gui.git] / src / LightApp / LightApp_ModuleAction.cxx
index d6a8ae9399a6d022b7f4d6913a35eb98a62b2562..429be2e643105a271f4381b5201a52d21ddfddb1 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2007-2022  CEA/DEN, EDF R&D, OPEN CASCADE
+// Copyright (C) 2007-2023  CEA, EDF, OPEN CASCADE
 //
 // This library is free software; you can redistribute it and/or
 // modify it under the terms of the GNU Lesser General Public
 //
 #include "LightApp_ModuleAction.h"
 
-#include <QtxComboBox.h>
 #include <QtxActionSet.h>
+#include <QtxComboBox.h>
+#include <QtxResourceMgr.h>
 #include <QVBoxLayout>
 #include <QApplication>
 #include <QEvent>
+#include <QMenu>
+#include <QSignalMapper>
+
+#include <utilities.h>
+
+// Prevent slot compilation error
+#pragma push_macro("slots")
+#undef slots
+#include "PyInterp_Utils.h"
+#pragma pop_macro("slots")
 
 /*!
   \class LightApp_ModuleAction::ActionSet
@@ -50,7 +61,7 @@ public:
   \param parent parent object
 */
 LightApp_ModuleAction::ActionSet::ActionSet( QObject* parent )
-: QtxActionSet( parent ) 
+: QtxActionSet( parent )
 {
 }
 
@@ -146,7 +157,7 @@ QList<QtxComboBox*> LightApp_ModuleAction::ComboAction::widgets() const
 
   QList<QWidget*> wlist = createdWidgets();
   for ( QList<QWidget*>::const_iterator wit = wlist.begin(); wit != wlist.end(); ++wit )
-    lst += (*wit)->findChildren<QtxComboBox*>();
+    lst += qobject_cast<QtxComboBox*>(*wit);
 
   return lst;
 }
@@ -162,18 +173,11 @@ QWidget* LightApp_ModuleAction::ComboAction::createWidget( QWidget* parent )
   if ( !parent->inherits( "QToolBar" ) )
     return 0;
 
-  QWidget* dumb = new QWidget( parent );
-  QVBoxLayout* l = new QVBoxLayout( dumb );
-  l->setSpacing( 0 ); l->setMargin( 0 );
-  QtxComboBox* cb = new QtxComboBox( dumb );
+  QtxComboBox* cb = new QtxComboBox( parent );
   cb->setSizeAdjustPolicy( QComboBox::AdjustToContents );
   cb->setFocusPolicy( Qt::NoFocus );
-  l->addWidget( cb );
-  l->addSpacing( 3 );
-
   connect( cb, SIGNAL( activatedId( int ) ), this, SIGNAL( activatedId( int ) ) );
-
-  return dumb;
+  return cb;
 }
 
 /*!
@@ -205,63 +209,76 @@ private:
   \brief An action, representing the list of modules to be inserted to the
   toolbar.
 
-  This action is represented in the toolbar as combo box and a set of buttons 
-  for each module. In addition to the modules items, the combo box contains 
-  an item corresponding to the "neutral point" of the application 
-  (when there is no active module).
-  
-  The action can be constructed with up to two parameters, defining the text
-  and icon to be displayed for the "neutral point".
+  In the toolbar this action is represented as the combo box with the list of
+  available modules, and a set of buttons for each module. Additionally, combo box
+  contains an item representing "neutral point" of the application (i.e. no active module).
 
-  Only one module can be active at the moment. It can be set programmatically 
+  In menu, the action is represented as a plain list of items, one per module.
+
+  Only one module can be active at the moment. It can be set programmatically
   with setActiveModule() function. Use this method with empty string to turn
   to the "neutral point". To get active module, use activeModule() function.
 
-  When user activates/deactivates any module, the signal moduleActivated() 
+  When user activates/deactivates a module, the moduleActivated() signal
   is emitted.
 
-  The action can be represented in the toolbar in different modes:
-  * as combo box only (Qtx::ComboItem)
-  * as set of modules buttons only (Qtx::Buttons)
-  * as combo box followed by the set of modules buttons (Qtx::All)
-  * as none (Qtx::None)
-  By default, both combo box and buttons set are shown. Use method 
-  setMode() to change this behavior.
+  The action also provides an additional separate item "Add modules"; when
+  this button is pressed, a adding() signal is emitted. This signal
+  can be connected to a dedicated slot aimed to dynamically add a new module
+  into the application. In addition, a button "Remove module" shows a dropdown menu
+  with the list of user modules; when any item is selected, the removing() signal
+  is emitted. This signal may be connected to a slot aimed to dynamically remove
+  selected user module from application.
+
+  It is possible to customize which elements to show via the setMode() of setModeEnabled()
+  functions. By default, all elements are shown. The following choices are possible:
 
-  An action can be also added to the popup menu, but combo box is never shown
-  in this case, only modules buttons.
+  - LightApp_ModuleAction::Buttons: show separate items for all modules
+  - LightApp_ModuleAction::List: show combo box with list of modules (in toolbar only)
+  - LightApp_ModuleAction::AddRemove: show "Add modules" and "Remove modules" items
+  - LightApp_ModuleAction::All: show all items
 */
 
 /*!
   \brief Constructor
-
-  Creates an module action with "neutral point" item described by \a text.
-
-  \param text "neutral point" item's text
+  \param resMgr resource manager
   \param parent parent object
 */
-LightApp_ModuleAction::LightApp_ModuleAction( const QString& text, QObject* parent )
+LightApp_ModuleAction::LightApp_ModuleAction( QtxResourceMgr* resMgr, QObject* parent )
 : QtxAction( parent )
 {
-  setText( text );
-  init();
-}
+  setText( tr( "APP_NAME" ) );
+  setIcon( resMgr->loadPixmap( "LightApp", tr( "APP_DEFAULT_ICO" ), false ) );
+  setVisible( false );
 
-/*!
-  \brief Constructor
+  myMode = All;
+  myCombo = new ComboAction( this );
+  myAdd = new QtxAction( tr( "ADD_MODULE"),
+                        resMgr->loadPixmap( "LightApp", tr( "ICON_ADD_MODULE" ), false ),
+                        tr( "ADD_MODULE"),
+                        0, this );
+  myRemove = new QtxAction( tr( "REMOVE_MODULE"),
+                           resMgr->loadPixmap( "LightApp", tr( "ICON_REMOVE_MODULE" ), false ),
+                           tr( "REMOVE_MODULE"),
+                           0, this );
+  myRemove->setEnabled( false );
+  myRemove->setMenu( new QMenu() );
+  myInfo = new QtxAction( tr( "INFO_MODULE"),
+                           resMgr->loadPixmap( "LightApp", tr( "ICON_INFO_MODULE" ), false ),
+                           tr( "INFO_MODULE"),
+                           0, this );
+  mySeparator = new QAction( this );
+  mySeparator->setSeparator( true );
+  mySet = new ActionSet( this );
 
-  Creates an module action with "neutral point" item described by \a text and \a ico.
+  myMapper = new QSignalMapper( this );
 
-  \param text "neutral point" item's text
-  \param ico "neutral point" item's icon
-  \param parent parent object
-*/
-LightApp_ModuleAction::LightApp_ModuleAction( const QString& text, const QIcon& ico, QObject* parent )
-: QtxAction( parent )
-{
-  setText( text );
-  setIcon( ico );
-  init();
+  connect( this,     SIGNAL( changed() ),          this, SLOT( onChanged() ) );
+  connect( myAdd,    SIGNAL( triggered( bool ) ),  this, SIGNAL( adding() ) );
+  connect( myInfo,   SIGNAL( triggered( bool ) ),  this, SIGNAL( showExtInfo() ) );
+  connect( mySet,    SIGNAL( triggered( int ) ),   this, SLOT( onTriggered( int ) ) );
+  connect( myCombo,  SIGNAL( activatedId( int ) ), this, SLOT( onComboActivated( int ) ) );
+  connect( myMapper, SIGNAL( mapped( QString ) ),  this, SIGNAL( removing( QString ) ) );
 }
 
 /*!
@@ -341,9 +358,29 @@ QAction* LightApp_ModuleAction::moduleAction( const QString& name ) const
 */
 void LightApp_ModuleAction::insertModule( const QString& name, const QIcon& ico,
                                           const int idx )
+{
+  insertModule( name, ico, false, idx );
+}
+
+/*!
+  \brief Add module into the list.
+  \param name module name
+  \param ico module icon
+  \param isCustom \c false to insert regular module, \c true to insert user module
+  \param idx position in the module list (if -1, the module is added to the end of list)
+  \sa removeModule()
+*/
+void LightApp_ModuleAction::insertModule( const QString& name, const QIcon& ico,
+                                         bool isCustom, const int idx)
+
 {
   QtxAction* a = new QtxAction( name, ico, name, 0, this, true );
   a->setStatusTip( tr( "ACTIVATE_MODULE_TOP" ).arg( name ) );
+  // Commented because the next call mySet->insertAction() overrides it with
+  // action id int != 0 value, so test a->data().toBool() is always true after that.
+  // Leave it here to mention that we need other approach to mark module as custom.
+  // a->setData( isCustom );
 
   mySet->insertAction( a, -1, idx );
   update();
@@ -356,18 +393,94 @@ void LightApp_ModuleAction::insertModule( const QString& name, const QIcon& ico,
 */
 void LightApp_ModuleAction::removeModule( const QString& name )
 {
+  MESSAGE("Start to remove module...");
+
   int id = mySet->moduleId( name );
+  SCRUTE(id);
   if ( id == -1 )
+  {
+    MESSAGE("Can't get a module's id! Return");
     return;
+  }
+
+  MESSAGE("Remove action by id...");
+  mySet->removeAction(id);
 
-  mySet->removeAction( id );
+  update();
+
+  MESSAGE("Module was removed");
+}
+
+/*!
+  \brief Add an installed extension. Now only to the Remove button's menu.
+  \param name an extension's name
+  \sa removeExtension()
+*/
+void LightApp_ModuleAction::insertExtension(const QString& name)
+{
+  MESSAGE("Insert an extension's action...");
+  SCRUTE(name.toStdString());
+
+  myRemove->setEnabled(true);
+
+  // Find a place to insert in the alphabetical order
+  QAction* insertBefore = nullptr;
+  foreach(QAction* curAction, myRemove->menu()->actions())
+  {
+    int compareRes = QString::compare(curAction->text(), name, Qt::CaseInsensitive);
+    if (!compareRes)
+    {
+      return; // already added
+    }
+    else if (compareRes > 0)
+    {
+      insertBefore = curAction;
+
+      SCRUTE(insertBefore->text().toStdString());
+      break;
+    }
+  }
+
+  QAction* inserted = new QAction(name);
+
+  myRemove->menu()->insertAction(insertBefore, inserted);
+  connect(inserted, SIGNAL(triggered()), myMapper, SLOT(map()));
+  myMapper->setMapping(inserted, name);
+
+  MESSAGE("An extension's action was inserted");
+}
+
+/*!
+  \brief Remove an installed extension.
+  \param name an extension's name
+  \sa insertExtension()
+*/
+void LightApp_ModuleAction::removeExtension(const QString& name)
+{
+  MESSAGE("Remove an extension's action...");
+  SCRUTE(name.toStdString());
+
+  foreach(QAction* ma, myRemove->menu()->actions())
+  {
+    if (ma->text() == name)
+    {
+      myRemove->menu()->removeAction(ma);
+
+      MESSAGE("Extension's action was removed");
+      break;
+    }
+  }
+
+  myRemove->setEnabled(!myRemove->menu()->actions().isEmpty());
+
+  updateExtActions();
   update();
 }
 
 /*!
   \brief Get active module.
 
-  If there is no active module ("neutral point"), then the null string 
+  If there is no active module ("neutral point"), then the null string
   is returned.
 
   \return active module name
@@ -399,30 +512,38 @@ void LightApp_ModuleAction::setActiveModule( const QString& name )
 
 /*!
   \brief Set action display mode.
-
-  Action can be represented in the toolbar as
-  * combo box only (Qtx::ComboItem)
-  * set of modules buttons only (Qtx::Buttons)
-  * combo box followed by the set of modules buttons (Qtx::All)
-  * none (Qtx::None)
-
-  \param mode action display mode
+  \param mode action display options (combination of flags)
   \sa mode()
 */
-void LightApp_ModuleAction::setMode( const int mode )
+void LightApp_ModuleAction::setMode( const LightApp_ModuleAction::Mode& mode )
 {
   myMode = mode;
   update();
 }
 
+/*!
+  \brief Enable / disable action display mode.
+  \param mode action display options (combination of flags)
+  \param enabled \c true to enable mode, \c false to disable mode
+  \sa mode()
+*/
+void LightApp_ModuleAction::setModeEnabled( const LightApp_ModuleAction::Mode& mode, bool enabled )
+{
+  if ( enabled )
+    myMode |= mode;
+  else
+    myMode &= ~mode;
+  update();
+}
+
 /*!
   \brief Get action display mode.
   \param mode action display mode
   \sa setMode()
 */
-int LightApp_ModuleAction::mode() const
+bool LightApp_ModuleAction::isModeEnabled( const LightApp_ModuleAction::Mode& mode ) const
 {
-  return myMode;
+  return (bool)( myMode & mode );
 }
 
 /*!
@@ -433,6 +554,10 @@ void LightApp_ModuleAction::addedTo( QWidget* w )
 {
   if ( w->inherits( "QToolBar" ) )
     w->insertAction( this, myCombo );
+  w->insertAction( this, myAdd );
+  w->insertAction( this, myRemove );
+  w->insertAction( this, myInfo );
+  w->insertAction( this, mySeparator );
   w->insertAction( this, mySet );
   update();
 }
@@ -447,6 +572,10 @@ void LightApp_ModuleAction::removedFrom( QWidget* w )
 {
   if ( w->inherits( "QToolBar" ) )
     w->removeAction( myCombo );
+  w->removeAction( myAdd );
+  w->removeAction( myRemove );
+  w->removeAction( myInfo );
+  w->removeAction( mySeparator );
   w->removeAction( mySet );
 }
 
@@ -470,23 +599,6 @@ bool LightApp_ModuleAction::event( QEvent* e )
   \param name module name (empty string for neutral point)
 */
 
-/*!
-  \brief Initialize an action,
-  \internal
-*/
-void LightApp_ModuleAction::init()
-{
-  setVisible( false );
-
-  myMode = All;
-  myCombo = new ComboAction( this );
-  mySet = new ActionSet( this );
-
-  connect( this,    SIGNAL( changed() ),          this, SLOT( onChanged() ) );
-  connect( mySet,   SIGNAL( triggered( int ) ),   this, SLOT( onTriggered( int ) ) );
-  connect( myCombo, SIGNAL( activatedId( int ) ), this, SLOT( onComboActivated( int ) ) );
-}
-
 /*!
   \brief Update an action.
   \internal
@@ -497,7 +609,20 @@ void LightApp_ModuleAction::update()
   for ( QList<QtxComboBox*>::const_iterator it = lst.begin(); it != lst.end(); ++it )
     update( *it );
 
-  myCombo->setVisible( myMode & ComboItem );
+
+  myCombo->setVisible( myMode & List );
+  if ( QString::compare(getenv("SALOME_ON_DEMAND"),"HIDE", Qt::CaseInsensitive) != 0)
+  {
+    myAdd->setVisible( myMode & AddRemove );
+    myRemove->setVisible( myMode & AddRemove );
+    myInfo->setVisible( myMode & All );
+  }
+  else
+  {
+    myAdd->setVisible(false);
+    myRemove->setVisible( false );
+    myInfo->setVisible( false );
+  }
   mySet->setVisible( myMode & Buttons );
 }
 
@@ -515,7 +640,7 @@ void LightApp_ModuleAction::update( QtxComboBox* cb )
   int curId = mySet->moduleId( active() );
   QList<QAction*> alist = mySet->actions();
   cb->clear();
-  
+
   cb->addItem( icon(), text() );
   cb->setId( 0, -1 );
 
@@ -531,6 +656,59 @@ void LightApp_ModuleAction::update( QtxComboBox* cb )
   cb->blockSignals( blocked );
 }
 
+/*!
+  \brief Update extension actions based on dependencies.
+  \internal
+  \param
+*/
+void LightApp_ModuleAction::updateExtActions()
+{
+  MESSAGE("Check dependencies to update extensions actions...");
+
+  // It should be set on the app start
+  auto extRootDir = getenv("SALOME_APPLICATION_DIR");
+  if (!extRootDir)
+  {
+    MESSAGE("Cannot get SALOME_APPLICATION_DIR env variable! Cancel adding selected extensions.");
+    return;
+  }
+  SCRUTE(extRootDir);
+
+  // Import Python module that manages SALOME extensions.
+  PyLockWrapper lck; // acquire GIL
+  PyObjWrapper extensionQuery = PyImport_ImportModule((char*)"SalomeOnDemandTK.extension_query");
+  PyObjWrapper extCanRemoveDict = PyObject_CallMethod(extensionQuery, (char*)"ext_canremove_flags", (char*)"s", extRootDir);
+  if (!extCanRemoveDict || extCanRemoveDict == Py_None)
+  {
+    MESSAGE("Couldn't get <ext>:<can_remove> dictionary from SalomeOnDemandTK.extension_query! Return.");
+    return;
+  }
+
+  // Iterate extensions' actions to disable ones we can't remove because of dependencies.
+  foreach(QAction* curAction, myRemove->menu()->actions())
+  {
+    const std::string action_name = curAction->text().toStdString();
+    SCRUTE(action_name);
+
+    PyObject* canRemoveObject = PyDict_GetItemString(extCanRemoveDict, action_name.c_str());
+    if (!canRemoveObject)
+    {
+      MESSAGE("Couldn't get can remove flag from dictionary! Skip.");
+      continue;
+    }
+
+    const int isTrueRes = PyObject_IsTrue(canRemoveObject);
+    if (isTrueRes == -1)
+    {
+      MESSAGE("PyObject_IsTrue() failed. Using false value instead.");
+    }
+    const bool canRemove = isTrueRes == 1;
+    SCRUTE(canRemove);
+
+    curAction->setEnabled(canRemove);
+  }
+}
+
 /*!
   \brief Get an action corresponding to the active module.
   \internal
@@ -599,7 +777,7 @@ void LightApp_ModuleAction::onTriggered( int id )
 /*!
   \brief Called when action state is changed.
   \internal
-  
+
   This slot is used to prevent making the parent action visible.
 */
 void LightApp_ModuleAction::onChanged()
@@ -620,4 +798,4 @@ void LightApp_ModuleAction::onChanged()
 void LightApp_ModuleAction::onComboActivated( int id )
 {
   QApplication::postEvent( this, new ActivateEvent( QEvent::MaxUser, id ) );
-} 
+}