223 lines
8.3 KiB
Rust
223 lines
8.3 KiB
Rust
//! State actions capture their own diagram; architecture selection stays separate.
|
|
use super::{
|
|
state_edit::{self, Endpoint, Event, Tool},
|
|
states,
|
|
};
|
|
use crate::{
|
|
app::{App, Message},
|
|
context_menu::{Entry, Menu},
|
|
model_menu::{self, Opened},
|
|
};
|
|
use oa_core::*;
|
|
|
|
pub fn popup(app: &App, opened: &Opened, clean: bool) -> Option<Menu<Message>> {
|
|
let project = app.project();
|
|
let machine = super::selected_owner(app)
|
|
.filter(|id| project.elements[id].kind() == Kind::StateMachine)?;
|
|
let context = app.state_context();
|
|
let request = &opened.request;
|
|
let id = request.object;
|
|
let item = |caption: &str, message: Message, enabled: bool| {
|
|
if clean && enabled {
|
|
Entry::action(
|
|
caption,
|
|
Message::Menu(model_menu::Event::Invoke(opened.token, Box::new(message))),
|
|
)
|
|
} else {
|
|
Entry::disabled(caption)
|
|
}
|
|
};
|
|
let diagram_event = Message::StateDiagram;
|
|
let form = |tool| {
|
|
Message::Behavior(super::Event::States(states::Event::Start(
|
|
machine, tool, id,
|
|
)))
|
|
};
|
|
let mut entries = vec![Entry::title(crate::views::short(project.named(id), 38))];
|
|
if !clean {
|
|
entries.push(Entry::title("Apply or cancel pending edits first"));
|
|
}
|
|
|
|
if let Some(context) = context.filter(|context| context.diagram == id) {
|
|
entries.extend([
|
|
item("View properties", Message::EditProperties(id), true),
|
|
item("Duplicate view", Message::DuplicateDiagram(id), true),
|
|
item("Fit diagram", diagram_event(Event::Fit(context)), true),
|
|
item(
|
|
"Create another state view",
|
|
diagram_event(Event::CreateView(machine)),
|
|
true,
|
|
),
|
|
Entry::separator(),
|
|
item(
|
|
"Create region…",
|
|
diagram_event(Event::Choose(Tool::Region)),
|
|
true,
|
|
),
|
|
item(
|
|
"New transition with details…",
|
|
form(states::Tool::Transition),
|
|
true,
|
|
),
|
|
]);
|
|
} else {
|
|
entries.extend([
|
|
item("Properties", Message::EditProperties(id), true),
|
|
item("Open source", Message::SourceForSelection, true),
|
|
item(
|
|
"Connections & references",
|
|
Message::InspectorTab(crate::navigation::InspectorTab::Connections),
|
|
true,
|
|
),
|
|
]);
|
|
if let Some(element) = project.elements.get(&id)
|
|
&& projection::state_machine_contains(project, machine, id)
|
|
{
|
|
entries.push(Entry::separator());
|
|
let creates = if element.kind() == Kind::Region {
|
|
Some((
|
|
"Create state…",
|
|
Kind::State,
|
|
states::Tool::Vertex,
|
|
Tool::Vertex(Kind::State),
|
|
))
|
|
} else if element.kind() == Kind::StateMachine || element.kind().is_stable_state() {
|
|
Some((
|
|
"Create region…",
|
|
Kind::Region,
|
|
states::Tool::Region,
|
|
Tool::Region,
|
|
))
|
|
} else {
|
|
None
|
|
};
|
|
if let Some((caption, kind, typed, tool)) = creates {
|
|
entries.push(if let Some(context) = context {
|
|
item(
|
|
caption,
|
|
diagram_event(Event::Choose(tool)),
|
|
state_edit::eligible_owner(project, context.diagram, id, kind),
|
|
)
|
|
} else {
|
|
item(caption, form(typed), true)
|
|
});
|
|
}
|
|
entries.push(item(
|
|
"New transition with details…",
|
|
form(states::Tool::Transition),
|
|
true,
|
|
));
|
|
if let Some(context) = context
|
|
&& let Some(node) = request
|
|
.placement
|
|
.and_then(|placement| project.diagrams[&context.diagram].nodes.get(&placement))
|
|
.filter(|node| node.element == id)
|
|
{
|
|
entries.push(Entry::separator());
|
|
entries.push(item(
|
|
"Placement properties",
|
|
Message::EditProperties(node.id),
|
|
true,
|
|
));
|
|
if element.kind() == Kind::StateMachine
|
|
|| element.kind() == Kind::Region
|
|
|| element.kind().is_stable_state()
|
|
{
|
|
let mut changed = node.clone();
|
|
changed.collapsed = !changed.collapsed;
|
|
entries.push(item(
|
|
if node.collapsed {
|
|
"Unfold contents"
|
|
} else {
|
|
"Fold contents"
|
|
},
|
|
Message::Command(Command::EditNode {
|
|
diagram: context.diagram,
|
|
original: node.id,
|
|
node: changed,
|
|
}),
|
|
true,
|
|
));
|
|
}
|
|
entries.push(
|
|
item(
|
|
"Hide from this diagram",
|
|
Message::Command(Command::Delete(node.id)),
|
|
true,
|
|
)
|
|
.shortcut("Delete"),
|
|
);
|
|
}
|
|
}
|
|
if let Some(relation) = project.relations.get(&id)
|
|
&& relation.kind() == RelationKind::StateTransition
|
|
&& projection::state_machine_contains(project, machine, relation.source())
|
|
&& projection::state_machine_contains(project, machine, relation.target())
|
|
{
|
|
entries.push(Entry::separator());
|
|
let edit = context.map_or_else(
|
|
|| Message::Behavior(super::Event::States(states::Event::Edit(id))),
|
|
|context| diagram_event(Event::EditTransition(context, id)),
|
|
);
|
|
entries.push(item("Edit transition", edit, true));
|
|
if let Some(context) = context {
|
|
let visible = projection::project_view(project, context.diagram)
|
|
.edges
|
|
.iter()
|
|
.any(|edge| edge.id == id);
|
|
for (caption, endpoint) in [
|
|
("Reconnect source", Endpoint::Source),
|
|
("Reconnect target", Endpoint::Target),
|
|
] {
|
|
entries.push(item(
|
|
caption,
|
|
diagram_event(Event::Choose(Tool::Reconnect {
|
|
relation: id,
|
|
endpoint,
|
|
})),
|
|
visible,
|
|
));
|
|
}
|
|
if !visible {
|
|
entries.push(Entry::title(
|
|
"Use transition details for hidden or internal endpoints",
|
|
));
|
|
}
|
|
}
|
|
}
|
|
if id != project.id {
|
|
entries.push(Entry::separator());
|
|
entries.push(item("Delete from model…", Message::Delete, true).shortcut(
|
|
if context.is_some_and(|context| {
|
|
context.diagram != id
|
|
&& (request.placement.is_some()
|
|
|| projection::project_view(project, context.diagram)
|
|
.edges
|
|
.iter()
|
|
.any(|edge| edge.id == id)
|
|
|| super::state_canvas::visible_row_relation(
|
|
project,
|
|
context.diagram,
|
|
&projection::project_view(project, context.diagram),
|
|
id,
|
|
))
|
|
}) {
|
|
if request.placement.is_some() {
|
|
"Shift+Delete"
|
|
} else {
|
|
"Delete / Shift+Delete"
|
|
}
|
|
} else {
|
|
""
|
|
},
|
|
));
|
|
}
|
|
}
|
|
Some(Menu {
|
|
token: opened.token,
|
|
anchor: request.anchor,
|
|
entries,
|
|
on_close: Message::Menu(model_menu::Event::Close),
|
|
})
|
|
}
|