cgma
|
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 }