ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
cvConstrainedPolyLineWidget.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 
10 #include <vtkCallbackCommand.h>
11 #include <vtkCommand.h>
12 #include <vtkObjectFactory.h>
13 #include <vtkPolyLineRepresentation.h>
14 #include <vtkRenderWindowInteractor.h>
15 
16 #include <algorithm>
17 #include <cctype>
18 
20 
22  : KeyEventCallbackCommand(nullptr) {
23  // Don't initialize KeyEventCallbackCommand here - wait for SetEnabled()
24  // This avoids accessing parent class members before they're fully
25  // initialized
26 }
27 
29  // CRITICAL: Remove observers BEFORE deleting callback command
30  // This prevents callbacks from being triggered during/after destruction
31  if (this->Interactor && this->KeyEventCallbackCommand) {
32  this->Interactor->RemoveObserver(this->KeyEventCallbackCommand);
33  }
34 
35  // Cleanup our callback command if we created one
36  if (this->KeyEventCallbackCommand) {
37  this->KeyEventCallbackCommand->Delete();
38  this->KeyEventCallbackCommand = nullptr;
39  }
40 }
41 
42 // From ParaView vtkPolyLineWidget::SetEnabled pattern
44  vtkRenderWindowInteractor* iren) {
45  // Call parent class first
46  this->Superclass::SetInteractor(iren);
47 }
48 
50  // CRITICAL: We need to register our custom keyboard handler BEFORE calling
51  // parent SetEnabled(), otherwise the parent class (vtkPolyLineWidget) will
52  // register its own keyboard handler first, which may conflict with ours.
53 
54  if (enabling && this->Interactor) {
55  // Create our custom keyboard event handler if not already created
56  if (!this->KeyEventCallbackCommand) {
57  this->KeyEventCallbackCommand = vtkCallbackCommand::New();
58  this->KeyEventCallbackCommand->SetClientData(this);
59  this->KeyEventCallbackCommand->SetCallback(
61  }
62 
63  // Register keyboard event observers with HIGH priority (10.0) BEFORE
64  // parent SetEnabled This ensures our handler is called before the
65  // parent class's handler, preventing conflicts and crashes
66  this->Interactor->AddObserver(vtkCommand::KeyPressEvent,
68  10.0); // High priority
69  this->Interactor->AddObserver(vtkCommand::KeyReleaseEvent,
71  10.0); // High priority
72  }
73 
74  // Call parent SetEnabled (this does necessary initialization for widget
75  // interaction)
76  this->Superclass::SetEnabled(enabling);
77 
78  if (enabling && this->Interactor && this->KeyEventCallbackCommand) {
79  // CRITICAL: After parent SetEnabled, parent class (vtkPolyLineWidget)
80  // has registered its own keyboard observers. We need to remove ALL
81  // keyboard observers and keep only ours.
82 
83  // Remove ALL keyboard observers (including parent's problematic ones)
84  this->Interactor->RemoveObservers(vtkCommand::KeyPressEvent);
85  this->Interactor->RemoveObservers(vtkCommand::KeyReleaseEvent);
86 
87  // Re-add ONLY our observers with high priority
88  this->Interactor->AddObserver(vtkCommand::KeyPressEvent,
89  this->KeyEventCallbackCommand, 10.0);
90  this->Interactor->AddObserver(vtkCommand::KeyReleaseEvent,
91  this->KeyEventCallbackCommand, 10.0);
92  }
93 
94  if (!enabling && this->Interactor) {
95  // Remove our custom keyboard event observers
96  if (this->KeyEventCallbackCommand) {
97  this->Interactor->RemoveObserver(this->KeyEventCallbackCommand);
98  }
99  // Note: Parent class observers are cleaned up by parent's SetEnabled(0)
100  // Our ProcessKeyEvents has safety checks (GetEnabled, GetProcessEvents)
101  // to prevent crashes even if called after disable
102  }
103 }
104 
105 // Keyboard event handling - directly copied from ParaView
106 // vtkPolyLineWidget::ProcessKeyEvents This is the core method of the control
107 // layer:
108 // 1. Receives keyboard events
109 // 2. Gets the representation
110 // 3. Sets the constraint status on the entire representation
112  unsigned long event,
113  void* clientdata,
114  void*) {
116  static_cast<cvConstrainedPolyLineWidget*>(clientdata);
117 
118  // CRITICAL Safety checks: ensure widget is in valid state
119  // This prevents crashes when callbacks are triggered during/after
120  // destruction
121  if (!self) {
122  return; // Widget destroyed
123  }
124 
125  // Extra safety: check if widget is being destroyed
126  try {
127  // Try to access a member - if widget is deleted, this may throw or
128  // crash
129  if (!self->GetEnabled()) {
130  return; // Widget disabled - don't process any events
131  }
132  } catch (...) {
133  return; // Widget in invalid state
134  }
135 
136  if (!self->Interactor || !self->WidgetRep) {
137  return; // Widget not fully initialized or already destroyed
138  }
139 
140  if (!self->GetProcessEvents()) {
141  return; // Widget locked - don't process events
142  }
143 
144  vtkPolyLineRepresentation* rep =
145  vtkPolyLineRepresentation::SafeDownCast(self->WidgetRep);
146  if (!rep) {
147  return;
148  }
149 
150  // CRITICAL: Safe access to Interactor - may be null during destruction
151  char* cKeySym = nullptr;
152  try {
153  if (self->Interactor) {
154  cKeySym = self->Interactor->GetKeySym();
155  }
156  } catch (...) {
157  return; // Interactor in invalid state
158  }
159 
160  if (!cKeySym) {
161  return; // No key symbol available
162  }
163 
164  std::string keySym = cKeySym;
165  std::transform(keySym.begin(), keySym.end(), keySym.begin(), ::toupper);
166 
167  // Only handle X, Y, Z keys - ignore all other keys to prevent conflicts
168  // with application shortcuts (like 'P' for picking)
169  bool isConstraintKey = (keySym == "X" || keySym == "Y" || keySym == "Z");
170  if (!isConstraintKey) {
171  return; // Let other keys be handled by application shortcuts
172  }
173 
174  // ParaView vtkPolyLineWidget implementation (vtkPolyLineWidget.cxx:279-300)
175  if (event == vtkCommand::KeyPressEvent) {
176  if (keySym == "X") {
177  // Constrain entire representation to X-axis
178  rep->SetXTranslationAxisOn();
179  } else if (keySym == "Y") {
180  // Constrain entire representation to Y-axis
181  rep->SetYTranslationAxisOn();
182  } else if (keySym == "Z") {
183  // Constrain entire representation to Z-axis
184  rep->SetZTranslationAxisOn();
185  }
186  } else if (event == vtkCommand::KeyReleaseEvent) {
187  if (keySym == "X" || keySym == "Y" || keySym == "Z") {
188  // Release all constraints
189  rep->SetTranslationAxisOff();
190  }
191  }
192 }
MouseEvent event
PolyLine Widget with XYZ constraints (100% consistent with ParaView)
void SetEnabled(int enabling) override
Override SetEnabled to register our custom keyboard handler.
vtkCallbackCommand * KeyEventCallbackCommand
static void ProcessKeyEvents(vtkObject *object, unsigned long event, void *clientdata, void *calldata)
Keyboard event handling - directly copied from ParaView vtkPolyLineWidget::ProcessKeyEvents.
void SetInteractor(vtkRenderWindowInteractor *iren) override
Override SetInteractor to register keyboard event observers.
vtkStandardNewMacro(cvConstrainedPolyLineWidget)