Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 9e32777271b29e841acde56bb2d0dd224f176352 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/*******************************************************************************
 * Copyright (c) 2011, 2013 Wind River Systems, Inc. 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:
 * Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.tcf.te.ui.trees;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.jface.viewers.ContentViewer;
import org.eclipse.jface.viewers.DecoratingLabelProvider;
import org.eclipse.jface.viewers.DecoratingStyledCellLabelProvider;
import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider.IStyledLabelProvider;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.jface.viewers.TreePathViewerSorter;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Tree;

/**
 * Common sorter implementation.
 * <p>
 * <b>Note:</b> The default implementation is implementing a case sensitive sorting. Numbers comes
 *              first, than uppercase before lowercase.
 */
public class TreeViewerSorter extends TreePathViewerSorter {

	/**
	 * If or if not the sorting should happen case sensitive.
	 * <p>
	 * The default implementation returns <code>true</code>.
	 *
	 * @return <code>True</code> if the sorting is case sensitive, <code>false</code> otherwise.
	 */
	protected boolean isCaseSensitve() {
		return true;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.viewers.ViewerComparator#compare(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
	 */
	@Override
	public int compare(Viewer viewer, Object e1, Object e2) {
		if(e1 instanceof Pending || e2 instanceof Pending) {
			return (e1 instanceof Pending) ? (e2 instanceof Pending ? 0 : 1) : -1;
		}
		if (viewer != null && viewer.getControl() != null && !viewer.getControl().isDisposed()) {
			return doCompare(viewer, e1, e2, doGetSortColumnLabel(viewer), doGetSortColumnIndex(viewer) , doDetermineInverter(viewer));
		}
		return super.compare(viewer, e1, e2);
	}

	/**
	 * Returns the text to compare for the given node and column index.
	 *
	 * @param viewer The viewer or <code>null</code>.
	 * @param node The node or <code>null</code>.
	 * @param index The column index or <code>-1</code>.
	 *
	 * @return The text for the given node and column index or <code>null</code>.
	 */
	protected String doGetText(Viewer viewer, Object node, int index) {
		if (node != null) {
			IBaseLabelProvider labelProvider = doGetLabelProvider(viewer);
			if (labelProvider instanceof ITableLabelProvider) {
				return ((ITableLabelProvider)labelProvider).getColumnText(node, index);
			}
			if (labelProvider instanceof ILabelProvider) {
				return ((ILabelProvider)labelProvider).getText(node);
			}
			if (labelProvider instanceof IStyledLabelProvider) {
				StyledString text = ((IStyledLabelProvider)labelProvider).getStyledText(node);
				return text != null ? text.getString() : null;
			}
		}
		return null;
	}

	/**
	 * Return the label provider associated with the specified viewer.
	 *
	 * @param viewer The viewer or <code>null</code>.
	 * @return The label provider or <code>null</code>.
	 */
	protected IBaseLabelProvider doGetLabelProvider(Viewer viewer) {
		if (viewer instanceof ContentViewer) {
			IBaseLabelProvider candidate = ((ContentViewer)viewer).getLabelProvider();
			// We don't want any decoration, so unwrap the decorating label provider here
			if (candidate instanceof DecoratingLabelProvider) candidate = ((DecoratingLabelProvider)candidate).getLabelProvider();
			if (candidate instanceof DecoratingStyledCellLabelProvider) candidate = ((DecoratingStyledCellLabelProvider)candidate).getStyledStringProvider();
			// Return the label provider
			return candidate;
		}
		return null;
	}

	/**
	 * Determine if or if not the sort direction needs to be inverted.
	 *
	 * @param viewer The viewer or <code>null</code>.
	 * @return <code>1</code> for original sort order, or <code>-1</code> for inverted sort order.
	 */
	protected int doDetermineInverter(Viewer viewer) {
		int inverter = 1;

		// Viewer must be of type TreeViewer and the tree must not be disposed yet
		if (viewer instanceof TreeViewer && ((TreeViewer)viewer).getTree() != null) {
			Tree tree = ((TreeViewer)viewer).getTree();
			if (!tree.isDisposed() && tree.getSortDirection() == SWT.DOWN) inverter = -1;
		}

		return inverter;
	}

	/**
	 * Return the label of the sort column of the given viewer.
	 *
	 * @param viewer The viewer or <code>null</code>.
	 * @return The label of the sort column or an empty string.
	 */
	protected String doGetSortColumnLabel(Viewer viewer) {
		// Viewer must be of type TreeViewer and the tree must not be disposed yet
		if (viewer instanceof TreeViewer && ((TreeViewer)viewer).getTree() != null && !((TreeViewer)viewer).getTree().isDisposed()) {
			Tree tree = ((TreeViewer)viewer).getTree();
			return tree.getSortColumn() != null ? tree.getSortColumn().getText() : ""; //$NON-NLS-1$
		}
		return ""; //$NON-NLS-1$
	}

	/**
	 * Return the index of the sort column of the given viewer.
	 *
	 * @param viewer The viewer or <code>null</code>.
	 * @return The index of the sort column or <code>-1</code>.
	 */
	protected int doGetSortColumnIndex(Viewer viewer) {
		if (viewer instanceof TreeViewer && ((TreeViewer)viewer).getTree() != null && !((TreeViewer)viewer).getTree().isDisposed()) {
			Tree tree = ((TreeViewer)viewer).getTree();
			return tree.getSortColumn() != null ? Arrays.asList(tree.getColumns()).indexOf(tree.getSortColumn()) : -1;
		}
		return -1;
	}

	/**
	 * Compare the given model nodes by the given sort column and inverter.
	 *
	 * @param viewer The viewer or <code>null</code>.
	 * @param node1 The first node or <code>null</code>.
	 * @param node2 The second node or <code>null</code>.
	 * @param sortColumn The sort column text or <code>null</code>.
	 * @param index The sort column index or <code>-1</code>.
	 * @param inverter The inverter.
	 *
	 * @return The compare result.
	 */
	protected int doCompare(Viewer viewer, Object node1, Object node2, String sortColumn, int index, int inverter) {
		if (node1 == null && node2 == null) return 0;
		if (node1 != null && node2 == null) return 1 * inverter;
		if (node1 == null && node2 != null) return -1 * inverter;

		// Get the labels
		String text1 = doGetText(viewer, node1, index);
		String text2 = doGetText(viewer, node2, index);

		// If we fail to determine the labels, we cannot continue
		if (text1 == null && text2 == null) return 0;
		if (text1 != null && text2 == null) return 1 * inverter;
		if (text1 == null && text2 != null) return -1 * inverter;

		// Convert the labels to compare to lowercase if the sorting is case-insensitive
		if (!isCaseSensitve()) {
			text1 = text1.toLowerCase();
			text2 = text2.toLowerCase();
		}

		// The tree sorts not strictly alphabetical. First comes entries starting with numbers,
		// second entries starting with uppercase and than all the rest. Additional, if a label contains
		// uppercase characters, it is sorted in before any labels being lowercase only.
		if (text1.length() > 0 && text2.length() > 0) {
			// Get the first characters of both
			char c1 = text1.charAt(0);
			char c2 = text2.charAt(0);

			if (Character.isDigit(c1) || Character.isDigit(c2)) {
				// Check on the differences. If both are digits, the standard compare will do it
				if (Character.isDigit(c1) && !Character.isDigit(c2)) return -1 * inverter;
				if (!Character.isDigit(c1) && Character.isDigit(c2)) return 1 * inverter;
			}

			if (Character.isUpperCase(c1) || Character.isUpperCase(c2)) {
				// Check on the differences. If both are uppercase characters, the standard compare will do it
				if (Character.isUpperCase(c1) && !Character.isUpperCase(c2)) return -1 * inverter;
				if (!Character.isUpperCase(c1) && Character.isUpperCase(c2)) return 1 * inverter;
			}

			// If the text to compare is kind of "<text><number>", and the "<text>" part
			// is the same, compare the numbers as number.
			Matcher m1 = Pattern.compile("(\\D+)(\\d+)").matcher(text1); //$NON-NLS-1$
			Matcher m2 = Pattern.compile("(\\D+)(\\d+)").matcher(text2); //$NON-NLS-1$
			if (m1.matches() && m2.matches()) {
				String p11 = m1.group(1);
				String p12 = m1.group(2);

				String p21 = m2.group(1);
				String p22 = m2.group(2);

				if (p11 != null && p11.equals(p21)) {
					// Compare the second parts as number
					try {
						int result = 0;
						long l1 = Long.parseLong(p12);
						long l2 = Long.parseLong(p22);

						if (l1 > l2) result = 1 * inverter;
						if (l1 < l2) result = -1 * inverter;

						return result;
					} catch (NumberFormatException e) { /* ignored on purpose */ }
				}
			}

			// If the text to compare represents a number after all, compare it as numbers
			if (text1.matches("\\d+") && text2.matches("\\d+")) { //$NON-NLS-1$ //$NON-NLS-2$
				try {
					int result = 0;
					long l1 = Long.parseLong(text1);
					long l2 = Long.parseLong(text2);

					if (l1 > l2) result = 1 * inverter;
					if (l1 < l2) result = -1 * inverter;

					return result;
				} catch (NumberFormatException e) { /* ignored on purpose */ }
			}

			if (text1.matches(".*[A-Z]+.*") || text2.matches(".*[A-Z]+.*")) { //$NON-NLS-1$ //$NON-NLS-2$
				if (text1.matches(".*[A-Z]+.*") && !text2.matches(".*[A-Z]+.*")) return -1 * inverter; //$NON-NLS-1$ //$NON-NLS-2$
				if (!text1.matches(".*[A-Z]+.*") && text2.matches(".*[A-Z]+.*")) return 1 * inverter; //$NON-NLS-1$ //$NON-NLS-2$

				// Additionally, it even depends on the position of the first uppercase
				// character if both strings contains them :-(
				int minLength = Math.min(text1.length(), text2.length());
				for (int i = 0; i < minLength; i++) {
					char ch1 = text1.charAt(i);
					char ch2 = text2.charAt(i);

					if (Character.isUpperCase(ch1) && !Character.isUpperCase(ch2)) return -1 * inverter;
					if (!Character.isUpperCase(ch1) && Character.isUpperCase(ch2)) return 1 * inverter;
					// If both are uppercase, we break the loop and compare as usual
					if (Character.isUpperCase(ch1) && Character.isUpperCase(ch2)) break;
				}
			}
		}

		// Compare the text alphabetical
		return getComparator().compare(text1, text2) * inverter;
	}
}

Back to the top