source: trunk/gli/src/org/greenstone/gatherer/cdm/LanguageManager.java@ 12284

Last change on this file since 12284 was 12281, checked in by kjdon, 18 years ago

renamed ListListener to AssignedListListener

  • Property svn:keywords set to Author Date Id Revision
File size: 24.2 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Author: John Thompson, Greenstone Digital Library, University of Waikato
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer.cdm;
28
29
30import java.awt.*;
31import java.awt.event.*;
32import java.io.*;
33import java.util.*;
34import javax.swing.*;
35import javax.swing.border.*;
36import javax.swing.event.*;
37import org.greenstone.gatherer.Configuration;
38import org.greenstone.gatherer.DebugStream;
39import org.greenstone.gatherer.Dictionary;
40import org.greenstone.gatherer.Gatherer;
41import org.greenstone.gatherer.gui.GLIButton;
42import org.greenstone.gatherer.util.CheckList;
43import org.greenstone.gatherer.util.CheckListEntry;
44import org.greenstone.gatherer.util.JarTools;
45import org.greenstone.gatherer.util.XMLTools;
46import org.w3c.dom.*;
47
48/** This class manages the language commands, remembering both a list of languages to build indexes in, plus the default language.
49 * @author John Thompson, Greenstone Digital Library, University of Waikato
50 * @version 2.3
51 */
52public class LanguageManager
53 extends DOMProxyListModel {
54
55 static public Document LANGUAGES_DOCUMENT = XMLTools.parseXMLFile("xml/languages.xml", true);
56
57 static final private Dimension COMPONENT_SIZE = new Dimension(125,25);
58
59 /** The visual controls for this manager. */
60 private Control controls = null;
61 /** A reference to this class as a model, for the inner controls class. */
62 private DOMProxyListModel model = null;
63 /** A hashtable of code->name mappings of known languages. */
64 private LinkedHashMap known_languages = null;
65 /** The default language object. */
66 private Language default_language = null;
67
68 /** Constructor. */
69 public LanguageManager(Element languages_element) {
70 super(languages_element, CollectionConfiguration.LANGUAGE_ELEMENT, new Language());
71
72 DebugStream.println("LanguageManager: " + getSize() + " languages parsed.");
73
74 this.model = this;
75 // Retrieve the default language
76 NodeList default_language_elements = CollectionDesignManager.collect_config.getDocumentElement().getElementsByTagName(CollectionConfiguration.LANGUAGE_DEFAULT_ELEMENT);
77 if(default_language_elements.getLength() > 0) {
78 default_language = new Language((Element)default_language_elements.item(0));
79 }
80 // Load a series of code->language mappings into known_languages, by reading from the 'languages.xml' file, which is essentially a subset of the ISO 639 Standard.
81 known_languages = new LinkedHashMap();
82 NodeList language_elements = LANGUAGES_DOCUMENT.getDocumentElement().getElementsByTagName(CollectionConfiguration.LANGUAGE_ELEMENT);
83 for(int i = 0; i < language_elements.getLength(); i++) {
84 Element language_element = (Element) language_elements.item(i);
85 String code = language_element.getAttribute(CollectionConfiguration.CODE_ATTRIBUTE);
86 String name = language_element.getAttribute(CollectionConfiguration.NAME_ATTRIBUTE);
87 known_languages.put(code.toLowerCase(), name);
88 name = null;
89 code = null;
90 language_element = null;
91 }
92 }
93
94 /** Method to add a new language.
95 * @param language The <strong>Language</strong> to add.
96 * @see org.greenstone.gatherer.Gatherer
97 * @see org.greenstone.gatherer.collection.CollectionManager
98 */
99 private void addLanguage(Language language) {
100 if(!contains(language)) {
101 // need to add a pseudo metadata
102 CollectionMeta metadatum = new CollectionMeta(CollectionConfiguration.STOP_CHARACTER + language.getCode());
103 metadatum.setValue(language.getName());
104 CollectionDesignManager.collectionmeta_manager.addMetadatum(metadatum);
105 add(getSize(), language);
106 Gatherer.c_man.configurationChanged();
107 }
108 }
109
110 public void destroy() {
111 if(controls != null) {
112 controls.destroy();
113 controls = null;
114 }
115 known_languages.clear();
116 known_languages = null;
117 default_language = null;
118 }
119
120 /** Method to retrieve the control for this manager.
121 * @return the Control for editing the language partitions
122 */
123 public Control getControls() {
124 if(controls == null) {
125 // Build controls
126 controls = new LanguageControl();
127 }
128 return controls;
129 }
130
131 /** Method to retrieve a certain language object by its code.
132 * @param code The two letter code of a language, as a <strong>String</strong>.
133 * @return The <strong>Language</strong> that matches the given code, or <i>null</i> if no such language exists.
134 */
135 public Language getLanguage(String code) {
136 int size = getSize();
137 for(int i = 0; i < size; i++) {
138 Language language = (Language) getElementAt(i);
139 if(language.getCode().equals(code)) {
140 return language;
141 }
142 }
143 return null;
144 }
145
146 public ArrayList getLanguages() {
147 return children();
148 }
149
150 /** Method to return a list of the known language codes.
151 * @return an ArrayList containing the series of known language codes as per the languages.dat file
152 */
153 public ArrayList getLanguageCodes() {
154 return new ArrayList(known_languages.keySet());
155 }
156
157 public String getLanguageName(String code) {
158 return (String) known_languages.get(code);
159 }
160
161 /** Called when the detail mode has changed which in turn may cause several design elements to be available/hidden
162 * @param mode the new mode as an int
163 */
164 public void modeChanged(int mode) {
165
166 }
167
168 private int moveLanguage(Language lang, boolean move_up)
169 {
170 // Determine the current position of the language
171 int position = indexOf(lang);
172 int new_position;
173
174 // Attempt to move the language up
175 if (move_up) {
176 // Check it's not already at the top
177 if (position == 0) {
178 return position;
179 }
180
181 // This automatically removes the language first, as an Element can only exist once in a particular document
182 new_position = position - 1;
183 addBefore(lang, (Language) getElementAt(new_position));
184 }
185
186 // Attempt to move the language down
187 else {
188 // Check it's not already at the bottom
189 if (position == (getSize()) - 1) {
190 return position;
191 }
192
193 // This automatically removes the language first, as an Element can only exist once in a particular document
194 new_position = position + 1;
195 addAfter(lang, (Language) getElementAt(new_position));
196 }
197
198 // Schedule the collection for saving
199 Gatherer.c_man.configurationChanged();
200 return new_position;
201 }
202
203 /** Method to remove a certain language.
204 * @param language The <strong>Language</strong> to remove.
205 * @see org.greenstone.gatherer.Gatherer
206 * @see org.greenstone.gatherer.collection.CollectionManager
207 */
208 private void removeLanguage(Language language) {
209 remove(language);
210 // Remove any collection metadata for this language
211 CollectionDesignManager.collectionmeta_manager.removeMetadata(CollectionConfiguration.STOP_CHARACTER + language.getCode());
212 if(default_language != null && default_language.equals(language)) {
213 setDefault(null);
214 }
215 Gatherer.c_man.configurationChanged();
216 }
217
218 private void replaceLanguage(Language old_language, Language new_language) {
219 // Remove old lang collection meta
220 CollectionDesignManager.collectionmeta_manager.removeMetadata(CollectionConfiguration.STOP_CHARACTER + old_language.getCode());
221 // Add new one
222 CollectionMeta metadatum = new CollectionMeta(CollectionConfiguration.STOP_CHARACTER + new_language.getCode());
223 metadatum.setValue(new_language.getName());
224 CollectionDesignManager.collectionmeta_manager.addMetadatum(metadatum);
225 if(default_language != null && default_language.equals(old_language)) {
226 setDefault(new_language);
227 }
228
229 // get the position of the old one
230 int position = indexOf(old_language);
231 remove(old_language);
232 add(position, new_language);
233
234 // Schedule the collection for saving
235 Gatherer.c_man.configurationChanged();
236
237 }
238 /** Method to set the default language.
239 * @param language The <strong>Language</strong> to use as a default, or <i>null</i> for no default.
240 * @see org.greenstone.gatherer.Gatherer
241 * @see org.greenstone.gatherer.collection.CollectionManager
242 */
243 public void setDefault(Language language) {
244 if(language != null) {
245 if(default_language == null) {
246 // Create the default index element, and place immediately after indexes element.
247 Element default_language_element = root.getOwnerDocument().createElement(CollectionConfiguration.LANGUAGE_DEFAULT_ELEMENT);
248 default_language = new Language(default_language_element);
249 Node target_node = CollectionConfiguration.findInsertionPoint(default_language_element);
250 if(target_node != null) {
251 root.getOwnerDocument().getDocumentElement().insertBefore(default_language_element, target_node);
252 }
253 else {
254 root.getOwnerDocument().getDocumentElement().appendChild(default_language_element);
255 }
256 }
257 default_language.setAssigned(true);
258 default_language.setCode(language.getCode());
259 }
260 else {
261 if(default_language != null) {
262 default_language.setAssigned(false);
263 }
264 }
265 Gatherer.c_man.configurationChanged();
266 }
267
268
269 /** This class represents the visual component of the Language Manager. */
270 private class LanguageControl
271 extends JPanel
272 implements Control {
273 /** The list of available languages */
274 private CheckList language_list = null;
275 /** The button to add a new language support. */
276 private JButton add_button = null;
277 /** The button to replace a language support. */
278 private JButton replace_button = null;
279 /** The button to remove a supported language. */
280 private JButton remove_button = null;
281 /** button to move a language up in the list */
282 private JButton move_down_button;
283 /** button to move a language down in the list */
284 private JButton move_up_button;
285 /** The button to set the current language as the default one. */
286 private JButton set_default_button = null;
287 /** A combobox listing the available supported languages. */
288 //private JComboBox language_combobox = null;
289 /** A list of currently supported languages. */
290 private JList selected_languages_list = null;
291 /** Constructor.
292 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.AddListener
293 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.ClearDefaultListener
294 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.ListListener
295 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.RemoveListener
296 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.SelectorListener
297 * @see org.greenstone.gatherer.cdm.LanguageManager.LanguageControl.SetDefaultListener
298 */
299 public LanguageControl() {
300 super();
301 // Creation.
302 JPanel center_panel = new JPanel();
303
304 JLabel selected_languages_list_label = new JLabel(Dictionary.get("CDM.LanguageManager.Assigned_Languages"));
305 selected_languages_list = new JList(model);
306 selected_languages_list.setCellRenderer(new MyLanguageListCellRenderer());
307 selected_languages_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
308 selected_languages_list.setVisibleRowCount(5);
309 JPanel details_panel = new JPanel();
310
311 JPanel control_panel = new JPanel();
312 JLabel selector_label = new JLabel(Dictionary.get("CDM.LanguageManager.Selector"));
313
314 language_list = new CheckList(false);
315 language_list.setListData(getLanguageCodes());
316 language_list.setToolTipText(Dictionary.get("CDM.LanguageManager.Selector_Tooltip"));
317 language_list.setCellRenderer(new LanguageCheckListCellRenderer());
318
319 JPanel movement_pane = new JPanel();
320 move_up_button = new GLIButton(Dictionary.get("CDM.Move.Move_Up"), JarTools.getImage("arrow-up.gif"), Dictionary.get("CDM.Move.Move_Up_Tooltip"));
321 move_up_button.setEnabled(false);
322
323 move_down_button = new GLIButton(Dictionary.get("CDM.Move.Move_Down"), JarTools.getImage("arrow-down.gif"), Dictionary.get("CDM.Move.Move_Down_Tooltip"));
324 move_down_button.setEnabled(false);
325
326 set_default_button = new GLIButton(Dictionary.get("CDM.LanguageManager.Set_Default"), Dictionary.get("CDM.LanguageManager.Set_Default_Tooltip"));
327 set_default_button.setEnabled(false);
328
329 JPanel button_panel = new JPanel();
330
331 add_button = new GLIButton(Dictionary.get("CDM.SubcollectionIndexManager.Add_Subindex"), Dictionary.get("CDM.LanguageManager.Add_Tooltip"));
332 add_button.setEnabled(false);
333
334 replace_button = new GLIButton(Dictionary.get("CDM.SubcollectionIndexManager.Replace_Subindex"), Dictionary.get("CDM.LanguageManager.Replace_Tooltip"));
335 replace_button.setEnabled(false);
336
337 remove_button = new GLIButton(Dictionary.get("CDM.SubcollectionIndexManager.Remove_Subindex"), Dictionary.get("CDM.LanguageManager.Remove_Tooltip"));
338 remove_button.setEnabled(false);
339
340 // Set up and connect listeners.
341 add_button.addActionListener(new AddListener());
342 add_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
343 move_down_button.addActionListener(new MoveListener(false));
344 move_down_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
345 move_up_button.addActionListener(new MoveListener(true));
346 move_up_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
347 remove_button.addActionListener(new RemoveListener());
348 remove_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
349 replace_button.addActionListener(new ReplaceListener());
350 replace_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
351
352 language_list.addListSelectionListener(new LanguageListListener());
353
354 set_default_button.addActionListener(new SetDefaultListener());
355 set_default_button.addActionListener(CollectionDesignManager.buildcol_change_listener);
356 selected_languages_list.addListSelectionListener(new AssignedListListener());
357
358 // Layout components
359 button_panel.setLayout(new GridLayout(1,3));
360 button_panel.add(add_button);
361 button_panel.add(replace_button);
362 button_panel.add(remove_button);
363
364 movement_pane.setBorder(BorderFactory.createEmptyBorder(0,2,0,0));
365 movement_pane.setLayout(new GridLayout(3,1));
366 movement_pane.add(move_up_button);
367 movement_pane.add(move_down_button);
368 movement_pane.add(set_default_button);
369
370 control_panel.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
371 control_panel.setLayout(new BorderLayout());
372 control_panel.add(selector_label, BorderLayout.WEST);
373 control_panel.add(new JScrollPane(language_list), BorderLayout.CENTER);
374 control_panel.add(button_panel, BorderLayout.SOUTH);
375
376 center_panel.setLayout(new BorderLayout());
377 center_panel.add(selected_languages_list_label, BorderLayout.NORTH);
378 center_panel.add(new JScrollPane(selected_languages_list), BorderLayout.CENTER);
379 center_panel.add(movement_pane, BorderLayout.EAST);
380
381 setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
382 setLayout(new BorderLayout());
383 add(center_panel, BorderLayout.NORTH);
384 add(control_panel, BorderLayout.CENTER);
385 }
386
387 /** Destructor. */
388 public void destroy() {
389 }
390
391 public void gainFocus() {
392 }
393
394 public void loseFocus() {
395 }
396
397
398 private void clearControls() {
399 selected_languages_list.clearSelection();
400 language_list.clearTicked();
401 add_button.setEnabled(false);
402 remove_button.setEnabled(false);
403 replace_button.setEnabled(false);
404 set_default_button.setEnabled(false);
405 move_down_button.setEnabled(false);
406 move_up_button.setEnabled(false);
407
408 }
409
410 private void updateControlsWithSelectedLanguage()
411 {
412 Language selected_lang = (Language) selected_languages_list.getSelectedValue();
413 if (selected_lang == null) {
414 clearControls();
415 return;
416 }
417
418 // Display the selected subcollection index's sources
419 language_list.clearTicked();
420 language_list.setTickedObjects(selected_lang.getCode().split(","));
421
422 }
423
424 private void validateButtons() {
425 boolean add_enabled = false;
426 boolean replace_enabled = false;
427
428 if (!language_list.isNothingTicked()) {
429 // Create a dummy Langauge and see if its in the collection
430 ArrayList langs = language_list.getTicked();
431 StringBuffer code_str = new StringBuffer();
432 boolean first = true;
433 for (int i=0; i<langs.size(); i++) {
434 if (!first) {
435 code_str.append(",");
436 } else {
437 first = false;
438 }
439 code_str.append(langs.get(i));
440 }
441 String lang_str = code_str.toString();
442
443 if (!model.contains(lang_str)) {
444 add_enabled = true;
445 if (!selected_languages_list.isSelectionEmpty()) {
446 replace_enabled = true;
447 }
448 }
449
450 }
451 add_button.setEnabled(add_enabled);
452 replace_button.setEnabled(replace_enabled);
453 }
454
455 /** Listens for actions apon the 'add' button in the LanguageManager controls, and if detected calls the add method of the manager with a newly created language. */
456 private class AddListener
457 implements ActionListener {
458 /** Add a new language support.
459 * @param event an ActionEvent
460 * @see org.greenstone.gatherer.cdm.Language
461 */
462 public void actionPerformed(ActionEvent event) {
463 if (!language_list.isNothingTicked()) {
464 ArrayList langs = language_list.getTicked();
465 addLanguage(new Language(language_list.getTicked()));
466 clearControls();
467 }
468 }
469 }
470
471 /** Listens for actions apon the 'remove' button in the LanguageManager controls, and if detected calls the remove method of the manager with the language selected for removal. */
472 private class RemoveListener
473 implements ActionListener {
474 /** Remove the currently selected language, if any.
475 * @param event An <strong>ActionEvent</strong>.
476 * @see org.greenstone.gatherer.cdm.Language
477 */
478 public void actionPerformed(ActionEvent event) {
479 Language delete_me = (Language)selected_languages_list.getSelectedValue();
480 if(delete_me != null) {
481 removeLanguage(delete_me);
482 }
483 }
484 }
485
486 private class ReplaceListener
487 implements ActionListener {
488
489 public void actionPerformed(ActionEvent event) {
490 if (selected_languages_list.isSelectionEmpty() || language_list.isNothingTicked()) {
491 // This should never happen, but just in case...
492 replace_button.setEnabled(false);
493 return;
494 }
495 Language old_language = (Language) selected_languages_list.getSelectedValue();
496 Language new_language = new Language(language_list.getTicked());
497 replaceLanguage(old_language, new_language);
498
499 }
500 }
501
502 private class LanguageListListener
503 implements ListSelectionListener {
504
505 public void valueChanged(ListSelectionEvent event) {
506 if (event.getValueIsAdjusting()) {
507 return;
508 }
509 validateButtons();
510 }
511 }
512
513 /** Listens for actions apon the 'set default' button in the LanguageManager controls, and if detected calls the <i>setDefault()</i> method of the manager with the language selected for default. */
514 private class SetDefaultListener
515 implements ActionListener {
516 /** Set the default index to the one currently selected, if any.
517 * @param event An <strong>ActionEvent</strong>.
518 * @see org.greenstone.gatherer.cdm.Language
519 */
520 public void actionPerformed(ActionEvent event) {
521 Language selected_language = (Language) selected_languages_list.getSelectedValue();
522 if(selected_language != null) {
523 setDefault(selected_language);
524 // This should cause a repaint of just the desired row
525 selected_languages_list.setSelectedValue(selected_language, true);
526 }
527 set_default_button.setEnabled(false);
528 }
529 }
530
531 private class MoveListener
532 implements ActionListener
533 {
534 private boolean move_up;
535
536 public MoveListener(boolean move_up)
537 {
538 this.move_up = move_up;
539 }
540
541 public void actionPerformed(ActionEvent event)
542 {
543 // Retrieve the selected language
544 Language language = (Language) selected_languages_list.getSelectedValue();
545 if (language != null) {
546 int new_position = moveLanguage(language, move_up);
547 // Ensure the language that moved is still selected
548 selected_languages_list.setSelectedIndex(new_position);
549 }
550 }
551 }
552
553
554 /** Listens for selections within the list on the LanguageManager controls, and if a change is detected enables, or disables, controls appropriately. */
555 private class AssignedListListener
556 implements ListSelectionListener {
557 /** Enable or disable controls depending on the current list selection.
558 * @param event A <strong>ListSelectionEvent</strong>.
559 */
560 public void valueChanged(ListSelectionEvent event) {
561 if (event.getValueIsAdjusting()) {
562 return;
563 }
564 if(selected_languages_list.isSelectionEmpty()) {
565 clearControls();
566 return;
567 }
568
569 int i = selected_languages_list.getSelectedIndex();
570 int size = selected_languages_list.getModel().getSize();
571 Language selected_lang = (Language)selected_languages_list.getSelectedValue();
572 remove_button.setEnabled(true);
573 replace_button.setEnabled(false);
574 add_button.setEnabled(false);
575 set_default_button.setEnabled(default_language == null || !default_language.equals(selected_lang));
576
577 if (i > 0) {
578 move_up_button.setEnabled(true);
579 }
580 else {
581 move_up_button.setEnabled(false);
582 }
583 if (i < size-1){
584 move_down_button.setEnabled(true);
585 }
586 else {
587 move_down_button.setEnabled(false);
588 }
589 updateControlsWithSelectedLanguage();
590 }
591 }
592
593 private class MyLanguageListCellRenderer
594 extends DefaultListCellRenderer
595 {
596 /** Return a component that has been configured to display the specified value. */
597 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
598 JLabel component = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
599 if (default_language != null && default_language.equals(value)) {
600 component.setText(component.getText() + " [" + Dictionary.get("CDM.LanguageManager.Default_Language")+"]");
601 }
602 return component;
603 }
604 }
605 }
606
607 /** A custom list cell renderer for producing rows which contain clickable check boxes. */
608 private class LanguageCheckListCellRenderer
609 implements ListCellRenderer
610 {
611 /** Return a component that has been configured to display the specified value. That component's paint method is then called to "render" the cell. If it is necessary to compute the dimensions of a list because the list cells do not have a fixed size, this method is called to generate a component on which getPreferredSize can be invoked.
612 * @param list The </strong>JList</strong> we're painting.
613 * @param value The value returned by list.getModel().getElementAt(index), as an <strong>Object</strong>.
614 * @param index The cells index as an <i>int</i>.
615 * @param is_selected <i>true</i> if the specified cell was selected, <i>false</i> otherwise.
616 * @param cell_has_focus <i>true</i> if and only if the specified cell has the focus.
617 * @return A <strong>Component</strong> whose paint() method will render the specified value.
618 */
619 public Component getListCellRendererComponent(JList list, Object value, int index, boolean is_selected, boolean cell_has_focus) {
620 JCheckBox checkbox = (JCheckBox) value;
621 checkbox.setBackground(list.getBackground());
622 checkbox.setForeground(list.getForeground());
623 checkbox.setBorderPainted(false);
624 checkbox.setEnabled(list.isEnabled());
625 checkbox.setFont(list.getFont());
626 checkbox.setFocusPainted(false);
627 checkbox.setBorder((is_selected) ? UIManager.getBorder("List.focusCellHighlightBorder") : new EmptyBorder(1, 1, 1, 1));
628
629 String code = (String)((CheckListEntry)list.getModel().getElementAt(index)).getObject();
630 checkbox.setText((String)known_languages.get(code));
631
632 return checkbox;
633 }
634 }
635
636}
Note: See TracBrowser for help on using the repository browser.