Below is the file 'ui.cc' from this revision. You can also download the file.

// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*-
// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
// all rights reserved.
// licensed to the public under the terms of the GNU GPL (>= 2)
// see the file COPYING for details

// this file contains a couple utilities to deal with the user
// interface. the global user_interface object 'ui' owns clog, so no
// writing to it directly!

#include "config.h"
#include "platform.hh"
#include "sanity.hh"
#include "ui.hh"
#include "transforms.hh"
#include "constants.hh"

#include <iostream>
#include <iomanip>
#include <boost/lexical_cast.hpp>

using namespace std;
using boost::lexical_cast;
struct user_interface ui;

ticker::ticker(string const & tickname, std::string const & s, size_t mod,
    bool kilocount) :
  ticks(0),
  mod(mod),
  kilocount(kilocount),
  name(tickname),
  shortname(s)
{
  I(ui.tickers.find(tickname) == ui.tickers.end());
  ui.tickers.insert(make_pair(tickname,this));
}

ticker::~ticker()
{
  I(ui.tickers.find(name) != ui.tickers.end());
  if (ui.some_tick_is_dirty)
    {
      ui.write_ticks();
    }
  ui.tickers.erase(name);
  ui.finish_ticking();
}

void
ticker::operator++()
{
  I(ui.tickers.find(name) != ui.tickers.end());
  ticks++;
  ui.some_tick_is_dirty = true;
  if (ticks % mod == 0)
    ui.write_ticks();
}

void
ticker::operator+=(size_t t)
{
  I(ui.tickers.find(name) != ui.tickers.end());
  size_t old = ticks;

  ticks += t;
  if (t != 0)
    {
      ui.some_tick_is_dirty = true;
      if (ticks % mod == 0 || (ticks / mod) > (old / mod))
        ui.write_ticks();
    }
}


tick_write_count::tick_write_count() : last_tick_len(0)
{
}

tick_write_count::~tick_write_count()
{
}

void tick_write_count::write_ticks()
{
  string tickline1, tickline2;
  bool first_tick = true;

  tickline1 = "monotone: ";
  tickline2 = "\rmonotone:";

  unsigned int width;
  unsigned int minwidth = 7;
  for (map<string,ticker *>::const_iterator i = ui.tickers.begin();
       i != ui.tickers.end(); ++i)
    {
      width = 1 + length(utf8(i->second->name));
      if (!first_tick)
        {
          tickline1 += " | ";
          tickline2 += " |";
        }
      first_tick = false;
      if (length(utf8(i->second->name)) < minwidth)
        {
          tickline1.append(minwidth - length(utf8(i->second->name)),' ');
          width += minwidth - length(utf8(i->second->name));
        }
      tickline1 += i->second->name;

      string count;
      if (i->second->kilocount && i->second->ticks >= 10000)
        { // automatic unit conversion is enabled
          float div;
          const char *message;
          if (i->second->ticks >= 1048576) {
          // ticks >=1MB, use Mb
            div = 1048576;
          // xgettext: mebibytes (2^20 bytes)
            message = N_("%.1f M");
          } else {
          // ticks <1MB, use kb
            div = 1024;
          // xgettext: kibibytes (2^10 bytes)
            message = N_("%.1f k");
          }
          // we reset the mod to the divider, to avoid spurious screen updates
          i->second->mod = static_cast<int>(div / 10.0);
          count = (F(message) % (i->second->ticks / div)).str();
        }
      else
        {
          // xgettext: bytes
          count = (F("%d") % i->second->ticks).str();
        }

      if (length(utf8(count)) < width)
        {
          tickline2.append(width - length(utf8(count)),' ');
        }
      else if (length(utf8(count)) > width)
        {
          // FIXME: not quite right, because substr acts on bytes rather than
          // characters; but there are always more bytes than characters, so
          // at worst this will just chop off a little too much.
          count = count.substr(length(utf8(count)) - width);
        }
      tickline2 += count;
    }

  if (!ui.tick_trailer.empty())
    {
      tickline2 += " ";
      tickline2 += ui.tick_trailer;
    }

  size_t curr_sz = length(utf8(tickline2));
  if (curr_sz < last_tick_len)
    tickline2.append(last_tick_len - curr_sz, ' ');
  last_tick_len = curr_sz;

  unsigned int tw = terminal_width();
  if(!ui.last_write_was_a_tick)
    {
      if (tw && length(utf8(tickline1)) > tw)
        {
          // FIXME: may chop off more than necessary (because we chop by
          // bytes, not by characters)
          tickline1.resize(tw);
        }
      clog << tickline1 << "\n";
    }
  if (tw && length(utf8(tickline2)) > tw)
    {
      // first character in tickline2 is "\r", which does not take up any
      // width, so we add 1 to compensate.
      // FIXME: may chop off more than necessary (because we chop by
      // bytes, not by characters)
      tickline2.resize(tw + 1);
    }
  clog << tickline2;
  clog.flush();
}

void tick_write_count::clear_line()
{
  clog << endl;
}


tick_write_dot::tick_write_dot()
{
}

tick_write_dot::~tick_write_dot()
{
}

void tick_write_dot::write_ticks()
{
  static const string tickline_prefix = "monotone: ";
  string tickline1, tickline2;
  bool first_tick = true;

  if (ui.last_write_was_a_tick)
    {
      tickline1 = "";
      tickline2 = "";
    }
  else
    {
      tickline1 = "monotone: ticks: ";
      tickline2 = "\n" + tickline_prefix;
      chars_on_line = tickline_prefix.size();
    }

  for (map<string,ticker *>::const_iterator i = ui.tickers.begin();
       i != ui.tickers.end(); ++i)
    {
      map<string,size_t>::const_iterator old = last_ticks.find(i->first);

      if (!ui.last_write_was_a_tick)
        {
          if (!first_tick)
            tickline1 += ", ";

          tickline1 +=
            i->second->shortname + "=\"" + i->second->name + "\""
            + "/" + lexical_cast<string>(i->second->mod);
          first_tick = false;
        }

      if (old == last_ticks.end()
          || ((i->second->ticks / i->second->mod)
              > (old->second / i->second->mod)))
        {
          chars_on_line += i->second->shortname.size();
          if (chars_on_line > guess_terminal_width())
            {
              chars_on_line = tickline_prefix.size() + i->second->shortname.size();
              tickline2 += "\n" + tickline_prefix;
            }
          tickline2 += i->second->shortname;

          if (old == last_ticks.end())
            last_ticks.insert(make_pair(i->first, i->second->ticks));
          else
            last_ticks[i->first] = i->second->ticks;
        }
    }

  clog << tickline1 << tickline2;
  clog.flush();
}

void tick_write_dot::clear_line()
{
  clog << endl;
}


user_interface::user_interface() :
  last_write_was_a_tick(false),
  t_writer(0)
{
#ifndef WIN32
  clog.sync_with_stdio(false);
#endif
  clog.unsetf(ios_base::unitbuf);
  if (have_smart_terminal())
    set_tick_writer(new tick_write_count);
  else
    set_tick_writer(new tick_write_dot);
}

user_interface::~user_interface()
{
  delete t_writer;
}

void
user_interface::finish_ticking()
{
  if (tickers.size() == 0 &&
      last_write_was_a_tick)
    {
      tick_trailer = "";
      t_writer->clear_line();
      last_write_was_a_tick = false;
    }
}

void
user_interface::set_tick_trailer(string const & t)
{
  tick_trailer = t;
}

void
user_interface::set_tick_writer(tick_writer * t)
{
  if (t_writer != 0)
    delete t_writer;
  t_writer = t;
}

void
user_interface::write_ticks()
{
  t_writer->write_ticks();
  last_write_was_a_tick = true;
  some_tick_is_dirty = false;
}

void
user_interface::warn(string const & warning)
{
  if (issued_warnings.find(warning) == issued_warnings.end())
    inform("warning: " + warning);
  issued_warnings.insert(warning);
}

void
user_interface::fatal(string const & fatal)
{
  inform(F("fatal: %s\n"
           "this is almost certainly a bug in monotone.\n"
           "please send this error message, the output of 'monotone --full-version',\n"
           "and a description of what you were doing to %s.\n")
         % fatal % PACKAGE_BUGREPORT);
}


static inline string
sanitize(string const & line)
{
  // FIXME: you might want to adjust this if you're using a charset
  // which has safe values in the sub-0x20 range. ASCII, UTF-8,
  // and most ISO8859-x sets do not.
  string tmp;
  tmp.reserve(line.size());
  for (size_t i = 0; i < line.size(); ++i)
    {
      if ((line[i] == '\n')
          || (static_cast<unsigned char>(line[i]) >= static_cast<unsigned char>(0x20)
              && line[i] != static_cast<char>(0x7F)))
        tmp += line[i];
      else
        tmp += ' ';
    }
  return tmp;
}

void
user_interface::ensure_clean_line()
{
  if (last_write_was_a_tick)
    {
      write_ticks();
      t_writer->clear_line();
    }
  last_write_was_a_tick = false;
}

void
user_interface::inform(string const & line)
{
  string prefixedLine;
  prefix_lines_with(_("monotone: "), line, prefixedLine);
  ensure_clean_line();
  clog << sanitize(prefixedLine) << endl;
  clog.flush();
}

unsigned int
guess_terminal_width()
{
  unsigned int w = terminal_width();
  if (!w)
    w = constants::default_terminal_width;
  return w;
}

const locale &
get_user_locale()
{
  // this is awkward because if LC_CTYPE is set to something the
  // runtime doesn't know about, it will fail. in that case,
  // the default will have to do.
  static bool init = false;
  static locale user_locale;
  if (!init)
    {
      init = true;
      try
        {
          user_locale = locale("");
        }
      catch( ... )
        {}
    }
  return user_locale;
}