libyui-qt  2.53.0
YQItemSelector.cc
1 /*
2  Copyright (C) 2019 SUSE LLC
3  This library is free software; you can redistribute it and/or modify
4  it under the terms of the GNU Lesser General Public License as
5  published by the Free Software Foundation; either version 2.1 of the
6  License, or (at your option) version 3.0 of the License. This library
7  is distributed in the hope that it will be useful, but WITHOUT ANY
8  WARRANTY; without even the implied warranty of MERCHANTABILITY or
9  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
10  License for more details. You should have received a copy of the GNU
11  Lesser General Public License along with this library; if not, write
12  to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
13  Floor, Boston, MA 02110-1301 USA
14 */
15 
16 
17 /*-/
18 
19  File: YQItemSelector.cc
20 
21  Author: Stefan Hundhammer <shundhammer@suse.de>
22 
23 /-*/
24 
25 
26 #include <QButtonGroup>
27 #include <QCheckBox>
28 #include <QHBoxLayout>
29 #include <QLabel>
30 #include <QRadioButton>
31 #include <QScrollBar>
32 #include <QStyle>
33 #include <QVBoxLayout>
34 
35 #define YUILogComponent "qt-ui"
36 #include <yui/YUILog.h>
37 #include <yui/YEvent.h>
38 #include "utf8.h"
39 #include "YQItemSelector.h"
40 #include "YQSignalBlocker.h"
41 #include "YQUI.h"
42 
43 #define ICON_SIZE 64
44 #define VERBOSE_SELECTION 0
45 
46 using std::string;
47 
48 
50  bool enforceSingleSelection )
51  : QScrollArea( (QWidget *) parent->widgetRep() )
52  , YItemSelector( parent, enforceSingleSelection )
53 {
54  init();
55 }
56 
57 
59  const YItemCustomStatusVector & customStates )
60  : QScrollArea( (QWidget *) parent->widgetRep() )
61  , YItemSelector( parent, customStates )
62 {
63  init();
64 }
65 
66 
68 {
69  setWidgetRep( this );
70 
71  setWidgetResizable( true );
72  setSizeAdjustPolicy( QAbstractScrollArea::AdjustToContentsOnFirstShow );
73 
74  _itemContainer = new QWidget( this );
75  _itemContainer->setObjectName( "YQItemSelectorItemContainer" );
76  YUI_CHECK_NEW( _itemContainer );
77 
78  QVBoxLayout * outerVBox = new QVBoxLayout( _itemContainer );
79  YUI_CHECK_NEW( outerVBox );
80 
81  _itemLayout = new QVBoxLayout();
82  outerVBox->addLayout( _itemLayout );
83  outerVBox->addStretch( 1000 ); // this takes up any excess space
84 
85  _buttonGroup = new QButtonGroup( this );
86  YUI_CHECK_NEW( _buttonGroup );
87 
88  this->QScrollArea::setWidget( _itemContainer );
89 }
90 
91 
93 {
94  // NOP
95 }
96 
97 
99 {
100  YUI_CHECK_PTR( itemWidget );
101 
102  _itemLayout->addWidget( itemWidget );
103 
104  if ( enforceSingleSelection() )
105  _buttonGroup->addButton( itemWidget->headingToggle() );
106 }
107 
108 
109 void YQItemSelector::addItem( YItem * item )
110 {
111  YUI_CHECK_PTR( item );
112  YItemSelector::addItem( item );
113 
114  YQSelectorItemWidget * itemWidget = new YQSelectorItemWidget( this, item );
115  YUI_CHECK_NEW( itemWidget );
116 
117  itemWidget->createWidgets();
118  _itemWidgets[ item ] = itemWidget;
119 
120  connect( itemWidget, &pclass( itemWidget )::selectionChanged,
121  this, &pclass( this )::slotSelectionChanged );
122 
123  if ( item->selected() && enforceSingleSelection() )
124  deselectOtherItems( item );
125 }
126 
127 
128 void YQItemSelector::addItems( const YItemCollection & itemCollection )
129 {
130  for ( YItem * item: itemCollection )
131  addItem( item );
132 }
133 
134 
135 void YQItemSelector::selectItem( YItem * item, bool selected )
136 {
137  YQSelectorItemWidget * itemWidget = _itemWidgets.value( item );
138 
139  if ( ! itemWidget )
140  YUI_THROW( YUIException( "Can't find selected item" ) );
141 
142  itemWidget->setSelected( selected );
143 
144  if ( enforceSingleSelection() )
145  {
146  if ( selected )
147  deselectOtherItems( item );
148  }
149 }
150 
151 
153 {
154  foreach ( YQSelectorItemWidget * itemWidget, _itemWidgets )
155  itemWidget->setSelected( false );
156 
157  YItemSelector::deselectAllItems();
158 }
159 
160 
161 void YQItemSelector::deselectOtherItems( YItem * selectedItem )
162 {
163  for ( QMap<YItem *, YQSelectorItemWidget *>::iterator it = _itemWidgets.begin();
164  it != _itemWidgets.end();
165  ++it )
166  {
167  if ( it.key() != selectedItem )
168  {
169  it.key()->setSelected( false ); // The YItem
170  it.value()->setSelected( false ); // ...and the corresponding widget
171  }
172  }
173 }
174 
175 
177 {
178  YQSignalBlocker sigBlocker( this );
179 
180  qDeleteAll( _itemWidgets.values() );
181  _itemWidgets.clear();
182 
183  YItemSelector::deleteAllItems();
184 }
185 
186 
187 void YQItemSelector::setEnabled( bool enabled )
188 {
189  _itemContainer->setEnabled( enabled );
190 }
191 
192 
194 {
195  int width = _itemContainer->sizeHint().width() + 2;
196 
197  QScrollBar * vScrollBar = verticalScrollBar();
198 
199  if ( vScrollBar ) // Compensate for vertical scroll bar
200  width += vScrollBar->sizeHint().width();
201 
202  return width;
203 }
204 
205 
207 {
208  if ( _itemWidgets.size() <= visibleItems() ) // No scrolling necessary
209  return _itemContainer->sizeHint().height() + 2;
210 
211  // The primitive approach would be to just always use that value. But then
212  // all items would always be visible, and the remaining widgets in the
213  // layout would have to fight for screen space. Since this widget tends to
214  // be a very large one, it would still dominate the layout; cutting off
215  // some pixels at its bottom wouldn't affect it much, but any not-so-high
216  // widgets like buttons would still be cut off.
217  //
218  // Thus, we try to add up the needed space for the first n items and return
219  // that as the preferred height.
220 
221  QList<YQSelectorItemWidget *> visibleItemWidgets =
222  findChildren<YQSelectorItemWidget *>().mid( 0, visibleItems() );
223 
224  int height = 0;
225 
226  // Each item might have a different height, so sum them up individually
227 
228  foreach ( YQSelectorItemWidget * itemWidget, visibleItemWidgets )
229  height += itemWidget->sizeHint().height();
230 
231  if ( ! visibleItemWidgets.isEmpty() )
232  {
233  height += ( visibleItemWidgets.size() + 0.0 ) * _itemLayout->spacing();
234  height += _itemContainer->layout()->contentsMargins().top();
235  }
236 
237  return height;
238 }
239 
240 
241 void YQItemSelector::setSize( int newWidth, int newHeight )
242 {
243  resize( newWidth, newHeight );
244 }
245 
246 
248 {
249  YQSelectorItemWidget * itemWidget = findChild<YQSelectorItemWidget *>();
250 
251  if ( itemWidget )
252  {
253  itemWidget->headingToggle()->setFocus();
254  return true;
255  }
256  else
257  {
258  // yuiMilestone() << "No itemWidget" << endl;
259  return false;
260  }
261 }
262 
263 
265  bool selected )
266 {
267  YUI_CHECK_PTR( itemWidget );
268 
269  YItem * item = itemWidget->item();
270  item->setSelected( selected );
271 
272  if ( selected )
273  {
274 #if VERBOSE_SELECTION
275  yuiMilestone() << "Selected " << item->label() << endl;
276 #endif
277 
278  if ( enforceSingleSelection() )
279  deselectOtherItems( item );
280  }
281 #if VERBOSE_SELECTION
282  else
283  yuiMilestone() << "Deselected " << item->label() << endl;
284 #endif
285 
286 #if VERBOSE_SELECTION
287  dumpItems();
288 #endif
289 
290  if ( notify() && ( selected || ! enforceSingleSelection() ) )
291  YQUI::ui()->sendEvent( new YWidgetEvent( this, YEvent::ValueChanged ) );
292 }
293 
294 
295 void YQItemSelector::activateItem( YItem * item )
296 {
297  if( notify() )
298  YQUI::ui()->sendEvent( new YWidgetEvent( this, YEvent::ValueChanged ) );
299 }
300 
301 //-----------------------------------------------------------------------------
302 
303 
305  YItem * item )
306  : QFrame( parent->itemContainer() )
307  , _parent( parent )
308  , _item( item )
309 {
310 }
311 
312 
314 {
315  // NOP
316 }
317 
318 
320 {
321  string description;
322  YDescribedItem * describedItem = dynamic_cast<YDescribedItem *>(_item);
323 
324  if ( describedItem )
325  description = describedItem->description();
326 
327  createWidgets( _item->label(),
328  description,
329  _item->iconName(),
330  _item->selected() );
331 }
332 
333 
334 void YQSelectorItemWidget::createWidgets( const string & label,
335  const string & description,
336  const string & iconName,
337  bool selected )
338 {
339  /*
340  * this (QFrame)
341  * _hBox
342  * _vBox
343  * _headingToggle
344  * _descriptionLabel
345  * _iconLabel
346  *
347  * +--------------------------------------------------+ QFrame (this)
348  * | ( ) Heading xx xx |
349  * | xx xx |
350  * | Description text Icon |
351  * | Description text xx xx |
352  * | ... xx xx |
353  * | Description text |
354  * +--------------------------------------------------+
355  */
356 
357  _descriptionLabel = 0;
358  _iconLabel = 0;
359 
360  // yuiMilestone() << "Creating item for " << label << endl;
361 
362 
363  // Parts initially generated with Qt Designer
364 
365  QSizePolicy sizePol( QSizePolicy::Preferred, QSizePolicy::Fixed );
366  sizePol.setHorizontalStretch( 0 );
367  sizePol.setVerticalStretch( 0 );
368  sizePol.setHeightForWidth( sizePolicy().hasHeightForWidth() );
369  setSizePolicy( sizePol );
370 
371  setFrameShape( QFrame::StyledPanel );
372  setFrameShadow( QFrame::Raised );
373 
374  _hBox = new QHBoxLayout( this ); // outer layout
375  _hBox->setSpacing( 6 );
376  _hBox->setContentsMargins( -1, 6, 6, 6 );
377 
378  _vBox = new QVBoxLayout(); // inner layout
379  _vBox->setSpacing( 6 );
380  _vBox->setContentsMargins( 0, 0, 0, 0 ); // don't let margins accumulate
381 
382 
383  //
384  // Heading (QRadioButton or QCheckBox)
385  //
386 
387  _headingToggle = createHeadingToggle( label, this );
388  YUI_CHECK_NEW( _headingToggle );
389 
390  _headingToggle->setObjectName( "YQSelectorItemHeading" ); // for QSS style sheets
391  _headingToggle->setChecked( selected );
392 
393  QFont font( _headingToggle->font() );
394  font.setBold( true );
395  _headingToggle->setFont( font );
396 
397  _vBox->addWidget( _headingToggle );
398  _hBox->addLayout( _vBox );
399 
400 
401  //
402  // Description (body text)
403  //
404 
405  if ( ! description.empty() )
406  {
407  _descriptionLabel = new QLabel( fromUTF8( description ), this );
408  YUI_CHECK_NEW( _descriptionLabel );
409  _descriptionLabel->setObjectName( "YQSelectorItemDescription" ); // for QSS
410  _descriptionLabel->setIndent( itemDescriptionIndent() ); // Compensate for QRadioButton icon
411 
412  _vBox->addWidget( _descriptionLabel );
413  }
414 
415 
416  //
417  // Icon
418  //
419 
420  if ( ! iconName.empty() )
421  {
422  _hBox->addStretch( 1000 ); // this takes up any excess space
423 
424  _iconLabel = new QLabel( "", this );
425  YUI_CHECK_NEW( _iconLabel );
426 
427  QIcon icon = YQUI::ui()->loadIcon( iconName );
428  _iconLabel->setPixmap( icon.pixmap( ICON_SIZE ) );
429 
430  _descriptionLabel->setObjectName( "YQSelectorItemIcon" ); // for QSS
431  _iconLabel->setIndent(0);
432 
433  QSizePolicy sizePol( _iconLabel->sizePolicy() );
434  sizePol.setHorizontalStretch( 0 );
435  sizePol.setVerticalStretch( 0 );
436  _iconLabel->setSizePolicy( sizePol );
437 
438  _hBox->addWidget( _iconLabel );
439  }
440 
441  YUI_CHECK_PTR( _parent );
442  _parent->addItemWidget( this );
443 }
444 
445 
446 QAbstractButton *
447 YQSelectorItemWidget::createHeadingToggle( const std::string & label,
448  QWidget * parent )
449 {
450  QAbstractButton * toggle = 0;
451 
452  if ( singleSelection() )
453  toggle = new QRadioButton( fromUTF8( label ), this );
454  else
455  toggle = new QCheckBox( fromUTF8( label ), this );
456 
457  YUI_CHECK_NEW( toggle );
458 
459  connect( toggle, &pclass( _headingToggle )::toggled,
460  this, &pclass( this )::slotSelectionChanged );
461 
462  return toggle;
463 }
464 
465 
467 {
468  // This magic number in should really come from the widget style and some
469  // queries like
470  //
471  // style()->pixelMetric( QStyle::PM_RadioButtonLabelSpacing );
472  //
473  // and then added up from all the necessary individual pieces. But most
474  // of those things are never clearly specified. In the Qt code itself
475  // there are gems like "width += 4" at strategic places. So there is no
476  // realistic way for us on this level to do that right.
477 
478  return 20;
479 }
480 
481 
483 {
484  return _parent && _parent->enforceSingleSelection();
485 }
486 
487 
489 {
490  return _headingToggle->isChecked();
491 }
492 
493 
495 {
496  YQSignalBlocker sigBlocker( this );
497  _headingToggle->setChecked( sel );
498 }
499 
500 
501 void YQSelectorItemWidget::slotSelectionChanged( bool selected )
502 {
503  emit selectionChanged( this, selected );
504 }
YQSignalBlocker
Helper class to block Qt signals for QWidgets or QObjects as long as this object exists.
Definition: YQSignalBlocker.h:37
YQItemSelector::deselectOtherItems
void deselectOtherItems(YItem *selectedItem)
Deselect all items except 'selectedItem'.
Definition: YQItemSelector.cc:161
YQItemSelector::addItems
virtual void addItems(const YItemCollection &itemCollection)
Add multiple items.
Definition: YQItemSelector.cc:128
YQItemSelector::preferredHeight
virtual int preferredHeight()
Preferred height of the widget.
Definition: YQItemSelector.cc:206
YQSelectorItemWidget
Class for the widgets of one ItemSelector item.
Definition: YQItemSelector.h:205
YQUI::sendEvent
void sendEvent(YEvent *event)
Widget event handlers (slots) call this when an event occured that should be the answer to a UserInpu...
Definition: YQUI.cc:480
YQItemSelector::setSize
virtual void setSize(int newWidth, int newHeight)
Set the new size of the widget.
Definition: YQItemSelector.cc:241
YQSelectorItemWidget::YQSelectorItemWidget
YQSelectorItemWidget(YQItemSelector *parent, YItem *item)
Constructor.
Definition: YQItemSelector.cc:304
YQItemSelector::init
void init()
Common initializations for all constructors.
Definition: YQItemSelector.cc:67
YQSelectorItemWidget::headingToggle
QAbstractButton * headingToggle() const
Return the widget that handles the selection: Either a QRadioButton or a QCheckBox.
Definition: YQItemSelector.h:252
YQItemSelector::YQItemSelector
YQItemSelector(YWidget *parent, bool enforceSingleSelection=true)
Standard constructor.
Definition: YQItemSelector.cc:49
YQSelectorItemWidget::singleSelection
bool singleSelection() const
Return 'true' if the parent YItemSelector has single selection (1-of-n).
Definition: YQItemSelector.cc:482
YQItemSelector::setKeyboardFocus
virtual bool setKeyboardFocus()
Accept the keyboard focus.
Definition: YQItemSelector.cc:247
YQItemSelector
Definition: YQItemSelector.h:43
YQUI::ui
static YQUI * ui()
Access the global Qt-UI.
Definition: YQUI.h:83
YQItemSelector::slotSelectionChanged
void slotSelectionChanged(YQSelectorItemWidget *itemWidget, bool selected)
Notification that an item has been selected.
Definition: YQItemSelector.cc:264
YQItemSelector::activateItem
virtual void activateItem(YItem *item)
Activate selected item.
Definition: YQItemSelector.cc:295
YQItemSelector::preferredWidth
virtual int preferredWidth()
Preferred width of the widget.
Definition: YQItemSelector.cc:193
YQSelectorItemWidget::itemDescriptionIndent
virtual int itemDescriptionIndent() const
Return the amount of indentation in pixels for the description text.
Definition: YQItemSelector.cc:466
YQSelectorItemWidget::createWidgets
virtual void createWidgets()
Create the subwidgets.
Definition: YQItemSelector.cc:319
YQSelectorItemWidget::createHeadingToggle
virtual QAbstractButton * createHeadingToggle(const std::string &label, QWidget *parent)
Create the appropriate toggle button for this item and connect it to appropriate slots.
Definition: YQItemSelector.cc:447
YQSelectorItemWidget::setSelected
virtual void setSelected(bool sel=true)
Select the appropriate widget according to the parent's selection policy (single or multi selection).
Definition: YQItemSelector.cc:494
YQItemSelector::setEnabled
virtual void setEnabled(bool enabled)
Set enabled/disabled state.
Definition: YQItemSelector.cc:187
YQItemSelector::selectItem
virtual void selectItem(YItem *item, bool selected=true)
Select or deselect an item.
Definition: YQItemSelector.cc:135
YQItemSelector::deselectAllItems
virtual void deselectAllItems()
Deselect all items.
Definition: YQItemSelector.cc:152
YQItemSelector::addItemWidget
void addItemWidget(YQSelectorItemWidget *itemWidget)
Add an item widget to the appropriate layout.
Definition: YQItemSelector.cc:98
YQItemSelector::addItem
virtual void addItem(YItem *item)
Add an item.
Definition: YQItemSelector.cc:109
YQItemSelector::~YQItemSelector
virtual ~YQItemSelector()
Destructor.
Definition: YQItemSelector.cc:92
YQSelectorItemWidget::~YQSelectorItemWidget
virtual ~YQSelectorItemWidget()
Destructor.
Definition: YQItemSelector.cc:313
YQItemSelector::deleteAllItems
virtual void deleteAllItems()
Delete all items.
Definition: YQItemSelector.cc:176
YQUI::loadIcon
QIcon loadIcon(const string &iconName) const
Load an icon.
Definition: YQUI.cc:708
YQSelectorItemWidget::selected
virtual bool selected() const
Return 'true' if this item is selected, 'false' otherwise.
Definition: YQItemSelector.cc:488