| /*=============================================================================# |
| # Copyright (c) 2012, 2020 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.internal.r.ui.dataeditor; |
| |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.ui.statushandlers.StatusManager; |
| |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet; |
| import org.eclipse.statet.jcommons.status.ErrorStatus; |
| import org.eclipse.statet.jcommons.status.NullProgressMonitor; |
| import org.eclipse.statet.jcommons.status.ProgressMonitor; |
| import org.eclipse.statet.jcommons.status.StatusException; |
| import org.eclipse.statet.jcommons.status.Statuses; |
| import org.eclipse.statet.jcommons.ts.core.SystemRunnable; |
| import org.eclipse.statet.jcommons.ts.core.Tool; |
| import org.eclipse.statet.jcommons.ts.core.ToolRunnable; |
| import org.eclipse.statet.jcommons.ts.core.ToolService; |
| |
| import org.eclipse.statet.ecommons.ui.components.StatusInfo; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| |
| import org.eclipse.statet.internal.r.ui.dataeditor.IFindListener.FindEvent; |
| import org.eclipse.statet.r.core.tool.TmpUtils; |
| import org.eclipse.statet.r.ui.RUI; |
| import org.eclipse.statet.rj.data.RDataUtils; |
| import org.eclipse.statet.rj.data.RObject; |
| import org.eclipse.statet.rj.data.RObjectFactory; |
| import org.eclipse.statet.rj.data.RStore; |
| import org.eclipse.statet.rj.data.UnexpectedRDataException; |
| import org.eclipse.statet.rj.services.FunctionCall; |
| import org.eclipse.statet.rj.services.RService; |
| import org.eclipse.statet.rj.services.util.dataaccess.LazyRStore; |
| import org.eclipse.statet.rj.services.util.dataaccess.LazyRStore.Fragment; |
| import org.eclipse.statet.rj.services.util.dataaccess.RDataAssignment; |
| import org.eclipse.statet.rj.ts.core.RToolService; |
| |
| |
| class FindManager { |
| |
| private static final int FIND_CELL= 1; |
| private static final int FIND_ROW= 2; |
| private static final int FIND_ERROR= -1; |
| |
| |
| private class FindLock extends Lock implements LazyRStore.Updater<RObject> { |
| |
| |
| @Override |
| public void scheduleUpdate(final LazyRStore<RObject> store, |
| final RDataAssignment assignment, final LazyRStore.Fragment<RObject> fragment, |
| final int flags, final ProgressMonitor m) { |
| if (fragment != null && this.state > 0) { |
| return; |
| } |
| if (!this.scheduled) { |
| this.scheduled= true; |
| FindManager.this.dataProvider.schedule(FindManager.this.findRunnable); |
| } |
| } |
| |
| } |
| |
| private final ToolRunnable findRunnable= new SystemRunnable() { |
| |
| @Override |
| public String getTypeId() { |
| return "r/dataeditor/find"; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public String getLabel() { |
| return "Find Data (" + FindManager.this.dataProvider.getInput().getName() + ")"; |
| } |
| |
| @Override |
| public boolean canRunIn(final Tool tool) { |
| return true; // TODO |
| } |
| |
| @Override |
| public boolean changed(final int event, final Tool process) { |
| switch (event) { |
| case MOVING_FROM: |
| return false; |
| case REMOVING_FROM: |
| case BEING_ABANDONED: |
| FindManager.this.lock.lock(); |
| try { |
| FindManager.this.lock.scheduled= false; |
| FindManager.this.lock.clear(); |
| } |
| finally { |
| FindManager.this.lock.unlock(); |
| } |
| break; |
| default: |
| break; |
| } |
| return true; |
| } |
| |
| @Override |
| public void run(final ToolService service, final ProgressMonitor m) throws StatusException { |
| runFind((RToolService) service, m); |
| } |
| |
| }; |
| |
| |
| private FindTask scheduledTask; |
| |
| private String rCacheFind; // only in R jobs |
| private FindTask currentTask; |
| private int activeMode; |
| private String activeExpression; |
| private long findTotalCount; |
| private long findFilteredCount; |
| private long findLastMatchIdx; |
| |
| private final FindLock lock= new FindLock(); |
| private final LazyRStore<RObject> findStore; |
| |
| private final CopyOnWriteIdentityListSet<IFindListener> listeners= new CopyOnWriteIdentityListSet<>(); |
| |
| private final AbstractRDataProvider<?> dataProvider; |
| |
| |
| public FindManager(final AbstractRDataProvider<?> dataProvider) { |
| this.dataProvider= dataProvider; |
| |
| this.findStore= new LazyRStore<>(0, 1, 5, this.lock); |
| } |
| |
| |
| public void addFindListener(final IFindListener listener) { |
| this.listeners.add(listener); |
| } |
| |
| public void removeFindListener(final IFindListener listener) { |
| this.listeners.remove(listener); |
| } |
| |
| void clean(final ProgressMonitor m) throws StatusException { |
| if (this.rCacheFind != null) { |
| this.dataProvider.getTmpItem().remove(this.rCacheFind, m); |
| this.rCacheFind= null; |
| } |
| } |
| |
| void clear(final int newState) { |
| clear(newState, true); |
| } |
| void clear(final int newState, final boolean forceUpdate) { |
| this.lock.lock(); |
| try { |
| this.scheduledTask= null; |
| if (!forceUpdate && this.findFilteredCount >= 0) { |
| this.findStore.clear(this.findFilteredCount); |
| } |
| else { |
| this.findStore.clear(0); |
| this.activeExpression= null; |
| } |
| this.findLastMatchIdx= -1; |
| |
| if (newState >= 0 && this.lock.state < Lock.ERROR_STATE) { |
| this.lock.state= newState; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| notifyListeners(null, new StatusInfo(IStatus.CANCEL, ""), -1, -1, -1); |
| } |
| |
| void reset(final boolean filter) { |
| this.lock.lock(); |
| try { |
| this.scheduledTask= null; |
| this.findStore.clear(-1); |
| if (filter) { |
| this.findFilteredCount= -1; |
| } |
| this.findLastMatchIdx= -1; |
| |
| if (this.lock.state < Lock.LOCAL_PAUSE_STATE) { |
| this.lock.state= Lock.LOCAL_PAUSE_STATE; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| } |
| |
| |
| public void find(final FindTask task) { |
| this.lock.lock(); |
| try { |
| this.scheduledTask= task; |
| if (!task.equals(this.currentTask)) { |
| clear(-1, !task.expression.equals(this.activeExpression)); |
| this.scheduledTask= task; |
| if (this.lock.state < Lock.LOCAL_PAUSE_STATE) { |
| this.lock.state= Lock.LOCAL_PAUSE_STATE; |
| } |
| } |
| if (this.dataProvider.getLockState() > Lock.LOCAL_PAUSE_STATE) { |
| return; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| |
| try { |
| findMatch(null, new NullProgressMonitor()); |
| } |
| catch (final StatusException e) {} |
| catch (final UnexpectedRDataException e) {} |
| } |
| |
| private void runFind(final RToolService r, final ProgressMonitor m) throws StatusException { |
| try { |
| final boolean updateFinding; |
| this.lock.lock(); |
| try { |
| this.currentTask= this.scheduledTask; |
| this.lock.scheduled= false; |
| |
| if (this.currentTask == null) { |
| return; |
| } |
| updateFinding= !this.currentTask.expression.equals(this.activeExpression); |
| if (this.lock.state > Lock.LOCAL_PAUSE_STATE) { |
| return; |
| } |
| if (this.lock.state == Lock.LOCAL_PAUSE_STATE && !updateFinding) { |
| this.lock.state= 0; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| if (updateFinding) { |
| try { |
| updateFindingCache(r, m); |
| } |
| catch (final Exception e) { |
| AbstractRDataProvider.checkCancel(e); |
| StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID, -1, |
| "An error occurred when evaluating find criteria for data viewer.", e)); |
| return; |
| } |
| } |
| |
| updateFindingFragments(r, m); |
| |
| findMatch(r, m); |
| } |
| catch (final StatusException e) { |
| if (e.getStatus().getSeverity() == IStatus.CANCEL) { |
| notifyListeners(this.currentTask, new StatusInfo(IStatus.CANCEL, ""), //$NON-NLS-1$ |
| -1, -1, -1 ); |
| } |
| throw e; |
| } |
| catch (final UnexpectedRDataException e) { |
| throw new StatusException(new ErrorStatus(RUI.BUNDLE_ID, |
| "An error occurred when evaluating find result for data viewer.", e )); |
| } |
| } |
| |
| private void updateFindingCache(final RToolService r, final ProgressMonitor m) throws UnexpectedRDataException, StatusException { |
| int mode= 0; |
| long count= 0; |
| long filteredCount= 0; |
| try { |
| if (this.rCacheFind == null) { |
| this.rCacheFind= this.dataProvider.getTmpItem().createSub("find"); |
| } |
| final boolean runWhich; |
| { final StringBuilder cmd= this.dataProvider.getRCmdStringBuilder(); |
| cmd.append("local({"); |
| cmd.append("x <- ").append(this.dataProvider.getInput().getFullName()).append("; "); |
| cmd.append("x.find <- (").append(this.currentTask.expression).append("); "); |
| cmd.append("dimnames(").append("x.find").append(") <- NULL; "); |
| cmd.append("assign('").append(this.rCacheFind).append("', envir= ").append(TmpUtils.ENV_FQ_NAME).append(", value= x.find); "); |
| cmd.append("})"); |
| final RObject logi= r.evalData(cmd.toString(), null, RObjectFactory.F_ONLY_STRUCT, RService.DEPTH_ONE, m); |
| if (logi.getRObjectType() == RObject.TYPE_ARRAY |
| && logi.getData().getStoreType() == RStore.LOGICAL |
| && logi.getLength() == this.dataProvider.getFullRowCount() * this.dataProvider.getColumnCount()) { |
| mode= (this.dataProvider.getColumnCount() == 1) ? FIND_ROW : FIND_CELL; |
| runWhich= true; |
| } |
| else if (logi.getRObjectType() == RObject.TYPE_VECTOR |
| && logi.getData().getStoreType() == RStore.LOGICAL |
| && logi.getLength() == this.dataProvider.getFullRowCount()) { |
| mode= FIND_ROW; |
| runWhich= true; |
| } |
| else if (logi.getRObjectType() == RObject.TYPE_VECTOR |
| && logi.getData().getStoreType() == RStore.INTEGER) { |
| mode= FIND_ROW; |
| runWhich= false; |
| } |
| else { |
| throw new UnexpectedRDataException(logi.toString()); |
| } |
| } |
| if (runWhich) { |
| final StringBuilder cmd= this.dataProvider.getRCmdStringBuilder(); |
| cmd.append("which(").append(TmpUtils.ENV_FQ_NAME+'$').append(this.rCacheFind).append(", arr.ind= TRUE)"); |
| this.dataProvider.getTmpItem().set(this.rCacheFind, cmd.toString(), m); |
| } |
| |
| { final FunctionCall call= r.createFunctionCall("NROW"); |
| call.add(TmpUtils.ENV_FQ_NAME+'$'+this.rCacheFind); |
| count= RDataUtils.checkSingleIntValue(call.evalData(m)); |
| } |
| filteredCount= getFilteredCount(count, r, m); |
| } |
| catch (final StatusException e) { |
| clean(m); |
| AbstractRDataProvider.checkCancel(e); |
| mode= FIND_ERROR; |
| throw e; |
| } |
| catch (final UnexpectedRDataException e) { |
| clean(m); |
| AbstractRDataProvider.checkCancel(e); |
| mode= FIND_ERROR; |
| throw e; |
| } |
| finally { |
| if (mode == FIND_ERROR) { |
| notifyListeners(this.currentTask, new StatusInfo(IStatus.ERROR, "Error"), -1, -1, -1); |
| } |
| this.lock.lock(); |
| try { |
| this.activeMode= mode; |
| this.activeExpression= this.currentTask.expression; |
| this.findTotalCount= count; |
| this.findFilteredCount= filteredCount; |
| this.findStore.clear(filteredCount); |
| this.findLastMatchIdx= -1; |
| if (mode != FIND_ERROR && this.lock.state < Lock.PAUSE_STATE) { |
| this.lock.state= 0; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| } |
| } |
| |
| private long getFilteredCount(final long count, |
| final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException { |
| final String filterVar= this.dataProvider.checkFilter(); |
| if (filterVar == null || count == 0) { |
| return count; |
| } |
| final FunctionCall call= r.createFunctionCall(RJTmp.GET_FILTERED_COUNT); |
| call.addChar(RJTmp.FILTER_PAR, filterVar); |
| call.addChar(RJTmp.INDEX_PAR, this.rCacheFind); |
| return RDataUtils.checkSingleIntValue(call.evalData(m)); |
| } |
| |
| private void updateFindingFragments(final RToolService r, final ProgressMonitor m) throws StatusException { |
| try { |
| while (true) { |
| final Fragment<RObject> fragment; |
| this.lock.lock(); |
| try { |
| fragment= this.findStore.getNextScheduledFragment(); |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| if (fragment == null) { |
| break; |
| } |
| if (m.isCanceled()) { |
| throw new CoreException(Status.CANCEL_STATUS); |
| } |
| |
| final RObject fragmentObject= loadFindFragment(fragment, r, m); |
| this.lock.lock(); |
| try { |
| this.findStore.updateFragment(fragment, fragmentObject); |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| } |
| } |
| catch (final Exception e) { |
| AbstractRDataProvider.checkCancel(e); |
| this.lock.lock(); |
| try { |
| clear(-1); |
| if (this.lock.state < Lock.RELOAD_STATE) { |
| this.lock.state= Lock.RELOAD_STATE; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| StatusManager.getManager().handle(new Status(IStatus.ERROR, RUI.BUNDLE_ID, -1, |
| "An error occurred when loading find matches for data viewer.", e)); |
| return; |
| } |
| } |
| |
| private RObject loadFindFragment(final LazyRStore.Fragment<RObject> fragment, |
| final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException { |
| final String revIndexName= this.dataProvider.checkRevIndex(r, m); |
| { final StringBuilder cmd= this.dataProvider.getRCmdStringBuilder(); |
| cmd.append("local({"); |
| if (revIndexName != null) { |
| if (this.activeMode == FIND_CELL) { |
| cmd.append("x <- ").append(TmpUtils.ENV_FQ_NAME+'$').append(revIndexName) |
| .append("[").append(TmpUtils.ENV_FQ_NAME+'$').append(this.rCacheFind).append("[,1L]").append("]\n"); |
| cmd.append("x <- cbind(x, ").append(TmpUtils.ENV_FQ_NAME+'$').append(this.rCacheFind).append("[,2L]").append(")\n"); |
| } |
| else { |
| cmd.append("x <- ").append(TmpUtils.ENV_FQ_NAME+'$').append(revIndexName) |
| .append("[").append(TmpUtils.ENV_FQ_NAME+'$').append(this.rCacheFind).append("]\n"); |
| } |
| cmd.append("x <- na.omit(x)\n"); |
| } |
| else { |
| cmd.append("x <- ").append(TmpUtils.ENV_FQ_NAME+'$').append(this.rCacheFind).append("\n"); |
| } |
| cmd.append("x <- x[order("); |
| if (this.activeMode == FIND_CELL) { |
| cmd.append((this.currentTask.firstInRow) ? "x[,1L], x[,2L]" : "x[,2L], x[,1L]"); |
| } |
| else { |
| cmd.append("x"); |
| } |
| cmd.append(")").append("[").append(fragment.getRowBeginIdx() + 1).append(":").append(fragment.getRowEndIdx()).append("]"); |
| if (this.activeMode == FIND_CELL) { |
| cmd.append(","); |
| } |
| cmd.append("]; "); |
| cmd.append("x; "); |
| cmd.append("})"); |
| return r.evalData(cmd.toString(), m); |
| } |
| } |
| |
| private void findMatch(final RToolService r, final ProgressMonitor m) throws StatusException, UnexpectedRDataException { |
| final FindTask task; |
| final int mode; |
| long filteredCount; |
| long totalCount; |
| long globalMatchIdx; |
| this.lock.lock(); |
| try { |
| task= this.scheduledTask; |
| mode= this.activeMode; |
| totalCount= this.findTotalCount; |
| filteredCount= this.findFilteredCount; |
| globalMatchIdx= this.findLastMatchIdx; |
| |
| if (task == null || !task.equals(this.currentTask) |
| || this.lock.state == Lock.LOCAL_PAUSE_STATE) { |
| notifyListeners(task, new StatusInfo(IStatus.INFO, "Finding..."), -1, -1, -1); |
| this.lock.scheduleUpdate(null, null, null, 0, null); |
| return; |
| } |
| if (mode == FIND_ERROR) { |
| notifyListeners(task, new StatusInfo(IStatus.ERROR, "Error."), -1, -1, -1); |
| return; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| |
| if (filteredCount < 0) { |
| if (r != null) { |
| filteredCount= getFilteredCount(totalCount, r, m); |
| this.lock.lock(); |
| try { |
| this.findFilteredCount= filteredCount; |
| this.findStore.clear(filteredCount); |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| } |
| else { |
| this.lock.lock(); |
| try { |
| if (task != this.scheduledTask) { |
| return; |
| } |
| this.lock.scheduleUpdate(null, null, null, 0, null); |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| return; |
| } |
| } |
| if (filteredCount <= 0) { |
| notifyListeners(task, new StatusInfo(IStatus.INFO, |
| NLS.bind("Not found (total {0}/{1}).", filteredCount, totalCount)), |
| filteredCount, -1, -1 ); |
| return; |
| } |
| else { |
| notifyListeners(task, new StatusInfo(IStatus.INFO, |
| NLS.bind("Finding {0} (total {1}/{2})...", new Object[] { |
| (task.forward ? "next" : "previous"), filteredCount, totalCount })), |
| filteredCount, -1, -1 ); |
| } |
| if (globalMatchIdx >= filteredCount) { |
| globalMatchIdx= filteredCount - 1; |
| } |
| else if (globalMatchIdx < 0) { |
| globalMatchIdx= filteredCount / 2; |
| } |
| try { |
| final long[] rPos; |
| final long[] low; |
| final long[] high; |
| final int rowIdx; |
| final int colIdx; |
| if (mode == FIND_CELL) { |
| rowIdx= (task.firstInRow) ? 0 : 1; |
| colIdx= (task.firstInRow) ? 1 : 0; |
| |
| rPos= new long[2]; |
| rPos[rowIdx]= task.rowIdx + 1; |
| rPos[colIdx]= task.columnIdx + 1; |
| low= new long[2]; |
| high= new long[2]; |
| } |
| else { |
| rowIdx= 0; |
| colIdx= -1; |
| |
| rPos= new long[] { task.rowIdx + 1 }; |
| low= new long[1]; |
| high= new long[1]; |
| } |
| |
| while (true) { |
| int last= 0; |
| LazyRStore.Fragment<RObject> fragment; |
| while (true) { |
| if (m.isCanceled()) { |
| throw new StatusException(Statuses.CANCEL_STATUS); |
| } |
| |
| fragment= this.lock.getFragment(this.findStore, globalMatchIdx, 0, 0, m); |
| if (fragment != null) { |
| final RStore<?> data= fragment.getRObject().getData(); |
| final int length= (int) fragment.getRowCount(); |
| low[rowIdx]= data.getInt(0); |
| high[rowIdx]= data.getInt(length - 1); |
| if (mode == FIND_CELL) { |
| low[colIdx]= data.getInt(length); |
| high[colIdx]= data.getInt(length + length - 1); |
| } |
| if (RDataUtils.compare(rPos, low) < 0) { |
| globalMatchIdx= fragment.getRowBeginIdx() - 1; |
| if (globalMatchIdx < 0 |
| || (task.forward && last == +1)) { |
| break; |
| } |
| last= -1; |
| } |
| if (RDataUtils.compare(rPos, high) > 0) { |
| globalMatchIdx= fragment.getRowEndIdx(); |
| if (globalMatchIdx > filteredCount |
| || (!task.forward && last == -1)) { |
| break; |
| } |
| last= +1; |
| } |
| break; |
| } |
| else if (r != null) { |
| updateFindingFragments(r, m); |
| this.lock.lock(); |
| try { |
| if (task != this.scheduledTask |
| || this.lock.state > 0) { |
| return; |
| } |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| } |
| else { |
| this.lock.lock(); |
| try { |
| if (task != this.scheduledTask) { |
| return; |
| } |
| this.lock.scheduleUpdate(null, null, null, 0, null); |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| return; |
| } |
| } |
| |
| final RStore<?> data= fragment.getRObject().getData(); |
| final int length= (int) fragment.getRowCount(); |
| long localMatchIdx; |
| if (mode == FIND_CELL) { |
| low[rowIdx]= 0; |
| low[colIdx]= length; |
| localMatchIdx= RDataUtils.binarySearch(data, low, length, rPos); |
| } |
| else { |
| localMatchIdx= RDataUtils.binarySearch(data, rPos[rowIdx]); |
| } |
| if (localMatchIdx >= 0) { |
| localMatchIdx += (task.forward) ? +1 : -1; |
| } |
| else { |
| localMatchIdx= -(localMatchIdx + 1); |
| localMatchIdx += (task.forward) ? 0 : -1; |
| } |
| if (localMatchIdx < 0 || localMatchIdx >= length) { |
| notifyListeners(task, new StatusInfo(IStatus.INFO, |
| NLS.bind("No further match (total {0}/{1}).", filteredCount, totalCount)), |
| filteredCount, -1, -1 ); |
| return; |
| } |
| this.lock.lock(); |
| try { |
| if (task != this.scheduledTask) { |
| return; |
| } |
| this.findLastMatchIdx= globalMatchIdx= fragment.getRowBeginIdx() + localMatchIdx; |
| } |
| finally { |
| this.lock.unlock(); |
| } |
| { final long posCol; |
| final long posRow; |
| rPos[rowIdx]= data.getInt(localMatchIdx); |
| posRow= rPos[rowIdx] - 1; |
| if (mode == FIND_CELL) { |
| rPos[colIdx]= data.getInt(length + localMatchIdx); |
| posCol= rPos[colIdx] - 1; |
| } |
| else { |
| posCol= -1; |
| } |
| if (task.filter == null || task.filter.match(posRow, posCol)) { |
| notifyListeners(task, new StatusInfo(IStatus.INFO, |
| NLS.bind("Match {0} (total {1}/{2}).", new Object[] { |
| (globalMatchIdx + 1), filteredCount, totalCount })), |
| filteredCount, posRow, posCol ); |
| return; |
| } |
| } |
| } |
| } |
| catch (final LoadDataException e) { |
| } |
| } |
| |
| private void notifyListeners(final FindTask task, final IStatus status, final long total, |
| final long rowIdx, final long colIdx) { |
| UIAccess.getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| FindManager.this.lock.lock(); |
| try { |
| if (task != null && task != FindManager.this.scheduledTask) { |
| return; |
| } |
| } |
| finally { |
| FindManager.this.lock.unlock(); |
| } |
| final FindEvent event= new FindEvent(status, total, rowIdx, colIdx); |
| for (final IFindListener listener : FindManager.this.listeners.toList()) { |
| listener.handleFindEvent(event); |
| } |
| } |
| }); |
| } |
| |
| } |