]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.browsing.ui.swt/src/org/simantics/browsing/ui/swt/FilterArea.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.browsing.ui.swt / src / org / simantics / browsing / ui / swt / FilterArea.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.browsing.ui.swt;\r
13 \r
14 import java.util.HashMap;\r
15 import java.util.Map;\r
16 import java.util.concurrent.TimeUnit;\r
17 import java.util.concurrent.atomic.AtomicInteger;\r
18 \r
19 import org.eclipse.jface.layout.GridDataFactory;\r
20 import org.eclipse.jface.layout.GridLayoutFactory;\r
21 import org.eclipse.jface.resource.FontDescriptor;\r
22 import org.eclipse.jface.resource.ImageDescriptor;\r
23 import org.eclipse.jface.resource.JFaceResources;\r
24 import org.eclipse.jface.resource.LocalResourceManager;\r
25 import org.eclipse.jface.viewers.IPostSelectionProvider;\r
26 import org.eclipse.jface.viewers.ISelection;\r
27 import org.eclipse.jface.viewers.ISelectionChangedListener;\r
28 import org.eclipse.jface.viewers.SelectionChangedEvent;\r
29 import org.eclipse.swt.SWT;\r
30 import org.eclipse.swt.accessibility.ACC;\r
31 import org.eclipse.swt.accessibility.AccessibleAdapter;\r
32 import org.eclipse.swt.accessibility.AccessibleControlAdapter;\r
33 import org.eclipse.swt.accessibility.AccessibleControlEvent;\r
34 import org.eclipse.swt.accessibility.AccessibleEvent;\r
35 import org.eclipse.swt.events.ModifyEvent;\r
36 import org.eclipse.swt.events.ModifyListener;\r
37 import org.eclipse.swt.events.MouseAdapter;\r
38 import org.eclipse.swt.events.MouseEvent;\r
39 import org.eclipse.swt.events.MouseMoveListener;\r
40 import org.eclipse.swt.events.MouseTrackListener;\r
41 import org.eclipse.swt.events.SelectionAdapter;\r
42 import org.eclipse.swt.events.SelectionEvent;\r
43 import org.eclipse.swt.graphics.Image;\r
44 import org.eclipse.swt.graphics.Point;\r
45 import org.eclipse.swt.layout.GridData;\r
46 import org.eclipse.swt.widgets.Composite;\r
47 import org.eclipse.swt.widgets.Control;\r
48 import org.eclipse.swt.widgets.Display;\r
49 import org.eclipse.swt.widgets.Label;\r
50 import org.eclipse.swt.widgets.Text;\r
51 import org.eclipse.ui.ISharedImages;\r
52 import org.eclipse.ui.internal.WorkbenchImages;\r
53 import org.eclipse.ui.internal.WorkbenchMessages;\r
54 import org.simantics.browsing.ui.GraphExplorer;\r
55 import org.simantics.browsing.ui.NodeContext;\r
56 import org.simantics.browsing.ui.common.processors.FilterSelectionRequestQueryProcessor;\r
57 import org.simantics.browsing.ui.common.views.IFilterArea;\r
58 import org.simantics.browsing.ui.common.views.IFilterAreaProvider;\r
59 import org.simantics.browsing.ui.common.views.IFocusable;\r
60 import org.simantics.db.layer0.SelectionHints;\r
61 import org.simantics.utils.threads.SWTThread;\r
62 import org.simantics.utils.threads.ThreadUtils;\r
63 import org.simantics.utils.ui.ISelectionUtils;\r
64 \r
65 @SuppressWarnings("restriction")\r
66 public class FilterArea extends Composite implements IFocusable, IFilterArea, IFilterAreaProvider {\r
67 \r
68     protected static final Integer                 FILTER_DELAY = 500;\r
69 \r
70     private final LocalResourceManager             resourceManager;\r
71 \r
72     protected GraphExplorer                        explorer;\r
73 \r
74     protected FilterSelectionRequestQueryProcessor queryProcessor;\r
75 \r
76     private Text                                   filterText;\r
77     private NodeContext                                                    currentContext;\r
78 \r
79     /**\r
80      * The control representing the clear button for the filter text entry. This\r
81      * value may be <code>null</code> if no such button exists, or if the\r
82      * controls have not yet been created.\r
83      */\r
84     protected Control                              clearButtonControl;\r
85 \r
86     /**\r
87      * Construct the filter area UI component.\r
88      * \r
89      * @param explorer\r
90      * @param queryProcessor\r
91      * @param parent\r
92      * @param style\r
93      */\r
94     public FilterArea(final GraphExplorer explorer, final FilterSelectionRequestQueryProcessor queryProcessor, Composite parent, int style) {\r
95         super(parent, style | SWT.BORDER);\r
96 \r
97         this.explorer = explorer;\r
98         this.queryProcessor = queryProcessor;\r
99 \r
100         resourceManager = new LocalResourceManager(JFaceResources.getResources(parent.getDisplay()), this);\r
101 \r
102         GridDataFactory.fillDefaults().grab(true, false).span(2, 1).applyTo(this);\r
103         GridLayoutFactory.fillDefaults().margins(0, 0).spacing(0, 0).numColumns(2).equalWidth(false).applyTo(this);\r
104 \r
105         createFilterText(this);\r
106         createFilterCancelIcon(this);\r
107 \r
108         this.setBackground(filterText.getBackground());\r
109 \r
110         addTextModifyListener();\r
111         addExplorerSelectionListener();\r
112     }\r
113 \r
114     private void createFilterText(FilterArea filterArea) {\r
115         //filterText = new Text(this, SWT.SINGLE | SWT.FLAT | SWT.SEARCH | SWT.ICON_CANCEL);\r
116         filterText = new Text(this, SWT.SINGLE | SWT.ICON_CANCEL);\r
117         GridDataFactory.fillDefaults().grab(true, false).applyTo(filterText);\r
118 \r
119         filterText.setFont(resourceManager.createFont(FontDescriptor.createFrom(filterText.getFont()).increaseHeight(-1)));\r
120 \r
121         // if we're using a field with built in cancel we need to listen for\r
122         // default selection changes (which tell us the cancel button has been\r
123         // pressed)\r
124         if ((filterText.getStyle() & SWT.ICON_CANCEL) != 0) {\r
125             filterText.addSelectionListener(new SelectionAdapter() {\r
126                 @Override\r
127                 public void widgetDefaultSelected(SelectionEvent e) {\r
128                     if (e.detail == SWT.ICON_CANCEL)\r
129                         clearText();\r
130                 }\r
131             });\r
132         }\r
133 \r
134         GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);\r
135 \r
136         // if the text widget supported cancel then it will have it's own\r
137         // integrated button. We can take all of the space.\r
138         if ((filterText.getStyle() & SWT.ICON_CANCEL) != 0)\r
139             gridData.horizontalSpan = 2;\r
140         filterText.setLayoutData(gridData);\r
141     }\r
142 \r
143     private void createFilterCancelIcon(Composite parent) {\r
144         // only create the button if the text widget doesn't support one\r
145         // natively\r
146         if ((filterText.getStyle() & SWT.ICON_CANCEL) == 0) {\r
147             final Image inactiveImage = WorkbenchImages.getImage(ISharedImages.IMG_ETOOL_CLEAR_DISABLED);\r
148             final Image activeImage = WorkbenchImages.getImage(ISharedImages.IMG_ETOOL_CLEAR);\r
149             final Image pressedImage = (Image) resourceManager.get( ImageDescriptor.createWithFlags( ImageDescriptor.createFromImage( activeImage ), SWT.IMAGE_GRAY ) );\r
150 \r
151             final Label clearButton= new Label(parent, SWT.NONE);\r
152             clearButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false));\r
153             clearButton.setImage(inactiveImage);\r
154             clearButton.setBackground(parent.getDisplay().getSystemColor(SWT.COLOR_LIST_BACKGROUND));\r
155             clearButton.setToolTipText(WorkbenchMessages.FilteredTree_ClearToolTip);\r
156             clearButton.addMouseListener(new MouseAdapter() {\r
157                 private MouseMoveListener fMoveListener;\r
158 \r
159                 @Override\r
160                 public void mouseDown(MouseEvent e) {\r
161                     clearButton.setImage(pressedImage);\r
162                     fMoveListener= new MouseMoveListener() {\r
163                         private boolean fMouseInButton= true;\r
164 \r
165                         @Override\r
166                         public void mouseMove(MouseEvent e) {\r
167                             boolean mouseInButton= isMouseInButton(e);\r
168                             if (mouseInButton != fMouseInButton) {\r
169                                 fMouseInButton= mouseInButton;\r
170                                 clearButton.setImage(mouseInButton ? pressedImage : inactiveImage);\r
171                             }\r
172                         }\r
173                     };\r
174                     clearButton.addMouseMoveListener(fMoveListener);\r
175                 }\r
176 \r
177                 @Override\r
178                 public void mouseUp(MouseEvent e) {\r
179                     if (fMoveListener != null) {\r
180                         clearButton.removeMouseMoveListener(fMoveListener);\r
181                         fMoveListener= null;\r
182                         boolean mouseInButton= isMouseInButton(e);\r
183                         clearButton.setImage(mouseInButton ? activeImage : inactiveImage);\r
184                         if (mouseInButton) {\r
185                             clearText();\r
186                             filterText.setFocus();\r
187                         }\r
188                     }\r
189                 }\r
190 \r
191                 private boolean isMouseInButton(MouseEvent e) {\r
192                     Point buttonSize = clearButton.getSize();\r
193                     return 0 <= e.x && e.x < buttonSize.x && 0 <= e.y && e.y < buttonSize.y;\r
194                 }\r
195             });\r
196             clearButton.addMouseTrackListener(new MouseTrackListener() {\r
197                 @Override\r
198                 public void mouseEnter(MouseEvent e) {\r
199                     clearButton.setImage(activeImage);\r
200                 }\r
201 \r
202                 @Override\r
203                 public void mouseExit(MouseEvent e) {\r
204                     clearButton.setImage(inactiveImage);\r
205                 }\r
206 \r
207                 @Override\r
208                 public void mouseHover(MouseEvent e) {\r
209                 }\r
210             });\r
211             clearButton.getAccessible().addAccessibleListener(\r
212                     new AccessibleAdapter() {\r
213                         @Override\r
214                         public void getName(AccessibleEvent e) {\r
215                             e.result= WorkbenchMessages.FilteredTree_AccessibleListenerClearButton;\r
216                         }\r
217                     });\r
218             clearButton.getAccessible().addAccessibleControlListener(\r
219                     new AccessibleControlAdapter() {\r
220                         @Override\r
221                         public void getRole(AccessibleControlEvent e) {\r
222                             e.detail= ACC.ROLE_PUSHBUTTON;\r
223                         }\r
224                     });\r
225             this.clearButtonControl = clearButton;\r
226         }\r
227     }\r
228 \r
229     /**\r
230      * Clears the text in the filter text widget.\r
231      */\r
232     protected void clearText() {\r
233         //System.out.println("clearText");\r
234         filterText.setText(""); //$NON-NLS-1$\r
235         // TODO: HACK: this can be used to access filter for root context\r
236         explorer.select(null);\r
237         //textChanged();\r
238     }\r
239 \r
240     protected void addTextModifyListener() {\r
241         filterText.addModifyListener(new ModifyListener() {\r
242 \r
243             Map<NodeContext, AtomicInteger> modCount = new HashMap<NodeContext, AtomicInteger>();\r
244 \r
245             @Override\r
246             public void modifyText(ModifyEvent e) {\r
247 \r
248                 final NodeContext context = getFilteredNode();\r
249                 if (context == null)\r
250                     return;\r
251 \r
252                 final String filter = filterText.getText();\r
253                 //System.out.println("Scheduling setFilter(" + context + ", " + filter + ")");\r
254 \r
255                 AtomicInteger i = modCount.get(context);\r
256                 if (i == null)\r
257                     modCount.put(context, new AtomicInteger());\r
258                 final AtomicInteger counter = modCount.get(context);\r
259                 final int count = counter.incrementAndGet();\r
260 \r
261                 ThreadUtils.getNonBlockingWorkExecutor().schedule(new Runnable() {\r
262                     @Override\r
263                     public void run() {\r
264                         int newCount = counter.get();\r
265                         if (newCount != count)\r
266                             return;\r
267                         //System.out.println("schedule setFilter(" + context + ", " + filter + ")");\r
268                         modCount.remove(context);\r
269                         if (isDisposed())\r
270                             return;\r
271                         ThreadUtils.asyncExec(SWTThread.getThreadAccess(getDisplay()), new Runnable() {\r
272                             @Override\r
273                             public void run() {\r
274                                 if (isDisposed())\r
275                                     return;\r
276                                 //System.out.println("queryProcessor.setFilter(" + context + ", " + filter + ")");\r
277                                 queryProcessor.setFilter(context, filter.isEmpty() ? null : filter);\r
278                             }\r
279                         });\r
280                     }\r
281                 }, FILTER_DELAY, TimeUnit.MILLISECONDS);\r
282             }\r
283         });\r
284     }\r
285 \r
286     protected void addExplorerSelectionListener() {\r
287         IPostSelectionProvider selectionProvider = (IPostSelectionProvider)explorer.getAdapter(IPostSelectionProvider.class);\r
288         selectionProvider.addSelectionChangedListener(new ISelectionChangedListener() {\r
289             @Override\r
290             public void selectionChanged(SelectionChangedEvent event) {\r
291 \r
292                 ISelection selection = event.getSelection();\r
293                 NodeContext context = ISelectionUtils.getSinglePossibleKey(selection, SelectionHints.KEY_MAIN, NodeContext.class);\r
294                 if(context == null) {\r
295                     context = ISelectionUtils.filterSingleSelection(selection, NodeContext.class);\r
296                 }\r
297 \r
298                 if (context == null) {\r
299                     context = explorer.getRoot();\r
300                 }\r
301 \r
302                 assert (context != null);\r
303 \r
304                 String filter = queryProcessor.getFilter(context);\r
305 \r
306                 if (filter == null)\r
307                     filter = "";\r
308 \r
309                 //filterText.setData(null);\r
310                 //System.out.println("selection changed, new filter: " + filter + " for context " + context + ", currentContext=" + currentContext);\r
311                 currentContext = context;\r
312                 filterText.setText(filter);\r
313                 //filterText.setData("context", context);\r
314             }\r
315         });\r
316     }\r
317 \r
318     protected NodeContext getFilteredNode() {\r
319         if(currentContext != null) return currentContext;\r
320         else return explorer.getRoot();\r
321 //      return currentContext;\r
322 //      NodeContext context = (NodeContext)filterText.getData("context");\r
323 //      if (context == null)\r
324 //              context = explorer.getRoot();\r
325 //        return context;\r
326 \r
327     }\r
328 \r
329     @Override\r
330     public void focus() {\r
331         if (filterText.isDisposed())\r
332             return;\r
333         Display d = filterText.getDisplay();\r
334         if (d.getThread() == Thread.currentThread()) {\r
335             doSetFocus();\r
336         } else {\r
337             d.asyncExec(new Runnable() {\r
338                 @Override\r
339                 public void run() {\r
340                     if (filterText.isDisposed())\r
341                         return;\r
342                     doSetFocus();\r
343                 }\r
344             });\r
345         }\r
346     }\r
347 \r
348     @Override\r
349     public void dispose() {\r
350         \r
351         if (isDisposed())\r
352             return;\r
353         \r
354         resourceManager.dispose();\r
355         explorer = null;\r
356         queryProcessor = null;\r
357         filterText = null;\r
358         currentContext = null;\r
359         clearButtonControl = null;\r
360         \r
361         super.dispose();\r
362         \r
363     }\r
364 \r
365     protected void doSetFocus() {\r
366         filterText.selectAll();\r
367         filterText.setFocus();\r
368     }\r
369 \r
370     @Override\r
371     public IFilterArea getFilterArea() {\r
372         return this;\r
373     }\r
374 \r
375 }\r