UndoStep* finish() { MapPart* part = map->getCurrentPart(); map->clearObjectSelection(false); add_step->removeContainedObjects(false); for (size_t i = 0; i < new_objects.size(); ++i) { Object* object = new_objects[i]; map->addObject(object); } // Do not merge this loop into the upper one; // theoretically undo step indices could be wrong this way. for (size_t i = 0; i < new_objects.size(); ++i) { Object* object = new_objects[i]; delete_step->addObject(part->findObjectIndex(object)); } map->emitSelectionChanged(); // Return undo step if (delete_step->isEmpty()) { delete delete_step; if (add_step->isEmpty()) { delete add_step; return NULL; } else return add_step; } else { if (add_step->isEmpty()) { delete add_step; return delete_step; } else { CombinedUndoStep* combined_step = new CombinedUndoStep(map); combined_step->push(add_step); combined_step->push(delete_step); return combined_step; } } }
bool BooleanTool::executeForObjects(PathObject* subject, PathObjects& in_objects, PathObjects& out_objects, CombinedUndoStep& undo_step) { if (!executeForObjects(subject, in_objects, out_objects)) { Q_ASSERT(out_objects.size() == 0); return false; // in release build } // Add original objects to undo step, and remove them from map. QScopedPointer<AddObjectsUndoStep> add_step(new AddObjectsUndoStep(map)); for (PathObject* object : in_objects) { if (op != Difference || object == subject) { add_step->addObject(object, object); } } // Keep as separate loop to get the correct index in the previous loop for (PathObject* object : in_objects) { if (op != Difference || object == subject) { map->removeObjectFromSelection(object, false); map->getCurrentPart()->deleteObject(object, true); object->setMap(map); // necessary so objects are saved correctly } } // Add resulting objects to map, and create delete step for them QScopedPointer<DeleteObjectsUndoStep> delete_step(new DeleteObjectsUndoStep(map)); MapPart* part = map->getCurrentPart(); for (PathObject* object : out_objects) { map->addObject(object); map->addObjectToSelection(object, false); } // Keep as separate loop to get the correct index in the previous loop for (PathObject* object : out_objects) { delete_step->addObject(part->findObjectIndex(object)); } undo_step.push(add_step.take()); undo_step.push(delete_step.take()); return true; }
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; }
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(); }