bineditorplugin.cpp 17.8 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
Eike Ziller's avatar
Eike Ziller committed
3
4
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
con's avatar
con committed
5
**
hjk's avatar
hjk committed
6
** This file is part of Qt Creator.
con's avatar
con committed
7
**
hjk's avatar
hjk committed
8
9
10
11
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
Eike Ziller's avatar
Eike Ziller committed
12
13
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
Eike Ziller's avatar
Eike Ziller committed
14
** use the contact form at http://www.qt.io/contact-us.
15
**
16
** GNU Lesser General Public License Usage
hjk's avatar
hjk committed
17
** Alternatively, this file may be used under the terms of the GNU Lesser
Eike Ziller's avatar
Eike Ziller committed
18
19
20
21
22
23
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
hjk's avatar
hjk committed
24
**
Eike Ziller's avatar
Eike Ziller committed
25
26
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
con's avatar
con committed
27
28
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
hjk's avatar
hjk committed
29
****************************************************************************/
hjk's avatar
hjk committed
30

con's avatar
con committed
31
32
33
34
#include "bineditorplugin.h"
#include "bineditor.h"
#include "bineditorconstants.h"

35
36
#include <coreplugin/icore.h>

37
#include <QCoreApplication>
38
39
40
41
42
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QRegExp>
#include <QVariant>
43

44
45
46
47
48
49
50
#include <QMenu>
#include <QAction>
#include <QMessageBox>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QRegExpValidator>
#include <QToolBar>
con's avatar
con committed
51

52
#include <coreplugin/actionmanager/actionmanager.h>
con's avatar
con committed
53
#include <coreplugin/coreconstants.h>
54
#include <coreplugin/id.h>
55
#include <coreplugin/editormanager/editormanager.h>
56
#include <coreplugin/editormanager/ieditor.h>
57
#include <coreplugin/find/ifindsupport.h>
58
#include <coreplugin/idocument.h>
59
#include <extensionsystem/pluginmanager.h>
con's avatar
con committed
60
#include <utils/reloadpromptutils.h>
61
#include <utils/qtcassert.h>
con's avatar
con committed
62

63
using namespace Utils;
64
using namespace Core;
con's avatar
con committed
65

hjk's avatar
hjk committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
namespace BinEditor {

///////////////////////////////// BinEditorWidgetFactory //////////////////////////////////

/*!
   \class BinEditor::BinEditorWidgetFactory
   \brief The BinEditorWidgetFactory class offers a service registered with
   PluginManager to create bin editor widgets for plugins
   without direct linkage.

   \sa ExtensionSystem::PluginManager::getObjectByClassName, ExtensionSystem::invoke
*/

class BinEditorWidgetFactory : public QObject
{
    Q_OBJECT
public:
    BinEditorWidgetFactory() {}

    Q_INVOKABLE QWidget *createWidget(QWidget *parent)
    {
        return new BinEditorWidget(parent);
    }
};

namespace Internal {
con's avatar
con committed
92

93
class BinEditorFind : public IFindSupport
con's avatar
con committed
94
95
{
    Q_OBJECT
96

con's avatar
con committed
97
public:
98
    BinEditorFind(BinEditorWidget *widget)
ck's avatar
ck committed
99
    {
100
        m_widget = widget;
ck's avatar
ck committed
101
        m_incrementalStartPos = m_contPos = -1;
102
        m_incrementalWrappedState = false;
ck's avatar
ck committed
103
    }
con's avatar
con committed
104
105

    bool supportsReplace() const { return false; }
hjk's avatar
hjk committed
106
107
    QString currentFindString() const { return QString(); }
    QString completedFindString() const { return QString(); }
108

109
    FindFlags supportedFindFlags() const
110
    {
111
        return FindBackward | FindCaseSensitively;
112
113
    }

ck's avatar
ck committed
114
115
116
    void resetIncrementalSearch()
    {
        m_incrementalStartPos = m_contPos = -1;
117
        m_incrementalWrappedState = false;
ck's avatar
ck committed
118
119
    }

120
    virtual void highlightAll(const QString &txt, FindFlags findFlags)
121
    {
122
        m_widget->highlightSearchResults(txt.toLatin1(), textDocumentFlagsForFindFlags(findFlags));
123
124
    }

125
    void clearHighlights()
hjk's avatar
hjk committed
126
127
128
    {
        m_widget->highlightSearchResults(QByteArray());
    }
con's avatar
con committed
129

130
    int find(const QByteArray &pattern, int pos, FindFlags findFlags, bool *wrapped)
hjk's avatar
hjk committed
131
    {
132
133
        if (wrapped)
            *wrapped = false;
con's avatar
con committed
134
        if (pattern.isEmpty()) {
135
            m_widget->setCursorPosition(pos);
con's avatar
con committed
136
137
138
            return pos;
        }

139
        int res = m_widget->find(pattern, pos, textDocumentFlagsForFindFlags(findFlags));
140
        if (res < 0) {
141
142
            pos = (findFlags & FindBackward) ? -1 : 0;
            res = m_widget->find(pattern, pos, textDocumentFlagsForFindFlags(findFlags));
143
144
145
146
147
148
            if (res < 0)
                return res;
            if (wrapped)
                *wrapped = true;
        }
        return res;
con's avatar
con committed
149
150
    }

151
    Result findIncremental(const QString &txt, FindFlags findFlags) {
con's avatar
con committed
152
        QByteArray pattern = txt.toLatin1();
ck's avatar
ck committed
153
154
155
        if (pattern != m_lastPattern)
            resetIncrementalSearch(); // Because we don't search for nibbles.
        m_lastPattern = pattern;
con's avatar
con committed
156
        if (m_incrementalStartPos < 0)
157
            m_incrementalStartPos = m_widget->selectionStart();
ck's avatar
ck committed
158
159
        if (m_contPos == -1)
            m_contPos = m_incrementalStartPos;
160
161
162
163
164
165
        bool wrapped;
        int found = find(pattern, m_contPos, findFlags, &wrapped);
        if (wrapped != m_incrementalWrappedState && (found >= 0)) {
            m_incrementalWrappedState = wrapped;
            showWrapIndicator(m_widget);
        }
ck's avatar
ck committed
166
167
168
        Result result;
        if (found >= 0) {
            result = Found;
169
            m_widget->highlightSearchResults(pattern, textDocumentFlagsForFindFlags(findFlags));
ck's avatar
ck committed
170
171
172
173
174
            m_contPos = -1;
        } else {
            if (found == -2) {
                result = NotYetFound;
                m_contPos +=
175
                        findFlags & FindBackward
176
                        ? -BinEditorWidget::SearchStride : BinEditorWidget::SearchStride;
ck's avatar
ck committed
177
178
179
            } else {
                result = NotFound;
                m_contPos = -1;
180
                m_widget->highlightSearchResults(QByteArray(), 0);
ck's avatar
ck committed
181
182
183
            }
        }
        return result;
con's avatar
con committed
184
185
    }

186
    Result findStep(const QString &txt, FindFlags findFlags) {
con's avatar
con committed
187
188
        QByteArray pattern = txt.toLatin1();
        bool wasReset = (m_incrementalStartPos < 0);
ck's avatar
ck committed
189
        if (m_contPos == -1) {
190
            m_contPos = m_widget->cursorPosition();
191
            if (findFlags & FindBackward)
192
                m_contPos = m_widget->selectionStart()-1;
ck's avatar
ck committed
193
        }
194
195
196
197
        bool wrapped;
        int found = find(pattern, m_contPos, findFlags, &wrapped);
        if (wrapped)
            showWrapIndicator(m_widget);
ck's avatar
ck committed
198
199
200
        Result result;
        if (found >= 0) {
            result = Found;
con's avatar
con committed
201
            m_incrementalStartPos = found;
ck's avatar
ck committed
202
203
            m_contPos = -1;
            if (wasReset)
204
                m_widget->highlightSearchResults(pattern, textDocumentFlagsForFindFlags(findFlags));
ck's avatar
ck committed
205
206
        } else if (found == -2) {
            result = NotYetFound;
207
            m_contPos += findFlags & FindBackward
208
                         ? -BinEditorWidget::SearchStride : BinEditorWidget::SearchStride;
ck's avatar
ck committed
209
210
211
212
213
214
        } else {
            result = NotFound;
            m_contPos = -1;
        }

        return result;
con's avatar
con committed
215
    }
ck's avatar
ck committed
216

con's avatar
con committed
217
private:
218
    BinEditorWidget *m_widget;
con's avatar
con committed
219
    int m_incrementalStartPos;
ck's avatar
ck committed
220
    int m_contPos; // Only valid if last result was NotYetFound.
221
    bool m_incrementalWrappedState;
ck's avatar
ck committed
222
    QByteArray m_lastPattern;
con's avatar
con committed
223
224
225
};


226
class BinEditorDocument : public IDocument
con's avatar
con committed
227
228
229
{
    Q_OBJECT
public:
230
    BinEditorDocument(BinEditorWidget *parent) :
231
        IDocument(parent)
con's avatar
con committed
232
    {
233
        setId(Core::Constants::K_DEFAULT_BINARY_EDITOR_ID);
hjk's avatar
hjk committed
234
        setMimeType(QLatin1String(BinEditor::Constants::C_BINEDITOR_MIMETYPE));
235
        m_widget = parent;
236
237
238
239
        connect(m_widget, SIGNAL(dataRequested(quint64)),
            this, SLOT(provideData(quint64)));
        connect(m_widget, SIGNAL(newRangeRequested(quint64)),
            this, SLOT(provideNewRange(quint64)));
con's avatar
con committed
240
241
    }

242
243
244
245
246
247
248
249
    bool setContents(const QByteArray &contents)
    {
        if (!contents.isEmpty())
            return false;
        m_widget->clear();
        return true;
    }

250
251
252
253
254
255
    ReloadBehavior reloadBehavior(ChangeTrigger state, ChangeType type) const
    {
        Q_UNUSED(state)
        return type == TypeRemoved ? BehaviorSilent : BehaviorAsk;
    }

256
    bool save(QString *errorString, const QString &fn, bool autoSave)
257
    {
258
        QTC_ASSERT(!autoSave, return true); // bineditor does not support autosave - it would be a bit expensive
259
260
        const FileName fileNameToUse = fn.isEmpty() ? filePath() : FileName::fromString(fn);
        if (m_widget->save(errorString, filePath().toString(), fileNameToUse.toString())) {
261
            setFilePath(fileNameToUse);
con's avatar
con committed
262
            return true;
ck's avatar
ck committed
263
264
        } else {
            return false;
con's avatar
con committed
265
266
267
        }
    }

268
    bool open(QString *errorString, const QString &fileName, quint64 offset = 0) {
con's avatar
con committed
269
        QFile file(fileName);
270
        quint64 size = static_cast<quint64>(file.size());
271
        if (size == 0 && !fileName.isEmpty()) {
Robert Loehning's avatar
Robert Loehning committed
272
            QString msg = tr("The Binary Editor cannot open empty files.");
273
274
275
            if (errorString)
                *errorString = msg;
            else
276
                QMessageBox::critical(ICore::mainWindow(), tr("File Error"), msg);
277
            return false;
278
        }
279
280
        if (offset >= size)
            return false;
281
        if (file.open(QIODevice::ReadOnly)) {
Daniel Teske's avatar
Daniel Teske committed
282
            file.close();
283
            setFilePath(FileName::fromString(fileName));
284
            m_widget->setSizes(offset, file.size());
con's avatar
con committed
285
286
            return true;
        }
287
288
289
290
291
        QString errStr = tr("Cannot open %1: %2").arg(
                QDir::toNativeSeparators(fileName), file.errorString());
        if (errorString)
            *errorString = errStr;
        else
292
            QMessageBox::critical(ICore::mainWindow(), tr("File Error"), errStr);
con's avatar
con committed
293
294
295
        return false;
    }

296
private slots:
297
298
    void provideData(quint64 block)
    {
299
        const FileName fn = filePath();
300
        if (fn.isEmpty())
301
            return;
302
        QFile file(fn.toString());
303
        if (file.open(QIODevice::ReadOnly)) {
304
            int blockSize = m_widget->dataBlockSize();
305
306
            file.seek(block * blockSize);
            QByteArray data = file.read(blockSize);
307
            file.close();
308
309
310
            const int dataSize = data.size();
            if (dataSize != blockSize)
                data += QByteArray(blockSize - dataSize, 0);
311
            m_widget->addData(block, data);
312
        } else {
313
            QMessageBox::critical(ICore::mainWindow(), tr("File Error"),
314
                                  tr("Cannot open %1: %2").arg(
315
                                        fn.toUserOutput(), file.errorString()));
316
317
        }
    }
318

319
320
    void provideNewRange(quint64 offset)
    {
321
        open(0, filePath().toString(), offset);
322
323
    }

324
public:
con's avatar
con committed
325
326

    QString defaultPath() const { return QString(); }
hjk's avatar
hjk committed
327

con's avatar
con committed
328
329
    QString suggestedFileName() const { return QString(); }

330
331
    bool isModified() const { return isTemporary()/*e.g. memory view*/ ? false
                                                                       : m_widget->isModified(); }
hjk's avatar
hjk committed
332

333
    bool isFileReadOnly() const {
334
        const FileName fn = filePath();
335
        if (fn.isEmpty())
hjk's avatar
hjk committed
336
            return false;
337
        return !fn.toFileInfo().isWritable();
con's avatar
con committed
338
339
340
341
    }

    bool isSaveAsAllowed() const { return true; }

342
    bool reload(QString *errorString, ReloadFlag flag, ChangeType type) {
343
        if (flag == FlagIgnore)
344
            return true;
345
        if (type == TypePermissions) {
con's avatar
con committed
346
            emit changed();
347
        } else {
348
            emit aboutToReload();
Fawzi Mohamed's avatar
Fawzi Mohamed committed
349
350
            int cPos = m_widget->cursorPosition();
            m_widget->clear();
351
            const bool success = open(errorString, filePath().toString());
Fawzi Mohamed's avatar
Fawzi Mohamed committed
352
            m_widget->setCursorPosition(cPos);
353
354
            emit reloadFinished(success);
            return success;
con's avatar
con committed
355
        }
356
        return true;
con's avatar
con committed
357
358
359
    }

private:
360
    BinEditorWidget *m_widget;
con's avatar
con committed
361
362
};

363
class BinEditor : public IEditor
con's avatar
con committed
364
365
366
{
    Q_OBJECT
public:
367
    BinEditor(BinEditorWidget *widget)
368
    {
369
        setWidget(widget);
370
        m_file = new BinEditorDocument(widget);
371
        m_context.add(Core::Constants::K_DEFAULT_BINARY_EDITOR_ID);
hjk's avatar
hjk committed
372
        m_context.add(Constants::C_BINEDITOR);
373
374
375
376
377
        m_addressEdit = new QLineEdit;
        QRegExpValidator * const addressValidator
            = new QRegExpValidator(QRegExp(QLatin1String("[0-9a-fA-F]{1,16}")),
                m_addressEdit);
        m_addressEdit->setValidator(addressValidator);
con's avatar
con committed
378
379
380
381
382
383

        QHBoxLayout *l = new QHBoxLayout;
        QWidget *w = new QWidget;
        l->setMargin(0);
        l->setContentsMargins(0, 0, 5, 0);
        l->addStretch(1);
384
        l->addWidget(m_addressEdit);
con's avatar
con committed
385
386
387
388
389
390
        w->setLayout(l);

        m_toolBar = new QToolBar;
        m_toolBar->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
        m_toolBar->addWidget(w);

hjk's avatar
hjk committed
391
392
        widget->setEditor(this);

393
        connect(widget, SIGNAL(cursorPositionChanged(int)), SLOT(updateCursorPosition(int)));
hjk's avatar
hjk committed
394
        connect(m_addressEdit, SIGNAL(editingFinished()), SLOT(jumpToAddress()));
395
396
        connect(widget, SIGNAL(modificationChanged(bool)), m_file, SIGNAL(changed()));
        updateCursorPosition(widget->cursorPosition());
con's avatar
con committed
397
    }
hjk's avatar
hjk committed
398

399
400
    ~BinEditor()
    {
401
        delete m_widget;
402
    }
con's avatar
con committed
403

404
405
    bool open(QString *errorString, const QString &fileName, const QString &realFileName) {
        QTC_ASSERT(fileName == realFileName, return false); // The bineditor can do no autosaving
406
        return m_file->open(errorString, fileName);
con's avatar
con committed
407
    }
408
    IDocument *document() { return m_file; }
con's avatar
con committed
409

con's avatar
con committed
410
    QWidget *toolBar() { return m_toolBar; }
con's avatar
con committed
411

412
private slots:
con's avatar
con committed
413
    void updateCursorPosition(int position) {
414
        m_addressEdit->setText(QString::number(editorWidget()->baseAddress() + position, 16));
415
416
417
    }

    void jumpToAddress() {
418
419
420
421
422
423
424
425
426
        editorWidget()->jumpToAddress(m_addressEdit->text().toULongLong(0, 16));
        updateCursorPosition(editorWidget()->cursorPosition());
    }

private:
    inline BinEditorWidget *editorWidget() const
    {
        QTC_ASSERT(qobject_cast<BinEditorWidget *>(m_widget.data()), return 0);
        return static_cast<BinEditorWidget *>(m_widget.data());
con's avatar
con committed
427
428
429
    }

private:
430
    BinEditorDocument *m_file;
con's avatar
con committed
431
    QToolBar *m_toolBar;
432
    QLineEdit *m_addressEdit;
con's avatar
con committed
433
434
435
436
437
438
439
440
441
};



///////////////////////////////// BinEditorFactory //////////////////////////////////

BinEditorFactory::BinEditorFactory(BinEditorPlugin *owner) :
    m_owner(owner)
{
442
443
444
    setId(Core::Constants::K_DEFAULT_BINARY_EDITOR_ID);
    setDisplayName(qApp->translate("OpenWith::Editors", Constants::C_BINEDITOR_DISPLAY_NAME));
    addMimeType(Constants::C_BINEDITOR_MIMETYPE);
con's avatar
con committed
445
446
}

447
IEditor *BinEditorFactory::createEditor()
con's avatar
con committed
448
{
449
    BinEditorWidget *widget = new BinEditorWidget();
hjk's avatar
hjk committed
450
451
    BinEditor *editor = new BinEditor(widget);

452
    m_owner->initializeEditor(widget);
hjk's avatar
hjk committed
453
    return editor;
con's avatar
con committed
454
455
456
457
458
}


///////////////////////////////// BinEditorPlugin //////////////////////////////////

459
BinEditorPlugin::BinEditorPlugin() : m_factory(0)
con's avatar
con committed
460
461
462
463
464
465
466
467
{
    m_undoAction = m_redoAction = m_copyAction = m_selectAllAction = 0;
}

BinEditorPlugin::~BinEditorPlugin()
{
}

468
QAction *BinEditorPlugin::registerNewAction(Id id, const QString &title)
con's avatar
con committed
469
470
{
    QAction *result = new QAction(title, this);
471
    ActionManager::registerAction(result, id, m_context);
con's avatar
con committed
472
473
474
    return result;
}

475
QAction *BinEditorPlugin::registerNewAction(Id id,
con's avatar
con committed
476
477
478
479
480
481
482
483
484
485
486
487
                                            QObject *receiver,
                                            const char *slot,
                                            const QString &title)
{
    QAction *rc = registerNewAction(id, title);
    if (!rc)
        return 0;

    connect(rc, SIGNAL(triggered()), receiver, slot);
    return rc;
}

488
void BinEditorPlugin::initializeEditor(BinEditorWidget *widget)
con's avatar
con committed
489
{
490
    m_context.add(Constants::C_BINEDITOR);
con's avatar
con committed
491
    if (!m_undoAction) {
hjk's avatar
hjk committed
492
493
494
495
        m_undoAction      = registerNewAction(Core::Constants::UNDO, this, SLOT(undoAction()), tr("&Undo"));
        m_redoAction      = registerNewAction(Core::Constants::REDO, this, SLOT(redoAction()), tr("&Redo"));
        m_copyAction      = registerNewAction(Core::Constants::COPY, this, SLOT(copyAction()));
        m_selectAllAction = registerNewAction(Core::Constants::SELECTALL, this, SLOT(selectAllAction()));
con's avatar
con committed
496
497
    }

498
499
    QObject::connect(widget, SIGNAL(undoAvailable(bool)), this, SLOT(updateActions()));
    QObject::connect(widget, SIGNAL(redoAvailable(bool)), this, SLOT(updateActions()));
con's avatar
con committed
500
501

    Aggregation::Aggregate *aggregate = new Aggregation::Aggregate;
502
    BinEditorFind *binEditorFind = new BinEditorFind(widget);
con's avatar
con committed
503
    aggregate->add(binEditorFind);
504
    aggregate->add(widget);
con's avatar
con committed
505
506
}

507
bool BinEditorPlugin::initialize(const QStringList &arguments, QString *errorMessage)
con's avatar
con committed
508
{
509
    Q_UNUSED(arguments)
510
    Q_UNUSED(errorMessage)
511

512
513
    connect(Core::EditorManager::instance(), SIGNAL(currentEditorChanged(Core::IEditor*)),
        this, SLOT(updateCurrentEditor(Core::IEditor*)));
con's avatar
con committed
514
515

    addAutoReleasedObject(new BinEditorFactory(this));
516
    addAutoReleasedObject(new BinEditorWidgetFactory);
con's avatar
con committed
517
518
519
520
521
522
523
    return true;
}

void BinEditorPlugin::extensionsInitialized()
{
}

524
void BinEditorPlugin::updateCurrentEditor(IEditor *editor)
con's avatar
con committed
525
{
526
    BinEditorWidget *binEditor = 0;
527
    if (editor)
528
        binEditor = qobject_cast<BinEditorWidget *>(editor->widget());
529
530
531
    if (m_currentEditor == binEditor)
        return;
    m_currentEditor = binEditor;
con's avatar
con committed
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
    updateActions();
}

void BinEditorPlugin::updateActions()
{
    bool hasEditor = (m_currentEditor != 0);
    if (m_selectAllAction)
        m_selectAllAction->setEnabled(hasEditor);
    if (m_undoAction)
        m_undoAction->setEnabled(m_currentEditor && m_currentEditor->isUndoAvailable());
    if (m_redoAction)
        m_redoAction->setEnabled(m_currentEditor && m_currentEditor->isRedoAvailable());
}

void BinEditorPlugin::undoAction()
{
    if (m_currentEditor)
        m_currentEditor->undo();
}

void BinEditorPlugin::redoAction()
{
    if (m_currentEditor)
        m_currentEditor->redo();
}

void BinEditorPlugin::copyAction()
{
    if (m_currentEditor)
        m_currentEditor->copy();
}

void BinEditorPlugin::selectAllAction()
{
    if (m_currentEditor)
        m_currentEditor->selectAll();
}

hjk's avatar
hjk committed
570
571
} // namespace Internal
} // namespace BinEditor
con's avatar
con committed
572
573

#include "bineditorplugin.moc"