tabsettings.cpp 11 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
5
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, 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.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
31
#include "tabsettings.h"

32
#include <QtCore/QDebug>
con's avatar
con committed
33
34
35
36
37
#include <QtCore/QSettings>
#include <QtCore/QString>
#include <QtGui/QTextCursor>
#include <QtGui/QTextDocument>

38
static const char *spacesForTabsKey = "SpacesForTabs";
39
static const char *autoSpacesForTabsKey = "AutoSpacesForTabs";
40
41
42
43
static const char *smartBackspaceKey = "SmartBackspace";
static const char *autoIndentKey = "AutoIndent";
static const char *tabSizeKey = "TabSize";
static const char *indentSizeKey = "IndentSize";
44
static const char *indentBracesKey = "IndentBraces";
Joel Nordell's avatar
Joel Nordell committed
45
static const char *tabKeyBehaviorKey = "TabKeyBehavior";
46
static const char *groupPostfix = "TabSettings";
con's avatar
con committed
47
48
49
50
51

namespace TextEditor {

TabSettings::TabSettings() :
    m_spacesForTabs(true),
52
    m_autoSpacesForTabs(false),
con's avatar
con committed
53
54
55
    m_autoIndent(true),
    m_smartBackspace(false),
    m_tabSize(8),
Joel Nordell's avatar
Joel Nordell committed
56
    m_indentSize(4),
57
    m_indentBraces(false),
Joel Nordell's avatar
Joel Nordell committed
58
    m_tabKeyBehavior(TabNeverIndents)
con's avatar
con committed
59
60
61
62
63
64
65
66
67
68
{
}

void TabSettings::toSettings(const QString &category, QSettings *s) const
{
    QString group = QLatin1String(groupPostfix);
    if (!category.isEmpty())
        group.insert(0, category);
    s->beginGroup(group);
    s->setValue(QLatin1String(spacesForTabsKey),  m_spacesForTabs);
69
    s->setValue(QLatin1String(autoSpacesForTabsKey),  m_autoSpacesForTabs);
con's avatar
con committed
70
71
72
73
    s->setValue(QLatin1String(autoIndentKey), m_autoIndent);
    s->setValue(QLatin1String(smartBackspaceKey), m_smartBackspace);
    s->setValue(QLatin1String(tabSizeKey), m_tabSize);
    s->setValue(QLatin1String(indentSizeKey), m_indentSize);
74
    s->setValue(QLatin1String(indentBracesKey), m_indentBraces);
Joel Nordell's avatar
Joel Nordell committed
75
    s->setValue(QLatin1String(tabKeyBehaviorKey), m_tabKeyBehavior);
con's avatar
con committed
76
77
78
79
80
81
82
83
84
85
86
87
    s->endGroup();
}

void TabSettings::fromSettings(const QString &category, const QSettings *s)
{
    QString group = QLatin1String(groupPostfix);
    if (!category.isEmpty())
        group.insert(0, category);
    group += QLatin1Char('/');

    *this = TabSettings(); // Assign defaults

88
    m_spacesForTabs     = s->value(group + QLatin1String(spacesForTabsKey), m_spacesForTabs).toBool();
89
    m_autoSpacesForTabs = s->value(group + QLatin1String(autoSpacesForTabsKey), m_autoSpacesForTabs).toBool();
90
91
92
93
94
95
    m_autoIndent        = s->value(group + QLatin1String(autoIndentKey), m_autoIndent).toBool();
    m_smartBackspace    = s->value(group + QLatin1String(smartBackspaceKey), m_smartBackspace).toBool();
    m_tabSize           = s->value(group + QLatin1String(tabSizeKey), m_tabSize).toInt();
    m_indentSize        = s->value(group + QLatin1String(indentSizeKey), m_indentSize).toInt();
    m_indentBraces      = s->value(group + QLatin1String(indentBracesKey), m_indentBraces).toBool();
    m_tabKeyBehavior    = (TabKeyBehavior)s->value(group + QLatin1String(tabKeyBehaviorKey), m_tabKeyBehavior).toInt();
con's avatar
con committed
96
97
}

mae's avatar
mae committed
98
99
100
101
102
103
104
105

bool TabSettings::cursorIsAtBeginningOfLine(const QTextCursor &cursor) const
{
    QString text = cursor.block().text();
    int fns = firstNonSpace(text);
    return (cursor.position() - cursor.block().position() <= fns);
}

con's avatar
con committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
int TabSettings::lineIndentPosition(const QString &text) const
{
    int i = 0;
    while (i < text.size()) {
        if (!text.at(i).isSpace())
            break;
        ++i;
    }
    int column = columnAt(text, i);
    return i - (column % m_indentSize);
}

int TabSettings::firstNonSpace(const QString &text) const
{
    int i = 0;
    while (i < text.size()) {
        if (!text.at(i).isSpace())
            return i;
        ++i;
    }
    return i;
}

129
130
131
132
133
134
QString TabSettings::indentationString(const QString &text) const
{
    return text.left(firstNonSpace(text));
}


135
136
137
138
139
140
int TabSettings::indentationColumn(const QString &text) const
{
    return columnAt(text, firstNonSpace(text));
}


con's avatar
con committed
141
142
143
144
145
146
147
148
149
150
151
int TabSettings::trailingWhitespaces(const QString &text) const
{
    int i = 0;
    while (i < text.size()) {
        if (!text.at(text.size()-1-i).isSpace())
            return i;
        ++i;
    }
    return i;
}

152
bool TabSettings::isIndentationClean(const QTextBlock &block) const
con's avatar
con committed
153
154
155
{
    int i = 0;
    int spaceCount = 0;
156
157
    QString text = block.text();
    bool spacesForTabs = guessSpacesForTabs(block);
con's avatar
con committed
158
159
160
161
162
163
164
    while (i < text.size()) {
        QChar c = text.at(i);
        if (!c.isSpace())
            return true;

        if (c == QLatin1Char(' ')) {
            ++spaceCount;
165
            if (!spacesForTabs && spaceCount == m_tabSize)
con's avatar
con committed
166
167
                return false;
        } else if (c == QLatin1Char('\t')) {
168
            if (spacesForTabs || spaceCount != m_indentSize)
con's avatar
con committed
169
170
171
172
173
174
175
176
                return false;
            spaceCount = 0;
        }
        ++i;
    }
    return true;
}

Joel Nordell's avatar
Joel Nordell committed
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
bool TabSettings::tabShouldIndent(const QTextDocument *document, QTextCursor cursor, int *suggestedPosition) const
{
    if (m_tabKeyBehavior == TabNeverIndents)
        return false;
    QTextCursor tc = cursor;
    if (suggestedPosition)
        *suggestedPosition = tc.position(); // At least suggest original position
    tc.movePosition(QTextCursor::StartOfLine);
    if (tc.atBlockEnd()) // cursor was on a blank line
        return true;
    if (document->characterAt(tc.position()).isSpace()) {
        tc.movePosition(QTextCursor::WordRight);
        if (tc.columnNumber() >= cursor.columnNumber()) {
            if (suggestedPosition)
                *suggestedPosition = tc.position(); // Suggest position after whitespace
            if (m_tabKeyBehavior == TabLeadingWhitespaceIndents)
                return true;
        }
    }
    return (m_tabKeyBehavior == TabAlwaysIndents);
}

con's avatar
con committed
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
int TabSettings::columnAt(const QString &text, int position) const
{
    int column = 0;
    for (int i = 0; i < position; ++i) {
        if (text.at(i) == QLatin1Char('\t'))
            column = column - (column % m_tabSize) + m_tabSize;
        else
            ++column;
    }
    return column;
}

int TabSettings::spacesLeftFromPosition(const QString &text, int position) const
{
    int i = position;
    while (i > 0) {
        if (!text.at(i-1).isSpace())
            break;
        --i;
    }
    return position - i;
}

int TabSettings::indentedColumn(int column, bool doIndent) const
{
    int aligned = (column / m_indentSize) * m_indentSize;
    if (doIndent)
        return aligned + m_indentSize;
    if (aligned < column)
        return aligned;
    return qMax(0, aligned - m_indentSize);
}

232
233
bool TabSettings::guessSpacesForTabs(const QTextBlock& _block) const {
    if (m_autoSpacesForTabs && _block.isValid()) {
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
        QVector<QTextBlock> currentBlocks(2, _block); // [0] looks back; [1] looks forward
        int maxLookAround = 100;
        while (maxLookAround-- > 0) {
            currentBlocks[0] = currentBlocks.at(0).previous();
            currentBlocks[1] = currentBlocks.at(1).next();
            bool done = true;
            foreach(QTextBlock block, currentBlocks) {
                if (block.isValid())
                    done = false;
                if (!block.isValid() || block.text().isEmpty())
                    continue;
                QChar firstChar = block.text().at(0);
                if (firstChar == QLatin1Char(' ')) {
                    return true;
                } else if (firstChar == QLatin1Char('\t')) {
                    return false;
                }
251
            }
252
253
            if (done)
                break;
254
255
256
257
258
259
        }
    }
    return m_spacesForTabs;
}

QString TabSettings::indentationString(int startColumn, int targetColumn, const QTextBlock& block) const
con's avatar
con committed
260
261
{
    targetColumn = qMax(startColumn, targetColumn);
262
    if (guessSpacesForTabs(block))
con's avatar
con committed
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
        return QString(targetColumn - startColumn, QLatin1Char(' '));

    QString s;
    int alignedStart = startColumn - (startColumn % m_tabSize) + m_tabSize;
    if (alignedStart > startColumn && alignedStart <= targetColumn) {
        s += QLatin1Char('\t');
        startColumn = alignedStart;
    }
    if (int columns = targetColumn - startColumn) {
        int tabs = columns / m_tabSize;
        s += QString(tabs, QLatin1Char('\t'));
        s += QString(columns - tabs * m_tabSize, QLatin1Char(' '));
    }
    return s;
}

void TabSettings::indentLine(QTextBlock block, int newIndent) const
{
    const QString text = block.text();
    const int oldBlockLength = text.size();

    // Quickly check whether indenting is required.
285
    if (indentationColumn(text) == newIndent)
con's avatar
con committed
286
287
        return;

288
    const QString indentString = indentationString(0, newIndent, block);
con's avatar
con committed
289
290
291
292
293
294
295
296
297
298
299
300
301
302
    newIndent = indentString.length();

    if (oldBlockLength == indentString.length() && text == indentString)
        return;

    QTextCursor cursor(block);
    cursor.beginEditBlock();
    cursor.movePosition(QTextCursor::StartOfBlock);
    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace(text));
    cursor.removeSelectedText();
    cursor.insertText(indentString);
    cursor.endEditBlock();
}

mae's avatar
mae committed
303
304
305
306
307
308
309
310
311
312
313
void TabSettings::reindentLine(QTextBlock block, int delta) const
{
    const QString text = block.text();
    const int oldBlockLength = text.size();

    int oldIndent = indentationColumn(text);
    int newIndent = qMax(oldIndent + delta, 0);

    if (oldIndent == newIndent)
        return;

314
    const QString indentString = indentationString(0, newIndent, block);
mae's avatar
mae committed
315
316
317
318
319
320
321
322
323
324
325
326
327
328
    newIndent = indentString.length();

    if (oldBlockLength == indentString.length() && text == indentString)
        return;

    QTextCursor cursor(block);
    cursor.beginEditBlock();
    cursor.movePosition(QTextCursor::StartOfBlock);
    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, firstNonSpace(text));
    cursor.removeSelectedText();
    cursor.insertText(indentString);
    cursor.endEditBlock();
}

con's avatar
con committed
329
330
331
bool TabSettings::equals(const TabSettings &ts) const
{
    return m_spacesForTabs == ts.m_spacesForTabs
332
        && m_autoSpacesForTabs == ts.m_autoSpacesForTabs
con's avatar
con committed
333
334
335
        && m_autoIndent == ts.m_autoIndent
        && m_smartBackspace == ts.m_smartBackspace
        && m_tabSize == ts.m_tabSize
Joel Nordell's avatar
Joel Nordell committed
336
        && m_indentSize == ts.m_indentSize
337
        && m_indentBraces == ts.m_indentBraces
Joel Nordell's avatar
Joel Nordell committed
338
        && m_tabKeyBehavior == ts.m_tabKeyBehavior;
con's avatar
con committed
339
340
341
}

} // namespace TextEditor