新二维码/条形码编辑逻辑
This commit is contained in:
+225
-3
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -6,6 +6,7 @@ DlgQrcodeFormat::DlgQrcodeFormat(QWidget *parent) :
|
||||
ui(new Ui::DlgQrcodeFormat)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setViewModel(m_vm);
|
||||
}
|
||||
|
||||
DlgQrcodeFormat::~DlgQrcodeFormat()
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -72,6 +72,6 @@ private:
|
||||
|
||||
bool IsdrawBackground;
|
||||
|
||||
QGraphicsObject* currFocusItem;
|
||||
QGraphicsObject* currFocusItem = nullptr;
|
||||
};
|
||||
#endif // PICSCENE_H
|
||||
|
||||
Reference in New Issue
Block a user