1 23 package org.enhydra.kelp.common.deployer; 24 25 import org.enhydra.tool.ToolBoxInfo; 27 import org.enhydra.tool.common.IconSet; 28 import org.enhydra.tool.common.ToolException; 29 import org.enhydra.tool.configure.ConfigTool; 30 31 import org.enhydra.kelp.common.Backward; 33 import org.enhydra.kelp.common.PathUtil; 34 import org.enhydra.kelp.common.event.SwingTableSelectionEvent; 35 import org.enhydra.kelp.common.event.SwingTableSelectionListener; 36 import org.enhydra.kelp.common.node.OtterTemplateNode; 37 import org.enhydra.kelp.common.node.OtterNode; 38 import org.enhydra.kelp.common.node.OtterProject; 39 40 import java.awt.*; 42 import java.awt.event.ActionEvent ; 43 import java.awt.event.ActionListener ; 44 import java.awt.event.ItemEvent ; 45 import java.awt.event.ItemListener ; 46 import java.beans.*; 47 import java.io.File ; 48 import java.util.ResourceBundle ; 49 import java.util.ArrayList ; 50 import java.util.Arrays ; 51 import java.util.Vector ; 52 import javax.swing.*; 53 import javax.swing.event.ListSelectionEvent ; 54 import javax.swing.event.ListSelectionListener ; 55 import javax.swing.table.AbstractTableModel ; 56 import javax.swing.table.DefaultTableModel ; 57 58 public class ReplacementTablePanel extends JPanel 60 implements ListSelectionListener , SwingTableSelectionListener { 61 static ResourceBundle res = 62 ResourceBundle.getBundle("org.enhydra.kelp.common.Res"); private OtterNode node = null; 64 private GridBagLayout layoutMain; 65 private JTable table; 66 private JScrollPane scrollTable; 67 private JButton buttonAdd; 68 private JButton buttonEdit; 69 private JButton buttonRemove; 70 private JButton buttonReset; 71 private JButton buttonUp; 72 private JButton buttonDown; 73 private LocalButtonListener buttonListener; 74 private LocalTableModel tableModel = new LocalTableModel(); 75 private int currentSelectionIndex = -1; 76 private SwingTableSelectionListener[] swingTableSelectionListeners = 77 new SwingTableSelectionListener[0]; 78 79 public ReplacementTablePanel() { 80 try { 81 jbInit(); 82 pmInit(); 83 } catch (Exception e) { 84 e.printStackTrace(); 85 } 86 } 87 88 public void clearAll() { 89 buttonAdd.removeActionListener(buttonListener); 90 buttonEdit.removeActionListener(buttonListener); 91 buttonReset.removeActionListener(buttonListener); 92 buttonRemove.removeActionListener(buttonListener); 93 buttonUp.removeActionListener(buttonListener); 94 buttonDown.removeActionListener(buttonListener); 95 removeSwingTableSelectionListener(this); 96 table.getSelectionModel().removeListSelectionListener(this); 97 tableModel.removeTableModelListener(table); 98 tableModel.removeAllRows(); 99 table.setModel(new DefaultTableModel ()); 100 removeAll(); 101 tableModel = null; 102 buttonListener = null; 103 node = null; 104 swingTableSelectionListeners = null; 105 } 106 107 110 public void valueChanged(ListSelectionEvent e) { 111 ListSelectionModel lsm = (ListSelectionModel) e.getSource(); 112 113 if (lsm.isSelectionEmpty()) { 114 currentSelectionIndex = -1; 115 } else { 116 currentSelectionIndex = lsm.getMinSelectionIndex(); 117 } 118 fireSwingTableSelectionEvent(); 119 } 120 121 private void fireSwingTableSelectionEvent() { 122 for (int i = 0; i < swingTableSelectionListeners.length; i++) { 123 swingTableSelectionListeners[i].onSwingTableSelection(new SwingTableSelectionEvent(this, 124 currentSelectionIndex)); 125 } 126 } 127 128 public synchronized void addSwingTableSelectionListener(SwingTableSelectionListener l) { 129 ArrayList list = null; 130 131 list = new ArrayList (Arrays.asList(swingTableSelectionListeners)); 132 if (!list.contains(l)) { 133 list.add(l); 134 list.trimToSize(); 135 swingTableSelectionListeners = 136 new SwingTableSelectionListener[list.size()]; 137 swingTableSelectionListeners = 138 (SwingTableSelectionListener[]) list.toArray(swingTableSelectionListeners); 139 } 140 list.clear(); 141 } 142 143 public synchronized void removeSwingTableSelectionListener(SwingTableSelectionListener l) { 144 ArrayList list = null; 145 146 list = new ArrayList (Arrays.asList(swingTableSelectionListeners)); 147 if (list.contains(l)) { 148 list.remove(l); 149 list.trimToSize(); 150 swingTableSelectionListeners = 151 new SwingTableSelectionListener[list.size()]; 152 swingTableSelectionListeners = 153 (SwingTableSelectionListener[]) list.toArray(swingTableSelectionListeners); 154 } 155 list.clear(); 156 } 157 158 public void onSwingTableSelection(SwingTableSelectionEvent event) { 159 boolean selection = (!event.isSelectionNull()); 160 161 buttonEdit.setEnabled(selection); 162 buttonRemove.setEnabled(selection); 163 if (selection) { 164 int index = event.getSelectionIndex(); 165 boolean down = (index < tableModel.getRowCount() - 1); 166 boolean up = (index > 0); 167 168 buttonUp.setEnabled(up); 169 buttonDown.setEnabled(down); 170 } else { 171 buttonUp.setEnabled(false); 172 buttonDown.setEnabled(false); 173 } 174 } 175 176 public OtterNode getNode() { 177 return node; 178 } 179 180 public void setNode(OtterNode n) { 181 node = n; 182 } 183 184 public void setIconView(boolean b) { 185 if (b) { 186 buttonAdd.setIcon(IconSet.getNewRowIcon()); 187 buttonAdd.setText(new String ()); 188 buttonDown.setIcon(IconSet.getDownIcon()); 189 buttonDown.setText(new String ()); 190 buttonEdit.setIcon(IconSet.getRowIcon()); 191 buttonEdit.setText(new String ()); 192 buttonUp.setIcon(IconSet.getUpIcon()); 193 buttonUp.setText(new String ()); 194 buttonRemove.setIcon(IconSet.getDeleteRowIcon()); 195 buttonRemove.setText(new String ()); 196 buttonReset.setIcon(IconSet.getUndoIcon()); 197 buttonReset.setText(new String ()); 198 } else { 199 buttonAdd.setIcon(null); 200 buttonAdd.setText(res.getString("buttonAdd_Text")); 201 buttonDown.setIcon(null); 202 buttonDown.setText(res.getString("buttonDown_Text")); 203 buttonEdit.setIcon(null); 204 buttonEdit.setText(res.getString("Edit")); 205 buttonUp.setIcon(null); 206 buttonUp.setText(res.getString("buttonUp_Text")); 207 buttonRemove.setIcon(null); 208 buttonRemove.setText(res.getString("Remove")); 209 buttonReset.setIcon(null); 210 buttonReset.setText(res.getString("buttonReset_Text")); 211 } 212 } 213 214 public boolean isIconView() { 215 return (buttonAdd.getIcon() == null); 216 } 217 218 public void readProperties() { 219 String stringIn = null; 220 File fileIn = null; 221 OtterProject project = null; 222 223 if (node == null) { 224 System.err.println("ReplacementTablePanel.readProperties(): node is null"); 225 } else { 226 project = node.getProject(); 227 if (node instanceof OtterTemplateNode) {} 228 else { 229 enableUI(true); 230 } 231 if (project.isDefaultProject()) { 232 buttonAdd.setEnabled(false); 233 buttonDown.setEnabled(false); 234 buttonUp.setEnabled(false); 235 buttonReset.setEnabled(false); 236 } else { 237 setReplacementTable(project.getReplacementTable()); 238 } 239 } 240 invalidate(); 241 } 242 243 public void writeProperties() { 244 OtterProject project = null; 245 246 if (node == null) { 247 System.err.println("ReplacementTablePanel.writeProperties() - node null"); 248 return; 249 } else { 250 project = node.getProject(); 251 } 252 if (project.isDefaultProject()) { 253 254 } else { 256 project.setReplacementTable(getReplacementTable()); 257 } 258 } 259 260 public boolean isDataEqual() { 261 String [][] projReps = new String [0][0]; 262 String [][] memReps = new String [0][0]; 263 boolean equal = false; 264 OtterProject project = null; 265 266 if (node == null) { 267 System.err.println("ReplacementTablePanel.isDataEqual(): node is null"); 268 } else { 269 project = node.getProject(); 270 projReps = project.getReplacementTable(); 271 memReps = getReplacementTable(); 272 if (projReps.length == memReps.length) { 273 equal = true; 274 for (int i = 0; i < memReps.length; i++) { 275 if (equal && (projReps[i].length == memReps[i].length)) { 276 for (int j = 0; j < memReps[i].length; j++) { 277 if (projReps[i][j].equals(memReps[i][j])) {} 278 else { 279 equal = false; 280 break; 281 } 282 } 283 } else { 284 equal = false; 285 break; 286 } 287 } 288 } 289 } 290 return equal; 291 } 292 293 public void enableUI(boolean enable) { 294 table.setEnabled(enable); 295 buttonAdd.setEnabled(enable); 296 buttonEdit.setEnabled(currentSelectionIndex > -1); 297 buttonRemove.setEnabled(currentSelectionIndex > -1); 298 buttonReset.setEnabled(enable); 299 } 300 301 private String [][] getReplacementTable() { 304 String [][] repTable = null; 305 LocalRow row = null; 306 int length = tableModel.getRowCount(); 307 308 repTable = new String [length][2]; 309 for (int i = 0; i < length; i++) { 310 row = tableModel.getRow(i); 311 repTable[i][0] = row.getFind(); 312 repTable[i][1] = row.getReplaceWith(); 313 } 314 return repTable; 315 } 316 317 private void setReplacementTable(String [][] repTable) { 318 int length = tableModel.getRowCount(); 319 320 tableModel.removeAllRows(); 321 for (int i = 0; i < repTable.length; i++) { 322 tableModel.addRow(repTable[i][0], repTable[i][1]); 323 } 324 removeTableSelections(); 325 } 326 327 private void removeTableSelections() { 328 if (table.getSelectedRow() > -1) { 329 table.removeRowSelectionInterval(table.getSelectedRow(), 330 table.getSelectedRow()); 331 } 332 currentSelectionIndex = -1; 333 fireSwingTableSelectionEvent(); 334 table.updateUI(); 335 } 336 337 private void defaultTable() { 338 int result = JOptionPane.YES_OPTION; 339 340 if (tableModel.getRowCount() > 0) { 341 result = 342 JOptionPane.showConfirmDialog(this, 343 res.getString("Replace_current_set"), 344 res.getString("Switch_to_Default"), 345 JOptionPane.YES_NO_OPTION); 346 } 347 if (result == JOptionPane.YES_OPTION) { 348 OtterProject project = node.getProject(); 349 String [][] def = new String [0][0]; 350 351 try { 352 def = ConfigTool.createReplacementStringArray(project.getRootPath(), 353 project.getDeployRootPath(), 354 ToolBoxInfo.getJavaPath()); 355 def = 356 Backward.createReplacementTable(def, 357 PathUtil.getInputTemplates(project)); 358 } catch (ToolException e) { 359 def = new String [0][0]; 360 e.printStackTrace(); 361 } 362 setReplacementTable(def); 363 } 364 } 365 366 private void addRow() { 367 ReplaceEditorDialog d = null; 368 369 if (getTopLevelAncestor() instanceof JDialog) { 370 JDialog parentDialog = (JDialog) this.getTopLevelAncestor(); 371 372 d = new ReplaceEditorDialog(parentDialog, 373 res.getString("Add_Replace_Text"), 374 true); 375 d.setFind(new String ()); 376 d.setReplaceWith(new String ()); 377 d.show(); 378 if (d.getOption() == ReplaceEditorDialog.ACTION_OK) { 379 if (d.getFind().trim().length() > 0) { 380 tableModel.addRow(d.getFind(), d.getReplaceWith()); 381 removeTableSelections(); 382 } 383 } 384 } 385 } 386 387 private void removeCurrent() { 388 int result = 0; 389 390 if (currentSelectionIndex > -1 391 && currentSelectionIndex < tableModel.getRowCount()) { 392 result = 393 JOptionPane.showConfirmDialog(this, 394 res.getString("Remove_current"), 395 res.getString("Confirm_Remove"), 396 JOptionPane.YES_NO_OPTION); 397 if (result == JOptionPane.YES_OPTION) { 398 tableModel.removeRow(currentSelectionIndex); 399 removeTableSelections(); 400 } 401 } 402 } 403 404 private void editCurrent() { 405 ReplaceEditorDialog d = null; 406 LocalRow row = tableModel.getRow(currentSelectionIndex); 407 408 if (getTopLevelAncestor() instanceof JDialog) { 409 JDialog parentDialog = (JDialog) this.getTopLevelAncestor(); 410 411 d = new ReplaceEditorDialog(parentDialog, 412 res.getString("Edit_Replace_Text"), 413 true); 414 d.setFind(row.getFind()); 415 d.setReplaceWith(row.getReplaceWith()); 416 d.show(); 417 if (d.getOption() == ReplaceEditorDialog.ACTION_OK) { 418 if (d.getFind().trim().length() > 0) { 419 row.setFind(d.getFind()); 420 row.setReplaceWith(d.getReplaceWith()); 421 table.updateUI(); 422 } 423 } 424 } 425 } 426 427 private void moveRowUp() { 428 tableModel.moveUp(currentSelectionIndex); 429 table.updateUI(); 430 } 431 432 private void moveRowDown() { 433 tableModel.moveDown(currentSelectionIndex); 434 table.updateUI(); 435 } 436 437 private void pmInit() throws Exception { 438 buttonEdit.setEnabled(false); 439 buttonEdit.setToolTipText(res.getString("buttonEdit_ToolTipText")); 440 buttonRemove.setEnabled(false); 441 buttonRemove.setToolTipText(res.getString("buttonRemove_ToolTipText")); 442 buttonListener = new LocalButtonListener(); 443 buttonAdd.addActionListener(buttonListener); 444 buttonAdd.setToolTipText(res.getString("buttonAdd_ToolTipText")); 445 buttonEdit.addActionListener(buttonListener); 446 buttonReset.addActionListener(buttonListener); 447 buttonReset.setToolTipText(res.getString("buttonReset_ToolTipText")); 448 buttonRemove.addActionListener(buttonListener); 449 buttonUp.addActionListener(buttonListener); 450 buttonUp.setToolTipText(res.getString("buttonUp_ToolTipText")); 451 buttonDown.addActionListener(buttonListener); 452 buttonDown.setToolTipText(res.getString("buttonDown_ToolTipText")); 453 tableModel = new LocalTableModel(); 454 table.setModel(tableModel); 455 456 tableModel.addTableModelListener(table); 458 table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 459 table.getTableHeader().setUpdateTableInRealTime(false); 460 table.getTableHeader().setReorderingAllowed(false); 461 ListSelectionModel rowSM = table.getSelectionModel(); 462 463 rowSM.addListSelectionListener(this); 464 addSwingTableSelectionListener(this); 465 setIconView(false); 466 } 467 468 private void jbInit() throws Exception { 469 layoutMain = 470 (GridBagLayout) Beans.instantiate(getClass().getClassLoader(), 471 GridBagLayout.class.getName()); 472 table = (JTable) Beans.instantiate(getClass().getClassLoader(), 473 JTable.class.getName()); 474 buttonAdd = (JButton) Beans.instantiate(getClass().getClassLoader(), 475 JButton.class.getName()); 476 buttonEdit = (JButton) Beans.instantiate(getClass().getClassLoader(), 477 JButton.class.getName()); 478 buttonRemove = 479 (JButton) Beans.instantiate(getClass().getClassLoader(), 480 JButton.class.getName()); 481 buttonReset = (JButton) Beans.instantiate(getClass().getClassLoader(), 482 JButton.class.getName()); 483 buttonUp = (JButton) Beans.instantiate(getClass().getClassLoader(), 484 JButton.class.getName()); 485 buttonDown = (JButton) Beans.instantiate(getClass().getClassLoader(), 486 JButton.class.getName()); 487 scrollTable = new JScrollPane(table); 488 table.setToolTipText(new String ()); 489 table.sizeColumnsToFit(JTable.AUTO_RESIZE_ALL_COLUMNS); 490 table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); 491 table.setColumnSelectionAllowed(false); 492 scrollTable.setMinimumSize(new Dimension(300, 100)); 493 scrollTable.setPreferredSize(new Dimension(300, 100)); 494 scrollTable.getViewport().add(table, BorderLayout.CENTER); 495 496 this.setLayout(layoutMain); 498 this.add(scrollTable, 499 new GridBagConstraints(3, 0, 6, 1, 0.8, 0.2, 500 GridBagConstraints.CENTER, 501 GridBagConstraints.BOTH, 502 new Insets(0, 0, 5, 0), 0, 0)); 503 this.add(buttonAdd, 504 new GridBagConstraints(3, 1, 1, 1, 0.1, 0.0, 505 GridBagConstraints.CENTER, 506 GridBagConstraints.NONE, 507 new Insets(2, 1, 5, 1), 0, 0)); 508 this.add(buttonEdit, 509 new GridBagConstraints(4, 1, 1, 1, 0.1, 0.0, 510 GridBagConstraints.CENTER, 511 GridBagConstraints.NONE, 512 new Insets(2, 1, 5, 1), 0, 0)); 513 this.add(buttonRemove, 514 new GridBagConstraints(5, 1, 1, 1, 0.1, 0.0, 515 GridBagConstraints.CENTER, 516 GridBagConstraints.NONE, 517 new Insets(2, 1, 5, 1), 0, 0)); 518 this.add(buttonReset, 519 new GridBagConstraints(8, 1, 1, 1, 0.1, 0.0, 520 GridBagConstraints.CENTER, 521 GridBagConstraints.NONE, 522 new Insets(2, 1, 5, 1), 0, 0)); 523 this.add(buttonUp, 524 new GridBagConstraints(6, 1, 1, 1, 0.1, 0.0, 525 GridBagConstraints.CENTER, 526 GridBagConstraints.NONE, 527 new Insets(2, 1, 5, 1), 0, 0)); 528 this.add(buttonDown, 529 new GridBagConstraints(7, 1, 1, 1, 0.1, 0.0, 530 GridBagConstraints.CENTER, 531 GridBagConstraints.NONE, 532 new Insets(2, 1, 5, 1), 0, 0)); 533 } 534 535 private class LocalButtonListener implements ActionListener { 537 public void actionPerformed(ActionEvent e) { 538 Object source = e.getSource(); 539 540 if (source == buttonReset) { 541 defaultTable(); 542 } else if (source == buttonRemove) { 543 removeCurrent(); 544 } else if (source == buttonAdd) { 545 addRow(); 546 } else if (source == buttonEdit) { 547 editCurrent(); 548 } else if (source == buttonUp) { 549 moveRowUp(); 550 } else if (source == buttonDown) { 551 moveRowDown(); 552 } 553 } 554 555 } 556 557 private class LocalTableModel extends AbstractTableModel { 559 private Vector rowVector = new Vector (); 560 561 public LocalTableModel() {} 562 563 public int getRowCount() { 564 int count = 0; 565 566 if (rowVector != null) { 567 count = rowVector.size(); 568 } 569 return count; 570 } 571 572 public int getColumnCount() { 573 return 2; 574 } 575 576 public String getColumnName(int columnIndex) { 577 String name = new String (); 578 579 switch (columnIndex) { 580 case 0: 581 name = res.getString("Text_to_find"); 582 break; 583 case 1: 584 name = res.getString("Replace_with"); 585 break; 586 } 587 return name; 588 } 589 590 public Class getColumnClass(int columnIndex) { 591 Class columnClass = null; 592 Object value = getValueAt(0, columnIndex); 593 594 if (value != null) { 595 columnClass = value.getClass(); 596 } 597 return columnClass; 598 } 599 600 public boolean isCellEditable(int rowIndex, int columnIndex) { 601 return false; 602 } 603 604 public Object getValueAt(int rowIndex, int columnIndex) { 605 Object value = null; 606 607 if (!isTableEmpty()) { 608 LocalRow row = (LocalRow) rowVector.elementAt(rowIndex); 609 LocalCell cell = new LocalCell(columnIndex, row); 610 611 value = cell.getValue(); 612 } 613 return value; 614 } 615 616 public void setValueAt(Object aValue, int rowIndex, int columnIndex) { 617 if (!isTableEmpty()) { 618 LocalRow row = (LocalRow) rowVector.elementAt(rowIndex); 619 LocalCell cell = new LocalCell(columnIndex, row); 620 621 cell.setValue(aValue); 622 fireTableCellUpdated(columnIndex, rowIndex); 623 } 624 } 625 626 protected LocalRow getRow(int rowIndex) { 627 LocalRow row = null; 628 629 if (!isTableEmpty()) { 630 if (rowVector.size() > rowIndex) { 631 row = (LocalRow) rowVector.elementAt(rowIndex); 632 } 633 } 634 return row; 635 } 636 637 protected void saveMap() { 638 OtterProject project = node.getProject(); 639 int rowCount = rowVector.size(); 640 String [][] map = new String [rowCount][2]; 641 LocalRow row = null; 642 643 for (int i = 0; i < rowCount; i++) { 644 row = (LocalRow) rowVector.elementAt(i); 645 map[i][0] = row.getFind(); 646 map[i][1] = row.getReplaceWith(); 647 } 648 project.setPackageMap(map); 649 } 650 651 protected void populateModel() { 652 OtterProject project = node.getProject(); 653 String [][] map = project.getPackageMap(); 654 655 if (map != null) { 656 int rowCount = map.length; 657 658 for (int i = 0; i < rowCount; i++) { 659 addRow(map[i][0], map[i][1]); 660 } 661 } 662 } 663 664 private boolean isTableEmpty() { 667 boolean empty = true; 668 669 if (rowVector != null) { 670 if (rowVector.size() > 0) { 671 empty = false; 672 } 673 } 674 return empty; 675 } 676 677 private void addRow(String f, String p) { 678 LocalRow newRow = null; 679 680 newRow = new LocalRow(f, p); 681 rowVector.addElement(newRow); 682 int size = rowVector.size(); 683 684 fireTableDataChanged(); 685 } 686 687 private void removeRow(int index) { 688 if (index < rowVector.size()) { 689 rowVector.removeElementAt(index); 690 fireTableDataChanged(); 691 } 692 } 693 694 private void moveUp(int index) { 695 if (index > 0 && index < rowVector.size()) { 696 Object current = rowVector.elementAt(index); 697 Object previous = rowVector.elementAt(index - 1); 698 699 rowVector.setElementAt(current, index - 1); 700 rowVector.setElementAt(previous, index); 701 fireTableDataChanged(); 702 } 703 } 704 705 private void moveDown(int index) { 706 if (index < (rowVector.size() - 1)) { 707 Object current = rowVector.elementAt(index); 708 Object next = rowVector.elementAt(index + 1); 709 710 rowVector.setElementAt(current, index + 1); 711 rowVector.setElementAt(next, index); 712 fireTableDataChanged(); 713 } 714 } 715 716 private void removeAllRows() { 717 rowVector.removeAllElements(); 718 fireTableDataChanged(); 719 } 720 721 } 722 723 private class LocalRow { 725 private String find = new String (); 726 private String replaceWith = new String (); 727 728 public LocalRow(String f, String rw) { 729 find = f; 730 replaceWith = rw; 731 } 732 733 public String getFind() { 734 return find; 735 } 736 737 public void setFind(String f) { 738 find = f; 739 saveToProject(); 740 } 741 742 public String getReplaceWith() { 743 return replaceWith; 744 } 745 746 public void setReplaceWith(String rw) { 747 replaceWith = rw; 748 saveToProject(); 749 } 750 751 private void saveToProject() {} 752 753 } 754 755 private class LocalCell { 757 private LocalRow row; 758 private int column; 759 760 public LocalCell(int c, LocalRow r) { 761 column = c; 762 row = r; 763 } 764 765 public boolean setValue(Object value) { 766 boolean set = true; 767 768 switch (column) { 769 case 0: 770 row.setFind((String ) value); 771 break; 772 case 1: 773 row.setReplaceWith((String ) value); 774 break; 775 default: 776 set = false; 777 break; 778 } 779 return set; 780 } 781 782 public Object getValue() { 783 Object value = null; 784 785 switch (column) { 786 case 0: 787 value = row.getFind(); 788 break; 789 case 1: 790 value = row.getReplaceWith(); 791 break; 792 } 793 return value; 794 } 795 796 protected LocalRow getRow() { 799 return row; 800 } 801 802 } 803 } 804 | Popular Tags |