| /******************************************************************************* |
| * Copyright (c) 2012 Tasktop Technologies and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Tasktop Technologies - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.mylyn.internal.tasks.index.ui; |
| |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.GregorianCalendar; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| import java.util.regex.Pattern; |
| |
| import org.eclipse.jface.fieldassist.IContentProposal; |
| import org.eclipse.jface.fieldassist.IContentProposalProvider; |
| import org.eclipse.mylyn.internal.tasks.core.AbstractTask; |
| import org.eclipse.mylyn.internal.tasks.core.TaskList; |
| import org.eclipse.mylyn.internal.tasks.index.core.TaskListIndex; |
| import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema; |
| import org.eclipse.mylyn.tasks.core.data.AbstractTaskSchema.Field; |
| import org.eclipse.mylyn.tasks.core.data.TaskAttribute; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * A content proposal provider for proposals on a task list index and task list. |
| * |
| * @author David Green |
| */ |
| public class ContentProposalProvider implements IContentProposalProvider { |
| |
| private static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s"); //$NON-NLS-1$ |
| |
| private static final Pattern PATTERN_SPECIAL_CHARACTERS = Pattern.compile("([+&\\|!\\(\\)\\{\\}\\[\\]^\"~\\*\\?:\\\\-])"); //$NON-NLS-1$ |
| |
| private final AbstractIndexReference reference; |
| |
| private final TaskList taskList; |
| |
| public ContentProposalProvider(TaskList taskList, AbstractIndexReference reference) { |
| this.taskList = taskList; |
| this.reference = reference; |
| } |
| |
| public static class ProposalContentState { |
| |
| public String beforePrefixContent; |
| |
| public String fieldPrefix = ""; //$NON-NLS-1$ |
| |
| public String prefix = ""; //$NON-NLS-1$ |
| |
| public String suffix = ""; //$NON-NLS-1$ |
| |
| public ProposalContentState(String contents) { |
| beforePrefixContent = contents; |
| } |
| |
| } |
| |
| public IContentProposal[] getProposals(String contents, int position) { |
| List<IContentProposal> proposals = new ArrayList<IContentProposal>(10); |
| |
| ProposalContentState contentState = computeProposalContentState(contents, position); |
| |
| // if we have a field prefix |
| if (contentState.fieldPrefix.length() > 0) { |
| AbstractTaskSchema.Field indexField = computeIndexField(contentState.fieldPrefix); |
| |
| // if it's a person field then suggest |
| // people from the task list |
| if (indexField != null && TaskAttribute.TYPE_PERSON.equals(indexField.getType())) { |
| computePersonProposals(proposals, contentState); |
| } |
| |
| } else { |
| |
| // suggest field name prefixes |
| for (Field field : reference.index().getIndexedFields()) { |
| |
| // searching on identifier field (task handle) is not useful |
| if (field.equals(TaskListIndex.FIELD_IDENTIFIER)) { |
| continue; |
| } |
| |
| String indexKey = field.getIndexKey(); |
| if (indexKey.startsWith(contentState.prefix)) { |
| String description; |
| if (TaskListIndex.FIELD_CONTENT.equals(field)) { |
| description = Messages.IndexSearchHandler_hint_content; |
| } else if (TaskListIndex.FIELD_PERSON.equals(field)) { |
| description = Messages.IndexSearchHandler_hint_person; |
| } else { |
| description = NLS.bind(Messages.IndexSearchHandler_hint_generic, field.getLabel()); |
| } |
| proposals.add(new ContentProposal(contentState.beforePrefixContent + indexKey + ":", //$NON-NLS-1$ |
| contentState.suffix, indexKey, description)); |
| |
| if (TaskAttribute.TYPE_DATE.equals(field.getType()) |
| || TaskAttribute.TYPE_DATETIME.equals(field.getType())) { |
| computeDateRangeProposals(proposals, contentState, field); |
| } |
| } |
| } |
| } |
| |
| return proposals.toArray(new IContentProposal[proposals.size()]); |
| } |
| |
| public ProposalContentState computeProposalContentState(String contents, int position) { |
| ProposalContentState contentState = new ProposalContentState(contents); |
| |
| if (position >= 0 && position <= contents.length()) { |
| int i = position; |
| while (i > 0 && !Character.isWhitespace(contents.charAt(i - 1)) && contents.charAt(i - 1) != ':') { |
| --i; |
| } |
| if (i > 0 && contents.charAt(i - 1) == ':') { |
| int fieldEnd = i - 1; |
| int fieldStart = i - 1; |
| while (fieldStart > 0 && Character.isLetter(contents.charAt(fieldStart - 1))) { |
| --fieldStart; |
| } |
| contentState.fieldPrefix = contents.substring(fieldStart, fieldEnd); |
| } |
| |
| contentState.prefix = contents.substring(i, position); |
| contentState.beforePrefixContent = contents.substring(0, i); |
| |
| if (position < contents.length()) { |
| i = position; |
| while (i < contents.length()) { |
| if (Character.isWhitespace(contents.charAt(i))) { |
| break; |
| } |
| ++i; |
| } |
| contentState.suffix = contents.substring(i); |
| } |
| } |
| return contentState; |
| } |
| |
| public Field computeIndexField(String fieldPrefix) { |
| for (Field field : reference.index().getIndexedFields()) { |
| if (field.getIndexKey().equals(fieldPrefix)) { |
| return field; |
| } |
| } |
| return null; |
| } |
| |
| public void computeDateRangeProposals(List<IContentProposal> proposals, ProposalContentState contentState, |
| Field field) { |
| // for date fields give suggestion of date range search |
| String description; |
| final Date now = new Date(); |
| final Date dateSearchUpperBound; |
| final Date dateSearchOneWeekLowerBound; |
| { |
| GregorianCalendar calendar = new GregorianCalendar(); |
| |
| calendar.setTime(now); |
| calendar.add(Calendar.DAY_OF_WEEK, 1); // one day in future due to GMT conversion in index |
| dateSearchUpperBound = calendar.getTime(); |
| |
| calendar.setTime(now); |
| calendar.add(Calendar.DAY_OF_WEEK, -7); |
| dateSearchOneWeekLowerBound = calendar.getTime(); |
| } |
| |
| description = NLS.bind(Messages.IndexSearchHandler_Generic_date_range_search_1_week, field.getLabel()); |
| |
| String label = NLS.bind(Messages.IndexSearchHandler_Past_week_date_range_label, field.getIndexKey()); |
| |
| String queryText = reference.index().computeQueryFieldDateRange(field, dateSearchOneWeekLowerBound, |
| dateSearchUpperBound); |
| |
| if (queryText.startsWith(contentState.prefix)) { |
| proposals.add(new ContentProposal(contentState.beforePrefixContent + queryText, contentState.suffix, label, |
| description)); |
| } |
| } |
| |
| public void computePersonProposals(List<IContentProposal> proposals, ProposalContentState contentState) { |
| Set<String> addresses = new TreeSet<String>(); |
| |
| Collection<AbstractTask> allTasks = taskList.getAllTasks(); |
| for (AbstractTask task : allTasks) { |
| addAddresses(addresses, task); |
| } |
| |
| for (String address : addresses) { |
| if (address.startsWith(contentState.prefix)) { |
| String proposalContent = address; |
| proposalContent = PATTERN_SPECIAL_CHARACTERS.matcher(proposalContent).replaceAll("\\\\$1"); //$NON-NLS-1$ |
| if (PATTERN_WHITESPACE.matcher(proposalContent).find()) { |
| proposalContent = "\"" + proposalContent + "\""; //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| proposals.add(new ContentProposal(contentState.beforePrefixContent + proposalContent, |
| contentState.suffix, address, null)); |
| } |
| } |
| } |
| |
| private void addAddresses(Set<String> addresses, AbstractTask task) { |
| String name = task.getOwner(); |
| if (name != null && name.trim().length() > 0) { |
| addresses.add(name.trim()); |
| } |
| } |
| } |