libyui-qt-pkg  2.45.28
YQPkgFilterTab.cc
1 /**************************************************************************
2 Copyright (C) 2000 - 2010 Novell, Inc.
3 All Rights Reserved.
4 
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 
19 **************************************************************************/
20 
21 
22 /*---------------------------------------------------------------------\
23 | |
24 | __ __ ____ _____ ____ |
25 | \ \ / /_ _/ ___|_ _|___ \ |
26 | \ V / _` \___ \ | | __) | |
27 | | | (_| |___) || | / __/ |
28 | |_|\__,_|____/ |_| |_____| |
29 | |
30 | core system |
31 | (C) SuSE GmbH |
32 \----------------------------------------------------------------------/
33 
34  File: YQPkgFilterTab.cc
35 
36  Author: Stefan Hundhammer <sh@suse.de>
37 
38  Textdomain "qt-pkg"
39 
40 /-*/
41 
42 
43 #include <vector>
44 #include <algorithm> // std::swap()
45 
46 #include <QAction>
47 #include <QHBoxLayout>
48 #include <QMenu>
49 #include <QPushButton>
50 #include <QSplitter>
51 #include <QStackedWidget>
52 #include <QTabBar>
53 #include <QToolButton>
54 #include <QSettings>
55 
56 #define YUILogComponent "qt-pkg"
57 #include "YUILog.h"
58 
59 #include "YUI.h"
60 #include "YUIException.h"
61 #include "YApplication.h"
62 #include "YQPkgFilterTab.h"
63 #include "YQPkgDiskUsageList.h"
64 #include "YQSignalBlocker.h"
65 #include "YQIconPool.h"
66 #include "YQi18n.h"
67 #include "utf8.h"
68 
69 
70 using std::vector;
71 typedef vector<YQPkgFilterPage *> YQPkgFilterPageVector;
72 
73 #define SHOW_ONLY_IMPORTANT_PAGES 1
74 #define VIEW_BUTTON_LEFT 1
75 
76 #define SETTINGS_DIR "YaST2"
77 
78 
79 #define MARGIN 5 // inner margin between 3D borders and content
80 #define TOP_EXTRA_MARGIN 3
81 #define SPLITTER_HALF_SPACING 2
82 
83 
85 {
86  YQPkgFilterTabPrivate( const QString & name )
87  : settingsName( name )
88  , baseClassWidgetStack(0)
89  , outerSplitter(0)
90  , leftPaneSplitter(0)
91  , filtersWidgetStack(0)
92  , diskUsageList(0)
93  , rightPane(0)
94  , viewButton(0)
95  , tabContextMenu(0)
96  , tabContextMenuPage(0)
97  {}
98 
99  QString settingsName;
100  QStackedWidget * baseClassWidgetStack;
101  QSplitter * outerSplitter;
102  QSplitter * leftPaneSplitter;
103  QStackedWidget * filtersWidgetStack;
104  YQPkgDiskUsageList * diskUsageList;
105  QWidget * rightPane;
106  QPushButton * viewButton;
107  QMenu * tabContextMenu;
108  QAction * actionMovePageLeft;
109  QAction * actionMovePageRight;
110  QAction * actionClosePage;
111  YQPkgFilterPage * tabContextMenuPage;
112  YQPkgFilterPageVector pages;
113 };
114 
115 
116 
117 
118 YQPkgFilterTab::YQPkgFilterTab( QWidget * parent, const QString & settingsName )
119  : QTabWidget( parent )
120  , priv( new YQPkgFilterTabPrivate( settingsName ) )
121 {
122  YUI_CHECK_NEW( priv );
123 
124  // Nasty hack: Find the base class's QStackedWidget in its widget tree so
125  // we have a place to put our own widgets. Unfortunately, this is private
126  // in the base class, but Qt lets us search the widget hierarchy by widget
127  // type.
128 
129  priv->baseClassWidgetStack = findChild<QStackedWidget*>();
130  YUI_CHECK_PTR( priv->baseClassWidgetStack );
131 
132  // Nasty hack: Disconnect the base class from signals from its tab bar.
133  // We will handle that signal on our own.
134 
135  disconnect( tabBar(), &QTabBar::currentChanged, 0, 0 );
136 
137 
138  //
139  // Splitter that divides this widget into a left and a right pane
140  //
141 
142  priv->outerSplitter = new QSplitter( Qt::Horizontal, this );
143  YUI_CHECK_NEW( priv->outerSplitter );
144 
145  priv->outerSplitter->setSizePolicy( QSizePolicy( QSizePolicy::Expanding,
146  QSizePolicy::Expanding ) );
147  priv->baseClassWidgetStack->addWidget( priv->outerSplitter );
148 
149 
150 #if SHOW_ONLY_IMPORTANT_PAGES
151 
152  //
153  // "View" and "Close" buttons
154  //
155 
156  QWidget * buttonBox = new QWidget( this );
157  YUI_CHECK_NEW( buttonBox );
158  setCornerWidget( buttonBox, Qt::TopRightCorner );
159  buttonBox->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) );
160 
161  QHBoxLayout * buttonBoxLayout = new QHBoxLayout( buttonBox );
162  YUI_CHECK_NEW( buttonBoxLayout );
163  buttonBox->setLayout( buttonBoxLayout );
164  buttonBoxLayout->setContentsMargins( 0, 0, 0, 0 );
165 
166 #if VIEW_BUTTON_LEFT
167 
168  // Translators: Button with pop-up menu to open a new page (very much like
169  // in a web browser) with another package filter view or to switch to an
170  // existing one if it's open already
171 
172  priv->viewButton = new QPushButton( _( "&View" ), this );
173  YUI_CHECK_NEW( priv->viewButton );
174  setCornerWidget( priv->viewButton, Qt::TopLeftCorner );
175 #else
176  priv->viewButton = new QPushButton( _( "&View" ), buttonBox );
177  YUI_CHECK_NEW( priv->viewButton );
178  buttonBoxLayout->addWidget( priv->viewButton );
179 
180 #endif // VIEW_BUTTON_LEFT
181 
182  QMenu * menu = new QMenu( priv->viewButton );
183  YUI_CHECK_NEW( menu );
184  priv->viewButton->setMenu( menu );
185 
186  connect( menu, SIGNAL( triggered( QAction * ) ),
187  this, SLOT ( showPage ( QAction * ) ) );
188 
189 #endif // SHOW_ONLY_IMPORTANT_PAGES
190 
191 
192  //
193  // Splitter that divides the left pane into upper filters area and disk usage area
194  //
195 
196  priv->leftPaneSplitter = new QSplitter( Qt::Vertical, priv->outerSplitter );
197  YUI_CHECK_NEW( priv->leftPaneSplitter );
198 
199 
200  //
201  // Left pane content
202  //
203 
204  priv->filtersWidgetStack = new QStackedWidget( priv->leftPaneSplitter );
205  YUI_CHECK_NEW( priv->filtersWidgetStack );
206 
207  priv->diskUsageList = new YQPkgDiskUsageList( priv->leftPaneSplitter );
208  YUI_CHECK_NEW( priv->diskUsageList );
209 
210  {
211  QSplitter * sp = priv->leftPaneSplitter;
212  sp->setStretchFactor( sp->indexOf( priv->filtersWidgetStack ), 1 );
213  sp->setStretchFactor( sp->indexOf( priv->diskUsageList ), 2 );
214 
215 
216  // FIXME: Don't always hide the disk usage list
217  QList<int> sizes;
218  sizes << priv->leftPaneSplitter->height();
219  sizes << 0;
220  sp->setSizes( sizes );
221  }
222 
223 
224  //
225  // Right pane
226  //
227 
228  priv->rightPane = new QWidget( priv->outerSplitter );
229  YUI_CHECK_NEW( priv->rightPane );
230 
231 
232  //
233  // Stretch factors for left and right pane
234  //
235  {
236  QSplitter * sp = priv->outerSplitter;
237  sp->setStretchFactor( sp->indexOf( priv->leftPaneSplitter ), 0 );
238  sp->setStretchFactor( sp->indexOf( priv->rightPane ), 1 );
239  }
240 
241 
242  // Set up connections
243 
244  connect( tabBar(), &QTabBar::currentChanged,
245  this, static_cast<void (YQPkgFilterTab::*)(int)>(&YQPkgFilterTab::showPage) );
246 
247  tabBar()->installEventFilter( this ); // for tab context menu
248 
249 
250  //
251  // Cosmetics
252  //
253 
254  priv->baseClassWidgetStack->setContentsMargins( MARGIN, // left
255  MARGIN + TOP_EXTRA_MARGIN, // top
256  MARGIN, // right
257  MARGIN ); // bottom
258 
259  priv->leftPaneSplitter->setContentsMargins ( 0, // left
260  0, // top
261  SPLITTER_HALF_SPACING, // right
262  0 ); // bottom
263 
264  // priv->rightPane->setContentsMargins() is set when widgets are added to the right pane
265 }
266 
267 
269 {
270  saveSettings();
271 
272  for ( YQPkgFilterPageVector::iterator it = priv->pages.begin();
273  it != priv->pages.end();
274  ++it )
275  {
276  delete (*it);
277  }
278 
279  priv->pages.clear();
280 }
281 
282 
283 QWidget *
285 {
286  return priv->rightPane;
287 }
288 
289 
292 {
293  return priv->diskUsageList;
294 }
295 
296 
297 void
298 YQPkgFilterTab::addPage( const QString & pageLabel,
299  QWidget * pageContent,
300  const QString & internalName )
301 {
302  YQPkgFilterPage * page = new YQPkgFilterPage( pageLabel,
303  pageContent,
304  internalName );
305  YUI_CHECK_NEW( page );
306 
307  priv->pages.push_back( page );
308  priv->filtersWidgetStack->addWidget( pageContent );
309 
310 
311  if ( priv->viewButton && priv->viewButton->menu() )
312  {
313  QAction * action = new QAction( pageLabel, this );
314  YUI_CHECK_NEW( action );
315  action->setData( qVariantFromValue( pageContent ) );
316 
317  priv->viewButton->menu()->addAction( action );
318  }
319 
320 #if ! SHOW_ONLY_IMPORTANT_PAGES
321  page->tabIndex = tabBar()->addTab( pageLabel );
322 #endif
323 }
324 
325 
326 void
327 YQPkgFilterTab::showPage( QWidget * pageContent )
328 {
329  YQPkgFilterPage * page = findPage( pageContent );
330  YUI_CHECK_PTR( page );
331 
332  showPage( page );
333 }
334 
335 
336 void
337 YQPkgFilterTab::showPage( const QString & internalName )
338 {
339  YQPkgFilterPage * page = findPage( internalName );
340  YUI_CHECK_PTR( page );
341 
342  showPage( page );
343 }
344 
345 
346 void
348 {
349  YQPkgFilterPage * page = findPage( tabIndex );
350 
351  if ( page )
352  showPage( page );
353 }
354 
355 
356 void
357 YQPkgFilterTab::showPage( QAction * action )
358 {
359  if ( ! action )
360  return;
361 
362  QWidget * pageContent = action->data().value<QWidget *>();
363  showPage( pageContent );
364 }
365 
366 
367 void
369 {
370  YUI_CHECK_PTR( page );
371  YQSignalBlocker sigBlocker( tabBar() );
372 
373  if ( page->tabIndex < 0 ) // No corresponding tab yet?
374  {
375  // Add a tab for that page
376  page->tabIndex = tabBar()->addTab( page->label );
377  }
378 
379  priv->filtersWidgetStack->setCurrentWidget( page->content );
380  tabBar()->setCurrentIndex( page->tabIndex );
381  priv->tabContextMenuPage = page;
382 
383  emit currentChanged( page->content );
384 }
385 
386 
387 void
389 {
390  while ( tabBar()->count() > 0 )
391  {
392  tabBar()->removeTab( 0 );
393  }
394 
395  for ( YQPkgFilterPageVector::iterator it = priv->pages.begin();
396  it != priv->pages.end();
397  ++it )
398  {
399  (*it)->tabIndex = -1;
400  }
401 }
402 
403 
404 void
406 {
407  if ( tabBar()->count() > 1 )
408  {
409  int currentIndex = tabBar()->currentIndex();
410  YQPkgFilterPage * currentPage = findPage( currentIndex );
411 
412  if ( currentPage )
413  currentPage->tabIndex = -1;
414 
415  tabBar()->removeTab( currentIndex );
416 
417  //
418  // Adjust tab index of the active pages to the right of that page
419  //
420 
421  for ( YQPkgFilterPageVector::iterator it = priv->pages.begin();
422  it != priv->pages.end();
423  ++it )
424  {
425  YQPkgFilterPage * page = *it;
426 
427  if ( page->tabIndex >= currentIndex )
428  page->tabIndex--;
429  }
430 
431  showPage( tabBar()->currentIndex() );
432  }
433 }
434 
435 
437 YQPkgFilterTab::findPage( QWidget * pageContent )
438 {
439  for ( YQPkgFilterPageVector::iterator it = priv->pages.begin();
440  it != priv->pages.end();
441  ++it )
442  {
443  if ( (*it)->content == pageContent )
444  return *it;
445  }
446 
447  return 0;
448 }
449 
450 
452 YQPkgFilterTab::findPage( const QString & internalName )
453 {
454  for ( YQPkgFilterPageVector::iterator it = priv->pages.begin();
455  it != priv->pages.end();
456  ++it )
457  {
458  if ( (*it)->id == internalName )
459  return *it;
460  }
461 
462  return 0;
463 }
464 
465 
468 {
469  if ( tabIndex < 0 )
470  return 0;
471 
472  for ( YQPkgFilterPageVector::iterator it = priv->pages.begin();
473  it != priv->pages.end();
474  ++it )
475  {
476  if ( (*it)->tabIndex == tabIndex )
477  return *it;
478  }
479 
480  return 0;
481 }
482 
483 
484 int
486 {
487  return tabBar()->count();
488 }
489 
490 
491 bool
492 YQPkgFilterTab::eventFilter ( QObject * watchedObj, QEvent * event )
493 {
494  if ( watchedObj == tabBar() &&
495  event && event->type() == QEvent::MouseButtonPress )
496  {
497  QMouseEvent * mouseEvent = dynamic_cast<QMouseEvent *> (event);
498 
499  if ( mouseEvent && mouseEvent->button() == Qt::RightButton )
500  {
501  return postTabContextMenu( mouseEvent->pos() );
502  }
503  }
504 
505  return QTabWidget::eventFilter( watchedObj, event );
506 }
507 
508 
509 bool
511 {
512  int tabIndex = tabBar()->tabAt( pos );
513 
514  if ( tabIndex >= 0 ) // -1 means "no tab at that position"
515  {
516  priv->tabContextMenuPage = findPage( tabIndex );
517 
518  if ( priv->tabContextMenuPage )
519  {
520  if ( ! priv->tabContextMenu )
521  {
522  // On-demand menu creation
523 
524  priv->tabContextMenu = new QMenu( this );
525  YUI_CHECK_NEW( priv->tabContextMenu );
526 
527  // Translators: Change this to "right" for Arabic and Hebrew
528  priv->actionMovePageLeft = new QAction( YUI::yApp()->reverseLayout() ?
529  YQIconPool::arrowRight() : YQIconPool::arrowLeft(),
530  _( "Move page &left" ), this );
531  YUI_CHECK_NEW( priv->actionMovePageLeft );
532 
533  connect( priv->actionMovePageLeft, SIGNAL( triggered() ),
534  this, SLOT ( contextMovePageLeft() ) );
535 
536 
537  // Translators: Change this to "left" for Arabic and Hebrew
538  priv->actionMovePageRight = new QAction( YUI::yApp()->reverseLayout() ?
539  YQIconPool::arrowLeft() : YQIconPool::arrowRight(),
540  _( "Move page &right" ), this );
541  YUI_CHECK_NEW( priv->actionMovePageRight );
542 
543  connect( priv->actionMovePageRight, SIGNAL( triggered() ),
544  this, SLOT ( contextMovePageRight() ) );
545 
546 
547  priv->actionClosePage = new QAction( YQIconPool::tabRemove(), _( "&Close page" ), this );
548  YUI_CHECK_NEW( priv->actionClosePage );
549 
550  connect( priv->actionClosePage, SIGNAL( triggered() ),
551  this, SLOT ( contextClosePage() ) );
552 
553 
554  priv->tabContextMenu->addAction( priv->actionMovePageLeft );
555  priv->tabContextMenu->addAction( priv->actionMovePageRight );
556  priv->tabContextMenu->addAction( priv->actionClosePage );
557  }
558 
559  // Enable / disable actions
560 
561  priv->actionMovePageLeft->setEnabled( tabIndex > 0 );
562  priv->actionMovePageRight->setEnabled( tabIndex < ( tabBar()->count() - 1 ) );
563  priv->actionClosePage->setEnabled( tabBar()->count() > 1 && priv->tabContextMenuPage->closeEnabled );
564 
565  priv->tabContextMenu->popup( tabBar()->mapToGlobal( pos ) );
566 
567  return true; // event consumed - no further processing
568  }
569  }
570 
571  return false; // no tab at that position
572 }
573 
574 
575 void
577 {
578  if ( priv->tabContextMenuPage )
579  {
580  int contextPageIndex = priv->tabContextMenuPage->tabIndex;
581  int otherPageIndex = contextPageIndex-1;
582 
583  if ( otherPageIndex >= 0 )
584  {
585  swapTabs( priv->tabContextMenuPage, findPage( otherPageIndex ) );
586  }
587  }
588 }
589 
590 
591 void
593 {
594  if ( priv->tabContextMenuPage )
595  {
596  int contextPageIndex = priv->tabContextMenuPage->tabIndex;
597  int otherPageIndex = contextPageIndex+1;
598 
599  if ( otherPageIndex < tabBar()->count() )
600  {
601  swapTabs( priv->tabContextMenuPage, findPage( otherPageIndex ) );
602  }
603  }
604 }
605 
606 
607 void
609 {
610  if ( ! page1 or ! page2 )
611  return;
612 
613  int oldCurrentIndex = tabBar()->currentIndex();
614  std::swap( page1->tabIndex, page2->tabIndex );
615  tabBar()->setTabText( page1->tabIndex, page1->label );
616  tabBar()->setTabText( page2->tabIndex, page2->label );
617 
618 
619  // If one of the two pages was the currently displayed one,
620  // make sure the same page is still displayed.
621 
622  if ( oldCurrentIndex == page1->tabIndex )
623  {
624  YQSignalBlocker sigBlocker( tabBar() );
625  tabBar()->setCurrentIndex( page2->tabIndex );
626  }
627  else if ( oldCurrentIndex == page2->tabIndex )
628  {
629  YQSignalBlocker sigBlocker( tabBar() );
630  tabBar()->setCurrentIndex( page1->tabIndex );
631  }
632 }
633 
634 
635 void
637 {
638  if ( priv->tabContextMenuPage )
639  {
640  int pageIndex = priv->tabContextMenuPage->tabIndex;
641  priv->tabContextMenuPage->tabIndex = -1;
642  tabBar()->removeTab( pageIndex );
643 
644 
645  //
646  // Adjust tab index of the active pages to the right of that page
647  //
648 
649  for ( YQPkgFilterPageVector::iterator it = priv->pages.begin();
650  it != priv->pages.end();
651  ++it )
652  {
653  YQPkgFilterPage * page = *it;
654 
655  if ( page->tabIndex >= pageIndex )
656  page->tabIndex--;
657  }
658 
659  showPage( tabBar()->currentIndex() ); // display the new current page
660  }
661 }
662 
663 
664 void
666 {
667  closeAllPages();
668  QSettings settings( QSettings::UserScope, SETTINGS_DIR, priv->settingsName );
669 
670  int size = settings.beginReadArray( "Tab_Pages" );
671 
672  for ( int i=0; i < size; i++ )
673  {
674  settings.setArrayIndex(i);
675  QString id = settings.value( "Page_ID" ).toString();
676  YQPkgFilterPage * page = findPage( id );
677 
678  if ( page )
679  {
680  yuiDebug() << "Restoring page \"" << toUTF8( id ) << "\"" << std::endl;
681  showPage( page );
682  }
683  else
684  yuiWarning() << "No page with ID \"" << toUTF8( id ) << "\"" << std::endl;
685  }
686 
687  settings.endArray();
688 
689  QString id = settings.value( "Current_Page" ).toString();
690 
691  if ( ! id.isEmpty() )
692  showPage( id );
693 }
694 
695 
696 void
698 {
699  QSettings settings( QSettings::UserScope, SETTINGS_DIR, priv->settingsName );
700 
701  settings.beginWriteArray( "Tab_Pages" );
702 
703  for ( int i=0; i < tabBar()->count(); i++ )
704  {
705  YQPkgFilterPage * page = findPage( i );
706 
707  if ( page )
708  {
709  settings.setArrayIndex(i);
710 
711  if ( page->id.isEmpty() )
712  yuiWarning() << "No ID for tab page \"" << page->label << "\"" << std::endl;
713  else
714  {
715  yuiDebug() << "Saving page #" << i << ": \"" << toUTF8( page->id ) << "\"" << std::endl;
716  settings.setValue( "Page_ID", page->id );
717  }
718  }
719  }
720 
721  settings.endArray();
722 
723  YQPkgFilterPage * currentPage = findPage( tabBar()->currentIndex() );
724 
725  if ( currentPage )
726  settings.setValue( "Current_Page", currentPage->id );
727 }
728 
729 
730 
void contextMovePageRight()
Move the current tab page (from the context menu) one position to the right.
bool postTabContextMenu(const QPoint &pos)
Open the tab context menu for the tab at the specified position.
void showPage(QWidget *page)
Show a page.
void saveSettings()
Save the current settings, including which tabs are currently open and in which order.
void addPage(const QString &pageLabel, QWidget *pageContent, const QString &internalName)
Add a page with a user-visible "pageLabel", a widget with the page content and an internal name (or I...
virtual ~YQPkgFilterTab()
Destructor.
YQPkgFilterTab(QWidget *parent, const QString &settingsName)
Constructor.
Widget for "tabbed browsing" in packages:
void swapTabs(YQPkgFilterPage *page1, YQPkgFilterPage *page2)
Swap two tabs and adjust their tab indices accordingly.
YQPkgDiskUsageList * diskUsageList() const
Return the disk usage list widget or 0 if there is none.
QWidget * rightPane() const
Return the right pane.
void closeAllPages()
Close all currently open pages.
void currentChanged(QWidget *newPageContent)
Emitted when the current page changes.
void loadSettings()
Load settings, including which tabs are to be opened and in which order.
virtual bool eventFilter(QObject *watchedObj, QEvent *event)
Event filter to catch mouse right clicks on open tabs for the tab context menu.
int tabCount() const
Return the number of open tabs.
Helper class for filter pages.
YQPkgFilterPage * findPage(QWidget *pageContent)
Find a filter page by its content widget (the widget that was passed to addPage() )...
void contextMovePageLeft()
Move the current tab page (from the context menu) one position to the left.
void contextClosePage()
Close the current tab page (from the context menu).
void closeCurrentPage()
Close the current page unless this is the last visible page.
List of disk usage of all attached partitions.