1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "pathlisteditor.h"
27
28 #include "hostosinfo.h"
29 #include "stringutils.h"
30
31 #include <QDebug>
32 #include <QFileDialog>
33 #include <QMenu>
34 #include <QMimeData>
35 #include <QPlainTextEdit>
36 #include <QPushButton>
37 #include <QSharedPointer>
38 #include <QTextBlock>
39 #include <QVBoxLayout>
40
41 /*!
42 \class Utils::PathListEditor
43
44 \brief The PathListEditor class is a control that lets the user edit a list
45 of (directory) paths
46 using the platform separator (';',':').
47
48 Typically used for
49 path lists controlled by environment variables, such as
50 PATH. It is based on a QPlainTextEdit as it should
51 allow for convenient editing and non-directory type elements like
52 \code
53 "etc/mydir1:$SPECIAL_SYNTAX:/etc/mydir2".
54 \endcode
55
56 When pasting text into it, the platform separator will be replaced
57 by new line characters for convenience.
58 */
59
60 namespace Utils {
61
62 const int PathListEditor::lastInsertButtonIndex = 0;
63
64 // ------------ PathListPlainTextEdit:
65 // Replaces the platform separator ';',':' by '\n'
66 // when inserting, allowing for pasting in paths
67 // from the terminal or such.
68
69 class PathListPlainTextEdit : public QPlainTextEdit {
70 public:
71 explicit PathListPlainTextEdit(QWidget *parent = nullptr);
72 protected:
73 void insertFromMimeData (const QMimeData *source) override;
74 };
75
PathListPlainTextEdit(QWidget * parent)76 PathListPlainTextEdit::PathListPlainTextEdit(QWidget *parent) :
77 QPlainTextEdit(parent)
78 {
79 // No wrapping, scroll at all events
80 setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
81 setLineWrapMode(QPlainTextEdit::NoWrap);
82 }
83
insertFromMimeData(const QMimeData * source)84 void PathListPlainTextEdit::insertFromMimeData(const QMimeData *source)
85 {
86 if (source->hasText()) {
87 // replace separator
88 QString text = source->text().trimmed();
89 text.replace(HostOsInfo::pathListSeparator(), QLatin1Char('\n'));
90 QSharedPointer<QMimeData> fixed(new QMimeData);
91 fixed->setText(text);
92 QPlainTextEdit::insertFromMimeData(fixed.data());
93 } else {
94 QPlainTextEdit::insertFromMimeData(source);
95 }
96 }
97
98 // ------------ PathListEditorPrivate
99 struct PathListEditorPrivate {
100 PathListEditorPrivate();
101
102 QHBoxLayout *layout;
103 QVBoxLayout *buttonLayout;
104 QPlainTextEdit *edit;
105 QString fileDialogTitle;
106 };
107
PathListEditorPrivate()108 PathListEditorPrivate::PathListEditorPrivate() :
109 layout(new QHBoxLayout),
110 buttonLayout(new QVBoxLayout),
111 edit(new PathListPlainTextEdit)
112 {
113 layout->setContentsMargins(0, 0, 0, 0);
114 layout->addWidget(edit);
115 layout->addLayout(buttonLayout);
116 buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored,
117 QSizePolicy::MinimumExpanding));
118 }
119
PathListEditor(QWidget * parent)120 PathListEditor::PathListEditor(QWidget *parent) :
121 QWidget(parent),
122 d(new PathListEditorPrivate)
123 {
124 setLayout(d->layout);
125 addButton(tr("Insert..."), this, [this](){
126 const QString dir = QFileDialog::getExistingDirectory(this, d->fileDialogTitle);
127 if (!dir.isEmpty())
128 insertPathAtCursor(QDir::toNativeSeparators(dir));
129 });
130 addButton(tr("Delete Line"), this, [this](){
131 deletePathAtCursor();
132 });
133 addButton(tr("Clear"), this, [this](){
134 d->edit->clear();
135 });
136 }
137
~PathListEditor()138 PathListEditor::~PathListEditor()
139 {
140 delete d;
141 }
142
addButton(const QString & text,QObject * parent,std::function<void ()> slotFunc)143 QPushButton *PathListEditor::addButton(const QString &text, QObject *parent,
144 std::function<void()> slotFunc)
145 {
146 return insertButton(d->buttonLayout->count() - 1, text, parent, slotFunc);
147 }
148
insertButton(int index,const QString & text,QObject * parent,std::function<void ()> slotFunc)149 QPushButton *PathListEditor::insertButton(int index /* -1 */, const QString &text, QObject *parent,
150 std::function<void()> slotFunc)
151 {
152 auto rc = new QPushButton(text, this);
153 QObject::connect(rc, &QPushButton::pressed, parent, slotFunc);
154 d->buttonLayout->insertWidget(index, rc);
155 return rc;
156 }
157
pathListString() const158 QString PathListEditor::pathListString() const
159 {
160 return pathList().join(HostOsInfo::pathListSeparator());
161 }
162
pathList() const163 QStringList PathListEditor::pathList() const
164 {
165 const QString text = d->edit->toPlainText().trimmed();
166 if (text.isEmpty())
167 return QStringList();
168 // trim each line
169 QStringList rc = text.split('\n', Qt::SkipEmptyParts);
170 const QStringList::iterator end = rc.end();
171 for (QStringList::iterator it = rc.begin(); it != end; ++it)
172 *it = it->trimmed();
173 return rc;
174 }
175
setPathList(const QStringList & l)176 void PathListEditor::setPathList(const QStringList &l)
177 {
178 d->edit->setPlainText(l.join(QLatin1Char('\n')));
179 }
180
setPathList(const QString & pathString)181 void PathListEditor::setPathList(const QString &pathString)
182 {
183 if (pathString.isEmpty()) {
184 clear();
185 } else {
186 setPathList(pathString.split(HostOsInfo::pathListSeparator(),
187 Qt::SkipEmptyParts));
188 }
189 }
190
fileDialogTitle() const191 QString PathListEditor::fileDialogTitle() const
192 {
193 return d->fileDialogTitle;
194 }
195
setFileDialogTitle(const QString & l)196 void PathListEditor::setFileDialogTitle(const QString &l)
197 {
198 d->fileDialogTitle = l;
199 }
200
clear()201 void PathListEditor::clear()
202 {
203 d->edit->clear();
204 }
205
text() const206 QString PathListEditor::text() const
207 {
208 return d->edit->toPlainText();
209 }
210
setText(const QString & t)211 void PathListEditor::setText(const QString &t)
212 {
213 d->edit->setPlainText(t);
214 }
215
insertPathAtCursor(const QString & path)216 void PathListEditor::insertPathAtCursor(const QString &path)
217 {
218 // If the cursor is at an empty line or at end(),
219 // just insert. Else insert line before
220 QTextCursor cursor = d->edit->textCursor();
221 QTextBlock block = cursor.block();
222 const bool needNewLine = !block.text().isEmpty();
223 if (needNewLine) {
224 cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
225 cursor.insertBlock();
226 cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
227 }
228 cursor.insertText(path);
229 if (needNewLine) {
230 cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
231 d->edit->setTextCursor(cursor);
232 }
233 }
234
deletePathAtCursor()235 void PathListEditor::deletePathAtCursor()
236 {
237 // Delete current line
238 QTextCursor cursor = d->edit->textCursor();
239 if (cursor.block().isValid()) {
240 cursor.movePosition(QTextCursor::StartOfLine, QTextCursor::MoveAnchor);
241 // Select down or until end of [last] line
242 if (!cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor))
243 cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor);
244 cursor.removeSelectedText();
245 d->edit->setTextCursor(cursor);
246 }
247 }
248
249 } // namespace Utils
250