ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvSelectionToolController.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
9 
11 #include "cvSelectionData.h"
12 #include "cvSelectionHighlighter.h"
13 #include "cvSelectionPipeline.h" // For invalidateCachedSelection
14 #include "cvViewSelectionManager.h"
15 
16 // CV_CORE_LIB
17 #include <CVLog.h>
18 
19 // QT
20 #include <QAction>
21 #include <QActionGroup>
22 #include <QWidget>
23 
24 //-----------------------------------------------------------------------------
26  static cvSelectionToolController _instance;
27  return &_instance;
28 }
29 
30 //-----------------------------------------------------------------------------
31 cvSelectionToolController::cvSelectionToolController(QObject* parent)
32  : QObject(parent),
33  m_parentWidget(nullptr),
34  m_manager(cvViewSelectionManager::instance()),
35  m_selectionToolsActive(false),
36  m_modifierGroup(nullptr) {
37  // Connect to manager signals
38  // Use QOverload to select the correct signal overload
39  connect(m_manager,
40  QOverload<const cvSelectionData&>::of(
42  this, [this](const cvSelectionData& data) {
43  emit selectionFinished(data);
44  });
45 
46  connect(m_manager,
47  QOverload<>::of(&cvViewSelectionManager::selectionChanged), this,
49 
50  CVLog::PrintVerbose("[cvSelectionToolController] Initialized");
51 }
52 
53 //-----------------------------------------------------------------------------
54 cvSelectionToolController::~cvSelectionToolController() {
55  // Clean up reactions
56  for (auto reaction : m_reactions) {
57  if (reaction) {
58  delete reaction;
59  }
60  }
61  m_reactions.clear();
62 
63  CVLog::PrintVerbose("[cvSelectionToolController] Destroyed");
64 }
65 
66 //-----------------------------------------------------------------------------
68  m_parentWidget = parent;
69 
71  "[cvSelectionToolController] Initialized with parent widget");
72 }
73 
74 //-----------------------------------------------------------------------------
76  if (m_manager) {
77  m_manager->setVisualizer(viewer);
78  }
79 
80  // Update all registered reactions with the visualizer
81  // This is necessary because reactions may be registered before
82  // the visualizer is set
83  for (auto reaction : m_reactions) {
84  if (reaction) {
85  reaction->setVisualizer(viewer);
86  }
87  }
88 
90  QString("[cvSelectionToolController] Visualizer set, updated %1 "
91  "reactions")
92  .arg(m_reactions.size()));
93 }
94 
95 //-----------------------------------------------------------------------------
97  QAction* action, SelectionMode mode) {
98  if (!action) {
100  "[cvSelectionToolController] Cannot register null action "
101  "(new)");
102  return nullptr;
103  }
104 
105  // Create the new simplified reaction (ParaView-style)
107  new cvRenderViewSelectionReaction(action, mode, m_modifierGroup);
108 
109  // Set visualizer if already available
110  if (m_manager && m_manager->getVisualizer()) {
111  reaction->setVisualizer(m_manager->getVisualizer());
112  }
113 
114  // Connect selection finished signal
115  connect(reaction, &cvRenderViewSelectionReaction::selectionFinished, this,
117 
118  // Connect zoom to box signal if applicable
119  if (mode == SelectionMode::ZOOM_TO_BOX) {
122  }
123 
124  // Monitor action state changes to track when selection tools are active
125  if (action->isCheckable()) {
126  connect(action, &QAction::toggled, this, [this, mode](bool checked) {
128  QString("[cvSelectionToolController] Action (new) for mode "
129  "%1 %2")
130  .arg(static_cast<int>(mode))
131  .arg(checked ? "checked" : "unchecked"));
132 
133  // For ZOOM_TO_BOX, don't update selection properties state
134  if (mode == SelectionMode::ZOOM_TO_BOX) {
135  return;
136  }
137 
138  // Update selection tools active state
139  bool anyActive = false;
140  for (auto it = m_reactions.constBegin();
141  it != m_reactions.constEnd(); ++it) {
142  SelectionMode reactionMode = it.key();
143  if (reactionMode == SelectionMode::ZOOM_TO_BOX) {
144  continue;
145  }
146 
147  QPointer<cvRenderViewSelectionReaction> r = it.value();
148  if (r && r->parentAction() &&
149  r->parentAction()->isCheckable() &&
150  r->parentAction()->isChecked()) {
151  anyActive = true;
152  break;
153  }
154  }
155 
156  setSelectionPropertiesActive(anyActive);
157  });
158  }
159 
160  // Store the reaction
161  m_reactions[mode] = reaction;
162 
164  QString("[cvSelectionToolController] Registered action for "
165  "mode %1")
166  .arg(static_cast<int>(mode)));
167 
168  return reaction;
169 }
170 
171 //-----------------------------------------------------------------------------
173  QAction* subtractAction,
174  QAction* toggleAction) {
175  m_addAction = addAction;
176  m_subtractAction = subtractAction;
177  m_toggleAction = toggleAction;
178 
179  // Create action group for mutual exclusivity
180  // Reference: pqStandardViewFrameActionsImplementation.cxx lines 266-285
181  m_modifierGroup = new QActionGroup(this);
182  // Set non-exclusive to allow unchecking by clicking on the same button
183  // We manually manage exclusivity in onModifierChanged
184  m_modifierGroup->setExclusive(false);
185 
186  if (addAction) {
187  addAction->setCheckable(true);
188  addAction->setData(
189  static_cast<int>(SelectionModifier::SELECTION_ADDITION));
190  m_modifierGroup->addAction(addAction);
191  }
192  if (subtractAction) {
193  subtractAction->setCheckable(true);
194  subtractAction->setData(
195  static_cast<int>(SelectionModifier::SELECTION_SUBTRACTION));
196  m_modifierGroup->addAction(subtractAction);
197  }
198  if (toggleAction) {
199  toggleAction->setCheckable(true);
200  toggleAction->setData(
201  static_cast<int>(SelectionModifier::SELECTION_TOGGLE));
202  m_modifierGroup->addAction(toggleAction);
203  }
204 
205  // Connect modifier changes using triggered signal
206  // Reference: pqStandardViewFrameActionsImplementation.cxx lines 284-285
207  connect(m_modifierGroup, &QActionGroup::triggered, this,
209 
211  "[cvSelectionToolController] Registered modifier actions");
212 }
213 
214 //-----------------------------------------------------------------------------
216  QAction* growAction, QAction* shrinkAction, QAction* clearAction) {
217  m_growAction = growAction;
218  m_shrinkAction = shrinkAction;
219  m_clearAction = clearAction;
220 
221  // Register these with new architecture reactions
222  // These are non-checkable instant actions, handled properly by
223  // cvRenderViewSelectionReaction
224  if (growAction) {
226  }
227  if (shrinkAction) {
229  }
230  if (clearAction) {
232  }
233 
235  "[cvSelectionToolController] Registered manipulation actions "
236  "(using new architecture)");
237 }
238 
239 //-----------------------------------------------------------------------------
242  // Disable all reactions
243  for (auto reaction : m_reactions) {
244  if (reaction && reaction != except && reaction->isActive()) {
245  if (reaction->parentAction() &&
246  reaction->parentAction()->isCheckable()) {
247  reaction->parentAction()->blockSignals(true);
248  reaction->parentAction()->setChecked(false);
249  reaction->parentAction()->blockSignals(false);
250  }
251  }
252  }
253 
254  // End any active reaction
256 
257  // Update selection properties panel
258  if (except == nullptr) {
260  }
261 
262  emit selectionToolStateChanged(except != nullptr);
263 }
264 
265 //-----------------------------------------------------------------------------
268 }
269 
270 //-----------------------------------------------------------------------------
273  if (m_manager) {
274  return m_manager->getCurrentMode();
275  }
276  return static_cast<SelectionMode>(-1);
277 }
278 
279 //-----------------------------------------------------------------------------
281  if (!isAnyToolActive()) {
282  return false;
283  }
284 
285  // Disable all selection tools via the reaction system
286  disableAllTools(nullptr);
287 
288  // Also explicitly uncheck all actions to keep UI synchronized
289  auto uncheckAction = [](QAction* action) {
290  if (action && action->isCheckable() && action->isChecked()) {
291  action->blockSignals(true);
292  action->setChecked(false);
293  action->blockSignals(false);
294  }
295  };
296 
297  uncheckAction(m_actions.selectSurfaceCells);
298  uncheckAction(m_actions.selectSurfacePoints);
299  uncheckAction(m_actions.selectFrustumCells);
300  uncheckAction(m_actions.selectFrustumPoints);
301  uncheckAction(m_actions.selectPolygonCells);
302  uncheckAction(m_actions.selectPolygonPoints);
303  uncheckAction(m_actions.selectBlocks);
304  uncheckAction(m_actions.selectFrustumBlocks);
305  uncheckAction(m_actions.interactiveSelectCells);
306  uncheckAction(m_actions.interactiveSelectPoints);
307  uncheckAction(m_actions.hoverCells);
308  uncheckAction(m_actions.hoverPoints);
309  uncheckAction(m_actions.zoomToBox);
310 
312  "[cvSelectionToolController] ESC key handled - disabled all "
313  "selection tools");
314  return true;
315 }
316 
317 //-----------------------------------------------------------------------------
319  if (m_manager) {
320  return m_manager->getHighlighter();
321  }
322  return nullptr;
323 }
324 
325 //-----------------------------------------------------------------------------
326 // history() removed - UI not implemented
327 
328 //-----------------------------------------------------------------------------
330  if (m_selectionToolsActive != active) {
331  m_selectionToolsActive = active;
332  emit selectionToolStateChanged(active);
333  }
334 }
335 
336 //-----------------------------------------------------------------------------
338  const cvSelectionData& selectionData) {
339  // CRITICAL: Block manager's signal to prevent double emission of
340  // selectionFinished The manager's selectionChanged is connected to emit
341  // selectionFinished, and we're already inside onSelectionFinished called
342  // from reaction's emit. This prevents:
343  // reaction->selectionFinished->here->manager->selectionChanged->selectionFinished(again!)
344  if (m_manager) {
345  m_manager->blockSignals(true);
346  m_manager->setCurrentSelection(selectionData);
347  m_manager->blockSignals(false);
348  }
349 
350  // Update manipulation action states
351  bool hasSelection = !selectionData.isEmpty();
352  if (m_growAction) {
353  m_growAction->setEnabled(hasSelection);
354  }
355  if (m_shrinkAction) {
356  m_shrinkAction->setEnabled(hasSelection);
357  }
358  if (m_clearAction) {
359  m_clearAction->setEnabled(hasSelection);
360  }
361 
362  // Update selection properties widget via signal
363  if (m_selectionToolsActive) {
364  emit selectionPropertiesUpdateRequested(selectionData);
365  }
366 
367  // Emit signal
368  emit selectionFinished(selectionData);
369 
371  QString("[cvSelectionToolController] Selection finished: %1 %2")
372  .arg(selectionData.count())
373  .arg(selectionData.fieldTypeString()));
374 }
375 
376 // undoSelection/redoSelection removed - UI not implemented
377 
378 //-----------------------------------------------------------------------------
380  // Reference:
381  // pqStandardViewFrameActionsImplementation::manageGroupExclusivity() lines
382  // 851-866
383  if (!action) {
384  return;
385  }
386 
387  // Handle non-checkable actions (shouldn't happen but be safe)
388  if (!action->isCheckable()) {
389  return;
390  }
391 
392  // Implement ParaView-style group exclusivity management
393  // When an action is checked, uncheck all others in the group
394  // When an action is unchecked (by clicking it again), revert to default
395  if (action->isChecked()) {
396  // Manually uncheck other actions in the group
397  // This is the key to ParaView's "manageGroupExclusivity" behavior
398  if (m_modifierGroup) {
399  for (QAction* groupAction : m_modifierGroup->actions()) {
400  if (groupAction != action && groupAction->isChecked()) {
401  groupAction->blockSignals(true);
402  groupAction->setChecked(false);
403  groupAction->blockSignals(false);
404  }
405  }
406  }
407 
408  // Set the modifier
409  if (m_manager) {
410  QVariant data = action->data();
411  if (data.isValid()) {
412  m_manager->setSelectionModifier(
413  static_cast<SelectionModifier>(data.toInt()));
414 
415  QString modeName;
416  switch (data.toInt()) {
417  case static_cast<int>(
419  modeName = "ADD (Ctrl)";
420  break;
421  case static_cast<int>(
423  modeName = "SUBTRACT (Shift)";
424  break;
425  case static_cast<int>(SelectionModifier::SELECTION_TOGGLE):
426  modeName = "TOGGLE (Ctrl+Shift)";
427  break;
428  default:
429  modeName = "DEFAULT";
430  break;
431  }
433  QString("[cvSelectionToolController] Selection "
434  "modifier: %1")
435  .arg(modeName));
436  }
437  }
438  } else {
439  // Action was unchecked - revert to default
440  if (m_manager) {
441  m_manager->setSelectionModifier(
444  "[cvSelectionToolController] Selection modifier: DEFAULT");
445  }
446  }
447 }
448 
449 //-----------------------------------------------------------------------------
450 // Note: onTooltipSettingsChanged has been removed as tooltip settings
451 // are now managed through cvSelectionLabelPropertiesDialog
452 
453 //-----------------------------------------------------------------------------
455 #ifdef USE_PCL_BACKEND
456  return true;
457 #else
458  return false;
459 #endif
460 }
461 
462 //-----------------------------------------------------------------------------
464  m_actions = actions;
465 
466  // Register selection modifier actions first (used by other reactions)
467  if (actions.addSelection || actions.subtractSelection ||
468  actions.toggleSelection) {
470  actions.toggleSelection);
471  }
472 
473  // NOTE: Using registerAction() for simplified ParaView-aligned architecture
474  // cvRenderViewSelectionReaction handles all selection logic directly
475  // without the intermediate cvRenderViewSelectionTool layer
476 
477  // Register surface selection actions
478  if (actions.selectSurfaceCells) {
481  }
482  if (actions.selectSurfacePoints) {
485  }
486 
487  // Register frustum selection actions
488  if (actions.selectFrustumCells) {
491  }
492  if (actions.selectFrustumPoints) {
495  }
496 
497  // Register polygon selection actions
498  if (actions.selectPolygonCells) {
501  }
502  if (actions.selectPolygonPoints) {
505  }
506 
507  // Register block selection actions
508  if (actions.selectBlocks) {
510  }
511  if (actions.selectFrustumBlocks) {
514  }
515 
516  // Register interactive selection actions
517  if (actions.interactiveSelectCells) {
520  }
521  if (actions.interactiveSelectPoints) {
524  }
525 
526  // Register hover/tooltip actions
527  if (actions.hoverCells) {
529  }
530  if (actions.hoverPoints) {
531  registerAction(actions.hoverPoints,
533  }
534 
535  // Register zoom to box action
536  if (actions.zoomToBox) {
538  }
539 
540  // Register manipulation actions (still using old API as they are simple)
542  actions.clearSelection);
543 
545  "[cvSelectionToolController] All selection actions registered "
546  "(using new ParaView-aligned architecture)");
547 }
548 
549 //-----------------------------------------------------------------------------
551  // Highlighter is now managed by cvViewSelectionManager
552  // This method is kept for compatibility but does nothing
554  "[cvSelectionToolController] Highlighter connection established "
555  "via manager");
556 }
557 
558 //-----------------------------------------------------------------------------
560  if (m_manager) {
561  // Invalidate cached selection in the pipeline
562  cvSelectionPipeline* pipeline = m_manager->getPipeline();
563  if (pipeline) {
564  pipeline->invalidateCachedSelection();
566  "[cvSelectionToolController] Selection cache invalidated "
567  "(scene content changed)");
568  }
569 
570  // Clear source object since scene changed
571  m_manager->setSourceObject(nullptr);
572  }
573 }
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool PrintVerbose(const char *format,...)
Prints out a verbose formatted message in console.
Definition: CVLog.cpp:103
cvRenderViewSelectionReaction handles all selection modes in a single class
static cvRenderViewSelectionReaction * activeReaction()
Get the currently active reaction (static)
void zoomToBoxCompleted(int xmin, int ymin, int xmax, int ymax)
Emitted when zoom to box is completed.
void selectionFinished(const cvSelectionData &selectionData)
Emitted when selection is finished.
bool isActive() const
Check if this reaction's selection is currently active.
void setVisualizer(ecvGenericVisualizer3D *viewer)
Set the visualizer for selection operations.
static void endActiveSelection()
Force end any active selection.
ecvGenericVisualizer3D * getVisualizer() const
Get the visualizer instance.
Encapsulates selection data without exposing VTK types.
QString fieldTypeString() const
Get human-readable field type string.
bool isEmpty() const
Check if selection is empty.
int count() const
Get number of selected items.
Helper class for highlighting selected elements in the visualizer.
Selection pipeline abstraction layer.
void invalidateCachedSelection()
Clear selection cache and invalidate cached buffers.
Controller class that manages all selection tools and their UI.
SelectionMode currentMode() const
Get the current active selection mode.
cvRenderViewSelectionReaction * registerAction(QAction *action, SelectionMode mode)
Register an action for a selection mode.
void initialize(QWidget *parent)
Initialize the controller with UI elements.
void selectionPropertiesUpdateRequested(const cvSelectionData &selectionData)
Emitted when selection properties should be updated Connect this to ecvPropertiesTreeDelegate::update...
void disableAllTools(cvRenderViewSelectionReaction *except=nullptr)
Disable all selection tools.
void connectHighlighter()
Connect highlighter for visual feedback.
void setVisualizer(ecvGenericVisualizer3D *viewer)
Set the visualizer for all selection operations.
bool handleEscapeKey()
Handle ESC key to exit selection tools.
void onModifierChanged(QAction *action)
Handle modifier action changes.
void setupActions(UiType *ui)
Setup all selection actions from a UI struct.
void registerManipulationActions(QAction *growAction, QAction *shrinkAction, QAction *clearAction)
Register grow/shrink/clear actions.
void invalidateCache()
Invalidate cached selection data.
static cvSelectionToolController * instance()
Get the singleton instance.
static bool isPCLBackendAvailable()
Check if PCL backend is available.
void zoomToBoxRequested(int xmin, int ymin, int xmax, int ymax)
Emitted for zoom to box requests.
void selectionHistoryChanged()
Emitted when selection history changes.
void selectionFinished(const cvSelectionData &selectionData)
Emitted when a selection operation is completed.
void setSelectionPropertiesActive(bool active)
Update selection properties widget state.
void registerModifierActions(QAction *addAction, QAction *subtractAction, QAction *toggleAction)
Register the selection modifier action group.
cvSelectionHighlighter * highlighter() const
Get the selection highlighter.
bool isAnyToolActive() const
Check if any selection tool is active.
void onSelectionFinished(const cvSelectionData &selectionData)
Handle selection finished from any tool.
void selectionToolStateChanged(bool anyToolActive)
Emitted when selection tool state changes Connect this to ecvPropertiesTreeDelegate::setSelectionTool...
Central manager for all selection operations in the view.
void setCurrentSelection(const cvSelectionData &selectionData, bool resetLayers=true)
Set the current selection data.
void setSelectionModifier(SelectionModifier modifier)
Set the selection modifier.
cvSelectionPipeline * getPipeline()
Get the selection pipeline.
void setVisualizer(ecvGenericVisualizer3D *viewer) override
Set the visualizer for all selection operations.
void setSourceObject(ccHObject *obj)
Set the source object for selection operations.
cvSelectionHighlighter * getHighlighter()
Get the shared highlighter.
SelectionMode getCurrentMode() const
Get the current active selection mode.
void selectionChanged()
Emitted when the selection has changed (legacy - for backward compatibility)
Generic visualizer 3D interface.
Simplified selection reaction class aligned with ParaView architecture.
@ SELECT_BLOCKS
Select blocks (rectangle)
@ SELECT_FRUSTUM_BLOCKS
Select blocks in frustum.
@ SELECT_FRUSTUM_POINTS
Select points in frustum.
@ SELECT_SURFACE_POINTS
Select points on surface (rectangle)
@ HOVER_CELLS_TOOLTIP
Show cell data tooltip on hover (read-only)
@ SELECT_SURFACE_POINTS_POLYGON
Select points on surface (polygon)
@ GROW_SELECTION
Expand selection by one layer.
@ SHRINK_SELECTION
Shrink selection by one layer.
@ SELECT_SURFACE_POINTS_INTERACTIVELY
@ SELECT_SURFACE_CELLS
Select cells on surface (rectangle)
@ SELECT_SURFACE_CELLS_INTERACTIVELY
@ HOVER_POINTS_TOOLTIP
Show point data tooltip on hover (read-only)
@ ZOOM_TO_BOX
Zoom to box region.
@ SELECT_SURFACE_CELLS_POLYGON
Select cells on surface (polygon)
@ SELECT_FRUSTUM_CELLS
Select cells in frustum.
@ CLEAR_SELECTION
Clear current selection.
@ SELECTION_TOGGLE
Toggle selection (Ctrl+Shift)
@ SELECTION_DEFAULT
Replace selection (default)
@ SELECTION_ADDITION
Add to selection (Ctrl)
@ SELECTION_SUBTRACTION
Subtract from selection (Shift)
GraphType data
Definition: graph_cut.cc:138
Structure to hold all selection action pointers.