Offline editing workflow

Download Sample Viewer

Description

This application shows an example of an editing workflow using a local geodatabase. In this workflow, connectivity is assumed at first and the feature data from the current map extent can be requested from the feature service to be stored locally on the machine in a geodatabase. Once this is complete, the features in the local geodatabase appear in the application via the creation of a FeatureLayer. This feature layer is created from a GeodatabaseFeatureTable, itself obtained from a Geodatabase which is created using the local '.geodatabase' file downloaded. Network connectivity is from then on not required in order for features to be displayed in the map or edited. Edits can be made to the features, then those edits can be synced back to the service once network connectivity is restored.

Note: this application uses a tiled map service layer as a basemap which requires connectivity to be displayed. Replacing this layer with a local tiled layer from a local tile package (.tpk) or compact cache for the area of your choice is the recommended solution for a completely disconnected application. To use the application, click on toolbar buttons on the left. The radio buttons will display some additional instructions/options in the main map panel. The application includes querying functionality in addition to editing.

Code snippet


   geodatabase = new Geodatabase(path);
   final GeodatabaseFeatureTable featureTable = geodatabase.getGeodatabaseFeatureTableByLayerId(0);
   
   // create a feature layer from the feature service table
   featureLayer = new FeatureLayer(featureTable);
   map.getLayers().add(featureLayer);

   // features could be edited in multiple ways - one of them is to add the edit tools picker
   jEditToolsPicker = new JEditToolsPicker(map);
   jEditToolsPicker.setCreationOverlay(overlay);
   jEditToolsPicker.setVisible(false);
  

Sample Code

/* Copyright 2014 Esri

All rights reserved under the copyright laws of the United States
and applicable international laws, treaties, and conventions.

You may freely redistribute and use this sample code, with or
without modification, provided you include the original copyright
notice and use restrictions.

See the use restrictions.*/
package com.esri.client.samples.editing;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.FileNotFoundException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.ListCellRenderer;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;

import com.esri.core.ags.FeatureServiceInfo;
import com.esri.core.geodatabase.Geodatabase;
import com.esri.core.geodatabase.GeodatabaseFeature;
import com.esri.core.geodatabase.GeodatabaseFeatureTable;
import com.esri.core.geodatabase.GeodatabaseFeatureTableEditErrors;
import com.esri.core.geometry.Envelope;
import com.esri.core.geometry.Point;
import com.esri.core.map.CallbackListener;
import com.esri.core.map.FeatureType;
import com.esri.core.renderer.Renderer;
import com.esri.core.symbol.Symbol;
import com.esri.core.table.TableException;
import com.esri.core.tasks.geodatabase.GenerateGeodatabaseParameters;
import com.esri.core.tasks.geodatabase.GeodatabaseStatusCallback;
import com.esri.core.tasks.geodatabase.GeodatabaseStatusInfo;
import com.esri.core.tasks.geodatabase.GeodatabaseSyncTask;
import com.esri.core.tasks.geodatabase.SyncGeodatabaseParameters;
import com.esri.core.tasks.geodatabase.SyncModel;
import com.esri.core.tasks.query.QueryParameters;
import com.esri.map.ArcGISFeatureLayer;
import com.esri.map.ArcGISTiledMapServiceLayer;
import com.esri.map.FeatureLayer;
import com.esri.map.JMap;
import com.esri.map.LayerInitializeCompleteEvent;
import com.esri.map.LayerInitializeCompleteListener;
import com.esri.map.MapOverlay;
import com.esri.toolkit.editing.JEditToolsPicker;
import com.esri.toolkit.editing.JTemplatePicker;
import com.esri.toolkit.utilities.ComponentDragger;

/***
 * This application shows an example of an editing workflow using a local 
 * geodatabase. In this workflow, connectivity is assumed at first and the 
 * feature data from the current map extent can be requested from the feature 
 * service to be stored locally on the machine in a geodatabase. Once this 
 * is complete, the features in the local geodatabase appear in the application 
 * via the creation of a <code>FeatureLayer</code>. This feature layer is created 
 * from a GeodatabaseFeatureTable, itself obtained from a Geodatabase which is 
 * created using the local '.geodatabase' file downloaded. Network connectivity 
 * is from then on not required in order for features to be displayed in the 
 * map or edited. Edits can be made to the features, then those edits can be 
 * synced back to the service once network connectivity is restored.
 * <p>
 * <b>Note:</b> this application uses a tiled map service layer as a basemap 
 * which requires connectivity to be displayed. Replacing this layer with a 
 * local tiled layer from a local tile package (.tpk) or compact cache for 
 * the area of your choice is the recommended solution for a completely 
 * disconnected application.
 * <p>
 * To use the application, click on toolbar buttons on the left. The radio buttons 
 * will display some additional instructions/options in the main map panel. The 
 * application includes querying functionality in addition to editing.
 *
 */
public class LocalGeodatabaseEditingApp {

  // ArcGIS objects 
  private Geodatabase geodatabase;
  private GeodatabaseSyncTask geodatabaseSyncTask;
  private JMap map;
  private FeatureLayer localFeatureLayer;

  // Swing components
  private JLayeredPane mapPane;
  private JProgressBar progressBar;
  private JToolBar toolbar;
  private ContextPanel geodatabasePanel;
  private ContextPanel searchPanel;
  private JRadioButton generateGeodatabaseButton;
  private JRadioButton editingButton;
  private JRadioButton searchButton;
  private JToggleButton syncButton;
  private JTextField textField;
  private JLabel labelViewing;

  private JTemplatePicker jPicker;
  private JEditToolsPicker jEditToolsPicker;
  
  // UI constants
  private static final Color TRANS_COLOR = new Color(0, 0, 0, 0);
  private static final Color BLACK_TRANSPARENT = new Color(0, 0, 0, 80);
  private static final String GEODATABASE_STRING = "Create geodatabase";
  private static final String SEARCH_STRING = "Search";
  private static final String EDITING_STRING = "Edit";
  private static final Dimension BUTTON_DIM = new Dimension(170, 30);
  private static final Dimension H_SPACE = new Dimension(5, 0);
  private static final Dimension V_SPACE = new Dimension(0, 5);

  private static final String FEATURE_SERVICE_URL = 
      "http://sampleserver6.arcgisonline.com/arcgis/rest/services/Sync/WildfireSync/FeatureServer";
  private static final String DEFAULT_PATH = System.getProperty("user.home") + System.getProperty("file.separator") + "fire.geodatabase";

  private ArcGISTiledMapServiceLayer tiledLayer = new ArcGISTiledMapServiceLayer(
      "http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer");
  private ArcGISFeatureLayer featureServiceLayer = new ArcGISFeatureLayer(FEATURE_SERVICE_URL + "/0");
  
  /**
   * Starting point of this application: creates the window, creates an 
   * instance of this application and calls method to create the UI.
   * 
   * @param args any arguments
   */
  public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        try {
          // instance of this application
          LocalGeodatabaseEditingApp application = new LocalGeodatabaseEditingApp();
          
          // create the UI, including the map, for the application
          JFrame appWindow = application.createWindow();
          appWindow.getContentPane().add(application.createUI());
          appWindow.setVisible(true);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }
  
  /**
   * Dispose the map and geodatabase. This must be called when you are finished 
   * using the map and geodatabase, for example on application exit.
   */
  public void dispose() {
    if (geodatabase != null) {
      geodatabase.dispose();
    }
    if (map != null) {
      map.dispose();
    }
  }
  
  /**
   * Creates the map, toolbar, status bar, various Swing panels used in the 
   * application, symbols for graphics, and more.
   */
  public JComponent createUI() {

    JPanel mainPanel = new JPanel();
    mainPanel.setLayout(new BorderLayout());

    mapPane = createMapPane();
    map = new JMap();

    map.getLayers().add(tiledLayer);
    
    // feature service layer will be removed once the geodatabase has been created
    featureServiceLayer.setUseAdvancedSymbols(true);
    map.getLayers().add(featureServiceLayer);
    
    map.setExtent(new Envelope(-1.3637862136574738E7, 4543803.104647163, -1.362424776285072E7, 4552972.107530484));
    
    // geodatabase creation panel
    geodatabasePanel = createGeodatabasePanel();
    mapPane.add(geodatabasePanel);

    // search/query panel
    searchPanel = createSearchPanel();
    mapPane.add(searchPanel);

    // add template picker
    jPicker = new JTemplatePicker(map);
    mapPane.add(jPicker, BorderLayout.WEST);
    jPicker.setIconWidth(36);
    jPicker.setIconHeight(36);
    jPicker.setShowNames(true);
    jPicker.setWatchMap(true);
    jPicker.setVisible(false);
    jPicker.setBackground(Color.LIGHT_GRAY);
    jPicker.setForeground(Color.WHITE);
    
    // add edit tools picker
    jEditToolsPicker = new JEditToolsPicker(map);
    jEditToolsPicker.setCreationOverlay(jPicker.getOverlay());
    jEditToolsPicker.setVisible(false);
    mapPane.add(jEditToolsPicker, BorderLayout.NORTH);

    // progress bar
    progressBar = createProgressBar(mapPane);
    mapPane.add(progressBar);
    // map must be added last to the layered pane!
    mapPane.add(map);
    // UI hooks for buttons
    mainPanel.add(mapPane, BorderLayout.CENTER);

    // create toolbar
    toolbar = new JToolBar();
    toolbar.setBorder(new LineBorder(Color.DARK_GRAY, 1));
    toolbar.setOrientation(SwingConstants.VERTICAL);
    mainPanel.add(toolbar, BorderLayout.WEST);

    // group where user can only select one button at a time
    ButtonGroup buttonGroup = new ButtonGroup();
    // listener for the button group
    ButtonGroupListener buttonGroupListener = new ButtonGroupListener();
    
    labelViewing = new JLabel();
    labelViewing.setText("Currently viewing online data");
    labelViewing.setFont(
        new Font(labelViewing.getFont().getFontName(), Font.ITALIC, labelViewing.getFont().getSize()));
    labelViewing.setForeground(Color.WHITE);
    labelViewing.setBackground(Color.DARK_GRAY);
    labelViewing.setOpaque(true);
    labelViewing.setBorder(new EmptyBorder(5, 5, 5, 5));
    toolbar.add(labelViewing);
    toolbar.add(new Box.Filler(V_SPACE, V_SPACE, V_SPACE));

    generateGeodatabaseButton = new JRadioButton("Create geodatabase");
    generateGeodatabaseButton.setName(GEODATABASE_STRING);
    toolbar.add(generateGeodatabaseButton);
    buttonGroup.add(generateGeodatabaseButton);
    generateGeodatabaseButton.addItemListener(buttonGroupListener);
    generateGeodatabaseButton.addActionListener(buttonGroupListener);
    
    searchButton = new JRadioButton("Search for features");
    searchButton.setName(SEARCH_STRING);
    toolbar.add(searchButton);
    buttonGroup.add(searchButton);
    searchButton.setEnabled(false);
    searchButton.addItemListener(buttonGroupListener);
    searchButton.addActionListener(buttonGroupListener);

    editingButton = new JRadioButton("Edit data");
    editingButton.setName(EDITING_STRING);
    toolbar.add(editingButton);
    buttonGroup.add(editingButton);
    editingButton.setEnabled(false);
    editingButton.addItemListener(buttonGroupListener);
    editingButton.addActionListener(buttonGroupListener);

    syncButton = new JToggleButton("Sync your edits");
    syncButton.setEnabled(false);
    syncButton.setPreferredSize(BUTTON_DIM);
    syncButton.setMaximumSize(BUTTON_DIM);
    toolbar.add(syncButton);
    syncButton.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent e) {
        try {
          synchronize();
        } catch (Exception e1) {
          e1.printStackTrace();
        }
      }
    });

    return mainPanel;
  }

  /**
   * If a geodatabase at the specified path exists, use it to create 
   * a GeodatabaseFeatureTable and FeatureLayer. Otherwise, start the process 
   * to request and download a geodatabase from the service.
   */
  private void onCreateGeodatabaseButtonClick() {
    final String pathToGeodatabase = textField.getText();
    try {
      createFeatureTable(pathToGeodatabase);
      JOptionPane.showMessageDialog(mapPane, "Creating feature table from existing geodatabase at " + 
      pathToGeodatabase, "Geodatabase exists", JOptionPane.WARNING_MESSAGE);
    } catch (FileNotFoundException e) {
      System.out.println("Geodatabase does not exist: creating geodatabase from service.");
      geodatabaseSyncTask = new GeodatabaseSyncTask(FEATURE_SERVICE_URL, null);
      geodatabaseSyncTask.fetchFeatureServiceInfo(new CallbackListener<FeatureServiceInfo>() {

        @Override
        public void onCallback(FeatureServiceInfo serviceInfo) {
          // sync supported?
          if (serviceInfo.isSyncEnabled()) {
            createGeodatabaseFromService(pathToGeodatabase);
          } else {
            System.out.println("This service is not sync enabled.");
          }
        }

        @Override
        public void onError(Throwable error) {
          updateProgressBarUI(null, false);
          System.err.println("Error fetching the FeatureServiceInfo: " + error.toString());
        }
      });
    }
  }

  /**
   * Create geodatabase, GeodatabaseFeatureTable from geodatabase path, and feature layer from 
   * GeodatabaseFeatureTable to display of features in the map.
   * 
   * @param path to a geodatabase file
   * @throws FileNotFoundException if there is no file at the specified path
   */
  private void createFeatureTable(String path) throws FileNotFoundException {
    geodatabase = new Geodatabase(path);
    final GeodatabaseFeatureTable geodatabaseTable = geodatabase.getGeodatabaseFeatureTableByLayerId(0);
    localFeatureLayer = new FeatureLayer(geodatabaseTable);

    localFeatureLayer.addLayerInitializeCompleteListener(new LayerInitializeCompleteListener() {

      @Override
      public void layerInitializeComplete(LayerInitializeCompleteEvent e) {
        
        // use a runnable as we need to execute code that uses Swing components on the EDT
        Runnable layerInitialisedRunnable = new Runnable() {

          @Override
          public void run() {
          //swap to offline data
            map.getLayers().remove(featureServiceLayer);
            featureServiceLayer.dispose();
            map.getLayers().add(localFeatureLayer);
            labelViewing.setText("Currently viewing offline data");

            // set up the array of type names and IDs for the search box
            List<FeatureType> featureTypes = geodatabaseTable.getFeatureTypes(); 

            Renderer renderer = localFeatureLayer.getRenderer();

            Map<String, Object> attributes = new HashMap<>();
            for (FeatureType t : featureTypes) {

              String typeIDfield = ((GeodatabaseFeatureTable)localFeatureLayer.getFeatureTable()).getTypeIdField();
              attributes.put(typeIDfield, new Integer(t.getId()));
              try {
                // get symbol image
                GeodatabaseFeature f = new GeodatabaseFeature(attributes, geodatabaseTable);
                Symbol symbol = renderer.getSymbol(f);
                Image image = localFeatureLayer.createSymbolImage(symbol, new Point(30.0, 30.0), 60, 60, new Color(0x0, true));
                // scale symbol image for the combo box
                image = image.getScaledInstance(40, 40, java.awt.Image.SCALE_SMOOTH);

                FeaturePrototype prototype = new FeaturePrototype(t.getName(), Integer.parseInt(t.getId()), new ImageIcon(image));
                comboBoxModel.addElement(prototype);
              } catch (TableException e1) {
                e1.printStackTrace();
              }
            }
            editingButton.setEnabled(true);
            searchButton.setEnabled(true);
            syncButton.setEnabled(true);
          }
        };

        if (SwingUtilities.isEventDispatchThread()) {
          layerInitialisedRunnable.run();
        } else {
          SwingUtilities.invokeLater(layerInitialisedRunnable);
        }
      }
    });

    localFeatureLayer.initializeAsync();
  }

  /**
   * Creates the geodatabase from the service using the API method 
   * <code>generateGeodatabase</code>. If successful, the geodatabase 
   * will be downloaded to the path passed in as a parameter
   * (e.g. on Windows c:/data/MyGeodatabase.geodatabase).
   *
   * @param path the path to download the geodatabase to
   */
  private void createGeodatabaseFromService(final String path) {

    //set up the parameters
    int[] layers = {0, 1, 2};
    GenerateGeodatabaseParameters params = new GenerateGeodatabaseParameters(
        layers, 
        map.getExtent(), 
        map.getSpatialReference(), 
        false, 
        SyncModel.LAYER,
        // optionally, specify the spatial reference of the geodatabase to be generated 
        map.getSpatialReference());

    GeodatabaseStatusCallback statusCallback = new GeodatabaseStatusCallback() {

      @Override
      public void statusUpdated(GeodatabaseStatusInfo status) {
        if (status.isDownloading()) {
          updateProgressBarUI(status);
        } else {
          updateProgressBarUI("Latest status: " + status.getStatus(), true);
        }
      }
    };

    //geodatabase complete callback
    CallbackListener<String> geodatabaseResponseCallback = new CallbackListener<String>() {

      @Override
      public void onError(Throwable e) {
        JOptionPane.showMessageDialog(mapPane, wrap("An error occured: " + e.getLocalizedMessage()), 
            "", JOptionPane.ERROR_MESSAGE);
        updateProgressBarUI(null, false);
        e.printStackTrace();
      }

      @Override
      public void onCallback(String geodatabasePath) {
        updateProgressBarUI("Geodatabase downloaded!", true);
        updateProgressBarUI(null, false);
        // now use the path to downloaded geodatabase to create a 
        // GeodatabaseFeatureTable and FeatureLayer
        try {
          createFeatureTable(geodatabasePath);
        } catch (FileNotFoundException e) {
          JOptionPane.showMessageDialog(mapPane, wrap("An error occured: " + e.getLocalizedMessage()), 
              "", JOptionPane.ERROR_MESSAGE);
          e.printStackTrace();
        }
      }
    };

    // ------------------------------------------------------------------------
    // Generate the geodatabase from the service and download
    // ------------------------------------------------------------------------
    geodatabaseSyncTask.generateGeodatabase(
        params, path, true, statusCallback, geodatabaseResponseCallback);
    updateProgressBarUI("Creating geodatabase from service...", true);
  }

  private void search(/*String searchText*/int searchItem) {
    QueryParameters query = new QueryParameters();
    String typeIDfield = ((GeodatabaseFeatureTable)localFeatureLayer.getFeatureTable()).getTypeIdField();
    query.setWhere(typeIDfield + "=" + searchItem);
    localFeatureLayer.getFeatureTable().queryIds(query, new CallbackListener<long[]>() {

      @Override
      public void onError(Throwable e) {
        e.printStackTrace();
      }

      @Override
      public void onCallback(long[] result) {
        if (result != null) {
          System.out.println("Number of results: "+ result.length);
          localFeatureLayer.selectFeatures(result, false);
        }
      }
    });
  }

  /**
   * Calls the API method <code>syncGeodatabase</code> to 
   * perform synchronization between the local geodatabase and the service.
   * 
   * @throws Exception 
   */
  protected void synchronize() throws Exception {
    if (geodatabase == null) {
      JOptionPane.showMessageDialog(mapPane, "Error: No geodatabase has been loaded.", 
          "", JOptionPane.ERROR_MESSAGE);
      return;
    }
    
    // main parameters
    final SyncGeodatabaseParameters syncParams = geodatabase.getSyncParameters();

    CallbackListener<Map<Integer,GeodatabaseFeatureTableEditErrors>> syncResponseCallback = 
        new CallbackListener<Map<Integer,GeodatabaseFeatureTableEditErrors>>() {

      @Override
      public void onError(Throwable e) {
        JOptionPane.showMessageDialog(mapPane, wrap("An error occured: " + e.getLocalizedMessage()), 
            "", JOptionPane.ERROR_MESSAGE);
        updateProgressBarUI(null, false);
        e.printStackTrace();
      }

      @Override
      public void onCallback(Map<Integer,GeodatabaseFeatureTableEditErrors> objs) {
        updateProgressBarUI(null, false);
      }
    };

    GeodatabaseStatusCallback statusCallback = new GeodatabaseStatusCallback() {

      @Override
      public void statusUpdated(GeodatabaseStatusInfo status) {
        updateProgressBarUI("Latest status: " + status.getStatus(), true);
      }
    };

    // ------------------------------------------------------------------------
    // Start sync
    // ------------------------------------------------------------------------
    if (geodatabaseSyncTask == null) {
      geodatabaseSyncTask = new GeodatabaseSyncTask(FEATURE_SERVICE_URL, null);
    }
    geodatabaseSyncTask.syncGeodatabase(syncParams, geodatabase, statusCallback, syncResponseCallback);
    updateProgressBarUI("Starting sync...", true);
  }
  
  private ContextPanel createGeodatabasePanel() {
    
    textField = new JTextField(DEFAULT_PATH);
    JButton createGeodatabaseButton = new JButton("Create geodatabase");
    createGeodatabaseButton.addActionListener(new ActionListener() {
      
      @Override
      public void actionPerformed(ActionEvent e) {
        onCreateGeodatabaseButtonClick();
      }
    });
    
    // button panel
    JPanel userPanel = new JPanel();
    userPanel.setLayout(new BoxLayout(userPanel, BoxLayout.Y_AXIS));
    userPanel.add(Box.createRigidArea(V_SPACE));
    userPanel.add(textField);
    userPanel.add(Box.createRigidArea(V_SPACE));
    userPanel.add(createGeodatabaseButton);
    userPanel.setBackground(BLACK_TRANSPARENT);
    
    ContextPanel panel = new ContextPanel();
    panel.setLocation(10, 10);
    panel.setSize(450, 95);
    panel.setTitle("Enter a path for the geodatabase to create:");
    panel.setContent(userPanel);
    return panel;
  }

  // model will be populated when the GeodatabaseFeatureTable is created
  private DefaultComboBoxModel<FeaturePrototype> comboBoxModel = new DefaultComboBoxModel<>();
  
  private ContextPanel createSearchPanel() {
    final JComboBox<FeaturePrototype> comboBox = new JComboBox<>(comboBoxModel);
    comboBox.setRenderer(new CellRenderer(true, true));
    comboBox.setAlignmentX(Component.LEFT_ALIGNMENT);

    JButton findButton = new JButton("Find and select features");

    findButton.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        if (localFeatureLayer != null) {
          localFeatureLayer.clearSelection();
        }
     
        search(((FeaturePrototype)comboBox.getSelectedItem()).getValue());
      }
    });

    JButton clearSelectionButton = new JButton("Clear selection");
    clearSelectionButton.addActionListener(new ActionListener() {

      @Override
      public void actionPerformed(ActionEvent arg0) {
        if (localFeatureLayer != null) {
          localFeatureLayer.clearSelection();
        }
      }
    });

    // button panel
    JPanel buttonPanel = new JPanel();
    buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS));
    buttonPanel.add(findButton);
    buttonPanel.add(Box.createRigidArea(H_SPACE));
    buttonPanel.add(clearSelectionButton);
    buttonPanel.setBackground(TRANS_COLOR);
    buttonPanel.setAlignmentX(Component.LEFT_ALIGNMENT);

    // button panel
    JPanel userPanel = new JPanel();
    userPanel.setLayout(new BoxLayout(userPanel, BoxLayout.Y_AXIS));
    userPanel.add(Box.createRigidArea(V_SPACE));
    userPanel.add(comboBox);
    userPanel.add(Box.createRigidArea(V_SPACE));
    userPanel.add(buttonPanel);
    userPanel.setBackground(BLACK_TRANSPARENT);
    comboBox.setPreferredSize(new Dimension(435, 28));

    ContextPanel panel = new ContextPanel();
    panel.setLocation(10, 10);
    panel.setSize(450, 95);
    panel.setTitle("Choose an event type value to search for:");
    panel.setContent(userPanel);
    return panel;
  }

  /**
   * Creates a progress bar.
   * @param parent progress bar's parent. The horizontal axis of the progress bar will be
   * center-aligned to the parent.
   * @return a progress bar.
   */
  private static JProgressBar createProgressBar(final JComponent parent) {
    final JProgressBar progressBar = new JProgressBar();
    progressBar.setSize(350, 20);
    parent.addComponentListener(new ComponentAdapter() {
      @Override
      public void componentResized(ComponentEvent e) {
        progressBar.setLocation(
            parent.getWidth()/2 - progressBar.getWidth()/2,
            parent.getHeight() - progressBar.getHeight() - 20);
      }
    });
    progressBar.setStringPainted(true);
    progressBar.setIndeterminate(true);
    progressBar.setMaximum(100);
    progressBar.setVisible(false);
    return progressBar;
  }

  /**
   * Updates progress bar UI from Swing's Event Dispatch Thread.
   * @param str string to be set.
   * @param visible flag to indicate visibility of the progress bar.
   */
  private void updateProgressBarUI(final String str, final boolean visible) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        if (str != null) {
          progressBar.setString(str);
          System.out.println(str);
        }
        progressBar.setVisible(visible);
      }
    }); 
  }

  /**
   * Updates progress bar UI from Swing's Event Dispatch Thread.
   * 
   * @param str string to be set.
   * @param visible flag to indicate visibility of the progress bar.
   */
  private void updateProgressBarUI(final GeodatabaseStatusInfo status) {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        progressBar.setString("Downloading...");
        progressBar.setIndeterminate(false);

        // We've preset the maximum value to 100 and calculate the percentage done
        // value here to allow for the fact that our file download size is a long
        // value and the progress bar only supports int.
        progressBar.setValue((int) ((status.getTotalBytesDownloaded() * 100) / status.getDownloadSize()));
        progressBar.setVisible(true);
      }
    });
  }

  /**
   * Creates a map pane which will contain the map component and be able to display other components above the map in
   * z-order, such as panels.
   * 
   * @return the map pane
   */
  private static JLayeredPane createMapPane() {
    JLayeredPane contentPane = new JLayeredPane();
    contentPane.setBounds(0, 0, 1000, 700);
    contentPane.setLayout(new BorderLayout(0, 0));
    contentPane.setVisible(true);
    return contentPane;
  }

  /**
   * Listener for the radio buttons in the button group. Each radio 
   * button has a corresponding panel which displays when the 
   * button is selected, and potentially has an associated overlay 
   * to respond to button clicks on the map in the appropriate way. 
   * Implements two interfaces so that the radio buttons can respond 
   * to state changes (selected, deselected), and also if the user 
   * clicks the button when it is already selected, for example if 
   * the context panel has been closed and the user wants it to 
   * display again.
   */
  class ButtonGroupListener implements ActionListener, ItemListener {

    @Override
    public void itemStateChanged(ItemEvent ev) {
      String name = ((AbstractButton)ev.getSource()).getName();
      boolean enablePanel = false;
      if (ev.getStateChange() == ItemEvent.SELECTED){
        enablePanel = true;
      }else if (ev.getStateChange() == ItemEvent.DESELECTED){
        enablePanel = false;
      }
      
      if (name.contentEquals(GEODATABASE_STRING)){
        geodatabasePanel.setVisible(enablePanel);
      } else if (name.contentEquals(SEARCH_STRING)){
        searchPanel.setVisible(enablePanel);
      } else if (name.contentEquals(EDITING_STRING)){
        jPicker.setVisible(enablePanel);
        jEditToolsPicker.setVisible(enablePanel);
      }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      if (((JRadioButton) e.getSource()).isSelected()) {
        if (((AbstractButton) e.getSource()).getName().contentEquals(GEODATABASE_STRING)) {
          geodatabasePanel.setVisible(true);
          jPicker.setEnabled(false);
        } else if (((AbstractButton) e.getSource()).getName().contentEquals(SEARCH_STRING)) {
          searchPanel.setVisible(true);
          jPicker.setEnabled(false);
        } else if (((AbstractButton) e.getSource()).getName().contentEquals(EDITING_STRING)) {
          jPicker.setEnabled(true);
          jPicker.setVisible(true);
          jEditToolsPicker.setVisible(true);
        }
      }
    }
  }

  private JFrame createWindow() {
    JFrame window = new JFrame("Disconnected Workflow Application");
    window.setSize(1000, 600);
    window.setLocationRelativeTo(null);
    window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    window.getContentPane().setLayout(new BorderLayout());
    // dispose map and geodatabase just before application window is closed
    window.addWindowListener(new WindowAdapter() {
      @Override
      public void windowClosing(WindowEvent windowEvent) {
        super.windowClosing(windowEvent);
        dispose();
      }
    });
    return window;
  }

  /**
   * A custom JPanel that is draggable and closeable, to show a title (text) and 
   * content below it (another JPanel).
   */
  class ContextPanel extends JPanel {

    private static final long serialVersionUID = 1L;
    private final Color BORDER_COLOR = new Color(0, 0, 0, 80);
    private final Color TOP_COLOR = new Color(0, 0, 0, 150);

    private JPanel contentPanel;
    private JLabel titleText;
    private MapOverlay overlay;

    private int defaultWidth = 400; //default
    private int defaultHeight = 100; //default
    private ImageIcon closeIcon;
    private boolean showCloseIcon = true;

    public ContextPanel() {
      super();
      initPanel();
    }

    public ContextPanel(boolean showCloseIcon) {
      super();
      this.showCloseIcon = showCloseIcon;
      initPanel();
    }

    public void setTitle(String title) {
      titleText.setText(title);
    }

    public void setContent(JPanel content) {
      contentPanel.add(content, BorderLayout.CENTER);
    }

    public Color getPanelColor() {
      return BORDER_COLOR;
    }

    public Color getTopColor() {
      return TOP_COLOR;
    }

    public MapOverlay getOverlay() {
      return overlay;
    }

    public void setOverlay(MapOverlay overlay) {
      this.overlay = overlay;
    }

    // override to deal with transparency issues when background color has alpha value
    @Override
    protected void paintComponent(Graphics g) {
      g.setColor( getBackground() );
      g.fillRect(0, 0, getWidth(), getHeight());
      super.paintComponent(g);
    }

    private void initPanel() {

      // title for panel
      titleText = new JLabel();
      titleText.setHorizontalAlignment(SwingConstants.LEFT);
      titleText.setFont(new Font(titleText.getFont().getName(), Font.PLAIN, 12));
      titleText.setBackground(TRANS_COLOR);
      titleText.setForeground(Color.WHITE);
      titleText.setBorder(BorderFactory.createEmptyBorder(2,10,2,10));

      JPanel topPanel = new JPanel();
      topPanel.setBackground(TOP_COLOR);
      topPanel.setLayout(new BorderLayout(0, 0));
      topPanel.add(titleText, BorderLayout.CENTER);

      // create close/hide panel button
      if (showCloseIcon) {
        loadCloseIcon();
        JButton close = new JButton(closeIcon);
        close.setBackground(BORDER_COLOR);
        close.setSize(closeIcon.getIconWidth(), closeIcon.getIconHeight());
        close.setBorder(null);
        close.setToolTipText("Close");
        close.setRolloverIcon(new ImageIcon(getClass().getResource("/com/esri/client/toolkit/images/close_select.png")));
        close.setRolloverEnabled(true);
        close.addActionListener(new ActionListener() {
          @Override
          public void actionPerformed(ActionEvent e) {
            setVisible(false);
          }
        });
        topPanel.add(close, BorderLayout.EAST);
      }

      // group the above UI items into a panel
      setLayout(new BorderLayout());
      setLocation(10, 10); // set a default location
      setSize(defaultWidth, defaultHeight);

      contentPanel = new JPanel();
      contentPanel.setLayout(new BorderLayout());
      add(topPanel, BorderLayout.NORTH);
      add(contentPanel, BorderLayout.CENTER);
      setOpaque(false);
      setBackground(new Color(0, 0, 0, 0));
      setBorder(new LineBorder(BORDER_COLOR, 5, false));
      setVisible(false);

      // make the result panel draggable using out component dragger
      ComponentDragger componentDragger = new ComponentDragger(this);
      addMouseMotionListener(componentDragger);
      addMouseListener(componentDragger);

      applyComponentOrientation(ComponentOrientation.getOrientation(Locale.getDefault()));
    }

    private void loadCloseIcon() {
      closeIcon = new ImageIcon(getClass().getResource("/com/esri/client/toolkit/images/close.png"));
    }
  }
  
  class FeaturePrototype {
    String text;
    int value;
    ImageIcon icon;
    
    public FeaturePrototype(String name, int value, ImageIcon icon) {
      this.text = name;
      this.value = value;
      this.icon = icon;
    }
    
    public String getText() {
      return text;
    }
    
    public int getValue() {
      return value;
    }
    
    public ImageIcon getIcon() {
      return icon;
    }
  }
  
  class CellRenderer extends JLabel implements ListCellRenderer<Object> {

    private static final long serialVersionUID = 1L;

    boolean showIcon = false;
    boolean showText = false;
    
    public CellRenderer(boolean showText, boolean showIcon) {
      this.showText = showText;
      this.showIcon = showIcon;
      setOpaque(true);
    }

    @Override
    public Component getListCellRendererComponent(JList<?> list, Object value,
        int index, boolean isSelected, boolean cellHasFocus) {
      if (isSelected) {
        setBackground(list.getSelectionBackground());
        setForeground(list.getSelectionForeground());
      } else {
        setBackground(list.getBackground());
        setForeground(list.getForeground());
      }

      setFont(list.getFont());

      if (showText) {
        setText(((FeaturePrototype)value).getText());
      }
       
      if (showIcon) {
        setIcon(((FeaturePrototype)value).getIcon());
      }

      return this;
    }
  }
  
  private String wrap(String str) {
    // create a HTML string that wraps text when longer
    return "<html><p style='width:200px;'>" + str + "</html>";
  }
}

Feedback on this topic?