mainwindow.cpp 64.7 KB
Newer Older
1
2
3
/*
 *  mainwindow.cpp  -  main application window
 *  Program:  kalarm
4
 *  SPDX-FileCopyrightText: 2001-2021 David Jarvie <djarvie@kde.org>
5
 *
6
 *  SPDX-License-Identifier: GPL-2.0-or-later
7
8
 */

Laurent Montel's avatar
Laurent Montel committed
9
#include "mainwindow.h"
David Jarvie's avatar
David Jarvie committed
10
11
12
13

#include "alarmlistdelegate.h"
#include "alarmlistview.h"
#include "birthdaydlg.h"
14
#include "datepicker.h"
David Jarvie's avatar
David Jarvie committed
15
16
17
#include "functions.h"
#include "kalarmapp.h"
#include "kamail.h"
David Jarvie's avatar
David Jarvie committed
18
#include "newalarmaction.h"
David Jarvie's avatar
David Jarvie committed
19
20
#include "prefdlg.h"
#include "preferences.h"
21
#include "resourcescalendar.h"
David Jarvie's avatar
David Jarvie committed
22
23
24
25
#include "resourceselector.h"
#include "templatedlg.h"
#include "templatepickdlg.h"
#include "traywindow.h"
26
#include "wakedlg.h"
27
#include "resources/datamodel.h"
28
#include "resources/resources.h"
David Jarvie's avatar
David Jarvie committed
29
#include "resources/eventmodel.h"
David Jarvie's avatar
David Jarvie committed
30
#include "lib/autoqpointer.h"
31
#include "lib/dragdrop.h"
David Jarvie's avatar
David Jarvie committed
32
33
#include "lib/messagebox.h"
#include "lib/synchtimer.h"
David Jarvie's avatar
David Jarvie committed
34
#include "kalarm_debug.h"
35

David Jarvie's avatar
David Jarvie committed
36
#include <KAlarmCal/AlarmText>
37

David Jarvie's avatar
David Jarvie committed
38
#include <AkonadiCore/Item>
39
#include <KCalendarCore/MemoryCalendar>
David Jarvie's avatar
David Jarvie committed
40
#include <KCalUtils/ICalDrag>
Laurent Montel's avatar
Laurent Montel committed
41
using namespace KCalendarCore;
42
using namespace KCalUtils;
David Jarvie's avatar
David Jarvie committed
43

David Jarvie's avatar
David Jarvie committed
44
#include <KAboutData>
45
#include <KToolBar>
46
47
#include <KActionCollection>
#include <KGlobalAccel>
48
#include <KStandardAction>
Laurent Montel's avatar
Laurent Montel committed
49
#include <KLocalizedString>
David Jarvie's avatar
Spacing    
David Jarvie committed
50
#include <KSharedConfig>
51
52
53
54
55
#include <KConfigGroup>
#include <KShortcutsDialog>
#include <KEditToolBar>
#include <KXMLGUIFactory>
#include <KToggleAction>
56
#include <KNotifyConfigWidget>
57
#include <KToolBarPopupAction>
58
#include <kxmlgui_version.h>
59

David Jarvie's avatar
Tidy up    
David Jarvie committed
60
#include <QAction>
61
#include <QSplitter>
62
#include <QVBoxLayout>
63
64
65
66
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QResizeEvent>
#include <QCloseEvent>
Laurent Montel's avatar
Laurent Montel committed
67
#include <QDesktopWidget>
David Jarvie's avatar
Spacing    
David Jarvie committed
68
#include <QMenu>
Laurent Montel's avatar
Laurent Montel committed
69
#include <QMimeDatabase>
Laurent Montel's avatar
Laurent Montel committed
70
#include <QInputDialog>
71
#include <QTimer>
Daniel Vrátil's avatar
Daniel Vrátil committed
72
#include <QUrl>
73
#include <QMenuBar>
David Jarvie's avatar
David Jarvie committed
74
#include <QSystemTrayIcon>
75
#include <QMimeData>
David Jarvie's avatar
David Jarvie committed
76

77
78
using namespace KAlarmCal;

79
80
//clazy:excludeall=non-pod-global-static

David Jarvie's avatar
David Jarvie committed
81
82
83
84
85
namespace
{
const QString UI_FILE(QStringLiteral("kalarmui.rc"));
const char*   WINDOW_NAME = "MainWindow";

86
87
88
89
90
91
const char*   VIEW_GROUP          = "View";
const char*   SHOW_COLUMNS        = "ShowColumns";
const char*   SHOW_ARCHIVED_KEY   = "ShowArchivedAlarms";
const char*   SHOW_RESOURCES_KEY  = "ShowResources";
const char*   RESOURCES_WIDTH_KEY = "ResourcesWidth";
const char*   SHOW_DATE_NAVIGATOR = "ShowDateNavigator";
David Jarvie's avatar
David Jarvie committed
92
93
94
95
96
97
98
99

QString             undoText;
QString             undoTextStripped;
QList<QKeySequence> undoShortcut;
QString             redoText;
QString             redoTextStripped;
QList<QKeySequence> redoShortcut;
}
100

101

102
/*=============================================================================
103
=  Class: MainWindow
104
=============================================================================*/
105

106
MainWindow::WindowList   MainWindow::mWindowList;
Laurent Montel's avatar
Laurent Montel committed
107
TemplateDlg*             MainWindow::mTemplateDlg = nullptr;
David Jarvie's avatar
David Jarvie committed
108

109

110
111
112
113
114
/******************************************************************************
* Construct an instance.
* To avoid resize() events occurring while still opening the calendar (and
* resultant crashes), the calendar is opened before constructing the instance.
*/
115
MainWindow* MainWindow::create(bool restored)
116
{
117
118
    theApp()->checkCalendar();    // ensure calendar is open
    return new MainWindow(restored);
119
120
}

121
MainWindow::MainWindow(bool restored)
122
    : MainWindowBase(nullptr, Qt::WindowContextHelpButtonHint)
123
{
124
    Q_UNUSED(restored)
125
    qCDebug(KALARM_LOG) << "MainWindow:";
126
127
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowModality(Qt::WindowModal);
David Jarvie's avatar
David Jarvie committed
128
    setObjectName(QStringLiteral("MainWin"));    // used by LikeBack
David Jarvie's avatar
David Jarvie committed
129
    setPlainCaption(KAboutData::applicationData().displayName());
130
    KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
131
132
133
134
    mShowResources     = config.readEntry(SHOW_RESOURCES_KEY, false);
    mShowDateNavigator = config.readEntry(SHOW_DATE_NAVIGATOR, false);
    mShowArchived      = config.readEntry(SHOW_ARCHIVED_KEY, false);
    mResourcesWidth    = config.readEntry(RESOURCES_WIDTH_KEY, 0);
135
    const QList<bool> showColumns = config.readEntry(SHOW_COLUMNS, QList<bool>());
136
137
138
139
140
141
142
143
144

    setAcceptDrops(true);         // allow drag-and-drop onto this window

    mSplitter = new QSplitter(Qt::Horizontal, this);
    mSplitter->setChildrenCollapsible(false);
    mSplitter->installEventFilter(this);
    setCentralWidget(mSplitter);

    // Create the calendar resource selector widget
145
    DataModel::widgetNeedsDatabase(this);
146
147
148
149
    mPanel = new QWidget(mSplitter);
    QVBoxLayout* vlayout = new QVBoxLayout(mPanel);
    vlayout->setContentsMargins(0, 0, 0, 0);

150
    mResourceSelector = new ResourceSelector(this, mPanel);
151
    vlayout->addWidget(mResourceSelector);
152
153
    mSplitter->setStretchFactor(0, 0);   // don't resize resource selector when window is resized
    mSplitter->setStretchFactor(1, 1);
David Jarvie's avatar
David Jarvie committed
154

155
156
157
158
    mDatePicker = new DatePicker(mPanel);
    vlayout->addWidget(mDatePicker);
    vlayout->addStretch();

159
    // Create the alarm list widget
160
    mListFilterModel = DataModel::createAlarmListModel(this);
161
    mListFilterModel->setEventTypeFilter(mShowArchived ? CalEvent::ACTIVE | CalEvent::ARCHIVED : CalEvent::ACTIVE);
162
163
    mListView = new AlarmListView(WINDOW_NAME, mSplitter);
    mListView->setModel(mListFilterModel);
164
    mListView->setColumnsVisible(showColumns);
165
    mListView->setItemDelegate(new AlarmListDelegate(mListView));
166
    connect(mListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::slotSelection);
Laurent Montel's avatar
Laurent Montel committed
167
    connect(mListView, &AlarmListView::contextMenuRequested, this, &MainWindow::slotContextMenuRequested);
168
    connect(mListView, &AlarmListView::columnsVisibleChanged, this, &MainWindow::slotAlarmListColumnsChanged);
169
170
    connect(Resources::instance(), &Resources::settingsChanged,
                             this, &MainWindow::slotCalendarStatusChanged);
Laurent Montel's avatar
Laurent Montel committed
171
    connect(mResourceSelector, &ResourceSelector::resized, this, &MainWindow::resourcesResized);
172
    connect(mDatePicker, &DatePicker::datesSelected, this, &MainWindow::datesSelected);
173
174
175
    mListView->installEventFilter(this);
    initActions();

Laurent Montel's avatar
Laurent Montel committed
176
    setAutoSaveSettings(QLatin1String(WINDOW_NAME), true);    // save toolbars, window sizes etc.
177
178
179
180
181
182
183
184
185
186
    mWindowList.append(this);
    if (mWindowList.count() == 1)
    {
        // It's the first main window
        if (theApp()->wantShowInSystemTray())
            theApp()->displayTrayIcon(true, this);     // create system tray icon for run-in-system-tray mode
        else if (theApp()->trayWindow())
            theApp()->trayWindow()->setAssocMainWindow(this);    // associate this window with the system tray icon
    }
    slotCalendarStatusChanged();   // initialise action states now that window is registered
187
188
}

189
MainWindow::~MainWindow()
190
{
191
    qCDebug(KALARM_LOG) << "~MainWindow";
192
193
    bool trayParent = isTrayParent();   // must call before removing from window list
    mWindowList.removeAt(mWindowList.indexOf(this));
David Jarvie's avatar
David Jarvie committed
194

195
196
    // Prevent view updates during window destruction
    delete mResourceSelector;
Laurent Montel's avatar
Laurent Montel committed
197
    mResourceSelector = nullptr;
198
199
    delete mDatePicker;
    mDatePicker = nullptr;
200
    delete mListView;
Laurent Montel's avatar
Laurent Montel committed
201
    mListView = nullptr;
David Jarvie's avatar
David Jarvie committed
202

203
204
205
206
207
208
209
    if (theApp()->trayWindow())
    {
        if (trayParent)
            delete theApp()->trayWindow();
        else
            theApp()->trayWindow()->removeWindow(this);
    }
210
    KSharedConfig::openConfig()->sync();    // save any new window size to disc
211
    theApp()->quitIf();
212
213
}

214
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
215
* Called when the QApplication::saveStateRequest() signal has been emitted.
216
217
218
* Save settings to the session managed config file, for restoration
* when the program is restored.
*/
David Jarvie's avatar
David Jarvie committed
219
void MainWindow::saveProperties(KConfigGroup& config)
220
{
221
222
    config.writeEntry("HiddenTrayParent", isTrayParent() && isHidden());
    config.writeEntry("ShowArchived", mShowArchived);
223
224
    config.writeEntry("ShowResources", mShowResources);
    config.writeEntry("ShowDateNavigator", mShowDateNavigator);
225
    config.writeEntry("ShowColumns", mListView->columnsVisible());
226
    config.writeEntry("ResourcesWidth", mResourceSelector->isVisible() ? mResourceSelector->width() : 0);
227
228
229
230
231
232
233
}

/******************************************************************************
* Read settings from the session managed config file.
* This function is automatically called whenever the app is being
* restored. Read in whatever was saved in saveProperties().
*/
David Jarvie's avatar
David Jarvie committed
234
void MainWindow::readProperties(const KConfigGroup& config)
235
{
236
237
238
239
240
241
242
    mHiddenTrayParent  = config.readEntry("HiddenTrayParent", true);
    mShowArchived      = config.readEntry("ShowArchived", false);
    mShowResources     = config.readEntry("ShowResources", false);
    mResourcesWidth    = config.readEntry("ResourcesWidth", (int)0);
    if (mResourcesWidth <= 0)
        mShowResources = false;
    mShowDateNavigator = config.readEntry("ShowDateNavigator", false);
243
    mListView->setColumnsVisible(config.readEntry("ShowColumns", QList<bool>()));
244
245
246
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
247
248
249
250
* Get the main main window, i.e. the parent of the system tray icon, or if
* none, the first main window to be created. Visible windows take precedence
* over hidden ones.
*/
251
MainWindow* MainWindow::mainMainWindow()
David Jarvie's avatar
David Jarvie committed
252
{
253
    MainWindow* tray = (theApp() && theApp()->trayWindow()) ? theApp()->trayWindow()->assocMainWindow() : nullptr;
254
255
256
    if (tray  &&  tray->isVisible())
        return tray;
    for (int i = 0, end = mWindowList.count();  i < end;  ++i)
257
258
        if (mWindowList.at(i)->isVisible())
            return mWindowList.at(i);
259
260
261
    if (tray)
        return tray;
    if (mWindowList.isEmpty())
Laurent Montel's avatar
Laurent Montel committed
262
        return nullptr;
263
    return mWindowList.at(0);
David Jarvie's avatar
David Jarvie committed
264
265
266
}

/******************************************************************************
267
* Check whether this main window is effectively the parent of the system tray icon.
268
*/
269
bool MainWindow::isTrayParent() const
270
{
271
    TrayWindow* tray = theApp()->trayWindow();
David Jarvie's avatar
David Jarvie committed
272
    if (!tray  ||  !QSystemTrayIcon::isSystemTrayAvailable())
273
274
275
276
        return false;
    if (tray->assocMainWindow() == this)
        return true;
    return mWindowList.count() == 1;
277
278
}

279
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
280
* Close all main windows.
281
*/
282
void MainWindow::closeAll()
283
{
284
285
    while (!mWindowList.isEmpty())
        delete mWindowList[0];    // N.B. the destructor removes the window from the list
286
287
}

288
289
290
291
292
/******************************************************************************
* Intercept events for the splitter widget.
*/
bool MainWindow::eventFilter(QObject* obj, QEvent* e)
{
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    if (obj == mSplitter)
    {
        switch (e->type())
        {
            case QEvent::Resize:
                // Don't change resources size while WINDOW is being resized.
                // Resize event always occurs before Paint.
                mResizing = true;
                break;
            case QEvent::Paint:
                // Allow resources to be resized again
                mResizing = false;
                break;
            default:
                break;
        }
    }
    else if (obj == mListView)
    {
        switch (e->type())
        {
            case QEvent::KeyPress:
            {
316
                auto ke = static_cast<QKeyEvent*>(e);
317
318
319
320
321
322
323
324
325
326
327
328
329
                if (ke->key() == Qt::Key_Delete  &&  ke->modifiers() == Qt::ShiftModifier)
                {
                    // Prevent Shift-Delete being processed by EventListDelegate
                    mActionDeleteForce->trigger();
                    return true;
                }
                break;
            }
            default:
                break;
        }
    }
    return false;
330
331
}

332
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
333
334
335
336
* Called when the window's size has changed (before it is painted).
* Sets the last column in the list view to extend at least to the right hand
* edge of the list view.
* Records the new size in the config file.
337
*/
338
void MainWindow::resizeEvent(QResizeEvent* re)
339
{
340
341
    // Save the window's new size only if it's the first main window
    MainWindowBase::resizeEvent(re);
342
343
344
345
346
347
348
349
350
    setSplitterSizes();
}

/******************************************************************************
* Emitted when the date selection changes in the date picker.
*/
void MainWindow::datesSelected(const QVector<QDate>& dates)
{
    mListFilterModel->setDateFilter(dates);
351
352
}

David Jarvie's avatar
David Jarvie committed
353
354
355
356
/******************************************************************************
* Called when the resources panel has been resized.
* Records the new size in the config file.
*/
David Jarvie's avatar
David Jarvie committed
357
358
void MainWindow::resourcesResized()
{
359
360
    if (!mShown  ||  mResizing)
        return;
David Jarvie's avatar
David Jarvie committed
361
    const QList<int> widths = mSplitter->sizes();
362
    if (widths.count() > 1  &&  mResourceSelector->isVisible())
363
364
365
366
367
368
369
370
    {
        mResourcesWidth = widths[0];
        // Width is reported as non-zero when resource selector is
        // actually invisible, so note a zero width in these circumstances.
        if (mResourcesWidth <= 5)
            mResourcesWidth = 0;
        else if (mainMainWindow() == this)
        {
371
372
373
374
            KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
            config.writeEntry(SHOW_RESOURCES_KEY, mShowResources);
            if (mShowResources)
                config.writeEntry(RESOURCES_WIDTH_KEY, mResourcesWidth);
375
            config.sync();
376
377
        }
    }
David Jarvie's avatar
David Jarvie committed
378
379
}

380
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
381
382
383
* Called when the window is first displayed.
* Sets the last column in the list view to extend at least to the right hand
* edge of the list view.
384
*/
385
void MainWindow::showEvent(QShowEvent* se)
386
{
387
    setSplitterSizes();
388
389
    MainWindowBase::showEvent(se);
    mShown = true;
390
391

    if (mMenuError)
David Jarvie's avatar
David Jarvie committed
392
        QTimer::singleShot(0, this, &MainWindow::showMenuErrorMessage);    //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks)
393
394
}

395
396
397
398
399
400
401
402
403
404
405
406
407
408
/******************************************************************************
* Set the sizes of the splitter panels.
*/
void MainWindow::setSplitterSizes()
{
    if (mShowResources  &&  mResourcesWidth > 0)
    {
        const QList<int> widths{ mResourcesWidth,
                                 width() - mResourcesWidth - mSplitter->handleWidth()
                               };
        mSplitter->setSizes(widths);
    }
}

409
/******************************************************************************
410
411
412
* Show the menu error message now that the main window has been displayed.
* Waiting until now lets the user easily associate the message with the main
* window which is faulty.
413
*/
414
void MainWindow::showMenuErrorMessage()
415
{
416
417
    if (mMenuError)
    {
Laurent Montel's avatar
Laurent Montel committed
418
        KAMessageBox::error(this, xi18nc("@info", "Failure to create menus (perhaps <filename>%1</filename> missing or corrupted)", UI_FILE));
419
420
        mMenuError = false;
    }
421
422
}

423
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
424
* Called after the window is hidden.
425
*/
426
void MainWindow::hideEvent(QHideEvent* he)
427
{
428
    MainWindowBase::hideEvent(he);
429
430
}

431
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
432
* Initialise the menu, toolbar and main window actions.
433
*/
434
void MainWindow::initActions()
435
{
436
437
    KActionCollection* actions = actionCollection();

Laurent Montel's avatar
Laurent Montel committed
438
    mActionTemplates = new QAction(i18nc("@action", "&Templates..."), this);
Laurent Montel's avatar
Laurent Montel committed
439
    actions->addAction(QStringLiteral("templates"), mActionTemplates);
Laurent Montel's avatar
Laurent Montel committed
440
    connect(mActionTemplates, &QAction::triggered, this, &MainWindow::slotTemplates);
441

442
    mActionNew = new NewAlarmAction(false, i18nc("@action", "&New"), this, actions);
443
    mActionNew->setActionNames(QStringLiteral("newDisplay"), QStringLiteral("newCommand"), QStringLiteral("newEmail"), QStringLiteral("newAudio"), QStringLiteral("newFromTemplate"));
Laurent Montel's avatar
Laurent Montel committed
444
    actions->addAction(QStringLiteral("new"), mActionNew);
445
446
    connect(mActionNew, &NewAlarmAction::selected, this, &MainWindow::slotNew);
    connect(mActionNew, &NewAlarmAction::selectedTemplate, this, &MainWindow::slotNewFromTemplate);
447

Laurent Montel's avatar
Laurent Montel committed
448
    mActionCreateTemplate = new QAction(i18nc("@action", "Create Tem&plate..."), this);
Laurent Montel's avatar
Laurent Montel committed
449
    actions->addAction(QStringLiteral("createTemplate"), mActionCreateTemplate);
Laurent Montel's avatar
Laurent Montel committed
450
    connect(mActionCreateTemplate, &QAction::triggered, this, &MainWindow::slotNewTemplate);
451

Laurent Montel's avatar
Laurent Montel committed
452
    mActionCopy = new QAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action", "&Copy..."), this);
Laurent Montel's avatar
Laurent Montel committed
453
    actions->addAction(QStringLiteral("copy"), mActionCopy);
454
    actions->setDefaultShortcut(mActionCopy, QKeySequence(Qt::SHIFT | Qt::Key_Insert));
Laurent Montel's avatar
Laurent Montel committed
455
    connect(mActionCopy, &QAction::triggered, this, &MainWindow::slotCopy);
456

Laurent Montel's avatar
Laurent Montel committed
457
    mActionModify = new QAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18nc("@action", "&Edit..."), this);
Laurent Montel's avatar
Laurent Montel committed
458
    actions->addAction(QStringLiteral("modify"), mActionModify);
459
    actions->setDefaultShortcut(mActionModify, QKeySequence(Qt::CTRL | Qt::Key_E));
Laurent Montel's avatar
Laurent Montel committed
460
    connect(mActionModify, &QAction::triggered, this, &MainWindow::slotModify);
461

Laurent Montel's avatar
Laurent Montel committed
462
    mActionDelete = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action", "&Delete"), this);
Laurent Montel's avatar
Laurent Montel committed
463
    actions->addAction(QStringLiteral("delete"), mActionDelete);
David Jarvie's avatar
Spacing    
David Jarvie committed
464
    actions->setDefaultShortcut(mActionDelete, QKeySequence::Delete);
Laurent Montel's avatar
Laurent Montel committed
465
    connect(mActionDelete, &QAction::triggered, this, &MainWindow::slotDeleteIf);
466
467

    // Set up Shift-Delete as a shortcut to delete without confirmation
Laurent Montel's avatar
Laurent Montel committed
468
    mActionDeleteForce = new QAction(i18nc("@action", "Delete Without Confirmation"), this);
Laurent Montel's avatar
Laurent Montel committed
469
    actions->addAction(QStringLiteral("delete-force"), mActionDeleteForce);
470
    actions->setDefaultShortcut(mActionDeleteForce, QKeySequence::Delete | Qt::SHIFT);
Laurent Montel's avatar
Laurent Montel committed
471
    connect(mActionDeleteForce, &QAction::triggered, this, &MainWindow::slotDeleteForce);
472

Laurent Montel's avatar
Laurent Montel committed
473
    mActionReactivate = new QAction(i18nc("@action", "Reac&tivate"), this);
Laurent Montel's avatar
Laurent Montel committed
474
    actions->addAction(QStringLiteral("undelete"), mActionReactivate);
475
    actions->setDefaultShortcut(mActionReactivate, QKeySequence(Qt::CTRL | Qt::Key_R));
Laurent Montel's avatar
Laurent Montel committed
476
    connect(mActionReactivate, &QAction::triggered, this, &MainWindow::slotReactivate);
477

Laurent Montel's avatar
Laurent Montel committed
478
    mActionEnable = new QAction(this);
Laurent Montel's avatar
Laurent Montel committed
479
    actions->addAction(QStringLiteral("disable"), mActionEnable);
480
    actions->setDefaultShortcut(mActionEnable, QKeySequence(Qt::CTRL | Qt::Key_B));
Laurent Montel's avatar
Laurent Montel committed
481
    connect(mActionEnable, &QAction::triggered, this, &MainWindow::slotEnable);
482

483
    QAction* action = new QAction(i18nc("@action", "Wake From Suspend..."), this);
Laurent Montel's avatar
Laurent Montel committed
484
    actions->addAction(QStringLiteral("wakeSuspend"), action);
Laurent Montel's avatar
Laurent Montel committed
485
    connect(action, &QAction::triggered, this, &MainWindow::slotWakeFromSuspend);
486

487
    action = KAlarm::createStopPlayAction(this);
Laurent Montel's avatar
Laurent Montel committed
488
    actions->addAction(QStringLiteral("stopAudio"), action);
489
    KGlobalAccel::setGlobalShortcut(action, QList<QKeySequence>());  // allow user to set a global shortcut
490
491

    mActionShowArchived = new KToggleAction(i18nc("@action", "Show Archi&ved Alarms"), this);
Laurent Montel's avatar
Laurent Montel committed
492
    actions->addAction(QStringLiteral("showArchivedAlarms"), mActionShowArchived);
493
    actions->setDefaultShortcut(mActionShowArchived, QKeySequence(Qt::CTRL | Qt::Key_P));
Laurent Montel's avatar
Laurent Montel committed
494
    connect(mActionShowArchived, &KToggleAction::triggered, this, &MainWindow::slotShowArchived);
495
496

    mActionToggleTrayIcon = new KToggleAction(i18nc("@action", "Show in System &Tray"), this);
Laurent Montel's avatar
Laurent Montel committed
497
    actions->addAction(QStringLiteral("showInSystemTray"), mActionToggleTrayIcon);
Laurent Montel's avatar
Laurent Montel committed
498
    connect(mActionToggleTrayIcon, &KToggleAction::triggered, this, &MainWindow::slotToggleTrayIcon);
499

Laurent Montel's avatar
Laurent Montel committed
500
    mActionToggleResourceSel = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-choose")), i18nc("@action", "Show &Calendars"), this);
Laurent Montel's avatar
Laurent Montel committed
501
    actions->addAction(QStringLiteral("showResources"), mActionToggleResourceSel);
Laurent Montel's avatar
Laurent Montel committed
502
    connect(mActionToggleResourceSel, &KToggleAction::triggered, this, &MainWindow::slotToggleResourceSelector);
503

504
    mActionToggleDateNavigator = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-calendar-month")), i18nc("@action", "Show Date Selector"), this);
505
    actions->addAction(QStringLiteral("showDateSelector"), mActionToggleDateNavigator);
506
507
    connect(mActionToggleDateNavigator, &KToggleAction::triggered, this, &MainWindow::slotToggleDateNavigator);

508
    mActionSpreadWindows = KAlarm::createSpreadWindowsAction(this);
Laurent Montel's avatar
Laurent Montel committed
509
    actions->addAction(QStringLiteral("spread"), mActionSpreadWindows);
510
    KGlobalAccel::setGlobalShortcut(mActionSpreadWindows, QList<QKeySequence>());  // allow user to set a global shortcut
511

Laurent Montel's avatar
Laurent Montel committed
512
    mActionImportAlarms = new QAction(i18nc("@action", "Import &Alarms..."), this);
Laurent Montel's avatar
Laurent Montel committed
513
    actions->addAction(QStringLiteral("importAlarms"), mActionImportAlarms);
Laurent Montel's avatar
Laurent Montel committed
514
    connect(mActionImportAlarms, &QAction::triggered, this, &MainWindow::slotImportAlarms);
515

Laurent Montel's avatar
Laurent Montel committed
516
    mActionImportBirthdays = new QAction(i18nc("@action", "Import &Birthdays..."), this);
Laurent Montel's avatar
Laurent Montel committed
517
    actions->addAction(QStringLiteral("importBirthdays"), mActionImportBirthdays);
Laurent Montel's avatar
Laurent Montel committed
518
    connect(mActionImportBirthdays, &QAction::triggered, this, &MainWindow::slotBirthdays);
519

Laurent Montel's avatar
Laurent Montel committed
520
    mActionExportAlarms = new QAction(i18nc("@action", "E&xport Selected Alarms..."), this);
Laurent Montel's avatar
Laurent Montel committed
521
    actions->addAction(QStringLiteral("exportAlarms"), mActionExportAlarms);
Laurent Montel's avatar
Laurent Montel committed
522
    connect(mActionExportAlarms, &QAction::triggered, this, &MainWindow::slotExportAlarms);
523

Laurent Montel's avatar
Laurent Montel committed
524
    mActionExport = new QAction(i18nc("@action", "E&xport..."), this);
Laurent Montel's avatar
Laurent Montel committed
525
    actions->addAction(QStringLiteral("export"), mActionExport);
Laurent Montel's avatar
Laurent Montel committed
526
    connect(mActionExport, &QAction::triggered, this, &MainWindow::slotExportAlarms);
527

Laurent Montel's avatar
Laurent Montel committed
528
    action = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18nc("@action", "&Refresh Alarms"), this);
Laurent Montel's avatar
Laurent Montel committed
529
    actions->addAction(QStringLiteral("refreshAlarms"), action);
Laurent Montel's avatar
Laurent Montel committed
530
    connect(action, &QAction::triggered, this, &MainWindow::slotRefreshAlarms);
531

David Jarvie's avatar
Spacing    
David Jarvie committed
532
    KToggleAction* toggleAction = KAlarm::createAlarmEnableAction(this);
Laurent Montel's avatar
Laurent Montel committed
533
    actions->addAction(QStringLiteral("alarmsEnable"), toggleAction);
534
535
536
    if (undoText.isNull())
    {
        // Get standard texts, etc., for Undo and Redo actions
Laurent Montel's avatar
Laurent Montel committed
537
        QAction* act = KStandardAction::undo(this, nullptr, actions);
Laurent Montel's avatar
Laurent Montel committed
538
        undoShortcut     = act->shortcuts();
539
        undoText         = act->text();
540
        undoTextStripped = KLocalizedString::removeAcceleratorMarker(undoText);
541
        delete act;
Laurent Montel's avatar
Laurent Montel committed
542
        act = KStandardAction::redo(this, nullptr, actions);
Laurent Montel's avatar
Laurent Montel committed
543
        redoShortcut     = act->shortcuts();
544
        redoText         = act->text();
545
        redoTextStripped = KLocalizedString::removeAcceleratorMarker(redoText);
546
547
        delete act;
    }
Laurent Montel's avatar
Laurent Montel committed
548
    mActionUndo = new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("edit-undo")), undoText, this);
Laurent Montel's avatar
Laurent Montel committed
549
    actions->addAction(QStringLiteral("edit_undo"), mActionUndo);
550
    actions->setDefaultShortcuts(mActionUndo, undoShortcut);
Laurent Montel's avatar
Laurent Montel committed
551
    connect(mActionUndo, &KToolBarPopupAction::triggered, this, &MainWindow::slotUndo);
552

Laurent Montel's avatar
Laurent Montel committed
553
    mActionRedo = new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("edit-redo")), redoText, this);
Laurent Montel's avatar
Laurent Montel committed
554
    actions->addAction(QStringLiteral("edit_redo"), mActionRedo);
555
    actions->setDefaultShortcuts(mActionRedo, redoShortcut);
Laurent Montel's avatar
Laurent Montel committed
556
    connect(mActionRedo, &KToolBarPopupAction::triggered, this, &MainWindow::slotRedo);
557

David Jarvie's avatar
David Jarvie committed
558
559
560
561
562
    KStandardAction::find(mListView, &EventListView::slotFind, actions);
    mActionFindNext = KStandardAction::findNext(mListView, &EventListView::slotFindNext, actions);
    mActionFindPrev = KStandardAction::findPrev(mListView, &EventListView::slotFindPrev, actions);
    KStandardAction::selectAll(mListView, &QTreeView::selectAll, actions);
    KStandardAction::deselect(mListView, &QAbstractItemView::clearSelection, actions);
563
564
    // Quit only once the event loop is called; otherwise, the parent window will
    // be deleted while still processing the action, resulting in a crash.
Laurent Montel's avatar
Laurent Montel committed
565
    QAction* act = KStandardAction::quit(nullptr, nullptr, actions);
Laurent Montel's avatar
Laurent Montel committed
566
    connect(act, &QAction::triggered, this, &MainWindow::slotQuit, Qt::QueuedConnection);
David Jarvie's avatar
David Jarvie committed
567
    KStandardAction::keyBindings(this, &MainWindow::slotConfigureKeys, actions);
568
    KStandardAction::configureNotifications(this, &MainWindow::slotConfigureNotifications, actions);
David Jarvie's avatar
David Jarvie committed
569
570
    KStandardAction::configureToolbars(this, &MainWindow::slotConfigureToolbar, actions);
    KStandardAction::preferences(this, &MainWindow::slotPreferences, actions);
571
572
573
574
575
576
577
578
579
    mActionShowMenuBar = KStandardAction::showMenubar(this, &MainWindow::slotToggleMenubar, actions);
    mHamburgerMenu = KStandardAction::hamburgerMenu(nullptr, nullptr, actions);
    mHamburgerMenu->setShowMenuBarAction(mActionShowMenuBar);
    mHamburgerMenu->setMenuBar(menuBar());
    connect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, [this]() {
        slotInitHamburgerMenu();
        // Needs to be run on demand, but the contents won't change, so disconnect now.
        disconnect(mHamburgerMenu, &KHamburgerMenu::aboutToShowMenu, this, nullptr);
    });
580
581
582
583
    mResourceSelector->initActions(actions);
    setStandardToolBarMenuEnabled(true);
    createGUI(UI_FILE);
    // Load menu and toolbar settings
584
    applyMainWindowSettings(KSharedConfig::openConfig()->group(WINDOW_NAME));
585

David Jarvie's avatar
David Jarvie committed
586
    mContextMenu = static_cast<QMenu*>(factory()->container(QStringLiteral("listContext"), this));
587
588
    QMenu* actionsMenu = static_cast<QMenu*>(factory()->container(QStringLiteral("actions"), this));
    mMenuError = (!mContextMenu  ||  !actionsMenu  ||  !resourceContextMenu());
589
590
591
592
593
    connect(mActionUndo->menu(), &QMenu::aboutToShow, this, &MainWindow::slotInitUndoMenu);
    connect(mActionUndo->menu(), &QMenu::triggered, this, &MainWindow::slotUndoItem);
    connect(mActionRedo->menu(), &QMenu::aboutToShow, this, &MainWindow::slotInitRedoMenu);
    connect(mActionRedo->menu(), &QMenu::triggered, this, &MainWindow::slotRedoItem);
    connect(Undo::instance(), &Undo::changed, this, &MainWindow::slotUndoStatus);
Laurent Montel's avatar
Laurent Montel committed
594
    connect(mListView, &AlarmListView::findActive, this, &MainWindow::slotFindActive);
595
596
    Preferences::connect(&Preferences::archivedKeepDaysChanged, this, &MainWindow::updateKeepArchived);
    Preferences::connect(&Preferences::showInSystemTrayChanged, this, &MainWindow::updateTrayIconAction);
597
    connect(theApp(), &KAlarmApp::trayIconToggled, this, &MainWindow::updateTrayIconAction);
598
599
600
601
602
603
604

    // Set menu item states
    setEnableText(true);
    mActionShowArchived->setChecked(mShowArchived);
    if (!Preferences::archivedKeepDays())
        mActionShowArchived->setEnabled(false);
    mActionToggleResourceSel->setChecked(mShowResources);
605
606
607
    mActionToggleDateNavigator->setChecked(mShowDateNavigator);
    slotToggleResourceSelector();   // give priority to resource selector over date navigator
    slotToggleDateNavigator();
608
609
610
611
612
613
614
615
616
617
618
619
620
621
    updateTrayIconAction();         // set the correct text for this action
    mActionUndo->setEnabled(Undo::haveUndo());
    mActionRedo->setEnabled(Undo::haveRedo());
    mActionFindNext->setEnabled(false);
    mActionFindPrev->setEnabled(false);

    mActionCopy->setEnabled(false);
    mActionModify->setEnabled(false);
    mActionDelete->setEnabled(false);
    mActionReactivate->setEnabled(false);
    mActionEnable->setEnabled(false);
    mActionCreateTemplate->setEnabled(false);
    mActionExport->setEnabled(false);

622
623
    mActionShowMenuBar->setChecked(Preferences::showMenuBar());
    slotToggleMenubar(true);
624

625
    Undo::emitChanged();     // set the Undo/Redo menu texts
626
627
}

628
629
630
631
632
633
/******************************************************************************
* Set up the Hamburger menu, which allows the menu bar to be easily reinstated
* after it has been hidden.
*/
void MainWindow::slotInitHamburgerMenu()
{
634
    QMenu* menu = new QMenu(this);
635
636
637
638
639
640
641
642
643
644
645
646
647
    KActionCollection* actions = actionCollection();
    menu->addAction(actions->action(QStringLiteral("new")));
    menu->addAction(actions->action(QStringLiteral("templates")));
    menu->addSeparator();
    menu->addAction(actions->action(QStringLiteral("edit_find")));
    menu->addSeparator();
    menu->addAction(actions->action(QStringLiteral("showArchivedAlarms")));
    menu->addAction(actions->action(QStringLiteral("showDateSelector")));
    menu->addSeparator();
    menu->addAction(actions->action(QLatin1String(KStandardAction::name(KStandardAction::Quit))));
    mHamburgerMenu->setMenu(menu);
}

David Jarvie's avatar
David Jarvie committed
648
649
650
/******************************************************************************
* Enable or disable the Templates menu item in every main window instance.
*/
651
void MainWindow::enableTemplateMenuItem(bool enable)
David Jarvie's avatar
David Jarvie committed
652
{
653
654
    for (int i = 0, end = mWindowList.count();  i < end;  ++i)
        mWindowList[i]->mActionTemplates->setEnabled(enable);
David Jarvie's avatar
David Jarvie committed
655
656
}

657
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
658
* Refresh the alarm list in every main window instance.
659
*/
660
void MainWindow::refresh()
661
{
662
    qCDebug(KALARM_LOG) << "MainWindow::refresh";
663
    DataModel::reload();
664
665
}

David Jarvie's avatar
David Jarvie committed
666
/******************************************************************************
667
668
* Called when the keep archived alarm setting changes in the user preferences.
* Enable/disable Show archived alarms option.
David Jarvie's avatar
David Jarvie committed
669
*/
670
void MainWindow::updateKeepArchived(int days)
David Jarvie's avatar
David Jarvie committed
671
{
672
    qCDebug(KALARM_LOG) << "MainWindow::updateKeepArchived:" << (bool)days;
673
674
675
    if (mShowArchived  &&  !days)
        slotShowArchived();   // toggle Show Archived option setting
    mActionShowArchived->setEnabled(days);
David Jarvie's avatar
David Jarvie committed
676
677
}

David Jarvie's avatar
David Jarvie committed
678
679
680
/******************************************************************************
* Select an alarm in the displayed list.
*/
681
void MainWindow::selectEvent(const QString& eventId)
David Jarvie's avatar
David Jarvie committed
682
{
683
    mListView->clearSelection();
684
    const QModelIndex index = mListFilterModel->eventIndex(eventId);
685
686
687
688
689
    if (index.isValid())
    {
        mListView->select(index);
        mListView->scrollTo(index);
    }
David Jarvie's avatar
David Jarvie committed
690
691
}

692
693
694
/******************************************************************************
* Return the single selected alarm in the displayed list.
*/
695
696
697
KAEvent MainWindow::selectedEvent() const
{
    return mListView->selectedEvent();
698
699
}

700
701
702
703
704
705
706
707
/******************************************************************************
* Deselect all alarms in the displayed list.
*/
void MainWindow::clearSelection()
{
    mListView->clearSelection();
}

708
709
710
711
712
713
714
715
716
717
718
719
/******************************************************************************
* Provide the context menu for the resource selector to use.
*/
QMenu* MainWindow::resourceContextMenu()
{
    // Recreate the resource selector context menu if it has been deleted
    // (which happens if the toolbar is edited).
    if (!mResourceContextMenu)
        mResourceContextMenu = static_cast<QMenu*>(factory()->container(QStringLiteral("resourceContext"), this));
    return mResourceContextMenu;
}

720
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
721
* Called when the New button is clicked to edit a new alarm to add to the list.
722
*/
David Jarvie's avatar
David Jarvie committed
723
void MainWindow::slotNew(EditAlarmDlg::Type type)
724
{
725
    KAlarm::editNewAlarm(type, mListView);
David Jarvie's avatar
David Jarvie committed
726
727
}

728
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
729
730
* Called when a template is selected from the New From Template popup menu.
* Executes a New Alarm dialog, preset from the selected template.
731
*/
732
void MainWindow::slotNewFromTemplate(const KAEvent& tmplate)
733
{
734
    KAlarm::editNewAlarm(tmplate, mListView);
735
736
}

David Jarvie's avatar
David Jarvie committed
737
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
738
739
* Called when the New Template button is clicked to create a new template
* based on the currently selected alarm.
David Jarvie's avatar
David Jarvie committed
740
*/
741
void MainWindow::slotNewTemplate()
David Jarvie's avatar
David Jarvie committed
742
{
743
744
    KAEvent event = mListView->selectedEvent();
    if (event.isValid())
745
        KAlarm::editNewTemplate(event, this);
746
747
}

David Jarvie's avatar
David Jarvie committed
748
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
749
750
* Called when the Copy button is clicked to edit a copy of an existing alarm,
* to add to the list.
David Jarvie's avatar
David Jarvie committed
751
*/
752
void MainWindow::slotCopy()
David Jarvie's avatar
David Jarvie committed
753
{
754
755
    KAEvent event = mListView->selectedEvent();
    if (event.isValid())
756
        KAlarm::editNewAlarm(event, this);
David Jarvie's avatar
David Jarvie committed
757
758
}

759
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
760
761
* Called when the Modify button is clicked to edit the currently highlighted
* alarm in the list.
762
*/
763
void MainWindow::slotModify()
764
{
765
766
    KAEvent event = mListView->selectedEvent();
    if (event.isValid())
767
        KAlarm::editAlarm(event, this);   // edit alarm (view-only mode if archived or read-only)
David Jarvie's avatar
David Jarvie committed
768
769
}

770
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
771
772
* Called when the Delete button is clicked to delete the currently highlighted
* alarms in the list.
773
*/
774
void MainWindow::slotDelete(bool force)
775
{
776
    QVector<KAEvent> events = mListView->selectedEvents();
777
778
779
    if (!force  &&  Preferences::confirmAlarmDeletion())
    {
        int n = events.count();
780
781
782
        if (KAMessageBox::warningContinueCancel(this, i18ncp("@info", "Do you really want to delete the selected alarm?",
                                                             "Do you really want to delete the %1 selected alarms?", n),
                                                i18ncp("@title:window", "Delete Alarm", "Delete Alarms", n),
Laurent Montel's avatar
Laurent Montel committed
783
                                                KGuiItem(i18nc("@action:button", "&Delete"), QStringLiteral("edit-delete")),
784
                                                KStandardGuiItem::cancel(),
David Jarvie's avatar
David Jarvie committed
785
                                                Preferences::CONFIRM_ALARM_DELETION)
786
787
788
789
790
791
            != KMessageBox::Continue)
            return;
    }

    // Remove any events which have just triggered, from the list to delete.
    Undo::EventList undos;
792
    for (int i = 0;  i < events.count();  )
793
    {
794
        Resource res = Resources::resourceForEvent(events[i].id());
795
        if (!res.isValid())
796
            events.remove(i);
797
        else
798
            undos.append(events[i++], res);
799
    }
800

801
    if (events.isEmpty())
802
        qCDebug(KALARM_LOG) << "MainWindow::slotDelete: No alarms left to delete";
803
804
805
    else
    {
        // Delete the events from the calendar and displays
806
807
808
809
810
811
812
813
814
        Resource resource;
        const KAlarm::UpdateResult status = KAlarm::deleteEvents(events, resource, true, this);
        if (status.status < KAlarm::UPDATE_FAILED)
        {
            // Create the undo list
            for (int i = status.failed.count();  --i >= 0; )
                undos.removeAt(status.failed.at(i));
            Undo::saveDeletes(undos);
        }
815
    }
816
817
}

David Jarvie's avatar
David Jarvie committed
818
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
819
820
* Called when the Reactivate button is clicked to reinstate the currently
* highlighted archived alarms in the list.
David Jarvie's avatar
David Jarvie committed
821
*/
822
void MainWindow::slotReactivate()
David Jarvie's avatar
David Jarvie committed
823
{
824
    QVector<KAEvent> events = mListView->selectedEvents();
825
    mListView->clearSelection();
826

827
    // Add the alarms to the displayed lists and to the calendar file
828
829
830
831
    Resource resource;   // active alarms resource which alarms are restored to
    QVector<int> ineligibleIndexes;
    const KAlarm::UpdateResult status = KAlarm::reactivateEvents(events, ineligibleIndexes, resource, this);
    if (status.status < KAlarm::UPDATE_FAILED)
832
    {
833
834
835
836
837
838
839
840
        // Create the undo list, excluding ineligible events
        Undo::EventList undos;
        for (int i = 0, end = events.count();  i < end;  ++i)
        {
            if (!ineligibleIndexes.contains(i)  &&  !status.failed.contains(i))
                undos.append(events[i], resource);
        }
        Undo::saveReactivates(undos);
841
    }
David Jarvie's avatar
David Jarvie committed
842
843
}

844
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
845
846
* Called when the Enable/Disable button is clicked to enable or disable the
* currently highlighted alarms in the list.
847
*/
848
void MainWindow::slotEnable()
849
{
850
    bool enable = mActionEnableEnable;    // save since changed in response to KAlarm::enableEvent()
David Jarvie's avatar
David Jarvie committed
851
    const QVector<KAEvent> events = mListView->selectedEvents();
852
    QVector<KAEvent> eventCopies;
David Jarvie's avatar
David Jarvie committed
853
854
855
    eventCopies.reserve(events.count());
    for (const KAEvent& event : events)
        eventCopies += event;
856
857
    KAlarm::enableEvents(eventCopies, enable, this);
    slotSelection();   // update Enable/Disable action text
858
859
}

860
/******************************************************************************