1 /***************************************************************************
2 * Copyright (C) 2007 by Pino Toscano <pino@kde.org> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
12 #include <qabstractitemdelegate.h>
14 #include <qapplication.h>
17 #include <qfontmetrics.h>
21 #include <qlistwidget.h>
23 #include <qscrollbar.h>
24 #include <qsplitter.h>
25 #include <qstackedwidget.h>
27 #include <kiconloader.h>
33 static const int SidebarItemType
= QListWidgetItem::UserType
+ 1;
35 /* List item representing a sidebar entry. */
36 class SidebarItem
: public QListWidgetItem
39 SidebarItem( QWidget
* w
, const QIcon
&icon
, const QString
&text
)
40 : QListWidgetItem( 0, SidebarItemType
),
43 setFlags( Qt::ItemIsSelectable
| Qt::ItemIsEnabled
);
49 QWidget
* widget() const
59 /* A simple delegate to paint the icon of each item */
60 #define ITEM_MARGIN_LEFT 5
61 #define ITEM_MARGIN_TOP 5
62 #define ITEM_MARGIN_RIGHT 5
63 #define ITEM_MARGIN_BOTTOM 5
64 #define ITEM_PADDING 5
66 class SidebarDelegate
: public QAbstractItemDelegate
69 SidebarDelegate( QObject
*parent
= 0 );
72 void setShowText( bool show
);
73 bool isTextShown() const;
75 // from QAbstractItemDelegate
76 void paint( QPainter
*painter
, const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const;
77 QSize
sizeHint( const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const;
83 SidebarDelegate::SidebarDelegate( QObject
*parent
)
84 : QAbstractItemDelegate( parent
), m_showText( true )
88 SidebarDelegate::~SidebarDelegate()
92 void SidebarDelegate::setShowText( bool show
)
97 bool SidebarDelegate::isTextShown() const
102 void SidebarDelegate::paint( QPainter
*painter
, const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
106 bool disabled
= false;
108 if ( !( option
.state
& QStyle::State_Enabled
) )
110 backBrush
= option
.palette
.brush( QPalette::Disabled
, QPalette::Base
);
111 foreColor
= option
.palette
.color( QPalette::Disabled
, QPalette::Text
);
114 else if ( option
.state
& ( QStyle::State_HasFocus
| QStyle::State_Selected
) )
116 backBrush
= option
.palette
.brush( QPalette::Highlight
);
117 foreColor
= option
.palette
.color( QPalette::HighlightedText
);
119 else if ( option
.state
& QStyle::State_MouseOver
)
121 backBrush
= option
.palette
.color( QPalette::Highlight
).light( 115 );
122 foreColor
= option
.palette
.color( QPalette::HighlightedText
);
125 else /*if ( option.state & QStyle::State_Enabled )*/
127 backBrush
= option
.palette
.brush( QPalette::Base
);
128 foreColor
= option
.palette
.color( QPalette::Text
);
130 QStyle
*style
= QApplication::style();
131 QStyleOptionViewItemV4
opt( option
);
132 // KStyle provides an "hover highlight" effect for free;
133 // but we want that for non-KStyle-based styles too
134 if ( !style
->inherits( "KStyle" ) && hover
)
136 Qt::BrushStyle bs
= opt
.backgroundBrush
.style();
137 if ( bs
> Qt::NoBrush
&& bs
< Qt::TexturePattern
)
138 opt
.backgroundBrush
= opt
.backgroundBrush
.color().light( 115 );
140 opt
.backgroundBrush
= backBrush
;
143 style
->drawPrimitive( QStyle::PE_PanelItemViewItem
, &opt
, painter
, 0 );
145 QIcon icon
= index
.data( Qt::DecorationRole
).value
< QIcon
>();
146 if ( !icon
.isNull() )
149 ( option
.rect
.width() - option
.decorationSize
.width() ) / 2,
152 iconpos
+= option
.rect
.topLeft();
153 QIcon::Mode iconmode
= disabled
? QIcon::Disabled
: QIcon::Normal
;
154 painter
->drawPixmap( iconpos
, icon
.pixmap( option
.decorationSize
, iconmode
) );
159 QString text
= index
.data( Qt::DisplayRole
).toString();
160 QRect fontBoundaries
= QFontMetrics( option
.font
).boundingRect( text
);
162 ITEM_MARGIN_LEFT
+ ( option
.rect
.width() - ITEM_MARGIN_LEFT
- ITEM_MARGIN_RIGHT
- fontBoundaries
.width() ) / 2,
163 ITEM_MARGIN_TOP
+ option
.decorationSize
.height() + ITEM_PADDING
165 fontBoundaries
.translate( -fontBoundaries
.topLeft() );
166 fontBoundaries
.translate( textPos
);
167 fontBoundaries
.translate( option
.rect
.topLeft() );
168 painter
->setPen( foreColor
);
169 painter
->drawText( fontBoundaries
, Qt::AlignCenter
, text
);
173 QSize
SidebarDelegate::sizeHint( const QStyleOptionViewItem
&option
, const QModelIndex
&index
) const
175 QSize
baseSize( option
.decorationSize
.width(), option
.decorationSize
.height() );
178 QRect fontBoundaries
= QFontMetrics( option
.font
).boundingRect( index
.data( Qt::DisplayRole
).toString() );
179 baseSize
.setWidth( qMax( fontBoundaries
.width(), baseSize
.width() ) );
180 baseSize
.setHeight( baseSize
.height() + fontBoundaries
.height() + ITEM_PADDING
);
182 return baseSize
+ QSize( ITEM_MARGIN_LEFT
+ ITEM_MARGIN_RIGHT
, ITEM_MARGIN_TOP
+ ITEM_MARGIN_BOTTOM
);
186 /* A custom list widget that ignores the events for disabled items */
187 class SidebarListWidget
: public QListWidget
190 SidebarListWidget( QWidget
*parent
= 0 );
191 ~SidebarListWidget();
195 void mouseDoubleClickEvent( QMouseEvent
*event
);
196 void mouseMoveEvent( QMouseEvent
*event
);
197 void mousePressEvent( QMouseEvent
*event
);
198 void mouseReleaseEvent( QMouseEvent
*event
);
200 QModelIndex
moveCursor( QAbstractItemView::CursorAction cursorAction
, Qt::KeyboardModifiers modifiers
);
203 SidebarListWidget::SidebarListWidget( QWidget
*parent
)
204 : QListWidget( parent
)
208 SidebarListWidget::~SidebarListWidget()
212 void SidebarListWidget::mouseDoubleClickEvent( QMouseEvent
*event
)
214 QModelIndex index
= indexAt( event
->pos() );
215 if ( index
.isValid() && !( index
.flags() & Qt::ItemIsSelectable
) )
218 QListWidget::mouseDoubleClickEvent( event
);
221 void SidebarListWidget::mouseMoveEvent( QMouseEvent
*event
)
223 QModelIndex index
= indexAt( event
->pos() );
224 if ( index
.isValid() && !( index
.flags() & Qt::ItemIsSelectable
) )
227 QListWidget::mouseMoveEvent( event
);
230 void SidebarListWidget::mousePressEvent( QMouseEvent
*event
)
232 QModelIndex index
= indexAt( event
->pos() );
233 if ( index
.isValid() && !( index
.flags() & Qt::ItemIsSelectable
) )
236 QListWidget::mousePressEvent( event
);
239 void SidebarListWidget::mouseReleaseEvent( QMouseEvent
*event
)
241 QModelIndex index
= indexAt( event
->pos() );
242 if ( index
.isValid() && !( index
.flags() & Qt::ItemIsSelectable
) )
245 QListWidget::mouseReleaseEvent( event
);
248 QModelIndex
SidebarListWidget::moveCursor( QAbstractItemView::CursorAction cursorAction
, Qt::KeyboardModifiers modifiers
)
250 Q_UNUSED( modifiers
)
251 QModelIndex oldindex
= currentIndex();
252 QModelIndex newindex
= oldindex
;
253 switch ( cursorAction
)
258 int row
= oldindex
.row() - 1;
259 while ( row
> -1 && !( model()->index( row
, 0 ).flags() & Qt::ItemIsSelectable
) ) --row
;
261 newindex
= model()->index( row
, 0 );
267 int row
= oldindex
.row() + 1;
268 int max
= model()->rowCount();
269 while ( row
< max
&& !( model()->index( row
, 0 ).flags() & Qt::ItemIsSelectable
) ) ++row
;
271 newindex
= model()->index( row
, 0 );
278 while ( row
< oldindex
.row() && !( model()->index( row
, 0 ).flags() & Qt::ItemIsSelectable
) ) ++row
;
279 if ( row
< oldindex
.row() )
280 newindex
= model()->index( row
, 0 );
286 int row
= model()->rowCount() - 1;
287 while ( row
> oldindex
.row() && !( model()->index( row
, 0 ).flags() & Qt::ItemIsSelectable
) ) --row
;
288 if ( row
> oldindex
.row() )
289 newindex
= model()->index( row
, 0 );
292 // no navigation possible for these
298 // dirty hack to change item when the key cursor changes item
299 if ( oldindex
!= newindex
)
301 emit
itemClicked( itemFromIndex( newindex
) );
307 /* Private storage. */
308 class Sidebar::Private
312 : sideWidget( 0 ), bottomWidget( 0 ), splitterSizesSet( false ),
317 void adjustListSize( bool recalc
, bool expand
= true );
319 SidebarListWidget
*list
;
321 QStackedWidget
*stack
;
322 QWidget
*sideContainer
;
326 QWidget
*bottomWidget
;
327 QList
< SidebarItem
* > pages
;
328 bool splitterSizesSet
;
330 SidebarDelegate
*sideDelegate
;
333 void Sidebar::Private::adjustListSize( bool recalc
, bool expand
)
335 QRect
bottomElemRect(
337 list
->sizeHintForIndex( list
->model()->index( list
->count() - 1, 0 ) )
342 for ( int i
= 0; i
< list
->count(); ++i
)
344 QSize s
= list
->sizeHintForIndex( list
->model()->index( i
, 0 ) );
348 bottomElemRect
.setWidth( w
);
350 bottomElemRect
.translate( 0, bottomElemRect
.height() * ( list
->count() - 1 ) );
351 itemsHeight
= bottomElemRect
.height() * list
->count();
352 list
->setMinimumHeight( itemsHeight
+ list
->frameWidth() * 2 );
353 int curWidth
= list
->minimumWidth();
354 int newWidth
= expand
355 ? qMax( bottomElemRect
.width() + list
->frameWidth() * 2, curWidth
)
356 : qMin( bottomElemRect
.width() + list
->frameWidth() * 2, curWidth
);
357 list
->setFixedWidth( newWidth
);
361 Sidebar::Sidebar( QWidget
*parent
)
362 : QWidget( parent
), d( new Private
)
364 QHBoxLayout
*mainlay
= new QHBoxLayout( this );
365 mainlay
->setMargin( 0 );
366 mainlay
->setSpacing( 0 );
368 d
->list
= new SidebarListWidget( this );
369 mainlay
->addWidget( d
->list
);
370 d
->list
->setMouseTracking( true );
371 d
->list
->viewport()->setAttribute( Qt::WA_Hover
);
372 d
->sideDelegate
= new SidebarDelegate( d
->list
);
373 d
->sideDelegate
->setShowText( Okular::Settings::sidebarShowText() );
374 d
->list
->setItemDelegate( d
->sideDelegate
);
375 d
->list
->setUniformItemSizes( true );
376 d
->list
->setSelectionMode( QAbstractItemView::SingleSelection
);
377 int iconsize
= Okular::Settings::sidebarIconSize();
378 d
->list
->setIconSize( QSize( iconsize
, iconsize
) );
379 d
->list
->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff
);
380 d
->list
->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff
);
381 d
->list
->setContextMenuPolicy( Qt::CustomContextMenu
);
382 QPalette pal
= d
->list
->palette();
383 pal
.setBrush( QPalette::Base
, pal
.brush( QPalette::Window
) );
384 d
->list
->setPalette( pal
);
386 d
->splitter
= new QSplitter( this );
387 mainlay
->addWidget( d
->splitter
);
388 d
->splitter
->setOpaqueResize( true );
389 d
->splitter
->setChildrenCollapsible( false );
391 d
->sideContainer
= new QWidget( d
->splitter
);
392 d
->sideContainer
->setMinimumWidth( 90 );
393 d
->sideContainer
->setMaximumWidth( 300 );
394 d
->vlay
= new QVBoxLayout( d
->sideContainer
);
395 d
->vlay
->setMargin( 0 );
397 d
->sideTitle
= new QLabel( d
->sideContainer
);
398 d
->vlay
->addWidget( d
->sideTitle
);
399 QFont tf
= d
->sideTitle
->font();
401 d
->sideTitle
->setFont( tf
);
402 d
->sideTitle
->setMargin( 3 );
403 d
->sideTitle
->setIndent( 3 );
405 d
->stack
= new QStackedWidget( d
->sideContainer
);
406 d
->vlay
->addWidget( d
->stack
);
407 d
->sideContainer
->hide();
409 connect( d
->list
, SIGNAL( itemClicked( QListWidgetItem
* ) ), this, SLOT( itemClicked( QListWidgetItem
* ) ) );
410 connect( d
->list
, SIGNAL( customContextMenuRequested( const QPoint
& ) ),
411 this, SLOT( listContextMenu( const QPoint
& ) ) );
412 connect( d
->splitter
, SIGNAL( splitterMoved( int, int ) ), this, SLOT( splitterMoved( int, int ) ) );
420 int Sidebar::addItem( QWidget
*widget
, const QIcon
&icon
, const QString
&text
)
425 SidebarItem
*newitem
= new SidebarItem( widget
, icon
, text
);
426 d
->list
->addItem( newitem
);
427 d
->pages
.append( newitem
);
428 widget
->setParent( d
->stack
);
429 d
->stack
->addWidget( widget
);
430 // updating the minimum height of the icon view, so all are visible with no scrolling
431 d
->adjustListSize( false, true );
432 return d
->pages
.count() - 1;
435 void Sidebar::setMainWidget( QWidget
*widget
)
437 delete d
->sideWidget
;
438 d
->sideWidget
= widget
;
441 // setting the splitter as parent for the widget automatically plugs it
442 // into the splitter, neat!
443 d
->sideWidget
->setParent( d
->splitter
);
445 if ( !d
->splitterSizesSet
)
447 QList
<int> splitterSizes
= Okular::Settings::splitterSizes();
448 if ( !splitterSizes
.count() )
450 // the first time use 1/10 for the panel and 9/10 for the pageView
451 splitterSizes
.push_back( 50 );
452 splitterSizes
.push_back( 500 );
454 d
->splitter
->setSizes( splitterSizes
);
455 d
->splitterSizesSet
= true;
460 void Sidebar::setBottomWidget( QWidget
*widget
)
462 delete d
->bottomWidget
;
463 d
->bottomWidget
= widget
;
464 if ( d
->bottomWidget
)
466 d
->bottomWidget
->setParent( this );
467 d
->vlay
->addWidget( d
->bottomWidget
);
471 void Sidebar::setItemEnabled( int index
, bool enabled
)
473 if ( index
< 0 || index
>= d
->pages
.count() )
476 Qt::ItemFlags f
= d
->pages
.at( index
)->flags();
479 f
|= Qt::ItemIsEnabled
;
480 f
|= Qt::ItemIsSelectable
;
484 f
&= ~Qt::ItemIsEnabled
;
485 f
&= ~Qt::ItemIsSelectable
;
487 d
->pages
.at( index
)->setFlags( f
);
489 if ( !enabled
&& index
== currentIndex() )
490 // find an enabled item, and select that one
491 for ( int i
= 0; i
< d
->pages
.count(); ++i
)
492 if ( d
->pages
.at(i
)->flags() & Qt::ItemIsEnabled
)
494 setCurrentIndex( i
);
499 bool Sidebar::isItemEnabled( int index
) const
501 if ( index
< 0 || index
>= d
->pages
.count() )
504 Qt::ItemFlags f
= d
->pages
.at( index
)->flags();
505 return ( f
& Qt::ItemIsEnabled
) == Qt::ItemIsEnabled
;
508 void Sidebar::setCurrentIndex( int index
)
510 if ( index
< 0 || index
>= d
->pages
.count() || !isItemEnabled( index
) )
513 itemClicked( d
->pages
.at( index
) );
514 QModelIndex modelindex
= d
->list
->model()->index( index
, 0 );
515 d
->list
->setCurrentIndex( modelindex
);
516 d
->list
->selectionModel()->select( modelindex
, QItemSelectionModel::ClearAndSelect
);
519 int Sidebar::currentIndex() const
521 return d
->list
->currentRow();
524 void Sidebar::setSidebarVisibility( bool visible
)
526 if ( visible
!= d
->list
->isHidden() )
529 static bool sideWasVisible
= !d
->sideContainer
->isHidden();
531 d
->list
->setHidden( !visible
);
534 d
->sideContainer
->setHidden( !sideWasVisible
);
535 sideWasVisible
= true;
539 sideWasVisible
= !d
->sideContainer
->isHidden();
540 d
->sideContainer
->setHidden( true );
544 bool Sidebar::isSidebarVisible() const
546 return !d
->sideContainer
->isHidden();
549 void Sidebar::itemClicked( QListWidgetItem
*item
)
554 SidebarItem
* sbItem
= dynamic_cast< SidebarItem
* >( item
);
558 if ( sbItem
->widget() == d
->stack
->currentWidget() )
560 if ( d
->sideContainer
->isVisible() )
562 d
->list
->selectionModel()->clear();
563 d
->sideContainer
->hide();
567 d
->sideContainer
->show();
572 if ( d
->sideContainer
->isHidden() )
573 d
->sideContainer
->show();
574 d
->stack
->setCurrentWidget( sbItem
->widget() );
575 d
->sideTitle
->setText( sbItem
->toolTip() );
579 void Sidebar::splitterMoved( int /*pos*/, int index
)
581 // if the side panel has been resized, save splitter sizes
586 void Sidebar::saveSplitterSize() const
588 Okular::Settings::setSplitterSizes( d
->splitter
->sizes() );
589 Okular::Settings::self()->writeConfig();
592 void Sidebar::listContextMenu( const QPoint
&pos
)
595 menu
.addTitle( i18n( "Okular" ) );
596 QAction
*showTextAct
= menu
.addAction( i18n( "Show Text" ) );
597 showTextAct
->setCheckable( true );
598 showTextAct
->setChecked( d
->sideDelegate
->isTextShown() );
599 connect( showTextAct
, SIGNAL( toggled( bool ) ), this, SLOT( showTextToggled( bool ) ) );
601 QActionGroup
*sizeGroup
= new QActionGroup( &menu
);
602 int curSize
= d
->list
->iconSize().width();
603 #define ADD_SIZE_ACTION( text, _itssize ) \
605 const int itssize = static_cast< int >( _itssize ); \
606 QAction *sizeAct = menu.addAction( text ); \
607 sizeAct->setCheckable( true ); \
608 sizeAct->setData( qVariantFromValue( itssize ) ); \
609 sizeAct->setChecked( itssize == curSize ); \
610 sizeGroup->addAction( sizeAct ); \
612 ADD_SIZE_ACTION( i18n( "Small Icons" ), KIconLoader::SizeSmallMedium
)
613 ADD_SIZE_ACTION( i18n( "Normal Icons" ), KIconLoader::SizeMedium
)
614 ADD_SIZE_ACTION( i18n( "Large Icons" ), KIconLoader::SizeLarge
)
615 #undef ADD_SIZE_ACTION
616 connect( sizeGroup
, SIGNAL( triggered( QAction
* ) ), this, SLOT( iconSizeChanged( QAction
* ) ) );
617 menu
.exec( mapToGlobal( pos
) );
620 void Sidebar::showTextToggled( bool on
)
622 d
->sideDelegate
->setShowText( on
);
623 d
->adjustListSize( true, on
);
626 Okular::Settings::setSidebarShowText( on
);
627 Okular::Settings::self()->writeConfig();
630 void Sidebar::iconSizeChanged( QAction
*action
)
632 int size
= action
->data().toInt();
633 int oldSize
= d
->list
->iconSize().width();
634 d
->list
->setIconSize( QSize( size
, size
) );
635 d
->adjustListSize( true, size
> oldSize
);
638 Okular::Settings::setSidebarIconSize( size
);
639 Okular::Settings::self()->writeConfig();
642 #include "sidebar.moc"