241 lines
8.6 KiB
Rust
241 lines
8.6 KiB
Rust
//! Captured diagram removal and model-confirmation lifetimes.
|
|
use super::*;
|
|
use crate::{model_menu, route_edit::Context};
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Intent {
|
|
pub context: Context,
|
|
pub object: Id,
|
|
pub placement: Option<Id>,
|
|
pub model: bool,
|
|
}
|
|
|
|
pub struct Confirmation {
|
|
pub snapshot: model_menu::Opened,
|
|
pub selection: Id,
|
|
pub placement: Option<Id>,
|
|
pub behavior_owner: Option<Id>,
|
|
}
|
|
|
|
pub fn enabled(app: &App) -> bool {
|
|
!app.busy && focus_enabled(app)
|
|
}
|
|
|
|
pub(crate) fn focus_enabled(app: &App) -> bool {
|
|
crate::behavior::canvas_workspace::keyboard_owner_enabled(app)
|
|
&& !app.draft_dirty()
|
|
&& !app.source_dirty()
|
|
&& !app.behavior.has_pending()
|
|
&& app
|
|
.architecture
|
|
.as_ref()
|
|
.is_none_or(|state| !state.inline())
|
|
}
|
|
|
|
pub(crate) fn behavior_owner(app: &App) -> Option<Id> {
|
|
app.topology_context()
|
|
.map(|context| context.owner)
|
|
.or_else(|| app.scenario_context().map(|context| context.scenario))
|
|
}
|
|
|
|
fn placement(app: &App) -> Option<Id> {
|
|
if behavior_owner(app).is_some() {
|
|
None
|
|
} else if app.page == Page::Behavior {
|
|
app.behavior.diagram.placement
|
|
} else {
|
|
app.selected_node
|
|
}
|
|
}
|
|
|
|
/// The derived canvases remove semantic occurrences, not reusable definitions.
|
|
/// Kept beside target capture so confirmation rendering uses the same meaning.
|
|
pub(crate) fn explanation(app: &App, object: Id) -> &'static str {
|
|
if app
|
|
.project()
|
|
.diagrams
|
|
.values()
|
|
.any(|diagram| diagram.nodes.contains_key(&object))
|
|
{
|
|
return "Remove only this diagram placement. The model element and its placements in other diagrams remain. Undo restores this placement.";
|
|
}
|
|
if app
|
|
.project()
|
|
.relations
|
|
.get(&object)
|
|
.is_some_and(|relation| {
|
|
matches!(
|
|
relation.kind(),
|
|
RelationKind::StateAction | RelationKind::StateTransition
|
|
)
|
|
})
|
|
{
|
|
return "Remove this state relation. Its states and referenced activities or functions remain. Undo restores the relation.";
|
|
}
|
|
if app
|
|
.project()
|
|
.relations
|
|
.get(&object)
|
|
.is_some_and(|relation| {
|
|
matches!(
|
|
relation.kind(),
|
|
RelationKind::SequenceLink | RelationKind::ContextualLink
|
|
)
|
|
})
|
|
{
|
|
return "Remove this process link. Its endpoint occurrences and referenced reusable model elements are preserved. Undo restores the deletion.";
|
|
}
|
|
match app
|
|
.project()
|
|
.elements
|
|
.get(&object)
|
|
.and_then(|element| element.shared())
|
|
{
|
|
Some(SharedBody::Lifeline { .. }) => {
|
|
"Remove this participant from the scenario. Its represented model element is preserved. Messages and other occurrences remain with affected participant references cleared. Undo restores the deletion."
|
|
}
|
|
Some(SharedBody::Occurrence {
|
|
body: OccurrenceBody::Fragment { .. } | OccurrenceBody::Branch { .. },
|
|
..
|
|
}) => {
|
|
"Remove this occurrence and its owned branches and steps. Referenced reusable model elements are preserved. Undo restores the deletion."
|
|
}
|
|
Some(SharedBody::Occurrence { .. }) => {
|
|
"Remove this occurrence and its dependent links. Referenced reusable model elements are preserved. Undo restores the deletion."
|
|
}
|
|
_ => {
|
|
"Owned children, incident relations, occurrences, placements and trace links will also be removed. The traced counterpart itself is preserved. Undo restores the deletion."
|
|
}
|
|
}
|
|
}
|
|
|
|
impl App {
|
|
pub(super) fn remove_from_diagram(&mut self, intent: Intent) {
|
|
if !enabled(self) || self.connection.is_some() || self.selected_bend.is_some() {
|
|
return;
|
|
}
|
|
let current = match self.page {
|
|
Page::Diagram if self.architecture.as_ref().is_none_or(|s| !s.saved.placing) => {
|
|
self.route_context()
|
|
}
|
|
Page::Behavior
|
|
if self.behavior.diagram.tool == crate::behavior::state_edit::Tool::Select =>
|
|
{
|
|
self.state_context()
|
|
}
|
|
_ => None,
|
|
};
|
|
let selected_object = self
|
|
.project()
|
|
.diagrams
|
|
.get(&intent.context.diagram)
|
|
.and_then(|diagram| diagram.nodes.get(&self.selected))
|
|
.map_or(self.selected, |node| node.element);
|
|
if current != Some(intent.context) || selected_object != intent.object {
|
|
return;
|
|
}
|
|
let view = projection::project_view(self.project(), intent.context.diagram);
|
|
let placement = if self.page == Page::Behavior {
|
|
self.behavior.diagram.placement
|
|
} else {
|
|
self.selected_node
|
|
};
|
|
if intent.placement != placement || intent.object == intent.context.diagram {
|
|
return;
|
|
}
|
|
let visible = if let Some(id) = intent.placement {
|
|
self.project().diagrams[&intent.context.diagram]
|
|
.nodes
|
|
.get(&id)
|
|
.is_some_and(|node| node.element == intent.object)
|
|
&& view
|
|
.nodes
|
|
.iter()
|
|
.any(|node| node.id == id && node.element == intent.object)
|
|
} else {
|
|
view.edges.iter().any(|edge| edge.id == intent.object)
|
|
|| view.ports.iter().any(|port| port.element == intent.object)
|
|
|| (self.page == Page::Behavior
|
|
&& crate::behavior::state_canvas::visible_row_relation(
|
|
self.project(),
|
|
intent.context.diagram,
|
|
&view,
|
|
intent.object,
|
|
))
|
|
};
|
|
if !visible {
|
|
return;
|
|
}
|
|
if !intent.model
|
|
&& let Some(placement) = intent.placement
|
|
{
|
|
if self.execute(Command::Delete(placement)) {
|
|
self.selected_node = None;
|
|
self.behavior.diagram.placement = None;
|
|
self.selected_bend = None;
|
|
self.status = "Hidden from this diagram · Model element preserved · Undo restores the placement".into();
|
|
}
|
|
} else {
|
|
self.begin_model_delete_target(intent.object, Some(intent.context));
|
|
}
|
|
}
|
|
|
|
pub(super) fn begin_model_delete(&mut self, canvas: Option<Context>) {
|
|
self.begin_model_delete_target(self.selected, canvas);
|
|
}
|
|
|
|
fn begin_model_delete_target(&mut self, object: Id, canvas: Option<Context>) {
|
|
if object == self.project().id || self.behavior.has_pending() {
|
|
return;
|
|
}
|
|
let mut request = model_menu::Request::object(object, iced::Point::ORIGIN);
|
|
request.canvas = canvas;
|
|
request.placement = placement(self);
|
|
// Opening a modal retires any pending canvas intent before capturing
|
|
// the confirmation; every new dialog receives its own replay token.
|
|
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
|
if canvas.is_some() {
|
|
request.canvas = if self.page == Page::Behavior {
|
|
self.state_context()
|
|
} else {
|
|
self.route_context()
|
|
};
|
|
}
|
|
self.menu_sequence = self.menu_sequence.wrapping_add(1);
|
|
self.delete_target =
|
|
model_menu::Opened::new(self, request, self.menu_sequence).map(|snapshot| {
|
|
Confirmation {
|
|
snapshot,
|
|
selection: self.selected,
|
|
placement: placement(self),
|
|
behavior_owner: behavior_owner(self),
|
|
}
|
|
});
|
|
self.delete_pending = self.delete_target.is_some();
|
|
}
|
|
|
|
pub(super) fn confirm_model_delete(&mut self, token: u64) {
|
|
let Some(target) = self.delete_target.as_ref() else {
|
|
return;
|
|
};
|
|
if target.snapshot.token != token {
|
|
return;
|
|
}
|
|
let valid = self.delete_pending
|
|
&& target.snapshot.valid(self)
|
|
&& target.selection == self.selected
|
|
&& target.placement == placement(self)
|
|
&& target.behavior_owner == behavior_owner(self)
|
|
&& !self.behavior.has_pending()
|
|
&& !self.source_dirty()
|
|
&& !self.draft_dirty();
|
|
let id = target.snapshot.request.object;
|
|
self.delete_pending = false;
|
|
self.delete_target = None;
|
|
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
|
if valid {
|
|
self.execute(Command::Delete(id));
|
|
}
|
|
}
|
|
}
|