mainwindow.cpp 58.5 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
14
15
16

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

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

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

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

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

74
75
using namespace KAlarmCal;

76
77
//clazy:excludeall=non-pod-global-static

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

const char*   VIEW_GROUP         = "View";
84
const char*   SHOW_COLUMNS       = "ShowColumns";
David Jarvie's avatar
David Jarvie committed
85
86
87
88
89
90
91
92
93
94
const char*   SHOW_ARCHIVED_KEY  = "ShowArchivedAlarms";
const char*   SHOW_RESOURCES_KEY = "ShowResources";

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

96

97
/*=============================================================================
98
=  Class: MainWindow
99
=============================================================================*/
100

101
MainWindow::WindowList   MainWindow::mWindowList;
Laurent Montel's avatar
Laurent Montel committed
102
TemplateDlg*             MainWindow::mTemplateDlg = nullptr;
David Jarvie's avatar
David Jarvie committed
103

104

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

116
MainWindow::MainWindow(bool restored)
117
    : MainWindowBase(nullptr, Qt::WindowContextHelpButtonHint)
118
{
119
    qCDebug(KALARM_LOG) << "MainWindow:";
120
121
    setAttribute(Qt::WA_DeleteOnClose);
    setWindowModality(Qt::WindowModal);
David Jarvie's avatar
David Jarvie committed
122
    setObjectName(QStringLiteral("MainWin"));    // used by LikeBack
David Jarvie's avatar
David Jarvie committed
123
    setPlainCaption(KAboutData::applicationData().displayName());
124
    KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
125
126
    mShowResources = config.readEntry(SHOW_RESOURCES_KEY, false);
    mShowArchived  = config.readEntry(SHOW_ARCHIVED_KEY, false);
127
    const QList<bool> showColumns = config.readEntry(SHOW_COLUMNS, QList<bool>());
128
129
    if (!restored)
    {
130
        KConfigGroup wconfig(KSharedConfig::openConfig(), WINDOW_NAME);
David Jarvie's avatar
David Jarvie committed
131
        mResourcesWidth = wconfig.readEntry(QStringLiteral("Splitter %1").arg(QApplication::desktop()->width()), (int)0);
132
133
134
135
136
137
138
139
140
141
    }

    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
142
    DataModel::widgetNeedsDatabase(this);
143
144
145
    mResourceSelector = new ResourceSelector(mSplitter);
    mSplitter->setStretchFactor(0, 0);   // don't resize resource selector when window is resized
    mSplitter->setStretchFactor(1, 1);
David Jarvie's avatar
David Jarvie committed
146

147
    // Create the alarm list widget
148
    mListFilterModel = DataModel::createAlarmListModel(this);
149
    mListFilterModel->setEventTypeFilter(mShowArchived ? CalEvent::ACTIVE | CalEvent::ARCHIVED : CalEvent::ACTIVE);
150
151
    mListView = new AlarmListView(WINDOW_NAME, mSplitter);
    mListView->setModel(mListFilterModel);
152
    mListView->setColumnsVisible(showColumns);
153
    mListView->setItemDelegate(new AlarmListDelegate(mListView));
154
    connect(mListView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &MainWindow::slotSelection);
Laurent Montel's avatar
Laurent Montel committed
155
    connect(mListView, &AlarmListView::contextMenuRequested, this, &MainWindow::slotContextMenuRequested);
156
    connect(mListView, &AlarmListView::columnsVisibleChanged, this, &MainWindow::slotAlarmListColumnsChanged);
157
158
    connect(Resources::instance(), &Resources::settingsChanged,
                             this, &MainWindow::slotCalendarStatusChanged);
Laurent Montel's avatar
Laurent Montel committed
159
    connect(mResourceSelector, &ResourceSelector::resized, this, &MainWindow::resourcesResized);
160
161
162
    mListView->installEventFilter(this);
    initActions();

Laurent Montel's avatar
Laurent Montel committed
163
    setAutoSaveSettings(QLatin1String(WINDOW_NAME), true);    // save toolbars, window sizes etc.
164
165
166
167
168
169
170
171
172
173
    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
174
175
}

176
MainWindow::~MainWindow()
177
{
178
    qCDebug(KALARM_LOG) << "~MainWindow";
179
180
    bool trayParent = isTrayParent();   // must call before removing from window list
    mWindowList.removeAt(mWindowList.indexOf(this));
David Jarvie's avatar
David Jarvie committed
181

182
183
    // Prevent view updates during window destruction
    delete mResourceSelector;
Laurent Montel's avatar
Laurent Montel committed
184
    mResourceSelector = nullptr;
185
    delete mListView;
Laurent Montel's avatar
Laurent Montel committed
186
    mListView = nullptr;
David Jarvie's avatar
David Jarvie committed
187

188
189
190
191
192
193
194
    if (theApp()->trayWindow())
    {
        if (trayParent)
            delete theApp()->trayWindow();
        else
            theApp()->trayWindow()->removeWindow(this);
    }
195
    KSharedConfig::openConfig()->sync();    // save any new window size to disc
196
    theApp()->quitIf();
197
198
}

199
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
200
* Called when the QApplication::saveStateRequest() signal has been emitted.
201
202
203
* Save settings to the session managed config file, for restoration
* when the program is restored.
*/
David Jarvie's avatar
David Jarvie committed
204
void MainWindow::saveProperties(KConfigGroup& config)
205
{
206
207
    config.writeEntry("HiddenTrayParent", isTrayParent() && isHidden());
    config.writeEntry("ShowArchived", mShowArchived);
208
    config.writeEntry("ShowColumns", mListView->columnsVisible());
209
    config.writeEntry("ResourcesWidth", mResourceSelector->isHidden() ? 0 : mResourceSelector->width());
210
211
212
213
214
215
216
}

/******************************************************************************
* 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
217
void MainWindow::readProperties(const KConfigGroup& config)
218
{
219
220
221
222
    mHiddenTrayParent = config.readEntry("HiddenTrayParent", true);
    mShowArchived     = config.readEntry("ShowArchived", false);
    mResourcesWidth   = config.readEntry("ResourcesWidth", (int)0);
    mShowResources    = (mResourcesWidth > 0);
223
    mListView->setColumnsVisible(config.readEntry("ShowColumns", QList<bool>()));
224
225
226
}

/******************************************************************************
David Jarvie's avatar
David Jarvie committed
227
228
229
230
* 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.
*/
231
MainWindow* MainWindow::mainMainWindow()
David Jarvie's avatar
David Jarvie committed
232
{
233
    MainWindow* tray = (theApp() && theApp()->trayWindow()) ? theApp()->trayWindow()->assocMainWindow() : nullptr;
234
235
236
    if (tray  &&  tray->isVisible())
        return tray;
    for (int i = 0, end = mWindowList.count();  i < end;  ++i)
237
238
        if (mWindowList.at(i)->isVisible())
            return mWindowList.at(i);
239
240
241
    if (tray)
        return tray;
    if (mWindowList.isEmpty())
Laurent Montel's avatar
Laurent Montel committed
242
        return nullptr;
243
    return mWindowList.at(0);
David Jarvie's avatar
David Jarvie committed
244
245
246
}

/******************************************************************************
247
* Check whether this main window is effectively the parent of the system tray icon.
248
*/
249
bool MainWindow::isTrayParent() const
250
{
251
    TrayWindow* tray = theApp()->trayWindow();
David Jarvie's avatar
David Jarvie committed
252
    if (!tray  ||  !QSystemTrayIcon::isSystemTrayAvailable())
253
254
255
256
        return false;
    if (tray->assocMainWindow() == this)
        return true;
    return mWindowList.count() == 1;
257
258
}

259
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
260
* Close all main windows.
261
*/
262
void MainWindow::closeAll()
263
{
264
265
    while (!mWindowList.isEmpty())
        delete mWindowList[0];    // N.B. the destructor removes the window from the list
266
267
}

268
269
270
271
272
/******************************************************************************
* Intercept events for the splitter widget.
*/
bool MainWindow::eventFilter(QObject* obj, QEvent* e)
{
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
    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:
            {
296
                auto ke = static_cast<QKeyEvent*>(e);
297
298
299
300
301
302
303
304
305
306
307
308
309
                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;
310
311
}

312
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
313
314
315
316
* 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.
317
*/
318
void MainWindow::resizeEvent(QResizeEvent* re)
319
{
320
321
322
323
324
325
326
327
328
    // Save the window's new size only if it's the first main window
    MainWindowBase::resizeEvent(re);
    if (mResourcesWidth > 0)
    {
        QList<int> widths;
        widths.append(mResourcesWidth);
        widths.append(width() - mResourcesWidth - mSplitter->handleWidth());
        mSplitter->setSizes(widths);
    }
329
330
}

David Jarvie's avatar
David Jarvie committed
331
332
333
334
/******************************************************************************
* Called when the resources panel has been resized.
* Records the new size in the config file.
*/
David Jarvie's avatar
David Jarvie committed
335
336
void MainWindow::resourcesResized()
{
337
338
    if (!mShown  ||  mResizing)
        return;
David Jarvie's avatar
David Jarvie committed
339
    const QList<int> widths = mSplitter->sizes();
340
341
342
343
344
345
346
347
348
    if (widths.count() > 1)
    {
        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)
        {
349
            KConfigGroup config(KSharedConfig::openConfig(), WINDOW_NAME);
David Jarvie's avatar
David Jarvie committed
350
            config.writeEntry(QStringLiteral("Splitter %1").arg(QApplication::desktop()->width()), mResourcesWidth);
351
            config.sync();
352
353
        }
    }
David Jarvie's avatar
David Jarvie committed
354
355
}

356
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
357
358
359
* 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.
360
*/
361
void MainWindow::showEvent(QShowEvent* se)
362
{
363
364
365
366
367
368
369
370
371
    if (mResourcesWidth > 0)
    {
        QList<int> widths;
        widths.append(mResourcesWidth);
        widths.append(width() - mResourcesWidth - mSplitter->handleWidth());
        mSplitter->setSizes(widths);
    }
    MainWindowBase::showEvent(se);
    mShown = true;
372
373

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

377
/******************************************************************************
378
379
380
* 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.
381
*/
382
void MainWindow::showMenuErrorMessage()
383
{
384
385
    if (mMenuError)
    {
Laurent Montel's avatar
Laurent Montel committed
386
        KAMessageBox::error(this, xi18nc("@info", "Failure to create menus (perhaps <filename>%1</filename> missing or corrupted)", UI_FILE));
387
388
        mMenuError = false;
    }
389
390
}

391
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
392
* Called after the window is hidden.
393
*/
394
void MainWindow::hideEvent(QHideEvent* he)
395
{
396
    MainWindowBase::hideEvent(he);
397
398
}

399
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
400
* Initialise the menu, toolbar and main window actions.
401
*/
402
void MainWindow::initActions()
403
{
404
405
    KActionCollection* actions = actionCollection();

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

410
    mActionNew = new NewAlarmAction(false, i18nc("@action", "&New"), this, actions);
411
    mActionNew->setActionNames(QStringLiteral("newDisplay"), QStringLiteral("newCommand"), QStringLiteral("newEmail"), QStringLiteral("newAudio"), QStringLiteral("newFromTemplate"));
Laurent Montel's avatar
Laurent Montel committed
412
    actions->addAction(QStringLiteral("new"), mActionNew);
413
414
    connect(mActionNew, &NewAlarmAction::selected, this, &MainWindow::slotNew);
    connect(mActionNew, &NewAlarmAction::selectedTemplate, this, &MainWindow::slotNewFromTemplate);
415

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

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

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

Laurent Montel's avatar
Laurent Montel committed
430
    mActionDelete = new QAction(QIcon::fromTheme(QStringLiteral("edit-delete")), i18nc("@action", "&Delete"), this);
Laurent Montel's avatar
Laurent Montel committed
431
    actions->addAction(QStringLiteral("delete"), mActionDelete);
David Jarvie's avatar
Spacing    
David Jarvie committed
432
    actions->setDefaultShortcut(mActionDelete, QKeySequence::Delete);
Laurent Montel's avatar
Laurent Montel committed
433
    connect(mActionDelete, &QAction::triggered, this, &MainWindow::slotDeleteIf);
434
435

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

Laurent Montel's avatar
Laurent Montel committed
441
    mActionReactivate = new QAction(i18nc("@action", "Reac&tivate"), this);
Laurent Montel's avatar
Laurent Montel committed
442
    actions->addAction(QStringLiteral("undelete"), mActionReactivate);
443
    actions->setDefaultShortcut(mActionReactivate, QKeySequence(Qt::CTRL | Qt::Key_R));
Laurent Montel's avatar
Laurent Montel committed
444
    connect(mActionReactivate, &QAction::triggered, this, &MainWindow::slotReactivate);
445

Laurent Montel's avatar
Laurent Montel committed
446
    mActionEnable = new QAction(this);
Laurent Montel's avatar
Laurent Montel committed
447
    actions->addAction(QStringLiteral("disable"), mActionEnable);
448
    actions->setDefaultShortcut(mActionEnable, QKeySequence(Qt::CTRL | Qt::Key_B));
Laurent Montel's avatar
Laurent Montel committed
449
    connect(mActionEnable, &QAction::triggered, this, &MainWindow::slotEnable);
450

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

455
    action = KAlarm::createStopPlayAction(this);
Laurent Montel's avatar
Laurent Montel committed
456
    actions->addAction(QStringLiteral("stopAudio"), action);
457
    KGlobalAccel::setGlobalShortcut(action, QList<QKeySequence>());  // allow user to set a global shortcut
458
459

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

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

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

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

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

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

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

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

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

David Jarvie's avatar
Spacing    
David Jarvie committed
496
    KToggleAction* toggleAction = KAlarm::createAlarmEnableAction(this);
Laurent Montel's avatar
Laurent Montel committed
497
    actions->addAction(QStringLiteral("alarmsEnable"), toggleAction);
498
499
500
    if (undoText.isNull())
    {
        // Get standard texts, etc., for Undo and Redo actions
Laurent Montel's avatar
Laurent Montel committed
501
        QAction* act = KStandardAction::undo(this, nullptr, actions);
Laurent Montel's avatar
Laurent Montel committed
502
        undoShortcut     = act->shortcuts();
503
        undoText         = act->text();
504
        undoTextStripped = KLocalizedString::removeAcceleratorMarker(undoText);
505
        delete act;
Laurent Montel's avatar
Laurent Montel committed
506
        act = KStandardAction::redo(this, nullptr, actions);
Laurent Montel's avatar
Laurent Montel committed
507
        redoShortcut     = act->shortcuts();
508
        redoText         = act->text();
509
        redoTextStripped = KLocalizedString::removeAcceleratorMarker(redoText);
510
511
        delete act;
    }
Laurent Montel's avatar
Laurent Montel committed
512
    mActionUndo = new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("edit-undo")), undoText, this);
Laurent Montel's avatar
Laurent Montel committed
513
    actions->addAction(QStringLiteral("edit_undo"), mActionUndo);
514
    actions->setDefaultShortcuts(mActionUndo, undoShortcut);
Laurent Montel's avatar
Laurent Montel committed
515
    connect(mActionUndo, &KToolBarPopupAction::triggered, this, &MainWindow::slotUndo);
516

Laurent Montel's avatar
Laurent Montel committed
517
    mActionRedo = new KToolBarPopupAction(QIcon::fromTheme(QStringLiteral("edit-redo")), redoText, this);
Laurent Montel's avatar
Laurent Montel committed
518
    actions->addAction(QStringLiteral("edit_redo"), mActionRedo);
519
    actions->setDefaultShortcuts(mActionRedo, redoShortcut);
Laurent Montel's avatar
Laurent Montel committed
520
    connect(mActionRedo, &KToolBarPopupAction::triggered, this, &MainWindow::slotRedo);
521

David Jarvie's avatar
David Jarvie committed
522
523
524
525
526
    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);
527
528
    // 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
529
    QAction* act = KStandardAction::quit(nullptr, nullptr, actions);
Laurent Montel's avatar
Laurent Montel committed
530
    connect(act, &QAction::triggered, this, &MainWindow::slotQuit, Qt::QueuedConnection);
David Jarvie's avatar
David Jarvie committed
531
532
    QAction* actionMenubar = KStandardAction::showMenubar(this, &MainWindow::slotShowMenubar, actions);
    KStandardAction::keyBindings(this, &MainWindow::slotConfigureKeys, actions);
533
    KStandardAction::configureNotifications(this, &MainWindow::slotConfigureNotifications, actions);
David Jarvie's avatar
David Jarvie committed
534
535
    KStandardAction::configureToolbars(this, &MainWindow::slotConfigureToolbar, actions);
    KStandardAction::preferences(this, &MainWindow::slotPreferences, actions);
536
537
538
539
    mResourceSelector->initActions(actions);
    setStandardToolBarMenuEnabled(true);
    createGUI(UI_FILE);
    // Load menu and toolbar settings
540
    applyMainWindowSettings(KSharedConfig::openConfig()->group(WINDOW_NAME));
541

David Jarvie's avatar
David Jarvie committed
542
543
544
    mContextMenu = static_cast<QMenu*>(factory()->container(QStringLiteral("listContext"), this));
    mActionsMenu = static_cast<QMenu*>(factory()->container(QStringLiteral("actions"), this));
    QMenu* resourceMenu = static_cast<QMenu*>(factory()->container(QStringLiteral("resourceContext"), this));
545
546
    mResourceSelector->setContextMenu(resourceMenu);
    mMenuError = (!mContextMenu  ||  !mActionsMenu  ||  !resourceMenu);
547
548
549
550
551
    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
552
    connect(mListView, &AlarmListView::findActive, this, &MainWindow::slotFindActive);
553
554
    Preferences::connect(&Preferences::archivedKeepDaysChanged, this, &MainWindow::updateKeepArchived);
    Preferences::connect(&Preferences::showInSystemTrayChanged, this, &MainWindow::updateTrayIconAction);
555
    connect(theApp(), &KAlarmApp::trayIconToggled, this, &MainWindow::updateTrayIconAction);
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577

    // Set menu item states
    setEnableText(true);
    mActionShowArchived->setChecked(mShowArchived);
    if (!Preferences::archivedKeepDays())
        mActionShowArchived->setEnabled(false);
    mActionToggleResourceSel->setChecked(mShowResources);
    slotToggleResourceSelector();
    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);

578
579
580
    const bool menuVisible = !menuBar()->isHidden();
    actionMenubar->setChecked(menuVisible);

581
582
    Undo::emitChanged();     // set the Undo/Redo menu texts
//    Daemon::monitoringAlarms();
583
584
}

David Jarvie's avatar
David Jarvie committed
585
586
587
/******************************************************************************
* Enable or disable the Templates menu item in every main window instance.
*/
588
void MainWindow::enableTemplateMenuItem(bool enable)
David Jarvie's avatar
David Jarvie committed
589
{
590
591
    for (int i = 0, end = mWindowList.count();  i < end;  ++i)
        mWindowList[i]->mActionTemplates->setEnabled(enable);
David Jarvie's avatar
David Jarvie committed
592
593
}

594
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
595
* Refresh the alarm list in every main window instance.
596
*/
597
void MainWindow::refresh()
598
{
599
    qCDebug(KALARM_LOG) << "MainWindow::refresh";
600
    DataModel::reload();
601
602
}

David Jarvie's avatar
David Jarvie committed
603
/******************************************************************************
604
605
* 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
606
*/
607
void MainWindow::updateKeepArchived(int days)
David Jarvie's avatar
David Jarvie committed
608
{
609
    qCDebug(KALARM_LOG) << "MainWindow::updateKeepArchived:" << (bool)days;
610
611
612
    if (mShowArchived  &&  !days)
        slotShowArchived();   // toggle Show Archived option setting
    mActionShowArchived->setEnabled(days);
David Jarvie's avatar
David Jarvie committed
613
614
}

David Jarvie's avatar
David Jarvie committed
615
616
617
/******************************************************************************
* Select an alarm in the displayed list.
*/
618
void MainWindow::selectEvent(const QString& eventId)
David Jarvie's avatar
David Jarvie committed
619
{
620
    mListView->clearSelection();
621
    const QModelIndex index = mListFilterModel->eventIndex(eventId);
622
623
624
625
626
    if (index.isValid())
    {
        mListView->select(index);
        mListView->scrollTo(index);
    }
David Jarvie's avatar
David Jarvie committed
627
628
}

629
630
631
/******************************************************************************
* Return the single selected alarm in the displayed list.
*/
632
633
634
KAEvent MainWindow::selectedEvent() const
{
    return mListView->selectedEvent();
635
636
}

637
638
639
640
641
642
643
644
/******************************************************************************
* Deselect all alarms in the displayed list.
*/
void MainWindow::clearSelection()
{
    mListView->clearSelection();
}

645
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
646
* Called when the New button is clicked to edit a new alarm to add to the list.
647
*/
David Jarvie's avatar
David Jarvie committed
648
void MainWindow::slotNew(EditAlarmDlg::Type type)
649
{
650
    KAlarm::editNewAlarm(type, mListView);
David Jarvie's avatar
David Jarvie committed
651
652
}

653
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
654
655
* Called when a template is selected from the New From Template popup menu.
* Executes a New Alarm dialog, preset from the selected template.
656
*/
657
void MainWindow::slotNewFromTemplate(const KAEvent& tmplate)
658
{
659
    KAlarm::editNewAlarm(tmplate, mListView);
660
661
}

David Jarvie's avatar
David Jarvie committed
662
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
663
664
* 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
665
*/
666
void MainWindow::slotNewTemplate()
David Jarvie's avatar
David Jarvie committed
667
{
668
669
    KAEvent event = mListView->selectedEvent();
    if (event.isValid())
670
        KAlarm::editNewTemplate(event, this);
671
672
}

David Jarvie's avatar
David Jarvie committed
673
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
674
675
* 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
676
*/
677
void MainWindow::slotCopy()
David Jarvie's avatar
David Jarvie committed
678
{
679
680
    KAEvent event = mListView->selectedEvent();
    if (event.isValid())
681
        KAlarm::editNewAlarm(event, this);
David Jarvie's avatar
David Jarvie committed
682
683
}

684
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
685
686
* Called when the Modify button is clicked to edit the currently highlighted
* alarm in the list.
687
*/
688
void MainWindow::slotModify()
689
{
690
691
    KAEvent event = mListView->selectedEvent();
    if (event.isValid())
692
        KAlarm::editAlarm(event, this);   // edit alarm (view-only mode if archived or read-only)
David Jarvie's avatar
David Jarvie committed
693
694
}

695
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
696
697
* Called when the Delete button is clicked to delete the currently highlighted
* alarms in the list.
698
*/
699
void MainWindow::slotDelete(bool force)
700
{
701
    QVector<KAEvent> events = mListView->selectedEvents();
702
703
704
    if (!force  &&  Preferences::confirmAlarmDeletion())
    {
        int n = events.count();
705
706
707
        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
708
                                                KGuiItem(i18nc("@action:button", "&Delete"), QStringLiteral("edit-delete")),
709
                                                KStandardGuiItem::cancel(),
David Jarvie's avatar
David Jarvie committed
710
                                                Preferences::CONFIRM_ALARM_DELETION)
711
712
713
714
715
716
            != KMessageBox::Continue)
            return;
    }

    // Remove any events which have just triggered, from the list to delete.
    Undo::EventList undos;
717
    for (int i = 0;  i < events.count();  )
718
    {
719
        Resource res = Resources::resourceForEvent(events[i].id());
720
        if (!res.isValid())
721
            events.remove(i);
722
        else
723
            undos.append(events[i++], res);
724
    }
725

726
    if (events.isEmpty())
727
        qCDebug(KALARM_LOG) << "MainWindow::slotDelete: No alarms left to delete";
728
729
730
    else
    {
        // Delete the events from the calendar and displays
731
732
733
734
735
736
737
738
739
        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);
        }
740
    }
741
742
}

David Jarvie's avatar
David Jarvie committed
743
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
744
745
* Called when the Reactivate button is clicked to reinstate the currently
* highlighted archived alarms in the list.
David Jarvie's avatar
David Jarvie committed
746
*/
747
void MainWindow::slotReactivate()
David Jarvie's avatar
David Jarvie committed
748
{
749
    QVector<KAEvent> events = mListView->selectedEvents();
750
    mListView->clearSelection();
751

752
    // Add the alarms to the displayed lists and to the calendar file
753
754
755
756
    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)
757
    {
758
759
760
761
762
763
764
765
        // 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);
766
    }
David Jarvie's avatar
David Jarvie committed
767
768
}

769
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
770
771
* Called when the Enable/Disable button is clicked to enable or disable the
* currently highlighted alarms in the list.
772
*/
773
void MainWindow::slotEnable()
774
{
775
    bool enable = mActionEnableEnable;    // save since changed in response to KAlarm::enableEvent()
David Jarvie's avatar
David Jarvie committed
776
    const QVector<KAEvent> events = mListView->selectedEvents();
777
    QVector<KAEvent> eventCopies;
David Jarvie's avatar
David Jarvie committed
778
779
780
    eventCopies.reserve(events.count());
    for (const KAEvent& event : events)
        eventCopies += event;
781
782
    KAlarm::enableEvents(eventCopies, enable, this);
    slotSelection();   // update Enable/Disable action text
783
784
}

785
/******************************************************************************
786
* Called when the columns visible in the alarm list view have changed.
787
*/
788
void MainWindow::slotAlarmListColumnsChanged()
789
{
790
791
792
    KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
    config.writeEntry(SHOW_COLUMNS, mListView->columnsVisible());
    config.sync();
793
794
}

David Jarvie's avatar
David Jarvie committed
795
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
796
* Called when the Show Archived Alarms menu item is selected or deselected.
David Jarvie's avatar
David Jarvie committed
797
*/
798
void MainWindow::slotShowArchived()
David Jarvie's avatar
David Jarvie committed
799
{
800
801
802
803
    mShowArchived = !mShowArchived;
    mActionShowArchived->setChecked(mShowArchived);
    mActionShowArchived->setToolTip(mShowArchived ? i18nc("@info:tooltip", "Hide Archived Alarms")
                                                  : i18nc("@info:tooltip", "Show Archived Alarms"));
804
    mListFilterModel->setEventTypeFilter(mShowArchived ? CalEvent::ACTIVE | CalEvent::ARCHIVED : CalEvent::ACTIVE);
805
    mListView->reset();
806
    KConfigGroup config(KSharedConfig::openConfig(), VIEW_GROUP);
807
    config.writeEntry(SHOW_ARCHIVED_KEY, mShowArchived);
808
    config.sync();
David Jarvie's avatar
David Jarvie committed
809
810
}

811
812
813
814
815
816
/******************************************************************************
* Called when the Spread Windows global shortcut is selected, to spread alarm
* windows so that they are all visible.
*/
void MainWindow::slotSpreadWindowsShortcut()
{
817
    mActionSpreadWindows->trigger();
818
819
}

820
821
822
823
824
825
826
827
/******************************************************************************
* Called when the Wake From Suspend menu option is selected.
*/
void MainWindow::slotWakeFromSuspend()
{
    (WakeFromSuspendDlg::create(this))->show();
}

828
/******************************************************************************
David Jarvie's avatar
David Jarvie committed
829
830
* Called when the Import Alarms menu item is selected, to merge alarms from an
* external calendar into the current calendars.
831
832
833
*/
void MainWindow::slotImportAlarms()
{
834
    Resource resource;
835
    KAlarm::importAlarms(resource, this);
836
837
}

838
839
840
841
842
843
/******************************************************************************
* Called when the Export Alarms menu item is selected, to export the selected
* alarms to an external calendar.
*/
void MainWindow::slotExportAlarms()
{
844
    const QVector<KAEvent> events = mListView->selectedEvents();
845
    if (!events.isEmpty())