From 145bf8c68894aa7072b11a93ce544551eab5e638 Mon Sep 17 00:00:00 2001 From: addi Date: Wed, 14 Dec 2016 10:00:35 +0100 Subject: [PATCH] Add Draw-figures Tab --- MinetestMapperGui.pro | 13 +- doc/drawfigure.html | 104 +++++++++++ drawmapfigure.cpp | 227 +++++++++++++++++++++++ drawmapfigure.h | 106 +++++++++++ drawmapfiguretablemodel.cpp | 248 +++++++++++++++++++++++++ drawmapfiguretablemodel.cpp.autosave | 248 +++++++++++++++++++++++++ drawmapfiguretablemodel.h | 51 ++++++ figuredelegate.cpp | 55 ++++++ figuredelegate.h | 35 ++++ geometry.cpp | 256 ++++++++++++++++++++++++++ geometry.h | 57 ++++++ geometrywidget.cpp | 265 +-------------------------- geometrywidget.h | 57 +----- images/desktop.ini | 3 + images/draw-arrow.svg | 67 +++++++ images/draw-circle.svg | 90 +++++++++ images/draw-ellipse.svg | 91 +++++++++ images/draw-line.svg | 66 +++++++ images/draw-point.svg | 90 +++++++++ images/draw-rectangle.svg | 92 ++++++++++ images/draw-text.svg | 74 ++++++++ images/draw-unknown.svg | 67 +++++++ images/drawarrow.png | Bin 0 -> 11133 bytes images/drawcircle.png | Bin 0 -> 5180 bytes images/drawline.png | Bin 0 -> 5166 bytes images/drawpoint.png | Bin 0 -> 5102 bytes images/drawrectangle.png | Bin 0 -> 5139 bytes images/drawtext.png | Bin 0 -> 5304 bytes mainwindow.cpp | 73 +++++++- mainwindow.h | 16 ++ mainwindow.ui | 172 ++++++++++++++++- makecolors.cpp | 7 +- minetestmappergui.qrc | 15 ++ 33 files changed, 2332 insertions(+), 313 deletions(-) create mode 100644 doc/drawfigure.html create mode 100644 drawmapfigure.cpp create mode 100644 drawmapfigure.h create mode 100644 drawmapfiguretablemodel.cpp create mode 100644 drawmapfiguretablemodel.cpp.autosave create mode 100644 drawmapfiguretablemodel.h create mode 100644 figuredelegate.cpp create mode 100644 figuredelegate.h create mode 100644 geometry.cpp create mode 100644 geometry.h create mode 100644 images/desktop.ini create mode 100644 images/draw-arrow.svg create mode 100644 images/draw-circle.svg create mode 100644 images/draw-ellipse.svg create mode 100644 images/draw-line.svg create mode 100644 images/draw-point.svg create mode 100644 images/draw-rectangle.svg create mode 100644 images/draw-text.svg create mode 100644 images/draw-unknown.svg create mode 100644 images/drawarrow.png create mode 100644 images/drawcircle.png create mode 100644 images/drawline.png create mode 100644 images/drawpoint.png create mode 100644 images/drawrectangle.png create mode 100644 images/drawtext.png diff --git a/MinetestMapperGui.pro b/MinetestMapperGui.pro index 3636810..0c8de00 100644 --- a/MinetestMapperGui.pro +++ b/MinetestMapperGui.pro @@ -16,17 +16,26 @@ TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ colorlineedit.cpp \ + geometry.cpp \ geometrywidget.cpp \ configdialog.cpp \ colorstxtassistant.cpp \ - makecolors.cpp + makecolors.cpp \ + drawmapfigure.cpp \ + drawmapfiguretablemodel.cpp \ + figuredelegate.cpp + HEADERS += mainwindow.h \ colorlineedit.h \ + geometry.h \ geometrywidget.h \ configdialog.h \ colorstxtassistant.h \ - makecolors.h + makecolors.h \ + drawmapfigure.h \ + drawmapfiguretablemodel.h \ + figuredelegate.h FORMS += mainwindow.ui \ geometrywidget.ui \ diff --git a/doc/drawfigure.html b/doc/drawfigure.html new file mode 100644 index 0000000..8f661d4 --- /dev/null +++ b/doc/drawfigure.html @@ -0,0 +1,104 @@ +

--draw[map]<figure> "<geometry> <color> [<text>]"

+
+

Draw a figure on the map, with the given geometry and color.

+

Possible figures are:

+ +

If --draw<figure> is used, the geometry specifies world coordinates; if --drawmap<figure> is used, the geometry specifies map (i.e. image) coordinates, where 0,0 is the top-left corner of the map-part of + the image, and coordinates increase to the right and down. Any points in the left and top scale area (if present) + have negative coordinates.

+

Note that the combination of geometry and color (and text if applicable) must be a single argument. This means that they + must be enclosed in quotes together on the command-line, else they will be misinterpreted as two or more + command-line arguments.

+

Example:

+
minetestmapper --drawcircle "10,10:6x6 red"
+

For the color of figures, an alpha value can be specified. Note that due to a bug in the drawing library, this has not + the expected effect when drawing circles and ellipses.

+

See also Geometry Syntax_ and Color Syntax_.

+

Interaction of figure geometry and map scaling

+

If the map is scaled, figures could either keep the same size in pixels, or the same size relative to the world, which + would make them appear smaller like the entire map. Whether they scale of not depends on how they are drawn:

+
+
    +
  • Figures which are drawn using map (i.e. image) coordinates are never scaled. It is assumed that it was the intention + to draw them on the image to begin with, and not in the world.
  • +
+
+

At the moment, figures which are drawn using world coordinates may or may not scale with the world.

+ +

In practise this means that two identically-sized figures in a full-scale map, may have different sizes after scaling, + depending on how their geometry was specified. The jury is still out as to whether this is a bug or a feature.

+
+

--draw[map]circle "<geometry> <color>"

+
+

Draw a circle on the map, with the given geometry and color.

+

If the geometry does not specify equal horizontal and vertical dimensions, then an ellipse will be drawn.

+

See --draw[map] for details.

+

An example circle:

+

image

+
+

--draw[map]ellipse "<geometry> <color>"

+
+

Draw an ellipse on the map. This is a synonym for --draw[map]circle.

+

See --draw[map] for details.

+
+

--draw[map]line "<geometry> <color>"

+
+

Draw a line on the map, with the given geometry and color.

+

See --draw[map] for details.

+

An example line:

+

image

+
+

--draw[map]arrow "<geometry> <color>"

+
+

Draw an arrow on the map, with the given geometry and color.

+

See --draw[map] for details.

+

An example arrow:

+

image

+
+

--draw[map]point "<x>,<y> <color>"

+
+

Draw a point on the map, at the given location, using the given color.

+

See --draw[map] for details.

+

An example point (red, in te white area):

+

image

+
+

--draw[map]rectangle "<geometry> <color>"

+
+

Draw a reactangle on the map, with the given geometry and color.

+

See --draw[map] for details.

+

An example rectangle:

+

image

+
+

--draw[map]text "<x>,<y> <color> <text>"

+
+

Write text on the map, at the specified location, using the given color.

+

The text can consist of any number of words. Be careful when using characters that the command shell may interpret, like + '"', '$', etc. On unix-like systems, use single quotes to avoid interpretation of + most characters (except for ' itself).

+

Due to a limitation of the drawing library, currently only text that can be represented in (i.e. converted to) the ISO8859-2 + character set is supported. Text that uses non-compatible characters will not be rendered correctly.

+

Note that the combination of geometry, color and text should be a single argument. This means that they must be enclosed + in quotes together on the command-line, else they will be misinterpreted as three command-line arguments.

+

Example:

+
minetestmapper --drawtext "20,-10 red This text will be on the map"
+

See also --draw[map] for more details.

+

Example text:

+

image

+
\ No newline at end of file diff --git a/drawmapfigure.cpp b/drawmapfigure.cpp new file mode 100644 index 0000000..97eba50 --- /dev/null +++ b/drawmapfigure.cpp @@ -0,0 +1,227 @@ +#include "drawmapfigure.h" + +DrawMapFigure::DrawMapFigure(QObject *parent) : + QObject(parent) +{ + geometry = new Geometry(); +} + +DrawMapFigure::DrawMapFigure(const QString &str, QObject *parent) : + QObject(parent) +{ + const QRegularExpression parser = QRegularExpression("^--draw(?map)?(?\\w+) \"(?.+)*\"$"); + QRegularExpressionMatch match = parser.match(str); + + QStringList xy; + if(match.hasMatch()){ + useImageCoordinates = (match.captured("map")=="map"); + QString params = match.captured("params"); + + color.setNamedColor(params.section(' ', 1, 1)); + bool ok; + figure = getFigure(match.captured("type")); + if(color.isValid() && ok){ + switch (figure) { + case Text: + //parse text and fall through for point + text = params.section(' ', 2);// everything after the 3rd whitespace + case Point: + //parse point and color + xy = params.section(' ', 0, 0).split(','); + point.setX(xy[0].toInt()); + point.setY(xy[1].toInt()); + break; + case Circle: + case Arrow: + case Line: + case Rectangle: + case Ellipse: + //parse geometry + geometry = new Geometry(params.section(' ', 0, 0)); + break; + default: + figure = Unknown; + geometry = new Geometry(); + break; + } + } + else{ + geometry = new Geometry(); + figure = Unknown; + } + + + } + else{ + geometry = new Geometry(); + figure = Unknown; + } +} + +bool DrawMapFigure::requiresPoint() const +{ + return (figure == Text || figure == Point); +} + +bool DrawMapFigure::requiresGeometry() const +{ + return (figure != Text && figure != Point); +} + +bool DrawMapFigure::requiresText() const +{ + return (figure == Text); +} + +QString DrawMapFigure::getString() const +{ + QStringList splitted = getSplittedString(); + + return QString("%1 \"%2\"").arg(splitted.at(0)).arg(splitted.at(1)); + +} + +QStringList DrawMapFigure::getSplittedString() const +{ + QString param; + QString argument; + + param = "--draw"; + if(useImageCoordinates) + param += "map"; + param += QString(metaFigure.key(figure)).toLower(); + + if(requiresGeometry()) + argument += geometry->getString(); + if(requiresPoint()) + argument += QString("%1,%2").arg(point.x()).arg(point.y()); + argument += ' '+ color.name(); + if(requiresText()) + argument += ' '+text; + + return QStringList()<(metaFigure.keyToValue(temp.toUtf8().constData())); +} + +int DrawMapFigure::getFigureIndex() const +{ + QMetaEnum metaEnum = QMetaEnum::fromType
(); + return metaEnum.value(figure); +} + +QPoint DrawMapFigure::getPoint() const +{ + return point; +} + +QString DrawMapFigure::getText() const +{ + return text; +} + +QColor DrawMapFigure::getColor() const +{ + return color; +} + +QStringList DrawMapFigure::getFigureList() +{ + QMetaEnum metaEnum = QMetaEnum::fromType
(); + QStringList list; + for(int i =0; i(value); +} + +void DrawMapFigure::setText(const QString &value) +{ + text = value; +} + +void DrawMapFigure::setColor(const QColor &value) +{ + color = value; +} + +Geometry *DrawMapFigure::getGeometry() +{ + return this->geometry; +} + +void DrawMapFigure::setGeometry(Geometry *value) +{ + geometry = value; +} + +void DrawMapFigure::setUseImageCoordinates(bool value) +{ + useImageCoordinates = value; +} + +void DrawMapFigure::setPoint(const QPoint &value) +{ + point = value; +} + +void DrawMapFigure::setPoint(const QVariant &value) +{ + point = value.toPoint(); +} + +void DrawMapFigure::setPoint(const QString &value) +{ + static const QRegularExpression regex("(\\-?\\d+)[ |,](\\-?\\d+)"); + QRegularExpressionMatch match = regex.match(value); + if(match.hasMatch()){ + point = QPoint(match.captured(1).toInt(), match.captured(2).toInt()); + } + else{ + qDebug() <<"Could not match point from String "<().key(figure); + return QIcon(QString(":/images/draw-%1").arg(a).toLower()); +} + + + + + + diff --git a/drawmapfigure.h b/drawmapfigure.h new file mode 100644 index 0000000..1b4a4db --- /dev/null +++ b/drawmapfigure.h @@ -0,0 +1,106 @@ +#ifndef DRAWMAPFIGURE_H +#define DRAWMAPFIGURE_H + +#include +#include +#include +#include +#include + +#include "geometry.h" + +class DrawMapFigure : public QObject +{ + Q_OBJECT +public: + enum Figure{ + Unknown, + Arrow, + Circle, + Ellipse, + Line, + Point, + Rectangle, + Text + + }; + Q_ENUM(Figure) + + explicit DrawMapFigure(QObject *parent = 0); + /** + * @brief DrawMapFigure + * @param str QString to convert to DrawMapFigure + * @param parent + */ + explicit DrawMapFigure(const QString &str, QObject *parent = 0); + + explicit DrawMapFigure(QColor color, QObject *parent = 0) : + color(color), QObject(parent) { } + + explicit DrawMapFigure(QPoint point, QColor color, QObject *parent = 0) : + point(point), color(color), QObject(parent) {} + + explicit DrawMapFigure(Geometry *geometry, QColor color, QObject *parent = 0) : + geometry(geometry), color(color), QObject(parent) { } + + //DrawMapFigure(const DrawMapFigure &f) {} + bool requiresPoint(void) const; + bool requiresGeometry(void) const; + bool requiresText(void) const; + + QString getString(void) const; + QStringList getSplittedString(void) const; + bool getUseImageCoordinates() const; + + int getFigureIndex() const; + + QPoint getPoint() const; + + QString getText() const; + /** + * @brief getColor + * @return + */ + QColor getColor() const; + + static QStringList getFigureList(); + + void setFigure(const Figure &value); + void setFigure(int value); + + void setText(const QString &value); + + void setColor(const QColor &value); + + Geometry* getGeometry(); + void setGeometry(Geometry *value); + + void setUseImageCoordinates(bool value); + void setPoint(const QString &value); + void setPoint(const QPoint &value); + + QIcon getIcon() const; + static QIcon getIcon(Figure figure); + + void setPoint(const QVariant &value); + + Figure getFigure() const; + Figure getFigure(const QString &str) const; + +signals: + +public slots: + +protected: + bool useImageCoordinates; + Figure figure = Unknown; + QPoint point; + Geometry *geometry; + QString text; + QColor color; + QMetaEnum metaFigure = QMetaEnum::fromType
(); +private: + +}; + +#endif // DRAWMAPFIGURE_H diff --git a/drawmapfiguretablemodel.cpp b/drawmapfiguretablemodel.cpp new file mode 100644 index 0000000..ec79a4f --- /dev/null +++ b/drawmapfiguretablemodel.cpp @@ -0,0 +1,248 @@ +#include "drawmapfiguretablemodel.h" + +#include + +DrawMapFigureTableModel::DrawMapFigureTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ + list = new QList; + header <<"Figure"<<"Use image coordinates"<<"Point"<<"Geometry"<<"Color"<<"Text"; +} + +DrawMapFigureTableModel::DrawMapFigureTableModel(QList *list, QObject *parent) + : QAbstractTableModel(parent) +{ + this->list = list; + header <<"Figure"<<"Use image coordinates"<<"Point"<<"Geometry"<<"Color"<<"Text"; +} + +QVariant DrawMapFigureTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section <= header.size()) + { + return header[section]; + } + else if(role == Qt::DisplayRole && orientation == Qt::Vertical){ + return section+1; + } + return QVariant(); +} + +bool DrawMapFigureTableModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + if (value != headerData(section, orientation, role) && role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + header[section] = value.toString(); + emit headerDataChanged(orientation, section, section); + return true; + } + + + return false; +} + + +int DrawMapFigureTableModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + if (parent.isValid()) + return 0; + + + return list->size(); +} + +int DrawMapFigureTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + if (parent.isValid()) + return 0; + + return header.size(); + // FIXME: Implement me! +} + +QVariant DrawMapFigureTableModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid()){ + int row = index.row(); + int col = index.column(); + DrawMapFigure *item = list->at(row); + QPoint p = item->getPoint(); + QMetaEnum metaEnum = QMetaEnum::fromType(); + if(role == Qt::DecorationRole && col ==0) + { + return item->getIcon(); + } + else if(role == Qt::BackgroundRole) + { + if(item->getFigure()==DrawMapFigure::Unknown){ + return QBrush(Qt::red); + } + else if(col == 2 && !item->requiresPoint()){ + return QBrush(Qt::lightGray); + } + else if (col == 3 && !item->requiresGeometry()){ + return QBrush(Qt::lightGray); + } + else if(col == 5 && !item->requiresText()){ + return QBrush(Qt::lightGray); + } + } + else if(role == Qt::EditRole ||role == Qt::DisplayRole){ + + switch(col){ + case 0: + return (role == Qt::EditRole) ? QVariant(item->getFigure()) : QString(metaEnum.key(item->getFigure())); + break; + + case 1: + return item->getUseImageCoordinates(); + break; + case 2: + return (item->requiresPoint()) ? QString("(%1,%2)").arg(p.x()).arg(p.y()) : QVariant(); + break; + case 3: + return item->requiresGeometry() ? item->getGeometry()->getString() : QVariant(); + break; + case 4: + return item->getColor(); + break; + case 5: + return item->getText(); + break; + default: + return QVariant(); + } + + // FIXME: Implement me! + return QVariant(); + } + else if(role == Qt::DecorationRole && col==4){ + return item->getColor(); + } + } + return QVariant(); +} + +bool DrawMapFigureTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole && data(index, role) != value) { + int row = index.row(); + int col = index.column(); + DrawMapFigure *item = list->at(row); + switch(col){ + case 0: + item->setFigure(value.toInt()); + break; + case 1: + item->setUseImageCoordinates(value.toBool()); + break; + case 2: + item->setPoint(value.toString()); + break; + case 3: + item->getGeometry()->set(value.toString()); + break; + case 4: + item->setColor(QColor(value.toString())); + break; + case 5: + item->setText(value.toString()); + default: + break; + } + // FIXME: Implement me! + emit dataChanged(index, index, QVector() << role); + return true; + } + return false; +} + +Qt::ItemFlags DrawMapFigureTableModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + Qt::ItemFlags flag = QAbstractItemModel::flags(index); + int col = index.column(); + DrawMapFigure *item = list->at(index.row()); + if(col == 0) + flag |= Qt::ItemIsEnabled|Qt::ItemIsEditable; + else if(col == 1) + flag |= Qt::ItemIsEditable; + else if (col == 2 && item->requiresPoint()) + flag |= Qt::ItemIsEditable; + else if (col == 3 && item->requiresGeometry()) + flag |= Qt::ItemIsEditable; + else if (col == 4) + flag |= Qt::ItemIsEditable; + else if(col == 5 && item->requiresText()) + flag |= Qt::ItemIsEditable; + + return flag; +} + +bool DrawMapFigureTableModel::insertRows(int position, int count, const QModelIndex &parent) +{ + beginInsertRows(parent, position, position + count - 1); + for (int row = 0; row < count; ++row) { + list->insert(position, new DrawMapFigure()); + } + endInsertRows(); + return true; +} + +bool DrawMapFigureTableModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + beginInsertColumns(parent, column, column + count - 1); + // FIXME: Implement me! + endInsertColumns(); + return false; +} + +bool DrawMapFigureTableModel::removeRows(int position, int count, const QModelIndex &parent) +{ + beginRemoveRows(parent, position, position + count - 1); + for (int row = 0; row < count; ++row) { + list->removeAt(position); + } + endRemoveRows(); + return true; +} + +bool DrawMapFigureTableModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + beginRemoveColumns(parent, column, column + count - 1); + // FIXME: Implement me! + endRemoveColumns(); + return false; +} + +QStringList DrawMapFigureTableModel::getStringList() const +{ + QStringList retval; + for (int i = 0; i < list->size(); ++i) + retval << list->at(i)->getString(); + return retval; +} + +QStringList DrawMapFigureTableModel::getArguments() const +{ + QStringList retval; + for (int i = 0; i < list->size(); ++i) + retval << list->at(i)->getSplittedString(); + return retval; +} + +void DrawMapFigureTableModel::insertStringList(const QStringList &other) +{ + if(other.length() > 0){ + int leng = other.length(); + beginInsertRows(QModelIndex() , list->length(), list->length()+leng - 1); + //insertRows(0, other.length(),QModelIndex()); + for(int i = 0; i < leng; ++i){ + list->append(new DrawMapFigure(other.at(i))); + } + endInsertRows(); + } +} diff --git a/drawmapfiguretablemodel.cpp.autosave b/drawmapfiguretablemodel.cpp.autosave new file mode 100644 index 0000000..ec79a4f --- /dev/null +++ b/drawmapfiguretablemodel.cpp.autosave @@ -0,0 +1,248 @@ +#include "drawmapfiguretablemodel.h" + +#include + +DrawMapFigureTableModel::DrawMapFigureTableModel(QObject *parent) + : QAbstractTableModel(parent) +{ + list = new QList; + header <<"Figure"<<"Use image coordinates"<<"Point"<<"Geometry"<<"Color"<<"Text"; +} + +DrawMapFigureTableModel::DrawMapFigureTableModel(QList *list, QObject *parent) + : QAbstractTableModel(parent) +{ + this->list = list; + header <<"Figure"<<"Use image coordinates"<<"Point"<<"Geometry"<<"Color"<<"Text"; +} + +QVariant DrawMapFigureTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal && section <= header.size()) + { + return header[section]; + } + else if(role == Qt::DisplayRole && orientation == Qt::Vertical){ + return section+1; + } + return QVariant(); +} + +bool DrawMapFigureTableModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role) +{ + if (value != headerData(section, orientation, role) && role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + header[section] = value.toString(); + emit headerDataChanged(orientation, section, section); + return true; + } + + + return false; +} + + +int DrawMapFigureTableModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + if (parent.isValid()) + return 0; + + + return list->size(); +} + +int DrawMapFigureTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + if (parent.isValid()) + return 0; + + return header.size(); + // FIXME: Implement me! +} + +QVariant DrawMapFigureTableModel::data(const QModelIndex &index, int role) const +{ + if (index.isValid()){ + int row = index.row(); + int col = index.column(); + DrawMapFigure *item = list->at(row); + QPoint p = item->getPoint(); + QMetaEnum metaEnum = QMetaEnum::fromType(); + if(role == Qt::DecorationRole && col ==0) + { + return item->getIcon(); + } + else if(role == Qt::BackgroundRole) + { + if(item->getFigure()==DrawMapFigure::Unknown){ + return QBrush(Qt::red); + } + else if(col == 2 && !item->requiresPoint()){ + return QBrush(Qt::lightGray); + } + else if (col == 3 && !item->requiresGeometry()){ + return QBrush(Qt::lightGray); + } + else if(col == 5 && !item->requiresText()){ + return QBrush(Qt::lightGray); + } + } + else if(role == Qt::EditRole ||role == Qt::DisplayRole){ + + switch(col){ + case 0: + return (role == Qt::EditRole) ? QVariant(item->getFigure()) : QString(metaEnum.key(item->getFigure())); + break; + + case 1: + return item->getUseImageCoordinates(); + break; + case 2: + return (item->requiresPoint()) ? QString("(%1,%2)").arg(p.x()).arg(p.y()) : QVariant(); + break; + case 3: + return item->requiresGeometry() ? item->getGeometry()->getString() : QVariant(); + break; + case 4: + return item->getColor(); + break; + case 5: + return item->getText(); + break; + default: + return QVariant(); + } + + // FIXME: Implement me! + return QVariant(); + } + else if(role == Qt::DecorationRole && col==4){ + return item->getColor(); + } + } + return QVariant(); +} + +bool DrawMapFigureTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole && data(index, role) != value) { + int row = index.row(); + int col = index.column(); + DrawMapFigure *item = list->at(row); + switch(col){ + case 0: + item->setFigure(value.toInt()); + break; + case 1: + item->setUseImageCoordinates(value.toBool()); + break; + case 2: + item->setPoint(value.toString()); + break; + case 3: + item->getGeometry()->set(value.toString()); + break; + case 4: + item->setColor(QColor(value.toString())); + break; + case 5: + item->setText(value.toString()); + default: + break; + } + // FIXME: Implement me! + emit dataChanged(index, index, QVector() << role); + return true; + } + return false; +} + +Qt::ItemFlags DrawMapFigureTableModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + Qt::ItemFlags flag = QAbstractItemModel::flags(index); + int col = index.column(); + DrawMapFigure *item = list->at(index.row()); + if(col == 0) + flag |= Qt::ItemIsEnabled|Qt::ItemIsEditable; + else if(col == 1) + flag |= Qt::ItemIsEditable; + else if (col == 2 && item->requiresPoint()) + flag |= Qt::ItemIsEditable; + else if (col == 3 && item->requiresGeometry()) + flag |= Qt::ItemIsEditable; + else if (col == 4) + flag |= Qt::ItemIsEditable; + else if(col == 5 && item->requiresText()) + flag |= Qt::ItemIsEditable; + + return flag; +} + +bool DrawMapFigureTableModel::insertRows(int position, int count, const QModelIndex &parent) +{ + beginInsertRows(parent, position, position + count - 1); + for (int row = 0; row < count; ++row) { + list->insert(position, new DrawMapFigure()); + } + endInsertRows(); + return true; +} + +bool DrawMapFigureTableModel::insertColumns(int column, int count, const QModelIndex &parent) +{ + beginInsertColumns(parent, column, column + count - 1); + // FIXME: Implement me! + endInsertColumns(); + return false; +} + +bool DrawMapFigureTableModel::removeRows(int position, int count, const QModelIndex &parent) +{ + beginRemoveRows(parent, position, position + count - 1); + for (int row = 0; row < count; ++row) { + list->removeAt(position); + } + endRemoveRows(); + return true; +} + +bool DrawMapFigureTableModel::removeColumns(int column, int count, const QModelIndex &parent) +{ + beginRemoveColumns(parent, column, column + count - 1); + // FIXME: Implement me! + endRemoveColumns(); + return false; +} + +QStringList DrawMapFigureTableModel::getStringList() const +{ + QStringList retval; + for (int i = 0; i < list->size(); ++i) + retval << list->at(i)->getString(); + return retval; +} + +QStringList DrawMapFigureTableModel::getArguments() const +{ + QStringList retval; + for (int i = 0; i < list->size(); ++i) + retval << list->at(i)->getSplittedString(); + return retval; +} + +void DrawMapFigureTableModel::insertStringList(const QStringList &other) +{ + if(other.length() > 0){ + int leng = other.length(); + beginInsertRows(QModelIndex() , list->length(), list->length()+leng - 1); + //insertRows(0, other.length(),QModelIndex()); + for(int i = 0; i < leng; ++i){ + list->append(new DrawMapFigure(other.at(i))); + } + endInsertRows(); + } +} diff --git a/drawmapfiguretablemodel.h b/drawmapfiguretablemodel.h new file mode 100644 index 0000000..df0a3c9 --- /dev/null +++ b/drawmapfiguretablemodel.h @@ -0,0 +1,51 @@ +#ifndef DRAWMAPFIGURETABLEMODEL_H +#define DRAWMAPFIGURETABLEMODEL_H + +#include +#include +#include "drawmapfigure.h" +#include "geometry.h" + +class DrawMapFigureTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit DrawMapFigureTableModel(QObject *parent = 0); + explicit DrawMapFigureTableModel(QList *list, QObject *parent = 0); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex& index) const override; + + // Add data: + bool insertRows(int position, int count, const QModelIndex &parent = QModelIndex()) override; + bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override; + + // Remove data: + bool removeRows(int position, int count, const QModelIndex &parent = QModelIndex()) override; + bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override; + + QStringList getStringList(void) const; + QStringList getArguments(void) const; + void insertStringList(const QStringList &other); + +private: + QList *list; + QStringList header; +}; + +#endif // DRAWMAPFIGURETABLEMODEL_H diff --git a/figuredelegate.cpp b/figuredelegate.cpp new file mode 100644 index 0000000..91d0a76 --- /dev/null +++ b/figuredelegate.cpp @@ -0,0 +1,55 @@ +#include "figuredelegate.h" + +#include + +FigureDelegate::FigureDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +list = DrawMapFigure::getFigureList(); +} + +QWidget *FigureDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &/* option */, + const QModelIndex &/* index */) const +{ + QComboBox *editor = new QComboBox(parent); + editor->setFrame(false); + QMetaEnum figureEnum = QMetaEnum::fromType(); + for(int i = 0; i(figureEnum.value(i)); + editor->addItem(DrawMapFigure::getIcon(f), list.at(i), f); + } + + return editor; +} + +void FigureDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + int value = index.model()->data(index, Qt::EditRole).toInt(); + + QComboBox *comboBox = static_cast(editor); + comboBox->setCurrentIndex(value); +} + +void FigureDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const +{ + QComboBox *comboBox = static_cast(editor); + + model->setData(index, comboBox->currentData(), Qt::EditRole); +} + +void FigureDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &/* index */) const +{ + editor->setGeometry(option.rect); +} + +/*void FigureDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyle* style = QApplication::style(); + QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option); + painter->drawText(textRect, option.displayAlignment, list.at(index.data().toInt())); + QStyledItemDelegate::paint(painter, option, index); +}*/ diff --git a/figuredelegate.h b/figuredelegate.h new file mode 100644 index 0000000..30f42f0 --- /dev/null +++ b/figuredelegate.h @@ -0,0 +1,35 @@ +#ifndef FIGUREDELEGATE_H +#define FIGUREDELEGATE_H + +#include +#include +#include +#include +#include +#include + +#include "drawmapfigure.h" + +class FigureDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + FigureDelegate(QObject *parent = 0); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const Q_DECL_OVERRIDE; + + void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const Q_DECL_OVERRIDE; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE; + + //void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE ; +private: + QStringList list; +}; + +#endif // FIGUREDELEGATE_H diff --git a/geometry.cpp b/geometry.cpp new file mode 100644 index 0000000..c59fb11 --- /dev/null +++ b/geometry.cpp @@ -0,0 +1,256 @@ +#include "geometry.h" + + +QMap Geometry::geometryIdNameMap; +QMap Geometry::geometryNameIdMap; +const Geometry::InitStatics initStatics; + +Geometry::InitStatics::InitStatics() +{ + Geometry::geometryIdNameMap[Geometry::FormatUnknown] = "unknown"; + Geometry::geometryIdNameMap[Geometry::FormatNone] = "none"; + Geometry::geometryIdNameMap[Geometry::CenterDimensions] = "center-dimensions"; + Geometry::geometryIdNameMap[Geometry::CornerDimensions] = "corner-dimensions"; + Geometry::geometryIdNameMap[Geometry::Corners] = "corners"; + Geometry::geometryIdNameMap[Geometry::FormatCustom] = "custom"; + + for (int i = Geometry::FormatNone; i < Geometry::FormatMax; i++) + Geometry::geometryNameIdMap[Geometry::geometryIdNameMap[static_cast(i)]] = static_cast(i); + +} + +const QString &Geometry::formatName(Geometry::Format id) +{ + if (id < Geometry::FormatNone || id >= Geometry::FormatMax) + id = Geometry::FormatUnknown; + return geometryIdNameMap[id]; +} + +Geometry::Format Geometry::formatId(const QString &name) +{ + if (geometryNameIdMap.find(name) == geometryNameIdMap.end()) + return Geometry::FormatUnknown; + else + return geometryNameIdMap[name]; +} + +Geometry::Format Geometry::set(QString str) +{ + qDebug()<<"Trying to detect format of "<x[<+|-xoffset><+|-yoffset>]"; + if(match.lastCapturedIndex() ==2){ + qDebug() << "format is CenterDimensions with center =0,0"; + center[0] = 0; + center[1] = 0; + dimension[0] = match.captured(1).toInt(); + dimension[1] = match.captured(2).toInt(); + computeCorner0(); + computeCorner1(); + if (adjustCorners()) { + // Order is important here! + computeDimensions(); + computeCenter(); + } + return Geometry::CenterDimensions; + } + else if(match.lastCapturedIndex() ==4){ + qDebug() << "format is CornerDimensions"; + corner[0][0] = match.captured(3).toInt(); + corner[0][1] = match.captured(4).toInt(); + dimension[0] = match.captured(1).toInt(); + dimension[1] = match.captured(2).toInt(); + computeCenter(); + computeCorner1(); + if (adjustCorners()) { + // Order is important here! + computeDimensions(); + computeCenter(); + } + return Geometry::CornerDimensions; + } + else return Geometry::FormatCustom; + } + else { + qDebug()<<"Warning: Could not parse format of string: "< 32767) { + corner[j][i] = 32767; + adjusted = true; + } + } + } + return adjusted; +} + +void Geometry::setCenterDimensions(int cx, int cy, int dx, int dy) +{ + center[0] = cx; + center[1] = cy; + dimension[0] = dx; + dimension[1] = dy; + computeCorner0(); + computeCorner1(); +} + +void Geometry::setCornerDimensions(int cx, int cy, int dx, int dy) +{ + corner[0][0] = cx; + corner[0][1] = cy; + dimension[0] = dx; + dimension[1] = dy; + computeCenter(); + computeCorner1(); +} + +void Geometry::setCorners(int c0x, int c0y, int c1x, int c1y) +{ + corner[0][0] = c0x; + corner[0][1] = c0y; + corner[1][0] = c1x; + corner[1][1] = c1y; + computeDimensions(); + computeCenter(); +} + +QString Geometry::getString(Geometry::Format format) +{ + switch (format) { + case CenterDimensions: + if(center[0]==0 && center[1] ==0) + return QString("%1x%2") + .arg(dimension[0]) + .arg(dimension[1]); + return QString("%1,%2:%3x%4") + .arg(center[0]) + .arg(center[1]) + .arg(dimension[0]) + .arg(dimension[1]); + break; + case CornerDimensions: + return QString("%1,%2+%3+%4") + .arg(corner[0][0]) + .arg(corner[0][1]) + .arg(dimension[0]) + .arg(dimension[1]); + break; + case Corners: + default: + return QString("%1,%2:%3,%4") + .arg(corner[0][0]) + .arg(corner[0][1]) + .arg(corner[1][0]) + .arg(corner[1][1]); + break; + } + +} + +void Geometry::computeCorner0(void) +{ + for (int i = 0; i < 2; i++) { + if (dimension[i] < 0) + corner[0][i] = center[i] + (-dimension[i]) / 2; + else + corner[0][i] = center[i] - dimension[i] / 2; + } +} + +void Geometry::computeCorner1(void) +{ + for (int i = 0; i < 2; i++) { + if (dimension[i] < 0) + corner[1][i] = corner[0][i] + dimension[i] + 1; + else + corner[1][i] = corner[0][i] + dimension[i] - 1; + } +} + +void Geometry::computeCenter(void) +{ + for (int i = 0; i < 2; i++) { + if (corner[0][i] > corner[1][i]) + center[i] = corner[0][i] - (-dimension[i]) / 2; + else + center[i] = corner[0][i] + dimension[i] / 2; + } +} + +void Geometry::computeDimensions(void) +{ + for (int i = 0; i < 2; i++) { + if (corner[0][i] > corner[1][i]) + dimension[i] = corner[1][i] - corner[0][i] - 1; + else + dimension[i] = corner[1][i] - corner[0][i] + 1; + } +} diff --git a/geometry.h b/geometry.h new file mode 100644 index 0000000..d328843 --- /dev/null +++ b/geometry.h @@ -0,0 +1,57 @@ +#ifndef GEOMETRY_H +#define GEOMETRY_H + +#include +#include +#include + +class Geometry +{ +public: + enum Format { + FormatKeep = -2, // Special value used when setting geometry: don't change current format. + FormatUnknown = -1, + FormatNone = 0, + CenterDimensions = 1, + CornerDimensions = 2, + Corners = 3, + FormatCustom, + FormatMax + }; + static QMap geometryIdNameMap; + static QMap geometryNameIdMap; + + static const QString &formatName(Geometry::Format id); + static Geometry::Format formatId(const QString &name); + struct InitStatics { InitStatics(void); }; + static const InitStatics initStatics; + friend struct InitStatics; + + int center[2]; + int dimension[2]; + int corner[2][2]; + + Geometry(void) { setMax(); } + Geometry(const char *s) { set(s); } + Geometry(const QString &s) { set(s); } + //Geometry(const Geometry &g); + + Geometry::Format set(QString str); + void setMax(void); + void setCenterDimensions(int cx, int cy, int dx, int dy); + void setCornerDimensions(int cx, int cy, int dx, int dy); + void setCorners(int c0x, int c0y, int c1x, int c1y); + QString getString(Geometry::Format format = Geometry::FormatNone); +private: + const QRegularExpression corners = QRegularExpression("(-?\\d*),(-?\\d*):(-?\\d*),(-?\\d*)"); + const QRegularExpression centerDimension = QRegularExpression("(-?\\d*),(-?\\d*):(-?\\d*)x(-?\\d*)"); + const QRegularExpression cornerDimension = QRegularExpression("(-?\\d*)[,:](-?\\d*)[+-](-?\\d*)[+-](-?\\d*)"); + const QRegularExpression cornerDimensionAlternate = QRegularExpression("(\\d*)x(\\d*)[+]?(-?\\d+)?[+]?(-?\\d+)?"); + bool adjustCorners(void); + void computeCorner0(void); + void computeCorner1(void); + void computeCenter(void); // Depends dimensions to be correct ! + void computeDimensions(void); +}; + +#endif // GEOMETRY_H diff --git a/geometrywidget.cpp b/geometrywidget.cpp index fc1adb1..8b81480 100644 --- a/geometrywidget.cpp +++ b/geometrywidget.cpp @@ -2,259 +2,6 @@ #include "ui_geometrywidget.h" -QMap Geometry::geometryIdNameMap; -QMap Geometry::geometryNameIdMap; -const Geometry::InitStatics initStatics; - -Geometry::InitStatics::InitStatics() -{ - Geometry::geometryIdNameMap[Geometry::FormatUnknown] = "unknown"; - Geometry::geometryIdNameMap[Geometry::FormatNone] = "none"; - Geometry::geometryIdNameMap[Geometry::CenterDimensions] = "center-dimensions"; - Geometry::geometryIdNameMap[Geometry::CornerDimensions] = "corner-dimensions"; - Geometry::geometryIdNameMap[Geometry::Corners] = "corners"; - Geometry::geometryIdNameMap[Geometry::FormatCustom] = "custom"; - - for (int i = Geometry::FormatNone; i < Geometry::FormatMax; i++) - Geometry::geometryNameIdMap[Geometry::geometryIdNameMap[static_cast(i)]] = static_cast(i); - -} - -const QString &Geometry::formatName(Geometry::Format id) -{ - if (id < Geometry::FormatNone || id >= Geometry::FormatMax) - id = Geometry::FormatUnknown; - return geometryIdNameMap[id]; -} - -Geometry::Format Geometry::formatId(const QString &name) -{ - if (geometryNameIdMap.find(name) == geometryNameIdMap.end()) - return Geometry::FormatUnknown; - else - return geometryNameIdMap[name]; -} - -Geometry::Format Geometry::set(QString str) -{ - qDebug()<<"Trying to detect format of "<x[<+|-xoffset><+|-yoffset>]"; - if(match.lastCapturedIndex() ==2){ - qDebug() << "format is CenterDimensions with center =0,0"; - center[0] = 0; - center[1] = 0; - dimension[0] = match.captured(1).toInt(); - dimension[1] = match.captured(2).toInt(); - computeCorner0(); - computeCorner1(); - if (adjustCorners()) { - // Order is important here! - computeDimensions(); - computeCenter(); - } - return Geometry::CenterDimensions; - } - else if(match.lastCapturedIndex() ==4){ - qDebug() << "format is CornerDimensions"; - corner[0][0] = match.captured(3).toInt(); - corner[0][1] = match.captured(4).toInt(); - dimension[0] = match.captured(1).toInt(); - dimension[1] = match.captured(2).toInt(); - computeCenter(); - computeCorner1(); - if (adjustCorners()) { - // Order is important here! - computeDimensions(); - computeCenter(); - } - return Geometry::CornerDimensions; - } - else return Geometry::FormatCustom; - } - else { - qDebug()<<"Warning: Could not parse format of string: "< 32767) { - corner[j][i] = 32767; - adjusted = true; - } - } - } - return adjusted; -} - -void Geometry::setCenterDimensions(int cx, int cy, int dx, int dy) -{ - center[0] = cx; - center[1] = cy; - dimension[0] = dx; - dimension[1] = dy; - computeCorner0(); - computeCorner1(); -} - -void Geometry::setCornerDimensions(int cx, int cy, int dx, int dy) -{ - corner[0][0] = cx; - corner[0][1] = cy; - dimension[0] = dx; - dimension[1] = dy; - computeCenter(); - computeCorner1(); -} - -void Geometry::setCorners(int c0x, int c0y, int c1x, int c1y) -{ - corner[0][0] = c0x; - corner[0][1] = c0y; - corner[1][0] = c1x; - corner[1][1] = c1y; - computeDimensions(); - computeCenter(); -} - -QString Geometry::getString(Geometry::Format format) -{ - switch (format) { - case CenterDimensions: - if(center[0]==0 && center[1] ==0) - return QString("%1x%2") - .arg(dimension[0]) - .arg(dimension[1]); - return QString("%1,%2:%3x%4") - .arg(center[0]) - .arg(center[1]) - .arg(dimension[0]) - .arg(dimension[1]); - break; - case CornerDimensions: - return QString("%1,%2+%3+%4") - .arg(corner[0][0]) - .arg(corner[0][1]) - .arg(dimension[0]) - .arg(dimension[1]); - break; - case Corners: - default: - return QString("%1,%2:%3,%4") - .arg(corner[0][0]) - .arg(corner[0][1]) - .arg(corner[1][0]) - .arg(corner[1][1]); - break; - } - -} - -void Geometry::computeCorner0(void) -{ - for (int i = 0; i < 2; i++) { - if (dimension[i] < 0) - corner[0][i] = center[i] + (-dimension[i]) / 2; - else - corner[0][i] = center[i] - dimension[i] / 2; - } -} - -void Geometry::computeCorner1(void) -{ - for (int i = 0; i < 2; i++) { - if (dimension[i] < 0) - corner[1][i] = corner[0][i] + dimension[i] + 1; - else - corner[1][i] = corner[0][i] + dimension[i] - 1; - } -} - -void Geometry::computeCenter(void) -{ - for (int i = 0; i < 2; i++) { - if (corner[0][i] > corner[1][i]) - center[i] = corner[0][i] - (-dimension[i]) / 2; - else - center[i] = corner[0][i] + dimension[i] / 2; - } -} - -void Geometry::computeDimensions(void) -{ - for (int i = 0; i < 2; i++) { - if (corner[0][i] > corner[1][i]) - dimension[i] = corner[1][i] - corner[0][i] - 1; - else - dimension[i] = corner[1][i] - corner[0][i] + 1; - } -} template static void setQWidgetValue(Q *object, V value) @@ -345,7 +92,7 @@ bool GeometryWidget::set(const QString geomStr, Geometry::Format requestedFormat return true; } -QString GeometryWidget::getGeometry() +QString GeometryWidget::getGeometry(void) const { Geometry geometry; Geometry::Format format = getFormat(); @@ -393,7 +140,7 @@ bool GeometryWidget::setFormat(Geometry::Format format) return true; } -Geometry::Format GeometryWidget::getFormat(void) +Geometry::Format GeometryWidget::getFormat(void) const { Geometry::Format format = static_cast(ui->geometryStackedWidget->currentIndex()); // Usage of '0' instead of a symbolic value is intentional. The currentIndex() should never be less than 0. @@ -419,6 +166,7 @@ void GeometryWidget::on_geometry_CD_CenterX_editingFinished() ui->geometry_CD_DimensionX->setValue(65536 - 2 * center); else if (center + (dimension - 1)/2 < -32768) ui->geometry_CD_DimensionX->setValue(65536 + 2 * (center - 1)); + emit editingFinished(); } void GeometryWidget::on_geometry_CD_CenterY_editingFinished() @@ -429,6 +177,7 @@ void GeometryWidget::on_geometry_CD_CenterY_editingFinished() ui->geometry_CD_DimensionY->setValue(65536 - 2 * center); else if (center + (dimension - 1)/2 < -32768) ui->geometry_CD_DimensionY->setValue(65536 + 2 * (center - 1)); + emit editingFinished(); } void GeometryWidget::on_geometry_CD_DimensionX_editingFinished() @@ -439,6 +188,7 @@ void GeometryWidget::on_geometry_CD_DimensionX_editingFinished() ui->geometry_CD_DimensionX->setValue(65536 - 2 * center); else if (center + (dimension - 1)/2 < -32768) ui->geometry_CD_DimensionX->setValue(65536 + 2 * (center - 1)); + emit editingFinished(); } void GeometryWidget::on_geometry_CD_DimensionY_editingFinished() @@ -449,6 +199,7 @@ void GeometryWidget::on_geometry_CD_DimensionY_editingFinished() ui->geometry_CD_DimensionY->setValue(65536 - 2 * center); else if (center + (dimension - 1)/2 < -32768) ui->geometry_CD_DimensionY->setValue(65536 + 2 * (center - 1)); + emit editingFinished(); } void GeometryWidget::on_geometry_C0D_CornerX_editingFinished() @@ -459,6 +210,7 @@ void GeometryWidget::on_geometry_C0D_CornerX_editingFinished() ui->geometry_C0D_DimensionX->setValue(32767 + 1 - corner); else if (corner + dimension - 1 < -32768) ui->geometry_C0D_DimensionX->setValue(-32768 + 1 - corner); + emit editingFinished(); } void GeometryWidget::on_geometry_C0D_CornerY_editingFinished() @@ -469,6 +221,7 @@ void GeometryWidget::on_geometry_C0D_CornerY_editingFinished() ui->geometry_C0D_DimensionY->setValue(32767 + 1 - corner); else if (corner + dimension - 1 < -32768) ui->geometry_C0D_DimensionY->setValue(-32768 + 1 - corner); + emit editingFinished(); } void GeometryWidget::on_geometry_C0D_DimensionX_editingFinished() @@ -479,6 +232,7 @@ void GeometryWidget::on_geometry_C0D_DimensionX_editingFinished() ui->geometry_C0D_DimensionX->setValue(32767 + 1 - corner); else if (corner + dimension - 1 < -32768) ui->geometry_C0D_DimensionX->setValue(-32768 + 1 - corner); + emit editingFinished(); } void GeometryWidget::on_geometry_C0D_DimensionY_editingFinished() @@ -489,6 +243,7 @@ void GeometryWidget::on_geometry_C0D_DimensionY_editingFinished() ui->geometry_C0D_DimensionY->setValue(32767 + 1 - corner); else if (corner + dimension - 1 < -32768) ui->geometry_C0D_DimensionY->setValue(-32768 + 1 - corner); + emit editingFinished(); } void GeometryWidget::on_geometry_parse_clicked() diff --git a/geometrywidget.h b/geometrywidget.h index 2923b27..0a5ffcc 100644 --- a/geometrywidget.h +++ b/geometrywidget.h @@ -5,64 +5,19 @@ #include #include -class Geometry; +#include "geometry.h" + class GeometryWidget; namespace Ui { class GeometryWidget; } -class Geometry -{ -public: - enum Format { - FormatKeep = -2, // Special value used when setting geometry: don't change current format. - FormatUnknown = -1, - FormatNone = 0, - CenterDimensions = 1, - CornerDimensions = 2, - Corners = 3, - FormatCustom, - FormatMax - }; - static QMap geometryIdNameMap; - static QMap geometryNameIdMap; - static const QString &formatName(Geometry::Format id); - static Geometry::Format formatId(const QString &name); - struct InitStatics { InitStatics(void); }; - static const InitStatics initStatics; - friend struct InitStatics; - - int center[2]; - int dimension[2]; - int corner[2][2]; - - Geometry(void) { setMax(); } - Geometry(const char *s) { set(s); } - Geometry(const QString &s) { set(s); } - Geometry(const Geometry &g); - - Geometry::Format set(QString str); - void setMax(void); - void setCenterDimensions(int cx, int cy, int dx, int dy); - void setCornerDimensions(int cx, int cy, int dx, int dy); - void setCorners(int c0x, int c0y, int c1x, int c1y); - QString getString(Geometry::Format format = Geometry::FormatNone); -private: - const QRegularExpression corners = QRegularExpression("(-?\\d*),(-?\\d*):(-?\\d*),(-?\\d*)"); - const QRegularExpression centerDimension = QRegularExpression("(-?\\d*),(-?\\d*):(-?\\d*)x(-?\\d*)"); - const QRegularExpression cornerDimension = QRegularExpression("(-?\\d*)[,:](-?\\d*)[+-](-?\\d*)[+-](-?\\d*)"); - const QRegularExpression cornerDimensionAlternate = QRegularExpression("(\\d*)x(\\d*)[+]?(-?\\d+)?[+]?(-?\\d+)?"); - bool adjustCorners(void); - void computeCorner0(void); - void computeCorner1(void); - void computeCenter(void); // Depends dimensions to be correct ! - void computeDimensions(void); -}; class GeometryWidget : public QWidget { Q_OBJECT + Q_PROPERTY(QString geometry READ getGeometry WRITE set NOTIFY editingFinished USER true) public: explicit GeometryWidget(QWidget *parent = 0); @@ -73,9 +28,9 @@ public: bool setFormat(int i) { return setFormat(static_cast(i)); } bool setFormat(Geometry::Format format); bool setFormat(QString formatStr) { return setFormat(Geometry::formatId(formatStr)); } - Geometry::Format getFormat(void); + Geometry::Format getFormat(void) const; QString getFormatStr(void) { return Geometry::formatName(getFormat()); } - QString getGeometry(); + QString getGeometry(void) const; private slots: void on_geometryFormat_currentIndexChanged(int index); @@ -99,6 +54,8 @@ private: QSpinBox *m_ui_C0D_corner[2]; QSpinBox *m_ui_C0D_dimension[2]; QSpinBox *m_ui_C01_corner[2][2]; +signals: + void editingFinished(void); }; diff --git a/images/desktop.ini b/images/desktop.ini new file mode 100644 index 0000000..1ccb9a2 --- /dev/null +++ b/images/desktop.ini @@ -0,0 +1,3 @@ +[LocalizedFileNames] +draw-arrow-up.png=@draw-arrow-up.png,0 +draw-text-2.png=@draw-text-2.png,0 diff --git a/images/draw-arrow.svg b/images/draw-arrow.svg new file mode 100644 index 0000000..4c017e9 --- /dev/null +++ b/images/draw-arrow.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/images/draw-circle.svg b/images/draw-circle.svg new file mode 100644 index 0000000..da6f8a7 --- /dev/null +++ b/images/draw-circle.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/images/draw-ellipse.svg b/images/draw-ellipse.svg new file mode 100644 index 0000000..9362670 --- /dev/null +++ b/images/draw-ellipse.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/images/draw-line.svg b/images/draw-line.svg new file mode 100644 index 0000000..974eb6e --- /dev/null +++ b/images/draw-line.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/images/draw-point.svg b/images/draw-point.svg new file mode 100644 index 0000000..01bcdd5 --- /dev/null +++ b/images/draw-point.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/images/draw-rectangle.svg b/images/draw-rectangle.svg new file mode 100644 index 0000000..4398213 --- /dev/null +++ b/images/draw-rectangle.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/images/draw-text.svg b/images/draw-text.svg new file mode 100644 index 0000000..074a392 --- /dev/null +++ b/images/draw-text.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + T + + diff --git a/images/draw-unknown.svg b/images/draw-unknown.svg new file mode 100644 index 0000000..e94b03f --- /dev/null +++ b/images/draw-unknown.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/images/drawarrow.png b/images/drawarrow.png new file mode 100644 index 0000000000000000000000000000000000000000..4f60478edaf57fd05fc243f34abfdf9d9a9c3a16 GIT binary patch literal 11133 zcmZu%RX`j~6ATdCIa~ru;A|heeeIn?%d1G z_RMrwRo8B)l7b{U3Ngxu4+K41*|cflmm zV~~9KK=DBuETZ-^be*; zt1m>8gNzLQtwoTdHY|^lGe;200EQ9_A|Z0cmLW~m{zonxEqm*G-K?#8v{+z9V!J-0 zi;7$nl7H9;!60{2@>F7da?I;4`}O*HS1K8k*_+mvXMYMbi_H%A_H*{l&2#zi^XG=A znT?IEvTmvT$Zpp{#A)#7Jn3+1gpp8-xsg2>rqC{kIqtl=g+k? zR+mHFmJJjlki#5Jti=k+BOQzJL%7hyn)&8NnQ&_RThfnMjH^&tDN8xYsMWOYf@RVk<=$%*i<;L|}sk!|3HdcWLX}t$xTF)7wYqvQMp`mEMc1q3l1n4 zQSr(|VYJ6k7|EtqHBb6YhJ+?@w>;Mb8%(l%RjhUt>9b7ta%I!R~U+?5`=Z zIx@G51)M}i?;%$8*4hR}*D@wOCCPIL!fJC!PX~ej--7TMO~tzt*>&0~Fzu(h?4IQ&r?vciSd z`3x=VezV+HX4hwkap|xsySjPeD?`Z~wFZBh6EsPt3&a7e4`B9}X)rGL=#e&Zhbx1}HKkw;1cKEj&n!u>whfSNh_=war8%G9ZEI_J=%fv78qw=z=+Ur< zy8mmJXNWKaWeHh)y0$&zbj zYb-4;3OL9L0WJqNMea~WrC^fv;=`W0bYq6=+3_au?A|xRI z*_bUfq=Ba$=mv-Y9rsDHI|g0XhrR;L2Xbt@2z!j8qe&7DB$MOvo@^7!8^ z9TNfVli1vGZ|`-y`1tfI(&4OuN7H6V8g40^vXNnCXd++@Cp$p;Vbduif2Xem!6-gY z!;w*F4HLGg8vP{vTmXEZtN{AJ+2?4%uBXH z@iw~CI}gjy%SNS$9o9%_aY&F97GcWIm_Yt*lfrobKyzXM56G5pVdGw=LG}Asmu>;g zr}_g6c_WXA(-Xw#TlfS5$53x=rzfrHr>I8>2F$I0Db_iNPhWLwH;@b!YUG6*N3l^K zu{~>AE}-dm_``7(AINtzOV7am@Fl}&m%}5!s6k6m*LGE(7Pn#X$b}e_UMN#p!Lo95 zA~RfY&Kc$LXt+1M^*iaP;95O4YgGyT_^ZRQtM#A+$>3f?#s&h>0{Z90J+PrL9VZ7z zLBdWBWFG#iXO`2C6Jkk0WD?DNVeV@7%lzrw;G{YGU8g|E>EOrcskl> zR)`CtwG|e%WaTI0)Luj?j=|XJJ5$WTYk?m|EdUm{xC-fTP;W;=3^J0el#=m)=nr73 z$bu2FVA5=W6=k*YASGjTkzFlpj1w#!)$(9UG5~kX_fAraEQj25StJUY4N>IFzP9I| z7UgRZ5YNH`i)iX#V}9hhN_bQzA%r4Onj(dhj0jNFUKs76NNk3E*mojBqQ7qoS>JzBDvRg)5%V?X^+ zyCXiCHS#YFPbJ=)%c{0wOIkefQ|MEgM+9;h%jZQ4Bk}=4_7*!-;{FTpG{CL-i?@^( zjl81iV-F@hRg(%zBQBKyb||f~UkQE)my(fa(6dM_ez9x|Q8^3FXd3i{sRzvY7!(IY z17dr-Ec?iM1AFas5>#lXSce2OJZ;%178CHtKZc_tV^8pTmLFX8e-Sj1<<&eAB$?qh?G(#+uZkR#UX~RPJ&0 zr1|s^%!+{98?az%(IOpg7LXWRq>sEDl^5P;c40i%jzK+Obh9) z|LQsfyU|J6TubmcAjH}2N4pxzFy=pU9(R;)x8$AY!7zVV zj0`XS%(G7}WKnzu9*@fALxsuYg~ziDWjfAM`QY#c5eAwbWc$#Z7PKH897q~(EGR&2 z8zS0E`29t=n<1&d5k&;)?{I4InQ9&v&9_ReI?B5ahB>~q`WdH;ez0N?-u45M@$TUy zS;t7HvfN}kVTRcG$%(4WSB;JlkQNr

L+GkjK%!RX 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 bJ0b$ArFqT4c0BZZ+7Hs=3g9YH!+`$*ktuiw literal 0 HcmV?d00001 diff --git a/images/drawcircle.png b/images/drawcircle.png new file mode 100644 index 0000000000000000000000000000000000000000..119204798010c1bddbea15d660e8348d3de5ebcc GIT binary patch literal 5180 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

c#TO=jYOV?fr zJ7hSWoV1nh(FlYik12rQMmGSE^?GT6weok~s6?VW>qQLvuwUTyfv0H9%W*l~-P*-q zH?tSG54Ho$yfu|NI$@ish8=oLX5%aWZpfAs09suj08JI_ z-xWXk+kx?v9pNQBU}cNQcinW8>IXY_zytfUeMr7LUyr3CNsy5L6K* zn=K*+1Ge~l*7bY;KFo$!?jZxN>~HM|z_=gAbdh)Gelu1M(OEFR(hVp)D$MmC3n>$* zz-=7s*qv_C5ZJBks_c!ZR^-@2i4a6_B}3i{4gwx-bWHqGsy zY@=e(QM)*^)Rjmx+y4tr`FrZ@m)3;0nGV+ZmoVW7%7zRKhY}ycu6+EX9a#W(V_S{@ zvK6DfTC1%q1%UcATqYcJH)AoL(F(2QN{F@P0UbvID)m6 z$Y4Ye^GNOz0lbTY_+Wq54{W3=$oWBpBF~50u_Svfx-y@3t>&U0Oo$SK{4)f)6SDUo zx(|Rs_{_YVb_7FpQU==S`M?YG#b0`sZN2U3K^*eC}r#5a0uv7Hm{p7k872j^Lr-Zxz`ThHM zm#6lD{UHS>!Ze&rOsgh0<6z`%({o1kcYo5#?EPzMfHX_Sg!Ku#)+mqM1`x4Qoz4M|ts@(^k0 zw}vc~zc$+YU#+m0mt#Vaepj=>_{u(Hgj|2I=KpMTsr=;

L3{+J3SQ^mkSe#MOuQ c9;y`mKV8cZ4wJt24*&oF07*qoM6N<$f@+3Xvj6}9 literal 0 HcmV?d00001 diff --git a/images/drawpoint.png b/images/drawpoint.png new file mode 100644 index 0000000000000000000000000000000000000000..9240c681edbc89a51f2cfafa66ca04b15dc7987e GIT binary patch literal 5102 zcmVC0000WV@Og>004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00039P)t-s|NsBdrC~^GEU2@P zt${tcjY-9sRo$^|Bu5A^QxrU3AXIlaY>!Z2gG6hQMCrYGp>;8pX(fqW8GTU=03!eZ z7XXc8CT)~OaYX@{Z!%$sISelV0002>$%Oz50APhU{{R4iR~j)=gmbDL7&`y}03;*-^xK3cuT5Q|^833!w8v`N$01W`3%XdEw|PuHb*psYl28^Aj2&rg!nPx00001 zVoOIv0Eh)0NB{r;32;bRa{vGi!~g&e!~vBn4jTXf5>rV;K~#9!ty>L4<4AI~`Yg%n zw${K5>>e~sZjMc)2xTQZB$>>SW4VRd?A~3HOD_5U|0M5KwZ06)>m!>*zr>PV*tNeMsbv+PeVwTtIT#*-8y&9er0C{u*4%tHc_1H9Vr|T zEeHF?<;EG8xtRe(QM_D6$>Ze{LY8H2@IdZzYn;U~D}eYQj^lLGJv}&BE|WN7OpyRl4ZtrxsbcHajPs_TbIoUAYLBeKM8pP9(Jacl2L?sclJp_Jjt`%zMc_4 zas)GpV9pb8)e z=odUsg9D7UYspFH<|N@^9PGz~3j_kbfWe8+6+oT~2>@hH07uKtzPex#A^0GTEY{C2 z!obRH23F3ShOac%9fbZ!`5+93RB+;gbuI*$QwM%^K*{M{5T7I?4&ZST^M8$k%h%>B z3UfZA5*|e9A!Q&5k7UXLCa8-nzbr(MiGcV6O+I4?AV0kX#C-@l1gWw?2(aebEm)A{ zZcYGk630Ccq6q?~BERSXO0lFOSum#unEB%Rcv_K@009w5txY3_0FFQ`Y(A?8QqWMu zKME?;H%D@sBsLWYj6ajWNjT>8fE3>`14ivxLiZ=)0!!~(l!mYIHKz{5QvaELllYG@ zzy(-0W1qnfnVGc~#4&n+ct8GWG!6*#*a`yJ&eRjL0YE?e&EY}vDEw)k6D$WJ$%?^> zD$UlAAod4G2SWz-2+~nbTG0a+IV1llMRRe0>JI?XPy~hy7)1otfI(0IRvt3}pwuo8 zmrnqgQo^A>#nRKSq$cn&8-O@kzKTBuAd;%g7)Vi`5daE)a(MVj049w#j6y25=L7(| z2`G>d$}<3HJz__gN@qAH06JhP9dJOf2jC=82SmTNIe3tsm{TYH>0AKfqoXB9AR>@s zpMK&1V>Xr%Fe%kw&W`Bd6$UYyPtzp&$gxQ!2Ae21`VS{_lw*T`2N(on*iJtVf#zY{ zY#0bS!7%{&93C)mz?qHJb{b&p8P|}=`lB2x--rul;Q%Tcrc#cD-SYRnut_H&$U_j| zglG;95WvwP{O^(uLO?O3Uif~JNC+Z#LM_-tr?c!2u(rQiN#YH*M+i2<@)NEfi4dtA z;aldL(iu`LOD2_1Hft}y}nCRORLU`Fbje{{2dhh zmx^U10lsAy>z4z_&a}a%&{@+tKox0BgEXAbGW?G*$);}Ye~FF1c@}_Ot#tPZQGgF5 z3K|4jOh#+h6Gtp5&{7EP>g67=XQS;eoTLdDkq`(irpYE9IRU>Sv+8f8J+f)~@|KJg z$T;gbcDt!`{FdLr1s#l|!3ienhGp@CN!$!1BnA2%7k(wQEw1Qy3^${MP*7k3K$sc2 zVsn-mXT|{F31TqWCQGxFtImUE5>*y1qk1G;xG%H*_U+p>h8*`>I|7jD?o;IbYk#>; z)&5EEj#M(?1kl)?MK84v02#MioXNt#Bi2o1vv1AczRlJtNr=dCP{xo7Z?mm zGXPNc%eQR6;IeP|({m@xqXw&3hIw7G0LW$lFuM7WjlPWeySHoZ1%-ChMFEKnXi=Iw z^XusRd^00}J_cF1DY1qTUAI9uCiYF}jn^r=Gvm^ik#GbclNj_EjP633ppYBat)6^{ zl5=-1ss#Sb10dWC$%fP6phkuG_wG19$DAry=HWm#rNGp>dLaZLCIj>QoIL@=8qRnT z0Ae73a4#n4z$5DlK)#_FhYGA6DB&=n*%#_S2f#c9Aa@gA1phmcq4QLS83@Qc0G#f9 z+uj-b=1rdtc-XbLMmB9`=n2*0fWX!vLL{Q~>iooNQsh>c{PraPOb=lcfhS@hZV(+n77$G&n}u@#m~cQLJf;8*0t0W| zMdzC!3z#Mpz!yO%ied=CBm%wf!%YbSvb`T%exCN?V7?51*e;5)2qE};JB@(!2RVAP zO!h%Ah*daz`0?6Vca@x21|k0v0I*ya-N6e7>;GWKl9U@Zi`)`pJgTy+1%tDBgc|*w zwL(I(Fl#x?XB<^oH*H-sEzH+#T~{7g)>deu1Nm!C40+CE^c67!>K0TukIW8ezLDwp zm)o^nWO1#wXgwZfg)=Oz_1=!OK;~{^{6FGY>pj8>m|LvhZ8ONgb%ksAZ-Be{3;-q& z%2X}Z4$F02R0Uj6RHX)@%f^rc{@Z{0XB9n@18G_NYok*fedzziRrveQ0nkCH82hqp z-~ybsEgd56?IbDuw(mm=5*EHJlmiBa@h4UFj{aNx>t6kS|NL|NJBy1|rO*%dz4tdB z3u=qr1>KRaJ0v+lH;sqexlav3lz>nW{$me}ukmko@0y0G?Yt*+T;lftPOUf!%BdOUou(p$!T$>JQb(~kC#3G+O zX>0un)amm9$aO`1T9rUL7M?8`^QjfHExsXM@O zlYxsBMUPpJmq3t=Rh%NqJEuLhpAg-T$q^$8k5dhxaMa~G0HR%4EKdNTY`l{cAx>04 zqOy069%w{FuNOml**)tYNdcnZqDvui&Gv}0OiCcT8&QXnk&#+MQbKlWiXiaQObXGpAuYygUeND%pwegJ@`BJX{5?3grdb*nW{TZf93k;yUw zkPU432puZTp+a@MEmdXUakLsGIl$LzIt11w>i9u%vDMmHi7KNvWaY-uK&;%h8u|gL zB~I%9*#6KDNCm8=55%g)@9J7IfQ6-PRS{~sTI7b*yx2(widRI2TUIgH+A{2e2ePa1 z)~3$>)zIMbxWBI{;5AdMCF{f+zQJm$#<%c3`RJw2apedAec_AOMlw)1!t7~sl{0%)CfwxTb9 zrvosG`jCG24<8=9o*v&n{0MWN^My3R0-$Ob(;796Pg=yNY^VqjK&B#`157oN5ME&q z#t;yyVU@sTZcN`5kCJ}K#`_PQ0|d-#|3l_)q?c((a4qXxTq_a;a!Rj>ghY(#0=h`R zYDeZ9Y2v`ZEf-QZ)gQVLj2w^+0|@-?yO)oTU+X5U=xojFVnKE5Z>cZR=@jA`1{MG$ z+D$8r?V5Kll6`?r!AkjpWL+qLaoSnace7__XXeqt>#~+sO(PfGNfvVPNdB5j_5ieH z&j2no%-yokh@GBW({bSAvm|t=5NK+l zvCOMVFV4&P%x$wuW*$t;nrM_GuQj^u1_2B(GFQnKLG^exd~c@!9_<%EA4Pj8b&`=h zm0r*>`-R*$tDW!x%3VA1yk?g} z<{j&(Bw>{Xwf5UPOK)HnW9J0EOGVZJf(gsQA&eXW=&$Kiz|NQ5k|ZQN z(3G7*wxT>b7?j$#>omnuP)q>yiueftmzQV08;x4&?RDd-k-kKF6ZpQ75Xj)fR;AKe zT;@mJSn;~i(+{WJy{0R<5#8_eTV=w}d+}gX*LvCG2p$Mjmj)5?E5m^}0062|2iLZh zMv*^mRJGAkE+*y?X4%&ql1}%YpJ>xbv0A4K`E{ASTIrElWNI0Gq!%nomLo&}ZIP5n zFI{^f?2zGfbf+zMPe!1>-EwCYvpgeQHex%)`}RmVZXrZiKl2x%W*#5 z-P*^n2Z8&*>)dpI;aOa6C_ui}eUKXD3yiOITSQ~jYJHRq5^m9q-RqvC;2r|LZ^88r z0FEu+r@WPn4DH62?r3B;#LHMEV+~v*{Qx_rc2tZ`?bHFM_E`k{+>p&j0JOS50IDL` zzs-K|H$CGEndWqkwX#Lzn`XR8^_`tM;ECyYjKJzUM+V51mgT1ELXCyqvG*aUtdnfE zh!_mm;`3S4F8?f(U*{DnIEjWywIroDB35+>Y&8V%4q|2gc+cOTl31#maE<_I8LG1{xu z+PYEzs89W2!j0}~C`Q_evhwhWecot zrPf^*xl|`ahI8lzB#@VSZ%t1H8hhX0l+@#Q*_fk!%y>(p=+av@F+=6y2v!#&gB^k{ zkK`&HfQN1n@9j_f0jUbo4|GuEd4D;UWUob6=F_IuT+|yAqJ$v-1cB~^EdE1_JO<%2 z^Jd%;^wp_j{cumHch5jt=~Oc#C60RsrSCRl!M|2Fl)M`w2=BI8Xv7KN{<6E}Hco|Z z!J`k1^6YzS_HIgq-O)<-PK#S{2B$V|39wW2;r-;=*>`WZSSaD{fBf*_;qufzvi}&# z=#2{Fs>#hb7`dxj&Zz$O&sv$ie@6|FX33ba{+@-vu+UqpmptUEZzlIf%+-JL#h?4z zvR88U^Bz*3>5aL&N@i4RcQ*6Qe{Jt}m5wN$6#R>svJis@-+-Vw7g#uoD~sRka zD<8c{A@tGOj{21(I#mw;t?XU5w_o(@AB(-^R;>%>yWNm!H5dz_oPR%7q42UeKz5&? zaXYy#`C#4k4p3UpUd4AGDVS_VX8o<(`=37ic=-$irY?}9@6&RMp-da4xB6zfN+;z? zwrv~AZ}8mUg22Ls2m9YO-Eh5q zO+#JQ|NQ7LrxDoi2TtuM6(@Hn<%z2NUo%<0>>$ZDo@}Gv@Fr5`9IsaR6e-`l%hqowx6s6?X49A-Ri@84^@i(ALw@sQ|8Xs Q$N&HU07*qoM6N<$fC0000WV@Og>004R> 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 BXaWEL literal 0 HcmV?d00001 diff --git a/images/drawtext.png b/images/drawtext.png new file mode 100644 index 0000000000000000000000000000000000000000..39f8139fc8da73c47d0c5761d2c42ab9088a64d7 GIT binary patch literal 5304 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 literal 0 HcmV?d00001 diff --git a/mainwindow.cpp b/mainwindow.cpp index b758cff..d1a1d41 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -78,7 +78,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); finishUiInitialisation(); readSettings(); - readProfile(currentProfile); + progressBar = new QProgressBar(ui->statusBar); progressBar->setAlignment(Qt::AlignRight); progressBar->setMaximumSize(180, 19); @@ -92,11 +92,34 @@ MainWindow::MainWindow(QWidget *parent) : createLanguageMenu(); createProfilesMenu(); + ui->figureSelect->addItems(DrawMapFigure::getFigureList()); + + drawMapFigureTable = new DrawMapFigureTableModel(this); + + ui->figures_list->setModel(drawMapFigureTable); + + ui->figures_list->setItemDelegateForColumn(0, new FigureDelegate(this)); + drawMapFigureTableMapper = new QDataWidgetMapper(this); + readProfile(currentProfile); + drawMapFigureTableMapper->setModel(drawMapFigureTable); + drawMapFigureTableMapper->addMapping(ui->figureSelect, 0, "currentIndex"); + drawMapFigureTableMapper->addMapping(ui->figureUseImageCoordinates,1); + drawMapFigureTableMapper->addMapping(ui->figure_point,2); + drawMapFigureTableMapper->addMapping(ui->figure_geometry, 3, "geometry"); + drawMapFigureTableMapper->addMapping(ui->figure_color,4); + drawMapFigureTableMapper->addMapping(ui->figure_text,5); + connect(ui->figures_list->selectionModel(), SIGNAL(currentRowChanged(QModelIndex,QModelIndex)), + drawMapFigureTableMapper, SLOT(setCurrentModelIndex(QModelIndex))); + ui->figures_list->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->figures_list->resizeColumnsToContents(); QCompleter *completer = new QCompleter(this); QDirModel *model =new QDirModel(completer); model->setFilter(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Drives); completer->setModel(model); ui->path_World->setCompleter(completer); + + + } void MainWindow::finishUiInitialisation(void) @@ -428,6 +451,9 @@ void MainWindow::on_button_generate_clicked() } } + // Draw figures tab + arguments << drawMapFigureTable->getArguments(); + ui->button_generate->setDisabled(true); if(ui->actionExpert_Mode->isChecked()){ @@ -543,6 +569,18 @@ void MainWindow::mapperFinisched(int exit) ui->statusBar->showMessage(tr("minetestmapper terminated")); } else{//something was wrong + QMessageBox errBox; + errBox.setText(tr("Minetest Mapper failed")); + errBox.setSizeGripEnabled(true); + errBox.setMinimumSize(800,600); + errBox.resize(800,600); + errBox.setIcon(QMessageBox::Icon::Critical); + errBox.setInformativeText(tr("Exit code: %1").arg(exit)); + errBox.setDetailedText(ui->statusBar->currentMessage()); + errBox.setStandardButtons(QMessageBox::Close); + errBox.setDefaultButton(QMessageBox::Close); + errBox.exec(); + QMessageBox::critical(this, tr("Minetest Mapper failed"), tr("

ERROR

minetestmapper failed

" "Exit code: %1
" @@ -811,7 +849,10 @@ void MainWindow::writeProfile(QString strProfile) /* * Todo: also save and restore other tiles */ + profile.endGroup(); + profile.beginGroup("drawFigures"); //tab7 Draw Figures + profile.setValue("drawMapFigures", drawMapFigureTable->getStringList()); profile.endGroup(); } @@ -911,6 +952,10 @@ void MainWindow::readProfile(QString strProfile) ui->tiles_coordinateX->setValue(profile.value("tiles_coordinateX",0).toInt()); ui->tiles_coordinateY->setValue(profile.value("tiles_coordinateY",0).toInt()); profile.endGroup(); + + profile.beginGroup("drawFigures"); + drawMapFigureTable->insertStringList(profile.value("drawMapFigures",QStringList()).toStringList()); + profile.endGroup(); } void MainWindow::closeEvent(QCloseEvent *event) @@ -1187,3 +1232,29 @@ QString MainWindow::getColorsTxtFilePath(QDir *appDir, QDir *worldDir) } return retval; } + +void MainWindow::on_button_addFigure_clicked() +{ + drawMapFigureTable->insertRow(0); +} + +void MainWindow::on_figure_geometry_apply_clicked() +{ + drawMapFigureTableMapper->submit(); +} + +void MainWindow::on_button_deleteFigure_clicked() +{ + QModelIndexList indexes; + while((indexes = ui->figures_list->selectionModel()->selectedIndexes()).size()) { + drawMapFigureTable->removeRow(indexes.first().row()); + } +} + +void MainWindow::on_figureSelect_currentIndexChanged(int index) +{ + QStringList lookup = QStringList()<<"figure" + << "drawmaparrow" << "drawmapcircle" << "drawmapellipse" + << "drawmapline" << "drawmappoint" << "drawmaprectangle" << "drawmaptext"; +ui->figureInformation->scrollToAnchor(lookup.at(index)); +} diff --git a/mainwindow.h b/mainwindow.h index 181ad84..0a9487c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #ifdef Q_OS_WIN #include #include @@ -20,6 +22,9 @@ #include "configdialog.h" #include "colorstxtassistant.h" +#include "drawmapfigure.h" +#include "drawmapfiguretablemodel.h" +#include "figuredelegate.h" namespace Ui { class MainWindow; @@ -104,6 +109,14 @@ private slots: void on_actionPreferences_triggered(); + void on_button_addFigure_clicked(); + + void on_figure_geometry_apply_clicked(); + + void on_button_deleteFigure_clicked(); + + void on_figureSelect_currentIndexChanged(int index); + private: bool portable; Ui::MainWindow *ui; @@ -135,6 +148,9 @@ private: //QSettings profile; QSettings *settings; QString getColorsTxtFilePath(QDir *appDir, QDir *worldDir); + + DrawMapFigureTableModel *drawMapFigureTable; + QDataWidgetMapper *drawMapFigureTableMapper; }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index 0a333ce..5f07ad8 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -894,7 +894,7 @@ Nodes higher than this level will not be drawn. This can be used to avoid floati 0 0 - 503 + 524 427 @@ -2178,6 +2178,174 @@ Nodes higher than this level will not be drawn. This can be used to avoid floati + + + Draw figures + + + + + + + 0 + 0 + + + + Elements + + + + + + + + + + + + + + - + + + + + + + + 0 + 0 + + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + 30 + + + + + + + + + + + 0 + 0 + + + + Properties + + + + + + + + + Information: + + + + + + + + + + Figure: + + + + + + + red + + + + + + + Color: + + + + + + + Use Image Coodinates + + + + + + + + + + Text: + + + + + + + Geometry + + + + + + + + + Apply + + + + + + + + + + Point + + + + + + + false + + + + qrc:/doc/drawfigure.html + + + + + + + + + MinetestMapper Output @@ -3092,7 +3260,7 @@ p, li { white-space: pre-wrap; } - + diff --git a/makecolors.cpp b/makecolors.cpp index b865dba..2695829 100644 --- a/makecolors.cpp +++ b/makecolors.cpp @@ -173,12 +173,13 @@ bool MakeColors::writeColorsTxt(const QString file) { mi.next(); const QString fullNodeName = mi.key(); - const QString currentMod = fullNodeName.split(':')[0]; + const QString currentMod = fullNodeName.section(':',0,0,QString::SectionIncludeLeadingSep); //write a new paragraph - if(currentMod != lastMod){ - out<images/open.svg images/icon.svg images/minetest.svg + doc/drawfigure.html + images/draw-arrow.svg + images/draw-circle.svg + images/draw-ellipse.svg + images/draw-line.svg + images/draw-point.svg + images/draw-rectangle.svg + images/draw-unknown.svg + images/draw-text.svg + images/drawarrow.png + images/drawcircle.png + images/drawline.png + images/drawpoint.png + images/drawrectangle.png + images/drawtext.png