bineditorplugin.cpp 17.8 KB
Newer Older
hjk's avatar
hjk committed
1
/****************************************************************************
con's avatar
con committed
2
**
3
** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
hjk's avatar
hjk committed
4
** Contact: http://www.qt-project.org/legal
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
12
** 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
** a written agreement between you and Digia.  For licensing terms and
Eike Ziller's avatar
Eike Ziller committed
13
14
** conditions see http://www.qt.io/licensing.  For further information
** 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
25
26
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt 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>
con's avatar
con committed
59
#include <coreplugin/mimedatabase.h>
60
#include <extensionsystem/pluginmanager.h>
con's avatar
con committed
61
#include <utils/reloadpromptutils.h>
62
#include <utils/qtcassert.h>
con's avatar
con committed
63

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 Core::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
    Core::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, Core::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, Core::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, Core::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, Core::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 Core::IDocument
con's avatar
con committed
227
228
229
{
    Q_OBJECT
public:
230
    BinEditorDocument(BinEditorWidget *parent) :
231
        Core::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
        const QString fileNameToUse
260
261
262
                = fn.isEmpty() ? filePath() : fn;
        if (m_widget->save(errorString, filePath(), fileNameToUse)) {
            setFilePath(fileNameToUse);
con's avatar
con committed
263
            return true;
ck's avatar
ck committed
264
265
        } else {
            return false;
con's avatar
con committed
266
267
268
        }
    }

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

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

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

325
public:
con's avatar
con committed
326
327

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

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

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

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

    bool isSaveAsAllowed() const { return true; }

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

private:
362
    BinEditorWidget *m_widget;
con's avatar
con committed
363
364
};

365
class BinEditor : public Core::IEditor
con's avatar
con committed
366
367
368
{
    Q_OBJECT
public:
369
    BinEditor(BinEditorWidget *widget)
370
    {
371
        setWidget(widget);
372
        m_file = new BinEditorDocument(widget);
373
        m_context.add(Core::Constants::K_DEFAULT_BINARY_EDITOR_ID);
hjk's avatar
hjk committed
374
        m_context.add(Constants::C_BINEDITOR);
375
376
377
378
379
        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
380
381
382
383
384
385

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

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

hjk's avatar
hjk committed
393
394
        widget->setEditor(this);

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

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

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

con's avatar
con committed
412
    QWidget *toolBar() { return m_toolBar; }
con's avatar
con committed
413

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

    void jumpToAddress() {
420
421
422
423
424
425
426
427
428
        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
429
430
431
    }

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



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

BinEditorFactory::BinEditorFactory(BinEditorPlugin *owner) :
    m_owner(owner)
{
444
445
446
    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
447
448
}

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

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


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

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

BinEditorPlugin::~BinEditorPlugin()
{
}

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

hjk's avatar
hjk committed
477
QAction *BinEditorPlugin::registerNewAction(Core::Id id,
con's avatar
con committed
478
479
480
481
482
483
484
485
486
487
488
489
                                            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;
}

490
void BinEditorPlugin::initializeEditor(BinEditorWidget *widget)
con's avatar
con committed
491
{
492
    m_context.add(Constants::C_BINEDITOR);
con's avatar
con committed
493
    if (!m_undoAction) {
hjk's avatar
hjk committed
494
495
496
497
        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
498
499
    }

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

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

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

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

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

void BinEditorPlugin::extensionsInitialized()
{
}

526
void BinEditorPlugin::updateCurrentEditor(Core::IEditor *editor)
con's avatar
con committed
527
{
528
    BinEditorWidget *binEditor = 0;
529
    if (editor)
530
        binEditor = qobject_cast<BinEditorWidget *>(editor->widget());
531
532
533
    if (m_currentEditor == binEditor)
        return;
    m_currentEditor = binEditor;
con's avatar
con committed
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
570
571
    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
572
573
} // namespace Internal
} // namespace BinEditor
con's avatar
con committed
574
575

#include "bineditorplugin.moc"