1 // Copyright (C) 2007-2019 CEA/DEN, EDF R&D, OPEN CASCADE
3 // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Lesser General Public License for more details.
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
23 // File: SalomeApp_Application.cxx
24 // Created: 10/22/2004 3:23:45 PM
25 // Author: Sergey LITONIN
28 // E.A. : On windows with python 2.6, there is a conflict
29 // E.A. : between pymath.h and Standard_math.h which define
30 // E.A. : some same symbols : acosh, asinh, ...
31 #include <Standard_math.hxx>
32 #ifndef DISABLE_PYCONSOLE
37 #ifndef DISABLE_PYCONSOLE
38 #include "SalomeApp_PyInterp.h"
39 #include "SalomeApp_NoteBook.h"
40 #include "LightApp_PyEditor.h"
41 #include "PyConsole_Console.h"
43 #include "SalomeApp_Application.h"
44 #include "SalomeApp_Study.h"
45 #include "SalomeApp_DataModel.h"
46 #include "SalomeApp_DataObject.h"
47 #include "SalomeApp_VisualState.h"
48 #include "SalomeApp_StudyPropertiesDlg.h"
49 #include "SalomeApp_LoadStudiesDlg.h"
50 #include "SalomeApp_ExitDlg.h"
52 #include <LightApp_Application.h>
53 #include <LightApp_FileValidator.h>
54 #include <LightApp_Module.h>
55 #include <LightApp_Preferences.h>
56 #include <LightApp_SelectionMgr.h>
57 #include <LightApp_NameDlg.h>
58 #include <LightApp_DataOwner.h>
60 #include <CAM_Module.h>
62 #include <SUIT_Tools.h>
63 #include <SUIT_Session.h>
64 #include <SUIT_Desktop.h>
65 #include <SUIT_DataBrowser.h>
66 #include <SUIT_FileDlg.h>
67 #include <SUIT_MessageBox.h>
68 #include <SUIT_ResourceMgr.h>
69 #include <SUIT_TreeModel.h>
70 #include <SUIT_ViewWindow.h>
71 #include <SUIT_ViewManager.h>
72 #include <SUIT_ViewModel.h>
73 #include <SUIT_OverrideCursor.h>
75 #include <QtxTreeView.h>
77 #include <SALOME_EventFilter.h>
79 // temporary commented
80 //#include <OB_ListItem.h>
83 #include <Utils_ORB_INIT.hxx>
84 #include <Utils_SINGLETON.hxx>
85 #include <SALOME_LifeCycleCORBA.hxx>
87 #include <QApplication>
92 #include <QPushButton>
94 #include <QListWidget>
95 #include <QGridLayout>
99 #include <SALOMEDSClient_ClientFactory.hxx>
100 #include <Basics_Utils.hxx>
102 #include <SALOME_ListIO.hxx>
103 #include <SALOME_Prs.h>
106 #include <ToolsGUI_CatalogGeneratorDlg.h>
107 #include <ToolsGUI_RegWidget.h>
111 #include <SALOMEDS_Tool.hxx>
113 /*!Internal class that updates object browser item properties */
114 // temporary commented
115 /*class SalomeApp_Updater : public OB_Updater
118 SalomeApp_Updater() : OB_Updater(){};
119 virtual ~SalomeApp_Updater(){};
120 virtual void update( SUIT_DataObject* theObj, OB_ListItem* theItem );
123 void SalomeApp_Updater::update( SUIT_DataObject* theObj, OB_ListItem* theItem )
125 if( !theObj || !theItem )
128 SalomeApp_DataObject* SAObj = dynamic_cast<SalomeApp_DataObject*>( theObj );
132 _PTR(SObject) SObj = SAObj->object();
135 _PTR( GenericAttribute ) anAttr;
138 if ( SObj->FindAttribute( anAttr, "AttributeSelectable" ) )
140 _PTR(AttributeSelectable) aAttrSel = anAttr;
141 theItem->setSelectable( aAttrSel->IsSelectable() );
144 if ( SObj->FindAttribute(anAttr, "AttributeExpandable") )
146 _PTR(AttributeExpandable) aAttrExpand = anAttr;
147 theItem->setExpandable( aAttrExpand->IsExpandable() );
150 //this attribute is not supported in the version of SALOME 3.x
151 //if ( SObj->FindAttribute(anAttr, "AttributeOpened") )
153 // _PTR(AttributeOpened) aAttrOpen = anAttr;
154 // theItem->setOpen( aAttrOpen->IsOpened() );
166 //! Constructor. Sets passed boolean flag to \c true.
167 MessageLocker( bool& Lock ) : myPrevState( Lock ), myLock( Lock ) { myLock = true; }
168 //! Destructor. Clear external boolean flag passed as parameter to the constructor to \c false.
169 ~MessageLocker() { myLock = myPrevState; }
172 bool& myLock; //! External 'Lock state' boolean flag
176 /*!Create new instance of SalomeApp_Application.*/
177 extern "C" SALOMEAPP_EXPORT SUIT_Application* createApplication()
179 return new SalomeApp_Application();
183 SalomeApp_Application::SalomeApp_Application()
184 : LightApp_Application(),
185 myIsCloseFromExit( false ),
186 myToIgnoreMessages( false )
191 *\li Destroy event filter.
193 SalomeApp_Application::~SalomeApp_Application()
195 // Do not destroy. It's a singleton !
196 //SALOME_EventFilter::Destroy();
199 QStringList __getArgsList(QString argsString)
201 // Special process if some items of 'args:' list are themselves lists
202 // Note that an item can be a list, but not a list of lists...
203 // So we can have something like this:
204 // myscript.py args:['file1','file2'],val1,"done",[1,2,3],[True,False],"ok"
205 // With such a call, argsString variable contains the string representing "[file1,file2]", "val1", "done", "[1,2,3]", "[True,False]", "ok"
206 // We have to split argsString to obtain: [[file1,file2],val1,done,[1,2,3],[True,False],ok]
207 argsString.replace("\\\"", "'"); // replace escaped double quotes by simple quotes
208 bool containsList = (QRegExp("(\\[[^\\]]*\\])").indexIn(argsString) >= 0);
210 QStringList sl = argsString.split("\"", QString::SkipEmptyParts);
215 return argsString.split(",", QString::SkipEmptyParts);
218 /*!Start application.*/
219 void SalomeApp_Application::start()
221 // process the command line options before start: to createActions in accordance to the options
222 static bool isFirst = true;
229 QStringList args = QApplication::arguments();
230 for (int i = 1; i < args.count(); i++) {
231 QRegExp rxs ("--study-hdf=(.+)");
232 if ( rxs.indexIn( args[i] ) >= 0 && rxs.capturedTexts().count() > 1 ) {
233 QString file = rxs.capturedTexts()[1];
234 QFileInfo fi ( file );
235 QString extension = fi.suffix().toLower();
236 if ( extension == "hdf" && fi.exists() )
237 hdffile = fi.absoluteFilePath();
240 QRegExp rxp ("--pyscript=\\[(.+)\\]");
241 if ( rxp.indexIn( args[i] ) >= 0 && rxp.capturedTexts().count() > 1 ) {
243 QStringList dictList = rxp.capturedTexts()[1].split("},", QString::SkipEmptyParts);
244 for (int k = 0; k < dictList.count(); ++k) {
245 QRegExp rxd ("[\\s]*\\{?([^\\{\\}]+)\\}?[\\s]*");
246 if ( rxd.indexIn( dictList[k] ) >= 0 && rxd.capturedTexts().count() > 1 ) {
247 for (int m = 1; m < rxd.capturedTexts().count(); ++m) {
248 pyfiles += rxd.capturedTexts()[m];
255 // Here pyfiles elements are: "script_name": [list_of_"arg"s]
256 // For example: "/absolute/path/to/my_script.py": ["1", "2"]
258 LightApp_Application::start();
259 SALOME_EventFilter::Init();
261 setProperty("open_study_from_command_line", true);
262 if ( !hdffile.isEmpty() ) // open hdf file given as parameter
263 onOpenDoc( hdffile );
264 setProperty("open_study_from_command_line", QVariant());
266 #ifndef DISABLE_PYCONSOLE
267 // import/execute python scripts
268 if ( pyfiles.count() > 0 && activeStudy() ) {
269 SalomeApp_Study* appStudy = dynamic_cast<SalomeApp_Study*>( activeStudy() );
270 PyConsole_Console* pyConsole = pythonConsole();
271 if ( appStudy && pyConsole ) {
272 if ( !getStudy()->GetProperties()->IsLocked() ) {
273 // pyfiles[j] is a dictionary: {"/absolute/path/to/script.py": [script_args]}
274 // Path is absolute, script has .py extension
275 for (int j = 0; j < pyfiles.count(); j++ ) {
276 // Extract scripts and their arguments, if any
277 QRegExp rxp ("\"(.+)\":[\\s]*\\[(.*)\\]");
278 if ( rxp.indexIn( pyfiles[j] ) >= 0 && rxp.capturedTexts().count() == 3 ) {
279 QString script = rxp.capturedTexts()[1];
281 QStringList argList = __getArgsList(rxp.capturedTexts()[2]);
282 for (int k = 0; k < argList.count(); k++ ) {
283 QString arg = argList[k].trimmed();
284 arg.remove( QRegExp("^[\"]") );
285 arg.remove( QRegExp("[\"]$") );
286 args << QString("\"%1\"").arg(arg);
288 if (args.count() == 1)
291 script.remove( QRegExp("^python.*[\\s]+") );
292 QString command = QString( "exec(open(\"%1\", \"rb\").read(), args=(%2))" ).arg(script).arg(args.join(","));
293 pyConsole->exec(command);
295 } // end for loop on pyfiles QStringList
301 LightApp_Application::start();
302 SALOME_EventFilter::Init();
307 void SalomeApp_Application::createActions()
309 LightApp_Application::createActions();
311 SUIT_Desktop* desk = desktop();
314 // "Save GUI State" command is moved to VISU module
315 // createAction( SaveGUIStateId, tr( "TOT_DESK_FILE_SAVE_GUI_STATE" ), QIcon(),
316 // tr( "MEN_DESK_FILE_SAVE_GUI_STATE" ), tr( "PRP_DESK_FILE_SAVE_GUI_STATE" ),
317 // 0, desk, false, this, SLOT( onSaveGUIState() ) );
320 createAction( DumpStudyId, tr( "TOT_DESK_FILE_DUMP_STUDY" ), QIcon(),
321 tr( "MEN_DESK_FILE_DUMP_STUDY" ), tr( "PRP_DESK_FILE_DUMP_STUDY" ),
322 Qt::CTRL+Qt::Key_D, desk, false, this, SLOT( onDumpStudy() ) );
325 createAction( LoadScriptId, tr( "TOT_DESK_FILE_LOAD_SCRIPT" ), QIcon(),
326 tr( "MEN_DESK_FILE_LOAD_SCRIPT" ), tr( "PRP_DESK_FILE_LOAD_SCRIPT" ),
327 Qt::CTRL+Qt::Key_T, desk, false, this, SLOT( onLoadScript() ) );
330 createAction( PropertiesId, tr( "TOT_DESK_PROPERTIES" ), QIcon(),
331 tr( "MEN_DESK_PROPERTIES" ), tr( "PRP_DESK_PROPERTIES" ),
332 0, desk, false, this, SLOT( onProperties() ) );
334 //! Catalog Generator
335 createAction( CatalogGenId, tr( "TOT_DESK_CATALOG_GENERATOR" ), QIcon(),
336 tr( "MEN_DESK_CATALOG_GENERATOR" ), tr( "PRP_DESK_CATALOG_GENERATOR" ),
337 Qt::ALT+Qt::SHIFT+Qt::Key_G, desk, false, this, SLOT( onCatalogGen() ) );
340 createAction( RegDisplayId, tr( "TOT_DESK_REGISTRY_DISPLAY" ), QIcon(),
341 tr( "MEN_DESK_REGISTRY_DISPLAY" ), tr( "PRP_DESK_REGISTRY_DISPLAY" ),
342 /*Qt::SHIFT+Qt::Key_D*/0, desk, false, this, SLOT( onRegDisplay() ) );
344 createAction( ConnectId, tr( "TOT_DESK_CONNECT_STUDY" ), QIcon(),
345 tr( "MEN_DESK_CONNECT" ), tr( "PRP_DESK_CONNECT" ),
346 Qt::CTRL+Qt::Key_L, desk, false, this, SLOT( onLoadDoc() ) );
347 //no need at this action for mono-study application because study is always exists
348 action( ConnectId )->setVisible( false );
350 createAction( DisconnectId, tr( "TOT_DESK_DISCONNECT_STUDY" ), QIcon(),
351 tr( "MEN_DESK_DISCONNECT" ), tr( "PRP_DESK_DISCONNECT" ),
352 Qt::CTRL+Qt::Key_U, desk, false, this, SLOT( onUnloadDoc() ) );
353 //no need at this action for mono-study application because study is always exists
354 action( DisconnectId )->setVisible( false );
357 int fileMenu = createMenu( tr( "MEN_DESK_FILE" ), -1 );
359 // "Save GUI State" command is renamed to "Save VISU State" and
360 // creation of menu item is moved to VISU
361 // createMenu( SaveGUIStateId, fileMenu, 10, -1 );
363 createMenu( ConnectId, fileMenu, 5 );
364 createMenu( DisconnectId, fileMenu, 5 );
365 createMenu( separator(), fileMenu, -1, 5 );
367 createMenu( DumpStudyId, fileMenu, 10, -1 );
368 createMenu( LoadScriptId, fileMenu, 10, -1 );
369 createMenu( separator(), fileMenu, -1, 10, -1 );
370 createMenu( PropertiesId, fileMenu, 10, -1 );
371 createMenu( separator(), fileMenu, -1, 10, -1 );
373 int toolsMenu = createMenu( tr( "MEN_DESK_TOOLS" ), -1, MenuToolsId, 50 );
374 createMenu( CatalogGenId, toolsMenu, 10, -1 );
375 createMenu( RegDisplayId, toolsMenu, 10, -1 );
376 createMenu( separator(), toolsMenu, -1, 15, -1 );
378 createExtraActions();
380 #ifndef DISABLE_PYCONSOLE
381 #ifndef DISABLE_SALOMEOBJECT
382 // import Python module that manages SALOME plugins
384 PyLockWrapper lck; // acquire GIL
385 PyObjWrapper pluginsmanager = PyImport_ImportModule((char*)"salome_pluginsmanager");
386 PyObjWrapper res = PyObject_CallMethod( pluginsmanager, (char*)"initialize", (char*)"isss",0,"salome",tr("MEN_DESK_TOOLS").toUtf8().data(),tr("MEN_DESK_PLUGINS").toUtf8().data());
390 // end of SALOME plugins loading
397 \brief Close application.
399 void SalomeApp_Application::onExit()
401 //MessageLocker ml( myToIgnoreMessages );
403 bool killServers = false;
406 if ( exitConfirmation() ) {
407 SalomeApp_ExitDlg dlg( desktop() );
408 result = dlg.exec() == QDialog::Accepted;
409 killServers = dlg.isServersShutdown();
413 if ( !killServers ) myIsCloseFromExit = true;
414 SUIT_Session::session()->closeSession( SUIT_Session::ASK, killServers );
415 if ( SUIT_Session::session()->applications().count() > 0 ) myIsCloseFromExit = false;
419 /*!SLOT. Create a document.*/
420 void SalomeApp_Application::onNewDoc()
422 MessageLocker ml( myToIgnoreMessages );
424 LightApp_Application::onNewDoc();
427 /*!SLOT. Load document.*/
428 void SalomeApp_Application::onLoadDoc()
430 MessageLocker ml( myToIgnoreMessages );
434 // rnv: According to the single-study approach on the server side
435 // can be only one study. So if it is exists connect to them,
436 // overwise show warning message: "No active study on the server"
439 SUIT_Session* aSession = SUIT_Session::session();
440 QList<SUIT_Application*> aAppList = aSession->applications();
442 QStringList unloadedStudies;
444 for ( unsigned int ind = 0; ind < List.size(); ind++ ) {
445 studyName = List[ind].c_str();
446 // Add to list only unloaded studies
447 bool isAlreadyOpen = false;
448 QListIterator<SUIT_Application*> it( aAppList );
449 while ( it.hasNext() && !isAlreadyOpen ) {
450 SUIT_Application* aApp = it.next();
451 if( !aApp || !aApp->activeStudy() )
453 if ( aApp->activeStudy()->studyName() == studyName )
454 isAlreadyOpen = true;
457 if ( !isAlreadyOpen )
458 unloadedStudies << studyName;
460 studyName = SalomeApp_LoadStudiesDlg::selectStudy( desktop(), unloadedStudies );
461 if ( studyName.isEmpty() )
466 SUIT_MessageBox::warning( desktop(),
467 QObject::tr("WRN_WARNING"),
468 QObject::tr("WRN_NO_STUDY_ON SERV") );
472 studyName = activeStudy()->studyName();
475 // this code replaces marker of windows drive and path become invalid therefore
476 // defines placed there
477 studyName.replace( QRegExp(":"), "/" );
480 if ( onLoadDoc( studyName ) ) {
482 updateViewManagers();
483 updateObjectBrowser( true );
487 /*!SLOT. Unload document.*/
488 void SalomeApp_Application::onUnloadDoc( bool ask )
491 activeStudy()->abortAllOperations();
492 if ( activeStudy()->isModified() ) {
493 QString docName = activeStudy()->studyName().trimmed();
494 int answer = SUIT_MessageBox::question( desktop(), tr( "DISCONNECT_CAPTION" ),
495 tr( "DISCONNECT_DESCRIPTION" ),
496 tr( "DISCONNECT_SAVE" ),
497 tr( "DISCONNECT_WO_SAVE" ),
498 tr( "APPCLOSE_CANCEL" ), 0 );
499 if ( answer == 0 ) { // save before unload
500 if ( activeStudy()->isSaved() )
502 else if ( !onSaveAsDoc() )
505 else if ( answer == 2 ) // Cancel
509 closeActiveDoc( false );
512 /*!SLOT. Create new study and load script*/
513 void SalomeApp_Application::onNewWithScript()
515 QStringList filtersList;
516 filtersList.append(tr("PYTHON_FILES_FILTER"));
517 filtersList.append(tr("ALL_FILES_FILTER"));
519 QString anInitialPath = "";
520 if ( SUIT_FileDlg::getLastVisitedPath().isEmpty() )
521 anInitialPath = QDir::currentPath();
523 QString aFile = SUIT_FileDlg::getFileName( desktop(), anInitialPath, filtersList, tr( "TOT_DESK_FILE_LOAD_SCRIPT" ), true, true );
525 if ( !aFile.isEmpty() )
529 QString command = QString("exec(open(\"%1\", \"rb\").read())").arg(aFile);
531 #ifndef DISABLE_PYCONSOLE
532 PyConsole_Console* pyConsole = pythonConsole();
535 pyConsole->exec( command );
541 /*!SLOT. Load document with \a aName.*/
542 bool SalomeApp_Application::onLoadDoc( const QString& aName )
544 if ( !LightApp_Application::closeDoc() )
548 if ( !activeStudy() ) {
549 // if no study - load in current desktop
550 res = useStudy( aName );
553 // if study exists - load in new desktop. Check: is the same file is loaded?
554 SUIT_Session* aSession = SUIT_Session::session();
555 QList<SUIT_Application*> aAppList = aSession->applications();
556 bool isAlreadyOpen = false;
557 SalomeApp_Application* aApp = 0;
558 for ( QList<SUIT_Application*>::iterator it = aAppList.begin();
559 it != aAppList.end() && !isAlreadyOpen; ++it ) {
560 aApp = dynamic_cast<SalomeApp_Application*>( *it );
561 if ( aApp && aApp->activeStudy()->studyName() == aName )
562 isAlreadyOpen = true;
564 if ( !isAlreadyOpen ) {
565 aApp = dynamic_cast<SalomeApp_Application*>( startApplication( 0, 0 ) );
567 res = aApp->useStudy( aName );
570 aApp->desktop()->activateWindow();
577 /*!SLOT. Parse message for desktop.*/
578 void SalomeApp_Application::onDesktopMessage( const QString& message )
580 if ( myToIgnoreMessages )
581 return; // a message from SALOMEDS is caused by GUI action
583 MessageLocker ml( myToIgnoreMessages );
585 if (message.indexOf("studyCreated") == 0) {
586 if (!activeStudy()) {
588 updateCommandsStatus();
591 if (message.indexOf("studyCleared") == 0) {
592 // Disconnect GUI from active study, because it was closed on DS side.
594 closeActiveDoc( false );
595 // Disable 'Connect' action
596 QAction* a = action( ConnectId );
598 a->setEnabled( false );
601 else if ( message.toLower() == "connect_to_study" ) {
603 useStudy( activeStudy()->studyName() );
605 if (message.indexOf("studyNameChanged") == 0) {
606 updateDesktopTitle();
608 LightApp_Application::onDesktopMessage( message );
611 /*!On module activation action.*/
612 void SalomeApp_Application::onModuleActivation( const QString& modName )
614 if (!activeStudy() && !modName.isEmpty())
617 LightApp_Application::onModuleActivation( modName );
620 /*!SLOT. Copy objects to study maneger from selection maneger..*/
621 void SalomeApp_Application::onCopy()
623 LightApp_Application::onCopy();
626 LightApp_SelectionMgr* mgr = selectionMgr();
627 mgr->selectedObjects(list);
629 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>(activeStudy());
630 if(study == NULL) return;
632 _PTR(Study) stdDS = getStudy();
635 SALOME_ListIteratorOfListIO it( list );
638 _PTR(SObject) so = stdDS->FindObjectID(it.Value()->getEntry());
643 onSelectionChanged();
651 /*!SLOT. Paste objects to study maneger from selection manager.*/
652 void SalomeApp_Application::onPaste()
654 LightApp_Application::onPaste();
657 LightApp_SelectionMgr* mgr = selectionMgr();
658 mgr->selectedObjects(list);
660 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>(activeStudy());
661 if(study == NULL) return;
663 _PTR(Study) stdDS = getStudy();
666 if ( stdDS->GetProperties()->IsLocked() ) {
667 SUIT_MessageBox::warning( desktop(),
668 QObject::tr("WRN_WARNING"),
669 QObject::tr("WRN_STUDY_LOCKED") );
673 SALOME_ListIteratorOfListIO it( list );
676 _PTR(SObject) so = stdDS->FindObjectID(it.Value()->getEntry());
681 updateObjectBrowser( true );
682 updateActions(); //SRN: BugID IPAL9377, case 3
690 /*!Check the application on closing.
691 * \retval true if possible, else false
693 bool SalomeApp_Application::isPossibleToClose( bool& closePermanently )
695 return LightApp_Application::isPossibleToClose( closePermanently );
698 /*! Check if the study is locked */
699 void SalomeApp_Application::onCloseDoc( bool ask )
701 if(getStudy()->IsStudyLocked()) {
702 if ( SUIT_MessageBox::question( desktop(),
703 QObject::tr( "WRN_WARNING" ),
704 QObject::tr( "CLOSE_LOCKED_STUDY" ),
705 SUIT_MessageBox::Yes | SUIT_MessageBox::No,
706 SUIT_MessageBox::No) == SUIT_MessageBox::No ) return;
709 MessageLocker ml( myToIgnoreMessages );
711 LightApp_Application::onCloseDoc( ask );
713 // reinitialize study to have empty data
714 //getStudy()->Init();
717 /*!SLOT. Reload document from the file.*/
718 bool SalomeApp_Application::onReopenDoc()
720 MessageLocker ml( myToIgnoreMessages );
722 return LightApp_Application::onReopenDoc();
726 /*!SLOT. Load document.*/
727 void SalomeApp_Application::onOpenDoc()
729 MessageLocker ml( myToIgnoreMessages );
731 LightApp_Application::onOpenDoc();
734 /*!SLOT. Load document.*/
735 bool SalomeApp_Application::onOpenDoc(const QString& name)
737 MessageLocker ml( myToIgnoreMessages );
739 return LightApp_Application::onOpenDoc(name);
742 /*!Sets enable or disable some actions on selection changed.*/
743 void SalomeApp_Application::onSelectionChanged()
746 LightApp_SelectionMgr* mgr = selectionMgr();
747 mgr->selectedObjects(list);
749 bool canCopy = false;
750 bool canPaste = false;
752 LightApp_Module* m = dynamic_cast<LightApp_Module*>( activeModule() );
755 canCopy = m->canCopy();
756 canPaste = m->canPaste();
759 SALOME_ListIteratorOfListIO it ( list );
761 if (it.More() && list.Extent() == 1) {
762 _PTR(SObject) so = getStudy()->FindObjectID(it.Value()->getEntry());
765 canCopy = canCopy || getStudy()->CanCopy(so);
766 canPaste = canPaste || getStudy()->CanPaste(so);
770 action(EditCopyId)->setEnabled(canCopy);
771 action(EditPasteId)->setEnabled(canPaste);
774 /*!Delete references.*/
775 void SalomeApp_Application::onDeleteInvalidReferences()
778 LightApp_SelectionMgr* mgr = selectionMgr();
779 mgr->selectedObjects( aList, QString(), false );
781 if( aList.IsEmpty() )
784 _PTR(Study) aStudyDS = getStudy();
785 _PTR(StudyBuilder) aStudyBuilder = aStudyDS->NewBuilder();
788 for( SALOME_ListIteratorOfListIO it( aList ); it.More(); it.Next() )
789 if ( it.Value()->hasEntry() )
791 _PTR(SObject) aSObject = aStudyDS->FindObjectID( it.Value()->getEntry() ), aRefObj = aSObject;
792 while( aRefObj && aRefObj->ReferencedObject( anObj ) )
795 if( aRefObj && aRefObj!=aSObject && QString( aRefObj->GetName().c_str() ).isEmpty() )
796 aStudyBuilder->RemoveReference( aSObject );
798 updateObjectBrowser();
802 void SalomeApp_Application::onOpenWith()
804 QApplication::setOverrideCursor( Qt::WaitCursor );
806 LightApp_SelectionMgr* mgr = selectionMgr();
807 mgr->selectedObjects(aList);
808 if (aList.Extent() != 1)
810 QApplication::restoreOverrideCursor();
813 Handle(SALOME_InteractiveObject) aIObj = aList.First();
814 QString aModuleName(aIObj->getComponentDataType());
815 QString aModuleTitle = moduleTitle(aModuleName);
816 activateModule(aModuleTitle);
817 QApplication::restoreOverrideCursor();
823 SUIT_Study* SalomeApp_Application::createNewStudy()
825 SalomeApp_Study* aStudy = new SalomeApp_Study( this );
827 // Set up processing of major study-related events
828 connect( aStudy, SIGNAL( created( SUIT_Study* ) ), this, SLOT( onStudyCreated( SUIT_Study* ) ) );
829 connect( aStudy, SIGNAL( opened ( SUIT_Study* ) ), this, SLOT( onStudyOpened ( SUIT_Study* ) ) );
830 connect( aStudy, SIGNAL( saved ( SUIT_Study* ) ), this, SLOT( onStudySaved ( SUIT_Study* ) ) );
831 connect( aStudy, SIGNAL( closed ( SUIT_Study* ) ), this, SLOT( onStudyClosed ( SUIT_Study* ) ) );
833 #ifndef DISABLE_PYCONSOLE
834 //to receive signal in application that NoteBook's variable was modified
835 connect( aStudy, SIGNAL(notebookVarUpdated(QString)),
836 this, SIGNAL(notebookVarUpdated(QString)) );
845 Enable/Disable menu items and toolbar buttons. Rebuild menu
847 void SalomeApp_Application::updateCommandsStatus()
849 LightApp_Application::updateCommandsStatus();
852 QAction* a = action( DumpStudyId );
854 a->setEnabled( activeStudy() );
856 #ifndef DISABLE_PYCONSOLE
858 a = action( LoadScriptId );
860 a->setEnabled( pythonConsole() );
864 a = action( PropertiesId );
866 a->setEnabled( activeStudy() );
868 // Save GUI state menu
869 a = action( SaveGUIStateId );
871 a->setEnabled( activeStudy() );
873 // Connect study menu
874 a = action( ConnectId );
876 a->setEnabled( !activeStudy() );
878 // Disconnect study menu
879 a = action( DisconnectId );
881 a->setEnabled( activeStudy() );
883 // update state of Copy/Paste menu items
884 onSelectionChanged();
888 \class DumpStudyFileDlg
889 Private class used in Dump Study operation. Consists 2 check boxes:
890 "Publish in study" and "Save GUI parameters"
892 class DumpStudyFileDlg : public SUIT_FileDlg
895 DumpStudyFileDlg( QWidget* parent ) : SUIT_FileDlg( parent, false, true, true )
897 QGridLayout* grid = ::qobject_cast<QGridLayout*>( layout() );
900 QWidget *hB = new QWidget( this );
901 myPublishChk = new QCheckBox( tr("PUBLISH_IN_STUDY") );
902 myMultiFileChk = new QCheckBox( tr("MULTI_FILE_DUMP") );
903 mySaveGUIChk = new QCheckBox( tr("SAVE_GUI_STATE") );
905 QHBoxLayout *layout = new QHBoxLayout;
906 layout->addWidget(myPublishChk);
907 layout->addWidget(myMultiFileChk);
908 layout->addWidget(mySaveGUIChk);
909 hB->setLayout(layout);
911 QPushButton* pb = new QPushButton(this);
913 int row = grid->rowCount();
914 grid->addWidget( new QLabel("", this), row, 0 );
915 grid->addWidget( hB, row, 1, 1, 3 );
916 grid->addWidget( pb, row, 5 );
921 QCheckBox* myPublishChk;
922 QCheckBox* myMultiFileChk;
923 QCheckBox* mySaveGUIChk;
926 /*!Private SLOT. On dump study.*/
927 void SalomeApp_Application::onDumpStudy( )
929 SalomeApp_Study* appStudy = dynamic_cast<SalomeApp_Study*>( activeStudy() );
930 if ( !appStudy ) return;
932 QStringList aFilters;
933 aFilters.append( tr( "PYTHON_FILES_FILTER" ) );
935 bool anIsPublish = true;
936 bool anIsMultiFile = false;
937 bool anIsSaveGUI = true;
939 if ( SUIT_ResourceMgr* aResourceMgr = resourceMgr() ) {
940 anIsPublish = aResourceMgr->booleanValue( "Study", "pydump_publish", anIsPublish );
941 anIsMultiFile = aResourceMgr->booleanValue( "Study", "multi_file_dump", anIsMultiFile );
942 anIsSaveGUI = aResourceMgr->booleanValue( "Study", "pydump_save_gui", anIsSaveGUI );
945 DumpStudyFileDlg fd( desktop() );
946 fd.setValidator( new LightApp_PyFileValidator( &fd ) );
947 fd.setWindowTitle( tr( "TOT_DESK_FILE_DUMP_STUDY" ) );
948 fd.setNameFilters( aFilters );
949 fd.myPublishChk->setChecked( anIsPublish );
950 fd.myMultiFileChk->setChecked( anIsMultiFile );
951 fd.mySaveGUIChk->setChecked( anIsSaveGUI );
952 if ( fd.exec() == QDialog::Accepted )
954 QString aFileName = fd.selectedFile();
956 bool toPublish = fd.myPublishChk->isChecked();
957 bool isMultiFile = fd.myMultiFileChk->isChecked();
958 bool toSaveGUI = fd.mySaveGUIChk->isChecked();
960 if ( !aFileName.isEmpty() ) {
961 QFileInfo aFileInfo(aFileName);
962 if( aFileInfo.isDir() ) // IPAL19257
965 // Issue 21377 - dump study implementation moved to SalomeApp_Study class
968 SUIT_OverrideCursor wc;
969 res = appStudy->dump( aFileName, toPublish, isMultiFile, toSaveGUI );
972 SUIT_MessageBox::warning( desktop(),
973 QObject::tr("WRN_WARNING"),
974 tr("WRN_DUMP_STUDY_FAILED") );
979 /*!Private SLOT. On load script.*/
980 void SalomeApp_Application::onLoadScript( )
982 if ( getStudy()->GetProperties()->IsLocked() ) {
983 SUIT_MessageBox::warning( desktop(),
984 QObject::tr("WRN_WARNING"),
985 QObject::tr("WRN_STUDY_LOCKED") );
989 QStringList filtersList;
990 filtersList.append(tr("PYTHON_FILES_FILTER"));
991 filtersList.append(tr("ALL_FILES_FILTER"));
993 QString anInitialPath = "";
994 if ( SUIT_FileDlg::getLastVisitedPath().isEmpty() )
995 anInitialPath = QDir::currentPath();
997 QString aFile = SUIT_FileDlg::getFileName( desktop(), anInitialPath, filtersList, tr( "TOT_DESK_FILE_LOAD_SCRIPT" ), true, true );
999 if ( !aFile.isEmpty() )
1002 QString command = QString("exec(open(\"%1\", \"rb\").read())").arg(aFile);
1004 #ifndef DISABLE_PYCONSOLE
1005 PyConsole_Console* pyConsole = pythonConsole();
1008 pyConsole->exec( command );
1013 /*!Private SLOT. On save GUI state.*/
1014 void SalomeApp_Application::onSaveGUIState()
1016 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>( activeStudy() );
1018 SalomeApp_VisualState( this ).storeState();
1019 updateSavePointDataObjects( study );
1020 updateObjectBrowser();
1025 /*!Public SLOT. Performs some actions when dockable windows are triggered.*/
1026 void SalomeApp_Application::onDockWindowVisibilityChanged( bool theIsVisible )
1028 LightApp_Application::onDockWindowVisibilityChanged( theIsVisible );
1029 QAction* send = ::qobject_cast<QAction*>( sender() );
1032 QString aWinName = send->data().toString();
1033 if ( theIsVisible && aWinName == "objectBrowser" )
1034 objectBrowserColumnsVisibility();
1038 QWidget* SalomeApp_Application::createWindow( const int flag )
1041 #ifndef DISABLE_PYCONSOLE
1042 if ( flag != WT_PyConsole ) wid = LightApp_Application::createWindow(flag);
1044 wid = LightApp_Application::createWindow(flag);
1047 SUIT_ResourceMgr* resMgr = resourceMgr();
1049 if ( flag == WT_ObjectBrowser )
1051 SUIT_DataBrowser* ob = qobject_cast<SUIT_DataBrowser*>( wid );
1053 // temporary commented
1054 //ob->setUpdater( new SalomeApp_Updater() );
1056 #ifdef WITH_SALOMEDS_OBSERVER
1057 //do not activate the automatic update of Qt tree through signal/slot
1058 ob->setAutoUpdate(false);
1059 //activate update of modified objects only
1060 ob->setUpdateModified(true);
1063 connect( ob, SIGNAL( doubleClicked( SUIT_DataObject* ) ), this, SLOT( onDblClick( SUIT_DataObject* ) ) );
1066 ValueCol = QObject::tr( "VALUE_COLUMN" ),
1067 IORCol = QObject::tr( "IOR_COLUMN" ),
1068 RefCol = QObject::tr( "REFENTRY_COLUMN" ),
1069 EntryCol = QObject::tr( "ENTRY_COLUMN" );
1071 SUIT_AbstractModel* treeModel = dynamic_cast<SUIT_AbstractModel*>( ob->model() );
1072 treeModel->registerColumn( 0, EntryCol, SalomeApp_DataObject::EntryId );
1073 treeModel->registerColumn( 0, ValueCol, SalomeApp_DataObject::ValueId );
1074 treeModel->registerColumn( 0, IORCol, SalomeApp_DataObject::IORId );
1075 treeModel->registerColumn( 0, RefCol, SalomeApp_DataObject::RefEntryId );
1076 treeModel->setAppropriate( EntryCol, Qtx::Toggled );
1077 treeModel->setAppropriate( ValueCol, Qtx::Toggled );
1078 treeModel->setAppropriate( IORCol, Qtx::Toggled );
1079 treeModel->setAppropriate( RefCol, Qtx::Toggled );
1081 bool autoSize = resMgr->booleanValue( "ObjectBrowser", "auto_size", false );
1082 bool autoSizeFirst = resMgr->booleanValue( "ObjectBrowser", "auto_size_first", true );
1083 bool resizeOnExpandItem = resMgr->booleanValue( "ObjectBrowser", "resize_on_expand_item", true );
1085 ob->setAutoSizeFirstColumn(autoSizeFirst);
1086 ob->setAutoSizeColumns(autoSize);
1087 ob->setResizeOnExpandItem(resizeOnExpandItem);
1088 ob->setProperty( "shortcut", QKeySequence( "Alt+Shift+O" ) );
1090 for ( int i = SalomeApp_DataObject::EntryId; i < SalomeApp_DataObject::LastId; i++ )
1092 bool shown = resourceMgr()->booleanValue( "ObjectBrowser", QString( "visibility_column_id_%1" ).arg( i-1 ), true );
1093 ob->treeView()->setColumnHidden( i, !shown );
1096 // temporary commented
1098 for ( int i = SalomeApp_DataObject::ValueIdx; i <= SalomeApp_DataObject::RefEntryIdx; i++ )
1100 ob->addColumn( tr( QString().sprintf( "OBJ_BROWSER_COLUMN_%d", i ) ), i );
1101 ob->setColumnShown( i, resMgr->booleanValue( "ObjectBrowser",
1102 QString().sprintf( "visibility_column_%d", i ), true ) );
1106 // temporary commented
1108 ob->setWidthMode( autoSize ? QListView::Maximum : QListView::Manual );
1109 ob->listView()->setColumnWidthMode( 0, autoSizeFirst ? QListView::Maximum : QListView::Manual );
1110 ob->resize( desktop()->width()/3, ob->height() );
1114 #ifndef DISABLE_PYCONSOLE
1115 else if ( flag == WT_PyConsole )
1117 PyConsole_Console* pyCons = new PyConsole_Console( desktop(), new LightApp_PyEditor( getPyInterp() ) );
1118 pyCons->setObjectName( "pythonConsole" );
1119 pyCons->setWindowTitle( tr( "PYTHON_CONSOLE" ) );
1120 pyCons->setFont(resourceMgr()->fontValue( "PyConsole", "font" ));
1121 pyCons->setIsShowBanner(resourceMgr()->booleanValue( "PyConsole", "show_banner", true ));
1122 pyCons->setAutoCompletion( resMgr->booleanValue( "PyConsole", "auto_completion", true ) );
1123 pyCons->setProperty( "shortcut", QKeySequence( "Alt+Shift+P" ) );
1126 else if ( flag == WT_NoteBook )
1128 setNoteBook( new SalomeApp_NoteBook( desktop() ) );
1129 //to receive signal in NoteBook that it's variable was modified
1130 connect( this, SIGNAL( notebookVarUpdated( QString ) ),
1131 getNoteBook(), SLOT( onVarUpdate( QString ) ) );
1133 wid = getNoteBook();
1134 wid->setObjectName( "noteBook" );
1140 /*!Create preferences.*/
1141 void SalomeApp_Application::createPreferences( LightApp_Preferences* pref )
1143 LightApp_Application::createPreferences(pref);
1148 int salomeCat = pref->addPreference( tr( "PREF_CATEGORY_SALOME" ) );
1149 int obTab = pref->addPreference( tr( "PREF_TAB_OBJBROWSER" ), salomeCat );
1150 int defCols = pref->addPreference( tr( "PREF_GROUP_DEF_COLUMNS" ), obTab );
1151 for ( int i = SalomeApp_DataObject::EntryId; i < SalomeApp_DataObject::LastId; i++ )
1153 pref->addPreference( tr( QString().sprintf( "OBJ_BROWSER_COLUMN_%d", i-SalomeApp_DataObject::EntryId ).toLatin1() ), defCols,
1154 LightApp_Preferences::Bool, "ObjectBrowser", QString().sprintf( "visibility_column_id_%d", i-1 ) );
1156 pref->setItemProperty( "orientation", Qt::Vertical, defCols );
1158 // adding preference to LightApp_Application handled preferences.. a bit of hacking with resources..
1159 int genTab = pref->addPreference( LightApp_Application::tr( "PREF_TAB_GENERAL" ), salomeCat );
1160 int studyGroup = pref->addPreference( LightApp_Application::tr( "PREF_GROUP_STUDY" ), genTab );
1161 pref->addPreference( tr( "PREF_STORE_VISUAL_STATE" ), studyGroup, LightApp_Preferences::Bool, "Study", "store_visual_state" );
1162 pref->addPreference( "", studyGroup, LightApp_Preferences::Space );
1163 pref->addPreference( tr( "PREF_PYDUMP_PUBLISH" ), studyGroup, LightApp_Preferences::Bool, "Study", "pydump_publish" );
1164 pref->addPreference( tr( "PREF_PYDUMP_MULTI_FILE" ), studyGroup, LightApp_Preferences::Bool, "Study", "multi_file_dump" );
1165 pref->addPreference( tr( "PREF_PYDUMP_SAVE_GUI" ), studyGroup, LightApp_Preferences::Bool, "Study", "pydump_save_gui" );
1166 pref->addPreference( "", studyGroup, LightApp_Preferences::Space );
1167 pref->addPreference( "", studyGroup, LightApp_Preferences::Space );
1170 /*!Update desktop title.*/
1171 void SalomeApp_Application::updateDesktopTitle() {
1172 QString aTitle = applicationName();
1173 QString aVer = applicationVersion();
1174 if ( !aVer.isEmpty() )
1175 aTitle += QString( " " ) + aVer;
1177 if ( activeStudy() )
1179 QString sName = SUIT_Tools::file( activeStudy()->studyName().trimmed(), false );
1180 if ( !sName.isEmpty() ) {
1181 if ( getStudy()->GetProperties()->IsLocked() ) {
1182 aTitle += QString( " - [%1 (%2)]").arg( sName ).arg( tr( "STUDY_LOCKED" ) );
1184 aTitle += QString( " - [%1]" ).arg( sName );
1189 desktop()->setWindowTitle( aTitle );
1192 int SalomeApp_Application::closeChoice( const QString& docName )
1194 QStringList buttons;
1195 QMap<int, int> choices;
1197 buttons << tr ("APPCLOSE_SAVE"); // Save & Clear
1198 choices.insert( idx++, CloseSave ); // ...
1199 buttons << tr ("APPCLOSE_CLOSE"); // Clear w/o saving
1200 choices.insert( idx++, CloseDiscard ); // ...
1201 if ( myIsCloseFromExit ) {
1202 buttons << tr ("APPCLOSE_UNLOAD_SAVE"); // Save & Disconnect
1203 choices.insert( idx++, CloseDisconnectSave ); // ...
1204 buttons << tr ("APPCLOSE_UNLOAD"); // Disconnect
1205 choices.insert( idx++, CloseDisconnect ); // ...
1207 buttons << tr ("APPCLOSE_CANCEL"); // Cancel
1208 choices.insert( idx++, CloseCancel ); // ...
1210 if( !activeStudy()->isModified() )
1212 int answer = SUIT_MessageBox::question( desktop(), tr( "APPCLOSE_CAPTION" ),
1213 tr( "APPCLOSE_DESCRIPTION" ), buttons, 0 );
1214 return choices[answer];
1217 bool SalomeApp_Application::closeAction( const int choice, bool& closePermanently )
1223 if ( activeStudy()->isSaved() )
1225 else if ( !onSaveAsDoc() )
1230 case CloseDisconnectSave:
1231 if ( activeStudy()->isSaved() )
1233 else if ( !onSaveAsDoc() )
1235 case CloseDisconnect:
1236 closeActiveDoc( false );
1237 closePermanently = false;
1246 int SalomeApp_Application::openChoice( const QString& aName )
1248 int choice = LightApp_Application::openChoice( aName );
1250 if ( QFileInfo( aName ).exists() ) {
1251 if ( choice == OpenNew ) { // The document isn't already open.
1253 if ( aName == getStudy()->Name().c_str() )
1255 // The document already exists in the study.
1256 // Do you want to reload it?
1258 int answer = SUIT_MessageBox::question( desktop(), tr( "WRN_WARNING" ), tr( "QUE_DOC_ALREADYEXIST" ).arg( aName ),
1259 SUIT_MessageBox::Yes | SUIT_MessageBox::No, SUIT_MessageBox::No );
1260 if ( answer == SUIT_MessageBox::Yes )
1261 choice = OpenRefresh;
1263 choice = OpenCancel;
1266 } else { // file is not exist on disk
1267 SUIT_MessageBox::warning( desktop(),
1268 QObject::tr("WRN_WARNING"),
1269 QObject::tr("WRN_FILE_NOT_EXIST").arg(aName.toUtf8().data()));
1276 bool SalomeApp_Application::openAction( const int aChoice, const QString& aName )
1279 int choice = aChoice;
1287 res = LightApp_Application::openAction( choice, aName );
1295 \brief Get map of the operations which can be performed
1296 on the module activation.
1298 The method should return the map of the kind \c {<id>:<name>}
1299 where \c <id> is an integer identifier of the operation and
1300 \c <name> is a title for the button to be added to the
1301 dialog box. After user selects the required operation by the
1302 clicking the corresponding button in the dialog box, its identifier
1303 is passed to the moduleActionSelected() method to process
1306 \return map of the operations
1307 \sa moduleActionSelected()
1309 QMap<int, QString> SalomeApp_Application::activateModuleActions() const
1311 QMap<int, QString> opmap = LightApp_Application::activateModuleActions();
1313 opmap.insert( LoadStudyId, tr( "ACTIVATE_MODULE_OP_LOAD" ) );
1315 opmap.insert( NewAndScriptId, tr( "ACTIVATE_MODULE_OP_SCRIPT" ) );
1320 \brief Called when the used selectes required operation chosen
1321 from "Activate module" dialog box.
1323 Performs the required operation according to the user choice.
1325 \param id operation identifier
1326 \sa activateModuleActions()
1328 void SalomeApp_Application::moduleActionSelected( const int id )
1334 case NewAndScriptId:
1338 LightApp_Application::moduleActionSelected( id );
1343 /*!Gets CORBA::ORB_var*/
1344 CORBA::ORB_var SalomeApp_Application::orb()
1346 static CORBA::ORB_var _orb;
1348 if ( CORBA::is_nil( _orb ) ) {
1349 Qtx::CmdLineArgs args;
1350 ORB_INIT& init = *SINGLETON_<ORB_INIT>::Instance();
1351 _orb = init( args.argc(), args.argv() );
1357 /*!Create and return SALOMEDS_Study.*/
1358 _PTR(Study) SalomeApp_Application::getStudy()
1360 static _PTR(Study) _study;
1362 CORBA::Object_var aSObject = namingService()->Resolve("/Study");
1363 SALOMEDS::Study_var aStudy = SALOMEDS::Study::_narrow(aSObject);
1364 _study = ClientFactory::Study(aStudy);
1369 /*!Create and return SALOME_NamingService.*/
1370 SALOME_NamingService* SalomeApp_Application::namingService()
1372 static SALOME_NamingService _ns(orb());
1376 /*!Create and return SALOME_LifeCycleCORBA.*/
1377 SALOME_LifeCycleCORBA* SalomeApp_Application::lcc()
1379 static SALOME_LifeCycleCORBA _lcc( namingService() );
1383 /*!Private SLOT. On preferences.*/
1384 void SalomeApp_Application::onProperties()
1386 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>( activeStudy() );
1390 _PTR(StudyBuilder) SB = study->studyDS()->NewBuilder();
1393 SalomeApp_StudyPropertiesDlg aDlg( desktop() );
1394 int res = aDlg.exec();
1395 if( res==QDialog::Accepted && aDlg.isChanged() )
1396 SB->CommitCommand();
1400 //study->updateCaptions();
1401 updateDesktopTitle();
1405 /*!Insert items in popup, which necessary for current application*/
1406 void SalomeApp_Application::contextMenuPopup( const QString& type, QMenu* thePopup, QString& title )
1408 LightApp_SelectionMgr* mgr = selectionMgr();
1409 bool cacheIsOn = mgr->isSelectionCacheEnabled();
1410 mgr->setSelectionCacheEnabled( true );
1412 LightApp_Application::contextMenuPopup( type, thePopup, title );
1414 // temporary commented
1415 /*OB_Browser* ob = objectBrowser();
1416 if ( !ob || type != ob->popupClientType() )
1419 // Get selected objects
1420 SALOME_ListIO aList;
1421 mgr->selectedObjects( aList, QString(), false );
1423 // add GUI state commands: restore, rename
1424 if ( aList.Extent() == 1 && aList.First()->hasEntry() &&
1425 QString( aList.First()->getEntry() ).startsWith( tr( "SAVE_POINT_DEF_NAME" ) ) ) {
1426 thePopup->addSeparator();
1427 thePopup->addAction( tr( "MEN_RESTORE_VS" ), this, SLOT( onRestoreGUIState() ) );
1428 thePopup->addAction( tr( "MEN_RENAME_VS" ), objectBrowser(),
1429 SLOT( onStartEditing() ), objectBrowser()->shortcutKey(SUIT_DataBrowser::RenameShortcut) );
1430 thePopup->addAction( tr( "MEN_DELETE_VS" ), this, SLOT( onDeleteGUIState() ) );
1433 // "Delete reference" item should appear only for invalid references
1435 // isInvalidRefs will be true, if at least one of selected objects is invalid reference
1436 bool isInvalidRefs = false;
1438 _PTR(SObject) anObj;
1439 for( SALOME_ListIteratorOfListIO it( aList ); it.More() && !isInvalidRefs; it.Next() )
1441 if( it.Value()->hasEntry() )
1443 _PTR(SObject) aSObject = getStudy()->FindObjectID( it.Value()->getEntry() ), aRefObj = aSObject;
1444 while( aRefObj && aRefObj->ReferencedObject( anObj ) )
1447 if( aRefObj && aRefObj!=aSObject && QString( aRefObj->GetName().c_str() ).isEmpty() )
1448 isInvalidRefs = true;
1452 // Add "Delete reference" item to popup
1453 if ( isInvalidRefs )
1455 thePopup->addSeparator();
1456 thePopup->addAction( tr( "MEN_DELETE_INVALID_REFERENCE" ), this, SLOT( onDeleteInvalidReferences() ) );
1460 // "Activate module" item should appear only if it's necessary
1461 if ( aList.Extent() == 1 ) {
1463 mgr->selectedObjects( aList );
1465 Handle(SALOME_InteractiveObject) aIObj = aList.First();
1467 // add extra popup menu (defined in XML)
1468 if ( myExtActions.size() > 0 ) {
1469 // Use only first selected object
1470 _PTR(SObject) aSO = getStudy()->FindObjectID( aIObj->getEntry() );
1472 _PTR( GenericAttribute ) anAttr;
1473 std::string auid = "AttributeUserID";
1474 auid += Kernel_Utils::GetGUID(Kernel_Utils::ObjectdID);
1475 if ( aSO->FindAttribute( anAttr, auid ) ) {
1476 _PTR(AttributeUserID) aAttrID = anAttr;
1477 QString aId = aAttrID->Value().c_str();
1478 if ( myExtActions.contains( aId ) ) {
1479 thePopup->addAction(myExtActions[aId]);
1485 // check if item is a "GUI state" item (also a first level object)
1486 QString entry( aIObj->getEntry() );
1487 if ( !entry.startsWith( tr( "SAVE_POINT_DEF_NAME" ) ) ) {
1488 QString aModuleName( aIObj->getComponentDataType() );
1489 QString aModuleTitle = moduleTitle( aModuleName );
1490 CAM_Module* currentModule = activeModule();
1491 if ( ( !currentModule || currentModule->moduleName() != aModuleTitle ) && !aModuleTitle.isEmpty() )
1492 thePopup->addAction( tr( "MEN_OPENWITH" ).arg( aModuleTitle ), this, SLOT( onOpenWith() ) );
1496 mgr->setSelectionCacheEnabled( cacheIsOn );
1499 /*!Update obect browser:
1500 1.if 'updateModels' true, update existing data models;
1501 2. update "non-existing" (not loaded yet) data models;
1502 3. update object browser if it exists */
1503 void SalomeApp_Application::updateObjectBrowser( const bool updateModels )
1505 // update "non-existing" (not loaded yet) data models
1506 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>(activeStudy());
1509 for ( _PTR(SComponentIterator) it ( getStudy()->NewComponentIterator() ); it->More(); it->Next() )
1511 _PTR(SComponent) aComponent ( it->Value() );
1513 #ifndef WITH_SALOMEDS_OBSERVER
1514 // with GUI observers this check is not needed anymore
1515 if ( aComponent->ComponentDataType() == study->getVisualComponentName().toLatin1().constData() )
1516 continue; // skip the magic "Interface Applicative" component
1518 if ( !objectBrowser() )
1519 getWindow( WT_ObjectBrowser );
1520 const bool isAutoUpdate = objectBrowser()->autoUpdate();
1521 objectBrowser()->setAutoUpdate( false );
1522 SalomeApp_DataModel::synchronize( aComponent, study );
1523 objectBrowser()->setAutoUpdate( isAutoUpdate );
1527 // create data objects that correspond to GUI state save points
1528 if ( study ) updateSavePointDataObjects( study );
1530 // update existing data models (already loaded SComponents)
1531 LightApp_Application::updateObjectBrowser( updateModels );
1534 /*!Display Catalog Genenerator dialog */
1535 void SalomeApp_Application::onCatalogGen()
1537 ToolsGUI_CatalogGeneratorDlg aDlg( desktop() );
1541 /*!Display Registry Display dialog */
1542 void SalomeApp_Application::onRegDisplay()
1544 CORBA::ORB_var anOrb = orb();
1545 ToolsGUI_RegWidget* regWnd = ToolsGUI_RegWidget::GetRegWidget( anOrb, desktop() );
1548 regWnd->activateWindow();
1551 /*!find original object by double click on item */
1552 void SalomeApp_Application::onDblClick( SUIT_DataObject* theObj )
1554 // Issue 21379: References are supported at LightApp_DataObject level
1555 LightApp_DataObject* obj = dynamic_cast<LightApp_DataObject*>( theObj );
1557 if( obj && obj->isReference() )
1559 QString entry = obj->refEntry();
1561 SUIT_DataOwnerPtrList aList;
1562 aList.append( new LightApp_DataOwner( entry ) );
1563 selectionMgr()->setSelected( aList, false );
1565 SUIT_DataBrowser* ob = objectBrowser();
1567 QModelIndexList aSelectedIndexes = ob->selectedIndexes();
1568 if ( !aSelectedIndexes.isEmpty() )
1569 ob->treeView()->scrollTo( aSelectedIndexes.first() );
1571 emit objectDoubleClicked( theObj );
1575 Creates new view manager
1576 \param type - type of view manager
1578 SUIT_ViewManager* SalomeApp_Application::newViewManager(const QString& type)
1580 return createViewManager(type);
1584 /*!Global utility function, returns selected GUI Save point object's ID */
1585 int getSelectedSavePoint( const LightApp_SelectionMgr* selMgr )
1587 SALOME_ListIO aList;
1588 selMgr->selectedObjects( aList );
1589 if( aList.Extent() > 0 ) {
1590 Handle(SALOME_InteractiveObject) aIObj = aList.First();
1591 QString entry( aIObj->getEntry() );
1592 QString startStr = QObject::tr( "SAVE_POINT_DEF_NAME" );
1593 if ( !entry.startsWith( startStr ) ) // it's a "GUI state" object
1595 bool ok; // conversion to integer is ok?
1596 int savePoint = entry.right( entry.length() - startStr.length() ).toInt( &ok );
1597 return ok ? savePoint : -1;
1602 /*!Called on Restore GUI State popup command*/
1603 void SalomeApp_Application::onRestoreGUIState()
1605 int savePoint = ::getSelectedSavePoint( selectionMgr() );
1606 if ( savePoint == -1 )
1608 SalomeApp_VisualState( this ).restoreState( savePoint );
1611 /*!Called on Delete GUI State popup command*/
1612 void SalomeApp_Application::onDeleteGUIState()
1614 int savePoint = ::getSelectedSavePoint( selectionMgr() );
1615 if ( savePoint == -1 )
1617 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>( activeStudy() );
1621 study->removeSavePoint( savePoint );
1622 updateSavePointDataObjects( study );
1625 /*!Called on New study operation*/
1626 void SalomeApp_Application::onStudyCreated( SUIT_Study* study )
1628 LightApp_Application::onStudyCreated( study );
1630 //#ifndef DISABLE_PYCONSOLE
1631 // desktop()->tabifyDockWidget( windowDock( getWindow( WT_NoteBook ) ),
1632 // windowDock( getWindow( WT_ObjectBrowser ) ) );
1635 loadDockWindowsState();
1637 objectBrowserColumnsVisibility();
1640 /*!Called on Open study operation*/
1641 void SalomeApp_Application::onStudyOpened( SUIT_Study* study )
1643 LightApp_Application::onStudyOpened( study );
1645 //#ifndef DISABLE_PYCONSOLE
1646 // desktop()->tabifyDockWidget( windowDock( getWindow( WT_NoteBook ) ),
1647 // windowDock( getWindow( WT_ObjectBrowser ) ) );
1650 loadDockWindowsState();
1652 objectBrowserColumnsVisibility();
1654 // temporary commented
1655 /*if ( objectBrowser() ) {
1656 updateSavePointDataObjects( dynamic_cast<SalomeApp_Study*>( study ) );
1657 objectBrowser()->updateTree( study->root() );
1661 /*! updateSavePointDataObjects: syncronize data objects that correspond to save points (gui states)*/
1662 void SalomeApp_Application::updateSavePointDataObjects( SalomeApp_Study* study )
1665 SUIT_DataBrowser* ob = objectBrowser();
1666 LightApp_SelectionMgr* selMgr = selectionMgr();
1668 if ( !study || !ob || !selMgr )
1671 // find GUI states root object
1672 SUIT_DataObject* guiRootObj = 0;
1674 study->root()->children( ch );
1675 DataObjectList::const_iterator it = ch.begin(), last = ch.end();
1676 for ( ; it != last ; ++it ) {
1677 if ( dynamic_cast<SalomeApp_SavePointRootObject*>( *it ) ) {
1682 std::vector<int> savePoints = study->getSavePoints();
1683 // case 1: no more save points but they existed in study's tree
1684 if ( savePoints.empty() && guiRootObj ) {
1685 //rnv : to fix bug "IPAL22450 TC6.3.0: sigsegv loop deleting the GUI state"
1686 // : set auto update to true for removing SalomeApp_SavePointRootObject from the SUIT_TreeModel
1687 const bool isAutoUpdate = ob->autoUpdate();
1688 selMgr->clearSelected();
1689 ob->setAutoUpdate(true);
1690 DataObjectList ch = guiRootObj->children();
1691 for( int i = 0; i < ch.size(); i++ )
1694 ob->setAutoUpdate(isAutoUpdate);
1697 // case 2: no more save points but root does not exist either
1698 if ( savePoints.empty() && !guiRootObj )
1700 // case 3: save points but no root for them - create it
1701 if ( !savePoints.empty() && !guiRootObj )
1702 guiRootObj = new SalomeApp_SavePointRootObject( study->root() );
1703 // case 4: everything already exists.. here may be a problem: we want "GUI states" root object
1704 // to be always the last one in the tree. Here we check - if it is not the last one - remove and
1706 if ( guiRootObj->nextBrother() ) {
1707 study->root()->removeChild(guiRootObj);
1708 study->root()->appendChild(guiRootObj);
1709 //study->root()->dump();
1712 // store data objects in a map id-to-DataObject
1713 QMap<int,SalomeApp_SavePointObject*> mapDO;
1715 guiRootObj->children( ch );
1716 for( it = ch.begin(), last = ch.end(); it != last ; ++it ) {
1717 SalomeApp_SavePointObject* dobj = dynamic_cast<SalomeApp_SavePointObject*>( *it );
1719 mapDO[dobj->getId()] = dobj;
1722 // iterate new save points. if DataObject with such ID not found in map - create DataObject
1723 // if in the map - remove it from map.
1724 for ( size_t i = 0; i < savePoints.size(); i++ )
1725 if ( !mapDO.contains( savePoints[i] ) )
1726 new SalomeApp_SavePointObject( guiRootObj, savePoints[i], study );
1728 mapDO.remove( savePoints[i] );
1730 // delete DataObjects that are still in the map -- their IDs were not found in data model
1731 if( mapDO.size() > 0) {
1732 //rnv : to fix bug "IPAL22450 TC6.3.0: sigsegv loop deleting the GUI state"
1733 // : set auto update to true for removing SalomeApp_SavePointObject from the SUIT_TreeModel
1734 selMgr->clearSelected();
1735 const bool isAutoUpdate = ob->autoUpdate();
1736 ob->setAutoUpdate(true);
1737 for ( QMap<int,SalomeApp_SavePointObject*>::Iterator it = mapDO.begin(); it != mapDO.end(); ++it )
1739 ob->setAutoUpdate(isAutoUpdate);
1743 /*! Check data object */
1744 bool SalomeApp_Application::checkDataObject(LightApp_DataObject* theObj)
1753 Opens other study into active Study. If Study is empty - creates it.
1754 \param theName - name of study
1756 bool SalomeApp_Application::useStudy( const QString& theName )
1759 SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( activeStudy() );
1762 res = aStudy->loadDocument( theName );
1763 updateDesktopTitle();
1764 updateCommandsStatus();
1768 /*! Show/hide object browser colums according to preferences */
1769 void SalomeApp_Application::objectBrowserColumnsVisibility()
1771 if ( objectBrowser() )
1772 for ( int i = SalomeApp_DataObject::EntryId; i < SalomeApp_DataObject::LastId; i++ )
1774 bool shown = resourceMgr()->booleanValue( "ObjectBrowser", QString( "visibility_column_id_%1" ).arg( i-1 ), true );
1775 objectBrowser()->treeView()->setColumnHidden( i, !shown );
1779 #ifndef DISABLE_PYCONSOLE
1780 /*! Set SalomeApp_NoteBook pointer */
1781 void SalomeApp_Application::setNoteBook( SalomeApp_NoteBook* theNoteBook )
1783 myNoteBook = theNoteBook;
1786 /*! Return SalomeApp_NoteBook pointer */
1787 SalomeApp_NoteBook* SalomeApp_Application::getNoteBook() const
1794 * Define extra actions defined in module definition XML file.
1795 * Additional popup items sections can be defined by parameter "popupitems".
1796 * Supported attributes:
1797 * title - title of menu item,
1798 * attributelocalid - AttributeLocalId defined for selected data item where menu command has to be applied,
1799 * method - method which has to be called when menu item is selected
1801 * <section name="MODULENAME">
1802 * <parameter name="popupitems" value="menuitem1:menuitem2:..."/>
1804 * <section name="importmed">
1805 * <parameter name="title" value="My menu"/>
1806 * <parameter name="objectid" value="VISU.Result"/>
1807 * <parameter name="method" value="nameOfModuleMethod"/>
1810 void SalomeApp_Application::createExtraActions()
1812 myExtActions.clear();
1813 SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
1815 QStringList aModules;
1816 modules(aModules, false);
1817 foreach(QString aModile, aModules) {
1818 QString aModName = moduleName(aModile);
1819 QString aSectionStr = resMgr->stringValue(aModName, "popupitems", QString());
1820 if (!aSectionStr.isNull()) {
1821 QStringList aSections = aSectionStr.split(':');
1822 foreach(QString aSection, aSections) {
1823 QString aTitle = resMgr->stringValue(aSection, "title", QString());
1824 QString aId = resMgr->stringValue(aSection, "objectid", QString());
1825 QString aSlot = resMgr->stringValue(aSection, "method", QString());
1826 if (aTitle.isEmpty() || aSlot.isEmpty() || aId.isEmpty())
1829 QString aModuleName = resMgr->stringValue(aSection, "module", QString());
1830 if (aModuleName.isNull())
1831 aModuleName = aModName;
1833 QAction* aAction = new QAction(aTitle, this);
1835 aData<<aModuleName<<aSlot;
1836 aAction->setData(aData);
1837 connect(aAction, SIGNAL(triggered()), this, SLOT(onExtAction()));
1838 myExtActions[aId] = aAction;
1845 * Called when extra action is selected
1847 void SalomeApp_Application::onExtAction()
1849 QAction* aAction = ::qobject_cast<QAction*>(sender());
1853 QVariant aData = aAction->data();
1854 QStringList aDataList = aData.value<QStringList>();
1855 if (aDataList.size() != 2)
1858 LightApp_SelectionMgr* aSelectionMgr = selectionMgr();
1859 SALOME_ListIO aListIO;
1860 aSelectionMgr->selectedObjects(aListIO);
1861 const Handle(SALOME_InteractiveObject)& anIO = aListIO.First();
1862 if (aListIO.Extent() < 1)
1864 if (!anIO->hasEntry())
1867 QString aEntry(anIO->getEntry());
1869 QApplication::setOverrideCursor( Qt::WaitCursor );
1870 QString aModuleTitle = moduleTitle(aDataList[0]);
1871 activateModule(aModuleTitle);
1872 QApplication::restoreOverrideCursor();
1874 QCoreApplication::processEvents();
1876 CAM_Module* aModule = activeModule();
1880 if (!QMetaObject::invokeMethod(aModule, qPrintable(aDataList[1]), Q_ARG(QString, aEntry)))
1881 printf("Error: Can't Invoke method %s\n", qPrintable(aDataList[1]));
1885 Checks that an object can be renamed.
1886 \param entry entry of the object
1887 \brief Return \c true if object can be renamed
1889 bool SalomeApp_Application::renameAllowed( const QString& entry) const
1891 return entry.startsWith( tr( "SAVE_POINT_DEF_NAME") );
1895 Rename object by entry.
1896 \param entry entry of the object
1897 \param name new name of the object
1898 \brief Return \c true if rename operation finished successfully, \c false otherwise.
1900 bool SalomeApp_Application::renameObject( const QString& entry, const QString& name )
1902 SalomeApp_Study* aStudy = dynamic_cast<SalomeApp_Study*>( activeStudy() );
1904 int savePoint = ::getSelectedSavePoint( selectionMgr() );
1906 if(!aStudy || savePoint == -1)
1909 if ( !name.isNull() && !name.isEmpty() ) {
1910 aStudy->setNameOfSavePoint( savePoint, name );
1911 updateSavePointDataObjects( aStudy );
1913 //Mark study as modified
1920 #ifndef DISABLE_PYCONSOLE
1921 //============================================================================
1922 /*! Function : onUpdateStudy
1923 * Purpose : Slot to update the study.
1925 //============================================================================
1926 void SalomeApp_Application::onUpdateStudy()
1928 QApplication::setOverrideCursor( Qt::WaitCursor );
1930 if( !updateStudy() )
1931 SUIT_MessageBox::warning( desktop(), tr( "ERROR" ), tr( "ERR_UPDATE_STUDY_FAILED" ) );
1933 QApplication::restoreOverrideCursor();
1936 //============================================================================
1937 /*! Function : updateStudy
1938 * Purpose : Update study by dumping the study to Python script and loading it.
1939 * It is used to apply variable modifications done in NoteBook to created objects.
1941 //============================================================================
1942 bool SalomeApp_Application::updateStudy()
1944 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>( activeStudy() );
1945 if ( !study || !myNoteBook )
1948 myNoteBook->setIsDumpedStudySaved( study->isSaved() );
1949 myNoteBook->setDumpedStudyName( study->studyName() );
1951 // get unique temporary directory name
1952 QString aTmpDir = QString::fromStdString( SALOMEDS_Tool::GetTmpDir() );
1954 if( aTmpDir.isEmpty() )
1957 if( aTmpDir.right( 1 ).compare( QDir::separator() ) == 0 )
1958 aTmpDir.remove( aTmpDir.length() - 1, 1 );
1960 // dump study to the temporary directory
1961 QString aScriptName( "notebook" );
1962 bool toPublish = true;
1963 bool isMultiFile = false;
1964 bool toSaveGUI = true;
1967 _PTR(AttributeParameter) ap;
1968 _PTR(IParameters) ip = ClientFactory::getIParameters(ap);
1969 if(ip->isDumpPython()) ip->setDumpPython(); //Unset DumpPython flag.
1970 if ( toSaveGUI ) { //SRN: Store a visual state of the study at the save point for DumpStudy method
1971 ip->setDumpPython();
1972 savePoint = SalomeApp_VisualState( this ).storeState(); //SRN: create a temporary save point
1974 bool ok = getStudy()->DumpStudy( aTmpDir.toStdString(), aScriptName.toStdString(), toPublish, isMultiFile );
1976 study->removeSavePoint(savePoint); //SRN: remove the created temporary save point.
1979 myNoteBook->setDumpedStudyScript( aTmpDir + QDir::separator() + aScriptName + ".py" );
1983 QList<SUIT_Application*> aList = SUIT_Session::session()->applications();
1984 int anIndex = aList.indexOf( this );
1986 // Disconnect dialog from application desktop in case if:
1987 // 1) Application is not the first application in the session
1988 // 2) Application is the first application in session but not the only.
1989 bool changeDesktop = ((anIndex > 0) || (anIndex == 0 && aList.count() > 1));
1990 if( changeDesktop ) {
1992 SalomeApp_Application* app = this;
1993 if( anIndex > 0 && anIndex < aList.count() )
1994 app = dynamic_cast<SalomeApp_Application*>( aList[ anIndex - 1 ] );
1995 else if(anIndex == 0 && aList.count() > 1)
1996 app = dynamic_cast<SalomeApp_Application*>( aList[ 1 ] );
2001 // creation a new study and restoring will be done in another application
2002 connect( this, SIGNAL( dumpedStudyClosed( const QString&, const QString&, bool ) ),
2003 app, SLOT( onRestoreStudy( const QString&, const QString&, bool ) ), Qt::UniqueConnection );
2006 QString aDumpScript = myNoteBook->getDumpedStudyScript();
2007 QString aStudyName = myNoteBook->getDumpedStudyName();
2008 bool isStudySaved = myNoteBook->isDumpedStudySaved();
2009 // clear a study (delete all objects)
2010 onCloseDoc( false );
2012 if( !changeDesktop ) {
2013 ok = onRestoreStudy( aDumpScript,
2022 //============================================================================
2023 /*! Function : onRestoreStudy
2024 * Purpose : Load the dumped study from Python script
2026 //============================================================================
2027 bool SalomeApp_Application::onRestoreStudy( const QString& theDumpScript,
2028 const QString& theStudyName,
2029 bool theIsStudySaved )
2033 // create a new study
2036 // get active application
2037 SalomeApp_Application* app = dynamic_cast<SalomeApp_Application*>( SUIT_Session::session()->activeApplication() );
2039 // load study from the temporary directory
2040 QFileInfo aScriptInfo = QFileInfo(theDumpScript);
2041 QString command = QString( "exec(open(\"%1\" ,\"rb\").read())" ).arg(aScriptInfo.canonicalFilePath());
2043 #ifndef DISABLE_PYCONSOLE
2044 PyConsole_Console* pyConsole = app->pythonConsole();
2046 pyConsole->execAndWait( command );
2049 // remove temporary directory
2050 QString aStudyName = aScriptInfo.baseName();
2051 QDir aDir = aScriptInfo.absoluteDir();
2052 QStringList aFiles = aDir.entryList( QStringList( "*.py*" ) );
2053 for( QStringList::iterator it = aFiles.begin(), itEnd = aFiles.end(); it != itEnd; ++it )
2054 ok = aDir.remove( *it ) && ok;
2056 ok = aDir.rmdir( aDir.absolutePath() );
2058 if( SalomeApp_Study* newStudy = dynamic_cast<SalomeApp_Study*>( app->activeStudy() ) )
2060 #ifndef DISABLE_PYCONSOLE
2061 if ( app->getNoteBook() )
2062 app->getNoteBook()->Init();
2063 newStudy->updateFromNotebook(theStudyName, theIsStudySaved);
2064 newStudy->Modified();
2065 updateDesktopTitle();
2076 Close the Application
2078 void SalomeApp_Application::afterCloseDoc()
2080 #ifndef DISABLE_PYCONSOLE
2081 // emit signal to restore study from Python script
2083 emit dumpedStudyClosed( myNoteBook->getDumpedStudyScript(),
2084 myNoteBook->getDumpedStudyName(),
2085 myNoteBook->isDumpedStudySaved() );
2088 LightApp_Application::afterCloseDoc();
2091 bool SalomeApp_Application::canOpenDoc( const QString& url )
2093 _PTR(Study) aStudyDS = getStudy();
2095 return aStudyDS->CanOpen( url.toUtf8().data() );
2100 Asks to close existing document.
2102 bool SalomeApp_Application::checkExistingDoc()
2104 return LightApp_Application::checkExistingDoc();
2108 #ifndef DISABLE_PYCONSOLE
2110 PyConsole_Interp* SalomeApp_Application::createPyInterp()
2112 return new SalomeApp_PyInterp;
2115 #endif // DISABLE_PYCONSOLE