计算机系统应用教程网站

网站首页 > 技术文章 正文

【小白课程】openKylin便签贴的设计与实现

btikc 2024-10-20 05:04:16 技术文章 8 ℃ 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便签需要继续改进的地方。欢迎感兴趣的小伙伴加入我们~

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表