kwrite.cpp 14.8 KB
Newer Older
1
/* This file is part of the KDE project
2
3
4
   SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
   SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
   SPDX-FileCopyrightText: 2001 Anders Lund <anders.lund@lund.tdcadsl.dk>
5

6
   SPDX-License-Identifier: LGPL-2.0-only
7
8
*/

Christoph Cullmann's avatar
Christoph Cullmann committed
9
10
#include "kwrite.h"

Daan De Meyer's avatar
Daan De Meyer committed
11
#include "config.h"
12
#include "kwriteapplication.h"
Daan De Meyer's avatar
Daan De Meyer committed
13

14
#include <ktexteditor/application.h>
15
#include <ktexteditor/editor.h>
16
#include <ktexteditor/modificationinterface.h>
17

Michal Humpula's avatar
Michal Humpula committed
18
#include <KActionCollection>
19
20
#include <KConfig>
#include <KConfigGui>
Michal Humpula's avatar
Michal Humpula committed
21
22
23
24
25
26
#include <KEditToolBar>
#include <KLocalizedString>
#include <KMessageBox>
#include <KRecentFilesAction>
#include <KShortcutsDialog>
#include <KSqueezedTextLabel>
27
28
#include <KStandardAction>
#include <KToggleAction>
Michal Humpula's avatar
Michal Humpula committed
29
#include <KXMLGUIFactory>
30

Daan De Meyer's avatar
Daan De Meyer committed
31
#ifdef KF5Activities_FOUND
32
#include <KActivities/ResourceInstance>
33
#endif
34

Michal Humpula's avatar
Michal Humpula committed
35
#include <QApplication>
Christoph Cullmann's avatar
Christoph Cullmann committed
36
#include <QDir>
37
#include <QDragEnterEvent>
Christoph Cullmann's avatar
Christoph Cullmann committed
38
#include <QFileDialog>
39
#include <QFileOpenEvent>
40
41
42
43
44
#include <QLabel>
#include <QMenuBar>
#include <QMimeData>
#include <QTextCodec>
#include <QTimer>
45

46
KWrite::KWrite(KTextEditor::Document *doc, KWriteApplication *app)
47
    : m_app(app)
48
    , m_mainWindow(this)
49
{
50
    if (!doc) {
51
        doc = KTextEditor::Editor::instance()->createDocument(nullptr);
52

53
54
55
56
        // enable the modified on disk warning dialogs if any
        if (qobject_cast<KTextEditor::ModificationInterface *>(doc)) {
            qobject_cast<KTextEditor::ModificationInterface *>(doc)->setModifiedOnDiskWarning(true);
        }
57

58
        m_app->addDocument(doc);
59
    }
60

61
    m_view = doc->createView(this);
62

63
    setCentralWidget(m_view);
64

65
    setupActions();
66

67
    // signals for the statusbar
68
    connect(m_view->document(), &KTextEditor::Document::modifiedChanged, this, &KWrite::modifiedChanged);
Laurent Montel's avatar
Laurent Montel committed
69
70
71
    connect(m_view->document(), &KTextEditor::Document::documentNameChanged, this, &KWrite::documentNameChanged);
    connect(m_view->document(), &KTextEditor::Document::readWriteChanged, this, &KWrite::documentNameChanged);
    connect(m_view->document(), &KTextEditor::Document::documentUrlChanged, this, &KWrite::urlChanged);
72

73
    setAcceptDrops(true);
74
75
76
    // clang-format off
    connect(m_view, SIGNAL(dropEventPass(QDropEvent*)), this, SLOT(slotDropEvent(QDropEvent*)));
    // clang-format on
77

78
79
80
    setXMLFile(QStringLiteral("kwriteui.rc"));
    createShellGUI(true);
    guiFactory()->addClient(m_view);
81

82
    // FIXME: make sure the config dir exists, any idea how to do it more cleanly?
83
    QDir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).mkpath(QStringLiteral("."));
84

85
86
    // call it as last thing, must be sure everything is already set up ;)
    setAutoSaveSettings();
87

88
    readConfig();
89

90
91
    documentNameChanged();
    show();
92

93
94
    // give view focus
    m_view->setFocus(Qt::OtherFocusReason);
95
96
97
98
99

    /**
     * handle mac os x like file open request via event filter
     */
    qApp->installEventFilter(this);
100
101
102
103
}

KWrite::~KWrite()
{
104
    m_app->removeWindow(this);
105
    guiFactory()->removeClient(m_view);
106

107
108
    KTextEditor::Document *doc = m_view->document();
    delete m_view;
109

110
111
    // kill document, if last view is closed
    if (doc->views().isEmpty()) {
112
        m_app->removeDocument(doc);
113
114
115
116
        delete doc;
    }

    KSharedConfig::openConfig()->sync();
117
118
}

119
QSize KWrite::sizeHint() const
Christoph Cullmann's avatar
Christoph Cullmann committed
120
121
122
123
124
125
126
{
    /**
     * have some useful size hint, else we have mini windows per default
     */
    return (QSize(640, 480).expandedTo(minimumSizeHint()));
}

127
128
void KWrite::setupActions()
{
129
    m_closeAction = actionCollection()->addAction(KStandardAction::Close, QStringLiteral("file_close"), this, SLOT(slotFlush()));
130
    m_closeAction->setIcon(QIcon::fromTheme(QStringLiteral("document-close")));
131
132
    m_closeAction->setWhatsThis(i18n("Use this command to close the current document"));
    m_closeAction->setDisabled(true);
133

134
    // setup File menu
Alexander Lohnau's avatar
Alexander Lohnau committed
135
136
137
138
139
140
    actionCollection()
        ->addAction(KStandardAction::New, QStringLiteral("file_new"), this, SLOT(slotNew()))
        ->setWhatsThis(i18n("Use this command to create a new document"));
    actionCollection()
        ->addAction(KStandardAction::Open, QStringLiteral("file_open"), this, SLOT(slotOpen()))
        ->setWhatsThis(i18n("Use this command to open an existing document for editing"));
141

142
    m_recentFiles = KStandardAction::openRecent(this, QOverload<const QUrl &>::of(&KWrite::slotOpen), this);
143
144
    actionCollection()->addAction(m_recentFiles->objectName(), m_recentFiles);
    m_recentFiles->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again."));
145

146
147
148
    QAction *a = actionCollection()->addAction(QStringLiteral("view_new_view"));
    a->setIcon(QIcon::fromTheme(QStringLiteral("window-new")));
    a->setText(i18n("&New Window"));
Laurent Montel's avatar
Laurent Montel committed
149
    connect(a, &QAction::triggered, this, &KWrite::newView);
150
    a->setWhatsThis(i18n("Create another view containing the current document"));
151

152
    actionCollection()->addAction(KStandardAction::Quit, this, SLOT(close()))->setWhatsThis(i18n("Close the current document view"));
153

154
155
156
157
158
159
160
161
162
    // replace settings dialog action from KTextEditor with a standard action
    delete m_view->actionCollection()->action(QStringLiteral("set_confdlg"));
    // slotConfigDialog is a private slot in KTextEditor::ViewPrivate, so we have to use KTextEditor::Editor::configDialog
    auto configDialogFunc = [this]() {
        KTextEditor::Editor::instance()->configDialog(m_view);
    };
    QAction *settingsConfigure = KStandardAction::preferences(KTextEditor::Editor::instance(), configDialogFunc, actionCollection());
    settingsConfigure->setWhatsThis(i18n("Configure various aspects of this application and the editing component."));

163
164
    // setup Settings menu
    setStandardToolBarMenuEnabled(true);
165

166
    m_paShowMenuBar = KStandardAction::showMenubar(this, &KWrite::toggleMenuBar, actionCollection());
167

168
    m_paShowStatusBar = KStandardAction::showStatusbar(this, &KWrite::toggleStatusBar, this);
169
170
    actionCollection()->addAction(m_paShowStatusBar->objectName(), m_paShowStatusBar);
    m_paShowStatusBar->setWhatsThis(i18n("Use this command to show or hide the view's statusbar"));
171

172
    m_paShowPath = new KToggleAction(i18n("Sho&w Path in Titlebar"), this);
173
    actionCollection()->addAction(QStringLiteral("set_showPath"), m_paShowPath);
Laurent Montel's avatar
Laurent Montel committed
174
    connect(m_paShowPath, &QAction::triggered, this, &KWrite::documentNameChanged);
175
    m_paShowPath->setWhatsThis(i18n("Show the complete document path in the window caption"));
176

177
178
    a = actionCollection()->addAction(KStandardAction::KeyBindings, this, SLOT(editKeys()));
    a->setWhatsThis(i18n("Configure the application's keyboard shortcut assignments."));
179

180
    a = actionCollection()->addAction(KStandardAction::ConfigureToolbars, QStringLiteral("options_configure_toolbars"), this, SLOT(editToolbars()));
181
    a->setWhatsThis(i18n("Configure which items should appear in the toolbar(s)."));
182
183
184
}

// load on url
185
void KWrite::loadURL(const QUrl &url)
186
{
187
    m_view->document()->openUrl(url);
188

Daan De Meyer's avatar
Daan De Meyer committed
189
#ifdef KF5Activities_FOUND
190
191
192
    if (!m_activityResource) {
        m_activityResource = new KActivities::ResourceInstance(winId(), this);
    }
193
    m_activityResource->setUri(m_view->document()->url());
194
195
#endif

196
    m_closeAction->setEnabled(true);
197
198
199
200
201
}

// is closing the window wanted by user ?
bool KWrite::queryClose()
{
202
203
204
    if (m_view->document()->views().count() > 1) {
        return true;
    }
205

206
207
    if (m_view->document()->queryClose()) {
        writeConfig();
208

209
210
        return true;
    }
211

212
    return false;
213
214
}

215
void KWrite::slotFlush()
216
{
217
218
219
220
221
222
223
224
225
    if (m_view->document()->closeUrl()) {
        m_closeAction->setDisabled(true);
    }
}

void KWrite::modifiedChanged()
{
    documentNameChanged();
    m_closeAction->setEnabled(true);
226
227
228
229
}

void KWrite::slotNew()
{
230
    m_app->newWindow();
231
232
233
234
}

void KWrite::slotOpen()
{
235
236
237
238
239
240
241
    // if file is not local, then remove filename from url
    QList<QUrl> urls;
    if (m_view->document()->url().isLocalFile()) {
        urls = QFileDialog::getOpenFileUrls(this, i18n("Open File"), m_view->document()->url());
    } else {
        urls = QFileDialog::getOpenFileUrls(this, i18n("Open File"), m_view->document()->url().adjusted(QUrl::RemoveFilename));
    }
Waqar Ahmed's avatar
Waqar Ahmed committed
242
    for (const QUrl &url : qAsConst(urls)) {
243
244
        slotOpen(url);
    }
245
246
}

247
void KWrite::slotOpen(const QUrl &url)
248
{
249
250
251
252
253
    if (url.isEmpty()) {
        return;
    }

    if (m_view->document()->isModified() || !m_view->document()->url().isEmpty()) {
254
        KWrite *t = m_app->newWindow();
255
256
257
258
        t->loadURL(url);
    } else {
        loadURL(url);
    }
259
260
}

261
void KWrite::urlChanged()
262
{
263
    if (!m_view->document()->url().isEmpty()) {
264
265
266
267
268
        m_recentFiles->addUrl(m_view->document()->url());
    }

    // update caption
    documentNameChanged();
269
270
271
272
}

void KWrite::newView()
{
273
    m_app->newWindow(m_view->document());
274
275
}

276
277
278
279
void KWrite::toggleMenuBar(bool showMessage)
{
    if (m_paShowMenuBar->isChecked()) {
        menuBar()->show();
280
281
282
        if (m_view->contextMenu()) {
            m_view->contextMenu()->removeAction(m_paShowMenuBar);
        }
283
284
285
    } else {
        if (showMessage) {
            const QString accel = m_paShowMenuBar->shortcut().toString();
286
287
288
289
            KMessageBox::information(this,
                                     i18n("This will hide the menu bar completely."
                                          " You can show it again by typing %1.",
                                          accel),
290
                                     i18n("Hide menu bar"),
Laurent Montel's avatar
Laurent Montel committed
291
                                     QStringLiteral("HideMenuBarWarning"));
292
293
        }
        menuBar()->hide();
294
295
296
        if (m_view->contextMenu()) {
            m_view->contextMenu()->addAction(m_paShowMenuBar);
        }
297
298
299
    }
}

300
301
void KWrite::toggleStatusBar()
{
302
    m_view->setStatusBarEnabled(m_paShowStatusBar->isChecked());
303
304
305
306
}

void KWrite::editKeys()
{
307
308
309
310
311
312
    KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
    dlg.addCollection(actionCollection());
    if (m_view) {
        dlg.addCollection(m_view->actionCollection());
    }
    dlg.configure();
313
314
315
316
}

void KWrite::editToolbars()
{
317
318
319
    KConfigGroup cfg = KSharedConfig::openConfig()->group("MainWindow");
    saveMainWindowSettings(cfg);
    KEditToolBar dlg(guiFactory(), this);
320

Laurent Montel's avatar
Laurent Montel committed
321
    connect(&dlg, &KEditToolBar::newToolBarConfig, this, &KWrite::slotNewToolbarConfig);
322
    dlg.exec();
323
324
}

325
326
void KWrite::slotNewToolbarConfig()
{
327
    applyMainWindowSettings(KSharedConfig::openConfig()->group("MainWindow"));
328
329
}

330
void KWrite::dragEnterEvent(QDragEnterEvent *event)
331
{
332
    const QList<QUrl> uriList = event->mimeData()->urls();
333
    event->setAccepted(!uriList.isEmpty());
334
335
}

336
void KWrite::dropEvent(QDropEvent *event)
337
{
338
    slotDropEvent(event);
339
340
}

341
void KWrite::slotDropEvent(QDropEvent *event)
342
{
343
    const QList<QUrl> textlist = event->mimeData()->urls();
344

345
    for (const QUrl &url : textlist) {
346
        slotOpen(url);
347
    }
348
349
}

350
void KWrite::slotEnableActions(bool enable)
351
{
352
353
354
    QList<QAction *> actions = actionCollection()->actions();
    QList<QAction *>::ConstIterator it = actions.constBegin();
    QList<QAction *>::ConstIterator end = actions.constEnd();
355

356
357
358
    for (; it != end; ++it) {
        (*it)->setEnabled(enable);
    }
359

360
361
362
    actions = m_view->actionCollection()->actions();
    it = actions.constBegin();
    end = actions.constEnd();
363

364
365
366
    for (; it != end; ++it) {
        (*it)->setEnabled(enable);
    }
367
368
}

369
// common config
370
void KWrite::readConfig(KSharedConfigPtr config)
371
{
372
    KConfigGroup cfg(config, "General Options");
373

374
    m_paShowMenuBar->setChecked(cfg.readEntry("ShowMenuBar", true));
375
376
    m_paShowStatusBar->setChecked(cfg.readEntry("ShowStatusBar", true));
    m_paShowPath->setChecked(cfg.readEntry("ShowPath", false));
377

378
    m_recentFiles->loadEntries(config->group("Recent Files"));
379

380
381
    // update visibility of menu bar and status bar
    toggleMenuBar(false);
382
    m_view->setStatusBarEnabled(m_paShowStatusBar->isChecked());
383
384
}

385
void KWrite::writeConfig(KSharedConfigPtr config)
386
{
387
    KConfigGroup generalOptions(config, "General Options");
388

389
    generalOptions.writeEntry("ShowMenuBar", m_paShowMenuBar->isChecked());
390
391
    generalOptions.writeEntry("ShowStatusBar", m_paShowStatusBar->isChecked());
    generalOptions.writeEntry("ShowPath", m_paShowPath->isChecked());
392

393
    m_recentFiles->saveEntries(KConfigGroup(config, "Recent Files"));
394

395
    config->sync();
396
397
}

398
// config file
399
400
void KWrite::readConfig()
{
401
    readConfig(KSharedConfig::openConfig());
402
403
404
405
}

void KWrite::writeConfig()
{
406
    writeConfig(KSharedConfig::openConfig());
407
408
409
410
411
}

// session management
void KWrite::restore(KConfig *config, int n)
{
412
    readPropertiesInternal(config, n);
413
414
}

415
void KWrite::readProperties(const KConfigGroup &config)
416
{
417
    readConfig();
418

419
    m_view->readSessionConfig(KConfigGroup(&config, QStringLiteral("General Options")));
420
421
}

422
void KWrite::saveProperties(KConfigGroup &config)
423
{
424
    writeConfig();
Urs Wolfer's avatar
Urs Wolfer committed
425

426
    config.writeEntry("DocumentNumber", m_app->documents().indexOf(m_view->document()) + 1);
427

428
429
    KConfigGroup cg(&config, QStringLiteral("General Options"));
    m_view->writeSessionConfig(cg);
430
431
}

432
void KWrite::saveGlobalProperties(KConfig *config) // save documents
433
{
434
    m_app->saveProperties(config);
435
436
}

437
void KWrite::documentNameChanged()
438
{
439
440
441
    QString readOnlyCaption;
    if (!m_view->document()->isReadWrite()) {
        readOnlyCaption = i18n(" [read only]");
442
443
    }

444
445
    if (m_view->document()->url().isEmpty()) {
        setCaption(i18n("Untitled") + readOnlyCaption + QStringLiteral(" [*]"), m_view->document()->isModified());
446
447
448
449
450
451
452
453
454
455
        return;
    }

    QString c;

    if (m_paShowPath->isChecked()) {
        c = m_view->document()->url().toString(QUrl::PreferLocalFile);

        const QString homePath = QDir::homePath();
        if (c.startsWith(homePath)) {
456
            c = QLatin1String("~") + c.right(c.length() - homePath.length());
457
458
        }

459
        // File name shouldn't be too long - Maciek
460
        if (c.length() > 64) {
461
            c = QLatin1String("...") + c.right(64);
462
463
464
465
        }
    } else {
        c = m_view->document()->url().fileName();

466
        // File name shouldn't be too long - Maciek
467
468
469
        if (c.length() > 64) {
            c = c.left(64) + QStringLiteral("...");
        }
470
    }
471
472

    setCaption(c + readOnlyCaption + QStringLiteral(" [*]"), m_view->document()->isModified());
473
}
474
475
476
477
478
479
480
481
482
483
484

bool KWrite::eventFilter(QObject *obj, QEvent *event)
{
    /**
     * handle mac os like file open
     */
    if (event->type() == QEvent::FileOpen) {
        /**
         * try to open and activate the new document, like we would do for stuff
         * opened via file dialog
         */
485
        QFileOpenEvent *foe = static_cast<QFileOpenEvent *>(event);
486
487
488
        slotOpen(foe->url());
        return true;
    }
489

490
491
492
493
494
    /**
     * else: pass over to default implementation
     */
    return KParts::MainWindow::eventFilter(obj, event);
}
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510

QList<KTextEditor::View *> KWrite::views()
{
    QList<KTextEditor::View *> list;
    list.append(m_view);
    return list;
}

KTextEditor::View *KWrite::activateView(KTextEditor::Document *document)
{
    if (m_view->document() == document) {
        return m_view;
    }

    return nullptr;
}