QScintilla入门指南之边界栏

一、边界栏基础知识

1. 边界栏基础知识

边界栏就是编辑器侧⾯的侧边栏。使⽤边界栏时,需要记住如下内容:

  • 无需创建边界栏对象:不必像通常创建对象那样创建边界栏。它们是⼀直存在的,但是默认情况下不可见。而且,默认情况下,有5个边界栏。但是从QScintilla 2.10开始,可以使用函数setMargins(int margins)来改变该值。而函数margins()则返回该值。
  • 边界栏标识符:每个边界栏都有⼀个唯⼀的标识符。计数从0开始。
  • 边界栏可见性:默认情况下有5个边界栏,但是默认情况下都是不可见的。如果要使得⼀个或多个可见,只需要将他们的宽度设置为⼤于0即可。
  • 边界栏类型:并非所有的边界类型都相同。存在多种边界类型,例如:行号区、符号区等。

如下图所示。该图显示了⼀个具有4个边界栏的编辑器。其中,边界0是行号区,边界1和3是符号区,边界2用于短文本:

1.1 边界栏类型

设置边界栏类型的函数如下:

void QsciScintilla::setMarginType(int margin, MarginType type);

其中参数MarginType是枚举变量,其常用类型如下:

  • NumberMargin:行号栏。记住,当行号增⻓时,边界宽度不会自动调整。因此,如果需要,必须要手动调整其宽度。
  • SymbolMargin:符号栏,包括用于折叠的符号。
  • SymbolMarginDefaultForegroundColor:符号栏使用默认前景色作为其背景色。
  • SymbolMarginDefaultBackgroundColor:符号栏使用默认背景色作为其背景色。
  • SymbolMarginColor:符号栏使用setMarginBackgroundColor()设置的颜色作为其背景色。
  • TextMargin:文本栏。⽂本栏用于显示文本。字体大小、族、颜色都是可选的。
  • TextMarginRightJustified:边界栏包含右对齐的样式文本。

1.2 边界宽度

如下函数用来设置指定边界的宽度(以像素为单位):

virtual void QsciScintilla::setMarginWidth(int margin, int width);

如下函数根据字符串字体样式计算所需的边界宽度:

virtual void QsciScintilla::setMarginWidth(int margin, const QString& s);

注意,没有函数用于指定边界的隐藏或者显示。非零值表示边界可见

1.3 边界颜色

边界栏的前景色通过如下函数修改:

virtual void QsciScintilla::setMarginsForegroundColor(const QColor& col)

注意,没有参数用于指定特定的边距,因此该函数会影响所有边距

而边界栏的背景色可通过如下函数修改:

virtual void QsciScintilla::setMarginsBackgroundColor(const QColor& col)

1.4 边界类型

1.4.1 行号区

如下语句可以将第n个边界栏设置为行号区:

editor->setMarginType(n, QsciScintilla::NumberMargin);

image

可以通过以下语句来设置边界文本(包括行号)的颜色:

editor->setMarginsForegroundColor(Qt::red);

1.4.2 符号区

如下四个函数可以将第n个边界设置为符号区:

editor->setMarginType(n, QsciScintilla::SymbolMargin);
editor->setMarginType(n, QsciScintilla::SymbolMarginDefaultBackgroundColor);
editor->setMarginType(n, QsciScintilla::SymbolMarginDefaultForegroundColor));
editor->setMarginType(n, QsciScintilla::SymbolMarginColor);

它们之间的区别与背景颜色有关:

  • 第⼀个函数在默认的灰色背景上显示符号区。可以更改该颜色,但是,对函数setMarginsBackgroundColor(const QColor& col)的调用会更改所有边界背景色(包括此颜色)。
  • 第⼆个函数在默认背景色上显示符号区。因此,此边界不会受到对setMarginsBackgroundColor(const QColor& col)的调用的影响。
  • 第三个函数在默认的前景色上显示符号区。因此,此边界不会收到对setMarginsBackgroundColor(const QColor& col)的调用的影响。
  • 第四个函数提供了最大的灵活性,但是仅在QScintilla 2.10中可用。无论是否进行其他设置,都可以选择此符号区的背景色。必须使用函数setMarginBackgroundColor(int margin, const QColor &col)

(1)创建符号样式

创建符号时,符号的样式可以指定以下3个可选项:

  • 内置符号(QScintilla的内置符号)
    • QsciScintilla::Circle
    • QsciScintilla::Rectangle
    • QsciScintilla::RightTriangle
    • QsciScintilla::SmallRectangle
    • 更多可见MarkerSymbol枚举变量
  • QPixmap:任何QPixmap对象都可以用作符号。
  • QImage:任何QImage对象都可以用作符号。

例如,定义如下5种符号:

QImage sym_0 = QImage("green_dot.png").scaled(QSize(16,16));
QImage sym_1 = QImage("green_arrow.png").scaled(QSize(16,16));
QImage sym_2 = QImage("red_dot.png").scaled(QSize(16,16));
QImage sym_3 = QImage("red_arrow.png").scaled(QSize(16,16));
QsciScintilla::MarkerSymbol sym_4 = QsciScintilla::Circle;

(2)绑定符号标记

每个符号都可以绑定一个标记,通过标记来使用不同的符号。使用如下重载函数可以来分配标记:

int QsciScintilla::markerDefine(MarkerSymbol sym, int markerNumber = -1);
int QsciScintilla::markerDefine(char ch, int markerNumber = -1);
int QsciScintilla::markerDefine(const QPixmap& pm, int markerNumber = -1);
int QsciScintilla::markerDefine(const QImage& im,int markerNumber = -1);

例如:

editor->markerDefine(sym_0, 0);
editor->markerDefine(sym_1, 1);
editor->markerDefine(sym_2, 2);
editor->markerDefine(sym_3, 3);
editor->markerDefine(sym_4, 4);

(3)显示符号

要在特定行的符号区中显示符号,必须要使用该函数将符号添加到该行:

int QsciScintilla::markerAdd(int linenr, int markerNumber);

注意:该函数没有参数用以指定特定的边界栏,它实际上尝试将符号放在左侧的每个符号区中,如图:

image

但是,QScintilla提供了⼀种从边界栏中过滤掉一些符号的机制:

virtual void QsciScintilla::setMarginMarkerMask(int margin, int mask);

其中,参数mask是通过二进制掩码的形式来指定该边界栏显示哪个符号(1表示显示),不显示哪个符号(0表示不显示)。标记掩码最多可以有32个。这是因为它实际上是⼀个32位宽的二进制数。因此,这也是编辑器中可定义的最大符号数量。

例如:

// 只显示标记为0、2、4的符号,即红点、绿点、黑圈
// 10101 - 显示 不显示 显示 不显示 显示
editor->setMarginMarkerMask(1, 0b10101);
// 只显示标记为1、3的符号,即红箭头、绿箭头
// 01010 - 不显示 显示 不显示 显示 不显示
editor->setMarginMarkerMask(3, 0b01010);

image

(4)符号句柄

假设现在已经将数百万个标符号记添加到代码栏,但是如何追踪它们?QScintilla提供了⼀个简便的机制。每个标记在添加到代码栏时,都会获得⼀个唯⼀的ID号:⼀个handler。如果将相同的绿色标记添加到2个不同的代码行,则每个代码行都会获得一个不同的ID号,因此,ID号表示特定代码行上的特定标记

符号的句柄可以在向特定行添加符号时通过函数返回值获得,如下所示:

// 将标记号markerNumber的实例添加到行号linenr。返回标记的句柄,可用于跟踪标记的位置,如果markerNumber无效,则返回-1。
int QsciScintilla::markerAdd(int linenr, int markerNumber);

(5)其他功能

如下,是一些其他常用的函数接口:

// 删除linenr行中带有标记号markerNumber的所有标记。如果markerNumber为-1,则从linenr中删除所有标记。
void QsciScintilla::markerDelete(int linenr, int markerNumber = -1);
// 使用标记句柄mhandle删除标记实例。
void QsciScintilla::markerDeleteHandle(int mhandle);
// 删除标记号为markerNumber的所有标记。如果markerNumber为 -1,则删除所有标记。
void QsciScintilla::markerDeleteAll(int markerNumber = -1);
// 返回行号linenr处标记的32位掩码。
unsigned QsciScintilla::markersAtLine(int linenr) const;
// 返回包含带有标记句柄mhandle的标记实例的行号。
int QsciScintilla::markerLine(int mhandle) const;
// 返回下一行的编号以包含来自32位标记掩码的至少一个标记。linenr是开始搜索的行号。mask是要搜索的标记的掩码。
int QsciScintilla::markerFindNext(int linenr, unsigned mask)const;
// 返回前一行的编号以包含至少一个来自32位标记掩码的标记。linenr是开始搜索的行号。mask是要搜索的标记的掩码。
int QsciScintilla::markerFindPrevious(int linenr, unsigned mask )const;

1.4.3 折叠区

折叠区用于显示折叠代码和展开代码的折叠符号,折叠区实际上是符号栏中的一种。

image

可以通过如下函数设置折叠栏样式。其默认样式为NoFoldStyle(即禁用折叠),默认边界区为2。

virtual void QsciScintilla::setFolding(FoldStyle fold, int margin = 2);

其中,FoldStyle是一个枚举变量,其值如下:

  • NoFoldStyle:禁用折叠区。
  • PlainFoldStyle:使用加号和减号的普通折叠样式。
  • CircledFoldStyle:使用带圆圈的加号和减号符号的带圆圈的折叠样式。
  • BoxedFoldStyle:使用盒形加号和减号的盒形折叠样式。
  • CircledTreeFoldStyle:使用带有圆形加号和减号以及圆角的扁平树样式。
  • BoxedTreeFoldStyle:使用带有盒形加号和减号以及直角的扁平树样式。

二、边界栏示例

定义如下枚举变量以及符号掩码:

// 符号样式
enum SymbolHandler
{
    POINT = 0,	// 断点
    LABEL,	// 标签
    ARROW	// 箭头
};

// 符号掩码
const int S_BREAK = 0b001;	// 显示断点
const int S_LABEL = 0b010;	// 显示标签
const int S_ARROW = 0b100;	// 显示箭头

mainwindow.cpp中定义如下代码:

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    editor = new QsciScintilla(this);
    setCentralWidget(editor);

    // 编辑器设置
    // ----------------------------------------
    // 设置Tab键为4个空格
    editor->setTabWidth(4);
    // 开启自动缩进
    editor->setAutoIndent(true);
    // 高亮当前行
    editor->setCaretLineVisible(true);
    // 设置当前行颜色
    editor->setCaretLineBackgroundColor(QColor("#E8E8FF"));
    // 编辑器放大
    editor->zoomIn(8);

    // 边界栏设置
    // ----------------------------------------
    // 设置行号区和行号区宽度
    editor->setMarginType(0, QsciScintilla::NumberMargin);
    editor->setMarginWidth(0, 30);
    // 设置符号区和符号区宽度
    editor->setMarginType(1, QsciScintilla::SymbolMargin);
    editor->setMarginWidth(1, 20);
    // 设置符号样式
    QImage breakpoint = QImage(":/breakpoint.png").scaled(QSize(25, 25));
    QImage label = QImage(":/label.png").scaled(QSize(25, 25));
    QImage arrow = QImage(":/arrow.png").scaled(QSize(25, 25));
    // 为每个符号赋予编号
    editor->markerDefine(breakpoint, SymbolHandler::POINT);
    editor->markerDefine(label, SymbolHandler::LABEL);
    editor->markerDefine(arrow, SymbolHandler::ARROW);
    // 设置符号区掩码 全部都可以显示
    editor->setMarginMarkerMask(1, S_BREAK | S_LABEL | S_ARROW);
    // 添加符号
    editor->markerAdd(0, SymbolHandler::ARROW);

运行结果如图:

三、鼠标点击

要想使鼠标点击边界区时显示符号,可以使用Qt的信号和槽机制使得边界区可以在鼠标点击时做出相应地反应。

3.1 设置符号区

首先,需要启用符号区的鼠标点击响应功能:

// 如果用户在开启点击能力的边界栏中单击,则会发出marginClicked()信号。
virtual void QsciScintilla::setMarginSensitivity(int margin, bool sens);

3.2 连接信号和槽

必须通过如下方式连接信号和槽:

connect(editor, SIGNAL(marginClicked(int, int, Qt::KeyboardModifiers)), this, SLOT(function(int, int, Qt::KeyboardModifiers)));

注意,不要使用如下方式连接信号和槽

connect(editor, &QsciScintilla::marginClicked, this, &QWidget::function);

3.3 实现槽函数

槽函数定义如下:

void function_name(int margin ,int line ,Qt::KeyboardModifiers state);

其中,各参数的解释如下:

  • margin:所单击的符号区索引,从0开始算
  • line:所单击的行号,从0开始算(注意,行号的显示是从1开始的)
  • state:此参数用于指示用户单击时是否发生了特殊情况,即是否在单击时按下了特殊按键。
    • Qt::ControlModifier:Ctrl键
    • Qt::ShiftModifier:Shift键
    • Qt::AltModifier:Alt键
    • Qt::MetaModifier:编辑器Emacs中用到的键位,国人键盘实际上并没有此键位

四、鼠标点击示例

mainwindow.h代码如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class QsciScintilla;

enum SymbolHandler
{
    POINT = 0,
    LABEL,
    ARROW
};

const int S_BREAK = 0b001;
const int S_LABEL = 0b010;
const int S_ARROW = 0b100;

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow 
{
    Q_OBJECT
public:
    typedef unsigned int mode_t;
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void updateSymbol(int, int, Qt::KeyboardModifiers);

private:
    inline bool isBreak(mode_t mode) { return ((mode & S_BREAK) == S_BREAK); }
    inline bool isLabel(mode_t mode) { return ((mode & S_LABEL) == S_LABEL); }
    inline bool isArrow(mode_t mode) { return ((mode & S_ARROW) == S_ARROW); }

private:
    Ui::MainWindow *ui;
    QsciScintilla* editor;
};
#endif // MAINWINDOW_H

mainwindow.cpp代码如下:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <Qsci/qsciscintilla.h>

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    editor = new QsciScintilla(this);
    setCentralWidget(editor);

    // 编辑器设置
    // ----------------------------------------
    // 设置Tab键为4个空格
    editor->setTabWidth(4);
    // 开启自动缩进
    editor->setAutoIndent(true);
    // 高亮当前行
    editor->setCaretLineVisible(true);
    // 设置当前行颜色
    editor->setCaretLineBackgroundColor(QColor("#E8E8FF"));
    // 编辑器放大
    editor->zoomIn(8);

    // 边界栏设置
    // ----------------------------------------
    // 设置行号区和行号区宽度
    editor->setMarginType(0, QsciScintilla::NumberMargin);
    editor->setMarginWidth(0, 30);
    // 设置符号区和符号区宽度 开启符号区可点击功能
    editor->setMarginType(1, QsciScintilla::SymbolMargin);
    editor->setMarginWidth(1, 20);
    editor->setMarginSensitivity(1, true);
    // 设置符号样式
    QImage breakpoint = QImage(":/breakpoint.png").scaled(QSize(25, 25));
    QImage label = QImage(":/label.png").scaled(QSize(25, 25));
    QImage arrow = QImage(":/arrow.png").scaled(QSize(25, 25));
    // 为每个符号赋予编号
    editor->markerDefine(breakpoint, SymbolHandler::POINT);
    editor->markerDefine(label, SymbolHandler::LABEL);
    editor->markerDefine(arrow, SymbolHandler::ARROW);
    // 设置符号区掩码 全部都可以显示
    editor->setMarginMarkerMask(1, S_BREAK | S_LABEL | S_ARROW);
    // 连接信号和槽
    connect(editor, SIGNAL(marginClicked(int, int, Qt::KeyboardModifiers)),
        this, SLOT(updateSymbol(int, int, Qt::KeyboardModifiers)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::updateSymbol(int margin, int line, Qt::KeyboardModifiers state)
{
    mode_t mask = editor->markersAtLine(line);
    switch (state) {
    case Qt::ControlModifier: // 按下Ctrl键
        if (isArrow(mask))
            editor->markerDelete(line, SymbolHandler::ARROW);
        else
            editor->markerAdd(line, SymbolHandler::ARROW); // 添加箭头
        break;
    case Qt::AltModifier: // 按下Alt键
        if (isLabel(mask))
            editor->markerDelete(line, SymbolHandler::LABEL);
        else
            editor->markerAdd(line, SymbolHandler::LABEL); // 添加标签
        break;
    default:
        if (isBreak(mask))
            editor->markerDelete(line, SymbolHandler::POINT);
        else
            editor->markerAdd(line, SymbolHandler::POINT); // 添加断点
    }
}

运行结果如下图所示:


其余该系列文章如下:

  1. QScintilla入门指南之 基本介绍
  2. QScintilla入门指南之 编辑器设置
  3. QScintilla入门指南之 词法分析器(正在更新...)
  4. QScintilla入门指南之 可点击文本(正在更新...)
  5. QScintilla入门指南之 自动补全(正在更新...)
posted @ 2022-04-24 15:17  Leaos  阅读(4269)  评论(3编辑  收藏  举报