OpenWalnut  1.5.0dev
WQtPropertyGroupWidget.cpp
1 //---------------------------------------------------------------------------
2 //
3 // Project: OpenWalnut ( http://www.openwalnut.org )
4 //
5 // Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
6 // For more information see http://www.openwalnut.org/copying
7 //
8 // This file is part of OpenWalnut.
9 //
10 // OpenWalnut is free software: you can redistribute it and/or modify
11 // it under the terms of the GNU Lesser General Public License as published by
12 // the Free Software Foundation, either version 3 of the License, or
13 // (at your option) any later version.
14 //
15 // OpenWalnut is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 // GNU Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public License
21 // along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
22 //
23 //---------------------------------------------------------------------------
24 
25 #include <string>
26 #include <algorithm>
27 
28 #include <QtCore/QSignalMapper>
29 #include <QApplication>
30 #include <QGroupBox>
31 #include <QPushButton>
32 #include <QToolButton>
33 #include <QScrollArea>
34 
35 #include "../events/WEventTypes.h"
36 #include "../events/WPropertyChangedEvent.h"
37 #include "../guiElements/WScaleToolButton.h"
38 
39 #include "../WQtGui.h"
40 #include "../WMainWindow.h"
41 
42 #include "core/common/WPropertyGroupBase.h"
43 #include "core/common/WLogger.h"
44 
45 #include "../WGuiConsts.h"
46 
47 #include "WQtPropertyGroupWidget.h"
48 
50  : QWidget( parent ),
51  m_name( group->getName().c_str() ),
52  m_group( group ),
53  m_nestingDepth( depth )
54 {
55  // note: never do layouts as none pointers
56  // on destruction of a widget it will try to delete them which will cause crashes
57  m_pageLayout = new QVBoxLayout();
58  m_pageLayout->setMargin( WGLOBAL_MARGIN );
59  m_pageLayout->setSpacing( WGLOBAL_SPACING );
60  m_pageLayout->setAlignment( Qt::AlignTop );
61 
62  m_controlLayout = new QGridLayout();
63  m_controlLayout->setMargin( WGLOBAL_MARGIN );
64  m_controlLayout->setSpacing( WGLOBAL_SPACING );
65  m_controlLayout->setAlignment( Qt::AlignTop );
66 
67  m_pageLayout->addLayout( m_controlLayout );
68 
69  // force widgets to horizontally be on top and shrink vertically to match scroll area
70  QSizePolicy sizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum );
71  sizePolicy.setHorizontalStretch( 0 );
72  sizePolicy.setVerticalStretch( 0 );
73  setSizePolicy( sizePolicy );
74 
75  // add the groups children
76  // read lock, gets unlocked upon destruction (out of scope)
77  WPropertyGroupBase::PropertySharedContainerType::ReadTicket propAccess = group->getProperties();
78  setName( QString::fromStdString( group->getName() ) );
79 
80  // iterate all properties.
81  for( WPropertyGroupBase::PropertyConstIterator iter = propAccess->get().begin(); iter != propAccess->get().end(); ++iter )
82  {
83  addProp( *iter );
84  }
85  propAccess.reset();
86  addSpacer();
87 
88  // empty groups are hidden too
89  // NOTE: the WPropertyGroupBase class fires the update condition if a prop gets added. So it automatically un-hides if a prop is added.
91  bool hide = ( r->get().empty() | m_group->isHidden() );
92  r.reset();
93  // NOTE: a simple setHidden( group->isHidden() ) causes the QWidgets to popup if hidden is false. This is why we set hidden only if it really
94  // is needed
95  if( hide && group->autoHideEmpty() )
96  {
97  // FIXME #381: do not hide by default. We need this to be fixed properly.
98  setHidden( true );
99  }
100  // setup the update callback
101  m_connection = m_group->getUpdateCondition()->subscribeSignal( boost::bind( &WQtPropertyGroupWidget::propertyChangeNotifier, this ) );
102 }
103 
105 {
106  // cleanup
107  m_connection.disconnect();
108 }
109 
111 {
112  QCoreApplication::postEvent( this, new WPropertyChangedEvent() );
113 }
114 
116 {
117  // a property changed
118  if( event->type() == WQT_PROPERTY_CHANGED_EVENT )
119  {
121 
122  // handle the case of a hidden property
123  setHidden( r->get().empty() | m_group->isHidden() );
124  emit hideSignal( m_group->isHidden() );
125 
126  // Remove all items we have a widget for but which is not in the property group anymore
127  for( PropertyWidgets::iterator i = m_propWidgets.begin(); i != m_propWidgets.end(); ++i )
128  {
129  // element in the group?
130  WPropertyGroupBase::PropertyContainerType::const_iterator found = std::find( r->get().begin(), r->get().end(), i->first );
131  if( found == r->get().end() )
132  {
133  // NO! Remove the widget. But not yet.
134  delete( i->second );
135  i->second = NULL;
136  }
137  }
138 
139  // Add all properties as widget of not yet there
140  for( WPropertyGroupBase::PropertyContainerType::const_iterator i = r->get().begin(); i != r->get().end(); ++i )
141  {
142  // is there a widget for this prop?
143  if( !m_propWidgets.count( *i ) )
144  {
145  // NO. Add it.
146  addProp( *i );
147  }
148  }
149 
150  return true;
151  }
152 
153  return QWidget::event( event );
154 }
155 
157 {
158  if( property->getType() == PV_GROUP )
159  {
160  addGroup( property->toPropGroupBase() );
161  return;
162  }
163 
164  // create a widget and increase counter if successful
165  WPropertyWidget* widget = WPropertyWidget::construct( property, m_controlLayout, this );
166  if( widget )
167  {
168  m_propWidgets[ property ] = widget;
169  }
170 }
171 
173 {
174  addGroup( new WQtPropertyGroupWidget( prop, m_nestingDepth + 1, this ) );
175 }
176 
177 QWidget* WQtPropertyGroupWidget::createPropertyGroupBox( QWidget* widget, bool asScrollArea, QWidget* parent, const QString& title, int nestingLevel )
178 {
179  WQtPropertyGroupWidget* gWidget = dynamic_cast< WQtPropertyGroupWidget* >( widget );
180 
181  QSizePolicy sizePolicy( QSizePolicy::Minimum, QSizePolicy::Maximum );
182  sizePolicy.setHorizontalStretch( 0 );
183  sizePolicy.setVerticalStretch( 0 );
184 
185  // create a scrollbox and group box containing the widget
186  QWidget* group = new QWidget();
187 
188  QScrollArea* scrollArea = 0;
189  QGridLayout* grid = new QGridLayout();
190  grid->setAlignment( Qt::AlignTop );
191  grid->addWidget( widget, 0, 0 );
192  grid->setMargin( WGLOBAL_MARGIN );
193  grid->setSpacing( WGLOBAL_SPACING );
194 
195  group->setLayout( grid );
196  if( asScrollArea )
197  {
198  scrollArea = new QScrollArea();
199  scrollArea->setWidgetResizable( true );
200  scrollArea->setWidget( group );
201  scrollArea->setAlignment( Qt::AlignTop );
202  group->show();
203  }
204 
205  // encapsulate it into an collapsable widget
206  QFrame* box = new QFrame( parent );
207  box->setFrameShape( QFrame::StyledPanel );
208  box->setObjectName( "PropertyGroupBox" );
209  box->setContentsMargins( 0, 0, 0, 0 );
210  QGridLayout* boxLayout = new QGridLayout();
211  boxLayout->setMargin( 0 );
212  boxLayout->setSpacing( 0 );
213  box->setLayout( boxLayout );
214 
215  // create a button as title
216  WScaleToolButton* boxTitle = new WScaleToolButton( WPREFERRED_LABEL_LENGTH , box );
217 
218  QString titleText = title;
219  if( gWidget )
220  {
221  titleText = ( title == "" ) ? gWidget->getName() : title;
222  }
223 
224  boxTitle->setText( titleText );
225  boxTitle->setToolTip( titleText );
226  boxLayout->addWidget( boxTitle, 0, 0 );
227 
228  // we need a separate widget to indent the content widget a bit without indenting the title ... yes this really looks like the mess you can
229  // do with DIVs in HTML :-/. What we do here is adding a widget which then contains the content widget and indents it. Setting the contents
230  // margins directly in the QFrame* box would also indent the title button which is not wanted
231  QWidget* boxContent = new QWidget();
232  boxContent->setContentsMargins( 5, 0, 0, 0 );
233  boxContent->setObjectName( "PropertyGroupBoxContent" );
234  QGridLayout* boxContentLayout = new QGridLayout();
235  boxContent->setLayout( boxContentLayout );
236  boxContentLayout->setMargin( 0 );
237  boxContentLayout->setSpacing( 0 );
238  boxLayout->addWidget( boxContent, 1, 0 );
239 
240  // set the button up
241  boxTitle->setMinimumHeight( WMIN_PROPGROUP_HEAD_HEIGHT );
242  boxTitle->setSizePolicy( sizePolicy );
243  boxTitle->setAutoRaise( true );
244  boxTitle->setAutoFillBackground( true );
245 
246  // content widget
247  QWidget* content = new QWidget();
248  content->setObjectName( "PropertyGroupContent" );
249  boxContentLayout->addWidget( content, 1, 0 );
250 
251  // content layout
252  QGridLayout* contentLayout = new QGridLayout();
253  content->setLayout( contentLayout );
254  contentLayout->setMargin( 0 );
255  contentLayout->setSpacing( 0 );
256 
257  // some styling
258  QPalette palette; // the palette is used to get default colors of the style/system
259  QColor defaultCol = QColor( "#b9b9b9" ); // palette.window().color().darker( 120 );
260  QColor brightTextCol = QColor( "#eeeeee" );
261  QColor darkTextCol = QColor( "#444444" );
262  QColor defaultTextCol = darkTextCol; // palette.windowText().color();
263 
264  int nestingColorLevel = nestingLevel;
265  if( gWidget )
266  {
267  nestingColorLevel = gWidget->m_nestingDepth % 10;
268  }
269 
270  switch( nestingColorLevel ) // NOTE: the first level 0 does not need a color as it does not provide a boxtitle, so we begin with 1
271  {
272  // All these colors are taken from the solarized pallette http://ethanschoonover.com/solarized
273  case 1:
274  defaultCol = QColor( "#949494" ); // palette.window().color().darker( 150 );
275  defaultTextCol = darkTextCol;
276  break;
277  case 2:
278  defaultCol = QColor( "#268bd2" );
279  defaultTextCol = brightTextCol;
280  break;
281  case 3:
282  defaultCol = QColor( "#2aa198" );
283  defaultTextCol = brightTextCol;
284  break;
285  case 4:
286  defaultCol = QColor( "#859900" );
287  defaultTextCol = brightTextCol;
288  break;
289  case 5:
290  defaultCol = QColor( "#b58900" );
291  defaultTextCol = brightTextCol;
292  break;
293  case 6:
294  defaultCol = QColor( "#cb4b16" );
295  defaultTextCol = brightTextCol;
296  break;
297  case 7:
298  defaultCol = QColor( "#dc322f" );
299  defaultTextCol = brightTextCol;
300  break;
301  case 8:
302  defaultCol = QColor( "#d33682" );
303  defaultTextCol = brightTextCol;
304  break;
305  case 9:
306  defaultCol = QColor( "#6c71c4" );
307  defaultTextCol = brightTextCol;
308  break;
309  }
310 
311  boxTitle->setStyleSheet( "background-color: " + defaultCol.name() + "; font-weight:bold; color: " + defaultTextCol.name() + ";" );
312  box->setStyleSheet( "QFrame#PropertyGroupBox{background-color: " + defaultCol.name() + ";}" );
313  content->setStyleSheet( "#PropertyGroupContent{ background-color: "+ palette.window().color().name() +";}" );
314 
315  // toggle should cause the body widget to appear/disappear
316  QSignalMapper* signalMapper = new QSignalMapper( box );
317  signalMapper->setMapping( boxTitle, boxContent );
318  connect( boxTitle, SIGNAL( released() ), signalMapper, SLOT( map() ) );
319  connect( signalMapper, SIGNAL( mapped( QWidget* ) ), WQtGui::getMainWindow(), SLOT( switchVisibility( QWidget* ) ) );
320 
321  // create a body widget
322  if( asScrollArea )
323  {
324  contentLayout->addWidget( scrollArea, 1, 0 );
325  }
326  else
327  {
328  contentLayout->addWidget( group, 1, 0 );
329  }
330 
331  // hide the box too if the property gets hidden
332  box->setHidden( widget->isHidden() );
333  if( gWidget )
334  {
335  // QWidget does not provide this signal.
336  connect( gWidget, SIGNAL( hideSignal( bool ) ), box, SLOT( setHidden( bool ) ) );
337  }
338 
339  return box;
340 }
341 
342 QWidget* WQtPropertyGroupWidget::createPropertyGroupBox( WPropertyGroupBase::SPtr group, bool asScrollArea, const QString& title,
343  size_t depth, QWidget* parent )
344 {
345  WQtPropertyGroupWidget* propWidget = createPropertyGroupWidget( group, title, depth, parent );
346 
347  // embed nicely into some scroll area or container or ....
348  QWidget* tab = WQtPropertyGroupWidget::createPropertyGroupBox( propWidget, asScrollArea, parent, propWidget->getName() );
349  QSizePolicy sizePolicy( QSizePolicy::Preferred, QSizePolicy::Maximum );
350  sizePolicy.setHorizontalStretch( 0 );
351  sizePolicy.setVerticalStretch( 0 );
352  tab->setSizePolicy( sizePolicy );
353  tab->setWindowTitle( propWidget->getName() );
354 
355  return tab;
356 }
357 
358 QWidget* WQtPropertyGroupWidget::createPropertyGroupBox( WPropertyGroupBase::SPtr group, const QString& title, size_t depth, QWidget* parent )
359 {
360  return createPropertyGroupBox( group, false, title, depth, parent );
361 }
362 
364  size_t depth, QWidget* parent )
365 {
366  QString titleCorrected = title;
367  if( title == "" )
368  {
369  titleCorrected = QString::fromStdString( group->getName() );
370  }
371 
372  WQtPropertyGroupWidget* propWidget = new WQtPropertyGroupWidget( group, depth, parent );
373  propWidget->setName( titleCorrected );
374 
375  return propWidget;
376 }
377 
379 {
380  QWidget* box = WQtPropertyGroupWidget::createPropertyGroupBox( widget, asScrollArea, this );
381 
382  // insert into layout
383  int row = m_controlLayout->rowCount();
384  m_controlLayout->addWidget( box, row, 0, 1, 2 );
385 
386  // also keep track of group widgets
387  m_propWidgets[ widget->getPropertyGroup() ] = box;
388 }
389 
391 {
392  m_pageLayout->addStretch();
393  setLayout( m_pageLayout );
394 }
395 
397 {
398  return m_name;
399 }
400 
402 {
403  m_name = name;
404 }
405 
407 {
408  return m_propWidgets.empty();
409 }
410 
412 {
413  return m_group;
414 }
415 
std::shared_ptr< WPropertyBase > SPtr
Convenience typedef for a std::shared_ptr< WPropertyBase >
Definition: WPropertyBase.h:53
Event signalling a new module has been associated with the root container in the kernel.
PropertyContainerType::const_iterator PropertyConstIterator
The const iterator type of the container.
std::shared_ptr< WPropertyGroupBase > SPtr
Convenience typedef for a std::shared_ptr< WPropertyGroupBase >.
Class building the base for all widgets representing properties.
static WPropertyWidget * construct(WPropertyBase::SPtr property, QGridLayout *propertyGrid=NULL, QWidget *parent=NULL)
Constructs a proper widget for the specified property.
static WMainWindow * getMainWindow()
Returns the current main window instance or NULL if not existent.
Definition: WQtGui.cpp:88
Container widget to contain a number of properties for the module context in the control panel.
QGridLayout * m_controlLayout
Layout used for each "widget combination".
QString m_name
The name used for this widget.
virtual ~WQtPropertyGroupWidget()
destructor
PropertyWidgets m_propWidgets
The map if property pointer to actual property widget.
boost::signals2::connection m_connection
The connection for propertyChangeNotifier().
void hideSignal(bool hide)
A Signal which gets emitted whenever the widget should be hidden.
QString getName()
The property group name.
QVBoxLayout * m_pageLayout
Layout used for the whole widget.
virtual bool event(QEvent *event)
Custom event dispatcher.
void setName(QString name)
Sets the name of this widget.
void addSpacer()
helper function to add a spacer at the end
WPropertyGroupBase::SPtr m_group
The property group handled here.
size_t m_nestingDepth
The depth of this group.
static QWidget * createPropertyGroupBox(QWidget *widget, bool asScrollArea=false, QWidget *parent=NULL, const QString &title="", int nestingLevel=0)
This function creates the fancy box around your specified group widget.
virtual void propertyChangeNotifier()
Callback for WPropertyBase::getChangeCondition.
WQtPropertyGroupWidget(WPropertyGroupBase::SPtr group, size_t depth=0, QWidget *parent=0)
Creates new widget for a property group.
static WQtPropertyGroupWidget * createPropertyGroupWidget(WPropertyGroupBase::SPtr group, const QString &title="", size_t depth=0, QWidget *parent=0)
Create a property widget using the given group.
WPropertyGroupBase::SPtr getPropertyGroup()
Get the managed group of this widget.
void addProp(WPropertyBase::SPtr property)
Adds a new property widget to the PropertyGroup.
bool isEmpty() const
True if there are no widgets inside.
void addGroup(WQtPropertyGroupWidget *widget, bool asScrollArea=false)
Adds an widget containing another property group to this widget.
Special Button that can shrink and expand in a layout.
virtual void setText(const QString &text)
reimplemented function to setText
std::shared_ptr< WSharedObjectTicketRead< T > > ReadTicket
Type for read tickets.
Definition: WSharedObject.h:65