Project

General

Profile

Download (18.9 KB) Statistics
| Branch: | Tag: | Revision:
1
/*******************************************************************************
2
 * Copyright (c) 2000, 2010 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 * IBM Corporation - initial API and implementation
10
 *******************************************************************************/
11
package org.eclipse.draw2d;
12

    
13
import org.eclipse.swt.accessibility.AccessibleControlEvent;
14
import org.eclipse.swt.accessibility.AccessibleControlListener;
15
import org.eclipse.swt.accessibility.AccessibleEvent;
16
import org.eclipse.swt.accessibility.AccessibleListener;
17
import org.eclipse.swt.dnd.DND;
18
import org.eclipse.swt.dnd.DragSource;
19
import org.eclipse.swt.dnd.DragSourceEvent;
20
import org.eclipse.swt.dnd.DragSourceListener;
21
import org.eclipse.swt.events.ControlAdapter;
22
import org.eclipse.swt.events.ControlEvent;
23
import org.eclipse.swt.events.DisposeEvent;
24
import org.eclipse.swt.events.DisposeListener;
25
import org.eclipse.swt.events.FocusEvent;
26
import org.eclipse.swt.events.FocusListener;
27
import org.eclipse.swt.events.KeyEvent;
28
import org.eclipse.swt.events.KeyListener;
29
import org.eclipse.swt.events.MouseEvent;
30
import org.eclipse.swt.events.MouseListener;
31
import org.eclipse.swt.events.MouseMoveListener;
32
import org.eclipse.swt.events.MouseTrackListener;
33
import org.eclipse.swt.events.PaintEvent;
34
import org.eclipse.swt.events.PaintListener;
35
import org.eclipse.swt.events.TraverseEvent;
36
import org.eclipse.swt.events.TraverseListener;
37
import org.eclipse.swt.graphics.Color;
38
import org.eclipse.swt.graphics.Font;
39
import org.eclipse.swt.graphics.GC;
40
import org.eclipse.swt.graphics.Point;
41
import org.eclipse.swt.widgets.Canvas;
42
import org.eclipse.swt.widgets.Display;
43
import org.eclipse.swt.widgets.Event;
44
import org.eclipse.swt.widgets.Listener;
45
import org.eclipse.swt.widgets.Widget;
46

    
47
import org.eclipse.core.runtime.IProgressMonitor;
48
import org.eclipse.core.runtime.IStatus;
49
import org.eclipse.core.runtime.Status;
50
import org.eclipse.core.runtime.jobs.Job;
51
import org.eclipse.ui.presentations.PresentationUtil;
52

    
53
import org.eclipse.draw2d.geometry.Rectangle;
54
import org.eclipse.draw2d.rap.swt.SWT;
55

    
56
/**
57
 * The LightweightSystem is the link between SWT and Draw2d. It is the component
58
 * that provides the ability for {@link Figure Figures} to be hosted on an SWT
59
 * Canvas.
60
 * <p>
61
 * Normal procedure for using a LightweightSystem:
62
 * <ol>
63
 * <li>Create an SWT Canvas.
64
 * <li>Create a LightweightSystem passing it that Canvas.
65
 * <li>Create a Draw2d Figure and call setContents(IFigure). This Figure will be
66
 * the top-level Figure of the Draw2d application.
67
 * </ol>
68
 */
69
public class LightweightSystem {
70

    
71
	private Canvas canvas;
72
	IFigure contents;
73
	private IFigure root;
74
	private EventDispatcher dispatcher;
75
	private UpdateManager manager = new DeferredUpdateManager();
76
	private int ignoreResize;
77
	private RAPDragTracker tracker;
78

    
79
	/**
80
	 * Constructs a LightweightSystem on Canvas <i>c</i>.
81
	 * 
82
	 * @param c
83
	 *            the canvas
84
	 * @since 2.0
85
	 */
86
	public LightweightSystem(Canvas c) {
87
		this();
88
		setControl(c);
89
	}
90

    
91
	/**
92
	 * Constructs a LightweightSystem <b>without</b> a Canvas.
93
	 */
94
	public LightweightSystem() {
95
		init();
96
	}
97

    
98
	/**
99
	 * Adds SWT listeners to the LightWeightSystem's Canvas. This allows for SWT
100
	 * events to be dispatched and handled by its {@link EventDispatcher}.
101
	 * <P>
102
	 * <EM>WARNING:</EM> This method should not be overridden.
103
	 * 
104
	 * @since 2.0
105
	 */
106
	protected void addListeners() {
107
		final EventHandler handler = createEventHandler();
108
		canvas.getAccessible().addAccessibleListener(handler);
109
		canvas.getAccessible().addAccessibleControlListener(handler);
110
		canvas.addMouseListener(handler);
111
		// UNSUPPORTED - api not implemented in RAP
112
		// canvas.addMouseMoveListener(handler);
113
		// canvas.addMouseTrackListener(handler);
114
		canvas.addKeyListener(handler);
115
		canvas.addTraverseListener(handler);
116
		canvas.addFocusListener(handler);
117
		canvas.addDisposeListener(handler);
118
		// HACK: for DND in RAP
119
		PresentationUtil.addDragListener(canvas, handler);
120
		DragSource dragSource = new DragSource(canvas, DND.DROP_MOVE
121
				| DND.DROP_COPY | DND.DROP_LINK);
122
		final DragSourceListener listener = new DragSourceListener() {
123

    
124
			public void dragStart(DragSourceEvent event) {
125
				tracker = new RAPDragTracker(handler, canvas);
126
				tracker.open();
127
			}
128

    
129
			public void dragSetData(DragSourceEvent event) {
130
			}
131

    
132
			public void dragFinished(DragSourceEvent event) {
133
				if (tracker != null) {
134
					tracker.close();
135
					tracker = null;
136
				}
137
			}
138
		};
139

    
140
		dragSource.addDragListener(listener);
141

    
142
		canvas.addListener(SWT.MouseWheel, handler);
143

    
144
		canvas.addControlListener(new ControlAdapter() {
145
			public void controlResized(ControlEvent e) {
146
				LightweightSystem.this.controlResized();
147
			}
148
		});
149
		canvas.addPaintListener(new PaintListener() {
150
			public void paintControl(PaintEvent event) {
151
				LightweightSystem.this.paint(event.gc);
152
			}
153
		});
154
	}
155

    
156
	/**
157
	 * Resizes and revalidates the root figure when the control is resized.
158
	 */
159
	protected void controlResized() {
160
		if (ignoreResize > 0)
161
			return;
162
		Rectangle r = new Rectangle(canvas.getClientArea());
163
		r.setLocation(0, 0);
164
		root.setBounds(r);
165
		root.revalidate();
166
		getUpdateManager().performUpdate();
167
	}
168

    
169
	/**
170
	 * Returns this LightwightSystem's EventDispatcher.
171
	 * 
172
	 * @return the event dispatcher
173
	 * @since 2.0
174
	 */
175
	protected EventDispatcher getEventDispatcher() {
176
		if (dispatcher == null)
177
			setEventDispatcher(new SWTEventDispatcher());
178
		return dispatcher;
179
	}
180

    
181
	/**
182
	 * Returns this LightweightSystem's root figure.
183
	 * 
184
	 * @return the root figure
185
	 * @since 2.0
186
	 */
187
	public IFigure getRootFigure() {
188
		return root;
189
	}
190

    
191
	/**
192
	 * Returns a new instance of this LightweightSystem's EventHandler.
193
	 * 
194
	 * @return the newly created event handler
195
	 * @since 2.0
196
	 */
197
	protected final EventHandler createEventHandler() {
198
		return internalCreateEventHandler();
199
	}
200

    
201
	/**
202
	 * Creates and returns the root figure.
203
	 * 
204
	 * @return the newly created root figure
205
	 */
206
	protected RootFigure createRootFigure() {
207
		RootFigure f = new RootFigure();
208
		f.addNotify();
209
		f.setOpaque(true);
210
		f.setLayoutManager(new StackLayout());
211
		return f;
212
	}
213

    
214
	/**
215
	 * Returns this LightweightSystem's UpdateManager.
216
	 * 
217
	 * @return the update manager
218
	 * @since 2.0
219
	 */
220
	public UpdateManager getUpdateManager() {
221
		return manager;
222
	}
223

    
224
	/**
225
	 * Initializes this LightweightSystem by setting the root figure.
226
	 */
227
	protected void init() {
228
		setRootPaneFigure(createRootFigure());
229
	}
230

    
231
	EventHandler internalCreateEventHandler() {
232
		return new EventHandler();
233
	}
234

    
235
	/**
236
	 * Invokes this LightweightSystem's {@link UpdateManager} to paint this
237
	 * LightweightSystem's Canvas and contents.
238
	 * 
239
	 * @param gc
240
	 *            the GC used for painting
241
	 * @since 2.0
242
	 */
243
	public void paint(GC gc) {
244
		getUpdateManager().paint(gc);
245
	}
246

    
247
	/**
248
	 * Sets the contents of the LightweightSystem to the passed figure. This
249
	 * figure should be the top-level Figure in a Draw2d application.
250
	 * 
251
	 * @param figure
252
	 *            the new root figure
253
	 * @since 2.0
254
	 */
255
	public void setContents(IFigure figure) {
256
		if (contents != null)
257
			root.remove(contents);
258
		contents = figure;
259
		root.add(contents);
260
	}
261

    
262
	/**
263
	 * Sets the LightweightSystem's control to the passed Canvas.
264
	 * 
265
	 * @param c
266
	 *            the canvas
267
	 * @since 2.0
268
	 */
269
	public void setControl(Canvas c) {
270
		if (canvas == c)
271
			return;
272
		canvas = c;
273
		if ((c.getStyle() & SWT.DOUBLE_BUFFERED) != 0)
274
			getUpdateManager().setGraphicsSource(
275
					new NativeGraphicsSource(canvas));
276
		else
277
			getUpdateManager().setGraphicsSource(
278
					new BufferedGraphicsSource(canvas));
279
		getEventDispatcher().setControl(c);
280
		addListeners();
281

    
282
		// Size the root figure and contents to the current control's size
283
		Rectangle r = new Rectangle(canvas.getClientArea());
284
		r.setLocation(0, 0);
285
		root.setBounds(r);
286
		root.revalidate();
287
	}
288

    
289
	/**
290
	 * Sets this LightweightSystem's EventDispatcher.
291
	 * 
292
	 * @param dispatcher
293
	 *            the new event dispatcher
294
	 * @since 2.0
295
	 */
296
	public void setEventDispatcher(EventDispatcher dispatcher) {
297
		this.dispatcher = dispatcher;
298
		dispatcher.setRoot(root);
299
		dispatcher.setControl(canvas);
300
	}
301

    
302
	void setIgnoreResize(boolean value) {
303
		if (value)
304
			ignoreResize++;
305
		else
306
			ignoreResize--;
307
	}
308

    
309
	/**
310
	 * Sets this LightweightSystem's root figure.
311
	 * 
312
	 * @param root
313
	 *            the new root figure
314
	 */
315
	protected void setRootPaneFigure(RootFigure root) {
316
		getUpdateManager().setRoot(root);
317
		this.root = root;
318
	}
319

    
320
	/**
321
	 * Sets this LightweightSystem's UpdateManager.
322
	 * 
323
	 * @param um
324
	 *            the new update manager
325
	 * @since 2.0
326
	 */
327
	public void setUpdateManager(UpdateManager um) {
328
		manager = um;
329
		manager.setRoot(root);
330
	}
331

    
332
	/**
333
	 * The figure at the root of the LightweightSystem. If certain properties
334
	 * (i.e. font, background/foreground color) are not set, the RootFigure will
335
	 * obtain these properties from LightweightSystem's Canvas.
336
	 */
337
	protected class RootFigure extends Figure {
338
		/** @see IFigure#getBackgroundColor() */
339
		public Color getBackgroundColor() {
340
			if (bgColor != null)
341
				return bgColor;
342
			if (canvas != null)
343
				return canvas.getBackground();
344
			return null;
345
		}
346

    
347
		/** @see IFigure#getFont() */
348
		public Font getFont() {
349
			if (font != null)
350
				return font;
351
			if (canvas != null)
352
				return canvas.getFont();
353
			return null;
354
		}
355

    
356
		/** @see IFigure#getForegroundColor() */
357
		public Color getForegroundColor() {
358
			if (fgColor != null)
359
				return fgColor;
360
			if (canvas != null)
361
				return canvas.getForeground();
362
			return null;
363
		}
364

    
365
		/** @see IFigure#getUpdateManager() */
366
		public UpdateManager getUpdateManager() {
367
			return LightweightSystem.this.getUpdateManager();
368
		}
369

    
370
		/** @see IFigure#internalGetEventDispatcher() */
371
		public EventDispatcher internalGetEventDispatcher() {
372
			return getEventDispatcher();
373
		}
374

    
375
		/**
376
		 * @see IFigure#isMirrored()
377
		 */
378
		public boolean isMirrored() {
379
			return (LightweightSystem.this.canvas.getStyle() & SWT.MIRRORED) != 0;
380
		}
381

    
382
		/** @see Figure#isShowing() */
383
		public boolean isShowing() {
384
			return true;
385
		}
386
	}
387

    
388
	protected class RAPDragTracker {
389
		public boolean cancelled;
390
		public boolean tracking;
391
		private final MouseMoveListener listener;
392
		private final Widget widget;
393

    
394
		public RAPDragTracker(final MouseMoveListener listener,
395
				final Widget widget) {
396
			this.listener = listener;
397
			this.widget = widget;
398
		}
399

    
400
		public void open() {
401
			Job dragJob = new Job("Drag-Job") {
402
				protected IStatus run(IProgressMonitor monitor) {
403
					// Run tracker until mouse up occurs or escape key pressed.
404
					final Display display = widget.getDisplay();
405
					cancelled = false;
406
					tracking = true;
407

    
408
					try {
409
						long timeout = 0;
410
						long refreshRate = 200;
411
						while (tracking && !cancelled) {
412
							if (display != null && !display.isDisposed()) {
413
								display.syncExec(new Runnable() {
414
									public void run() {
415
										if (canvas.isDisposed()) {
416
											tracking = false;
417
											cancelled = true;
418
											return;
419
										}
420
										Event ev = new Event();
421
										ev.display = display;
422
										Point loc = canvas.toControl(display
423
												.getCursorLocation());
424
										ev.type = SWT.DragDetect;
425
										ev.widget = widget;
426
										ev.button = 1;
427
										ev.x = loc.x;
428
										ev.y = loc.y;
429
										MouseEvent me = new MouseEvent(ev);
430
										me.stateMask = SWT.BUTTON1;
431
										listener.mouseMove(me);
432
									}
433
								});
434
								timeout += refreshRate;
435
								if (timeout >= 60000) {
436
									cancelled = true;
437
								}
438
								Thread.sleep(refreshRate);
439
							}
440

    
441
						}
442
					} catch (InterruptedException e) {
443
						e.printStackTrace();
444
					} finally {
445
						display.syncExec(new Runnable() {
446
							public void run() {
447
								close();
448
							}
449
						});
450
					}
451
					return Status.OK_STATUS;
452
				}
453
			};
454
			dragJob.schedule();
455
		}
456

    
457
		public void close() {
458
			tracking = false;
459
		}
460
	}
461

    
462
	/**
463
	 * Listener used to get all necessary events from the Canvas and pass them
464
	 * on to the {@link EventDispatcher}.
465
	 */
466
	protected class EventHandler implements MouseMoveListener, MouseListener,
467
			AccessibleControlListener, KeyListener, TraverseListener,
468
			FocusListener, AccessibleListener, MouseTrackListener, Listener,
469
			DisposeListener {
470
		/** @see FocusListener#focusGained(FocusEvent) */
471
		public void focusGained(FocusEvent e) {
472
			getEventDispatcher().dispatchFocusGained(e);
473
		}
474

    
475
		/** @see FocusListener#focusLost(FocusEvent) */
476
		public void focusLost(FocusEvent e) {
477
			getEventDispatcher().dispatchFocusLost(e);
478
		}
479

    
480
		/** @see AccessibleControlListener#getChild(AccessibleControlEvent) */
481
		public void getChild(AccessibleControlEvent e) {
482
			EventDispatcher.AccessibilityDispatcher ad;
483
			ad = getEventDispatcher().getAccessibilityDispatcher();
484
			if (ad != null)
485
				ad.getChild(e);
486
		}
487

    
488
		/** @see AccessibleControlListener#getChildAtPoint(AccessibleControlEvent) */
489
		public void getChildAtPoint(AccessibleControlEvent e) {
490
			EventDispatcher.AccessibilityDispatcher ad;
491
			ad = getEventDispatcher().getAccessibilityDispatcher();
492
			if (ad != null)
493
				ad.getChildAtPoint(e);
494
		}
495

    
496
		/** @see AccessibleControlListener#getChildCount(AccessibleControlEvent) */
497
		public void getChildCount(AccessibleControlEvent e) {
498
			EventDispatcher.AccessibilityDispatcher ad;
499
			ad = getEventDispatcher().getAccessibilityDispatcher();
500
			if (ad != null)
501
				ad.getChildCount(e);
502
		}
503

    
504
		/** @see AccessibleControlListener#getChildren(AccessibleControlEvent) */
505
		public void getChildren(AccessibleControlEvent e) {
506
			EventDispatcher.AccessibilityDispatcher ad;
507
			ad = getEventDispatcher().getAccessibilityDispatcher();
508
			if (ad != null)
509
				ad.getChildren(e);
510
		}
511

    
512
		/** @see AccessibleControlListener#getDefaultAction(AccessibleControlEvent) */
513
		public void getDefaultAction(AccessibleControlEvent e) {
514
			EventDispatcher.AccessibilityDispatcher ad;
515
			ad = getEventDispatcher().getAccessibilityDispatcher();
516
			if (ad != null)
517
				ad.getDefaultAction(e);
518
		}
519

    
520
		/** @see AccessibleListener#getDescription(AccessibleEvent) */
521
		public void getDescription(AccessibleEvent e) {
522
			EventDispatcher.AccessibilityDispatcher ad;
523
			ad = getEventDispatcher().getAccessibilityDispatcher();
524
			if (ad != null)
525
				ad.getDescription(e);
526
		}
527

    
528
		/** @see AccessibleControlListener#getFocus(AccessibleControlEvent) */
529
		public void getFocus(AccessibleControlEvent e) {
530
			EventDispatcher.AccessibilityDispatcher ad;
531
			ad = getEventDispatcher().getAccessibilityDispatcher();
532
			if (ad != null)
533
				ad.getFocus(e);
534
		}
535

    
536
		/** @see AccessibleListener#getHelp(AccessibleEvent) */
537
		public void getHelp(AccessibleEvent e) {
538
			EventDispatcher.AccessibilityDispatcher ad;
539
			ad = getEventDispatcher().getAccessibilityDispatcher();
540
			if (ad != null)
541
				ad.getHelp(e);
542
		}
543

    
544
		/** @see AccessibleListener#getKeyboardShortcut(AccessibleEvent) */
545
		public void getKeyboardShortcut(AccessibleEvent e) {
546
			EventDispatcher.AccessibilityDispatcher ad;
547
			ad = getEventDispatcher().getAccessibilityDispatcher();
548
			if (ad != null)
549
				ad.getKeyboardShortcut(e);
550
		}
551

    
552
		/** @see AccessibleControlListener#getLocation(AccessibleControlEvent) */
553
		public void getLocation(AccessibleControlEvent e) {
554
			EventDispatcher.AccessibilityDispatcher ad;
555
			ad = getEventDispatcher().getAccessibilityDispatcher();
556
			if (ad != null)
557
				ad.getLocation(e);
558
		}
559

    
560
		/** @see AccessibleListener#getName(AccessibleEvent) */
561
		public void getName(AccessibleEvent e) {
562
			EventDispatcher.AccessibilityDispatcher ad;
563
			ad = getEventDispatcher().getAccessibilityDispatcher();
564
			if (ad != null)
565
				ad.getName(e);
566
		}
567

    
568
		/** @see AccessibleControlListener#getRole(AccessibleControlEvent) */
569
		public void getRole(AccessibleControlEvent e) {
570
			EventDispatcher.AccessibilityDispatcher ad;
571
			ad = getEventDispatcher().getAccessibilityDispatcher();
572
			if (ad != null)
573
				ad.getRole(e);
574
		}
575

    
576
		/** @see AccessibleControlListener#getSelection(AccessibleControlEvent) */
577
		public void getSelection(AccessibleControlEvent e) {
578
			EventDispatcher.AccessibilityDispatcher ad;
579
			ad = getEventDispatcher().getAccessibilityDispatcher();
580
			if (ad != null)
581
				ad.getSelection(e);
582
		}
583

    
584
		/** @see AccessibleControlListener#getState(AccessibleControlEvent) */
585
		public void getState(AccessibleControlEvent e) {
586
			EventDispatcher.AccessibilityDispatcher ad;
587
			ad = getEventDispatcher().getAccessibilityDispatcher();
588
			if (ad != null)
589
				ad.getState(e);
590
		}
591

    
592
		/** @see AccessibleControlListener#getValue(AccessibleControlEvent) */
593
		public void getValue(AccessibleControlEvent e) {
594
			EventDispatcher.AccessibilityDispatcher ad;
595
			ad = getEventDispatcher().getAccessibilityDispatcher();
596
			if (ad != null)
597
				ad.getValue(e);
598
		}
599

    
600
		/**
601
		 * @see Listener#handleEvent(org.eclipse.swt.widgets.Event)
602
		 * @since 3.1
603
		 */
604
		public void handleEvent(Event event) {
605
			// Mouse wheel events
606
			if (event.type == SWT.MouseWheel)
607
				getEventDispatcher().dispatchMouseWheelScrolled(event);
608
			// HACK: for DND in RAP
609
			else if (event.type == SWT.DragDetect) {
610
				MouseEvent me = new MouseEvent(event);
611
				me.stateMask = SWT.BUTTON1;
612
				getEventDispatcher().dispatchMouseMoved(me);
613
			}
614
		}
615

    
616
		/** @see KeyListener#keyPressed(KeyEvent) */
617
		public void keyPressed(KeyEvent e) {
618
			getEventDispatcher().dispatchKeyPressed(e);
619
		}
620

    
621
		/** @see KeyListener#keyReleased(KeyEvent) */
622
		public void keyReleased(KeyEvent e) {
623
			getEventDispatcher().dispatchKeyReleased(e);
624
		}
625

    
626
		/** @see TraverseListener#keyTraversed(TraverseEvent) */
627
		public void keyTraversed(TraverseEvent e) {
628
			/*
629
			 * Doit is almost always false by default for Canvases with
630
			 * KeyListeners. Set to true to allow normal behavior. For example,
631
			 * in Dialogs ESC should close.
632
			 */
633
			e.doit = true;
634
			getEventDispatcher().dispatchKeyTraversed(e);
635
		}
636

    
637
		/** @see MouseListener#mouseDoubleClick(MouseEvent) */
638
		public void mouseDoubleClick(MouseEvent e) {
639
			getEventDispatcher().dispatchMouseDoubleClicked(e);
640
		}
641

    
642
		/** @see MouseListener#mouseDown(MouseEvent) */
643
		public void mouseDown(MouseEvent e) {
644
			getEventDispatcher().dispatchMousePressed(e);
645
		}
646

    
647
		/** @see MouseTrackListener#mouseEnter(MouseEvent) */
648
		public void mouseEnter(MouseEvent e) {
649
			getEventDispatcher().dispatchMouseEntered(e);
650
		}
651

    
652
		/** @see MouseTrackListener#mouseExit(MouseEvent) */
653
		public void mouseExit(MouseEvent e) {
654
			getEventDispatcher().dispatchMouseExited(e);
655
		}
656

    
657
		/** @see MouseTrackListener#mouseHover(MouseEvent) */
658
		public void mouseHover(MouseEvent e) {
659
			getEventDispatcher().dispatchMouseHover(e);
660
		}
661

    
662
		/** @see MouseMoveListener#mouseMove(MouseEvent) */
663
		public void mouseMove(MouseEvent e) {
664
			getEventDispatcher().dispatchMouseMoved(e);
665
		}
666

    
667
		/** @see MouseListener#mouseUp(MouseEvent) */
668
		public void mouseUp(MouseEvent e) {
669
			getEventDispatcher().dispatchMouseReleased(e);
670
			if (tracker != null) {
671
				tracker.close();
672
				tracker = null;
673
			}
674
		}
675

    
676
		/** @see DisposeListener#widgetDisposed(DisposeEvent) */
677
		public void widgetDisposed(DisposeEvent e) {
678
			getUpdateManager().dispose();
679
		}
680
	}
681

    
682
}
(101-101/171)