Qt 字符串(21):Text Codecs Example【官翻】

Text Codecs Example

如何使用文本编解码器导入和导出文本。

文本编解码器示例演示了使用编解码器导入和导出文本以确保字符被正确编码、避免数据丢失并保留各种脚本中使用的正确符号背后的原则。

2

main.cpp

#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow mainWin;
    mainWin.show();
    return app.exec();
}

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtWidgets>

class EncodingDialog;
class PreviewForm;

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow();
private slots:
    void open();
    void save();
    void about();
    void aboutToShowSaveAsMenu();
    void encodingDialog();

private:
    void findCodecs();
    void createMenus();

    QVector<QAction *> saveAsActs;
    QPlainTextEdit *textEdit;
    PreviewForm *previewForm;
    QVector<QTextCodec *> codecs;
    EncodingDialog *m_encodingDialog = nullptr;
};
#endif

MainWindow.cpp

#include "mainwindow.h"
#include "encodingdialog.h"
#include "previewform.h"
/* ****************************************************************************
 * 构造函数
 **************************************************************************** */
MainWindow::MainWindow()
{
    //构建QPlainTextEdit
    textEdit = new QPlainTextEdit;
    // 设置换行模式
    textEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
    // 设置中央控件
    setCentralWidget(textEdit);
    // 获取可用的codec,并存放在全局变量 QVector<QTextCodec *> codecs;
    findCodecs();

    // 预览对话框
    previewForm = new PreviewForm(this);
    // 传递 codecs 给 预览对话框
    previewForm->setCodecList(codecs);

    //  创建菜单
    createMenus();
    // 设置窗口标题
    setWindowTitle(tr("Codecs"));

    // 获取屏幕大小
    const QRect screenGeometry = screen()->geometry();
    // 利用屏幕的大小,调整窗口大小 1/2*w, 2/3*h
    resize(screenGeometry.width() / 2, screenGeometry.height() * 2 / 3);
}

/* ****************************************************************************
 * 文件对话框打开操作
 **************************************************************************** */
void MainWindow::open()
{
    // 利用文件对话框获取文件名  ,样式:"E:/Project/QtProject/QtWidgets/codecs/encodingdialog.h"
    const QString fileName = QFileDialog::getOpenFileName(this);
    // 文件名为空,退出
    if (fileName.isEmpty())
        return;
     // 用文件名构造QFile
    QFile file(fileName);
    // 打开文件失败,告警,退出
    if (!file.open(QFile::ReadOnly)) {

        QMessageBox::warning(this, tr("Codecs"),
                             tr("Cannot read file %1:\n%2").arg(QDir::toNativeSeparators(fileName),
                            file.errorString()));

        // QDir::toNativeSeparators(fileName) 用本地路径隔离符
        // 样式:"E:\\Project\\QtProject\\QtWidgets\\codecs\\codecs.qrc"
        return;
    }

    // 用字节数组读取文件
    const QByteArray data = file.readAll();
    // 预览对话框设置标题
    previewForm->setWindowTitle(tr("Choose Encoding for %1").arg(QFileInfo(fileName).fileName()));
    // 预览对换框设置已经编码的数据
    previewForm->setEncodedData(data);

    // 预览对话框模态运行,确认返回
    if (previewForm->exec())
        // 确认返回后,文本编辑器设置预览对话框解码的数据
        textEdit->setPlainText(previewForm->decodedString());
}

/* ****************************************************************************
 * 文件保存
 **************************************************************************** */
void MainWindow::save()
{
    // 获取发送信号的动作对象
    const QAction *action = qobject_cast<const QAction *>(sender());
    // 获取动作的数据内容
    const QByteArray codecName = action->data().toByteArray();
    // 通过数据内容构造字符串
    const QString title = tr("Save As (%1)").arg(QLatin1String(codecName));
    // 文件对话框的保存操作
    QString fileName = QFileDialog::getSaveFileName(this, title);
    if (fileName.isEmpty())
        return;
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text)) {
        QMessageBox::warning(this, tr("Codecs"),
                             tr("Cannot write file %1:\n%2")
                             .arg(QDir::toNativeSeparators(fileName),
                                  file.errorString()));
        return;
    }
    // 利用文件构造文本流
    QTextStream out(&file);
    // 文本流设置编解码器   void setCodec(const char *codecName)
    out.setCodec(codecName.constData());
    out << textEdit->toPlainText();
}

/* ****************************************************************************
 * 自定义帮助菜单
 **************************************************************************** */
void MainWindow::about()
{
    QMessageBox::about(this, tr("About Codecs"),
            tr("The <b>Codecs</b> example demonstrates how to read and write "
               "files using various encodings."));
}


/* ****************************************************************************
 * 在菜单完全出现前,分析菜单里的每一个动作代表的编解码器是否能够解析文本内容
 * 如果编解码器能够分析文本内容,则对应的动作设置为可见
 ****************************************************************************** */
void MainWindow::aboutToShowSaveAsMenu()
{
    // 获取文本编辑器的纯文本
    const QString currentText = textEdit->toPlainText();
    // 迭代动作容器
    for (QAction *action : qAsConst(saveAsActs)) {
        //通过动作的data数据,取得编解码器
        const QByteArray codecName = action->data().toByteArray();
        //获取编解码器
        const QTextCodec *codec = QTextCodec::codecForName(codecName);
        // 动作设置可视
        action->setVisible(codec && codec->canEncode(currentText));
    }
}


/* ****************************************************************************
 * 获取能用的编解码器,并使用codec的名称进行自定义排序
 ****************************************************************************** */
void MainWindow::findCodecs()
{
    // 字符串:QTextCodec * 的QMap
    QMap<QString, QTextCodec *> codecMap;
    //ISO-8859-11.... 形式的正则表达式,捕获具体的字符集数11
    QRegularExpression iso8859RegExp("^ISO[- ]8859-([0-9]+).*$");
    QRegularExpressionMatch match;

    // 获取所有可用编解码器的mib列表。MIBEnum值是MIBs中用于标识编码字符集的唯一值
    const QList<int> mibs = QTextCodec::availableMibs();
    for (int mib : mibs) {
        //  通过MIBEnum值获取编解码器
        QTextCodec *codec = QTextCodec::codecForMib(mib);
        //  获取编解码器的名称,并转大写
        QString sortKey = codec->name().toUpper();
        char rank;

        // UTF-8开头
        if (sortKey.startsWith(QLatin1String("UTF-8"))) {
            rank = 1;
        }
        //  UTF-16开头
        else if (sortKey.startsWith(QLatin1String("UTF-16"))) {
            rank = 2;
        }
        //  iso8859RegExp有匹配
        else if ((match = iso8859RegExp.match(sortKey)).hasMatch()) {
            // 提取捕获的字符集数字是1个数字   1~9
            if (match.capturedRef(1).size() == 1)
                rank = 3;
            // 提取捕获的字符集数字不是1个数字  10~15
            else
                rank = 4;
        }
        //  不是utf 和iso8859开头的编解码器
        else {
            rank = 5;
        }
        //  在codec的名称QString前添加字符 1~5 ,作为map的键值进行排序
        sortKey.prepend(QLatin1Char('0' + rank));
        // 将键值对添加到map中
        codecMap.insert(sortKey, codec);
    }
    // qAsConst在Qt中的主要用途是防止隐式共享的Qt容器分离,就是变成右值
    for (const auto &codec : qAsConst(codecMap))
        // 将编解码器放入  QVector<QTextCodec *>  容器
      codecs += codec;
}
/* ****************************************************************************
 * 创建菜单
 ****************************************************************************** */
void MainWindow::createMenus()
{
    // 在菜单栏添加菜单File
    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));

    // 添加 openAct 动作,并关联到open槽函数,使用QKeySequence::Open设置快捷键
    QAction *openAct = fileMenu->addAction(tr("&Open..."), this, &MainWindow::open);
    openAct->setShortcuts(QKeySequence::Open);

     // 添加 saveAsMenu 子菜单
    QMenu *saveAsMenu = fileMenu->addMenu(tr("&Save As"));

    //  链接子菜单的 aboutToShow 信号  到  aboutToShow槽函数
    //  aboutToShow信号恰好在菜单显示给用户之前发出。
//    connect(saveAsMenu, &QMenu::aboutToShow,this, &MainWindow::aboutToShowSaveAsMenu);

    // 迭代codec的向量容器
    for (const QTextCodec *codec : qAsConst(codecs)) {
        // 获取迭代编解码器的名称
        const QByteArray name = codec->name();
        // 获取编解码器的名称,添加动作
        QAction *action = saveAsMenu->addAction(tr("%1...").arg(QLatin1String(name)));
        // 每个动作设置数据,名称数据
        action->setData(QVariant(name));
        // 动作的触发的信号 连接到 save()
        connect(action, &QAction::triggered, this, &MainWindow::save);
        // 将动作添加到 QVector<QAction *> saveAsActs 容器中
        saveAsActs.append(action);
    }
    // file菜单添加分割线
    fileMenu->addSeparator();
    // 添加exit动作,关联到close()槽函数
    QAction *exitAct = fileMenu->addAction(tr("E&xit"), this, &QWidget::close);
    // 设置Shortcuts快捷键序列
    exitAct->setShortcuts(QKeySequence::Quit);

    // 添加 Tool 菜单
    auto toolMenu =  menuBar()->addMenu(tr("&Tools"));
    // 添加encodingAction 动作,连接到encodingDialog槽函数
    auto encodingAction = toolMenu->addAction(tr("Encodings"), this, &MainWindow::encodingDialog);
    encodingAction->setShortcut(Qt::CTRL + Qt::Key_E);
    // 设置工具提示
    encodingAction->setToolTip(tr("Shows a dialog allowing to convert to common encoding in programming languages."));

    // 菜单栏添加分割线
    menuBar()->addSeparator();

    // 添加 helpMenu 菜单
    QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
    // 添加 About动作,连接到about()
    helpMenu->addAction(tr("&About"), this, &MainWindow::about);
    // 添加 About Qt动作,连接到QApplication::aboutQt()
    helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
}
/* ****************************************************************************
 * 显示encodingDialog,和菜单encodingAction关联
 ****************************************************************************** */
void MainWindow::encodingDialog()
{
    // m_encodingDialog为空,就先构造一个EncodingDialog
    if (!m_encodingDialog) {
        m_encodingDialog = new EncodingDialog(this);
        const QRect screenGeometry = screen()->geometry();
        m_encodingDialog->setMinimumWidth(screenGeometry.width() / 4);
    }
    // 显示对话框
    m_encodingDialog->show();
    //     对话框前置,在视觉上位于任何重叠的同级小部件的前面。
    m_encodingDialog->raise();

}

PreviewForm.h

#ifndef PREVIEWFORM_H
#define PREVIEWFORM_H

#include <QtWidgets>

class PreviewForm : public QDialog
{
    Q_OBJECT

public:
    explicit PreviewForm(QWidget *parent = nullptr);
    // 设置编解码器的列表
    void setCodecList(const QVector<QTextCodec *> &list);
    // 设置编码的数据
    void setEncodedData(const QByteArray &data);
    // 返回解码的字符串
    QString decodedString() const { return decodedStr; }

private slots:
    // 更新文本编辑器
    void updateTextEdit();

private:
    void reset();
    // 用 encodedData 接受存储的数据
    QByteArray encodedData;
    // 用 decodedStr 接受解码的数据
    QString decodedStr;
    // 确认按钮,需要更改使能状态
    QPushButton *okButton;
    // 选项卡控件
    QTabWidget *tabWidget;
    // 编码器名称的组合框
    QComboBox *encodingComboBox;
    // 显示解码后的文本控件
    QPlainTextEdit *textEdit;
    // 显示解码后的16进制格式的文本控件
    QPlainTextEdit *hexDumpEdit;
    // 显示解码的状态标签
    QLabel *statusLabel;
};

#endif

PreviewForm.cpp

#include "previewform.h"

// 创建十六进制转储的助手

/* ****************************************************************************
 * 添加相应的空白字符,针对最后一行数据的第二、三部分的内容
 ****************************************************************************** */
static void indent(QTextStream &str, int indent)
{
    for (int i = 0; i < indent; ++i)
        str << ' ';
}


/* ****************************************************************************
 * 将字节数组数据line转成16进制字符串
 ****************************************************************************** */
static void formatHex(QTextStream &str, const QByteArray &data)
{
    // 备份域宽
    const int fieldWidth = str.fieldWidth();
    // 备份域对齐方式
    const QTextStream::FieldAlignment alignment = str.fieldAlignment();
    // 备份流对象的进制基数
    const int base = str.integerBase();
    // 备份填充字符
    const QChar padChar = str.padChar();
    // 设置16进制的流
    str.setIntegerBase(16);
    // 设置填充字符
    str.setPadChar(QLatin1Char('0'));
    // 设置对齐方式
    str.setFieldAlignment(QTextStream::AlignRight);
    // 获取字节数组的头指针 使用reinterpret_cast转换,重新解释为const unsigned char *类型
    const unsigned char *p = reinterpret_cast<const unsigned char *>(data.constBegin());
    // 迭代字节数组的每一个字节
    for (const unsigned char *end = p + data.size(); p < end; ++p) {
        // 插入1个空格
        str << ' ';
        // 设置2个字符的域宽
        str.setFieldWidth(2);
        // 添加字节内容,使用进制自动转换
        str << unsigned(*p);

        // 恢复域宽
        str.setFieldWidth(fieldWidth);
    }
    // 恢复文本流的设置
    str.setFieldAlignment(alignment);
    str.setPadChar(padChar);
    str.setIntegerBase(base);
}

/* ****************************************************************************
 * 将字节数组数据line 按字节打印可见字符,包括转义
 ****************************************************************************** */
static void formatPrintableCharacters(QTextStream &str, const QByteArray &data)
{
    for (const char c : data) {
        switch (c) {
        case '\0':
            str << "\\0";
            break;
        case '\t':
            str << "\\t";
            break;
        case '\r':
            str << "\\r";
            break;
        case '\n':
            str << "\\n";
            break;
        default:
            if (c >= 32 && uchar(c) < 127) //可见字符,
                str << ' ' << c; // 保持2位宽度
            else    // 不可见字符
                str << "..";
            break;
        }
    }
}
/* ****************************************************************************
 * 将字节数据转成含有固定格式的16进制字符串
 * 每行的样式:
 *  首地址   16个字节的内容                                     拉丁字母显示
 *  00008010 4b 45 46 49 4c 45 29 2e 52 65 6c 65 61 73 65 3a  K E F I L E ) . R e l e a s e :
 *  00008020 20 4d 61 6b 65 66 69 6c 65 0d 0a                 M a k e f i l e\r\n
 ****************************************************************************** */
static QString formatHexDump(const QByteArray &data)
{
    // 一行显示16个字节
    enum { lineWidth = 16 };
    // 使用字符串作为文本流对象
    QString result;
    QTextStream str(&result);
    // 设置16进制基数
    str.setIntegerBase(16);
    // 填充字符为 0
    str.setPadChar(QLatin1Char('0'));
    // 备份域宽
    const int fieldWidth = str.fieldWidth();
    // 备份域对齐方式
    const QTextStream::FieldAlignment alignment = str.fieldAlignment();

    //    每行的样式:
    //    00008010 4b 45 46 49 4c 45 29 2e 52 65 6c 65 61 73 65 3a  K E F I L E ) . R e l e a s e :
    //    00008020 20 4d 61 6b 65 66 69 6c 65 0d 0a                 M a k e f i l e\r\n

    // 构造字符串内容
    for (int a = 0, size = data.size(); a < size; a += lineWidth) {
        // 第一部分的首地址内容:00000000
        str.setFieldAlignment(QTextStream::AlignRight); // 对齐方式
        str.setFieldWidth(8);   // 域宽
        str << a;               // 16进制模式,a的数值,0填充
        str.setFieldWidth(fieldWidth);  // 复位域宽
        str.setFieldAlignment(alignment);   // 复位对齐方式

        // 第二部分 20 4d 61 6b 65 66 69 6c 65 0d 0a                 的内容
        // 获取结束位置,如果没有超过数据的字节数组的长度
        const int end = qMin(a + lineWidth, size);
        // 获取字节数组中间的内容,位置由每行首地址 和 每行长度确定
        const QByteArray line = data.mid(a, end - a);
        // 设置第二部分的内容, line的内容转成16进制
        formatHex(str, line);

        // 第二部分内容,如果最后一行第二部分数据不满,插入空白字符
        indent(str, 3 * (lineWidth - line.size()));

        // 第三部分  打印可见字符及转义字符
        str << ' '; // 插入1个空白字符,和第二部分分开
        // 将字节数组数据line 按字节打印成可见字符,添加到str文本流中
        formatPrintableCharacters(str, line);
        // 第三部分内容,如果最后一行第二部分数据不满,插入空白字符
        indent(str, 2 * (lineWidth - line.size()));
        // 一行数据结束,插入换行
        str << '\n';
    }
    return result;
}
/* ****************************************************************************
 * 预览表单的构造函数
 ****************************************************************************** */
PreviewForm::PreviewForm(QWidget *parent)
    : QDialog(parent)
{
    // 取消对话框默认的帮助按钮
    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
    // 创建组合框,用来显示编码器的名称
    encodingComboBox = new QComboBox;
    // 创建一个标签,作为组合框的伙伴
    QLabel *encodingLabel = new QLabel(tr("&Encoding:"));
    encodingLabel->setBuddy(encodingComboBox);
    // 创建纯文本编辑器,显示预览的文本
    textEdit = new QPlainTextEdit;
    // 设置换行模式
    textEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
    // 设置只读模式
    textEdit->setReadOnly(true);
    // 创建纯文本编辑器,16进制显示内容
    hexDumpEdit = new QPlainTextEdit;
    hexDumpEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
    hexDumpEdit->setReadOnly(true);
    // 设置字体
    hexDumpEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
    // 创建对话框按钮组
    QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
    okButton = buttonBox->button(QDialogButtonBox::Ok);
    // 链接组合框的activated信号,与updateTextEdit()
    connect(encodingComboBox, QOverload<int>::of(&QComboBox::activated),
            this, &PreviewForm::updateTextEdit);
    // 链接对话框按钮组的accepted信号到QDialog::accept()
    connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
     // 链接对话框按钮组的 rejected 信号到QDialog::reject()
    connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);

    // 主布局管理器 使用 QGridLayout
    QGridLayout *mainLayout = new QGridLayout(this);
    mainLayout->addWidget(encodingLabel, 0, 0);
    mainLayout->addWidget(encodingComboBox, 0, 1);
    // 创建全局 QTabWidget 控件
    tabWidget = new QTabWidget;
    // 添加选项卡
    tabWidget->addTab(textEdit, tr("Preview"));
    tabWidget->addTab(hexDumpEdit, tr("Hex Dump"));
    // 布局添加选项卡控件
    mainLayout->addWidget(tabWidget, 1, 0, 1, 2);
    // 构造全局状态标签
    statusLabel = new QLabel;
    mainLayout->addWidget(statusLabel, 2, 0, 1, 2);
    mainLayout->addWidget(buttonBox, 3, 0, 1, 2);

    const QRect screenGeometry = screen()->geometry();
    resize(screenGeometry.width() * 2 / 5, screenGeometry.height() / 2);
}

/* ****************************************************************************
 * 使用参数设置组合框的列表项
 ****************************************************************************** */
void PreviewForm::setCodecList(const QVector<QTextCodec *> &list)
{
    // 清空清除组合框,移除所有项目。
    encodingComboBox->clear();
    // 使用codec的名称,逐项添加QTextCodec *,添加MIB数据
    for (const QTextCodec *codec : list) {
        encodingComboBox->addItem(QLatin1String(codec->name()),
                                  QVariant(codec->mibEnum()));
    }
}

/* ****************************************************************************
 * 预览表单对话框 复位
 ****************************************************************************** */
void PreviewForm::reset()
{
    // 已解码的内容清空
    decodedStr.clear();
    // 文本内容清空
    textEdit->clear();
    // 16进制文本内容清空
    hexDumpEdit->clear();
    // 状态标签内容清空
    statusLabel->clear();
    // 状态标签格式清空
    statusLabel->setStyleSheet(QString());
    // 确定按钮 不可用
    okButton->setEnabled(false);
    // 选项卡控件 显示第0个选项页
    tabWidget->setCurrentIndex(0);
}
/* ****************************************************************************
 * 设置编码的数据
 ****************************************************************************** */
void PreviewForm::setEncodedData(const QByteArray &data)
{
    // 复位
    reset();
    // 用参数为全局 encodedData 赋值
    encodedData = data;
    // 将字节数据转成含有固定格式的16进制字符串
    hexDumpEdit->setPlainText(formatHexDump(data));
    // 更新文本编辑器
    updateTextEdit();
}
/* ****************************************************************************
 * 更新文本编辑器
 ****************************************************************************** */
void PreviewForm::updateTextEdit()
{
    // 从组合框获取mib值
    int mib = encodingComboBox->itemData(encodingComboBox->currentIndex()).toInt();
    // 从mib值获取编解码器
    const QTextCodec *codec = QTextCodec::codecForMib(mib);
    // 从编解码器获取名称
    const QString name = QLatin1String(codec->name());

    // 将输入中的前size大小字符从该编解码器的编码转换为Unicode,并以QString的形式返回结果。使用的转换器的状态state被更新。
    QTextCodec::ConverterState state;
    decodedStr = codec->toUnicode(encodedData.constData(), encodedData.size(), &state);

    bool success = true;
    // 编解码器的转态中有剩余字符
    if (state.remainingChars) {
        // 未成功解码
        success = false;
        // 构造信息 已经转换的字节数量
        const QString message = tr("%1: conversion error at character %2")
            .arg(name).arg(encodedData.size() - state.remainingChars + 1);
        // 状态标签更新信息
        statusLabel->setText(message);
        // 状态标签用红色显示
        statusLabel->setStyleSheet(QStringLiteral("background-color: \"red\";"));
    }
    // 编解码器的转态有非法的字符
    else if (state.invalidChars) {
        // 状态标签设置文本:n is used in conjunction with %n to support plural forms.
        statusLabel->setText(tr("%1: %n invalid characters", nullptr, state.invalidChars).arg(name));
        // 状态标签用黄色显示
        statusLabel->setStyleSheet(QStringLiteral("background-color: \"yellow\";"));
    }
    // 其他都是正常情况
    else {
        statusLabel->setText(tr("%1: %n bytes converted", nullptr, encodedData.size()).arg(name));
        statusLabel->setStyleSheet(QString());
    }
    // 成功解码
    if (success)
        // 文本编辑器设置文本
        textEdit->setPlainText(decodedStr);
    else  // 解码未成功
        // 文本编辑器清空
        textEdit->clear();
    // 确定按钮功能设置使用
    okButton->setEnabled(success);
}

EncodingDialog.h

#ifndef ENCODINGDIALOG_H
#define ENCODINGDIALOG_H

#include <QDialog>
// 前置声明
QT_FORWARD_DECLARE_CLASS(QLineEdit)

class EncodingDialog : public QDialog
{
    Q_OBJECT
public:
    explicit EncodingDialog(QWidget *parent = nullptr);
    // 编码器的枚举
    enum Encoding { Unicode, Utf8, Utf16, Utf32, Latin1, EncodingCount };

private slots:
    void textChanged(const QString &t);

private:
    // 行文本的指针数组 EncodingCount的值,正好是枚举类型的长度
    QLineEdit *m_lineEdits[EncodingCount];
};

#endif // ENCODINGDIALOG_H

EncodingDialog.cpp

#include "encodingdialog.h"
#include <QtWidgets>

#include <QTextStream>
// 格式化字符序列的助手
/* ****************************************************************************
 * 用 formatEscapedNumber 方法,格式化转义字符 例如'\x0a'
 * str:文本流
 * i1,i2:迭代器,头尾指针
 * base: 进制的基数
 * Width:宽度
 * prefix:前缀字符
 * **************************************************************************** */
template <class Int>
static void formatEscapedNumber(QTextStream &str, Int value, int base,
                                int width = 0,char prefix = 0)
{
    str << '\\';
    if (prefix)
        str << prefix;
    // 备份文件流的格式
    const auto oldPadChar = str.padChar();
    const auto oldFieldWidth = str.fieldWidth();
    const auto oldFieldAlignment = str.fieldAlignment();
    const auto oldIntegerBase = str.integerBase();
    // 设置文本流的格式
    str.setPadChar(QLatin1Char('0'));
    str.setFieldWidth(width);
    str.setFieldAlignment(QTextStream::AlignRight);
    str.setIntegerBase(base);
    // 将value添加到文本流
    str << value;
    // 恢复文本流的格式
    str.setIntegerBase(oldIntegerBase);
    str.setFieldAlignment(oldFieldAlignment);
    str.setFieldWidth(oldFieldWidth);
    str.setPadChar(oldPadChar);
}

/* ****************************************************************************
 * 用 formatSpecialCharacter 方法,格式化转义字符 例如'\x0a'
 * '\\','\"','\n',
 * **************************************************************************** */
template <class Int>
static bool formatSpecialCharacter(QTextStream &str, Int value)
{
    bool result = true;
    switch (value) {
    case '\\':
        str << "\\\\";
        break;
    case '\"':
        str << "\\\"";
        break;
    case '\n':
        str << "\\n";
        break;
    default:
        result = false;
        break;
    }
    return result;
}

/* ****************************************************************************
 * 在traits的帮助下格式化字符序列(QChar, ushort (UTF-16),
 * uint (UTF-32)或char (Latin1, Utf-8),
 * traits指定如何获取用于检查打印性的代码以及如何输出纯ASCII值。
 * **************************************************************************** */

template <EncodingDialog::Encoding>
struct FormattingTraits
{
};

template <>
struct FormattingTraits<EncodingDialog::Unicode>
{
    static ushort code(QChar c) { return c.unicode(); }
    static char toAscii(QChar c) { return c.toLatin1(); }
};

template <>
struct FormattingTraits<EncodingDialog::Utf8>
{
    static ushort code(char c) { return uchar(c); }
    static char toAscii(char c) { return c; }
};

template <>
struct FormattingTraits<EncodingDialog::Utf16>
{
    static ushort code(ushort c) { return c; }
    static char toAscii(ushort c) { return char(c); }
};

template <>
struct FormattingTraits<EncodingDialog::Utf32>
{
    static uint code(uint c) { return c; }
    static char toAscii(uint c) { return char(c); }
};

template <>
struct FormattingTraits<EncodingDialog::Latin1>
{
    static uchar code(char c) { return uchar(c); }
    static char toAscii(char  c) { return c; }
};

static bool isHexDigit(char c)
{
    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
        || (c >= 'A' && c <= 'F');
}

/* ****************************************************************************
 * 用formatStringSequence方法,进行编码
 * str:文本流
 * i1,i2:迭代器
 * escapeIntegerBase: 进制的基数
 * escapeWidth:
 * **************************************************************************** */
template <EncodingDialog::Encoding encoding, class Iterator>
static void formatStringSequence(QTextStream &str, Iterator i1, Iterator i2,
                                 int escapeIntegerBase, int escapeWidth,
                                 char escapePrefix = 0)
{
    str << '"';
    bool separateHexEscape = false;  // 单独16进制转义
    for (; i1 != i2; ++i1) {
        const auto code = FormattingTraits<encoding>::code(*i1);
        // 编码在127以上,需要转义
        if (code >= 0x80) {
            formatEscapedNumber(str, code, escapeIntegerBase, escapeWidth, escapePrefix);
            separateHexEscape = escapeIntegerBase == 16 && escapeWidth == 0;
        }
        // 编码在127以内,特殊的转义,
        else {
            // 用 formatSpecialCharacter 方法,格式化转义字符 例如'\x0a'
            if (!formatSpecialCharacter(str, code)) {
                const char c = FormattingTraits<encoding>::toAscii(*i1);
                //对于可变宽度/十六进制:终止文字以停止数字解析(“\x12”“34…”)。
                if (separateHexEscape && isHexDigit(c))
                    str << "\" \"";
                str << c;
            }
            separateHexEscape = false;
        }
    }
    str << '"';
}
/* ****************************************************************************
 * encodedString()方法使用Encoding枚举代表的编码器,对字符串编码
 * **************************************************************************** */
static QString encodedString(const QString &value, EncodingDialog::Encoding e)
{
    QString result;
    QTextStream str(&result);
    switch (e) {
    case EncodingDialog::Unicode:
        // 调用formatStringSequence方法,格式化value字符串,指定文本流str,讫始指针,16进制,域宽4,前缀
        formatStringSequence<EncodingDialog::Unicode>(str, value.cbegin(), value.cend(),16, 4, 'u');
        break;
    case EncodingDialog::Utf8: {
        const QByteArray utf8 = value.toUtf8();
        str << "u8";
        formatStringSequence<EncodingDialog::Utf8>(str, utf8.cbegin(), utf8.cend(),8, 3);
    }
        break;
    case EncodingDialog::Utf16: {
        auto utf16 = value.utf16();
        auto utf16End = utf16 + value.size();
        str << 'u';
        formatStringSequence<EncodingDialog::Utf16>(str, utf16, utf16End,
                                                    16, 0, 'x');
    }
        break;
    case EncodingDialog::Utf32: {
        auto utf32 = value.toUcs4();
        str << 'U';
        formatStringSequence<EncodingDialog::Utf32>(str, utf32.cbegin(), utf32.cend(),
                                                    16, 0, 'x');
    }
        break;
    case EncodingDialog::Latin1: {
        const QByteArray latin1 = value.toLatin1();
        formatStringSequence<EncodingDialog::Latin1>(str, latin1.cbegin(), latin1.cend(),
                                                     16, 0, 'x');
    }
        break;
    case EncodingDialog::EncodingCount:
        break;
    }
    return result;
}

// Dialog helpers
static const char *encodingLabels[]
{
    QT_TRANSLATE_NOOP("EncodingDialog", "Unicode:"),
    QT_TRANSLATE_NOOP("EncodingDialog", "UTF-8:"),
    QT_TRANSLATE_NOOP("EncodingDialog", "UTF-16:"),
    QT_TRANSLATE_NOOP("EncodingDialog", "UTF-32:"),
    QT_TRANSLATE_NOOP("EncodingDialog", "Latin1:")
};

static const char *encodingToolTips[]
{
    QT_TRANSLATE_NOOP("EncodingDialog", "Unicode points for use with any encoding (C++, Python)"),
    QT_TRANSLATE_NOOP("EncodingDialog", "QString::fromUtf8()"),
    QT_TRANSLATE_NOOP("EncodingDialog", "wchar_t on Windows, char16_t everywhere"),
    QT_TRANSLATE_NOOP("EncodingDialog", "wchar_t on Unix (Ucs4)"),
    QT_TRANSLATE_NOOP("EncodingDialog", "QLatin1String")
};

// 带有工具按钮的只读行编辑,用于复制内容
class DisplayLineEdit : public QLineEdit
{
    Q_OBJECT
public:
    explicit DisplayLineEdit(const QIcon &icon, QWidget *parent = nullptr);

public slots:
    void copyAll();
};
// DisplayLineEdit构造函数
DisplayLineEdit::DisplayLineEdit(const QIcon &icon, QWidget *parent) :
    QLineEdit(parent)
{
    // 设置只读
    setReadOnly(true);
#if QT_CONFIG(clipboard) && QT_CONFIG(action)
    // 利用icon创建一个QAction,并制定icon在行文本的尾部
    auto copyAction = addAction(icon, QLineEdit::TrailingPosition);
    // 链接copyAction的triggered信号到copyAll()
    connect(copyAction, &QAction::triggered, this, &DisplayLineEdit::copyAll);
#endif
}
/* ****************************************************************************
 * DisplayLineEdit::copyAll()
 * 将行编辑的数据拷贝到系统剪切板
 **************************************************************************** */
void DisplayLineEdit::copyAll()
{
#if QT_CONFIG(clipboard)
    QGuiApplication::clipboard()->setText(text());
#endif
}
/* ****************************************************************************
 * 表单布局管理器通过字符串添加一行控件
 **************************************************************************** */
static void addFormLayoutRow(QFormLayout *formLayout, const QString &text,
                             QWidget *w, const QString &toolTip)
{
    auto label = new QLabel(text);
    label->setToolTip(toolTip);
    w->setToolTip(toolTip);
    label->setBuddy(w);
    formLayout->addRow(label, w);
}
/* ****************************************************************************
 * 构造函数
 **************************************************************************** */
EncodingDialog::EncodingDialog(QWidget *parent) :
    QDialog(parent)
{
    // 去掉对话框的帮助按钮
    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
    // 设置窗口标题
    setWindowTitle(tr("Encodings"));

    // 使用表达布局器,auto真是好用
    auto formLayout = new QFormLayout;
    // 源行文本控件
    auto sourceLineEdit = new QLineEdit(this);
    // 行文本控件在有一些文本时显示一个处于尾部清除按钮
    sourceLineEdit->setClearButtonEnabled(true);

    // 链接行文本的textChanged信号到textChanged()
    connect(sourceLineEdit, &QLineEdit::textChanged, this, &EncodingDialog::textChanged);
    // 表单布局管理器通过字符串添加一行控件
    addFormLayoutRow(formLayout, tr("&Source:"), sourceLineEdit, tr("Enter text"));
    // 从资源文件获取icon
    const auto copyIcon = QIcon::fromTheme(QLatin1String("edit-copy"),
                                           QIcon(QLatin1String(":/images/editcopy")));
    for (int i = 0; i < EncodingCount; ++i) {
        // 构造DisplayLineEdit对象
        m_lineEdits[i] = new DisplayLineEdit(copyIcon, this);
        // 表单布局添加一行
        addFormLayoutRow(formLayout, tr(encodingLabels[i]),
                         m_lineEdits[i], tr(encodingToolTips[i]));
    }
    // 主布局管理器 垂直箱式布局
    auto mainLayout = new QVBoxLayout(this);
    // 主布局添加表单布局
    mainLayout->addLayout(formLayout);
    // 定义关闭按钮的对话框close按钮
    auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
    // 链接关闭按钮的rejected信号到QDialog::reject
    connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
    // 主布局添加 关闭按钮
    mainLayout->addWidget(buttonBox);
}
/* ****************************************************************************
 * 处理sourceLineEdit触发的textChanged信号
 * sourceLineEdit触发
 **************************************************************************** */
void EncodingDialog::textChanged(const QString &t)
{   // 如果字符为空
    if (t.isEmpty()) {
        // 逐个清空DisplayLineEdit控件
        for (auto lineEdit : m_lineEdits)
            lineEdit->clear();
    } else {
         for (int i = 0; i < EncodingCount; ++i)
             // encodedString()方法使用Encoding枚举代表的编码器,对字符串编码
             // DisplayLineEdit控件设置编码后的字符串
             m_lineEdits[i]->setText(encodedString(t, static_cast<Encoding>(i)));
    }
}

#include "encodingdialog.moc"

程序图标资源:

程序中唯一图标为 editcopy.png

总结

PreviewForm比较简单,重点在EncodingDialog这个类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值