libyui-qt  2.52.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 //-----------------------------------------------------------------------------
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 }
Helper class to block Qt signals for QWidgets or QObjects as long as this object exists.
virtual int itemDescriptionIndent() const
Return the amount of indentation in pixels for the description text.
virtual void setEnabled(bool enabled)
Set enabled/disabled state.
virtual void createWidgets()
Create the subwidgets.
virtual ~YQItemSelector()
Destructor.
YQItemSelector(YWidget *parent, bool enforceSingleSelection=true)
Standard constructor.
virtual int preferredWidth()
Preferred width of the widget.
QAbstractButton * headingToggle() const
Return the widget that handles the selection: Either a QRadioButton or a QCheckBox.
QIcon loadIcon(const string &iconName) const
Load an icon.
Definition: YQUI.cc:708
void init()
Common initializations for all constructors.
void deselectOtherItems(YItem *selectedItem)
Deselect all items except &#39;selectedItem&#39;.
virtual void addItems(const YItemCollection &itemCollection)
Add multiple items.
virtual ~YQSelectorItemWidget()
Destructor.
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
virtual void deselectAllItems()
Deselect all items.
virtual void setSize(int newWidth, int newHeight)
Set the new size of the widget.
Class for the widgets of one ItemSelector item.
void slotSelectionChanged(YQSelectorItemWidget *itemWidget, bool selected)
Notification that an item has been selected.
virtual int preferredHeight()
Preferred height of the widget.
virtual void setSelected(bool sel=true)
Select the appropriate widget according to the parent&#39;s selection policy (single or multi selection)...
virtual void activateItem(YItem *item)
Activate selected item.
bool singleSelection() const
Return &#39;true&#39; if the parent YItemSelector has single selection (1-of-n).
virtual void addItem(YItem *item)
Add an item.
virtual bool selected() const
Return &#39;true&#39; if this item is selected, &#39;false&#39; otherwise.
virtual bool setKeyboardFocus()
Accept the keyboard focus.
YQSelectorItemWidget(YQItemSelector *parent, YItem *item)
Constructor.
virtual QAbstractButton * createHeadingToggle(const std::string &label, QWidget *parent)
Create the appropriate toggle button for this item and connect it to appropriate slots.
virtual void deleteAllItems()
Delete all items.
static YQUI * ui()
Access the global Qt-UI.
Definition: YQUI.h:83
void addItemWidget(YQSelectorItemWidget *itemWidget)
Add an item widget to the appropriate layout.
virtual void selectItem(YItem *item, bool selected=true)
Select or deselect an item.