libyui-ncurses  2.55.0
NCItemSelector.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: NCItemSelector.cc
20 
21  Author: Stefan Hundhammer <shundhammer@suse.de>
22 
23 /-*/
24 
25 #include <boost/algorithm/string.hpp>
26 #include <algorithm>
27 #include <vector>
28 
29 #define YUILogComponent "ncurses"
30 #include <yui/YUILog.h>
31 #include "NCItemSelector.h"
32 #include "YNCursesUI.h"
33 
34 using std::string;
35 using std::vector;
36 
37 
38 
39 NCItemSelectorBase::NCItemSelectorBase( YWidget * parent, bool enforceSingleSelection )
40  : YItemSelector( parent, enforceSingleSelection )
41  , NCPadWidget( parent )
42  , _prefSize( 50, 5 ) // width, height
43  , _prefSizeDirty( true )
44  , _selectorWidth( string( "|[x] |" ).size() )
45 {
46  // yuiDebug() << endl;
47  InitPad();
48 }
49 
50 
52  const YItemCustomStatusVector & customStates )
53  : YItemSelector( parent, customStates )
54  , NCPadWidget( parent )
55  , _prefSize( 50, 5 ) // width, height
56  , _prefSizeDirty( true )
57  , _selectorWidth( 0 )
58 {
59  // yuiDebug() << endl;
60  InitPad();
61 
62  // Find the longest text indicator
63 
64  for ( int i=0; i < customStatusCount(); ++i )
65  {
66  int len = customStatus( i ).textIndicator().size();
67 
68  if ( _selectorWidth < len )
69  _selectorWidth = len;
70  }
71 
72  _selectorWidth += string( "| |" ).size();
73 }
74 
75 
77 {
78  // yuiDebug() << endl;
79 }
80 
81 
83 {
84  wsze psze( defPadSze() );
85  NCTablePad * npad = new NCTablePad( psze.H, psze.W, *this );
86  npad->bkgd( listStyle().item.plain );
87  npad->SetSepChar( ' ' );
88  return npad;
89 }
90 
91 
93 {
94  return preferredSize().W;
95 }
96 
97 
99 {
100  return preferredSize().H;
101 }
102 
103 
105 {
106  if ( _prefSizeDirty )
107  {
108  const int minHeight = 5; // 2 frame lines + 3 lines for content
109  const int minWidth = 20;
110  int visibleItemsCount = std::min( itemsCount(), visibleItems() );
111 
112  _prefSize.W = 0;
113  _prefSize.H = 0;
114 
115  for ( int i=0; i < visibleItemsCount; ++i )
116  {
117  if ( _prefSize.H > i ) // need a separator line?
118  ++_prefSize.H; // for the separator line
119 
120  ++_prefSize.H; // For the item label
121 
122  vector<string> lines = descriptionLines( itemAt( i ) );
123  _prefSize.H += lines.size();
124 
125  for ( const string & line: lines ) // as wide as the longest line
126  _prefSize.W = std::max( _prefSize.W, (int) line.size() + _selectorWidth );
127  }
128 
129  _prefSize.H += 2; // for the frame lines
130  _prefSize.W = std::max( _prefSize.W, minWidth );
131  _prefSize.H = std::max( _prefSize.H, minHeight );
132  _prefSizeDirty = false;
133  }
134 
135  return _prefSize;
136 }
137 
138 
139 void NCItemSelectorBase::setSize( int newwidth, int newheight )
140 {
141  wRelocate( wpos( 0 ), wsze( newheight, newwidth ) );
142 }
143 
144 
146 {
147  if ( ! grabFocus() )
148  return YWidget::setKeyboardFocus();
149 
150  return true;
151 }
152 
153 
155 {
156  NCWidget::setEnabled( do_bv );
157  YItemSelector::setEnabled( do_bv );
158 }
159 
160 
162 {
163  _prefSizeDirty = true;
164  YItemSelector::setVisibleItems( newVal );
165 }
166 
167 
169 {
170  if ( !myPad()->Lines() )
171  return 0;
172 
173  NCTableTag * tag = tagCell( currentLine() );
174 
175  return tag ? tag->origItem() : 0;
176 }
177 
178 
180 {
181  int lineNo = findItemLine( item );
182 
183  if ( lineNo >= 0 )
184  myPad()->ScrlLine( lineNo );
185 }
186 
187 
188 void NCItemSelectorBase::addItem( YItem * item )
189 {
190  vector<NCTableCol*> cells( 2U, 0 );
191 
192  if ( item )
193  {
194  _prefSizeDirty = true;
195  int lineNo = myPad()->Lines();
196 
197  if ( lineNo > itemsCount() )
198  {
199  // Add a blank line as a separator from the previous item
200  //
201  // ...but only if there is any previous item that had a description.
202  // If there are only items without description, we don't need separator lines.
203 
204  cells[0] = new NCTableCol( "", NCTableCol::SEPARATOR );
205  cells[1] = new NCTableCol( "", NCTableCol::SEPARATOR );
206  myPad()->Append( cells );
207  ++lineNo;
208  }
209 
210  // yuiDebug() << "Adding new item " << item->label() << " at line #" << lineNo << endl;
211 
212  // Add the item label with "[ ]" or "( )" for selection
213 
214  YItemSelector::addItem( item );
215  cells[0] = createTagCell( item );
216  cells[1] = new NCTableCol( item->label() );
217 
218  NCTableLine * tableLine = new NCTableLine( cells );
219  myPad()->Append( tableLine );
220 
221  if ( enforceSingleSelection() && item->selected() )
222  myPad()->ScrlLine( lineNo );
223 
224 
225  // Add the item description (possible multi-line)
226 
227  vector<string> lines = descriptionLines( item );
228 
229  for ( const string & line: lines )
230  {
231  cells[0] = new NCTableCol( "", NCTableCol::PLAIN );
232  cells[1] = new NCTableCol( line, NCTableCol::PLAIN );
233  myPad()->Append( cells );
234  }
235 
236  DrawPad();
237  }
238 }
239 
240 
242 {
243  NCTableLine * tableLine = myPad()->ModifyLine( index );
244 
245  if ( ! tableLine )
246  return 0;
247 
248  return dynamic_cast<NCTableTag *> ( tableLine->GetCol( 0 ) );
249 }
250 
251 
252 int NCItemSelectorBase::findItemLine( YItem * wantedItem ) const
253 {
254  for ( int lineNo = 0; lineNo < (int) myPad()->Lines(); ++lineNo )
255  {
256  NCTableTag * tag = tagCell( lineNo );
257 
258  if ( tag && tag->origItem() == wantedItem )
259  return lineNo;
260  }
261 
262  return -1;
263 }
264 
265 
266 string NCItemSelectorBase::description( YItem * item ) const
267 {
268  string desc;
269 
270  if ( item )
271  {
272  YDescribedItem * descItem = dynamic_cast<YDescribedItem *>( item );
273 
274  if ( descItem )
275  desc = descItem->description();
276  }
277 
278  return desc;
279 }
280 
281 
282 vector<string>
284 {
285  vector<string> lines;
286 
287  // This temporary variable is only needed to work around a bug in older boost versions:
288  // https://github.com/boostorg/algorithm/commit/c6f784cb
289 
290  string desc = description( item );
291  boost::split( lines, desc, boost::is_any_of( "\n" ) );
292 
293  return lines;
294 }
295 
296 
298 {
299  YItemSelector::deleteAllItems();
300  myPad()->ClearTable();
301  DrawPad();
302 }
303 
304 
305 void NCItemSelectorBase::selectItem( YItem *item, bool selected )
306 {
307  if ( item )
308  {
309  YItemSelector::selectItem( item, selected );
310 
311  NCTableTag * tag = (NCTableTag *) item->data();
312  YUI_CHECK_PTR( tag );
313 
314  tag->SetSelected( selected );
315 
316  DrawPad();
317  }
318 }
319 
320 
322 {
323  YItemSelector::deselectAllItems();
324 
325  for ( int i = 0; i < linesCount(); i++ )
326  {
327  NCTableTag * tag = tagCell( i );
328 
329  if ( tag )
330  tag->SetSelected( false );
331  }
332 
333  DrawPad();
334 }
335 
336 
337 YItem *
339 {
340  YItem * item = 0;
341 
342  while ( currentLine() < linesCount() - 1 )
343  {
344  YItem * item = currentItem();
345 
346  if ( item )
347  return item;
348 
349  // yuiDebug() << "Scrolling down" << endl;
350  myPad()->ScrlDown();
351  }
352 
353  if ( ! item ) // That was the last one
354  item = scrollUpToPreviousItem();
355 
356  return item;
357 }
358 
359 
360 YItem *
362 {
363  while ( true )
364  {
365  YItem * item = currentItem();
366 
367  if ( item )
368  return item;
369 
370  if ( currentLine() == 0 )
371  return 0;
372 
373  // yuiDebug() << "Scrolling up" << endl;
374  myPad()->ScrlUp();
375  }
376 
377  /**NOTREACHED**/
378  return 0;
379 }
380 
381 
384 {
385  NCursesEvent ret;
386  YItem * changedItem = 0;
387  YItem * curItem = currentItem();
388 
389  switch ( key )
390  {
391  case KEY_SPACE:
392  case KEY_RETURN: // Cycle item status and stay on this item
393 
394  if ( ! curItem )
395  curItem = scrollUpToPreviousItem();
396 
397  if ( curItem )
398  {
400  changedItem = curItem;
401  }
402 
403  break;
404 
405 
406  case '+': // Select this item and go to the next item
407 
408  if ( ! curItem )
409  curItem = scrollUpToPreviousItem();
410 
411  if ( curItem &&
412  curItem->status() != 1 &&
413  statusChangeAllowed( curItem->status(), 1 ) )
414  {
415  setItemStatus( curItem, 1 );
416  changedItem = curItem;
417  }
418 
419  if ( ! enforceSingleSelection() )
420  {
421  myPad()->ScrlDown();
422  curItem = scrollDownToNextItem();
423  }
424 
425  break;
426 
427 
428  case '-': // Deselect this item and go to the next item
429 
430  if ( ! curItem )
431  curItem = scrollUpToPreviousItem();
432 
433  if ( curItem &&
434  curItem->status() > 0 &&
435  statusChangeAllowed( curItem->status(), 0 ) )
436  {
437  setItemStatus( curItem, 0 );
438  changedItem = curItem;
439  }
440 
441  if ( ! enforceSingleSelection() )
442  {
443  myPad()->ScrlDown();
444  curItem = scrollDownToNextItem();
445  }
446 
447  break;
448 
449  // Those keys have different meanings in this widget:
450  // Scroll up and down by item, not by line.
451 
452  case KEY_UP: // Scroll up one item
453  myPad()->ScrlUp();
455  break;
456 
457  case KEY_DOWN: // Scroll down one item
458  myPad()->ScrlDown();
460  break;
461 
462  case KEY_END: // Scroll to the last item
463  myPad()->ScrlToLastLine();
464  // We want to be on the last item, not on the last line
466  break;
467 
468  // The Home key (scroll to the first item) is handled in the base
469  // class: Since the first is on the first line, so we can simply let
470  // the base class scroll to the first line.
471 
472 
473  // We would have liked to use KEY_SHIFT_UP and KEY_SHIFT_DOWN for
474  // scrolling up or down by line rather by item, but unfortunately
475  // NCurses does not support that at all; there are no predefined keys
476  // for any of that (but oddly enough KEY_SLEFT and KEY_SRIGHT for
477  // shifted arrow left or right), and there is no way to query the
478  // status of the modifier keys.
479  //
480  // See also /usr/include/ncurses/ncurses.h .
481  //
482  // There are lots of articles on StackOverflow etc. about this topic,
483  // but there is not a single portable solution; not even portable
484  // between the various terminal emulators (xterm, KDE konsole,
485  // gnome-terminal, xfce4-terminal) or the Linux console, let alone all
486  // the various other terminal types out there.
487  //
488  // So we have to resort to different keys. Not sure how many users will
489  // even realize that, but maybe some users actually try to use
490  // 'vi'-like keys like 'j' or 'k'.
491 
492  case 'j': // For 'vi' fans: Scroll down one line
493  myPad()->ScrlDown();
494  break;
495 
496  case 'k': // For 'vi' fans: Scroll up one line
497  myPad()->ScrlUp();
498  break;
499 
500  default:
501  handleInput( key ); // Call base class input handler
502  break;
503  }
504 
505  if ( notify() && changedItem )
506  ret = valueChangedNotify( changedItem );
507 
508  return ret;
509 }
510 
511 
513 {
514  if ( notify() )
515  {
516  NCursesEvent event = valueChangedNotify( item );
517  event.selection = (YMenuItem *) item;
518  event.widget = this;
519 
520  YNCursesUI::ui()->sendEvent( event );
521  }
522 }
523 
524 // ----------------------------------------------------------------------
525 
526 
527 
528 NCItemSelector::NCItemSelector( YWidget * parent, bool enforceSingleSelection )
529  : NCItemSelectorBase( parent, enforceSingleSelection )
530 {
531  yuiDebug() << endl;
532 }
533 
534 
536 {
537  yuiDebug() << endl;
538 }
539 
540 
541 NCTableTag *
543 {
544  NCTableTag * tag = new NCTableTag( item, item->selected(), enforceSingleSelection() );
545  YUI_CHECK_NEW( tag );
546 
547  return tag;
548 }
549 
550 
553 {
554  if ( enforceSingleSelection() && item && item->selected() )
555  deselectAllItemsExcept( item );
556 
557  yuiDebug() << "Sending ValueChanged event for " << (YItemSelector* ) this << endl;
558 
559  return NCursesEvent::ValueChanged;
560 }
561 
562 
564 {
565  YItem *item = currentItem();
566 
567  if ( item )
568  {
569  if ( enforceSingleSelection() )
570  {
571  selectItem( item, true );
572  deselectAllItemsExcept( item );
573  }
574  else // Multi-selection
575  {
576  selectItem( item, !( item->selected() ) );
577  }
578  }
579 }
580 
581 
582 bool NCItemSelector::statusChangeAllowed( int fromStatus, int toStatus )
583 {
584  if ( fromStatus == toStatus ) // No use setting to the same status as before
585  return false;
586 
587  if ( toStatus < 0 || toStatus > 1 )
588  return false;
589 
590  if ( enforceSingleSelection() )
591  return toStatus == 1; // Allow only setting 0 -> 1
592  else
593  return true;
594 }
595 
596 
597 void NCItemSelector::deselectAllItemsExcept( YItem * exceptItem )
598 {
599  for ( YItemIterator it = itemsBegin(); it != itemsEnd(); ++it )
600  {
601  if ( *it != exceptItem )
602  {
603  (*it)->setSelected( false );
604  NCTableTag * tag = (NCTableTag *) (*it)->data();
605 
606  if ( tag )
607  tag->SetSelected( false );
608  }
609  }
610 
611  DrawPad();
612 }
wsze
Definition: position.h:155
NCItemSelectorBase::activateItem
virtual void activateItem(YItem *item)
Activate selected item.
Definition: NCItemSelector.cc:512
NCItemSelectorBase::tagCell
virtual NCTableTag * tagCell(int index) const
Return the tag cell (the cell with the "[x]" or "(x)" selector) for the item with the specified index...
Definition: NCItemSelector.cc:241
YNCursesUI::ui
static YNCursesUI * ui()
Access the global Y2NCursesUI.
Definition: YNCursesUI.h:93
YNCursesUI::sendEvent
void sendEvent(NCursesEvent event)
Send an event to the UI.
Definition: YNCursesUI.cc:455
NCItemSelectorBase::myPad
virtual NCTablePad * myPad() const
Return the pad for this widget; overloaded to narrow the type.
Definition: NCItemSelector.h:257
NCTableCol
Definition: NCTableItem.h:147
NCItemSelectorBase::CreatePad
virtual NCPad * CreatePad()
Create the pad for this widget.
Definition: NCItemSelector.cc:82
NCItemSelectorBase::currentItem
virtual YItem * currentItem() const
Return the current item, i.e.
Definition: NCItemSelector.cc:168
NCItemSelectorBase::deselectAllItems
virtual void deselectAllItems()
Deselect all items.
Definition: NCItemSelector.cc:321
NCItemSelectorBase::preferredHeight
virtual int preferredHeight()
Return the preferred height for this widget.
Definition: NCItemSelector.cc:98
NCItemSelector::cycleCurrentItemStatus
virtual void cycleCurrentItemStatus()
Cycle the status of the current item through its possible values.
Definition: NCItemSelector.cc:563
NCItemSelectorBase::descriptionLines
std::vector< std::string > descriptionLines(YItem *item) const
Return the description text for an item as multiple lines.
Definition: NCItemSelector.cc:283
NCWidget::setEnabled
virtual void setEnabled(bool do_bv)=0
Pure virtual to make sure every widget implements it.
Definition: NCWidget.cc:391
NCItemSelectorBase::setEnabled
virtual void setEnabled(bool do_bv)
Enable or disable this widget.
Definition: NCItemSelector.cc:154
NCItemSelector::createTagCell
virtual NCTableTag * createTagCell(YItem *item)
Create a tag cell for an item.
Definition: NCItemSelector.cc:542
NCTablePad
Definition: NCTablePad.h:151
NCItemSelectorBase::statusChangeAllowed
virtual bool statusChangeAllowed(int fromStatus, int toStatus)
Return 'true' if a status change (by user interaction) from status 'fromStatus' to status 'toStatus' ...
Definition: NCItemSelector.h:194
NCItemSelectorBase::NCItemSelectorBase
NCItemSelectorBase(YWidget *parent, bool enforceSingleSelection)
Standard constructor.
Definition: NCItemSelector.cc:39
NCItemSelectorBase::selectItem
virtual void selectItem(YItem *item, bool selected)
Select or deselect an item.
Definition: NCItemSelector.cc:305
NCItemSelectorBase::cycleCurrentItemStatus
virtual void cycleCurrentItemStatus()=0
Cycle the status of the current item through its possible values.
NCItemSelector::~NCItemSelector
virtual ~NCItemSelector()
Destructor.
Definition: NCItemSelector.cc:535
NCItemSelectorBase::setSize
virtual void setSize(int newWidth, int newHeight)
Set the size of this widget.
Definition: NCItemSelector.cc:139
NCItemSelectorBase::setVisibleItems
virtual void setVisibleItems(int newVal)
Set the number of visible items for this widget.
Definition: NCItemSelector.cc:161
NCItemSelectorBase::addItem
virtual void addItem(YItem *item)
Add an item to this widget.
Definition: NCItemSelector.cc:188
NCPad
Definition: NCPad.h:94
NCItemSelector::statusChangeAllowed
virtual bool statusChangeAllowed(int fromStatus, int toStatus)
Return 'true' if a status change (by user interaction) from status 'fromStatus' to status 'toStatus' ...
Definition: NCItemSelector.cc:582
NCItemSelector::valueChangedNotify
virtual NCursesEvent valueChangedNotify(YItem *item)
Notification that a status value was just changed in the input handler and the 'notify' flag is set.
Definition: NCItemSelector.cc:552
wpos
Definition: position.h:110
NCItemSelectorBase
Definition: NCItemSelector.h:38
NCItemSelectorBase::scrollDownToNextItem
YItem * scrollDownToNextItem()
If the cursor is not on the first line of an item (the line with the "[x]" selector),...
Definition: NCItemSelector.cc:338
NCursesWindow::bkgd
int bkgd(const chtype ch)
Set the background property and apply it to the window.
Definition: ncursesw.h:1442
NCItemSelectorBase::scrollUpToPreviousItem
YItem * scrollUpToPreviousItem()
If the cursor is not on the first line of an item (the line with the "[x]" selector),...
Definition: NCItemSelector.cc:361
NCItemSelectorBase::~NCItemSelectorBase
virtual ~NCItemSelectorBase()
Destructor.
Definition: NCItemSelector.cc:76
NCItemSelector::NCItemSelector
NCItemSelector(YWidget *parent, bool enforceSingleSelection)
Constructor.
Definition: NCItemSelector.cc:528
NCTableLine
Definition: NCTableItem.h:40
NCItemSelectorBase::description
std::string description(YItem *item) const
Return the desription text for an item.
Definition: NCItemSelector.cc:266
NCItemSelectorBase::valueChangedNotify
virtual NCursesEvent valueChangedNotify(YItem *item)=0
Notification that a status value was just changed in the input handler and the 'notify' flag is set.
NCItemSelectorBase::wHandleInput
virtual NCursesEvent wHandleInput(wint_t key)
Handle keyboard input.
Definition: NCItemSelector.cc:383
NCItemSelectorBase::createTagCell
virtual NCTableTag * createTagCell(YItem *item)=0
Create a tag cell for an item.
NCItemSelector::deselectAllItemsExcept
void deselectAllItemsExcept(YItem *exceptItem)
Deselect all items except the specified one.
Definition: NCItemSelector.cc:597
NCItemSelectorBase::linesCount
int linesCount() const
Return the number of lines in this widget.
Definition: NCItemSelector.h:120
NCItemSelectorBase::setCurrentItem
virtual void setCurrentItem(YItem *item)
Set the current item, i.e.
Definition: NCItemSelector.cc:179
NCItemSelectorBase::preferredSize
virtual wsze preferredSize()
Return the preferred size for this widget.
Definition: NCItemSelector.cc:104
NCItemSelectorBase::deleteAllItems
virtual void deleteAllItems()
Delete all items.
Definition: NCItemSelector.cc:297
NCursesEvent
Definition: NCurses.h:73
NCItemSelectorBase::currentLine
int currentLine() const
Return number of the current line, i.e.
Definition: NCItemSelector.h:126
NCPadWidget
Definition: NCPadWidget.h:38
NCItemSelectorBase::findItemLine
int findItemLine(YItem *item) const
Return the line number that contains the first line of 'item' or -1 if not found.
Definition: NCItemSelector.cc:252
NCItemSelectorBase::preferredWidth
virtual int preferredWidth()
Return the preferred width for this widget.
Definition: NCItemSelector.cc:92
NCTableTag
Definition: NCTablePad.h:102
NCItemSelectorBase::setKeyboardFocus
virtual bool setKeyboardFocus()
Set the keyboard focus to this widget.
Definition: NCItemSelector.cc:145