1- using System . Collections . Generic ;
2- using System . Diagnostics ;
1+ using System . Diagnostics ;
32using System . Windows ;
43using System . Windows . Controls ;
54using System . Windows . Input ;
@@ -61,6 +60,7 @@ public event ConnectorEventHandler Disconnect
6160 public static readonly DependencyProperty DisconnectCommandProperty = DependencyProperty . Register ( nameof ( DisconnectCommand ) , typeof ( ICommand ) , typeof ( Connector ) ) ;
6261 private static readonly DependencyPropertyKey IsPendingConnectionPropertyKey = DependencyProperty . RegisterReadOnly ( nameof ( IsPendingConnection ) , typeof ( bool ) , typeof ( Connector ) , new FrameworkPropertyMetadata ( BoxValue . False ) ) ;
6362 public static readonly DependencyProperty IsPendingConnectionProperty = IsPendingConnectionPropertyKey . DependencyProperty ;
63+ public static readonly DependencyProperty HasCustomContextMenuProperty = NodifyEditor . HasCustomContextMenuProperty . AddOwner ( typeof ( Connector ) ) ;
6464
6565 /// <summary>
6666 /// Gets the location in graph space coordinates where <see cref="Connection"/>s can be attached to.
@@ -100,18 +100,22 @@ public ICommand? DisconnectCommand
100100 set => SetValue ( DisconnectCommandProperty , value ) ;
101101 }
102102
103- #endregion
104-
105- static Connector ( )
103+ /// <summary>
104+ /// Gets or sets a value indicating whether the connector uses a custom context menu.
105+ /// </summary>
106+ /// <remarks>When set to true, the connector handles the right-click event for specific operations.</remarks>
107+ public bool HasCustomContextMenu
106108 {
107- DefaultStyleKeyProperty . OverrideMetadata ( typeof ( Connector ) , new FrameworkPropertyMetadata ( typeof ( Connector ) ) ) ;
108- FocusableProperty . OverrideMetadata ( typeof ( Connector ) , new FrameworkPropertyMetadata ( BoxValue . True ) ) ;
109+ get => ( bool ) GetValue ( HasCustomContextMenuProperty ) ;
110+ set => SetValue ( HasCustomContextMenuProperty , value ) ;
109111 }
110112
111- public Connector ( )
112- {
113- _states . Push ( GetInitialState ( ) ) ;
114- }
113+ /// <summary>
114+ /// Gets a value indicating whether the connector has a context menu.
115+ /// </summary>
116+ public bool HasContextMenu => ContextMenu != null || HasCustomContextMenu ;
117+
118+ #endregion
115119
116120 #region Fields
117121
@@ -162,6 +166,19 @@ public Connector()
162166
163167 #endregion
164168
169+ static Connector ( )
170+ {
171+ DefaultStyleKeyProperty . OverrideMetadata ( typeof ( Connector ) , new FrameworkPropertyMetadata ( typeof ( Connector ) ) ) ;
172+ FocusableProperty . OverrideMetadata ( typeof ( Connector ) , new FrameworkPropertyMetadata ( BoxValue . True ) ) ;
173+ }
174+
175+ public Connector ( )
176+ {
177+ InputProcessor . AddHandler ( new ConnectorDisconnectState ( this ) ) ;
178+ InputProcessor . AddHandler ( new ConnectorConnectingState ( this ) ) ;
179+ InputProcessor . AddHandler ( new ConnectorDefaultState ( this ) ) ;
180+ }
181+
165182 /// <inheritdoc />
166183 public override void OnApplyTemplate ( )
167184 {
@@ -314,68 +331,18 @@ public void UpdateAnchor()
314331
315332 #endregion
316333
317- #region State Handling
318-
319- private readonly Stack < ConnectorState > _states = new Stack < ConnectorState > ( ) ;
320-
321- /// <summary>The current state of the connector.</summary>
322- public ConnectorState State => _states . Peek ( ) ;
323-
324- /// <summary>Creates the initial state of the connector.</summary>
325- /// <returns>The initial state.</returns>
326- protected virtual ConnectorState GetInitialState ( )
327- => new ConnectorDefaultState ( this ) ;
328-
329- /// <summary>Pushes the given state to the stack.</summary>
330- /// <param name="state">The new state of the connector.</param>
331- /// <remarks>Calls <see cref="ConnectorState.Enter"/> on the new state.</remarks>
332- public void PushState ( ConnectorState state )
333- {
334- var prev = State ;
335- _states . Push ( state ) ;
336- state . Enter ( prev ) ;
337- }
338-
339- /// <summary>Pops the current <see cref="State"/> from the stack.</summary>
340- /// <remarks>It doesn't pop the initial state. (see <see cref="GetInitialState"/>)
341- /// <br />Calls <see cref="ConnectorState.Exit"/> on the current state.
342- /// <br />Calls <see cref="ConnectorState.ReEnter"/> on the previous state.
343- /// </remarks>
344- public void PopState ( )
345- {
346- // Never remove the default state
347- if ( _states . Count > 1 )
348- {
349- ConnectorState prev = _states . Pop ( ) ;
350- prev . Exit ( ) ;
351- State . ReEnter ( prev ) ;
352- }
353- }
334+ #region Gesture Handling
354335
355- /// <summary>Pops all states from the connector.</summary>
356- /// <remarks>It doesn't pop the initial state. (see <see cref="GetInitialState"/>)</remarks>
357- public void PopAllStates ( )
358- {
359- while ( _states . Count > 1 )
360- {
361- PopState ( ) ;
362- }
363- }
336+ protected InputProcessor InputProcessor { get ; } = new InputProcessor { ProcessHandledEvents = true } ;
364337
365338 /// <inheritdoc />
366339 protected override void OnMouseDown ( MouseButtonEventArgs e )
367- {
368- Focus ( ) ;
369-
370- this . CaptureMouseSafe ( ) ;
371-
372- State . HandleMouseDown ( e ) ;
373- }
340+ => InputProcessor . Process ( e ) ;
374341
375342 /// <inheritdoc />
376343 protected override void OnMouseUp ( MouseButtonEventArgs e )
377344 {
378- State . HandleMouseUp ( e ) ;
345+ InputProcessor . Process ( e ) ;
379346
380347 // Release the mouse capture if all the mouse buttons are released and there's no sticky connection pending
381348 if ( ! IsPendingConnection && IsMouseCaptured && e . RightButton == MouseButtonState . Released && e . LeftButton == MouseButtonState . Released && e . MiddleButton == MouseButtonState . Released )
@@ -386,21 +353,38 @@ protected override void OnMouseUp(MouseButtonEventArgs e)
386353
387354 /// <inheritdoc />
388355 protected override void OnMouseMove ( MouseEventArgs e )
389- => State . HandleMouseMove ( e ) ;
356+ => InputProcessor . Process ( e ) ;
390357
391358 /// <inheritdoc />
392359 protected override void OnMouseWheel ( MouseWheelEventArgs e )
393- => State . HandleMouseWheel ( e ) ;
360+ => InputProcessor . Process ( e ) ;
394361
395362 /// <inheritdoc />
396363 protected override void OnLostMouseCapture ( MouseEventArgs e )
397- => PopAllStates ( ) ;
364+ => InputProcessor . Process ( e ) ;
398365
366+ /// <inheritdoc />
399367 protected override void OnKeyUp ( KeyEventArgs e )
400- => State . HandleKeyUp ( e ) ;
368+ {
369+ InputProcessor . Process ( e ) ;
401370
371+ if ( ! IsPendingConnection && IsMouseCaptured )
372+ {
373+ ReleaseMouseCapture ( ) ;
374+ }
375+ }
376+
377+ /// <inheritdoc />
402378 protected override void OnKeyDown ( KeyEventArgs e )
403- => State . HandleKeyDown ( e ) ;
379+ {
380+ InputProcessor . Process ( e ) ;
381+
382+ // Release the mouse capture if all the mouse buttons are released and there's no sticky connection pending
383+ if ( ! IsPendingConnection && IsMouseCaptured && Mouse . RightButton == MouseButtonState . Released && Mouse . LeftButton == MouseButtonState . Released && Mouse . MiddleButton == MouseButtonState . Released )
384+ {
385+ ReleaseMouseCapture ( ) ;
386+ }
387+ }
404388
405389 #endregion
406390
@@ -468,26 +452,31 @@ public void UpdatePendingConnection(Point position)
468452 }
469453
470454 /// <summary>
471- /// Cancels the current pending connection without completing it.
455+ /// Cancels the current pending connection without completing it if <see cref="AllowPendingConnectionCancellation"/> is true.
456+ /// Otherwise, it completes the pending connection by calling <see cref="EndConnecting()"/>.
472457 /// </summary>
473458 /// <remarks>This method has no effect if there's no pending connection.</remarks>
474459 public void CancelConnecting ( )
475460 {
476- if ( ! IsPendingConnection )
461+ if ( ! AllowPendingConnectionCancellation )
477462 {
463+ EndConnecting ( ) ;
478464 return ;
479465 }
480466
481- var args = new PendingConnectionEventArgs ( DataContext )
467+ if ( IsPendingConnection )
482468 {
483- RoutedEvent = PendingConnectionCompletedEvent ,
484- Anchor = Anchor ,
485- Source = this ,
486- Canceled = true
487- } ;
488- RaiseEvent ( args ) ;
489-
490- IsPendingConnection = false ;
469+ var args = new PendingConnectionEventArgs ( DataContext )
470+ {
471+ RoutedEvent = PendingConnectionCompletedEvent ,
472+ Anchor = Anchor ,
473+ Source = this ,
474+ Canceled = true
475+ } ;
476+ RaiseEvent ( args ) ;
477+
478+ IsPendingConnection = false ;
479+ }
491480 }
492481
493482 /// <summary>
0 commit comments