private boolean findEntry() {
    Cursor waitCursor = shell.getDisplay().getSystemCursor(SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);

    boolean matchCase = searchDialog.getMatchCase();
    boolean matchWord = searchDialog.getMatchWord();
    String searchString = searchDialog.getSearchString();
    int column = searchDialog.getSelectedSearchArea();

    searchString = matchCase ? searchString : searchString.toLowerCase();

    boolean found = false;
    if (searchDialog.getSearchDown()) {
      for (int i = table.getSelectionIndex() + 1; i < table.getItemCount(); i++) {
        if (found = findMatch(searchString, table.getItem(i), column, matchWord, matchCase)) {
          table.setSelection(i);
          break;
        }
      }
    } else {
      for (int i = table.getSelectionIndex() - 1; i > -1; i--) {
        if (found = findMatch(searchString, table.getItem(i), column, matchWord, matchCase)) {
          table.setSelection(i);
          break;
        }
      }
    }

    shell.setCursor(null);

    return found;
  }
  private boolean save() {
    if (file == null) return saveAs();

    Cursor waitCursor = new Cursor(shell.getDisplay(), SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);

    TableItem[] items = table.getItems();
    String[] lines = new String[items.length];
    for (int i = 0; i < items.length; i++) {
      String[] itemText = new String[table.getColumnCount()];
      for (int j = 0; j < itemText.length; j++) {
        itemText[j] = items[i].getText(j);
      }
      lines[i] = encodeLine(itemText);
    }

    FileWriter fileWriter = null;
    try {
      fileWriter = new FileWriter(file.getAbsolutePath(), false);
      for (int i = 0; i < lines.length; i++) {
        fileWriter.write(lines[i]);
      }
    } catch (FileNotFoundException e) {
      displayError(resMessages.getString("File_not_found") + "\n" + file.getName());
      return false;
    } catch (IOException e) {
      displayError(resMessages.getString("IO_error_write") + "\n" + file.getName());
      return false;
    } finally {
      shell.setCursor(null);
      waitCursor.dispose();

      if (fileWriter != null) {
        try {
          fileWriter.close();
        } catch (IOException e) {
          displayError(resMessages.getString("IO_error_close") + "\n" + file.getName());
          return false;
        }
      }
    }

    shell.setText(resMessages.getString("Title_bar") + file.getName());
    isModified = false;
    return true;
  }
  private void openAddressBook() {
    FileDialog fileDialog = new FileDialog(shell, SWT.OPEN);

    fileDialog.setFilterExtensions(new String[] {"*.adr;", "*.*"});
    fileDialog.setFilterNames(
        new String[] {
          resAddressBook.getString("Book_filter_name") + " (*.adr)",
          resAddressBook.getString("All_filter_name") + " (*.*)"
        });
    String name = fileDialog.open();

    if (name == null) return;
    File file = new File(name);
    if (!file.exists()) {
      displayError(
          resAddressBook.getString("File")
              + file.getName()
              + " "
              + resAddressBook.getString("Does_not_exist"));
      return;
    }

    Cursor waitCursor = shell.getDisplay().getSystemCursor(SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);

    FileReader fileReader = null;
    BufferedReader bufferedReader = null;
    String[] data = new String[0];
    try {
      fileReader = new FileReader(file.getAbsolutePath());
      bufferedReader = new BufferedReader(fileReader);
      String nextLine = bufferedReader.readLine();
      while (nextLine != null) {
        String[] newData = new String[data.length + 1];
        System.arraycopy(data, 0, newData, 0, data.length);
        newData[data.length] = nextLine;
        data = newData;
        nextLine = bufferedReader.readLine();
      }
    } catch (FileNotFoundException e) {
      displayError(resAddressBook.getString("File_not_found") + "\n" + file.getName());
      return;
    } catch (IOException e) {
      displayError(resAddressBook.getString("IO_error_read") + "\n" + file.getName());
      return;
    } finally {

      shell.setCursor(null);

      if (fileReader != null) {
        try {
          fileReader.close();
        } catch (IOException e) {
          displayError(resAddressBook.getString("IO_error_close") + "\n" + file.getName());
          return;
        }
      }
    }

    String[][] tableInfo = new String[data.length][table.getColumnCount()];
    int writeIndex = 0;
    for (int i = 0; i < data.length; i++) {
      String[] line = decodeLine(data[i]);
      if (line != null) tableInfo[writeIndex++] = line;
    }
    if (writeIndex != data.length) {
      String[][] result = new String[writeIndex][table.getColumnCount()];
      System.arraycopy(tableInfo, 0, result, 0, writeIndex);
      tableInfo = result;
    }
    Arrays.sort(tableInfo, new RowComparator(0));

    for (int i = 0; i < tableInfo.length; i++) {
      TableItem item = new TableItem(table, SWT.NONE);
      item.setText(tableInfo[i]);
    }
    shell.setText(resAddressBook.getString("Title_bar") + fileDialog.getFileName());
    isModified = false;
    this.file = file;
  }
  private void openAddressBook(String name) {
    if (name == null) return;
    File file = new File(name);
    if (!file.exists()) {
      displayError(
          resMessages.getString("File")
              + file.getName()
              + " "
              + resMessages.getString("Does_not_exist"));
      return;
    }

    Cursor waitCursor = new Cursor(shell.getDisplay(), SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);

    FileReader fileReader = null;
    BufferedReader bufferedReader = null;
    String[] data = new String[0];
    try {
      fileReader = new FileReader(file.getAbsolutePath());
      bufferedReader = new BufferedReader(fileReader);
      String nextLine = bufferedReader.readLine();
      while (nextLine != null) {
        String[] newData = new String[data.length + 1];
        System.arraycopy(data, 0, newData, 0, data.length);
        newData[data.length] = nextLine;
        data = newData;
        nextLine = bufferedReader.readLine();
      }
    } catch (FileNotFoundException e) {
      displayError(resMessages.getString("File_not_found") + "\n" + file.getName());
      return;
    } catch (IOException e) {
      displayError(resMessages.getString("IO_error_read") + "\n" + file.getName());
      return;
    } finally {

      shell.setCursor(null);
      waitCursor.dispose();

      if (fileReader != null) {
        try {
          fileReader.close();
        } catch (IOException e) {
          displayError(resMessages.getString("IO_error_close") + "\n" + file.getName());
          return;
        }
      }
    }

    String[][] tableInfo = new String[data.length][table.getColumnCount()];
    for (int i = 0; i < data.length; i++) {
      tableInfo[i] = decodeLine(data[i]);
    }

    Arrays.sort(tableInfo, new RowComparator(0));

    for (int i = 0; i < tableInfo.length; i++) {
      TableItem item = new TableItem(table, SWT.NONE);
      item.setText(tableInfo[i]);
    }
    shell.setText(resMessages.getString("Title_bar") + file.getName());
    isModified = false;
    this.file = file;
  }
  /**
   * This method is called by Spoon when the user opens the settings dialog of the step. It should
   * open the dialog and return only once the dialog has been closed by the user.
   *
   * <p>If the user confirms the dialog, the meta object (passed in the constructor) must be updated
   * to reflect the new step settings. The changed flag of the meta object must reflect whether the
   * step configuration was changed by the dialog.
   *
   * <p>If the user cancels the dialog, the meta object must not be updated, and its changed flag
   * must remain unaltered.
   *
   * <p>The open() method must return the name of the step after the user has confirmed the dialog,
   * or null if the user cancelled the dialog.
   */
  public String open() {

    // store some convenient SWT variables
    Shell parent = getParent();
    Display display = parent.getDisplay();

    // SWT code for preparing the dialog
    shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN | SWT.MAX);
    props.setLook(shell);
    setShellImage(shell, meta);

    // Save the value of the changed flag on the meta object. If the user cancels
    // the dialog, it will be restored to this saved value.
    // The "changed" variable is inherited from BaseStepDialog
    changed = meta.hasChanged();

    // The ModifyListener used on all controls. It will update the meta object to
    // indicate that changes are being made.
    ModifyListener lsMod =
        new ModifyListener() {
          public void modifyText(ModifyEvent e) {
            meta.setChanged();
          }
        };

    // ------------------------------------------------------- //
    // SWT code for building the actual settings dialog        //
    // ------------------------------------------------------- //
    FormLayout formLayout = new FormLayout();
    formLayout.marginWidth = Const.FORM_MARGIN;
    formLayout.marginHeight = Const.FORM_MARGIN;

    shell.setLayout(formLayout);
    shell.setText(BaseMessages.getString(PKG, "UniqueListStep.Shell.Title"));

    int middle = props.getMiddlePct();
    int margin = Const.MARGIN;

    // Stepname line
    wlStepname = new Label(shell, SWT.RIGHT);
    wlStepname.setText(BaseMessages.getString(PKG, "System.Label.StepName"));
    props.setLook(wlStepname);
    fdlStepname = new FormData();
    fdlStepname.left = new FormAttachment(0, 0);
    fdlStepname.right = new FormAttachment(middle, -margin);
    fdlStepname.top = new FormAttachment(0, margin);
    wlStepname.setLayoutData(fdlStepname);

    wStepname = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
    wStepname.setText(stepname);
    props.setLook(wStepname);
    wStepname.addModifyListener(lsMod);
    fdStepname = new FormData();
    fdStepname.left = new FormAttachment(middle, 0);
    fdStepname.top = new FormAttachment(0, margin);
    fdStepname.right = new FormAttachment(100, 0);
    wStepname.setLayoutData(fdStepname);

    // Treat Consecutive Delimiters as One
    //
    wlRemoveBlanks = new Label(shell, SWT.RIGHT);
    wlRemoveBlanks.setText("Treat Consecutive Delims as One");
    wlRemoveBlanks.setToolTipText(
        "Multiple delimiters in a row will be condensed. Essentially removes blank spaces");
    props.setLook(wlRemoveBlanks);
    fdlRemoveBlanks = new FormData();
    fdlRemoveBlanks.left = new FormAttachment(0, 0);
    fdlRemoveBlanks.top = new FormAttachment(wStepname, margin);
    fdlRemoveBlanks.right = new FormAttachment(middle, -margin);
    wlRemoveBlanks.setLayoutData(fdlRemoveBlanks);
    wRemoveBlanks = new Button(shell, SWT.CHECK);
    wRemoveBlanks.setToolTipText(
        "Multiple delimiters in a row will be condensed. Essentially removes blank spaces");
    props.setLook(wRemoveBlanks);
    wRemoveBlanks.addSelectionListener(
        new SelectionAdapter() {
          public void widgetSelected(SelectionEvent e) {
            meta.setChanged();
          }
        });
    fdRemoveBlanks = new FormData();
    fdRemoveBlanks.left = new FormAttachment(middle, 0);
    fdRemoveBlanks.top = new FormAttachment(wStepname, margin);
    fdRemoveBlanks.right = new FormAttachment(100, 0);
    wRemoveBlanks.setLayoutData(fdRemoveBlanks);

    // OK and cancel buttons
    wOK = new Button(shell, SWT.PUSH);
    wOK.setText(BaseMessages.getString(PKG, "System.Button.OK"));
    wGet = new Button(shell, SWT.PUSH);
    wGet.setText("Get");
    wCancel = new Button(shell, SWT.PUSH);
    wCancel.setText(BaseMessages.getString(PKG, "System.Button.Cancel"));

    setButtonPositions(new Button[] {wOK, wGet, wCancel}, margin, null);

    wlGroup = new Label(shell, SWT.NONE);
    wlGroup.setText("Fields");
    props.setLook(wlGroup);
    fdlGroup = new FormData();
    fdlGroup.left = new FormAttachment(0, 0);
    fdlGroup.top = new FormAttachment(wlRemoveBlanks, margin);
    wlGroup.setLayoutData(fdlGroup);

    // Fields
    colinf =
        new ColumnInfo[] {
          new ColumnInfo("Field Name", ColumnInfo.COLUMN_TYPE_CCOMBO, new String[] {""}, false),
          new ColumnInfo("Current Delimiter", ColumnInfo.COLUMN_TYPE_TEXT, false),
          new ColumnInfo("Output Field Name (Optional)", ColumnInfo.COLUMN_TYPE_TEXT, false),
          new ColumnInfo("Output Delimiter (Optional)", ColumnInfo.COLUMN_TYPE_TEXT, false)
        };

    int nrRows = (meta.getSourceFields() != null ? meta.getSourceFields().length : 1);

    wGroup =
        new TableView(
            transMeta,
            shell,
            SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI | SWT.V_SCROLL | SWT.H_SCROLL,
            colinf,
            nrRows,
            lsMod,
            props);

    fdGroup = new FormData();
    fdGroup.left = new FormAttachment(0, 0);
    fdGroup.top = new FormAttachment(wlGroup, margin);
    fdGroup.right = new FormAttachment(100, 0);
    fdGroup.bottom = new FormAttachment(wGet, -margin);
    wGroup.setLayoutData(fdGroup);

    final Runnable runnable =
        new Runnable() {
          public void run() {
            StepMeta stepMeta = transMeta.findStep(stepname);
            if (stepMeta != null) {
              try {
                RowMetaInterface row = transMeta.getPrevStepFields(stepMeta);

                // Remember these fields...
                for (int i = 0; i < row.size(); i++) {
                  inputFields.put(row.getValueMeta(i).getName(), i);
                }
                setComboBoxes();
              } catch (KettleException e) {
                logError(BaseMessages.getString(PKG, "System.Dialog.GetFieldsFailed.Message"));
              }
            }
          }
        };
    new Thread(runnable).start();

    // Add listeners for cancel and OK
    lsCancel =
        new Listener() {
          public void handleEvent(Event e) {
            cancel();
          }
        };
    lsOK =
        new Listener() {
          public void handleEvent(Event e) {
            ok();
          }
        };
    lsGet =
        new Listener() {
          public void handleEvent(Event event) {
            get();
          }
        };

    wCancel.addListener(SWT.Selection, lsCancel);
    wOK.addListener(SWT.Selection, lsOK);
    wGet.addListener(SWT.Selection, lsGet);

    // default listener (for hitting "enter")
    lsDef =
        new SelectionAdapter() {
          public void widgetDefaultSelected(SelectionEvent e) {
            ok();
          }
        };
    wStepname.addSelectionListener(lsDef);

    // Detect X or ALT-F4 or something that kills this window and cancel the dialog properly
    shell.addShellListener(
        new ShellAdapter() {
          public void shellClosed(ShellEvent e) {
            cancel();
          }
        });

    // Set/Restore the dialog size based on last position on screen
    // The setSize() method is inherited from BaseStepDialog
    setSize();

    // populate the dialog with the values from the meta object
    populateDialog();

    // restore the changed flag to original value, as the modify listeners fire during dialog
    // population
    meta.setChanged(changed);

    // open dialog and enter event loop
    shell.open();
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch()) display.sleep();
    }

    // at this point the dialog has closed, so either ok() or cancel() have been executed
    // The "stepname" variable is inherited from BaseStepDialog
    return stepname;
  }