Autopsy  4.6.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
GlobalEditListPanel.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2011-2017 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.keywordsearch;
20 
21 import java.awt.EventQueue;
22 import java.beans.PropertyChangeEvent;
23 import java.beans.PropertyChangeListener;
24 import java.util.Arrays;
25 import java.util.List;
26 import java.util.logging.Level;
27 import java.util.regex.Pattern;
28 import java.util.regex.PatternSyntaxException;
29 import javax.swing.JTable;
30 import javax.swing.ListSelectionModel;
31 import javax.swing.event.ListSelectionEvent;
32 import javax.swing.event.ListSelectionListener;
33 import javax.swing.table.AbstractTableModel;
34 import javax.swing.table.TableColumn;
35 import org.netbeans.spi.options.OptionsPanelController;
36 import org.openide.util.NbBundle;
40 
44 class GlobalEditListPanel extends javax.swing.JPanel implements ListSelectionListener, OptionsPanel {
45 
46  private static final Logger logger = Logger.getLogger(GlobalEditListPanel.class.getName());
47  private static final long serialVersionUID = 1L;
48  private final KeywordTableModel tableModel;
49  private KeywordList currentKeywordList;
50 
54  GlobalEditListPanel() {
55  tableModel = new KeywordTableModel();
56  initComponents();
57  customizeComponents();
58  }
59 
60  private void customizeComponents() {
61  newKeywordsButton.setToolTipText((NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.customizeComponents.addWordToolTip")));
62  deleteWordButton.setToolTipText(NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.customizeComponents.removeSelectedMsg"));
63 
64  keywordTable.getParent().setBackground(keywordTable.getBackground());
65  final int width = jScrollPane1.getPreferredSize().width;
66  keywordTable.setAutoResizeMode(JTable.AUTO_RESIZE_NEXT_COLUMN);
67  TableColumn column;
68  for (int i = 0; i < keywordTable.getColumnCount(); i++) {
69  column = keywordTable.getColumnModel().getColumn(i);
70  if (i == 0) {
71  column.setPreferredWidth(((int) (width * 0.90)));
72  } else {
73  column.setPreferredWidth(((int) (width * 0.10)));
74  }
75  }
76  keywordTable.setCellSelectionEnabled(false);
77  keywordTable.setRowSelectionAllowed(true);
78 
79  final ListSelectionModel lsm = keywordTable.getSelectionModel();
80  lsm.addListSelectionListener(new ListSelectionListener() {
81  @Override
82  public void valueChanged(ListSelectionEvent e) {
83  boolean canDelete = !(lsm.isSelectionEmpty() || currentKeywordList.isEditable() || IngestManager.getInstance().isIngestRunning());
84  boolean canEdit = canDelete && (lsm.getMaxSelectionIndex() == lsm.getMinSelectionIndex()); //edit only enabled with single selection
85  deleteWordButton.setEnabled(canDelete);
86  editWordButton.setEnabled(canEdit);
87  }
88  });
89 
90  setButtonStates();
91 
92  IngestManager.getInstance().addIngestJobEventListener(new PropertyChangeListener() {
93  @Override
94  public void propertyChange(PropertyChangeEvent evt) {
95  Object source = evt.getSource();
96  if (source instanceof String && ((String) source).equals("LOCAL")) { //NON-NLS
97  EventQueue.invokeLater(() -> {
98  setButtonStates();
99  });
100  }
101  }
102  });
103  }
104 
108  void setButtonStates() {
109  boolean isIngestRunning = IngestManager.getInstance().isIngestRunning();
110  boolean isListSelected = currentKeywordList != null;
111 
112  // items that only need a selected list
113  boolean canEditList = isListSelected && !isIngestRunning;
114  ingestMessagesCheckbox.setEnabled(canEditList);
115  ingestMessagesCheckbox.setSelected(currentKeywordList != null && currentKeywordList.getIngestMessages());
116 
117  // items that need an unlocked list w/out ingest running
118  boolean canAddWord = canEditList && !currentKeywordList.isEditable();
119  newKeywordsButton.setEnabled(canAddWord);
120 
121  // items that need a non-empty list
122  if ((currentKeywordList == null) || (currentKeywordList.getKeywords().isEmpty())) {
123  deleteWordButton.setEnabled(false);
124  editWordButton.setEnabled(false);
125  }
126  }
127 
128  @NbBundle.Messages({"GlobalEditListPanel.editKeyword.title=Edit Keyword",
129  "GlobalEditListPanel.warning.title=Warning",
130  "GlobalEditListPanel.warning.text=Boundary characters ^ and $ do not match word boundaries. Consider\nreplacing with an explicit list of boundary characters, such as [ \\.,]"})
131 
138  private boolean addKeywordsAction(String existingKeywords, boolean isLiteral, boolean isWholeWord) {
139  String keywordsToRedisplay = existingKeywords;
140  AddKeywordsDialog dialog = new AddKeywordsDialog();
141 
142  int goodCount = 0;
143  int dupeCount = 0;
144  int badCount = 1; // Default to 1 so we enter the loop the first time
145 
146  if (!existingKeywords.isEmpty()) { //if there is an existing keyword then this action was called by the edit button
147  dialog.setTitle(NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.editKeyword.title"));
148  }
149  while (badCount > 0) {
150  dialog.setInitialKeywordList(keywordsToRedisplay, isLiteral, isWholeWord);
151  dialog.display();
152 
153  goodCount = 0;
154  dupeCount = 0;
155  badCount = 0;
156  keywordsToRedisplay = "";
157  boolean displayedBoundaryWarning = false;
158 
159  if (!dialog.getKeywords().isEmpty()) {
160 
161  for (String newWord : dialog.getKeywords()) {
162  if (newWord.isEmpty()) {
163  continue;
164  }
165 
166  final Keyword keyword = new Keyword(newWord, !dialog.isKeywordRegex(), dialog.isKeywordExact(), currentKeywordList.getName(), newWord);
167  if (currentKeywordList.hasKeyword(keyword)) {
168  dupeCount++;
169  continue;
170  }
171 
172  // Check if it is a regex and starts or ends with a boundary character
173  if (( ! displayedBoundaryWarning) && dialog.isKeywordRegex()) {
174  if(newWord.startsWith("^") ||
175  (newWord.endsWith("$") && ! newWord.endsWith("\\$"))) {
176 
177  KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.title"),
178  NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.warning.text"),
179  KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN);
180  // Only display the warning once
181  displayedBoundaryWarning = true;
182  }
183  }
184 
185  //check if valid
186  boolean valid = true;
187  try {
188  Pattern.compile(newWord);
189  } catch (PatternSyntaxException ex1) {
190  valid = false;
191  } catch (IllegalArgumentException ex2) {
192  valid = false;
193  }
194  if (!valid) {
195 
196  // Invalid keywords will reappear in the UI
197  keywordsToRedisplay += newWord + "\n";
198  badCount++;
199  continue;
200  }
201 
202  // Add the new keyword
203  tableModel.addKeyword(keyword);
204  goodCount++;
205  }
206  XmlKeywordSearchList.getCurrent().addList(currentKeywordList);
207  firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
208 
209  if ((badCount > 0) || (dupeCount > 0)) {
210  // Display the error counts to the user
211  // The add keywords dialog will pop up again if any were invalid with any
212  // invalid entries (valid entries and dupes will disappear)
213 
214  String summary = "";
215  KeywordSearchUtil.DIALOG_MESSAGE_TYPE level = KeywordSearchUtil.DIALOG_MESSAGE_TYPE.INFO;
216  if (goodCount > 0) {
217  if (goodCount > 1) {
218  summary += NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.keywordsAddedPlural.text", goodCount) + "\n";
219  } else {
220  summary += NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.keywordsAdded.text", goodCount) + "\n";
221  }
222  }
223  if (dupeCount > 0) {
224  if (dupeCount > 1) {
225  summary += NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.keywordDupesSkippedPlural.text", dupeCount) + "\n";
226  } else {
227  summary += NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.keywordDupesSkipped.text", dupeCount) + "\n";
228  }
229  level = KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN;
230  }
231  if (badCount > 0) {
232  if (badCount > 1) {
233  summary += NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.keywordErrorsPlural.text", badCount) + "\n";
234  } else {
235  summary += NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.keywordErrors.text", badCount) + "\n";
236  }
237  level = KeywordSearchUtil.DIALOG_MESSAGE_TYPE.ERROR;
238  }
239  KeywordSearchUtil.displayDialog(NbBundle.getMessage(this.getClass(), "GlobalEditListPanel.addKeywordResults.text"),
240  summary, level);
241  }
242  }
243  }
244  setFocusOnKeywordTextBox();
245  setButtonStates();
246  return (goodCount >= 1 && dupeCount == 0);
247  }
248 
255  private void deleteKeywordAction(int[] selectedKeywords) {
256  tableModel.deleteSelected(selectedKeywords);
257  XmlKeywordSearchList.getCurrent().addList(currentKeywordList);
258  setButtonStates();
259  }
260 
266  @SuppressWarnings("unchecked")
267  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
268  private void initComponents() {
269 
270  listEditorPanel = new javax.swing.JPanel();
271  jScrollPane1 = new javax.swing.JScrollPane();
272  keywordTable = new javax.swing.JTable();
273  ingestMessagesCheckbox = new javax.swing.JCheckBox();
274  keywordsLabel = new javax.swing.JLabel();
275  newKeywordsButton = new javax.swing.JButton();
276  deleteWordButton = new javax.swing.JButton();
277  editWordButton = new javax.swing.JButton();
278 
279  setMinimumSize(new java.awt.Dimension(0, 0));
280 
281  listEditorPanel.setMinimumSize(new java.awt.Dimension(0, 0));
282 
283  jScrollPane1.setPreferredSize(new java.awt.Dimension(340, 300));
284 
285  keywordTable.setModel(tableModel);
286  keywordTable.setAutoResizeMode(javax.swing.JTable.AUTO_RESIZE_OFF);
287  keywordTable.setGridColor(new java.awt.Color(153, 153, 153));
288  keywordTable.setMaximumSize(new java.awt.Dimension(30000, 30000));
289  keywordTable.getTableHeader().setReorderingAllowed(false);
290  jScrollPane1.setViewportView(keywordTable);
291 
292  ingestMessagesCheckbox.setSelected(true);
293  ingestMessagesCheckbox.setText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "KeywordSearchEditListPanel.ingestMessagesCheckbox.text")); // NOI18N
294  ingestMessagesCheckbox.setToolTipText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "KeywordSearchEditListPanel.ingestMessagesCheckbox.toolTipText")); // NOI18N
295  ingestMessagesCheckbox.addActionListener(new java.awt.event.ActionListener() {
296  public void actionPerformed(java.awt.event.ActionEvent evt) {
297  ingestMessagesCheckboxActionPerformed(evt);
298  }
299  });
300 
301  keywordsLabel.setText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "KeywordSearchEditListPanel.keywordsLabel.text")); // NOI18N
302 
303  newKeywordsButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/add16.png"))); // NOI18N
304  newKeywordsButton.setText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.newKeywordsButton.text")); // NOI18N
305  newKeywordsButton.addActionListener(new java.awt.event.ActionListener() {
306  public void actionPerformed(java.awt.event.ActionEvent evt) {
307  newKeywordsButtonActionPerformed(evt);
308  }
309  });
310 
311  deleteWordButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/delete16.png"))); // NOI18N
312  deleteWordButton.setText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "KeywordSearchEditListPanel.deleteWordButton.text")); // NOI18N
313  deleteWordButton.addActionListener(new java.awt.event.ActionListener() {
314  public void actionPerformed(java.awt.event.ActionEvent evt) {
315  deleteWordButtonActionPerformed(evt);
316  }
317  });
318 
319  editWordButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/keywordsearch/edit16.png"))); // NOI18N
320  editWordButton.setText(org.openide.util.NbBundle.getMessage(GlobalEditListPanel.class, "GlobalEditListPanel.editWordButton.text")); // NOI18N
321  editWordButton.addActionListener(new java.awt.event.ActionListener() {
322  public void actionPerformed(java.awt.event.ActionEvent evt) {
323  editWordButtonActionPerformed(evt);
324  }
325  });
326 
327  javax.swing.GroupLayout listEditorPanelLayout = new javax.swing.GroupLayout(listEditorPanel);
328  listEditorPanel.setLayout(listEditorPanelLayout);
329  listEditorPanelLayout.setHorizontalGroup(
330  listEditorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
331  .addGroup(listEditorPanelLayout.createSequentialGroup()
332  .addContainerGap()
333  .addGroup(listEditorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
334  .addGroup(listEditorPanelLayout.createSequentialGroup()
335  .addComponent(keywordsLabel)
336  .addGap(0, 0, Short.MAX_VALUE))
337  .addGroup(listEditorPanelLayout.createSequentialGroup()
338  .addGap(10, 10, 10)
339  .addGroup(listEditorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
340  .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
341  .addGroup(listEditorPanelLayout.createSequentialGroup()
342  .addGroup(listEditorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
343  .addComponent(ingestMessagesCheckbox)
344  .addGroup(listEditorPanelLayout.createSequentialGroup()
345  .addComponent(newKeywordsButton)
346  .addGap(14, 14, 14)
347  .addComponent(editWordButton)
348  .addGap(14, 14, 14)
349  .addComponent(deleteWordButton)))
350  .addGap(0, 0, Short.MAX_VALUE)))))
351  .addContainerGap())
352  );
353 
354  listEditorPanelLayout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {deleteWordButton, editWordButton, newKeywordsButton});
355 
356  listEditorPanelLayout.setVerticalGroup(
357  listEditorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
358  .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, listEditorPanelLayout.createSequentialGroup()
359  .addContainerGap()
360  .addComponent(keywordsLabel)
361  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
362  .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 257, Short.MAX_VALUE)
363  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
364  .addGroup(listEditorPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
365  .addComponent(deleteWordButton)
366  .addComponent(newKeywordsButton)
367  .addComponent(editWordButton))
368  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
369  .addComponent(ingestMessagesCheckbox)
370  .addGap(9, 9, 9))
371  );
372 
373  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
374  this.setLayout(layout);
375  layout.setHorizontalGroup(
376  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
377  .addComponent(listEditorPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
378  );
379  layout.setVerticalGroup(
380  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
381  .addGroup(layout.createSequentialGroup()
382  .addComponent(listEditorPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
383  .addGap(5, 5, 5))
384  );
385  }// </editor-fold>//GEN-END:initComponents
386 
387  private void deleteWordButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_deleteWordButtonActionPerformed
388  if (KeywordSearchUtil.displayConfirmDialog(NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.removeKwMsg"),
389  NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.deleteWordButtonActionPerformed.delConfirmMsg"),
390  KeywordSearchUtil.DIALOG_MESSAGE_TYPE.WARN)) {
391  deleteKeywordAction(keywordTable.getSelectedRows());
392  firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
393  }
394  }//GEN-LAST:event_deleteWordButtonActionPerformed
395 
396  private void ingestMessagesCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ingestMessagesCheckboxActionPerformed
397  currentKeywordList.setIngestMessages(ingestMessagesCheckbox.isSelected());
398  XmlKeywordSearchList updater = XmlKeywordSearchList.getCurrent();
399  updater.addList(currentKeywordList);
400  firePropertyChange(OptionsPanelController.PROP_CHANGED, null, null);
401  }//GEN-LAST:event_ingestMessagesCheckboxActionPerformed
402 
403  private void newKeywordsButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_newKeywordsButtonActionPerformed
404  addKeywordsAction("", true, true);
405  }//GEN-LAST:event_newKeywordsButtonActionPerformed
406 
407  private void editWordButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_editWordButtonActionPerformed
408  int[] selectedKeywords = keywordTable.getSelectedRows();
409  if (selectedKeywords.length == 1) {
410  Keyword currentKeyword = currentKeywordList.getKeywords().get(selectedKeywords[0]);
411  if (addKeywordsAction(currentKeyword.getSearchTerm(), currentKeyword.searchTermIsLiteral(), currentKeyword.searchTermIsWholeWord())) {
412  deleteKeywordAction(selectedKeywords);
413  }
414  }
415  }//GEN-LAST:event_editWordButtonActionPerformed
416 
417  // Variables declaration - do not modify//GEN-BEGIN:variables
418  private javax.swing.JButton deleteWordButton;
419  private javax.swing.JButton editWordButton;
420  private javax.swing.JCheckBox ingestMessagesCheckbox;
421  private javax.swing.JScrollPane jScrollPane1;
422  private javax.swing.JTable keywordTable;
423  private javax.swing.JLabel keywordsLabel;
424  private javax.swing.JPanel listEditorPanel;
425  private javax.swing.JButton newKeywordsButton;
426  // End of variables declaration//GEN-END:variables
427 
428  @Override
429  public void valueChanged(ListSelectionEvent e) {
430  //respond to list selection changes in KeywordSearchListManagementPanel
431  ListSelectionModel listSelectionModel = (ListSelectionModel) e.getSource();
432  currentKeywordList = null;
433  if (!listSelectionModel.isSelectionEmpty()) {
434  XmlKeywordSearchList loader = XmlKeywordSearchList.getCurrent();
435  if (listSelectionModel.getMinSelectionIndex() == listSelectionModel.getMaxSelectionIndex()) {
436  currentKeywordList = loader.getListsL(false).get(listSelectionModel.getMinSelectionIndex());
437  }
438  }
439  tableModel.resync();
440  setButtonStates();
441  }
442 
443  @Override
444  public void store() {
445  // Implemented by parent panel
446  }
447 
448  @Override
449  public void load() {
450  // Implemented by parent panel
451  }
452 
453  KeywordList getCurrentKeywordList() {
454  return currentKeywordList;
455  }
456 
457  void setCurrentKeywordList(KeywordList list) {
458  currentKeywordList = list;
459  }
460 
461  private class KeywordTableModel extends AbstractTableModel {
462 
463  @Override
464  public int getColumnCount() {
465  return 2;
466  }
467 
468  @Override
469  public int getRowCount() {
470  return currentKeywordList == null ? 0 : currentKeywordList.getKeywords().size();
471  }
472 
473  @Override
474  public String getColumnName(int column) {
475  String colName = null;
476 
477  switch (column) {
478  case 0:
479  colName = NbBundle.getMessage(this.getClass(), "KeywordSearchEditListPanel.kwColName");
480  break;
481  case 1:
482  colName = NbBundle.getMessage(this.getClass(), "KeywordSearch.typeColLbl");
483  break;
484  default:
485  ;
486  }
487  return colName;
488  }
489 
490  @Override
491  public Object getValueAt(int rowIndex, int columnIndex) {
492  Object ret = null;
493  if (currentKeywordList == null) {
494  return "";
495  }
496  Keyword word = currentKeywordList.getKeywords().get(rowIndex);
497  switch (columnIndex) {
498  case 0:
499  ret = word.getSearchTerm();
500  break;
501  case 1:
502  ret = word.getSearchTermType();
503  break;
504  default:
505  logger.log(Level.SEVERE, "Invalid table column index: {0}", columnIndex); //NON-NLS
506  break;
507  }
508  return ret;
509  }
510 
511  @Override
512  public boolean isCellEditable(int rowIndex, int columnIndex) {
513  return false;
514  }
515 
516  @Override
517  public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
518  }
519 
520  @Override
521  public Class<?> getColumnClass(int c) {
522  return getValueAt(0, c).getClass();
523  }
524 
525  void addKeyword(Keyword keyword) {
526  if (!currentKeywordList.hasKeyword(keyword)) {
527  currentKeywordList.getKeywords().add(keyword);
528  }
529  fireTableDataChanged();
530  }
531 
532  void resync() {
533  fireTableDataChanged();
534  }
535 
536  //delete selected from handle, events are fired from the handle
537  void deleteSelected(int[] selected) {
538  List<Keyword> words = currentKeywordList.getKeywords();
539  Arrays.sort(selected);
540  for (int arrayi = selected.length - 1; arrayi >= 0; arrayi--) {
541  words.remove(selected[arrayi]);
542  }
543  resync();
544  }
545  }
546 
550  void setFocusOnKeywordTextBox() {
551  newKeywordsButton.requestFocus();
552  }
553 }

Copyright © 2012-2016 Basis Technology. Generated on: Mon May 7 2018
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.