Refactor FILTER validation and operator normalization (v2.2)

This commit is contained in:
turbuczn committed 2026-10-12 22:27:19 +02:00
1 parent 390e12169a
commit 7c61583ac2
2 files changed
+145 -70

No files matched your search

+81 -68
View File
@@ -106,6 +106,7 @@ pub(in crate::app) use file::background_task;
#[cfg(not(target_arch = "wasm32"))]
pub(in crate::app) use file::native_paths_match;
mod page_setup_import;
mod qselect;
mod style;
pub(in crate::app) mod util;
mod viewport;
@@ -7011,14 +7012,7 @@ impl OpenCADStudio {
if matches!(scope, crate::app::QSelectScope::CurrentSelection) {
state.append = false;
}
if matches!(
state.operator,
crate::app::QSelectOp::Gt | crate::app::QSelectOp::Lt
) && !state.property.as_ref().is_some_and(|property| {
matches!(property.editor, crate::app::QSelectValueEditor::Number)
}) {
state.operator = crate::app::QSelectOp::Eq;
}
state.normalize_operator();
}
Task::none()
}
@@ -7042,14 +7036,7 @@ impl OpenCADStudio {
state.property = kept_property;
state.value.clear();
state.error = None;
if matches!(
state.operator,
crate::app::QSelectOp::Gt | crate::app::QSelectOp::Lt
) && !state.property.as_ref().is_some_and(|property| {
matches!(property.editor, crate::app::QSelectValueEditor::Number)
}) {
state.operator = crate::app::QSelectOp::Eq;
}
state.normalize_operator();
}
Task::none()
}
@@ -7059,14 +7046,7 @@ impl OpenCADStudio {
state.property = p;
state.value.clear();
state.error = None;
if matches!(
state.operator,
crate::app::QSelectOp::Gt | crate::app::QSelectOp::Lt
) && !state.property.as_ref().is_some_and(|property| {
matches!(property.editor, crate::app::QSelectValueEditor::Number)
}) {
state.operator = crate::app::QSelectOp::Eq;
}
state.normalize_operator();
}
Task::none()
}
@@ -7105,47 +7085,77 @@ impl OpenCADStudio {
Task::none()
}
Message::QSelectPickObject => self.on_qselect_pick_object(),
Message::QSelectAddCondition => {
if let Some(state) = self.qselect.as_mut() {
let condition = state.draft_condition();
if let Some(error) = condition.validation_error() {
state.error = Some(error);
return Task::none();
}
if let Some(index) = state.editing_condition.take() {
if let Some(existing) = state.conditions.get_mut(index) { *existing = condition; }
} else { state.conditions.push(condition); }
state.error = None;
}
Task::none()
}
Message::QSelectEditCondition(index) => {
let condition = self.qselect.as_ref().and_then(|state| state.conditions.get(index).cloned());
if let Some(condition) = condition {
let scope = self.qselect.as_ref().unwrap().scope;
let properties = self.tabs[self.active_tab].scene.qselect_properties(condition.type_filter.as_deref(), scope);
if let Some(state) = self.qselect.as_mut() {
state.available_properties = properties;
state.type_filter = condition.type_filter;
state.property = condition.property;
state.operator = condition.operator;
state.value = condition.value;
state.editing_condition = Some(index);
state.error = None;
}
}
Task::none()
}
Message::QSelectRemoveCondition(index) => {
if let Some(state) = self.qselect.as_mut() {
if index < state.conditions.len() {
state.conditions.remove(index);
state.editing_condition = match state.editing_condition {
Some(editing) if editing == index => None,
Some(editing) if editing > index => Some(editing - 1),
other => other,
};
}
state.error = None;
}
Task::none()
}
Message::QSelectClearConditions => {
if let Some(state) = self.qselect.as_mut() {
state.conditions.clear();
state.editing_condition = None;
state.error = None;
}
Task::none()
}
Message::QSelectCancelEdit => {
if let Some(state) = self.qselect.as_mut() {
state.editing_condition = None;
state.error = None;
}
Task::none()
}
Message::QSelectApply => {
let validation_error = self.qselect.as_ref().and_then(|state| {
let candidate_count = self.tabs[self.active_tab]
.scene
.qselect_candidate_count(state.scope);
if candidate_count == 0 {
if self.tabs[self.active_tab].scene.qselect_candidate_count(state.scope) == 0 {
Some(crate::t!("No objects are available in this scope.").into_owned())
} else if let Some(property) = state.property.as_ref() {
if matches!(state.operator, crate::app::QSelectOp::Any) {
None
} else if matches!(
state.operator,
crate::app::QSelectOp::Gt | crate::app::QSelectOp::Lt
) && !matches!(
property.editor,
crate::app::QSelectValueEditor::Number
) {
Some(
crate::t!("This operator requires a numeric property.")
.into_owned(),
)
} else {
match &property.editor {
crate::app::QSelectValueEditor::Number
if crate::entities::common::parse_f64(&state.value)
.is_none() =>
{
Some(crate::t!("Enter a valid number.").into_owned())
}
crate::app::QSelectValueEditor::Choice(_)
if state.value.is_empty() =>
{
Some(crate::t!("Choose a value.").into_owned())
}
crate::app::QSelectValueEditor::Text
| crate::app::QSelectValueEditor::Number
| crate::app::QSelectValueEditor::Choice(_) => None,
}
}
} else if state.editing_condition.is_some() {
Some(crate::t!("Update or cancel the condition edit before applying.").into_owned())
} else if state.conditions.is_empty() {
state.draft_condition().validation_error()
} else {
None
state.conditions.iter().find_map(|condition| condition.validation_error())
}
});
if let Some(error) = validation_error {
@@ -7160,13 +7170,11 @@ impl OpenCADStudio {
self.qselect_settings = Some((&state).into());
self.reset_modal_geometry();
let i = self.active_tab;
let matched = self.tabs[i].scene.qselect(
state.scope,
state.type_filter.as_deref(),
state.property.as_ref().map(|p| p.field.as_str()),
state.operator,
&state.value,
state.mode,
let conditions = if state.conditions.is_empty() {
vec![state.draft_condition()]
} else { state.conditions.clone() };
let matched = self.tabs[i].scene.qselect_conditions(
state.scope, &conditions, state.mode,
state.append && matches!(state.scope, crate::app::QSelectScope::CurrentSpace),
);
self.command_line
@@ -7190,6 +7198,11 @@ impl OpenCADStudio {
.collect();
wblock.error_message = None;
self.active_modal = Some(super::ModalKind::WriteBlock);
} else {
// FILTER may be opened from a still-focused command input.
// Release text focus so Delete and clipboard shortcuts act
// on the resulting pick-first selection, not on that input.
return self.unfocus_widgets();
}
Task::none()
}