home *** CD-ROM | disk | FTP | other *** search
/ Power GUI Programming with VisualAge C++ / powergui.iso / trialva / ibmcppw / samples / ioc / lancelot / ltimec.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  1996-02-22  |  21.9 KB  |  594 lines

  1. /******************************************************************************
  2. * .FILE:         ltimec.cpp                                                   *
  3. *                                                                             *
  4. * .DESCRIPTION:  Lancelot Sample Program:              Class Implementation   *
  5. *                                                                             *
  6. * .CLASSES:      TimeCardPage                                                 *
  7. *                WeekEndingDate                                               *
  8. *                                                                             *
  9. * .COPYRIGHT:                                                                 *
  10. *                                                                             *
  11. * .DISCLAIMER:                                                                *
  12. *                                                                             *
  13. * .NOTE: WE RECOMMEND USING A FIXED SPACE FONT TO LOOK AT THE SOURCE          *
  14. *                                                                             *
  15. ******************************************************************************/
  16.  
  17. #ifndef _IBASE_                         //Make sure ibase.hpp is included
  18.   #include <ibase.hpp>                  //  since that is where IC_<environ>
  19. #endif                                  //  is defined.
  20. #include <stdio.h>
  21. #include <iexcbase.hpp>
  22. #include <ireslib.hpp>
  23. #include <ihelp.hpp>
  24. #include <iscroll.hpp>
  25. #include "lancelot.h"
  26. #include "ltimec.hpp"
  27.  
  28.  
  29. /*************************************************************/
  30. /* CLASS TimeCardPage - constructor                          */
  31. /*************************************************************/
  32.  
  33. TimeCardPage :: TimeCardPage( IWindow*        pParent,
  34.                         ProjectPage*   pProjPage,
  35.                         TasksPage*     pTasksPage,
  36.                         const IString&  aKey)
  37.               : IMultiCellCanvas( ID_TIMECARD_PAGE, pParent, pParent),
  38.                 pageButtons(ID_TIMECARD_PAGE_BUTTONS, this, this, false),
  39.                 pageScrollButtons( ID_TIMECARD_PAGE_SCROLL_BUTTONS,
  40.                         this, this ),
  41.                 tcVp(ID_TIMECARD_VP, this, this,
  42.                         IRectangle(),
  43.                         (IViewPort::defaultStyle() &
  44.                         ~IViewPort::asNeededVerticalScrollBar ) |
  45.                         IViewPort::alwaysVerticalScrollBar),
  46.                 totalHoursText(ID_NO_ITEM, this , this ),
  47.                 totalBillableHoursText (ID_NO_ITEM, this, this ),
  48.                 pProjectPage(pProjPage),
  49.                 pTasksPage(pTasksPage),
  50.                 weekEndingDate(IDate()),
  51.                 pTimeCardData( new
  52.                         LTimeCardData( aKey, weekEndingDate.date() ) ),
  53.                 timeCard(&tcVp,
  54.                         pProjPage,
  55.                         pTasksPage,
  56.                         pTimeCardData),
  57. #ifndef IC_MOTIF
  58.                 pieChart( ID_TIMECARD_PIECHART,
  59.                         this, this, IRectangle(), pTimeCardData ),
  60. #endif
  61.                 Key(aKey),
  62.                 thePageSettings( IApplication::current().userResourceLibrary().loadString(
  63.                         STR_TCD_TIMECARD_TAB), NULL,
  64.                         INotebook::PageSettings::autoPageSize
  65.                         | INotebook::PageSettings::majorTab )
  66. {
  67.   pageScrollButtons.setDisplayText(pTimeCardData->theWeek.asString());
  68.  
  69.   totalHoursText.setAlignment(IStaticText::topCenter);
  70.   totalBillableHoursText.setAlignment(  IStaticText::topCenter);
  71.  
  72.   setDisplayText(IString(timeCard.getTotalHours()),
  73.                  IString(timeCard.getTotalBillable()));
  74.  
  75.   setCells();
  76.   handleIt();
  77. };
  78.  
  79. /*************************************************************/
  80. /* CLASS TimeCardPage - ~TimeCardPage - destructor           */
  81. /*************************************************************/
  82.  
  83. TimeCardPage :: ~TimeCardPage()
  84. {
  85.    ICommandHandler::stopHandlingEventsFor( &pageButtons );
  86.    ICommandHandler::stopHandlingEventsFor( &pageScrollButtons );
  87.    ISelectHandler::stopHandlingEventsFor( &pageButtons );
  88.    delete( pTimeCardData );
  89. };
  90.  
  91.  
  92. /****************************************************************************
  93. * CLASS TimeCardPage :: handleIt()                                          *
  94. ****************************************************************************/
  95. TimeCardPage&  TimeCardPage :: handleIt()
  96. {
  97.    ICommandHandler::handleEventsFor( &pageButtons );
  98.    ICommandHandler::handleEventsFor( &pageScrollButtons );
  99.    ISelectHandler::handleEventsFor( &pageButtons );
  100.    return *this;
  101. }
  102.  
  103. /****************************************************************************
  104. * CLASS TimeCardPage :: setCells() - set up the multi-cell canvas cells     *
  105. ****************************************************************************/
  106. TimeCardPage& TimeCardPage :: setCells()
  107. {
  108.    addToCell(&pageScrollButtons,       2,  1);
  109.  
  110.    addToCell(&tcVp,                    1,  4,  3,  1);
  111.  
  112. #ifndef IC_MOTIF
  113.    addToCell(&pieChart,                4,  4);
  114.    setColumnWidth( 4, 0, true );
  115.    setRowHeight( 4, 0, true );
  116. #endif
  117.  
  118.    addToCell(&totalHoursText,          2,  6);
  119.    addToCell(&totalBillableHoursText,  2,  7);
  120.  
  121.    addToCell(&pageButtons,             2,  8);
  122.  
  123. /*---------------------------------------------------------------------------
  124. | Get the minimum size for the view-window portion of the viewport.         |
  125. | Since the viewport will have a vertical scrollbar, we need to add it's    |
  126. |  width to the view-window's width.                                        |
  127. | Since the timecard's height is too much to fit on the notebook page,      |
  128. |  reduce the height by 1/4.                                                |
  129. | Set the minimum size of the viewport.                                     |
  130. ---------------------------------------------------------------------------*/
  131.    ISize tcVpMinSize( windowWithHandle( tcVp.viewWindow() )->minimumSize() );
  132.    tcVpMinSize += ISize( tcVp.verticalScrollBar()->size().width(), 0 );
  133.    tcVpMinSize.setHeight( tcVpMinSize.height()/4 );
  134.    tcVp.setMinimumSize( tcVpMinSize );
  135.  
  136.    return *this;
  137. };
  138.  
  139. /**************************************************************************/
  140. /* CLASS TimeCardPage :: setDisplayText()                                 */
  141. /**************************************************************************/
  142.  
  143. TimeCardPage& TimeCardPage :: setDisplayText(const char* totHrs,
  144.                                              const char* totBillHrs)
  145. {
  146.    totalHoursText.setText(         STR_TCD_TOTAL_HOURS_TEXT );
  147.    totalBillableHoursText.setText( STR_TCD_TOTAL_BILLABLE_HOURS_TEXT);
  148.  
  149.    totalHoursText.setText( totalHoursText.text() + totHrs);
  150.    totalBillableHoursText.setText( totalBillableHoursText.text() + totBillHrs);
  151.  
  152.    return *this;
  153. };
  154.  
  155.  
  156.  
  157.  
  158. TimeCard :: TimeCard(IWindow* pParent
  159.                    , ProjectPage* pProjPage
  160.                    , TasksPage  * pTasksPage
  161.                    , LTimeCardData  * rLTimeCardData)
  162.           : IMultiCellCanvas(ID_TIMECARD_PAGE,
  163.                                pParent, pParent,
  164.                                IRectangle() )
  165.             ,dateHeader(ID_TIMECARD_DATE_HEADER, this, this)
  166.             ,projectHeader(ID_TIMECARD_PROJECT_HEADER, this, this)
  167.             ,taskHeader(ID_TIMECARD_TASK_HEADER, this, this)
  168.             ,hourHeader(ID_TIMECARD_HOUR_HEADER, this, this)
  169. #ifdef IC_MOTIF
  170.             ,dateHandler( TEST_DATE )
  171. #endif
  172.             ,pProjectPage(pProjPage)
  173.             ,pTasksPage(pTasksPage)
  174.             ,totalHours(0)
  175.             ,totalBillable(0)
  176. {
  177.   dateHeader.setText(IApplication::current().userResourceLibrary()
  178.                      .loadString(STR_TCD_DATE_HEADER));
  179.   projectHeader.setText(IApplication::current().userResourceLibrary()
  180.                      .loadString(STR_TCD_PROJECT_HEADER));
  181.   taskHeader.setText(IApplication::current().userResourceLibrary()
  182.                      .loadString(STR_TCD_TASK_HEADER));
  183.   hourHeader.setText(IApplication::current().userResourceLibrary()
  184.                      .loadString(STR_TCD_HOUR_HEADER));
  185.  
  186.   for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  187.   {
  188. /*---------------------------------------------------------------------------
  189. | Create an entryfield for entering the date.                               |
  190. | Set the character limit.                                                  |
  191. | Since the default minimumSize is too wide,                                |
  192. | - get the minimumSize                                                     |
  193. | - get the current font's average character width                          |
  194. | - multiply the font width by the limit of the entryfield and add a buffer |
  195. |   to avoid the entryfield from scrolling                                  |
  196. | Set the new minimum size.                                                 |
  197. | Auto delete the object when no longer available.                          |
  198. | Create a de the object when no longer available.                          |
  199. ---------------------------------------------------------------------------*/
  200.     date[index] = new IEntryField(ID_TCD_DATE0_EF, this, this,
  201.                                   IRectangle(),
  202.                                   IEntryField::classDefaultStyle |
  203.                                   IControl::tabStop);
  204.     date[index]->setLimit(  DISPLAY_LIMIT_DATE  );
  205.     ISize dateMinSize( date[index]->minimumSize() );
  206.     dateMinSize.setWidth( font().avgCharWidth() * ( DISPLAY_LIMIT_DATE + 2 ) );
  207.     date[index]->setMinimumSize( dateMinSize );
  208.     date[index]->setAutoDeleteObject();
  209.  
  210. /*---------------------------------------------------------------------------
  211. | Create a downdownlist combobox for the project.                           |
  212. | Set the character limit.                                                  |
  213. | Auto delete the object when no longer available.                          |
  214. ---------------------------------------------------------------------------*/
  215.     proj[index] = new IComboBox(ID_TCD_PROJ0_CBX, this, this,
  216.                                IRectangle(),
  217.                                IComboBox::defaultStyle() &
  218.                                ~IComboBox::simpleType     |
  219.                                IComboBox::readOnlyDropDownType |
  220.                                IControl::tabStop);
  221.     proj[index]->setLimit(  DISPLAY_LIMIT       );
  222.     proj[index]->setAutoDeleteObject();
  223.  
  224. /*---------------------------------------------------------------------------
  225. | Create a downdownlist combobox for the task.                              |
  226. | Set the character limit.                                                  |
  227. | Auto delete the object when no longer available.                          |
  228. ---------------------------------------------------------------------------*/
  229.     task[index] = new IComboBox(ID_TCD_TASK0_CBX, this, this,
  230.                                IRectangle(),
  231.                                IComboBox::defaultStyle() &
  232.                                ~IComboBox::simpleType     |
  233.                                IComboBox::readOnlyDropDownType |
  234.                                IControl::tabStop);
  235.     task[index]->setLimit(  DISPLAY_LIMIT       );
  236.     task[index]->setAutoDeleteObject();
  237.  
  238. /*---------------------------------------------------------------------------
  239. | Create a spinbutton for entering the hours.                               |
  240. | Set the character limit.                                                  |
  241. | Since the default minimumSize is too wide,                                |
  242. | - get the minimumSize                                                     |
  243. | - get the current font's average character width                          |
  244. | - multiply the font width by the limit of the entryfield and add a buffer |
  245. |   to avoid the entryfield from scrolling and for the spinbutton's arrows  |
  246. | Set the new minimum size                                                  |
  247. | Auto delete the object when no longer available.                          |
  248. ---------------------------------------------------------------------------*/
  249.     hours[index] = new INumericSpinButton(ID_TCD_HOURS0_EF, this, this,
  250.                                IRectangle(),
  251.                                INumericSpinButton::defaultStyle() |
  252.                                IControl::tabStop);
  253.     hours[index]->setLimit( DISPLAY_LIMIT_HOURS );
  254.     ISize hoursMinSize( hours[index]->minimumSize() );
  255.     hoursMinSize.setWidth( font().avgCharWidth() * ( DISPLAY_LIMIT_HOURS + 2 + 3 ) );
  256.     hours[index]->setMinimumSize( hoursMinSize );
  257.     hours[index]->setAutoDeleteObject();
  258.   }
  259.  
  260.   fillComboBox();
  261.   fillVp(rLTimeCardData);
  262.   fillHours(rLTimeCardData->employeeNumber());
  263.   handleIt();
  264.   setCells();
  265. };
  266.  
  267.  
  268. /*************************************************************/
  269. /* CLASS TimeCard - ~TimeCardPage - destructor           */
  270. /*************************************************************/
  271.  
  272. TimeCard :: ~TimeCard()
  273. {
  274. };
  275.  
  276. /*************************************************************/
  277. /* CLASS TimeCard - handleIt()                               */
  278. /*************************************************************/
  279.  
  280. TimeCard& TimeCard :: handleIt()
  281. {
  282.  
  283. #ifdef IC_MOTIF
  284.   for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  285.   {
  286.     dateHandler.handleEventsFor( date[index] );
  287.   }
  288. #endif
  289.  
  290.   return *this;
  291. };
  292.  
  293.  
  294. TimeCard& TimeCard :: fillHours(const IString& key)
  295. {
  296.   LTaskData taskD(key);
  297.   int billable = 0;
  298.   int hrs=0;
  299.  
  300.   for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  301.   {
  302.     if (hours[index]->isValid())
  303.       hrs += hours[index]->value();
  304.     if (taskD.isInAndBillable(key, task[index]->text()))
  305.       billable += hours[index]->value();
  306.   }
  307.  
  308.   totalHours = hrs;
  309.   totalBillable = billable;
  310.   return *this;
  311. };
  312.  
  313. /*************************************************************/
  314. /* CLASS TimeCard - fillVp                                   */
  315. /*************************************************************/
  316.  
  317. TimeCard& TimeCard :: fillVp(LTimeCardData * tcp)
  318. {
  319.   for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  320.   {
  321.     date[index]->setText(   tcp->tcard[index].date()          );
  322.     proj[index]->setText(   tcp->tcard[index].projectName()   );
  323.     task[index]->setText(   tcp->tcard[index].taskName()      );
  324.     hours[index]->setValue( tcp->tcard[index].hours().asInt() );
  325.   }
  326.  
  327.   return *this;
  328. };
  329.  
  330.  
  331. /**************************************************************************/
  332. /* CLASS TimeCard :: setCells() - set up the multi-cell canvas cells   */
  333. /**************************************************************************/
  334.  
  335. TimeCard& TimeCard :: setCells()
  336. {
  337.   unsigned short theRow;
  338.  
  339.   addToCell(&dateHeader,     1,  1);
  340.   addToCell(&projectHeader,  3,  1);
  341.   addToCell(&taskHeader,     5,  1);
  342.   addToCell(&hourHeader,     7,  1);
  343.  
  344.   for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  345.   {
  346.      theRow = (index+1) * 2 + 1;
  347.      addToCell(date[index],  1, theRow);
  348.      addToCell(proj[index],  3, theRow);
  349.      addToCell(task[index],  5, theRow);
  350.      addToCell(hours[index], 7, theRow);
  351.   }
  352.  
  353.    return *this;
  354. };
  355.  
  356.  
  357. /**************************************************************************/
  358. /* CLASS TimeCard :: fillListBox() - set up the drop down list boxes  */
  359. /**************************************************************************/
  360.  
  361. TimeCard& TimeCard :: fillComboBox()
  362. {
  363.   // make sure pointers are ok
  364.   IString
  365.     theProj,
  366.     theDesc,
  367.     theMgr,
  368.     theAct,
  369.     theTask,
  370.     theBill;
  371.  
  372.   LProjectData::Rule theRule=LProjectData::na;
  373.   LTaskData::Rule theRuleT=LTaskData::na;
  374.  
  375.   mtComboBoxes();
  376.  
  377.   pProjectPage->setProjectData();
  378.   // get project data and fill up drop down
  379.   if (pProjectPage->projData().setFirst())
  380.  
  381.      while (pProjectPage->projData().getItem(theProj,
  382.                                               theDesc,
  383.                                               theMgr,
  384.                                               theAct,
  385.                                               theRule)) {
  386.        for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  387.        {
  388.          proj[index]->addAsLast(theProj);
  389.        }
  390.          pProjectPage->projData().getNext();
  391.      }
  392.  
  393.  
  394.  
  395.   pTasksPage->setTasksData();
  396.   // get task data and fill up drop down
  397.   if (pTasksPage->getTaskData().setFirst()) {
  398.  
  399.      while (pTasksPage->getTaskData().getItem(theTask,
  400.                                          theDesc,
  401.                                          theBill,
  402.                                          theRuleT))
  403.      {
  404.        for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  405.        {
  406.          task[index]->addAsLast(theTask);
  407.        }
  408.  
  409.          pTasksPage->getTaskData().getNext();
  410.      }
  411.   }
  412.  
  413.    for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  414.    {
  415.      hours[index]->setRange(IRange(0, 24));
  416.    }
  417.  
  418.   return *this;
  419. };
  420.  
  421.  
  422. TimeCard& TimeCard :: mtComboBoxes() {
  423.   for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  424.   {
  425.     proj[index]->removeAll();
  426.     task[index]->removeAll();
  427.   }
  428.   return *this;
  429. };
  430.  
  431. // need to check date is in range
  432.  
  433. IBase :: Boolean TimeCardPage :: verifyAndSave( IString& theString
  434.                                                ,IString& theEntry
  435.                                                ,const IString theName )
  436. {
  437.     int ix;
  438.     long hrs;
  439.  
  440.   // verify the data
  441.  
  442.      if ((theName.length() == 0) && (Key.length() == 0 ))
  443.         return true ;
  444.  
  445.      // take what is in the bag and write it to the database
  446.      // data is ok, now save it.
  447.  
  448.      IString a,b,c,d ;
  449.      int totalHours=0;
  450.  
  451.      // get the data from the ui fields and save in the data area
  452.  
  453.      Boolean incompleteMsg( false );
  454.      unsigned short saveIndex( 0 );
  455.  
  456.      for ( unsigned short index=0; index < ID_TIMECARD_ENTRIES; index++ )
  457.      {
  458.        a = timeCard.date[index]->text();
  459.        b = timeCard.proj[index]->text();
  460.        c = timeCard.task[index]->text();
  461.        hrs = timeCard.hours[index]->value();
  462.  
  463.        pTimeCardData->tcard[index].setDate( "" );
  464.        pTimeCardData->tcard[index].setProjectName( "" );
  465.        pTimeCardData->tcard[index].setTaskName( "" );
  466.        pTimeCardData->tcard[index].setHours( 0 );
  467.  
  468.        if ((0 != a.length() ) &&
  469.            (0 != b.length() ) &&
  470.            (0 != c.length() ))
  471.        {
  472.           d = IString(hrs) ;
  473.           totalHours += hrs ;
  474.  
  475.           pTimeCardData->tcard[saveIndex].setDate(        a);
  476.           pTimeCardData->tcard[saveIndex].setProjectName( b);
  477.           pTimeCardData->tcard[saveIndex].setTaskName(    c);
  478.           pTimeCardData->tcard[saveIndex].setHours(       d);
  479.           saveIndex++;
  480.        }
  481.        else if ( ( !incompleteMsg ) &&
  482.                  ( 0 != a.length() ) ||
  483.                  ( 0 != b.length() ) ||
  484.                  ( 0 != c.length() ) )
  485.        {
  486.          IMessageBox msgBox( this );
  487.          msgBox.show( STR_TIMECARD_NOSAVE
  488.                       ,IMessageBox::warningIcon |
  489.                       IMessageBox::okButton );
  490.          incompleteMsg = true;
  491.          continue;
  492.        }
  493.      } /* endfor */
  494.  
  495.      IString k;
  496.  
  497.     if (theName.length()>0)
  498.        k = theName;
  499.     else
  500.        k = Key ;
  501.  
  502.      // concat the date
  503.      k+= pTimeCardData->theWeek.asString();
  504.  
  505.      pTimeCardData->save(k);
  506.  
  507.      return (true);
  508. };
  509.  
  510.  
  511. IBase::Boolean TimeCardPage :: command(ICommandEvent &cmdEvent)
  512. {
  513.   Boolean rc = false;
  514.   LTimeCardData *pTCD;
  515.   IString badString, badControl;
  516.  
  517.   switch (cmdEvent.commandId()) {       //Get command id
  518.  
  519.     case ID_BUTTON_UNDO:             //Get the original data back
  520.       delete(pTimeCardData);
  521.       pTimeCardData =  new LTimeCardData( Key, weekEndingDate.date() );
  522.       timeCard.fillVp(pTimeCardData);
  523.       timeCard.fillHours(pTimeCardData->employeeNumber());
  524.       pageScrollButtons.setDisplayText(pTimeCardData->theWeek.asString());
  525.       rc = true;
  526.       break;
  527.  
  528.    case   ID_BUTTON_NEXT:
  529.       verifyAndSave( badString, badControl, Key);
  530.       delete(pTimeCardData);
  531.       pTimeCardData =  new LTimeCardData( Key,
  532.                        weekEndingDate.nextDate(WeekEndingDate::next) );
  533.       timeCard.fillVp(pTimeCardData);
  534.       timeCard.fillHours(pTimeCardData->employeeNumber());
  535. #ifndef IC_MOTIF
  536.       pieChart.refreshData( pTimeCardData );
  537.       pieChart.drawPie();
  538. #endif
  539.       pageScrollButtons.setDisplayText(pTimeCardData->theWeek.asString());
  540.       setDisplayText(IString(timeCard.getTotalHours()),
  541.                      IString(timeCard.getTotalBillable()));
  542.       rc = true;
  543.       break;
  544.  
  545.    case   ID_BUTTON_PREV:
  546.       verifyAndSave( badString, badControl, Key);
  547.       delete(pTimeCardData);
  548.       pTimeCardData =  new LTimeCardData( Key,
  549.                        weekEndingDate.nextDate(WeekEndingDate::previous) );
  550.  
  551.       timeCard.fillVp(pTimeCardData);
  552.       timeCard.fillHours(pTimeCardData->employeeNumber());
  553. #ifndef IC_MOTIF
  554.       pieChart.refreshData( pTimeCardData );
  555.       pieChart.drawPie();
  556. #endif
  557.       pageScrollButtons.setDisplayText(pTimeCardData->theWeek.asString());
  558.       setDisplayText(IString(timeCard.getTotalHours()),
  559.                      IString(timeCard.getTotalBillable()));
  560.       rc = true;
  561.       break;
  562.  
  563.     case ID_BUTTON_HELP:
  564.       // Product Information processing
  565.       IHelpWindow::helpWindow( this )->show(
  566.            IResourceId( ID_TIMECARD_PAGE ) );
  567.       rc = true;
  568.  
  569.   } /* end switch */
  570.  
  571.   return rc;
  572. };
  573.  
  574.  
  575. WeekEndingDate :: WeekEndingDate(IDate aDate)
  576. {
  577.    // given a date, set it to friday (it should be so easy!)
  578.    while (aDate.dayOfWeek() != IDate::Friday) {
  579.       aDate+=1;
  580.    }
  581.  
  582.    theDate = aDate;
  583. };
  584.  
  585. WeekEndingDate :: ~WeekEndingDate()
  586. {};
  587.  
  588. const IDate WeekEndingDate :: nextDate(WeekScroll scroll)
  589. {
  590.     (scroll == WeekEndingDate::next) ? theDate += 7 : theDate -= 7;
  591.     return theDate;
  592. };
  593.  
  594.