Added
Link Here
|
1 |
/******************************************************************************* |
2 |
* Copyright (c) 2004, 2008 Mylyn project committers 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 |
|
9 |
package org.eclipse.mylyn.tasks.ui.wizards; |
10 |
|
11 |
import java.lang.reflect.InvocationTargetException; |
12 |
import java.util.ArrayList; |
13 |
import java.util.Collections; |
14 |
import java.util.Comparator; |
15 |
import java.util.List; |
16 |
|
17 |
import org.eclipse.core.runtime.IConfigurationElement; |
18 |
import org.eclipse.core.runtime.IExtension; |
19 |
import org.eclipse.core.runtime.IExtensionPoint; |
20 |
import org.eclipse.core.runtime.IExtensionRegistry; |
21 |
import org.eclipse.core.runtime.IProgressMonitor; |
22 |
import org.eclipse.core.runtime.IStatus; |
23 |
import org.eclipse.core.runtime.MultiStatus; |
24 |
import org.eclipse.core.runtime.Platform; |
25 |
import org.eclipse.core.runtime.Status; |
26 |
import org.eclipse.core.runtime.SubProgressMonitor; |
27 |
import org.eclipse.jface.dialogs.IMessageProvider; |
28 |
import org.eclipse.jface.layout.GridDataFactory; |
29 |
import org.eclipse.jface.operation.IRunnableWithProgress; |
30 |
import org.eclipse.jface.wizard.WizardPage; |
31 |
import org.eclipse.mylyn.commons.core.StatusHandler; |
32 |
import org.eclipse.mylyn.internal.tasks.ui.TasksUiPlugin; |
33 |
import org.eclipse.mylyn.tasks.core.TaskRepository; |
34 |
import org.eclipse.swt.SWT; |
35 |
import org.eclipse.swt.layout.GridLayout; |
36 |
import org.eclipse.swt.widgets.Composite; |
37 |
import org.eclipse.ui.forms.events.ExpansionAdapter; |
38 |
import org.eclipse.ui.forms.events.ExpansionEvent; |
39 |
import org.eclipse.ui.forms.widgets.ExpandableComposite; |
40 |
import org.eclipse.ui.forms.widgets.FormToolkit; |
41 |
|
42 |
/** |
43 |
* An abstract base class for repository settings page that supports the <code>taskRepositoryPageContributor</code> |
44 |
* extension point. |
45 |
* |
46 |
* {@link ITaskRepositoryPage} implementations are encouraged to extend {@link AbstractRepositorySettingsPage} if |
47 |
* possible as it provides a standard UI for managing server settings. |
48 |
* |
49 |
* @see AbstractRepositorySettingsPage |
50 |
* |
51 |
* @since 3.1 |
52 |
* |
53 |
* @author David Green |
54 |
*/ |
55 |
public abstract class AbstractExtensibleRepositorySettingsPage extends WizardPage implements ITaskRepositoryPage { |
56 |
|
57 |
private static final String KIND = "connectorKind"; |
58 |
|
59 |
private static final String TASK_REPOSITORY_PAGE_CONTRIBUTOR = "taskRepositoryPageContributor"; |
60 |
|
61 |
private static final String TASK_REPOSITORY_PAGE_CONTRIBUTOR_EXTENSION = "org.eclipse.mylyn.tasks.ui.taskRepositoryPageContributor"; |
62 |
|
63 |
private static final Comparator<AbstractTaskRepositoryPageContribution> CONTRIBUTION_COMPARATOR = new ContributionComparator(); |
64 |
|
65 |
protected final TaskRepository repository; |
66 |
|
67 |
private final List<AbstractTaskRepositoryPageContribution> contributions = new ArrayList<AbstractTaskRepositoryPageContribution>(); |
68 |
|
69 |
protected FormToolkit toolkit; |
70 |
|
71 |
protected Composite compositeContainer; |
72 |
|
73 |
private final AbstractTaskRepositoryPageContribution.Listener contributionListener = new AbstractTaskRepositoryPageContribution.Listener() { |
74 |
public void validationRequired(AbstractTaskRepositoryPageContribution contribution) { |
75 |
validatePageSettings(); |
76 |
} |
77 |
}; |
78 |
|
79 |
public AbstractExtensibleRepositorySettingsPage(String title, String description, TaskRepository repository) { |
80 |
super(title); |
81 |
if (repository != null && !repository.getConnectorKind().equals(getConnectorKind())) { |
82 |
throw new IllegalArgumentException(); |
83 |
} |
84 |
this.repository = repository; |
85 |
setTitle(title); |
86 |
setDescription(description); |
87 |
} |
88 |
|
89 |
/** |
90 |
* Get the kind of connector supported by this page. |
91 |
* |
92 |
* @return the kind of connector, never null |
93 |
*/ |
94 |
public abstract String getConnectorKind(); |
95 |
|
96 |
@Override |
97 |
public void dispose() { |
98 |
if (toolkit != null) { |
99 |
toolkit.dispose(); |
100 |
toolkit = null; |
101 |
} |
102 |
super.dispose(); |
103 |
} |
104 |
|
105 |
public void createControl(Composite parent) { |
106 |
toolkit = new FormToolkit(TasksUiPlugin.getDefault().getFormColors(parent.getDisplay())); |
107 |
|
108 |
compositeContainer = new Composite(parent, SWT.NULL); |
109 |
GridLayout layout = new GridLayout(1, true); |
110 |
compositeContainer.setLayout(layout); |
111 |
|
112 |
createContents(compositeContainer); |
113 |
|
114 |
setControl(compositeContainer); |
115 |
} |
116 |
|
117 |
/** |
118 |
* Create the contents of the page. Subclasses may override this method to change where the contributions are added. |
119 |
*/ |
120 |
protected void createContents(Composite parent) { |
121 |
createSettingControls(parent); |
122 |
|
123 |
addContributions(parent); |
124 |
} |
125 |
|
126 |
/** |
127 |
* create the controls of this page |
128 |
*/ |
129 |
protected abstract void createSettingControls(Composite parent); |
130 |
|
131 |
@Override |
132 |
public boolean isPageComplete() { |
133 |
return super.isPageComplete() && conributionsIsPageComplete(); |
134 |
} |
135 |
|
136 |
@Override |
137 |
public boolean canFlipToNextPage() { |
138 |
return super.canFlipToNextPage() && contributionsCanFlipToNextPage(); |
139 |
} |
140 |
|
141 |
private boolean contributionsCanFlipToNextPage() { |
142 |
for (AbstractTaskRepositoryPageContribution contribution : contributions) { |
143 |
if (!contribution.canFlipToNextPage()) { |
144 |
return false; |
145 |
} |
146 |
} |
147 |
return true; |
148 |
} |
149 |
|
150 |
private boolean conributionsIsPageComplete() { |
151 |
for (AbstractTaskRepositoryPageContribution contribution : contributions) { |
152 |
if (!contribution.isPageComplete()) { |
153 |
return false; |
154 |
} |
155 |
} |
156 |
return true; |
157 |
} |
158 |
|
159 |
/** |
160 |
* subclasses should only call this method if they override {@link #createContents(Composite)} |
161 |
* |
162 |
* @param parentControl |
163 |
* the container into which the contributions will create their UI |
164 |
*/ |
165 |
protected void addContributions(Composite parentControl) { |
166 |
List<ITaskRepositoryPageContributor> contributors = findApplicableContributors(); |
167 |
for (ITaskRepositoryPageContributor contributor : contributors) { |
168 |
AbstractTaskRepositoryPageContribution contribution = contributor.createContribution(getConnectorKind(), |
169 |
repository); |
170 |
if (contribution != null) { |
171 |
contributions.add(contribution); |
172 |
contribution.addListener(contributionListener); |
173 |
} |
174 |
} |
175 |
if (!contributions.isEmpty()) { |
176 |
Collections.sort(contributions, CONTRIBUTION_COMPARATOR); |
177 |
|
178 |
for (AbstractTaskRepositoryPageContribution contribution : contributions) { |
179 |
|
180 |
ExpandableComposite section = toolkit.createExpandableComposite(parentControl, |
181 |
ExpandableComposite.COMPACT | ExpandableComposite.TWISTIE | ExpandableComposite.TITLE_BAR); |
182 |
section.clientVerticalSpacing = 0; |
183 |
section.setBackground(parentControl.getBackground()); |
184 |
section.setFont(parentControl.getFont()); |
185 |
section.addExpansionListener(new ExpansionAdapter() { |
186 |
@Override |
187 |
public void expansionStateChanged(ExpansionEvent e) { |
188 |
getControl().getShell().pack(); |
189 |
} |
190 |
}); |
191 |
section.setText(contribution.getTitle()); |
192 |
section.setToolTipText(contribution.getDescription()); |
193 |
|
194 |
GridDataFactory.fillDefaults().grab(true, false).applyTo(section); |
195 |
|
196 |
Composite sectionContentsContainer = toolkit.createComposite(section); |
197 |
sectionContentsContainer.setBackground(parentControl.getBackground()); |
198 |
contribution.createControl(sectionContentsContainer, toolkit); |
199 |
|
200 |
section.setClient(sectionContentsContainer); |
201 |
} |
202 |
} |
203 |
} |
204 |
|
205 |
/** |
206 |
* Validate the settings of this page, not including contributions. This method should not be called directly by |
207 |
* page implementations. Implementations of this method must be capable of running in a non-UI thread. |
208 |
* |
209 |
* @return the status, or null if there are no messages. |
210 |
* |
211 |
* @see #validatePageSettings() |
212 |
*/ |
213 |
protected abstract IStatus validate(IProgressMonitor monitor); |
214 |
|
215 |
/** |
216 |
* Overriding methods should call <code>super.applyTo(repository)</code> |
217 |
*/ |
218 |
public void applyTo(TaskRepository repository) { |
219 |
applyContributionSettingsTo(repository); |
220 |
} |
221 |
|
222 |
private void applyContributionSettingsTo(TaskRepository repository) { |
223 |
for (AbstractTaskRepositoryPageContribution contribution : contributions) { |
224 |
contribution.applyTo(repository); |
225 |
} |
226 |
} |
227 |
|
228 |
/** |
229 |
* compute the validation |
230 |
* |
231 |
* @return a status if there is a message to display, otherwise null |
232 |
*/ |
233 |
private IStatus computeValidation(IProgressMonitor monitor) { |
234 |
int factor = 100; |
235 |
monitor.beginTask("Validating settings", (contributions.size() + 1) * factor); |
236 |
|
237 |
IStatus cumulativeResult = null; |
238 |
|
239 |
// validate the page |
240 |
{ |
241 |
SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, factor); |
242 |
cumulativeResult = validate(subMonitor); |
243 |
subMonitor.done(); |
244 |
} |
245 |
|
246 |
// validate contributions |
247 |
for (AbstractTaskRepositoryPageContribution contribution : contributions) { |
248 |
SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, factor); |
249 |
IStatus result = contribution.validate(subMonitor); |
250 |
if (result != null) { |
251 |
if (cumulativeResult == null) { |
252 |
cumulativeResult = result; |
253 |
} else if (cumulativeResult instanceof MultiStatus) { |
254 |
((MultiStatus) cumulativeResult).add(result); |
255 |
} else { |
256 |
cumulativeResult = new MultiStatus(cumulativeResult.getPlugin(), cumulativeResult.getCode(), |
257 |
new IStatus[] { cumulativeResult, result }, null, null); |
258 |
} |
259 |
} |
260 |
subMonitor.done(); |
261 |
} |
262 |
monitor.done(); |
263 |
return cumulativeResult; |
264 |
} |
265 |
|
266 |
/** |
267 |
* Validate all settings in the page including contributions. This method should be called whenever a setting is |
268 |
* changed on the page. |
269 |
* |
270 |
* The results of validation are applied and the buttons of the page are updated. |
271 |
* |
272 |
* @see #validate(IProgressMonitor) |
273 |
* @see #applyValidationResult(IStatus[]) |
274 |
*/ |
275 |
protected void validatePageSettings() { |
276 |
final Object[] result = new Object[1]; |
277 |
try { |
278 |
getWizard().getContainer().run(true, true, new IRunnableWithProgress() { |
279 |
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { |
280 |
result[0] = computeValidation(monitor); |
281 |
} |
282 |
}); |
283 |
} catch (InvocationTargetException e) { |
284 |
StatusHandler.fail(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, |
285 |
"Internal error validating repository", e.getCause())); |
286 |
return; |
287 |
} catch (InterruptedException e) { |
288 |
// canceled |
289 |
return; |
290 |
} |
291 |
applyValidationResult((IStatus[]) result[0]); |
292 |
getWizard().getContainer().updateButtons(); |
293 |
} |
294 |
|
295 |
/** |
296 |
* Apply the results of validation to the page. The implementation finds the most {@link IStatus#getSeverity() |
297 |
* severe} status and {@link #setMessage(String, int) applies the message} to the page. |
298 |
* |
299 |
* @param statuses |
300 |
* an array of statuses that indicate the result of validation, or null |
301 |
*/ |
302 |
protected void applyValidationResult(IStatus[] statuses) { |
303 |
if (statuses == null || statuses.length == 0) { |
304 |
setMessage(null, IMessageProvider.INFORMATION); |
305 |
setErrorMessage(null); |
306 |
} else { |
307 |
// find the most severe status |
308 |
IStatus status = statuses[0]; |
309 |
for (IStatus s : statuses) { |
310 |
if (status == null || s.getSeverity() > status.getSeverity()) { |
311 |
status = s; |
312 |
} |
313 |
} |
314 |
int messageType; |
315 |
switch (status.getSeverity()) { |
316 |
case IStatus.OK: |
317 |
case IStatus.INFO: |
318 |
messageType = IMessageProvider.INFORMATION; |
319 |
break; |
320 |
case IStatus.WARNING: |
321 |
messageType = IMessageProvider.WARNING; |
322 |
break; |
323 |
case IStatus.ERROR: |
324 |
default: |
325 |
messageType = IMessageProvider.ERROR; |
326 |
break; |
327 |
} |
328 |
setErrorMessage(null); |
329 |
setMessage(status.getMessage(), messageType); |
330 |
} |
331 |
} |
332 |
|
333 |
private List<ITaskRepositoryPageContributor> findApplicableContributors() { |
334 |
List<ITaskRepositoryPageContributor> contributors = new ArrayList<ITaskRepositoryPageContributor>(); |
335 |
|
336 |
IExtensionRegistry registry = Platform.getExtensionRegistry(); |
337 |
|
338 |
IExtensionPoint editorExtensionPoint = registry.getExtensionPoint(TASK_REPOSITORY_PAGE_CONTRIBUTOR_EXTENSION); |
339 |
IExtension[] editorExtensions = editorExtensionPoint.getExtensions(); |
340 |
for (IExtension extension : editorExtensions) { |
341 |
IConfigurationElement[] elements = extension.getConfigurationElements(); |
342 |
for (IConfigurationElement element : elements) { |
343 |
if (element.getName().equals(TASK_REPOSITORY_PAGE_CONTRIBUTOR)) { |
344 |
String kind = element.getAttribute(KIND); |
345 |
if (kind == null || "".equals(kind) || getConnectorKind().equals(kind)) { |
346 |
try { |
347 |
Object contributor = element.createExecutableExtension("class"); |
348 |
contributors.add((ITaskRepositoryPageContributor) contributor); |
349 |
} catch (Exception e) { |
350 |
StatusHandler.log(new Status(IStatus.ERROR, TasksUiPlugin.ID_PLUGIN, "Could not load " |
351 |
+ TASK_REPOSITORY_PAGE_CONTRIBUTOR, e)); |
352 |
} |
353 |
} |
354 |
} |
355 |
} |
356 |
} |
357 |
|
358 |
return contributors; |
359 |
} |
360 |
|
361 |
private static class ContributionComparator implements Comparator<AbstractTaskRepositoryPageContribution> { |
362 |
|
363 |
public int compare(AbstractTaskRepositoryPageContribution o1, AbstractTaskRepositoryPageContribution o2) { |
364 |
if (o1 == o2) { |
365 |
return 0; |
366 |
} |
367 |
String s1 = o1.getTitle(); |
368 |
String s2 = o2.getTitle(); |
369 |
int i = s1.compareTo(s2); |
370 |
if (i == 0) { |
371 |
i = new Integer(System.identityHashCode(o1)).compareTo(System.identityHashCode(o2)); |
372 |
} |
373 |
return i; |
374 |
} |
375 |
|
376 |
} |
377 |
} |