2026-10-04 15:47:09 +02:00
|
|
|
//! Copying or moving elements to a place of the file tree: what paste and move are made of.
|
|
|
|
|
//! The elements come as the content of a clipboard, and a destination says where they go among
|
|
|
|
|
//! the elements already there.
|
2026-10-04 15:26:58 +02:00
|
|
|
|
|
|
|
|
use std::{collections::HashSet, rc::Rc};
|
|
|
|
|
|
|
|
|
|
use crate::{
|
2026-10-26 18:51:56 +01:00
|
|
|
ClipboardContent, ClipboardRoute, ClipboardSegment, ClipboardTrack, CommandError, File, FileId,
|
|
|
|
|
Route, RouteId, Selection, State, Track, TrackId, TrackSegment, TrackSegmentId, Waypoint,
|
|
|
|
|
WaypointId, copy_file, copy_route, copy_segment, copy_track, copy_waypoint,
|
2026-10-04 15:26:58 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/// Where elements are put, among the others of the same list.
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
pub enum Place<T> {
|
|
|
|
|
/// After all of them.
|
|
|
|
|
End,
|
|
|
|
|
/// Right after the one with this id, or at the end if it is not there (anymore).
|
|
|
|
|
After(T),
|
|
|
|
|
/// At this position (at the end if it is past it).
|
|
|
|
|
Index(usize),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<T: Copy + PartialEq> Place<T> {
|
|
|
|
|
/// The position in the list of `ids`.
|
|
|
|
|
pub fn position(&self, ids: &[T]) -> usize {
|
|
|
|
|
match self {
|
|
|
|
|
Place::End => ids.len(),
|
|
|
|
|
Place::After(id) => ids
|
|
|
|
|
.iter()
|
|
|
|
|
.position(|other| other == id)
|
|
|
|
|
.map_or(ids.len(), |i| i + 1),
|
|
|
|
|
Place::Index(index) => (*index).min(ids.len()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-04 15:47:09 +02:00
|
|
|
/// Where elements go. What can go where:
|
|
|
|
|
/// - files: among the files;
|
2026-10-26 18:51:56 +01:00
|
|
|
/// - routes: among the files (each route becomes a file, named like the route, or like the file
|
|
|
|
|
/// it comes from), or among the routes of a file. Among the tracks of a file, they stay routes,
|
|
|
|
|
/// which come before the tracks: they go after the other routes;
|
2026-10-04 15:47:09 +02:00
|
|
|
/// - tracks: among the files (each track becomes a file, named like the track, or like the file
|
2026-10-26 18:51:56 +01:00
|
|
|
/// it comes from), or among the tracks of a file. Among the routes of a file, they stay tracks,
|
|
|
|
|
/// which come after the routes: they go before the other tracks;
|
2026-10-04 15:47:09 +02:00
|
|
|
/// - segments: among the files (each segment becomes a file with a track, named like its track,
|
2026-10-26 18:51:56 +01:00
|
|
|
/// or like its file), among the routes or the tracks of a file (each segment becomes a route or
|
|
|
|
|
/// a track), or among the segments of a track;
|
2026-10-04 15:47:09 +02:00
|
|
|
/// - waypoints: among the waypoints of a file.
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
pub enum Destination {
|
|
|
|
|
Files(Place<FileId>),
|
2026-10-26 18:51:56 +01:00
|
|
|
Routes(FileId, Place<RouteId>),
|
2026-10-04 15:47:09 +02:00
|
|
|
Tracks(FileId, Place<TrackId>),
|
|
|
|
|
Segments(FileId, TrackId, Place<TrackSegmentId>),
|
|
|
|
|
Waypoints(FileId, Place<WaypointId>),
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-04 15:26:58 +02:00
|
|
|
pub fn waypoint_ids(file: &File) -> impl Iterator<Item = WaypointId> + '_ {
|
2026-10-07 18:22:30 +02:00
|
|
|
file.wpt.iter().map(|wpt| wpt.id)
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn file_mut<'a>(state: &'a mut State, id: FileId) -> Result<&'a mut File, CommandError> {
|
|
|
|
|
state
|
|
|
|
|
.files
|
|
|
|
|
.get_mut(&id)
|
|
|
|
|
.map(Rc::make_mut)
|
|
|
|
|
.ok_or(CommandError::NothingToDo)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_file(name: &str, trk: Vec<Track>) -> File {
|
|
|
|
|
let mut file = File {
|
|
|
|
|
trk,
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
|
|
|
|
file.info.name = name.to_owned();
|
|
|
|
|
file
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Adds the files to the state, at `place` in the order of the files.
|
2026-10-04 15:47:09 +02:00
|
|
|
fn add_files(state: &mut State, files: Vec<Rc<File>>, place: Place<FileId>) -> Selection {
|
2026-10-04 15:26:58 +02:00
|
|
|
let at = place.position(&state.order.0);
|
|
|
|
|
let ids: Vec<FileId> = files.iter().map(|file| file.id).collect();
|
|
|
|
|
for file in files {
|
2026-10-04 15:47:09 +02:00
|
|
|
state.files.insert(file.id, file);
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
|
|
|
|
state.order.0.splice(at..at, ids.iter().copied());
|
2026-10-04 15:47:09 +02:00
|
|
|
Selection::File {
|
|
|
|
|
file_ids: ids.into_iter().collect(),
|
|
|
|
|
}
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-04 15:47:09 +02:00
|
|
|
/// Removes the elements of the content from the files, wherever they are.
|
|
|
|
|
pub fn remove_elements(state: &mut State, content: &ClipboardContent) {
|
|
|
|
|
match content {
|
|
|
|
|
ClipboardContent::Files(files) => {
|
|
|
|
|
let ids: HashSet<FileId> = files.iter().map(|file| file.id).collect();
|
|
|
|
|
state.files.retain(|id, _| !ids.contains(id));
|
|
|
|
|
state.order.0.retain(|id| !ids.contains(id));
|
|
|
|
|
}
|
2026-10-26 18:51:56 +01:00
|
|
|
ClipboardContent::Routes(routes) => {
|
|
|
|
|
let ids: HashSet<RouteId> = routes.iter().map(|rte| rte.route.id).collect();
|
|
|
|
|
for file in state.files.values_mut() {
|
|
|
|
|
if file.rte.iter().any(|rte| ids.contains(&rte.id)) {
|
|
|
|
|
Rc::make_mut(file).rte.retain(|rte| !ids.contains(&rte.id));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
ClipboardContent::Tracks(tracks) => {
|
|
|
|
|
let ids: HashSet<TrackId> = tracks.iter().map(|trk| trk.track.id).collect();
|
|
|
|
|
for file in state.files.values_mut() {
|
|
|
|
|
if file.trk.iter().any(|trk| ids.contains(&trk.id)) {
|
|
|
|
|
Rc::make_mut(file).trk.retain(|trk| !ids.contains(&trk.id));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ClipboardContent::Segments(segments) => {
|
|
|
|
|
let ids: HashSet<TrackSegmentId> = segments.iter().map(|seg| seg.segment.id).collect();
|
|
|
|
|
for file in state.files.values_mut() {
|
|
|
|
|
let contains = |trk: &Track| trk.trkseg.iter().any(|seg| ids.contains(&seg.id));
|
|
|
|
|
if file.trk.iter().any(contains) {
|
|
|
|
|
for trk in Rc::make_mut(file).trk.iter_mut() {
|
|
|
|
|
trk.trkseg.retain(|seg| !ids.contains(&seg.id));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ClipboardContent::Waypoints(waypoints) => {
|
|
|
|
|
let ids: HashSet<WaypointId> = waypoints.iter().map(|wpt| wpt.id).collect();
|
|
|
|
|
for file in state.files.values_mut() {
|
|
|
|
|
if waypoint_ids(file).any(|id| ids.contains(&id)) {
|
2026-10-07 18:22:30 +02:00
|
|
|
Rc::make_mut(file).wpt.edit(
|
2026-10-04 15:47:09 +02:00
|
|
|
|wpt| ids.contains(&wpt.id),
|
|
|
|
|
|wpts| {
|
|
|
|
|
wpts.retain(|wpt| !ids.contains(&wpt.id));
|
|
|
|
|
true
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
}
|
2026-10-04 15:26:58 +02:00
|
|
|
|
2026-10-04 15:47:09 +02:00
|
|
|
/// Puts the elements of the content at the destination: copies of them, or the elements
|
|
|
|
|
/// themselves, with their ids, when `cut` (they are removed from where they are first). Returns
|
|
|
|
|
/// the selection of what was put.
|
|
|
|
|
pub fn transfer(
|
|
|
|
|
state: &mut State,
|
|
|
|
|
content: &ClipboardContent,
|
|
|
|
|
cut: bool,
|
|
|
|
|
to: Destination,
|
|
|
|
|
) -> Result<Selection, CommandError> {
|
|
|
|
|
// the destination has to be there, before anything is changed
|
|
|
|
|
let exists = match to {
|
|
|
|
|
Destination::Files(_) => true,
|
2026-10-26 18:51:56 +01:00
|
|
|
Destination::Routes(file_id, _) | Destination::Tracks(file_id, _) => {
|
|
|
|
|
state.files.contains_key(&file_id)
|
|
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
Destination::Segments(file_id, trk_id, _) => state
|
|
|
|
|
.files
|
|
|
|
|
.get(&file_id)
|
|
|
|
|
.is_some_and(|file| file.trk.iter().any(|trk| trk.id == trk_id)),
|
|
|
|
|
Destination::Waypoints(file_id, _) => state.files.contains_key(&file_id),
|
2026-10-04 15:26:58 +02:00
|
|
|
};
|
2026-10-08 19:04:46 +02:00
|
|
|
let valid = matches!(
|
|
|
|
|
(content, to),
|
2026-10-04 15:47:09 +02:00
|
|
|
(ClipboardContent::Files(_), Destination::Files(_))
|
2026-10-08 19:04:46 +02:00
|
|
|
| (
|
2026-10-26 18:51:56 +01:00
|
|
|
ClipboardContent::Routes(_) | ClipboardContent::Tracks(_),
|
|
|
|
|
Destination::Files(_) | Destination::Routes(..) | Destination::Tracks(..)
|
2026-10-08 19:04:46 +02:00
|
|
|
)
|
|
|
|
|
| (
|
|
|
|
|
ClipboardContent::Segments(_),
|
2026-10-26 18:51:56 +01:00
|
|
|
Destination::Files(_)
|
|
|
|
|
| Destination::Routes(..)
|
|
|
|
|
| Destination::Tracks(..)
|
|
|
|
|
| Destination::Segments(..),
|
2026-10-08 19:04:46 +02:00
|
|
|
)
|
|
|
|
|
| (ClipboardContent::Waypoints(_), Destination::Waypoints(..))
|
|
|
|
|
);
|
2026-10-04 15:47:09 +02:00
|
|
|
let empty = match content {
|
|
|
|
|
ClipboardContent::Files(files) => files.is_empty(),
|
2026-10-26 18:51:56 +01:00
|
|
|
ClipboardContent::Routes(routes) => routes.is_empty(),
|
2026-10-04 15:47:09 +02:00
|
|
|
ClipboardContent::Tracks(tracks) => tracks.is_empty(),
|
|
|
|
|
ClipboardContent::Segments(segments) => segments.is_empty(),
|
|
|
|
|
ClipboardContent::Waypoints(waypoints) => waypoints.is_empty(),
|
|
|
|
|
};
|
|
|
|
|
if !exists || !valid || empty {
|
2026-10-04 15:26:58 +02:00
|
|
|
return Err(CommandError::NothingToDo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if cut {
|
2026-10-04 15:47:09 +02:00
|
|
|
remove_elements(state, content);
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
let copy = !cut;
|
|
|
|
|
match (content, to) {
|
|
|
|
|
(ClipboardContent::Files(files), Destination::Files(place)) => {
|
|
|
|
|
let files = files
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|file| {
|
|
|
|
|
if copy {
|
|
|
|
|
Rc::new(copy_file(file))
|
|
|
|
|
} else {
|
|
|
|
|
file.clone()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
Ok(add_files(state, files, place))
|
|
|
|
|
}
|
2026-10-26 18:51:56 +01:00
|
|
|
(ClipboardContent::Routes(routes), to) => transfer_routes(state, routes, copy, to),
|
2026-10-04 15:47:09 +02:00
|
|
|
(ClipboardContent::Tracks(tracks), to) => transfer_tracks(state, tracks, copy, to),
|
|
|
|
|
(ClipboardContent::Segments(segments), to) => transfer_segments(state, segments, copy, to),
|
|
|
|
|
(ClipboardContent::Waypoints(waypoints), Destination::Waypoints(file_id, place)) => {
|
|
|
|
|
transfer_waypoints(state, waypoints, copy, file_id, place)
|
|
|
|
|
}
|
|
|
|
|
_ => Err(CommandError::NothingToDo),
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-10-04 15:26:58 +02:00
|
|
|
|
2026-10-26 18:51:56 +01:00
|
|
|
/// Puts the routes among the routes of a file, at `place`, and gives the selection of them.
|
|
|
|
|
fn insert_routes(
|
|
|
|
|
state: &mut State,
|
|
|
|
|
file_id: FileId,
|
|
|
|
|
place: Place<RouteId>,
|
|
|
|
|
routes: Vec<Route>,
|
|
|
|
|
) -> Result<Selection, CommandError> {
|
|
|
|
|
let ids: HashSet<RouteId> = routes.iter().map(|rte| rte.id).collect();
|
|
|
|
|
let file = file_mut(state, file_id)?;
|
|
|
|
|
let existing: Vec<RouteId> = file.rte.iter().map(|rte| rte.id).collect();
|
|
|
|
|
let at = place.position(&existing);
|
|
|
|
|
file.rte.splice(at..at, routes);
|
|
|
|
|
Ok(Selection::Route {
|
|
|
|
|
file_id,
|
|
|
|
|
rte_ids: ids,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn transfer_routes(
|
|
|
|
|
state: &mut State,
|
|
|
|
|
routes: &[ClipboardRoute],
|
|
|
|
|
copy: bool,
|
|
|
|
|
to: Destination,
|
|
|
|
|
) -> Result<Selection, CommandError> {
|
|
|
|
|
let routes: Vec<(Route, &str)> = routes
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|clip| {
|
|
|
|
|
let route = if copy {
|
|
|
|
|
copy_route(&clip.route)
|
|
|
|
|
} else {
|
|
|
|
|
clip.route.clone()
|
|
|
|
|
};
|
|
|
|
|
(route, clip.file_name.as_str())
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
match to {
|
|
|
|
|
Destination::Files(place) => {
|
|
|
|
|
let files = routes
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(route, file_name)| {
|
|
|
|
|
let name = route
|
|
|
|
|
.info
|
|
|
|
|
.name
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_else(|| file_name.to_owned());
|
|
|
|
|
Rc::new(File {
|
|
|
|
|
rte: vec![route],
|
|
|
|
|
..new_file(&name, vec![])
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
Ok(add_files(state, files, place))
|
|
|
|
|
}
|
|
|
|
|
Destination::Routes(file_id, place) => {
|
|
|
|
|
let routes = routes.into_iter().map(|(route, _)| route).collect();
|
|
|
|
|
insert_routes(state, file_id, place, routes)
|
|
|
|
|
}
|
|
|
|
|
Destination::Tracks(file_id, _) => {
|
|
|
|
|
let routes = routes.into_iter().map(|(route, _)| route).collect();
|
|
|
|
|
insert_routes(state, file_id, Place::End, routes)
|
|
|
|
|
}
|
|
|
|
|
_ => Err(CommandError::NothingToDo),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Puts the tracks among the tracks of a file, at `place`, and gives the selection of them.
|
|
|
|
|
fn insert_tracks(
|
|
|
|
|
state: &mut State,
|
|
|
|
|
file_id: FileId,
|
|
|
|
|
place: Place<TrackId>,
|
|
|
|
|
tracks: Vec<Track>,
|
|
|
|
|
) -> Result<Selection, CommandError> {
|
|
|
|
|
let ids: HashSet<TrackId> = tracks.iter().map(|trk| trk.id).collect();
|
|
|
|
|
let file = file_mut(state, file_id)?;
|
|
|
|
|
let existing: Vec<TrackId> = file.trk.iter().map(|trk| trk.id).collect();
|
|
|
|
|
let at = place.position(&existing);
|
|
|
|
|
file.trk.splice(at..at, tracks);
|
|
|
|
|
Ok(Selection::Track {
|
|
|
|
|
file_id,
|
|
|
|
|
trk_ids: ids,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-04 15:47:09 +02:00
|
|
|
fn transfer_tracks(
|
|
|
|
|
state: &mut State,
|
|
|
|
|
tracks: &[ClipboardTrack],
|
|
|
|
|
copy: bool,
|
|
|
|
|
to: Destination,
|
|
|
|
|
) -> Result<Selection, CommandError> {
|
|
|
|
|
let tracks: Vec<(Track, &str)> = tracks
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|clip| {
|
|
|
|
|
let track = if copy {
|
|
|
|
|
copy_track(&clip.track)
|
|
|
|
|
} else {
|
|
|
|
|
clip.track.clone()
|
|
|
|
|
};
|
|
|
|
|
(track, clip.file_name.as_str())
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
match to {
|
|
|
|
|
Destination::Files(place) => {
|
2026-10-04 15:26:58 +02:00
|
|
|
let files = tracks
|
|
|
|
|
.into_iter()
|
2026-10-04 15:47:09 +02:00
|
|
|
.map(|(track, file_name)| {
|
2026-10-04 15:26:58 +02:00
|
|
|
let name = track
|
|
|
|
|
.info
|
|
|
|
|
.name
|
|
|
|
|
.clone()
|
2026-10-04 15:47:09 +02:00
|
|
|
.unwrap_or_else(|| file_name.to_owned());
|
|
|
|
|
Rc::new(new_file(&name, vec![track]))
|
2026-10-04 15:26:58 +02:00
|
|
|
})
|
|
|
|
|
.collect();
|
2026-10-04 15:47:09 +02:00
|
|
|
Ok(add_files(state, files, place))
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
Destination::Tracks(file_id, place) => {
|
2026-10-26 18:51:56 +01:00
|
|
|
let tracks = tracks.into_iter().map(|(track, _)| track).collect();
|
|
|
|
|
insert_tracks(state, file_id, place, tracks)
|
|
|
|
|
}
|
|
|
|
|
Destination::Routes(file_id, _) => {
|
|
|
|
|
let tracks = tracks.into_iter().map(|(track, _)| track).collect();
|
|
|
|
|
insert_tracks(state, file_id, Place::Index(0), tracks)
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
_ => Err(CommandError::NothingToDo),
|
|
|
|
|
}
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-04 15:47:09 +02:00
|
|
|
fn transfer_segments(
|
2026-10-04 15:26:58 +02:00
|
|
|
state: &mut State,
|
2026-10-04 15:47:09 +02:00
|
|
|
segments: &[ClipboardSegment],
|
|
|
|
|
copy: bool,
|
|
|
|
|
to: Destination,
|
2026-10-04 15:26:58 +02:00
|
|
|
) -> Result<Selection, CommandError> {
|
2026-10-04 15:47:09 +02:00
|
|
|
let segments: Vec<(TrackSegment, &str)> = segments
|
2026-10-04 15:26:58 +02:00
|
|
|
.iter()
|
2026-10-04 15:47:09 +02:00
|
|
|
.map(|clip| {
|
|
|
|
|
let segment = if copy {
|
|
|
|
|
copy_segment(&clip.segment)
|
|
|
|
|
} else {
|
|
|
|
|
clip.segment.clone()
|
|
|
|
|
};
|
|
|
|
|
(segment, clip.name.as_str())
|
|
|
|
|
})
|
2026-10-04 15:26:58 +02:00
|
|
|
.collect();
|
|
|
|
|
// a segment on its own needs a track
|
|
|
|
|
let own_track = |segment: TrackSegment| Track {
|
|
|
|
|
trkseg: vec![segment],
|
|
|
|
|
..Default::default()
|
|
|
|
|
};
|
2026-10-04 15:47:09 +02:00
|
|
|
match to {
|
|
|
|
|
Destination::Files(place) => {
|
2026-10-04 15:26:58 +02:00
|
|
|
let files = segments
|
|
|
|
|
.into_iter()
|
2026-10-04 15:47:09 +02:00
|
|
|
.map(|(segment, name)| Rc::new(new_file(name, vec![own_track(segment)])))
|
2026-10-04 15:26:58 +02:00
|
|
|
.collect();
|
2026-10-04 15:47:09 +02:00
|
|
|
Ok(add_files(state, files, place))
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
2026-10-26 18:51:56 +01:00
|
|
|
Destination::Routes(file_id, place) => {
|
|
|
|
|
let routes = segments
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|(seg, _)| Route {
|
|
|
|
|
seg,
|
|
|
|
|
..Default::default()
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
insert_routes(state, file_id, place, routes)
|
|
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
Destination::Tracks(file_id, place) => {
|
2026-10-26 18:51:56 +01:00
|
|
|
let tracks = segments
|
2026-10-04 15:47:09 +02:00
|
|
|
.into_iter()
|
|
|
|
|
.map(|(segment, _)| own_track(segment))
|
|
|
|
|
.collect();
|
2026-10-26 18:51:56 +01:00
|
|
|
insert_tracks(state, file_id, place, tracks)
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
Destination::Segments(file_id, trk_id, place) => {
|
|
|
|
|
let segments: Vec<TrackSegment> =
|
|
|
|
|
segments.into_iter().map(|(segment, _)| segment).collect();
|
|
|
|
|
let ids: HashSet<TrackSegmentId> = segments.iter().map(|seg| seg.id).collect();
|
|
|
|
|
let trk = file_mut(state, file_id)?
|
2026-10-04 15:26:58 +02:00
|
|
|
.trk
|
|
|
|
|
.iter_mut()
|
|
|
|
|
.find(|trk| trk.id == trk_id)
|
|
|
|
|
.ok_or(CommandError::NothingToDo)?;
|
|
|
|
|
let existing: Vec<TrackSegmentId> = trk.trkseg.iter().map(|seg| seg.id).collect();
|
|
|
|
|
let at = place.position(&existing);
|
|
|
|
|
trk.trkseg.splice(at..at, segments);
|
2026-10-04 15:47:09 +02:00
|
|
|
Ok(Selection::TrackSegment {
|
|
|
|
|
file_id,
|
2026-10-04 15:26:58 +02:00
|
|
|
trk_id,
|
|
|
|
|
trkseg_ids: ids,
|
2026-10-04 15:47:09 +02:00
|
|
|
})
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
2026-10-04 15:47:09 +02:00
|
|
|
Destination::Waypoints(..) => Err(CommandError::NothingToDo),
|
|
|
|
|
}
|
2026-10-04 15:26:58 +02:00
|
|
|
}
|
|
|
|
|
|
2026-10-04 15:47:09 +02:00
|
|
|
fn transfer_waypoints(
|
2026-10-04 15:26:58 +02:00
|
|
|
state: &mut State,
|
2026-10-04 15:47:09 +02:00
|
|
|
waypoints: &[Waypoint],
|
|
|
|
|
copy: bool,
|
2026-10-04 15:26:58 +02:00
|
|
|
file_id: FileId,
|
|
|
|
|
place: Place<WaypointId>,
|
|
|
|
|
) -> Result<Selection, CommandError> {
|
2026-10-04 15:47:09 +02:00
|
|
|
let waypoints: Vec<Waypoint> = waypoints
|
2026-10-04 15:26:58 +02:00
|
|
|
.iter()
|
2026-10-04 15:47:09 +02:00
|
|
|
.map(|wpt| {
|
|
|
|
|
if copy {
|
|
|
|
|
copy_waypoint(wpt)
|
|
|
|
|
} else {
|
|
|
|
|
wpt.clone()
|
|
|
|
|
}
|
|
|
|
|
})
|
2026-10-04 15:26:58 +02:00
|
|
|
.collect();
|
|
|
|
|
let ids = waypoints.iter().map(|wpt| wpt.id).collect();
|
2026-10-04 15:47:09 +02:00
|
|
|
let file = file_mut(state, file_id)?;
|
2026-10-04 15:26:58 +02:00
|
|
|
let existing: Vec<WaypointId> = waypoint_ids(file).collect();
|
2026-10-07 18:22:30 +02:00
|
|
|
file.wpt.insert_at(place.position(&existing), waypoints);
|
2026-10-04 15:26:58 +02:00
|
|
|
Ok(Selection::Waypoint {
|
2026-10-04 15:47:09 +02:00
|
|
|
file_id,
|
2026-10-04 15:26:58 +02:00
|
|
|
wpt_ids: ids,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_place_position() {
|
|
|
|
|
let ids = [1, 2, 3];
|
|
|
|
|
assert_eq!(Place::End.position(&ids), 3);
|
|
|
|
|
assert_eq!(Place::After(1).position(&ids), 1);
|
|
|
|
|
assert_eq!(Place::After(3).position(&ids), 3);
|
|
|
|
|
// not there: at the end
|
|
|
|
|
assert_eq!(Place::After(9).position(&ids), 3);
|
|
|
|
|
assert_eq!(Place::Index(0).position(&ids), 0);
|
|
|
|
|
assert_eq!(Place::Index(2).position(&ids), 2);
|
|
|
|
|
assert_eq!(Place::Index(10).position(&ids), 3);
|
|
|
|
|
assert_eq!(Place::<i32>::End.position(&[]), 0);
|
|
|
|
|
assert_eq!(Place::<i32>::Index(4).position(&[]), 0);
|
|
|
|
|
}
|
|
|
|
|
}
|