新二维码/条形码编辑逻辑

This commit is contained in:
2026-05-06 10:34:48 +08:00
parent a06c926162
commit a9e99f4425
76 changed files with 12679 additions and 12744 deletions
+225 -3
View File
@@ -5,6 +5,7 @@
#include "CounterEditor.h"
#include <QDebug>
#include <QKeyEvent>
#include "DlgQrcodeFormat.h"
// 使用SysPara的相关参数构造CounterEditorVM
CounterEditorVM makeCounterVM(SysPara* sysPara, COUNTER_TYPE type, int digitalNum)
@@ -53,6 +54,8 @@ BarcodeEditor::BarcodeEditor(QWidget *parent) :
m_vm.dateViewModel = DateItemModel::createDefaultReplaceModel();
m_dateReplaceEditor->setDateModel(m_vm.dateViewModel);
connect(ui->cbBoxType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BarcodeEditor::onTypeChanged);
connect(&m_barcodeFormatEditor, &DlgBarcodeFormat::accepted, this, &BarcodeEditor::onBarcodeFormatConfirmed);
connect(&m_qrcodeFormatEditor, &DlgQrcodeFormat::accepted, this, &BarcodeEditor::onQrcodeFormatConfirmed);
}
@@ -86,6 +89,21 @@ void BarcodeEditor::bindViewModel(const BarcodeViewModel& vm)
{
m_vm = vm;
// 通用设置视图模型
auto b = ui->widGeneralSettings->blockSignals(true);
ui->widGeneralSettings->setViewModel(vm.generalSettingsVM);
ui->widGeneralSettings->blockSignals(b);
// 内容模版
b = ui->lineEditContent->blockSignals(true);
ui->lineEditContent->setText(m_vm.contentPattern);
ui->lineEditContent->blockSignals(b);
// 类型选择框
b = ui->cbBoxType->blockSignals(true);
ui->cbBoxType->setCurrentIndex(m_vm.type);
ui->cbBoxType->blockSignals(b);
// 缺失的代替表用默认值补齐
DateItemModel& dateVm = m_vm.dateViewModel;
if (dateVm.dataMap.isEmpty() || dateVm.daysMap.isEmpty() || dateVm.monthsMap.isEmpty()
@@ -154,8 +172,15 @@ void BarcodeEditor::bindViewModel(const BarcodeViewModel& vm)
BarcodeViewModel BarcodeEditor::viewModel()
{
m_vm.contentPattern = ui->lineEditContent->text();
m_vm.type = (BarcodeType) (ui->cbBoxType->currentIndex());
// 从子编辑器拉取最新值回写到 m_vm
// 通用设置
m_vm.generalSettingsVM = ui->widGeneralSettings->viewModel();
// 日期设置,如果存在 {t} 变量
if(m_vm.contentPattern.contains("{t}")) {
DateFormatEditorVM fmt = m_dateFormatEditor->viewModel();
m_vm.dateViewModel.datetimePattern = fmt.pattern;
@@ -182,6 +207,7 @@ BarcodeViewModel BarcodeEditor::viewModel()
m_vm.dateViewModel.hoursMap = rep.hoursMap;
}
// 计数器设置,如果存在 {c} 变量
if(m_vm.contentPattern.contains("{c}")) {
CounterEditorVM cnt = m_counterEditor->viewModel();
m_vm.counterViewModel.counterType = cnt.counterType;
@@ -191,6 +217,58 @@ BarcodeViewModel BarcodeEditor::viewModel()
return m_vm;
}
void BarcodeEditor::restoreViewModel()
{
m_vm.generalSettingsVM = ItemGeneralSettingsVM();
m_vm.contentPattern = "";
m_vm.counterViewModel = CounterViewModel();
m_vm.dateViewModel = DateItemModel::createDefaultReplaceModel();
m_vm.type = BARCODE_TYPE_BARCODE;
m_vm.format.setValue(DlgBarcodeFormatVM());
bindViewModel(m_vm);
}
/**
* @brief 逻辑基本类似 CounterVarSet::applyUIPara
*
*/
void BarcodeEditor::applyCounterConfigToSysPara()
{
if(!m_vm.contentPattern.contains("{c}"))
return;
CounterEditorVM cnt = m_counterEditor->viewModel();
if(cnt.counterType == COUNTER_TYPE_1) {
m_sysPara->para.cnt1StartValue = cnt.counter1.startValue;
m_sysPara->para.cnt1EndValue = cnt.counter1.endValue;
m_sysPara->para.cnt1RepeateValue = cnt.counter1.repeatValue;
m_sysPara->para.cnt1StepValue = cnt.counter1.stepValue;
m_sysPara->para.cnt1CurrValue = cnt.counter1.currValue;
m_sysPara->status.cnt1CurrentValueInitDoneFlag = !cnt.counter1.editCurrValue;
if(cnt.counter1.eleEyeIncEn)
m_sysPara->para.eleEyeClearCntInSynMode = (m_sysPara->para.eleEyeClearCntInSynMode | 0x10); // CNT1 +1
else
m_sysPara->para.eleEyeClearCntInSynMode = (m_sysPara->para.eleEyeClearCntInSynMode & 0xef); // CNT1 = current
}
else if(cnt.counterType == COUNTER_TYPE_2) {
m_sysPara->para.cnt2StartValue = cnt.counter2.startValue;
m_sysPara->para.cnt2EndValue = cnt.counter2.endValue;
m_sysPara->para.cnt2RepeateValue = cnt.counter2.repeatValue;
m_sysPara->para.cnt2StepValue = cnt.counter2.stepValue;
m_sysPara->para.cnt2CurrValue = cnt.counter2.currValue;
m_sysPara->status.cnt2CurrentValueInitDoneFlag = !cnt.counter2.editCurrValue;
if(cnt.counter2.eleEyeIncEn)
m_sysPara->para.eleEyeClearCntInSynMode = (m_sysPara->para.eleEyeClearCntInSynMode | 0x20); // CNT2 +1
else
m_sysPara->para.eleEyeClearCntInSynMode = (m_sysPara->para.eleEyeClearCntInSynMode & 0xdf); // CNT2 = current
}
m_sysPara->devOp->writeFpgaCalandar(0);
}
void BarcodeEditor::onBtnFormatClicked()
{
if(m_vm.type == BARCODE_TYPE_BARCODE) {
@@ -228,12 +306,15 @@ void BarcodeEditor::onBtnCounterClicked()
void BarcodeEditor::onCheckBtnClicked()
{
m_vm = viewModel();
applyCounterConfigToSysPara();
emit accepted();
hide();
}
void BarcodeEditor::onCloseBtnClicked()
{
emit rejected();
hide();
}
void BarcodeEditor::onBarcodeFormatConfirmed()
@@ -256,6 +337,19 @@ void BarcodeEditor::onQrcodeFormatConfirmed()
ui->cbBoxType->blockSignals(b);
}
void BarcodeEditor::onTypeChanged(int i)
{
if(m_vm.type == i)
return;
m_vm.type = (BarcodeType)(i);
if(m_vm.type == BARCODE_TYPE_BARCODE) {
m_vm.format.setValue(DlgBarcodeFormatVM());
}
else
m_vm.format.setValue(DlgQrcodeFormatVM());
}
bool BarcodeEditor::eventFilter(QObject *obj, QEvent *event)
{
if(obj == ui->lineEditContent) {
@@ -269,9 +363,17 @@ bool BarcodeEditor::eventFilter(QObject *obj, QEvent *event)
}
// 碰到 {t} 或 {c},直接删除
QString varText = text.mid(cursor - 3, 3);
if(varText == "{t}" || varText == "{c}") {
ui->lineEditContent->setText(text.remove(cursor - 3, 3));
auto datePatternPos = text.indexOf("{t}");
auto counterPatternPos = text.indexOf("{c}");
if(datePatternPos >= 0 || counterPatternPos >= 0) {
if(datePatternPos < cursor && cursor <= datePatternPos + 3) {
ui->lineEditContent->setText(text.remove(datePatternPos, 3));
return true;
}
else if(counterPatternPos < cursor && cursor <= counterPatternPos + 3) {
ui->lineEditContent->setText(text.remove(counterPatternPos, 3));
return true;
}
}
}
}
@@ -342,3 +444,123 @@ void BarcodeEditor::onContentChanged(QString content)
m_vm.contentPattern = content;
}
void BarcodeViewModel::saveToXml(const BarcodeViewModel &vm, QXmlStreamAttributes &attrs)
{
attrs.append("barcodeContentPattern", vm.contentPattern);
attrs.append("barcodeType", QString::number(int(vm.type)));
if(vm.type == BARCODE_TYPE_BARCODE) {
DlgBarcodeFormatVM barcodeFormat = vm.format.value<DlgBarcodeFormatVM>();
attrs.append("barcodeFormat", barcodeFormat.type);
attrs.append("barcodeHeight", QString::number(barcodeFormat.height));
attrs.append("barcodeWidth", QString::number(barcodeFormat.width));
attrs.append("barcodeScale", QString::number(barcodeFormat.scale));
attrs.append("barcodeInkType", QString::number(int(barcodeFormat.inkType)));
attrs.append("barcodeTxtVisible", barcodeFormat.txtVisible ? "true" : "false");
}
else {
DlgQrcodeFormatVM qrcodeFormat = vm.format.value<DlgQrcodeFormatVM>();
attrs.append("qrcodeFormat", QString::number(qrcodeFormat.type));
attrs.append("qrcodeSize", QString::number(qrcodeFormat.size));
attrs.append("qrcodeLevel", QString::number(int(qrcodeFormat.level)));
attrs.append("qrcodeInkType", QString::number(int(qrcodeFormat.inkType)));
}
if(vm.contentPattern.contains("{t}")) {
DateItemModel::saveToXml(vm.dateViewModel, attrs);
}
if(vm.contentPattern.contains("{c}")) {
attrs.append("counterType", QString::number(vm.counterViewModel.counterType));
attrs.append("counterDigitalNum", QString::number(vm.counterViewModel.digitalNum));
}
}
BarcodeViewModel BarcodeViewModel::loadFromXml(const QXmlStreamAttributes &attrs)
{
BarcodeViewModel vm;
vm.contentPattern = attrs.value("barcodeContentPattern").toString();
vm.type = static_cast<BarcodeType>(attrs.value("barcodeType").toInt());
if(vm.type == BARCODE_TYPE_BARCODE) {
DlgBarcodeFormatVM barcodeFormat;
barcodeFormat.type = attrs.value("barcodeFormat").toString();
barcodeFormat.height = attrs.value("barcodeHeight").toInt();
barcodeFormat.width = attrs.value("barcodeWidth").toInt();
barcodeFormat.scale = attrs.value("barcodeScale").toInt();
barcodeFormat.inkType = static_cast<InkType>(attrs.value("barcodeInkType").toInt());
barcodeFormat.txtVisible = attrs.value("barcodeTxtVisible").toString() == "true";
vm.format.setValue(barcodeFormat);
}
else {
DlgQrcodeFormatVM qrcodeFormat;
qrcodeFormat.type = static_cast<QRCodeType>(attrs.value("qrcodeFormat").toInt());
qrcodeFormat.size = static_cast<QRCodeSize>(attrs.value("qrcodeSize").toInt());
qrcodeFormat.level = static_cast<QRCodeLevel>(attrs.value("qrcodeLevel").toInt());
qrcodeFormat.inkType = static_cast<InkType>(attrs.value("qrcodeInkType").toInt());
vm.format.setValue(qrcodeFormat);
}
if(vm.contentPattern.contains("{t}")) {
DateItemModel::loadFromXml(vm.dateViewModel, attrs);
}
if(vm.contentPattern.contains("{c}")) {
vm.counterViewModel.counterType = static_cast<COUNTER_TYPE>(attrs.value("counterType").toInt());
vm.counterViewModel.digitalNum = attrs.value("counterDigitalNum").toInt();
}
return vm;
}
QString BarcodeViewModel::toString(const BarcodeViewModel &vm)
{
QString str;
str += QString("type: %1\r\n").arg(int(vm.type));
str += QString("contentPattern: \"%1\"\r\n").arg(vm.contentPattern);
// counterViewModel
str += QString("counterViewModel: \r\n");
str += QString("\t counter type: %1\r\n").arg(vm.counterViewModel.counterType);
str += QString("\t digital number: %1\r\n").arg(vm.counterViewModel.digitalNum);
str += QString("\t letter space: %1\r\n").arg(vm.counterViewModel.letterSpace);
// dateViewModel
str += QString("dateViewModel: \r\n");
str += QString("\t pattern: \"%1\"\r\n").arg(vm.dateViewModel.datetimePattern);
int expirationValue = 0;
int expirationType = 0;
if(vm.dateViewModel.daysOffset > 0) {
expirationValue = vm.dateViewModel.daysOffset;
expirationType = EXPIRATION_TYPE_DAY;
}
else if(vm.dateViewModel.monthsOffset > 0) {
expirationValue = vm.dateViewModel.monthsOffset;
expirationType = EXPIRATION_TYPE_MONTH;
}
str += QString("\t expirationValue: %1\r\n").arg(expirationValue);
str += QString("\t expirationType: %1\r\n").arg(expirationType);
// format
str += QString("format: \n");
if(vm.type == BARCODE_TYPE_BARCODE) {
DlgBarcodeFormatVM barcodeFormat = vm.format.value<DlgBarcodeFormatVM>();
str += QString("\t type: \"%1\"\r\n").arg(barcodeFormat.type);
str += QString("\t height: %1\r\n").arg(barcodeFormat.height);
str += QString("\t width: %1\r\n").arg(barcodeFormat.width);
str += QString("\t scale: %1\r\n").arg(barcodeFormat.scale);
str += QString("\t inkType: %1\r\n").arg(int(barcodeFormat.inkType));
str += QString("\t txtVisible: %1\r\n").arg(barcodeFormat.txtVisible ? "true" : "false");
}
else {
DlgQrcodeFormatVM qrcodeFormat = vm.format.value<DlgQrcodeFormatVM>();
str += QString("\t type: %1\r\n").arg(int(qrcodeFormat.type));
str += QString("\t size: %1\r\n").arg(int(qrcodeFormat.size));
str += QString("\t level: %1\r\n").arg(int(qrcodeFormat.level));
str += QString("\t inkType: %1\r\n").arg(int(qrcodeFormat.inkType));
}
return str;
}
+58 -5
View File
@@ -8,6 +8,7 @@
#include "dlgQrcodeFormat.h"
#include "DlgBarcodeFormat.h"
#include <QVariant>
#include "ItemGeneralSettings.h"
class DateFormatEditor;
class DateReplaceEditor;
@@ -25,11 +26,57 @@ enum BarcodeType {
* 仅当 contentPattern 含有相应变量时,counterViewModel / dateViewModel 字段才有意义。
*/
struct BarcodeViewModel {
QString contentPattern; // 条形码内容模版,例如 "ABC{t}{c1}"
CounterViewModel counterViewModel; // 计数器视图模型
DateItemModel dateViewModel; // 日期视图模型
BarcodeType type = BARCODE_TYPE_BARCODE;
QVariant format;
ItemGeneralSettingsVM generalSettingsVM; // 通用设置视图模型
QString contentPattern; // 条形码内容模版,例如 "ABC{t}{c1}"
CounterViewModel counterViewModel; // 计数器视图模型
DateItemModel dateViewModel; // 日期视图模型
BarcodeType type = BARCODE_TYPE_BARCODE; // 条形码 或 二维码
QVariant format; // 条形码/二维码 格式设置,如果是条形码,则实际类型为 DlgBarcodeFormatVM,如果是二维码,则实际类型为 DlgQrcodeFormatVM
std::function<QImage()> imageGenerator; // 条形码/二维码 图片生成函数
/**
* @brief 将vm保存到xml属性集
*
* @param vm 条码视图模型
* @param attrs xml属性集
*/
static void saveToXml(const BarcodeViewModel& vm, QXmlStreamAttributes& attrs);
/**
* @brief 从xml属性集加载条码视图模型
*
* @param attrs xml属性集
* @return BarcodeViewModel 条码视图模型
*/
static BarcodeViewModel loadFromXml(const QXmlStreamAttributes& attrs);
/**
* @brief
*
* @param vm 条码视图模型
* @return QString 字符串表示
*
* @example
* type: 0
* contentPattern: ABC{t}{c1}
* counterViewModel:
* \t counter type: 7
* \t digital number: 2
* \t letter space: 2
* dateViewModel:
* \t pattern: "yyyy-MM-dd HH:mm:ss"
* \t expirationValue: 0
* \t expirationType: 0
* format:
* \t type: "BARCODE_128"
* \t height: 9
* \t width: 36
* \t scale: 1
* \t inkType: 0
* \t txtVisible: false
*/
static QString toString(const BarcodeViewModel& vm);
};
Q_DECLARE_METATYPE(BarcodeViewModel);
@@ -75,6 +122,10 @@ public:
*/
BarcodeViewModel viewModel();
void restoreViewModel();
void applyCounterConfigToSysPara();
private slots:
/**
@@ -126,6 +177,8 @@ private slots:
*/
void onQrcodeFormatConfirmed();
void onTypeChanged(int i);
protected:
virtual bool eventFilter(QObject* obj, QEvent* event) override;
+20 -2
View File
@@ -126,9 +126,15 @@ QSpinBox::down-arrow {
<x>520</x>
<y>540</y>
<width>101</width>
<height>50</height>
<height>40</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>40</height>
</size>
</property>
<property name="text">
<string>✓</string>
</property>
@@ -139,9 +145,21 @@ QSpinBox::down-arrow {
<x>640</x>
<y>540</y>
<width>101</width>
<height>50</height>
<height>40</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>40</height>
</size>
</property>
<property name="text">
<string>✕</string>
</property>
+1 -10
View File
@@ -257,15 +257,6 @@
</property>
</widget>
</widget>
<tabstops>
<tabstop>BarCodeType</tabstop>
<tabstop>comboBoxInkColor</tabstop>
<tabstop>BarCodeHeigh</tabstop>
<tabstop>BarCodeDot</tabstop>
<tabstop>BarCodeScale</tabstop>
<tabstop>checkBoxAddText</tabstop>
<tabstop>checkBtn</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
@@ -280,7 +271,7 @@
</hint>
<hint type="destinationlabel">
<x>417</x>
<y>379</y>
<y>460</y>
</hint>
</hints>
</connection>
+1
View File
@@ -6,6 +6,7 @@ DlgQrcodeFormat::DlgQrcodeFormat(QWidget *parent) :
ui(new Ui::DlgQrcodeFormat)
{
ui->setupUi(this);
setViewModel(m_vm);
}
DlgQrcodeFormat::~DlgQrcodeFormat()
+1 -1
View File
@@ -31,7 +31,7 @@ enum QRCodeSize {
};
struct DlgQrcodeFormatVM {
QRCodeType type = QR_CODE_TYPE_DATA_MATRIX;
QRCodeType type = QR_CODE_TYPE_QRCODE;
QRCodeLevel level = QR_CODE_LEVEL_L;
QRCodeSize size = QR_CODE_SIZE_DEFAULT;
InkType inkType = INK_TYPE_BLACK;
+454 -4
View File
@@ -1,4 +1,312 @@
#include "ItemCommon.h"
#include "DlgQrcodeFormat.h"
#include "DlgBarcodeFormat.h"
#include <QtQREnCode/qrencode.h>
#include "Matrix/dm200.h"
#include "BarCode/barcode.h"
#include <QPainter>
#include <cstring>
/**
* @brief 将libqrencode的QRcode结构渲染成QImage
*
* 避免依赖文件系统或PostScript输出,直接在内存中生成可用于UI显示或打印的图像。
* 使用2D像素数组而非向量,支持任意缩放因子。
*/
static QImage renderQRCode(const QRcode *qrcode, int moduleSize = 4, QRgb foreground = 0xff000000, QRgb background = 0xffffffff)
{
if (!qrcode || qrcode->width <= 0) {
return QImage();
}
int margin = 1;
int imageSize = (qrcode->width + margin * 2) * moduleSize;
QImage image(imageSize, imageSize, QImage::Format_ARGB32);
image.fill(background);
const unsigned char *data = qrcode->data;
for (int y = 0; y < qrcode->width; ++y) {
for (int x = 0; x < qrcode->width; ++x) {
bool moduleOn = (data[y * qrcode->width + x] & 0x01) != 0;
QRgb color = moduleOn ? foreground : background;
int px = (margin + x) * moduleSize;
int py = (margin + y) * moduleSize;
for (int dy = 0; dy < moduleSize; ++dy) {
for (int dx = 0; dx < moduleSize; ++dx) {
image.setPixel(px + dx, py + dy, color);
}
}
}
}
return image;
}
/**
* @brief 将DataMatrix位图网格渲染成QImage
*
* iec16022ecc200返回原始位数组,需转换为可视化图像以供UI或打印使用。
* 与renderQRCode采用相同的像素化策略,确保两种编码方式的输出一致性。
*/
static QImage renderDataMatrix(const unsigned char *grid, int cols, int rows, int moduleSize = 1, QRgb foreground = 0xff000000, QRgb background = 0xffffffff)
{
if (!grid || cols <= 0 || rows <= 0) {
return QImage();
}
int width = cols * moduleSize;
int height = rows * moduleSize;
QImage image(width, height, QImage::Format_ARGB32);
image.fill(background);
for (int y = 0; y < rows; ++y) {
for (int x = 0; x < cols; ++x) {
bool bit = (grid[y * cols + x] & 0x01) != 0;
QRgb color = bit ? foreground : background;
int px = x * moduleSize;
int py = y * moduleSize;
for (int dy = 0; dy < moduleSize; ++dy) {
for (int dx = 0; dx < moduleSize; ++dx) {
image.setPixel(px + dx, py + dy, color);
}
}
}
}
return image;
}
static QRecLevel qrCodeLevelFromEnum(QRCodeLevel level)
{
switch (level) {
case QR_CODE_LEVEL_M: return QR_ECLEVEL_M;
case QR_CODE_LEVEL_Q: return QR_ECLEVEL_Q;
case QR_CODE_LEVEL_H: return QR_ECLEVEL_H;
default: return QR_ECLEVEL_L;
}
}
/**
* @brief 根据格式配置生成二维码或DataMatrix图像
*
* 支持两种编码方式:
* - DataMatrix:适合小容量数据和有限空间环境(如工业标签)
* - QR Code:高容量、高可靠性,广泛应用于商业和物流
*
* 直接在内存中渲染,避免对dlgQrCode或外部库的依赖。
* 颜色反转机制(INK_TYPE_WHITE)用于支持黑底白码的特殊打印场景。
*/
static QImage generateQRCodeImage(const QString &content, const DlgQrcodeFormatVM &vm)
{
if (content.isEmpty()) {
return QImage();
}
QRecLevel level = qrCodeLevelFromEnum(vm.level);
// 适配黑底白码场景(逆色):打印机某些模式下需要反转前景/背景色
QRgb foreground = vm.inkType == INK_TYPE_WHITE ? 0x00ffffff : 0xff000000;
QRgb background = vm.inkType == INK_TYPE_WHITE ? 0xff000000 : 0x00ffffff;
QByteArray bytes = content.toUtf8();
// DataMatrix:调用者指定尺寸约束(sizeType),库返回实际分配的网格
if (vm.type == QR_CODE_TYPE_DATA_MATRIX) {
short cols = 0;
short rows = 0;
short sizeType = static_cast<short>(vm.size);
// iec16022ecc200会根据sizeType调整输出尺寸,支持灵活的条码宽高比
unsigned char *grid = iec16022ecc200(&cols, &rows, reinterpret_cast<unsigned char *>(bytes.data()), &sizeType);
if (!grid || cols <= 0 || rows <= 0) {
if (grid) {
free(grid);
}
return QImage();
}
QImage image = renderDataMatrix(grid, cols, rows, 1, foreground, background);
free(grid);
return image;
}
// QR Code:版本0表示自动选择版本(1-40),确保内容总是被编码
QRcode *qrcode = QRcode_encodeString(bytes.constData(), 0, level, QR_MODE_8, 1);
if (!qrcode) {
return QImage();
}
QImage image = renderQRCode(qrcode, 1, foreground, background);
QRcode_free(qrcode);
return image;
}
/**
* @brief 将Barcode_Item的编码数据(partial)渲染成QImage
*
* partial字段存储压缩的条形码宽度数据(高4位=空白宽度,低3位=黑条宽度),
* 需展开为像素数组。barWidth参数控制每个模块的物理宽度(用于放大)。
*
* 这种直接渲染方式避免了PostScript生成和文件I/O的开销。
*/
static QImage renderBarcodeImage(const Barcode_Item *bc, int barHeight = 12, int barWidth = 1, QRgb foreground = 0xff000000, QRgb background = 0xffffffff)
{
if (!bc || !bc->partial) {
return QImage();
}
// 约束:barHeight和barWidth必须为正值。默认值支持小尺寸标签的视觉清晰度
if (barHeight <= 0) {
barHeight = 12;
}
if (barWidth <= 0) {
barWidth = 1;
}
// xoffset=8提供左边距(4像素),用于对齐打印布局和改善美观度
int xoffset = 8;
int len = static_cast<int>(strlen(bc->partial));
// 计算条形码总宽度:每字节的低4位表示条纹宽度单位数之和
int bcWidth1 = 0;
for (int i = 0; i < len; ++i) {
bcWidth1 += (bc->partial[i] & 0x0f);
}
// 最终宽度 = 左边距 + 条纹单位总数 * 每单位像素数
int imageWidth = (xoffset >> 1) + bcWidth1 * barWidth;
if (imageWidth <= 0 || barHeight <= 0) {
return QImage();
}
QImage image(imageWidth, barHeight, QImage::Format_ARGB32);
image.fill(background);
for (int y = 0; y < barHeight; ++y) {
int xpos = xoffset >> 1;
for (int n = 0; n < len; ++n) {
// partial数据编码方案:奇数索引=黑条,偶数索引=空白
// 低3位存储实际宽度,需乘以barWidth得到像素宽度
int run = (bc->partial[n] & 0x07) * barWidth;
if (n & 1) {
// 奇数索引:绘制黑条(前景色)
for (int k = 0; k < run; ++k) {
if (xpos >= 0 && xpos < imageWidth) {
image.setPixel(xpos, y, foreground);
}
++xpos;
}
} else {
// 偶数索引:跳过空白区域(已由背景色填充)
xpos += run;
}
}
}
return image;
}
static int barcodeTypeFromString(const QString &type)
{
QString t = type.trimmed().toUpper();
if (t == "BARCODE_128") return BARCODE_128;
if (t == "BARCODE_93") return BARCODE_93;
if (t == "BARCODE_ISBN") return BARCODE_ISBN;
if (t == "BARCODE_EAN") return BARCODE_EAN;
if (t == "BARCODE_UPC") return BARCODE_UPC;
if (t == "BARCODE_39") return BARCODE_39;
if (t == "BARCODE_CBR") return BARCODE_CBR;
if (t == "BARCODE_PLS") return BARCODE_PLS;
if (t == "BARCODE_MSI") return BARCODE_MSI;
return BARCODE_128;
}
static QString barcodeTypeNameFromIndex(int index)
{
switch (index) {
case 0: return QLatin1String("BARCODE_128");
case 1: return QLatin1String("BARCODE_93");
case 2: return QLatin1String("BARCODE_ISBN");
case 3: return QLatin1String("BARCODE_EAN");
case 4: return QLatin1String("BARCODE_UPC");
case 5: return QLatin1String("BARCODE_39");
case 6: return QLatin1String("BARCODE_CBR");
case 7: return QLatin1String("BARCODE_PLS");
case 8: return QLatin1String("BARCODE_MSI");
default: return QLatin1String("BARCODE_128");
}
}
static QImage generateQRCodeImage(const QString &content, int qrType)
{
DlgQrcodeFormatVM vm;
vm.type = qrType == 0 ? QR_CODE_TYPE_DATA_MATRIX : QR_CODE_TYPE_QRCODE;
return generateQRCodeImage(content, vm);
}
/**
* @brief 根据格式配置生成条形码图像
*
* 支持多种条形码标准(128、93、EAN等),直接渲染到QImage,无需文件操作。
* 避免对QBarCode类的依赖,实现自包含的编码流程。
*/
static QImage generateBarCodeImage(const QString &content, const DlgBarcodeFormatVM &vm)
{
if (content.isEmpty()) {
return QImage();
}
// UTF-8编码确保国际字符被正确处理,与打印驱动兼容
QByteArray bytes = content.toUtf8();
// Barcode_Create内部用strdup复制字符串,无需手动生命周期管理
struct Barcode_Item *bc = Barcode_Create(bytes.data());
if (!bc) {
return QImage();
}
int encodingFlag = barcodeTypeFromString(vm.type);
// BARCODE_OUT_PS: 预期PostScript格式输出(internal flag),BARCODE_OUT_NOHEADERS: 不生成PS头
int flags = encodingFlag | BARCODE_OUT_PS | BARCODE_OUT_NOHEADERS;
// 传递缩放倍数到库,影响条纹宽度和字体大小的计算(510/60为库内部单位转换)
int width = qMax(1, vm.scale) * 510;
int height = qMax(1, vm.scale) * 60;
if (Barcode_Position(bc, width, height, 0, 0, 0.0) < 0 || Barcode_Encode(bc, flags) < 0) {
Barcode_Delete(bc);
return QImage();
}
// 黑底白码支持(INK_TYPE_WHITE表示反向模式)
QRgb foreground = vm.inkType == INK_TYPE_WHITE ? 0x00ffffff : 0xff000000;
QRgb background = vm.inkType == INK_TYPE_WHITE ? 0xff000000 : 0x00ffffff;
QImage image = renderBarcodeImage(bc, vm.height, vm.width, foreground, background);
Barcode_Delete(bc);
// 可选文本叠加:某些应用场景(如物流标签)需要条码旁显示人类可读的内容
if (vm.txtVisible) {
QPainter painter(&image);
painter.setPen(foreground);
QFont font = painter.font();
// 字体大小自适应:防止文本溢出图像,同时保持可读性
font.setPixelSize(qMin(12, image.height()));
painter.setFont(font);
painter.drawText(image.rect().adjusted(1, 1, -1, -1), Qt::AlignBottom | Qt::AlignHCenter, content);
}
// 最终放大:scale>1时对整个图像进行像素放大(用于低DPI打印环保模式)
if (vm.scale > 1) {
image = image.scaled(image.width() * vm.scale, image.height() * vm.scale, Qt::KeepAspectRatio, Qt::FastTransformation);
}
return image;
}
static QImage generateBarCodeImage(const QString &content, int barCodeType)
{
DlgBarcodeFormatVM vm;
vm.type = barcodeTypeNameFromIndex(barCodeType);
return generateBarCodeImage(content, vm);
}
QString generateTxtFromDateItemModel(const DateItemModel &model, const QDateTime &time)
{
@@ -73,7 +381,7 @@ QString generateTxtFromDateItemModel(const DateItemModel &model, const QDateTime
if (result.contains(weekNumberPattern)) {
result.replace(weekNumberPattern, QString::number(applyDateTime.date().weekNumber()));
}
return result;
}
@@ -87,12 +395,154 @@ QString generateTxtFromBarcodeViewModel(const BarcodeViewModel &vm, const std::a
}
pos = vm.contentPattern.indexOf("{c}");
if(pos != 1) {
if(pos != -1) {
auto cntValue = vm.counterViewModel.counterType == COUNTER_TYPE_1 ? counterValues[0] : counterValues[1];
auto valueStr = QString("%1").arg(cntValue, 10, QChar('0'));;
valueStr.right(vm.counterViewModel.digitalNum);
valueStr = valueStr.right(vm.counterViewModel.digitalNum);
txt.replace(pos, 3, valueStr);
}
return txt;
}
}
/**
* @brief 统一的条码/二维码生成入口
*
* @details
* 目的:将二维码和条形码编码逻辑独立出来,避免对UI对话框类(dlgQrCode、QBarCode)的依赖,
* 实现在数据层直接进行编码,支持后端打印任务、异步处理等场景。
*
* 多态format参数设计的理由:
* - 支持便利调用(int):向后兼容现有代码路径,使用库默认配置
* - 支持细粒度控制(DlgQrcodeFormatVM/DlgBarcodeFormatVM):UI配置直通生成函数
* - 避免序列化开销:QVariant作为类型安全的通用容器
*
* @param content 已格式化的文本内容(由调用者负责模板替换如{t}、{c})
* @param type 条码类型(BARCODE_TYPE_QRCODE 或 BARCODE_TYPE_BARCODE)
* @param format 配置参数容器,支持以下类型:
* - BARCODE_TYPE_QRCODE: DlgQrcodeFormatVM(完整配置)或 int(0=DataMatrix, 1=QRCode,使用默认值)
* - BARCODE_TYPE_BARCODE: DlgBarcodeFormatVM(完整配置)或 int(条形码类型索引,使用默认值)
* @return QImage 生成的图像;失败返回空QImage
*/
QImage generateBarcodeImage(const QString &content, BarcodeType type, const QVariant &format)
{
if (content.isEmpty()) {
return QImage();
}
if (type == BARCODE_TYPE_QRCODE) {
// 优先级:完整VM配置 > 简化int配置 > 默认配置
// 原因:支持从UI直接传递结构体,同时保留向后兼容的快速路径
if (format.canConvert<DlgQrcodeFormatVM>()) {
DlgQrcodeFormatVM vm = format.value<DlgQrcodeFormatVM>();
return generateQRCodeImage(content, vm);
}
if (format.isValid() && format.canConvert<int>()) {
return generateQRCodeImage(content, format.toInt());
}
return generateQRCodeImage(content, static_cast<int>(QR_CODE_TYPE_QRCODE));
}
if (type == BARCODE_TYPE_BARCODE) {
if (format.canConvert<DlgBarcodeFormatVM>()) {
DlgBarcodeFormatVM vm = format.value<DlgBarcodeFormatVM>();
return generateBarCodeImage(content, vm);
}
if (format.isValid() && format.canConvert<int>()) {
return generateBarCodeImage(content, format.toInt());
}
return generateBarCodeImage(content, DlgBarcodeFormatVM());
}
return QImage();
}
/**
* @brief 将 QImage 中所有等于 bg 的边缘像素裁掉,仅保留有效内容的最小外接矩形
*
* 复刻自 PicTextItem 的 clipImage:用四个方向扫描确定 left/top/right/bottom 边界,
* 然后调用 QImage::copy 输出裁剪后的图像。这样字符串如 "aaa" 不会保留 "j" 才会用到的下行空白,
* 节省后续打印或合成时的画布空间。
*/
static QImage clipTxtImage(const QImage &img, QRgb bg)
{
if (img.isNull()) {
return img;
}
QRect clipRect;
for (int x = 0; x < img.width(); ++x) {
for (int y = 0; y < img.height(); ++y) {
if (img.pixel(x, y) != bg) {
clipRect.setLeft(x);
goto top;
}
}
}
// 整张图都是背景色,直接返回原图(无可裁剪内容)
return img;
top:
for (int y = 0; y < img.height(); ++y) {
for (int x = 0; x < img.width(); ++x) {
if (img.pixel(x, y) != bg) {
clipRect.setTop(y);
goto right;
}
}
}
right:
for (int x = img.width() - 1; x >= 0; --x) {
for (int y = 0; y < img.height(); ++y) {
if (img.pixel(x, y) != bg) {
clipRect.setRight(x + 1);
goto bottom;
}
}
}
bottom:
for (int y = img.height() - 1; y >= 0; --y) {
for (int x = 0; x < img.width(); ++x) {
if (img.pixel(x, y) != bg) {
clipRect.setBottom(y + 1);
goto end;
}
}
}
end:
return img.copy(clipRect);
}
QImage txtToImage(const QString &txt, const QFont &font, const QColor &fgColor, const QColor &bgColor)
{
if (txt.isEmpty()) {
return QImage();
}
// 用 QFontMetrics 计算给定字体下文本的水平推进宽度和单行高度,
// 这样初始画布刚好能容纳整段文本,避免后续裁剪时出现截断
QFontMetrics fm(font);
int width = fm.horizontalAdvance(txt);
int height = fm.height();
if (width <= 0 || height <= 0) {
return QImage();
}
QImage img(width, height, QImage::Format_ARGB32);
QRgb bgRgb = bgColor.rgba();
img.fill(bgRgb);
QPainter painter;
painter.begin(&img);
painter.setFont(font);
painter.setPen(fgColor);
painter.drawText(img.rect(), txt);
painter.end();
// 按背景色裁剪,使输出图像紧贴文字像素的最小包围盒(详见函数注释)
return clipTxtImage(img, bgRgb);
}
+46 -1
View File
@@ -6,7 +6,26 @@
#include <QString>
#include <QVector>
#include "BarcodeEditor.h"
#include <array>
#include <QImage>
/**
* @brief 根据日期视图模型和时间生成字符串
* @param model 包含日期格式模版和相关配置的视图模型
* @param time 用于生成字符串的时间
* @return QString 生成的字符串
* @remark
* 该函数根据 model.datetimePattern 中的格式模版生成字符串,支持以下格式:
* - yyyy: 四位数年份,支持替换为自定义字符串(通过 model.yearsMap)
* - YY: 两位数年份
* - MM: 两位数月份,支持替换为自定义字符串(通过 model.monthsMap)
* - dd: 两位数日期,支持替换为自定义字符串(通过 model.daysMap)
* - HH: 两位数小时,支持替换为自定义字符串(通过 model.hoursMap)
* - mm: 两位数分钟
* - ss: 两位数秒钟
* - w: 星期几,支持替换为自定义字符串(通过 model.weeksMap)
* - c: 年中周数
*/
QString generateTxtFromDateItemModel(const DateItemModel& model, const QDateTime& time);
/**
@@ -28,4 +47,30 @@ QString generateTxtFromDateItemModel(const DateItemModel& model, const QDateTime
*/
QString generateTxtFromBarcodeViewModel(const BarcodeViewModel& vm, const std::array<int, 2> counterValues, const QDateTime& time);
#endif // _ITEM_COMMON_H_
/**
* @brief 根据条形码类型和内容生成条形码或二维码图像
* @param content 已格式化的文本内容
* @param type 条码类型
* @param format 配置参数
* @return QImage 生成的图像;如果失败返回空图像
* @note
* format 参数类型:
* - BARCODE_TYPE_QRCODE: DlgQrcodeFormatVM 或 int
* - BARCODE_TYPE_BARCODE: DlgBarcodeFormatVM 或 int
* 如果 format 是 int 类型, 对于 QR Code,0 表示 Data Matrix,1 表示 QR Code;对于条形码,int 值对应 DlgBarcodeFormatVM 中 type 的索引。
* 如果 format 是 int 类型,则使用默认配置并根据 int 值选择二维码或条形码的子类型;如果 format 是对应的 VM 类型,则使用其中的详细配置。
*/
QImage generateBarcodeImage(const QString& content, BarcodeType type, const QVariant& format);
/**
* @brief 将文本转换为图像
* @param txt 要转换的文本
* @param font 文本使用的字体
* @param fgColor 文本颜色,默认为黑色
* @param bgColor 背景颜色,默认为白色
* @return QImage 转换后的图像
* @note 返回的字符串图像会裁剪至最小边界以节省空间。例如字符串"aaa"的高度会比"bbbjjj"小,因为前者没有上下行部分。
*/
QImage txtToImage(const QString& txt, const QFont& font, const QColor& fgColor = Qt::black, const QColor& bgColor = Qt::white);
#endif // _ITEM_COMMON_H_
+115 -51
View File
@@ -18,6 +18,14 @@ ItemGeneralSettings::ItemGeneralSettings(QWidget *parent) :
connect(&m_directionButtonGroup, static_cast<void(QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked),
this, &ItemGeneralSettings::ondirectionChanged);
connect(ui->boldSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
this, &ItemGeneralSettings::onBoldSpinBoxValueChanged);
connect(ui->txtSpanSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
this, &ItemGeneralSettings::onLetterSpaceSpinBoxChanged);
connect(ui->invertCheckBox, &QCheckBox::stateChanged,
this, [this](int state) { onColorReversalCheckBoxChecked(state == Qt::Checked); });
initUI();
}
ItemGeneralSettings::~ItemGeneralSettings()
@@ -28,35 +36,51 @@ ItemGeneralSettings::~ItemGeneralSettings()
void ItemGeneralSettings::edit(PicTextItem *item)
{
m_currItem = item;
if(!m_currItem) {
ui->boldSpinBox->setValue(ui->boldSpinBox->minimum());
ui->txtSpanSpinBox->setValue(ui->txtSpanSpinBox->minimum());
ui->normalRBtn->setChecked(true);
ui->invertCheckBox->setChecked(false);
ui->previewWidget->setImg(QImage());
return;
// 从item快照创建ViewModel(如果item为空,使用默认值)
setViewModel(ItemGeneralSettingsVM::fromItem(item));
}
void ItemGeneralSettings::initUI()
{
ui->densitySpinBox->setValue(0);
ui->invertCheckBox->setChecked(false);
ui->alternateCheckBox->setChecked(false);
ui->boldSpinBox->setValue(0);
ui->txtSpanSpinBox->setValue(0);
ui->normalRBtn->setChecked(true);
ui->applyCounterCheckBox->setChecked(false);
ui->counterIDSpinBox->setValue(0);
}
void ItemGeneralSettings::setViewModel(const ItemGeneralSettingsVM &vm)
{
m_vmSnapshot = vm;
m_vm = vm;
// 阻止信号以避免重复回调
auto blockBold = ui->boldSpinBox->blockSignals(true);
auto blockSpan = ui->txtSpanSpinBox->blockSignals(true);
auto blockInvert = ui->invertCheckBox->blockSignals(true);
// 根据m_vm更新UI显示
ui->boldSpinBox->setValue(m_vm.boldMultiple);
ui->txtSpanSpinBox->setValue(m_vm.letterSpace);
auto dirBtn = m_directionButtonGroup.button(m_vm.direction);
if (dirBtn) {
dirBtn->setChecked(true);
}
auto b = ui->boldSpinBox->blockSignals(true);
ui->boldSpinBox->setValue(item->boldMultiple());
ui->boldSpinBox->blockSignals(b);
b = ui->txtSpanSpinBox->blockSignals(true);
ui->txtSpanSpinBox->setValue(item->letterSpace());
ui->txtSpanSpinBox->blockSignals(b);
auto dirBtn = m_directionButtonGroup.button(item->direction());
b = dirBtn->blockSignals(true);
dirBtn->setChecked(true);
dirBtn->blockSignals(b);
b = ui->invertCheckBox->blockSignals(true);
ui->invertCheckBox->setChecked(item->colorReversal());
ui->invertCheckBox->blockSignals(b);
// 文字、日期、轮班可调字符间距
// 计数器类的字符间距由 @see CounterVarSet 单独调整
switch(item->sourceType()) {
ui->invertCheckBox->setChecked(m_vm.colorReversal);
// 恢复信号
ui->boldSpinBox->blockSignals(blockBold);
ui->txtSpanSpinBox->blockSignals(blockSpan);
ui->invertCheckBox->blockSignals(blockInvert);
// 字间距可见性由item的类型决定
switch(m_vm.sourceType) {
case SourceType::TEXT:
case SourceType::DATETIME:
case SourceType::SCHEDULE_SHIFT:
@@ -67,44 +91,84 @@ void ItemGeneralSettings::edit(PicTextItem *item)
ui->letterSpacePanel->setVisible(false);
break;
}
updatePreview();
}
ui->previewWidget->setImg(m_currItem->preview());
void ItemGeneralSettings::applyToItem(PicTextItem *item)
{
if (!item) {
return;
}
// VM的属性被逐一应用到item
item->setDirection(m_vm.direction);
item->setBoldMultiple(m_vm.boldMultiple);
item->setLetterSpace(m_vm.letterSpace);
item->setColorReversal(m_vm.colorReversal);
item->applyModify();
}
void ItemGeneralSettings::ondirectionChanged(int dir)
{
if(!m_currItem) {
return;
}
m_currItem->setDirection(ItemDirectionType(dir));
m_currItem->applyModify();
ui->previewWidget->setImg(m_currItem->preview());
// 只更新VM,不直接修改item
m_vm.direction = ItemDirectionType(dir);
updatePreview();
}
void ItemGeneralSettings::onBoldSpinBoxValueChanged(int multiple)
{
if(m_currItem) {
m_currItem->setBoldMultiple(multiple);
m_currItem->applyModify();
ui->previewWidget->setImg(m_currItem->preview());
}
// 只更新VM
m_vm.boldMultiple = multiple;
updatePreview();
}
void ItemGeneralSettings::onLetterSpaceSpinBoxChanged(int space)
{
if(m_currItem) {
m_currItem->setLetterSpace(space);
m_currItem->applyModify();
ui->previewWidget->setImg(m_currItem->preview());
}
// 只更新VM
m_vm.letterSpace = space;
updatePreview();
}
void ItemGeneralSettings::onColorReversalCheckBoxChecked(bool b)
{
if(m_currItem) {
m_currItem->setColorReversal(b);
m_currItem->applyModify();
ui->previewWidget->setImg(m_currItem->preview());
}
// 只更新VM
m_vm.colorReversal = b;
updatePreview();
}
void ItemGeneralSettings::updatePreview()
{
// 预览需要基于item的source和m_vm的属性组合渲染
// 如果m_currItem为空,显示空图像
if (!m_currItem) {
if(m_vm.imageGenerator) {
ui->previewWidget->setImg(m_vm.imageGenerator());
}
else {
ui->previewWidget->setImg(QImage());
}
return;
}
// 临时应用VM到item以获取预览,随后还原
auto backupDir = m_currItem->direction();
auto backupBold = m_currItem->boldMultiple();
auto backupSpace = m_currItem->letterSpace();
auto backupReverse = m_currItem->colorReversal();
m_currItem->setDirection(m_vm.direction);
m_currItem->setBoldMultiple(m_vm.boldMultiple);
m_currItem->setLetterSpace(m_vm.letterSpace);
m_currItem->setColorReversal(m_vm.colorReversal);
m_currItem->applyModify();
ui->previewWidget->setImg(m_currItem->preview());
// 还原原始状态(preview()后item的属性不应该被留下改动)
m_currItem->setDirection(backupDir);
m_currItem->setBoldMultiple(backupBold);
m_currItem->setLetterSpace(backupSpace);
m_currItem->setColorReversal(backupReverse);
m_currItem->applyModify();
}
+89
View File
@@ -5,11 +5,52 @@
#include <QButtonGroup>
#include "pictextitem.h"
#include <QPainter>
#include <QImage>
namespace Ui {
class ItemGeneralSettings;
}
/**
* @brief 图元通用属性的ViewModel
*
* 目的:解耦UI编辑器与PicTextItem,支持灵活的数据绑定和批量操作。
* UI修改属性时先更新VM,再决定何时应用到item(实时或延迟)。
*/
struct ItemGeneralSettingsVM {
// 图元类型,用于判断是否显示字间距
SourceType sourceType = SourceType::UNKNOWN;
ItemDirectionType direction = NORMAL;
int boldMultiple = 1;
int letterSpace = 0;
bool colorReversal = false;
std::function<QImage()> imageGenerator; // 图片生成函数
// 便利方法:从PicTextItem快照创建VM
static ItemGeneralSettingsVM fromItem(PicTextItem *item) {
ItemGeneralSettingsVM vm;
if (item) {
vm.sourceType = item->sourceType();
vm.direction = item->direction();
vm.boldMultiple = item->boldMultiple();
vm.letterSpace = item->letterSpace();
vm.colorReversal = item->colorReversal();
}
return vm;
}
// 检查是否有改动
bool hasDifference(const ItemGeneralSettingsVM &other) const {
return sourceType != other.sourceType ||
direction != other.direction ||
boldMultiple != other.boldMultiple ||
letterSpace != other.letterSpace ||
colorReversal != other.colorReversal;
}
};
class ItemGeneralSettings : public QWidget
{
Q_OBJECT
@@ -18,10 +59,45 @@ public:
explicit ItemGeneralSettings(QWidget *parent = nullptr);
~ItemGeneralSettings();
/**
* @brief 加载PicTextItem的属性到ViewModel,初始化UI
*
* 原因:UI应该显示item的状态,但不应该直接持有指针长期修改。
* edit()后,用户对UI的编辑会更新m_vm,而不是直接改item。
*/
void edit(PicTextItem* item);
/**
* @brief 设置当前ViewModel
*
* @param vm
*/
void setViewModel(const ItemGeneralSettingsVM& vm);
/**
* @brief 获取当前ViewModel
*
* 调用者可以检查是否有改动,并决定是否应用
*/
ItemGeneralSettingsVM viewModel() const { return m_vm; }
/**
* @brief 将ViewModel的属性应用到指定的PicTextItem
*
* 解耦的核心:UI不再直接修改item,而是通过VM进行间接修改。
* 这支持撤销/重做、批量编辑等高级功能。
*/
void applyToItem(PicTextItem* item);
/**
* @brief 当前编辑的item源引用(可能为空)
*
* 主要用于获取preview()时作为示例源,不用于直接修改
*/
PicTextItem* currentItem() { return m_currItem; }
void initUI();
private slots:
void ondirectionChanged(int dir);
void onBoldSpinBoxValueChanged(int multiple);
@@ -29,10 +105,23 @@ private slots:
void onColorReversalCheckBoxChecked(bool b);
private:
/**
* @brief 刷新预览,基于当前VM和item的source渲染
*/
void updatePreview();
Ui::ItemGeneralSettings *ui;
QButtonGroup m_directionButtonGroup;
// 原始item的快照,用于preview()和后续应用
PicTextItem* m_currItem = nullptr;
// UI编辑的状态,独立于m_currItem
ItemGeneralSettingsVM m_vm;
// 初始加载时的VM状态,用于检测改动
ItemGeneralSettingsVM m_vmSnapshot;
QPainter m_painter;
};
+3 -38
View File
@@ -12,7 +12,7 @@ PicDateItem::PicDateItem(QGraphicsItem *parent, QGraphicsScene *scene, const Sou
updateDatetimeStr();
});
m_timer.start();
// m_timer.start();
}
QVariant PicDateItem::source()
@@ -70,48 +70,13 @@ void PicDateItem::setCustomAttrs(const QXmlStreamAttributes &attrs)
// yearsMap = "2023<subbr>2023年<br>2024<subbr>2024年<br>2025<subbr>2025年"
auto m = DateItemModel();
m.dataMap = attrs.value("dataMap").toString().split("<br>");
m.daysMap = attrs.value("daysMap").toString().split("<br>");
m.hoursMap = attrs.value("hoursMap").toString().split("<br>");
m.monthsMap = attrs.value("monthsMap").toString().split("<br>");
m.weeksMap = attrs.value("weeksMap").toString().split("<br>");
auto yearsMap = attrs.value("yearsMap").toString().split("<br>");
for(auto it = yearsMap.begin(); it != yearsMap.end(); ++it) {
auto yearPair = it->split("<subbr>");
if(yearPair.size() == 2) {
m.yearsMap[yearPair[0].toInt()] = yearPair[1];
}
}
m.monthsOffset = attrs.value("monthsOffset").toInt();
m.daysOffset = attrs.value("daysOffset").toInt();
m.datetimePattern = attrs.value("datetimePattern").toString();
DateItemModel::loadFromXml(m, attrs);
setModel(std::move(m));
}
void PicDateItem::saveCustomAttrs(QXmlStreamAttributes &attrs)
{
auto dataMap = m_model.dataMap.join("<br>");
auto daysMap = m_model.daysMap.join("<br>");
auto hoursMap = m_model.hoursMap.join("<br>");
auto monthsMap = m_model.monthsMap.join("<br>");
auto weeksMap = m_model.weeksMap.join("<br>");
QString yearsMap;
for(auto it = m_model.yearsMap.begin(); it != m_model.yearsMap.end(); ++it) {
yearsMap.append(QString("%1<subbr>%2<br>").arg(it.key()).arg(it.value()));
}
attrs.append("dataMap", dataMap);
attrs.append("daysMap", daysMap);
attrs.append("hoursMap", hoursMap);
attrs.append("monthsMap", monthsMap);
attrs.append("weeksMap", weeksMap);
attrs.append("yearsMap", yearsMap);
attrs.append("datetimePattern", m_model.datetimePattern);
attrs.append("monthsOffset", QString::number(m_model.monthsOffset));
attrs.append("daysOffset", QString::number(m_model.daysOffset));
DateItemModel::saveToXml(m_model, attrs);
}
QImage PicDateItem::itemImage()
+1 -1
View File
@@ -9,7 +9,7 @@ PicScheduleShiftItem::PicScheduleShiftItem(QGraphicsItem *parent, QGraphicsScene
connect(&m_updateTimer, &QTimer::timeout, [this](){
updateScheduleName();
});
m_updateTimer.start();
// m_updateTimer.start();
}
QVariant PicScheduleShiftItem::source()
+96 -1
View File
@@ -44,6 +44,7 @@ ScheduleShiftsEditor::ScheduleShiftsEditor(QWidget *parent) :
ui->btnAdd->setEnabled(false);
ui->btnRemove->setEnabled(false);
applyStyleSheet();
}
ScheduleShiftsEditor::~ScheduleShiftsEditor()
@@ -162,7 +163,101 @@ void ScheduleShiftsEditor::applyDataModel()
void ScheduleShiftsEditor::applyStyleSheet()
{
// TODO:: 应用样式表
QString styleSheet =
R"(
/*
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 180px;
height: 90px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
*/
QPushButton#closeBtn, QPushButton#checkBtn{
background-color: #E6F7FF;
border: 1px solid #87CEEB;
border-radius: 4px;
font-size: 24px;
color: #228B22;
}
QPushButton#closeBtn:hover,QPushButton#checkBtn:hover{
background-color: #D1E9FF;
}
QPushButton#closeBtn:pressed,QPushButton#checkBtn:pressed{
background-color: #BAE7FF;
}
QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 100px;
height: 50px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}
QTabWidget::pane {
border: 1px solid #dddddd;
background-color: #ffffff;
}
QTabBar::tab:focus {
outline: none;
}
QTabBar::tab {
min-width: 90px;
min-height: 45px;
padding: 4px 10px;
margin-right: 4px;
border: 1px solid #dddddd;
border-bottom: none;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-color: #5396C1;
color: #ffffff;
}
QTabWidget::tab-bar {
top: -3px;
left: 10px;
}
QTabBar::tab:selected {
background-color: #6bb1e0;
color: #ffffff;
box-shadow: 0 -3px 8px rgba(44, 125, 232, 0.25);
}
)";
this->setStyleSheet(styleSheet);
}
SelectedStyleTimeEdit::SelectedStyleTimeEdit(QWidget *parent)
+1 -20
View File
@@ -149,26 +149,7 @@
</widget>
<widget class="QWidget" name="scheduleShiftsEditTab">
<property name="styleSheet">
<string notr="true"> QPushButton {
background-color: #5396C1;
color: #ffffff;
border: 1px solid #000000;
border-radius: 3px;
font-size: 18px;
font-weight:500;
width: 180px;
height: 90px;
}
QPushButton:hover {
background-color: #5396C1;
}
QPushButton:disabled {
background-color: #7C828E;
color: #606060;
border-color: #808080;
}</string>
<string notr="true"/>
</property>
<attribute name="title">
<string>班时编号</string>
+79
View File
@@ -1,4 +1,5 @@
#include "ViewModels.h"
#include <QDebug>
DateItemModel DateItemModel::createDefaultReplaceModel()
{
@@ -28,3 +29,81 @@ DateItemModel DateItemModel::createDefaultReplaceModel()
return model;
}
void DateItemModel::loadFromXml(DateItemModel &vm, const QXmlStreamAttributes &attrs)
{
auto& m = vm;
m.dataMap = attrs.value("dataMap").toString().split("<br>");
m.daysMap = attrs.value("daysMap").toString().split("<br>");
m.hoursMap = attrs.value("hoursMap").toString().split("<br>");
m.monthsMap = attrs.value("monthsMap").toString().split("<br>");
m.weeksMap = attrs.value("weeksMap").toString().split("<br>");
auto yearsMap = attrs.value("yearsMap").toString().split("<br>");
for(auto it = yearsMap.begin(); it != yearsMap.end(); ++it) {
auto yearPair = it->split("<subbr>");
if(yearPair.size() == 2) {
m.yearsMap[yearPair[0].toInt()] = yearPair[1];
}
}
m.monthsOffset = attrs.value("monthsOffset").toInt();
m.daysOffset = attrs.value("daysOffset").toInt();
m.datetimePattern = attrs.value("datetimePattern").toString();
}
void DateItemModel::saveToXml(const DateItemModel &vm, QXmlStreamAttributes &attrs)
{
auto& m = vm;
auto dataMap = m.dataMap.join("<br>");
auto daysMap = m.daysMap.join("<br>");
auto hoursMap = m.hoursMap.join("<br>");
auto monthsMap = m.monthsMap.join("<br>");
auto weeksMap = m.weeksMap.join("<br>");
QString yearsMap;
for(auto it = m.yearsMap.begin(); it != m.yearsMap.end(); ++it) {
yearsMap.append(QString("%1<subbr>%2<br>").arg(it.key()).arg(it.value()));
}
attrs.append("dataMap", dataMap);
attrs.append("daysMap", daysMap);
attrs.append("hoursMap", hoursMap);
attrs.append("monthsMap", monthsMap);
attrs.append("weeksMap", weeksMap);
attrs.append("yearsMap", yearsMap);
attrs.append("datetimePattern", m.datetimePattern);
attrs.append("monthsOffset", QString::number(m.monthsOffset));
attrs.append("daysOffset", QString::number(m.daysOffset));
}
void CounterViewModel::loadFromXml(CounterViewModel &vm, const QXmlStreamAttributes &attrs)
{
auto typeRef = attrs.value("counter_type");
auto digitalNumRef = attrs.value("digital_num");
bool ok = false;
int counterType = typeRef.toInt(&ok);
if(!ok || (counterType != COUNTER_TYPE_1 && COUNTER_TYPE_2 != counterType)) {
qWarning() << "Invalied counter type: " << counterType;
return;
}
ok = false;
int digitalNum = digitalNumRef.toInt(&ok);
if(!ok || digitalNum <= 0) {
qWarning() << "Invalied counter digital number: " << digitalNum;
return;
}
vm.counterType = static_cast<COUNTER_TYPE>(counterType);
vm.digitalNum = digitalNum;
qInfo() << "Loaded attrs from xml: " << " counter type: " << counterType << ", digitalNum: " << digitalNum;
}
void CounterViewModel::saveToXml(CounterViewModel &vm, QXmlStreamAttributes &attrs)
{
attrs.append("counter_type", QString::number(vm.counterType));
attrs.append("digital_num", QString::number(vm.digitalNum));
qInfo() << "Set attrs to xml: " << " counter type: " << vm.counterType << ", digitalNum: " << vm.digitalNum;
}
+6
View File
@@ -7,6 +7,7 @@
#include <QSharedPointer>
#include "SysPara.h"
#include <QMap>
#include <QXmlStreamAttributes>
enum InkType {
INK_TYPE_BLACK,
@@ -32,6 +33,9 @@ struct CounterViewModel {
int letterSpace = 2;
int charWidth = 0; // 字符宽度,仅在 enbCustomCharWidth == true 时有意义
bool enbCustomCharWidth = false;
static void loadFromXml(CounterViewModel& vm, const QXmlStreamAttributes& attrs);
static void saveToXml(CounterViewModel& vm, QXmlStreamAttributes& attrs);
};
Q_DECLARE_METATYPE(CounterViewModel);
@@ -52,6 +56,8 @@ struct DateItemModel {
QStringList hoursMap;
static DateItemModel createDefaultReplaceModel();
static void loadFromXml(DateItemModel& vm, const QXmlStreamAttributes& attrs);
static void saveToXml(const DateItemModel& vm, QXmlStreamAttributes& attrs);
};
Q_DECLARE_METATYPE(DateItemModel);
+52 -8
View File
@@ -1,38 +1,82 @@
#include "piccodeitem.h"
#include "ItemCommon.h"
#include <QDebug>
PicCodeItem::PicCodeItem(QGraphicsItem *parent, QGraphicsScene *scene, const SourceType type)
:PicTextItem(parent, scene, type)
:PicTextItem(parent, scene, type),
m_currCounterValues({0, 0})
{
setFlag(QGraphicsItem::ItemIsMovable);
setFlag(QGraphicsItem::ItemIsSelectable);
setFlag(QGraphicsItem::ItemIsFocusable);
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
connect(&m_dateVarTimer, &QTimer::timeout, this, [this](){
updateVariants();
});
// m_dateVarTimer.start(50);
}
PicCodeItem::~PicCodeItem()
{
m_dateVarTimer.stop();
}
QVariant PicCodeItem::source()
{
return imagePath;
return m_currCodeTxt;
}
void PicCodeItem::setSource(QVariant var)
{
imagePath = var.toString();
applyModify();
}
void PicCodeItem::setBarcodeViewModel(const BarcodeViewModel &vm)
{
m_vm = vm;
m_currCodeTxt = "";
updateVariants();
qInfo() << "Barcode model: \r\n" << qPrintable(BarcodeViewModel::toString(vm));
}
BarcodeViewModel PicCodeItem::barcodeViewModel()
{
return m_vm;
}
bool PicCodeItem::updateVariants(const QVector<int> counters)
{
m_currCounterValues = counters;
return updateVariants();
}
QImage PicCodeItem::itemImage()
{
return QImage(imagePath);
return m_codeImage;
}
void PicCodeItem::setCustomAttrs(const QXmlStreamAttributes &attrs)
{
imagePath = attrs.value("CodeImage").toString();
auto vm = BarcodeViewModel::loadFromXml(attrs);
setBarcodeViewModel(vm);
}
void PicCodeItem::saveCustomAttrs(QXmlStreamAttributes &attrs)
{
attrs.append("CodeImage", imagePath);
BarcodeViewModel::saveToXml(m_vm, attrs);
}
bool PicCodeItem::updateVariants()
{
auto txt = generateTxtFromBarcodeViewModel(m_vm, {m_currCounterValues[0], m_currCounterValues[1]}, QDateTime::currentDateTime());
if(txt != m_currCodeTxt || m_currCodeTxt.isEmpty()) {
m_currCodeTxt = txt;
m_codeImage = generateBarcodeImage(txt, m_vm.type, m_vm.format);
applyModify();
return true;
}
return false;
}
+15 -5
View File
@@ -2,19 +2,24 @@
#define PICCODEITEM_H
#include "pictextitem.h"
#include "BarcodeEditor.h"
#include <QTimer>
class PicCodeItem : public PicTextItem
{
public:
PicCodeItem(QGraphicsItem *parent = 0, QGraphicsScene *scene = 0,
const SourceType type = SourceType::CODE);
virtual ~PicCodeItem() {}
virtual ~PicCodeItem();
virtual QVariant source() override;
virtual void setSource(QVariant var) override;
void setBarcodeViewModel(const BarcodeViewModel& vm);
BarcodeViewModel barcodeViewModel();
bool updateVariants(const QVector<int> counters);
protected:
/**
* @brief itemImage 子类可以重新实现该方法用于返回已经渲染好了的但未应用方向、水平拉伸的图像
@@ -29,9 +34,14 @@ protected:
virtual void saveCustomAttrs(QXmlStreamAttributes& attrs);
private:
// 保存图片的绝对路径
QString imagePath = "";
bool updateVariants();
BarcodeViewModel m_vm;
QVector<int> m_currCounterValues;
QString m_currCodeTxt;
QImage m_codeImage;
QTimer m_dateVarTimer;
};
#endif // PICCODEITEM_H
+2
View File
@@ -55,6 +55,7 @@ void PicScene::keyPressEvent(QKeyEvent *event)
{
QGraphicsScene::keyPressEvent(event);
}
//edit@12-09
void PicScene::drawBackground(QPainter *painter, const QRectF &rect)
{
@@ -112,6 +113,7 @@ void PicScene::drawBackground(QPainter *painter, const QRectF &rect)
emit showTextWidthHeight();
}
//edit@12-09
void PicScene::setIsdrawBackground(bool newIsdrawBackground)
{
+1 -1
View File
@@ -72,6 +72,6 @@ private:
bool IsdrawBackground;
QGraphicsObject* currFocusItem;
QGraphicsObject* currFocusItem = nullptr;
};
#endif // PICSCENE_H