pathlisteditor.cpp 9.32 KB
Newer Older
1 2 3 4
/**************************************************************************
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies).
6
**
Eike Ziller's avatar
Eike Ziller committed
7
** Contact: http://www.qt-project.org/
8 9 10 11
**
**
** GNU Lesser General Public License Usage
**
hjk's avatar
hjk committed
12 13 14 15 16 17
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
18
**
con's avatar
con committed
19
** In addition, as a special exception, Nokia gives you certain additional
hjk's avatar
hjk committed
20
** rights. These rights are described in the Nokia Qt LGPL Exception
con's avatar
con committed
21 22
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
23 24 25 26 27
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
28 29 30 31 32
**
**************************************************************************/

#include "pathlisteditor.h"

33 34
#include "hostosinfo.h"

35 36 37 38 39 40 41 42 43 44
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPlainTextEdit>
#include <QToolButton>
#include <QSpacerItem>
#include <QFileDialog>
#include <QTextCursor>
#include <QTextBlock>
#include <QMenu>
#include <QAction>
45

46 47 48 49 50
#include <QSignalMapper>
#include <QMimeData>
#include <QSharedPointer>
#include <QDir>
#include <QDebug>
51

52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
/*!
    \class Utils::PathListEditor

    \brief A control that let's the user edit a list of (directory) paths
    using the platform separator (';',':').

    Typically used for
    path lists controlled by environment variables, such as
    PATH. It is based on a QPlainTextEdit as it should
    allow for convenient editing and non-directory type elements like
    \code
    "etc/mydir1:$SPECIAL_SYNTAX:/etc/mydir2".
    \endcode

    When pasting text into it, the platform separator will be replaced
    by new line characters for convenience.
 */

70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
namespace Utils {

// ------------ PathListPlainTextEdit:
// Replaces the platform separator ';',':' by '\n'
// when inserting, allowing for pasting in paths
// from the terminal or such.

class PathListPlainTextEdit : public QPlainTextEdit {
public:
    explicit PathListPlainTextEdit(QWidget *parent = 0);
protected:
    virtual void insertFromMimeData (const QMimeData *source);
};

PathListPlainTextEdit::PathListPlainTextEdit(QWidget *parent) :
    QPlainTextEdit(parent)
{
    // No wrapping, scroll at all events
    setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    setLineWrapMode(QPlainTextEdit::NoWrap);
}

void PathListPlainTextEdit::insertFromMimeData(const QMimeData *source)
{
    if (source->hasText()) {
        // replace separator
        QString text = source->text().trimmed();
        text.replace(PathListEditor::separator(), QLatin1Char('\n'));
        QSharedPointer<QMimeData> fixed(new QMimeData);
        fixed->setText(text);
        QPlainTextEdit::insertFromMimeData(fixed.data());
    } else {
        QPlainTextEdit::insertFromMimeData(source);
    }
}

// ------------ PathListEditorPrivate
struct PathListEditorPrivate {
    PathListEditorPrivate();

    QHBoxLayout *layout;
    QVBoxLayout *buttonLayout;
112 113
    QToolButton *toolButton;
    QMenu *buttonMenu;
114 115 116 117 118 119 120 121
    QPlainTextEdit *edit;
    QSignalMapper *envVarMapper;
    QString fileDialogTitle;
};

PathListEditorPrivate::PathListEditorPrivate()   :
        layout(new QHBoxLayout),
        buttonLayout(new QVBoxLayout),
122 123
        toolButton(new QToolButton),
        buttonMenu(new QMenu),
124 125 126
        edit(new PathListPlainTextEdit),
        envVarMapper(0)
{
127
    layout->setMargin(0);
128
    layout->addWidget(edit);
129
    buttonLayout->addWidget(toolButton);
130
    buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::MinimumExpanding));
131
    layout->addLayout(buttonLayout);
132 133 134 135
}

PathListEditor::PathListEditor(QWidget *parent) :
        QWidget(parent),
hjk's avatar
hjk committed
136
        d(new PathListEditorPrivate)
137
{
hjk's avatar
hjk committed
138 139 140 141 142
    setLayout(d->layout);
    d->toolButton->setPopupMode(QToolButton::MenuButtonPopup);
    d->toolButton->setText(tr("Insert..."));
    d->toolButton->setMenu(d->buttonMenu);
    connect(d->toolButton, SIGNAL(clicked()), this, SLOT(slotInsert()));
143 144

    addAction(tr("Add..."), this, SLOT(slotAdd()));
145
    addAction(tr("Delete Line"), this, SLOT(deletePathAtCursor()));
146
    addAction(tr("Clear"), this, SLOT(clear()));
147 148 149 150
}

PathListEditor::~PathListEditor()
{
hjk's avatar
hjk committed
151
    delete d;
152 153
}

154
static inline QAction *createAction(QObject *parent, const QString &text, QObject * receiver, const char *slotFunc)
155
{
156 157 158
    QAction *rc = new QAction(text, parent);
    QObject::connect(rc, SIGNAL(triggered()), receiver, slotFunc);
    return rc;
159 160
}

161
QAction *PathListEditor::addAction(const QString &text, QObject * receiver, const char *slotFunc)
162
{
163
    QAction *rc = createAction(this, text, receiver, slotFunc);
hjk's avatar
hjk committed
164
    d->buttonMenu->addAction(rc);
165 166 167
    return rc;
}

168 169 170 171 172
QAction *PathListEditor::insertAction(int index /* -1 */, const QString &text, QObject * receiver, const char *slotFunc)
{
    // Find the 'before' action
    QAction *beforeAction = 0;
    if (index >= 0) {
hjk's avatar
hjk committed
173
        const QList<QAction*> actions = d->buttonMenu->actions();
174 175 176 177 178
        if (index < actions.size())
            beforeAction = actions.at(index);
    }
    QAction *rc = createAction(this, text, receiver, slotFunc);
    if (beforeAction) {
hjk's avatar
hjk committed
179
        d->buttonMenu->insertAction(beforeAction, rc);
180
    } else {
hjk's avatar
hjk committed
181
        d->buttonMenu->addAction(rc);
182 183 184 185 186 187 188 189 190
    }
    return rc;
}

int PathListEditor::lastAddActionIndex()
{
    return 0; // Insert/Add
}

191 192 193 194 195 196 197
QString PathListEditor::pathListString() const
{
    return pathList().join(separator());
}

QStringList PathListEditor::pathList() const
{
hjk's avatar
hjk committed
198
    const QString text = d->edit->toPlainText().trimmed();
199 200 201 202 203 204 205 206 207 208 209 210
    if (text.isEmpty())
        return QStringList();
    // trim each line
    QStringList rc = text.split(QLatin1Char('\n'), QString::SkipEmptyParts);
    const QStringList::iterator end = rc.end();
    for (QStringList::iterator it = rc.begin(); it != end; ++it)
        *it = it->trimmed();
    return rc;
}

void PathListEditor::setPathList(const QStringList &l)
{
hjk's avatar
hjk committed
211
    d->edit->setPlainText(l.join(QString(QLatin1Char('\n'))));
212 213 214 215 216 217 218 219 220 221 222 223 224
}

void PathListEditor::setPathList(const QString &pathString)
{
    if (pathString.isEmpty()) {
        clear();
    } else {
        setPathList(pathString.split(separator(), QString::SkipEmptyParts));
    }
}

void PathListEditor::setPathListFromEnvVariable(const QString &var)
{
225
    setPathList(QString::fromLocal8Bit(qgetenv(var.toLocal8Bit())));
226 227 228 229
}

QString PathListEditor::fileDialogTitle() const
{
hjk's avatar
hjk committed
230
    return d->fileDialogTitle;
231 232 233 234
}

void PathListEditor::setFileDialogTitle(const QString &l)
{
hjk's avatar
hjk committed
235
    d->fileDialogTitle = l;
236 237 238 239
}

void PathListEditor::clear()
{
hjk's avatar
hjk committed
240
    d->edit->clear();
241 242 243 244
}

void PathListEditor::slotAdd()
{
hjk's avatar
hjk committed
245
    const QString dir = QFileDialog::getExistingDirectory(this, d->fileDialogTitle);
246
    if (!dir.isEmpty())
247 248 249 250 251
        appendPath(QDir::toNativeSeparators(dir));
}

void PathListEditor::slotInsert()
{
hjk's avatar
hjk committed
252
    const QString dir = QFileDialog::getExistingDirectory(this, d->fileDialogTitle);
253 254
    if (!dir.isEmpty())
        insertPathAtCursor(QDir::toNativeSeparators(dir));
255 256 257 258
}

QChar PathListEditor::separator()
{
259
    return HostOsInfo::isWindowsHost() ? QLatin1Char(';') : QLatin1Char(':');
260 261 262
}

// Add a button "Import from 'Path'"
263
void PathListEditor::addEnvVariableImportAction(const QString &var)
264
{
hjk's avatar
hjk committed
265 266 267
    if (!d->envVarMapper) {
        d->envVarMapper = new QSignalMapper(this);
        connect(d->envVarMapper, SIGNAL(mapped(QString)), this, SLOT(setPathListFromEnvVariable(QString)));
268 269
    }

270
    QAction *a = insertAction(lastAddActionIndex() + 1,
hjk's avatar
hjk committed
271 272
                              tr("From \"%1\"").arg(var), d->envVarMapper, SLOT(map()));
    d->envVarMapper->setMapping(a, var);
273 274 275 276
}

QString PathListEditor::text() const
{
hjk's avatar
hjk committed
277
    return d->edit->toPlainText();
278 279 280 281
}

void PathListEditor::setText(const QString &t)
{
hjk's avatar
hjk committed
282
    d->edit->setPlainText(t);
283 284 285 286 287 288
}

void PathListEditor::insertPathAtCursor(const QString &path)
{
    // If the cursor is at an empty line or at end(),
    // just insert. Else insert line before
hjk's avatar
hjk committed
289
    QTextCursor cursor = d->edit->textCursor();
290 291 292 293 294 295 296 297 298 299
    QTextBlock block = cursor.block();
    const bool needNewLine = !block.text().isEmpty();
    if (needNewLine) {
        cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
        cursor.insertBlock();
        cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
    }
    cursor.insertText(path);
    if (needNewLine) {
        cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
hjk's avatar
hjk committed
300
        d->edit->setTextCursor(cursor);
301 302 303
    }
}

304 305 306 307 308 309 310 311 312
void PathListEditor::appendPath(const QString &path)
{
    QString paths = text().trimmed();
    if (!paths.isEmpty())
        paths += QLatin1Char('\n');
    paths += path;
    setText(paths);
}

313 314 315
void PathListEditor::deletePathAtCursor()
{
    // Delete current line
hjk's avatar
hjk committed
316
    QTextCursor cursor = d->edit->textCursor();
317 318
    if (cursor.block().isValid()) {
        cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
319 320 321 322
        // Select down or until end of [last] line
        if (!cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor))
            cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
        cursor.removeSelectedText();
hjk's avatar
hjk committed
323
        d->edit->setTextCursor(cursor);
324 325 326 327
    }
}

} // namespace Utils