libyui-qt  2.56.2
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 
303 {
304  // Any of the items might have its keyboard shortcut changed, but we don't
305  // know which one. So let's simply set all item labels again.
306 
307  for ( YItemConstIterator it = itemsBegin(); it != itemsEnd(); ++it )
308  {
309  YItem * item = *it;
310  _itemWidgets[item]->setLabel( fromUTF8( item->label() ) );
311  }
312 }
313 
314 
315 //-----------------------------------------------------------------------------
316 
317 
319  YItem * item )
320  : QFrame( parent->itemContainer() )
321  , _parent( parent )
322  , _item( item )
323 {
324 }
325 
326 
328 {
329  // NOP
330 }
331 
332 
334 {
335  string description;
336  YDescribedItem * describedItem = dynamic_cast<YDescribedItem *>(_item);
337 
338  if ( describedItem )
339  description = describedItem->description();
340 
341  createWidgets( _item->label(),
342  description,
343  _item->iconName(),
344  _item->selected() );
345 }
346 
347 
348 void YQSelectorItemWidget::createWidgets( const string & label,
349  const string & description,
350  const string & iconName,
351  bool selected )
352 {
353  /*
354  * this (QFrame)
355  * _hBox
356  * _vBox
357  * _headingToggle
358  * _descriptionLabel
359  * _iconLabel
360  *
361  * +--------------------------------------------------+ QFrame (this)
362  * | ( ) Heading xx xx |
363  * | xx xx |
364  * | Description text Icon |
365  * | Description text xx xx |
366  * | ... xx xx |
367  * | Description text |
368  * +--------------------------------------------------+
369  */
370 
371  _descriptionLabel = 0;
372  _iconLabel = 0;
373 
374  // yuiMilestone() << "Creating item for " << label << endl;
375 
376 
377  // Parts initially generated with Qt Designer
378 
379  QSizePolicy sizePol( QSizePolicy::Preferred, QSizePolicy::Fixed );
380  sizePol.setHorizontalStretch( 0 );
381  sizePol.setVerticalStretch( 0 );
382  sizePol.setHeightForWidth( sizePolicy().hasHeightForWidth() );
383  setSizePolicy( sizePol );
384 
385  setFrameShape( QFrame::StyledPanel );
386  setFrameShadow( QFrame::Raised );
387 
388  _hBox = new QHBoxLayout( this ); // outer layout
389  _hBox->setSpacing( 6 );
390  _hBox->setContentsMargins( -1, 6, 6, 6 );
391 
392  _vBox = new QVBoxLayout(); // inner layout
393  _vBox->setSpacing( 6 );
394  _vBox->setContentsMargins( 0, 0, 0, 0 ); // don't let margins accumulate
395 
396 
397  //
398  // Heading (QRadioButton or QCheckBox)
399  //
400 
401  _headingToggle = createHeadingToggle( label, this );
402  YUI_CHECK_NEW( _headingToggle );
403 
404  _headingToggle->setObjectName( "YQSelectorItemHeading" ); // for QSS style sheets
405  _headingToggle->setChecked( selected );
406 
407  QFont font( _headingToggle->font() );
408  font.setBold( true );
409  _headingToggle->setFont( font );
410 
411  _vBox->addWidget( _headingToggle );
412  _hBox->addLayout( _vBox );
413 
414 
415  //
416  // Description (body text)
417  //
418 
419  if ( ! description.empty() )
420  {
421  _descriptionLabel = new QLabel( fromUTF8( description ), this );
422  YUI_CHECK_NEW( _descriptionLabel );
423  _descriptionLabel->setObjectName( "YQSelectorItemDescription" ); // for QSS
424  _descriptionLabel->setIndent( itemDescriptionIndent() ); // Compensate for QRadioButton icon
425 
426  _vBox->addWidget( _descriptionLabel );
427  }
428 
429 
430  //
431  // Icon
432  //
433 
434  if ( ! iconName.empty() )
435  {
436  _hBox->addStretch( 1000 ); // this takes up any excess space
437 
438  _iconLabel = new QLabel( "", this );
439  YUI_CHECK_NEW( _iconLabel );
440 
441  QIcon icon = YQUI::ui()->loadIcon( iconName );
442  _iconLabel->setPixmap( icon.pixmap( ICON_SIZE ) );
443 
444  _descriptionLabel->setObjectName( "YQSelectorItemIcon" ); // for QSS
445  _iconLabel->setIndent(0);
446 
447  QSizePolicy sizePol( _iconLabel->sizePolicy() );
448  sizePol.setHorizontalStretch( 0 );
449  sizePol.setVerticalStretch( 0 );
450  _iconLabel->setSizePolicy( sizePol );
451 
452  _hBox->addWidget( _iconLabel );
453  }
454 
455  YUI_CHECK_PTR( _parent );
456  _parent->addItemWidget( this );
457 }
458 
459 
460 QAbstractButton *
461 YQSelectorItemWidget::createHeadingToggle( const std::string & label,
462  QWidget * parent )
463 {
464  QAbstractButton * toggle = 0;
465 
466  if ( singleSelection() )
467  toggle = new QRadioButton( fromUTF8( label ), this );
468  else
469  toggle = new QCheckBox( fromUTF8( label ), this );
470 
471  YUI_CHECK_NEW( toggle );
472 
473  connect( toggle, &pclass( _headingToggle )::toggled,
474  this, &pclass( this )::slotSelectionChanged );
475 
476  return toggle;
477 }
478 
479 
481 {
482  // This magic number in should really come from the widget style and some
483  // queries like
484  //
485  // style()->pixelMetric( QStyle::PM_RadioButtonLabelSpacing );
486  //
487  // and then added up from all the necessary individual pieces. But most
488  // of those things are never clearly specified. In the Qt code itself
489  // there are gems like "width += 4" at strategic places. So there is no
490  // realistic way for us on this level to do that right.
491 
492  return 20;
493 }
494 
495 
497 {
498  return _parent && _parent->enforceSingleSelection();
499 }
500 
501 
503 {
504  return _headingToggle->isChecked();
505 }
506 
507 
509 {
510  YQSignalBlocker sigBlocker( this );
511  _headingToggle->setChecked( sel );
512 }
513 
514 
515 void YQSelectorItemWidget::slotSelectionChanged( bool selected )
516 {
517  emit selectionChanged( this, selected );
518 }
519 
520 
521 void YQSelectorItemWidget::setLabel( const QString & label )
522 {
523  _headingToggle->setText( label );
524 }
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:212
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:318
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:266
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:496
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:480
YQSelectorItemWidget::createWidgets
virtual void createWidgets()
Create the subwidgets.
Definition: YQItemSelector.cc:333
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:461
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:508
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
YQSelectorItemWidget::setLabel
void setLabel(const QString &label)
Set a new label.
Definition: YQItemSelector.cc:521
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:327
YQItemSelector::deleteAllItems
virtual void deleteAllItems()
Delete all items.
Definition: YQItemSelector.cc:176
YQItemSelector::shortcutChanged
virtual void shortcutChanged()
Notification that some shortcut was changed.
Definition: YQItemSelector.cc:302
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:502