001    /*
002     * CRIMSON
003     * Copyright (c) 2006, Stephen Fisher, Susan Davidson, and Junhyong Kim, 
004     * University of Pennsylvania.
005     *
006     * This program is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU General Public License as
008     * published by the Free Software Foundation; either version 2 of the
009     * License, or (at your option) any later version.
010     *
011     * This program is distributed in the hope that it will be useful, but
012     * WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * General Public License for more details.
015     *
016     * You should have received a copy of the GNU General Public License
017     * along with this program; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019     * 02110-1301 USA.
020     *
021     * @(#)Root.java
022     */
023    
024    package edu.upenn.crimson.gui;
025    
026    import edu.upenn.crimson.*;
027    import java.awt.*;
028    import java.awt.event.*;
029    import javax.swing.*;
030    import java.net.URL;
031    import java.net.MalformedURLException;
032    
033    /**
034     * Base window for GUI.
035     *
036     * @author  Stephen Fisher
037     * @version $Id: Root.java,v 1.46 2007/10/09 20:14:16 fisher Exp $
038     */
039    
040    public class Root  {
041             private static RootFrame rootFrame = null;
042    
043             /**
044              * This will contain all error and warning messages.  This output
045              * panel can be disabled by setting the 'showMessages' flag to
046              * false.  When disabled, all messages printed will be displayed
047              * in the console.
048              */
049             public static JTextArea messages = new JTextArea(20, 90);
050    
051             /**
052              * This will be used to display the progress during importing and
053              * exporting of trees.
054              */
055             public static JProgressBar progressBar = new JProgressBar();
056    
057             /** 
058              * Flag whether to display messages in the 'messages' panel.  If
059              * false, then print the messages in the console.  If true, then
060              * messages will only go to the messages panel and not the console.
061              */
062             public static boolean showMessages = true;
063    
064             /**
065              * This will contain all commands that the GUI generates and sends
066              * to the console to be run.  The text in this panel can be copied
067              * and pasted into a python file to run directly in the console.
068              */
069             public static JTextArea history = new JTextArea(10, 90);
070    
071             public static JLabel statusBar;
072    
073             public static JFrame show() {
074                      // only allow one instance of rootFrame
075                      if (rootFrame == null) rootFrame = new RootFrame();
076                      rootFrame.setVisible(true);
077                      return rootFrame;
078             }
079                      
080             /** 
081              * This will add the String to the history and then run the
082              * command through the console.
083              */
084             public static void runCommand(String msg) {
085                      runCommand(msg, true);
086             }
087    
088             /** 
089              * This will add the String to the history and if 'toConsole',
090              * then run the command through the console.
091              */
092             public static void runCommand(String msg, boolean toConsole) {
093                      history.append(msg + "\n");
094                      if (toConsole) CrimsonMain.console.exec("print; " + msg);
095             }
096    
097             /** 
098              * This will turn on the progress bar. 
099              * @XXX For some reason this doesn't work.  I think 'exec()' is
100              * blocking the gui.
101              */
102             public static void startProgressBar(String msg) {
103                      progressBar.setString(msg);
104                      progressBar.setStringPainted(true);
105                      progressBar.setIndeterminate(true);
106    
107                      if (rootFrame != null) rootFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
108             }
109             
110             /** This will turn off the progress bar. */
111             public static void stopProgressBar() {
112                      progressBar.setString("");
113                      progressBar.setStringPainted(false);
114                      progressBar.setIndeterminate(false);
115    
116                      if (rootFrame != null) rootFrame.setCursor(null);
117             }
118             
119             public static boolean isVisible() { 
120                      if ((rootFrame != null) && (rootFrame.isVisible())) return true;
121                      else return false;
122             }
123    
124             private static class RootFrame extends JFrame implements ActionListener {
125                      // file menu
126                      private JMenuItem setDBType;
127                      private JMenuItem openDB;
128                      private JMenuItem closeDB;
129                      //              private JMenuItem reopenDB;
130                      private JMenuItem testDB;
131                      private JMenuItem quit;
132    
133                      // edit menu
134                      private JMenuItem copyHistory;
135                      private JMenuItem clearHistory;
136                      private JMenuItem selectHistory;
137                      private JCheckBoxMenuItem toggleShowMessages;
138                      private JMenuItem clearMessages;
139                      
140                      // tree menu
141                      private JMenuItem loadTree;
142                      private JMenuItem appendTree;
143                      private JMenuItem exportTree;
144                      private JMenuItem statsTree;
145                      private JMenuItem newickTree;
146                      private JMenuItem view3DTree;
147                      private JMenuItem refreshTree;
148                      private JMenuItem manageTree;
149                      private JMenuItem deleteTree;
150                      
151                      // model menu
152                      private JMenuItem newModel;
153                      private JMenuItem refreshModel;
154                      private JMenuItem viewModel;
155                      private JMenuItem manageModel;
156                      private JMenuItem deleteModel;
157                      
158                      // query menu
159                      private JMenuItem newQuery;
160                      private JMenuItem loadQuery;
161                      private JMenuItem loadAllQuery;
162                      private JMenuItem publishQuery;
163                      private JMenuItem importQuery;
164                      private JMenuItem exportQuery;
165                      private JMenuItem viewQuery;
166                      private JMenuItem manageQuery;
167                      /*              private JMenuItem runQuery; */
168                      private JMenuItem deleteQuery;
169    
170                      // help menu
171                      private JMenuItem helpTopics;
172                      private JMenuItem viewAPI;
173                      private JMenuItem about;
174                      
175                      private JTextArea messages = Root.messages;
176                      private JTextArea history = Root.history;
177                      private JLabel statusBar = Root.statusBar;
178                      private JProgressBar progressBar = Root.progressBar;
179                      
180                      public RootFrame() {
181                                    super("CRIMSON");
182                                    
183                                    // this will cause all warnings to be sent here instead of to
184                                    // the console
185                                    CrimsonUtils.guiMessages = messages;
186                                    
187                                    //                setDefaultCloseOperation(DISPOSE_ON_CLOSE);
188                                    setDefaultCloseOperation(EXIT_ON_CLOSE);
189                                    
190                                    //                addWindowListener(new WindowAdapter() {
191                                    //                                       public void windowClosing(WindowEvent e) { System.exit(0); }
192                                    //                              });
193                                    
194                                    /**** FILE MENU ****/
195                                    JMenu fileMenu = new JMenu("File");
196                                    fileMenu.setMnemonic(KeyEvent.VK_F);
197                                    
198                                    setDBType = addMenuItem(setDBType, fileMenu, "Set Database Type", KeyEvent.VK_S, new ImageIcon("icons/connect_m.png"));
199                                    openDB = addMenuItem(openDB, fileMenu, "Open Database...", KeyEvent.VK_O, new ImageIcon("icons/connect_m.png"));
200                                    //                              reopenDB = addMenuItem(reopenDB, fileMenu, "Re-open Database", KeyEvent.VK_R, new ImageIcon("icons/refresh_m.png"));
201                                    closeDB = addMenuItem(closeDB, fileMenu, "Close Database", KeyEvent.VK_C, new ImageIcon("icons/disconnect_m.png"));
202                                    fileMenu.addSeparator();
203                                    testDB = addMenuItem(testDB, fileMenu, "Test Database Connection", -1, new ImageIcon("icons/test_m.png"));
204                                    fileMenu.addSeparator();
205                                    quit = addMenuItem(quit, fileMenu, "Quit", -1, new ImageIcon("icons/quit_m.png"));
206                                    quit.setMnemonic(KeyEvent.VK_Q);
207                                    
208                                    /**** EDIT MENU ****/
209                                    JMenu editMenu = new JMenu("Edit");
210                                    editMenu.setMnemonic(KeyEvent.VK_E);
211                                    copyHistory = addMenuItem(copyHistory, editMenu, "Copy History", -1, new ImageIcon("icons/copy_m.png"));
212                                    copyHistory.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
213                                    clearHistory = addMenuItem(clearHistory, editMenu, "Clear History", -1, new ImageIcon("icons/clear_m.png"));
214                                    clearHistory.setMnemonic(KeyEvent.VK_C);
215                                    selectHistory = addMenuItem(selectHistory, editMenu, "Select All History", -1, new ImageIcon(""));
216                                    selectHistory.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.CTRL_MASK));
217                                    editMenu.addSeparator();
218                                    toggleShowMessages = new JCheckBoxMenuItem("Enable Message Window");
219                                    toggleShowMessages.addActionListener(this);
220                                    toggleShowMessages.setSelected(Root.showMessages);
221                                    editMenu.add(toggleShowMessages);
222                                    clearMessages = addMenuItem(clearMessages, editMenu, "Clear Messages", -1, new ImageIcon("icons/clear_m.png"));
223                                    
224                                    /**** TREE MENU ****/
225                                    JMenu treeMenu = new JMenu("Tree");
226                                    treeMenu.setMnemonic(KeyEvent.VK_T);
227                                    loadTree = addMenuItem(loadTree, treeMenu, "Load...", KeyEvent.VK_L, new ImageIcon("icons/load_m.png"));
228                                    appendTree = addMenuItem(appendTree, treeMenu, "Append...", -1, new ImageIcon("icons/append_m.png"));
229                                    appendTree.setMnemonic(KeyEvent.VK_A);
230                                    exportTree = addMenuItem(exportTree, treeMenu, "Export Tree...", -1, new ImageIcon("icons/export_m.png"));
231                                    exportTree.setMnemonic(KeyEvent.VK_S);
232                                    treeMenu.addSeparator();
233                                    statsTree = addMenuItem(statsTree, treeMenu, "Stats Tree...", -1, null);
234                                    statsTree.setMnemonic(KeyEvent.VK_V);
235                                    newickTree = addMenuItem(newickTree, treeMenu, "View Tree...", -1, null);
236                                    newickTree.setMnemonic(KeyEvent.VK_N);
237                                    if (CrimsonMain.WALRUS) {
238                                        view3DTree = addMenuItem(view3DTree, treeMenu, "3D View Tree...", -1, new ImageIcon("icons/view_m.png"));
239                                        view3DTree.setMnemonic(KeyEvent.VK_3);
240                                    }
241                                    treeMenu.addSeparator();
242                                    refreshTree = addMenuItem(refreshTree, treeMenu, "Refresh Tree List", -1, new ImageIcon("icons/refresh_m.png"));
243                                    refreshTree.setMnemonic(KeyEvent.VK_R);
244                                    manageTree = addMenuItem(manageTree, treeMenu, "Manage", -1, new ImageIcon("icons/browse_m.png"));
245                                    manageTree.setMnemonic(KeyEvent.VK_M);
246                                    treeMenu.addSeparator();
247                                    deleteTree = addMenuItem(deleteTree, treeMenu, "Delete", -1, new ImageIcon("icons/delete_m.png"));
248                                    deleteTree.setMnemonic(KeyEvent.VK_D);
249                                    
250                                    /**** MODEL MENU ****/
251                                    JMenu modelMenu = new JMenu("Model");
252                                    modelMenu.setMnemonic(KeyEvent.VK_M);
253                                    newModel = addMenuItem(newModel, modelMenu, "New...", -1, new ImageIcon("icons/new_m.png"));
254                                    newModel.setMnemonic(KeyEvent.VK_N);
255                                    modelMenu.addSeparator();
256                                    refreshModel = addMenuItem(refreshModel, modelMenu, "Refresh Model List", -1, new ImageIcon("icons/refresh_m.png"));
257                                    refreshModel.setMnemonic(KeyEvent.VK_R);
258                                    viewModel = addMenuItem(viewModel, modelMenu, "View", -1, new ImageIcon("icons/view_m.png"));
259                                    viewModel.setMnemonic(KeyEvent.VK_V);
260                                    manageModel = addMenuItem(manageModel, modelMenu, "Manage", -1, new ImageIcon("icons/browse_m.png"));
261                                    manageModel.setMnemonic(KeyEvent.VK_M);
262                                    modelMenu.addSeparator();
263                                    deleteModel = addMenuItem(deleteModel, modelMenu, "Delete", -1, new ImageIcon("icons/delete_m.png"));
264                                    deleteModel.setMnemonic(KeyEvent.VK_D);
265                                    
266                                    /**** QUERY MENU ****/
267                                    JMenu queryMenu = new JMenu("Query");
268                                    queryMenu.setMnemonic(KeyEvent.VK_Q);
269                                    newQuery = addMenuItem(newQuery, queryMenu, "New...", -1, new ImageIcon("icons/new_m.png"));
270                                    newQuery.setMnemonic(KeyEvent.VK_N);
271                                    loadQuery = addMenuItem(loadQuery, queryMenu, "Load From DB", -1, new ImageIcon("icons/load_m.png"));
272                                    loadQuery.setMnemonic(KeyEvent.VK_L);
273                                    loadAllQuery = addMenuItem(loadAllQuery, queryMenu, "Load All From DB", -1, new ImageIcon("icons/refresh_m.png"));
274                                    loadAllQuery.setMnemonic(KeyEvent.VK_A);
275                                    publishQuery = addMenuItem(publishQuery, queryMenu, "Publish To DB...", -1, new ImageIcon("icons/publish_m.png"));
276                                    publishQuery.setMnemonic(KeyEvent.VK_S);
277                                    queryMenu.addSeparator();
278                                    importQuery = addMenuItem(importQuery, queryMenu, "Import...", -1, new ImageIcon("icons/import_m.png"));
279                                    importQuery.setMnemonic(KeyEvent.VK_L);
280                                    exportQuery = addMenuItem(exportQuery, queryMenu, "Export...", -1, new ImageIcon("icons/export_m.png"));
281                                    exportQuery.setMnemonic(KeyEvent.VK_E);
282                                    queryMenu.addSeparator();
283                                    viewQuery = addMenuItem(viewQuery, queryMenu, "View", -1, new ImageIcon("icons/view_m.png"));
284                                    viewQuery.setMnemonic(KeyEvent.VK_V);
285                                    manageQuery = addMenuItem(manageQuery, queryMenu, "Manage", -1, new ImageIcon("icons/browse_m.png"));
286                                    manageQuery.setMnemonic(KeyEvent.VK_M);
287                                    /*
288                                    runQuery = addMenuItem(runQuery, queryMenu, "Run", KeyEvent.VK_G, new ImageIcon("icons/search_m.png"));
289                                    */
290                                    queryMenu.addSeparator();
291                                    deleteQuery = addMenuItem(deleteQuery, queryMenu, "Delete", -1, new ImageIcon("icons/delete_m.png"));
292                                    deleteQuery.setMnemonic(KeyEvent.VK_D);
293    
294                                    /**** HELP MENU ****/
295                                    JMenu helpMenu = new JMenu("Help");
296                                    helpMenu.setMnemonic(KeyEvent.VK_H);
297                                    helpTopics = addMenuItem(helpTopics, helpMenu, "Script Commands", -1, new ImageIcon("icons/help_m.png"));
298                                    viewAPI = addMenuItem(viewAPI, helpMenu, "API Documentation", -1, new ImageIcon("icons/info_m.png"));
299                                    helpMenu.addSeparator();
300                                    about = addMenuItem(about, helpMenu, "About", -1, new ImageIcon("icons/info_m.png"));
301                                    
302                                    // put menu together and add to root pane
303                                    JMenuBar menuBar = new JMenuBar();
304                                    menuBar.add(fileMenu);
305                                    menuBar.add(editMenu);
306                                    menuBar.add(treeMenu);
307                                    menuBar.add(modelMenu);
308                                    menuBar.add(queryMenu);
309                                    // push help menu to the right side
310                                    menuBar.add(Box.createHorizontalGlue());
311                                    menuBar.add(helpMenu);
312                                    getRootPane().setJMenuBar(menuBar);
313                                    
314                                    // add messages as a scrolled text area.
315                                    messages.setEditable(false);
316                                    JScrollPane messagesSP = new JScrollPane(messages);
317                                    messagesSP.setBorder(BorderFactory.createLoweredBevelBorder());
318                                    JPanel messagesP = new JPanel(false);
319                                    messagesP.setLayout(new BorderLayout());
320                                    messagesP.add(new JLabel(" Messages:"), BorderLayout.NORTH);
321                                    messagesP.add(messagesSP, BorderLayout.CENTER);
322                                    
323                                    // add history as a scrolled text area.
324                                    history.setEditable(false);
325                                    JScrollPane historySP = new JScrollPane(history);
326                                    historySP.setBorder(BorderFactory.createLoweredBevelBorder());
327                                    JPanel historyP = new JPanel(false);
328                                    historyP.setLayout(new BorderLayout());
329                                    historyP.add(new JLabel(" History:"), BorderLayout.NORTH);
330                                    historyP.add(historySP, BorderLayout.CENTER);
331                                    
332                                    // setup main JPanel for window
333                                    JPanel mainPane = new JPanel(false);
334                                    mainPane.setLayout(new BorderLayout());
335                                    JSplitPane mainSplitP = new JSplitPane(JSplitPane.VERTICAL_SPLIT, 
336                                                                                                                                            messagesP, historyP);
337                                    mainSplitP.setOneTouchExpandable(true);
338                                    // provide minimum sizes for the two components in the split pane
339                                    messagesP.setMinimumSize(new Dimension(700, 400));
340                                    historyP.setMinimumSize(new Dimension(700, 200));
341                                    // provide a preferred size for the split pane
342                                    mainSplitP.setPreferredSize(new Dimension(700, 600));
343                                    // resize upper panel by 50% more that lower when the window
344                                    // is expended
345                                    mainSplitP.setResizeWeight(0.75);
346                                    mainPane.add(mainSplitP, BorderLayout.CENTER);
347                                    
348                                    // add status bar at bottom of window.  Use a 'spacer' so that
349                                    // anything added to statusBar later won't be crammed into the
350                                    // left margin.
351                                    statusBar = new JLabel(" ");
352                                    JPanel statusBarP = new JPanel(false);
353                                    statusBarP.setBorder(BorderFactory.createLoweredBevelBorder());
354                                    statusBarP.setLayout(new BorderLayout());
355                                    statusBarP.add(new JLabel(" "), BorderLayout.WEST);
356                                    statusBarP.add(statusBar, BorderLayout.CENTER);
357                                    JPanel progressBarP = new JPanel(false);
358                                    progressBarP.setLayout(new BorderLayout());
359                                    progressBarP.add(new JLabel("Progress: "), BorderLayout.WEST);
360                                    progressBarP.add(progressBar, BorderLayout.CENTER);
361                                    statusBarP.add(progressBarP, BorderLayout.EAST);
362                                    mainPane.add(statusBarP, BorderLayout.SOUTH);
363                                    getContentPane().add(mainPane);
364                                    pack();
365                                    
366                                    // set the default window size
367                                    //                setSize(getSize().width + 300, getSize().height + 200);
368                                    
369                                    // display the window
370                                    setVisible(true);
371                      }
372                      
373                      //--------------------------------------------------------------------------
374                      // Miscellaneous Methods
375                      
376                      /** Handle menu items. */
377                      public void actionPerformed(ActionEvent e) {
378                                    //                history.append("Event source: " + ((JMenuItem) e.getSource()).getText() + "\n");
379                                    
380                                    // erase status bar whenever there is an ActionEvent.
381                                    statusBar.setText("");
382                                    
383                                    if (e.getSource() instanceof JMenuItem) {
384                                             JMenuItem source = (JMenuItem) e.getSource();
385                                             
386                                             if (source == setDBType) {
387                                                      GUIUtils.setDBType();
388                                             } else if (source == openDB) {
389                                                      GUIUtils.openDatabase();
390                                             } else if (source == closeDB) {
391                                                      Root.runCommand("closeDatabase()");
392                                             } else if (source == testDB) {
393                                                      Root.runCommand("testConnection()");
394                                             } else if (source == quit) {
395                                                      System.exit(0);
396    
397                                             } else if (source == copyHistory) {
398                                                      history.copy();
399                                                      // exit here so the 'history' routines below don't undo this
400                                                      return;
401                                             } else if (source == clearHistory) {
402                                                      history.selectAll();
403                                                      history.replaceSelection("");
404                                                      return;
405                                             } else if (source == selectHistory) {
406                                                      history.selectAll();
407                                                      // exit here so the 'history' routines below don't undo this
408                                                      return;
409                                             } else if (source == toggleShowMessages) {
410                                                      Root.showMessages = toggleShowMessages.isSelected();
411                                                      
412                                                      if (Root.showMessages) CrimsonUtils.guiMessages = messages;
413                                                      else CrimsonUtils.guiMessages = null;
414                                                      return;
415                                             } else if (source == clearMessages) {
416                                                      messages.selectAll();
417                                                      messages.replaceSelection("");
418                                                      return;
419    
420                                             } else if (source == loadTree) {
421                                                      TreeUtils.loadTree();
422                                             } else if (source == appendTree) {
423                                                      TreeUtils.appendTree("");
424                                             } else if (source == exportTree) {
425                                                      TreeUtils.exportTree("");
426                                             } else if (source == statsTree) {
427                                                      TreeUtils.statsTree("");
428                                             } else if (source == newickTree) {
429                                                      TreeUtils.newickTree("");
430                                             } else if ((source == view3DTree) && CrimsonMain.WALRUS) {
431                                                      TreeUtils.view3DTree("");
432                                             } else if (source == refreshTree) {
433                                                      Root.runCommand("loadAllTrees()");
434                                             } else if (source == manageTree) {
435                                                      Root.runCommand("treeManager()");
436                                             } else if (source == deleteTree) {
437                                                      TreeUtils.deleteTree("");
438    
439                                             } else if (source == newModel) {
440                                                      Root.runCommand("newModel()");
441                                             } else if (source == refreshTree) {
442                                                      Root.runCommand("loadAllModels()");
443                                             } else if (source == viewModel) {
444                                                      Root.runCommand("viewModel()");
445                                             } else if (source == manageModel) {
446                                                      Root.runCommand("modelManager()");
447                                             } else if (source == deleteModel) {
448                                                      ModelUtils.deleteModel("");
449    
450                                             } else if (source == newQuery) {
451                                                      QueryUtils.newQuery();
452                                             } else if (source == loadQuery) {
453                                                      QueryUtils.loadQuery();
454                                             } else if (source == loadAllQuery) {
455                                                      Root.runCommand("loadAllQueries()");
456                                             } else if (source == publishQuery) {
457                                                      QueryUtils.publishQuery("");
458                                             } else if (source == importQuery) {
459                                                      QueryUtils.importQuery();
460                                             } else if (source == exportQuery) {
461                                                      QueryUtils.exportQuery("");
462                                             } else if (source == viewQuery) {
463                                                      Root.runCommand("viewQuery()");
464                                             } else if (source == manageQuery) {
465                                                      Root.runCommand("queryManager()");
466                                             } else if (source == deleteQuery) {
467                                                      QueryUtils.deleteQuery("");
468    
469                                             } else if (source == helpTopics) {
470                                                      Root.runCommand("help()");
471                                             } else if (source == viewAPI) {
472                                                      try {
473                                                                    URL url = new URL("file:documentation/index.html");
474                                                                    new ViewHTML(url);
475                                                                    Root.runCommand("viewAPI()", false);
476                                                      } catch (MalformedURLException urlException) {
477                                                                    CrimsonUtils.printMsg("API documentation not found.", CrimsonUtils.ERROR);
478                                                      }
479                                             } else if (source == about) {
480                                                      Root.runCommand("about()");
481                                             } else {
482                                                      statusBar.setText("Menu item not yet available.");
483                                                      CrimsonUtils.printMsg("Menu item not yet available.");
484                                             }
485                                             
486                                             history.setCaretPosition(history.getDocument().getLength());
487                                    }
488                      }
489                      
490                      /** Add specified item to specified menu. */
491                      private JMenuItem addMenuItem(JMenuItem item, JMenu menu, String label, int mnemonic, ImageIcon image) {
492                                    JMenuItem out;
493                                    if (image == null) {
494                                             out = new JMenuItem(label);
495                                    } else {
496                                             out = new JMenuItem(label, image);
497                                    }
498                                    if (mnemonic != -1) { 
499                                             out.setMnemonic(mnemonic);
500                                             out.setAccelerator(KeyStroke.getKeyStroke(mnemonic, ActionEvent.ALT_MASK));
501                                    }
502                                    out.addActionListener(this);
503                                    menu.add(out);
504                                    return out;
505                      }
506                      
507             } // Root.java
508    }