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