source: trunk/autoquest-ui-swt/src/main/java/de/ugoe/cs/autoquest/ui/swt/ShowTaskTreeDialog.java @ 1636

Last change on this file since 1636 was 1636, checked in by pharms, 10 years ago
  • adapted sorting of tasks based on their event coverage
File size: 24.5 KB
Line 
1//   Copyright 2012 Georg-August-Universität Göttingen, Germany
2//
3//   Licensed under the Apache License, Version 2.0 (the "License");
4//   you may not use this file except in compliance with the License.
5//   You may obtain a copy of the License at
6//
7//       http://www.apache.org/licenses/LICENSE-2.0
8//
9//   Unless required by applicable law or agreed to in writing, software
10//   distributed under the License is distributed on an "AS IS" BASIS,
11//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//   See the License for the specific language governing permissions and
13//   limitations under the License.
14
15package de.ugoe.cs.autoquest.ui.swt;
16
17import java.awt.GraphicsDevice;
18import java.awt.GraphicsEnvironment;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.Comparator;
22import java.util.LinkedList;
23import java.util.List;
24
25import org.eclipse.swt.SWT;
26import org.eclipse.swt.custom.SashForm;
27import org.eclipse.swt.events.SelectionAdapter;
28import org.eclipse.swt.events.SelectionEvent;
29import org.eclipse.swt.events.ShellAdapter;
30import org.eclipse.swt.events.ShellEvent;
31import org.eclipse.swt.events.ShellListener;
32import org.eclipse.swt.layout.GridData;
33import org.eclipse.swt.layout.GridLayout;
34import org.eclipse.swt.widgets.Button;
35import org.eclipse.swt.widgets.Composite;
36import org.eclipse.swt.widgets.Dialog;
37import org.eclipse.swt.widgets.Display;
38import org.eclipse.swt.widgets.Event;
39import org.eclipse.swt.widgets.Label;
40import org.eclipse.swt.widgets.Listener;
41import org.eclipse.swt.widgets.Shell;
42import org.eclipse.swt.widgets.TabFolder;
43import org.eclipse.swt.widgets.TabItem;
44import org.eclipse.swt.widgets.Tree;
45import org.eclipse.swt.widgets.TreeColumn;
46import org.eclipse.swt.widgets.TreeItem;
47
48import de.ugoe.cs.autoquest.tasktrees.treeifc.IIteration;
49import de.ugoe.cs.autoquest.tasktrees.treeifc.IMarkingTemporalRelationship;
50import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptional;
51import de.ugoe.cs.autoquest.tasktrees.treeifc.IOptionalInstance;
52import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelection;
53import de.ugoe.cs.autoquest.tasktrees.treeifc.ISelectionInstance;
54import de.ugoe.cs.autoquest.tasktrees.treeifc.ISequence;
55import de.ugoe.cs.autoquest.tasktrees.treeifc.IStructuringTemporalRelationship;
56import de.ugoe.cs.autoquest.tasktrees.treeifc.ITask;
57import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstance;
58import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskInstanceList;
59import de.ugoe.cs.autoquest.tasktrees.treeifc.ITaskModel;
60import de.ugoe.cs.autoquest.tasktrees.treeifc.IUserSession;
61import de.ugoe.cs.autoquest.tasktrees.treeifc.TaskMetric;
62
63/**
64 * <p>
65 * a dialog to inspect the tasks and task instances of a task model
66 * </p>
67 *
68 * @author Patrick Harms
69 */
70public class ShowTaskTreeDialog extends Dialog {
71
72    /** the main shell */
73    private Shell shell;
74   
75    /** the listener for the main shell to prevent disposing required */
76    private ShellListener shellListener;
77   
78    /** the tab folder containing the instance tree as well as the model tree */
79    private TabFolder tabFolder;
80
81    /** the tree of task instances on the left */
82    private Tree instanceTree;
83   
84    /** the tree of tasks on the left */
85    private Tree modelTree;
86   
87    /** the tree of a specific task on the right */
88    private Tree taskDetailsTree;
89
90    /** the tree of execution variants of a specific task on the right */
91    private Tree executionVariantsTree;
92
93    /** the tree of involved GUI elements of a specific task on the right bottom */
94    private Tree involvedTargetsTree;
95
96    /** the table containing the parents tasks of a displayed task */
97    private Tree parentTasks;
98
99    /** the displayed task model */
100    private ITaskModel taskModel;
101   
102    /** default expansion listener */
103    private Listener defaultExpansionListener = new Listener() {
104        public void handleEvent(final Event event) {
105            ensureChildren((TreeItem) event.item);
106            ((TreeItem) event.item).setExpanded(true);
107        }
108    };
109
110    /**
111     * creates the dialog
112     */
113    public ShowTaskTreeDialog(Shell parent, int style, ITaskModel taskModel, String taskTreeName) {
114        super(parent, style);
115        setText("Task Model " + taskTreeName);
116        this.taskModel = taskModel;
117    }
118
119    /**
120     * displays the dialog
121     * @param task
122     */
123    public void open() {
124        open(null);
125    }
126
127    /**
128     * displays the dialog with a specific task opened
129     * @param task
130     */
131    public void open(ITask task) {
132        if (shell == null) {
133            createContents();
134            shell.open();
135            shell.layout();
136
137            VisualizationUtils.updateColumnWidths(taskDetailsTree);
138        }
139        else {
140            shell.setVisible(true);
141        }
142       
143        if (task != null) {
144            shellListener = new ShellAdapter() {
145                @Override
146                public void shellClosed(ShellEvent e) {
147                    e.doit = false;
148                    shell.setVisible(false);
149                }
150            };
151           
152            shell.addShellListener(shellListener);
153        }
154
155        if (task != null) {
156            navigateTo(task);
157        }
158       
159        Display display = getParent().getDisplay();
160        while (!shell.isDisposed() && shell.isVisible()) {
161            if (!display.readAndDispatch()) {
162                display.sleep();
163            }
164        }
165       
166        if (task == null) {
167            dispose();
168        }
169    }
170
171    /**
172     * disposes the dialog if it is not used anymore
173     */
174    public void dispose() {
175        if ((shell != null) && (!shell.isDisposed())) {
176            if (shellListener != null) {
177                shell.removeShellListener(shellListener);
178            }
179           
180            shell.dispose();
181        }
182    }
183
184    /**
185     * creates the two views, one on the task instances on the left, on on the task models on the
186     * right. Also adds a selection adapter to the task instances so that for a selected task
187     * instance always the respective model is presented.
188     */
189    private void createContents() {
190        shell = new Shell(getParent(), SWT.SHELL_TRIM | SWT.BORDER);
191
192        GraphicsDevice gd =
193            GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
194       
195        shell.setSize(gd.getDisplayMode().getWidth(), gd.getDisplayMode().getHeight());
196        shell.setText(getText());
197
198        shell.setLayout(new GridLayout(4, false));
199       
200        SashForm mainSashForm = new SashForm(shell, SWT.HORIZONTAL);
201        mainSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 4, 1));
202       
203        createSessionsAndTasksTabFolder(mainSashForm);
204        createTaskDetailsView(mainSashForm);
205
206        mainSashForm.setWeights(new int[] { 1, 3 });
207
208        Button btnExpandAll = new Button(shell, SWT.NONE);
209        btnExpandAll.addSelectionListener(new SelectionAdapter() {
210            @Override
211            public void widgetSelected(SelectionEvent e) {
212                if (tabFolder.getSelectionIndex() == 0) {
213                    VisualizationUtils.expandAll(instanceTree, true);
214                }
215                else {
216                    VisualizationUtils.expandAll(modelTree, true);
217                }
218            }
219        });
220        btnExpandAll.setText("Expand all");
221
222        Button btnCollapseAll = new Button(shell, SWT.NONE);
223        btnCollapseAll.addSelectionListener(new SelectionAdapter() {
224            @Override
225            public void widgetSelected(SelectionEvent e) {
226                if (tabFolder.getSelectionIndex() == 0) {
227                    VisualizationUtils.expandAll(instanceTree, false);
228                }
229                else {
230                    VisualizationUtils.expandAll(modelTree, false);
231                }
232            }
233        });
234        btnCollapseAll.setText("Collapse all");
235       
236        new Label(shell, SWT.NONE);
237        new Label(shell, SWT.NONE);
238    }
239
240    /**
241     *
242     */
243    private void createSessionsAndTasksTabFolder(SashForm mainSashForm) {
244        tabFolder = new TabFolder(mainSashForm, SWT.NONE);
245        tabFolder.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
246        tabFolder.addSelectionListener(new SelectionAdapter() {
247            @Override
248            public void widgetSelected(SelectionEvent e) {
249                VisualizationUtils.updateColumnWidths(modelTree);
250            }
251        });
252       
253        TabItem instanceTreeTab = new TabItem(tabFolder, SWT.NONE);
254        instanceTreeTab.setText("Task Instances");
255
256        instanceTree = new Tree(tabFolder, SWT.BORDER | SWT.SINGLE | SWT.VIRTUAL);
257        instanceTree.addSelectionListener(new SelectionAdapter() {
258            @Override
259            public void widgetSelected(SelectionEvent e) {
260                TreeItem[] selectedItems = instanceTree.getSelection();
261                if ((selectedItems.length == 1) &&
262                    (selectedItems[0].getData() instanceof ITaskInstance))
263                {
264                    displayTaskDetails(((ITaskInstance) selectedItems[0].getData()).getTask());
265                }
266                else {
267                    clearTaskDetails();
268                }
269            }
270        });
271
272        VisualizationUtils.addItemSpecificContextMenu
273            (instanceTree, ITaskInstance.class, "show in task list", new SelectionAdapter()
274        {
275            @Override
276            public void widgetSelected(SelectionEvent e) {
277                navigateTo(((ITaskInstance) instanceTree.getSelection()[0].getData()).getTask());
278            }
279        });
280
281        buildInstanceTree();
282        instanceTreeTab.setControl(instanceTree);
283
284        TabItem modelTreeTab = new TabItem(tabFolder, SWT.NONE);
285        modelTreeTab.setText("Tasks");
286       
287        modelTree =
288            VisualizationUtils.createTaskDetailsTree(tabFolder, "tasks in model", taskModel);
289       
290        // show task details if requested
291        modelTree.addSelectionListener(new SelectionAdapter() {
292            @Override
293            public void widgetSelected(SelectionEvent e) {
294                TreeItem[] selectedItems = modelTree.getSelection();
295                if ((selectedItems.length == 1) && (selectedItems[0].getData() instanceof ITask)) {
296                    displayTaskDetails((ITask) selectedItems[0].getData());
297                }
298                else {
299                    clearTaskDetails();
300                }
301            }
302        });
303       
304        buildModelTree(taskModel);
305       
306        modelTreeTab.setControl(modelTree);
307    }
308
309    /**
310     *
311     */
312    private void createTaskDetailsView(SashForm mainSashForm) {
313        Composite detailsComposite = new Composite(mainSashForm, SWT.NO_BACKGROUND);
314        detailsComposite.setLayout(new GridLayout(1, false));
315        detailsComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
316       
317        new Label(detailsComposite, SWT.NONE).setText("Task Details:");
318       
319        SashForm detailsSashForm = new SashForm(detailsComposite, SWT.VERTICAL);
320        detailsSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
321       
322        SashForm detailsTopSashForm = new SashForm(detailsSashForm, SWT.HORIZONTAL);
323        detailsTopSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
324       
325        taskDetailsTree =
326            VisualizationUtils.createTaskDetailsTree(detailsTopSashForm, "task details", taskModel);
327       
328        VisualizationUtils.addExpansionListener(taskDetailsTree, defaultExpansionListener);
329       
330        VisualizationUtils.addItemSpecificContextMenu(taskDetailsTree, ITask.class,
331                                                      "show in task list", new SelectionAdapter()
332        {
333            @Override
334            public void widgetSelected(SelectionEvent e) {
335                navigateTo((ITask) taskDetailsTree.getSelection()[0].getData());
336            }
337        });
338       
339        executionVariantsTree = new Tree(detailsTopSashForm, SWT.BORDER | SWT.SINGLE | SWT.WRAP);
340        executionVariantsTree.setHeaderVisible(true);
341       
342        TreeColumn taskColumn = new TreeColumn(executionVariantsTree, SWT.NONE);
343        taskColumn.setText("instance variants");
344        taskColumn.setAlignment(SWT.LEFT);
345
346        // load the correct children on expansion
347        VisualizationUtils.addExpansionListener(executionVariantsTree, defaultExpansionListener);
348       
349        detailsTopSashForm.setWeights(new int[] { 3, 2 });
350
351        SashForm detailsBottomSashForm = new SashForm(detailsSashForm, SWT.HORIZONTAL);
352        detailsBottomSashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
353       
354        parentTasks = VisualizationUtils.createTaskDetailsTree
355            (detailsBottomSashForm, "parent tasks", taskModel);
356       
357        VisualizationUtils.addItemSpecificContextMenu(parentTasks, ITask.class,
358                                                      "show in task list", new SelectionAdapter()
359        {
360            @Override
361            public void widgetSelected(SelectionEvent e) {
362                navigateTo((ITask) parentTasks.getSelection()[0].getData());
363            }
364        });
365       
366        involvedTargetsTree =
367            VisualizationUtils.createTargetsTree(detailsBottomSashForm, "involved GUI elements");
368       
369        detailsBottomSashForm.setWeights(new int[] { 2, 3 });
370        detailsSashForm.setWeights(new int[] { 1, 1 });
371    }
372
373    /**
374     * convenience method for creating the display of the instances
375     */
376    private void buildInstanceTree() {
377        List<IUserSession> sessions = taskModel.getUserSessions();
378
379        // load the correct children on expansion
380        VisualizationUtils.addExpansionListener(instanceTree, defaultExpansionListener);
381
382        TreeItem root = new TreeItem(instanceTree, SWT.NULL);
383        root.setText(sessions.size() + " sessions");
384        root.setData(taskModel);
385       
386        for (IUserSession session : sessions) {
387            buildInstanceTree(root, session);
388        }
389       
390        root.setExpanded(true);
391    }
392
393    /**
394     * convenience method for creating the display of the instances
395     */
396    private void buildInstanceTree(TreeItem currentParent, IUserSession session) {
397        TreeItem item = new TreeItem(currentParent, SWT.NULL);
398        item.setText(session.toString());
399        item.setData(session);
400       
401        // simulate a child
402        new TreeItem(item, SWT.NULL);
403    }
404
405    /**
406     * convenience method for creating the display of the task model
407     */
408    private void buildModelTree(ITaskModel taskModel) {
409        Collection<ITask> allTasks = taskModel.getTasks();
410       
411        List<ITask> sequences = new LinkedList<ITask>();
412        List<ITask> iterations = new LinkedList<ITask>();
413        List<ITask> selections = new LinkedList<ITask>();
414        List<ITask> optionals = new LinkedList<ITask>();
415        List<ITask> others = new LinkedList<ITask>();
416       
417        TreeItem root = new TreeItem(modelTree, SWT.NULL);
418        root.setText(allTasks.size() + " tasks in model");
419        root.setData(taskModel);
420
421        createSortedTaskLists(allTasks, sequences, iterations, selections, optionals, others);
422       
423        //allTasks = createSortedTaskList(allTasks);
424       
425        createModelTreeItemFor(sequences, sequences.size() + " Sequences", root);
426        createModelTreeItemFor(iterations, iterations.size() + " Iterations", root);
427        createModelTreeItemFor(selections, selections.size() + " Selections", root);
428        createModelTreeItemFor(optionals, optionals.size() + " Optionals", root);
429        createModelTreeItemFor(others, others.size() + " other Tasks", root);
430       
431        root.setExpanded(true);
432    }
433
434    /**
435     *
436     */
437    private void createSortedTaskLists(Collection<ITask> allTasks,
438                                       List<ITask>       sequences,
439                                       List<ITask>       iterations,
440                                       List<ITask>       selections,
441                                       List<ITask>       optionals,
442                                       List<ITask>       others)
443    {
444        List<ITask> toAdd;
445       
446        for (ITask task : allTasks) {
447            if (task instanceof ISequence) {
448                toAdd = sequences;
449            }
450            else if (task instanceof IIteration) {
451                toAdd = iterations;
452            }
453            else if (task instanceof ISelection) {
454                toAdd = selections;
455            }
456            else if (task instanceof IOptional) {
457                toAdd = optionals;
458            }
459            else {
460                toAdd = others;
461            }
462           
463            int taskValue = taskModel.getTaskInfo(task).getMeasureValue(TaskMetric.EVENT_COVERAGE);
464            int start = 0;
465            int end = toAdd.size();
466            int center = 0;
467            int centerValue;
468           
469            while (start != end) {
470                center = start + ((end - start) / 2);
471               
472                if ((center != start) || (center != end)) {
473                    //centerValue = toAdd.get(center).getInstances().size();
474                    centerValue = taskModel.getTaskInfo(toAdd.get(center)).getMeasureValue
475                        (TaskMetric.EVENT_COVERAGE);
476               
477                    if (centerValue > taskValue) {
478                        start = Math.max(center, start + 1);
479                    }
480                    else if (centerValue < taskValue) {
481                        end = Math.min(center, end - 1);
482                    }
483                    else {
484                        // add the event directly where the center is, as the count of the center
485                        // and the new task are equal
486                        end = center;
487                        start = end;
488                        break;
489                    }
490                }
491                else {
492                    // add the event to the position denoted by the add index
493                    break;
494                }
495            }
496           
497            toAdd.add(start, task);
498        }
499    }
500
501    /**
502     *
503     */
504    private void clearTaskDetails() {
505        taskDetailsTree.removeAll();
506        executionVariantsTree.removeAll();
507        involvedTargetsTree.removeAll();
508        parentTasks.removeAll();
509    }
510
511    /**
512     *
513     */
514    private void displayTaskDetails(ITask task) {
515        clearTaskDetails();
516       
517        VisualizationUtils.createTreeItemFor(task, taskDetailsTree, taskModel, true);
518        VisualizationUtils.expandAll(taskDetailsTree, true);
519       
520        // do it twice. Otherwise, it doesn't work
521        VisualizationUtils.updateColumnWidths(taskDetailsTree);
522       
523        Collection<Collection<ITaskInstance>> executionVariants = task.getExecutionVariants();
524        List<Collection<ITaskInstance>> sortedExecutionVariants =
525            new LinkedList<Collection<ITaskInstance>>(executionVariants);
526       
527        Collections.sort(sortedExecutionVariants, new Comparator<Collection<ITaskInstance>>() {
528            @Override
529            public int compare(Collection<ITaskInstance> list1, Collection<ITaskInstance> list2) {
530                return list2.size() - list1.size();
531            }
532        });
533       
534        int counter = 1;
535        for (Collection<ITaskInstance> variant : sortedExecutionVariants) {
536            TreeItem item = new TreeItem(executionVariantsTree, SWT.NULL);
537            if (variant.size() > 1) {
538                item.setText("variant " + counter++ + " (executed " + variant.size() + " times)");
539            }
540            else {
541                item.setText("variant " + counter++ + " (executed once)");
542            }
543            item.setData(variant);
544           
545            createTreeItemFor(variant.iterator().next(), item);
546        }
547
548        VisualizationUtils.expandAll(executionVariantsTree, true);
549        VisualizationUtils.updateColumnWidths(executionVariantsTree);
550       
551        addParentTasks(task);
552        VisualizationUtils.updateColumnWidths(parentTasks);
553       
554        VisualizationUtils.addInvolvedTargets(involvedTargetsTree, task);
555    }
556
557    /**
558     *
559     */
560    private void addParentTasks(ITask task) {
561        for (ITask candidate : taskModel.getTasks()) {
562            if (((candidate instanceof IStructuringTemporalRelationship) &&
563                 (((IStructuringTemporalRelationship) candidate).getChildren().contains(task))) ||
564                ((candidate instanceof IMarkingTemporalRelationship) &&
565                 (((IMarkingTemporalRelationship) candidate).getMarkedTask().equals(task))))
566            {
567                VisualizationUtils.createTreeItemFor(candidate, parentTasks, taskModel, false);
568            }
569        }
570    }
571
572    /**
573     *
574     */
575    private void navigateTo(ITask task) {
576        tabFolder.setSelection(1);
577       
578        OUTER:
579        for (TreeItem sublist : modelTree.getItem(0).getItems()) {
580            for (TreeItem taskItem : sublist.getItems()) {
581                if (task.equals(taskItem.getData())) {
582                    modelTree.setSelection(taskItem);
583                    sublist.setExpanded(true);
584                    VisualizationUtils.updateColumnWidths(modelTree);
585                    break OUTER;
586                }
587            }
588        }
589       
590        displayTaskDetails(task);
591    }
592   
593    /**
594     * ensures, that the children of a specific node are loaded
595     */
596    private void ensureChildren(TreeItem parent) {
597        TreeItem[] items = parent.getItems();
598        if ((items == null) || (items.length == 0) || (items[0].getData() == null)) {
599            if (items != null) {
600                for (int i = 0; i < items.length; i++) {
601                    items[i].dispose();
602                }
603            }
604
605            if (parent.getData() instanceof ITask) {
606                ITask task = (ITask) parent.getData();
607
608                if (task instanceof IStructuringTemporalRelationship) {
609                    for (ITask subTask : ((IStructuringTemporalRelationship) task).getChildren()) {
610                        VisualizationUtils.createTreeItemFor(subTask, parent, taskModel, true);
611                    }
612                }
613                else if (task instanceof IMarkingTemporalRelationship) {
614                    VisualizationUtils.createTreeItemFor
615                        (((IMarkingTemporalRelationship) task).getMarkedTask(), parent,
616                         taskModel, true);
617                }
618            }
619            else if (parent.getData() instanceof List<?>) {
620                @SuppressWarnings("unchecked")
621                List<ITask> tasks = (List<ITask>) parent.getData();
622                for (ITask task : tasks) {
623                    VisualizationUtils.createTreeItemFor(task, parent, taskModel, false);
624                }
625            }
626            else if (parent.getData() instanceof ITaskInstanceList) {
627                for (ITaskInstance subInstance : (ITaskInstanceList) parent.getData()) {
628                    createTreeItemFor(subInstance, parent);
629                }
630            }
631            else if (parent.getData() instanceof ITaskInstance) {
632                ITaskInstance instance = (ITaskInstance) parent.getData();
633
634                if (instance instanceof ISelectionInstance) {
635                    if (((ISelectionInstance) instance).getChild() != null) {
636                        createTreeItemFor(((ISelectionInstance) instance).getChild(), parent);
637                    }
638                }
639                else if (instance instanceof IOptionalInstance) {
640                    if (((IOptionalInstance) instance).getChild() != null) {
641                        createTreeItemFor(((IOptionalInstance) instance).getChild(), parent);
642                    }
643                }
644            }
645        }
646    }
647
648    /**
649     *
650     */
651    private void createModelTreeItemFor(List<ITask> taskList, String name, TreeItem parent) {
652        TreeItem item = new TreeItem(parent, SWT.NULL);
653        item.setText(name);
654        item.setData(taskList);
655       
656        // simulate a child
657        if (taskList.size() > 0) {
658            for (ITask task : taskList) {
659                VisualizationUtils.createTreeItemFor(task, item, taskModel, false);
660            }
661        }
662    }
663
664    /**
665     * convenience method to create a tree item for a task
666     */
667    private void createTreeItemFor(ITaskInstance taskInstance,
668                                   TreeItem      parent)
669    {
670        TreeItem item = new TreeItem(parent, SWT.NULL);
671        item.setText(taskInstance.toString());
672        item.setData(taskInstance);
673       
674        // simulate a child
675        if ((taskInstance instanceof ITaskInstanceList) ||
676            (taskInstance instanceof ISelectionInstance) ||
677            (taskInstance instanceof IOptionalInstance))
678        {
679            new TreeItem(item, SWT.NULL);
680        }
681    }
682}
Note: See TracBrowser for help on using the repository browser.