cgma
TtyProgressTool.cpp
Go to the documentation of this file.
00001 //-------------------------------------------------------------------------
00002 // Filename      : TtyProgressTool.cpp
00003 //
00004 // Purpose       : Progress bar for use in text terminals (ttys)
00005 //
00006 // Special Notes : 
00007 //
00008 // Creator       : Jason Kraftcheck
00009 //
00010 // Creation Date : 06/16/03
00011 //-------------------------------------------------------------------------
00012 #ifndef _WIN32
00013 #  include <unistd.h>
00014 #endif
00015 #include <cstring>
00016 #include "AppUtil.hpp"
00017 #include "TtyProgressTool.hpp"
00018 #include "CubitUtil.hpp"
00019 
00020   // Enable/disable display of progress bar and/or numeric percent.
00021 const bool DISPLAY_PROGRESS_BAR = true;
00022 const bool DISPLAY_NUM_PERCENT  = true;
00023 
00024   // Min size for progress bar.  Will not be shown if available
00025   // space is less than this value.
00026 const int MIN_PROGRESS_BAR_WIDTH = 10;
00027   // Maximum size for progress bar.  If 0, limited only by available
00028   // space.
00029 const int MAX_PROGRESS_BAR_WIDTH =  0;
00030 
00031   // Characters composing progress bar.
00032 const char PROGRESS_BAR_START   = '|';
00033 const char PROGRESS_BAR_END     = '|';
00034 const char PROGRESS_BAR_FILLED  = '=';
00035 const char PROGRESS_BAR_CURRENT = '>';
00036 const char PROGRESS_BAR_EMPTY   = ' ';
00037 
00038 
00039 /* buffer layout:
00040  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
00041  |\r|m |e |s |s |a |g |e |: |  || |= |= |= |= |= |> |  |  || |9 |9 |9 |% |
00042  +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
00043   ^                             ^                                         ^
00044   |                             |                                         |
00045   +--- lineBuffer               +--- barStart                bufferEnd ---+
00046 */
00047 
00048 
00049 TtyProgressTool::~TtyProgressTool()
00050 { 
00051   clear_all();
00052 }
00053 
00054 void TtyProgressTool::clear_all()
00055 {
00056     // free allocated buffers 
00057     // (messages were created with strdup (CubitUtil::util_strdup),
00058     // which uses malloc so use free() (CubitUtil::util_strdup_free())).
00059   delete [] lineBuffer; 
00060   if (firstMessage) 
00061     CubitUtil::util_strdup_free(firstMessage);
00062   if (secondMessage)
00063     CubitUtil::util_strdup_free(secondMessage);
00064   
00065     // zero data so calls to step or percent fail.
00066   myRange = 0;
00067   prevWidth = 0;
00068   lineBuffer = barStart = bufferEnd = 0;
00069   firstMessage = secondMessage = 0;
00070 }
00071 
00072 
00073 bool TtyProgressTool::update()
00074 {
00075     // Minimum/maximum space for progress bar.  (Add 2 to constants
00076     // for the leading and trailing '|' character.) 
00077   const int MIN_PROGRESS_WIDTH = MIN_PROGRESS_BAR_WIDTH + 2;
00078   const int MAX_PROGRESS_WIDTH = MAX_PROGRESS_BAR_WIDTH + 2;
00079   
00080     // Get terminal width.  Assume 80 if query fails.
00081   int width, height;
00082   if ( !AppUtil::instance()->get_terminal_size(height, width) )
00083     width = 80;
00084 
00085 #ifdef _WIN32
00086     // Windows cmd.exe wraps the line when the last character
00087     // of the line is output, rather than the first character
00088     // after the end of the line.  Stop one short so to avoid
00089     // newlines.
00090   width--;
00091 #endif
00092 
00093     // If terminal width hasn't changed then no update is
00094     // required.  return.
00095   if ( width && width == prevWidth )
00096     return true;
00097   prevWidth = width;
00098   
00099     // If width isn't at least 6 don't try to display anything.
00100     // Clear data and return.
00101   if ( width < 4 )
00102   {
00103     delete [] lineBuffer;
00104     lineBuffer = barStart = bufferEnd = 0;
00105     return false;
00106   }
00107 
00108     // Re-create lineBuffer for the new terminal width if necessary.
00109   if ( !lineBuffer || (bufferEnd - lineBuffer - 1 < width) )
00110   {
00111     delete [] lineBuffer;
00112 #ifdef _WIN32
00113     lineBuffer = new char [width+2];
00114     lineBuffer[width+1] = '\r';
00115 #else
00116     lineBuffer = new char [width+1];
00117 #endif
00118     if ( !lineBuffer )
00119       return false;
00120   }
00121   
00122   bufferEnd = lineBuffer + width + 1;    // one past end of buffer
00123   barStart = 0;              // start of progress bar (null if no progress bar)
00124   char* bar_end = 0;         // end of progress bar
00125 
00126     // Current start and end (fill lineBuffer until start == end)
00127   char* start = lineBuffer;
00128   char* end = bufferEnd;
00129   
00130     // Use \r to move cursor to beginning of line without advancing a line.
00131   *(start++) = '\r'; // leading \r
00132   
00133     // If displaying numeric percent on end of line, allocate 4 spaces
00134     // at end of line and place '%' char in the fourth one.
00135   if (DISPLAY_NUM_PERCENT && (end - start > 3))
00136   {
00137     end[-1] = '%';
00138     end -= 4;
00139   }
00140 
00141     // Copy first message into line
00142   if (firstMessage && (end - start > 2))
00143   {
00144     int len = strlen(firstMessage);
00145       // need to truncate message?
00146     if (len + 2 > end - start)
00147       len = end - start - 2;
00148     memcpy(start, firstMessage, len);
00149     start += len;
00150       // add trailing ": "
00151     *(start++) = ':';
00152     *(start++) = ' ';
00153   }
00154   
00155     // Allocate space for minimum progress bar if sufficient space
00156   if (DISPLAY_PROGRESS_BAR && (end - start > MIN_PROGRESS_WIDTH))
00157   {
00158     bar_end = end;
00159     end -= MIN_PROGRESS_WIDTH;
00160   }
00161   
00162     // Copy second message into line buffer
00163   if (secondMessage && (end - start > 2))
00164   {
00165     int len = strlen(secondMessage);
00166       // need to truncate message?
00167     if (len + 2 > end - start) 
00168       len = end - start - 2;
00169     memcpy(start, secondMessage, len);
00170     start += len;
00171       // add trailing ": "
00172     *(start++) = ':';
00173     *(start++) = ' ';
00174   }
00175   
00176     // Finish progress bar setup (if progress bar is displayed at all)
00177   if (bar_end)
00178   {
00179       // Grow progress bar up to MAX_PROGRESS_WIDTH (or all the 
00180       // remaining space if MAX_PROGRESS_BAR_WIDTH is unset)
00181     int diff = bar_end - start - MAX_PROGRESS_WIDTH;
00182     end = start;
00183     if (MAX_PROGRESS_BAR_WIDTH && diff >  0)
00184       end += diff;
00185     
00186       // Put in bounding '|' chars for progress bar and
00187       // initialize barStart to point *after* the leading '|'
00188     barStart = end;
00189     barStart[0] = PROGRESS_BAR_START;
00190     bar_end[-1] = PROGRESS_BAR_END;
00191     barStart++;
00192   }
00193   
00194     // fill any remaining space with spaces
00195   if (start < end)
00196     memset( start, ' ', end - start );
00197       
00198   return true;
00199 }
00200 
00201 void TtyProgressTool::start( int lower, int upper, 
00202                              const char* s1, const char* s2, 
00203                              CubitBoolean, CubitBoolean )
00204 {
00205     // get rid of any old state if someone forgot to call end().
00206   clear_all();
00207   
00208     // store passed range
00209   myRange = upper - lower;
00210   currentVal = currPercent = 0;
00211   prevWidth = 0;
00212   
00213     // copy passed messages
00214   const char* empty = "";
00215   if (s1)
00216     firstMessage = CubitUtil::util_strdup((char*)s1);
00217   else
00218     firstMessage = CubitUtil::util_strdup((char*)empty);
00219   if (s2)
00220     secondMessage = CubitUtil::util_strdup((char*)s2);
00221 
00222     // generate line buffer
00223   display(0,0);
00224 }
00225 
00226 void TtyProgressTool::step()
00227 {
00228   int new_count = currentVal + 1;
00229   if( myRange > 0 )
00230     display( new_count, 100 * new_count / myRange );
00231 }
00232 
00233 void TtyProgressTool::percent( double p )
00234 {
00235   display( (int)(p * myRange), (int)(100 * p) );
00236 }
00237 
00238 void TtyProgressTool::end()
00239 {
00240     // clear terminal line
00241   if (lineBuffer)
00242   {
00243     memset( lineBuffer + 1, ' ', bufferEnd - lineBuffer - 1 );
00244     fwrite( lineBuffer, bufferEnd - lineBuffer, 1, stdout );
00245     putchar('\r');
00246     fflush( stdout );
00247   }
00248 
00249     // clear internal data  
00250   clear_all();
00251 }
00252 
00253 void TtyProgressTool::check_interrupt()
00254 {
00255 }
00256 
00257 void TtyProgressTool::display( int new_count, int new_percent )
00258 {
00259   int prev_width = prevWidth;
00260   bool do_output = false;
00261   
00262     // check for change in terminal width and update if necessary.
00263   if (!update())
00264     return;
00265   
00266     // if tty size changed or percent changed, need to update  
00267   if (prev_width != prevWidth || new_percent != currPercent)
00268   {
00269     currPercent = new_percent;
00270     do_output = true;
00271   }
00272 
00273     // calculate start of numeric percent in lineBuffer
00274   char* pctStart = bufferEnd;
00275   if (DISPLAY_NUM_PERCENT)
00276     pctStart -= 4; // start of numeric percent
00277   
00278     // check if the progress bar output is changing, and if
00279     // so set do_output to true
00280   size_t bar_width = 0, bar_drawn = 0, old_drawn;
00281   if (barStart && myRange > 0)
00282   {
00283     bar_width = pctStart - barStart - 1;
00284     bar_drawn = new_count >= myRange ? bar_width :
00285                 new_count <= 0       ? 0         :
00286                 bar_width * new_count / myRange;
00287     old_drawn = currentVal >= myRange ? bar_width :
00288                 currentVal <= 0       ? 0         :
00289                 bar_width * currentVal / myRange;
00290     if (bar_drawn != old_drawn)
00291       do_output = true;
00292   }
00293   currentVal = new_count;
00294   
00295   if (DISPLAY_NUM_PERCENT && do_output)
00296   {
00297       // normalize numeric percent to values that can be displayed
00298     int pct = currPercent;
00299     if (pct < 0)
00300       pct = 0;
00301     else if(pct > 999)
00302       pct = 999;
00303 
00304       // display numeric percent in last three chars of line
00305     pctStart[2] = (char)('0' + pct % 10);
00306     pctStart[1] = pctStart[0] = ' ';
00307     for ( int i = 1; i >= 0 && pct > 9; i-- )
00308     {
00309       pct /= 10;
00310       pctStart[i] = (char)('0' + pct % 10);
00311     }
00312   }
00313   
00314     // if progress bar is to be displayed, update it.
00315   if (barStart && do_output)
00316   {
00317       // write '=' for filled section of bar
00318     if (bar_drawn > 1)
00319       memset(barStart, PROGRESS_BAR_FILLED, bar_drawn - 1);
00320       // write '>' between filled and unfilled sections of bar
00321     if (bar_drawn > 0)
00322       barStart[bar_drawn - 1] = PROGRESS_BAR_CURRENT;
00323       // write spaces in unfilled part of bar
00324     if (bar_drawn < bar_width)
00325       memset(barStart + bar_drawn, PROGRESS_BAR_EMPTY, bar_width - bar_drawn);
00326   }
00327   
00328     // write the buffer to the terminal
00329   if (do_output)
00330   {
00331     fwrite( lineBuffer, bufferEnd - lineBuffer, 1, stdout );
00332     fflush( stdout );
00333   }
00334 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines