sidebar.cpp 24.9 KB
Newer Older
Pino Toscano's avatar
Pino Toscano committed
1 2
/***************************************************************************
 *   Copyright (C) 2007 by Pino Toscano <pino@kde.org>                     *
3
 *   Copyright (C) 2009 by Eike Hein <hein@kde.org>                        *
Pino Toscano's avatar
Pino Toscano committed
4 5 6 7 8 9 10 11 12 13
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 ***************************************************************************/

#include "sidebar.h"

#include <qabstractitemdelegate.h>
14
#include <qaction.h>
15
#include <qapplication.h>
Pino Toscano's avatar
Pino Toscano committed
16 17
#include <qevent.h>
#include <qfont.h>
18
#include <qfontmetrics.h>
Pino Toscano's avatar
Pino Toscano committed
19 20 21 22 23 24 25 26
#include <qlabel.h>
#include <qlayout.h>
#include <qlist.h>
#include <qlistwidget.h>
#include <qpainter.h>
#include <qscrollbar.h>
#include <qsplitter.h>
#include <qstackedwidget.h>
Vishesh Handa's avatar
Vishesh Handa committed
27
#include <qmimedata.h>
Pino Toscano's avatar
Pino Toscano committed
28

29
#include <kiconloader.h>
Laurent Montel's avatar
Laurent Montel committed
30
#include <KLocalizedString>
Laurent Montel's avatar
Laurent Montel committed
31
#include <qmenu.h>
32
#include <kcolorscheme.h>
Vishesh Handa's avatar
Vishesh Handa committed
33
#include <kurlmimedata.h>
34

Pino Toscano's avatar
Pino Toscano committed
35 36 37 38 39 40 41 42 43
#include "settings.h"

static const int SidebarItemType = QListWidgetItem::UserType + 1;

/* List item representing a sidebar entry. */
class SidebarItem : public QListWidgetItem
{
    public:
        SidebarItem( QWidget* w, const QIcon &icon, const QString &text )
44
            : QListWidgetItem( nullptr, SidebarItemType ),
Pino Toscano's avatar
Pino Toscano committed
45 46 47 48
              m_widget( w )
        {
            setFlags( Qt::ItemIsSelectable | Qt::ItemIsEnabled );
            setIcon( icon );
49
            setText( text );
Pino Toscano's avatar
Pino Toscano committed
50 51 52
            setToolTip( text );
        }

53 54 55
        SidebarItem(const SidebarItem &) = delete;
        SidebarItem &operator=(const SidebarItem &) = delete;

Pino Toscano's avatar
Pino Toscano committed
56 57 58 59 60 61 62 63 64 65 66
        QWidget* widget() const
        {
            return m_widget;
        }

    private:
        QWidget *m_widget;
};


/* A simple delegate to paint the icon of each item */
67 68 69 70 71 72
#define ITEM_MARGIN_LEFT 5
#define ITEM_MARGIN_TOP 5
#define ITEM_MARGIN_RIGHT 5
#define ITEM_MARGIN_BOTTOM 5
#define ITEM_PADDING 5

Pino Toscano's avatar
Pino Toscano committed
73 74
class SidebarDelegate : public QAbstractItemDelegate
{
75 76
    Q_OBJECT

Pino Toscano's avatar
Pino Toscano committed
77
    public:
Laurent Montel's avatar
Laurent Montel committed
78
        SidebarDelegate( QObject *parent = Q_NULLPTR );
79
        ~SidebarDelegate() override;
Pino Toscano's avatar
Pino Toscano committed
80

81 82 83
        void setShowText( bool show );
        bool isTextShown() const;

84

Pino Toscano's avatar
Pino Toscano committed
85
        // from QAbstractItemDelegate
86 87
        void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const override;
        QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const override;
88

89 90 91
    private slots:
        void updateBrushCache();

92 93
    private:
        bool m_showText;
94 95 96 97
        QScopedPointer<KStatefulBrush> m_windowBackground;
        QScopedPointer<KStatefulBrush> m_windowForeground;
        QScopedPointer<KStatefulBrush> m_selectionBackground;
        QScopedPointer<KStatefulBrush> m_selectionForeground;
Pino Toscano's avatar
Pino Toscano committed
98 99 100
};

SidebarDelegate::SidebarDelegate( QObject *parent )
101
    : QAbstractItemDelegate( parent ), m_showText( true ),
102 103
    m_windowBackground( nullptr ), m_windowForeground( nullptr ),
    m_selectionBackground( nullptr ), m_selectionForeground( nullptr )
Pino Toscano's avatar
Pino Toscano committed
104
{
105
    updateBrushCache();
106
    connect(qApp, &QGuiApplication::paletteChanged, this, &SidebarDelegate::updateBrushCache);
Pino Toscano's avatar
Pino Toscano committed
107 108 109 110 111 112
}

SidebarDelegate::~SidebarDelegate()
{
}

113 114 115 116 117 118 119 120 121 122
void SidebarDelegate::setShowText( bool show )
{
    m_showText = show;
}

bool SidebarDelegate::isTextShown() const
{
    return m_showText;
}

123 124 125 126 127 128 129 130
void SidebarDelegate::updateBrushCache()
{
    m_windowBackground.reset(new KStatefulBrush(KColorScheme::Window, KColorScheme::NormalBackground));
    m_windowForeground.reset(new KStatefulBrush(KColorScheme::Window, KColorScheme::NormalText));
    m_selectionBackground.reset(new KStatefulBrush(KColorScheme::Selection, KColorScheme::NormalBackground));
    m_selectionForeground.reset(new KStatefulBrush(KColorScheme::Selection, KColorScheme::NormalText));
}

Pino Toscano's avatar
Pino Toscano committed
131 132 133
void SidebarDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
    QBrush backBrush;
134
    QColor foreColor;
Pino Toscano's avatar
Pino Toscano committed
135
    bool disabled = false;
136
    bool hover = false;
137

Pino Toscano's avatar
Pino Toscano committed
138 139
    if ( !( option.state & QStyle::State_Enabled ) )
    {
140 141
        backBrush = m_windowBackground->brush(QPalette::Disabled);
        foreColor = m_windowForeground->brush(QPalette::Disabled).color();
Pino Toscano's avatar
Pino Toscano committed
142 143
        disabled = true;
    }
144
    else if ( option.state & QStyle::State_HasFocus )
Pino Toscano's avatar
Pino Toscano committed
145
    {
146 147
        backBrush = m_selectionBackground->brush(option.palette);
        foreColor = m_selectionForeground->brush(option.palette).color();
Pino Toscano's avatar
Pino Toscano committed
148
    }
149 150 151 152 153
    else if ( option.state & QStyle::State_Selected )
    {
        backBrush = m_selectionBackground->brush(option.palette);
        foreColor = m_windowForeground->brush(option.palette).color();
    }
Pino Toscano's avatar
Pino Toscano committed
154 155
    else if ( option.state & QStyle::State_MouseOver )
    {
Laurent Montel's avatar
Laurent Montel committed
156
        backBrush = m_selectionBackground->brush(option.palette).color().lighter( 115 );
157
        foreColor = m_windowForeground->brush(option.palette).color();
158
        hover = true;
Pino Toscano's avatar
Pino Toscano committed
159 160 161
    }
    else /*if ( option.state & QStyle::State_Enabled )*/
    {
162 163
        backBrush = m_windowBackground->brush(option.palette);
        foreColor = m_windowForeground->brush(option.palette).color();
Pino Toscano's avatar
Pino Toscano committed
164
    }
165
    QStyle *style = QApplication::style();
166
    QStyleOptionViewItem opt( option );
167 168 169 170 171 172
    // KStyle provides an "hover highlight" effect for free;
    // but we want that for non-KStyle-based styles too
    if ( !style->inherits( "KStyle" ) && hover )
    {
        Qt::BrushStyle bs = opt.backgroundBrush.style();
        if ( bs > Qt::NoBrush && bs < Qt::TexturePattern )
Laurent Montel's avatar
Laurent Montel committed
173
            opt.backgroundBrush = opt.backgroundBrush.color().lighter( 115 );
174 175 176 177
        else
            opt.backgroundBrush = backBrush;
    }
    painter->save();
178
    style->drawPrimitive( QStyle::PE_PanelItemViewItem, &opt, painter, nullptr );
179
    painter->restore();
Pino Toscano's avatar
Pino Toscano committed
180 181 182 183 184
    QIcon icon = index.data( Qt::DecorationRole ).value< QIcon >();
    if ( !icon.isNull() )
    {
        QPoint iconpos(
            ( option.rect.width() - option.decorationSize.width() ) / 2,
185
            ITEM_MARGIN_TOP
Pino Toscano's avatar
Pino Toscano committed
186 187 188 189 190
        );
        iconpos += option.rect.topLeft();
        QIcon::Mode iconmode = disabled ? QIcon::Disabled : QIcon::Normal;
        painter->drawPixmap( iconpos, icon.pixmap( option.decorationSize, iconmode ) );
    }
191 192 193 194

    if ( m_showText )
    {
        QString text = index.data( Qt::DisplayRole ).toString();
195
        QRect fontBoundaries = QFontMetrics( option.font ).boundingRect( QRect(), Qt::AlignCenter, text );
196 197 198 199 200 201 202
        QPoint textPos(
            ITEM_MARGIN_LEFT + ( option.rect.width() - ITEM_MARGIN_LEFT - ITEM_MARGIN_RIGHT - fontBoundaries.width() ) / 2,
            ITEM_MARGIN_TOP + option.decorationSize.height() + ITEM_PADDING
        );
        fontBoundaries.translate( -fontBoundaries.topLeft() );
        fontBoundaries.translate( textPos );
        fontBoundaries.translate( option.rect.topLeft() );
203
        painter->setPen( foreColor );
204 205
        painter->drawText( fontBoundaries, Qt::AlignCenter, text );
    }
Pino Toscano's avatar
Pino Toscano committed
206 207 208 209
}

QSize SidebarDelegate::sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const
{
210 211 212
    QSize baseSize( option.decorationSize.width(), option.decorationSize.height() );
    if ( m_showText )
    {
213
        QRect fontBoundaries = QFontMetrics( option.font ).boundingRect( QRect(), Qt::AlignCenter, index.data( Qt::DisplayRole ).toString() );
214 215 216 217
        baseSize.setWidth( qMax( fontBoundaries.width(), baseSize.width() ) );
        baseSize.setHeight( baseSize.height() + fontBoundaries.height() + ITEM_PADDING );
    }
    return baseSize + QSize( ITEM_MARGIN_LEFT + ITEM_MARGIN_RIGHT, ITEM_MARGIN_TOP + ITEM_MARGIN_BOTTOM );
Pino Toscano's avatar
Pino Toscano committed
218 219 220 221 222 223
}


/* A custom list widget that ignores the events for disabled items */
class SidebarListWidget : public QListWidget
{
224
    Q_OBJECT
Pino Toscano's avatar
Pino Toscano committed
225
    public:
Laurent Montel's avatar
Laurent Montel committed
226
        SidebarListWidget( QWidget *parent = Q_NULLPTR );
227
        ~SidebarListWidget() override;
Pino Toscano's avatar
Pino Toscano committed
228

229 230 231 232 233 234 235 236
        int countVisible() const {
            int ret = 0;
            for ( int i = 0, c = count(); i < c; ++i ) {
                ret += !item(i)->isHidden();
            }
            return ret;
        }

Pino Toscano's avatar
Pino Toscano committed
237 238
    protected:
        // from QListWidget
239 240 241 242
        void mouseDoubleClickEvent( QMouseEvent *event ) override;
        void mouseMoveEvent( QMouseEvent *event ) override;
        void mousePressEvent( QMouseEvent *event ) override;
        void mouseReleaseEvent( QMouseEvent *event ) override;
Pino Toscano's avatar
Pino Toscano committed
243

244
        QModelIndex moveCursor( QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers ) override;
245 246 247 248 249 250 251 252 253 254 255 256 257

    private:
        // These two are used to keep track of the row an initial mousePress-
        // Event() occurs on and the row the cursor moves over while the left
        // mouse button is pressed, respectively, as well as for event compre-
        // ssion, to avoid calling SideBar::itemClicked() multiple times for
        // the same item in a row on mouseMoveEvent()'s. This code is written
        // under the assumption that the number and positions of items in the
        // list won't change while the user interacts with it using the mouse.
        // Future work here must see to that this assumption continues to hold
        // up, or achieve calling SideBar::itemClicked() differently.
        int mousePressedRow;
        int rowUnderMouse;
Pino Toscano's avatar
Pino Toscano committed
258 259 260 261 262
};

SidebarListWidget::SidebarListWidget( QWidget *parent )
    : QListWidget( parent )
{
263 264
    mousePressedRow = -1;
    rowUnderMouse = -1;
Pino Toscano's avatar
Pino Toscano committed
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
}

SidebarListWidget::~SidebarListWidget()
{
}

void SidebarListWidget::mouseDoubleClickEvent( QMouseEvent *event )
{
    QModelIndex index = indexAt( event->pos() );
    if ( index.isValid() && !( index.flags() & Qt::ItemIsSelectable ) )
        return;

    QListWidget::mouseDoubleClickEvent( event );
}

void SidebarListWidget::mouseMoveEvent( QMouseEvent *event )
{
    QModelIndex index = indexAt( event->pos() );
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301

    if ( index.isValid() )
    {
        if ( index.flags() & Qt::ItemIsSelectable )
        {
            if ( event->buttons() & Qt::LeftButton
                 && index.row() != mousePressedRow
                 && index.row() != rowUnderMouse )
            {
                mousePressedRow = -1;
                rowUnderMouse = index.row();

                QMetaObject::invokeMethod(parent(), "itemClicked", Qt::DirectConnection,
                    Q_ARG(QListWidgetItem*, item(index.row())));
            }
        }
        else
            return;
    }
Pino Toscano's avatar
Pino Toscano committed
302 303 304 305 306 307 308

    QListWidget::mouseMoveEvent( event );
}

void SidebarListWidget::mousePressEvent( QMouseEvent *event )
{
    QModelIndex index = indexAt( event->pos() );
309 310 311 312 313 314 315 316 317 318 319

    if ( index.isValid() )
    {
        if ( index.flags() & Qt::ItemIsSelectable )
        {
            if ( event->buttons() & Qt::LeftButton )
                mousePressedRow = index.row();
        }
        else
            return;
    }
Pino Toscano's avatar
Pino Toscano committed
320 321 322 323 324 325 326

    QListWidget::mousePressEvent( event );
}

void SidebarListWidget::mouseReleaseEvent( QMouseEvent *event )
{
    QModelIndex index = indexAt( event->pos() );
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349

    if ( index.isValid() )
    {
        if ( index.flags() & Qt::ItemIsSelectable )
        {
            if ( event->button() == Qt::LeftButton
                 && index.row() != rowUnderMouse )
            {
                QMetaObject::invokeMethod(parent(), "itemClicked", Qt::DirectConnection,
                    Q_ARG(QListWidgetItem*, item(index.row())));
            }
        }
        else
        {
            mousePressedRow = -1;
            rowUnderMouse = -1;

            return;
        }
    }

    mousePressedRow = -1;
    rowUnderMouse = -1;
Pino Toscano's avatar
Pino Toscano committed
350 351 352 353 354 355

    QListWidget::mouseReleaseEvent( event );
}

QModelIndex SidebarListWidget::moveCursor( QAbstractItemView::CursorAction cursorAction, Qt::KeyboardModifiers modifiers )
{
356
    Q_UNUSED( modifiers )
Pino Toscano's avatar
Pino Toscano committed
357
    QModelIndex oldindex = currentIndex();
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403
    QModelIndex newindex = oldindex;
    switch ( cursorAction )
    {
        case MoveUp:
        case MovePrevious:
        {
            int row = oldindex.row() - 1;
            while ( row > -1 && !( model()->index( row, 0 ).flags() & Qt::ItemIsSelectable ) ) --row;
            if ( row > -1 )
                newindex = model()->index( row, 0 );
            break;
        }
        case MoveDown:
        case MoveNext:
        {
            int row = oldindex.row() + 1;
            int max = model()->rowCount();
            while ( row < max && !( model()->index( row, 0 ).flags() & Qt::ItemIsSelectable ) ) ++row;
            if ( row < max )
                newindex = model()->index( row, 0 );
            break;
        }
        case MoveHome:
        case MovePageUp:
        {
            int row = 0;
            while ( row < oldindex.row() && !( model()->index( row, 0 ).flags() & Qt::ItemIsSelectable ) ) ++row;
            if ( row < oldindex.row() )
                newindex = model()->index( row, 0 );
            break;
        }
        case MoveEnd:
        case MovePageDown:
        {
            int row = model()->rowCount() - 1;
            while ( row > oldindex.row() && !( model()->index( row, 0 ).flags() & Qt::ItemIsSelectable ) ) --row;
            if ( row > oldindex.row() )
                newindex = model()->index( row, 0 );
            break;
        }
        // no navigation possible for these
        case MoveLeft:
        case MoveRight:
            break;
    }

Pino Toscano's avatar
Pino Toscano committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417
    // dirty hack to change item when the key cursor changes item
    if ( oldindex != newindex )
    {
        emit itemClicked( itemFromIndex( newindex ) );
    }
    return newindex;
}


/* Private storage. */
class Sidebar::Private
{
public:
    Private()
418
        : sideWidget( nullptr ), bottomWidget( nullptr ), splitterSizesSet( false ),
419
          itemsHeight( 0 )
Pino Toscano's avatar
Pino Toscano committed
420 421 422
    {
    }

423 424 425 426 427 428 429 430
    int indexOf(QWidget *w) const
    {
        for (int i = 0; i < pages.count(); ++i) {
            if (pages[i]->widget() == w) return i;
        }
        return -1;
    }

431
    void adjustListSize( bool recalc, bool expand = true );
432 433

    SidebarListWidget *list;
Pino Toscano's avatar
Pino Toscano committed
434 435 436 437 438 439 440 441 442
    QSplitter *splitter;
    QStackedWidget *stack;
    QWidget *sideContainer;
    QLabel *sideTitle;
    QVBoxLayout *vlay;
    QWidget *sideWidget;
    QWidget *bottomWidget;
    QList< SidebarItem* > pages;
    bool splitterSizesSet;
443 444
    int itemsHeight;
    SidebarDelegate *sideDelegate;
Pino Toscano's avatar
Pino Toscano committed
445 446
};

447
void Sidebar::Private::adjustListSize( bool recalc, bool expand )
448
{
449
    QSize bottomElemSize( list->sizeHintForIndex( list->model()->index( list->count() - 1, 0 ) ) );
450 451 452 453 454 455 456 457 458
    if ( recalc )
    {
        int w = 0;
        for ( int i = 0; i < list->count(); ++i )
        {
            QSize s = list->sizeHintForIndex( list->model()->index( i, 0 ) );
            if ( s.width() > w )
                w = s.width();
        }
459
        bottomElemSize.setWidth( w );
460
    }
461
    itemsHeight = bottomElemSize.height() * list->countVisible();
462
    list->setMinimumHeight( itemsHeight + list->frameWidth() * 2 );
463

464 465
    int curWidth = list->minimumWidth();
    int newWidth = expand
466 467
                   ? qMax( bottomElemSize.width() + list->frameWidth() * 2, curWidth )
                   : qMin( bottomElemSize.width() + list->frameWidth() * 2, curWidth );
468 469 470
    list->setFixedWidth( newWidth );
}

Pino Toscano's avatar
Pino Toscano committed
471 472 473 474 475

Sidebar::Sidebar( QWidget *parent )
    : QWidget( parent ), d( new Private )
{
    QHBoxLayout *mainlay = new QHBoxLayout( this );
476
    mainlay->setContentsMargins( 0, 0, 0, 0 );
477
    mainlay->setSpacing( 0 );
Pino Toscano's avatar
Pino Toscano committed
478

479
    setAutoFillBackground( true );
480
    setAcceptDrops( true );
481

Pino Toscano's avatar
Pino Toscano committed
482 483 484 485
    d->list = new SidebarListWidget( this );
    mainlay->addWidget( d->list );
    d->list->setMouseTracking( true );
    d->list->viewport()->setAttribute( Qt::WA_Hover );
486
    d->sideDelegate = new SidebarDelegate( d->list );
487
    d->sideDelegate->setShowText( Okular::Settings::sidebarShowText() );
488
    d->list->setItemDelegate( d->sideDelegate );
Pino Toscano's avatar
Pino Toscano committed
489 490
    d->list->setUniformItemSizes( true );
    d->list->setSelectionMode( QAbstractItemView::SingleSelection );
491 492
    int iconsize = Okular::Settings::sidebarIconSize();
    d->list->setIconSize( QSize( iconsize, iconsize ) );
Pino Toscano's avatar
Pino Toscano committed
493
    d->list->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
494 495
    d->list->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
    d->list->setContextMenuPolicy( Qt::CustomContextMenu );
496
    d->list->viewport()->setAutoFillBackground( false );
Pino Toscano's avatar
Pino Toscano committed
497 498 499 500 501 502 503 504

    d->splitter = new QSplitter( this );
    mainlay->addWidget( d->splitter );
    d->splitter->setOpaqueResize( true );
    d->splitter->setChildrenCollapsible( false );

    d->sideContainer = new QWidget( d->splitter );
    d->sideContainer->setMinimumWidth( 90 );
505
    d->sideContainer->setMaximumWidth( 600 );
Pino Toscano's avatar
Pino Toscano committed
506
    d->vlay = new QVBoxLayout( d->sideContainer );
507
    d->vlay->setContentsMargins( 0, 0, 0, 0 );
Pino Toscano's avatar
Pino Toscano committed
508 509 510 511 512 513 514 515 516 517 518 519

    d->sideTitle = new QLabel( d->sideContainer );
    d->vlay->addWidget( d->sideTitle );
    QFont tf = d->sideTitle->font();
    tf.setBold( true );
    d->sideTitle->setFont( tf );
    d->sideTitle->setMargin( 3 );
    d->sideTitle->setIndent( 3 );

    d->stack = new QStackedWidget( d->sideContainer );
    d->vlay->addWidget( d->stack );

Laurent Montel's avatar
Laurent Montel committed
520 521
    connect(d->list, &SidebarListWidget::customContextMenuRequested, this, &Sidebar::listContextMenu);
    connect(d->splitter, &QSplitter::splitterMoved, this, &Sidebar::splitterMoved);
522

523
    setCollapsed( true );
524
    setFocusProxy( d->list );
Pino Toscano's avatar
Pino Toscano committed
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542
}

Sidebar::~Sidebar()
{
    delete d;
}

int Sidebar::addItem( QWidget *widget, const QIcon &icon, const QString &text )
{
    if ( !widget )
        return -1;

    SidebarItem *newitem = new SidebarItem( widget, icon, text );
    d->list->addItem( newitem );
    d->pages.append( newitem );
    widget->setParent( d->stack );
    d->stack->addWidget( widget );
    // updating the minimum height of the icon view, so all are visible with no scrolling
543
    d->adjustListSize( false, true );
Pino Toscano's avatar
Pino Toscano committed
544 545 546 547 548 549 550
    return d->pages.count() - 1;
}

void Sidebar::setMainWidget( QWidget *widget )
{
    delete d->sideWidget;
    d->sideWidget = widget;
551
    if ( d->sideWidget )
Pino Toscano's avatar
Pino Toscano committed
552 553 554
    {
        // setting the splitter as parent for the widget automatically plugs it
        // into the splitter, neat!
555
        d->sideWidget->setParent( d->splitter );
Pino Toscano's avatar
Pino Toscano committed
556 557 558 559

        if ( !d->splitterSizesSet )
        {
            QList<int> splitterSizes = Okular::Settings::splitterSizes();
560
            if ( splitterSizes.isEmpty() )
Pino Toscano's avatar
Pino Toscano committed
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
            {
                // the first time use 1/10 for the panel and 9/10 for the pageView
                splitterSizes.push_back( 50 );
                splitterSizes.push_back( 500 );
            }
            d->splitter->setSizes( splitterSizes );
            d->splitterSizesSet = true;
        }
    }
}

void Sidebar::setBottomWidget( QWidget *widget )
{
    delete d->bottomWidget;
    d->bottomWidget = widget;
576
    if ( d->bottomWidget )
Pino Toscano's avatar
Pino Toscano committed
577
    {
578
        d->bottomWidget->setParent( this );
Pino Toscano's avatar
Pino Toscano committed
579 580 581 582
        d->vlay->addWidget( d->bottomWidget );
    }
}

583 584 585 586 587 588 589
void Sidebar::setItemEnabled( QWidget *widget, bool enabled )
{
    const int index = d->indexOf( widget );
    setIndexEnabled( index, enabled );
}

void Sidebar::setIndexEnabled( int index, bool enabled )
Pino Toscano's avatar
Pino Toscano committed
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
{
    if ( index < 0 || index >= d->pages.count() )
        return;

    Qt::ItemFlags f = d->pages.at( index )->flags();
    if ( enabled )
    {
        f |= Qt::ItemIsEnabled;
        f |= Qt::ItemIsSelectable;
    }
    else
    {
        f &= ~Qt::ItemIsEnabled;
        f &= ~Qt::ItemIsSelectable;
    }
    d->pages.at( index )->setFlags( f );

607
    if ( !enabled && index == d->list->currentRow() && isSidebarVisible() )
Pino Toscano's avatar
Pino Toscano committed
608 609 610 611 612 613 614 615 616
        // find an enabled item, and select that one
        for ( int i = 0; i < d->pages.count(); ++i )
            if ( d->pages.at(i)->flags() & Qt::ItemIsEnabled )
            {
                setCurrentIndex( i );
                break;
            }
}

617
bool Sidebar::isItemEnabled( QWidget *widget ) const
Pino Toscano's avatar
Pino Toscano committed
618
{
619 620 621 622 623 624 625
    const int index = d->indexOf( widget );
    return isIndexEnabled( index );
}

bool Sidebar::isIndexEnabled( int index ) const
{
    if ( index < 0 )
Pino Toscano's avatar
Pino Toscano committed
626 627 628 629 630 631
        return false;

    Qt::ItemFlags f = d->pages.at( index )->flags();
    return ( f & Qt::ItemIsEnabled ) == Qt::ItemIsEnabled;
}

632
void Sidebar::setCurrentItem( QWidget *widget, SetCurrentItemBehaviour b )
Pino Toscano's avatar
Pino Toscano committed
633
{
634 635 636 637 638 639 640
    const int index = d->indexOf( widget );
    setCurrentIndex( index, b );
}

void Sidebar::setCurrentIndex( int index, SetCurrentItemBehaviour b )
{
    if ( index < 0 || !isIndexEnabled( index ) )
Pino Toscano's avatar
Pino Toscano committed
641 642
        return;

643
    itemClicked( d->pages.at( index ), b );
644 645 646
    QModelIndex modelindex = d->list->model()->index( index, 0 );
    d->list->setCurrentIndex( modelindex );
    d->list->selectionModel()->select( modelindex, QItemSelectionModel::ClearAndSelect );
Pino Toscano's avatar
Pino Toscano committed
647 648
}

649
QWidget *Sidebar::currentItem() const
Pino Toscano's avatar
Pino Toscano committed
650
{
651 652
    const int row = d->list->currentRow();
    if (row < 0 || row >= d->pages.count())
653
        return nullptr;
654 655

    return d->pages[row]->widget();
Pino Toscano's avatar
Pino Toscano committed
656 657 658 659
}

void Sidebar::setSidebarVisibility( bool visible )
{
660
    if ( visible != d->list->isHidden() )
661 662
        return;

663
    static bool wasCollapsed = isCollapsed();
664

665
    d->list->setHidden( !visible );
666 667
    if ( visible )
    {
668 669
        setCollapsed( wasCollapsed );
        wasCollapsed = false;
670 671 672
    }
    else
    {
673 674
        wasCollapsed = isCollapsed();
        setCollapsed( true );
675
    }
Pino Toscano's avatar
Pino Toscano committed
676 677
}

678 679
bool Sidebar::isSidebarVisible() const
{
680
    return !d->list->isHidden();
681 682
}

683 684 685 686 687 688 689 690 691 692
void Sidebar::setCollapsed( bool collapsed )
{
    d->sideContainer->setHidden( collapsed );
}

bool Sidebar::isCollapsed() const
{
    return d->sideContainer->isHidden();
}

693 694 695 696 697 698 699 700 701
void Sidebar::moveSplitter(int sideWidgetSize)
{
    QList<int> splitterSizeList = d->splitter->sizes();
    const int total = splitterSizeList.at( 0 ) + splitterSizeList.at( 1 );
    splitterSizeList.replace( 0, total - sideWidgetSize );
    splitterSizeList.replace( 1, sideWidgetSize );
    d->splitter->setSizes( splitterSizeList );
}

702
void Sidebar::setItemVisible( QWidget *widget, bool visible )
703
{
704 705 706 707
    const int index = d->indexOf( widget );
    if ( index < 0 )
        return;

708
    d->list->setRowHidden( index, !visible );
709
    setIndexEnabled( index, visible );
710 711
}

Pino Toscano's avatar
Pino Toscano committed
712
void Sidebar::itemClicked( QListWidgetItem *item )
713 714 715 716
{
    itemClicked( item, UncollapseIfCollapsed );
}

717
void Sidebar::itemClicked( QListWidgetItem *item, SetCurrentItemBehaviour b )
Pino Toscano's avatar
Pino Toscano committed
718 719 720 721 722 723 724 725
{
    if ( !item )
        return;

    SidebarItem* sbItem = dynamic_cast< SidebarItem* >( item );
    if ( !sbItem )
        return;

726
    if ( sbItem->widget() == d->stack->currentWidget() )
Pino Toscano's avatar
Pino Toscano committed
727
    {
728
        if ( !isCollapsed() )
729 730
        {
            d->list->selectionModel()->clear();
731
            setCollapsed( true );
732 733 734
        }
        else
        {
735 736 737 738 739
            if ( b == UncollapseIfCollapsed )
            {
                setCollapsed( false );
                d->list->show();
            }
740
        }
Pino Toscano's avatar
Pino Toscano committed
741 742 743
    }
    else
    {
744
        if ( isCollapsed() && b == UncollapseIfCollapsed )
745
        {
746
            setCollapsed( false );
747 748
            d->list->show();
        }
Pino Toscano's avatar
Pino Toscano committed
749 750 751 752 753 754 755 756 757 758 759 760 761 762 763
        d->stack->setCurrentWidget( sbItem->widget() );
        d->sideTitle->setText( sbItem->toolTip() );
    }
}

void Sidebar::splitterMoved( int /*pos*/, int index )
{
    // if the side panel has been resized, save splitter sizes
    if ( index == 1 )
        saveSplitterSize();
}

void Sidebar::saveSplitterSize() const
{
    Okular::Settings::setSplitterSizes( d->splitter->sizes() );
764
    Okular::Settings::self()->save();
Pino Toscano's avatar
Pino Toscano committed
765 766
}

767
void Sidebar::listContextMenu( const QPoint pos )
768
{
Vishesh Handa's avatar
Vishesh Handa committed
769 770
    QMenu menu( this );
    menu.setTitle( i18n( "Okular" ) );
771 772 773
    QAction *showTextAct = menu.addAction( i18n( "Show Text" ) );
    showTextAct->setCheckable( true );
    showTextAct->setChecked( d->sideDelegate->isTextShown() );
Laurent Montel's avatar
Laurent Montel committed
774
    connect(showTextAct, &QAction::toggled, this, &Sidebar::showTextToggled);
775 776 777
    menu.addSeparator();
    QActionGroup *sizeGroup = new QActionGroup( &menu );
    int curSize = d->list->iconSize().width();
778
#define ADD_SIZE_ACTION( text, _itssize ) \
779
{ \
780
    const int itssize = static_cast< int >( _itssize ); \
781 782
    QAction *sizeAct = menu.addAction( text ); \
    sizeAct->setCheckable( true ); \
783
    sizeAct->setData( QVariant::fromValue( itssize ) ); \
784 785 786
    sizeAct->setChecked( itssize == curSize ); \
    sizeGroup->addAction( sizeAct ); \
}
787 788 789
    ADD_SIZE_ACTION( i18n( "Small Icons" ), KIconLoader::SizeSmallMedium )
    ADD_SIZE_ACTION( i18n( "Normal Icons" ), KIconLoader::SizeMedium )
    ADD_SIZE_ACTION( i18n( "Large Icons" ), KIconLoader::SizeLarge )
790
#undef ADD_SIZE_ACTION
Laurent Montel's avatar
Laurent Montel committed
791
    connect(sizeGroup, &QActionGroup::triggered, this, &Sidebar::iconSizeChanged);
792 793 794 795 796 797
    menu.exec( mapToGlobal( pos ) );
}

void Sidebar::showTextToggled( bool on )
{
    d->sideDelegate->setShowText( on );
798
    d->adjustListSize( true, on );
799 800
    d->list->reset();
    d->list->update();
801
    Okular::Settings::setSidebarShowText( on );
802
    Okular::Settings::self()->save();
803
}
Pino Toscano's avatar
Pino Toscano committed
804

805 806 807 808 809
void Sidebar::iconSizeChanged( QAction *action )
{
    int size = action->data().toInt();
    int oldSize = d->list->iconSize().width();
    d->list->setIconSize( QSize( size, size ) );
810
    d->adjustListSize( true, size > oldSize );
811 812 813
    d->list->reset();
    d->list->update();
    Okular::Settings::setSidebarIconSize( size );
814
    Okular::Settings::self()->save();
815 816
}

817 818
void Sidebar::dragEnterEvent( QDragEnterEvent* event )
{
Vishesh Handa's avatar
Vishesh Handa committed
819
    event->setAccepted( event->mimeData()->hasUrls() );
820 821 822 823
}

void Sidebar::dropEvent( QDropEvent* event )
{
Vishesh Handa's avatar
Vishesh Handa committed
824
    const QList<QUrl> list = KUrlMimeData::urlsFromMimeData( event->mimeData() );
Albert Astals Cid's avatar
Albert Astals Cid committed
825
    emit urlsDropped( list );
826 827
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
828
#include "sidebar.moc"