diff --git a/plugins/contextbrowser/contextbrowserview.cpp b/plugins/contextbrowser/contextbrowserview.cpp index 9c344868f..d4e3a74fa 100644 --- a/plugins/contextbrowser/contextbrowserview.cpp +++ b/plugins/contextbrowser/contextbrowserview.cpp @@ -1,395 +1,391 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program 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 program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowserview.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include "contextbrowser.h" #include "debug.h" #include #include #include #include "browsemanager.h" #include #include #include #include #include #include using namespace KDevelop; namespace { enum Direction { NextUse, PreviousUse }; void selectUse(ContextBrowserView* view, Direction direction) { auto abstractNaviWidget = dynamic_cast(view->navigationWidget()); if (!abstractNaviWidget) { return; } auto usesWidget = dynamic_cast(abstractNaviWidget->context()->widget()); if (!usesWidget) { return; } OneUseWidget* first = nullptr, *previous = nullptr, *current = nullptr; for (auto item : usesWidget->items()) { auto topContext = dynamic_cast(item); if (!topContext) { continue; } for (auto item : topContext->items()) { auto navigationList = dynamic_cast(item); if (!navigationList) { continue; } for (auto item : navigationList->items()) { auto use = dynamic_cast(item); if (!use) { continue; } if (!first) { first = use; } current = use; if (direction == PreviousUse && current->isHighlighted() && previous) { previous->setHighlighted(true); previous->activateLink(); current->setHighlighted(false); return; } if (direction == NextUse && previous && previous->isHighlighted()) { current->setHighlighted(true); current->activateLink(); previous->setHighlighted(false); return; } previous = current; } } } if (direction == NextUse && first) { first->setHighlighted(true); first->activateLink(); if (current && current->isHighlighted()) current->setHighlighted(false); return; } if (direction == PreviousUse && current) { current->setHighlighted(true); current->activateLink(); if (first && first->isHighlighted()) { first->setHighlighted(false); } } } } QWidget* ContextBrowserView::createWidget(KDevelop::DUContext* context) { m_context = IndexedDUContext(context); if(m_context.data()) { return m_context.data()->createNavigationWidget(nullptr, nullptr, {}, {}, AbstractNavigationWidget::EmbeddableWidget); } return nullptr; } KDevelop::IndexedDeclaration ContextBrowserView::declaration() const { return m_declaration; } QWidget* ContextBrowserView::createWidget(Declaration* decl, TopDUContext* topContext) { m_declaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, topContext, {}, {}, AbstractNavigationWidget::EmbeddableWidget); } void ContextBrowserView::resetWidget() { if (m_navigationWidget) { delete m_navigationWidget; m_navigationWidget = nullptr; } } void ContextBrowserView::declarationMenu() { DUChainReadLocker lock(DUChain::lock()); AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget) { AbstractDeclarationNavigationContext* navigationContext = dynamic_cast(navigationWidget->context().data()); if(navigationContext && navigationContext->declaration().data()) { KDevelop::DeclarationContext* c = new KDevelop::DeclarationContext(navigationContext->declaration().data()); lock.unlock(); QMenu menu; QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( c ); ContextMenuExtension::populateMenu(&menu, extensions); menu.exec(QCursor::pos()); } } } -void ContextBrowserView::updateLockIcon(bool checked) { - m_lockAction->setIcon(QIcon::fromTheme(checked ? QStringLiteral("document-encrypt") : QStringLiteral("document-decrypt"))); -} - ContextBrowserView::ContextBrowserView( ContextBrowserPlugin* plugin, QWidget* parent ) : QWidget(parent), m_plugin(plugin), m_navigationWidget(new QTextBrowser()), m_autoLocked(false) { + setWindowTitle(i18n("Code Browser")); setWindowIcon( QIcon::fromTheme(QStringLiteral("code-context"), windowIcon()) ); m_allowLockedUpdate = false; m_declarationMenuAction = new QAction(QIcon::fromTheme(QStringLiteral("code-class")), QString(), this); m_declarationMenuAction->setToolTip(i18n("Declaration menu")); connect(m_declarationMenuAction, &QAction::triggered, this, &ContextBrowserView::declarationMenu); addAction(m_declarationMenuAction); - m_lockAction = new QAction(this); - m_lockAction->setCheckable(true); - m_lockAction->setChecked(false); + m_lockAction = new KToggleAction(QIcon::fromTheme(QStringLiteral("object-unlocked")), i18n("Lock Current View"), this); m_lockAction->setToolTip(i18n("Lock current view")); + m_lockAction->setCheckedState(KGuiItem(i18n("Unlock Current View"), QIcon::fromTheme(QStringLiteral("object-locked")), i18n("Unlock current view"))); + m_lockAction->setChecked(false); addAction(m_lockAction); - updateLockIcon(m_lockAction->isChecked()); - connect(m_lockAction, &QAction::triggered, this, &ContextBrowserView::updateLockIcon); m_layout = new QVBoxLayout; m_layout->setSpacing(0); m_layout->setMargin(0); m_layout->addWidget(m_navigationWidget); //m_layout->addStretch(); setLayout(m_layout); m_plugin->registerToolView(this); } ContextBrowserView::~ContextBrowserView() { m_plugin->unRegisterToolView(this); } void ContextBrowserView::focusInEvent(QFocusEvent* event) { //Indicate that we have focus qCDebug(PLUGIN_CONTEXTBROWSER) << "got focus"; // parentWidget()->setBackgroundRole(QPalette::ToolTipBase); /* m_layout->removeItem(m_buttons);*/ return QWidget::focusInEvent(event); } void ContextBrowserView::focusOutEvent(QFocusEvent* event) { qCDebug(PLUGIN_CONTEXTBROWSER) << "lost focus"; // parentWidget()->setBackgroundRole(QPalette::Background); /* m_layout->insertLayout(0, m_buttons); for(int a = 0; a < m_buttons->count(); ++a) { QWidgetItem* item = dynamic_cast(m_buttons->itemAt(a)); }*/ QWidget::focusOutEvent(event); } bool ContextBrowserView::event(QEvent* event) { QKeyEvent* keyEvent = dynamic_cast(event); if(hasFocus() && keyEvent) { AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget && event->type() == QEvent::KeyPress) { int key = keyEvent->key(); if(key == Qt::Key_Left) navigationWidget->previous(); if(key == Qt::Key_Right) navigationWidget->next(); if(key == Qt::Key_Up) navigationWidget->up(); if(key == Qt::Key_Down) navigationWidget->down(); if(key == Qt::Key_Return || key == Qt::Key_Enter) navigationWidget->accept(); if(key == Qt::Key_L) m_lockAction->toggle(); } } return QWidget::event(event); } void ContextBrowserView::showEvent(QShowEvent* event) { DUChainReadLocker lock(DUChain::lock(), 200); if (!lock.locked()) { QWidget::showEvent(event); return; } TopDUContext* top = m_lastUsedTopContext.data(); if(top && m_navigationWidgetDeclaration.isValid()) { //Update the navigation-widget Declaration* decl = m_navigationWidgetDeclaration.getDeclaration(top); if(decl) setDeclaration(decl, top, true); } QWidget::showEvent(event); } bool ContextBrowserView::isLocked() const { bool isLocked; if (m_allowLockedUpdate) { isLocked = false; } else { isLocked = m_lockAction->isChecked(); } return isLocked; } void ContextBrowserView::updateMainWidget(QWidget* widget) { if (widget) { setUpdatesEnabled(false); qCDebug(PLUGIN_CONTEXTBROWSER) << ""; resetWidget(); m_navigationWidget = widget; m_layout->insertWidget(1, widget, 1); m_allowLockedUpdate = false; setUpdatesEnabled(true); if (widget->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("contextChanged(bool,bool)")) != -1) { connect(widget, SIGNAL(contextChanged(bool,bool)), this, SLOT(navigationContextChanged(bool,bool))); } } } void ContextBrowserView::navigationContextChanged(bool wasInitial, bool isInitial) { if(wasInitial && !isInitial && !m_lockAction->isChecked()) { m_autoLocked = true; m_lockAction->setChecked(true); }else if(!wasInitial && isInitial && m_autoLocked) { m_autoLocked = false; m_lockAction->setChecked(false); }else if(isInitial) { m_autoLocked = false; } } void ContextBrowserView::selectNextItem() { selectUse(this, NextUse); } void ContextBrowserView::selectPreviousItem() { selectUse(this, PreviousUse); } void ContextBrowserView::setDeclaration(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, bool force) { m_lastUsedTopContext = IndexedTopDUContext(topContext); if(isLocked() && (!m_navigationWidget.data() || !isVisible())) { // Automatically remove the locked state if the view is not visible or the widget was deleted, // because the locked state has side-effects on other navigation functionality. m_autoLocked = false; m_lockAction->setChecked(false); } if(m_navigationWidgetDeclaration == decl->id() && !force) return; m_navigationWidgetDeclaration = decl->id(); if (!isLocked() && (isVisible() || force)) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(decl, topContext); updateMainWidget(w); } } KDevelop::IndexedDeclaration ContextBrowserView::lockedDeclaration() const { if(m_lockAction->isChecked()) return declaration(); else return KDevelop::IndexedDeclaration(); } void ContextBrowserView::allowLockedUpdate() { m_allowLockedUpdate = true; } void ContextBrowserView::setNavigationWidget(QWidget* widget) { updateMainWidget(widget); } void ContextBrowserView::setContext(KDevelop::DUContext* context) { if(!context) return; m_lastUsedTopContext = IndexedTopDUContext(context->topContext()); if(context->owner()) { if(context->owner()->id() == m_navigationWidgetDeclaration) return; m_navigationWidgetDeclaration = context->owner()->id(); }else{ m_navigationWidgetDeclaration = DeclarationId(); } if (!isLocked() && isVisible()) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(context); updateMainWidget(w); } } void ContextBrowserView::setSpecialNavigationWidget(QWidget* widget) { if (!isLocked() && isVisible()) { Q_ASSERT(widget); updateMainWidget(widget); } else if(widget) { widget->deleteLater(); } } diff --git a/plugins/contextbrowser/contextbrowserview.h b/plugins/contextbrowser/contextbrowserview.h index 48a4d8417..ea25173b7 100644 --- a/plugins/contextbrowser/contextbrowserview.h +++ b/plugins/contextbrowser/contextbrowserview.h @@ -1,118 +1,118 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program 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 program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_CONTEXTBROWSERVIEW_H #define KDEVPLATFORM_PLUGIN_CONTEXTBROWSERVIEW_H #include #include #include #include #include #include #include class ContextBrowserPlugin; class QVBoxLayout; class QHBoxLayout; class QAction; class QCheckBox; class QMenu; class KComboBox; +class KToggleAction; namespace KDevelop { class IDocument; } class ContextBrowserView : public QWidget, public KDevelop::IToolViewActionListener { Q_OBJECT Q_INTERFACES(KDevelop::IToolViewActionListener) public: ContextBrowserView( ContextBrowserPlugin*, QWidget* parent ); ~ContextBrowserView() override; //duchain must be locked void setContext(KDevelop::DUContext* context); void setDeclaration(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, bool force = false); void setSpecialNavigationWidget(QWidget*); void updateMainWidget(QWidget*); /** Allows a single update of the view even if in locked state. * This is needed to browse while the locked button is checked. */ void allowLockedUpdate(); ///Returns the currently locked declaration, or invalid of none is locked atm. KDevelop::IndexedDeclaration lockedDeclaration() const; void setNavigationWidget(QWidget* widget); QWidget* navigationWidget() { return m_navigationWidget; } //duchain must be locked QWidget* createWidget(KDevelop::DUContext* context); //duchain must be locked QWidget* createWidget(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext); KDevelop::IndexedDeclaration declaration() const; ///Returns whether the view is currently locked bool isLocked() const; private Q_SLOTS: - void updateLockIcon(bool); void declarationMenu(); void navigationContextChanged(bool wasInitial, bool isInitial); void selectNextItem() override; void selectPreviousItem() override; private: void showEvent(QShowEvent* event) override; bool event(QEvent* event) override; void focusInEvent(QFocusEvent* event) override; void focusOutEvent(QFocusEvent* event) override; void resetWidget(); private: KDevelop::IndexedDeclaration m_declaration; ContextBrowserPlugin* m_plugin; QVBoxLayout* m_layout; - QAction* m_lockAction; + KToggleAction* m_lockAction; QAction* m_declarationMenuAction; QHBoxLayout* m_buttons; QPointer m_navigationWidget; KDevelop::DeclarationId m_navigationWidgetDeclaration; bool m_allowLockedUpdate; KDevelop::IndexedTopDUContext m_lastUsedTopContext; KDevelop::IndexedDUContext m_context; // Whether the lock-button was activated automatically due to user navigation bool m_autoLocked; }; #endif diff --git a/plugins/grepview/grepoutputmodel.cpp b/plugins/grepview/grepoutputmodel.cpp index fc7961658..b3c169c58 100644 --- a/plugins/grepview/grepoutputmodel.cpp +++ b/plugins/grepview/grepoutputmodel.cpp @@ -1,492 +1,494 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepoutputmodel.h" #include "grepviewplugin.h" #include "greputil.h" #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputItem::GrepOutputItem(DocumentChangePointer change, const QString &text, bool checkable) : QStandardItem(), m_change(change) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) setCheckState(Qt::Checked); } GrepOutputItem::GrepOutputItem(const QString& filename, const QString& text, bool checkable) : QStandardItem(), m_change(new DocumentChange(IndexedString(filename), KTextEditor::Range::invalid(), QString(), QString())) { setText(text); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); setCheckable(checkable); if(checkable) { #if QT_VERSION >= 0x050600 setAutoTristate(true); #else setTristate(true); #endif setCheckState(Qt::Checked); } } int GrepOutputItem::lineNumber() const { // line starts at 0 for cursor but we want to start at 1 return m_change->m_range.start().line() + 1; } QString GrepOutputItem::filename() const { return m_change->m_document.str(); } DocumentChangePointer GrepOutputItem::change() const { return m_change; } bool GrepOutputItem::isText() const { return m_change->m_range.isValid(); } void GrepOutputItem::propagateState() { for(int i = 0; i < rowCount(); i++) { GrepOutputItem *item = static_cast(child(i)); if(item->isEnabled()) { item->setCheckState(checkState()); item->propagateState(); } } } void GrepOutputItem::refreshState() { if(rowCount() > 0) { int checked = 0; int unchecked = 0; int enabled = 0; //only enabled items are relevants for(int i = 0; i < rowCount(); i++) { QStandardItem *item = child(i); if(item->isEnabled()) { enabled += 1; switch(child(i)->checkState()) { case Qt::Checked: checked += 1; break; case Qt::Unchecked: unchecked += 1; break; default: break; } } } if(enabled == 0) { setCheckState(Qt::Unchecked); setEnabled(false); } else if(checked == enabled) { setCheckState(Qt::Checked); } else if (unchecked == enabled) { setCheckState(Qt::Unchecked); } else { setCheckState(Qt::PartiallyChecked); } } if(GrepOutputItem *p = static_cast(parent())) { p->refreshState(); } } QVariant GrepOutputItem::data ( int role ) const { GrepOutputModel *grepModel = static_cast(model()); if(role == Qt::ToolTipRole && grepModel && isText()) { QString start = text().left(m_change->m_range.start().column()).toHtmlEscaped(); - QString repl = "" + grepModel->replacementFor(m_change->m_oldText).toHtmlEscaped() + ""; + // show replaced version in tooltip if we are in replace mode + const QString match = isCheckable() ? grepModel->replacementFor(m_change->m_oldText) : m_change->m_oldText; + const QString repl = QLatin1String("") + match.toHtmlEscaped() + QLatin1String(""); QString end = text().right(text().length() - m_change->m_range.end().column()).toHtmlEscaped(); return QVariant(QString(start + repl + end).trimmed()); } else if (role == Qt::FontRole) { return QFontDatabase::systemFont(QFontDatabase::FixedFont); } else { return QStandardItem::data(role); } } GrepOutputItem::~GrepOutputItem() {} /////////////////////////////////////////////////////////////// GrepOutputModel::GrepOutputModel( QObject *parent ) : QStandardItemModel( parent ), m_regExp(), m_replacement(), m_replacementTemplate(), m_finalReplacement(), m_finalUpToDate(false), m_rootItem(nullptr), m_fileCount(0), m_matchCount(0), m_itemsCheckable(false) { connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } GrepOutputModel::~GrepOutputModel() {} void GrepOutputModel::clear() { QStandardItemModel::clear(); // the above clear() also destroys the root item, so invalidate the pointer m_rootItem = nullptr; m_fileCount = 0; m_matchCount = 0; } void GrepOutputModel::setRegExp(const QRegExp& re) { m_regExp = re; m_finalUpToDate = false; } void GrepOutputModel::setReplacement(const QString& repl) { m_replacement = repl; m_finalUpToDate = false; } void GrepOutputModel::setReplacementTemplate(const QString& tmpl) { m_replacementTemplate = tmpl; m_finalUpToDate = false; } QString GrepOutputModel::replacementFor(const QString &text) { if(!m_finalUpToDate) { m_finalReplacement = substitudePattern(m_replacementTemplate, m_replacement); m_finalUpToDate = true; } return QString(text).replace(m_regExp, m_finalReplacement); } void GrepOutputModel::activate( const QModelIndex &idx ) { QStandardItem *stditem = itemFromIndex(idx); GrepOutputItem *grepitem = dynamic_cast(stditem); if( !grepitem || !grepitem->isText() ) return; QUrl url = QUrl::fromLocalFile(grepitem->filename()); int line = grepitem->lineNumber() - 1; KTextEditor::Range range( line, 0, line+1, 0); // Try to find the actual text range we found during the grep IDocument* doc = ICore::self()->documentController()->documentForUrl( url ); if(!doc) doc = ICore::self()->documentController()->openDocument( url, range ); if(!doc) return; if (KTextEditor::Document* tdoc = doc->textDocument()) { KTextEditor::Range matchRange = grepitem->change()->m_range; QString actualText = tdoc->text(matchRange); QString expectedText = grepitem->change()->m_oldText; if (actualText == expectedText) { range = matchRange; } } ICore::self()->documentController()->activateDocument( doc, range ); } QModelIndex GrepOutputModel::previousItemIndex(const QModelIndex ¤tIdx) const { GrepOutputItem* current_item = nullptr; if (!currentIdx.isValid()) { // no item selected, search recursively for the last item in search results QStandardItem *it = item(0,0); while (it) { QStandardItem *child = it->child( it->rowCount() - 1 ); if (!child) return it->index(); it = child; } return QModelIndex(); } else current_item = dynamic_cast(itemFromIndex(currentIdx)); if (current_item->parent() != nullptr) { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row > 0) { int idx_last_item = current_item->parent()->child(item_row - 1)->rowCount() - 1; return current_item->parent()->child(item_row - 1)->child(idx_last_item)->index(); } } else // the item is a match { if(row > 0) return current_item->parent()->child(row - 1)->index(); else // we return the index of the last item of the previous file { int parrent_row = current_item->parent()->row(); if(parrent_row > 0) { int idx_last_item = current_item->parent()->parent()->child(parrent_row - 1)->rowCount() - 1; return current_item->parent()->parent()->child(parrent_row - 1)->child(idx_last_item)->index(); } } } } return currentIdx; } QModelIndex GrepOutputModel::nextItemIndex(const QModelIndex ¤tIdx) const { GrepOutputItem* current_item = nullptr; if (!currentIdx.isValid()) { QStandardItem *it = item(0,0); if (!it) return QModelIndex(); current_item = dynamic_cast(it); } else current_item = dynamic_cast(itemFromIndex(currentIdx)); if (current_item->parent() == nullptr) { // root item with overview of search results if (current_item->rowCount() > 0) return nextItemIndex(current_item->child(0)->index()); else return QModelIndex(); } else { int row = currentIdx.row(); if(!current_item->isText()) // the item is a file { int item_row = current_item->row(); if(item_row < current_item->parent()->rowCount()) { return current_item->parent()->child(item_row)->child(0)->index(); } } else // the item is a match { if(row < current_item->parent()->rowCount() - 1) return current_item->parent()->child(row + 1)->index(); else // we return the index of the first item of the next file { int parrent_row = current_item->parent()->row(); if(parrent_row < current_item->parent()->parent()->rowCount() - 1) { return current_item->parent()->parent()->child(parrent_row + 1)->child(0)->index(); } } } } return currentIdx; } const GrepOutputItem *GrepOutputModel::getRootItem() const { return m_rootItem; } bool GrepOutputModel::itemsCheckable() const { return m_itemsCheckable; } void GrepOutputModel::makeItemsCheckable(bool checkable) { if(m_itemsCheckable == checkable) return; if(m_rootItem) makeItemsCheckable(checkable, m_rootItem); m_itemsCheckable = checkable; } void GrepOutputModel::makeItemsCheckable(bool checkable, GrepOutputItem* item) { item->setCheckable(checkable); if(checkable) { item->setCheckState(Qt::Checked); if(item->rowCount() && checkable) #if QT_VERSION >= 0x050600 item->setAutoTristate(true); #else item->setTristate(true); #endif } for(int row = 0; row < item->rowCount(); ++row) makeItemsCheckable(checkable, static_cast(item->child(row, 0))); } void GrepOutputModel::appendOutputs( const QString &filename, const GrepOutputItem::List &items ) { if(items.isEmpty()) return; if(rowCount() == 0) { m_rootItem = new GrepOutputItem(QString(), QString(), m_itemsCheckable); appendRow(m_rootItem); } m_fileCount += 1; m_matchCount += items.length(); const QString matchText = i18np("1 match", "%1 matches", m_matchCount); const QString fileText = i18np("1 file", "%1 files", m_fileCount); m_rootItem->setText(i18nc("%1 is e.g. '4 matches', %2 is e.g. '1 file'", "

%1 in %2

", matchText, fileText)); QString fnString = i18np("%2 (one match)", "%2 (%1 matches)", items.length(), ICore::self()->projectController()->prettyFileName(QUrl::fromLocalFile(filename))); GrepOutputItem *fileItem = new GrepOutputItem(filename, fnString, m_itemsCheckable); m_rootItem->appendRow(fileItem); foreach( const GrepOutputItem& item, items ) { GrepOutputItem* copy = new GrepOutputItem(item); copy->setCheckable(m_itemsCheckable); if(m_itemsCheckable) { copy->setCheckState(Qt::Checked); if(copy->rowCount()) #if QT_VERSION >= 0x050600 copy->setAutoTristate(true); #else copy->setTristate(true); #endif } fileItem->appendRow(copy); } } void GrepOutputModel::updateCheckState(QStandardItem* item) { // if we don't disconnect the SIGNAL, the setCheckState will call it in loop disconnect(this, &GrepOutputModel::itemChanged, nullptr, nullptr); // try to update checkstate on non checkable items would make a checkbox appear if(item->isCheckable()) { GrepOutputItem *it = static_cast(item); it->propagateState(); it->refreshState(); } connect(this, &GrepOutputModel::itemChanged, this, &GrepOutputModel::updateCheckState); } void GrepOutputModel::doReplacements() { Q_ASSERT(m_rootItem); if (!m_rootItem) return; // nothing to do, abort DocumentChangeSet changeSet; changeSet.setFormatPolicy(DocumentChangeSet::NoAutoFormat); for(int fileRow = 0; fileRow < m_rootItem->rowCount(); fileRow++) { GrepOutputItem *file = static_cast(m_rootItem->child(fileRow)); for(int matchRow = 0; matchRow < file->rowCount(); matchRow++) { GrepOutputItem *match = static_cast(file->child(matchRow)); if(match->checkState() == Qt::Checked) { DocumentChangePointer change = match->change(); // setting replacement text based on current replace value change->m_newText = replacementFor(change->m_oldText); changeSet.addChange(change); // this item cannot be checked anymore match->setCheckState(Qt::Unchecked); match->setEnabled(false); } } } DocumentChangeSet::ChangeResult result = changeSet.applyAllChanges(); if(!result.m_success) { DocumentChangePointer ch = result.m_reasonChange; if(ch) emit showErrorMessage(i18nc("%1 is the old text, %2 is the new text, %3 is the file path, %4 and %5 are its row and column", "Failed to replace %1 by %2 in %3:%4:%5", ch->m_oldText.toHtmlEscaped(), ch->m_newText.toHtmlEscaped(), ch->m_document.toUrl().toLocalFile(), ch->m_range.start().line() + 1, ch->m_range.start().column() + 1)); } } void GrepOutputModel::showMessageSlot(IStatus* status, const QString& message) { m_savedMessage = message; m_savedIStatus = status; showMessageEmit(); } void GrepOutputModel::showMessageEmit() { emit showMessage(m_savedIStatus, m_savedMessage); } bool GrepOutputModel::hasResults() { return(m_matchCount > 0); } diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index 2cacea3da..ac2412a56 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,395 +1,396 @@ /************************************************************************** * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepoutputview.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "ui_grepoutputview.h" #include "grepviewplugin.h" #include "grepdialog.h" #include "greputil.h" #include "grepjob.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; GrepOutputViewFactory::GrepOutputViewFactory(GrepViewPlugin* plugin) : m_plugin(plugin) {} QWidget* GrepOutputViewFactory::create(QWidget* parent) { return new GrepOutputView(parent, m_plugin); } Qt::DockWidgetArea GrepOutputViewFactory::defaultPosition() { return Qt::BottomDockWidgetArea; } QString GrepOutputViewFactory::id() const { return QStringLiteral("org.kdevelop.GrepOutputView"); } const int GrepOutputView::HISTORY_SIZE = 5; GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(nullptr) , m_prev(nullptr) , m_collapseAll(nullptr) , m_expandAll(nullptr) , m_clearSearchHistory(nullptr) , m_statusLabel(nullptr) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(QIcon::fromTheme(QStringLiteral("edit-find"), windowIcon())); m_prev = new QAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("&Previous Item"), this); m_prev->setEnabled(false); m_next = new QAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("&Next Item"), this); m_next->setEnabled(false); m_collapseAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-left-double")), i18n("C&ollapse All"), this); // TODO change icon m_collapseAll->setEnabled(false); m_expandAll = new QAction(QIcon::fromTheme(QStringLiteral("arrow-right-double")), i18n("&Expand All"), this); // TODO change icon m_expandAll->setEnabled(false); QAction *separator = new QAction(this); separator->setSeparator(true); QAction *newSearchAction = new QAction(QIcon::fromTheme(QStringLiteral("edit-find")), i18n("New &Search"), this); m_clearSearchHistory = new QAction(QIcon::fromTheme(QStringLiteral("edit-clear-list")), i18n("Clear Search History"), this); QAction *refreshAction = new QAction(QIcon::fromTheme(QStringLiteral("view-refresh")), i18n("Refresh"), this); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); addAction(refreshAction); addAction(m_clearSearchHistory); separator = new QAction(this); separator->setSeparator(true); addAction(separator); QWidgetAction *statusWidget = new QWidgetAction(this); m_statusLabel = new QLabel(this); statusWidget->setDefaultWidget(m_statusLabel); addAction(statusWidget); modelSelector->setEditable(false); modelSelector->setContextMenuPolicy(Qt::CustomContextMenu); connect(modelSelector, &KComboBox::customContextMenuRequested, this, &GrepOutputView::modelSelectorContextMenu); connect(modelSelector, static_cast(&KComboBox::currentIndexChanged), this, &GrepOutputView::changeModel); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); + resultsTreeView->setRootIsDecorated(false); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, &QAction::triggered, this, &GrepOutputView::selectPreviousItem); connect(m_next, &QAction::triggered, this, &GrepOutputView::selectNextItem); connect(m_collapseAll, &QAction::triggered, this, &GrepOutputView::collapseAllItems); connect(m_expandAll, &QAction::triggered, this, &GrepOutputView::expandAllItems); connect(applyButton, &QPushButton::clicked, this, &GrepOutputView::onApply); connect(m_clearSearchHistory, &QAction::triggered, this, &GrepOutputView::clearSearchHistory); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-ok-apply"))); connect(replacementCombo, &KComboBox::editTextChanged, this, &GrepOutputView::replacementTextChanged); connect(replacementCombo, static_cast(&KComboBox::returnPressed), this, &GrepOutputView::onApply); connect(newSearchAction, &QAction::triggered, this, &GrepOutputView::showDialog); connect(refreshAction, &QAction::triggered, this, &GrepOutputView::refresh); resultsTreeView->header()->setStretchLastSection(true); resultsTreeView->header()->setStretchLastSection(true); updateCheckable(); } void GrepOutputView::replacementTextChanged(QString) { updateCheckable(); if (model()) { // see https://bugs.kde.org/show_bug.cgi?id=274902 - renewModel can trigger a call here without an active model updateApplyState(model()->index(0, 0), model()->index(0, 0)); } } GrepOutputView::~GrepOutputView() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); cg.writeEntry("LastReplacementItems", qCombo2StringList(replacementCombo, true)); emit outputViewIsClosed(); } GrepOutputModel* GrepOutputView::renewModel(const QString& name, const QString& description) { // Crear oldest model while(modelSelector->count() > GrepOutputView::HISTORY_SIZE) { QVariant var = modelSelector->itemData(GrepOutputView::HISTORY_SIZE - 1); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(GrepOutputView::HISTORY_SIZE - 1); } replacementCombo->clearEditText(); GrepOutputModel* newModel = new GrepOutputModel(resultsTreeView); applyButton->setEnabled(false); // text may be already present newModel->setReplacement(replacementCombo->currentText()); connect(newModel, &GrepOutputModel::rowsRemoved, this, &GrepOutputView::rowsRemoved); connect(resultsTreeView, &QTreeView::activated, newModel, &GrepOutputModel::activate); connect(replacementCombo, &KComboBox::editTextChanged, newModel, &GrepOutputModel::setReplacement); connect(newModel, &GrepOutputModel::rowsInserted, this, &GrepOutputView::expandElements); connect(newModel, &GrepOutputModel::showErrorMessage, this, &GrepOutputView::showErrorMessage); connect(m_plugin, &GrepViewPlugin::grepJobFinished, this, &GrepOutputView::updateScrollArea); // appends new model to history const QString displayName = i18n("Search \"%1\" in %2 (at time %3)", name, description, QTime::currentTime().toString(QStringLiteral("hh:mm"))); modelSelector->insertItem(0, displayName, qVariantFromValue(newModel)); modelSelector->setCurrentIndex(0);//setCurrentItem(displayName); updateCheckable(); return newModel; } GrepOutputModel* GrepOutputView::model() { return static_cast(resultsTreeView->model()); } void GrepOutputView::changeModel(int index) { if (model()) { disconnect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); disconnect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); } replacementCombo->clearEditText(); //after deleting the whole search history, index is -1 if(index >= 0) { QVariant var = modelSelector->itemData(index); GrepOutputModel *resultModel = static_cast(qvariant_cast(var)); resultsTreeView->setModel(resultModel); resultsTreeView->expandAll(); connect(model(), &GrepOutputModel::showMessage, this, &GrepOutputView::showMessage); connect(model(), &GrepOutputModel::dataChanged, this, &GrepOutputView::updateApplyState); model()->showMessageEmit(); applyButton->setEnabled(model()->hasResults() && model()->getRootItem() && model()->getRootItem()->checkState() != Qt::Unchecked && !replacementCombo->currentText().isEmpty()); if(model()->hasResults()) expandElements(QModelIndex()); } updateCheckable(); updateApplyState(model()->index(0, 0), model()->index(0, 0)); } void GrepOutputView::setMessage(const QString& msg, MessageType type) { if (type == Error) { QPalette palette = m_statusLabel->palette(); KColorScheme::adjustForeground(palette, KColorScheme::NegativeText, QPalette::WindowText); m_statusLabel->setPalette(palette); } else { m_statusLabel->setPalette(QPalette()); } m_statusLabel->setText(msg); } void GrepOutputView::showErrorMessage( const QString& errorMessage ) { setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { setMessage(message, Information); } void GrepOutputView::onApply() { if(model()) { Q_ASSERT(model()->rowCount()); // ask a confirmation before an empty string replacement if(replacementCombo->currentText().length() == 0 && KMessageBox::questionYesNo(this, i18n("Do you want to replace with an empty string?"), i18n("Start replacement")) == KMessageBox::No) { return; } setEnabled(false); model()->doReplacements(); setEnabled(true); } } void GrepOutputView::showDialog() { m_plugin->showDialog(true); } void GrepOutputView::refresh() { m_plugin->showDialog(true, QString(), false); } void GrepOutputView::expandElements(const QModelIndex& index) { m_prev->setEnabled(true); m_next->setEnabled(true); m_collapseAll->setEnabled(true); m_expandAll->setEnabled(true); resultsTreeView->expand(index); } void GrepOutputView::selectPreviousItem() { if (!model()) { return; } QModelIndex prev_idx = model()->previousItemIndex(resultsTreeView->currentIndex()); if (prev_idx.isValid()) { resultsTreeView->setCurrentIndex(prev_idx); model()->activate(prev_idx); } } void GrepOutputView::selectNextItem() { if (!model()) { return; } QModelIndex next_idx = model()->nextItemIndex(resultsTreeView->currentIndex()); if (next_idx.isValid()) { resultsTreeView->setCurrentIndex(next_idx); model()->activate(next_idx); } } void GrepOutputView::collapseAllItems() { // Collapse everything resultsTreeView->collapseAll(); // Now reopen the first children, which correspond to the files. resultsTreeView->expand(resultsTreeView->model()->index(0, 0)); } void GrepOutputView::expandAllItems() { resultsTreeView->expandAll(); } void GrepOutputView::rowsRemoved() { m_prev->setEnabled(model()->rowCount()); m_next->setEnabled(model()->rowCount()); } void GrepOutputView::updateApplyState(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(bottomRight); if (!model() || !model()->hasResults()) { applyButton->setEnabled(false); return; } // we only care about the root item if(!topLeft.parent().isValid()) { applyButton->setEnabled(topLeft.data(Qt::CheckStateRole) != Qt::Unchecked && model()->itemsCheckable()); } } void GrepOutputView::updateCheckable() { if(model()) model()->makeItemsCheckable(!replacementCombo->currentText().isEmpty() || model()->itemsCheckable()); } void GrepOutputView::clearSearchHistory() { GrepJob *runningJob = m_plugin->grepJob(); if(runningJob) { runningJob->kill(); } while(modelSelector->count() > 0) { QVariant var = modelSelector->itemData(0); qvariant_cast(var)->deleteLater(); modelSelector->removeItem(0); } applyButton->setEnabled(false); m_statusLabel->setText(QString()); } void GrepOutputView::modelSelectorContextMenu(const QPoint& pos) { QPoint globalPos = modelSelector->mapToGlobal(pos); QMenu myMenu; myMenu.addAction(m_clearSearchHistory); myMenu.exec(globalPos); } void GrepOutputView::updateScrollArea() { for (int col = 0; col < model()->columnCount(); ++col) resultsTreeView->resizeColumnToContents(col); } diff --git a/plugins/problemreporter/problemsview.cpp b/plugins/problemreporter/problemsview.cpp index c174e7eac..2f832a2e5 100644 --- a/plugins/problemreporter/problemsview.cpp +++ b/plugins/problemreporter/problemsview.cpp @@ -1,530 +1,531 @@ /* * Copyright 2015 Laszlo Kis-Adam * * This program 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 program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemsview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "problemtreeview.h" #include "problemmodel.h" namespace KDevelop { void ProblemsView::setupActions() { { m_fullUpdateAction = new QAction(this); m_fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_fullUpdateAction->setText(i18n("Force Full Update")); m_fullUpdateAction->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); connect(m_fullUpdateAction, &QAction::triggered, this, [this]() { currentView()->model()->forceFullUpdate(); }); addAction(m_fullUpdateAction); } { m_scopeMenu = new KActionMenu(this); m_scopeMenu->setDelayed(false); m_scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); m_scopeMenu->setObjectName(QStringLiteral("scopeMenu")); QActionGroup* scopeActions = new QActionGroup(this); m_currentDocumentAction = new QAction(this); m_currentDocumentAction->setText(i18n("Current Document")); m_currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); QAction* openDocumentsAction = new QAction(this); openDocumentsAction->setText(i18n("Open Documents")); openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); QAction* currentProjectAction = new QAction(this); currentProjectAction->setText(i18n("Current Project")); currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); QAction* allProjectAction = new QAction(this); allProjectAction->setText(i18n("All Projects")); allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); QVector actions; actions.push_back(m_currentDocumentAction); actions.push_back(openDocumentsAction); actions.push_back(currentProjectAction); actions.push_back(allProjectAction); m_showAllAction = new QAction(this); m_showAllAction->setText(i18n("Show All")); m_showAllAction->setToolTip(i18nc("@info:tooltip", "Display ALL problems")); actions.push_back(m_showAllAction); foreach (QAction* action, actions) { action->setCheckable(true); scopeActions->addAction(action); m_scopeMenu->addAction(action); } addAction(m_scopeMenu); QSignalMapper* scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(m_currentDocumentAction, CurrentDocument); scopeMapper->setMapping(openDocumentsAction, OpenDocuments); scopeMapper->setMapping(currentProjectAction, CurrentProject); scopeMapper->setMapping(allProjectAction, AllProjects); connect(m_currentDocumentAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(openDocumentsAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(currentProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(allProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); { scopeMapper->setMapping(actions.last(), BypassScopeFilter); connect(actions.last(), &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); } connect(scopeMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { setScope(index); }); } { m_showImportsAction = new QAction(this); addAction(m_showImportsAction); m_showImportsAction->setCheckable(true); m_showImportsAction->setChecked(false); m_showImportsAction->setText(i18n("Show Imports")); m_showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); connect(m_showImportsAction, &QAction::triggered, this, [this](bool checked) { currentView()->model()->setShowImports(checked); }); } { m_severityActions = new QActionGroup(this); m_errorSeverityAction = new QAction(this); m_errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors")); m_errorSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-error"))); m_errorSeverityAction->setIconText(i18n("Show Errors")); m_warningSeverityAction = new QAction(this); m_warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display warnings")); m_warningSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-warning"))); m_warningSeverityAction->setIconText(i18n("Show Warnings")); m_hintSeverityAction = new QAction(this); m_hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display hints")); m_hintSeverityAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); m_hintSeverityAction->setIconText(i18n("Show Hints")); QAction* severityActionArray[] = { m_errorSeverityAction, m_warningSeverityAction, m_hintSeverityAction }; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); m_severityActions->addAction(severityActionArray[i]); addAction(severityActionArray[i]); } m_severityActions->setExclusive(false); m_hintSeverityAction->setChecked(true); m_warningSeverityAction->setChecked(true); m_errorSeverityAction->setChecked(true); connect(m_errorSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_warningSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); connect(m_hintSeverityAction, &QAction::toggled, this, &ProblemsView::handleSeverityActionToggled); } { m_groupingMenu = new KActionMenu(i18n("Grouping"), this); m_groupingMenu->setDelayed(false); QActionGroup* groupingActions = new QActionGroup(this); QAction* noGroupingAction = new QAction(i18n("None"), this); QAction* pathGroupingAction = new QAction(i18n("Path"), this); QAction* severityGroupingAction = new QAction(i18n("Severity"), this); QAction* groupingActionArray[] = { noGroupingAction, pathGroupingAction, severityGroupingAction }; for (unsigned i = 0; i < sizeof(groupingActionArray) / sizeof(QAction*); ++i) { QAction* action = groupingActionArray[i]; action->setCheckable(true); groupingActions->addAction(action); m_groupingMenu->addAction(action); } addAction(m_groupingMenu); noGroupingAction->setChecked(true); QSignalMapper* groupingMapper = new QSignalMapper(this); groupingMapper->setMapping(noGroupingAction, NoGrouping); groupingMapper->setMapping(pathGroupingAction, PathGrouping); groupingMapper->setMapping(severityGroupingAction, SeverityGrouping); connect(noGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(pathGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(severityGroupingAction, &QAction::triggered, groupingMapper, static_cast(&QSignalMapper::map)); connect(groupingMapper, static_cast(&QSignalMapper::mapped), this, [this](int index) { currentView()->model()->setGrouping(index); }); } { QTimer* filterTimer = new QTimer(this); filterTimer->setSingleShot(true); connect(filterTimer, &QTimer::timeout, this, [this]() { setFilter(m_filterEdit->text()); }); m_filterEdit = new QLineEdit(this); m_filterEdit->setClearButtonEnabled(true); m_filterEdit->setPlaceholderText(i18n("Search...")); QSizePolicy p(m_filterEdit->sizePolicy()); p.setHorizontalPolicy(QSizePolicy::Fixed); m_filterEdit->setSizePolicy(p); connect(m_filterEdit, &QLineEdit::textChanged, this, [filterTimer](const QString&) { filterTimer->start(500); }); QWidgetAction* filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(m_filterEdit); addAction(filterAction); m_prevTabIdx = -1; setFocusProxy(m_filterEdit); } } void ProblemsView::updateActions() { auto problemModel = currentView()->model(); Q_ASSERT(problemModel); m_fullUpdateAction->setVisible(problemModel->features().testFlag(ProblemModel::CanDoFullUpdate)); m_fullUpdateAction->setToolTip(problemModel->fullUpdateTooltip()); m_showImportsAction->setVisible(problemModel->features().testFlag(ProblemModel::CanShowImports)); m_scopeMenu->setVisible(problemModel->features().testFlag(ProblemModel::ScopeFilter)); m_severityActions->setVisible(problemModel->features().testFlag(ProblemModel::SeverityFilter)); m_groupingMenu->setVisible(problemModel->features().testFlag(ProblemModel::Grouping)); m_showAllAction->setVisible(problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)); m_showImportsAction->setChecked(false); problemModel->setShowImports(false); // Show All should be default if it's supported. It helps with error messages that are otherwise invisible if (problemModel->features().testFlag(ProblemModel::CanByPassScopeFilter)) { //actions.last()->setChecked(true); setScope(BypassScopeFilter); } else { m_currentDocumentAction->setChecked(true); setScope(CurrentDocument); } problemModel->setSeverities(IProblem::Error | IProblem::Warning | IProblem::Hint); setFocus(); // set focus to default widget (filterEdit) } /// TODO: Move to util? /// Note: Support for recursing into child indices would be nice class ItemViewWalker { public: explicit ItemViewWalker(QItemSelectionModel* itemView); void selectNextIndex(); void selectPreviousIndex(); enum Direction { NextIndex, PreviousIndex }; void selectIndex(Direction direction); private: QItemSelectionModel* m_selectionModel; }; ItemViewWalker::ItemViewWalker(QItemSelectionModel* itemView) : m_selectionModel(itemView) { } void ItemViewWalker::selectNextIndex() { selectIndex(NextIndex); } void ItemViewWalker::selectPreviousIndex() { selectIndex(PreviousIndex); } void ItemViewWalker::selectIndex(Direction direction) { if (!m_selectionModel) { return; } const QModelIndexList list = m_selectionModel->selectedRows(); const QModelIndex currentIndex = list.value(0); if (!currentIndex.isValid()) { /// no selection yet, just select the first const QModelIndex firstIndex = m_selectionModel->model()->index(0, 0); m_selectionModel->setCurrentIndex(firstIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); return; } const int nextRow = currentIndex.row() + (direction == NextIndex ? 1 : -1); const QModelIndex nextIndex = currentIndex.sibling(nextRow, 0); if (!nextIndex.isValid()) { return; /// never invalidate the selection } m_selectionModel->setCurrentIndex(nextIndex, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } ProblemsView::ProblemsView(QWidget* parent) : QWidget(parent) { setWindowTitle(i18n("Problems")); setWindowIcon(QIcon::fromTheme(QStringLiteral("script-error"), windowIcon())); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); m_tabWidget = new QTabWidget(this); m_tabWidget->setTabPosition(QTabWidget::South); + m_tabWidget->setDocumentMode(true); layout->addWidget(m_tabWidget); setupActions(); } ProblemsView::~ProblemsView() { } void ProblemsView::load() { m_tabWidget->clear(); KDevelop::ProblemModelSet* pms = KDevelop::ICore::self()->languageController()->problemModelSet(); QVector v = pms->models(); QVectorIterator itr(v); while (itr.hasNext()) { const KDevelop::ModelData& data = itr.next(); addModel(data); } connect(pms, &ProblemModelSet::added, this, &ProblemsView::onModelAdded); connect(pms, &ProblemModelSet::removed, this, &ProblemsView::onModelRemoved); connect(m_tabWidget, &QTabWidget::currentChanged, this, &ProblemsView::onCurrentChanged); if (m_tabWidget->currentIndex() == 0) { updateActions(); return; } m_tabWidget->setCurrentIndex(0); } void ProblemsView::onModelAdded(const ModelData& data) { addModel(data); } void ProblemsView::showModel(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_tabWidget->setCurrentIndex(i); return; } } } void ProblemsView::onModelRemoved(const QString& id) { for (int i = 0; i < m_models.size(); ++i) { if (m_models[i].id == id) { m_models.remove(i); QWidget* w = m_tabWidget->widget(i); m_tabWidget->removeTab(i); delete w; return; } } } void ProblemsView::onCurrentChanged(int idx) { if (idx == -1) return; setFilter(QStringLiteral(""), m_prevTabIdx); setFilter(QStringLiteral("")); m_prevTabIdx = idx; updateActions(); } void ProblemsView::onViewChanged() { ProblemTreeView* view = static_cast(sender()); int idx = m_tabWidget->indexOf(view); int rows = view->model()->rowCount(); updateTab(idx, rows); } void ProblemsView::addModel(const ModelData& newData) { // We implement follows tabs order: // // 1) First tab always used by "Parser" model due to it's the most important // problem listing, it should be at the front (K.Funk idea at #kdevelop IRC channel). // // 2) Other tabs are alphabetically ordered. static const QString parserId = QStringLiteral("Parser"); auto model = newData.model; auto view = new ProblemTreeView(nullptr, model); connect(view, &ProblemTreeView::changed, this, &ProblemsView::onViewChanged); connect(model, &ProblemModel::fullUpdateTooltipChanged, this, [this, model]() { if (currentView()->model() == model) { m_fullUpdateAction->setToolTip(model->fullUpdateTooltip()); } }); int insertIdx = 0; if (newData.id != parserId) { for (insertIdx = 0; insertIdx < m_models.size(); ++insertIdx) { const ModelData& currentData = m_models[insertIdx]; // Skip first element if it's already occupied by "Parser" model if (insertIdx == 0 && currentData.id == parserId) continue; if (currentData.name.localeAwareCompare(newData.name) > 0) break; } } m_tabWidget->insertTab(insertIdx, view, newData.name); m_models.insert(insertIdx, newData); updateTab(insertIdx, model->rowCount()); } void ProblemsView::updateTab(int idx, int rows) { if (idx < 0 || idx >= m_models.size()) return; const QString name = m_models[idx].name; const QString tabText = i18nc("%1: tab name, %2: number of problems", "%1 (%2)", name, rows); m_tabWidget->setTabText(idx, tabText); } ProblemTreeView* ProblemsView::currentView() const { return qobject_cast(m_tabWidget->currentWidget()); } void ProblemsView::selectNextItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectNextIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::selectPreviousItem() { auto view = currentView(); if (view) { ItemViewWalker walker(view->selectionModel()); walker.selectPreviousIndex(); view->openDocumentForCurrentProblem(); } } void ProblemsView::handleSeverityActionToggled() { currentView()->model()->setSeverities( (m_errorSeverityAction->isChecked() ? IProblem::Error : IProblem::Severities()) | (m_warningSeverityAction->isChecked() ? IProblem::Warning : IProblem::Severities()) | (m_hintSeverityAction->isChecked() ? IProblem::Hint : IProblem::Severities()) ); } void ProblemsView::setScope(int scope) { m_scopeMenu->setText(i18n("Scope: %1", m_scopeMenu->menu()->actions().at(scope)->text())); currentView()->model()->setScope(scope); } void ProblemsView::setFilter(const QString& filterText) { setFilter(filterText, m_tabWidget->currentIndex()); } void ProblemsView::setFilter(const QString& filterText, int tabIdx) { if (tabIdx < 0 || tabIdx >= m_tabWidget->count()) return; ProblemTreeView* view = static_cast(m_tabWidget->widget(tabIdx)); int rows = view->setFilter(filterText); updateTab(tabIdx, rows); if (tabIdx == m_tabWidget->currentIndex()) { QSignalBlocker blocker(m_filterEdit); m_filterEdit->setText(filterText); } } } diff --git a/shell/uicontroller.cpp b/shell/uicontroller.cpp index 5e3167f21..eed2deab3 100644 --- a/shell/uicontroller.cpp +++ b/shell/uicontroller.cpp @@ -1,757 +1,757 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program 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 program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "uicontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "configpage.h" #include "configdialog.h" #include "debug.h" #include "editorconfigpage.h" #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "partdocument.h" #include "textdocument.h" #include "documentcontroller.h" #include #include "workingsetcontroller.h" #include "workingsets/workingset.h" #include "settings/bgpreferences.h" #include "settings/languagepreferences.h" #include "settings/environmentpreferences.h" #include "settings/pluginpreferences.h" #include "settings/projectpreferences.h" #include "settings/sourceformattersettings.h" #include "settings/uipreferences.h" #include "settings/templateconfig.h" #include "settings/analyzerspreferences.h" #include "settings/documentationpreferences.h" namespace KDevelop { class UiControllerPrivate { public: explicit UiControllerPrivate(UiController *controller) : areasRestored(false), m_controller(controller) { if (Core::self()->workingSetControllerInternal()) Core::self()->workingSetControllerInternal()->initializeController(m_controller); m_controller->connect(m_controller, &Sublime::Controller::mainWindowAdded, m_controller, &UiController::mainWindowAdded); QMap desired; desired[QStringLiteral("org.kdevelop.ClassBrowserView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.DocumentsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.FileManagerView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.ProblemReporterView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.OutputView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.ContextBrowser")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.SnippetView")] = Sublime::Right; desired[QStringLiteral("org.kdevelop.ExternalScriptView")] = Sublime::Right; Sublime::Area* a = new Sublime::Area(m_controller, QStringLiteral("code"), i18n("Code")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("document-edit")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.debugger.VariablesView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.debugger.BreakpointsView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.StackView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.debugger.ConsoleView")] = Sublime::Bottom; desired[QStringLiteral("org.kdevelop.KonsoleView")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("debug"), i18n("Debug")); a->setDesiredToolViews(desired); - a->setIconName(QStringLiteral("tools-report-bug")); + a->setIconName(QStringLiteral("debug-run")); m_controller->addDefaultArea(a); desired.clear(); desired[QStringLiteral("org.kdevelop.ProjectsView")] = Sublime::Left; desired[QStringLiteral("org.kdevelop.PatchReview")] = Sublime::Bottom; a = new Sublime::Area(m_controller, QStringLiteral("review"), i18n("Review")); a->setDesiredToolViews(desired); a->setIconName(QStringLiteral("applications-engineering")); m_controller->addDefaultArea(a); if(!(Core::self()->setupFlags() & Core::NoUi)) { defaultMainWindow = new MainWindow(m_controller); m_controller->addMainWindow(defaultMainWindow); activeSublimeWindow = defaultMainWindow; } else { activeSublimeWindow = defaultMainWindow = nullptr; } m_assistantTimer.setSingleShot(true); m_assistantTimer.setInterval(100); } void widgetChanged(QWidget*, QWidget* now) { if (now) { Sublime::MainWindow* win = qobject_cast(now->window()); if( win ) { activeSublimeWindow = win; } } } Core *core; QPointer defaultMainWindow; QHash factoryDocuments; QPointer activeSublimeWindow; bool areasRestored; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; private: UiController *m_controller; }; class UiToolViewFactory: public Sublime::ToolFactory { public: explicit UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} ~UiToolViewFactory() override { delete m_factory; } QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = nullptr) override { Q_UNUSED( doc ); return m_factory->create(parent); } QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return m_factory->contextMenuActions( viewWidget ); } QList toolBarActions( QWidget* viewWidget ) const override { return m_factory->toolBarActions( viewWidget ); } QString id() const override { return m_factory->id(); } private: IToolViewFactory *m_factory; }; class ViewSelectorItem: public QListWidgetItem { public: explicit ViewSelectorItem(const QString &text, QListWidget *parent = nullptr, int type = Type) :QListWidgetItem(text, parent, type) {} IToolViewFactory *factory; }; class NewToolViewListWidget: public QListWidget { Q_OBJECT public: explicit NewToolViewListWidget(MainWindow *mw, QWidget* parent = nullptr) :QListWidget(parent), m_mw(mw) { connect(this, &NewToolViewListWidget::doubleClicked, this, &NewToolViewListWidget::addNewToolViewByDoubleClick); } Q_SIGNALS: void addNewToolView(MainWindow *mw, QListWidgetItem *item); private Q_SLOTS: void addNewToolViewByDoubleClick(QModelIndex index) { QListWidgetItem *item = itemFromIndex(index); // Disable item so that the toolview can not be added again. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); emit addNewToolView(m_mw, item); } private: MainWindow *m_mw; }; UiController::UiController(Core *core) :Sublime::Controller(nullptr), IUiController(), d(new UiControllerPrivate(this)) { setObjectName(QStringLiteral("UiController")); d->core = core; if (!defaultMainWindow() || (Core::self()->setupFlags() & Core::NoUi)) return; connect(qApp, &QApplication::focusChanged, this, [&] (QWidget* old, QWidget* now) { d->widgetChanged(old, now); } ); setupActions(); } UiController::~UiController() { delete d; } void UiController::setupActions() { } void UiController::mainWindowAdded(Sublime::MainWindow* mainWindow) { connect(mainWindow, &MainWindow::activeToolViewChanged, this, &UiController::slotActiveToolViewChanged); connect(mainWindow, &MainWindow::areaChanged, this, &UiController::slotAreaChanged); // also check after area reconstruction } // FIXME: currently, this always create new window. Probably, // should just rename it. void UiController::switchToArea(const QString &areaName, SwitchMode switchMode) { if (switchMode == ThisWindow) { showArea(areaName, activeSublimeWindow()); return; } MainWindow *main = new MainWindow(this); addMainWindow(main); showArea(areaName, main); main->initialize(); // WTF? First, enabling this code causes crashes since we // try to disconnect some already-deleted action, or something. // Second, this code will disconnection the clients from guiFactory // of the previous main window. Ick! #if 0 //we need to add all existing guiclients to the new mainwindow //@todo adymo: add only ones that belong to the area (when the area code is there) foreach (KXMLGUIClient *client, oldMain->guiFactory()->clients()) main->guiFactory()->addClient(client); #endif main->show(); } QWidget* UiController::findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) { if(!d->areasRestored || !activeArea()) return nullptr; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc && doc->title() == name && view->widget()) { if(flags & Raise) view->requestRaise(); return view->widget(); } } QWidget* ret = nullptr; if(flags & Create) { Sublime::ToolDocument* doc = d->factoryDocuments.value(factory); if(!doc) { doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments.insert(factory, doc); } Sublime::View* view = addToolViewToArea(factory, doc, activeArea()); if(view) ret = view->widget(); if(flags & Raise) findToolView(name, factory, Raise); } return ret; } void UiController::raiseToolView(QWidget* toolViewWidget) { if(!d->areasRestored) return; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { if(view->widget() == toolViewWidget) { view->requestRaise(); return; } } } void UiController::addToolView(const QString & name, IToolViewFactory *factory, FindFlags state) { if (!factory) return; qCDebug(SHELL) ; Sublime::ToolDocument *doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments[factory] = doc; /* Until areas are restored, we don't know which views should be really added, and which not, so we just record view availability. */ if (d->areasRestored && state != None) { foreach (Sublime::Area* area, allAreas()) { addToolViewToArea(factory, doc, area); } } } void KDevelop::UiController::raiseToolView(Sublime::View * view) { foreach( Sublime::Area* area, allAreas() ) { if( area->toolViews().contains( view ) ) area->raiseToolView( view ); } slotActiveToolViewChanged(view); } void UiController::slotAreaChanged(Sublime::Area*) { // this slot gets call if an area in *any* MainWindow changed // so let's first get the "active area" const auto area = activeSublimeWindow()->area(); if (area) { // walk through shown tool views and maku sure the const auto shownIds = area->shownToolViews(Sublime::AllPositions); foreach (Sublime::View* toolView, area->toolViews()) { if (shownIds.contains(toolView->document()->documentSpecifier())) { slotActiveToolViewChanged(toolView); } } } } void UiController::slotActiveToolViewChanged(Sublime::View* view) { if (!view) { return; } // record the last active tool view action listener if (qobject_cast(view->widget())) { d->activeActionListener = view->widget(); } } void KDevelop::UiController::removeToolView(IToolViewFactory *factory) { if (!factory) return; qCDebug(SHELL) ; //delete the tooldocument Sublime::ToolDocument *doc = d->factoryDocuments.value(factory); ///@todo adymo: on document deletion all its views shall be also deleted foreach (Sublime::View *view, doc->views()) { foreach (Sublime::Area *area, allAreas()) if (area->removeToolView(view)) view->deleteLater(); } d->factoryDocuments.remove(factory); delete doc; } Sublime::Area *UiController::activeArea() { Sublime::MainWindow *m = activeSublimeWindow(); if (m) return activeSublimeWindow()->area(); return nullptr; } Sublime::MainWindow *UiController::activeSublimeWindow() { return d->activeSublimeWindow; } MainWindow *UiController::defaultMainWindow() { return d->defaultMainWindow; } void UiController::initialize() { defaultMainWindow()->initialize(); } void UiController::cleanup() { foreach (Sublime::MainWindow* w, mainWindows()) w->saveSettings(); saveAllAreas(KSharedConfig::openConfig()); } void UiController::selectNewToolViewToAdd(MainWindow *mw) { if (!mw || !mw->area()) return; QDialog *dia = new QDialog(mw); dia->setWindowTitle(i18n("Select Tool View to Add")); auto mainLayout = new QVBoxLayout(dia); NewToolViewListWidget *list = new NewToolViewListWidget(mw, dia); list->setSelectionMode(QAbstractItemView::ExtendedSelection); list->setSortingEnabled(true); for (QHash::const_iterator it = d->factoryDocuments.constBegin(); it != d->factoryDocuments.constEnd(); ++it) { ViewSelectorItem *item = new ViewSelectorItem(it.value()->title(), list); item->factory = it.key(); if (!item->factory->allowMultiple() && toolViewPresent(it.value(), mw->area())) { // Disable item if the toolview is already present. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } list->addItem(item); } list->setFocus(); connect(list, &NewToolViewListWidget::addNewToolView, this, &UiController::addNewToolView); mainLayout->addWidget(list); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dia->connect(buttonBox, &QDialogButtonBox::accepted, dia, &QDialog::accept); dia->connect(buttonBox, &QDialogButtonBox::rejected, dia, &QDialog::reject); mainLayout->addWidget(buttonBox); if (dia->exec() == QDialog::Accepted) { foreach (QListWidgetItem* item, list->selectedItems()) { addNewToolView(mw, item); } } delete dia; } void UiController::addNewToolView(MainWindow *mw, QListWidgetItem* item) { ViewSelectorItem *current = static_cast(item); Sublime::ToolDocument *doc = d->factoryDocuments[current->factory]; Sublime::View *view = doc->createView(); mw->area()->addToolView(view, Sublime::dockAreaToPosition(current->factory->defaultPosition())); current->factory->viewCreated(view); } void UiController::showSettingsDialog() { auto parent = activeMainWindow(); auto editorConfigPage = new EditorConfigPage(parent); auto languageConfigPage = new LanguagePreferences(parent); auto analyzersPreferences = new AnalyzersPreferences(parent); auto documentationPreferences = new DocumentationPreferences(parent); auto configPages = QVector { new UiPreferences(parent), new PluginPreferences(parent), new SourceFormatterSettings(parent), new ProjectPreferences(parent), new EnvironmentPreferences(QString(), parent), new TemplateConfig(parent), editorConfigPage }; ConfigDialog cfgDlg(configPages, parent); auto addPluginPages = [&](IPlugin* plugin) { for (int i = 0, numPages = plugin->configPages(); i < numPages; ++i) { auto page = plugin->configPage(i, &cfgDlg); if (!page) continue; if (page->configPageType() == ConfigPage::LanguageConfigPage) { cfgDlg.addSubConfigPage(languageConfigPage, page); } else if (page->configPageType() == ConfigPage::AnalyzerConfigPage) { cfgDlg.addSubConfigPage(analyzersPreferences, page); } else if (page->configPageType() == ConfigPage::DocumentationConfigPage) { cfgDlg.addSubConfigPage(documentationPreferences, page); } else { // insert them before the editor config page cfgDlg.addConfigPage(page, editorConfigPage); } } }; cfgDlg.addConfigPage(documentationPreferences, configPages[5]); cfgDlg.addConfigPage(analyzersPreferences, documentationPreferences); cfgDlg.addConfigPage(languageConfigPage, analyzersPreferences); cfgDlg.addSubConfigPage(languageConfigPage, new BGPreferences(parent)); foreach (IPlugin* plugin, ICore::self()->pluginController()->loadedPlugins()) { addPluginPages(plugin); } // TODO: only load settings if a UI related page was changed? connect(&cfgDlg, &ConfigDialog::configSaved, activeSublimeWindow(), &Sublime::MainWindow::loadSettings); // make sure that pages get added whenever a new plugin is loaded (probably from the plugin selection dialog) // removal on plugin unload is already handled in ConfigDialog connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, &cfgDlg, addPluginPages); cfgDlg.exec(); } Sublime::Controller* UiController::controller() { return this; } KParts::MainWindow *UiController::activeMainWindow() { return activeSublimeWindow(); } void UiController::saveArea(Sublime::Area * area, KConfigGroup & group) { area->save(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); set->saveFromArea(area, area->rootIndex()); } } void UiController::loadArea(Sublime::Area * area, const KConfigGroup & group) { area->load(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); Q_ASSERT(set->isConnected(area)); Q_UNUSED(set); } } void UiController::saveAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = mainWindows().size(); uiConfig.writeEntry("Main Windows Count", wc); for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); foreach (Sublime::Area* defaultArea, defaultAreas()) { // FIXME: using object name seems ugly. QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); areaConfig.deleteGroup(); areaConfig.writeEntry("id", type); saveArea(area, areaConfig); areaConfig.sync(); } } uiConfig.sync(); } void UiController::loadAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = uiConfig.readEntry("Main Windows Count", 1); /* It is expected the main windows are restored before restoring areas. */ if (wc > mainWindows().size()) wc = mainWindows().size(); QList changedAreas; /* Offer all toolviews to the default areas. */ foreach (Sublime::Area *area, defaultAreas()) { QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } /* Restore per-windows areas. */ for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); Sublime::MainWindow *mw = mainWindows()[w]; /* We loop over default areas. This means that if the config file has an area of some type that is not in default set, we'd just ignore it. I think it's fine -- the model were a given mainwindow can has it's own area types not represented in the default set is way too complex. */ foreach (Sublime::Area* defaultArea, defaultAreas()) { QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); qCDebug(SHELL) << "Trying to restore area " << type; /* This is just an easy check that a group exists, to avoid "restoring" area from empty config group, wiping away programmatically installed defaults. */ if (areaConfig.readEntry("id", "") == type) { qCDebug(SHELL) << "Restoring area " << type; loadArea(area, areaConfig); } // At this point we know which toolviews the area wants. // Tender all tool views we have. QHash::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } // Force reload of the changes. showAreaInternal(mw->area(), mw); mw->enableAreaSettingsSave(); } d->areasRestored = true; } void UiController::addToolViewToDockArea(IToolViewFactory* factory, Qt::DockWidgetArea area) { addToolViewToArea(factory, d->factoryDocuments.value(factory), activeArea(), Sublime::dockAreaToPosition(area)); } bool UiController::toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area) { for (Sublime::View *view : doc->views()) { if( area->toolViews().contains( view ) ) return true; } return false; } void UiController::addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area) { if (area->wantToolView(factory->id())) { addToolViewToArea(factory, doc, area); } } Sublime::View* UiController::addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p) { Sublime::View* view = doc->createView(); area->addToolView( view, p == Sublime::AllPositions ? Sublime::dockAreaToPosition(factory->defaultPosition()) : p); connect(view, &Sublime::View::raise, this, static_cast(&UiController::raiseToolView)); factory->viewCreated(view); return view; } void UiController::registerStatus(QObject* status) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; mw->registerStatus(status); } void UiController::showErrorMessage(const QString& message, int timeout) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } const QHash< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { return d->factoryDocuments; } QWidget* UiController::activeToolViewActionListener() const { return d->activeActionListener; } QList UiController::allAreas() const { return Sublime::Controller::allAreas(); } } #include "uicontroller.moc" #include "moc_uicontroller.cpp"