Qt Creator源码分析系列——UI界面:ThemeChooser类

该篇文章内容主要集中Qt Creator软件界面部分代码的分析。从分析插件中的plugins\coreplugin中的ThemeChooser模块开始,项目文件在路径\qt-creator-master\qt-creator-master\src\plugins\coreplugin\下。

涉及的三个类的关系,越往下越底层。Theme类负责对.creatortheme主题文件的数据进行读取和分类存储。而ThemeEntry类负责对主题文件的文件名和路径进行归档和Theme对象的创建。ThemeListModel是基于ThemeEntry的数据创建model。ThemeChooser则是负责下拉框和ThemeListModel的管理。ThemeChooser类则负责主题切换的逻辑。
在这里插入图片描述

ThemeEntry类

ThemeEntry类有三个私有成员变量,Id类型的m_id、QString类型的m_filePath和QString类型的m_displayName。该类是ThemeChooser模块中最底层的类,并且和Theme类密切相关。

class ThemeEntry
{
public:
    ThemeEntry() = default;
    ThemeEntry(Id id, const QString &filePath);
    Id id() const;
    QString displayName() const;
    QString filePath() const;
    static QList<ThemeEntry> availableThemes();
    static Id themeSetting();
    static Utils::Theme *createTheme(Id id);
private:
    Id m_id;
    QString m_filePath;
    mutable QString m_displayName;
};

使用id和filePath来初始化相应变量的构造函数。

ThemeEntry::ThemeEntry(Id id, const QString &filePath) : m_id(id), m_filePath(filePath)
{
}

如果m_displayName不为空,则直接返回。否则,从QSettings设置的m_filePath路径指定的ini文件中读取themeNameKey的值作为m_displayName。

static const char themeNameKey[] = "ThemeName";
QString ThemeEntry::displayName() const
{
    if (m_displayName.isEmpty() && !m_filePath.isEmpty()) {
        QSettings settings(m_filePath, QSettings::IniFormat);
        m_displayName = settings.value(QLatin1String(themeNameKey), QCoreApplication::tr("unnamed")).toString();
    }
    return m_displayName;
}

addThemesFromPath函数从path处读取themes。如图所示,QtCreator的主题文件都是以.creatortheme后缀结尾,并且文件内容都是以键值对保存的。通过QDir的Filter功能过滤掉其他文件,留下主题文件。

static void addThemesFromPath(const QString &path, QList<ThemeEntry> *themes)
{
    static const QLatin1String extension("*.creatortheme");
    QDir themeDir(path);
    themeDir.setNameFilters({extension});
    themeDir.setFilter(QDir::Files);
    const QStringList themeList = themeDir.entryList();
    foreach (const QString &fileName, themeList) {
        QString id = QFileInfo(fileName).completeBaseName();  // 提取文件名,不包含最后一个.和.后面的后缀
        // 使用ThemeEntry对象,将文件名最为Id,绝对路径作为m_filePath
        themes->append(ThemeEntry(Id::fromString(id), themeDir.absoluteFilePath(fileName)));
    }
}
QList<ThemeEntry> ThemeEntry::availableThemes()
{
    QList<ThemeEntry> themes;
    static const QString installThemeDir = ICore::resourcePath() + QLatin1String("/themes");
    static const QString userThemeDir = ICore::userResourcePath() + QLatin1String("/themes");
    // 将installThemeDir目录下的.creatortheme的绝对路径和文件名加载到themes中
    addThemesFromPath(installThemeDir, &themes);
    if (themes.isEmpty())
        qWarning() << "Warning: No themes found in installation: "
                   << QDir::toNativeSeparators(installThemeDir);
    // move default theme to front
    // 找出默认主题的序号
    int defaultIndex = Utils::indexOf(themes, Utils::equal(&ThemeEntry::id, Id(Constants::DEFAULT_THEME)));
    // 将其加载到themes列表的首部
    if (defaultIndex > 0) { // == exists and not at front
        ThemeEntry defaultEntry = themes.takeAt(defaultIndex);
        themes.prepend(defaultEntry);
    }
    addThemesFromPath(userThemeDir, &themes);
    return themes;
}

在这里插入图片描述在这里插入图片描述
从上一节我们可以知道这里主题的Id,其实指的是dark、default、design、flat、flat-dark和flat-light。这是加载主题的最关键的动作。Theme类相关内容可以参考:
Qt Creator源码分析系列——UI界面:Theme类

Theme *ThemeEntry::createTheme(Id id)
{
    if (!id.isValid())
        return nullptr;
    // 从可以可获取的主题列表中找到Id为形参指定id的主题ThemeEntry
    // 并使用theme的readSettings函数加载主题内容
    const ThemeEntry entry = Utils::findOrDefault(availableThemes(),  Utils::equal(&ThemeEntry::id, id));
    if (!entry.id().isValid())
        return nullptr;
    // 使用QSettings从文件
    QSettings themeSettings(entry.filePath(), QSettings::IniFormat);
    Theme *theme = new Theme(entry.id().toString());
    theme->readSettings(themeSettings);
    return theme;
}
Id ThemeEntry::themeSetting()
{
    const Id setting = Id::fromSetting(ICore::settings()->value(QLatin1String(Constants::SETTINGS_THEME), QLatin1String(Constants::DEFAULT_THEME)));
    const QList<ThemeEntry> themes = availableThemes();
    if (themes.empty())
        return Id();
    const bool settingValid = Utils::contains(themes, Utils::equal(&ThemeEntry::id, setting));
    return settingValid ? setting : themes.first().id();
}

ThemeListModel类

ThemeListModel类实现了MVC模式中的Model。其继承自QAbstractListModel。最重要的成员就是m_themes,它的类型是QList<ThemeEntry>,也就和上面讲的类相关。rowCount和data函数是父类QAbstractListModel函数的重载。

class ThemeListModel : public QAbstractListModel
{
public:
    ThemeListModel(QObject *parent = nullptr): QAbstractListModel(parent) { }

    int rowCount(const QModelIndex &parent) const override
    {
        return parent.isValid() ? 0 : m_themes.size();
    }
    QVariant data(const QModelIndex &index, int role) const override
    {
        if (role == Qt::DisplayRole)
            return m_themes.at(index.row()).displayName();
        return QVariant();
    }

    void setThemes(const QList<ThemeEntry> &themes)
    {
        beginResetModel();
        m_themes = themes;  // 这里初始化成员变量m_themes
        endResetModel();
    }
	// 查询第index个主题条目
    const ThemeEntry &themeAt(int index) const
    {
        return m_themes.at(index);
    }
    // 清除第index个主题条目
	void removeTheme(int index)
    {
        beginRemoveRows(QModelIndex(), index, index);
        m_themes.removeAt(index);
        endRemoveRows();
    }

private:
    QList<ThemeEntry> m_themes;
};

在这里插入图片描述
开始模型重置操作。重置操作会在任何附加的视图中将模型重置为当前状态。

ThemeChooserPrivate类

ThemeChooserPrivate类比较简单,有ThemeListModel指针和QComboxBox指针成员。ThemeListModel是上面所讲解的类。

class ThemeChooserPrivate
{
public:
    ThemeChooserPrivate(QWidget *widget);
    ~ThemeChooserPrivate();
public:
    ThemeListModel *m_themeListModel;
    QComboBox *m_themeComboBox;
};

构造函数初始化列表中对两个成员变量进行了初始化。并且创建横向布局QHBoxLayout,添加m_themeComboBox和新建的Label。

ThemeChooserPrivate::ThemeChooserPrivate(QWidget *widget) : m_themeListModel(new ThemeListModel), m_themeComboBox(new QComboBox)
{
    auto layout = new QHBoxLayout(widget);
    layout->addWidget(m_themeComboBox);
    auto overriddenLabel = new QLabel;
    overriddenLabel->setText(ThemeChooser::tr("Current theme: %1").arg(creatorTheme()->displayName()));
    layout->addWidget(overriddenLabel);
    layout->setContentsMargins(0, 0, 0, 0);
    // QSpacerItem类在布局中提供空白
    auto horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
    layout->addSpacerItem(horizontalSpacer);
    // 添加模型
    m_themeComboBox->setModel(m_themeListModel);
    // 查询当前应用主题是否在可获得主题列表中
    const QList<ThemeEntry> themes = ThemeEntry::availableThemes();
    const Id themeSetting = ThemeEntry::themeSetting();
    const int selected = Utils::indexOf(themes, Utils::equal(&ThemeEntry::id, themeSetting));
    // 为m_themeListModel设置数据源,也就是主题源
    m_themeListModel->setThemes(themes);
    if (selected >= 0)
        m_themeComboBox->setCurrentIndex(selected);
}
ThemeChooserPrivate::~ThemeChooserPrivate()
{
    delete m_themeListModel;
}

布局如图所示:
在这里插入图片描述

ThemeChooser类

这里ThemeChooser和Theme一样,都有一个相应的Private类,用于处理数据。

class ThemeChooser : public QWidget
{
    Q_OBJECT
public:
    ThemeChooser(QWidget *parent = nullptr);
    ~ThemeChooser() override;
    void apply();
private:
    ThemeChooserPrivate *d;
};

构造和析构都很简单,如下所示:

ThemeChooser::ThemeChooser(QWidget *parent) :
    QWidget(parent)
{
    d = new ThemeChooserPrivate(this);
}
ThemeChooser::~ThemeChooser()
{
    delete d;
}

这个类最重要的一个函数如下所示:

void ThemeChooser::apply()
{
	// 获得下拉框选中的序号
    const int index = d->m_themeComboBox->currentIndex();
    if (index == -1)
        return;
    // 获取选中主题的Id
    const QString themeId = d->m_themeListModel->themeAt(index).id().toString();
    // 取得目前设置的主题Id
    QSettings *settings = ICore::settings();
    const QString currentThemeId = ThemeEntry::themeSetting().toString();
    // 如果需要切换主题
    if (currentThemeId != themeId) {
        // save filename of selected theme in global config
        // 保存当前选中的主题Id到全局配置中
        settings->setValue(QLatin1String(Constants::SETTINGS_THEME), themeId);
        // 弹出非模态对话框,告知主题切换在重启后生效
        RestartDialog restartDialog(ICore::dialogParent(),
                                    tr("The theme change will take effect after restart."));
        restartDialog.exec();
    }
}
发布了134 篇原创文章 · 获赞 141 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/asmartkiller/article/details/104413733