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 }
virtual wsze preferredSize()
Return the preferred size for this widget.
virtual ~NCItemSelectorBase()
Destructor.
virtual int preferredHeight()
Return the preferred height for this widget.
virtual void deleteAllItems()
Delete all items.
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...
virtual void addItem(YItem *item)
Add an item to this widget.
virtual NCTableTag * createTagCell(YItem *item)=0
Create a tag cell for an item.
virtual void selectItem(YItem *item, bool selected)
Select or deselect an item.
int findItemLine(YItem *item) const
Return the line number that contains the first line of 'item' or -1 if not found.
virtual bool setKeyboardFocus()
Set the keyboard focus to this widget.
int linesCount() const
Return the number of lines in this widget.
virtual void setVisibleItems(int newVal)
Set the number of visible items for this widget.
int currentLine() const
Return number of the current line, i.e.
virtual int preferredWidth()
Return the preferred width for this widget.
YItem * scrollUpToPreviousItem()
If the cursor is not on the first line of an item (the line with the "[x]" selector),...
virtual void setSize(int newWidth, int newHeight)
Set the size of this widget.
virtual void cycleCurrentItemStatus()=0
Cycle the status of the current item through its possible values.
virtual bool statusChangeAllowed(int fromStatus, int toStatus)
Return 'true' if a status change (by user interaction) from status 'fromStatus' to status 'toStatus' ...
NCItemSelectorBase(YWidget *parent, bool enforceSingleSelection)
Standard constructor.
virtual YItem * currentItem() const
Return the current item, i.e.
virtual NCTablePad * myPad() const
Return the pad for this widget; overloaded to narrow the type.
std::vector< std::string > descriptionLines(YItem *item) const
Return the description text for an item as multiple lines.
virtual void activateItem(YItem *item)
Activate selected item.
virtual void setCurrentItem(YItem *item)
Set the current item, i.e.
virtual void deselectAllItems()
Deselect all items.
YItem * scrollDownToNextItem()
If the cursor is not on the first line of an item (the line with the "[x]" selector),...
std::string description(YItem *item) const
Return the desription text for an item.
virtual NCursesEvent valueChangedNotify(YItem *item)=0
Notification that a status value was just changed in the input handler and the 'notify' flag is set.
virtual NCPad * CreatePad()
Create the pad for this widget.
virtual NCursesEvent wHandleInput(wint_t key)
Handle keyboard input.
virtual void setEnabled(bool do_bv)
Enable or disable this widget.
NCItemSelector(YWidget *parent, bool enforceSingleSelection)
Constructor.
virtual ~NCItemSelector()
Destructor.
virtual bool statusChangeAllowed(int fromStatus, int toStatus)
Return 'true' if a status change (by user interaction) from status 'fromStatus' to status 'toStatus' ...
virtual NCTableTag * createTagCell(YItem *item)
Create a tag cell for an item.
virtual NCursesEvent valueChangedNotify(YItem *item)
Notification that a status value was just changed in the input handler and the 'notify' flag is set.
virtual void cycleCurrentItemStatus()
Cycle the status of the current item through its possible values.
void deselectAllItemsExcept(YItem *exceptItem)
Deselect all items except the specified one.
Definition: NCPad.h:94
virtual void setEnabled(bool do_bv)=0
Pure virtual to make sure every widget implements it.
Definition: NCWidget.cc:391
int bkgd(const chtype ch)
Set the background property and apply it to the window.
Definition: ncursesw.h:1442
void sendEvent(NCursesEvent event)
Send an event to the UI.
Definition: YNCursesUI.cc:455
static YNCursesUI * ui()
Access the global Y2NCursesUI.
Definition: YNCursesUI.h:93
Definition: position.h:110
Definition: position.h:155