【小白课程】openKylin便签贴的设计与实现
ztj100 2025-07-10 22:14 7 浏览 0 评论
openKylin便签贴作为侧边栏的一个小插件,提供便捷的文本记录和灵活的页面展示。openKylin便签贴分为两个部分:便签列表和便签页。其中,便签列表以列表形式展示所有内容,可切换图标/列表视图,并提供搜索查找功能;便签页提供编辑内容功能,可对内容进行字体大小/颜色、斜体、下划线、有序/无序、插入图片等操作。
openKylin便签贴及相关软件包安装:
$sudo apt install ukui-notebook
注意:openKylin 1.0.1及2.0 Alpha版本均已预装
一.openKylin便签贴功能介绍
1.便签列表
- 实时按照修改时间倒序排序
- 显示每条便签的修改时间和部分文本内容
- 新建:列表条目增加并打开一个便签页
- 搜索:匹配列表中所有便签的文本内容进行搜索
- 删除:删除当前列表选中条目,删除后自动选中列表中上一条便签,若删除时,对应条目的便签为打开状态,则同时关闭此便签页;若无列表中无条目选中,则删除无效
- 支持双击列表/图标条目,打开或重新激活置顶便签并获取输入焦点
2.便签页
- 支持文本修改自动保存
- 支持用户自定义便签头颜色并保存数据库
- 文本修改后,此便签页对应便签列表中条目自动置顶排序
- 便签头颜色修改后,此便签页对应便签列表中条目自动更新同步
- 删除此便签:删除此便签,并删除此便签页对应便签列表中对应条目
- 打开便签:任意便签可重新唤起便签列表
- 新建:在任一便签页新建会创建新便签页,同步到便签
- 关闭:关闭当前便签页,若当前便签页文本内容为空,则删除此便签,并删除此便签页对应便签列表中条目
- 支持加粗、斜体、下划线、删除线、无序列表、有序列表
- 支持修改字体大小,字体颜色
二.openKylin 便签贴实现原理
便签贴基于QT实现,主要涉及便签列表的QListView类和便签编辑页的QTextEdit类。所以在讲便签贴具体实现之前,简单介绍一下这两个类。
1. QListView
QListView可以用来以列表的形式展示数据,在Qt中使用model/View结构来管理数据与视图的关系,model负责数据的存取,数据的交互通过delegate来实现.
(1)数据模型
- QT提供了一些现成的models用于处理数据项:
- QStringListModel 用于存储简单的QString列表。
- QStandardItemModel 管理复杂的树型结构数据项,每项都可以包含任意数据。
- QDirModel 提供本地文件系统中的文件与目录信息。
- QSqlQueryModel, QSqlTableModel,QSqlRelationTableModel用来访问数据库。
模型中的每个数据项都有一个与之对应的role来存储某一类数据。需要存取自定义数据可以使用UserRole,UserRole+1...
便签使用QAbstractListModel,自定义了可编辑列表模型noteModel。便签定义NoteRole存储数据对象:
enum NoteRoles{
NoteID = Qt::UserRole + 1,
NoteFullTitle,
NoteCreationDateTime,
NoteLastModificationDateTime,
NoteDeletionDateTime,
NoteContent,
NoteScrollbarPos,
NoteColor,
NoteMdContent,
};
数据的存取:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE;
(2)自定义delegate
模型的交互和绘制通过自定义delegate来实现。便签定义了两种delegate——list和icon,这两种基本只在item的绘制有区别。
2.QTextEdit
QTextEdit类是一个多行文本框控件,可以显示多行文本内容,当文本内容超出控件显示范围时,可以显示水平垂直滚动条,用于编辑和显示纯文本和富文本。
操作函数:
- setPlainText() 设置多行文本框的内容
- toPlainText() 返回多行文本框的文本内容
- setHtml() 设置多行文本框的文本内容为HTML文档,HTML文档是描述网页的
- toHtml() 返回多行文本框的HTML内容
- clear() 清除多行文本框的内容
信号:
- textChanged():文本改动信号
- currentCharFormatChanged(const QTextCharFormat &format):文本风格改动信号
- void cursorPositionChanged():光标位置变化信号
- QTextEdit常与QTextCursor一起使用,提供接口进行编辑。
常用函数:
- beginEditBlock() endEditBlock():分组游标操作
- insertBlock() 将新文本块(段落)插入光标位于光标位置的文档,并将光标移动到新块的开头。
- insertFragment() 将现有文本片段插入到光标位置的文档中。
- insertImage() 将图像插入到光标位置的文档中。
- insertText() 在光标位置将文本插入到文档中。
- insertFrame() 在光标的当前块之后将框架插入到文档中,并将光标移动到新框架中空块的开始。
- insertList() 在光标位置将列表插入到文档中,并将光标移动到列表中第一个项目的开始。
- insertTable() 在光标的当前块之后将表插入到文档中,并将光标移动到表后块的开始。
三.openKylin 便签贴具体实现
1. 便签列表
首先,介绍一下便签列表涉及的几个类。
- NoteData 便签数据类,记录便签id,头颜色,标题,最新编辑时间等内容
- NoteModel 继承QAbstractListModel,便签列表模型抽象类,展示和管理列表数据。
- QModelIndex 可以用来引用模型中的项,它包含确定这个项在模型中的位置所需的所有信息。索引拥有行信息、列信息,可以使用row()、column()和parent()函数来获取这些信息。为noteModel 模型和noteView 列表提供“桥梁”,供索引。
- NoteView 继承Qlistview,设置NoteModel 为模型,并显示listview。最终自定义的列表模型中的数据以列表形式显示。
(1) 新建
首先确定是否有其他正在进行的操作,若无则可以开始新建便签操作,并将列表滚动到最高处。m_noteCounter记录便签数,加一。产生新便签数据类,插入新便签到便签模型中,并将数据保存到数据库中。
void Widget::createNewNote()
{
if (!m_isOperationRunning) {
m_isOperationRunning = true;
m_noteView->scrollToTop();
++m_noteCounter;
NoteData *tmpNote = generateNote(m_noteCounter);
// insert the new note to NoteModel
QModelIndex indexSrc = m_noteModel->insertNote(tmpNote, 0);
// update the editor header date label
QString dateTimeFromDB = tmpNote->lastModificationdateTime().toString(Qt::ISODate);
QString dateTimeForEditor = getNoteDateEditor(dateTimeFromDB);
// 从排序过滤器模型返回与给定 indexSrc 对应的源模型索引。
m_currentSelectedNoteProxy = m_proxyModel->mapFromSource(indexSrc);
saveNoteToDB(m_currentSelectedNoteProxy);
// 设置索引 m_currentSelectedNoteProxy 所在的页面为当前页面
m_noteView->setCurrentIndex(m_currentSelectedNoteProxy);
m_isOperationRunning = false;
}
......
}
(2) 便签列表
双击列表项打开便签,或者右键弹出操作菜单,可打开、删除和清空列表。
- 打开:在滚动区域单机便签,为取消突出显示上一个选定的便签。如果在临时便签存在时选择便签,即为删除临时便签,突出显示所选便签,并将所选便签内容加载到textedit。
- 删除:通过当前列表模型获取noteid,删除对应noteid的便签项,保存到数据库。
- 清空:清空便签模型、列表等所有信息,并删除数据库内容。
(3)内容搜索
// 搜索栏文本输入
connect(m_searchLine, &QLineEdit::textChanged, this, &Widget::onSearchEditTextChanged);
使用Queue队列获取搜索栏文本内容,将用于过滤模型内容的固定字符串设置为给定模式,根据过滤模型的noteid,显示筛选后的listview.
void Widget::onSearchEditTextChanged(const QString &keyword)
{
m_searchQueue.enqueue(keyword);
if (!m_isOperationRunning) {
m_isOperationRunning = true;
// disable animation while searching
m_noteView->setAnimationEnabled(false);
while (!m_searchQueue.isEmpty()) {
qApp->processEvents();
QString str = m_searchQueue.dequeue();
if (str.isEmpty()) {
clearSearch();
} else {
m_noteView->setFocusPolicy(Qt::NoFocus);
// 过滤
findNotesContain(str);
}
}
m_noteView->setAnimationEnabled(true);
m_isOperationRunning = false;
}
}
void Widget::findNotesContain(const QString &keyword)
{
// 将用于过滤源模型内容的固定字符串设置为给定模式
m_proxyModel->setFilterFixedString(keyword);
// 如果匹配到不止一行
if (m_proxyModel->rowCount() > 0) {
selectFirstNote();
} else {
m_currentSelectedNoteProxy = QModelIndex();
}
}
(4)视图切换
视图有列表视图和图标视图,自定义两种模型代理iconViewModeDelegate和listViewModeDelegate。
m_proxyModel->setSourceModel(m_noteModel); // 代理真正的数据模型,对数据进行排序和过滤
m_proxyModel->setFilterKeyColumn(0); // 此属性保存用于读取源模型内容的键的列,listview只有一列所以是0
m_proxyModel->setFilterRole(NoteModel::NoteMdContent);// 此属性保留项目角色,该角色用于在过滤项目时查询源模型的数据
m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);//
m_noteView->setItemDelegate(new iconViewModeDelegate(m_noteView)); // 安装定制delegate提供编辑功能
m_noteView->setModel(m_proxyModel); // 设置view的model是proxyModel,proxyModel作为view和QAbstractListModel的桥
2.便签页
便签页操作区:
(1)文本编辑
文本编辑是基于QTextEdit实现的。
connect(ui->textEdit, &QTextEdit::textChanged, this, &EditPage::textChangedSlot);
void EditPage::textChangedSlot()
{
emit texthasChanged(m_noteId, this->m_id);
}
绑定m_noteId为便签id,m_id为当前编辑页面id。两个一起确定编辑的便签页面。通过此信号texthasChanged()传递给便签列表页面,实时更新对应列表项标题等内容。
connect(m_editors[m_editors.size() - 1], SIGNAL(texthasChanged(int,int)), this,
SLOT(onTextEditTextChanged(int,int)));
(2)便签头
自定义便签头noteHead,继承QWidget,显示便签头颜色。便签头菜单noteHeadMenu,也是一个QWidget。包括新建按钮、调色板、选项和关闭按钮。
点击调色板按钮,可弹出调色板菜单,这个菜单由PaletteWidget.ui实现。
点击以上调色按钮选择颜色后,以选择红色为例:
void EditPage::redBtnSlot()
{
QColor color((PaletteWidget::KY_RED));
m_editColor = color;
emit colorhasChanged(m_editColor, m_noteId);
m_noteHead->colorWidget = color;
m_noteHeadMenu->colorWidget = color;
update();
}
设置便签头颜色为红色并更新,同时给便签列表窗口发送颜色改变信号colorhasChanged(),便签列表收到信号后更新列表头颜色。
connect(m_editors[m_editors.size() - 1], SIGNAL(colorhasChanged(QColor,int)), this,
SLOT(onColorChanged(QColor,int)));
(3)字体风格
字体风格包括字体大小、颜色、加粗、下划线等。首先以字体大小/颜色按钮组为例,介绍一下。
- 按钮组
- CustomPushButtonGroup继承QFrame,通过加载qss样式文件设置字体大小/颜色按钮组。
- 按钮下拉选项
- 字体大小下拉选项SetFontSize和字体颜色下拉选项SetFontColor,都是基于QListWidget编写的widget窗口。
// 字体颜色大小
connect(m_setSizePage->ui->listWidget, &QListWidget::itemClicked, this,
&EditPage::setFontSizeSlot);
connect(m_setColorFontPage->ui->listWidget, &QListWidget::itemClicked, this,
&EditPage::setFontColorSlot);
以字号修改为例,字体大小和颜色都是采用QTextCharFormat对象实现的。
// 字号
void EditPage::setFontSizeSlot()
{
int num = m_setSizePage->ui->listWidget->currentRow();
ui->fontTwinButtonGroup->getFontSizeBtn()->setButtonSize(QString::number(num+10));
m_setSizePage->close();
update();
QTextCharFormat fmt;
InformationCollector::getInstance().addMessage(QString("set font size to %1.").arg(num+10));
fmt.setFontPointSize(num+10);
mergeFormatOnWordOrSelection(fmt);
}
选择字号后,设置QTextCharFormat文本格式,根据格式作以下操作:
void EditPage::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
{
QTextCursor cursor = ui->textEdit->textCursor();
if (!cursor.hasSelection()) {
// cursor.select(QTextCursor::WordUnderCursor);
}
cursor.mergeCharFormat(format);
ui->textEdit->mergeCurrentCharFormat(format);
ui->textEdit->setFocus(Qt::TabFocusReason);
}
获取控件的焦点,假设当前控件上的文本并没有被选中,就指定光标区域所在的词为高亮选定词,从而设置字体风格样式。其他的字体风格,如加粗、斜体等:
- 加粗:
- fmt.setFontWeight(QFont::Bold);
- 斜体:
- fmt.setFontItalic(QFont::StyleItalic);
- 下划线:
- fmt.setFontUnderline(ui->fontPropertyWidget->underlineBtn()->isCheckable());
- 删除线:
- fmt.setFontStrikeOut(ui->fontPropertyWidget->strikeOutBtn()->isCheckable());
(4)图片插入
插入图片和字体风格类似,采用对QTextImageFormat 对象进行操作。在光标处进行图片插入。
connect(ui->fontPropertyWidget->insertBtn(), &QPushButton::clicked, this, &EditPage::insertpicture);
void EditPage::insertpicture()
{
......
QTextCursor cursor = ui->textEdit->textCursor();
if(cursor.atStart())
{
m_isInsImg = true;
}
QTextImageFormat imageFormat;
imageFormat.setWidth ( image.width() );
imageFormat.setHeight ( image.height() );
imageFormat.setName ( QString("data:image/%1;base64,%2")
.arg(QString("%1.%2").arg(rand()).arg(format))
.arg(base64l.data())
);
cursor.insertImage ( imageFormat );
}
(5)有序/无序列表
对QTextListFormat对象进行操作,listFmt.setStyle(style)设置列表样式,无序为QTextListFormat::ListDisc,有序为
QTextListFormat::ListDecimal。
connect(ui->fontPropertyWidget->unorderedBtn(), &QPushButton::clicked, this, &EditPage::setUnorderedListSlot);
connect(ui->fontPropertyWidget->orderedBtn(), &QPushButton::clicked, this, &EditPage::setOrderedListSlot);
QTextListFormat listFmt;
if (cursor.currentList()) {
listFmt = cursor.currentList()->format();
}
listFmt.setStyle(style);
cursor.createList(listFmt);
上述代码首先检查游标是否在现有列表中,如果是,则为新列表的列表格式提供适当的缩进级别。这允许创建嵌套列表,增加缩进级别。更复杂的实现还将对列表每个级别的项目符号使用不同的符号。
3.pc/平板模式切换
在openKylin平板模式下,便签贴支持全屏化,隐藏最大化按钮,dbus信号监听模式切换。
QDBusConnection::sessionBus().connect(KYLIN_ROTATION_SERVICE, KYLIN_ROTATION_PATH, KYLIN_ROTATION_INTERFACE,
QString("rotations_change_signal"), this, SLOT(rotationChanged(QString)));
QDBusConnection::sessionBus().connect(KYLIN_ROTATION_SERVICE, KYLIN_ROTATION_PATH, KYLIN_ROTATION_INTERFACE,
QString("mode_change_signal"), this, SLOT(modeChanged(bool)));
模式切换:
4. 数据同步
便签使用SQLite数据库对数据增删改查操作,具体表结构如下:
id | INTEGER | 便签在数据库中唯一标识,用以区分不同便签 |
creation_date | INTEGER | 便签创建日期 |
modification_date | INTEGER | 便签修改日期 |
deletion_date | INTEGER | 便签删除日期 |
content | TEXT | 便签内容,以html形式存储 |
full_title | TEXT | 便签标题 |
note_color | INTEGER | 便签头颜色 |
md_content | TEXT | 文本化的数据内容,用于实现便签的查找操作 |
以上就是openKylin便签贴实现原理,界面的加载逻辑简单,其中由于便签编辑控件选择的是textedit,对于获取图片插入状态,缩进控制等相关功能存在问题,这也是后续openKylin便签需要继续改进的地方。欢迎感兴趣的小伙伴加入我们~
相关推荐
- Sublime Text 4 稳定版 Build 4113 发布
-
IT之家7月18日消息知名编辑器SublimeText4近日发布了Build4113版本,是SublimeText4的第二个稳定版。IT之家了解到,SublimeTe...
- 【小白课程】openKylin便签贴的设计与实现
-
openKylin便签贴作为侧边栏的一个小插件,提供便捷的文本记录和灵活的页面展示。openKylin便签贴分为两个部分:便签列表...
- 壹啦罐罐 Android 手机里的 Xposed 都装了啥
-
这是少数派推出的系列专题,叫做「我的手机里都装了啥」。这个系列将邀请到不同的玩家,从他们各自的角度介绍手机中最爱的或是日常使用最频繁的App。文章将以「每周一篇」的频率更新,内容范围会包括iOS、...
- 电气自动化专业词汇中英文对照表(电气自动化专业英语单词)
-
专业词汇中英文对照表...
- Python界面设计Tkinter模块的核心组件
-
我们使用一个模块,我们要熟悉这个模块的主要元件。如我们设计一个窗口,我们可以用Tk()来完成创建;一些交互元素,按钮、标签、编辑框用到控件;怎么去布局你的界面,我们可以用到pack()、grid()...
- 以色列发现“死海古卷”新残片(死海古卷是真的吗)
-
编译|陈家琦据艺术新闻网(artnews.com)报道,3月16日,以色列考古学家发现了死海古卷(DeadSeaScrolls)新残片。新出土的羊皮纸残片中包括以希腊文书写的《十二先知书》段落,这...
- 鸿蒙Next仓颉语言开发实战教程:订单列表
-
大家上午好,最近不断有友友反馈仓颉语言和ArkTs很像,所以要注意不要混淆。今天要分享的是仓颉语言开发商城应用的订单列表页。首先来分析一下这个页面,它分为三大部分,分别是导航栏、订单类型和订单列表部分...
- 哪些模块可以用在 Xposed for Lollipop 上?Xposed 模块兼容性解答
-
虽然已经有了XposedforLollipop的安装教程,但由于其还处在alpha阶段,一些Xposed模块能不能依赖其正常工作还未可知。为了解决大家对于模块兼容性的疑惑,笔者尽可能多...
- 利用 Fluid 自制 Mac 版 Overcast 应用
-
我喜爱收听播客,健身、上/下班途中,工作中,甚至是忙着做家务时。大多数情况下我会用MarcoArment开发的Overcast(Freemium)在iPhone上收听,这是我目前最喜爱的Po...
- 浅色Al云食堂APP代码(三)(手机云食堂)
-
以下是进一步优化完善后的浅色AI云食堂APP完整代码,新增了数据可视化、用户反馈、智能推荐等功能,并优化了代码结构和性能。项目结构...
- 实战PyQt5: 121-使用QImage实现一个看图应用
-
QImage简介QImage类提供了独立于硬件的图像表示形式,该图像表示形式可以直接访问像素数据,并且可以用作绘制设备。QImage是QPaintDevice子类,因此可以使用QPainter直接在图...
- 滚动条隐藏及美化(滚动条隐藏但是可以滚动)
-
1、滚动条隐藏背景/场景:在移动端,滑动的时候,会显示默认滚动条,如图1://隐藏代码:/*隐藏滚轮*/.ul-scrool-box::-webkit-scrollbar,.ul-scrool...
- 浅色AI云食堂APP完整代码(二)(ai 食堂)
-
以下是整合后的浅色AI云食堂APP完整代码,包含后端核心功能、前端界面以及优化增强功能。项目采用Django框架开发,支持库存管理、订单处理、财务管理等核心功能,并包含库存预警、数据导出、权限管理等增...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)
- vmware17pro最新密钥 (34)
- mysql单表最大数据量 (35)