《QT实用小工具·六十》Qt 多列时间轴控件

2024-05-08 06:12

本文主要是介绍《QT实用小工具·六十》Qt 多列时间轴控件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1、概述
源码放在文章末尾

Qt 多列时间轴控件。

可与多段字符串格式自由转换,也可手动添加列表项。

专门用来以时间轴作为事件线发展顺序的故事大纲。

特点
时间背包功能:记录所有物品或属性发生的变化,随时回溯
时间可输入任意内容,不限于时间
每一时间段允许多列,即多个文字节点
全自动调整节点大小(宽高),尽量看起来舒服
行与行、列与列 之间任意拖拽更换顺序
可与文字自由转换,默认一段为一个文字节点
方便的多行操作
按需修改快捷键
所有编辑可撤销
美观的调整动画

项目demo演示如下所示:
在这里插入图片描述
项目部分代码如下所示:

#ifndef TIMELINEWIDGET_H
#define TIMELINEWIDGET_H#include <QObject>
#include <QDebug>
#include <QInputDialog>
#include <QMenu>
#include <QAction>
#include <QMimeData>
#include <QDrag>
#include <QPropertyAnimation>
#include <QScrollArea>
#include <QScrollBar>
#include <QPlainTextEdit>
#include <QTimer>
#include "timelinebucket.h"
#include "labeleditor.h"class TimelineWidget : public QScrollArea
{Q_OBJECT
public:TimelineWidget(QWidget *parent = nullptr);void addItem(QString time, QString text);void addItem(QString time, QStringList texts);TimelineBucket* insertItem(QString time, QStringList texts, int index = -1);void removeItem(int index);void clearAll();int count();int indexOf(TimelineBucket* bucket) const;TimelineBucket* at(int index) const;void moveBucket(int from_index, int to_index);void selectAll();void unselectAll();void selectItem(TimelineBucket* bucket);void selectItems(QList<int> rows, bool clearBefore = true);void unselectItem(TimelineBucket* bucket);void setCurrentItem(int row, bool multi = false);void setCurrentItem(TimelineBucket* bucket, bool multi = false);void scrollTo(int index = -1);QList<int> selectedIndexes(int delta = 0) const;void resetWidth();void adjustBucketsPositions(int start = -1);void adjustBucketsPositionsWithAnimation(int start = 0, int end = -1);void fromString(QString string, QString time_reg = "【(.*)】", QString node_split = "\n", QString nodes_split = "\n");QString toString(QString time_format = "【%1】", QString para_split = "\n", QString line_split = "\n\n");protected:void keyPressEvent(QKeyEvent* event) override;private:TimelineBucket *createItemWidget(QString time, QStringList texts);signals:void manualSelected(); // 鼠标/键盘事件void targetItemsChanged(); // 选中项改变,或者选中的内容改变了public slots:void updateUI();void slotBucketWidgetToSelect(TimelineBucket* bucket);void slotTimeWidgetClicked(TimelineTimeLabel* label);void slotTextWidgetClicked(TimelineTextLabel* label);void slotTimeWidgetDoubleClicked(TimelineTimeLabel* label);void slotTextWidgetDoubleClicked(TimelineTextLabel* label);void slotMenuShowed(const QPoint& pos);void slotDroppedAndMoved(TimelineBucket* from, TimelineBucket* to);void slotEditChanged();void slotEdit(int row, int col);void finishEditing();void actionAddText();void actionAddTextLeft();void actionAddTextRight();void actionEditTime();void actionEditText(int index);void actionAddLine();void actionInsertAbove();void actionInsertUnder();void actionDeleteLine();void actionCopyText();void actionPaste();private:QWidget* center_widget;QList<TimelineBucket*> buckets;QList<TimelineBucket*> selected_buckets;int current_index;LabelEditor* edit;TimelineBucket* editing_bucket;QLabel* editing_label;bool _adusting_buckets_size; // 是否正在调整索引buckets大小(无视此时触发的sizeHintChanged信号)bool _width_need_adjust = false; // 下次动画是否强制调整宽度
};#endif // TIMELINEWIDGET_H
#include "timelinewidget.h"TimelineWidget::TimelineWidget(QWidget *parent) : QScrollArea(parent)
{setAcceptDrops(true);setAttribute(Qt::WA_TransparentForMouseEvents, false);setContextMenuPolicy(Qt::CustomContextMenu);setFocusPolicy(Qt::StrongFocus);connect(this,SIGNAL(customContextMenuRequested (const QPoint&)),this,SLOT(slotMenuShowed(const QPoint&)));_adusting_buckets_size = false;_width_need_adjust = false;current_index = -1;center_widget = new QWidget(this);setWidget(center_widget);editing_bucket = nullptr;editing_label = nullptr;edit = new LabelEditor(center_widget);connect(edit, &LabelEditor::textChanged, this, [=] {if (editing_label == nullptr)return ;editing_label->setText(edit->toPlainText());
//        if (editing_label->objectName() == "TimelineTextLabel") {
//            static_cast<TimelineTextLabel>(editing_label).adjustSize(true, edit->toPlainText());
//        } else {editing_label->adjustSize();
//        }edit->move(editing_label->pos() + editing_label->parentWidget()->pos());edit->resize(editing_label->size());editing_bucket->adjustWidgetsPositions();});connect(edit, &LabelEditor::signalEditCanceled, this, [=](QString origin) {if (editing_label == nullptr)return ;edit->setPlainText(origin); // 设置回初始内容QTimer::singleShot(0, [=]{editing_label = nullptr;editing_bucket = nullptr;});this->setFocus();});connect(edit, &LabelEditor::signalEditFinished, this, [=](QString text) {if (editing_label == nullptr) // 快速按下两次时会触发这个信号槽,而第一次已经使 editing_label = nullptrreturn ;// 编辑结束,保存 undoQString orig = edit->getOriginText();if (text != orig) // 文本有变动{if (editing_bucket->indexOf(static_cast<TimelineTextLabel*>(editing_label)) >= 0){timeline_undos->modifyCommand(editing_bucket, static_cast<TimelineTextLabel*>(editing_label), orig, text);emit targetItemsChanged();}else{timeline_undos->modifyCommand(editing_bucket, orig, text);emit targetItemsChanged();}}edit->hide();editing_label = nullptr;editing_bucket = nullptr;this->setFocus();});edit->hide();updateUI();
}void TimelineWidget::addItem(QString time, QString text)
{addItem(time, QStringList{text});
}void TimelineWidget::addItem(QString time, QStringList texts)
{insertItem(time, texts, -1);
}TimelineBucket *TimelineWidget::insertItem(QString time, QStringList texts, int index)
{TimelineBucket* bucket = createItemWidget(time, texts);bucket->adjustWidgetsPositions();if (index < 0 || index >= count()) // 添加到末尾{if (count() >= 1)bucket->move(buckets.last()->pos());buckets.append(bucket);bucket->setVerticalIndex(count()-1); // 已经添加了,下标索引要-1if (count())bucket->move(buckets.last()->geometry().topLeft());}else // 插入到中间{buckets.insert(index, bucket);for (int i = index; i < count(); i++)buckets.at(i)->setVerticalIndex(i);if (index+1 < count())bucket->move(buckets.at(index+1)->geometry().topLeft());else if (index > 0)bucket->move(buckets.at(index-1)->geometry().topLeft());}bucket->show();// 设置item的尺寸connect(bucket, &TimelineBucket::signalSizeHintChanged, this, [=](QSize size){if (!_adusting_buckets_size)adjustBucketsPositions(buckets.indexOf(bucket));});// 连接事件信号connect(bucket, &TimelineBucket::signalBucketWidgetPressed, this, [=]{ slotBucketWidgetToSelect(bucket); });connect(bucket, SIGNAL(signalTimeWidgetClicked(TimelineTimeLabel*)), this, SLOT(slotTimeWidgetClicked(TimelineTimeLabel*)));connect(bucket, SIGNAL(signalTextWidgetClicked(TimelineTextLabel*)), this, SLOT(slotTextWidgetClicked(TimelineTextLabel*)));connect(bucket, SIGNAL(signalTimeWidgetDoubleClicked(TimelineTimeLabel*)), this, SLOT(slotTimeWidgetDoubleClicked(TimelineTimeLabel*)));connect(bucket, SIGNAL(signalTextWidgetDoubleClicked(TimelineTextLabel*)), this, SLOT(slotTextWidgetDoubleClicked(TimelineTextLabel*)));connect(bucket, &TimelineBucket::signalDroppedAndMoved, this, [=](TimelineBucket* from_bucket) {slotDroppedAndMoved(from_bucket, bucket);});return bucket;
}void TimelineWidget::removeItem(int index)
{if (index < 0 || index >= count())return ;auto bucket = buckets.takeAt(index);selected_buckets.removeOne(bucket);bucket->deleteLater();adjustBucketsPositionsWithAnimation(index);
}void TimelineWidget::clearAll()
{while (buckets.size()){buckets.takeFirst()->deleteLater();}
}int TimelineWidget::count()
{return buckets.size();
}int TimelineWidget::indexOf(TimelineBucket *bucket) const
{return buckets.indexOf(bucket);
}TimelineBucket *TimelineWidget::at(int index) const
{if (index < 0 || index >= buckets.size())return nullptr;return buckets.at(index);
}void TimelineWidget::moveBucket(int from_index, int to_index)
{if (from_index == to_index) // 很可能发生的自己和自己交换return ;if (from_index < 0 || to_index < 0)return ;finishEditing();// 交换 bucketTimelineBucket* bucket = buckets.at(from_index);buckets.removeAt(from_index);if (from_index < to_index) // 下移{buckets.insert(to_index, bucket);for (int i = from_index; i <= to_index; i++)buckets.at(i)->setVerticalIndex(i);}else // 上移{buckets.insert(to_index, bucket);for (int i = from_index; i >= to_index; i--)buckets.at(i)->setVerticalIndex(i);}adjustBucketsPositionsWithAnimation(qMin(from_index, to_index));
}void TimelineWidget::selectAll()
{int left = horizontalScrollBar()->sliderPosition(),right = horizontalScrollBar()->sliderPosition() + horizontalScrollBar()->pageStep();bool odd = true;foreach (TimelineBucket* bucket, buckets) {if (odd)bucket->setPressPos(QPoint(left, bucket->height()/2));elsebucket->setPressPos(QPoint(right, bucket->height()/2));odd = !odd;bucket->setSelected(true);}selected_buckets = buckets;emit targetItemsChanged();
}void TimelineWidget::unselectAll()
{foreach (TimelineBucket* bucket, selected_buckets) {bucket->setSelected(false);}selected_buckets.clear();emit targetItemsChanged();
}void TimelineWidget::selectItem(TimelineBucket *bucket)
{bucket->setSelected(true);if (!selected_buckets.contains(bucket))selected_buckets.append(bucket);emit targetItemsChanged();
}void TimelineWidget::selectItems(QList<int> rows, bool clearBefore)
{if (clearBefore)unselectAll();foreach (auto row, rows){auto bucket = buckets.at(row);bucket->setSelected(true);if (!selected_buckets.contains(bucket))selected_buckets.append(bucket);}emit targetItemsChanged();
}void TimelineWidget::unselectItem(TimelineBucket *bucket)
{bucket->setSelected(false);selected_buckets.removeOne(bucket);emit targetItemsChanged();
}void TimelineWidget::setCurrentItem(int row, bool multi)
{if (!multi)unselectAll();selectItem(buckets.at(row));current_index = row;
}void TimelineWidget::setCurrentItem(TimelineBucket *bucket, bool multi)
{if (!multi)unselectAll();selectItem(bucket);current_index = buckets.indexOf(bucket);
}/*** 确保某个bucket可视*/
void TimelineWidget::scrollTo(int index)
{if (index == -1)index = current_index;if (index == -1)return ;auto bucket = buckets.at(index);int h = bucket->height();int top = bucket->pos().y();int bottom = bucket->geometry().bottom();if (top - h < verticalScrollBar()->sliderPosition()) // 在上面{verticalScrollBar()->setSliderPosition(top - h);}else if (bottom + h > verticalScrollBar()->sliderPosition() + verticalScrollBar()->pageStep()) // 在下面{verticalScrollBar()->setSliderPosition(bottom + h - verticalScrollBar()->pageStep());}
}QList<int> TimelineWidget::selectedIndexes(int delta) const
{int size = buckets.size();QList<int> indexes;for (int i = 0; i < size; i++)if (buckets.at(i)->isSelected())indexes << (i+delta);return indexes;
}void TimelineWidget::resetWidth()
{_width_need_adjust = true;
}/*** 调整某一个位置及后面的所有top*/
void TimelineWidget::adjustBucketsPositions(int start)
{int end = count();int top = (start-1) >= 0 ? buckets.at(start-1)->geometry().bottom() : 0;int max_width = 0;int current_width = center_widget->width();if (start > 0)max_width = center_widget->width();for (int i = start; i < end; i++){TimelineBucket* bucket = buckets.at(i);if (max_width < bucket->width())max_width = bucket->width();bucket->move(bucket->pos().x(), top);top += bucket->height();}_adusting_buckets_size = true;{if (max_width != current_width || _width_need_adjust){foreach (auto bucket, buckets){bucket->resize(max_width, bucket->height());}}int height = 0;if (buckets.size())height = top + buckets.last()->height();elseheight = 50;center_widget->resize(max_width, height);}_adusting_buckets_size = false;
}/*** 调整某一范围内 buckets 的位置* 并且包含位置移动动画*/
void TimelineWidget::adjustBucketsPositionsWithAnimation(int start, int end)
{if (end == -1)end = count();elseend++;int top = (start-1) >= 0 ? buckets.at(start-1)->geometry().bottom() : 0;int current_width = center_widget->width();int max_width = 0;if (start > 0)max_width = center_widget->width();for (int i = start; i < end; i++){TimelineBucket* bucket = buckets.at(i);if (max_width < bucket->width())max_width = bucket->width();if (top != bucket->pos().y()){QPropertyAnimation* ani = new QPropertyAnimation(bucket, "pos");ani->setStartValue(bucket->pos());ani->setEndValue(QPoint(bucket->pos().x(), top));ani->setDuration(300);ani->setEasingCurve(QEasingCurve::OutQuart);connect(ani, SIGNAL(finished()), ani, SLOT(deleteLater()));ani->start();}top += bucket->height();}// 这句会在启动时触发 signalSizeHintChanged,但是必须需要啊// _adusting_buckets_size = true;{if (max_width != current_width || _width_need_adjust){foreach (auto bucket, buckets){bucket->resize(max_width, bucket->height());}}int height = 0;if (buckets.size())height = top + buckets.last()->height();elseheight = 50;center_widget->resize(max_width, height);}// _adusting_buckets_size = false;
}/*** 从字符串中读取* @param string       带格式的字符串* @param time_format  获取时间正则表达式,以第一个括号确定(不要带有 ^ $ 标记!)* @param node_split   时间节点内分段格式* @param nodes_split  时间节点之间分段格式*/
void TimelineWidget::fromString(QString string, QString time_reg, QString node_split, QString nodes_split)
{QList<QString> times;QList<QStringList> textss;if (node_split == nodes_split) // 分段符一致,以每一段的时间标记为准{QString time, time_total;QStringList texts;QRegExp rx(time_reg);rx.setMinimal(true);QStringList lines = string.split(nodes_split, QString::SkipEmptyParts);for (int i = 0; i < lines.length(); i++){QString& line = lines[i];int pos = rx.indexIn(line);if (pos != -1) // 找到时间标记{// 添加上一个时间轴if (time != nullptr || texts.length() > 0){times.append(time);textss.append(texts);time = "";texts.clear();}time_total = rx.cap(0);time = rx.cap(1);// 删除行内标记QRegExp ex(time_total + "[\\s ]*");line.replace(ex, "");if (!line.trimmed().isEmpty()) // 这一段还有其他内容,继续便利i--;}else{texts.append(line.trimmed());}}if (time != nullptr || texts.length() > 0){times.append(time);textss.append(texts);time = "";texts.clear();}}else // 根据分割来{QStringList lines = string.split(nodes_split, QString::SkipEmptyParts);foreach (QString line, lines){QString time_total, time; // 带格式的时间字符串、纯时间字符串QStringList texts;QRegExp rx(time_reg);rx.setMinimal(true);int pos = rx.indexIn(line);if (pos != -1){time_total = rx.cap(0);time = rx.cap(1);// 删除时间标记QRegExp ex(time_total + "[\\s ]*");line.replace(ex, "");}texts = line.split(node_split, QString::SkipEmptyParts);for (int i = 0; i < texts.size(); i++){texts[i] = texts[i].trimmed();}times.append(time);textss.append(texts);}}QList<int> indexs;int c = count();for (int i = 0; i < times.size(); i++)indexs.append(c);timeline_undos->addCommand(indexs, times, textss);
}/*** 将时间轴转换成带分段格式的字符串* @param time_format 时间格式,以 %1 确定* @param para_split  同一时间节点内分段格式* @param line_split  时间节点之间的分段格式* @return 所有字符串*/
QString TimelineWidget::toString(QString time_format, QString para_split, QString line_split)
{QString result;foreach (auto bucket, buckets){if (!result.isEmpty())result += line_split;result += bucket->toString(time_format, para_split);}return result;
}void TimelineWidget::keyPressEvent(QKeyEvent *event)
{auto modifiers = event->modifiers();auto key = event->key();switch (key){case Qt::Key_Up:if (current_index > 0){if (modifiers == Qt::NoModifier) // 上移选中项{auto bucket = buckets.at(current_index-1);bucket->setPressPos(QPoint(qMin(bucket->width(), horizontalScrollBar()->pageStep()), bucket->height()));setCurrentItem(current_index-1);scrollTo();emit manualSelected();return ;}else if (modifiers == Qt::ShiftModifier) // 上移并选中/取消你{auto bucket = buckets.at(current_index);auto bucket_up = buckets.at(current_index-1);if (bucket->isSelected() && bucket_up->isSelected()){unselectItem(bucket);current_index--;}else{bucket_up->setPressPos(QPoint(qMin(bucket_up->width(), horizontalScrollBar()->pageStep()), bucket_up->height()));setCurrentItem(current_index-1, true);}scrollTo();emit manualSelected();return ;}}break;case Qt::Key_Down:if (current_index > -1 && current_index < count()-1){if (modifiers == Qt::NoModifier) // 下移选中项{auto bucket = buckets.at(current_index+1);bucket->setPressPos(QPoint(qMin(bucket->width(), horizontalScrollBar()->pageStep()), 0));setCurrentItem(current_index+1);scrollTo();emit manualSelected();return ;}else if (modifiers == Qt::ShiftModifier) // 下移并选中/取消{auto bucket = buckets.at(current_index);auto bucket_down = buckets.at(current_index+1);if (bucket->isSelected() && bucket_down->isSelected()){unselectItem(bucket);current_index++;}else{bucket_down->setPressPos(QPoint(qMin(bucket_down->width(), horizontalScrollBar()->pageStep()), 0));setCurrentItem(current_index+1, true);}scrollTo();emit manualSelected();return ;}}break;case Qt::Key_Home:if (current_index > 0 && modifiers == Qt::ShiftModifier){int index = current_index;while (index >= 0){setCurrentItem(index, true);index--;}scrollTo();emit manualSelected();return ;}break;case Qt::Key_End:if (current_index > -1 && modifiers == Qt::ShiftModifier){int index = current_index;while (index < count()){setCurrentItem(index, true);index++;}scrollTo();emit manualSelected();return ;}break;case Qt::Key_Delete:{int index = current_index;actionDeleteLine();// 删除键删除的需要继续保持选中状态if (index > -1 && index < count()) // 聚焦原来的同一个索引setCurrentItem(index);else if (index > 0 && index == count()) // 聚焦最后一个setCurrentItem(index-1);return ;}case Qt::Key_Insert:actionInsertAbove();return ;case Qt::Key_Escape:if (current_index > -1){if (selected_buckets.size() > 1)setCurrentItem(current_index);else if (selected_buckets.size())unselectItem(buckets.at(current_index));return ;}else{unselectAll();}break;case Qt::Key_A:if (modifiers == Qt::ControlModifier){selectAll();return ;}break;case Qt::Key_Z:if (modifiers == Qt::ControlModifier){timeline_undos->undoCommand();return ;}break;case Qt::Key_Y:if (modifiers == Qt::ControlModifier){timeline_undos->redoCommand();return ;}break;case Qt::Key_C:actionCopyText();return ;case Qt::Key_Tab:/*** 注意:如果要监听到 Tab 键,要禁止 Tab 切换* QWidget::setFocus(Qt::NoFocus)*/if (modifiers == Qt::NoModifier){actionAddText();}return ;case Qt::Key_Enter:case Qt::Key_Return:if (modifiers == Qt::ShiftModifier)actionInsertAbove();else if (modifiers == Qt::ControlModifier)actionAddLine();else if (modifiers == Qt::NoModifier)actionInsertUnder();return ;case Qt::Key_Space:actionEditText(0);return ;case Qt::Key_1:actionEditText(0);return ;case Qt::Key_2:actionEditText(1);return ;case Qt::Key_3:actionEditText(2);return ;case Qt::Key_4:actionEditText(3);return ;case Qt::Key_5:actionEditText(4);return ;case Qt::Key_6:actionEditText(5);return ;case Qt::Key_7:actionEditText(6);return ;case Qt::Key_8:actionEditText(7);return ;case Qt::Key_9:actionEditText(8);return ;case Qt::Key_0:case Qt::Key_QuoteLeft: // 反撇号actionEditTime();return ;case Qt::Key_Apostrophe: // 这是单引号……break;}QScrollArea::keyPressEvent(event);
}TimelineBucket *TimelineWidget::createItemWidget(QString time, QStringList texts)
{TimelineBucket* bucket = new TimelineBucket(center_widget);bucket->setTime(time);bucket->setText(texts);connect(bucket, SIGNAL(signalBucketContentsChanged()), this, SIGNAL(targetItemsChanged()));return bucket;
}void TimelineWidget::updateUI()
{QString style = "#TimelineTimeLabel { background:white; border: 1px solid orange; border-radius: 5px; padding: 10px; }""#TimelineTextLabel { background:white; border: 1px solid blue; border-radius: 5px; padding: 10px; }""#TimelineEdit { background:white; border: 1px solid transparent; border-radius: 5px; padding: 5px; margin: 1px; margin-left: 4px;}";setStyleSheet(style);
}void TimelineWidget::slotBucketWidgetToSelect(TimelineBucket *bucket)
{finishEditing();if (QApplication::keyboardModifiers() == Qt::NoModifier) // 没有修饰符,单选{setCurrentItem(bucket);}else if (QApplication::keyboardModifiers() == Qt::ControlModifier) // 按下 ctrl{if (!bucket->isSelected())setCurrentItem(bucket, true);elseunselectItem(bucket);}else if (QApplication::keyboardModifiers() == Qt::ShiftModifier) // 按下 shift{int prev = current_index; // 上次按下的int curr = buckets.indexOf(bucket);if (prev != -1){if (prev < curr){// 判断是否已经全选bool has_unselect = false;for (int i = prev; i <= curr; i++){if (!buckets.at(i)->isSelected()){has_unselect = true;break;}}// 再次遍历,如果有没有选择的,则选择;否则取消选择for (int i = prev; i <= curr; i++){TimelineBucket* bucket = buckets[i];if (bucket->isSelected() != has_unselect){selected_buckets.append(bucket);bucket->setSelected(has_unselect);}}}else if (prev > curr){bool has_unselect = false;for (int i = prev; i >= curr; i--){if (!buckets.at(i)->isSelected()){has_unselect = true;break;}}for (int i = prev; i >= curr; i--){TimelineBucket* bucket = buckets[i];if (bucket->isSelected() != has_unselect){selected_buckets.append(bucket);bucket->setSelected(has_unselect);}}}}current_index = curr;}emit manualSelected();
}void TimelineWidget::slotTimeWidgetClicked(TimelineTimeLabel *label)
{}void TimelineWidget::slotTextWidgetClicked(TimelineTextLabel *label)
{}void TimelineWidget::slotTimeWidgetDoubleClicked(TimelineTimeLabel *label)
{QTimer::singleShot(0, [=]{editing_bucket = buckets.at(current_index);editing_label = label;edit->move(label->pos() + label->parentWidget()->pos());edit->setPlainText(label->text());edit->resize(label->size());edit->setOriginText(label->text());edit->show();edit->raise();edit->setFocus();edit->selectAll();});
}void TimelineWidget::slotTextWidgetDoubleClicked(TimelineTextLabel *label)
{QTimer::singleShot(0, [=]{editing_bucket = buckets.at(current_index);editing_label = label;edit->move(label->pos() + label->parentWidget()->pos());edit->setPlainText(label->text());edit->resize(label->size());edit->setOriginText(label->text());edit->show();edit->raise();edit->setFocus();edit->selectAll();});
}void TimelineWidget::slotMenuShowed(const QPoint &pos)
{QMenu* menu = new QMenu("菜单", this);QAction* add_text_action = new QAction("添加文字节点", this);QAction* add_line_action = new QAction("添加新行", this);QAction* insert_above_action = new QAction("上方插入行", this);QAction* insert_under_action = new QAction("下方插入行", this);QAction* delete_line_action = new QAction("删除行", this);QAction* copy_text_action = new QAction("复制文字", this);QAction* paste_action = new QAction("剪贴板导入", this);menu->addAction(add_text_action);menu->addAction(add_line_action);menu->addAction(insert_above_action);menu->addAction(insert_under_action);menu->addAction(delete_line_action);menu->addSeparator();menu->addAction(copy_text_action);menu->addAction(paste_action);if (current_index == -1){add_text_action->setEnabled(false);insert_above_action->setEnabled(false);insert_under_action->setEnabled(false);delete_line_action->setEnabled(false);copy_text_action->setEnabled(false);}// 设置事件connect(add_text_action, SIGNAL(triggered()), this, SLOT(actionAddText()));connect(add_line_action, SIGNAL(triggered()), this, SLOT(actionAddLine()));connect(insert_above_action, SIGNAL(triggered()), this, SLOT(actionInsertAbove()));connect(insert_under_action, SIGNAL(triggered()), this, SLOT(actionInsertUnder()));connect(delete_line_action, SIGNAL(triggered()), this, SLOT(actionDeleteLine()));connect(copy_text_action, SIGNAL(triggered()), this, SLOT(actionCopyText()));connect(paste_action, SIGNAL(triggered()), this, SLOT(actionPaste()));menu->exec(QCursor::pos());
}void TimelineWidget::slotDroppedAndMoved(TimelineBucket *from, TimelineBucket *to)
{int from_index = buckets.indexOf(from);int to_index = buckets.indexOf(to);timeline_undos->moveCommand(from_index, to_index);
}void TimelineWidget::slotEditChanged()
{}/*** 编辑某一个节点* @param row 时间行* @param col 改行第几项。0为时间,1开始为文字*/
void TimelineWidget::slotEdit(int row, int col)
{if (row < 0 || row >= buckets.size())return;auto bucket = buckets.at(row);bucket->edit(col);
}/*** 准备进行其他操作时,如果正在编辑,则结束编辑*/
void TimelineWidget::finishEditing()
{edit->finishIfEditing();
}void TimelineWidget::actionAddText()
{QList<int> bucket_indexes = selectedIndexes();QList<QList<int>> texts_indexes;foreach (auto bucket_index, bucket_indexes){texts_indexes << QList<int>{buckets.at(bucket_index)->count()};}timeline_undos->addCommand(bucket_indexes, texts_indexes);if (bucket_indexes.size() == 1){// 等待动画结束,显示编辑框QTimer::singleShot(300, [=]{slotEdit(bucket_indexes.first(), buckets.at(bucket_indexes.first())->count());});}
}void TimelineWidget::actionAddTextLeft()
{}void TimelineWidget::actionAddTextRight()
{}void TimelineWidget::actionEditTime()
{if (current_index == -1)return ;auto bucket = at(current_index);slotTimeWidgetDoubleClicked(bucket->timeLabel());
}void TimelineWidget::actionEditText(int index)
{if (current_index == -1)return ;auto bucket = at(current_index);if (bucket->count() <= index)return ;slotTextWidgetDoubleClicked(bucket->at(index));
}void TimelineWidget::actionAddLine()
{timeline_undos->addCommand(count());setCurrentItem(count()-1);scrollTo();
}void TimelineWidget::actionInsertAbove()
{QList<int> indexes = selectedIndexes();timeline_undos->addCommand(indexes);unselectAll();int cumu = 0;for (int i = 0; i < indexes.count(); i++){auto bucket = buckets.at(indexes.at(i)+cumu);bucket->setSelected(true);selected_buckets.append(bucket);cumu++;}
}void TimelineWidget::actionInsertUnder()
{QList<int> indexes = selectedIndexes(1);timeline_undos->addCommand(indexes);unselectAll();int cumu = 0;for (int i = 0; i < indexes.count(); i++){auto bucket = buckets.at(indexes.at(i)+cumu);bucket->setSelected(true);selected_buckets.append(bucket);cumu++;}
}void TimelineWidget::actionDeleteLine()
{finishEditing();QList<int> indexes = selectedIndexes();timeline_undos->deleteCommand(indexes);selected_buckets.clear();current_index = -1;
}void TimelineWidget::actionCopyText()
{QString result;foreach (auto bucket, buckets){if (bucket->isSelected()){if (!result.isEmpty())result += "\n";result += bucket->toString();}}QApplication::clipboard()->setText(result);
}void TimelineWidget::actionPaste()
{QString text = QApplication::clipboard()->text();if (text.isEmpty())return ;int c = this->count();fromString(text);int c2 = this->count();if (c == c2) // 没有变化return ;unselectAll();for (int i = c; i < c2; i++)selectItem(at(i));scrollTo(c2-1);scrollTo(c);
}

源码下载

这篇关于《QT实用小工具·六十》Qt 多列时间轴控件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/969534

相关文章

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

超强的截图工具:PixPin

你是否还在为寻找一款功能强大、操作简便的截图工具而烦恼?市面上那么多工具,常常让人无从选择。今天,想给大家安利一款神器——PixPin,一款真正解放双手的截图工具。 想象一下,你只需要按下快捷键就能轻松完成多种截图任务,还能快速编辑、标注甚至保存多种格式的图片。这款工具能满足这些需求吗? PixPin不仅支持全屏、窗口、区域截图等基础功能,它还可以进行延时截图,让你捕捉到每个关键画面。不仅如此

【QT】基础入门学习

文章目录 浅析Qt应用程序的主函数使用qDebug()函数常用快捷键Qt 编码风格信号槽连接模型实现方案 信号和槽的工作机制Qt对象树机制 浅析Qt应用程序的主函数 #include "mywindow.h"#include <QApplication>// 程序的入口int main(int argc, char *argv[]){// argc是命令行参数个数,argv是

lvgl8.3.6 控件垂直布局 label控件在image控件的下方显示

在使用 LVGL 8.3.6 创建一个垂直布局,其中 label 控件位于 image 控件下方,你可以使用 lv_obj_set_flex_flow 来设置布局为垂直,并确保 label 控件在 image 控件后添加。这里是如何步骤性地实现它的一个基本示例: 创建父容器:首先创建一个容器对象,该对象将作为布局的基础。设置容器为垂直布局:使用 lv_obj_set_flex_flow 设置容器

PR曲线——一个更敏感的性能评估工具

在不均衡数据集的情况下,精确率-召回率(Precision-Recall, PR)曲线是一种非常有用的工具,因为它提供了比传统的ROC曲线更准确的性能评估。以下是PR曲线在不均衡数据情况下的一些作用: 关注少数类:在不均衡数据集中,少数类的样本数量远少于多数类。PR曲线通过关注少数类(通常是正类)的性能来弥补这一点,因为它直接评估模型在识别正类方面的能力。 精确率与召回率的平衡:精确率(Pr

Python QT实现A-star寻路算法

目录 1、界面使用方法 2、注意事项 3、补充说明 用Qt5搭建一个图形化测试寻路算法的测试环境。 1、界面使用方法 设定起点: 鼠标左键双击,设定红色的起点。左键双击设定起点,用红色标记。 设定终点: 鼠标右键双击,设定蓝色的终点。右键双击设定终点,用蓝色标记。 设置障碍点: 鼠标左键或者右键按着不放,拖动可以设置黑色的障碍点。按住左键或右键并拖动,设置一系列黑色障碍点

husky 工具配置代码检查工作流:提交代码至仓库前做代码检查

提示:这篇博客以我前两篇博客作为先修知识,请大家先去看看我前两篇博客 博客指路:前端 ESlint 代码规范及修复代码规范错误-CSDN博客前端 Vue3 项目开发—— ESLint & prettier 配置代码风格-CSDN博客 husky 工具配置代码检查工作流的作用 在工作中,我们经常需要将写好的代码提交至代码仓库 但是由于程序员疏忽而将不规范的代码提交至仓库,显然是不合理的 所

10个好用的AI写作工具【亲测免费】

1. 光速写作 传送入口:http://u3v.cn/6hXWYa AI打工神器,一键生成文章&ppt 2. 讯飞写作 传送入口:http://m6z.cn/5ODiSw 3. 讯飞绘文 传送入口:https://turbodesk.xfyun.cn/?channelid=gj3 4. AI排版助手 传送入口:http://m6z.cn/6ppnPn 5. Kim