commit 81e2f99ab5113bedbafc565c1071dec89e54fea0 Author: wyc <1104749580@qq.com> Date: Tue Jun 16 13:33:27 2026 +0800 首次提交 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/YSGraphic_Core/Axis/AbsAxis.cpp b/YSGraphic_Core/Axis/AbsAxis.cpp new file mode 100644 index 0000000..f3231d9 --- /dev/null +++ b/YSGraphic_Core/Axis/AbsAxis.cpp @@ -0,0 +1,218 @@ +#include +#include "AbsAxis_p.h" + +namespace YSG { + Q_Ptr_cpp(AbsAxis) + PROP_P(AbsAxis, int, x) + PROP_P(AbsAxis, int, y) + PROP_P(AbsAxis, Qt::Orientation, orientation) + PROP_P(AbsAxis, size_t, pixelSize) + PROP_P(AbsAxis, int, tickLength) + PROP_P(AbsAxis, int, subTickLength) + PROP_P(AbsAxis, QColor, color) + PROP_P(AbsAxis, QChar, formatChar) + PROP_P(AbsAxis, QLocale, locale) + PROP_P(AbsAxis, QString, unitText) + PROP_P(AbsAxis, QFont, unitTextFont) + PROP_P(AbsAxis, QPen, unitTextPen) + PROP_P(AbsAxis, QBrush, unitTextBackgroundBrush) + PROP_P(AbsAxis, int, rotate) // 不旋转 + + double AbsAxis::pixelToCoord(double pixel, SRC src) { + return d()->pixelToCoord(pixel, src); + } + + double AbsAxis::coordToPixel(double coord, SRC src) { + return d()->coordToPixel(coord, src); + } + + QVector AbsAxisPrivate::createTickVector(double tickStep, const Range &range) { + double mTickOrigin = renderState()->coordStart; + QVector result; + // Generate tick positions according to tickStep: + // do not use qFloor here, or we'll lose 64 bit precision + + // qDebug() << "tickStep:" << tickStep; + // + + auto firstStep = qint64(floor(qAbs((range.lower-mTickOrigin)/tickStep))); + auto lastStep = qint64(ceil((qAbs(range.upper-mTickOrigin)/tickStep))); + + + + + int tickcount = int(qAbs(lastStep-firstStep)+1); + if (tickcount < 0) tickcount = 0; + + if(range.lower > range.upper) tickStep = -tickStep; + + result.resize(tickcount); + for (int i=0; i AbsAxisPrivate::createLabelVector(const QVector &ticks, SRC src) { + QVector result; + result.reserve(ticks.size()); + foreach (double tickCoord, ticks) + result.append(getTickLabel(tickCoord, src)); + return result; + } + + QVector AbsAxisPrivate::createSubTickVector(int subTickCount, const QVector &ticks, const Range& range) { + QVector result; + if (subTickCount <= 0 || ticks.size() < 2) + return result; + + result.reserve((ticks.size()-1)*subTickCount); + for (int i=1; i 0.2 substep + case 2: result = 3; break; // 2.0 -> 0.5 substep + case 3: result = 2; break; // 3.0 -> 1.0 substep + case 4: result = 3; break; // 4.0 -> 1.0 substep + case 5: result = 4; break; // 5.0 -> 1.0 substep + case 6: result = 2; break; // 6.0 -> 2.0 substep + case 7: result = 6; break; // 7.0 -> 1.0 substep + case 8: result = 3; break; // 8.0 -> 2.0 substep + case 9: result = 2; break; // 9.0 -> 3.0 substep + } + } else + { + // handle cases with significantly fractional mantissa: + if (qAbs(fracPart-0.5) < epsilon) // *.5 mantissa + { + switch (intPart) + { + case 1: result = 2; break; // 1.5 -> 0.5 substep + case 2: result = 4; break; // 2.5 -> 0.5 substep + case 3: result = 4; break; // 3.5 -> 0.7 substep + case 4: result = 2; break; // 4.5 -> 1.5 substep + case 5: result = 4; break; // 5.5 -> 1.1 substep (won't occur with default getTickStep from here on) + case 6: result = 4; break; // 6.5 -> 1.3 substep + case 7: result = 2; break; // 7.5 -> 2.5 substep + case 8: result = 4; break; // 8.5 -> 1.7 substep + case 9: result = 4; break; // 9.5 -> 1.9 substep + } + } + // if mantissa fraction isn't 0.0 or 0.5, don't bother finding good sub tick marks, leave default + } + + + return result; + } + + + double AbsAxisPrivate::getMantissa(double input, double *magnitude) { + const double mag = std::pow(10.0, std::floor(std::log10(input))); + if (magnitude) *magnitude = mag; + return input / mag; + } + + + + double AbsAxisPrivate::cleanMantissa(double input) { + double magnitude; + const double mantissa = getMantissa(input, &magnitude); + return pickClosest(mantissa, QVector() << 1.0 << 2.0 << 2.5 << 5.0 << 10.0)*magnitude; + } + + + double AbsAxisPrivate::pickClosest(double target, const QVector &candidates) { + if (candidates.size() == 1) + return candidates.first(); + QVector::const_iterator it = std::lower_bound(candidates.constBegin(), candidates.constEnd(), target); + if (it == candidates.constEnd()) + return *(it - 1); + else if (it == candidates.constBegin()) + return *it; + else + return target - *(it - 1) < *it - target ? *(it - 1) : *it; + } + + + + QString AbsAxisPrivate::getTickLabel(double tick, SRC src) { + auto renderState = reinterpret_cast(getState(src)); + return renderState->locale.toString(tick, renderState->formatChar.toLatin1(), renderState->numberPrecision); + } + + double AbsAxis::startCoord(SRC src) { + INIT_GET(AbsAxis) + return renderState->coordStart; + } + + double AbsAxis::endCoord(SRC src) { + INIT_GET(AbsAxis) + return renderState->coordStart + renderState->coordLength; + } + + Range AbsAxis::coordRange(SRC src) { + INIT_GET(AbsAxis) + return {renderState->coordStart, renderState->coordStart + renderState->coordLength}; + } + + int AbsAxis::getPixelPointSize(Range range, SRC src) { + INIT_GET(AbsAxis) + return static_cast(qAbs(range.length()/coordRange(src).length()) * renderState->pixelSize); + } + + int AbsAxis::getPixelPointSize(SRC src) { + INIT_GET(AbsAxis) + return qAbs(renderState->pixelSize); + } + + + double AbsAxis::getTickStep(const Range &range) {return d()->getTickStep(range);} + int AbsAxis::getSubTickCount(double tickStep) {return d()->getSubTickCount(tickStep);} + QString AbsAxis::getTickLabel(double tick, SRC src) { + INIT_GET(AbsAxis) + return d()->getTickLabel(tick, src); + } + QVector AbsAxis::createLabelVector(const QVector &ticks, SRC src) { + INIT_GET(AbsAxis) + return d()->createLabelVector(ticks, src); + } + + QVector AbsAxis::createTickVector(double tickStep, const Range &range) { + return d()->createTickVector(tickStep, range); + } + QVector AbsAxis::createSubTickVector(int subTickCount, const QVector &ticks, const Range& range) { + return d()->createSubTickVector(subTickCount, ticks, range); + } +} \ No newline at end of file diff --git a/YSGraphic_Core/Axis/AbsAxis.h b/YSGraphic_Core/Axis/AbsAxis.h new file mode 100644 index 0000000..e695b09 --- /dev/null +++ b/YSGraphic_Core/Axis/AbsAxis.h @@ -0,0 +1,83 @@ + +#pragma once + +#include "../RenderAble.h" + + +namespace YSG { +// const double delta = event->angleDelta().y(); +// double wheelSteps = delta / 120.0; +// double factor = qPow(0.8, wheelSteps); +struct AbsAxis_Prop{ + int x{}; + int y{}; + Qt::Orientation orientation = Qt::Horizontal; + int tickLength = 10; + int subTickLength = 5; + double coordStart = 0; + double coordLength = 20; + QColor color = Qt::white; + QChar formatChar = 'f'; + QLocale locale; + QString unitText = ""; + QFont unitTextFont; + QPen unitTextPen = QPen(Qt::white); + QBrush unitTextBackgroundBrush = QBrush(Qt::black); + int rotate = 0; // 不旋转 +}; +struct AbsAxisPrivate; +class LIB_DECL AbsAxis : public RenderAble { +public: + Q_Ptr2(AbsAxis) + PROP(int, x) + PROP(int, y) + PROP(Qt::Orientation, orientation) + PROP(size_t, pixelSize) + PROP(int, tickLength) + PROP(int, subTickLength) + PROP(QColor, color) + PROP(QChar, formatChar) + PROP(QLocale, locale) + PROP(QString, unitText) + PROP(QFont, unitTextFont) + PROP(QPen, unitTextPen) + PROP(QBrush, unitTextBackgroundBrush) + PROP(int, rotate) // 不旋转 + [[nodiscard]] virtual double pixelToCoord(double pixel, SRC src); + [[nodiscard]] virtual double coordToPixel(double coord, SRC src); + [[nodiscard]] virtual double getTickStep(const Range &range); + [[nodiscard]] virtual int getSubTickCount(double tickStep); + virtual QString getTickLabel(double tick, SRC); + virtual QVector createTickVector(double tickStep, const Range &range); + virtual QVector createSubTickVector(int subTickCount, const QVector &ticks, const Range& range); + virtual QVector createLabelVector(const QVector &ticks, SRC); + double startCoord(SRC=SRC::Auto); + double endCoord(SRC=SRC::Auto); + Range coordRange(SRC=SRC::Auto); + int getPixelPointSize(Range range, SRC=SRC::Cache); + int getPixelPointSize(SRC=SRC::Cache); + template struct BuilderT; +}; +template +struct AbsAxis::BuilderT : protected AbsAxis_Prop { + PROP_BT(int, x) + PROP_BT(int, y) + PROP_BT(Qt::Orientation, orientation) + PROP_BT(size_t, pixelSize) + PROP_BT(int, tickLength) + PROP_BT(int, subTickLength) + PROP_BT(QColor, color) + PROP_BT(QChar, formatChar) + PROP_BT(QLocale, locale) + PROP_BT(QString, unitText) + PROP_BT(QFont, unitTextFont) + PROP_BT(QPen, unitTextPen) + PROP_BT(QBrush, unitTextBackgroundBrush) + PROP_BT(int, rotate) // 不旋转 + SETTER_T(QString, layerName, "axis") +protected: + Plot *plot{}; + void set(AbsAxis* ret); +}; +} + diff --git a/YSGraphic_Core/Axis/AbsAxis_p.h b/YSGraphic_Core/Axis/AbsAxis_p.h new file mode 100644 index 0000000..d537521 --- /dev/null +++ b/YSGraphic_Core/Axis/AbsAxis_p.h @@ -0,0 +1,154 @@ +#pragma once + +#include "AbsAxis.h" + +namespace YSG { + struct AbsAxisRenderState : RenderState, AbsAxis_Prop { + + size_t pixelSize{}; + int numberPrecision = 2; + double coordStart = 0; + double coordLength = 20; + void scaleRange(double factor, double center) { + coordStart = (coordStart - center) * factor + center; + coordLength *= factor; + } + }; + struct AbsAxisTempData : TempData {}; + + struct AbsAxisPrivate : RenderData { + D_Ptr(AbsAxis) + int mTickCount = 2; + std::atomic pixelStart{}; + double getMantissa(double input, double *magnitude=nullptr); + [[nodiscard]] double cleanMantissa(double input); + [[nodiscard]] double pickClosest(double target, const QVector &candidates); + + + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + } + + void draw(QPainter* painter) override { + AbsAxisRenderState* s = renderState(); + pixelStart = s->orientation == Qt::Horizontal ? s->x : s->y; + Range range = {s->coordStart, s->coordStart + s->coordLength}; + AbsAxis* q = this->q(); + double tickStep = q->getTickStep(range); + QVector ticks = q->createTickVector(tickStep, range); + int subTickCount = q->getSubTickCount(tickStep); + QVector subTicks = q->createSubTickVector(subTickCount, ticks, range); + QVector labels = q->createLabelVector(ticks, SRC::Render); + while (!ticks.empty() && !range.contain(ticks.last())) { + ticks.removeLast(); + } + while (!subTicks.empty() && !range.contain(subTicks.last())) { + subTicks.removeLast(); + } + QFontMetrics fm(painter->font()); + int h = fm.height(); + painter->setPen(s->color); + if(s->orientation == Qt::Horizontal) { + painter->drawLine(s->x, s->y, s->x + s->pixelSize, s->y); + int n = ticks.size(); + for(int i = 0; i < n; ++i) { + double tick = ticks[i]; + double x = q->coordToPixel(tick, SRC::Render); + painter->drawLine((int)x, s->y, (int)x, s->y + s->tickLength); + const QString& label = labels[i]; + QPoint start((int)x, s->y + s->tickLength); + painter->translate(start); + painter->rotate(s->rotate); + painter->drawText(0, 0, label); + painter->resetTransform(); + } + for(double tick : subTicks) { + double x = q->coordToPixel(tick, SRC::Render); + painter->drawLine((int)x, s->y, (int)x, s->y + s->subTickLength); + } + } else if(s->orientation == Qt::Vertical) { + painter->drawLine(s->x, s->y, s->x, s->y + s->pixelSize); + int n = ticks.size(); + for(int i = 0; i < n; ++i) { + double tick = ticks[i]; + double y = q->coordToPixel(tick, SRC::Render); + const QString& label = labels[i]; + painter->drawLine(s->x, (int)y, s->x + s->tickLength, (int)y); + QPoint start = QPoint(s->x + s->tickLength * 2, (int)y + h/2); + painter->translate(start); + painter->rotate(s->rotate); + painter->drawText(0, 0, label); + painter->resetTransform(); + } + for(double tick : subTicks) { + double y = q->coordToPixel(tick, SRC::Render); + painter->drawLine(s->x, (int)y, s->x + s->subTickLength, (int)y); + } + } else { + qDebug() << "error s->mOrientation == " << s->orientation; + } + if(s->unitText.isEmpty()) return; + double w = fm.horizontalAdvance(s->unitText); + + if(s->orientation == Qt::Horizontal) { + double x = pixelStart + s->pixelSize - w; + double y = s->y - h; + QRectF area(x, y, w, h); + painter->fillRect(area, s->unitTextBackgroundBrush); + painter->setPen(s->unitTextPen); + painter->setFont(s->unitTextFont); + painter->drawText(area, Qt::AlignCenter, s->unitText); + } else if (s->orientation == Qt::Vertical) { + double x = s->x; + double y = pixelStart; + QRectF area(x, y, w, h); + painter->fillRect(area, s->unitTextBackgroundBrush); + painter->setPen(s->unitTextPen); + painter->setFont(s->unitTextFont); + painter->drawText(area, Qt::AlignCenter, s->unitText); + } + } + + protected: + [[nodiscard]] virtual double pixelToCoord(double pixel, SRC src) { + auto s = static_cast(getState(src)); + return s->coordStart + s->coordLength * (pixel - (double)pixelStart)/(double)s->pixelSize; + } + [[nodiscard]] virtual double coordToPixel(double coord, SRC src) { + auto s = static_cast(getState(src)); + return pixelStart + (double)s->pixelSize * (coord - s->coordStart)/s->coordLength; + } + virtual double getTickStep(const Range &range){ + double exactStep = range.size()/double(mTickCount+1e-10); // mTickCount ticks on average, the small addition is to prevent jitter on exact integers + return cleanMantissa(exactStep); + } + virtual int getSubTickCount(double tickStep); + virtual QVector createTickVector(double tickStep, const Range &range); + virtual QVector createSubTickVector(int subTickCount, const QVector &ticks, const Range& range); + virtual QString getTickLabel(double tick, SRC); + virtual QVector createLabelVector(const QVector &ticks, SRC); + }; + + template + void AbsAxis::BuilderT::set(AbsAxis* ret) { + ret->init(plot, layerName); + AbsAxisPrivate* pd = ret->d(); + AbsAxisRenderState* sc = pd->renderStateCache(); + PROP_RT(int, x) + PROP_RT(int, y) + PROP_RT(Qt::Orientation, orientation) + PROP_RT(int, tickLength) + PROP_RT(int, subTickLength) + PROP_RT(QColor, color) + PROP_RT(QChar, formatChar) + PROP_RT(QLocale, locale) + PROP_RT(QString, unitText) + PROP_RT(QFont, unitTextFont) + PROP_RT(QPen, unitTextPen) + PROP_RT(QBrush, unitTextBackgroundBrush) + PROP_RT(int, rotate) // 不旋转 + } + +} + diff --git a/YSGraphic_Core/Axis/Axis.cpp b/YSGraphic_Core/Axis/Axis.cpp new file mode 100644 index 0000000..eea687e --- /dev/null +++ b/YSGraphic_Core/Axis/Axis.cpp @@ -0,0 +1,24 @@ +#include "Axis_p.h" + +namespace YSG { + Q_Ptr_cpp(Axis) + PROP_P(Axis, int, numberPrecision) + PROP_P(Axis, double, coordStart) + PROP_P(Axis, double, coordLength) + void Axis::set_coordRange(Range range){ + INIT_SET(AbsAxis) + sc->coordStart = range.lower; + sc->coordLength = range.upper - range.lower; + } + Axis::Builder::Builder(Plot* plot, Qt::Orientation orientation) { + this->plot = plot; + this->orientation = orientation; + } + Axis* Axis::Builder::build() { + Axis* ret = new Axis(); + Axis::BuilderT::set(ret); + return ret; + } + + +} \ No newline at end of file diff --git a/YSGraphic_Core/Axis/Axis.h b/YSGraphic_Core/Axis/Axis.h new file mode 100644 index 0000000..856e79c --- /dev/null +++ b/YSGraphic_Core/Axis/Axis.h @@ -0,0 +1,52 @@ +#pragma once +#include "AbsAxis.h" + +namespace YSG { + struct AxisRenderState; + struct AxisTempData; + struct AxisPrivate; + class LIB_DECL Axis : public AbsAxis { + Q_Ptr2(Axis) + public: + PROP(int, numberPrecision) + PROP(double, coordStart) + PROP(double, coordLength) + void set_coordRange(Range range); + template struct BuilderT; + struct Builder; + }; + + template + struct Axis::BuilderT : AbsAxis::BuilderT{ + PROP_BT(int, numberPrecision) + // PROP_BT(double, coordStart) + // PROP_BT(double, coordLength) + That& set_coordRange(Range r) { + That& t = *static_cast(this); + t.coordStart = std::move(r.lower); + t.coordLength = std::move(r.length()); + return t; + } + That& set_use_wheel(bool useWheel) { + That& t = *static_cast(this); + this->useWheel = useWheel; + return t; + } + That& set_use_drag(bool useDrag) { + That& t = *static_cast(this); + this->useDrag = useDrag; + return t; + } + protected: + void set(Axis* ret); + int numberPrecision = 2; + bool useWheel = false; + bool useDrag = false; + }; + + struct LIB_DECL Axis::Builder : BuilderT{ + Builder(Plot *plot, Qt::Orientation orientation); + Axis* build(); + }; +} + diff --git a/YSGraphic_Core/Axis/Axis_p.h b/YSGraphic_Core/Axis/Axis_p.h new file mode 100644 index 0000000..bafadc1 --- /dev/null +++ b/YSGraphic_Core/Axis/Axis_p.h @@ -0,0 +1,80 @@ +#pragma once + +#include "Axis.h" +#include "AbsAxis_p.h" +#include + +namespace YSG { + struct AxisRenderState : AbsAxisRenderState{ + + }; + + struct AxisTempData : AbsAxisTempData { + + }; + + struct AxisPrivate : AbsAxisPrivate { + D_Ptr(Axis) + bool useWheel = false; + bool useDrag = false; + protected: + bool selectTest(const QPointF& pos) override {return true;} + void wheelEvent(QWheelEvent* event) override { + if(useWheel) { + const double delta = event->angleDelta().y(); + double wheelSteps = delta / 120.0; + double factor = std::pow(0.8, wheelSteps); + auto rc = renderStateCache(); + double cursor_coord = pixelToCoord(event->position().x(), SRC::Cache); + double new_coordLength = rc->coordLength * factor; + double new_coordStart = cursor_coord - new_coordLength * (cursor_coord - rc->coordStart) / rc->coordLength; + rc->coordStart = new_coordStart; + rc->coordLength = new_coordLength; + } + } + bool dragging = false; + QPoint startGlobalPos, startPos; + double startCoordValue; + double pixelPreCoord; + void mousePressEvent(QMouseEvent* event) override { + if(!useDrag) return; + if(event->button() != Qt::LeftButton) return; + if(dragging) return; + dragging = true; + startGlobalPos = QCursor::pos(); + startPos = event->pos(); + auto rc = renderStateCache(); + startCoordValue = rc->coordStart; + pixelPreCoord = rc->coordLength/static_cast(rc->pixelSize); + } + void mouseMoveEvent(QMouseEvent* event) override { + if(!useDrag) return; + if(!(event->buttons() & Qt::LeftButton)) return; + if(!dragging) return; + QPoint delt = startGlobalPos - QCursor::pos(); + auto rc = renderStateCache(); + auto coordDelt = pixelPreCoord * (rc->orientation == Qt::Horizontal ? delt.x() :delt.y()); + rc->coordStart = startCoordValue + coordDelt; + } + void mouseReleaseEvent(QMouseEvent* event) override { + if(!useDrag) return; + if(event->button() != Qt::LeftButton) return; + if(!dragging) return; + dragging = false; + } + }; + + template + void Axis::BuilderT::set(Axis* ret) { + AbsAxis::BuilderT::set(ret); + AxisPrivate* pd = ret->d(); + AxisRenderState* sc = pd->renderStateCache(); + PROP_RT(int, numberPrecision) + PROP_RT(double, coordStart) + PROP_RT(double, coordLength) + pd->useWheel = useWheel; + pd->useDrag = useDrag; + } +} + + diff --git a/YSGraphic_Core/Axis/FrequentAxis.cpp b/YSGraphic_Core/Axis/FrequentAxis.cpp new file mode 100644 index 0000000..dbdd460 --- /dev/null +++ b/YSGraphic_Core/Axis/FrequentAxis.cpp @@ -0,0 +1,16 @@ +#include "FrequentAxis_p.h" +#include "Axis_p.h" + + +namespace YSG { + Q_Ptr_cpp(FrequentAxis) + FrequentAxis::Builder::Builder(Plot* plot, Qt::Orientation orientation) { + this->plot = plot; + this->orientation = orientation; + } + FrequentAxis* FrequentAxis::Builder::build() { + auto ret = new FrequentAxis(); + set(ret); + return ret; + } +} \ No newline at end of file diff --git a/YSGraphic_Core/Axis/FrequentAxis.h b/YSGraphic_Core/Axis/FrequentAxis.h new file mode 100644 index 0000000..91d6ce9 --- /dev/null +++ b/YSGraphic_Core/Axis/FrequentAxis.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Axis.h" +namespace YSG { + struct FrequentAxisPrivate; + class LIB_DECL FrequentAxis : public Axis { + public: + Q_Ptr2(FrequentAxis) + struct Builder; + }; + + struct LIB_DECL FrequentAxis::Builder : BuilderT{ + Builder(Plot *plot, Qt::Orientation orientation); + FrequentAxis* build(); + }; +} + diff --git a/YSGraphic_Core/Axis/FrequentAxis_p.h b/YSGraphic_Core/Axis/FrequentAxis_p.h new file mode 100644 index 0000000..bc24da2 --- /dev/null +++ b/YSGraphic_Core/Axis/FrequentAxis_p.h @@ -0,0 +1,79 @@ +#pragma once + +#include "FrequentAxis.h" +#include "AbsAxis_p.h" +#include "Axis_p.h" +namespace YSG { + struct FrequentAxisTempData : AxisTempData {}; + struct FrequentAxisRenderState : AxisRenderState {}; + struct FrequentAxisPrivate : AxisPrivate { + D_Ptr(FrequentAxis) + enum XUnitType { + UnKnow = -1, Hz = 0, KHz = 3, MHz = 6, GHz = 9 + } xUnitType = UnKnow; + static QString getXUnitTypeString(XUnitType unitType) { + switch (unitType) { + case Hz: + return "Hz"; + case KHz: + return "KHz"; + case MHz: + return "MHz"; + case GHz: + return "GHz"; + default: + return "Unknown"; + } + } + QVector createLabelVector(const QVector &ticks, SRC src) override { + if (!ticks.empty()) { + double range = ticks.last() - ticks.first(); + XUnitType beforeUnitType = xUnitType; + if (range >= 1e9) { + xUnitType = GHz; + } else if (range >= 1e6) { + xUnitType = MHz; + } else if (range >= 1e3) { + xUnitType = KHz; + } else { + xUnitType = Hz; + } + if (beforeUnitType != xUnitType) { + renderState()->unitText = "频率/" + getXUnitTypeString(xUnitType); + { + SpinLockGuard _guard (&mBufferLock); + renderStateCache()->unitText = "频率/" + getXUnitTypeString(xUnitType); + } + } + } + QVector result; + result.reserve(ticks.size()); + for (const double &tickCoord: ticks) { + result.append(getTrueValue(getTickLabel(tickCoord, src))); + } + return result; + } + + QString getTrueValue(QString value) { + value.replace(",", ""); + int moveNum = xUnitType; + AbsAxisRenderState* s = renderState(); + int precision = s->numberPrecision; + int dotIndex = value.indexOf('.'); + if (dotIndex == -1) { + dotIndex = value.size(); + } + QString number = value.left(dotIndex) + value.mid(dotIndex + 1); + int afterDotIndex = dotIndex - moveNum; + if (afterDotIndex < 1) { + for (int i = 0; i < 1 - afterDotIndex; ++i) { + number.prepend('0'); + } + afterDotIndex = 1; + } + number.insert(afterDotIndex, '.'); + return QString::number(number.toDouble(), 'd', precision); + } + }; + +} diff --git a/YSGraphic_Core/Axis/TimeAxis.cpp b/YSGraphic_Core/Axis/TimeAxis.cpp new file mode 100644 index 0000000..fb22f91 --- /dev/null +++ b/YSGraphic_Core/Axis/TimeAxis.cpp @@ -0,0 +1,41 @@ +#include "TimeAxis_p.h" +#include "../base/Plot.h" + +namespace YSG { + Q_Ptr_cpp(TimeAxis) + PROP_P(TimeAxis, int, timePointSize) + PROP_P(TimeAxis, int, tickPixelSpace) + PROP_P(TimeAxis, QString, timeFormat) + PROP_P(TimeAxis, QFont, font) + int TimeAxis::giveData(QTime time) { + if(!ok()) return -1; + std::pair ret = d()->mTimeTicker.getNewDataTickInfo(); + auto td = d()->tempDataCache(); + td->mAllTime.push_back(time); + if(ret.second) { + td->mData.push_front({time, ret.first}); + } + return ret.first; + } + + QTime TimeAxis::tickToTime(int tick, SRC src) { + INIT_GET(TimeAxis) + int offset = tick - (int)renderState->coordStart; + return *(d()->mRingBuffer.list() + offset); + } + TimeAxis::Builder::Builder(Plot* plot, Qt::Orientation orientation) { + this->plot = plot; + this->orientation = orientation; + } + TimeAxis* TimeAxis::Builder::build() { + auto ret = new TimeAxis(); + set(ret); + TimeAxisPrivate* pd = ret->d(); + TimeAxisRenderState* sc = pd->renderStateCache(); + PROP_R(int, timePointSize) + PROP_R(int, tickPixelSpace) + PROP_R(QString, timeFormat) + PROP_R(QFont, font) + return ret; + } +} diff --git a/YSGraphic_Core/Axis/TimeAxis.h b/YSGraphic_Core/Axis/TimeAxis.h new file mode 100644 index 0000000..94fc981 --- /dev/null +++ b/YSGraphic_Core/Axis/TimeAxis.h @@ -0,0 +1,35 @@ +#pragma once + +#include "AbsAxis.h" +namespace YSG { + struct TimeAxisPrivate; + struct TimeTicker; + class LIB_DECL TimeAxis : public AbsAxis { + public: + Q_Ptr2(TimeAxis) + PROP(int, timePointSize) + PROP(int, tickPixelSpace) + PROP(QString, timeFormat) + PROP(QFont, font) + int giveData(QTime time); + QTime tickToTime(int tick, SRC=SRC::Auto); + struct Builder; + }; + + struct TimeAxis_Prop { + int timePointSize = 100; + int tickPixelSpace = 8; + QString timeFormat = "mm:ss.zzz"; + QFont font; + }; + + struct LIB_DECL TimeAxis::Builder : AbsAxis::BuilderT , protected TimeAxis_Prop{ + PROP_B(int, timePointSize) + PROP_B(int, tickPixelSpace) + PROP_B(QString, timeFormat) + PROP_B(QFont, font) + Builder(Plot *plot, Qt::Orientation orientation); + TimeAxis* build(); + }; + +} diff --git a/YSGraphic_Core/Axis/TimeAxis_p.h b/YSGraphic_Core/Axis/TimeAxis_p.h new file mode 100644 index 0000000..229d423 --- /dev/null +++ b/YSGraphic_Core/Axis/TimeAxis_p.h @@ -0,0 +1,192 @@ +#pragma once + +#include "AbsAxis_p.h" +#include "Core/Base/RingBuffer.hpp" +#include "TimeAxis.h" + + +#include + +#include "YSGraphic_Core/base/RingBuffer.hpp" + + +namespace YSG { + + struct TimeTicker { + bool mStartCoordToEndCoord = true; + // 最小值的范围 lower在这里面循环变化 + int mLower = 0, mTimePointSize{}; + int mLowerMin{}, mLowerMax{}; //闭区间 + int mTickStart = 0, mTickSpace = 4; + [[nodiscard]] int upper() const { + return mLower - 1 + mTimePointSize; + } + + void setTickSpace(int tickSpace) { + if(mTickSpace == tickSpace) return; + mTickSpace = tickSpace; + mTickStart = 0; + } + + void setTimePointSize(int timePointSize) { + if(mTimePointSize == timePointSize) return; + mTimePointSize = timePointSize; + // 除2怕溢出 + mLowerMin = std::numeric_limits::min()/2; + mLowerMax = std::numeric_limits::max()/2 - timePointSize + 1; + mTickStart = 0; + } + + std::pair getNewDataTickInfo() { + { // 获取 坐标轴的起点 + if(mStartCoordToEndCoord) { + if(mLower != mLowerMin) mLower--; + else { + mLower = mLowerMax; + } + } else { + if(mLower != mLowerMax) mLower++; + else { + mLower = mLowerMin; + } + } + } + mTickStart = ++mTickStart % mTickSpace; + int endIndex = mLower + mTimePointSize - 1; + auto startIndex = mLower; + if(mTickStart == 1) { + return {mStartCoordToEndCoord ? startIndex : endIndex, true}; + } + return {mStartCoordToEndCoord ? startIndex : endIndex, false}; + } + }; + struct TimeTick { + QTime mTime; + int mTick{}; + }; + + struct TimeAxisTempData : AbsAxisTempData { + std::list mData; + std::list mAllTime; + }; + + + struct TimeAxisRenderState : AbsAxisRenderState, TimeAxis_Prop { + int tickSpace = 4; + int getTickSpaceHint(SRC src = SRC::Cache) { + QFontMetrics fm(font); + double rate = (double)timePointSize/(double)pixelSize; + double ret = rate * (tickPixelSpace + (orientation == Qt::Horizontal ? fm.horizontalAdvance(timeFormat) : fm.height())); + + return std::max(1, (int)ret); + } + }; + + struct TimeAxisPrivate : AbsAxisPrivate { + D_Ptr(TimeAxis) + TimeTicker mTimeTicker{}; + int mTickPixelSpace; + QFont mFont; + QString mTimeFormat; + YSG::RingBuffer mRingBuffer; + std::list mData; + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + auto d = tempData(); + auto s = renderState(); + auto sc = renderStateCache(); + bool refreshSpace = false; + if(mTimeTicker.mTimePointSize != s->timePointSize) { + mTimeTicker.setTimePointSize(s->timePointSize); + mRingBuffer.resize(sizeof(QTime), s->timePointSize, 10 * s->timePointSize); + mData.clear(); + refreshSpace = true; + } + if(mTickPixelSpace != s->tickPixelSpace) { + mTickPixelSpace = s->tickPixelSpace; + refreshSpace = true; + } + if(mFont != s->font) { + mFont = s->font; + refreshSpace = true; + } + if(mTimeFormat != s->timeFormat) { + mTimeFormat = s->timeFormat; + if(s->orientation == Qt::Horizontal) { + refreshSpace = true; + } + } + if(refreshSpace) { + s->tickSpace = s->getTickSpaceHint(); + sc->tickSpace = s->tickSpace; + if(mTimeTicker.mTickSpace != s->tickSpace) { + mTimeTicker.setTickSpace(s->tickSpace); + mData.resize(0); + } + } + s->coordStart = mTimeTicker.mLower; + s->coordLength = mTimeTicker.mTimePointSize; + sc->coordStart = mTimeTicker.mLower; + sc->coordLength = mTimeTicker.mTimePointSize; + + int lowwer = mTimeTicker.mLower; + int upper = mTimeTicker.upper(); + if(lowwer > upper) std::swap(lowwer, upper); + + + for(QTime& time : d->mAllTime) { + mRingBuffer.pushData(&time); + } + d->mAllTime.clear(); + + mData.splice(mData.begin(), d->mData); + + while (!mData.empty()) { + int tick = mData.back().mTick; + if(tick < lowwer || tick > upper) { + mData.pop_back(); + } else { + break; + } + } + + int i = 0; + ticks.clear(); + labels.clear(); + for(TimeTick& t : mData) { + ticks.append(t.mTick); + labels.append(t.mTime.toString(s->timeFormat)); + ++i; + } + } + + + QVector createLabelVector(const QVector& ticks, SRC src) override { + return labels; + } + + QVector createTickVector(double tickStep, const Range& range) override { + return ticks; + } + + QVector createSubTickVector(int subTickCount, const QVector& ticks, const Range& range) override{return {};} + + // 最小值的范围 lower在这里面循环变化 + int mLower = 0, mLowerMin = 0, mLowerMax{}; //闭区间 + int mTickStart = 0; + + QVector labels; + QVector ticks; + + void draw(QPainter* painter) override { + AbsAxisPrivate::draw(painter); + } + + QString getTickLabel(double tick, SRC src) override { + auto renderState = reinterpret_cast(getState(src)); + return q()->tickToTime((int)tick, src).toString(renderState->timeFormat); + } + }; +} + diff --git a/YSGraphic_Core/CacheModel.h b/YSGraphic_Core/CacheModel.h new file mode 100644 index 0000000..c859c35 --- /dev/null +++ b/YSGraphic_Core/CacheModel.h @@ -0,0 +1,16 @@ +#pragma once +#include "GlobalTypes.h" +namespace YSG{ + template + struct CacheModel { + T render, cache; + SpinLock mLock; + void loadCache() { + render = cache; + } + T* getState(SRC src = SRC::Render) { + if(src == SRC::Cache) return &cache; + return &render; + } + }; +} diff --git a/YSGraphic_Core/DemoGallery/AfterglowPlot.h b/YSGraphic_Core/DemoGallery/AfterglowPlot.h new file mode 100644 index 0000000..9d7f9b8 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/AfterglowPlot.h @@ -0,0 +1,51 @@ +#pragma once +#include "Tool.h" + +static QWidget* createAfterglow() { + class AfterglowPlot : public YSG::Plot { + public: + YSG::FrequentAxis *xAxis{}; + YSG::Axis *yAxis{}; + YSG::Afterglow *ag{}; + YSG::MutiSelectRect* msr{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "AfterglowPlot"; + bindRenderThread(mObjectName + "Thread"); + xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal).build(); + yAxis = YSG::Axis::Builder(this, Qt::Vertical).build(); + ag = YSG::Afterglow::Builder(xAxis, yAxis) + .set_frequentRange({0, 100}) + .set_powerRange({0, 100}) + .build(); + msr = YSG::MutiSelectRect::Builder(xAxis, yAxis).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + xAxis->set_x(0); + xAxis->set_y(height() - 1); + xAxis->set_pixelSize(width()); + yAxis->set_x(0); + yAxis->set_y(0); + yAxis->set_pixelSize(height()); + ag->set_frequentPointSize(ag->frequentAxis()->getPixelPointSize()); + ag->set_powerPointSize(ag->powerAxis()->getPixelPointSize()); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = + bw(cw(ag, -1), "余辉图") + + bw(cw(xAxis), "频率轴") + + bw(cw(yAxis),"功率概率轴") + + bw(cw(msr), "多选框"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new AfterglowPlot; + w->init(); + return w; +} + diff --git a/YSGraphic_Core/DemoGallery/AudioPlot.h b/YSGraphic_Core/DemoGallery/AudioPlot.h new file mode 100644 index 0000000..d4945ae --- /dev/null +++ b/YSGraphic_Core/DemoGallery/AudioPlot.h @@ -0,0 +1,50 @@ +#pragma once +#include "Tool.h" + + +static QWidget* createAudioPlot() { + class AudioPlot : public YSG::Plot { + public: + YSG::TimeAxis *xAxis{}; + YSG::Axis *yAxis{}; + YSG::AudioFrequent *audio{}; + YSG::MutiSelectRect* msr{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "AudioPlot"; + bindRenderThread(mObjectName + "Thread"); + xAxis = YSG::TimeAxis::Builder(this, Qt::Horizontal) + .set_tickLength(-10) + .set_subTickLength(-5).build(); + yAxis = YSG::Axis::Builder(this, Qt::Vertical).build(); + audio = YSG::AudioFrequent::Builder(xAxis, yAxis).build(); + msr = YSG::MutiSelectRect::Builder(xAxis, yAxis).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + xAxis->set_x(0); + xAxis->set_y(height() - 1); + xAxis->set_pixelSize(width()); + yAxis->set_x(0); + yAxis->set_y(0); + yAxis->set_pixelSize(height()); + audio->set_timePointSize(xAxis->getPixelPointSize()); + } + + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = + bw( cw(audio, -1), "音频图") + + bw(cw(yAxis),"功率y轴") + + bw(cw(xAxis), "时间轴") + + bw(cw(msr), "多选框"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new AudioPlot; + w->init(); + return w; +} diff --git a/YSGraphic_Core/DemoGallery/DemoGallery.cpp b/YSGraphic_Core/DemoGallery/DemoGallery.cpp new file mode 100644 index 0000000..b290b35 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/DemoGallery.cpp @@ -0,0 +1,198 @@ +#include "DemoGallery.h" + + +#include "YSGraphic_Core/Qt/File_Gather_Widget.h" +#include "YSGraphic_Core/Qt/File_Player_Widget.h" +#include "YSGraphic_Core/Qt/Value_Select.h" + +#include + +using namespace Psc; +QStandardItem* DemoGallery::getFromData(const TData& data) { + auto w = new QWidget; + auto l = new QVBoxLayout(w); + l->setContentsMargins({0, 0, 0, 0}); + l->setSpacing(0); + l->addWidget(data.mPlot); + //l->addStretch(); + mDataMap[data.mName] = w; + return new QStandardItem(data.mName); +} + +QList DemoGallery::getList(const QVector& datas) { + QList ret; + for(const TData& data : datas) { + ret.append(getFromData(data)); + } + return ret; +} + + +void DemoGallery::change_to(const QString& name) { + if (mDataMap.contains(name)) { + QWidget* associatedWidget = mDataMap[name]; + int i= mTabWidget->indexOf(associatedWidget); + if (i == -1) { + // 标签页不存在,添加新的标签页 + mTabWidget->addTab(associatedWidget, name); + mTabWidget->setCurrentIndex(mTabWidget->indexOf(associatedWidget)); + } else { + // 标签页已经存在,设置为当前标签页 + mTabWidget->setCurrentIndex(i); + } + } +} + + + + +class ComboBoxExample : public QWidget { +public: + ComboBoxExample(QWidget* parent = nullptr) : QWidget(parent) { + // 创建一个QComboBox + QComboBox* comboBox = new QComboBox(this); + + // 向QComboBox添加一些选项 + comboBox->addItem("Option 1"); + comboBox->addItem("Option 2"); + comboBox->addItem("Option 3"); + + // 创建一个标签用来显示选中的选项 + QLabel* label = new QLabel("Selected: ", this); + + + QObject::connect(comboBox, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) { + // 这里你可以获取当前选择项的索引 + label->setText("Selected: " + comboBox->currentText()); + }); + + // 布局 + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget(comboBox); + layout->addWidget(label); + + setLayout(layout); + } +}; + +DemoGallery::DemoGallery() : + treeView(new QTreeView(this)), + standardModel(new QStandardItemModel(this)), + mTabWidget(new QTabWidget) + { + mMainLayout = new QHBoxLayout(this); + mMainLayout->setContentsMargins(0, 0, 0, 0); + mMainLayout->setSpacing(0); + + treeView->setFixedWidth(200); + treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); + mMainLayout->addWidget(treeView); + mTabWidget->setTabsClosable(true); + mMainLayout->addWidget(mTabWidget); + //treeView->setItemDelegate(new Delegate); + treeView->setHeaderHidden(true); + QStandardItem *root = standardModel->invisibleRootItem(); + auto plottable = new QStandardItem("plottable"); + plottable->appendRows(getList({ + {"余晖图",createAfterglow()}, + {"音频图", createAudioPlot()}, + {"星座图", createPlanispherePlot()}, + {"频谱图", createSpectrunPlot()}, + {"瀑布图", createWaterFallPlot()}, + {"扫频图", createSweepFrequentPlot()}, + })); + + auto base = new QStandardItem("基础元素"); + base->appendRows(getList({ + {"轴测试", createAxisTestPlot()}, + {"时间轴测试", createTimeAxisTestPlot()}, + {"频率轴测试", createFrequentAxisTestPlot()}, + {"多选框测试", createMutiSelectPlot()}, + })); + + auto t1 = new Byte_Select; + + auto t2 = new Test_File_Gather_Widget; + auto t3 = new Test_File_Play_Widget; + auto custom_widget = new QStandardItem("自定义控件"); + custom_widget->appendRows(getList({ + {"Enum_Select", t1}, + {"Test_File_Gather_Widget", t2}, + {"Test_File_Play_Widget", t3}, + {"表格", create_Table_View()}, + {"树", create_Tree_View()}, + })); + + + auto Qt_raw_widget = new QStandardItem("Qt原生元素"); + + + + + + + + Qt_raw_widget->appendRows(getList({ + // QSizeGrip 一个改变大小的注脚 + // QDesktopWidget 不知道干嘛的 + // QFocusFrame 不知道干嘛的 + + {"QKeySequenceEdit", new QKeySequenceEdit}, + {"ComboBoxExample", new ComboBoxExample}, + //像QWidget 但不知到有什么区别 + {"QPushButton", new QPushButton}, + {"QFrame", new QFrame}, + {"QStatusBar", new QStatusBar}, + {"QGroupBox", new QGroupBox}, + {"QSplashScreen", new QSplashScreen}, + {"QAbstractSlider", new QAbstractSlider}, + {"QRubberBand::Line", new QRubberBand(QRubberBand::Line)}, + {"QRubberBand::Rectangle", new QRubberBand(QRubberBand::Rectangle)}, + {"QProgressBar", new QProgressBar()}, + {"QDialog", new QDialog}, + {"QTabBar", new QTabBar}, + {"QDialogButtonBox", new QDialogButtonBox}, + {"QCalendarWidget", new QCalendarWidget}, + {"QMenuBar", new QMenuBar}, + {"QTabWidget", new QTabWidget}, + {"QOpenGLWidget", new QOpenGLWidget}, + {"QMdiSubWindow", new QMdiSubWindow}, + {"QDockWidget", new QDockWidget}, + {"QAbstractSpinBox", new QAbstractSpinBox}, + //{"QAbstractButton", new QAbstractButton}, + {"QTabBar", new QTabBar}, + {"QComboBox", new QComboBox}, + {"QMainWindow", new QMainWindow}, + {"QMenu", new QMenu}, + //{"QWindowContainer", new QWindowContainer}, + //{"QDesktopScreenWidget", new QDesktopScreenWidget}, + {"QColumnView", new QColumnView}, + {"QWizardPage", new QWizardPage}, + + })); + + //change_to("表格"); + change_to("Test_File_Gather_Widget"); + //change_to("树"); + + root->appendRows({plottable, base, custom_widget, Qt_raw_widget}); + treeView->setModel(standardModel); + treeView->expandAll(); + + + QObject::connect(mTabWidget, &QTabWidget::tabCloseRequested, [this](int index) { + // 处理标签页关闭请求 + qDebug() << "Closing tab at index:" << index; + + // 从 QTabWidget 中移除指定的标签页 + mTabWidget->removeTab(index); + }); + + QObject::connect(treeView, &QTreeView::clicked, [this](const QModelIndex& index) { + QStandardItem* item = this->standardModel->itemFromIndex(index); + if (item) { + QString itemName = item->text(); + change_to(itemName); + } + }); + } diff --git a/YSGraphic_Core/DemoGallery/DemoGallery.h b/YSGraphic_Core/DemoGallery/DemoGallery.h new file mode 100644 index 0000000..b1ec58f --- /dev/null +++ b/YSGraphic_Core/DemoGallery/DemoGallery.h @@ -0,0 +1,50 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include "AfterglowPlot.h" +#include "AudioPlot.h" +#include "Norml_test.h" +#include "PlanispherePlot.h" +#include "SpectrumPlot.h" +#include "SweepFrequentPlot.h" +#include "WaterFallPlot.h" +#include "base/AxisTest.h" +#include "base/FrequentAxisTest.h" +#include "base/MutiSelectTest.h" +#include "base/TimeAxisTest.h" +#include "../component/Table/Table_View.h" +#include "../component/Tree/Tree_View.h" +class QTreeView; +class QStandardItemModel; +class QStandardItem; + + + + +class DemoGallery : public QWidget +{ +public: + DemoGallery(); + QTreeView *treeView; + QStandardItemModel *standardModel; + QHBoxLayout *mMainLayout; + QTabWidget *mTabWidget{}; + QWidget* mCurWidget{}; + struct TData { + QString mName; + QWidget* mPlot; + }; + QMap mDataMap; + QStandardItem* getFromData(const TData& data); + QList getList(const QVector& datas); + void change_to(const QString& name); +}; + + + diff --git a/YSGraphic_Core/DemoGallery/Norml_test.h b/YSGraphic_Core/DemoGallery/Norml_test.h new file mode 100644 index 0000000..c1845a3 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/Norml_test.h @@ -0,0 +1,358 @@ +#pragma once +#include "../component/Table/Table_View.h" +#include "Tool.h" +#include "../component/Table/Base_Info.h" +#include "../component/Table/Base_Table_Data.h" + +namespace Psc { +// +// static QVector cw(Table_View* view) { +// QVector ret; +// ret.append({ +// YSG::createEditWidget("隐藏行", [view, m](const int& value) { +// m->row_info_list[value]->hide = true; +// view->update(); +// }, 1), +// YSG::createEditWidget("显示行", [view, m](const int& value) { +// m->row_info_list[value]->hide = false; +// view->update(); +// }, 1), +// YSG::createEditWidget("隐藏列", [view, m](const int& value) { +// m->col_info_list[value]->hide = true; +// view->update(); +// }, 1), +// YSG::createEditWidget("显示列", [view, m](const int& value) { +// m->col_info_list[value]->hide = false; +// view->update(); +// }, 1), +// +// +// YSG::createEditWidget_List("合并单元格", [view, m](const QVector& values) { +// int row = values[0]; +// int col = values[1]; +// int row_span = values[2]; +// int col_span = values[3]; +// m->merge(row, col, row_span, col_span); +// view->update(); +// }, {4, 4, 4, 4}), +// YSG::createEditWidget_List("拆分单元格", [view, m](const QVector& values) { +// int row = values[0]; +// int col = values[1]; +// int row_span = values[2]; +// int col_span = values[3]; +// m->split(row, col, row_span, col_span); +// view->update(); +// }, {4, 4, 2, 4}), +// +// YSG::createEditWidget_List("添加行", [view, m](const QVector& values) { +// int pos = values[0]; +// int num = values[1]; +// int col_num = m->col_info_list.size(); +// QVector row_infos; +// for (int i = 0; i < num; i++) +// { +// auto info = std::make_shared(10, "asdasdasd"); +// row_infos.append(info); +// } +// QVector> data; +// for (int i = 0; i < num; i++) +// { +// QVector row; +// for (int j = 0; j < col_num; j++) +// { +// auto cur = std::make_shared(); +// cur->content = "2222"; +// row.append(cur); +// } +// data.append(row); +// } +// +// m->insert_row(pos, row_infos, data); +// view->update(); +// }, {4, 1}), +// +// YSG::createEditWidget_List("添加列", [view, m](const QVector& values) { +// int pos = values[0]; +// int num = values[1]; +// int row_num = m->row_info_list.size(); +// QVector col_infos; +// for (int i = 0; i < num; i++) +// { +// auto info = std::make_shared(10, "asdasdasd"); +// col_infos.append(info); +// } +// QVector> data; +// for (int i = 0; i < num; i++) +// { +// QVector col; +// for (int j = 0; j < row_num; j++) +// { +// auto cur = std::make_shared(); +// cur->content = "2222"; +// col.append(cur); +// } +// data.append(col); +// } +// +// m->insert_col(pos, col_infos, data); +// view->update(); +// }, {5, 1}), +// +// YSG::createEditWidget_List("重新设置所有行列内容", [view, m](const QVector& values) { +// for (int i = 0; i < m->total_col(); i++) +// { +// m->col_info_list[i]->content = QString("%1").arg(i); +// } +// for (int i = 0; i < m->total_row(); i++) +// { +// m->row_info_list[i]->content = QString("%1").arg(i); +// } +// for (int i = 0; i < m->total_row(); i++) +// { +// +// for (int j = 0; j < m->total_col(); j++) +// { +// auto d = m->data[i][j]; +// d->content = QString("[%1,%2]").arg(i).arg(j); +// } +// } +// view->update(); +// }, {}), +// }); +// +// return ret; +// } + + + static QVector cw(Table_View* view) { + QVector ret; + auto m = dynamic_cast(view->model); + ret.append({ + YSG::createEditWidget("隐藏行", [view, m](const int& value) { + m->get_row_info(value)->hide = true; + view->update(); + }, 1), + YSG::createEditWidget("显示行", [view, m](const int& value) { + m->get_row_info(value)->hide = false; + view->update(); + }, 1), + YSG::createEditWidget("隐藏列", [view, m](const int& value) { + m->get_col_info(value)->hide = true; + view->update(); + }, 1), + YSG::createEditWidget("显示列", [view, m](const int& value) { + m->get_col_info(value)->hide = false; + view->update(); + }, 1), + + + YSG::createEditWidget_List("合并单元格", [view, m](const QVector& values) { + int row = values[0]; + int col = values[1]; + int row_span = values[2]; + int col_span = values[3]; + m->merge(row, col, row_span, col_span); + view->update(); + }, {4, 4, 4, 4}), + YSG::createEditWidget_List("拆分单元格", [view, m](const QVector& values) { + int row = values[0]; + int col = values[1]; + int row_span = values[2]; + int col_span = values[3]; + m->split(row, col, row_span, col_span); + view->update(); + }, {4, 4, 2, 4}), + + YSG::createEditWidget_List("添加行", [view, m](const QVector& values) { + int pos = values[0]; + int num = values[1]; + int col_num = m->total_col(); + QVector row_infos; + for (int i = 0; i < num; i++) + { + auto info = new Row_Info(10, "asdasdasd"); + row_infos.append(info); + } + QVector> data; + for (int i = 0; i < num; i++) + { + QVector row; + for (int j = 0; j < col_num; j++) + { + auto cur = new Table_Paint_Data(""); + cur->content = "2222"; + row.append(cur); + } + data.append(row); + } + + m->insert_row(pos, row_infos, data, Edge_Expand_Type::None); + view->update(); + }, {4, 1}), + + YSG::createEditWidget_List("添加列", [view, m](const QVector& values) { + int pos = values[0]; + int num = values[1]; + int row_num = m->total_row(); + QVector col_infos; + for (int i = 0; i < num; i++) + { + auto info = new Col_Info(10, "asdasdasd"); + col_infos.append(info); + } + QVector> data; + for (int i = 0; i < num; i++) + { + QVector col; + for (int j = 0; j < row_num; j++) + { + auto cur = new Table_Paint_Data(""); + cur->content = "2222"; + col.append(cur); + } + data.append(col); + } + + + + m->insert_col(pos, col_infos, data, Edge_Expand_Type::None); + view->update(); + }, {5, 1}), + + YSG::createEditWidget_List("重新设置所有行列内容", [view, m](const QVector& values) { + for (int i = 0; i < m->total_col(); i++) + { + m->get_col_info(i)->content = QString("%1").arg(i); + } + for (int i = 0; i < m->total_row(); i++) + { + m->get_row_info(i)->content = QString("%1").arg(i); + } + for (int i = 0; i < m->total_row(); i++) + { + + for (int j = 0; j < m->total_col(); j++) + { + auto d = (Table_Paint_Data*)m->get_table_data(i, j); + d->content = QString("[%1,%2]").arg(i).arg(j); + } + } + view->update(); + }, {}), + }); + + return ret; + } + inline Table_View* create_Table_View2() { + struct Table_View2 : Table_View { + QMenu *mMenu{}; + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + //mMenu = createMenu_Normal(this, cw(this)); + mMenu = createMenu_Normal(this, cw(this)); + + } + mMenu->exec(event->globalPos()); + } + }; + auto ret = new Table_View2; + int row_num = 10; + int col_num = 10; + // int row_num = 14; + // int col_num = 15; + + auto m = new Memory_Table_Model(); + ret->model = m; + + + QVector col_infos; + for (int i = 0; i < col_num; ++i) + { + col_infos.push_back(new Col_Info(80, QString::number(i))); + } + QVector> data; + data.resize(col_infos.size()); + m->insert_col(0, col_infos, data, Edge_Expand_Type::None); + + + for (int i = 0; i < row_num; ++i) + { + auto row_info = new Row_Info(50, QString::number(i)); + QVector row; + for (int j = 0; j < col_num; ++j) + { + auto d = new Table_Paint_Data(""); + d->parent = d; + d->content = QString("(%1,%2)").arg(i).arg(j); + row.push_back(d); + } + m->insert_row(m->total_row(), {row_info}, {row}, Edge_Expand_Type::Positive); + } + + + + + + + return ret; + } + + inline Table_View* create_Table_View() { + struct Table_View2 : Table_View { + QMenu *mMenu{}; + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + //mMenu = createMenu_Normal(this, cw(this)); + mMenu = createMenu_Normal(this, cw(this)); + + } + mMenu->exec(event->globalPos()); + } + }; + auto ret = new Table_View2; + int row_num = 10; + int col_num = 10; + // int row_num = 14; + // int col_num = 15; + + auto m = new Memory_Table_Model; + ret->model = m; + + for (int i = 0; i < col_num; ++i) + { + m->col_info_list.push_back(new Col_Info(80, QString::number(i))); + } + for (int i = 0; i < row_num; ++i) + { + m->row_info_list.push_back(new Row_Info(50, QString::number(i))); + } + for (int i = 0; i < col_num; ++i) + { + QVector row; + for (int j = 0; j < row_num; ++j) + { + auto d = new Table_Paint_Data(""); + d->parent = d; + d->content = QString("(%1,%2)").arg(i).arg(j); + row.push_back(d); + } + m->data.push_back(row); + } + + { + auto t = new Widget_Data(); + t->widget = new QPushButton("222222"); + m->data[2][2] = t; + } + + //ret->model->merage(2, 2, 4, 4); + + // + // ret->model->row_info_list[2]->hide = true; + // ret->model->col_info_list[2]->hide = true; + // ret->model->col_info_list[3]->hide = true; + // ret->model->col_info_list[4]->hide = true; + return ret; + } +} + diff --git a/YSGraphic_Core/DemoGallery/PlanispherePlot.h b/YSGraphic_Core/DemoGallery/PlanispherePlot.h new file mode 100644 index 0000000..0372883 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/PlanispherePlot.h @@ -0,0 +1,62 @@ +#pragma once +#include "Tool.h" + +static QWidget* createPlanispherePlot() { + class TimeAxis; + class PlanispherePlot : public YSG::Plot { + public: + YSG::Axis *xAxis{}; + YSG::Axis *yAxis{}; + YSG::Planisphere *mPlanisphere{}; + YSG::MutiSelectRect *msr{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "PlanispherePlot"; + bindRenderThread(mObjectName + "Thread"); + + + xAxis = YSG::Axis::Builder(this, Qt::Horizontal) + .set_tickLength(-10) + .set_subTickLength(-5).build(); + + yAxis = YSG::Axis::Builder(this, Qt::Vertical).build(); + + + mPlanisphere = YSG::Planisphere::Builder(xAxis, yAxis) + .set_layerName("plottable") + .set_continueMillisecond(2000).build(); + + + msr = YSG::MutiSelectRect::Builder(xAxis, yAxis).build(); + + + } + protected: + void resizeEvent(QResizeEvent* event) override { + xAxis->set_x(0); + xAxis->set_y(height() - 1); + xAxis->set_pixelSize(width()); + yAxis->set_x(0); + yAxis->set_y(0); + yAxis->set_pixelSize(height()); + mPlanisphere->setToAxisCenter(); + } + + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = + bw(cw(mPlanisphere, -1), "星座图") + + bw(cw(xAxis), "I轴") + + bw(cw(yAxis), "Q轴"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new PlanispherePlot; + w->init(); + + + return w; +} diff --git a/YSGraphic_Core/DemoGallery/SpectrumPlot.h b/YSGraphic_Core/DemoGallery/SpectrumPlot.h new file mode 100644 index 0000000..5817572 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/SpectrumPlot.h @@ -0,0 +1,64 @@ +#pragma once +#include "Tool.h" + +static QWidget* createSpectrunPlot() { + class SpectrumPlot : public YSG::Plot { + public: + YSG::FrequentAxis *xAxis{}; + YSG::Axis *yAxis{}; + YSG::MutiSelectRect* msr{}; + YSG::Spectrum *sp{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "SpectrumPlot"; + bindRenderThread(mObjectName + "Thread"); + xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal) + .set_coordRange({0, 100}) + .set_tickLength(-10) + .set_subTickLength(-5) + .set_use_wheel(true) + .set_use_drag(true) + .build(); + + yAxis = YSG::Axis::Builder(this, Qt::Vertical) + .set_coordRange({100, 0}) + .set_unitText("功率") + .set_use_drag(true) + .build(); + + sp = YSG::Spectrum::Builder(xAxis, yAxis) + .set_frequentRange({0, 100}) + .build(); + + msr = YSG::MutiSelectRect::Builder(xAxis, yAxis).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + int leftMargin = 20, rightMargin = 20, topMargin = 20, bottomMargin = 20; + int w = width() - leftMargin - rightMargin; + int h = height() - topMargin - bottomMargin; + xAxis->set_x(leftMargin); + xAxis->set_y(height() - bottomMargin); + xAxis->set_pixelSize(w); + yAxis->set_x(leftMargin); + yAxis->set_y(topMargin); + yAxis->set_pixelSize(h); + sp->set_frequentPointSize(sp->frequentAxis()->getPixelPointSize()); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = + bw(cw(sp, -1), "频谱图") + + bw(cw(xAxis), "频率x轴") + + bw(cw(yAxis), "功率y轴") + + bw(cw(msr), "多选框"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new SpectrumPlot; + w->init(); + return w; +} diff --git a/YSGraphic_Core/DemoGallery/SweepFrequentPlot.h b/YSGraphic_Core/DemoGallery/SweepFrequentPlot.h new file mode 100644 index 0000000..100e83c --- /dev/null +++ b/YSGraphic_Core/DemoGallery/SweepFrequentPlot.h @@ -0,0 +1,59 @@ +#pragma once +#include "Tool.h" + +static QWidget* createSweepFrequentPlot() { + class SweepFrequentPlot : public YSG::Plot { + public: + YSG::Axis *xAxis{}; + YSG::Axis *yAxis{}; + YSG::MutiSelectRect *msr{}; + YSG::SweepFrequent* sf{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "AxisTestPlot"; + bindRenderThread(mObjectName + "Thread"); + + xAxis = YSG::Axis::Builder(this, Qt::Horizontal) + .set_coordRange({0, 100}) + .set_tickLength(-10).build(); + + yAxis = YSG::Axis::Builder(this, Qt::Vertical) + .set_coordRange({100, 0}) + .build(); + + + sf = YSG::SweepFrequent::Builder(xAxis, yAxis) + .set_blockNum(200) + .set_blockFrequentPointSize(20) + .set_frequentRange({0, 100}).build(); + + msr = YSG::MutiSelectRect::Builder(xAxis, yAxis).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + xAxis->set_x(0); + xAxis->set_y(height() - 1); + xAxis->set_pixelSize(width()); + yAxis->set_x(0); + yAxis->set_y(0); + yAxis->set_pixelSize(height()); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = + bw(cw(sf, -1), "扫频图") + + bw(cw(xAxis), "x轴") + + bw(cw(yAxis), "y轴") + + bw(cw(msr), "多选框"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new SweepFrequentPlot; + w->init(); + return w; +} + + diff --git a/YSGraphic_Core/DemoGallery/Tool.h b/YSGraphic_Core/DemoGallery/Tool.h new file mode 100644 index 0000000..65f9858 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/Tool.h @@ -0,0 +1,737 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include "../GenerateMockData.h" +#include "../RenderAble.h" +#include "../base/Plot.h" +#include "../GlobalTypes.h" +#include +#include +#include +#include "../base/MutiSelectRect.h" +#include "../plottable/Afterglow.h" +#include "../plottable/AudioFrequent.h" +#include "../plottable/Planisphere.h" +#include "../plottable/WaterFall.h" +#include "../plottable/Spectrum.h" +#include "../plottable/SweepFrequent.h" +#include "../Axis/TimeAxis.h" +#include "../Axis/AbsAxis.h" +#include "../Axis/FrequentAxis.h" +#include +#include +#include +#include +#include +#include "../base/SelectColorDialog/SelectColorDialog.h" + +namespace YSG { + + + static QColor getColor(int lowest, int highest) { + int r = QRandomGenerator::global()->bounded(lowest, highest); + int g = QRandomGenerator::global()->bounded(lowest, highest); + int b = QRandomGenerator::global()->bounded(lowest, highest); + return {r, g, b}; + } + + static QString getBackgroundStyleSheet() { + QString color = QString("background-color: %1;").arg(getColor(176, 256).name()); + return color; + } + + static QString getButtonStyleSheet() { + QString color = QString("background-color: %1; color: #000000;").arg(getColor(200, 256).name()); + return color; + } + + template + QString getInitValue(const T& value) { + return QString::number(value); + } + template<> + inline QString getInitValue(const QString& value) { + return value; + } + + template + T getValueFromString(const QString& value) { + return value.toDouble(); + } + template<> + inline QString getValueFromString(const QString& value) { + return value; + } + + + template + static QWidget* createEditWidget_List(const QString& buttonText, + const std::function&)>& click, const QVector& initValues) { + auto container = new QWidget; + auto layout = new QHBoxLayout(container); + layout->setAlignment(Qt::AlignLeft); + layout->setContentsMargins(0 ,0, 0, 0); + auto confirm = new QPushButton(buttonText); + // QApplication* app = (QApplication*)QApplication::instance(); + // qDebug() << "Global StyleSheet:" << app->styleSheet(); + confirm->setStyleSheet(getButtonStyleSheet()); + + // 清除 QSS + + + QFont font; + font.setFamily("Segoe UI"); + confirm->setFont(font); + confirm->setMinimumWidth(QFontMetrics(confirm->font()).horizontalAdvance(buttonText) + 8); + confirm->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + layout->addWidget(confirm); + QVector lineEditList; + for(const T& initValue : initValues) { + QString initValueStr = getInitValue(initValue); + auto curEdit = new QLineEdit(initValueStr); + curEdit->setStyleSheet(getButtonStyleSheet()); + // curEdit->setMaximumWidth(50); + auto fm = QFontMetrics(QFont()); + int width = fm.horizontalAdvance(initValueStr); + // curEdit->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + curEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + int w = qMax((int)((double)width * 1.4) + 16, 50); + curEdit->setMinimumWidth(w); + lineEditList.append(curEdit); + layout->addWidget(curEdit); + } + + + + QObject::connect(confirm, &QPushButton::clicked, [lineEditList, click]() { + QVector values; + for(QLineEdit* lineEdit : lineEditList) { + values.append(getValueFromString(lineEdit->text())); + } + click(values); + }); + return container; + } + + + template + static QWidget* createEditWidget(const QString& buttonText, + const std::function& click, const T& initValues) { + return createEditWidget_List(buttonText, [click](const QVector& data) { + click(data.first()); + }, QVector{initValues}); + } + + static QWidget* createRange(Axis* axis, const QString& text = "更改轴范围") { + return createEditWidget_List(text, [axis](const QVector& values) { + axis->set_coordRange(Range{values[0], values[1]}); + }, {axis->coordRange().lower, axis->coordRange().upper}); + } + + static QMenu* findParentMenu(QWidget* widget) { + // 从当前窗口开始,逐步检查父窗口 + QWidget* parent = widget->parentWidget(); + while (parent) { + if (QMenu* menu = qobject_cast(parent)) { + return menu; // 找到第一个 QMenu 类型的父窗口 + } + parent = parent->parentWidget(); + } + return nullptr; // 如果没有找到 QMenu 类型的父窗口 + } + + static QWidget* createSelectColor(const QString& text, const std::function& click, const QColor& color = QColor()) { + auto ret = new QWidget; + auto l = new QHBoxLayout(ret); + l->setContentsMargins(0, 0, 0, 0); + //l->setSpacing(0); + QString bs = getButtonStyleSheet(); + auto showColor = new QWidget; + showColor->setStyleSheet(QString("background-color: %1; border: 2px solid #000000;").arg(color.name(QColor::HexArgb))); + showColor->setFixedWidth(30); + static QPushButton *btn = nullptr; + //static auto dialog = new CustomColorDialog(Qt::white, QApplication::activeWindow()); + static auto dialog = new SelectColorDialog; + dialog->resize(800, 600); + // dialog->adjustSize(); + auto label = new QLineEdit(color.isValid() ? color.name(QColor::HexArgb) : "无效"); + label->setAlignment(Qt::AlignCenter); + label->setStyleSheet("background-color: #ffffff; color: #000000;"); + //label->setAlignment(Qt::AlignRight | Qt::AlignVCenter); + auto button = new QPushButton(text); + button->setStyleSheet(bs); + QObject::connect(button, &QPushButton::clicked, [button, label]() { + btn = button; + dialog->setCurrentColor(QColor(label->text())); + dialog->show(); + }); + QObject::connect(dialog, &QDialog::finished, [ret, button, click, label, showColor](int result) { + if(btn != button) return; + if (result == QDialog::Accepted) { + QColor selectColor = dialog->currentColor(); + click(selectColor); + label->setText(selectColor.name(QColor::HexArgb)); + showColor->setStyleSheet(QString("background-color: %1; border: 2px solid #000000;") + .arg(selectColor.name(QColor::HexArgb))); + } else if (result == -1) { + click(QColor()); + label->setText("无颜色"); + showColor->setStyleSheet(QString("background-color: rgba(255, 255, 255, 0); border: 2px solid #000000;")); + } + QMenu* menu = findParentMenu(ret); + if(menu) { + menu->show(); + } + }); + l->addWidget(button); + l->addWidget(label); + l->addWidget(showColor); + return ret; + } + + static QPushButton *createButton(const QString& text, const std::function& click) { + auto button = new QPushButton(text); + button->setStyleSheet(getButtonStyleSheet()); + QObject::connect(button, &QPushButton::clicked, [click]() { + click(); + }); + return button; + } + + + + static QPushButton *createToggleButton(const QString& text, const QString& text2, const std::function& click, bool firstState) { + auto button = new QPushButton(firstState ? text : text2); + button->setStyleSheet(getButtonStyleSheet()); + QObject::connect(button, &QPushButton::clicked, [button, click, text, text2]() { + click(); + if(button->text() == text2) { + button->setText(text); + } else { + button->setText(text2); + } + }); + return button; + } + + + static QLabel* createLabel(const QString& text, int h = 50, const QColor& color = Qt::black) { + auto l = new QLabel(text); + QFont font; + font.setFamily("Segoe UI"); + font.setBold(true); + l->setFont(font); + l->setFixedHeight(h); + l->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); + l->setStyleSheet(QString("color: %1;").arg(color.name(QColor::HexArgb))); + return l; + } + + + +} + + + +static QVector bw(const QVector& widgets, const QString& name) { + if(widgets.empty()) return {}; + auto ret = new QWidget; + ret->setStyleSheet(YSG::getBackgroundStyleSheet()); + auto l = new QVBoxLayout(ret); + auto label = new QLabel(name); + label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); + QFontMetrics fm(label->font()); + label->setFixedSize(fm.horizontalAdvance(name) + 16, fm.height() + 8); + label->setStyleSheet(QString("color: %1; background-color: %2; padding: 0px; margin: 0px;") + .arg( + QColor(Qt::white).name(QColor::HexArgb), + QColor(Qt::black).name(QColor::HexArgb)) + ); + l->addWidget(label, 0, Qt::AlignHCenter | Qt::AlignBottom); + for(QWidget *w : widgets) { + l->addWidget(w); + } + return {ret}; +} + +static QWidget* createControl(YSG::Plot* plot, const QVector& widgets = {}) { + auto control = new QWidget; + auto l = new QVBoxLayout(control); + l->setAlignment(Qt::AlignTop); + l->setContentsMargins({0, 0, 0, 0}); + l->setSpacing(0); + QVector ws = { + YSG::createToggleButton("暂停渲染", "开始渲染", [plot]() { + plot->isRendering() ? plot->pauseRender() : plot->startRender(); + }, plot->isRendering()), + YSG::createToggleButton("停止性能测试", "启动性能测试", [plot]() { + plot->set_usePerformanceShower(plot->usePerformanceShower() ? false : true); + }, plot->usePerformanceShower()), + YSG::createSelectColor("更改背景色", [plot](const QColor& color) { + plot->setBackgroundColor(color); + }, plot->backgroundColor()), + YSG::createEditWidget_List("渲染数据Timer", [plot](QVector data) { + if(data[0] < 0) { + plot->pauseRender(); + } else { + plot->startRender(data[0]); + } + }, {30}), + }; + + + for(QWidget* cur : bw(ws, "plot基础") + widgets) { + l->addWidget(cur); + } + return control; +} + +static QVector cw(YSG::AbsAxis* it) { + if(it == nullptr) return{}; + auto t = QVector { + YSG::createButton("反转轴整体方向", [it](){ + it->set_orientation(it->orientation() == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal); + }), + YSG::createEditWidget_List("主刻度值", [it](const QVector& values) { + it->set_tickLength(values[0]); + }, {it->tickLength()}), + YSG::createEditWidget_List("子刻度值", [it](const QVector& values) { + it->set_subTickLength(values[0]); + }, {it->subTickLength()}), + YSG::createSelectColor("更改轴颜色", [it](const QColor& color) { + it->set_color(color); + }, it->color()), + YSG::createEditWidget_List("设置单位", [it](const QVector& values) { + it->set_unitText(values[0]); + }, {it->unitText()}), + YSG::createSelectColor("更改UnitText颜色", [it](const QColor& color) { + it->set_unitTextPen(QPen(color)); + }, it->color()), + YSG::createSelectColor("更改UnitText背景色", [it](const QColor& color) { + it->set_unitTextBackgroundBrush(QBrush(color)); + }, it->color()), + YSG::createEditWidget_List("更改刻度斜度", [it](const QVector& values) { + it->set_rotate(values[0]); + }, {it->rotate()}), + }; + return t; +} + +static QVector cw(YSG::Axis* it) { + if(it == nullptr) return{}; + auto t = QVector { + YSG::createRange(it), + }; + return cw(dynamic_cast(it)) + t; +} + +static QVector cw(YSG::FrequentAxis* it) { + if(it == nullptr) return{}; + auto t = QVector { + + }; + return cw(dynamic_cast(it)) + t; +} + +static QVector cw(YSG::TimeAxis* it, int numPreSecond = -1) { + if(it == nullptr) return{}; + auto t = QVector { + YSG::createEditWidget_List("时间格式", [it](QVector data) { + it->set_timeFormat(data[0]); + }, {it->timeFormat()}), + YSG::createEditWidget_List("单位间隔pixel", [it](QVector data) { + it->set_tickPixelSpace(data[0]); + }, {it->tickPixelSpace()}), + }; + + auto timer = new QTimer(it->mPlot); + if(numPreSecond > 0) timer->start(static_cast(1000.0/static_cast(numPreSecond))); + QObject::connect(timer, &QTimer::timeout, [it]() { + it->giveData(QTime::currentTime()); + }); + t.append(YSG::createEditWidget_List("推数据Timer(次/s)", [timer](QVector data) { + if(data[0] < 0) { + timer->stop(); + } else { + timer->start((int)(1000.0/data[0])); + } + }, {timer->interval() > 0 ? 1000.0/timer->interval() : -1} )); + return t + cw(dynamic_cast(it)); +} + + +static QVector cw(YSG::MutiSelectRect* it) { + if(it == nullptr) return{}; + auto t = QVector{ + YSG::createSelectColor("更改填充色", [it](const QColor& color) { + it->setRectBrush(QBrush(color)); + }, it->rectBrush().color()), + YSG::createSelectColor("更改边框色", [it](const QColor& color) { + qDebug() << " color.isValid() == " << color.isValid(); + QPen pen(color); + pen.setStyle(Qt::DashLine); + it->setBorderPen(pen); + qDebug() << " color.isValid() == " << pen.color().isValid(); + }, it->borderPen().color()), + YSG::createSelectColor("更改字色", [it](const QColor& color) { + it->setFontPen(QPen(color)); + }, it->fontPen().color()) + }; + return t; +} + +template +static QVector cw(YSG::HoverInfoRenderAble* it) { + if(it == nullptr) return{}; + auto t = QVector{ + YSG::createLabel("悬浮信息"), + YSG::createToggleButton("启动悬浮信息", "停止悬浮信息", [it]() { + it->setUseHoverInfo(!it->useHoverInfo()); + }, !it->useHoverInfo()), + YSG::createSelectColor("设置文字颜色", [it](const QColor& c) { + QPen pen = it->hoverInfoPen(); + pen.setColor(c); + it->setHoverInfoPen(pen); + }, it->hoverInfoPen().color()), + YSG::createSelectColor("设置背景颜色", [it](const QColor& c) { + QBrush brush = it->hoverInfoBackgroundBrush(); + brush.setColor(c); + it->setHoverInfoBackgroundBrush(brush); + }, it->hoverInfoBackgroundBrush().color()) + }; + + return t; +} + +static QVector cw(YSG::WaterFall* it, int numPreSecond = -1) { + if(it == nullptr) return{}; + auto timer = new QTimer(it->mPlot); + if(numPreSecond > 0) timer->start(static_cast(1000.0/static_cast(numPreSecond))); + QObject::connect(timer, &QTimer::timeout, [it]() { + QVector d = YSG::getData(it->powerRange(), it->frequentPointSize()); + int tick = it->timeAxis()->giveData(QTime::currentTime()); + it->giveData(tick, d); + }); + auto t = QVector { + YSG::createEditWidget_List("推数据Timer(次/s)", [timer](QVector data) { + if(data[0] < 0) { + timer->stop(); + } else { + timer->start((int)(1000.0/data[0])); + } + }, {timer->interval() > 0 ? 1000.0/timer->interval() : -1}), + }; + return t + cw(static_cast*>(it)); +} + +static QVector cw(YSG::AudioFrequent* it, int numPreSecond = -1) { + if(it == nullptr) return{}; + auto timer = new QTimer(it->mPlot); + if(numPreSecond > 0) timer->start(static_cast(1000.0/static_cast(numPreSecond))); + QObject::connect(timer, &QTimer::timeout, [it]() { + int tick = it->timeAxis()->giveData(QTime::currentTime()); + it->giveData(tick, YSG::getData({0, 20})); + }); + auto t = QVector { + YSG::createEditWidget_List("推数据Timer(次/s)", [timer](QVector data) { + if(data[0] < 0) { + timer->stop(); + } else { + timer->start((int)(1000.0/data[0])); + } + }, {timer->interval() > 0 ? 1000.0/timer->interval() : -1}), + }; + return t; +} + +static QVector cw(YSG::Planisphere* it, int numPreSecond = -1) { + if(it == nullptr) return{}; + auto timer = new QTimer(it->mPlot); + QObject::connect(timer, &QTimer::timeout, [it]() { + for(int i = 0; i < 10; ++i) { + double x = YSG::getData(it->IRange()); + double y = YSG::getData(it->QRange()); + it->giveData({x, y}); + } + }); + if(numPreSecond > 0) timer->start(static_cast(1000.0/static_cast(numPreSecond))); + + auto t = QVector{ + YSG::createEditWidget_List("推数据Timer(次/s)", [timer](QVector data) { + if(data[0] < 0) { + timer->stop(); + } else { + timer->start((int)(1000.0/data[0])); + } + }, {timer->interval() > 0 ? 1000.0/timer->interval() : -1}), + YSG::createEditWidget_List("设置持续时间(ms)", [it](QVector data) { + it->set_continueMillisecond(data[0]); + }, {it->continueMillisecond()}), + YSG::createSelectColor("更改IQ点颜色", [it](const QColor& color) { + it->set_pointColor(color); + }, it->pointColor()), + YSG::createSelectColor("更改锚点颜色", [it](const QColor& color) { + it->set_anchorColor(color); + }, it->anchorColor()), + YSG::createEditWidget_List("I范围", [it](QVector data) { + it->set_IRange({data[0], data[1]}); + }, {it->IRange().lower, it->IRange().upper}), + YSG::createEditWidget_List("Q范围", [it](QVector data) { + it->set_QRange({data[0], data[1]}); + }, {it->QRange().lower, it->QRange().upper}), + YSG::createButton("设置到轴中心", [it]() { + it->setToAxisCenter(); + }), + }; + return t; +} + + +static QVector cw(YSG::Afterglow* it, int numPreSecond = -1) { + if(it == nullptr) return{}; + static auto createBaseData = [it]() { + YSG::Range r = it->powerRange(); + QVector ret(it->frequentPointSize()); + ret[0] = getData(r); + for(int i = 1; i < it->frequentPointSize(); ++i) { + double rate; + if(i % 20 == 0) { + double that = qAbs(ret[i - 1] - r.lower)/r.size() - 0.5; + rate = (1.0 - that) * YSG::getData({0.8, 1.2}); + } else { + rate = YSG::getData({0.8, 1.2}); + } + ret[i] = YSG::clamp(ret[i - 1]*rate, r.lower, r.upper); + } + return ret; + }; + static QVector baseData = createBaseData(); + auto timer = new QTimer(it->mPlot); + QObject::connect(timer, &QTimer::timeout, [it]() { + YSG::Range r = it->powerRange(); + int n = baseData.size(); + QVector curData(n); + for (int i = 0; i < n; ++i) { + double rate = YSG::getData({0.8, 1.2}); + curData[i] = YSG::clamp(baseData[i]*rate, r.lower, r.upper); + } + it->giveData(curData); + }); + if(numPreSecond > 0) timer->start(static_cast(1000.0/static_cast(numPreSecond))); + auto t = QVector{ + YSG::createEditWidget_List("推数据Timer(次/s)", [timer](QVector data) { + if(data[0] < 0) { + timer->stop(); + } else { + timer->start((int)(1000.0/data[0])); + } + }, {timer->interval() > 0 ? 1000.0/timer->interval() : -1}), + YSG::createEditWidget_List("频率范围", [it](QVector data) { + it->set_frequentRange({data[0], data[1]}); + }, {it->frequentRange().lower, it->frequentRange().upper}), + YSG::createEditWidget_List("功率概率范围", [it](QVector data) { + it->set_powerRange({data[0], data[1]}); + }, {it->powerRange().lower, it->powerRange().upper}), + YSG::createButton("更改基础线", []() { + baseData = createBaseData(); + }), + + }; + return t; +} + + +static QVector cw(YSG::Spectrum* it, int numPreSecond = -1) { + if(it == nullptr) return{}; + auto timer = new QTimer(it->mPlot); + if(numPreSecond > 0) timer->start(static_cast(1000.0/static_cast(numPreSecond))); + QObject::connect(timer, &QTimer::timeout, [it]() { + it->giveData(YSG::getData(it->powerAxis()->coordRange(), it->frequentPointSize())); + }); + auto t = QVector { + YSG::createEditWidget_List("推数据Timer(次/s)", [timer](QVector data) { + if(data[0] < 0) { + timer->stop(); + } else { + timer->start((int)(1000.0/data[0])); + } + }, {timer->interval() > 0 ? 1000.0/timer->interval() : -1}), + YSG::createEditWidget_List("频率范围", [it](QVector data) { + it->set_frequentRange({data[0], data[1]}); + }, {it->frequentRange().lower, it->frequentRange().upper}), + YSG::createEditWidget_List("设置频率点数", [it](QVector data) { + it->set_frequentPointSize(data[0]); + }, {it->frequentPointSize()}), + YSG::createLabel("sweepRect"), + YSG::createToggleButton("停止sweepRect", "启动sweepRect", [it]() { + it->set_useSweepFrequentRect(!it->useSweepFrequentRect()); + }, it->useSweepFrequentRect()), + YSG::createEditWidget_List("sweepRect 范围", [it](QVector data) { + if(!it->useSweepFrequentRect()) it->set_useSweepFrequentRect(true); + it->set_sweepFrequentRange({data[0], data[1]}); + }, {it->sweepFrequentRange().lower, it->sweepFrequentRange().upper}), + YSG::createEditWidget_List("中频", [it](QVector data) { + if(!it->useSweepFrequentRect()) it->set_useSweepFrequentRect(true); + it->set_middleSweepFrequent(data[0]); + }, {it->middleSweepFrequent()}), + YSG::createSelectColor("扫频矩形", [it](const QColor& c) { + it->set_sweepRectBrush(QBrush(c)); + }), + YSG::createSelectColor("中频", [it](const QColor& c) { + if(!c.isValid()) { + it->set_middleFrequentPen(Qt::NoPen); + } + it->set_middleFrequentPen(QPen(c)); + }, it->middleFrequentPen().color()), + YSG::createLabel("marker"), + YSG::createToggleButton("停止最大值marker", "启动最大值marker", [it]() { + it->set_useMaxMarker(!it->useMaxMarker()); + }, it->useMaxMarker()), + YSG::createToggleButton("停止最小值marker", "启动最小值marker", [it]() { + it->set_useMinMarker(!it->useMinMarker()); + }, it->useMinMarker()), + YSG::createEditWidget_List("添加自定义marker", [it](QVector data) { + it->addCustomMarker(data[0]); + }, {0}), + YSG::createEditWidget_List("添加自定义Linemarker", [it](QVector data) { + it->addCustomLineMarker(data[0]); + }, {0}), + YSG::createEditWidget_List("删除marker", [it](QVector data) { + it->removeCustomMarker(data[0]); + }, {0}), + YSG::createEditWidget_List("清楚所有marker", [it](QVector data) { + it->clearAllCustomMarker(); + }, {}), + YSG::createEditWidget_List("选择当前marker", [it](QVector data) { + it->setSelectedLineMarker(data[0]); + }, {0}), + YSG::createEditWidget_List("选择下一个marker", [it](QVector data) { + it->selectNext(); + }, {}), + YSG::createEditWidget_List("选择上一个marker", [it](QVector data) { + it->selectPrevious(); + }, {}), + YSG::createLabel("当前线"), + YSG::createSelectColor("当前线色", [it](const QColor& c) { + if(!c.isValid()) { + it->set_curPen(Qt::NoPen); + return; + } + it->set_curPen(c); + }, it->curPen().color()), + YSG::createSelectColor("当前线填充色", [it](const QColor& c) { + it->set_curBrush(c); + }, it->curBrush().color()), + YSG::createLabel("最大值线"), + YSG::createToggleButton("停止最大值线", "启动最大值线",[it]() { + it->set_useMaxLine(!it->useMaxLine()); + }, it->useMaxLine()), + YSG::createSelectColor("最大值线色", [it](const QColor& c) { + if(c.isValid()) { + it->set_maxPen(Qt::NoPen); + return; + } + it->set_maxPen(c); + }, it->maxPen().color()), + YSG::createSelectColor("最大值线填充色", [it](const QColor& c) { + it->set_maxBrush(c); + }, it->maxBrush().color()), + YSG::createLabel("最小值线"), + YSG::createToggleButton("停止最小值线", "启动最小值线", [it]() { + it->set_useMinLine(!it->useMinLine()); + }, it->useMinLine()), + YSG::createSelectColor("最小值线色", [it](const QColor& c) { + it->set_minPen(c); + }, it->minPen().color()), + YSG::createSelectColor("最小值线填充色", [it](const QColor& c) { + it->set_minBrush(c); + }, it->minBrush().color()), + }; + + + return cw(static_cast*>(it)) + t; +} + +static QVector cw(YSG::SweepFrequent* it, int numPreSecond = -1) { + if(it == nullptr) return{}; + auto timer = new QTimer(it->mPlot); + if(numPreSecond > 0) timer->start(static_cast(1000.0/static_cast(numPreSecond))); + QObject::connect(timer, &QTimer::timeout, [it]() { + it->giveData(YSG::getData(it->powerAxis()->coordRange(), it->blockFrequentPointSize())); + }); + auto t = QVector { + YSG::createEditWidget_List("推数据Timer(次/s)", [timer](QVector data) { + if(data[0] < 0) { + timer->stop(); + } else { + timer->start((int)(1000.0/data[0])); + } + }, {timer->interval() > 0 ? 1000.0/timer->interval() : -1}), + YSG::createSelectColor("线颜色", [it](const QColor& color) { + it->set_pen(QPen(color)); + }, it->pen().color()), + YSG::createSelectColor("扫频线颜色", [it](const QColor& color) { + QPen p = it->curFrequentPen(); + p.setColor(color); + it->set_curFrequentPen(p); + }, it->curFrequentPen().color()), + YSG::createEditWidget_List("频率范围", [it](const QVector& data) { + it->set_frequentRange({data[0], data[1]}); + }, {it->frequentRange().lower, it->frequentRange().upper}) + }; + return t; +} + +static QMenu* createMenu(YSG::Plot* plot, const QVector& widgets) { + auto menu = new QMenu(plot); + menu->setObjectName("memu"); + //menu->setStyle(nullptr); + menu->hide(); + menu->setContentsMargins(0 ,0, 0, 0); + auto container = createControl(plot, widgets); + auto scrollArea = new QScrollArea; + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setContentsMargins(0, 0, 0, 0); + scrollArea->setWidget(container); + scrollArea->setWidgetResizable(true); + scrollArea->setObjectName("scrollArea"); + auto scrollAction = new QWidgetAction(menu); + scrollAction->setDefaultWidget(scrollArea); + menu->addAction(scrollAction); + return menu; +} + +static QMenu* createMenu_Normal(QWidget* w, const QVector& widgets) { + auto menu = new QMenu(w); + menu->setObjectName("memu_normal"); + //menu->setStyle(nullptr); + menu->hide(); + menu->setContentsMargins(0 ,0, 0, 0); + auto container = new QWidget; + auto l = new QVBoxLayout(container); + l->setAlignment(Qt::AlignTop); + l->setContentsMargins({0, 0, 0, 0}); + l->setSpacing(4); + for (auto widget : widgets) + { + l->addWidget(widget); + } + auto scrollArea = new QScrollArea; + scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea->setContentsMargins(0, 0, 0, 0); + scrollArea->setWidget(container); + scrollArea->setWidgetResizable(true); + scrollArea->setObjectName("scrollArea2"); + auto scrollAction = new QWidgetAction(menu); + scrollAction->setDefaultWidget(scrollArea); + menu->addAction(scrollAction); + + + + return menu; +} + diff --git a/YSGraphic_Core/DemoGallery/WaterFallPlot.h b/YSGraphic_Core/DemoGallery/WaterFallPlot.h new file mode 100644 index 0000000..a070fc0 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/WaterFallPlot.h @@ -0,0 +1,63 @@ +#pragma once +#include "Tool.h" + +static QWidget* createWaterFallPlot() { + class WaterFallPlot : public YSG::Plot { + public: + YSG::FrequentAxis *xAxis{}; + YSG::TimeAxis *yAxis{}; + YSG::MutiSelectRect *msr{}; + YSG::WaterFall *wf{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "WaterFallPlot"; + bindRenderThread(mObjectName + "Thread"); + + xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal) + .set_tickLength(-10) + .set_subTickLength(-5) + .build(); + + yAxis = YSG::TimeAxis::Builder(this, Qt::Vertical) + .set_timePointSize(800).build(); + + + wf = YSG::WaterFall::Builder(xAxis, yAxis) + .set_layerName("plottable") + .set_frequentRange({0, 20}) + .build(); + + msr = YSG::MutiSelectRect::Builder(xAxis, yAxis).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + xAxis->set_x(0); + xAxis->set_y(height()); + xAxis->set_pixelSize(width()); + yAxis->set_x(0); + yAxis->set_y(0); + yAxis->set_pixelSize(height()); + yAxis->set_timePointSize(height()); + + //wf->set_frequentPointSize(wf->frequentAxis()->getPixelPointSize()); + wf->set_frequentPointSize(width()); + + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = + bw(cw(wf, -1), "瀑布图") + + bw(cw(xAxis), "频率轴") + + bw(cw(yAxis), "时间轴") + + bw(cw(msr), "多选框"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new WaterFallPlot; + w->init(); + return w; +} + diff --git a/YSGraphic_Core/DemoGallery/base/AxisTest.h b/YSGraphic_Core/DemoGallery/base/AxisTest.h new file mode 100644 index 0000000..95a2658 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/base/AxisTest.h @@ -0,0 +1,42 @@ +#pragma once +#include "../Tool.h" + +static QWidget* createAxisTestPlot() { + class AxisTestPlot : public YSG::Plot { + public: + YSG::Axis *axis{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "AxisTestPlot"; + bindRenderThread("AxisTestPlotThread"); + axis = YSG::Axis::Builder(this, Qt::Horizontal) + .set_tickLength(-10) + .set_subTickLength(-5) + .set_coordRange({-12000, 12000}) + .set_use_wheel(true) + .build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + int space = 50; + axis->set_x(space); + axis->set_y(height()/2); + axis->set_pixelSize(width() - space * 2); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = + bw( cw(axis), "轴"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new AxisTestPlot; + w->init(); + + return w; +} + + diff --git a/YSGraphic_Core/DemoGallery/base/FrequentAxis.h b/YSGraphic_Core/DemoGallery/base/FrequentAxis.h new file mode 100644 index 0000000..d275438 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/base/FrequentAxis.h @@ -0,0 +1,40 @@ +#pragma once +#include "../Tool.h" + + +static QWidget* createFrequentAxisTestPlot() { + class AxisTestPlot : public YSG::Plot { + public: + YSG::FrequentAxis *axis{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "AxisTestPlot"; + bindRenderThread("AxisTestPlotThread"); + axis = new YSG::FrequentAxis; + axis->init(this, "axis"); + axis->setOrientation(Qt::Horizontal); + axis->setTickLength(-10); + axis->setSubTickLength(-5); + + } + protected: + void resizeEvent(QResizeEvent* event) override { + int space = 50; + axis->setX(space); + axis->setY(height()/2); + axis->setPixelLength(width() - space * 2); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = bw( cw(axis), "轴"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new AxisTestPlot; + w->init(); + return w; +} + diff --git a/YSGraphic_Core/DemoGallery/base/FrequentAxisTest.h b/YSGraphic_Core/DemoGallery/base/FrequentAxisTest.h new file mode 100644 index 0000000..99a095e --- /dev/null +++ b/YSGraphic_Core/DemoGallery/base/FrequentAxisTest.h @@ -0,0 +1,39 @@ +#pragma once +#include "../Tool.h" + + +static QWidget* createFrequentAxisTestPlot() { + class AxisTestPlot : public YSG::Plot { + public: + YSG::FrequentAxis *axis{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "AxisTestPlot"; + bindRenderThread("AxisTestPlotThread"); + axis = YSG::FrequentAxis::Builder(this, Qt::Horizontal) + .set_tickLength(-10) + .set_subTickLength(-5) + .set_coordRange({-12000, 12000}) + .build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + int space = 50; + axis->set_x(space); + axis->set_y(height()/2); + axis->set_pixelSize(width() - space * 2); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = bw( cw(axis), "轴"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new AxisTestPlot; + w->init(); + return w; +} + diff --git a/YSGraphic_Core/DemoGallery/base/MutiSelectTest.h b/YSGraphic_Core/DemoGallery/base/MutiSelectTest.h new file mode 100644 index 0000000..0ec086b --- /dev/null +++ b/YSGraphic_Core/DemoGallery/base/MutiSelectTest.h @@ -0,0 +1,48 @@ +#pragma once +#include "../Tool.h" + + + +static QWidget* createMutiSelectPlot() { + class MutiSelectPlot : public YSG::Plot{ + public: + YSG::Axis *xAxis{}; + YSG::Axis *yAxis{}; + YSG::MutiSelectRect *mMutiSelectRect{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "PlanispherePlot"; + bindRenderThread("PlanispherePlotThread"); + + xAxis = YSG::Axis::Builder(this, Qt::Horizontal) + .set_tickLength(-10) + .set_subTickLength(-5).build(); + + yAxis = YSG::Axis::Builder(this, Qt::Vertical).build(); + mMutiSelectRect = YSG::MutiSelectRect::Builder(xAxis, yAxis).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + xAxis->set_x(0); + xAxis->set_y(height() - 1); + xAxis->set_pixelSize(width()); + yAxis->set_x(0); + yAxis->set_y(0); + //yAxis->setCoordReserve(true); + yAxis->set_pixelSize(height()); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = bw( cw(mMutiSelectRect), "多选框"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + + auto w = new MutiSelectPlot; + w->init(); + + return w; +} diff --git a/YSGraphic_Core/DemoGallery/base/TimeAxisTest.h b/YSGraphic_Core/DemoGallery/base/TimeAxisTest.h new file mode 100644 index 0000000..54159c0 --- /dev/null +++ b/YSGraphic_Core/DemoGallery/base/TimeAxisTest.h @@ -0,0 +1,38 @@ +#pragma once +#include "../Tool.h" + + +static QWidget* createTimeAxisTestPlot() { + class AxisTestPlot : public YSG::Plot { + public: + YSG::TimeAxis *axis{}; + QMenu *mMenu{}; + void init() override { + Plot::init(); + mObjectName = "TimeAxisTestPlot"; + bindRenderThread(mObjectName + "Thread"); + axis = YSG::TimeAxis::Builder(this, Qt::Horizontal) + .set_tickLength(-10) + .set_subTickLength(-5).build(); + } + protected: + void resizeEvent(QResizeEvent* event) override { + int space = 50; + axis->set_x(space); + axis->set_y(height()/2); + axis->set_pixelSize(width() - space * 2); + } + void contextMenuEvent(QContextMenuEvent* event) override { + if(!mMenu) { + auto ws = bw( cw(axis, 30), "时间轴"); + mMenu = createMenu(this, ws); + } + mMenu->exec(event->globalPos()); + } + }; + auto w = new AxisTestPlot; + w->init(); + + return w; +} + diff --git a/YSGraphic_Core/GenerateMockData.cpp b/YSGraphic_Core/GenerateMockData.cpp new file mode 100644 index 0000000..cc2aa19 --- /dev/null +++ b/YSGraphic_Core/GenerateMockData.cpp @@ -0,0 +1,40 @@ +#include "GenerateMockData.h" + +#include +namespace YSG { + std::random_device rd; + std::default_random_engine eng(rd()); + QVector getData(Range valueRange, int size) { + if(valueRange.lower > valueRange.upper) { + std::swap(valueRange.lower, valueRange.upper); + } + std::uniform_real_distribution dist(valueRange.lower, valueRange.upper); + QVector ret(size); + for (auto &value: ret) { + value = dist(eng); + } + return ret; + } + double getData(Range valueRange) { + if(valueRange.lower > valueRange.upper) { + std::swap(valueRange.lower, valueRange.upper); + } + std::uniform_real_distribution dist(valueRange.lower, valueRange.upper); + return dist(eng); + } + QVector getData(int value, int size) { + QVector ret(size); + for (auto &v: ret) { + v = value; + } + return ret; + } + QVector getColoredData(int size) { + QVector ret(size); + for (int i = 0; i < size; ++i) { + ret[i] = i; + } + return ret; + } + +} diff --git a/YSGraphic_Core/GenerateMockData.h b/YSGraphic_Core/GenerateMockData.h new file mode 100644 index 0000000..8d24e7c --- /dev/null +++ b/YSGraphic_Core/GenerateMockData.h @@ -0,0 +1,9 @@ +#pragma once +#include "GlobalTypes.h" + +namespace YSG { + QVector LIB_DECL getData(Range valueRange, int size); + double LIB_DECL getData(Range valueRange); + QVector LIB_DECL getData(int value, int size); + QVector LIB_DECL getColoredData(int size); +} diff --git a/YSGraphic_Core/GlobalTypes.h b/YSGraphic_Core/GlobalTypes.h new file mode 100644 index 0000000..8a42830 --- /dev/null +++ b/YSGraphic_Core/GlobalTypes.h @@ -0,0 +1,213 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// Q_DECL_IMPORT +#ifdef NDEBUG + #define ASSERT(condition, message) ((void)0) // 在发布模式下什么都不做 +#else + #define ASSERT(condition, message) \ + do { \ + if (!(condition)) { \ + std::cerr << "Assertion failed: " << message \ + << "\nFile: " << __FILE__ \ + << "\nLine: " << __LINE__ \ + << std::endl; \ + throw std::runtime_error(message); \ + } \ + } while (0) +#endif + +#if defined(buildLib) + #define LIB_DECL +#elif defined(buildDll) + #define LIB_DECL Q_DECL_EXPORT +#elif defined(buildExe) + #define LIB_DECL +#else + #define LIB_DECL +#endif + +namespace YSG { + QVector getTurboColorGradient(); + class AbsAxis; + class Axis; + struct RenderData; + struct RenderAble; + class Plot; + struct LIB_DECL Range { + double lower; + double upper; + double center() { + return (lower + upper)/2.0; + } + [[nodiscard]] double size() const { + return qAbs(upper - lower); + } + [[nodiscard]] double length() const { + return upper - lower; + } + [[nodiscard]] double middle() const { + return lower + (upper - lower)/2; + } + bool operator==(const Range& other) const { + return (lower == other.lower && upper == other.upper); + } + bool operator!=(const Range& other) const { + return !(*this == other); + } + [[nodiscard]] bool contain(double value) const { + if(lower < upper) { + return value > lower - 0.0001 && value < upper + 0.0001; + } else { + return value > upper - 0.0001 && value < lower + 0.0001; + } + } + }; + QDebug operator<<(QDebug debug, const Range &range); + + struct LIB_DECL SpinLock { + void lock(); + bool tryLock(); + bool tryLock(int durationMillis); + void unlock(); + private: + std::atomic_flag flag = ATOMIC_FLAG_INIT; + }; + + struct LIB_DECL SpinLockGuard { + explicit SpinLockGuard(SpinLock *lock); + ~SpinLockGuard(); + SpinLock *mLock; + }; + + + enum class LIB_DECL SRC { + Auto, //第一次loadCache之前是Cache,其他是Render + Cache, + Render, + }; +} + + + +#define PROP(Type, Name) \ +Type Name(SRC src = SRC::Auto); \ +void set_##Name(Type); + + +#define PROP_G(className, Type, propName) \ +Type className::propName(SRC src) { \ +INIT_GET(className) \ +return renderState->propName; \ +} + +#define PROP_S(className, Type, propName) \ +void className::set_##propName(Type t) { \ +INIT_SET(className) \ +sc->propName = std::move(t); \ +} + +#define PROP_P(className, Type, propName) \ +PROP_G(className, Type, propName) \ +PROP_S(className, Type, propName) + +#define PROP_B(TYPE, FIELD) \ +public: \ +Builder& set_##FIELD(TYPE value) { \ +this->FIELD = std::move(value); \ +return *this; \ +} + +#define PROP_BT(TYPE, FIELD) \ +public: \ +That& set_##FIELD(TYPE value) { \ + static_cast(this)->FIELD = std::move(value); \ + return *static_cast(this); \ +} + + +#define PROP_R(TYPE, FIELD) sc->FIELD = this->FIELD; +#define PROP_RT(TYPE, FIELD) sc->FIELD = static_cast(this)->FIELD; + + +#define SETTER_T(TYPE, FIELD, DEFAULT) \ +private: \ +TYPE FIELD = DEFAULT; \ +public: \ +That& set_##FIELD(TYPE value) { \ +static_cast(this)->FIELD = value; \ +return *static_cast(this); \ +} + +#define SETTER_2_RANGE_T(bits, FIELD, DEFAULT) \ +private: \ +int FIELD = DEFAULT; \ +public: \ +T& set_##FIELD(int value) { \ +if (value < 0 || value >= (1 << bits)) { \ +throw std::out_of_range("Value out of range: " #FIELD); \ +} \ +static_cast(this)->FIELD = value; \ +return *static_cast(this); \ +} + +#define SETTER_RANGE_T(lower, upper, FIELD, DEFAULT) \ +private: \ +int FIELD = DEFAULT; \ +public: \ +Builder& set_##FIELD(int value) { \ +if (value < lower || value > upper) { \ +throw std::out_of_range("Value out of range: " #FIELD); \ +} \ +static_cast(this)->FIELD = value; \ +return *static_cast(this); \ +} + +#define SETTER(TYPE, FIELD, DEFAULT) \ +protected: \ +TYPE FIELD = DEFAULT; \ +public: \ +Builder& set_##FIELD(TYPE value) { \ +this->FIELD = std::move(value); \ +return *this; \ +} + +#define PROPERTY_H(className, Type, name) \ +void set_##name(Type name); \ +Type name(SRC src = SRC::Auto); + +#define PROPERTY_CPP(className, Type, name) \ +public: \ + void set_##name(Type name) { \ + className##Private* pd = d(); \ + className##RenderState* sc = pd->renderStateCache(); \ + className##TempData* td = pd->tempDataCache(); \ + SpinLockGuard _guard(&pd->mBufferLock); \ + sc->##name = name; \ + } \ + Type name(SRC src) { \ + className##Private* privateData = d(); \ + className##RenderState* renderState = reinterpret_cast(d()->getState(src)); \ + return renderState->##name; \ + } + + +namespace YSG { + template + constexpr const T& clamp(const T& v, const T& lo, const T& hi) { + return (v < lo) ? lo : (v > hi) ? hi : v; + } +} + +#undef min +#undef max diff --git a/YSGraphic_Core/Qt/File_Gather_Widget.cpp b/YSGraphic_Core/Qt/File_Gather_Widget.cpp new file mode 100644 index 0000000..1bfc868 --- /dev/null +++ b/YSGraphic_Core/Qt/File_Gather_Widget.cpp @@ -0,0 +1,200 @@ +#include "File_Gather_Widget.h" + +namespace Psc { +void File_Gather_Widget::append(const std::uint8_t *data, size_t size) { + if (!init) { + Message::show( + Message::error, + "缓冲区还未初始化", + this, + 3000, + Message::Top_Right + ); + return; + } + file_gather.append(data, size); + value->set_text(QString::fromStdString(file_gather.append_value.to_json().to_json_string())); + speed->set_text(QString::fromStdString(file_gather.append_speed.to_json().to_json_string())); +} + + +File_Gather_Widget::Config::Config() +{ + buffer_size = 100000; + log_path = ""; +} + +Psc::JSON File_Gather_Widget::Config::to_json() { + return VAR_JSON_2(buffer_size, log_path); +} + +void File_Gather_Widget::Config::from_json(Psc::JSON* that_json) { + Get_J(buffer_size) + Get_J(log_path) +} + + + +// 缓存文件目录 和 文件缓冲区大小 +File_Gather_Widget::File_Gather_Widget(const std::string& id) : Base_Cache(id) { + auto layout = new QVBoxLayout(this); + + value = new Text_Shower(); + speed = new Text_Shower(); + value->set_title("值"); + speed->set_title("速度"); + path_select = new File_Tool("采集文件"); + + size_select = new Byte_Select(); + size_select->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + size_select->set_number_value(0); + size_select->set_title("缓冲区大小"); + + + confirm_button = new QPushButton("确定"); + layout->addWidget(path_select); + layout->addWidget(size_select); + layout->addWidget(value); + layout->addWidget(speed); + layout->addWidget(confirm_button); + layout->addStretch(); + QObject::connect(confirm_button, &QPushButton::clicked, [this]() { + + if (init) { + Message::show( + Message::warning, + "已经初始化完成过", + this, + 3000, + Message::Top_Right + ); + return; + } + QString qpath = path_select->path(); + + if (qpath.isEmpty()) { + Message::show( + Message::error, + "路径不能为空", + this, + 3000, + Message::Top_Right + ); + return; + } + + std::filesystem::path path = qpath.toStdString(); + + try { + + // 如果是文件路径 + std::filesystem::path parent = path.parent_path(); + + if (!parent.empty() && !std::filesystem::exists(parent)) { + if (!std::filesystem::create_directories(parent)) { + Message::show( + Message::error, + "目录创建失败", + this, + 3000, + Message::Top_Right + ); + return; + } + } + + // 如果文件不存在就创建 + if (!std::filesystem::exists(path)) { + std::ofstream ofs(path.string(), std::ios::binary); + if (!ofs) { + Message::show( + Message::error, + "文件创建失败", + this, + 3000, + Message::Top_Right + ); + return; + } + } + + } catch (const std::exception& e) { + + Message::show( + Message::error, + QString("路径错误: ") + e.what(), + this, + 3000, + Message::Top_Right + ); + return; + } + + + init = false; + file_gather.init(path.string(), size_select->value()); + init = true; + + Message::show( + Message::success, + "初始化成功", + this, + 2000, + Message::Top_Right + ); + + // 保存初始化成功的配置文件 + save(); + }); + + + + + + auto json_file = get_config_path(); + Config config; + if (!std::filesystem::exists(json_file)) { + save(config); + } + auto tt = Psc::try_parse_json_file(json_file).and_then([&config](JSON&& json)->expected + { + config.from_json(&json); + return {}; + }); + if (!tt) + { + Message::show( + Message::success, + "配置文件加载错误! " + QString::fromStdString(json_file), + this, + 2000, + Message::Top_Right + ); + } + size_select->set_number_value(config.buffer_size); + path_select->set_path(QString::fromStdString(config.log_path)); +} + + +void File_Gather_Widget::save() +{ + Config config; + config.buffer_size = size_select->value(); + config.log_path = path_select->path().toStdString(); + save(config); +} + +void File_Gather_Widget::save(Config& config) +{ + auto json_file = get_config_path(); + std::ofstream ofs(json_file, std::ios::out | std::ios::trunc); + ofs << config.to_json().to_json_string(); + ofs.close(); +} + +void File_Gather_Widget::contextMenuEvent(QContextMenuEvent *event) { + QWidget::contextMenuEvent(event); + +} + +} // namespace Psc \ No newline at end of file diff --git a/YSGraphic_Core/Qt/File_Gather_Widget.h b/YSGraphic_Core/Qt/File_Gather_Widget.h new file mode 100644 index 0000000..bb1be01 --- /dev/null +++ b/YSGraphic_Core/Qt/File_Gather_Widget.h @@ -0,0 +1,67 @@ +#pragma once + +#include "Message.h" +#include "File_Tool.h" +#include "Text_Shower.h" +#include "Value_Select.h" + +#include +#include + +#include "Core/Base/File_Helper.h" +#include "Core/Base/JSON.h" + +namespace Psc { + + +// 这个是用来采集文件使用的控件 +class File_Gather_Widget : public QWidget, public Base_Cache { +public: + void append(const std::uint8_t* data, size_t size); + + struct Config{ + std::size_t buffer_size; + std::string log_path; + Config(); + Psc::JSON to_json(); + void from_json(Psc::JSON* that_json); + }; + // 初始化参数 + Byte_Select *size_select; + File_Tool *path_select; + QPushButton *confirm_button{}; + Text_Shower *value; + Text_Shower *speed; + void save(); + void save(Config& config); + + File_Gather_Widget(const std::string& id); // 一个名称用于标识 他会在可执行文件所在目录存储一些路径信息 + File_Gather file_gather; +protected: + void contextMenuEvent(QContextMenuEvent *event) override; + +}; + + + + +class Test_File_Gather_Widget : public QWidget { +public: + File_Gather_Widget *file_gather_widget; + Test_File_Gather_Widget() { + auto layout = new QVBoxLayout(this); + file_gather_widget = new File_Gather_Widget("Test_File_Gather_Widget"); + layout->addWidget(file_gather_widget); + auto content = new QLineEdit("测试输入内容123"); + auto confirm = new QPushButton("确定"); + layout->addWidget(content); + layout->addWidget(confirm); + QObject::connect(confirm, &QPushButton::clicked, [this, content]() { + auto str = "123"; + auto text = content->text().toStdString(); + file_gather_widget->append((uint8_t*)text.data(), text.size()); + }); + } +}; + +} diff --git a/YSGraphic_Core/Qt/File_Player_Widget.cpp b/YSGraphic_Core/Qt/File_Player_Widget.cpp new file mode 100644 index 0000000..281fdc4 --- /dev/null +++ b/YSGraphic_Core/Qt/File_Player_Widget.cpp @@ -0,0 +1,248 @@ +#include "File_Player_Widget.h" + +namespace Psc +{ + File_Play_Widget::Config::Config() + { + log_path = ""; + } + + Psc::JSON File_Play_Widget::Config::to_json() + { + return VAR_JSON_1(log_path); + } + + void File_Play_Widget::Config::from_json(Psc::JSON* that_json) + { + + Get_J(log_path) + } + + void File_Play_Widget::save() + { + Config config; + config.log_path = path_select->path().toStdString(); + save(config); + } + + void File_Play_Widget::save(Config& config) + { + auto json_file = get_config_path(); + std::ofstream ofs(json_file, std::ios::out | std::ios::trunc); + ofs << config.to_json().to_json_string(); + ofs.close(); + } + + + File_Play_Widget::File_Play_Widget(const std::string& id) : Base_Cache(id) + { + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(4, 4, 4, 4); + layout->setSpacing(6); + + path_select = new File_Tool2("回放文件"); + layout->addWidget(path_select); + + progress_bar = new QProgressBar(); + progress_bar->setRange(0, 1000); + progress_bar->setValue(0); + layout->addWidget(progress_bar); + + speed_show = new Text_Shower(); + speed_show->set_title("回放速度显示"); + layout->addWidget(speed_show); + + confirm_button = new QPushButton("确定"); + layout->addWidget(confirm_button); + QObject::connect(confirm_button, &QPushButton::clicked, [this]() + { + if (init) + { + Message::show( + Message::warning, + "已经初始化完成过", + this, + 3000, + Message::Top_Right + ); + return; + } + QString qpath = path_select->path(); + + if (qpath.isEmpty()) + { + Message::show( + Message::error, + "路径不能为空", + this, + 3000, + Message::Top_Right + ); + return; + } + + std::filesystem::path path = qpath.toStdString(); + + try + { + // 如果是文件路径 + std::filesystem::path parent = path.parent_path(); + + if (!parent.empty() && !std::filesystem::exists(parent)) + { + if (!std::filesystem::create_directories(parent)) + { + Message::show( + Message::error, + "目录创建失败", + this, + 3000, + Message::Top_Right + ); + return; + } + } + + // 如果文件不存在就创建 + if (!std::filesystem::exists(path)) + { + std::ofstream ofs(path.string(), std::ios::binary); + if (!ofs) + { + Message::show( + Message::error, + "文件创建失败", + this, + 3000, + Message::Top_Right + ); + return; + } + } + } + catch (const std::exception& e) + { + Message::show( + Message::error, + QString("路径错误: ") + e.what(), + this, + 3000, + Message::Top_Right + ); + return; + } + + + + Message::show( + Message::success, + "初始化成功", + this, + 2000, + Message::Top_Right + ); + + // 保存初始化成功的配置文件 + save(); + }); + + + auto json_file = get_config_path(); + Config config; + if (!std::filesystem::exists(json_file)) + { + save(config); + } + auto tt = Psc::try_parse_json_file(json_file).and_then([&config](JSON&& json)-> expected + { + config.from_json(&json); + return {}; + }); + if (!tt) + { + Message::show( + Message::success, + "配置文件加载错误!", + this, + 2000, + Message::Top_Right + ); + } + path_select->set_path(QString::fromStdString(config.log_path)); + } + + + void File_Play_Widget::start(size_t chunk_size, std::chrono::milliseconds interval) + { + QString path = path_select->path(); + if (path.isEmpty()) + { + Message::show(Message::error, QString("路径为空"), this, 3000, + Message::Top_Right); + return; + } + + try + { + file_player.open(path.toStdString()); + } + catch (std::exception& e) + { + Message::show(Message::error, QString("文件打开失败: ") + e.what(), this, + 3000, Message::Top_Right); + return; + } + speed.clear(); + progress_bar->setValue(0); + timer = new QTimer(this); + timer->setInterval(interval.count()); + + connect(timer, &QTimer::timeout, this, [this, chunk_size]() + { + std::vector buffer(chunk_size); + size_t n = file_player.read(buffer.data(), chunk_size); + if (n == 0 || file_player.eof()) + { + timer->stop(); + file_player.close(); + progress_bar->setValue(1000); + return; + } + speed.update(n); + + speed_show->set_text( + QString::fromStdString(speed.to_json().to_json_string())); + + double p = file_player.progress(); + progress_bar->setValue(static_cast(p * 1000)); + }); + timer->start(); + } + + + Test_File_Play_Widget::Test_File_Play_Widget() + { + auto layout = new QVBoxLayout(this); + file_play_widget = new File_Play_Widget("Test_File_Play_Widget"); + layout->addWidget(file_play_widget); + layout->addStretch(); + interval_select = new Value_Select_Simple(); + chunk_select = new Value_Select_Simple(); + interval_select->set_value(50); + chunk_select->set_value(4096); + interval_select->set_title("时间间隔(ms)"); + chunk_select->set_title("块大小(byte)"); + start_button = new QPushButton("开始回放"); + layout->addWidget(interval_select); + layout->addWidget(chunk_select); + layout->addWidget(start_button); + + connect(start_button, &QPushButton::clicked, this, [this]() + { + file_play_widget->speed.clear(); + size_t chunk = chunk_select->get_value(); + int interval = interval_select->get_value(); + file_play_widget->start(chunk, std::chrono::milliseconds(interval)); + }); + } +} // namespace Psc diff --git a/YSGraphic_Core/Qt/File_Player_Widget.h b/YSGraphic_Core/Qt/File_Player_Widget.h new file mode 100644 index 0000000..764187f --- /dev/null +++ b/YSGraphic_Core/Qt/File_Player_Widget.h @@ -0,0 +1,54 @@ +#pragma once + + +#include + +#include "File_Tool2.h" +#include "Message.h" +#include "Text_Shower.h" +#include "Value_Select.h" + +#include +#include +#include +#include +#include +#include + +#include "global.h" +#include "Core/Base/File_Helper.h" +#include "Core/Base/JSON.h" + +namespace Psc { + +class File_Play_Widget : public QWidget, public Base_Cache{ +public: + explicit File_Play_Widget(const std::string& id); + void start(size_t chunk_size, std::chrono::milliseconds interval); + struct Config{ + std::string log_path; + Config(); + Psc::JSON to_json(); + void from_json(Psc::JSON* that_json); + }; + void save(); + void save(Config& config); + File_Tool2 *path_select{}; + QProgressBar *progress_bar{}; + Text_Shower *speed_show{}; + Speed_Statistics speed; + File_Player file_player; + QPushButton *confirm_button{}; + QTimer *timer{}; +}; + +class Test_File_Play_Widget : public QWidget { +public: + File_Play_Widget *file_play_widget{}; + Value_Select_Simple *interval_select; + Value_Select_Simple *chunk_select; + QPushButton *start_button{}; + Test_File_Play_Widget(); +}; + +} // namespace Psc \ No newline at end of file diff --git a/YSGraphic_Core/Qt/File_Tool.cpp b/YSGraphic_Core/Qt/File_Tool.cpp new file mode 100644 index 0000000..060f15a --- /dev/null +++ b/YSGraphic_Core/Qt/File_Tool.cpp @@ -0,0 +1,153 @@ +#include "File_Tool.h" + +Psc::File_Tool::File_Tool(const QString& title, Mode mode, QWidget* parent) + + : QWidget(parent), mode_(mode) +{ + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + title_label = new QLabel(title, this); + + path_edit = new QLineEdit(this); + path_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + browse_btn = new QPushButton("选择", this); + browse_btn->setFixedWidth(48); + set_help_text(browse_btn, "点击打开文件\n支持手动输入路径"); + + + clear_btn = new QPushButton("清空", this); + clear_btn->setFixedWidth(48); + set_help_text(clear_btn, "点击清空当前文件内容"); + + backup_clear_btn = new QPushButton("备份清空", this); + backup_clear_btn->setFixedWidth(72); + set_help_text(backup_clear_btn, "点击清空,并且备份当前文件"); + + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(4); + + if (!title.isEmpty()) + layout->addWidget(title_label); + + layout->addWidget(path_edit); + layout->addWidget(browse_btn); + layout->addWidget(clear_btn); + layout->addWidget(backup_clear_btn); + + // 选择路径 + connect(browse_btn, &QPushButton::clicked, this, [this]() + { + QString selected; + + switch (mode_) + { + case Mode::OpenFile: + selected = QFileDialog::getOpenFileName(this, "Select File"); + break; + case Mode::SaveFile: + selected = QFileDialog::getSaveFileName(this, "Save File"); + break; + case Mode::Directory: + selected = QFileDialog::getExistingDirectory(this, "Select Directory"); + break; + } + + if (!selected.isEmpty()) + { + path_edit->setText(selected); + emit path_changed(selected); + } + }); + + + // 清空文件 + connect(clear_btn, &QPushButton::clicked, this, [this]() + { + const QString p = path_edit->text(); + if (p.isEmpty()) + return; + + QFileInfo info(p); + if (!info.exists() || !info.isFile()) + { + Message::show(Message::error, "文件不存在!", + this, 2000, Message::Top_Right); + return; + } + + QFile file(p); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + Message::show(Message::error, "无法清空文件!", + this, 2000, Message::Top_Right); + return; + } + + file.close(); + + Message::show(Message::success, "文件已清空", + this, 1500, Message::Top_Right); + }); + + // 备份并清空 + connect(backup_clear_btn, &QPushButton::clicked, this, [this]() + { + const QString p = path_edit->text(); + if (p.isEmpty()) + return; + + QFileInfo info(p); + if (!info.exists() || !info.isFile()) + { + Message::show(Message::error, "文件不存在!", + this, 2000, Message::Top_Right); + return; + } + + QString backupPath = + info.absolutePath() + "/" + + info.completeBaseName() + "_" + + QDateTime::currentDateTime().toString("yyyy_MM_dd_hh_mm_ss") + + "." + info.suffix(); + + if (!QFile::copy(p, backupPath)) + { + Message::show(Message::error, "备份失败!", + this, 2000, Message::Top_Right); + return; + } + + QFile file(p); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + { + Message::show(Message::error, "清空失败!", + this, 2000, Message::Top_Right); + return; + } + + file.close(); + + Message::show(Message::success, "已备份并清空", + this, 1500, Message::Top_Right); + }); + + connect(path_edit, &QLineEdit::editingFinished, this, + [this]() { emit path_changed(path_edit->text()); }); +} + +void Psc::File_Tool::set_title(const QString& t) +{ + title_label->setText(t); + title_label->setVisible(!t.isEmpty()); +} + +QString Psc::File_Tool::path() const +{ return path_edit->text(); } + +void Psc::File_Tool::set_path(const QString& p) +{ + path_edit->setText(p); + emit path_changed(p); +} diff --git a/YSGraphic_Core/Qt/File_Tool.h b/YSGraphic_Core/Qt/File_Tool.h new file mode 100644 index 0000000..e8b7b49 --- /dev/null +++ b/YSGraphic_Core/Qt/File_Tool.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Message.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "global.h" + +namespace Psc { +class File_Tool : public QWidget { + Q_OBJECT +public: + enum class Mode { OpenFile, SaveFile, Directory }; + explicit File_Tool(const QString& title = {}, Mode mode = Mode::OpenFile, QWidget *parent = nullptr); + void set_title(const QString& t); + [[nodiscard]] QString path() const; + void set_path(const QString &p); +signals: + void path_changed(const QString &); +private: + Mode mode_; + QLabel* title_label{}; + QLineEdit *path_edit{}; + QPushButton *browse_btn{}; + QPushButton *clear_btn{}; + QPushButton *backup_clear_btn{}; +}; +} // namespace Psc \ No newline at end of file diff --git a/YSGraphic_Core/Qt/File_Tool2.h b/YSGraphic_Core/Qt/File_Tool2.h new file mode 100644 index 0000000..a1a5c1e --- /dev/null +++ b/YSGraphic_Core/Qt/File_Tool2.h @@ -0,0 +1,147 @@ +#pragma once + +#include "Message.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Psc { + +class File_Tool2 : public QWidget { + Q_OBJECT +public: + enum class Mode { OpenFile, SaveFile, Directory }; + + explicit File_Tool2(const QString& title = {}, + Mode mode = Mode::OpenFile, + QWidget *parent = nullptr) + : QWidget(parent), mode_(mode) + { + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + title_label = new QLabel(title, this); + + path_edit = new QLineEdit(this); + path_edit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + browse_btn = new QPushButton("选择", this); + browse_btn->setFixedWidth(48); + + open_file_btn = new QPushButton("文件", this); + open_file_btn->setFixedWidth(40); + + open_dir_btn = new QPushButton("目录", this); + open_dir_btn->setFixedWidth(40); + + + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(4); + + if (!title.isEmpty()) + layout->addWidget(title_label); + + layout->addWidget(path_edit); + layout->addWidget(browse_btn); + layout->addWidget(open_file_btn); + layout->addWidget(open_dir_btn); + + + // 选择路径 + connect(browse_btn, &QPushButton::clicked, this, [this]() { + QString selected; + + switch (mode_) { + case Mode::OpenFile: + selected = QFileDialog::getOpenFileName(this, "Select File"); + break; + case Mode::SaveFile: + selected = QFileDialog::getSaveFileName(this, "Save File"); + break; + case Mode::Directory: + selected = QFileDialog::getExistingDirectory(this, "Select Directory"); + break; + } + + if (!selected.isEmpty()) { + path_edit->setText(selected); + emit path_changed(selected); + } + }); + + // 打开文件 + connect(open_file_btn, &QPushButton::clicked, this, [this]() { + const QString p = path_edit->text(); + if (p.isEmpty()) + return; + + QFileInfo info(p); + if (!info.exists() || !info.isFile()) { + Message::show(Message::error,"文件不存在!", + this,2000,Message::Top_Right); + return; + } + + QDesktopServices::openUrl( + QUrl::fromLocalFile(info.absoluteFilePath())); + }); + + // 打开目录 + connect(open_dir_btn, &QPushButton::clicked, this, [this]() { + const QString p = path_edit->text(); + if (p.isEmpty()) + return; + + QFileInfo info(p); + if (!info.exists()) { + Message::show(Message::error,"目录不存在!", + this,2000,Message::Top_Right); + return; + } + + QString dirPath = info.isDir() + ? info.absoluteFilePath() + : info.absolutePath(); + + QDesktopServices::openUrl( + QUrl::fromLocalFile(dirPath)); + }); + + connect(path_edit, &QLineEdit::editingFinished, this, + [this]() { emit path_changed(path_edit->text()); }); + } + + void set_title(const QString& t) { + title_label->setText(t); + title_label->setVisible(!t.isEmpty()); + } + + [[nodiscard]] QString path() const { return path_edit->text(); } + + void set_path(const QString &p) { + path_edit->setText(p); + emit path_changed(p); + } + +signals: + void path_changed(const QString &); + +private: + Mode mode_; + QLabel* title_label{}; + QLineEdit *path_edit{}; + QPushButton *browse_btn{}; + QPushButton *open_file_btn{}; + QPushButton *open_dir_btn{}; +}; + +} // namespace Psc \ No newline at end of file diff --git a/YSGraphic_Core/Qt/Message.cpp b/YSGraphic_Core/Qt/Message.cpp new file mode 100644 index 0000000..31903b3 --- /dev/null +++ b/YSGraphic_Core/Qt/Message.cpp @@ -0,0 +1,479 @@ +#include "Message.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Psc::Message { + +static Config global_config; + +class Toast; +class ResizeWatcher; + +static QHash>> containers; +static QHash> watchers; +static QHash>> queues; + +/* -------------------- 工具函数 -------------------- */ + +static bool is_dark_palette() +{ + QColor c = QApplication::palette().window().color(); + return c.lightness() < 128; +} + +static QColor interpolate(const QColor& a, const QColor& b, double t) +{ + return QColor( + a.red() + (b.red() - a.red()) * t, + a.green() + (b.green() - a.green()) * t, + a.blue() + (b.blue() - a.blue()) * t, + a.alpha() + (b.alpha() - a.alpha()) * t + ); +} + +struct ThemeColors { + QColor bg; + QColor text; + QColor progress_bg; + QColor progress_fg; +}; + +static ThemeColors theme_for(Type t) +{ + bool dark; + + if (global_config.theme == Theme_Mode::Auto) + dark = is_dark_palette(); + else + dark = (global_config.theme == Theme_Mode::Dark); + + QColor base; + switch (t) { + case success: base = QColor("#52C41A"); break; + case error: base = QColor("#F5222D"); break; + case warning: base = QColor("#FAAD14"); break; + default: base = QColor("#1890FF"); break; + } + + ThemeColors c; + + if (dark) { + c.bg = base.darker(250); + c.bg.setAlpha(210); + c.text = Qt::white; + c.progress_fg = base; + c.progress_bg = QColor(255,255,255,30); + } else { + c.bg = base.lighter(180); + c.bg.setAlpha(220); + c.text = QColor(30,30,30); + c.progress_fg = base.darker(110); + c.progress_bg = QColor(0,0,0,20); + } + + return c; +} + +/* -------------------- 毛玻璃 -------------------- */ + +static QPixmap blur_pixmap(const QPixmap& src, int radius) +{ + QGraphicsScene scene; + QGraphicsPixmapItem item; + item.setPixmap(src); + + QGraphicsBlurEffect blur; + blur.setBlurRadius(radius); + item.setGraphicsEffect(&blur); + + scene.addItem(&item); + + QImage result(src.size(), QImage::Format_ARGB32); + result.fill(Qt::transparent); + + QPainter painter(&result); + scene.render(&painter); + + return QPixmap::fromImage(result); +} + +static QPixmap generate_noise(int w, int h) +{ + QImage img(w, h, QImage::Format_ARGB32); + + for (int y=0; ybounded(20); + img.setPixelColor(x,y,QColor(255,255,255,gray)); + } + + return QPixmap::fromImage(img); +} + +/* -------------------- Toast -------------------- */ + +class Toast : public QWidget { +public: + Toast(Type t, + const QString& text, + QWidget* parent, + int duration) + : QWidget(parent), + type(t), + duration_ms(duration) + { + setAttribute(Qt::WA_DeleteOnClose); + setWindowFlags(Qt::FramelessWindowHint); + + auto shadow = new QGraphicsDropShadowEffect(this); + shadow->setBlurRadius(30); + shadow->setOffset(0,8); + shadow->setColor(QColor(0,0,0,120)); + setGraphicsEffect(shadow); + + opacity = new QGraphicsOpacityEffect(this); + setGraphicsEffect(opacity); + opacity->setOpacity(0); + + fade = new QPropertyAnimation(opacity,"opacity",this); + fade->setDuration(200 * global_config.animation_speed); + + move_anim = new QPropertyAnimation(this,"pos",this); + move_anim->setDuration(400 * global_config.animation_speed); + move_anim->setEasingCurve(QEasingCurve::OutBack); + + label = new QLabel(text,this); + label->setWordWrap(true); + label->setMaximumWidth(global_config.max_width); + + progress = new QProgressBar(this); + progress->setRange(0,duration_ms); + progress->setValue(duration_ms); + progress->setTextVisible(false); + progress->setFixedHeight(3); + + auto layout = new QVBoxLayout(this); + layout->setContentsMargins(16,12,16,8); + layout->addWidget(label); + layout->addWidget(progress); + + adjustSize(); + + apply_theme(theme_for(type)); + start_timer(); + } + + void appear_from(const QPoint& start, const QPoint& end) + { + prepare_blur(); + move(start); + show(); + + fade->setStartValue(0); + fade->setEndValue(1); + fade->start(); + + move_anim->setStartValue(start); + move_anim->setEndValue(end); + move_anim->start(); + } + + void move_to(const QPoint& target) + { + if (pos()==target) return; + move_anim->stop(); + move_anim->setStartValue(pos()); + move_anim->setEndValue(target); + move_anim->start(); + } + + void disappear() + { + fade->setStartValue(1); + fade->setEndValue(0); + connect(fade,&QPropertyAnimation::finished, + this,&QWidget::deleteLater); + fade->start(); + } + +protected: + void mousePressEvent(QMouseEvent*) override { disappear(); } + void enterEvent(QEvent*) override + { + if (timer) + timer->stop(); + } + + void leaveEvent(QEvent*) override + { + if (timer) + timer->start(); + } + + void paintEvent(QPaintEvent* e) override + { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QRectF r = rect(); + + QPainterPath path; + path.addRoundedRect(r, 12, 12); + + // 1️⃣ 裁剪圆角区域 + painter.setClipPath(path); + + // 2️⃣ 画毛玻璃 + if (global_config.enable_blur && !blurred_background.isNull()) + { + painter.drawPixmap(r.toRect(), blurred_background); + } + + // 3️⃣ 半透明背景 + painter.fillPath(path, current_theme.bg); + + // 4️⃣ Acrylic 噪点 + if (global_config.acrylic_mode) + { + painter.drawPixmap(r.toRect(), + generate_noise(width(), height())); + } + + // 5️⃣ 解除裁剪 + painter.setClipping(false); + + // 6️⃣ 调用基类,让子控件正常绘制 + QWidget::paintEvent(e); + } + +private: + void prepare_blur() + { + if (!global_config.enable_blur) return; + + QWidget* p = parentWidget(); + if (!p) return; + + + QPixmap grab = p->grab(geometry()); + blurred_background = blur_pixmap(grab,20); + } + + void apply_theme(const ThemeColors& c) + { + current_theme = c; + + label->setStyleSheet(QString( + "background:transparent;color:%1;" + ).arg(c.text.name())); + + progress->setStyleSheet(QString( + "QProgressBar{background:%1;border:none;}" + "QProgressBar::chunk{background:%2;border-radius:2px;}" + ).arg(c.progress_bg.name(QColor::HexArgb)) + .arg(c.progress_fg.name())); + } + + void start_timer() + { + if (!timer) + { + timer = new QTimer(this); + timer->setInterval(16); + + connect(timer, &QTimer::timeout, this, [this]() { + + elapsed_ms += 16; + int remain = duration_ms - elapsed_ms; + + progress->setValue(std::max(0, remain)); + + if (remain <= 0) + { + timer->stop(); + disappear(); + } + }); + } + + timer->start(); + } + + Type type; + int duration_ms; + int elapsed_ms = 0; + QLabel* label{}; + QProgressBar* progress{}; + QTimer* timer{}; + QGraphicsOpacityEffect* opacity{}; + QPropertyAnimation* fade{}; + QPropertyAnimation* move_anim{}; + ThemeColors current_theme; + QPixmap blurred_background; +}; + +/* -------------------- 布局 -------------------- */ + +static QWidget* top_parent(QWidget* w) +{ + while (w && w->parentWidget()) + w=w->parentWidget(); + return w; +} + +static void relayout(QWidget* top, Corner corner) +{ + auto& list = containers[top]; + + constexpr int margin=24; + constexpr int spacing=16; + + int y=(corner==Top_Left||corner==Top_Right) + ?margin:top->height()-margin; + + for(auto& toast:list) + { + if(!toast) continue; + + QPoint target; + + if(corner==Top_Left){ + target={margin,y}; + y+=toast->height()+spacing; + } + else if(corner==Top_Right){ + target={top->width()-toast->width()-margin,y}; + y+=toast->height()+spacing; + } + else if(corner==Bottom_Left){ + y-=toast->height(); + target={margin,y}; + y-=spacing; + } + else{ + y-=toast->height(); + target={top->width()-toast->width()-margin,y}; + y-=spacing; + } + + toast->move_to(target); + } +} +static void process_queue(QWidget* top, Corner corner) +{ + if (queues[top].isEmpty()) + return; + + auto item = queues[top].dequeue(); + + auto* toast = new Toast( + item.first, + item.second, + top, + global_config.default_duration + ); + + auto& list = containers[top]; + + constexpr int margin = 24; + constexpr int spacing = 16; + + // --------- 1️⃣ 计算最终位置 ---------- + int y = (corner == Top_Left || corner == Top_Right) + ? margin + : top->height() - margin; + + for (auto& t : list) + { + if (!t) continue; + + if (corner == Top_Left || corner == Top_Right) + y += t->height() + spacing; + else + y -= t->height() + spacing; + } + + QPoint final_pos; + + if (corner == Top_Left) + final_pos = QPoint(margin, y); + else if (corner == Top_Right) + final_pos = QPoint(top->width() - toast->width() - margin, y); + else if (corner == Bottom_Left) + final_pos = QPoint(margin, y - toast->height()); + else + final_pos = QPoint(top->width() - toast->width() - margin, + y - toast->height()); + + // --------- 2️⃣ 立即定位到 final ---------- + toast->move(final_pos); + + // --------- 3️⃣ 加入容器 ---------- + list.push_back(toast); + + // --------- 4️⃣ 计算飞入起点 ---------- + QPoint start = final_pos; + + if (corner == Top_Right || corner == Bottom_Right) + start.setX(top->width()); + else + start.setX(-toast->width()); + + // --------- 5️⃣ 飞入 ---------- + toast->appear_from(start, final_pos); + + // --------- 6️⃣ 超限处理 ---------- + if (list.size() > global_config.max_count) + { + auto old = list.front(); + list.removeFirst(); + if (old) old->disappear(); + } + + // --------- 7️⃣ 旧 toast 重排 ---------- + relayout(top, corner); +} + +/* -------------------- 公共接口 -------------------- */ + +void show(Type type, + const QString& text, + QWidget* parent, + int, + Corner corner) +{ + if(!parent) return; + + QWidget* top=top_parent(parent); + if(!top) return; + + queues[top].enqueue({type,text}); + + QTimer::singleShot(global_config.queue_interval, + top, + [top,corner](){ + process_queue(top,corner); + }); +} + +void set_config(const Config& cfg) +{ + global_config=cfg; +} + +} // namespace Message \ No newline at end of file diff --git a/YSGraphic_Core/Qt/Message.h b/YSGraphic_Core/Qt/Message.h new file mode 100644 index 0000000..3641679 --- /dev/null +++ b/YSGraphic_Core/Qt/Message.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace Psc::Message { + +enum Type { success, error, warning, info }; +enum Corner { Top_Left, Top_Right, Bottom_Left, Bottom_Right }; +enum class Theme_Mode { Light, Dark, Auto }; + +struct Config { + Corner default_corner = Top_Right; + int default_duration = 3000; + int max_count = 5; + int max_width = 360; + int queue_interval = 120; + + Theme_Mode theme = Theme_Mode::Auto; + + bool enable_blur = true; + bool acrylic_mode = false; + + double animation_speed = 1.0; +}; + +void set_config(const Config& cfg); + +void show(Type type, + const QString& text, + QWidget* parent, + int duration_ms = -1, + Corner corner = Top_Right); + +} // namespace Message \ No newline at end of file diff --git a/YSGraphic_Core/Qt/Text_Shower.cpp b/YSGraphic_Core/Qt/Text_Shower.cpp new file mode 100644 index 0000000..827ea05 --- /dev/null +++ b/YSGraphic_Core/Qt/Text_Shower.cpp @@ -0,0 +1,126 @@ +#include "Text_Shower.h" +#include +#include +#include + +namespace Psc { + +Text_Shower::Text_Shower(QWidget* parent) + : QWidget(parent) +{ + setAttribute(Qt::WA_TranslucentBackground); + + // 宽度交给布局,高度最小 + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); +} + +void Text_Shower::set_text(const QString& text) +{ + m_text = text; + updateGeometry(); // 通知布局重新计算 + update(); +} + +void Text_Shower::set_title(const QString& title) +{ + m_title = title; + updateGeometry(); + update(); +} + +void Text_Shower::set_max_width(int w) +{ + m_max_width = w; + updateGeometry(); +} + +bool Text_Shower::hasHeightForWidth() const +{ + return true; +} + +int Text_Shower::heightForWidth(int width) const +{ + width = std::min(width, m_max_width); + return calculate_height(width); +} + +QSize Text_Shower::sizeHint() const +{ + int w = std::min(400, m_max_width); + return QSize(w, calculate_height(w)); +} + +QSize Text_Shower::minimumSizeHint() const +{ + return QSize(120, 40); +} + +int Text_Shower::calculate_height(int width) const +{ + QFontMetrics fm(font()); + + int content_width = width - m_padding * 2; + if (content_width <= 0) + return 40; + + QRect textRect = fm.boundingRect( + QRect(0, 0, content_width, 10000), + Qt::TextWordWrap, + m_text + ); + + int height = textRect.height(); + + if (!m_title.isEmpty()) + { + QFont titleFont = font(); + titleFont.setBold(true); + QFontMetrics titleFm(titleFont); + + height += titleFm.height() + 6; + } + + height += m_padding * 2; + + return height; +} + +void Text_Shower::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + QRect r = rect(); + + painter.setBrush(QColor(255,255,255)); + painter.setPen(QPen(QColor(0,0,0), 1)); + painter.drawRoundedRect(r.adjusted(0,0,-1,-1), m_radius, m_radius); + + int y = m_padding; + + if (!m_title.isEmpty()) + { + QFont titleFont = font(); + titleFont.setBold(true); + painter.setFont(titleFont); + + QFontMetrics titleFm(titleFont); + painter.drawText(m_padding, + y + titleFm.ascent(), + m_title); + + y += titleFm.height() + 6; + painter.setFont(font()); + } + + QRect textRect(m_padding, y, + width() - m_padding * 2, + height() - y - m_padding); + + painter.drawText(textRect, + Qt::AlignLeft | Qt::TextWordWrap, + m_text); +} + +} \ No newline at end of file diff --git a/YSGraphic_Core/Qt/Text_Shower.h b/YSGraphic_Core/Qt/Text_Shower.h new file mode 100644 index 0000000..8334d63 --- /dev/null +++ b/YSGraphic_Core/Qt/Text_Shower.h @@ -0,0 +1,37 @@ +#pragma once +#include +#include + +namespace Psc { + +class Text_Shower : public QWidget { + Q_OBJECT +public: + explicit Text_Shower(QWidget* parent = nullptr); + + void set_text(const QString& text); + void set_title(const QString& title); + void set_max_width(int w); + + [[nodiscard]] QSize sizeHint() const override; + [[nodiscard]] QSize minimumSizeHint() const override; + + [[nodiscard]] bool hasHeightForWidth() const override; + [[nodiscard]] int heightForWidth(int width) const override; + +protected: + void paintEvent(QPaintEvent*) override; + +private: + [[nodiscard]] int calculate_height(int width) const; + +private: + QString m_text; + QString m_title; + + int m_padding = 8; + int m_radius = 8; + int m_max_width = 600; // 可选上限 +}; + +} \ No newline at end of file diff --git a/YSGraphic_Core/Qt/Value_Select.cpp b/YSGraphic_Core/Qt/Value_Select.cpp new file mode 100644 index 0000000..b817590 --- /dev/null +++ b/YSGraphic_Core/Qt/Value_Select.cpp @@ -0,0 +1,35 @@ +#include "Value_Select.h" +#include + + +namespace Psc { + + + + + + + + +} + +#ifdef _USE_GTEST +#include + +#include +#include + +// TEST(Enum_Select, xor1233) { +// int argc = 0; +// QApplication app(argc, nullptr); +// +// +// QWidget w; +// +// w.show(); +// app.exec(); +// } + +#endif + + diff --git a/YSGraphic_Core/Qt/Value_Select.h b/YSGraphic_Core/Qt/Value_Select.h new file mode 100644 index 0000000..13cd554 --- /dev/null +++ b/YSGraphic_Core/Qt/Value_Select.h @@ -0,0 +1,155 @@ +#pragma once +#include "Core/Base/global_include.h" + +#include +#include +#include +#include +#include + +#include "magic_enum/magic_enum.hpp" + +namespace Psc { + +// 用于选择一个值 +template +class Value_Select_Simple : public QWidget { +public: + QLabel *label; + QLineEdit *number_input; + void set_title(const QString& title) const { + label->setText(title); + } + [[nodiscard]] QString get_title() const { + return label->text(); + } + void set_value(Value_Type value) { + number_input->setText(QString::number(value)); + } + Value_Type get_value() { + auto str = number_input->text(); + if constexpr (std::is_integral_v) { + bool ok = false; + if constexpr (std::is_signed_v) { + auto v = str.toLongLong(&ok); + if (!ok) + throw std::runtime_error("Invalid integer"); + return static_cast(v); + } else { + auto v = str.toULongLong(&ok); + if (!ok) + throw std::runtime_error("Invalid unsigned integer"); + return static_cast(v); + } + } else if constexpr (std::is_floating_point_v) { + bool ok = false; + auto v = str.toDouble(&ok); + if (!ok) + throw std::runtime_error("Invalid float"); + return static_cast(v); + } else { + static_assert(sizeof(Value_Type) == 0, "Unsupported Value_Type"); + } + return Value_Type(); + } + explicit Value_Select_Simple(QWidget *parent = nullptr) : QWidget(parent) { + label = new QLabel(this); + number_input = new QLineEdit(this); + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(label); + layout->addWidget(number_input); + } +}; + + +// 选择带枚举单位的值 +template +class Value_Select : public QWidget { +public: + Value_Select_Simple* number; + QComboBox *unit; + void set_title(const QString& title) const { + number->set_title(title); + } + [[nodiscard]] QString get_title() const { + return number->get_title(); + } + explicit Value_Select(QWidget *parent = nullptr) : QWidget(parent) { + unit = new QComboBox(this); + number = new Value_Select_Simple; + for (auto ev : magic_enum::enum_values()) { + unit->addItem(enum_name(ev), static_cast(ev)); + } + auto layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(number); + layout->addWidget(unit); + } + + Value_Select &set_default_unit(Enum_Type value) { + int idx = unit->findData(static_cast(value)); + if (idx >= 0) + unit->setCurrentIndex(idx); + return *this; + } + + void set_number_value(Value_Type value) { + number->set_value(value); + } + + Enum_Type unit_enum() { return from_value(unit->currentData().toInt()); } + + virtual Value_Type value() { return number->get_value() * uint_value(unit_enum()); } + virtual Value_Type uint_value(Enum_Type enum_val) { return 1; } +protected: + Enum_Type from_name(const QString &name) { + auto v = magic_enum::enum_cast(name.toStdString()); + if (!v) + throw std::runtime_error("Invalid enum name"); + return *v; + } + + Enum_Type from_value(int value) { + auto v = magic_enum::enum_cast(value); + if (!v) + throw std::runtime_error("Invalid enum value"); + return *v; + } + + [[nodiscard]] QString enum_name(Enum_Type value) { + return QString::fromStdString(std::string(magic_enum::enum_name(value))); + } + + [[nodiscard]] QString enum_type_name() const { + return QString::fromStdString( + std::string(magic_enum::enum_type_name())); + } +}; + +enum class Memory_Size_Uint : size_t { Byte, KB, MB, GB }; +class Byte_Select : public Value_Select { +public: + size_t uint_value(Memory_Size_Uint e) override { + static constexpr size_t table[] = {1ull, 1024ull, 1024ull * 1024ull, + 1024ull * 1024ull * 1024ull}; + return table[static_cast(e)]; + } +}; + +enum class Frequent_Uint : size_t { + HZ, + KHZ, + MHZ, +}; +class Frequent_Select : public Value_Select { +public: + size_t uint_value(Frequent_Uint e) override { + static constexpr size_t table[] = {1ull, 1000ull, 1000ull * 1000ull}; + return table[static_cast(e)]; + } +}; + +} // namespace Psc \ No newline at end of file diff --git a/YSGraphic_Core/Qt/global.cpp b/YSGraphic_Core/Qt/global.cpp new file mode 100644 index 0000000..4dfbb2e --- /dev/null +++ b/YSGraphic_Core/Qt/global.cpp @@ -0,0 +1,31 @@ +#include "global.h" + +#include "Core/Base/global_include.h" +#include "Core/system/export.h" + +namespace Psc +{ + void set_help_text(QWidget* widget, const QString& text) + { + widget->setToolTip(text); + widget->setStatusTip(text); + widget->setWhatsThis(text); + } + + std::string get_config_dir() + { + std::string ret = Psc::get_exe_dir() + "/psc_config"; + create_dir_if_not_exists(ret); + return ret; + } + + Base_Cache::Base_Cache(const std::string& id) : id(id) + { + + } + + std::string Base_Cache::get_config_path() const + { + return get_config_dir() + "/" + id + "_config.json"; + } +} diff --git a/YSGraphic_Core/Qt/global.h b/YSGraphic_Core/Qt/global.h new file mode 100644 index 0000000..d93e86a --- /dev/null +++ b/YSGraphic_Core/Qt/global.h @@ -0,0 +1,21 @@ +#pragma once +#include +#include + +namespace Psc { + void set_help_text(QWidget* widget, const QString& text); + std::string get_config_dir(); + + + class Base_Cache + { + public: + std::string id; + Base_Cache(const std::string& id); + std::string get_config_path() const; + std::atomic_bool init = false; + }; + + + +} \ No newline at end of file diff --git a/YSGraphic_Core/Qt/功能性ui.txt b/YSGraphic_Core/Qt/功能性ui.txt new file mode 100644 index 0000000..e69de29 diff --git a/YSGraphic_Core/RenderAble.cpp b/YSGraphic_Core/RenderAble.cpp new file mode 100644 index 0000000..499ad7c --- /dev/null +++ b/YSGraphic_Core/RenderAble.cpp @@ -0,0 +1,163 @@ +#include "RenderAble.h" +#include "base/Plot.h" +#include "Axis/AbsAxis.h" +#include "plottable/HoverInfo.h" + + +namespace YSG { + QDebug operator<<(QDebug debug, const Range& range) { + QDebugStateSaver saver(debug); // 保存当前的 qDebug 状态 + debug.nospace() << "Range(" << range.lower << ", " << range.upper << ")"; + return debug; + } + + void SpinLock::lock() { + while (flag.test_and_set(std::memory_order_acquire)) {} + } + bool SpinLock::tryLock() { + return !flag.test_and_set(std::memory_order_acquire); + } + bool SpinLock::tryLock(int durationMillis) { + if (durationMillis == 0) { + return tryLock(); + } + auto duration = std::chrono::milliseconds(durationMillis); + auto start = std::chrono::steady_clock::now(); + while (std::chrono::steady_clock::now() - start < duration) { + if (tryLock()) { + return true; + } + } + return false; + } + void SpinLock::unlock() { + flag.clear(std::memory_order_release); + } + SpinLockGuard::~SpinLockGuard() { + if (mLock) mLock->unlock(); + } + SpinLockGuard::SpinLockGuard(SpinLock *lock) : mLock(lock) { + if (mLock) mLock->lock(); + } + + void RenderAble::init(Plot* plot, QString layerName) { + if(layerName.isEmpty()) { + layerName = plot->mDefaultLayerName; + } + plot->mLayerMap[layerName]->mRenderAbles.append(this); + mPlot = plot; + dPtr = createRenderData(); + dPtr->mRenderState = createRenderState(); + dPtr->mRenderStateCache = createRenderState(); + dPtr->mTempData = createTempData(); + dPtr->mTempDataCache = createTempData(); + dPtr->mRenderState->dPtr = dPtr; + dPtr->mRenderStateCache->dPtr = dPtr; + dPtr->qPtr = this; + } + + + bool RenderAble::ok() const { + return dPtr->mRenderStart && mPlot && !mPlot->mFirstResize && !mPlot->mFirstPrepareData; + } + + RenderAble::~RenderAble(){ + } + + bool RenderAble::dectCycleRely() { + QQueue queue; + queue.enqueue(this); + while (!queue.isEmpty()) { + RenderAble* current = queue.dequeue(); + QSet visited; + QSet recursionStack; + QVector path; + bool has = hasCycleRely(current, visited, recursionStack, path); + if(has){ + return true; + } + } + qDebug() << this << " 检测成功!"; + return false; + } + + bool RenderAble::hasCycleRely(RenderAble* node, QSet& visited, QSet& recursionStack, + QVector& currentPath) { + if (!node) return false; + + if (recursionStack.contains(node)) { + // 节点已经在当前递归路径中,检测到循环 + qDebug() << "Cycle detected in path:"; + + // 打印循环依赖链 + for (RenderAble* n : currentPath) { + qDebug() << n->mObjectName << "(" << n << ")->"; + } + qDebug() << node; // 打印形成循环的节点 + return true; + } + + if (visited.contains(node)) { + // 节点已经被处理过,无需再次处理 + return false; + } + + // 标记当前节点为已访问 + visited.insert(node); + // 将当前节点标记为递归路径中的节点 + recursionStack.insert(node); + // 记录当前节点在路径中 + currentPath.append(node); + + // 递归检查所有子节点 + for (RenderAble* child : node->mBeRelyList) { + if (hasCycleRely(child, visited, recursionStack, currentPath)) { + return true; + } + } + + // 从递归路径中移除当前节点 + recursionStack.remove(node); + // 从路径中移除当前节点 + currentPath.removeLast(); + return false; + } + + + + + + QVector getTurboColorGradient() { + QVector colors; + + QFile file(":/turbo.colorMap"); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "getTurboColorGradient2: Failed to open file"; + return colors; // 返回一个空的 std::vector + } + + QByteArray fileData = file.readAll(); + file.close(); + + // 检查文件内容 + if (fileData.size() != 768) { + qDebug() << "getTurboColorGradient2: File size is not as expected (768 bytes), got" << fileData.size(); + return colors; // 返回一个空的 std::vector + } + + // 确保每个颜色有 3 个字节 (RGB),总共有 256 个颜色 + colors.reserve(256); + for (int i = 0; i < 256; ++i) { + uchar r = fileData[i * 3]; + uchar g = fileData[i * 3 + 1]; + uchar b = fileData[i * 3 + 2]; + colors.push_back(qRgb(r, g, b)); + } + + return colors; + } + + SRC RenderState::src() { + return dPtr->mRenderState == this ? SRC::Render : SRC::Cache; + } +} diff --git a/YSGraphic_Core/RenderAble.h b/YSGraphic_Core/RenderAble.h new file mode 100644 index 0000000..1be75d5 --- /dev/null +++ b/YSGraphic_Core/RenderAble.h @@ -0,0 +1,195 @@ +#pragma once +#include +#include + + +#include "GlobalTypes.h" + + + +namespace YSG { + + #define INIT_GET(className) className##Private* privateData = d(); \ + className##RenderState* renderState = reinterpret_cast(d()->getState(src)); \ + + + struct TempData { + virtual ~TempData() = default; + }; + struct RenderState { + virtual ~RenderState() = default; + RenderData *dPtr{}; + SRC src(); + }; + struct RenderData { + SpinLock mBufferLock; + RenderAble *qPtr{}; + bool mRenderStart = false; + RenderState* getState(SRC src) const { + switch (src) { + case SRC::Render: { + return mRenderState; + } + case SRC::Cache: { + return mRenderStateCache; + } + case SRC::Auto: { + return mRenderStart ? mRenderState : mRenderStateCache; + } + } + qDebug() << "RenderState* RenderData::getState(SRC src) error! 不存在的枚举值" << static_cast(src); + return nullptr; + } + + RenderState *mRenderState{}, *mRenderStateCache{}; + TempData *mTempData{}, *mTempDataCache{}; + protected: + virtual void loadCache() = 0; + virtual RenderState* renderState() = 0; + virtual RenderState* renderStateCache() = 0; + virtual TempData* tempData() = 0; + virtual TempData* tempDataCache() = 0; + + virtual ~RenderData() { + delete mTempData; + delete mTempDataCache; + delete mRenderState; + delete mRenderStateCache; + } + virtual void draw(QPainter* painter){} + virtual void prepareData() { + SpinLockGuard g(&mBufferLock); + //qDebug() << "qPtr->mObjectName " << qPtr->mObjectName; + loadCache(); + } + virtual bool selectTest(const QPointF& pos){return false;} + virtual void plotEvent(QEvent* event){} + virtual void mousePressEvent(QMouseEvent* event) {} + virtual void mouseMoveEvent(QMouseEvent* event) {} + virtual void mouseReleaseEvent(QMouseEvent* event) {} + virtual void keyPressEvent(QKeyEvent* event){} + virtual void keyReleaseEvent(QKeyEvent* event){} + virtual void firstResizeEvent(QResizeEvent* event){} + virtual void resizeEvent(QResizeEvent* event){} + virtual void wheelEvent(QWheelEvent* event){} + friend struct RenderAble; + }; + + struct BaseRenderData : RenderData { + RenderState* renderState() override {return nullptr;} + RenderState* renderStateCache() override {return nullptr;} + TempData* tempData() override {return nullptr;} + TempData* tempDataCache() override {return nullptr;} + void loadCache() override{} + }; + + class Plot; + struct RenderAble { + virtual RenderData* createRenderData() {return new BaseRenderData;} + virtual RenderState* createRenderState(){return new RenderState;} + virtual TempData* createTempData() {return new TempData;} + virtual void init(Plot *plot, QString layerName = ""); + virtual void draw(QPainter* painter){dPtr->draw(painter);} + virtual void prepareData(){dPtr->prepareData();} + virtual bool selectTest(const QPointF& pos){return dPtr->selectTest(pos);} + virtual void plotEvent(QEvent* event){dPtr->plotEvent(event);} + virtual void mousePressEvent(QMouseEvent* event) {dPtr->mousePressEvent(event);} + virtual void mouseMoveEvent(QMouseEvent* event) {dPtr->mouseMoveEvent(event);} + virtual void mouseReleaseEvent(QMouseEvent* event) {dPtr->mouseReleaseEvent(event);} + virtual void keyPressEvent(QKeyEvent* event){dPtr->keyPressEvent(event);} + virtual void keyReleaseEvent(QKeyEvent* event){dPtr->keyReleaseEvent(event);} + virtual void firstResizeEvent(QResizeEvent* event){dPtr->firstResizeEvent(event);} + virtual void resizeEvent(QResizeEvent* event){dPtr->resizeEvent(event);} + virtual void wheelEvent(QWheelEvent* event){dPtr->wheelEvent(event);}; + + QString mObjectName; + RenderData *dPtr{}; + Plot *mPlot{}; + [[nodiscard]] bool ok() const; + bool mPrepared = false; + // 这里面可以预先准备数据用来同步 + virtual ~RenderAble(); + virtual bool select(QPointF pos){return false;} + bool mVisable = true; + RenderAble* mParent{}; + QVector mBeRelyList; + bool dectCycleRely(); + private: + bool hasCycleRely(RenderAble* node, QSet& visited, QSet& recursionStack, QVector& currentPath); + }; + + + #define Q_Ptr2(ClassName) \ + ClassName##Private* d() {return reinterpret_cast(dPtr);} \ + RenderData* createRenderData() override; \ + RenderState* createRenderState() override; \ + TempData* createTempData() override; \ + protected: \ + ClassName(); \ + friend struct ClassName##Private; \ + friend struct ClassName##RenderState; \ + friend struct ClassName##TempData; \ + public: + + + #define Q_Ptr(ClassName) \ + ClassName(); \ + ClassName##Private* d() {return reinterpret_cast(dPtr);} \ + RenderData* createRenderData() override; \ + RenderState* createRenderState() override; \ + TempData* createTempData() override; \ + + + + #define Q_Ptr_cpp(ClassName) \ + ClassName::ClassName() { \ + dPtr = new ClassName##Private; \ + mObjectName = #ClassName; \ + } \ + RenderData* ClassName::createRenderData() {return new ClassName##Private;} \ + RenderState* ClassName::createRenderState() {return new ClassName##RenderState;} \ + TempData* ClassName::createTempData() {return new ClassName##TempData;} \ + + + #define D_Ptr_3(ClassName) \ + ClassName##RenderState* renderState() override {return reinterpret_cast(mRenderState);} \ + ClassName##RenderState* renderStateCache() override {return reinterpret_cast(mRenderStateCache);} \ + ClassName##TempData* tempData() override {return reinterpret_cast(mTempData);} \ + ClassName##TempData* tempDataCache() override {return reinterpret_cast(mTempDataCache);} \ + friend class ClassName; \ + void loadCache() override { \ + *renderState() = *renderStateCache(); \ + std::swap(mTempData, mTempDataCache); \ + if(!mRenderStart) mRenderStart = true; \ + } + + + + #define D_Ptr(ClassName) \ + ClassName* q() {return reinterpret_cast(qPtr);} \ + ClassName##RenderState* renderState() override {return reinterpret_cast(mRenderState);} \ + ClassName##RenderState* renderStateCache() override {return reinterpret_cast(mRenderStateCache);} \ + ClassName##TempData* tempData() override {return reinterpret_cast(mTempData);} \ + ClassName##TempData* tempDataCache() override {return reinterpret_cast(mTempDataCache);} \ + friend class ClassName; \ + void loadCache() override { \ + *renderState() = *renderStateCache(); \ + std::swap(mTempData, mTempDataCache); \ + if(!mRenderStart) mRenderStart = true; \ + } + + #define D_Ptr2(ClassName) \ + ClassName* q() {return reinterpret_cast(qPtr);} \ + ClassName##RenderState* renderState() override {return reinterpret_cast(mRenderState);} \ + ClassName##RenderState* renderStateCache() override {return reinterpret_cast(mRenderStateCache);} \ + ClassName##TempData* tempData() override {return reinterpret_cast(mTempData);} \ + ClassName##TempData* tempDataCache() override {return reinterpret_cast(mTempDataCache);} \ + friend class ClassName; + + #define INIT_SET(className) className##Private* pd = d(); \ + className##RenderState* sc = pd->renderStateCache();\ + className##TempData* td = pd->tempDataCache(); \ + SpinLockGuard _guard(&pd->mBufferLock); \ + + +} \ No newline at end of file diff --git a/YSGraphic_Core/algorithm.hpp b/YSGraphic_Core/algorithm.hpp new file mode 100644 index 0000000..450db3c --- /dev/null +++ b/YSGraphic_Core/algorithm.hpp @@ -0,0 +1,89 @@ +#pragma once +#include + +namespace YSG{ + #define LOWER(offset) l_lower(spaces[offset]) + #define UPPER(offset) l_upper(spaces[offset]) + template + int binary_search(double value, T** spaces, int spaceSize, std::function& l_lower, std::function& l_upper) { + int startIndex = 0, endIndex = spaceSize - 1; + if(startIndex == endIndex) return startIndex; + double min = LOWER(startIndex), max = UPPER(endIndex); + if(value < min) { + //qDebug() << QString("warnning binary_search value:%1 < %2").arg(value).arg(min); + return startIndex; + } + if(value > max) { + //qDebug() << QString("warnning binary_search value:%1 > %2").arg(value).arg(max); + return endIndex; + } + while(startIndex != endIndex) { + if(startIndex+1 == endIndex) { + double mid = (UPPER(startIndex)+LOWER(endIndex))/2.0; + if(value < mid) return startIndex; + return endIndex; + } + int m = (startIndex + endIndex)/2; + double lower = m!=0 ? (UPPER(m-1)+LOWER(m))/2.0 : min; + double upper = m!=spaceSize-1 ? (UPPER(m)+LOWER(m+1))/2.0 : max; + //qDebug() << " lower == " << lower << " upper == " << upper << "midIndex == " << m << " value == " << value; + //qDebug() << " startIndex == " << startIndex << " endIndex == " << endIndex; + if(value < lower) { + endIndex = m; + } else if(value > upper) { + startIndex = m; + } else { + return m; + } + } + qDebug() << QString("warnning startIndex:%1, endIndex:%2").arg(startIndex).arg(endIndex); + return startIndex; + } + #undef LOWER + #undef UPPER + + static int binary_search(double value, const QVector& data, bool& ok) { + if (data.isEmpty()) { + ok = false; + return -1; // 返回 -1 表示数组为空 + } + + // 判断排序顺序 + bool ascendingOrder = data[0] < data[data.size() - 1]; + int left = 0; + int right = data.size() - 1; + + // 二分查找 + while (left <= right) { + int mid = left + (right - left) / 2; + + if (mid < data.size() - 1) { + if (ascendingOrder) { + if (data[mid] <= value && data[mid + 1] > value) { + ok = true; + return mid; + } else if (data[mid] < value) { + left = mid + 1; + } else { + right = mid - 1; + } + } else { // 处理降序排列的情况 + if (data[mid] >= value && data[mid + 1] < value) { + ok = true; + return mid; + } else if (data[mid] > value) { + left = mid + 1; + } else { + right = mid - 1; + } + } + } else { + break; // 如果 mid 已经是最后一个元素,不再继续查找 + } + } + + ok = false; + // 如果找不到,则返回 -1,表示 value 不在区间内 + return -1; + } +} \ No newline at end of file diff --git a/YSGraphic_Core/base/CircularLinkedList.hpp b/YSGraphic_Core/base/CircularLinkedList.hpp new file mode 100644 index 0000000..fda5376 --- /dev/null +++ b/YSGraphic_Core/base/CircularLinkedList.hpp @@ -0,0 +1,96 @@ +#pragma once + +namespace YSG { + template + struct CircularLinkedListNode { + CircularLinkedListNode *prev = nullptr, *next = nullptr; + T data; + }; + template + class CircularLinkedList { + public: + CircularLinkedListNode *mHead = nullptr; + CircularLinkedListNode *mCur = nullptr; + void init(const QVector &dataList) { + mHead = new CircularLinkedListNode(); + mHead->data = dataList.first(); + CircularLinkedListNode *prev = mHead; + int n = dataList.size(); + for (int i = 1; i < n - 1; i++) { + auto node = new CircularLinkedListNode(); + node->data = dataList[i]; + node->prev = prev; + prev->next = node; + prev = node; + } + auto tail = new CircularLinkedListNode(); + tail->data = dataList.last(); + tail->prev = prev; + prev->next = tail; + tail->next = mHead; + mHead->prev = tail; + mCur = mHead; + } + CircularLinkedListNode *move(int step = 1) { + if (step == 0) return nullptr; + return step > 0 ? next(step) : prev(-step); + } + CircularLinkedListNode *next(int step = 1) { + if (step <= 0) return nullptr; + for (int i = 0; i < step; i++) { + mCur = mCur->next; + } + return mCur; + } + CircularLinkedListNode *prev(int step = 1) { + if (step <= 0) return nullptr; + for (int i = 0; i < step; i++) { + mCur = mCur->prev; + } + return mCur; + } + void insertNext(const T &t, CircularLinkedListNode *that = nullptr) { + if (that == nullptr) that = mCur; + auto node = new CircularLinkedListNode(); + node->data = t; + node->next = that->next; + node->prev = that; + that->next = node; + } + void insertPrev(const T &t, CircularLinkedListNode *that = nullptr) { + if (that == nullptr) that = mCur; + auto node = new CircularLinkedListNode(); + node->data = t; + node->prev = that->prev; + node->next = that; + that->prev = node; + } + QVector *> toVector() { + if (mHead == nullptr) return {}; + QVector *> ret; + CircularLinkedListNode *cur = mHead; + do { + ret.push_back(cur); + cur = cur->next; + } while (cur != mHead); + return ret; + } + void clear() { + if (mHead == nullptr) return; + CircularLinkedListNode *cur = mHead; + do { + CircularLinkedListNode *temp = cur; + cur = cur->next; + delete temp; + } while (cur != mHead); + mHead = nullptr; + } + ~CircularLinkedList() { + clear(); + } + }; +} + + + + diff --git a/YSGraphic_Core/base/FileDialog/FileDialog.cpp b/YSGraphic_Core/base/FileDialog/FileDialog.cpp new file mode 100644 index 0000000..3d1a53c --- /dev/null +++ b/YSGraphic_Core/base/FileDialog/FileDialog.cpp @@ -0,0 +1,39 @@ +#include "FileDialog.h" +#include +namespace YSG { + FileDialog::FileDialog(QWidget *parent) : QWidget(parent) { + //setAttribute(Qt::WA_OpaquePaintEvent); + } + FileDialog::~FileDialog() { + } + void FileFolder::draw(int index, QPainter *painter) { + static QImage fileFolder = QImage(":fileFolder.png"); + int x = 10 + 20 * deep; + int y = 10 + 30 * index; +// painter->drawImage(x, y, fileFolder); + painter->drawImage(0, 0, fileFolder); + painter->drawText(x + 20, y + 10, mName); + } + void FileDialog::paintEvent(QPaintEvent *event) { + QWidget::paintEvent(event); + QPainter painter(this); + int index = 0; + for (FileFolder *&root: roots) { + QQueue que; + root->deep = 0; + que.enqueue(root); + while (!que.empty()) { + FileFolder *cur = que.dequeue(); + int n = cur->sons.size(); + cur->draw(index++, &painter); + for (int i = 0; i < n; ++i) { + if (cur->isShowSons) { + cur->sons[i]->deep = cur->deep + 1; + que.enqueue(cur->sons[i]); + } + } + } + } + painter.end(); + } +} diff --git a/YSGraphic_Core/base/FileDialog/FileDialog.h b/YSGraphic_Core/base/FileDialog/FileDialog.h new file mode 100644 index 0000000..0341c4e --- /dev/null +++ b/YSGraphic_Core/base/FileDialog/FileDialog.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../Node.hpp" +#include "Core/Base/global_include.h" +#include + +namespace YSG { + class FileFolder : public Node { + public: + QString mName; + bool isShowSons = false; + int deep = 0; + void draw(int index, QPainter *painter); + }; + class FileDialog : public QWidget, public NodeManager, public Psc::Singleton { + Q_OBJECT + public: + explicit FileDialog(QWidget *parent = nullptr); + ~FileDialog() override; + protected: + void paintEvent(QPaintEvent *event) override; + }; +} + + + + diff --git a/YSGraphic_Core/base/FileDialog/icon/FileFolder.qrc b/YSGraphic_Core/base/FileDialog/icon/FileFolder.qrc new file mode 100644 index 0000000..86119ff --- /dev/null +++ b/YSGraphic_Core/base/FileDialog/icon/FileFolder.qrc @@ -0,0 +1,6 @@ + + + fileFolder.png + arrowDown.png + + \ No newline at end of file diff --git a/YSGraphic_Core/base/FileDialog/icon/arrowDown.png b/YSGraphic_Core/base/FileDialog/icon/arrowDown.png new file mode 100644 index 0000000..b56a075 Binary files /dev/null and b/YSGraphic_Core/base/FileDialog/icon/arrowDown.png differ diff --git a/YSGraphic_Core/base/FileDialog/icon/fileFolder.png b/YSGraphic_Core/base/FileDialog/icon/fileFolder.png new file mode 100644 index 0000000..68f2f79 Binary files /dev/null and b/YSGraphic_Core/base/FileDialog/icon/fileFolder.png differ diff --git a/YSGraphic_Core/base/Global.cpp b/YSGraphic_Core/base/Global.cpp new file mode 100644 index 0000000..f260624 --- /dev/null +++ b/YSGraphic_Core/base/Global.cpp @@ -0,0 +1,50 @@ +#include "Global.h" +#include +#include "Plot_p.h" + +namespace YSG { + + TimerThread::TimerThread() { + connect(QApplication::instance(), &QApplication::aboutToQuit, [&]() { + if (isRunning()) { + quit(); + wait(); + } + delete this; + }); + } + + void TimerThread::run() { + for (auto &plot: mPlots) { + plot->d->mTimer = new QTimer; + plot->d->mTimer->setTimerType(Qt::PreciseTimer); + plot->d->mTimerThread = this; + connect(plot->d->mTimer, &QTimer::timeout, plot, &Plot::render, Qt::DirectConnection); + // show里的调用可能比这个 线程开启提前, 那是 timer是nullptr + if (plot->isVisible()) plot->startRender(); + } + exec(); + for (auto &plot: mPlots) { + plot->d->mTimer->stop(); + delete plot->d->mTimer; + } + } + + void Global::startAllTimeThread() { + for (auto &thread: mTimerThreadMap) { + //qDebug() << "startAllTimeThread == " << thread->objectName(); + thread->start(); + } + mTimerId = startTimer(10); + } + + void Global::timerEvent(QTimerEvent* event) { + if (event->timerId() != mTimerId) return; + for (TimerThread *t: mTimerThreadMap) { + for (Plot *plot: t->mPlots) { + if (plot->d->mPlotState.loadAcquire() != PlotPrivate::PlotState::NeedToPaint) continue; + plot->repaint(); + } + } + } +} diff --git a/YSGraphic_Core/base/Global.h b/YSGraphic_Core/base/Global.h new file mode 100644 index 0000000..e760c63 --- /dev/null +++ b/YSGraphic_Core/base/Global.h @@ -0,0 +1,93 @@ +#pragma once +#include "../GlobalTypes.h" +#include "Core/Base/global_include.h" + +#include +#include + +namespace YSG { + class LIB_DECL TimerThread : public QThread { + Q_OBJECT + public: + QList mPlots; + TimerThread(); + void run() override; + }; + + class LIB_DECL Global : public QObject, public Psc::Singleton { + public: + int mTimerId{}; + QMap mTimerThreadMap; + int mInterval = 10; + void startAllTimeThread(); + void timerEvent(QTimerEvent *event) override; + QVector mColorMap = getTurboColorGradient(); + }; +} + +#define QLOG_POS QString(__func__) + QString(" ") + __FILE__ + QString(":") + QString::number(__LINE__) + + +template +struct has_qdebug_stream : std::false_type {}; + +template +struct has_qdebug_stream() << std::declval() + ) + > +> : std::true_type {}; + +template +QString to_qstring(const T& value) +{ + using C = std::decay_t; + if constexpr (has_qdebug_stream::value) + { + QString s; + QDebug dbg(&s); + dbg << value; + return s; + } + else + { + static_assert(sizeof(C) == 0, + "Psc: to_qstring type error"); + } + return ""; +} + +// // 通用模板 +// template +// QString to_qstring(const T& value) { +// static_assert(std::is_arithmetic::value, "T must be an arithmetic type"); +// return QString::number(value); +// } +// +// inline QString to_qstring(const char* value) { +// return value; +// } +// +// inline QString to_qstring(const QString& value) { +// return value; +// } + +// 特化模板:处理 bool 类型 +template <> +inline QString to_qstring(const bool& value) { + return value ? QStringLiteral("true") : QStringLiteral("false"); +} +#define VAR_QSTR(P) ("[" + to_qstring(#P) + "]:" + to_qstring(P) + to_qstring(", ")) +#define VAR_QSTR_1(P1) VAR_QSTR(P1) +#define VAR_QSTR_2(P1, P2) VAR_QSTR_1(P1) + VAR_QSTR(P2) +#define VAR_QSTR_3(P1, P2, P3) VAR_QSTR_2(P1, P2) + VAR_QSTR(P3) +#define VAR_QSTR_4(P1, P2, P3, P4) VAR_QSTR_3(P1, P2, P3) + VAR_QSTR(P4) +#define VAR_QSTR_5(P1, P2, P3, P4, P5) VAR_QSTR_4(P1, P2, P3, P4) + VAR_QSTR(P5) +#define VAR_QSTR_6(P1, P2, P3, P4, P5, P6) VAR_QSTR_5(P1, P2, P3, P4, P5) + VAR_QSTR(P6) +#define VAR_QSTR_7(P1, P2, P3, P4, P5, P6, P7) VAR_QSTR_6(P1, P2, P3, P4, P5, P6) + VAR_QSTR(P7) +#define VAR_QSTR_8(P1, P2, P3, P4, P5, P6, P7, P8) VAR_QSTR_7(P1, P2, P3, P4, P5, P6, P7) + VAR_QSTR(P8) +#define VAR_QSTR_9(P1, P2, P3, P4, P5, P6, P7, P8, P9) VAR_QSTR_8(P1, P2, P3, P4, P5, P6, P7, P8) + VAR_QSTR(P9) +#define VAR_QSTR_10(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) VAR_QSTR_9(P1, P2, P3, P4, P5, P6, P7, P8, P9) + VAR_QSTR(P10) + diff --git a/YSGraphic_Core/base/Graphic.cpp b/YSGraphic_Core/base/Graphic.cpp new file mode 100644 index 0000000..0838e26 --- /dev/null +++ b/YSGraphic_Core/base/Graphic.cpp @@ -0,0 +1,32 @@ +#include "Graphic.h" + + +namespace YSG { + Graphic::Graphic() { + setSurfaceType(OpenGLSurface); + } + + bool Graphic::event(QEvent* event) { + bool ret = QWindow::event(event); + switch (event->type()) { + case QEvent::Resize: { + auto e = reinterpret_cast(event); + { + SpinLockGuard g(&mResizeMtx); + for(PainterLayer* b : mPainterBuffers) { + QSize size = e->size(); + b->mBuffer1 = new QPixmap(size); + b->mBuffer2 = new QPixmap(size); + } + } + if(mFirstResize) mFirstResize = false; + break; + } + default: { + break; + } + } + return ret; + } + +} diff --git a/YSGraphic_Core/base/Graphic.h b/YSGraphic_Core/base/Graphic.h new file mode 100644 index 0000000..3f03c91 --- /dev/null +++ b/YSGraphic_Core/base/Graphic.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include "../GlobalTypes.h" +namespace YSG{ + class LIB_DECL GraphicLayer { + public: + }; + class PainterLayer : public GraphicLayer{ + public: + QPixmap *mBuffer1{}; + QPixmap *mBuffer2{}; + bool it = true; + void draw() { + it = !it; + QPixmap* cur = it ? mBuffer1 : mBuffer2; + QPainter painter(cur); + painter.fillRect(cur->rect(), Qt::red); + } + void updateTexture() { + QPixmap* cur = it ? mBuffer1 : mBuffer2; + } + }; + class LIB_DECL OpenglLayer : public GraphicLayer { + public: + void draw(); + }; + class Graphic : public QWindow { + public: + Graphic(); + QVector mLayers; + QVector mPainterBuffers; + QVector mOpenglBuffers; + SpinLock mPainterRenderMtx, mResizeMtx; + bool mFirstResize; + protected: + bool event(QEvent*) override; + public: + virtual void createLayers() { + + }; + void render() { + if(mFirstResize) return; + SpinLockGuard g(&mResizeMtx); + for(OpenglLayer* b : mOpenglBuffers) { + b->draw(); + } + // 插入opengl同步变量 + for(PainterLayer* b : mPainterBuffers) { + b->draw(); + b->updateTexture(); + } + // 等待opengl同步变量 + // opengl汇总绘制 + } + }; +} + diff --git a/YSGraphic_Core/base/MutiSelectRect.cpp b/YSGraphic_Core/base/MutiSelectRect.cpp new file mode 100644 index 0000000..33c61a8 --- /dev/null +++ b/YSGraphic_Core/base/MutiSelectRect.cpp @@ -0,0 +1,70 @@ +#include "MutiSelectRect_p.h" +namespace YSG { + Q_Ptr_cpp(MutiSelectRect) + MutiSelectRect::Builder::Builder(AbsAxis* keyAxis, AbsAxis* valueAxis) { + ASSERT(keyAxis->mPlot != nullptr, "Afterglow build error! keyAxis->mPlot == nullptr"); + ASSERT(keyAxis->mPlot == valueAxis->mPlot, "Afterglow build error! keyAxis->mPlot != valueAxis->mPlot"); + plot = keyAxis->mPlot; + this->keyAxis = keyAxis; + this->valueAxis = valueAxis; + } + + MutiSelectRect* MutiSelectRect::Builder::build() { + auto ret = new MutiSelectRect(); + ret->init(plot, layerName); + MutiSelectRectPrivate* pd = ret->d(); + MutiSelectRectRenderState* sc = pd->renderStateCache(); + sc->mHorizontalAxis = keyAxis; + sc->mVerticalAxis = valueAxis; + sc->mFont = font; + sc->mFontPen = fontPen; + sc->mRectBrush = rectBrush; + sc->mBorderPen = borderPen; + return ret; + } + + void MutiSelectRect::setHorizontalAxis(AbsAxis* axis) { + INIT_SET(MutiSelectRect) + sc->mHorizontalAxis = axis; + } + + void MutiSelectRect::setVerticalAxis(AbsAxis* axis) { + INIT_SET(MutiSelectRect) + sc->mVerticalAxis = axis; + } + + QFont MutiSelectRect::font(SRC src) { + INIT_GET(MutiSelectRect) + return renderState->mFont; + } + QPen MutiSelectRect::fontPen(SRC src) { + INIT_GET(MutiSelectRect) + return renderState->mFontPen; + } + QBrush MutiSelectRect::rectBrush(SRC src) { + INIT_GET(MutiSelectRect) + return renderState->mRectBrush; + } + QPen MutiSelectRect::borderPen(SRC src) { + INIT_GET(MutiSelectRect) + return renderState->mBorderPen; + } + + void MutiSelectRect::setFont(const QFont& font){ + INIT_SET(MutiSelectRect) + sc->mFont = font; + } + void MutiSelectRect::setFontPen(const QPen& pen){ + INIT_SET(MutiSelectRect) + sc->mFontPen = pen.color().isValid() ? pen : Qt::NoPen; + } + void MutiSelectRect::setRectBrush(const QBrush& brush) { + INIT_SET(MutiSelectRect) + sc->mRectBrush = brush.color().isValid() ? brush : Qt::NoBrush; + } + void MutiSelectRect::setBorderPen(const QPen& pen) { + INIT_SET(MutiSelectRect) + sc->mBorderPen = pen.color().isValid() ? pen : Qt::NoPen; + } + +} \ No newline at end of file diff --git a/YSGraphic_Core/base/MutiSelectRect.h b/YSGraphic_Core/base/MutiSelectRect.h new file mode 100644 index 0000000..3ec8f00 --- /dev/null +++ b/YSGraphic_Core/base/MutiSelectRect.h @@ -0,0 +1,41 @@ +#pragma once + +#include "../RenderAble.h" + +namespace YSG { + struct MutiSelectRectPrivate; + class LIB_DECL MutiSelectRect : public RenderAble { + public: + Q_Ptr2(MutiSelectRect) + void setHorizontalAxis(AbsAxis* axis); + void setVerticalAxis(AbsAxis* axis); + QFont font(SRC=SRC::Auto); + QPen fontPen(SRC=SRC::Auto); + QBrush rectBrush(SRC=SRC::Auto); + QPen borderPen(SRC=SRC::Auto); + void setFont(const QFont& font); + void setFontPen(const QPen& pen); + void setRectBrush(const QBrush& brush); + void setBorderPen(const QPen& pen); + struct Builder; + }; + + struct LIB_DECL MutiSelectRect::Builder { + SETTER(QFont, font, QFont()) + SETTER(QPen, fontPen, QPen(Qt::white)) + SETTER(QBrush, rectBrush, QColor(0, 0, 255, 50)) + SETTER(QPen, borderPen, []() { + QPen pen(Qt::white); + pen.setStyle(Qt::DashLine); + return pen; + }()) + SETTER(QString, layerName, "legend") + Builder(AbsAxis* keyAxis, AbsAxis* valueAxis); + MutiSelectRect* build(); + protected: + AbsAxis* keyAxis; + AbsAxis* valueAxis{}; + Plot *plot{}; + }; +} + diff --git a/YSGraphic_Core/base/MutiSelectRect_p.h b/YSGraphic_Core/base/MutiSelectRect_p.h new file mode 100644 index 0000000..86e9bef --- /dev/null +++ b/YSGraphic_Core/base/MutiSelectRect_p.h @@ -0,0 +1,144 @@ +#pragma once + +#include + +#include "MutiSelectRect.h" +#include "Plot.h" +#include "../Axis/AbsAxis.h" + +namespace YSG { + struct MutiSelectRectRenderState : YSG::RenderState { + QVector mRects; + QFont mFont; + QPen mBorderPen, mFontPen = QPen(Qt::white); + QBrush mRectBrush = QColor(0, 0, 255, 50); + AbsAxis *mHorizontalAxis{}, *mVerticalAxis{}; + + MutiSelectRectRenderState() { + // NoPen, + // SolidLine, + // DashLine, + // DotLine, + // DashDotLine, + // DashDotDotLine, + // CustomDashLine + mBorderPen.setStyle(Qt::DashLine); + mBorderPen.setColor(Qt::white); + } + }; + + struct MutiSelectRectTempData : YSG::TempData { + + }; + + struct MutiSelectRectPrivate : YSG::RenderData { + D_Ptr(MutiSelectRect) + + bool mActive = false; + protected: + + bool selectTest(const QPointF& pos) override { + return true; + } + + // event->button():返回引发该事件的单个鼠标按钮。通常在 mousePressEvent 和 mouseReleaseEvent 中使用。 + // event->buttons():返回一个标志,指示当前按下的所有鼠标按钮。可以在任何鼠标事件中使用,特别是 mouseMoveEvent 中。 + void mousePressEvent(QMouseEvent* event) override { + MutiSelectRectRenderState* sc = renderStateCache(); + SpinLockGuard _guard(&mBufferLock); + if (event->button() != Qt::LeftButton) return; + QVector &rectList = sc->mRects; + if (!(QApplication::keyboardModifiers() & Qt::ControlModifier)) { + rectList.clear(); + } else { + int n = rectList.size(); + for (int i = n - 1; i >= 0; i--) { + auto &rect = rectList[i]; + if (qFuzzyCompare(rect.width(), 0.0) || qFuzzyCompare(rect.height(), 0.0)) { + rectList.removeAt(i); + } + } + } + double x = sc->mHorizontalAxis->pixelToCoord(event->pos().x(), SRC::Render); + double y = sc->mVerticalAxis->pixelToCoord(event->pos().y(), SRC::Render); + rectList.append(QRectF(QPointF(x, y), QPointF(x, y))); + mActive = true; + } + + void mouseMoveEvent(QMouseEvent* event) override { + if(!(event->buttons() & Qt::LeftButton)) return; + MutiSelectRectRenderState* sc = renderStateCache(); + SpinLockGuard _guard(&mBufferLock); + QVector &rectList = sc->mRects; + if (rectList.empty()) return; + rectList.last().setBottomRight(event->pos()); + QRectF &r = rectList.last(); + r.setRight(sc->mHorizontalAxis->pixelToCoord(r.right(), SRC::Render)); + r.setBottom(sc->mVerticalAxis->pixelToCoord(r.bottom(), SRC::Render)); + } + + void mouseReleaseEvent(QMouseEvent* event) override {} + + void keyPressEvent(QKeyEvent* event) override { + MutiSelectRectRenderState* sc = renderStateCache(); + SpinLockGuard _guard(&mBufferLock); + if (event->key() == Qt::Key_Escape && mActive) { + mActive = false; + QVector &rectList = sc->mRects; + if (rectList.empty()) return; + rectList.removeLast(); + } + } + + void keyReleaseEvent(QKeyEvent* event) override { + MutiSelectRectRenderState* sc = renderStateCache(); + MutiSelectRectTempData* td = tempDataCache(); + SpinLockGuard _guard(&mBufferLock); + } + + + void draw(QPainter* painter) override { + if(!mActive) return; + MutiSelectRectRenderState* s = renderState(); + QList drawRectList; + for (QRectF r: s->mRects) { + if (qFuzzyCompare(r.width(), 0.0) || qFuzzyCompare(r.height(), 0.0)) continue; + r.setX(s->mHorizontalAxis->coordToPixel(r.x(), SRC::Render)); + r.setY(s->mVerticalAxis->coordToPixel(r.y(), SRC::Render)); + r.setRight(s->mHorizontalAxis->coordToPixel(r.right(), SRC::Render)); + r.setBottom(s->mVerticalAxis->coordToPixel(r.bottom(), SRC::Render)); + drawRectList.append(r); + } + painter->save(); + QFontMetrics xfm(s->mHorizontalAxis->unitTextFont()); + QFontMetrics yfm(s->mVerticalAxis->unitTextFont()); + painter->setPen(s->mBorderPen); + painter->setBrush(s->mRectBrush); + for (auto &rect: drawRectList) { + painter->drawRect(rect); + } + painter->setPen(s->mFontPen); + painter->setFont(s->mFont); + int n = drawRectList.size(); + for (int i = 0; i < n; ++i) { + QRectF &rect = drawRectList[i]; + QRectF &coordRect = s->mRects[i]; + double OffsetX = coordRect.right() - coordRect.left(); + double OffsetY = coordRect.bottom() - coordRect.top(); + QString text1 = "OffsetX == " + QString::number(OffsetX) + s->mHorizontalAxis->unitText(); + QString text2 = "OffsetY == " + QString::number(OffsetY) + s->mVerticalAxis->unitText(); + double width1 = xfm.horizontalAdvance(text1) + 32; + double width2 = yfm.horizontalAdvance(text2) + 32; + double rect1X = rect.x() + rect.width() / 2 - width1 / 2; + double rect2X = rect.x() + rect.width() / 2 - width2 / 2; + QRectF rect1 = QRectF(rect1X, rect.y(), width1, xfm.lineSpacing()); + QRectF rect2 = QRectF(rect2X, rect1.bottom(), width2, yfm.lineSpacing()); + painter->drawText(rect1, Qt::AlignCenter, text1); + painter->drawText(rect2, Qt::AlignCenter, text2); + } + painter->restore(); + } + }; + +} + diff --git a/YSGraphic_Core/base/Node.hpp b/YSGraphic_Core/base/Node.hpp new file mode 100644 index 0000000..aaae361 --- /dev/null +++ b/YSGraphic_Core/base/Node.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +namespace YSG { + template + class Node { + public: + explicit Node() = default; + int index() { + if (!father) return -1; + return father->sons.indexOf(static_cast(this)); + } + bool root() { return !father; } + bool leaf() { return sons.empty(); } + bool first() { + if (!father) return false; + return this == father->sons.first(); + } + bool last() { + if (!father) return false; + return this == father->sons.last(); + } + That *previous() { + if (!father) return nullptr; + int i = father->sons.indexOf(static_cast(this)) - 1; + if (i < 0) return nullptr; + return father->sons[i]; + } + That *next() { + if (!father) return nullptr; + int i = father->sons.indexOf(static_cast(this)) + 1; + if (i > father->sons.mBufferSize()) return nullptr; + return father->sons[i]; + } + That *lastChild() { + if (sons.empty()) return nullptr; + return sons.last(); + } + That *firstChild() { + if (sons.empty()) return nullptr; + return sons.first(); + } + That *rootNode() { + That *ret = static_cast(this); + while (ret->father != nullptr) { + ret = ret->father; + } + return ret; + } + int deep() { + That *ret = static_cast(this); + while (ret->father != nullptr) { + ret = ret->father; + ret++; + } + return ret; + } + //遍历值 + QVector deepTraversed() { + std::vector ret; + ret.append(static_cast(this)); + for (auto &child: sons) { + ret.append(child->deepTraversed()); + } + return ret; + } + QVector sequenceTraversed() { + QVector ret; + QQueue que; + That *that = static_cast(this); + que.enqueue(that); + while (!que.empty()) { + That *cur = que.dequeue(); + int n = cur->sons.mBufferSize(); + ret.append(cur); + for (int i = 0; i < n; ++i) { + que.enqueue(cur->sons[i]); + } + } + return ret; + } + QVector descendants() { + QVector &&ret = sequenceTraversed(); + ret.pop_front(); + return ret; + } + That *father = nullptr; + QVector sons; + }; + template + class NodeManager { + public: + QVector roots; + }; +} diff --git a/YSGraphic_Core/base/PerformanceShower.cpp b/YSGraphic_Core/base/PerformanceShower.cpp new file mode 100644 index 0000000..ce08b72 --- /dev/null +++ b/YSGraphic_Core/base/PerformanceShower.cpp @@ -0,0 +1,105 @@ +#include "PerformanceShower_p.h" +#include +#include + +namespace YSG { + PerformanceShower::PerformanceShower(QWidget *parent) : QWidget(parent) { + setAttribute(Qt::WA_OpaquePaintEvent, true); + auto layout = new QVBoxLayout(this); + layout->setSpacing(0); + + } + + + void PerformanceShower::refreshCounter() { + for(CounterLine& value : mCounter) { + value.mCountTimes = 0; + value.mTotalStart = std::chrono::system_clock::now(); + } + } + + void PerformanceShower::counterIncrease(const QString& counterName) { + if(!mCounter.contains(counterName)) { + mCounter[counterName].mName = counterName; + mCounter[counterName].mTotalStart = std::chrono::system_clock::now(); + } + mCounter[counterName].mCountTimes++; + } + + void PerformanceShower::paintEvent(QPaintEvent *event) { + QPainter painter(this); + QPoint topLeft = rect().topLeft(); + int x = topLeft.x() + mLeftMargin; + int y = topLeft.y() + mTopMargin; + painter.fillRect(rect(), Qt::white); + painter.setFont(mFont); + { + SpinLockGuard lock(&mMutex); + QFontMetrics fm(mFont); + int n = mAllInfo.size(); + for (int i = 0; i < n; i++) { + painter.drawText(topLeft + QPointF(x, y + i * fm.lineSpacing() + fm.lineSpacing()), mAllInfo[i]); + } + } + painter.end(); + } + + void PerformanceShower::mousePressEvent(QMouseEvent *event) { + if (mMoving) return; + if (event->button() == Qt::LeftButton) { + mMoving = true; + mStartPos = pos(); + mStartGlobalPos = event->globalPos(); + } + event->accept(); + } + + void PerformanceShower::mouseMoveEvent(QMouseEvent *event) { + if (!mMoving) return; + move(mStartPos + event->globalPos() - mStartGlobalPos); + event->accept(); + } + void PerformanceShower::mouseReleaseEvent(QMouseEvent *event) { + if (!mMoving) return; + mMoving = false; + event->accept(); + } + + QVector PerformanceShower::all() { + QVector infoList = mFixedInfo; + for(PerformanceLine& l: mInfoMap) infoList.append(l.toString()); + for(CounterLine& c: mCounter) infoList.append(c.toString()); + for (auto it = mInfos.cbegin(); it != mInfos.cend(); ++it) { + infoList.append(QString("%1: %2").arg(it.key(), it.value())); + } + return infoList; + } + + void PerformanceShower::refresh() { + { + SpinLockGuard lock(&mMutex); + mAllInfo = all(); + int n = mAllInfo.size(); + if (mOldSize != n) { + int maxWidth = 0; + QFontMetrics fm(mFont); + for (int i = 0; i < n; i++) maxWidth = qMax(maxWidth, fm.horizontalAdvance(mAllInfo[i])); + int w = maxWidth + mLeftMargin + mRightMargin; + int h = fm.lineSpacing() * n + mTopMargin + mBottomMargin; + QMetaObject::invokeMethod(this, "firstResize", Q_ARG(int, w), Q_ARG(int, h)); + } + mOldSize = n; + } + QMetaObject::invokeMethod(this, "update"); + } + + + + void PerformanceShower::firstResize(int w, int h) { + //qDebug() << "PerformanceShower::firstResize(int w, int h) " << w << " " << h; + resize(w, h); + } + void PerformanceShower::resizeEvent(QResizeEvent *event) { + event->accept(); + } +} diff --git a/YSGraphic_Core/base/PerformanceShower_p.h b/YSGraphic_Core/base/PerformanceShower_p.h new file mode 100644 index 0000000..fc032c6 --- /dev/null +++ b/YSGraphic_Core/base/PerformanceShower_p.h @@ -0,0 +1,104 @@ +#pragma once + +#include "../RenderAble.h" +namespace YSG { + struct PerformanceLine { + PerformanceLine() = default; + explicit PerformanceLine(const QString& data) { + mData = data; + int argIndex = 1; + while(data.contains("%" + QString::number(argIndex))) { + argIndex++; + } + mPlaceHolderNum = argIndex - 1; + mValues.resize(mPlaceHolderNum); + } + QString mData; + int mPlaceHolderNum{}; + QVector mValues; + QString toString() { + QString ret = mData; + int argIndex = 1; + for (const double& value : mValues) { + ret =ret.replace("%"+QString::number(argIndex), QString::number(value, 'f', 2)); + argIndex++; + } + return ret; + } + }; + struct CounterLine { + QString mName; + std::chrono::system_clock::time_point mTotalStart; + int mCountTimes = 0; + [[nodiscard]] double getRate() const { + double useTime = std::chrono::duration(std::chrono::system_clock::now() - mTotalStart).count(); + return mCountTimes / useTime; + } + + [[nodiscard]] QString toString() const { + double useTime = std::chrono::duration(std::chrono::system_clock::now() - mTotalStart).count(); + // return QString("%1 :%2 (次/s) 当前次数%3,当前时间 %4") + // .arg(mName, QString::number(mCountTimes / useTime, 'f', 2)) + // .arg(mCountTimes) + // .arg(QString::number(useTime, 'f', 2)); + return QString("%1 :%2 (次/s)") + .arg(mName, QString::number(mCountTimes / useTime, 'f', 2)); + } + }; + class PerformanceShower : public QWidget { + Q_OBJECT + public: + explicit PerformanceShower(QWidget *parent = nullptr); + QFont mFont; + int mTopMargin = 4, mLeftMargin = 4, mRightMargin = 28, mBottomMargin = 8; + int mOldSize = -1; + QVector mAllInfo; + Q_INVOKABLE void firstResize(int w, int h); + void refresh(); + QVector all(); + SpinLock mMutex; + QMap mInfoMap; + QMap mCounter; + QVector mFixedInfo; + QMap mInfos; + + void refreshCounter(); + void counterIncrease(const QString& counterName); + protected: + void paintEvent(QPaintEvent *event) override; + bool mMoving = false; + QPoint mStartPos, mStartGlobalPos; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + }; + + class Cacl { + public: + std::chrono::system_clock::time_point mStart; + QString mName; + PerformanceShower *mPerformanceShower{}; + Cacl(const QString& name, PerformanceShower* performanceShower) { + if(!performanceShower) return; + mStart = std::chrono::system_clock::now(); + mName = name; + mPerformanceShower = performanceShower; + } + ~Cacl() { + if(!mPerformanceShower) return; + double useTime = std::chrono::duration(std::chrono::system_clock::now() - mStart).count() * 1000; + if(!mPerformanceShower->mInfoMap.contains(mName)) { + mPerformanceShower->mInfoMap[mName] = PerformanceLine(mName); + } + PerformanceLine& data = mPerformanceShower->mInfoMap[mName]; + data.mValues[0] = useTime; + if(data.mValues.size() > 1) { + data.mValues[1] = data.mValues[1] * 0.9 + useTime * 0.1; //约等于近10次平均 + } + } + }; +} + + + diff --git a/YSGraphic_Core/base/Plot.cpp b/YSGraphic_Core/base/Plot.cpp new file mode 100644 index 0000000..ffbd40d --- /dev/null +++ b/YSGraphic_Core/base/Plot.cpp @@ -0,0 +1,279 @@ +#include "Plot_p.h" +#include "PerformanceShower_p.h" +#include "Global.h" + +namespace YSG { + class PrePareMutiDataMutexGuard { + public: + QVector mLockList; + PrePareMutiDataMutexGuard(RenderAble* renderAble, Plot* plot) { + for(PrePareMutiDataMutex* curLock : plot->mMutiSpinLock) { + if(curLock->mSet.contains(renderAble)) { + mLockList.append(curLock); + } + } + for(PrePareMutiDataMutex* curLock : mLockList) curLock->lock(renderAble); + } + ~PrePareMutiDataMutexGuard() { + for(PrePareMutiDataMutex* curLock : mLockList) curLock->unlock(); + } + }; + + + void startAllRenderThread() { + Global::instance()->startAllTimeThread(); + } + + void Plot::bindRenderThread(const QString& threadName) { + auto g = Global::instance(); + if (!g->mTimerThreadMap.contains(threadName)) { + auto newThread = new TimerThread; + g->mTimerThreadMap[threadName] = newThread; + newThread->setObjectName(threadName); + } + g->mTimerThreadMap[threadName]->mPlots.append(this); + } + + Plot::Plot(){ + d = new PlotPrivate(this); + setAttribute(Qt::WA_OpaquePaintEvent, true); + setFocusPolicy(Qt::NoFocus); + } + bool Plot::usePerformanceShower() { + return d->mShower; + } + void Plot::set_usePerformanceShower(bool use) { + SpinLockGuard g(&d->mRenderLock); + + if(!use) { + PerformanceShower* t = d->mShower; + d->mShower = nullptr; + delete t; + } else { + if(d->mShower) return; + d->mShower = new PerformanceShower(this); + d->mShower->show(); + } + } + + Plot::~Plot() { + delete d->mRenderBuffer; + } + + bool Plot::isRendering() { + if(!d->mTimer) return false; + return d->mTimer->isActive(); + } + + void Plot::startRender(int refreshTimesPreSecond) { + d->mRefreshTimesPreSecond = refreshTimesPreSecond; + startRender(); + } + + void Plot::startRender() const { + if(d->mTimer) QMetaObject::invokeMethod(d->mTimer, "start", Qt::AutoConnection, Q_ARG(int, qRound(1000.0 / (double) d->mRefreshTimesPreSecond))); + } + + void Plot::pauseRender() const { + if(d->mTimer) QMetaObject::invokeMethod(d->mTimer, "stop", Qt::AutoConnection); + } + + void Plot::paintEvent(QPaintEvent* event) { + SpinLockGuard guard(&d->mBufferLock); + QPainter painter(this); + painter.drawPixmap(QPoint(0, 0), d->mPaintBuffer); + //painter.drawImage(QPoint(0, 0), d->mPaintBuffer); + painter.end(); + if(d->mShower) d->mShower->counterIncrease("plot paint"); + d->mPlotState.storeRelease(PlotPrivate::NeedToRender); + } + void Plot::render() { + if (mFirstResize) return; + if (d->mPlotState.loadAcquire() != PlotPrivate::NeedToRender) return; + if(!d->mRenderBuffer) return; + SpinLockGuard renderGuard(&d->mRenderLock); + if(d->mRenderBuffer->isNull()) return; + + if(d->mShower) { + d->mShower->counterIncrease("渲染帧率"); + d->mShower->mInfos["interval"] = QString("渲染:%1, global:%2").arg(d->mTimer->interval()).arg(Global::instance()->mInterval); + } + Cacl renderData("整体渲染耗时: %1 avg: %2", d->mShower); + QPainter painter(d->mRenderBuffer); + painter.fillRect(rect(), d->mBackground); + { + Cacl prepareData("准备数据耗时: %1 avg: %2", d->mShower); + SpinLockGuard prepareDataLock(&d->mPrepareDataLock); + for(Layer* layer : mLayerList) { + for(RenderAble *cur : layer->mRenderAbles) { + //qDebug() << "info: "<< layer->mLayerName << " " << cur->mObjectName; + for(auto rely : cur->mBeRelyList) { + if(!rely->mPrepared){ + PrePareMutiDataMutexGuard _guard(rely, this); + // qDebug() << cur->mObjectName << "rely: " << mObjectName; + rely->prepareData(); + rely->mPrepared = true; + } + } + if(!cur->mPrepared){ + PrePareMutiDataMutexGuard _guard(cur, this); + cur->prepareData(); + //qDebug() << cur->mObjectName << " prepareData"; + cur->mPrepared = true; + } + } + } + if(mFirstPrepareData) mFirstPrepareData = false; + } + + + { + Cacl prepareData("draw耗时: %1 avg: %2", d->mShower); + for(Layer* layer : mLayerList) { + for(RenderAble* cur : layer->mRenderAbles) { + if(!cur->mVisable) continue; + cur->draw(&painter); + } + } + } + + + { + SpinLockGuard bufferGuard(&d->mBufferLock); + // d->mPaintBuffer = *d->mRenderBuffer; + d->mPaintBuffer = QPixmap::fromImage(*d->mRenderBuffer); + d->mPlotState.storeRelease(PlotPrivate::NeedToPaint); + } + + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + able->mPrepared = false; + } + } + if(d->mShower) { + d->mShower->refresh(); + } + } + + QColor Plot::backgroundColor() { + return d->mBackground; + } + + void Plot::setBackgroundColor(const QColor& color) const { + d->mBackground = color; + } + + bool Plot::event(QEvent* event) { + bool ret = QWidget::event(event); + switch (event->type()){ + case QEvent::Resize: { + auto e = reinterpret_cast(event); + { + //qDebug() << resizeEvent->size(); + SpinLockGuard guard(&d->mRenderLock); + delete d->mRenderBuffer; + //d->mRenderBuffer = new QPixmap(e->size()); + d->mRenderBuffer = new QImage(e->size().width(), e->size().height(), QImage::Format_ARGB32); + + if(mFirstResize) { + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + able->firstResizeEvent(e); + } + } + } + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + able->resizeEvent(e); + } + } + if(mFirstResize) mFirstResize = false; + } + break; + } + case QEvent::Show: { + startRender(); + break; + } + case QEvent::Hide: { + pauseRender(); + break; + } + case QEvent::Enter: { + break; + } + case QEvent::Leave: { + + break; + } + case QEvent::Wheel: { + auto e = reinterpret_cast(event); + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + able->wheelEvent(e); + } + } + } + case QEvent::MouseMove: { + auto e = reinterpret_cast(event); + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + if(able->selectTest(e->pos())) { + able->mouseMoveEvent(e); + } + } + } + break; + } + case QEvent::MouseButtonPress: { + auto e = reinterpret_cast(event); + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + if(able->selectTest(e->pos())) { + able->mousePressEvent(e); + } + } + } + break; + } + case QEvent::MouseButtonRelease: { + auto e = reinterpret_cast(event); + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + if(able->selectTest(e->pos())) { + able->mouseReleaseEvent(e); + } + } + } + break; + } + case QEvent::KeyPress: { + auto e = reinterpret_cast(event); + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + able->keyPressEvent(e); + } + } + break; + } + case QEvent::KeyRelease: { + auto e = reinterpret_cast(event); + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + able->keyReleaseEvent(e); + } + } + break; + } + default: break; + } + for(Layer* layer : mLayerList) { + for(RenderAble* able : layer->mRenderAbles) { + able->plotEvent(event); + } + } + return ret; + } + + +} diff --git a/YSGraphic_Core/base/Plot.h b/YSGraphic_Core/base/Plot.h new file mode 100644 index 0000000..12df4e5 --- /dev/null +++ b/YSGraphic_Core/base/Plot.h @@ -0,0 +1,98 @@ +#pragma once + + +#include +#include "../RenderAble.h" + + + + +namespace YSG { + void LIB_DECL startAllRenderThread(); + class TimerThread; + struct PlotPrivate; + // 此锁用于锁住从mSet里第一个元素,到最后一个元素的渲染过程 用于时间同步 + // mSet 里的元素修改前 加上此锁,可保证 所有的这个元素组数据同步 + class LIB_DECL PrePareMutiDataMutex { + public: + SpinLock mRangeLock; + QSet mSet; + QVector mList; + // lock,unlock都只在Plot渲染函数render里调用 一个线程,所以不用加锁 + std::chrono::system_clock::time_point start; + void lock(RenderAble* a) { + if(mList.empty()) { + mRangeLock.lock(); + start = std::chrono::system_clock::now(); + //qDebug() << "lock " << QTime::currentTime().toString("ss:zzz"); + mList.reserve(mSet.size()); + for(RenderAble* that : mSet) { + mList.append(that); + } + } + mList.removeOne(a); + } + void unlock() { + if(mList.empty()) { + //qDebug() << "unlock " << QTime::currentTime().toString("ss:zzz"); + mRangeLock.unlock(); + double duration = std::chrono::duration(std::chrono::system_clock::now() - start).count() * 1000; + //qDebug() << "duration == " << duration; + } + } + }; + + class PerformanceShower; + class LIB_DECL Plot : public QWidget { + Q_OBJECT + public: + bool usePerformanceShower(); + void set_usePerformanceShower(bool use); + + void bindRenderThread(const QString& threadName); + virtual void init() { + initLayer(); + } + void render(); + QColor backgroundColor(); + bool mFirstResize = true, mFirstPrepareData = true; + void setBackgroundColor(const QColor& color) const; + struct Layer { + QString mLayerName; + QVector mRenderAbles; + }; + QString mObjectName; + QList mLayerList; + QString mDefaultLayerName; + virtual void initLayer() { + auto addLayer = [this](QString&& name) { + auto layer = new Layer; + layer->mLayerName = name; + mLayerList.append(layer); + mLayerMap[name] = layer; + }; + addLayer("background"); + addLayer("plottable"); + addLayer("axis"); + addLayer("legend"); + mDefaultLayerName = "plottable"; + } + bool isRendering(); + void startRender(int refreshTimesPreSecond); + void startRender() const; + void pauseRender() const; + QVector mMutiSpinLock; + Plot(); + ~Plot() override; + PlotPrivate *d{}; + private: + QHash mLayerMap; + bool event(QEvent* event) final; + void paintEvent(QPaintEvent* event) final; + friend class TimerThread; + friend struct RenderAble; + }; + + + +} diff --git a/YSGraphic_Core/base/Plot_p.h b/YSGraphic_Core/base/Plot_p.h new file mode 100644 index 0000000..ea1f4c2 --- /dev/null +++ b/YSGraphic_Core/base/Plot_p.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Plot.h" +#include +#include + + +namespace YSG { + struct PlotPrivate { + explicit PlotPrivate(Plot* plot) : q(plot) {}; + Plot *q{}; + int mRefreshTimesPreSecond = 30; + TimerThread* mTimerThread{}; + QTimer *mTimer{}; + enum PlotState { NeedToRender, NeedToPaint}; + QAtomicInteger mPlotState = NeedToRender; + SpinLock mBufferLock, mRenderLock, mPrepareDataLock, mMustPrepareDataLock; + //QPixmap *mRenderBuffer{}, mPaintBuffer; + //QImage *mRenderBuffer{}, mPaintBuffer; + QImage *mRenderBuffer{}; + QPixmap mPaintBuffer{}; + QColor mBackground = Qt::black; + PerformanceShower *mShower{}; + }; + + +} + + diff --git a/YSGraphic_Core/base/RingBuffer.hpp b/YSGraphic_Core/base/RingBuffer.hpp new file mode 100644 index 0000000..72bff36 --- /dev/null +++ b/YSGraphic_Core/base/RingBuffer.hpp @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include + +namespace YSG { + class RingBuffer { + public: + // 用char*是因为我的内存 是紧密连接的 + // struct CustomType; CustomType* it; it[i] 这种形式是不行的 + template + [[nodiscard]] T* list() const { + return (T*)(mData+s*l); + } + int n{}, l{}, m{}; + // s,e 差值始终为 n-1 + int e{}, eMin{}, eMax{}; // [n-1, m-1] + int s{}, sMin{}, sMax{}; // [0, m-n] + int f = 0; + char* mData{}; + bool isLeft = true; + template + T* operator[](int offset) { + return reinterpret_cast(mData + offset * l); + } + void resize(int _l, int _n, int _m) { + n = _n; + l = _l; + m = _m; + eMin = n - 1; + eMax = m - 1; + sMin = 0; + sMax = m - n; + if(isLeft) { //向左移 初始在最右端 + s = sMax; e = eMax; + } else { + s = sMin; e = eMin; + } + f = 0; + delete []mData; + int size = m * l; + mData = new char[size]; + for(int i = 0; i < size; ++i) { + mData[i] = 0; + } + } + void pushData(void* data){ + if(f < n) { //共l次,前几次赋值逻辑和后面不一样 + + if(isLeft) { + // std::cout << "s == " << s << " sMin-1 " << (sMin-1) << std::endl; + //std::cout << "f == " << f << " dst " << l*(sMax+1) << " src " << l*sMax << " size=" << l*f << std::endl; + + + //std::cout << "f == " << f << " dst " << l* << " src " << l*sMax << " size=" << l*f << std::endl; + + //std::cout << "dst:" << (sMax+1) << " src" << sMax; + std::memmove(mData+l*(sMax+1), mData+l*sMax, l*f); + std::memmove(mData+l*sMax, data, l); + } else { + std::memmove(mData+l*(eMin-f), mData+l*(eMin-f+1), l*f); + std::memmove(mData+l*eMin, data, l); + } + f++; + return; + } + if(isLeft) { + s--; e--; + if(s == sMin-1) { + s = sMax; e = eMax; + std::memmove(mData+l*(sMax+1), mData, l*(n - 1)); + std::memmove(mData+l*sMax, data, l); + } else { + std::memmove(mData+l*s, data, l); + } + } else { + s++; e++; + if(e == eMax+1) { + s = sMin; e = eMin; + std::memmove(mData, mData+l*(eMin+1), l*(n - 1)); + std::memmove(mData+l*eMin, data, l); + } else { + std::memmove(mData+l*e, data, l); + } + } + } + }; + + class MutiRingBuffer { + public: + int bufferNum{}, n{}, m{}; + std::vector buffers; + void resize(std::vector _ls, int _n, int _m) { + n = _n; + m = _m; + bufferNum = (int)_ls.size(); + buffers.resize(bufferNum); + for(int i = 0; i < bufferNum; ++i) { + buffers[i].resize(_ls[i], _n, _m); + } + } + void pushData(const std::vector& datas) { + for(int i = 0; i < bufferNum; ++i) { + buffers[i].pushData(datas[i]); + } + } + }; +} + + + diff --git a/YSGraphic_Core/base/RollObject.h b/YSGraphic_Core/base/RollObject.h new file mode 100644 index 0000000..76ef8b3 --- /dev/null +++ b/YSGraphic_Core/base/RollObject.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +namespace YSG { + template + class RollObject : QObject { + public: + explicit RollObject(QObject *parent = nullptr) : QObject(parent) {} + explicit RollObject(QVector widgets, std::function enterFunc, + std::function leaveFunc, QObject *parent) : + mWidgets(std::move(widgets)), mEnterFunc(enterFunc), mLeaveFunc(leaveFunc), QObject(parent) { + if (mEnterFunc) mEnterFunc(mWidgets[mIndex]); + } + int mIndex = 0; + QVector mWidgets; + std::function mEnterFunc; + std::function mLeaveFunc; + T *cur() { + if (mWidgets.empty()) return nullptr; + return mWidgets[mIndex]; + } + void setCur(T *cur) { + if (mWidgets.empty()) return; + int index = mWidgets.indexOf(cur); + if (mLeaveFunc) mLeaveFunc(mWidgets[mIndex]); + mIndex = index; + if (mEnterFunc) mEnterFunc(mWidgets[mIndex]); + mWidgets[mIndex]->update(); + } + void rollRight() { + if (mWidgets.empty()) return; + if (mLeaveFunc) mLeaveFunc(mWidgets[mIndex]); + mWidgets[mIndex]->update(); + if (mWidgets.last() != mWidgets[mIndex]) { + mIndex++; + } else { + mIndex = 0; + } + if (mEnterFunc) mEnterFunc(mWidgets[mIndex]); + mWidgets[mIndex]->update(); + } + void rollLeft() { + if (mWidgets.empty()) return; + if (mLeaveFunc) mLeaveFunc(mWidgets[mIndex]); + mWidgets[mIndex]->update(); + if (mWidgets.first() != mWidgets[mIndex]) { + mIndex--; + } else { + mIndex = mWidgets.size() - 1; + } + if (mEnterFunc) mEnterFunc(mWidgets[mIndex]); + mWidgets[mIndex]->update(); + } + QHBoxLayout *hBoxLayout(int space) { + auto ret = new QHBoxLayout; + ret->setMargin(0); + ret->setSpacing(space); + for (auto widget: mWidgets) { + ret->addWidget(widget, 1); + } + return ret; + } + }; +} + + + + diff --git a/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog.cpp b/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog.cpp new file mode 100644 index 0000000..2f916d6 --- /dev/null +++ b/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog.cpp @@ -0,0 +1,870 @@ +#include + +#include "SelectColorDialog_p.h" + + +namespace YSG { + void rgb2hsv(QRgb rgb, int& h, int& s, int& v) { + QColor c; + c.setRgb(rgb); + c.getHsv(&h, &s, &v); + } + void QColorWell::paintEvent(QPaintEvent* e) { + QRect r = e->rect(); + int cx = r.x(); + int cy = r.y(); + int ch = r.height(); + int cw = r.width(); + int colfirst = columnAt(cx); + int collast = columnAt(cx + cw); + int rowfirst = rowAt(cy); + int rowlast = rowAt(cy + ch); + if (isRightToLeft()) + { + int t = colfirst; + colfirst = collast; + collast = t; + } + QPainter painter(this); + QPainter* p = &painter; + QRect rect(0, 0, cellw, cellh); + if (collast < 0 || collast >= ncols) collast = ncols - 1; + if (rowlast < 0 || rowlast >= nrows) rowlast = nrows - 1; + // Go through the rows + for (int r = rowfirst; r <= rowlast; ++r) + { + // get row position and height + int rowp = rowY(r); + // Go through the columns in the row r + // if we know from where to where, go through [colfirst, collast], + // else go through all of them + for (int c = colfirst; c <= collast; ++c) + { + // get position and width of column c + int colp = columnX(c); + // Translate painter and draw the cell + rect.translate(colp, rowp); + paintCell(p, r, c, rect); + rect.translate(-colp, -rowp); + } + } + } + + + void QColorWell::resizeEvent(QResizeEvent* event) { + // qDebug() << "sizeHint == " << size(); + cellw = width()/ncols; + cellh = height()/nrows; + if(cellw > cellh) cellw = cellh; + if(cellh > cellw) cellh = cellw; + + QWidget::resizeEvent(event); + } + + void QColorWell::paintCell(QPainter* p, int row, int col, const QRect& rect) { + const QPalette& g = palette(); + QStyleOptionFrame opt; + opt.initFrom(this); + int dfw = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt); + opt.lineWidth = dfw; + opt.midLineWidth = 1; + opt.rect = rect.adjusted(cellMargin, cellMargin, -cellMargin, -cellMargin); + opt.palette = g; + opt.state = QStyle::State_Enabled | QStyle::State_Sunken; + style()->drawPrimitive(QStyle::PE_Frame, &opt, p, this); + //cellMargin += dfw; + if ((row == curRow) && (col == curCol)) + { + // if (hasFocus()) + // { + QStyleOptionFocusRect opt; + opt.palette = g; + opt.rect = rect; + opt.state = QStyle::State_None | QStyle::State_KeyboardFocusChange; + style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt, p, this); + // } + } + paintCellContents(p, row, col, opt.rect.adjusted(dfw, dfw, -dfw, -dfw)); + } + + + /* + Sets the cell currently having the focus. This is not necessarily + the same as the currently selected cell. + */ + void QColorWell::setCurrent(int row, int col) { + if ((curRow == row) && (curCol == col)) return; + if (row < 0 || col < 0) row = col = -1; + int oldRow = curRow; + int oldCol = curCol; + curRow = row; + curCol = col; + updateCell(oldRow, oldCol); + updateCell(curRow, curCol); + emit currentChanged(curRow, curCol); + } + /* + Sets the currently selected cell to \a row, \a column. If \a row or + \a column are less than zero, the current cell is unselected. + + Does not set the position of the focus indicator. + */ + void QColorWell::setSelected(int row, int col) { + int oldRow = selRow; + int oldCol = selCol; + if (row < 0 || col < 0) row = col = -1; + selCol = col; + selRow = row; + updateCell(oldRow, oldCol); + updateCell(selRow, selCol); + if (row >= 0) { + emit selected(row, col); + } +#if QT_CONFIG(menu) + if (isVisible() && qobject_cast(parentWidget())) parentWidget()->close(); +#endif + } + void QColorWell::focusInEvent(QFocusEvent*) { + updateCell(curRow, curCol); + emit currentChanged(curRow, curCol); + } + void QColorWell::focusOutEvent(QFocusEvent*) { + updateCell(curRow, curCol); + } + void QColorWell::keyPressEvent(QKeyEvent* e) { + switch (e->key()) + { + // Look at the key code + case Qt::Key_Left: // If 'left arrow'-key, + if (curCol > 0) // and cr't not in leftmost col + setCurrent(curRow, curCol - 1); // set cr't to next left column + break; + case Qt::Key_Right: // Correspondingly... + if (curCol < ncols - 1) setCurrent(curRow, curCol + 1); + break; + case Qt::Key_Up: if (curRow > 0) setCurrent(curRow - 1, curCol); + break; + case Qt::Key_Down: if (curRow < nrows - 1) setCurrent(curRow + 1, curCol); + break; +#if 0 + // bad idea that shouldn't have been implemented; very counterintuitive + case Qt::Key_Return: + case Qt::Key_Enter: + /* + ignore the key, so that the dialog get it, but still select + the current row/col + */ + e->ignore(); + // fallthrough intended +#endif + case Qt::Key_Space: setSelected(curRow, curCol); + break; + default: // If not an interesting key, + e->ignore(); // we don't accept the event + return; + } + } + int QColorLuminancePicker::y2val(int y) { + int d = height() - 2 * coff - 1; + return 255 - (y - coff) * 255 / d; + } + int QColorLuminancePicker::val2y(int v) { + int d = height() - 2 * coff - 1; + return coff + (255 - v) * d / 255; + } + QColorLuminancePicker::QColorLuminancePicker(QWidget* parent) : QWidget(parent) { + hue = 100; + val = 100; + sat = 100; + pix = nullptr; + // setAttribute(WA_NoErase, true); + } + QColorLuminancePicker::~QColorLuminancePicker() { + delete pix; + } + void QColorLuminancePicker::mouseMoveEvent(QMouseEvent* m) { + setVal(y2val(m->y())); + } + void QColorLuminancePicker::mousePressEvent(QMouseEvent* m) { + setVal(y2val(m->y())); + } + void QColorLuminancePicker::setVal(int v) { + if (val == v) return; + val = qMax(0, qMin(v, 255)); + delete pix; + pix = nullptr; + repaint(); + emit newHsv(hue, sat, val); + } + //receives from a hue,sat chooser and relays. + void QColorLuminancePicker::setCol(int h, int s) { + setCol(h, s, val); + emit newHsv(h, s, val); + } + void QColorLuminancePicker::paintEvent(QPaintEvent*) { + int w = width() - 5; + QRect r(0, foff, w, height() - 2 * foff); + int wi = r.width() - 2; + int hi = r.height() - 2; + if (!pix || pix->height() != hi || pix->width() != wi) + { + delete pix; + QImage img(wi, hi, QImage::Format_RGB32); + int y; + uint* pixel = (uint*)img.scanLine(0); + for (y = 0; y < hi; y++) + { + uint* end = pixel + wi; + std::fill(pixel, end, QColor::fromHsv(hue, sat, y2val(y + coff)).rgb()); + pixel = end; + } + pix = new QPixmap(QPixmap::fromImage(img)); + } + QPainter p(this); + p.drawPixmap(1, coff, *pix); + const QPalette& g = palette(); + qDrawShadePanel(&p, r, g, true); + p.setPen(g.windowText().color()); + p.setBrush(g.windowText()); + QPolygon a; + int y = val2y(val); + a.setPoints(3, w, y, w + 5, y + 5, w + 5, y - 5); + p.eraseRect(w, 0, 5, height()); + p.drawPolygon(a); + } + void QColorLuminancePicker::setCol(int h, int s, int v) { + val = v; + hue = h; + sat = s; + delete pix; + pix = nullptr; + repaint(); + } + QPoint QColorPicker::colPt() { + QRect r = contentsRect(); + return QPoint((360 - hue) * (r.width() - 1) / 360, (255 - sat) * (r.height() - 1) / 255); + } + int QColorPicker::huePt(const QPoint& pt) { + QRect r = contentsRect(); + return 360 - pt.x() * 360 / (r.width() - 1); + } + int QColorPicker::satPt(const QPoint& pt) { + QRect r = contentsRect(); + return 255 - pt.y() * 255 / (r.height() - 1); + } + void QColorPicker::setCol(const QPoint& pt) { + setCol(huePt(pt), satPt(pt)); + } + QColorPicker::QColorPicker(QWidget* parent) : QFrame(parent), crossVisible(true) { + hue = 0; + sat = 0; + setCol(150, 255); + setAttribute(Qt::WA_NoSystemBackground); + //setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + } + QColorPicker::~QColorPicker() = default; + void QColorPicker::setCrossVisible(bool visible) { + if (crossVisible != visible) + { + crossVisible = visible; + update(); + } + } + + void QColorPicker::setCol(int h, int s) { + int nhue = qMin(qMax(0, h), 359); + int nsat = qMin(qMax(0, s), 255); + if (nhue == hue && nsat == sat) return; + QRect r(colPt(), QSize(20, 20)); + hue = nhue; + sat = nsat; + r = r.united(QRect(colPt(), QSize(20, 20))); + r.translate(contentsRect().x() - 9, contentsRect().y() - 9); + // update(r); + repaint(r); + } + void QColorPicker::mouseMoveEvent(QMouseEvent* m) { + QPoint p = m->pos() - contentsRect().topLeft(); + setCol(p); + emit newCol(hue, sat); + } + void QColorPicker::mousePressEvent(QMouseEvent* m) { + QPoint p = m->pos() - contentsRect().topLeft(); + setCol(p); + emit newCol(hue, sat); + } + void QColorPicker::paintEvent(QPaintEvent*) { + QPainter p(this); + drawFrame(&p); + QRect r = contentsRect(); + p.drawPixmap(r.topLeft(), pix); + if (crossVisible) + { + QPoint pt = colPt() + r.topLeft(); + p.setPen(Qt::black); + p.fillRect(pt.x() - 9, pt.y(), 20, 2, Qt::black); + p.fillRect(pt.x(), pt.y() - 9, 2, 20, Qt::black); + } + } + void QColorPicker::resizeEvent(QResizeEvent* ev) { + QFrame::resizeEvent(ev); + int w = width() - frameWidth() * 2; + int h = height() - frameWidth() * 2; + QImage img(w, h, QImage::Format_RGB32); + int x, y; + uint* pixel = (uint*)img.scanLine(0); + for (y = 0; y < h; y++) + { + const uint* end = pixel + w; + x = 0; + while (pixel < end) + { + QPoint p(x, y); + QColor c; + c.setHsv(huePt(p), satPt(p), 200); + *pixel = c.rgb(); + ++pixel; + ++x; + } + } + pix = QPixmap::fromImage(img); + } + void QColorShowLabel::paintEvent(QPaintEvent* e) { + QPainter p(this); + drawFrame(&p); + p.fillRect(contentsRect() & e->rect(), col); + } + void QColorShower::showAlpha(bool b) { + alphaLab->setVisible(b); + alphaEd->setVisible(b); + } + inline bool QColorShower::isAlphaVisible() const { + return alphaLab->isVisible(); + } + void QColorShowLabel::mousePressEvent(QMouseEvent* e) { + mousePressed = true; + pressPos = e->pos(); + } + void QColorShowLabel::mouseMoveEvent(QMouseEvent* e) { +#if !QT_CONFIG(draganddrop) + Q_UNUSED(e); +#else + if (!mousePressed) return; + if ((pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) + { + QMimeData* mime = new QMimeData; + mime->setColorData(col); + QPixmap pix(30, 20); + pix.fill(col); + QPainter p(&pix); + p.drawRect(0, 0, pix.width() - 1, pix.height() - 1); + p.end(); + QDrag* drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(pix); + mousePressed = false; + drg->exec(Qt::CopyAction); + } +#endif + } + + void QColorShowLabel::dragEnterEvent(QDragEnterEvent* e) { + if (qvariant_cast(e->mimeData()->colorData()).isValid()) e->accept(); + else e->ignore(); + } + void QColorShowLabel::dragLeaveEvent(QDragLeaveEvent*) { + } + void QColorShowLabel::dropEvent(QDropEvent* e) { + QColor color = qvariant_cast(e->mimeData()->colorData()); + if (color.isValid()) + { + col = color; + repaint(); + emit colorDropped(col.rgb()); + e->accept(); + } else + { + e->ignore(); + } + } + void QColorPickingEventFilter::updatePos() { + QPoint mousePos = QCursor::pos(); + setPosition(mousePos + QPoint{-100, -100}); + //move(mousePos + QPoint{-100, -100}); + QColor color = grabScreenColor(mousePos); + //qDebug() << color; + m_dp->cs->setRgb(color.rgb()); + } + bool QColorPickingEventFilter::eventFilter(QObject* object, QEvent* event) { + switch (event->type()) { + case QEvent::MouseMove: { + updatePos(); + return true; + break; + } + case QEvent::MouseButtonPress: { + stopGetScreen(); + return true; + break; + } + case QEvent::KeyPress: { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + //qDebug() << "Escape pressed, stopping color picking"; + stopGetScreen(); + } + break; + } + default: + break; + } + return false; // Don't block the event from reaching the rest of the application + } + + void QColorPickingEventFilter::startGetScreen() { + auto q = m_dp->q; + q->setMouseTracking(true); + q->grabKeyboard(); + q->grabMouse(Qt::CrossCursor); + QApplication::instance()->installEventFilter(this); +#ifdef WIN32 + this->show(); +#endif + updatePos(); + } + void QColorPickingEventFilter::stopGetScreen() { + auto q = m_dp->q; + q->setMouseTracking(false); + q->releaseKeyboard(); + q->releaseMouse(); + QApplication::instance()->removeEventFilter(this); +#ifdef WIN32 + this->hide(); +#endif + } + + SelectColorDialogPrivate::SelectColorDialogPrivate(SelectColorDialog* mSelectColorDialog) : q(mSelectColorDialog) { + mQColorPickingEventFilter = new QColorPickingEventFilter(this); + auto mainLay = new QVBoxLayout(mSelectColorDialog); + auto upLay = new QHBoxLayout; + auto lowLay = new QHBoxLayout; + mainLay->addLayout(upLay); + mainLay->addLayout(lowLay); + auto leftLay = new QVBoxLayout; + auto rightLay = new QVBoxLayout; + upLay->addLayout(leftLay, 3); + upLay->addLayout(rightLay, 4); + auto h = new QHBoxLayout; + rightLay->addLayout(h, 4); + + int i = 0; + for (int g = 0; g < 4; ++g) + for (int r = 0; r < 4; ++r) + for (int b = 0; b < 3; ++b) + standardRgb[i++] = qRgb(r * 255 / 3, g * 255 / 3, b * 255 / 2); + std::fill(customRgb, customRgb + customColorRows * colorColumns, 0xffffffff); + standard = new QColorWell(q ,standardColorRows, colorColumns, standardRgb); + custom = new QColorWell(q ,customColorRows, colorColumns, customRgb); + auto lblBasicColors = new QLabel("基本颜色:"); + lblBasicColors->setBuddy(standard); + leftLay->addWidget(lblBasicColors); + leftLay->addWidget(standard, 6); + auto screenColorPickerButton = new QPushButton("选择屏幕颜色"); + leftLay->addWidget(screenColorPickerButton); + + + auto lblCustomColors = new QLabel("自定义颜色:"); + leftLay->addWidget(lblCustomColors); + leftLay->addWidget(custom, 2); + auto addCusBt = new QPushButton("添加当前颜色为自定义颜色"); + QObject::connect(addCusBt, &QPushButton::clicked, [this]() { + const int i = custom->curRow + customColorRows * custom->curCol; + customRgb[i] = cs->currentColor(); + custom->updateCell(custom->curRow, custom->curCol); + }); + auto addCurrent = new QPushButton("添加自定义为当前颜色"); + QObject::connect(addCurrent, &QPushButton::clicked, [this]() { + const int i = custom->curRow + customColorRows * custom->curCol; + if(i > 0 && i < customColorRows * colorColumns) { + cs->setRgb(customRgb[i]); + } + }); + leftLay->addWidget(addCusBt); + leftLay->addWidget(addCurrent); + + cp = new QColorPicker(q); + cp->setFrameStyle(QFrame::Panel + QFrame::Sunken); + h->addWidget(cp, 11); + lp = new QColorLuminancePicker(q); + h->addWidget(lp, 1); + cs = new QColorShower(q); + rightLay->addWidget(cs, 3); + auto selectNull = new QPushButton("选择空"); + auto ok = new QPushButton("确定"); + auto cancel = new QPushButton("取消"); + lowLay->addWidget(selectNull); + lowLay->addWidget(ok); + lowLay->addWidget(cancel); + + QObject::connect(selectNull, &QPushButton::clicked, [this]() { + void finished(int result); + q->emit finished(-1); + }); + QObject::connect(ok, &QPushButton::clicked, [this]() { + q->emit accept(); + }); + QObject::connect(cancel, &QPushButton::clicked, [this]() { + q->emit reject(); + }); + + QObject::connect(screenColorPickerButton, &QPushButton::clicked, [this]() { + mQColorPickingEventFilter->startGetScreen(); + }); + + QObject::connect(standard, &QColorWell::currentChanged, [this](int r, int c) { + const int i = r + standardColorRows * c; + setCurrentColor(standardRgb[i]); + }); + + QObject::connect(cp, SIGNAL(newCol(int,int)), lp, SLOT(setCol(int,int))); + QObject::connect(lp, &QColorLuminancePicker::newHsv, q, [this](int h, int s, int v) { + cs->setHsv(h, s, v); + cp->setCol(h, s); + lp->setCol(h, s, v); + }); + } + void QColorShowLabel::mouseReleaseEvent(QMouseEvent*) { + if (!mousePressed) return; + mousePressed = false; + } + + QColorShower::QColorShower(SelectColorDialog* parent) : QWidget(parent) { + colorDialog = parent; + curCol = qRgb(255, 255, 255); + curQColor = Qt::white; + gl = new QGridLayout(this); + const int s = gl->spacing(); + gl->setContentsMargins(s, s, s, s); + lab = new QColorShowLabel(this); +#ifdef QT_SMALL_COLORDIALOG + lab->setMinimumHeight(60); +#endif + lab->setMinimumWidth(60); + // For QVGA screens only the comboboxes and color label are visible. + // For nHD screens only color and luminence pickers and color label are visible. +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lab, 0, 0, -1, 1); +#else + gl->addWidget(lab, 0, 0, 1, -1); +#endif + connect(lab, SIGNAL(colorDropped(QRgb)), this, SIGNAL(newCol(QRgb))); + connect(lab, SIGNAL(colorDropped(QRgb)), this, SLOT(setRgb(QRgb))); + hEd = new QColSpinBox(this); + hEd->setRange(0, 359); + lblHue = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblHue->setBuddy(hEd); +#endif + lblHue->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblHue, 0, 1); + gl->addWidget(hEd, 0, 2); +#else + gl->addWidget(lblHue, 1, 0); + gl->addWidget(hEd, 2, 0); +#endif + sEd = new QColSpinBox(this); + lblSat = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblSat->setBuddy(sEd); +#endif + lblSat->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblSat, 1, 1); + gl->addWidget(sEd, 1, 2); +#else + gl->addWidget(lblSat, 1, 1); + gl->addWidget(sEd, 2, 1); +#endif + vEd = new QColSpinBox(this); + lblVal = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblVal->setBuddy(vEd); +#endif + lblVal->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblVal, 2, 1); + gl->addWidget(vEd, 2, 2); +#else + gl->addWidget(lblVal, 1, 2); + gl->addWidget(vEd, 2, 2); +#endif + rEd = new QColSpinBox(this); + lblRed = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblRed->setBuddy(rEd); +#endif + lblRed->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblRed, 0, 3); + gl->addWidget(rEd, 0, 4); +#else + gl->addWidget(lblRed, 3, 0); + gl->addWidget(rEd, 4, 0); +#endif + gEd = new QColSpinBox(this); + lblGreen = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblGreen->setBuddy(gEd); +#endif + lblGreen->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblGreen, 1, 3); + gl->addWidget(gEd, 1, 4); +#else + gl->addWidget(lblGreen, 3, 1); + gl->addWidget(gEd, 4, 1); +#endif + bEd = new QColSpinBox(this); + lblBlue = new QLabel(this); +#ifndef QT_NO_SHORTCUT + lblBlue->setBuddy(bEd); +#endif + lblBlue->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblBlue, 2, 3); + gl->addWidget(bEd, 2, 4); +#else + gl->addWidget(lblBlue, 3, 2); + gl->addWidget(bEd, 4, 2); +#endif + alphaEd = new QColSpinBox(this); + alphaLab = new QLabel(this); +#ifndef QT_NO_SHORTCUT + alphaLab->setBuddy(alphaEd); +#endif + alphaLab->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if !defined(QT_SMALL_COLORDIALOG) + gl->addWidget(alphaLab, 3, 1, 1, 3); + gl->addWidget(alphaEd, 3, 4); +#else + gl->addWidget(alphaLab, 1, 3, 3, 1); + gl->addWidget(alphaEd, 4, 3); +#endif + alphaEd->hide(); + alphaLab->hide(); + lblHtml = new QLabel(this); + htEd = new QLineEdit(this); +#ifndef QT_NO_SHORTCUT + lblHtml->setBuddy(htEd); +#endif +#if QT_CONFIG(regularexpression) + QRegularExpression regExp(QStringLiteral("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})")); + QRegularExpressionValidator* validator = new QRegularExpressionValidator(regExp, this); + htEd->setValidator(validator); +#elif !defined(QT_NO_REGEXP) + QRegExp regExp(QStringLiteral("#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})")); + QRegExpValidator *validator = new QRegExpValidator(regExp, this); + htEd->setValidator(validator); +#else + htEd->setReadOnly(true); +#endif + htEd->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); + lblHtml->setAlignment(Qt::AlignRight | Qt::AlignVCenter); +#if defined(QT_SMALL_COLORDIALOG) + gl->addWidget(lblHtml, 5, 0); + gl->addWidget(htEd, 5, 1, 1, /*colspan=*/ 2); +#else + gl->addWidget(lblHtml, 5, 1); + gl->addWidget(htEd, 5, 2, 1, /*colspan=*/ 3); +#endif + connect(hEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(sEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(vEd, SIGNAL(valueChanged(int)), this, SLOT(hsvEd())); + connect(rEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(gEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(bEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(alphaEd, SIGNAL(valueChanged(int)), this, SLOT(rgbEd())); + connect(htEd, SIGNAL(textEdited(QString)), this, SLOT(htmlEd())); + retranslateStrings(); + } + void QColorWell::paintCellContents(QPainter* p, int row, int col, const QRect& r) { + int i = row + col * nrows; + p->fillRect(r, QColor(values[i])); + } + void QColorWell::mousePressEvent(QMouseEvent* e) { + oldCurrent = QPoint(selectedRow(), selectedColumn()); + QPoint pos = e->pos(); + setCurrent(rowAt(pos.y()), columnAt(pos.x())); + mousePressed = true; + pressPos = e->pos(); + } + void QColorWell::mouseMoveEvent(QMouseEvent* e) { +#if QT_CONFIG(draganddrop) + if (!mousePressed) return; + if ((pressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) + { + setCurrent(oldCurrent.x(), oldCurrent.y()); + int i = rowAt(pressPos.y()) + columnAt(pressPos.x()) * nrows; + QColor col(values[i]); + QMimeData* mime = new QMimeData; + mime->setColorData(col); + QPixmap pix(cellw, cellh); + pix.fill(col); + QPainter p(&pix); + p.drawRect(0, 0, pix.width() - 1, pix.height() - 1); + p.end(); + QDrag* drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(pix); + mousePressed = false; + drg->exec(Qt::CopyAction); + } +#endif + } +#if QT_CONFIG(draganddrop) + void QColorWell::dragEnterEvent(QDragEnterEvent* e) { + if (qvariant_cast(e->mimeData()->colorData()).isValid()) e->accept(); + else e->ignore(); + } + void QColorWell::dragLeaveEvent(QDragLeaveEvent*) { + if (hasFocus()) parentWidget()->setFocus(); + } + void QColorWell::dragMoveEvent(QDragMoveEvent* e) { + if (qvariant_cast(e->mimeData()->colorData()).isValid()) + { + setCurrent(rowAt(e->pos().y()), columnAt(e->pos().x())); + e->accept(); + } else + { + e->ignore(); + } + } + void QColorWell::dropEvent(QDropEvent* e) { + QColor col = qvariant_cast(e->mimeData()->colorData()); + if (col.isValid()) + { + int i = rowAt(e->pos().y()) + columnAt(e->pos().x()) * nrows; + emit colorChanged(i, col.rgb()); + e->accept(); + } else + { + e->ignore(); + } + } +#endif // QT_CONFIG(draganddrop) + void QColorWell::mouseReleaseEvent(QMouseEvent* e) { + if (!mousePressed) return; + setSelected(curRow, curCol); + mousePressed = false; + } + void QColorShower::rgbEd() { + rgbOriginal = true; + curCol = qRgba(rEd->value(), gEd->value(), bEd->value(), currentAlpha()); + rgb2hsv(currentColor(), hue, sat, val); + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + htEd->setText(QColor(curCol).name()); + showCurrentColor(); + emit newCol(currentColor()); + updateQColor(); + } + void QColorShower::hsvEd() { + rgbOriginal = false; + hue = hEd->value(); + sat = sEd->value(); + val = vEd->value(); + QColor c; + c.setHsv(hue, sat, val); + curCol = c.rgb(); + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + htEd->setText(c.name()); + showCurrentColor(); + emit newCol(currentColor()); + updateQColor(); + } + void QColorShower::htmlEd() { + QColor c; + QString t = htEd->text(); + c.setNamedColor(t); + if (!c.isValid()) return; + curCol = qRgba(c.red(), c.green(), c.blue(), currentAlpha()); + rgb2hsv(curCol, hue, sat, val); + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + showCurrentColor(); + emit newCol(currentColor()); + updateQColor(); + } + void QColorShower::setRgb(QRgb rgb) { + rgbOriginal = true; + curCol = rgb; + rgb2hsv(currentColor(), hue, sat, val); + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + htEd->setText(QColor(rgb).name()); + showCurrentColor(); + updateQColor(); + } + void QColorShower::setHsv(int h, int s, int v) { + if (h < -1 || (uint)s > 255 || (uint)v > 255) return; + rgbOriginal = false; + hue = h; + val = v; + sat = s; + QColor c; + c.setHsv(hue, sat, val); + curCol = c.rgb(); + hEd->setValue(hue); + sEd->setValue(sat); + vEd->setValue(val); + rEd->setValue(qRed(currentColor())); + gEd->setValue(qGreen(currentColor())); + bEd->setValue(qBlue(currentColor())); + htEd->setText(c.name()); + showCurrentColor(); + updateQColor(); + } + void QColorShower::retranslateStrings() { + lblHue->setText(SelectColorDialog::tr("Hu&e:")); + lblSat->setText(SelectColorDialog::tr("&Sat:")); + lblVal->setText(SelectColorDialog::tr("&Val:")); + lblRed->setText(SelectColorDialog::tr("&Red:")); + lblGreen->setText(SelectColorDialog::tr("&Green:")); + lblBlue->setText(SelectColorDialog::tr("Bl&ue:")); + alphaLab->setText(SelectColorDialog::tr("A&lpha channel:")); + lblHtml->setText(SelectColorDialog::tr("&HTML:")); + } + void QColorShower::updateQColor() { + QColor oldQColor(curQColor); + curQColor.setRgba(qRgba(qRed(curCol), qGreen(curCol), qBlue(curCol), currentAlpha())); + if (curQColor != oldQColor) emit currentColorChanged(curQColor); + } + void QColorShower::showCurrentColor() { + lab->setColor(currentColor()); + lab->repaint(); + } + SelectColorDialog::SelectColorDialog() { + d_ptr = new SelectColorDialogPrivate(this); + } + SelectColorDialog::~SelectColorDialog() { + delete d_ptr; + } + void SelectColorDialog::setCurrentColor(const QColor& color) const { + d_ptr->setCurrentColor(color); + } + QColor SelectColorDialog::currentColor() const { + return d_ptr->currentColor(); + } +} diff --git a/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog.h b/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog.h new file mode 100644 index 0000000..915165c --- /dev/null +++ b/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog.h @@ -0,0 +1,17 @@ +#pragma once + +#include "../../GlobalTypes.h" +#include +namespace YSG { + class SelectColorDialogPrivate; + class LIB_DECL SelectColorDialog : public QDialog { + QOBJECT_H + public: + SelectColorDialog(); + ~SelectColorDialog() override; + SelectColorDialogPrivate* d_ptr{}; + void setCurrentColor(const QColor &color) const; + [[nodiscard]] QColor currentColor() const; + }; +} + diff --git a/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog_p.h b/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog_p.h new file mode 100644 index 0000000..a3c021b --- /dev/null +++ b/YSGraphic_Core/base/SelectColorDialog/SelectColorDialog_p.h @@ -0,0 +1,302 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SelectColorDialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace YSG { + void rgb2hsv(QRgb rgb, int& h, int& s, int& v); + class QColorWell : public QWidget { + Q_OBJECT public: + QColorWell(QWidget* parent, int r, int c, const QRgb* vals) : values(vals){ + nrows = r; + ncols = c; + setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); + setFocusPolicy(Qt::StrongFocus); + } + [[nodiscard]] int selectedColumn() const { return selCol; } + [[nodiscard]] int selectedRow() const { return selRow; } + virtual void setCurrent(int row, int col); + virtual void setSelected(int row, int col); + [[nodiscard]] inline int rowAt(int y) const { return y / cellh; } + [[nodiscard]] inline int columnAt(int x) const { + if (isRightToLeft()) return ncols - (x / cellw) - 1; + return x / cellw; + } + [[nodiscard]] inline int rowY(int row) const { return cellh * row; } + [[nodiscard]] inline int columnX(int column) const { + if (isRightToLeft()) return cellw * (ncols - column - 1); + return cellw * column; + } + [[nodiscard]] inline QRect cellRect() const { return {0, 0, cellw, cellh}; } + [[nodiscard]] inline QSize gridSize() const { return {ncols * cellw, nrows * cellh}; } + [[nodiscard]] QRect cellGeometry(int row, int column) const { + QRect r; + if (row >= 0 && row < nrows && column >= 0 && column < ncols) + r.setRect(columnX(column), rowY(row), cellw, cellh); + return r; + } + inline void updateCell(int row, int column) { update(cellGeometry(row, column)); } + void paintCell(QPainter*, int row, int col, const QRect&); + const QRgb* values; + bool mousePressed = false; + QPoint pressPos{}; + QPoint oldCurrent{-1, -1}; + int nrows; + int ncols; + int cellw = 28; + int cellh = 24; + int curRow = 0; + int curCol = 0; + int selRow = -1; + int selCol = -1; + int cellMargin = 3; //margin + signals: + void colorChanged(int index, QRgb color); + void selected(int row, int col); + void currentChanged(int row, int col); + protected: + void paintCellContents(QPainter*, int row, int col, const QRect&); + void mousePressEvent(QMouseEvent* e) override; + void mouseMoveEvent(QMouseEvent* e) override; + void mouseReleaseEvent(QMouseEvent* e) override; + void dragEnterEvent(QDragEnterEvent* e) override; + void dragLeaveEvent(QDragLeaveEvent* e) override; + void dragMoveEvent(QDragMoveEvent* e) override; + void dropEvent(QDropEvent* e) override; + void keyPressEvent(QKeyEvent*) override; + void focusInEvent(QFocusEvent*) override; + void focusOutEvent(QFocusEvent*) override; + void paintEvent(QPaintEvent*) override; + void resizeEvent(QResizeEvent* event) override; + }; + + + class QColorPicker : public QFrame { + Q_OBJECT public: + explicit QColorPicker(QWidget* parent); + ~QColorPicker() override; + void setCrossVisible(bool visible); + public slots: + void setCol(int h, int s); + signals: + void newCol(int h, int s); + protected: + void paintEvent(QPaintEvent*) override; + void mouseMoveEvent(QMouseEvent*) override; + void mousePressEvent(QMouseEvent*) override; + void resizeEvent(QResizeEvent*) override; + int hue; + int sat; + QPoint colPt(); + int huePt(const QPoint& pt); + int satPt(const QPoint& pt); + void setCol(const QPoint& pt); + QPixmap pix; + bool crossVisible; + }; + class QColorLuminancePicker : public QWidget { + Q_OBJECT public: + explicit QColorLuminancePicker(QWidget* parent = nullptr); + ~QColorLuminancePicker() override; + public slots: + void setCol(int h, int s, int v); + void setCol(int h, int s); + signals: + void newHsv(int h, int s, int v); + protected: + void paintEvent(QPaintEvent*) override; + void mouseMoveEvent(QMouseEvent*) override; + void mousePressEvent(QMouseEvent*) override; + enum { foff = 3, coff = 4 }; //frame and contents offset + int val; + int hue; + int sat; + int y2val(int y); + int val2y(int val); + void setVal(int v); + QPixmap* pix; + }; + class QColSpinBox : public QSpinBox { + public: + explicit QColSpinBox(QWidget* parent) : QSpinBox(parent) { setRange(0, 255); } + void setValue(int i) { + const QSignalBlocker blocker(this); + QSpinBox::setValue(i); + } + }; + class QColorShowLabel; + class QColorShower : public QWidget { + Q_OBJECT public: + explicit QColorShower(SelectColorDialog* parent); + void setHsv(int h, int s, int v); + [[nodiscard]] int currentAlpha() const { return alphaEd->value(); } + void setCurrentAlpha(int a) { + alphaEd->setValue(a); + rgbEd(); + } + void showAlpha(bool b); + [[nodiscard]] bool isAlphaVisible() const; + [[nodiscard]] QRgb currentColor() const { return curCol; } + [[nodiscard]] QColor currentQColor() const { return curQColor; } + void retranslateStrings(); + void updateQColor(); + public slots: + void setRgb(QRgb rgb); + signals: + void newCol(QRgb rgb); + void currentColorChanged(const QColor& color); + private slots: + void rgbEd(); + void hsvEd(); + void htmlEd(); + private: + void showCurrentColor(); + int hue{}, sat{}, val{}; + QRgb curCol; + QColor curQColor; + QLabel *lblHue{}; + QLabel *lblSat{}; + QLabel *lblVal{}; + QLabel *lblRed{}; + QLabel *lblGreen{}; + QLabel *lblBlue{}; + QLabel *lblHtml{}; + QColSpinBox *hEd{}; + QColSpinBox *sEd{}; + QColSpinBox *vEd{}; + QColSpinBox *rEd{}; + QColSpinBox *gEd{}; + QColSpinBox *bEd{}; + QColSpinBox *alphaEd{}; + QLabel *alphaLab{}; + QLineEdit *htEd{}; + QColorShowLabel *lab{}; + bool rgbOriginal{}; + SelectColorDialog *colorDialog{}; + QGridLayout *gl{}; + }; + + class QColorShowLabel : public QFrame { + Q_OBJECT public: + explicit QColorShowLabel(QWidget* parent) : QFrame(parent) { + setFrameStyle(QFrame::Panel | QFrame::Sunken); + setAcceptDrops(true); + mousePressed = false; + } + void setColor(QColor c) { col = std::move(c); } + signals: + void colorDropped(QRgb); + protected: + void paintEvent(QPaintEvent*) override; + void mousePressEvent(QMouseEvent* e) override; + void mouseMoveEvent(QMouseEvent* e) override; + void mouseReleaseEvent(QMouseEvent* e) override; + void dragEnterEvent(QDragEnterEvent* e) override; + void dragLeaveEvent(QDragLeaveEvent* e) override; + void dropEvent(QDropEvent* e) override; + QColor col; + bool mousePressed; + QPoint pressPos; + }; + class SelectColorDialogPrivate; + + class QColorPickingEventFilter : public QWindow { + public: + // #如果出现黑屏的情况可能是开启了wayland, + // sudo nano /etc/gdm3/custom.conf + QColor grabScreenColor(const QPoint &p) { + // 获取当前的屏幕对象 + QScreen *screen = QGuiApplication::screenAt(p); // 获取光标所在的屏幕 + if (!screen) { + qDebug() << "如果未能找到屏幕,返回无效颜色"; + return QColor(); + } + // 截取屏幕上 (p.x(), p.y()) 坐标的 1x1 区域 + QPixmap pixmap = screen->grabWindow(0, p.x(), p.y(), 1, 1); + QImage image = pixmap.toImage(); + + if (image.isNull()) { + qDebug() << "如果无法获取图像,返回无效颜色"; + return QColor(); + } + return image.pixelColor(0, 0); // 返回 (0, 0) 位置的颜色 + } + explicit QColorPickingEventFilter(SelectColorDialogPrivate *dp) : m_dp(dp) { + resize(200, 200); + //setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool); + setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool); + // setAttribute(Qt::WA_TranslucentBackground); + // setWindowOpacity(0.0); + setVisible(false); + } + void updatePos(); + bool eventFilter(QObject *, QEvent *event) override; + void startGetScreen(); + void stopGetScreen(); + protected: + SelectColorDialogPrivate *m_dp; + }; + + + class SelectColorDialogPrivate { + public: + enum { + colorColumns = 8, + standardColorRows = 6, + customColorRows = 2 + }; + enum SetColorMode { + ShowColor = 0x1, + SelectColor = 0x2, + SetColorAll = ShowColor | SelectColor + }; + QRgb standardRgb[standardColorRows * colorColumns]{}, customRgb[customColorRows * colorColumns]{}; + SelectColorDialog *q{}; + QColorShower *cs{}; + QColorWell *standard{}, *custom{}; + QColorPicker *cp{}; + QColorLuminancePicker *lp{}; + QColorPickingEventFilter *mQColorPickingEventFilter{}; + ~SelectColorDialogPrivate() { + delete mQColorPickingEventFilter; + } + explicit SelectColorDialogPrivate(SelectColorDialog* mSelectColorDialog); + void setCurrentColor(const QColor &color) const { + auto rgb = color.rgb(); + int h, s, v; + rgb2hsv(rgb, h, s, v); + cp->setCol(h, s); + lp->setCol(h, s, v); + cs->setRgb(rgb); + } + QColor currentColor() { + return cs->currentColor(); + } + }; +} + diff --git a/YSGraphic_Core/base/SingletonWidget.hpp b/YSGraphic_Core/base/SingletonWidget.hpp new file mode 100644 index 0000000..364ba9f --- /dev/null +++ b/YSGraphic_Core/base/SingletonWidget.hpp @@ -0,0 +1,27 @@ +#pragma once +#include +namespace YSG { + template + class SingletonWidget { + public: + static T *instance() { + if (!mInstance) { + qInfo() << "error SingletonWidget 未创建" << mInstance; + } + return mInstance; + } + SingletonWidget() { + if (mInstance) { + qInfo() << "error SingletonWidget 重复创建" << mInstance; + } + mInstance = static_cast(this); + } + virtual ~SingletonWidget() {} + SingletonWidget(T &&) = delete; + SingletonWidget(const T &) = delete; + void operator=(const T &) = delete; + protected: + static T *mInstance; + }; + template T *SingletonWidget::mInstance = nullptr; +} diff --git a/YSGraphic_Core/base/Spin_Lock.h b/YSGraphic_Core/base/Spin_Lock.h new file mode 100644 index 0000000..f54188e --- /dev/null +++ b/YSGraphic_Core/base/Spin_Lock.h @@ -0,0 +1,7 @@ +#pragma once +#include +#include + +namespace YSG { + +} diff --git a/YSGraphic_Core/base/VirtualKeyBoard/InputCore.cpp b/YSGraphic_Core/base/VirtualKeyBoard/InputCore.cpp new file mode 100644 index 0000000..d07193c --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/InputCore.cpp @@ -0,0 +1,86 @@ +#include "InputCore.h" + +#include "Core/Base/global_include.h" +#include "Core/system/export.h" + +namespace YSG { + InputCore::InputCore(QObject *parent) : QObject(parent) { + } + InputCore::~InputCore() { + } + bool InputCore::getEnable() { + return m_able; + } + void InputCore::setEnable(bool able) { + m_able = able; + im_flush_cache(); + emit signal_input_able(able); + } + bool InputCore::init(int max_spell_len, int max_out_len) { + QString path = QString::fromStdString(Psc::get_exe_dir()); + QFile file_pinyin(dict_path); + if (!file_pinyin.exists()) { + QFile::copy(":/dict_pinyin.dat", path + "/" + dict_path); + QFile::setPermissions(path, QFileDevice::ReadOther | QFileDevice::WriteOther); + } + QFile file_user(dict_user_path); + if (!file_user.exists()) { + QFile::copy(":/dict_pinyin_user.dat", path + "/" + dict_user_path); + QFile::setPermissions(path, QFileDevice::ReadOther | QFileDevice::WriteOther); + } + m_spell_len = max_spell_len; + m_out_len = max_out_len; + bool ret = im_open_decoder(QString("%1/" + dict_path).arg(path).toLocal8Bit().data(), + QString("%1/" + dict_user_path).arg(path).toLocal8Bit().data()); + if (!ret) + return ret; + im_set_max_lens(static_cast(m_spell_len), static_cast(m_out_len)); + reset_search(); + m_able = ret; + return ret; + } + void InputCore::deinit() { + im_close_decoder(); + } + void InputCore::reset_search() { + if (m_able) + im_reset_search(); + } + unsigned int InputCore::search(const QString &spell) { + if (!m_able) + return 0; + QByteArray bytearray; + char *pinyin; + bytearray = spell.toUtf8(); + pinyin = bytearray.data(); + size_t candnum = im_search(pinyin, static_cast(bytearray.size())); + if (static_cast(candnum) < m_out_len) { + return static_cast(candnum); + } + return static_cast(m_out_len); + } + int InputCore::cur_search_pos() { + const uint16 *start_pos; + size_t pos_len; + pos_len = im_get_spl_start_pos(start_pos); + return static_cast(pos_len); + } + QStringList InputCore::get_candidate(unsigned int candnum) { + QStringList textList; + if (candnum == 0) + return textList; + char16 *cand_buf = new char16[m_out_len]; + for (unsigned int i = 0; i < candnum; i++) { + char16 *cand; + cand = im_get_candidate(i, cand_buf, static_cast(m_out_len)); + if (cand) { + textList.append(QString::fromUtf16(cand)); + } else { + continue; + } + } + delete[] cand_buf; + //qDebug()< +#include +#include +#include +#include "googlepinyin/atomdictbase.h" +#include "googlepinyin/dictbuilder.h" +#include "googlepinyin/dictdef.h" +#include "googlepinyin/dictlist.h" +#include "googlepinyin/dicttrie.h" +#include "googlepinyin/lpicache.h" +#include "googlepinyin/matrixsearch.h" +#include "googlepinyin/mystdlib.h" +#include "googlepinyin/ngram.h" +#include "googlepinyin/pinyinime.h" +#include "googlepinyin/searchutility.h" +#include "googlepinyin/spellingtable.h" +#include "googlepinyin/spellingtrie.h" +#include "googlepinyin/splparser.h" +#include "googlepinyin/sync.h" +#include "googlepinyin/userdict.h" +#include "googlepinyin/utf16char.h" +#include "googlepinyin/utf16reader.h" +using namespace ime_pinyin; +namespace YSG { + class InputCore : public QObject { + Q_OBJECT + Q_PROPERTY(bool m_able READ getEnable WRITE setEnable NOTIFY signal_input_able) + public: + explicit InputCore(QObject *parent = nullptr); + ~InputCore() override; + bool init(int max_spell_len = 64, int max_out_len = 64); + void deinit(); + unsigned int search(const QString &spell); + int cur_search_pos(); + void reset_search(); + bool getEnable(); + void setEnable(bool able); + QStringList get_candidate(unsigned int cnadnum); + signals: + void signal_input_able(bool); + private: + bool m_able = false; + int m_spell_len; + int m_out_len; + const QString dict_path = "dict_pinyin.dat"; + const QString dict_user_path = "dict_pinyin_user.dat"; + }; +} diff --git a/YSGraphic_Core/base/VirtualKeyBoard/VirtualKeyBoard.cpp b/YSGraphic_Core/base/VirtualKeyBoard/VirtualKeyBoard.cpp new file mode 100644 index 0000000..b99b485 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/VirtualKeyBoard.cpp @@ -0,0 +1,203 @@ +#include "VirtualKeyBoard.h" +#include +#include +#include +namespace YSG { + KeyButton::KeyButton() { + /*支持长按*/ +// this->setAutoRepeat(true); +// this->setAutoRepeatDelay(500); +// this->setAutoRepeatInterval(100); + connect(this, &QPushButton::pressed, [&]() { + mVirtualKeyBoard->handleKeyPress(this); + }); + } + VirtualKeyBoard::VirtualKeyBoard(QWidget *parent) : QWidget(parent) { + this->setStyleSheet( + "QPushButton{" + "font: 16pt 黑体;" + "font-size: 26px;" + "color: white;" + "margin: 2px;" + "border-radius: 5px;" + "background: #00C78C;}" + "QPushButton:pressed{" + "background: #F0FFFF ;}" + "QListWidget::Item:hover { background: #00C78C; color: white; }" + "QListWidget {outline: none; border:1px solid #00000000; color: black; }" + "QLineEdit {outline: none; border:1px solid #00000000; color: black;}" + ); + QSize screenSize = QGuiApplication::primaryScreen()->size(); + resize(screenSize / 2); + mMainLayout = new QVBoxLayout(this); + sizePolicy.setHorizontalPolicy(QSizePolicy::Preferred); + sizePolicy.setVerticalPolicy(QSizePolicy::Preferred); + sizePolicy.setHorizontalStretch(1); + sizePolicy.setVerticalStretch(0); + mEnglish = createEnglish(); + mChinese = createChinese(); + mSymbol = createSymbol(); + mMainLayout->addWidget(mEnglish); + mMainLayout->addWidget(mChinese); + mMainLayout->addWidget(mSymbol); + mCurWidget = mEnglish; + mChinese->hide(); + mSymbol->hide(); + setWindowFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool | Qt::WindowDoesNotAcceptFocus); + mCore = new InputCore(this); + if (!mCore->init()) { + qDebug() << "字典加载失败,请将dict文件移至工作目录"; + } + + + + /* 设置为列表显示模式 */ + listWidget->setViewMode(QListView::ListMode); + /* 从左往右排列 */ + listWidget->setFlow(QListView::LeftToRight); + /* 屏蔽水平滑动条 */ + listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + /* 屏蔽垂直滑动条 */ + listWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + /* 设置为像素滚动 */ + listWidget->setHorizontalScrollMode(QListWidget::ScrollPerPixel); + /* 设置鼠标左键拖动 */ + QScroller::grabGesture(listWidget, QScroller::LeftMouseButtonGesture); + //设置不可选中 + connect(mEdit, &QLineEdit::selectionChanged, [=] { mEdit->deselect(); }); + + //中文输入 + connect(mEdit, &QLineEdit::textChanged, [=](const QString &text) { + /* 单个字符过长引发崩溃 + * 处理:单个字符限制长度为10 + */ + if (text.isEmpty()) { + return; + } + if (text.length() >= 10) { + if (issingleChar(text)) { + QString deal_text = text.mid(0, 10); + getCandidateList(deal_text); + return; + } + getCandidateList(text); + return; + } else { + getCandidateList(text); + } + }); + connect(listWidget, &QListWidget::itemClicked, [=](QListWidgetItem *item) { + QKeyEvent keyPress(QEvent::KeyPress, Qt::Key_unknown, Qt::NoModifier, item->text()); + QCoreApplication::sendEvent(mInputWidget, &keyPress); + listWidget->clear(); + mEdit->clear(); + }); + } + VirtualKeyBoard::~VirtualKeyBoard() { + } + bool VirtualKeyBoard::issingleChar(QString spell) { + bool ret = true; + for (auto s: spell) { + if (s != spell[0]) { + ret = false; + break; + } + } + return ret; + } + void VirtualKeyBoard::setEdit(QWidget *edit) { + this->mInputWidget = edit; + this->mInputWidget->installEventFilter(this); + } + void VirtualKeyBoard::getCandidateList(const QString spell) { + unsigned int cand = mCore->search(spell); + if (cand == 0) { + return; + } + QStringList ret = mCore->get_candidate(cand); + if (ret.isEmpty()) { + return; + } + listWidget->clear(); + listWidget->addItems(ret); + } + void VirtualKeyBoard::handleKeyPress(KeyButton *button) { + if (mState == Chinese) { + if (button->mKey != Qt::Key_Backspace) { + mEdit->insert(button->mText); + } else { + if (!mEdit->text().isEmpty()) { + mEdit->backspace(); + } else { + listWidget->clear(); + QKeyEvent keyPress(QEvent::KeyPress, button->mKey, button->modifiers, button->mText); + QCoreApplication::sendEvent(mInputWidget, &keyPress); + } + } + } else { + if (button->isText) { + QKeyEvent keyPress(QEvent::KeyPress, button->mKey, button->modifiers, button->mText); + if(mInputWidget) { + QCoreApplication::sendEvent(mInputWidget, &keyPress); + } else + { + qWarning("VirtualKeyBoard mInputWidget == NULL!"); + } + } + } + } + bool VirtualKeyBoard::eventFilter(QObject *watched, QEvent *event) { + bool ok = false; + if (QApplication::focusWidget() == watched && event->type() == QEvent::MouseButtonRelease) { + ok = true; + } + if (event->type() == QEvent::FocusIn) { + ok = true; + } + if (ok) { + QWidget *w = qobject_cast(watched); + setEdit(w); + QPoint pos = w->mapToGlobal(QPoint(0, 0)); + QSize screenSize = QGuiApplication::primaryScreen()->size(); + int keyboardWidth = width(); + int keyboardHeight = height(); + int screenWidth = screenSize.width(); + int screenHeight = screenSize.height(); + int x = pos.x(); + int y = pos.y() + w->height(); + if (x + keyboardWidth > screenWidth) { + setFixedWidth(screenWidth - pos.x() - w->width()); + } + if (y + keyboardHeight > screenHeight) { + setFixedHeight(screenHeight - pos.y() - w->height()); + } + move(x, y); + show(); + } + if (event->type() == QEvent::FocusOut) { + this->hide(); + } + return QObject::eventFilter(watched, event); + } + void VirtualKeyBoard::mousePressEvent(QMouseEvent *event) { + if (mMoving) return; + if (event->button() == Qt::LeftButton) { + mMoving = true; + mStartPos = pos(); + mStartGlobalPos = event->globalPos(); + QApplication::setOverrideCursor(Qt::ClosedHandCursor); + } + event->accept(); + } + void VirtualKeyBoard::mouseMoveEvent(QMouseEvent *event) { + if (!mMoving) return; + move(mStartPos + event->globalPos() - mStartGlobalPos); + event->accept(); + } + void VirtualKeyBoard::mouseReleaseEvent(QMouseEvent *event) { + if (!mMoving) return; + mMoving = false; + event->accept(); + QApplication::restoreOverrideCursor(); + } +} \ No newline at end of file diff --git a/YSGraphic_Core/base/VirtualKeyBoard/VirtualKeyBoard.h b/YSGraphic_Core/base/VirtualKeyBoard/VirtualKeyBoard.h new file mode 100644 index 0000000..2719bbb --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/VirtualKeyBoard.h @@ -0,0 +1,314 @@ +#pragma once + +#include "InputCore.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Core/Base/global_include.h" + +namespace YSG { + class VirtualKeyBoard; + class KeyButton : public QPushButton { + Q_OBJECT + public: + explicit KeyButton(); + Qt::Key mKey; + QString mText; + Qt::KeyboardModifiers modifiers = Qt::NoModifier; + bool isText = false; + VirtualKeyBoard *mVirtualKeyBoard{}; + }; + + class VirtualKeyBoard : public QWidget, public Psc::Singleton { + Q_OBJECT + public: + explicit VirtualKeyBoard(QWidget *parent = nullptr); + ~VirtualKeyBoard() override; + bool eventFilter(QObject *watched, QEvent *event) override; + void setEdit(QWidget *edit); + void getCandidateList(QString spell); + bool issingleChar(QString spell); + void handleKeyPress(KeyButton *button); + bool mMoving = false; + QPoint mStartPos, mStartGlobalPos; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + public: + enum State { + English, + Chinese, + } mState = English; + QWidget *mInputWidget{}; + InputCore *mCore; + KeyButton * + createTextKey(const QString &showText, const QString &text, Qt::Key key, Qt::KeyboardModifiers modifiers, + QHBoxLayout *layout) { + auto t = createTextKeyButton(text, key, layout, true); + t->setText(showText); + t->modifiers = modifiers; + return t; + } + QSizePolicy sizePolicy; + KeyButton *createTextKeyButton(const QString &text, Qt::Key key, QHBoxLayout *layout, bool isText = true) { + auto ret = new KeyButton; + ret->setObjectName(text); + ret->mKey = key; + ret->mText = text; + ret->isText = isText; + layout->addWidget(ret); + ret->setSizePolicy(sizePolicy); + ret->mVirtualKeyBoard = this; + return ret; + } + QWidget *mEnglish, *mChinese, *mSymbol, *mCurWidget{}; + QVBoxLayout *mMainLayout; + QLineEdit *mEdit{}; + QListWidget *listWidget{}; + QWidget *createChinese() { + auto ret = new QWidget; + auto mainLayout = new QVBoxLayout(ret); + QFont font; + font.setFamily("黑体"); // 设置字体为黑体 + font.setPointSize(16); // 设置字体大小为 16pt + QSizePolicy sizePolicy2(QSizePolicy::Preferred, QSizePolicy::Preferred); + mEdit = new QLineEdit; + mEdit->setSizePolicy(sizePolicy2); + listWidget = new QListWidget; + listWidget->setSizePolicy(sizePolicy2); + mEdit->setFont(font); + listWidget->setFont(font); + int lineHeight = QFontMetrics(font).lineSpacing() + 8; + mEdit->setFixedHeight(lineHeight); + listWidget->setFixedHeight(lineHeight); + auto line0 = new QHBoxLayout; + line0->addWidget(mEdit, 3); + line0->addWidget(listWidget, 5); + mainLayout->addLayout(line0); + auto line1 = new QHBoxLayout(); + mainLayout->addLayout(line1, 5); + createTextKey("q", "q", Qt::Key_Q, Qt::NoModifier, line1); + createTextKey("w", "w", Qt::Key_W, Qt::NoModifier, line1); + createTextKey("e", "e", Qt::Key_E, Qt::NoModifier, line1); + createTextKey("r", "r", Qt::Key_R, Qt::NoModifier, line1); + createTextKey("t", "t", Qt::Key_T, Qt::NoModifier, line1); + createTextKey("y", "y", Qt::Key_Y, Qt::NoModifier, line1); + createTextKey("u", "u", Qt::Key_U, Qt::NoModifier, line1); + createTextKey("i", "i", Qt::Key_I, Qt::NoModifier, line1); + createTextKey("o", "o", Qt::Key_O, Qt::NoModifier, line1); + createTextKey("p", "p", Qt::Key_P, Qt::NoModifier, line1); + auto line2 = new QHBoxLayout(); + mainLayout->addLayout(line2, 5); + createTextKey("a", "a", Qt::Key_A, Qt::NoModifier, line2); + createTextKey("s", "s", Qt::Key_S, Qt::NoModifier, line2); + createTextKey("d", "d", Qt::Key_D, Qt::NoModifier, line2); + createTextKey("f", "f", Qt::Key_F, Qt::NoModifier, line2); + createTextKey("g", "g", Qt::Key_G, Qt::NoModifier, line2); + createTextKey("h", "h", Qt::Key_H, Qt::NoModifier, line2); + createTextKey("j", "j", Qt::Key_J, Qt::NoModifier, line2); + createTextKey("k", "k", Qt::Key_K, Qt::NoModifier, line2); + createTextKey("l", "l", Qt::Key_L, Qt::NoModifier, line2); + auto line3 = new QHBoxLayout(); + mainLayout->addLayout(line3, 5); + createTextKey("z", "z", Qt::Key_Z, Qt::NoModifier, line3); + createTextKey("x", "x", Qt::Key_X, Qt::NoModifier, line3); + createTextKey("c", "c", Qt::Key_C, Qt::NoModifier, line3); + createTextKey("v", "v", Qt::Key_V, Qt::NoModifier, line3); + createTextKey("b", "b", Qt::Key_B, Qt::NoModifier, line3); + createTextKey("n", "n", Qt::Key_N, Qt::NoModifier, line3); + createTextKey("m", "m", Qt::Key_M, Qt::NoModifier, line3); + createTextKey("Backspace", "\b", Qt::Key_Backspace, Qt::NoModifier, line3); + auto line4 = new QHBoxLayout(); + mainLayout->addLayout(line4, 5); + auto changeTo123 = new QPushButton; + line4->addWidget(changeTo123, 1); + changeTo123->setSizePolicy(sizePolicy); + changeTo123->setText("?123"); + connect(changeTo123, &KeyButton::clicked, [this, changeTo123]() { + mCurWidget->hide(); + mSymbol->show(); + }); + auto changeToEnglish = new QPushButton; + changeToEnglish->setText("En"); + changeToEnglish->setSizePolicy(sizePolicy); + line4->addWidget(changeToEnglish, 1); + connect(changeToEnglish, &KeyButton::clicked, [&, changeToEnglish]() { + mCurWidget->hide(); + mEnglish->show(); + mCurWidget = mEnglish; + mState = English; + }); + auto space = createTextKey(" ", " ", Qt::Key_Space, Qt::NoModifier, line4); + QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy1.setHorizontalStretch(5); + space->setSizePolicy(sizePolicy1); + auto hide = new QPushButton; + hide->setText("隐藏"); + hide->setSizePolicy(sizePolicy); + line4->addWidget(hide, 1); + connect(hide, &KeyButton::clicked, this, &QWidget::hide); + return ret; + } + QWidget *createEnglish() { + auto ret = new QWidget; + auto mainLayout = new QVBoxLayout(ret); + auto line1 = new QHBoxLayout(); + mainLayout->addLayout(line1); + createTextKey("q", "q", Qt::Key_Q, Qt::NoModifier, line1); + createTextKey("w", "w", Qt::Key_W, Qt::NoModifier, line1); + createTextKey("e", "e", Qt::Key_E, Qt::NoModifier, line1); + createTextKey("r", "r", Qt::Key_R, Qt::NoModifier, line1); + createTextKey("t", "t", Qt::Key_T, Qt::NoModifier, line1); + createTextKey("y", "y", Qt::Key_Y, Qt::NoModifier, line1); + createTextKey("u", "u", Qt::Key_U, Qt::NoModifier, line1); + createTextKey("i", "i", Qt::Key_I, Qt::NoModifier, line1); + createTextKey("o", "o", Qt::Key_O, Qt::NoModifier, line1); + createTextKey("p", "p", Qt::Key_P, Qt::NoModifier, line1); + auto line2 = new QHBoxLayout(); + mainLayout->addLayout(line2); + createTextKey("a", "a", Qt::Key_A, Qt::NoModifier, line2); + createTextKey("s", "s", Qt::Key_S, Qt::NoModifier, line2); + createTextKey("d", "d", Qt::Key_D, Qt::NoModifier, line2); + createTextKey("f", "f", Qt::Key_F, Qt::NoModifier, line2); + createTextKey("g", "g", Qt::Key_G, Qt::NoModifier, line2); + createTextKey("h", "h", Qt::Key_H, Qt::NoModifier, line2); + createTextKey("j", "j", Qt::Key_J, Qt::NoModifier, line2); + createTextKey("k", "k", Qt::Key_K, Qt::NoModifier, line2); + createTextKey("l", "l", Qt::Key_L, Qt::NoModifier, line2); + auto line3 = new QHBoxLayout(); + mainLayout->addLayout(line3); + auto capsLock = new QPushButton; + line3->addWidget(capsLock, 1); + capsLock->setSizePolicy(sizePolicy); + capsLock->setText("大写"); + connect(capsLock, &QPushButton::clicked, [this, capsLock]() { + if (capsLock->text() == "大写") { + for (auto child: mEnglish->findChildren()) { + if ((child->mText.size() == 1) && child->text().isLower()) { + QString &&text = child->text().toUpper(); + child->setText(text); + child->mText = text; + } + } + capsLock->setText("小写"); + } else { + for (auto child: mEnglish->findChildren()) { + if ((child->mText.size() == 1) && child->text().isUpper()) { + QString &&text = child->text().toLower(); + child->setText(text); + child->mText = text; + } + } + capsLock->setText("大写"); + } + }); + createTextKey("z", "z", Qt::Key_Z, Qt::NoModifier, line3); + createTextKey("x", "x", Qt::Key_X, Qt::NoModifier, line3); + createTextKey("c", "c", Qt::Key_C, Qt::NoModifier, line3); + createTextKey("v", "v", Qt::Key_V, Qt::NoModifier, line3); + createTextKey("b", "b", Qt::Key_B, Qt::NoModifier, line3); + createTextKey("n", "n", Qt::Key_N, Qt::NoModifier, line3); + createTextKey("m", "m", Qt::Key_M, Qt::NoModifier, line3); + createTextKey("Backspace", "\b", Qt::Key_Backspace, Qt::NoModifier, line3); + auto line4 = new QHBoxLayout(); + mainLayout->addLayout(line4); + auto changeTo123 = new QPushButton; + line4->addWidget(changeTo123, 1); + changeTo123->setSizePolicy(sizePolicy); + changeTo123->setText("?123"); + connect(changeTo123, &KeyButton::clicked, [this, changeTo123]() { + mCurWidget->hide(); + mSymbol->show(); + }); + auto changeToCh = new QPushButton; + changeToCh->setText("中文"); + changeToCh->setSizePolicy(sizePolicy); + line4->addWidget(changeToCh, 1); + connect(changeToCh, &KeyButton::clicked, [&]() { + mCurWidget->hide(); + mChinese->show(); + mCurWidget = mChinese; + mState = Chinese; + }); + auto space = createTextKey(" ", " ", Qt::Key_Space, Qt::NoModifier, line4); + QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy1.setHorizontalStretch(5); + space->setSizePolicy(sizePolicy1); + auto hide = new QPushButton; + hide->setText("隐藏"); + hide->setSizePolicy(sizePolicy); + line4->addWidget(hide, 1); + connect(hide, &KeyButton::clicked, this, &QWidget::hide); + return ret; + } + QWidget *createSymbol() { + auto ret = new QWidget; + auto mainLayout = new QVBoxLayout(ret); + auto line1 = new QHBoxLayout(); + mainLayout->addLayout(line1); + createTextKey("1", "1", Qt::Key_1, Qt::NoModifier, line1); + createTextKey("2", "2", Qt::Key_2, Qt::NoModifier, line1); + createTextKey("3", "3", Qt::Key_3, Qt::NoModifier, line1); + createTextKey("4", "4", Qt::Key_4, Qt::NoModifier, line1); + createTextKey("5", "5", Qt::Key_5, Qt::NoModifier, line1); + createTextKey("6", "6", Qt::Key_6, Qt::NoModifier, line1); + createTextKey("7", "7", Qt::Key_7, Qt::NoModifier, line1); + createTextKey("8", "8", Qt::Key_8, Qt::NoModifier, line1); + createTextKey("9", "9", Qt::Key_9, Qt::NoModifier, line1); + createTextKey("0", "0", Qt::Key_0, Qt::NoModifier, line1); + auto line2 = new QHBoxLayout(); + mainLayout->addLayout(line2); + createTextKey("!", "!", Qt::Key_1, Qt::ShiftModifier, line2); + createTextKey("@", "@", Qt::Key_2, Qt::ShiftModifier, line2); + createTextKey("#", "#", Qt::Key_3, Qt::ShiftModifier, line2); + createTextKey("%", "%", Qt::Key_5, Qt::ShiftModifier, line2); + createTextKey("&&", "&", Qt::Key_7, Qt::ShiftModifier, line2); + createTextKey("*", "*", Qt::Key_8, Qt::ShiftModifier, line2); + createTextKey("(", "(", Qt::Key_9, Qt::ShiftModifier, line2); + createTextKey(")", ")", Qt::Key_0, Qt::ShiftModifier, line2); + createTextKey("-", "-", Qt::Key_Minus, Qt::NoModifier, line2); + auto line3 = new QHBoxLayout(); + mainLayout->addLayout(line3); + createTextKey("_", "_", Qt::Key_Underscore, Qt::ShiftModifier, line3); + createTextKey(":", ":", Qt::Key_Colon, Qt::ShiftModifier, line3); + createTextKey(";", ";", Qt::Key_Semicolon, Qt::NoModifier, line3); + createTextKey("/", "/", Qt::Key_Slash, Qt::NoModifier, line3); + createTextKey(".", ".", Qt::Key_Period, Qt::NoModifier, line3); + createTextKey(",", ",", Qt::Key_Comma, Qt::NoModifier, line3); + createTextKey("?", "?", Qt::Key_Question, Qt::ShiftModifier, line3); + createTextKey("Backspace", "\b", Qt::Key_Backspace, Qt::NoModifier, line3); + auto line4 = new QHBoxLayout(); + mainLayout->addLayout(line4); + auto back = new QPushButton; + back->setText("返回"); + back->setSizePolicy(sizePolicy); + line4->addWidget(back, 1); + connect(back, &KeyButton::clicked, [&]() { + mSymbol->hide(); + mCurWidget->show(); + }); + auto space = createTextKey(" ", " ", Qt::Key_Space, Qt::NoModifier, line4); + QSizePolicy sizePolicy1(QSizePolicy::Preferred, QSizePolicy::Preferred); + sizePolicy1.setHorizontalStretch(5); + space->setSizePolicy(sizePolicy1); + auto hide = new QPushButton; + hide->setText("隐藏"); + hide->setSizePolicy(sizePolicy); + line4->addWidget(hide, 1); + connect(hide, &KeyButton::clicked, this, &QWidget::hide); + return ret; + } + }; +} + diff --git a/YSGraphic_Core/base/VirtualKeyBoard/dict/dict.qrc b/YSGraphic_Core/base/VirtualKeyBoard/dict/dict.qrc new file mode 100644 index 0000000..ac999e5 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/dict/dict.qrc @@ -0,0 +1,6 @@ + + + dict_pinyin.dat + dict_pinyin_user.dat + + diff --git a/YSGraphic_Core/base/VirtualKeyBoard/dict/dict_pinyin.dat b/YSGraphic_Core/base/VirtualKeyBoard/dict/dict_pinyin.dat new file mode 100644 index 0000000..311ab24 Binary files /dev/null and b/YSGraphic_Core/base/VirtualKeyBoard/dict/dict_pinyin.dat differ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/dict/dict_pinyin_user.dat b/YSGraphic_Core/base/VirtualKeyBoard/dict/dict_pinyin_user.dat new file mode 100644 index 0000000..dca7368 Binary files /dev/null and b/YSGraphic_Core/base/VirtualKeyBoard/dict/dict_pinyin_user.dat differ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/atomdictbase.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/atomdictbase.h new file mode 100644 index 0000000..fbab8b2 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/atomdictbase.h @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This class defines AtomDictBase class which is the base class for all atom + * dictionaries. Atom dictionaries are managed by the decoder class + * MatrixSearch. + * + * When the user appends a new character to the Pinyin string, all enabled atom + * dictionaries' extend_dict() will be called at least once to get candidates + * ended in this step (the information of starting step is also given in the + * parameter). Usually, when extend_dict() is called, a MileStoneHandle object + * returned by a previous calling for a earlier step is given to speed up the + * look-up process, and a new MileStoneHandle object will be returned if + * the extension is successful. + * + * A returned MileStoneHandle object should keep alive until Function + * reset_milestones() is called and this object is noticed to be reset. + * + * Usually, the atom dictionary can use step information to manage its + * MileStoneHandle objects, or it can make the objects in ascendant order to + * make the reset easier. + * + * When the decoder loads the dictionary, it will give a starting lemma id for + * this atom dictionary to map a inner id to a global id. Global ids should be + * used when an atom dictionary talks to any component outside. + */ +#ifndef PINYINIME_INCLUDE_ATOMDICTBASE_H__ +#define PINYINIME_INCLUDE_ATOMDICTBASE_H__ +#include +#include "./dictdef.h" +#include "./searchutility.h" +namespace ime_pinyin { + class AtomDictBase { + public: + virtual ~AtomDictBase() {} + /** + * Load an atom dictionary from a file. + * + * @param file_name The file name to load dictionary. + * @param start_id The starting id used for this atom dictionary. + * @param end_id The end id (included) which can be used for this atom + * dictionary. User dictionary will always use the last id space, so it can + * ignore this paramter. All other atom dictionaries should check this + * parameter. + * @return True if succeed. + */ + virtual bool load_dict(const char *file_name, LemmaIdType start_id, + LemmaIdType end_id) = 0; + /** + * Close this atom dictionary. + * + * @return True if succeed. + */ + virtual bool close_dict() = 0; + /** + * Get the total number of lemmas in this atom dictionary. + * + * @return The total number of lemmas. + */ + virtual size_t number_of_lemmas() = 0; + /** + * This function is called by the decoder when user deletes a character from + * the input string, or begins a new input string. + * + * Different atom dictionaries may implement this function in different way. + * an atom dictionary can use one of these two parameters (or both) to reset + * its corresponding MileStoneHandle objects according its detailed + * implementation. + * + * For example, if an atom dictionary uses step information to manage its + * MileStoneHandle objects, parameter from_step can be used to identify which + * objects should be reset; otherwise, if another atom dictionary does not + * use the detailed step information, it only uses ascendant handles + * (according to step. For the same step, earlier call, smaller handle), it + * can easily reset those MileStoneHandle which are larger than from_handle. + * + * The decoder always reset the decoding state by step. So when it begins + * resetting, it will call reset_milestones() of its atom dictionaries with + * the step information, and the MileStoneHandle objects returned by the + * earliest calling of extend_dict() for that step. + * + * If an atom dictionary does not implement incremental search, this function + * can be totally ignored. + * + * @param from_step From which step(included) the MileStoneHandle + * objects should be reset. + * @param from_handle The ealiest MileStoneHandle object for step from_step + */ + virtual void reset_milestones(uint16 from_step, + MileStoneHandle from_handle) = 0; + /** + * Used to extend in this dictionary. The handle returned should keep valid + * until reset_milestones() is called. + * + * @param from_handle Its previous returned extended handle without the new + * spelling id, it can be used to speed up the extending. + * @param dep The paramter used for extending. + * @param lpi_items Used to fill in the lemmas matched. + * @param lpi_max The length of the buffer + * @param lpi_num Used to return the newly added items. + * @return The new mile stone for this extending. 0 if fail. + */ + virtual MileStoneHandle extend_dict(MileStoneHandle from_handle, + const DictExtPara *dep, + LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num) = 0; + /** + * Get lemma items with scores according to a spelling id stream. + * This atom dictionary does not need to sort the returned items. + * + * @param splid_str The spelling id stream buffer. + * @param splid_str_len The length of the spelling id stream buffer. + * @param lpi_items Used to return matched lemma items with scores. + * @param lpi_max The maximum size of the buffer to return result. + * @return The number of matched items which have been filled in to lpi_items. + */ + virtual size_t get_lpis(const uint16 *splid_str, uint16 splid_str_len, + LmaPsbItem *lpi_items, size_t lpi_max) = 0; + /** + * Get a lemma string (The Chinese string) by the given lemma id. + * + * @param id_lemma The lemma id to get the string. + * @param str_buf The buffer to return the Chinese string. + * @param str_max The maximum size of the buffer. + * @return The length of the string, 0 if fail. + */ + virtual uint16 get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, + uint16 str_max) = 0; + /** + * Get the full spelling ids for the given lemma id. + * If the given buffer is too short, return 0. + * + * @param splids Used to return the spelling ids. + * @param splids_max The maximum buffer length of splids. + * @param arg_valid Used to indicate if the incoming parameters have been + * initialized are valid. If it is true, the splids and splids_max are valid + * and there may be half ids in splids to be updated to full ids. In this + * case, splids_max is the number of valid ids in splids. + * @return The number of ids in the buffer. + */ + virtual uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids, + uint16 splids_max, bool arg_valid) = 0; + /** + * Function used for prediction. + * No need to sort the newly added items. + * + * @param last_hzs The last n Chinese chracters(called Hanzi), its length + * should be less than or equal to kMaxPredictSize. + * @param hzs_len specifies the length(<= kMaxPredictSize) of the history. + * @param npre_items Used used to return the result. + * @param npre_max The length of the buffer to return result + * @param b4_used Number of prediction result (from npre_items[-b4_used]) + * from other atom dictionaries. A atom ditionary can just ignore it. + * @return The number of prediction result from this atom dictionary. + */ + virtual size_t predict(const char16 last_hzs[], uint16 hzs_len, + NPredictItem *npre_items, size_t npre_max, + size_t b4_used) = 0; + /** + * Add a lemma to the dictionary. If the dictionary allows to add new + * items and this item does not exist, add it. + * + * @param lemma_str The Chinese string of the lemma. + * @param splids The spelling ids of the lemma. + * @param lemma_len The length of the Chinese lemma. + * @param count The frequency count for this lemma. + */ + virtual LemmaIdType put_lemma(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count) = 0; + /** + * Update a lemma's occuring count. + * + * @param lemma_id The lemma id to update. + * @param delta_count The frequnecy count to ajust. + * @param selected Indicate whether this lemma is selected by user and + * submitted to target edit box. + * @return The id if succeed, 0 if fail. + */ + virtual LemmaIdType update_lemma(LemmaIdType lemma_id, int16 delta_count, + bool selected) = 0; + /** + * Get the lemma id for the given lemma. + * + * @param lemma_str The Chinese string of the lemma. + * @param splids The spelling ids of the lemma. + * @param lemma_len The length of the lemma. + * @return The matched lemma id, or 0 if fail. + */ + virtual LemmaIdType get_lemma_id(char16 lemma_str[], uint16 splids[], + uint16 lemma_len) = 0; + /** + * Get the lemma score. + * + * @param lemma_id The lemma id to get score. + * @return The score of the lemma, or 0 if fail. + */ + virtual LmaScoreType get_lemma_score(LemmaIdType lemma_id) = 0; + /** + * Get the lemma score. + * + * @param lemma_str The Chinese string of the lemma. + * @param splids The spelling ids of the lemma. + * @param lemma_len The length of the lemma. + * @return The score of the lamm, or 0 if fail. + */ + virtual LmaScoreType get_lemma_score(char16 lemma_str[], uint16 splids[], + uint16 lemma_len) = 0; + /** + * If the dictionary allowed, remove a lemma from it. + * + * @param lemma_id The id of the lemma to remove. + * @return True if succeed. + */ + virtual bool remove_lemma(LemmaIdType lemma_id) = 0; + /** + * Get the total occuring count of this atom dictionary. + * + * @return The total occuring count of this atom dictionary. + */ + virtual size_t get_total_lemma_count() = 0; + /** + * Set the total occuring count of other atom dictionaries. + * + * @param count The total occuring count of other atom dictionaies. + */ + virtual void set_total_lemma_count_of_others(size_t count) = 0; + /** + * Notify this atom dictionary to flush the cached data to persistent storage + * if necessary. + */ + virtual void flush_cache() = 0; + }; +} +#endif // PINYINIME_INCLUDE_ATOMDICTBASE_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictbuilder.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictbuilder.cpp new file mode 100644 index 0000000..035ec03 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictbuilder.cpp @@ -0,0 +1,1067 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include "dictbuilder.h" +#include "dicttrie.h" +#include "mystdlib.h" +#include "ngram.h" +#include "searchutility.h" +#include "spellingtable.h" +#include "spellingtrie.h" +#include "splparser.h" +#include "utf16reader.h" +namespace ime_pinyin { +#ifdef ___BUILD_MODEL___ + + static const size_t kReadBufLen = 512; + static const size_t kSplTableHashLen = 2000; + + // Compare a SingleCharItem, first by Hanzis, then by spelling ids, then by + // frequencies. + int cmp_scis_hz_splid_freq(const void* p1, const void* p2) { + const SingleCharItem *s1, *s2; + s1 = static_cast(p1); + s2 = static_cast(p2); + + if (s1->hz < s2->hz) + return -1; + if (s1->hz > s2->hz) + return 1; + + if (s1->splid.half_splid < s2->splid.half_splid) + return -1; + if (s1->splid.half_splid > s2->splid.half_splid) + return 1; + + if (s1->splid.full_splid < s2->splid.full_splid) + return -1; + if (s1->splid.full_splid > s2->splid.full_splid) + return 1; + + if (s1->freq > s2->freq) + return -1; + if (s1->freq < s2->freq) + return 1; + return 0; + } + + int cmp_scis_hz_splid(const void* p1, const void* p2) { + const SingleCharItem *s1, *s2; + s1 = static_cast(p1); + s2 = static_cast(p2); + + if (s1->hz < s2->hz) + return -1; + if (s1->hz > s2->hz) + return 1; + + if (s1->splid.half_splid < s2->splid.half_splid) + return -1; + if (s1->splid.half_splid > s2->splid.half_splid) + return 1; + + if (s1->splid.full_splid < s2->splid.full_splid) + return -1; + if (s1->splid.full_splid > s2->splid.full_splid) + return 1; + + return 0; + } + + int cmp_lemma_entry_hzs(const void* p1, const void* p2) { + size_t size1 = utf16_strlen(((const LemmaEntry*)p1)->hanzi_str); + size_t size2 = utf16_strlen(((const LemmaEntry*)p2)->hanzi_str); + if (size1 < size2) + return -1; + else if (size1 > size2) + return 1; + + return utf16_strcmp(((const LemmaEntry*)p1)->hanzi_str, + ((const LemmaEntry*)p2)->hanzi_str); + } + + int compare_char16(const void* p1, const void* p2) { + if (*((const char16*)p1) < *((const char16*)p2)) + return -1; + if (*((const char16*)p1) > *((const char16*)p2)) + return 1; + return 0; + } + + int compare_py(const void* p1, const void* p2) { + int ret = utf16_strcmp(((const LemmaEntry*)p1)->spl_idx_arr, + ((const LemmaEntry*)p2)->spl_idx_arr); + + if (0 != ret) + return ret; + + return static_cast(((const LemmaEntry*)p2)->freq) - + static_cast(((const LemmaEntry*)p1)->freq); + } + + // First hanzi, if the same, then Pinyin + int cmp_lemma_entry_hzspys(const void* p1, const void* p2) { + size_t size1 = utf16_strlen(((const LemmaEntry*)p1)->hanzi_str); + size_t size2 = utf16_strlen(((const LemmaEntry*)p2)->hanzi_str); + if (size1 < size2) + return -1; + else if (size1 > size2) + return 1; + int ret = utf16_strcmp(((const LemmaEntry*)p1)->hanzi_str, + ((const LemmaEntry*)p2)->hanzi_str); + + if (0 != ret) + return ret; + + ret = utf16_strcmp(((const LemmaEntry*)p1)->spl_idx_arr, + ((const LemmaEntry*)p2)->spl_idx_arr); + return ret; + } + + int compare_splid2(const void* p1, const void* p2) { + int ret = utf16_strcmp(((const LemmaEntry*)p1)->spl_idx_arr, + ((const LemmaEntry*)p2)->spl_idx_arr); + return ret; + } + + DictBuilder::DictBuilder() { + lemma_arr_ = NULL; + lemma_num_ = 0; + + scis_ = NULL; + scis_num_ = 0; + + lma_nodes_le0_ = NULL; + lma_nodes_ge1_ = NULL; + + lma_nds_used_num_le0_ = 0; + lma_nds_used_num_ge1_ = 0; + + homo_idx_buf_ = NULL; + homo_idx_num_eq1_ = 0; + homo_idx_num_gt1_ = 0; + + top_lmas_ = NULL; + top_lmas_num_ = 0; + + spl_table_ = NULL; + spl_parser_ = NULL; + } + + DictBuilder::~DictBuilder() { + free_resource(); + } + + bool DictBuilder::alloc_resource(size_t lma_num) { + if (0 == lma_num) + return false; + + free_resource(); + + lemma_num_ = lma_num; + lemma_arr_ = new LemmaEntry[lemma_num_]; + + top_lmas_num_ = 0; + top_lmas_ = new LemmaEntry[kTopScoreLemmaNum]; + + // New the scis_ buffer to the possible maximum size. + scis_num_ = lemma_num_ * kMaxLemmaSize; + scis_ = new SingleCharItem[scis_num_]; + + // The root and first level nodes is less than kMaxSpellingNum + 1 + lma_nds_used_num_le0_ = 0; + lma_nodes_le0_ = new LmaNodeLE0[kMaxSpellingNum + 1]; + + // Other nodes is less than lemma_num + lma_nds_used_num_ge1_ = 0; + lma_nodes_ge1_ = new LmaNodeGE1[lemma_num_]; + + homo_idx_buf_ = new LemmaIdType[lemma_num_]; + spl_table_ = new SpellingTable(); + spl_parser_ = new SpellingParser(); + + if (NULL == lemma_arr_ || NULL == top_lmas_ || + NULL == scis_ || NULL == spl_table_ || + NULL == spl_parser_ || NULL == lma_nodes_le0_ || + NULL == lma_nodes_ge1_ || NULL == homo_idx_buf_) { + free_resource(); + return false; + } + + memset(lemma_arr_, 0, sizeof(LemmaEntry) * lemma_num_); + memset(scis_, 0, sizeof(SingleCharItem) * scis_num_); + memset(lma_nodes_le0_, 0, sizeof(LmaNodeLE0) * (kMaxSpellingNum + 1)); + memset(lma_nodes_ge1_, 0, sizeof(LmaNodeGE1) * lemma_num_); + memset(homo_idx_buf_, 0, sizeof(LemmaIdType) * lemma_num_); + spl_table_->init_table(kMaxPinyinSize, kSplTableHashLen, true); + + return true; + } + + char16* DictBuilder::read_valid_hanzis(const char *fn_validhzs, size_t *num) { + if (NULL == fn_validhzs || NULL == num) + return NULL; + + *num = 0; + FILE *fp = fopen(fn_validhzs, "rb"); + if (NULL == fp) + return NULL; + + char16 utf16header; + if (fread(&utf16header, sizeof(char16), 1, fp) != 1 || + 0xfeff != utf16header) { + fclose(fp); + return NULL; + } + + fseek(fp, 0, SEEK_END); + *num = ftell(fp) / sizeof(char16); + assert(*num >= 1); + *num -= 1; + + char16 *hzs = new char16[*num]; + if (NULL == hzs) { + fclose(fp); + return NULL; + } + + fseek(fp, 2, SEEK_SET); + + if (fread(hzs, sizeof(char16), *num, fp) != *num) { + fclose(fp); + delete [] hzs; + return NULL; + } + fclose(fp); + + myqsort(hzs, *num, sizeof(char16), compare_char16); + return hzs; + } + + bool DictBuilder::hz_in_hanzis_list(const char16 *hzs, size_t hzs_len, + char16 hz) { + if (NULL == hzs) + return false; + + char16 *found; + found = static_cast( + mybsearch(&hz, hzs, hzs_len, sizeof(char16), compare_char16)); + if (NULL == found) + return false; + + assert(*found == hz); + return true; + } + + // The caller makes sure that the parameters are valid. + bool DictBuilder::str_in_hanzis_list(const char16 *hzs, size_t hzs_len, + const char16 *str, size_t str_len) { + if (NULL == hzs || NULL == str) + return false; + + for (size_t pos = 0; pos < str_len; pos++) { + if (!hz_in_hanzis_list(hzs, hzs_len, str[pos])) + return false; + } + return true; + } + + void DictBuilder::get_top_lemmas() { + top_lmas_num_ = 0; + if (NULL == lemma_arr_) + return; + + for (size_t pos = 0; pos < lemma_num_; pos++) { + if (0 == top_lmas_num_) { + top_lmas_[0] = lemma_arr_[pos]; + top_lmas_num_ = 1; + continue; + } + + if (lemma_arr_[pos].freq > top_lmas_[top_lmas_num_ - 1].freq) { + if (kTopScoreLemmaNum > top_lmas_num_) + top_lmas_num_ += 1; + + size_t move_pos; + for (move_pos = top_lmas_num_ - 1; move_pos > 0; move_pos--) { + top_lmas_[move_pos] = top_lmas_[move_pos - 1]; + if (0 == move_pos - 1 || + (move_pos - 1 > 0 && + top_lmas_[move_pos - 2].freq > lemma_arr_[pos].freq)) { + break; + } + } + assert(move_pos > 0); + top_lmas_[move_pos - 1] = lemma_arr_[pos]; + } else if (kTopScoreLemmaNum > top_lmas_num_) { + top_lmas_[top_lmas_num_] = lemma_arr_[pos]; + top_lmas_num_ += 1; + } + } + + if (kPrintDebug0) { + printf("\n------Top Lemmas------------------\n"); + for (size_t pos = 0; pos < top_lmas_num_; pos++) { + printf("--%d, idx:%06d, score:%.5f\n", pos, top_lmas_[pos].idx_by_hz, + top_lmas_[pos].freq); + } + } + } + + void DictBuilder::free_resource() { + if (NULL != lemma_arr_) + delete [] lemma_arr_; + + if (NULL != scis_) + delete [] scis_; + + if (NULL != lma_nodes_le0_) + delete [] lma_nodes_le0_; + + if (NULL != lma_nodes_ge1_) + delete [] lma_nodes_ge1_; + + if (NULL != homo_idx_buf_) + delete [] homo_idx_buf_; + + if (NULL != spl_table_) + delete spl_table_; + + if (NULL != spl_parser_) + delete spl_parser_; + + lemma_arr_ = NULL; + scis_ = NULL; + lma_nodes_le0_ = NULL; + lma_nodes_ge1_ = NULL; + homo_idx_buf_ = NULL; + spl_table_ = NULL; + spl_parser_ = NULL; + + lemma_num_ = 0; + lma_nds_used_num_le0_ = 0; + lma_nds_used_num_ge1_ = 0; + homo_idx_num_eq1_ = 0; + homo_idx_num_gt1_ = 0; + } + + size_t DictBuilder::read_raw_dict(const char* fn_raw, + const char *fn_validhzs, + size_t max_item) { + if (NULL == fn_raw) return 0; + + Utf16Reader utf16_reader; + if (!utf16_reader.open(fn_raw, kReadBufLen * 10)) + return false; + + char16 read_buf[kReadBufLen]; + + // Read the number of lemmas in the file + size_t lemma_num = 240000; + + // allocate resource required + if (!alloc_resource(lemma_num)) { + utf16_reader.close(); + } + + // Read the valid Hanzi list. + char16 *valid_hzs = NULL; + size_t valid_hzs_num = 0; + valid_hzs = read_valid_hanzis(fn_validhzs, &valid_hzs_num); + + // Begin reading the lemma entries + for (size_t i = 0; i < max_item; i++) { + // read next entry + if (!utf16_reader.readline(read_buf, kReadBufLen)) { + lemma_num = i; + break; + } + + size_t token_size; + char16 *token; + char16 *to_tokenize = read_buf; + + // Get the Hanzi string + token = utf16_strtok(to_tokenize, &token_size, &to_tokenize); + if (NULL == token) { + free_resource(); + utf16_reader.close(); + return false; + } + + size_t lemma_size = utf16_strlen(token); + + if (lemma_size > kMaxLemmaSize) { + i--; + continue; + } + + if (lemma_size > 4) { + i--; + continue; + } + + // Copy to the lemma entry + utf16_strcpy(lemma_arr_[i].hanzi_str, token); + + lemma_arr_[i].hz_str_len = token_size; + + // Get the freq string + token = utf16_strtok(to_tokenize, &token_size, &to_tokenize); + if (NULL == token) { + free_resource(); + utf16_reader.close(); + return false; + } + lemma_arr_[i].freq = utf16_atof(token); + + if (lemma_size > 1 && lemma_arr_[i].freq < 60) { + i--; + continue; + } + + // Get GBK mark, if no valid Hanzi list available, all items which contains + // GBK characters will be discarded. Otherwise, all items which contains + // characters outside of the valid Hanzi list will be discarded. + token = utf16_strtok(to_tokenize, &token_size, &to_tokenize); + assert(NULL != token); + int gbk_flag = utf16_atoi(token); + if (NULL == valid_hzs || 0 == valid_hzs_num) { + if (0 != gbk_flag) { + i--; + continue; + } + } else { + if (!str_in_hanzis_list(valid_hzs, valid_hzs_num, + lemma_arr_[i].hanzi_str, lemma_arr_[i].hz_str_len)) { + i--; + continue; + } + } + + // Get spelling String + bool spelling_not_support = false; + for (size_t hz_pos = 0; hz_pos < (size_t)lemma_arr_[i].hz_str_len; + hz_pos++) { + // Get a Pinyin + token = utf16_strtok(to_tokenize, &token_size, &to_tokenize); + if (NULL == token) { + free_resource(); + utf16_reader.close(); + return false; + } + + assert(utf16_strlen(token) <= kMaxPinyinSize); + + utf16_strcpy_tochar(lemma_arr_[i].pinyin_str[hz_pos], token); + + format_spelling_str(lemma_arr_[i].pinyin_str[hz_pos]); + + // Put the pinyin to the spelling table + if (!spl_table_->put_spelling(lemma_arr_[i].pinyin_str[hz_pos], + lemma_arr_[i].freq)) { + spelling_not_support = true; + break; + } + } + + // The whole line must have been parsed fully, otherwise discard this one. + token = utf16_strtok(to_tokenize, &token_size, &to_tokenize); + if (spelling_not_support || NULL != token) { + i--; + continue; + } + } + + delete [] valid_hzs; + utf16_reader.close(); + + printf("read successfully, lemma num: %zd\n", lemma_num); + + return lemma_num; + } + + bool DictBuilder::build_dict(const char *fn_raw, + const char *fn_validhzs, + DictTrie *dict_trie) { + if (NULL == fn_raw || NULL == dict_trie) + return false; + + lemma_num_ = read_raw_dict(fn_raw, fn_validhzs, 240000); + if (0 == lemma_num_) + return false; + + // Arrange the spelling table, and build a spelling tree + // The size of an spelling. '\0' is included. If the spelling table is + // initialized to calculate the spelling scores, the last char in the + // spelling string will be score, and it is also included in spl_item_size. + size_t spl_item_size; + size_t spl_num; + const char* spl_buf; + spl_buf = spl_table_->arrange(&spl_item_size, &spl_num); + if (NULL == spl_buf) { + free_resource(); + return false; + } + + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + + if (!spl_trie.construct(spl_buf, spl_item_size, spl_num, + spl_table_->get_score_amplifier(), + spl_table_->get_average_score())) { + free_resource(); + return false; + } + + printf("spelling tree construct successfully.\n"); + + // Convert the spelling string to idxs + for (size_t i = 0; i < lemma_num_; i++) { + for (size_t hz_pos = 0; hz_pos < (size_t)lemma_arr_[i].hz_str_len; + hz_pos++) { + uint16 spl_idxs[2]; + uint16 spl_start_pos[3]; + bool is_pre = true; + int spl_idx_num = + spl_parser_->splstr_to_idxs(lemma_arr_[i].pinyin_str[hz_pos], + strlen(lemma_arr_[i].pinyin_str[hz_pos]), + spl_idxs, spl_start_pos, 2, is_pre); + assert(1 == spl_idx_num); + + if (spl_trie.is_half_id(spl_idxs[0])) { + uint16 num = spl_trie.half_to_full(spl_idxs[0], spl_idxs); + assert(0 != num); + } + lemma_arr_[i].spl_idx_arr[hz_pos] = spl_idxs[0]; + } + } + + // Sort the lemma items according to the hanzi, and give each unique item a + // id + sort_lemmas_by_hz(); + + scis_num_ = build_scis(); + + // Construct the dict list + dict_trie->dict_list_ = new DictList(); + bool dl_success = dict_trie->dict_list_->init_list(scis_, scis_num_, + lemma_arr_, lemma_num_); + assert(dl_success); + + // Construct the NGram information + NGram& ngram = NGram::get_instance(); + ngram.build_unigram(lemma_arr_, lemma_num_, + lemma_arr_[lemma_num_ - 1].idx_by_hz + 1); + + // sort the lemma items according to the spelling idx string + myqsort(lemma_arr_, lemma_num_, sizeof(LemmaEntry), compare_py); + + get_top_lemmas(); + +#ifdef ___DO_STATISTICS___ + stat_init(); +#endif + + lma_nds_used_num_le0_ = 1; // The root node + bool dt_success = construct_subset(static_cast(lma_nodes_le0_), + lemma_arr_, 0, lemma_num_, 0); + if (!dt_success) { + free_resource(); + return false; + } + +#ifdef ___DO_STATISTICS___ + stat_print(); +#endif + + // Move the node data and homo data to the DictTrie + dict_trie->root_ = new LmaNodeLE0[lma_nds_used_num_le0_]; + dict_trie->nodes_ge1_ = new LmaNodeGE1[lma_nds_used_num_ge1_]; + size_t lma_idx_num = homo_idx_num_eq1_ + homo_idx_num_gt1_ + top_lmas_num_; + dict_trie->lma_idx_buf_ = new unsigned char[lma_idx_num * kLemmaIdSize]; + assert(NULL != dict_trie->root_); + assert(NULL != dict_trie->lma_idx_buf_); + dict_trie->lma_node_num_le0_ = lma_nds_used_num_le0_; + dict_trie->lma_node_num_ge1_ = lma_nds_used_num_ge1_; + dict_trie->lma_idx_buf_len_ = lma_idx_num * kLemmaIdSize; + dict_trie->top_lmas_num_ = top_lmas_num_; + + memcpy(dict_trie->root_, lma_nodes_le0_, + sizeof(LmaNodeLE0) * lma_nds_used_num_le0_); + memcpy(dict_trie->nodes_ge1_, lma_nodes_ge1_, + sizeof(LmaNodeGE1) * lma_nds_used_num_ge1_); + + for (size_t pos = 0; pos < homo_idx_num_eq1_ + homo_idx_num_gt1_; pos++) { + id_to_charbuf(dict_trie->lma_idx_buf_ + pos * kLemmaIdSize, + homo_idx_buf_[pos]); + } + + for (size_t pos = homo_idx_num_eq1_ + homo_idx_num_gt1_; + pos < lma_idx_num; pos++) { + LemmaIdType idx = + top_lmas_[pos - homo_idx_num_eq1_ - homo_idx_num_gt1_].idx_by_hz; + id_to_charbuf(dict_trie->lma_idx_buf_ + pos * kLemmaIdSize, idx); + } + + if (kPrintDebug0) { + printf("homo_idx_num_eq1_: %d\n", homo_idx_num_eq1_); + printf("homo_idx_num_gt1_: %d\n", homo_idx_num_gt1_); + printf("top_lmas_num_: %d\n", top_lmas_num_); + } + + free_resource(); + + if (kPrintDebug0) { + printf("Building dict succeds\n"); + } + return dt_success; + } + + void DictBuilder::id_to_charbuf(unsigned char *buf, LemmaIdType id) { + if (NULL == buf) return; + for (size_t pos = 0; pos < kLemmaIdSize; pos++) { + (buf)[pos] = (unsigned char)(id >> (pos * 8)); + } + } + + void DictBuilder::set_son_offset(LmaNodeGE1 *node, size_t offset) { + node->son_1st_off_l = static_cast(offset); + node->son_1st_off_h = static_cast(offset >> 16); + } + + void DictBuilder:: set_homo_id_buf_offset(LmaNodeGE1 *node, size_t offset) { + node->homo_idx_buf_off_l = static_cast(offset); + node->homo_idx_buf_off_h = static_cast(offset >> 16); + + } + + // All spelling strings will be converted to upper case, except that + // spellings started with "ZH"/"CH"/"SH" will be converted to + // "Zh"/"Ch"/"Sh" + void DictBuilder::format_spelling_str(char *spl_str) { + if (NULL == spl_str) + return; + + uint16 pos = 0; + while ('\0' != spl_str[pos]) { + if (spl_str[pos] >= 'a' && spl_str[pos] <= 'z') + spl_str[pos] = spl_str[pos] - 'a' + 'A'; + + if (1 == pos && 'H' == spl_str[pos]) { + if ('C' == spl_str[0] || 'S' == spl_str[0] || 'Z' == spl_str[0]) { + spl_str[pos] = 'h'; + } + } + pos++; + } + } + + LemmaIdType DictBuilder::sort_lemmas_by_hz() { + if (NULL == lemma_arr_ || 0 == lemma_num_) + return 0; + + myqsort(lemma_arr_, lemma_num_, sizeof(LemmaEntry), cmp_lemma_entry_hzs); + + lemma_arr_[0].idx_by_hz = 1; + LemmaIdType idx_max = 1; + for (size_t i = 1; i < lemma_num_; i++) { + if (utf16_strcmp(lemma_arr_[i].hanzi_str, lemma_arr_[i-1].hanzi_str)) { + idx_max++; + lemma_arr_[i].idx_by_hz = idx_max; + } else { + idx_max++; + lemma_arr_[i].idx_by_hz = idx_max; + } + } + return idx_max + 1; + } + + size_t DictBuilder::build_scis() { + if (NULL == scis_ || lemma_num_ * kMaxLemmaSize > scis_num_) + return 0; + + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + + // This first one is blank, because id 0 is invalid. + scis_[0].freq = 0; + scis_[0].hz = 0; + scis_[0].splid.full_splid = 0; + scis_[0].splid.half_splid = 0; + scis_num_ = 1; + + // Copy the hanzis to the buffer + for (size_t pos = 0; pos < lemma_num_; pos++) { + size_t hz_num = lemma_arr_[pos].hz_str_len; + for (size_t hzpos = 0; hzpos < hz_num; hzpos++) { + scis_[scis_num_].hz = lemma_arr_[pos].hanzi_str[hzpos]; + scis_[scis_num_].splid.full_splid = lemma_arr_[pos].spl_idx_arr[hzpos]; + scis_[scis_num_].splid.half_splid = + spl_trie.full_to_half(scis_[scis_num_].splid.full_splid); + if (1 == hz_num) + scis_[scis_num_].freq = lemma_arr_[pos].freq; + else + scis_[scis_num_].freq = 0.000001; + scis_num_++; + } + } + + myqsort(scis_, scis_num_, sizeof(SingleCharItem), cmp_scis_hz_splid_freq); + + // Remove repeated items + size_t unique_scis_num = 1; + for (size_t pos = 1; pos < scis_num_; pos++) { + if (scis_[pos].hz == scis_[pos - 1].hz && + scis_[pos].splid.full_splid == scis_[pos - 1].splid.full_splid) + continue; + scis_[unique_scis_num] = scis_[pos]; + scis_[unique_scis_num].splid.half_splid = + spl_trie.full_to_half(scis_[pos].splid.full_splid); + unique_scis_num++; + } + + scis_num_ = unique_scis_num; + + // Update the lemma list. + for (size_t pos = 0; pos < lemma_num_; pos++) { + size_t hz_num = lemma_arr_[pos].hz_str_len; + for (size_t hzpos = 0; hzpos < hz_num; hzpos++) { + SingleCharItem key; + key.hz = lemma_arr_[pos].hanzi_str[hzpos]; + key.splid.full_splid = lemma_arr_[pos].spl_idx_arr[hzpos]; + key.splid.half_splid = spl_trie.full_to_half(key.splid.full_splid); + + SingleCharItem *found; + found = static_cast(mybsearch(&key, scis_, + unique_scis_num, + sizeof(SingleCharItem), + cmp_scis_hz_splid)); + + assert(found); + + lemma_arr_[pos].hanzi_scis_ids[hzpos] = + static_cast(found - scis_); + lemma_arr_[pos].spl_idx_arr[hzpos] = found->splid.full_splid; + } + } + + return scis_num_; + } + + bool DictBuilder::construct_subset(void* parent, LemmaEntry* lemma_arr, + size_t item_start, size_t item_end, + size_t level) { + if (level >= kMaxLemmaSize || item_end <= item_start) + return false; + + // 1. Scan for how many sons + size_t parent_son_num = 0; + // LemmaNode *son_1st = NULL; + // parent.num_of_son = 0; + + LemmaEntry *lma_last_start = lemma_arr_ + item_start; + uint16 spl_idx_node = lma_last_start->spl_idx_arr[level]; + + // Scan for how many sons to be allocaed + for (size_t i = item_start + 1; i< item_end; i++) { + LemmaEntry *lma_current = lemma_arr + i; + uint16 spl_idx_current = lma_current->spl_idx_arr[level]; + if (spl_idx_current != spl_idx_node) { + parent_son_num++; + spl_idx_node = spl_idx_current; + } + } + parent_son_num++; + +#ifdef ___DO_STATISTICS___ + // Use to indicate whether all nodes of this layer have no son. + bool allson_noson = true; + + assert(level < kMaxLemmaSize); + if (parent_son_num > max_sonbuf_len_[level]) + max_sonbuf_len_[level] = parent_son_num; + + total_son_num_[level] += parent_son_num; + total_sonbuf_num_[level] += 1; + + if (parent_son_num == 1) + sonbufs_num1_++; + else + sonbufs_numgt1_++; + total_lma_node_num_ += parent_son_num; +#endif + + // 2. Update the parent's information + // Update the parent's son list; + LmaNodeLE0 *son_1st_le0 = NULL; // only one of le0 or ge1 is used + LmaNodeGE1 *son_1st_ge1 = NULL; // only one of le0 or ge1 is used. + if (0 == level) { // the parent is root + (static_cast(parent))->son_1st_off = + lma_nds_used_num_le0_; + son_1st_le0 = lma_nodes_le0_ + lma_nds_used_num_le0_; + lma_nds_used_num_le0_ += parent_son_num; + + assert(parent_son_num <= 65535); + (static_cast(parent))->num_of_son = + static_cast(parent_son_num); + } else if (1 == level) { // the parent is a son of root + (static_cast(parent))->son_1st_off = + lma_nds_used_num_ge1_; + son_1st_ge1 = lma_nodes_ge1_ + lma_nds_used_num_ge1_; + lma_nds_used_num_ge1_ += parent_son_num; + + assert(parent_son_num <= 65535); + (static_cast(parent))->num_of_son = + static_cast(parent_son_num); + } else { + set_son_offset((static_cast(parent)), + lma_nds_used_num_ge1_); + son_1st_ge1 = lma_nodes_ge1_ + lma_nds_used_num_ge1_; + lma_nds_used_num_ge1_ += parent_son_num; + + assert(parent_son_num <= 255); + (static_cast(parent))->num_of_son = + (unsigned char)parent_son_num; + } + + // 3. Now begin to construct the son one by one + size_t son_pos = 0; + + lma_last_start = lemma_arr_ + item_start; + spl_idx_node = lma_last_start->spl_idx_arr[level]; + + size_t homo_num = 0; + if (lma_last_start->spl_idx_arr[level + 1] == 0) + homo_num = 1; + + size_t item_start_next = item_start; + + for (size_t i = item_start + 1; i < item_end; i++) { + LemmaEntry* lma_current = lemma_arr_ + i; + uint16 spl_idx_current = lma_current->spl_idx_arr[level]; + + if (spl_idx_current == spl_idx_node) { + if (lma_current->spl_idx_arr[level + 1] == 0) + homo_num++; + } else { + // Construct a node + LmaNodeLE0 *node_cur_le0 = NULL; // only one of them is valid + LmaNodeGE1 *node_cur_ge1 = NULL; + if (0 == level) { + node_cur_le0 = son_1st_le0 + son_pos; + node_cur_le0->spl_idx = spl_idx_node; + node_cur_le0->homo_idx_buf_off = homo_idx_num_eq1_ + homo_idx_num_gt1_; + node_cur_le0->son_1st_off = 0; + homo_idx_num_eq1_ += homo_num; + } else { + node_cur_ge1 = son_1st_ge1 + son_pos; + node_cur_ge1->spl_idx = spl_idx_node; + + set_homo_id_buf_offset(node_cur_ge1, + (homo_idx_num_eq1_ + homo_idx_num_gt1_)); + set_son_offset(node_cur_ge1, 0); + homo_idx_num_gt1_ += homo_num; + } + + if (homo_num > 0) { + LemmaIdType* idx_buf = homo_idx_buf_ + homo_idx_num_eq1_ + + homo_idx_num_gt1_ - homo_num; + if (0 == level) { + assert(homo_num <= 65535); + node_cur_le0->num_of_homo = static_cast(homo_num); + } else { + assert(homo_num <= 255); + node_cur_ge1->num_of_homo = (unsigned char)homo_num; + } + + for (size_t homo_pos = 0; homo_pos < homo_num; homo_pos++) { + idx_buf[homo_pos] = lemma_arr_[item_start_next + homo_pos].idx_by_hz; + } + +#ifdef ___DO_STATISTICS___ + if (homo_num > max_homobuf_len_[level]) + max_homobuf_len_[level] = homo_num; + + total_homo_num_[level] += homo_num; +#endif + } + + if (i - item_start_next > homo_num) { + void *next_parent; + if (0 == level) + next_parent = static_cast(node_cur_le0); + else + next_parent = static_cast(node_cur_ge1); + construct_subset(next_parent, lemma_arr, + item_start_next + homo_num, i, level + 1); +#ifdef ___DO_STATISTICS___ + + total_node_hasson_[level] += 1; + allson_noson = false; +#endif + } + + // for the next son + lma_last_start = lma_current; + spl_idx_node = spl_idx_current; + item_start_next = i; + homo_num = 0; + if (lma_current->spl_idx_arr[level + 1] == 0) + homo_num = 1; + + son_pos++; + } + } + + // 4. The last one to construct + LmaNodeLE0 *node_cur_le0 = NULL; // only one of them is valid + LmaNodeGE1 *node_cur_ge1 = NULL; + if (0 == level) { + node_cur_le0 = son_1st_le0 + son_pos; + node_cur_le0->spl_idx = spl_idx_node; + node_cur_le0->homo_idx_buf_off = homo_idx_num_eq1_ + homo_idx_num_gt1_; + node_cur_le0->son_1st_off = 0; + homo_idx_num_eq1_ += homo_num; + } else { + node_cur_ge1 = son_1st_ge1 + son_pos; + node_cur_ge1->spl_idx = spl_idx_node; + + set_homo_id_buf_offset(node_cur_ge1, + (homo_idx_num_eq1_ + homo_idx_num_gt1_)); + set_son_offset(node_cur_ge1, 0); + homo_idx_num_gt1_ += homo_num; + } + + if (homo_num > 0) { + LemmaIdType* idx_buf = homo_idx_buf_ + homo_idx_num_eq1_ + + homo_idx_num_gt1_ - homo_num; + if (0 == level) { + assert(homo_num <= 65535); + node_cur_le0->num_of_homo = static_cast(homo_num); + } else { + assert(homo_num <= 255); + node_cur_ge1->num_of_homo = (unsigned char)homo_num; + } + + for (size_t homo_pos = 0; homo_pos < homo_num; homo_pos++) { + idx_buf[homo_pos] = lemma_arr[item_start_next + homo_pos].idx_by_hz; + } + +#ifdef ___DO_STATISTICS___ + if (homo_num > max_homobuf_len_[level]) + max_homobuf_len_[level] = homo_num; + + total_homo_num_[level] += homo_num; +#endif + } + + if (item_end - item_start_next > homo_num) { + void *next_parent; + if (0 == level) + next_parent = static_cast(node_cur_le0); + else + next_parent = static_cast(node_cur_ge1); + construct_subset(next_parent, lemma_arr, + item_start_next + homo_num, item_end, level + 1); +#ifdef ___DO_STATISTICS___ + + total_node_hasson_[level] += 1; + allson_noson = false; +#endif + } + +#ifdef ___DO_STATISTICS___ + if (allson_noson) { + total_sonbuf_allnoson_[level] += 1; + total_node_in_sonbuf_allnoson_[level] += parent_son_num; + } +#endif + + assert(son_pos + 1 == parent_son_num); + return true; + } + +#ifdef ___DO_STATISTICS___ + void DictBuilder::stat_init() { + memset(max_sonbuf_len_, 0, sizeof(size_t) * kMaxLemmaSize); + memset(max_homobuf_len_, 0, sizeof(size_t) * kMaxLemmaSize); + memset(total_son_num_, 0, sizeof(size_t) * kMaxLemmaSize); + memset(total_node_hasson_, 0, sizeof(size_t) * kMaxLemmaSize); + memset(total_sonbuf_num_, 0, sizeof(size_t) * kMaxLemmaSize); + memset(total_sonbuf_allnoson_, 0, sizeof(size_t) * kMaxLemmaSize); + memset(total_node_in_sonbuf_allnoson_, 0, sizeof(size_t) * kMaxLemmaSize); + memset(total_homo_num_, 0, sizeof(size_t) * kMaxLemmaSize); + + sonbufs_num1_ = 0; + sonbufs_numgt1_ = 0; + total_lma_node_num_ = 0; + } + + void DictBuilder::stat_print() { + printf("\n------------STAT INFO-------------\n"); + printf("[root is layer -1]\n"); + printf(".. max_sonbuf_len per layer(from layer 0):\n "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", max_sonbuf_len_[i]); + printf("-, \n"); + + printf(".. max_homobuf_len per layer:\n -, "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", max_homobuf_len_[i]); + printf("\n"); + + printf(".. total_son_num per layer:\n "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", total_son_num_[i]); + printf("-, \n"); + + printf(".. total_node_hasson per layer:\n 1, "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", total_node_hasson_[i]); + printf("\n"); + + printf(".. total_sonbuf_num per layer:\n "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", total_sonbuf_num_[i]); + printf("-, \n"); + + printf(".. total_sonbuf_allnoson per layer:\n "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", total_sonbuf_allnoson_[i]); + printf("-, \n"); + + printf(".. total_node_in_sonbuf_allnoson per layer:\n "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", total_node_in_sonbuf_allnoson_[i]); + printf("-, \n"); + + printf(".. total_homo_num per layer:\n 0, "); + for (size_t i = 0; i < kMaxLemmaSize; i++) + printf("%d, ", total_homo_num_[i]); + printf("\n"); + + printf(".. son buf allocation number with only 1 son: %d\n", sonbufs_num1_); + printf(".. son buf allocation number with more than 1 son: %d\n", + sonbufs_numgt1_); + printf(".. total lemma node number: %d\n", total_lma_node_num_ + 1); + } +#endif // ___DO_STATISTICS___ + +#endif // ___BUILD_MODEL___ +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictbuilder.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictbuilder.h new file mode 100644 index 0000000..219fcd2 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictbuilder.h @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_DICTBUILDER_H__ +#define PINYINIME_INCLUDE_DICTBUILDER_H__ +#include +#include "./utf16char.h" +#include "./dictdef.h" +#include "./dictlist.h" +#include "./spellingtable.h" +#include "./spellingtrie.h" +#include "./splparser.h" +namespace ime_pinyin { +#ifdef ___BUILD_MODEL___ + +#define ___DO_STATISTICS___ + + class DictTrie; + + class DictBuilder { + private: + // The raw lemma array buffer. + LemmaEntry *lemma_arr_; + size_t lemma_num_; + + // Used to store all possible single char items. + // Two items may have the same Hanzi while their spelling ids are different. + SingleCharItem *scis_; + size_t scis_num_; + + // In the tree, root's level is -1. + // Lemma nodes for root, and level 0 + LmaNodeLE0 *lma_nodes_le0_; + + // Lemma nodes for layers whose levels are deeper than 0 + LmaNodeGE1 *lma_nodes_ge1_; + + // Number of used lemma nodes + size_t lma_nds_used_num_le0_; + size_t lma_nds_used_num_ge1_; + + // Used to store homophonies' ids. + LemmaIdType *homo_idx_buf_; + // Number of homophonies each of which only contains one Chinese character. + size_t homo_idx_num_eq1_; + // Number of homophonies each of which contains more than one character. + size_t homo_idx_num_gt1_; + + // The items with highest scores. + LemmaEntry *top_lmas_; + size_t top_lmas_num_; + + SpellingTable *spl_table_; + SpellingParser *spl_parser_; + +#ifdef ___DO_STATISTICS___ + size_t max_sonbuf_len_[kMaxLemmaSize]; + size_t max_homobuf_len_[kMaxLemmaSize]; + + size_t total_son_num_[kMaxLemmaSize]; + size_t total_node_hasson_[kMaxLemmaSize]; + size_t total_sonbuf_num_[kMaxLemmaSize]; + size_t total_sonbuf_allnoson_[kMaxLemmaSize]; + size_t total_node_in_sonbuf_allnoson_[kMaxLemmaSize]; + size_t total_homo_num_[kMaxLemmaSize]; + + size_t sonbufs_num1_; // Number of son buffer with only 1 son + size_t sonbufs_numgt1_; // Number of son buffer with more 1 son; + + size_t total_lma_node_num_; + + void stat_init(); + void stat_print(); +#endif + + public: + + DictBuilder(); + ~DictBuilder(); + + // Build dictionary trie from the file fn_raw. File fn_validhzs provides + // valid chars. If fn_validhzs is NULL, only chars in GB2312 will be + // included. + bool build_dict(const char* fn_raw, const char* fn_validhzs, + DictTrie *dict_trie); + + private: + // Fill in the buffer with id. The caller guarantees that the paramters are + // vaild. + void id_to_charbuf(unsigned char *buf, LemmaIdType id); + + // Update the offset of sons for a node. + void set_son_offset(LmaNodeGE1 *node, size_t offset); + + // Update the offset of homophonies' ids for a node. + void set_homo_id_buf_offset(LmaNodeGE1 *node, size_t offset); + + // Format a speling string. + void format_spelling_str(char *spl_str); + + // Sort the lemma_arr by the hanzi string, and give each of unique items + // a id. Why we need to sort the lemma list according to their Hanzi string + // is to find items started by a given prefix string to do prediction. + // Actually, the single char items are be in other order, for example, + // in spelling id order, etc. + // Return value is next un-allocated idx available. + LemmaIdType sort_lemmas_by_hz(); + + // Build the SingleCharItem list, and fill the hanzi_scis_ids in the + // lemma buffer lemma_arr_. + // This function should be called after the lemma array is ready. + // Return the number of unique SingleCharItem elements. + size_t build_scis(); + + // Construct a subtree using a subset of the spelling array (from + // item_star to item_end) + // parent is the parent node to update the necessary information + // parent can be a member of LmaNodeLE0 or LmaNodeGE1 + bool construct_subset(void* parent, LemmaEntry* lemma_arr, + size_t item_start, size_t item_end, size_t level); + + + // Read valid Chinese Hanzis from the given file. + // num is used to return number of chars. + // The return buffer is sorted and caller needs to free the returned buffer. + char16* read_valid_hanzis(const char *fn_validhzs, size_t *num); + + + // Read a raw dictionary. max_item is the maximum number of items. If there + // are more items in the ditionary, only the first max_item will be read. + // Returned value is the number of items successfully read from the file. + size_t read_raw_dict(const char* fn_raw, const char *fn_validhzs, + size_t max_item); + + // Try to find if a character is in hzs buffer. + bool hz_in_hanzis_list(const char16 *hzs, size_t hzs_len, char16 hz); + + // Try to find if all characters in str are in hzs buffer. + bool str_in_hanzis_list(const char16 *hzs, size_t hzs_len, + const char16 *str, size_t str_len); + + // Get these lemmas with toppest scores. + void get_top_lemmas(); + + // Allocate resource to build dictionary. + // lma_num is the number of items to be loaded + bool alloc_resource(size_t lma_num); + + // Free resource. + void free_resource(); + }; +#endif // ___BUILD_MODEL___ +} +#endif // PINYINIME_INCLUDE_DICTBUILDER_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictdef.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictdef.h new file mode 100644 index 0000000..9a47e22 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictdef.h @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_DICTDEF_H__ +#define PINYINIME_INCLUDE_DICTDEF_H__ +#include +#include "./utf16char.h" +namespace ime_pinyin { + +// Enable the following line when building the binary dictionary model. +// #define ___BUILD_MODEL___ + + typedef unsigned char uint8; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef signed char int8; + typedef short int16; + typedef int int32; + typedef long long int64; + typedef unsigned long long uint64; + const bool kPrintDebug0 = false; + const bool kPrintDebug1 = false; + const bool kPrintDebug2 = false; +// The max length of a lemma. + const size_t kMaxLemmaSize = 8; +// The max length of a Pinyin (spelling). + const size_t kMaxPinyinSize = 6; +// The number of half spelling ids. For Chinese Pinyin, there 30 half ids. +// See SpellingTrie.h for details. + const size_t kHalfSpellingIdNum = 29; +// The maximum number of full spellings. For Chinese Pinyin, there are only +// about 410 spellings. +// If change this value is bigger(needs more bits), please also update +// other structures like SpellingNode, to make sure than a spelling id can be +// stored. +// -1 is because that 0 is never used. + const size_t kMaxSpellingNum = 512 - kHalfSpellingIdNum - 1; + const size_t kMaxSearchSteps = 40; +// One character predicts its following characters. + const size_t kMaxPredictSize = (kMaxLemmaSize - 1); +// LemmaIdType must always be size_t. + typedef size_t LemmaIdType; + const size_t kLemmaIdSize = 3; // Actually, a Id occupies 3 bytes in storage. + const size_t kLemmaIdComposing = 0xffffff; + typedef uint16 LmaScoreType; + typedef uint16 KeyScoreType; +// Number of items with highest score are kept for prediction purpose. + const size_t kTopScoreLemmaNum = 10; + const size_t kMaxPredictNumByGt3 = 1; + const size_t kMaxPredictNumBy3 = 2; + const size_t kMaxPredictNumBy2 = 2; +// The last lemma id (included) for the system dictionary. The system +// dictionary's ids always start from 1. + const LemmaIdType kSysDictIdEnd = 500000; +// The first lemma id for the user dictionary. + const LemmaIdType kUserDictIdStart = 500001; +// The last lemma id (included) for the user dictionary. + const LemmaIdType kUserDictIdEnd = 600000; + typedef struct { + uint16 half_splid: 5; + uint16 full_splid: 11; + } SpellingId, *PSpellingId; +/** + * We use different node types for different layers + * Statistical data of the building result for a testing dictionary: + * root, level 0, level 1, level 2, level 3 + * max son num of one node: 406 280 41 2 - + * max homo num of one node: 0 90 23 2 2 + * total node num of a layer: 1 406 31766 13516 993 + * total homo num of a layer: 9 5674 44609 12667 995 + * + * The node number for root and level 0 won't be larger than 500 + * According to the information above, two kinds of nodes can be used; one for + * root and level 0, the other for these layers deeper than 0. + * + * LE = less and equal, + * A node occupies 16 bytes. so, totallly less than 16 * 500 = 8K + */ + struct LmaNodeLE0 { + uint32 son_1st_off; + uint32 homo_idx_buf_off; + uint16 spl_idx; + uint16 num_of_son; + uint16 num_of_homo; + }; +/** + * GE = great and equal + * A node occupies 8 bytes. + */ + struct LmaNodeGE1 { + uint16 son_1st_off_l; // Low bits of the son_1st_off + uint16 homo_idx_buf_off_l; // Low bits of the homo_idx_buf_off_1 + uint16 spl_idx; + unsigned char num_of_son; // number of son nodes + unsigned char num_of_homo; // number of homo words + unsigned char son_1st_off_h; // high bits of the son_1st_off + unsigned char homo_idx_buf_off_h; // high bits of the homo_idx_buf_off + }; +#ifdef ___BUILD_MODEL___ + struct SingleCharItem { + float freq; + char16 hz; + SpellingId splid; + }; + + struct LemmaEntry { + LemmaIdType idx_by_py; + LemmaIdType idx_by_hz; + char16 hanzi_str[kMaxLemmaSize + 1]; + + // The SingleCharItem id for each Hanzi. + uint16 hanzi_scis_ids[kMaxLemmaSize]; + + uint16 spl_idx_arr[kMaxLemmaSize + 1]; + char pinyin_str[kMaxLemmaSize][kMaxPinyinSize + 1]; + unsigned char hz_str_len; + float freq; + }; +#endif // ___BUILD_MODEL___ +} // namespace ime_pinyin + +#endif // PINYINIME_INCLUDE_DICTDEF_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictlist.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictlist.cpp new file mode 100644 index 0000000..4f33e72 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictlist.cpp @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "dictlist.h" +#include "mystdlib.h" +#include "ngram.h" +#include "searchutility.h" +namespace ime_pinyin { + DictList::DictList() { + initialized_ = false; + scis_num_ = 0; + scis_hz_ = NULL; + scis_splid_ = NULL; + buf_ = NULL; + spl_trie_ = SpellingTrie::get_cpinstance(); + assert(kMaxLemmaSize == 8); + cmp_func_[0] = cmp_hanzis_1; + cmp_func_[1] = cmp_hanzis_2; + cmp_func_[2] = cmp_hanzis_3; + cmp_func_[3] = cmp_hanzis_4; + cmp_func_[4] = cmp_hanzis_5; + cmp_func_[5] = cmp_hanzis_6; + cmp_func_[6] = cmp_hanzis_7; + cmp_func_[7] = cmp_hanzis_8; + } + DictList::~DictList() { + free_resource(); + } + bool DictList::alloc_resource(size_t buf_size, size_t scis_num) { + // Allocate memory + buf_ = static_cast(malloc(buf_size * sizeof(char16))); + if (NULL == buf_) + return false; + scis_num_ = scis_num; + scis_hz_ = static_cast(malloc(scis_num_ * sizeof(char16))); + if (NULL == scis_hz_) + return false; + scis_splid_ = static_cast + (malloc(scis_num_ * sizeof(SpellingId))); + if (NULL == scis_splid_) + return false; + return true; + } + void DictList::free_resource() { + if (NULL != buf_) + free(buf_); + buf_ = NULL; + if (NULL != scis_hz_) + free(scis_hz_); + scis_hz_ = NULL; + if (NULL != scis_splid_) + free(scis_splid_); + scis_splid_ = NULL; + } +#ifdef ___BUILD_MODEL___ + bool DictList::init_list(const SingleCharItem *scis, size_t scis_num, + const LemmaEntry *lemma_arr, size_t lemma_num) { + if (NULL == scis || 0 == scis_num || NULL == lemma_arr || 0 == lemma_num) + return false; + + initialized_ = false; + + if (NULL != buf_) + free(buf_); + + // calculate the size + size_t buf_size = calculate_size(lemma_arr, lemma_num); + if (0 == buf_size) + return false; + + if (!alloc_resource(buf_size, scis_num)) + return false; + + fill_scis(scis, scis_num); + + // Copy the related content from the array to inner buffer + fill_list(lemma_arr, lemma_num); + + initialized_ = true; + return true; + } + + size_t DictList::calculate_size(const LemmaEntry* lemma_arr, size_t lemma_num) { + size_t last_hz_len = 0; + size_t list_size = 0; + size_t id_num = 0; + + for (size_t i = 0; i < lemma_num; i++) { + if (0 == i) { + last_hz_len = lemma_arr[i].hz_str_len; + + assert(last_hz_len > 0); + assert(lemma_arr[0].idx_by_hz == 1); + + id_num++; + start_pos_[0] = 0; + start_id_[0] = id_num; + + last_hz_len = 1; + list_size += last_hz_len; + } else { + size_t current_hz_len = lemma_arr[i].hz_str_len; + + assert(current_hz_len >= last_hz_len); + + if (current_hz_len == last_hz_len) { + list_size += current_hz_len; + id_num++; + } else { + for (size_t len = last_hz_len; len < current_hz_len - 1; len++) { + start_pos_[len] = start_pos_[len - 1]; + start_id_[len] = start_id_[len - 1]; + } + + start_pos_[current_hz_len - 1] = list_size; + + id_num++; + start_id_[current_hz_len - 1] = id_num; + + last_hz_len = current_hz_len; + list_size += current_hz_len; + } + } + } + + for (size_t i = last_hz_len; i <= kMaxLemmaSize; i++) { + if (0 == i) { + start_pos_[0] = 0; + start_id_[0] = 1; + } else { + start_pos_[i] = list_size; + start_id_[i] = id_num; + } + } + + return start_pos_[kMaxLemmaSize]; + } + + void DictList::fill_scis(const SingleCharItem *scis, size_t scis_num) { + assert(scis_num_ == scis_num); + + for (size_t pos = 0; pos < scis_num_; pos++) { + scis_hz_[pos] = scis[pos].hz; + scis_splid_[pos] = scis[pos].splid; + } + } + + void DictList::fill_list(const LemmaEntry* lemma_arr, size_t lemma_num) { + size_t current_pos = 0; + + utf16_strncpy(buf_, lemma_arr[0].hanzi_str, + lemma_arr[0].hz_str_len); + + current_pos = lemma_arr[0].hz_str_len; + + size_t id_num = 1; + + for (size_t i = 1; i < lemma_num; i++) { + utf16_strncpy(buf_ + current_pos, lemma_arr[i].hanzi_str, + lemma_arr[i].hz_str_len); + + id_num++; + current_pos += lemma_arr[i].hz_str_len; + } + + assert(current_pos == start_pos_[kMaxLemmaSize]); + assert(id_num == start_id_[kMaxLemmaSize]); + } + + char16* DictList::find_pos2_startedbyhz(char16 hz_char) { + char16 *found_2w = static_cast + (mybsearch(&hz_char, buf_ + start_pos_[1], + (start_pos_[2] - start_pos_[1]) / 2, + sizeof(char16) * 2, cmp_hanzis_1)); + if (NULL == found_2w) + return NULL; + + while (found_2w > buf_ + start_pos_[1] && *found_2w == *(found_2w - 1)) + found_2w -= 2; + + return found_2w; + } +#endif // ___BUILD_MODEL___ + char16 *DictList::find_pos_startedbyhzs(const char16 last_hzs[], + size_t word_len, int (*cmp_func)(const void *, const void *)) { + char16 *found_w = static_cast + (mybsearch(last_hzs, buf_ + start_pos_[word_len - 1], + (start_pos_[word_len] - start_pos_[word_len - 1]) + / word_len, + sizeof(char16) * word_len, cmp_func)); + if (NULL == found_w) + return NULL; + while (found_w > buf_ + start_pos_[word_len - 1] && + cmp_func(found_w, found_w - word_len) == 0) + found_w -= word_len; + return found_w; + } + size_t DictList::predict(const char16 last_hzs[], uint16 hzs_len, + NPredictItem *npre_items, size_t npre_max, + size_t b4_used) { + assert(hzs_len <= kMaxPredictSize && hzs_len > 0); + + // 1. Prepare work + int (*cmp_func)(const void *, const void *) = cmp_func_[hzs_len - 1]; + NGram &ngram = NGram::get_instance(); + size_t item_num = 0; + + // 2. Do prediction + for (uint16 pre_len = 1; pre_len <= kMaxPredictSize + 1 - hzs_len; + pre_len++) { + uint16 word_len = hzs_len + pre_len; + char16 *w_buf = find_pos_startedbyhzs(last_hzs, word_len, cmp_func); + if (NULL == w_buf) + continue; + while (w_buf < buf_ + start_pos_[word_len] && + cmp_func(w_buf, last_hzs) == 0 && + item_num < npre_max) { + memset(npre_items + item_num, 0, sizeof(NPredictItem)); + utf16_strncpy(npre_items[item_num].pre_hzs, w_buf + hzs_len, pre_len); + npre_items[item_num].psb = + ngram.get_uni_psb((size_t) (w_buf - buf_ - start_pos_[word_len - 1]) + / word_len + start_id_[word_len - 1]); + npre_items[item_num].his_len = hzs_len; + item_num++; + w_buf += word_len; + } + } + size_t new_num = 0; + for (size_t i = 0; i < item_num; i++) { + // Try to find it in the existing items + size_t e_pos; + for (e_pos = 1; e_pos <= b4_used; e_pos++) { + if (utf16_strncmp((*(npre_items - e_pos)).pre_hzs, npre_items[i].pre_hzs, + kMaxPredictSize) == 0) + break; + } + if (e_pos <= b4_used) + continue; + + // If not found, append it to the buffer + npre_items[new_num] = npre_items[i]; + new_num++; + } + return new_num; + } + uint16 DictList::get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, + uint16 str_max) { + if (!initialized_ || id_lemma >= start_id_[kMaxLemmaSize] || NULL == str_buf + || str_max <= 1) + return 0; + + // Find the range + for (uint16 i = 0; i < kMaxLemmaSize; i++) { + if (i + 1 > str_max - 1) + return 0; + if (start_id_[i] <= id_lemma && start_id_[i + 1] > id_lemma) { + size_t id_span = id_lemma - start_id_[i]; + uint16 *buf = buf_ + start_pos_[i] + id_span * (i + 1); + for (uint16 len = 0; len <= i; len++) { + str_buf[len] = buf[len]; + } + str_buf[i + 1] = (char16) '\0'; + return i + 1; + } + } + return 0; + } + uint16 DictList::get_splids_for_hanzi(char16 hanzi, uint16 half_splid, + uint16 *splids, uint16 max_splids) { + char16 *hz_found = static_cast + (mybsearch(&hanzi, scis_hz_, scis_num_, sizeof(char16), cmp_hanzis_1)); + assert(NULL != hz_found && hanzi == *hz_found); + + // Move to the first one. + while (hz_found > scis_hz_ && hanzi == *(hz_found - 1)) + hz_found--; + + // First try to found if strict comparison result is not zero. + char16 *hz_f = hz_found; + bool strict = false; + while (hz_f < scis_hz_ + scis_num_ && hanzi == *hz_f) { + uint16 pos = hz_f - scis_hz_; + if (0 == half_splid || scis_splid_[pos].half_splid == half_splid) { + strict = true; + } + hz_f++; + } + uint16 found_num = 0; + while (hz_found < scis_hz_ + scis_num_ && hanzi == *hz_found) { + uint16 pos = hz_found - scis_hz_; + if (0 == half_splid || + (strict && scis_splid_[pos].half_splid == half_splid) || + (!strict && spl_trie_->half_full_compatible(half_splid, + scis_splid_[pos].full_splid))) { + assert(found_num + 1 < max_splids); + splids[found_num] = scis_splid_[pos].full_splid; + found_num++; + } + hz_found++; + } + return found_num; + } + LemmaIdType DictList::get_lemma_id(const char16 *str, uint16 str_len) { + if (NULL == str || str_len > kMaxLemmaSize) + return 0; + char16 *found = find_pos_startedbyhzs(str, str_len, cmp_func_[str_len - 1]); + if (NULL == found) + return 0; + assert(found > buf_); + assert(static_cast(found - buf_) >= start_pos_[str_len - 1]); + return static_cast + (start_id_[str_len - 1] + + (found - buf_ - start_pos_[str_len - 1]) / str_len); + } + void DictList::convert_to_hanzis(char16 *str, uint16 str_len) { + assert(NULL != str); + for (uint16 str_pos = 0; str_pos < str_len; str_pos++) { + str[str_pos] = scis_hz_[str[str_pos]]; + } + } + void DictList::convert_to_scis_ids(char16 *str, uint16 str_len) { + assert(NULL != str); + for (uint16 str_pos = 0; str_pos < str_len; str_pos++) { + str[str_pos] = 0x100; + } + } + bool DictList::save_list(FILE *fp) { + if (!initialized_ || NULL == fp) + return false; + if (NULL == buf_ || 0 == start_pos_[kMaxLemmaSize] || + NULL == scis_hz_ || NULL == scis_splid_ || 0 == scis_num_) + return false; + if (fwrite(&scis_num_, sizeof(uint32), 1, fp) != 1) + return false; + if (fwrite(start_pos_, sizeof(uint32), kMaxLemmaSize + 1, fp) != + kMaxLemmaSize + 1) + return false; + if (fwrite(start_id_, sizeof(uint32), kMaxLemmaSize + 1, fp) != + kMaxLemmaSize + 1) + return false; + if (fwrite(scis_hz_, sizeof(char16), scis_num_, fp) != scis_num_) + return false; + if (fwrite(scis_splid_, sizeof(SpellingId), scis_num_, fp) != scis_num_) + return false; + if (fwrite(buf_, sizeof(char16), start_pos_[kMaxLemmaSize], fp) != + start_pos_[kMaxLemmaSize]) + return false; + return true; + } + bool DictList::load_list(FILE *fp) { + if (NULL == fp) + return false; + initialized_ = false; + if (fread(&scis_num_, sizeof(uint32), 1, fp) != 1) + return false; + if (fread(start_pos_, sizeof(uint32), kMaxLemmaSize + 1, fp) != + kMaxLemmaSize + 1) + return false; + if (fread(start_id_, sizeof(uint32), kMaxLemmaSize + 1, fp) != + kMaxLemmaSize + 1) + return false; + free_resource(); + if (!alloc_resource(start_pos_[kMaxLemmaSize], scis_num_)) + return false; + if (fread(scis_hz_, sizeof(char16), scis_num_, fp) != scis_num_) + return false; + if (fread(scis_splid_, sizeof(SpellingId), scis_num_, fp) != scis_num_) + return false; + if (fread(buf_, sizeof(char16), start_pos_[kMaxLemmaSize], fp) != + start_pos_[kMaxLemmaSize]) + return false; + initialized_ = true; + return true; + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictlist.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictlist.h new file mode 100644 index 0000000..08b1cba --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dictlist.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_DICTLIST_H__ +#define PINYINIME_INCLUDE_DICTLIST_H__ +#include +#include +#include "./dictdef.h" +#include "./searchutility.h" +#include "./spellingtrie.h" +#include "./utf16char.h" +namespace ime_pinyin { + class DictList { + private: + bool initialized_; + const SpellingTrie *spl_trie_; + // Number of SingCharItem. The first is blank, because id 0 is invalid. + uint32 scis_num_; + char16 *scis_hz_; + SpellingId *scis_splid_; + // The large memory block to store the word list. + char16 *buf_; + // Starting position of those words whose lengths are i+1, counted in + // char16 + uint32 start_pos_[kMaxLemmaSize + 1]; + uint32 start_id_[kMaxLemmaSize + 1]; + int (*cmp_func_[kMaxLemmaSize])(const void *, const void *); + bool alloc_resource(size_t buf_size, size_t scim_num); + void free_resource(); +#ifdef ___BUILD_MODEL___ + // Calculate the requsted memory, including the start_pos[] buffer. + size_t calculate_size(const LemmaEntry *lemma_arr, size_t lemma_num); + + void fill_scis(const SingleCharItem *scis, size_t scis_num); + + // Copy the related content to the inner buffer + // It should be called after calculate_size() + void fill_list(const LemmaEntry *lemma_arr, size_t lemma_num); + + // Find the starting position for the buffer of those 2-character Chinese word + // whose first character is the given Chinese character. + char16* find_pos2_startedbyhz(char16 hz_char); +#endif + // Find the starting position for the buffer of those words whose lengths are + // word_len. The given parameter cmp_func decides how many characters from + // beginning will be used to compare. + char16 *find_pos_startedbyhzs(const char16 last_hzs[], + size_t word_Len, + int (*cmp_func)(const void *, const void *)); + public: + DictList(); + ~DictList(); + bool save_list(FILE *fp); + bool load_list(FILE *fp); +#ifdef ___BUILD_MODEL___ + // Init the list from the LemmaEntry array. + // lemma_arr should have been sorted by the hanzi_str, and have been given + // ids from 1 + bool init_list(const SingleCharItem *scis, size_t scis_num, + const LemmaEntry *lemma_arr, size_t lemma_num); +#endif + // Get the hanzi string for the given id + uint16 get_lemma_str(LemmaIdType id_hz, char16 *str_buf, uint16 str_max); + void convert_to_hanzis(char16 *str, uint16 str_len); + void convert_to_scis_ids(char16 *str, uint16 str_len); + // last_hzs stores the last n Chinese characters history, its length should be + // less or equal than kMaxPredictSize. + // hzs_len specifies the length(<= kMaxPredictSize). + // predict_buf is used to store the result. + // buf_len specifies the buffer length. + // b4_used specifies how many items before predict_buf have been used. + // Returned value is the number of newly added items. + size_t predict(const char16 last_hzs[], uint16 hzs_len, + NPredictItem *npre_items, size_t npre_max, + size_t b4_used); + // If half_splid is a valid half spelling id, return those full spelling + // ids which share this half id. + uint16 get_splids_for_hanzi(char16 hanzi, uint16 half_splid, + uint16 *splids, uint16 max_splids); + LemmaIdType get_lemma_id(const char16 *str, uint16 str_len); + }; +} +#endif // PINYINIME_INCLUDE_DICTLIST_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dicttrie.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dicttrie.cpp new file mode 100644 index 0000000..e61cdb8 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dicttrie.cpp @@ -0,0 +1,837 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "dicttrie.h" +#include "dictbuilder.h" +#include "lpicache.h" +#include "mystdlib.h" +#include "ngram.h" +namespace ime_pinyin { + DictTrie::DictTrie() { + spl_trie_ = SpellingTrie::get_cpinstance(); + root_ = NULL; + splid_le0_index_ = NULL; + lma_node_num_le0_ = 0; + nodes_ge1_ = NULL; + lma_node_num_ge1_ = 0; + lma_idx_buf_ = NULL; + lma_idx_buf_len_ = 0; + total_lma_num_ = 0; + top_lmas_num_ = 0; + dict_list_ = NULL; + parsing_marks_ = NULL; + mile_stones_ = NULL; + reset_milestones(0, kFirstValidMileStoneHandle); + } + DictTrie::~DictTrie() { + free_resource(true); + } + void DictTrie::free_resource(bool free_dict_list) { + if (NULL != root_) + free(root_); + root_ = NULL; + if (NULL != splid_le0_index_) + free(splid_le0_index_); + splid_le0_index_ = NULL; + if (NULL != nodes_ge1_) + free(nodes_ge1_); + nodes_ge1_ = NULL; + if (NULL != lma_idx_buf_) + free(lma_idx_buf_); + lma_idx_buf_ = NULL; + if (free_dict_list) { + if (NULL != dict_list_) { + delete dict_list_; + } + dict_list_ = NULL; + } + if (parsing_marks_) + delete[] parsing_marks_; + parsing_marks_ = NULL; + if (mile_stones_) + delete[] mile_stones_; + mile_stones_ = NULL; + reset_milestones(0, kFirstValidMileStoneHandle); + } + inline size_t DictTrie::get_son_offset(const LmaNodeGE1 *node) { + return ((size_t) node->son_1st_off_l + ((size_t) node->son_1st_off_h << 16)); + } + inline size_t DictTrie::get_homo_idx_buf_offset(const LmaNodeGE1 *node) { + return ((size_t) node->homo_idx_buf_off_l + + ((size_t) node->homo_idx_buf_off_h << 16)); + } + inline LemmaIdType DictTrie::get_lemma_id(size_t id_offset) { + LemmaIdType id = 0; + for (uint16 pos = kLemmaIdSize - 1; pos > 0; pos--) + id = (id << 8) + lma_idx_buf_[id_offset * kLemmaIdSize + pos]; + id = (id << 8) + lma_idx_buf_[id_offset * kLemmaIdSize]; + return id; + } +#ifdef ___BUILD_MODEL___ + bool DictTrie::build_dict(const char* fn_raw, const char* fn_validhzs) { + DictBuilder* dict_builder = new DictBuilder(); + + free_resource(true); + + return dict_builder->build_dict(fn_raw, fn_validhzs, this); + } + + bool DictTrie::save_dict(FILE *fp) { + if (NULL == fp) + return false; + + if (fwrite(&lma_node_num_le0_, sizeof(uint32), 1, fp) != 1) + return false; + + if (fwrite(&lma_node_num_ge1_, sizeof(uint32), 1, fp) != 1) + return false; + + if (fwrite(&lma_idx_buf_len_, sizeof(uint32), 1, fp) != 1) + return false; + + if (fwrite(&top_lmas_num_, sizeof(uint32), 1, fp) != 1) + return false; + + if (fwrite(root_, sizeof(LmaNodeLE0), lma_node_num_le0_, fp) + != lma_node_num_le0_) + return false; + + if (fwrite(nodes_ge1_, sizeof(LmaNodeGE1), lma_node_num_ge1_, fp) + != lma_node_num_ge1_) + return false; + + if (fwrite(lma_idx_buf_, sizeof(unsigned char), lma_idx_buf_len_, fp) != + lma_idx_buf_len_) + return false; + + return true; + } + + bool DictTrie::save_dict(const char *filename) { + if (NULL == filename) + return false; + + if (NULL == root_ || NULL == dict_list_) + return false; + + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + NGram &ngram = NGram::get_instance(); + + FILE *fp = fopen(filename, "wb"); + if (NULL == fp) + return false; + + if (!spl_trie.save_spl_trie(fp) || !dict_list_->save_list(fp) || + !save_dict(fp) || !ngram.save_ngram(fp)) { + fclose(fp); + return false; + } + + fclose(fp); + return true; + } +#endif // ___BUILD_MODEL___ + bool DictTrie::load_dict(FILE *fp) { + if (NULL == fp) + return false; + if (fread(&lma_node_num_le0_, sizeof(uint32), 1, fp) != 1) + return false; + if (fread(&lma_node_num_ge1_, sizeof(uint32), 1, fp) != 1) + return false; + if (fread(&lma_idx_buf_len_, sizeof(uint32), 1, fp) != 1) + return false; + if (fread(&top_lmas_num_, sizeof(uint32), 1, fp) != 1 || + top_lmas_num_ >= lma_idx_buf_len_) + return false; + free_resource(false); + root_ = static_cast + (malloc(lma_node_num_le0_ * sizeof(LmaNodeLE0))); + nodes_ge1_ = static_cast + (malloc(lma_node_num_ge1_ * sizeof(LmaNodeGE1))); + lma_idx_buf_ = (unsigned char *) malloc(lma_idx_buf_len_); + total_lma_num_ = lma_idx_buf_len_ / kLemmaIdSize; + size_t buf_size = SpellingTrie::get_instance().get_spelling_num() + 1; + assert(lma_node_num_le0_ <= buf_size); + splid_le0_index_ = static_cast(malloc(buf_size * sizeof(uint16))); + + // Init the space for parsing. + parsing_marks_ = new ParsingMark[kMaxParsingMark]; + mile_stones_ = new MileStone[kMaxMileStone]; + reset_milestones(0, kFirstValidMileStoneHandle); + if (NULL == root_ || NULL == nodes_ge1_ || NULL == lma_idx_buf_ || + NULL == splid_le0_index_ || NULL == parsing_marks_ || + NULL == mile_stones_) { + free_resource(false); + return false; + } + if (fread(root_, sizeof(LmaNodeLE0), lma_node_num_le0_, fp) + != lma_node_num_le0_) + return false; + if (fread(nodes_ge1_, sizeof(LmaNodeGE1), lma_node_num_ge1_, fp) + != lma_node_num_ge1_) + return false; + if (fread(lma_idx_buf_, sizeof(unsigned char), lma_idx_buf_len_, fp) != + lma_idx_buf_len_) + return false; + + // The quick index for the first level sons + uint16 last_splid = kFullSplIdStart; + size_t last_pos = 0; + for (size_t i = 1; i < lma_node_num_le0_; i++) { + for (uint16 splid = last_splid; splid < root_[i].spl_idx; splid++) + splid_le0_index_[splid - kFullSplIdStart] = last_pos; + splid_le0_index_[root_[i].spl_idx - kFullSplIdStart] = + static_cast(i); + last_splid = root_[i].spl_idx; + last_pos = i; + } + for (uint16 splid = last_splid + 1; + splid < buf_size + kFullSplIdStart; splid++) { + assert(static_cast(splid - kFullSplIdStart) < buf_size); + splid_le0_index_[splid - kFullSplIdStart] = last_pos + 1; + } + return true; + } + bool DictTrie::load_dict(const char *filename, LemmaIdType start_id, + LemmaIdType end_id) { + if (NULL == filename || end_id <= start_id) + return false; + FILE *fp = fopen(filename, "rb"); + if (NULL == fp) + return false; + free_resource(true); + dict_list_ = new DictList(); + if (NULL == dict_list_) { + fclose(fp); + return false; + } + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + NGram &ngram = NGram::get_instance(); + if (!spl_trie.load_spl_trie(fp) || !dict_list_->load_list(fp) || + !load_dict(fp) || !ngram.load_ngram(fp) || + total_lma_num_ > end_id - start_id + 1) { + free_resource(true); + fclose(fp); + return false; + } + fclose(fp); + return true; + } + bool DictTrie::load_dict_fd(int sys_fd, long start_offset, + long length, LemmaIdType start_id, + LemmaIdType end_id) { + if (start_offset < 0 || length <= 0 || end_id <= start_id) + return false; + FILE *fp = fdopen(sys_fd, "rb"); + if (NULL == fp) + return false; + if (-1 == fseek(fp, start_offset, SEEK_SET)) { + fclose(fp); + return false; + } + free_resource(true); + dict_list_ = new DictList(); + if (NULL == dict_list_) { + fclose(fp); + return false; + } + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + NGram &ngram = NGram::get_instance(); + if (!spl_trie.load_spl_trie(fp) || !dict_list_->load_list(fp) || + !load_dict(fp) || !ngram.load_ngram(fp) || + ftell(fp) < start_offset + length || + total_lma_num_ > end_id - start_id + 1) { + free_resource(true); + fclose(fp); + return false; + } + fclose(fp); + return true; + } + size_t DictTrie::fill_lpi_buffer(LmaPsbItem lpi_items[], size_t lpi_max, + LmaNodeLE0 *node) { + size_t lpi_num = 0; + NGram &ngram = NGram::get_instance(); + for (size_t homo = 0; homo < (size_t) node->num_of_homo; homo++) { + lpi_items[lpi_num].id = get_lemma_id(node->homo_idx_buf_off + + homo); + lpi_items[lpi_num].lma_len = 1; + lpi_items[lpi_num].psb = + static_cast(ngram.get_uni_psb(lpi_items[lpi_num].id)); + lpi_num++; + if (lpi_num >= lpi_max) + break; + } + return lpi_num; + } + size_t DictTrie::fill_lpi_buffer(LmaPsbItem lpi_items[], size_t lpi_max, + size_t homo_buf_off, LmaNodeGE1 *node, + uint16 lma_len) { + size_t lpi_num = 0; + NGram &ngram = NGram::get_instance(); + for (size_t homo = 0; homo < (size_t) node->num_of_homo; homo++) { + lpi_items[lpi_num].id = get_lemma_id(homo_buf_off + homo); + lpi_items[lpi_num].lma_len = lma_len; + lpi_items[lpi_num].psb = + static_cast(ngram.get_uni_psb(lpi_items[lpi_num].id)); + lpi_num++; + if (lpi_num >= lpi_max) + break; + } + return lpi_num; + } + void DictTrie::reset_milestones(uint16 from_step, MileStoneHandle from_handle) { + if (0 == from_step) { + parsing_marks_pos_ = 0; + mile_stones_pos_ = kFirstValidMileStoneHandle; + } else { + if (from_handle > 0 && from_handle < mile_stones_pos_) { + mile_stones_pos_ = from_handle; + MileStone *mile_stone = mile_stones_ + from_handle; + parsing_marks_pos_ = mile_stone->mark_start; + } + } + } + MileStoneHandle DictTrie::extend_dict(MileStoneHandle from_handle, + const DictExtPara *dep, + LmaPsbItem *lpi_items, size_t lpi_max, + size_t *lpi_num) { + if (NULL == dep) + return 0; + + // from LmaNodeLE0 (root) to LmaNodeLE0 + if (0 == from_handle) { + assert(0 == dep->splids_extended); + return extend_dict0(from_handle, dep, lpi_items, lpi_max, lpi_num); + } + + // from LmaNodeLE0 to LmaNodeGE1 + if (1 == dep->splids_extended) + return extend_dict1(from_handle, dep, lpi_items, lpi_max, lpi_num); + + // From LmaNodeGE1 to LmaNodeGE1 + return extend_dict2(from_handle, dep, lpi_items, lpi_max, lpi_num); + } + MileStoneHandle DictTrie::extend_dict0(MileStoneHandle from_handle, + const DictExtPara *dep, + LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num) { + assert(NULL != dep && 0 == from_handle); + *lpi_num = 0; + MileStoneHandle ret_handle = 0; + uint16 splid = dep->splids[dep->splids_extended]; + uint16 id_start = dep->id_start; + uint16 id_num = dep->id_num; + LpiCache &lpi_cache = LpiCache::get_instance(); + bool cached = lpi_cache.is_cached(splid); + + // 2. Begin exgtending + // 2.1 Get the LmaPsbItem list + LmaNodeLE0 *node = root_; + size_t son_start = splid_le0_index_[id_start - kFullSplIdStart]; + size_t son_end = splid_le0_index_[id_start + id_num - kFullSplIdStart]; + for (size_t son_pos = son_start; son_pos < son_end; son_pos++) { + assert(1 == node->son_1st_off); + LmaNodeLE0 *son = root_ + son_pos; + assert(son->spl_idx >= id_start && son->spl_idx < id_start + id_num); + if (!cached && *lpi_num < lpi_max) { + bool need_lpi = true; + if (spl_trie_->is_half_id_yunmu(splid) && son_pos != son_start) + need_lpi = false; + if (need_lpi) + *lpi_num += fill_lpi_buffer(lpi_items + (*lpi_num), + lpi_max - *lpi_num, son); + } + + // If necessary, fill in a new mile stone. + if (son->spl_idx == id_start) { + if (mile_stones_pos_ < kMaxMileStone && + parsing_marks_pos_ < kMaxParsingMark) { + parsing_marks_[parsing_marks_pos_].node_offset = son_pos; + parsing_marks_[parsing_marks_pos_].node_num = id_num; + mile_stones_[mile_stones_pos_].mark_start = parsing_marks_pos_; + mile_stones_[mile_stones_pos_].mark_num = 1; + ret_handle = mile_stones_pos_; + parsing_marks_pos_++; + mile_stones_pos_++; + } + } + if (son->spl_idx >= id_start + id_num - 1) + break; + } + + // printf("----- parsing marks: %d, mile stone: %d \n", parsing_marks_pos_, + // mile_stones_pos_); + return ret_handle; + } + MileStoneHandle DictTrie::extend_dict1(MileStoneHandle from_handle, + const DictExtPara *dep, + LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num) { + assert(NULL != dep && from_handle > 0 && from_handle < mile_stones_pos_); + MileStoneHandle ret_handle = 0; + + // 1. If this is a half Id, get its corresponding full starting Id and + // number of full Id. + size_t ret_val = 0; + uint16 id_start = dep->id_start; + uint16 id_num = dep->id_num; + + // 2. Begin extending. + MileStone *mile_stone = mile_stones_ + from_handle; + for (uint16 h_pos = 0; h_pos < mile_stone->mark_num; h_pos++) { + ParsingMark p_mark = parsing_marks_[mile_stone->mark_start + h_pos]; + uint16 ext_num = p_mark.node_num; + for (uint16 ext_pos = 0; ext_pos < ext_num; ext_pos++) { + LmaNodeLE0 *node = root_ + p_mark.node_offset + ext_pos; + size_t found_start = 0; + size_t found_num = 0; + for (size_t son_pos = 0; son_pos < (size_t) node->num_of_son; son_pos++) { + assert(node->son_1st_off <= lma_node_num_ge1_); + LmaNodeGE1 *son = nodes_ge1_ + node->son_1st_off + son_pos; + if (son->spl_idx >= id_start + && son->spl_idx < id_start + id_num) { + if (*lpi_num < lpi_max) { + size_t homo_buf_off = get_homo_idx_buf_offset(son); + *lpi_num += fill_lpi_buffer(lpi_items + (*lpi_num), + lpi_max - *lpi_num, homo_buf_off, son, + 2); + } + + // If necessary, fill in the new DTMI + if (0 == found_num) { + found_start = son_pos; + } + found_num++; + } + if (son->spl_idx >= id_start + id_num - 1 || son_pos == + (size_t) node->num_of_son - 1) { + if (found_num > 0) { + if (mile_stones_pos_ < kMaxMileStone && + parsing_marks_pos_ < kMaxParsingMark) { + parsing_marks_[parsing_marks_pos_].node_offset = + node->son_1st_off + found_start; + parsing_marks_[parsing_marks_pos_].node_num = found_num; + if (0 == ret_val) + mile_stones_[mile_stones_pos_].mark_start = + parsing_marks_pos_; + parsing_marks_pos_++; + } + ret_val++; + } + break; + } // for son_pos + } // for ext_pos + } // for h_pos + } + if (ret_val > 0) { + mile_stones_[mile_stones_pos_].mark_num = ret_val; + ret_handle = mile_stones_pos_; + mile_stones_pos_++; + ret_val = 1; + } + + // printf("----- parsing marks: %d, mile stone: %d \n", parsing_marks_pos_, + // mile_stones_pos_); + return ret_handle; + } + MileStoneHandle DictTrie::extend_dict2(MileStoneHandle from_handle, + const DictExtPara *dep, + LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num) { + assert(NULL != dep && from_handle > 0 && from_handle < mile_stones_pos_); + MileStoneHandle ret_handle = 0; + + // 1. If this is a half Id, get its corresponding full starting Id and + // number of full Id. + size_t ret_val = 0; + uint16 id_start = dep->id_start; + uint16 id_num = dep->id_num; + + // 2. Begin extending. + MileStone *mile_stone = mile_stones_ + from_handle; + for (uint16 h_pos = 0; h_pos < mile_stone->mark_num; h_pos++) { + ParsingMark p_mark = parsing_marks_[mile_stone->mark_start + h_pos]; + uint16 ext_num = p_mark.node_num; + for (uint16 ext_pos = 0; ext_pos < ext_num; ext_pos++) { + LmaNodeGE1 *node = nodes_ge1_ + p_mark.node_offset + ext_pos; + size_t found_start = 0; + size_t found_num = 0; + for (size_t son_pos = 0; son_pos < (size_t) node->num_of_son; son_pos++) { + assert(node->son_1st_off_l > 0 || node->son_1st_off_h > 0); + LmaNodeGE1 *son = nodes_ge1_ + get_son_offset(node) + son_pos; + if (son->spl_idx >= id_start + && son->spl_idx < id_start + id_num) { + if (*lpi_num < lpi_max) { + size_t homo_buf_off = get_homo_idx_buf_offset(son); + *lpi_num += fill_lpi_buffer(lpi_items + (*lpi_num), + lpi_max - *lpi_num, homo_buf_off, son, + dep->splids_extended + 1); + } + + // If necessary, fill in the new DTMI + if (0 == found_num) { + found_start = son_pos; + } + found_num++; + } + if (son->spl_idx >= id_start + id_num - 1 || son_pos == + (size_t) node->num_of_son - 1) { + if (found_num > 0) { + if (mile_stones_pos_ < kMaxMileStone && + parsing_marks_pos_ < kMaxParsingMark) { + parsing_marks_[parsing_marks_pos_].node_offset = + get_son_offset(node) + found_start; + parsing_marks_[parsing_marks_pos_].node_num = found_num; + if (0 == ret_val) + mile_stones_[mile_stones_pos_].mark_start = + parsing_marks_pos_; + parsing_marks_pos_++; + } + ret_val++; + } + break; + } + } // for son_pos + } // for ext_pos + } // for h_pos + + if (ret_val > 0) { + mile_stones_[mile_stones_pos_].mark_num = ret_val; + ret_handle = mile_stones_pos_; + mile_stones_pos_++; + } + + // printf("----- parsing marks: %d, mile stone: %d \n", parsing_marks_pos_, + // mile_stones_pos_); + return ret_handle; + } + bool DictTrie::try_extend(const uint16 *splids, uint16 splid_num, + LemmaIdType id_lemma) { + if (0 == splid_num || NULL == splids) + return false; + void *node = root_ + splid_le0_index_[splids[0] - kFullSplIdStart]; + for (uint16 pos = 1; pos < splid_num; pos++) { + if (1 == pos) { + LmaNodeLE0 *node_le0 = reinterpret_cast(node); + LmaNodeGE1 *node_son; + uint16 son_pos; + for (son_pos = 0; son_pos < static_cast(node_le0->num_of_son); + son_pos++) { + assert(node_le0->son_1st_off <= lma_node_num_ge1_); + node_son = nodes_ge1_ + node_le0->son_1st_off + + son_pos; + if (node_son->spl_idx == splids[pos]) + break; + } + if (son_pos < node_le0->num_of_son) + node = reinterpret_cast(node_son); + else + return false; + } else { + LmaNodeGE1 *node_ge1 = reinterpret_cast(node); + LmaNodeGE1 *node_son; + uint16 son_pos; + for (son_pos = 0; son_pos < static_cast(node_ge1->num_of_son); + son_pos++) { + assert(node_ge1->son_1st_off_l > 0 || node_ge1->son_1st_off_h > 0); + node_son = nodes_ge1_ + get_son_offset(node_ge1) + son_pos; + if (node_son->spl_idx == splids[pos]) + break; + } + if (son_pos < node_ge1->num_of_son) + node = reinterpret_cast(node_son); + else + return false; + } + } + if (1 == splid_num) { + LmaNodeLE0 *node_le0 = reinterpret_cast(node); + size_t num_of_homo = (size_t) node_le0->num_of_homo; + for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) { + LemmaIdType id_this = get_lemma_id(node_le0->homo_idx_buf_off + homo_pos); + char16 str[2]; + get_lemma_str(id_this, str, 2); + if (id_this == id_lemma) + return true; + } + } else { + LmaNodeGE1 *node_ge1 = reinterpret_cast(node); + size_t num_of_homo = (size_t) node_ge1->num_of_homo; + for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) { + size_t node_homo_off = get_homo_idx_buf_offset(node_ge1); + if (get_lemma_id(node_homo_off + homo_pos) == id_lemma) + return true; + } + } + return false; + } + size_t DictTrie::get_lpis(const uint16 *splid_str, uint16 splid_str_len, + LmaPsbItem *lma_buf, size_t max_lma_buf) { + if (splid_str_len > kMaxLemmaSize) + return 0; +#define MAX_EXTENDBUF_LEN 200 + size_t *node_buf1[MAX_EXTENDBUF_LEN]; // use size_t for data alignment + size_t *node_buf2[MAX_EXTENDBUF_LEN]; + LmaNodeLE0 **node_fr_le0 = + reinterpret_cast(node_buf1); // Nodes from. + LmaNodeLE0 **node_to_le0 = + reinterpret_cast(node_buf2); // Nodes to. + LmaNodeGE1 **node_fr_ge1 = NULL; + LmaNodeGE1 **node_to_ge1 = NULL; + size_t node_fr_num = 1; + size_t node_to_num = 0; + node_fr_le0[0] = root_; + if (NULL == node_fr_le0[0]) + return 0; + size_t spl_pos = 0; + while (spl_pos < splid_str_len) { + uint16 id_num = 1; + uint16 id_start = splid_str[spl_pos]; + // If it is a half id + if (spl_trie_->is_half_id(splid_str[spl_pos])) { + id_num = spl_trie_->half_to_full(splid_str[spl_pos], &id_start); + assert(id_num > 0); + } + + // Extend the nodes + if (0 == spl_pos) { // From LmaNodeLE0 (root) to LmaNodeLE0 nodes + for (size_t node_fr_pos = 0; node_fr_pos < node_fr_num; node_fr_pos++) { + LmaNodeLE0 *node = node_fr_le0[node_fr_pos]; + assert(node == root_ && 1 == node_fr_num); + size_t son_start = splid_le0_index_[id_start - kFullSplIdStart]; + size_t son_end = + splid_le0_index_[id_start + id_num - kFullSplIdStart]; + for (size_t son_pos = son_start; son_pos < son_end; son_pos++) { + assert(1 == node->son_1st_off); + LmaNodeLE0 *node_son = root_ + son_pos; + assert(node_son->spl_idx >= id_start + && node_son->spl_idx < id_start + id_num); + if (node_to_num < MAX_EXTENDBUF_LEN) { + node_to_le0[node_to_num] = node_son; + node_to_num++; + } + // id_start + id_num - 1 is the last one, which has just been + // recorded. + if (node_son->spl_idx >= id_start + id_num - 1) + break; + } + } + spl_pos++; + if (spl_pos >= splid_str_len || node_to_num == 0) + break; + // Prepare the nodes for next extending + // next time, from LmaNodeLE0 to LmaNodeGE1 + LmaNodeLE0 **node_tmp = node_fr_le0; + node_fr_le0 = node_to_le0; + node_to_le0 = NULL; + node_to_ge1 = reinterpret_cast(node_tmp); + } else if (1 == spl_pos) { // From LmaNodeLE0 to LmaNodeGE1 nodes + for (size_t node_fr_pos = 0; node_fr_pos < node_fr_num; node_fr_pos++) { + LmaNodeLE0 *node = node_fr_le0[node_fr_pos]; + for (size_t son_pos = 0; son_pos < (size_t) node->num_of_son; + son_pos++) { + assert(node->son_1st_off <= lma_node_num_ge1_); + LmaNodeGE1 *node_son = nodes_ge1_ + node->son_1st_off + + son_pos; + if (node_son->spl_idx >= id_start + && node_son->spl_idx < id_start + id_num) { + if (node_to_num < MAX_EXTENDBUF_LEN) { + node_to_ge1[node_to_num] = node_son; + node_to_num++; + } + } + // id_start + id_num - 1 is the last one, which has just been + // recorded. + if (node_son->spl_idx >= id_start + id_num - 1) + break; + } + } + spl_pos++; + if (spl_pos >= splid_str_len || node_to_num == 0) + break; + // Prepare the nodes for next extending + // next time, from LmaNodeGE1 to LmaNodeGE1 + node_fr_ge1 = node_to_ge1; + node_to_ge1 = reinterpret_cast(node_fr_le0); + node_fr_le0 = NULL; + node_to_le0 = NULL; + } else { // From LmaNodeGE1 to LmaNodeGE1 nodes + for (size_t node_fr_pos = 0; node_fr_pos < node_fr_num; node_fr_pos++) { + LmaNodeGE1 *node = node_fr_ge1[node_fr_pos]; + for (size_t son_pos = 0; son_pos < (size_t) node->num_of_son; + son_pos++) { + assert(node->son_1st_off_l > 0 || node->son_1st_off_h > 0); + LmaNodeGE1 *node_son = nodes_ge1_ + + get_son_offset(node) + son_pos; + if (node_son->spl_idx >= id_start + && node_son->spl_idx < id_start + id_num) { + if (node_to_num < MAX_EXTENDBUF_LEN) { + node_to_ge1[node_to_num] = node_son; + node_to_num++; + } + } + // id_start + id_num - 1 is the last one, which has just been + // recorded. + if (node_son->spl_idx >= id_start + id_num - 1) + break; + } + } + spl_pos++; + if (spl_pos >= splid_str_len || node_to_num == 0) + break; + // Prepare the nodes for next extending + // next time, from LmaNodeGE1 to LmaNodeGE1 + LmaNodeGE1 **node_tmp = node_fr_ge1; + node_fr_ge1 = node_to_ge1; + node_to_ge1 = node_tmp; + } + + // The number of node for next extending + node_fr_num = node_to_num; + node_to_num = 0; + } // while + + if (0 == node_to_num) + return 0; + NGram &ngram = NGram::get_instance(); + size_t lma_num = 0; + + // If the length is 1, and the splid is a one-char Yunmu like 'a', 'o', 'e', + // only those candidates for the full matched one-char id will be returned. + if (1 == splid_str_len && spl_trie_->is_half_id_yunmu(splid_str[0])) + node_to_num = node_to_num > 0 ? 1 : 0; + for (size_t node_pos = 0; node_pos < node_to_num; node_pos++) { + size_t num_of_homo = 0; + if (spl_pos <= 1) { // Get from LmaNodeLE0 nodes + LmaNodeLE0 *node_le0 = node_to_le0[node_pos]; + num_of_homo = (size_t) node_le0->num_of_homo; + for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) { + size_t ch_pos = lma_num + homo_pos; + lma_buf[ch_pos].id = + get_lemma_id(node_le0->homo_idx_buf_off + homo_pos); + lma_buf[ch_pos].lma_len = 1; + lma_buf[ch_pos].psb = + static_cast(ngram.get_uni_psb(lma_buf[ch_pos].id)); + if (lma_num + homo_pos >= max_lma_buf - 1) + break; + } + } else { // Get from LmaNodeGE1 nodes + LmaNodeGE1 *node_ge1 = node_to_ge1[node_pos]; + num_of_homo = (size_t) node_ge1->num_of_homo; + for (size_t homo_pos = 0; homo_pos < num_of_homo; homo_pos++) { + size_t ch_pos = lma_num + homo_pos; + size_t node_homo_off = get_homo_idx_buf_offset(node_ge1); + lma_buf[ch_pos].id = get_lemma_id(node_homo_off + homo_pos); + lma_buf[ch_pos].lma_len = splid_str_len; + lma_buf[ch_pos].psb = + static_cast(ngram.get_uni_psb(lma_buf[ch_pos].id)); + if (lma_num + homo_pos >= max_lma_buf - 1) + break; + } + } + lma_num += num_of_homo; + if (lma_num >= max_lma_buf) { + lma_num = max_lma_buf; + break; + } + } + return lma_num; + } + uint16 DictTrie::get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, + uint16 str_max) { + return dict_list_->get_lemma_str(id_lemma, str_buf, str_max); + } + uint16 DictTrie::get_lemma_splids(LemmaIdType id_lemma, uint16 *splids, + uint16 splids_max, bool arg_valid) { + char16 lma_str[kMaxLemmaSize + 1]; + uint16 lma_len = get_lemma_str(id_lemma, lma_str, kMaxLemmaSize + 1); + assert((!arg_valid && splids_max >= lma_len) || lma_len == splids_max); + uint16 spl_mtrx[kMaxLemmaSize * 5]; + uint16 spl_start[kMaxLemmaSize + 1]; + spl_start[0] = 0; + uint16 try_num = 1; + for (uint16 pos = 0; pos < lma_len; pos++) { + uint16 cand_splids_this = 0; + if (arg_valid && spl_trie_->is_full_id(splids[pos])) { + spl_mtrx[spl_start[pos]] = splids[pos]; + cand_splids_this = 1; + } else { + cand_splids_this = dict_list_->get_splids_for_hanzi(lma_str[pos], + arg_valid ? splids[pos] : 0, spl_mtrx + spl_start[pos], + kMaxLemmaSize * 5 - spl_start[pos]); + assert(cand_splids_this > 0); + } + spl_start[pos + 1] = spl_start[pos] + cand_splids_this; + try_num *= cand_splids_this; + } + for (uint16 try_pos = 0; try_pos < try_num; try_pos++) { + uint16 mod = 1; + for (uint16 pos = 0; pos < lma_len; pos++) { + uint16 radix = spl_start[pos + 1] - spl_start[pos]; + splids[pos] = spl_mtrx[spl_start[pos] + try_pos / mod % radix]; + mod *= radix; + } + if (try_extend(splids, lma_len, id_lemma)) + return lma_len; + } + return 0; + } + void DictTrie::set_total_lemma_count_of_others(size_t count) { + NGram &ngram = NGram::get_instance(); + ngram.set_total_freq_none_sys(count); + } + void DictTrie::convert_to_hanzis(char16 *str, uint16 str_len) { + return dict_list_->convert_to_hanzis(str, str_len); + } + void DictTrie::convert_to_scis_ids(char16 *str, uint16 str_len) { + return dict_list_->convert_to_scis_ids(str, str_len); + } + LemmaIdType DictTrie::get_lemma_id(const char16 lemma_str[], uint16 lemma_len) { + if (NULL == lemma_str || lemma_len > kMaxLemmaSize) + return 0; + return dict_list_->get_lemma_id(lemma_str, lemma_len); + } + size_t DictTrie::predict_top_lmas(size_t his_len, NPredictItem *npre_items, + size_t npre_max, size_t b4_used) { + NGram &ngram = NGram::get_instance(); + size_t item_num = 0; + size_t top_lmas_id_offset = lma_idx_buf_len_ / kLemmaIdSize - top_lmas_num_; + size_t top_lmas_pos = 0; + while (item_num < npre_max && top_lmas_pos < top_lmas_num_) { + memset(npre_items + item_num, 0, sizeof(NPredictItem)); + LemmaIdType top_lma_id = get_lemma_id(top_lmas_id_offset + top_lmas_pos); + top_lmas_pos += 1; + if (dict_list_->get_lemma_str(top_lma_id, + npre_items[item_num].pre_hzs, + kMaxLemmaSize - 1) == 0) { + continue; + } + npre_items[item_num].psb = ngram.get_uni_psb(top_lma_id); + npre_items[item_num].his_len = his_len; + item_num++; + } + return item_num; + } + size_t DictTrie::predict(const char16 *last_hzs, uint16 hzs_len, + NPredictItem *npre_items, size_t npre_max, + size_t b4_used) { + return dict_list_->predict(last_hzs, hzs_len, npre_items, npre_max, b4_used); + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dicttrie.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dicttrie.h new file mode 100644 index 0000000..b1b77ea --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/dicttrie.h @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_DICTTRIE_H__ +#define PINYINIME_INCLUDE_DICTTRIE_H__ +#include +#include "./atomdictbase.h" +#include "./dictdef.h" +#include "./dictlist.h" +#include "./searchutility.h" +namespace ime_pinyin { + class DictTrie : AtomDictBase { + private: + struct ParsingMark { + size_t node_offset: 24; + size_t node_num: 8; // Number of nodes with this spelling id given + // by spl_id. If spl_id is a Shengmu, for nodes + // in the first layer of DictTrie, it equals to + // SpellingTrie::shm2full_num(); but for those + // nodes which are not in the first layer, + // node_num < SpellingTrie::shm2full_num(). + // For a full spelling id, node_num = 1; + }; + // Used to indicate an extended mile stone. + // An extended mile stone is used to mark a partial match in the dictionary + // trie to speed up further potential extending. + // For example, when the user inputs "w", a mile stone is created to mark the + // partial match status, so that when user inputs another char 'm', it will be + // faster to extend search space based on this mile stone. + // + // For partial match status of "wm", there can be more than one sub mile + // stone, for example, "wm" can be matched to "wanm", "wom", ..., etc, so + // there may be more one parsing mark used to mark these partial matchings. + // A mile stone records the starting position in the mark list and number of + // marks. + struct MileStone { + uint16 mark_start; + uint16 mark_num; + }; + DictList *dict_list_; + const SpellingTrie *spl_trie_; + LmaNodeLE0 *root_; // Nodes for root and the first layer. + LmaNodeGE1 *nodes_ge1_; // Nodes for other layers. + + // An quick index from spelling id to the LmaNodeLE0 node buffer, or + // to the root_ buffer. + // Index length: + // SpellingTrie::get_instance().get_spelling_num() + 1. The last one is used + // to get the end. + // All Shengmu ids are not indexed because they will be converted into + // corresponding full ids. + // So, given an id splid, the son is: + // root_[splid_le0_index_[splid - kFullSplIdStart]] + uint16 *splid_le0_index_; + uint32 lma_node_num_le0_; + uint32 lma_node_num_ge1_; + // The first part is for homophnies, and the last top_lma_num_ items are + // lemmas with highest scores. + unsigned char *lma_idx_buf_; + uint32 lma_idx_buf_len_; // The total size of lma_idx_buf_ in byte. + uint32 total_lma_num_; // Total number of lemmas in this dictionary. + uint32 top_lmas_num_; // Number of lemma with highest scores. + + // Parsing mark list used to mark the detailed extended statuses. + ParsingMark *parsing_marks_; + // The position for next available mark. + uint16 parsing_marks_pos_; + // Mile stone list used to mark the extended status. + MileStone *mile_stones_; + // The position for the next available mile stone. We use positions (except 0) + // as handles. + MileStoneHandle mile_stones_pos_; + // Get the offset of sons for a node. + inline size_t get_son_offset(const LmaNodeGE1 *node); + // Get the offset of homonious ids for a node. + inline size_t get_homo_idx_buf_offset(const LmaNodeGE1 *node); + // Get the lemma id by the offset. + inline LemmaIdType get_lemma_id(size_t id_offset); + void free_resource(bool free_dict_list); + bool load_dict(FILE *fp); + // Given a LmaNodeLE0 node, extract the lemmas specified by it, and fill + // them into the lpi_items buffer. + // This function is called by the search engine. + size_t fill_lpi_buffer(LmaPsbItem lpi_items[], size_t max_size, + LmaNodeLE0 *node); + // Given a LmaNodeGE1 node, extract the lemmas specified by it, and fill + // them into the lpi_items buffer. + // This function is called by inner functions extend_dict0(), extend_dict1() + // and extend_dict2(). + size_t fill_lpi_buffer(LmaPsbItem lpi_items[], size_t max_size, + size_t homo_buf_off, LmaNodeGE1 *node, + uint16 lma_len); + // Extend in the trie from level 0. + MileStoneHandle extend_dict0(MileStoneHandle from_handle, + const DictExtPara *dep, LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num); + // Extend in the trie from level 1. + MileStoneHandle extend_dict1(MileStoneHandle from_handle, + const DictExtPara *dep, LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num); + // Extend in the trie from level 2. + MileStoneHandle extend_dict2(MileStoneHandle from_handle, + const DictExtPara *dep, LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num); + // Try to extend the given spelling id buffer, and if the given id_lemma can + // be successfully gotten, return true; + // The given spelling ids are all valid full ids. + bool try_extend(const uint16 *splids, uint16 splid_num, LemmaIdType id_lemma); +#ifdef ___BUILD_MODEL___ + bool save_dict(FILE *fp); +#endif // ___BUILD_MODEL___ + static const int kMaxMileStone = 100; + static const int kMaxParsingMark = 600; + static const MileStoneHandle kFirstValidMileStoneHandle = 1; + friend class DictParser; + friend class DictBuilder; + public: + DictTrie(); + ~DictTrie(); +#ifdef ___BUILD_MODEL___ + // Construct the tree from the file fn_raw. + // fn_validhzs provide the valid hanzi list. If fn_validhzs is + // NULL, only chars in GB2312 will be included. + bool build_dict(const char *fn_raw, const char *fn_validhzs); + + // Save the binary dictionary + // Actually, the SpellingTrie/DictList instance will be also saved. + bool save_dict(const char *filename); +#endif // ___BUILD_MODEL___ + void convert_to_hanzis(char16 *str, uint16 str_len); + void convert_to_scis_ids(char16 *str, uint16 str_len); + // Load a binary dictionary + // The SpellingTrie instance/DictList will be also loaded + bool load_dict(const char *filename, LemmaIdType start_id, + LemmaIdType end_id); + bool load_dict_fd(int sys_fd, long start_offset, long length, + LemmaIdType start_id, LemmaIdType end_id); + bool close_dict() { return true; } + size_t number_of_lemmas() { return 0; } + void reset_milestones(uint16 from_step, MileStoneHandle from_handle); + MileStoneHandle extend_dict(MileStoneHandle from_handle, + const DictExtPara *dep, + LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num); + size_t get_lpis(const uint16 *splid_str, uint16 splid_str_len, + LmaPsbItem *lpi_items, size_t lpi_max); + uint16 get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, uint16 str_max); + uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids, + uint16 splids_max, bool arg_valid); + size_t predict(const char16 *last_hzs, uint16 hzs_len, + NPredictItem *npre_items, size_t npre_max, + size_t b4_used); + LemmaIdType put_lemma(char16 /*lemma_str*/[], uint16 /*splids*/[], + uint16 /*lemma_len*/, uint16 /*count*/) { return 0; } + LemmaIdType update_lemma(LemmaIdType /*lemma_id*/, int16 /*delta_count*/, + bool /*selected*/) { return 0; } + LemmaIdType get_lemma_id(char16 /*lemma_str*/[], uint16 /*splids*/[], + uint16 /*lemma_len*/) { return 0; } + LmaScoreType get_lemma_score(LemmaIdType /*lemma_id*/) { return 0; } + LmaScoreType get_lemma_score(char16 /*lemma_str*/[], uint16 /*splids*/[], + uint16 /*lemma_len*/) { return 0; } + bool remove_lemma(LemmaIdType /*lemma_id*/) { return false; } + size_t get_total_lemma_count() { return 0; } + void set_total_lemma_count_of_others(size_t count); + void flush_cache() {} + LemmaIdType get_lemma_id(const char16 lemma_str[], uint16 lemma_len); + // Fill the lemmas with highest scores to the prediction buffer. + // his_len is the history length to fill in the prediction buffer. + size_t predict_top_lmas(size_t his_len, NPredictItem *npre_items, + size_t npre_max, size_t b4_used); + }; +} +#endif // PINYINIME_INCLUDE_DICTTRIE_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/googlepinyin.pro b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/googlepinyin.pro new file mode 100644 index 0000000..c7994e6 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/googlepinyin.pro @@ -0,0 +1,43 @@ +QT -= gui + +TARGET = googlepinyin +TEMPLATE = lib +CONFIG += staticlib + +SOURCES += \ + dictbuilder.cpp \ + dictlist.cpp \ + dicttrie.cpp \ + lpicache.cpp \ + matrixsearch.cpp \ + mystdlib.cpp \ + ngram.cpp \ + pinyinime.cpp \ + searchutility.cpp \ + spellingtable.cpp \ + spellingtrie.cpp \ + splparser.cpp \ + sync.cpp \ + userdict.cpp \ + utf16char.cpp \ + utf16reader.cpp + +HEADERS += \ + atomdictbase.h \ + dictbuilder.h \ + dictdef.h \ + dictlist.h \ + dicttrie.h \ + lpicache.h \ + matrixsearch.h \ + mystdlib.h \ + ngram.h \ + pinyinime.h \ + searchutility.h \ + spellingtable.h \ + spellingtrie.h \ + splparser.h \ + sync.h \ + userdict.h \ + utf16char.h \ + utf16reader.h diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/lpicache.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/lpicache.cpp new file mode 100644 index 0000000..b65a2d0 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/lpicache.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "lpicache.h" +namespace ime_pinyin { + LpiCache *LpiCache::instance_ = NULL; + LpiCache::LpiCache() { + lpi_cache_ = new LmaPsbItem[kFullSplIdStart * kMaxLpiCachePerId]; + lpi_cache_len_ = new uint16[kFullSplIdStart]; + assert(NULL != lpi_cache_); + assert(NULL != lpi_cache_len_); + for (uint16 id = 0; id < kFullSplIdStart; id++) + lpi_cache_len_[id] = 0; + } + LpiCache::~LpiCache() { + if (NULL != lpi_cache_) + delete[] lpi_cache_; + if (NULL != lpi_cache_len_) + delete[] lpi_cache_len_; + } + LpiCache &LpiCache::get_instance() { + if (NULL == instance_) { + instance_ = new LpiCache(); + assert(NULL != instance_); + } + return *instance_; + } + bool LpiCache::is_cached(uint16 splid) { + if (splid >= kFullSplIdStart) + return false; + return lpi_cache_len_[splid] != 0; + } + size_t LpiCache::put_cache(uint16 splid, LmaPsbItem lpi_items[], + size_t lpi_num) { + uint16 num = kMaxLpiCachePerId; + if (num > lpi_num) + num = static_cast(lpi_num); + LmaPsbItem *lpi_cache_this = lpi_cache_ + splid * kMaxLpiCachePerId; + for (uint16 pos = 0; pos < num; pos++) + lpi_cache_this[pos] = lpi_items[pos]; + lpi_cache_len_[splid] = num; + return num; + } + size_t LpiCache::get_cache(uint16 splid, LmaPsbItem lpi_items[], + size_t lpi_max) { + if (lpi_max > lpi_cache_len_[splid]) + lpi_max = lpi_cache_len_[splid]; + LmaPsbItem *lpi_cache_this = lpi_cache_ + splid * kMaxLpiCachePerId; + for (uint16 pos = 0; pos < lpi_max; pos++) { + lpi_items[pos] = lpi_cache_this[pos]; + } + return lpi_max; + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/lpicache.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/lpicache.h new file mode 100644 index 0000000..755febb --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/lpicache.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_ANDPY_INCLUDE_LPICACHE_H__ +#define PINYINIME_ANDPY_INCLUDE_LPICACHE_H__ +#include +#include "./searchutility.h" +#include "./spellingtrie.h" +namespace ime_pinyin { +// Used to cache LmaPsbItem list for half spelling ids. + class LpiCache { + private: + static LpiCache *instance_; + static const int kMaxLpiCachePerId = 15; + LmaPsbItem *lpi_cache_; + uint16 *lpi_cache_len_; + public: + LpiCache(); + ~LpiCache(); + static LpiCache &get_instance(); + // Test if the LPI list of the given splid has been cached. + // If splid is a full spelling id, it returns false, because we only cache + // list for half ids. + bool is_cached(uint16 splid); + // Put LPI list to cahce. If the length of the list, lpi_num, is longer than + // the cache buffer. the list will be truncated, and function returns the + // maximum length of the cache buffer. + // Note: splid must be a half id, and lpi_items must be not NULL. The + // caller of this function should guarantee this. + size_t put_cache(uint16 splid, LmaPsbItem lpi_items[], size_t lpi_num); + // Get the cached list for the given half id. + // Return the length of the cached buffer. + // Note: splid must be a half id, and lpi_items must be not NULL. The + // caller of this function should guarantee this. + size_t get_cache(uint16 splid, LmaPsbItem lpi_items[], size_t lpi_max); + }; +} // namespace + +#endif // PINYINIME_ANDPY_INCLUDE_LPICACHE_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/matrixsearch.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/matrixsearch.cpp new file mode 100644 index 0000000..dbff8fc --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/matrixsearch.cpp @@ -0,0 +1,1734 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include "lpicache.h" +#include "matrixsearch.h" +#include "mystdlib.h" +#include "ngram.h" +#include "userdict.h" +namespace ime_pinyin { +#define PRUMING_SCORE 8000.0 + MatrixSearch::MatrixSearch() { + inited_ = false; + spl_trie_ = SpellingTrie::get_cpinstance(); + reset_pointers_to_null(); + pys_decoded_len_ = 0; + mtrx_nd_pool_used_ = 0; + dmi_pool_used_ = 0; + xi_an_enabled_ = false; + dmi_c_phrase_ = false; + assert(kMaxSearchSteps > 0); + max_sps_len_ = kMaxSearchSteps - 1; + max_hzs_len_ = kMaxSearchSteps; + } + MatrixSearch::~MatrixSearch() { + free_resource(); + } + void MatrixSearch::reset_pointers_to_null() { + dict_trie_ = NULL; + user_dict_ = NULL; + spl_parser_ = NULL; + share_buf_ = NULL; + + // The following four buffers are used for decoding, and they are based on + // share_buf_, no need to delete them. + mtrx_nd_pool_ = NULL; + dmi_pool_ = NULL; + matrix_ = NULL; + dep_ = NULL; + + // Based on share_buf_, no need to delete them. + npre_items_ = NULL; + } + bool MatrixSearch::alloc_resource() { + free_resource(); + dict_trie_ = new DictTrie(); + user_dict_ = static_cast(new UserDict()); + spl_parser_ = new SpellingParser(); + size_t mtrx_nd_size = sizeof(MatrixNode) * kMtrxNdPoolSize; + mtrx_nd_size = align_to_size_t(mtrx_nd_size) / sizeof(size_t); + size_t dmi_size = sizeof(DictMatchInfo) * kDmiPoolSize; + dmi_size = align_to_size_t(dmi_size) / sizeof(size_t); + size_t matrix_size = sizeof(MatrixRow) * kMaxRowNum; + matrix_size = align_to_size_t(matrix_size) / sizeof(size_t); + size_t dep_size = sizeof(DictExtPara); + dep_size = align_to_size_t(dep_size) / sizeof(size_t); + + // share_buf's size is determined by the buffers for search. + share_buf_ = new size_t[mtrx_nd_size + dmi_size + matrix_size + dep_size]; + if (NULL == dict_trie_ || NULL == user_dict_ || NULL == spl_parser_ || + NULL == share_buf_) + return false; + + // The buffers for search are based on the share buffer + mtrx_nd_pool_ = reinterpret_cast(share_buf_); + dmi_pool_ = reinterpret_cast(share_buf_ + mtrx_nd_size); + matrix_ = reinterpret_cast(share_buf_ + mtrx_nd_size + dmi_size); + dep_ = reinterpret_cast + (share_buf_ + mtrx_nd_size + dmi_size + matrix_size); + + // The prediction buffer is also based on the share buffer. + npre_items_ = reinterpret_cast(share_buf_); + npre_items_len_ = (mtrx_nd_size + dmi_size + matrix_size + dep_size) * + sizeof(size_t) / sizeof(NPredictItem); + return true; + } + void MatrixSearch::free_resource() { + if (NULL != dict_trie_) + delete dict_trie_; + if (NULL != user_dict_) + delete user_dict_; + if (NULL != spl_parser_) + delete spl_parser_; + if (NULL != share_buf_) + delete[] share_buf_; + reset_pointers_to_null(); + } + bool MatrixSearch::init(const char *fn_sys_dict, const char *fn_usr_dict) { + if (NULL == fn_sys_dict || NULL == fn_usr_dict) + return false; + if (!alloc_resource()) + return false; + if (!dict_trie_->load_dict(fn_sys_dict, 1, kSysDictIdEnd)) + return false; + + // If engine fails to load the user dictionary, reset the user dictionary + // to NULL. + if (!user_dict_->load_dict(fn_usr_dict, kUserDictIdStart, kUserDictIdEnd)) { + delete user_dict_; + user_dict_ = NULL; + } else { + user_dict_->set_total_lemma_count_of_others(NGram::kSysDictTotalFreq); + } + reset_search0(); + inited_ = true; + return true; + } + bool MatrixSearch::init_fd(int sys_fd, long start_offset, long length, + const char *fn_usr_dict) { + if (NULL == fn_usr_dict) + return false; + if (!alloc_resource()) + return false; + if (!dict_trie_->load_dict_fd(sys_fd, start_offset, length, 1, kSysDictIdEnd)) + return false; + if (!user_dict_->load_dict(fn_usr_dict, kUserDictIdStart, kUserDictIdEnd)) { + delete user_dict_; + user_dict_ = NULL; + } else { + user_dict_->set_total_lemma_count_of_others(NGram::kSysDictTotalFreq); + } + reset_search0(); + inited_ = true; + return true; + } + void MatrixSearch::init_user_dictionary(const char *fn_usr_dict) { + assert(inited_); + if (NULL != user_dict_) { + delete user_dict_; + user_dict_ = NULL; + } + if (NULL != fn_usr_dict) { + user_dict_ = static_cast(new UserDict()); + if (!user_dict_->load_dict(fn_usr_dict, kUserDictIdStart, kUserDictIdEnd)) { + delete user_dict_; + user_dict_ = NULL; + } + } + reset_search0(); + } + bool MatrixSearch::is_user_dictionary_enabled() const { + return NULL != user_dict_; + } + void MatrixSearch::set_max_lens(size_t max_sps_len, size_t max_hzs_len) { + if (0 != max_sps_len) + max_sps_len_ = max_sps_len; + if (0 != max_hzs_len) + max_hzs_len_ = max_hzs_len; + } + void MatrixSearch::close() { + flush_cache(); + free_resource(); + inited_ = false; + } + void MatrixSearch::flush_cache() { + if (NULL != user_dict_) + user_dict_->flush_cache(); + } + void MatrixSearch::set_xi_an_switch(bool xi_an_enabled) { + xi_an_enabled_ = xi_an_enabled; + } + bool MatrixSearch::get_xi_an_switch() { + return xi_an_enabled_; + } + bool MatrixSearch::reset_search() { + if (!inited_) + return false; + return reset_search0(); + } + bool MatrixSearch::reset_search0() { + if (!inited_) + return false; + pys_decoded_len_ = 0; + mtrx_nd_pool_used_ = 0; + dmi_pool_used_ = 0; + + // Get a MatrixNode from the pool + matrix_[0].mtrx_nd_pos = mtrx_nd_pool_used_; + matrix_[0].mtrx_nd_num = 1; + mtrx_nd_pool_used_ += 1; + + // Update the node, and make it to be a starting node + MatrixNode *node = mtrx_nd_pool_ + matrix_[0].mtrx_nd_pos; + node->id = 0; + node->score = 0; + node->from = NULL; + node->step = 0; + node->dmi_fr = (PoolPosType) -1; + matrix_[0].dmi_pos = 0; + matrix_[0].dmi_num = 0; + matrix_[0].dmi_has_full_id = 1; + matrix_[0].mtrx_nd_fixed = node; + lma_start_[0] = 0; + fixed_lmas_ = 0; + spl_start_[0] = 0; + fixed_hzs_ = 0; + dict_trie_->reset_milestones(0, 0); + if (NULL != user_dict_) + user_dict_->reset_milestones(0, 0); + return true; + } + bool MatrixSearch::reset_search(size_t ch_pos, bool clear_fixed_this_step, + bool clear_dmi_this_step, + bool clear_mtrx_this_step) { + if (!inited_ || ch_pos > pys_decoded_len_ || ch_pos >= kMaxRowNum) + return false; + if (0 == ch_pos) { + reset_search0(); + } else { + // Prepare mile stones of this step to clear. + MileStoneHandle *dict_handles_to_clear = NULL; + if (clear_dmi_this_step && matrix_[ch_pos].dmi_num > 0) { + dict_handles_to_clear = dmi_pool_[matrix_[ch_pos].dmi_pos].dict_handles; + } + + // If there are more steps, and this step is not allowed to clear, find + // milestones of next step. + if (pys_decoded_len_ > ch_pos && !clear_dmi_this_step) { + dict_handles_to_clear = NULL; + if (matrix_[ch_pos + 1].dmi_num > 0) { + dict_handles_to_clear = + dmi_pool_[matrix_[ch_pos + 1].dmi_pos].dict_handles; + } + } + if (NULL != dict_handles_to_clear) { + dict_trie_->reset_milestones(ch_pos, dict_handles_to_clear[0]); + if (NULL != user_dict_) + user_dict_->reset_milestones(ch_pos, dict_handles_to_clear[1]); + } + pys_decoded_len_ = ch_pos; + if (clear_dmi_this_step) { + dmi_pool_used_ = matrix_[ch_pos - 1].dmi_pos + + matrix_[ch_pos - 1].dmi_num; + matrix_[ch_pos].dmi_num = 0; + } else { + dmi_pool_used_ = matrix_[ch_pos].dmi_pos + matrix_[ch_pos].dmi_num; + } + if (clear_mtrx_this_step) { + mtrx_nd_pool_used_ = matrix_[ch_pos - 1].mtrx_nd_pos + + matrix_[ch_pos - 1].mtrx_nd_num; + matrix_[ch_pos].mtrx_nd_num = 0; + } else { + mtrx_nd_pool_used_ = matrix_[ch_pos].mtrx_nd_pos + + matrix_[ch_pos].mtrx_nd_num; + } + + // Modify fixed_hzs_ + if (fixed_hzs_ > 0 && + ((kLemmaIdComposing != lma_id_[0]) || + (kLemmaIdComposing == lma_id_[0] && + spl_start_[c_phrase_.length] <= ch_pos))) { + size_t fixed_ch_pos = ch_pos; + if (clear_fixed_this_step) + fixed_ch_pos = fixed_ch_pos > 0 ? fixed_ch_pos - 1 : 0; + while (NULL == matrix_[fixed_ch_pos].mtrx_nd_fixed && fixed_ch_pos > 0) + fixed_ch_pos--; + fixed_lmas_ = 0; + fixed_hzs_ = 0; + if (fixed_ch_pos > 0) { + while (spl_start_[fixed_hzs_] < fixed_ch_pos) + fixed_hzs_++; + assert(spl_start_[fixed_hzs_] == fixed_ch_pos); + while (lma_start_[fixed_lmas_] < fixed_hzs_) + fixed_lmas_++; + assert(lma_start_[fixed_lmas_] == fixed_hzs_); + } + + // Re-search the Pinyin string for the unlocked lemma + // which was previously fixed. + // + // Prepare mile stones of this step to clear. + MileStoneHandle *dict_handles_to_clear = NULL; + if (clear_dmi_this_step && ch_pos == fixed_ch_pos && + matrix_[fixed_ch_pos].dmi_num > 0) { + dict_handles_to_clear = dmi_pool_[matrix_[fixed_ch_pos].dmi_pos].dict_handles; + } + + // If there are more steps, and this step is not allowed to clear, find + // milestones of next step. + if (pys_decoded_len_ > fixed_ch_pos && !clear_dmi_this_step) { + dict_handles_to_clear = NULL; + if (matrix_[fixed_ch_pos + 1].dmi_num > 0) { + dict_handles_to_clear = + dmi_pool_[matrix_[fixed_ch_pos + 1].dmi_pos].dict_handles; + } + } + if (NULL != dict_handles_to_clear) { + dict_trie_->reset_milestones(fixed_ch_pos, dict_handles_to_clear[0]); + if (NULL != user_dict_) + user_dict_->reset_milestones(fixed_ch_pos, dict_handles_to_clear[1]); + } + pys_decoded_len_ = fixed_ch_pos; + if (clear_dmi_this_step && ch_pos == fixed_ch_pos) { + dmi_pool_used_ = matrix_[fixed_ch_pos - 1].dmi_pos + + matrix_[fixed_ch_pos - 1].dmi_num; + matrix_[fixed_ch_pos].dmi_num = 0; + } else { + dmi_pool_used_ = matrix_[fixed_ch_pos].dmi_pos + + matrix_[fixed_ch_pos].dmi_num; + } + if (clear_mtrx_this_step && ch_pos == fixed_ch_pos) { + mtrx_nd_pool_used_ = matrix_[fixed_ch_pos - 1].mtrx_nd_pos + + matrix_[fixed_ch_pos - 1].mtrx_nd_num; + matrix_[fixed_ch_pos].mtrx_nd_num = 0; + } else { + mtrx_nd_pool_used_ = matrix_[fixed_ch_pos].mtrx_nd_pos + + matrix_[fixed_ch_pos].mtrx_nd_num; + } + for (uint16 re_pos = fixed_ch_pos; re_pos < ch_pos; re_pos++) { + add_char(pys_[re_pos]); + } + } else if (fixed_hzs_ > 0 && kLemmaIdComposing == lma_id_[0]) { + for (uint16 subpos = 0; subpos < c_phrase_.sublma_num; subpos++) { + uint16 splpos_begin = c_phrase_.sublma_start[subpos]; + uint16 splpos_end = c_phrase_.sublma_start[subpos + 1]; + for (uint16 splpos = splpos_begin; splpos < splpos_end; splpos++) { + // If ch_pos is in this spelling + uint16 spl_start = c_phrase_.spl_start[splpos]; + uint16 spl_end = c_phrase_.spl_start[splpos + 1]; + if (ch_pos >= spl_start && ch_pos < spl_end) { + // Clear everything after this position + c_phrase_.chn_str[splpos] = static_cast('\0'); + c_phrase_.sublma_start[subpos + 1] = splpos; + c_phrase_.sublma_num = subpos + 1; + c_phrase_.length = splpos; + if (splpos == splpos_begin) { + c_phrase_.sublma_num = subpos; + } + } + } + } + + // Extend the composing phrase. + reset_search0(); + dmi_c_phrase_ = true; + uint16 c_py_pos = 0; + while (c_py_pos < spl_start_[c_phrase_.length]) { + bool b_ac_tmp = add_char(pys_[c_py_pos]); + assert(b_ac_tmp); + c_py_pos++; + } + dmi_c_phrase_ = false; + lma_id_num_ = 1; + fixed_lmas_ = 1; + fixed_lmas_no1_[0] = 0; // A composing string is always modified. + fixed_hzs_ = c_phrase_.length; + lma_start_[1] = fixed_hzs_; + lma_id_[0] = kLemmaIdComposing; + matrix_[spl_start_[fixed_hzs_]].mtrx_nd_fixed = mtrx_nd_pool_ + + matrix_[spl_start_[fixed_hzs_]].mtrx_nd_pos; + } + } + return true; + } + void MatrixSearch::del_in_pys(size_t start, size_t len) { + while (start < kMaxRowNum - len && '\0' != pys_[start]) { + pys_[start] = pys_[start + len]; + start++; + } + } + size_t MatrixSearch::search(const char *py, size_t py_len) { + if (!inited_ || NULL == py) + return 0; + + // If the search Pinyin string is too long, it will be truncated. + if (py_len > kMaxRowNum - 1) + py_len = kMaxRowNum - 1; + + // Compare the new string with the previous one. Find their prefix to + // increase search efficiency. + size_t ch_pos = 0; + for (ch_pos = 0; ch_pos < pys_decoded_len_; ch_pos++) { + if ('\0' == py[ch_pos] || py[ch_pos] != pys_[ch_pos]) + break; + } + bool clear_fix = true; + if (ch_pos == pys_decoded_len_) + clear_fix = false; + reset_search(ch_pos, clear_fix, false, false); + memcpy(pys_ + ch_pos, py + ch_pos, py_len - ch_pos); + pys_[py_len] = '\0'; + while ('\0' != pys_[ch_pos]) { + if (!add_char(py[ch_pos])) { + pys_decoded_len_ = ch_pos; + break; + } + ch_pos++; + } + + // Get spelling ids and starting positions. + get_spl_start_id(); + + // If there are too many spellings, remove the last letter until the spelling + // number is acceptable. + while (spl_id_num_ > 9) { + py_len--; + reset_search(py_len, false, false, false); + pys_[py_len] = '\0'; + get_spl_start_id(); + } + prepare_candidates(); + if (kPrintDebug0) { + printf("--Matrix Node Pool Used: %d\n", mtrx_nd_pool_used_); + printf("--DMI Pool Used: %d\n", dmi_pool_used_); + if (kPrintDebug1) { + for (PoolPosType pos = 0; pos < dmi_pool_used_; pos++) { + debug_print_dmi(pos, 1); + } + } + } + return ch_pos; + } + size_t MatrixSearch::delsearch(size_t pos, bool is_pos_in_splid, + bool clear_fixed_this_step) { + if (!inited_) + return 0; + size_t reset_pos = pos; + + // Out of range for both Pinyin mode and Spelling id mode. + if (pys_decoded_len_ <= pos) { + del_in_pys(pos, 1); + reset_pos = pys_decoded_len_; + // Decode the string after the un-decoded position + while ('\0' != pys_[reset_pos]) { + if (!add_char(pys_[reset_pos])) { + pys_decoded_len_ = reset_pos; + break; + } + reset_pos++; + } + get_spl_start_id(); + prepare_candidates(); + return pys_decoded_len_; + } + + // Spelling id mode, but out of range. + if (is_pos_in_splid && spl_id_num_ <= pos) + return pys_decoded_len_; + + // Begin to handle two modes respectively. + // Pinyin mode by default + size_t c_py_len = 0; // The length of composing phrase's Pinyin + size_t del_py_len = 1; + if (!is_pos_in_splid) { + // Pinyin mode is only allowed to delete beyond the fixed lemmas. + if (fixed_lmas_ > 0 && pos < spl_start_[lma_start_[fixed_lmas_]]) + return pys_decoded_len_; + del_in_pys(pos, 1); + + // If the deleted character is just the one after the last fixed lemma + if (pos == spl_start_[lma_start_[fixed_lmas_]]) { + // If all fixed lemmas have been merged, and the caller of the function + // request to unlock the last fixed lemma. + if (kLemmaIdComposing == lma_id_[0] && clear_fixed_this_step) { + // Unlock the last sub lemma in the composing phrase. Because it is not + // easy to unlock it directly. Instead, we re-decode the modified + // composing phrase. + c_phrase_.sublma_num--; + c_phrase_.length = c_phrase_.sublma_start[c_phrase_.sublma_num]; + reset_pos = spl_start_[c_phrase_.length]; + c_py_len = reset_pos; + } + } + } else { + del_py_len = spl_start_[pos + 1] - spl_start_[pos]; + del_in_pys(spl_start_[pos], del_py_len); + if (pos >= lma_start_[fixed_lmas_]) { + c_py_len = 0; + reset_pos = spl_start_[pos + 1] - del_py_len; + } else { + c_py_len = spl_start_[lma_start_[fixed_lmas_]] - del_py_len; + reset_pos = c_py_len; + if (c_py_len > 0) + merge_fixed_lmas(pos); + } + } + if (c_py_len > 0) { + assert(c_phrase_.length > 0 && c_py_len == + c_phrase_.spl_start[c_phrase_.sublma_start[c_phrase_.sublma_num]]); + // The composing phrase is valid, reset all search space, + // and begin a new search which will only extend the composing + // phrase. + reset_search0(); + dmi_c_phrase_ = true; + // Extend the composing phrase. + uint16 c_py_pos = 0; + while (c_py_pos < c_py_len) { + bool b_ac_tmp = add_char(pys_[c_py_pos]); + assert(b_ac_tmp); + c_py_pos++; + } + dmi_c_phrase_ = false; + + // Fixd the composing phrase as the first choice. + lma_id_num_ = 1; + fixed_lmas_ = 1; + fixed_lmas_no1_[0] = 0; // A composing string is always modified. + fixed_hzs_ = c_phrase_.length; + lma_start_[1] = fixed_hzs_; + lma_id_[0] = kLemmaIdComposing; + matrix_[spl_start_[fixed_hzs_]].mtrx_nd_fixed = mtrx_nd_pool_ + + matrix_[spl_start_[fixed_hzs_]].mtrx_nd_pos; + } else { + // Reseting search only clear pys_decoded_len_, but the string is kept. + reset_search(reset_pos, clear_fixed_this_step, false, false); + } + + // Decode the string after the delete position. + while ('\0' != pys_[reset_pos]) { + if (!add_char(pys_[reset_pos])) { + pys_decoded_len_ = reset_pos; + break; + } + reset_pos++; + } + get_spl_start_id(); + prepare_candidates(); + return pys_decoded_len_; + } + size_t MatrixSearch::get_candidate_num() { + if (!inited_ || 0 == pys_decoded_len_ || + 0 == matrix_[pys_decoded_len_].mtrx_nd_num) + return 0; + return 1 + lpi_total_; + } + char16 *MatrixSearch::get_candidate(size_t cand_id, char16 *cand_str, + size_t max_len) { + if (!inited_ || 0 == pys_decoded_len_ || NULL == cand_str) + return NULL; + if (0 == cand_id) { + return get_candidate0(cand_str, max_len, NULL, false); + } else { + cand_id--; + } + + // For this case: the current sentence is a word only, and the user fixed it, + // so the result will be fixed to the sentence space, and + // lpi_total_ will be set to 0. + if (0 == lpi_total_) { + return get_candidate0(cand_str, max_len, NULL, false); + } + LemmaIdType id = lpi_items_[cand_id].id; + char16 s[kMaxLemmaSize + 1]; + uint16 s_len = lpi_items_[cand_id].lma_len; + if (s_len > 1) { + s_len = get_lemma_str(id, s, kMaxLemmaSize + 1); + } else { + // For a single character, Hanzi is ready. + s[0] = lpi_items_[cand_id].hanzi; + s[1] = static_cast(0); + } + if (s_len > 0 && max_len > s_len) { + utf16_strncpy(cand_str, s, s_len); + cand_str[s_len] = (char16) '\0'; + return cand_str; + } + return NULL; + } + void MatrixSearch::update_dict_freq() { + if (NULL != user_dict_) { + // Update the total frequency of all lemmas, including system lemmas and + // user dictionary lemmas. + size_t total_freq = user_dict_->get_total_lemma_count(); + dict_trie_->set_total_lemma_count_of_others(total_freq); + } + } + bool MatrixSearch::add_lma_to_userdict(uint16 lma_fr, uint16 lma_to, + float score) { + if (lma_to - lma_fr <= 1 || NULL == user_dict_) + return false; + char16 word_str[kMaxLemmaSize + 1]; + uint16 spl_ids[kMaxLemmaSize]; + uint16 spl_id_fr = 0; + for (uint16 pos = lma_fr; pos < lma_to; pos++) { + LemmaIdType lma_id = lma_id_[pos]; + if (is_user_lemma(lma_id)) { + user_dict_->update_lemma(lma_id, 1, true); + } + uint16 lma_len = lma_start_[pos + 1] - lma_start_[pos]; + utf16_strncpy(spl_ids + spl_id_fr, spl_id_ + lma_start_[pos], lma_len); + uint16 tmp = get_lemma_str(lma_id, word_str + spl_id_fr, + kMaxLemmaSize + 1 - spl_id_fr); + assert(tmp == lma_len); + tmp = get_lemma_splids(lma_id, spl_ids + spl_id_fr, lma_len, true); + if (tmp != lma_len) { + return false; + } + spl_id_fr += lma_len; + } + assert(spl_id_fr <= kMaxLemmaSize); + return user_dict_->put_lemma(static_cast(word_str), spl_ids, + spl_id_fr, 1); + } + void MatrixSearch::debug_print_dmi(PoolPosType dmi_pos, uint16 nest_level) { + if (dmi_pos >= dmi_pool_used_) return; + DictMatchInfo *dmi = dmi_pool_ + dmi_pos; + if (1 == nest_level) { + printf("-----------------%d\'th DMI node begin----------->\n", dmi_pos); + } + if (dmi->dict_level > 1) { + debug_print_dmi(dmi->dmi_fr, nest_level + 1); + } + printf("---%d\n", dmi->dict_level); + printf(" MileStone: %x, %x\n", dmi->dict_handles[0], dmi->dict_handles[1]); + printf(" Spelling : %s, %d\n", SpellingTrie::get_instance(). + get_spelling_str(dmi->spl_id), dmi->spl_id); + printf(" Total Pinyin Len: %d\n", dmi->splstr_len); + if (1 == nest_level) { + printf("<----------------%d\'th DMI node end--------------\n\n", dmi_pos); + } + } + bool MatrixSearch::try_add_cand0_to_userdict() { + size_t new_cand_num = get_candidate_num(); + if (fixed_hzs_ > 0 && 1 == new_cand_num) { + float score_from = 0; + uint16 lma_id_from = 0; + uint16 pos = 0; + bool modified = false; + while (pos < fixed_lmas_) { + if (lma_start_[pos + 1] - lma_start_[lma_id_from] > + static_cast(kMaxLemmaSize)) { + float score_to_add = + mtrx_nd_pool_[matrix_[spl_start_[lma_start_[pos]]] + .mtrx_nd_pos].score - score_from; + if (modified) { + score_to_add += 1.0; + if (score_to_add > NGram::kMaxScore) { + score_to_add = NGram::kMaxScore; + } + add_lma_to_userdict(lma_id_from, pos, score_to_add); + } + lma_id_from = pos; + score_from += score_to_add; + + // Clear the flag for next user lemma. + modified = false; + } + if (0 == fixed_lmas_no1_[pos]) { + modified = true; + } + pos++; + } + + // Single-char word is not allowed to add to userdict. + if (lma_start_[pos] - lma_start_[lma_id_from] > 1) { + float score_to_add = + mtrx_nd_pool_[matrix_[spl_start_[lma_start_[pos]]] + .mtrx_nd_pos].score - score_from; + if (modified) { + score_to_add += 1.0; + if (score_to_add > NGram::kMaxScore) { + score_to_add = NGram::kMaxScore; + } + add_lma_to_userdict(lma_id_from, pos, score_to_add); + } + } + } + return true; + } +// Choose a candidate, and give new candidates for next step. +// If user finishes selection, we will try to communicate with user dictionary +// to add new items or update score of some existing items. +// +// Basic rule: +// 1. If user selects the first choice: +// 1.1. If the first choice is not a sentence, instead, it is a lemma: +// 1.1.1. If the first choice is a user lemma, notify the user +// dictionary that a user lemma is hit, and add occuring count +// by 1. +// 1.1.2. If the first choice is a system lemma, do nothing. +// 1.2. If the first choice is a sentence containing more than one lemma: +// 1.2.1. The whole sentence will be added as a user lemma. If the +// sentence contains user lemmas, -> hit, and add occuring count +// by 1. + size_t MatrixSearch::choose(size_t cand_id) { + if (!inited_ || 0 == pys_decoded_len_) + return 0; + if (0 == cand_id) { + fixed_hzs_ = spl_id_num_; + matrix_[spl_start_[fixed_hzs_]].mtrx_nd_fixed = mtrx_nd_pool_ + + matrix_[spl_start_[fixed_hzs_]].mtrx_nd_pos; + for (size_t pos = fixed_lmas_; pos < lma_id_num_; pos++) { + fixed_lmas_no1_[pos] = 1; + } + fixed_lmas_ = lma_id_num_; + lpi_total_ = 0; // Clean all other candidates. + + // 1. It is the first choice + if (1 == lma_id_num_) { + // 1.1. The first choice is not a sentence but a lemma + if (is_user_lemma(lma_id_[0])) { + // 1.1.1. The first choice is a user lemma, notify the user dictionary + // that it is hit. + if (NULL != user_dict_) + user_dict_->update_lemma(lma_id_[0], 1, true); + } else { + // 1.1.2. do thing for a system lemma. + } + } else { + // 1.2. The first choice is a sentence. + // 1.2.1 Try to add the whole sentence to user dictionary, the whole + // sentence may be splitted into many items. + if (NULL != user_dict_) { + try_add_cand0_to_userdict(); + } + } + update_dict_freq(); + return 1; + } else { + cand_id--; + } + + // 2. It is not the full sentence candidate. + // Find the length of the candidate. + LemmaIdType id_chosen = lpi_items_[cand_id].id; + LmaScoreType score_chosen = lpi_items_[cand_id].psb; + size_t cand_len = lpi_items_[cand_id].lma_len; + assert(cand_len > 0); + + // Notify the atom dictionary that this item is hit. + if (is_user_lemma(id_chosen)) { + if (NULL != user_dict_) { + user_dict_->update_lemma(id_chosen, 1, true); + } + update_dict_freq(); + } + + // 3. Fixed the chosen item. + // 3.1 Get the steps number. + size_t step_fr = spl_start_[fixed_hzs_]; + size_t step_to = spl_start_[fixed_hzs_ + cand_len]; + + // 3.2 Save the length of the original string. + size_t pys_decoded_len = pys_decoded_len_; + + // 3.2 Reset the space of the fixed part. + reset_search(step_to, false, false, true); + + // 3.3 For the last character of the fixed part, the previous DMI + // information will be kept, while the MTRX information will be re-extended, + // and only one node will be extended. + matrix_[step_to].mtrx_nd_num = 0; + LmaPsbItem lpi_item; + lpi_item.psb = score_chosen; + lpi_item.id = id_chosen; + PoolPosType step_to_dmi_fr = match_dmi(step_to, + spl_id_ + fixed_hzs_, cand_len); + //assert(step_to_dmi_fr != static_cast(-1)); + + extend_mtrx_nd(matrix_[step_fr].mtrx_nd_fixed, &lpi_item, 1, + step_to_dmi_fr, step_to); + matrix_[step_to].mtrx_nd_fixed = mtrx_nd_pool_ + matrix_[step_to].mtrx_nd_pos; + mtrx_nd_pool_used_ = matrix_[step_to].mtrx_nd_pos + + matrix_[step_to].mtrx_nd_num; + if (id_chosen == lma_id_[fixed_lmas_]) + fixed_lmas_no1_[fixed_lmas_] = 1; + else + fixed_lmas_no1_[fixed_lmas_] = 0; + lma_id_[fixed_lmas_] = id_chosen; + lma_start_[fixed_lmas_ + 1] = lma_start_[fixed_lmas_] + cand_len; + fixed_lmas_++; + fixed_hzs_ = fixed_hzs_ + cand_len; + while (step_to != pys_decoded_len) { + bool b = add_char(pys_[step_to]); + assert(b); + step_to++; + } + if (fixed_hzs_ < spl_id_num_) { + prepare_candidates(); + } else { + lpi_total_ = 0; + if (NULL != user_dict_) { + try_add_cand0_to_userdict(); + } + } + return get_candidate_num(); + } + size_t MatrixSearch::cancel_last_choice() { + if (!inited_ || 0 == pys_decoded_len_) + return 0; + size_t step_start = 0; + if (fixed_hzs_ > 0) { + size_t step_end = spl_start_[fixed_hzs_]; + MatrixNode *end_node = matrix_[step_end].mtrx_nd_fixed; + assert(NULL != end_node); + step_start = end_node->from->step; + if (step_start > 0) { + DictMatchInfo *dmi = dmi_pool_ + end_node->dmi_fr; + fixed_hzs_ -= dmi->dict_level; + } else { + fixed_hzs_ = 0; + } + reset_search(step_start, false, false, false); + while (pys_[step_start] != '\0') { + bool b = add_char(pys_[step_start]); + assert(b); + step_start++; + } + prepare_candidates(); + } + return get_candidate_num(); + } + size_t MatrixSearch::get_fixedlen() { + if (!inited_ || 0 == pys_decoded_len_) + return 0; + return fixed_hzs_; + } + bool MatrixSearch::prepare_add_char(char ch) { + if (pys_decoded_len_ >= kMaxRowNum - 1 || + (!spl_parser_->is_valid_to_parse(ch) && ch != '\'')) + return false; + if (dmi_pool_used_ >= kDmiPoolSize) return false; + pys_[pys_decoded_len_] = ch; + pys_decoded_len_++; + MatrixRow *mtrx_this_row = matrix_ + pys_decoded_len_; + mtrx_this_row->mtrx_nd_pos = mtrx_nd_pool_used_; + mtrx_this_row->mtrx_nd_num = 0; + mtrx_this_row->dmi_pos = dmi_pool_used_; + mtrx_this_row->dmi_num = 0; + mtrx_this_row->dmi_has_full_id = 0; + return true; + } + bool MatrixSearch::is_split_at(uint16 pos) { + return !spl_parser_->is_valid_to_parse(pys_[pos - 1]); + } + void MatrixSearch::fill_dmi(DictMatchInfo *dmi, MileStoneHandle *handles, + PoolPosType dmi_fr, uint16 spl_id, + uint16 node_num, unsigned char dict_level, + bool splid_end_split, unsigned char splstr_len, + unsigned char all_full_id) { + dmi->dict_handles[0] = handles[0]; + dmi->dict_handles[1] = handles[1]; + dmi->dmi_fr = dmi_fr; + dmi->spl_id = spl_id; + dmi->dict_level = dict_level; + dmi->splid_end_split = splid_end_split ? 1 : 0; + dmi->splstr_len = splstr_len; + dmi->all_full_id = all_full_id; + dmi->c_phrase = 0; + } + bool MatrixSearch::add_char(char ch) { + if (!prepare_add_char(ch)) + return false; + return add_char_qwerty(); + } + bool MatrixSearch::add_char_qwerty() { + matrix_[pys_decoded_len_].mtrx_nd_num = 0; + bool spl_matched = false; + uint16 longest_ext = 0; + // Extend the search matrix, from the oldest unfixed row. ext_len means + // extending length. + for (uint16 ext_len = kMaxPinyinSize + 1; ext_len > 0; ext_len--) { + if (ext_len > pys_decoded_len_ - spl_start_[fixed_hzs_]) + continue; + + // Refer to the declaration of the variable dmi_has_full_id for the + // explanation of this piece of code. In one word, it is used to prevent + // from the unwise extending of "shoud ou" but allow the reasonable + // extending of "heng ao", "lang a", etc. + if (ext_len > 1 && 0 != longest_ext && + 0 == matrix_[pys_decoded_len_ - ext_len].dmi_has_full_id) { + if (xi_an_enabled_) + continue; + else + break; + } + uint16 oldrow = pys_decoded_len_ - ext_len; + + // 0. If that row is before the last fixed step, ignore. + if (spl_start_[fixed_hzs_] > oldrow) + continue; + + // 1. Check if that old row has valid MatrixNode. If no, means that row is + // not a boundary, either a word boundary or a spelling boundary. + // If it is for extending composing phrase, it's OK to ignore the 0. + if (0 == matrix_[oldrow].mtrx_nd_num && !dmi_c_phrase_) + continue; + + // 2. Get spelling id(s) for the last ext_len chars. + uint16 spl_idx; + bool is_pre = false; + spl_idx = spl_parser_->get_splid_by_str(pys_ + oldrow, + ext_len, &is_pre); + if (is_pre) + spl_matched = true; + if (0 == spl_idx) + continue; + bool splid_end_split = is_split_at(oldrow + ext_len); + + // 3. Extend the DMI nodes of that old row + // + 1 is to extend an extra node from the root + for (PoolPosType dmi_pos = matrix_[oldrow].dmi_pos; + dmi_pos < matrix_[oldrow].dmi_pos + matrix_[oldrow].dmi_num + 1; + dmi_pos++) { + DictMatchInfo *dmi = dmi_pool_ + dmi_pos; + if (dmi_pos == matrix_[oldrow].dmi_pos + matrix_[oldrow].dmi_num) { + dmi = NULL; // The last one, NULL means extending from the root. + } else { + // If the dmi is covered by the fixed arrange, ignore it. + if (fixed_hzs_ > 0 && + pys_decoded_len_ - ext_len - dmi->splstr_len < + spl_start_[fixed_hzs_]) { + continue; + } + // If it is not in mode for composing phrase, and the source DMI node + // is marked for composing phrase, ignore this node. + if (dmi->c_phrase != 0 && !dmi_c_phrase_) { + continue; + } + } + + // For example, if "gao" is extended, "g ao" is not allowed. + // or "zh" has been passed, "z h" is not allowed. + // Both word and word-connection will be prevented. + if (longest_ext > ext_len) { + if (NULL == dmi && 0 == matrix_[oldrow].dmi_has_full_id) { + continue; + } + + // "z h" is not allowed. + if (NULL != dmi && spl_trie_->is_half_id(dmi->spl_id)) { + continue; + } + } + dep_->splids_extended = 0; + if (NULL != dmi) { + uint16 prev_ids_num = dmi->dict_level; + if ((!dmi_c_phrase_ && prev_ids_num >= kMaxLemmaSize) || + (dmi_c_phrase_ && prev_ids_num >= kMaxRowNum)) { + continue; + } + DictMatchInfo *d = dmi; + while (d) { + dep_->splids[--prev_ids_num] = d->spl_id; + if ((PoolPosType) -1 == d->dmi_fr) + break; + d = dmi_pool_ + d->dmi_fr; + } + assert(0 == prev_ids_num); + dep_->splids_extended = dmi->dict_level; + } + dep_->splids[dep_->splids_extended] = spl_idx; + dep_->ext_len = ext_len; + dep_->splid_end_split = splid_end_split; + dep_->id_num = 1; + dep_->id_start = spl_idx; + if (spl_trie_->is_half_id(spl_idx)) { + // Get the full id list + dep_->id_num = spl_trie_->half_to_full(spl_idx, &(dep_->id_start)); + assert(dep_->id_num > 0); + } + uint16 new_dmi_num; + new_dmi_num = extend_dmi(dep_, dmi); + if (new_dmi_num > 0) { + if (dmi_c_phrase_) { + dmi_pool_[dmi_pool_used_].c_phrase = 1; + } + matrix_[pys_decoded_len_].dmi_num += new_dmi_num; + dmi_pool_used_ += new_dmi_num; + if (!spl_trie_->is_half_id(spl_idx)) + matrix_[pys_decoded_len_].dmi_has_full_id = 1; + } + + // If get candiate lemmas, try to extend the path + if (lpi_total_ > 0) { + uint16 fr_row; + if (NULL == dmi) { + fr_row = oldrow; + } else { + assert(oldrow >= dmi->splstr_len); + fr_row = oldrow - dmi->splstr_len; + } + for (PoolPosType mtrx_nd_pos = matrix_[fr_row].mtrx_nd_pos; + mtrx_nd_pos < matrix_[fr_row].mtrx_nd_pos + + matrix_[fr_row].mtrx_nd_num; + mtrx_nd_pos++) { + MatrixNode *mtrx_nd = mtrx_nd_pool_ + mtrx_nd_pos; + extend_mtrx_nd(mtrx_nd, lpi_items_, lpi_total_, + dmi_pool_used_ - new_dmi_num, pys_decoded_len_); + if (longest_ext == 0) + longest_ext = ext_len; + } + } + } // for dmi_pos + } // for ext_len + mtrx_nd_pool_used_ += matrix_[pys_decoded_len_].mtrx_nd_num; + if (dmi_c_phrase_) + return true; + return (matrix_[pys_decoded_len_].mtrx_nd_num != 0 || spl_matched); + } + void MatrixSearch::prepare_candidates() { + // Get candiates from the first un-fixed step. + uint16 lma_size_max = kMaxLemmaSize; + if (lma_size_max > spl_id_num_ - fixed_hzs_) + lma_size_max = spl_id_num_ - fixed_hzs_; + uint16 lma_size = lma_size_max; + + // If the full sentense candidate's unfixed part may be the same with a normal + // lemma. Remove the lemma candidate in this case. + char16 fullsent[kMaxLemmaSize + 1]; + char16 *pfullsent = NULL; + uint16 sent_len; + pfullsent = get_candidate0(fullsent, kMaxLemmaSize + 1, &sent_len, true); + + // If the unfixed part contains more than one ids, it is not necessary to + // check whether a lemma's string is the same to the unfixed part of the full + // sentence candidate, so, set it to NULL; + if (sent_len > kMaxLemmaSize) + pfullsent = NULL; + lpi_total_ = 0; + size_t lpi_num_full_match = 0; // Number of items which are fully-matched. + while (lma_size > 0) { + size_t lma_num; + lma_num = get_lpis(spl_id_ + fixed_hzs_, lma_size, + lpi_items_ + lpi_total_, + size_t(kMaxLmaPsbItems - lpi_total_), + pfullsent, lma_size == lma_size_max); + if (lma_num > 0) { + lpi_total_ += lma_num; + // For next lemma candidates which are not the longest, it is not + // necessary to compare with the full sentence candiate. + pfullsent = NULL; + } + if (lma_size == lma_size_max) { + lpi_num_full_match = lpi_total_; + } + lma_size--; + } + + // Sort those partially-matched items by their unified scores. + myqsort(lpi_items_ + lpi_num_full_match, lpi_total_ - lpi_num_full_match, + sizeof(LmaPsbItem), cmp_lpi_with_unified_psb); + if (kPrintDebug0) { + printf("-----Prepare candidates, score:\n"); + for (size_t a = 0; a < lpi_total_; a++) { + printf("[%03d]%d ", a, lpi_items_[a].psb); + if ((a + 1) % 6 == 0) printf("\n"); + } + printf("\n"); + } + if (kPrintDebug0) { + printf("--- lpi_total_ = %d\n", lpi_total_); + } + } + const char *MatrixSearch::get_pystr(size_t *decoded_len) { + if (!inited_ || NULL == decoded_len) + return NULL; + *decoded_len = pys_decoded_len_; + return pys_; + } + void MatrixSearch::merge_fixed_lmas(size_t del_spl_pos) { + if (fixed_lmas_ == 0) + return; + // Update spelling segmentation information first. + spl_id_num_ -= 1; + uint16 del_py_len = spl_start_[del_spl_pos + 1] - spl_start_[del_spl_pos]; + for (size_t pos = del_spl_pos; pos <= spl_id_num_; pos++) { + spl_start_[pos] = spl_start_[pos + 1] - del_py_len; + if (pos == spl_id_num_) + break; + spl_id_[pos] = spl_id_[pos + 1]; + } + + // Begin to merge. + uint16 phrase_len = 0; + + // Update the spelling ids to the composing phrase. + // We need to convert these ids into full id in the future. + memcpy(c_phrase_.spl_ids, spl_id_, spl_id_num_ * sizeof(uint16)); + memcpy(c_phrase_.spl_start, spl_start_, (spl_id_num_ + 1) * sizeof(uint16)); + + // If composing phrase has not been created, first merge all fixed + // lemmas into a composing phrase without deletion. + if (fixed_lmas_ > 1 || kLemmaIdComposing != lma_id_[0]) { + uint16 bp = 1; // Begin position of real fixed lemmas. + // There is no existing composing phrase. + if (kLemmaIdComposing != lma_id_[0]) { + c_phrase_.sublma_num = 0; + bp = 0; + } + uint16 sub_num = c_phrase_.sublma_num; + for (uint16 pos = bp; pos <= fixed_lmas_; pos++) { + c_phrase_.sublma_start[sub_num + pos - bp] = lma_start_[pos]; + if (lma_start_[pos] > del_spl_pos) { + c_phrase_.sublma_start[sub_num + pos - bp] -= 1; + } + if (pos == fixed_lmas_) + break; + uint16 lma_len; + char16 *lma_str = c_phrase_.chn_str + + c_phrase_.sublma_start[sub_num] + phrase_len; + lma_len = get_lemma_str(lma_id_[pos], lma_str, kMaxRowNum - phrase_len); + assert(lma_len == lma_start_[pos + 1] - lma_start_[pos]); + phrase_len += lma_len; + } + assert(phrase_len == lma_start_[fixed_lmas_]); + c_phrase_.length = phrase_len; // will be deleted by 1 + c_phrase_.sublma_num += fixed_lmas_ - bp; + } else { + for (uint16 pos = 0; pos <= c_phrase_.sublma_num; pos++) { + if (c_phrase_.sublma_start[pos] > del_spl_pos) { + c_phrase_.sublma_start[pos] -= 1; + } + } + phrase_len = c_phrase_.length; + } + assert(phrase_len > 0); + if (1 == phrase_len) { + // After the only one is deleted, nothing will be left. + fixed_lmas_ = 0; + return; + } + + // Delete the Chinese character in the merged phrase. + // The corresponding elements in spl_ids and spl_start of the + // phrase have been deleted. + char16 *chn_str = c_phrase_.chn_str + del_spl_pos; + for (uint16 pos = 0; + pos < c_phrase_.sublma_start[c_phrase_.sublma_num] - del_spl_pos; + pos++) { + chn_str[pos] = chn_str[pos + 1]; + } + c_phrase_.length -= 1; + + // If the deleted spelling id is in a sub lemma which contains more than + // one id, del_a_sub will be false; but if the deleted id is in a sub lemma + // which only contains 1 id, the whole sub lemma needs to be deleted, so + // del_a_sub will be true. + bool del_a_sub = false; + for (uint16 pos = 1; pos <= c_phrase_.sublma_num; pos++) { + if (c_phrase_.sublma_start[pos - 1] == + c_phrase_.sublma_start[pos]) { + del_a_sub = true; + } + if (del_a_sub) { + c_phrase_.sublma_start[pos - 1] = + c_phrase_.sublma_start[pos]; + } + } + if (del_a_sub) + c_phrase_.sublma_num -= 1; + return; + } + void MatrixSearch::get_spl_start_id() { + lma_id_num_ = 0; + lma_start_[0] = 0; + spl_id_num_ = 0; + spl_start_[0] = 0; + if (!inited_ || 0 == pys_decoded_len_ || + 0 == matrix_[pys_decoded_len_].mtrx_nd_num) + return; + + // Calculate number of lemmas and spellings + // Only scan those part which is not fixed. + lma_id_num_ = fixed_lmas_; + spl_id_num_ = fixed_hzs_; + MatrixNode *mtrx_nd = mtrx_nd_pool_ + matrix_[pys_decoded_len_].mtrx_nd_pos; + while (mtrx_nd != mtrx_nd_pool_) { + if (fixed_hzs_ > 0) { + if (mtrx_nd->step <= spl_start_[fixed_hzs_]) + break; + } + + // Update the spelling segamentation information + unsigned char word_splstr_len = 0; + PoolPosType dmi_fr = mtrx_nd->dmi_fr; + if ((PoolPosType) -1 != dmi_fr) + word_splstr_len = dmi_pool_[dmi_fr].splstr_len; + while ((PoolPosType) -1 != dmi_fr) { + spl_start_[spl_id_num_ + 1] = mtrx_nd->step - + (word_splstr_len - dmi_pool_[dmi_fr].splstr_len); + spl_id_[spl_id_num_] = dmi_pool_[dmi_fr].spl_id; + spl_id_num_++; + dmi_fr = dmi_pool_[dmi_fr].dmi_fr; + } + + // Update the lemma segmentation information + lma_start_[lma_id_num_ + 1] = spl_id_num_; + lma_id_[lma_id_num_] = mtrx_nd->id; + lma_id_num_++; + mtrx_nd = mtrx_nd->from; + } + + // Reverse the result of spelling info + for (size_t pos = fixed_hzs_; + pos < fixed_hzs_ + (spl_id_num_ - fixed_hzs_ + 1) / 2; pos++) { + if (spl_id_num_ + fixed_hzs_ - pos != pos + 1) { + spl_start_[pos + 1] ^= spl_start_[spl_id_num_ - pos + fixed_hzs_]; + spl_start_[spl_id_num_ - pos + fixed_hzs_] ^= spl_start_[pos + 1]; + spl_start_[pos + 1] ^= spl_start_[spl_id_num_ - pos + fixed_hzs_]; + spl_id_[pos] ^= spl_id_[spl_id_num_ + fixed_hzs_ - pos - 1]; + spl_id_[spl_id_num_ + fixed_hzs_ - pos - 1] ^= spl_id_[pos]; + spl_id_[pos] ^= spl_id_[spl_id_num_ + fixed_hzs_ - pos - 1]; + } + } + + // Reverse the result of lemma info + for (size_t pos = fixed_lmas_; + pos < fixed_lmas_ + (lma_id_num_ - fixed_lmas_ + 1) / 2; pos++) { + assert(lma_id_num_ + fixed_lmas_ - pos - 1 >= pos); + if (lma_id_num_ + fixed_lmas_ - pos > pos + 1) { + lma_start_[pos + 1] ^= lma_start_[lma_id_num_ - pos + fixed_lmas_]; + lma_start_[lma_id_num_ - pos + fixed_lmas_] ^= lma_start_[pos + 1]; + lma_start_[pos + 1] ^= lma_start_[lma_id_num_ - pos + fixed_lmas_]; + lma_id_[pos] ^= lma_id_[lma_id_num_ - 1 - pos + fixed_lmas_]; + lma_id_[lma_id_num_ - 1 - pos + fixed_lmas_] ^= lma_id_[pos]; + lma_id_[pos] ^= lma_id_[lma_id_num_ - 1 - pos + fixed_lmas_]; + } + } + for (size_t pos = fixed_lmas_ + 1; pos <= lma_id_num_; pos++) { + if (pos < lma_id_num_) + lma_start_[pos] = lma_start_[pos - 1] + + (lma_start_[pos] - lma_start_[pos + 1]); + else + lma_start_[pos] = lma_start_[pos - 1] + lma_start_[pos] - + lma_start_[fixed_lmas_]; + } + + // Find the last fixed position + fixed_hzs_ = 0; + for (size_t pos = spl_id_num_; pos > 0; pos--) { + if (NULL != matrix_[spl_start_[pos]].mtrx_nd_fixed) { + fixed_hzs_ = pos; + break; + } + } + return; + } + size_t MatrixSearch::get_spl_start(const uint16 *&spl_start) { + get_spl_start_id(); + spl_start = spl_start_; + return spl_id_num_; + } + size_t MatrixSearch::extend_dmi(DictExtPara *dep, DictMatchInfo *dmi_s) { + if (dmi_pool_used_ >= kDmiPoolSize) return 0; + if (dmi_c_phrase_) + return extend_dmi_c(dep, dmi_s); + LpiCache &lpi_cache = LpiCache::get_instance(); + uint16 splid = dep->splids[dep->splids_extended]; + bool cached = false; + if (0 == dep->splids_extended) + cached = lpi_cache.is_cached(splid); + + // 1. If this is a half Id, get its corresponding full starting Id and + // number of full Id. + size_t ret_val = 0; + PoolPosType mtrx_dmi_fr = (PoolPosType) -1; // From which dmi node + + lpi_total_ = 0; + MileStoneHandle from_h[3]; + from_h[0] = 0; + from_h[1] = 0; + if (0 != dep->splids_extended) { + from_h[0] = dmi_s->dict_handles[0]; + from_h[1] = dmi_s->dict_handles[1]; + } + + // 2. Begin exgtending in the system dictionary + size_t lpi_num = 0; + MileStoneHandle handles[2]; + handles[0] = handles[1] = 0; + if (from_h[0] > 0 || NULL == dmi_s) { + handles[0] = dict_trie_->extend_dict(from_h[0], dep, lpi_items_, + kMaxLmaPsbItems, &lpi_num); + } + if (handles[0] > 0) + lpi_total_ = lpi_num; + if (NULL == dmi_s) { // from root + assert(0 != handles[0]); + mtrx_dmi_fr = dmi_pool_used_; + } + + // 3. Begin extending in the user dictionary + if (NULL != user_dict_ && (from_h[1] > 0 || NULL == dmi_s)) { + handles[1] = user_dict_->extend_dict(from_h[1], dep, + lpi_items_ + lpi_total_, + kMaxLmaPsbItems - lpi_total_, + &lpi_num); + if (handles[1] > 0) { + if (kPrintDebug0) { + for (size_t t = 0; t < lpi_num; t++) { + printf("--Extend in user dict: uid:%d uscore:%d\n", lpi_items_[lpi_total_ + t].id, + lpi_items_[lpi_total_ + t].psb); + } + } + lpi_total_ += lpi_num; + } + } + if (0 != handles[0] || 0 != handles[1]) { + if (dmi_pool_used_ >= kDmiPoolSize) return 0; + DictMatchInfo *dmi_add = dmi_pool_ + dmi_pool_used_; + if (NULL == dmi_s) { + fill_dmi(dmi_add, handles, + (PoolPosType) -1, splid, + 1, 1, dep->splid_end_split, dep->ext_len, + spl_trie_->is_half_id(splid) ? 0 : 1); + } else { + fill_dmi(dmi_add, handles, + dmi_s - dmi_pool_, splid, 1, + dmi_s->dict_level + 1, dep->splid_end_split, + dmi_s->splstr_len + dep->ext_len, + spl_trie_->is_half_id(splid) ? 0 : dmi_s->all_full_id); + } + ret_val = 1; + } + if (!cached) { + if (0 == lpi_total_) + return ret_val; + if (kPrintDebug0) { + printf("--- lpi_total_ = %d\n", lpi_total_); + } + myqsort(lpi_items_, lpi_total_, sizeof(LmaPsbItem), cmp_lpi_with_psb); + if (NULL == dmi_s && spl_trie_->is_half_id(splid)) + lpi_total_ = lpi_cache.put_cache(splid, lpi_items_, lpi_total_); + } else { + assert(spl_trie_->is_half_id(splid)); + lpi_total_ = lpi_cache.get_cache(splid, lpi_items_, kMaxLmaPsbItems); + } + return ret_val; + } + size_t MatrixSearch::extend_dmi_c(DictExtPara *dep, DictMatchInfo *dmi_s) { + lpi_total_ = 0; + uint16 pos = dep->splids_extended; + assert(dmi_c_phrase_); + if (pos >= c_phrase_.length) + return 0; + uint16 splid = dep->splids[pos]; + if (splid == c_phrase_.spl_ids[pos]) { + DictMatchInfo *dmi_add = dmi_pool_ + dmi_pool_used_; + MileStoneHandle handles[2]; // Actually never used. + if (NULL == dmi_s) + fill_dmi(dmi_add, handles, + (PoolPosType) -1, splid, + 1, 1, dep->splid_end_split, dep->ext_len, + spl_trie_->is_half_id(splid) ? 0 : 1); + else + fill_dmi(dmi_add, handles, + dmi_s - dmi_pool_, splid, 1, + dmi_s->dict_level + 1, dep->splid_end_split, + dmi_s->splstr_len + dep->ext_len, + spl_trie_->is_half_id(splid) ? 0 : dmi_s->all_full_id); + if (pos == c_phrase_.length - 1) { + lpi_items_[0].id = kLemmaIdComposing; + lpi_items_[0].psb = 0; // 0 is bigger than normal lemma score. + lpi_total_ = 1; + } + return 1; + } + return 0; + } + size_t MatrixSearch::extend_mtrx_nd(MatrixNode *mtrx_nd, LmaPsbItem lpi_items[], + size_t lpi_num, PoolPosType dmi_fr, + size_t res_row) { + assert(NULL != mtrx_nd); + matrix_[res_row].mtrx_nd_fixed = NULL; + if (mtrx_nd_pool_used_ >= kMtrxNdPoolSize - kMaxNodeARow) + return 0; + if (0 == mtrx_nd->step) { + // Because the list is sorted, if the source step is 0, it is only + // necessary to pick up the first kMaxNodeARow items. + if (lpi_num > kMaxNodeARow) + lpi_num = kMaxNodeARow; + } + MatrixNode *mtrx_nd_res_min = mtrx_nd_pool_ + matrix_[res_row].mtrx_nd_pos; + for (size_t pos = 0; pos < lpi_num; pos++) { + float score = mtrx_nd->score + lpi_items[pos].psb; + if (pos > 0 && score - PRUMING_SCORE > mtrx_nd_res_min->score) + break; + + // Try to add a new node + size_t mtrx_nd_num = matrix_[res_row].mtrx_nd_num; + MatrixNode *mtrx_nd_res = mtrx_nd_res_min + mtrx_nd_num; + bool replace = false; + // Find its position + while (mtrx_nd_res > mtrx_nd_res_min && score < (mtrx_nd_res - 1)->score) { + if (static_cast(mtrx_nd_res - mtrx_nd_res_min) < kMaxNodeARow) + *mtrx_nd_res = *(mtrx_nd_res - 1); + mtrx_nd_res--; + replace = true; + } + if (replace || (mtrx_nd_num < kMaxNodeARow && + matrix_[res_row].mtrx_nd_pos + mtrx_nd_num < kMtrxNdPoolSize)) { + mtrx_nd_res->id = lpi_items[pos].id; + mtrx_nd_res->score = score; + mtrx_nd_res->from = mtrx_nd; + mtrx_nd_res->dmi_fr = dmi_fr; + mtrx_nd_res->step = res_row; + if (matrix_[res_row].mtrx_nd_num < kMaxNodeARow) + matrix_[res_row].mtrx_nd_num++; + } + } + return matrix_[res_row].mtrx_nd_num; + } + PoolPosType MatrixSearch::match_dmi(size_t step_to, uint16 spl_ids[], + uint16 spl_id_num) { + if (pys_decoded_len_ < step_to || 0 == matrix_[step_to].dmi_num) { + return static_cast(-1); + } + for (PoolPosType dmi_pos = 0; dmi_pos < matrix_[step_to].dmi_num; dmi_pos++) { + DictMatchInfo *dmi = dmi_pool_ + matrix_[step_to].dmi_pos + dmi_pos; + if (dmi->dict_level != spl_id_num) + continue; + bool matched = true; + for (uint16 spl_pos = 0; spl_pos < spl_id_num; spl_pos++) { + if (spl_ids[spl_id_num - spl_pos - 1] != dmi->spl_id) { + matched = false; + break; + } + dmi = dmi_pool_ + dmi->dmi_fr; + } + if (matched) { + return matrix_[step_to].dmi_pos + dmi_pos; + } + } + return static_cast(-1); + } + char16 *MatrixSearch::get_candidate0(char16 *cand_str, size_t max_len, + uint16 *retstr_len, + bool only_unfixed) { + if (pys_decoded_len_ == 0 || + matrix_[pys_decoded_len_].mtrx_nd_num == 0) + return NULL; + LemmaIdType idxs[kMaxRowNum]; + size_t id_num = 0; + MatrixNode *mtrx_nd = mtrx_nd_pool_ + matrix_[pys_decoded_len_].mtrx_nd_pos; + if (kPrintDebug0) { + printf("--- sentence score: %f\n", mtrx_nd->score); + } + if (kPrintDebug1) { + printf("==============Sentence DMI (reverse order) begin===========>>\n"); + } + while (mtrx_nd != NULL) { + idxs[id_num] = mtrx_nd->id; + id_num++; + if (kPrintDebug1) { + printf("---MatrixNode [step: %d, lma_idx: %d, total score:%.5f]\n", + mtrx_nd->step, mtrx_nd->id, mtrx_nd->score); + debug_print_dmi(mtrx_nd->dmi_fr, 1); + } + mtrx_nd = mtrx_nd->from; + } + if (kPrintDebug1) { + printf("<<==============Sentence DMI (reverse order) end=============\n"); + } + size_t ret_pos = 0; + do { + id_num--; + if (0 == idxs[id_num]) + continue; + char16 str[kMaxLemmaSize + 1]; + uint16 str_len = get_lemma_str(idxs[id_num], str, kMaxLemmaSize + 1); + if (str_len > 0 && ((!only_unfixed && max_len - ret_pos > str_len) || + (only_unfixed && max_len - ret_pos + fixed_hzs_ > str_len))) { + if (!only_unfixed) + utf16_strncpy(cand_str + ret_pos, str, str_len); + else if (ret_pos >= fixed_hzs_) + utf16_strncpy(cand_str + ret_pos - fixed_hzs_, str, str_len); + ret_pos += str_len; + } else { + return NULL; + } + } while (id_num != 0); + if (!only_unfixed) { + if (NULL != retstr_len) + *retstr_len = ret_pos; + cand_str[ret_pos] = (char16) '\0'; + } else { + if (NULL != retstr_len) + *retstr_len = ret_pos - fixed_hzs_; + cand_str[ret_pos - fixed_hzs_] = (char16) '\0'; + } + return cand_str; + } + size_t MatrixSearch::get_lpis(const uint16 *splid_str, size_t splid_str_len, + LmaPsbItem *lma_buf, size_t max_lma_buf, + const char16 *pfullsent, bool sort_by_psb) { + if (splid_str_len > kMaxLemmaSize) + return 0; + size_t num1 = dict_trie_->get_lpis(splid_str, splid_str_len, + lma_buf, max_lma_buf); + size_t num2 = 0; + if (NULL != user_dict_) { + num2 = user_dict_->get_lpis(splid_str, splid_str_len, + lma_buf + num1, max_lma_buf - num1); + } + size_t num = num1 + num2; + if (0 == num) + return 0; + + // Remove repeated items. + if (splid_str_len > 1) { + LmaPsbStrItem *lpsis = reinterpret_cast(lma_buf + num); + size_t lpsi_num = (max_lma_buf - num) * sizeof(LmaPsbItem) / + sizeof(LmaPsbStrItem); + //assert(lpsi_num > num); + if (num > lpsi_num) num = lpsi_num; + lpsi_num = num; + for (size_t pos = 0; pos < lpsi_num; pos++) { + lpsis[pos].lpi = lma_buf[pos]; + get_lemma_str(lma_buf[pos].id, lpsis[pos].str, kMaxLemmaSize + 1); + } + myqsort(lpsis, lpsi_num, sizeof(LmaPsbStrItem), cmp_lpsi_with_str); + size_t remain_num = 0; + for (size_t pos = 0; pos < lpsi_num; pos++) { + if (pos > 0 && utf16_strcmp(lpsis[pos].str, lpsis[pos - 1].str) == 0) { + if (lpsis[pos].lpi.psb < lpsis[pos - 1].lpi.psb) { + assert(remain_num > 0); + lma_buf[remain_num - 1] = lpsis[pos].lpi; + } + continue; + } + if (NULL != pfullsent && utf16_strcmp(lpsis[pos].str, pfullsent) == 0) + continue; + lma_buf[remain_num] = lpsis[pos].lpi; + remain_num++; + } + + // Update the result number + num = remain_num; + } else { + // For single character, some characters have more than one spelling, for + // example, "de" and "di" are all valid for a Chinese character, so when + // the user input "d", repeated items are generated. + // For single character lemmas, Hanzis will be gotten + for (size_t pos = 0; pos < num; pos++) { + char16 hanzis[2]; + get_lemma_str(lma_buf[pos].id, hanzis, 2); + lma_buf[pos].hanzi = hanzis[0]; + } + myqsort(lma_buf, num, sizeof(LmaPsbItem), cmp_lpi_with_hanzi); + size_t remain_num = 0; + for (size_t pos = 0; pos < num; pos++) { + if (pos > 0 && lma_buf[pos].hanzi == lma_buf[pos - 1].hanzi) { + if (NULL != pfullsent && + static_cast(0) == pfullsent[1] && + lma_buf[pos].hanzi == pfullsent[0]) + continue; + if (lma_buf[pos].psb < lma_buf[pos - 1].psb) { + assert(remain_num > 0); + assert(lma_buf[remain_num - 1].hanzi == lma_buf[pos].hanzi); + lma_buf[remain_num - 1] = lma_buf[pos]; + } + continue; + } + if (NULL != pfullsent && + static_cast(0) == pfullsent[1] && + lma_buf[pos].hanzi == pfullsent[0]) + continue; + lma_buf[remain_num] = lma_buf[pos]; + remain_num++; + } + num = remain_num; + } + if (sort_by_psb) { + myqsort(lma_buf, num, sizeof(LmaPsbItem), cmp_lpi_with_psb); + } + return num; + } + uint16 MatrixSearch::get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, + uint16 str_max) { + uint16 str_len = 0; + if (is_system_lemma(id_lemma)) { + str_len = dict_trie_->get_lemma_str(id_lemma, str_buf, str_max); + } else if (is_user_lemma(id_lemma)) { + if (NULL != user_dict_) { + str_len = user_dict_->get_lemma_str(id_lemma, str_buf, str_max); + } else { + str_len = 0; + str_buf[0] = static_cast('\0'); + } + } else if (is_composing_lemma(id_lemma)) { + if (str_max <= 1) + return 0; + str_len = c_phrase_.sublma_start[c_phrase_.sublma_num]; + if (str_len > str_max - 1) + str_len = str_max - 1; + utf16_strncpy(str_buf, c_phrase_.chn_str, str_len); + str_buf[str_len] = (char16) '\0'; + return str_len; + } + return str_len; + } + uint16 MatrixSearch::get_lemma_splids(LemmaIdType id_lemma, uint16 *splids, + uint16 splids_max, bool arg_valid) { + uint16 splid_num = 0; + if (arg_valid) { + for (splid_num = 0; splid_num < splids_max; splid_num++) { + if (spl_trie_->is_half_id(splids[splid_num])) + break; + } + if (splid_num == splids_max) + return splid_num; + } + if (is_system_lemma(id_lemma)) { + splid_num = dict_trie_->get_lemma_splids(id_lemma, splids, splids_max, + arg_valid); + } else if (is_user_lemma(id_lemma)) { + if (NULL != user_dict_) { + splid_num = user_dict_->get_lemma_splids(id_lemma, splids, splids_max, + arg_valid); + } else { + splid_num = 0; + } + } else if (is_composing_lemma(id_lemma)) { + if (c_phrase_.length > splids_max) { + return 0; + } + for (uint16 pos = 0; pos < c_phrase_.length; pos++) { + splids[pos] = c_phrase_.spl_ids[pos]; + if (spl_trie_->is_half_id(splids[pos])) { + return 0; + } + } + } + return splid_num; + } + size_t MatrixSearch::inner_predict(const char16 *fixed_buf, uint16 fixed_len, + char16 predict_buf[][kMaxPredictSize + 1], + size_t buf_len) { + size_t res_total = 0; + memset(npre_items_, 0, sizeof(NPredictItem) * npre_items_len_); + // In order to shorten the comments, j-character candidates predicted by + // i-character prefix are called P(i,j). All candiates predicted by + // i-character prefix are called P(i,*) + // Step 1. Get P(kMaxPredictSize, *) and sort them, here + // P(kMaxPredictSize, *) == P(kMaxPredictSize, 1) + for (size_t len = fixed_len; len > 0; len--) { + // How many blank items are available + size_t this_max = npre_items_len_ - res_total; + size_t res_this; + // If the history is longer than 1, and we can not get prediction from + // lemmas longer than 2, in this case, we will add lemmas with + // highest scores as the prediction result. + if (fixed_len > 1 && 1 == len && 0 == res_total) { + // Try to find if recent n (n>1) characters can be a valid lemma in system + // dictionary. + bool nearest_n_word = false; + for (size_t nlen = 2; nlen <= fixed_len; nlen++) { + if (dict_trie_->get_lemma_id(fixed_buf + fixed_len - nlen, nlen) > 0) { + nearest_n_word = true; + break; + } + } + res_this = dict_trie_->predict_top_lmas(nearest_n_word ? len : 0, + npre_items_ + res_total, + this_max, res_total); + res_total += res_this; + } + + // How many blank items are available + this_max = npre_items_len_ - res_total; + res_this = 0; + if (!kOnlyUserDictPredict) { + res_this = + dict_trie_->predict(fixed_buf + fixed_len - len, len, + npre_items_ + res_total, this_max, + res_total); + } + if (NULL != user_dict_) { + res_this = res_this + + user_dict_->predict(fixed_buf + fixed_len - len, len, + npre_items_ + res_total + res_this, + this_max - res_this, res_total + res_this); + } + if (kPredictLimitGt1) { + myqsort(npre_items_ + res_total, res_this, sizeof(NPredictItem), + cmp_npre_by_score); + if (len > 3) { + if (res_this > kMaxPredictNumByGt3) + res_this = kMaxPredictNumByGt3; + } else if (3 == len) { + if (res_this > kMaxPredictNumBy3) + res_this = kMaxPredictNumBy3; + } else if (2 == len) { + if (res_this > kMaxPredictNumBy2) + res_this = kMaxPredictNumBy2; + } + } + res_total += res_this; + } + res_total = remove_duplicate_npre(npre_items_, res_total); + if (kPreferLongHistoryPredict) { + myqsort(npre_items_, res_total, sizeof(NPredictItem), + cmp_npre_by_hislen_score); + } else { + myqsort(npre_items_, res_total, sizeof(NPredictItem), + cmp_npre_by_score); + } + if (buf_len < res_total) { + res_total = buf_len; + } + if (kPrintDebug2) { + printf("/////////////////Predicted Items Begin////////////////////>>\n"); + for (size_t i = 0; i < res_total; i++) { + printf("---"); + for (size_t j = 0; j < kMaxPredictSize; j++) { + printf("%d ", npre_items_[i].pre_hzs[j]); + } + printf("\n"); + } + printf("< kMaxPredictSize || 0 == buf_len) + return 0; + return inner_predict(fixed_buf, fixed_len, predict_buf, buf_len); + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/matrixsearch.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/matrixsearch.h new file mode 100644 index 0000000..76293f0 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/matrixsearch.h @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_ANDPY_INCLUDE_MATRIXSEARCH_H__ +#define PINYINIME_ANDPY_INCLUDE_MATRIXSEARCH_H__ +#include +#include "./atomdictbase.h" +#include "./dicttrie.h" +#include "./searchutility.h" +#include "./spellingtrie.h" +#include "./splparser.h" +namespace ime_pinyin { + static const size_t kMaxRowNum = kMaxSearchSteps; + typedef struct { + // MileStoneHandle objects for the system and user dictionaries. + MileStoneHandle dict_handles[2]; + // From which DMI node. -1 means it's from root. + PoolPosType dmi_fr; + // The spelling id for the Pinyin string from the previous DMI to this node. + // If it is a half id like Shengmu, the node pointed by dict_node is the first + // node with this Shengmu, + uint16 spl_id; + // What's the level of the dict node. Level of root is 0, but root is never + // recorded by dict_node. + unsigned char dict_level: 7; + // If this node is for composing phrase, this bit is 1. + unsigned char c_phrase: 1; + // Whether the spl_id is parsed with a split character at the end. + unsigned char splid_end_split: 1; + // What's the length of the spelling string for this match, for the whole + // word. + unsigned char splstr_len: 7; + // Used to indicate whether all spelling ids from the root are full spelling + // ids. This information is useful for keymapping mode(not finished). Because + // in this mode, there is no clear boundaries, we prefer those results which + // have full spelling ids. + unsigned char all_full_id: 1; + } DictMatchInfo, *PDictMatchInfo; + typedef struct MatrixNode { + LemmaIdType id; + float score; + MatrixNode *from; + // From which DMI node. Used to trace the spelling segmentation. + PoolPosType dmi_fr; + uint16 step; + } MatrixNode, *PMatrixNode; + typedef struct { + // The MatrixNode position in the matrix pool + PoolPosType mtrx_nd_pos; + // The DictMatchInfo position in the DictMatchInfo pool. + PoolPosType dmi_pos; + uint16 mtrx_nd_num; + uint16 dmi_num: 15; + // Used to indicate whether there are dmi nodes in this step with full + // spelling id. This information is used to decide whether a substring of a + // valid Pinyin should be extended. + // + // Example1: shoudao + // When the last char 'o' is added, the parser will find "dao" is a valid + // Pinyin, and because all dmi nodes at location 'd' (including those for + // "shoud", and those for "d") have Shengmu id only, so it is not necessary + // to extend "ao", otherwise the result may be "shoud ao", that is not + // reasonable. + // + // Example2: hengao + // When the last 'o' is added, the parser finds "gao" is a valid Pinyin. + // Because some dmi nodes at 'g' has Shengmu ids (hen'g and g), but some dmi + // nodes at 'g' has full ids ('heng'), so it is necessary to extend "ao", thus + // "heng ao" can also be the result. + // + // Similarly, "ganga" is expanded to "gang a". + // + // For Pinyin string "xian", because "xian" is a valid Pinyin, because all dmi + // nodes at 'x' only have Shengmu ids, the parser will not try "x ian" (and it + // is not valid either). If the parser uses break in the loop, the result + // always be "xian"; but if the parser uses continue in the loop, "xi an" will + // also be tried. This behaviour can be set via the function + // set_xi_an_switch(). + uint16 dmi_has_full_id: 1; + // Points to a MatrixNode of the current step to indicate which choice the + // user selects. + MatrixNode *mtrx_nd_fixed; + } MatrixRow, *PMatrixRow; +// When user inputs and selects candidates, the fixed lemma ids are stored in +// lma_id_ of class MatrixSearch, and fixed_lmas_ is used to indicate how many +// lemmas from the beginning are fixed. If user deletes Pinyin characters one +// by one from the end, these fixed lemmas can be unlocked one by one when +// necessary. Whenever user deletes a Chinese character and its spelling string +// in these fixed lemmas, all fixed lemmas will be merged together into a unit +// named ComposingPhrase with a lemma id kLemmaIdComposing, and this composing +// phrase will be the first lemma in the sentence. Because it contains some +// modified lemmas (by deleting a character), these merged lemmas are called +// sub lemmas (sublma), and each of them are represented individually, so that +// when user deletes Pinyin characters from the end, these sub lemmas can also +// be unlocked one by one. + typedef struct { + uint16 spl_ids[kMaxRowNum]; + uint16 spl_start[kMaxRowNum]; + char16 chn_str[kMaxRowNum]; // Chinese string. + uint16 sublma_start[kMaxRowNum]; // Counted in Chinese characters. + size_t sublma_num; + uint16 length; // Counted in Chinese characters. + } ComposingPhrase, *TComposingPhrase; + class MatrixSearch { + private: + // If it is true, prediction list by string whose length is greater than 1 + // will be limited to a reasonable number. + static const bool kPredictLimitGt1 = false; + // If it is true, the engine will prefer long history based prediction, + // for example, when user inputs "BeiJing", we prefer "DaXue", etc., which are + // based on the two-character history. + static const bool kPreferLongHistoryPredict = true; + // If it is true, prediction will only be based on user dictionary. this flag + // is for debug purpose. + static const bool kOnlyUserDictPredict = false; + // The maximum buffer to store LmaPsbItems. + static const size_t kMaxLmaPsbItems = 1450; + // How many rows for each step. + static const size_t kMaxNodeARow = 5; + // The maximum length of the sentence candidates counted in chinese + // characters + static const size_t kMaxSentenceLength = 16; + // The size of the matrix node pool. + static const size_t kMtrxNdPoolSize = 200; + // The size of the DMI node pool. + static const size_t kDmiPoolSize = 800; + // Used to indicate whether this object has been initialized. + bool inited_; + // Spelling trie. + const SpellingTrie *spl_trie_; + // Used to indicate this switcher status: when "xian" is parseed, should + // "xi an" also be extended. Default is false. + // These cases include: xia, xian, xiang, zhuan, jiang..., etc. The string + // should be valid for a FULL spelling, or a combination of two spellings, + // first of which is a FULL id too. So even it is true, "da" will never be + // split into "d a", because "d" is not a full spelling id. + bool xi_an_enabled_; + // System dictionary. + DictTrie *dict_trie_; + // User dictionary. + AtomDictBase *user_dict_; + // Spelling parser. + SpellingParser *spl_parser_; + // The maximum allowed length of spelling string (such as a Pinyin string). + size_t max_sps_len_; + // The maximum allowed length of a result Chinese string. + size_t max_hzs_len_; + // Pinyin string. Max length: kMaxRowNum - 1 + char pys_[kMaxRowNum]; + // The length of the string that has been decoded successfully. + size_t pys_decoded_len_; + // Shared buffer for multiple purposes. + size_t *share_buf_; + MatrixNode *mtrx_nd_pool_; + PoolPosType mtrx_nd_pool_used_; // How many nodes used in the pool + DictMatchInfo *dmi_pool_; + PoolPosType dmi_pool_used_; // How many items used in the pool + + MatrixRow *matrix_; // The first row is for starting + + DictExtPara *dep_; // Parameter used to extend DMI nodes. + + NPredictItem *npre_items_; // Used to do prediction + size_t npre_items_len_; + // The starting positions and lemma ids for the full sentence candidate. + size_t lma_id_num_; + uint16 lma_start_[kMaxRowNum]; // Counted in spelling ids. + LemmaIdType lma_id_[kMaxRowNum]; + size_t fixed_lmas_; + // If fixed_lmas_ is bigger than i, Element i is used to indicate whether + // the i'th lemma id in lma_id_ is the first candidate for that step. + // If all candidates are the first one for that step, the whole string can be + // decoded by the engine automatically, so no need to add it to user + // dictionary. (We are considering to add it to user dictionary in the + // future). + uint8 fixed_lmas_no1_[kMaxRowNum]; + // Composing phrase + ComposingPhrase c_phrase_; + // If dmi_c_phrase_ is true, the decoder will try to match the + // composing phrase (And definitely it will match successfully). If it + // is false, the decoder will try to match lemmas items in dictionaries. + bool dmi_c_phrase_; + // The starting positions and spelling ids for the first full sentence + // candidate. + size_t spl_id_num_; // Number of splling ids + uint16 spl_start_[kMaxRowNum]; // Starting positions + uint16 spl_id_[kMaxRowNum]; // Spelling ids + // Used to remember the last fixed position, counted in Hanzi. + size_t fixed_hzs_; + // Lemma Items with possibility score, two purposes: + // 1. In Viterbi decoding, this buffer is used to get all possible candidates + // for current step; + // 2. When the search is done, this buffer is used to get candiates from the + // first un-fixed step and show them to the user. + LmaPsbItem lpi_items_[kMaxLmaPsbItems]; + size_t lpi_total_; + // Assign the pointers with NULL. The caller makes sure that all pointers are + // not valid before calling it. This function only will be called in the + // construction function and free_resource(). + void reset_pointers_to_null(); + bool alloc_resource(); + void free_resource(); + // Reset the search space totally. + bool reset_search0(); + // Reset the search space from ch_pos step. For example, if the original + // input Pinyin is "an", reset_search(1) will reset the search space to the + // result of "a". If the given position is out of range, return false. + // if clear_fixed_this_step is true, and the ch_pos step is a fixed step, + // clear its fixed status. if clear_dmi_his_step is true, clear the DMI nodes. + // If clear_mtrx_this_sTep is true, clear the mtrx nodes of this step. + // The DMI nodes will be kept. + // + // Note: this function should not destroy content of pys_. + bool reset_search(size_t ch_pos, bool clear_fixed_this_step, + bool clear_dmi_this_step, bool clear_mtrx_this_step); + // Delete a part of the content in pys_. + void del_in_pys(size_t start, size_t len); + // Delete a spelling id and its corresponding Chinese character, and merge + // the fixed lemmas into the composing phrase. + // del_spl_pos indicates which spelling id needs to be delete. + // This function will update the lemma and spelling segmentation information. + // The caller guarantees that fixed_lmas_ > 0 and del_spl_pos is within + // the fixed lemmas. + void merge_fixed_lmas(size_t del_spl_pos); + // Get spelling start posistions and ids. The result will be stored in + // spl_id_num_, spl_start_[], spl_id_[]. + // fixed_hzs_ will be also assigned. + void get_spl_start_id(); + // Get all lemma ids with match the given spelling id stream(shorter than the + // maximum length of a word). + // If pfullsent is not NULL, means the full sentence candidate may be the + // same with the coming lemma string, if so, remove that lemma. + // The result is sorted in descendant order by the frequency score. + size_t get_lpis(const uint16 *splid_str, size_t splid_str_len, + LmaPsbItem *lma_buf, size_t max_lma_buf, + const char16 *pfullsent, bool sort_by_psb); + uint16 get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, uint16 str_max); + uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids, + uint16 splids_max, bool arg_valid); + // Extend a DMI node with a spelling id. ext_len is the length of the rows + // to extend, actually, it is the size of the spelling string of splid. + // return value can be 1 or 0. + // 1 means a new DMI is filled in (dmi_pool_used_ is the next blank DMI in + // the pool). + // 0 means either the dmi node can not be extended with splid, or the splid + // is a Shengmu id, which is only used to get lpi_items, or the result node + // in DictTrie has no son, it is not nccessary to keep the new DMI. + // + // This function modifies the content of lpi_items_ and lpi_total_. + // lpi_items_ is used to get the LmaPsbItem list, lpi_total_ returns the size. + // The function's returned value has no relation with the value of lpi_num. + // + // If dmi == NULL, this function will extend the root node of DictTrie + // + // This function will not change dmi_nd_pool_used_. Please change it after + // calling this function if necessary. + // + // The caller should guarantees that NULL != dep. + size_t extend_dmi(DictExtPara *dep, DictMatchInfo *dmi_s); + // Extend dmi for the composing phrase. + size_t extend_dmi_c(DictExtPara *dep, DictMatchInfo *dmi_s); + // Extend a MatrixNode with the give LmaPsbItem list. + // res_row is the destination row number. + // This function does not change mtrx_nd_pool_used_. Please change it after + // calling this function if necessary. + // return 0 always. + size_t extend_mtrx_nd(MatrixNode *mtrx_nd, LmaPsbItem lpi_items[], + size_t lpi_num, PoolPosType dmi_fr, size_t res_row); + // Try to find a dmi node at step_to position, and the found dmi node should + // match the given spelling id strings. + PoolPosType match_dmi(size_t step_to, uint16 spl_ids[], uint16 spl_id_num); + bool add_char(char ch); + bool prepare_add_char(char ch); + // Called after prepare_add_char, so the input char has been saved. + bool add_char_qwerty(); + // Prepare candidates from the last fixed hanzi position. + void prepare_candidates(); + // Is the character in step pos a splitter character? + // The caller guarantees that the position is valid. + bool is_split_at(uint16 pos); + void fill_dmi(DictMatchInfo *dmi, MileStoneHandle *handles, + PoolPosType dmi_fr, + uint16 spl_id, uint16 node_num, unsigned char dict_level, + bool splid_end_split, unsigned char splstr_len, + unsigned char all_full_id); + size_t inner_predict(const char16 fixed_scis_ids[], uint16 scis_num, + char16 predict_buf[][kMaxPredictSize + 1], + size_t buf_len); + // Add the first candidate to the user dictionary. + bool try_add_cand0_to_userdict(); + // Add a user lemma to the user dictionary. This lemma is a subset of + // candidate 0. lma_from is from which lemma in lma_ids_, lma_num is the + // number of lemmas to be combined together as a new lemma. The caller + // gurantees that the combined new lemma's length is less or equal to + // kMaxLemmaSize. + bool add_lma_to_userdict(uint16 lma_from, uint16 lma_num, float score); + // Update dictionary frequencies. + void update_dict_freq(); + void debug_print_dmi(PoolPosType dmi_pos, uint16 nest_level); + public: + MatrixSearch(); + ~MatrixSearch(); + bool init(const char *fn_sys_dict, const char *fn_usr_dict); + bool init_fd(int sys_fd, long start_offset, long length, + const char *fn_usr_dict); + void init_user_dictionary(const char *fn_usr_dict); + bool is_user_dictionary_enabled() const; + void set_max_lens(size_t max_sps_len, size_t max_hzs_len); + void close(); + void flush_cache(); + void set_xi_an_switch(bool xi_an_enabled); + bool get_xi_an_switch(); + // Reset the search space. Equivalent to reset_search(0). + // If inited, always return true; + bool reset_search(); + // Search a Pinyin string. + // Return value is the position successfully parsed. + size_t search(const char *py, size_t py_len); + // Used to delete something in the Pinyin string kept by the engine, and do + // a re-search. + // Return value is the new length of Pinyin string kept by the engine which + // is parsed successfully. + // If is_pos_in_splid is false, pos is used to indicate that pos-th Pinyin + // character needs to be deleted. If is_pos_in_splid is true, all Pinyin + // characters for pos-th spelling id needs to be deleted. + // If the deleted character(s) is just after a fixed lemma or sub lemma in + // composing phrase, clear_fixed_this_step indicates whether we needs to + // unlock the last fixed lemma or sub lemma. + // If is_pos_in_splid is false, and pos-th character is in the range for the + // fixed lemmas or composing string, this function will do nothing and just + // return the result of the previous search. + size_t delsearch(size_t pos, bool is_pos_in_splid, + bool clear_fixed_this_step); + // Get the number of candiates, called after search(). + size_t get_candidate_num(); + // Get the Pinyin string stored by the engine. + // *decoded_len returns the length of the successfully decoded string. + const char *get_pystr(size_t *decoded_len); + // Get the spelling boundaries for the first sentence candidate. + // Number of spellings will be returned. The number of valid elements in + // spl_start is one more than the return value because the last one is used + // to indicate the beginning of the next un-input speling. + // For a Pinyin "women", the returned value is 2, spl_start is [0, 2, 5] . + size_t get_spl_start(const uint16 *&spl_start); + // Get one candiate string. If full sentence candidate is available, it will + // be the first one. + char16 *get_candidate(size_t cand_id, char16 *cand_str, size_t max_len); + // Get the first candiate, which is a "full sentence". + // retstr_len is not NULL, it will be used to return the string length. + // If only_unfixed is true, only unfixed part will be fetched. + char16 *get_candidate0(char16 *cand_str, size_t max_len, + uint16 *retstr_len, bool only_unfixed); + // Choose a candidate. The decoder will do a search after the fixed position. + size_t choose(size_t cand_id); + // Cancel the last choosing operation, and return the new number of choices. + size_t cancel_last_choice(); + // Get the length of fixed Hanzis. + size_t get_fixedlen(); + size_t get_predicts(const char16 fixed_buf[], + char16 predict_buf[][kMaxPredictSize + 1], + size_t buf_len); + }; +} +#endif // PINYINIME_ANDPY_INCLUDE_MATRIXSEARCH_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/mystdlib.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/mystdlib.cpp new file mode 100644 index 0000000..a112eba --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/mystdlib.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +namespace ime_pinyin { + +// For debug purpose. You can add a fixed version of qsort and bsearch functions +// here so that the output will be totally the same under different platforms. + + void myqsort(void *p, size_t n, size_t es, + int (*cmp)(const void *, const void *)) { + qsort(p, n, es, cmp); + } + void *mybsearch(const void *k, const void *b, + size_t n, size_t es, + int (*cmp)(const void *, const void *)) { + return bsearch(k, b, n, es, cmp); + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/mystdlib.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/mystdlib.h new file mode 100644 index 0000000..914ca01 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/mystdlib.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_MYSTDLIB_H__ +#define PINYINIME_INCLUDE_MYSTDLIB_H__ +#include +namespace ime_pinyin { + void myqsort(void *p, size_t n, size_t es, + int (*cmp)(const void *, const void *)); + void *mybsearch(const void *key, const void *base, + size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); +} +#endif // PINYINIME_INCLUDE_MYSTDLIB_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/ngram.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/ngram.cpp new file mode 100644 index 0000000..7018799 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/ngram.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "mystdlib.h" +#include "ngram.h" +namespace ime_pinyin { +#define ADD_COUNT 0.3 + int comp_double(const void *p1, const void *p2) { + if (*static_cast(p1) < *static_cast(p2)) + return -1; + if (*static_cast(p1) > *static_cast(p2)) + return 1; + return 0; + } + inline double distance(double freq, double code) { + // return fabs(freq - code); + return freq * fabs(log(freq) - log(code)); + } +// Find the index of the code value which is nearest to the given freq + int qsearch_nearest(double code_book[], double freq, int start, int end) { + if (start == end) + return start; + if (start + 1 == end) { + if (distance(freq, code_book[end]) > distance(freq, code_book[start])) + return start; + return end; + } + int mid = (start + end) / 2; + if (code_book[mid] > freq) + return qsearch_nearest(code_book, freq, start, mid); + else + return qsearch_nearest(code_book, freq, mid, end); + } + size_t update_code_idx(double freqs[], size_t num, double code_book[], + CODEBOOK_TYPE *code_idx) { + size_t changed = 0; + for (size_t pos = 0; pos < num; pos++) { + CODEBOOK_TYPE idx; + idx = qsearch_nearest(code_book, freqs[pos], 0, kCodeBookSize - 1); + if (idx != code_idx[pos]) + changed++; + code_idx[pos] = idx; + } + return changed; + } + double recalculate_kernel(double freqs[], size_t num, double code_book[], + CODEBOOK_TYPE *code_idx) { + double ret = 0; + size_t *item_num = new size_t[kCodeBookSize]; + assert(item_num); + memset(item_num, 0, sizeof(size_t) * kCodeBookSize); + double *cb_new = new double[kCodeBookSize]; + assert(cb_new); + memset(cb_new, 0, sizeof(double) * kCodeBookSize); + for (size_t pos = 0; pos < num; pos++) { + ret += distance(freqs[pos], code_book[code_idx[pos]]); + cb_new[code_idx[pos]] += freqs[pos]; + item_num[code_idx[pos]] += 1; + } + for (size_t code = 0; code < kCodeBookSize; code++) { + assert(item_num[code] > 0); + code_book[code] = cb_new[code] / item_num[code]; + } + delete[] item_num; + delete[] cb_new; + return ret; + } + void iterate_codes(double freqs[], size_t num, double code_book[], + CODEBOOK_TYPE *code_idx) { + size_t iter_num = 0; + double delta_last = 0; + do { + size_t changed = update_code_idx(freqs, num, code_book, code_idx); + double delta = recalculate_kernel(freqs, num, code_book, code_idx); + if (kPrintDebug0) { + printf("---Unigram codebook iteration: %d : %d, %.9f\n", + iter_num, changed, delta); + } + iter_num++; + if (iter_num > 1 && + (delta == 0 || fabs(delta_last - delta) / fabs(delta) < 0.000000001)) + break; + delta_last = delta; + } while (true); + } + NGram *NGram::instance_ = NULL; + NGram::NGram() { + initialized_ = false; + idx_num_ = 0; + lma_freq_idx_ = NULL; + sys_score_compensation_ = 0; +#ifdef ___BUILD_MODEL___ + freq_codes_df_ = NULL; +#endif + freq_codes_ = NULL; + } + NGram::~NGram() { + if (NULL != lma_freq_idx_) + free(lma_freq_idx_); +#ifdef ___BUILD_MODEL___ + if (NULL != freq_codes_df_) + free(freq_codes_df_); +#endif + if (NULL != freq_codes_) + free(freq_codes_); + } + NGram &NGram::get_instance() { + if (NULL == instance_) + instance_ = new NGram(); + return *instance_; + } + bool NGram::save_ngram(FILE *fp) { + if (!initialized_ || NULL == fp) + return false; + if (0 == idx_num_ || NULL == freq_codes_ || NULL == lma_freq_idx_) + return false; + if (fwrite(&idx_num_, sizeof(uint32), 1, fp) != 1) + return false; + if (fwrite(freq_codes_, sizeof(LmaScoreType), kCodeBookSize, fp) != + kCodeBookSize) + return false; + if (fwrite(lma_freq_idx_, sizeof(CODEBOOK_TYPE), idx_num_, fp) != idx_num_) + return false; + return true; + } + bool NGram::load_ngram(FILE *fp) { + if (NULL == fp) + return false; + initialized_ = false; + if (fread(&idx_num_, sizeof(uint32), 1, fp) != 1) + return false; + if (NULL != lma_freq_idx_) + free(lma_freq_idx_); + if (NULL != freq_codes_) + free(freq_codes_); + lma_freq_idx_ = static_cast + (malloc(idx_num_ * sizeof(CODEBOOK_TYPE))); + freq_codes_ = static_cast + (malloc(kCodeBookSize * sizeof(LmaScoreType))); + if (NULL == lma_freq_idx_ || NULL == freq_codes_) + return false; + if (fread(freq_codes_, sizeof(LmaScoreType), kCodeBookSize, fp) != + kCodeBookSize) + return false; + if (fread(lma_freq_idx_, sizeof(CODEBOOK_TYPE), idx_num_, fp) != idx_num_) + return false; + initialized_ = true; + total_freq_none_sys_ = 0; + return true; + } + void NGram::set_total_freq_none_sys(size_t freq_none_sys) { + total_freq_none_sys_ = freq_none_sys; + if (0 == total_freq_none_sys_) { + sys_score_compensation_ = 0; + } else { + double factor = static_cast(kSysDictTotalFreq) / ( + kSysDictTotalFreq + total_freq_none_sys_); + sys_score_compensation_ = static_cast( + log(factor) * kLogValueAmplifier); + } + } +// The caller makes sure this oject is initialized. + float NGram::get_uni_psb(LemmaIdType lma_id) { + return static_cast(freq_codes_[lma_freq_idx_[lma_id]]) + + sys_score_compensation_; + } + float NGram::convert_psb_to_score(double psb) { + float score = static_cast( + log(psb) * static_cast(kLogValueAmplifier)); + if (score > static_cast(kMaxScore)) { + score = static_cast(kMaxScore); + } + return score; + } +#ifdef ___BUILD_MODEL___ + bool NGram::build_unigram(LemmaEntry *lemma_arr, size_t lemma_num, + LemmaIdType next_idx_unused) { + if (NULL == lemma_arr || 0 == lemma_num || next_idx_unused <= 1) + return false; + + double total_freq = 0; + double *freqs = new double[next_idx_unused]; + if (NULL == freqs) + return false; + + freqs[0] = ADD_COUNT; + total_freq += freqs[0]; + LemmaIdType idx_now = 0; + for (size_t pos = 0; pos < lemma_num; pos++) { + if (lemma_arr[pos].idx_by_hz == idx_now) + continue; + idx_now++; + + assert(lemma_arr[pos].idx_by_hz == idx_now); + + freqs[idx_now] = lemma_arr[pos].freq; + if (freqs[idx_now] <= 0) + freqs[idx_now] = 0.3; + + total_freq += freqs[idx_now]; + } + + double max_freq = 0; + idx_num_ = idx_now + 1; + assert(idx_now + 1 == next_idx_unused); + + for (size_t pos = 0; pos < idx_num_; pos++) { + freqs[pos] = freqs[pos] / total_freq; + assert(freqs[pos] > 0); + if (freqs[pos] > max_freq) + max_freq = freqs[pos]; + } + + // calculate the code book + if (NULL == freq_codes_df_) + freq_codes_df_ = new double[kCodeBookSize]; + assert(freq_codes_df_); + memset(freq_codes_df_, 0, sizeof(double) * kCodeBookSize); + + if (NULL == freq_codes_) + freq_codes_ = new LmaScoreType[kCodeBookSize]; + assert(freq_codes_); + memset(freq_codes_, 0, sizeof(LmaScoreType) * kCodeBookSize); + + size_t freq_pos = 0; + for (size_t code_pos = 0; code_pos < kCodeBookSize; code_pos++) { + bool found = true; + + while (found) { + found = false; + double cand = freqs[freq_pos]; + for (size_t i = 0; i < code_pos; i++) + if (freq_codes_df_[i] == cand) { + found = true; + break; + } + if (found) + freq_pos++; + } + + freq_codes_df_[code_pos] = freqs[freq_pos]; + freq_pos++; + } + + myqsort(freq_codes_df_, kCodeBookSize, sizeof(double), comp_double); + + if (NULL == lma_freq_idx_) + lma_freq_idx_ = new CODEBOOK_TYPE[idx_num_]; + assert(lma_freq_idx_); + + iterate_codes(freqs, idx_num_, freq_codes_df_, lma_freq_idx_); + + delete [] freqs; + + if (kPrintDebug0) { + printf("\n------Language Model Unigram Codebook------\n"); + } + + for (size_t code_pos = 0; code_pos < kCodeBookSize; code_pos++) { + double log_score = log(freq_codes_df_[code_pos]); + float final_score = convert_psb_to_score(freq_codes_df_[code_pos]); + if (kPrintDebug0) { + printf("code:%d, probability:%.9f, log score:%.3f, final score: %.3f\n", + code_pos, freq_codes_df_[code_pos], log_score, final_score); + } + freq_codes_[code_pos] = static_cast(final_score); + } + + initialized_ = true; + return true; + } +#endif +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/ngram.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/ngram.h new file mode 100644 index 0000000..2359f0b --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/ngram.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_NGRAM_H__ +#define PINYINIME_INCLUDE_NGRAM_H__ +#include +#include +#include "./dictdef.h" +namespace ime_pinyin { + typedef unsigned char CODEBOOK_TYPE; + static const size_t kCodeBookSize = 256; + class NGram { + public: + // The maximum score of a lemma item. + static const LmaScoreType kMaxScore = 0x3fff; + // In order to reduce the storage size, the original log value is amplified by + // kScoreAmplifier, and we use LmaScoreType to store. + // After this process, an item with a lower score has a higher frequency. + static const int kLogValueAmplifier = -800; + // System words' total frequency. It is not the real total frequency, instead, + // It is only used to adjust system lemmas' scores when the user dictionary's + // total frequency changes. + // In this version, frequencies of system lemmas are fixed. We are considering + // to make them changable in next version. + static const size_t kSysDictTotalFreq = 100000000; + private: + static NGram *instance_; + bool initialized_; + uint32 idx_num_; + size_t total_freq_none_sys_; + // Score compensation for system dictionary lemmas. + // Because after user adds some user lemmas, the total frequency changes, and + // we use this value to normalize the score. + float sys_score_compensation_; +#ifdef ___BUILD_MODEL___ + double *freq_codes_df_; +#endif + LmaScoreType *freq_codes_; + CODEBOOK_TYPE *lma_freq_idx_; + public: + NGram(); + ~NGram(); + static NGram &get_instance(); + bool save_ngram(FILE *fp); + bool load_ngram(FILE *fp); + // Set the total frequency of all none system dictionaries. + void set_total_freq_none_sys(size_t freq_none_sys); + float get_uni_psb(LemmaIdType lma_id); + // Convert a probability to score. Actually, the score will be limited to + // kMaxScore, but at runtime, we also need float expression to get accurate + // value of the score. + // After the conversion, a lower score indicates a higher probability of the + // item. + static float convert_psb_to_score(double psb); +#ifdef ___BUILD_MODEL___ + // For constructing the unigram mode model. + bool build_unigram(LemmaEntry *lemma_arr, size_t num, + LemmaIdType next_idx_unused); +#endif + }; +} +#endif // PINYINIME_INCLUDE_NGRAM_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/pinyinime.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/pinyinime.cpp new file mode 100644 index 0000000..d4ae040 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/pinyinime.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "pinyinime.h" +#include "dicttrie.h" +#include "matrixsearch.h" +#include "spellingtrie.h" +#ifdef __cplusplus +extern "C" { +#endif +using namespace ime_pinyin; +// The maximum number of the prediction items. +static const size_t kMaxPredictNum = 500; +// Used to search Pinyin string and give the best candidate. +MatrixSearch *matrix_search = NULL; +char16 predict_buf[kMaxPredictNum][kMaxPredictSize + 1]; +bool im_open_decoder(const char *fn_sys_dict, const char *fn_usr_dict) { + if (NULL != matrix_search) + delete matrix_search; + matrix_search = new MatrixSearch(); + if (NULL == matrix_search) { + return false; + } + return matrix_search->init(fn_sys_dict, fn_usr_dict); +} +bool im_open_decoder_fd(int sys_fd, long start_offset, long length, + const char *fn_usr_dict) { + if (NULL != matrix_search) + delete matrix_search; + matrix_search = new MatrixSearch(); + if (NULL == matrix_search) + return false; + return matrix_search->init_fd(sys_fd, start_offset, length, fn_usr_dict); +} +void im_close_decoder() { + if (NULL != matrix_search) { + matrix_search->close(); + delete matrix_search; + } + matrix_search = NULL; +} +void im_set_max_lens(size_t max_sps_len, size_t max_hzs_len) { + if (NULL != matrix_search) { + matrix_search->set_max_lens(max_sps_len, max_hzs_len); + } +} +void im_flush_cache() { + if (NULL != matrix_search) + matrix_search->flush_cache(); +} +// To be updated. +size_t im_search(const char *pybuf, size_t pylen) { + if (NULL == matrix_search) + return 0; + matrix_search->search(pybuf, pylen); + return matrix_search->get_candidate_num(); +} +size_t im_delsearch(size_t pos, bool is_pos_in_splid, + bool clear_fixed_this_step) { + if (NULL == matrix_search) + return 0; + matrix_search->delsearch(pos, is_pos_in_splid, clear_fixed_this_step); + return matrix_search->get_candidate_num(); +} +void im_reset_search() { + if (NULL == matrix_search) + return; + matrix_search->reset_search(); +} +// To be removed +size_t im_add_letter(char) { + return 0; +} +const char *im_get_sps_str(size_t *decoded_len) { + if (NULL == matrix_search) + return NULL; + return matrix_search->get_pystr(decoded_len); +} +char16 *im_get_candidate(size_t cand_id, char16 *cand_str, + size_t max_len) { + if (NULL == matrix_search) + return NULL; + return matrix_search->get_candidate(cand_id, cand_str, max_len); +} +size_t im_get_spl_start_pos(const uint16 *&spl_start) { + if (NULL == matrix_search) + return 0; + return matrix_search->get_spl_start(spl_start); +} +size_t im_choose(size_t choice_id) { + if (NULL == matrix_search) + return 0; + return matrix_search->choose(choice_id); +} +size_t im_cancel_last_choice() { + if (NULL == matrix_search) + return 0; + return matrix_search->cancel_last_choice(); +} +size_t im_get_fixed_len() { + if (NULL == matrix_search) + return 0; + return matrix_search->get_fixedlen(); +} +// To be removed +bool im_cancel_input() { + return true; +} +size_t im_get_predicts(const char16 *his_buf, + char16 (*&pre_buf)[kMaxPredictSize + 1]) { + if (NULL == his_buf) + return 0; + size_t fixed_len = utf16_strlen(his_buf); + const char16 *fixed_ptr = his_buf; + if (fixed_len > kMaxPredictSize) { + fixed_ptr += fixed_len - kMaxPredictSize; + fixed_len = kMaxPredictSize; + } + pre_buf = predict_buf; + return matrix_search->get_predicts(his_buf, pre_buf, kMaxPredictNum); +} +void im_enable_shm_as_szm(bool enable) { + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + spl_trie.szm_enable_shm(enable); +} +void im_enable_ym_as_szm(bool enable) { + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + spl_trie.szm_enable_ym(enable); +} +void im_init_user_dictionary(const char *fn_usr_dict) { + if (!matrix_search) + return; + matrix_search->flush_cache(); + matrix_search->init_user_dictionary(fn_usr_dict); +} +bool im_is_user_dictionary_enabled(void) { + return NULL != matrix_search ? matrix_search->is_user_dictionary_enabled() : false; +} +#ifdef __cplusplus +} +#endif diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/pinyinime.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/pinyinime.h new file mode 100644 index 0000000..2be1b71 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/pinyinime.h @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_ANDPYIME_H__ +#define PINYINIME_INCLUDE_ANDPYIME_H__ +#include +#include "./dictdef.h" +#ifdef __cplusplus +extern "C" { +#endif +namespace ime_pinyin { + /** + * Open the decoder engine via the system and user dictionary file names. + * + * @param fn_sys_dict The file name of the system dictionary. + * @param fn_usr_dict The file name of the user dictionary. + * @return true if open the decoder engine successfully. + */ + bool im_open_decoder(const char *fn_sys_dict, const char *fn_usr_dict); + /** + * Open the decoder engine via the system dictionary FD and user dictionary + * file name. Because on Android, the system dictionary is embedded in the + * whole application apk file. + * + * @param sys_fd The file in which the system dictionary is embedded. + * @param start_offset The starting position of the system dictionary in the + * file sys_fd. + * @param length The length of the system dictionary in the file sys_fd, + * counted in byte. + * @return true if succeed. + */ + bool im_open_decoder_fd(int sys_fd, long start_offset, long length, + const char *fn_usr_dict); + /** + * Close the decoder engine. + */ + void im_close_decoder(); + /** + * Set maximum limitations for decoding. If this function is not called, + * default values will be used. For example, due to screen size limitation, + * the UI engine of the IME can only show a certain number of letters(input) + * to decode, and a certain number of Chinese characters(output). If after + * user adds a new letter, the input or the output string is longer than the + * limitations, the engine will discard the recent letter. + * + * @param max_sps_len Maximum length of the spelling string(Pinyin string). + * @max_hzs_len Maximum length of the decoded Chinese character string. + */ + void im_set_max_lens(size_t max_sps_len, size_t max_hzs_len); + /** + * Flush cached data to persistent memory. Because at runtime, in order to + * achieve best performance, some data is only store in memory. + */ + void im_flush_cache(); + /** + * Use a spelling string(Pinyin string) to search. The engine will try to do + * an incremental search based on its previous search result, so if the new + * string has the same prefix with the previous one stored in the decoder, + * the decoder will only continue the search from the end of the prefix. + * If the caller needs to do a brand new search, please call im_reset_search() + * first. Calling im_search() is equivalent to calling im_add_letter() one by + * one. + * + * @param sps_buf The spelling string buffer to decode. + * @param sps_len The length of the spelling string buffer. + * @return The number of candidates. + */ + size_t im_search(const char *sps_buf, size_t sps_len); + /** + * Make a delete operation in the current search result, and make research if + * necessary. + * + * @param pos The posistion of char in spelling string to delete, or the + * position of spelling id in result string to delete. + * @param is_pos_in_splid Indicate whether the pos parameter is the position + * in the spelling string, or the position in the result spelling id string. + * @return The number of candidates. + */ + size_t im_delsearch(size_t pos, bool is_pos_in_splid, + bool clear_fixed_this_step); + /** + * Reset the previous search result. + */ + void im_reset_search(); + /** + * Add a Pinyin letter to the current spelling string kept by decoder. If the + * decoder fails in adding the letter, it will do nothing. im_get_sps_str() + * can be used to get the spelling string kept by decoder currently. + * + * @param ch The letter to add. + * @return The number of candidates. + */ + size_t im_add_letter(char ch); + /** + * Get the spelling string kept by the decoder. + * + * @param decoded_len Used to return how many characters in the spelling + * string is successfully parsed. + * @return The spelling string kept by the decoder. + */ + const char *im_get_sps_str(size_t *decoded_len); + /** + * Get a candidate(or choice) string. + * + * @param cand_id The id to get a candidate. Started from 0. Usually, id 0 + * is a sentence-level candidate. + * @param cand_str The buffer to store the candidate. + * @param max_len The maximum length of the buffer. + * @return cand_str if succeeds, otherwise NULL. + */ + char16 *im_get_candidate(size_t cand_id, char16 *cand_str, + size_t max_len); + /** + * Get the segmentation information(the starting positions) of the spelling + * string. + * + * @param spl_start Used to return the starting posistions. + * @return The number of spelling ids. If it is L, there will be L+1 valid + * elements in spl_start, and spl_start[L] is the posistion after the end of + * the last spelling id. + */ + size_t im_get_spl_start_pos(const uint16 *&spl_start); + /** + * Choose a candidate and make it fixed. If the candidate does not match + * the end of all spelling ids, new candidates will be provided from the + * first unfixed position. If the candidate matches the end of the all + * spelling ids, there will be only one new candidates, or the whole fixed + * sentence. + * + * @param cand_id The id of candidate to select and make it fixed. + * @return The number of candidates. If after the selection, the whole result + * string has been fixed, there will be only one candidate. + */ + size_t im_choose(size_t cand_id); + /** + * Cancel the last selection, or revert the last operation of im_choose(). + * + * @return The number of candidates. + */ + size_t im_cancel_last_choice(); + /** + * Get the number of fixed spelling ids, or Chinese characters. + * + * @return The number of fixed spelling ids, of Chinese characters. + */ + size_t im_get_fixed_len(); + /** + * Cancel the input state and reset the search workspace. + */ + bool im_cancel_input(); + /** + * Get prediction candiates based on the given fixed Chinese string as the + * history. + * + * @param his_buf The history buffer to do the prediction. It should be ended + * with '\0'. + * @param pre_buf Used to return prediction result list. + * @return The number of predicted result string. + */ + size_t im_get_predicts(const char16 *his_buf, + char16 (*&pre_buf)[kMaxPredictSize + 1]); + /** + * Enable Shengmus in ShouZiMu mode. + */ + void im_enable_shm_as_szm(bool enable); + /** + * Enable Yunmus in ShouZiMu mode. + */ + void im_enable_ym_as_szm(bool enable); + /** + * Initializes or uninitializes the user dictionary. + * + * @param fn_usr_dict The file name of the user dictionary. + */ + void im_init_user_dictionary(const char *fn_usr_dict); + /** + * Returns the current status of user dictinary. + */ + bool im_is_user_dictionary_enabled(void); +} +#ifdef __cplusplus +} +#endif +#endif // PINYINIME_INCLUDE_ANDPYIME_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/searchutility.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/searchutility.cpp new file mode 100644 index 0000000..6b3908c --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/searchutility.cpp @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "mystdlib.h" +#include "searchutility.h" +namespace ime_pinyin { + bool is_system_lemma(LemmaIdType lma_id) { + return (0 < lma_id && lma_id <= kSysDictIdEnd); + } + bool is_user_lemma(LemmaIdType lma_id) { + return (kUserDictIdStart <= lma_id && lma_id <= kUserDictIdEnd); + } + bool is_composing_lemma(LemmaIdType lma_id) { + return (kLemmaIdComposing == lma_id); + } + int cmp_lpi_with_psb(const void *p1, const void *p2) { + if ((static_cast(p1))->psb > + (static_cast(p2))->psb) + return 1; + if ((static_cast(p1))->psb < + (static_cast(p2))->psb) + return -1; + return 0; + } + int cmp_lpi_with_unified_psb(const void *p1, const void *p2) { + const LmaPsbItem *item1 = static_cast(p1); + const LmaPsbItem *item2 = static_cast(p2); + + // The real unified psb is psb1 / lma_len1 and psb2 * lma_len2 + // But we use psb1 * lma_len2 and psb2 * lma_len1 to get better + // precision. + size_t up1 = item1->psb * (item2->lma_len); + size_t up2 = item2->psb * (item1->lma_len); + if (up1 < up2) { + return -1; + } + if (up1 > up2) { + return 1; + } + return 0; + } + int cmp_lpi_with_id(const void *p1, const void *p2) { + if ((static_cast(p1))->id < + (static_cast(p2))->id) + return -1; + if ((static_cast(p1))->id > + (static_cast(p2))->id) + return 1; + return 0; + } + int cmp_lpi_with_hanzi(const void *p1, const void *p2) { + if ((static_cast(p1))->hanzi < + (static_cast(p2))->hanzi) + return -1; + if ((static_cast(p1))->hanzi > + (static_cast(p2))->hanzi) + return 1; + return 0; + } + int cmp_lpsi_with_str(const void *p1, const void *p2) { + return utf16_strcmp((static_cast(p1))->str, + (static_cast(p2))->str); + } + int cmp_hanzis_1(const void *p1, const void *p2) { + if (*static_cast(p1) < + *static_cast(p2)) + return -1; + if (*static_cast(p1) > + *static_cast(p2)) + return 1; + return 0; + } + int cmp_hanzis_2(const void *p1, const void *p2) { + return utf16_strncmp(static_cast(p1), + static_cast(p2), 2); + } + int cmp_hanzis_3(const void *p1, const void *p2) { + return utf16_strncmp(static_cast(p1), + static_cast(p2), 3); + } + int cmp_hanzis_4(const void *p1, const void *p2) { + return utf16_strncmp(static_cast(p1), + static_cast(p2), 4); + } + int cmp_hanzis_5(const void *p1, const void *p2) { + return utf16_strncmp(static_cast(p1), + static_cast(p2), 5); + } + int cmp_hanzis_6(const void *p1, const void *p2) { + return utf16_strncmp(static_cast(p1), + static_cast(p2), 6); + } + int cmp_hanzis_7(const void *p1, const void *p2) { + return utf16_strncmp(static_cast(p1), + static_cast(p2), 7); + } + int cmp_hanzis_8(const void *p1, const void *p2) { + return utf16_strncmp(static_cast(p1), + static_cast(p2), 8); + } + int cmp_npre_by_score(const void *p1, const void *p2) { + if ((static_cast(p1))->psb > + (static_cast(p2))->psb) + return 1; + if ((static_cast(p1))->psb < + (static_cast(p2))->psb) + return -1; + return 0; + } + int cmp_npre_by_hislen_score(const void *p1, const void *p2) { + if ((static_cast(p1))->his_len < + (static_cast(p2))->his_len) + return 1; + if ((static_cast(p1))->his_len > + (static_cast(p2))->his_len) + return -1; + if ((static_cast(p1))->psb > + (static_cast(p2))->psb) + return 1; + if ((static_cast(p1))->psb < + (static_cast(p2))->psb) + return -1; + return 0; + } + int cmp_npre_by_hanzi_score(const void *p1, const void *p2) { + int ret_v = (utf16_strncmp((static_cast(p1))->pre_hzs, + (static_cast(p2))->pre_hzs, kMaxPredictSize)); + if (0 != ret_v) + return ret_v; + if ((static_cast(p1))->psb > + (static_cast(p2))->psb) + return 1; + if ((static_cast(p1))->psb < + (static_cast(p2))->psb) + return -1; + return 0; + } + size_t remove_duplicate_npre(NPredictItem *npre_items, size_t npre_num) { + if (NULL == npre_items || 0 == npre_num) + return 0; + myqsort(npre_items, npre_num, sizeof(NPredictItem), cmp_npre_by_hanzi_score); + size_t remain_num = 1; // The first one is reserved. + for (size_t pos = 1; pos < npre_num; pos++) { + if (utf16_strncmp(npre_items[pos].pre_hzs, + npre_items[remain_num - 1].pre_hzs, + kMaxPredictSize) != 0) { + if (remain_num != pos) { + npre_items[remain_num] = npre_items[pos]; + } + remain_num++; + } + } + return remain_num; + } + size_t align_to_size_t(size_t size) { + size_t s = sizeof(size_t); + return (size + s - 1) / s * s; + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/searchutility.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/searchutility.h new file mode 100644 index 0000000..04f22de --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/searchutility.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_ANDPY_INCLUDE_SEARCHCOMMON_H__ +#define PINYINIME_ANDPY_INCLUDE_SEARCHCOMMON_H__ +#include +#include "./spellingtrie.h" +namespace ime_pinyin { +// Type used to identify the size of a pool, such as id pool, etc. + typedef uint16 PoolPosType; +// Type used to identify a parsing mile stone in an atom dictionary. + typedef uint16 MileStoneHandle; +// Type used to express a lemma and its probability score. + typedef struct { + size_t id: (kLemmaIdSize * 8); + size_t lma_len: 4; + uint16 psb; // The score, the lower psb, the higher possibility. + // For single character items, we may also need Hanzi. + // For multiple characer items, ignore it. + char16 hanzi; + } LmaPsbItem, *PLmaPsbItem; +// LmaPsbItem extended with string. + typedef struct { + LmaPsbItem lpi; + char16 str[kMaxLemmaSize + 1]; + } LmaPsbStrItem, *PLmaPsbStrItem; + typedef struct { + float psb; + char16 pre_hzs[kMaxPredictSize]; + uint16 his_len; // The length of the history used to do the prediction. + } NPredictItem, *PNPredictItem; +// Parameter structure used to extend in a dictionary. All dictionaries +// receives the same DictExtPara and a dictionary specific MileStoneHandle for +// extending. +// +// When the user inputs a new character, AtomDictBase::extend_dict() will be +// called at least once for each dictionary. +// +// For example, when the user inputs "wm", extend_dict() will be called twice, +// and the DictExtPara parameter are as follows respectively: +// 1. splids = {w, m}; splids_extended = 1; ext_len = 1; step_no = 1; +// splid_end_split = false; id_start = wa(the first id start with 'w'); +// id_num = number of ids starting with 'w'. +// 2. splids = {m}; splids_extended = 0; ext_len = 1; step_no = 1; +// splid_end_split = false; id_start = wa; id_num = number of ids starting with +// 'w'. +// +// For string "women", one of the cases of the DictExtPara parameter is: +// splids = {wo, men}, splids_extended = 1, ext_len = 3 (length of "men"), +// step_no = 4; splid_end_split = false; id_start = men, id_num = 1. +// + typedef struct { + // Spelling ids for extending, there are splids_extended + 1 ids in the + // buffer. + // For a normal lemma, there can only be kMaxLemmaSize spelling ids in max, + // but for a composing phrase, there can kMaxSearchSteps spelling ids. + uint16 splids[kMaxSearchSteps]; + // Number of ids that have been used before. splids[splids_extended] is the + // newly added id for the current extension. + uint16 splids_extended; + // The step span of the extension. It is also the size of the string for + // the newly added spelling id. + uint16 ext_len; + // The step number for the current extension. It is also the ending position + // in the input Pinyin string for the substring of spelling ids in splids[]. + // For example, when the user inputs "women", step_no = 4. + // This parameter may useful to manage the MileStoneHandle list for each + // step. When the user deletes a character from the string, MileStoneHandle + // objects for the the steps after that character should be reset; when the + // user begins a new string, all MileStoneHandle objects should be reset. + uint16 step_no; + // Indicate whether the newly added spelling ends with a splitting character + bool splid_end_split; + // If the newly added id is a half id, id_start is the first id of the + // corresponding full ids; if the newly added id is a full id, id_start is + // that id. + uint16 id_start; + // If the newly added id is a half id, id_num is the number of corresponding + // ids; if it is a full id, id_num == 1. + uint16 id_num; + } DictExtPara, *PDictExtPara; + bool is_system_lemma(LemmaIdType lma_id); + bool is_user_lemma(LemmaIdType lma_id); + bool is_composing_lemma(LemmaIdType lma_id); + int cmp_lpi_with_psb(const void *p1, const void *p2); + int cmp_lpi_with_unified_psb(const void *p1, const void *p2); + int cmp_lpi_with_id(const void *p1, const void *p2); + int cmp_lpi_with_hanzi(const void *p1, const void *p2); + int cmp_lpsi_with_str(const void *p1, const void *p2); + int cmp_hanzis_1(const void *p1, const void *p2); + int cmp_hanzis_2(const void *p1, const void *p2); + int cmp_hanzis_3(const void *p1, const void *p2); + int cmp_hanzis_4(const void *p1, const void *p2); + int cmp_hanzis_5(const void *p1, const void *p2); + int cmp_hanzis_6(const void *p1, const void *p2); + int cmp_hanzis_7(const void *p1, const void *p2); + int cmp_hanzis_8(const void *p1, const void *p2); + int cmp_npre_by_score(const void *p1, const void *p2); + int cmp_npre_by_hislen_score(const void *p1, const void *p2); + int cmp_npre_by_hanzi_score(const void *p1, const void *p2); + size_t remove_duplicate_npre(NPredictItem *npre_items, size_t npre_num); + size_t align_to_size_t(size_t size); +} // namespace + +#endif // PINYINIME_ANDPY_INCLUDE_SEARCHCOMMON_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtable.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtable.cpp new file mode 100644 index 0000000..6d37c75 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtable.cpp @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include "spellingtable.h" +namespace ime_pinyin { +#ifdef ___BUILD_MODEL___ + + const char SpellingTable:: + kNotSupportList[kNotSupportNum][kMaxSpellingSize + 1] = {"HM", "HNG", "NG"}; + + // "" is the biggest, so that all empty strings will be moved to the end + // _eb mean empty is biggest + int compare_raw_spl_eb(const void* p1, const void* p2) { + if ('\0' == (static_cast(p1))->str[0]) + return 1; + + if ('\0' == (static_cast(p2))->str[0]) + return -1; + + return strcmp((static_cast(p1))->str, + (static_cast(p2))->str); + } + + size_t get_odd_next(size_t value) { + size_t v_next = value; + while (true) { + size_t v_next_sqrt = (size_t)sqrt(v_next); + + bool is_odd = true; + for (size_t v_dv = 2; v_dv < v_next_sqrt + 1; v_dv++) { + if (v_next % v_dv == 0) { + is_odd = false; + break; + } + } + + if (is_odd) + return v_next; + + v_next++; + } + + // never reach here + return 0; + } + + SpellingTable::SpellingTable() { + need_score_ = false; + raw_spellings_ = NULL; + spelling_buf_ = NULL; + spelling_num_ = 0; + total_freq_ = 0; + frozen_ = true; + } + + SpellingTable::~SpellingTable() { + free_resource(); + } + + size_t SpellingTable::get_hash_pos(const char* spelling_str) { + size_t hash_pos = 0; + for (size_t pos = 0; pos < spelling_size_; pos++) { + if ('\0' == spelling_str[pos]) + break; + hash_pos += (size_t)spelling_str[pos]; + } + + hash_pos = hash_pos % spelling_max_num_; + return hash_pos; + } + + size_t SpellingTable::hash_pos_next(size_t hash_pos) { + hash_pos += 123; + hash_pos = hash_pos % spelling_max_num_; + return hash_pos; + } + + void SpellingTable::free_resource() { + if (NULL != raw_spellings_) + delete [] raw_spellings_; + raw_spellings_ = NULL; + + if (NULL != spelling_buf_) + delete [] spelling_buf_; + spelling_buf_ = NULL; + } + + bool SpellingTable::init_table(size_t pure_spl_size, size_t spl_max_num, + bool need_score) { + if (pure_spl_size == 0 || spl_max_num ==0) + return false; + + need_score_ = need_score; + + free_resource(); + + spelling_size_ = pure_spl_size + 1; + if (need_score) + spelling_size_ += 1; + spelling_max_num_ = get_odd_next(spl_max_num); + spelling_num_ = 0; + + raw_spellings_ = new RawSpelling[spelling_max_num_]; + spelling_buf_ = new char[spelling_max_num_ * (spelling_size_)]; + if (NULL == raw_spellings_ || NULL == spelling_buf_) { + free_resource(); + return false; + } + + memset(raw_spellings_, 0, spelling_max_num_ * sizeof(RawSpelling)); + memset(spelling_buf_, 0, spelling_max_num_ * (spelling_size_)); + frozen_ = false; + total_freq_ = 0; + return true; + } + + bool SpellingTable::put_spelling(const char* spelling_str, double freq) { + if (frozen_ || NULL == spelling_str) + return false; + + for (size_t pos = 0; pos < kNotSupportNum; pos++) { + if (strcmp(spelling_str, kNotSupportList[pos]) == 0) { + return false; + } + } + + total_freq_ += freq; + + size_t hash_pos = get_hash_pos(spelling_str); + + raw_spellings_[hash_pos].str[spelling_size_ - 1] = '\0'; + + if (strncmp(raw_spellings_[hash_pos].str, spelling_str, + spelling_size_ - 1) == 0) { + raw_spellings_[hash_pos].freq += freq; + return true; + } + + size_t hash_pos_ori = hash_pos; + + while (true) { + if (strncmp(raw_spellings_[hash_pos].str, + spelling_str, spelling_size_ - 1) == 0) { + raw_spellings_[hash_pos].freq += freq; + return true; + } + + if ('\0' == raw_spellings_[hash_pos].str[0]) { + raw_spellings_[hash_pos].freq += freq; + strncpy(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1); + raw_spellings_[hash_pos].str[spelling_size_ - 1] = '\0'; + spelling_num_++; + return true; + } + + hash_pos = hash_pos_next(hash_pos); + if (hash_pos_ori == hash_pos) + return false; + } + + // never reach here + return false; + } + + bool SpellingTable::contain(const char* spelling_str) { + if (NULL == spelling_str || NULL == spelling_buf_ || frozen_) + return false; + + size_t hash_pos = get_hash_pos(spelling_str); + + if ('\0' == raw_spellings_[hash_pos].str[0]) + return false; + + if (strncmp(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1) + == 0) + return true; + + size_t hash_pos_ori = hash_pos; + + while (true) { + hash_pos = hash_pos_next(hash_pos); + if (hash_pos_ori == hash_pos) + return false; + + if ('\0' == raw_spellings_[hash_pos].str[0]) + return false; + + if (strncmp(raw_spellings_[hash_pos].str, spelling_str, spelling_size_ - 1) + == 0) + return true; + } + + // never reach here + return false; + } + + const char* SpellingTable::arrange(size_t *item_size, size_t *spl_num) { + if (NULL == raw_spellings_ || NULL == spelling_buf_ || + NULL == item_size || NULL == spl_num) + return NULL; + + qsort(raw_spellings_, spelling_max_num_, sizeof(RawSpelling), + compare_raw_spl_eb); + + // After sorting, only the first spelling_num_ items are valid. + // Copy them to the destination buffer. + for (size_t pos = 0; pos < spelling_num_; pos++) { + strncpy(spelling_buf_ + pos * spelling_size_, raw_spellings_[pos].str, + spelling_size_); + } + + if (need_score_) { + if (kPrintDebug0) + printf("------------Spelling Possiblities--------------\n"); + + double max_score = 0; + double min_score = 0; + + // After sorting, only the first spelling_num_ items are valid. + for (size_t pos = 0; pos < spelling_num_; pos++) { + raw_spellings_[pos].freq /= total_freq_; + if (need_score_) { + if (0 == pos) { + max_score = raw_spellings_[0].freq; + min_score = max_score; + } else { + if (raw_spellings_[pos].freq > max_score) + max_score = raw_spellings_[pos].freq; + if (raw_spellings_[pos].freq < min_score) + min_score = raw_spellings_[pos].freq; + } + } + } + + if (kPrintDebug0) + printf("-----max psb: %f, min psb: %f\n", max_score, min_score); + + max_score = log(max_score); + min_score = log(min_score); + + if (kPrintDebug0) + printf("-----max log value: %f, min log value: %f\n", + max_score, min_score); + + // The absolute value of min_score is bigger than that of max_score because + // both of them are negative after log function. + score_amplifier_ = 1.0 * 255 / min_score; + + double average_score = 0; + for (size_t pos = 0; pos < spelling_num_; pos++) { + double score = log(raw_spellings_[pos].freq) * score_amplifier_; + assert(score >= 0); + + average_score += score; + + // Because of calculation precision issue, score might be a little bigger + // than 255 after being amplified. + if (score > 255) + score = 255; + char *this_spl_buf = spelling_buf_ + pos * spelling_size_; + this_spl_buf[spelling_size_ - 1] = + static_cast((unsigned char)score); + + if (kPrintDebug0) { + printf("---pos:%d, %s, psb:%d\n", pos, this_spl_buf, + (unsigned char)this_spl_buf[spelling_size_ -1]); + } + } + average_score /= spelling_num_; + assert(average_score <= 255); + average_score_ = static_cast(average_score); + + if (kPrintDebug0) + printf("\n----Score Amplifier: %f, Average Score: %d\n", score_amplifier_, + average_score_); + } + + *item_size = spelling_size_; + *spl_num = spelling_num_; + frozen_ = true; + return spelling_buf_; + } + + float SpellingTable::get_score_amplifier() { + return static_cast(score_amplifier_); + } + + unsigned char SpellingTable::get_average_score() { + return average_score_; + } + +#endif // ___BUILD_MODEL___ +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtable.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtable.h new file mode 100644 index 0000000..c6a1777 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtable.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_SPELLINGTABLE_H__ +#define PINYINIME_INCLUDE_SPELLINGTABLE_H__ +#include +#include "./dictdef.h" +namespace ime_pinyin { +#ifdef ___BUILD_MODEL___ + + const size_t kMaxSpellingSize = kMaxPinyinSize; + + typedef struct { + char str[kMaxSpellingSize + 1]; + double freq; + } RawSpelling, *PRawSpelling; + + // This class is used to store the spelling strings + // The length of the input spelling string should be less or equal to the + // spelling_size_ (set by init_table). If the input string is too long, + // we only keep its first spelling_size_ chars. + class SpellingTable { + private: + static const size_t kNotSupportNum = 3; + static const char kNotSupportList[kNotSupportNum][kMaxSpellingSize + 1]; + + bool need_score_; + + size_t spelling_max_num_; + + RawSpelling *raw_spellings_; + + // Used to store spelling strings. If the spelling table needs to calculate + // score, an extra char after each spelling string is the score. + // An item with a lower score has a higher probability. + char *spelling_buf_; + size_t spelling_size_; + + double total_freq_; + + size_t spelling_num_; + + double score_amplifier_; + + unsigned char average_score_; + + // If frozen is true, put_spelling() and contain() are not allowed to call. + bool frozen_; + + size_t get_hash_pos(const char* spelling_str); + size_t hash_pos_next(size_t hash_pos); + void free_resource(); + public: + SpellingTable(); + ~SpellingTable(); + + // pure_spl_size is the pure maximum spelling string size. For example, + // "zhuang" is the longgest item in Pinyin, so pure_spl_size should be 6. + // spl_max_num is the maximum number of spelling strings to store. + // need_score is used to indicate whether the caller needs to calculate a + // score for each spelling. + bool init_table(size_t pure_spl_size, size_t spl_max_num, bool need_score); + + // Put a spelling string to the table. + // It always returns false if called after arrange() withtout a new + // init_table() operation. + // freq is the spelling's occuring count. + // If the spelling has been in the table, occuring count will accumulated. + bool put_spelling(const char* spelling_str, double spl_count); + + // Test whether a spelling string is in the table. + // It always returns false, when being called after arrange() withtout a new + // init_table() operation. + bool contain(const char* spelling_str); + + // Sort the spelling strings and put them from the begin of the buffer. + // Return the pointer of the sorted spelling strings. + // item_size and spl_num return the item size and number of spelling. + // Because each spelling uses a '\0' as terminator, the returned item_size is + // at least one char longer than the spl_size parameter specified by + // init_table(). If the table is initialized to calculate score, item_size + // will be increased by 1, and current_spl_str[item_size - 1] stores an + // unsinged char score. + // An item with a lower score has a higher probability. + // Do not call put_spelling() and contains() after arrange(). + const char* arrange(size_t *item_size, size_t *spl_num); + + float get_score_amplifier(); + + unsigned char get_average_score(); + }; +#endif // ___BUILD_MODEL___ +} +#endif // PINYINIME_INCLUDE_SPELLINGTABLE_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtrie.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtrie.cpp new file mode 100644 index 0000000..8cc7713 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtrie.cpp @@ -0,0 +1,707 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include "dictdef.h" +#ifdef _WIN32 +#define snprintf _snprintf +#endif +#ifdef ___BUILD_MODEL___ +#include "spellingtable.h" +#endif +#include "spellingtrie.h" +namespace ime_pinyin { + SpellingTrie *SpellingTrie::instance_ = NULL; +// z/c/s is for Zh/Ch/Sh + const char SpellingTrie::kHalfId2Sc_[kFullSplIdStart + 1] = + "0ABCcDEFGHIJKLMNOPQRSsTUVWXYZz"; +// Bit 0 : is it a Shengmu char? +// Bit 1 : is it a Yunmu char? (one char is a Yunmu) +// Bit 2 : is it enabled in ShouZiMu(first char) mode? + unsigned char SpellingTrie::char_flags_[] = { + // a b c d e f g + 0x02, 0x01, 0x01, 0x01, 0x02, 0x01, 0x01, + // h i j k l m n + 0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, + // o p q r s t + 0x02, 0x01, 0x01, 0x01, 0x01, 0x01, + // u v w x y z + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01 + }; + int compare_spl(const void *p1, const void *p2) { + return strcmp((const char *) (p1), (const char *) (p2)); + } + SpellingTrie::SpellingTrie() { + spelling_buf_ = NULL; + spelling_size_ = 0; + spelling_num_ = 0; + spl_ym_ids_ = NULL; + splstr_queried_ = NULL; + splstr16_queried_ = NULL; + root_ = NULL; + dumb_node_ = NULL; + splitter_node_ = NULL; + instance_ = NULL; + ym_buf_ = NULL; + f2h_ = NULL; + szm_enable_shm(true); + szm_enable_ym(true); +#ifdef ___BUILD_MODEL___ + node_num_ = 0; +#endif + } + SpellingTrie::~SpellingTrie() { + if (NULL != spelling_buf_) + delete[] spelling_buf_; + if (NULL != splstr_queried_) + delete[] splstr_queried_; + if (NULL != splstr16_queried_) + delete[] splstr16_queried_; + if (NULL != spl_ym_ids_) + delete[] spl_ym_ids_; + if (NULL != root_) { + free_son_trie(root_); + delete root_; + } + if (NULL != dumb_node_) { + delete[] dumb_node_; + } + if (NULL != splitter_node_) { + delete[] splitter_node_; + } + if (NULL != instance_) { + delete instance_; + instance_ = NULL; + } + if (NULL != ym_buf_) + delete[] ym_buf_; + if (NULL != f2h_) + delete[] f2h_; + } + bool SpellingTrie::if_valid_id_update(uint16 *splid) const { + if (NULL == splid || 0 == *splid) + return false; + if (*splid >= kFullSplIdStart) + return true; + if (*splid < kFullSplIdStart) { + char ch = kHalfId2Sc_[*splid]; + if (ch > 'Z') { + return true; + } else { + if (szm_is_enabled(ch)) { + return true; + } else if (is_yunmu_char(ch)) { + assert(h2f_num_[*splid] > 0); + *splid = h2f_start_[*splid]; + return true; + } + } + } + return false; + } + bool SpellingTrie::is_half_id(uint16 splid) const { + if (0 == splid || splid >= kFullSplIdStart) + return false; + return true; + } + bool SpellingTrie::is_full_id(uint16 splid) const { + if (splid < kFullSplIdStart || splid >= kFullSplIdStart + spelling_num_) + return false; + return true; + } + bool SpellingTrie::half_full_compatible(uint16 half_id, uint16 full_id) const { + uint16 half_fr_full = full_to_half(full_id); + if (half_fr_full == half_id) + return true; + + // &~0x20 is used to conver the char to upper case. + // So that Zh/Ch/Sh(whose char is z/c/s) can be matched with Z/C/S. + char ch_f = (kHalfId2Sc_[half_fr_full] & (~0x20)); + char ch_h = kHalfId2Sc_[half_id]; + if (ch_f == ch_h) + return true; + return false; + } + bool SpellingTrie::is_half_id_yunmu(uint16 splid) const { + if (0 == splid || splid >= kFullSplIdStart) + return false; + char ch = kHalfId2Sc_[splid]; + // If ch >= 'a', that means the half id is one of Zh/Ch/Sh + if (ch >= 'a') { + return false; + } + return char_flags_[ch - 'A'] & kHalfIdYunmuMask; + } + bool SpellingTrie::is_shengmu_char(char ch) const { + return char_flags_[ch - 'A'] & kHalfIdShengmuMask; + } + bool SpellingTrie::is_yunmu_char(char ch) const { + return char_flags_[ch - 'A'] & kHalfIdYunmuMask; + } + bool SpellingTrie::is_szm_char(char ch) const { + return is_shengmu_char(ch) || is_yunmu_char(ch); + } + bool SpellingTrie::szm_is_enabled(char ch) const { + return char_flags_[ch - 'A'] & kHalfIdSzmMask; + } + void SpellingTrie::szm_enable_shm(bool enable) { + if (enable) { + for (char ch = 'A'; ch <= 'Z'; ch++) { + if (is_shengmu_char(ch)) + char_flags_[ch - 'A'] = char_flags_[ch - 'A'] | kHalfIdSzmMask; + } + } else { + for (char ch = 'A'; ch <= 'Z'; ch++) { + if (is_shengmu_char(ch)) + char_flags_[ch - 'A'] = char_flags_[ch - 'A'] & (kHalfIdSzmMask ^ 0xff); + } + } + } + void SpellingTrie::szm_enable_ym(bool enable) { + if (enable) { + for (char ch = 'A'; ch <= 'Z'; ch++) { + if (is_yunmu_char(ch)) + char_flags_[ch - 'A'] = char_flags_[ch - 'A'] | kHalfIdSzmMask; + } + } else { + for (char ch = 'A'; ch <= 'Z'; ch++) { + if (is_yunmu_char(ch)) + char_flags_[ch - 'A'] = char_flags_[ch - 'A'] & (kHalfIdSzmMask ^ 0xff); + } + } + } + bool SpellingTrie::is_szm_enabled(char ch) const { + return char_flags_[ch - 'A'] & kHalfIdSzmMask; + } + const SpellingTrie *SpellingTrie::get_cpinstance() { + return &get_instance(); + } + SpellingTrie &SpellingTrie::get_instance() { + if (NULL == instance_) + instance_ = new SpellingTrie(); + return *instance_; + } + uint16 SpellingTrie::half2full_num(uint16 half_id) const { + if (NULL == root_ || half_id >= kFullSplIdStart) + return 0; + return h2f_num_[half_id]; + } + uint16 SpellingTrie::half_to_full(uint16 half_id, uint16 *spl_id_start) const { + if (NULL == spl_id_start || NULL == root_ || half_id >= kFullSplIdStart) + return 0; + *spl_id_start = h2f_start_[half_id]; + return h2f_num_[half_id]; + } + uint16 SpellingTrie::full_to_half(uint16 full_id) const { + if (NULL == root_ || full_id < kFullSplIdStart || + full_id > spelling_num_ + kFullSplIdStart) + return 0; + return f2h_[full_id - kFullSplIdStart]; + } + void SpellingTrie::free_son_trie(SpellingNode *node) { + if (NULL == node) + return; + for (size_t pos = 0; pos < node->num_of_son; pos++) { + free_son_trie(node->first_son + pos); + } + if (NULL != node->first_son) + delete[] node->first_son; + } + bool SpellingTrie::construct(const char *spelling_arr, size_t item_size, + size_t item_num, float score_amplifier, + unsigned char average_score) { + if (spelling_arr == NULL) + return false; + memset(h2f_start_, 0, sizeof(uint16) * kFullSplIdStart); + memset(h2f_num_, 0, sizeof(uint16) * kFullSplIdStart); + + // If the arr is the same as the buf, means this function is called by + // load_table(), the table data are ready; otherwise the array should be + // saved. + if (spelling_arr != spelling_buf_) { + if (NULL != spelling_buf_) + delete[] spelling_buf_; + spelling_buf_ = new char[item_size * item_num]; + if (NULL == spelling_buf_) + return false; + memcpy(spelling_buf_, spelling_arr, sizeof(char) * item_size * item_num); + } + spelling_size_ = item_size; + spelling_num_ = item_num; + score_amplifier_ = score_amplifier; + average_score_ = average_score; + if (NULL != splstr_queried_) + delete[] splstr_queried_; + splstr_queried_ = new char[spelling_size_]; + if (NULL == splstr_queried_) + return false; + if (NULL != splstr16_queried_) + delete[] splstr16_queried_; + splstr16_queried_ = new char16[spelling_size_]; + if (NULL == splstr16_queried_) + return false; + + // First, sort the buf to ensure they are in ascendant order + qsort(spelling_buf_, spelling_num_, spelling_size_, compare_spl); +#ifdef ___BUILD_MODEL___ + node_num_ = 1; +#endif + root_ = new SpellingNode(); + memset(root_, 0, sizeof(SpellingNode)); + dumb_node_ = new SpellingNode(); + memset(dumb_node_, 0, sizeof(SpellingNode)); + dumb_node_->score = average_score_; + splitter_node_ = new SpellingNode(); + memset(splitter_node_, 0, sizeof(SpellingNode)); + splitter_node_->score = average_score_; + memset(level1_sons_, 0, sizeof(SpellingNode *) * kValidSplCharNum); + root_->first_son = construct_spellings_subset(0, spelling_num_, 0, root_); + + // Root's score should be cleared. + root_->score = 0; + if (NULL == root_->first_son) + return false; + h2f_start_[0] = h2f_num_[0] = 0; + if (!build_f2h()) + return false; +#ifdef ___BUILD_MODEL___ + if (kPrintDebug0) { + printf("---SpellingTrie Nodes: %d\n", (int)node_num_); + } + return build_ym_info(); +#else + return true; +#endif + } +#ifdef ___BUILD_MODEL___ + const char* SpellingTrie::get_ym_str(const char *spl_str) { + bool start_ZCS = false; + if (is_shengmu_char(*spl_str)) { + if ('Z' == *spl_str || 'C' == *spl_str || 'S' == *spl_str) + start_ZCS = true; + spl_str += 1; + if (start_ZCS && 'h' == *spl_str) + spl_str += 1; + } + return spl_str; + } + + bool SpellingTrie::build_ym_info() { + bool sucess; + SpellingTable *spl_table = new SpellingTable(); + + sucess = spl_table->init_table(kMaxPinyinSize - 1, 2 * kMaxYmNum, false); + assert(sucess); + + for (uint16 pos = 0; pos < spelling_num_; pos++) { + const char *spl_str = spelling_buf_ + spelling_size_ * pos; + spl_str = get_ym_str(spl_str); + if ('\0' != spl_str[0]) { + sucess = spl_table->put_spelling(spl_str, 0); + assert(sucess); + } + } + + size_t ym_item_size; // '\0' is included + size_t ym_num; + const char* ym_buf; + ym_buf = spl_table->arrange(&ym_item_size, &ym_num); + + if (NULL != ym_buf_) + delete [] ym_buf_; + ym_buf_ = new char[ym_item_size * ym_num]; + if (NULL == ym_buf_) { + delete spl_table; + return false; + } + + memcpy(ym_buf_, ym_buf, sizeof(char) * ym_item_size * ym_num); + ym_size_ = ym_item_size; + ym_num_ = ym_num; + + delete spl_table; + + // Generate the maping from the spelling ids to the Yunmu ids. + if (spl_ym_ids_) + delete spl_ym_ids_; + spl_ym_ids_ = new uint8[spelling_num_ + kFullSplIdStart]; + if (NULL == spl_ym_ids_) + return false; + + memset(spl_ym_ids_, 0, sizeof(uint8) * (spelling_num_ + kFullSplIdStart)); + + for (uint16 id = 1; id < spelling_num_ + kFullSplIdStart; id++) { + const char *str = get_spelling_str(id); + + str = get_ym_str(str); + if ('\0' != str[0]) { + uint8 ym_id = get_ym_id(str); + spl_ym_ids_[id] = ym_id; + assert(ym_id > 0); + } else { + spl_ym_ids_[id] = 0; + } + } + return true; + } +#endif + SpellingNode *SpellingTrie::construct_spellings_subset( + size_t item_start, size_t item_end, size_t level, SpellingNode *parent) { + if (level >= spelling_size_ || item_end <= item_start || NULL == parent) + return NULL; + SpellingNode *first_son = NULL; + uint16 num_of_son = 0; + unsigned char min_son_score = 255; + const char *spelling_last_start = spelling_buf_ + spelling_size_ * item_start; + char char_for_node = spelling_last_start[level]; + assert((char_for_node >= 'A' && char_for_node <= 'Z') || + 'h' == char_for_node); + + // Scan the array to find how many sons + for (size_t i = item_start + 1; i < item_end; i++) { + const char *spelling_current = spelling_buf_ + spelling_size_ * i; + char char_current = spelling_current[level]; + if (char_current != char_for_node) { + num_of_son++; + char_for_node = char_current; + } + } + num_of_son++; + + // Allocate memory +#ifdef ___BUILD_MODEL___ + node_num_ += num_of_son; +#endif + first_son = new SpellingNode[num_of_son]; + memset(first_son, 0, sizeof(SpellingNode) * num_of_son); + + // Now begin construct tree + size_t son_pos = 0; + spelling_last_start = spelling_buf_ + spelling_size_ * item_start; + char_for_node = spelling_last_start[level]; + bool spelling_endable = true; + if (spelling_last_start[level + 1] != '\0') + spelling_endable = false; + size_t item_start_next = item_start; + for (size_t i = item_start + 1; i < item_end; i++) { + const char *spelling_current = spelling_buf_ + spelling_size_ * i; + char char_current = spelling_current[level]; + assert(is_valid_spl_char(char_current)); + if (char_current != char_for_node) { + // Construct a node + SpellingNode *node_current = first_son + son_pos; + node_current->char_this_node = char_for_node; + + // For quick search in the first level + if (0 == level) + level1_sons_[char_for_node - 'A'] = node_current; + if (spelling_endable) { + node_current->spelling_idx = kFullSplIdStart + item_start_next; + } + if (spelling_last_start[level + 1] != '\0' || i - item_start_next > 1) { + size_t real_start = item_start_next; + if (spelling_last_start[level + 1] == '\0') + real_start++; + node_current->first_son = + construct_spellings_subset(real_start, i, level + 1, + node_current); + if (real_start == item_start_next + 1) { + uint16 score_this = static_cast( + spelling_last_start[spelling_size_ - 1]); + if (score_this < node_current->score) + node_current->score = score_this; + } + } else { + node_current->first_son = NULL; + node_current->score = static_cast( + spelling_last_start[spelling_size_ - 1]); + } + if (node_current->score < min_son_score) + min_son_score = node_current->score; + bool is_half = false; + if (level == 0 && is_szm_char(char_for_node)) { + node_current->spelling_idx = + static_cast(char_for_node - 'A' + 1); + if (char_for_node > 'C') + node_current->spelling_idx++; + if (char_for_node > 'S') + node_current->spelling_idx++; + h2f_num_[node_current->spelling_idx] = i - item_start_next; + is_half = true; + } else if (level == 1 && char_for_node == 'h') { + char ch_level0 = spelling_last_start[0]; + uint16 part_id = 0; + if (ch_level0 == 'C') + part_id = 'C' - 'A' + 1 + 1; + else if (ch_level0 == 'S') + part_id = 'S' - 'A' + 1 + 2; + else if (ch_level0 == 'Z') + part_id = 'Z' - 'A' + 1 + 3; + if (0 != part_id) { + node_current->spelling_idx = part_id; + h2f_num_[node_current->spelling_idx] = i - item_start_next; + is_half = true; + } + } + if (is_half) { + if (h2f_num_[node_current->spelling_idx] > 0) + h2f_start_[node_current->spelling_idx] = + item_start_next + kFullSplIdStart; + else + h2f_start_[node_current->spelling_idx] = 0; + } + + // for next sibling + spelling_last_start = spelling_current; + char_for_node = char_current; + item_start_next = i; + spelling_endable = true; + if (spelling_current[level + 1] != '\0') + spelling_endable = false; + son_pos++; + } + } + + // the last one + SpellingNode *node_current = first_son + son_pos; + node_current->char_this_node = char_for_node; + + // For quick search in the first level + if (0 == level) + level1_sons_[char_for_node - 'A'] = node_current; + if (spelling_endable) { + node_current->spelling_idx = kFullSplIdStart + item_start_next; + } + if (spelling_last_start[level + 1] != '\0' || + item_end - item_start_next > 1) { + size_t real_start = item_start_next; + if (spelling_last_start[level + 1] == '\0') + real_start++; + node_current->first_son = + construct_spellings_subset(real_start, item_end, level + 1, + node_current); + if (real_start == item_start_next + 1) { + uint16 score_this = static_cast( + spelling_last_start[spelling_size_ - 1]); + if (score_this < node_current->score) + node_current->score = score_this; + } + } else { + node_current->first_son = NULL; + node_current->score = static_cast( + spelling_last_start[spelling_size_ - 1]); + } + if (node_current->score < min_son_score) + min_son_score = node_current->score; + assert(son_pos + 1 == num_of_son); + bool is_half = false; + if (level == 0 && szm_is_enabled(char_for_node)) { + node_current->spelling_idx = static_cast(char_for_node - 'A' + 1); + if (char_for_node > 'C') + node_current->spelling_idx++; + if (char_for_node > 'S') + node_current->spelling_idx++; + h2f_num_[node_current->spelling_idx] = item_end - item_start_next; + is_half = true; + } else if (level == 1 && char_for_node == 'h') { + char ch_level0 = spelling_last_start[0]; + uint16 part_id = 0; + if (ch_level0 == 'C') + part_id = 'C' - 'A' + 1 + 1; + else if (ch_level0 == 'S') + part_id = 'S' - 'A' + 1 + 2; + else if (ch_level0 == 'Z') + part_id = 'Z' - 'A' + 1 + 3; + if (0 != part_id) { + node_current->spelling_idx = part_id; + h2f_num_[node_current->spelling_idx] = item_end - item_start_next; + is_half = true; + } + } + if (is_half) { + if (h2f_num_[node_current->spelling_idx] > 0) + h2f_start_[node_current->spelling_idx] = + item_start_next + kFullSplIdStart; + else + h2f_start_[node_current->spelling_idx] = 0; + } + parent->num_of_son = num_of_son; + parent->score = min_son_score; + return first_son; + } + bool SpellingTrie::save_spl_trie(FILE *fp) { + if (NULL == fp || NULL == spelling_buf_) + return false; + if (fwrite(&spelling_size_, sizeof(uint32), 1, fp) != 1) + return false; + if (fwrite(&spelling_num_, sizeof(uint32), 1, fp) != 1) + return false; + if (fwrite(&score_amplifier_, sizeof(float), 1, fp) != 1) + return false; + if (fwrite(&average_score_, sizeof(unsigned char), 1, fp) != 1) + return false; + if (fwrite(spelling_buf_, sizeof(char) * spelling_size_, + spelling_num_, fp) != spelling_num_) + return false; + return true; + } + bool SpellingTrie::load_spl_trie(FILE *fp) { + if (NULL == fp) + return false; + if (fread(&spelling_size_, sizeof(uint32), 1, fp) != 1) + return false; + if (fread(&spelling_num_, sizeof(uint32), 1, fp) != 1) + return false; + if (fread(&score_amplifier_, sizeof(float), 1, fp) != 1) + return false; + if (fread(&average_score_, sizeof(unsigned char), 1, fp) != 1) + return false; + if (NULL != spelling_buf_) + delete[] spelling_buf_; + spelling_buf_ = new char[spelling_size_ * spelling_num_]; + if (NULL == spelling_buf_) + return false; + if (fread(spelling_buf_, sizeof(char) * spelling_size_, + spelling_num_, fp) != spelling_num_) + return false; + return construct(spelling_buf_, spelling_size_, spelling_num_, + score_amplifier_, average_score_); + } + bool SpellingTrie::build_f2h() { + if (NULL != f2h_) + delete[] f2h_; + f2h_ = new uint16[spelling_num_]; + if (NULL == f2h_) + return false; + for (uint16 hid = 0; hid < kFullSplIdStart; hid++) { + for (uint16 fid = h2f_start_[hid]; + fid < h2f_start_[hid] + h2f_num_[hid]; fid++) + f2h_[fid - kFullSplIdStart] = hid; + } + return true; + } + size_t SpellingTrie::get_spelling_num() { + return spelling_num_; + } + uint8 SpellingTrie::get_ym_id(const char *ym_str) { + if (NULL == ym_str || NULL == ym_buf_) + return 0; + for (uint8 pos = 0; pos < ym_num_; pos++) + if (strcmp(ym_buf_ + ym_size_ * pos, ym_str) == 0) + return pos + 1; + return 0; + } + const char *SpellingTrie::get_spelling_str(uint16 splid) { + splstr_queried_[0] = '\0'; + if (splid >= kFullSplIdStart) { + splid -= kFullSplIdStart; + snprintf(splstr_queried_, spelling_size_, "%s", + spelling_buf_ + splid * spelling_size_); + } else { + if (splid == 'C' - 'A' + 1 + 1) { + snprintf(splstr_queried_, spelling_size_, "%s", "Ch"); + } else if (splid == 'S' - 'A' + 1 + 2) { + snprintf(splstr_queried_, spelling_size_, "%s", "Sh"); + } else if (splid == 'Z' - 'A' + 1 + 3) { + snprintf(splstr_queried_, spelling_size_, "%s", "Zh"); + } else { + if (splid > 'C' - 'A' + 1) + splid--; + if (splid > 'S' - 'A' + 1) + splid--; + splstr_queried_[0] = 'A' + splid - 1; + splstr_queried_[1] = '\0'; + } + } + return splstr_queried_; + } + const char16 *SpellingTrie::get_spelling_str16(uint16 splid) { + splstr16_queried_[0] = '\0'; + if (splid >= kFullSplIdStart) { + splid -= kFullSplIdStart; + for (size_t pos = 0; pos < spelling_size_; pos++) { + splstr16_queried_[pos] = static_cast + (spelling_buf_[splid * spelling_size_ + pos]); + } + } else { + if (splid == 'C' - 'A' + 1 + 1) { + splstr16_queried_[0] = static_cast('C'); + splstr16_queried_[1] = static_cast('h'); + splstr16_queried_[2] = static_cast('\0'); + } else if (splid == 'S' - 'A' + 1 + 2) { + splstr16_queried_[0] = static_cast('S'); + splstr16_queried_[1] = static_cast('h'); + splstr16_queried_[2] = static_cast('\0'); + } else if (splid == 'Z' - 'A' + 1 + 3) { + splstr16_queried_[0] = static_cast('Z'); + splstr16_queried_[1] = static_cast('h'); + splstr16_queried_[2] = static_cast('\0'); + } else { + if (splid > 'C' - 'A' + 1) + splid--; + if (splid > 'S' - 'A' + 1) + splid--; + splstr16_queried_[0] = 'A' + splid - 1; + splstr16_queried_[1] = '\0'; + } + } + return splstr16_queried_; + } + size_t SpellingTrie::get_spelling_str16(uint16 splid, char16 *splstr16, + size_t splstr16_len) { + if (NULL == splstr16 || splstr16_len < kMaxPinyinSize + 1) return 0; + if (splid >= kFullSplIdStart) { + splid -= kFullSplIdStart; + for (size_t pos = 0; pos <= kMaxPinyinSize; pos++) { + splstr16[pos] = static_cast + (spelling_buf_[splid * spelling_size_ + pos]); + if (static_cast('\0') == splstr16[pos]) { + return pos; + } + } + } else { + if (splid == 'C' - 'A' + 1 + 1) { + splstr16[0] = static_cast('C'); + splstr16[1] = static_cast('h'); + splstr16[2] = static_cast('\0'); + return 2; + } else if (splid == 'S' - 'A' + 1 + 2) { + splstr16[0] = static_cast('S'); + splstr16[1] = static_cast('h'); + splstr16[2] = static_cast('\0'); + return 2; + } else if (splid == 'Z' - 'A' + 1 + 3) { + splstr16[0] = static_cast('Z'); + splstr16[1] = static_cast('h'); + splstr16[2] = static_cast('\0'); + return 2; + } else { + if (splid > 'C' - 'A' + 1) + splid--; + if (splid > 'S' - 'A' + 1) + splid--; + splstr16[0] = 'A' + splid - 1; + splstr16[1] = '\0'; + return 1; + } + } + + // Not reachable. + return 0; + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtrie.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtrie.h new file mode 100644 index 0000000..2e65812 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/spellingtrie.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_SPELLINGTRIE_H__ +#define PINYINIME_INCLUDE_SPELLINGTRIE_H__ +#include +#include +#include "./dictdef.h" +namespace ime_pinyin { + static const unsigned short kFullSplIdStart = kHalfSpellingIdNum + 1; +// Node used for the trie of spellings + struct SpellingNode { + SpellingNode *first_son; + // The spelling id for each node. If you need more bits to store + // spelling id, please adjust this structure. + uint16 spelling_idx: 11; + uint16 num_of_son: 5; + char char_this_node; + unsigned char score; + }; + class SpellingTrie { + private: + static const int kMaxYmNum = 64; + static const size_t kValidSplCharNum = 26; + static const uint16 kHalfIdShengmuMask = 0x01; + static const uint16 kHalfIdYunmuMask = 0x02; + static const uint16 kHalfIdSzmMask = 0x04; + // Map from half spelling id to single char. + // For half ids of Zh/Ch/Sh, map to z/c/s (low case) respectively. + // For example, 1 to 'A', 2 to 'B', 3 to 'C', 4 to 'c', 5 to 'D', ..., + // 28 to 'Z', 29 to 'z'. + // [0] is not used to achieve better efficiency. + static const char kHalfId2Sc_[kFullSplIdStart + 1]; + static unsigned char char_flags_[]; + static SpellingTrie *instance_; + // The spelling table + char *spelling_buf_; + // The size of longest spelling string, includes '\0' and an extra char to + // store score. For example, "zhuang" is the longgest item in Pinyin list, + // so spelling_size_ is 8. + // Structure: The string ended with '\0' + score char. + // An item with a lower score has a higher probability. + uint32 spelling_size_; + // Number of full spelling ids. + uint32 spelling_num_; + float score_amplifier_; + unsigned char average_score_; + // The Yunmu id list for the spelling ids (for half ids of Shengmu, + // the Yunmu id is 0). + // The length of the list is spelling_num_ + kFullSplIdStart, + // so that spl_ym_ids_[splid] is the Yunmu id of the splid. + uint8 *spl_ym_ids_; + // The Yunmu table. + // Each Yunmu will be assigned with Yunmu id from 1. + char *ym_buf_; + size_t ym_size_; // The size of longest Yunmu string, '\0'included. + size_t ym_num_; + // The spelling string just queried + char *splstr_queried_; + // The spelling string just queried + char16 *splstr16_queried_; + // The root node of the spelling tree + SpellingNode *root_; + // If a none qwerty key such as a fnction key like ENTER is given, this node + // will be used to indicate that this is not a QWERTY node. + SpellingNode *dumb_node_; + // If a splitter key is pressed, this node will be used to indicate that this + // is a splitter key. + SpellingNode *splitter_node_; + // Used to get the first level sons. + SpellingNode *level1_sons_[kValidSplCharNum]; + // The full spl_id range for specific half id. + // h2f means half to full. + // A half id can be a ShouZiMu id (id to represent the first char of a full + // spelling, including Shengmu and Yunmu), or id of zh/ch/sh. + // [1..kFullSplIdStart-1] is the arrange of half id. + uint16 h2f_start_[kFullSplIdStart]; + uint16 h2f_num_[kFullSplIdStart]; + // Map from full id to half id. + uint16 *f2h_; +#ifdef ___BUILD_MODEL___ + // How many node used to build the trie. + size_t node_num_; +#endif + SpellingTrie(); + void free_son_trie(SpellingNode *node); + // Construct a subtree using a subset of the spelling array (from + // item_star to item_end). + // Member spelliing_buf_ and spelling_size_ should be valid. + // parent is used to update its num_of_son and score. + SpellingNode *construct_spellings_subset(size_t item_start, size_t item_end, + size_t level, SpellingNode *parent); + bool build_f2h(); + // The caller should guarantee ch >= 'A' && ch <= 'Z' + bool is_shengmu_char(char ch) const; + // The caller should guarantee ch >= 'A' && ch <= 'Z' + bool is_yunmu_char(char ch) const; +#ifdef ___BUILD_MODEL___ + // Given a spelling string, return its Yunmu string. + // The caller guaratees spl_str is valid. + const char* get_ym_str(const char *spl_str); + + // Build the Yunmu list, and the mapping relation between the full ids and the + // Yunmu ids. This functin is called after the spelling trie is built. + bool build_ym_info(); +#endif + friend class SpellingParser; + friend class SmartSplParser; + friend class SmartSplParser2; + public: + ~SpellingTrie(); + inline static bool is_valid_spl_char(char ch) { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); + } + // The caller guarantees that the two chars are valid spelling chars. + inline static bool is_same_spl_char(char ch1, char ch2) { + return ch1 == ch2 || ch1 - ch2 == 'a' - 'A' || ch2 - ch1 == 'a' - 'A'; + } + // Construct the tree from the input pinyin array + // The given string list should have been sorted. + // score_amplifier is used to convert a possibility value into score. + // average_score is the average_score of all spellings. The dumb node is + // assigned with this score. + bool construct(const char *spelling_arr, size_t item_size, size_t item_num, + float score_amplifier, unsigned char average_score); + // Test if the given id is a valid spelling id. + // If function returns true, the given splid may be updated like this: + // When 'A' is not enabled in ShouZiMu mode, the parsing result for 'A' is + // first given as a half id 1, but because 'A' is a one-char Yunmu and + // it is a valid id, it needs to updated to its corresponding full id. + bool if_valid_id_update(uint16 *splid) const; + // Test if the given id is a half id. + bool is_half_id(uint16 splid) const; + bool is_full_id(uint16 splid) const; + // Test if the given id is a one-char Yunmu id (obviously, it is also a half + // id), such as 'A', 'E' and 'O'. + bool is_half_id_yunmu(uint16 splid) const; + // Test if this char is a ShouZiMu char. This ShouZiMu char may be not enabled. + // For Pinyin, only i/u/v is not a ShouZiMu char. + // The caller should guarantee that ch >= 'A' && ch <= 'Z' + bool is_szm_char(char ch) const; + // Test If this char is enabled in ShouZiMu mode. + // The caller should guarantee that ch >= 'A' && ch <= 'Z' + bool szm_is_enabled(char ch) const; + // Enable/disable Shengmus in ShouZiMu mode(using the first char of a spelling + // to input). + void szm_enable_shm(bool enable); + // Enable/disable Yunmus in ShouZiMu mode. + void szm_enable_ym(bool enable); + // Test if this char is enabled in ShouZiMu mode. + // The caller should guarantee ch >= 'A' && ch <= 'Z' + bool is_szm_enabled(char ch) const; + // Return the number of full ids for the given half id. + uint16 half2full_num(uint16 half_id) const; + // Return the number of full ids for the given half id, and fill spl_id_start + // to return the first full id. + uint16 half_to_full(uint16 half_id, uint16 *spl_id_start) const; + // Return the corresponding half id for the given full id. + // Not frequently used, low efficient. + // Return 0 if fails. + uint16 full_to_half(uint16 full_id) const; + // To test whether a half id is compatible with a full id. + // Generally, when half_id == full_to_half(full_id), return true. + // But for "Zh, Ch, Sh", if fussy mode is on, half id for 'Z' is compatible + // with a full id like "Zhe". (Fussy mode is not ready). + bool half_full_compatible(uint16 half_id, uint16 full_id) const; + static const SpellingTrie *get_cpinstance(); + static SpellingTrie &get_instance(); + // Save to the file stream + bool save_spl_trie(FILE *fp); + // Load from the file stream + bool load_spl_trie(FILE *fp); + // Get the number of spellings + size_t get_spelling_num(); + // Return the Yunmu id for the given Yunmu string. + // If the string is not valid, return 0; + uint8 get_ym_id(const char *ym_str); + // Get the readonly Pinyin string for a given spelling id + const char *get_spelling_str(uint16 splid); + // Get the readonly Pinyin string for a given spelling id + const char16 *get_spelling_str16(uint16 splid); + // Get Pinyin string for a given spelling id. Return the length of the + // string, and fill-in '\0' at the end. + size_t get_spelling_str16(uint16 splid, char16 *splstr16, + size_t splstr16_len); + }; +} +#endif // PINYINIME_INCLUDE_SPELLINGTRIE_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/splparser.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/splparser.cpp new file mode 100644 index 0000000..b4ccf8e --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/splparser.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "splparser.h" +namespace ime_pinyin { + SpellingParser::SpellingParser() { + spl_trie_ = SpellingTrie::get_cpinstance(); + } + bool SpellingParser::is_valid_to_parse(char ch) { + return SpellingTrie::is_valid_spl_char(ch); + } + uint16 SpellingParser::splstr_to_idxs(const char *splstr, uint16 str_len, + uint16 spl_idx[], uint16 start_pos[], + uint16 max_size, bool &last_is_pre) { + if (NULL == splstr || 0 == max_size || 0 == str_len) + return 0; + if (!SpellingTrie::is_valid_spl_char(splstr[0])) + return 0; + last_is_pre = false; + const SpellingNode *node_this = spl_trie_->root_; + uint16 str_pos = 0; + uint16 idx_num = 0; + if (NULL != start_pos) + start_pos[0] = 0; + bool last_is_splitter = false; + while (str_pos < str_len) { + char char_this = splstr[str_pos]; + // all characters outside of [a, z] are considered as splitters + if (!SpellingTrie::is_valid_spl_char(char_this)) { + // test if the current node is endable + uint16 id_this = node_this->spelling_idx; + if (spl_trie_->if_valid_id_update(&id_this)) { + spl_idx[idx_num] = id_this; + idx_num++; + str_pos++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + if (idx_num >= max_size) + return idx_num; + node_this = spl_trie_->root_; + last_is_splitter = true; + continue; + } else { + if (last_is_splitter) { + str_pos++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + continue; + } else { + return idx_num; + } + } + } + last_is_splitter = false; + SpellingNode *found_son = NULL; + if (0 == str_pos) { + if (char_this >= 'a') + found_son = spl_trie_->level1_sons_[char_this - 'a']; + else + found_son = spl_trie_->level1_sons_[char_this - 'A']; + } else { + SpellingNode *first_son = node_this->first_son; + // Because for Zh/Ch/Sh nodes, they are the last in the buffer and + // frequently used, so we scan from the end. + for (int i = 0; i < node_this->num_of_son; i++) { + SpellingNode *this_son = first_son + i; + if (SpellingTrie::is_same_spl_char( + this_son->char_this_node, char_this)) { + found_son = this_son; + break; + } + } + } + + // found, just move the current node pointer to the the son + if (NULL != found_son) { + node_this = found_son; + } else { + // not found, test if it is endable + uint16 id_this = node_this->spelling_idx; + if (spl_trie_->if_valid_id_update(&id_this)) { + // endable, remember the index + spl_idx[idx_num] = id_this; + idx_num++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + if (idx_num >= max_size) + return idx_num; + node_this = spl_trie_->root_; + continue; + } else { + return idx_num; + } + } + str_pos++; + } + uint16 id_this = node_this->spelling_idx; + if (spl_trie_->if_valid_id_update(&id_this)) { + // endable, remember the index + spl_idx[idx_num] = id_this; + idx_num++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + } + last_is_pre = !last_is_splitter; + return idx_num; + } + uint16 SpellingParser::splstr_to_idxs_f(const char *splstr, uint16 str_len, + uint16 spl_idx[], uint16 start_pos[], + uint16 max_size, bool &last_is_pre) { + uint16 idx_num = splstr_to_idxs(splstr, str_len, spl_idx, start_pos, + max_size, last_is_pre); + for (uint16 pos = 0; pos < idx_num; pos++) { + if (spl_trie_->is_half_id_yunmu(spl_idx[pos])) { + spl_trie_->half_to_full(spl_idx[pos], spl_idx + pos); + if (pos == idx_num - 1) { + last_is_pre = false; + } + } + } + return idx_num; + } + uint16 SpellingParser::splstr16_to_idxs(const char16 *splstr, uint16 str_len, + uint16 spl_idx[], uint16 start_pos[], + uint16 max_size, bool &last_is_pre) { + if (NULL == splstr || 0 == max_size || 0 == str_len) + return 0; + if (!SpellingTrie::is_valid_spl_char(splstr[0])) + return 0; + last_is_pre = false; + const SpellingNode *node_this = spl_trie_->root_; + uint16 str_pos = 0; + uint16 idx_num = 0; + if (NULL != start_pos) + start_pos[0] = 0; + bool last_is_splitter = false; + while (str_pos < str_len) { + char16 char_this = splstr[str_pos]; + // all characters outside of [a, z] are considered as splitters + if (!SpellingTrie::is_valid_spl_char(char_this)) { + // test if the current node is endable + uint16 id_this = node_this->spelling_idx; + if (spl_trie_->if_valid_id_update(&id_this)) { + spl_idx[idx_num] = id_this; + idx_num++; + str_pos++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + if (idx_num >= max_size) + return idx_num; + node_this = spl_trie_->root_; + last_is_splitter = true; + continue; + } else { + if (last_is_splitter) { + str_pos++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + continue; + } else { + return idx_num; + } + } + } + last_is_splitter = false; + SpellingNode *found_son = NULL; + if (0 == str_pos) { + if (char_this >= 'a') + found_son = spl_trie_->level1_sons_[char_this - 'a']; + else + found_son = spl_trie_->level1_sons_[char_this - 'A']; + } else { + SpellingNode *first_son = node_this->first_son; + // Because for Zh/Ch/Sh nodes, they are the last in the buffer and + // frequently used, so we scan from the end. + for (int i = 0; i < node_this->num_of_son; i++) { + SpellingNode *this_son = first_son + i; + if (SpellingTrie::is_same_spl_char( + this_son->char_this_node, char_this)) { + found_son = this_son; + break; + } + } + } + + // found, just move the current node pointer to the the son + if (NULL != found_son) { + node_this = found_son; + } else { + // not found, test if it is endable + uint16 id_this = node_this->spelling_idx; + if (spl_trie_->if_valid_id_update(&id_this)) { + // endable, remember the index + spl_idx[idx_num] = id_this; + idx_num++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + if (idx_num >= max_size) + return idx_num; + node_this = spl_trie_->root_; + continue; + } else { + return idx_num; + } + } + str_pos++; + } + uint16 id_this = node_this->spelling_idx; + if (spl_trie_->if_valid_id_update(&id_this)) { + // endable, remember the index + spl_idx[idx_num] = id_this; + idx_num++; + if (NULL != start_pos) + start_pos[idx_num] = str_pos; + } + last_is_pre = !last_is_splitter; + return idx_num; + } + uint16 SpellingParser::splstr16_to_idxs_f(const char16 *splstr, uint16 str_len, + uint16 spl_idx[], uint16 start_pos[], + uint16 max_size, bool &last_is_pre) { + uint16 idx_num = splstr16_to_idxs(splstr, str_len, spl_idx, start_pos, + max_size, last_is_pre); + for (uint16 pos = 0; pos < idx_num; pos++) { + if (spl_trie_->is_half_id_yunmu(spl_idx[pos])) { + spl_trie_->half_to_full(spl_idx[pos], spl_idx + pos); + if (pos == idx_num - 1) { + last_is_pre = false; + } + } + } + return idx_num; + } + uint16 SpellingParser::get_splid_by_str(const char *splstr, uint16 str_len, + bool *is_pre) { + if (NULL == is_pre) + return 0; + uint16 spl_idx[2]; + uint16 start_pos[3]; + if (splstr_to_idxs(splstr, str_len, spl_idx, start_pos, 2, *is_pre) != 1) + return 0; + if (start_pos[1] != str_len) + return 0; + return spl_idx[0]; + } + uint16 SpellingParser::get_splid_by_str_f(const char *splstr, uint16 str_len, + bool *is_pre) { + if (NULL == is_pre) + return 0; + uint16 spl_idx[2]; + uint16 start_pos[3]; + if (splstr_to_idxs(splstr, str_len, spl_idx, start_pos, 2, *is_pre) != 1) + return 0; + if (start_pos[1] != str_len) + return 0; + if (spl_trie_->is_half_id_yunmu(spl_idx[0])) { + spl_trie_->half_to_full(spl_idx[0], spl_idx); + *is_pre = false; + } + return spl_idx[0]; + } + uint16 SpellingParser::get_splids_parallel(const char *splstr, uint16 str_len, + uint16 splidx[], uint16 max_size, + uint16 &full_id_num, bool &is_pre) { + if (max_size <= 0 || !is_valid_to_parse(splstr[0])) + return 0; + splidx[0] = get_splid_by_str(splstr, str_len, &is_pre); + full_id_num = 0; + if (0 != splidx[0]) { + if (splidx[0] >= kFullSplIdStart) + full_id_num = 1; + return 1; + } + return 0; + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/splparser.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/splparser.h new file mode 100644 index 0000000..11727e1 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/splparser.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_SPLPARSER_H__ +#define PINYINIME_INCLUDE_SPLPARSER_H__ +#include "./dictdef.h" +#include "./spellingtrie.h" +namespace ime_pinyin { + class SpellingParser { + protected: + const SpellingTrie *spl_trie_; + public: + SpellingParser(); + // Given a string, parse it into a spelling id stream. + // If the whole string are sucessfully parsed, last_is_pre will be true; + // if the whole string is not fullly parsed, last_is_pre will return whether + // the last part of the string is a prefix of a full spelling string. For + // example, given string "zhengzhon", "zhon" is not a valid speling, but it is + // the prefix of "zhong". + // + // If splstr starts with a character not in ['a'-z'] (it is a split char), + // return 0. + // Split char can only appear in the middle of the string or at the end. + uint16 splstr_to_idxs(const char *splstr, uint16 str_len, uint16 splidx[], + uint16 start_pos[], uint16 max_size, bool &last_is_pre); + // Similar to splstr_to_idxs(), the only difference is that splstr_to_idxs() + // convert single-character Yunmus into half ids, while this function converts + // them into full ids. + uint16 splstr_to_idxs_f(const char *splstr, uint16 str_len, uint16 splidx[], + uint16 start_pos[], uint16 max_size, bool &last_is_pre); + // Similar to splstr_to_idxs(), the only difference is that this function + // uses char16 instead of char8. + uint16 splstr16_to_idxs(const char16 *splstr, uint16 str_len, uint16 splidx[], + uint16 start_pos[], uint16 max_size, bool &last_is_pre); + // Similar to splstr_to_idxs_f(), the only difference is that this function + // uses char16 instead of char8. + uint16 splstr16_to_idxs_f(const char16 *splstr16, uint16 str_len, + uint16 splidx[], uint16 start_pos[], + uint16 max_size, bool &last_is_pre); + // If the given string is a spelling, return the id, others, return 0. + // If the give string is a single char Yunmus like "A", and the char is + // enabled in ShouZiMu mode, the returned spelling id will be a half id. + // When the returned spelling id is a half id, *is_pre returns whether it + // is a prefix of a full spelling string. + uint16 get_splid_by_str(const char *splstr, uint16 str_len, bool *is_pre); + // If the given string is a spelling, return the id, others, return 0. + // If the give string is a single char Yunmus like "a", no matter the char + // is enabled in ShouZiMu mode or not, the returned spelling id will be + // a full id. + // When the returned spelling id is a half id, *p_is_pre returns whether it + // is a prefix of a full spelling string. + uint16 get_splid_by_str_f(const char *splstr, uint16 str_len, bool *is_pre); + // Splitter chars are not included. + bool is_valid_to_parse(char ch); + // When auto-correction is not enabled, get_splid_by_str() will be called to + // return the single result. When auto-correction is enabled, this function + // will be called to get the results. Auto-correction is not ready. + // full_id_num returns number of full spelling ids. + // is_pre returns whether the given string is the prefix of a full spelling + // string. + // If splstr starts with a character not in [a-zA-Z] (it is a split char), + // return 0. + // Split char can only appear in the middle of the string or at the end. + // The caller should guarantee NULL != splstr && str_len > 0 && NULL != splidx + uint16 get_splids_parallel(const char *splstr, uint16 str_len, + uint16 splidx[], uint16 max_size, + uint16 &full_id_num, bool &is_pre); + }; +} +#endif // PINYINIME_INCLUDE_SPLPARSER_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/sync.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/sync.cpp new file mode 100644 index 0000000..459df43 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/sync.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sync.h" +#include +#include +#ifdef ___SYNC_ENABLED___ +namespace ime_pinyin { + Sync::Sync() + : userdict_(NULL), + dictfile_(NULL), + last_count_(0) { + }; + Sync::~Sync() { + } + bool Sync::begin(const char *filename) { + if (userdict_) { + finish(); + } + if (!filename) { + return false; + } + dictfile_ = strdup(filename); + if (!dictfile_) { + return false; + } + userdict_ = new UserDict(); + if (!userdict_) { + free(dictfile_); + dictfile_ = NULL; + return false; + } + if (userdict_->load_dict((const char *) dictfile_, kUserDictIdStart, + kUserDictIdEnd) == false) { + delete userdict_; + userdict_ = NULL; + free(dictfile_); + dictfile_ = NULL; + return false; + } + userdict_->set_limit(kUserDictMaxLemmaCount, kUserDictMaxLemmaSize, kUserDictRatio); + return true; + } + int Sync::put_lemmas(char16 *lemmas, int len) { + return userdict_->put_lemmas_no_sync_from_utf16le_string(lemmas, len); + } + int Sync::get_lemmas(char16 *str, int size) { + return userdict_->get_sync_lemmas_in_utf16le_string_from_beginning(str, size, &last_count_); + } + int Sync::get_last_got_count() { + return last_count_; + } + int Sync::get_total_count() { + return userdict_->get_sync_count(); + } + void Sync::clear_last_got() { + if (last_count_ < 0) { + return; + } + userdict_->clear_sync_lemmas(0, last_count_); + last_count_ = 0; + } + void Sync::finish() { + if (userdict_) { + userdict_->close_dict(); + delete userdict_; + userdict_ = NULL; + free(dictfile_); + dictfile_ = NULL; + last_count_ = 0; + } + } + int Sync::get_capacity() { + UserDict::UserDictStat stat; + userdict_->state(&stat); + return stat.limit_lemma_count - stat.lemma_count; + } +} +#endif diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/sync.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/sync.h new file mode 100644 index 0000000..4a43db5 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/sync.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_SYNC_H__ +#define PINYINIME_INCLUDE_SYNC_H__ +#define ___SYNC_ENABLED___ +#ifdef ___SYNC_ENABLED___ +#include "userdict.h" +namespace ime_pinyin { +// Class for user dictionary synchronization +// This class is not thread safe +// Normal invoking flow will be +// begin() -> +// put_lemmas() x N -> +// { +// get_lemmas() -> +// [ get_last_got_count() ] -> +// clear_last_got() -> +// } x N -> +// finish() + class Sync { + public: + Sync(); + ~Sync(); + static const int kUserDictMaxLemmaCount = 5000; + static const int kUserDictMaxLemmaSize = 200000; + static const int kUserDictRatio = 20; + bool begin(const char *filename); + // Merge lemmas downloaded from sync server into local dictionary + // lemmas, lemmas string encoded in UTF16LE + // len, length of lemmas string + // Return how many lemmas merged successfully + int put_lemmas(char16 *lemmas, int len); + // Get local new user lemmas into UTF16LE string + // str, buffer ptr to store new user lemmas + // size, size of buffer + // Return length of returned buffer in measure of UTF16LE + int get_lemmas(char16 *str, int size); + // Return lemmas count in last get_lemmas() + int get_last_got_count(); + // Return total lemmas count need get_lemmas() + int get_total_count(); + // Clear lemmas got by recent get_lemmas() + void clear_last_got(); + void finish(); + int get_capacity(); + private: + UserDict *userdict_; + char *dictfile_; + int last_count_; + }; +} +#endif +#endif // PINYINIME_INCLUDE_SYNC_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/userdict.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/userdict.cpp new file mode 100644 index 0000000..e1bf42c --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/userdict.cpp @@ -0,0 +1,2049 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "userdict.h" +#include "splparser.h" +#include "ngram.h" +#include +#include +#include +#ifdef ___DEBUG_PERF___ +#include +#endif +#ifdef _WIN32 +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#ifndef _WIN32 +#include +#endif +#include +#ifdef _WIN32 +#undef max +#undef min +#include +#include +#else +#include +#endif +#include +namespace ime_pinyin { +#ifdef _WIN32 + static int gettimeofday(struct timeval *tp, void *) { + const qint64 current_msecs_since_epoch = QDateTime::currentMSecsSinceEpoch(); + tp->tv_sec = (long) (current_msecs_since_epoch / 1000); + tp->tv_usec = (long) ((current_msecs_since_epoch % 1000) * 1000); + return 0; + } +#endif +#ifdef ___DEBUG_PERF___ + static uint64 _ellapse_ = 0; + static struct timeval _tv_start_, _tv_end_; +#define DEBUG_PERF_BEGIN \ + do { \ + gettimeofday(&_tv_start_, NULL); \ + } while (0) +#define DEBUG_PERF_END \ + do { \ + gettimeofday(&_tv_end_, NULL); \ + _ellapse_ = (_tv_end_.tv_sec - _tv_start_.tv_sec) * 1000000 + \ + (_tv_end_.tv_usec - _tv_start_.tv_usec); \ + } while (0) +#define LOGD_PERF(message) \ + ALOGD("PERFORMANCE[%s] %llu usec.", message, _ellapse_); +#else +#define DEBUG_PERF_BEGIN +#define DEBUG_PERF_END +#define LOGD_PERF(message) +#endif + +// XXX File load and write are thread-safe by g_mutex_ +#ifdef _WIN32 + static QMutex g_mutex_; +#define pthread_mutex_lock(MUTEX) ((MUTEX)->lock()) +#define pthread_mutex_unlock(MUTEX) ((MUTEX)->unlock()) +#define pthread_mutex_trylock(MUTEX) (!(MUTEX)->tryLock(0)) +#else + static pthread_mutex_t g_mutex_ = PTHREAD_MUTEX_INITIALIZER; +#endif + static struct timeval g_last_update_ = {0, 0}; + inline uint32 UserDict::get_dict_file_size(UserDictInfo *info) { + return (4 + info->lemma_size + (info->lemma_count << 3) + #ifdef ___PREDICT_ENABLED___ + + (info->lemma_count << 2) + #endif + #ifdef ___SYNC_ENABLED___ + + (info->sync_count << 2) + #endif + + sizeof(*info)); + } + inline LmaScoreType UserDict::translate_score(int raw_score) { + // 1) ori_freq: original user frequency + uint32 ori_freq = extract_score_freq(raw_score); + // 2) lmt_off: lmt index (week offset for example) + uint64 lmt_off = ((raw_score & 0xffff0000) >> 16); + if (kUserDictLMTBitWidth < 16) { + uint64 mask = ~(1 << kUserDictLMTBitWidth); + lmt_off &= mask; + } + // 3) now_off: current time index (current week offset for example) + // assuming load_time_ is around current time + uint64 now_off = load_time_.tv_sec; + now_off = (now_off - kUserDictLMTSince) / kUserDictLMTGranularity; + now_off = (now_off << (64 - kUserDictLMTBitWidth)); + now_off = (now_off >> (64 - kUserDictLMTBitWidth)); + // 4) factor: decide expand-factor + int delta = now_off - lmt_off; + if (delta > 4) + delta = 4; + int factor = 80 - (delta << 4); + double tf = (double) (dict_info_.total_nfreq + total_other_nfreq_); + return (LmaScoreType) (log((double) factor * (double) ori_freq / tf) + * NGram::kLogValueAmplifier); + } + inline int UserDict::extract_score_freq(int raw_score) { + // Frequence stored in lowest 16 bits + int freq = (raw_score & 0x0000ffff); + return freq; + } + inline uint64 UserDict::extract_score_lmt(int raw_score) { + uint64 lmt = ((raw_score & 0xffff0000) >> 16); + if (kUserDictLMTBitWidth < 16) { + uint64 mask = ~(1 << kUserDictLMTBitWidth); + lmt &= mask; + } + lmt = lmt * kUserDictLMTGranularity + kUserDictLMTSince; + return lmt; + } + inline int UserDict::build_score(uint64 lmt, int freq) { + lmt = (lmt - kUserDictLMTSince) / kUserDictLMTGranularity; + lmt = (lmt << (64 - kUserDictLMTBitWidth)); + lmt = (lmt >> (64 - kUserDictLMTBitWidth)); + uint16 lmt16 = (uint16) lmt; + int s = freq; + s &= 0x0000ffff; + s = (lmt16 << 16) | s; + return s; + } + inline int64 UserDict::utf16le_atoll(uint16 *s, int len) { + int64 ret = 0; + if (len <= 0) + return ret; + int flag = 1; + const uint16 *endp = s + len; + if (*s == '-') { + flag = -1; + s++; + } else if (*s == '+') { + s++; + } + while (*s >= '0' && *s <= '9' && s < endp) { + ret += ret * 10 + (*s) - '0'; + s++; + } + return ret * flag; + } + inline int UserDict::utf16le_lltoa(int64 v, uint16 *s, int size) { + if (!s || size <= 0) + return 0; + uint16 *endp = s + size; + int ret_len = 0; + if (v < 0) { + *(s++) = '-'; + ++ret_len; + v *= -1; + } + uint16 *b = s; + while (s < endp && v != 0) { + *(s++) = '0' + (v % 10); + v = v / 10; + ++ret_len; + } + if (v != 0) + return 0; + --s; + while (b < s) { + *b = *s; + ++b, --s; + } + return ret_len; + } + inline void UserDict::set_lemma_flag(uint32 offset, uint8 flag) { + offset &= kUserDictOffsetMask; + lemmas_[offset] |= flag; + } + inline char UserDict::get_lemma_flag(uint32 offset) { + offset &= kUserDictOffsetMask; + return (char) (lemmas_[offset]); + } + inline char UserDict::get_lemma_nchar(uint32 offset) { + offset &= kUserDictOffsetMask; + return (char) (lemmas_[offset + 1]); + } + inline uint16 *UserDict::get_lemma_spell_ids(uint32 offset) { + offset &= kUserDictOffsetMask; + return (uint16 *) (lemmas_ + offset + 2); + } + inline uint16 *UserDict::get_lemma_word(uint32 offset) { + offset &= kUserDictOffsetMask; + uint8 nchar = get_lemma_nchar(offset); + return (uint16 *) (lemmas_ + offset + 2 + (nchar << 1)); + } + inline LemmaIdType UserDict::get_max_lemma_id() { + // When a lemma is deleted, we don't not claim its id back for + // simplicity and performance + return start_id_ + dict_info_.lemma_count - 1; + } + inline bool UserDict::is_valid_lemma_id(LemmaIdType id) { + if (id >= start_id_ && id <= get_max_lemma_id()) + return true; + return false; + } + inline bool UserDict::is_valid_state() { + if (state_ == USER_DICT_NONE) + return false; + return true; + } + UserDict::UserDict() + : start_id_(0), + version_(0), + lemmas_(NULL), + offsets_(NULL), + scores_(NULL), + ids_(NULL), +#ifdef ___PREDICT_ENABLED___ + predicts_(NULL), +#endif +#ifdef ___SYNC_ENABLED___ + syncs_(NULL), + sync_count_size_(0), +#endif + offsets_by_id_(NULL), + lemma_count_left_(0), + lemma_size_left_(0), + dict_file_(NULL), + state_(USER_DICT_NONE) { + memset(&dict_info_, 0, sizeof(dict_info_)); + memset(&load_time_, 0, sizeof(load_time_)); +#ifdef ___CACHE_ENABLED___ + cache_init(); +#endif + } + UserDict::~UserDict() { + close_dict(); + } + bool UserDict::load_dict(const char *file_name, LemmaIdType start_id, + LemmaIdType end_id) { +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_BEGIN; +#endif + dict_file_ = strdup(file_name); + if (!dict_file_) + return false; + start_id_ = start_id; + if (false == validate(file_name) && false == reset(file_name)) { + goto error; + } + if (false == load(file_name, start_id)) { + goto error; + } + state_ = USER_DICT_SYNC; + gettimeofday(&load_time_, NULL); +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_END; + LOGD_PERF("load_dict"); +#endif + return true; + error: + free((void *) dict_file_); + dict_file_ = NULL; + start_id_ = 0; + return false; + } + bool UserDict::close_dict() { + if (state_ == USER_DICT_NONE) + return true; + if (state_ == USER_DICT_SYNC) + goto out; + + // If dictionary is written back by others, + // we can not simply write back here + // To do a safe flush, we have to discard all newly added + // lemmas and try to reload dict file. + pthread_mutex_lock(&g_mutex_); + if (load_time_.tv_sec > g_last_update_.tv_sec || + (load_time_.tv_sec == g_last_update_.tv_sec && + load_time_.tv_usec > g_last_update_.tv_usec)) { + write_back(); + gettimeofday(&g_last_update_, NULL); + } + pthread_mutex_unlock(&g_mutex_); + out: + free((void *) dict_file_); + free(lemmas_); + free(offsets_); + free(offsets_by_id_); + free(scores_); + free(ids_); +#ifdef ___PREDICT_ENABLED___ + free(predicts_); +#endif + version_ = 0; + dict_file_ = NULL; + lemmas_ = NULL; +#ifdef ___SYNC_ENABLED___ + syncs_ = NULL; + sync_count_size_ = 0; +#endif + offsets_ = NULL; + offsets_by_id_ = NULL; + scores_ = NULL; + ids_ = NULL; +#ifdef ___PREDICT_ENABLED___ + predicts_ = NULL; +#endif + memset(&dict_info_, 0, sizeof(dict_info_)); + lemma_count_left_ = 0; + lemma_size_left_ = 0; + state_ = USER_DICT_NONE; + return true; + } + size_t UserDict::number_of_lemmas() { + return dict_info_.lemma_count; + } + void UserDict::reset_milestones(uint16 from_step, MileStoneHandle from_handle) { + return; + } + MileStoneHandle UserDict::extend_dict(MileStoneHandle from_handle, + const DictExtPara *dep, + LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num) { + if (is_valid_state() == false) + return 0; + bool need_extend = false; +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_BEGIN; +#endif + *lpi_num = _get_lpis(dep->splids, dep->splids_extended + 1, + lpi_items, lpi_max, &need_extend); +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_END; + LOGD_PERF("extend_dict"); +#endif + return ((*lpi_num > 0 || need_extend) ? 1 : 0); + } + int UserDict::is_fuzzy_prefix_spell_id( + const uint16 *id1, uint16 len1, const UserDictSearchable *searchable) { + if (len1 < searchable->splids_len) + return 0; + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + uint32 i = 0; + for (i = 0; i < searchable->splids_len; i++) { + const char py1 = *spl_trie.get_spelling_str(id1[i]); + uint16 off = 8 * (i % 4); + const char py2 = ((searchable->signature[i / 4] & (0xff << off)) >> off); + if (py1 == py2) + continue; + return 0; + } + return 1; + } + int UserDict::fuzzy_compare_spell_id( + const uint16 *id1, uint16 len1, const UserDictSearchable *searchable) { + if (len1 < searchable->splids_len) + return -1; + if (len1 > searchable->splids_len) + return 1; + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + uint32 i = 0; + for (i = 0; i < len1; i++) { + const char py1 = *spl_trie.get_spelling_str(id1[i]); + uint16 off = 8 * (i % 4); + const char py2 = ((searchable->signature[i / 4] & (0xff << off)) >> off); + if (py1 == py2) + continue; + if (py1 > py2) + return 1; + return -1; + } + return 0; + } + bool UserDict::is_prefix_spell_id( + const uint16 *fullids, uint16 fulllen, + const UserDictSearchable *searchable) { + if (fulllen < searchable->splids_len) + return false; + uint32 i = 0; + for (; i < searchable->splids_len; i++) { + uint16 start_id = searchable->splid_start[i]; + uint16 count = searchable->splid_count[i]; + if (fullids[i] >= start_id && fullids[i] < start_id + count) + continue; + else + return false; + } + return true; + } + bool UserDict::equal_spell_id( + const uint16 *fullids, uint16 fulllen, + const UserDictSearchable *searchable) { + if (fulllen != searchable->splids_len) + return false; + uint32 i = 0; + for (; i < fulllen; i++) { + uint16 start_id = searchable->splid_start[i]; + uint16 count = searchable->splid_count[i]; + if (fullids[i] >= start_id && fullids[i] < start_id + count) + continue; + else + return false; + } + return true; + } + int32 UserDict::locate_first_in_offsets(const UserDictSearchable *searchable) { + int32 begin = 0; + int32 end = dict_info_.lemma_count - 1; + int32 middle = -1; + int32 first_prefix = middle; + int32 last_matched = middle; + while (begin <= end) { + middle = (begin + end) >> 1; + uint32 offset = offsets_[middle]; + uint8 nchar = get_lemma_nchar(offset); + const uint16 *splids = get_lemma_spell_ids(offset); + int cmp = fuzzy_compare_spell_id(splids, nchar, searchable); + int pre = is_fuzzy_prefix_spell_id(splids, nchar, searchable); + if (pre) + first_prefix = middle; + if (cmp < 0) { + begin = middle + 1; + } else if (cmp > 0) { + end = middle - 1; + } else { + end = middle - 1; + last_matched = middle; + } + } + return first_prefix; + } + void UserDict::prepare_locate(UserDictSearchable *searchable, + const uint16 *splid_str, + uint16 splid_str_len) { + searchable->splids_len = splid_str_len; + memset(searchable->signature, 0, sizeof(searchable->signature)); + SpellingTrie &spl_trie = SpellingTrie::get_instance(); + uint32 i = 0; + for (; i < splid_str_len; i++) { + if (spl_trie.is_half_id(splid_str[i])) { + searchable->splid_count[i] = + spl_trie.half_to_full(splid_str[i], + &(searchable->splid_start[i])); + } else { + searchable->splid_count[i] = 1; + searchable->splid_start[i] = splid_str[i]; + } + const unsigned char py = *spl_trie.get_spelling_str(splid_str[i]); + searchable->signature[i >> 2] |= (py << (8 * (i % 4))); + } + } + size_t UserDict::get_lpis(const uint16 *splid_str, uint16 splid_str_len, + LmaPsbItem *lpi_items, size_t lpi_max) { + return _get_lpis(splid_str, splid_str_len, lpi_items, lpi_max, NULL); + } + size_t UserDict::_get_lpis(const uint16 *splid_str, + uint16 splid_str_len, LmaPsbItem *lpi_items, + size_t lpi_max, bool *need_extend) { + bool tmp_extend; + if (!need_extend) + need_extend = &tmp_extend; + *need_extend = false; + if (is_valid_state() == false) + return 0; + if (lpi_max <= 0) + return 0; + if (0 == pthread_mutex_trylock(&g_mutex_)) { + if (load_time_.tv_sec < g_last_update_.tv_sec || + (load_time_.tv_sec == g_last_update_.tv_sec && + load_time_.tv_usec < g_last_update_.tv_usec)) { + // Others updated disk file, have to reload + pthread_mutex_unlock(&g_mutex_); + flush_cache(); + } else { + pthread_mutex_unlock(&g_mutex_); + } + } else { + } + UserDictSearchable searchable; + prepare_locate(&searchable, splid_str, splid_str_len); + uint32 max_off = dict_info_.lemma_count; +#ifdef ___CACHE_ENABLED___ + int32 middle; + uint32 start, count; + bool cached = cache_hit(&searchable, &start, &count); + if (cached) { + middle = start; + max_off = start + count; + } else { + middle = locate_first_in_offsets(&searchable); + start = middle; + } +#else + int32 middle = locate_first_in_offsets(&searchable); +#endif + if (middle == -1) { +#ifdef ___CACHE_ENABLED___ + if (!cached) + cache_push(USER_DICT_MISS_CACHE, &searchable, 0, 0); +#endif + return 0; + } + size_t lpi_current = 0; + bool fuzzy_break = false; + bool prefix_break = false; + while ((size_t) middle < max_off && !fuzzy_break && !prefix_break) { + if (lpi_current >= lpi_max) + break; + uint32 offset = offsets_[middle]; + // Ignore deleted lemmas + if (offset & kUserDictOffsetFlagRemove) { + middle++; + continue; + } + uint8 nchar = get_lemma_nchar(offset); + uint16 *splids = get_lemma_spell_ids(offset); +#ifdef ___CACHE_ENABLED___ + if (!cached && 0 != fuzzy_compare_spell_id(splids, nchar, &searchable)) { +#else + if (0 != fuzzy_compare_spell_id(splids, nchar, &searchable)) { +#endif + fuzzy_break = true; + } + if (prefix_break == false) { + if (is_fuzzy_prefix_spell_id(splids, nchar, &searchable)) { + if (*need_extend == false && + is_prefix_spell_id(splids, nchar, &searchable)) { + *need_extend = true; + } + } else { + prefix_break = true; + } + } + if (equal_spell_id(splids, nchar, &searchable) == true) { + lpi_items[lpi_current].psb = translate_score(scores_[middle]); + lpi_items[lpi_current].id = ids_[middle]; + lpi_items[lpi_current].lma_len = nchar; + lpi_current++; + } + middle++; + } +#ifdef ___CACHE_ENABLED___ + if (!cached) { + count = middle - start; + cache_push(USER_DICT_CACHE, &searchable, start, count); + } +#endif + return lpi_current; + } + uint16 UserDict::get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, + uint16 str_max) { + if (is_valid_state() == false) + return 0; + if (is_valid_lemma_id(id_lemma) == false) + return 0; + uint32 offset = offsets_by_id_[id_lemma - start_id_]; + uint8 nchar = get_lemma_nchar(offset); + char16 *str = get_lemma_word(offset); + uint16 m = nchar < str_max - 1 ? nchar : str_max - 1; + int i = 0; + for (; i < m; i++) { + str_buf[i] = str[i]; + } + str_buf[i] = 0; + return m; + } + uint16 UserDict::get_lemma_splids(LemmaIdType id_lemma, uint16 *splids, + uint16 splids_max, bool arg_valid) { + if (is_valid_lemma_id(id_lemma) == false) + return 0; + uint32 offset = offsets_by_id_[id_lemma - start_id_]; + uint8 nchar = get_lemma_nchar(offset); + const uint16 *ids = get_lemma_spell_ids(offset); + int i = 0; + for (; i < nchar && i < splids_max; i++) + splids[i] = ids[i]; + return i; + } + size_t UserDict::predict(const char16 last_hzs[], uint16 hzs_len, + NPredictItem *npre_items, size_t npre_max, + size_t b4_used) { + uint32 new_added = 0; +#ifdef ___PREDICT_ENABLED___ + int32 end = dict_info_.lemma_count - 1; + int j = locate_first_in_predicts((const uint16 *) last_hzs, hzs_len); + if (j == -1) + return 0; + while (j <= end) { + uint32 offset = predicts_[j]; + // Ignore deleted lemmas + if (offset & kUserDictOffsetFlagRemove) { + j++; + continue; + } + uint32 nchar = get_lemma_nchar(offset); + uint16 *words = get_lemma_word(offset); + uint16 *splids = get_lemma_spell_ids(offset); + if (nchar <= hzs_len) { + j++; + continue; + } + if (memcmp(words, last_hzs, hzs_len << 1) == 0) { + if (new_added >= npre_max) { + return new_added; + } + uint32 cpy_len = + (nchar < kMaxPredictSize ? (nchar << 1) : (kMaxPredictSize << 1)) + - (hzs_len << 1); + npre_items[new_added].his_len = hzs_len; + npre_items[new_added].psb = get_lemma_score(words, splids, nchar); + memcpy(npre_items[new_added].pre_hzs, words + hzs_len, cpy_len); + if ((cpy_len >> 1) < kMaxPredictSize) { + npre_items[new_added].pre_hzs[cpy_len >> 1] = 0; + } + new_added++; + } else { + break; + } + j++; + } +#endif + return new_added; + } + int32 UserDict::locate_in_offsets(char16 lemma_str[], uint16 splid_str[], + uint16 lemma_len) { + int32 max_off = dict_info_.lemma_count; + UserDictSearchable searchable; + prepare_locate(&searchable, splid_str, lemma_len); +#ifdef ___CACHE_ENABLED___ + int32 off; + uint32 start, count; + bool cached = load_cache(&searchable, &start, &count); + if (cached) { + off = start; + max_off = start + count; + } else { + off = locate_first_in_offsets(&searchable); + start = off; + } +#else + int32 off = locate_first_in_offsets(&searchable); +#endif + if (off == -1) { + return off; + } + while (off < max_off) { + uint32 offset = offsets_[off]; + if (offset & kUserDictOffsetFlagRemove) { + off++; + continue; + } + uint16 *splids = get_lemma_spell_ids(offset); +#ifdef ___CACHE_ENABLED___ + if (!cached && 0 != fuzzy_compare_spell_id(splids, lemma_len, &searchable)) + break; +#else + if (0 != fuzzy_compare_spell_id(splids, lemma_len, &searchable)) + break; +#endif + if (equal_spell_id(splids, lemma_len, &searchable) == true) { + uint16 *str = get_lemma_word(offset); + uint32 i = 0; + for (i = 0; i < lemma_len; i++) { + if (str[i] == lemma_str[i]) + continue; + break; + } + if (i < lemma_len) { + off++; + continue; + } +#ifdef ___CACHE_ENABLED___ + // No need to save_cache here, since current function is invoked by + // put_lemma. It's rarely possible for a user input same lemma twice. + // That means first time user type a new lemma, it is newly added into + // user dictionary, then it's possible that user type the same lemma + // again. + // Another reason save_cache can not be invoked here is this function + // aborts when lemma is found, and it never knows the count. +#endif + return off; + } + off++; + } + return -1; + } +#ifdef ___PREDICT_ENABLED___ + uint32 UserDict::locate_where_to_insert_in_predicts( + const uint16 *words, int lemma_len) { + int32 begin = 0; + int32 end = dict_info_.lemma_count - 1; + int32 middle = end; + uint32 last_matched = middle; + while (begin <= end) { + middle = (begin + end) >> 1; + uint32 offset = offsets_[middle]; + uint8 nchar = get_lemma_nchar(offset); + const uint16 *ws = get_lemma_word(offset); + uint32 minl = nchar < lemma_len ? nchar : lemma_len; + uint32 k = 0; + int cmp = 0; + for (; k < minl; k++) { + if (ws[k] < words[k]) { + cmp = -1; + break; + } else if (ws[k] > words[k]) { + cmp = 1; + break; + } + } + if (cmp == 0) { + if (nchar < lemma_len) + cmp = -1; + else if (nchar > lemma_len) + cmp = 1; + } + if (cmp < 0) { + begin = middle + 1; + last_matched = middle; + } else if (cmp > 0) { + end = middle - 1; + } else { + end = middle - 1; + last_matched = middle; + } + } + return last_matched; + } + int32 UserDict::locate_first_in_predicts(const uint16 *words, int lemma_len) { + int32 begin = 0; + int32 end = dict_info_.lemma_count - 1; + int32 middle = -1; + int32 last_matched = middle; + while (begin <= end) { + middle = (begin + end) >> 1; + uint32 offset = offsets_[middle]; + uint8 nchar = get_lemma_nchar(offset); + const uint16 *ws = get_lemma_word(offset); + uint32 minl = nchar < lemma_len ? nchar : lemma_len; + uint32 k = 0; + int cmp = 0; + for (; k < minl; k++) { + if (ws[k] < words[k]) { + cmp = -1; + break; + } else if (ws[k] > words[k]) { + cmp = 1; + break; + } + } + if (cmp == 0) { + if (nchar >= lemma_len) + last_matched = middle; + if (nchar < lemma_len) + cmp = -1; + else if (nchar > lemma_len) + cmp = 1; + } + if (cmp < 0) { + begin = middle + 1; + } else if (cmp > 0) { + end = middle - 1; + } else { + end = middle - 1; + } + } + return last_matched; + } +#endif + LemmaIdType UserDict::get_lemma_id(char16 lemma_str[], uint16 splids[], + uint16 lemma_len) { + int32 off = locate_in_offsets(lemma_str, splids, lemma_len); + if (off == -1) { + return 0; + } + return ids_[off]; + } + LmaScoreType UserDict::get_lemma_score(LemmaIdType lemma_id) { + if (is_valid_state() == false) + return 0; + if (is_valid_lemma_id(lemma_id) == false) + return 0; + return translate_score(_get_lemma_score(lemma_id)); + } + LmaScoreType UserDict::get_lemma_score(char16 lemma_str[], uint16 splids[], + uint16 lemma_len) { + if (is_valid_state() == false) + return 0; + return translate_score(_get_lemma_score(lemma_str, splids, lemma_len)); + } + int UserDict::_get_lemma_score(LemmaIdType lemma_id) { + if (is_valid_state() == false) + return 0; + if (is_valid_lemma_id(lemma_id) == false) + return 0; + uint32 offset = offsets_by_id_[lemma_id - start_id_]; + uint32 nchar = get_lemma_nchar(offset); + uint16 *spl = get_lemma_spell_ids(offset); + uint16 *wrd = get_lemma_word(offset); + int32 off = locate_in_offsets(wrd, spl, nchar); + if (off == -1) { + return 0; + } + return scores_[off]; + } + int UserDict::_get_lemma_score(char16 lemma_str[], uint16 splids[], + uint16 lemma_len) { + if (is_valid_state() == false) + return 0; + int32 off = locate_in_offsets(lemma_str, splids, lemma_len); + if (off == -1) { + return 0; + } + return scores_[off]; + } +#ifdef ___SYNC_ENABLED___ + void UserDict::remove_lemma_from_sync_list(uint32 offset) { + offset &= kUserDictOffsetMask; + uint32 i = 0; + for (; i < dict_info_.sync_count; i++) { + unsigned int off = (syncs_[i] & kUserDictOffsetMask); + if (off == offset) + break; + } + if (i < dict_info_.sync_count) { + syncs_[i] = syncs_[dict_info_.sync_count - 1]; + dict_info_.sync_count--; + } + } +#endif +#ifdef ___PREDICT_ENABLED___ + void UserDict::remove_lemma_from_predict_list(uint32 offset) { + offset &= kUserDictOffsetMask; + uint32 i = 0; + for (; i < dict_info_.lemma_count; i++) { + unsigned int off = (predicts_[i] & kUserDictOffsetMask); + if (off == offset) { + predicts_[i] |= kUserDictOffsetFlagRemove; + break; + } + } + } +#endif + bool UserDict::remove_lemma_by_offset_index(int offset_index) { + if (is_valid_state() == false) + return 0; + int32 off = offset_index; + if (off == -1) { + return false; + } + uint32 offset = offsets_[off]; + uint32 nchar = get_lemma_nchar(offset); + offsets_[off] |= kUserDictOffsetFlagRemove; +#ifdef ___SYNC_ENABLED___ + // Remove corresponding sync item + remove_lemma_from_sync_list(offset); +#endif +#ifdef ___PREDICT_ENABLED___ + remove_lemma_from_predict_list(offset); +#endif + dict_info_.free_count++; + dict_info_.free_size += (2 + (nchar << 2)); + if (state_ < USER_DICT_OFFSET_DIRTY) + state_ = USER_DICT_OFFSET_DIRTY; + return true; + } + bool UserDict::remove_lemma(LemmaIdType lemma_id) { + if (is_valid_state() == false) + return 0; + if (is_valid_lemma_id(lemma_id) == false) + return false; + uint32 offset = offsets_by_id_[lemma_id - start_id_]; + uint32 nchar = get_lemma_nchar(offset); + uint16 *spl = get_lemma_spell_ids(offset); + uint16 *wrd = get_lemma_word(offset); + int32 off = locate_in_offsets(wrd, spl, nchar); + return remove_lemma_by_offset_index(off); + } + void UserDict::flush_cache() { + LemmaIdType start_id = start_id_; + if (!dict_file_) + return; + const char *file = strdup(dict_file_); + if (!file) + return; + close_dict(); + load_dict(file, start_id, kUserDictIdEnd); + free((void *) file); +#ifdef ___CACHE_ENABLED___ + cache_init(); +#endif + return; + } + bool UserDict::reset(const char *file) { + FILE *fp = fopen(file, "w+"); + if (!fp) { + return false; + } + uint32 version = kUserDictVersion; + size_t wred = fwrite(&version, 1, 4, fp); + UserDictInfo info; + memset(&info, 0, sizeof(info)); + // By default, no limitation for lemma count and size + // thereby, reclaim_ratio is never used + wred += fwrite(&info, 1, sizeof(info), fp); + if (wred != sizeof(info) + sizeof(version)) { + fclose(fp); + unlink(file); + return false; + } + fclose(fp); + return true; + } + bool UserDict::validate(const char *file) { + // b is ignored in POSIX compatible os including Linux + // while b is important flag for Windows to specify binary mode + FILE *fp = fopen(file, "rb"); + if (!fp) { + return false; + } + size_t size; + size_t readed; + uint32 version; + UserDictInfo dict_info; + + // validate + int err = fseek(fp, 0, SEEK_END); + if (err) { + goto error; + } + size = ftell(fp); + if (size < 4 + sizeof(dict_info)) { + goto error; + } + err = fseek(fp, 0, SEEK_SET); + if (err) { + goto error; + } + readed = fread(&version, 1, sizeof(version), fp); + if (readed < sizeof(version)) { + goto error; + } + if (version != kUserDictVersion) { + goto error; + } + err = fseek(fp, -1 * sizeof(dict_info), SEEK_END); + if (err) { + goto error; + } + readed = fread(&dict_info, 1, sizeof(dict_info), fp); + if (readed != sizeof(dict_info)) { + goto error; + } + if (size != get_dict_file_size(&dict_info)) { + goto error; + } + fclose(fp); + return true; + error: + fclose(fp); + return false; + } + bool UserDict::load(const char *file, LemmaIdType start_id) { + if (0 != pthread_mutex_trylock(&g_mutex_)) { + return false; + } + // b is ignored in POSIX compatible os including Linux + // while b is important flag for Windows to specify binary mode + FILE *fp = fopen(file, "rb"); + if (!fp) { + pthread_mutex_unlock(&g_mutex_); + return false; + } + size_t readed, toread; + UserDictInfo dict_info; + uint8 *lemmas = NULL; + uint32 *offsets = NULL; +#ifdef ___SYNC_ENABLED___ + uint32 *syncs = NULL; +#endif + uint32 *scores = NULL; + uint32 *ids = NULL; + uint32 *offsets_by_id = NULL; +#ifdef ___PREDICT_ENABLED___ + uint32 *predicts = NULL; +#endif + size_t i; + int err; + err = fseek(fp, -1 * sizeof(dict_info), SEEK_END); + if (err) goto error; + readed = fread(&dict_info, 1, sizeof(dict_info), fp); + if (readed != sizeof(dict_info)) goto error; + lemmas = (uint8 *) malloc( + dict_info.lemma_size + + (kUserDictPreAlloc * (2 + (kUserDictAverageNchar << 2)))); + if (!lemmas) goto error; + offsets = (uint32 *) malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2); + if (!offsets) goto error; +#ifdef ___PREDICT_ENABLED___ + predicts = (uint32 *) malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2); + if (!predicts) goto error; +#endif +#ifdef ___SYNC_ENABLED___ + syncs = (uint32 *) malloc((dict_info.sync_count + kUserDictPreAlloc) << 2); + if (!syncs) goto error; +#endif + scores = (uint32 *) malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2); + if (!scores) goto error; + ids = (uint32 *) malloc((dict_info.lemma_count + kUserDictPreAlloc) << 2); + if (!ids) goto error; + offsets_by_id = (uint32 *) malloc( + (dict_info.lemma_count + kUserDictPreAlloc) << 2); + if (!offsets_by_id) goto error; + err = fseek(fp, 4, SEEK_SET); + if (err) goto error; + readed = 0; + while (readed < dict_info.lemma_size && !ferror(fp) && !feof(fp)) { + readed += fread(lemmas + readed, 1, dict_info.lemma_size - readed, fp); + } + if (readed < dict_info.lemma_size) + goto error; + toread = (dict_info.lemma_count << 2); + readed = 0; + while (readed < toread && !ferror(fp) && !feof(fp)) { + readed += fread((((uint8 *) offsets) + readed), 1, toread - readed, fp); + } + if (readed < toread) + goto error; +#ifdef ___PREDICT_ENABLED___ + toread = (dict_info.lemma_count << 2); + readed = 0; + while (readed < toread && !ferror(fp) && !feof(fp)) { + readed += fread((((uint8 *) predicts) + readed), 1, toread - readed, fp); + } + if (readed < toread) + goto error; +#endif + readed = 0; + while (readed < toread && !ferror(fp) && !feof(fp)) { + readed += fread((((uint8 *) scores) + readed), 1, toread - readed, fp); + } + if (readed < toread) + goto error; +#ifdef ___SYNC_ENABLED___ + toread = (dict_info.sync_count << 2); + readed = 0; + while (readed < toread && !ferror(fp) && !feof(fp)) { + readed += fread((((uint8 *) syncs) + readed), 1, toread - readed, fp); + } + if (readed < toread) + goto error; +#endif + for (i = 0; i < dict_info.lemma_count; i++) { + ids[i] = start_id + i; + offsets_by_id[i] = offsets[i]; + } + lemmas_ = lemmas; + offsets_ = offsets; +#ifdef ___SYNC_ENABLED___ + syncs_ = syncs; + sync_count_size_ = dict_info.sync_count + kUserDictPreAlloc; +#endif + offsets_by_id_ = offsets_by_id; + scores_ = scores; + ids_ = ids; +#ifdef ___PREDICT_ENABLED___ + predicts_ = predicts; +#endif + lemma_count_left_ = kUserDictPreAlloc; + lemma_size_left_ = kUserDictPreAlloc * (2 + (kUserDictAverageNchar << 2)); + memcpy(&dict_info_, &dict_info, sizeof(dict_info)); + state_ = USER_DICT_SYNC; + fclose(fp); + pthread_mutex_unlock(&g_mutex_); + return true; + error: + if (lemmas) free(lemmas); + if (offsets) free(offsets); +#ifdef ___SYNC_ENABLED___ + if (syncs) free(syncs); +#endif + if (scores) free(scores); + if (ids) free(ids); + if (offsets_by_id) free(offsets_by_id); +#ifdef ___PREDICT_ENABLED___ + if (predicts) free(predicts); +#endif + fclose(fp); + pthread_mutex_unlock(&g_mutex_); + return false; + } + void UserDict::write_back() { + // XXX write back is only allowed from close_dict due to thread-safe sake + if (state_ == USER_DICT_NONE || state_ == USER_DICT_SYNC) + return; + int fd = open(dict_file_, O_WRONLY); + if (fd == -1) + return; + switch (state_) { + case USER_DICT_DEFRAGMENTED: + write_back_all(fd); + break; + case USER_DICT_LEMMA_DIRTY: + write_back_lemma(fd); + break; + case USER_DICT_OFFSET_DIRTY: + write_back_offset(fd); + break; + case USER_DICT_SCORE_DIRTY: + write_back_score(fd); + break; +#ifdef ___SYNC_ENABLED___ + case USER_DICT_SYNC_DIRTY: + write_back_sync(fd); + break; +#endif + default: + break; + } + // It seems truncate is not need on Linux, Windows except Mac + // I am doing it here anyway for safety. + off_t cur = lseek(fd, 0, SEEK_CUR); +#ifndef _WIN32 + ftruncate(fd, cur); +#endif + close(fd); + state_ = USER_DICT_SYNC; + } +#ifdef ___SYNC_ENABLED___ + void UserDict::write_back_sync(int fd) { + int err = lseek(fd, 4 + dict_info_.lemma_size + + (dict_info_.lemma_count << 3) + #ifdef ___PREDICT_ENABLED___ + + (dict_info_.lemma_count << 2) +#endif + , SEEK_SET); + if (err == -1) + return; + write(fd, syncs_, dict_info_.sync_count << 2); + write(fd, &dict_info_, sizeof(dict_info_)); + } +#endif + void UserDict::write_back_offset(int fd) { + int err = lseek(fd, 4 + dict_info_.lemma_size, SEEK_SET); + if (err == -1) + return; + write(fd, offsets_, dict_info_.lemma_count << 2); +#ifdef ___PREDICT_ENABLED___ + write(fd, predicts_, dict_info_.lemma_count << 2); +#endif + write(fd, scores_, dict_info_.lemma_count << 2); +#ifdef ___SYNC_ENABLED___ + write(fd, syncs_, dict_info_.sync_count << 2); +#endif + write(fd, &dict_info_, sizeof(dict_info_)); + } + void UserDict::write_back_score(int fd) { + int err = lseek(fd, 4 + dict_info_.lemma_size + + (dict_info_.lemma_count << 2) + #ifdef ___PREDICT_ENABLED___ + + (dict_info_.lemma_count << 2) +#endif + , SEEK_SET); + if (err == -1) + return; + write(fd, scores_, dict_info_.lemma_count << 2); +#ifdef ___SYNC_ENABLED___ + write(fd, syncs_, dict_info_.sync_count << 2); +#endif + write(fd, &dict_info_, sizeof(dict_info_)); + } + void UserDict::write_back_lemma(int fd) { + int err = lseek(fd, 4, SEEK_SET); + if (err == -1) + return; + // New lemmas are always appended, no need to write whole lemma block + size_t need_write = kUserDictPreAlloc * + (2 + (kUserDictAverageNchar << 2)) - lemma_size_left_; + err = lseek(fd, dict_info_.lemma_size - need_write, SEEK_CUR); + if (err == -1) + return; + write(fd, lemmas_ + dict_info_.lemma_size - need_write, need_write); + write(fd, offsets_, dict_info_.lemma_count << 2); +#ifdef ___PREDICT_ENABLED___ + write(fd, predicts_, dict_info_.lemma_count << 2); +#endif + write(fd, scores_, dict_info_.lemma_count << 2); +#ifdef ___SYNC_ENABLED___ + write(fd, syncs_, dict_info_.sync_count << 2); +#endif + write(fd, &dict_info_, sizeof(dict_info_)); + } + void UserDict::write_back_all(int fd) { + // XXX lemma_size is handled differently in writeall + // and writelemma. I update lemma_size and lemma_count in different + // places for these two cases. Should fix it to make it consistent. + int err = lseek(fd, 4, SEEK_SET); + if (err == -1) + return; + write(fd, lemmas_, dict_info_.lemma_size); + write(fd, offsets_, dict_info_.lemma_count << 2); +#ifdef ___PREDICT_ENABLED___ + write(fd, predicts_, dict_info_.lemma_count << 2); +#endif + write(fd, scores_, dict_info_.lemma_count << 2); +#ifdef ___SYNC_ENABLED___ + write(fd, syncs_, dict_info_.sync_count << 2); +#endif + write(fd, &dict_info_, sizeof(dict_info_)); + } +#ifdef ___CACHE_ENABLED___ + bool UserDict::load_cache(UserDictSearchable *searchable, + uint32 *offset, uint32 *length) { + UserDictCache *cache = &caches_[searchable->splids_len - 1]; + if (cache->head == cache->tail) + return false; + uint16 j, sig_len = kMaxLemmaSize / 4; + uint16 i = cache->head; + while (1) { + j = 0; + for (; j < sig_len; j++) { + if (cache->signatures[i][j] != searchable->signature[j]) + break; + } + if (j < sig_len) { + i++; + if (i >= kUserDictCacheSize) + i -= kUserDictCacheSize; + if (i == cache->tail) + break; + continue; + } + *offset = cache->offsets[i]; + *length = cache->lengths[i]; + return true; + } + return false; + } + void UserDict::save_cache(UserDictSearchable *searchable, + uint32 offset, uint32 length) { + UserDictCache *cache = &caches_[searchable->splids_len - 1]; + uint16 next = cache->tail; + cache->offsets[next] = offset; + cache->lengths[next] = length; + uint16 sig_len = kMaxLemmaSize / 4; + uint16 j = 0; + for (; j < sig_len; j++) { + cache->signatures[next][j] = searchable->signature[j]; + } + if (++next >= kUserDictCacheSize) { + next -= kUserDictCacheSize; + } + if (next == cache->head) { + cache->head++; + if (cache->head >= kUserDictCacheSize) { + cache->head -= kUserDictCacheSize; + } + } + cache->tail = next; + } + void UserDict::reset_cache() { + memset(caches_, 0, sizeof(caches_)); + } + bool UserDict::load_miss_cache(UserDictSearchable *searchable) { + UserDictMissCache *cache = &miss_caches_[searchable->splids_len - 1]; + if (cache->head == cache->tail) + return false; + uint16 j, sig_len = kMaxLemmaSize / 4; + uint16 i = cache->head; + while (1) { + j = 0; + for (; j < sig_len; j++) { + if (cache->signatures[i][j] != searchable->signature[j]) + break; + } + if (j < sig_len) { + i++; + if (i >= kUserDictMissCacheSize) + i -= kUserDictMissCacheSize; + if (i == cache->tail) + break; + continue; + } + return true; + } + return false; + } + void UserDict::save_miss_cache(UserDictSearchable *searchable) { + UserDictMissCache *cache = &miss_caches_[searchable->splids_len - 1]; + uint16 next = cache->tail; + uint16 sig_len = kMaxLemmaSize / 4; + uint16 j = 0; + for (; j < sig_len; j++) { + cache->signatures[next][j] = searchable->signature[j]; + } + if (++next >= kUserDictMissCacheSize) { + next -= kUserDictMissCacheSize; + } + if (next == cache->head) { + cache->head++; + if (cache->head >= kUserDictMissCacheSize) { + cache->head -= kUserDictMissCacheSize; + } + } + cache->tail = next; + } + void UserDict::reset_miss_cache() { + memset(miss_caches_, 0, sizeof(miss_caches_)); + } + void UserDict::cache_init() { + reset_cache(); + reset_miss_cache(); + } + bool UserDict::cache_hit(UserDictSearchable *searchable, + uint32 *offset, uint32 *length) { + bool hit = load_miss_cache(searchable); + if (hit) { + *offset = 0; + *length = 0; + return true; + } + hit = load_cache(searchable, offset, length); + if (hit) { + return true; + } + return false; + } + void UserDict::cache_push(UserDictCacheType type, + UserDictSearchable *searchable, + uint32 offset, uint32 length) { + switch (type) { + case USER_DICT_MISS_CACHE: + save_miss_cache(searchable); + break; + case USER_DICT_CACHE: + save_cache(searchable, offset, length); + break; + default: + break; + } + } +#endif + void UserDict::defragment(void) { +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_BEGIN; +#endif + if (is_valid_state() == false) + return; + // Fixup offsets_, set REMOVE flag to lemma's flag if needed + size_t first_freed = 0; + size_t first_inuse = 0; + while (first_freed < dict_info_.lemma_count) { + // Find first freed offset + while ((offsets_[first_freed] & kUserDictOffsetFlagRemove) == 0 && + first_freed < dict_info_.lemma_count) { + first_freed++; + } + if (first_freed < dict_info_.lemma_count) { + // Save REMOVE flag to lemma flag + int off = offsets_[first_freed]; + set_lemma_flag(off, kUserDictLemmaFlagRemove); + } else { + break; + } + // Find first inuse offse after first_freed + first_inuse = first_freed + 1; + while ((offsets_[first_inuse] & kUserDictOffsetFlagRemove) && + (first_inuse < dict_info_.lemma_count)) { + // Save REMOVE flag to lemma flag + int off = offsets_[first_inuse]; + set_lemma_flag(off, kUserDictLemmaFlagRemove); + first_inuse++; + } + if (first_inuse >= dict_info_.lemma_count) { + break; + } + // Swap offsets_ + int tmp = offsets_[first_inuse]; + offsets_[first_inuse] = offsets_[first_freed]; + offsets_[first_freed] = tmp; + // Move scores_, no need to swap + tmp = scores_[first_inuse]; + scores_[first_inuse] = scores_[first_freed]; + scores_[first_freed] = tmp; + // Swap ids_ + LemmaIdType tmpid = ids_[first_inuse]; + ids_[first_inuse] = ids_[first_freed]; + ids_[first_freed] = tmpid; + // Go on + first_freed++; + } +#ifdef ___PREDICT_ENABLED___ + // Fixup predicts_ + first_freed = 0; + first_inuse = 0; + while (first_freed < dict_info_.lemma_count) { + // Find first freed offset + while ((predicts_[first_freed] & kUserDictOffsetFlagRemove) == 0 && + first_freed < dict_info_.lemma_count) { + first_freed++; + } + if (first_freed >= dict_info_.lemma_count) + break; + // Find first inuse offse after first_freed + first_inuse = first_freed + 1; + while ((predicts_[first_inuse] & kUserDictOffsetFlagRemove) + && (first_inuse < dict_info_.lemma_count)) { + first_inuse++; + } + if (first_inuse >= dict_info_.lemma_count) { + break; + } + // Swap offsets_ + int tmp = predicts_[first_inuse]; + predicts_[first_inuse] = predicts_[first_freed]; + predicts_[first_freed] = tmp; + // Go on + first_freed++; + } +#endif + dict_info_.lemma_count = first_freed; + // Fixup lemmas_ + size_t begin = 0; + size_t end = 0; + size_t dst = 0; + int total_size = dict_info_.lemma_size + lemma_size_left_; + int total_count = dict_info_.lemma_count + lemma_count_left_; + size_t real_size = total_size - lemma_size_left_; + while (dst < real_size) { + unsigned char flag = get_lemma_flag(dst); + unsigned char nchr = get_lemma_nchar(dst); + if ((flag & kUserDictLemmaFlagRemove) == 0) { + dst += nchr * 4 + 2; + continue; + } + break; + } + if (dst >= real_size) + return; + end = dst; + while (end < real_size) { + begin = end + get_lemma_nchar(end) * 4 + 2; + repeat: + // not used any more + if (begin >= real_size) + break; + unsigned char flag = get_lemma_flag(begin); + unsigned char nchr = get_lemma_nchar(begin); + if (flag & kUserDictLemmaFlagRemove) { + begin += nchr * 4 + 2; + goto repeat; + } + end = begin + nchr * 4 + 2; + while (end < real_size) { + unsigned char eflag = get_lemma_flag(end); + unsigned char enchr = get_lemma_nchar(end); + if ((eflag & kUserDictLemmaFlagRemove) == 0) { + end += enchr * 4 + 2; + continue; + } + break; + } + memmove(lemmas_ + dst, lemmas_ + begin, end - begin); + for (size_t j = 0; j < dict_info_.lemma_count; j++) { + if (offsets_[j] >= begin && offsets_[j] < end) { + offsets_[j] -= (begin - dst); + offsets_by_id_[ids_[j] - start_id_] = offsets_[j]; + } +#ifdef ___PREDICT_ENABLED___ + if (predicts_[j] >= begin && predicts_[j] < end) { + predicts_[j] -= (begin - dst); + } +#endif + } +#ifdef ___SYNC_ENABLED___ + for (size_t j = 0; j < dict_info_.sync_count; j++) { + if (syncs_[j] >= begin && syncs_[j] < end) { + syncs_[j] -= (begin - dst); + } + } +#endif + dst += (end - begin); + } + dict_info_.free_count = 0; + dict_info_.free_size = 0; + dict_info_.lemma_size = dst; + lemma_size_left_ = total_size - dict_info_.lemma_size; + lemma_count_left_ = total_count - dict_info_.lemma_count; + + // XXX Without following code, + // offsets_by_id_ is not reordered. + // That's to say, all removed lemmas' ids are not collected back. + // There may not be room for addition of new lemmas due to + // offsests_by_id_ reason, although lemma_size_left_ is fixed. + // By default, we do want defrag as fast as possible, because + // during defrag procedure, other peers can not write new lemmas + // to user dictionary file. + // XXX If write-back is invoked immediately after + // this defragment, no need to fix up following in-mem data. + for (uint32 i = 0; i < dict_info_.lemma_count; i++) { + ids_[i] = start_id_ + i; + offsets_by_id_[i] = offsets_[i]; + } + state_ = USER_DICT_DEFRAGMENTED; +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_END; + LOGD_PERF("defragment"); +#endif + } +#ifdef ___SYNC_ENABLED___ + void UserDict::clear_sync_lemmas(unsigned int start, unsigned int end) { + if (is_valid_state() == false) + return; + if (end > dict_info_.sync_count) + end = dict_info_.sync_count; + memmove(syncs_ + start, syncs_ + end, (dict_info_.sync_count - end) << 2); + dict_info_.sync_count -= (end - start); + if (state_ < USER_DICT_SYNC_DIRTY) + state_ = USER_DICT_SYNC_DIRTY; + } + int UserDict::get_sync_count() { + if (is_valid_state() == false) + return 0; + return dict_info_.sync_count; + } + LemmaIdType UserDict::put_lemma_no_sync(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count, uint64 lmt) { + int again = 0; + begin: + LemmaIdType id; + uint32 *syncs_bak = syncs_; + syncs_ = NULL; + id = _put_lemma(lemma_str, splids, lemma_len, count, lmt); + syncs_ = syncs_bak; + if (id == 0 && again == 0) { + if ((dict_info_.limit_lemma_count > 0 && + dict_info_.lemma_count >= dict_info_.limit_lemma_count) + || (dict_info_.limit_lemma_size > 0 && + dict_info_.lemma_size + (2 + (lemma_len << 2)) + > dict_info_.limit_lemma_size)) { + // XXX Always reclaim and defrag in sync code path + // sync thread is background thread and ok with heavy work + reclaim(); + defragment(); + flush_cache(); + again = 1; + goto begin; + } + } + return id; + } + int UserDict::put_lemmas_no_sync_from_utf16le_string(char16 *lemmas, int len) { + int newly_added = 0; + SpellingParser *spl_parser = new SpellingParser(); + if (!spl_parser) { + return 0; + } +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_BEGIN; +#endif + char16 *ptr = lemmas; + + // Extract pinyin,words,frequence,last_mod_time + char16 *p = ptr, *py16 = ptr; + char16 *hz16 = NULL; + int py16_len = 0; + uint16 splid[kMaxLemmaSize]; + int splid_len = 0; + int hz16_len = 0; + char16 *fr16 = NULL; + int fr16_len = 0; + while (p - ptr < len) { + // Pinyin + py16 = p; + splid_len = 0; + while (*p != 0x2c && (p - ptr) < len) { + if (*p == 0x20) + splid_len++; + p++; + } + splid_len++; + if (p - ptr == len) + break; + py16_len = p - py16; + if (kMaxLemmaSize < splid_len) { + break; + } + bool is_pre; + int splidl = spl_parser->splstr16_to_idxs_f( + py16, py16_len, splid, NULL, kMaxLemmaSize, is_pre); + if (splidl != splid_len) + break; + // Phrase + hz16 = ++p; + while (*p != 0x2c && (p - ptr) < len) { + p++; + } + hz16_len = p - hz16; + if (hz16_len != splid_len) + break; + // Frequency + fr16 = ++p; + fr16_len = 0; + while (*p != 0x2c && (p - ptr) < len) { + p++; + } + fr16_len = p - fr16; + uint32 intf = (uint32) utf16le_atoll(fr16, fr16_len); + // Last modified time + fr16 = ++p; + fr16_len = 0; + while (*p != 0x3b && (p - ptr) < len) { + p++; + } + fr16_len = p - fr16; + uint64 last_mod = utf16le_atoll(fr16, fr16_len); + put_lemma_no_sync(hz16, splid, splid_len, intf, last_mod); + newly_added++; + p++; + } +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_END; + LOGD_PERF("put_lemmas_no_sync_from_utf16le_string"); +#endif + return newly_added; + } + int UserDict::get_sync_lemmas_in_utf16le_string_from_beginning( + char16 *str, int size, int *count) { + int len = 0; + *count = 0; + int left_len = size; + if (is_valid_state() == false) + return len; + SpellingTrie *spl_trie = &SpellingTrie::get_instance(); + if (!spl_trie) { + return 0; + } + uint32 i; + for (i = 0; i < dict_info_.sync_count; i++) { + int offset = syncs_[i]; + uint32 nchar = get_lemma_nchar(offset); + uint16 *spl = get_lemma_spell_ids(offset); + uint16 *wrd = get_lemma_word(offset); + int score = _get_lemma_score(wrd, spl, nchar); + static char score_temp[32], *pscore_temp = score_temp; + static char16 temp[256], *ptemp = temp; + pscore_temp = score_temp; + ptemp = temp; + uint32 j; + // Add pinyin + for (j = 0; j < nchar; j++) { + int ret_len = spl_trie->get_spelling_str16( + spl[j], ptemp, temp + sizeof(temp) - ptemp); + if (ret_len <= 0) + break; + ptemp += ret_len; + if (ptemp < temp + sizeof(temp) - 1) { + *(ptemp++) = ' '; + } else { + j = 0; + break; + } + } + if (j < nchar) { + continue; + } + ptemp--; + if (ptemp < temp + sizeof(temp) - 1) { + *(ptemp++) = ','; + } else { + continue; + } + // Add phrase + for (j = 0; j < nchar; j++) { + if (ptemp < temp + sizeof(temp) - 1) { + *(ptemp++) = wrd[j]; + } else { + break; + } + } + if (j < nchar) { + continue; + } + if (ptemp < temp + sizeof(temp) - 1) { + *(ptemp++) = ','; + } else { + continue; + } + // Add frequency + uint32 intf = extract_score_freq(score); + int ret_len = utf16le_lltoa(intf, ptemp, temp + sizeof(temp) - ptemp); + if (ret_len <= 0) + continue; + ptemp += ret_len; + if (ptemp < temp + sizeof(temp) - 1) { + *(ptemp++) = ','; + } else { + continue; + } + // Add last modified time + uint64 last_mod = extract_score_lmt(score); + ret_len = utf16le_lltoa(last_mod, ptemp, temp + sizeof(temp) - ptemp); + if (ret_len <= 0) + continue; + ptemp += ret_len; + if (ptemp < temp + sizeof(temp) - 1) { + *(ptemp++) = ';'; + } else { + continue; + } + + // Write to string + int need_len = ptemp - temp; + if (need_len > left_len) + break; + memcpy(str + len, temp, need_len * 2); + left_len -= need_len; + len += need_len; + (*count)++; + } + if (len > 0) { + if (state_ < USER_DICT_SYNC_DIRTY) + state_ = USER_DICT_SYNC_DIRTY; + } + return len; + } +#endif + bool UserDict::state(UserDictStat *stat) { + if (is_valid_state() == false) + return false; + if (!stat) + return false; + stat->version = version_; + stat->file_name = dict_file_; + stat->load_time.tv_sec = load_time_.tv_sec; + stat->load_time.tv_usec = load_time_.tv_usec; + pthread_mutex_lock(&g_mutex_); + stat->last_update.tv_sec = g_last_update_.tv_sec; + stat->last_update.tv_usec = g_last_update_.tv_usec; + pthread_mutex_unlock(&g_mutex_); + stat->disk_size = get_dict_file_size(&dict_info_); + stat->lemma_count = dict_info_.lemma_count; + stat->lemma_size = dict_info_.lemma_size; + stat->delete_count = dict_info_.free_count; + stat->delete_size = dict_info_.free_size; +#ifdef ___SYNC_ENABLED___ + stat->sync_count = dict_info_.sync_count; +#endif + stat->limit_lemma_count = dict_info_.limit_lemma_count; + stat->limit_lemma_size = dict_info_.limit_lemma_size; + stat->reclaim_ratio = dict_info_.reclaim_ratio; + return true; + } + void UserDict::set_limit(uint32 max_lemma_count, + uint32 max_lemma_size, uint32 reclaim_ratio) { + dict_info_.limit_lemma_count = max_lemma_count; + dict_info_.limit_lemma_size = max_lemma_size; + if (reclaim_ratio > 100) + reclaim_ratio = 100; + dict_info_.reclaim_ratio = reclaim_ratio; + } + void UserDict::reclaim() { + if (is_valid_state() == false) + return; + switch (dict_info_.reclaim_ratio) { + case 0: + return; + case 100: + // TODO: CLEAR to be implemented + assert(false); + return; + default: + break; + } + + // XXX Reclaim is only based on count, not size + uint32 count = dict_info_.lemma_count; + int rc = count * dict_info_.reclaim_ratio / 100; + UserDictScoreOffsetPair *score_offset_pairs = NULL; + score_offset_pairs = (UserDictScoreOffsetPair *) malloc( + sizeof(UserDictScoreOffsetPair) * rc); + if (score_offset_pairs == NULL) { + return; + } + for (int i = 0; i < rc; i++) { + int s = scores_[i]; + score_offset_pairs[i].score = s; + score_offset_pairs[i].offset_index = i; + } + for (int i = (rc + 1) / 2; i >= 0; i--) + shift_down(score_offset_pairs, i, rc); + for (uint32 i = rc; i < dict_info_.lemma_count; i++) { + int s = scores_[i]; + if (s < score_offset_pairs[0].score) { + score_offset_pairs[0].score = s; + score_offset_pairs[0].offset_index = i; + shift_down(score_offset_pairs, 0, rc); + } + } + for (int i = 0; i < rc; i++) { + int off = score_offset_pairs[i].offset_index; + remove_lemma_by_offset_index(off); + } + if (rc > 0) { + if (state_ < USER_DICT_OFFSET_DIRTY) + state_ = USER_DICT_OFFSET_DIRTY; + } + free(score_offset_pairs); + } + inline void UserDict::swap(UserDictScoreOffsetPair *sop, int i, int j) { + int s = sop[i].score; + int p = sop[i].offset_index; + sop[i].score = sop[j].score; + sop[i].offset_index = sop[j].offset_index; + sop[j].score = s; + sop[j].offset_index = p; + } + void UserDict::shift_down(UserDictScoreOffsetPair *sop, int i, int n) { + int par = i; + while (par < n) { + int left = par * 2 + 1; + int right = left + 1; + if (left >= n && right >= n) + break; + if (right >= n) { + if (sop[left].score > sop[par].score) { + swap(sop, left, par); + par = left; + continue; + } + } else if (sop[left].score > sop[right].score && + sop[left].score > sop[par].score) { + swap(sop, left, par); + par = left; + continue; + } else if (sop[right].score > sop[left].score && + sop[right].score > sop[par].score) { + swap(sop, right, par); + par = right; + continue; + } + break; + } + } + LemmaIdType UserDict::put_lemma(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count) { + return _put_lemma(lemma_str, splids, lemma_len, count, time(NULL)); + } + LemmaIdType UserDict::_put_lemma(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count, uint64 lmt) { +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_BEGIN; +#endif + if (is_valid_state() == false) + return 0; + int32 off = locate_in_offsets(lemma_str, splids, lemma_len); + if (off != -1) { + int delta_score = count - scores_[off]; + dict_info_.total_nfreq += delta_score; + scores_[off] = build_score(lmt, count); + if (state_ < USER_DICT_SCORE_DIRTY) + state_ = USER_DICT_SCORE_DIRTY; +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_END; + LOGD_PERF("_put_lemma(update)"); +#endif + return ids_[off]; + } else { + if ((dict_info_.limit_lemma_count > 0 && + dict_info_.lemma_count >= dict_info_.limit_lemma_count) + || (dict_info_.limit_lemma_size > 0 && + dict_info_.lemma_size + (2 + (lemma_len << 2)) + > dict_info_.limit_lemma_size)) { + // XXX Don't defragment here, it's too time-consuming. + return 0; + } + int flushed = 0; + if (lemma_count_left_ == 0 || + lemma_size_left_ < (size_t) (2 + (lemma_len << 2))) { + + // XXX When there is no space for new lemma, we flush to disk + // flush_cache() may be called by upper user + // and better place shoule be found instead of here + flush_cache(); + flushed = 1; + // Or simply return and do nothing + // return 0; + } +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_END; + LOGD_PERF(flushed ? "_put_lemma(flush+add)" : "_put_lemma(add)"); +#endif + LemmaIdType id = append_a_lemma(lemma_str, splids, lemma_len, count, lmt); +#ifdef ___SYNC_ENABLED___ + if (syncs_ && id != 0) { + queue_lemma_for_sync(id); + } +#endif + return id; + } + return 0; + } +#ifdef ___SYNC_ENABLED___ + void UserDict::queue_lemma_for_sync(LemmaIdType id) { + if (dict_info_.sync_count < sync_count_size_) { + syncs_[dict_info_.sync_count++] = offsets_by_id_[id - start_id_]; + } else { + uint32 *syncs = (uint32 *) realloc( + syncs_, (sync_count_size_ + kUserDictPreAlloc) << 2); + if (syncs) { + sync_count_size_ += kUserDictPreAlloc; + syncs_ = syncs; + syncs_[dict_info_.sync_count++] = offsets_by_id_[id - start_id_]; + } + } + } +#endif + LemmaIdType UserDict::update_lemma(LemmaIdType lemma_id, int16 delta_count, + bool selected) { +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_BEGIN; +#endif + if (is_valid_state() == false) + return 0; + if (is_valid_lemma_id(lemma_id) == false) + return 0; + uint32 offset = offsets_by_id_[lemma_id - start_id_]; + uint8 lemma_len = get_lemma_nchar(offset); + char16 *lemma_str = get_lemma_word(offset); + uint16 *splids = get_lemma_spell_ids(offset); + int32 off = locate_in_offsets(lemma_str, splids, lemma_len); + if (off != -1) { + int score = scores_[off]; + int count = extract_score_freq(score); + uint64 lmt = extract_score_lmt(score); + if (count + delta_count > kUserDictMaxFrequency || + count + delta_count < count) { + delta_count = kUserDictMaxFrequency - count; + } + count += delta_count; + dict_info_.total_nfreq += delta_count; + if (selected) { + lmt = time(NULL); + } + scores_[off] = build_score(lmt, count); + if (state_ < USER_DICT_SCORE_DIRTY) + state_ = USER_DICT_SCORE_DIRTY; +#ifdef ___DEBUG_PERF___ + DEBUG_PERF_END; + LOGD_PERF("update_lemma"); +#endif +#ifdef ___SYNC_ENABLED___ + queue_lemma_for_sync(ids_[off]); +#endif + return ids_[off]; + } + return 0; + } + size_t UserDict::get_total_lemma_count() { + return dict_info_.total_nfreq; + } + void UserDict::set_total_lemma_count_of_others(size_t count) { + total_other_nfreq_ = count; + } + LemmaIdType UserDict::append_a_lemma(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count, uint64 lmt) { + LemmaIdType id = get_max_lemma_id() + 1; + size_t offset = dict_info_.lemma_size; + if (offset > kUserDictOffsetMask) + return 0; + lemmas_[offset] = 0; + lemmas_[offset + 1] = (uint8) lemma_len; + for (size_t i = 0; i < lemma_len; i++) { + *((uint16 *) &lemmas_[offset + 2 + (i << 1)]) = splids[i]; + *((char16 *) &lemmas_[offset + 2 + (lemma_len << 1) + (i << 1)]) + = lemma_str[i]; + } + uint32 off = dict_info_.lemma_count; + offsets_[off] = offset; + scores_[off] = build_score(lmt, count); + ids_[off] = id; +#ifdef ___PREDICT_ENABLED___ + predicts_[off] = offset; +#endif + offsets_by_id_[id - start_id_] = offset; + dict_info_.lemma_count++; + dict_info_.lemma_size += (2 + (lemma_len << 2)); + lemma_count_left_--; + lemma_size_left_ -= (2 + (lemma_len << 2)); + + // Sort + + UserDictSearchable searchable; + prepare_locate(&searchable, splids, lemma_len); + size_t i = 0; + while (i < off) { + offset = offsets_[i]; + uint32 nchar = get_lemma_nchar(offset); + uint16 *spl = get_lemma_spell_ids(offset); + if (0 <= fuzzy_compare_spell_id(spl, nchar, &searchable)) + break; + i++; + } + if (i != off) { + uint32 temp = offsets_[off]; + memmove(offsets_ + i + 1, offsets_ + i, (off - i) << 2); + offsets_[i] = temp; + temp = scores_[off]; + memmove(scores_ + i + 1, scores_ + i, (off - i) << 2); + scores_[i] = temp; + temp = ids_[off]; + memmove(ids_ + i + 1, ids_ + i, (off - i) << 2); + ids_[i] = temp; + } +#ifdef ___PREDICT_ENABLED___ + uint32 j = 0; + uint16 *words_new = get_lemma_word(predicts_[off]); + j = locate_where_to_insert_in_predicts(words_new, lemma_len); + if (j != off) { + uint32 temp = predicts_[off]; + memmove(predicts_ + j + 1, predicts_ + j, (off - j) << 2); + predicts_[j] = temp; + } +#endif + if (state_ < USER_DICT_LEMMA_DIRTY) + state_ = USER_DICT_LEMMA_DIRTY; +#ifdef ___CACHE_ENABLED___ + cache_init(); +#endif + dict_info_.total_nfreq += count; + return id; + } +} diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/userdict.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/userdict.h new file mode 100644 index 0000000..cbeaab7 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/userdict.h @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_USERDICT_H__ +#define PINYINIME_INCLUDE_USERDICT_H__ +#define ___CACHE_ENABLED___ +#define ___SYNC_ENABLED___ +#define ___PREDICT_ENABLED___ + +// Debug performance for operations +// #define ___DEBUG_PERF___ + +#ifdef _WIN32 +#include // timeval +#else +#include +#endif +#include "atomdictbase.h" +namespace ime_pinyin { + class UserDict : public AtomDictBase { + public: + UserDict(); + ~UserDict(); + bool load_dict(const char *file_name, LemmaIdType start_id, + LemmaIdType end_id); + bool close_dict(); + size_t number_of_lemmas(); + void reset_milestones(uint16 from_step, MileStoneHandle from_handle); + MileStoneHandle extend_dict(MileStoneHandle from_handle, + const DictExtPara *dep, LmaPsbItem *lpi_items, + size_t lpi_max, size_t *lpi_num); + size_t get_lpis(const uint16 *splid_str, uint16 splid_str_len, + LmaPsbItem *lpi_items, size_t lpi_max); + uint16 get_lemma_str(LemmaIdType id_lemma, char16 *str_buf, + uint16 str_max); + uint16 get_lemma_splids(LemmaIdType id_lemma, uint16 *splids, + uint16 splids_max, bool arg_valid); + size_t predict(const char16 last_hzs[], uint16 hzs_len, + NPredictItem *npre_items, size_t npre_max, + size_t b4_used); + // Full spelling ids are required + LemmaIdType put_lemma(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count); + LemmaIdType update_lemma(LemmaIdType lemma_id, int16 delta_count, + bool selected); + LemmaIdType get_lemma_id(char16 lemma_str[], uint16 splids[], + uint16 lemma_len); + LmaScoreType get_lemma_score(LemmaIdType lemma_id); + LmaScoreType get_lemma_score(char16 lemma_str[], uint16 splids[], + uint16 lemma_len); + bool remove_lemma(LemmaIdType lemma_id); + size_t get_total_lemma_count(); + void set_total_lemma_count_of_others(size_t count); + void flush_cache(); + void set_limit(uint32 max_lemma_count, uint32 max_lemma_size, + uint32 reclaim_ratio); + void reclaim(); + void defragment(); +#ifdef ___SYNC_ENABLED___ + void clear_sync_lemmas(unsigned int start, unsigned int end); + int get_sync_count(); + LemmaIdType put_lemma_no_sync(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count, uint64 lmt); + /** + * Add lemmas encoded in UTF-16LE into dictionary without adding sync flag. + * + * @param lemmas in format of 'wo men,WM,0.32;da jia,DJ,0.12' + * @param len length of lemmas string in UTF-16LE + * @return newly added lemma count + */ + int put_lemmas_no_sync_from_utf16le_string(char16 *lemmas, int len); + /** + * Get lemmas need sync to a UTF-16LE string of above format. + * Note: input buffer (str) must not be too small. If str is too small to + * contain single one lemma, there might be a dead loop. + * + * @param str buffer to write lemmas + * @param size buffer size in UTF-16LE + * @param count output value of lemma returned + * @return UTF-16LE string length + */ + int get_sync_lemmas_in_utf16le_string_from_beginning( + char16 *str, int size, int *count); +#endif + struct UserDictStat { + uint32 version; + const char *file_name; + struct timeval load_time; + struct timeval last_update; + uint32 disk_size; + uint32 lemma_count; + uint32 lemma_size; + uint32 delete_count; + uint32 delete_size; +#ifdef ___SYNC_ENABLED___ + uint32 sync_count; +#endif + uint32 reclaim_ratio; + uint32 limit_lemma_count; + uint32 limit_lemma_size; + }; + bool state(UserDictStat *stat); + private: + uint32 total_other_nfreq_; + struct timeval load_time_; + LemmaIdType start_id_; + uint32 version_; + uint8 *lemmas_; + // In-Memory-Only flag for each lemma + static const uint8 kUserDictLemmaFlagRemove = 1; + // Inuse lemmas' offset + uint32 *offsets_; + // Highest bit in offset tells whether corresponding lemma is removed + static const uint32 kUserDictOffsetFlagRemove = (1 << 31); + // Maximum possible for the offset + static const uint32 kUserDictOffsetMask = ~(kUserDictOffsetFlagRemove); + // Bit width for last modified time, from 1 to 16 + static const uint32 kUserDictLMTBitWidth = 16; + // Granularity for last modified time in second + static const uint32 kUserDictLMTGranularity = 60 * 60 * 24 * 7; + // Maximum frequency count + static const uint16 kUserDictMaxFrequency = 0xFFFF; +#define COARSE_UTC(year, month, day, hour, minute, second) \ + ( \ + (year - 1970) * 365 * 24 * 60 * 60 + \ + (month - 1) * 30 * 24 * 60 * 60 + \ + (day - 1) * 24 * 60 * 60 + \ + (hour - 0) * 60 * 60 + \ + (minute - 0) * 60 + \ + (second - 0) \ + ) + static const uint64 kUserDictLMTSince = COARSE_UTC(2009, 1, 1, 0, 0, 0); + // Correspond to offsets_ + uint32 *scores_; + // Following two fields are only valid in memory + uint32 *ids_; +#ifdef ___PREDICT_ENABLED___ + uint32 *predicts_; +#endif +#ifdef ___SYNC_ENABLED___ + uint32 *syncs_; + size_t sync_count_size_; +#endif + uint32 *offsets_by_id_; + size_t lemma_count_left_; + size_t lemma_size_left_; + const char *dict_file_; + // Be sure size is 4xN + struct UserDictInfo { + // When limitation reached, how much percentage will be reclaimed (1 ~ 100) + uint32 reclaim_ratio; + // maximum lemma count, 0 means no limitation + uint32 limit_lemma_count; + // Maximum lemma size, it's different from + // whole disk file size or in-mem dict size + // 0 means no limitation + uint32 limit_lemma_size; + // Total lemma count including deleted and inuse + // Also indicate offsets_ size + uint32 lemma_count; + // Total size of lemmas including used and freed + uint32 lemma_size; + // Freed lemma count + uint32 free_count; + // Freed lemma size in byte + uint32 free_size; +#ifdef ___SYNC_ENABLED___ + uint32 sync_count; +#endif + int32 total_nfreq; + } dict_info_; + static const uint32 kUserDictVersion = 0x0ABCDEF0; + static const uint32 kUserDictPreAlloc = 32; + static const uint32 kUserDictAverageNchar = 8; + enum UserDictState { + // Keep in order + USER_DICT_NONE = 0, + USER_DICT_SYNC, +#ifdef ___SYNC_ENABLED___ + USER_DICT_SYNC_DIRTY, +#endif + USER_DICT_SCORE_DIRTY, + USER_DICT_OFFSET_DIRTY, + USER_DICT_LEMMA_DIRTY, + USER_DICT_DEFRAGMENTED, + } state_; + struct UserDictSearchable { + uint16 splids_len; + uint16 splid_start[kMaxLemmaSize]; + uint16 splid_count[kMaxLemmaSize]; + // Compact inital letters for both FuzzyCompareSpellId and cache system + uint32 signature[kMaxLemmaSize / 4]; + }; +#ifdef ___CACHE_ENABLED___ + enum UserDictCacheType { + USER_DICT_CACHE, + USER_DICT_MISS_CACHE, + }; + static const int kUserDictCacheSize = 4; + static const int kUserDictMissCacheSize = kMaxLemmaSize - 1; + struct UserDictMissCache { + uint32 signatures[kUserDictMissCacheSize][kMaxLemmaSize / 4]; + uint16 head, tail; + } miss_caches_[kMaxLemmaSize]; + struct UserDictCache { + uint32 signatures[kUserDictCacheSize][kMaxLemmaSize / 4]; + uint32 offsets[kUserDictCacheSize]; + uint32 lengths[kUserDictCacheSize]; + // Ring buffer + uint16 head, tail; + } caches_[kMaxLemmaSize]; + void cache_init(); + void cache_push(UserDictCacheType type, + UserDictSearchable *searchable, + uint32 offset, uint32 length); + bool cache_hit(UserDictSearchable *searchable, + uint32 *offset, uint32 *length); + bool load_cache(UserDictSearchable *searchable, + uint32 *offset, uint32 *length); + void save_cache(UserDictSearchable *searchable, + uint32 offset, uint32 length); + void reset_cache(); + bool load_miss_cache(UserDictSearchable *searchable); + void save_miss_cache(UserDictSearchable *searchable); + void reset_miss_cache(); +#endif + LmaScoreType translate_score(int f); + int extract_score_freq(int raw_score); + uint64 extract_score_lmt(int raw_score); + inline int build_score(uint64 lmt, int freq); + inline int64 utf16le_atoll(uint16 *s, int len); + inline int utf16le_lltoa(int64 v, uint16 *s, int size); + LemmaIdType _put_lemma(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count, uint64 lmt); + size_t _get_lpis(const uint16 *splid_str, uint16 splid_str_len, + LmaPsbItem *lpi_items, size_t lpi_max, bool *need_extend); + int _get_lemma_score(char16 lemma_str[], uint16 splids[], uint16 lemma_len); + int _get_lemma_score(LemmaIdType lemma_id); + int is_fuzzy_prefix_spell_id(const uint16 *id1, uint16 len1, + const UserDictSearchable *searchable); + bool is_prefix_spell_id(const uint16 *fullids, + uint16 fulllen, const UserDictSearchable *searchable); + uint32 get_dict_file_size(UserDictInfo *info); + bool reset(const char *file); + bool validate(const char *file); + bool load(const char *file, LemmaIdType start_id); + bool is_valid_state(); + bool is_valid_lemma_id(LemmaIdType id); + LemmaIdType get_max_lemma_id(); + void set_lemma_flag(uint32 offset, uint8 flag); + char get_lemma_flag(uint32 offset); + char get_lemma_nchar(uint32 offset); + uint16 *get_lemma_spell_ids(uint32 offset); + uint16 *get_lemma_word(uint32 offset); + // Prepare searchable to fasten locate process + void prepare_locate(UserDictSearchable *searchable, + const uint16 *splids, uint16 len); + // Compare initial letters only + int32 fuzzy_compare_spell_id(const uint16 *id1, uint16 len1, + const UserDictSearchable *searchable); + // Compare exactly two spell ids + // First argument must be a full id spell id + bool equal_spell_id(const uint16 *fullids, + uint16 fulllen, const UserDictSearchable *searchable); + // Find first item by initial letters + int32 locate_first_in_offsets(const UserDictSearchable *searchable); + LemmaIdType append_a_lemma(char16 lemma_str[], uint16 splids[], + uint16 lemma_len, uint16 count, uint64 lmt); + // Check if a lemma is in dictionary + int32 locate_in_offsets(char16 lemma_str[], + uint16 splid_str[], uint16 lemma_len); + bool remove_lemma_by_offset_index(int offset_index); +#ifdef ___PREDICT_ENABLED___ + uint32 locate_where_to_insert_in_predicts(const uint16 *words, + int lemma_len); + int32 locate_first_in_predicts(const uint16 *words, int lemma_len); + void remove_lemma_from_predict_list(uint32 offset); +#endif +#ifdef ___SYNC_ENABLED___ + void queue_lemma_for_sync(LemmaIdType id); + void remove_lemma_from_sync_list(uint32 offset); + void write_back_sync(int fd); +#endif + void write_back_score(int fd); + void write_back_offset(int fd); + void write_back_lemma(int fd); + void write_back_all(int fd); + void write_back(); + struct UserDictScoreOffsetPair { + int score; + uint32 offset_index; + }; + inline void swap(UserDictScoreOffsetPair *sop, int i, int j); + void shift_down(UserDictScoreOffsetPair *sop, int i, int n); + + // On-disk format for each lemma + // +-------------+ + // | Version (4) | + // +-------------+ + // +-----------+-----------+--------------------+-------------------+ + // | Spare (1) | Nchar (1) | Splids (2 x Nchar) | Lemma (2 x Nchar) | + // +-----------+-----------+--------------------+-------------------+ + // ... + // +-----------------------+ +-------------+ <---Offset of offset + // | Offset1 by_splids (4) | ... | OffsetN (4) | + // +-----------------------+ +-------------+ +#ifdef ___PREDICT_ENABLED___ + // +----------------------+ +-------------+ + // | Offset1 by_lemma (4) | ... | OffsetN (4) | + // +----------------------+ +-------------+ +#endif + // +------------+ +------------+ + // | Score1 (4) | ... | ScoreN (4) | + // +------------+ +------------+ +#ifdef ___SYNC_ENABLED___ + // +-------------+ +-------------+ + // | NewAdd1 (4) | ... | NewAddN (4) | + // +-------------+ +-------------+ +#endif + // +----------------+ + // | Dict Info (4x) | + // +----------------+ + }; +} +#endif diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16char.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16char.cpp new file mode 100644 index 0000000..b2dfe30 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16char.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "utf16char.h" +namespace ime_pinyin { +#ifdef __cplusplus + extern "C" { +#endif + char16 *utf16_strtok(char16 *utf16_str, size_t *token_size, + char16 **utf16_str_next) { + if (NULL == utf16_str || NULL == token_size || NULL == utf16_str_next) { + return NULL; + } + + // Skip the splitters + size_t pos = 0; + while ((char16) ' ' == utf16_str[pos] || (char16) '\n' == utf16_str[pos] + || (char16) '\t' == utf16_str[pos]) + pos++; + utf16_str += pos; + pos = 0; + while ((char16) '\0' != utf16_str[pos] && (char16) ' ' != utf16_str[pos] + && (char16) '\n' != utf16_str[pos] + && (char16) '\t' != utf16_str[pos]) { + pos++; + } + char16 *ret_val = utf16_str; + if ((char16) '\0' == utf16_str[pos]) { + *utf16_str_next = NULL; + if (0 == pos) + return NULL; + } else { + *utf16_str_next = utf16_str + pos + 1; + } + utf16_str[pos] = (char16) '\0'; + *token_size = pos; + return ret_val; + } + int utf16_atoi(const char16 *utf16_str) { + if (NULL == utf16_str) + return 0; + int value = 0; + int sign = 1; + size_t pos = 0; + if ((char16) '-' == utf16_str[pos]) { + sign = -1; + pos++; + } + while ((char16) '0' <= utf16_str[pos] && + (char16) '9' >= utf16_str[pos]) { + value = value * 10 + static_cast(utf16_str[pos] - (char16) '0'); + pos++; + } + return value * sign; + } + float utf16_atof(const char16 *utf16_str) { + // A temporary implemetation. + char char8[256]; + if (utf16_strlen(utf16_str) >= 256) return 0; + utf16_strcpy_tochar(char8, utf16_str); + return atof(char8); + } + size_t utf16_strlen(const char16 *utf16_str) { + if (NULL == utf16_str) + return 0; + size_t size = 0; + while ((char16) '\0' != utf16_str[size]) + size++; + return size; + } + int utf16_strcmp(const char16 *str1, const char16 *str2) { + size_t pos = 0; + while (str1[pos] == str2[pos] && (char16) '\0' != str1[pos]) + pos++; + return static_cast(str1[pos]) - static_cast(str2[pos]); + } + int utf16_strncmp(const char16 *str1, const char16 *str2, size_t size) { + size_t pos = 0; + while (pos < size && str1[pos] == str2[pos] && (char16) '\0' != str1[pos]) + pos++; + if (pos == size) + return 0; + return static_cast(str1[pos]) - static_cast(str2[pos]); + } + // we do not consider overlapping + char16 *utf16_strcpy(char16 *dst, const char16 *src) { + if (NULL == src || NULL == dst) + return NULL; + char16 *cp = dst; + while ((char16) '\0' != *src) { + *cp = *src; + cp++; + src++; + } + *cp = *src; + return dst; + } + char16 *utf16_strncpy(char16 *dst, const char16 *src, size_t size) { + if (NULL == src || NULL == dst || 0 == size) + return NULL; + if (src == dst) + return dst; + char16 *cp = dst; + if (dst < src || (dst > src && dst >= src + size)) { + while (size-- && (*cp++ = *src++)); + } else { + cp += size - 1; + src += size - 1; + while (size-- && (*cp-- == *src--)); + } + return dst; + } + // We do not handle complicated cases like overlapping, because in this + // codebase, it is not necessary. + char *utf16_strcpy_tochar(char *dst, const char16 *src) { + if (NULL == src || NULL == dst) + return NULL; + char *cp = dst; + while ((char16) '\0' != *src) { + *cp = static_cast(*src); + cp++; + src++; + } + *cp = *src; + return dst; + } +#ifdef __cplusplus + } +#endif +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16char.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16char.h new file mode 100644 index 0000000..ee8ec08 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16char.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_UTF16CHAR_H__ +#define PINYINIME_INCLUDE_UTF16CHAR_H__ +#include +namespace ime_pinyin { +#ifdef __cplusplus + extern "C" { +#endif + typedef unsigned short char16; + // Get a token from utf16_str, + // Returned pointer is a '\0'-terminated utf16 string, or NULL + // *utf16_str_next returns the next part of the string for further tokenizing + char16 *utf16_strtok(char16 *utf16_str, size_t *token_size, + char16 **utf16_str_next); + int utf16_atoi(const char16 *utf16_str); + float utf16_atof(const char16 *utf16_str); + size_t utf16_strlen(const char16 *utf16_str); + int utf16_strcmp(const char16 *str1, const char16 *str2); + int utf16_strncmp(const char16 *str1, const char16 *str2, size_t size); + char16 *utf16_strcpy(char16 *dst, const char16 *src); + char16 *utf16_strncpy(char16 *dst, const char16 *src, size_t size); + char *utf16_strcpy_tochar(char *dst, const char16 *src); +#ifdef __cplusplus + } +#endif +} +#endif // PINYINIME_INCLUDE_UTF16CHAR_H__ diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16reader.cpp b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16reader.cpp new file mode 100644 index 0000000..f78f6cb --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16reader.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "utf16reader.h" +namespace ime_pinyin { +#define MIN_BUF_LEN 128 +#define MAX_BUF_LEN 65535 + Utf16Reader::Utf16Reader() { + fp_ = NULL; + buffer_ = NULL; + buffer_total_len_ = 0; + buffer_next_pos_ = 0; + buffer_valid_len_ = 0; + } + Utf16Reader::~Utf16Reader() { + if (NULL != fp_) + fclose(fp_); + if (NULL != buffer_) + delete[] buffer_; + } + bool Utf16Reader::open(const char *filename, size_t buffer_len) { + if (filename == NULL) + return false; + if (buffer_len < MIN_BUF_LEN) + buffer_len = MIN_BUF_LEN; + else if (buffer_len > MAX_BUF_LEN) + buffer_len = MAX_BUF_LEN; + buffer_total_len_ = buffer_len; + if (NULL != buffer_) + delete[] buffer_; + buffer_ = new char16[buffer_total_len_]; + if (NULL == buffer_) + return false; + if ((fp_ = fopen(filename, "rb")) == NULL) + return false; + + // the UTF16 file header, skip + char16 header; + if (fread(&header, sizeof(header), 1, fp_) != 1 || header != 0xfeff) { + fclose(fp_); + fp_ = NULL; + return false; + } + return true; + } + char16 *Utf16Reader::readline(char16 *read_buf, size_t max_len) { + if (NULL == fp_ || NULL == read_buf || 0 == max_len) + return NULL; + size_t ret_len = 0; + do { + if (buffer_valid_len_ == 0) { + buffer_next_pos_ = 0; + buffer_valid_len_ = fread(buffer_, sizeof(char16), + buffer_total_len_, fp_); + if (buffer_valid_len_ == 0) { + if (0 == ret_len) + return NULL; + read_buf[ret_len] = (char16) '\0'; + return read_buf; + } + } + for (size_t i = 0; i < buffer_valid_len_; i++) { + if (i == max_len - 1 || + buffer_[buffer_next_pos_ + i] == (char16) '\n') { + if (ret_len + i > 0 && read_buf[ret_len + i - 1] == (char16) '\r') { + read_buf[ret_len + i - 1] = (char16) '\0'; + } else { + read_buf[ret_len + i] = (char16) '\0'; + } + i++; + buffer_next_pos_ += i; + buffer_valid_len_ -= i; + if (buffer_next_pos_ == buffer_total_len_) { + buffer_next_pos_ = 0; + buffer_valid_len_ = 0; + } + return read_buf; + } else { + read_buf[ret_len + i] = buffer_[buffer_next_pos_ + i]; + } + } + ret_len += buffer_valid_len_; + buffer_valid_len_ = 0; + } while (true); + + // Never reach here + return NULL; + } + bool Utf16Reader::close() { + if (NULL != fp_) + fclose(fp_); + fp_ = NULL; + if (NULL != buffer_) + delete[] buffer_; + buffer_ = NULL; + return true; + } +} // namespace ime_pinyin diff --git a/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16reader.h b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16reader.h new file mode 100644 index 0000000..b8246d0 --- /dev/null +++ b/YSGraphic_Core/base/VirtualKeyBoard/googlepinyin/utf16reader.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINYINIME_INCLUDE_UTF16READER_H__ +#define PINYINIME_INCLUDE_UTF16READER_H__ +#include +#include "./utf16char.h" +namespace ime_pinyin { + class Utf16Reader { + private: + FILE *fp_; + char16 *buffer_; + size_t buffer_total_len_; + size_t buffer_next_pos_; + // Always less than buffer_total_len_ - buffer_next_pos_ + size_t buffer_valid_len_; + public: + Utf16Reader(); + ~Utf16Reader(); + // filename is the name of the file to open. + // buffer_len specifies how long buffer should be allocated to speed up the + // future reading + bool open(const char *filename, size_t buffer_len); + char16 *readline(char16 *read_buf, size_t max_len); + bool close(); + }; +} +#endif // PINYINIME_INCLUDE_UTF16READER_H__ diff --git a/YSGraphic_Core/base/algorithm.hpp b/YSGraphic_Core/base/algorithm.hpp new file mode 100644 index 0000000..466b2a8 --- /dev/null +++ b/YSGraphic_Core/base/algorithm.hpp @@ -0,0 +1,91 @@ +#pragma once + + +namespace YSG{ + #define LOWER(offset) l_lower(spaces[offset]) + #define UPPER(offset) l_upper(spaces[offset]) + template + int binary_search(double value, T** spaces, int spaceSize, std::function& l_lower, std::function& l_upper) { + int startIndex = 0, endIndex = spaceSize - 1; + if(startIndex == endIndex) return startIndex; + double min = LOWER(startIndex), max = UPPER(endIndex); + if(value < min) { + //qDebug() << QString("warnning binary_search value:%1 < %2").arg(value).arg(min); + return startIndex; + } + if(value > max) { + //qDebug() << QString("warnning binary_search value:%1 > %2").arg(value).arg(max); + return endIndex; + } + while(startIndex != endIndex) { + if(startIndex+1 == endIndex) { + double mid = (UPPER(startIndex)+LOWER(endIndex))/2.0; + if(value < mid) return startIndex; + return endIndex; + } + int m = (startIndex + endIndex)/2; + double lower = m!=0 ? (UPPER(m-1)+LOWER(m))/2.0 : min; + double upper = m!=spaceSize-1 ? (UPPER(m)+LOWER(m+1))/2.0 : max; + //qDebug() << " lower == " << lower << " upper == " << upper << "midIndex == " << m << " value == " << value; + //qDebug() << " startIndex == " << startIndex << " endIndex == " << endIndex; + if(value < lower) { + endIndex = m; + } else if(value > upper) { + startIndex = m; + } else { + return m; + } + } + qDebug() << QString("warnning startIndex:%1, endIndex:%2").arg(startIndex).arg(endIndex); + return startIndex; + } + #undef LOWER + #undef UPPER + + static int binary_search(double value, const QVector& data, bool& ok) { + if (data.isEmpty()) { + ok = false; + return -1; // 返回 -1 表示数组为空 + } + + // 判断排序顺序 + bool ascendingOrder = data[0] < data[data.size() - 1]; + int left = 0; + int right = data.size() - 1; + + // 二分查找 + while (left <= right) { + int mid = left + (right - left) / 2; + + if (mid < data.size() - 1) { + if (ascendingOrder) { + if (data[mid] <= value && data[mid + 1] > value) { + ok = true; + return mid; + } else if (data[mid] < value) { + left = mid + 1; + } else { + right = mid - 1; + } + } else { // 处理降序排列的情况 + if (data[mid] >= value && data[mid + 1] < value) { + ok = true; + return mid; + } else if (data[mid] > value) { + left = mid + 1; + } else { + right = mid - 1; + } + } + } else { + break; // 如果 mid 已经是最后一个元素,不再继续查找 + } + } + + ok = false; + // 如果找不到,则返回 -1,表示 value 不在区间内 + return -1; + } +} + + diff --git a/YSGraphic_Core/base/export.h b/YSGraphic_Core/base/export.h new file mode 100644 index 0000000..12d4492 --- /dev/null +++ b/YSGraphic_Core/base/export.h @@ -0,0 +1,11 @@ +#include "CircularLinkedList.hpp" +#include "FileDialog/FileDialog.h" +#include "Node.hpp" +#include "RingBuffer.hpp" +#include "RollObject.h" +#include "SingletonWidget.hpp" +#include "Spin_Lock.h" +#include "VirtualKeyBoard/VirtualKeyBoard.h" +#include "algorithm.hpp" + + diff --git a/YSGraphic_Core/component/Background.h b/YSGraphic_Core/component/Background.h new file mode 100644 index 0000000..4a17e62 --- /dev/null +++ b/YSGraphic_Core/component/Background.h @@ -0,0 +1,145 @@ +#pragma once +#include "global.h" + +enum class Mouse_State { + Default, + Pressed, + Entered +}; + + +struct Border { + QColor border_color = Qt::red; + int len = 4; +}; + +using Handle_Mouse = std::function; +struct Background : PaintAble { + QGraphicsDropShadowEffect shadowEffect; + Border *border{}; + QMap map; + Qt::SizeMode mode = Qt::RelativeSize; + qreal radius = 0.1; + + bool enter = false; + Mouse_State mouse_state = Mouse_State::Default; + QVector mouse_handlers; + + + void set_border(const QColor& border_color, int len = 1) { + if(!border) border = new Border; + border->border_color = border_color; + border->len = len; + } + + void set_effect(QWidget* that) { + shadowEffect.setBlurRadius(10); // 设置模糊半径 + shadowEffect.setOffset(5, 5); // 设置阴影偏移量(x, y) + shadowEffect.setColor(Qt::black); // 设置阴影颜色 + that->setGraphicsEffect(&shadowEffect); + } + + void set_color(const QColor& color) { + map[Mouse_State::Default] = color; + double r = color.red(); + double g = color.green(); + double b = color.blue(); + //int alpha = color.alpha(); + double upper = 1.6; + double lower = 0.4; + auto ur = qMin(255, (int)(r * upper)); + auto ug = qMin(255, (int)(g * upper)); + auto ub = qMin(255, (int)(b * upper)); + int lr = (int)(r * lower); + int lg = (int)(g * lower); + int lb = (int)(b * lower); + map[Mouse_State::Pressed] = QColor(ur, ug, ub); + map[Mouse_State::Entered] = QColor(lr, lg, lb); + } + + Event_Type mouse_event(QEvent::Type t, QMouseEvent* e) override { + if (t == QEvent::MouseButtonPress) + { + mouse_state = Mouse_State::Pressed; + } else if (t == QEvent::MouseButtonRelease) + { + if (enter) mouse_state = Mouse_State::Entered; + else mouse_state = Mouse_State::Default; + } + for (auto& handler : mouse_handlers) { + handler(t, e); + } + return Event_Type::refresh; + } + + + Event_Type event(QEvent::Type t, QEvent* e) override { + if (t == QEvent::Enter) + { + enter = true; + mouse_state = Mouse_State::Entered; + return Event_Type::refresh; + } else if (t == QEvent::Leave) + { + enter = false; + mouse_state = Mouse_State::Default; + return Event_Type::refresh; + } + return PaintAble::event(t, e); + } + void paint(QPainter* painter, QRect rect) override { + painter->save(); + int halfPenWidth = 0; + QRect adjustedRect = rect.adjusted(halfPenWidth, halfPenWidth, -halfPenWidth, -halfPenWidth); + qreal xRadius, yRadius; + getRoundedRectRadius(adjustedRect, xRadius, yRadius); + QPainterPath borderPath; + borderPath.addRoundedRect(adjustedRect, xRadius, yRadius); + QRect innerRect = adjustedRect; + if(border) { + innerRect.adjust(border->len, border->len, -border->len, -border->len); + } + // 内部区域边距 + QPainterPath innerPath; + innerPath.addRoundedRect(innerRect, xRadius, yRadius); + QColor c = map[mouse_state]; + painter->setPen(Qt::NoPen); + if (c.isValid()) { + painter->save(); + if (border) { + painter->setBrush(border->border_color); + painter->drawPath(borderPath); + } + painter->setBrush(c); + painter->drawPath(innerPath); + painter->restore(); + } else + { + if (border) { + borderPath = borderPath.subtracted(innerPath); + painter->setBrush(border->border_color); + painter->drawPath(borderPath); + } + } + painter->restore(); + } + + void set_radius(Qt::SizeMode mode, qreal value) { + this->mode = mode; + this->radius = value; + } + + +protected: + + void getRoundedRectRadius(const QRect& rect, qreal& xRadius, qreal& yRadius) const { + if (mode == Qt::RelativeSize) { + xRadius = rect.width() * radius; + yRadius = rect.height() * radius; + } else { + xRadius = radius; + yRadius = radius; + } + } +}; + diff --git a/YSGraphic_Core/component/Base/Base.h b/YSGraphic_Core/component/Base/Base.h new file mode 100644 index 0000000..9d56b8c --- /dev/null +++ b/YSGraphic_Core/component/Base/Base.h @@ -0,0 +1,67 @@ +#pragma once +#include +#include + + +namespace Psc { + enum class Direction { + First, + Middle, + Last, + Default + }; + class Layout_Item { + public: + enum Size_Policy { + Get_Main_By_Cross = 1, + Cross_Expand = 2, + Grow = 4, + Shrink = 8, + Ignore = 16 + }; + Direction direction = Direction::Default; + QFlags size_policy; + bool use_get_main_by_cros = false; + virtual int get_main_by_cros(int cros) { + return cros; + } + virtual ~Layout_Item() = default; + [[nodiscard]] int x() const { return _x;} + [[nodiscard]] int y() const { return _y;} + [[nodiscard]] int w() const { return _w;} + [[nodiscard]] int h() const { return _h;} + void set_x(int x) {_x = x;} + void set_y(int y) {_y = y;} + void set_w(int w) {_w = w;} + void set_h(int h) {_h = h;} + void set_size(int w, int h) {_w = w; _h = h;} + void set_size(QSize size) {_w = size.width(); _h = size.height();} + virtual QSize sizeHint() const { + return QSize(_w, _h); + } + virtual QSize minimumSize() const { + return QSize(_w, _h); + } + virtual QSize maximumSize() const { + return QSize(_w, _h); + } + [[nodiscard]] QRect rect() const { + return {_x, _y, _w, _h}; + } + protected: + int _x = 0, _y = 0, _w = 0, _h = 0; + }; + + + class E : public Layout_Item { + public: + virtual ~E() = default; + virtual void paint(QPainter *painter, QRect r, QPaintEvent *event){} + virtual void event(QEvent::Type t, QEvent* e) {} + void update() { + need_update = true; + } + bool need_update = false; + }; +} + diff --git a/YSGraphic_Core/component/Base/Delegate.h b/YSGraphic_Core/component/Base/Delegate.h new file mode 100644 index 0000000..51a5b67 --- /dev/null +++ b/YSGraphic_Core/component/Base/Delegate.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +namespace Psc { + template + class Delegate { + public: + QVector handlers; + Delegate& operator+=(const Handler_Type& handler) { + if (std::find(handlers.begin(), handlers.end(), handler) == handlers.end()) { + handlers.push_back(handler); + } + return *this; + } + Delegate& operator-=(const Handler_Type& handler) { + auto it = std::find(handlers.begin(), handlers.end(), handler); + if (it != handlers.end()) { + handlers.erase(it); // 移除对应的处理程序 + } + return *this; + } + template + void invoke(Args&&... args) const { + for (auto& handler : handlers) { + handler(std::forward(args)...); // 完美转发参数 + } + } + }; + + using MousePressEvent_Handler = std::function; + +} + diff --git a/YSGraphic_Core/component/Base/Layout.cpp b/YSGraphic_Core/component/Base/Layout.cpp new file mode 100644 index 0000000..772ff70 --- /dev/null +++ b/YSGraphic_Core/component/Base/Layout.cpp @@ -0,0 +1,185 @@ +#include "Layout.h" + +#include + +#include "3rd/magic_enum/export.h" +#include "Core/Base/global_include.h" +#include "Core/spdlog/export.h" + +namespace Psc { + struct HorizontalTraits { + static int get_main(const Layout_Item* i) { return i->x(); } + static void set_main(Layout_Item* i, int v) { i->set_x(v); } + + static int get_cros(const Layout_Item* i) { return i->y(); } + static void set_cros(Layout_Item* i, int v) { i->set_y(v); } + + static int get_main_len(const Layout_Item* i) { return i->w(); } + static void set_main_len(Layout_Item* i, int v) { i->set_w(v); } + + static int get_cros_len(const Layout_Item* i) { return i->h(); } + static void set_cros_len(Layout_Item* i, int v) { i->set_h(v); } + + static int main_prefix(const QMargins& m) { return m.left(); } + static int main_suffix(const QMargins& m) { return m.right(); } + static int cros_prefix(const QMargins& m) { return m.top(); } + static int cros_suffix(const QMargins& m) { return m.bottom(); } + + static int main_length(const Layout* l) { return l->w(); } + static int cros_length(const Layout* l) { return l->h(); } + }; + + struct VerticalTraits { + static int get_main(const Layout_Item* i) { return i->y(); } + static void set_main(Layout_Item* i, int v) { i->set_y(v); } + + static int get_cros(const Layout_Item* i) { return i->x(); } + static void set_cros(Layout_Item* i, int v) { i->set_x(v); } + + static int get_main_len(const Layout_Item* i) { return i->h(); } + static void set_main_len(Layout_Item* i, int v) { i->set_h(v); } + + static int get_cros_len(const Layout_Item* i) { return i->w(); } + static void set_cros_len(Layout_Item* i, int v) { i->set_w(v); } + + static int main_prefix(const QMargins& m) { return m.top(); } + static int main_suffix(const QMargins& m) { return m.bottom(); } + static int cros_prefix(const QMargins& m) { return m.left(); } + static int cros_suffix(const QMargins& m) { return m.right(); } + + static int main_length(const Layout* l) { return l->h(); } + static int cros_length(const Layout* l) { return l->w(); } + }; + + // TODO: maxsize mainsize 不知道怎么和 grow shrink 合并处理 + template + void Layout::layout_impl() { + int main_prefix = T::main_prefix(margins); + int main_suffix = T::main_suffix(margins); + int cros_prefix = T::cros_prefix(margins); + int cros_suffix = T::cros_suffix(margins); + + int main_length = T::main_length(this); + int cros_length = T::cros_length(this); + + int cros_max = cros_length - cros_prefix - cros_suffix; + int total_main = main_prefix + main_suffix; + + QVector grow_items; + QVector shrink_items; + + // ---------- 第一遍:初始化尺寸 & 收集 grow / shrink ---------- + for (auto* item : items) { + auto size = item->sizeHint(); + auto& flags = item->size_policy; + item->set_size(size); + + if (flags.testFlag(Cross_Expand)) { + T::set_cros_len(item, cros_max); + } + + if (flags.testFlag(Get_Main_By_Cross)) { + int cros_len = T::get_cros_len(item); + int main_len = item->get_main_by_cros(cros_len); + T::set_main_len(item, main_len); + } + + total_main += T::get_main_len(item); + + // 明确语义:Grow / Shrink 不允许同时生效 + if (flags.testFlag(Grow) && !flags.testFlag(Shrink)) { + grow_items.push_back(item); + } else if (flags.testFlag(Shrink)) { + shrink_items.push_back(item); + } + } + + int available = main_length - total_main; + + // ---------- Grow ---------- + if (available > 0 && !grow_items.isEmpty()) { + int count = grow_items.size(); + int each = available / count; + int remain = available % count; + + for (int i = 0; i < grow_items.size(); ++i) { + auto* item = grow_items[i]; + int delta = each + (i < remain ? 1 : 0); + int len = T::get_main_len(item); + T::set_main_len(item, len + delta); + } + + available = 0; + } + + // ---------- Shrink(按比例,修正版本) ---------- + if (available < 0 && !shrink_items.isEmpty()) { + int deficit = -available; + + int shrink_cap = 0; + for (auto* item : shrink_items) { + shrink_cap += T::get_main_len(item); + } + + if (shrink_cap > 0) { + int actual_shrink = std::min(deficit, shrink_cap); + int remain = actual_shrink; + + for (int i = 0; i < shrink_items.size(); ++i) { + auto* item = shrink_items[i]; + int cur = T::get_main_len(item); + + int delta = actual_shrink * cur / shrink_cap; + if (i == shrink_items.size() - 1) + delta = remain; // 吃掉误差 + + delta = std::min(delta, cur); + T::set_main_len(item, cur - delta); + remain -= delta; + } + + available += actual_shrink; + } + } + + // ---------- 最后一步:设置每个 item 的位置 ---------- + int main_pos = main_prefix; + for (Layout_Item * item : items) { + auto dr = item->direction; + if (dr == Direction::Default) { + dr = direction; + if (dr == Direction::Default) { + dr = Direction::Middle; + } + } + if (dr == Direction::Middle) { + int cur = T::get_cros_len(item); + auto middle = cros_prefix + (cros_length - cros_prefix - cros_suffix)/2; + auto pos = middle - cur/2; + T::set_cros(item, pos); + } else if (dr == Direction::First) { + T::set_cros(item, cros_prefix); + } else if (dr == Direction::Last) { + int cur = T::get_cros_len(item); + auto last = cros_length - cros_suffix; + auto pos = last - cur; + T::set_cros(item, pos); + } else { + std::cout << "未知的类型 " << VAR_STR_1(dr) << LOG_POS << std::endl; + std::terminate(); + } + + T::set_main(item, main_pos); + main_pos += T::get_main_len(item) + space; + } + } + + + + void Layout::layout() { + if (orientation == Qt::Horizontal) + layout_impl(); + else + layout_impl(); + } +} diff --git a/YSGraphic_Core/component/Base/Layout.h b/YSGraphic_Core/component/Base/Layout.h new file mode 100644 index 0000000..31ca2b7 --- /dev/null +++ b/YSGraphic_Core/component/Base/Layout.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include +#include "Base.h" + + +namespace Psc { + class Layout : public Layout_Item { + public: + explicit Layout(Qt::Orientation orientation = Qt::Horizontal) : orientation(orientation) {} + int space = 8; + QMargins margins = {8, 8, 8, 8}; + QVector items; + Qt::Orientation orientation = Qt::Horizontal; + void layout(); + Direction direction = Direction::Default; + protected: + template void layout_impl(); + }; + +} + + + + diff --git a/YSGraphic_Core/component/Base/global.h b/YSGraphic_Core/component/Base/global.h new file mode 100644 index 0000000..4a8d1c4 --- /dev/null +++ b/YSGraphic_Core/component/Base/global.h @@ -0,0 +1,176 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Delegate.h" +#include "Layout.h" +#include "YSGraphic_Core/component/SVG.h" + + +namespace Psc { + + + class Border : public E { + public: + int width = 4; + QPen pen = QPen(); + QColor color = Qt::red; + std::optional filled_color = QColor(200, 0, 0); + Border(){} + Border(int width, QPen pen, QColor color) : width(width), pen(pen), color(color) {} + void paint(QPainter *painter, QRect r, QPaintEvent *event) override { + pen.setColor(color); + pen.setWidth(width); + painter->setPen(pen); + // if (filled_color.has_value()) { + // painter->fillRect(r, filled_color.value()); + // } + painter->drawRect(r); + } + }; + + class Icon : public E { + public: + SVGImage_Render svg; + Icon() { + svg.set_path(":/you_jian_tou.svg").set_color(Qt::red).create(); + } + void paint(QPainter *painter, QRect r, QPaintEvent *event) override { + svg.render(painter, r); + } + [[nodiscard]] QSize sizeHint() const override { + return {20 , 20}; + } + }; + + + class Icon2 : public E { + public: + QIcon icon; + Icon2(QIcon icon) : icon(icon) { + + } + Icon2() = default; + void paint(QPainter *painter, QRect r, QPaintEvent *event) override { + icon.paint(painter, r); + } + [[nodiscard]] QSize sizeHint() const override { + return {20 , 20}; + } + }; + + // https://runebook.dev/zh/docs/qt/qtextlayout/draw + class Text : public E { + public: + QFont font; + QFontMetrics font_metrics; + QMargins margins; + QString text; + QPen pen = QPen(); + Text() : font_metrics(font) {} + [[nodiscard]] QSize sizeHint() const override { + auto w = font_metrics.horizontalAdvance(text); + auto h = font_metrics.height(); + return {w + margins.left() + margins.right(), h + margins.top() + margins.bottom()}; + } + + // TODO QTextLayout 研究一下 + void paint(QPainter *painter, QRect r, QPaintEvent *event = nullptr) override { + int x = r.left() + margins.left(); + int y = r.top() + margins.top() + font_metrics.height(); + painter->drawText(x, y, text); + + } + + }; + + + + class Button : public E { + public: + Border border; + Text text; + Icon icon; + QMargins inter_margin; + Delegate press_event; + QRect rect; + Layout layout = Layout(Qt::Horizontal); + Button() { + layout.space = 16; + text.text = "123456"; + icon.use_get_main_by_cros = true; + icon.size_policy.setFlag(Cross_Expand); + icon.size_policy.setFlag(Get_Main_By_Cross); + layout.items.append(&icon); + layout.items.append(&text); + }; + + void paint(QPainter *painter, QRect r, QPaintEvent *event) override { + border.paint(painter, r, event); + auto r_icon = icon.rect(); + auto r_text = text.rect(); + painter->setPen(QPen(Qt::blue)); + painter->drawRect(r_icon); + painter->drawRect(r_text); + icon.paint(painter, r_icon, event); + text.paint(painter, r_text, event); + } + void event(QEvent::Type t, QEvent *e) override { + if (t == QEvent::MouseButtonPress) { + QMouseEvent* me = static_cast(e); + if (rect.contains(me->pos())) { + press_event.invoke(me); + } + } + if (t == QEvent::Enter) { + border.color = Qt::blue; + update(); + } + if (t == QEvent::Leave) { + border.color = Qt::black; + update(); + } + if (t == QEvent::Resize) { + QResizeEvent* resize = static_cast(e); + layout.set_size(resize->size()); + layout.layout(); + + auto r_icon = icon.rect(); + auto r_text = text.rect(); + + //qDebug() << "resize" << r_icon << " " << r_text; + } + } + }; + + template + class W : public QWidget { + public: + E* inter; + template + W(Args&&... args) : inter(new T(std::forward(args)...)) {} + bool event(QEvent *e) override { + inter->event(e->type(), e); + if (inter->need_update) { + update(); + inter->need_update = false; + } + return QWidget::event(e); + } + void paintEvent(QPaintEvent *event) override { + QPainter painter(this); + + inter->paint(&painter, rect(), event); + } + }; +} + + + + diff --git a/YSGraphic_Core/component/Button.h b/YSGraphic_Core/component/Button.h new file mode 100644 index 0000000..9ef6e54 --- /dev/null +++ b/YSGraphic_Core/component/Button.h @@ -0,0 +1,87 @@ +#pragma once +#include "Background.h" +#include "Content.h" +#include +// #define SETTER3(class_name, Type, prop, value) \ +// Type prop; +// +// #define SETTER2(class_name, Type, prop) \ +// class_name& set_##prop(const Type& prop) { this->##prop = prop; return *this;} \ +// Type prop; +// +// #define SETTER(Type, prop, value) \ +// auto& set_##prop(const Type& prop) { this->##prop = prop; return *this;} \ +// Type prop = value; +struct Button : Base { + Q_OBJECT +public: + Background background; + Content content; + Button() { + paintAbles.append(&background); + paintAbles.append(&content); + } + [[nodiscard]] QSize sizeHint() const override { + auto ret = content.sizeHint(); + if(background.border){ + int a = background.border->len * 2; + ret += QSize(a, a); + } + return ret; + } +}; +inline Button* create_icon(const QString& path, const QColor& background, const Handle_Mouse& f) { + auto ret = new Button(); + ret->content.set_icon(path, QColor(Qt::color0)); + ret->background.set_color(background); + ret->background.mouse_handlers.append(f); + return ret; +}; + + + +struct Switch_Button : Base { + Q_OBJECT +public: + Background background; + Content content1; + Content content2; + Switch_Button() { + paintAbles.append(&background); + paintAbles.append(&content1); + paintAbles.append(&content2); + content1.show = true; + content2.show = false; + } + [[nodiscard]] QSize sizeHint() const override { + auto ret = content1.show ? content1.sizeHint() : content2.sizeHint(); + if(background.border){ + int a = background.border->len * 2; + ret += QSize(a, a); + } + return ret; + } +}; +inline Switch_Button* create_switch_icon(const QColor& background, const QString& path, const Handle_Mouse& f, + const QString& path2, const Handle_Mouse& f2) { + auto ret = new Switch_Button(); + ret->content1.set_icon(path, QColor(Qt::color0)); + ret->content2.set_icon(path2, QColor(Qt::color0)); + ret->background.set_color(background); + ret->background.mouse_handlers.append([ret, f, f2](QEvent::Type t, QMouseEvent* e) { + if (t == QEvent::MouseButtonPress) + { + if (ret->content1.show) + { + f(t, e); + } else + { + f2(t, e); + } + std::swap(ret->content1.show, ret->content2.show); + ret->update(); + } + }); + return ret; +} + diff --git a/YSGraphic_Core/component/ComboBox.h b/YSGraphic_Core/component/ComboBox.h new file mode 100644 index 0000000..59bbc16 --- /dev/null +++ b/YSGraphic_Core/component/ComboBox.h @@ -0,0 +1,215 @@ +#pragma once +#include +#include "global.h" + +class ComboBox : public QComboBox { +public: + ComboBox() { + setStyle(new Style()); + setView(new view()); + + setItemDelegate(new comboboxdelegate()); + + QWidget* container = this->findChild(); + if (container) + { + container->setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint); + container->setAttribute(Qt::WA_TranslucentBackground); + } + } + class Style : public QProxyStyle { + public: + void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter, + const QWidget* widget) const override { + // 先调用默认的绘制 + auto t = qobject_cast(widget); + if (!t) return; + if (option->type == QStyleOption::SO_FocusRect) + { + const QStyleOptionComboBox* opt = qstyleoption_cast(option); + // QProxyStyle::drawPrimitive(element, option, painter, widget); + } else + { + qDebug() << QString::fromStdString(enum_int_to_string(option->type).value()); + } + if (element == QStyle::PE_FrameFocusRect) + { + // QProxyStyle::drawPrimitive(element, option, painter, widget); + } else + { + qDebug() << "drawPrimitive == " << element; + } + } + void drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, + const QWidget* widget) const override { + auto t = qobject_cast(widget); + if (!t) return; + auto opt = qstyleoption_cast(option); + // 控制当前选项外部的边框 + if (control == QStyle::CC_ComboBox) + { + QPen pen; + painter->setPen(pen); + painter->drawRoundedRect(widget->rect().adjusted(0, 0, -1, -2), 4, 4); + // QProxyStyle::drawComplexControl(control, option, painter, widget); + } else + { + qDebug() << "lineEdit drawComplexControl " << control; + } + } + void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, + const QWidget* widget) const override { + auto t = qobject_cast(widget); + if (!t) return; + auto opt = qstyleoption_cast(option); + // 当前选择的选项 + if (element == QStyle::CE_ComboBoxLabel) + { + QProxyStyle::drawControl(element, option, painter, widget); + } else + { + qDebug() << "lineEdit drawControl " << element; + } + } + QRect subElementRect(SubElement element, const QStyleOption* option, const QWidget* widget) const override { + auto t = qobject_cast(widget); + if (!t) return QProxyStyle::subElementRect(element, option, widget); + auto opt = qstyleoption_cast(option); + QRect ret = QProxyStyle::subElementRect(element, option, widget); + // 这里可以自定义返回不同的 QRect 以控制子元素的大小或位置 + if (element == QStyle::SE_ComboBoxLayoutItem) + { + // rect.adjust(5, 5, -5, -5); // 例如,让文本内容区域缩小一点 + + // return rect.adjusted(5, 5, -5, -5); + return ret; + } else + { + qDebug() << "lineEdit subElementRect " << element; + } + return ret; + } + QRect subControlRect(ComplexControl cc, const QStyleOptionComplex* option, SubControl sc, + const QWidget* widget) const override { + auto ret = QProxyStyle::subControlRect(cc, option, sc, widget); + auto opt = qstyleoption_cast(option); + if (cc == QStyle::CC_ComboBox) + { + if (sc == SC_ComboBoxArrow) + { + // qDebug() << "This is the dropdown arrow button area: " << ret; + } else if (sc == SC_ComboBoxListBoxPopup) + { + // qDebug() << "This is the edit box area: " << ret; + } else if (sc == QStyle::SC_ScrollBarSubLine) + { + // qDebug() << "QStyle::SC_ScrollBarSubLine: " << ret; + } else if (sc == QStyle::SC_ScrollBarAddLine) + { + // qDebug() << "QStyle::SC_ScrollBarSubLine: " << ret; + } else + { + qDebug() << "Other subcontrol: " << sc; + } + } else + { + qDebug() << "QRect subControlRect ??????? : " << sc; + } + return ret; + } + }; + class view : public QListView { + public: + view(QWidget* parent = nullptr) : QListView(parent) { + // 构造函数内容 + + } + [[nodiscard]] QSize viewportSizeHint() const override{ + return QListView::viewportSizeHint(); + } + protected: + void paintEvent(QPaintEvent* e) override { + QPainter painter(this->viewport()); + auto s = this->viewport()->objectName(); + + // 绘制背景色 +// painter.setBrush(Qt::blue); +// painter.drawRoundedRect(rect(), 4, 4); // 使用白色填充整个视图区域 + + // 调用基类的 paintEvent 以确保默认绘制行为 + QListView::paintEvent(e); + } + }; + + class comboboxdelegate : public QStyledItemDelegate { + public: + explicit comboboxdelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} + ~comboboxdelegate() {} + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { + QStyledItemDelegate::paint(painter, option, index); + // if (index.isValid()) + // { + // painter->save(); + // QString name = index.data(Qt::DisplayRole).toString(); + // QVariant icondata = index.data(Qt::DecorationRole); + // QIcon icon = icondata.value(); + // QString number = index.data(Qt::UserRole).toString(); + // bool isonline = index.data(Qt::UserRole + 1).toBool(); + // qDebug() << name << number << isonline; + // QRectF rect; + // rect.setX(option.rect.x()); + // rect.setY(option.rect.y()); + // rect.setWidth(option.rect.width() - 1); + // rect.setHeight(option.rect.height() - 1); + // QPainterPath path; + // path.moveTo(rect.topRight() - QPointF(radius, 0)); + // path.lineTo(rect.topLeft() + QPointF(radius, 0)); + // path.quadTo(rect.topLeft(), rect.topLeft() + QPointF(0, radius)); + // path.lineTo(rect.bottomLeft() + QPointF(0, -radius)); + // path.quadTo(rect.bottomLeft(), rect.bottomLeft() + QPointF(radius, 0)); + // path.lineTo(rect.bottomRight() - QPointF(radius, 0)); + // path.quadTo(rect.bottomRight(), rect.bottomRight() + QPointF(0, -radius)); + // path.lineTo(rect.topRight() + QPointF(0, radius)); + // path.quadTo(rect.topRight(), rect.topRight() + QPointF(-radius, -0)); + // if (option.state.testFlag(QStyle::State_Selected)) + // { + // painter->setPen(QPen(Qt::blue)); + // painter->setBrush(QColor(0, 255, 127)); + // painter->drawPath(path); + // } else if (option.state.testFlag(QStyle::State_Raised)) + // { + // painter->setPen(QPen(Qt::green)); + // painter->setBrush(QColor(0, 127, 255)); + // painter->drawPath(path); + // } else + // { + // painter->setPen(QPen(Qt::gray)); + // painter->setBrush(QColor(255, 255, 255)); + // painter->drawPath(path); + // } + // QRect iconRect = QRect(rect.left() + 3, rect.top() + 3, 30, 30); + // QRect nameRect = QRect(iconRect.right() + 5, rect.top() + 3, rect.width() - 80, 20); + // QRect circle = QRect(nameRect.right() + 5, nameRect.top(), 10, 10); + // QRect numberRect = QRect(nameRect.x(), nameRect.y() + 20, 100, 20); + // qDebug() << iconRect << nameRect << circle << numberRect; + // painter->drawEllipse(circle); + // painter->setPen(QPen(QColor(0, 0, 0))); + // painter->setFont(QFont("微软雅黑", 9, QFont::Bold)); + // painter->drawText(nameRect, Qt::AlignLeft, name); + // painter->drawText(numberRect, Qt::AlignLeft, number); + // painter->drawPixmap(iconRect, icon.pixmap(iconRect.size())); + // painter->restore(); + // } else + // { + // qDebug() << "index is vaild?"; + // } + } + [[nodiscard]] QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override { + Q_UNUSED(option) + Q_UNUSED(index) + return QStyledItemDelegate::sizeHint(option, index); + } + private: + qreal radius = 8; + }; +}; diff --git a/YSGraphic_Core/component/Content.h b/YSGraphic_Core/component/Content.h new file mode 100644 index 0000000..b7a9bd7 --- /dev/null +++ b/YSGraphic_Core/component/Content.h @@ -0,0 +1,85 @@ +#pragma once +#include +#include +#include "global.h" +#include "SVG.h" + +struct Text { + QFont font; + QPen pen; + QString text; + + Text(QString text, QPen pen = QPen(), const QFont& font = QFont()) + : + text(std::move(text)), + pen(std::move(pen)), + font(font) + { + + } +}; + + + +// 水平布局 +struct Content : PaintAble { + Text* text{}; + SVGImage_Render* icon{}; + Layout layout; + enum Plottable { + Plottable_Icon, + Plottable_Text, + }; + [[nodiscard]] QSize sizeHint() const override { + QSize ret{0, 0}; + ret += QSize{layout.prefix + layout.suffix, layout.second_prefix + layout.second_suffix}; + if(text){ + QFontMetrics fm(text->font); + ret += QSize {fm.horizontalAdvance(text->text), fm.height()}; + } + return ret; + } + void set_icon(const QString& icon_path, const QColor& icon_color) { + icon = new SVGImage_Render(); + icon->set_path(icon_path).set_color(icon_color).create(); + } + Content() { + layout.set_margin(4); + layout.main_type = Layout::Center_Space; + layout.second_type = Layout::Center_Space; + } + Event_Type resize_event(QResizeEvent* e) override { + layout.infos.clear(); + int a = qMin(e->size().width(), e->size().height()); + if (icon) { + Layout_Info t(Plottable_Icon, Layout_Item_Type::Fixed, Layout_Item_Type2::Fixed, a, a); + layout.infos.append(t); + } + if (text) { + QFontMetrics fm(text->font); + int len = fm.horizontalAdvance(text->text); + Layout_Info t(Plottable_Text, Layout_Item_Type::Fixed, Layout_Item_Type2::Expand, len, 0); + layout.infos.append(t); + } + layout.cacl(e->size().width(), e->size().height()); + return Event_Type::refresh; + } + void paint(QPainter* painter, QRect rect) override { + painter->save(); + if (icon) { + auto pos = layout.get(Plottable_Icon).value(); + QRect r = pos.h_rect(); + // painter->drawRect(r); + icon->render(painter, r); + } + if (text) { + painter->setPen(text->pen); + painter->setFont(text->font); + auto pos = layout.get(Plottable_Text).value(); + QRect r = pos.h_rect(); + //painter->drawRect(r); + painter->drawText(r, Qt::AlignCenter, text->text); + } + painter->restore(); + } +}; diff --git a/YSGraphic_Core/component/Input.h b/YSGraphic_Core/component/Input.h new file mode 100644 index 0000000..b76a866 --- /dev/null +++ b/YSGraphic_Core/component/Input.h @@ -0,0 +1,134 @@ +#pragma once +#include "global.h" + +// https://blog.csdn.net/MrHHHHHH/article/details/134182253 +class Input : public QLineEdit { +public: + explicit Input(QWidget* parent = nullptr) : QLineEdit(parent) { + setStyle(new Style()); + setFrame(true); + // 当光标的位置在文本编辑器中改变时,这个信号会被触发。old和new参数分别表示光标改变前后的位置。 + connect(this, &QLineEdit::cursorPositionChanged, this, [=](int nOld, int nNew){ + //qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "nOld :" << nOld << "nNew :" << nNew; + }); + // 当用户完成文本编辑(例如按下Enter键或点击其他区域使编辑结束)时,这个信号会被触发。 + connect(this, &QLineEdit::editingFinished, this, [=](){ + //qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "text :" << this->text(); + }); + // 当用户在文本编辑器中按下Enter键时,这个信号会被触发。 + connect(this, &QLineEdit::returnPressed, this, [=](){ + //qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "text :" << this->text(); + }); + // 当文本编辑器中的选区发生变化时,这个信号会被触发。例如,当用户选择或取消选择文本时。 + connect(this, &QLineEdit::selectionChanged, this, [=](){ + //qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "text :" << this->text(); + }); + // 当文本编辑器中的文本发生更改时,这个信号会被触发。参数text是改变后的文本内容。 + connect(this, &QLineEdit::textChanged, this, [=](const QString& text){ + // qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "text :" << text; + }); + // 当文本编辑器开始编辑新的文本时,这个信号会被触发。参数text是当前编辑的文本内容。 + connect(this, &QLineEdit::textEdited, this, [=](const QString& text){ + //qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "text :" << text; + }); + // update(); + } + QString unit = "MHZ"; + QFont font; + QPen pen; + QColor focus_color = Qt::red; + QColor default_color = Qt::black; +protected: + void paintEvent(QPaintEvent* event) override { + QLineEdit::paintEvent(event); // 调用原始绘制 + auto fm = QFontMetrics(font); + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); + int w = width(); + int h = height(); + int pen_w = 1; + if (QApplication::focusWidget() == this) + { + painter.setPen(QPen(focus_color, pen_w)); + } else + { + painter.setPen(QPen(default_color, pen_w)); + } + painter.drawRoundedRect(rect(), h/4, h/4); + painter.setPen(pen); + painter.setFont(font); + int unitWidth = fm.horizontalAdvance(unit); + int th = fm.height(); + int x = rect().right() - unitWidth - 5; // 计算单位的绘制位置 + painter.drawText(x, height() / 2 + th / 2, unit); + setTextMargins(0, 0, unitWidth + 4, 0); // 右边留出单位的空间 + } + class Style : public QProxyStyle { + public: + void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption* option, QPainter* painter, + const QWidget* widget) const override { + // 先调用默认的绘制 + auto t = qobject_cast(widget); + if (!t) return; + static bool it = true; + // QProxyStyle::drawPrimitive(element, option, painter, widget); + // qDebug() << "t->hasFrame() == " << t->hasFrame(); + if (it) + { + // QProxyStyle::drawPrimitive(element, option, painter, widget); + it = false; + } + // 行编辑器边框 + if (element == PE_PanelLineEdit) + { + // + } + // 行编辑器边框 + if (element == PE_FrameLineEdit) + { +// qDebug() << "lineEdit drawPrimitive " << element; +// qDebug() << "width " << option->rect.width(); + } + } + void drawComplexControl(ComplexControl control, const QStyleOptionComplex* option, QPainter* painter, + const QWidget* widget) const override { + // auto t = qobject_cast(widget); + // if (!t) return; + // qDebug() << "lineEdit drawComplexControl " << control; + // QProxyStyle::drawComplexControl(control, option, painter, widget); + } + void drawControl(ControlElement element, const QStyleOption* option, QPainter* painter, + const QWidget* widget) const override { + // auto t = qobject_cast(widget); + // qDebug() << "lineEdit drawControl " << element; + // QProxyStyle::drawControl(element, option, painter, widget); + // if (!t) return; + } + // void drawItemText(QPainter* painter, const QRect& rect, int flags, const QPalette& pal, bool enabled, + // const QString& text, QPalette::ColorRole textRole) const override { + // + // + // } + // void drawItemPixmap(QPainter* painter, const QRect& rect, int alignment, const QPixmap& pixmap) const + // override { + // + // } + QRect subElementRect(SubElement element, const QStyleOption* option, const QWidget* widget) const override { + auto t = qobject_cast(widget); + if (!t) + { + return QProxyStyle::subElementRect(element, option, widget); + } + // qDebug() << "lineEdit subElementRect " << element; + // 这里可以自定义返回不同的 QRect 以控制子元素的大小或位置 + if (element == SE_LineEditContents) + { + QRect rect = QProxyStyle::subElementRect(element, option, widget); + // rect.adjust(5, 5, -5, -5); // 例如,让文本内容区域缩小一点 + return rect; + } + return QProxyStyle::subElementRect(element, option, widget); + } + }; +}; + diff --git a/YSGraphic_Core/component/SVG.cpp b/YSGraphic_Core/component/SVG.cpp new file mode 100644 index 0000000..089fa73 --- /dev/null +++ b/YSGraphic_Core/component/SVG.cpp @@ -0,0 +1,62 @@ +#include "SVG.h" +#include +#include +#include +#include +#include +#include "../base/Global.h" +void SVGImage_Render::render(QPainter* painter, const QRectF& rect) { + render(painter, rect, _color); +} + +void SVGImage_Render::render(QPainter *painter) { + QSvgRenderer renderer(change_color(_color)); + renderer.render(painter); +} + +void SVGImage_Render::render(QPainter* painter, const QRectF& rect, const QColor& color) { + QSvgRenderer renderer(change_color(color)); + renderer.render(painter, rect); +} + +QImage SVGImage_Render::to_image(QSize size, const QColor& color) { + QImage image(size, QImage::Format_ARGB32); + image.fill(Qt::transparent); + QPainter painter(&image); + QSvgRenderer renderer(change_color(color)); + renderer.render(&painter, QRectF(QPointF(0, 0), QSizeF(size))); + return image; +} + +QPixmap SVGImage_Render::to_pixmap(QSize size, const QColor& color) { + return QPixmap::fromImage(to_image(size, color)); +} + +QByteArray SVGImage_Render::change_color(const QColor& color) { + QDomElement root = doc.documentElement(); + auto text = color.name(QColor::HexRgb).toUpper(); + root.setAttribute("fill", text); + if (color.alpha() != 255) { + root.setAttribute("fill-opacity", QString::number(color.alphaF())); + } + QByteArray ret; + QTextStream outStream(&ret); + doc.save(outStream, 4); + return ret; +} + +SVGImage_Render& SVGImage_Render::create() { + QFile file(_path); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + qWarning() << VAR_QSTR_1(_path) + "无法打开! " + QLOG_POS; + return *this; + } + QTextStream in(&file); + QString svgContent = in.readAll(); + file.close(); + doc.setContent(svgContent); + return *this; +} + + diff --git a/YSGraphic_Core/component/SVG.h b/YSGraphic_Core/component/SVG.h new file mode 100644 index 0000000..ada2a04 --- /dev/null +++ b/YSGraphic_Core/component/SVG.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include + + + +struct SVGImage_Render { + SVGImage_Render& set_path(const QString& path) { this->_path = path; return *this;} + SVGImage_Render& set_color(const QColor& color) {this->_color = color; return *this;} + SVGImage_Render& create(); + void render(QPainter* painter); + void render(QPainter* painter, const QRectF& rect); + void render(QPainter* painter, const QRectF& rect, const QColor& color); + QImage to_image(QSize size, const QColor& c); + QPixmap to_pixmap(QSize size, const QColor& c); + const QColor& get_color() {return _color;}; +protected: + QString _path; + QColor _color = QColor(Qt::red); + QDomDocument doc; + QByteArray change_color(const QColor& color); +}; + diff --git a/YSGraphic_Core/component/Table/Base_Info.cpp b/YSGraphic_Core/component/Table/Base_Info.cpp new file mode 100644 index 0000000..762b301 --- /dev/null +++ b/YSGraphic_Core/component/Table/Base_Info.cpp @@ -0,0 +1,16 @@ +#include "Base_Info.h" + +#include "YSGraphic_Core/GenerateMockData.h" +#include "YSGraphic_Core/GlobalTypes.h" + +namespace Psc { + Base_Info::Base_Info(int len, QString content) : len(len), content(std::move(content)) { + YSG::Range r{155, 255}; + QColor random_color(YSG::getData(r), YSG::getData(r), YSG::getData(r)); + background_color = random_color; + } + void Base_Info::paint(QPainter* painter, QRect rect) { + painter->fillRect(rect, background_color); + painter->drawText(rect, Qt::AlignCenter, content); + } +} diff --git a/YSGraphic_Core/component/Table/Base_Info.h b/YSGraphic_Core/component/Table/Base_Info.h new file mode 100644 index 0000000..4817cfc --- /dev/null +++ b/YSGraphic_Core/component/Table/Base_Info.h @@ -0,0 +1,25 @@ +#pragma once +#include "global.h" +namespace Psc { + struct Base_Info { + virtual ~Base_Info() = default; + int len; + QString content{}; + bool hide = false; + QColor background_color; + explicit Base_Info(int len, QString content); + [[nodiscard]] QString to_string() const { return QString("w:%1, content:%2").arg(len).arg(content); } + virtual void paint(QPainter *painter, QRect rect); + }; + + struct Row_Info : Base_Info { + explicit Row_Info(int h, const QString& content) : Base_Info(h, content) {} + int h() { return len; } + QString to_string() { return QString("h:%1, content:%2").arg(len).arg(content); } + }; + struct Col_Info : Base_Info { + explicit Col_Info(int w, const QString& content) : Base_Info(w, content) {} + int w() { return len; } + }; +} + diff --git a/YSGraphic_Core/component/Table/Base_Table_Data.cpp b/YSGraphic_Core/component/Table/Base_Table_Data.cpp new file mode 100644 index 0000000..e9af3b0 --- /dev/null +++ b/YSGraphic_Core/component/Table/Base_Table_Data.cpp @@ -0,0 +1,63 @@ +#include "Base_Table_Data.h" +#include "Table_View.h" +namespace Psc { + + + + Base_Table_Data::Base_Table_Data() { + parent = this; + } + + Table_Paint_Data::Table_Paint_Data(QIcon ic, QString t) { + YSG::Range r{155, 255}; + QColor random_color(YSG::getData(r), YSG::getData(r), YSG::getData(r)); + background_color = random_color; + icon = Icon2(ic); + text.text = t; + layout.items.append(&icon.value()); + layout.items.append(&text); + } + Table_Paint_Data::Table_Paint_Data(QString t) { + YSG::Range r{155, 255}; + QColor random_color(YSG::getData(r), YSG::getData(r), YSG::getData(r)); + background_color = random_color; + text.text = t; + layout.items.append(&text); + } + + + + void Table_Paint_Data::update(bool show, Table_View* view, QPainter* painter, QRect rect) { + painter->fillRect(rect, background_color); + layout.set_x(rect.left()); + layout.set_y(rect.top()); + layout.set_w(rect.width()); + layout.set_h(rect.height()); + layout.layout(); + text.paint(painter, text.rect(), nullptr); + if (icon.has_value()) { + icon.value().paint(painter, icon.value().rect(), nullptr); + } + // painter->drawText(text.rect(), Qt::AlignCenter, text.text); + } + + + void Widget_Data::update(bool show, Table_View* view, QPainter* painter, QRect rect) { + // auto tl = view->to_inside_pos(rect.topLeft()); + // auto br = view->to_inside_pos(rect.bottomRight()); + auto tl = rect.topLeft() - view->get_view_rect().topLeft(); + auto br = tl + QPoint(rect.width(), rect.height()); + QRect r(tl, br); + //qDebug() << r << "\n"; + if (show) { + widget->setParent(view->container); + widget->show(); + widget->installEventFilter(view); + widget->setGeometry(r); + } else { + widget->setParent(nullptr); + widget->hide(); + widget->removeEventFilter(view); + } + } +} diff --git a/YSGraphic_Core/component/Table/Base_Table_Data.h b/YSGraphic_Core/component/Table/Base_Table_Data.h new file mode 100644 index 0000000..29da9a4 --- /dev/null +++ b/YSGraphic_Core/component/Table/Base_Table_Data.h @@ -0,0 +1,39 @@ +#pragma once + +#include "global.h" +#include "YSGraphic_Core/component/Base/global.h" +#include "YSGraphic_Core/component/Base/Layout.h" + + +namespace Psc { + class Icon2; + class Table_View; + + struct Table_Edit_Able { + QString content; + }; + + struct Base_Table_Data { + virtual ~Base_Table_Data() = default; + Base_Table_Data* parent{}; + Base_Table_Data(); + virtual void update(bool show, Table_View* view, QPainter* painter, QRect rect){}; + }; + + struct Table_Paint_Data : Base_Table_Data, Table_Edit_Able { + QColor background_color; + Table_Paint_Data(QIcon icon, QString text); + Table_Paint_Data(QString text); + std::optional icon; + Text text; + Layout layout; + void update(bool show, Table_View* view, QPainter* painter, QRect rect) override; + }; + + struct Widget_Data : Base_Table_Data { + QWidget* widget{}; + void update(bool show, Table_View* view, QPainter* painter, QRect rect) override; + }; +} + + diff --git a/YSGraphic_Core/component/Table/Base_Table_Model.cpp b/YSGraphic_Core/component/Table/Base_Table_Model.cpp new file mode 100644 index 0000000..86ddf98 --- /dev/null +++ b/YSGraphic_Core/component/Table/Base_Table_Model.cpp @@ -0,0 +1,339 @@ +#include "Base_Table_Model.h" +#include "../../GenerateMockData.h" +#include "../../GlobalTypes.h" +#include "../../base/Global.h" +#include "Base_Info.h" +#include "Base_Table_Data.h" +namespace Psc { + struct Row_Info; + Pos get_pos(Base_Info** infos, int n, int space, int pos, int margin) { + int cur_line_pos = margin; + int last_i = 0; + for (int i = 0; i < n - 1; ++i) + { + auto cur = infos[i]; + if (cur->hide) + { + continue; + } + int step = cur->len + space; + if (cur_line_pos + step > pos) + { + return {i, cur_line_pos, pos - cur_line_pos}; + } + cur_line_pos += step; + last_i = i; + } + return {std::max(0, n - 1), cur_line_pos, pos - cur_line_pos}; + } + + + Base_Table_Model::Base_Table_Model() {} + Row_Info* Base_Table_Model::create_row_info(int h) { + return new Row_Info(h, ""); + } + Col_Info* Base_Table_Model::create_col_info(int w) { + return new Col_Info(w, ""); + } + // 核心位置计算 + int Memory_Table_Model::cell_x(int index) { + if (index == 0) return 0; + int ret = 0; + for (int i = 0; i < index; i++) + { + auto cur = col_info_list[i]; + if (cur->hide) continue; + ret += cur->len + col_space; + } + return ret; + } + + + int Memory_Table_Model::cell_y(int index) { + if (index == 0) return 0; + int ret = 0; + for (int i = 0; i < index; i++) + { + auto cur = row_info_list[i]; + if (cur->hide) continue; + ret += cur->len + row_space; + } + return ret; + } + + + int Memory_Table_Model::row_span_len(int row, int row_span) { + int ret = -row_space; + for (int i = 0; i < row_span; i++) + { + auto cur = row_info_list[row + i]; + if (cur->hide) continue; + ret += cur->len + row_space; + } + return ret; + } + int Memory_Table_Model::col_span_len(int col, int col_span) { + int ret = -col_space; + for (int i = 0; i < col_span; i++) + { + auto cur = col_info_list[col + i]; + if (cur->hide) continue; + ret += cur->len + col_space; + } + return ret; + } + + Pos Memory_Table_Model::get_col(int x) { + return get_pos((Base_Info**)col_info_list.data(), col_info_list.size(), col_space, x, 0); + } + + + Pos Memory_Table_Model::get_row(int y) { + return get_pos((Base_Info**)row_info_list.data(), row_info_list.size(), row_space, y, 0); + } + + + + QVector> exchange_row_col(const QVector>& data) { + QVector> ret; + int m = data.size(); + int n = data.first().size(); + for (int i = 0; i < n; ++i) { + QVector t; + for (int j = 0; j < m; ++j) { + t.push_back(data[j][i]); + } + ret.push_back(t); + } + return ret; + }; + + + + enum Cell_Type { + Merge_head, + Merge_Cell, + Normal + }; + + + Cell_Type cell_type(const QVector>& data, int col, int row) { + auto that = data[col][row]; + if (that->parent != that) return Merge_Cell; + if (data[col + 1][row]->parent == that) return Merge_head; + if (data[col][row + 1]->parent == that) return Merge_head; + return Normal; + } + + Cell_Info Base_Table_Model::get_span(int xi, int yi) { + return Cell_Info(xi, yi, col_span_len(xi, 1), row_span_len(yi, 1)); + } + Find_Ret Base_Table_Model::find_parent(Base_Table_Data *parent, int xi, int yi) { + return Find_Ret(xi, yi); + } + + + void Memory_Table_Model::split(int row, int col, int row_span, int col_span) { + for (int i = 0; i < col_span; ++i) { + for (int j = 0; j < row_span; ++j) { + auto cur = data[col + i][row + j]; + cur->parent = cur; + } + } + } + void Memory_Table_Model::merge(int row, int col, int row_span, int col_span) { + auto start = data[col][row]; + for (int i = 0; i < row_span; ++i) { + for (int j = 0; j < col_span; ++j) { + if (i == 0 && j == 0) continue; + int cur_row = row + i; + int cur_col= col + j; + auto cur = data[cur_col][cur_row]; + cur->parent = start; + } + } + } + + // parent 是复刻他上一行的parent + bool Memory_Table_Model::insert_row(int pos, const QVector& infos, const QVector>& d, Edge_Expand_Type t) { + int col_num = col_info_list.size(); + int insert_row_num = infos.size(); + if (!d.empty() && d.first().size() != col_num) { + qDebug() << VAR_QSTR_2(d.first().size(), col_num) << " " << QLOG_POS; + return false; + } + // 行结构转列结构 + auto insert_col_list = exchange_row_col(d); + for (int col = 0; col < col_num; ++col) { + Base_Table_Data* prev = nullptr; + Base_Table_Data* next = nullptr; + Base_Table_Data* par = nullptr; + int row_num = total_row(); + if (pos - 1 > 0 && pos - 1 < row_num) { + prev = data[col][pos - 1]; + } + if (pos < row_num) { + next = data[col][pos]; + } + if (t == Edge_Expand_Type::Positive) { + if (prev && prev->parent != prev) { + par = prev->parent; + } + } else if (t == Edge_Expand_Type::Negative) { + if (next && next->parent != next) { + par = next->parent; + } + } else if (t == Edge_Expand_Type::None) { + if (next && prev && prev->parent == next->parent) { + par = prev->parent; + } + } + for (int j = 0; j < insert_row_num; ++j) { + auto cur = insert_col_list[col][j]; + cur->parent = par ? par : cur; + } + data[col] = data[col].mid(0, pos) + insert_col_list[col] + data[col].mid(pos); + } + // 添加结构信息 + row_info_list = row_info_list.mid(0, pos) + infos + row_info_list.mid(pos); + return true; + } + + + bool Memory_Table_Model::insert_col(int pos, const QVector& infos, const QVector>& d, Edge_Expand_Type t) { + int row_num = row_info_list.size(); + int insert_col_num = infos.size(); + if (!d.empty() && d.first().size() != row_num) { + qDebug() << VAR_QSTR_2(d.first().size(), row_num) << " " << QLOG_POS; + return false; + } + for (int row = 0; row < row_num; ++row) { + Base_Table_Data* prev = nullptr; + Base_Table_Data* next = nullptr; + Base_Table_Data* par = nullptr; + int col_num = total_col(); + if (pos - 1 > 0 && pos - 1 < col_num) { + prev = data[pos - 1][row]; + } + if (pos < col_num) { + next = data[pos][row]; + } + if (t == Edge_Expand_Type::Positive) { + if (prev && prev->parent != prev) { + par = prev->parent; + } + } else if (t == Edge_Expand_Type::Negative) { + if (next && next->parent != next) { + par = next->parent; + } + } else if (t == Edge_Expand_Type::None) { + if (next && prev && prev->parent == next->parent) { + par = prev->parent; + } + } + for (int col = 0; col < insert_col_num; ++col) { + auto cur = d[col][row]; + cur->parent = par ? par : cur; + } + } + data = data.mid(0, pos) + d + data.mid(pos); + // 添加结构信息 + col_info_list = col_info_list.mid(0, pos) + infos + col_info_list.mid(pos); + return true; + } + + bool Memory_Table_Model::remove_row(int pos, int num) { + if (pos + num >= row_info_list.size()) { + qDebug() << VAR_QSTR_3(pos, num, row_info_list.size()) << " " << QLOG_POS; + return false; + } + int n = row_info_list.size(); + for (int i = 0; i < n; ++i) { + auto& col = data[i]; + for (auto it : col.mid(pos, num)) { + delete it; + } + col.erase(col.begin() + pos, col.begin() + pos + num); + } + for (int i = 0; i < num; ++i) { + delete row_info_list[pos + i]; + } + row_info_list.erase(row_info_list.begin() + pos, row_info_list.begin() + pos + num); + return true; + } + + bool Memory_Table_Model::remove_col(int pos, int num) { + if (pos + num >= col_info_list.size()) { + qDebug() << VAR_QSTR_3(pos, num, col_info_list.size()) << " " << QLOG_POS; + return false; + } + for (auto& it : data.mid(pos, num)) { + for (auto t : it) { + delete t; + } + } + data.erase(data.begin() + pos, data.end() + pos + num); + for (int i = 0; i < num; ++i) { + delete col_info_list[pos + i]; + } + col_info_list.erase(col_info_list.begin() + pos, col_info_list.begin() + pos + num); + return true; + } + + Cell_Info Memory_Table_Model::get_span(int xi, int yi) { + int col_num = data.size(); + int row_num = data.first().size(); + Base_Table_Data* that = data[xi][yi]; + int col_span = 1; + int row_span = 1; + int sw = col_info_list[xi]->len; + int sh = row_info_list[yi]->len; + while (true) { + int index = xi + col_span; + if (index >= col_num) break; + auto cur = data[index][yi]; + if (cur->parent != that) { + break; + } + sw += col_info_list[index]->len + col_space; + col_span++; + } + while (true) { + int index = yi + row_span; + if (index >= row_num) break; + auto cur = data[xi][index]; + if (cur->parent != that) { + break; + } + sh += row_info_list[index]->len + row_space; + row_span++; + } + return {col_span, row_span, sw, sh}; + } + Find_Ret Memory_Table_Model::find_parent(Base_Table_Data* parent, int xi, int yi) { + int cur_x = xi; + int cur_y = yi; + if (xi != 0){ + while (true) { + auto cur = data[cur_x - 1][yi]; + if (cur->parent != parent && cur->parent != nullptr) { + break; + } + cur_x--; + if (cur_x == 0) break; + } + } + if (yi != 0){ + while (true) { + auto cur = data[xi][cur_y -1]; + if (cur->parent != parent && cur->parent != nullptr) { + break; + } + cur_y--; + if (cur_y == 0) break; + } + } + //qDebug() << "find_parent: " << data[xi][yi]->content << " " << cur_x << " " << cur_y; + return {cur_x, cur_y}; + } +} \ No newline at end of file diff --git a/YSGraphic_Core/component/Table/Base_Table_Model.h b/YSGraphic_Core/component/Table/Base_Table_Model.h new file mode 100644 index 0000000..ab81d4c --- /dev/null +++ b/YSGraphic_Core/component/Table/Base_Table_Model.h @@ -0,0 +1,92 @@ +#pragma once +#include "global.h" +namespace Psc { + // 主要包含 行列位置的核心位置计算 + + // using T_Data = std::shared_ptr; + + struct Edit_Func { + virtual ~Edit_Func() = default; + virtual bool insert_row(int pos, const QVector& infos, const QVector>& data, Edge_Expand_Type t) { + return true; + } + virtual bool insert_col(int pos, const QVector& infos, const QVector>& data, Edge_Expand_Type t) { + return true; + } + virtual bool remove_row(int pos, int num) { + return true; + } + virtual bool remove_col(int pos, int num) { + return true; + } + virtual void merge(int row, int col, int row_span, int col_span) { + + } + virtual void split(int row, int col, int row_span, int col_span) { + + } + }; + + + struct Base_Table_Model { + virtual ~Base_Table_Model() = default; + Base_Table_Model(); + int width() { return col_span_len(0, total_col()); } + int height() { return row_span_len(0, total_row()); } + // 重载这两个函数可以实现使用自己自定义的 Row_Info Col_Info + virtual Row_Info* create_row_info(int h); + virtual Col_Info* create_col_info(int w); + virtual Base_Table_Data* get_table_data(int col, int row) = 0; + virtual Col_Info* get_col_info(int pos) = 0; + virtual Row_Info* get_row_info(int pos) = 0; + // 表格绝对位置计算函数 + virtual Pos get_col(int x) = 0; + virtual Pos get_row(int y) = 0; + virtual int cell_x(int index) = 0; + virtual int cell_y(int index) = 0; + virtual int total_row() = 0; + virtual int total_col() = 0; + virtual int row_span_len(int row, int row_span) = 0; + virtual int col_span_len(int col, int col_span) = 0; + virtual Cell_Info get_span(int xi, int yi); // 获取某一位置,隶属于哪一个大格子(合并单元格场景下) + virtual Find_Ret find_parent(Base_Table_Data* parent, int xi, int yi); // 从xi,yi位置寻找到其隶属于的大格子(合并单元格场景下) + int col_space = 14; + int row_space = 14; + int col_min_w = 8; + int row_min_h = 8; + }; + Pos get_pos(Base_Info** infos, int n, int space, int pos, int margin); + + + // 直接把数据放在内存里的模型 + struct Memory_Table_Model : Base_Table_Model, Edit_Func { + void split(int row, int col, int row_span, int col_span) override; + void merge(int row, int col, int row_span, int col_span) override; + bool insert_row(int pos, const QVector& infos, const QVector>& data, Edge_Expand_Type) override; + bool insert_col(int pos, const QVector& infos, const QVector>& data, Edge_Expand_Type) override; + bool remove_row(int pos, int num) override; + bool remove_col(int pos, int num) override; + // 表格区域绝对位置计算 + Pos get_col(int x) override; + Pos get_row(int y) override; + int cell_x(int index) override; + int cell_y(int index) override; + int total_row() override { return row_info_list.size(); } + int total_col() override { return col_info_list.size(); } + int row_span_len(int row, int row_span) override; + int col_span_len(int col, int col_span) override; + Cell_Info get_span(int xi, int yi) override; + Find_Ret find_parent(Base_Table_Data* parent, int xi, int yi) override; + Row_Info* get_row_info(int pos) override { return row_info_list[pos]; } + Col_Info* get_col_info(int pos) override { return col_info_list[pos]; } + Base_Table_Data* get_table_data(int col, int row) override { return data[col][row]; } + protected: + QVector col_info_list; + QVector row_info_list; + QVector> data; + friend Table_View* create_Table_View(); + friend Table_View* create_Table_View2(); + }; +} + + diff --git a/YSGraphic_Core/component/Table/Table_View.cpp b/YSGraphic_Core/component/Table/Table_View.cpp new file mode 100644 index 0000000..8de8e0b --- /dev/null +++ b/YSGraphic_Core/component/Table/Table_View.cpp @@ -0,0 +1,565 @@ +#include "Table_View.h" +#include +#include +#include "../../GenerateMockData.h" +#include "Base_Table_Data.h" +#include "Base_Info.h" + +namespace Psc { + /* + 我的表格是一个存粹的painter 实现 + + 这样的话 我任何东西都可以 通过 虚函数去实现 + + 我可以虚函数 给这些所有 + + + + 视觉上元素的拓展无非就是 位置,如何绘制,允许传来自定义参数, 允许嵌入自己的组件 + 所以我每一个视觉上的组件都要有这个信息 + + 那么元素上的事件处理呢? 无非是鼠标 键盘 + + + */ + QPoint Table_View::to_inside_pos(QPoint widget_pos) { + auto rect = get_view_rect(); + return widget_pos + this->_view_pos - QPoint(rect.left(), rect.top()); + } + QPoint Table_View::to_widget_pos(QPoint inside_pos) { + auto rect = get_view_rect(); + return inside_pos - this->_view_pos + QPoint(rect.left(), rect.top()); + } + QRect Table_View::get_view_rect() { + int left = row_info_w + row_info_inside_space + left_margin ; + int top = col_info_h + col_info_inside_space + top_margin; + int right = (v_bar.show ? static_cast(v_bar.w) : 0) + right_margin; + int bottom = (h_bar.show ? static_cast(h_bar.h) : 0) + bottom_margin; + QRect all_view_rect = QRect(left, top, width() - left - right, height() - top - bottom); + return all_view_rect; + } + + Table_View::Table_View() { + setAttribute(Qt::WA_Hover); + setMouseTracking(true); + edit = new QTextEdit(); + container = new Container(this); + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + } + + + void Table_View::resizeEvent(QResizeEvent* event) { + QRect vr = get_view_rect(); + h_bar.start = vr.x(); + h_bar.resize(vr.width(), vr.width(), model->width()); + v_bar.start = vr.y(); + v_bar.resize(vr.height(), vr.height(), model->height()); + container->setGeometry(vr); + QWidget::resizeEvent(event); + } + + bool Table_View::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::Enter) { + setCursor(Qt::ArrowCursor); + } + return QObject::eventFilter(obj, event); + } + + void Table_View::paintEvent(QPaintEvent* event) { + static QImage img(":/jian_hao.png"); + static QImage img2(":/jia_hao.png"); + static int space = 4; + QPainter painter(this); + // 视图位置转换为绝对位置 + QRect cells_widget_rect = get_view_rect(); + QPen pen(Qt::black, 1); + painter.setPen(pen); + painter.drawRect(cells_widget_rect.adjusted(-1, -1, 0, 0)); + auto inside_sta_pos = to_inside_pos(cells_widget_rect.topLeft()); + auto inside_end_pos = to_inside_pos(cells_widget_rect.bottomRight()); + Pos x_sta = model->get_col(inside_sta_pos.rx()); + Pos y_sta = model->get_row(inside_sta_pos.ry()); + Pos x_end = model->get_col(inside_end_pos.rx()); + Pos y_end = model->get_row(inside_end_pos.ry()); + struct Paint_Cache { + int cell_abs_pos; + int index; + Base_Info* info; + }; + // 计算横着每个格子起点的绝对位置 + int xsi = x_sta.index; + int max_col_num = std::min(x_end.index - x_sta.index + 1, model->total_col()); + int col_num = 0; + QVector xs; + { + int cur_x = x_sta.cell_pos; + for (int i = 0; i < max_col_num; ++i) + { + auto cur = model->get_col_info(xsi + i); + if (cur->hide) continue; + xs.push_back({cur_x, i, cur}); + cur_x += cur->len + model->col_space; + } + col_num = xs.size(); + } + // 计算竖着每个格子起点的绝对位置 + int ysi = y_sta.index; + int max_row_num = std::min(y_end.index - y_sta.index + 1, model->total_row()); + int row_num = 0; + QVector ys; + { + int cur_y = y_sta.cell_pos; + for (int i = 0; i < max_row_num; ++i) + { + auto cur = model->get_row_info(ysi + i); + if (cur->hide) continue; + ys.push_back({cur_y, i, cur}); + cur_y += cur->len + model->row_space; + } + row_num = ys.size(); + } + // 绘制表头 水平 + { + painter.setPen(Qt::black); + int end_y = cells_widget_rect.y() - col_info_inside_space; + int sta_y = end_y - col_info_h; + { + QPoint s = QPoint(cells_widget_rect.x(), sta_y); + QPoint e = QPoint(cells_widget_rect.right(), end_y); + painter.setClipRect(QRect(s, e)); + } + QVector middle_xs(col_num + 1); + for (int i = 0; i < col_num; ++i) + { + auto& xd = xs[i]; + int sta_x = to_widget_pos({xd.cell_abs_pos, 0}).rx(); + int end_x = to_widget_pos({xd.cell_abs_pos + xd.info->len, 0}).rx(); + if (i == 0) { + middle_xs[0] = sta_x - model->row_space / 2; + } + middle_xs[i + 1] = end_x + model->row_space / 2; + QRect re(QPoint(sta_x, sta_y), QPoint(end_x, end_y)); + xd.info->paint(&painter, re); + } + { + QPoint s = QPoint(cells_widget_rect.x(), sta_y); + painter.setClipRect(QRect(s, cells_widget_rect.bottomRight())); + } + painter.setPen(Qt::black); + for (int i = 0; i < col_num; ++i) + { + int middle_x = middle_xs[i]; + painter.drawLine(QPoint(middle_x, 0), QPoint(middle_x, height())); + } + } + // 绘制表头 竖直 + { + painter.setPen(Qt::black); + int end_x = cells_widget_rect.x() - row_info_inside_space; + int sta_x = end_x - row_info_w; + { + QPoint s = QPoint(sta_x, cells_widget_rect.y()); + QPoint e = QPoint(end_x, cells_widget_rect.bottom()); + painter.setClipRect(QRect(s, e)); + } + QVector middle_ys(row_num); + for (int i = 0; i < row_num; ++i) + { + auto& yd = ys[i]; + int sta_y = to_widget_pos({0, yd.cell_abs_pos}).ry(); + int end_y = to_widget_pos({0, yd.cell_abs_pos + yd.info->len}).ry(); + middle_ys[i] = end_y + model->col_space / 2; + QRect re(QPoint(sta_x, sta_y), QPoint(end_x, end_y)); + yd.info->paint(&painter, re); + } + + { + QPoint s = QPoint(sta_x, cells_widget_rect.y()); + painter.setClipRect(QRect(s, cells_widget_rect.bottomRight())); + } + painter.setPen(Qt::black); + for (int i = 0; i < row_num; ++i) + { + int middle_y = middle_ys[i]; + painter.drawLine(QPoint(0, middle_y), QPoint(width(), middle_y)); + } + } + // 绘制滚动条 + if (h_bar.show) { + h_bar.draw(&painter); + } + if (v_bar.show) { + v_bar.draw(&painter); + } + QSet set; + // 绘制小方格 + painter.setClipRect(cells_widget_rect); + painter.setPen(Qt::black); + for (auto& xd : xs) + { + for (auto& yd : ys) + { + int xi = xsi + xd.index; + int yi = ysi + yd.index; + auto cur_cell = model->get_table_data(xi, yi); + QPoint cur_inside_sta_pos; + QPoint cur_inside_end_pos; + auto show_cell = cur_cell; + bool show = true; + + if (cur_cell->parent != nullptr && cur_cell->parent != cur_cell) { + auto r = model->find_parent(cur_cell->parent, xi, yi); + if ( + r.col_index < xsi || + r.row_index < ysi + ) { + if (!set.contains(cur_cell->parent)) { + set.insert(cur_cell->parent); + int sxi = r.col_index; + int syi = r.row_index; + show = true; + auto info = model->get_span(sxi, syi); + int x = model->cell_x(sxi); + int y = model->cell_y(syi); + cur_inside_sta_pos = {x, y}; + cur_inside_end_pos = {x + info.sw, y + info.sh}; + show_cell = model->get_table_data(sxi, syi); + } else + { + show = false; + } + } + } else + { + auto info = model->get_span(xi, yi); + cur_inside_sta_pos = {xd.cell_abs_pos, yd.cell_abs_pos}; + cur_inside_end_pos = {xd.cell_abs_pos + info.sw, yd.cell_abs_pos + info.sh}; + } + + QPoint cur_widget_sta_pos = to_widget_pos(cur_inside_sta_pos); + QPoint cur_widget_end_pos = to_widget_pos(cur_inside_end_pos); + QRect cur_view_rect = QRect(cur_widget_sta_pos, cur_widget_end_pos); + show_cell->update(show, this, &painter, cur_view_rect); + } + } + } + + bool Table_View::event(QEvent* e) { + if ( + e->type() == QEvent::MouseButtonPress || + e->type() == QEvent::MouseMove || + e->type() == QEvent::MouseButtonRelease || + e->type() == QEvent::MouseButtonDblClick + ) { + auto event = static_cast(e); + mouseEvent(event, event->type()); + return true; + } + return QWidget::event(e); + } + + void Table_View::leaveEvent(QEvent* event) { + QWidget::leaveEvent(event); + } + + + void Table_View::mouseEvent(QMouseEvent* event, QEvent::Type type) { + QRect cells_widget_rect = get_view_rect(); + auto pos = event->pos(); + auto abs_pos = to_inside_pos(pos); + Pos px = model->get_col(abs_pos.x()); + Pos py = model->get_row(abs_pos.y()); + bool drag_on_col_space = false; + bool drag_on_row_space = false; + Col_Info* cur_col{}; + Row_Info* cur_row{}; + if (model->total_col() != 0) { + cur_col = model->get_col_info(px.index); + if (use_drag_w && px.offset > cur_col->len && px.offset < cur_col->len + model->col_space) { + drag_on_col_space = true; + } + } + if (model->total_row() != 0) { + cur_row = model->get_row_info(py.index); + if (use_drag_h && py.offset > cur_row->len && py.offset < cur_row->len + model->row_space) { + drag_on_row_space = true; + } + } + bool on_h_slider = h_bar.get_slider_rect(width(), height()).contains(event->pos()); + bool on_v_slider = v_bar.get_slider_rect(width(), height()).contains(event->pos()); + bool on_cells_view = cells_widget_rect.contains(pos); + if (cur_col && cur_row) { + auto cur_cell = model->get_table_data(px.index, py.index); + auto parent_pos = model->find_parent(cur_cell->parent, px.index, py.index); + auto show_cell = model->get_table_data(parent_pos.col_index,parent_pos.row_index); + int sci = parent_pos.col_index; + int sri = parent_pos.row_index; + auto sx = model->cell_x(sci); + auto sy = model->cell_y(sri); + auto info = model->get_span(parent_pos.col_index, parent_pos.row_index); + QPoint s_sta = to_widget_pos({sx, sy}); + QPoint s_end = to_widget_pos({sx + info.sw, sy + info.sh}); + QRect srect = QRect(s_sta, s_end); + if (type == QEvent::MouseButtonDblClick) + { + if (on_cells_view && srect.contains(s_sta)) + { + edit->setGeometry(srect); + prev_data = show_cell; + auto ttd = dynamic_cast(show_cell); + if (ttd) { + edit->setText(ttd->content); + edit->setParent(this); + edit->show(); + } + } + } + } + + if (type == QEvent::MouseButtonPress) + { + //qDebug() << "cur_cell " << cur_cell->content ; + //qDebug() << "cur_cell_parent "<< (cur_cell->parent ? cur_cell->parent->content : "nullptr"); + edit->hide(); + if (prev_data) + { + auto ttd = dynamic_cast(prev_data); + if (ttd) { + ttd->content = edit->toPlainText(); + prev_data = nullptr; + update(); + } + } + // qDebug() << log; + if (isCtrlPressed()) + { + isDragging = true; + drag_type = Table_Move; + setCursor(Qt::OpenHandCursor); + } else if (on_h_slider) + { + isDragging = true; + drag_type = H_scrollBar; + setCursor(Qt::SizeHorCursor); + } else if (on_v_slider) + { + isDragging = true; + drag_type = V_scrollBar; + setCursor(Qt::SizeVerCursor); + } else if (drag_on_col_space) + { + isDragging = true; + drag_type = H_Expand; + col_expand = cur_col; + start_expand_len = col_expand->len; + setCursor(Qt::SplitHCursor); + } else if (drag_on_row_space) + { + isDragging = true; + drag_type = V_Expand; + row_expand = cur_row; + start_expand_len = row_expand->len; + setCursor(Qt::SplitVCursor); + } else + { + setCursor(Qt::ArrowCursor); + } + if (isDragging) + { + start_pos = event->pos(); + save_pos = _view_pos; + } + } else if (type == QEvent::MouseButtonRelease) + { + isDragging = false; + if (drag_on_col_space) + { + setCursor(Qt::SplitHCursor); + } else if (drag_on_row_space) + { + setCursor(Qt::SplitVCursor); + } else if (on_h_slider) + { + setCursor(Qt::SizeHorCursor); + } else if (on_v_slider) + { + setCursor(Qt::SizeVerCursor); + } else + { + setCursor(Qt::ArrowCursor); + } + } else if (type == QEvent::MouseMove) + { + if (isCtrlPressed()) + { + if (isDragging) { + setCursor(Qt::ClosedHandCursor); + } else + { + setCursor(Qt::OpenHandCursor); + } + } else + { + if (!isDragging) + { + if (drag_on_col_space) + { + setCursor(Qt::SplitHCursor); + } else if (drag_on_row_space) + { + setCursor(Qt::SplitVCursor); + } else if (on_h_slider) + { + setCursor(Qt::SizeHorCursor); + } else if (on_v_slider) + { + setCursor(Qt::SizeVerCursor); + } else + { + setCursor(Qt::ArrowCursor); + } + } + } + + if (!isDragging) return; + QPoint cur_pos = event->pos(); + int deltaX = cur_pos.x() - start_pos.x(); // 计算X轴的拖动距离 + int deltaY = cur_pos.y() - start_pos.y(); // 计算Y轴的拖动距离 + if (drag_type == Table_Move && isCtrlPressed()) + { + int end_x = save_pos.x() - deltaX; + int end_y = save_pos.y() - deltaY; + _view_pos.setX(end_x); + _view_pos.setY(end_y); + h_bar.set_view_pos(_view_pos.rx()); + v_bar.set_view_pos(_view_pos.ry()); + } else if (drag_type == H_scrollBar) + { + int end_x = save_pos.x() + deltaX / h_bar.rate(); + _view_pos.setX(end_x); + h_bar.set_view_pos(end_x); + } else if (drag_type == V_scrollBar) + { + int end_y = save_pos.y() + deltaY / v_bar.rate(); + _view_pos.setY(end_y); + v_bar.set_view_pos(end_y); + } else if (drag_type == H_Expand) + { + col_expand->len = std::max(model->col_min_w, start_expand_len + deltaX); + } else if (drag_type == V_Expand) + { + row_expand->len = std::max(model->row_min_h, start_expand_len + deltaY); + } + update(); + } + } + + + void Table_View::wheelEvent(QWheelEvent* event) { + int single_step = 12; + double v_steps = event->angleDelta().y() / 120; + v_bar.set_view_pos(v_bar.get_view_pos() - v_steps * single_step); + double h_steps = event->angleDelta().x() / 120; + h_bar.set_view_pos(h_bar.get_view_pos() - h_steps * single_step); + _view_pos.setX(h_bar.view_pos()); + _view_pos.setY(v_bar.view_pos()); + update(); + } +} +#ifdef YC_USE_GTEST +#include +TEST(Table, get_pos_test) { + QVector info_list; + // 4 2 1 + // 4 6 7 + info_list.push_back(new Base_info(4, "")); + info_list.push_back(new Base_info(1, "")); + auto size = static_cast(info_list.size()); + auto d = info_list.data(); + QVector> combinedData = { + {Base_Table_Info::Pos{0, 0, -7}, -7}, + {Base_Table_Info::Pos{0, 0, 4}, 4}, + {Base_Table_Info::Pos{1, 6, 1}, 7}, + {Base_Table_Info::Pos{1, 6, 2}, 8}, + }; + for (auto it : combinedData) + { + auto answer = it.first; + auto input = it.second; + auto pos = get_pos(d, size, 2, input, 0); + EXPECT_TRUE(pos == answer) << "输入:" << input << "\n" + << "结果:" + pos.to_string() << "\n" + << "期望:" + answer.to_string() << "\n"; + } +} +// 测试 Matrix 类的基本操作 +TEST(MatrixTest, BasicOperations) { + // 初始化一个 3x3 矩阵 + Table mat; + mat.data = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + // 测试 total_row 和 total_col + EXPECT_EQ(mat.total_row(), 3); + EXPECT_EQ(mat.total_col(), 3); + // 测试 at 方法 + EXPECT_EQ(mat.at(0, 0), 1); + EXPECT_EQ(mat.at(1, 1), 5); + EXPECT_EQ(mat.at(2, 2), 9); + // 测试 row 方法 + QVector row_0 = mat.row(0); + EXPECT_EQ(row_0.size(), 3); + EXPECT_EQ(row_0[0], 1); + EXPECT_EQ(row_0[1], 2); + EXPECT_EQ(row_0[2], 3); + // 测试 col 方法 + QVector col_0 = mat.col(0); + EXPECT_EQ(col_0.size(), 3); + EXPECT_EQ(col_0[0], 1); + EXPECT_EQ(col_0[1], 4); + EXPECT_EQ(col_0[2], 7); + // 测试 sub_matrix 方法 + Table sub_mat = mat.sub_table(0, 0, 2, 2); + EXPECT_EQ(sub_mat.total_row(), 2); + EXPECT_EQ(sub_mat.total_col(), 2); + EXPECT_EQ(sub_mat.at(0, 0), 1); + EXPECT_EQ(sub_mat.at(1, 1), 5); +} +TEST(MatrixTest, SubMatrix) { + // 测试提取子矩阵 + Table mat; + mat.data = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + Table sub_mat = mat.sub_table(1, 1, 2, 2); + EXPECT_EQ(sub_mat.total_row(), 2); + EXPECT_EQ(sub_mat.total_col(), 2); + EXPECT_EQ(sub_mat.at(0, 0), 5); + EXPECT_EQ(sub_mat.at(0, 1), 6); + EXPECT_EQ(sub_mat.at(1, 0), 8); + EXPECT_EQ(sub_mat.at(1, 1), 9); +} +TEST(MatrixTest, EmptyMatrix) { + // 测试空矩阵 + Table mat; + EXPECT_EQ(mat.total_row(), 0); + EXPECT_EQ(mat.total_col(), 0); +} +TEST(MatrixTest, SingleElementMatrix) { + // 测试 1x1 矩阵 + Table mat; + mat.data = {{42}}; + EXPECT_EQ(mat.total_row(), 1); + EXPECT_EQ(mat.total_col(), 1); + EXPECT_EQ(mat.at(0, 0), 42); +} +#include +#ifdef _WINDOWS +#include +#endif +int main() { + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); +#ifdef _WINDOWS + SetConsoleOutputCP(CP_UTF8); +#endif + testing::InitGoogleTest(); + RUN_ALL_TESTS(); + return 0; +} +#endif diff --git a/YSGraphic_Core/component/Table/Table_View.h b/YSGraphic_Core/component/Table/Table_View.h new file mode 100644 index 0000000..3b19477 --- /dev/null +++ b/YSGraphic_Core/component/Table/Table_View.h @@ -0,0 +1,110 @@ +#pragma once +#include "../../DemoGallery/Tool.h" +#include "Base_Table_Model.h" +#include "global.h" +#include "scroll.h" + +namespace Psc { + /* + 使用Data更好 因为使用Data如果你想用指针你依然可以传入指针 + 表格功能分析 + 1. 拖动改变大小 + 2. 合并单元格 + 3. 内嵌控件 + 首先是表格数据结构 包含位置和 ui计算 + 表格自身内容绘制 自定义组件嵌入 + 合并单元格通过 合并入控制 + 表格也是model view 格式 + view 根据model的接口 两个rect计算需要绘制的元素以及位置 然后 开始绘制 + */ + + class Container : public QWidget { + public: + Container(QWidget *parent = nullptr) : QWidget(parent) { + setMouseTracking(true); + setWindowFlag(Qt::WindowTransparentForInput, true); // 透传所有输入事件 + // 使该控件对所有事件透明,事件会传递给下层控件或父控件 + //setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_InputMethodTransparent); + } + + void paintEvent(QPaintEvent* event) override { + QPainter painter(this); + painter.fillRect(rect(), QColor(200, 200, 200, 100)); + painter.end(); + } + void mousePressEvent(QMouseEvent *event) override { + event->ignore(); // 不拦截,交给子控件或父控件处理 + } + void mouseReleaseEvent(QMouseEvent *event) override { + event->ignore(); + } + void mouseMoveEvent(QMouseEvent *event) override { + event->ignore(); + } + }; + + + class Table_View : public QWidget { + public: + Table_View(); + Container* container; + Base_Table_Model* model{}; + V_ScrollBar v_bar; + H_ScrollBar h_bar; + QTextEdit* edit; + // inside and outside + int col_info_h = 30; + int row_info_w = 30; + int col_info_inside_space = 10; + int row_info_inside_space = 10; + + int h_bar_space = 10; + int v_bar_space = 10; + + int top_margin = 10; + int left_margin = 10; + int right_margin = 10; + int bottom_margin = 10; + + QPoint to_inside_pos(QPoint widget_pos); + QPoint to_widget_pos(QPoint inside_pos); + QRect get_view_rect(); + + static bool isCtrlPressed() { + if (Qt::ControlModifier & QApplication::keyboardModifiers()) + { + return true; + } else + { + return false; + } + } + enum Drag_Type { + Table_Move, + H_scrollBar, + V_scrollBar, + V_Expand, + H_Expand, + } drag_type{}; + bool isDragging = false; + QPoint start_pos; + QPoint _view_pos = {0, 0}; + QPoint save_pos; + Col_Info* col_expand{}; + Row_Info* row_expand{}; + int start_expand_len{}; + Base_Table_Data* prev_data{}; + bool use_drag_h = true; + bool use_drag_w = true; + protected: + bool event(QEvent* event) override; + bool eventFilter(QObject* obj, QEvent* event) override; + void paintEvent(QPaintEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + void mouseEvent(QMouseEvent* event, QEvent::Type type); + void leaveEvent(QEvent* event) override; + }; +} + diff --git a/YSGraphic_Core/component/Table/global.cpp b/YSGraphic_Core/component/Table/global.cpp new file mode 100644 index 0000000..47afadb --- /dev/null +++ b/YSGraphic_Core/component/Table/global.cpp @@ -0,0 +1,4 @@ +#include "global.h" +namespace Psc { + +} \ No newline at end of file diff --git a/YSGraphic_Core/component/Table/global.h b/YSGraphic_Core/component/Table/global.h new file mode 100644 index 0000000..2c5955c --- /dev/null +++ b/YSGraphic_Core/component/Table/global.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Psc { + struct Base_Table_Data; + struct Base_Info; + struct Row_Info; + struct Col_Info; + class Table_View; + + struct Cell_Info { + int col_idx, row_idx; + int sw, sh; + }; + struct Pos { + int index; // 头的索引 + int cell_pos; // index位置的块 头的绝对位置 + int offset; // 相对于块头的偏移 + [[nodiscard]] int pos() const { return cell_pos + offset; } + Pos(int index, int cell_pos, int offset) : index(index), cell_pos(cell_pos), offset(offset) {} + bool operator==(const Pos& p) const { return p.cell_pos == cell_pos && index == p.index && offset == p.offset; } + bool operator!=(const Pos& pos) const { return !operator==(pos); } + QString to_string() const { + return QString("index:%1 pos:%2 offset:%3").arg(index).arg(cell_pos).arg(offset); + } + }; + + struct Find_Ret { + int col_index; + int row_index; + QString to_string() const { + return QString("Find_Ret(%1, %2)").arg(col_index).arg(row_index); + } + }; + // 纯虚函数 + enum class Edge_Expand_Type { + Positive, // 顺着坐标轴方向拓展 + Negative, // 逆着坐标轴方向拓展 + None // 不进行边缘拓展 + }; +} + + diff --git a/YSGraphic_Core/component/Table/scroll.cpp b/YSGraphic_Core/component/Table/scroll.cpp new file mode 100644 index 0000000..8d85304 --- /dev/null +++ b/YSGraphic_Core/component/Table/scroll.cpp @@ -0,0 +1,51 @@ +#include "scroll.h" +namespace Psc { + void V_ScrollBar::draw(QPainter* painter) { + auto device = painter->device(); + double ww = device->width(); + double hh = device->height(); + painter->save(); + auto bar_rect = get_bar_rect(ww, hh); + painter->setClipRect(bar_rect); + painter->fillRect(bar_rect, background_color); + painter->fillRect(get_slider_rect(ww, hh), slider_color); + painter->restore(); + } + + QRect V_ScrollBar::get_slider_rect(int ww, int hh) { + double x = ww - w; + QRect slider(x, pos() + start, w, slider_len()); + return slider; + } + + QRect V_ScrollBar::get_bar_rect(int ww, int hh) { + double x = ww - w; + QRect bar_rect(x, 0 + start, w, bar_len()); + return bar_rect; + } + + void H_ScrollBar::draw(QPainter* painter) { + auto device = painter->device(); + double ww = device->width(); + double hh = device->height(); + + painter->save(); + auto bar_rect = get_bar_rect(ww, hh); + painter->setClipRect(bar_rect); + painter->fillRect(bar_rect, background_color); + painter->fillRect(get_slider_rect(ww, hh), slider_color); + painter->restore(); + } + + QRect H_ScrollBar::get_slider_rect(int ww, int hh) { + double y = hh - h; + QRect slider_rect(pos() + start, y, slider_len(), h); + return slider_rect; + } + + QRect H_ScrollBar::get_bar_rect(int ww, int hh) { + double y = hh - h; + QRect bar_rect(0 + start, y, bar_len(), h); + return bar_rect; + } +} \ No newline at end of file diff --git a/YSGraphic_Core/component/Table/scroll.h b/YSGraphic_Core/component/Table/scroll.h new file mode 100644 index 0000000..c44a934 --- /dev/null +++ b/YSGraphic_Core/component/Table/scroll.h @@ -0,0 +1,74 @@ +#pragma once +#include +#include +#include +namespace Psc { + class Base_ScrollBar { + public: + // 距离控件起点的位置 + int start = 0; + double get_view_pos() { + return this->_view_pos; + } + + void set_view_pos(int view_pos) { + this->_view_pos = view_pos; + } + + void resize(int bar_len, int view_len, int owner_len) { + this->_bar_len = bar_len; + this->view_len = view_len; + this->owner_len = owner_len; + } + bool show = true; + QColor background_color = Qt::gray; + QColor slider_color = Qt::black; + protected: + // 主位置情况 + double owner_len = 1; + double _view_pos = 0; + double view_len = 1; + // 滚动条位置情况 + double _bar_len = 1; //滚动条长度 + public: + double rate() { + return _bar_len / owner_len; + } + + double bar_len() { + return _bar_len; + } + + double pos() { + return _view_pos * _bar_len / owner_len; + } + + double view_pos() { + return _view_pos; + } + + double slider_len() { + return view_len * _bar_len / owner_len; + } + }; + + // 向下 + class V_ScrollBar : public Base_ScrollBar { + public: + double w = 8; + void draw(QPainter* painter); + QRect get_slider_rect(int ww, int hh); + QRect get_bar_rect(int ww, int hh); + }; + + // 向下 + class H_ScrollBar : public Base_ScrollBar { + public: + double h = 8; + void draw(QPainter* painter); + QRect get_slider_rect(int ww, int hh); + QRect get_bar_rect(int ww, int hh); + }; +} + + diff --git a/YSGraphic_Core/component/Tree/Tree_Model.cpp b/YSGraphic_Core/component/Tree/Tree_Model.cpp new file mode 100644 index 0000000..f569fd9 --- /dev/null +++ b/YSGraphic_Core/component/Tree/Tree_Model.cpp @@ -0,0 +1,193 @@ +#include "Tree_Model.h" +#include +#include +#include +namespace Psc { + int Node::getDeep() { + int ret = -1; + Node* cur = static_cast(this); + while (cur) + { + cur = cur->par; + ret++; + } + return ret; + } + Node::Node() {} + int Node::index() { return par->sons.indexOf(static_cast(this)); } + bool Node::isRoot() { return par == nullptr; } + bool Node::isLeaf() { return !sons.size(); } + bool Node::isFirst() { return index() == 0; } + bool Node::isLast() { return index() == par->sons.size() - 1; } + Node* Node::previous() { + if (isFirst()) return nullptr; + return par->sons[index() - 1]; + } + Node* Node::next() { + if (isLast()) return nullptr; + return par->sons[index() + 1]; + } + Node* Node::lastChild() { + if (isLeaf()) std::cout << "lastChild() error"; + return sons[sons.size() - 1]; + } + Node* Node::firstChild() { + if (isLeaf()) std::cout << "firstChild() error"; + return sons[0]; + } + Node* Node::root() { + Node* ret = static_cast(this); + while (ret->par != nullptr) + { + ret = ret->par; + } + return ret; + } + QVector Node::deepTraversed() { + QVector result; + if (!this) return result; + + QStack stack; + stack.push(this); + + while (!stack.isEmpty()) { + Node* node = stack.pop(); + result.append(node); + + // 逆序压入子节点,保证从左到右的遍历顺序 + for (int i = node->sons.size() - 1; i >= 0; --i) { + stack.push(node->sons[i]); + } + } + + return result; + } + QVector Node::sequenceTraversed() { + QVector ret; + QQueue que; + Node* that = this; + que.enqueue(that); + while (!que.empty()) + { + Node* cur = que.dequeue(); + int n = cur->sons.size(); + ret.append(cur); + for (int i = 0; i < n; ++i) + { + que.enqueue(cur->sons[i]); + } + } + return ret; + } + + + + QVector Node::descendants() { + QVector&& ret = sequenceTraversed(); + ret.pop_front(); + return ret; + } + + + QVector deepTraversed(const QVector& nodes) { + QVector result; + if (nodes.isEmpty()) return result; + + QStack stack; + for (Node* node : nodes) { + if (node) stack.push_front(node); + } + + while (!stack.isEmpty()) { + Node* node = stack.pop(); + result.append(node); + + // 逆序压入子节点,保证从左到右的遍历顺序 + for (int i = node->sons.size() - 1; i >= 0; --i) { + stack.push(node->sons[i]); + } + } + + return result; + } + + + +#define ReMain (pos - total_height) + + int Tree_Model::height() { + int total_height = 0; + QStack> stack; + for (Node* node : roots) { + if (node) stack.push_front({node, 0}); + } + Node* cur{}; + int deep{}; + while (!stack.isEmpty()) { + auto t = stack.top(); + cur = t.first; + deep = t.second; + stack.pop(); + if (cur->expand) + { + for (int i = cur->sons.size() - 1; i >= 0; --i) { + stack.push({cur->sons[i], deep + 1}); + } + } + total_height += cur->height + space; + } + return total_height; + } + + + Tree_Pos get_pos(int pos, const QVector& roots, int space) { + int total_height = 0; + QStack> stack; + for (Node* node : roots) { + if (node) stack.push_front({node, 0}); + } + Node* cur{}; + int deep{}; + while (!stack.isEmpty()) { + auto t = stack.top(); + cur = t.first; + deep = t.second; + if (ReMain < cur->height) + { + return Tree_Pos{cur, deep, total_height, ReMain, stack}; + } + stack.pop(); + if (cur->expand) + { + for (int i = cur->sons.size() - 1; i >= 0; --i) { + stack.push({cur->sons[i], deep + 1}); + } + } + total_height += cur->height + space; + } + return Tree_Pos{cur, deep, total_height, ReMain, stack}; + } + + + void select_node(const QVector& roots, int view_y, int length, int space, Node_Call_Back call_back) { + auto ret = get_pos(view_y, roots, space); + QStack>& stack = ret.rest; + int cur_inside_y = ret.node_pos; + int cur_inside_end = cur_inside_y + length; + while (!stack.isEmpty()) { + auto [cur, depth] = stack.pop(); + if (cur->expand) + { + for (int i = cur->sons.size() - 1; i >= 0; --i) { + stack.push({cur->sons[i], depth + 1}); + } + } + call_back(cur, depth, cur_inside_y); + cur_inside_y += cur->height + space; + if (cur_inside_y > cur_inside_end) { + break; + } + } + } +} + diff --git a/YSGraphic_Core/component/Tree/Tree_Model.h b/YSGraphic_Core/component/Tree/Tree_Model.h new file mode 100644 index 0000000..dfcaffe --- /dev/null +++ b/YSGraphic_Core/component/Tree/Tree_Model.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include +namespace Psc { + class Node { + public: + explicit Node(); + //判断状态 + int index(); + bool isRoot(); + bool isLeaf(); + bool isFirst(); + bool isLast(); + Node *previous(); + Node *next(); + Node *lastChild(); + Node *firstChild(); + Node *root(); + int getDeep(); + //遍历值 + QVector deepTraversed(); + QVector sequenceTraversed(); + QVector descendants(); + + + Node *par = nullptr; + QVector sons; + QString text; + bool expand = true; + int height = 20; + + void addChild(Node *child) { + sons.append(child); + child->par = this; + } + }; + + + struct Tree_Pos { + Node *p{}; + int depth{}; + int node_pos{}; + int offset{}; + int pos() {return node_pos + offset;} + QStack> rest; + }; + + + class Tree_Model { + public: + QVector roots; + int tab_pixel_size = 8; + int png_len = 20; + int tab_width = 10; + int space = 4; + int height(); + int width() { + return 1000; + } + }; + + + Tree_Pos get_pos(int pos, const QVector& roots, int space); + QVector deepTraversed(const QVector& nodes); + + using Node_Call_Back = const std::function&; + void select_node(const QVector& roots, int view_y, int length, int space, Node_Call_Back call_back); +} diff --git a/YSGraphic_Core/component/Tree/Tree_View.cpp b/YSGraphic_Core/component/Tree/Tree_View.cpp new file mode 100644 index 0000000..cac74bb --- /dev/null +++ b/YSGraphic_Core/component/Tree/Tree_View.cpp @@ -0,0 +1,233 @@ +#include "Tree_View.h" +#include +#include +#include +#include +#include "Tree_Model.h" +#include "../../component/SVG.h" +#include + +namespace Psc { + Tree_View* create_Tree_View() { + auto cn = [](const QString& text) { + auto ret = new Node(); + ret->text = text; + return ret; + }; + auto ret = new Tree_View(); + auto& roots = ret->model->roots; + int a1 = 10; + int a2 = 4; + int a3 = 10; + for (int i1 = 0; i1 < a1; i1++) + { + QString t1 = QString("%1").arg(i1); + auto n1 = cn(t1); + for (int i2 = 0; i2 < a2; i2++) + { + QString t2 = t1 + QString("_%1").arg(i2); + auto n2 = cn(t2); + for (int i3 = 0; i3 < a3; i3++) + { + QString t3 = t2 + QString("_%1").arg(i3); + auto n3 = cn(t3); + n2->addChild(n3); + } + n1->addChild(n2); + } + roots.append(n1); + } + return ret; + } + + + QPoint Tree_View::to_inside_pos(QPoint widget_pos) { + auto rect = get_view_rect(); + return widget_pos + this->_view_pos - QPoint(rect.left(), rect.top()); + } + QPoint Tree_View::to_widget_pos(QPoint inside_pos) { + auto rect = get_view_rect(); + return inside_pos - this->_view_pos + QPoint(rect.left(), rect.top()); + } + QRect Tree_View::get_view_rect() { + int w = width() - left_margin - right_margin; + int h = height() - top_margin - bottom_margin; + return QRect(left_margin, top_margin, w, h); + } + + + + + Tree_View::Tree_View() { + _view_pos = {0, 0}; + //_view_pos = {0, 0}; + model = new Tree_Model(); + } + + + void Tree_View::paintEvent(QPaintEvent* event) { + QPainter painter(this); + QRect view_rect = get_view_rect(); + painter.drawRect(view_rect); + // painter.fillRect(rect(), Qt::yellow); + auto& roots = model->roots; + QFontMetrics fm(font); + static auto expand = SVGImage_Render().set_path(":/you_jian_tou.svg").create().to_image({model->png_len, model->png_len}, Qt::black); + static auto retract = expand.transformed(QTransform().rotate(90)); + + painter.setClipRect(view_rect); + select_node(roots, _view_pos.y(), view_rect.height(), model->space, [&](Node* cur, int depth, int cur_inside_y) { + int x = depth * model->tab_width; + int cur_h = cur->height; + { + // 绘制箭头 + QPoint sta = {x, cur_inside_y}; + QPoint end = {x + model->png_len, cur_inside_y + model->png_len}; + QPoint wsta = to_widget_pos(sta); + QPoint wend = to_widget_pos(end); + QRect r(wsta, wend); + painter.drawRect(r); + painter.drawImage(r.topLeft(), cur->expand ? expand : retract); + } + { + // 绘制文字 + x += model->png_len; + QPoint sta = {x, cur_inside_y + cur_h}; + painter.drawText(to_widget_pos(sta), cur->text); + } + }); + + + h_bar.draw(&painter); + v_bar.draw(&painter); + QWidget::paintEvent(event); + } + + + void Tree_View::mouseEvent(QMouseEvent* event, QEvent::Type type) { + QRect view_rect = get_view_rect(); + QPoint pos = event->pos(); + bool on_h_slider = h_bar.get_slider_rect(width(), height()).contains(event->pos()); + bool on_v_slider = v_bar.get_slider_rect(width(), height()).contains(event->pos()); + if (type == QEvent::MouseButtonPress || type == QEvent::MouseButtonDblClick) + { + select_node(model->roots, _view_pos.y(), view_rect.height(), model->space, [&](Node* cur, int depth, int cur_inside_y) { + int x = depth * model->tab_width; + int cur_h = cur->height; + QPoint sta = {x, cur_inside_y}; + QPoint end = {x + model->png_len, cur_inside_y + model->png_len}; + QPoint wsta = to_widget_pos(sta); + QPoint wend = to_widget_pos(end); + QRect r(wsta, wend); + if (r.contains(pos)) + { + cur->expand = !cur->expand; + qDebug() << "1111111 1 " << cur->expand; + update(); + } + }); + } + + if (type == QEvent::MouseButtonPress) + { + if (on_h_slider) + { + isDragging = true; + drag_type = H_scrollBar; + setCursor(Qt::SizeHorCursor); + } else if (on_v_slider) + { + isDragging = true; + drag_type = V_scrollBar; + setCursor(Qt::SizeVerCursor); + } else + { + setCursor(Qt::ArrowCursor); + } + if (isDragging) + { + start_pos = event->pos(); + save_pos = _view_pos; + } + } + else if (type == QEvent::MouseButtonRelease) + { + if (on_h_slider) + { + setCursor(Qt::SizeHorCursor); + } else if (on_v_slider) + { + setCursor(Qt::SizeVerCursor); + } else + { + setCursor(Qt::ArrowCursor); + } + } + else if (type == QEvent::MouseMove) + { + if (on_h_slider) + { + setCursor(Qt::SizeHorCursor); + } else if (on_v_slider) + { + setCursor(Qt::SizeVerCursor); + } else + { + setCursor(Qt::ArrowCursor); + } + if (!isDragging) return; + QPoint cur_pos = event->pos(); + int deltaX = cur_pos.x() - start_pos.x(); // 计算X轴的拖动距离 + int deltaY = cur_pos.y() - start_pos.y(); // 计算Y轴的拖动距离 + if (drag_type == H_scrollBar) + { + int end_x = save_pos.x() + deltaX / h_bar.rate(); + _view_pos.setX(end_x); + h_bar.set_view_pos(end_x); + } else if (drag_type == V_scrollBar) + { + int end_y = save_pos.y() + deltaY / v_bar.rate(); + _view_pos.setY(end_y); + v_bar.set_view_pos(end_y); + } else + { + setCursor(Qt::ArrowCursor); + } + update(); + } + } + + bool Tree_View::event(QEvent* e) { + if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseMove || + e->type() == QEvent::MouseButtonRelease || e->type() == QEvent::MouseButtonDblClick) + { + auto event = static_cast(e); + mouseEvent(event, event->type()); + return true; + } + return QWidget::event(e); + } + + + + void Tree_View::resizeEvent(QResizeEvent* event) { + QRect cells_view_rect = get_view_rect(); + h_bar.start = cells_view_rect.x(); + h_bar.resize(cells_view_rect.width(), cells_view_rect.width(), model->width()); + v_bar.start = cells_view_rect.y(); + v_bar.resize(cells_view_rect.height(), cells_view_rect.height(), model->height()); + QWidget::resizeEvent(event); + } + + + void Tree_View::wheelEvent(QWheelEvent* event) { + double single_step = 12; + double v_steps = event->angleDelta().y() / 120.0; + v_bar.set_view_pos(v_bar.get_view_pos() - v_steps * single_step); + double h_steps = event->angleDelta().x() / 120.0; + h_bar.set_view_pos(h_bar.get_view_pos() - h_steps * single_step); + _view_pos.setX(h_bar.view_pos()); + _view_pos.setY(v_bar.view_pos()); + update(); + } +} \ No newline at end of file diff --git a/YSGraphic_Core/component/Tree/Tree_View.h b/YSGraphic_Core/component/Tree/Tree_View.h new file mode 100644 index 0000000..74978e8 --- /dev/null +++ b/YSGraphic_Core/component/Tree/Tree_View.h @@ -0,0 +1,41 @@ +#pragma once +#include +#include +#include +#include + +#include "../Table/scroll.h" +namespace Psc { + class Tree_Model; + class Tree_View : public QWidget { + public: + Tree_Model* model{}; + int left_margin = 30, right_margin = 30, top_margin = 30, bottom_margin = 30; + QFont font; + QPen pen; + Tree_View(); + QRect get_view_rect(); + QPoint to_inside_pos(QPoint widget_pos); + QPoint to_widget_pos(QPoint inside_pos); + + V_ScrollBar v_bar; + H_ScrollBar h_bar; + enum Drag_Type { + H_scrollBar, + V_scrollBar + } drag_type{}; + bool isDragging = false; + QPoint start_pos; + QPoint save_pos; + QPoint _view_pos = {0, 0}; + protected: + void paintEvent(QPaintEvent* event) override; + bool event(QEvent* event) override; + void mouseEvent(QMouseEvent* event, QEvent::Type type); + void resizeEvent(QResizeEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + }; + + Tree_View* create_Tree_View(); +} + diff --git a/YSGraphic_Core/component/Tree_Table/TreeTable.cpp b/YSGraphic_Core/component/Tree_Table/TreeTable.cpp new file mode 100644 index 0000000..71bf3a7 --- /dev/null +++ b/YSGraphic_Core/component/Tree_Table/TreeTable.cpp @@ -0,0 +1 @@ +#include "TreeTable.h" diff --git a/YSGraphic_Core/component/Tree_Table/TreeTable.h b/YSGraphic_Core/component/Tree_Table/TreeTable.h new file mode 100644 index 0000000..e00706f --- /dev/null +++ b/YSGraphic_Core/component/Tree_Table/TreeTable.h @@ -0,0 +1,10 @@ +#pragma once + + + + + +class TreeTable { + +}; + diff --git a/YSGraphic_Core/component/effect.cpp b/YSGraphic_Core/component/effect.cpp new file mode 100644 index 0000000..88ffd9f --- /dev/null +++ b/YSGraphic_Core/component/effect.cpp @@ -0,0 +1,28 @@ +#include "effect.h" + +#include +#include + +void Wave_effect::draw(QPainter* painter) { + auto r = boundingRect(); + qDebug() << r; + + //r.adjusted(-10, -10, 10, 10); + + //painter->setBrush(Qt::NoBrush); + + + painter->fillRect(r, Qt::yellow); + + drawSource(painter); +} + +QRectF Wave_effect::boundingRectFor(const QRectF& sourceRect) const { + + auto r = QGraphicsEffect::boundingRectFor(sourceRect); + qDebug() << "Wave_effect::boundingRectFor:" << sourceRect << " " << r; + return r.adjusted(-10, -10, 10, 10); + //return QGraphicsEffect::boundingRectFor(sourceRect); +} + + diff --git a/YSGraphic_Core/component/effect.h b/YSGraphic_Core/component/effect.h new file mode 100644 index 0000000..a0bf04c --- /dev/null +++ b/YSGraphic_Core/component/effect.h @@ -0,0 +1,11 @@ +#pragma once +#include + + +//https://blog.csdn.net/kenfan1647/article/details/116198981 +class Wave_effect : public QGraphicsEffect { +protected: + void draw(QPainter* painter) override; +public: + QRectF boundingRectFor(const QRectF& sourceRect) const override; +}; diff --git a/YSGraphic_Core/component/frameless/FrameLessWidget.cpp b/YSGraphic_Core/component/frameless/FrameLessWidget.cpp new file mode 100644 index 0000000..b8df4ca --- /dev/null +++ b/YSGraphic_Core/component/frameless/FrameLessWidget.cpp @@ -0,0 +1,266 @@ +#include "FrameLessWidget.h" +#include +#include +#include +#include + +using changeSize = std::function; +FrameLessWidget::FrameLessWidget(QWidget *parent) : QWidget(parent) { + setMouseTracking(true); + setAttribute(Qt::WA_Hover); + setAttribute(Qt::WA_TranslucentBackground); + //setAttribute(Qt::WA_Mapped); + setWindowFlags( + Qt::Window | + Qt::FramelessWindowHint | + Qt::WindowSystemMenuHint | + Qt::WindowMinimizeButtonHint | + Qt::WindowMaximizeButtonHint + ); + space = 0; + radius = 16; + topMargin = 16; + bottomMargin = 16; + rightMargin = 16; + leftMargin = 16; + shapes.append + ({ + Qt::SizeFDiagCursor, Qt::SizeVerCursor, Qt::SizeBDiagCursor, + Qt::SizeHorCursor, Qt::ArrowCursor, Qt::SizeHorCursor, + Qt::SizeBDiagCursor, Qt::SizeVerCursor, Qt::SizeFDiagCursor, + Qt::ArrowCursor + }); + + changeSizes.append + ({ + [this]() { //R11 + setGeometry(startX + dx, startY + dy, qMax(startW - dx, getMinWidth()), qMax(startH - dy, getMinHeight())); + }, + [this]() { //R12 + setGeometry(startX, startY + dy, qMax(startW, getMinWidth()), qMax(startH - dy, getMinHeight())); + }, + [this]() { //R13 + setGeometry(startX, startY + dy, qMax(startW + dx, getMinWidth()), qMax(startH - dy, getMinHeight())); + }, + [this]() { //R21 + setGeometry(startX + dx, startY, qMax(startW - dx, getMinWidth()), qMax(startH, getMinHeight())); + }, + []() { + //R22 + }, + [this]() { //R23 + setGeometry(startX, startY, qMax(startW + dx, getMinWidth()), qMax(startH, getMinHeight())); + }, + [this]() { //R31 + setGeometry(startX + dx, startY, qMax(startW - dx, getMinWidth()), qMax(startH + dy, getMinHeight())); + }, + [this]() { //R32 + setGeometry(startX, startY, qMax(startW, getMinWidth()), qMax(startH + dy, getMinHeight())); + }, + [this]() { //R33 + setGeometry(startX, startY, qMax(startW + dx, getMinWidth()), qMax(startH + dy, getMinHeight())); + }, + []() { + //null + } + }); +} + +int FrameLessWidget::getMinWidth() { + return 0; +} + +int FrameLessWidget::getMinHeight() { + return 0; +} + + +void FrameLessWidget::paintEvent(QPaintEvent* event) { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + painter.fillRect(rect(), QColor(255, 255, 255, 1)); //不能改 让他不是透明的 // painter.fillRect(r11, Qt::blue); + // painter.fillRect(r12, Qt::darkCyan); + // painter.fillRect(r13, Qt::green); + // painter.fillRect(r21, Qt::gray); + // painter.fillRect(r22, Qt::red); + // painter.fillRect(r23, Qt::darkGreen); + // painter.fillRect(r31, Qt::black); + // painter.fillRect(r32, Qt::darkRed); + // painter.fillRect(r33, Qt::darkBlue); 否则改变大小失效 + + + // https://blog.csdn.net/liushuaitong/article/details/122117384 + // https://www.cnblogs.com/linuxAndMcu/p/11057347.html + QRect rect = r22; + + + //painter.drawRoundedRect(rect, radius, radius); + //画阴影边框 + auto color = background_color; + + + // int xRadius = r22.width(); + // int yRadius = r22.height(); + + int xRadius = radius; + int yRadius = radius; + int mul = 2; + int n = topMargin/mul; + auto max = double(n*n*n); + double value = background_color.alpha(); + for (int i = 0; i < n; i++) { + if (i == 0) + { + painter.setPen(Qt::NoPen); + QColor b = QColor::fromRgb(background_color.red(), background_color.green(), background_color.blue()); + painter.setBrush(b); + painter.drawRoundedRect(rect, xRadius, yRadius); + painter.setBrush(Qt::NoBrush); + } else + { + double rate = (i*i*i)/max; + color.setAlpha(static_cast(value * (1-rate))); + painter.setBrush(color); + painter.drawRoundedRect(rect.adjusted(-i * mul, -i * mul, i * mul, i * mul), xRadius, yRadius); + } + + } + painter.end(); +} + + +bool FrameLessWidget::event(QEvent *event) { + if (event->type() == QEvent::HoverMove) { + auto *hoverEvent = static_cast(event); + // qDebug() << hoverEvent->pos(); + QMouseEvent mouseEvent(QEvent::MouseMove, hoverEvent->pos(), + Qt::NoButton, Qt::NoButton, Qt::NoModifier); + mouseMoveEvent(&mouseEvent); + } + return QWidget::event(event); +} + +void FrameLessWidget::setPos() { + x1 = 0; + x2 = x1 + leftMargin + space; + x3 = width() - rightMargin - space; + y1 = 0; + y2 = y1 + topMargin + space; + y3 = height() - bottomMargin - space; +} + +void FrameLessWidget::setRs() { + int w1 = x2 - x1, w2 = x3 - x2, w3 = width() - x3; + int h1 = y2 - y1, h2 = y3 - y2, h3 = height() - y3; + r11.setRect(x1, y1, w1, h1); + r12.setRect(x2, y1, w2, h1); + r13.setRect(x3, y1, w3, h1); + r21.setRect(x1, y2, w1, h2); + r22.setRect(x2, y2, w2, h2); + r23.setRect(x3, y2, w3, h2); + r31.setRect(x1, y3, w1, h3); + r32.setRect(x2, y3, w2, h3); + r33.setRect(x3, y3, w3, h3); +} + + + +void FrameLessWidget::mousePressEvent(QMouseEvent *event) { + auto pos = event->pos(); + if (event->button() != Qt::LeftButton) return; + if (changingSize) return; + if (drag_moving) return; + curR = getRectType(pos); + if (curR != Drag_R) + { + changingSize = true; + setCursor(shapes[curR]); + } else { + drag_moving = true; + setCursor(Qt::ClosedHandCursor); + } + + if (changingSize || drag_moving) + { + startX = this->x(); + startY = this->y(); + startGlobalX = event->globalPos().x(); + startGlobalY = event->globalPos().y(); + startW = width(); + startH = height(); + } + + +} + +void FrameLessWidget::mouseReleaseEvent(QMouseEvent *event) { + if (changingSize) { + changingSize = false; + } + if (drag_moving) { + drag_moving = false; + setCursor(Qt::OpenHandCursor); + } +} + +void FrameLessWidget::mouseMoveEvent(QMouseEvent *event) { + RectType ret = getRectType(event->pos()); + if (ret == Drag_R) + { + if (!drag_moving) + { + setCursor(Qt::OpenHandCursor); + } + } else + { + setCursor(shapes[ret]); + } + if (!changingSize && !drag_moving) { + return; + } + const QPoint &curGlobalPos = event->globalPos(); + dx = curGlobalPos.x() - startGlobalX; + dy = curGlobalPos.y() - startGlobalY; + if (changingSize) { + changeSizes[curR](); + } + else if (drag_moving) + { + move(startX + dx, startY + dy); + } +} + +RectType FrameLessWidget::getRectType(const QPoint &p) const { + if (p.y() < y2) { + if (p.x() < x2) return R11; + if (p.x() > x3) return R13; + if (p.x() < (x2 + x3)/4) return Drag_R; + return R12; + } + if (p.y() > y3) { + if (p.x() < x2) return R31; + if (p.x() > x3) return R33; + return R32; + } + if (p.x() < x2) return R21; + if (p.x() > x3) return R23; + return R22; +} + +void FrameLessWidget::resizeEvent(QResizeEvent *event) { + if (isMaximized()) { + QRect screenSize = screen()->availableGeometry() + .adjusted(-leftMargin, -topMargin, rightMargin, bottomMargin); + setGeometry(screenSize); + } + setPos(); + setRs(); +} + +void FrameLessWidget::showEvent(QShowEvent *event) { + + + + QWidget::showEvent(event); + +} diff --git a/YSGraphic_Core/component/frameless/FrameLessWidget.h b/YSGraphic_Core/component/frameless/FrameLessWidget.h new file mode 100644 index 0000000..ba7a252 --- /dev/null +++ b/YSGraphic_Core/component/frameless/FrameLessWidget.h @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +enum RectType { R11, R12, R13, R21, R22, R23, R31, R32, R33, null, Drag_R}; +using changeSize = std::function; +class FrameLessWidget : public QWidget { +public: + explicit FrameLessWidget(QWidget* parent = nullptr); + int topMargin; + int bottomMargin; + int rightMargin; + int leftMargin; + int space; + QRect r11, r12, r13, r21, r22, r23, r31, r32, r33; + int x1{}, x2{}, x3{}, y1{}, y2{}, y3{}; + + bool changingSize = false; + bool drag_moving = false; + RectType curR = null; + int startX{}; + int startY{}; + int startGlobalX{}; + int startGlobalY{}; + int startW{}; + int startH{}; + + int dx{}, dy{}; + QList shapes; + QList changeSizes; + int radius; + QColor background_color = QColor(150, 150, 150, 130); + virtual int getMinWidth(); + virtual int getMinHeight(); + void setPos(); + void setRs(); + + [[nodiscard]] RectType getRectType(const QPoint& pos) const; + // window->showFullScreen(); + // window->showNormal(); + // window->showMinimized(); + // window->showMaximized(); + + +protected: + void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + bool event(QEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void showEvent(QShowEvent* event) override; +}; + diff --git a/YSGraphic_Core/component/global.h b/YSGraphic_Core/component/global.h new file mode 100644 index 0000000..77d8e28 --- /dev/null +++ b/YSGraphic_Core/component/global.h @@ -0,0 +1,242 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum class Layout_Item_Type { + Fixed, + Expand +}; + +enum class Layout_Item_Type2 { + Fixed, + Expand +}; + +// 用于线性布局的元素 +struct Layout_Info { + Layout_Info() = default; + + Layout_Item_Type main_type{}; + Layout_Item_Type2 second_type{}; + + Layout_Info(int key, Layout_Item_Type main_type, Layout_Item_Type2 second_type, int main_value, int second_value) : + main_type(main_type), + second_type(second_type) + { + this->key = key; + if (main_type == Layout_Item_Type::Fixed) { + len =main_value; + } else { + weight = main_value; + } + second_len = second_value; + } + + + int len{}; // 线性布局主要位置 + int second_len{}; // 线性布局次要位置 + + int weight{}; // 主要位置权重布局 + bool hide = false; + int key{}; +}; + +struct Layout { + int prefix = 8, suffix = 8; + int space = 8; + int second_prefix = 8, second_suffix = 8; + QVector infos; + enum Type { + Prefix_Space, + Surfix_Space, + Center_Space + }; + void set_margin(int margin){ + prefix = margin; + suffix = margin; + second_prefix = margin; + second_suffix = margin; + } + Type main_type = Prefix_Space; + Type second_type = Prefix_Space; + typedef QFlags Alignment; + + struct Pos { + int start; + int len; + int second_start; + int second_len; + QRect h_rect() { + return {start, second_start, len, second_len}; + } + QRect v_rect() { + return {second_start, start, second_len, len}; + } + }; + QMap cache; + std::optional get(int key) { + auto iter = cache.find(key); + if (iter == cache.end()) { + return std::nullopt; + } + return *iter; + } + void cacl(int len, int second_len) { + QVector ret(infos.size()); + int total_fixed_pixel_size = 0; + int total_weight = 0; + int show_size = 0; + int n = infos.size(); + for (int i = 0; i < n; ++i) { + auto& info = infos[i]; + if (info.hide) { + continue; + } + if (info.main_type == Layout_Item_Type::Fixed) { + total_fixed_pixel_size += info.len; + } + if (info.main_type == Layout_Item_Type::Expand) { + total_weight += info.weight; + } + show_size++; + } + int show_width = total_fixed_pixel_size + (show_size - 1) * space; + int remain = len - show_width - (prefix + suffix); + int main_cur_pos = 0; + if (main_type == Type::Prefix_Space) { + main_cur_pos = prefix; + } + if (main_type == Type::Center_Space) { + main_cur_pos = (len - show_width)/2; + } + if (main_type == Type::Surfix_Space) { + main_cur_pos = len - show_width - suffix; + } + + for (int i = 0; i < n; ++i) { + auto& info = infos[i]; + if (info.hide) continue; + auto& pos = ret[i]; + int cur_len = info.main_type == Layout_Item_Type::Fixed ? info.len : + static_cast((double)remain * (double)info.weight/(double)total_weight); + pos.start = main_cur_pos; + pos.len = cur_len; + + int info_second_len{}; + if (info.second_type == Layout_Item_Type2::Fixed) { + info_second_len = info.second_len; + } + if (info.second_type == Layout_Item_Type2::Expand) { + info_second_len = second_len - second_prefix - second_suffix; + } + if (second_type == Type::Prefix_Space) + { + pos.second_len = second_prefix; + } + if (second_type == Type::Center_Space) + { + pos.second_start = (second_len - info_second_len) /2; + } + if (second_type == Type::Surfix_Space) + { + pos.second_start = second_len - info_second_len- second_suffix; + } + pos.second_len = info_second_len; + main_cur_pos += space + cur_len; + } + cache.clear(); + for (int i = 0; i < n; ++i) + { + auto key = infos[i].key; + cache[key] = ret[i]; + } + } +}; + + + + +enum class Event_Type { + refresh, + Nothing +}; + +struct PaintAble { + PaintAble() = default; + virtual QSize sizeHint() const { + return {24, 24}; + } + virtual ~PaintAble() = default; + virtual void paint(QPainter* painter, QRect rect) = 0; + // 鼠标操作 + virtual Event_Type mouse_event(QEvent::Type t, QMouseEvent* e) { + return Event_Type::Nothing; + } + virtual Event_Type resize_event(QResizeEvent* e) { + return Event_Type::Nothing; + } + virtual Event_Type event(QEvent::Type t, QEvent* e) { + if (t == QEvent::Resize) { + return resize_event(dynamic_cast(e)); + } + if ( + t == QEvent::MouseButtonPress || + t == QEvent::MouseButtonRelease || + t == QEvent::MouseMove || + t == QEvent::MouseButtonDblClick + ) { + return mouse_event(t, dynamic_cast(e)); + } + return Event_Type::Nothing; + } + bool show = true; +}; + + + +struct Base : QWidget { + QVector paintAbles; + void paintEvent(QPaintEvent* event) override { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + QRect rect = event->rect(); + for (auto paintAble : paintAbles) { + if (paintAble->show) { + paintAble->paint(&painter, rect); + } + } + painter.end(); + } + + bool event(QEvent* e) override { + auto t = e->type(); + for(PaintAble* paintAble : paintAbles){ + Event_Type r = paintAble->event(t, e); + if (r == Event_Type::refresh) { + update(); + } + } + return QWidget::event(e); + } +}; + + + + + + + diff --git a/YSGraphic_Core/component/simple_widget.cpp b/YSGraphic_Core/component/simple_widget.cpp new file mode 100644 index 0000000..e69de29 diff --git a/YSGraphic_Core/component/simple_widget.h b/YSGraphic_Core/component/simple_widget.h new file mode 100644 index 0000000..bddece3 --- /dev/null +++ b/YSGraphic_Core/component/simple_widget.h @@ -0,0 +1,119 @@ +#pragma once +#include "global.h" + +#include + +#include +#include +#include +#include +#include + +#include "effect.h" + + +#include +#include + +#include +#include + +#include +#include + + + + +#include +#include + +#include +#include + +#include +#include +#include "Background.h" +struct Wave : PaintAble { + QColor color = Qt::blue; + QGraphicsDropShadowEffect shadowEffect; + Wave() { + shadowEffect.setBlurRadius(10); // 设置模糊半径 + shadowEffect.setOffset(5, 5); // 设置阴影偏移量(x, y) + shadowEffect.setColor(Qt::black); // 设置阴影颜色 + } + void paint(QPainter* painter, QRect rect) override { + + } +}; + +template +struct Wrapper : QMainWindow { + Wrapper() { + resize(400, 400); + auto* w = new Widget(); + auto* ww = static_cast(w); + ww->setParent(this); + ww->setFixedSize(200, 200); + ww->move(100, 100); + } + +}; + + + + + + + +class WaveWidget : public QWidget +{ + Q_OBJECT + +public: + explicit WaveWidget(QWidget *parent = nullptr) + : QWidget(parent), m_offset(0) { + setFixedSize(400, 300); // 设置窗口大小 + + // 定时器:每隔一段时间更新一次动画 + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &WaveWidget::updateWave); + m_timer->start(16); // 约60fps + + // 初始化动画 + m_animation = new QPropertyAnimation(this, "wavePosition"); + m_animation->setDuration(1000); + m_animation->setLoopCount(-1); // 无限循环 + m_animation->setEasingCurve(QEasingCurve::Linear); + } + +protected: + void paintEvent(QPaintEvent *event) override { + QPainter painter(this); + painter.setBrush(Qt::blue); + + // 通过 m_offset 控制波浪的 y 坐标 + int y = 150 + 50 * std::sin(m_offset); // 使用正弦波函数产生波浪效果 + + painter.drawEllipse(QPoint(200, y), 20, 20); // 绘制一个圆形 + + QWidget::paintEvent(event); + } + + private slots: + void updateWave() { + // 每次更新时间,更新 m_offset 以形成波动效果 + m_offset += 0.1; + if (m_offset > 2 * (3.14159265358979323846)) { + m_offset = 0; // 重新从0开始 + } + update(); // 更新界面 + } + +private: + float m_offset; // 波动的偏移量 + QTimer *m_timer; + QPropertyAnimation *m_animation; +}; + + + diff --git a/YSGraphic_Core/component/style文档.md b/YSGraphic_Core/component/style文档.md new file mode 100644 index 0000000..392609a --- /dev/null +++ b/YSGraphic_Core/component/style文档.md @@ -0,0 +1,9 @@ + + + + +https://doc.qt.io/qt-5/qstyleoption.html + +qstyleoption_cast() +enum QStyleOption::OptionType 枚举到具体子类的映射 +https://doc.qt.io/qt-5/qstyleoption.html#OptionType-enum \ No newline at end of file diff --git a/YSGraphic_Core/export.cpp b/YSGraphic_Core/export.cpp new file mode 100644 index 0000000..9157966 --- /dev/null +++ b/YSGraphic_Core/export.cpp @@ -0,0 +1,41 @@ +#include "export.h" +#include + +ScreenInfo getScreenInfo() +{ + ScreenInfo info; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0) + + // Qt5.3+ / Qt6 + QScreen* screen = QGuiApplication::primaryScreen(); + if (!screen) + return info; + + info.fullSize = screen->geometry().size(); + info.availableSize = screen->availableGeometry().size(); + +#elif QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + + // Qt5 早期版本 + QDesktopWidget* desktop = QApplication::desktop(); + if (!desktop) + return info; + + info.fullSize = desktop->screenGeometry().size(); + info.availableSize = desktop->availableGeometry().size(); + +#else + + // Qt4 + QDesktopWidget* desktop = QApplication::desktop(); + if (!desktop) + return info; + + info.fullSize = desktop->screenGeometry().size(); + info.availableSize = desktop->availableGeometry().size(); + +#endif + + return info; +} \ No newline at end of file diff --git a/YSGraphic_Core/export.h b/YSGraphic_Core/export.h new file mode 100644 index 0000000..c6f3f9c --- /dev/null +++ b/YSGraphic_Core/export.h @@ -0,0 +1,35 @@ +#pragma once + + +#include "base/Plot.h" +#include "GenerateMockData.h" +#include "base/MutiSelectRect.h" + +//plottable +#include "plottable/Afterglow.h" +#include "plottable/AudioFrequent.h" +#include "plottable/Planisphere.h" +#include "plottable/Spectrum.h" +#include "plottable/SweepFrequent.h" +#include "plottable/WaterFall.h" + +//axis +#include "Axis/FrequentAxis.h" +#include "Axis/TimeAxis.h" +#include "Axis/TimeAxis.h" + + + +#include "CacheModel.h" + + +struct ScreenInfo +{ + QSize fullSize; // 包含任务栏 + QSize availableSize; // 不包含任务栏 +}; + +ScreenInfo getScreenInfo(); + + + diff --git a/YSGraphic_Core/main.cpp b/YSGraphic_Core/main.cpp new file mode 100644 index 0000000..7617885 --- /dev/null +++ b/YSGraphic_Core/main.cpp @@ -0,0 +1,79 @@ +#include +#include + +#ifdef _WINDOWS +#include +#include +#endif +#include +#include +#include +#include "GenerateMockData.h" +#include "base/Graphic.h" +#include "base/Plot.h" +#include "component/Button.h" +#include "component/simple_widget.h" +#include "plottable/Afterglow.h" +#include "plottable/Planisphere.h" +#include "plottable/Planisphere_p.h" +#include "plottable/Spectrum.h" +#include "plottable/Spectrum_p.h" +#include "plottable/WaterFall.h" +#include "plottable/WaterFall_p.h" +#include "DemoGallery/AfterglowPlot.h" +#include "DemoGallery/DemoGallery.h" +#include "DemoGallery/PlanispherePlot.h" +#include "DemoGallery/SpectrumPlot.h" +#include "DemoGallery/WaterFallPlot.h" +#include "base/SelectColorDialog/SelectColorDialog.h" +class Temp : public QWidget { +public: + Temp() { + setFixedSize(400 ,400); + image.load("D:/wyc/projects/CLionProjects/plantSunCat/mainProjects/YSGraphic/src/img.png"); + } + QImage image; +protected: + void paintEvent(QPaintEvent* event) override { + QPainter painter(this); + double w = width(); + double h = height(); + // painter.scale(-1.5, 1); + // painter.drawImage(QRectF(-100, 100, 100, 100), image); + + painter.scale(1.5, 1); + painter.drawImage(QRectF(100, 100, 100, 100), image); + painter.end(); + } +}; + + +#ifdef _build_exe +int main(int argc, char *argv[]) { + QApplication app(argc, argv); + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); +#ifdef _WINDOWS + SetConsoleOutputCP(CP_UTF8); + system("chcp 65001"); + //setbuf(stdout, 0); +#endif + + Button btn; + + btn.show(); + // WaveWidget w; + // w.resize(400, 400); + // w.show(); + + + return QApplication::exec(); +} +int main2(int argc, char *argv[]) +{ + QApplication a(argc, argv); + WaveWidget w; + w.show(); + return a.exec(); +} + +#endif \ No newline at end of file diff --git a/YSGraphic_Core/plottable/Afterglow.cpp b/YSGraphic_Core/plottable/Afterglow.cpp new file mode 100644 index 0000000..44474b3 --- /dev/null +++ b/YSGraphic_Core/plottable/Afterglow.cpp @@ -0,0 +1,114 @@ +#include "Afterglow_p.h" +#include +namespace YSG { + Q_Ptr_cpp(Afterglow) + PROP_P(Afterglow, FrequentAxis*, frequentAxis) + PROP_P(Afterglow, Axis*, powerAxis) + PROP_P(Afterglow, Range, frequentRange) + PROP_P(Afterglow, Range, powerRange) + PROP_G(Afterglow, int, frequentPointSize) + PROP_G(Afterglow, int, powerPointSize) + PROP_P(Afterglow, int, bufferSize) + PROP_P(Afterglow, bool, interpolate) + PROP_P(Afterglow, double, attenuationRate) + PROP_P(Afterglow, int, initStrongValue) + void Afterglow::set_frequentPointSize(int t) { + AfterglowPrivate* pd = d(); + AfterglowRenderState* sc = pd->renderStateCache(); + AfterglowTempData* td = pd->tempDataCache(); + SpinLockGuard _guard(&pd->mBufferLock); + sc->frequentPointSize = std::move(t); + sc->update_PointSize(sc->frequentPointSize, sc->powerPointSize); + } + void Afterglow::set_powerPointSize(int t) { + AfterglowPrivate* pd = d(); + AfterglowRenderState* sc = pd->renderStateCache(); + AfterglowTempData* td = pd->tempDataCache(); + SpinLockGuard _guard(&pd->mBufferLock); + sc->powerPointSize = std::move(t); + sc->update_PointSize(sc->frequentPointSize, sc->powerPointSize); + } + + + Afterglow::Builder::Builder(FrequentAxis* frequentAxis, Axis* powerAxis){ + init(frequentAxis, powerAxis); + } + Afterglow* Afterglow::Builder::build() { + auto ret = new Afterglow(); + set(ret); + return ret; + } + + + void Afterglow::giveData(const QVector &powerRangeData) { + if(!ok()) return; + INIT_SET(Afterglow) + sc->pushData(powerRangeData); + } + + void AfterglowRenderState::pushData(const QVector &powerRangeData) { + + if (powerRangeData.size() != frequentPointSize) { + qDebug() << QString("pushData(const QVector &powerRangeData) error 数组大小不匹配 需要%1, 实际为%2") + .arg(frequentPointSize).arg(powerRangeData.size()); + return; + } + if (curIndex == initStrongValue) { + curIndex = 0; + double oldRemain = 1.0 - attenuationRate; + double cacheRemain = attenuationRate / (double) initStrongValue; //这一次的概率 [0,1] + for (int i = 0; i < bufferSize; ++i) { + //std::cout << mCachedPowerData[i] << " " ; + mutexPowerData[i] = oldRemain*oldCachePowerData[i] + cacheRemain*cachedPowerData[i]; + cachedPowerData[i] = 0; + } + //std::cout << std::endl; + { + auto d = reinterpret_cast(dPtr); + SpinLockGuard g(&d->dataLock); + std::memmove(oldCachePowerData.data(), mutexPowerData.data(), bufferSize * sizeof(double)); + d->OK.storeRelease(true); + } + } + QVector indexList(frequentPointSize); + double rate = (double)powerPointSize/powerRange.size(); + for (int i = 0; i < frequentPointSize; ++i) { + int lineIndex = (int) ((powerRangeData[i] - powerRange.lower) * rate); + if (lineIndex < 0) lineIndex = 0; + if (lineIndex > powerPointSize - 1) lineIndex = powerPointSize - 1; + indexList[i] = lineIndex; + } + + if (!interpolate) { //插值 填入没有的概率地方 + for (int i = 0; i < frequentPointSize; ++i) { + int index = indexList[i] * frequentPointSize + i; + cachedPowerData[index]++; + } + } else { + int lastLower = -1, lastUpper = -1; + for (int i = 0; i < frequentPointSize; ++i) { + int y = indexList[i]; + cachedPowerData[y * frequentPointSize + i]++; + if (y - lastUpper >= 2) { + for (int curY = lastUpper + 1; curY < y; ++curY) { + cachedPowerData[curY * frequentPointSize + i]++; + } + lastUpper = y; + lastLower = lastLower + 1; + continue; + } + if (lastLower - y >= 2) { + for (int curY = y + 1; curY < lastLower; ++curY) { + cachedPowerData[curY * frequentPointSize + i]++; + } + lastUpper = lastLower - 1; + lastLower = y; + continue; + } + lastUpper = y; + lastLower = y; + } + } + curIndex++; + } +} diff --git a/YSGraphic_Core/plottable/Afterglow.h b/YSGraphic_Core/plottable/Afterglow.h new file mode 100644 index 0000000..d14452a --- /dev/null +++ b/YSGraphic_Core/plottable/Afterglow.h @@ -0,0 +1,65 @@ +#pragma once +#include "../RenderAble.h" +#include "../Axis/FrequentAxis.h" +namespace YSG { + class FrequentAxis; + class AfterglowPlot; + struct AfterglowPrivate; + + + class LIB_DECL Afterglow : public RenderAble { + public: + Q_Ptr2(Afterglow) + PROP(FrequentAxis*, frequentAxis) + PROP(Axis*, powerAxis) + PROP(Range, frequentRange) + PROP(Range, powerRange) + PROP(int, frequentPointSize) + PROP(int, powerPointSize) + PROP(int, bufferSize) + PROP(bool, interpolate) + PROP(double, attenuationRate) + PROP(int, initStrongValue) + void giveData(const QVector &powerRangeData); + template struct BuilderT; + struct Builder; + }; + + + struct Afterglow_Prop { + FrequentAxis *frequentAxis{}; + Axis *powerAxis{}; + Range frequentRange{0,10}; + Range powerRange{0, 10}; + int frequentPointSize{}; + int powerPointSize{}; + int bufferSize{}; + bool interpolate = true; //线性插值 + double attenuationRate = 0.2; //衰减率 衰减率为 1.0代表 不使用衰减 + int initStrongValue = 100; //初始强度 + }; + template + struct Afterglow::BuilderT { + PROP_BT(FrequentAxis*, frequentAxis) + PROP_BT(Axis*, powerAxis) + PROP_BT(Range, frequentRange) + PROP_BT(Range, powerRange) + PROP_BT(int, frequentPointSize) + PROP_BT(int, powerPointSize) + PROP_BT(int, bufferSize) + PROP_BT(bool, interpolate) + PROP_BT(double, attenuationRate) + PROP_BT(int, initStrongValue) + SETTER_T(QString, layerName, "plottable") + void init(FrequentAxis* frequentAxis, Axis* powerAxis); + void set(Afterglow* t); + protected: + Plot *plot{}; + }; + + struct LIB_DECL Afterglow::Builder : Afterglow::BuilderT, Afterglow_Prop { + Afterglow* build(); + Builder(FrequentAxis* frequentAxis, Axis* powerAxis); + }; + +} diff --git a/YSGraphic_Core/plottable/Afterglow_p.h b/YSGraphic_Core/plottable/Afterglow_p.h new file mode 100644 index 0000000..b234d18 --- /dev/null +++ b/YSGraphic_Core/plottable/Afterglow_p.h @@ -0,0 +1,124 @@ +#pragma once +#include "Afterglow.h" +#include "../Axis/FrequentAxis.h" +#include "../Axis/FrequentAxis_p.h" +#include "../base/PerformanceShower_p.h" +#include "../base/Plot_p.h" +#include "../base/Global.h" + +namespace YSG { + + + + struct AfterglowRenderState : RenderState, Afterglow_Prop { + int curIndex = 0; + QVector cachedPowerData; + QVector oldCachePowerData; + QVector mutexPowerData; + // 等比数列求和公式 a1*(1-q^n)/(1-q) n->inf 时为 a1/(1-q) + [[nodiscard]] int strengthValue() const { return (int) ((double) initStrongValue / (1.0 - attenuationRate)); } + void update_PointSize(int frequentPointSize, int powerPointSize) { + this->frequentPointSize = frequentPointSize; + this->powerPointSize = powerPointSize; + bufferSize = frequentPointSize * powerPointSize; + cachedPowerData.resize(bufferSize); + cachedPowerData.fill(0); + oldCachePowerData.resize(bufferSize); + oldCachePowerData.fill(0); + mutexPowerData.resize(bufferSize); + mutexPowerData.fill(0); + curIndex = 0; + } + void pushData(const QVector &powerRangeData); + }; + + + struct AfterglowTempData : TempData {}; + struct AfterglowPrivate : RenderData { + D_Ptr(Afterglow) + QImage image; + SpinLock dataLock; + QAtomicInteger OK = false; + void draw(QPainter* painter) override { + AfterglowRenderState* s = renderState(); + PerformanceShower *shower = q()->mPlot->d->mShower; + if(shower) { + shower->mInfoMap["colCount, rowCount"] = PerformanceLine(QString("colCount:%1, rowCount:%2") + .arg(s->frequentPointSize).arg( s->powerPointSize)); + } + AbsAxis *hAxis = s->frequentAxis, *vAxis = s->powerAxis, *valueAxis = s->powerAxis; + Range &hRange = s->frequentRange, &vRange = s->powerRange; + + int colCount = s->frequentPointSize, rowCount = s->powerPointSize; + if(image.width() != colCount || image.height() != rowCount) { + image = QImage(colCount, rowCount, QImage::Format_ARGB32_Premultiplied); + image.fill(0); + } + { + if(OK.loadAcquire() == true) { + QVector& mColorMap = Global::instance()->mColorMap; + Cacl c("Afterglow setTime:%1 avg %2", q()->mPlot->d->mShower); + //double rate = 1.0/s->strengthValue() * 256.0; + for (int row = 0; row < rowCount; ++row) { + QRgb* scanLine = reinterpret_cast(image.scanLine(row)); + double* list = s->oldCachePowerData.data() + row*colCount; + for (int col = 0; col < colCount; ++col) { + int colorOffset = (int)(list[col] * 255); + if (colorOffset < 0 || colorOffset > 255) { + qDebug() << "colorOffset:" << colorOffset << " value: " << list[col] + << " startCoord:" << valueAxis->startCoord() << " endCoord:" << valueAxis->endCoord(); + } + //std::cout << " " << list[col]; + scanLine[col] = mColorMap.at(colorOffset); + } + //std::cout << std::endl; + } + OK.storeRelease(false); + } + } + double x = hAxis->coordToPixel(hRange.lower, SRC::Render); + double y = vAxis->coordToPixel(vRange.lower, SRC::Render); + double w = hAxis->coordToPixel(hRange.upper, SRC::Render) - x; + double h = vAxis->coordToPixel(vRange.upper, SRC::Render) - y; + + Range &&hAxisRange = hAxis->coordRange(), &&vAxisRange = vAxis->coordRange(); + double hr = ((hAxisRange.lower < hAxisRange.upper) ^ (hRange.lower < hRange.upper)) ? -1 : 1; + double vr = ((vAxisRange.lower < vAxisRange.upper) ^ (vRange.lower < vRange.upper)) ? -1 : 1; + + painter->save(); + painter->scale(hr, vr); + painter->drawImage(QRectF(hr * x, vr * y, hr * w, vr * h), image); + painter->restore(); + } + + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + } + }; + template + void Afterglow::BuilderT::init(FrequentAxis* frequentAxis, Axis* powerAxis) { + ASSERT(frequentAxis->mPlot != nullptr, "Afterglow build error! frequentAxis->mPlot == nullptr"); + ASSERT(frequentAxis->mPlot == powerAxis->mPlot, "Afterglow build error! frequentAxis->mPlot != powerAxis->mPlot"); + plot = frequentAxis->mPlot; + static_cast(this)->frequentAxis = frequentAxis; + static_cast(this)->powerAxis = powerAxis; + } + template + void Afterglow::BuilderT::set(Afterglow* ret) { + ret->init(plot, layerName); + AfterglowPrivate* pd = ret->d(); + AfterglowRenderState* sc = pd->renderStateCache(); + PROP_RT(FrequentAxis*, frequentAxis) + PROP_RT(Axis*, powerAxis) + PROP_RT(Range, frequentRange) + PROP_RT(Range, powerRange) + PROP_RT(int, frequentPointSize) + PROP_RT(int, powerPointSize) + PROP_RT(int, bufferSize) + PROP_RT(bool, interpolate) + PROP_RT(double, attenuationRate) + PROP_RT(int, initStrongValue) + } + +} diff --git a/YSGraphic_Core/plottable/AudioFrequent.cpp b/YSGraphic_Core/plottable/AudioFrequent.cpp new file mode 100644 index 0000000..6f31761 --- /dev/null +++ b/YSGraphic_Core/plottable/AudioFrequent.cpp @@ -0,0 +1,38 @@ +#include "AudioFrequent_p.h" +#include "../Axis/TimeAxis.h" + + +namespace YSG { + Q_Ptr_cpp(AudioFrequent) + PROP_P(AudioFrequent, int, timePointSize) + PROP_P(AudioFrequent, TimeAxis*, timeAxis) + PROP_P(AudioFrequent, Axis*, valueAxis) + PROP_P(AudioFrequent, Range, keyRange) + PROP_P(AudioFrequent, QColor, color) + + AudioFrequent::Builder::Builder(TimeAxis* timeAxis, Axis* valueAxis) { + ASSERT(timeAxis->mPlot != nullptr, "AudioFrequent build error! timeAxis->mPlot == nullptr"); + ASSERT(timeAxis->mPlot == valueAxis->mPlot, "AudioFrequent build error! timeAxis->mPlot != valueAxis->mPlot"); + plot = timeAxis->mPlot; + this->timeAxis = timeAxis; + this->valueAxis = valueAxis; + } + AudioFrequent* AudioFrequent::Builder::build() { + auto ret = new AudioFrequent(); + ret->init(plot, layerName); + AudioFrequentPrivate* pd = ret->d(); + AudioFrequentRenderState* sc = pd->renderStateCache(); + PROP_R(int, timePointSize) + PROP_R(TimeAxis*, timeAxis) + PROP_R(Axis*, valueAxis) + PROP_R(Range, keyRange) + PROP_R(QColor, color) + return ret; + } + void AudioFrequent::giveData(int tick, double data) { + Q_UNUSED(tick) + if(!ok()) return; + INIT_SET(AudioFrequent) + td->mDataList.push_front(data); + } +} diff --git a/YSGraphic_Core/plottable/AudioFrequent.h b/YSGraphic_Core/plottable/AudioFrequent.h new file mode 100644 index 0000000..942d588 --- /dev/null +++ b/YSGraphic_Core/plottable/AudioFrequent.h @@ -0,0 +1,45 @@ +#pragma once +#include "../Axis/AbsAxis.h" +#include "../GlobalTypes.h" +#include + + + + +namespace YSG { + class TimeAxis; + struct AudioFrequentPrivate; + class LIB_DECL AudioFrequent : public RenderAble { + public: + Q_Ptr2(AudioFrequent) + void giveData(int tick, double data); + PROP(int, timePointSize) + PROP(TimeAxis*, timeAxis) + PROP(Axis*, valueAxis) + PROP(Range, keyRange) + PROP(QColor, color) + struct Builder; + }; + struct AudioFrequent_Prop { + int timePointSize = 100; + TimeAxis *timeAxis; + Axis *valueAxis{}; + Range keyRange = {0, 20}; + QColor color = Qt::yellow; + }; + struct LIB_DECL AudioFrequent::Builder : AudioFrequent_Prop { + PROP_B(int, timePointSize) + PROP_B(TimeAxis*, timeAxis) + PROP_B(Axis*, valueAxis) + PROP_B(Range, keyRange) + PROP_B(QColor, color) + SETTER(QString, layerName, "plottable") + Builder(TimeAxis* timeAxis, Axis* valueAxis); + AudioFrequent* build(); + protected: + Axis* valueAxis; + TimeAxis* timeAxis{}; + Plot *plot{}; + }; +} + diff --git a/YSGraphic_Core/plottable/AudioFrequent_p.h b/YSGraphic_Core/plottable/AudioFrequent_p.h new file mode 100644 index 0000000..f485680 --- /dev/null +++ b/YSGraphic_Core/plottable/AudioFrequent_p.h @@ -0,0 +1,68 @@ +#pragma once + +#include "AudioFrequent.h" +#include "../RenderAble.h" +#include "../Axis/Axis_p.h" +#include "../Axis/TimeAxis.h" +#include "../Axis/TimeAxis_p.h" +#include "../base/PerformanceShower_p.h" +#include "../base/Plot_p.h" + + +namespace YSG { + struct AudioFrequentTempData : TempData { + std::list mDataList; + }; + struct AudioFrequentRenderState : RenderState, AudioFrequent_Prop {}; + + struct AudioFrequentPrivate : RenderData { + D_Ptr(AudioFrequent) + YSG::MutiRingBuffer mMutiBuffer; + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + AudioFrequentRenderState *s = renderState(); + AudioFrequentTempData *d = tempData(); + if(mMutiBuffer.buffers.empty() || mMutiBuffer.n != s->timePointSize) { + mMutiBuffer.resize({sizeof(double)}, s->timePointSize, s->timePointSize * 10); + PerformanceShower *shower = q()->mPlot->d->mShower; + if(shower) { + shower->mInfoMap["TimePointSize"] = PerformanceLine(QString("timePointSize:%1") + .arg(s->timePointSize)); + } + } + for(double& power : d->mDataList) { + mMutiBuffer.pushData({&power}); + } + d->mDataList.clear(); + } + + + void draw(QPainter* painter) override { + AudioFrequentRenderState *s = renderState(); + PerformanceShower *shower = q()->mPlot->d->mShower; + if(shower) { + shower->mInfoMap["timePointSize"] = PerformanceLine(QString("timePointSize:%1") + .arg(s->timeAxis->timePointSize())); + } + painter->save(); + AudioFrequentTempData *d = tempData(); + QPolygonF polyline; + int start = (int)s->timeAxis->startCoord(); + QPen newPen = QPen(s->color); + newPen.setWidth(0); + painter->setPen(newPen); + auto l1 = mMutiBuffer.buffers[0].list(); + for(int i = 0; i < mMutiBuffer.buffers[0].f; ++i) { + double* power = l1 + i; + int index = s->timeAxis->d()->mTimeTicker.mStartCoordToEndCoord ? start + i : + start + s->timeAxis->d()->mTimeTicker.mTimePointSize - i; + double x = s->timeAxis->coordToPixel(index, SRC::Render); + double y = s->valueAxis->coordToPixel(*power, SRC::Render); + polyline.append({x, y}); + } + painter->drawPolyline(polyline); + painter->restore(); + } + }; +} diff --git a/YSGraphic_Core/plottable/HoverInfo.cpp b/YSGraphic_Core/plottable/HoverInfo.cpp new file mode 100644 index 0000000..01cc5d0 --- /dev/null +++ b/YSGraphic_Core/plottable/HoverInfo.cpp @@ -0,0 +1,41 @@ +#include "HoverInfo.h" + +#include "../base/Plot.h" + +namespace YSG { + void HoverInfoRenderState::drawHover(QPainter* painter, AbsAxis* h, AbsAxis* v) { + QPoint pos = QCursor::pos() - h->mPlot->mapToGlobal(QPoint(0, 0)); + if(!renderAble) { + auto that = dynamic_cast(this); + renderAble = dynamic_cast(that->dPtr->qPtr); + } + QVector lines = renderAble->createHoverString(h, v, pos); + QFontMetrics fm(hoverInfoFont); + int width = std::numeric_limits::min(); + for(QString& s : lines) width = qMax(width, fm.horizontalAdvance(s)); + QRect r = QRect(pos.x() + hoverInfoLeft, pos.y() + hoverInfoTop, + (int)width + hoverInfoRight + hoverInfoLeft, fm.height() * lines.size() + hoverInfoBottom); + painter->fillRect(r, hoverInfoBrush); + painter->setPen(hoverInfoPen); + painter->setFont(hoverInfoFont); + int lineHeight = fm.height(); + for (int i = 0; i < lines.size(); ++i) { + QRect lineRect(r.left(), r.top() + i * lineHeight, r.width(), lineHeight); + painter->drawText(lineRect, Qt::AlignCenter, lines[i]); + } + } + + bool HoverInfoRenderState::hoverOK(RenderAble* able) const { + QPoint pos = QCursor::pos() - able->mPlot->mapToGlobal(QPoint(0, 0)); + auto tmp = dynamic_cast(able); + return useHoverInfo && able->mPlot->underMouse() && tmp->hoverTest(pos); + } + + QVector HoverInfoRenderAbleInterFace::createHoverString(AbsAxis* h, AbsAxis* v, QPoint pos) { + double tick1 = h->pixelToCoord(pos.x(), SRC::Render); + double tick2 = v->pixelToCoord(pos.y(), SRC::Render); + QString line1 = h->getTickLabel(tick1, SRC::Render) + h->unitText(); + QString line2 = v->getTickLabel(tick2, SRC::Render) + v->unitText(); + return {line1, line2}; + } +} diff --git a/YSGraphic_Core/plottable/HoverInfo.h b/YSGraphic_Core/plottable/HoverInfo.h new file mode 100644 index 0000000..cf126b5 --- /dev/null +++ b/YSGraphic_Core/plottable/HoverInfo.h @@ -0,0 +1,138 @@ +#pragma once +#include "../RenderAble.h" +#include "../Axis/AbsAxis.h" + +namespace YSG { + struct HoverInfoRenderState; + struct HoverInfoRenderAbleInterFace; + + struct LIB_DECL HoverInfoRenderState { + virtual ~HoverInfoRenderState() = default; + HoverInfoRenderState() { + hoverInfoBrush = QBrush(Qt::white); + } + int hoverInfoLeft = 4, hoverInfoTop = 4, hoverInfoRight = 4, hoverInfoBottom = 4; + QBrush hoverInfoBrush; + QFont hoverInfoFont; + QPen hoverInfoPen; + bool useHoverInfo = true; + HoverInfoRenderAbleInterFace *renderAble{}; + void drawHover(QPainter* painter, AbsAxis *h, AbsAxis *v); + bool hoverOK(RenderAble* able) const; + }; + + + struct LIB_DECL HoverInfoRenderAbleInterFace { + virtual QVector createHoverString(AbsAxis* h, AbsAxis* v, QPoint pos); + virtual ~HoverInfoRenderAbleInterFace() = default; + virtual void drawHover(QPainter* painter, AbsAxis *h, AbsAxis *v) { + return getState(SRC::Render)->drawHover(painter, h, v); + } + virtual bool hoverOK(RenderAble* able) { + return getState(SRC::Render)->hoverOK(able); + } + virtual bool hoverTest(QPoint pos) { + return true; + } + + bool useHoverInfo(SRC src = SRC::Auto) { + return getState(src)->useHoverInfo; + } + void setUseHoverInfo(bool use) { + SpinLockGuard g(lock()); + getState(SRC::Cache)->useHoverInfo = use; + } + + void setHoverInfoFont(const QFont& font) { + SpinLockGuard g(lock()); + getState(SRC::Cache)->hoverInfoFont = font; + } + + QFont HoverInfoFont(SRC src = SRC::Auto) { + return getState(src)->hoverInfoFont; + } + + QBrush hoverInfoBackgroundBrush(SRC src = SRC::Auto) { + return getState(src)->hoverInfoBrush; + } + + void setHoverInfoBackgroundBrush(const QBrush& brush) { + SpinLockGuard g(lock()); + getState(SRC::Cache)->hoverInfoBrush = brush.color().isValid() ? brush : Qt::NoBrush; + } + + QPen hoverInfoPen(SRC src = SRC::Auto) { + return getState(src)->hoverInfoPen; + } + + void setHoverInfoPen(const QPen& pen) { + SpinLockGuard g(lock()); + getState(SRC::Cache)->hoverInfoPen = pen.color().isValid() ? pen : Qt::NoPen; + } + + void getHoverInfoContentsMargins(int& left, int& top, int& right, int& bottom, SRC src) { + auto s = getState(src); + left = s->hoverInfoLeft; + top = s->hoverInfoTop; + right = s->hoverInfoRight; + bottom = s->hoverInfoBottom; + } + + void setHoverInfoContentsMargins(int left, int top, int right, int bottom) { + SpinLockGuard g(lock()); + auto sc = getState(SRC::Cache); + sc->hoverInfoLeft = left; + sc->hoverInfoTop = top; + sc->hoverInfoRight = right; + sc->hoverInfoBottom = bottom; + } + protected: + virtual HoverInfoRenderState* getState(SRC src) = 0; + virtual SpinLock* lock() = 0; + }; + + + struct LIB_DECL HoverInfoIndependRenderAble : HoverInfoRenderAbleInterFace { + private: + SpinLock mtx; + HoverInfoRenderState s, sc; + SpinLock* lock() override { + return &mtx; + } + HoverInfoRenderState* getState(SRC src) override { + if(src == SRC::Cache){ + return ≻ + } + return &s; + } + public: + void drawHover(QPainter* painter, AbsAxis* h, AbsAxis* v) override { + s.renderAble = this; + sc.renderAble = this; + { + SpinLockGuard g(lock()); + s = sc; + } + HoverInfoRenderAbleInterFace::drawHover(painter, h, v); + } + }; + + + template + struct LIB_DECL HoverInfoRenderAble : HoverInfoRenderAbleInterFace { + private: + That *that() { + return static_cast(this); + } + SpinLock* lock() override { + return &that()->dPtr->mBufferLock; + } + HoverInfoRenderState* getState(SRC src) override { + auto t = that(); + return dynamic_cast(t->dPtr->getState(src)); + } + }; + + +} + diff --git a/YSGraphic_Core/plottable/Planisphere.cpp b/YSGraphic_Core/plottable/Planisphere.cpp new file mode 100644 index 0000000..cc5f2fb --- /dev/null +++ b/YSGraphic_Core/plottable/Planisphere.cpp @@ -0,0 +1,59 @@ +#include + +#include "Planisphere_p.h" +#include "../Axis/AbsAxis_p.h" + + +namespace YSG { + Q_Ptr_cpp(Planisphere) + PROP_P(Planisphere, Axis*, IAxis) + PROP_P(Planisphere, Axis*, QAxis) + PROP_P(Planisphere, Range, IRange) + PROP_P(Planisphere, Range, QRange) + PROP_P(Planisphere, QColor, pointColor) + PROP_P(Planisphere, QColor, anchorColor) + PROP_P(Planisphere, int, continueMillisecond) + PROP_P(Planisphere, Planisphere_Type, type) + + + void Planisphere::giveData(QPointF pos) { + if(!ok()) return; + if(!mPlot->isVisible()) return; + INIT_SET(Planisphere) + td->mDataList.push_back({pos}); + } + + Planisphere::Builder::Builder(Axis* IAxis, Axis* QAxis) { + ASSERT(IAxis->mPlot != nullptr, "Afterglow build error! IAxis->mPlot == nullptr"); + ASSERT(IAxis->mPlot == QAxis->mPlot, "Afterglow build error! IAxis->mPlot != QAxis->mPlot"); + plot = IAxis->mPlot; + this->IAxis = IAxis; + this->QAxis = QAxis; + } + Planisphere* Planisphere::Builder::build() { + auto ret = new Planisphere(); + ret->init(plot, layerName); + PlanispherePrivate* pd = ret->d(); + PlanisphereRenderState* sc = pd->renderStateCache(); + PROP_R(Axis*, IAxis) + PROP_R(Axis*, QAxis) + PROP_R(Range, IRange) + PROP_R(Range, QRange) + PROP_R(QColor, pointColor) + PROP_R(QColor, anchorColor) + PROP_R(int, continueMillisecond) + PROP_R(Planisphere_Type, type) + return ret; + } + + void Planisphere::setToAxisCenter() { + PlanisphereRenderState *s = d()->renderStateCache(); + double w = mPlot->width(), h = mPlot->height(); + double l = w < h ? w : h; + double iL = s->IAxis->coordRange().size()/w * l/2; + double qL = s->QAxis->coordRange().size()/h * l/2; + double im = s->IAxis->coordRange().middle(), qm = s->IAxis->coordRange().middle(); + set_IRange({im-iL, im+iL}); + set_QRange({qm-qL, qm+qL}); + } +} diff --git a/YSGraphic_Core/plottable/Planisphere.h b/YSGraphic_Core/plottable/Planisphere.h new file mode 100644 index 0000000..4f8e8d5 --- /dev/null +++ b/YSGraphic_Core/plottable/Planisphere.h @@ -0,0 +1,50 @@ +#pragma once +#include "../RenderAble.h" +namespace YSG { + enum class Planisphere_Type {Psk8 = 8}; + + struct PlanispherePrivate; + class LIB_DECL Planisphere : public RenderAble { + public: + Q_Ptr2(Planisphere) + void giveData(QPointF pos); + PROP(Axis*, IAxis) + PROP(Axis*, QAxis) + PROP(Range, IRange) + PROP(Range, QRange) + PROP(QColor, pointColor) + PROP(QColor, anchorColor) + PROP(int, continueMillisecond) + PROP(Planisphere_Type, type) + void setToAxisCenter(); + struct Builder; + }; + + struct Planisphere_Prop { + Axis *IAxis{}; + Axis *QAxis{}; + Range IRange{0, 100}; + Range QRange{0, 100}; + QColor pointColor = Qt::red; + QColor anchorColor = Qt::yellow; + int continueMillisecond = 1000; + Planisphere_Type type = Planisphere_Type::Psk8; + }; + struct LIB_DECL Planisphere::Builder : Planisphere_Prop { + PROP_B(Axis*, IAxis) + PROP_B(Axis*, QAxis) + PROP_B(Range, IRange) + PROP_B(Range, QRange) + PROP_B(QColor, pointColor) + PROP_B(QColor, anchorColor) + PROP_B(int, continueMillisecond) + PROP_B(Planisphere_Type, type) + SETTER(QString, layerName, "plottable") + Builder(Axis* IAxis, Axis* QAxis); + Planisphere* build(); + protected: + Plot *plot{}; + }; + + +} diff --git a/YSGraphic_Core/plottable/Planisphere_p.h b/YSGraphic_Core/plottable/Planisphere_p.h new file mode 100644 index 0000000..e5be19f --- /dev/null +++ b/YSGraphic_Core/plottable/Planisphere_p.h @@ -0,0 +1,128 @@ +#pragma once +#include "Planisphere.h" +#include "../Axis/Axis.h" +#include "../base/Plot_p.h" + +namespace YSG { + struct PlanisphereData { + QPointF mPos; + int mRemainTimes{}; + int maxTotalTimes{}; + }; + struct PlanisphereTempData : TempData { + std::list mDataList; + }; + + struct PlanisphereRenderState : RenderState, Planisphere_Prop { + + }; + + struct PlanispherePrivate : RenderData { + D_Ptr(Planisphere) + std::list dataList; + QVector xs, ys; + QVector fixedPoints; + void update_FixedPoints() { + PlanisphereRenderState *s = renderState(); + fixedPoints.clear(); + if(s->type == Planisphere_Type::Psk8) { + double xS = s->IAxis->coordToPixel(s->IRange.lower, SRC::Render); + double yS = s->QAxis->coordToPixel(s->QRange.lower, SRC::Render); + double xL = s->IAxis->coordToPixel(s->IRange.upper, SRC::Render) - xS; + double yL = s->QAxis->coordToPixel(s->QRange.upper, SRC::Render) - yS; + double xSpace = xL/ 6.0; + double ySpace = yL/ 6.0; + double xLineSpace = xL / 4.0; + double yLineSpace = yL / 4.0; + fixedPoints.push_back({xS + 2 * xSpace, yS + 1 * ySpace}); + fixedPoints.push_back({xS + 1 * xSpace, yS + 2 * ySpace}); + fixedPoints.push_back({xS + 1 * xSpace, yS + 4 * ySpace}); + fixedPoints.push_back({xS + 2 * xSpace, yS + 5 * ySpace}); + fixedPoints.push_back({xS + 4 * xSpace, yS + 5 * ySpace}); + fixedPoints.push_back({xS + 5 * xSpace, yS + 4 * ySpace}); + fixedPoints.push_back({xS + 5 * xSpace, yS + 4 * ySpace}); + fixedPoints.push_back({xS + 5 * xSpace, yS + 2 * ySpace}); + fixedPoints.push_back({xS + 4 * xSpace, yS + 1 * ySpace}); + xs.resize(5); ys.resize(5); + double curX = xS, curY = yS; + for(int i = 0; i < 5; ++i) { + xs[i] = curX; + curX += xLineSpace; + ys[i] = curY; + curY += yLineSpace; + } + } + } + void refresh() { + for (auto it = dataList.begin(); it != dataList.end();) { + if (--it->mRemainTimes == 0) + dataList.erase(it++); + else + ++it; + } + } + + + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + PlanisphereTempData *d = tempData(); + PlanisphereRenderState *s = renderState(); + int times = qMax(5, (s->continueMillisecond * q()->mPlot->d->mRefreshTimesPreSecond) / 1000); + for(PlanisphereData& data : d->mDataList) { + data.maxTotalTimes = times; + data.mRemainTimes = times; + } + dataList.splice(dataList.end(), d->mDataList); + refresh(); + } + + void draw(QPainter *painter) override { + update_FixedPoints(); + PlanisphereRenderState *s = renderState(); + { + double xS = xs.first(), xE = xs.last(); + double yS = ys.first(), yE = ys.last(); + QPen pen; + pen.setStyle(Qt::DotLine); + pen.setColor(Qt::white); + painter->setPen(pen); + for(double x : xs) painter->drawLine(QPointF(x, yS), QPointF(x, yE)); + for(double y : ys) painter->drawLine(QPointF(xS, y), QPointF(xE, y)); + } + { + painter->setPen(Qt::NoPen); + for(PlanisphereData &data : dataList) { + QColor c = s->pointColor; + c.setAlphaF(c.alphaF() * data.mRemainTimes / data.maxTotalTimes); + painter->setBrush(c); + double centerX = s->IAxis->coordToPixel(data.mPos.x(), SRC::Render); + double centerY = s->QAxis->coordToPixel(data.mPos.y(), SRC::Render); + double radius = 2; + painter->drawEllipse({centerX - radius, centerY - radius}, 2 * radius, 2 * radius); + } + } + { + painter->setPen(QPen(s->anchorColor, 2)); + painter->setBrush(Qt::NoBrush); + QVector &fixedPoints = this->fixedPoints; + for (auto &point: fixedPoints) { + int radius = 6; + painter->drawLine(QPointF{point.x() - radius, point.y()}, {point.x() + radius, point.y()}); + painter->drawLine(QPointF{point.x(), point.y() - radius}, {point.x(), point.y() + radius}); + } + } + } + + + + void plotEvent(QEvent* event) override { + if(event->type() == QEvent::Resize){ + auto e = reinterpret_cast(event); + + + } + } + + }; +} diff --git a/YSGraphic_Core/plottable/Spectrum.cpp b/YSGraphic_Core/plottable/Spectrum.cpp new file mode 100644 index 0000000..8578526 --- /dev/null +++ b/YSGraphic_Core/plottable/Spectrum.cpp @@ -0,0 +1,187 @@ +#include "Spectrum_p.h" + + + +namespace YSG { + Q_Ptr_cpp(Spectrum) + PROP_P(Spectrum, FrequentAxis*, frequentAxis) + PROP_P(Spectrum, Axis*, powerAxis); + PROP_G(Spectrum, int, frequentPointSize); + PROP_G(Spectrum, Range, frequentRange); + PROP_P(Spectrum, double, middleSweepFrequent); + PROP_P(Spectrum, Range, sweepFrequentRange); + PROP_P(Spectrum, bool, useMaxLine); + PROP_P(Spectrum, bool, useMinLine); + PROP_P(Spectrum, bool, useMaxMarker); + PROP_P(Spectrum, bool, useMinMarker); + PROP_P(Spectrum, bool, useSweepFrequentRect); + PROP_P(Spectrum, QBrush, maxBrush); + PROP_P(Spectrum, QBrush, curBrush); + PROP_P(Spectrum, QBrush, minBrush); + PROP_P(Spectrum, QPen, maxPen); + PROP_P(Spectrum, QPen, curPen); + PROP_P(Spectrum, QPen, minPen); + PROP_P(Spectrum, QPen, selectMarkerPen); + PROP_P(Spectrum, QPen, markerPen); + PROP_P(Spectrum, QPen, middleFrequentPen); + PROP_P(Spectrum, QBrush, sweepRectBrush); + + void Spectrum::set_frequentPointSize(int frequentPointSize) { + INIT_SET(Spectrum) + sc->frequentPointSize = frequentPointSize; + sc->updateFrequentPointSize(sc->frequentPointSize, sc->frequentRange); + } + + void Spectrum::set_frequentRange(Range frequentRange){ + INIT_SET(Spectrum) + sc->frequentRange = frequentRange; + sc->updateFrequentPointSize(sc->frequentPointSize, sc->frequentRange); + } + + + Spectrum::Builder::Builder(FrequentAxis* frequentAxis, Axis* powerAxis) { + ASSERT(frequentAxis->mPlot != nullptr, "Afterglow build error! frequentAxis->mPlot == nullptr"); + ASSERT(frequentAxis->mPlot == powerAxis->mPlot, "Afterglow build error! frequentAxis->mPlot != powerAxis->mPlot"); + plot = frequentAxis->mPlot; + this->frequentAxis = frequentAxis; + this->powerAxis = powerAxis; + } + Spectrum* Spectrum::Builder::build() { + auto ret = new Spectrum; + ret->init(plot, layerName); + SpectrumPrivate* pd = ret->d(); + SpectrumRenderState* sc = pd->renderStateCache(); + PROP_R(FrequentAxis*, frequentAxis) + PROP_R(Axis*, powerAxis); + PROP_R(int, frequentPointSize); + PROP_R(Range, frequentRange); + PROP_R(double, middleSweepFrequent); + PROP_R(Range, sweepFrequentRange); + PROP_R(bool, useMaxLine); + PROP_R(bool, useMinLine); + PROP_R(bool, useMaxMarker); + PROP_R(bool, useMinMarker); + PROP_R(bool, useSweepFrequentRect); + PROP_R(QBrush, maxBrush); + PROP_R(QBrush, curBrush); + PROP_R(QBrush, minBrush); + PROP_R(QPen, maxPen); + PROP_R(QPen, curPen); + PROP_R(QPen, minPen); + PROP_R(QPen, selectMarkerPen); + PROP_R(QPen, markerPen); + PROP_R(QPen, middleFrequentPen); + PROP_R(QBrush, sweepRectBrush); + sc->updateFrequentPointSize(sc->frequentPointSize, sc->frequentRange); + return ret; + } + + Spectrum* SpectrumPrivate::q() { + return reinterpret_cast(qPtr); + } + void SpectrumPrivate::draw(QPainter* painter) { + auto s = renderState(); + PerformanceShower *shower = q()->mPlot->d->mShower; + if(shower) { + shower->mInfoMap["mFrequentPointSize"] = PerformanceLine(QString("mFrequentPointSize:%1") + .arg(s->frequentPointSize)); + } + s->draw(painter); + if(q()->hoverOK(q())) { + q()->drawHover(painter, s->frequentAxis, s->powerAxis); + } + } + + + void Spectrum::giveData(const QVector &lineData){ + if(!ok()) return; + INIT_SET(Spectrum) + sc->setFrenquentData(lineData); + } + + void Spectrum::addCustomMarker(double frequent){ + INIT_SET(Spectrum) + sc->markerList.append(Marker{frequent, false}); + } + + void Spectrum::addCustomLineMarker(double frequent){ + INIT_SET(Spectrum) + sc->markerList.append(Marker{frequent, true}); + } + + void Spectrum::removeCustomMarker(double frequent){ + INIT_SET(Spectrum) + for (Marker &marker: sc->markerList) { + if (qFuzzyCompare(marker.frequent, frequent)) { + sc->markerList.removeOne(marker); + break; + } + } + } + + void Spectrum::removeCurrentMarker(SRC src) { + INIT_SET(Spectrum) + sc->markerList.removeAt(sc->curSelectIndex); + } + + void Spectrum::clearAllCustomMarker(SRC src) { + INIT_SET(Spectrum) + sc->markerList.clear(); + } + + double Spectrum::getMarkFrequent(int markIndex, SRC src) { + INIT_GET(Spectrum) + if (markIndex < 0 || markIndex >= renderState->markerList.size()) + return -1; + return renderState->markerList[markIndex].frequent; + } + + void Spectrum::setMarkerFrequent(int markerIndex, double frequent) { + INIT_SET(Spectrum) + QVector &markers = sc->markerList; + if (markerIndex < 0 || markerIndex >= markers.size()) + return; + markers[markerIndex].frequent = frequent; + } + + void Spectrum::setCurrentMarkerFrequent(double frequent) { + INIT_SET(Spectrum) + sc->setCurFrequent(frequent); + } + + int Spectrum::selectLineMarkerSize(SRC src) { + INIT_GET(Spectrum) + return renderState->markerList.size(); + } + + int Spectrum::curSelectMarkerIndex(SRC src) { + INIT_GET(Spectrum) + return renderState->curSelectIndex; + } + + void Spectrum::setSelectedLineMarker(int markerIndex){ + INIT_SET(Spectrum) + sc->setSelectIndex(markerIndex); + } + + void Spectrum::selectNext(SRC src) { + INIT_SET(Spectrum) + sc->selectNextMarker(); + } + + void Spectrum::selectPrevious(SRC src) { + INIT_SET(Spectrum) + sc->selectPrevMarker(); + } + + void Spectrum::unSelectMarker(SRC src) { + INIT_SET(Spectrum) + sc->unSelect(); + } + + double Spectrum::getPower(double frequent, bool& ok, SRC src) { + INIT_GET(Spectrum) + return renderState->getY(frequent, ok); + } + +} diff --git a/YSGraphic_Core/plottable/Spectrum.h b/YSGraphic_Core/plottable/Spectrum.h new file mode 100644 index 0000000..8299abe --- /dev/null +++ b/YSGraphic_Core/plottable/Spectrum.h @@ -0,0 +1,108 @@ +#pragma once +#include "HoverInfo.h" +#include "Spectrum.h" + + +namespace YSG { + class FrequentAxis; + struct SpectrumPrivate; + class LIB_DECL Spectrum : public RenderAble, public HoverInfoRenderAble { + public: + Q_Ptr2(Spectrum) + PROP(FrequentAxis*, frequentAxis) + PROP(Axis*, powerAxis); + PROP(int, frequentPointSize); + PROP(Range, frequentRange); + PROP(double, middleSweepFrequent); + PROP(Range, sweepFrequentRange); + PROP(bool, useMaxLine); + PROP(bool, useMinLine); + PROP(bool, useMaxMarker); + PROP(bool, useMinMarker); + PROP(bool, useSweepFrequentRect); + PROP(QBrush, maxBrush); + PROP(QBrush, curBrush); + PROP(QBrush, minBrush); + PROP(QPen, maxPen); + PROP(QPen, curPen); + PROP(QPen, minPen); + PROP(QPen, selectMarkerPen); + PROP(QPen, markerPen); + PROP(QPen, middleFrequentPen); + PROP(QBrush, sweepRectBrush); + void giveData(const QVector &lineData); + double getPower(double frequent, bool& ok, SRC=SRC::Cache); + void addCustomMarker(double frequent); + void addCustomLineMarker(double frequent); + void removeCustomMarker(double frequent); + void removeCurrentMarker(SRC=SRC::Auto); + void clearAllCustomMarker(SRC=SRC::Auto); + int selectLineMarkerSize(SRC=SRC::Auto); + int curSelectMarkerIndex(SRC=SRC::Auto); + void setSelectedLineMarker(int markerIndex); + void selectNext(SRC=SRC::Auto); + void unSelectMarker(SRC=SRC::Auto); + void selectPrevious(SRC=SRC::Auto); + double getMarkFrequent(int markIndex, SRC=SRC::Auto); + void setMarkerFrequent(int markerIndex, double frequent); + void setCurrentMarkerFrequent(double frequent); + struct Builder; + }; + + struct Spectrum_PROP { + FrequentAxis *frequentAxis{}; + Axis *powerAxis{}; + int frequentPointSize{}; + Range frequentRange{}; + double middleSweepFrequent = 50; + Range sweepFrequentRange{40, 60}; + bool useMaxLine = false; + bool useMinLine = false; + bool useMaxMarker = false; + bool useMinMarker = false; + bool useSweepFrequentRect = false; + QBrush maxBrush; + QBrush curBrush; + QBrush minBrush; + QPen maxPen = QPen(Qt::red); + QPen curPen = QPen(Qt::green); + QPen minPen = QPen(Qt::white); + QPen selectMarkerPen = QPen(Qt::darkBlue, 2); + QPen markerPen = QPen(Qt::red); + QPen middleFrequentPen = QPen(Qt::red); + QBrush sweepRectBrush = QBrush(QColor(255, 255, 0, 100)); + Spectrum_PROP() { + maxBrush = QBrush(QColor(), Qt::NoBrush); + curBrush = QBrush(QColor(), Qt::NoBrush); + minBrush = QBrush(QColor(), Qt::NoBrush); + } + }; + struct LIB_DECL Spectrum::Builder : Spectrum_PROP { + PROP_B(FrequentAxis*, frequentAxis) + PROP_B(Axis*, powerAxis); + PROP_B(int, frequentPointSize); + PROP_B(Range, frequentRange); + PROP_B(double, middleSweepFrequent); + PROP_B(Range, sweepFrequentRange); + PROP_B(bool, useMaxLine); + PROP_B(bool, useMinLine); + PROP_B(bool, useMaxMarker); + PROP_B(bool, useMinMarker); + PROP_B(bool, useSweepFrequentRect); + PROP_B(QBrush, maxBrush); + PROP_B(QBrush, curBrush); + PROP_B(QBrush, minBrush); + PROP_B(QPen, maxPen); + PROP_B(QPen, curPen); + PROP_B(QPen, minPen); + PROP_B(QPen, selectMarkerPen); + PROP_B(QPen, markerPen); + PROP_B(QPen, middleFrequentPen); + PROP_B(QBrush, sweepRectBrush); + SETTER(QString, layerName, "plottable") + Builder(FrequentAxis* frequentAxis, Axis* axis); + Spectrum* build(); + protected: + Plot *plot{}; + }; +} diff --git a/YSGraphic_Core/plottable/Spectrum_p.h b/YSGraphic_Core/plottable/Spectrum_p.h new file mode 100644 index 0000000..73b0f3e --- /dev/null +++ b/YSGraphic_Core/plottable/Spectrum_p.h @@ -0,0 +1,226 @@ +#pragma once +#include "../Axis/FrequentAxis.h" +#include "../RenderAble.h" +#include "../base/PerformanceShower_p.h" +#include "../base/Plot_p.h" +#include "HoverInfo.h" +#include "Spectrum.h" +#include "YSGraphic_Core/base/algorithm.hpp" + + +namespace YSG { + + struct Marker { + Marker() = default; + Marker(double frequent, bool drawLine) : frequent(frequent), drawLine(drawLine) { + + } + double frequent{}; + bool drawLine = true; + bool operator==(const Marker &other) const { + return qFuzzyCompare(frequent, other.frequent) && drawLine == other.drawLine; + } + }; + struct SpectrumTempData : TempData {}; + + struct SpectrumRenderState : RenderState, HoverInfoRenderState, Spectrum_PROP { + QVector markerList; + int curSelectIndex = -1; + double minMarkerKey{}; + double minMarkerValue{}; + double maxMarkerKey{}; + double maxMarkerValue{}; + QVector frequents; + QVector maxPowers, curPowers, minPowers; + + void unSelect() { + curSelectIndex = -1; + } + void selectNextMarker() { + if(markerList.empty()) return; + curSelectIndex = (curSelectIndex + 1) % markerList.size(); + } + void selectPrevMarker() { + if(markerList.empty()) return; + curSelectIndex = (curSelectIndex - 1 + markerList.size()) % markerList.size(); + } + void setSelectIndex(int index) { + if (index < 0 || index >= markerList.size()) { + qDebug() << "SpectrogramLineCache::setSelectIndex index out of range"; + return; + } + curSelectIndex = index; + } + + void setCurFrequent(double frequent) { + setFrequent(curSelectIndex, frequent); + } + + void setFrequent(int index, double frequent) { + if (index < 0 || index >= markerList.size()) { + qDebug() << "SpectrogramLineCache::setSelectIndex index out of range"; + return; + } + markerList[index].frequent = frequent; + } + + void updateFrequentPointSize(int frequentPointSize, Range frequentRange) { + if(frequentPointSize == 0) return; + maxPowers.resize(frequentPointSize); + curPowers.resize(frequentPointSize); + minPowers.resize(frequentPointSize); + frequents.resize(frequentPointSize); + double cur = frequentRange.lower; + double step = frequentRange.length() / (frequentPointSize - 1); + frequents[0] = frequentRange.lower; + for(int i = 1; i < frequentPointSize; ++i) { + cur += step; + frequents[i] = cur; + } + maxPowers.fill(frequentRange.lower); + minPowers.fill(frequentRange.upper); + } + + void setFrenquentData(const QVector& lineData) { + if(lineData.size() != frequentPointSize) { + qDebug() << "setFrenquentData 数据大小不对 " << lineData.size() << " mFrequentPointSize == " << frequentPointSize; + return; + } + curPowers = lineData; + curPowers.detach(); + double min = std::numeric_limits::max(); + double max = std::numeric_limits::min(); + int minIndex = 0, maxIndex = 0; + for (int i = 0; i < frequentPointSize; ++i) { + double &curData = curPowers[i]; + if (useMaxLine && curData > maxPowers.at(i)) { + maxPowers[i] = curData; + } + if (useMinLine && curData < minPowers.at(i)) { + minPowers[i] = curData; + } + if (useMaxMarker) { + if (curData > max) { + max = curData; + maxIndex = i; + } + } + if (useMinMarker) { + if (curData < min) { + min = curData; + minIndex = i; + } + } + } + maxMarkerKey = frequents[maxIndex]; + maxMarkerValue = curPowers[maxIndex]; + minMarkerKey = frequents[minIndex]; + minMarkerValue = curPowers[minIndex]; + } + + [[nodiscard]] double getY(const double &x, bool& ok) { + SpinLockGuard dd(&dPtr->mBufferLock); + int i = binary_search(x, frequents, ok); + if(!ok) return -1; + const double &x1 = frequents.at(i); + const double &x2 = frequents.at(i + 1); + const double &y1 = curPowers.at(i); + const double &y2 = curPowers.at(i + 1); + double k = (y2 - y1) / (x2 - x1); + double b = y1 - k * x1; + return (int) (k * x + b); + } + + void drawLine(QPainter* painter, const QVector& ys, const QPen& pen, const QBrush& brush) { + if(frequents.empty()) return; + int n = frequents.size(); + QVector points(n+2); + double upperBoundPixel = powerAxis->coordToPixel(powerAxis->coordRange().upper, SRC::Render); + points.first() = QPointF(frequentAxis->coordToPixel(frequents.first(), SRC::Render), upperBoundPixel); + points.last() = QPointF(frequentAxis->coordToPixel(frequents.last(), SRC::Render), upperBoundPixel); + for (int i = 0; i < n; ++i) { + double x = frequentAxis->coordToPixel(frequents.at(i), SRC::Render); + double y = powerAxis->coordToPixel(ys.at(i), SRC::Render); + points[i + 1] = QPointF(x, y); + } + painter->setPen(pen); + if(pen.style() != Qt::NoPen) painter->drawPolyline(points.data() + 1, n); + painter->setBrush(brush); + if(brush.style() != Qt::NoBrush) painter->drawPolygon(points.data(), n + 2); + } + + void draw(QPainter* painter) { + if(useMaxLine) drawLine(painter, maxPowers, maxPen, maxBrush); + drawLine(painter, curPowers, curPen, curBrush); + if(useMinLine) drawLine(painter, minPowers, minPen, minBrush); + if (useMaxMarker) drawMarker(painter, maxMarkerKey, maxMarkerValue, false, false); + if (useMinMarker) drawMarker(painter, minMarkerKey, minMarkerValue, false, false); + for (Marker &customMarker: markerList) { + bool ok; + double value = getY(customMarker.frequent, ok); + if(ok) drawMarker(painter, customMarker.frequent, value, customMarker.drawLine, + markerList.indexOf(customMarker) == curSelectIndex); + } + if (useSweepFrequentRect) { + drawAxisRect(painter, sweepFrequentRange, middleSweepFrequent); + } + } + + void drawMarker(QPainter *painter, double key, double value, bool isLine = false, bool isSelected = false) { + double x = frequentAxis->coordToPixel(key, SRC::Render); + double y = powerAxis->coordToPixel(value, SRC::Render); + painter->setPen(isSelected ? selectMarkerPen : markerPen); + QString text1 = QString::number(key, 'f', 2) + frequentAxis->unitText(); + QString text2 = QString::number(value, 'f', 2) + powerAxis->unitText(); + QFontMetrics fm(painter->font()); + int h = fm.height(); + int w = qMax(fm.horizontalAdvance(text1), fm.horizontalAdvance(text2)); + painter->drawText(QPointF(x - (double) w / 2, y - h * 2), text1); + painter->drawText(QPointF(x - (double) w / 2, y - h), text2); + if (!isLine) { + painter->drawEllipse(QPointF(x, y), 2, 2); + } else { + painter->drawLine(QPointF(x, powerAxis->y()), QPointF(x, powerAxis->pixelSize())); + } + } + + void drawAxisRect(QPainter *painter, Range range, double middleX) const { + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(sweepRectBrush); + double pixelMin = frequentAxis->coordToPixel(range.lower, SRC::Render); + double pixelMax = frequentAxis->coordToPixel(range.upper, SRC::Render); + double pixelMiddle = frequentAxis->coordToPixel(middleX, SRC::Render); + Range r = powerAxis->coordRange(); + double min = powerAxis->coordToPixel(r.lower, SRC::Render); + double max = powerAxis->coordToPixel(r.upper, SRC::Render); + painter->drawRect(QRectF(pixelMin, min, pixelMax - pixelMin, max - min)); + painter->setPen(middleFrequentPen); + painter->setPen(QPen(QColor(255, 0, 0))); + painter->drawLine(QPointF(pixelMiddle, min), QPointF(pixelMiddle, max)); + painter->restore(); + } + }; + + struct SpectrumPrivate : RenderData { + D_Ptr_3(Spectrum) + Spectrum* q(); + void draw(QPainter* painter) override; + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + auto s = renderState(); + auto sc = renderStateCache(); + + } + + bool selectTest(const QPointF& pos) override { + auto sc = renderStateCache(); + double frequent = sc->frequentAxis->pixelToCoord(pos.x(), SRC::Render); + bool ok; + double power = sc->getY(frequent, ok); + if(!ok) return false; + return qAbs(power - sc->powerAxis->pixelToCoord(pos.y(), SRC::Render)) < 4; + } + }; +} diff --git a/YSGraphic_Core/plottable/SweepFrequent.cpp b/YSGraphic_Core/plottable/SweepFrequent.cpp new file mode 100644 index 0000000..7c3eae6 --- /dev/null +++ b/YSGraphic_Core/plottable/SweepFrequent.cpp @@ -0,0 +1,49 @@ +#include + +#include "SweepFrequent_p.h" +#include "../Axis/AbsAxis.h" + + +namespace YSG { + Q_Ptr_cpp(SweepFrequent) + PROP_P(SweepFrequent, Axis*, frequentAxis) + PROP_P(SweepFrequent, Axis*, powerAxis) + PROP_P(SweepFrequent, Range, frequentRange) + PROP_P(SweepFrequent, int, blockFrequentPointSize) + PROP_P(SweepFrequent, int, blockNum) + PROP_P(SweepFrequent, QPen, pen) + PROP_P(SweepFrequent, QPen, curFrequentPen) + + void SweepFrequent::giveData(const QVector& data) { + if(!ok()) return; + INIT_SET(SweepFrequent) + if(data.size() != sc->blockFrequentPointSize) { + qDebug() << "error data size == " << data.size() << " BlockFrquentPointSize" << sc->blockFrequentPointSize; + return; + } + td->mData.push_front(data); + } + + + SweepFrequent::Builder::Builder(Axis* frequentAxis, Axis* powerAxis) { + ASSERT(frequentAxis->mPlot != nullptr, "Afterglow build error! frequentAxis->mPlot == nullptr"); + ASSERT(frequentAxis->mPlot == powerAxis->mPlot, "Afterglow build error! frequentAxis->mPlot != powerAxis->mPlot"); + plot = frequentAxis->mPlot; + this->frequentAxis = frequentAxis; + this->powerAxis = powerAxis; + } + SweepFrequent* SweepFrequent::Builder::build() { + auto ret = new SweepFrequent(); + ret->init(plot, layerName); + SweepFrequentPrivate* pd = ret->d(); + SweepFrequentRenderState* sc = pd->renderStateCache(); + PROP_R(Axis*, frequentAxis) + PROP_R(Axis*, powerAxis) + PROP_R(Range, frequentRange) + PROP_R(int, blockFrequentPointSize) + PROP_R(int, blockNum) + PROP_R(QPen, pen) + PROP_R(QPen, curFrequentPen) + return ret; + } +} diff --git a/YSGraphic_Core/plottable/SweepFrequent.h b/YSGraphic_Core/plottable/SweepFrequent.h new file mode 100644 index 0000000..159626a --- /dev/null +++ b/YSGraphic_Core/plottable/SweepFrequent.h @@ -0,0 +1,50 @@ +#pragma once +#include "../RenderAble.h" + +namespace YSG { + + struct SweepFrequentPrivate; + class LIB_DECL SweepFrequent : public RenderAble { + public: + Q_Ptr2(SweepFrequent) + void giveData(const QVector& data); + PROP(Axis*, frequentAxis) + PROP(Axis*, powerAxis) + PROP(Range, frequentRange) + PROP(int, blockFrequentPointSize) + PROP(int, blockNum) + PROP(QPen, pen) + PROP(QPen, curFrequentPen) + struct Builder; + }; + + struct SweepFrequent_Prop { + Axis *frequentAxis{}; + Axis *powerAxis{}; + Range frequentRange{}; + int blockFrequentPointSize{}; + int blockNum{}; + QPen pen; + QPen curFrequentPen; + SweepFrequent_Prop() { + pen = QPen(Qt::yellow); + curFrequentPen = QPen(Qt::red); + curFrequentPen.setWidth(2); + } + }; + struct LIB_DECL SweepFrequent::Builder : SweepFrequent_Prop { + PROP_B(Axis*, frequentAxis) + PROP_B(Axis*, powerAxis) + PROP_B(Range, frequentRange) + PROP_B(int, blockFrequentPointSize) + PROP_B(int, blockNum) + PROP_B(QPen, pen) + PROP_B(QPen, curFrequentPen) + SETTER(QString, layerName, "plottable") + Builder(Axis* frequentAxis, Axis* powerAxis); + SweepFrequent* build(); + protected: + Plot *plot{}; + }; +} + diff --git a/YSGraphic_Core/plottable/SweepFrequent_p.h b/YSGraphic_Core/plottable/SweepFrequent_p.h new file mode 100644 index 0000000..cfd0b00 --- /dev/null +++ b/YSGraphic_Core/plottable/SweepFrequent_p.h @@ -0,0 +1,94 @@ +#pragma once +#include "SweepFrequent.h" +#include "../Axis/Axis.h" +#include "../base/PerformanceShower_p.h" +#include "../base/Plot.h" +#include "../base/Plot_p.h" + + +namespace YSG { + + struct SweepFrequentRenderState : YSG::RenderState, SweepFrequent_Prop { + + }; + + struct SweepFrequentTempData : YSG::TempData { + std::list> mData; + }; + + struct SweepFrequentPrivate : YSG::RenderData { + D_Ptr(SweepFrequent) + QVector mFrequents; + int mOffset; + int mBlockFrquentPointSize{}; + int mBlockNum; + Range mFrequentRange; + bool mFristLoop = true; + void setUpFrequents(int blockFrquentPointSize, int blockNum, Range frequentRange) { + mBlockFrquentPointSize = blockFrquentPointSize; + mBlockNum = blockNum; + mFrequentRange = frequentRange; + SweepFrequentRenderState *s = renderState(); + int pointSize = blockNum * blockFrquentPointSize; + mFrequents.resize(pointSize); + int spaceSize = pointSize - 1; + double step = frequentRange.length() / spaceSize; + double cur = frequentRange.lower; + for(int i = 0; i < spaceSize; ++i) { + mFrequents[i].setX(cur); + cur += step; + } + mFrequents.last().setX(frequentRange.upper); + mFristLoop = true; + } + protected: + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + SweepFrequentRenderState *s = renderState(); + SweepFrequentTempData *d = tempData(); + if( + mBlockFrquentPointSize != s->blockFrequentPointSize || + mBlockNum != s->blockNum || + mFrequentRange != s->frequentRange + ) { + setUpFrequents(s->blockFrequentPointSize, s->blockNum, s->frequentRange); + mOffset = 0; + } + for(const QVector& data : d->mData) { + QPointF* start = &mFrequents[mOffset * mBlockFrquentPointSize]; + for(int i = 0; i < mBlockFrquentPointSize; ++i) { + start[i].setY(data[i]); + } + mOffset++; + if(mOffset == mBlockNum) { + mOffset = 0; + mFristLoop = false; + } + } + d->mData.clear(); + } + + void draw(QPainter* painter) override { + SweepFrequentRenderState *s = renderState(); + int n = mFrequents.size(); + QVector data(n); + { + Cacl prepareData("轴映射数据耗时: %1 avg: %2", q()->mPlot->d->mShower); + for(int i = 0; i < n; ++i) { + const QPointF& p = mFrequents[i]; + data[i].rx() = s->frequentAxis->coordToPixel(p.x(), SRC::Render); + data[i].ry() = s->powerAxis->coordToPixel(p.y(), SRC::Render); + } + } + + painter->setPen(s->pen); + painter->drawPolyline(data.data(), !mFristLoop ? data.size() : mOffset * mBlockFrquentPointSize); + + painter->setPen(s->curFrequentPen); + double f = mOffset * mFrequentRange.length()/(mBlockNum-1.0); + double ff = s->frequentAxis->coordToPixel(f, SRC::Render); + painter->drawLine(QPointF(ff, 0), QPointF(ff, q()->mPlot->height())); + } + }; +} diff --git a/YSGraphic_Core/plottable/WaterFall.cpp b/YSGraphic_Core/plottable/WaterFall.cpp new file mode 100644 index 0000000..6a13745 --- /dev/null +++ b/YSGraphic_Core/plottable/WaterFall.cpp @@ -0,0 +1,41 @@ +#include "WaterFall_p.h" +#include "../base/Plot.h" + + +namespace YSG { + Q_Ptr_cpp(WaterFall) + PROP_P(WaterFall, FrequentAxis*, frequentAxis) + PROP_P(WaterFall, TimeAxis*, timeAxis) + PROP_P(WaterFall, Range, frequentRange) + PROP_P(WaterFall, Range, powerRange) + PROP_P(WaterFall, int, frequentPointSize) + + void WaterFall::giveData(int tick, const QVector& data) { + if(!ok()) return; + INIT_SET(WaterFall) + td->mDataList.push_back({tick, data}); + } + + WaterFall::Builder::Builder(FrequentAxis* frequentAxis, TimeAxis* timeAxis) { + ASSERT(frequentAxis->mPlot != nullptr, "Afterglow build error! frequentAxis->mPlot == nullptr"); + ASSERT(frequentAxis->mPlot == timeAxis->mPlot, "Afterglow build error! frequentAxis->mPlot != timeAxis->mPlot"); + plot = frequentAxis->mPlot; + this->frequentAxis = frequentAxis; + this->timeAxis = timeAxis; + } + WaterFall* WaterFall::Builder::build() { + auto ret = new WaterFall(); + ret->init(plot, layerName); + WaterFallPrivate* pd = ret->d(); + WaterFallRenderState* sc = pd->renderStateCache(); + PROP_R(FrequentAxis*, frequentAxis) + PROP_R(TimeAxis*, timeAxis) + PROP_R(Range, frequentRange) + PROP_R(Range, powerRange) + PROP_R(int, frequentPointSize) + return ret; + } + +} + + diff --git a/YSGraphic_Core/plottable/WaterFall.h b/YSGraphic_Core/plottable/WaterFall.h new file mode 100644 index 0000000..f6b5061 --- /dev/null +++ b/YSGraphic_Core/plottable/WaterFall.h @@ -0,0 +1,41 @@ +#pragma once +#include "HoverInfo.h" + +namespace YSG { + struct WaterFallPrivate; + class FrequentAxis; + class TimeAxis; + // 如果timeAxis是竖轴,并且timePointSize 和竖着的像素点数没倍数关系,那么随机数整体看着会闪烁 + // 如果和竖着的像素有倍数关系,但是timePointSize 是奇数,那么看着中间会闪烁 + class LIB_DECL WaterFall : public RenderAble, public HoverInfoRenderAble{ + public: + Q_Ptr2(WaterFall) + void giveData(int tick, const QVector& data); + PROP(FrequentAxis*, frequentAxis) + PROP(TimeAxis*, timeAxis) + PROP(Range, frequentRange) + PROP(Range, powerRange) + PROP(int, frequentPointSize) + struct Builder; + }; + struct WaterFall_Prop { + FrequentAxis *frequentAxis{}; + TimeAxis *timeAxis{}; + Range frequentRange{0, 10}; + Range powerRange{0, 10}; + int frequentPointSize{}; + int tickSpace = 50; + }; + struct LIB_DECL WaterFall::Builder : WaterFall_Prop { + PROP_B(FrequentAxis*, frequentAxis) + PROP_B(TimeAxis*, timeAxis) + PROP_B(Range, frequentRange) + PROP_B(Range, powerRange) + PROP_B(int, frequentPointSize) + SETTER(QString, layerName, "plottable") + Builder(FrequentAxis* frequentAxis, TimeAxis* timeAxis); + WaterFall* build(); + protected: + Plot *plot{}; + }; +} diff --git a/YSGraphic_Core/plottable/WaterFall_p.h b/YSGraphic_Core/plottable/WaterFall_p.h new file mode 100644 index 0000000..a64ec77 --- /dev/null +++ b/YSGraphic_Core/plottable/WaterFall_p.h @@ -0,0 +1,156 @@ +#pragma once +#include +#include + +#include "../Axis/FrequentAxis_p.h" +#include "../Axis/TimeAxis_p.h" +#include "../RenderAble.h" +#include "../base/Global.h" +#include "../base/PerformanceShower_p.h" +#include "../base/Plot.h" +#include "../base/Plot_p.h" +#include "WaterFall.h" + + +namespace YSG { + + + + struct WaterFallData { + int mTick; + QVector mFrequent; + }; + + struct WaterFallTempData : TempData { + std::list mDataList; + }; + + struct WaterFallRenderState : RenderState, HoverInfoRenderState, WaterFall_Prop { + + }; + + struct WaterFallPrivate : RenderData { + D_Ptr(WaterFall) + YSG::MutiRingBuffer mutiBuffer; + QImage image; + int listByteSize{}; + bool selectTest(const QPointF& pos) override {return true;} + void mouseMoveEvent(QMouseEvent* event) override { + WaterFallRenderState* sc = renderStateCache(); + { + SpinLockGuard guard(&mBufferLock); + + } + } + + void prepareData() override { + SpinLockGuard guard(&mBufferLock); + loadCache(); + WaterFallTempData* data = tempData(); + const WaterFallRenderState* s = renderState(); + int colCount = s->frequentPointSize, rowCount = s->timeAxis->timePointSize(); + listByteSize = colCount * (int)sizeof(double); + if(mutiBuffer.n != rowCount || mutiBuffer.buffers.empty() || mutiBuffer.buffers[0].l != listByteSize) { + mutiBuffer.resize({listByteSize, sizeof(int)}, rowCount, rowCount * 10); + } + for(WaterFallData& wd : data->mDataList) { + QVector& frequentData = wd.mFrequent; + if(frequentData.size() != colCount) { + qDebug() << "WaterFall 舍弃数据 colCount == " << colCount << + " dataSize == " << frequentData.size() << + "s FrequentRectCount==" << s->frequentPointSize << + "sc FrequentRectCount==" << renderStateCache()->frequentPointSize; + break; + } + mutiBuffer.pushData({frequentData.data(), &wd.mTick}); + } + data->mDataList.clear(); + } + + + + void draw(QPainter* painter) override { + WaterFallRenderState* s = renderState(); + PerformanceShower *shower = q()->mPlot->d->mShower; + if(shower) { + shower->mInfoMap["FrequentRectCount"] = PerformanceLine(QString("FrequentRectCount:%1 timePointSize: %2") + .arg(s->frequentPointSize).arg(s->timeAxis->timePointSize())); + } + WaterFallTempData* data = tempData(); + AbsAxis *hAxis = s->frequentAxis; + TimeAxis *vAxis = s->timeAxis; + Range hRange = s->frequentRange, vRange = s->timeAxis->coordRange(); + + TimeTicker& maker = s->timeAxis->d()->mTimeTicker; + if(!maker.mStartCoordToEndCoord){ + std::swap(vRange.lower, vRange.upper); + } + int colCount = s->frequentPointSize, rowCount = s->timeAxis->timePointSize(); + if(image.width() != colCount || image.height() != rowCount) { + image = QImage(colCount, rowCount, QImage::Format_ARGB32_Premultiplied); + image.fill(q()->mPlot->backgroundColor()); + PerformanceShower *shower = q()->mPlot->d->mShower; + if(shower) { + shower->mInfoMap["colCount, rowCount"] = PerformanceLine(QString("colCount:%1, rowCount:%2") + .arg(colCount).arg(rowCount)); + } + } + double valueStartCoord = s->powerRange.lower; + double rate = 256.0 / s->powerRange.size(); + + + auto data2 = mutiBuffer.buffers[0].list(); + auto l2 = mutiBuffer.buffers[1].list(); + QVector& mColorMap = Global::instance()->mColorMap; + { + Cacl c("waterfull setTime:%1", q()->mPlot->d->mShower); + int startTick = (int)s->timeAxis->startCoord(); + // 不用 mMutiBuffer.buffers[0].f 的话,可能会出现一种情况那就是 + // 意外的通过了 tickOffset < 0 || tickOffset >= rowCount 导致显示0值时的颜色 + // 这种意外的原因时0值的位置 默认tick值0 在当前坐标轴显示的tick范围内 + for(int row = 0; row < mutiBuffer.buffers[0].f; ++row) { + auto list = (double*)(data2+row*listByteSize); + int tick = *(l2 + row); + int tickOffset = tick - startTick; + // 瀑布图数据对应的 tick 可能不是 timeTick轴能显示的 + if(tickOffset < 0 || tickOffset >= rowCount) continue; + QRgb* scanLine = reinterpret_cast(image.scanLine(tickOffset)); + + + //std::string color_offset_line = "row: " + std::to_string(row) + // + " tick: " + std::to_string(tick) + // + " tick off: " + std::to_string(tickOffset) + " "; + for (int col = 0; col < colCount; ++col) { + int colorOffset = int((list[col] - valueStartCoord) * rate); + if (colorOffset < 0 || colorOffset > 255) { + qDebug() << "colorOffset:" << colorOffset << " value: " << list[col] << s->frequentRange; + } + //color_offset_line += std::to_string(colorOffset) + ","; + scanLine[col] = mColorMap.at(clamp(colorOffset, 0, 255)); + } + + //std::cout << color_offset_line<< std::endl; + } + } + + + double x = hAxis->coordToPixel(hRange.lower, SRC::Render); + double y = vAxis->coordToPixel(vRange.lower, SRC::Render); + double w = hAxis->coordToPixel(hRange.upper, SRC::Render) - x; + double h = vAxis->coordToPixel(vRange.upper, SRC::Render) - y; + Range &&hAxisRange = hAxis->coordRange(), &&vAxisRange = vAxis->coordRange(); + double hr = ((hAxisRange.lower < hAxisRange.upper) ^ (hRange.lower < hRange.upper)) ? -1 : 1; + double vr = ((vAxisRange.lower < vAxisRange.upper) ^ (vRange.lower < vRange.upper)) ? -1 : 1; + + painter->save(); + painter->scale(hr, vr); + painter->drawImage(QRectF(hr * x, vr * y, hr * w, vr * h), image); + painter->restore(); + + if(q()->hoverOK(q())) { + q()->drawHover(painter, s->frequentAxis, s->timeAxis); + } + } + }; + +} diff --git a/YSGraphic_Core/resource/icon b/YSGraphic_Core/resource/icon new file mode 100644 index 0000000..0f9362d --- /dev/null +++ b/YSGraphic_Core/resource/icon @@ -0,0 +1,13 @@ + + +https://blog.csdn.net/yuan2019035055/article/details/145653647 + + + + +// GIF 动画 +https://zhuanlan.zhihu.com/p/349035523 + + +// 主要在这里找的图标 +https://fonts.google.com/icons?icon.query=filter+&icon.size=24&icon.color=%235985E1 \ No newline at end of file diff --git a/YSGraphic_Core/resource/resource.qrc b/YSGraphic_Core/resource/resource.qrc new file mode 100644 index 0000000..6c43cdc --- /dev/null +++ b/YSGraphic_Core/resource/resource.qrc @@ -0,0 +1,7 @@ + + + turbo.colorMap + you_jian_tou.svg + + + diff --git a/YSGraphic_Core/resource/turbo.colorMap b/YSGraphic_Core/resource/turbo.colorMap new file mode 100644 index 0000000..1e25aed --- /dev/null +++ b/YSGraphic_Core/resource/turbo.colorMap @@ -0,0 +1,4 @@ +;0C2J3Q4X5_!6f$7m'8s*9y-:/;2<5=8>;?>?@@CAFAIBKBNCQDTDVDYE\E^EaFdFfFiFkFnGqGsGvGxG{G}FFFFFEEDCBA@>=;:87531/.,*('%#"  "%'*,/258 \ No newline at end of file diff --git a/main.cmake b/main.cmake new file mode 100644 index 0000000..91f2518 --- /dev/null +++ b/main.cmake @@ -0,0 +1,25 @@ +w_use3rd(Qt5) + + +set(other_library Qt5::Widgets Qt5::Core Qt5::Svg Qt5::Xml Qt5::PrintSupport Qt5::Multimedia Qt5::QuickWidgets) +find_package(OpenGL) +if(OpenGL_FOUND) + list(APPEND other_library OpenGL::GL) +endif() + + + +set(dir "${CMAKE_CURRENT_LIST_DIR}/YSGraphic_Core") +file(GLOB_RECURSE srcs "${dir}/*.c" "${dir}/*.cpp" "${dir}/*.h") +add_library(YSGraphic_Core STATIC "${srcs}") +target_include_directories(YSGraphic_Core PUBLIC "${CMAKE_CURRENT_LIST_DIR}") + +set(Core Core_Static) + +target_link_libraries(YSGraphic_Core PUBLIC ${other_library}) +set_property(TARGET YSGraphic_Core PROPERTY AUTOMOC ON) +set_property(TARGET YSGraphic_Core PROPERTY AUTOUIC ON) +set_property(TARGET YSGraphic_Core PROPERTY AUTORCC ON) + +target_link_libraries(YSGraphic_Core PUBLIC ${Core}) +