mainwindow.cpp 18.2 KB
Newer Older
1
/* This file is part of the KDevelop project
2 3 4 5
Copyright 2002 Falk Brettschneider <falkbr@kdevelop.org>
Copyright 2003 John Firebaugh <jfirebaugh@kde.org>
Copyright 2006 Adam Treat <treat@kde.org>
Copyright 2006, 2007 Alexander Dymo <adymo@kdevelop.org>
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Library General Public License for more details.

You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB.  If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "mainwindow.h"
#include "mainwindow_p.h"

25
#include <QDBusConnection>
26
#include <QDomElement>
27
#include <QDragEnterEvent>
28
#include <QDropEvent>
29
#include <QMenu>
30
#include <QMenuBar>
31
#include <QMimeData>
32
#include <QUrl>
33

34
#include <KActionCollection>
35
#include <KLocalizedString>
36 37 38
#include <KShortcutsDialog>
#include <KTextEditor/Document>
#include <KTextEditor/View>
39
#include <KWindowSystem>
40
#include <KXMLGUIFactory>
41

42
#include <sublime/area.h>
43 44 45
#include "shellextension.h"
#include "partcontroller.h"
#include "plugincontroller.h"
46
#include "projectcontroller.h"
47
#include "uicontroller.h"
48
#include "documentcontroller.h"
49
#include "workingsetcontroller.h"
50
#include "sessioncontroller.h"
51
#include "sourceformattercontroller.h"
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
52
#include "areadisplay.h"
53
#include "project.h"
Dāvis Mosāns's avatar
Dāvis Mosāns committed
54
#include "debug.h"
55
#include "uiconfig.h"
56
#include "ktexteditorpluginintegration.h"
57

58 59
#include <interfaces/isession.h>
#include <interfaces/iprojectcontroller.h>
60 61 62
#include <sublime/view.h>
#include <sublime/document.h>
#include <sublime/urldocument.h>
63 64 65
#include <sublime/container.h>
#include <util/path.h>
#include <util/widgetcolorizer.h>
66

67 68 69 70 71 72
using namespace KDevelop;

namespace {

QColor defaultColor(const QPalette& palette)
{
73
    return palette.windowText().color();
74 75
}

76
QColor colorForDocument(const QUrl& url, const QPalette& palette, const QColor& defaultColor)
77
{
78 79
    auto project = Core::self()->projectController()->findProjectForUrl(url);
    if (!project)
80
        return defaultColor;
81 82 83 84 85

    return WidgetColorizer::colorForId(qHash(project->path()), palette);
}

}
86

87
void MainWindow::applyMainWindowSettings(const KConfigGroup& config)
88
{
89 90
    Q_D(MainWindow);

91
    if(!d->changingActiveView())
92
        KXmlGuiWindow::applyMainWindowSettings(config);
93
}
94

95 96
void MainWindow::createGUI(KParts::Part* part)
{
97 98
    Sublime::MainWindow::setWindowTitleHandling(false);
    Sublime::MainWindow::createGUI(part);
99 100
}

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
101
void MainWindow::initializeCorners()
102
{
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
103 104 105
    const KConfigGroup cg = KSharedConfig::openConfig()->group( "UiSettings" );
    const int bottomleft = cg.readEntry( "BottomLeftCornerOwner", 0 );
    const int bottomright = cg.readEntry( "BottomRightCornerOwner", 0 );
Dāvis Mosāns's avatar
Dāvis Mosāns committed
106 107
    qCDebug(SHELL) << "Bottom Left:" << bottomleft;
    qCDebug(SHELL) << "Bottom Right:" << bottomright;
108 109 110 111 112 113 114 115 116 117 118

    // 0 means vertical dock (left, right), 1 means horizontal dock( top, bottom )
    if( bottomleft == 0 )
        setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea );
    else if( bottomleft == 1 )
        setCorner( Qt::BottomLeftCorner, Qt::BottomDockWidgetArea );

    if( bottomright == 0 )
        setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea );
    else if( bottomright == 1 )
        setCorner( Qt::BottomRightCorner, Qt::BottomDockWidgetArea );
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
119 120 121 122 123 124 125 126 127 128
}

MainWindow::MainWindow( Sublime::Controller *parent, Qt::WindowFlags flags )
        : Sublime::MainWindow( parent, flags )
{
    QDBusConnection::sessionBus().registerObject( QStringLiteral("/kdevelop/MainWindow"),
        this, QDBusConnection::ExportScriptableSlots );

    setAcceptDrops( true );
    initializeCorners();
129

130
    setObjectName( QStringLiteral("MainWindow") );
131 132 133
    d_ptr = new MainWindowPrivate(this);

    Q_D(MainWindow);
134

135 136 137
    setStandardToolBarMenuEnabled( true );
    d->setupActions();

138 139 140 141
    if( !ShellExtension::getInstance()->xmlFile().isEmpty() )
    {
        setXMLFile( ShellExtension::getInstance() ->xmlFile() );
    }
142

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
143
    menuBar()->setCornerWidget(new AreaDisplay(this), Qt::TopRightCorner);
144 145 146 147
}

MainWindow::~ MainWindow()
{
148
    if (memberList().count() == 1) {
149
        // We're closing down...
150
        Core::self()->shutdown();
151
    }
152

153
    delete d_ptr;
154 155
}

156 157
KTextEditorIntegration::MainWindow *MainWindow::kateWrapper() const
{
158 159
    Q_D(const MainWindow);

160 161 162
    return d->kateWrapper();
}

163 164
void MainWindow::split(Qt::Orientation orientation)
{
165 166
    Q_D(MainWindow);

167 168 169
    d->split(orientation);
}

170 171 172 173 174 175 176 177 178 179 180 181
void MainWindow::ensureVisible()
{
    if (isMinimized()) {
        if (isMaximized()) {
            showMaximized();
        } else {
            showNormal();
        }
    }
    KWindowSystem::forceActiveWindow(winId());
}

182 183
QAction* MainWindow::createCustomElement(QWidget* parent, int index, const QDomElement& element)
{
184
    QAction* before = nullptr;
185 186 187 188 189 190 191
    if (index > 0 && index < parent->actions().count())
        before = parent->actions().at(index);

    //KDevelop needs to ensure that separators defined as <Separator style="visible" />
    //are always shown in the menubar. For those, we create special disabled actions
    //instead of calling QMenuBar::addSeparator() because menubar separators are ignored
    if (element.tagName().toLower() == QLatin1String("separator")
192
            && element.attribute(QStringLiteral("style")) == QLatin1String("visible")) {
193
        if ( auto* bar = qobject_cast<QMenuBar*>( parent ) ) {
194
            QAction *separatorAction = new QAction(QStringLiteral("|"), this);
195 196 197 198 199 200 201 202 203
            bar->insertAction( before, separatorAction );
            separatorAction->setDisabled(true);
            return separatorAction;
        }
    }

    return KXMLGUIBuilder::createCustomElement(parent, index, element);
}

204 205 206 207 208 209 210
bool KDevelop::MainWindow::event( QEvent* ev )
{
    if ( ev->type() == QEvent::PaletteChange )
        updateAllTabColors();
    return Sublime::MainWindow::event(ev);
}

211 212
void MainWindow::dragEnterEvent( QDragEnterEvent* ev )
{
213 214
    const QMimeData* mimeData = ev->mimeData();
    if (mimeData->hasUrls()) {
215
        ev->acceptProposedAction();
216 217 218 219 220 221
    } else if (mimeData->hasText()) {
        // also take text which contains a URL
        const QUrl url = QUrl::fromUserInput(mimeData->text());
        if (url.isValid()) {
            ev->acceptProposedAction();
        }
222 223 224 225 226
    }
}

void MainWindow::dropEvent( QDropEvent* ev )
{
227 228 229
    Sublime::View* dropToView = viewForPosition(mapToGlobal(ev->pos()));
    if(dropToView)
        activateView(dropToView);
Milian Wolff's avatar
Milian Wolff committed
230

231 232 233 234 235 236 237 238 239 240 241 242 243 244
    QList<QUrl> urls;

    const QMimeData* mimeData = ev->mimeData();
    if (mimeData->hasUrls()) {
        urls = mimeData->urls();
    } else if (mimeData->hasText()) {
        const QUrl url = QUrl::fromUserInput(mimeData->text());
        if (url.isValid()) {
            urls << url;
        }
    }

    bool eventUsed = false;
    if (urls.size() == 1) {
245
        eventUsed = Core::self()->projectControllerInternal()->fetchProjectFromUrl(urls.at(0), ProjectController::NoFetchFlags);
246 247 248
    }

    if (!eventUsed) {
249
        for(const auto& url : qAsConst(urls)) {
250 251
            Core::self()->documentController()->openDocument(url);
        }
252
    }
253

254 255 256
    ev->acceptProposedAction();
}

257 258
void MainWindow::loadSettings()
{
Dāvis Mosāns's avatar
Dāvis Mosāns committed
259
    qCDebug(SHELL) << "Loading Settings";
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
260
    initializeCorners();
261

262 263
    updateAllTabColors();

264 265 266
    Sublime::MainWindow::loadSettings();
}

267 268 269 270
void MainWindow::configureShortcuts()
{
    ///Workaround for a problem with the actions: Always start the shortcut-configuration in the first mainwindow, then propagate the updated
    ///settings into the other windows
271 272


Dāvis Mosāns's avatar
Dāvis Mosāns committed
273
// We need to bring up the shortcut dialog ourself instead of
274 275 276 277
//      Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->configureShortcuts();
// so we can connect to the saved() signal to propagate changes in the editor shortcuts

   KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this);
278 279 280

    const auto firstMainWindowClientsBefore = Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients();
    for (KXMLGUIClient* client : firstMainWindowClientsBefore) {
281 282 283
        if(client && !client->xmlFile().isEmpty())
            dlg.addCollection( client->actionCollection() );
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
284

285
    connect(&dlg, &KShortcutsDialog::saved, this, &MainWindow::shortcutsChanged);
286
    dlg.configure(true);
287 288

    QMap<QString, QKeySequence> shortcuts;
289 290 291 292 293
    // querying again just in case something changed behind our back
    const auto firstMainWindowClientsAfter = Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients();
    for (KXMLGUIClient* client : firstMainWindowClientsAfter) {
        const auto actions = client->actionCollection()->actions();
        for (QAction* action : actions) {
294 295 296 297 298
            if(!action->objectName().isEmpty()) {
                shortcuts[action->objectName()] = action->shortcut();
            }
        }
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
299

300
    for(int a = 1; a < Core::self()->uiControllerInternal()->mainWindows().size(); ++a) {
301 302 303 304
        const auto clients = Core::self()->uiControllerInternal()->mainWindows()[a]->guiFactory()->clients();
        for (KXMLGUIClient* client : clients) {
            const auto actions = client->actionCollection()->actions();
            for (QAction* action : actions) {
Dāvis Mosāns's avatar
Dāvis Mosāns committed
305
                qCDebug(SHELL) << "transferring setting shortcut for" << action->objectName();
306 307 308
                const auto shortcutIt = shortcuts.constFind(action->objectName());
                if (shortcutIt != shortcuts.constEnd()) {
                    action->setShortcut(*shortcutIt);
309 310 311 312
                }
            }
        }
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
313

314 315
}

316 317
void MainWindow::shortcutsChanged()
{
318 319
    KTextEditor::View *activeClient = Core::self()->documentController()->activeTextDocumentView();
    if(!activeClient)
320 321
        return;

322 323
    const auto documents = Core::self()->documentController()->openDocuments();
    for (IDocument* doc : documents) {
324 325
        KTextEditor::Document *textDocument = doc->textDocument();
        if (textDocument) {
326 327
            const auto views = textDocument->views();
            for (KTextEditor::View* client : views) {
328 329
                if (client != activeClient) {
                    client->reloadXML();
330 331 332 333 334 335 336
                }
            }
        }
    }
}


337 338
void MainWindow::initialize()
{
339 340
    Q_D(MainWindow);

341 342
    KStandardAction::keyBindings(this, SLOT(configureShortcuts()), actionCollection());
    setupGUI( KXmlGuiWindow::ToolBar | KXmlGuiWindow::Create | KXmlGuiWindow::Save );
Dāvis Mosāns's avatar
Dāvis Mosāns committed
343

344
    Core::self()->partController()->addManagedTopLevelWidget(this);
Dāvis Mosāns's avatar
Dāvis Mosāns committed
345 346
    qCDebug(SHELL) << "Adding plugin-added connection";

347 348 349 350 351 352 353 354
    connect( Core::self()->pluginController(), &IPluginController::pluginLoaded,
             d, &MainWindowPrivate::addPlugin);
    connect( Core::self()->pluginController(), &IPluginController::pluginUnloaded,
             d, &MainWindowPrivate::removePlugin);
    connect( Core::self()->partController(), &IPartController::activePartChanged,
        d, &MainWindowPrivate::activePartChanged);
    connect( this, &MainWindow::activeViewChanged,
        d, &MainWindowPrivate::changeActiveView);
355 356
    connect(Core::self()->sourceFormatterControllerInternal(), &SourceFormatterController::hasFormattersChanged,
             d, &MainWindowPrivate::updateSourceFormatterGuiClient);
Dāvis Mosāns's avatar
Dāvis Mosāns committed
357

358 359
    const auto plugins = Core::self()->pluginController()->loadedPlugins();
    for (IPlugin* plugin : plugins) {
David Nolden's avatar
David Nolden committed
360
        d->addPlugin(plugin);
361
    }
Dāvis Mosāns's avatar
Dāvis Mosāns committed
362

363
    guiFactory()->addClient(Core::self()->sessionController());
364 365 366
    if (Core::self()->sourceFormatterControllerInternal()->hasFormatters()) {
        guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal());
    }
367

368 369
    // Needed to re-plug the actions from the sessioncontroller as xmlguiclients don't
    // seem to remember which actions where plugged in.
370 371
    Core::self()->sessionController()->updateXmlGuiActionList();

372
    d->setupGui();
Dāvis Mosāns's avatar
Dāvis Mosāns committed
373

374 375
    qRegisterMetaType<QPointer<KTextEditor::Document>>();

376
    //Queued so we process it with some delay, to make sure the rest of the UI has already adapted
377 378 379 380 381 382
    connect(Core::self()->documentController(), &IDocumentController::documentActivated,
            // Use a queued connection, because otherwise the view is not yet fully set up
            // but wrap the document in a smart pointer to guard against crashes when it
            // gets deleted in the meantime
            this, [this](IDocument *doc) {
                const auto textDocument = QPointer<KTextEditor::Document>(doc->textDocument());
383 384
                QMetaObject::invokeMethod(this, "documentActivated", Qt::QueuedConnection,
                                          Q_ARG(QPointer<KTextEditor::Document>, textDocument));
385 386
            });

387 388 389
    connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &MainWindow::updateCaption, Qt::QueuedConnection);
    connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateCaption, Qt::QueuedConnection);
    connect(Core::self()->sessionController()->activeSession(), &ISession::sessionUpdated, this, &MainWindow::updateCaption);
390

391 392
    connect(Core::self()->documentController(), &IDocumentController::documentOpened, this, &MainWindow::updateTabColor);
    connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateTabColor);
393 394 395
    connect(this, &Sublime::MainWindow::viewAdded, this, &MainWindow::updateAllTabColors);
    connect(Core::self()->projectController(), &ProjectController::projectOpened, this, &MainWindow::updateAllTabColors, Qt::QueuedConnection);

396
    updateCaption();
397 398 399 400 401 402 403 404 405 406 407 408 409 410
}

void MainWindow::cleanup()
{
}

void MainWindow::setVisible( bool visible )
{
    KXmlGuiWindow::setVisible( visible );
    emit finishedLoading();
}

bool MainWindow::queryClose()
{
411 412
    if (!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(this, IDocument::Default))
        return false;
413

414 415 416
    return Sublime::MainWindow::queryClose();
}

417 418
void MainWindow::documentActivated(const QPointer<KTextEditor::Document>& textDocument)
{
419 420
    Q_D(MainWindow);

421 422 423 424 425 426 427 428 429 430
    updateCaption();

    // update active document connection
    disconnect(d->activeDocumentReadWriteConnection);
    if (textDocument) {
        d->activeDocumentReadWriteConnection = connect(textDocument, &KTextEditor::Document::readWriteChanged,
                                                    this, &MainWindow::updateCaption);
    }
}

431
void MainWindow::updateCaption()
432
{
433 434
    const auto activeSession = Core::self()->sessionController()->activeSession();
    QString title = activeSession ? activeSession->description() : QString();
435
    QString localFilePath;
Milian Wolff's avatar
Milian Wolff committed
436

437 438 439
    if(area()->activeView())
    {
        if(!title.isEmpty())
440
            title += QLatin1String(" - [ ");
Milian Wolff's avatar
Milian Wolff committed
441

442
        Sublime::Document* doc = area()->activeView()->document();
443
        auto* urlDoc = qobject_cast<Sublime::UrlDocument*>(doc);
444
        if(urlDoc)
445
        {
446 447 448
            if (urlDoc->url().isLocalFile()) {
                localFilePath = urlDoc->url().toLocalFile();
            }
Milian Wolff's avatar
Milian Wolff committed
449
            title += Core::self()->projectController()->prettyFileName(urlDoc->url(), KDevelop::IProjectController::FormatPlain);
450
        }
451 452
        else
            title += doc->title();
Milian Wolff's avatar
Milian Wolff committed
453

454 455
        auto iDoc = qobject_cast<IDocument*>(doc);
        if (iDoc && iDoc->textDocument() && !iDoc->textDocument()->isReadWrite()) {
456
            title += i18n(" (read only)");
457
        }
458

459
        title += QLatin1String(" ]");
460
    }
Milian Wolff's avatar
Milian Wolff committed
461

462
    setWindowFilePath(localFilePath);
463
    setCaption(title);
464 465
}

466 467 468 469 470 471 472
void MainWindow::updateAllTabColors()
{
    auto documentController = Core::self()->documentController();
    if (!documentController)
        return;

    const auto defaultColor = ::defaultColor(palette());
473 474
    if (UiConfig::colorizeByProject()) {
        QHash<const Sublime::View*, QColor> viewColors;
475 476
        const auto containers = this->containers();
        for (auto* container : containers) {
477
            const auto views = container->views();
478 479
            viewColors.reserve(views.size());
            viewColors.clear();
480
            for (auto view : views) {
481 482
                const auto urlDoc = qobject_cast<Sublime::UrlDocument*>(view->document());
                if (urlDoc) {
483
                    viewColors[view] = colorForDocument(urlDoc->url(), palette(), defaultColor);
484 485
                }
            }
486
            container->setTabColors(viewColors);
487 488
        }
    } else {
489 490
        const auto containers = this->containers();
        for (auto* container : containers) {
491 492 493 494 495 496 497 498 499 500
            container->resetTabColors(defaultColor);
        }
    }
}

void MainWindow::updateTabColor(IDocument* doc)
{
    if (!UiConfig::self()->colorizeByProject())
        return;

501
    const auto color = colorForDocument(doc->url(), palette(), defaultColor(palette()));
502 503 504 505
    const auto containers = this->containers();
    for (auto* container : containers) {
        const auto views = container->views();
        for (auto* view : views) {
506 507
            const auto urlDoc = qobject_cast<Sublime::UrlDocument*>(view->document());
            if (urlDoc && urlDoc->url() == doc->url()) {
508
                container->setTabColor(view, color);
509 510 511 512 513
            }
        }
    }
}

514 515
void MainWindow::registerStatus(QObject* status)
{
516 517
    Q_D(MainWindow);

518 519 520
    d->registerStatus(status);
}

521 522
void MainWindow::initializeStatusBar()
{
523 524
    Q_D(MainWindow);

525 526 527
    d->setupStatusBar();
}

528 529
void MainWindow::showErrorMessage(const QString& message, int timeout)
{
530 531
    Q_D(MainWindow);

532 533 534
    d->showErrorMessage(message, timeout);
}

535
void MainWindow::tabContextMenuRequested(Sublime::View* view, QMenu* menu)
536
{
537 538
    Q_D(MainWindow);

539 540 541 542
    Sublime::MainWindow::tabContextMenuRequested(view, menu);
    d->tabContextMenuRequested(view, menu);
}

543
void MainWindow::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab)
544
{
545 546
    Q_D(MainWindow);

547
    d->tabToolTipRequested(view, container, tab);
548 549
}

550 551
void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position)
{
552 553
    Q_D(MainWindow);

554 555 556
    d->dockBarContextMenuRequested(area, position);
}

557 558
void MainWindow::newTabRequested()
{
559 560
    Q_D(MainWindow);

561 562 563 564
    Sublime::MainWindow::newTabRequested();

    d->fileNew();
}