Ejemplo n.º 1
0
  public static void reportExceptions(
      ArrayList exceptions, DataSourceQuery dataSourceQuery, WorkbenchContext context) {
    context
        .getIWorkbench()
        .getFrame()
        .getOutputFrame()
        .addHeader(
            1,
            exceptions.size()
                + " problem"
                + StringUtil.s(exceptions.size())
                + " loading "
                + dataSourceQuery.toString()
                + "."
                + ((exceptions.size() > 10) ? " First and last five:" : ""));
    context.getIWorkbench().getFrame().getOutputFrame().addText("See View / Log for stack traces");
    context.getIWorkbench().getFrame().getOutputFrame().append("<ul>");

    Collection exceptionsToReport =
        exceptions.size() <= 10
            ? exceptions
            : CollectionUtil.concatenate(
                Arrays.asList(
                    new Collection[] {
                      exceptions.subList(0, 5),
                      exceptions.subList(exceptions.size() - 5, exceptions.size())
                    }));
    for (Iterator j = exceptionsToReport.iterator(); j.hasNext(); ) {
      Exception exception = (Exception) j.next();
      context.getIWorkbench().getGuiComponent().log(StringUtil.stackTrace(exception));
      context.getIWorkbench().getFrame().getOutputFrame().append("<li>");
      context
          .getIWorkbench()
          .getFrame()
          .getOutputFrame()
          .append(GUIUtil.escapeHTML(WorkbenchFrameImpl.toMessage(exception), true, true));
      context.getIWorkbench().getFrame().getOutputFrame().append("</li>");
    }
    context.getIWorkbench().getFrame().getOutputFrame().append("</ul>");
  }
Ejemplo n.º 2
0
/** This class is responsible for the main window of the JUMP application. */
public class WorkbenchFrameImpl extends WorkbenchFrame
    implements LayerViewPanelContext, ViewportListener, WorkbenchGuiComponent {
  BorderLayout borderLayout1 = new BorderLayout();
  JLabel coordinateLabel = new JLabel();
  private JPopupMenu popupMenu = new TrackedPopupMenu();
  JMenuBar menuBar = new JMenuBar();
  JMenu fileMenu = (JMenu) FeatureInstaller.installMnemonic(new JMenu("File"), menuBar);
  JMenuItem exitMenuItem = FeatureInstaller.installMnemonic(new JMenuItem("E&xit"), fileMenu);
  GridBagLayout gridBagLayout1 = new GridBagLayout();
  JLabel messageLabel = new JLabel();
  JPanel statusPanel = new JPanel();
  JLabel timeLabel = new JLabel();

  // <<TODO:FEATURE>> Before JUMP Workbench closes, prompt the user to save
  // any
  // unsaved layers [Jon Aquino]
  WorkbenchToolBar toolBar;
  JMenu windowMenu = (JMenu) FeatureInstaller.installMnemonic(new JMenu("Window"), menuBar);
  private TitledPopupMenu categoryPopupMenu =
      new TitledPopupMenu() {
        {
          addPopupMenuListener(
              new PopupMenuListener() {
                public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                  LayerNamePanel panel =
                      ((LayerNamePanelProxy) getActiveInternalFrame()).getLayerNamePanel();
                  setTitle(
                      (panel.selectedNodes(Category.class).size() != 1)
                          ? ("("
                              + panel.selectedNodes(Category.class).size()
                              + " categories selected)")
                          : ((Category) panel.selectedNodes(Category.class).iterator().next())
                              .getName());
                }

                public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}

                public void popupMenuCanceled(PopupMenuEvent e) {}
              });
        }
      };

  private JDesktopPane desktopPane = new JDesktopPane();

  // <<TODO:REMOVE>> Actually we're not using the three optimization
  // parameters
  // below. Remove. [Jon Aquino]
  private int envelopeRenderingThreshold = 500;
  private HTMLFrame outputFrame =
      new HTMLFrame(this) {
        public void setTitle(String title) {
          // Don't allow the title of the output frame to be changed.
        }

        {
          super.setTitle("Output");
        }
      };

  private ImageIcon icon;
  private TitledPopupMenu layerNamePopupMenu =
      new TitledPopupMenu() {
        {
          addPopupMenuListener(
              new PopupMenuListener() {
                public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                  LayerNamePanel panel =
                      ((LayerNamePanelProxy) getActiveInternalFrame()).getLayerNamePanel();
                  setTitle(
                      (panel.selectedNodes(Layer.class).size() != 1)
                          ? ("(" + panel.selectedNodes(Layer.class).size() + " layers selected)")
                          : ((Layerable) panel.selectedNodes(Layer.class).iterator().next())
                              .getName());
                }

                public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}

                public void popupMenuCanceled(PopupMenuEvent e) {}
              });
        }
      };

  private TitledPopupMenu wmsLayerNamePopupMenu =
      new TitledPopupMenu() {
        {
          addPopupMenuListener(
              new PopupMenuListener() {
                public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                  LayerNamePanel panel =
                      ((LayerNamePanelProxy) getActiveInternalFrame()).getLayerNamePanel();
                  setTitle(
                      (panel.selectedNodes(WMSLayer.class).size() != 1)
                          ? ("("
                              + panel.selectedNodes(WMSLayer.class).size()
                              + " WMS layers selected)")
                          : ((Layerable) panel.selectedNodes(WMSLayer.class).iterator().next())
                              .getName());
                }

                public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}

                public void popupMenuCanceled(PopupMenuEvent e) {}
              });
        }
      };

  private LayerNamePanelListener layerNamePanelListener =
      new LayerNamePanelListener() {
        public void layerSelectionChanged() {
          toolBar.updateEnabledState();
        }
      };

  private LayerViewPanelListener layerViewPanelListener =
      new LayerViewPanelListener() {
        public void cursorPositionChanged(String x, String y) {
          coordinateLabel.setText("(" + x + ", " + y + ")");
        }

        public void selectionChanged() {
          toolBar.updateEnabledState();
        }

        public void fenceChanged() {
          toolBar.updateEnabledState();
        }

        public void painted(Graphics graphics) {}
      };

  // <<TODO:NAMING>> This name is not clear [Jon Aquino]
  private int maximumFeatureExtentForEnvelopeRenderingInPixels = 10;

  // <<TODO:NAMING>> This name is not clear [Jon Aquino]
  private int minimumFeatureExtentForAnyRenderingInPixels = 2;
  private StringBuffer log = new StringBuffer();
  private int taskSequence = 1;
  private WorkbenchContext workbenchContext;
  private JLabel memoryLabel = new JLabel();
  private String lastStatusMessage = "";
  private Set choosableStyleClasses = new HashSet();
  private JLabel wmsLabel = new JLabel();
  private ArrayList easyKeyListeners = new ArrayList();
  private Map nodeClassToLayerNamePopupMenuMap =
      CollectionUtil.createMap(
          new Object[] {
            Layer.class,
            layerNamePopupMenu,
            WMSLayer.class,
            wmsLayerNamePopupMenu,
            Category.class,
            categoryPopupMenu
          });
  private int positionIndex = -1;
  private int primaryInfoFrameIndex = -1;

  public WorkbenchFrameImpl(String title, ImageIcon icon, final WorkbenchContext workbenchContext)
      throws Exception {
    setTitle(title);
    new Timer(
            1000,
            new ActionListener() {
              public void actionPerformed(ActionEvent e) {
                memoryLabel.setText(getMBCommittedMemory() + " MB Committed Memory");
                memoryLabel.setToolTipText(
                    LayerManager.layerManagerCount()
                        + " Layer Manager"
                        + StringUtil.s(LayerManager.layerManagerCount()));
              }
            })
        .start();
    this.workbenchContext = workbenchContext;
    this.icon = icon;
    toolBar = new WorkbenchToolBar(workbenchContext);
    try {
      jbInit();
      configureStatusLabel(messageLabel, 300);
      configureStatusLabel(coordinateLabel, 150);
      configureStatusLabel(timeLabel, 200);
      configureStatusLabel(wmsLabel, 100);
    } catch (Exception e) {
      e.printStackTrace();
    }
    new RecursiveKeyListener(this) {
      public void keyTyped(KeyEvent e) {
        for (Iterator i = easyKeyListeners.iterator(); i.hasNext(); ) {
          KeyListener l = (KeyListener) i.next();
          l.keyTyped(e);
        }
      }

      public void keyPressed(KeyEvent e) {
        for (Iterator i = new ArrayList(easyKeyListeners).iterator(); i.hasNext(); ) {
          KeyListener l = (KeyListener) i.next();
          l.keyPressed(e);
        }
      }

      public void keyReleased(KeyEvent e) {
        for (Iterator i = new ArrayList(easyKeyListeners).iterator(); i.hasNext(); ) {
          KeyListener l = (KeyListener) i.next();
          l.keyReleased(e);
        }
      }
    };
    installKeyboardShortcutListener();
  }

  /**
   * Unlike #add(KeyListener), listeners registered using this method are notified when KeyEvents
   * occur on this frame's child components. Note: Bug: KeyListeners registered using this method
   * may receive events multiple times.
   *
   * @see #addKeyboardShortcut
   */
  public void addEasyKeyListener(KeyListener l) {
    easyKeyListeners.add(l);
  }

  public void removeEasyKeyListener(KeyListener l) {
    easyKeyListeners.remove(l);
  }

  public String getMBCommittedMemory() {
    return new DecimalFormat("###,###")
        .format(
            (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())
                / (1024 * 1024d));
  }

  /**
   * @param newEnvelopeRenderingThreshold the number of on-screen features above which envelope
   *     rendering should occur
   */
  public void setEnvelopeRenderingThreshold(int newEnvelopeRenderingThreshold) {
    envelopeRenderingThreshold = newEnvelopeRenderingThreshold;
  }

  public void setMaximumFeatureExtentForEnvelopeRenderingInPixels(
      int newMaximumFeatureExtentForEnvelopeRenderingInPixels) {
    maximumFeatureExtentForEnvelopeRenderingInPixels =
        newMaximumFeatureExtentForEnvelopeRenderingInPixels;
  }

  public void log(String message) {
    log.append(new Date() + "  " + message + System.getProperty("line.separator"));
  }

  public String getLog() {
    return log.toString();
  }

  public void setMinimumFeatureExtentForAnyRenderingInPixels(
      int newMinimumFeatureExtentForAnyRenderingInPixels) {
    minimumFeatureExtentForAnyRenderingInPixels = newMinimumFeatureExtentForAnyRenderingInPixels;
  }

  public void displayLastStatusMessage() {
    setStatusMessage(lastStatusMessage);
  }

  public void setStatusMessage(String message) {
    lastStatusMessage = message;
    setStatusBarText(message);
    setStatusBarTextHighlighted(false, null);
  }

  private void setStatusBarText(String message) {
    // <<TODO:IMPROVE>> Treat null messages like "" [Jon Aquino]
    messageLabel.setText((message == "") ? " " : message);
    messageLabel.setToolTipText(message);

    // Make message at least a space so that status bar won't collapse [Jon
    // Aquino]
  }

  /** To highlight a message, call #warnUser. */
  private void setStatusBarTextHighlighted(boolean highlighted, Color color) {
    // Use #coordinateLabel rather than (unattached) dummy label because
    // dummy label's background does not change when L&F changes. [Jon
    // Aquino]
    messageLabel.setForeground(highlighted ? Color.black : coordinateLabel.getForeground());
    messageLabel.setBackground(highlighted ? color : coordinateLabel.getBackground());
  }

  public void setTimeMessage(String message) {
    // <<TODO:IMPROVE>> Treat null messages like "" [Jon Aquino]
    timeLabel.setText((message == "") ? " " : message);

    // Make message at least a space so that status bar won't collapse [Jon
    // Aquino]
  }

  public TaskComponent getActiveTaskComponent() {
    if (desktopPane.getSelectedFrame() instanceof TaskComponent)
      return (TaskComponent) desktopPane.getSelectedFrame();
    else return null;
  }

  public JInternalFrame getActiveInternalFrame() {

    return desktopPane.getSelectedFrame();
  }

  public JInternalFrame[] getInternalFrames() {
    return desktopPane.getAllFrames();
  }

  public TitledPopupMenu getCategoryPopupMenu() {
    return categoryPopupMenu;
  }

  public WorkbenchContext getContext() {
    return workbenchContext;
  }

  public Container getDesktopPane() {
    return desktopPane;
  }

  public int getEnvelopeRenderingThreshold() {
    return envelopeRenderingThreshold;
  }

  public TitledPopupMenu getLayerNamePopupMenu() {
    return layerNamePopupMenu;
  }

  public TitledPopupMenu getWMSLayerNamePopupMenu() {
    return wmsLayerNamePopupMenu;
  }

  public LayerViewPanelListener getLayerViewPanelListener() {
    return layerViewPanelListener;
  }

  public Map getNodeClassToPopupMenuMap() {
    return nodeClassToLayerNamePopupMenuMap;
  }

  public LayerNamePanelListener getLayerNamePanelListener() {
    return layerNamePanelListener;
  }

  public int getMaximumFeatureExtentForEnvelopeRenderingInPixels() {
    return maximumFeatureExtentForEnvelopeRenderingInPixels;
  }

  public int getMinimumFeatureExtentForAnyRenderingInPixels() {
    return minimumFeatureExtentForAnyRenderingInPixels;
  }

  public HTMLFrame getOutputFrame() {
    return outputFrame;
  }

  public WorkbenchToolBar getToolBar() {
    return toolBar;
  }
  // Aqui no creo que deba ir un TaskFrame por si se quiere activar otro tipo de JinternalFrame
  public void activateFrame(JInternalFrame frame) {
    frame.moveToFront();
    frame.requestFocus();
    try {
      frame.setSelected(true);
      if (!(frame instanceof TaskFrame)) {
        frame.setMaximum(false);
      }
    } catch (PropertyVetoException e) {
      warnUser(StringUtil.stackTrace(e));
    }
  }

  /**
   * If internalFrame is a LayerManagerProxy, the close behaviour will be altered so that the user
   * is prompted if it is the last window on the LayerManager.
   */
  // REVISAR: No parece necesario evitar el JInternalFrame
  // public void addInternalFrame(final TaskComponent internalFrame) {
  public void addInternalFrame(final JInternalFrame internalFrame) {

    addInternalFrame(internalFrame, false, true);
  }
  // REVISAR: No parece necesario evitar el JInternalFrame
  // public void addInternalFrame(final TaskComponent internalFrame,
  public void addInternalFrame(
      final JInternalFrame internalFrame, boolean alwaysOnTop, boolean autoUpdateToolBar) {
    if (internalFrame instanceof LayerManagerProxy) {
      setClosingBehaviour((LayerManagerProxy) internalFrame);
      installTitleBarModifiedIndicator((LayerManagerProxy) internalFrame);
    }

    // <<TODO:IMPROVE>> Listen for when the frame closes, and when it does,
    // activate the topmost frame. Because Swing does not seem to do this
    // automatically. [Jon Aquino]
    internalFrame.setFrameIcon(icon);

    // Call JInternalFrame#setVisible before JDesktopPane#add; otherwise,
    // the
    // TreeLayerNamePanel starts too narrow (100 pixels or so) for some
    // reason.
    // <<TODO>>Investigate. [Jon Aquino]
    internalFrame.setVisible(true);
    desktopPane.add(
        (Component) internalFrame,
        alwaysOnTop ? JLayeredPane.PALETTE_LAYER : JLayeredPane.DEFAULT_LAYER);
    if (autoUpdateToolBar) {
      internalFrame.addInternalFrameListener(
          new InternalFrameListener() {
            public void internalFrameActivated(InternalFrameEvent e) {
              toolBar.updateEnabledState();

              // Associate current cursortool with the new frame [Jon
              // Aquino]
              toolBar.reClickSelectedCursorToolButton();
            }

            public void internalFrameClosed(InternalFrameEvent e) {
              toolBar.updateEnabledState();
            }

            public void internalFrameClosing(InternalFrameEvent e) {
              toolBar.updateEnabledState();
            }

            public void internalFrameDeactivated(InternalFrameEvent e) {
              toolBar.updateEnabledState();
            }

            public void internalFrameDeiconified(InternalFrameEvent e) {
              toolBar.updateEnabledState();
            }

            public void internalFrameIconified(InternalFrameEvent e) {
              toolBar.updateEnabledState();
            }

            public void internalFrameOpened(InternalFrameEvent e) {
              toolBar.updateEnabledState();
            }
          });

      // Call #activateFrame *after* adding the listener. [Jon Aquino]
      activateFrame(internalFrame);
      position(internalFrame);
    }
  }

  private void installTitleBarModifiedIndicator(final LayerManagerProxy internalFrame) {
    final JInternalFrame i = (JInternalFrame) internalFrame;
    new Block() {
      // Putting updatingTitle in a Block is better than making it an
      // instance variable, because this way there is one updatingTitle
      // for each
      // internal frame, rather than one for all internal frames. [Jon
      // Aquino]
      private boolean updatingTitle = false;

      private void updateTitle() {
        if (updatingTitle) {
          return;
        }
        updatingTitle = true;
        try {
          String newTitle = i.getTitle();
          if (newTitle.charAt(0) == '*') {
            newTitle = newTitle.substring(1);
          }
          if (!internalFrame
              .getLayerManager()
              .getLayersWithModifiedFeatureCollections()
              .isEmpty()) {
            newTitle = '*' + newTitle;
          }
          i.setTitle(newTitle);
        } finally {
          updatingTitle = false;
        }
      }

      public Object yield() {
        internalFrame
            .getLayerManager()
            .addLayerListener(
                new LayerListener() {
                  public void layerChanged(LayerEvent e) {
                    if ((e.getType() == LayerEventType.METADATA_CHANGED)
                        || (e.getType() == LayerEventType.REMOVED)) {
                      updateTitle();
                    }
                  }

                  public void categoryChanged(CategoryEvent e) {}

                  public void featuresChanged(FeatureEvent e) {}
                });
        i.addPropertyChangeListener(
            JInternalFrame.TITLE_PROPERTY,
            new PropertyChangeListener() {
              public void propertyChange(PropertyChangeEvent e) {
                updateTitle();
              }
            });

        return null;
      }
    }.yield();
  }

  private void setClosingBehaviour(final LayerManagerProxy internalFrame) {
    final JInternalFrame i = (JInternalFrame) internalFrame;
    i.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    i.addInternalFrameListener(
        new InternalFrameAdapter() {
          public void internalFrameClosing(InternalFrameEvent e) {
            if (1
                == getInternalFramesAssociatedWith((LayerManager) internalFrame.getLayerManager())
                    .size()) {
              if (confirmClose(
                  "Close Task",
                  internalFrame.getLayerManager().getLayersWithModifiedFeatureCollections())) {
                GUIUtil.dispose(i, desktopPane);
                internalFrame.getLayerManager().dispose();
              }
            } else {
              GUIUtil.dispose(i, desktopPane);
            }
          }
        });
  }

  private Collection getInternalFramesAssociatedWith(LayerManager layerManager) {
    ArrayList internalFramesAssociatedWithLayerManager = new ArrayList();
    JInternalFrame[] internalFrames = getInternalFrames();
    for (int i = 0; i < internalFrames.length; i++) {
      if (internalFrames[i] instanceof LayerManagerProxy
          && (((LayerManagerProxy) internalFrames[i]).getLayerManager() == layerManager)) {
        internalFramesAssociatedWithLayerManager.add(internalFrames[i]);
      }
    }

    return internalFramesAssociatedWithLayerManager;
  }

  public TaskFrame addTaskFrame() {
    TaskFrame f = addTaskFrame(createTask());

    return f;
  }

  public Task createTask() {
    Task task = new Task();

    // LayerManager shouldn't automatically add categories in its
    // constructor.
    // Sometimes we want to create a LayerManager with no categories
    // (e.g. in OpenProjectPlugIn). [Jon Aquino]
    task.getLayerManager().addCategory(StandardCategoryNames.WORKING);
    task.getLayerManager().addCategory(StandardCategoryNames.SYSTEM);
    task.setName("Task " + taskSequence++);

    return task;
  }

  public TaskFrame addTaskFrame(Task task) {
    return addTaskFrame(new TaskFrame(task, workbenchContext));
  }

  public TaskFrame addTaskFrame(TaskFrame taskFrame) {
    taskFrame
        .getTask()
        .getLayerManager()
        .addLayerListener(
            new LayerListener() {
              public void featuresChanged(FeatureEvent e) {}

              public void categoryChanged(CategoryEvent e) {
                toolBar.updateEnabledState();
              }

              public void layerChanged(LayerEvent layerEvent) {
                toolBar.updateEnabledState();
              }
            });
    addInternalFrame(taskFrame);
    taskFrame
        .getLayerViewPanel()
        .getLayerManager()
        .getUndoableEditReceiver()
        .add(
            new UndoableEditReceiver.Listener() {
              public void undoHistoryChanged() {
                toolBar.updateEnabledState();
              }

              public void undoHistoryTruncated() {
                toolBar.updateEnabledState();
                log("Undo history was truncated");
              }
            });

    return taskFrame;
  }

  public void flash(final HTMLFrame frame) {
    final Color originalColor = frame.getBackgroundColor();
    new Timer(
            100,
            new ActionListener() {
              private int tickCount = 0;

              public void actionPerformed(ActionEvent e) {
                try {
                  tickCount++;
                  frame.setBackgroundColor(((tickCount % 2) == 0) ? originalColor : Color.yellow);
                  if (tickCount == 2) {
                    Timer timer = (Timer) e.getSource();
                    timer.stop();
                  }
                } catch (Throwable t) {
                  handleThrowable(t);
                }
              }
            })
        .start();
  }

  private void flashStatusMessage(final String message, final Color color) {
    new Timer(
            100,
            new ActionListener() {
              private int tickCount = 0;

              public void actionPerformed(ActionEvent e) {
                tickCount++;

                // This message is important, so overwrite whatever is on the
                // status bar. [Jon Aquino]
                setStatusBarText(message);
                setStatusBarTextHighlighted((tickCount % 2) == 0, color);
                if (tickCount == 4) {
                  Timer timer = (Timer) e.getSource();
                  timer.stop();
                }
              }
            })
        .start();
  }

  /**
   * Can be called regardless of whether the current thread is the AWT event dispatch thread.
   *
   * @param t Description of the Parameter
   */
  public void handleThrowable(final Throwable t) {
    log(StringUtil.stackTrace(t));

    Component parent = this;
    Window[] ownedWindows = getOwnedWindows();
    for (int i = 0; i < ownedWindows.length; i++) {
      if (ownedWindows[i] instanceof Dialog
          && ownedWindows[i].isVisible()
          && ((Dialog) ownedWindows[i]).isModal()) {
        parent = ownedWindows[i];

        break;
      }
    }
    //        if (lastFiveThrowableDates.size() == 5 && new Date().getTime()
    //                        - ((Date) lastFiveThrowableDates.get(0)).getTime() < 1000 * 60) {
    //            flashStatusMessage(t.toString(), Color.red);
    //        } else {
    handleThrowable(t, parent);
    //        }
  }

  public static void handleThrowable(final Throwable t, final Component parent) {
    t.printStackTrace(System.err);
    SwingUtilities.invokeLater(
        new Runnable() {
          public void run() {
            ErrorDialog.show(
                parent,
                StringUtil.toFriendlyName(t.getClass().getName()),
                toMessage(t),
                StringUtil.stackTrace(t));
          }
        });
  }

  private ArrayList lastFiveThrowableDates =
      new ArrayList() {
        public boolean add(Object o) {
          if (size() == 5) {
            remove(0);
          }
          return super.add(o);
        }
      };

  public static String toMessage(Throwable t) {
    String message;
    if (t.getLocalizedMessage() == null) {
      message = "No description was provided";
    } else if (t.getLocalizedMessage().toLowerCase().indexOf("side location conflict") > -1) {
      message = t.getLocalizedMessage() + " -- Check for invalid geometries.";
    } else {
      message = t.getLocalizedMessage();
    }

    return message + " (" + StringUtil.toFriendlyName(t.getClass().getName()) + ")";
  }

  public boolean hasInternalFrame(JInternalFrame internalFrame) {
    JInternalFrame[] frames = desktopPane.getAllFrames();
    for (int i = 0; i < frames.length; i++) {
      if (frames[i] == internalFrame) {
        return true;
      }
    }

    return false;
  }

  public void removeInternalFrame(JInternalFrame internalFrame) {
    // Looks like #closeFrame is the proper way to remove an internal
    // frame.
    // It will activate the next frame. [Jon Aquino]
    desktopPane.getDesktopManager().closeFrame(internalFrame);
  }

  public void warnUser(String warning) {
    log("Warning: " + warning);
    flashStatusMessage(warning, Color.yellow);
  }

  public void zoomChanged(Envelope modelEnvelope) {
    toolBar.updateEnabledState();
  }

  void exitMenuItem_actionPerformed(ActionEvent e) {
    closeApplication();
  }

  void this_componentShown(ComponentEvent e) {
    try {
      // If the first internal frame is not a TaskWindow (as may be the
      // case in
      // custom workbenches), #updateEnabledState() will ensure that the
      // cursor-tool buttons are disabled. [Jon Aquino]
      toolBar.updateEnabledState();
    } catch (Throwable t) {
      handleThrowable(t);
    }
  }

  void this_windowClosing(WindowEvent e) {
    closeApplication();
  }

  void windowMenu_menuSelected(MenuEvent e) {
    // <<TODO:MAINTAINABILITY>> This algorithm is not robust. It assumes
    // the Window
    // menu has exactly one "regular" menu item (newWindowMenuItem). [Jon
    // Aquino]
    if (windowMenu.getItemCount() > 0
        && windowMenu.getItem(0) != null
        && windowMenu
            .getItem(0)
            .getText()
            .equals(AbstractPlugIn.createName(CloneWindowPlugIn.class))) {
      JMenuItem newWindowMenuItem = windowMenu.getItem(0);
      windowMenu.removeAll();
      windowMenu.add(newWindowMenuItem);
      windowMenu.addSeparator();
    } else {
      // ezLink doesn't have a Clone Window menu [Jon Aquino]
      windowMenu.removeAll();
    }

    // final TaskComponent[] frames = (TaskComponent[]) desktopPane.getAllFrames();
    final JInternalFrame[] frames = desktopPane.getAllFrames();
    for (int i = 0; i < frames.length; i++) {
      JMenuItem menuItem = new JMenuItem();
      // Increase truncation threshold from 20 to 40, for eziLink [Jon
      // Aquino]
      menuItem.setText(GUIUtil.truncateString(frames[i].getTitle(), 40));
      associate(menuItem, frames[i]);
      windowMenu.add(menuItem);
    }
    if (windowMenu.getItemCount() == 0) {
      // For ezLink [Jon Aquino]
      windowMenu.add(new JMenuItem("(No Windows)"));
    }
  }

  private void associate(JMenuItem menuItem, final JInternalFrame frame) {
    menuItem.addActionListener(
        new ActionListener() {
          public void actionPerformed(ActionEvent e) {
            try {
              activateFrame((JInternalFrame) frame);
            } catch (Throwable t) {
              handleThrowable(t);
            }
          }
        });
  }

  private void closeApplication() {
    if (confirmClose("Exit JUMP", getLayersWithModifiedFeatureCollections())) {
      // PersistentBlackboardPlugIn listens for when the workbench is
      // hidden [Jon Aquino]
      setVisible(false);

      // Invoke System#exit after all pending GUI events have been fired
      // (e.g. the hiding of this WorkbenchFrame) [Jon Aquino]
      SwingUtilities.invokeLater(
          new Runnable() {
            public void run() {
              System.exit(0);
            }
          });
    }
  }

  private Collection getLayersWithModifiedFeatureCollections() {
    ArrayList layersWithModifiedFeatureCollections = new ArrayList();
    for (Iterator i = getLayerManagers().iterator(); i.hasNext(); ) {
      LayerManager layerManager = (LayerManager) i.next();
      layersWithModifiedFeatureCollections.addAll(
          layerManager.getLayersWithModifiedFeatureCollections());
    }

    return layersWithModifiedFeatureCollections;
  }

  private Collection getLayerManagers() {
    // Multiple windows may point to the same LayerManager, so use
    // a Set. [Jon Aquino]
    HashSet layerManagers = new HashSet();
    JInternalFrame[] internalFrames = getInternalFrames();
    for (int i = 0; i < internalFrames.length; i++) {
      if (internalFrames[i] instanceof LayerManagerProxy) {
        layerManagers.add(((LayerManagerProxy) internalFrames[i]).getLayerManager());
      }
    }

    return layerManagers;
  }

  private void configureStatusLabel(JLabel label, int width) {
    label.setMinimumSize(new Dimension(width, (int) label.getMinimumSize().getHeight()));
    label.setMaximumSize(new Dimension(width, (int) label.getMaximumSize().getHeight()));
    label.setPreferredSize(new Dimension(width, (int) label.getPreferredSize().getHeight()));
  }

  private void jbInit() throws Exception {
    setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
    this.setIconImage(icon.getImage());
    this.addComponentListener(
        new java.awt.event.ComponentAdapter() {
          public void componentShown(ComponentEvent e) {
            this_componentShown(e);
          }
        });
    this.getContentPane().setLayout(borderLayout1);
    this.addWindowListener(
        new java.awt.event.WindowAdapter() {
          public void windowClosing(WindowEvent e) {
            this_windowClosing(e);
          }
        });
    this.setJMenuBar(menuBar);

    // This size is chosen so that when the user hits the Info tool, the
    // window
    // fits between the lower edge of the TaskFrame and the lower edge of
    // the
    // WorkbenchFrame. See the call to #setSize in InfoFrame. [Jon Aquino]
    setSize(900, 665);

    // OUTLINE_DRAG_MODE is excruciatingly slow in JDK 1.4.1, so don't use
    // it.
    // (although it's supposed to be fixed in 1.4.2, which has not yet been
    // released). (see Sun Java Bug ID 4665237). [Jon Aquino]
    // desktopPane.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
    messageLabel.setOpaque(true);
    memoryLabel.setText("jLabel1");
    wmsLabel.setHorizontalAlignment(SwingConstants.LEFT);
    wmsLabel.setText(" ");
    this.getContentPane().add(statusPanel, BorderLayout.SOUTH);
    exitMenuItem.addActionListener(
        new java.awt.event.ActionListener() {
          public void actionPerformed(ActionEvent e) {
            exitMenuItem_actionPerformed(e);
          }
        });
    windowMenu.addMenuListener(
        new javax.swing.event.MenuListener() {
          public void menuCanceled(MenuEvent e) {}

          public void menuDeselected(MenuEvent e) {}

          public void menuSelected(MenuEvent e) {
            windowMenu_menuSelected(e);
          }
        });
    coordinateLabel.setBorder(BorderFactory.createLoweredBevelBorder());
    wmsLabel.setBorder(BorderFactory.createLoweredBevelBorder());
    coordinateLabel.setText(" ");
    statusPanel.setLayout(gridBagLayout1);
    statusPanel.setBorder(BorderFactory.createRaisedBevelBorder());
    messageLabel.setBorder(BorderFactory.createLoweredBevelBorder());
    messageLabel.setText(" ");
    timeLabel.setBorder(BorderFactory.createLoweredBevelBorder());
    timeLabel.setText(" ");
    memoryLabel.setBorder(BorderFactory.createLoweredBevelBorder());
    memoryLabel.setText(" ");
    menuBar.add(fileMenu);
    menuBar.add(windowMenu);
    getContentPane().add(toolBar, BorderLayout.NORTH);
    getContentPane().add(desktopPane, BorderLayout.CENTER);
    fileMenu.addSeparator();
    fileMenu.add(exitMenuItem);
    statusPanel.add(
        coordinateLabel,
        new GridBagConstraints(
            5,
            1,
            1,
            1,
            0.0,
            0.0,
            GridBagConstraints.WEST,
            GridBagConstraints.HORIZONTAL,
            new Insets(0, 0, 0, 0),
            0,
            0));
    statusPanel.add(
        timeLabel,
        new GridBagConstraints(
            2,
            1,
            1,
            1,
            0.0,
            0.0,
            GridBagConstraints.CENTER,
            GridBagConstraints.HORIZONTAL,
            new Insets(0, 0, 0, 0),
            0,
            0));
    statusPanel.add(
        messageLabel,
        new GridBagConstraints(
            1,
            1,
            1,
            1,
            0.0,
            0.0,
            GridBagConstraints.CENTER,
            GridBagConstraints.HORIZONTAL,
            new Insets(0, 0, 0, 0),
            0,
            0));

    // Give memoryLabel the 1.0 weight. All the rest should have their
    // sizes
    // configured using #configureStatusLabel. [Jon Aquino]
    statusPanel.add(
        memoryLabel,
        new GridBagConstraints(
            3,
            1,
            1,
            1,
            1.0,
            0.0,
            GridBagConstraints.CENTER,
            GridBagConstraints.HORIZONTAL,
            new Insets(0, 0, 0, 0),
            0,
            0));
    statusPanel.add(
        wmsLabel,
        new GridBagConstraints(
            4,
            1,
            1,
            1,
            0.0,
            0.0,
            GridBagConstraints.WEST,
            GridBagConstraints.NONE,
            new Insets(0, 0, 0, 0),
            0,
            0));
  }

  private void position(JInternalFrame internalFrame) {
    final int STEP = 5;
    GUIUtil.Location location = null;
    if (internalFrame instanceof PrimaryInfoFrame) {
      primaryInfoFrameIndex++;

      int offset = (primaryInfoFrameIndex % 3) * STEP;
      location = new GUIUtil.Location(offset, true, offset, true);
    } else {
      positionIndex++;

      int offset = (positionIndex % 5) * STEP;
      location = new GUIUtil.Location(offset, false, offset, false);
    }
    GUIUtil.setLocation((Component) internalFrame, location, desktopPane);
  }

  /**
   * Fundamental Style classes (like BasicStyle, VertexStyle, and LabelStyle) cannot be removed, and
   * are thus excluded from the choosable Style classes.
   */
  public Set getChoosableStyleClasses() {
    return Collections.unmodifiableSet(choosableStyleClasses);
  }

  public void addChoosableStyleClass(Class choosableStyleClass) {
    Assert.isTrue(ChoosableStyle.class.isAssignableFrom(choosableStyleClass));
    choosableStyleClasses.add(choosableStyleClass);
  }

  private HashMap keyCodeAndModifiersToPlugInAndEnableCheckMap = new HashMap();

  /**
   * Adds a keyboard shortcut for a plugin. logs plugin exceptions.
   *
   * <p>note - attaching to keyCode 'a', modifiers =1 will detect shift-A events. It will *not*
   * detect caps-lock-'a'. This is due to inconsistencies in java.awt.event.KeyEvent. In the
   * unlikely event you actually do want to also also attach to caps-lock-'a', then make two
   * shortcuts - one to keyCode 'a' and modifiers =1 (shift-A) and one to keyCode 'A' and
   * modifiers=0 (caps-lock A).
   *
   * <p>For more details, see the java.awt.event.KeyEvent class - it has a full explaination.
   *
   * @param keyCode What key to attach to (See java.awt.event.KeyEvent)
   * @param modifiers 0= none, 1=shift, 2= cntrl, 8=alt, 3=shift+cntrl, etc... See the modifier mask
   *     constants in the Event class
   * @param plugIn What plugin to execute
   * @param enableCheck Is the key enabled at the moment?
   */
  public void addKeyboardShortcut(
      final int keyCode, final int modifiers, final PlugIn plugIn, final EnableCheck enableCheck) {
    // Overwrite existing shortcut [Jon Aquino]
    keyCodeAndModifiersToPlugInAndEnableCheckMap.put(
        keyCode + ":" + modifiers, new Object[] {plugIn, enableCheck});
  }

  private void installKeyboardShortcutListener() {
    addEasyKeyListener(
        new KeyListener() {
          public void keyTyped(KeyEvent e) {}

          public void keyReleased(KeyEvent e) {}

          public void keyPressed(KeyEvent e) {
            Object[] plugInAndEnableCheck =
                (Object[])
                    keyCodeAndModifiersToPlugInAndEnableCheckMap.get(
                        e.getKeyCode() + ":" + e.getModifiers());
            if (plugInAndEnableCheck == null) {
              return;
            }
            PlugIn plugIn = (PlugIn) plugInAndEnableCheck[0];
            EnableCheck enableCheck = (EnableCheck) plugInAndEnableCheck[1];
            if (enableCheck != null && enableCheck.check(null) != null) {
              return;
            }
            // #toActionListener handles checking if the plugIn is a
            // ThreadedPlugIn,
            // and making calls to UndoableEditReceiver if necessary. [Jon
            // Aquino 10/15/2003]
            AbstractPlugIn.toActionListener(plugIn, workbenchContext, new TaskMonitorManager())
                .actionPerformed(null);
          }
        });
  }

  private boolean confirmClose(String action, Collection modifiedLayers) {
    if (modifiedLayers.isEmpty()) {
      return true;
    }

    JOptionPane pane =
        new JOptionPane(
            StringUtil.split(
                modifiedLayers.size()
                    + " dataset"
                    + StringUtil.s(modifiedLayers.size())
                    + " "
                    + ((modifiedLayers.size() > 1) ? "have" : "has")
                    + " been modified ("
                    + ((modifiedLayers.size() > 3) ? "e.g. " : "")
                    + StringUtil.toCommaDelimitedString(
                        new ArrayList(modifiedLayers)
                            .subList(0, Math.min(3, modifiedLayers.size())))
                    + "). Continue?",
                80),
            JOptionPane.WARNING_MESSAGE);
    pane.setOptions(new String[] {action, "Cancel"});
    pane.createDialog(this, AppContext.getMessage("GeopistaName")).setVisible(true);

    return pane.getValue().equals(action);
  }

  public WorkbenchToolBar getToolBar(String toolBarName) {
    return null;
  }

  /* (non-Javadoc)
   * @see com.geopista.editor.WorkbenchGuiComponent#getMainFrame()
   */
  public JFrame getMainFrame() {

    return this;
  }

  public TaskComponent[] getInternalTaskComponents() {
    JInternalFrame[] internalFrames = desktopPane.getAllFrames();
    TaskComponent[] taskComponents = new TaskComponent[internalFrames.length];
    for (int n = 0; n < internalFrames.length; n++) {
      taskComponents[n] = (TaskComponent) internalFrames[n];
    }
    return taskComponents;
  }

  public JPopupMenu getLayerViewPopupMenu() {
    // TODO Auto-generated method stub
    return popupMenu;
  }
}