#include #include #include "symbol.h" const double ConstPi = 3.14159265358979323846; //!< Constant for PI. const double ConstPi2 = ConstPi / 2.0; //!< Constant for PI / 2. namespace Operation { Q_NAMESPACE } // // symbol item functions // const SymEditSymbol::Item gDefaultItem; //!< Empty default item. //! Constructor. SymEditSymbol::Item::Item() : Operation(Operation::None), Fill(0), Align(9) { } //@{ //! Constructor. /*! \param operation Item operation. \param point Item position. \param value Item value. \param color Item color index. \param fill Item area fill. */ SymEditSymbol::Item::Item(Operation::Type operation, QPoint point, int value, int color, int fill) : Operation(operation), Point(point), Value(value), Color(color), Fill(fill), Align(9) { } SymEditSymbol::Item::Item(Operation::Type operation, QPoint point, QString value, int color, int align) : Operation(operation), Point(point), Value(0), Text(value), Color(color), Fill(0), Align(align) { } //@} //! Constructor. /*! \param operation Item operation. \param point Item position. \param end End position. \param value Item value. \param color Item color index. \param fill Item area fill. */ SymEditSymbol::Item::Item(Operation::Type operation, QPoint point, QPoint end, int value, int color, int fill) : Operation(operation), Point(point), End(end), Value(value), Color(color), Fill(fill), Align(0) { } // // symbol functions // //! Constructor. SymEditSymbol::SymEditSymbol() : ActiveIndex(-1) { } //! Load symbol from string. /*! \param buffer String buffer. */ void SymEditSymbol::Load(const QString& buffer) { Items.clear(); QPoint position(0, 0); int color = 1, fill = 0, align = 9; for ( const auto& string : buffer.split(';', QString::SkipEmptyParts) ) { int type = string.at(0).toLatin1(); int comma = string.indexOf(','); if ( comma >= 0 ) { int x = string.mid(1, comma - 1).toInt(); int y = string.mid(comma + 1).toInt(); if ( type == 'D' ) Items.push_back(Item(Operation::Line, position, QPoint(x, y), 0, color, fill)); else if ( type == 'B' ) Items.push_back(Item(Operation::Rectangle, position, QPoint(x, y), 0, color, fill)); // else if ( type == 'H' ) // Items.push_back(Item(Operation::Arc, position, QPoint(x, y), color, fill)); position = QPoint(x, y); } else // single parameter { int value = string.mid(1).toInt(); if ( type == 'C' ) color = value; else if ( type == 'F' ) fill = value; else if ( type == 'J' ) align = value; else if ( type == 'R' ) Items.push_back(Item(Operation::Circle, position, value, color, fill)); else if ( type == '!' ) Items.push_back(Item(Operation::Text, position, string.mid(1), color, align)); else if ( type == '$' || type == '#' ) Items.push_back(Item(Operation::Text, position, string, color, align)); } } ActiveIndex = static_cast(Items.size()) - 1; } //! Save symbol to string. /*! \param buffer String buffer. \param rich Rich text with layout. \return Reference to buffer. */ QString& SymEditSymbol::Save(QString& buffer, bool rich) const { auto appendpoint = [](QString& buffer, const QPoint& point, int count) { QString value; buffer.append(value.setNum(point.x())); if ( count > 1 ) buffer.append(',').append(value.setNum(point.y())); buffer.append(';'); }; auto appendoption = [](QChar option, QString& buffer, int index) { QString value; buffer.append(option).append(value.setNum(index)).append(';'); return index; }; buffer.clear(); QPoint position(0, 0); int index = 0, color = 1, fill = 0, align = 9; for ( const auto& item : Items ) { if ( rich && index == ActiveIndex ) buffer.append(""); switch ( item.Operation ) { case Operation::Line: case Operation::Rectangle: { if ( item.Color != color ) color = appendoption('C', buffer, item.Color); if ( item.Fill != fill ) fill = appendoption('F', buffer, item.Fill); if ( item.Point != position ) appendpoint(buffer.append('U'), item.Point, 2); appendpoint(buffer.append(item.Operation == Operation::Line ? 'D' : 'B'), item.End, 2); position = item.End; break; } case Operation::Circle: { if ( item.Color != color ) color = appendoption('C', buffer, item.Color); if ( item.Fill != fill ) fill = appendoption('F', buffer, item.Fill); if ( item.Point != position ) appendpoint(buffer.append('U'), item.Point, 2); appendoption('R', buffer, item.Value); position = item.Point; break; } case Operation::Text: { if ( item.Color != color ) color = appendoption('C', buffer, item.Color); if ( item.Align != align ) align = appendoption('J', buffer, item.Align); if ( item.Point != position ) appendpoint(buffer.append('U'), item.Point, 2); if ( item.Text.front() != '$' && item.Text.front() != '#' ) buffer.append('!'); // constant text buffer.append(item.Text).append(';'); break; } case Operation::Arc: { if ( item.Color != color ) color = appendoption('C', buffer, item.Color); if ( item.Fill != fill ) fill = appendoption('F', buffer, item.Fill); if ( item.Point != position ) appendpoint(buffer.append('U'), item.Point, 2); appendoption('H', buffer, item.Value); position = item.Point; break; } default: assert(0); } if ( rich && index == ActiveIndex ) buffer.append(""); ++index; } if ( !buffer.isEmpty() && buffer.back() == ';' ) buffer.chop(1); return buffer; } //! Clear symbol. void SymEditSymbol::Clear() { Items.clear(); } //! Add symbol item. /*! \param operation Item operation. \param point Item position. \param value Item value. \param color Item color index. \param fill Item area fill. \return Reference to item. */ SymEditSymbol::Item& SymEditSymbol::AddItem(Operation::Type operation, QPoint point, int value, int color, int fill) { Item item(operation, point, value, color, fill); ActiveIndex = static_cast(Items.size()); Items.push_back(item); return Items.back(); } //! Add symbol item. /*! \param operation Item operation. \param point Item position. \param end End position. \param value Item value. \param color Item color index. \param fill Item area fill. \return Reference to item. */ SymEditSymbol::Item& SymEditSymbol::AddItem(Operation::Type operation, QPoint point, QPoint end, int value, int color, int fill) { Item item(operation, point, end, value, color, fill); ActiveIndex = static_cast(Items.size()); Items.push_back(item); return Items.back(); } //! Add symbol item. /*! \param operation Item operation. \param point Item position. \param value Item value. \param color Item color index. \param align Item text alignment. \return Reference to item. */ SymEditSymbol::Item& SymEditSymbol::AddItem(Operation::Type operation, QPoint point, QString value, int color, int align) { Item item(operation, point, value, color, align); ActiveIndex = static_cast(Items.size()); Items.push_back(item); return Items.back(); } //! Remove item. /*! \param index Item index. \return True for success. Activates previous or first item. */ bool SymEditSymbol::RemoveItem(int index) { if ( static_cast(index) < Items.size() ) { Items.erase(Items.begin() + index); ActiveIndex = (index == 0 && !Items.empty() ? 0 : index - 1); return true; } return false; } //! Select item nearest to point. /*! \param point Point coordinates. \return Nearest item index. */ int SymEditSymbol::SelectItem(QPoint point) const { ActiveIndex = -1; int index = 0; double minimum = 100.0; auto difference = [](const QPoint& start, const QPoint& end) { return QPoint(end.x() - start.x(), end.y() - start.y()); }; auto length = [](const QPoint& point) { return sqrt(point.x() * point.x() + point.y() * point.y()); }; auto scalar = [](const QPoint& start, const QPoint& end) { return static_cast(start.x() * end.x() + start.y() * end.y()); }; auto cross = [](const QPoint& start, const QPoint& end) { return static_cast(start.x() * end.y() - start.y() * end.x()); }; auto projection = [&](const QPoint& point, const QPoint& start, const QPoint& end) { QPoint line = difference(start, end); return scalar(difference(start, point), line) / length(line); }; auto check = [&](double distance) { if ( ActiveIndex < 0 || distance < minimum ) { ActiveIndex = index; minimum = distance; } }; auto distance = [&](const QPoint& point, const QPoint& start, const QPoint& end) { QPoint line = difference(start, end); double len = length(line), a = projection(point, start, end); if ( a > 0.0 && a < len ) check(fabs(cross(difference(start, point), line) / len)); check(length(difference(point, start))); check(length(difference(point, end))); }; for ( const auto& item : Items ) { switch ( item.Operation ) { case Operation::Line: { distance(point, item.Point, item.End); break; } case Operation::Rectangle: { distance(point, item.Point, QPoint(item.Point.x(), item.End.y())); distance(point, QPoint(item.Point.x(), item.End.y()), item.End); distance(point, item.End, QPoint(item.End.x(), item.Point.y())); distance(point, QPoint(item.End.x(), item.Point.y()), item.Point); break; } case Operation::Circle: { check(fabs(length(difference(point, item.Point)) - item.End.x())); break; } case Operation::Text: { check(fabs(length(difference(point, item.Point)))); break; } case Operation::Arc: { //## break; } default: assert(0); } ++index; } return ActiveIndex; } //! Set active item index. /*! \param index Active item index or -1 for deactivation. \return True for success. */ bool SymEditSymbol::SetActiveIndex(int index) { if ( index >= -1 && index < static_cast(Items.size()) ) { ActiveIndex = index; return true; } return false; } //! Get active item index. /*! \return Active item index. */ int SymEditSymbol::GetActiveIndex() const { return static_cast(ActiveIndex); } //! Get item count. /*! \return Item count. */ int SymEditSymbol::GetItemCount() const { return static_cast(Items.size()); } //! Get item from index. /*! \param index Item index. \return Item from index. */ const SymEditSymbol::Item& SymEditSymbol::GetItem(int index) const { if ( index >= 0 && index < static_cast(Items.size()) ) return Items.at(static_cast(index)); return gDefaultItem; } //! Rotate symbol. /*! \param dir Positive value rotates right, negative left */ void SymEditSymbol::RotateSymbol(int dir) { auto rotate = [](QPoint& point, int dir) { double length = sqrt(point.x() * point.x() + point.y() * point.y()); double angle = atan2(point.y(), point.x()); angle += (dir > 0 ? -ConstPi2 : ConstPi2); double x = cos(angle) * length, y = sin(angle) * length; point.setX(static_cast(x + (x > 0.0 ? 0.5 : -0.5))); point.setY(static_cast(y + (y > 0.0 ? 0.5 : -0.5))); }; for ( auto& item : Items ) { rotate(item.Point, dir); if ( item.Operation == Operation::Line || item.Operation == Operation::Rectangle ) rotate(item.End, dir); } } //! Raise or lower item. /*! \param dir Positive value raises, negative lowers. \return True for success. */ bool SymEditSymbol::RaiseItem(int dir) { if ( !Items.empty() ) { size_t index = static_cast(ActiveIndex); if ( dir > 0 && index < Items.size() - 1 ) // raise { std::swap(Items[index], Items[index + 1]); ++ActiveIndex; return true; } if ( dir < 0 && index > 0 ) // lower { std::swap(Items[index - 1], Items[index]); --ActiveIndex; return true; } } return false; }