void DrawPathTool::updateDrawHover() { if (!shift_pressed) angle_helper->getConstrainedCursorPosMap(cur_pos_map, constrained_pos_map); if (!previous_point_is_curve_point && !left_mouse_down && editingInProgress()) { // Show a line to the cursor position as preview hidePreviewPoints(); if (!path_has_preview_point) { preview_path->addCoordinate(MapCoord(constrained_pos_map)); path_has_preview_point = true; } preview_path->setCoordinate(preview_path->getCoordinateCount() - 1, MapCoord(constrained_pos_map)); updatePreviewPath(); updateDirtyRect(); // TODO: Possible optimization: mark only the last segment as dirty } else if (previous_point_is_curve_point && !left_mouse_down && editingInProgress()) { setPreviewPointsPosition(constrained_pos_map, 1); updateDirtyRect(); } }
void TextSymbol::createBaselineRenderables( const TextObject* text_object, const VirtualCoordVector& coords, ObjectRenderables& output) const { const MapColor* dominant_color = guessDominantColor(); if (dominant_color && text_object->getNumLines() > 0) { // Insert text boundary LineSymbol line_symbol; line_symbol.setColor(dominant_color); line_symbol.setLineWidth(0); const TextObjectLineInfo* line = text_object->getLineInfo(0); QRectF text_bbox(line->line_x, line->line_y - line->ascent, line->width, line->ascent + line->descent); for (int i = 1; i < text_object->getNumLines(); ++i) { const TextObjectLineInfo* line = text_object->getLineInfo(i); rectInclude(text_bbox, QRectF(line->line_x, line->line_y - line->ascent, line->width, line->ascent + line->descent)); } Q_UNUSED(coords); // coords should be used for calcTextToMapTransform() QTransform text_to_map = text_object->calcTextToMapTransform(); PathObject path; path.addCoordinate(MapCoord(text_to_map.map(text_bbox.topLeft()))); path.addCoordinate(MapCoord(text_to_map.map(text_bbox.topRight()))); path.addCoordinate(MapCoord(text_to_map.map(text_bbox.bottomRight()))); path.addCoordinate(MapCoord(text_to_map.map(text_bbox.bottomLeft()))); path.parts().front().setClosed(true, true); path.updatePathCoords(); LineRenderable* line_renderable = new LineRenderable(&line_symbol, path.parts().front(), false); output.insertRenderable(line_renderable); } }
void DrawPathTool::createPreviewCurve(MapCoord position, float direction) { if (!path_has_preview_point) { int last = preview_path->getCoordinateCount() - 1; (preview_path->getCoordinate(last)).setCurveStart(true); preview_path->addCoordinate(MapCoord(0, 0)); preview_path->addCoordinate(MapCoord(0, 0)); if (draw_dash_points) position.setDashPoint(true); position.setCurveStart(false); preview_path->addCoordinate(position); path_has_preview_point = true; } // Adjust the preview curve int last = preview_path->getCoordinateCount() - 1; MapCoord previous_point = preview_path->getCoordinate(last - 3); MapCoord last_point = preview_path->getCoordinate(last); double bezier_handle_distance = BEZIER_HANDLE_DISTANCE * previous_point.distanceTo(last_point); preview_path->setCoordinate(last - 2, MapCoord(previous_point.x() - bezier_handle_distance * sin(previous_point_direction), previous_point.y() - bezier_handle_distance * cos(previous_point_direction))); preview_path->setCoordinate(last - 1, MapCoord(last_point.x() + bezier_handle_distance * sin(direction), last_point.y() + bezier_handle_distance * cos(direction))); updatePreviewPath(); }
TestMap::TestMap() { MapCoord coord; map = new Map(); MapColor* black = new MapColor(); black->setCmyk(MapColorCmyk(0.0f, 0.0f, 0.0f, 1.0f)); black->setOpacity(1.0f); black->setName("black"); map->addColor(black, 0); line_symbol = new LineSymbol(); line_symbol->setLineWidth(1); line_symbol->setColor(black); map->addSymbol(line_symbol, 0); line_object = new PathObject(line_symbol); line_object->addCoordinate(MapCoord(10, 10)); coord = MapCoord(20, 10); coord.setCurveStart(true); line_object->addCoordinate(coord); line_object->addCoordinate(MapCoord(20, 20)); line_object->addCoordinate(MapCoord(30, 20)); line_object->addCoordinate(MapCoord(30, 10)); map->addObject(line_object); // TODO: fill map with more content as needed }
void FileFormatTest::mapCoordtoString() { QCOMPARE(MapCoord().toString(), QString("0 0;")); // Verify toString for native coordinates at the numeric limits. auto native_x = MapCoord().nativeX(); using bounds = std::numeric_limits<decltype(native_x)>; static_assert(sizeof(decltype(native_x)) == sizeof(qint32), "This test assumes qint32 native coordinates"); QCOMPARE(MapCoord::fromNative(bounds::max(), bounds::max(), 8).toString(), QString("2147483647 2147483647 8;")); QCOMPARE(MapCoord::fromNative(bounds::min(), bounds::min(), 1).toString(), QString("-2147483648 -2147483648 1;")); }
TimedRestGather::TimedRestGather(uint16 x, uint16 y) : TimedPartyMove(50) { MapCoord center = MapCoord(x, y); init(¢er, 0, 0); // set dest to campfire location Game::get_game()->get_map_window()->updateAmbience(); check_campfire(); }
void TextObject::setBox(qint32 mid_x, qint32 mid_y, double width, double height) { coords.resize(2); coords[0].setNativeX(mid_x); coords[0].setNativeY(mid_y); coords[1] = MapCoord(width, height); setOutputDirty(); }
void PassPoint::save(QXmlStreamWriter& xml) const { XmlElementWriter passpoint{xml, QLatin1String("passpoint")}; passpoint.writeAttribute(QLatin1String("error"), error); { XmlElementWriter element{xml, QLatin1String("source")}; MapCoord(src_coords).save(xml); } { XmlElementWriter element{xml, QLatin1String("destination")}; MapCoord(dest_coords).save(xml); } { XmlElementWriter element{xml, QLatin1String("calculated")}; MapCoord(calculated_coords).save(xml); } }
TextObject::TextObject(const Symbol* symbol) : Object(Object::Text, symbol) , h_align(AlignHCenter) , v_align(AlignVCenter) , rotation(0.0f) { Q_ASSERT(!symbol || (symbol->getType() == Symbol::Text)); coords.reserve(2); coords.push_back(MapCoord(0, 0)); }
void DrawFreehandTool::updatePath() { float length_threshold_sq = 0.06f*0.06f; // minimum point distance in mm if (!dragging) { preview_path->clearCoordinates(); preview_path->addCoordinate(MapCoord(cur_pos_map)); } else { if (last_pos_map.distanceSquaredTo(cur_pos_map) < length_threshold_sq) return; preview_path->addCoordinate(MapCoord(cur_pos_map)); } last_pos_map = cur_pos_map; updatePreviewPath(); setDirtyRect(); }
void BooleanTool::rebuildSegmentFromPathOnly( const ClipperLib::IntPoint& start_point, const ClipperLib::IntPoint& second_point, const ClipperLib::IntPoint& second_last_point, const ClipperLib::IntPoint& end_point, PathObject* object) { MapCoord start_point_c = MapCoord::fromNative64(start_point.X, start_point.Y); MapCoord second_point_c = MapCoord::fromNative64(second_point.X, second_point.Y); MapCoord second_last_point_c = MapCoord::fromNative64(second_last_point.X, second_last_point.Y); MapCoord end_point_c = MapCoord::fromNative64(end_point.X, end_point.Y); MapCoordF polygon_start_tangent = MapCoordF(second_point_c - start_point_c); polygon_start_tangent.normalize(); MapCoordF polygon_end_tangent = MapCoordF(second_last_point_c - end_point_c); polygon_end_tangent.normalize(); double tangent_length = BEZIER_HANDLE_DISTANCE * start_point_c.distanceTo(end_point_c); object->addCoordinate(MapCoord(MapCoordF(start_point_c) + tangent_length * polygon_start_tangent)); object->addCoordinate(MapCoord((MapCoordF(end_point_c) + tangent_length * polygon_end_tangent))); object->addCoordinate(end_point_c); }
MapCoord GameWorldBase::CalcDistanceAroundBorderY(const MapCoord y1, const MapCoord y2) const { int diff = int(y2) - int(y1); if(diff >= 0) // Differenz positiv --> nicht über den Rand, d.h. normale Distanz return MapCoord(diff); else { // Ansonten Stück bis zum Rand und das Stück vom Rand bis zu Punkt 2 return (width-y1) + y2; } }
bool GeoreferencingTool::mouseReleaseEvent(QMouseEvent* event, MapCoordF map_coord, MapWidget*) { bool handled = false; switch (event->button()) { case Qt::LeftButton: dialog->setMapRefPoint(MapCoord(map_coord)); // fall through case Qt::RightButton: QTimer::singleShot(0, dialog, SIGNAL(exec())); handled = true; break; default: ; // nothing } return handled; }
void RotatePatternTool::dragMove() { const auto rotation = -M_PI / 2 - (constrained_pos_map - click_pos_map).angle(); for (auto object : editedObjects()) { /// \todo Refactor, provide a unified interface for rotation in Object if (object->getType() == Object::Point) { if (object->getSymbol()->asPoint()->isRotatable()) object->asPoint()->setRotation(rotation); } else if (object->getType() == Object::Path) { object->asPath()->setPatternOrigin(MapCoord(click_pos_map)); object->asPath()->setPatternRotation(rotation); } } updatePreviewObjects(); updateStatusText(); }
void DrawPathTool::undoLastPoint() { Q_ASSERT(editingInProgress()); if (preview_path->getCoordinateCount() <= (preview_path->parts().front().isClosed() ? 3 : (path_has_preview_point ? 2 : 1))) { abortDrawing(); return; } auto& part = preview_path->parts().back(); auto last_index = part.last_index; auto prev_coord_index = part.prevCoordIndex(part.last_index); auto prev_coord = preview_path->getCoordinate(prev_coord_index); // Pre-undo preparation if (path_has_preview_point) { if (prev_coord.isCurveStart()) { // Undo just the preview point path_has_preview_point = false; } else { // Remove the preview point from a straight edge, preparing for re-adding. Q_ASSERT(!previous_point_is_curve_point); preview_path->deleteCoordinate(last_index, false); last_index = prev_coord_index; prev_coord_index = part.prevCoordIndex(part.last_index); prev_coord = preview_path->getCoordinate(prev_coord_index); path_has_preview_point = !prev_coord.isCurveStart(); } } if (prev_coord.isCurveStart()) { // Removing last point of a curve, no re-adding of preview point. MapCoord prev_drag = preview_path->getCoordinate(prev_coord_index+1); previous_point_direction = -atan2(prev_drag.x() - prev_coord.x(), prev_coord.y() - prev_drag.y()); previous_pos_map = MapCoordF(prev_coord); previous_drag_map = MapCoordF((prev_coord.x() + prev_drag.x()) / 2, (prev_coord.y() + prev_drag.y()) / 2); previous_point_is_curve_point = true; path_has_preview_point = false; } else if (!path_has_preview_point) { // Removing last point from a straight edge, no re-adding of preview point. previous_point_is_curve_point = false; } // Actually delete the last point of the edge. preview_path->deleteCoordinate(last_index, false); if (preview_path->getRawCoordinateVector().empty()) { // Re-add first point. prev_coord.setCurveStart(false); preview_path->addCoordinate(prev_coord); } // Post-undo if (path_has_preview_point) { // Re-add preview point. preview_path->addCoordinate(MapCoord(cur_pos_map)); } else if (previous_point_is_curve_point && dragging) { cur_pos = click_pos; cur_pos_map = click_pos_map; } dragging = false; updateHover(); updatePreviewPath(); updateAngleHelper(); updateDirtyRect(); }
bool TemplateTrack::import(QWidget* dialog_parent) { if (track.getNumWaypoints() == 0 && track.getNumSegments() == 0) { QMessageBox::critical(dialog_parent, tr("Error"), tr("The path is empty, there is nothing to import!")); return false; } const Track::ElementTags& tags = track.tags(); DeleteObjectsUndoStep* undo_step = new DeleteObjectsUndoStep(map); MapPart* part = map->getCurrentPart(); std::vector< Object* > result; map->clearObjectSelection(false); if (track.getNumWaypoints() > 0) { int res = QMessageBox::question(dialog_parent, tr("Question"), tr("Should the waypoints be imported as a line going through all points?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (res == QMessageBox::No) { for (int i = 0; i < track.getNumWaypoints(); i++) result.push_back(importWaypoint(templateToMap(track.getWaypoint(i).map_coord), track.getWaypointName(i))); } else { PathObject* path = importPathStart(); for (int i = 0; i < track.getNumWaypoints(); i++) path->addCoordinate(MapCoord(templateToMap(track.getWaypoint(i).map_coord))); importPathEnd(path); path->setTag(QStringLiteral("name"), QString{}); result.push_back(path); } } int skipped_paths = 0; for (int i = 0; i < track.getNumSegments(); i++) { const int segment_size = track.getSegmentPointCount(i); if (segment_size == 0) { ++skipped_paths; continue; // Don't create path without objects. } PathObject* path = importPathStart(); QString name = track.getSegmentName(i); if (!tags[name].isEmpty()) { path->setTags(tags[name]); } else { path->setTag(QStringLiteral("name"), name); } for (int j = 0; j < segment_size; j++) { const TrackPoint& track_point = track.getSegmentPoint(i, j); auto coord = MapCoord { templateToMap(track_point.map_coord) }; if (track_point.is_curve_start && j < segment_size - 3) coord.setCurveStart(true); path->addCoordinate(coord); } if (track.getSegmentPoint(i, 0).gps_coord == track.getSegmentPoint(i, segment_size-1).gps_coord) { path->closeAllParts(); } importPathEnd(path); result.push_back(path); } for (int i = 0; i < (int)result.size(); ++i) // keep as separate loop to get the correct (final) indices undo_step->addObject(part->findObjectIndex(result[i])); map->setObjectsDirty(); map->push(undo_step); map->emitSelectionChanged(); map->emitSelectionEdited(); // TODO: is this necessary here? if (skipped_paths) { QMessageBox::information( dialog_parent, tr("Import problems"), tr("%n path object(s) could not be imported (reason: missing coordinates).", "", skipped_paths) ); } return true; }
bool DrawPathTool::mouseReleaseEvent(QMouseEvent* event, MapCoordF map_coord, MapWidget* widget) { if (!isDrawingButton(event->button())) return false; left_mouse_down = false; if (picking_angle) { picking_angle = false; picked_angle = pickAngle(map_coord, widget); return true; } else if (!editingInProgress()) { return false; } if (following) { finishFollowing(); if ((event->button() == Qt::RightButton) && drawOnRightClickEnabled()) finishDrawing(); return true; } if (!create_segment) return true; if (previous_point_is_curve_point && !dragging && !create_spline_corner) { // The new point has not been added yet MapCoord coord; if (shift_pressed) { coord = snap_helper->snapToObject(map_coord, widget); } else if (angle_helper->isActive()) { QPointF constrained_pos; angle_helper->getConstrainedCursorPositions(map_coord, constrained_pos_map, constrained_pos, widget); coord = MapCoord(constrained_pos_map); } else { coord = MapCoord(map_coord); } if (draw_dash_points) coord.setDashPoint(true); preview_path->addCoordinate(coord); updatePreviewPath(); updateDirtyRect(); } previous_point_is_curve_point = dragging; if (previous_point_is_curve_point) { QPointF constrained_pos; angle_helper->getConstrainedCursorPositions(map_coord, constrained_pos_map, constrained_pos, widget); previous_pos_map = click_pos_map; previous_drag_map = constrained_pos_map; previous_point_direction = calculateRotation(constrained_pos.toPoint(), constrained_pos_map); } updateAngleHelper(); create_spline_corner = false; path_has_preview_point = false; dragging = false; if ((event->button() == Qt::RightButton) && drawOnRightClickEnabled()) finishDrawing(); return true; }
bool DrawPathTool::mouseMoveEvent(QMouseEvent* event, MapCoordF map_coord, MapWidget* widget) { cur_pos = event->pos(); cur_pos_map = map_coord; if (!containsDrawingButtons(event->buttons())) { updateHover(); } else if (!editingInProgress()) { left_mouse_down = true; if (picking_angle) pickAngle(map_coord, widget); else return false; } else if (following) { updateFollowing(); } else { bool drag_distance_reached = (event->pos() - click_pos).manhattanLength() >= Settings::getInstance().getStartDragDistancePx(); if (dragging && !drag_distance_reached) { if (create_spline_corner) { create_spline_corner = false; } else if (path_has_preview_point) { undoLastPoint(); } } else if (drag_distance_reached) { // Giving a direction by dragging dragging = true; create_spline_corner = false; create_segment = true; if (previous_point_is_curve_point) angle_helper->setCenter(click_pos_map); QPointF constrained_pos; angle_helper->getConstrainedCursorPositions(map_coord, constrained_pos_map, constrained_pos, widget); if (previous_point_is_curve_point) { hidePreviewPoints(); float drag_direction = calculateRotation(constrained_pos.toPoint(), constrained_pos_map); // Add a new node or convert the last node into a corner? if ((widget->mapToViewport(previous_pos_map) - click_pos).manhattanLength() >= Settings::getInstance().getStartDragDistancePx()) createPreviewCurve(MapCoord(click_pos_map), drag_direction); else { create_spline_corner = true; // This hides the old direction indicator previous_drag_map = previous_pos_map; } } updateDirtyRect(); } } return true; }
MapCoord MapView::viewToMap(double x, double y) const { return MapCoord(view_to_map.m11() * x + view_to_map.m12() * y + view_to_map.m13(), view_to_map.m21() * x + view_to_map.m22() * y + view_to_map.m23()); }
void DrawRectangleTool::updateRectangle() { double angle = angle_helper->getConstrainedCursorPosMap(constrained_pos_map, constrained_pos_map); if (angles.size() == 1) { // Update vectors and angles forward_vector = constrained_pos_map - MapCoordF(preview_path->getCoordinate(0)); forward_vector.normalize(); angles.back() = -forward_vector.angle(); // Update rectangle MapCoord coord = MapCoord(constrained_pos_map); coord.setDashPoint(draw_dash_points); preview_path->setCoordinate(1, coord); } else { // Update vectors and angles forward_vector = MapCoordF::fromPolar(1.0, -angle); angles.back() = angle; // Update rectangle auto cur_point_index = angles.size(); deleteClosePoint(); float forward_dist = MapCoordF::dotProduct(forward_vector, constrained_pos_map - MapCoordF(preview_path->getCoordinate(cur_point_index - 1))); MapCoord coord = preview_path->getCoordinate(cur_point_index - 1) + MapCoord(forward_dist * forward_vector); snapped_to_line = false; float best_snap_distance_sq = std::numeric_limits<float>::max(); if (ctrl_pressed && angles.size() > 2) { // Try to snap to existing lines MapCoord original_coord = coord; for (int i = 0; i < (int)angles.size() - 1; ++i) { MapCoordF direction(100, 0); direction.rotate(-angles[i]); int num_steps; double angle_step, angle_offset = 0; if (i == 0 || qAbs(qAbs(fmod_pos(angles[i], M_PI) - fmod_pos(angles[i-1], M_PI)) - (M_PI / 2)) < 0.1) { num_steps = 2; angle_step = M_PI/2; } else { num_steps = 4; angle_step = M_PI/4; } for (int k = 0; k < num_steps; ++k) { if (qAbs(fmod_pos(angle, M_PI) - fmod_pos(angles[i] - (angle_offset + k * angle_step), M_PI)) < 0.1) continue; MapCoordF rotated_direction = direction; rotated_direction.rotate(angle_offset + k * angle_step); QLineF a(QPointF(preview_path->getCoordinate(cur_point_index - 1)), MapCoordF(preview_path->getCoordinate(cur_point_index - 1)) + forward_vector); QLineF b(QPointF(preview_path->getCoordinate(i)), MapCoordF(preview_path->getCoordinate(i)) + rotated_direction); QPointF intersection_point; QLineF::IntersectType intersection_type = a.intersect(b, &intersection_point); if (intersection_type == QLineF::NoIntersection) continue; float snap_distance_sq = original_coord.distanceSquaredTo(MapCoord(intersection_point)); if (snap_distance_sq > best_snap_distance_sq) continue; best_snap_distance_sq = snap_distance_sq; coord = MapCoord(intersection_point); snapped_to_line_a = coord; snapped_to_line_b = coord + MapCoord(rotated_direction); snapped_to_line = true; } } } coord.setDashPoint(draw_dash_points); preview_path->setCoordinate(cur_point_index, coord); auto close_vector = calculateClosingVector(); float close_dist = MapCoordF::dotProduct(close_vector, MapCoordF(preview_path->getCoordinate(0) - preview_path->getCoordinate(cur_point_index))); coord = preview_path->getCoordinate(cur_point_index) + MapCoord(close_dist * close_vector); coord.setDashPoint(draw_dash_points); preview_path->setCoordinate(cur_point_index + 1, coord); preview_path->parts().front().setClosed(true, true); } updatePreviewPath(); updateDirtyRect(); }
void NativeFileImport::import(bool load_symbols_only) { addWarning(Importer::tr("This file uses an obsolete format. " "Support for this format is to be removed from this program soon. " "To be able to open the file in the future, save it again.")); MapCoord::boundsOffset().reset(true); char buffer[4]; stream->read(buffer, 4); // read the magic int version; stream->read((char*)&version, sizeof(int)); if (version < 0) { addWarning(Importer::tr("Invalid file format version.")); } else if (version < NativeFileFormat::least_supported_file_format_version) { throw FileFormatException(Importer::tr("Unsupported old file format version. Please use an older program version to load and update the file.")); } else if (version > NativeFileFormat::current_file_format_version) { throw FileFormatException(Importer::tr("Unsupported new file format version. Some map features will not be loaded or saved by this version of the program. Consider updating.")); } if (version <= 16) { Georeferencing georef; stream->read((char*)&georef.scale_denominator, sizeof(int)); if (version >= 15) loadString(stream, map->map_notes); bool gps_projection_params_set; // obsolete stream->read((char*)&gps_projection_params_set, sizeof(bool)); GPSProjectionParameters gps_projection_parameters; // obsolete stream->read((char*)&gps_projection_parameters, sizeof(GPSProjectionParameters)); if (gps_projection_params_set) { LatLon ref_point = LatLon::fromRadiant(gps_projection_parameters.center_latitude, gps_projection_parameters.center_longitude); georef.setGeographicRefPoint(ref_point); } *map->georeferencing = georef; } else if (version >= 17) { loadString(stream, map->map_notes); Georeferencing georef; stream->read((char*)&georef.scale_denominator, sizeof(int)); double value; if (version >= 18) { stream->read((char*)&value, sizeof(double)); georef.declination = Georeferencing::roundDeclination(value); } stream->read((char*)&value, sizeof(double)); georef.grivation = Georeferencing::roundDeclination(value); georef.grivation_error = value - georef.grivation; double x,y; stream->read((char*)&x, sizeof(double)); stream->read((char*)&y, sizeof(double)); georef.map_ref_point = MapCoord(x,y); stream->read((char*)&x, sizeof(double)); stream->read((char*)&y, sizeof(double)); georef.projected_ref_point = QPointF(x,y); loadString(stream, georef.projected_crs_id); loadString(stream, georef.projected_crs_spec); stream->read((char*)&y, sizeof(double)); stream->read((char*)&x, sizeof(double)); georef.geographic_ref_point = LatLon::fromRadiant(y, x); QString geographic_crs_id, geographic_crs_spec; loadString(stream, geographic_crs_id); // reserved for geographic crs id loadString(stream, geographic_crs_spec); // reserved for full geographic crs specification if (geographic_crs_spec != Georeferencing::geographic_crs_spec) { addWarning( Importer::tr("The geographic coordinate reference system of the map was \"%1\". This CRS is not supported. Using \"%2\"."). arg(geographic_crs_spec). arg(Georeferencing::geographic_crs_spec) ); } if (version <= 17) georef.initDeclination(); // Correctly set georeferencing state georef.setProjectedCRS(georef.projected_crs_id, georef.projected_crs_spec); *map->georeferencing = georef; } if (version >= 24) map->setGrid(MapGrid().load(stream, version)); map->renderable_options = Symbol::RenderNormal; if (version >= 25) { bool area_hatching_enabled, baseline_view_enabled; stream->read((char*)&area_hatching_enabled, sizeof(bool)); stream->read((char*)&baseline_view_enabled, sizeof(bool)); if (area_hatching_enabled) map->renderable_options |= Symbol::RenderAreasHatched; if (baseline_view_enabled) map->renderable_options |= Symbol::RenderBaselines; } if (version >= 6) { bool print_params_set; stream->read((char*)&print_params_set, sizeof(bool)); if (print_params_set) { MapPrinterConfig printer_config(*map); stream->read((char*)&printer_config.page_format.orientation, sizeof(int)); stream->read((char*)&printer_config.page_format.paper_size, sizeof(int)); float resolution; stream->read((char*)&resolution, sizeof(float)); printer_config.options.resolution = qRound(resolution); stream->read((char*)&printer_config.options.show_templates, sizeof(bool)); if (version >= 24) stream->read((char*)&printer_config.options.show_grid, sizeof(bool)); else printer_config.options.show_grid = false; stream->read((char*)&printer_config.center_print_area, sizeof(bool)); float print_area_left, print_area_top, print_area_width, print_area_height; stream->read((char*)&print_area_left, sizeof(float)); stream->read((char*)&print_area_top, sizeof(float)); stream->read((char*)&print_area_width, sizeof(float)); stream->read((char*)&print_area_height, sizeof(float)); printer_config.print_area = QRectF(print_area_left, print_area_top, print_area_width, print_area_height); if (version >= 26) { bool print_different_scale_enabled; stream->read((char*)&print_different_scale_enabled, sizeof(bool)); stream->read((char*)&printer_config.options.scale, sizeof(int)); if (!print_different_scale_enabled) printer_config.options.scale = map->getScaleDenominator(); } map->setPrinterConfig(printer_config); } } if (version >= 16) { stream->read((char*)&map->image_template_use_meters_per_pixel, sizeof(bool)); stream->read((char*)&map->image_template_meters_per_pixel, sizeof(double)); stream->read((char*)&map->image_template_dpi, sizeof(double)); stream->read((char*)&map->image_template_scale, sizeof(double)); } // Load colors int num_colors; stream->read((char*)&num_colors, sizeof(int)); map->color_set->colors.resize(num_colors); for (int i = 0; i < num_colors; ++i) { int priority; stream->read((char*)&priority, sizeof(int)); MapColor* color = new MapColor(priority); MapColorCmyk cmyk; stream->read((char*)&cmyk.c, sizeof(float)); stream->read((char*)&cmyk.m, sizeof(float)); stream->read((char*)&cmyk.y, sizeof(float)); stream->read((char*)&cmyk.k, sizeof(float)); color->setCmyk(cmyk); float opacity; stream->read((char*)&opacity, sizeof(float)); color->setOpacity(opacity); QString name; loadString(stream, name); color->setName(name); map->color_set->colors[i] = color; } // Load symbols int num_symbols; stream->read((char*)&num_symbols, sizeof(int)); map->symbols.resize(num_symbols); for (int i = 0; i < num_symbols; ++i) { QScopedValueRollback<MapCoord::BoundsOffset> offset { MapCoord::boundsOffset() }; MapCoord::boundsOffset().reset(false); int symbol_type; stream->read((char*)&symbol_type, sizeof(int)); Symbol* symbol = Symbol::getSymbolForType(static_cast<Symbol::Type>(symbol_type)); if (!symbol) { throw FileFormatException(Importer::tr("Error while loading a symbol with type %2.").arg(symbol_type)); } if (!symbol->load(stream, version, map)) { throw FileFormatException(Importer::tr("Error while loading a symbol.")); } map->symbols[i] = symbol; } if (!load_symbols_only) { // Load templates stream->read((char*)&map->first_front_template, sizeof(int)); int num_templates; stream->read((char*)&num_templates, sizeof(int)); map->templates.resize(num_templates); for (int i = 0; i < num_templates; ++i) { QString path; loadString(stream, path); auto temp = Template::templateForFile(path, map); if (!temp) temp.reset(new TemplateImage(path, map)); // fallback if (version >= 27) { loadString(stream, path); temp->setTemplateRelativePath(path); } temp->loadTemplateConfiguration(stream, version); map->templates[i] = temp.release(); } if (version >= 28) { int num_closed_templates; stream->read((char*)&num_closed_templates, sizeof(int)); map->closed_templates.resize(num_closed_templates); for (int i = 0; i < num_closed_templates; ++i) { QString path; loadString(stream, path); auto temp = Template::templateForFile(path, map); if (!temp) temp.reset(new TemplateImage(path, map)); // fallback loadString(stream, path); temp->setTemplateRelativePath(path); temp->loadTemplateConfiguration(stream, version); map->closed_templates[i] = temp.release(); } } // Restore widgets and views if (view) { view->load(stream, version); } else { MapView tmp{ map }; tmp.load(stream, version); } // Load undo steps if (version >= 7) { if (!map->undoManager().load(stream, version)) { throw FileFormatException(Importer::tr("Error while loading undo steps.")); } } // Load parts stream->read((char*)&map->current_part_index, sizeof(int)); int num_parts; if (stream->read((char*)&num_parts, sizeof(int)) < (int)sizeof(int)) { throw FileFormatException(Importer::tr("Error while reading map part count.")); } delete map->parts[0]; map->parts.resize(num_parts); for (int i = 0; i < num_parts; ++i) { MapPart* part = new MapPart({}, map); if (!part->load(stream, version, map)) { throw FileFormatException(Importer::tr("Error while loading map part %2.").arg(i+1)); } map->parts[i] = part; } } emit map->currentMapPartIndexChanged(map->current_part_index); emit map->currentMapPartChanged(map->getPart(map->current_part_index)); }
void BooleanTool::rebuildSegment( ClipperLib::Path::size_type start_index, ClipperLib::Path::size_type end_index, bool sequence_increasing, const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { auto num_points = polygon.size(); object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(true); if ((start_index + 1) % num_points == end_index) { // This could happen for a straight line or a very flat curve - take coords directly from original rebuildTwoIndexSegment(start_index, end_index, sequence_increasing, polygon, polymap, object); return; } // Get polygon point coordinates const auto& start_point = polygon.at(start_index); const auto& second_point = polygon.at((start_index + 1) % num_points); const auto& second_last_point = polygon.at((end_index - 1) % num_points); const auto& end_point = polygon.at(end_index); // Try to find the middle coordinates in the same part bool found = false; PathCoordInfo second_info{ nullptr, nullptr }; PathCoordInfo second_last_info{ nullptr, nullptr }; for (auto second_it = polymap.find(second_point); second_it != polymap.end(); ++second_it) { for (auto second_last_it = polymap.find(second_last_point); second_last_it != polymap.end() && second_last_it.key() == second_last_point; ++second_last_it) { if (second_it->first == second_last_it->first && second_it->second->index == second_last_it->second->index) { // Same part found = true; second_info = *second_it; second_last_info = *second_last_it; break; } } if (found) break; } if (!found) { // Need unambiguous path part information to find the original object with high probability qDebug() << "BooleanTool::rebuildSegment: cannot identify original object!"; rebuildSegmentFromPathOnly(start_point, second_point, second_last_point, end_point, object); return; } const PathPart* original_path = second_info.first; // Try to find the outer coordinates in the same part PathCoordInfo start_info{ nullptr, nullptr }; for (auto start_it = polymap.find(start_point); start_it != polymap.end() && start_it.key() == start_point; ++start_it) { if (start_it->first == original_path) { start_info = *start_it; break; } } Q_ASSERT(!start_info.first || start_info.first == second_info.first); PathCoordInfo end_info{ nullptr, nullptr }; for (auto end_it = polymap.find(end_point); end_it != polymap.end() && end_it.key() == end_point; ++end_it) { if (end_it->first == original_path) { end_info = *end_it; break; } } Q_ASSERT(!end_info.first || end_info.first == second_info.first); const PathObject* original = original_path->path; auto edge_start = second_info.second->index; if (edge_start == second_info.first->last_index) edge_start = second_info.first->first_index; // Find out start tangent auto start_param = 0.0; MapCoord start_coord = MapCoord(0.001 * start_point.X, 0.001 * start_point.Y); MapCoord start_tangent; MapCoord end_tangent; MapCoord end_coord; double start_error_sq, end_error_sq; // Maximum difference in mm from reconstructed start and end coords to the // intersection points returned by Clipper const double error_bound = 0.4; if (sequence_increasing) { if ( second_info.second->param == 0.0 || ( start_info.first && start_info.second->param == 0.0 && ( start_info.second->index == edge_start || (start_info.second->index == start_info.first->last_index && start_info.first->first_index == edge_start) ) ) ) { // Take coordinates directly start_tangent = original->getCoordinate(edge_start + 1); end_tangent = original->getCoordinate(edge_start + 2); start_error_sq = start_coord.distanceSquaredTo(original->getCoordinate(edge_start + 0)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in increasing direct case: " << sqrt(start_error_sq); } else { // Approximate coords const PathCoord* prev_coord = second_info.second - 1; auto dx = second_point.X - start_point.X; auto dy = second_point.Y - start_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_start_param = (second_info.second->param - prev_coord->param) * point_dist / qMax(1e-7f, (second_info.second->clen - prev_coord->clen)); start_param = qBound(0.0, second_info.second->param - delta_start_param, 1.0); MapCoordF unused, o2, o3, o4; PathCoord::splitBezierCurve(MapCoordF(original->getCoordinate(edge_start + 0)), MapCoordF(original->getCoordinate(edge_start + 1)), MapCoordF(original->getCoordinate(edge_start + 2)), MapCoordF(original->getCoordinate(edge_start + 3)), start_param, unused, unused, o2, o3, o4); start_tangent = MapCoord(o3); end_tangent = MapCoord(o4); start_error_sq = start_coord.distanceSquaredTo(MapCoord(o2)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in increasing general case: " << sqrt(start_error_sq); } // Find better end point approximation and its tangent if ( second_last_info.second->param == 0.0 || (end_info.first && end_info.second->param == 0.0 && ( end_info.second->index == edge_start+3 || (end_info.second->index == end_info.first->first_index && end_info.first->last_index == edge_start+3) ) ) ) { // Take coordinates directly end_coord = original->getCoordinate(edge_start + 3); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in increasing direct case: " << sqrt(end_error_sq); } else { // Approximate coords const PathCoord* next_coord = second_last_info.second + 1; auto next_coord_param = next_coord->param; if (next_coord_param == 0.0) next_coord_param = 1.0; auto dx = end_point.X - second_last_point.X; auto dy = end_point.Y - second_last_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_end_param = (next_coord_param - second_last_info.second->param) * point_dist / qMax(1e-7f, (next_coord->clen - second_last_info.second->clen)); auto end_param = (second_last_info.second->param + delta_end_param - start_param) / (1.0 - start_param); MapCoordF o0, o1, o2, unused; PathCoord::splitBezierCurve(MapCoordF(start_coord), MapCoordF(start_tangent), MapCoordF(end_tangent), MapCoordF(original->getCoordinate(edge_start + 3)), end_param, o0, o1, o2, unused, unused); start_tangent = MapCoord(o0); end_tangent = MapCoord(o1); end_coord = MapCoord(o2); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in increasing general case: " << sqrt(end_error_sq); } } else // if (!sequence_increasing) { if ( second_info.second->param == 0.0 || ( start_info.first && start_info.second->param == 0.0 && ( start_info.second->index == edge_start+3 || (start_info.second->index == start_info.first->first_index && start_info.first->last_index == edge_start+3) ) ) ) { // Take coordinates directly start_tangent = original->getCoordinate(edge_start + 2); end_tangent = original->getCoordinate(edge_start + 1); start_error_sq = start_coord.distanceSquaredTo(original->getCoordinate(edge_start + 3)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in decreasing direct case: " << sqrt(start_error_sq); } else { // Approximate coords const PathCoord* next_coord = second_info.second + 1; auto next_coord_param = next_coord->param; if (next_coord_param == 0.0) next_coord_param = 1.0; auto dx = second_point.X - start_point.X; auto dy = second_point.Y - start_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_start_param = (next_coord_param - second_info.second->param) * point_dist / qMax(1e-7f, (next_coord->clen - second_info.second->clen)); start_param = qBound(0.0, 1.0 - second_info.second->param + delta_start_param, 1.0); MapCoordF unused, o2, o3, o4; PathCoord::splitBezierCurve(MapCoordF(original->getCoordinate(edge_start + 3)), MapCoordF(original->getCoordinate(edge_start + 2)), MapCoordF(original->getCoordinate(edge_start + 1)), MapCoordF(original->getCoordinate(edge_start + 0)), start_param, unused, unused, o2, o3, o4); start_tangent = MapCoord(o3); end_tangent = MapCoord(o4); start_error_sq = start_coord.distanceSquaredTo(MapCoord(o2)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in decreasing general case: " << sqrt(start_error_sq); } // Find better end point approximation and its tangent if ( second_last_info.second->param == 0.0 || ( end_info.first && end_info.second->param == 0.0 && ( end_info.second->index == edge_start || (end_info.second->index == end_info.first->last_index && end_info.first->first_index == edge_start) ) ) ) { // Take coordinates directly end_coord = original->getCoordinate(edge_start + 0); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in decreasing direct case: " << sqrt(end_error_sq); } else { // Approximate coords const PathCoord* prev_coord = second_last_info.second - 1; auto dx = end_point.X - second_last_point.X; auto dy = end_point.Y - second_last_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_end_param = (second_last_info.second->param - prev_coord->param) * point_dist / qMax(1e-7f, (second_last_info.second->clen - prev_coord->clen)); auto end_param = (1.0 - second_last_info.second->param + delta_end_param) / (1 - start_param); MapCoordF o0, o1, o2, unused; PathCoord::splitBezierCurve(MapCoordF(start_coord), MapCoordF(start_tangent), MapCoordF(end_tangent), MapCoordF(original->getCoordinate(edge_start + 0)), end_param, o0, o1, o2, unused, unused); start_tangent = MapCoord(o0); end_tangent = MapCoord(o1); end_coord = MapCoord(o2); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in decreasing general case: " << sqrt(end_error_sq); } } if (start_error_sq <= error_bound && end_error_sq <= error_bound) { // Rebuild bezier curve using information from original curve object->addCoordinate(start_tangent); object->addCoordinate(end_tangent); object->addCoordinate(resetCoordinate(end_coord)); } else { // Rebuild bezier curve approximately using tangents derived from result polygon rebuildSegmentFromPathOnly(start_point, second_point, second_last_point, end_point, object); } }
void BooleanTool::polygonToPathPart(const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { auto num_points = polygon.size(); if (num_points < 3) return; // Index of first used point in polygon auto part_start_index = 0u; auto cur_info = PathCoordInfo{ nullptr, nullptr }; // Check if we can find either an unknown intersection point // or a path coord with parameter 0. // This gives a starting point to search for curves to rebuild // (because we cannot start in the middle of a curve) for (; part_start_index < num_points; ++part_start_index) { auto current_point = polygon.at(part_start_index); if (!polymap.contains(current_point)) break; if (polymap.value(current_point).second->param == 0.0) { cur_info = polymap.value(current_point); break; } } if (part_start_index == num_points) { // Did not find a valid starting point. Return the part as a polygon. for (auto i = 0u; i < num_points; ++i) object->addCoordinate(MapCoord(0.001 * polygon.at(i).X, 0.001 * polygon.at(i).Y), (i == 0)); object->parts().back().setClosed(true, true); return; } // Add the first point to the object rebuildCoordinate(part_start_index, polygon, polymap, object, true); // Index of first segment point in polygon auto segment_start_index = part_start_index; bool have_sequence = false; bool sequence_increasing = false; bool stop_before = false; // Advance along the boundary and rebuild the curve for every sequence // of path coord pointers with the same path and index. auto i = part_start_index; do { ++i; if (i >= num_points) i = 0; PathCoordInfo new_info{ nullptr, nullptr }; auto new_point = polygon.at(i); if (polymap.contains(new_point)) new_info = polymap.value(new_point); if (cur_info.first && cur_info.first == new_info.first) { // Same original part auto cur_coord_index = cur_info.second->index; MapCoord& cur_coord = cur_info.first->path->getCoordinate(cur_coord_index); auto new_coord_index = new_info.second->index; MapCoord& new_coord = new_info.first->path->getCoordinate(new_coord_index); auto cur_coord_index_adjusted = cur_coord_index; if (cur_coord_index_adjusted == new_info.first->first_index) cur_coord_index_adjusted = new_info.first->last_index; auto new_coord_index_adjusted = new_coord_index; if (new_coord_index_adjusted == new_info.first->first_index) new_coord_index_adjusted = new_info.first->last_index; if (cur_coord_index == new_coord_index) { // Somewhere on a curve bool param_increasing = new_info.second->param > cur_info.second->param; if (!have_sequence) { have_sequence = true; sequence_increasing = param_increasing; } else if (have_sequence && sequence_increasing != param_increasing) { stop_before = true; } } else if (new_info.second->param == 0.0 && ( (cur_coord.isCurveStart() && new_coord_index_adjusted == cur_coord_index + 3) || (!cur_coord.isCurveStart() && new_coord_index_adjusted == cur_coord_index + 1) ) ) { // Original curve is from cur_coord_index to new_coord_index_adjusted. if (!have_sequence) { have_sequence = true; sequence_increasing = true; } else { stop_before = !sequence_increasing; } } else if (cur_info.second->param == 0.0 && ( (new_coord.isCurveStart() && new_coord_index + 3 == cur_coord_index_adjusted) || (!new_coord.isCurveStart() && new_coord_index + 1 == cur_coord_index_adjusted) ) ) { // Original curve is from new_coord_index to cur_coord_index_adjusted. if (!have_sequence) { have_sequence = true; sequence_increasing = false; } else { stop_before = sequence_increasing; } } else if ((segment_start_index + 1) % num_points != i) { // Not immediately after segment_start_index stop_before = true; } } if (i == part_start_index || stop_before || (new_info.second && new_info.second->param == 0.0) || (cur_info.first && (cur_info.first != new_info.first || cur_info.second->index != new_info.second->index) && i != (segment_start_index + 1) % num_points) || !new_info.first) { if (stop_before) { if (i == 0) i = num_points - 1; else --i; } if (have_sequence) // A sequence of at least two points belonging to the same curve rebuildSegment(segment_start_index, i, sequence_increasing, polygon, polymap, object); else // A single straight edge rebuildCoordinate(i, polygon, polymap, object); if (stop_before) { ++i; if (i >= num_points) i = 0; rebuildCoordinate(i, polygon, polymap, object); stop_before = false; } segment_start_index = i; have_sequence = false; } cur_info = new_info; } while (i != part_start_index); object->parts().back().connectEnds(); }
MapCoord SnappingToolHelper::snapToObject(MapCoordF position, MapWidget* widget, SnappingToolHelperSnapInfo* info, Object* exclude_object, float snap_distance) { if (snap_distance < 0) snap_distance = 0.001f * widget->getMapView()->pixelToLength(Settings::getInstance().getMapEditorSnapDistancePx()); float closest_distance_sq = snap_distance * snap_distance; auto result_position = MapCoord { position }; SnappingToolHelperSnapInfo result_info; result_info.type = NoSnapping; result_info.object = NULL; result_info.coord_index = -1; result_info.path_coord.pos = MapCoordF(0, 0); result_info.path_coord.index = -1; result_info.path_coord.clen = -1; result_info.path_coord.param = -1; if (filter & (ObjectCorners | ObjectPaths)) { // Find map objects at the given position SelectionInfoVector objects; map->findAllObjectsAt(position, snap_distance, true, false, false, true, objects); // Find closest snap spot from map objects for (SelectionInfoVector::const_iterator it = objects.begin(), end = objects.end(); it != end; ++it) { Object* object = it->second; if (object == exclude_object) continue; float distance_sq; if (object->getType() == Object::Point && filter & ObjectCorners) { PointObject* point = object->asPoint(); distance_sq = point->getCoordF().distanceSquaredTo(position); if (distance_sq < closest_distance_sq) { closest_distance_sq = distance_sq; result_position = point->getCoord(); result_info.type = ObjectCorners; result_info.object = object; result_info.coord_index = 0; } } else if (object->getType() == Object::Path) { PathObject* path = object->asPath(); if (filter & ObjectPaths) { PathCoord path_coord; path->calcClosestPointOnPath(position, distance_sq, path_coord); if (distance_sq < closest_distance_sq) { closest_distance_sq = distance_sq; result_position = MapCoord(path_coord.pos); result_info.object = object; if (path_coord.param == 0.0) { result_info.type = ObjectCorners; result_info.coord_index = path_coord.index; } else { result_info.type = ObjectPaths; result_info.coord_index = -1; result_info.path_coord = path_coord; } } } else { MapCoordVector::size_type index; path->calcClosestCoordinate(position, distance_sq, index); if (distance_sq < closest_distance_sq) { closest_distance_sq = distance_sq; result_position = path->getCoordinate(index); result_info.type = ObjectCorners; result_info.object = object; result_info.coord_index = index; } } } else if (object->getType() == Object::Text) { // No snapping to texts continue; } } } // Find closest grid snap position if ((filter & GridCorners) && widget->getMapView()->isGridVisible() && map->getGrid().isSnappingEnabled() && map->getGrid().getDisplayMode() == MapGrid::AllLines) { MapCoordF closest_grid_point = map->getGrid().getClosestPointOnGrid(position, map); float distance_sq = closest_grid_point.distanceSquaredTo(position); if (distance_sq < closest_distance_sq) { closest_distance_sq = distance_sq; result_position = MapCoord(closest_grid_point); result_info.type = GridCorners; result_info.object = NULL; result_info.coord_index = -1; } } // Return if (snap_mark != result_position || snapped_type != result_info.type) { snap_mark = result_position; snapped_type = result_info.type; emit displayChanged(); } if (info != NULL) *info = result_info; return result_position; }
bool DrawRectangleTool::mousePressEvent(QMouseEvent* event, MapCoordF map_coord, MapWidget* widget) { // Adjust flags to have possibly more recent state int modifiers = (event->modifiers() | (key_button_bar ? key_button_bar->activeModifiers() : 0)); ctrl_pressed = modifiers & Qt::ControlModifier; shift_pressed = modifiers & Qt::ShiftModifier; cur_map_widget = widget; if (isDrawingButton(event->button())) { dragging = false; click_pos = event->pos(); click_pos_map = map_coord; cur_pos = event->pos(); cur_pos_map = click_pos_map; if (shift_pressed) cur_pos_map = MapCoordF(snap_helper->snapToObject(cur_pos_map, widget)); constrained_pos_map = cur_pos_map; if (!editingInProgress()) { if (ctrl_pressed) { // Pick direction pickDirection(cur_pos_map, widget); } else { // Start drawing if (angle_helper->isActive()) angle_helper->setCenter(click_pos_map); startDrawing(); MapCoord coord = MapCoord(cur_pos_map); coord.setDashPoint(draw_dash_points); preview_path->addCoordinate(coord); preview_path->addCoordinate(coord); angles.push_back(0); updateStatusText(); } } else { if (angles.size() >= 2 && drawingParallelTo(angles[angles.size() - 2])) { // Drawing parallel to last section, just move the last point undoLastPoint(); } // Add new point int cur_point_index = angles.size(); if (!preview_path->getCoordinate(cur_point_index).isPositionEqualTo(preview_path->getCoordinate(cur_point_index - 1))) { MapCoord coord = MapCoord(cur_pos_map); coord.setDashPoint(draw_dash_points); preview_path->addCoordinate(coord); if (angles.size() == 1) { // Bring to correct number of points: line becomes a rectangle preview_path->addCoordinate(coord); } angles.push_back(0); angle_helper->setActive(true, MapCoordF(preview_path->getCoordinate(cur_point_index))); angle_helper->clearAngles(); angle_helper->addAngles(angles[0], M_PI/4); if (event->button() != Qt::RightButton || !drawOnRightClickEnabled()) { updateHover(false); updateHover(false); // Call it again, really. } } } } else if (event->button() == Qt::RightButton && editingInProgress()) { constrained_pos_map = MapCoordF(preview_path->getCoordinate(angles.size() - 1)); undoLastPoint(); if (editingInProgress()) // despite undoLastPoint() finishDrawing(); no_more_effect_on_click = true; } else { return false; } return true; }
void DrawCircleTool::updateCircle() { MapCoordF first_pos_map; if (start_from_center) first_pos_map = circle_start_pos_map + (circle_start_pos_map - opposite_pos_map); else first_pos_map = circle_start_pos_map; float radius = 0.5f * first_pos_map.distanceTo(opposite_pos_map); float kappa = BEZIER_KAPPA; float m_kappa = 1 - BEZIER_KAPPA; MapCoordF across = opposite_pos_map - first_pos_map; across.setLength(radius); MapCoordF right = across.perpRight(); float right_radius = radius; if (second_point_set && dragging) { if (right.length() < 1e-8) right_radius = 0; else { MapCoordF to_cursor = cur_pos_map - first_pos_map; right_radius = MapCoordF::dotProduct(to_cursor, right) / right.length(); } } right.setLength(right_radius); preview_path->clearCoordinates(); preview_path->addCoordinate(MapCoord(first_pos_map, MapCoord::CurveStart)); preview_path->addCoordinate(MapCoord(first_pos_map + kappa * right)); preview_path->addCoordinate(MapCoord(first_pos_map + right + m_kappa * across)); preview_path->addCoordinate(MapCoord(first_pos_map + right + across, MapCoord::CurveStart)); preview_path->addCoordinate(MapCoord(first_pos_map + right + (1 + kappa) * across)); preview_path->addCoordinate(MapCoord(first_pos_map + kappa * right + 2 * across)); preview_path->addCoordinate(MapCoord(first_pos_map + 2 * across, MapCoord::CurveStart)); preview_path->addCoordinate(MapCoord(first_pos_map - kappa * right + 2 * across)); preview_path->addCoordinate(MapCoord(first_pos_map - right + (1 + kappa) * across)); preview_path->addCoordinate(MapCoord(first_pos_map - right + across, MapCoord::CurveStart)); preview_path->addCoordinate(MapCoord(first_pos_map - right + m_kappa * across)); preview_path->addCoordinate(MapCoord(first_pos_map - kappa * right)); preview_path->parts().front().setClosed(true, false); updatePreviewPath(); setDirtyRect(); }
void CutTool::pathFinished(PathObject* split_path) { Map* map = this->map(); // Get path endpoint and check if it is on the area boundary const MapCoordVector& path_coords = split_path->getRawCoordinateVector(); MapCoord path_end = path_coords.at(path_coords.size() - 1); PathObject* edited_path = reinterpret_cast<PathObject*>(edit_object); PathCoord end_path_coord; float distance_sq; edited_path->calcClosestPointOnPath(MapCoordF(path_end), distance_sq, end_path_coord); float click_tolerance_map = 0.001 * edit_widget->getMapView()->pixelToLength(clickTolerance()); if (distance_sq > click_tolerance_map*click_tolerance_map) { QMessageBox::warning(window(), tr("Error"), tr("The split line must end on the area boundary!")); pathAborted(); return; } else if (drag_part_index != edited_path->findPartIndexForIndex(end_path_coord.index)) { QMessageBox::warning(window(), tr("Error"), tr("Start and end of the split line are at different parts of the object!")); pathAborted(); return; } else if (drag_start_len == end_path_coord.clen) { QMessageBox::warning(window(), tr("Error"), tr("Start and end of the split line are at the same position!")); pathAborted(); return; } Q_ASSERT(split_path->parts().size() == 1); split_path->parts().front().setClosed(false); split_path->setCoordinate(split_path->getCoordinateCount() - 1, MapCoord(end_path_coord.pos)); // Do the splitting const double split_threshold = 0.01; MapPart* part = map->getCurrentPart(); AddObjectsUndoStep* add_step = new AddObjectsUndoStep(map); add_step->addObject(part->findObjectIndex(edited_path), edited_path); map->removeObjectFromSelection(edited_path, false); map->deleteObject(edited_path, true); map->setObjectsDirty(); DeleteObjectsUndoStep* delete_step = new DeleteObjectsUndoStep(map); PathObject* holes = nullptr; // if the edited path contains holes, they are saved in this temporary object if (edited_path->parts().size() > 1) { holes = edited_path->duplicate()->asPath(); holes->deletePart(0); } bool ok; Q_UNUSED(ok); // "ok" is only used in Q_ASSERT. PathObject* parts[2] = { new PathObject { edited_path->parts().front() }, nullptr }; const PathPart& drag_part = edited_path->parts()[drag_part_index]; if (drag_part.isClosed()) { parts[1] = new PathObject { *parts[0] }; parts[0]->changePathBounds(drag_part_index, drag_start_len, end_path_coord.clen); ok = parts[0]->connectIfClose(split_path, split_threshold); Q_ASSERT(ok); parts[1]->changePathBounds(drag_part_index, end_path_coord.clen, drag_start_len); ok = parts[1]->connectIfClose(split_path, split_threshold); Q_ASSERT(ok); } else { float min_cut_pos = qMin(drag_start_len, end_path_coord.clen); float max_cut_pos = qMax(drag_start_len, end_path_coord.clen); float path_len = drag_part.path_coords.back().clen; if (min_cut_pos <= 0 && max_cut_pos >= path_len) { ok = parts[0]->connectIfClose(split_path, split_threshold); Q_ASSERT(ok); parts[1] = new PathObject { *split_path }; parts[1]->setSymbol(edited_path->getSymbol(), false); } else if (min_cut_pos <= 0 || max_cut_pos >= path_len) { float cut_pos = (min_cut_pos <= 0) ? max_cut_pos : min_cut_pos; parts[1] = new PathObject { *parts[0] }; parts[0]->changePathBounds(drag_part_index, 0, cut_pos); ok = parts[0]->connectIfClose(split_path, split_threshold); Q_ASSERT(ok); parts[1]->changePathBounds(drag_part_index, cut_pos, path_len); ok = parts[1]->connectIfClose(split_path, split_threshold); Q_ASSERT(ok); } else { parts[1] = new PathObject { *parts[0] }; PathObject* temp_path = new PathObject { *parts[0] }; parts[0]->changePathBounds(drag_part_index, min_cut_pos, max_cut_pos); ok = parts[0]->connectIfClose(split_path, split_threshold); Q_ASSERT(ok); parts[1]->changePathBounds(drag_part_index, 0, min_cut_pos); ok = parts[1]->connectIfClose(split_path, split_threshold); Q_ASSERT(ok); temp_path->changePathBounds(drag_part_index, max_cut_pos, path_len); ok = parts[1]->connectIfClose(temp_path, split_threshold); Q_ASSERT(ok); delete temp_path; } } // If the object had holes, check into which parts they go if (holes) { for (const auto& hole : holes->parts()) { PathPartVector::size_type part_index = (parts[0]->isPointOnPath(MapCoordF(holes->getCoordinate(hole.first_index)), 0, false, false) != Symbol::NoSymbol) ? 0 : 1; parts[part_index]->getCoordinate(parts[part_index]->getCoordinateCount() - 1).setHolePoint(true); parts[part_index]->appendPathPart(hole); } } for (auto& object : parts) { map->addObject(object); delete_step->addObject(part->findObjectIndex(object)); map->addObjectToSelection(object, false); } CombinedUndoStep* undo_step = new CombinedUndoStep(map); undo_step->push(add_step); undo_step->push(delete_step); map->push(undo_step); map->setObjectsDirty(); pathAborted(); }
void PathObjectTest::calcIntersectionsTest_data() { QTest::addColumn<void*>("v_path1"); QTest::addColumn<void*>("v_path2"); QTest::addColumn<void*>("v_predicted_intersections"); PathObject::Intersection intersection; intersection.part_index = 0; intersection.other_part_index = 0; // Line-Line perpendicular intersection DummyPathObject* perpendicular1 = new DummyPathObject(); perpendicular1->addCoordinate(MapCoord(10, 20)); perpendicular1->addCoordinate(MapCoord(30, 20)); DummyPathObject* perpendicular2 = new DummyPathObject(); perpendicular2->addCoordinate(MapCoord(20, 10)); perpendicular2->addCoordinate(MapCoord(20, 30)); PathObject::Intersections* intersections_perpendicular = new PathObject::Intersections(); intersection.coord = MapCoordF(20, 20); intersection.length = 10; intersection.other_length = 10; intersections_perpendicular->push_back(intersection); QTest::newRow("Line-Line perpendicular") << (void*)perpendicular1 << (void*)perpendicular2 << (void*)intersections_perpendicular; // Line-Line perpendicular intersection, test reversed QTest::newRow("Line-Line perpendicular, test reversed") << (void*)perpendicular2 << (void*)perpendicular1 << (void*)intersections_perpendicular; // Line-Line perpendicular intersection, line1 reversed PathObject* perpendicular3 = perpendicular1->duplicate()->asPath(); perpendicular3->reverse(); QTest::newRow("Line-Line perpendicular, line1 reversed") << (void*)perpendicular2 << (void*)perpendicular3 << (void*)intersections_perpendicular; // Line-Line parallel intersection DummyPathObject* parallel1 = new DummyPathObject(); parallel1->addCoordinate(MapCoord(10, 0)); parallel1->addCoordinate(MapCoord(30, 0)); parallel1->addCoordinate(MapCoord(50, 0)); DummyPathObject* parallel2 = new DummyPathObject(); parallel2->addCoordinate(MapCoord(20, 0)); parallel2->addCoordinate(MapCoord(40, 0)); parallel2->addCoordinate(MapCoord(60, 0)); PathObject::Intersections* intersections_parallel = new PathObject::Intersections(); intersection.coord = MapCoordF(20, 0); intersection.length = 10; intersection.other_length = 0; intersections_parallel->push_back(intersection); intersection.coord = MapCoordF(50, 0); intersection.length = 40; intersection.other_length = 30; intersections_parallel->push_back(intersection); QTest::newRow("Line-Line parallel") << (void*)parallel1 << (void*)parallel2 << (void*)intersections_parallel; // Intersection at parallel start / end DummyPathObject* parallel3 = new DummyPathObject(); parallel3->addCoordinate(MapCoord(20, 10)); parallel3->addCoordinate(MapCoord(20, 0)); parallel3->addCoordinate(MapCoord(30, 0)); parallel3->addCoordinate(MapCoord(30, -10)); PathObject::Intersections* intersections_parallel_se = new PathObject::Intersections(); intersection.coord = MapCoordF(20, 0); intersection.length = 10; intersection.other_length = 10; intersections_parallel_se->push_back(intersection); intersection.coord = MapCoordF(30, 0); intersection.length = 20; intersection.other_length = 20; intersections_parallel_se->push_back(intersection); QTest::newRow("Line-Line parallel with intersection at start / end") << (void*)parallel1 << (void*)parallel3 << (void*)intersections_parallel_se; // Intersection at point DummyPathObject* point1 = new DummyPathObject(); point1->addCoordinate(MapCoord(10, 30)); point1->addCoordinate(MapCoord(30, 30)); point1->addCoordinate(MapCoord(50, 30)); DummyPathObject* point2 = new DummyPathObject(); point2->addCoordinate(MapCoord(30, 10)); point2->addCoordinate(MapCoord(30, 30)); point2->addCoordinate(MapCoord(30, 50)); PathObject::Intersections* intersections_point = new PathObject::Intersections(); intersection.coord = MapCoordF(30, 30); intersection.length = 20; intersection.other_length = 20; intersections_point->push_back(intersection); QTest::newRow("Intersection at point") << (void*)point1 << (void*)point2 << (void*)intersections_point; // Duplicate of object intersection DummyPathObject* duplicate1 = new DummyPathObject(); duplicate1->addCoordinate(MapCoord(10, 30)); duplicate1->addCoordinate(MapCoord(30, 30)); duplicate1->addCoordinate(MapCoord(50, 50)); duplicate1->addCoordinate(MapCoord(50, 70)); duplicate1->update(); // for duplicate1->parts().front().getLength(); PathObject::Intersections* intersections_duplicate = new PathObject::Intersections(); intersection.coord = MapCoordF(10, 30); intersection.length = 0; intersection.other_length = 0; intersections_duplicate->push_back(intersection); intersection.coord = MapCoordF(50, 70); intersection.length = duplicate1->parts().front().length(); intersection.other_length = duplicate1->parts().front().length(); intersections_duplicate->push_back(intersection); QTest::newRow("Start/end intersections for duplicate") << (void*)duplicate1 << (void*)duplicate1 << (void*)intersections_duplicate; // Reversed duplicate PathObject* duplicate2 = duplicate1->duplicate()->asPath(); duplicate2->reverse(); PathObject::Intersections* intersections_duplicate_reversed = new PathObject::Intersections(); intersection.coord = MapCoordF(10, 30); intersection.length = 0; intersection.other_length = duplicate1->parts().front().length(); intersections_duplicate_reversed->push_back(intersection); intersection.coord = MapCoordF(50, 70); intersection.length = duplicate1->parts().front().length(); intersection.other_length = 0; intersections_duplicate_reversed->push_back(intersection); QTest::newRow("Start/end intersections for reversed duplicate") << (void*)duplicate1 << (void*)duplicate2 << (void*)intersections_duplicate_reversed; // Duplicate of closed object intersection PathObject* duplicate1_closed = duplicate1->duplicate()->asPath(); duplicate1_closed->parts().front().setClosed(true); PathObject::Intersections* no_intersections = new PathObject::Intersections(); QTest::newRow("No intersections for closed duplicate") << (void*)duplicate1_closed << (void*)duplicate1_closed << (void*)no_intersections; // Parallel at start intersection DummyPathObject* ps1 = new DummyPathObject(); ps1->addCoordinate(MapCoord(10, 10)); ps1->addCoordinate(MapCoord(30, 10)); ps1->addCoordinate(MapCoord(30, 30)); ps1->addCoordinate(MapCoord(10, 30)); ps1->parts().front().setClosed(true); DummyPathObject* ps2 = new DummyPathObject(); ps2->addCoordinate(MapCoord(10, 10)); ps2->addCoordinate(MapCoord(30, 10)); PathObject::Intersections* intersections_ps = new PathObject::Intersections(); intersection.coord = MapCoordF(10, 10); intersection.length = 0; intersection.other_length = 0; intersections_ps->push_back(intersection); intersection.coord = MapCoordF(30, 10); intersection.length = 20; intersection.other_length = 20; intersections_ps->push_back(intersection); intersection.coord = MapCoordF(10, 10); intersection.length = 80; intersection.other_length = 0; intersections_ps->push_back(intersection); QTest::newRow("Parallel at start intersection") << (void*)ps1 << (void*)ps2 << (void*)intersections_ps; // Parallel at end intersection DummyPathObject* pe1 = new DummyPathObject(); pe1->addCoordinate(MapCoord(10, 10)); pe1->addCoordinate(MapCoord(30, 10)); pe1->addCoordinate(MapCoord(30, 30)); pe1->addCoordinate(MapCoord(10, 30)); pe1->parts().front().setClosed(true); DummyPathObject* pe2 = new DummyPathObject(); pe2->addCoordinate(MapCoord(10, 30)); pe2->addCoordinate(MapCoord(10, 10)); PathObject::Intersections* intersections_pe = new PathObject::Intersections(); intersection.coord = MapCoordF(10, 10); intersection.length = 0; intersection.other_length = 20; intersections_pe->push_back(intersection); intersection.coord = MapCoordF(10, 30); intersection.length = 60; intersection.other_length = 0; intersections_pe->push_back(intersection); intersection.coord = MapCoordF(10, 10); intersection.length = 80; intersection.other_length = 20; intersections_pe->push_back(intersection); QTest::newRow("Parallel at end intersection") << (void*)pe1 << (void*)pe2 << (void*)intersections_pe; // a inside b DummyPathObject* aib1 = new DummyPathObject(); aib1->addCoordinate(MapCoord(10, 0)); aib1->addCoordinate(MapCoord(30, 0)); DummyPathObject* aib2 = new DummyPathObject(); aib2->addCoordinate(MapCoord(0, 0)); aib2->addCoordinate(MapCoord(40, 0)); PathObject::Intersections* intersections_aib = new PathObject::Intersections(); intersection.coord = MapCoordF(10, 0); intersection.length = 0; intersection.other_length = 10; intersections_aib->push_back(intersection); intersection.coord = MapCoordF(30, 0); intersection.length = 20; intersection.other_length = 30; intersections_aib->push_back(intersection); QTest::newRow("a inside b") << (void*)aib1 << (void*)aib2 << (void*)intersections_aib; // b inside a PathObject::Intersections* intersections_bia = new PathObject::Intersections(); intersection.coord = MapCoordF(10, 0); intersection.length = 10; intersection.other_length = 0; intersections_bia->push_back(intersection); intersection.coord = MapCoordF(30, 0); intersection.length = 30; intersection.other_length = 20; intersections_bia->push_back(intersection); QTest::newRow("b inside a") << (void*)aib2 << (void*)aib1 << (void*)intersections_bia; }