protected final void doActionPerformed(ActionEvent evt) {
    try {
      ApplicationDiagram diag =
          (ApplicationDiagram) (ApplicationContext.getFocusManager().getFocusObject());
      Point clickPosition = getDiagramLocation(evt);
      if (clickPosition == null)
        clickPosition = GraphicUtil.rectangleGetCenter(diag.getMainView().getViewRect());
      java.awt.Image image = SRSystemClipboard.getClipboardImage();
      if (image == null) return;
      DbObject diagGo = diag.getDiagramGO();
      Db db = diagGo.getDb();

      ImageIcon icon = new ImageIcon(image);
      int imageHeigth = icon.getIconHeight();
      int imageWidth = icon.getIconWidth();

      db.beginTrans(Db.WRITE_TRANS, LocaleMgr.action.getString("pasteDiagramImage"));

      DbObject imageGo = diagGo.createComponent(DbGraphic.fImageGoDiagramImage.getMetaClass());
      imageGo.set(DbGraphic.fImageGoDiagramImage, image);
      imageGo.set(
          DbGraphic.fGraphicalObjectRectangle,
          new Rectangle(
              clickPosition.x - (imageWidth / 2),
              clickPosition.y - (imageHeigth / 2),
              imageWidth,
              imageHeigth));
      db.commitTrans();
    } catch (Exception e) {
      org.modelsphere.jack.util.ExceptionHandler.processUncatchedException(
          ApplicationContext.getDefaultMainFrame(), e);
    }
  }
public class Explorer extends DefaultTreeModel implements DbRefreshListener {
  public static final String kUpdate0 = LocaleMgr.screen.getString("Update0");
  private static final Icon kLocalIcon =
      GraphicUtil.loadIcon(ApplicationContext.class, "international/resources/localnode.gif");
  private static final Icon kRepositoryIcon =
      GraphicUtil.loadIcon(ApplicationContext.class, "international/resources/repositorynode.gif");

  public static final String ROOT = "ROOT"; // NOT LOCALIZABLE, property key
  public static final String DB_RAM = "RAM"; // NOT LOCALIZABLE, property key

  private ArrayList tooltipsFields = new ArrayList();

  class PreferencesListener implements PropertyChangeListener {
    public void propertyChange(PropertyChangeEvent evt) {
      try {
        Explorer.this.refresh();
      } catch (Exception e) {
        ExceptionHandler.processUncatchedException(ApplicationContext.getDefaultMainFrame(), e);
      }
    }
  }

  class DbsListener implements DbListener {
    public void dbCreated(Db db) {
      try {
        if (db instanceof DbRAM) {
          Explorer.this.refresh();
        } else {
          Explorer.this.reloadAll();
        }
      } catch (Exception e) {
        ExceptionHandler.processUncatchedException(ApplicationContext.getDefaultMainFrame(), e);
      }
    }

    public void dbTerminated(Db db) {
      try {
        Explorer.this.refresh();
      } catch (Exception e) {
        ExceptionHandler.processUncatchedException(ApplicationContext.getDefaultMainFrame(), e);
      }
    }
  }

  private DbsListener dbslistener = new DbsListener();

  public Explorer() {
    super(new DynamicNode(ROOT));
    PropertiesSet preferences = PropertiesManager.getPreferencePropertiesSet();
    if (preferences != null) {
      preferences.addPrefixPropertyChangeListener(
          DisplayToolTipsOptionGroup.class,
          DisplayToolTipsOptionGroup.TOOLTIPS_FIELD_VISIBILITY_PREFIX,
          new PreferencesListener());
      Db.addDbListener(dbslistener);
      tooltipsFields.addAll(Arrays.asList(DisplayToolTipsOptionGroup.getAvailableMetaFields()));
    }
  }

  public final void showContent() {
    setRoot(createRootNode());
    installDbListeners(true);
  }

  public final void hideContent() {
    setRoot(new DynamicNode(ROOT));
    installDbListeners(false);
  }

  // Overridden
  public void installDbListeners(boolean install) {
    MetaField[] tipsFields = DisplayToolTipsOptionGroup.getAvailableMetaFields();
    MetaField[] metaFields = new MetaField[] {DbObject.fComponents, DbSemanticalObject.fName};
    if (install) {
      MetaField.addDbRefreshListener(this, null, metaFields);
      MetaField.addDbRefreshListener(this, null, tipsFields);
    } else {
      MetaField.removeDbRefreshListener(this, metaFields);
      MetaField.removeDbRefreshListener(this, tipsFields);
    }
  }

  /**
   * If CS application, create a root node with 2 subnodes: RAM and repository; the RAM subnode
   * contains the list of RAM projects as subnodes. If RAM application, create a root node with the
   * list of projects as subnodes.
   */
  private DynamicNode createRootNode() {
    DynamicNode rootNode = null;
    DynamicNode RAMNode = null;
    try {
      RAMNode = new DynamicNode(DB_RAM);
      RAMNode.setDisplayText(DbRAM.DISPLAY_NAME);
      RAMNode.setIcon(kLocalIcon);
      loadChildren(RAMNode);
      Db[] dbs = Db.getDbs();
      for (int i = 0; i < dbs.length; i++) {
        if (dbs[i] instanceof DbRAM) continue;
        if (rootNode == null) {
          rootNode = new DynamicNode(ROOT);
          rootNode.setHasLoaded();
          rootNode.add(RAMNode);
        }
        DynamicNode dbNode = new DynamicNode(dbs[i]);
        dbNode.setDisplayText(dbs[i].getDBMSName());
        dbNode.setIcon(kRepositoryIcon);
        rootNode.add(dbNode);
        loadChildren(dbNode);
      }
    } catch (DbException ex) {
      org.modelsphere.jack.util.ExceptionHandler.processUncatchedException(
          ApplicationContext.getDefaultMainFrame(), ex);
    }
    return (rootNode != null ? rootNode : RAMNode);
  }

  protected final DynamicNode createNode(DbObject dbParent, DbObject dbo) throws DbException {
    return (dbParent == getDbParent(dbo, Db.NEW_VALUE)
        ? createPrimaryNode(dbParent, dbo)
        : createSecondaryNode(dbParent, dbo));
  }

  // Overridden
  protected DynamicNode createPrimaryNode(DbObject dbParent, DbObject dbo) throws DbException {
    DynamicNode node = new DynamicNode(dbo, getSequence(dbParent, dbo));
    String displayText = getDisplayText(dbParent, dbo);
    String editText = getEditText(dbParent, dbo);
    String tooltips = getToolTipsText(dbParent, dbo);
    node.setDisplayText(displayText, editText);
    node.setToolTips(tooltips);
    node.setIcon(getIcon(dbo));
    node.setGroupParams(getGroupParams(dbParent, dbo));
    node.setIsLeaf(isLeaf(dbParent, dbo));
    node.setDefaultAction(getDefaultAction(dbo));
    return node;
  }

  // Overridden
  protected DynamicNode createSecondaryNode(DbObject dbParent, DbObject dbo) throws DbException {
    DynamicNode primNode = getDynamicNode(dbo, true);
    DynamicNode node = new DynamicNode(primNode, primNode.insertIndex);
    node.setGroupParams(getGroupParams(dbParent, dbo));
    node.setDefaultAction(getDefaultAction(dbo));
    return node;
  }

  protected String getDisplayText(DbObject dbParent, DbObject dbo) throws DbException {
    return ApplicationContext.getSemanticalModel().getDisplayText(dbo, Explorer.class);
  }

  protected String getToolTipsText(DbObject dbParent, DbObject dbo) throws DbException {
    return ToolTipsServices.getToolTips(dbo, Explorer.class);
  }

  protected String getEditText(DbObject dbParent, DbObject dbo) throws DbException {
    MetaField editableMetafield = getEditableMetaField(dbo);
    String editText = null;
    if (editableMetafield == null) editText = dbo.getName();
    else editText = (String) dbo.get(editableMetafield);
    return editText == null ? "" : editText;
  }

  protected int getSequence(DbObject dbParent, DbObject dbo) throws DbException {
    return dbParent.getComponents().indexOf(dbo);
  }

  // Overridden
  protected GroupParams getGroupParams(DbObject dbParent, DbObject dbo) throws DbException {
    return GroupParams.defaultGroupParams;
  }

  /*
   * Specifies an action to activate on double click. Beware that it must not conflict with the
   * default double click behavior of JTree (expand, collapse). Recommanded to avoid returning an
   * action for a 'not leaf' object.
   */
  protected AbstractApplicationAction getDefaultAction(DbObject dbo) throws DbException {
    return null;
  }

  // Overridden
  protected boolean isLeaf(DbObject dbParent, DbObject dbo) throws DbException {
    return false;
  }

  // Overridden
  protected boolean isContainerRoot(Object object) {
    return false;
  }

  protected final void loadChildren(DynamicNode node) throws DbException {
    if (node.hasLoaded() || node.isLeaf()) return;
    node.setHasLoaded();
    Object userObject = node.getUserObject();
    if (userObject == ROOT) return;
    SrVector children = new SrVector(10);
    boolean isSorted = true;
    DbObject dbParent = null;
    if (userObject == DB_RAM) {
      Db[] dbs = Db.getDbs();
      for (int i = 0; i < dbs.length; i++) {
        if (dbs[i] instanceof DbRAM) insertProjects(children, dbs[i]);
      }
    } else if (userObject instanceof Db) {
      insertProjects(children, (Db) userObject);
    } else {
      dbParent = (DbObject) userObject;
      dbParent.getDb().beginTrans(Db.READ_TRANS);
      insertComponents(children, dbParent);
      isSorted = childrenAreSorted(dbParent);
      dbParent.getDb().commitTrans();
    }

    if (isSorted) {
      children.sort(getComparator(dbParent));
    }

    ArrayList groupNodeList = new ArrayList();
    DynamicNode groupNode = null;
    Enumeration enumeration = children.elements();
    while (enumeration.hasMoreElements()) {
      DynamicNode childNode = (DynamicNode) enumeration.nextElement();
      GroupParams group = childNode.getGroupParams();
      if (group.name == null) {
        node.add(childNode);
      } else {
        if (groupNode == null) {
          groupNode = createGroupNode(group);
          node.add(groupNode);
          groupNodeList.add(groupNode);
        } else if (!groupNode.toString().equals(group.name)) {
          boolean groupFound = false;
          for (int i = 0; i < groupNodeList.size(); i++) {
            groupNode = (DynamicNode) groupNodeList.get(i);
            if (groupNode.toString().equals(group.name)) {
              groupFound = true;
              break;
            }
          }
          if (!groupFound) {
            groupNode = createGroupNode(group);
            node.add(groupNode);
            groupNodeList.add(groupNode);
          }
        }
        groupNode.add(childNode);
      }
    }
    groupNodeList.clear();
  }

  private DynamicNode createGroupNode(GroupParams group) {
    DynamicNode groupNode = new DynamicNode(group);
    groupNode.setDisplayText(group.name);
    groupNode.setIcon(group.icon);
    groupNode.setGroupParams(group);
    groupNode.setHasLoaded();
    return groupNode;
  }

  private void insertProjects(SrVector children, Db db) throws DbException {
    db.beginTrans(Db.READ_TRANS);
    DbObject parent = db.getRoot();
    DbEnumeration dbEnum = parent.getComponents().elements(DbProject.metaClass);
    while (dbEnum.hasMoreElements())
      children.addElement(createPrimaryNode(parent, dbEnum.nextElement()));
    dbEnum.close();
    db.commitTrans();
  }

  // Overridden
  protected void insertComponents(SrVector children, DbObject dbParent) throws DbException {
    SemanticalModel model = ApplicationContext.getSemanticalModel();

    DbEnumeration dbEnum = dbParent.getComponents().elements();
    while (dbEnum.hasMoreElements()) {
      DbObject dbo = dbEnum.nextElement();
      boolean isVisible = model.isVisibleOnScreen(dbParent, dbo, Explorer.class);
      if (isVisible) {
        children.addElement(createPrimaryNode(dbParent, dbo));
      } // end if
    } // end while
    dbEnum.close();
  } // end insertComponents()

  // Rebuilds the hierarchy of loaded nodes.
  public final void refresh() throws DbException {
    refresh((DynamicNode) getRoot());
  }

  // Reload all
  // Use this method instead of refresh() if refresh() doesn't work.
  public final void reloadAll() throws DbException {
    setRoot(createRootNode());
  }

  protected final void refresh(DynamicNode parentNode) throws DbException {
    if (ApplicationContext.getFocusManager().isGuiLocked()) return;

    if (parentNode == null) return;
    if (!parentNode.hasLoaded() || parentNode.isLeaf()) {
      if (parentNode.getUserObject() instanceof DbObject)
        updateNode((DbObject) parentNode.getUserObject());
      return;
    }
    Object userObject = parentNode.getUserObject();
    if (userObject == ROOT) {
      int count = getChildCount(parentNode);
      for (int i = 0; i < count; i++) {
        DynamicNode node = (DynamicNode) getChild(parentNode, i);
        userObject = node.getUserObject();
        if (userObject == DB_RAM) refresh(node);
        else {
          if (((Db) userObject).isValid()) {
            ((Db) userObject).beginTrans(Db.READ_TRANS);
            refresh(node);
            ((Db) userObject).commitTrans();
          } else {
            removeNode(node);
          }
        }
      }
      return;
    }
    if (userObject == DB_RAM) {
      int count = getChildCount(parentNode);
      for (int i = count - 1; i >= 0; i--) {
        DynamicNode node = (DynamicNode) getChild(parentNode, i);
        Db db = ((DbObject) node.getUserObject()).getDb();
        if (!db.isValid()) {
          removeNode(node);
          continue;
        }
        db.beginTrans(Db.READ_TRANS);
        refresh(node);
        // refresh the display text for the projects - we do not want to
        // apply a full update
        // using update(dbo) because we want to preserve the expanded
        // state for projects.
        node.setDisplayText(getDisplayText(null, (DbObject) node.getUserObject()));
        db.commitTrans();
      }
      // check for missing Db
      Db[] dbs = Db.getDbs();
      for (int i = 0; i < dbs.length; i++) {
        if (!dbs[i].isValid() || !(dbs[i] instanceof DbRAM)) continue;
        DynamicNode dbNode = getDynamicNode(parentNode, dbs[i], 0);
        if (dbNode != null) continue;
        dbs[i].beginTrans(Db.READ_TRANS);
        DbEnumeration dbEnum = dbs[i].getRoot().getComponents().elements(DbProject.metaClass);
        if (dbEnum.hasMoreElements()) {
          getDynamicNode(dbEnum.nextElement(), false);
        }
        dbEnum.close();
        dbs[i].commitTrans();
      }
      return;
    }

    SrVector children = new SrVector(10);
    if (userObject instanceof Db) {
      insertProjects(children, (Db) userObject);
      children.sort();
    } else if (((DbObject) userObject).isAlive()) {
      insertComponents(children, (DbObject) userObject);
      if (childrenAreSorted((DbObject) userObject))
        children.sort(getComparator((DbObject) userObject));
    }

    DynamicNode groupNode = null;
    int index = 0;
    int iGroup = 0;
    for (int i = 0; i < children.size(); i++) {
      DynamicNode srcNode = (DynamicNode) children.elementAt(i);
      GroupParams group = srcNode.getGroupParams();
      if (group.name == null) {
        refreshNode(srcNode, parentNode, index);
        index++;
      } else {
        if (groupNode == null || !groupNode.toString().equals(group.name)) {
          if (groupNode != null) deleteNodes(groupNode, iGroup);
          groupNode = getGroupNode(parentNode, group, index);
          if (groupNode == null) {
            groupNode = createGroupNode(group);
            insertNodeInto(groupNode, parentNode, index);
          } else if (groupNode != getChild(parentNode, index)) {
            removeNodeFromParent(groupNode);
            insertNodeInto(groupNode, parentNode, index);
          }
          index++;
          iGroup = 0;
        }
        refreshNode(srcNode, groupNode, iGroup);
        iGroup++;
      }
    }
    if (groupNode != null) deleteNodes(groupNode, iGroup);
    deleteNodes(parentNode, index);

    // Refresh subnodes in a separate pass to avoid interference from
    // automatic
    // adding of a primary node when adding a secondary node.
    refreshChildren(parentNode);
  }

  private void refreshNode(DynamicNode srcNode, DynamicNode parentNode, int index)
      throws DbException {
    DynamicNode dstNode = getDynamicNode(parentNode, srcNode.getUserObject(), index);
    if (dstNode == null) {
      insertNodeInto(srcNode, parentNode, index);
    } else {
      if (dstNode != getChild(parentNode, index)) {
        removeNodeFromParent(dstNode);
        insertNodeInto(dstNode, parentNode, index);
      }
      if (dstNode.getUserObject() instanceof DynamicNode) {
        nodeChanged(dstNode); // in case text of primary node has
        // changed
      } else if (!dstNode.toString().equals(srcNode.toString())
          || dstNode.getIcon() != srcNode.getIcon()
          || (dstNode.getToolTips() != null
              && !dstNode.getToolTips().equals(srcNode.getToolTips()))) {
        dstNode.setDisplayText(srcNode.toString(), srcNode.getEditText());
        dstNode.setIcon(srcNode.getIcon());
        dstNode.setToolTips(srcNode.getToolTips());
        nodeChanged(dstNode);
      }
    }
  }

  private void deleteNodes(DynamicNode parentNode, int iStart) {
    while (iStart < getChildCount(parentNode))
      removeNodeFromParent((DynamicNode) getChild(parentNode, iStart));
  }

  protected final void refreshChildren(DynamicNode parentNode) throws DbException {
    int count = getChildCount(parentNode);
    for (int i = 0; i < count; i++) {
      DynamicNode node = (DynamicNode) getChild(parentNode, i);
      if (node.getUserObject() instanceof GroupParams) refreshChildren(node);
      else refresh(node);
    }
  }

  private DynamicNode getGroupNode(DynamicNode parentNode, GroupParams group, int iStart) {
    DynamicNode nodeFound = null;
    int count = getChildCount(parentNode);
    for (int i = iStart; i < count; i++) {
      DynamicNode node = (DynamicNode) getChild(parentNode, i);
      if (node.getUserObject() instanceof GroupParams && node.toString().equals(group.name)) {
        nodeFound = node;
        break;
      }
    }
    return nodeFound;
  }

  /*
   * Search an object in the whole tree. For optimization, we build the inverse path of <dbo>
   * (from the child node to the root node), by getting the information from the database. Then,
   * instead of scanning the whole tree, we just follow the path obtained from the database. This
   * way, we always find a primary node (anyway if a secondary node exists, its primary node
   * necessarily exists).
   */
  public final DynamicNode getDynamicNode(DbObject dbo, boolean load) throws DbException {
    return getDynamicNode(dbo, Db.NEW_VALUE, load);
  }

  public final DynamicNode getDynamicNode(DbObject dbo, int which, boolean load)
      throws DbException {
    SrVector path = new SrVector(10);
    Db db = dbo.getDb();
    db.beginTrans(Db.READ_TRANS);
    while (dbo != null && !(dbo instanceof DbRoot)) {
      path.addElement(dbo);
      dbo = getDbParent(dbo, which);
    }
    if (db instanceof DbRAM) {
      if (DB_RAM != ((DynamicNode) getRoot()).getUserObject()) path.addElement(DB_RAM);
    } else path.addElement(db);

    DynamicNode nodeFound = (DynamicNode) getRoot();
    for (int i = path.size(); --i >= 0; ) {
      if (load) loadChildren(nodeFound);
      Object userObj = path.elementAt(i);
      dbo = null;
      DbObject dbParent = null;
      GroupParams group = GroupParams.defaultGroupParams;
      DynamicNode groupNode = null;
      DynamicNode node = null;
      if (userObj instanceof DbObject) {
        dbo = (DbObject) userObj;
        dbParent = getDbParent(dbo, which);
        group = getGroupParams(dbParent, dbo);
      }
      if (group.name == null) {
        node = getDynamicNode(nodeFound, userObj, 0);
      } else {
        groupNode = getGroupNode(nodeFound, group, 0);
        if (groupNode != null) node = getDynamicNode(groupNode, userObj, 0);
      }
      if (node == null && nodeFound.hasLoaded() && !nodeFound.isLeaf() && which == Db.NEW_VALUE) {

        SemanticalModel model = ApplicationContext.getSemanticalModel();
        boolean visible = model.isVisibleOnScreen(dbParent, dbo, Explorer.class);

        if (visible) {
          node = createPrimaryNode(dbParent, dbo);
          if (group.name == null) {
            insertNodeInto(node, nodeFound, getInsertionIndex(dbParent, dbo, nodeFound, node));
          } else {
            if (groupNode == null) {
              groupNode = createGroupNode(group);
              insertNodeInto(groupNode, nodeFound, getSortedIndex(nodeFound, groupNode));
            }
            insertNodeInto(node, groupNode, getInsertionIndex(dbParent, dbo, groupNode, node));
          }
        }
      }
      nodeFound = node;
      if (nodeFound == null) break;
    }
    db.commitTrans();
    return nodeFound;
  }

  private DynamicNode getDynamicNode(DynamicNode parentNode, Object userObj, int iStart)
      throws DbException {
    DynamicNode nodeFound = null;
    int count = getChildCount(parentNode);
    for (int i = iStart; i < count; i++) {
      DynamicNode node = (DynamicNode) getChild(parentNode, i);
      if (node.getUserObject() == userObj) {
        nodeFound = node;
        break;
      }
    }
    return nodeFound;
  }

  // The value type for the returned MetaField MUST be String.
  // Default will use getName and setName defined on DbObject
  protected MetaField getEditableMetaField(DbObject dbo) {
    return null;
  }

  // Called at the end of a cell edition, to process the edition result.
  public final void valueForPathChanged(TreePath path, Object newValue) {
    DynamicNode node = (DynamicNode) path.getLastPathComponent();
    Object obj = node.getRealObject();
    if (obj instanceof DbObject) {
      boolean editable = false;
      try {
        Db db = ((DbObject) obj).getDb();
        if (db.isValid()) {
          db.beginReadTrans();
          MetaField editableMetaField = getEditableMetaField((DbObject) obj);
          if (editableMetaField == null)
            editable =
                ApplicationContext.getSemanticalModel()
                    .isNameEditable((DbObject) obj, Explorer.class);
          else
            editable =
                ApplicationContext.getSemanticalModel()
                    .isEditable(editableMetaField, (DbObject) obj, Explorer.class);
          db.commitTrans();
        }
      } catch (DbException ex) {
        org.modelsphere.jack.util.ExceptionHandler.processUncatchedException(
            ApplicationContext.getDefaultMainFrame(), ex);
        editable = false;
      }
      if (!editable) return;
    } else {
      return;
    }
    DbObject semObj = (DbObject) obj;
    try {
      MetaField editableMetaField = getEditableMetaField(semObj);
      String transName =
          (editableMetaField == null
              ? LocaleMgr.action.getString("rename")
              : MessageFormat.format(kUpdate0, new Object[] {editableMetaField.getGUIName()}));
      semObj.getDb().beginTrans(Db.WRITE_TRANS, transName);
      if (editableMetaField == null) {
        if (semObj.getTransStatus() == Db.OBJ_REMOVED) return;
        semObj.setName((String) newValue);
      } else semObj.set(editableMetaField, (String) newValue);
      semObj.getDb().commitTrans();
      nodeChanged(node); // in case it is a secondary node (only the
      // primary node is updated by the refresh
      // listener).
    } catch (Exception ex) {
      org.modelsphere.jack.util.ExceptionHandler.processUncatchedException(
          ApplicationContext.getDefaultMainFrame(), ex);
    }
  }

  // ////////////////////////////////////
  // DbRefreshListener SUPPORT
  //
  // Overridden
  public void refreshAfterDbUpdate(DbUpdateEvent evt) throws DbException {
    if (evt.metaField == DbObject.fComponents) { // add, remove, or move to
      // another parent
      if (evt.neighbor instanceof DbGraphicalObjectI) // optimization:
        // discard
        // immediately the
        // graphical objects
        return;
      if (evt.op == Db.REMOVE_FROM_RELN) {
        // DbObject removed or with a new parent: remove the node from
        // its old parent if loaded.
        DynamicNode node = getDynamicNode(evt.neighbor, Db.OLD_VALUE, false);
        if (node != null) removeNode(node);
      } else if (evt.op == Db.ADD_TO_RELN) {
        // DbObject added or with a new parent: if its new parent has
        // its chidren loaded,
        // add a node for the new child.
        getDynamicNode(evt.neighbor, false);
      } else { // Db.REINSERT_IN_RELN
        DynamicNode node = getDynamicNode(evt.neighbor, false);
        if ((node != null && !childrenAreSorted(evt.dbo))
            || (node != null && !node.getGroupParams().sorted)) {
          DynamicNode parentNode = (DynamicNode) node.getParent();
          updateInsertIndexInChildrenOfNode(parentNode);
          removeNodeFromParent(node);
          int index = getInsertionIndex(evt.dbo, evt.neighbor, parentNode, node);
          insertNodeInto(node, parentNode, index);
        }
      }
    }
    // name refresh
    else if (evt.metaField == DbSemanticalObject.fName || tooltipsFields.contains(evt.metaField)) {
      updateNode(evt.dbo);
    }
  }

  //
  // End of DbRefreshListener SUPPORT
  // ////////////////////////////////////

  protected final void updateInsertIndexInChildrenOfNode(DynamicNode parentNode)
      throws DbException {
    Enumeration childrenEnum = parentNode.children();
    while (childrenEnum.hasMoreElements()) {
      DynamicNode node = (DynamicNode) childrenEnum.nextElement();
      DbObject dbo = (DbObject) node.getRealObject();
      DbObject dbParent = dbo.getComposite();
      node.insertIndex = getSequence(dbParent, dbo);
    }
  }

  // Remove the group node if this node is the last of its group.
  protected final void removeNode(DynamicNode node) {
    DynamicNode parentNode = (DynamicNode) node.getParent();
    if (parentNode.getUserObject() instanceof GroupParams && getChildCount(parentNode) == 1)
      removeNodeFromParent(parentNode);
    else removeNodeFromParent(node);
  }

  // This method refreshes the display text of a node.
  protected final void updateNode(DbObject dbo) throws DbException {
    DynamicNode node = getDynamicNode(dbo, false);
    if (node == null) return;

    // backup selection and restore later
    Object focusObject = ApplicationContext.getFocusManager().getFocusObject();
    TreePath[] selPaths = null;
    if (focusObject instanceof ExplorerView) {
      selPaths = ((ExplorerView) focusObject).getSelectionPaths();
    }

    String displayText = getDisplayText(null, dbo);
    String editText = getEditText(null, dbo);
    String toolTipText = getToolTipsText(null, dbo);
    Icon icon = getIcon(dbo);

    node.setDisplayText(displayText, editText);
    node.setToolTips(toolTipText);
    node.setIcon(icon);
    nodeChanged(node);
    DynamicNode parentNode = (DynamicNode) node.getParent();
    Object parent = parentNode.getUserObject();
    if (!(parent instanceof DbObject) || childrenAreSorted((DbObject) parent)) {
      removeNodeFromParent(node);
      int index = getSortedIndex(parentNode, node);
      insertNodeInto(node, parentNode, index);
    }

    // Restore selection
    if (focusObject instanceof ExplorerView && selPaths != null) {
      ExplorerView explorerView = (ExplorerView) focusObject;
      try { // Should not occurs
        explorerView.setSelectionPaths(selPaths);
      } catch (Exception e) {
        explorerView.setSelectionPaths(new TreePath[] {});
      }
    }
  }

  // This method updates the explorer for changes in a relation N where all
  // children are secondary nodes.
  // IMPORTANT: a node whose children are secondary nodes cannot have grouping
  // nodes.
  protected final void updateSecondaryChildren(DbUpdateEvent evt) throws DbException {
    if (!ApplicationContext.getSemanticalModel()
        .isVisibleOnScreen(evt.dbo, evt.neighbor, Explorer.class)) return;
    DynamicNode parentNode = getDynamicNode(evt.dbo, false);
    if (parentNode == null || !parentNode.hasLoaded()) return;
    DynamicNode childNode = null;
    int nb = getChildCount(parentNode);
    for (int i = 0; i < nb; i++) {
      DynamicNode node = (DynamicNode) getChild(parentNode, i);
      if (((DynamicNode) node.getUserObject()).getUserObject() == evt.neighbor) {
        childNode = node;
        break;
      }
    }
    if (evt.op == Db.REMOVE_FROM_RELN) {
      if (childNode != null) removeNodeFromParent(childNode);
    } else if (evt.op == Db.ADD_TO_RELN) {
      if (childNode == null) {
        childNode = createSecondaryNode(evt.dbo, evt.neighbor);
        int index = getInsertionIndex(evt.dbo, evt.neighbor, parentNode, childNode);
        insertNodeInto(childNode, parentNode, index);
      }
    } else { // Db.REINSERT_IN_RELN
      if (!childrenAreSorted(evt.dbo) && childNode != null) {
        removeNodeFromParent(childNode);
        int index = getInsertionIndex(evt.dbo, evt.neighbor, parentNode, childNode);
        insertNodeInto(childNode, parentNode, index);
      }
    }
  }

  protected final int getInsertionIndex(
      DbObject dbParent, DbObject dbo, DynamicNode parentNode, DynamicNode node)
      throws DbException {
    if (childrenAreSorted(dbParent)) return getSortedIndex(parentNode, node);
    return Math.max(0, Math.min(getIndex(dbParent, dbo), getChildCount(parentNode)));
  }

  // Proceed by binary search because comparison may be long.
  protected final int getSortedIndex(DynamicNode parentNode, DynamicNode childNode) {
    int lo = -1;
    int hi = getChildCount(parentNode);
    while (lo + 1 != hi) {
      int mid = (lo + hi) / 2;
      DynamicNode node = (DynamicNode) getChild(parentNode, mid);
      if (childNode.compareTo(node) < 0) hi = mid;
      else lo = mid;
    }
    return hi;
  }

  // Overridden
  protected boolean childrenAreSorted(DbObject dbo) throws DbException {
    return true;
  }

  // Override to provide a custom Comparator.
  // If null, a default Comparator will be used.
  // parent may be null.
  // Note, this one should be moved in the group (providing a GUI name for
  // each available comparators for the group
  // and an option, may be an Action, for user selection of the comparator)
  protected Comparator getComparator(DbObject parent) throws DbException {
    return null;
  }

  protected Icon getIcon(DbObject dbo) throws DbException {
    return dbo.getSemanticalIcon(DbObject.SHORT_FORM);
  }

  // Must return the index in the children list if childrenAreSorted(dbParent)
  // returns false.
  // Overridden
  protected int getIndex(DbObject dbParent, DbObject dbo) throws DbException {
    return dbParent.getComponents().indexOf(dbo);
  }

  // Overridden
  protected DbObject getDbParent(DbObject dbo, int which) throws DbException {
    return (DbObject) dbo.get(DbObject.fComposite, which);
  }
}