SidebarMode.cpp 28.3 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**************************************************************************
 * Copyright (C) 2009 by Ben Cooksley <bcooksley@kde.org>                 *
 *                                                                        *
 * 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.                 *
 *                                                                        *
 * This program is distributed in the hope that it will be useful,        *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of         *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
 * GNU General Public License for more details.                           *
 *                                                                        *
 * You should have received a copy of the GNU General Public License      *
 * along with this program; if not, write to the Free Software            *
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA          *
 * 02110-1301, USA.                                                       *
***************************************************************************/

#include "SidebarMode.h"

#include "MenuItem.h"
#include "MenuModel.h"
#include "ModuleView.h"
#include "MenuProxyModel.h"
#include "BaseData.h"
Marco Martin's avatar
Marco Martin committed
27
#include "ToolTips/tooltipmanager.h"
28
29
30
31
32

#include <QHBoxLayout>

#include <QAction>
#include <KAboutData>
33
#include <KCModuleInfo>
34
#include <KDescendantsProxyModel>
35
36
37
#include <KStandardAction>
#include <KLocalizedString>
#include <KIconLoader>
Marco Martin's avatar
Marco Martin committed
38
#include <KLocalizedContext>
39
#include <KServiceTypeTrader>
Marco Martin's avatar
Marco Martin committed
40
41
#include <KXmlGuiWindow>
#include <KActionCollection>
Marco Martin's avatar
Marco Martin committed
42
43
#include <KPackage/Package>
#include <KPackage/PackageLoader>
44
#include <KDeclarative/KDeclarative>
Marco Martin's avatar
Marco Martin committed
45
#include <QStandardItemModel>
Marco Martin's avatar
Marco Martin committed
46
47
48
#include <QQuickWidget>
#include <QQmlEngine>
#include <QQmlContext>
Marco Martin's avatar
Marco Martin committed
49
50
#include <QGraphicsOpacityEffect>
#include <QLabel>
51
#include <QMenu>
52
53
#include <QDebug>

54
55
56
57
58
59
60
61
62
#include <KActivities/Stats/ResultModel>
#include <KActivities/Stats/ResultSet>
#include <KActivities/Stats/Terms>

namespace KAStats = KActivities::Stats;

using namespace KAStats;
using namespace KAStats::Terms;

63
64
K_PLUGIN_FACTORY( SidebarModeFactory, registerPlugin<SidebarMode>(); )

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
FocusHackWidget::FocusHackWidget(QWidget *parent)
    : QWidget(parent)
{}
FocusHackWidget::~FocusHackWidget()
{}

void FocusHackWidget::focusNext()
{
    focusNextChild();
}

void FocusHackWidget::focusPrevious()
{
    focusNextPrevChild(false);
}

81
SubcategoryModel::SubcategoryModel(QAbstractItemModel *parentModel, QObject *parent)
82
    : KSelectionProxyModel(nullptr, parent),
83
        m_parentModel(parentModel)
84
85
86
87
88
{
    setSourceModel(parentModel);
    setSelectionModel(new QItemSelectionModel(parentModel, this));
    setFilterBehavior(SubTreesWithoutRoots);
}
89
90

QString SubcategoryModel::title() const
91
{
92
93
    return m_title;
}
94

95
96
void SubcategoryModel::setParentIndex(const QModelIndex &activeModule)
{
97
    selectionModel()->select(activeModule, QItemSelectionModel::ClearAndSelect);
98
99
100
    m_title = activeModule.data(Qt::DisplayRole).toString();
    emit titleChanged();
}
101
102


103
104
105
class MostUsedModel : public QSortFilterProxyModel
{
public:
Laurent Montel's avatar
Laurent Montel committed
106
    explicit MostUsedModel(QObject *parent = nullptr)
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
        : QSortFilterProxyModel (parent)
    {
        sort(0, Qt::DescendingOrder);
        setSortRole(ResultModel::ScoreRole);
        setDynamicSortFilter(true);
        //prepare default items
        m_defaultModel = new QStandardItemModel(this);
        QStandardItem *item = new QStandardItem();
        item->setData(QUrl(QStringLiteral("kcm:kcm_lookandfeel.desktop")), ResultModel::ResourceRole);
        m_defaultModel->appendRow(item);
        item = new QStandardItem();
        item->setData(QUrl(QStringLiteral("kcm:user_manager.desktop")), ResultModel::ResourceRole);
        m_defaultModel->appendRow(item);
        item = new QStandardItem();
        item->setData(QUrl(QStringLiteral("kcm:screenlocker.desktop")), ResultModel::ResourceRole);
        m_defaultModel->appendRow(item);
        item = new QStandardItem();
        item->setData(QUrl(QStringLiteral("kcm:powerdevilprofilesconfig.desktop")), ResultModel::ResourceRole);
        m_defaultModel->appendRow(item);
        item = new QStandardItem();
        item->setData(QUrl(QStringLiteral("kcm:kcm_kscreen.desktop")), ResultModel::ResourceRole);
        m_defaultModel->appendRow(item);
    }

    void setResultModel(ResultModel *model)
    {
        if (m_resultModel == model) {
            return;
        }

        auto updateModel = [this]() {
            if (m_resultModel->rowCount() >= 5) {
                setSourceModel(m_resultModel);
            } else {
                setSourceModel(m_defaultModel);
            }
        };

        m_resultModel = model;

        connect(m_resultModel, &QAbstractItemModel::rowsInserted, this, updateModel);
        connect(m_resultModel, &QAbstractItemModel::rowsRemoved, this, updateModel);

        updateModel();
    }

Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
153
    QHash<int, QByteArray> roleNames() const override
154
155
156
157
158
159
160
161
    {
        QHash<int, QByteArray> roleNames;
        roleNames.insert(Qt::DisplayRole, "display");
        roleNames.insert(Qt::DecorationRole, "decoration");
        roleNames.insert(ResultModel::ScoreRole, "score");
        return roleNames;
    }

162
163
164
165
166
167
168
    bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override
    {
        const QString desktopName = sourceModel()->index(source_row, 0, source_parent).data(ResultModel::ResourceRole).toUrl().path();
        KService::Ptr service = KService::serviceByStorageId(desktopName);
        return service;
    }

169
170
171
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
    {
        MenuItem *mi;
172
        const QString desktopName = QSortFilterProxyModel::data(index, ResultModel::ResourceRole).toUrl().path();
173
174
175
176
177
178
179
180
181
182

        if (m_menuItems.contains(desktopName)) {
            mi = m_menuItems.value(desktopName);
        } else {
            mi = new MenuItem(false, nullptr);
            const_cast<MostUsedModel *>(this)->m_menuItems.insert(desktopName, mi);

            KService::Ptr service = KService::serviceByStorageId(desktopName);

            if (!service || !service->isValid()) {
183
184
                qWarning()<<desktopName;
                m_resultModel->forgetResource(QStringLiteral("kcm:") % desktopName);
185
186
187
188
189
190
191
192
193
                return QVariant();
            }
            mi->setService(service);
        }

        switch (role) {
            case Qt::UserRole:
                return QVariant::fromValue(mi);
            case Qt::DisplayRole:
194
195
196
197
198
                if (mi->service() && mi->service()->isValid()) {
                    return mi->service()->name();
                } else {
                    return QVariant();
                }
199
            case Qt::DecorationRole:
200
201
202
203
204
                if (mi->service() && mi->service()->isValid()) {
                    return mi->service()->icon();
                } else {
                    return QVariant();
                }
205
            case ResultModel::ScoreRole:
206
                return QSortFilterProxyModel::data(index, ResultModel::ScoreRole).toInt();
207
208
209
210
211
212
213
            default:
                return QVariant();
        }
    }

private:
    QHash<QString, MenuItem *> m_menuItems;
214
    // Model when there is nothing from kactivities-stat
215
    QStandardItemModel *m_defaultModel;
216
    // Model fed by kactivities-stats
217
218
219
    ResultModel *m_resultModel;
};

220
221
class SidebarMode::Private {
public:
Marco Martin's avatar
Marco Martin committed
222
223
224
225
    Private()
      : quickWidget( nullptr ),
        moduleView( nullptr ),
        collection( nullptr ),
226
227
        activeCategoryRow( -1 ),
        activeSubCategoryRow( -1 )
Marco Martin's avatar
Marco Martin committed
228
229
    {}

230
231
232
233
    virtual ~Private() {
        delete aboutIcon;
    }

Laurent Montel's avatar
Laurent Montel committed
234
    ToolTipManager *toolTipManager = nullptr;
Tien Do Nam's avatar
Tien Do Nam committed
235
    ToolTipManager *mostUsedToolTipManager = nullptr;
Laurent Montel's avatar
Laurent Montel committed
236
    QQuickWidget * quickWidget = nullptr;
Marco Martin's avatar
Marco Martin committed
237
    KPackage::Package package;
Laurent Montel's avatar
Laurent Montel committed
238
239
240
241
242
    SubcategoryModel * subCategoryModel = nullptr;
    MostUsedModel * mostUsedModel = nullptr;
    FocusHackWidget * mainWidget = nullptr;
    QQuickWidget * placeHolderWidget = nullptr;
    QHBoxLayout * mainLayout = nullptr;
243
    KDeclarative::KDeclarative kdeclarative;
244
    MenuModel * model = nullptr;
Laurent Montel's avatar
Laurent Montel committed
245
246
    MenuProxyModel * categorizedModel = nullptr;
    MenuProxyModel * searchModel = nullptr;
247
    KDescendantsProxyModel * flatModel = nullptr;
Laurent Montel's avatar
Laurent Montel committed
248
249
250
    KAboutData * aboutIcon = nullptr;
    ModuleView * moduleView = nullptr;
    KActionCollection *collection = nullptr;
Marco Martin's avatar
Marco Martin committed
251
    QPersistentModelIndex activeCategoryIndex;
252
253
254
    int activeCategoryRow = -1;
    int activeSubCategoryRow = -1;
    int activeSearchRow = -1;
255
256
257
258
259
260
261
262
263
    bool m_actionMenuVisible = false;
    void setActionMenuVisible(SidebarMode* sidebarMode, const bool &actionMenuVisible)
    {
        if (m_actionMenuVisible == actionMenuVisible) {
            return;
        }
        m_actionMenuVisible = actionMenuVisible;
        emit sidebarMode->actionMenuVisibleChanged();
    }
264
    bool m_introPageVisible = true;
265
    bool m_defaultsIndicatorsVisible = false;
266
267
};

Marco Martin's avatar
Marco Martin committed
268
269
SidebarMode::SidebarMode( QObject *parent, const QVariantList &args )
    : BaseMode( parent, args )
270
271
    , d( new Private() )
{
Marco Martin's avatar
Marco Martin committed
272
    qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
273
274
    d->aboutIcon = new KAboutData( QStringLiteral("SidebarView"), i18n( "Sidebar View" ),
                                 QStringLiteral("1.0"), i18n( "Provides a categorized sidebar for control modules." ),
275
                                 KAboutLicense::GPL, i18n( "(c) 2017, Marco Martin" ) );
276
277
278
    d->aboutIcon->addAuthor( i18n( "Marco Martin" ), i18n( "Author" ), QStringLiteral("mart@kde.org") );
    d->aboutIcon->addAuthor( i18n( "Ben Cooksley" ), i18n( "Author" ), QStringLiteral("bcooksley@kde.org") );
    d->aboutIcon->addAuthor( i18n( "Mathias Soeken" ), i18n( "Developer" ), QStringLiteral("msoeken@informatik.uni-bremen.de") );
279
280

    qmlRegisterType<QAction>();
281
    qmlRegisterType<QAbstractItemModel>();
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
}

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

KAboutData * SidebarMode::aboutData()
{
    return d->aboutIcon;
}

ModuleView * SidebarMode::moduleView() const
{
    return d->moduleView;
}

QWidget * SidebarMode::mainWidget()
{
Marco Martin's avatar
Marco Martin committed
301
    if( !d->quickWidget ) {
302
303
304
305
306
        initWidget();
    }
    return d->mainWidget;
}

Marco Martin's avatar
Marco Martin committed
307
QAbstractItemModel * SidebarMode::categoryModel() const
308
309
310
311
312
{
    return d->categorizedModel;
}

QAbstractItemModel * SidebarMode::searchModel() const
Marco Martin's avatar
Marco Martin committed
313
{
314
    return d->searchModel;
Marco Martin's avatar
Marco Martin committed
315
316
317
318
319
320
321
}

QAbstractItemModel * SidebarMode::subCategoryModel() const
{
    return d->subCategoryModel;
}

322
323
324
325
326
QAbstractItemModel * SidebarMode::mostUsedModel() const
{
    return d->mostUsedModel;
}

327
328
329
QList<QAbstractItemView*> SidebarMode::views() const
{
    QList<QAbstractItemView*> list;
Marco Martin's avatar
Marco Martin committed
330
    //list.append( d->categoryView );
331
332
333
334
335
    return list;
}

void SidebarMode::initEvent()
{
336
    d->model = new MenuModel( rootItem(), this );
337
    foreach( MenuItem * child, rootItem()->children() ) {
338
        d->model->addException( child );
339
340
    }

341
342
    d->categorizedModel = new MenuProxyModel( this );
    d->categorizedModel->setCategorizedModel( true );
343
    d->categorizedModel->setSourceModel( d->model );
344
345
    d->categorizedModel->sort( 0 );
    d->categorizedModel->setFilterHighlightsEntries( false );
Marco Martin's avatar
Marco Martin committed
346

347
348
349
    d->flatModel = new KDescendantsProxyModel( this );
    d->flatModel->setSourceModel( d->model );

350
    d->searchModel = new MenuProxyModel( this );
351
    d->searchModel->setCategorizedModel( true );
352
    d->searchModel->setFilterHighlightsEntries( false );
353
    d->searchModel->setSourceModel( d->flatModel );
354

355
356
    d->mostUsedModel = new MostUsedModel( this );

357
    d->subCategoryModel = new SubcategoryModel( d->categorizedModel, this );
358
    d->mainWidget = new FocusHackWidget();
359
    d->mainWidget->installEventFilter(this);
360
361
362
363
    d->mainLayout = new QHBoxLayout(d->mainWidget);
    d->mainLayout->setContentsMargins(0, 0, 0, 0);
    d->moduleView = new ModuleView( d->mainWidget );
    connect( d->moduleView, &ModuleView::moduleChanged, this, &SidebarMode::moduleLoaded );
364
    connect(d->moduleView, &ModuleView::moduleSaved, this, &SidebarMode::updateDefaults);
Friedrich W. H. Kossebau's avatar
Friedrich W. H. Kossebau committed
365
    d->quickWidget = nullptr;
Marco Martin's avatar
Marco Martin committed
366
    moduleView()->setFaceType(KPageView::Plain);
Marco Martin's avatar
Marco Martin committed
367
368
369
370
371
372
    if (applicationMode() == BaseMode::InfoCenter) {
        d->moduleView->setSaveStatistics(false);
        d->moduleView->setApplyVisible(false);
        d->moduleView->setDefaultsVisible(false);
        d->moduleView->setResetVisible(false);
    }
373
374
}

375
QAction *SidebarMode::action(const QString &name) const
Marco Martin's avatar
Marco Martin committed
376
377
{
    if (!d->collection) {
378
        return nullptr;
Marco Martin's avatar
Marco Martin committed
379
380
    }

381
382
383
384
385
386
387
    return d->collection->action(name);
}

QString SidebarMode::actionIconName(const QString &name) const
{
    if (QAction *a = action(name)) {
        return a->icon().name();
Marco Martin's avatar
Marco Martin committed
388
    }
389
390

    return QString();
Marco Martin's avatar
Marco Martin committed
391
392
}

393
void SidebarMode::requestToolTip(const QModelIndex &index, const QRectF &rect)
Marco Martin's avatar
Marco Martin committed
394
{
395
396
397
    if (showToolTips() && index.model()) {
        d->toolTipManager->setModel(index.model());
        d->toolTipManager->requestToolTip(index, rect.toRect());
Nate Graham's avatar
Nate Graham committed
398
399
400
    }
}

Tien Do Nam's avatar
Tien Do Nam committed
401
402
403
404
405
406
407
void SidebarMode::requestMostUsedToolTip(int index, const QRectF &rect)
{
    if (showToolTips()) {
        d->mostUsedToolTipManager->requestToolTip(d->mostUsedModel->index(index, 0), rect.toRect());
    }
}

Marco Martin's avatar
Marco Martin committed
408
409
410
411
412
void SidebarMode::hideToolTip()
{
    d->toolTipManager->hideToolTip();
}

Tien Do Nam's avatar
Tien Do Nam committed
413
414
415
416
417
void SidebarMode::hideMostUsedToolTip()
{
    d->mostUsedToolTipManager->hideToolTip();
}

418
419
420
421
422
423
void SidebarMode::showActionMenu(const QPoint &position)
{
    QMenu *menu = new QMenu();
    connect(menu, &QMenu::aboutToHide, this, [this] () { d->setActionMenuVisible(this, false); } );
    menu->setAttribute(Qt::WA_DeleteOnClose);

424
    const QStringList actionList { QStringLiteral("configure"), QStringLiteral("help_contents"), QStringLiteral("help_report_bug"), QStringLiteral("help_about_app"), QStringLiteral("help_about_kde") };
425
426
427
428
429
430
431
432
    for (const QString &actionName : actionList) {
        menu->addAction(d->collection->action(actionName));
    }

    menu->popup(position);
    d->setActionMenuVisible(this, true);
}

433
void SidebarMode::loadModule( const QModelIndex& activeModule, const QStringList &args )
434
{
435
436
437
438
    if (!activeModule.isValid()) {
        return;
    }

439
440
441
    MenuItem *mi = activeModule.data(MenuModel::MenuItemRole).value<MenuItem *>();

    if (!mi) {
442
443
444
        return;
    }

445
446
447
448
    // If we are trying to load a module already open
    if (d->moduleView->activeModule() && mi->item() == *d->moduleView->activeModule()) {
        return;
    }
449

450
    if( !d->moduleView->resolveChanges() ) {
451
452
453
        return;
    }

454
455
    d->moduleView->closeModules();

Marco Martin's avatar
Marco Martin committed
456
457
458
459
460
461
462
    if (homeItem()) {
        d->m_introPageVisible = activeModule == d->categorizedModel->mapFromSource(d->model->indexForItem(homeItem()));
        emit introPageVisibleChanged();
    } else {
        setIntroPageVisible(false);
    }

463
    if ( mi->children().length() < 1) {
464
        d->moduleView->loadModule( activeModule, args );
Marco Martin's avatar
Marco Martin committed
465
    } else {
466
        d->moduleView->loadModule( activeModule.model()->index(0, 0, activeModule), args );
Marco Martin's avatar
Marco Martin committed
467
    }
468

469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
    if (activeModule.model() == d->categorizedModel) {
        const int newCategoryRow = activeModule.row();

        if (d->activeCategoryRow == newCategoryRow) {
            return;
        }

        d->activeCategoryIndex = activeModule;
        d->activeCategoryRow = newCategoryRow;

        d->activeSubCategoryRow = 0;

        d->subCategoryModel->setParentIndex( activeModule );

        if (d->activeSearchRow > -1) {
            d->activeSearchRow = -1;
            emit activeSearchRowChanged();
        }

        emit activeCategoryRowChanged();
        emit activeSubCategoryRowChanged();

    } else if (activeModule.model() == d->subCategoryModel) {
        if (d->activeSearchRow > -1) {
            d->activeSearchRow = -1;
            emit activeSearchRowChanged();
        }
        d->activeSubCategoryRow = activeModule.row();
        emit activeSubCategoryRowChanged();

    } else if (activeModule.model() == d->searchModel) {
        QModelIndex originalIndex = d->categorizedModel->mapFromSource(
            d->flatModel->mapToSource(
                d->searchModel->mapToSource(activeModule)));

        if (originalIndex.isValid()) {
            //are we in a  subcategory of the top categories?
            if (originalIndex.parent().isValid() && mi->parent()->menu()) {
                d->activeCategoryRow = originalIndex.parent().row();
                d->activeSubCategoryRow = originalIndex.row();

            // Is this kcm directly at the top level without a top category?
            } else {
                d->activeCategoryRow = originalIndex.row();
                d->activeSubCategoryRow = -1;
            }

            d->subCategoryModel->setParentIndex( originalIndex.parent() );
            emit activeCategoryRowChanged();
            emit activeSubCategoryRowChanged();
        }

        d->activeSearchRow = activeModule.row();
        emit activeSearchRowChanged();

524
    } else {
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
        if (d->activeSearchRow > -1) {
            d->activeSearchRow = -1;
            emit activeSearchRowChanged();
        }

        QModelIndex flatIndex;

        // search the corresponding item on the main model
        for (int i = 0; i < d->flatModel->rowCount(); ++i) {
            QModelIndex idx = d->flatModel->index(i, 0);
            MenuItem *otherMi = idx.data(MenuModel::MenuItemRole).value<MenuItem *>();

            if (otherMi->item() == mi->item()) {
                flatIndex = idx;
                break;
            }
        }

        if (flatIndex.isValid()) {
            QModelIndex idx = d->categorizedModel->mapFromSource(d->flatModel->mapToSource(flatIndex));

            MenuItem *parentMi = idx.parent().data(MenuModel::MenuItemRole).value<MenuItem *>();
            if (idx.isValid()) {
548
                if (parentMi && parentMi->menu()) {
549
550
551
552
553
554
555
556
557
558
559
560
                    d->subCategoryModel->setParentIndex( idx.parent() );
                    d->activeCategoryRow = idx.parent().row();
                    d->activeSubCategoryRow = idx.row();
                } else {
                    d->activeCategoryRow = idx.row();
                    d->activeSubCategoryRow = -1;
                }
                emit activeCategoryRowChanged();
                emit activeSubCategoryRowChanged();
            }
        }
    }
561
562
563
564
}

void SidebarMode::moduleLoaded()
{
Marco Martin's avatar
Marco Martin committed
565
566
    d->placeHolderWidget->hide();
    d->moduleView->show();
567
568
569
570
571
572
    if (applicationMode() == BaseMode::InfoCenter) {
        d->moduleView->setSaveStatistics(false);
        d->moduleView->setApplyVisible(false);
        d->moduleView->setDefaultsVisible(false);
        d->moduleView->setResetVisible(false);
    }
573
574
}

575
576
577
578
579
580
581
void SidebarMode::updateDefaults()
{
    QModelIndex categoryIdx = d->categorizedModel->index(d->activeCategoryRow, 0);
    auto item = categoryIdx.data(Qt::UserRole).value<MenuItem*>();
    Q_ASSERT(item);
    // If subcategory exist update from subcategory
    if (!item->children().isEmpty()) {
Benjamin Port's avatar
Benjamin Port committed
582
583
        auto subCateogryIdx = d->subCategoryModel->index(d->activeSubCategoryRow, 0);
        item = subCateogryIdx.data(Qt::UserRole).value<MenuItem*>();
584
585
586
587
588
589
590
    }
    item->updateDefaultIndicator();

    auto sourceIdx = d->categorizedModel->mapToSource(categoryIdx);
    emit d->model->dataChanged(sourceIdx, sourceIdx);

    auto subCateogryIdx = d->subCategoryModel->index(d->activeSubCategoryRow, 0);
591
592
    auto subCategorySourceIdx = d->categorizedModel->mapToSource(d->subCategoryModel->mapToSource(subCateogryIdx));
    emit d->model->dataChanged(subCategorySourceIdx, subCategorySourceIdx);
593
594
}

595
int SidebarMode::activeSearchRow() const
Marco Martin's avatar
Marco Martin committed
596
{
597
    return d->activeSearchRow;
Marco Martin's avatar
Marco Martin committed
598
599
}

600
int SidebarMode::activeCategoryRow() const
Marco Martin's avatar
Marco Martin committed
601
{
602
    return d->activeCategoryRow;
Marco Martin's avatar
Marco Martin committed
603
604
}

605
606
607
608
609
610
void SidebarMode::setIntroPageVisible(const bool &introPageVisible)
{
    if (d->m_introPageVisible == introPageVisible) {
        return;
    }

Marco Martin's avatar
Marco Martin committed
611
612
    // TODO: Make the intro page of SystemSettings a KCM as well
    if (homeItem()) {
613
614
        d->placeHolderWidget->hide();
        d->moduleView->show();
Marco Martin's avatar
Marco Martin committed
615
616
617
618
619
        if (introPageVisible) {
            loadModule(d->categorizedModel->mapFromSource(d->model->indexForItem(homeItem())));
        }
    } else {
        if (introPageVisible) {
620
            d->subCategoryModel->setParentIndex(QModelIndex());
Marco Martin's avatar
Marco Martin committed
621
622
623
624
625
626
627
628
629
630
            d->activeCategoryRow = -1;
            emit activeCategoryRowChanged();
            d->activeSubCategoryRow = -1;
            emit activeSubCategoryRowChanged();
            d->placeHolderWidget->show();
            d->moduleView->hide();
        } else {
            d->placeHolderWidget->hide();
            d->moduleView->show();
        }
631
632
633
634
635
636
    }

    d->m_introPageVisible = introPageVisible;
    emit introPageVisibleChanged();
}

637
638
639
640
void SidebarMode::toggleDefaultsIndicatorsVisibility()
{
    d->m_defaultsIndicatorsVisible = !d->m_defaultsIndicatorsVisible;
    d->moduleView->moduleShowDefaultsIndicators(d->m_defaultsIndicatorsVisible);
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668

    if (d->m_defaultsIndicatorsVisible) {
        for (int i = 0; i < d->flatModel->rowCount(); ++i) {
            QModelIndex idx = d->flatModel->index(i, 0);
            auto item = idx.data(MenuModel::MenuItemRole).value<MenuItem *>();
            if (item->menu()) {
                continue;
            }
            const bool showIndicator = item->showDefaultIndicator();
            item->updateDefaultIndicator();
            if (showIndicator == item->showDefaultIndicator()) {
                continue;
            }
            auto itemIdx = d->model->indexForItem(item);
            emit d->model->dataChanged(itemIdx, itemIdx);
            MenuItem *parent = item->parent();
            while (parent) {
                auto parentIdx = d->model->indexForItem(parent);
                if (parentIdx.isValid()) {
                    emit d->model->dataChanged(parentIdx, parentIdx);
                    parent = parent->parent();
                } else {
                    parent = nullptr;
                }
            }
        }
    }

669
670
671
    emit defaultsIndicatorsVisibleChanged();
}

672
673
674
675
676
int SidebarMode::width() const
{
    return d->mainWidget->width();
}

677
678
679
680
681
bool SidebarMode::actionMenuVisible() const
{
    return d->m_actionMenuVisible;
}

682
int SidebarMode::activeSubCategoryRow() const
683
{
684
    return d->activeSubCategoryRow;
685
686
}

687
688
689
690
691
bool SidebarMode::introPageVisible() const
{
    return (d->m_introPageVisible);
}

692
693
694
695
696
bool SidebarMode::defaultsIndicatorsVisible() const
{
    return d->m_defaultsIndicatorsVisible;
}

697
698
699
void SidebarMode::initWidget()
{
    // Create the widgets
Marco Martin's avatar
Marco Martin committed
700

Marco Martin's avatar
Marco Martin committed
701
702
703
    if (!KMainWindow::memberList().isEmpty()) {
        KXmlGuiWindow *mainWindow = qobject_cast<KXmlGuiWindow *>(KMainWindow::memberList().first());
        if (mainWindow) {
Marco Martin's avatar
Marco Martin committed
704
            d->collection = mainWindow->actionCollection();
Marco Martin's avatar
Marco Martin committed
705
706
        }
    }
Marco Martin's avatar
Marco Martin committed
707
708

    d->quickWidget = new QQuickWidget(d->mainWidget);
709
    d->quickWidget->quickWindow()->setTitle(i18n("Sidebar"));
Marco Martin's avatar
Marco Martin committed
710
    d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
711
    qmlRegisterUncreatableType<SidebarMode>("org.kde.systemsettings", 1, 0, "SystemSettings", QStringLiteral("Not creatable, use the systemsettings attached property"));
712

713
714
    d->quickWidget->engine()->rootContext()->setContextProperty(QStringLiteral("systemsettings"), this);
    d->package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML"));
Marco Martin's avatar
Marco Martin committed
715
    d->package.setPath(QStringLiteral("org.kde.systemsettings.sidebar"));
716
717

    d->kdeclarative.setDeclarativeEngine(d->quickWidget->engine());
718
719
    d->kdeclarative.setupEngine(d->quickWidget->engine());
    d->kdeclarative.setupContext();
720

721
    d->quickWidget->setSource(QUrl::fromLocalFile(d->package.filePath("mainscript")));
722

723
724
725
726
727
728
    if (!d->quickWidget->rootObject()) {
        for (const auto &err : d->quickWidget->errors()) {
            qWarning() << err.toString();
        }
        qFatal("Fatal error while loading the sidebar view qml component");
    }
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
    const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt();
    if (rootImplicitWidth != 0) {
        d->quickWidget->setFixedWidth(rootImplicitWidth);
    } else {
        d->quickWidget->setFixedWidth(240);
    }
    connect(d->quickWidget->rootObject(), &QQuickItem::implicitWidthChanged,
            this, [this]() {
                const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt();
                if (rootImplicitWidth != 0) {
                    d->quickWidget->setFixedWidth(rootImplicitWidth);
                } else {
                    d->quickWidget->setFixedWidth(240);
                }
            });
744
745
    connect(d->quickWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext()));
    connect(d->quickWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious()));
746

Marco Martin's avatar
Marco Martin committed
747
    d->quickWidget->installEventFilter(this);
748

Marco Martin's avatar
Marco Martin committed
749
    d->placeHolderWidget = new QQuickWidget(d->mainWidget);
750
    d->placeHolderWidget->quickWindow()->setTitle(i18n("Most Used"));
Marco Martin's avatar
Marco Martin committed
751
752
    d->placeHolderWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
    d->placeHolderWidget->engine()->rootContext()->setContextObject(new KLocalizedContext(d->placeHolderWidget));
753
754
    d->placeHolderWidget->engine()->rootContext()->setContextProperty(QStringLiteral("systemsettings"), this);
    d->placeHolderWidget->setSource(QUrl::fromLocalFile(d->package.filePath("ui", QStringLiteral("introPage.qml"))));
755
756
757
    connect(d->placeHolderWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext()));
    connect(d->placeHolderWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious()));
    d->placeHolderWidget->installEventFilter(this);
Marco Martin's avatar
Marco Martin committed
758

Marco Martin's avatar
Marco Martin committed
759
    d->mainLayout->addWidget( d->quickWidget );
Marco Martin's avatar
Marco Martin committed
760
    d->moduleView->hide();
761
    d->mainLayout->addWidget( d->moduleView );
Marco Martin's avatar
Marco Martin committed
762
    d->mainLayout->addWidget( d->placeHolderWidget );
763
    emit changeToolBarItems(BaseMode::NoItems);
764

765
    d->toolTipManager = new ToolTipManager(d->categorizedModel, d->quickWidget, ToolTipManager::ToolTipPosition::Right);
Tien Do Nam's avatar
Tien Do Nam committed
766
767
    d->mostUsedToolTipManager = new ToolTipManager(d->mostUsedModel, d->placeHolderWidget, ToolTipManager::ToolTipPosition::BottomCenter);

768
    d->mostUsedModel->setResultModel(new ResultModel( AllResources | Agent(QStringLiteral("org.kde.systemsettings")) | HighScoredFirst | Limit(5), this));
Marco Martin's avatar
Marco Martin committed
769
770
771
772
773
774

    if (homeItem()) {
        d->placeHolderWidget->hide();
        d->moduleView->show();
        loadModule(d->categorizedModel->mapFromSource(d->model->indexForItem(homeItem())));
    }
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791

    if (!startupModule().isEmpty()) {
        MenuItem *item = rootItem()->descendantForModule(startupModule());
        if (item) {
            loadModule(d->model->indexForItem(item), startupModuleArgs());
        }
    }
}

void SidebarMode::reloadStartupModule()
{
    if (!startupModule().isEmpty()) {
        MenuItem *item = rootItem()->descendantForModule(startupModule());
        if (item) {
            loadModule(d->model->indexForItem(item), startupModuleArgs());
        }
    }
792
793
}

Marco Martin's avatar
Marco Martin committed
794
795
bool SidebarMode::eventFilter(QObject* watched, QEvent* event)
{
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
    //FIXME: those are all workarounds around the QQuickWidget brokeness
    if ((watched == d->quickWidget || watched == d->placeHolderWidget)
         && event->type() == QEvent::KeyPress) {
        //allow tab navigation inside the qquickwidget
        QKeyEvent *ke = static_cast<QKeyEvent *>(event);
        QQuickWidget *qqw = static_cast<QQuickWidget *>(watched);
        if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
            QCoreApplication::sendEvent(qqw->quickWindow(), event);
            return true;
        }
    } else if ((watched == d->quickWidget || watched == d->placeHolderWidget)
                && event->type() == QEvent::FocusIn) {
        QFocusEvent *fe = static_cast<QFocusEvent *>(event);
        QQuickWidget *qqw = static_cast<QQuickWidget *>(watched);
        if (qqw && qqw->rootObject()) {
            if (fe->reason() == Qt::TabFocusReason) {
                QMetaObject::invokeMethod(qqw->rootObject(), "focusFirstChild");
            } else if (fe->reason() == Qt::BacktabFocusReason) {
                QMetaObject::invokeMethod(qqw->rootObject(), "focusLastChild");
            }
        }
    } else if (watched == d->quickWidget && event->type() == QEvent::Leave) {
Marco Martin's avatar
Marco Martin committed
818
        QCoreApplication::sendEvent(d->quickWidget->quickWindow(), event);
819
820
    } else if (watched == d->mainWidget && event->type() == QEvent::Resize) {
        emit widthChanged();
821
822
    } else if (watched == d->mainWidget && event->type() == QEvent::Show) {
        emit changeToolBarItems(BaseMode::NoItems);
Marco Martin's avatar
Marco Martin committed
823
824
825
826
    }
    return BaseMode::eventFilter(watched, event);
}

827
828
void SidebarMode::giveFocus()
{
Marco Martin's avatar
Marco Martin committed
829
    d->quickWidget->setFocus();
830
831
832
}

#include "SidebarMode.moc"