Skip to content
Snippets Groups Projects
editorview.cpp 19.70 KiB
/***************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2008-2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact:  Qt Software Information (qt-info@nokia.com)
**
**
** Non-Open Source Usage
**
** Licensees may use this file in accordance with the Qt Beta Version
** License Agreement, Agreement version 2.2 provided with the Software or,
** alternatively, in accordance with the terms contained in a written
** agreement between you and Nokia.
**
** GNU General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the packaging
** of this file.  Please review the following information to ensure GNU
** General Public Licensing requirements will be met:
**
** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
** http://www.gnu.org/copyleft/gpl.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt GPL Exception
** version 1.3, included in the file GPL_EXCEPTION.txt in this package.
**
***************************************************************************/

#include "editorview.h"
#include "editormanager.h"
#include "coreimpl.h"

#include <utils/qtcassert.h>

#include <QtCore/QDebug>
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include <QtCore/QMimeData>

#include <QtGui/QApplication>
#include <QtGui/QComboBox>
#include <QtGui/QHBoxLayout>
#include <QtGui/QLabel>
#include <QtGui/QMouseEvent>
#include <QtGui/QPainter>
#include <QtGui/QStackedWidget>
#include <QtGui/QStyle>
#include <QtGui/QStyleOption>
#include <QtGui/QToolBar>
#include <QtGui/QToolButton>
#ifdef Q_WS_MAC
#include <qmacstyle_mac.h>
#endif

Q_DECLARE_METATYPE(Core::IEditor *)

using namespace Core;
using namespace Core::Internal;


//================EditorModel====================
int EditorModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return 1;
}

int EditorModel::rowCount(const QModelIndex &parent) const
{
    if (!parent.isValid())
        return m_editors.count();
    return 0;
}

void EditorModel::addEditor(IEditor *editor, bool isDuplicate)
{
    if (isDuplicate) {
        m_duplicateEditors.append(editor);
        return;
    }

    int index = 0;

    QString fileName = editor->file()->fileName();
    for (index = 0; index < m_editors.count(); ++index)
        if (fileName < m_editors.at(index)->file()->fileName())
            break;

    beginInsertRows(QModelIndex(), index, index);
    m_editors.insert(index, editor);
    connect(editor, SIGNAL(changed()), this, SLOT(itemChanged()));
    endInsertRows();
}

void EditorModel::removeEditor(IEditor *editor)
{
    m_duplicateEditors.removeAll(editor);
    int idx = m_editors.indexOf(editor);
    if (idx < 0)
        return;
    beginRemoveRows(QModelIndex(), idx, idx);
    m_editors.removeAt(idx);
    endRemoveRows();
    disconnect(editor, SIGNAL(changed()), this, SLOT(itemChanged()));
}

bool EditorModel::isDuplicate(IEditor *editor) const
{
    return m_duplicateEditors.contains(editor);
}

IEditor *EditorModel::originalForDuplicate(IEditor *duplicate) const
{
    IFile *file = duplicate->file();
    foreach(IEditor *e, m_editors)
        if (e->file() == file)
            return e;
    return 0;
}

QList<IEditor *> EditorModel::duplicatesFor(IEditor *editor) const
{
    QList<IEditor *> result;
    IFile *file = editor->file();
    foreach(IEditor *e, m_duplicateEditors)
        if (e->file() == file)
            result += e;
    return result;
}

void EditorModel::emitDataChanged(IEditor *editor)
{
    int idx = m_editors.indexOf(editor);
    if (idx < 0)
        return;
    QModelIndex mindex = index(idx, 0);
    emit dataChanged(mindex, mindex);
}

QModelIndex EditorModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    if (column != 0 || row < 0 || row >= m_editors.count())
        return QModelIndex();
    return createIndex(row, column);
}

QVariant EditorModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();
    IEditor *editor = m_editors.at(index.row());
    QTC_ASSERT(editor, return QVariant());
    switch (role) {
    case Qt::DisplayRole:
        return editor->file()->isModified()
                ? editor->displayName() + QLatin1String("*")
                : editor->displayName();
    case Qt::DecorationRole:
        return editor->file()->isReadOnly()
                ? QIcon(QLatin1String(":/core/images/locked.png"))
                : QIcon();
    case Qt::ToolTipRole:
        return editor->file()->fileName().isEmpty()
                ? editor->displayName()
                : QDir::toNativeSeparators(editor->file()->fileName());
    case Qt::UserRole:
        return qVariantFromValue(editor);
    default:
        return QVariant();
    }
    return QVariant();
}

QModelIndex EditorModel::indexOf(IEditor *editor) const
{
    int idx = m_editors.indexOf(editor);
    if (idx < 0)
        return indexOf(editor->file()->fileName());
    return createIndex(idx, 0);
}

QModelIndex EditorModel::indexOf(const QString &fileName) const
{
    for (int i = 0; i < m_editors.count(); ++i)
        if (m_editors.at(i)->file()->fileName() == fileName)
            return createIndex(i, 0);
    return QModelIndex();
}


void EditorModel::itemChanged()
{
    emitDataChanged(qobject_cast<IEditor*>(sender()));
}



//================EditorView====================

EditorView::EditorView(EditorModel *model, QWidget *parent) :
    QWidget(parent),
    m_toolBar(new QWidget),
    m_container(new QStackedWidget(this)),
    m_editorList(new QComboBox),
    m_closeButton(new QToolButton),
    m_lockButton(new QToolButton),
    m_defaultToolBar(new QToolBar(this)),
    m_infoWidget(new QFrame(this)),
    m_editorForInfoWidget(0)
{
    QVBoxLayout *tl = new QVBoxLayout(this);
    tl->setSpacing(0);
    tl->setMargin(0);
    {
        m_editorList->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
        m_editorList->setMinimumContentsLength(20);
        m_editorList->setModel(model);
        m_editorList->setMaxVisibleItems(40);

        QToolBar *editorListToolBar = new QToolBar;

        editorListToolBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored);
        editorListToolBar->addWidget(m_editorList);

        m_defaultToolBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
        m_activeToolBar = m_defaultToolBar;

        QHBoxLayout *toolBarLayout = new QHBoxLayout;
        toolBarLayout->setMargin(0);
        toolBarLayout->setSpacing(0);
        toolBarLayout->addWidget(m_defaultToolBar);
        m_toolBar->setLayout(toolBarLayout);

        m_lockButton->setAutoRaise(true);
        m_lockButton->setProperty("type", QLatin1String("dockbutton"));

        m_closeButton->setAutoRaise(true);
        m_closeButton->setIcon(QIcon(":/core/images/closebutton.png"));
        m_closeButton->setProperty("type", QLatin1String("dockbutton"));

        QToolBar *rightToolBar = new QToolBar;
        rightToolBar->setLayoutDirection(Qt::RightToLeft);
        rightToolBar->addWidget(m_closeButton);
        rightToolBar->addWidget(m_lockButton);

        QHBoxLayout *toplayout = new QHBoxLayout;
        toplayout->setSpacing(0);
        toplayout->setMargin(0);
        toplayout->addWidget(editorListToolBar);
        toplayout->addWidget(m_toolBar, 1); // Custom toolbar stretches
        toplayout->addWidget(rightToolBar);

        QWidget *top = new QWidget;
        QVBoxLayout *vlayout = new QVBoxLayout(top);
        vlayout->setSpacing(0);
        vlayout->setMargin(0);
        vlayout->addLayout(toplayout);
        tl->addWidget(top);

        connect(m_editorList, SIGNAL(activated(int)), this, SLOT(listSelectionActivated(int)));
        connect(m_lockButton, SIGNAL(clicked()), this, SLOT(makeEditorWritable()));
        connect(m_closeButton, SIGNAL(clicked()), this, SLOT(closeView()));
    }
    {
        m_infoWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
        m_infoWidget->setLineWidth(1);
        m_infoWidget->setForegroundRole(QPalette::ToolTipText);
        m_infoWidget->setBackgroundRole(QPalette::ToolTipBase);
        m_infoWidget->setAutoFillBackground(true);


        QHBoxLayout *hbox = new QHBoxLayout(m_infoWidget);
        hbox->setMargin(2);
        m_infoWidgetLabel = new QLabel("Placeholder");
        m_infoWidgetLabel->setForegroundRole(QPalette::ToolTipText);
        hbox->addWidget(m_infoWidgetLabel);
        hbox->addStretch(1);

        m_infoWidgetButton = new QToolButton;
        m_infoWidgetButton->setText(tr("Placeholder"));
        hbox->addWidget(m_infoWidgetButton);

        QToolButton *closeButton = new QToolButton;
        closeButton->setAutoRaise(true);
        closeButton->setIcon(QIcon(":/core/images/clear.png"));
        closeButton->setToolTip(tr("Close"));
        connect(closeButton, SIGNAL(clicked()), m_infoWidget, SLOT(hide()));

        hbox->addWidget(closeButton);


        m_infoWidget->setVisible(false);
        tl->addWidget(m_infoWidget);
    }
    tl->addWidget(m_container);

}

void EditorView::showEditorInfoBar(const QString &kind,
                                           const QString &infoText,
                                           const QString &buttonText,
                                           QObject *object, const char *member)
{
    m_infoWidgetKind = kind;
    m_infoWidgetLabel->setText(infoText);
    m_infoWidgetButton->setText(buttonText);
    m_infoWidgetButton->disconnect();
    if (object && member)
        connect(m_infoWidgetButton, SIGNAL(clicked()), object, member);
    m_infoWidget->setVisible(true);
    m_editorForInfoWidget = currentEditor();
}

void EditorView::hideEditorInfoBar(const QString &kind)
{
    if (kind == m_infoWidgetKind)
        m_infoWidget->setVisible(false);
}


EditorView::~EditorView()
{
}

void EditorView::addEditor(IEditor *editor)
{
    if (m_container->indexOf(editor->widget()) != -1)
        return;

    m_container->addWidget(editor->widget());
    m_widgetEditorMap.insert(editor->widget(), editor);

    QToolBar *toolBar = editor->toolBar();
    if (toolBar) {
        toolBar->setVisible(false); // will be made visible in setCurrentEditor
        m_toolBar->layout()->addWidget(toolBar);
    }
    connect(editor, SIGNAL(changed()), this, SLOT(checkEditorStatus()));

    if (m_container->count() == 1) {
        m_editorList->setCurrentIndex(qobject_cast<EditorModel*>(m_editorList->model())->indexOf(editor->file()->fileName()).row());
        updateEditorStatus(editor);
        updateToolBar(editor);
    }
}

bool EditorView::hasEditor(IEditor *editor) const
{
    return (m_container->indexOf(editor->widget()) != -1);
}

void EditorView::closeView()
{
    if (editorCount() == 0)
        return;
    EditorManager *em = CoreImpl::instance()->editorManager();
    em->closeView(this);
}

void EditorView::removeEditor(IEditor *editor)
{
    QTC_ASSERT(editor, return);
    const int index = m_container->indexOf(editor->widget());
    if (index != -1) {
        m_container->removeWidget(editor->widget());
        m_widgetEditorMap.remove(editor->widget());
        editor->widget()->setParent(0);
        disconnect(editor, SIGNAL(changed()), this, SLOT(updateEditorStatus()));
        QToolBar *toolBar = editor->toolBar();
        if (toolBar != 0) {
            if (m_activeToolBar == toolBar) {
                m_activeToolBar = m_defaultToolBar;
                m_activeToolBar->setVisible(true);
            }
            m_toolBar->layout()->removeWidget(toolBar);
            toolBar->setVisible(false);
            toolBar->setParent(0);
        }
    }
}

IEditor *EditorView::currentEditor() const
{
    if (m_container->count() > 0)
        return m_widgetEditorMap.value(m_container->currentWidget());
    return 0;
}

void EditorView::setCurrentEditor(IEditor *editor)
{
    if (!editor || m_container->count() <= 0
        || m_container->indexOf(editor->widget()) == -1)
        return;
    if (editor)
        qDebug() << "EditorView::setCurrentEditor" << editor << editor->file()->fileName();

    const int idx = m_container->indexOf(editor->widget());
    QTC_ASSERT(idx >= 0, return);
    m_container->setCurrentIndex(idx);
    m_editorList->setCurrentIndex(qobject_cast<EditorModel*>(m_editorList->model())->indexOf(editor->file()->fileName()).row());
    updateEditorStatus(editor);
    updateToolBar(editor);


    if (editor != m_editorForInfoWidget) {
        m_infoWidget->hide();
        m_editorForInfoWidget = 0;
    }
}

void EditorView::checkEditorStatus()
{
        IEditor *editor = qobject_cast<IEditor *>(sender());
        if (editor == currentEditor())
            updateEditorStatus(editor);
}

void EditorView::updateEditorStatus(IEditor *editor)
{
    static const QIcon lockedIcon(QLatin1String(":/core/images/locked.png"));
    static const QIcon unlockedIcon(QLatin1String(":/core/images/unlocked.png"));

    if (editor->file()->isReadOnly()) {
        m_lockButton->setIcon(lockedIcon);
        m_lockButton->setEnabled(!editor->file()->fileName().isEmpty());
        m_lockButton->setToolTip(tr("Make writable"));
    } else {
        m_lockButton->setIcon(unlockedIcon);
        m_lockButton->setEnabled(false);
        m_lockButton->setToolTip(tr("File is writable"));
    }
    if (currentEditor() == editor)
        m_editorList->setToolTip(
                editor->file()->fileName().isEmpty()
                ? editor->displayName()
                    : QDir::toNativeSeparators(editor->file()->fileName())
                    );

}

void EditorView::updateToolBar(IEditor *editor)
{
    QToolBar *toolBar = editor->toolBar();
    if (!toolBar)
        toolBar = m_defaultToolBar;
    if (m_activeToolBar == toolBar)
        return;
    toolBar->setVisible(true);
    m_activeToolBar->setVisible(false);
    m_activeToolBar = toolBar;
}

int EditorView::editorCount() const
{
    return m_container->count();
}

QList<IEditor *> EditorView::editors() const
{
    return m_widgetEditorMap.values();
}


void EditorView::makeEditorWritable()
{
    CoreImpl::instance()->editorManager()->makeEditorWritable(currentEditor());
}

void EditorView::listSelectionActivated(int index)
{
    QAbstractItemModel *model = m_editorList->model();
    IEditor *editor = model->data(model->index(index, 0), Qt::UserRole).value<IEditor*>();
    CoreImpl::instance()->editorManager()->activateEditor(this, editor);
}


SplitterOrView::SplitterOrView(EditorView *view, QWidget *parent)
        : QWidget(parent)
{
    m_isRoot = true;
    m_layout = new QStackedLayout(this);
    m_view = view;
    m_splitter = 0;
    m_layout->addWidget(m_view);
}

SplitterOrView::SplitterOrView(Core::IEditor *editor, QWidget *parent)
        : QWidget(parent)
{
    m_isRoot = false;
    m_layout = new QStackedLayout(this);
    m_view = new EditorView(CoreImpl::instance()->editorManager()->openedEditorsModel());
    m_view->addEditor(editor);
    m_splitter = 0;
    m_layout->addWidget(m_view);
}


SplitterOrView *SplitterOrView::findFirstView()
{
    if (m_splitter) {
        for (int i = 0; i < m_splitter->count(); ++i) {
            if (SplitterOrView *splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i)))
                if (SplitterOrView *result = splitterOrView->findFirstView())
                    return result;
        }
        return 0;
    }
    return this;
}

SplitterOrView *SplitterOrView::findView(Core::IEditor *editor)
{
    if (!editor || hasEditor(editor))
        return this;
    if (m_splitter) {
        for (int i = 0; i < m_splitter->count(); ++i) {
            if (SplitterOrView *splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i)))
                if (SplitterOrView *result = splitterOrView->findView(editor))
                    return result;
        }
    }
    return 0;
}

SplitterOrView *SplitterOrView::findSplitter(Core::IEditor *editor)
{
    if (m_splitter) {
        for (int i = 0; i < m_splitter->count(); ++i) {
            if (SplitterOrView *splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i))) {
                if (splitterOrView->hasEditor(editor))
                    return this;
                if (SplitterOrView *result = splitterOrView->findSplitter(editor))
                    return result;
            }
        }
    }
    return 0;
}

SplitterOrView *SplitterOrView::findSplitter(SplitterOrView *child)
{
    if (m_splitter) {
        for (int i = 0; i < m_splitter->count(); ++i) {
            if (SplitterOrView *splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i))) {
                if (splitterOrView == child)
                    return this;
                if (SplitterOrView *result = splitterOrView->findSplitter(child))
                    return result;
            }
        }
    }
    return 0;
}

SplitterOrView *SplitterOrView::findNextView(Core::IEditor *editor)
{
    bool found = false;
    return findNextView_helper(editor, &found);
}

SplitterOrView *SplitterOrView::findNextView_helper(Core::IEditor *editor, bool *found)
{
    if (*found && m_view) {
        return this;
    }

    if (hasEditor(editor)) {
        *found = true;
        return 0;
    }

    if (m_splitter) {
        for (int i = 0; i < m_splitter->count(); ++i) {
            if (SplitterOrView *splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i))) {
                if (SplitterOrView *result = splitterOrView->findNextView_helper(editor, found))
                    return result;
            }
        }
    }
    return 0;
}

void SplitterOrView::split(Qt::Orientation orientation)
{
    Q_ASSERT(m_view && m_splitter == 0);
    m_splitter = new QSplitter(this);
    m_splitter->setOrientation(orientation);
    Core::IEditor *e = m_view->currentEditor();

    SplitterOrView *focusView = 0;

    m_view->removeEditor(e);
    m_splitter->addWidget((focusView = new SplitterOrView(e)));

    Core::IEditor *duplicate = CoreImpl::instance()->editorManager()->duplicateEditor(e);
    m_splitter->addWidget(new SplitterOrView(duplicate));
    m_layout->addWidget(m_splitter);

    if (!m_isRoot) {
        delete m_view;
        m_view = 0;
    } else {
        m_layout->setCurrentWidget(m_splitter);
    }

    CoreImpl::instance()->editorManager()->activateEditor(e);
}

void SplitterOrView::close()
{
    Q_ASSERT(!m_isRoot);
    if (m_view) {
        m_view->closeView();
        delete m_view;
        m_view = 0;
    }
    closeSplitterEditors();
}

void SplitterOrView::closeSplitterEditors()
{
    if (!m_splitter)
        return;
    for (int i = 0; i < m_splitter->count(); ++i) {
        if (SplitterOrView *splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i))) {
            splitterOrView->close();
        }
    }
}

void SplitterOrView::unsplit(Core::IEditor *editor)
{
    if (!m_splitter)
        return;

    qDebug() << "unsplit" << this << m_splitter;
#if 0
    SplitterOrView *splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(0));
    Q_ASSERT(splitterOrView != 0);

    qDebug() << "splitter or view is" << splitterOrView;

    if (editor) { // pick the other side
        if (SplitterOrView *view = findView(editor)) {
            qDebug() << "view to close is" << view;
            view->close();
            delete view;
        }
        splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(0));
        qDebug() << "other splitter or view is" << splitterOrView;
    }

    QSplitter *old_splitter = m_splitter;
    EditorView *old_view = m_view;

    m_splitter = splitterOrView->splitter();
    m_view = splitterOrView->view();

    qDebug() << "new splitter/view" << m_splitter << m_view;

    if (m_splitter)
        m_layout->addWidget(m_splitter);
    if (m_view)
        m_layout->addWidget(m_view);

//    delete old_view;
//    delete old_splitter;
#endif
}