1 // Copyright (C) 2007-2012 CEA/DEN, EDF R&D, OPEN CASCADE
3 // This library is free software; you can redistribute it and/or
4 // modify it under the terms of the GNU Lesser General Public
5 // License as published by the Free Software Foundation; either
6 // version 2.1 of the License.
8 // This library is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 // Lesser General Public License for more details.
13 // You should have received a copy of the GNU Lesser General Public
14 // License along with this library; if not, write to the Free Software
15 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
20 // File : GEOMToolsGUI_MaterialPropertiesDlg.cxx
21 // Author : Margarita KARPUNINA, Open CASCADE S.A.S. (margarita.karpunina@opencascade.com)
23 #include "GEOMToolsGUI_MaterialPropertiesDlg.h"
25 #include "GeometryGUI.h"
26 #include "GEOM_Constants.h"
27 #include "GEOM_VTKPropertyMaterial.hxx"
30 #include <QtxColorButton.h>
31 #include <QtxDoubleSpinBox.h>
32 #include <SUIT_Desktop.h>
33 #include <SUIT_MessageBox.h>
34 #include <SUIT_OverrideCursor.h>
35 #include <SUIT_ResourceMgr.h>
36 #include <SUIT_Session.h>
37 #include <SUIT_ViewManager.h>
38 #include <SALOME_ListIteratorOfListIO.hxx>
39 #include <OCCViewer_ViewModel.h>
40 #include <SVTK_ViewModel.h>
41 #include <SVTK_ViewWindow.h>
42 #include <SVTK_View.h>
43 #include <LightApp_SelectionMgr.h>
44 #include <SalomeApp_Application.h>
45 #include <SalomeApp_Study.h>
46 #include <SalomeApp_Tools.h>
49 #include <QGridLayout>
50 #include <QHBoxLayout>
55 #include <QPushButton>
56 #include <QVBoxLayout>
58 #define MARGIN 9 // layout spacing
59 #define SPACING 6 // layout margin
62 \class GEOMToolsGUI_MaterialList
63 \brief Internal class, used to handle context menu event from materials list
70 GEOMToolsGUI_MaterialList::GEOMToolsGUI_MaterialList( QWidget* parent )
71 : QListWidget( parent )
75 \brief Context menu event, emits context menu signal passing event as parameter
77 void GEOMToolsGUI_MaterialList::contextMenuEvent( QContextMenuEvent* e )
79 emit contextMenu( e );
83 \class GEOMToolsGUI_MaterialPropertiesDlg
84 \brief GEOM material properties dialog box class
86 The dialog box is used to set material properties for the presentation objects.
91 \param parent parent widget
93 GEOMToolsGUI_MaterialPropertiesDlg::GEOMToolsGUI_MaterialPropertiesDlg( QWidget* parent )
94 : QtxDialog( parent, true, true, OK | Close | Apply | Help )
97 setWindowTitle( tr( "MATERIAL_PROPERTIES_TLT" ) );
100 QVBoxLayout* main = new QVBoxLayout( mainFrame() );
101 main->setMargin( 0 );
102 main->setSpacing( SPACING );
104 // add top-level frame box to enclose all main widgets (excluding buttons)
105 QFrame* fr = new QFrame( mainFrame() );
106 fr->setFrameStyle( QFrame::Box | QFrame::Sunken );
107 main->addWidget( fr );
109 // materials list widget
110 myMaterials = new GEOMToolsGUI_MaterialList( fr );
112 QWidget* propWidget = new QWidget( fr );
114 // layout main widgets
115 QHBoxLayout* frLayout = new QHBoxLayout( fr );
116 frLayout->setMargin( MARGIN );
117 frLayout->setSpacing( SPACING );
118 frLayout->addWidget( myMaterials );
119 frLayout->addWidget( propWidget );
121 // layout for material properties widgets
122 QGridLayout* propLayout = new QGridLayout( propWidget );
123 propLayout->setMargin( 0 );
124 propLayout->setSpacing( SPACING );
126 // current color widgets
127 myColorLab = new QLabel( tr( "CURRENT_COLOR" ), propWidget );
128 myColor = new QtxColorButton( propWidget );
130 // "physical" material type widgets
131 myPhysical = new QCheckBox( tr( "PHYSICAL" ), propWidget );
133 // reflection components widgets
134 for ( int i = Material_Model::Ambient; i <= Material_Model::Emissive; i++ )
138 refl.label = new QLabel( tr( QString( "REFLECTION_%1" ).arg( i ).toLatin1().data() ), propWidget );
140 refl.color = new QtxColorButton( propWidget );
141 //refl.color->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) );
143 refl.coef = new QtxDoubleSpinBox( propWidget );
144 refl.coef->setPrecision( 4 );
145 refl.coef->setDecimals( 4 );
146 refl.coef->setRange( 0., 1. );
147 refl.coef->setSingleStep( 0.05 );
148 refl.coef->setMinimumWidth( 80 );
150 refl.enabled = new QCheckBox( tr( "ENABLED" ), propWidget );
152 myReflection << refl;
156 QLabel* shininessLab = new QLabel( tr( "SHININESS" ), propWidget );
157 myShininess = new QtxDoubleSpinBox( propWidget );
158 myShininess->setPrecision( 4 );
159 myShininess->setDecimals( 4 );
160 myShininess->setRange( 0., 1. );
161 myShininess->setSingleStep( 0.05 );
164 QFrame* line1 = new QFrame( propWidget );
165 line1->setFrameStyle( QFrame::HLine | QFrame::Sunken );
166 QFrame* line2 = new QFrame( propWidget );
167 line2->setFrameStyle( QFrame::HLine | QFrame::Sunken );
169 // add / remove material buttons
170 myAddButton = new QPushButton( tr( "ADD_MATERIAL" ), propWidget );
171 myDelButton = new QPushButton( tr( "DELETE_MATERIAL" ), propWidget );
174 QHBoxLayout* btnLayout = new QHBoxLayout;
175 btnLayout->setMargin( 0 );
176 btnLayout->setSpacing( SPACING );
177 btnLayout->addWidget( myAddButton );
178 btnLayout->addWidget( myDelButton );
180 // layout all properties widgets together
181 propLayout->addWidget( myColorLab, 0, 0 );
182 propLayout->addWidget( myColor, 0, 1 );
183 propLayout->addWidget( line1, 1, 0, 1, 4 );
184 propLayout->addWidget( myPhysical, 2, 0, 1, 2 );
185 for ( int i = Material_Model::Ambient; i <= Material_Model::Emissive; i++ ) {
186 propLayout->addWidget( myReflection[i].label, i+3, 0 );
187 propLayout->addWidget( myReflection[i].color, i+3, 1 );
188 propLayout->addWidget( myReflection[i].coef, i+3, 2 );
189 propLayout->addWidget( myReflection[i].enabled, i+3, 3 );
191 propLayout->addWidget( shininessLab, 7, 0 );
192 propLayout->addWidget( myShininess, 7, 2 );
193 propLayout->addWidget( line2, 8, 0, 1, 4 );
194 propLayout->setRowStretch( 9, 5 );
195 propLayout->addLayout( btnLayout, 10, 0, 1, 4 );
197 // initialize dialog box
198 setButtonPosition( Right, Close );
200 // fill in the list of materials
201 QStringList globalMaterials = myResourceMgr.materials( Material_ResourceMgr::Global );
202 QStringList userMaterials = myResourceMgr.materials( Material_ResourceMgr::User );
204 QListWidgetItem* item;
206 // - current material
207 item = new QListWidgetItem( tr( "CURRENT_MATERIAL" ) );
208 item->setForeground( QColor( Qt::red ) );
209 item->setData( TypeRole, QVariant( Current ) );
210 myMaterials->addItem( item );
211 // - global materials
212 foreach( QString material, globalMaterials ) {
213 item = new QListWidgetItem( material );
214 item->setForeground( QColor( Qt::blue ) );
215 item->setData( TypeRole, QVariant( Global ) );
216 item->setData( NameRole, QVariant( material ) );
217 myMaterials->addItem( item );
220 foreach ( QString material, userMaterials ) {
221 item = new QListWidgetItem( material );
222 item->setData( TypeRole, QVariant( User ) );
223 item->setData( NameRole, QVariant( material ) );
224 item->setFlags( item->flags() | Qt::ItemIsEditable );
225 myMaterials->addItem( item );
227 // install event filter to the materials list to process key press events
228 myMaterials->installEventFilter( this );
231 // note: all widgets, that change material properties, are connected to the common signal
232 // changed(), instead of connecting directly to the slot - this allows easy temporary blocking
233 // of the change signal
234 connect( myPhysical, SIGNAL( toggled( bool ) ), this, SIGNAL( changed() ) );
235 for ( int i = Material_Model::Ambient; i <= Material_Model::Emissive; i++ ) {
236 connect( myReflection[i].color, SIGNAL( changed( QColor ) ), this, SIGNAL( changed() ) );
237 connect( myReflection[i].coef, SIGNAL( valueChanged( double ) ), this, SIGNAL( changed() ) );
238 connect( myReflection[i].enabled, SIGNAL( toggled( bool ) ), this, SIGNAL( changed() ) );
240 connect( myShininess, SIGNAL( valueChanged( double ) ), this, SIGNAL( changed() ) );
241 connect( myMaterials, SIGNAL( itemSelectionChanged() ),
242 this, SLOT( onMaterialChanged() ) );
243 connect( myMaterials, SIGNAL( itemChanged( QListWidgetItem* ) ),
244 this, SLOT( onItemChanged( QListWidgetItem* ) ) );
245 connect( myMaterials, SIGNAL( contextMenu( QContextMenuEvent* ) ),
246 this, SLOT( onContextMenu( QContextMenuEvent* ) ) );
247 connect( myAddButton, SIGNAL( clicked() ), this, SLOT( onAddMaterial() ) );
248 connect( myDelButton, SIGNAL( clicked() ), this, SLOT( onDeleteMaterial() ) );
249 connect( this, SIGNAL( dlgApply() ), this, SLOT( onApply() ) );
250 connect( this, SIGNAL( dlgHelp() ), this, SLOT( onHelp() ) );
251 connect( this, SIGNAL( changed() ), this, SLOT( onChanged() ) );
253 // initialize current material model according to the selection
254 myColor->setColor( SUIT_Session::session()->resourceMgr()->colorValue( "Geometry", "shading_color", QColor( 255, 0, 0 ) ) );
255 myCurrentModel.fromResources( SUIT_Session::session()->resourceMgr()->stringValue( "Geometry", "material", "Plastic" ) );
256 SalomeApp_Application* app = dynamic_cast< SalomeApp_Application* >( SUIT_Session::session()->activeApplication() );
257 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>( app->activeStudy() );
258 LightApp_SelectionMgr* selMgr = app->selectionMgr();
260 SALOME_ListIO selected;
261 selMgr->selectedObjects( selected );
262 if ( selected.Extent() > 0 ) {
263 Handle(SALOME_InteractiveObject) io = selected.First();
264 if ( !io.IsNull() ) {
265 SUIT_ViewWindow* window = app->desktop()->activeWindow();
267 int mgrId = window->getViewManager()->getGlobalId();
268 PropMap propMap = study->getObjectPropMap( mgrId, io->getEntry() );
269 QString matProp = propMap.value(MATERIAL_PROP).toString();
270 if ( !matProp.isEmpty() )
271 myCurrentModel.fromProperties( matProp );
272 QColor c = propMap.value(COLOR_PROP).value<QColor>();
274 myColor->setColor( c );
280 // finally activate current material properties
281 myMaterials->setCurrentRow( 0 );
287 GEOMToolsGUI_MaterialPropertiesDlg::~GEOMToolsGUI_MaterialPropertiesDlg()
292 \brief Called when "OK" button is clicked
294 void GEOMToolsGUI_MaterialPropertiesDlg::accept()
305 bool GEOMToolsGUI_MaterialPropertiesDlg::eventFilter( QObject* o, QEvent* e )
307 // process key press event from materials list
308 if ( o == myMaterials && e->type() == QEvent::KeyPress ) {
309 QKeyEvent* ke = (QKeyEvent*)e;
310 if ( ke->key() == Qt::Key_Delete ) {
314 return QtxDialog::eventFilter( o, e );
318 \brief Initialize dialog box widgets from material model
319 \param model material model
321 void GEOMToolsGUI_MaterialPropertiesDlg::fromModel( const Material_Model& model )
323 // reflection components
324 for ( int i = Material_Model::Ambient; i <= Material_Model::Emissive; i++ )
326 myReflection[i].color->setColor( model.color( (Material_Model::ReflectionType)i ) );
327 myReflection[i].coef->setValue( model.reflection( (Material_Model::ReflectionType)i ) );
328 myReflection[i].enabled->setChecked( model.hasReflection( (Material_Model::ReflectionType)i ) );
332 myShininess->setValue( model.shininess() );
334 // type (physical or no)
335 myPhysical->setChecked( model.isPhysical() );
339 \brief Save values from dialog box widgets to material model
340 \param model material model to be filled in
342 void GEOMToolsGUI_MaterialPropertiesDlg::toModel( Material_Model& model ) const
344 // type (physical or no)
345 model.setPhysical( myPhysical->isChecked() );
348 model.setShininess( myShininess->value() );
350 // reflection components
351 for ( int i = Material_Model::Ambient; i <= Material_Model::Emissive; i++ )
353 model.setColor ( (Material_Model::ReflectionType)i, myReflection[i].color->color() );
354 model.setReflection( (Material_Model::ReflectionType)i, myReflection[i].coef->value() );
355 model.setReflection( (Material_Model::ReflectionType)i, myReflection[i].enabled->isChecked() );
360 \brief Find unique name for the material name
361 \param name material name template
362 \param item the item to be ignored when browsing through the materials list
363 \param addSuffix if \c true, the integrer suffix is always added to the material name (otherwise
364 suffix is added only if item name is not unique)
365 \return new unique material name
367 QString GEOMToolsGUI_MaterialPropertiesDlg::findUniqueName( const QString& name, QListWidgetItem* item, bool addSuffix )
371 for( int i = 1; i < myMaterials->count(); i++ ) {
372 if ( item == myMaterials->item( i ) ) continue;
373 QString iname = myMaterials->item( i )->text();
374 if ( iname == name ) {
377 else if ( iname.startsWith( name ) ) {
378 iname = iname.mid( name.length() ).trimmed();
380 int nx = iname.toInt( &ok );
381 if ( ok ) idx = qMax( idx, nx );
384 return found || addSuffix ? QString( "%1 %2" ).arg( name ).arg( idx+1 ) : name;
388 \brief Called when "Apply" button is pressed
390 void GEOMToolsGUI_MaterialPropertiesDlg::onApply()
392 // save user materials
393 myResourceMgr.save();
395 // store selected material properties in the current model
396 toModel( myCurrentModel );
398 SalomeApp_Application* app = dynamic_cast< SalomeApp_Application* >( SUIT_Session::session()->activeApplication() );
399 LightApp_SelectionMgr* selMgr = app->selectionMgr();
400 SalomeApp_Study* study = dynamic_cast<SalomeApp_Study*>( app->activeStudy() );
402 if ( !study ) return;
405 SALOME_ListIO selected;
406 selMgr->selectedObjects( selected );
407 if ( selected.IsEmpty() ) return;
409 SUIT_ViewWindow* window = app->desktop()->activeWindow();
410 int mgrId = window->getViewManager()->getGlobalId();
412 // convert current material properties to the string representation
413 QString prop = myCurrentModel.toProperties();
415 if ( window && window->getViewManager()->getType() == SVTK_Viewer::Type() ) {
417 SVTK_ViewWindow* vtkVW = dynamic_cast<SVTK_ViewWindow*>( window );
421 SVTK_View* aView = vtkVW->getView();
423 // get VTK material properties from the current model
424 GEOM_VTKPropertyMaterial* vtkProp = myCurrentModel.getMaterialVTKProperty();
426 SUIT_OverrideCursor wc();
428 for ( SALOME_ListIteratorOfListIO It( selected ); It.More(); It.Next() ) {
429 // set material property to the presentation
430 aView->SetMaterial( It.Value(), vtkProp );
431 // store chosen material in the property map
432 study->setObjectProperty( mgrId, It.Value()->getEntry(), MATERIAL_PROP, prop );
433 // set correct color for the non-physical material
434 if ( !myCurrentModel.isPhysical() ) {
435 aView->SetColor( It.Value(), myColor->color() );
436 study->setObjectProperty( mgrId, It.Value()->getEntry(), COLOR_PROP, myColor->color() );
440 GeometryGUI::Modified();
442 else if ( window && window->getViewManager()->getType() == OCCViewer_Viewer::Type() ) {
444 OCCViewer_Viewer* vm = dynamic_cast<OCCViewer_Viewer*>( window->getViewManager()->getViewModel() );
448 Handle(AIS_InteractiveContext) ic = vm->getAISContext();
450 // get OCC material aspect from the current model
451 Graphic3d_MaterialAspect occAspect = myCurrentModel.getMaterialOCCAspect();
453 SUIT_OverrideCursor wc();
455 for ( SALOME_ListIteratorOfListIO It( selected ); It.More(); It.Next() ) {
456 Handle(GEOM_AISShape) aisShape = GEOMBase::ConvertIOinGEOMAISShape( It.Value(), true );
457 if ( !aisShape.IsNull() ) {
458 // set material property to the presentation
459 aisShape->SetMaterial( occAspect );
460 // store chosen material in the property map
461 study->setObjectProperty( mgrId, It.Value()->getEntry(), MATERIAL_PROP, prop );
462 // set correct color for the non-physical material
463 if ( !myCurrentModel.isPhysical() ) {
464 aisShape->SetShadingColor( SalomeApp_Tools::color( myColor->color() ) );
465 study->setObjectProperty( mgrId, It.Value()->getEntry(), COLOR_PROP, myColor->color() );
466 ic->RecomputePrsOnly( aisShape, Standard_False );
468 //if ( aisShape->DisplayMode() != AIS_Shaded/*aisShape->DisplayMode() == GEOM_AISShape::ShadingWithEdges*/)
469 ic->Redisplay( aisShape, Standard_False );
472 ic->UpdateCurrentViewer();
473 GeometryGUI::Modified();
478 \brief Called when "Help" button is pressed
480 void GEOMToolsGUI_MaterialPropertiesDlg::onHelp()
482 LightApp_Application* app = (LightApp_Application*)(SUIT_Session::session()->activeApplication());
483 app->onHelpContextModule( "GEOM", "material_page.html" );
487 \brief Called when user selects any material item in the materials list
489 void GEOMToolsGUI_MaterialPropertiesDlg::onMaterialChanged()
491 // get currently selected item
492 QListWidgetItem* item = myMaterials->currentItem();
495 bool blocked = blockSignals( true );
497 int type = item->data( TypeRole ).toInt();
498 if ( type == Current ) {
499 // initialize widgets from current material model
500 fromModel( myCurrentModel );
503 // initialize widgets from chosen material model (using resources manager)
504 Material_Model model;
505 model.fromResources( item->data( NameRole ).toString(), &myResourceMgr );
509 blockSignals( blocked );
511 // update buttons state
516 \brief Called when any material parameter is changed by the user
518 void GEOMToolsGUI_MaterialPropertiesDlg::onChanged()
520 // get currently selected item
521 QListWidgetItem* item = myMaterials->currentItem();
522 int type = item->data( TypeRole ).toInt();
525 if ( type == Current ) {
526 // for the current model do not perform any actions except store changed values
527 toModel( myCurrentModel );
529 else if ( type == User ) {
530 // for the user model, simply store the changes in the resource manager
531 Material_Model model;
533 // check if the user model is renamed
534 QString oldName = item->data( NameRole ).toString(), newName = item->text();
535 if ( oldName == newName ) {
536 // model is not renamed: store data using current name
537 model.toResources( oldName, &myResourceMgr );
540 // model is renamed: remove model with old name and store model using new name
541 myResourceMgr.remove( oldName );
542 model.toResources( newName, &myResourceMgr );
543 item->setData( NameRole, newName );
547 // it is no allowed to change global material
548 // user is asked about creating of a new user material model based on the currently selected one
549 if ( SUIT_MessageBox::question( this,
550 tr( "GEOM_WRN_WARNING" ),
551 tr( "QUE_CREATE_NEW_MATERIAL" ),
552 QMessageBox::Yes | QMessageBox::No,
553 QMessageBox::Yes ) == QMessageBox::Yes ) {
554 // user has chosen creation of new user model
558 // user has rejected creation of new user model: revert changes
559 bool blocked = blockSignals( true );
560 Material_Model model;
561 model.fromResources( item->data( NameRole ).toString(), &myResourceMgr );
563 blockSignals( blocked );
567 // update buttons state
572 \brief Called when user material is renamed by the user
574 void GEOMToolsGUI_MaterialPropertiesDlg::onItemChanged( QListWidgetItem* item )
576 // check new name to be unique (add suffix if necessary)
577 QString newName = item->text();
578 QString uniqueName = findUniqueName( newName, item );
579 if ( uniqueName != newName ) {
580 bool blocked = myMaterials->blockSignals( true );
581 item->setText( uniqueName );
582 myMaterials->blockSignals( blocked );
588 \brief Process context menu event from materials list
590 void GEOMToolsGUI_MaterialPropertiesDlg::onContextMenu( QContextMenuEvent* e )
592 QListWidgetItem* item = myMaterials->itemAt( e->pos() );
593 QMap<QAction*, int> actionMap;
596 if ( item && item->data( TypeRole ).toInt() == User ) {
597 actionMap[ m.addAction( tr( "RENAME_MATERIAL" ) ) ] = 0;
601 actionMap[ m.addAction( tr( "ADD_MATERIAL" ) ) ] = 1;
602 // delete user material
603 if ( item && item->data( TypeRole ).toInt() == User ) {
604 actionMap[ m.addAction( tr( "DELETE_MATERIAL" ) ) ] = 2;
606 QAction* a = m.exec( e->globalPos() );
607 switch( actionMap[ a ] ) {
610 myMaterials->editItem( item );
617 // delete user material
626 \brief Delete currently selected user model
628 void GEOMToolsGUI_MaterialPropertiesDlg::onDeleteMaterial()
630 QListWidgetItem* item = myMaterials->currentItem();
631 if ( item && item->data( TypeRole ).toInt() == User ) {
632 if ( SUIT_MessageBox::question( this,
633 tr( "GEOM_WRN_WARNING" ),
634 tr( "QUE_REMOVE_MATERIAL" ).arg( item->text() ),
635 QMessageBox::Yes | QMessageBox::No,
636 QMessageBox::Yes ) == QMessageBox::Yes ) {
637 myResourceMgr.remove( item->data( NameRole ).toString() );
644 \brief Add new user material model
646 void GEOMToolsGUI_MaterialPropertiesDlg::onAddMaterial()
648 QString newName = findUniqueName( tr( "CUSTOM_MATERIAL" ), 0, true );
649 QListWidgetItem* item = new QListWidgetItem( newName );
650 item->setData( TypeRole, QVariant( User ) );
651 item->setData( NameRole, QVariant( newName ) );
652 item->setFlags( item->flags() | Qt::ItemIsEditable );
653 myMaterials->addItem( item );
655 Material_Model model;
657 model.toResources( newName, &myResourceMgr );
658 myMaterials->setCurrentItem( item );
659 myMaterials->editItem( item );
663 \brief Update buttons state
665 void GEOMToolsGUI_MaterialPropertiesDlg::updateState()
667 QListWidgetItem* item = myMaterials->currentItem();
668 myDelButton->setEnabled( item && item->data( TypeRole ).toInt() == User );
669 myColorLab->setEnabled( !myPhysical->isChecked() );
670 myColor->setEnabled( !myPhysical->isChecked() );
671 myReflection[0].color->setEnabled( myPhysical->isChecked() );