diff --git a/language/classmodel/documentclassesfolder.cpp b/language/classmodel/documentclassesfolder.cpp index 7fee4272c..67b616128 100644 --- a/language/classmodel/documentclassesfolder.cpp +++ b/language/classmodel/documentclassesfolder.cpp @@ -1,451 +1,451 @@ /* * KDevelop Class Browser * * Copyright 2009 Lior Mualem * * 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 "documentclassesfolder.h" #include "../duchain/declaration.h" #include "../duchain/duchainlock.h" #include "../duchain/duchain.h" #include "../duchain/persistentsymboltable.h" #include "../duchain/codemodel.h" #include #include #include using namespace KDevelop; using namespace ClassModelNodes; ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// /// Contains a static list of classes within the namespace. class ClassModelNodes::StaticNamespaceFolderNode : public Node { public: StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, NodesModelInterface* a_model); /// Returns the qualified identifier for this node const KDevelop::QualifiedIdentifier& qualifiedIdentifier() const { return m_identifier; } public: // Node overrides bool getIcon(QIcon& a_resultIcon) override; int getScore() const override { return 101; } private: /// The namespace identifier. KDevelop::QualifiedIdentifier m_identifier; }; StaticNamespaceFolderNode::StaticNamespaceFolderNode(const KDevelop::QualifiedIdentifier& a_identifier, NodesModelInterface* a_model) : Node(a_identifier.last().toString(), a_model) , m_identifier(a_identifier) { } bool StaticNamespaceFolderNode::getIcon(QIcon& a_resultIcon) { static QIcon folderIcon = QIcon::fromTheme(QStringLiteral("namespace")); a_resultIcon = folderIcon; return true; } ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// DocumentClassesFolder::OpenedFileClassItem::OpenedFileClassItem(const KDevelop::IndexedString& a_file, const KDevelop::IndexedQualifiedIdentifier& a_classIdentifier, ClassModelNodes::ClassNode* a_nodeItem) : file(a_file) , classIdentifier(a_classIdentifier) , nodeItem(a_nodeItem) { } DocumentClassesFolder::DocumentClassesFolder(const QString& a_displayName, NodesModelInterface* a_model) : DynamicFolderNode(a_displayName, a_model) , m_updateTimer( new QTimer(this) ) { connect( m_updateTimer, &QTimer::timeout, this, &DocumentClassesFolder::updateChangedFiles); } void DocumentClassesFolder::updateChangedFiles() { bool hadChanges = false; // re-parse changed documents. foreach( const IndexedString& file, m_updatedFiles ) { // Make sure it's one of the monitored files. if ( m_openFiles.contains(file) ) hadChanges |= updateDocument(file); } // Processed all files. m_updatedFiles.clear(); // Sort if had changes. if ( hadChanges ) recursiveSort(); } void DocumentClassesFolder::nodeCleared() { // Clear cached namespaces list (node was cleared). m_namespaces.clear(); // Clear open files and classes list m_openFiles.clear(); m_openFilesClasses.clear(); // Stop the update timer. m_updateTimer->stop(); } void DocumentClassesFolder::populateNode() { // Start updates timer - this is the required delay. m_updateTimer->start(2000); } QSet< KDevelop::IndexedString > DocumentClassesFolder::getAllOpenDocuments() { return m_openFiles; } ClassNode* DocumentClassesFolder::findClassNode(const IndexedQualifiedIdentifier& a_id) { // Make sure that the classes node is populated, otherwise // the lookup will not work. performPopulateNode(); ClassIdentifierIterator iter = m_openFilesClasses.get().find(a_id); if ( iter == m_openFilesClasses.get().end() ) return nullptr; // If the node is invisible - make it visible by going over the identifiers list. if ( iter->nodeItem == nullptr ) { QualifiedIdentifier qualifiedIdentifier = a_id.identifier(); // Ignore zero length identifiers. if ( qualifiedIdentifier.count() == 0 ) return nullptr; ClassNode* closestNode = nullptr; int closestNodeIdLen = qualifiedIdentifier.count(); // First find the closest visible class node by reverse iteration over the id list. while ( (closestNodeIdLen > 0) && (closestNode == nullptr) ) { // Omit one from the end. --closestNodeIdLen; // Find the closest class. closestNode = findClassNode(qualifiedIdentifier.mid(0, closestNodeIdLen)); } if ( closestNode != nullptr ) { // Start iterating forward from this node by exposing each class. // By the end of this loop, closestNode should hold the actual node. while ( closestNode && (closestNodeIdLen < qualifiedIdentifier.count()) ) { // Try the next Id. ++closestNodeIdLen; closestNode = closestNode->findSubClass(qualifiedIdentifier.mid(0, closestNodeIdLen)); } } return closestNode; } return iter->nodeItem; } void DocumentClassesFolder::closeDocument(const IndexedString& a_file) { // Get list of nodes associated with this file and remove them. std::pair< FileIterator, FileIterator > range = m_openFilesClasses.get().equal_range( a_file ); if ( range.first != m_openFilesClasses.get().end() ) { BOOST_FOREACH( const OpenedFileClassItem& item, range ) { if ( item.nodeItem ) removeClassNode(item.nodeItem); } // Clear the lists m_openFilesClasses.get().erase(range.first, range.second); } // Clear the file from the list of monitored documents. m_openFiles.remove(a_file); } bool DocumentClassesFolder::updateDocument(const KDevelop::IndexedString& a_file) { uint codeModelItemCount = 0; const CodeModelItem* codeModelItems; CodeModel::self().items(a_file, codeModelItemCount, codeModelItems); // List of declared namespaces in this file. QSet< QualifiedIdentifier > declaredNamespaces; // List of removed classes - it initially contains all the known classes, we'll eliminate them // one by one later on when we encounter them in the document. QMap< IndexedQualifiedIdentifier, FileIterator > removedClasses; { std::pair< FileIterator, FileIterator > range = m_openFilesClasses.get().equal_range( a_file ); for ( FileIterator iter = range.first; iter != range.second; ++iter ) { removedClasses.insert(iter->classIdentifier, iter); } } bool documentChanged = false; for(uint codeModelItemIndex = 0; codeModelItemIndex < codeModelItemCount; ++codeModelItemIndex) { const CodeModelItem& item = codeModelItems[codeModelItemIndex]; // Don't insert unknown or forward declarations into the class browser - if ( (item.kind & CodeModelItem::Unknown) || (item.kind & CodeModelItem::ForwardDeclaration) ) + if ( item.kind == CodeModelItem::Unknown || (item.kind & CodeModelItem::ForwardDeclaration) ) continue; KDevelop::QualifiedIdentifier id = item.id.identifier(); // Don't add empty identifiers. if ( id.count() == 0 ) continue; // If it's a namespace, create it in the list. if ( item.kind & CodeModelItem::Namespace ) { // This should create the namespace folder and add it to the cache. getNamespaceFolder(id); // Add to the locally created namespaces. declaredNamespaces.insert(id); } else if ( item.kind & CodeModelItem::Class ) { // Ignore empty unnamed classes. if ( id.last().toString().isEmpty() ) continue; // See if it matches our filter? if ( isClassFiltered(id) ) continue; // Is this a new class or an existing class? if ( removedClasses.contains(id) ) { // It already exist - remove it from the known classes and continue. removedClasses.remove(id); continue; } // Where should we put this class? Node* parentNode = nullptr; // Check if it's namespaced and add it to the proper namespace. if ( id.count() > 1 ) { QualifiedIdentifier parentIdentifier(id.left(-1)); // Look up the namespace in the cache. // If we fail to find it we assume that the parent context is a class // and in that case, when the parent class gets expanded, it will show it. NamespacesMap::iterator iter = m_namespaces.find(parentIdentifier); if ( iter != m_namespaces.end() ) { // Add to the namespace node. parentNode = iter.value(); } else { // Reaching here means we didn't encounter any namespace declaration in the document // But a class might still be declared under a namespace. // So we'll perform a more through search to see if it's under a namespace. DUChainReadLocker readLock(DUChain::lock()); uint declsCount = 0; const IndexedDeclaration* decls; PersistentSymbolTable::self().declarations(parentIdentifier, declsCount, decls); for ( uint i = 0; i < declsCount; ++i ) { // Look for the first valid declaration. if ( decls->declaration() ) { // See if it should be namespaced. if ( decls->declaration()->kind() == Declaration::Namespace ) { // This should create the namespace folder and add it to the cache. parentNode = getNamespaceFolder(parentIdentifier); // Add to the locally created namespaces. declaredNamespaces.insert(parentIdentifier); } break; } } } } else { // Add to the main root. parentNode = this; } ClassNode* newNode = nullptr; if ( parentNode != nullptr ) { // Create the new node and add it. IndexedDeclaration decl; uint count = 0; const IndexedDeclaration* declarations; DUChainReadLocker lock; PersistentSymbolTable::self().declarations(item.id, count, declarations); for ( uint i = 0; i < count; ++i ) { if (declarations[i].indexedTopContext().url() == a_file) { decl = declarations[i]; break; } } if (decl.isValid()) { newNode = new ClassNode(decl.declaration(), m_model); parentNode->addNode( newNode ); } } // Insert it to the map - newNode can be 0 - meaning the class is hidden. m_openFilesClasses.insert( OpenedFileClassItem( a_file, id, newNode ) ); documentChanged = true; } } // Remove empty namespaces from the list. // We need this because when a file gets unloaded, we unload the declared classes in it // and if a namespace has no class in it, it'll forever exist and no one will remove it // from the children list. foreach( const QualifiedIdentifier& id, declaredNamespaces ) removeEmptyNamespace(id); // Clear erased classes. foreach( const FileIterator item, removedClasses ) { if ( item->nodeItem ) removeClassNode(item->nodeItem); m_openFilesClasses.get().erase(item); documentChanged = true; } return documentChanged; } void DocumentClassesFolder::parseDocument(const IndexedString& a_file) { // Add the document to the list of open files - this means we monitor it. if ( !m_openFiles.contains(a_file) ) m_openFiles.insert(a_file); updateDocument(a_file); } void DocumentClassesFolder::removeClassNode(ClassModelNodes::ClassNode* a_node) { // Get the parent namespace identifier. QualifiedIdentifier parentNamespaceIdentifier; if ( auto namespaceParent = dynamic_cast(a_node->getParent()) ) { parentNamespaceIdentifier = namespaceParent->qualifiedIdentifier(); } // Remove the node. a_node->removeSelf(); // Remove empty namespace removeEmptyNamespace(parentNamespaceIdentifier); } void DocumentClassesFolder::removeEmptyNamespace(const QualifiedIdentifier& a_identifier) { // Stop condition. if ( a_identifier.count() == 0 ) return; // Look it up in the cache. NamespacesMap::iterator iter = m_namespaces.find(a_identifier); if ( iter != m_namespaces.end() ) { if ( !(*iter)->hasChildren() ) { // Remove this node and try to remove the parent node. QualifiedIdentifier parentIdentifier = (*iter)->qualifiedIdentifier().left(-1); (*iter)->removeSelf(); m_namespaces.remove(a_identifier); removeEmptyNamespace(parentIdentifier); } } } StaticNamespaceFolderNode* DocumentClassesFolder::getNamespaceFolder(const KDevelop::QualifiedIdentifier& a_identifier) { // Stop condition. if ( a_identifier.count() == 0 ) return nullptr; // Look it up in the cache. NamespacesMap::iterator iter = m_namespaces.find(a_identifier); if ( iter == m_namespaces.end() ) { // It's not in the cache - create folders up to it. Node* parentNode = getNamespaceFolder(a_identifier.left(-1)); if ( parentNode == nullptr ) parentNode = this; // Create the new node. StaticNamespaceFolderNode* newNode = new StaticNamespaceFolderNode(a_identifier, m_model); parentNode->addNode( newNode ); // Add it to the cache. m_namespaces.insert( a_identifier, newNode ); // Return the result. return newNode; } else return *iter; } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/cvs/cvsproxy.cpp b/plugins/cvs/cvsproxy.cpp index 75e8f8053..287cb3efe 100644 --- a/plugins/cvs/cvsproxy.cpp +++ b/plugins/cvs/cvsproxy.cpp @@ -1,480 +1,480 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * * * 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 "cvsproxy.h" #include #include #include #include #include #include #include #include "cvsjob.h" #include "cvsannotatejob.h" #include "cvslogjob.h" #include "cvsstatusjob.h" #include "cvsdiffjob.h" #include "debug.h" #include CvsProxy::CvsProxy(KDevelop::IPlugin* parent) : QObject(parent), vcsplugin(parent) { } CvsProxy::~CvsProxy() { } bool CvsProxy::isValidDirectory(QUrl dirPath) const { const QFileInfo fsObject( dirPath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); return dir.exists(QStringLiteral("CVS")); } bool CvsProxy::isVersionControlled(QUrl filePath) const { const QFileInfo fsObject( filePath.toLocalFile() ); QDir dir = fsObject.isDir() ? fsObject.absoluteDir() : fsObject.dir(); if( !dir.cd(QStringLiteral("CVS")) ) return false; if( fsObject.isDir() ) return true; QFile cvsEntries( dir.absoluteFilePath(QStringLiteral("Entries")) ); cvsEntries.open( QIODevice::ReadOnly ); QString cvsEntriesData = cvsEntries.readAll(); cvsEntries.close(); return ( cvsEntriesData.indexOf( fsObject.fileName() ) != -1 ); } bool CvsProxy::prepareJob(CvsJob* job, const QString& repository, enum RequestedOperation op) { // Only do this check if it's a normal operation like diff, log ... // For other operations like "cvs import" isValidDirectory() would fail as the // directory is not yet under CVS control if (op == CvsProxy::NormalOperation && !isValidDirectory(QUrl::fromLocalFile(repository))) { qCDebug(PLUGIN_CVS) << repository << " is not a valid CVS repository"; return false; } // clear commands and args from a possible previous run job->clear(); // setup the working directory for the new job job->setDirectory(repository); return true; } bool CvsProxy::addFileList(CvsJob* job, const QString& repository, const QList& urls) { QStringList args; QDir repoDir(repository); foreach(const QUrl &url, urls) { ///@todo this is ok for now, but what if some of the urls are not /// to the given repository const QString file = repoDir.relativeFilePath(url.toLocalFile()); args << KShell::quoteArg( file ); } *job << args; return true; } QString CvsProxy::convertVcsRevisionToString(const KDevelop::VcsRevision & rev) { QString str; switch (rev.revisionType()) { case KDevelop::VcsRevision::Special: break; case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) str = "-r"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::Date: if (rev.revisionValue().isValid()) str = "-D"+rev.revisionValue().toString(); break; case KDevelop::VcsRevision::GlobalNumber: // !! NOT SUPPORTED BY CVS !! default: break; } return str; } QString CvsProxy::convertRevisionToPrevious(const KDevelop::VcsRevision& rev) { QString str; // this only works if the revision is a real revisionnumber and not a date or special switch (rev.revisionType()) { case KDevelop::VcsRevision::FileNumber: if (rev.revisionValue().isValid()) { QString orig = rev.revisionValue().toString(); // First we need to find the base (aka branch-part) of the revision number which will not change QString base(orig); base.truncate(orig.lastIndexOf(QLatin1Char('.'))); // next we need to cut off the last part of the revision number // this number is a count of revisions with a branch // so if we want to diff to the previous we just need to lower it by one int number = orig.midRef(orig.lastIndexOf(QLatin1Char('.'))+1).toInt(); if (number > 1) // of course this is only possible if our revision is not the first on the branch number--; str = QStringLiteral("-r") + base + '.' + QString::number(number); qCDebug(PLUGIN_CVS) << "Converted revision "<(); if (specialtype == KDevelop::VcsRevision::Previous) { rA = convertRevisionToPrevious(revB); } } else { rA = convertVcsRevisionToString(revA); } if (!rA.isEmpty()) *job << rA; QString rB = convertVcsRevisionToString(revB); if (!rB.isEmpty()) *job << rB; // in case the QUrl is a directory there is no filename if (!info.fileName().isEmpty()) *job << KShell::quoteArg(info.fileName()); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob * CvsProxy::annotate(const QUrl& url, const KDevelop::VcsRevision& rev) { QFileInfo info(url.toLocalFile()); CvsAnnotateJob* job = new CvsAnnotateJob(vcsplugin); if ( prepareJob(job, info.absolutePath()) ) { *job << "cvs"; *job << "annotate"; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; *job << KShell::quoteArg(info.fileName()); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob* CvsProxy::edit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "edit"; addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob* CvsProxy::unedit(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "unedit"; addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob* CvsProxy::editors(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "editors"; addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob* CvsProxy::commit(const QString& repo, const QList& files, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "commit"; *job << "-m"; *job << KShell::quoteArg( message ); addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob* CvsProxy::add(const QString& repo, const QList& files, bool recursiv, bool binary) { Q_UNUSED(recursiv); // FIXME recursiv is not implemented yet CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "add"; if (binary) { *job << "-kb"; } addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob * CvsProxy::remove(const QString& repo, const QList& files) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "remove"; *job << "-f"; //existing files will be deleted addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob * CvsProxy::update(const QString& repo, const QList& files, const KDevelop::VcsRevision & rev, const QString & updateOptions, bool recursive, bool pruneDirs, bool createDirs) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "update"; if (recursive) *job << "-R"; else *job << "-L"; if (pruneDirs) *job << "-P"; if (createDirs) *job << "-d"; if (!updateOptions.isEmpty()) *job << updateOptions; QString revision = convertVcsRevisionToString(rev); if (!revision.isEmpty()) *job << revision; addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob * CvsProxy::import(const QUrl& directory, const QString & server, const QString & repositoryName, const QString & vendortag, const QString & releasetag, const QString& message) { CvsJob* job = new CvsJob(vcsplugin); if ( prepareJob(job, directory.toLocalFile(), CvsProxy::Import) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d"; *job << server; *job << "import"; *job << "-m"; *job << KShell::quoteArg( message ); *job << repositoryName; *job << vendortag; *job << releasetag; return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob * CvsProxy::checkout(const QUrl& targetDir, const QString & server, const QString & module, const QString & checkoutOptions, const QString & revision, bool recursive, bool pruneDirs) { CvsJob* job = new CvsJob(vcsplugin); ///@todo when doing a checkout we don't have the targetdir yet, /// for now it'll work to just run the command from the root if ( prepareJob(job, QStringLiteral("/"), CvsProxy::CheckOut) ) { *job << "cvs"; *job << "-q"; // don't print directory changes *job << "-d" << server; *job << "checkout"; if (!checkoutOptions.isEmpty()) *job << checkoutOptions; if (!revision.isEmpty()) { *job << "-r" << revision; } if (pruneDirs) *job << "-P"; if (!recursive) *job << "-l"; *job << "-d" << targetDir.toString(QUrl::PreferLocalFile | QUrl::StripTrailingSlash); *job << module; return job; } - if (job) delete job; + delete job; return nullptr; } CvsJob * CvsProxy::status(const QString & repo, const QList & files, bool recursive, bool taginfo) { CvsStatusJob* job = new CvsStatusJob(vcsplugin); job->setCommunicationMode( KProcess::MergedChannels ); if ( prepareJob(job, repo) ) { *job << "cvs"; *job << "status"; if (recursive) *job << "-R"; else *job << "-l"; if (taginfo) *job << "-v"; addFileList(job, repo, files); return job; } - if (job) delete job; + delete job; return nullptr; } diff --git a/plugins/quickopen/expandingtree/expandingdelegate.cpp b/plugins/quickopen/expandingtree/expandingdelegate.cpp index cb6a3e391..024eb1fab 100644 --- a/plugins/quickopen/expandingtree/expandingdelegate.cpp +++ b/plugins/quickopen/expandingtree/expandingdelegate.cpp @@ -1,344 +1,344 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2006 Hamish Rodda * Copyright (C) 2007 David Nolden * * 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 "expandingdelegate.h" #include #include #include #include #include #include #include "expandingwidgetmodel.h" #include "../debug.h" ExpandingDelegate::ExpandingDelegate(ExpandingWidgetModel* model, QObject* parent) : QItemDelegate(parent) , m_model(model) { } //Gets the background-color in the way QItemDelegate does it static QColor getUsedBackgroundColor(const QStyleOptionViewItem & option, const QModelIndex& index) { if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { QPalette::ColorGroup cg = option.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) cg = QPalette::Inactive; return option.palette.brush(cg, QPalette::Highlight).color(); } else { QVariant value = index.data(Qt::BackgroundRole); if ((value).canConvert()) return qvariant_cast(value).color(); } return QApplication::palette().base().color(); } static void dampColors(QColor& col) { //Reduce the colors that are less visible to the eye, because they are closer to black when it comes to contrast //The most significant color to the eye is green. Then comes red, and then blue, with blue _much_ less significant. col.setBlue(0); col.setRed(col.red() / 2); } //A hack to compute more eye-focused contrast values static double readabilityContrast(QColor foreground, QColor background) { dampColors(foreground); dampColors(background); return abs(foreground.green()-background.green()) + abs(foreground.red()-background.red()) + abs(foreground.blue() - background.blue()); } void ExpandingDelegate::paint( QPainter * painter, const QStyleOptionViewItem & optionOld, const QModelIndex & index ) const { QStyleOptionViewItem option(optionOld); m_currentIndex = index; adjustStyle(index, option); if( index.column() == 0 ) model()->placeExpandingWidget(index); //Make sure the decorations are painted at the top, because the center of expanded items will be filled with the embedded widget. if( model()->isPartiallyExpanded(index) == ExpandingWidgetModel::ExpandUpwards ) m_cachedAlignment = Qt::AlignBottom; else m_cachedAlignment = Qt::AlignTop; option.decorationAlignment = m_cachedAlignment; option.displayAlignment = m_cachedAlignment; //qCDebug( PLUGIN_QUICKOPEN ) << "Painting row " << index.row() << ", column " << index.column() << ", internal " << index.internalPointer() << ", drawselected " << option.showDecorationSelected << ", selected " << (option.state & QStyle::State_Selected); m_cachedHighlights.clear(); m_backgroundColor = getUsedBackgroundColor(option, index); if (model()->indexIsItem(index) ) { m_currentColumnStart = 0; m_cachedHighlights = createHighlighting(index, option); } /*qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for line:"; foreach (const QTextLayout::FormatRange& fr, m_cachedHighlights) qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << " format ";*/ QItemDelegate::paint(painter, option, index); ///This is a bug workaround for the Qt raster paint engine: It paints over widgets embedded into the viewport when updating due to mouse events ///@todo report to Qt Software if( model()->isExpanded(index) && model()->expandingWidget( index ) ) model()->expandingWidget( index )->update(); } QList ExpandingDelegate::createHighlighting(const QModelIndex& index, QStyleOptionViewItem& option) const { Q_UNUSED( index ); Q_UNUSED( option ); return QList(); } QSize ExpandingDelegate::basicSizeHint( const QModelIndex& index ) const { return QItemDelegate::sizeHint( QStyleOptionViewItem(), index ); } QSize ExpandingDelegate::sizeHint ( const QStyleOptionViewItem & option, const QModelIndex & index ) const { QSize s = QItemDelegate::sizeHint( option, index ); if( model()->isExpanded(index) && model()->expandingWidget( index ) ) { QWidget* widget = model()->expandingWidget( index ); QSize widgetSize = widget->size(); s.setHeight( widgetSize.height() + s.height() + 10 ); //10 is the sum that must match exactly the offsets used in ExpandingWidgetModel::placeExpandingWidgets - } else if( model()->isPartiallyExpanded( index ) ) { + } else if( model()->isPartiallyExpanded( index ) != ExpandingWidgetModel::ExpansionType::NotExpanded) { s.setHeight( s.height() + 30 + 10 ); } return s; } void ExpandingDelegate::adjustStyle( const QModelIndex& index, QStyleOptionViewItem & option ) const { Q_UNUSED(index) Q_UNUSED(option) } void ExpandingDelegate::adjustRect(QRect& rect) const { if (!model()->indexIsItem(m_currentIndex) /*&& m_currentIndex.column() == 0*/) { rect.setLeft(model()->treeView()->columnViewportPosition(0)); int columnCount = model()->columnCount(m_currentIndex.parent()); if(!columnCount) return; rect.setRight(model()->treeView()->columnViewportPosition(columnCount-1) + model()->treeView()->columnWidth(columnCount-1)); } } void ExpandingDelegate::drawDisplay( QPainter * painter, const QStyleOptionViewItem & option, const QRect & _rect, const QString & text ) const { QRect rect(_rect); adjustRect(rect); QTextLayout layout(text, option.font, painter->device()); #if QT_VERSION < 0x050600 QList additionalFormats; #else QVector additionalFormats; #endif int missingFormats = text.length(); for (int i = 0; i < m_cachedHighlights.count(); ++i) { if (m_cachedHighlights[i].start + m_cachedHighlights[i].length <= m_currentColumnStart) continue; if (additionalFormats.isEmpty()) if (i != 0 && m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length > m_currentColumnStart) { QTextLayout::FormatRange before; before.start = 0; before.length = m_cachedHighlights[i - 1].start + m_cachedHighlights[i - 1].length - m_currentColumnStart; before.format = m_cachedHighlights[i - 1].format; additionalFormats.append(before); } QTextLayout::FormatRange format; format.start = m_cachedHighlights[i].start - m_currentColumnStart; format.length = m_cachedHighlights[i].length; format.format = m_cachedHighlights[i].format; additionalFormats.append(format); } if(!additionalFormats.isEmpty()) missingFormats = text.length() - (additionalFormats.back().length + additionalFormats.back().start); if (missingFormats > 0) { QTextLayout::FormatRange format; format.start = text.length() - missingFormats; format.length = missingFormats; QTextCharFormat fm; fm.setForeground(option.palette.text()); format.format = fm; additionalFormats.append(format); } if(m_backgroundColor.isValid()) { QColor background = m_backgroundColor; // qCDebug(PLUGIN_QUICKOPEN) << text << "background:" << background.name(); //Now go through the formats, and make sure the contrast background/foreground is readable for(int a = 0; a < additionalFormats.size(); ++a) { QColor currentBackground = background; if(additionalFormats[a].format.hasProperty(QTextFormat::BackgroundBrush)) currentBackground = additionalFormats[a].format.background().color(); QColor currentColor = additionalFormats[a].format.foreground().color(); double currentContrast = readabilityContrast(currentColor, currentBackground); QColor invertedColor(0xffffffff-additionalFormats[a].format.foreground().color().rgb()); double invertedContrast = readabilityContrast(invertedColor, currentBackground); // qCDebug(PLUGIN_QUICKOPEN) << "values:" << invertedContrast << currentContrast << invertedColor.name() << currentColor.name(); if(invertedContrast > currentContrast) { // qCDebug(PLUGIN_QUICKOPEN) << text << additionalFormats[a].length << "switching from" << currentColor.name() << "to" << invertedColor.name(); QBrush b(additionalFormats[a].format.foreground()); b.setColor(invertedColor); additionalFormats[a].format.setForeground(b); } } } for(int a = additionalFormats.size()-1; a >= 0; --a) { if(additionalFormats[a].length == 0){ additionalFormats.removeAt(a); }else{ ///For some reason the text-formats seem to be invalid in some way, sometimes ///@todo Fix this properly, it sucks not copying everything over QTextCharFormat fm; fm.setForeground(QBrush(additionalFormats[a].format.foreground().color())); fm.setBackground(additionalFormats[a].format.background()); fm.setUnderlineStyle( additionalFormats[a].format.underlineStyle() ); fm.setUnderlineColor( additionalFormats[a].format.underlineColor() ); fm.setFontWeight( additionalFormats[a].format.fontWeight() ); additionalFormats[a].format = fm; } } // qCDebug( PLUGIN_QUICKOPEN ) << "Highlights for text [" << text << "] col start " << m_currentColumnStart << ":"; // foreach (const QTextLayout::FormatRange& fr, additionalFormats) // qCDebug( PLUGIN_QUICKOPEN ) << fr.start << " len " << fr.length << "foreground" << fr.format.foreground() << "background" << fr.format.background(); #if QT_VERSION < 0x050600 layout.setAdditionalFormats(additionalFormats); #else layout.setFormats(additionalFormats); #endif QTextOption to; to.setAlignment( m_cachedAlignment ); to.setWrapMode(QTextOption::WrapAnywhere); layout.setTextOption(to); layout.beginLayout(); QTextLine line = layout.createLine(); line.setLineWidth(rect.width()); layout.endLayout(); //We need to do some hand layouting here if( to.alignment() & Qt::AlignBottom) layout.draw(painter, QPoint(rect.left(), rect.bottom() - (int)line.height()) ); else layout.draw(painter, rect.topLeft() ); return; //if (painter->fontMetrics().width(text) > textRect.width() && !text.contains(QLatin1Char('\n'))) //str = elidedText(option.fontMetrics, textRect.width(), option.textElideMode, text); //qt_format_text(option.font, textRect, option.displayAlignment, str, 0, 0, 0, 0, painter); } void ExpandingDelegate::drawDecoration(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QPixmap& pixmap) const { if (model()->indexIsItem(m_currentIndex) ) QItemDelegate::drawDecoration(painter, option, rect, pixmap); } void ExpandingDelegate::drawBackground ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const { Q_UNUSED(index) QStyleOptionViewItem opt = option; //initStyleOption(&opt, index); //Problem: This isn't called at all, because drawBrackground is not virtual :-/ QStyle *style = model()->treeView()->style() ? model()->treeView()->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter); } ExpandingWidgetModel* ExpandingDelegate::model() const { return m_model; } void ExpandingDelegate::heightChanged() const { } bool ExpandingDelegate::editorEvent ( QEvent * event, QAbstractItemModel * /*model*/, const QStyleOptionViewItem & /*option*/, const QModelIndex & index ) { if( event->type() == QEvent::MouseButtonRelease ) { event->accept(); model()->setExpanded(index, !model()->isExpanded( index )); heightChanged(); return true; } else { event->ignore(); } return false; } QList ExpandingDelegate::highlightingFromVariantList(const QList& customHighlights) const { QList ret; for (int i = 0; i + 2 < customHighlights.count(); i += 3) { if (!customHighlights[i].canConvert(QVariant::Int) || !customHighlights[i+1].canConvert(QVariant::Int) || !customHighlights[i+2].canConvert()) { qWarning() << "Unable to convert triple to custom formatting."; continue; } QTextLayout::FormatRange format; format.start = customHighlights[i].toInt(); format.length = customHighlights[i+1].toInt(); format.format = customHighlights[i+2].value().toCharFormat(); if(!format.format.isValid()) qWarning() << "Format is not valid"; ret << format; } return ret; } diff --git a/plugins/quickopen/expandingtree/expandingtree.cpp b/plugins/quickopen/expandingtree/expandingtree.cpp index 54682ddc3..aa4f5896e 100644 --- a/plugins/quickopen/expandingtree/expandingtree.cpp +++ b/plugins/quickopen/expandingtree/expandingtree.cpp @@ -1,78 +1,78 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "expandingtree.h" #include #include #include #include #include "expandingwidgetmodel.h" #include #include using namespace KDevelop; ExpandingTree::ExpandingTree(QWidget* parent) : QTreeView(parent) { m_drawText.documentLayout()->setPaintDevice(this); setUniformRowHeights(false); } void ExpandingTree::drawRow ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const { QTreeView::drawRow( painter, option, index ); const ExpandingWidgetModel* eModel = qobject_cast(model()); - if( eModel && eModel->isPartiallyExpanded( index ) ) + if( eModel && eModel->isPartiallyExpanded( index ) != ExpandingWidgetModel::ExpansionType::NotExpanded) { QRect rect = eModel->partialExpandRect( index ); if( rect.isValid() ) { painter->fillRect(rect,QBrush(0xffffffff)); QAbstractTextDocumentLayout::PaintContext ctx; // since arbitrary HTML can be shown use a black on white color scheme here ctx.palette = QPalette( Qt::black, Qt::white ); ctx.clip = QRectF(0,0,rect.width(),rect.height());; painter->setViewTransformEnabled(true); painter->translate(rect.left(), rect.top()); m_drawText.setHtml( eModel->partialExpandText( index ) ); m_drawText.setPageSize(QSizeF(rect.width(), rect.height())); m_drawText.documentLayout()->draw( painter, ctx ); painter->translate(-rect.left(), -rect.top()); } } } int ExpandingTree::sizeHintForColumn ( int column ) const { return columnWidth( column ); } void ExpandingTree::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const { const auto& path = index.data(ProjectPathRole).value(); if (path.isValid()) { const auto color = WidgetColorizer::colorForId(qHash(path), palette(), true); WidgetColorizer::drawBranches(this, painter, rect, index, color); } QTreeView::drawBranches(painter, rect, index); } diff --git a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp index 4eaa3d242..0599bbe24 100644 --- a/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp +++ b/plugins/quickopen/expandingtree/expandingwidgetmodel.cpp @@ -1,529 +1,529 @@ /* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2007 David Nolden * * 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 "expandingwidgetmodel.h" #include #include #include #include #include #include #include #include "expandingdelegate.h" #include #include "../debug.h" QIcon ExpandingWidgetModel::m_expandedIcon; QIcon ExpandingWidgetModel::m_collapsedIcon; using namespace KTextEditor; inline QModelIndex firstColumn( const QModelIndex& index ) { return index.sibling(index.row(), 0); } ExpandingWidgetModel::ExpandingWidgetModel( QWidget* parent ) : QAbstractTableModel(parent) { } ExpandingWidgetModel::~ExpandingWidgetModel() { clearExpanding(); } static QColor doAlternate(QColor color) { QColor background = QApplication::palette().background().color(); return KColorUtils::mix(color, background, 0.15); } uint ExpandingWidgetModel::matchColor(const QModelIndex& index) const { int matchQuality = contextMatchQuality( index.sibling(index.row(), 0) ); if( matchQuality > 0 ) { bool alternate = index.row() & 1; QColor badMatchColor(0xff00aa44); //Blue-ish green QColor goodMatchColor(0xff00ff00); //Green QColor background = treeView()->palette().light().color(); QColor totalColor = KColorUtils::mix(badMatchColor, goodMatchColor, ((float)matchQuality)/10.0); if(alternate) totalColor = doAlternate(totalColor); const float dynamicTint = 0.2f; const float minimumTint = 0.2f; double tintStrength = (dynamicTint*matchQuality)/10; if(tintStrength) tintStrength += minimumTint; //Some minimum tinting strength, else it's not visible any more return KColorUtils::tint(background, totalColor, tintStrength ).rgb(); }else{ return 0; } } QVariant ExpandingWidgetModel::data( const QModelIndex & index, int role ) const { switch( role ) { case Qt::BackgroundRole: { if( index.column() == 0 ) { //Highlight by match-quality uint color = matchColor(index); if( color ) return QBrush( color ); } //Use a special background-color for expanded items if( isExpanded(index) ) { if( index.row() & 1 ) { return doAlternate(treeView()->palette().toolTipBase().color()); } else { return treeView()->palette().toolTipBase(); } } } } return QVariant(); } void ExpandingWidgetModel::clearMatchQualities() { m_contextMatchQualities.clear(); } QModelIndex ExpandingWidgetModel::partiallyExpandedRow() const { if( m_partiallyExpanded.isEmpty() ) return QModelIndex(); else return m_partiallyExpanded.constBegin().key(); } void ExpandingWidgetModel::clearExpanding() { clearMatchQualities(); QMap oldExpandState = m_expandState; foreach( QPointer widget, m_expandingWidgets ) delete widget; m_expandingWidgets.clear(); m_expandState.clear(); m_partiallyExpanded.clear(); for( QMap::const_iterator it = oldExpandState.constBegin(); it != oldExpandState.constEnd(); ++it ) if(it.value() == Expanded) emit dataChanged(it.key(), it.key()); } ExpandingWidgetModel::ExpansionType ExpandingWidgetModel::isPartiallyExpanded(const QModelIndex& index) const { if( m_partiallyExpanded.contains(firstColumn(index)) ) return m_partiallyExpanded[firstColumn(index)]; else return NotExpanded; } void ExpandingWidgetModel::partiallyUnExpand(const QModelIndex& idx_) { QModelIndex index( firstColumn(idx_) ); m_partiallyExpanded.remove(index); m_partiallyExpanded.remove(idx_); } int ExpandingWidgetModel::partiallyExpandWidgetHeight() const { return 60; ///@todo use font-metrics text-height*2 for 2 lines } void ExpandingWidgetModel::rowSelected(const QModelIndex& idx_) { QModelIndex idx( firstColumn(idx_) ); if( !m_partiallyExpanded.contains( idx ) ) { QModelIndex oldIndex = partiallyExpandedRow(); //Unexpand the previous partially expanded row if( !m_partiallyExpanded.isEmpty() ) { ///@todo allow multiple partially expanded rows while( !m_partiallyExpanded.isEmpty() ) m_partiallyExpanded.erase(m_partiallyExpanded.begin()); //partiallyUnExpand( m_partiallyExpanded.begin().key() ); } //Notify the underlying models that the item was selected, and eventually get back the text for the expanding widget. if( !idx.isValid() ) { //All items have been unselected if( oldIndex.isValid() ) emit dataChanged(oldIndex, oldIndex); } else { QVariant variant = data(idx, CodeCompletionModel::ItemSelected); if( !isExpanded(idx) && variant.type() == QVariant::String) { //Either expand upwards or downwards, choose in a way that //the visible fields of the new selected entry are not moved. if( oldIndex.isValid() && (oldIndex < idx || (!(oldIndex < idx) && oldIndex.parent() < idx.parent()) ) ) m_partiallyExpanded.insert(idx, ExpandUpwards); else m_partiallyExpanded.insert(idx, ExpandDownwards); //Say that one row above until one row below has changed, so no items will need to be moved(the space that is taken from one item is given to the other) if( oldIndex.isValid() && oldIndex < idx ) { emit dataChanged(oldIndex, idx); if( treeView()->verticalScrollMode() == QAbstractItemView::ScrollPerItem ) { //Qt fails to correctly scroll in ScrollPerItem mode, so the selected index is completely visible, //so we do the scrolling by hand. QRect selectedRect = treeView()->visualRect(idx); QRect frameRect = treeView()->frameRect(); if( selectedRect.bottom() > frameRect.bottom() ) { int diff = selectedRect.bottom() - frameRect.bottom(); //We need to scroll down QModelIndex newTopIndex = idx; QModelIndex nextTopIndex = idx; QRect nextRect = treeView()->visualRect(nextTopIndex); while( nextTopIndex.isValid() && nextRect.isValid() && nextRect.top() >= diff ) { newTopIndex = nextTopIndex; nextTopIndex = treeView()->indexAbove(nextTopIndex); if( nextTopIndex.isValid() ) nextRect = treeView()->visualRect(nextTopIndex); } treeView()->scrollTo( newTopIndex, QAbstractItemView::PositionAtTop ); } } //This is needed to keep the item we are expanding completely visible. Qt does not scroll the view to keep the item visible. //But we must make sure that it isn't too expensive. //We need to make sure that scrolling is efficient, and the whole content is not repainted. //Since we are scrolling anyway, we can keep the next line visible, which might be a cool feature. //Since this also doesn't work smoothly, leave it for now //treeView()->scrollTo( nextLine, QAbstractItemView::EnsureVisible ); } else if( oldIndex.isValid() && idx < oldIndex ) { emit dataChanged(idx, oldIndex); //For consistency with the down-scrolling, we keep one additional line visible above the current visible. //Since this also doesn't work smoothly, leave it for now /* QModelIndex prevLine = idx.sibling(idx.row()-1, idx.column()); if( prevLine.isValid() ) treeView()->scrollTo( prevLine );*/ } else emit dataChanged(idx, idx); } else if( oldIndex.isValid() ) { //We are not partially expanding a new row, but we previously had a partially expanded row. So signalize that it has been unexpanded. emit dataChanged(oldIndex, oldIndex); } } }else{ qCDebug( PLUGIN_QUICKOPEN ) << "ExpandingWidgetModel::rowSelected: Row is already partially expanded"; } } QString ExpandingWidgetModel::partialExpandText(const QModelIndex& idx) const { if( !idx.isValid() ) return QString(); return data(firstColumn(idx), CodeCompletionModel::ItemSelected).toString(); } QRect ExpandingWidgetModel::partialExpandRect(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( !idx.isValid() ) return QRect(); ExpansionType expansion = ExpandDownwards; if( m_partiallyExpanded.find(idx) != m_partiallyExpanded.constEnd() ) expansion = m_partiallyExpanded[idx]; //Get the whole rectangle of the row: QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) rightMostIndex = tempIndex; QRect rect = treeView()->visualRect(idx); QRect rightMostRect = treeView()->visualRect(rightMostIndex); rect.setLeft( rect.left() + 20 ); rect.setRight( rightMostRect.right() - 5 ); //These offsets must match exactly those used in ExpandingDelegate::sizeHint() int top = rect.top() + 5; int bottom = rightMostRect.bottom() - 5 ; if( expansion == ExpandDownwards ) top += basicRowHeight(idx); else bottom -= basicRowHeight(idx); rect.setTop( top ); rect.setBottom( bottom ); return rect; } bool ExpandingWidgetModel::isExpandable(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( !m_expandState.contains(idx) ) { m_expandState.insert(idx, NotExpandable); QVariant v = data(idx, CodeCompletionModel::IsExpandable); if( v.canConvert() && v.toBool() ) m_expandState[idx] = Expandable; } return m_expandState[idx] != NotExpandable; } bool ExpandingWidgetModel::isExpanded(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); return m_expandState.contains(idx) && m_expandState[idx] == Expanded; } void ExpandingWidgetModel::setExpanded(QModelIndex idx_, bool expanded) { QModelIndex idx(firstColumn(idx_)); //qCDebug( PLUGIN_QUICKOPEN ) << "Setting expand-state of row " << idx.row() << " to " << expanded; if( !idx.isValid() ) return; if( isExpandable(idx) ) { if( !expanded && m_expandingWidgets.contains(idx) && m_expandingWidgets[idx] ) { m_expandingWidgets[idx]->hide(); } m_expandState[idx] = expanded ? Expanded : Expandable; if( expanded ) partiallyUnExpand(idx); if( expanded && !m_expandingWidgets.contains(idx) ) { QVariant v = data(idx, CodeCompletionModel::ExpandingWidget); if( v.canConvert() ) { m_expandingWidgets[idx] = v.value(); } else if( v.canConvert() ) { //Create a html widget that shows the given string KTextEdit* edit = new KTextEdit( v.toString() ); edit->setReadOnly(true); edit->resize(200, 50); //Make the widget small so it embeds nicely. m_expandingWidgets[idx] = edit; } else { m_expandingWidgets[idx] = nullptr; } } //Eventually partially expand the row - if( !expanded && firstColumn(treeView()->currentIndex()) == idx && !isPartiallyExpanded(idx) ) + if( !expanded && firstColumn(treeView()->currentIndex()) == idx && (isPartiallyExpanded(idx) == ExpandingWidgetModel::ExpansionType::NotExpanded) ) rowSelected(idx); //Partially expand the row. emit dataChanged(idx, idx); if(treeView()) treeView()->scrollTo(idx); } } int ExpandingWidgetModel::basicRowHeight( const QModelIndex& idx_ ) const { QModelIndex idx(firstColumn(idx_)); ExpandingDelegate* delegate = dynamic_cast( treeView()->itemDelegate(idx) ); if( !delegate || !idx.isValid() ) { qCDebug( PLUGIN_QUICKOPEN ) << "ExpandingWidgetModel::basicRowHeight: Could not get delegate"; return 15; } return delegate->basicSizeHint( idx ).height(); } void ExpandingWidgetModel::placeExpandingWidget(const QModelIndex& idx_) { QModelIndex idx(firstColumn(idx_)); QWidget* w = nullptr; if( m_expandingWidgets.contains(idx) ) w = m_expandingWidgets[idx]; if( w && isExpanded(idx) ) { if( !idx.isValid() ) return; QRect rect = treeView()->visualRect(idx); if( !rect.isValid() || rect.bottom() < 0 || rect.top() >= treeView()->height() ) { //The item is currently not visible w->hide(); return; } QModelIndex rightMostIndex = idx; QModelIndex tempIndex = idx; while( (tempIndex = rightMostIndex.sibling(rightMostIndex.row(), rightMostIndex.column()+1)).isValid() ) rightMostIndex = tempIndex; QRect rightMostRect = treeView()->visualRect(rightMostIndex); //Find out the basic height of the row rect.setLeft( rect.left() + 20 ); rect.setRight( rightMostRect.right() - 5 ); //These offsets must match exactly those used in KateCompletionDeleage::sizeHint() rect.setTop( rect.top() + basicRowHeight(idx) + 5 ); rect.setHeight( w->height() ); if( w->parent() != treeView()->viewport() || w->geometry() != rect || !w->isVisible() ) { w->setParent( treeView()->viewport() ); w->setGeometry(rect); w->show(); } } } void ExpandingWidgetModel::placeExpandingWidgets() { for( QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { placeExpandingWidget(it.key()); } } int ExpandingWidgetModel::expandingWidgetsHeight() const { int sum = 0; for( QMap >::const_iterator it = m_expandingWidgets.constBegin(); it != m_expandingWidgets.constEnd(); ++it ) { if( isExpanded(it.key() ) && (*it) ) sum += (*it)->height(); } return sum; } QWidget* ExpandingWidgetModel::expandingWidget(const QModelIndex& idx_) const { QModelIndex idx(firstColumn(idx_)); if( m_expandingWidgets.contains(idx) ) return m_expandingWidgets[idx]; else return nullptr; } void ExpandingWidgetModel::cacheIcons() const { if( m_expandedIcon.isNull() ) m_expandedIcon = QIcon::fromTheme(QStringLiteral("arrow-down")); if( m_collapsedIcon.isNull() ) m_collapsedIcon = QIcon::fromTheme(QStringLiteral("arrow-right")); } QList mergeCustomHighlighting( int leftSize, const QList& left, int rightSize, const QList& right ) { QList ret = left; if( left.isEmpty() ) { ret << QVariant(0); ret << QVariant(leftSize); ret << QTextFormat(QTextFormat::CharFormat); } if( right.isEmpty() ) { ret << QVariant(leftSize); ret << QVariant(rightSize); ret << QTextFormat(QTextFormat::CharFormat); } else { QList::const_iterator it = right.constBegin(); while( it != right.constEnd() ) { { QList::const_iterator testIt = it; for(int a = 0; a < 2; a++) { ++testIt; if(testIt == right.constEnd()) { qWarning() << "Length of input is not multiple of 3"; break; } } } ret << QVariant( (*it).toInt() + leftSize ); ++it; ret << QVariant( (*it).toInt() ); ++it; ret << *it; if(!(*it).value().isValid()) qCDebug( PLUGIN_QUICKOPEN ) << "Text-format is invalid"; ++it; } } return ret; } //It is assumed that between each two strings, one space is inserted QList mergeCustomHighlighting( QStringList strings, QList highlights, int grapBetweenStrings ) { if(strings.isEmpty()) { qWarning() << "List of strings is empty"; return QList(); } if(highlights.isEmpty()) { qWarning() << "List of highlightings is empty"; return QList(); } if(strings.count() != highlights.count()) { qWarning() << "Length of string-list is " << strings.count() << " while count of highlightings is " << highlights.count() << ", should be same"; return QList(); } //Merge them together QString totalString = strings[0]; QVariantList totalHighlighting = highlights[0]; strings.pop_front(); highlights.pop_front(); while( !strings.isEmpty() ) { totalHighlighting = mergeCustomHighlighting( totalString.length(), totalHighlighting, strings[0].length(), highlights[0] ); totalString += strings[0]; for(int a = 0; a < grapBetweenStrings; a++) totalString += ' '; strings.pop_front(); highlights.pop_front(); } //Combine the custom-highlightings return totalHighlighting; } diff --git a/plugins/testview/testview.cpp b/plugins/testview/testview.cpp index eddfb3d50..e39085f0f 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,412 +1,411 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula 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. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testview.h" #include "testviewplugin.h" #include "testviewdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; enum CustomRoles { ProjectRole = Qt::UserRole + 1, SuiteRole, CaseRole }; TestView::TestView(TestViewPlugin* plugin, QWidget* parent) : QWidget(parent) , m_plugin(plugin) , m_tree(new QTreeView(this)) , m_filter(new KRecursiveFilterProxyModel(this)) { setWindowIcon(QIcon::fromTheme(QStringLiteral("preflight-verifier"), windowIcon())); setWindowTitle(i18n("Unit Tests")); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); layout->addWidget(m_tree); m_tree->setSortingEnabled(true); m_tree->header()->hide(); m_tree->setIndentation(10); m_tree->setEditTriggers(QTreeView::NoEditTriggers); m_tree->setSelectionBehavior(QTreeView::SelectRows); m_tree->setSelectionMode(QTreeView::SingleSelection); m_tree->setExpandsOnDoubleClick(false); m_tree->sortByColumn(0, Qt::AscendingOrder); connect(m_tree, &QTreeView::activated, this, &TestView::doubleClicked); m_model = new QStandardItemModel(this); m_filter->setSourceModel(m_model); m_tree->setModel(m_filter); QAction* showSource = new QAction( QIcon::fromTheme(QStringLiteral("code-context")), i18n("Show Source"), this ); connect (showSource, &QAction::triggered, this, &TestView::showSource); m_contextMenuActions << showSource; addAction(plugin->actionCollection()->action(QStringLiteral("run_all_tests"))); addAction(plugin->actionCollection()->action(QStringLiteral("stop_running_tests"))); QAction* runSelected = new QAction( QIcon::fromTheme(QStringLiteral("system-run")), i18n("Run Selected Tests"), this ); connect (runSelected, &QAction::triggered, this, &TestView::runSelectedTests); addAction(runSelected); QLineEdit* edit = new QLineEdit(parent); edit->setPlaceholderText(i18n("Filter...")); edit->setClearButtonEnabled(true); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(edit); connect(edit, &QLineEdit::textChanged, this, &TestView::changeFilter); addAction(widgetAction); setFocusProxy(edit); IProjectController* pc = ICore::self()->projectController(); connect (pc, &IProjectController::projectClosed, this, &TestView::removeProject); ITestController* tc = ICore::self()->testController(); connect (tc, &ITestController::testSuiteAdded, this, &TestView::addTestSuite); connect (tc, &ITestController::testSuiteRemoved, this, &TestView::removeTestSuite); connect (tc, &ITestController::testRunFinished, this, &TestView::updateTestSuite); connect (tc, &ITestController::testRunStarted, this, &TestView::notifyTestCaseStarted); foreach (ITestSuite* suite, tc->testSuites()) { addTestSuite(suite); } } TestView::~TestView() { } void TestView::updateTestSuite(ITestSuite* suite, const TestResult& result) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Updating test suite" << suite->name(); item->setIcon(iconForTestResult(result.suiteResult)); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (result.testCaseResults.contains(caseItem->text())) { TestResult::TestCaseResult caseResult = result.testCaseResults.value(caseItem->text(), TestResult::NotRun); caseItem->setIcon(iconForTestResult(caseResult)); } } } void TestView::changeFilter(const QString &newFilter) { m_filter->setFilterWildcard(newFilter); if (newFilter.isEmpty()) { m_tree->collapseAll(); } else { m_tree->expandAll(); } } void TestView::notifyTestCaseStarted(ITestSuite* suite, const QStringList& test_cases) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Notify a test of the suite " << suite->name() << " has started"; // Global test suite icon item->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (test_cases.contains(caseItem->text())) { // Each test case icon caseItem->setIcon(QIcon::fromTheme(QStringLiteral("process-idle"))); } } } QIcon TestView::iconForTestResult(TestResult::TestCaseResult result) { switch (result) { case TestResult::NotRun: return QIcon::fromTheme(QStringLiteral("code-function")); case TestResult::Skipped: return QIcon::fromTheme(QStringLiteral("task-delegate")); case TestResult::Passed: return QIcon::fromTheme(QStringLiteral("dialog-ok-apply")); case TestResult::UnexpectedPass: // This is a very rare occurrence, so the icon should stand out return QIcon::fromTheme(QStringLiteral("dialog-warning")); case TestResult::Failed: return QIcon::fromTheme(QStringLiteral("edit-delete")); case TestResult::ExpectedFail: return QIcon::fromTheme(QStringLiteral("dialog-ok")); case TestResult::Error: return QIcon::fromTheme(QStringLiteral("dialog-cancel")); default: return QIcon::fromTheme(QLatin1String("")); } } QStandardItem* TestView::itemForSuite(ITestSuite* suite) { foreach (QStandardItem* item, m_model->findItems(suite->name(), Qt::MatchRecursive)) { if (item->parent() && item->parent()->text() == suite->project()->name() && !item->parent()->parent()) { return item; } } return nullptr; } QStandardItem* TestView::itemForProject(IProject* project) { - foreach (QStandardItem* item, m_model->findItems(project->name())) - { - return item; + QList itemsForProject = m_model->findItems(project->name()); + if (!itemsForProject.isEmpty()) { + return itemsForProject.first(); } - return addProject(project); } void TestView::runSelectedTests() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { //if there's no selection we'll run all of them (or only the filtered) //in case there's a filter. const int rc = m_filter->rowCount(); for(int i=0; iindex(i, 0); } } QList jobs; ITestController* tc = ICore::self()->testController(); /* * NOTE: If a test suite or a single test case was selected, * the job is launched in Verbose mode with raised output window. * If a project is selected, it is launched silently. * * This is the somewhat-intuitive approach. Maybe a configuration should be offered. */ foreach (const QModelIndex& idx, indexes) { QModelIndex index = m_filter->mapToSource(idx); if (index.parent().isValid() && indexes.contains(index.parent())) { continue; } QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == nullptr) { // A project was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->data(ProjectRole).toString()); foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { jobs << suite->launchAllCases(ITestSuite::Silent); } } else if (item->parent()->parent() == nullptr) { // A suite was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); jobs << suite->launchAllCases(ITestSuite::Verbose); } else { // This was a single test case IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); const QString testCase = item->data(CaseRole).toString(); jobs << suite->launchCase(testCase, ITestSuite::Verbose); } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test", "Run %1 tests", jobs.size())); compositeJob->setProperty("test_job", true); ICore::self()->runController()->registerJob(compositeJob); } } void TestView::showSource() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } IndexedDeclaration declaration; ITestController* tc = ICore::self()->testController(); QModelIndex index = m_filter->mapToSource(indexes.first()); QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == nullptr) { // No sense in finding source code for projects. return; } else if (item->parent()->parent() == nullptr) { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); declaration = suite->declaration(); } else { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); declaration = suite->caseDeclaration(item->data(CaseRole).toString()); } DUChainReadLocker locker; Declaration* d = declaration.data(); if (!d) { return; } QUrl url = d->url().toUrl(); KTextEditor::Cursor cursor = d->rangeInCurrentRevision().start(); locker.unlock(); IDocumentController* dc = ICore::self()->documentController(); qCDebug(PLUGIN_TESTVIEW) << "Activating declaration in" << url; dc->openDocument(url, cursor); } void TestView::addTestSuite(ITestSuite* suite) { QStandardItem* projectItem = itemForProject(suite->project()); Q_ASSERT(projectItem); QStandardItem* suiteItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("view-list-tree")), suite->name()); suiteItem->setData(suite->name(), SuiteRole); foreach (const QString& caseName, suite->cases()) { QStandardItem* caseItem = new QStandardItem(iconForTestResult(TestResult::NotRun), caseName); caseItem->setData(caseName, CaseRole); suiteItem->appendRow(caseItem); } projectItem->appendRow(suiteItem); } void TestView::removeTestSuite(ITestSuite* suite) { QStandardItem* item = itemForSuite(suite); item->parent()->removeRow(item->row()); } QStandardItem* TestView::addProject(IProject* project) { QStandardItem* projectItem = new QStandardItem(QIcon::fromTheme(QStringLiteral("project-development")), project->name()); projectItem->setData(project->name(), ProjectRole); m_model->appendRow(projectItem); return projectItem; } void TestView::removeProject(IProject* project) { QStandardItem* projectItem = itemForProject(project); m_model->removeRow(projectItem->row()); } void TestView::doubleClicked(const QModelIndex& index) { m_tree->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); runSelectedTests(); } QList< QAction* > TestView::contextMenuActions() { return m_contextMenuActions; } diff --git a/shell/settings/sourceformattersettings.cpp b/shell/settings/sourceformattersettings.cpp index 6395d807d..500d3a345 100644 --- a/shell/settings/sourceformattersettings.cpp +++ b/shell/settings/sourceformattersettings.cpp @@ -1,541 +1,540 @@ /* This file is part of KDevelop * Copyright (C) 2008 Cédric Pasteur 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. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sourceformattersettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "editstyledialog.h" #include "../debug.h" #define STYLE_ROLE (Qt::UserRole+1) using KDevelop::Core; using KDevelop::ISourceFormatter; using KDevelop::SourceFormatterStyle; using KDevelop::SourceFormatterController; using KDevelop::SourceFormatter; namespace { namespace Strings { QString userStylePrefix() { return QStringLiteral("User"); } } } LanguageSettings::LanguageSettings() : selectedFormatter(nullptr), selectedStyle(nullptr) { } SourceFormatterSettings::SourceFormatterSettings(QWidget* parent) : KDevelop::ConfigPage(nullptr, nullptr, parent) { setupUi(this); connect( cbLanguages, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSettings::selectLanguage ); connect( cbFormatters, static_cast(&KComboBox::currentIndexChanged), this, &SourceFormatterSettings::selectFormatter ); connect( chkKateModelines, &QCheckBox::toggled, this, &SourceFormatterSettings::somethingChanged ); connect( chkKateOverrideIndentation, &QCheckBox::toggled, this, &SourceFormatterSettings::somethingChanged ); connect( styleList, &QListWidget::currentRowChanged, this, &SourceFormatterSettings::selectStyle ); connect( btnDelStyle, &QPushButton::clicked, this, &SourceFormatterSettings::deleteStyle ); connect( btnNewStyle, &QPushButton::clicked, this, &SourceFormatterSettings::newStyle ); connect( btnEditStyle, &QPushButton::clicked, this, &SourceFormatterSettings::editStyle ); connect( styleList, &QListWidget::itemChanged, this, &SourceFormatterSettings::styleNameChanged ); m_document = KTextEditor::Editor::instance()->createDocument(this); m_document->setReadWrite(false); m_view = m_document->createView(textEditor); m_view->setStatusBarEnabled(false); QVBoxLayout *layout2 = new QVBoxLayout(textEditor); layout2->addWidget(m_view); textEditor->setLayout(layout2); m_view->show(); KTextEditor::ConfigInterface *iface = qobject_cast(m_view); if (iface) { iface->setConfigValue(QStringLiteral("dynamic-word-wrap"), false); iface->setConfigValue(QStringLiteral("icon-bar"), false); } } SourceFormatterSettings::~SourceFormatterSettings() { qDeleteAll(formatters); } void selectAvailableStyle(LanguageSettings& lang) { Q_ASSERT(!lang.selectedFormatter->styles.empty()); lang.selectedStyle = *lang.selectedFormatter->styles.begin(); } void SourceFormatterSettings::reset() { SourceFormatterController* fmtctrl = Core::self()->sourceFormatterControllerInternal(); QList plugins = KDevelop::ICore::self()->pluginController()->allPluginsForExtension( QStringLiteral("org.kdevelop.ISourceFormatter") ); foreach( KDevelop::IPlugin* plugin, plugins ) { KDevelop::ISourceFormatter* ifmt = plugin->extension(); auto info = KDevelop::Core::self()->pluginControllerInternal()->pluginInfo( plugin ); KDevelop::SourceFormatter* formatter; FormatterMap::const_iterator iter = formatters.constFind(ifmt->name()); if (iter == formatters.constEnd()) { formatter = fmtctrl->createFormatterForPlugin(ifmt); formatters[ifmt->name()] = formatter; } else { formatter = iter.value(); } foreach ( const SourceFormatterStyle* style, formatter->styles ) { foreach ( const SourceFormatterStyle::MimeHighlightPair& item, style->mimeTypes() ) { QMimeType mime = QMimeDatabase().mimeTypeForName(item.mimeType); if (!mime.isValid()) { qWarning() << "plugin" << info.name() << "supports unknown mimetype entry" << item.mimeType; continue; } QString languageName = item.highlightMode; LanguageSettings& l = languages[languageName]; l.mimetypes.append(mime); l.formatters.insert( formatter ); } } } // Sort the languages, preferring firstly active, then loaded languages QList sortedLanguages; foreach(const auto language, KDevelop::ICore::self()->languageController()->activeLanguages() + KDevelop::ICore::self()->languageController()->loadedLanguages()) { if( languages.contains( language->name() ) && !sortedLanguages.contains(language->name()) ) { sortedLanguages.push_back( language->name() ); } } foreach( const QString& name, languages.keys() ) if( !sortedLanguages.contains( name ) ) sortedLanguages.push_back( name ); foreach( const QString& name, sortedLanguages ) { // Pick the first appropriate mimetype for this language KConfigGroup grp = fmtctrl->sessionConfig(); LanguageSettings& l = languages[name]; const QList mimetypes = l.mimetypes; foreach (const QMimeType& mimetype, mimetypes) { QStringList formatterAndStyleName = grp.readEntry(mimetype.name(), QString()).split(QStringLiteral("||"), QString::KeepEmptyParts); FormatterMap::const_iterator formatterIter = formatters.constFind(formatterAndStyleName.first()); if (formatterIter == formatters.constEnd()) { qCDebug(SHELL) << "Reference to unknown formatter" << formatterAndStyleName.first(); Q_ASSERT(!l.formatters.empty()); // otherwise there should be no entry for 'name' l.selectedFormatter = *l.formatters.begin(); selectAvailableStyle(l); } else { l.selectedFormatter = formatterIter.value(); SourceFormatter::StyleMap::const_iterator styleIter = l.selectedFormatter->styles.constFind(formatterAndStyleName.at( 1 )); if (styleIter == l.selectedFormatter->styles.constEnd()) { qCDebug(SHELL) << "No style" << formatterAndStyleName.at( 1 ) << "found for formatter" << formatterAndStyleName.first(); selectAvailableStyle(l); } else { l.selectedStyle = styleIter.value(); } } - break; } if (!l.selectedFormatter) { Q_ASSERT(!l.formatters.empty()); l.selectedFormatter = *l.formatters.begin(); } if (!l.selectedStyle) { selectAvailableStyle(l); } } bool b = blockSignals( true ); cbLanguages->blockSignals( !b ); cbFormatters->blockSignals( !b ); styleList->blockSignals( !b ); chkKateModelines->blockSignals( !b ); chkKateOverrideIndentation->blockSignals( !b ); cbLanguages->clear(); cbFormatters->clear(); styleList->clear(); chkKateModelines->setChecked( fmtctrl->sessionConfig().readEntry( SourceFormatterController::kateModeLineConfigKey(), false ) ); chkKateOverrideIndentation->setChecked( fmtctrl->sessionConfig().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), false ) ); foreach( const QString& name, sortedLanguages ) { cbLanguages->addItem( name ); } if( cbLanguages->count() == 0 ) { cbLanguages->setEnabled( false ); selectLanguage( -1 ); } else { cbLanguages->setCurrentIndex( 0 ); selectLanguage( 0 ); } updatePreview(); blockSignals( b ); cbLanguages->blockSignals( b ); cbFormatters->blockSignals( b ); styleList->blockSignals( b ); chkKateModelines->blockSignals( b ); chkKateOverrideIndentation->blockSignals( b ); } void SourceFormatterSettings::apply() { KConfigGroup globalConfig = Core::self()->sourceFormatterControllerInternal()->globalConfig(); foreach( SourceFormatter* fmt, formatters ) { KConfigGroup fmtgrp = globalConfig.group( fmt->formatter->name() ); // delete all styles so we don't leave any behind when all user styles are deleted foreach( const QString& subgrp, fmtgrp.groupList() ) { if( subgrp.startsWith( Strings::userStylePrefix() ) ) { fmtgrp.deleteGroup( subgrp ); } } foreach( const SourceFormatterStyle* style, fmt->styles ) { if( style->name().startsWith( Strings::userStylePrefix() ) ) { KConfigGroup stylegrp = fmtgrp.group( style->name() ); stylegrp.writeEntry( SourceFormatterController::styleCaptionKey(), style->caption() ); stylegrp.writeEntry( SourceFormatterController::styleContentKey(), style->content() ); stylegrp.writeEntry( SourceFormatterController::styleMimeTypesKey(), style->mimeTypesVariant() ); stylegrp.writeEntry( SourceFormatterController::styleSampleKey(), style->overrideSample() ); } } } KConfigGroup sessionConfig = Core::self()->sourceFormatterControllerInternal()->sessionConfig(); for ( LanguageMap::const_iterator iter = languages.constBegin(); iter != languages.constEnd(); ++iter ) { foreach(const QMimeType& mime, iter.value().mimetypes) { sessionConfig.writeEntry(mime.name(), QStringLiteral("%1||%2").arg(iter.value().selectedFormatter->formatter->name(), iter.value().selectedStyle->name())); } } sessionConfig.writeEntry( SourceFormatterController::kateModeLineConfigKey(), chkKateModelines->isChecked() ); sessionConfig.writeEntry( SourceFormatterController::kateOverrideIndentationConfigKey(), chkKateOverrideIndentation->isChecked() ); sessionConfig.sync(); globalConfig.sync(); Core::self()->sourceFormatterControllerInternal()->settingsChanged(); } void SourceFormatterSettings::defaults() { // do nothing } void SourceFormatterSettings::enableStyleButtons() { bool userEntry = styleList->currentItem() && styleList->currentItem()->data( STYLE_ROLE ).toString().startsWith( Strings::userStylePrefix() ); QString languageName = cbLanguages->currentText(); QMap< QString, LanguageSettings >::const_iterator it = languages.constFind(languageName); bool hasEditWidget = false; if (it != languages.constEnd()) { const LanguageSettings& l = it.value(); Q_ASSERT(l.selectedFormatter); ISourceFormatter* fmt = l.selectedFormatter->formatter; hasEditWidget = ( fmt && fmt->editStyleWidget( l.mimetypes.first() ) ); } btnDelStyle->setEnabled( userEntry ); btnEditStyle->setEnabled( userEntry && hasEditWidget ); btnNewStyle->setEnabled( cbFormatters->currentIndex() >= 0 && hasEditWidget ); } void SourceFormatterSettings::selectLanguage( int idx ) { cbFormatters->clear(); if( idx < 0 ) { cbFormatters->setEnabled( false ); selectFormatter( -1 ); return; } cbFormatters->setEnabled( true ); { QSignalBlocker blocker(cbFormatters); LanguageSettings& l = languages[cbLanguages->itemText( idx )]; foreach( const SourceFormatter* fmt, l.formatters ) { cbFormatters->addItem( fmt->formatter->caption(), fmt->formatter->name() ); } cbFormatters->setCurrentIndex(cbFormatters->findData(l.selectedFormatter->formatter->name())); } selectFormatter( cbFormatters->currentIndex() ); emit changed(); } void SourceFormatterSettings::selectFormatter( int idx ) { styleList->clear(); if( idx < 0 ) { styleList->setEnabled( false ); enableStyleButtons(); return; } styleList->setEnabled( true ); LanguageSettings& l = languages[ cbLanguages->currentText() ]; Q_ASSERT( idx < l.formatters.size() ); FormatterMap::const_iterator formatterIter = formatters.constFind(cbFormatters->itemData( idx ).toString()); Q_ASSERT( formatterIter != formatters.constEnd() ); Q_ASSERT( l.formatters.contains(formatterIter.value()) ); if (l.selectedFormatter != formatterIter.value()) { l.selectedFormatter = formatterIter.value(); l.selectedStyle = nullptr; // will hold 0 until a style is picked } foreach( const SourceFormatterStyle* style, formatterIter.value()->styles ) { if ( ! style->supportsLanguage(cbLanguages->currentText())) { // do not list items which do not support the selected language continue; } QListWidgetItem* item = addStyle( *style ); if (style == l.selectedStyle) { styleList->setCurrentItem(item); } } if (l.selectedStyle == nullptr) { styleList->setCurrentRow(0); } enableStyleButtons(); emit changed(); } void SourceFormatterSettings::selectStyle( int row ) { if( row < 0 ) { enableStyleButtons(); return; } styleList->setCurrentRow( row ); LanguageSettings& l = languages[ cbLanguages->currentText() ]; l.selectedStyle = l.selectedFormatter->styles[styleList->item( row )->data( STYLE_ROLE ).toString()]; enableStyleButtons(); updatePreview(); emit changed(); } void SourceFormatterSettings::deleteStyle() { Q_ASSERT( styleList->currentRow() >= 0 ); QListWidgetItem* item = styleList->currentItem(); LanguageSettings& l = languages[ cbLanguages->currentText() ]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatter::StyleMap::iterator styleIter = fmt->styles.find(item->data( STYLE_ROLE ).toString()); QStringList otherLanguageNames; QList otherlanguages; for ( LanguageMap::iterator languageIter = languages.begin(); languageIter != languages.end(); ++languageIter ) { if ( &languageIter.value() != &l && languageIter.value().selectedStyle == styleIter.value() ) { otherLanguageNames.append(languageIter.key()); otherlanguages.append(&languageIter.value()); } } if (!otherLanguageNames.empty() && KMessageBox::warningContinueCancel(this, i18n("The style %1 is also used for the following languages:\n%2.\nAre you sure you want to delete it?", styleIter.value()->caption(), otherLanguageNames.join(QStringLiteral("\n"))), i18n("Style being deleted")) != KMessageBox::Continue) { return; } styleList->takeItem( styleList->currentRow() ); fmt->styles.erase(styleIter); delete item; selectStyle( styleList->count() > 0 ? 0 : -1 ); foreach (LanguageSettings* lang, otherlanguages) { selectAvailableStyle(*lang); } updatePreview(); emit changed(); } void SourceFormatterSettings::editStyle() { QString language = cbLanguages->currentText(); Q_ASSERT( languages.contains( language ) ); LanguageSettings& l = languages[ language ]; SourceFormatter* fmt = l.selectedFormatter; QMimeType mimetype = l.mimetypes.first(); if( fmt->formatter->editStyleWidget( mimetype ) != nullptr ) { EditStyleDialog dlg( fmt->formatter, mimetype, *l.selectedStyle, this ); if( dlg.exec() == QDialog::Accepted ) { l.selectedStyle->setContent(dlg.content()); } updatePreview(); emit changed(); } } void SourceFormatterSettings::newStyle() { QListWidgetItem* item = styleList->currentItem(); LanguageSettings& l = languages[ cbLanguages->currentText() ]; SourceFormatter* fmt = l.selectedFormatter; int idx = 0; for( int i = 0; i < styleList->count(); i++ ) { QString name = styleList->item( i )->data( STYLE_ROLE ).toString(); if( name.startsWith( Strings::userStylePrefix() ) && name.midRef( Strings::userStylePrefix().length() ).toInt() >= idx ) { idx = name.midRef( Strings::userStylePrefix().length() ).toInt(); } } // Increase number for next style idx++; SourceFormatterStyle* s = new SourceFormatterStyle( QStringLiteral( "%1%2" ).arg( Strings::userStylePrefix() ).arg( idx ) ); if( item ) { SourceFormatterStyle* existstyle = fmt->styles[ item->data( STYLE_ROLE ).toString() ]; s->setCaption( i18n( "New %1", existstyle->caption() ) ); s->copyDataFrom( existstyle ); } else { s->setCaption( i18n( "New Style" ) ); } fmt->styles[ s->name() ] = s; QListWidgetItem* newitem = addStyle( *s ); selectStyle( styleList->row( newitem ) ); styleList->editItem( newitem ); emit changed(); } void SourceFormatterSettings::styleNameChanged( QListWidgetItem* item ) { if ( !item->isSelected() ) { return; } LanguageSettings& l = languages[ cbLanguages->currentText() ]; l.selectedStyle->setCaption( item->text() ); emit changed(); } QListWidgetItem* SourceFormatterSettings::addStyle( const SourceFormatterStyle& s ) { QListWidgetItem* item = new QListWidgetItem( styleList ); item->setText( s.caption() ); item->setData( STYLE_ROLE, s.name() ); if( s.name().startsWith( Strings::userStylePrefix() ) ) { item->setFlags( item->flags() | Qt::ItemIsEditable ); } styleList->addItem( item ); return item; } void SourceFormatterSettings::updatePreview() { m_document->setReadWrite( true ); QString langName = cbLanguages->itemText( cbLanguages->currentIndex() ); if( !langName.isEmpty() ) { LanguageSettings& l = languages[ langName ]; SourceFormatter* fmt = l.selectedFormatter; SourceFormatterStyle* style = l.selectedStyle; descriptionLabel->setText( style->description() ); if( style->usePreview() ) { ISourceFormatter* ifmt = fmt->formatter; QMimeType mime = l.mimetypes.first(); m_document->setHighlightingMode( style->modeForMimetype( mime ) ); //NOTE: this is ugly, but otherwise kate might remove tabs again :-/ // see also: https://bugs.kde.org/show_bug.cgi?id=291074 KTextEditor::ConfigInterface* iface = qobject_cast(m_document); QVariant oldReplaceTabs; if (iface) { oldReplaceTabs = iface->configValue(QStringLiteral("replace-tabs")); iface->setConfigValue(QStringLiteral("replace-tabs"), false); } m_document->setText( ifmt->formatSourceWithStyle( *style, ifmt->previewText( *style, mime ), QUrl(), mime ) ); if (iface) { iface->setConfigValue(QStringLiteral("replace-tabs"), oldReplaceTabs); } previewLabel->show(); textEditor->show(); }else{ previewLabel->hide(); textEditor->hide(); } } else { m_document->setText( i18n( "No Language selected" ) ); } m_view->setCursorPosition( KTextEditor::Cursor( 0, 0 ) ); m_document->setReadWrite( false ); } void SourceFormatterSettings::somethingChanged() { // Widgets are managed manually, so we have to explicitly tell KCModule // that we have some changes, otherwise it won't call "save" and/or will not activate // "Appy" emit changed(); } QString SourceFormatterSettings::name() const { return i18n("Source Formatter"); } QString SourceFormatterSettings::fullName() const { return i18n("Configure Source Formatter"); } QIcon SourceFormatterSettings::icon() const { return QIcon::fromTheme(QStringLiteral("text-field")); }