diff --git a/plugins/grepview/grepoutputdelegate.cpp b/plugins/grepview/grepoutputdelegate.cpp index 1d75efa365..6c923bbd3b 100644 --- a/plugins/grepview/grepoutputdelegate.cpp +++ b/plugins/grepview/grepoutputdelegate.cpp @@ -1,179 +1,183 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright (C) 2007 Andreas Pakulat * * Copyright 2010 Julien Desgats * * * * 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 "grepoutputdelegate.h" #include "grepoutputmodel.h" #include #include #include #include #include #include #include #include #include #include GrepOutputDelegate* GrepOutputDelegate::m_self = 0; GrepOutputDelegate* GrepOutputDelegate::self() { Q_ASSERT(m_self); return m_self; } GrepOutputDelegate::GrepOutputDelegate( QObject* parent ) : QStyledItemDelegate(parent) { Q_ASSERT(!m_self); m_self = this; } GrepOutputDelegate::~GrepOutputDelegate() { m_self = 0; } QColor GrepOutputDelegate::blendColor(QColor color1, QColor color2, double blend) const { return QColor(color1.red() * blend + color2.red() * (1-blend), color1.green() * blend + color2.green() * (1-blend), color1.blue() * blend + color2.blue() * (1-blend)); } void GrepOutputDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { // there is no function in QString to left-trim. A call to remove this this regexp does the job static const QRegExp leftspaces("^\\s*", Qt::CaseSensitive, QRegExp::RegExp); // rich text component const GrepOutputModel *model = dynamic_cast(index.model()); const GrepOutputItem *item = dynamic_cast(model->itemFromIndex(index)); QStyleOptionViewItemV4 options = option; initStyleOption(&options, index); // building item representation QTextDocument doc; QTextCursor cur(&doc); QPalette::ColorGroup cg = options.state & QStyle::State_Enabled ? QPalette::Normal : QPalette::Disabled; QPalette::ColorRole cr = options.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; QTextCharFormat fmt = cur.charFormat(); fmt.setFont(options.font); if(item && item->isText()) { // Use custom manual highlighting const KDevelop::SimpleRange rng = item->change()->m_range; // the line number appears grayed fmt.setForeground(options.palette.brush(QPalette::Disabled, cr)); cur.insertText(i18n("Line %1: ",item->lineNumber()), fmt); // switch to normal color fmt.setForeground(options.palette.brush(cg, cr)); cur.insertText(item->text().left(rng.start.column).remove(leftspaces), fmt); fmt.setFontWeight(QFont::Bold); // Blend the highlighted background color // For some reason, it is extremely slow to use alpha-blending directly here QColor bgHighlight = blendColor(option.palette.brush(QPalette::Highlight).color(), option.palette.brush(QPalette::Base).color(), 0.3); fmt.setBackground(bgHighlight); cur.insertText(item->text().mid(rng.start.column, rng.end.column - rng.start.column), fmt); fmt.clearBackground(); fmt.setFontWeight(QFont::Normal); cur.insertText(item->text().right(item->text().length() - rng.end.column), fmt); }else{ QString text; if(item) text = item->text(); else text = index.data().toString(); // Simply insert the text as html. We use this for the titles. doc.setHtml(text); } painter->save(); options.text = QString(); // text will be drawn separately options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter, options.widget); // set correct draw area QRect clip = options.widget->style()->subElementRect(QStyle::SE_ItemViewItemText, &options); QFontMetrics metrics(options.font); painter->translate(clip.topLeft() - QPoint(0, metrics.descent())); // We disable the clipping for now, as it leads to strange clipping errors // clip.setTopLeft(QPoint(0,0)); // painter->setClipRect(clip); QAbstractTextDocumentLayout::PaintContext ctx; // ctx.clip = clip; painter->setBackground(Qt::transparent); doc.documentLayout()->draw(painter, ctx); painter->restore(); } QSize GrepOutputDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { const GrepOutputModel *model = dynamic_cast(index.model()); const GrepOutputItem *item = dynamic_cast(model->itemFromIndex(index)); QSize ret = QStyledItemDelegate::sizeHint(option, index); //take account of additional width required for highlighting (bold text) //and line numbers. These are not included in the default Qt size calculation. if(item && item->isText()) { QFont font = option.font; + QFontMetrics metrics(font); font.setBold(true); QFontMetrics bMetrics(font); - //TODO: calculate width with more accuracy: here the whole text is considerated as bold - int width = bMetrics.width(item->text()) + - option.fontMetrics.width(i18n("Line %1: ",item->lineNumber())) + - std::max(option.decorationSize.width(), 0); + const KDevelop::SimpleRange rng = item->change()->m_range; + + int width = metrics.width(item->text().left(rng.start.column)) + + metrics.width(item->text().right(item->text().length() - rng.end.column)) + + bMetrics.width(item->text().mid(rng.start.column, rng.end.column - rng.start.column)) + + option.fontMetrics.width(i18n("Line %1: ",item->lineNumber())) + + std::max(option.decorationSize.width(), 0); ret.setWidth(width); }else{ // This is only used for titles, so not very performance critical QString text; if(item) text = item->text(); else text = index.data().toString(); QTextDocument doc; doc.setDocumentMargin(0); doc.setHtml(text); QSize newSize = doc.size().toSize(); if(newSize.height() > ret.height()) ret.setHeight(newSize.height()); } ret.setHeight(ret.height()+2); // We slightly increase the vertical size, else the view looks too crowded return ret; } diff --git a/plugins/grepview/grepoutputview.cpp b/plugins/grepview/grepoutputview.cpp index bc41dd1c6c..e86137d7a6 100644 --- a/plugins/grepview/grepoutputview.cpp +++ b/plugins/grepview/grepoutputview.cpp @@ -1,378 +1,387 @@ /************************************************************************** * 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 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 "org.kdevelop.GrepOutputView"; } const int GrepOutputView::HISTORY_SIZE = 5; GrepOutputView::GrepOutputView(QWidget* parent, GrepViewPlugin* plugin) : QWidget(parent) , m_next(0) , m_prev(0) , m_collapseAll(0) , m_expandAll(0) , m_clearSearchHistory(0) , m_statusLabel(0) , m_plugin(plugin) { Ui::GrepOutputView::setupUi(this); setWindowTitle(i18nc("@title:window", "Find/Replace Output View")); setWindowIcon(SmallIcon("edit-find")); m_prev = new QAction(KIcon("go-previous"), i18n("&Previous Item"), this); m_prev->setEnabled(false); m_next = new QAction(KIcon("go-next"), i18n("&Next Item"), this); m_next->setEnabled(false); m_collapseAll = new QAction(KIcon("arrow-left-double"), i18n("C&ollapse All"), this); // TODO change icon m_collapseAll->setEnabled(false); m_expandAll = new QAction(KIcon("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(KIcon("edit-find"), i18n("New &Search"), this); m_clearSearchHistory = new QAction(KIcon("edit-clear-list"), i18n("Clear Search History"), this); addAction(m_prev); addAction(m_next); addAction(m_collapseAll); addAction(m_expandAll); addAction(separator); addAction(newSearchAction); 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, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(modelSelectorContextMenu(QPoint))); connect(modelSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeModel(int))); resultsTreeView->setItemDelegate(GrepOutputDelegate::self()); resultsTreeView->setHeaderHidden(true); resultsTreeView->setUniformRowHeights(false); + resultsTreeView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); connect(m_prev, SIGNAL(triggered(bool)), this, SLOT(selectPreviousItem())); connect(m_next, SIGNAL(triggered(bool)), this, SLOT(selectNextItem())); connect(m_collapseAll, SIGNAL(triggered(bool)), this, SLOT(collapseAllItems())); connect(m_expandAll, SIGNAL(triggered(bool)), this, SLOT(expandAllItems())); connect(applyButton, SIGNAL(clicked()), this, SLOT(onApply())); connect(m_clearSearchHistory, SIGNAL(triggered(bool)), this, SLOT(clearSearchHistory())); connect(resultsTreeView, SIGNAL(collapsed(QModelIndex)), this, SLOT(updateScrollArea(QModelIndex))); connect(resultsTreeView, SIGNAL(expanded(QModelIndex)), this, SLOT(updateScrollArea(QModelIndex))); IPlugin *outputView = ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IOutputView"); connect(outputView, SIGNAL(selectPrevItem()), this, SLOT(selectPreviousItem())); connect(outputView, SIGNAL(selectNextItem()), this, SLOT(selectNextItem())); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); replacementCombo->addItems( cg.readEntry("LastReplacementItems", QStringList()) ); replacementCombo->setInsertPolicy(QComboBox::InsertAtTop); applyButton->setIcon(KIcon("dialog-ok-apply")); connect(replacementCombo, SIGNAL(editTextChanged(QString)), SLOT(replacementTextChanged(QString))); connect(newSearchAction, SIGNAL(triggered(bool)), this, SLOT(showDialog())); 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(QString name, QString descriptionOrUrl) { // 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, SIGNAL(rowsRemoved(QModelIndex,int,int)), this, SLOT(rowsRemoved())); connect(resultsTreeView, SIGNAL(activated(QModelIndex)), newModel, SLOT(activate(QModelIndex))); connect(replacementCombo, SIGNAL(editTextChanged(QString)), newModel, SLOT(setReplacement(QString))); connect(newModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(expandElements(QModelIndex))); connect(newModel, SIGNAL(showErrorMessage(QString,int)), this, SLOT(showErrorMessage(QString))); QString prettyUrl = descriptionOrUrl; if(descriptionOrUrl.startsWith('/')) prettyUrl = ICore::self()->projectController()->prettyFileName(descriptionOrUrl, KDevelop::IProjectController::FormatPlain); // appends new model to history QString displayName = i18n("Search \"%1\" in %2 (at time %3)", name, prettyUrl, QTime::currentTime().toString("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) { disconnect(model(), SIGNAL(showMessage(KDevelop::IStatus*,QString)), this, SLOT(showMessage(KDevelop::IStatus*,QString))); disconnect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateApplyState(QModelIndex,QModelIndex))); 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); connect(model(), SIGNAL(showMessage(KDevelop::IStatus*,QString)), this, SLOT(showMessage(KDevelop::IStatus*,QString))); connect(model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(updateApplyState(QModelIndex,QModelIndex))); 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) +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 ) { - setStyleSheet("QLabel { color : red; }"); - setMessage(errorMessage); + setMessage(errorMessage, Error); } void GrepOutputView::showMessage( KDevelop::IStatus* , const QString& message ) { - setStyleSheet(""); - setMessage(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::expandElements(const QModelIndex&) { m_prev->setEnabled(true); m_next->setEnabled(true); m_collapseAll->setEnabled(true); m_expandAll->setEnabled(true); resultsTreeView->expandAll(); + for (int col = 0; col < model()->columnCount(); ++col) + resultsTreeView->resizeColumnToContents(col); } 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(const QModelIndex& index) { resultsTreeView->resizeColumnToContents( index.column() ); } diff --git a/plugins/grepview/grepoutputview.h b/plugins/grepview/grepoutputview.h index c17a82eb12..881dbc0a94 100644 --- a/plugins/grepview/grepoutputview.h +++ b/plugins/grepview/grepoutputview.h @@ -1,94 +1,99 @@ /************************************************************************** * 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. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H #define KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H #include #include "ui_grepoutputview.h" namespace KDevelop { class IStatus; } class QModelIndex; class GrepViewPlugin; class GrepOutputModel; class GrepOutputDelegate; class GrepOutputViewFactory: public KDevelop::IToolViewFactory { public: GrepOutputViewFactory(GrepViewPlugin* plugin); virtual QWidget* create(QWidget* parent = 0); virtual Qt::DockWidgetArea defaultPosition(); virtual QString id() const; private: GrepViewPlugin* m_plugin; }; class GrepOutputView : public QWidget, Ui::GrepOutputView { Q_OBJECT public: + enum MessageType { + Information, + Error + }; + GrepOutputView(QWidget* parent, GrepViewPlugin* plugin); ~GrepOutputView(); GrepOutputModel* model(); /** * This causes the creation of a new model, the old one is kept in model history. * Oldest models are deleted if needed. * @return pointer to the new model */ GrepOutputModel* renewModel(QString name, QString descriptionOrUrl); - void setMessage(const QString& msg); + void setMessage(const QString& msg, MessageType type = Information); public Q_SLOTS: void showErrorMessage( const QString& errorMessage ); void showMessage( KDevelop::IStatus*, const QString& message ); void updateApplyState(const QModelIndex &topLeft, const QModelIndex &bottomRight); void changeModel(int index); void replacementTextChanged(QString); Q_SIGNALS: void outputViewIsClosed(); private: static const int HISTORY_SIZE; QAction* m_next; QAction* m_prev; QAction* m_collapseAll; QAction* m_expandAll; QAction* m_clearSearchHistory; QLabel* m_statusLabel; GrepViewPlugin *m_plugin; private slots: void selectPreviousItem(); void selectNextItem(); void collapseAllItems(); void expandAllItems(); void onApply(); void showDialog(); void expandElements( const QModelIndex & parent ); void rowsRemoved(); void clearSearchHistory(); void modelSelectorContextMenu(const QPoint& pos); void updateScrollArea( const QModelIndex &index ); void updateCheckable(); }; #endif // KDEVPLATFORM_PLUGIN_GREPOUTPUTVIEW_H